java 泛型
java泛型
一.泛型
一.泛型
- “泛型”意思就是适用于许多许多的类型。
二.简单泛型
- 使用泛型的目的之一: 指定容器持有什么类型,让编译器确保正确性,而不是在运行期发现错误
- 这个容器可以看成是有其他类型对象作为成员的类,而不单单只是JDK中的容器类。
- java泛型的核心概念:告诉编译器想使用什么类型,然后编译器帮你处理一切细节
1.一个元组类库
- 元组是对象,是将一组对象直接打包存储于其中的一个单一对象。Java中没有元组这个类库,Python中有元组这一概念。
- 可以通过泛型构建自己的元组类库。
class TwoTuple<A,B>{ public final A first; public final B second; TwoTuple(A a, B b){ first = a; second = b; }
- 元组允许读取元素,但不能插入新元素,不可以修改元素值,因为元素被设置为final。
- 元组可以任意长度,可以存储任何类型对象。
2.元组泛型的继承
- 父类的泛型同样可以继承给子类,但要显示的写出父类的泛型
class ThreeTuple<A,B,C> extends TwoTuple<A,B>{ public final C three; public ThreeTuple(A a, B b, C c){ super(a,b); three = c; }
-
一个方法只能返回一个对象,但返回一个元组就可以包含多个对象。
public static TwoTuple<String,Integer> f(){ return new TwoTuple<String, Integer>("hi",99); }
3.一个类堆栈
- 内部类可以访问外部类的类型参数
4. 泛型接口
- 泛型也可以用于接口,例如生成器,生成器是专门负责创建对象类,一般只定义一个方法。
-
package net.mindview.util; public interface Generator<T> { T next(); } ///:~
5.泛型方法
- 泛型方法所在的类可以是泛型类,也可以不是泛型类,并且泛型标识符可以完全不一样,也就是说泛型方法和泛型类无关。
- 普通static方法无法访问泛型类的类型参数,如果要是使用泛型就要定义成泛型静态方法
要定义泛型方法只需要将泛型参数列表置于返回值前,就像下面这样:
public class GenericMethods { public <T> void f(T x) { System.out.println(x.getClass().getName()); } public static void main(String[] args) { GenericMethods gm = new GenericMethods(); gm.f(""); gm.f(1); gm.f(1.0); gm.f(1.0F); gm.f('c'); gm.f(gm); } }
5.1 杠杆利用类型参数推断
- 使用泛型方法时编译期会通过类型参数推断来为我们找出具体类型,而不必自己声明时什么类型
-
package net.mindview.util; import java.util.*; public class New { public static <K,V> Map<K,V> map() { return new HashMap<K,V>(); } public static <T> List<T> list() { return new ArrayList<T>(); } public static <T> LinkedList<T> lList() { return new LinkedList<T>(); } public static <T> Set<T> set() { return new HashSet<T>(); } public static <T> Queue<T> queue() { return new LinkedList<T>(); } // Examples: public static void main(String[] args) { Map<String, List<String>> sls = New.map(); List<String> ls = New.list(); LinkedList<String> lls = New.lList(); Set<String> ss = New.set(); Queue<String> qs = New.queue(); } } ///:~
- 类型推断只对赋值操作有效,其它时候不起作用
-
package generics; //: generics/LimitsOfInference.java import typeinfo.pets.*; import java.util.*; public class LimitsOfInference { static void f(Map<Person, List<? extends Pet>> petPeople) {} public static void main(String[] args) { // f(New.map()); // Does not compile } } ///:~
5.2.显示类型说明
- 类型推断只对赋值操作有效,其它时候不起作用,通过显示类型说明可以解决上面的问题
-
package generics; //: generics/ExplicitTypeSpecification.java import typeinfo.pets.*; import java.util.*; import net.mindview.util.*; public class ExplicitTypeSpecification { static void f(Map<Person, List<Pet>> petPeople) {} public static void main(String[] args) { f(New.<Person, List<Pet>>map());//如果在定义该方法的类的内部必须加this,如果是static方法必须在.操作符前加上类名 } } ///:~
5.3 可变参数与泛型方法
泛型方法与可变参数列表能够很好地共存,下面的方法展示了和类库java.util.Arrays.asList()方法相同的功能
package generics; //: generics/GenericVarargs.java import java.util.*; public class GenericVarargs { public static <T> List<T> makeList(T... args) { List<T> result = new ArrayList<T>(); for(T item : args) result.add(item); return result; } public static void main(String[] args) { List<String> ls = makeList("A"); System.out.println(ls); ls = makeList("A", "B", "C"); System.out.println(ls); ls = makeList("ABCDEFFHIJKLMNOPQRSTUVWXYZ".split("")); System.out.println(ls); } } /* Output: [A] [A, B, C] [, A, B, C, D, E, F, F, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z] *///:~
5.4 用于Generator的泛型方法
package generics; //: generics/Generators.java // A utility to use with Generators. import generics.coffee.*; import java.util.*; import net.mindview.util.*; public class Generators { public static <T> Collection<T> fill(Collection<T> coll, Generator<T> gen, int n) { for(int i = 0; i < n; i++) coll.add(gen.next()); return coll; } public static void main(String[] args) { Collection<Coffee> coffee = fill( new ArrayList<Coffee>(), new CoffeeGenerator(), 4); for(Coffee c : coffee) System.out.println(c); Collection<Integer> fnumbers = fill( new ArrayList<Integer>(), new Fibonacci(), 12); for(int i : fnumbers) System.out.print(i + ", "); } } /* Output: Americano 0 Latte 1 Americano 2 Mocha 3 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, *///:~
6. 匿名内部类
泛型还可以应用于匿名类以及匿名内部类
7.构建复杂模型
15.7 擦除
- jvm并不认识泛型因此需要将泛型擦除。
- ArrayList<String> 和 ArrayList<Integer>很容易被认为是不同类型。因为他们有不同的行为,但程序却认为他们是相同的,正是因为擦除的存在。
- 擦除的结果就是把一个对象变为它的原生类
- 泛型只是用来检查类型正确性,泛型部分的代码不会参与运行,这是由于泛型的擦除作用。
- 在泛型代码内部无法获得有关泛型参数的类型的信息。
15.7.1 擦除的边界
- 边界<T extends HasF > 声明 T 只能为HasF或者从HasF导出的类型。
- 泛型只是在静态类型检查期间出现来验证类型正确性,程序一但运行后泛型都将被擦除,替换成他们的非泛型上界,如List<T>被擦除为List,List<String>被擦除为List<Object>, <T extends HasF > 擦除为 <HasF>
15.7.2 迁移的兼容性
- 擦除的核心动机是它使得泛化的客户端可以用非泛化的类库来使用,反之亦然,这经常被称为"迁移的兼容性".
15.7.3擦除的问题
- 泛型不能当做一个类去操作,如Foo<cat>不能代表Cat这个类,因为它会被擦除为Object.
-
class Foo<T>{ T var; }
15.7.4 边界处的动作
- 泛型中创建数:Array.
newInstance(类<?> componentType, int length) 并且要强制转型为T[]类型。
-
public class Test<T> { private Class<T> kind; T[] create(int size){ return (T[]) Array.newInstance(kind,size);//必须强转T[] } List<T> createList(){ return new ArrayList<T>(); } }
- 边界就是对象进入和离开方法的地方,编译期执行类型检查和插入转型代码就是在边界处。
- 编译期执行了类型检查确保了数据一致性,在离开方法时由编译器为你插入转型代码执行转型,此时转型是安全的。
- 由于擦除kind实际被存储为Class,因此创建数组无法后知道要转型成什么类型,因此必须强转。但创建容器就不需要强转了,编译期可以保证类型的一致性,如果类型不一致不通过编译。
- 使用泛型时,类型检查由编译器执行,请记住.---边界就是发生动作的地方
15.8 擦除的补偿
- 由于擦除存在,所以任何在 运行时 需要知道泛型代表的类型的 确切类型信息 的操作都无法工作。
- 解决办法:引入类型标签
- 给泛型类添加一个标签成员Class<T> kind; 构造器传入类型参数赋值给kind,这样就得到了泛型的类型。
15.8.1 创建类型实例
- 创建泛型的实例 不可以 new T() 一来因为擦除,二来因为不能确定T是否有默认构造器.
- 利用类型标签 可以kind.newInstance()创建对象,但遇到没有默认构造器的类如Integer,运行时就会出错,而编译期无法捕获错误。
- Java解决方案是传入一个显示工厂对象来创建实例,并且限制其类型。
package generics; //: generics/FactoryConstraint.java interface FactoryI<T> { T create(); } class Foo2<T> { private T x; public <F extends FactoryI<T>> Foo2(F factory) { x = factory.create(); // } // ... } class IntegerFactory implements FactoryI<Integer> { public Integer create() { //限定类型为Integer return new Integer(0); } } class Widget { public static class Factory implements FactoryI<Widget> { public Widget create() { return new Widget(); } } } public class FactoryConstraint { public static void main(String[] args) { new Foo2<Integer>(new IntegerFactory()); new Foo2<Widget>(new Widget.Factory()); } } ///:~
- 使用模板方法设计模式
package generics; //: generics/CreatorGeneric.java abstract class GenericWithCreate<T> {//模板 final T element; GenericWithCreate() { element = create(); } abstract T create();//子类中定义 } class X {} class Creator extends GenericWithCreate<X> { X create() { return new X(); } void f() { System.out.println(element.getClass().getSimpleName()); } } public class CreatorGeneric { public static void main(String[] args) { Creator c = new Creator(); c.f(); } } /* Output: X *///:~
15.8.2泛型数组
- 不能直接创建泛型数组 T[] array = new T[size]
- 可以定义一个泛型数组的引用 T[ ] array ,但无法使用这个引用。
- 解决办法
- 可以使用ArrayList<T>来代替数组达到相同目的。
- 内部创建一个Object[ ] 数组,在需要时将数组内的对象转型为需要的类型,但不能将Object[ ]转型为T[ ],因为没有任何方式可以改变数组底层类型。
- 最好的解决办法时引入类型标记 Class<T> type
package generics; //: generics/GenericArrayWithTypeToken.java import java.lang.reflect.*; public class GenericArrayWithTypeToken<T> { private T[] array; @SuppressWarnings("unchecked") public GenericArrayWithTypeToken(Class<T> type, int sz) { array = (T[])Array.newInstance(type, sz);//这里获得了正确的类型 } public void put(int index, T item) { array[index] = item; } public T get(int index) { return array[index]; } // Expose the underlying representation: public T[] rep() { return array; } public static void main(String[] args) { GenericArrayWithTypeToken<Integer> gai = new GenericArrayWithTypeToken<Integer>( Integer.class, 10); // This now works: Integer[] ia = gai.rep(); } } ///:~
15.6 边界
- 边界使得你可以在用于泛型的参数类型上设置限制条件,规定泛型可以应用的类型,其潜在的效果是按边界类型调用方法其方法,
- 无边界泛型参数只能可以用从Objec调用的方法,但是如果将这个参数限制为某个类型的子集,那么你就可以用这些子集来调用方法。
15.10 通配符
- 数组的一种特殊行为:数组只可以存储子类不可以存储父类
package generics; //: generics/CovariantArrays.java class Fruit {} class Apple extends Fruit {} class Jonathan extends Apple {} class Orange extends Fruit {} public class CovariantArrays { public static void main(String[] args) { Fruit[] fruit = new Apple[10]; fruit[0] = new Apple(); // OK fruit[1] = new Jonathan(); // OK // Runtime type is Apple[], not Fruit[] or Orange[]: try { // Compiler allows you to add Fruit: fruit[0] = new Fruit(); // ArrayStoreException } catch(Exception e) { System.out.println(e); }//RuntimeException try { // Compiler allows you to add Oranges: fruit[0] = new Orange(); // ArrayStoreException } catch(Exception e) { System.out.println(e); } } } /* Output: java.lang.ArrayStoreException: Fruit java.lang.ArrayStoreException: Orange *///:~
- 通配符可以允许某种类型向上转型,与普通边界对比:
- List<T extends Fruit> first = new ArrayList<T>(); 只能使用T
- List<? extends Fruit> first = new ArrayList<Apple>(); //可以使用各种Fruit的子类。
- List<? extends Fruit> 读作具有任何从Fruit继承的类型列表。
15.10.1 编译器有多聪明
- 数组可以向上转型,即导出类型的数组可以赋值给及基本类型数组的引用
- 而导出类型的泛型确不能赋值给基本类型泛型的引用 如: List<Fruit> list = new ArrayList<Apple>(); 语法错误
- Apple 是 Fruit 的子类,但 Apple所在的List却不是Fruit所在的List的子类,故不能这样转型。
15.10.2 逆变
- 超类通配符
- <? extends Fruit> 上界 ?是Fruit 的子类,但具体是什么不知道,因此当调用get方法时返回的对象可以赋值给Fruit引用,而add添加对象时由于不清楚具体要添加什么子类所以无法使用add方法。
- List<Fruit> fruit = new ArrayList<Fruit>(); //fruit可以添加任意Fruit的子类
- <? super Apple > 下界 也称 逆变 ?是Apple的父类,但具体是什么类型不得而知,因此当调用add方法添加对象时可以添加Apple和其子类对象,但调用get方法时无法确定要返回什么类型,因此不能调用get方法返回具体类型,只能返回Object。
15.10.3 无界通配符
- 无界通配符<?>表示任何类型,相当于使用原生类 ,他还意味着声明 我要在这里使用泛型
- List实际上是List<Object>,List实际表示"持有任何Object类型的原生List".而List<?>表示"具有某种特定类型的非元素List,只是我们不知道那种类型事什么
- 使用确切类型替代通配符的好处是,可以用泛型参数做更多的事,但是使用通配符使得你必须接受范围更宽的参数化类型作为参数.因此必须逐个情况地权衡利弊,找到更适合你的方法
- <?>与上下界之间的区别
- 一个方法的参数的类型如是 List ,List<?> ,则可以接收任何形式的List参数,参数是不是泛型无所谓。
- 参数的类型如果是List<? extends/super A > ,则只能接收泛型的List参数.
- 如果参数的类型是 <?> 或者 <? extends A>,则该方法无法调用
- <?>可以向上转型
- 多个泛型参数下只有全为?时编译器无法与原生类区分,但只要有过一个参数不是?就会有所区分如Map<String, ?>必须传入map<String,?>类型的参数,而Map<?,?>可以传入new HashMap();
- 3
15.10.4 捕获的转换
- 向一个<?>方法传入一个原生类型,编译器可以捕获实际类型参数,这个<? >方法调用其他方法时向其他方法传递被捕获的对象时就会传递确切类型。 如 A a =new A<B>(); 将a传入f(A<?> s)方法,f可以捕获确切类型 即s=A<B>
15.11 问题
-
15.11.1 任何基本类型都不能作为类型参数
- 基本数据类型无法作为泛型的类型参数,如<T> T不能是int 可以使用包装器类
- 自动包装机制不能用于数组,int[ ]不能成为Integer[ ]
- 带有泛型类型参数的转换或者使用instanceof判断类型都没有任何效果
- 被擦除后如果产生相同的方法签名那么不允许编译。
-
15.11.2 实现参数化接口
- 一个类不能实现一个泛型接口的来自变体,由于擦除的原因,这两个变体会变成相同的接口
interface A<T>{}
class B implements A<D>{} class C extends B implments A<D>{} //error
- 一个类不能实现一个泛型接口的来自变体,由于擦除的原因,这两个变体会变成相同的接口
-
15.11.3 转型和警告
- 带有泛型类型参数的转换或者使用instanceof判断类型都没有任何效果
-
15.11.4 重载
- 当有多个泛型标识符时,由于擦除的原因,重载方法将产生相同的类型签名,下面的程序不能重载
package generics; //: generics/UseList.java // {CompileTimeError} (Won't compile) import java.util.*; public class UseList<W,T> { void f(List<T> v) {} void f(List<W> v) {} //将f改名,不重载的化可以编译 } ///:~
- 当有多个泛型标识符时,由于擦除的原因,重载方法将产生相同的类型签名,下面的程序不能重载
-
15.11.5 基类劫持了接口
15.12 自限定类型
- 在java泛型中,有一个好像是经常性出现的惯用法,它相当令人费解:
class SelfBounded<T extends SelfBounded<T>>{//...
-
15.12.1 古怪的循环泛型
- 古怪的循环是指类相当高古怪的出现在它自己的基类中这一事实
class Generictype<T>{}
public class CuriouslyRecurringGeneric extends GenericType<CuriouslyRecurringGeneric>{} - 基类用导出类替代其参数,这意味着泛型基类变成了一种其所有导出类的公共功能的模板,但是这些功能对其所有参数和返回值,将使用导出类,也就是说,在所产生的类中将使用确切类型而不基类型
public class BasicHolder<T> {
T element;
public void set(T arg) { element = arg; }
public T get() { return element; }
public void f() {
System.out.println(element.getClass().getSimpleName());
}
} ///:~class A extends BasicHolder{}
public class Test {
public static void main(String[] args) {
A a = new A(), b = new A(); b.set(b); b.f();
}
}
- 古怪的循环是指类相当高古怪的出现在它自己的基类中这一事实
-
15.12.2 自限定
class SelfBounded<T extends SelfBounded<T>>{//...
A extends SelfBounded<A> //使用方法 -
15.12.2 参数协变
-
15.13 动态类型安全
- java.util.Collections中有一组工具,可以进行动态类型安全检查,他们是
- checkedCollection()
- checkedList()
- checkedMap()
- checkedSet()
- checkedSortedMap()
- checkedSortedSet()
package ch35; //: generics/CheckedList.java // Using Collection.checkedList(). import typeinfo.pets.*; import java.util.*; public class CheckedList { @SuppressWarnings("unchecked") static void oldStyleMethod(List probablyDogs) { probablyDogs.add(new Cat()); } public static void main(String[] args) { List dogs1 = new ArrayList(); oldStyleMethod(dogs1); // Quietly accepts a Cat List dogs2 = Collections.checkedList( new ArrayList(), Dog.class); try { oldStyleMethod(dogs2); // Throws an exception } catch(Exception e) { System.out.println(e); } // Derived types work fine: List pets = Collections.checkedList( new ArrayList(), Pet.class); pets.add(new Dog()); pets.add(new Cat()); } } /* Output: java.lang.ClassCastException: Attempt to insert class typeinfo.pets.Cat element into collection with element type class typeinfo.pets.Dog *///:~
- java.util.Collections中有一组工具,可以进行动态类型安全检查,他们是
-
15.14 异常
- 由于擦除的原因,将泛型应用于异常是非常受限的,catch语句不能捕获泛型的异常,因为在编译期和运行时都必须知道异常的确切类型,泛型类也不能直接或间接继承来自Throwable
- 可以编写随检查型异常的类型而发生变化的泛型代码
package generics; //: generics/ThrowGenericException.java import java.util.*; interface Processor<T,E extends Exception> { void process(List resultCollector) throws E; } class ProcessRunner<T,E extends Exception> extends ArrayList<Processor<T,E>> { List processAll() throws E { List resultCollector = new ArrayList(); for(Processor<T,E> processor : this) processor.process(resultCollector); return resultCollector; } } class Failure1 extends Exception {} class Processor1 implements Processor<String,Failure1> { static int count = 3; public void process(List resultCollector) throws Failure1 { if(count-- > 1) resultCollector.add("Hep!"); else resultCollector.add("Ho!"); if(count < 0) throw new Failure1(); } } class Failure2 extends Exception {} class Processor2 implements Processor<Integer,Failure2> { static int count = 2; public void process(List resultCollector) throws Failure2 { if(count-- == 0) resultCollector.add(47); else { resultCollector.add(11); } if(count < 0) throw new Failure2(); } } public class ThrowGenericException { public static void main(String[] args) { ProcessRunner<String,Failure1> runner = new ProcessRunner<String,Failure1>(); for(int i = 0; i < 3; i++) runner.add(new Processor1()); try { System.out.println(runner.processAll()); } catch(Failure1 e) { System.out.println(e); } ProcessRunner<Integer,Failure2> runner2 = new ProcessRunner<Integer,Failure2>(); for(int i = 0; i < 3; i++) runner2.add(new Processor2()); try { System.out.println(runner2.processAll()); } catch(Failure2 e) { System.out.println(e); } } } ///:~