带你入门Java的泛型
目录
- 泛型
- 1、简单泛型
- (1)元组
- (2)堆栈
- 2、泛型接口
- 3、泛型方法
- (1)类型推断
- (2)通用的Generator
- (3)Set实用工具实现数学方法
- 4、擦除
- (1)迁移兼容性
- (2)擦除的问题
- 5、擦除的补偿
- (1)由于擦除原因,无法通过instanceof比较类型。如果引入类型标签,就可以转而使用动态的isInstance()。
- (2)创建类型实例
- (3)泛型数组
- 6、边界
- 7、通配符
- (1)List<? extends Fruit>协变
- (2)List<? super Fruit>逆变
- (3)无界通配符List<?>
- (4)捕获转换
- 8、问题
- (1)任何基本类型都不能作为类型参数
- (2)实现参数化接口
- (3)转型和警告
- (4)重载
- (5)基类劫持了接口
- 9、自限定
- 10、异常
- 总结
泛型
1、简单泛型
泛型的主要目的之一就是用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性。
泛型暂时不指定类型,在使用时决定具体使用什么类型。通过<T>来实现,T就是类型参数。
(1)元组
class TwoTuple<A,B>{ public final A first; public final B second; public TwoTuple(A a,B b){ first = a; second = b; } @Override public String toString() { return "{ " + first + ", " + second + '}'; } }
(2)堆栈
class LinkedStack<T>{ private class Node { T item; Node next; Node() { item = null; next = null; } Node(T item, Node next) { this.item = item; this.next = next; } boolean end() { return item == null && next == null; } } private Node top = new Node(); public void push(T item) { top = new Node(item, top); } public T pop() { T result = top.item; if(!top.end()) top = top.next; return result; } } (3)RandomList class RandomList<T>{ private ArrayList<T> storage = new ArrayList<>(); private Random rand = new Random(47); public void add(T item){ storage.add(item); } public T select(){ return storage.get(rand.nextInt(storage.size())); } }
2、泛型接口
泛型也可以应用于接口,例如生成器,这是一种专门负责创建对象的类。
import net.mindview.util.Generator; import java.util.Iterator; class Fibonacci implements Generator<Integer> { private int count = 0; public Integer next(){ return fib(count++); } private int fib(int n){ if(n<2) return 1; return fib(n-2) + fib(n-1); } } class IterableFibonacci implements Iterable<Integer> { private Fibonacci fib = new Fibonacci(); private int n; public IterableFibonacci(int count){ n = count; } @Override public Iterator<Integer> iterator() { return new Iterator<Integer>() { @Override public boolean hasNext() { return n>0; } @Override public Integer next() { n--; return fib.next(); } public void remove() { // Not implemented throw new UnsupportedOperationException(); } }; } }
3、泛型方法
泛型方法使得该方法能够独立于类而产生变化。使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型,这称为类型参数推断。
class GenericMethods{ public <T> void f(T x){ System.out.println(x.getClass().getSimpleName()); } }
(1)类型推断
使用泛型有时候需要向程序中加入更多的代码。如下所示:
Map<Person,List<? extends Pet>> petPerson = new HashMap<Person,List<? extends Pet>>();
在泛型方法中可以通过类型推断来简化一部分工作。如下所示:
class New{ public static <K,V> Map<K,V> map(){ return new HashMap<K,V>(); } public static void main(String[] args) { Map<Person,List<? extends Pet>> petPerson = New.map(); } }
类型推断只对赋值操作有效,其他时候并不起作用。如果将一个泛型方法的结果作为参数,传递给另一个方法时,另一个方法需要显式的类型说明。如下所示:
public class ExplicitTypeSpecification{ static void f(Map<Person,List<? extends Pet>> petPerson){} public static void main(String[] args) { f(New.<Person,List<? extends Pet>>map()); } }
(2)通用的Generator
import net.mindview.util.Generator; public class BasicGenerator<T> implements Generator<T>{ private Class<T> type; public BasicGenerator(Class<T> type){ this.type = type; } public T next(){ try { return type.newInstance(); }catch (Exception e){ throw new RuntimeException(e); } } public static <T> Generator<T> create(Class<T> type){ return new BasicGenerator<T>(type); } }
(3)Set实用工具实现数学方法
public class Sets{ @SuppressWarnings("unchecked") protected static <T> Set<T> copy(Set<T> s) { if(s instanceof EnumSet) return ((EnumSet)s).clone(); return new HashSet<T>(s); } //并集 public static <T> Set<T> union(Set<T> a, Set<T> b) { Set<T> result = copy(a); result.addAll(b); return result; } //交集 public static <T> Set<T> intersection(Set<T> a, Set<T> b) { Set<T> result = copy(a); result.retainAll(b); return result; } //差集 public static <T> Set<T> difference(Set<T> superset, Set<T> subset) { Set<T> result = copy(superset); result.removeAll(subset); return result; } //包含除了交集以外的所有元素 public static <T> Set<T> complement(Set<T> a, Set<T> b) { return difference(union(a, b), intersection(a, b)); } }
4、擦除
Java泛型是使用擦除来实现的,这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此List<String>和List<Integer>在运行时事实上是相同的类型,都被擦除成它们的“原生”类型List。
(1)迁移兼容性
泛型类型只有在静态类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为他们的非泛型上界。擦除的核心动机是它使得泛化的客户端可以用非泛化的类库来使用,反之亦然,这经常被称为“迁移兼容性”。
(2)擦除的问题
泛型的所有关于参数的类型信息都丢失了,所以不能用于显式地引用运行时类型的操作之中,例如转型、instanceof操作和new表达式。
5、擦除的补偿
(1)由于擦除原因,无法通过instanceof比较类型。如果引入类型标签,就可以转而使用动态的isInstance()。
public class ClassTypeCapture<T>{ Class<T> kind; public ClassTypeCapture(Class<T> kind){ this.kind = kind; } public boolean f(Object arg){ return kind.isInstance(arg); } }
(2)创建类型实例
通过工厂对象来创建实例。如果使用类型标签,就可以使用newInstance()来创建这个类型的新对象。
class ClassAsFactory<T>{ T x; public ClassAsFactory(Class<T> kind){ try{ x = kind.newInstance(); }catch(Exception e){ throw new RuntimeException(e); } } }
如果类没有默认的构造器,上面的案例会创建失败。为了解决这个问题,可以通过显示的工厂来实现。
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(){ return new Integer(6); } }
另一种方式是模板方法设计模式。
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(); } }
(3)泛型数组
无法通过 T[] array = new T[sz] 来创建泛型数组,一般的解决方法是在需要泛型数组的地方都使用ArrayList。
在创建泛型数组时,有以下三种情况:
①创建时强制转型
public class GenericArray<T>{ private T[] array; @SuppressWarnings("unchecked") public GenericArray(int sz){ array = (T[])new Object[sz]; } public T[] rep(){ return array; } public static void main(String[] args) { GenericArray<Integer> gai = new GenericArray<Integer>(10); Integer[] ia = gai.rep();//引起ClassCastException Object[] oa = gai.rep(); } }
②方法返回时强制转型
class GenericArray2<T>{ private Object[] array; @SuppressWarnings("unchecked") public GenericArray(int sz){ array = new Object[sz]; } public T[] rep(){ return (T[])array; } public static void main(String[] args) { GenericArray<Integer> gai = new GenericArray<Integer>(10); Integer[] ia = gai.rep();//引起ClassCastException Object[] oa = gai.rep(); } }
③使用Array.newInstance()
以上两种方法都无法创建具体类型的数组,无法推翻底层的数组类型,只能是Object[]。通过传入类型标记Class<T>,可以从擦除中恢复。
class GenericArray3<T>{ private T[] array; @SuppressWarnings("unchecked") public GenericArray(Class<T> type,int sz){ array = (T[]) Array.newInstance(type,sz); } public T[] rep(){ return array; } public static void main(String[] args) { GenericArray<Integer> gai = new GenericArray<Integer>(Integer.class,10); Integer[] ia = gai.rep();//可以正常运行 Object[] oa = gai.rep(); } }
6、边界
边界使得你可以在用于泛型的参数类型上设置限制条件,可以按照自己的边界类型来调用方法。
public class Test { public static void main(String[] args) { Man m = new Man(); m.hear(); m.smell(); } } interface SuperPower{} interface SuperHearing extends SuperPower{ void hearSubtleNoises(); } interface SuperSmell extends SuperPower{ void trackBySmell(); } class SuperHero<POWER extends SuperPower>{ POWER power; SuperHero(POWER power){ this.power = power; } POWER getPower(){ return power; } } class CaineHero<POWER extends SuperHearing & SuperSmell> extends SuperHero<POWER>{ CaineHero(POWER power){ super(power); } void hear(){ power.hearSubtleNoises(); } void smell(){ power.trackBySmell(); } } class SuperHearSmell implements SuperHearing,SuperSmell{ @Override public void hearSubtleNoises() { System.out.println("hearSubtleNoises"); } @Override public void trackBySmell() { System.out.println("trackBySmell"); } } class Man extends CaineHero<SuperHearSmell>{ Man(){ super(new SuperHearSmell()); } }
7、通配符
(1)List<? extends Fruit>协变
表示具有任何从Fruit继承的类型的列表。List<? extends Fruit>可以合法地指向一个List<Apple>。一旦执行这种类型的向上转型,就将丢失掉向其中传递任何对象的能力,甚至是传递Object也不行。
List<? extends Fruit> flist = Arrays.asList(new Apple()); //Compile Error:can't add any type of object //add()的参数是<? extends Fruit>,编译器不知道需要Fruit的哪个 //具体的子类型,因此不接受任何类型的Fruit //flist.add(new Apple()); //flist.add(new Fruit()); //flist.add(new Object()); flist.add(null);//Legal but uninteresting Apple a = (Apple)flist.get(0);//No warning Fruit f = flist.get(0);//No warning flist.contains(new Apple());//参数是Object flist.indexOf(new Apple());//参数是Object
(2)List<? super Fruit>逆变
超类型通配符可以安全地传递一个类型对象到泛型类型中。List<? super Fruit>意味着向其中添加Fruit或Fruit的子类型是安全的。
List<? super Fruit> flist = new ArrayList<Fruit>(); flist.add(new Apple()); flist.add(new Fruit()); //Error:Incompatible Type //Fruit f = flist.get(0); Object f = flist.get(0);//OK,but type information has been lost
(3)无界通配符List<?>
List实际上表示“持有任何Object类型的原生List”,List<?>表示“具有某种特定类型的非原生List,只是我们不知道那种类型是什么”,List<? extends Object>表示“类型是Object的导出类”。
无界通配符的一个重要应用:处理多个泛型参数时,允许一个参数可以是任何类型,同时为其他参数确定某种特定类型。
Map<String,?> map = new HashMap<String,Integer>; map = new HashMap<String,String>;
原生Holder与Holder<?>是大致相同的事物,但存在不同。它们会揭示相同的问题,但是后者将这些问题作为错误而不是警告报告。
static void rawArgs(Holder holder,Object arg){ //holder.set(arg); //Warning:Unchecked call to set(T) as member //of the raw type Holder //holder.set(new Wildcards());//Same Warning //Can't do this:don't have any 'T' //T t = holder.get(); //OK,but type infomation has been lost Object obj = holder.get(); } //Similar to rawArgs(),but errors instead of warnings static void unboundedArg(Holder<?> holder,Object arg){ //holder.set(arg); //Error:set(capture of ?) in Holder<capture of ?> //cannot be applied to (Object) //holder.set(new Wildcards());//Same Error //Can't do this:don't have any 'T' //T t = holder.get(); //OK,but type infomation has been lost Object obj = holder.get(); }
(4)捕获转换
未指定的通配符类型被捕获,并被转换为确切类型。在f2()中调用f1(),参数类型在调用f2()的过程中被捕获,因此它可以在对f1()的调用中被使用。不能从f2()中返回T,因为T对于f2()来说是未知的。
static <T> void f1(Holder<T> holder){ T t = holder.get(); System.out.println(t.getClass().getSimpleName()); } static <T> void f2(Holder<T> holder){ f1(holder); }
8、问题
(1)任何基本类型都不能作为类型参数
(2)实现参数化接口
一个类不能实现同一个泛型接口的两种变体。将泛型参数移除掉后,这段代码就可以正常编译了。
interface Payable<T>{} class Employee implements Payable<Employee>{} //Compile Error:cannot be inherited with different type arguments class Hourly extends Employee implements Payable<Hourly>{}
(3)转型和警告
使用带有泛型类型参数的转型或instanceof不会有任何效果。
由于擦除原因,编译器无法知道这个转型是否安全,并且pop()方法实际上并没有执行任何转型。如果没有@SuppressWarnings注解,编译器将对pop()产生“Unchecked cast”警告。
private int index = 0; private Object[] storage; @SuppressWarnings("unchecked") public T pop(){ return (T)storage[--index]; }
(4)重载
由于擦除的原因,重载方法将产生相同的类型签名,导致程序不能编译。
public class UseList<W,T>{ void f(List<T> v){} void f(List<W> v){} }
(5)基类劫持了接口
一旦为Comparable确定了ComparablePet参数,那么其他任何实现类都不能与ComparablePet之外的任何对象比较。在前面的“实现参数化接口”章节里面的第一个例子,就体现了基类劫持接口。
public class ComparablePet implements Comparable<ComparablePet> { public int compareTo(ComparablePet arg) { return 0; } } class Cat extends ComparablePet implements Comparable<Cat>{ // Error: Comparable cannot be inherited with // different arguments: <Cat> and <Pet> public int compareTo(Cat arg) { return 0; } } ///:~ class Hamster extends ComparablePet implements Comparable<ComparablePet>{ public int compareTo(ComparablePet arg) { return 0; } }
9、自限定
class Subtype extends BasicHolder<Subtype> {}这样用,就构成自限定了。从定义上来说,它继承的父类的类型参数是它自己。
从使用上来说,Subtype对象本身的类型是Subtype,且Subtype对象继承而来的成员(element)、方法的形参(set方法)、方法的返回值(get方法)也是Subtype了(这就是自限定的重要作用)。这样Subtype对象就只允许和Subtype对象(而不是别的类型的对象)交互了。
class BasicHolder<T> { T element; void set(T arg) { element = arg; } T get() { return element; } void f() { System.out.println(element.getClass().getSimpleName()); } } class Subtype extends BasicHolder<Subtype> {} public class CRGWithBasicHolder { public static void main(String[] args) { Subtype st1 = new Subtype(), st2 = new Subtype(), st3 = new Subtype(); st1.set(st2); st2.set(st3); Subtype st4 = st1.get().get(); st1.f(); } } /* Output: Subtype */
10、异常
由于擦除原因,将泛型应用于异常是非常受限的。但是,类型参数可能会在一个方法的throws子句中用到,这使得你可以编写随检查型异常的类型而发生变化的泛型代码。
interface Processor<T,E extends Exception> { void process(List<T> resultCollector) throws E; } class ProcessRunner<T,E extends Exception> extends ArrayList<Processor<T,E>> { List<T> processAll() throws E { List<T> resultCollector = new ArrayList<T>(); for(Processor<T,E> processor : this) processor.process(resultCollector); return resultCollector; } } class Failure extends Exception {} class Processor1 implements Processor<String,Failure> { static int count = 3; public void process(List<String> resultCollector) throws Failure1_1, Failure1_2 { if(count-- > 1) resultCollector.add("Hep!"); else resultCollector.add("Ho!"); if(count < 0) throw new Failure1(); } } public class Test { public static void main(String[] args) { ProcessRunner<String,Failure> runner = new ProcessRunner<String,Failure>(); for(int i = 0; i < 3; i++) runner.add(new Processor1()); try { System.out.println(runner.processAll()); } catch(Failure e) { System.out.println(e); } } }
总结
本篇文章就到这里了,希望能给您带来帮助,也希望您能够多多关注我们的更多内容!