为什么要有泛型

举例

泛型:标签

中药店里,每个抽屉外面都贴着标签

超市购物架上有很多瓶子,每个瓶子装的是什么,有标签

泛型的设计背景

集合容器类在设计阶段/生命阶段不能确定这个容器到底存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Objcet,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他部分是确定的,例如关于这个元素如何保存,如何管理(增改删查的方法等)等是确定的。因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection,List,ArrayList 这个就是类型参数,即泛型。

其他说明

  • 所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。

  • 从JDK1.5以后,Java引入了“参数化类型(Parameterizedtype)”的概念,允许我们在创建集合时再指定集合元素的类型,正如:List,这表明该List只能保存字符串类型的对象。

  • JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。

那么为什么要有泛型呢?

那么为什么要有泛型呢,直接Object不是也可以存储数据吗?

  1. 解决元素存储的安全性问题,好比商品、药品标签,不会弄错。例如我只想往集合中添加String类型的元素,但其实任意类型的元素都可以随便放入集合,所以类型不安全。

  2. 解决获取数据元素时,需要类型强制转换的问题,好比不用每回拿商品、药品都要辨别。例如每次想存入Integer类型时,实际上都是存入的Object类型,读取的时候还需要将Object类型强转成Integer类型,如果不小心存入了String类型的数据,那么将它强转成Integer类型也会发生错误,还是不安全。下面比较一下使用泛型和不使用泛型的代码量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//不使用泛型,需要先判断类型,再进行强转
TreeSet<Student> treeSet = new TreeSet<>(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Student && o2 instanceof Student) {
Student s1 = (Student) o1;
Student s2 = (Student) o2;
int res = s1.getName().compareTo(s2.getName());
if (res == 0) {
return Integer.compare(s1.getId(), s2.getId());
} else return res;
} else throw new RuntimeException("输入数据类型不匹配");
}
});
//使用泛型
TreeMap treeMap = new TreeMap<Person, Integer>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
int res = -o1.getName().compareTo(o2.getName());
if(res==0) {
return -Integer.compare(o1.getId(), o2.getId());
} else return res;
}
});
  1. Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁、健壮。例如下面的代码,在编译期就会报’java.util.ArrayList’ 中的 ‘add(java.lang.Integer)’ 无法应用于 ‘(java.lang.String)’
1
2
ArrayList<Integer> list = new ArrayList<>();
list.add("张三");

在集合中使用泛型

  1. 集合接口或集合类在jdk5.0时都修改为带泛型的结构。

  2. 在实例化集合类时,可以指明具体的泛型类型

  3. 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。例如:add(E e) —>实例化以后:add(Integer e)

  4. 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。

注意:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换

  • 以HashMap为例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void test01() {
HashMap<String, Integer> map = new HashMap<>();
map.put("张三", 167);
map.put("李四", 972);
map.put("王五", 348);
map.put("赵六", 565);
map.put("孙七", 415);
map.put("周八", 671);
//泛型的嵌套
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
String key = entry.getKey();
int value = entry.getValue();
System.out.println("key=" + key + " value=" + value);
}
}
  • 以ArrayList为例
1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test02(){
ArrayList<Integer> list = new ArrayList<>();
list.add(213);
list.add(231);
list.add(123);
list.add(132);
list.add(312);
list.add(321);
for (int num : list) {
System.out.println(num);
}
}

练习

定义一个Employee类,该类包含: private成员变量name, age, birthday, 其中birthday为MyDate类的对象;
并为每一个属性定义getter, setter方法;并重写toString 方法输出 name, age, birthday

MyDate类包含:private 成员变量 month,day,year;并为每一个属性定义getter, setter方法;

创建该类的5个对象,并把这些对象放入TreeSet集合中(TreeSet需使用泛型来定义),
分别按以下两种方式对集合中的元素进行排序,并遍历输出:

  1. 使Employee 继承Comparable接口,并按name排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class Employee implements Comparable<Employee>{
private String name;
private int age;
private MyDate birthday;

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;
}

public MyDate getBirthday() {
return birthday;
}

public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}

public Employee(String name, int age, MyDate birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}

@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
'}';
}

