2015-03-20 2 views
3

Пусть я 2 класса под названием RealNumber и IntNumber и метод называется plus:В этой иерархии классов Java, как использовать Generics, чтобы приведение типов было ненужным?

public class RealNumber<S extends RealNumber> { 
    public S plus(S realNumber) { 
     ... 
     } 
    } 

public class IntNumber 
     extends RealNumber<IntNumber> { } 

При использовании метода plus я получаю некоторые предупреждения компилятора и ошибки:

RealNumber x = new RealNumber(); 
IntNumber y = new IntNumber(); 

RealNumber sum1 = x.plus(x); // Warning: Unchecked call to 'plus(S)' as a member of raw type 'RealNumber'. 
RealNumber sum2 = x.plus(y); // Warning: Unchecked call to 'plus(S)' as a member of raw type 'RealNumber'. 
RealNumber sum3 = y.plus(x); // Error: plus (IntNumber) in RealNumber cannot be applied to (RealNumber). 
RealNumber sum4 = y.plus(y); // This works fine. 
IntNumber sum5 = y.plus(x); // Error: plus (IntNumber) in RealNumber cannot be applied to (RealNumber). 
IntNumber sum6 = y.plus(y); // This works fine. 

Чтобы это исправить, я изменить метод plus следующим образом:

public <T extends RealNumber> S plus(T realNumber) { 
    ... 
    } 

И теперь все работает отлично. Хорошо! Однако, я хочу сейчас, чтобы добавить третий класс под названием PositiveIntNumber, расширяющий IntNumber:

public class PositiveIntNumber extends IntNumber { } 

Это, конечно, не работает:

RealNumber x = new RealNumber(); 
IntNumber y = new IntNumber(); 
PositiveIntNumber z = new PositiveIntNumber(); 

RealNumber sum1 = x.plus(x); // Fine. 
RealNumber sum2 = x.plus(y); // Fine. 
RealNumber sum3 = y.plus(x); // Fine. 
RealNumber sum4 = y.plus(y); // Fine. 
IntNumber sum5 = y.plus(x); // Fine. 
IntNumber sum6 = y.plus(y); // Fine. 
RealNumber sum7 = x.plus(z); // Fine. 
IntNumber sum8 = y.plus(z); // Fine. 
PositiveIntNumber sum9 = z.plus(x); // Error: incompatible types: no instance(s) of type variable(s) T exist so that IntNumber conforms to PositiveIntNumber 
PositiveIntNumber sum10 = z.plus(y); // Error: incompatible types: no instance(s) of type variable(s) T exist so that IntNumber conforms to PositiveIntNumber 
PositiveIntNumber sum11 = z.plus(z); // Error: incompatible types: no instance(s) of type variable(s) T exist so that IntNumber conforms to PositiveIntNumber 

Чтобы исправить это снова, я изменять определения классов, следующим образом:

public class IntNumber<S extends IntNumber> 
     extends RealNumber<S> { } 

public class PositiveIntNumber 
     extends IntNumber<PositiveIntNumber> 
    { } 

Это решает проблему PositiveIntNumber, но ломает IntNumber:

RealNumber x = new RealNumber(); 
IntNumber y = new IntNumber(); 
PositiveIntNumber z = new PositiveIntNumber(); 

RealNumber sum1 = x.plus(x); // Fine. 
RealNumber sum2 = x.plus(y); // Fine. 
RealNumber sum3 = y.plus(x); // Fine. 
RealNumber sum4 = y.plus(y); // Fine. 
IntNumber sum5 = y.plus(x); // Error: incompatible types: RealNumber cannot be converted to IntNumber. 
IntNumber sum6 = y.plus(y); // Error: incompatible types: RealNumber cannot be converted to IntNumber. 
RealNumber sum7 = x.plus(z); // Fine. 
IntNumber sum8 = y.plus(z); // Error: incompatible types: RealNumber cannot be converted to IntNumber. 
PositiveIntNumber sum9 = z.plus(x); // Fine. 
PositiveIntNumber sum10 = z.plus(y); // Fine. 
PositiveIntNumber sum11 = z.plus(z); // Fine. 

Литая фиксирует это, но это не должно быть необходимым, на мой взгляд:

IntNumber sum5 = (IntNumber)y.plus(x); 

Итак, у меня есть два вопроса:

1) Поскольку y является IntNumber, и возвращаемый тип S extends IntNumber, почему y.plus (...) return RealNumber?

2) Как исправить это?

Edit: Написание RealNumber<RealNumber> должно быть ненужным, так как RealNumber<IntNumber> и RealNumber<PositiveIntNumber> не делают абсолютно никакого смысла. Поэтому, возможно, полное использование таких дженериков просто неверно. Я думаю, что это отвечает на вопрос 1: y.plus(...) возвращает RealNumber, потому что y является сырым, поэтому система типа Java просто больше не заботится о том, чтобы S расширяет IntNumber. Однако я хочу избежать повторения метода plus во всех подклассах RealNumber, и я должен каким-то образом использовать дженерики, чтобы этого избежать. Таким образом, вопрос 2 все еще стоит. Что мне делать?

Примечание: Мои фактические классы не являются действительными и целыми, а некоторые другие сложные бизнес-классы. Думайте о них как о классах A, B, C и не ставьте под сомнение модель. Они являются «добавочными», да, но здесь X.plus (Y) должен возвращать тип X для каждого X и Y.

+4

Для одного, 'RealNumber' является общим. Параметрируйте его использование. Не используйте необработанные типы. –

+0

