您的位置:首页 > 编程语言 > Java开发

java基础--泛型

2016-01-07 20:34 525 查看
在java 1.5之前,一般的类和方法,只能使用具体的类型(基础类型或自定义类型),如果要编写可以适应多种类型的代码,就只能使用多态这种泛化机制,然后通过强制类型转换来实现参数的“任意化”。但这种转换要求实际参数类型可以预知。且对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。

泛型的类型参数只能是类类型(包括自定义类),不能是基本数据类型(可以使其包装类)。

泛型的参数类型可以使用extends语句,例如
<T extends superclass>
。习惯上称为“有界类型”。

泛型的参数类型还可以是通配符类型。例如
Class<?> classType = 

Class.forName("java.lang.String");


1.泛型类

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

[code]class Gen {
    public Object ob; // 定义一个通用类型成员

    public Gen(Object ob) {
        this.ob = ob;
    }

    public void getTyep() {
        System.out.println("实际类型是: " + ob.getClass().getName());
    }
}

public class GenDemo {
    public static void main(String[] args) {
        // Integer版本
        Gen intOb = new Gen(new Integer(88));
        intOb.getTyep();
        System.out.println("value= " + (Integer) intOb.ob);//需要类型转换
        System.out.println("---------------------------------");
        // String版本
        Gen strOb = new Gen("Hello Gen!");
        strOb.getTyep();
        System.out.println("value= " + (String) strOb.ob);//需要类型转换
    }
}


强制类型转换很麻烦,还要事先知道各个Object具体类型是什么,才能做出正确转换。否则,要是转换的类型不对,就会出现运行时错误。而使用泛型就可以暂时先不指定类型,稍后再决定具体使用什么类型。

实现泛型版本

[code]class Gen2<T> {
    public T ob; // 定义泛型成员变量

    public Gen2(T ob) {
        this.ob = ob;
    }

    public void getType() {
        System.out.println("T的实际类型是: " + ob.getClass().getName());
    }
}

public class GenDemo2 {
    public static void main(String[] args) {
        // Integer版本
        Gen2<Integer> intOb = new Gen2<Integer>(88);
        intOb.getType();
        System.out.println("value= " + intOb.ob);
        System.out.println("----------------------------------");
        // String版本
        Gen2<String> strOb = new Gen2<String>("Hello Gen!");
        strOb.getType();
        System.out.println("value= " + strOb.ob);
    }
}


元组

一次方法调用返回多个对象,是编程中经常用到的功能,但是return语句只能返回一个对象。通常的解决办法是创建一个对象来持有想要返回的多个对象,通过返回一个这种对象来获取我们需要的多个对象。有了泛型就能够简化这种操作,编写出通用的代码,同时在编译器确保类型安全。

元组(tuple)是将一组对象直接打包存储于一个单一对象中。这个对象允许读取其中的对象,不允许向其中放入新对象。这个概念也称为数据传送对象信使

元组可以具有任意长度,元组中的对象可以是任意不同的类型。例如:

[code]//2维元组,持有两个对象
class TwoTuple<A,B> {
    public final A a;
    public final B b;
    public TwoTuple(A a,B b) {
        this.a = a;
        this.b = b;
    }
}

public class TupleTest {
    //返回这个对象
    public TwoTuple<String,Integer> f() {
        return new TwoTuple<String,Integer>("hi",10);
    }
}


注:对象a,b声明成final不声明成private不违反java编程的安全原则吗?

声明成private类型,a,b就只能在类内部使用,只有添加get,set方法才能通过对象来调用

声明成final的元素,当他们第一次被初始化以后就不能被修改,同样保证了编程的安全性。

可以通过继承机制实现类型更长的元组,例如:

[code]//3维元组,持有三个对象
class ThreeTuple<A,B,C> extends TwoTuple<A,B> {
    public final C c;
    public ThreeTuple(A a,B b,C c) {
        super(a,b);
        this.c = c;
    }
}


2.泛型方法

要定义泛型方法,只需将泛型参数列表置于返回值之前。是否拥有泛型方法,与其所在的类是否是泛型没有关系,泛型方法使得该方法能够独立于类而产生变化。

基本原则:如果使用泛型方法可以取代整个类泛型化,就应该使用泛型方法。

如:

[code]   public class GenericMethods {
    public <T> void f(T x) {
        System.out.println(x.getClass().getName());
    }

    public static void main(String[] args) {
        GenericMethods gm = new GenericMethods();
        gm.f(1); //输出:java.lang.Integer
        gm.f("a");  //输出:java.lang.String
        gm.f(gm);  //输出:GenericMethods
    }
}


注意:

使用泛型方法时,不必指明参数类型,编译器会自己找出具体的类型。这称为类型参数推断(type argument inference)。泛型方法除了定义不同,调用就像普通方法一样。

如果f()传入基本类型,自动打包机制就会介入其中,将基本类型的值包装为对应的对象。

对于一个static的方法而言,无法访问泛型类的类型参数,所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。

类型推断只对赋值操作有效,如果你将一个泛型方法调用的结果作为参数,传递给另一个方法,这时编译器并不会执行类型推断。编译器认为:调用泛型方法后,其返回值被赋给一个Object类型的变量。这时必须显示指明泛型方法的类型。如:

[code]public class GenericMethods {

    public <T> List<T> g() {
        return new ArrayList<T>();
    }

    public void h(List<String> x) {
        System.out.println(x.getClass().getName());
    }

    public static void main(String[] args) {
        GenericMethods gm = new GenericMethods();
        List<String> list = gm.g(); //类型推断
        //gm.h(gm.g());   //报错,不能由h中参数类型String,推断出g()中类型
        gm.h(gm.<String>g()); //显示指明类型,输出:java.util.ArrayList
    }
}


可变参数与泛型方法

可变参数:适用于参数个数不确定,类型确定的情况,java把可变参数当做数组处理。注意:可变参数必须位于最后一项。当可变参数个数多余一个时,必将有一个不是最后一项,所以只支持有一个可变参数。

可变参数的特点:

只能出现在参数列表的最后;

…位于变量类型和变量名之间,前后有无空格都可以;

调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中一数组的形式访问可变参数。

泛型方法与可变参数列表能够很好的共存:

