Подтвердить что ты не робот

Генераторы 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 работает. Мне нужен синтаксис, который позволит мне вводить все, что реализует необходимые интерфейсы.

4b9b3361

Ответ 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();