java 中的泛型
泛型
1.泛型的作用
当元素的类型不确定,其他部分是确定的,例如关于这个元素如何保存,如何管理是确定的,因此把元素的类型设计成一个参数,这个类型参数叫做泛型。
其他说明
- 泛型,允许在定义类和接口的时候,通过一个标识标识类中某个属性的类型或者是某个方法的返回值及参数类型,这个类型参数将在使用时确定(传入实际的类型参数,也称类型实参)。
- 从JDK1.5以后,Java引入了“参数化类型”的概念,在创建集合时再指定集合元素的类型,例如
List<String>
。
当创建和声明对象时传入类型实参
之后,类中含有泛型的方法和属性中的类型实参
也都确定。
public class Generic { @Test public void test() { // 没有使用泛型之前,存储的类型是 Object ArrayList list = new ArrayList(); } @Test public void test1() { // 泛型参数为包装类 Object的子类 ArrayList<String> list = new ArrayList(); // ArrayList<String> list1 = new ArrayList<String>(); list.add("Tom"); list.add("Jerry"); list.add("John"); list.add("DingJun"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String stuName = iterator.next(); System.out.println(stuName); } } // 集合中的HashMap泛型 @Test public void test2() { Map<String,Integer> map = new HashMap<String,Integer>(); map.put("Tom",12); map.put("Jerry",20); map.put("John",8); map.put("Keli",10); // map.put(12,12); // 泛型的嵌套 Set<Map.Entry<String, Integer>> entries = map.entrySet(); Iterator<Map.Entry<String, Integer>> iterator = entries.iterator(); while (iterator.hasNext()) { Map.Entry<String, Integer> next = iterator.next(); System.out.println(next.getKey() + "-----" + next.getValue()); } } }
2. 自定义泛型
2.1 举例
public class Order<T> { String orderName; int orderId; // 类的内部结构可以使用类的泛型 T orderT; public Order() {}; // 以下方法都不是泛型方法 public Order(String orderName,int orderId,T orderT) { this.orderId = orderId; this.orderName = orderName; this.orderT = orderT; } public T getOrderT() { return orderT; } public void setOrderT(T orderT) { this.orderT = orderT; } @Override public String toString() { return "Order{" + "orderName='" + orderName + '\'' + ", orderId=" + orderId + ", orderT=" + orderT + '}'; } } public class GenericTest { @Test public void tets() { // 如果定义了泛型类,实例化没有指定类的泛型,则认为此泛型类型为Object类型 // 要求:建议在实例化时指明类的泛型 Order order = new Order(); order.setOrderT(123); System.out.println(order); order.setOrderT("Tom"); System.out.println(order); Order<String> order1 = new Order<String>("OrderAA",123,"DingJun"); order.setOrderT("AA:hello"); } }
2.2 泛型方法
-
泛型方法与泛型类不同的:类型参数(尖括号)是写在返回值前面的,
<T>
中的 T 被称为类型参数,而方法中的T
被称为参数化类型
,它不是真正运行时真正的参数(被指定的类型才是真正的参数)。 -
声明的类型参数可以当作
返回值
-
泛型类中的类型参数与泛型方法中的类型参数是没有联系的,泛型方法始终以自己定义的参数为准。
public class Test1 { public <T> T testMethod(T t) { return t; } public void test1() { Test1 test = new Test1(); System.out.println(test.testMethod("ILoveYou")); } }
public class OrderTest2 { public static void main(String[] args) { Integer[] arr = new Integer[]{1,2,3,4}; GenericTest test = new GenericTest(); List<Integer> list = test.copyFromArrayToList(arr); System.out.println(list); } } class GenericTest { public static <E> List<E> copyFromArrayToList(E[] arr) { ArrayList<E> list = new ArrayList<>(); for(E e : arr) { list.add(e); } return list; } }
2.3 自定义泛型类泛型接口注意点
-
泛型可有多个参数
<E1,E2,E3>
-
泛型类的构造器
public GenericClass() { }
-
实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致
-
泛型不同的引用不能相互赋值。
-
泛型如果不指定,将被擦除,泛型对应的类型均按照
Object
处理,但不等价于Object
-
泛型的指定中不能使用基本数据类型,可以使用包装类替换
-
静态方法
中不能使用类的泛型(类声明的泛型)
-
异常类不可以时泛型的
不能使用
new E[]
,但是E[] elements = (E[]) new Object[capacity]
OrderTest 类
public class OrderTest<T> { String orderName; int orderId; T orderT; public OrderTest() { // 创建 T 类型的数组 // 编译不通过 // T [] arr = new T[10]; // 编译通过 T[] arr =(T[]) new Object[10]; } public void show() { // 编译不通过 /*try{ }catch (T t) { }*/ } }
2.4 在继承中使用泛型
- 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型
- 1.子类不保留父类的泛型:按需实现
- 没有类型擦除
- 具体类型
- 2.子类保留父类的泛型:泛型子类
- 全部保留
- 部分保留
class Father<T1,T2> {} // 子类不保留父类的泛型 // 1) 没有类型擦除 class Son1 extends Father {} // 等价于class Son1 extends Father<Object,Object> // 2) 具体类型 class Son2 extends Father<Integer,String>{ } // 子类保留父类的泛型 // 1) 全部保留 class Son3 extends Father<T1,T2> {} // 2) 部分保留 class Son4 extends Father<Integer,T2> {}
注:子类除了指定或保留父类的泛型,还可以增加自己的泛型
2.5 泛型在继承上的体现
子类的引用可以赋给父类
public class GenericTest3 { /* 类A是类B的父类,但是G<A>和G<B>二者不具备子父类关系,二者是并列关系 补充: 类A是类B的父类,A<G> 和 B<G> */ @Test public void test() { Object obj = null; String str = null; obj = str; Object[] arr1 = null; String [] arr2 = null; arr1 = arr2; // String 是Object的子类,引用可以赋给Object,是多态的体现 List<Object> list1 = null; List<String> list2 = null; // 错误 // list1 = list2; // list1 和list2不具有子父类关系 show(list1); show1(list2); } public void show1(List<String> list) { } public void show(List<Object> list) { } @Test public void test2() { // 子父类的关系 ArrayList<String> list1 = null; List<String> list2 = null; ArrayList<String > list3 = null; list1 = list3; list2 = list3; } }
3.通配符的使用
- 通配符:
?
- 比如:
List<?>
,Map<?,?>
List<?>
是 List、List等各种泛型List的父类
- 比如:
- 读取
List<?>
的对象list中的元素时,是安全的,不管list的真实类型是什么,包含的都是Object
- 不能向其中添加对象,因为不知道元素的类型(可以添加null)
- 调用
get()
方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个Object
。
@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("AA"); list3.add("BB"); list3.add("CC"); // List<?>是各种泛型List的父类 list = list3; // 除了添加null以外不能添加数据 // list.add("DD"); // 可以获取读出数据,读取的类型为Object System.out.println(list.get(0)); } public void print(List<?> list) { Iterator<?> iterator = list.iterator(); while (iterator.hasNext()) { Object next = iterator.next(); System.out.println(next); } }
有限制条件的通配符
说明
-
允许所有泛型的引用调用
-
通配符指定上限
extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即
< =
-
通配符指定下限
下限 super :使用时指定的类型不能小于操作的类,即
> =
<? extends Number>
:只允许泛型为 Number 及 Number的子类的引用调用
<? super Number>
:只允许泛型为Number 及 Number父类的引用调用
<? extends Comparable>
:只允许泛型为实现Comparable接口的实现类的引用调用
可以在父类容器中添加子类(子类是继承的父类),而使用通配符的 List 不确定类型,所以添加写入数据时需要添加最小的类。
@Test public void test4() { // Person 或Person的子类 List<? extends Person> list1 = null; List<? super Person> list2 = null; List<Student> list3 = new ArrayList<Student>(); List<Person> list4 = new ArrayList<Person>(); List<Object> list5 = new ArrayList<Object>(); list1 = list3; list1 = list4; // Object 不是 Person的子类 // list1 = list5; // list2 = list3; list2 = list5; list2 = list4; // Person是Student的父类 // 读取数据 list1 = list3; // <= Person 包括Person和Person的父类, 最大的父类为Person Person person = list1.get(0); // 编译不通过 // Student s = list1.get(0); // 这行编译不通过,需要强转才可行 Student s = (Student) list1.get(0); // >= Person 可能是Person的父类, 那么这里写所有类的父类Object list2 = list4; Object object = list2.get(0); // 写入数据(子类 可以 赋给父类), 在父类中添加子类(因为子类是继承的父类) // add要添加最小的类,而list1中的类是 <= Person,找不到最小的,不能赋值 // list1.add(new Student()); //编译不通过,因为list1中的对象有可能还是 // list1.add(new Object()); // Object最大,也不行 // list2中的类是 >= Person, 最小的类是Person list2.add(new Person()); list2.add(new Student()); // Student类比Person类 还小 }
4. 泛型嵌套
外层泛型中的一个参数也是一个泛型。
public class GenericTest { public static void main(String[] args) { HashMap<String, ArrayList<Citizen>> map = new HashMap<String,ArrayList<Citizen>>(); ArrayList<Citizen> list = new ArrayList<Citizen>(); list.add(new Citizen("刘恺威")); list.add(new Citizen("杨幂")); list.add(new Citizen("小糯米")); map.put("刘恺威",list); Set<Map.Entry<String,ArrayList<Citizen>>> entrySet = map.entrySet(); Iterator<Map.Entry<String, ArrayList<Citizen>>> iterator = entrySet.iterator(); while (iterator.hasNext()) { Map.Entry<String, ArrayList<Citizen>> entry = iterator.next(); String key = entry.getKey(); ArrayList<Citizen> value = entry.getValue(); System.out.println("户主"+key); System.out.println("家庭成员"+value); } } }
4. 作业与测试
Dao类
public class Dao<T> { private Map<String,T> map = new HashMap<String, T>(); public void save(String id,T entity) { map.put(id,entity); } public T get(String id) { // 通过key获取对象value return map.get(id); } public void update(String id,T entity) { if(map.containsKey(id)) { map.put(id,entity); }else { System.out.println("map中不存在key为id的内容"); } } public List<T> list() { // 错误的 // List<T> list = (List<T>) map.values(); // return list; // list是有序的, map.values()是无序的 // 子类能--->父类 // 父类 不能强转 为子类 ArrayList<T> list = new ArrayList<T>(); Collection<T> values = map.values(); for(T t : values) { list.add(t); } return list; } // 删除指定id对象 public void delete(String id) { if(map.containsKey(id)) { map.remove(id); } } }
User
public class User { private int id; private int age; private String 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; } public User() { } @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); } public User(int id, int age, String name) { this.id = id; this.age = age; this.name = name; } }
DaoTest
public class DaoTest { @Test public void test() { Dao<User> dao = new Dao<User>(); dao.save("1001",new User(1001,34,"周杰伦")); dao.save("1002",new User(1002,20,"昆凌")); dao.save("1003",new User(1003,25,"丁俊")); dao.update("1003",new User(1003,30,"丁力")); // dao.delete("1001"); List<User> list = dao.list(); list.forEach(System.out::println); } }
5.类型擦除
泛型是 Java1.5版本才引进的概念,很显然,泛型代码可以和之前版本的代码很好的兼容。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具