网站首页 > java教程 正文
泛型是什么?
用来规定一个类、接口或方法所能接受的数据的类型. 就像在声明方法时指定参数一样, 我们在声明一个类, 接口或方法时, 也可以指定其"类型参数", 也就是泛型.
泛型的好处
- 提高安全性: 将运行期的错误转换到编译期. 如果我们在对一个对象所赋的值不符合其泛型的规定, 就会编译报错.避免强转: 比如我们在使用List时, 如果我们不使用泛型, 当从List中取出元素时, 其类型会是默认的Object, 我们必须将其向下转型为String才能使用。比如:
List l = new ArrayList(); l.add("abc"); String s = (String) l.get(0);
而使用泛型,就可以保证存入和取出的都是String类型, 不必在进行cast了。比如:
List<String> l = new ArrayList<>(); l.add("abc"); String s = l.get(0);
泛型的使用
1. 定义类/接口:
public class Test<T> { private T obj; public T getObj() { return obj; } public void setObj(T obj) { this.obj = obj; } }
- 使用方式:
- List<String> l = new ArrayList<>( );重点说明:变量类型中的泛型,和实例类型中的泛型,必须保证相同(不支持继承关系)。既然有了这个规定, 因此在JDK1.7时就推出了一个新特性叫菱形泛型(The Diamond), 就是说后面的泛型可以省略直接写成<>, 反正前后一致。
2. 定义方法:
public <Q extends Object,T> void print(Q q) { System.out.println(q); }
- 说明:泛型的声明,必须在方法的修饰符(public,static,final,abstract等)之后,返回值声明之前。方法参数列表,以及方法体中用到的所有泛型变量,都必须声明。使用方式:
- 太简单,不说了。
泛型中的通配符
1. 作用:规定只允许某一部分类作为泛型;
2. 分类:
- 无边界通配符(<?>):
- 无边界的通配符的主要作用就是让泛型能够接受未知类型的数据。固定上边界通配符(<? extends E>):
- 使用固定上边界的通配符的泛型, 就能够接受指定类及其子类类型的数据。
- 要声明使用该类通配符, 采用<? extends E>的形式, 这里的E就是该泛型的上边界. 注意: 这里虽然用的是extends关键字, 却不仅限于继承了父类E的子类, 也可以代指显现了接口E的类.固定下边界通配符(<? super E>):
- 使用固定下边界的通配符的泛型, 就能够接受指定类及其父类类型的数据。
- 要声明使用该类通配符, 采用<? super E>的形式, 这里的E就是该泛型的下边界.
注意: 你可以为一个泛型指定上边界或下边界, 但是不能同时指定上下边界。
3. 使用方法:
3.1 无边界通配符:
public static void printList(List<?> list) { for (Object o : list) { System.out.println(o); } } public static void main(String[] args) { List<String> l1 = new ArrayList<>(); l1.add("aa"); l1.add("bb"); l1.add("cc"); printList(l1); List<Integer> l2 = new ArrayList<>(); l2.add(11); l2.add(22); l2.add(33); printList(l2);
注意:这里的printList方法不能写成public static void printList(List<Object> list)的形式。原因在上文提到过,变量类型中的泛型,和实例类型中的泛型,必须保证相同。两者之间不支持继承关系。
- 重点说明:我们不能对List<?>使用add,get以及List拥有的其他方法。
- 原因是,我们不确定该List的类型, 也就不知道add,或者get方法的参数类型。
- 但是也有特例。
- 请看下面代码:
public static void addTest(List<?> list) { Object o = new Object(); // list.add(o); // 编译报错 // list.add(1); // 编译报错 // list.add("ABC"); // 编译报错 list.add(null); // 特例 // String s = list.get(0); // 编译报错 // Integer i = list.get(1); // 编译报错 Object o = list.get(2); // 特例 } 这个地方有点不好理解。
我们可以假设:使用这些方法编译不报错。
以上面的代码为例,并且取消上面的注释。
由于参数的泛型不确定,调用者可能会传List<Number>,也可能传List<String>。
当调用者传过来的参数是List<Interger>,执行到list.add(o)以及list.("ABC")的时候,系统肯定会抛出异常,使得后面的代码无法执行。
所以,编译器其实是把运行时可能出现的异常放在编译阶段来检查,提高了代码的健壮性以及安全性。
2. 固定上边界通配符:
public static double sumOfList(List<? extends Number> list) { double s = 0.0; for (Number n : list) { // 注意这里得到的n是其上边界类型的, 也就是Number,需要将其转换为double. s += n.doubleValue(); } return s; } public static void main(String[] args) { List<Integer> list1 = Arrays.asList(1, 2, 3, 4); System.out.println(sumOfList(list1)); List<Double> list2 = Arrays.asList(1.1, 2.2, 3.3, 4.4); System.out.println(sumOfList(list2)); }
- 重点说明:我们不能对List<? extends E>使用add方法。
- 原因是,我们不确定该List的类型, 也就不知道add方法的参数类型。
- 但是也有特例。
- 请看下面代码:
public static void addTest2(List<? extends Number> l) { // l.add(1); // 编译报错 // l.add(1.1); // 编译报错 l.add(null); Number number = l.get(1); // 正常 }
目的跟第一种通配符类似,就是编译器其实是把运行时可能出现的异常放在编译阶段来检查。
但是,我们可以保证不管参数是什么泛型,里面的元素肯定是Number或者其子类,所以,从List中获取一个Number元素的get()方法是允许的。
3. 固定下边界通配符:
public static void addNumbers(List<? super Integer> list) { for (int i = 1; i <= 10; i++) { list.add(i); } } public static void main(String[] args) { List<Object> list1 = new ArrayList<>(); addNumbers(list1); System.out.println(list1); List<Number> list2 = new ArrayList<>(); addNumbers(list2); System.out.println(list2); List<Double> list3 = new ArrayList<>(); // addNumbers(list3); // 编译报错 }
- 重点说明:我们不能对List<? super E>使用get方法。
- 原因是,我们不确定该List的类型, 也就不知道get方法的参数类型。
- 但是也有特例。
- 请看下面代码:
public static void getTest2(List<? super Integer> list) { // Integer i = list.get(0); //编译报错 Object o = list.get(1); }
目的跟第一种通配符类似,就是编译器其实是把运行时可能出现的异常放在编译阶段来检查。
但是,我们可以保证不管参数是什么泛型,里面的元素肯定是Integer,所以,从List中add一个Integer元素的add()方法是允许的。
- 典型使用场景:
- 使用<? super E>有个常见的场景就是Comparator。
- TreeSet有这么一个构造方法:TreeSet(Comparator<? super E> comparator) ,就是使用Comparator来创建TreeSet。
- 请看下面的代码:
import java.util.Comparator; import java.util.TreeSet; class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } 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; } } class Student extends Person { public Student(String name, int age) { super(name, age); } } class comparatorTest1 implements Comparator<Person> { @Override public int compare(Person s1, Person s2) { int num = s1.getAge() - s2.getAge(); return num == 0 ? s1.getName().compareTo(s2.getName()) : num; } } public class Test { public static void main(String[] args) { TreeSet<Student> ts2 = new TreeSet<>(new comparatorTest1()); ts2.add(new Student("Susan", 23)); ts2.add(new Student("Rose", 27)); ts2.add(new Student("Jane", 19)); for (Student stu : ts2) { System.out.println(stu.getName() + ":" + stu.getAge()); } } }
注意:通过查看TreeSet源码得知,构造方法TreeSet(Comparator<? super E> comparator)中的E,来源于泛型类 TreeSet<E>,在这里就是变量ts2的类型TreeSet<Student> 中的Student 。因为泛型限定是<? super E>,即<? super Student>,所以Comparator的泛型必须是Student的父类,即Person。
总结:
有人将上面的原则总结了一下,写作"in out"原则, 归纳起来就是:
in或者producer就是你要读取出数据以供随后使用(想象一下List的get), 这时使用extends关键字, 固定上边界的通配符. 你可以将该对象当做一个只读对象;out或者consumer就是你要将已有的数据写入对象(想象一下List的add), 这时使用super关键字, 固定下边界的通配符. 你可以将该对象当做一个只能写入的对象;当你希望in或producer的数据能够使用Object类中的方法访问时, 使用无边界通配符;当你需要一个既能读又能写的对象时, 就不要使用通配符了.
猜你喜欢
- 2024-10-16 Java泛型最全总结(java泛型的实现原理)
- 2024-10-16 Java高级面试:Java的泛型实现机制是怎么样的?
- 2024-10-16 深入理解 Java 泛型(java泛型的实现原理)
- 2024-10-16 关于Java中的泛型使用你不知道的那些事?
- 2024-10-16 Java泛型机制详解;这些你都知道吗?
- 2024-10-16 Java中的泛型是什么?(java里面的泛型)
- 2024-10-16 学习廖雪峰的JAVA教程---泛型(使用泛型)
- 2024-10-16 Java泛型的好处及实现原理详解(java里泛型)
- 2024-10-16 java中的泛型是什么(java中泛型的理解)
- 2024-10-16 一句话概述什么是泛型,教你直白理解Java泛型
你 发表评论:
欢迎- 最近发表
-
- Java常量定义防暴指南:从"杀马特"到"高富帅"的华丽转身
- Java接口设计原则与实践:优雅编程的艺术
- java 包管理、访问修饰符、static/final关键字
- Java工程师的代码规范与最佳实践:优雅代码的艺术
- 编写一个java程序(编写一个Java程序计算并输出1到n的阶乘)
- Mycat的搭建以及配置与启动(mycat部署)
- Weblogic 安装 -“不是有效的 JDK Java 主目录”解决办法
- SpringBoot打包部署解析:jar包的生成和结构
- 《Servlet》第05节:创建第一个Servlet程序(HelloSevlet)
- 你认为最简单的单例模式,东西还挺多
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)