пятница, 14 июня 2013 г.

Как я пытался понять смысл метода finalize

Недавно меня пригласили в одну компанию на собеседование на должность Java-программиста. На собеседовании зашла речь о работе метода finalize. Я имел лишь поверхностное представление о работе этого метода и не смог дать достойного его описания интервьюверам. Поэтому после собеседования я должен был провести работу над ошибками и во всем разобраться.

Мои знания ограничивались тем, что метод finalize вызывается в момент, когда сборщик мусора начинает утилизировать объект. И я не совсем понимал для чего он служит. Я думал, что это что-то типа деструктора, в котором можно освобождать определенные ресурсы после того, как они больше не нужны, причем даже ресурсы, которые хранятся в других объектах, что не верно.

Так вот, первое, что требовалось понять - назначение.

Предназначен этот метод для автоматического освобождения системных ресурсов, занимаемых объектом, на котором будет данный метод вызван. Это кажется удобным, чтобы не помнить постоянно, например, что мы должны закрыть соединение с каким-то ресурсом, когда оно больше не требуется.

Не стоит полагаться на finalize для чистки данных. Во-первых, нет гарантии, что он будет вызван, т.к. где-то может остаться ссылка на объект. Во-вторых, нет гарантии на то, в какое время будет вызван метод. Это связано с тем, что после того, как объект становится доступным для сборки и, если в нем переопределен метод finalize, то он не вызывается сразу, а помещается в очередь, которая обрабатывается специально созданным для этого потоком. Стоит отметить, что в очередь на финализацию попадают только те объекты, в которых переопределен метод finalize.

Есть вероятность, что этот метод не будет вызван совсем. Это может произойти в момент, когда объект уже станет доступным для сборщика мусора и программа завершит свою работу.

Интересной особенностью метода является то, что он может снова сделать объект доступным, присвоив this какой-нибудь переменной, хотя так делать не рекомендуется, т.к. при восстановлении объекта, повторно finalize вызван не будет

Может случиться еще один редкий момент. У нас есть класс A, в котором реализован метод finalize. Мы создаем класс B extends A, в котором забываем про finalize. Объекты класса B содержат в себе много данных. Когда объекты классы B становятся ненужными, они попадут в очередь на финализацию и определенное время еще будут занимать память, вместо того, чтобы миновать этой очереди и сразу утилизироваться.

Еще одним недостатком является то, что надо помнить про вызов finalize-метода супер-класса, если мы переопределяем его. Разработчик не вызовет - никто не вызовет.

Исключения, брошенные в методе finalize, не обрабатываются потоком-финализатором, т.е. данный стектрейс скорее всего нельзя будет отследить.

Есть один способ быть уверенным, что finalize-методы были запущены для объектов, доступных для сборки: вызвать System.runFinalization() или Runtime.getRuntime().runFinalization(). Выход из метода осуществляется только тогда, когда все доступные методы объектов для финализации будут выполнены

Для себя я сделал вывод, что пользоваться этим методом без особой надобности не стоит, а случаи этой особой надобности на моей двух-с-половиной-летней практике пока не встречались.

Лучше вместо finalize писать методы типа close в java.io и вызывать их в блоке finally. Недостатком является то, что разработкик должен помнить, что ресурс после использования нужно закрыть. На помощь тут нам пришла Java SE 7 со своими try-with-resources

Но ведь этот метод для чего-то есть. Где и как его можно использовать? Есть ли примеры использования?

Finalize можно использовать как последний шанс закрыть ресурс, но никогда как первая или единственная попытка. Т.е. в дополнение к тому, что клиент может вызвать, например, метод close на объекте, представляющем ресурс. А может и забыть. Тогда можно попытаться ему помочь. Так сделано, например, в классе FileInputStream.java:


protected void finalize() throws IOException {
    if ((fd != null) &&  (fd != FileDescriptor.in)) {
        /*
         * Finalize should not release the FileDescriptor if another
         * stream is still using it. If the user directly invokes
         * close() then the FileDescriptor is also released.
         */
        runningFinalize.set(Boolean.TRUE);
        try {
            close();
        } finally {
            runningFinalize.set(Boolean.FALSE);
        }
    }
}

Данный подход часто используется в библиотеках Java.

