Java泛型

来源:互联网 发布:利用js实现动态时间 编辑:程序博客网 时间:2024/06/03 17:01
1、泛型使用:
    在Java 7 以前,如果使用带泛型的接口、类定义变量,那么调用构造器创建对象时构造器的后面也必须带泛型,这显得有些多余了。例如如下两条语句:
   
    List<String> strList = new ArrayList<String>();    Map<String, Integer> scores = new HashMap<String, Integer>();

    从Java 7开始,Java允许在构造器后不需要带完整的泛型信息,只要给出一对尖括号(<>)即可,Java可以推断尖括号里应该是什么泛型信息。即上面两条语句可以改写为如下形式:
    
   List<String> strList = new ArrayList<>();   Map<String, Integer> scores = new HashMap<>();

2、并不存在泛型类
    ArrayList<String> 类像是一种特殊的ArrayList 类:该ArrayList<String> 对象只能添加String 对象作为集合元素。但实际上,系统并没有为ArrayList<String> 生成新的class文件,而且也不会把ArrayList<String> 当成新类来处理。
    看下面代码的打印结果是什么?
    
    // 分别创建List<String> 对象和List<Interger>对象    List<String> l1 = new ArrayList<>();    List<Integer> l2 = new ArrayList<>();    // 调用getClass() 方法来比较l1和l2的类是否相等    System.out.println(l1.getClass() == l2.getClass());

运行上面的代码片段,实际输出的是true。因为不管泛型的实际类型参数是什么,它们在运行时总是同样的类(class).
不管为泛型的类型形参传入哪一种类型参数,对于Java来说,它们依然被当成同一个类处理在内存中也只占用一块内存空间,因此在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参。

3、类型通配符
    提出一个问题:如果Foo是Bar 的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G<Foo>是否是G<Bar>的子类型?答案是否定的。数组和泛型有所不同,假设Foo是Bar的一个子类型(子类或者子接口),那么Foo[]依然是Bar[]的子类型;但G<Foo>不是G<Bar>的子类型。
    (1) 使用类型通配符
    为了表示各种泛型List 的父类,可以使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List 集合,写作:List<?>(意思是元素类型位置的List)。这个问号(?) 被称为通配符,它的元素类型可以匹配任何类型。
    但这种带通配符的List 仅表示它是各种泛型List 的父类,并不能把元素加入到其中。例如,如下代码将会引起编译错误。
    List<?> c = new ArrayList<String>();
    // 下面程序引起编译错误
    c.add(new Object());
    因为程序无法确定c集合中元素的类型,所以不能向其中添加对象。根据前面的List<E> 接口定义的代码可以发现:add()方法有类型参数E 作为集合的元素类型,所以传给add 的参数必须是E 类的对象或其子类的对象。但因为在该例中不知道E 是什么类型,所以程序无法将任何对象“丢进”该集合。唯一的例外是null,它是所有引用类型的实例。

    (2) 设定类型通配符的上限
    当直接使用List<?> 这种形式时,即表明这个List 集合可以是任何泛型List 的父类。但还有一个特殊的情形,程序不希望这个List<?> 是任何泛型List 的父类,只希望它代表某一类泛型List 的父类。考虑一个简单的绘图程序,下面先定义三个形状类。
   
    // 定义一个抽象类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 + "上");        }    }

    上面定义了三个形状类,其中Shape 是一个抽象父类,该抽象父类有两个子类:Circle 和 Rectangle 。接下来定义一个Canvas类,该画布类可以画数量不等的形状(Shape 子类的对象),那应该如何定义这个Canvas类呢?考虑如下的Canvas实现类。
    public class Canvas    {        // 同时在画布上绘制多个形状        public void drawAll(List<Shape> shapes)        {            for(Shape s : shapes)            {                s.draw(this);            }        }    }

注意上面的drawAll()方法的形参类型是List<Shape>,而List<Circle>并不是List<Shape>的子类型,因此,下面代码将引起编译错误。
    List<Circle> circleList = new ArrayList<>();    Canvas c = new Canvas();

    不能把List<Circle>当成List<Shape>使用,所以不能把List<Circle>对象当成List<Shape>使用。为了表示List<Circle>的父类,可以使用List<?>,把Canvas 改成如下形式:
     public class Canvas    {        // 同时在画布上绘制多个形状        public void drawAll(List<?> shapes)        {            for(Object obj : shapes)            {                Shape s = (Shape) obj;                s.draw(this);            }        }    }

    上面程序使用了通配符来表示所有的类型。上面的drawAll()方法可以接受List<Circle> 对象作为参数,问题是上面的方法实现体显得极为臃肿而烦琐:使用了泛型还需要进行强制类型转换。
    实际上需要一种泛型表示方法,它可以表示所有的Shape泛型List 的父类。为了满足这种需求,Java泛型提供了被限制的泛型通配符。被限制的泛型通配符表示如下:
    // 它表示所有Shape泛型List 的父类    List<? extends Shape>    有了这种被限制的泛型通配符,就可以把上面的Canvas程序改为如下形式:    public class Canvas    {        //  同时在画布上绘制多个形状,使用被限制的泛型通配符        public void drawAll(List<? extends Shape> shapes)        {            for(Shape s : shapes)            {                s.draw(this);            }        }    }

    将Canvas改为如上形式,就可以把List<Circle> 对象当成List<? extends Shape> 使用。即List<? extends Shape> 可以表示List<Circle>、List<Rectangle>的父类--只要List后尖括号里的类型是Shape的子类型。
    类似地,由于程序无法确定这个受限制的通配符的具体类型,所以不能把Shape对象或其子类的对象加入到这个泛型集合中。例如,下面代码就是错误的。
    public void addRectangle(List<? extends Shape> shapes)    {        // 下面代码引起编译错误        shapes.add(new Rectangle());    }  

    (3) 设定类型形参的上限
    Java 泛型不仅允许在使用通配符形参时设定上限,而且可以在定义类型形参时设定上限,用于表示传给该类型形参的实际类型要么是该上限类型,要么是该上限类型的子类。下面程序示范了这种用法。
    public class Apple<T extends Number>    {        T col;        public static void main(String[] args)        {            Apple<Integer> ai = new Apple<>();            Apple<Double> ad = new Apple<>();            // 下面代码将引发编译异常,下面代码试图把String类型传给T形参            // 但String 不是Number 的子类型,所以引起编译错误            Apple<String> as = new Apple<>();        }    } 

1 0
原创粉丝点击