前言
- 参考:
- CSDN-java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一
- 《Java核心技术 卷1》第8章 泛型程序设计
介绍
- 定义:JDK1.5定义了泛型,即参数化类型,将类型参数应用到不同的类、接口、方法中,将类型作为参数进行传递,在使用时传入具体类型。
- 从Java程序设计语言1.0版本发布以来,变化最大的部分就是泛型。JDK5增加泛型机制的主要原因是为了满足JDK5之前的Java规范。
- 使用泛型机制编写的程序代码要比杂乱地使用Object变量,然后再进行强制类型转换的程序代码具有更好的安全性和可读性。泛型对集合类尤其有用。
- 另外泛型编写的代码可以被很多不同类型的对象所重用,例如一个ArrayList类就可以聚集任何类型的对象。
- 类型参数的好处:在引入类型参数之前我们一般使用Object,有两个问题,一是获取一个值的时候必须进行强制类型转换,二是没有类型检查,可能编译和运行的时候都不会出错,但是在使用元素的时候就会出错。泛型提供了一个更好的解决方案,就是类型参数,另外通过类型参数来指示元素的类型,这使得代码具有更好的可读性,例如ArrayList。
- 当调用get的时候不需要进行类型强制转换,编译器就知道返回值类型,例如
ArrayList<String>
调用get时返回值类型为String,而不是Object。 - 另外还可以检查插入的值类型是否错误,比如编译器知道ArrayList中add方法中有一个String类型的参数,这比使用Object类型的参数安全一些。
- 总结,类型参数的魅力在于,使得程序具有更好的可读性和安全性。
- 补充:
- 泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
- 不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错:
if(ex_num instanceof Generic<Number>){}
特性
- 泛型只在编译阶段有效。
- 泛型是通过类型擦除实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息,例如
List<String>
在运行时仅用一个List来表示。这样做的目的,是确保能和Java5之前的版本兼容。 - 类型擦除:泛型信息值存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,会被替换为限定类型,之前泛型类中的类型参数部分如果没有指定上限,则会被转译成普通的Object类型。
- 虚拟机没有泛型类型对象,所有对象都属于普通类。
- 泛型类型在逻辑上可以看成是多个不同的类型,实际上都是相同的基本类型,比如
List<String>
和List<Integer>
,实际上编译之后都是类型List,举例:
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
List<Integer> integerList=new ArrayList<>();
if((stringList.getClass()).equals(integerList.getClass())){
System.out.println("类型相同");
}
}
}
- 泛型中的限定通配符和非限定通配符:限定通配符对类型进行了限制,有两种,一种是
<? extends T>,它通过确保类型必须是T的子类来设定类型的上线,另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下届。另一方面<?>
表示非限定通配符,它可以用任意类型来替代。
- 泛型类型必须用限定内的类型来初始化,否则会导致编译错误。
- 补充:
- 同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的,它们之间也没有任何关系,只是都是相同的基本类型罢了,但是不能相互转换。
泛型的使用
- 泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法。
- 类型变量使用大写形式,且比较短。在Java库中,使用变量E表示集合的元素类型,K和V分别表示表的关键字与值的类型,T(需要时还可以用临近的字母U和S)表示“任意类型”。
泛型类
- 泛型类:就是具有一个或多个类型变量的类。
- 泛型类可看作普通类的工厂。
- 举例,一个普通的泛型类:
public class Generic<T>{
private T key;
public Generic(T key){
this.key=key;
}
public T getKey(){
return key;
}
}
泛型接口
- 泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:
public interface Generator<T>{
public T next();
}
- 当实现泛型接口的类,未传入泛型实参时:
public class FruitGenerator<T> implements Generator<T>{
@Override
public T next(){
return null;
}
}
- 当实现泛型接口的类,传入泛型实参时:
public class FruitGenerator implements Generator<String>{
@Override
public String next(){
return null;
}
}
泛型方法
- 在Java中,泛型类的定义非常简单,泛型方法就比较复杂了。泛型类中很多成员方法使用了泛型,很多泛型类中也包含着泛型方法。
- 泛型方法的基本介绍:
- public与返回值中间
<T>
(放在关键字后面,返回类型的前面)非常重要,可以理解为声明此方法为泛型方法。 - 只有声明了
<T>
的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。 <T>
表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
泛型方法与可变参数
- 举例:
public class Main{
public <T> void printMsg(T... args){
for(T t:args){
System.out.println(t);
}
}
public static void main(String[] args) {
printMsg("111",222,"aaaa","2323.4",55.55);
}
}
静态方法与泛型
- 静态方法有一种情况需要注意一下,就是在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型,如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法。
public class StaticGeneric<T>{
public static <T> void shot(T t){}
}
泛型方法总结
- 泛型方法能使方法独立于类而产生变化。只有声明了
<T>
的才是泛型方法。
关于泛型数组
- 在Java中是”不能创建一个确切的泛型类型的数组“,但是使用通配符创建泛型数组是可以的。因为对于通配符的方式,最后取出数据是要做显式的类型转换的。
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
List<String>[] ls = new ArrayList<>[10];
List<?>[] ls=new ArrayList<?>[10];
List<String>[] ls=new ArrayList[10];
}
}
补充
- 定义形参,传入实参。
- 桥方法保持多态。
面试重点
- 泛型定义:JDK1.5定义了泛型,即参数化类型,将类型参数应用到不同的类、接口、方法中,将类型作为参数进行传递,在使用时传入具体类型。
- 为什么要使用泛型(泛型优点):
- 在引入类型参数之前我们一般使用Object,有两个问题,一是获取一个值的时候必须进行强制类型转换,二是没有类型检查,可能编译和运行的时候都不会出错,但是在使用元素的时候就会出错。
- 引入泛型之后使程序具有了更好的可读性和安全性。
- 可读性:不需要强制转换,编译器就知道返回值类型。另外不需要杂乱地使用Object变量。
- 安全性:增加了类型检测,比如
List<String>
在插入元素的时候就会提示错误,不会等到获取/使用元素的时候才出错。
- 泛型特性:
- 泛型只在编译阶段有效,在虚拟机中对象只有普通对象,在编译时会进行类型擦除,擦除与泛型有关的信息,将泛型用限定类型替代,如果没有上限,则可用Object替代。
- 限定修饰符包含两种,
<? extends T>和<? super T>,一个指定上界,一个指定下界;非限定修饰符<?>
可以表示任何类型,用Object替代。
- 泛型使用:
- 泛型有三种使用方式,分别是:泛型类、泛型接口、泛型方法。
- 类型参数用大写字母表示,Java库中,E表示集合的元素类型、K,V表示键与值的类型、T(U,S)等表示任意类型。
- 总结:
- 上面总结的基本够了。