原文地址https://zhuanlan.zhihu.com/p/28242753
用来规定一个类、接口或方法所能接受的数据的类型. 就像在声明方法时指定参数一样, 我们在声明一个类, 接口或方法时, 也可以指定其"类型参数", 也就是泛型.
List l = new ArrayList();
l.add("abc");
String s = (String) l.get(0);
而使用泛型,就可以保证存入和取出的都是String类型, 不必在进行cast了。比如:
List<String> l = new ArrayList<>();
l.add("abc");
String s = l.get(0);
1. 定义类/接口:
public class Test<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
2. 定义方法:
public void print(Q q) {
System.out.println(q);
}
1. 作用:规定只允许某一部分类作为泛型;
2. 分类:
注意: 你可以为一个泛型指定上边界或下边界, 但是不能同时指定上下边界。
3. 使用方法:
3.1 无边界通配符:
public static void printList(List> list) {
for (Object o : list) {
System.out.println(o);
}
}
public static void main(String[] args) {
List<String> l1 = new ArrayList<>();
l1.add("aa");
l1.add("bb");
l1.add("cc");
printList(l1);
List<Integer> l2 = new ArrayList<>();
l2.add(11);
l2.add(22);
l2.add(33);
printList(l2);
注意:
这里的printList方法不能写成public static void printList(List
public static void addTest(List> list) {
Object o = new Object();
// list.add(o); // 编译报错
// list.add(1); // 编译报错
// list.add("ABC"); // 编译报错
list.add(null); // 特例
// String s = list.get(0); // 编译报错
// Integer i = list.get(1); // 编译报错
Object o = list.get(2); // 特例
}
这个地方有点不好理解。
我们可以假设:使用这些方法编译不报错。
以上面的代码为例,并且取消上面的注释。
由于参数的泛型不确定,调用者可能会传List
当调用者传过来的参数是List
所以,编译器其实是把运行时可能出现的异常放在编译阶段来检查,提高了代码的健壮性以及安全性。
2. 固定上边界通配符:
public static double sumOfList(List extends Number> list) {
double s = 0.0;
for (Number n : list) {
// 注意这里得到的n是其上边界类型的, 也就是Number,需要将其转换为double.
s += n.doubleValue();
}
return s;
}
public static void main(String[] args) {
List<Integer> list1 = Arrays.asList(1, 2, 3, 4);
System.out.println(sumOfList(list1));
List<Double> list2 = Arrays.asList(1.1, 2.2, 3.3, 4.4);
System.out.println(sumOfList(list2));
}
public static void addTest2(List extends Number> l) {
// l.add(1); // 编译报错
// l.add(1.1); // 编译报错
l.add(null);
Number number = l.get(1); // 正常
}
目的跟第一种通配符类似,就是编译器其实是把运行时可能出现的异常放在编译阶段来检查。
但是,我们可以保证不管参数是什么泛型,里面的元素肯定是Number或者其子类,所以,从List中获取一个Number元素的get()方法是允许的。
3. 固定下边界通配符:
public static void addNumbers(List super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
public static void main(String[] args) {
List<Object> list1 = new ArrayList<>();
addNumbers(list1);
System.out.println(list1);
List<Number> list2 = new ArrayList<>();
addNumbers(list2);
System.out.println(list2);
List<Double> list3 = new ArrayList<>();
// addNumbers(list3); // 编译报错
}
public static void getTest2(List super Integer> list) {
// Integer i = list.get(0); //编译报错
Object o = list.get(1);
}
目的跟第一种通配符类似,就是编译器其实是把运行时可能出现的异常放在编译阶段来检查。
但是,我们可以保证不管参数是什么泛型,里面的元素肯定是Integer,所以,从List中add一个Integer元素的add()方法是允许的。
import java.util.Comparator;
import java.util.TreeSet;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Student extends Person {
public Student(String name, int age) {
super(name, age);
}
}
class comparatorTest1 implements Comparator<Person> {
@Override
public int compare(Person s1, Person s2) {
int num = s1.getAge() - s2.getAge();
return num == 0 ? s1.getName().compareTo(s2.getName()) : num;
}
}
public class Test {
public static void main(String[] args) {
TreeSet<Student> ts2 = new TreeSet<>(new comparatorTest1());
ts2.add(new Student("Susan", 23));
ts2.add(new Student("Rose", 27));
ts2.add(new Student("Jane", 19));
for (Student stu : ts2) {
System.out.println(stu.getName() + ":" + stu.getAge());
}
}
}
注意:
通过查看TreeSet源码得知,构造方法TreeSet(Comparator super E> comparator)中的E,来源于泛型类 TreeSet,在这里就是变量ts2的类型TreeSet 中的Student 。
因为泛型限定是 super E>,即 super Student>,所以Comparator的泛型必须是Student的父类,即Person。
有人将上面的原则总结了一下,写作"in out"原则, 归纳起来就是: