Как правильно вернуть что-то, что расширяется от класса и реализует интерфейс?

Задний план

Этот вопрос предназначен как для разработчиков Java, так и для Android.

В Android я хочу иметь функцию, которая возвращает что-то, что расширяется из View и реализует проверяемый< /a>, оба являются частью Android API.

Что я пробовал

После небольшого поиска здесь и в других местах в Интернете я нашел это решение:

private <T extends View & Checkable> T get() {
    return (T) mView;
}

Обратите внимание, что mView относится к любому типу, который расширяет View (а их много), но я гарантирую, что он реализует Checkable, так как там могут быть только эти типы.

Это показывает предупреждение «Безопасность типов: непроверенное приведение от View к T», но это не проблема. Проблема в том, что я не могу правильно использовать этот метод, например:

View v=get(); //this works fine
Checkable c=get(); // this shows error

вторая строка показывает ошибку:

Несоответствие границ: общий метод get() типа ... неприменим для аргументов (). Выведенный тип Checkable&View не является допустимой заменой ограниченного параметра.

Поэтому я попытался использовать "," вместо "&" . такая же ошибка.

Однако, когда я использую «,» и меняю порядок, я получаю предупреждение «Параметр типа View скрывает тип View», и обе строки работают нормально.

Это код:

private <T extends Checkable, View> T get() {
    return null;
}

private void foo() {
    final View v = get(); // this works fine
    final Checkable c = get();// this works fine too
}

Однако, когда я отправляю возвращаемый тип другим функциям, они не получают сам тип как нечто, расширяющее класс и интерфейс, и все время требуют приведения (к представлению).

Вопрос

Как правильно это сделать?

Если последний способ, который я показал, правильный, почему я получаю предупреждение об этом? и почему порядок важен, если всегда есть максимум один класс для расширения?


person android developer    schedule 30.10.2013    source источник
comment
Является ли mView типом, который вы создали?   -  person Kevin Bowersox    schedule 30.10.2013
comment
@KevinBowersox, это не проблема. я мог бы даже вернуть туда null, если бы захотел.   -  person android developer    schedule 30.10.2013


Ответы (3)


Вы получаете ошибку, потому что тип, выводимый для параметра T во втором месте вызова, то есть Checkable c = get(), просто Checkable, как правильно сообщает сообщение об ошибке.

Если вы заставите вызов использовать совместимый тип View & Checkable, он скомпилируется. Для этого вы должны указать тип явно, например:

private <T extends View & Checkable> T get() {
   return (T) mView;
}

private <T extends View & Checkable> void useGeneric() {
    View v = this.get(); // no need for an explicit type
    Checkable c = this.<T>get(); // an explicit type needed
    T t = this.get(); // best way
}

private void useSpecific() {
    class Specific extends View implements Checkable {}
    Checkable c = this.<Specific>get(); // risk of ClassCastException
    Specific s = this.get(); // best way; risk of ClassCastException
}

Тот факт, что в случае присвоения классу он не требует явного типа, в то время как он требуется в случае интерфейса, выглядит для меня артефактом алгоритма вывода типа, асимметрией класса/интерфейса.

person charlie    schedule 24.08.2016
comment
Хороший. Не знал, что я могу это сделать. Что делать, если нужно вернуть более одного интерфейса? - person android developer; 27.08.2016
comment
@androiddeveloper: Вы имеете в виду, например. View & Checkable & Closeable? Просто соедините таким образом все необходимые интерфейсы с помощью & в каждом определении параметра типа. - person charlie; 29.08.2016
comment
В ПОРЯДКЕ. Еще раз спасибо - person android developer; 29.08.2016

Прежде чем я углублюсь в дженерики... Почему бы вам просто не ввести CheckableView и не вернуть этот тип?

public class CheckableView extends View implements Checkable {
}


 private CheckableView getCheckableView() {
     return checkableView;
 }

Если бы View был интерфейсом, он работал бы:

interface View {}
interface Checkable {}
class CheckableView implements Checkable, View {}

public class Main {

    private static CheckableView checkableView;

    private static  <T extends View & Checkable> T get() {
        return (T) checkableView;
    }

    public static void main(String[] args) {
        View view = Main.get();
        Checkable checkable = Main.get();
    }
}

Я думаю, что проблема с пониманием дженериков заключается в том, что T extends View & Checkable в реальном случае не означает, что вы можете вернуть любой тип, который расширяет View и реализует Checkable.

Это означает, что клиент может привести возвращенный тип к любому типу, который расширяет View и реализует Checkable.

Клиент может сделать что-то вроде этого:

class CheckableView extends View implements Checkable {}
class OtherView extends View implements Checkable {}

private static  <T extends View & Checkable> T get() {
    return (T) checkableView;
}

OtherView otherView = Main.get();
CheckableView checkableView = Main.get();

но что вы хотите сделать сейчас с реализацией?

 private <T extends View & Checkable> T get() {
      return ...;
 }

Что должен возвращать этот метод? Если вы вернете CheckableView, компилятор скажет, что вы должны привести его к T, потому что клиент может привести его к любому типу T. Но это также означает, что вы получите предупреждение Unchecked cast from CheckableView to T, потому что, если клиент приведет его к любому другому типу, кроме CheckableView, например. OhterView, вы получите ClassCastException.

Поскольку разработчик метода get не может знать, к какому типу его когда-либо приведет клиент, он не может определить, какой объект следует вернуть.

И если разработчик возвращает определенный объект (например, CheckableView), то клиент может привести его только к этому типу. Так почему же разработчик не меняет сигнатуру метода, чтобы он возвращал именно этот тип? .... или ввести специальный тип или интерфейс для этого случая?

