Java泛型

概述

今天重新回归Java基础,温故一下什么泛型,要经常回头看看复习以前的基础知识是给自己定下计划的一部分,其中Java基础尤为重要。

什么是泛型

泛型(generic),参数化类型。就是将原来具体参数的类型参数化,是针对参数类型而制定的。在定义一个方法的时候用的是形参,而调用该方法传递的是实参。用形参定义方法的时候我们已经约定好参数的类型,而泛型就是将制定好的参数类型参数化。

泛型的基本使用

当不用泛型时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test1{     
Object value;
public Object getValue(){
return value
}
public void setValue(Object value){
this.value = value;
}
}

//使用
Test1 test = new Test1();
test.setValue(1111);//可以传Intger
int value = (int)test.getValue();//强转一下

test.setValue("zhouk");//可以传String
String value = (String)test.getValue();//也要强转

以上,可以看出能为成员变量value设不同类型的值,能达到形参类型的参数化。
但是,getValue的时候需要强转。泛型就能很好的解决这个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test1<T>{     
T value;
public T getValue(){
return value
}
public void setValue(T value){
this.value = value;
}
}
//使用
Test1<Integer> test = new Test1<Integer>();
test.setValue(1111);//可以传Intger
int value = test.getValue();//不需要强转

Test1<String> test1 = new Test1<String>();
test1.setValue("zhouk");//可以传String
String value = test1.getValue();//也不需要强转

可以看出用来泛化参数的类型,在使用的时候再指定参数的类型,获取的时候就无需再强转。上面的Integer和String也成了参数,即时所说的形参类型参数化。
一旦指定了具体的参数类型,就必需按照给定的类型传值,否则就编译不过。例如:

1
2
3
Test1<Integer> test = new Test1<Integer>();
test.setValue(1111);//可以传Intger
test.setValue("zhouk");//编译不通过

小结

所以泛型只在编译阶段有效,有类型的安全检测机制,提高了软件的安全性。
利用泛型的参数化类型,可以进行不同的拓展,符合面向抽象的开发思想
取消了运行时的强转,也增加了代码的可读性。

泛型的拓展

除了上面所说的泛型类,事实上还可以有泛型方法、泛型接口。
为方便规范,用一些大写字母来代表泛型(非硬性要求)

1
2
3
4
5
6
7
1、T代表一般的任何类

2、E代表Element的意思,或者Exception异常的意思

3、K代表Key的意思

4、V代表Value的意思

泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test2{
public <T> void method1(T t){

}

public <T> T method2(T t){
return T;
}
}

//使用
Test2 test = new Test2();
test.<String>method1("zhouk");
String str = test.<String>method2("zhouk");

泛型接口

1
2
3
4
//与泛型类类似
public interface interf<T>{

}

通配符

有两种限定通配符,一种是<? extends T>它通过确保类型必须是T的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。另一方面<?>表示了非限定通配符,因为<?>可以用任意类型来替代。
例如:

1
2
3
4
5
6
7
8
9
//无限定通配符
List<?> list1 = new ArrayList<?>();//非限定通配符,未知类型的List
List<Object> list2 = new ArrayList<Object>();//任意类型的List
//list1和list2的区别是:list1是未知类型的List,你可以把List<Integer> 或者List<String> 赋值给List<?>,但是不能把List<Integer> 或者List<String>赋值给List<Object>

//限定通配符
List<? extends T> list3;//可以存放任何T的父类
List<? super T> list4;//可以存放任何T的子类
//例如:List<? extends Number>可以接收List<Integer>或者List<Double>的赋值,因为Interger和Double是Number的子类。而List<? super Number>就不可以接收这样的赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//异常使用
public static void fillNumberList(List<? extends Number> list) {
list.add(new Integer(0));//编译错误
list.add(new Float(1.0));//编译错误
}
public static void main(String[] args) {
List<? extends Number> list=new ArrayList();
list.add(new Integer(1));//编译错误
list.add(new Float(1.0));//编译错误
}

//正常使用
public static void fillNumberList(List<? super Number> list) {
list.add(new Integer(0));
list.add(new Float(1.0));
}
public static void main(String[] args) {
List<? super Number> list=new ArrayList();
list.add(new Integer(1));
list.add(new Float(1.1));
}

面试题

  1. Java中的泛型是什么 ? 使用泛型的好处是什么?
    泛型是一种参数化类型的机制。它可以使得代码适用于各种类型,从而编写更加通用的代码,例如集合框架。
    泛型是一种编译时类型确认机制。它提供了编译期的类型安全,确保在泛型类型(通常为泛型集合)上只能使用正确类型的对象,避免了在运行时出现ClassCastException。
  2. Java的泛型是如何工作的 ? 什么是类型擦除 ?
    泛型的正常工作是依赖编译器在编译源码的时候,先进行类型检查,然后进行类型擦除并且在类型参数出现的地方插入强制转换的相关指令实现的。
    编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如List在运行时仅用一个List类型来表示。为什么要进行擦除呢?这是为了避免类型膨胀。
  3. 什么是泛型中的限定通配符和非限定通配符 ?
    限定通配符对类型进行了限制。有两种限定通配符,一种是<? extends T>它通过确保类型必须是T的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。另一方面<?>表示了非限定通配符,因为<?>可以用任意类型来替代。
  4. List<? extends T>和List <? super T>之间有什么区别 ?
    这和上一个面试题有联系,有时面试官会用这个问题来评估你对泛型的理解,而不是直接问你什么是限定通配符和非限定通配符。这两个List的声明都是限定通配符的例子,List<? extends T>可以接受任何继承自T的类型的List,而List<? super T>可以接受任何T的父类构成的List。例如List<? extends Number>可以接受List或List。在本段出现的连接中可以找到更多信息。
  5. Java中List和原始类型List之间的区别?
    原始类型和带参数类型之间的主要区别是,在编译时编译器不会对原始类型进行类型安全检查