Как избежать "несоответствия типов" в статическом универсальном фабричном методе? - PullRequest
5 голосов
/ 01 июня 2011

Либо я слишком глуп, чтобы использовать Google, либо никто другой до сих пор не сталкивался с этой проблемой.

Я пытаюсь скомпилировать следующий код:

public interface MyClass {
  public class Util {
    private static MyClass _this;
    public static <T extends MyClass> T getInstance(Class<T> clazz) {
      if(_this == null) {
        try {
          _this = clazz.newInstance();
        } catch(Exception e) {
          e.printStackTrace();
        }
      }
      return _this;
    }
  }
}

Howerer, inстрока "return _this;"Я получаю сообщение об ошибке «Несоответствие типов: невозможно преобразовать из MyClass в T» Почему это?T расширяет MyClass, так в чем же проблема?Если я изменяю строку на «return (T) _this;», я просто получаю предупреждение о непроверенном приведении, но мне не нравятся предупреждения ;-) Есть ли способ достичь того, что я хочу, без ошибки или предупреждения?

Ответы [ 4 ]

6 голосов
/ 01 июня 2011

Представьте, что у вас есть две реализации MyClass, Foo и Bar.Поскольку поле типа MyClass, _this может быть Foo или Bar.

Теперь, поскольку ваш метод getInstance возвращает <T extends MyClass>, разрешено вызывать его любым изэти способы:

MyClass myClass = Util.getInstance(MyClass.class);

Это не работает, если это первый вызов, потому что MyClass является интерфейсом и не может быть создан с помощью newInstance().

Foo foo = Util.getInstance(Foo.class);
Bar bar = Util.getInstance(Bar.class);

Что бы произошло, если бы _this был экземпляром Foo, а вы назвали Util.getInstance(Bar.class)?Вот почему вам не разрешено это делать.

4 голосов
/ 01 июня 2011

Это потому, что переменная _this имеет тип MyClass, а не тип T.Даже если он содержит экземпляр T, компилятор не может это узнать.

2 голосов
/ 01 июня 2011

Я только что проверил, что это делает компилятор счастливым и все еще ограничивает типы желаемым способом:

public interface MyClass {
  public class Util {
    private static MyClass _this;
      public static MyClass getInstance(Class<? extends MyClass> clazz) {
        if(_this == null) {
          try {
            _this = clazz.newInstance();
          } catch(Exception e) {
            e.printStackTrace();
          }
        }
        return _this;
    }
  }
}

Edit:

Думая о клиентском коде, на самом деле это просто обнаруживает ошибку в дизайне этой фабрики. Представь себе:

MyClass foo = MyClass.getInstance(Foo.class); // sets _this to a Foo and returns it

MyClass bar = MyClass.getInstance(Bar.class); // _this is already set to a Foo and
                                              // we return a Foo when we probably
                                              // are expecting a Bar!
1 голос
/ 01 июня 2011

«Несоответствие типов» ...

... обусловлено следующим:

  • T представляет подкласс MyClass.
  • getInstance объявляется как возвращающий объект типа T
  • Возвращает объект типа MyClass.

Это похоже на объявление метода, возвращающего Double пока он возвращает немного Number.

Решение ...

... состоит в том, чтобы изменить оператор возврата на

return (T) _this;

(и добавить @SuppressWarnings("unchecked"), если вы хотите избавиться от предупреждения).

Но есть проблема ...

Как указывает ColinD: Предположим, у вас есть

class MyClassImpl1 implements MyClass {
}

class MyClassImpl2 implements MyClass {
}

ивыполните следующие действия:

MyClassImpl1 o1 = MyClass.Util.getInstance(MyClassImpl1.class);
// _this now holds a value of type MyClassImpl1...

// ... which causes this line to throw a ClassCastException.
MyClassImpl2 o2 = MyClass.Util.getInstance(MyClassImpl2.class);
...