JavaEE - 12泛型
(1)为什么要有泛型(Generic)
(1.1)泛型的引入
- 泛型: 标签。 中药店,每个抽屉外面贴着的标签。
- 集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,在JDK1.5之前只能把元素类型设计为Object,在JDK1.5之后使用泛型来解决。
- 除了元素的类型不确定,其他的部分都是确定的,例如:如何保存元素、如何管理。因此把元素的类型设计成一个参数,这个类型参数叫做泛型。
- Collection<E>,List<E>,ArrayList<E> 这个<E>就是类型参数,即泛型。
- public interface Collection<E> extends Iterable<E> {}
(1.2)泛型概念
- 泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者某个方法的返回值及参数类型。
- 这个类型参数将在使用时(继承或实现这个接口,用这个类型声明变量、创建对象时)确定。即传入实际的类型参数,也称为类型实参。
- JDK1.5以后,Java引入了"参数化类型(Parameterized type)"的概念,允许创建集合时再指定集合元素的类型。
- List<String>, 表明该List 只能保存字符串类型的对象。
- JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而在声明集合变量、创建集合对象时传入类型实参。
(1.3)为什么引入泛型
- 解决元素存储的安全性问题,好比商品、药品标签,不会弄错。 直接用Object,任何类型都可以添加到集合中,类型不安全。
- 解决获取数据元素时,需要类型强制转换的问题, 好比不用每回拿商品、药品都要辨别。可能有ClassCastException。
@Test public void test1(){ ArrayList list = new ArrayList(); // 存放学生的考试成绩 list.add(78); list.add(99); // 类型不安全 // list.add("Tom"); for(Object obj: list){ int stuScore = (Integer) obj; //java.lang.ClassCastException System.out.println(stuScore); } }
(2)在集合中使用泛型
- 集合接口或集合类在JDK5.0时都修改为带泛型的结构。
- 在实例化集合类时,可以指明具体的泛型类型。
- 指明以后,在集合类或接口中凡是定义类或接口时,内部结构(方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
- 比如: add(E e) --> 实例化以后:add(Integet e)
- 注意点: 泛型的类型必须是类,不能是基本数据类型,需要用到基本数据类型的位置,使用包装类。
- 如果实例化时,没有指明泛型的类型,默认类型为java.lang.Object类型。
- JDK7新特性: 类型推断 Map<String, Integer> map = new HashMap<>();
(2.1)List列表
@Test public void test2(){ //ArrayList<int> list = new ArrayList<int>(); ArrayList<Integer> list = new ArrayList<Integer>(); list.add(78); list.add(99); // list.add("Tom"); 添加报错 Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()){ Integer stuScore = iterator.next(); System.out.println(stuScore); } }
(2.2)Map集合
@Test public void test3(){ Map<String, Integer> map = new HashMap<>(); // 类型推断 map.put("zhangsan",23); map.put("007",13); System.out.println(map); //{007=13, zhangsan=23} Set<Map.Entry<String, Integer>> entries = map.entrySet(); System.out.println(entries); //{007=13, zhangsan=23} }
(2.3)自然排序
public class Person implements Comparable<Person> { //public class Person implements Comparable {
// 没有指明泛型时的写法 // @Override // public int compareTo(Object o){ // if(o instanceof Person){ // Person p = (Person) o; // return -this.name.compareTo(p.name); // }else { // throw new RuntimeException("输入的类型不匹配"); // } // } @Override public int compareTo(Person o){ return -this.name.compareTo(o.name); } }
@Test public void test4(){ TreeSet<Person> set = new TreeSet<>(); Person p1 = new Person("Tom",22); Person p2 = new Person("Sun",24); set.add(p1); set.add(p2); Iterator<Person> iterator = set.iterator(); while (iterator.hasNext()){ Person person = iterator.next(); System.out.println(person); // Person{name='Tom', age=22} Person{name='Sun', age=24} } }
(2.4)定制排序
@Test public void test4(){ Comparator<Person> com = new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { return o1.getName().compareTo(o2.getName()); } }; TreeSet<Person> set = new TreeSet<>(com); Person p1 = new Person("Tom",22); Person p2 = new Person("Sun",24); set.add(p1); set.add(p2); Iterator<Person> iterator = set.iterator(); while (iterator.hasNext()){ Person person = iterator.next(); System.out.println(person); // Person{name='Sun', age=24} Person{name='Tom', age=22} } }
(3)自定义泛型结构
- 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。如:<E1,E2,E3>
- 泛型类的构造器:public GenericClass(){}。 错误的:public GenericClass<E>(){}
- 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
- 泛型不同的引用不能相互赋值。
- 尽管在编译时ArrayList<String>和ArrayList<Integer>是两种类型,但在运行时只有一个ArrayList被加载到JVM中。
- 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。经验: 泛型要使用一路都用。要不用,一路都不要用。
- 如果泛型类是一个接口或抽象类,则不可创建泛型类的对象。
- JDK1.7,泛型的简化操作: ArrayList<Fruit> flist = new ArrayList<>();
- 泛型的指定中不能使用基本数据类型,可以使用包装类替换。
- 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型与返回值类型。但在静态方法中不能使用类的泛型。
- 异常类不能是泛型的。编译不通过。
- 不能使用new E[]。 但是可以 E[] elements = (E[])new Object[capacity]; 参考ArrayList源码中声明:Object[] elementDat,而非泛型参数类型数组。
- 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
- 子类不保留父类的泛型: 按需实现
- 没有类型 擦除
- 具体类型
- 子类保留父类的泛型: 泛型子类
- 全部保留
- 部分保留
- 子类除了指定或保留父类的泛型,还可以增加自己的泛型。
- 子类不保留父类的泛型: 按需实现
(3.1)定义泛型类、泛型接口
public class Order<T> { int orderId; String orderName; // 类的内部结构 使用类的泛型 T orderT; public Order(){} public Order(int orderId, String orderName, T orderT) { this.orderId = orderId; this.orderName = orderName; this.orderT = orderT; } // 省略其他代码 }
@Test public void test5(){ // 定义了泛型类,实例化没有指明类的泛型,泛型类型为Object类型 Order order = new Order(); order.setOrderT(123); order.setOrderT("ac"); System.out.println(order); //Order{orderId=0, orderName='null', orderT=ac} // 要求: 如果定义了类是带泛型的,在实例化时要指明类的泛型。 Order<String> order1 = new Order<String>(123,"NewOrder1","123"); //order1.setOrderT(123); 报错 order1.setOrderT("456"); System.out.println(order1); //Order{orderId=123, orderName='NewOrder1', orderT=456} }
子类在继承带泛型的父类时,指明了泛型类型,则在实例化子类对象时,不再需要指明泛型。
public class SubOrder extends Order<Integer> { }
@Test public void test6(){ SubOrder subOrder = new SubOrder(); subOrder.setOrderT(1102); System.out.println(subOrder); //Order{orderId=0, orderName='null', orderT=1102} }
子类在继承带泛型的父类时,继承泛型类型
public class NewSubOrder<T> extends Order<T> { // 仍然是泛型类 }
@Test public void test7(){ NewSubOrder subOrder = new NewSubOrder(); subOrder.setOrderT(1102); System.out.println(subOrder); //Order{orderId=0, orderName='null', orderT=1102} NewSubOrder<String> subOrder1 = new NewSubOrder<>(); subOrder1.setOrderT("1104"); System.out.println(subOrder1); //Order{orderId=0, orderName='null', orderT=1104} }
泛型不同的引用 不能相互赋值。
@Test public void test8(){ ArrayList<String> list1 = null; ArrayList<Integer> list2 = new ArrayList<Integer>(); // 泛型不同的引用 不能相互赋值。 // list1 = list2; Person p1 = null; Person p2 = null; p1 = p2; }
不能使用new E[]。
public Order(){ // T[] arr = new T[10]; T[] arr1 = (T[])new Object[10]; }
子类继承带泛型的父类
class Father<T1,T2> { } // 1) 子类不保留父类泛型 - 没有类型、擦除: 等价于 class Son1 extends Father<Object,Object> class Son1 extends Father{ } // 2) 子类不保留父类泛型 - 具体类型 class Son2 extends Father<Integer, String>{ } // 3) 子类保留父类泛型 - 全部保留 class Son3<T1,T2> extends Father<T1, T2>{ } // 4) 子类保留父类泛型 - 部分保留 class Son4<T2> extends Father<Integer, T2>{ }
(3.2)泛型方法
- 在方法中出现泛型的结构,泛型参数与类的泛型参数没有任何关系。泛型方法所属的类是不是泛型类都没有关系。
- 泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的,并非在实例化类时确定。
如下方法不是泛型方法
public T getOrderT() { return orderT; } public void setOrderT(T orderT) { this.orderT = orderT; }
泛型方法
public <E> List<E> copyFromArrayToList(E[] arr){ ArrayList<E> list = new ArrayList<>(); for(E e : arr){ list.add(e); } return list; } @Test public void test9() { Integer[] arr = new Integer[]{1, 2, 3, 4, 5}; // 泛型方法在调用时,指明泛型参数的类型 List<Integer> list = copyFromArrayToList(arr); System.out.println(list); }
静态泛型方法
public static <E> List<E> copyFromArrayToList(E[] arr){ ArrayList<E> list = new ArrayList<>(); for(E e : arr){ list.add(e); } return list; } @Test public void test9() { Integer[] arr = new Integer[]{1, 2, 3, 4, 5}; List<Integer> list = GenericTest.copyFromArrayToList(arr); System.out.println(list); }
(4)泛型在继承上的体现
类A是类B的父类, G<A> 和 G<B> 二者不具备子父类关系,二者是并列关系。
@Test public void test10(){ Object object = null; String string = null; object = string; Object[] arr1 = null; String[] arr2 = null; arr1 = arr2; List<Object> list1 = null; List<String> list2 = null;
// 此时list1 和 list2 的类型不具有子父类关系 ;编译不通过 // list1 = list2; // Required type: List<Object> Provided : List<String> }
类A是类B的父类, A<G> 是 B<G> 的父类
@Test public void test11(){ AbstractList<String> list1 = null; List<String> list2 = null; ArrayList<String> list3 = null; list1 = list3; list2 = list3; }
public class Dao<T> { // 添加一条记录 public void add(T t){ } // 删除一条记录 public boolean remove(int index){ return false; } public List<T> getForList(int index){ return null; } // 泛型方法: 获取表中一共有多少条记录? 获取最大的员工年龄 public <E> E getValue(){ return null; } }
public class PersonDao extends Dao<Person> { }
@Test public void test1(){ PersonDao personDao = new PersonDao(); personDao.add(new Person()); List<Person> personList = personDao.getForList(10); }
(5)通配符的使用
- 使用类型通配符:? 如List<?> Map<?,?>。List<?>是 List<String>、List<Object>等各种泛型List的父类。
- 读取List<?> 的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。
- 写入list中的元素时,不行。因为不知道c的元素类型,不能向其中添加对象。唯一的例外是null,它是所有类型的成员。
- <?> 允许所有泛型的引用调用
- 通配符指定上限: 上限extends: 使用时指定的类型必须是继承某个类,或者实现某个接口,即 <=
- 通配符指定下限: 下限super: 使用时指定的类型不能小于操作的类, 即>=
- <? extends Number> (无穷下,Number] 只允许泛型为Number及Number子类的引用调用
- <? super Number> [Number,无穷大) 只允许泛型为Number及Number父类的引用调用
- <? extends Comparable> 只允许泛型为实现Comparable接口的实现类的引用调用
类A 是类B的父类, G<A> 和G<B> 是没有关系的, 二者共同的父类是: G<?>
@Test public void test12() throws NullPointerException { List<Object> list1 = null ; List<String> list2 = null; List<?> list = null; list = list1; list = list2; print(list1); print(list2); }
public void print(List<?> list) { Iterator<?> iterator = list.iterator(); while (iterator.hasNext()){ Object obj = iterator.next(); System.out.println(obj); } }
List<?> 不能添加数据, 除了null。可以读取数据,数据类型为Object。
@Test public void test12(){ List<String> list3 = new ArrayList<>(); list3.add("AAA"); List<?> list = new ArrayList<>(); list = list3; // list.add("DDD"); // 对于List<?> 就不能向其内部添加数据。除了null之外。 System.out.println(list); // [AAA] Object o = list.get(0); // 允许读取数据,数据类型为Object System.out.println(o); //AAA }
public void test13(){ List<? extends Person> list1 = null; List<? super Person> list2 = null; List<Student> list3 = null; List<Person> list4 = null; List<Object> list5 = null; list1 = list3; list1 = list4; // list1 = list5; // list1 的范围(无穷, Person] // list2 = list3; // list2 的范围 [Person, 无穷) list2 = list4; list2 = list5; Person p1 = list1.get(0); // Student s1 = list1.get(0); //编译不通过 Object o1 = list2.get(0); // Person p2 = list2.get(0); // 编译不通过 // list1.add(new Student()); // 编译不通过 // list1.add(new Person()); // 编译不通过 // list1.add(new Object()) // 编译不通过 list2.add(new Person()); list2.add(new Student()); }
(6)泛型应用
(6.1)泛型嵌套
@Test public void test14(){ HashMap<String, ArrayList<Person>> map = new HashMap<>(); ArrayList<Person> list = new ArrayList<>(); list.add(new Person("孙悟空",1000)); list.add(new Person("猪八戒",400)); list.add(new Person("沙僧",100)); list.add(new Person("唐僧",30)); map.put("唐僧",list); Set<Map.Entry<String, ArrayList<Person>>> entrySet = map.entrySet(); Iterator<Map.Entry<String, ArrayList<Person>>> iterator = entrySet.iterator(); while (iterator.hasNext()){ Map.Entry<String, ArrayList<Person>> entry = iterator.next(); String key = entry.getKey(); ArrayList<Person> value = entry.getValue(); System.out.println("领导:" + key); //领导:唐僧 //团队成员[Person{name='孙悟空', age=1000}, Person{name='猪八戒', age=400}, Person{name='沙僧', age=100}, Person{name='唐僧', age=30}] System.out.println("团队成员" + value); } }
通过构造器设置信息属性内容
public class Employee<T extends Info> { private T info; public Employee(T info){ this.info = info; } public T getInfo() { return info; } public void setInfo(T info) { this.info = info; } @Override public String toString() { return "Employee{" + "info=" + info + '}'; } }
(6.2)案例: User - Dao - UserDao - UserTest
User.class
public class User { private int id; private String name; private int age; //省略其他代码 }
Dao.class
public class Dao<T> { private Map<String,T> map = new HashMap<>(); // 保存 T类型的对象到 Map成员变量中 public void save(String id, T entity){ map.put(id,entity); } // 从map中获取id对应的对象 public T get(String id){ return map.get(id); } // 替换map中key 为id的内容,改为entity对象 public void update(String id, T entity){
if(map.containsKey(id){ map.put(id, entity);
} } // 返回 map中存放的所有 T 对象 public List<T> list(){ List<T> list = new ArrayList<>(); for(T t : map.values()){ list.add(t); } return list; } // 删除指定 id 对象 public void delete(String id){ map.remove(id); } }
UserDao.class
public class UserDao extends Dao<User> { }
UserTest.class
@Test public void test1(){ UserDao userDao = new UserDao(); User u1 = new User(1,"AAA",13); User u2 = new User(2,"BBB",23); User u3 = new User(3,"CCC",15); userDao.save("AAA",u1); userDao.save("BBB",u2); userDao.save("CCC",u3); List<User> list = userDao.list(); System.out.println(list); //[User{id=1, name='AAA', age=13}, User{id=3, name='CCC', age=15}, User{id=2, name='BBB', age=23}] User user = userDao.get("AAA"); System.out.println(user);//User{id=1, name='AAA', age=13} userDao.update("CCC", new User(13, "CCC", 18)); userDao.save("Tom",new User(4, "Tom",21)); userDao.delete("BBB"); List<User> list1 = userDao.list(); System.out.println(list1); //[User{id=1, name='AAA', age=13}, User{id=13, name='CCC', age=18}, User{id=4, name='Tom', age=21}] }