пятница, 20 июня 2014 г.

Инициализация статических блоков. Хитрожопый пример.

Что будет выведено при выполнении данного кода?
class A {
    static {
        System.out.print("A");
    }
}
 
class B extends A {
    public static int b = 2;
 
    static {
        System.out.print("B");
    }
}
 
class C extends B {
    static {
        System.out.print("C");
        System.exit(0);
    }
}
 
public class StaticInitTest {
    public static void main(String[] args) {
        System.out.println(C.b);
    }
}
Ожидаемым ответом было ABC: C extends B, B extends A; значит, инициализируем статические блоки A, B, C, где и попадаем на System.exit(0).
Но это не так. Класс C не будет загружен вообще. Потому что:
 - не создавался объект класса С
 - не вызывались его статические методы
 - не было обращения к его статическим полям
А как же System.out.println(C.b)?
У класса С нет своих статических полей. Поле b принадлежит классу B, а не C, в этом и фокус. Класс B будет загружен, а к классу C (вопреки интуиции=)) обращения не было, его static блок выполнен не будет.
Правильный ответ - AB2.

P.S. Вопрос взят у Seregs12324 :: quizful.net

вторник, 10 июня 2014 г.

Java 8. Interfaces. Default methods. Static methods in interfaces.

В  Java 8 появилась возможность добавлять в интерфейсы не только абстрактные, но и содержащие реализацию методы.
interface Informative {
    default void tellAboutYourself() {
        System.out.println("I am " + getClass().getCanonicalName());
    }
}
 
class Board implements Informative{
    public static void main(String[] args) {
        new Board().tellAboutYourself();
    }
}
Output:
I am Board

Возможная польза от использования дефолтных методов:
  • Можно расширять существующие интерфейсы, не трогая их реализации
  • Функционал, общий для потомков, можно вынести в интерфейс
Однако, если класс реализует несколько интерфейсов с одноименными методами, хотя бы один из которых - дефолтный, то в этом классе придется переопределять метод.
interface Informative {
    default void tellAboutYourself() {
        System.out.println("I am " + getClass());
    }
}
interface Communicable {
    default void tellAboutYourself() {
        System.out.println("My name is " + getClass());
    }
} 
class Board implements Informative, Communicable {
    //compile error:
    //class Board inherits unrelated defaults for tellAboutYourself()
    // from types Informative and Communicable
}
interface Informative {
    default void tellAboutYourself() {
        System.out.println("I am " + getClass());
    }
}
interface Communicable {
    void tellAboutYourself();
}
class Board implements Informative, Communicable {
    //compile error:
    // Board is not abstract and does not override 
    // abstract method tellAboutYourself() in Communicable    
}

Зато при переопределении можно вызвать методы всех родительских интерфейсов.

interface Communicable {
    default void tellAboutYourself() {
        System.out.println("I am Communicable interface");
    }
}
 
interface Informative {
    default void tellAboutYourself() {
        System.out.println("I am Informative interface");
    }
}
 
class Board implements Informative , Communicable{
    @Override
    public void tellAboutYourself() {
        Informative.super.tellAboutYourself();
        Communicable.super.tellAboutYourself();
        System.out.println("My name is Board");
    }
}
У статических методов интерфейсов я нашла только одно отличие от статических методов классов: их нельзя вызвать через объект:
interface Informative {
    static void tellAboutYourself() {
        System.out.println("I am Informative interface");
    }
}
class Board implements Informative {
    public static void main(String[] args) {
        Informative informative = new Board();
        informative.tellAboutYourself();       //illegal static 
        // interface method call. The receiver expression 
        // should be replaced with the type qualifier 'Informative'
    }
}
class Board {
    public static void tellAboutYourself() {
        System.out.println("I am Board class");
    }
}
 
class BillBoard extends Board {
    public static void main(String[] args) {
        new BillBoard().tellAboutYourself();
    }
}
А вообще - хорошие штуки добавили, чо.

понедельник, 9 июня 2014 г.

Виды полиморфизма


  • Ситуативный (ad-hoc) полиморфизм: различные реализации метода с одним именем для ограниченного набора заданных аргументов и их комбинаций.

class Student {
    public void say(String text) {
        System.out.println(text);
    }
 
    public void say() {
        say("Arte et humanitate, labore et scientia!");
    }
 
