Java中泛型
来源:互联网 发布:java saas架构设计 编辑:程序博客网 时间:2024/06/10 16:20
==《Thinking in Java》 第15章笔记==
即使使用了接口,就要求代码必须使用特定的接口,对程序的约束也还是太强了。我们希望达到的目的是编写更通用的代码,要使代码能够应用与“某种不具体的类型”,而不是一个具体的接口或类。泛型这个术语的意思就是适用于许多许多的类型。
- 15.1 与C++的比较
public class Holder<T>{ private T a; ...}
T就是类型参数,Java泛型的核心概念:告诉编译器想使用什么类型,然后编译器帮你处理一切细节。
- 15.2.1 一个元祖类库
元祖:将一组对象直接打包存储于其中的一个单一对象。
public class TwoTuple<A,B>{ public final A first; public fianl B second; public TwoTuple(A a, B b){ first = a; second = b; }}
通过final关键字保证安全性,可以随心所欲的使用这两个对象,却无法改变这两个对象。
- 15.3 泛型接口
例如生成器,是工厂方法设计模式的一种应用。
public interface Generator<T> { T next();}public class CoffeeGenerator implements Generator<Coffee>, Iterable<Coffee> { private Class[] types = {Latte.class, Mocha.class, Cappuccino.class, Americano.class, Breve.class}; private static Random random = new Random(44); public CoffeeGenerator() {} private int size = 0; public CoffeeGenerator(int sz) {size = sz;} @Override public Coffee next() { try{ return (Coffee) types[random.nextInt(types.length)].newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } class CoffeeIterator implements Iterator<Coffee>{ int count = size; @Override public boolean hasNext() { return count > 0; } @Override public Coffee next() { count --; return CoffeeGenerator.this.next(); } @Override public void remove() { throw new UnsupportedOperationException(); } } public Iterator<Coffee> iterator(){ return new CoffeeIterator(); } public static void main(String[] args){ CoffeeGenerator gen = new CoffeeGenerator(); for(int i = 0; i < 5; i++) System.out.println(gen.next()); for(Coffee c : new CoffeeGenerator(5)) System.out.println(c); }}
注意:基本类型无法作为类型参数,不过Java SE5具备了自动打包和自动拆包的功能,可以很方便地在基本类型和其相应的包装器类型之间进行转换。
- 15.4 泛型方法
是否拥有泛型方法,与其所在的类是否是泛型没有关系,可以是泛型类,也可以不是泛型类。
如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法,因为它可以使事情更加清楚明白。另外,对于一个static的方法而言,无法访问泛型类的类型参数。
public <T> void f(T x){ System.out.println(x.getClass().getName());}
调用:
gm.f("");gm.f(1);gm.f(1.0);...
使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型。这称为类型参数判断。这就好像是f()被无限次的重载过。如果f()调用时传入了基本类型,那么自动打包机制就会接入其中,将基本类型的值包装为对应的对象。
- 15.4.3 用于Generator的泛型方法
public class Generators { public static <T> Collection<T> fill(Collection<T> coll, Generator<T> generator, int n){ for(int i =0; i < n; i++) coll.add(generator.next()); return coll; }}//调用fill(new ArrayList<Coffee>(), new CoffeeGenerator(), 4);
- 15.5 匿名内部类
public static Generator<Teller> generator = new Generator<Teller>(){ public Teller next(){ return new Teller(); } };
- 15.7 擦除的神秘之处
Class c1 = new ArrayList<String>().getClass();Class c2 = new ArrayList<Integer>().getClass();System.out.println(c1==c2);
输出为true。
Java泛型是使用擦除来实现的,在泛型代码内部,无法获得任何有关泛型参数类型的信息。因此List和List在运行时事实上是相同的类型。
因为泛型内部是没有类型信息的,所以要调用t.f()方法时,也是不可以的,解决方法是给定泛型类的边界:,如此,就可以调用t.f().
public Test<T extends HasF>{ private T t; ... public void test(){ t.f(); }}
编译器会把类型参数替换为它的擦除,上述的例子T擦除到了HasF.
如果自己去执行擦除,那么前一个例子可以简单的创建出一个没有泛型的类:
public Test(){ private HasF t; ... public void test(){ t.f(); }}
但是泛型可以返回确切的类型,而用Object的话必须强转:
public T get() { return t; }
- 15.7.2 迁移兼容性
泛型类型只有在静态类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为它们的非泛型上届,例如,List这样的类型注解将被擦除为List,而普通的类型变量在未指定边界的情况下将被擦除为Object。
迁移兼容性:擦除的核心动机是它使得泛化的客户端可以用非泛化的类库来使用,反之亦然。
- 15.8 擦除的补偿
擦除丢失了在泛型代码中执行某些操作的能力,任何在运行时需要知道确切类型信息的操作都将无法工作:
T var = new T();//ErrorT[] arr = new T[size];//Errorif(arg instanceof T){}//ErrorT[] arr = (T)new Object[size];//Unchecked warning
- 15.8.1 创建类型实例
C++中可以直接创建,Java中的解决方法是传递一个工厂对象,并用它创建新的实例,最便利的工厂对象就是Class对象:
class ClassAsFactory<T>{ T x; public ClssAsFactory(Class<T> kind){ try{ x = kind.newInstance(); }catch(Exception e){ ... } }}...ClassAsFactory<Coffee> fe = new ClassAsFactory<>(Coffee.class);
但是如果传入的是Integer.class,可以编译但是最终却catch到了异常,因为Integer没有任何默认的构造器。因此Sun建议使用显示的工厂。
interface Factory<T>{ T create();}class Foo2<T>{ private T x; public <F extends Factory<T>> Foo2(F factory){ x = factory.create(); }}class IntegerFactory implements Factory<Integer>{ public Integer create(){ return new Integer(0); }}//调用new Foo2<Integer>(new IntegerFactory());
或者是模板方法设计模式,下面的示例中,create()就是在子类中定义的、用来产生子类类型的对象:
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(){ ... }}//调用Creator c = new Creator();c.f();
- 15.8.2 泛型数组
不能直接创建泛型数组,一般的解决方案是在任何想要创建泛型数组的地方都使用ArrayList:
private List<T> array = new ArrayList<T>();public void add(T item) {}public T get() {}
既然所有数组无论它们持有的类型如何,都具有相同的结构(每个数组槽位的尺寸和数组的布局),那么看起来应该能创建一个Object数组,并将其转型为所希望的数组类型,事实上这可以编译,但是不能运行:
public class ArrayOfGeneric { static final int SIZE = 100; static Generic<Integer>[] gia; static Generic[] giaNormal; public static void main(String[] args){ //java.lang.Object; cannot be cast to [Lcom.whu.fly.Chapter15.GenericInterface.Generic;// gia = (Generic<Integer>[]) new Object[SIZE]; //Error:创建泛型数组// gia = new Generic<Integer>[SIZE]; gia = new Generic[SIZE]; System.out.println(gia.getClass().getSimpleName()); giaNormal = new Generic[SIZE]; giaNormal[0] = new Generic<Double>(); gia[0] = new Generic<Integer>(); }}
数组将跟踪它们的实际类型,而这个类型是在数组被创建的时候确定的,因此,即使gia已经被转型为Generic[],但这个信息只存在于编译器,在运行时,它仍旧是Object数组。
gia = (Generic[]) new Object[SIZE]将报java.lang.Object; cannot be cast to [Lcom.whu.fly.Chapter15.GenericInterface.Generic的错误。而gia = new Generic[SIZE]将报Error:创建泛型数组的错误。
第一个错误原因显而易见,第二个错误原因==我觉得==是无法建立泛型数组,所以用了通用的Generic[]类型,但是gia中并不能放其他类型的,gia[1] = new Generic()会报错无法将Double插入到Integer中。
成功创建泛型数组的唯一方式就是创建一个被擦除类型的新数组,然后对其转型:
public class GenericArray<T> { private T[] array; public GenericArray(int size){ array = (T[]) new Object[size]; } public void put(int index, T item){ array[index] = item; } public T get(int index){ return array[index]; } public T[] rep(){ return array; } public static void main(String[] args){ GenericArray<Integer> gai = new GenericArray<>(10);// java.lang.Object; cannot be cast to [Ljava.lang.Integer// Integer[] ia = gai.rep(); Object[] oa = gai.rep(); }}
rep()返回的是T[],但是尝试左右Integer[]引用来捕获,依然会报错,这是因为实际的运行时类型是Object[]。
因为有了擦除,数组的运行时类型就只能是Object[],如果我们立即将其转型为T[],那么在编译器该数组的实际类型就将丢失,而编译器可能会错误某些潜在的错误检查。正因为这样,最好是在集合内部使用Object[]:
public class GenericArray2<T> { private Object[] array; public GenericArray2(int size){ array = new Object[size]; } public void put(int index, T item){ array[index] = item; } public T get(int index){ return (T)array[index]; } public T[] rep(){ return (T[])array; } public static void main(String[] args){ GenericArray2<Integer> gai = new GenericArray2<>(10); for(int i = 0; i < 10; i ++) gai.put(i, i); for(int i = 0; i < 10; i++) System.out.print(gai.get(i) + " "); System.out.println(); //java.lang.Object; cannot be cast to [Ljava.lang.Integer// Integer[] ia = gai.rep(); }}
依然不能将gai.rep()转为Integer[],它只能是Object[]。
如果确实需要具体的类型,可以传递一个类型标记:
public class GenericArrayWithTypeToken<T> { private T[] array; public GenericArrayWithTypeToken(Class<T> type, int size){ array = (T[]) Array.newInstance(type, size); } public void put(int index, T item){ array[index] = item; } public T get(int index){ return array[index]; } public T[] rep(){ return array; } public static void main(String[] args){ GenericArrayWithTypeToken<Integer> gai = new GenericArrayWithTypeToken<>(Integer.class, 10); Integer[] ia = gai.rep(); }}
类型标记Class被传到构造器中,以便从擦除中回复,使得我们可以创建需要的实际类型的数组。
- 15.9 边界
如15.7所讲,边界使得你可以在用于泛型的参数类型上设置限制条件,可以按照自己的边界类型来调用方法。
public class ColoredDimension<T extends FruitClass & Inter1 & Inter2> {}
边界可以设置多个,但是class必须在前面,然后是接口(接口可以是多个)。
- 15.10 通配符
数组可以由一个父类的引用来持有子类的数组,例如:
Fruit[] fruits = new Apple[10];fruits[0] = new Apple();//可以编译,但是不能运行fruits[1] = new Fruit();
因为它有一个Fruit[]的引用,所以它没有理由不允许将Fruit对象或者任何其子类加入其中,因此,在编译器这是允许的,但是它的实际类型是Apple[],在运行时,数组机制知道它处理的是Apple[],因此会抛出异常。
在使用泛型容器时,这种“向上转型”就不允许了:
//编译期报错List<Fruit> fruitList = new ArrayList<Apple>();
这实际上根本不是向上转型,Apple的List不是Fruit的List。
真正的问题是我们在谈论容器的类型,而不是容器持有的类型。与数组不同,泛型没有内建的协变类型(????)。这是因为数组在语言中是完全定义的,因此可以内建了编译期和运行时的检查,但是在使用泛型时,编译期和运行时系统都不知道你想用类型做些什么,以及应采用什么样的规则。
有时你想要在两个类型之间建立某种类型的向上转型关系,这正是通配符所允许的:
List<? extends Fruit> fList = new ArrayList<Apple>();
但是此时就丢失了向其中传递任何对象的能力,甚至是Object:
fList.add(new Apple());//ErrorfList.add(new Fruit());//ErrorfList.add(new Object());//ErrorfList.add(null);//可以,但是没有意义
List
public class Holder<T>{ private T value; public Holder(){ } public Holder(T val){ value = val; } public void set(T val){ value = val; } public T get(){ return value; }}Holder<? extends Fruit> fruit = new Holder<Apple>(new Apple());
创建了一个Holder,可以将其向上转型为Holder
static void writeTo(List<? super Apple> apples){ apples.add(new Apple()); apples.add(new Jonathan()); apples.add(new Fruit());//Error}
- 15.10.3 无界通配符
无界通配符
ArrayList fList = new ArrayList<Fruit>();fList.add(new Apple("an apple"));fList.add(new Fruit());fList.add(new Object());//fList.add(null);for(Object f : fList){ System.out.println(f.getClass().getSimpleName());}Apple apple = (Apple) fList.get(0);System.out.println(apple.getAppleName());
完全可以正常运行,可以添加,可以强制类型转换后(get()返回的是Object)调用它的方法。
ArrayList<Fruit> fList = new ArrayList();
可以是任何,限制跟普通的new ArrayList一样。
public class Wildcards { static void rawArgs(Holder holder, Object arg){// holder.set(arg);//unchecked warning// holder.set(new Wildcards());//same warning //OK, 但是类型信息丢失了 Object obj = holder.get(); } static void unboundedArg(Holder<?> holder, Object arg){// holder.set(arg);//Error.Capture of ? cannot be applied to objec// holder.set(new Wildcards());//same error //OK, 但是类型信息丢失了 Object object = holder.get(); } static <T> T exact1(Holder<T> holder){ T t = holder.get(); return t; } static <T> T exact2(Holder<T> holder, T arg){ holder.set(arg); T t = holder.get(); return t; } static <T> T wildSubtype(Holder<? extends T> holder, T arg){// holder.set(arg);//Error capture of ? extends T cannot be applied to T T t = holder.get(); return t; } static <T> void wildSupertype(Holder<? super T> holder, T arg){ holder.set(arg);// T t= holder.get();//? super T 不是T Object obj = holder.get(); } public static void main(String[] args){ Holder raw = new Holder<Long>(); //Or raw = new Holder(); Holder<Long> qualified = new Holder<>(); Holder<?> unbounded = new Holder<Long>(); Holder<? extends Long> bounded = new Holder<Long>(); Long lng = 1L; rawArgs(raw, lng); rawArgs(qualified, lng); rawArgs(unbounded, lng); rawArgs(bounded, lng); unboundedArg(raw, lng); unboundedArg(qualified, lng); unboundedArg(unbounded, lng); unboundedArg(bounded, lng); Object ri = exact1(raw);//unchecked warning Long r2 = exact1(qualified); Object r3 = exact1(unbounded);//Must return Object Long r4 = exact1(bounded); Long r5 = exact2(raw, lng);//unchecked warning,from Holder to Holder<Long> Long r6 = exact2(qualified, lng);// Long r7 = exact2(unbounded, lng);//Error, (Holder<T>, T) cannot be applied to (Holder<capture of ?>, Long)// Long r8 = exact2(bounded, lng);//Error, (Holder<T>, T) cannot be applied to (Holder<capture of ? extends Long>, Long) Long r9 = wildSubtype(raw, lng);//unchecked warning Long r10 = wildSubtype(qualified, lng); //OK, 但是只能返回Object Object r11 = wildSubtype(unbounded, lng); Long r12 = wildSubtype(bounded, lng); wildSubtype(raw, lng);//unchecked warning,from Holder to Holder<Long> wildSupertype(qualified, lng);// wildSupertype(unbounded, lng);//Error:(Holder<? super T>, T>) cannot be applied to (Holder<capture of ?>, Long);// wildSupertype(bounded, lng);//Error:(Holder<? super T>, T>) cannot be applied to (Holder<capture of ? extends Long>, Long); }}
上面的示例中包含了各种Holder作为参数的用法,它们都具有不同的形式,包括原生类型,具体的类型参数以及无界通配符参数。
总结如下:
1. 只要使用了原生类型,都会放弃编译期的检查,在rawArgs()中,可以将任何类型的对象传递给set(),这个对象会被向上转型为Object。
2. 在unboundedArg()中可以看出
public class CaptureConversion { static <T> void f1(Holder<T> holder){ T t = holder.get(); System.out.println(t.getClass().getSimpleName()); } static void f2(Holder<?> holder){ f1(holder); } public static void main(String[] args){ Holder raw = new Holder<Integer>(1); f1(raw);//有警告 f2(raw);//无警告 Holder rawBasic = new Holder(); rawBasic.set(new Object());//警告 f2(rawBasic);//无警告 Holder<?> wildcard = new Holder<>(1.0); f2(wildcard); }}output//IntegerIntegerObjectDouble
最后一个是根据(1.0)判断的,如果换成了1.0f,那么就会输出Float。
此处,f1()中的类型参数都是确切的,没有通配符或者边界。在f2()中,Holder参数是一个无界通配符,因此它看起来是未知的。但是,在f2()中,f1()被调用,而f1()需要一个已知参数。这里所发生的的是:参数类型在调用f2()的过程中被捕获。
- 15.11.2 实现参数化接口
一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口:
interface Payable<t>{}class Employee implements Payable<Employee> {}//不能编译class Hourly extends Employee implements Payable<Hourly> {}
如果从Payable的两种用法中都移除掉泛型参数,这段代码就可以编译。
- 15.11.4 重载
public class UseList<W,T>{ void f(List<T> t){} void f(List<W> w){}}
由于擦除的原因,重载方法将产生相同的类型签名,因此必须提供明显有区别的方法名,f1(),f2()。
- 15.12 自限定的类型
class SelfBounded<T extends SelfBounded<T>>{}
基类用导出类替代其参数,这意味着泛型基类变成了一种其所有导出类的公共的模板。
class A extends SelfBounded<A> {}class B extends SelfBounded<A> {}//也行class C extends SelfBounded<B> {}//Error,B不满足T extends SelfBound<T>(T一致)
自限定的参数的意义:它可以保证类型参数必须与正在被定义的类相同,即这个类所用的类型参数将与使用这个参数的类具有相同的基类型。
- JAVA中泛型
- java中泛型
- java中泛型
- java中泛型
- Java 中泛型
- java中泛型
- Java中泛型
- java中泛型
- java 中泛型
- Java中泛型 使用
- java中泛型方法
- java中泛型的使用
- Java中泛型的使用
- Java中泛型小结
- java中泛型小结
- JAVA中泛型的本质
- java中泛型的作用
- java中泛型的应用
- 第三讲 Groovy闭包和对象
- 24 点游戏 c语言
- 程序员励志故事①
- js 生成二维码
- Laravel
- Java中泛型
- 版本控制的使用方法。
- C#教程
- Socket编程,自己电脑做服务端和客户端
- 数据库
- 九度OJ题目1202:排序
- z-index最大值,最小值,在不同浏览器的取值
- Python3:urllib.request详解
- 管道命令