@Override
public int compareTo(Employee o) {
return this.name.compareTo(o.name);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class MyDate {
private int year;
private int month;
private int day;

public int getYear() {
return year;
}

public void setYear(int year) {
this.year = year;
}

public int getMonth() {
return month;
}

public void setMonth(int month) {
this.month = month;
}

public int getDay() {
return day;
}

public void setDay(int day) {
this.day = day;
}

public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}

@Override
public String toString() {
return "MyDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void test03() {
TreeSet<Employee> employees = new TreeSet<>();
employees.add(new Employee("A张三", 18, new MyDate(2004, 1, 7)));
employees.add(new Employee("C张三", 55, new MyDate(1967, 11, 17)));
employees.add(new Employee("E张三", 49, new MyDate(1973, 12, 22)));
employees.add(new Employee("D张三", 18, new MyDate(2004, 1, 21)));
employees.add(new Employee("B张三", 18, new MyDate(2004, 8, 17)));
for (Employee e : employees) {
System.out.println(e);
}
}
/*
按name排序
Employee{name='A张三', age=18, birthday=MyDate{year=2004, month=1, day=7}}
Employee{name='B张三', age=18, birthday=MyDate{year=2004, month=8, day=17}}
Employee{name='C张三', age=55, birthday=MyDate{year=1967, month=11, day=17}}
Employee{name='D张三', age=18, birthday=MyDate{year=2004, month=1, day=21}}
Employee{name='E张三', age=49, birthday=MyDate{year=1973, month=12, day=22}}
*/
  1. 创建TreeSet时传入Comparator对象,按生日日期的先后排序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Test
public void test04() {
TreeSet<Employee> employees = new TreeSet<>(new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
int yearCompare = Integer.compare(o1.getBirthday().getYear(), o2.getBirthday().getYear());
if (yearCompare == 0) {
int monthCompare = Integer.compare(o1.getBirthday().getMonth(), o2.getBirthday().getMonth());
if (monthCompare == 0) {
return Integer.compare(o1.getBirthday().getDay(), o2.getBirthday().getDay());
} else return monthCompare;
} else return yearCompare;
}
});
employees.add(new Employee("A张三", 18, new MyDate(2004, 1, 7)));
employees.add(new Employee("C张三", 55, new MyDate(1967, 11, 17)));
employees.add(new Employee("E张三", 49, new MyDate(1973, 12, 22)));
employees.add(new Employee("D张三", 18, new MyDate(2004, 1, 21)));
employees.add(new Employee("B张三", 18, new MyDate(2004, 8, 17)));
for (Employee e : employees) {
System.out.println(e);
}
}
/*
按生日排序
Employee{name='C张三', age=55, birthday=MyDate{year=1967, month=11, day=17}}
Employee{name='E张三', age=49, birthday=MyDate{year=1973, month=12, day=22}}
Employee{name='A张三', age=18, birthday=MyDate{year=2004, month=1, day=7}}
Employee{name='D张三', age=18, birthday=MyDate{year=2004, month=1, day=21}}
Employee{name='B张三', age=18, birthday=MyDate{year=2004, month=8, day=17}}
*/

自定义泛型结构

自定义泛型类举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class OrderTest<T> {
String orderNmae;
int orderId;
T ordetT;

public OrderTest(String orderNmae, int orderId, T ordetT) {
this.orderNmae = orderNmae;
this.orderId = orderId;
this.ordetT = ordetT;
}

public OrderTest() {

}

@Override
public String toString() {
return "OrderTest{" +
"orderNmae='" + orderNmae + '\'' +
", orderId=" + orderId +
", ordetT=" + ordetT +
'}';
}

public String getOrderNmae() {
return orderNmae;
}

public void setOrderNmae(String orderNmae) {
this.orderNmae = orderNmae;
}

public int getOrderId() {
return orderId;
}

public void setOrderId(int orderId) {
this.orderId = orderId;
}

public T getOrdetT() {
return ordetT;
}

public void setOrdetT(T ordetT) {
this.ordetT = ordetT;
}
}
1
2
3
//SubOrder:不是泛型类 已经指明了泛型是Integer
public class SubOrder extends OrderTest<Integer>{
}
1
2
3
//SubOrder1<T>:仍然是泛型类
public class SubOrder1<T> extends OrderTest<T> {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test01() {
/*
通过泛型指定类型为String 那么构造器中
public OrderTest(String orderNmae, int orderId, T ordetT)
T的类型就是String
*/
OrderTest<String> orderTest = new OrderTest<>("A张三", 18, "饲养员");
//由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型。
SubOrder subOrder = new SubOrder();
subOrder.setOrdetT(23);
//subOrder1在继承父类时没有指定泛型,所以这里还需要自己指定一下
SubOrder1<String> subOrder1 = new SubOrder1<>();
subOrder1.setOrdetT("李四");
}

自定义泛型类泛型结构的注意点

  1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>

  2. 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。

  3. 泛型不同的引用不能相互赋值。(将String泛型结构赋值给Integer)

  4. 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。

  5. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。

  6. 泛型的指定中不能使用基本数据类型,可以使用包装类替换。

