为什么使用泛型
泛型能使类型转换的错误在编译时被发现,从而增加程序的健壮性。看一个例子
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)不能用基本类型实例化类型参数
例如
- class Pair<K,V> {
- private K key;
- private V value;
- public Pair(K key, V value) {
- this.key = key;
- this.value = value;
- }
- // ...
- }
当创建一个Pair类时,不能用基本类型来替代K,V两个类型参数。
- Pair<int,char> p = new Pair<>(8, 'a'); // 编译错误
- Pair<Integer,Character> p = new Pair<>(8, 'a'); //正确写法
(2)不可实例化类型参数
例如:
- public static <E> void append(List<E> list) {
- E elem = new E(); // 编译错误
- list.add(elem);
- }
但是,我们可以通过反射实例化带有类型参数的对象:
- public static <E> void append(List<E> list, Class<E> cls) throws Exception{
- E elem = cls.newInstance(); // 正确
- list.add(elem);
- }
- List<String> ls = new ArrayList<>();
- append(ls,String.class); //传入类型参数的Class对象
(3)不能在静态字段上使用泛型
通过一个反例来说明:
- public class MobileDevice <T> {
- private static T os; //假如我们定义了一个带泛型的静态字段
- // ...
- }
- MobileDevice<Smartphone> phone = new MobileDevice<>();
- MobileDevice<Pager> pager = new MobileDevice<>();
- MobileDevice<TabletPC> pc = new MobileDevice<>();
因为静态变量是类变量,被所有实例共享,此时,静态变量os的真实类型是什么呢?显然不能同时是Smartphone、Pager、TabletPC。
这就是为什么不能在静态字段上使用泛型的原因。
(4)不能对带有参数化类型的类使用cast或instanceof方法
- public static<E> void rtti(List<E> list) {
- if (list instanceof ArrayList<Integer>){ // 编译错误
- // ...
- }
- }
传给该方法的参数化类型集合为:
S = { ArrayList<Integer>,ArrayList<String> LinkedList<Character>, ... }
运行环境并不会跟踪类型参数,所以分辨不出ArrayList<Integer>与ArrayList<String>,我们能做的至多是使用无限定通配符来验证list是否为ArrayList:
- public static void rtti(List<?> list) {
- if (list instanceof ArrayList<?>){ // 正确
- // ...
- }
- }
同样,不能将参数转换成一个带参数化类型的对象,除非它的参数化类型为无限定通配符(<?>):
- List<Integer> li = new ArrayList<>();
- List<Number> ln = (List<Number>) li; // 编译错误
当然,如果编译器知道参数化类型肯定有效,是允许这种转换的:
- List<String> l1 = ...;
- ArrayList<String> l2 = (ArrayList<String>)l1; // 允许转变,类型参数没变化
(5)不能创建带有参数化类型的数组
例如:
- List<Integer>[] arrayOfLists = new List<Integer>[2]; // 编译错误
下面通过两段代码来解释为什么不行。先来看一个正常的操作:
- Object [] strings= new String[2];
- string s[0] ="hi"; // 插入正常
- string s[1] =100; //报错,因为100不是String类型
同样的操作,如果使用的是泛型数组,就会出问题:
- Object[] stringLists = new List<String>[]; // 该句代码实际上会报错,但是我们先假定它可以执行
- string Lists[0] =new ArrayList<String>(); // 插入正常
- string Lists[1] =new ArrayList<Integer>(); // 该句代码应该报ArrayStoreException的异常,但是运行环境探测不到
(6)不能创建、捕获泛型异常
泛型类不能直接或间接继承Throwable类
- class MathException<T> extends Exception { /* ... */ } //编译错误
- class QueueFullException<T> extends Throwable { /* ... */} // 编译错误
方法不能捕获泛型异常:
- public static<T extends Exception, J> void execute(List<J> jobs) {
- try {
- for (J job : jobs)
- // ...
- } catch (T e) { // 编译错误
- // ...
- }
- }
但是,我们可以在throw子句中使用类型参数:
- class Parser<T extends Exception> {
- public void parse(File file) throws T{ // 正确
- // ...
- }
- }
(7)不能重载经过类型擦除后形参转化为相同原始类型的方法
先来看一段代码:
- List<String> l1 = new ArrayList<String>();
- List<Integer> l2 = new ArrayList<Integer>();
- System.out.println(l1.getClass()== l2.getClass());
打印结果可能与我们猜测的不一样,打印出的是true,而非false,因为一个泛型类的所有实例在运行时具有相同的运行时类(class),而不管他们的实际类型参数。
事实上,泛型之所以叫泛型,就是因为它对所有其可能的类型参数,有同样的行为;同样的类可以被当作许多不同的类型。
认识到了这一点,再来看下面的例子:
- public class Example {
- public void print(Set<String> strSet){ } //编译错误
- public void print(Set<Integer> intSet) { } //编译错误
- }
因为Set<String>与Set<Integer>本质上属于同一个运行时类,在经过类型擦出以后,上面的两个方法会共享一个方法签名,相当于一个方法,所以重载出错。
2.
相关推荐
java泛型详解.pdf
Java泛型详解,Java泛型详解,Java泛型详解,Java泛型详解
思维导图之Java泛型详解
【Java泛型详解】 Java泛型是Java SE 5.0引入的新特性,它允许在类、接口和方法声明中使用类型参数,从而增强了代码的类型安全性和重用性。泛型的主要目标是提高代码的复用性,减少类型转换,并在编译时捕获类型...
Java 泛型是一种强大的语言特性,它允许在类、接口和方法中使用类型参数,以实现类型的安全性和代码的重用性。泛型是 Java 从 JDK 5.0 版本开始引入的重要特性,目的是增强类型系统,提高程序的类型安全性,减少运行...
还有类型擦除,这是Java泛型的一个特性,意味着在运行时,所有的泛型信息都会被移除,因此泛型并不影响程序的性能,但它提供了编译时的类型检查。 在集合框架中,泛型发挥了重要作用。Java的List、Set、Map等集合类...
### JAVA泛型详解 #### 泛型:打破具体类型的枷锁 在传统的Java编程中,类和方法的设计往往受限于具体的类型,无论是基本数据类型还是自定义的类。这种限制在面对多变的需求时显得尤为棘手,因为它迫使开发者重复...
Java 泛型详解 Java 泛型是 Java SE 5.0 中引入的一项特征,它允许程序员在编译时检查类型安全,从而减少了 runtime 错误的可能性。泛型的主要优点是可以Reusable Code,让程序员编写更加灵活和可维护的代码。 ...
让我们深入探讨一下Java泛型的各个方面。 首先,泛型(Generic type)是一种在定义类、接口或方法时,使用类型参数的方式。类型参数就像是方法的形参,但用于类型而非值。例如,当我们创建一个泛型列表`List<T>`,`...
### Java泛型的使用详细讲解 #### 一、引言 在Java开发中,泛型是一种重要的语言特性,它能够帮助开发者在不增加代码量的情况下处理多种数据类型,同时还能保持代码的清晰度和可读性。本文将详细介绍Java泛型的...
### Java 泛型详解与应用 #### 一、什么是Java泛型? Java泛型(Generics)是一种在编译时确保类型安全的机制,它允许程序员编写类型安全的通用类或方法,而无需进行显式的类型转换。在Java 1.5引入泛型之前,集合...
Java泛型机制详解 Java泛型是Java语言中的一种机制,用于在编译期检查类型安全。Java泛型的出现解决了Java早期版本中类型安全检查的缺陷。Java泛型的好处是可以在编译期检查类型安全,避免了运行时的...
### Java泛型详解 #### 一、Java泛型概述 Java泛型(Generics)是Java SE 5.0引入的一项重要新特性,它允许开发者在定义类、接口或方法时使用类型参数(Type Parameters)。类型参数在使用时可以用具体的类型来...