为什么要有泛型
举例
泛型:标签
中药店里,每个抽屉外面都贴着标签
超市购物架上有很多瓶子,每个瓶子装的是什么,有标签
泛型的设计背景
集合容器类在设计阶段/生命阶段不能确定这个容器到底存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Objcet,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他部分是确定的,例如关于这个元素如何保存,如何管理(增改删查的方法等)等是确定的。因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection,List,ArrayList 这个就是类型参数,即泛型。
其他说明
-
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
-
从JDK1.5以后,Java引入了“参数化类型(Parameterizedtype)”的概念,允许我们在创建集合时再指定集合元素的类型,正如:List,这表明该List只能保存字符串类型的对象。
-
JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。
那么为什么要有泛型呢?
那么为什么要有泛型呢,直接Object不是也可以存储数据吗?
-
解决元素存储的安全性问题,好比商品、药品标签,不会弄错。例如我只想往集合中添加String类型的元素,但其实任意类型的元素都可以随便放入集合,所以类型不安全。
-
解决获取数据元素时,需要类型强制转换的问题,好比不用每回拿商品、药品都要辨别。例如每次想存入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; } });
|
- Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁、健壮。例如下面的代码,在编译期就会报’java.util.ArrayList’ 中的 ‘add(java.lang.Integer)’ 无法应用于 ‘(java.lang.String)’
1 2
| ArrayList<Integer> list = new ArrayList<>(); list.add("张三");
|
在集合中使用泛型
-
集合接口或集合类在jdk5.0时都修改为带泛型的结构。
-
在实例化集合类时,可以指明具体的泛型类型
-
指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。例如:add(E e) —>实例化以后:add(Integer e)
-
如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。
注意:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
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); } }
|
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需使用泛型来定义),
分别按以下两种方式对集合中的元素进行排序,并遍历输出:
- 使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); } }
|
- 创建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); } }
|
自定义泛型结构
自定义泛型类举例
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
| public class SubOrder extends OrderTest<Integer>{ }
|
1 2 3
| 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() {
OrderTest<String> orderTest = new OrderTest<>("A张三", 18, "饲养员"); SubOrder subOrder = new SubOrder(); subOrder.setOrdetT(23); SubOrder1<String> subOrder1 = new SubOrder1<>(); subOrder1.setOrdetT("李四"); }
|
自定义泛型类泛型结构的注意点
-
泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
-
实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
-
泛型不同的引用不能相互赋值。(将String泛型结构赋值给Integer)
-
泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。
-
如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
-
泛型的指定中不能使用基本数据类型,可以使用包装类替换。
-
静态方法中不能使用类的泛型。因为静态方法早于类的加载(我还没声明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 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
| public class Customer { }
|
1 2 3
| public class CustomerDAO extends DAO<Customer>{ }
|
1 2 3
| public class Student { }
|
1 2 3
| 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<>(); }
|
类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; }
|
通配符的使用
1 2 3
| List<?> objects = new ArrayList<>(); objects.add(null);
|
- 我们可以调用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); } }
|
有限制条件的通配符的使用
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;
list2 = people; list2 = objects;
list1 = students; Person p = list1.get(0);
list2 = people; Object object = list2.get(0);
list2.add(new Student()); list2.add(new 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
| 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); } } }
|