  7. 静态方法中不能使用类的泛型。因为静态方法早于类的加载(我还没声明T是什么类型,你就要用了)

1
2
3
public static void show(T ordetT){
System.out.println(ordetT);
}

在继承中使用泛型

  • 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:

    • 子类不保留父类的泛型:按需实现

      • 没有类型擦除

      • 具体类型

    • 子类保留父类的泛型:泛型子类

      • 全部保留

      • 部分保留

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Father<T1, T2> {}

// 子类不保留父类的泛型
// - 没有类型擦除
class Son1 extends Father {// 等价于class Son extends Father<Object,Object>{}
}
// - 具体类型
class Son2 extends Father<Integer, String> {}


// 子类保留父类的泛型
// - 全部保留
class Son3<T1, T2> extends Father<T1, T2> {}
// - 部分保留
class Son4<T2> extends Father<Integer, T2> {}

自定义泛型方法举例

  • 泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。

  • 换句话说,泛型方法所属的类是不是泛型类都没有关系。

  • 泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的,与类的实例化无关。

举例:在之前的OrderTest类中,添加以下泛型方法

1
2
3
4
5
6
7
public <E> List<E> copyFromArrayToList(E[] arr) {
ArrayList<E> arrayList = new ArrayList<>();
for (E e : arr) {
arrayList.add(e);
}
return arrayList;
}

类的泛型参数设置为String,而泛型方法的参数为Integer

1
2
3
4
5
6
7
8
9
@Test
public void test02() {
OrderTest<String> orderTest = new OrderTest<>();
Integer[] ints = new Integer[]{1,2,3,4,5};
List<Integer> list = orderTest.copyFromArrayToList(ints);
for (int num : list) {
System.out.println(num);
}
}

在之前的泛型方法上加上static关键字修饰

1
2
3
4
5
6
7
public static <E> List<E> copyFromArrayToList(E[] arr) {
ArrayList<E> arrayList = new ArrayList<>();
for (E e : arr) {
arrayList.add(e);
}
return arrayList;
}
1
2
3
4
5
6
7
8
@Test
public void test02() {
Integer[] ints = new Integer[]{1,2,3,4,5};
List<Integer> list = OrderTest.copyFromArrayToList(ints);
for (int num : list) {
System.out.println(num);
}
}

泛型类和泛型方法的使用情景

例如我们要对数据库中的多张表,进行增删改查操作,那我们可以创建一个泛型类DAO data(base) access object
那么我们可以为数据库中的每一张表定义一个类,那么对数据库中的增删改查操作,就可以用DAO类中提供的增删改查操作来完成,同时DAO类的泛型参数也就是每一张表对应的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class DAO<T> {
//添加一条记录
public void add(T t) {

}

//删除一条记录
public boolean remove(int index) {

return false;
}

//修改一条记录
public void update(int index, T t) {

}

//查询一条记录
public T getIndex(int index) {

return null;
}

//查询多条记录
public List<T> getForList(int index) {

return null;
}

//泛型方法
//举例:获取表中一共有多少条记录?获取最大的员工入职时间?
public <E> E getValue() {

return null;
}
}
1
2
3
//此类对应数据库中的customers表
public class Customer {
}
1
2
3
//只能操作某一个表的DAO
public class CustomerDAO extends DAO<Customer>{
}
1
2
3
//此类对应数据库中的student表
public class Student {
}
1
2
3
//只能操作某一个表的DAO
public class StudentDAO extends DAO<Student>{
}
1
2
3
4
5
6
7
8
9
10
11
12
public class DAOTest {
@Test
public void test() {
CustomerDAO customerDAO = new CustomerDAO();
customerDAO.add(new Customer());
List<Customer> customerList = customerDAO.getForList(10);

StudentDAO studentDAO = new StudentDAO();
studentDAO.add(new Student());
Student student = studentDAO.getIndex(1);
}
}

泛型在继承上的体现——通配符

泛型在继承方面的体现

虽然类A是类B的父类,但是G<A> 和G<B>二者不具备子父类关系,二者是并列关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test1() {
Object obj = new Object();
String str = new String();
obj = str;

Object[] arr1 = null;
String[] arr2 = null;
arr1 = arr2;

ArrayList<Object> objects = new ArrayList<>();
ArrayList<String> strings = new ArrayList<>();
//objects = strings;
//编译不通过,此时的objects和strings不具备子父类关系
}

类A是类B的父类,A<G> 是 B<G> 的父类

1
2
3
4
5
6
@Test
public void test2() {
ArrayList<Integer> arrayList = new ArrayList<>();
List<Integer> list = null;
list = arrayList;
}

通配符的使用

  • 通配符:“?”

