笔记来自2019求知讲堂零基础Java入门编程视频教程 https://www.bilibili.com/video/av76235341
泛型(Generic)
泛型的使用是为了解决数据类型的安全性问题。
其主要原理是在类声明时通过一个标识符表示类中某个属性的类型或者某个方法的参数类型和返回值类型。
但实际上,Java中的泛型只在编译阶段有效,泛型信息不会进入到运行时阶段。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除,并且在对象进入和离开方法的边界处添加类型检查和强制类型转换的方法。
泛型集合
在集合类型后用一对尖括号<>来说明这个集合存放的数据类型。
//set集合中只能存放String类型的数据
Set<String> set = new HashSet<String>();
泛型类
结合实例理解:
类A中有泛型T,那么在实例化的时候,将泛型指定为什么类型,T就是什么类型
public class Test {
public static void main(String[] args) {
//这里指定泛型为String
A<String> a1 = new A<String>();
a1.setKey("123"); //类A中的set方法参数就必须是String类型
a1.getKey(); //类A中get方法的返回值也是String类型
//同理,泛型指定为Integer,类A的get、set方法的参数和返回值就变成了Integer类型
A<Integer> a2 = new A<Integer>();
a2.setKey(3);
a2.getKey();
//如果没有指定泛型,默认是Object类型
A a3 = new A();
//上面的写法等同于:A<Object> a3 = new A<Object>();
a3.setKey(new Object());
a3.getKey();
//泛型不同的引用不能相互赋值
a1 = a2; //错误信息:Type mismatch: cannot convert from A<Integer> to A<String>
}
}
//类A指定了一个泛型T
//这里的T可以任意命名,但通常使用T,意为type
class A<T>{
private T key;
public void setKey(T key) {
this.key = key;
}
public T getKey() {
return key;
}
}
泛型接口
- 声明泛型接口
interface IB<T>{
T test(T t);
}
- 当一个类实现泛型接口时,若没有指定泛型的具体类型,这个类本身就相当于一个泛型类。那么在声明类的时候,需要将泛型的声明也一起加到类中,如果不声明泛型,编译器会报错。
class B1<T> implements IB<T>{
@Override
public T test(T t) {
return t;
}
}
- 当一个类实现泛型接口时,若指定了泛型的具体数据类型(例子中为String),这个类本身就相当于一个普通的不含泛型的类。那么这个类实现接口的所有方法,都要将泛型替换成实际的数据类型
class B2 implements IB<String>{
@Override
public String test(String t) {
return t;
}
}
- 在使用以上两种实现类的时候:
B1类实例化时,需要指定泛型
B2类实例化的时候,不必指定泛型
public class Test1 {
public static void main(String[] args) {
//B1类实例化时,需要指定泛型
B1<String> b1 = new B1<String>();
//B2类实例化的时候,不必指定泛型,指定了反而是错的
B2 b2 = new B2();
}
}
泛型方法
方法也可以被泛型化。
不管此时定义在其中的类是不是被泛型化的。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
- 泛型方法的定义
一般来说,泛型方法的定义在返回值类型前。
class C<E>{
private E e;
//无返回值的泛型方法
public <T> void test1(T s) {
//在类上定义的泛型,可以在普通方法上使用,也可以在泛型方法上使用
System.out.println(this.e);
System.out.println(s);
}
//静态方法不能使用类上定义的泛型,但可以使用自己定义的泛型
public static <T> void test4(T t) {
System.out.println(t);
}
//有返回值的泛型方法
public <T> T test2(T s) {
return s;
}
//形参为可变参数的泛型方法
public <T> void test3(T... strs) {
for (T s:strs) {
System.out.println(s);
}
}
}
- 泛型方法的使用
泛型方法在调用前没有固定的数据类型,在调用时传入的参数决定了泛型的具体类型。也就是说,泛型方法会在调用时确定泛型的具体数据类型。
public class Test2 {
public static void main(String[] args) {
C<Object> c = new C<Object>();
//泛型方法在调用前没有固定的数据类型,在调用时传入的参数决定了泛型的具体类型
//也就是说,泛型方法会在调用时确定泛型的具体数据类型
//以test2方法为例
//传入的参数是Boolean类型,泛型就固定成Boolean类型,返回值也是Boolean类型
Boolean a = c.test2(true);
//传入的参数是Integer类型,泛型就固定成Integer类型,返回值也是Integer类型
Integer b = c.test2(456);
}
}
通配符(?)
不确定集合中元素的具体的数据类型,可以使用? 表示所有类型。
class D{
//test方法需要一个List集合的参数,但不确定List集合中存储的是什么数据类型
public void test(List<?> list) {
System.out.println(list);
}
}
有限制的通配符
例如:
- <? extends Person>
只允许泛型为Person以及Person的子类的引用调用
- <? super Person>
只允许泛型为Person以及Person的父类的引用调用
- <? extends Comparable>
只允许泛型为实现Comparable接口的实现类的引用调用
通过例子来理解:
//现在有一组继承和实现关系
class D1{}
class D2 extends D1{}
class D3 extends D2{}
class D4 extends D3{}
interface ID{}
class D5 implements ID{}
//类D中有三个方法
class D{
//test2方法,参数是一个List,其泛型类型只能是D3及其子类
public void test2(List<? extends D3> list) {
}
//test3方法,参数是一个List,其泛型类型只能是D3及其父类
public void test3(List<? super D3> list) {
}
//test4方法,参数是一个List,其泛型类型只能是ID接口的实现类
public void test4(List<? extends ID> list) {
}
}
//main方法中
D d = new D();
List<D1> d1 = new ArrayList<D1>();
List<D2> d2 = new ArrayList<D2>();
List<D3> d3 = new ArrayList<D3>();
List<D4> d4 = new ArrayList<D4>();
//测试test2方法
d.test2(d3); //正确
d.test2(d4); //正确
d.test2(d1); //报错
d.test2(d2); //报错
//测试test3方法
d.test3(d1);
d.test3(d2);
d.test3(d3);
d.test3(d4); //报错
//测试test4方法
d.test2(d3); //正确
d.test2(d4); //正确
d.test2(d1); //报错
d.test2(d2); //报错
d.test4(d5);
d.test4(d1); //报错