Java泛型

来源:互联网 发布:linux vim 复制粘贴 编辑:程序博客网 时间:2024/04/29 06:45

这是我看了几个网友的文章之后总结出来的,没用总结那么细,但是对我们学习泛型应该基本够用了,有什么遗漏的地方希望大家提出来!


为什么使用泛型

  使用泛型的典型例子,是在集合中的泛型使用

  在使用泛型前,存入集合中的元素可以是任何类型的,当从集合中取出时,所有的元素都是Object类型,需要进行向下的强制类型转换,转换到特定的类型。

如果我们只写一个排序方法,就能够对整形数组、字符串数组甚至支持排序的任何类型的数组进行排序,这该多好啊。

使用Java泛型的概念,我们可以写一个泛型方法来对一个对象数组排序。然后,调用该泛型方法来对整型数组、浮点数数组、字符串数组等进行排序。

泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单
在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。而泛型解决了这个隐患!
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------

规则限制

1、泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
2、同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
3、泛型的类型参数可以有多个。
4、泛型的参数类型可以使用extends语句,例如<T extends superclass>。习惯上称为“有界类型”。
5、泛型的参数类型还可以是通配符类型。例如Class<?> classType = Class.forName("java.lang.String");
例子一:使用了泛型
class Gen<T> {
    private T ob; // 定义泛型成员变量
 
    public Gen(T ob) {
        this.ob = ob;
    }
 
    public T getOb() {
        return ob;
    }
 
    public void setOb(T ob) {
        this.ob = ob;
    }
 
    public void showType() {
        System.out.println("T的实际类型是: " + ob.getClass().getName());
    }
}
 
public class GenDemo {
    public static void main(String[] args) {
        // 定义泛型类Gen的一个Integer版本
        Gen<Integer> intOb = new Gen<Integer>(88);
        intOb.showType();
        int i = intOb.getOb();
        System.out.println("value= " + i);
        System.out.println("----------------------------------");
        // 定义泛型类Gen的一个String版本
        Gen<String> strOb = new Gen<String>("Hello Gen!");
        strOb.showType();
        String s = strOb.getOb();
        System.out.println("value= " + s);
    }
}
例子二:没有使用泛型

class Gen2 {
    private Object ob; // 定义一个通用类型成员
 
    public Gen2(Object ob) {
        this.ob = ob;
    }
 
    public Object getOb() {
        return ob;
    }
 
    public void setOb(Object ob) {
        this.ob = ob;
    }
 
    public void showTyep() {
        System.out.println("T的实际类型是: " + ob.getClass().getName());
    }
}
 
public class GenDemo2 {
    public static void main(String[] args) {
        // 定义类Gen2的一个Integer版本
        Gen2 intOb = new Gen2(new Integer(88));
        intOb.showTyep();
        int i = (Integer) intOb.getOb();
        System.out.println("value= " + i);
        System.out.println("---------------------------------");
        // 定义类Gen2的一个String版本
        Gen2 strOb = new Gen2("Hello Gen!");
        strOb.showTyep();
        String s = (String) strOb.getOb();
        System.out.println("value= " + s);
    }
}
两个例子运行Demo结果是相同的

-----------------------------------------------------------------------------------------------------------------------------------------

深入泛型

有两个类如下,要构造两个类的对象,并打印出各自的成员x

public class StringFoo {
    private String x;
 
    public StringFoo(String x) {
        this.x = x;
    }
 
    public String getX() {
        return x;
    }
 
    public void setX(String x) {
        this.x = x;
    }
}
 
public class DoubleFoo {
    private Double x;
 
    public DoubleFoo(Double x) {
        this.x = x;
    }
 
    public Double getX() {
        return x;
    }
 
    public void setX(Double x) {
        this.x = x;
    }
}

重构

因为上面的类中,成员和方法的逻辑都一样,就是类型不一样,因此考虑重构。Object是所有类的父类,因此可以考虑用Object做为成员类型,这样就可以实现通用了,实际上就是“Object泛型”,暂时这么称呼。

public class ObjectFoo {
    private Object x;
 
    public ObjectFoo(Object x) {
        this.x = x;
    }
 
    public Object getX() {
        return x;
    }
 
    public void setX(Object x) {
        this.x = x;
    }
}
public class ObjectFooDemo {
    public static void main(String args[]) {
        ObjectFoo strFoo = new ObjectFoo(new StringFoo("Hello Generics!"));
        ObjectFoo douFoo = new ObjectFoo(new DoubleFoo(new Double("33")));
        ObjectFoo objFoo = new ObjectFoo(new Object());
        System.out.println("strFoo.getX=" (StringFoo) strFoo.getX());
        System.out.println("douFoo.getX=" (DoubleFoo) douFoo.getX());
        System.out.println("objFoo.getX=" + objFoo.getX());
    }
}

运行结果如下:
strFoo.getX=StringFoo@5d748654
douFoo.getX=DoubleFoo@d1f24bb
objFoo.getX=java.lang.Object@19821f
解说:在Java 1.5之前,为了让类有通用性,往往将参数类型、返回类型设置为Object类型,当获取这些返回类型来使用时候,必须将其“强制”转换为原有的类型或者接口,然后才可以调用对象上的方法。

