Что, если из метода нужно вернуть два значения? Например, метод должен вернуть окружность, в которую должен приземлиться парашютист, и точку, из которой он выпрыгнул.
Нужно просто вернуть объект, который будет хранить оба этих значения. Напишем класс для этого:
public final class ParachuteMission {
private final Circle target;
private final Point droppingPoint;
public ParachuteMission(Circle target, Point droppingPoint) {
this.target = target;
this.droppingPoint = droppingPoint;
}
public Circle getTarget() {
return target;
}
public Point getDroppingPoint() {
return droppingPoint;
}
@Override
public String toString() {
return "ParachuteMission(drop at " + droppingPoint + ", land to " + target + ")";
}
}
Можно воспользовать этим классом так:
public class ParachuteMissionExample {
public static void main(String[] args) {
ParachuteMission mission = generateRandomMission();
System.out.println(
"Dropping point is at (" +
mission.getDroppingPoint().getX() + "; " +
mission.getDroppingPoint().getY() + ")");
System.out.println("Must land in " + mission.getTarget());
}
private static ParachuteMission generateRandomMission() {
Random random = new Random();
return new ParachuteMission(
new Circle(random.nextDouble(),
random.nextDouble(),
random.nextDouble()),
new Point(random.nextDouble(),
random.nextDouble()));
}
}
Dropping point is at (0.1559734552983515; 0.8449202669900885) Must land in Circle(at (0.22848368817342946; 0.5060404193327808), r=0.14557853694639322)
Когда подобная задача возникает довольно часто, не всегда удобно писать новый класс для того, чтобы вернуть пару значений из метода. Переименуем ParachuteMission в Tuple (кортеж) и сделаем его универсальным, чтобы его же можно было использовать в похожих ситуациях, но с объектами любого типа:
public final class Tuple {
private final Object first;
private final Object second;
public Tuple(Object first, Object second) {
this.first = first;
this.second = second;
}
public Object getFirst() {
return first;
}
public Object getSecond() {
return second;
}
@Override
public String toString() {
return "Tuple(first=" + first + ", second=" + second + ")";
}
}
Попробуем воспользоваться:
public static void main(String[] args) {
Tuple mission = generateRandomMission();
System.out.println(
"Dropping point is at (" +
((Point) mission.getSecond()).getX() + "; " +
((Point) mission.getSecond()).getY() + ")");
System.out.println("Must land in " + mission.getFirst());
}
private static Tuple generateRandomMission() {
Random random = new Random();
return new Tuple(
new Circle(random.nextDouble(),
random.nextDouble(),
random.nextDouble()),
new Point(random.nextDouble(),
random.nextDouble()));
}
mission.getSecond() возвращает Object, и нам приходится явно приводить тип к Point: (Point) mission.getSecond(). Уже только после этого можно вызвать метод getX(), объявленный в классе Point.
Кроме того, что это создаёт визуальный мусор, высока вероятность ошибиться, например, вместо значения свойства second привести к типу Point значение свойства first: (Point) mission.getFirst(), что приведёт к ошибке во время выполнения:
java.lang.ClassCastException: Circle cannot be cast to Point
Generics
Другое название: обобщения. Шаблоны (templates) из C++ выполняют похожую задачу, но работают по-другому.
Для того чтобы позволить классу безопасно работать с объектами произвольного типа, разработали (позаимствовали) принцип Generics, который является реализацией так называемого параметрического полиморфизма.
public final class GenericTuple<F, S> {
private final F first;
private final S second;
public GenericTuple(F first, S second) {
this.first = first;
this.second = second;
}
public F getFirst() {
return first;
}
public S getSecond() {
return second;
}
@Override
public String toString() {
return "Tuple(first=" + first + ", second=" + second + ")";
}
}
F и S — это так называемые параметры типа. Каждый из них может быть любым ссылочным типом в каждом отдельном случае. Воспользуемся этим классом:
public static void main(String[] args) {
GenericTuple<Circle, Point> mission = generateRandomMission();
System.out.println(
"Dropping point is at (" +
mission.getSecond().getX() + "; " +
mission.getSecond().getY() + ")");
System.out.println("Must land in " + mission.getFirst());
}
private static GenericTuple<Circle, Point> generateRandomMission() {
Random random = new Random();
return new GenericTuple<>(
new Circle(random.nextDouble(),
random.nextDouble(),
random.nextDouble()),
new Point(random.nextDouble(),
random.nextDouble()));
// <> называется 'diamond mark'.
// <Circle, Point> на его месте писать не обязательно,
// компилятор подставит типы автоматически,
// исходя из типа возвращаемого методом значения.
// Это называется выведением (выводом) типа,
// type inference.
}
Приведения типа исчезают. Ошибочный код (Point) mission.getFirst() теперь не скомпилируется, т. к. компилятору известно, что mission.first в данном случае — Circle, а не Point.