無錫網(wǎng)站的建設(shè)搜索引擎優(yōu)化關(guān)鍵詞選擇的方法有哪些
目錄
泛型存在的問題
在泛型中使用基本類型
實現(xiàn)參數(shù)化接口
類型轉(zhuǎn)換和警告
無法實現(xiàn)的重載
基類會劫持接口
自限定類型
奇異遞歸類型
自限定
自限定提供的參數(shù)協(xié)變性
本筆記參考自: 《On Java 中文版》
泛型存在的問題
??????? 接下來討論的,是在泛型中經(jīng)常可能遇到的一些問題。
在泛型中使用基本類型
??????? Java的泛型并不支持基本類型,因此我們無法將其用作泛型的類型參數(shù)。一個替代的方法是使用基本類型的包裝類:
【例子:通過包裝類使用泛型】
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;public class ListOfInt {public static void main(String[] args) {List<Integer> li = IntStream.range(38, 48).boxed() // 將基本類型轉(zhuǎn)換成其對應(yīng)的包裝類.collect(Collectors.toList());System.out.println(li);}
}
??????? 程序執(zhí)行的結(jié)果是:
??????? 這足以應(yīng)付大部分的情況。但如果真的需要追求性能,可以使用專門適配基本類型的集合,例如org.apache.commons.collections.primitives。
??????? 或者,可以使用泛型集合來裝載基本類型:
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;public class ByteSet {Byte[] possibles = {1, 2, 3, 4, 5, 6, 7, 8, 9};Set<Byte> mySet1 =new HashSet<>(Arrays.asList(possibles));// 不可行的方式:/* Set<Byte> mySet2 =new HashSet<>(Arrays.<Byte>asList(1, 2, 3, 4, 5, 6, 7, 8, 9)); */
}
??????? 在這里,自動裝箱機制為我們解決了轉(zhuǎn)換問題。但它不會總是有效,例如:
【例子:向數(shù)組中填充對象】
import java.util.*;
import java.util.function.*;interface FillArray {static <T> T[] fill(T[] a, Supplier<T> gen) {// 使用get()填充數(shù)組aArrays.setAll(a, n -> gen.get());return a;}static int[] fill(int[] a, IntSupplier gen) {Arrays.setAll(a, n -> gen.getAsInt());return a;}static long[] fill(long[] a, LongSupplier gen) {Arrays.setAll(a, n -> gen.getAsLong());return a;}static double[] fill(double[] a, DoubleSupplier gen) {Arrays.setAll(a, n -> gen.getAsDouble());return a;}
}interface Rand {// SplittableRandom也是用于生成隨機數(shù)的類SplittableRandom r = new SplittableRandom(47);class StringGenerator implements Supplier<String> {int strlen;StringGenerator(int strlen) {this.strlen = strlen;}@Overridepublic String get() {return r.ints(strlen, 'a', 'z' + 1).collect(StringBuilder::new,StringBuilder::appendCodePoint,StringBuilder::append).toString();}}class IntegerGenerator implements IntSupplier {@Overridepublic int getAsInt() {return r.nextInt(10_000);}}
}public class PrimitiveGenericTest {public static void main(String[] args) {String[] strings = FillArray.fill(new String[5], new Rand.StringGenerator(7));System.out.println(Arrays.toString(strings));int[] integers = FillArray.fill(new int[9], new Rand.IntegerGenerator());System.out.println(Arrays.toString(integers));}
}
??????? 程序執(zhí)行的結(jié)果是:
??????? 由于自動裝箱對數(shù)組無效,因此需要我們手動重載FillArray.fill()方法,或者通過一個生成器來包裝輸出結(jié)果。
實現(xiàn)參數(shù)化接口
??????? 一個類無法實現(xiàn)同一個泛型接口的兩種變體:
因為類型擦除,這兩個變體實際上都表示著原生的Payable。換言之,上述代碼中Hourly將同一個接口實現(xiàn)了兩次。
類型轉(zhuǎn)換和警告
??????? 因為類型擦除,我們無法對類型參數(shù)使用類型轉(zhuǎn)換或instanceof。因此,有時會需要在邊界處進行類型轉(zhuǎn)換:
【例子:在泛型邊界處進行類型轉(zhuǎn)換】
import java.util.Arrays;
import java.util.stream.Stream;class FixedSizeStack<T> {private final int size;private Object[] storage;private int index = 0;FixedSizeStack(int size) {this.size = size;storage = new Object[size];}public void push(T item) {if (index < size)storage[index++] = item;}@SuppressWarnings("unchecked")public T pop() {return index == 0 ?null : (T) storage[--index];}@SuppressWarnings("unchecked")Stream<T> stream() {return (Stream<T>) Arrays.stream(storage);}
}public class GenericCast {static String[] letters ="ABCDEFGHIJKLMNOPQRST".split("");public static void main(String[] args) {FixedSizeStack<String> strings =new FixedSizeStack<>(letters.length);Arrays.stream(letters).forEach(strings::push);System.out.println(strings.pop());strings.stream().map(s -> s + " ").forEach(System.out::print);}
}
??????? 程序執(zhí)行的結(jié)果是:
??????? pop()和stram()會產(chǎn)生警告,因為編譯器無法知道這種類型轉(zhuǎn)換是否安全。在本例中,類型參數(shù)T會被擦除成Object。
??????? 雖然在泛型的邊界處,類型轉(zhuǎn)換會自動發(fā)生。但有時我們?nèi)匀恍枰謩舆M行類型轉(zhuǎn)換,此時編譯器會發(fā)出警告:
【例子:對泛型進行轉(zhuǎn)型】
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;public class NeedCasting {@SuppressWarnings("unchecked")public void f(String[] args) throws Exception {ObjectInputStream in = new ObjectInputStream(new FileInputStream(args[0]));List<Integer> shapes = (List<Integer>) in.readObject();}
}
??? 實際上,readObject()不會知道它正在讀取什么,因此它會返回Object。
??????? 現(xiàn)在注釋掉@SuppressWarnings("unchecked"),并且使用參數(shù)-Xlint:unchecked進行編譯:
警告清楚地告訴了我們,readObject()會返回一個未經(jīng)檢查的Object。
??????? Java 5還引入了一個轉(zhuǎn)型方法,通過Class.cast(),可以將對象強制轉(zhuǎn)換成目標(biāo)類型。這個方法也適用于泛型:
【例子:嘗試強制轉(zhuǎn)換泛型】
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;public class ClassCasting {@SuppressWarnings("unchecked")public void f(String[] args) throws Exception {ObjectInputStream in = new ObjectInputStream(new FileInputStream(args[0]));// 無法編譯的代碼:// List<Integer> lw1 =// List<>.class.cast(in.readObject()); // 使用cast()進行強制類型轉(zhuǎn)換// 會引發(fā)警告:List<Integer> lw2 = List.class.cast(in.readObject());// 無法編譯:// List<Integer> lw3 = List<Integer>.class.cast(in.readObject());// 會引發(fā)警告List<Integer> lw4 = (List<Integer>) List.class.cast(in.readObject());}
}
??????? 然而,如代碼所示。這些做法都會存在著這樣那樣的限制。
無法實現(xiàn)的重載
??????? 由于類型擦除,下面的這種寫法是不被允許的:
【例子:無法實現(xiàn)的重載】
import java.util.List;public class UseList<W, T> {void f(List<T> v) {}void f(List<W> v) {}
}
??????? 因為被擦除的參數(shù)無法作為單獨的參數(shù)列表,所以我們還需要為每一個相似的方法提高不同的方法名。
基類會劫持接口
??????? 假設(shè)我們想要創(chuàng)建一個類,這個類實現(xiàn)了Comparable接口,這樣這個類的不同對象就能進行互相的比較:
【例子:實現(xiàn)了Comparable的父類】
public class ComparablePetimplements Comparable<ComparablePet> {@Overridepublic int compareTo(ComparablePet arg) {return 0;}
}
??????? 一個好的想法是,任何繼承了這個類的子類,其對象之間應(yīng)該也能進行比較(在這個例子中,父類是Pet,子類就是Cat)。然而事實并不會如我們所愿:
??????? 遺憾的是,若繼承了父類的泛型接口,編譯器不會再允許我們添加另一個Comparable接口。在這里,我們只能遵循父類的比較方式。
??? 我們還可以在子類中重寫compareTo()的行為,但這種行為是面向ComparablePet的(而不是限定在這個子類中)。
自限定類型
??????? 自限定類型來自于Java早期的泛型使用習(xí)慣:
class SelfBounded<T extends SelfBounded<T>> { // ...
在這里,類型參數(shù)的邊界就是類本身:SelfBounded有一個類型參數(shù)T,而參數(shù)T的邊界卻又是SelfBounded。
??? 這種寫法更加強調(diào)extends在泛型參數(shù)中使用時的含義。
奇異遞歸類型
??????? 先看一個自限定類型的簡化版本。盡管無法直接繼承泛型參數(shù),但我們可以繼承一個使用了泛型參數(shù)的類。
【例子:繼承泛型類】
class GenericType<T> {
}public class CuriouslyRecurringGenericextends GenericType<CuriouslyRecurringGeneric> {
}
????????這種方式被稱為奇異遞歸泛型。其中,“奇異遞歸”是指子類奇怪地出現(xiàn)在了其基類中的現(xiàn)象、
??????? 要理解這一點,首先需要明確:Java泛型的重點在于參數(shù)和返回類型,因此可以生成將派生類型作為參數(shù)和返回值的基類。派生類型也可作為字段,不過此時它們會被擦除為Object。
【例子:用子類替換基類的參數(shù)】
??????? 首先定義一個簡單的泛型:
public class BasicHolder<T> {T element;void set(T arg) {element = arg;}T get() {return element;}void f() {System.out.println(element.getClass().getSimpleName());}
}
??????? 在這個基類中,所有方法的接收或返回值(若有)都是T。接下來嘗試使用這個類:
class Subtype extends BasicHolder<Subtype> {
}public class CRGWithBasicHolder {public static void main(String[] args) {// Subtype中的所有方法,其接收和返回的都是Subtype:Subtype st1 = new Subtype(),st2 = new Subtype();st1.set(st2);Subtype st3 = st1.get();st1.f();}
}
??????? 程序執(zhí)行的結(jié)果是:
??????? 需要注意的是,Subtype類中,所有方法的接收和返回值都已經(jīng)變成了Subtype。這就是一個奇異遞歸泛型:基類用子類替換了其參數(shù)。在這里,基類用于提供通用的方法模板,而子類使用的方法都會具有一個具體的類型,即子類自身。
自限定
??????? 上述的BasicHolder可以將任何類型作為其泛型參數(shù):
【例子:BasicHolder的廣泛應(yīng)用】
class Other {
}// 將不相關(guān)的Other作為參數(shù)
class BasicOther extends BasicHolder<Other> {
}
?????? 自限定在這種操作的基礎(chǔ)上更進一步,它強制地把泛型作為自身的邊界參數(shù)進行使用:
// 自限定類型:
class SelfBounded<T extends SelfBounded<T>> {T element;SelfBounded<T> set(T arg) {element = arg;return this;}T get() {return element;}
}class A extends SelfBounded<A> {
}// 屬于SelfBounding<>的類型也可以這樣使用:
class B extends SelfBounded<A> {
}class C extends SelfBounded<C> {C setAndGet(C arg) {set(arg);return get();}
}class D {
}
// 但這種做法是不被允許的:
// class E extends SelfBounding<D> {
// }// 這樣的可以(自限定的語法并非強制性的):
class F extends SelfBounded {
}public class SelfBounding {public static void main(String[] args) {A a = new A();a.set(new A());a = a.set(new A()).get();a = a.get();C c = new C();c = c.setAndGet(new C());}
}
??????? 需要注意的是,自限定類型會要求類處于繼承關(guān)系中。因此像E這種并不處于繼承關(guān)系中的類無法使用自限定。
??????? 除此之外,可以看到編譯器并沒有對F這種寫法發(fā)出警告:
class F extends SelfBounded {}
由此可知,編譯器對自限定的語法并不做強制要求,這需要程序員自己注意(或使用工具保證不會使用原生類型)。
??????? 注意:自限定類型只服務(wù)于強制繼承關(guān)系。若使用自限定,這意味著該類使用的類型參數(shù)和使用該參數(shù)的類屬于同一個基類。
??? 對于普通的泛型類而言,像上例中的E這樣的類型是可以作為泛型參數(shù)的。這種泛型類就沒有對繼承關(guān)系的強制性要求。
??????? 除此之外,自限定還可用于泛型方法:
【例子:使用了自限定的泛型方法】
public class SelfBoundingMethods {static <T extends SelfBounded<T>> T f(T arg) {return arg.set(arg).get();}public static void main(String[] args) {A a = f(new A());}
}
這種做法的特點是,方法f()無法應(yīng)用于自限定參數(shù)規(guī)定范圍之外的對象。
自限定提供的參數(shù)協(xié)變性
??????? 自限定類型的價值在于它可以生成協(xié)變參數(shù)類型,即方法參數(shù)的類型會隨著子類而變化。現(xiàn)在先來看一個協(xié)變參數(shù)類型的例子,這種寫法是Java 5引入的:
【例子:Java中的協(xié)變參數(shù)類型】
class Base {
}class Derived extends Base {
}interface OrdinaryGetter {Base get();
}interface DerivedGetter extends OrdinaryGetter {@OverrideDerived get();
}public class CovariantReturnTypes {void test(DerivedGetter d) {Derived d2 = d.get();}
}
??????? 這種做法有著自洽的邏輯:子類方法可以返回比其基類方法更加具體的類型(但這種寫法在Java 5之前是行不通的)。
??????? 而自限定方法則可以直接返回精確的派生類型:
【例子:自限定的返回值】
interface GenericGetter<T extends GenericGetter<T>> {T get();
}interface Getter extends GenericGetter<Getter> {
}public class GenericsAndReturnTypes {void test(Getter g) {Getter result = g.get();// 因為返回的類型是子類,因此可以用基類來承接:GenericGetter gg = g.get();}
}
??? 不過,這種做法只在引入了協(xié)變類型的Java 5之后有效。
??????? 與上述這兩種形式不同,在普通的類中,參數(shù)的類型無法隨子類型而變化。
【例子:普通類的返回值】
class OrdinarySetter {void set(Base base) {System.out.println("OrdinarySetter.set(Base)");}
}class DerivedSetter extends OrdinarySetter {void set(Derived derived) {System.out.println("DerivedSetter.set(Derived)");}
}public class OrdinaryArguments {public static void main(String[] args) {Base base = new Base();Derived derived = new Derived();DerivedSetter ds = new DerivedSetter();ds.set(derived);// 編譯通過,但這里發(fā)生的不是重寫,是重載:ds.set(base);}
}
??????? 程序執(zhí)行的結(jié)果是:
??????? 盡管在main()中,ds.set(derived)和ds.set(base)都是合法的,但發(fā)生的并不是重寫,而是重載。從輸出可以看出,在子類DerivedSetter中存在著兩個set()方法,一個參數(shù)是Base,另一個的是Derived。
??? 若對DerivedSetter的set()方法使用@Override注釋,就可以看出問題。
??????? 當(dāng)使用自限定類型時,子類中來自基類的方法的參數(shù)會發(fā)生改變,因此會出現(xiàn)下面這種情況:
【例子:子類方法的參數(shù)會被重寫】
interface SelfBoundSetter<T extends SelfBoundSetter<T>> {void set(T arg);
}interface Setter extends SelfBoundSetter<Setter> {// 未進行任何改動,但實際上set()已經(jīng)被重寫
}public class SelfBoundingAndCovariantArguments {void testA(Setter s1, Setter s2, SelfBoundSetter sbs) {s1.set(s2);// 不允許這么做:// s1.set(sbs);}
}
??????? s1.set(sbs)存在問題:
編譯器認(rèn)為基類無法匹配當(dāng)前set()的類型,盡管上述代碼中并沒有在Setter中顯式地重寫set()方法,但set()的參數(shù)確實已經(jīng)被重寫了。
??????? 若不使用自限定,那么普通的繼承機制就會啟動:
【例子:普通的繼承機制】
// 非自限定的類型:
class OtherGenericSetter<T> {void set(T arg) {System.out.println("GenericSetter.set(Base)");}
}class DerivedGS extends OtherGenericSetter<Base> {void set(Derived derived) {System.out.println("DerivedGS.set(Derived)");}
}public class PlainGenericInheritance {public static void main(String[] args) {Base base = new Base();Derived derived = new Derived();DerivedGS dgs = new DerivedGS();dgs.set(derived);// 發(fā)生了重載:dgs.set(base);}
}
??????? 程序執(zhí)行的結(jié)果是:
??????? 顯然,這里發(fā)生的還是重載。若使用的是自限定,最后只會有一個接收確切類型參數(shù)的方法版本。