jls. заменяемый возвращаемый тип. Что это означает?

Я читаю jls и столкнулся со следующим термином:

return-type-substitutable

фрагмент из JLS

Объявление метода d1 с типом возвращаемого значения R1 можно заменить другим методом d2 с типом возвращаемого значения R2, ​​если верно любое из следующих условий:

Если R1 недействителен, то R2 недействителен.

Если R1 является примитивным типом, то R2 идентичен R1.

Если R1 является ссылочным типом, то верно одно из следующего:

--R1, адаптированный к параметрам типа d2 (§8.4.4), является подтипом R2.

--R1 может быть преобразован в подтип R2 непроверенным преобразованием (§5.1.9).

--d1 не имеет такой же подписи, как d2 (§8.4.2), и R1 = |R2|.

первые два пункта понятно.

можешь уточнить

--R1, адаптированный к параметрам типа d2 (§8.4.4), является подтипом R2.

--R1 может быть преобразован в подтип R2 непроверенным преобразованием (§5.1.9).

--d1 не имеет такой же подписи, как d2 (§8.4.2), и R1 = |R2|.

Благодарность

P.S. для Луиджи Мендосы

interface Foo {
        List<String> foo(String arg1, String arg2);
}

class Bar implements Foo {
    @Override
    public ArrayList<String> foo(String arg1, String arg2) {
        //implementation...
        return  null;
    }

    public String foo(String arg1, String arg2, String arg3) {
        //implementation...
        return  null;
    }
}

это рабочий вариант.

Причина моего вопроса - я хочу понять следующую фразу из jls:

Если объявление метода d1 с типом возвращаемого значения R1 переопределяет или скрывает объявление другого метода d2 с типом возвращаемого значения R2, ​​то d1 должен быть заменяемым по типу возвращаемого значения (§8.4.5) для d2, иначе возникает ошибка времени компиляции.

правило:

If R1 is a reference type then **one of the following** is true:
...
--d1 does not have the same signature as d2 (§8.4.2), and R1 = |R2|.
...

код:

interface Foo {
        List<String> foo(String arg1, String arg2);
}

class Bar implements Foo {
    @Override
    public List<String> anotherName(String arg1, String arg2,Object obj) {
           return  null;
    }

это ошибка компиляции.

R1==R2 ( List<String > == List<String>)

d1!=d2

Где я нарушил правило?


person gstackoverflow    schedule 11.05.2014    source источник
comment
Из вашего последнего редактирования нет метода anotherName для переопределения, поэтому вы получаете ошибку компилятора. Кроме того, вы говорите, что Bar реализует Foo, но в классе Bar нет метода public List<String> foo(String arg1, String arg2) (или другого метода, подпись которого foo(String arg1, String arg2) возвращает List или подтип List).   -  person Luiggi Mendoza    schedule 11.05.2014
comment
@Luiggi Mendoza Я написал правило и написал пример кода в соответствии с моим видением этого правила. Можете ли вы указать места правил, где я их нарушил?   -  person gstackoverflow    schedule 11.05.2014
comment
Я уже указал их в своем комментарии.   -  person Luiggi Mendoza    schedule 11.05.2014
comment
@Luiggi Mendoza, ваш комментарий распространен. Я согласился с этим. Но это ваша java понятно. Представьте, что вы новичок в java и пытаетесь следовать правилам. Пожалуйста, добавьте к вашему вопросу обновление в следующем формате: цитата из правила - нарушение в моем коде.   -  person gstackoverflow    schedule 11.05.2014
comment
Я говорю о 2 правилах: 1. Если объявление метода d1 с типом возвращаемого значения R1 переопределяет или скрывает объявление другого метода d2 с типом возвращаемого значения R2, ​​то d1 должен быть заменяемым по типу возвращаемого значения (§8.4.5) для d2, иначе возникнет ошибка времени компиляции. встречается как d2 (§8.4.2), и R1 = |R2|   -  person gstackoverflow    schedule 11.05.2014
comment
и 2. Объявление метода d1 с типом возвращаемого значения R1 может быть заменено по типу возвращаемого значения другим методом d2 с типом возвращаемого значения R2, ​​если верно любое из следующих условий: Если R1 является ссылочным типом, то верно одно из следующих условий: d1 не имеет той же подписи, что и d2 (§8.4.2), и R1 = |R2|.   -  person gstackoverflow    schedule 11.05.2014
comment
Проблема не в том, новичок вы или нет, проблема в том, что вы вообще не понимаете концепций.   -  person Luiggi Mendoza    schedule 11.05.2014
comment
Написал еще один ответ, пытаясь объяснить проблему.   -  person Luiggi Mendoza    schedule 11.05.2014
comment
@Luiggi Mendoza I Как я понял, Если объявление метода d1 с типом возврата R1 переопределяет или скрывает, подразумевается, что правила скрытия или переопределения соблюдаются.   -  person gstackoverflow    schedule 13.05.2014


Ответы (2)


Давайте иметь этот интерфейс

interface Foo {
    List<String> foo(String arg1, String arg2);
}

И класс, реализующий это

class Bar implements Foo {
    @Override
    public List<String> foo(String arg1, String arg2) {
        //implementation...
    }
}

У нас есть:

  • Bar#foo as d1
  • List<String> тип возврата в d1 как R1.
  • Foo#foo as d2
  • List<String> тип возврата в d2 как R2.

R1, адаптированный к параметрам типа d2 (§8.4.4), является подтипом R2.

Это означает, что R1 может быть подтипом R2, что означает, что R1 должен пройти тест IS-A. Итак, мы можем сделать следующее:

class Bar implements Foo {
    @Override
    public ArrayList<String> foo(String arg1, String arg2) {
        //implementation...
    }
}

R1 можно преобразовать в подтип R2 путем непроверенного преобразования (§5.1.9).

Это больше относится к дженерикам. Это означает, что R1 должен пройти тест IS-A, даже если он выдает предупреждение о непроверенном переопределении. Итак, мы можем сделать следующее:

class Bar implements Foo {
    @Override
    public ArrayList foo(String arg1, String arg2) {
        //implementation...
    }
}

d1 не имеет той же подписи, что и d2 (§8.4.2) и R1 = |R2|

Это означает перегрузку:

class Bar implements Foo {
    @Override
    public ArrayList<String> foo(String arg1, String arg2) {
        //implementation...
    }

    public ArrayList<String> foo(String arg1, String arg2, String arg3) {
        //implementation...
    }
}
person Luiggi Mendoza    schedule 11.05.2014
comment
d1 не имеет той же подписи, что и d2 для jls недостаточно точно - я могу написать абсолютно любую подпись. и что это значит ** R1 = |R2| ** - person gstackoverflow; 11.05.2014
comment
@gstackoverflow означает, что вы можете возвращать один и тот же тип для разных подписей, что является другим способом сказать о перегрузке. - person Luiggi Mendoza; 11.05.2014
comment
перегрузка - это ситуация, когда имя метода одинаковое, но другой список параметров. Это не связано с типом возврата - person gstackoverflow; 11.05.2014
comment
как насчет брекетов вокруг tge R2? - person gstackoverflow; 11.05.2014
comment
@gstackoverflow относится к типу класса. - person Luiggi Mendoza; 11.05.2014
comment
Если R1 примитивный тип, то R2 идентичен R1. аналог этой фразы для ссылочных типов? - person gstackoverflow; 11.05.2014
comment
@gstackoverflow да, но в конце концов вы изучаете множество концепций и понимаете, как работает Java. - person Luiggi Mendoza; 11.05.2014
comment
В моем мозгу я использую другие термины для объяснения Как работает Java - person gstackoverflow; 11.05.2014
comment
пожалуйста, прочитайте обновление - у меня неправильное понимание последнего пункта. Я использую разные типы возврата (R1 != |R2|), и в любом случае это не ошибка. подробности в обновлении темы - person gstackoverflow; 11.05.2014
comment
@gstackoverflow это ожидаемое поведение. Я не понимаю, в чем там проблема. Если d1 и d2 совершенно разные, а также R1 и R2 совершенно разные, он должен скомпилироваться и запуститься. - person Luiggi Mendoza; 11.05.2014
comment
очевидно, я не понимаю смысла d1 не имеет той же подписи, что и d2 (§8.4.2), и R1 = |R2| - person gstackoverflow; 11.05.2014
comment
@gstackoverflow, и мой вопрос в том, что вы до сих пор не понимаете. - person Luiggi Mendoza; 11.05.2014
comment
похоже, я не понимаю условия использования return-type-substitutable - person gstackoverflow; 11.05.2014
comment
@ Luiggi Mendoza из jls: Если объявление метода d1 с типом возвращаемого значения R1 переопределяет или скрывает объявление другого метода d2 с типом возвращаемого значения R2, ​​то d1 должен быть заменяемым по типу возвращаемого значения (§8.4.5) для d2, или возникает ошибка времени компиляции - person gstackoverflow; 11.05.2014
comment
@gstackoverflow хорошо, сначала вы должны понять текущее правило в своем исходном вопросе. После этого попробуйте разработать кейс, чтобы нарушить другое правило. Например, иметь метод с той же сигнатурой и другим типом возвращаемого значения. - person Luiggi Mendoza; 11.05.2014
comment
Я сделал это. пожалуйста, подтвердите - person gstackoverflow; 11.05.2014

Если объявление метода d1 с типом возвращаемого значения R1 переопределяет или скрывает объявление другого метода d2 с типом возвращаемого значения R2, ​​то d1 должен быть заменяемым по типу возвращаемого значения (§8.4.5) для d2, или ошибка времени компиляции возникает как d2. (§8.4.2) и R1 = |R2|.

Разобьем это на части:

Во-первых, нам нужно объявление метода d2 и тип возвращаемого значения R2:

class SomeClass {
    public List<String> d2() {
        return null;
    }
}

Теперь мы определяем класс, расширяющий SomeClass, который определяет d1 и возвращает R1.

class AnotherClass extends SomeClass {
    //@Override annotation means it is overriding a method in parent class
    //d2 here is d1
    //Object here is R1
    @Override
    public List<String> d2() {
    }
}

Как это можно скомпилировать? Поскольку d1 является типом возвращаемого значения, замещающим d2, это означает, что R1 может заменить R2 в качестве типа возвращаемого значения. Это также называется ковариантным типом возврата и рассматривается в Учебники по Java. Возврат значения из метода. Это подтверждается другим правилом:

Объявление метода d1 с типом возвращаемого значения R1 может быть заменено по типу возвращаемого значения другим методом d2 с типом возвращаемого значения R2, ​​если выполняется любое из следующих условий: Если R1 является ссылочным типом, то выполняется одно из следующих условий: подпись как d2 (§8.4.2) и R1 = |R2|.

Это означает, что все эти методы допустимы для переопределения SomeClass#d2:

class AnotherClass extends SomeClass {
    //ArrayList implements List
    @Override
    public ArrayList<String> d2() {
        return null;
    }
}

class AnotherClass extends SomeClass {
    //raw List can be casted to `List<String>` by unchecked conversion
    //means you don't need a explicit cast to convert `List` to `List<String>`
    //due to type erasure
    @Override
    public List d2() {
        return null;
    }
}

class AnotherClass extends SomeClass {
    //combination of both examples above
    @Override
    public ArrayList d2() {
        return null;
    }
}

Но это недопустимые реализации:

class AnotherClass extends SomeClass {
    //the signature is not the same
    @Override
    public List<String> d1() {
        return null;
    }
}

class AnotherClass extends SomeClass {
    //Set is not a subtype of List
    @Override
    public Set<String> d2() {
        return null;
    }
}

class AnotherClass extends SomeClass {
    //Collection is not a subtype of List
    @Override
    public Collection<String> d2() {
        return null;
    }
}
person Luiggi Mendoza    schedule 11.05.2014
comment
проблема со следующей фразой: d1 не имеет той же подписи, что и d2 - person gstackoverflow; 11.05.2014
comment
@gstackoverflow вы знаете, что такое подпись? - person Luiggi Mendoza; 11.05.2014
comment
это имя метода и список аргументов - person gstackoverflow; 11.05.2014
comment
@gstackoverflow хорошо, тогда в последних фрагментах кода d1 и d2 не имеют одинаковой подписи, поэтому ошибка компилятора. Какую часть этого ты не понимаешь? - person Luiggi Mendoza; 11.05.2014
comment
В соответствии с этими правилами **, если d1 не имеет той же подписи, что и d2 (§8.4.2), и R1 = |R2|**, то объявление метода d1 с типом возвращаемого значения R1 можно заменить другим методом. d2 тогда ... - person gstackoverflow; 11.05.2014
comment
Если объявление метода d1 с типом возвращаемого значения R1 переопределяет или скрывает объявление другого метода d2 с типом возвращаемого значения R2, ​​то d1 должен быть заменяемым по типу возвращаемого значения (§8.4.5) для d2, иначе возникнет ошибка времени компиляции. происходит - person gstackoverflow; 11.05.2014
comment
@gstackoverflow, но это не применяется для переопределения метода. Вы понимаете, что значит переопределить? Для переопределения вам нужно, чтобы оба метода имели одинаковую подпись. Поскольку момент d1 и d2 не имеет одинаковой подписи, это вообще не имеет приоритета. Период. - person Luiggi Mendoza; 11.05.2014
comment
@gstackoverflow, кстати, ссылки в моих ответах не для украшения. Они дополняют ответ. Я рекомендую вам прочитать их. - person Luiggi Mendoza; 11.05.2014
comment
ааааааааааааааааааа... - person gstackoverflow; 11.05.2014
comment
@gstackoverflow, если d1 и d2 не имеют одинаковой подписи, мы не можем говорить ни о переопределении, ни о сокрытии. Вы знаете об этом? - person Luiggi Mendoza; 11.05.2014
comment
Я знаю об этом, но я пытался слепо следовать правилам - person gstackoverflow; 11.05.2014
comment
Это правило d1 не имеет той же подписи, что и d2 для сокрытия только в этом случае... - person gstackoverflow; 11.05.2014
comment
Как я понимаю, вы акцентируете внимание на переопределении в Если объявление метода d1 с типом возвращаемого значения R1 переопределяет или скрывает объявление другого метода d2 с типом возвращаемого значения R2, ​​то d1 должен быть замещаемым по типу возвращаемого значения (§8.4.5) для d2, или возникает ошибка времени компиляции как d2 (§8.4.2) и R1 = |R2|.. - person gstackoverflow; 11.05.2014