Вы должны обратить больше внимания на предупреждение, подобное этому * «Предупреждение: Непроверенный вызов« плюс (S) »в качестве члена необработанного типа« RealNumber ». * *. Как и это предупреждение, и @SotiriosDelimanolis говорит вам, не используйте вместо него исходный «RealNumber» Parameterize: «RealNumber x = new RealNumber <>();' – Tom

+0

Я не получаю 'sum5'. Почему «IntNumber» плюс «RealNumber» дают «IntNumber»? –

ответ

1

Все комментарии, приведенные выше, являются правильными. если вы определяете общий класс, вы должны определить его также, если вы его используете.

public class GenericNumber { 

    public static void main(String[] args) { 
     // ------ 
     // ------ The important change 
     // ------ 
     RealNumber<RealNumber> x = new RealNumber<>();    
     IntNumber<IntNumber> y = new IntNumber<>(); 
     PositiveIntNumber z = new PositiveIntNumber(); 
     // Everything works fine 
     RealNumber sum1 = x.plus(x); 
     RealNumber sum2 = x.plus(y); 
     RealNumber sum3 = y.plus(x); 
     RealNumber sum4 = y.plus(y); 
     IntNumber sum5 = y.plus(x); 
     IntNumber sum6 = y.plus(y); 
     PositiveIntNumber sum9 = z.plus(x); 
     PositiveIntNumber sum10 = z.plus(y); 
     PositiveIntNumber sum11 = z.plus(z); 
    } 

    public static class RealNumber<S extends RealNumber> { 

     public <T extends RealNumber> S plus(T realNumber) { 
      return null; 
     } 
    } 

    public static class IntNumber<S extends IntNumber> extends RealNumber<S> { 
    } 

    public static class PositiveIntNumber extends IntNumber<PositiveIntNumber> { 
    } 

} 
+0

Проблема заключается в том, что запись 'RealNumber ' должна быть ненужной, поскольку 'RealNumber ' и' RealNumber 'не имеют абсолютно никакого смысла.Поэтому, возможно, полное использование таких дженериков просто неверно. Тем не менее, я хочу избежать повторения метода «плюс» во всех подклассах «RealNumber», и я должен был бы использовать дженерики, чтобы каким-то образом избежать этого. Или нет? – MarcG

+0

Вы правы. В вашем случае 'RealNumber ' не имеет смысла, но это возможно с вашим определением. Если вы определяете общий тип типа «RealNumber », вам нужно объявить общий тип, если вы его используете (т. Е. RealNumber ). Взгляните на http://docs.oracle.com/javase/tutorial/java/generics/types.html#instantiation – drkunibar

+0

Так что мое определение неверно. Это я знал с самого начала. Мой вопрос - как это исправить. Мне нужно иметь новое определение ... Использовать дженерики каким-то другим способом, чем тот, который я представил ... Или, может быть, дженерики - неправильный инструмент, и я должен снова повторять метод «плюс» снова и снова? ... – MarcG

2

Я предполагаю, что вы пытаетесь создать иерархию типа, который выражает «Addables», которые возвращают данные, два «операндов», более общее из двух, с помощью наследования для моделирования математической взаимосвязи "особый случай".

Прежде чем идти дальше, вы должны рассмотреть this link, который использует пример Square is-a Rectangle, чтобы проиллюстрировать, почему использование наследования часто является плохой идеей, но в случае неизменяемых объектов, таких как те, которые вы предлагаете вы можете быть в порядке.

Вы хотите, чтобы как Int.plus (Real), так и Real.plus (Int) возвращали Real и Int.plus (Int), чтобы вернуть Int. Такая же модель должна работать для Nat.plus (Int) и т. Д.

Пока все типы статически известны, это должно быть кусок торта по типу.

class Real { 
    Real plus(Real r) { ... } 
    Int floor() { ... } 
} 
class Int extends Real { 
    Int plus(Int i) { 
     return plus(i.asReal()).floor(); 
    } 
    Nat abs() { ... } 
    Real asReal() { 
     return this; 
    } 
} 
class Nat extends Int { 
    Nat plus(Nat n) { 
     return plus(n.asInt()).abs(); 
    } 
    Int asInt() { 
     return this; 
    } 
} 

Перегрузки plus позволяет компилятору определить «наиболее специфический» плюс операции, которые могут быть статически проверить. Компилятор не может знать, что добавление двух реалов, которые являются целыми, приводит к другому реальному, что является целым числом. В этом случае некоторые нецелые числа объединяются в целые числа; Я уверен, что вы не собираетесь, чтобы система типов каким-то образом представляла этот факт.

В соответствии с этой договоренностью все, кроме sum5, sum9, и sum10 Тип проверки, которая (на мой взгляд) точно так, как должна быть.

Кстати, в отношении шаблона «странно повторяющегося типа», который вы пытались продемонстрировать, вы делаете это немного неправильно. Вы должны указать параметр типа везде происходит общий тип, даже внутри типа связал себя:

class G< T extends G<T> > {} 

Вы, и все остальные комментаторы и ответчики здесь, были убрав вторую T.

+0

Одна вещь, которую я изучил из этого моего вопроса, заключается в том, что в SO вы всегда должны указывать свои классы A, B, C. Если вы этого не сделаете, люди начнут подвергать сомнению вашу модель. Вы говорите, что 'Int.plus (Real)' должен возвращать 'Real'. Однако мои фактические классы здесь не Реальные и Целые, а некоторые другие сложные бизнес-классы. Они «добавочные», да, но здесь A.plus (B) должен вернуть A, для каждого A и B. – MarcG

+0

Я задал еще один вопрос, который лучше отражает мою проблему, здесь: http://stackoverflow.com/questions/ 29188497/использование-оф-воспроизведенного-в-окончательной-метода, который-возвраты-а-значение-оф-же типа в своем-о – MarcG