`
chenzehe
  • 浏览: 538183 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Java泛型

 
阅读更多

1、 泛型为JDK1.5 之后出现的新特性,类中的属性类型由外部定义,而且在声明类的时候应该采取下面形式:

class 类名称 < 泛型类型,泛型类型, ...>{

}

如下类Point

package com.chenzehe.test;

public class Point<T> { // T为泛型类型,也可以由其它字母声明
     private T x ;
     private T y ;

     public T getX() {
         return x ;
     }

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

     public T getY() {
         return y ;
     }

     public void setY(T y) {
         this . y = y;
     }
} 
 

程序中属性的类型不再由程序中设定,而是由实例化对象的时候在外部指定。

    在没有泛型之前,从集合中读取到每个对象都必须进行转换,如果不小心插入了类型错误的对象,只有在运行到转换的地方才会出现错误。有了泛型之后,可以告诉编译器每个集合中接受哪些对象类型,在编译的时候就能知道插入的类型是否正确。

 

2、 泛型擦除

如果在使用泛型的时候没有指定泛型类型,则表示擦除泛型,class<T> 将按照 Object 类型进行接收,保证程序不出错,如下:

package com.chenzehe.test;

public class Test {
     public static void main(String[] args) {
         Point point = new Point ();
         point.setX(10) ;
         point.setY(20) ;
     }
} 
 

但是该程序会出现警告信息,要想去掉警告信息,则指定 T Object 类型,如
Point <Object> point = new Point <Object>();

但是这种程序没有任何意义,在开发中建议不要擦除泛型。

      每全泛型都定义一个原生态类型(raw type),即不带任何实际类型参数的泛型名称,如List<T>相对应的原生态类型就是List,原生态类型就像从类型声明中删除所有泛型信息一样。

     在jdk 1.5之前,有如下代码:

private final Collection stamps=... ;

      如果不小心将一个Coin类型对象放到stamp集合中,这种错误在编译时不会出现任何错误:

stamps.add(new Coin(...));
      直到从stamp集合中获取coin时才会出现错误:
for (Iterator i = stamps.iterator() ; i.hasNext();){
    Stamp s = (Stamp) i.next();
}
      有了泛型,就可以有如下声明:
private final Collection<Stamp> stamps = ... ;

      如果插入的对象类型不是Stamp编译器就会报错而不用等到运行到该行代码才出错。

      如上所述,如果不提供类型参数,使用集合类型和其它泛型也仍然是合法的,但是不应该这么做,如果使用原生态类型,就失掉了泛型在安全性和表述性方面的优势。既然不应该这么做,但是为什么jdk中还允许呢,这是为了提供兼容性,因为泛型出现的时候,已经存在没有使用泛型的大量java代码,人们认为让所有这些代码保持合法,并且能够与使用泛型的代码互用。

 

3、 泛型通配符?

如下程序:


Integer integer = new Integer(10);
Object obj = integer; 
 

可以进行向上转型,而对于泛型中定义的类型则不能向上转型,如:


Point<Object> objectPoint = new Point<Object>();
Point<Integer> integerPoint = new Point<Integer>();
objectPoint = integerPoint ; // 不能向上转型 
 

如下代码,在调用 print函数时不能向上转型而出错:


package com.chenzehe.test;

public class Test {
     public static void main(String[] args) {
         Point<Object> objectPoint = new Point<Object>();
         Point<Integer> integerPoint = new Point<Integer>();
         print (objectPoint);
         print (integerPoint); //不能向上转型,调用出错
     }

     public static void print(Point<Object> p) {
         System. out .println(p.getX());
         System. out .println(p.getY());
     }
} 
 

要想实现调用,则可以在声明函数参数时使用擦除泛型的方式,如下:


public static void print(Point p) {
     System. out .println(p.getX());
     System. out .println(p.getY());
} 
 

      这样 print (integerPoint); 就可以实现调用,但是 print函数声明还是有警告信息,并且在使用时转型还可能出现异常,最好的解决方法是声明 print 函数时使用泛型通配符?,如:


public static void print(Point< ? > p) {
     System. out .println(p.getX());
     System. out .println(p.getY());
} 
 

      ?表示可以接收任何泛型,但是使用泛型通配符时,该类型对象只有可读属性,没有可写属性,如上面p.getX() 方法可以使用,但是 set 方法不能使用,如 p. setX (123); 不可以使用。

      泛型通配符和原生态类型间有什么区别?泛型通配符是类型安全的,原生态类型是不安全的。

 

      虽然不应该在代码中使用像List这样的原生态类型,使用参数化的类型以允许插入任意对象,如List<Object>,这样还是可以的。不严格的来说,原生态类型逃避了类型检察,而List<Object>告诉编译器他能够持有任意类型的对象。虽然你可以将List<String>传递给类型List的参数,但是不能将他传递给类型List<Object>的参数。

 

4、 泛型上限

指泛型类型可以指定的最大的父类,如将上限设置为Number 类型,则此时可以接收的泛型类型只能为 Number 或其子类 Integer 等,语法为:

Class <T extends 父类 >

如将上面Point 类泛型设置为最后只能是 Number 类型:


package com.chenzehe.test;

    public class Point< T extends Number> { // T 只能为 Number类型或其子类
         private T x ;
         private T y ;

         public T getX() {
             return x ;
         }

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

         public T getY() {
             return y ;
         }

         public void setY( T y) {
             this . y = y;
         }
} 
 

在使用该泛型时, Point<Integer> integerPoint = new Point<Integer>(); 可以使用,而 Point< String > stringPoint = new Point< String >(); 则不能使用。

泛型上限也可以在方法上使用,如下面在接收参数时使用:


package com.chenzehe.test;

public class Test {
     public static void main(String[] args) {
         Point<Integer> integerPoint = new Point<Integer>();
         print (integerPoint);
     }

     public static void print(Point<? extends Number > p) {
         System. out .println(p.getX());
         System. out .println(p.getY());
     }
}

 

 

5、 泛型下限

指泛型只能设置其具体的类或者父类,使用super 关键字定义,在定义泛型类型时不能使用泛型下限,只有在使用时才能定义下限,如:

Point类要定义成 public class Point<T super Integer> 则报错,但是在 print方法中可以定义,如:


public static void print(Point<? super Integer > p) {
     System. out .println(p.getX());
     System. out .println(p.getY());
} 
 

 

6、 泛型接口

泛型不但可以作用在类上,还可以作用在接口上,定义如下:

interface 接口名称 < 泛型类型 , 泛型类型 ,...>

如下实例定义泛型接口IDemo


package  com.chenzehe.test;
public   interface  IDemo<T>  {
     void   print ( T param ) ;
} 
 

有两种方式可以实现该接口,方式一:实现类不指定具体泛型类型


package  com.chenzehe.test;
public   class  DemoImpl<T>  implements  IDemo<T>  {
     public   void  print ( T  param )   {
         System. out .println ( "param = "  +  param ) ;
     }
} 
 

方式二:实现类指定具体泛型类型


package  com.chenzehe.test;
public   class  DemoImpl  implements  IDemo< String >  {
     public   void  print ( String  param )   {
        System. out .println ( "param = "  + param ) ;
    }
} 
 

 

7、 泛型方法

泛型还可以定义在方法上,在方法中使用泛型,该方法所在的类不一定是泛型操作类。如:


package  com.chenzehe.test;
public   class  Demo  {
    public  <T> T print ( T  param )   {
         return   param ;
     }
} 
 

也可以将方法返回值定义成泛型数组:


public  <T>  T []  Print ( T... params )   {
     return  params;
} 
 

 

8、 泛型嵌套设置

定义Info 类:


package  com.chenzehe.test;
public   class  Info<T>  {
     private  T param ;

     public  T getParam ()   {
         return   this . param ;
     }

     public   void  setParam ( T param )   {
         this . param  = param;
     }
} 
 

定义Person 类:


package  com.chenzehe.test;
public   class  Person< T >  {
     private   T info ;

     public   T  getInfo ()   {
         return   this . info ;
     }

     public   void  setInfo ( T  info )   {
         this . info  = info;
     }
} 
 

测试类:


package  com.chenzehe.test;
public   class  Test  {
     public   static   void  main ( String []  args )   {
         Person<Info<String>> person =  new  Person<Info<String>> () ;
         person.setInfo ( new  Info<String> ()) ;
         person.getInfo () .setParam ( "chenzehe" ) ;
         System. out .println ( person.getInfo () .getParam ()) ;
     }
} 
 

 

 

9、 消除非受检警告

    在使用泛型编程时,会遇到许多编译器警告:非受检强制转化警告(unchecked cast warnings)、非受检方法调用警告、以及非受检转换警告(unchecked conversion warnings)。

      有许多非受检警告很容易消除,如下面的警告:

Set<Lark> exaltation = new HashSet();
      编译器会提示出错,可以改成下面就消除警告
Set<Lark> exaltation = new HashSet<Lark>();
    如果无法消除警告,同时可以证明引起警告的代码类型是安全的,只有在这种情况下才可以使用一个@SuppressWarnings("unchecked")注解来禁止这个警告。SuppressWarnings可以使用在任何粒度的级别中,从局部变量到整个类都可以,应该在尽可能小的范围中使用SuppressWarnings注解。
   
 

10、列表优先于数组

      数组与泛型相比有两个重要的不同点,首先,数组是协变的(covariant),表示如果Sub为Super的子类型,那么数组类型Sub[]就是Super[]的子类型,相反泛型是不可变的(invariant),表示对于任意两个不同的类型Type1和Type2,List<Type1>即不是List<Type2>的子类型,也不是List<Type2>的父类型。

      下在代码是合法的:

// Fails at runtime
Object[] objectsArray = new Long[1];
objectsArray[0] = "I don't fit in";  // Throws ArrayStoreException
      下面代码是不合法的:
// Won't compile!
List<Object> ol = new ArrayList<Long>(); // Incompatible types
ol.add("I don't fit in");
     上面两种方法都不能将String存到Long容器中,但是使用数组,则只有在运行时才发现异常,利用列表,则可以在编辑时就发现错误。
 
      数组和泛型第二大区别在于,数组是具体化的(reified)。因此数组会在运行时才知道并检查它们的元素类型约束,而泛型则是擦除(erasure)来实现的,因此泛型只在编译的时候强化他们的类型信息,并在运行时丢弃(或擦除)它们的类型信息。
      由于上述区别,因此数组和泛型不能很好地混合使用。例如创建泛型、参数化类型或者类型参数的数组都是非法的,下面表达式都是非法的:new List<E>[]、new List<String>[]、new E[],这些在编译时都会导致一个generic array creation错误。
 
10、优先考虑泛型
      一般来说,将集合声明参数化,以及使用JDK所提供的泛型和泛型方法都不太难,编写自己的泛型会比较困难此,如下面代码是一个堆栈的实现:
import java.util.Arrays;
import java.util.EmptyStackException;

/**
 * Huisou.com Inc. Copyright (c) 2011-2012 All Rights Reserved.
 */
public class Stack {
	private Object[]			elements;
	private int					size						= 0;
	private static final int	DEFAULT_INITIAL_CAPACITY	= 16;
	
	public Stack() {
		elements = new Object[DEFAULT_INITIAL_CAPACITY];
	}
	
	public void push(Object e) {
		ensureCapacity();
		elements[size++] = e;
	}
	
	public Object pop() {
		if (0 == size) {
			throw new EmptyStackException();
		}
		Object result = elements[--size];
		elements[size] = null;// 不设置为null可能导致内存溢出
		return result;
	}
	
	public boolean isEmpty() {
		return size == 0;
	}
	
	private void ensureCapacity() {
		if (elements.length == size) {
			elements = Arrays.copyOf(elements, 2 * size + 1);
		}
	}
}
 
把该Stack类改成用泛型实现如下:
import java.util.Arrays;
import java.util.EmptyStackException;

/**
 * Huisou.com Inc. Copyright (c) 2011-2012 All Rights Reserved.
 */
public class Stack<E> {
	private E[]			elements;
	private int					size						= 0;
	private static final int	DEFAULT_INITIAL_CAPACITY	= 16;
	
	public Stack() {
		elements = new E[DEFAULT_INITIAL_CAPACITY];
	}
	
	public void push(E e) {
		ensureCapacity();
		elements[size++] = e;
	}
	
	public E pop() {
		if (0 == size) {
			throw new EmptyStackException();
		}
		E result = elements[--size];
		elements[size] = null;// 不设置为null可能导致内存溢出
		return result;
	}
	
	public boolean isEmpty() {
		return size == 0;
	}
	
	private void ensureCapacity() {
		if (elements.length == size) {
			elements = Arrays.copyOf(elements, 2 * size + 1);
		}
	}
}
 
上面代码在
elements = new E[DEFAULT_INITIAL_CAPACITY];
报错,不能把泛型和数组混合使用,要解决该问题有两种方法:
 
第一种,直接绕过创建泛型数组的禁令,创建一个Object数组,然后将它转换成泛型数组类型,如下
elements =(E[]) new Object[DEFAULT_INITIAL_CAPACITY];
 该代码编辑器会有警告信息,确保未受检的转换不会危及到程序的类型安全信息,上面代码中,只有push(E e)方法会改会elements中元素类型,而push方法只处理E类型信息,所以可以认为是类型安全的,在该方法中使SuppressWarnings来消除警告信息,如下:
	@SuppressWarnings("unchecked")
	public Stack() {
		elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
	}
 
第二种,将elements域的类型从E[]改成Object[]类型,如下:
private Object[]			elements;

public Stack() {
	elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
此种方法在pop()方法中也会报错:
E result = elements[--size];
 把数组中取得的元素Object转换成E,但是会有一个警告信息:
E result = (E) elements[--size];
 由于E是一个不可具体化的(non-reifiable)类型,编译器无法在编译的时候进行转换检验,但是可以证明未受检的类型是安全的,所以可以在该转换上加上SuppressWarnings消除警告信息,注意没必要在pop方法上使用SuppressWarnings,如下:
	public E pop() {
		if (0 == size) {
			throw new EmptyStackException();
		}
		@SuppressWarnings("unchecked")
		E result = (E) elements[--size];
		elements[size] = null;// 不设置为null可能导致内存溢出
		return result;
	}
 
上面两种方法使用哪种都可以,但是禁止数组类型的未受检比禁止标量类型的更加危险,所以建议采用第二种方法,但是在比Stack更实际的应用中,或许代码中有多个地方需要从数组中读取元素,因此第二种方案需要多次进行转换成E,而不是只转换成E[],这也是第一种方案更常用的原因。
 
11、泛型的本质
     C#泛型类在编译时,先生成中间代码IL,通用类型T只是一个占位符。在实例化类时,根据用户指定的数据类型代替T并由即时编译器(JIT)生成本地代码,这个本地代码中已经使用了实际的数据类型,等同于用实际类型写的类。C#里面的泛型无论是在程序的源代码中、编译后的IL中、还是运行时的CLR中都是切实存在的,List<Integer>与List<String>就是两个不同的泛型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,是真实的泛型。
     Java中的泛型不一样,是Java的语法糖,它只在程序源代码中存在,编译后的字节码文件中已经已经被替换为原生类型,并且根据需要在相应的地方加入的强制类型转换,因此对于Java来说,List<Integer>与List<String>实现上是同一个类型,经过编译后都成为List类型,所以说Java语言的泛型就是种语法糖,Java的泛型实现为类型擦除,为伪泛型。
 
 
    

 

 

 

分享到:
评论

相关推荐

    Java泛型的用法及T.class的获取过程解析

    Java泛型的用法及T.class的获取过程解析 Java泛型是Java编程语言中的一种重要特性,它允许开发者在编写代码时指定类型参数,从而提高代码的灵活性和可读性。本文将详细介绍Java泛型的用法 及T.class的获取过程解析...

    Java泛型三篇文章,让你彻底理解泛型(super ,extend等区别)

    Java 泛型详解 Java 泛型是 Java SE 5.0 中引入的一项特征,它允许程序员在编译时检查类型安全,从而减少了 runtime 错误的可能性。泛型的主要优点是可以Reusable Code,让程序员编写更加灵活和可维护的代码。 ...

    Java泛型应用实例

    Java泛型是Java编程语言中的一个强大特性,它允许我们在定义类、接口和方法时指定类型参数,从而实现代码的重用和类型安全。在Java泛型应用实例中,我们可以看到泛型如何帮助我们提高代码的灵活性和效率,减少运行时...

    很好的Java泛型的总结

    Java泛型机制详解 Java泛型是Java语言中的一种机制,用于在编译期检查类型安全。Java泛型的出现解决了Java早期版本中类型安全检查的缺陷。Java泛型的好处是可以在编译期检查类型安全,避免了运行时的...

    java 泛型类的类型识别示例

    综上所述,虽然Java泛型在编译后会进行类型擦除,但通过上述技巧,我们仍然能够在运行时获得关于泛型类实例化类型的一些信息。在实际开发中,这些方法可以帮助我们编写更加灵活和安全的代码。在示例文件`GenericRTTI...

    java泛型技术之发展

    Java泛型是Java编程语言中的一个关键特性,它在2004年随着Java SE 5.0的发布而引入,极大地增强了代码的类型安全性和重用性。本篇文章将深入探讨Java泛型的发展历程、核心概念以及其在实际开发中的应用。 1. **发展...

    SUN公司Java泛型编程文档

    Java泛型是Java编程语言中的一个关键特性,它在2004年随着JDK 5.0的发布被引入。这个特性极大地提高了代码的类型安全性和可读性,减少了在运行时出现ClassCastException的可能性。SUN公司的Java泛型编程文档,包括...

    java 泛型接口示例

    下面我们将详细探讨Java泛型接口的相关知识点。 1. **泛型接口的定义** 泛型接口的定义方式与普通接口类似,只是在接口名之后添加了尖括号`&lt;T&gt;`,其中`T`是一个类型参数,代表某种未知的数据类型。例如: ```java...

    java 泛型方法使用示例

    下面我们将深入探讨Java泛型方法的概念、语法以及使用示例。 **一、泛型方法概念** 泛型方法是一种具有类型参数的方法,这些类型参数可以在方法声明时指定,并在方法体内部使用。与类的泛型类似,它们提供了编译时...

    java泛型的内部原理及更深应用

    Java泛型是Java编程语言中的一个强大特性,它允许在定义类、接口和方法时使用类型参数,从而实现参数化类型。这使得代码更加安全、可读性更强,并且能够减少类型转换的必要。在“java泛型的内部原理及更深应用”这个...

    JAVA泛型加减乘除

    这是一个使用JAVA实现的泛型编程,分为两部分,第一部分创建泛型类,并实例化泛型对象,得出相加结果。 第二部分用户自行输入0--4,选择要进行的加减乘除运算或退出,再输入要进行运算的两个数,并返回运算结果及...

    Java 泛型擦除后的三种补救方法

    Java 泛型是一种强大的工具,它允许我们在编程时指定变量的类型,提供了编译时的类型安全。然而,Java 的泛型在运行时是被擦除的,这意味着在运行时刻,所有的泛型类型信息都会丢失,无法直接用来创建对象或进行类型...

    java泛型学习ppt

    "Java 泛型学习" Java 泛型是 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的...

    Java泛型使用详细分析.pdf

    Java 泛型使用详细分析 Java 泛型是 Java 语言中的一种类型系统特性,允许开发者在编译期检查类型安全,以避免在运行时出现类型相关的错误。在本文中,我们将详细介绍 Java 泛型的使用方法和实现原理。 一、泛型的...

    Java泛型技术之发展.pdf

    Java泛型是Java编程语言中的一个关键特性,它在2004年随着Java SE 5.0的发布而引入,极大地增强了代码的类型安全性和重用性。本篇文章将深入探讨Java泛型的发展历程、核心概念以及其在实际开发中的应用。 1. **发展...

    面试必须资料java泛型攻略、

    #### 一、什么是Java泛型? Java泛型(Generics)是一种在编译时确保类型安全的机制,它允许程序员编写类型安全的通用类或方法,而无需进行显式的类型转换。在Java 1.5引入泛型之前,集合类(如`ArrayList`)只能...

Global site tag (gtag.js) - Google Analytics