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

oop: overload

    Надо понимать, что перегрузка метода - это один из трех распространенных примеров реализации полиморфизма (наравне с переопределением метода и генериками).

    Примеры перегрузки методов (overloading)

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

    Допустим у нас есть два пользовательских класса Circle и Rectangle:    

public class Circle {
    private final double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }
}
public class Rectangle {
    private final double width;
    private final double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    public double getWidth() {
        return width;
    }

    public double getHeight() {
        return height;
    }
}
 
    Мы можем создать утилитарный класс FigureUtils0 с двумя версиями метода perimeter:
public class FigureUtils0 {
    
    public static double perimeter(Rectangle rect) {
        return 2 * (rect.getHeight() + rect.getWidth());
    }

    public static double perimeter(Circle circle) {
        return 2 * Math.PI * circle.getRadius();
    }    
}

    Теперь мы можем писать такой код:
public class _0_OverloadingTestA {
    public static void main(String[] args) {
        Circle circle = new Circle(1);
        Rectangle rect = new Rectangle(2, 1);

        System.out.println("perimeter: " + FigureUtils0.perimeter(circle));
        System.out.println("perimeter: " + FigureUtils0.perimeter(rect));
    }
}
    Или с использованием import static даже такой:
import static FigureUtils0.perimeter;

public class _0_OverloadingTestB {
    public static void main(String[] args) {
        Circle circle = new Circle(1);
        Rectangle rect = new Rectangle(2, 1);

        System.out.println("perimeter: " + perimeter(circle));
        System.out.println("perimeter: " + perimeter(rect));
    }
}
>> perimeter: 6.283185307179586
>> perimeter: 6.0

    Таким образом создается впечатление, что у нас есть один "универсальный" метод perimeter(...), который возвращает периметр чего-угодно.
    Аналогичным образом "устроен" System.out.println(...) - есть множество версия для различных типов:
    public void println(boolean x)
    public void println(char x)
    public void println(double x)
    public void println(char x[])
    public void println(Object x)
    ...




    Представим себе, что мы хотим иметь список экземпляров. Конечно, мы можем создать List<Object>, но лучше создать "пустого предка" Figure и переписать Круг и Прямоугольник:
public interface Figure {
}
public class CircleThatFigure implements Figure {
    private final double radius;

    public CircleThatFigure(double radius) {
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }
}
public class RectangleThatFigure implements Figure {
    private final double width;
    private final double height;

    public RectangleThatFigure(double width, double height) {
        this.width = width;
        this.height = height;
    }

    public double getWidth() {
        return width;
    }

    public double getHeight() {
        return height;
    }
}
public class FigureUtils1 {
    
    public static double perimeter(RectangleThatFigure rect) {
        return 2 * (rect.getHeight() + rect.getWidth());
    }

    public static double perimeter(CircleThatFigure circle) {
        return 2 * Math.PI * circle.getRadius();
    }
}

    Одна проблема, код не компилируется
import java.util.ArrayList;
import java.util.List;

public class _1_OverloadingTest {
    public static void main(String[] args) {
        List<Figure> figures = new ArrayList<>();
        figures.add(new CircleThatFigure(1));
        figures.add(new RectangleThatFigure(2, 1));

        for (Figure figure : figures) {
            System.out.println("perimeter: " + FigureUtils1.perimeter(figure));
        }
    }
}
так как выбор одного метода из множества перегруженных методов происходит в момент компиляции, а компилятор не знает какой из вариантов perimeter() вызвать.

    Вот одно из решений проблемы:
public class FigureUtils2 {
    
    public static double perimeter(RectangleThatFigure rect) {
        return 2 * (rect.getHeight() + rect.getWidth());
    }

    public static double perimeter(CircleThatFigure circle) {
        return 2 * Math.PI * circle.getRadius();
    }

    public static double perimeter(Figure figure) {
        if (figure instanceof RectangleThatFigure) {
            return perimeter((RectangleThatFigure) figure);
        } else if (figure instanceof CircleThatFigure) {
            return perimeter((CircleThatFigure) figure);
        } else {
            throw new Error("Bad type. Only CircleThatFigure + RectangleThatFigure allowed.");
        }
    }
}
    Теперь работает:
import java.util.ArrayList;
import java.util.List;

public class _2_OverloadingTest {
    public static void main(String[] args) {
        List<Figure> figures = new ArrayList<>();
        figures.add(new CircleThatFigure(1));
        figures.add(new RectangleThatFigure(2, 1));

        for (Figure figure : figures) {
            System.out.println("perimeter: " + FigureUtils2.perimeter(figure));
        }
    }
}
>> perimeter: 6.283185307179586
>> perimeter: 6.0

    Однако у нас появилась "проблемная цепочка" if/else-if/else-if/...
    "Проблемность" в том, что в ней явно перечислены потомки Figure. С появлением нового потомка мы либо переписываем этот метод, либо получаем Error в момент обращения. Компилятор не отслеживает соответствие между всеми потомками Figure и цепочкой if/else-if/else-if/...

    Можем предложить такой вариант, с регистрацией "обработчиков периметра" в момент выполнения программы:
import java.util.HashMap;
import java.util.Map;

public class FigureUtils3 {
    private static final Map<Class, PerimeterCalculator> calculators
            = new HashMap<>();
    static {
        calculators.put(
                CircleThatFigure.class,
                new PerimeterCalculator() {
            @Override
            public double calculate(Figure figure) {
                double radius = ((CircleThatFigure) figure).getRadius();
                return 2 * Math.PI * radius;
            }
        });
        calculators.put(
                RectangleThatFigure.class,
                new PerimeterCalculator() {
            @Override
            public double calculate(Figure figure) {
                double weight = ((RectangleThatFigure) figure).getWidth();
                double height = ((RectangleThatFigure) figure).getHeight();
                return 2 * (weight + height);
            }
        });
    }

    public static double perimeter(Figure figure) {
        PerimeterCalculator calculator = calculators.get(figure.getClass());
        if (calculator != null) {
            return calculator.calculate(figure);
        } else {
            throw new Error("Bad type. Only " + calculators.keySet() + " allowed.");
        }
    }

    private static interface PerimeterCalculator {
        double calculate(Figure figure);
    }
}
    Особенно проблему не решает, но популярно у разработчиков.

    Аналогичный вариант, полностью кастомизируемый в момент выполнения пользователей.
import java.util.HashMap;
import java.util.Map;

public class FigureUtils4 {
    private final Map<Class, PerimeterCalculator> calculators
            = new HashMap<>();

    public void addCalculator(Class clazz, PerimeterCalculator calculator) {
        calculators.put(clazz, calculator);
    }    

    public double perimeter(Figure figure) {
        PerimeterCalculator calculator 
                = calculators.get(figure.getClass());
        if (calculator != null) {
            return calculator.calculate(figure);
        } else {
            throw new Error(
                    "Bad type. Only " + calculators.keySet() + " allowed.");
        }
    }

    public static interface PerimeterCalculator {
        double calculate(Figure figure);
    }
}
    Его не приходится переписывать каждый раз с появлением нового потомка у Figure. Но следить, что бы для каждого потомка Figure был зарегистрирован "калькулятор периметра" должен разработчик.


    Средствами языка невозможно "финализировать иерархию", т.е. определить что будут эти и только эти потомки, однако средствами языка можно заставить всех непосредственных потомков существовать в едином месте:
public abstract class Figure2 {
    private Figure2() {}

    public static class Circle2 extends Figure2 {
        private final double radius;

        public Circle2(double radius) {
            this.radius = radius;
        }

        public double getRadius() {
            return radius;
        }
    }

    public static class Rectangle2 extends Figure2 {
        private final double width;
        private final double height;

        public Rectangle2(double width, double height) {
            this.width = width;
            this.height = height;
        }

        public double getWidth() {
            return width;
        }

        public double getHeight() {
            return height;
        }
    }
}
    Поскольку конструктор Figure2 приватный (private), то наследники (которым он необходим) должны свои конструкторы объявлять внутри Figure2, т.е. и сами наследники должны объявляться внутри Figure2. Сказанное относится только к непосредственным наследникам Figure2.    
   
    Вариант использования:     
import java.util.ArrayList;
import java.util.List;

import static Figure2.Circle2;
import static Figure2.Rectangle2;

public class _4_OverloadingTest {
    public static void main(String[] args) {
        List<Figure2> figures = new ArrayList<>();
        figures.add(new Circle2(1));
        figures.add(new Rectangle2(2, 1));

        for (Figure2 figure : figures) {
            // ...
        }
    }
}

    Предыдущий трюк кратко
1) не компилируется
public class A {
    private A() {}
}
public class B extends A {
    public B() {}
}
2) не компилируется
public class A {
    private A() {}
}
public class B extends A {
    public B() {
        super();
    }
}
3) компилируется
public class A {
    private A() {}

    public static class B extends A {
        public B() {}
    }    
}
    но теперь можно добавить потомка от класса B    
class C extends A.B {}
x
x

x
x

x
x

x



    Правила перегрузки методов (overloading)


----------------------------------------------------------------
-- Код:
public class Overload {
    public void method(Object o) {
        System.out.println("Object");
    }
    public void method(java.io.FileNotFoundException f) {
        System.out.println("FileNotFoundException");
    }
    public void method(java.io.IOException i) {
        System.out.println("IOException");
    }
    public static void main(String args[]) {
        Overload test = new Overload();
        test.method(null);
    }
}
-- Вопросы/задания:
1) Что выведет данный код? Почему?
2) Что произойдет, если я добавлю метод

    public void method(NotSerializableException n) {
        System.out.println("FileNotFoundException");
    }
?