`

java泛型详解

 
阅读更多

为什么使用泛型

泛型能使类型转换的错误在编译时被发现,从而增加程序的健壮性。看一个例子

public class Box{  
    private Object object;  
   
    public void set(Object object) {  
            this.object= object;  
       }  
    public Object get() {  
             return object;  
       }  
}  

 其中set方法可以接受任何java对象作为参数,加入在某个地方使用该类,预期object属性是Integer类型,但是实际set的是String类型,就会抛出一个运行时错误,这个错误在编译阶段无法检测。如:

Box box = new Box();
box.set("abc");
Integer a = (Integer)box.get();//编译不报错,运行时报ClassCastException

 使用泛型改造以上代码

public class Box<T>{  
    private T t;  
    public void set(T t) {  
            this.t= t;  
       }  
    public T get() {  
             return t;  
       }  
}  

 当我们使用这个Box类时会指定T的类型,该类型参数可以是类,接口,数组等,但是不能是基本数据类型。比如:

Box<Integer> box = new Box<Integer>; //指定了类型类型为Integer  
//box.set("abc");  该句在编译时就会报错  
box.set(new Integer(2));  
Integer a = box.get();  //不用转换类型  

可以看到,泛型还免除了我们手动进行类型转换。 

在引入泛型机制前,要在方法中支持多个数据类型,需要对方法进行重载,在引入泛型后可以更简洁的解决问题,更进一步可以定义多个参数以及返回值之间的关系。例如:

public void write(Integer i, Integer[] ia);  
public void write(Double  d, Double[] da);  
public void write(Long l, Long[] la);  

 的泛型版本为

public <T> void write(T t,T[] ta);

 总体来说,泛型机制能够在定义类、接口、方法时把“类型”作为一个参数,有点类似方法中的形参,如此我们就能通过不同的输入参数来实现方法的重用。不同于形参的是,泛型“参数”的输入是类型。

 

命名规则

类型参数的命名有一套默认规则,为了提高代码的维护性和可读性,强烈建议遵循这些规则。JDK中随处可见这些命名规则的应用。

E-Element

K-Key

V-Value

N-Number

T-Type

S,U,V etc. - 第二个、第三个、第四个参数

 

泛型原理简述

java中的泛型是个语法糖,作用发生在编译阶段。在编译过程中,正确检验泛型结果后,会将其擦除,并在对象进入和离开边界处添加类型检查和类型转换的方法。因此,成功编译后的class文件是不包含任何泛型信息的。

可以用一个反射的例子来证明

ArrayList<Integer> list = new ArrayList<Integer>();
		Class c = list.getClass();
		try {
			Method method = c.getMethod("add", Object.class);
			method.invoke(list, "abc");
			System.out.println(list.get(0));
		} catch (Exception e){
		}

 能正确打印出“abc”

 

 

泛型类与泛型方法的使用

泛型类的基本写法

class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
  private 泛型标识 /*(成员变量类型)*/ var; 
  .....

  }
}

 

泛型类,是在实例化类的时候指明泛型的具体类型;而泛型方法是在调用方法的使用才指明泛型具体类型

泛型方法基本写法

/**
 * 说明:
 *     1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
 *     2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
 *     3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
 *   
 */
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
  IllegalAccessException{
        T instance = tClass.newInstance();
        return instance;
}

 

 

泛型的上下限和通配符

Integer是Number的子类,那么在Box<Number>作为形参的方法中能不能使用Box<Integer>呢?在上面的Box类中添加方法

public  static void showValue(Box<Number> box){
		System.out.println(box.getObject());
	}

 在该类中main方法编写代码

public static void main(String[] args) {
		Box<Integer> box = new Box<Integer>();
		box.setObject(3);
		showValue(box);//出错
	}

 看来不行?但是又想只要是泛型具体类型只要是Number子类都能用这个方法要怎么办呢?总不能每个子类写一遍方法把。

这里可以用通配符“?”

将上面静态方法改成

public  static void showValue(Box<? extends Number> box){
		System.out.println(box.getObject());
	}

 问题解决,<? extends Number> 代表可以接受Number以及他的子类作为类型参数,这种声明方式称为上限通配符。<? super Number>  代表可以接受Number以及他的父类作为类型参数。称为下限通配符。

 

?单独使用时称作无限定通配符。通常一下两种情况会使用无限定通配符:

1.编写一个方法,可以使用Object类中提供的功能来实现;

2.代码实现的功能与类型参数无关,比如List.clear()、List.size()等方法,还有经常使用的Class<?>方法,他们实现的功能都与类型参数无关。

通配符可以看做类型参数的实参

 

 

泛型使用的几个注意点

(1)不能用基本类型实例化类型参数

例如

 
  1. class Pair<K,V> {  
  2.    
  3.     private K key;  
  4.     private V value;  
  5.    
  6.     public Pair(K key, V value) {  
  7.         this.key = key;  
  8.         this.value = value;  
  9.     }  
  10.    
  11.     // ...  
  12. }  


当创建一个Pair类时,不能用基本类型来替代K,V两个类型参数。

 
  1. Pair<int,char> p = new Pair<>(8, 'a'); // 编译错误  
  2. Pair<Integer,Character> p = new Pair<>(8, 'a'); //正确写法  

 

 

(2)不可实例化类型参数

例如:

 
  1. public static <E> void append(List<E> list) {  
  2.     E elem = new E();  // 编译错误  
  3.     list.add(elem);  
  4. }  

 

 

但是,我们可以通过反射实例化带有类型参数的对象:

 
  1. public static <E> void append(List<E> list, Class<E> cls) throws Exception{  
  2.     E elem = cls.newInstance();   // 正确  
  3.     list.add(elem);  
  4. }  
  5.    
  6. List<String> ls = new ArrayList<>();  
  7. append(ls,String.class);  //传入类型参数的Class对象  

 

 

(3)不能在静态字段上使用泛型

通过一个反例来说明:

 
  1. public class MobileDevice <T> {  
  2.     private static T os;  //假如我们定义了一个带泛型的静态字段  
  3.    
  4.     // ...  
  5. }  
  6.    
  7. MobileDevice<Smartphone> phone = new MobileDevice<>();  
  8. MobileDevice<Pager> pager = new MobileDevice<>();  
  9. MobileDevice<TabletPC> pc = new MobileDevice<>();  

 

因为静态变量是类变量,被所有实例共享,此时,静态变量os的真实类型是什么呢?显然不能同时是Smartphone、Pager、TabletPC。

这就是为什么不能在静态字段上使用泛型的原因。

 

(4)不能对带有参数化类型的类使用cast或instanceof方法

 
  1. public static<E> void rtti(List<E> list) {  
  2.     if (list instanceof ArrayList<Integer>){  // 编译错误  
  3.         // ...  
  4.     }  
  5. }  

 

传给该方法的参数化类型集合为:

S = { ArrayList<Integer>,ArrayList<String> LinkedList<Character>, ... }

运行环境并不会跟踪类型参数,所以分辨不出ArrayList<Integer>与ArrayList<String>,我们能做的至多是使用无限定通配符来验证list是否为ArrayList:

 
  1. public static void rtti(List<?> list) {  
  2.     if (list instanceof ArrayList<?>){  // 正确  
  3.         // ...  
  4.     }  
  5. }  

 

 

同样,不能将参数转换成一个带参数化类型的对象,除非它的参数化类型为无限定通配符(<?>):

 
  1. List<Integer> li = new ArrayList<>();  
  2. List<Number>  ln = (List<Number>) li;  // 编译错误  

 

 

当然,如果编译器知道参数化类型肯定有效,是允许这种转换的:

 
  1. List<String> l1 = ...;  
  2. ArrayList<String> l2 = (ArrayList<String>)l1;  // 允许转变,类型参数没变化  

 

 

(5)不能创建带有参数化类型的数组

 

例如:

 
  1. List<Integer>[] arrayOfLists = new List<Integer>[2]; // 编译错误  

 

 

下面通过两段代码来解释为什么不行。先来看一个正常的操作:

 
  1. Object [] strings= new String[2];  
  2. string s[0] ="hi";   // 插入正常  
  3. string s[1] =100;    //报错,因为100不是String类型  

 

 

同样的操作,如果使用的是泛型数组,就会出问题:

 

[java] view plain copy
 
  1. Object[] stringLists = new List<String>[]; // 该句代码实际上会报错,但是我们先假定它可以执行  
  2. string Lists[0] =new ArrayList<String>();   // 插入正常  
  3. string Lists[1] =new ArrayList<Integer>();  // 该句代码应该报ArrayStoreException的异常,但是运行环境探测不到  

 

 

(6)不能创建、捕获泛型异常

泛型类不能直接或间接继承Throwable类

 

[java] view plain copy
 
  1. class MathException<T> extends Exception { /* ... */ }    //编译错误  
  2.    
  3. class QueueFullException<T> extends Throwable { /* ... */} // 编译错误  


方法不能捕获泛型异常:

 

 

[java] view plain copy
 
  1. public static<T extends Exception, J> void execute(List<J> jobs) {  
  2.     try {  
  3.         for (J job : jobs)  
  4.             // ...  
  5.     } catch (T e) {   // 编译错误  
  6.         // ...  
  7.     }  
  8. }  


但是,我们可以在throw子句中使用类型参数:

 

 

[java] view plain copy
 
  1. class Parser<T extends Exception> {  
  2.     public void parse(File file) throws T{     // 正确  
  3.         // ...  
  4.     }  
  5. }  

 

 

(7)不能重载经过类型擦除后形参转化为相同原始类型的方法

先来看一段代码:

 

[java] view plain copy
 
  1. List<String> l1 = new ArrayList<String>();  
  2. List<Integer> l2 = new ArrayList<Integer>();  
  3. System.out.println(l1.getClass()== l2.getClass());  

打印结果可能与我们猜测的不一样,打印出的是true,而非false,因为一个泛型类的所有实例在运行时具有相同的运行时类(class),而不管他们的实际类型参数。

 

事实上,泛型之所以叫泛型,就是因为它对所有其可能的类型参数,有同样的行为;同样的类可以被当作许多不同的类型。

认识到了这一点,再来看下面的例子:

 

[java] view plain copy
 
  1. public class Example {  
  2.     public void print(Set<String> strSet){ }  //编译错误  
  3.     public void print(Set<Integer> intSet) { }  //编译错误  
  4. }  

 

 

因为Set<String>与Set<Integer>本质上属于同一个运行时类,在经过类型擦出以后,上面的两个方法会共享一个方法签名,相当于一个方法,所以重载出错。

 

 

2.

分享到:
评论

相关推荐

    java泛型详解.pdf

    java泛型详解.pdf

    Java 泛型详解

    Java泛型详解,Java泛型详解,Java泛型详解,Java泛型详解

    思维导图之Java泛型详解

    思维导图之Java泛型详解

    JAVA泛型详解DD

    【Java泛型详解】 Java泛型是Java SE 5.0引入的新特性,它允许在类、接口和方法声明中使用类型参数,从而增强了代码的类型安全性和重用性。泛型的主要目标是提高代码的复用性,减少类型转换,并在编译时捕获类型...

    java泛型详解java泛型详解.doc

    Java 泛型是一种强大的语言特性,它允许在类、接口和方法中使用类型参数,以实现类型的安全性和代码的重用性。泛型是 Java 从 JDK 5.0 版本开始引入的重要特性,目的是增强类型系统,提高程序的类型安全性,减少运行...

    JAVA泛型详解[参考].pdf

    还有类型擦除,这是Java泛型的一个特性,意味着在运行时,所有的泛型信息都会被移除,因此泛型并不影响程序的性能,但它提供了编译时的类型检查。 在集合框架中,泛型发挥了重要作用。Java的List、Set、Map等集合类...

    JAVA泛型详解

    ### JAVA泛型详解 #### 泛型:打破具体类型的枷锁 在传统的Java编程中,类和方法的设计往往受限于具体的类型,无论是基本数据类型还是自定义的类。这种限制在面对多变的需求时显得尤为棘手,因为它迫使开发者重复...

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

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

    深入理解java泛型详解

    让我们深入探讨一下Java泛型的各个方面。 首先,泛型(Generic type)是一种在定义类、接口或方法时,使用类型参数的方式。类型参数就像是方法的形参,但用于类型而非值。例如,当我们创建一个泛型列表`List&lt;T&gt;`,`...

    java 泛型的使用 详细讲解

    ### Java泛型的使用详细讲解 #### 一、引言 在Java开发中,泛型是一种重要的语言特性,它能够帮助开发者在不增加代码量的情况下处理多种数据类型,同时还能保持代码的清晰度和可读性。本文将详细介绍Java泛型的...

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

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

    很好的Java泛型的总结

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

    Java深度历险之Java泛型.docx

    ### Java泛型详解 #### 一、Java泛型概述 Java泛型(Generics)是Java SE 5.0引入的一项重要新特性,它允许开发者在定义类、接口或方法时使用类型参数(Type Parameters)。类型参数在使用时可以用具体的类型来...

Global site tag (gtag.js) - Google Analytics