Java泛型(generics)提供了编译时类型安全检测机制,该机制允许程序员啊在编译时检测到非法的类型,使更多的bug在编译时期就可以被发现,为代码增加稳定性。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
泛型的由来
Java SE 1.5之前,没有泛型的情况下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。Java语言引入泛型来解决这个安全问题。
public static void main(String[] args) {
List list = new ArrayList<>();
list.add("Cat_and_Mouse");
list.add(110);
System.out.println(list); //正常运行
for(int i = 0; i < list.size(); i++) {
String name = (String) list.get(i); // 取出Integer时,运行出现异常
System.out.println(name);
}
}
当我们在运行如上代码的时候,会报出如下错误:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at com.day16.GenericLearn.main(GenericLearn.java:15)
这里的将数据添加到List集合中并且打印的做法是正确的,没有报错,但是在取出元素的时候报错了,报错的信息是类强制转换异常,解决这个问题,我们就要用到泛型。
泛型的好处
- 类型安全:泛型的主要目标是在编译的时候检查类型安全(将运行期的错误转换到编译期——泛型只在编译阶段有效)。
- 消除了强制类型转换:所有的强制转换都是自动和隐式的,使得代码可读性好,减少了很多出错的机会
- 在其作用域内可以统一参数类型,提高代码的重用率。
类型参数的命名规则:一个大写字母,最仓常用的类型参数名称如下:
- E - Element
- K -Key
- T -Type
- N -Number
- V -Value
- S,U,V -2nd,3trd,4th
泛型的使用
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法。
泛型类
泛型类型用于类的定义中,被称为泛型类。在编译期,是无法知道K和V具体是什么类型,只有在运行时才会真正根据类型来构造和分配内存。
实例
public class GenericClass {
private K key;
private V value;
public GenericClass(K k, V v) {
this.key = k;
this.value = v;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
public static void main(String[] args) {
GenericClass g = new GenericClass("name", 1970);
System.out.println(g.getKey() + ":" + g.getValue()); //name:1970
GenericClass g2 = new GenericClass<>(1970, "year");
System.out.println(g2.getKey() + ":" + g2.getValue()); //1970:year
}
}
泛型方法
定义泛型方法时,必须在返回值前边加一个
实例
public class GenericExercise {
public static E method(E[] e) {
return e[e.length/2];
}
public static void main(String[] args) {
String[] str = {"第1个元素","第2个元素","第3个元素","第4个元素","第5个元素","第6个元素","第7个元素"};
System.out.println(method(str)); // 第4个元素
Integer[] in = {1,10,100,1000,10000,100000,1000000};
System.out.println(method(in)); // 1000
// 参数的传递不能传递基本数据类型,必须是引用数据类型
}
}
泛型接口
泛型接口与泛型类的定义及使用基本相同.泛型接口常被用在各种类的生产器中:
实例
public interface GenericInterface {
public T next();
}
未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中. 如果不声明泛型,如:class GenericImplements implements GenericInteface
public class GenericImplements implements GenericInterface {
@Override
public T next() {
return null;
}
}
传入泛型实参时:
定义一个类实现这个接口,我们只写了一个泛型接口,但是我们可以为T传入无数个实参,形成无数种类型的GeneratorInterface接口.在实现类实现泛型接口时,如已将泛型类型插入实参类型,则所有使用泛型的地方都要替换成传入的实参类型,即:GenericInterface
public class GenericImplements implements GenericInterface {
public String[] string = {"Apple", "Oracle","Google","Facebook","Tencent"};
@Override
public String next() {
return string[2];
}
}
泛型通配符
通配符只有在修饰一个变量时会用到,使用它可以很方便地引用包含了多种类型的泛型.主要有以下三类:
- 无边界的通配符(Unbounded Wildcards),就是>,比如List>.
无边界的通配符的主要作用就是让泛型能够接受未知类型的数据. - 固定上边界的通配符(Upper Bounded Wildcards)
使用固定上边界的通配符的泛型,就能够接受指定类及其子类类型的数据,要声明使用该类通配符,采用 extends E>的形式,这里的E就是该泛型的上边界,注意:这里虽然使用的是extends关键字,却不仅限于继承了父类E的子类,也可以代指显现了接口E的类. - 固定下边界的通配符(Lower Bounded Wildcards)
使用固定下边界的通配符的泛型,就能够接受指定类及其父类类型的数据,要声明使用该类通配符,采用 super E>的形式,这里的E就是该泛型的下边界.
注意:你可以为一个泛型指定上边界或下边界,但是不能同时指定上下边界
基本使用方法
- 无边界的通配符的使用:
import java.util.ArrayList;
import java.util.List;
public class GenericExercise {
public static void main(String[] args) {
// 无边界的通配符的使用
List stringList = new ArrayList<>();
stringList.add("abcd");
stringList.add("efgh");
stringList.add("ijkl");
stringList.add("mnop");
stringList.add("qrst");
stringList.add("uvwx");
stringList.add("yz");
// 传入的是元素为String类型的List集合
func(stringList);
List integerList = new ArrayList<>();
integerList.add(1234);
integerList.add(5678);
integerList.add(90);
// 传入的是元素为Integer类型的List集合
func(integerList);
}
public static void func(List> list) {
for (Object o : list) {
System.out.println(o);
}
list.add("123"); // 报错
}
}
这种使用List>的方式就是父类引用指向子类对象.
注意:
- 这里的func方法不能写成
public static void func(List
的形式,虽然Object类是所有类的父类,但是List - 我们不能对List>使用add方法,但是add(null)是例外.因为我们不确定该List的类型,不知道add什么类型的数据才对,只有null是所有引用数据类型都具有的元素
- List>不能使用get方法,只有Object类型是例外.原因很简单,因为不知道要传入的List是什么类型的,所以无法接受得到get,但是Object是所有数据类型的父类,所以只有它可以
list.add("abc"); //编译报错
list.add(255); // 编译报错
list.add(null); // 正常运行
String s = list.get(1); // 编译报错
Integer i = list.get(2); // 编译报错
Object o1 = list.get(0); // 正常运行
- 固定上边界的通配符的使用
利用 extends Number>形式的通配符,可以实现泛型的向上转型
import java.util.ArrayList;
import java.util.List;
public class GenericExercise {
public static E method(E[] e) {
return e[e.length/2];
}
public static void main(String[] args) {
List stringList = new ArrayList<>();
stringList.add("abcd");
stringList.add("efgh");
stringList.add("ijkl");
stringList.add("mnop");
stringList.add("qrst");
stringList.add("uvwx");
stringList.add("yz");
// 传入的元素是String类型的list集合
number(stringList); // 编译报错
//传入的是Integer类型的list集合
List integerList = new ArrayList<>();
integerList.add(1234);
integerList.add(5678);
integerList.add(90);
number(integerList);
// 传入的元素是Double类型的list集合
List doubleList = new ArrayList<>();
doubleList.add(12.34);
doubleList.add(56.78);
doubleList.add(9.0);
number(doubleList);
}
// 固定上边界的通配符的使用
public static void number(List extends Number> list) {
for (Number n : list) {
System.out.println(n);
}
list.add(123.3); // 报错
}
}
- 固定下边界通配符的使用
import java.util.ArrayList;
import java.util.List;
public class GenericExercise {
public static void main(String[] args) {
// 传入的是元素为String类型的List集合
func(stringList);
List integerList = new ArrayList<>();
integerList.add(1234);
integerList.add(5678);
integerList.add(90);
// 传入的元素是Double类型的list集合
List doubleList = new ArrayList<>();
doubleList.add(12.34);
doubleList.add(56.78);
doubleList.add(9.0);
List
- 无边界的通配符">"不能添加元素
- 固定上边界的" extends T>"不能添加元素
- 固定下边界的" super T>"能添加元素