2015-09-05 4 views
10

Рассмотрим следующие два класса:Почему невозможно использовать примитивные типы с полиморфными типами возврата?

public interface Foo<T> 
{ 
    public T moo(); 
} 

public class IntFoo implements Foo<Integer> 
{ 
    public int moo() 
    { 
     return 0; 
    } 
} 

Этот код выдаст ошибку в publicintmoo, говоря, что int несовместима с перекрытых метода обратного типа Integer. Строго говоря, это верно, поскольку int не напрямуюInteger. Тем не менее, мы все знаем, что они могут быть неявно преобразованы друг в друга с использованием автоматического (un) бокса. Что менее знают тот факт, что компилятор генерирует метод моста в этом примере:

public class IntFoo implements Foo<Integer> 
{ 
    public <synthetic> <bridge> Object moo() 
    { 
     return this.moo(); // upcast 
    } 

    public Integer moo() { 
     return 0; 
    } 
} 

Это должно быть сделано, потому что JVM различает типы возвращаемого при разрешении методов, а так как стертое возвращение типа Foo.moo является Object, компилятор сгенерировал мостовой метод с той же сигнатурой, что и метод.

Я задаюсь вопросом, почему это не будет работать с примитивными полиморфных типов возврата, а также:

public class IntFoo implements Foo<Integer> 
{ 
    public <synthetic> <bridge> Object moo() 
    { 
     return Integer.valueOf(this.moo()); 
    } 

    public int moo() 
    { 
     return 0; 
    } 
} 

Там, кажется, не будет какой-либо причине не имеют этой функции:

IntFoo intFoo = new IntFoo(); 
Foo<Integer> foo = intFoo; 
Integer i = foo.moo(); // calls the synthetic method, which boxes the result of the actual implementation 

Фактически, этот скриншот сеанса REPL показывает, что я даже смог реализовать это в своем custom programming language (который скомпилирован до байт-кода Java):

REPL session

+0

Приятно знать о методе синтаксического моста, который был для меня новым. – Mifeet

ответ

6

Как всегда с этими вопросами, ответ заключается в том, что вы должны спросить у дизайнеров языка. Я не вижу причин, почему это невозможно. Однако, на мой взгляд, эта функция будет довольно бессмысленной. Как вы указываете в вопросе, только когда moo вызывается для переменной статического типа IntFoo, что примитив будет возвращен; на переменной типа Foo<Integer>, Integer будет возвращаться в любом случае. Таким образом, вы можете добиться практически того же самого, выполнив это.

public class IntFoo implements Foo<Integer> { 

    @Override 
    public Integer moo() { return mooAsInt(); } 

    public int mooAsInt() { return 0; } 
} 

Лично я считаю, что это лучше, потому что это гораздо более очевидно, когда бокс делает/не происходит. В вашем предложении moo() может вернуть int или Integer в зависимости от статического типа переменной, что было бы крайне запутанным.

+0

Я не думаю, что эта функция была бы бессмысленной любыми способами: конкретным примером может служить класс «BitSet», который расширяет «Set ». Таким образом, вы можете получить бесплатную версию BitSet без использования бокса и использовать функциональность Set , если что-то ожидает общий набор. – Clashsoft

+0

@Clashsoft Я полностью согласен с этим. Я сам спорил о чем-то подобном (см. Вопрос «SparseArray vs HashMap»), где я утверждаю, что 'SparseArray ' должен был реализовывать 'Map '. Таким образом, он может рассматриваться как «Карта», когда требуется, но имеет дело только с примитивными ключами. Это было бы здорово, но просто вы можете это сделать, перегрузив методы - нет необходимости в новых правилах. –

+0

Но вы можете перегружать только типы параметров, а не типы возвращаемых данных. Это делает невозможным переопределить Map в вашем сценарии, а это значит, что вам понадобится дополнительная оболочка, которая реализует интерфейс Map и делегирует экземпляр SpareArray. – Clashsoft

2

Проблема с примитивами заключается в том, что они нуждаются в различном объеме пространства в стеке ... В отличие от ссылок на объекты, которые все занимают одинаковое пространство в стеке. И у вас не может быть стековый фрейм с переменным размером, потому что в противном случае во время выполнения стек не знал бы, куда вернуться к методу exit

+2

Где вам нужны разные размеры стека в моем примере? Если вы вызываете метод overridden напрямую, вы получаете «int» из реализации, иначе метод моста вызывается и обрабатывает бокс для вас. – Clashsoft

3

По моему скромному мнению, причина чисто синтаксическая. Как вы продемонстрировали в вопросе, можно сгенерировать метод, возвращающий int, и способ, реализующий метод интерфейса Foo, возвращающий Object в байт-код.

Однако, если посмотреть на проблему с точки синтаксиса Java зрения (т.е. не байт-код),

public class IntFoo implements Foo<Integer> { 
    public int moo() { return 0; } 
} 

должен переопределить метод Integer moo(), который он не делает. Как вы сказали сами, Java отличает типы возврата, а int moo() - это не то же самое, что и Integer moo().

+0

Действительно, не может быть очевидно, что 'int moo()' переопределяет 'T moo()' (с 'T = Integer'). Но если бы вы могли написать 'Foo ', все было бы более очевидно. Угадайте, что нам придется ждать Java 10, тогда:/ – Clashsoft

Смежные вопросы