实现

强制类型转换很麻烦,我还要事先知道各个Object具体类型是什么,才能做出正确转换。否则,要是转换的类型不对,比如将“Hello Generics!”字符串强制转换为Double,那么编译的时候不会报错,可是运行的时候就挂了。那有没有不强制转换的办法----有,改用 Java5泛型来实现。
public class GenericsFoo<T> {
    private T x;
 
    public GenericsFoo(T x) {
        this.x = x;
    }
 
    public T getX() {
        return x;
    }
 
    public void setX(T x) {
        this.x = x;
    }
}
 
public class GenericsFooDemo {
    public static void main(String args[]) {
        GenericsFoo<String> strFoo = new GenericsFoo<String>("Hello Generics!");
        GenericsFoo<Double> douFoo = new GenericsFoo<Double>(new Double("33"));
        GenericsFoo<Object> objFoo = new GenericsFoo<Object>(new Object());
        System.out.println("strFoo.getX=" + strFoo.getX());
        System.out.println("douFoo.getX=" + douFoo.getX());
        System.out.println("objFoo.getX=" + objFoo.getX());
    }
}
运行结果:
strFoo.getX=Hello Generics!
douFoo.getX=33.0
objFoo.getX=java.lang.Object@19821f
和使用“Object泛型”方式实现结果的完全一样,但是这个Demo简单多了,里面没有强制类型转换信息。

下面解释一下上面泛型类的语法:
使用<T>来声明一个类型持有者名称,如:GenericsFoo<T>,然后就可以把T当作一个类型代表来声明成员、参数和返回值类型。
当然T仅仅是个名字,这个名字可以自行定义,可以不用T,用B、G、E、F、H等等代替。
class GenericsFoo<T> 声明了一个泛型类,这个T没有任何限制,实际上相当于Object类型,实际上相当于 class GenericsFoo<T extends Object>
与Object泛型类相比,使用泛型所定义的类在声明和构造实例的时候,可以使用“<实际类型>”来一并指定泛型类型持有者的真实类型。类如
GenericsFoo<Double> douFoo=new GenericsFoo<Double>(new Double("33"));
当然,也可以在构造对象的时候不使用尖括号指定泛型类型的真实类型,但是你在使用该对象的时候,就需要强制转换了。比如:GenericsFoo douFoo=new GenericsFoo(new Double("33"));
实际上,当构造对象时不指定类型信息的时候,默认会使用Object类型,这也是要强制转换的原因。
----------------------------------------------------------------------------------------------------------------------------------------

高级应用

有界的类型参数:

可能有时候,你会想限制参数的类型种类范围。例如,一个操作数字的方法可能只希望接受Number或者Number子类的实例

这就是有界类型参数的目的。

要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界(<T extends Comparable<T>>)

实例

下面的例子演示了"extends"如何使用在一般意义上的意思"extends"(类)或者"implements"(接口)。该例子中的泛型方法返回三个可比较对象的最大值。

public class MaximumTest{   // 比较三个值并返回最大值   public static <T extends Comparable<T>> T maximum(T x, T y, T z)   {                           T max = x; // 假设x是初始最大值      if ( y.compareTo( max ) > 0 ){         max = y; //y 更大      }      if ( z.compareTo( max ) > 0 ){         max = z; // 现在 z 更大                 }      return max; // 返回最大对象   }   public static void main( String args[] )   {      System.out.printf( "Max of %d, %d and %d is %d\n\n",                   3, 4, 5, maximum( 3, 4, 5 ) );       System.out.printf( "Maxm of %.1f,%.1f and %.1f is %.1f\n\n",                   6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ) );       System.out.printf( "Max of %s, %s and %s is %s\n","pear",         "apple", "orange", maximum( "pear", "apple", "orange" ) );   }}

编译以上代码,运行结果如下所示:

Maximum of 3, 4 and 5 is 5 Maximum of 6.6, 8.8 and 7.7 is 8.8 Maximum of pear, apple and orange is pear

限制泛型

在上面的例子中,由于没有限制class GenericsFoo<T>类型持有者T的范围,实际上这里的限定类型相当于Object,这和“Object泛型”实质是一样的。限制比如我们要限制T为集合接口类型。只需要这么做:
class GenericsFoo<T extends Collection>,这样类中的泛型T只能是Collection接口的实现类,传入非Collection接口编译会出错
注意:<T extends Collection>这里的限定使用关键字extends,后面可以是类也可以是接口。但这里的extends已经不是继承的含义了,应该理解为T类型是实现Collection接口的类型,或者T是继承了XX类的类型。
下面继续对上面的例子改进,我只要实现了集合接口的类型:
public class CollectionGenFoo<T extends Collection> {
    private T x;
 
    public CollectionGenFoo(T x) {
        this.x = x;
    }
 
    public T getX() {
        return x;
    }
 
