JAVA泛型

来源:互联网 发布:北京seo排名 编辑:程序博客网 时间:2024/06/05 09:24

java泛型应用是java核心基础之一,从java 5开始引进泛型。如果你曾经使用过java Collection,那你已经算是接触过泛型了。在java Collection里使用泛型是一件很简单的事情,可泛型还具有很多你意想不到的作用。在深入了解泛型之前,首先来了解一下泛型的一些基本概念与原理。
一、java 泛型引入
java泛型的应用可以提高的代码的复用性,同时泛型提供了类型检查,减少了数据的类型转换,同时保证了类型安全。下面看一下,泛型如何保证了类型安全:

List list = new ArrayList();list.add("abc");list.add(new Integer(1));   //可以通过编译for (Object object : list) {    System.out.println((String)object);//抛出ClassCastException异常}
     //上面的代码会在运行时抛出ClassCastException,因为它尝试将一个Integer转换为String。接着,来看一下从java5开始,     //Collection的用法:List<String> list = new ArrayList<>();list.add("abc");//list.add(new Integer(1)); //编译错误for (String string : list) {    System.out.println(string);//无需任何强制类型转换}
注意到,List的创建增加了类型参数String,因此只能向list添加String类型对象,添加其他对象会抛出编译异常;同样可以注意到,foreach循环不需要再添加任何强制类型转换,也就移除了运行时的ClassCastException异常。

二、泛型的类与接口
既然是学泛型,自然就要知道如何去使用泛型定义自己的类和接口。同时为了加深理解泛型的作用,先引进一个原始的类:

public class Gen {    private Object obj;    public Object getObj() {        return obj;    }    public void setObj(Object obj) {        this.obj = obj;    }    public static void main(String[] args) {        Gen gen = new Gen();        gen.setObj("abc");        String str = (String) gen.getObj();        //类型转换,可能会引起运行时ClassCastException    }}
原始类的定义,容易引发ClassCastException,和第一大点谈到的类似。现在来看一下泛型类来重新定义Gen — 使用<>指定泛型参数,如下:
public class Gen<T> {    T obj;    public T getObj() {        return obj;    }    public void setObj(T obj) {        this.obj = obj;    }    public static void main(String[] args) {        Gen<String> gen = new Gen<>();        gen.setObj("abc");//      gen.setObj(10);     //无法通过编译        String str = gen.getObj();  //无需类型转换        //-----------------------------        Gen gen2 = new Gen();//raw type原始类型        gen2.setObj("abc");        gen2.setObj(10);    //可以通过编译,自动装箱将10转化为Integer对象        Integer num = (Integer) gen2.getObj();//使用了强制类型转换    }}
细心的你会发现在main()方法里是使用泛型类型Gen<String>,便不再需要强制类型转换,也就移除了运行时的ClassCastException。同时为了区别,在此也定义了一个没有使用泛型类型的gen2,这时,编译器会弹出一个警告“Gen is a raw type,References to generic type Gen<T> should be parameterized”。当我们不提供泛型类型时,会默认使用Object会代替,也是因此这样,gen2可以设置String和Integer类型,不过,我们应尽量去避免这种这种情况的出现,如此,便又需要用到强制类型转换,也伴随着运行时的ClassCastException异常。tips:可以使用@SuppressWarnings("rawtypes")来抑制编译器弹出警告。接口的泛型应用和类的泛型应用很类似,如下:
public interface List <E> {     void add(E x);     Iterator<E> iterator();}public interface Iterator<E> {     E next();     boolean hasNext();}
类似的,可以将此应用到自定义的接口与类当中。另外再提一下的是,可以使用多个泛型参数来定义接口与类,比如Map<K,V>;同时,泛型类型也可以作为一个参数来用,如下:new HashMap<String, List<String>>()。

三、泛型的命名规范
为了更好地去理解泛型,我们也需要去理解java泛型的命名规范。为了与java关键字区别开来,java泛型参数只是使用一个大写字母来定义。各种常用泛型参数的意义如下:

E — Element,常用在java Collection里,如:List,Iterator,Set
K,V — Key,Value,代表Map的键值对
N — Number,数字
T — Type,类型,如String,Integer等等

四、泛型的方法与构造函数
有时候我们并不希望整个类都被泛型化,这时可以只在某个方法上应用泛型。因为构造函数是一种特殊的方法,因此也可以在构造函数上应用泛型。Demo GenMethod演示了如何在方法上应用泛型和调用泛型方法,

public class GenMethod {    public static <T> void fromArrayToCollection(T[] a,Collection<T> c){        for (T t : a) {          c.add(t);        }    }    public static void main(String[] args) {        Object[] oa = new Object[100];        Collection<Object> co = new ArrayList<>();        GenMethod.<Object>fromArrayToCollection(oa, co);    }       }
GenMethod 的代码不多,不过需要注意的地方却不少。第一、定义方法所用的泛型参数需要在修饰符之后添加,如上面的,public static <T>,如果有多个泛型参数,可如此定义<K,V>或者<T1,T2>。第二,不建议在泛型变量里添加其他类型,如下面的代码,将会引起编译错误(或隐含错误),如下:
public static <T> void fromArrayToCollection(T[] a,Collection<T> c){        for (T t : a) {            c.add(t);            c.add(new Object());            }    }

第三、看一下泛型方法的调用GenMethod.<Object >fromArrayToCollection(oa, co); 在方法前声明了泛型类型Object。不过因为编译器可以推断这个泛型类型,因此也可以这样写:
GenMethod.fromArrayToCollection(oa, co)。
为了加深对编译器推断泛型类型的了解,再看一下如下几个推断:

String[] sa = new String[100];Collection<String> cs = new ArrayList<String>();// T 推断为StringfromArrayToCollection(sa, cs);// T 推断为ObjectfromArrayToCollection(sa, co);Integer[] ia = new Integer[100];Float[] fa = new Float[100];Number[] na = new Number[100];Collection<Number> cn = new ArrayList<Number>();//T 推断为NumberfromArrayToCollection(ia, cn);//T 推断为NumberfromArrayToCollection(fa, cn);//T 推断为NumberfromArrayToCollection(na, cn);//T 推断为ObjectfromArrayToCollection(na, co);//编译错误,Number与String不能兼容fromArrayToCollection(na, cs);

四、泛型参数的界限
有时候,你会希望泛型类型只能是某一部分类型,比如操作数据的时候,你会希望是Number或其子类类型。这个想法其实就是给泛型参数添加一个界限。其定义形式为:

 <T extends BoundingType>
此定义表示T应该是BoundingType的子类型(subtype)。T和BoundingType可以是类,也可以是接口。另外注意的是,此处的”extends“表示的子类型,不等同于继承。Demo:
public class Box<T> {    private T t;    public void set(T t) {        this.t = t;    }    public T get() {        return t;    }    public <U extends Number> void inspect(U u) {        System.out.println("T: " + t.getClass().getName());        System.out.println("U: " + u.getClass().getName());    }    public static void main(String[] args) {        Box<String> integerBox = new Box<>();        integerBox.set("abc");  //能通过编译,因为T指定为String类型//      integerBox.inspect("abc");//不能通过编译,因为U必须是Number类型或其子类        integerBox.inspect(new Integer(10));    }}
通过`Box<T>`,了解了如何为泛型参数添加一个界限。可问题也来了,既然限定了泛型参数的界限,那时候可以调用BoundingType(上指Number)的相对应的方法呢??答案是肯定的,如下:
public class NumberTest<T extends Integer> {    private T num;    public NumberTest(T num) { this.num = num;}    public boolean isOdd(){        return num.intValue()%2 == 1;    }    //....}
接着引入下一个问题,如何为泛型参数添加多个限制范围,多重限制范围格式如下:
 <T extends A & B & C>
一个泛型参数可以有多重限制范围,使用“&”分隔。且限制范围中之多有一个类。如果用一个类作为限定,它必须是限定列表中的第一个。举例如下:
Class A { /* ... */ }interface B { /* ... */ }interface C { /* ... */ }class D <T extends A & B & C> { /* ... */ }
如果BoundingType不是放在第一位,会产生编译异常:
class D <T extends B & A & C> { /* ... */ }  // 无法通过编译

五、泛型方法与泛参界限的综合
如果说泛型方法是一个有用的工具,那泛参的界限就应该这个工具的灵魂,为这个工具添加了一些“行为准则”。如下:设计一个方法,统计在一个数组里比指定元素大的个数,

public static <T> int countGreater(T[] array,T elem) {        int count = 0;        for (T t : array) {            if (t > elem) {//编译错误                ++count;            }        }        return  count;    }
发现上面这个方法无法通过编译,为什么呢??因为操作符“>”只可以用在基本数据类型(byte,char,short,int,float,long,double,boolean),却不可以用来比较类对象之间的大小(除非实现了Comparable接口)。`想要解决这个矛盾,就需要为<T>添加一个界限Comparable<T>:`
public interface Comparable<T> {    public int compareTo(T o);}    更改后的代码如下:public static <T extends Comparable<T>> int countGreater(T[] array,T elem) {        int count = 0;        for (T t : array) {            if (t.compareTo(elem) > 0) {//无编译错误                ++count;            }        }        return  count;    }
  除了上述方式,也可以选择添加界限Comparator<T,T>,只不过此界限需要两个参数而已,Comparator的定义与使用以前已经谈过,这里不再累述

六、泛型、继承与子类型
如果两个类之间相互兼容(继承与被继承),那么便可以将一个类对象赋值给另一个类对象,比如:你可以将一个String对象赋值给Object,String是Object的子类,

String someString = new String();Object someObject = new Object();someObject = someString;
如果你熟悉面向对象技术,会知道这是一种“is-a”关系。String是Object的一种对象,所以上面的赋值是可以的。同理,Integer、Double是Number的一类对象,下面的赋值也可以:
public void someMethod(Number n) { /* ... */ }someMethod(new Integer(10));   // OKsomeMethod(new Double(10.1);   // OK
这种“is-a”关系,同样也是用泛型。如果你将泛参设置Number,那么在随后的调用里,只需要传入一个数据对象就行了,如下:
Box<Number> box = new Box<>();box.add(new Integer(1));   box.add(new Double(1.0));
 现在,考虑一下下面这种方法:
public void someMethod(Box<Number> n) { /*.....*/}
   这个方法可以接受什么类型的参数呢??显然,这个方法接受Box<Number>类型的参数??那又是否可以接受Box<Integer>或者Box<Double>类型的参数的??答案是否定的,因为Box<Integer>与Box<Double>都不是Box<Number>的子类。在泛型编程里,这是一个容易混淆的概念,但又必须要懂的原理。如下图:

这里写图片描述

generics-subtypeRelationship    从图可以看到,即使IntegerNumber的子类,但Box<Integer>并不是Box<Number>的子类。Box<Integer>与Box<Number>的共同父类是Object。换言之,无论类A与类B是否存在关联,MyClass<A>与MyClass<B>都没有任何关联,其共同的父类的是Object。那是否说,泛型就不存在子类呢??这个留待解决,看完本文便可以知晓。七、泛型类与子类型    在谈这一小节时,先回顾一下泛型方法的“extends”含义,泛型的“extends”与继承的“extends”并不一样,泛型的“extends”其后可以是一个类(如T extends Number),同样也可以是一个接口(如T extends List<T>)。泛型的”extends“代表子类型,而不是子类,或许你可以把等其同于”extends(继承)“和”implement的并集。    在泛型里,也存在子类型,前提是其泛型参数的限制并没有改变,可以认为泛参没有改变,其实就是从原来的类或接口来判断泛型的子类型。为了形象理解,我们已collection类来作个例子,如:ArrayList<E> implement List<E>,而List<E> extends  Collection<E>,那么ArrayList<String>就是List<String>的子类型,而List<String>则是Collection<String>,其关系图如下:

这里写图片描述

generics-sampleHierarchy    深入一点来谈,现在假设需要定义自己的List接口 — PayLoadList,其定义如下:interface PayloadList<E,P> extends List<E> {  void setPayload(int index, P val);  //...}    如上,则下面的样例都是List<String>子类型,:PayloadList<String,String>PayloadList<String,Integer>PayloadList<String,Exception>

这里写图片描述

原文链接 http://peiquan.blog.51cto.com/7518552/1302898

0 0