среда, 1 августа 2012 г.

oop: object construction

Конструктор
Инициализатор
Значения полей по умолчанию

Конструктор: this(...)

     Класс-предок
    Факт: У класса всегда есть один и только один непосредственный класс-предок.
    Магия: если предок не объявлен явно (extends), то компилятор подставляет java.lang.Object.



    Переопределение конструкторов и this(...)
    Можно вызвать "цепочку" переопределенных конструкторов.
    class A {
        A() {this(0);};
        A(int k) {};
    }   
    или

    class A {
        A() {};
        A(int k) {this();};
    }   

    Но в ней не должно быть рекурсии прямой


    class A { // не компилируется
        A() {this();};
    }   

    или косвенной, компилятор зорко следит за этим.

    class A { // не компилируется
        A() {this(0);};
        A(int ki) {this();};
    }   






    Обратите внимание на порядок вызова в консоли:


    class A {
        A() {this(0); System.out.print("A()");};
        A(int k) {System.out.print("A(int)");};
    } 
    class App {
        public static void main(String[] args) {
            new A();
        }
    }  
    >> A(int)A()



    Так как вызов this(...) должен происходить только первой строкой:

    class A {
        A() {System.out.print("A()");this(0);};
        A(int k) {System.out.print("A(int)");};
    } 


    Однако туда можно втиснуться (плохая практика?):

    class A {
        A() {this(f());};
        A(int k) {System.out.print("A(int)");};
        int f() {System.out.print("f()"); return 0;}
    } 

    class App {
        public static void main(String[] args) {
            new A();
        }
    }  
    >> f()A(int)




    this(...) можно вызывать только из другого конструктора (а не метода, скажем):

    class A {
        A(int k) {};
        void f(){this(0);}         
    } 





    Если явно вызвали какой-то конструктор, то конструктор по умолчанию не вызовется:

    class A {
        A() {System.out.print("A()");};
        A(int k) {System.out.print("A(int)");};
    } 
    class App {
        public static void main(String[] args) {
            new A(0);
        }
    }  
    >> A(int)




    Как увидим дальше это не верно для конструктора предка.



    Конструктор по умолчанию
    У класса всегда есть как минимум один конструктор. 
    Магия: если нет явных объявлений конструкторов, то создается конструктор по умолчанию (конструктор без аргументов, с пустым "телом" и областью видимости как у класса):

    class A {}   
    class App {
        public static void main(String[] args) {
            new A(); // OK
        }
    }

    Что полностью эквивалентно:


    class A {
        A() {}
    }   
    class App {
        public static void main(String[] args) {
            new A(); // OK
        }
    }



    Если у класса есть хотя бы один явно объявленный конструктор, то конструктор по умолчанию не добавляется:

    class A {
        A(int k) {}
    }   
    class App {
        public static void main(String[] args) {
            new A(); // не компилируется
        }
    }




    Надо так:

    class A {
        A(int k) {}
    }   
    class App {
        public static void main(String[] args) {
            new A(0); 
        }
    }




    Или так:

    class A {
        A() {}
        A(int k) {}
    }   
    class App {
        public static void main(String[] args) {
            new A(); 
        }
    }



    Отсутствие перекрытия предков и super(...)
    Конструкторы, в отличии от методов, не наследуются (и у B нет конструктора с аргументом int): 

    class A {
        A() {}
        A(int k) {}
    }   

    class B extends A {}   

    class App { // не компилируется
        public static void main(String[] args) {
            new B(0); 
        }
    }



    Методы, в отличии от конструкторов наследуются(у B есть методы f() и g(int) - он их получил от A): 

    class A {
        f() {}
        g(int k) {}
    }   

    class B extends A {}   

    class App { // не компилируется
        public static void main(String[] args) {
            new B().f(); 
            new B().g(0); 
        }
    }





    XXX
    Каждая цепочка конструкторов должна к конце концов вызвать один и только один раз конструктор предка: 
    class A {
        A(int k) {}
    }   
    class B extends A {}

    class App { // не компилируется
        public static void main(String[] args) {
            new B(0); 
        }
    }



    Магия: если в каком либо из конструкторов не объявлен вызов конкретного конструктора предка, то вызывается конструктор предка без аргументов. Если такого не имеется - происходит ошибка компиляции.
    Мы можем в конструкторе объекта явно выбирать, какой из конструкторов предка вызвать при помощи super(...).
    Если явно вызывается конструктор предка super(...), то он должен идти первой строкой. Вызов второго (третьего, ...) конструктора предка недопустим и приведет к ошибке компиляции. Если вызов идет не первой строкой - ошибка компиляции.


http://www.stylusstudio.com/xquery_primer.html
http://tamanmohamed.blogspot.com/2012/06/jdk7-part-1-java-7-dolphin-new-features.html

http://dou.ua/adv/
http://dou.ua/partners/

http://habrahabr.ru/post/151193/
http://leonwolf.livejournal.com/347761.html
http://leonwolf.livejournal.com/456839.html

http://docstore.mik.ua/orelly/java-ent/security/ch04_03.htm

http://www.javabeat.net/2007/04/the-java-6-0-compiler-api/


Факты     

   
    Класс-предок
    У класса всегда есть один и только один непосредственный класс-предок. 
    Магия: если предок не объявлен явно (extends), то компилятор подставляет java.lang.Object.
    Если предок объявлен явно, то компилятор НЕ подставляет java.lang.Object.