    public void setX(T x) {
        this.x = x;
    }
}
实例化的时候可以这么写:
public class CollectionGenFooDemo {
    public static void main(String args[]) {
        CollectionGenFoo<ArrayList> listFoo = null;
        listFoo = new CollectionGenFoo<ArrayList>(new ArrayList());
        // 出错了,不让这么干。
        // 原来作者写的这个地方有误,需要将listFoo改为listFoo1
        // 需要将CollectionGenFoo<Collection>改为CollectionGenFoo<ArrayList>
        // CollectionGenFoo<Collection> listFoo1 = null;
        // listFoo1=new CollectionGenFoo<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所拥有的特性了
}

通配符泛型

为了解决类型被限制死了不能动态根据实例来确定的缺点,引入了“通配符泛型”,针对上面的例子,使用通配泛型格式为<? extends Collection>,“?”代表未知类型,这个类型是实现Collection接口。那么上面实现的方式可以写为:
public class CollectionGenFooDemo {
    public static void main(String args[]) {
        CollectionGenFoo<ArrayList> listFoo = null;
        listFoo = new CollectionGenFoo<ArrayList>(new ArrayList());
        // 出错了,不让这么干。
        // 原来作者写的这个地方有误,需要将listFoo改为listFoo1
        // CollectionGenFoo<Collection> listFoo1 = null;
        // listFoo1=new CollectionGenFoo<ArrayList>(new ArrayList());
        System.out.println("实例化成功!");
    }
}
注意:
1、如果只指定了<?>,而没有extends,则默认是允许Object及其下的任何Java类了。也就是任意类。
2、通配符泛型不单可以向下限制,如<? extends Collection>,还可以向上限制,如<? super Double>,表示类型只能接受Double及其上层父类类型,如Number、Object类型的实例。
3、泛型类定义可以有多个泛型参数,中间用逗号隔开,还可以定义泛型接口,泛型方法。这些都与泛型类中泛型的使用规则类似。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------

泛型方法

我们可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。

下面是定义泛型方法的规则:

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的<E>)。
  • 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
  • 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
  • 泛型方法方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等)。

实例

下面的例子演示了如何使用泛型方法打印不同字符串的元素:

public class GenericMethodTest{   // 泛型方法 printArray                            public static <E> void printArray(E[] inputArray )   {      // 输出数组元素                     for (E element : inputArray ){                    System.out.printf( "%s ", element );         }         System.out.println();    }    public static void main( String args[] )    {        // 创建不同类型数组: Integer, Double 和 Character        Integer[] intArray = { 1, 2, 3, 4, 5 };        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };        Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };        System.out.println( "Array integerArray contains:" );        printArray( intArray  ); // 传递一个整型数组        System.out.println( "\nArray doubleArray contains:" );        printArray( doubleArray ); // 传递一个双精度型数组        System.out.println( "\nArray characterArray contains:" );        printArray( charArray ); // 传递一个字符型型数组    } }

编译以上代码,运行结果如下所示:

Array integerArray contains:1 2 3 4 5 6 Array doubleArray contains:1.1 2.2 3.3 4.4 Array characterArray contains:H E L L O


泛型类

泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分

和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。

实例

如下实例演示了我们如何定义一个泛型类:

public class Box<T> {   private T t;   public void add(T t) {    this.t = t;  }   public T get() {    return t;  }   public static void main(String[] args) {     Box<Integer> integerBox = new Box<Integer>();     Box<String> stringBox = new Box<String>();        integerBox.add(new Integer(10));     stringBox.add(new String("Hello World"));      System.out.printf("Integer Value :%d\n\n", integerBox.get());     System.out.printf("String Value :%s\n", stringBox.get());  }}

编译以上代码,运行结果如下所示:

Integer Value :10 String Value :Hello World





有界的类型参数:

可能有时候,你会想限制参数的类型种类范围。例如,一个操作数字的方法可能只希望接受Number或者Number子类的实例

这就是有界类型参数的目的。

要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界(<T extends Comparable<T>>)

实例

下面的例子演示了"extends"如何使用在一般意义上的意思"extends"(类)或者"implements"(接口)。该例子中的泛型方法返回三个可比较对象的最大值。

public class MaximumTest{   // 比较三个值并返回最大值   public static <T extends Comparable<T>> T maximum(T x, T y, T z)   {                           T max = x; // 假设x是初始最大值      if ( y.compareTo( max ) > 0 ){         max = y; //y 更大      }      if ( z.compareTo( max ) > 0 ){         max = z; // 现在 z 更大                 }      return max; // 返回最大对象   }   public static void main( String args[] )   {      System.out.printf( "Max of %d, %d and %d is %d\n\n",                   3, 4, 5, maximum( 3, 4, 5 ) );       System.out.printf( "Maxm of %.1f,%.1f and %.1f is %.1f\n\n",                   6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ) );       System.out.printf( "Max of %s, %s and %s is %s\n","pear",         "apple", "orange", maximum( "pear", "apple", "orange" ) );   }}

编译以上代码,运行结果如下所示:

Maximum of 3, 4 and 5 is 5 Maximum of 6.6, 8.8 and 7.7 is 8.8 Maximum of pear, apple and orange is pear
0 0