У меня есть код, по которому я хочу разобраться, как работает рекурсивные generics. Смысл задачи в том, что-бы метод compareTo, принимал для сравнения, только объекты того типа, на котором он вызывается. То есть от класса Product наследуется Milk, и Phone, и объект Phone не должен принимать для сравнения в compareTo, объект Milk, только Phone. И есть место, которое я не как не могу понять. Это место Product<T extends Product<T>> автор кода утверждает, что для правильной работы, необходимо параметризировать Product<T>. Но код работает точно так же, если написать Product<T extends Product>. Объясните пожалуйста, это я что-то не понимаю, или автор перемудрил. И если первое, то почему так?
class Product<T extends Product<T>> implements Comparable<T> {
private int price;
Product(int price) {
this.price = price;
}
public int getPrice() {
return price;
}
@Override
public int compareTo(T o) {
return o.getPrice() - this.price;
}
}
class Milk extends Product<Milk> {
Milk(int price) {
super(price);
}
}
class Phone extends Product<Phone> {
Phone(int price) {
super(price);
}
}
Использование T extends Product<T> вместо T extends Product не препятствует созданию классов, у которых в качестве T участвует другой тип или T вообще не указан:
class Phone extends Product<Milk> { ... }
class Chair extends Product { ... }
Однако в случае использования T extends Product<T> не получится создать такой класс:
class CustomChair extends Product<Chair>
потому что это приведёт к ошибке компиляции:
error: type argument Chair is not within bounds of type-variable T
class CustomChair extends Product
where T is a type-variable:
T extends Product declared in class Product
Ошибка компиляции будет даже если класс Chair задан так:
class Chair extends Product<Milk> { ... }
Так как в обоих случаях класс Chair не подходит в качестве T из-за требования T extends Product<T>. Вот Chair extends Product<Chair> подошло бы.
Можно сказать, что T extends Product<T> задаёт более жесткие условия для T, чем T extends Product. Где-то это нужно, где-то - нет.
Например, если бы в классе Product были такие поля:
private T friend, friendOfFriend;
и такие методы:
public void setFriend(T t)
{
friend = t;
friendOfFriend = t.getFriend();
}
public T getFriend() { return friend; }
public T getFriendOfFriend() { return friendOfFriend; }
То в случае использования T extends Product пришлось бы использовать
friendOfFriend = (T)t.getFriend();
для приведения Product к T, что чревато ClassCastException.
Например, при всё тех же class Chair extends Product и class CustomChair extends Product<Chair>:
Milk milk = new Milk(1);
Chair chair = new Chair(10);
chair.setFriend(milk);
CustomChair customChair = new CustomChair(20);
customChair.setFriend(chair);
Chair c = customChair.getFriendOfFriend();
В реальных условиях, надеюсь, никто не пытается подружить стул с молоком, но всё же.
С T extends Product<T> молоко получится подружить только с молоком:
Milk milk1 = new Milk(1);
Milk milk2 = new Milk(2);
Milk milk3 = new Milk(3);
milk2.setFriend(milk3);
milk1.setFriend(milk2);
Milk m = milk1.getFriendOfFriend();
System.out.println(m.getPrice());
Сборка персонального компьютера от Artline: умный выбор для современных пользователей