Java 泛型

作者: 数独题 | 来源:发表于2016-11-19 11:50 被阅读158次
    基础

    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,不管泛型的实际参数是什么,运行时总有相同的class
    System.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<Foo>并不是G<Bar>的子类型,这一点非常注意!

    数组和泛型有所不同,假设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<?>,这种写法可以适用于任何支持泛型声明的接口和类,比如:Set<?>、Collection<?>、Map<?,?>等,但这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素加入其中,唯一例外的是null,它是所有应用类型的实例。另一方面,程序可以调用get()方法来返回List<?>集合指定索引处的元素。
    如:

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

    将Canvas类修改成下面的形式,就可以把List<Circle>对象当成List<? extends Shape>使用。即List<? extends Shape>可以表示成List<Circle>、List<Rectangle>的父类。Shape称为这个通配符的上限。

    //定义一个抽象类Shape
    public abstract class Shape
    {
        public abstract void draw(Canvas c);
    }
    
    //定义Shape的子类Circle
    public class Circle extends Shape
    {
        //实现画图方法
        public void draw(Canvas c)
        {
            System.out.println("在画布"+c+"上画一个圆")
        }
    }
    
    //定义Shape的子类Rectangle
    public 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<? extends T>的语法

    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);
                
        }
    }
    
    设定通配符下限

    <? extends Type>,这个通配符表示他必须是Type本身,或是其父类
    实现集合复制:

    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;
    }
    

    相关文章

      网友评论

        本文标题:Java 泛型

        本文链接:https://www.haomeiwen.com/subject/efdspttx.html