    public static void main(String[] args) {
        Student student = new Student();
        student.say();
        student.say("Ignorantia nоn est argumentum!");
    }
}
Output:
Arte et humanitate, labore et scientia!
Ignorantia nоn est argumentum!

  • Параметрический полиморфизм: определение функции или типа данных обобщенно, чтобы работа со значениями происходила единообразно. 

class Wrapper<T> {
    private T t;
 
    public void wrap(T t) {
        this.t = t;
        System.out.println("I'm wrapping " + t.getClass());
    }
 
    public static void main(String[] args) {
        Wrapper wrapper = new Wrapper();
        wrapper.wrap("Book");
        wrapper.wrap(5);
        wrapper.wrap(new Socket());
    }
}
Output:

I'm wrapping class java.lang.String
I'm wrapping class java.lang.Integer
I'm wrapping class java.net.Socket

  • Полиморфизм подтипов (включения): если функция определена для типа SyperType, то её корректно использовать и для всех типов SybType extends SuperType (SuperType :> SubType).

class Int {
    public static void sayIntValue(Number number) {
        System.out.println("int value of " + number + " is " + number.intValue());
    }
 
    public static void main(String[] args) {
        sayIntValue(new Integer(10));
        sayIntValue(new Double(36.6));
        sayIntValue(new Float(22.1));
    }
}
Output:
int value of 10 is 10
int value of 36.6 is 36
int value of 22.1 is 22

Порядок инициализации в Java


  1. От самого базового до производного класса инициализируются статические переменные и статические блоки в порядке их определения
  2. От самого базового до производного класса инициализируются нестатические переменные и блоки в порядке их определения и вызывается конструктор класса.

воскресенье, 8 июня 2014 г.

Полиморфизм в Java. Динамическое и статическое связывание. Инициализация объектов. Поведение полиморфных методов при вызове из конструкторов.

В языках программирования и теории типов полиморфизмом называется единообразная обработка разнотипных данных. Целью полиморфизма, применительно к объектно-ориентированному программированию, является использование одного имени для задания общих для класса действий.

В языке Java объектные переменные являются полиморфными (polymorphic). Например:


class King {
    public static void main(String[] args) {
        King king = new King();
        king = new AerysTargaryen();
        king = new RobertBaratheon();
    }
}
 
class RobertBaratheon extends King {
}
 
class AerysTargaryen extends King {
}

Переменная типа King может ссылаться как на объект типа King, так и на объект любого подкласса King.

Возьмем следующий пример:


class King {
    public void speech() {
        System.out.println("I'm the King of the Andals!");
    }
 
    public void speech(String quotation) {
        System.out.println("Wise man said: " + quotation);
    }
 
    public void speech(Boolean speakLoudly){
        if (speakLoudly)
            System.out.println("I'M THE KING OF THE ANDALS!!!11");
        else
            System.out.println("i'm... the king...");
 
    }
}
 
class AerysTargaryen extends King {
    @Override
    public void speech() {
        System.out.println("Burn them all...");
    }
 
    @Override
    public void speech(String quotation) {
        System.out.println(quotation+" ... And now burn them all!");
    }
 
}
 
class Kingdom {
    public static void main(String[] args) {
        King king = new AerysTargaryen();
        king.speech("Homo homini lupus est");
    }
}
 

Что происходит, когда вызывается метод, принадлежащий объекту king?

1. Компилятор проверяет объявленный тип объекта и имя метода, нумерует все методы с именем speech в классе AerusTargarien и все открытые методы speech в суперклассах AerusTargarien. Теперь компилятору известны возможные кандидаты при вызове метода.

2. Компилятор определяет типы передаваемых в метод аргументов. Если найден единственный метод, сигнатура которого совпадает с аргументами, происходит вызов. Этот процесс называется разрешением перегрузки (overloading resolution). Т.е. при вызове  king.speech("Homo homini lupus est") компилятор выберет метод speech(String quotation), а не speech().

Если компилятор находит несколько методов  с подходящими параметрами (или ни одного), выдается сообщение об ошибке.






Теперь компилятор знает имя и типы параметров метода,подлежащего вызову.

3. В случае, если вызываемый метод является private, static, final или конструктором, используется статическое связывание (early binding). В остальных случаях метод, подлежащий вызову, определяется по фактическому типу объекта, через который происходит вызов. Т.е. во время выполнения программы используется динамическое связывание (late binding).
   

4. Виртуальная машина заранее создает таблицу методов для каждого класса, в которой перечисляются сигнатуры всех методов и фактические методы, подлежащие вызову. 