  • List<?>是List、ArrayList等各种泛型List的父类。

  • 读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。但是我们不能向其中添加对象,null除外,因为它是所有类型的成员

1
2
3
List<?> objects = new ArrayList<>();
objects.add(null);
//objects.add(123); 不允许
  • 我们可以调用get()方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个Object。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Test
public void test3(){
ArrayList<Object> objects = new ArrayList<>();
objects.add(123);
objects.add("张三");
objects.add(new Date());

ArrayList<Integer> integers = new ArrayList<>();
integers.add(9527);
integers.add(4396);
integers.add(5421);

print(objects);
System.out.println("************");
print(integers);
System.out.println("***********");
ArrayList<?> list = integers;
Object o1 = list.get(0);
Object o2 = list.get(1);
Object o3 = list.get(2);
System.out.println(o1);
System.out.println(o2);
System.out.println(o3);
}

public void print(List<?> list){
for (Object o : list) {
System.out.println(o);
}
}

有限制条件的通配符的使用

  • 允许所有泛型的引用调用
  • 通配符指定上限上限,extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<=

  • 通配符指定下限,super:使用时指定的类型不能小于操作的类,即>=

  • 举例

    • (无穷小, Number],只允许泛型为Number及Number子类的引用调用
    • [Number , 无穷大),只允许泛型为Number及Number父类的引用调用
    • ,只允许泛型为实现Comparable接口的实现类的引用调用
1
2
public class Person {
}
1
2
public class Student extends Person{
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Test
public void test4() {
List<? extends Person> list1 = null;
List<? super Person> list2 = null;

ArrayList<Student> students = new ArrayList<>();
ArrayList<Person> people = new ArrayList<>();
ArrayList<Object> objects = new ArrayList<>();

list1 = students;
list1 = people;
//list1=objects; //编译不通过 objects不是Person本身或子类

//list2=students; //编译不通过,students不是Person本身或父类
list2 = people;
list2 = objects;

//读取数据
list1 = students;
Person p = list1.get(0);
//Student s = list1.get(0); //编译不通过,list1中可能存在比Student大的类,这样可能将父类对象赋给子类 list1(负无穷,Person]

list2 = people;
Object object = list2.get(0);
//Person person = list2.get(0); //编译不通过,理由同上

//写入数据
//list1.add(new Student()); //编译不通过,list1中可能存在比Student小的类,这样可能将父类对象赋给子类 list1(负无穷,Person]
list2.add(new Student());
list2.add(new Person());
}

自定义泛型类练习

  • 定义个泛型类DAO<T>,在其中定义一个Map成员变量,Map的键为String类型,值为T类型。

  • 分别创建以下方法:

    • public void save(String id,T entity): 保存T类型的对象到Map成员变量中

    • public T get(String id): 从map 中获取id 对应的对象

    • public void update(String id,T entity): 替换map 中 key为id 的内容,改为entity对象

    • public List<T> list(): 返回map中存放的所有T对象

    • public void delete(String id): 删除指定id 对象

  • 定义一个user类:

    • 该类包含: private成员变量(int类型)id,age; (String类型)name。
  • 定义一个测试类:

    • 创建DAO类的对象,分别调用其save、get、update、list、delete方法来操作User对象,

    • 使用Junit单元测试类进行测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class DAO<T> {
Map<String, T> map = new HashMap<>();

public void save(String id, T entity) {
map.put(id, entity);
}

public T get(String id) {
return map.get(id);
}

public void update(String id, T entity) {
if(map.containsKey(id))
map.put(id,entity);
}

public List<T> list() {
ArrayList<T> list = new ArrayList<>();
for (T t : map.values()) {
list.add(t);
}
return list;
}

public void delete(String id) {
map.remove(id);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class User {
private int id;
private int age;
private String name;

public User() {
}

public User(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return id == user.id && age == user.age && Objects.equals(name, user.name);
}

@Override
public int hashCode() {
return Objects.hash(id, age, name);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class FinalTest {
public static void main(String[] args) {
DAO<User> userDAO = new DAO<>();
userDAO.save("001", new User(1001, 18, "Faker"));
userDAO.save("002", new User(1002, 18, "Viper"));
userDAO.save("003", new User(1003, 18, "ShowMaker"));
for (User user : userDAO.list()) {
System.out.println(user);
}
System.out.println("*******************");
userDAO.update("002", new User(1002, 19, "UZI"));
for (User user : userDAO.list()) {
System.out.println(user);
}
System.out.println("*******************");
System.out.println(userDAO.get("003"));
System.out.println("*******************");
userDAO.delete("1001");
for (User user : userDAO.list()) {
System.out.println(user);
}
}
}