Преобразования между типами
Есть понятие восходящего преобразования - это означает, что объектная переменная класса-предка может содержать адрес объектной переменной класса-потомка.
class Human {
...
}
class Programmer extends Human {
...
}
class Student extends Human {
...
}
По правилу восходящего преобразования (неявное преобразование):
Programmer programmer = new Programmer(...);
Student student = new Student(...);
Human h1 = programmer;
Human h2 = student;
Но, есть нюанс - если мы захотим через human использовать методы/поля Programmer или Student, мы не сможем этого сделать.
Потому что тип Human не содержит в себе нужных методов/полей, хотя сам объект содержит:
// System.out.println(human.getLanguage());
// human.work();
Для того чтобы обратиться к этим полям/методам, необходимо сделать нисходящее преобразование (явных):
Programmer programmer1 = (Programmer) human;
programmer1.work();
Возможные проблемы - в переменной human может лежать не программист, а другой потомок, например Student
Тогда при преобразовании мы получим ошибку - ClassCastException - нельзя из Student сделать Programmer
Избежать такой ошибки можно с помощью предварительной проверки через instanceof:
if (human instanceof Programmer) {
Programmer programmer1 = (Programmer) human; // нисходящее преобразование
}
Т.е. мы делаем преобразование только в том случае, когда реально в переменной human лежит Programmer
equals
Задача equals проверить, эквиваленты ли сами объекты между собой.
Сигнатура метода equals - boolean equals(Object obj)
Т.е. в переменную obj можно положить абсолютно любой объект, потому что Object - предок всех классов
Но при сравнении, мы должны иметь доступ к полям/методам, которые лежат в obj
Я не имею доступа к x и y потому что пока у меня на руках не Point2D, а Object
На помощь мне приходит нисходящее преобразование - Point2D that = (Point2D)obj
Чтобы исключить возможность ошибки (если вдруг у меня вместо obj лежит другой объект другого класса) - необходимо сначала сделать проверку:
if (obj instanceof Point2D)
Тогда у меня преобразование сработает только в том случае, когда реально в obj лежит Point2D, а не что-то другое
Если у меня там лежит что-то другое, я просто не буду делать преобразования и ловить ошибку.
Проблема equals в потомках и предках
Пусть имеется следующий набор:
class A {
private int x;
private int y;
pubic boolean equals(...) {
return this.x == that.x && this.y == that.y;
}
class B extends A {
private int z;
}
main() {
B b1 = new B(1, 2, 3);
B b2 = new B(1, 2, 5);
b1.equals(b2) - у нас с вами в классе B нет метода equals, поэтому вызывается отнаследованный от A метод, который сравнивает только x и y
Т.е. в потомке мы не сделали свой equals и получили проблему - поля, которых нет в предке просто не рассматриваются.
Как решить эту проблему? Надо написать свой equals внутри потомка, который сравнивает дополнительные поля потомка.
Проблема нарушения симметрии для Point2D и для Point2DColored
a ~ b -> true
b ~ a -> false
В случае, когда мы вызываем a.equals(b), то вызывается реализация Point2D. Далее, когда вызывается такая штука как:
obj instanceof Point2D
то даже в случае, когда в obj лежит Point2DColored оператор instanceof даст true, потому что Point2DColor является потомком Point2D
далее потомок просто повышается до предка и вы сравниваете только координаты, забыв про цвет.
Если координаты равны, то будет true
В случае, когда b.equals(a), то вызывается реализация Point2DColored.
Далее, когда вызывается такая штука как:
obj instanceof Point2DColored
то в случае, когда в obj лежит предок Point2DColored, например Point2D, то операция instanceof даст false
Проблема в несимметричности операции instanceof
class A
class B extends A
A a = new A();
B b = new B();
b instanceof A - true
a instanceof B - false
А как написать правильный equals?
Отказаться от использования instanceof
Заменить использованием getClass() - это метод класса Object, поэтому он есть у всех классов. Грубо говоря, этот метод возвращает название класса, которому принадлежит объект.
Просто добавим проверку this.getClass() == obj.getClass()
Важно - можно сравнивать классы через ==, поскольку в памяти они существуют в одном экземпляре и это работает быстрее.
На практике - используйте то, что предлагает Intellij IDEA
Заходим в класс, нажимаем Alt Insert, Equals And HashCode, IntelliJ IDEA Default и много enter-ов.
Когда нам понадобится хеш код?
Когда будем использовать HashSet и HashMap
Comments