Рассмотрим этот пример (типичный для книг ООП):
У меня есть 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();
Есть ли способ, как я могу использовать дженерики для возвращаемого типа, чтобы избавиться от приведения типов, чтобы я мог сказать
jerry.callFriend("spike").bark();
jerry.callFriend("quacker").quack();
Вот некоторый исходный код с возвращаемым типом, передаваемый методу в качестве параметра, который никогда не используется.
public<T extends Animal> T callFriend(String name, T unusedTypeObj){
return (T)friends.get(name);
}
Есть ли способ выяснить тип возвращаемого значения во время выполнения без использования дополнительного параметра instanceof
? Или, по крайней мере, передав класс типа вместо фиктивного экземпляра.
Я понимаю, что дженерики предназначены для проверки типов во время компиляции, но есть ли обходной путь для этого?
источник
Нет. Компилятор не может знать, какой тип
jerry.callFriend("spike")
вернется. Кроме того, ваша реализация просто скрывает приведение в методе без какой-либо дополнительной безопасности типов. Учти это:В этом конкретном случае создание абстрактного
talk()
метода и соответствующая его переопределение в подклассах послужит вам гораздо лучше:источник
Вы можете реализовать это так:
(Да, это допустимый код; см . Общие сведения Java: Общий тип, определенный только как возвращаемый тип .)
Тип возврата будет выведен из вызывающей стороны. Однако обратите внимание на
@SuppressWarnings
аннотацию: это говорит о том, что этот код не является безопасным для типов . Вы должны проверить это самостоятельно, или вы можете получитьClassCastExceptions
во время выполнения.К сожалению, то, как вы его используете (без присвоения возвращаемого значения временной переменной), единственный способ сделать компилятор счастливым - это вызвать его так:
Хотя это может быть немного приятнее, чем кастинг, вам, вероятно, лучше дать
Animal
классу абстрактныйtalk()
метод, как сказал Дэвид Шмитт.источник
jerry.CallFriend<Dog>(...
выглядит лучше.java.util.Collections.emptyList()
функция JRE реализована именно так, и ее Javadoc объявляет себя безопасным для типов.Этот вопрос очень похож на пункт 29 в эффективной Java - «Рассмотрим безопасные гетерогенные контейнеры». Ответ Лаз - самый близкий к решению Блоха. Тем не менее, и put, и get должны использовать литерал Class для безопасности. Подписи станут:
Внутри обоих методов вы должны проверить, что параметры являются нормальными. Посмотрите Эффективную Java и Класс javadoc для получения дополнительной информации.
источник
Кроме того, вы можете попросить метод вернуть значение данного типа таким образом
Больше примеров здесь в документации по Oracle Java
источник
Вот более простая версия:
Полностью рабочий код:
источник
Casting to T not needed in this case but it's a good practice to do
. Я имею в виду, что если об этом позаботятся во время выполнения, что означает «хорошая практика»?Как вы сказали, прохождение класса будет в порядке, вы можете написать так:
И затем используйте это так:
Не идеально, но это в значительной степени, насколько вы получаете с дженериками Java. Существует способ реализации Typesafe гетерогенных контейнеров (THC) с использованием супер-токенов , но у него снова есть свои проблемы.
источник
Основываясь на той же идее, что и Super Type Tokens, вы можете создать типизированный идентификатор для использования вместо строки:
Но я думаю, что это может победить цель, поскольку теперь вам нужно создавать новые объекты id для каждой строки и удерживать их (или восстанавливать их с правильной информацией о типе).
Но теперь вы можете использовать класс так, как вы хотели, без приведения.
Это просто скрывает параметр типа внутри идентификатора, хотя это означает, что вы можете получить тип из идентификатора позже, если хотите.
Вам также потребуется реализовать методы сравнения и хеширования TypedID, если вы хотите иметь возможность сравнивать два идентичных экземпляра идентификатора.
источник
"Есть ли способ выяснить тип возвращаемого значения во время выполнения без использования дополнительного параметра instanceof?"
В качестве альтернативного решения вы можете использовать шаблон посетителя, как этот. Сделайте Animal абстрактным и сделайте его видимым:
Visitable просто означает, что реализация Animal готова принять посетителя:
И реализация посетителя может посетить все подклассы животного:
Так, например, реализация Dog будет выглядеть так:
Хитрость здесь в том, что, поскольку Dog знает, какой это тип, он может вызвать соответствующий перегруженный метод посещения посетителя v, передав «this» в качестве параметра. Другие подклассы реализуют accept () точно так же.
Класс, который хочет вызвать специфичные для подкласса методы, должен затем реализовать интерфейс Visitor следующим образом:
Я знаю, что это гораздо больше интерфейсов и методов, чем вы рассчитывали, но это стандартный способ получить дескриптор для каждого конкретного подтипа с точно нулевым экземпляром проверок и нулевым типом приведений. И все это делается стандартным языком, не зависящим от языка, так что это не только для Java, но любой ОО-язык должен работать так же.
источник
Невозможно. Как Карта должна знать, какой подкласс Animal она получит, учитывая только ключ String?
Единственный способ, которым это возможно, - это если каждый Animal принимает только один тип друга (тогда это может быть параметр класса Animal), или метод callFriend () получает параметр типа. Но похоже, что вы упускаете момент наследования: вы можете обращаться с подклассами одинаково только при использовании исключительно методов суперкласса.
источник
Я написал статью, в которой содержится доказательство концепции, вспомогательные классы и тестовый класс, демонстрирующий, как ваши супертипы могут быть получены вашими классами во время выполнения. В двух словах, он позволяет вам делегировать альтернативные реализации в зависимости от фактических общих параметров, передаваемых вызывающей стороной. Пример:
TimeSeries<Double>
делегаты в закрытый внутренний класс, который используетdouble[]
TimeSeries<OHLC>
делегаты в закрытый внутренний класс, который используетArrayList<OHLC>
Видеть: Использование TypeTokens для получения общих параметров
Спасибо
Ричард Гомес - Блог
источник
Здесь есть много хороших ответов, но это тот подход, который я выбрал для теста Appium, когда действие над одним элементом может привести к переходу в разные состояния приложения в зависимости от настроек пользователя. Хотя это не соответствует соглашениям примера OP, я надеюсь, что это кому-то поможет.
Если вы не хотите выдавать ошибки, вы можете отловить их так:
источник
Не совсем, потому что, как вы говорите, компилятор знает только, что callFriend () возвращает Animal, а не Dog или Duck.
Разве вы не можете добавить абстрактный метод makeNoise () в Animal, который будет реализован в виде лая или кряка его подклассами?
источник
То, что вы ищете здесь, это абстракция. Код против интерфейсов больше, и вам придется делать меньше приведения.
Пример ниже в C #, но концепция остается той же.
источник
Я сделал следующее в моем lib kontraktor:
подклассы:
по крайней мере, это работает внутри текущего класса и при наличии строго типизированной ссылки. Многократное наследование работает, но потом становится действительно сложно :)
источник
Я знаю, что это совершенно другое, что тот спросил. Другим способом решения этой проблемы было бы отражение. Я имею в виду, что это не пользуется преимуществом Generics, но позволяет вам каким-то образом эмулировать поведение, которое вы хотите выполнить (собачий лай, крякание утки и т. Д.), Не заботясь о приведении типов:
источник
что о
}
источник
Существует другой подход, вы можете сузить тип возвращаемого значения, когда вы переопределяете метод. В каждом подклассе вы должны переопределить callFriend, чтобы вернуть этот подкласс. Стоимость будет составлять несколько объявлений callFriend, но вы можете выделить общие части для метода, вызываемого внутри. Это кажется мне намного проще, чем решения, упомянутые выше, и не требует дополнительного аргумента для определения типа возвращаемого значения.
источник
public int getValue(String name){}
, неотличимpublic boolean getValue(String name){}
с точки зрения компиляторов. Вам нужно будет либо изменить тип параметра, либо добавить / удалить параметры для распознавания перегрузки. Может быть, я просто неправильно понимаю тебя ...Вы можете вернуть любой тип и получить непосредственно как. Не нужно типизировать.
Это лучше, если вы хотите настроить любой другой вид записей вместо реальных курсоров.
источник
(Cursor) cursor
например.cursor
должен быть aCursor
и всегда будет возвращатьPerson
(или ноль). Использование обобщений снимает проверку этих ограничений, делая код небезопасным.