java基础--泛型

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

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

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

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

  • 泛型的参数类型还可以是通配符类型。例如Class<?> classType =
    Class.forName("java.lang.String");

1.泛型类

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

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具体类型是什么,才能做出正确转换。否则,要是转换的类型不对,就会出现运行时错误。而使用泛型就可以暂时先不指定类型,稍后再决定具体使用什么类型。
实现泛型版本

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)是将一组对象直接打包存储于一个单一对象中。这个对象允许读取其中的对象,不允许向其中放入新对象。这个概念也称为数据传送对象信使

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

//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的元素,当他们第一次被初始化以后就不能被修改,同样保证了编程的安全性。

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

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

2.泛型方法

要定义泛型方法,只需将泛型参数列表置于返回值之前。是否拥有泛型方法,与其所在的类是否是泛型没有关系,泛型方法使得该方法能够独立于类而产生变化。
基本原则:如果使用泛型方法可以取代整个类泛型化,就应该使用泛型方法。
如:

   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类型的变量。这时必须显示指明泛型方法的类型。如:

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把可变参数当做数组处理。注意:可变参数必须位于最后一项。当可变参数个数多余一个时,必将有一个不是最后一项,所以只支持有一个可变参数。

可变参数的特点:

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

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

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

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

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会在编译时检查类型的安全性,但会在运行时擦除所有这些信息。

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对象数组。
在泛型方法内部,无法获得任何有关泛型参数的类型信息。
如以下代码:

public class ErasedType2<T> {

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

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

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,后面可以是类也可以是接口

限制为集合接口的类型:

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

实例化:

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 限定,且有多种类型的时候,只能存在一个是类,并且类写在第一位,接口列在后面,也就是:

<T extends SomeClass & interface1 & interface2 & interface3>

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

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

5.通配符泛型

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

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类型的实例。

例如:

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类型

参考:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值