Молимо погледајте ова честа питања за више информација:
ОЗИКС0З
Ево укратко неких важних тачака из овог често постављаног питања.
Пример (генеричког типа):
class Pair<X,Y> {
private X first;
private Y second; public Pair(X a1, Y a2) {
first = a1;
second = a2;
}
public X getFirst() { return first; }
public Y getSecond() { return second; }
public void setFirst(X arg) { first = arg; }
public void setSecond(Y arg) { second = arg; }
}
Класа Пар има два параметра типа Кс и И . Они се замењују аргументима типа када је генерички тип Пар је инстанциран. На пример, у декларацији ПаирОЗИКС0ЗСтринг, ДатеОЗИКС1З параметар типа Кс се замењује аргументом типа Стринг и И is replaced by Датум .
Обим идентификатора Кс и И је целокупна дефиниција класе. У овом опсегу два параметра типа Кс и И се користе као и типови (са неким ограничењима). У горњем примеру, параметри типа се користе као аргумент и тип враћања метода инстанце и типови поља инстанце.
Параметри типа се могу декларисати са границама. Границе дају приступ методама непознатог типа за који се означава параметар типа. У нашем примеру, ми не позивамо методе непознатог типа Кс и И . Из тог разлога, два параметра типа су неограничена.
Пример (конкретног параметаризованог типа):
публиц воид принтПаир( ПаирОЗИКС0ЗСтринг,ЛонгОЗИКС1З пар) {
System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} ПаирОЗИКС0ЗСтринг,ЛонгОЗИКС1З граница = ново ПаирОЗИКС0ЗСтринг,ЛонгОЗИКС1З ("максимално",1024Л);
принтПаир(лимит);
Пример (параметаризованог типа са џокер знаком):
публиц воид принтПаир( ПаирОЗИКС0З?,?ОЗИКС1З пар) {
System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} ПаирОЗИКС0З?,?ОЗИКС1З граница = ново ПаирОЗИКС0ЗСтринг,ЛонгОЗИКС1З ("максимално",1024Л);
принтПаир(лимит);
Све инстанције генеричког типа деле исту репрезентацију типа времена извршавања, односно репрезентацију сировог типа. На пример, инстанције генеричког типа Лист , као што је ЛистОЗИКС0ЗДатеОЗИКС1З , ЛистОЗИКС0ЗСтрингОЗИКС1З , ЛистОЗИКС0ЗЛонгОЗИКС1З , итд. имају различите статичке типове у време компајлирања, али исти динамички тип Лист у време извођења.
Глумачка екипа се састоји од два дела:
- статичку проверу типа коју изводи компајлер у време компајлирања и
- динамичка провера типа коју врши виртуелна машина у току рада.
Статички део разврстава бесмислене цаст, који не могу успети, као што је цаст из Стринг то Датум или од ЛистОЗИКС0ЗСтрингОЗИКС1З то ЛистОЗИКС0ЗДатеОЗИКС1З .
Динамички део користи информације о типу времена извршавања и врши проверу типа у току извршавања. То подиже а ЦлассЦастЕкцептион ако динамички тип објекта није циљни тип (или подтип циљног типа) цаст. Примери ливења са динамичким делом су одливци из Објекат то Стринг или од Објекат то ЛистОЗИКС0ЗСтрингОЗИКС1З . То су такозвана спуштања, од супертипа до подтипа.
Примери конкретних параметризованих типова су ЛистОЗИКС0ЗСтрингОЗИКС1З , МапОЗИКС0ЗСтринг,ДатеОЗИКС1З , али не ЛистОЗИКС0З? проширује БројОЗИКС1З или МапОЗИКС0ЗСтринг,?ОЗИКС1З .
Понекад се очекује да а ЛистОЗИКС0ЗОбјецтОЗИКС1З био би супертип а ЛистОЗИКС0ЗСтрингОЗИКС1З , јер Објекат је супертип Стринг . This expectation stems from the fact that such a type relationship exists for arrays: Објекат[] је супертип Стринг[] , јер Објекат је супертип Стринг . (Ова врста односа је позната као коваријанса .) Однос супер-подтипова типова компоненти проширује се на одговарајуће типове низа. Такав однос типа не постоји за инстанцирање генеричких типова. (Параметризовани типови су не коваријантна.)
Недостатак односа супер-подтипова међу инстанцијама истог генеричког типа има различите последице. Ево примера.
Пример:
воид принтАлл(АрраиЛистОЗИКС0ЗОбјецтОЗИКС1З ц) {
for (Object o : c)
System.out.println(o);
} АрраиЛистОЗИКС0ЗСтрингОЗИКС1З лист = нев АрраиЛистОЗИКС2ЗСтрингОЗИКС3З();
... попуни листу ...
принтАлл(лист); // грешка
А АрраиЛистОЗИКС0ЗСтрингОЗИКС1З објекат не може бити прослеђен као аргумент методи која тражи а АрраиЛистОЗИКС0ЗОбјецтОЗИКС1З јер су ова два типа инстанције истог генеричког типа, али за другачије аргументи типа, и из тог разлога нису компатибилни један са другим.
Конкретни параметризовани типови су конкретне инстанције генеричког типа. Они су скоро као типови; постоји само неколико ограничења. Не могу се користити у следеће сврхе:
- за креирање низова
- у обради изузетака
- у класи литерал
- у ан инстанцеоф израз
Низови који садрже елементе чији је тип конкретан параметризовани тип су недозвољени.
Пример (незаконитог типа низа):
статички воид тест() {
ПаирОЗИКС0ЗИнтегер,ИнтегерОЗИКС1З[] интПаирАрр = ново ПаирОЗИКС1ЗИнтегер,ИнтегерОЗИКС2ЗОЗИКС0З ; // грешка
addElements(intPairArr);
Pair<Integer,Integer> pair = intPairArr[1];
Integer i = pair.getFirst();
pair.setSecond(i);
}
статиц воид аддЕлементс( Објекат[] објАрр) {
objArr[0] = new Pair<Integer,Integer>(0,0);
objArr[1] = new Pair<String,String>("",""); // should fail with ArrayStoreException
}
Компајлер забрањује креирање низова чији је тип компоненте конкретан параметризовани тип, нпр ПаирОЗИКС0ЗИнтегер,ИнтегерОЗИКС1З у нашем примеру. У претходном уносу смо расправљали зашто је разумно да компајлер квалификује а ПаирОЗИКС0ЗИнтегер,ИнтегерОЗИКС1З[] као незаконито. Кључни проблем је у томе што компајлер и систем времена извршавања морају осигурати да је низ а хомогена редослед елемената истог типа. Једна од провера типа, односно провера низа-сторе-провера коју обавља виртуелна машина у току рада, не успева да открије увредљиво уметање ванземаљског елемента. У примеру, друго уметање у аддЕлементс метода би требало да не успе, јер су додавали пар стрингова низу интегралних вредности, али не успева како се очекивало. Разлози су разматрани у претходном уносу.
Ако не можемо да користимо низове који садрже елементе чији је тип конкретан параметризовани тип, шта користимо као заобилазно решење?
Хајде да размотримо 3 замислива решења:
- низ сирових врста
- низ неограниченог џокерског параметаризованог типа
- збирка уместо низа
Док су низови конкретних параметризованих типова недозвољени, колекције конкретних параметризованих типова су дозвољене.
Параметаризовани џокер тип је инстанција генеричког типа где је бар један аргумент типа џокер знак. Примери параметаризованих типова џокер знакова су ЦоллецтионОЗИКС0З?ОЗИКС1З , ЛистОЗИКС0З? проширује БројОЗИКС1З , ЦомпараторОЗИКС0З? супер СтрингОЗИКС1З и ПаирОЗИКС0ЗСтринг,?ОЗИКС1З . Параметаризовани тип са џокер знаком означава породицу типова која се састоји од конкретних инстанција генеричког типа. Врста џокера који се користи одређује који конкретни параметризовани типови припадају породици. На пример, џокер параметаризовани тип ЦоллецтионОЗИКС0З?ОЗИКС1З означава породицу свих инстанција Цоллецтион интерфејс без обзира на аргумент типа. Параметризовани џокер тип ЛистОЗИКС0З? проширује БројОЗИКС1З denotes the family of all list types where the element type is a subtype of Број . Параметризовани џокер тип ЦомпараторОЗИКС0З? супер СтрингОЗИКС1З је породица свих инстанција Цомпаратор интерфејс за типове аргумената типа који су супертипови Стринг.
Параметризовани тип са џокер знаком није тип у редовном смислу (различит од непараметризоване класе/интерфејса или сировог типа).
За куцање се могу користити џокерски параметризовани типови (попут непараметризованих класа и интерфејса):
- као аргументи и типови повратних метода
- као тип поља или локалне референтне променљиве
- као тип компоненте аргумента типа низа других параметризованих типова
- као циљни тип у бацањима
Не само да типови могу бити генерички, већ и методе могу бити генеричке. Статичке и нестатичке методе као и конструктори могу имати параметре типа. Синтакса за декларацију параметара формалног типа је слична синтакси за генеричке типове. Одељак параметара типа је ограничен угаоним заградама и појављује се пре повратног типа методе. Његова синтакса и значење су идентични листи параметара типа генеричког типа.
Ево примера генеричког мак метода која израчунава највећу вредност у колекцији елемената непознатог типа А .
Пример (генеричке методе):
цласс Цоллецтионс {
public static ОЗИКС0ЗА проширује УпоредивоОЗИКС1ЗАОЗИКС2ЗОЗИКС3З А мак(ЦоллецтионОЗИКС0З А ОЗИКС0З кс) {
Iterator< А ОЗИКС0З ки = кс.итератор();
А в = ки.нект();
while (xi.hasNext()) {
А к = ки.нект();
if (w.compareTo(x) < 0) w = x;
}
return w;
}
}
Тхе мак метода има један параметар типа, именован А . То је држач места за тип елемента колекције на којој метода ради. Параметар типа има ограничење; мора бити тип који је подтип ЦомпараблеОЗИКС0ЗАОЗИКС1З
Пример (овог генеричког типа, овог пута са границе):
јавни интерфејс ЦомпараблеОЗИКС0ЗТОЗИКС1З {
public int compareTo(T arg);
}
јавна класа ТрееМапОЗИКС0ЗКеи проширује ЦомпараблеОЗИКС0ЗКеиОЗИКС1З ,ДатаОЗИКС0З{
private static class Entry<K,V> {
K key;
V value;
Entry<K,V> left = null;
Entry<K,V> right = null;
Entry<K,V> parent;
}
private transient Entry<Key,Data> root = null;
...
private Entry<Key,Data> getEntry(Key key) {
Entry<Key,Data> p = root;
Key k = key;
while (p != null) {
int cmp = k. упореди са(п.кључ) ;
if (cmp == 0)
return p;
else if (cmp < 0)
p = p.left;
else
p = p.right;
}
return null;
}
public boolean containsKey(Key key) {
return getEntry(key) != null;
}
...
}
У горњем примеру, параметар типа Кључ има везу ЦомпараблеОЗИКС0ЗКеиОЗИКС1З . Спецификација везе има два ефекта:
- Даје приступ методама које боунд специфицира . У примеру, везани ЦомпараблеОЗИКС0ЗКеиОЗИКС1З даје приступ цомпареТо метод који желимо да позовемо у имплементацији нашег ТрееМап класе.
- За инстанцирање генеричког типа могу се користити само типови “у границама”. In the example, a parameterized type such as ТрееМапОЗИКС0ЗНумбер,СтрингОЗИКС1З био би одбијен, јер тип Број није подтип ЦомпараблеОЗИКС0ЗНумберОЗИКС1З . Параметризовани тип као што је ТрееМапОЗИКС0ЗСтринг,СтрингОЗИКС1З би било прихваћено, јер је тип Стринг унутар граница, тј. представља подтип ЦомпараблеОЗИКС0ЗСтрингОЗИКС1З .
Све класе, интерфејси и типови енума могу се користити као везани параметри типа, укључујући угнежђене и унутрашње типове. Ни примитивни типови ни типови низа се не користе као везани параметар типа.
Примери (границе параметара типа):
класа Кс0 ОЗИКС0ЗТ продужава инт ОЗИКС0З { ... } // грешка
класа Кс1 ОЗИКС0ЗТ продужава Објекат[] ОЗИКС0З { ... } // грешка
класа Кс2 ОЗИКС0ЗТ продужава Број ОЗИКС0З { ... }
класа Кс3 ОЗИКС0ЗТ продужава Стринг ОЗИКС0З { ... }
класа Кс4 ОЗИКС0ЗТ продужава Руннабле ОЗИКС0З { ... }
класа Кс5 ОЗИКС0ЗТ продужава Тхреад.Стате ОЗИКС0З { ... }
класа Кс6 ОЗИКС0ЗТ продужава Лист ОЗИКС0З { ... }
класа Кс7 ОЗИКС0ЗТ продужава ЛистОЗИКС0ЗСтрингОЗИКС1З ОЗИКС0З { ... }
класа Кс8 ОЗИКС0ЗТ продужава ЛистОЗИКС0З? проширује БројОЗИКС1З ОЗИКС0З { ... }
класа Кс9 ОЗИКС0ЗТ продужава ЦомпараблеОЗИКС0З? супер НумберОЗИКС1З ОЗИКС0З { ... }
класа Кс10ОЗИКС0ЗТ продужава Мап.ЕнтриОЗИКС0З?,?ОЗИКС1З ОЗИКС0З { ... }
Узорак кода показује да примитивни типови као нпр инт и типови низова као што су Објекат[] нису дозвољени као везани параметар типа.
Типови класа, као нпр Број или Стринг , и типови интерфејса, као што су Руннабле , дозвољени су као везани параметар типа.
Енум типови, као што су Тхреад.Стате такође су дозвољени као везани параметар типа. Тхреад.Стате је пример угнежђеног типа који се користи као везани параметар типа. Дозвољени су и нестатични унутрашњи типови.
Необрађени типови су дозвољени као везани за параметар типа; Лист је пример.
Параметризовани типови су дозвољени као везани за параметар типа, укључујући конкретне параметризоване типове као што су ЛистОЗИКС0ЗСтрингОЗИКС1З , ограничени џокерски параметризовани типови као што су ЛистОЗИКС0З? проширује БројОЗИКС1З и ЦомпараблеОЗИКС0З? супер ЛонгОЗИКС1З , и неограничени џокерски параметризовани типови као што су Мап.ЕнтриОЗИКС0З?,?ОЗИКС1З . Граница која је џокер параметризовани тип дозвољава као аргумент типа све типове који припадају породици типова коју џокер означава. Веза параметаризованог типа са џокером даје само ограничен приступ пољима и методама; ограничења зависе од врсте џокера.
Пример (законски параметризованог типа као везаног параметра типа):
класа КСОЗИКС0З Т продужава ЛистОЗИКС0З? проширује БројОЗИКС1З ОЗИКС0З {
public void someMethod(T t) {
t.add(new Long(0L)); // грешка
Number n = t.remove(0);
}
}
класа тест {
public static void main(String[] args) {
КСОЗИКС0ЗАрраиЛистОЗИКС1З Дуго >> x1 = new X<ArrayList<Long>>();
КСОЗИКС0ЗАрраиЛистОЗИКС1З Стринг ОЗИКС0ЗОЗИКС1З к2 = нев КСОЗИКС2ЗАрраиЛистОЗИКС3ЗСтрингОЗИКС4ЗОЗИКС5З(); // грешка
}
}
Референтне варијабле типа Т (параметар типа) се третирају као референтне променљиве џокер типа (везани параметар типа). У нашем примеру последица је да компајлер одбија позивање метода које узимају аргумент „непознатог“ типа који означава параметар типа, као што је Лист.адд , јер је граница џокерски параметризовани тип са горњом границом.
У исто време везани ЛистОЗИКС0З? проширује БројОЗИКС1З одређује типове који се могу користити као аргументи типа. Компајлер прихвата све аргументе типа који припадају породици типова ЛистОЗИКС0З? проширује БројОЗИКС1З , односно сви подтипови Лист са аргументом типа који је подтип Број .
Параметар типа се може користити као граница другог параметра типа.
Пример (параметра типа који се користи као везани параметар типа):
класа Трипле ОЗИКС0ЗТОЗИКС1З {
private T fst, snd, trd;
public < У продужава Т , В продужава Т , В продужава Т ОЗИКС0З Трипле(У арг1, В арг2, В арг3) {
fst = arg1;
snd = arg2;
trd = arg3;
}
}
У овом примеру параметар типа Т параметризоване класе се користи као веза параметара типа У , В и В параметаризоване методе инстанце те класе.
Додатне могућности за коришћење параметара типа као граница других параметара типа укључују ситуације у којима је угнежђени тип дефинисан унутар генеричког типа или је локална класа дефинисана унутар генеричког метода. Чак је дозвољено користити параметар типа као везан за други параметар типа у истом одељку параметара типа.
Веза која је класа даје приступ свим њеним јавним члановима, односно јавним пољима, методама и угнежђеним типовима. Само конструктори нису доступни, јер не постоји гаранција да поткласа везе има исте конструкторе као и везана.
Пример (класе која се користи као веза параметра типа):
јавни час СуперЦласс { // static members
public enum EnumType {THIS, THAT}
public static Object staticField;
public static void staticMethod() { ... }// non-static members
public class InnerClass { ... }
public Object nonStaticField;
public void nonStaticMethod() { ... }// constructors
public SuperClass() { ... }// private members
private Object privateField;...
}јавни завршни разред СомеЦлассОЗИКС0ЗТ продужава СуперЦласс ОЗИКС0З {
private T object;
public SomeClass(T t) { object = t; }public String toString() {
return
"static nested type : "+T.EnumType.class+"\n"
+"static field : "+T.staticField+"\n"
+"static method : "+T.staticMethod()+"\n"
+"non-static nested type: "+T.InnerClass.class+"\n"
+"non-static field : "+object.nonStaticField+"\n"
+"non-static method : "+object.nonStaticMethod()+"\n"
+"constructor : "+(new T())+"\n" // error
+"private member : "+object.privateField+"\n" // error
;
}
}
Везани СуперЦласс даје приступ својим угнежђеним типовима, статичким пољима и методама и нестатичким пољима и методама. Само конструктор није доступан. То је зато што се конструктори не наслеђују. Свака поткласа дефинише своје конструкторе и не мора да подржава конструкторе своје суперкласе. Стога не постоји гаранција да ће поткласа СуперЦласс имаће исти конструктор као и његова суперкласа.
Контекст у коме ЕнумОЗИКС0ЗЕ проширује ЕнумОЗИКС1ЗЕОЗИКС2ЗОЗИКС3З ” појављује се декларација Енум класа у пакету јава.ланг :
јавни апстрактни час ЕнумОЗИКС0ЗЕ проширује ЕнумОЗИКС1ЗЕОЗИКС2ЗОЗИКС3З {
...
}
Тип Енум је заједничка основна класа свих типова набрајања. У Јави тип набрајања као што је Боја се преводи у класу Боја који се протеже ЕнумОЗИКС0ЗЦолорОЗИКС1З . Сврха суперкласе Енум је да обезбеди функционалност која је заједничка свим типовима набрајања.
Ево скице класе Енум :
јавни апстрактни час ЕнумОЗИКС0З Е проширује ЕнумОЗИКС0ЗЕОЗИКС1ЗОЗИКС2З имплементира ЦомпараблеОЗИКС0З Е ОЗИКС0З, сериализабле {
private final String name;
public final String name() { ... } private final int ordinal;
public final int ordinal() { ... }protected Enum(String name, int ordinal) { ... }
public String toString() { ... }
public final boolean equals(Object other) { ... }
public final int hashCode() { ... }
protected final Object clone() throws CloneNotSupportedException { ... }
public final int compareTo( Е о) { ... }public final Class< Е ОЗИКС0З гетДецларингЦласс() { ... }
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { ... }
}
Изненађујућа карактеристика у декларацији “ ЕнумОЗИКС0ЗЕ проширује ЕнумОЗИКС1ЗЕОЗИКС2ЗОЗИКС3З “ је чињеница да новодефинисана класа Енум и његов новодефинисани параметар типа Е појављују се у граници тог параметра истог типа. То значи да је Енум тип мора бити инстанциран за један од његових подтипова. Да бисте разумели зашто ово има смисла, узмите у обзир да је сваки енум тип преведен у подтип Енум .
Ево измишљеног типа енума Боја :
енум Боја {ЦРВЕНО, ПЛАВО, ЗЕЛЕНО}
Преводилац га преводи у следећу класу:
јавни завршни час Боја протеже ЕнумОЗИКС0ЗЦолорОЗИКС1З {
public static final Color[] values() { return (Color[])$VALUES.clone(); }
public static Color valueOf(String name) { ... } private Color(String s, int i) { super(s, i); }public static final Color RED;
public static final Color BLUE;
public static final Color GREEN;private static final Color $VALUES[];
static {
RED = new Color("RED", 0);
BLUE = new Color("BLUE", 1);
GREEN = new Color("GREEN", 2);
$VALUES = (new Color[] { RED, BLUE, GREEN });
}
}
Наслеђе има ефекат да Боја тип наслеђује све методе имплементиране у ЕнумОЗИКС0ЗЦолорОЗИКС1З . Међу њима је и цомпареТо методом. Тхе Боја. цомпареТо метода би вероватно требало да узме а Боја као аргумент. Да би се ово десило час Енум је генерички и Енум.цомпареТо метода узима Енум параметар типа ‘с Е као аргумент. Као резултат, тип Боја изведено из ЕнумОЗИКС0ЗЦолорОЗИКС1З наслеђује а цомпареТо метода која захтева а Боја као и аргумент, баш како треба.
Ако сецирамо декларацију “ ЕнумОЗИКС0ЗЕ проширује ЕнумОЗИКС1ЗЕОЗИКС2ЗОЗИКС3З „Можемо видети да овај образац има неколико аспеката.
Прво, постоји чињеница да је везани параметар типа сам тип: “ Енум ОЗИКС0ЗЕ се протеже Енум ОЗИКС0ЗЕОЗИКС1ЗОЗИКС2З „. Обезбеђује да само подтипови типа Енум су дозвољени као аргументи типа. (Теоретски, тип Енум може бити инстанцирано на себи, као у ЕнумОЗИКС0ЗЕнумОЗИКС1З , али то свакако није замишљено и тешко је замислити ситуацију у којој би таква инстанција била корисна.)
Друго, постоји чињеница да је тип везани за параметар параметризовани тип Енум ОЗИКС0ЗЕОЗИКС1З , који користи параметар типа Е као аргумент типа везе. Ова декларација осигурава да однос наслеђивања између подтипа и инстанције Енум је облика Кс проширује ЕнумОЗИКС0ЗКСОЗИКС1З “. Подтип као што је “ Кс проширује ЕнумОЗИКС0ЗИОЗИКС1З ” не може бити декларисана због аргумента типа И не би било у границама; само подтипови ЕнумОЗИКС0ЗКСОЗИКС1З су у границама.
Треће, ту је чињеница да Енум је генерички на првом месту. То значи да неки од метода класе Енум узети аргумент или вратити вредност непознатог типа (или на други начин зависити од непознатог типа). Као што већ знамо, овај непознати тип ће касније бити подтип Кс оф ЕнумОЗИКС0ЗКСОЗИКС1З . Дакле, у параметризованом типу ЕнумОЗИКС0ЗКСОЗИКС1З , ове методе укључују подтип Кс , а наслеђују се у подтип Кс . Тхе цомпареТо метода је пример такве методе; наслеђује се од суперкласе у сваку подкласу и има специфичан потпис за подкласу у сваком случају.
Да сумирамо, декларација “ ЕнумОЗИКС0ЗЕ проширује ЕнумОЗИКС1ЗЕОЗИКС2ЗОЗИКС3З ” може се дешифровати као: Енум је генерички тип који се може инстанцирати само за своје подтипове, а ти подтипови ће наследити неке корисне методе, од којих неке узимају аргументе специфичне за подтип (или на други начин зависе од подтипа).
Параметри типа доње границе класа
Параметри типа могу имати неколико граница, као у класа БокОЗИКС0ЗТ проширује НумберОЗИКС1З {...} . Али параметар типа не може имати доњу границу, односно конструкцију као што је класа БокОЗИКС0ЗТ супер БројОЗИКС0З {...} није дозвољено. Зашто не? Одговор је: бесмислено је јер вам ништа не би купило да је дозвољено. Хајде да видимо зашто су параметри типа доње границе класа збуњујући тако што ћемо истражити шта значи горња граница параметра типа.
Горња граница параметра типа има три ефекта:
- Ограничена инстанција. Горња граница ограничава скуп типова који се могу користити за инстанцирање генеричког типа. Ако прогласимо а класа БокОЗИКС0ЗТ проширује НумберОЗИКС1З {...} онда би компајлер обезбедио да само подтипови Број може се користити као аргумент типа. То јест, а БокОЗИКС0ЗНумберОЗИКС1З или а БокОЗИКС0ЗЛонгОЗИКС1З је дозвољено, али а БокОЗИКС0ЗОбјецтОЗИКС1З или БокОЗИКС0ЗСтрингОЗИКС1З био би одбијен
Пример (ограничене инстанцирања због горње границе параметра типа):
class Box<T extends Number> {
private T value;
public Box(T t) { value = t; }
...
} класа тест {
public static void main(String[] args) {
Box<Long> boxOfLong = new Box<Long>(0L); // фине
Box<String> boxOfString = new Box<String>(""); // грешка: Стринг није унутар граница
}
}
- Приступ нестатичним члановима. Горња граница даје приступ свим јавним нестатичким методама и пољима горње границе. У имплементацији нашег класа БокОЗИКС0ЗТ проширује НумберОЗИКС1З {...} можемо позвати све јавне нестатичке методе дефинисане у класи Број , као што је интВалуе() на пример. Без горње границе компајлер би одбио свако такво позивање. Пример (приступа нестатичним члановима због горње границе параметра типа):
class Box<T extends Number> {
private T value;
public Box(T t) { value = t; }
public int increment() { return value.intValue()+1; } // ОЗИКС0З= би била грешка без везаног броја
...
}
- Унесите Ерасуре. Крајња лева горња граница се користи за брисање типа и замењује параметар типа у бајт коду. У нашем класа БокОЗИКС0ЗТ проширује НумберОЗИКС1З {...} све појаве Т би била замењена горњом границом Број . На пример, ако класа Кутија има приватно поље типа Т и метод празнина (Т садржај) за подешавање овог приватног поља, тада би поље било типа Број након брисања типа и метода би била преведена у метод воид сет (број садржаја) .
Пример (употребе горње границе за параметар типа у брисању типа – пре тип брисања):
класа БокОЗИКС0З Т проширује Број ОЗИКС0З {
приватни Т вредност;
public Box( Т т) { валуе = т; }
...
}
- Пример (употребе горње границе за параметар типа у брисању типа –
после
- тип брисања):
ОЗИКС0З
цласс Бок {
приватни Број вредност;
public Box( Број т) { валуе = т; }
...
}
- Поред тога, крајња лева горња граница се појављује на даљим локацијама, као што су аутоматски уметнути преливи и методе премошћавања.
Ако би доње границе биле дозвољене за параметре типа, које би нуспојаве би или требале имати? Ако конструкт као што је класа БокОЗИКС0ЗТ супер БројОЗИКС0З {...} било дозвољено, шта би то значило? Шта би 3 нежељена ефекта горње границе параметра типа – ограничена инстанција, приступ нестатичном члану, брисање типа – значила за доњу границу?
Ограничене инстанције. Компајлер би могао да ограничи скуп типова који се могу користити за инстанцирање генеричког типа са параметром типа доње границе. На пример, компајлер би могао да дозволи инстанцирање као што је БокОЗИКС0ЗНумберОЗИКС1З и БокОЗИКС0ЗОбјецтОЗИКС1З од а БокОЗИКС0ЗТ супер НумберОЗИКС0З и одбацити инстанције као што су БокОЗИКС0ЗЛонгОЗИКС1З или БокОЗИКС0ЗСхортОЗИКС1З . Ово би био ефекат у складу са рестриктивним споредним ефектом описаним за горње границе параметара типа.
Приступ нестатичним члановима. Доња граница параметра типа не даје приступ ниједним посебним методама осим оних које су наслеђене из класе Објекат . У примеру БокОЗИКС0ЗТ супер НумберОЗИКС0З супертипови Број немају ништа заједничко, осим што су референтни типови и стога подтипови Објекат . Преводилац не може да претпостави да поље типа Т је типа Број или његов подтип. Уместо тога, поље типа Т може бити било којег супертипа Број , као што је Сериализабле или Објекат . Позивање методе као што је интВалуе() више није безбедан тип и компајлер би морао да га одбије. Као последица тога, доња веза параметра типа не би дала приступ нестатичним члановима изван оних дефинисаних у класи Објекат и на тај начин има исти ефекат као „без граница“.
Унесите Ерасуре . Пратећи ову линију логике, нема смисла заменити појављивања параметра типа његовом крајњом левом доњом границом. Декларисање методе као што је конструктор кутија( Т т) као конструктор кутија( Број т) нема смисла, с обзиром на то Т је замењен супертипом Број . Ан Објекат може се с правом пренети конструктору у инстанцији БокОЗИКС0ЗОбјецтОЗИКС1З а конструктор би то одбио. Ово би било погрешно. Дакле, брисање типа би заменило сва појављивања променљиве типа Т по врсти Објекат , а не његовом доњом границом. Опет, доња граница би имала исти ефекат као “без граница”.
Да ли желите да схватите шта би значило да оба ниже _и_ горње границе су дозвољене? Лично, не желим ни да размишљам о томе и више бих волео да га поднесем под „неуправљиво“, ако дозволите.
Суштина је: све то “ супер „обавезно би купио је ограничење које само супертипови Број могу се користити као аргументи типа. А и то се често погрешно схвата. Не би то значило класа БокОЗИКС0ЗТ супер НумберОЗИКС1З {...} садржи само примере супертипова Број . Сасвим обрнуто – као што показује пример у наставку!
Пример (употребе горње границе за параметар типа у брисању типа – пре тип брисања):
класа БокОЗИКС0З Т супер број ОЗИКС0З {
приватни Т вредност;
public Box( Т т) { валуе = т; }
...
}
Пример (употребе горње границе за параметар типа у брисању типа – после тип брисања):
цласс Бок {
приватни Објекат вредност;
public Box( Објекат т) { валуе = т; }
...
}
А класа БокОЗИКС0ЗТ супер БројОЗИКС0З {...} био би преведен типом брисања у а Кутија који садржи ан Објекат поље и његов конструктор би био преведен на Кутија (Објекат т) . То је фундаментално другачије од а класа БокОЗИКС0ЗТ протеже БројОЗИКС0З {...} , што би било преведено на а Кутија који садржи а Број поље и његов конструктор би био преведен на Кутија (број т) . Сходно томе, а БокОЗИКС0ЗНумберОЗИКС1З инстанцирано из а класа БокОЗИКС0ЗТ проширује НумберОЗИКС1З {...} би се разликовало од а БокОЗИКС0ЗНумберОЗИКС1З инстанција из а класа БокОЗИКС0ЗТ супер НумберОЗИКС1З {...} , што ће вероватно изазвати забуну. Из тог разлога доње границе немају смисла за параметре типа класа.
ОЗИКС0З
ОЗИКС0З
Параметри типа доње границе метода
У вези са методама и њиховим типовима аргумената, параметар типа са доњом границом повремено може бити користан.
Пример (метода који би имао користи од параметра типа са доњом границом):
класа ПаирОЗИКС0ЗКС,ИОЗИКС1З {
private X first;
private Y second;
...
public < Супер Кс , Б супер И ОЗИКС0З Б аддТоМап(МапОЗИКС1ЗА,БОЗИКС2З мапа) { // грешка: параметар типа не може имати доњу границу
return map.put(first, second);
}
}
класа тест {
public static void main(String[] args) {
Pair<String,Long> pair = new Pair<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
Number number = pair.addToMap(map);
}
}
Тхе аддТоМап() метода додаје садржај пара на мапу. Било која мапа која може да садржи супертипове Кс и И би урадио. Мапа је стави() метода враћа вредност пронађену на мапи за дати кључ, ако већ постоји унос кључ/вредност за кључ на мапи. Повратна вредност мапе стави() метод ће бити враћен из аддТоМап() методом. Под овим околностима, желео би да прогласимо метод као што је приказано изнад: Мапа је параметризована супертиповима параметара типа пара и аддТоМап() Враћани тип методе је тип вредности мапе.
Пошто компајлер не дозвољава доње границе за параметре типа, потребно нам је решење.
Једно решење које вам пада на памет је употреба џокера, јер џокер знакови могу имати доњу границу. Ево заобилазно решење помоћу џокера.
Пример (заобилазно решење за претходни пример коришћењем џокер знакова):
класа ПаирОЗИКС0ЗКС,ИОЗИКС1З {
private X first;
private Y second;
...
јавности Објекат аддТоМап(МапОЗИКС0З ? Супер Кс , ? супер И ОЗИКС0З мапа) {
return map.put(first, second);
}
}
класа тест {
public static void main(String[] args) {
Pair<String,Long> pair = new Pair<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
Number number = (број) паир.аддТоМап(мапа);
}
}
Ради, осим што не постоји начин да се декларише тип враћања по жељи. То би био супертип И које компајлер хвата из типа карте, али не постоји синтакса за његово навођење. Не смемо да прогласимо тип враћања као “ ? супер И “, јер “ ? супер И ” је џокер знак, а не тип и стога није дозвољен као тип враћања. Немамо избора и морамо га користити Објекат уместо тога као тип враћања нашег метода. Овај прилично неспецифичан тип враћања заузврат приморава позиваоце на аддТоМап() метода у превођење повратне вредности наниже из Објекат свом стварном типу. Ово није баш оно што смо имали на уму.
Још једно решење је употреба статичких метода. Ево заобилазно решење са статичким уместо нестатичким методом.
Пример (заобилазно решење за претходни пример коришћењем статичког метода):
класа ПаирОЗИКС0ЗКС,ИОЗИКС1З {
private X first;
private Y second;
...
јавности статична ОЗИКС0З А , Б ,Кс продужава А ,И продужава Б ОЗИКС0З Б аддТоМап(ПаирОЗИКС1ЗКС,ИОЗИКС2З пар, МапОЗИКС3З А , Б ОЗИКС0З мапа) {
return map.put(pair.first,pair.second);
}
}
класа тест {
public static void main(String[] args) {
Pair<String,Long> pair = new Pair<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
Number number = Пар .аддТоМап( пар ,мапа);
}
}
Генерички аддТоМап() метода има четири параметра типа: два чувара места Кс и И за тип пара и два чувара места А и Б за тип карте. А и Б су супертипови Кс и И , јер Кс и И are declared with А и Б као њихове горње границе. (Имајте на уму да параметри типа генеричке методе Кс и И немају никакве везе са Пар разредне Кс и И параметрима. Имена Кс и И се поново користе за генеричку методу да би се лако препознали као параметри типа пара. Користећи четири параметра типа можемо декларисати прецизан тип повратка по жељи: исти је тип као и тип вредности мапе.
Пример (нелегалне употребе параметра типа у статичком контексту генеричке класе):
цласс СомеЦласс ОЗИКС0ЗТОЗИКС1З {
// статички иницијализатор, статичко поље, статички метод
статична {
SomeClass< Т ОЗИКС0З тест = нови СомеЦлассОЗИКС1З Т ОЗИКС0З(); // грешка
}
приватни статична Т globalInfo; // грешка
јавности статична Т getGlobalInfo() { // грешка
return globalInfo;
}
// нестатички иницијализатор, нестатичко поље, нестатичка метода
{
SomeClass< Т ОЗИКС0З тест = нови СомеЦлассОЗИКС1З Т ОЗИКС0З();
}
приватни Т лоцалИнфо;
јавности Т getLocalInfo() {
return localInfo;
}
// статички угнежђени типови
јавности статична класе Неуспех продужава изузетак {
private final Т info; // грешка
public Failure( Т т) { инфо = т; } // грешка
јавности Т getInfo() { return info; } // грешка
}
приватни интерфејс Може се копирати {
Т copy(); // грешка
}
приватни енум држава {
VALID, INVALID;
приватни Т инфо; // грешка
public void setInfo( Т т) { инфо = т; } // грешка
јавности Т getInfo() { return info; } // грешка
}
// нестатичке угнежђене типове
јавности класе Аццессор {
јавности Т гетИнфо() { ретурн лоцалИнфо; }
}
}
Пример илуструје да се параметар типа не може користити у статичком контексту генеричке класе. Такође показује да се угнежђени интерфејси и енум типови сматрају члановима статичног типа класе. Само унутрашње класе, односно нестатичке угнежђене класе, могу да користе параметар типа генеричке класе која обухвата.
Опсег параметра типа интерфејса је цела дефиниција интерфејса, осим свих поља или угнежђених типова. То је зато што су поља и угнежђени типови дефинисани у интерфејсу имплицитно статични.
Пример (нелегалне употребе параметра типа у генеричком интерфејсу):
интерфејс СомеИнтерфаце ОЗИКС0ЗТОЗИКС1З {
// поље
SomeClass< Т ОЗИКС0З вредност = нова СомеЦлассОЗИКС1З Т ОЗИКС0З(); // грешка
// угнежђени тип
класе Аццессор {
јавности Т getInfo() { // грешка
return value.getGlobalInfo();
}
}
// методе
Т гетВалуе();
}
Пример показује да су поља интерфејса имплицитно статична, тако да параметар типа не може да се користи било где у декларацији поља генеричког интерфејса. Слично томе, угнежђена класа се сматра статичком угнежђеном класом, а не унутрашњом класом, и из тог разлога је употреба параметра типа било где у угнежђеној класи незаконита.
Заобилазно решење за угнежђене статичке класе и интерфејсе:
У случају статичких угнежђених класа и интерфејса ово није велика невоља. Као заобилазно решење можемо да генеришемо саму статичку класу или интерфејс.
Пример (заобилазно решење – генеришите угнежђени статички тип):
цласс Враппер ОЗИКС0ЗТОЗИКС1З {
private final Т тхеОбјецт;
public Wrapper( Т т) { тхеОбјецт = т; }
јавности Т гетВраппер() { ретурн тхеОбјецт; } public Immutable ОЗИКС0ЗТОЗИКС1З макеИммутабле() {
return new Immutable ОЗИКС0ЗТОЗИКС1З (тхеОбјецт);
}
public Mutable ОЗИКС0ЗТОЗИКС1З макеМутабле() {
return new Mutable ОЗИКС0ЗТОЗИКС1З (тхеОбјецт);
}
private static <A> A makeClone(A theObject) { ... }public static final class Immutable ОЗИКС0ЗАОЗИКС1З { // је сада генеричка класа
private final А тхеОбјецт;public Immutable( А арг) {
theObject = makeClone(arg);
}
јавности А гетВраппедИтем() {
return makeClone(theObject);
}
}
public static class Mutable ОЗИКС0ЗАОЗИКС1З {
... similar ...
}
}
Не постоји такво решење за угнежђене типове енума јер не могу бити генерички.
Заобилазно решење за угнежђене статичке класе:
Ако је угнежђени статички тип класа, алтернативно решење би било претварање статичке класе у нестатичку унутрашњу класу.
Пример (заобилазно решење – користите унутрашњу класу):
цласс Враппер ОЗИКС0ЗТОЗИКС1З {
private final Т тхеОбјецт;
public Wrapper( Т т) { тхеОбјецт = т; }
јавности Т гетВраппер() { ретурн тхеОбјецт; } public Mutable makeMutable() {
return ово. нови променљиви(тхеОбјецт);
}
public Immutable makeImmutable() {
return ово. нев Иммутабле(тхеОбјецт);
}
private static <A> A makeClone(A theObject) { ... }public final class Immutable { // више није статична класа
private final Т тхеОбјецт;public Immutable( Т арг) {
theObject = makeClone(arg);
}
јавности Т гетВраппедИтем() {
return makeClone(theObject);
}
}
public class Mutable
... similar ...
}
}
Ово заобилазно решење долази са одређеним трошковима јер све унутрашње класе имају скривену референцу на инстанцу спољашњег типа, што им у овом примеру нити треба нити им је потребна; скривена референца је само пртљаг. Често се унутрашње класе комбинују са интерфејсима како би се унутрашња класа одржала као приватни детаљ имплементације затворене класе. И ми можемо да урадимо исто овде.
Пример (претходно решење је прецизирано):
цласс Враппер ОЗИКС0ЗТОЗИКС1З {
private final Т тхеОбјецт;
public Wrapper( Т т) { тхеОбјецт = т; }
јавности Т гетВраппер() { ретурн тхеОбјецт; } public Mutable ОЗИКС0ЗТОЗИКС1З макеМутабле() {
return this.new Mutable(theObject);
}
public Immutable ОЗИКС0ЗТОЗИКС1З макеИммутабле() {
return this.new Immutable(theObject);
}
private static <A> A makeClone(A theObject) { ... }јавни интерфејс НепроменљивОЗИКС0ЗСОЗИКС1З {
S getWrappedItem();
}
јавни интерфејс МутаблеОЗИКС0ЗСОЗИКС1З {
S getWrappedItem();
void setWrappedItem(S arg);
}
приватни финал цласс ИммутаблеИмплементатион имплементира ИммутаблеОЗИКС0ЗТОЗИКС1З {private final T theObject;
public ImmutableImplementation(T arg) {
theObject = makeClone(arg);
}
public T getWrappedItem() {
return makeClone(theObject);
}
}
приватни цласс МутаблеИмплементатион имплементира МутаблеОЗИКС0ЗТОЗИКС1З {
... similar ...
}
}
Као што видите, угнежђени јавни интерфејси морају бити генерички, док унутрашње приватне класе могу да користе параметар типа затворене класе.
ОЗИКС0З
ОЗИКС1З
ОЗИКС2З
ОЗИКС0З

Оставите одговор