Вопрос-ответ

How do I make the method return type generic?

Как мне сделать тип возвращаемого значения метода универсальным?

Рассмотрим этот пример (типичный для книг по ООП):

У меня есть Animal класс, где у каждого Animal может быть много друзей.
И подклассы, такие как Dog, Duck, Mouse и т.д., Которые добавляют специфическое поведение, такое как bark(), quack() и т.д.

Вот Animal класс:

public class Animal {
private Map<String,Animal> friends = new HashMap<>();

public void addFriend(String name, Animal animal){
friends.put(name,animal);
}

public Animal callFriend(String name){
return friends.get(name);
}
}

И вот несколько фрагментов кода с большим количеством приведения типов:

Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());

((Dog) jerry.callFriend("spike")).bark();
((Duck) jerry.callFriend("quacker")).quack();

Есть ли какой-либо способ, которым я могу использовать generics для возвращаемого типа, чтобы избавиться от приведения типов, чтобы я мог сказать

jerry.callFriend("spike").bark();
jerry.callFriend("quacker").quack();

Вот некоторый начальный код с типом возвращаемого значения, передаваемым методу в качестве параметра, который никогда не использовался.

public<T extends Animal> T callFriend(String name, T unusedTypeObj){
return (T)friends.get(name);
}

Есть ли способ определить тип возвращаемого значения во время выполнения без использования дополнительного параметра instanceof? Или, по крайней мере, передав класс этого типа вместо фиктивного экземпляра.

Я понимаю, что generics предназначены для проверки типов во время компиляции, но есть ли обходной путь для этого?

Переведено автоматически
Ответ 1

Вы могли бы определить callFriend таким образом:

public <T extends Animal> T callFriend(String name, Class<T> type) {
return type.cast(friends.get(name));
}

Затем вызовите его как таковой:

jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();

Преимущество этого кода в том, что он не генерирует никаких предупреждений компилятора. Конечно, на самом деле это всего лишь обновленная версия кастинга времен, предшествовавших появлению generic, и она не добавляет никакой дополнительной безопасности.

Ответ 2

Вы могли бы реализовать это следующим образом:

@SuppressWarnings("unchecked")
public <T extends Animal> T callFriend(String name) {
return (T)friends.get(name);
}

(Да, это юридический код; см. Java Generics: универсальный тип, определенный только как возвращаемый тип.)

Тип возвращаемого значения будет определяться вызывающим объектом. Однако обратите внимание на @SuppressWarnings аннотацию: в ней говорится, что этот код небезопасен для типов. Вы должны проверить это самостоятельно, или вы могли бы получить ClassCastExceptions во время выполнения.

К сожалению, при том способе, которым вы его используете (без присвоения возвращаемого значения временной переменной), единственный способ порадовать компилятор - это вызвать его следующим образом:

jerry.<Dog>callFriend("spike").bark();

Хотя это может быть немного приятнее, чем приведение, вам, вероятно, лучше предоставить Animal классу абстрактный talk() метод, как сказал Дэвид Шмитт.

Ответ 3

Нет. Компилятор не может знать, какой тип jerry.callFriend("spike") вернет. Кроме того, ваша реализация просто скрывает приведение в методе без какой-либо дополнительной безопасности типов. Учтите это:

jerry.addFriend("quaker", new Duck());
jerry.callFriend("quaker", /* unused */ new Dog()); // dies with illegal cast

В этом конкретном случае создание абстрактного talk() метода и его соответствующее переопределение в подклассах сослужило бы вам гораздо лучшую службу:

Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());

jerry.callFriend("spike").talk();
jerry.callFriend("quacker").talk();
Ответ 4

Вот более простая версия:

public <T> T callFriend(String name) {
return (T) friends.get(name); //Casting to T not needed in this case but its a good practice to do
}

Полностью рабочий код:

    public class Test {
public static class Animal {
private Map<String,Animal> friends = new HashMap<>();

public void addFriend(String name, Animal animal){
friends.put(name,animal);
}

public <T> T callFriend(String name){
return (T) friends.get(name);
}
}

public static class Dog extends Animal {

public void bark() {
System.out.println("i am dog");
}
}

public static class Duck extends Animal {

public void quack() {
System.out.println("i am duck");
}
}

public static void main(String [] args) {
Animal animals = new Animal();
animals.addFriend("dog", new Dog());
animals.addFriend("duck", new Duck());

Dog dog = animals.callFriend("dog");
dog.bark();

Duck duck = animals.callFriend("duck");
duck.quack();

}
}
java generics