Java泛型

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


为什么使用泛型

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

  在使用泛型前,存入集合中的元素可以是任何类型的,当从集合中取出时,所有的元素都是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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值