class X extends Object {}
class Y {}
class Z extends Thread {}
class Test {
    public static void main(String[] args) {
        // проверим при помощи Reflection API
        Class[] classes = {X.class,Y.class};
        for (Class clazz : classes) {
            Class parent = clazz.getSuperclass();
            System.out.println(clazz + " extends " + parent);
        }
        // проверим при помощи компилятора 
        Object o1 = new X();
        Object o2 = new Y();
        Object o3 = new Z();
    }
}
Код выведет:
>> class X extends class java.lang.Object
>> class Y extends class java.lang.Object
>> class Z extends class java.lang.Thread
У X непосредственный предок объявлен явно Object - компилятор не тронул, у Y непосредственный предок не объявлен и компилятор подставил Object, у Z непосредственный предок объявлен явно Thread - компилятор не тронул.

    Двух и более предков объявить нельзя (в java нет множественного наследования).
class X {}

class Y {}

class Z extends X, Y {} //ошибка компиляции




    Предком может быть только класс, но не интерфейс.
interface I {}
class X extends I {} // ошибка компиляции


    Если класс не объявляет явно непосредственного класса предка но наследует один или более интерфейсов, то все равно компилятор подставляет непосредственным классом-предком java.lang.Object.



interface I {}

interface J {}

class X implements I {} 

class Y implements I, J {} 






эквивалентно


interface I {}

interface J {}

class X extends Object implements I {} 

class Y extends Object implements I, J {} 







    Следствие #1: у каждого класса одним из предков будет java.lang.Object. Так как либо твой непосредственный предок Object, либо другой класс. У другого класса непосредственный предок Object либо третий класс. И так далее. Так как у наследования невозможны циклы и количество классов в JVM ограничено, то одним из предков будет Object.

    Следствие #2: экземпляр каждого объекта можно привести к java.lang.Object. Так как Object - один из предков.
    Следствие #3: у каждого экземпляра каждого класса есть методы getClass(), hashCode(), toString(), equals(Object), wait(), wait(long), wait(long, long), notify(), notifyAll(), clone(), finalize()Так как Object - один из предков.
    Следствие #4: вы должны знать, что делают эти методы. 

    Конструктор по умолчанию

    У класса всегда есть как минимум один конструктор. 
    Магия: если нет явных объявлений конструкторов, то создается конструктор по умолчанию (конструктор без аргументов).
    Если у класса есть хотя бы один явно объявленный конструктор, то конструктор по умолчанию не добавляется.


import java.lang.reflect.Constructor;
import java.util.Arrays;
class DefaultConstructors {
    public static void main(String[] args) {
        // проверка при помощи Reflection API
        Class[] classes = {A.class, B.class, C.class, D.class};
        for (Class clazz : classes) {
            Constructor[] c = clazz.getDeclaredConstructors();
            System.out.println(Arrays.toString(c));
        }
        // проверка при помощи компилятора
        new A();
        new B();
        new C(); // ошибка компиляции
        new D();
    }
}
class A {}
class B {
    B() {}
}
class C {
    C(int x) {}
}
class D {
    D() {}
    D(int x) {}
}

если закомментировать строку "new C();", то код выведет

>> [A()]
>> [B()]
>> [C(int)]
>> [D(), D(int)]
Классу A компилятор "дописал" конструктор по умолчанию A(), так как у него не было ни одного конструктора.
Классу B компилятор не "дописал" конструктор по умолчанию B(), так как у него есть конструктор B().
Классу С компилятор не "дописал" конструктор по умолчанию С(), так как у него есть конструктор C(int).
Классу D компилятор не "дописал" конструктор по умолчанию D(), так как у него есть два конструктора D() и D(int).


    Конструкторы предков и super(...)

    Конструкторы, в отличии от методов, не наследуются.


class A {
    A(int i) {}
    A(Object o) {}
    void f() {}
    int g(int i) {return i + 1;}
}
class B extends A {}

public class ConstructorInheritance {
    public static void main(String[] args) {
        new B().f();
        new B().g(0);

        new A();
        new A(0);
        new A(null);
        new B();
        new B(0); // ошибка компиляции
        new B(null);  // ошибка компиляции
    }
}

Класс B унаследовал методы f() и g(int) от A, но не унаследовал конструкторы A(int) и A(Object). A() и B() - конструкторы по умолчанию от компилятора.

    Процесс конструирования объекта не зависит от того кому будет присвоена или передана ссылка.


class A {}
class B extends A {}
class C extends B {}
class X {
    void f(A a) {}; 
    void g(Object o){}
}

new C();
C c = new C();
B b = new C();
A a = new A();
new X().f(new C());
new X().g(new C());
Все 6 последних строк (содержащих new C()) пройдут идентично для процесса конструирования. Когда конструируется объект - это процесс не зависит от того что ПОТОМ я сделаю с ссылкой на него.

    Процесс конструирования объекта происходит "сверху-вниз": конструируется самый верхний предок, потом ниже, ... и потом вызывается конструктор объекта. Никаким образом невозможно пропустить конструктор одного из предков.


    Магия: если в каком либо из конструкторов не объявлен вызов конкретного конструктора предка, то вызывается конструктор предка без аргументов. Если такого не имеется - происходит ошибка компиляции. 
    Мы можем в конструкторе объекта явно выбирать, какой из конструкторов предка вызвать при помощи super(...).
    Если явно вызывается конструктор предка super(...), то он должен идти первой строкой. Вызов второго (третьего, ...) конструктора предка недопустим и приведет к ошибке компиляции. Если вызов идет не первой строкой - ошибка компиляции.


из
class A {}
class B extends A {}
компилятор сделает


class A extends Object {
    A() {super();}
}
class B extends A {
    B() {super();}
}


    Другие конструкторы объекта и this(...)