Таблица методов для класса King выглядит так:

  • speech() - King.speech()
  • speech(String quotation) - King.speech(String quotation)
  • speech(Boolean speakLoudly) - King.speech(Boolean speakLoudly)
А для класса AerysTargaryen - так:

  • speech() - AerysTargaryen.speech()
  • speech(String quotation) - AerysTargaryen.speech(String quotation)
  • speech(Boolean speakLoudly) - King.speech(Boolean speakLoudly)

Методы, унаследованные от Object, в данном примере игнорируются.

При вызове  king.speech():

  1. Определяется фактический тип переменной king. В данном случае это AerysTargaryen.
  2. Виртуальная машина определяет класс, к которому принадлежит метод speech() 
  3. Происходит вызов метода.
Связывание всех методов в Java осуществляется полиморфно, через позднее связывание. Динамическое связывание обладает одной важной особенностью: оно позволяет модифицировать программы без перекомпиляции их кодов. Это делает программы динамически расширяемыми (extensible).

А что произойдет, если вызвать в конструкторе динамически связываемый метод конструируемого объекта? Например:


class King {
    King() {
        System.out.println("Call King constructor");
        speech();   //polymorphic method overriden in AerysTargaryen 
    } 
    public void speech() {
        System.out.println("I'm the King of the Andals!");
    }
}
 
class AerysTargaryen extends King {
    private String victimName;
 
    AerysTargaryen() {
        System.out.println("Call Aerys Targaryen constructor");
        victimName = "Lyanna Stark";
        speech();
    }
 
    @Override
    public void speech() {
        System.out.println("Burn " + victimName + "!");
    }
}
 
class Kingdom {
    public static void main(String[] args) {
        King king = new AerysTargaryen();
    }
}
 

Результат:


Call King constructor
Burn null!
Call Aerys Targaryen constructor
Burn Lyanna Stark!

Конструктор базового класса всегда вызывается в процессе конструирования производного класса. Вызов автоматически проходит вверх по цепочке наследования, так что в конечном итоге вызываются конструкторы всех базовых классов по всей цепочке наследования. 

Это значит, что при вызове конструктора new AerysTargaryen() будут вызваны:

  1. new Object()
  2. new King()
  3. new AerysTargaryen()

По определению, задача конструктора — дать объекту жизнь. Внутри любого конструктора объект может быть сформирован лишь частично — известно только то, что объекты базового класса были проинициализированы. Если конструктор является лишь очередным шагом на пути построения объекта класса, производного от класса данного конструктора, «производные» части еще не были инициализированы на момент вызова текущего конструктора.
Однако динамически связываемый вызов может перейти во «внешнюю» часть иерархии, то есть к производным классам. Если он вызовет метод производного класса в конструкторе, это может привести к манипуляциям с неинициализированными данными, что мы и видим в результате работы данного примера.
Результат работы программы обусловлен выполнение алгоритма иницализации объекта:
  1. Память, выделенная под новый объект, заполняется двоичными нулями.
  2. Конструкторы базовых классов вызываются в описанном ранее порядке. В этот момент вызывается переопределенный метод speech() (да, перед вызовом конструктора класса AerysTargaryen), где обнаруживается, что переменная victimName равна null из-за первого этапа.
  3. Вызываются инициализаторы членов класса в порядке их определения.
  4. Исполняется тело конструктора производного класса.
В частности из-за таких поведенческих моментов стоит придерживаться следующего правила написания конструкторов:
 - выполняйте в конструкторе лишь самые необходимые и простые действия по инициализации объекта
 - по возможности избегайте вызова методов, не определенных как private или final (что в данном контексте одно и то же). 


Использованы материалы:
  1. Eckel B. - Thinking in Java4th Edition - Chapter 8
  2. Cay S. Horstmann, Gary Cornell - Core Java 1 - Chapter 5
  3. Wikipedia 

среда, 4 июня 2014 г.

Транзакции. ACID.

Транзакции являются одним из фундаментальных концептов всех СУБД. Сущность транзакции состоит в связывании нескольких шагов в одну операцию по принципу все-или-ничего. Внутренние промежуточные состояния между шагами не видны для других конкурирующих транзакций и если во время выполнения транзакции случится ошибка, которая помешает транзакции завершится, то в базе данных никаких изменений сделано не будет.

Требования к транзакционной системе обозначаются акронимом ACID. Они дают представление о принципах, обеспечивающих надежную  и предсказуемую работу БД.