[code]public class GenericVarargs {
    public static <T> void print(T ...array) {
        for(T item : array) {
            System.out.print(item + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        print("A","B","C"); //输出:A B C
        print(1,2,3,4); //输出:1 2 3 4
    }
}


3.类型擦除与补偿

使用泛型时,java会在编译时检查类型的安全性,但会在运行时擦除所有这些信息。

[code]import java.util.ArrayList;
import java.util.Arrays;

public class ErasedType {
    public static void main(String[] args) {
        Class c1 = new ArrayList<String>().getClass();
        Class c2 = new ArrayList<Integer>().getClass();
        System.out.println(c1 == c2);//输出:true
        System.out.println(Arrays.toString(c1.getTypeParameters()));//输出:[E]
    }
}


ArrayList<String>
ArrayList<Integet>
在运行时事实上是相同的类型,这两种形式都被擦除成他们的原生类型,即ArrayList。

class.getTypeParameters()
将返回一个TypeVariable对象数组。

在泛型方法内部,无法获得任何有关泛型参数的类型信息。

如以下代码:

[code]public class ErasedType2<T> {

    public static void f(Object obj) {
        if(obj instanceof T) {  } //错误
        T var = new T();          //错误
        T[] array = new T[10];    //错误
    }
}


虽然偶尔可以绕过这些问题来编程,但有时必须通过引入类型标签来对擦除进行补偿。这需要显式的传递类型的Class对象。

如代码:

[code]public class TypeOffset<T> {
    Class<T> obj;
    public TypeOffset(Class<T> obj) {
        this.obj = obj;
    }

    public boolean f(Object arg) {
        return obj.isInstance(arg);
    }

    public static void main(String[] args) {
        TypeOffset<Cat> to = new TypeOffset<Cat>(Cat.class);
        System.out.println(to.f(new Cat())); //true
        System.out.println(to.f(new Animal())); //false
    }

}
class Animal {   }
class Cat extends Animal { }


用动态的isInstance()方法,代替instanceof,判断是否是Cat类型的对象。

4.限制泛型

要限制泛型类
class Generics<T>
中类型T为集合接口类型,只需要这么做:
class Generics<T extends Collection>
,这样类中的泛型T只能是Collection接口的实现类,传入非Collection接口编译会出错。

实际上
class Generics<T>
的限定类型相当于Object,这和“Object泛型”实质是一样的。

注意:
<T extends Collection>
这里的限定使用关键字extends,后面可以是类也可以是接口

限制为集合接口的类型:

[code]public class CollectionGen<T extends Collection> {
    private T x;

    public CollectionGen(T x) {
        this.x = x;
    }

    public T getX() {
        return x;
    }

    public void setX(T x) {
        this.x = x;
    }
}


实例化:

[code]public class CollectionGenDemo {
    public static void main(String args[]) {
        CollectionGen<ArrayList> listFoo = null;
        listFoo = new CollectionGen<ArrayList>(new ArrayList());
        System.out.println("实例化成功!");
    }
}


当前看到的这个写法是可以编译通过,并运行成功。可是注释掉的两行加上就出错了,因为
<T extends Collection>
这么定义类型的时候,就限定了构造此类实例的时候T是确定的一个类型,这个类型实现了Collection接口,但是实现 Collection接口的类很多很多,如果针对每一种都要写出具体的子类类型,那也太麻烦了,干脆还不如用Object通用一下。泛型针对这种情况还有更好的解决方案,那就是“通配符泛型”。

多接口限制

虽然Java泛型简单的用 extends 统一的表示了原有的 extends 和 implements 的概念,但仍要遵循应用的体系,Java 只能继承一个类,但可以实现多个接口,所以你的某个类型需要用 extends 限定,且有多种类型的时候,只能存在一个是类,并且类写在第一位,接口列在后面,也就是:

[code]<T extends SomeClass & interface1 & interface2 & interface3>


这里的例子仅演示了泛型方法的类型限定,对于泛型类中类型参数的限制用完全一样的规则,只是加在类声明的头部,如:

[code]public class Demo<T extends Comparable & Serializable> {
    // T类型就可以用Comparable声明的方法和Seriablizable所拥有的特性了
}


5.通配符泛型

为了解决类型被限制死了不能动态根据实例来确定的缺点,引入了“通配符泛型”,使用通配泛型格式为

[code]import java.util.*;

public class CovariantGenerics {
    public static void main(String[] args) {
        List<Apple> flist = new ArrayList<Apple>();
        flist.add(new Apple());
        //flist.add(new Orange());  //不能编译,
        List<?> slist = flist;
        System.out.println(slist.get(0).getClass().getName()); //输出:com.generics.Apple ,说明实际存入的为Apple类型
        //Apple ap = slist.get(0); //但取出后为Object实例,不能赋给ap
    }
}

class Fruit {  }

class Apple extends Fruit { }

class Orange extends Fruit { }


注意:

如果只指定了
<?>
,而没有extends,则默认为
<? extends Object>


通配符泛型不单可以向下限制,如
<? extends Collection>
,还可以向上限制,如
<? super Double>
,表示类型只能接受Double及其上层父类类型,如Number、Object类型的实例。

例如:

[code]import java.util.*;

public class CovariantGenerics {
    public static void main(String[] args) {
        List<Apple> flist = new ArrayList<Apple>();
        flist.add(new Apple());

        List<? extends Fruit> slist = flist;
        //slist.add(new Apple());  //任何类型都不能存入
        System.out.println(slist.get(0).getClass().getName()); //输出:com.generics.Apple ,说明实际存入的为Apple类型
        Fruit ap = slist.get(0); //但取出后为Fruit实例

        List<? super Apple> tlist = flist;
        tlist.add(new RedApple()); //Apple及其子类能够存入
        Object ob = tlist.get(0);  //但取出后为Object
    }
}

class Fruit {  }

class Apple extends Fruit { }

class RedApple extends Apple { }


<? extends Fruit>
,通配符告诉编译器我们正在处理Fruit的子类型,但它不知道这个子类型究竟是什么。因为没法确定,为了保证类型安全,就不允许往里面加入任何这种类型的数据。另一方面,不论它是什么类型,它总是类型Fruit的子类型,当我们在读取数据时,能确保返回的数据是一个Fruit类型的实例

相应的对于
<? super Apple>
,编译器知道我们正在处理Apple的父类型的数据,当我们加入一个Apple或其子类型的数据时,能够保证类型安全。另一方面,由于不知道我们处理的究竟是那种父类型,但一定能够保证是Object或其子类型,所以当读取数据时返回Object类型

参考:

think in java

百度百科
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: