Надо понимать, что перегрузка метода - это один из трех распространенных примеров реализации полиморфизма (наравне с переопределением метода и генериками).
Примеры перегрузки методов (overloading)
Перегрузка методов - это пример реализации полиморфизма, для работы которого нет надобности в наследовании.
Допустим у нас есть два пользовательских класса Circle и Rectangle:
Правила перегрузки методов (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");
}
?
Примеры перегрузки методов (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");
}
?