整理了Java泛型的相关知识,算是比较基础的,但愿你们一块儿学习进步。 java
Java 泛型(generics)是 JDK 5 中引入的一个新特性,其本质是参数化类型,解决不肯定具体对象类型的问题。其所操做的数据类型被指定为一个参数(type parameter)这种参数类型能够用在类、接口和方法的建立中,分别称为泛型类、泛型接口、泛型方法。面试
泛型类(generic class) 就是具备一个或多个类型变量的类。一个泛型类的简单例子以下:编程
//常见的如T、E、K、V等形式的参数经常使用于表示泛型,编译时没法知道它们类型,实例化时须要指定。
public class Pair <K,V>{
private K first;
private V second;
public Pair(K first, V second) {
this.first = first;
this.second = second;
}
public K getFirst() {
return first;
}
public void setFirst(K first) {
this.first = first;
}
public V getSecond() {
return second;
}
public void setSecond(V second) {
this.second = second;
}
public static void main(String[] args) {
// 此处K传入了Integer,V传入String类型
Pair<Integer,String> pairInteger = new Pair<>(1, "第二");
System.out.println("泛型测试,first is " + pairInteger.getFirst()
+ " ,second is " + pairInteger.getSecond());
}
}
复制代码
运行结果以下:数组
泛型测试,first is 1 ,second is 第二
复制代码
泛型也能够应用于接口。安全
public interface Generator<T> {
T next();
}
复制代码
实现类去实现这个接口的时候,能够指定泛型T的具体类型。bash
指定具体类型为Integer的实现类:app
public class NumberGenerator implements Generator<Integer> {
@Override
public Integer next() {
return new Random().nextInt();
}
}
复制代码
指定具体类型为String的实现类:dom
public class StringGenerator implements Generator<String> {
@Override
public String next() {
return "测试泛型接口";
}
}
复制代码
具备一个或多个类型变量的方法,称之为泛型方法。ide
public class GenericMethods {
public <T> void f(T x){
System.out.println(x.getClass().getName());
}
public static void main(String[] args) {
GenericMethods gm = new GenericMethods();
gm.f("字符串");
gm.f(666);
}
}
复制代码
运行结果:post
java.lang.String
java.lang.Integer
复制代码
Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,而且全部的强制转换都是自动和隐式的,提升代码的重用率。
咱们先来看看一个只能持有单个对象的类。
public class Holder1 {
private Automobile a;
public Holder1(Automobile a) {
this.a = a;
}
public Automobile getA() {
return a;
}
}
复制代码
咱们能够发现,这个类的重用性不怎样。要使它持有其余类型的任何对象,在jdk1.5泛型以前,能够把类型设置为Object,以下:
public class Holder2 {
private Object a;
public Holder2(Object a) {
this.a = a;
}
public Object getA() {
return a;
}
public void setA(Object a) {
this.a = a;
}
public static void main(String[] args) {
Holder2 holder2 = new Holder2(new Automobile());
//强制转换
Automobile automobile = (Automobile) holder2.getA();
holder2.setA("测试泛型");
String s = (String) holder2.getA();
}
}
复制代码
咱们引入泛型,实现功能那个跟Holder2类一致的Holder3,以下:
public class Holder3<T> {
private T a;
public T getA() {
return a;
}
public void setA(T a) {
this.a = a;
}
public Holder3(T a) {
this.a = a;
}
public static void main(String[] args) {
Holder3<Automobile> holder3 = new Holder3<>(new Automobile());
Automobile automobile = holder3.getA();
}
}
复制代码
所以,泛型的好处很明显了:
咱们定义泛型时,常常遇见T,E,K,V,?等通配符。本质上这些都是通配符,是编码时一种约定俗成的东西。固然,你换个A-Z中另外一个字母表示没有关系,可是为了可读性,通常有如下定义:
为何须要引入通配符呢,咱们先来看一个例子:
class Fruit{
public int getWeigth(){
return 0;
}
}
//Apple是水果Fruit类的子类
class Apple extends Fruit {
public int getWeigth(){
return 5;
}
}
public class GenericTest {
//数组的传参
static int sumWeigth(Fruit[] fruits) {
int weight = 0;
for (Fruit fruit : fruits) {
weight += fruit.getWeigth();
}
return weight;
}
static int sumWeight1(List<? extends Fruit> fruits) {
int weight = 0;
for (Fruit fruit : fruits) {
weight += fruit.getWeigth();
}
return weight;
}
static int sumWeigth2(List<Fruit> fruits){
int weight = 0;
for (Fruit fruit : fruits) {
weight += fruit.getWeigth();
}
return weight;
}
public static void main(String[] args) {
Fruit[] fruits = new Apple[10];
sumWeigth(fruits);
List<Apple> apples = new ArrayList<>();
sumWeight1(apples);
//报错
sumWeigth2(apples);
}
}
复制代码
咱们能够发现,Fruit[]与Apple[]是兼容的。List<Fruit>
与List<Apple>
不兼容的,集合List是不能协变的,会报错,而List与List<? extends Fruits> 是OK的,这就是通配符的魅力所在。通配符一般分三类:
无边界通配符,它的使用形式是一个单独的问号:List<?>,也就是没有任何限定。
看个例子:
public class GenericTest {
public static void printList(List<?> list) {
for (Object object : list) {
System.out.println(object);
}
}
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("A");
list1.add("B");
List<Integer> list2 = new ArrayList<>();
list2.add(100);
list2.add(666);
//报错,List<?>不能添加任何类型
List<?> list3 = new ArrayList<>();
list3.add(666);
}
}
复制代码
无界通配符(<?>)能够适配任何引用类型,看起来与原生类型等价,但与原生类型仍是有区别,使用 无界通配符则代表在使用泛型 。同时,List<?> list不能够添加任何类型,由于并不知道实际是哪一种类型。可是List list由于持有的是Object类型对象,因此能够add任何类型的对象。
使用 <? extends Fruit> 形式的通配符,就是上边界限定通配符。 extends关键字表示这个泛型中的参数必须是 E 或者 E 的子类,请看demo:
class apple extends Fruit{}
static int sumWeight1(List<? extends Fruit> fruits) {
int weight = 0;
for (Fruit fruit : fruits) {
weight += fruit.getWeigth();
}
return weight;
}
public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
sumWeight1(apples);
}
复制代码
可是,如下这段代码是不可行的:
static int sumWeight1(List<? extends Fruit> fruits){
//报错
fruits.add(new Fruit());
//报错
fruits.add(new Apple());
}
复制代码
List<Fruit>
里只能添加Fruit类对象及其子类对象(如Apple对象,Oragne对象),在List<Apple>
里只能添加Apple类和其子类对象。List<Fruit>、List<Apple>
等都是List<? extends Fruit>的子类型。假设一开始传参是List<Fruit> list
,两个添加没问题,那若是传来List<Apple> list
,添加就失败了,编译器为了保护本身,直接禁用添加功能了。使用 <? super E> 形式的通配符,就是下边界限定通配符。 super关键字表示这个泛型中的参数必须是所指定的类型E,或者是此类型的父类型,直至 Object。
public class GenericTest {
private static <T> void test(List<? super T> dst, List<T> src){
for (T t : src) {
dst.add(t);
}
}
public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
List<Fruit> fruits = new ArrayList<>();
test(fruits, apples);
}
}
复制代码
能够发现,List<? super E>添加是没有问题的,由于子类是能够指向父类的,它添加并不像List<? extends E>会出现安全性问题,因此可行。
什么是Java泛型擦除呢? 先来看demo:
Class c1 = new ArrayList<Integer>().getClass();
Class c2 = new ArrayList<String>().getClass();
System.out.println(c1 == c2);
/* Output
true
*/
复制代码
ArrayList <Integer>
和ArrayList <String>
很容易被认为是不一样的类型。可是这里输出结果是true,这是由于Java泛型是使用擦除实现的,不论是ArrayList<Integer>()
仍是new ArrayList<String>()
,在编译生成的字节码中都不包含泛型中的类型参数,即都擦除成了ArrayList,也就是被擦除成“原生类型”,这就是泛型擦除。
Java泛型在编译期完成,它是依赖编译器实现的。其实,编译器主要作了这些工做:
再看个例子:
public class GenericTest<T> {
private T t;
public T get() {
return t;
}
public void set(T t) {
this.t = t;
}
public static void main(String[] args) {
GenericTest<String> test = new GenericTest<String>();
test.set("jay@huaxiao");
String s = test.get();
System.out.println(s);
}
}
/* Output
jay@huaxiao
*/
复制代码
javap -c GenericTest.class反编译GenericTest类可得
public class generic.GenericTest<T> {
public generic.GenericTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public T get();
Code:
0: aload_0
1: getfield #2 // Field t:Ljava/lang/Object;
4: areturn
public void set(T);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field t:Ljava/lang/Object;
5: return
public static void main(java.lang.String[]);
Code:
0: new #3 // class generic/GenericTest
3: dup
4: invokespecial #4 // Method "<init>":()V
7: astore_1
8: aload_1
9: ldc #5 // String jay@huaxiao
11: invokevirtual #6 // Method set:(Ljava/lang/Object;)V
14: aload_1
15: invokevirtual #7 // Method get:()Ljava/lang/Object;
18: checkcast #8 // class java/lang/String
21: astore_2
22: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
25: aload_2
26: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
29: return
}
复制代码
使用Java泛型须要考虑如下一些约束与限制,其实几乎都跟泛型擦除有关。
不能用类型参数代替基本类型。所以, 没有 Pair<double>
, 只 有Pair<Double>
。 固然, 其缘由是类型擦除。擦除以后, Pair 类含有 Object 类型的域, 而 Object 不能存储 double值。
如,getClass()方法等只返回原始类型,由于JVM根本就不知道泛型这回事,它只知道原始类型。
if(a instanceof Pair<String>) //ERROR,仅测试了a是不是任意类型的一个Pair,会看到编译器ERROR警告
if(a instanceof Pair<T>) //ERROR
Pair<String> p = (Pair<String>) a;//WARNING,仅测试a是不是一个Pair
Pair<String> stringPair = ...;
Pair<Employee> employeePair = ...;
if(stringPair.getClass() == employeePair.getClass()) //会获得true,由于两次调用getClass都将返回Pair.class
复制代码
不能实例化参数化类型的数组, 例如:
Pair<String>[] table = new Pair<String>[10]; // Error
复制代码
不能使用像 new T(...),newT[...] 或 T.class 这样的表达式中的类型变量。例如, 下面的Pair<T>
构造器就是非法的:
public Pair() { first = new T(); second = new T(); } // Error
复制代码
interface Swim<T> {}
class Duck implements Swim<Duck> {}
class UglyDuck extends Duck implements Swim<UglyDuck> {}
复制代码
@SuppressWamings("unchecked")
public static <T extends Throwable〉void throwAs(Throwable e) throws T { throw (T) e; }
复制代码
public class Response<T> extends BaseResponse {
private static final long serialVersionUID = -xxx;
private T data;
private String code;
public Response() {
}
public T getData() {
return this.data;
}
public void setData(T data,String code ) {
this.data = data;
this.code = code;
}
}
复制代码
Java泛型常见几道面试题