Возможно, это решение для вас

Создайте интерфейс, который будет возвращать метод get, например.

public interface CheckableView {
}

Измените метод get, чтобы он возвращал CheckableView

private CheckableView get() {
}

Напишите адаптер, который адаптирует ваши View, реализующие Checkable, к CheckableView.

public class CheckableViewAdapter<T extends View & Checkable> implements CheckableView {

    private T checkableView;

    public  CheckableViewAdapter(T checkableView) {
        this.checkableView = checkableView;
    }
}

Теперь вы можете создавать экземпляры CheckableView для каждого View, реализующего Checkable.

class SomeView extends View implements Checkable {}

SomeView someView = ...;
CheckableView checkableView = new CheckableViewAdapter<SomeView>(someView);

Добавьте методы, которые нужны клиенту метода get, в интерфейс CheckableView и реализуйте его в файле CheckableViewAdapter.

public interface CheckableView {
     public void bringToFront();
}

public class CheckableViewAdapter<T extends View & Checkable> implements CheckableView {

    private T checkableView;

    public  CheckableViewAdapter(T checkableView) {
        this.checkableView = checkableView;
    }

        public void bringToFront(){
            checkableView.bringToFront();
        }
}

или просто

public interface CheckableView {
     public View getView();
     public Checkable getCheckable();
}
person René Link    schedule 30.10.2013
comment
Что, если checkableView не CheckableView? Например: CheckBox. - person Eng.Fouad; 30.10.2013
comment
это проблематично, так как я не думаю, что смогу преобразовать mView в новый класс. mView может быть экземпляром любого класса, но я гарантирую, что он расширяется от View и реализует Checkable. Кроме того, представление о том, что это интерфейс, проблематично, поскольку оно является частью API Android. - person android developer; 30.10.2013
comment
View есть android.view.View, создать свой не поможет. - person Eng.Fouad; 30.10.2013
comment
@Eng.Fouad Я знаю, что View в Android — это класс, а не интерфейс. Вот почему я сказал, что если бы это было - person René Link; 30.10.2013
comment
@RenéLink Я не понимаю, что вы предлагаете делать тогда. - person android developer; 30.10.2013
comment
@androiddeveloper Я хочу сказать, что тип привязывается, когда клиент вызывает метод get, и это означает, что тогда клиент определяет конкретный тип. Таким образом, вы не можете вернуть какие-либо объекты, которые «T расширяет View & Checkable», потому что клиент может привести (привязать тип) к любому объекту, который соответствует этому. - person René Link; 30.10.2013
comment
@androiddeveloper Я имею в виду ... вы читали такой метод, как этот, get возвращает любой тип, который расширяет View и реализует Checkable, но вы должны прочитать его, get возвращает тип T, который расширяет View и реализует checkable. Тип T привязывается при вызове метода - person René Link; 30.10.2013
comment
@RenéLink, так что же нужно сделать, чтобы это заработало? я хочу, чтобы он возвращал объект, который расширяет класс и реализует интерфейс. это невозможно без всяких странностей или кастинга? - person android developer; 30.10.2013
comment
@androiddeveloper Я рекомендую ввести для этого специальный класс, например CheckableView, который я предложил. Затем расширяет представления, которые вы хотите от этого класса. Я знаю, что в Android у вас есть проблема, заключающаяся в том, что вы должны расширить представление или действие, и это усложняет вашу реализацию. Итак, если вы хотите создать базовый класс и используете фреймворк, для которого требуется базовый класс, вы должны расширить базовые классы фреймворка. Эта проблема проектирования в Android является причиной существования таких классов, как RoboOrmLiteListActivity. Может быть, теперь вы представите RoboOrmLiteListCheckableActivity;) - person René Link; 30.10.2013
comment
@RenéLink для этого мне потребуется взять код каждого View, который расширяется от Checkable , скопировать его и создать новый, который просто расширяется от нового класса. это не практично. - person android developer; 30.10.2013
comment
@androiddeveloper Я не знаю, как CheckableView используется в вашем приложении, но я обновлю свой ответ и попытаюсь показать другой способ. - person René Link; 30.10.2013
comment
@RenéLink, это хорошее решение, но это не то, о чем я просил. возвращаемое значение на самом деле является оболочкой/держателем, который содержит то, что я хотел вернуть - person android developer; 30.10.2013
comment
@androiddeveloper Да, я знаю. Я буду продолжать следить за этим вопросом, потому что меня также интересуют другие решения. - person René Link; 30.10.2013
comment
+1 за Почему бы вам просто не ввести CheckableView и не вернуть этот тип? - У меня была похожая проблема, и это дало мне подсказку, в которой я нуждался (должен был это увидеть, фейспалм самому себе...) - person RobertG; 20.02.2015

Проблема в том, что у вас вместо амперсанда запятая! Это определение:

private <T extends Checkable, View> T get() {

Не является перекрестком! Скорее, он объявляет 2 универсальных типа, один с именем T, а другой с именем View, который не используется в вашем классе - это то же самое, что и кодирование:

private <T extends Checkable, V> T get() {

Итак, измените его на:

private <T extends View & Checkable> T get() {

Обратите внимание, что при общих пересечениях класс должен стоять перед интерфейсом.

person Bohemian♦    schedule 30.10.2013
comment
Прочитайте первую часть его вопроса. - person Eng.Fouad; 30.10.2013
comment
Я уже пробовал & способ. это не сработало. может я что-то пропустил? - person android developer; 30.10.2013