Генераторы Java, похоже, работают по-разному для классов, чем для методов
Я следую этому:
http://rickyclarkson.blogspot.com/2006/07/duck-typing-in-java-and-no-reflection.html
И я пытаюсь адаптировать это:
<T extends CanQuack & CanWalk> void doDucklikeThings(T t)
{
t.quack();
t.walk();
}
Для этого:
public class Activate<D extends CanQuack & CanWalk> {
D d = new MyWaterFowl(); //Type mismatch
}
Несмотря на то, что MyWaterFowl реализует эти интерфейсы.
Мне нужно решение, которое никогда не упоминает MyWaterFowl в < > , так как я собираюсь в конечном итоге просто вставлять его (или что-то еще, что реализует эти интерфейсы).
Если ваш ответ в основном "Вы не можете этого сделать, какой бы он был?". Пожалуйста, объясните, почему он работает над методом doDucklikeThings и окончательно заявляет, что невозможно сделать то же самое с классом или, если возможно, как это сделать.
T в doDucklikeThings должен быть чем-то действительным, так как он работает. Если бы я передал это в класс, что бы я прошел?
Как запрошено здесь код MyWaterFowl:
package singleResponsibilityPrinciple;
interface CanWalk { public void walk(); }
interface CanQuack { public void quack(); }
interface CanSwim { public void swim(); }
public class MyWaterFowl implements CanWalk, CanQuack, CanSwim {
public void walk() { System.out.println("I'm walkin` here!"); }
public void quack() { System.out.println("Quack!"); }
public void swim() { System.out.println("Stroke! Stroke! Stroke!"); }
}
Помните, что я подтвердил, что doDucklikeThings работает. Мне нужен синтаксис, который позволит мне вводить все, что реализует необходимые интерфейсы.
Ответы
Ответ 1
На самом деле это можно сделать.
Общий код между < > отлично. Это не отличается для методов и классов. Просто нужно завершить инъекцию зависимостей:
public class Activate<D extends CanQuack & CanWalk> {
private D d;
Activate(D d) {
this.d = d;
}
void doDuckLikeThings() {
d.quack();
d.walk();
//d.swim(); //Doesn't work
//And shouldn't since that was the whole point of ISP.
}
}
public class MainClass {
public static void main(String[] args) {
Activate<MyWaterFowl> a = new Activate<>(new MyWaterFowl());
a.doDuckLikeThings();
}
}
Думал, что я дам ответ, в котором говорится, что делать, чтобы исправить это.
Ответ 2
Это не работает, потому что класс/метод является общим, а вызывающий объект вашего класса/метода может установить D
в MyAmericanEagle
.
Activate<MyAmericanEagle> active = new Activate<>();
Тогда ваш код приведет к
MyAmericanEagle d = new MyWaterFowl();
Так как это не имеет смысла (приведет к ClassCastException), компилятор отклонит его.
Ответ 3
// Type mismatch
Несмотря на то, что MyWaterFowl реализует эти интерфейсы.
Это не о типе D
, реализующем эти интерфейсы (и/или расширении класса). Общая переменная типа привязана к определенному аргументу типа. Этот тип может отличаться от MyWaterFowl
, поэтому вы не можете использовать их взаимозаменяемо.
Чтобы ответить на ваши изменения, вы делаете две совершенно разные вещи в своих двух фрагментах. Тип переменной объявляется с некоторыми ограничениями. Поэтому гарантируется, что это тип, который реализует некоторый интерфейс (или расширяет некоторый класс), но вы не знаете, какой тип это в любом случае.
Я хочу прояснить две вещи, которые вы сделали, т.е. что вы ожидаете в своем вопросе и решение, которое вы дали в своем ответе.
Generics - это функция времени компиляции, где код сервера, например
class Activate<D extends CanWalk & CanQuack> {
D instance;
public Activate(D d) {
this.instance = d;
}
public D getInstance() {
return instance ;
}
}
объявляет переменную . Это переменная. В контексте декларации вы не знаете его точный тип во время компиляции.
Код клиента, например,
new Activate<>(new MyWaterFowl());
связывает тип MyWaterFowl
с переменной типа, объявленной в Activate
. Таким образом, код клиента знает, что D
находится во время компиляции.
Если следующий
public D getInstance() {
D someRef = new MyWaterFowl();
return someRef;
}
было разрешено в коде сервера, это не получилось бы
Activate<SomeOtherBird> activate = new Activate<>(new SomeOtherBird());
SomeOtherBird reference = activate.getInstance();
Общие признаки гарантируют, что getInstance()
безопасен по типу, поскольку он объявлен как возвращающий любой тип, привязанный к переменной типа D
. В этом случае это SomeOtherBird
. Если код getInstance()
, приведенный выше, был разрешен, тип безопасности будет нарушен, так как getInstance()
вернет нечто, отличное от того, что было связано с ним.
Это не меняет того факта, что внутри вашего кода сервера (общий класс) вы знаете границы D
, т.е. это как CanQuack
, так и CanWalk
. Таким образом, все, что может сделать объект этих типов, может иметь объект, на который ссылается переменная D
.
Ответ 4
Тип класса, который расширяет Object, отличается от типа объекта. Это означает, что вы не можете создавать суперкласс для своих расширителей.
Когда вы скомпилируете это:
1 public class Sample<T extends Object> {
2 public Sample() {
3 T t = new Object();
4 }
5 }
вы получили ошибку incompatible types
.
Sample.java:3: error: incompatible types
T t = new Object();
^
required: T
found: Object
where T is a type-variable:
T extends Object declared in class Sample
1 error
То же самое, что и при использовании в не общей форме:
1 public class Sample{
2 public Sample() {
3 }
4 }
5
6 class SampleExtends extends Sample {
7 public SampleExtends() {
8
9 }
10 }
11
12 class User {
13 public User() {
14 SampleExtends se = new Sample();
15 }
16 }
вы получаете эту ошибку от компилятора: несовместимые типы
Sample.java:14: error: incompatible types
SampleExtends se = new Sample();
^
required: SampleExtends
found: Sample
1 error
Ответ 5
Что вы ожидаете от фактического типа D
во втором фрагменте кода?
Скажем, что-то делает это:
Activate<Daffy> myActivate = Activate<Daffy>();
Что тогда должно произойти? Это означает, что D
должен иметь тип Daffy
, но вы пытаетесь установить D
в экземпляр MyWaterFowl
.
Ответ 6
Generics работает одинаково для классов, как для методов.
Вы, кажется, неправильно понимаете Generics. Параметр типа <T extends CanQuack & CanWalk>
- это не определение нового псевдонима типа, действительного для области действия метода или класса, это placeholder для некоторого типа strong > , который позже заполняется кем-то другим.
Для общего метода, такого как ваш пример
<T extends CanQuack & CanWalk> void doDucklikeThings(T t)
{
t.quack();
t.walk();
}
вызывающий метод решает, какой тип заменить на T (в пределах, указанных границами).
Таким образом, можно использовать
doDuckLikeThings(myWaterfowl);
и компилятор догадывается, какой параметр типа вы хотите здесь (вероятно, тип переменной myWaterfowl
).
Для общего класса это код, который создает экземпляр класса, который решает фактический параметр типа.
Поэтому я могу сказать
Activate<Duck> duckActivator = new Activate<Duck>();
После этого экземпляр duckActivator
представляет собой экземпляр Activate<Duck>
, а внутри него D
теперь привязан к Duck
.
Теперь, очевидно, ваше объявление переменной говорит
Duck d = new MyWaterfowl();
и это не сработает.
Он не будет работать в общем методе - это тоже неверно:
<T extends CanQuack & CanWalk> void doDucklikeThings()
{
T t = new MyWaterfowl();
}
Мне нужно решение, которое никогда не упоминает myWaterfowl
в <>
, так как я собираюсь в конечном итоге просто вводите его (или что-нибудь еще, что реализует эти интерфейсы).
Тогда не упоминайте и его конструктор.
Тот, кто вводит ваш утиный подобный объект, также должен решить фактический параметр типа, т.е. Это должен быть тот, кто создает экземпляр Activate
. И это не может измениться после создания вашего объекта Activate
.
Решение кандидата по актуальной проблеме
Если это ограничение не подходит для вас (например, потому что один и тот же экземпляр Activate
должен иметь возможность работать с различными типами утиных объектов один за другим), возможно, параметр типа не для вас.
Может быть, вместо этого используется общая оболочка с параметром типа подстановки для переменной.
Возьмите эту общую оболочку (или что-то подобное):
public class Holder<T> {
private final T t;
public T get() { return t; }
public Holder(T t) { this.t = t; }
}
Тогда ваш класс Activate не нуждается в параметре типа, но просто содержит переменную владельца с параметром подстановочного типа. (Обратите внимание, что фактическое создание экземпляра Holder имеет аргумент типа без подстановочных знаков.)
public class Activate {
private Holder<? extends CanQuack & CanWalk> holder;
public <D extends CanQuack & CanWalk> void setDuckLike(D d) {
this.holder = new Holder<D>(d);
}
public Activate() {}
public void doDucklikeThings() {
holder.get().quack();
holder.get().walk();
}
}
Теперь вы делаете что-то вроде этого:
Activate a = new Activate();
a.setDuckLike(new Duck());
a.doDucklikeThings();
a.setDuckLike(new MyWaterfowl());
a.doDucklikeThings();