Глядзіце гэты FAQ для атрымання дадатковай інфармацыі:
http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html#SECTION1
Вось некалькі важных момантаў з гэтага FAQ у кароткай форме.
Прыклад (агульны тып):
клас Pair<X,Y> {
прыватны X першы;
прыватны Y другі; публічная пара (X a1, Y a2) {
першы = a1;
другі = a2;
}
грамадскі X getFirst() {вярнуць першы; }
грамадскі Y getSecond() {вярнуць секунду; }
public void setFirst(X arg) { first = arg; }
грамадскі несапраўдны setSecond(Y arg) { second = arg; }
}
Клас Пара мае два параметры тыпу X і Я . Яны замяняюцца аргументамі тыпу, калі агульны тып Пара ствараецца. Напрыклад, у дэкларацыі Пара<String, Дата> параметр тыпу X замяняецца аргументам тыпу радок і Я замяняецца на Дата .
Вобласць прымянення ідэнтыфікатараў X і Я ёсць поўнае вызначэнне класа. У гэтай вобласці два параметры тыпу X і Я выкарыстоўваюцца тыпы яка (з некаторымі абмежаваннямі). У прыведзеным вышэй прыкладзе параметры тыпу выкарыстоўваюцца ў якасці аргументаў і тыпу вяртання метаду аб'екта і тыпу тыпу аб'екта.
Параметры тыпу могуць адлюстроўвацца з абмежаваннямі. Межы даюць доступ да метаданых невядомага тыпу, што азначае параметр тыпу. У нашым прыкладзе мы не выклікаем ніякіх метадаў невядомых тыпаў X і Я . Па гэтай прычыне колькасць двух тыпаў параметраў неабмежаваная.
Прыклад (канкрэтны параметрізаваны тып):
public void printPair( Пара<String,Long> пара) {
System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} Пара<String,Long> ліміт = новы Пара<String,Long> ("максімум",1024л);
printPair(ліміт);
Прыклад (параметрызаванага тыпу падстаноўкі):
public void printPair( Пара<?,?> пара) {
System.out.println("("+pair.getFirst()+","+pair.getSecond()+")");
} Пара<?,?> ліміт = новы Пара<String,Long> ("максімум",1024л);
printPair(ліміт);
Усе экзэмпляры агульнага тыпу маюць аднолькавае прадстаўленне тыпу падчас выканання, а менавіта прадстаўленне неапрацаванага тыпу. Напрыклад, асобнікі агульнага тыпу Спіс , такія як Спіс<Дата> , Спіс<Радок> , Спіс<Long> і г. д. маюць розныя статычныя тыпы падчас кампіляцыі, але аднолькавы дынамічны тып Спіс падчас выканання.
Акцёрскі склад складаецца з двух частак:
- праверка статычнага тыпу, якая выконваецца кампілятарам падчас кампіляцыі і
- дынамічная праверка тыпу, якая выконваецца віртуальнай машынай падчас выканання.
Статычная частка сартуе бессэнсоўныя касты, якія не маюць поспеху, напрыклад, кастынг з радок што Дата або ад Спіс<Радок> што Спіс<Дата> .
Дынамічная частка выкарыстоўвае інфармацыю пра тып выканання і выконвае праверку тыпу падчас выканання. Гэта падымае а ClassCastException калі дынамічны тып аб'екта не з'яўляецца мэтавым тыпам (або падтыпам мэтавага тыпу) прывядзення. Прыкладамі клеяў з дынамічнай часткай з'яўляюцца клеі Аб'ект што радок або ад Аб'ект што Спіс<Радок> . Гэта так званыя даункасты, чорт супертыпу ды падціпу.
Прыклады канкрэтных параметрізаваных тыпаў Спіс<Радок> , Map<String,Date> , але не Спіс<? пашырае Number> або Map<String,?> .
Я чакаю, што здарылася? Спіс<Аб'ект> будзе супертыпам a Спіс<Радок> , таму што Аб'ект z’ - супертып радок . Гэта чаканне вынікае з таго факту, што існуе такі тып адносін для масіваў: Аб'ект[] z’ - супертып Радок [] , таму што Аб'ект z’ - супертып радок . (Гэты тып адносін вядомы як каварыянтнасць .) Адносіны супер-падтыпаў тыпаў кампанентаў распаўсюджваюцца на адпаведныя тыпы масіваў. Для асобнікаў агульных тыпаў такіх адносін тыпаў не існуе. (Параметрызаваныя тыпы не каварыянт.)
Абсутнасць суперсувязі падтыпу паміж адным і тым жа выглядам мае розныя наступствы. Вось прыклад.
прыклад:
void printAll(ArrayList<Object> c) {
для (Аб'ект o : c)
System.out.println(o);
} ArrayList<String> спіс = новы ArrayList<String>();
... запоўніць спіс ...
надрукавацьУсе(спіс); // памылка
А ArrayList<String> Ab’ect не можа быць перададзены ў якасці аргументу метаду, які запытвае a ArrayList<Object> таму што два пыпы з’ajaјастыўнікамі днѐга такога тыпу, але для розныя тыпу аргументаў, і па гэтай прычыне яны не сумяшчальныя адзін з адным.
Канкрэтныя параметрызаваныя тыпы – - гэта канкрэтныя асобнікі агульнага тыпу. Яны амал як тыпы; ёсць толькі некалькі абмежаванняў. Іх нельга выкарыстоўваць у наступных мэтах:
- для стварэння масіваў
- у апрацоўцы выключэнняў
- у літаральным класе
- у ан асобнік выраз
Масівы, якія змяшчаюць элементы, тып z’ з'яўляецца пэўным параметрізаваным тыпам z’, з'яўляюцца незаконнымі.
Прыклад (няправільнага тыпу масіва):
статычны тэст пустаты() {
Пара<Цэлы лік,Цэлы лік>[] intPairArr = новы Пара<Цэлы лік,Цэлы лік>[10] ; // памылка
addElements(intPairArr);
Pair<Integer,Integer> pair = intPairArr[1];
Цэлы лік i = pair.getFirst();
pair.setSecond(i);
}
static void addElements( Аб'ект[] objArr) {
objArr[0] = новая пара <Integer,Integer>(0,0);
objArr[1] = новы Pair<String,String>("",""); // павінна выйсці з няўдачай з ArrayStoreException
}
Кампілятар забараняе стварэнне масіваў, тыпам кампанентаў якіх з'яўляецца ’, пэўныя параметрызаваныя тыпы, напрыклад. Пара<Цэлы лік,Цэлы лік> у нашым прыкладзе. У папярэднім запісе мы абмяркоўвалі, чаму разумна, што кампілятар кваліфікуе a Пара<Цэлы лік,Цэлы лік>[] як незаконны. Ключавая праблема заключаецца ў тым, што кампілятар і асяроддзе выканання павінны гарантаваць, што масіў z’ з'яўляецца a аднародны паслядоўнасць аднатыпных элементаў. Адна з праверак тыпаў, а менавіта праверка захавання масіва, якая выконваецца віртуальнай машынай падчас выканання, не можа выявіць парушальную ўстаўку старонняга элемента. У прыкладзе другая ўстаўка ў addElements Метад павінен выйсці з ладу, таму што яны дадалі пару радкоў у масіў інтэгральных значэнняў, але гэта не збой, я чакаў. Прычыны абмяркоўваліся ў папярэднім пасце.
Калі мы не можам выкарыстоўваць масівы з элементамі, тып якіх z’ з'яўляецца пэўным параметрізаваным тыпам, што мы выкарыстоўваем у якасці абыходнага шляху?
Давайце разгледзім 3 магчымыя абыходныя шляхі:
- масіў сырых тыпаў
- масіў неабмежаванага параметрізаванага тыпу падстаноўкі
- калекцыя замест масіва
Хоць масівы пэўных параметрізаваных тыпаў z’ з'яўляюцца незаконнымі, калекцыі пэўных параметрізаваных тыпаў дапускаюцца.
Параметраваны тып падстаноўкі – - гэта асобнік агульнага тыпу, дзе ёсць толькі адзін аргумент тыпу z’, які з'яўляецца сімвалам падстаноўкі. Прыклады параметрізаваных тыпаў падстаноўных знакаў: Калекцыя<?> , Спіс<? пашырае Number> , Кампаратар <? супер String> і Пара<String,?> . Параметрызаваны тып падстаноўкі абазначае сямейства тыпаў, якія складаюцца з канкрэтных асобнікаў агульнага тыпу. Тып выкарыстоўванага сімвала падстаноўкі вызначае, якія канкрэтныя параметрызаваныя тыпы належаць да сямейства. Напрыклад, параметрізаваны тып падстаноўкі Калекцыя<?> абазначае сямейства ўсіх асобнікаў Калекцыя інтэрфейс незалежна ад аргументу тыпу. Параметраваны тып падстаноўкі Спіс<? пашырае Number> паказвае сямейства ўсіх тыпаў спісаў, дзе тып элемента z’ з'яўляецца падтыпам Нумар . Параметраваны тып падстаноўкі Кампаратар <? супер String> гэта сямейства ўсіх асобнікаў Кампаратар інтэрфейс для тыпаў аргументаў, якія з'яўляюцца супертыпамі радок.
Параметрызаваны тып замены не з'яўляецца тыпам у звычайным разуменні (у адрозненне ад непараметрызаванага класа/інтэрфейсу або неапрацаванага тыпу).
Для ўводу можна выкарыстоўваць параметрізаваныя тыпы падстаноўных знакаў (напрыклад, непараметрызаваныя класы і інтэрфейсы):
- у якасці аргументаў і тыпаў вяртання метадаў
- як тып поля або лакальнай спасылкавай зменнай
- як тып кампанента аргумента тыпу масіва іншых параметрізаваных тыпаў
- як тып мэты ў кідках
Універсальны мойкун быты не цісо тыпы, але і метады. Статычныя і нестатычныя метады, а таксама канструктары могуць мець параметры тыпу. Сінтаксіс ab’yavy параметраў фармальнага тыпу, аналагічны сінтаксісу агульных тыпаў. Раздзел параметраў тыпу абмежаваны вуглавымі дужкамі, а z’ з'яўляецца перад тыпам, які вяртаецца метадам. Яго сінтаксіс і значэнне ідэнтычныя спісу параметраў агульнага тыпу.
Вось прыклад радавога макс метад, які вылічвае найбольшае значэнне ў калекцыі элементаў невядомага тыпу А .
Прыклад (агульны метад):
клас Калекцыі {
грамадская статыка <A пашырае Comparable<A>> A макс.(Калекцыя< А > xs) {
Ітэратар< А > xi = xs.iterator();
А w = xi.next();
у той час як (xi.hasNext()) {
А x = xi.next();
if (w.compareTo(x) < 0) w = x;
}
вяртанне ж;
}
}
The макс метад мае адзін параметр тыпу, наз А . Гэта запаўняльнік для тыпу элемента збору, над якім працуе метад. Тып параметра мае абмежаванні; Гэта павінен быць тып, падтыпам якога з'яўляецца ’ Параўнальны<A>
Прыклад (на гэты раз таго ж агульнага тыпу з межы):
публічны інтэрфейс Comparable<T> {
public int compareTo(T arg);
}
публічны клас TreeMap<Key пашырае Comparable<Key> ,Data>{
прыватны статычны клас Entry<K,V> {
ключ К;
V значэнне;
Entry<K,V> злева = нуль;
Entry<K,V> справа = нуль;
Запіс<K,V> бацька;
}
прыватны пераходны Entry<Key,Data> корань = нуль;
...
прыватны Entry<Key,Data> getEntry(ключ ключа) {
Entry<Key,Data> p = корань;
Ключ k = ключ;
у той час як (p != null) {
int cmp = k. параўнаць(p.key) ;
калі (cmp == 0)
вяртанне р;
інакш, калі (cmp < 0)
p = p.left;
яшчэ
p = p.справа;
}
вярнуць нуль;
}
public boolean containsKey(Ключ ключа) {
return getEntry(key) != null;
}
...
}
У прыведзеным вышэй прыкладзе параметр тыпу ключ мае мяжу Параўнальны<Key> . Спецыфікацыя мяжы мае два наступствы:
- Гэта дае доступ да метадаў, якія вызначае прывязка . У прыкладзе прывязаны Параўнальны<Key> дае доступ да параўнаць метад, які мы хочам выклікаць пры рэалізацыі нашага TreeMap клас.
- Толькі тыпы “ у ” могуць быць выкарыстаны для стварэння асобніка агульнага тыпу. У прыкладзе параметрізаваны тып, напрыклад TreeMap<Number,String> будзе адхілена, таму што тып Нумар не z’ мой падтып з'яўляецца Параўнальны<Нумер> . Параметраваны тып, як TreeMap<String,String> ён будзе прыняты, таму што тып String знаходзіцца ў межах, таму OZYX0 з'яўляецца падтыпам Параўнальны<String> .
Усе класы, інтэрфейсы і тыпы пераўтварэння могуць выкарыстоўвацца ў якасці параметраў прывязаных тыпаў, уключаючы ўкладзеныя і ўнутраныя тыпы. Ні прымітыўныя тыпы, ні тыпы масіваў не могуць выкарыстоўвацца ў якасці абмежаванняў параметраў тыпу.
Прыклады (параметры тыпу абмежавання):
клас X0 <T пашырае міжнар > {...} // памылка
клас X1 <T пашыраецца Аб'ект[] > {...} // памылка
клас X2 <T пашырае Нумар > {...}
клас X3 <T пашыраецца радок > {...}
клас X4 <T пашыраецца Працаздольны > {...}
клас X5 <T пашыраецца Нітка.Дзярж > {...}
клас X6 <T пашыраецца Спіс > {...}
клас X7 <T пашырае Спіс<Радок> > {...}
клас X8 <T пашыраецца Спіс<? пашырае Number> > {...}
клас X9 <T пашыраецца Параўнальны<? супер нумар> > {...}
клас X10<T пашыраецца Карта.Уваход<?,?> > {...}
Прыклад кода паказвае, што прымітыўныя тыпы, такія як міжнар і тыпы масіваў, такія як Аб'ект[] яны не дапускаюцца ў якасці звязаных параметраў тыпу.
Тыпы класаў, напрыклад Нумар або радок , і тыпы інтэрфейсаў, такія як Працаздольны , дапускаецца цвёрда звязаны тып параметра.
Тыпы пералічэння, такая як Нітка.Дзярж таксама дазволены як прывязаны параметр тыпу. Нітка.Дзярж z’ - прыклад укладзенага тыпу, які выкарыстоўвае моцны параметр тыпу. Дапускаюцца таксама нестатычныя ўнутраныя тыпы.
Неапрацаваныя тыпы дазволена моцна прывязваць да параметраў тыпу; Спіс z’ з'яўляецца далучаным.
Дазваляецца прывязваць параметрізаваныя тыпы да параметраў тыпу, у тым ліку пэўныя параметрізаваныя тыпы, такія як Спіс<Радок> , параметрізаваныя тыпы з абмежаванымі сімваламі падстаноўкі, такія як Спіс<? пашырае Number> і Параўнальны<? супер Long> , і неабмежаваныя параметрізаваныя тыпы падстаноўкі, такія як Карта.Уваход<?,?> . Абмежаванне z’ajajajajajajajajajaja z’ajajajajajajajajaja z’ajajajajajajajaja za parametrizovannym падстаноўка падстаноўка дазваляе ў якасці аргументу тыпу выкарыстоўваць усе тыпы, якія павінны належаць да сямейства тыпаў, якое абазначае падстаноўка. Прывязаны параметрызаваны тып падстаноўкі дае толькі абмежаваны доступ да палёў і метадаў; абмежаванні залегаць ад тыпу падстаноўчага знака.
Прыклад (тып з параметрамі падстаноўкі і параметрам тыпу):
клас X< T пашырае List<? пашырае Number> > {
public void someMethod(T t) {
t.add(новы доўгі(0L)); // памылка
Лік n = t.remove(0);
}
}
клас Тэст {
public static void main(String[] args) {
X<ArrayList< Доўгі >> x1 = новы X<ArrayList<Long>>();
X<ArrayList< радок >> x2 = новы X<ArrayList<String>>(); // памылка
}
}
Спасылкавыя зменныя тыпу Т (тып параметру) разглядаюцца як стандартныя замены тыпу зменнай (прывязаны да тыпу параметру). У нашым прыкладзе наступствам ’ з'яўляецца тое, што кампілятар ігнаруе выклікі метадаў, якія прымаюць аргумент “ невядомага тыпу ”, які азначае параметр тыпу, напрыклад. Пералічыць.дадаць , таму што мяжа z’ - гэта параметрызаваны тып падстанцыі з верхняй мяжой.
Пры гэтым падключ Спіс<? пашырае Number> вызначае тыпы, якія можна выкарыстоўваць у якасці аргументаў тыпу. Кампілятар прымае ўсе аргументы тыпу, якія належаць да сямейства тыпаў Спіс<? пашырае Number> , гэта значыць усе падтыпы Спіс з аргументамі тыпу, што ’ з'яўляецца ў якасці падтыпу Нумар .
Параметр тыпу можа выкарыстоўвацца ў іншым параметры тыпу.
Прыклад (параметр тыпу выкарыстоўваецца як параметр тыпу):
клас Трайны <T> {
прыватны Т фст, снд, трд;
публічны < У пашырае Т , С пашырае Т , С пашырае Т > Triple(U arg1, V arg2, W arg3) {
fst = arg1;
snd = arg2;
trd = arg3;
}
}
У гэтым прыкладзе параметр тыпу Т параметрізаванага класа выкарыстоўваецца як абмежаванне параметраў тыпу У , С і С асобнік параметрізаванага метаду гэтага класа.
Дадатковыя магчымасці выкарыстання параметраў тыпу ў якасці межаў для іншых параметраў тыпу ўключаюць сітуацыі, калі ўкладзены тып вызначаны ўнутры агульнадаступнага тыпу або лакальны клас вызначаны ўнутры публічнага метаду. Таксама дазваляецца выкарыстоўваць параметр тыпу ў спалучэнні з іншым параметрам тыпу ў тым жа раздзеле параметраў тыпу.
Абмежаванне, якое класіфікуе ’, дае доступ да ўсіх яго публічных членаў, г.зн. публічных палёў, метадаў і ўкладзеных тыпаў. Толькі канструктары не даступныя, таму што няма ніякай гарантыі, што звязаны падклас мае тыя ж канструктары, што і звязаны.
Прыклад (клас, які выкарыстоўваецца ў якасці мяжы параметра тыпу):
грамадскі клас Суперклас { // статычныя члены
публічны пералік EnumType {THIS, THAT}
публічны статычны аб'ект staticField;
public static void staticMethod() { ... }// нестатычныя члены
публічны клас InnerClass { ... }
публічны аб'ект nonStaticField;
public void nonStaticMethod() { ... }// канструктары
публічны SuperClass() { ... }// прыватныя члены
прыватны аб'ект privateField;...
}адкрыты канчатковы клас SomeClass<T пашыраецца Суперклас > {
прыватны Т аб'ект;
грамадскі SomeClass(T t) { аб'ект = t; }грамадскасць String toString() {
вяртанне
"статычны ўкладзены тып : "+T.EnumType.class+"\n"
+"static field : "+T.staticField+"\n"
+"статычны метад : "+T.staticMethod()+"\n"
+"нестатычны ўкладзены тып: "+T.InnerClass.class+"\n"
+"нестатычнае поле : "+object.nonStaticField+"\n"
+"нестатычны метад : "+object.nonStaticMethod()+"\n"
+"канструктар : "+(новы T())+"\n" // памылка
+"прыватны ўдзельнік : "+object.privateField+"\n" // памылка
;
}
}
Звязаны Суперклас дае доступ да сваіх укладзеных тыпаў, статычных палёў і метадаў і нестатычных палёў і метадаў. Недаступны толькі канструктар. Гэта таму, што канструктары не перадаюцца ў спадчыну. Кожны падклас вызначае ўласныя канструктары і не патрабуе падтрымкі канструктараў свайго суперкласа. Такім чынам, няма ніякай гарантыі, што падклас Суперклас budde meshet toi zha structurator, soto i jago superclass.
Кантэкст, у якім Enum<E пашырае Enum<E>> » дэкларацыя z’a Пералік клас у пакеце java.lang :
грамадскі абстрактны клас Enum<E пашырае Enum<E>> {
...
}
Тып Пералік z’ - агульны базавы клас для ўсіх тыпаў вылічэнняў. In Java takı tip peraličenja, yak Колер пераводзіцца ў клас Колер што пашыраецца Enum<Color> . Прызначэнне суперкласса Пералік неабходна забяспечыць функцыянальнасць, агульную для ўсіх тыпаў транзакцый.
Вось класная замалёўка Пералік :
грамадскі абстрактны клас Enum< Э пашырае Enum<E>> рэалізуе Comparable< Э >, серыялізаваны {
прыватнае канчатковае імя радка;
публічнае канчатковае імя радка() { ... } прыватны канчатковы int парадкавы;
public final int ordinal() { ... }абаронены Enum(Імя радка, int парадкавы нумар) { ... }
агульнадаступны радок toString() { ... }
public final boolean equals(Object other) { ... }
public final int hashCode() { ... }
абаронены канчатковы Object clone() кідае CloneNotSupportedException { ... }
public final int compareTo( Э о) {...}публічны фінал Class< Э > getDeclaringClass() { ... }
public static <T пашырае Enum<T>> T valueOf(Class<T> enumType, String name) { ... }
}
Дзіўная цацка ў дэкларацыі “ Enum<E пашырае Enum<E>> ” z’ - гэта новы вызначаны клас Пералік і яго новы параметр тыпу Э z’ з'яўляюцца ў гэтым жа параметры тыпу. Гэта значыць што Пералік тып павінен быць створаны для аднаго з яго падтыпаў. Каб зразумець, чаму гэта мае сэнс, улічыце, што кожны тып пералічэння пераводзіцца ў падтып Пералік .
Гэта надуманы тып разліку Колер :
пералік Колер {ЧЫРВОНЫ, СІНІ, ЗЯЛЁНЫ}
Кампілятар перакладае яго ў наступны клас:
публічны выпускны клас Колер пашыраецца Enum<Color> {
публічны статычны канчатковы колер [] значэнні () { вяртанне (Колер []) $ VALUES.clone (); }
public static Color valueOf(String name) { ... } прыватны колер (String s, int i) { super (s, i); }публічны статычны канчатковы колер ЧЫРВОНЫ;
публічны статычны канчатковы колер СІНІ;
публічны статычны канчатковы колер ЗЯЛЁНЫ;прыватны статычны канчатковы колер $VALUES[];
статычны {
RED = новы колер ("RED", 0);
СІНІ = новы колер ("СІНІ", 1);
GREEN = новы колер ("GREEN", 2);
$ЗНАЧЭННІ = (новы колер[] {ЧЫРВОНЫ, СІНІ, ЗЯЛЁНЫ });
}
}
Спадчына мае эфект, што Колер тып успадкоўвае ўсе метады, рэалізаваныя ў Enum<Color> . Сярод іх ёсць параўнаць метад. The Колер. параўнаць метад, верагодна, варта прыняць a Колер як аргумент. Для таго, каб гэта адбылося клас Пералік z’ з'яўляецца агульным і Enum.compareTo метад прымае Пералік параметр тыпу Э як аргумент. У выніку тып Колер атрыманы з Enum<Color> успадкоўвае а параўнаць метад, які прымае a Колер yak і аргумент, менавіта, yak патрэбны.
Калі вы прааналізуеце дэкларацыю “ Enum<E пашырае Enum<E>> “ мы бачым, што гэтая мадэль мае некалькі аспектаў.
Па-першае, ёсць той факт, што звязаны тып параметру – таго ж тыпу: “ Пералік <E пашыраецца Пералік <E>> “. Гэта гарантуе, што ўводзяцца толькі падтыпы Пералік дазволены ў якасці аргументаў тыпу. (Тэарэтычна, тыпу Пералік можа быць створаны на сабе, як у Enum<Enum> , але гэта, вядома, не прызначана, і цяжка ўявіць сітуацыю, у якой такі чалавек быў бы карысны.
Іншым з'яўляецца той факт, што тып, звязаны з параметрам, з'яўляецца параметризированным тыпам. Пералік <E> , які выкарыстоўвае параметр тыпу Э як аргумент тыпу прывязанага. Гэта дэкларацыя гарантуе, што адносіны спадчыннасці паміж падтыпам і асобнікам Пералік мае форму X пашырае Enum<X> “. Такі падтып, моцны “ X пашырае Enum<Y> ” не можа быць аб'яўлены ab’ для аргумента тыпу Я не было б у межах; толькі падтыпы Enum<X> знаходзяцца ўнутры
Па-трэцяе, ёсць той факт, што Пералік z’ распаўсюджаны ў першую чаргу. Гэта азначае, што некаторыя метады класа Пералік прыняць аргумент або вярнуць значэнне невядомага тыпу (або іншым чынам залежаць ад невядомага тыпу). Як мы ўжо ведаем, гэты невядомы тып пазней стане падтыпам X з Enum<X> . Такім чынам, у параметрізаваным тыпе Enum<X> , гэтыя метады ўключаюць падтып X , і яны перадаюцца ў спадчыну ў падтып X . The параўнаць Да такога метаду прымяняецца метад z’; ён успадкоўвае ад суперкласса ў кожным падкласе і мае спецыфічную сігнатуру падкласа ў кожным выпадку.
У заключэнне дэкларацыя “ Enum<E пашырае Enum<E>> ” можна расшыфраваць так: Пералік з’ з'яўляецца агульным тыпам, асобнікі якога могуць быць створаны толькі для яго падтыпаў, і гэтыя падтыпы будуць успадкоўваць некаторыя карысныя метады, некаторыя з якіх прымаюць аргументы, характэрныя для падтыпу (ці ў іншым выпадку, яны будуць залежаць ад падтыпу).
Параметры тыпу ніжняй мяжы класаў
Параметры тыпу могуць мець некалькі абмежаванняў, як у клас Box<T пашырае Number> {...} . Але параметр тыпу не можа мець ніжняй мяжы, гэта значыць такая канструкцыя, як клас Box<T вялікі Нумар> {...} не дапускаецца. чаму б і не Адказ: гэта бессэнсоўна, таму што гэта не купіла б вам нічога, калі б гэта было дазволена. Давайце паглядзім, чаму ніжняя мяжа параметраў тыпу класа збівае з панталыку, пасля таго, што азначае верхняя мяжа параметра тыпу.
Верхняя мяжа параметру тыпу мае тры наступствы:
- Абмежаваная інстанцыяцыя. Верхняя мяжа абмяжоўвае набор тыпаў, якія можна выкарыстоўваць для стварэння асобніка агульнага тыпу. Калі мы абOZYX0Зявым а клас Box<T пашырае Number> {...} тады кампілятар гарантуе, што толькі падтыпы Нумар можа выкарыстоўвацца як аргумент тыпу. Гэта значыць, а Скрынка<Нумар> або а Box<Long> дазволена, але a Скрынка<Аб'ект> або Box<String> будзе адхілена
Прыклад (абмежаванага стварэння асобніка з-за верхняй мяжы параметра тыпу):
клас Box<T пашырае Number> {
прыватнае значэнне Т;
грамадская скрынка (T t) {значэнне = t; }
...
} клас Тэст {
public static void main(String[] args) {
Box<Long> boxOfLong = новы Box<Long>(0L); // добра
Box<String> boxOfString = новы Box<String>(""); // памылка: радок не знаходзіцца ў межах
}
}
- Доступ да нестатычных удзельнікаў. Верхняя мяжа дае доступ да ўсіх публічных нестатычных метадаў і палёў верхняй мяжы. У ажыццяўленні нашага клас Box<T пашырае Number> {...} мы можам выклікаць усе публічныя нестатычныя метады, вызначаныя ў класе Нумар , такія як intValue() напрыклад. Без верхняй мяжы кампілятар адхіліў бы любы такі выклік. Прыклад (доступу да нестатычных элементаў з-за верхняй мяжы параметру тыпу):
клас Box<T пашырае Number> {
прыватнае значэнне Т;
грамадская скрынка (T t) {значэнне = t; }
грамадскі інкрэмент() {вярнуць value.intValue()+1; } // <= будзе памылкай без прывязанага нумара
...
}
- Тып Erasure. Самая левая верхняя мяжа выкарыстоўваецца для сцірання тыпу і замяняе параметр тыпу ў байт-кодзе. У нашым клас Box<T пашырае Number> {...} усе выпадкі Т будзе заменена верхняй мяжой Нумар . Напрыклад, калі клас скрынка мае прыватнае поле тыпу Т і метад набор несапраўдных (змест T) для ўстаноўкі гэтага прыватнага поля, то поле будзе тыпу Нумар пасля сцірання тыпу і метад будзе пераведзены ў метад набор несапраўдных (змесціва лікаў) .
Прыклад (з выкарыстаннем верхняй мяжы параметра тыпу ў тыпе выдалення – раней тып сцірання):
клас Box< T пашырае лік > {
прыватны Т каштоўнасць;
грамадская скрыня( Т t) {значэнне = t; }
...
}
- Прыклад (з выкарыстаннем верхняй мяжы параметра тыпу ў тыпе выдалення –
пасля
- тып сцірання):
клас Box {
прыватны Нумар каштоўнасць;
грамадская скрыня( Нумар t) {значэнне = t; }
...
}
- Акрамя таго, крайняя левая верхняя мяжа ’ з'яўляецца ў іншых месцах, такіх як аўтаматычна ўстаўленыя клеі і метады моста.
Калі б былі дазволеныя ніжнія межы параметраў тыпу, якія пабочныя эфекты яны б або павінны мець? Калі такая канструкцыя, як клас Box<T вялікі Нумар> {...} былі пражэненыя, што б гэта цанцала? Што азначаюць 3 пабочныя эфекты верхняй мяжы параметра тыпу –, абмежаванае стварэнне асобніка, доступ да нестатычнага члена, выдаленне тыпу – для ніжняй мяжы?
Абмежаваныя асобнікі. Кампілятар можа абмежаваць набор тыпаў, якія могуць быць выкарыстаны для стварэння асобніка агульнага тыпу з ніжняй мяжой параметра тыпу. Напрыклад, кампілятар можа дазволіць такія асобнікі, як Скрынка<Нумар> і Скрынка<Аб'ект> ад а Скрынка<T вялікі Нумар> і адхіліць асобнікі, такія як Box<Long> або Скрынка<Кароткая> . Гэта будзе эфект у адпаведнасці з абмежавальным пабочным эфектам, апісаным для верхніх межаў параметраў тыпу.
Доступ да нестатычных удзельнікаў. Ніжэйшая мяжа параметра тыпу не дае доступу да якіх-небудзь пэўных метадаў, акрамя тых, што ўспадкаваны ад класа Аб'ект . У прыкладзе в Скрынка<T вялікі Нумар> супертыпы Нумар не маюць нічога агульнага, акрамя таго, што яны з'яўляюцца эталоннымі тыпамі і, такім чынам, падтыпамі Аб'ект . Кампілятар не можа меркаваць, што поле тыпу Т мае тып Нумар або яго падтып. Замест гэтага поле тыпу Т можа быць любога супертыпу Нумар , такія як Серыялізуецца або Аб'ект . Выклік такога метаду, як intValue() з’ больш не тыпабяспечны, і кампілятар павінен быў адключыць яго. У выніку ніжняя мяжа параметра тыпу не дазваляе доступ да нестатычных членаў, акрамя вызначаных у класе Аб'ект І, такім чынам, ён мае той жа эфект, што і “ без абмежавання ”.
Тып Erasure . Прытрымліваючыся гэтай логікі, няма сэнсу замяняць параметр тыпу ўводу ніжняй мяжой. Ab’jawnie metadu, падобны канструктар скрынка( Т т) як канструктар скрынка( Нумар т) не мае сэнсу, улічваючы гэта Т замяняецца супертыпам Нумар . Ан Аб'ект можна правільна перадаць канструктару ў асобніку Скрынка<Аб'ект> і канструктар адхіліў бы яго. Гэта было б зусім няправільна. Такім чынам, сціранне тыпу заменіць усе ўваходжанні зменнай тыпу Т па тыпу Аб'ект , а не ніжняя мяжа. Зноў жа, ніжні ліміт будзе мець той жа эфект, што і “ без ліміту ”.
Вы хочаце даведацца, што азначаюць абодва ніжэй _і_ былі дазволеныя верхнія межы? Асабіста я нават не хачу пра гэта думаць і аддаю перавагу паставіць яго на “ некіравальны ”, калі вы гэта дазволіце.
Сутнасток такая: усё, што “ вялікі ” падключаны купіў бы вам гэта абмежаванне толькі супертыпаў Нумар могуць быць выкарыстаны ў якасці аргументаў тыпу. І нават гэта часта няправільна разумеюць. Гэта НЕ будзе азначаць, што клас Box<T супернумар> {...} змяшчае толькі асобнікі супертыпаў Нумар . Наадварот, – як паказвае прыклад ніжэй!
Прыклад (з выкарыстаннем верхняй мяжы параметра тыпу ў тыпе выдалення – раней тып сцірання):
клас Box< Т супер лік > {
прыватны Т каштоўнасць;
грамадская скрыня( Т t) {значэнне = t; }
...
}
Прыклад (з выкарыстаннем верхняй мяжы параметра тыпу ў тыпе выдалення – пасля тып сцірання):
клас Box {
прыватны Аб'ект каштоўнасць;
грамадская скрыня( Аб'ект t) {значэнне = t; }
...
}
А клас Box<T вялікі Нумар> {...} будзе пераведзены тыпам сцірання ў a скрынка які змяшчае ан Аб'ект поле і яго канструктар будуць перакладзены на Скрынка (аб'ект t) . Гэта прынцыпова адрозніваецца ад а клас Box<T пашыраецца Нумар> {...} , што было б у перакладзе на а скрынка які змяшчае а Нумар поле і яго канструктар будуць перакладзены на Скрынка (колькасць т) . Такім чынам, а Скрынка<Нумар> асобнік ад а клас Box<T пашырае Number> {...} будзе адрознівацца ад a Скрынка<Нумар> асобнік ад a клас Box<T супернумар> {...} , што можа выклікаць блытаніну. Па гэтай прычыне ніжнія межы не маюць значэння для параметраў тыпу класа.
Параметры тыпу ніжняй мяжы метадаў
Параметр тыпу з ніжняй мяжой, пагадзінны, можа быць карысны ў спалучэнні з метадамі і тыпамі аргументаў.
Прыклад (метад, які выйграе тып параметра з ніжняй мяжой):
клас Pair<X,Y> {
прыватны X першы;
прыватны Y другі;
...
публічны < Супер X , B супер Y > B addToMap(Map<A,B> map) { // памылка: параметр тыпу не можа мець ніжняй мяжы
вяртанне map.put(першы, другі);
}
}
клас Тэст {
public static void main(String[] args) {
Pair<String,Long> пара = новая пара<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
Нумар нумар = pair.addToMap(карта);
}
}
The addToMap() метад дадае змесціва пары на карту. Любая карта, якая можа ўтрымліваць супертыпы X і Я зрабіў бы. Карта пакласці() метад вяртае значэнне, знойдзенае ў карце для дадзенага ключа, калі ўжо ёсць запіс ключ-значэнне для ключа ў карце. Вяртанае значэнне карты пакласці() метад павінен быць вернуты з addToMap() метад. При деже спортивных чтолись бабОЗYX0Завить метад, как выступление выступление: карта параметризуаля супретипами параметров типа пари и addToMap() Тып вяртання метаду – - гэта тып значэння карты.
Паколькі кампілятар не дазваляе ніжнія межы параметраў тыпу, нам патрэбны абыходны шлях.
Адзін з абыходных шляхоў, які прыходзіць на розум, –, - гэта выкарыстанне падстаноўных знакаў, таму што ў падстаноўных знакаў можа быць меншы ліміт. Вось спосаб перамяшчэння з дапамогай індэкса.
Прыклад (абыход рашэння для папярэдняга прыкладу з выкарыстаннем падстаноўных знакаў):
клас Pair<X,Y> {
прыватны X першы;
прыватны Y другі;
...
грамадскасць Аб'ект addToMap(Map< ? Супер Ікс , ? супер Y карта >) {
вяртанне map.put(першы, другі);
}
}
клас Тэст {
public static void main(String[] args) {
Pair<String,Long> пара = новая пара<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
Нумар лік = (Лік) pair.addToMap(карта);
}
}
Гэта працуе, за выключэннем таго, што няма магчымасці адлюстраваць патрэбны тып вяртання. Гэта быў бы супертып Я што кампілятар фіксуе тып карты, але няма сінтаксісу для яго інструкцый. Мы не павінны ab’ дэклараваць тып вяртання “ ? супер Y “ таму што ” ? супер Y ” з’ з'яўляецца сімвалам падстаноўкі, а не тыпам, і таму не дазваляецца ў якасці тыпу вяртання. У нас няма выбару, і мы павінны Аб'ект замест гэтага як тып вяртання нашага метаду. Гэты даволі неспецыфічны тып вяртання, у сваю чаргу, прымушае тых, хто выклікае addToMap() метад для прывядзення вяртанага значэння ўніз з Аб'ект каб адпавядаць сапраўднаму тыпу. Гэта не зусім тое, што мы мелі на ўвазе.
Іншае абыходнае рашэнне – з выкарыстаннем статычных метадаў. Гэта абыходны шлях - гэта статычная метадама замест нестатычнай.
Прыклад (абыход рашэння для папярэдняга прыкладу з выкарыстаннем статычнага метаду):
клас Pair<X,Y> {
прыватны X першы;
прыватны Y другі;
...
грамадскасць статычны < А , Б ,X пашырае А ,Ю пашырае В > B addToMap(Пара<X,Y> пара, Map< А , Б карта >) {
вяртанне map.put(pair.first,pair.second);
}
}
клас Тэст {
public static void main(String[] args) {
Pair<String,Long> пара = новая пара<>("ABC",42L);
Map<CharSequence, Number> map = HashMap<CharSequence, Number>();
Нумар лік = Пара .addToMap( пара ,карта);
}
}
Радавы addToMap() метад мае чатыры параметры тыпу: два запаўняльнікі X і Я для тыпу пары і двух запаўняльнікаў А і Б для тыпу карты. А і Б z’ - супертыпы X і Я , таму што X і Я ab’are с А і Б як іх верхнія межы. (Звярніце ўвагу, што параметры тыпу агульнага метаду X і Я не маюць нічога агульнага з Пара класа X і Я параметры. Імёны X і Я паўторна выкарыстоўваецца для агульнага метаду, каб зрабіць іх лёгка распазнаваць як параметры тыпу пар. З дапамогай чатырох параметраў тыпу мы можам па жаданні вызначыць дакладны тып вяртання: гэта той жа тып, што і тып значэння карты.
Прыклад (незаконнае выкарыстанне параметра тыпу ў статычным кантэксце агульнага класа):
клас SomeClass <T> {
// статычны ініцыялізатар, статычнае поле, статычны метад
статычны {
SomeClass< Т Тэст > = новы SomeClass< Т >(); // памылка
}
прыватны статычны Т globalInfo; // памылка
грамадскасць статычны Т getGlobalInfo() { // памылка
вярнуць globalInfo;
}
// нестатычны ініцыялізатар, нестатычнае поле, нестатычны метад
{
SomeClass< Т Тэст > = новы SomeClass< Т >();
}
прыватны Т мясцоваяІнфармацыя;
грамадскасць Т getLocalInfo() {
вярнуць лакальную інфармацыю;
}
// статычныя ўкладзеныя тыпы
грамадскасць статычны клас Няўдача пашырае выключэнне {
прыватны фінал Т інфармацыя; // памылка
публічны збой( Т t) {інфармацыя = t; } // памылка
грамадскасць Т getInfo() {вярнуць інфармацыю; } // памылка
}
прыватны інтэрфейс Магчымасць капіравання {
Т капіяваць(); // памылка
}
прыватны пералік стан {
ДЭЖЫЎНЫ, НЕСПРАВЯДНЫ;
прыватны Т інфармацыя; // памылка
public void setInfo( Т t) {інфармацыя = t; } // памылка
грамадскасць Т getInfo() {вярнуць інфармацыю; } // памылка
}
// нестатычныя ўкладзеныя тыпы
грамадскасць клас Accessor {
грамадскасць Т getInfo() {вярнуць лакальную інфармацыю; }
}
}
Прыклад паказвае, што параметр тыпу нельга выкарыстоўваць у статычным кантэксце агульнага класа. Гэта таксама паказвае, што ўкладзеныя інтэрфейсы і тыпы вылічэнняў лічацца членамі тыпу статычнага класа. Толькі ўнутраныя класы, г.зн. нестатычныя ўкладзеныя класы, могуць выкарыстоўваць параметр тыпу ахопліваючага агульнага класа.
Ab’ём параметр тыпу інтэрфейсу – гэта ўсё вызначэнне інтэрфейсу, за выключэннем любых палёў або ўкладзеных тыпаў. Гэта таму, што ўкладзеныя тыпы, вызначаныя ў інтэрфейсе, няяўна статычныя.
Прыклад (незаконнага выкарыстання параметру тыпу ў агульным інтэрфейсе):
інтэрфейс SomeInterface <T> {
// поле
SomeClass< Т Значэнне > = новы SomeClass< Т >(); // памылка
// гнездавы тып
клас Accessor {
грамадскасць Т getInfo() { // памылка
вяртаць value.getGlobalInfo();
}
}
// метады
Т атрымацьЗначэнне();
}
Прыклад паказвае, што палі інтэрфейсу няяўна статычныя, таму параметр тыпу нельга выкарыстоўваць нідзе ў дэкларацыі поля агульнадаступнага інтэрфейсу. Падобным чынам укладзены клас лічыцца статычным укладзеным класам, а не ўнутраным класам, і таму выкарыстанне параметра тыпу ў любым месцы ўкладзенага класа з'яўляецца незаконным.
Абыходны шлях для ўкладзеных статычных класаў і інтэрфейсаў:
У выпадку са статычнымі ўкладзенымі класамі і інтэрфейсамі гэта не з'яўляецца сур'ёзнай праблемай. У якасці абыходнага шляху мы можам стварыць статычны клас або інтэрфейс самастойна.
Прыклад (абыход – стварэння ўкладзенага статычнага тыпу):
клас Wrapper <T> {
прыватны фінал Т theObject;
публічная абалонка ( Т t) { theObject = t; }
грамадскасць Т getWrapper() {вярнуць аб'ект; } грамадскі нязменны <T> makeImmutable() {
вярнуць новы Imutable <T> (аб'ект);
}
публічны зменлівы <T> makeMutable() {
вярнуць новы зменлівы <T> (аб'ект);
}
private static <A> A makeClone(A theObject) { ... }публічны статычны канчатковы клас Нязменны ОЗЫХ0ЗАОЗЫХ1З { // зараз з'яўляецца агульным класам
прыватны фінал А theObject;публічны Нязменны( А аргумент) {
theObject = makeClone(arg);
}
грамадскасць А getWrappedItem() {
вяртанне makeClone(theObject);
}
}
публічны статычны клас Mutable ОЗЫХ0ЗАОЗЫХ1З {
... падобны ...
}
}
Такога абыходнага шляху для ўкладзеных тыпаў пералічэнняў няма, таму што яны не могуць быць агульнымі.
Абыходны шлях для ўкладзеных статычных класаў:
Калі ўкладзены статычны тып z’ з'яўляецца класам, альтэрнатыўным абыходным шляхам з'яўляецца пераўтварэнне статычнага класа ў нестатычны ўнутраны клас.
Прыклад (абыходны шлях – выкарыстоўвае ўнутраны клас):
клас Wrapper <T> {
прыватны фінал Т theObject;
публічная абалонка ( Т t) { theObject = t; }
грамадскасць Т getWrapper() {вярнуць аб'ект; } public Mutable makeMutable() {
вяртанне гэта. новы зменлівы(theObject);
}
public Immutable makeImmutable() {
вяртанне гэта. новы Immutable(theObject);
}
private static <A> A makeClone(A theObject) { ... }публічны канчатковы клас Нязменны { // больш не з'яўляецца статычным класам
прыватны фінал Т theObject;публічны Нязменны( Т аргумент) {
theObject = makeClone(arg);
}
грамадскасць Т getWrappedItem() {
вяртанне makeClone(theObject);
}
}
публічны клас Mutable
... падобны ...
}
}
Гэты абыходны шлях прадугледжвае некаторыя накладныя выдаткі, таму што ўсе ўнутраныя класы маюць схаваную спасылку на экзэмпляр знешняга тыпу, які не патрэбны і не выкарыстоўваецца ў гэтым дадатку; схаваная спасылка – гэта ўсяго толькі багаж. Большасць унутраных класаў падлучаны да інтэрфейсаў, каб захаваць прыватнасць унутранага класа з дэталямі рэалізацыі канчатковага класа. Мы можам зрабіць тое ж самае тут.
Прыклад (перароблены папярэдні абыходны шлях):
клас Wrapper <T> {
прыватны фінал Т theObject;
публічная абалонка ( Т t) { theObject = t; }
грамадскасць Т getWrapper() {вярнуць аб'ект; } публічны зменлівы <T> makeMutable() {
вярнуць this.new Mutable(theObject);
}
грамадскі нязменны <T> makeImmutable() {
вярнуць this.new Imutable(theObject);
}
private static <A> A makeClone(A theObject) { ... }публічны інтэрфейс нязменны<S> {
S getWrappedItem();
}
публічны інтэрфейс Зменлівы <S> {
S getWrappedItem();
несапраўдны setWrappedItem(S arg);
}
прыватны канчатковы клас ImmutableImplementation рэалізуе Immutable<T> {прыватны канчатковы T theObject;
public ImmutableImplementation(T arg) {
theObject = makeClone(arg);
}
грамадскі T getWrappedItem() {
вяртанне makeClone(theObject);
}
}
прыватны клас MutableImplementation рэалізуе Mutable<T> {
... падобны ...
}
}
Як бачыце, укладзеныя публічныя інтэрфейсы павінны быць агульнымі, у той час як прыватныя ўнутраныя класы могуць выкарыстоўваць параметр тыпу заключнага класа.

Пакінуць адказ