1.泛型的理解及优点
1.1 传统方法和泛型比较
使用传统方法会遇到以下问题:
- 不能对加入到集合中的数据类型进行约束
- 遍历的时候需要进行类型转换,如果集合数据量较大会影响效率
// 类的定义 class Dog { private String name; public Dog(String name) { this.name = name; } public String getName() { return name; } } class Cat { private String name; public Cat(String name) { this.name = name; } public String getName() { return name; } } // 不使用泛型会出现的隐患(编译没有错误,所以写完代码后不会报错,但是运行报错) ArrayList list = new ArrayList(); list.add(new Dog("dog")); list.add(new Cat("cat")); for (Object o : list) { // 如果改为Dog o : list的话,编译器会报错 Dog dog = (Dog) o; System.out.println(dog.getName()); // 类型转换错误,有一个对象是Cat类 }
使用泛型:
ArrayList<Dog> list = new ArrayList<Dog>(); // 表示放到集合中的元素是Dog类型 list.add(new Dog("dog")); // list.add(new Cat("cat")); // 编译器会检测并报错 for (Dog o : list) { // 改为Dog o后编译器不会报错,不需要进行类型转换 Dog dog = (Dog) o; System.out.println(dog.getName()); }
1.2 理解
- 泛型可以理解为广泛的类型(又称参数化类型),对于
int
类型可以赋值1,2...
,对于泛型E
可以赋值Integet,Dog...
:// public class ArrayList<E>中的E相当于变量,将Dog这个值赋予这个变量 ArrayList<Dog> list = new ArrayList<Dog>();
- 在类声明或实例化时只要指定需要的具体类型即可
- 可以在类声明时通过一个标识表示类中某个属性的类型,或者是某个方法的返回值类型,或者是参数类型:
class Dog<E> { // 相当于把E替换为需要的类 private E name; // 类中某个属性的类型 public Dog(E name) { this.name = name; // 参数类型 } public E test(){ // 方法的返回值类型 return name; } }
- 在编译期间即可确定
E
具体是什么类型:Dog<String> dog = new Dog<String>(100); // 编译就报错
tips:
- 标识可以使用任意字母表示泛型,字母
E
不是必须的,可以使用K、V、T
等字母
1.3 好处
- 编译时会检查添加元素的类型,提高安全性
- 减少类型转换的次数,提高效率(传统方法中需要进行两次转换
Dog->Object->Dog
)- 不再提示编译警告
2.使用细节
- 泛型只能使用引用类型,不能使用基本类型:
Dog<int> dog = new Dog<int>(111); // 报错
- 在给泛型指定具体类型后,可以传入该类型或者子类类型:
// 类的定义 class Father{} class Son extends Father{} class A<E>{ E e; public A(E e) { this.e = e; } } A<Father> a = new A<Father>(new Son()); // 不会报错,可以视为Father f = new Son();
- 泛型的使用:
A<Father> a = new A<Father>(new Son()); A<Father> a = new A<>(new Son()); // 简化形式
tips:
- 在传统方法中其实默认泛型
E
为Object
类型:// 定义类 class A<E>{ E e; public A() { } public A(E e) { this.e = e; } } // 即使使用了泛型定义类,也是可以使用传统方式创建A类对象 A a = new A(); // 等价于A<Object> a = new A<Object>();
3.自定义泛型
3.1 自定义泛型类
- 语法:
class 类名<T,R,...>{ // 可以定义多个泛型 成员属性、方法 }
- 注意细节:
- 普通成员可以使用泛型,但是静态成员中不能使用类的泛型(类加载时对象还没创建,而泛型在类创建时才会指定,JVM无法完成初始化)
- 使用泛型的数组不能初始化(类型无法确定就不清楚要开辟多大空间)
class Person<R, T> { // 下面三行语句都报错 static R r; T[] t = new T[10]; public static void test(T r){} }
3.2 自定义泛型接口
- 语法:
interface 接口名<T,R,...>{ // 可以定义多个泛型 成员方法 }
注意细节:
- 静态成员中不能使用泛型
- 泛型接口的类型在继承接口或实现接口时确定
interface Person<R, T> { void test(R t); } class A implements Person<String, Integer>{ @Override public void test(String t) {} }
tips:
- 接口中的属性是
public static final
修饰的,所以在接口中没有成员:interface Person<R, T> { R t; // 报错 }
- 如果接口定义的方法中使用到了定义类时使用到的泛型,其他类在实现该接口时就必须指明所有泛型的具体类型,不能认定其中某个为
Object
:interface Person<R, T> { void test(R t); } class A implements Person<String>{ // 报错,需要将两个泛型都指定或者都不指定 }
3.3 自定义泛型方法
- 基本语法:
修饰符<T,R> 返回类型 方法名(参数列表){ }
注意细节:
- 泛型方法可以定义在普通类中(被调用的时候类型会确定),也可以定义在泛型类中
// 类的定义 class Person{ public<E> void test(E e){ System.out.println(e.getClass()); } } // 使用泛型方法 Person person = new Person(); person.test("psj"); // class java.lang.String
tips:
- 泛型方法不等价于使用了泛型:
class Person<E>{ public void test(E e){ // 使用了泛型,但不是泛型方法。并且类一定要是自定义泛型类 } } class Person<R>{ public<E> void test(R r){ // 泛型方法可以不使用定义的泛型,并且可以使用泛型类的泛型 } }
4.泛型的继承和通配符
- 继承性:泛型不具备继承性
List<Object> list = new ArrayList<String>(); // 报错
通配符:
<?>
:表示支持任意泛型类型// 参数中使用<?> public static void test1(List<?> list){ } // 测试语句 List<String> list1 = new ArrayList<>(); List<Integer> list2 = new ArrayList<>(); // 都不会报错 test1(list1); test1(list2);
<? extends A>
:表示支持A类及A类的子类,规定了泛型的上限// 类的定义 class A{} class B extends A{} class C extends B{} // 参数中使用<? extends A> public static void test2(List<? extends A> list){ } // 测试语句 List<String> list1 = new ArrayList<>(); List<A> list2 = new ArrayList<>(); List<B> list3 = new ArrayList<>(); List<C> list4 = new ArrayList<>(); test2(list1); // 报错,String不是A类的子类 // 下面语句没问题 test2(list3); test2(list4); test2(list5);
<? super A>
:表示支持A类及A类的父类,规定了泛型的下限// 类的定义 class A{} class B extends A{} class C extends B{} // 参数中使用<? super A> public static void test3(List<? super A> list){ // 定义该方法的类不需要是泛型类,使用的是?不是具体的字母 } // 测试语句 List<String> list1 = new ArrayList<>(); List<A> list2 = new ArrayList<>(); List<B> list3 = new ArrayList<>(); List<C> list4 = new ArrayList<>(); test3(list1); // 报错 test3(list2); // 没问题 test3(list3); // 报错 test3(list4); // 报错
tips:
- 在自定义泛型类和自定义泛型方法中不能使用
<? ...>
:class A<? extends List>{ } // 报错 class A<T extends List>{ } // 正确 public<E extends List> void test(E e){} // 正确 public<? extends List> void test(E e){} // 错误
- 如
<R extends List>
,即使List是接口也是使用extends
不是implements
,表示R
必须是一个List
继承体系中的类<?>
等价于<? extends object>
- 在自定义的类/接口中,静态成员不能使用泛型,但是可以使用
<?>
:class A<T>{ public static void test1(List<? extends String> list){} // 正确 public static void test2(List<? extends T> list){} // 错误 }