PS. Прошу не принимать написанное за чистую правду. Я написал лишь то, как я понял прочитанный материал. Приму с радостью любые дополнения, критику и исправления.

Список использованной литературы:
How to Handle Java Finalization's Memory-Retention Issues
Java Finalize method call
java.lang. Class Object
10 points on finalize method in Java – Tutorial Example
Habrahabr. Finalize и Finalizer

среда, 12 июня 2013 г.

Статическая и динамическая загрузка классов

Статическая загрузка классов - это всем привычная загрузка, которая производится автоматически. При запуске программы загрузчик классов рекурсивно загружает все классы, встречающиеся в программе, начиная с main-класса. Объекты таких классов создаются стандартным способом - через оператор new.

Динамическая загрузка классов производится через метод Class.forName(String className) или с использованием ClassLoader-а. Динамическая загрузка классов имеет смысл, когда требуется загрузить класс во время выполнения программы, когда нужно заменить класс, изменив, например, какую-то логику, не рестартуя приложения. Иногда может понадобиться загружать классы удаленно, например по http


Class animalClass = Class.forName("org.dmitrievs.Cat") ;
//Создается класс с конструктором по умолчанию
myAnimal = (Cat) animalClass.newInstance(); 

//загружаем класс через ClassLoader
ClassLoader classLoader = MainClass.class.getClassLoader();
Class aClass = classLoader.loadClass("com.jenkov.MyClass");
myClass = (MyClass) aClass.newInstance();

Один и тот же ClassLoader не может повторно загружать один и тот же класс, поэтому для повторной закрузки используется новый загрузчик. Подробнее можно почитать тут: http://tutorials.jenkov.com/java-reflection/dynamic-class-loading-reloading.html

вторник, 4 июня 2013 г.

Перегрузка методов

Всё ли в порядке с классом?

public class B {
    public int test(int i){return 1;};
    public int test(float i){return 2;};
    public int test(Integer i){return 3;};
    
    public static void main(String[] args) {
        B b = new B();
        System.out.printf("%d %d %d", b.test(1), b.test(1.0f), 
            b.test(Integer.valueOf(1)));
    }
}

Ответ

Output: 1 2 3 Класс скомпилируется и будет работать, т.к. значения считаются разными и попадут в нужные методы

Где начинаются проблемы?


Object[] o = new Integer[2];//1
o[0] = "fff"; //2
System.out.println(o[0]); //3

Ответ

Строка 1 отработает без проблем. 2 строка скомпилируется, массивы ковариантны. 3 строку компилятор тоже пропустит. А вот при запуске на 3-й строке выскочит ArrayStoreExceptions

Кто первый?

Что выведет программа?

class Tree {
    static int i[];
    public static void main(String... args)
    {
        int eye[] = new int[0];
        try {
            try {
                System.out.println(i.length);
            } finally{
                i = eye;
            }
        } catch (RuntimeException e) {
            e.printStackTrace();
            System.err.println(i.length);
        }
    }
}

Ответ

Output: 0
Cначала выполнится блок finally, затем программа попадет в блок с исключением.

Объявления массивов и коллекций

Можно ли выполнять следующие объявления?

Object[] array = new String[5]; //1
Object [] object = new String[5][5][5]; //2
List<Integer> list = new ArrayList<Number>(); //3
List<? extends Number> list = new ArrayList<Number>(); //4
List<Number> list2 = new ArrayList<Integer>(); //5
List<? super Integer> list3 = new ArrayList<Number>(); //6

Ответ

1. Можно. Массив String является подтипом массиват Object
2. Можно
3. Нельзя, т.к. колленция не является подтипом коллекции
4. Можно. Тут мы говорим, что коллекция может соржать любой тип, наследующий от Number. Считается, что Number наследует от Number
5. Нельзя, т.к. колленция не является подтипом коллекции
6. Можно. Коллекция может содержать объект любого типа, от которого наследует Integer

Определяем поведение для группы элементов в enum

Пример взят из книги Effective Java

// The strategy enum pattern
enum PayrollDay {
    MONDAY(PayType.WEEKDAY), 
    TUESDAY(PayType.WEEKDAY),
    WEDNESDAY(PayType.WEEKDAY), 
    THURSDAY(PayType.WEEKDAY),
    FRIDAY(PayType.WEEKDAY),
    SATURDAY(PayType.WEEKEND), 
    SUNDAY(PayType.WEEKEND);
    
