Java中的泛型

来源:互联网 发布:淘宝上传的视频压缩过 编辑:程序博客网 时间:2024/06/08 18:28

引言


泛型的本质是所操作的数据类型被指定为一个参数。参数类型可以用在类、接口和方法的创建中,分别成为泛型类、泛型接口、泛型方法。

 

使用泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。


泛型的类型参数只能是类类型的(包括自定义类),不能是简单类型。


同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型实例是不兼容的。


泛型的类型参数可以有多个。


泛型的参数类型可以使用extends语句。习惯上称为“有界类型”。


泛型的参数类型还可以是通配符类型。


我们用一张图来说明一下泛型的来源:




我们通过非泛型类和泛型类入手,讲解泛型的优点,学会这个基本上学会了泛型的70%的内容。


实例一:


1)泛型类:


<span style="font-size:18px;">class Container<T> {    private T data;    public Container() {    }    public Container(T data) {        setData(data);    }    public T getData() {        return data;    }    public void setData(T data) {        this.data = data;    }}</span>

调用:


<span style="font-size:18px;">public class GenericTest { public static void main(String[] args) { Container<String> name = new Container<String>("corn"); Container<Integer> age = new Container<Integer>(712); Container<Number> number = new Container<Number>(314);        getData(name);        getData(age);        getData(number);                //getUpperNumberData(name); // 1        getUpperNumberData(age);    // 2        getUpperNumberData(number); // 3    }    public static void getData(Container<?> data) {        System.out.println("data :" + data.getData());    }    public static void getUpperNumberData(Container<? extends Number> data){        System.out.println("data :" + data.getData());    }    }</span>

结果:


data :corn
data :712
data :314
data :712
data :314


2)非泛型类:


<span style="font-size:18px;">public class ObjectTest {    private Object x;     public ObjectTest(Object x) {        this.x = x;    }     public Object getX() {        return x;    }     public void setX(Object x) {        this.x = x;    }}</span>

调用:


<span style="font-size:18px;">public class GenericTest { public static void main(String[] args) {  ObjectTest name = new ObjectTest(new String("corn")); ObjectTest age = new ObjectTest(new Integer(2)); ObjectTest number = new ObjectTest(new Float(2));         getDataToString(name); getDataToInteger(age); getDataToFloat(number);    }    public static void getDataToString(ObjectTest data) {        System.out.println("data :" + (String)data.getX());    }        public static void getDataToInteger(ObjectTest data) {        System.out.println("data :" + (Integer)data.getX());    }        public static void getDataToFloat(ObjectTest data) {        System.out.println("data :" + (Float)data.getX());    }    }</span>

结果:


data :corn
data :2
data :2.0


解析:


我们在Java5之前,为了让类有通用性,往往将参数类型、返回类型设置为Object类型,当获取这些返回类型来使用时候,必须将其“强制”转换为原有的类型或者接口,然后才可以调用对象上的方法。


强制类型转换很麻烦,我还要事先知道各个Object具体类型是什么,才能做出正确转换。否则,要是转换的类型不对,那么编译的时候不会报错,课时运行的时候就会出错。有没有不强制转换的办法,就是泛型来实现。


下面解释一下上面的泛型类的语法:


使用<T>来生命一个类型持有者名称,然后就可以把T当做一个类型代表来生命成员、参数和返回值类型。

 

当然T仅仅是一个名字,这个名字可以自行定义。如果我们创建的类不是泛型类,也就是我们没有定义T,这个时候,我们就可以用?代替(只能用于类型实参不能用于类型形参)。其实有时候,Object和T之间的区别在于,Object所有类的一个基类,而T只是一个通配符(名称)而已。


T如果没有任何限制,实际上相当于Object类型。

 

Object泛型类相比,使用泛型所定义的类在声明和构造函数时候,可以使用"<实际类型>"来一并制定泛型类型持有者的真实类型。


泛型方法:


泛型方式使用的原因,我们有这种情况,就是该类并不是每一个方法都需要用到泛型的,可能仅仅是某一个方法使用到泛型,这个时候,我们就可以使用泛型方法。


实例:


<span style="font-size:18px;">public class GenericTest2 {public <T> void function(T x) {        System.out.println(x.getClass().getName());    }public static void main(String[] args) {GenericTest2 gt = new GenericTest2();gt.function(" ");gt.function(10);gt.function('a');gt.function(gt);    }}</span>

结果:


java.lang.String
java.lang.Integer
java.lang.Character
GenericTest2


解析:


使用泛型方法时,不必指明参数类型,编译器会自己找出具体的类型。泛型方法除了定义不同,调用就像普通方法一样。

需要注意,一个static方法,无法访问泛型类的类型参数,所以,若要static方法需要使用泛型能力,必须使其成为泛型方法。

什么意思?


很简单:


<span style="font-size:18px;">public static T main(T args) {GenericTest2 gt = new GenericTest2();gt.function(" ");gt.function(10);gt.function('a');gt.function(gt);return args;    }</span>

这样写是无法进行编译的,要这样写:


<span style="font-size:18px;">public static<T> T main(T args) {GenericTest2 gt = new GenericTest2();gt.function(" ");gt.function(10);gt.function('a');gt.function(gt);return args;    }</span>

这样写就没有问题了。就是让static也为泛型方法。


注:

这里我写一个代码就是将Integer类型赋值给Number类型的对象,但是,编译没有办法通过,按照类型通配符上线和类型通配符下限的说法,在逻辑上面Integer和float都是Number的子类,是可以编译通过的,但是没有,这个问题很纠结。


<span style="font-size:18px;">public class GenericTest { public static void main(String[] args) {          Container<Integer> a = new Container<Integer>(712); Container<Number> b = a;  // 1 Container<Float> f = new Container<Float>(3.14f);// b.setData(f);        // 2 getData(b);    }    public static void getData(Container<?> data) {        System.out.println("data :" + data.getData());    }       }</span>



在代码//1处出现了错误提示信息:The method getData(Container<Number>) in the t ype GenericTest is not applicable for the arguments (Container<Integer>)。显然,通过提示信息,我们知道Container<Number>在逻辑上不能视为Container<Integer>的父类。那么,原因何在呢?


然后,在网上查到的资料是这样的:


假设Container<Number>在逻辑上可以视为Container<Integer>的父类,那么//1和//2处将不会有错误提示了,那么问题就出来了,通过getData()方法取出数据时到底是什么类型呢?Integer? Float? 还是Number?且由于在编程过程中的顺序不可控性,导致在必要的时候必须要进行类型判断,且进行强制类型转换。显然,这与泛型的理念矛盾,因此,在逻辑上Container<Number>不能视为Container<Integer>的父类。


好,那我们回过头来继续看“类型通配符”中的第一个例子,我们知道其具体的错误提示的深层次原因了。那么如何解决呢?总部能再定义一个新的函数吧。这和Java中的多态理念显然是违背的,因此,我们需要一个在逻辑上可以用来表示同时是Container<Integer>和Container<Number>的父类的一个引用类型,由此,类型通配符应运而生。


类型通配符一般是使用 ? 代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且Container<?>在逻辑上是Container<Integer>、Container<Number>...等所有Container<具体类型实参>的父类。由此,我们依然可以定义泛型方法,来完成此类需求。


我们将代码改为:


<span style="font-size:18px;">public class GenericTest { public static void main(String[] args) {          Container<Integer> a = new Container<Integer>(712); Container<?> b = a;  // 1 Container<Float> f = new Container<Float>(3.14f);// b.setData(f);        // 2 getData(b);    }    public static void getData(Container<?> data) {        System.out.println("data :" + data.getData());    }       }</span>


我们明白一件事情就可以了:在代码中,?只能作为实参不能最为形参。



结束:


Java语言中添加泛型类型肯定会大大增强我们使用静态类型系统的能力。学习如何使用泛型类型箱单简单,但是同样也需要避免一些缺陷。
















1 0
原创粉丝点击