Java泛型
1 - 为什么要有泛型?
/*
1 泛型:标签
2 举例:
①中药店,每个抽屉外面贴着标签
②超市购物架上很多瓶子,每个瓶子装的是什么,有标签
3 泛型的设计背景
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的 对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来 解决。因为这个时候除了元素的类型不确定,其他部分是确定的,例如关于 这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个 参数,这个类型参数叫做泛型。Collection<E>,List<E>,ArrayList<E> 这个<E>就 是类型参数,即泛型。
*/
2 - 泛型的概念
/*
1 所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类 型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如, 继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实 际的类型参数,也称为类型实参)。
2 从JDK1.5以后,Java引入了“参数化类型(Parameterized type)”的概念, 允许我们在创建集合时再指定集合元素的类型,正如:List<String>,这表明 该List只能保存字符串类型的对象。
3 JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持, 从而可以在声明集合变量、创建集合对象时传入类型实参
*/
3 - 在集合中使用泛型
package com.lzh.java1; import org.junit.Test; import java.util.*; /* 泛型的使用 1 JDK 5.0新增的特性 2 在集合中使用泛型总结: ①集合接口或集合类在JDK5.0时都修改为带泛型的结构 ②在实例化集合类时,可以指明具体的泛型类型 ③指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型位置,都指定为实例化的泛型类型。 比如:add(E e) --> 实例化以后 --> add(Integer e) ④注意点:泛型的类型必须是类,不能是基本数据类型,需要用到基本数据类型的位置,替换为包装类即可 ⑤如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型 */ public class GenericTest { @Test public void test1(){ // 集合中未使用泛型情况(以ArrayList为例) ArrayList list = new ArrayList(); list.add(96); list.add(88); list.add(45); list.add(60); list.add("howie"); // 问题1:类型不安全 for(Object score:list){ // 问题2:强转时,可能出现ClassCastException int studentScore = (Integer)score; System.out.println(studentScore); } } @Test public void test2(){ // 集合中使用泛型的情况(以ArrayList为例) ArrayList<Integer> list = new ArrayList<Integer>(); list.add(96); list.add(88); list.add(45); list.add(60); // list.add("name"); 编译不通过 // 编译时,就会进行类型检查,保证数据的安全 // Iterator iterator = list.iterator(); // while(iterator.hasNext()){ // System.out.println(iterator.next()); // } // 避免了强转操作 for(Integer score:list){ System.out.println(score); } } @Test public void test3(){ // 在集合中使用泛型(以HashMap为例) HashMap<String,Integer> dict = new HashMap<String,Integer>(); dict.put("alex",21); dict.put("howie",22); dict.put("李白",21); dict.put("韩信",26); // 泛型的嵌套 Set<Map.Entry<String,Integer>> entrySet = dict.entrySet(); Iterator<Map.Entry<String,Integer>> iterator = entrySet.iterator(); while(iterator.hasNext()){ Map.Entry<String, Integer> entry = iterator.next(); System.out.println(entry.getKey()+":"+entry.getValue()); } } }
4 - 自定义泛型结构:泛型类与泛型接口
自定义泛型结构说明
/* 1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如: <E1,E2,E3> 2. 泛型类的构造器如下:public GenericClass(){}。 而下面是错误的:public GenericClass<E>(){} 3. 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。 4. 泛型不同的引用不能相互赋值。 >尽管在编译时ArrayList<String>和ArrayList<Integer>是两种类型,但是,在运行时只有 一个ArrayList被加载到JVM中。 5. 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价 于Object。经验:泛型要使用一路都用。要不用,一路都不要用。 6. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。 7. jdk1.7,泛型的简化操作:ArrayList<Fruit> flist = new ArrayList<>(); 8. 泛型的指定中不能使用基本数据类型,可以使用包装类替换 9. 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态 属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法 中不能使用类的泛型。 10. 异常类不能是泛型的 11. 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity]; 参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。 12.父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型: 1 子类不保留父类的泛型:按需实现 ① 没有类型 擦除 ② 具体类型 2 子类保留父类的泛型:泛型子类 ① 全部保留 ② 部分保留 结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自 己的泛型 */
package com.lzh.java1; import org.junit.Test; import java.util.ArrayList; /* 3 如何自定义泛型结构:泛型类、泛型接口;泛型方法 */ // 泛型类 public class GenericOrder<T> { // T:类的泛型 String orderName; int orderID; // 类的内部结构就可以使用类的泛型 T orderT; public GenericOrder(){} public GenericOrder(String orderName,int orderID,T orderT){ this.orderName = orderName; this.orderID = orderID; this.orderT = orderT; } public T getOrderT(){ return this.orderT; } public void setOrderT(T orderT) { this.orderT = orderT; } @Override public String toString() { return "GenericOrder{" + "orderName='" + orderName + '\'' + ", orderID=" + orderID + ", orderT=" + orderT + '}'; } @Test public void test1(){ // 如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object类型 // 要求:如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。 GenericOrder order = new GenericOrder(); order.setOrderT(123); order.setOrderT("AA"); // 建议:实例化时指明类的泛型 GenericOrder<String> order1 = new GenericOrder<String>("AA",101,"order:123"); order1.setOrderT("order:hello"); } } class SubOrder<T> extends GenericOrder<T>{ // SubOrder<T> :任然是泛型类 @Test public void test1(){ // 由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型 SubOrder subOrder = new SubOrder(); subOrder.setOrderT(123); SubOrder<String> stringSubOrder = new SubOrder<>();// 类型推断 stringSubOrder.setOrderT("hello"); } @Test public void test2(){ ArrayList<String> stringsList = null; ArrayList<Integer> integerList = null; // 泛型不同的引用不能互相赋值 // stringsList = integerList; 报错 } // 静态方法中不能使用类的泛型。 // @Test // public static void test3(T orderT){ // System.out.println(orderT); // } // 处理异常里,编译不通过 public void show(){ // try{ // // }catch(T t){ // // } // T[] tArray = new T[10]; 编译不通过 T[] arr = (T[]) new Object[10]; } } // 异常类不能声明为泛型类 //class MyException<T> extends Exception{ // //}
class Father<T1, T2> { } // 子类不保留父类的泛型 // 1)没有类型 擦除 class Son<A, B> extends Father{//等价于class Son extends Father<Object,Object>{ } } // 2)具体类型 class Son2<A, B> extends Father<Integer, String> { } // 子类保留父类的泛型 // 1)全部保留 class Son3<T1, T2, A, B> extends Father<T1, T2> { } // 2)部分保留 class Son4<T2, A, B> extends Father<Integer, T2> { }
5 - 自定义泛型结构:泛型方法
package com.lzh.java1; import org.junit.Test; import java.util.ArrayList; import java.util.List; /* 1 方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型 方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。 2 泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系,即泛型方法所属的类是不是泛型类都没有关系 泛型方法的格式: [访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常 3 泛型方法声明泛型时也可以指定上限(后面介绍) 4 泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。 */ class Method<T>{ String name; int age; T methodT; public Method(){} public Method(String name,int age,T methodT){ this.name = name; this.age = age; this.methodT = methodT; } // 如下三个方法都不是泛型方法 public T getMethodT() { return methodT; } public void setMethodT(T methodT) { this.methodT = methodT; } @Override public String toString() { return "Method{" + "name='" + name + '\'' + ", age=" + age + ", methodT=" + methodT + '}'; } } public class GenericMethodTest<E> { E genericMethodTestE; public GenericMethodTest(){} // public GenericMethodTest(E genericMethodTestE){ // this.genericMethodTestE = genericMethodTestE; // } // 自定义泛型方法 public <E> List<E> copyFromToArrayList(E[] array){ ArrayList<E> arrayList = new ArrayList<>(); for(E e:array){ arrayList.add(e); } return arrayList; } @Test // 测试泛型方法 public void test1(){ GenericMethodTest<String> test = new GenericMethodTest<>(); Integer[] array = new Integer[]{1,2,3,4}; // 泛型方法在调用时(与类的泛型参数没有关系),指明泛型参数的类型 List<Integer> lists = test.copyFromToArrayList(array); System.out.println(lists); } }
6 - 体会泛型的使用
package com.lzh.java1; import org.junit.Test; import java.util.List; /* 数据访问对象DOA:date(base) access object */ public class DAO<E> { // 表的共性操作DOA // 添加一条记录 public void add(E e){ } // 删除一条记录 public boolean delete(int index){ return true; } // 修改一条记录 public void update(int index,E e){ } // 查询一条记录 public E getIndex(int index){ return null; } // 查询 public List<E> getForList(int index){ return null; } // 泛型方法 // 举例:获取表中一共有多少条记录?获取最大的员工入职时间? public <T> T getValue(){ // 返回值类型不确定,所以定义成泛型方法 return null; } } class User{ // 此类对应数据库中的User表 } class UserDOA extends DAO<User>{ // 只能操作一个表的DOA @Test public void test(){ UserDOA user = new UserDOA(); // user.add(User e); // 所有的操作只能操作User } }
7 - 泛型在继承上的体现
/*
1 如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的 类或接口,G<B>并不是G<A>的子类型!
2 比如:String是Object的子类,但是List<String >并不是List<Object> 的子类
*/
/* * 1 泛型在继承方面的使用 * ①类A是类B的父类,G<A> 和G<B> 二者不具备子父类关系,二者是并列关系。 * ②类A是类B的父类 A<G> B<G> */ @Test public void test1(){ Object obj = null; String str = null; // obj = str; 体现多态性 Object[] objects = null; Integer[] integers = null; // objects = integers; 体现多态 List<Object> list1 = null; List<String> list2 = null; // list1 = list2; 编译不通过 // 此时的list1和list2的类型不具有子父类关系 // 证明 /* 假设 list1 = list2; list1.add(123); // 此时导致混入非String的数据,出错。 */ } @Test public void test2(){ // 当类A与类B是接口关系时 List<String> list1 = null; ArrayList<String> list2 = null; AbstractList<String> list3 = null; // List的父类 list3 = list2; list1 = list2; List<String> l1 = new ArrayList<>(); }
8 - 通配符的使用
/*
1 使用类型通配符:?
比如:List<?> ,Map<?,?> List<?>是List<String>、List<Object>等各种泛型List的父类。
2 读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型 是什么,它包含的都是Object。
3 写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中 添加对象。 唯一的例外是null,它是所有类型的成员。
① 将任意元素加入到其中不是类型安全的:
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // 编译错误
/* 因为我们不知道c的元素类型,我们不能向其中添加对象。add方法有类型参数E作为集 合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知 道那是什么类型,所以我们无法传任何东西进去 */
② 唯一的例外的是null,它是所有类型的成员。
③另一方面,我们可以调用get()方法并使用其返回值。返回值是一个未知的 类型,但是我们知道,它总是一个Object
*/
/* * 2 通配符的使用 * 通配符:? */ @Test public void test3(){ List<Object> list1 = null; List<String> list2 = null; List<?> list = null; // 通配符的使用 list = list1; list = list2; print(list1); print(list2); List<String> list3 = new ArrayList<>(); list3.add("dd"); list3.add("aa"); list3.add("cc"); list3.add("ff"); list = list3; // list.add("123"); 编译不通过 // list.add("?"); 编译不通过 // 添加(写入):对于List<?>就不能向其内部添加数据,除了null之外 list.add(null); // 相当于默认值 // 获取(读取):允许读取数据,读取的数据类型为Object Object obj = list.get(0); System.out.println(obj); } public void print(List<?> list){ Iterator iterator = list.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); } }
注意点
9 - 有限制的通配符
/*
1 <?> 允许所有泛型的引用调用
2 通配符指定上限 上限extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
3 通配符指定下限 下限super:使用时指定的类型不能小于操作的类,即>=
4 举例:
① <? extends Number> (无穷小 , Number] 只允许泛型为Number及Number子类的引用调用
② <? super Number> [Number , 无穷大) 只允许泛型为Number及Number父类的引用调用
③ <? extends Comparable> 只允许泛型为实现Comparable接口的实现类的引用调用
*/
@Test public void test4(){ /* * 3 有限制条件的通配符使用 * ? extends A: * G < ? extends A > 可以作为G<A> 和 G<B>的父类,其中B是A的子类 * ? super A: * G < ? super A > 可以作为G<A> 和 G<B>的父类,其中B是A的父类 */ List<? extends Person> list1 = null; // <= List<? super Person> list2 = null; // >= List<Object> list3 = new ArrayList<Object>(); // Person的父类 List<Person> list4 = new ArrayList<Person>(); List<Son> list5 = new ArrayList<Son>(); // Person的子类 list1 = list5; list1 = list4; // list1 = list3; list2 = list3; list2 = list4; // 读取数据 list1 = list4; Person persons = list1.get(0); Object obj1 = list1.get(0); list2 = list4; Object obj2 = list2.get(0); // 编译不通过 // Person person2 = list2.get(0); // Son son = list2.get(0); // 写入数据 // 不能在写入数据,除了null // list1.add(new Person()); // list1.add(new Son()); // list1.add(new Object()); list1.add(null); list2.add(new Person()); list2.add(new Son()); // list2.add(new Object()); 编译不通过 }
10 - 练习
/*
① 定义个泛型类 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 单元测试类进行测试。
*/
package com.lzh.exer; import java.util.*; /* ① 定义个泛型类 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 单元测试类进行测试。 */ public class DAO<T> { private Map<String,T> map = new HashMap<>(); public void save(String id,T entity){ this.map.put(id, entity); } // 从 map 中获取 id 对应的对象 public T get(String id) { return this.map.get(id); } // 替换 map 中 key 为 id 的内容, 改为 entity 对象 public void update(String id,T entity) { if(map.containsKey(id)){ this.map.put(id,entity); } } // 返回 map 中存放的所有 T 对象 public List<T> list() { Collection<T> values = map.values(); // 错误的写法 //return (List<T>) values; List<T> list = new ArrayList<>(); Iterator<T> iterator = values.iterator(); while(iterator.hasNext()){ list.add(iterator.next()); } return list; } // 删除指定 id 对象 public void delete(String id) { map.remove(id); } } // 定义一个 User 类: 该类包含:private 成员变量(int 类型) id,age;( String 类型)name。 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 == null || getClass() != o.getClass()) 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); } } // 定义一个测试类: 创建 DAO 类的对象, 分别调用其 save、get、update、list、delete 方 法来操作 User 对象, 使用 Junit 单元测试类进行测试。 class DAOTest{ public static void main(String[] args) { DAO<User> dao = new DAO<>(); dao.save("01",new User(01,20,"貂蝉")); dao.save("02",new User(02,22,"张飞")); dao.save("03",new User(03,23,"李白")); dao.save("04",new User(04,28,"韩信")); List<User> userList = dao.list(); // userList.foreach(System.out::println) for(User user:userList){ System.out.println(user); } dao.update("02",new User(02,18,"百里玄策")); dao.list().forEach(System.out::println); dao.delete("04"); dao.list().forEach(System.out::println); } }