泛型:
泛型是一种不确定的类型,需要使用的时候根据传进来的数据类型来确定
因为java是强类型的语言,当我们不确定数据类型的时候,最好用泛型
public class Point<T>{
T x;
T y;
}
注意,T是泛型参数,表示一种数据类型,具体是什么类型,需要将来使用Point的时候进行传参来确定
注意,如果将来Point在使用的时候,没有给泛型参数T传值,那么T默认就表示为Object类型
注意,T是泛型参数的名字,也就是相当于形参,名字随便起,但是一般用一个有意义的大写字母
Point 类的使用:
public static void main(String[] args) {
//p1对象中x和y的属性类型都是Integer
Point<Integer> p1 = new Point<Integer>();
p1.x = 1;
p1.y = 2;
//p2对象中x和y的属性类型都是String
Point<String> p2 = new Point<String>();
p2.x = "1";
p2.y = "2";
//p3对象中x和y的属性类型都是Object
Point p3 = new Point();
p3.x = new Object();
p3.y = new Object();
}
Point类的中x和y属性的类型,是可以根据我们在使用时所传的参数,进行临时变化的。
如果没有传这个泛型参数,那么这个参数T就默认是Object类型
注意,给泛型参照传的值,只能是引用类型,不能是基本类型: Point 编译报错
泛型也叫做 类型参数化(Parameterized by types)
大写字母一般使用
T type 类型
E element 元素
K key
V value
方法重载时,泛型不同重载不算成功
public void Test(List<String>){
}
public void Test(List<Integer>){
}//由于类型擦除,jvm编译的时候默认两个方法一样
泛型的种类
java中的泛型分三种使用情况:
泛型类
泛型接口
泛型方法
泛型类,如果泛型参数定义在类上面,那么这个类就是一个泛型类
在类中,就可以使用这个T来代表某一个类型,这个类型具体是什么将来使用的时候再传参确定
public class Point<T>{...}
public static void main(String[] args){
Point<String> p = new Point<>();
}
泛型接口,如果泛型参数定义在接口上面,那么这个接口就是一个泛型接口
在接口中,就可以使用这个T来代表某一个类型,这个类型具体是什么将来使用的时候再传参确定
public interface Action<T>{...}
public static void main(String[] args){
//创建匿名内部类
Action<String> a = new Action<>(){
//...
};
}
泛型方法,如果泛型参数定义在方法上面,那么这个方法就是一个泛型方法
public class Test{
public <T> T test(T t){
//..
}
}
public static void main(String[] args){
Test t = new Test();
String str = t.test("hello");
Integer i = t.test(1);
Double d = t.test(10.5D);
}
可以看出,调用test方法时候,我们传什么类型的参数,test方法的返回类型就是什么类型的。
通配符
先观察如下情况
public void test1(Collection<Integer> c){
}
public void test2(Collection<String> c){
}
public void test3(Collection<Object> c){
}
在这种情况下,就可以使用通配符(?)来表示泛型的父类型:
public void test(Collection<?> c){
}
这时候test方法中的参数类型,使用了泛型,并且使用问号来表示这个泛型的类型,这个问号就是通配符,可以匹配所有的泛型类型
test方法可以接收 泛型是任意类型的 Collection集合对象
Collection<?> c = new ArrayList<String>();
//编译报错
//因为变量c所声明的类型是Collection,同时泛型类型是通配符(?)
//那么编译器也不知道这个?将来会是什么类型,因为这个?只是一个通配符
//所以,编译器不允许使用变量c来向集合中添加新数据。
c.add("hello");
//编译通过
//但是有一个值是可以添加到集合中的,null
//集合中一定存的是引用类型,null是所有引用类型共同的一个值,所以一定可以添加进去。
c.add(null);
//虽然使用通配符(?)的集合,不能再往其中添加数据了,但是可以遍历集合取出数据:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("hello1");
list.add("hello2");
list.add("hello3");
list.add("hello4");
Collection<?> c = list;
//编译报错
//c.add("hello5");
for(Object obj : c){
System.out.println(obj);
}
}
//所有?通配符一般在传参的时候用
泛型边界
在默认情况下,泛型的类型是可以任意设置的,只要是引用类型就可以。
如果在泛型中使用 extends 和 super 关键字,就可以对泛型的类型进行限制。即:规定泛型的上限和下限。
泛型的上限:
例如: List<? extends Number> list
将来引用list就可以接收泛型是 Number 或者 Number 子类型的List集合对象
public static void main(String[] args) {
List<? extends Number> list;
//list可以指向泛型是Number或者Number【子】类型的集合对象
list = new ArrayList<Number>();
list = new ArrayList<Integer>();
list = new ArrayList<Double>();
//编译报错,因为String不是Number类型,也不是Number的子类型
//list = new ArrayList<String>();
}
能表示数字的类型都是Number类型的子类型,例如Byte Short Integer Long等
泛型的下限:
例如: List<? super Number> list
将来引用list就可以接收泛型是 Number 或者 Number 父类型的List集合对象
public static void main(String[] args) {
List<? super Number> list;
//list可以指向泛型是Number或者Number【父】类型的集合对象
list = new ArrayList<Number>();
list = new ArrayList<Serializable>();
list = new ArrayList<Object>();
//编译报错,因为String不是Number类型,也不是Number的父类型
//list = new ArrayList<String>();
//编译报错,因为Integer不是Number类型,也不是Number的父类型
//list = new ArrayList<Integer>();
}
泛型中 extends 和 super 对比:
使用extends可以定义泛型的【上限】,这个就表示将来泛型所接收的类型【最大】是什么类型。
可以是这个最大类型或者它的【子类型】。
使用super可以定义泛型的【下限】,这个就表示将来泛型所接收的类型【最小】是什么类型。可
以是这个【最小类型】或者它的【父类型】。
extends 限定泛型的上限的使用场景
- 在声明泛型类或者泛型接口的时候【可以】使用
- 在声明泛型方法的时候【可以】使用
- 在声明变量的时候【可以】使用
super 限定泛型的下限的使用场景
- 在声明泛型类或者泛型接口的时候【不能】使用
- 在声明泛型方法的时候【不能】使用
- 在声明变量的时候【可以】使用
类型擦除
泛型类型仅存在于编译期间,编译后的字节码和运行时不包含泛型信息,所有的泛型类型映射到同一份 字节码。
注意,泛型信息被擦除后,所有的泛型类型都会统一变为原始类型:Object
例如,定义一个泛型类 Generic 是这样的:
class Generic<T> {
private T obj;
public Generic(T o) {
obj = o;
}
public T getObj() {
return obj;
}
}
那么,Java编译后的字节码中 Generic 相当于这样的:(类型擦除,变为原始类型Object)
class Generic {
private Object obj;
public Generic(Object o) {
obj = o;
}
public Object getObj() {
return obj;
}
}
Java的泛型只存在于编译时期,泛型使编译器可以在编译期间对类型进行检查以提高 类型安全,减少运行时由于对象类型不匹配引发的异常。