    private final PayType payType;
    
    PayrollDay(PayType payType) { this.payType = payType; }
    
    double pay(double hoursWorked, double payRate) {
        return payType.pay(hoursWorked, payRate);
    }
    
    // The strategy enum type
    private enum PayType {
        WEEKDAY {
            double overtimePay(double hours, double payRate) {
                return hours <= HOURS_PER_SHIFT ? 0 :
                (hours - HOURS_PER_SHIFT) * payRate / 2;
            }
        },
        WEEKEND {
            double overtimePay(double hours, double payRate) {
                return hours * payRate / 2;
            }
        };
        
        private static final int HOURS_PER_SHIFT = 8;
        
        abstract double overtimePay(double hrs, double payRate);
        
        double pay(double hoursWorked, double payRate) {
            double basePay = hoursWorked * payRate;
            return basePay + overtimePay(hoursWorked, payRate);
        }
    }
}

воскресенье, 2 июня 2013 г.

Создание объектов с переменным числом параметров. Три способа.

1. Простой и понятный, но не самый изящный способ - использовать паттерн Телескоп


public class Menu {
    private final String salad; //optional
    private final String soup; //required
    private final String second; //required
    private final String tea; //optional
    private final String desert; //optional
    private final String fruits; //optional
    Menu(String soup, String second) {
        this(soup, second, null);
    }
    Menu(String soup, String second, String salad) {
        this(soup, second, salad, null);
    }
    Menu(String soup, String second, String salad, String tea) {
        this(soup, second, salad, tea, null);
    }
    Menu(String soup, String second, String salad, String tea, String desert) {
        this(soup, second, salad, tea, desert, null);
    }
    Menu(String soup, String second, String salad, String tea, String desert, String fruits) {
        this.soup = soup;
        this.second = second;
        this.salad = salad;
        this.tea = tea;
        this.desert = desert;
        this.fruits = fruits;
    }
}

2. Использовать класс с пустым конструктором и множеством сеттеров. Такой паттерн называется JavaBeans. Его недостатками являются возможность создать недоконструированный объект, а так же невозможность создавать Immutable-объекты


public class Menu {
    private String salad; //optional
    private final String soup; //required
    private final String second; //required
    private String tea; //optional
    private String desert; //optional
    private String fruits; //optional
    Menu(String soup, String second) {
        this.soup = soup;
        this.second = second;
    }

    public void setSalad(String salad) {
        this.salad = salad;
    }
    
    public void setTea(String tea) {
        this.tea = tea;
    }
    
    public void setDesert(String desert) {
        this.desert = desert;
    }
    
    public void setFruits(String fruits) {
        this.fruits = fruits;
    }
}

3. Использовать шаблон Строитель. Немного усложненная структура, но зато как красиво можно собирать объекты.


public class Menu {
    private final String soup; //required
    private final String second; //required
    
    private final String salad; //optional
    private final String tea; //optional
    private final String desert; //optional
    private final String fruits; //optional
    
    public static class MenuBuilder {
        private final String soup; //required
        private final String second; //required
        
        private String salad; //optional
        private String tea; //optional
        private String desert; //optional
        private String fruits; //optional
        
        MenuBuilder(String soup, String second) {
            this.soup = soup;
            this.second = second;
        }
        
        public MenuBuilder setSalad(String salad) {
            this.salad = salad;
            return this;
        }
        
        public MenuBuilder setTea(String tea) {
            this.tea = tea;
            return this;
        }
        
        public MenuBuilder setDesert(String desert) {
            this.desert = desert;
            return this;
        }
        
        public MenuBuilder setFruits(String fruits) {
            this.fruits = fruits;
            return this;
        }
        
        public Menu build() {
            return new Menu(this);
        }
    }
    
    private Menu(MenuBuilder builder) {
        soup = builder.soup;
        second = builder.second;
        salad = builder.salad;
        tea = builder.tea;
        desert = builder.desert;
        fruits = builder.fruits;
    }
    
    public static void main(String[] args) {
        Menu menu = new Menu.MenuBuilder("soup", "second")
            .setDesert("desert")
            .setFruits("fruits")
            .setSalad("salad")
            .setTea("tea")
            .build();
    }
}