1. 泛型概述
1.1 生活中的例子
- 举例1:中药店,每个抽屉外面贴着标签
- 举例2:超市购物架上很多瓶子,每个瓶子装的是什么,有标签
- 举例3:家庭厨房中:
Java中的泛型,就类似于上述场景中的标签
。
1.2 泛型的引入
在Java中,我们在声明方法时,当在完成方法功能时如果有未知的数据
需要参与,这些未知的数据需要在调用方法时才能确定,那么我们把这样的数据通过形参
表示。在方法体中,用这个形参名来代表那个未知的数据,而调用者在调用时,对应的传入实参
就可以了。
受以上启发,JDK1.5设计了泛型的概念。泛型即为“类型参数
”,这个类型参数在声明它的类、接口或方法中,代表未知的某种通用类型。
举例1:
集合类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK5.0之前只能把元素类型设计为Object,JDK5.0时Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时指定集合元素的类型。比如:List<String>,这表明该List只能保存字符串类型的对象。
使用集合存储数据时,除了元素的类型不确定,其他部分是确定的(例如关于这个元素如何保存,如何管理等)。
举例2:
java.lang.Comparable接口和java.util.Comparator接口,是用于比较对象大小的接口。这两个接口只是限定了当一个对象大于另一个对象时返回正整数,小于返回负整数,等于返回0,但是并不确定是什么类型的对象比较大小。JDK5.0之前只能用Object类型表示,使用时既麻烦又不安全,因此 JDK5.0 给它们增加了泛型。
其中<T>
就是类型参数,即泛型。
所谓泛型,就是允许在定义类、接口时通过一个
标识
表示类中某个属性的类型
或者是某个方法的返回值或参数的类型
。这个类型参数将在使用时(例如,继承或实现这个接口、创建对象或调用方法时)确定(即传入实际的类型参数,也称为类型实参)。
2. 使用泛型举例
自从JDK5.0引入泛型的概念之后,对之前核心类库中的API做了很大的修改,例如:JDK5.0改写了集合框架中的全部接口和类、java.lang.Comparable接口、java.util.Comparator接口、Class类等。为这些接口、类增加了泛型支持,从而可以在声明变量、创建对象时传入类型实参。
2.1 集合中使用泛型
2.1.1 举例
集合中没有使用泛型时:
集合中使用泛型时:
Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。即,把不安全的因素在编译期间就排除了,而不是运行期;既然通过了编译,那么类型一定是符合要求的,就避免了类型转换。
同时,代码更加简洁、健壮。
把一个集合中的内容限制为一个特定的数据类型,这就是generic背后的核心思想。
举例:
//泛型在List中的使用 @Test public void test1(){ //举例:将学生成绩保存在ArrayList中 //标准写法: //ArrayList<Integer> list = new ArrayList<Integer>(); //jdk7的新特性:类型推断 ArrayList<Integer> list = new ArrayList<>(); list.add(56); //自动装箱 list.add(76); list.add(88); list.add(89); //当添加非Integer类型数据时,编译不通过 //list.add("Tom");//编译报错 Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()){ //不需要强转,直接可以获取添加时的元素的数据类型 Integer score = iterator.next(); System.out.println(score); } }
举例:
//泛型在Map中的使用 @Test public void test2(){ HashMap<String,Integer> map = new HashMap<>(); map.put("Tom",67); map.put("Jim",56); map.put("Rose",88); //编译不通过 // map.put(67,"Jack"); //遍历key集 Set<String> keySet = map.keySet(); for(String str:keySet){ System.out.println(str); } //遍历value集 Collection<Integer> values = map.values(); Iterator<Integer> iterator = values.iterator(); while(iterator.hasNext()){ Integer value = iterator.next(); System.out.println(value); } //遍历entry集 Set<Map.Entry<String, Integer>> entrySet = map.entrySet(); Iterator<Map.Entry<String, Integer>> iterator1 = entrySet.iterator(); while(iterator1.hasNext()){ Map.Entry<String, Integer> entry = iterator1.next(); String key = entry.getKey(); Integer value = entry.getValue(); System.out.println(key + ":" + value); } }
2.1.2 练习
练习1:
(1)创建一个ArrayList集合对象,并指定泛型为<Integer> (2)添加5个[0,100)以内的整数到集合中 (3)使用foreach遍历输出5个整数 (4)使用集合的removeIf方法删除偶数,为Predicate接口指定泛型<Ineteger> (5)再使用Iterator迭代器输出剩下的元素,为Iterator接口指定泛型<Integer>
package com.atguigu.genericclass.use; import java.util.ArrayList; import java.util.Iterator; import java.util.Random; import java.util.function.Predicate; public class TestNumber { public static void main(String[] args) { ArrayList<Integer> coll = new ArrayList<Integer>(); Random random = new Random(); for (int i = 1; i <= 5 ; i++) { coll.add(random.nextInt(100)); } System.out.println("coll中5个随机数是:"); for (Integer integer : coll) { System.out.println(integer); } //方式1:使用集合的removeIf方法删除偶数 coll.removeIf(new Predicate<Integer>() { @Override public boolean test(Integer integer) { return integer % 2 == 0; } }); //方式2:调用Iterator接口的remove()方法 //Iterator<Integer> iterator1 = coll.iterator(); //while(coll.hasNext()){ // Integer i = coll.next(); // if(i % 2 == 0){ // coll.remove(); // } //} System.out.println("coll中删除偶数后:"); Iterator<Integer> iterator = coll.iterator(); while(iterator.hasNext()){ Integer number = iterator.next(); System.out.println(number); } } }
练习2:编写一个简单的同学通迅录
需求说明:
- 查询所有通讯录的同学信息。
- 输入姓名,根据姓名查询指定同学信息。如果该姓名不存在,输出提示信息。
- 添加同学,姓名重复的不能添加。
- 根据学员姓名删除学员。
- 按姓名排序查询学员。
分析:
- 使用HashMap<K,V>存储同学信息,使用同学姓名做key,同学对象做value。
- 同学对象包含的属性有:姓名、年龄、住址、爱好等。
2.2 比较器中使用泛型
2.2.1 举例
package com.atguigu.generic; public class Circle{ private double radius; public Circle(double radius) { super(); this.radius = radius; } public double getRadius() { return radius; } public void setRadius(double radius) { this.radius = radius; } @Override public String toString() { return "Circle [radius=" + radius + "]"; } }
使用泛型之前:
package com.atguigu.generic; import java.util.Comparator; class CircleComparator implements Comparator{ @Override public int compare(Object o1, Object o2) { //强制类型转换 Circle c1 = (Circle) o1; Circle c2 = (Circle) o2; return Double.compare(c1.getRadius(), c2.getRadius()); } } //测试: public class TestNoGeneric { public static void main(String[] args) { CircleComparator com = new CircleComparator(); System.out.println(com.compare(new Circle(1), new Circle(2))); System.out.println(com.compare("圆1", "圆2"));//运行时异常:ClassCastException } }
使用泛型之后:
package com.atguigu.generic; import java.util.Comparator; class CircleComparator1 implements Comparator<Circle> { @Override public int compare(Circle o1, Circle o2) { //不再需要强制类型转换,代码更简洁 return Double.compare(o1.getRadius(), o2.getRadius()); } } //测试类 public class TestHasGeneric { public static void main(String[] args) { CircleComparator1 com = new CircleComparator1(); System.out.println(com.compare(new Circle(1), new Circle(2))); //System.out.println(com.compare("圆1", "圆2")); //编译错误,因为"圆1", "圆2"不是Circle类型,是String类型,编译器提前报错, //而不是冒着风险在运行时再报错。 } }
2.2.2 练习
(1)声明矩形类Rectangle,包含属性长和宽,属性私有化,提供有参构造、get/set方法、重写toString方法,提供求面积和周长的方法。
(2)矩形类Rectangle实现java.lang.Comparable接口,并指定泛型为,重写int compareTo(T t)方法,按照矩形面积比较大小,面积相等的,按照周长比较大小。
(3)在测试类中,创建Rectangle数组,并创建5个矩形对象
(4)调用Arrays的sort方法,给矩形数组排序,并显示排序前后的结果。
package com.atguigu.genericclass.use; public class Rectangle implements Comparable<Rectangle>{ private double length; private double width; public Rectangle(double length, double width) { this.length = length; this.width = width; } public double getLength() { return length; } public void setLength(double length) { this.length = length; } public double getWidth() { return width; } public void setWidth(double width) { this.width = width; } //获取面积 public double area(){ return length * width; } //获取周长 public double perimeter(){ return 2 * (length + width); } @Override public String toString() { return "Rectangle{" + "length=" + length + ", width=" + width + ",area =" + area() + ",perimeter = " + perimeter() + '}'; } @Override public int compareTo(Rectangle o) { int compare = Double.compare(area(), o.area()); return compare != 0 ? compare : Double.compare(perimeter(),o.perimeter()); } }
package com.atguigu.genericclass.use; import java.util.Arrays; public class TestRectangle { public static void main(String[] args) { Rectangle[] arr = new Rectangle[4]; arr[0] = new Rectangle(6,2); arr[1] = new Rectangle(4,3); arr[2] = new Rectangle(12,1); arr[3] = new Rectangle(5,4); System.out.println("排序之前:"); for (Rectangle rectangle : arr) { System.out.println(rectangle); } Arrays.sort(arr); System.out.println("排序之后:"); for (Rectangle rectangle : arr) { System.out.println(rectangle); } } }
2.3 相关使用说明
- 在创建集合对象的时候,可以指明泛型的类型。
具体格式为:List list = new ArrayList(); - JDK7.0时,有新特性,可以简写为:
List list = new ArrayList<>(); //类型推断 - 泛型,也称为泛型参数,即参数的类型,只能使用引用数据类型进行赋值。(不能使用基本数据类型,可以使用包装类替换)
- 集合声明时,声明泛型参数。在使用集合时,可以具体指明泛型的类型。一旦指明,类或接口内部,凡是使用泛型参数的位置,都指定为具体的参数类型。如果没有指明的话,看做是Object类型。