Java 泛型

来源:互联网 发布:按键精灵抓取网页数据 编辑:程序博客网 时间:2024/06/05 04:13
基础

Java集合的缺点:将一个对象放进一个集合时,集合就会忘记这个对象的数据类型,当取出这个对象时,该对象的类型就会变成Object类型,对对象进行使用时要进行相应的类型转换。因此指定下面两种方法。
创建集合时指定类型参数:

public static void main(String[] args) {        //创建一个只能保存String类型的集合        List<String> list=new ArrayList<String>();        list.add("beautiful");        list.add("quickly");        list.forEach(str->System.out.println(((String)str).length()));    }

java 7泛型的菱形语法

public static void main(String[] args) {        //Java自动推断出ArrayList的<>里是String类型        List<String> list=new ArrayList<>();        list.add("beautiful");        list.add("Strongger");        list.forEach(ele->System.out.println(ele));        //java自动推断出HashMap的<>里是String,List<String>        Map<String,List<String>> map=new HashMap<>();    }

一、泛型简介

概念:
所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参在声明变量、创建对象、调用方法时动态指定(即传入实际的类型形参,也可传入类型实参)。

定义泛型接口、类
public interface List<E>    {        //接口中的类型E可以作为类型使用        void add(E x);        Iterator<E> iterator();    }    public interface Iterator<E>    {        //在接口里E完全可以作为类型使用        E next();    }    public interface Map<K,V>    {        //在接口里K,V完全可以作为类型使用        Set<K> keySet();        V put(K key,V value);    }

包含泛型声明的类型可以在定义变量、创建对象时传入一个类型实参,从而可以动态地生成无数多个逻辑上的子类,但这种子类在物理上并不存在

public class Apple<T>{    //使用T类型形参定义实例变量    private T info;    //使用类型形参T定义构造器    public Apple(T info)    {        this.info=info;    }    public T getInfo() {        return info;    }    public void setInfo(T info) {        this.info = info;    }    public static void main(String[] args) {        //由于传给T形参的是String,所以构造器参数指定是String        Apple<String> a1=new Apple<>("苹果");        System.out.println(a1.getInfo());        //传给T形参的类型是Double,所以构造器参数指定是Double        Apple<Double> a2=new Apple<>(5.66);        System.out.println(a2.getInfo());    }}
泛型类派生子类
//使用Apple类时为T形参传入String类型//如果使用T类型参数时,所有使用T类型形参的地方都将会被替换成String类型public class A extends Apple<String>
//使用Apple类时不传入实际的形参public class A extends Apple
public class A2 extends Apple{     public String getInfo()      {             //super.getInfo()方法返回值是Object类型            //必须使用toString()方法才返回String类型            return super.getInfo().toString();      }}
不存在泛型类

instanceof运算符不能使用泛型类,因为系统中不会存在正真的泛型类

List<String> list1=new ArrayList<>();List<Integer> list2=new ArrayList<>();//下面将返回True,不管泛型的实际参数是什么,运行时总有相同的classSystem.out.println(list1.getClass()==list2.getClass());
public class R<T>{    //下面语句错误,不能在静态变量声明中使用类型形参    static T info;    T age;    public void foo(T msg){}    //下面语句错误,不能在静态方法声明中使用类型形参    public stativ void bar(T msg){}}

二、类型通配符

如果Foo是Bar的一个子类型(子类或者接口),而G是具有泛型声明的类或接口,G并不是G的子类型,这一点非常注意!

数组和泛型有所不同,假设Foo是Bar的一个子类型(子类或接口),那么Foo[]依然是Bar[]的子类型。

使用类型通配符
//public void test(List c)//{//  for(int i=0;i<c.size();i++)//    {//      System.out.println(c.get(i))//   }//}//以上List集合中的元素类型是不确定,使用泛型是会引起泛型警告,修改如下public void test(List<?> c){    for(int i=0;i<c.size();i++)    {        System.out.println(c.get(i))    }}

上面程序使用的List

List<?> c=new ArrayList<String>();//下面程序引起编译错误c.add(new Object());
设定类型通配符的上限

将Canvas类修改成下面的形式,就可以把List对象当成List

//定义一个抽象类Shapepublic abstract class Shape{    public abstract void draw(Canvas c);}//定义Shape的子类Circlepublic class Circle extends Shape{    //实现画图方法    public void draw(Canvas c)    {        System.out.println("在画布"+c+"上画一个圆")    }}//定义Shape的子类Rectanglepublic class Rectangle extends Shape{    //实现画图方法    public void draw(Canvas c)    {        System.out.println("在画布"+c+"上画一个矩形")    }}public class Canvas{    //同时在画布上绘制多个形状,使用被限制的泛型通配符    public void drawAll(List<? extends Shape> shapes)    {        for(Shape s : shapes)        {            s.draw(this);        }    }}
设定类型形参的上限

设定类型形参的上限时,要么传给类型形参的类型是上限类型,要么是上限类型的子类

public class Apple<T extends Number>{    T col;    public static void main(String[] args)    {        Apple<Integer> a1=new Apple<>();        Apple<Double> a2=new Apple<>();        //下面代码将会引发编译异常,因为String不是Number的子类型        Apple<String> as=new Apple<>();    }}

程序需要为类型形参设定多个上限(至多有一个父类上限,可以有多个接口上限)

public class Apple<T extends Number & java.io.Serializable>{    .......}

三、泛型方法

定义泛型方法
public class GenericMethodTest {    //声明一个泛型方法,该方法中带一个T类型形参    static <T> void fromArrayToCollection(T[] a,Collection<T> c)    {        for(T o:a)        {            c.add(o);        }    }    public static void main(String[] args) {        Object[] oa=new Object[100];        Collection<Object> co=new ArrayList<>();        //下面代码中T代表Object类型        fromArrayToCollection(oa, co);        String[] sa=new String[100];        Collection<String> cs=new ArrayList<>();        //下面代码中T代表String类型        fromArrayToCollection(sa, cs);        //下面代码中T代表Object类型        fromArrayToCollection(sa, co);        Integer[] ia=new Integer[100];        Float[] fa=new Float[100];        Number[] na=new Number[100];        Collection<Number> cn=new ArrayList<>();        //下面代码中T代表Number类型        fromArrayToCollection(ia, cn);        fromArrayToCollection(fa, cn);        fromArrayToCollection(na, cn);        //下面将出现编译错误因为Number既不是String类型,也不是String的子类        //fromArrayToCollection(na, cs);    }}

在方法中一个类型形参是另一个类型形参的子类时,可以使用如Collection

public class RightTest{    //声明一个泛型方法,两个形参存在继承关系时,只需要提供一个T形参    static <T> void test(Collection<? extends T> from,Collection<T> to)    {        for(T ele : from)        {            to.add(ele);        }    }    public static void main(String[] args)    {        List<Object> ao=new ArrayList<>();        List<String> as=new ArrayList<>();        //调用方法        test(as,ao);    }}
泛型方法和类型通配符的区别
//类型通配符方式public interface Collection<E>{    <T> boolean containsAll(Collection<T> c);    <T extends E> boolean addAll(Collection<T> c);}
//泛型方法public interface Collection<E>{    boolean containsAll(Collection<?> c);    boolean addAll(Collection<? extends E> c);}

如果某方法中一个形参(a)的类型或返回值的类型依赖于另一个形参(b)的类型,则形参(b)的类型声明不应该使用通配符——因为形参(a)或返回值的类型依赖于形参(b)的类型,如果形参(b)的类型无法确定,程序就无法定义形参(a)的类型,在这种情况下只能考虑使用在方法签名中声明类型形参——泛型方法。

Java 7 的“菱形”语法与泛型构造器

泛型构造器

class Foo{    public <T> Foo(T t)    {        System.out.println(t);    }}public class GenericConstructor{    public static void main(Stringp[] args)    {        //泛型方法中的T参数是String        new Foo("张三");        //泛型方法中的T参数是Integer        new Foo(10);        //显示指定泛型构造器的T参数是String类型        //传给构造器的实参也是String类型        new <String> Foo("张三");        //下面语句将出现编译错误        new <String> Foo(10);    }}

菱形语法

class Foo<E>{    public <T> Foo(T t)    {        System.out.println(t);    }}public class GenericConstructor{    public static void main(Stringp[] args)    {        //Foo类声明中的E形参是String类型        //泛型方法中的T参数是Integer类型        Foo<String> f1=new Foo<>(10);        //显示指定泛型构造器中声明的T形参是Integer类型        Foo<String> f2=new <Integer> Foo<String>(5);        //如果显示指定泛型构造器中声明的T形参是Integer类型        //就不能使用菱形语法,下面语句错误        //Foo<String> f3=new <Integer> Foo<>(5);    }}
设定通配符下限

public class MyUtil{    //下面dest集合元素的类型必须与src集合元素的类型相同,或是其父类    public static <T> T copy(Collection<? super T> dest,Collection<T> src)    {        T last=null;        for(T ele : src)        {            last=ele;            dest.add(ele);        }        return last;    }    public static void main(String[] args)    {        List<Number> ln=new ArrayList<>();        List<Integer> li=new ArrayList<>();        li.add(5);        //此处可以确保最后一个被复制的类型是Integet类型        Integer last=copy(ln,lu);        System.out.println(ln);    }}

四、擦除和转换

当把一个具有泛型信息的对象赋给一个没有泛型信息量的变量是,所有在尖括号之间的类型信息都将被扔掉。

class Apple<T extends Number>{    T size;    public Apple()    {}    public Apple(T size)    {        this.size=size;    }    public void setSize(T size)    {        this.size=size;    }    public T getSize()    {        return this.size;    }}public class ErasureTest{    public static void main(String[] args)    {        Apple<Integer> a=new Apple<>(6);        //a的getSize()方法返回Integer对象        Integer as=a.getSize();        //把a对象赋给Apple对象,丢失尖括号里的类型信息        Apple b=a;        //b只知道size类型是Number        Number size1=b.getSize();        //下面语句将发生编译错误        //Integet size2=b.getSize();    }}

擦除

public class ErasureTest2{    public static void main(String[] args)    {        List<Integer> li=new ArrayList<>();        li.add(5);        li.add(6);        List list=li;        //下面代码引起警告,编译、运行时完全正常        List<String> ls=list;        //但是要访问集合里面的元素就会发生运行时异常        System.out.println(ls.get(0));    }}

上面的类转换成下面的类:

public class ErasureTest2{    public static void main(String[] args)    {        List<Integer> li=new ArrayList<>();        li.add(5);        li.add(6);        System.out.println((String)li.get(0));    }}

五、泛型与数组

Java允许创建无上限的通配符泛型数组:

List<?>[] lsa=new ArrayList<?>[10];Object[] oa=lsa;List<Integer> li=new ArrayList<Integer>();li.add(new Integer(3));oa[1]=li;//下面代码将引发异常String s=(String)lsa[1].get(0);

上面代码修改成如下代码

List<?>[] lsa=new ArrayList<?>[10];Object[] oa=lsa;List<Integer> li=new ArrayList<Integer>();li.add(new Integer(3));oa[1]=li;Object s=lsa[1].get(0);if(s instanceof String){    //下面代码安全    String s=(String)s;}
0 0