# 第 13 章_泛型 (Generic)
讲师:尚硅谷 - 宋红康(江湖人称:康师傅)
官网:http://www.atguigu.com
BGM:
# 本章专题与脉络
# 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 举例
集合中没有使用泛型时:
- 问题 1:类型不安全
- 问题 2:需要强转,很繁琐,还可能报错 ClassCastException
集合中使用泛型时:
Java 泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生 ClassCastException 异常。即,把不安全的因素在编译期间就排除了,而不是运行期;既然通过了编译,那么类型一定是符合要求的,就避免了类型转换。
同时,代码更加简洁、健壮。
把一个集合中的内容限制为一个特定的数据类型,这就是 generic 背后的核心思想。
举例(泛型在 List 中的使用):
// 泛型在 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 中的使用):
// 泛型在 Map 中的使用 | |
@Test | |
public void test2(){ | |
//jdk7 的新特性:类型推断 | |
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(); | |
//jdk10 新特性:var | |
//var entrySet = map.entrySet(); | |
//var 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
(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<Integer> list = new ArrayList<Integer>();
JDK7.0 时,有新特性,可以简写为:
List<Integer> list = new ArrayList<>(); //类型推断
泛型,也称为泛型参数,即参数的类型,只能使用引用数据类型进行赋值。(不能使用基本数据类型,可以使用包装类替换)
集合声明时,声明泛型参数。在使用集合时,可以具体指明泛型的类型。一旦指明,类或接口内部,凡是使用泛型参数的位置,都指定为具体的参数类型。如果没有指明泛型的类型,就看做是 Object 类型。
# 3. 自定义泛型结构
# 3.1 泛型的基础说明
1、<类型> 这种语法形式就叫泛型。
<类型> 的形式我们称为类型参数,这里的 "类型" 习惯上使用 T 表示,是 Type 的缩写。即:
。 :代表未知的数据类型,我们可以指定为 , , 等。 - 类比方法的参数的概念,我们把
,称为类型形参,将 称为类型实参,有助于我们理解泛型
- 类比方法的参数的概念,我们把
这里的 T,可以替换成 K,V 等任意字母。
2、在哪里可以声明类型变量 <T>
声明类或接口时,在类名或接口名后面声明泛型类型,我们把这样的类或接口称为
泛型类
或泛型接口
。回顾一下类和接口的区别:
类 接口 对事物的抽象 对动作的抽象 成员变量、成员方法、构造器 方法(抽象方法、默认方法、静态方法、私有方法) 只能 继承
一个类,可以实现
多个接口可以 继承
多个接口
【修饰符】 class 类名<类型变量列表> 【extends 父类】 【implements 接口们】{ | |
} | |
【修饰符】 interface 接口名<类型变量列表> 【extends 接口们】{ | |
} | |
// 例如: | |
public class ArrayList<E>{ | |
.... | |
} | |
public interface Map<K,V>{ | |
.... | |
} |
- 声明方法时,在【修饰符】与返回值类型之间声明类型变量,我们把声明了类型变量的方法,称为
泛型方法
。
[修饰符] <类型变量列表> 返回值类型 方法名([形参列表])[throws 异常列表]{ | |
//... | |
} | |
// 例如:java.util.Arrays 类中的 | |
public static <T> List<T> asList(T... a){ | |
.... | |
} |
# 3.2 自定义 泛型类
或 泛型接口
当我们在类或接口中定义某个成员时,该成员的相关类型是不确定的,而这个类型需要在使用这个类或接口时才可以确定,那么我们可以使用泛型类、泛型接口。
# 3.2.1 说明
① 我们在声明完自定义泛型类以后,可以在类的内部(比如:属性、方法、构造器中)使用类的泛型。
② 我们在创建自定义泛型类的对象时,可以指明泛型参数类型。一旦指明,内部凡是使用类的泛型参数的位置,都具体化为指定的类的泛型类型。
③ 如果在创建自定义泛型类的对象时,没有指明泛型参数类型,那么泛型将被擦除,泛型对应的类型均按照 Object 处理,但不等价于 Object。
- 经验:泛型要使用一路都用。要不用,一路都不要用。
④ 泛型的指定中必须使用引用数据类型。不能使用基本数据类型,此时只能使用包装类替换。
⑤ 除创建泛型类对象外,子类继承泛型类时、实现类实现泛型接口时,也可以确定泛型结构中的泛型参数。
如果我们在给泛型类提供子类时,子类也不确定泛型的类型,则可以继续使用泛型参数。
我们还可以在现有的父类的泛型参数的基础上,新增泛型参数。
# 3.2.2 注意
① 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
② JDK7.0 开始,泛型的简化操作:ArrayList
③ 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
④ 不能使用 new E []。但是可以: E[] elements = (E[])new Object[capacity]
;
参考:ArrayList 源码中声明:Object [] elementData,而非泛型参数类型数组。
⑤ 在类 / 接口上声明的泛型,在本类或本接口中即代表某种类型,但不可以在静态方法中使用类的泛型。
⑥ 异常类不能是带泛型的。
# 3.2.2 举例
举例 1:泛型类
class Person<T> { | |
// 使用 T 类型定义变量 | |
private T info; | |
// 使用 T 类型定义一般方法 | |
public T getInfo() { | |
return info; | |
} | |
public void setInfo(T info) { | |
this.info = info; | |
} | |
// 使用 T 类型定义构造器 | |
public Person() { | |
} | |
public Person(T info) { | |
this.info = info; | |
} | |
//static 的方法中不能声明泛型 | |
//public static void show(T t) { | |
// | |
//} | |
// 不能在 try-catch 中使用泛型定义 | |
//public void test() { | |
//try { | |
// | |
//} catch (MyException<T> ex) { | |
// | |
//} | |
//} | |
} |
举例 2:泛型类在继承中
class Father<T1, T2> { | |
} | |
// 子类不保留父类的泛型 | |
// 1) 没有类型 擦除 | |
class Son1 extends Father {// 等价于 class Son extends Father<Object,Object>{ | |
} | |
// 2) 具体类型 | |
class Son2 extends Father<Integer, String> { | |
} | |
// 子类保留父类的泛型 | |
// 1) 全部保留 | |
class Son3<T1, T2> extends Father<T1, T2> { | |
} | |
// 2) 部分保留 | |
class Son4<T2> extends Father<Integer, T2> { | |
} |
举例 3:
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> { | |
} |
# 3.2.3 练习
练习 1:
声明一个学生类,该学生包含姓名、成绩,而此时学生的成绩类型不确定,为什么呢,因为,语文老师希望成绩是 “优秀”、“良好”、“及格”、“不及格”,数学老师希望成绩是 89.5, 65.0,英语老师希望成绩是 'A','B','C','D','E'。那么我们在设计这个学生类时,就可以使用泛型。
package com.atguigu.genericclass.define; | |
class Student<T>{ | |
private String name; | |
private T score; | |
public Student() { | |
super(); | |
} | |
public Student(String name, T score) { | |
super(); | |
this.name = name; | |
this.score = score; | |
} | |
public String getName() { | |
return name; | |
} | |
public void setName(String name) { | |
this.name = name; | |
} | |
public T getScore() { | |
return score; | |
} | |
public void setScore(T score) { | |
this.score = score; | |
} | |
@Override | |
public String toString() { | |
return "姓名:" + name + ", 成绩:" + score; | |
} | |
} | |
public class TestStudent { | |
public static void main(String[] args) { | |
// 语文老师使用时: | |
Student<String> stu1 = new Student<String>("张三", "良好"); | |
// 数学老师使用时: | |
//Student<double> stu2 = new Student<double>("张三", 90.5);// 错误,必须是引用数据类型 | |
Student<Double> stu2 = new Student<Double>("张三", 90.5); | |
// 英语老师使用时: | |
Student<Character> stu3 = new Student<Character>("张三", 'C'); | |
// 错误的指定 | |
//Student<Object> stu = new Student<String>();// 错误的 | |
} | |
} |
练习 2:
定义个泛型类 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 单元测试类进行测试。
代码实现:
/** | |
* @author 尚硅谷 - 宋红康 | |
* @create 8:45 | |
*/ | |
public class DAO<T> { | |
private Map<String,T> map ; | |
{ | |
map = new HashMap<String,T>(); | |
} | |
// 保存 T 类型的对象到 Map 成员变量中 | |
public void save(String id,T entity){ | |
if(!map.containsKey(id)){ | |
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(){ | |
// 错误的: | |
// Collection<T> values = map.values(); | |
// System.out.println(values.getClass()); | |
// return (List<T>) values; | |
// 正确的方式 1: | |
// ArrayList<T> list = new ArrayList<>(); | |
// Collection<T> values = map.values(); | |
// list.addAll(values); | |
// return list; | |
// 正确的方式 2: | |
Collection<T> values = map.values(); | |
ArrayList<T> list = new ArrayList<>(values); | |
return list; | |
} | |
// 删除指定 id 对象 | |
public void delete(String id){ | |
map.remove(id); | |
} | |
} |
package com.atguigu02.selfdefine.exer1; | |
import java.util.Objects; | |
/** | |
* 定义一个 User 类: | |
* 该类包含:private 成员变量(int 类型) id,age;(String 类型)name。 | |
* | |
* @author 尚硅谷 - 宋红康 | |
* @create 9:02 | |
*/ | |
public 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; | |
/* | |
Objects.equals () 的优点是可以处理空值, | |
- 如果两个参数都是空值,它会返回 true; | |
- 如果其中一个参数是空值,它会返回 false。 | |
- 如果两个参数都不是空值,它会调用第一个参数的 equals () 方法来比较两个对象是否相等。 | |
在比较字符串时可以避免空指针异常,因此没使用 String 类中的 equals () 方法 | |
*/ | |
return id == user.id && age == user.age && Objects.equals(name, user.name); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(id, age, name); | |
} | |
} |
package com.atguigu02.selfdefine.exer1; | |
import java.util.List; | |
/** | |
* @author 尚硅谷 - 宋红康 | |
* @create 9:04 | |
*/ | |
public class DAOTest { | |
public static void main(String[] args) { | |
DAO<User> dao = new DAO<>(); | |
dao.save("1001",new User(1,34,"曹操")); | |
dao.save("1002",new User(2,33,"刘备")); | |
dao.save("1003",new User(3,24,"孙权")); | |
dao.update("1002",new User(2,23,"刘禅")); | |
dao.delete("1003"); | |
List<User> list = dao.list(); | |
for(User u : list){ | |
System.out.println(u); | |
} | |
} | |
} |
# 3.3 自定义泛型方法
如果我们定义类、接口时没有使用 <泛型参数>,但是某个方法形参类型不确定时,这个方法可以单独定义 <泛型参数>
。
注意区分
泛型类中的方法
与泛型方法
:
- 前者不是泛型方法
- 后者所在类也不一定是泛型类
# 3.3.1 说明
- 泛型方法的格式:
[访问权限] <T> 返回值类型 方法名([T 参数名称]) [抛出的异常]{ | |
} |
- 方法,也可以被泛型化,与其所在的类是否是泛型类没有关系。
- 泛型方法中的泛型参数在方法被调用时确定。
- 泛型方法可以根据需要,声明为 static 的。
# 3.3.2 举例
举例 1:将 DAO 中操作数据库相关表的方法声明为泛型方法
DAO:
Data Access Object ,
数据访问对象
。它是一种设计模式,用于将 高层的业务服务 与 底层的数据访问 API 或操作 分离开来。 在 Java 服务器开发的三层架构中,通常分为控制层(Controller)、表示层(Service)和数据访问层(DAO)。数据访问层专门负责与数据库进行数据交互。
DAO 模式提供了
访问关系型数据库系统所需操作的接口,封装了操作数据库中相关表的增删改查操作
,将数据访问和业务逻辑分离,对上层提供面向对象的数据访问接口
。这样可以使程序结构变得清晰,减少了代码量,并且降低了代码的耦合性,提高了代码扩展性和系统的可移植性。ORM:
Object-Relational Mapping ,
对象关系映射
,它是一种编程技术,用于在关系数据库和面向对象编程语言的堆之间转换数据。这样就创建了一个虚拟的对象数据库,可以从编程语言中使用 ¹。简单来说,ORM 就是连接面向对象编程(OOP)
和关系数据库
的层。 数据库中的一个表 对应 Java 中的一个类
表中的一条记录 对应 Java 中的一个实例对象
表中的一个字段 对应 Java 类的一个属性
public class DAO { | |
public <E> E get(int id, E e) { | |
E result = null; | |
return result; | |
} | |
} |
举例 2:
public static <T> void fromArrayToCollection(T[] a, Collection<T> c) { | |
for (T o : a) { | |
c.add(o); | |
} | |
} | |
public static void main(String[] args) { | |
Object[] ao = new Object[100]; | |
Collection<Object> co = new ArrayList<Object>(); | |
fromArrayToCollection(ao, co); | |
String[] sa = new String[20]; | |
Collection<String> cs = new ArrayList<>(); | |
fromArrayToCollection(sa, cs); | |
Collection<Double> cd = new ArrayList<>(); | |
// 下面代码中 T 是 Double 类,但 sa 是 String 类型,编译错误。 | |
// fromArrayToCollection(sa, cd); | |
// 下面代码中 T 是 Object 类型,sa 是 String 类型,可以赋值成功。 | |
fromArrayToCollection(sa, co); | |
} |
举例 3:
class MyArrays { | |
public static <T> void sort(T[] arr){ | |
for (int i = 1; i < arr.length; i++) { | |
for (int j = 0; j < arr.length-i; j++) { | |
if(((Comparable<T>)arr[j]).compareTo(arr[j+1])>0){ | |
T temp = arr[j]; | |
arr[j] = arr[j+1]; | |
arr[j+1] = temp; | |
} | |
} | |
} | |
} | |
} | |
public class MyArraysTest { | |
public static void main(String[] args) { | |
int[] arr = {3,2,5,1,4}; | |
// MyArrays.sort (arr);// 错误的,因为 int [] 不是对象数组 | |
String[] strings = {"hello","java","song"}; | |
MyArrays.sort(strings); | |
System.out.println(Arrays.toString(strings)); | |
Circle[] circles = {new Circle(2.0),new Circle(1.2),new Circle(3.0)}; | |
MyArrays.sort(circles); // 编译通过,运行报错,因为 Circle 没有实现 Comparable 接口 | |
} | |
} |
# 3.3.3 练习
练习 1: 泛型方法
编写一个泛型方法,实现任意引用类型数组指定位置元素交换。
public static
/** | |
* @author 尚硅谷 - 宋红康 | |
* @create 9:11 | |
*/ | |
public class Exer01 { | |
// 编写一个泛型方法,实现任意引用类型数组指定位置元素交换。 | |
public static <E> void method( E[] arr,int a,int b){ | |
E temp = arr[a]; | |
arr[a] = arr[b]; | |
arr[b] = temp; | |
} | |
@Test | |
public void testMethod(){ | |
Integer[] arr = new Integer[]{10,20,30,40}; | |
method(arr,2,3); | |
for(Integer i : arr){ | |
System.out.println(i); | |
} | |
} | |
} |
练习 2: 泛型方法
编写一个泛型方法,接收一个任意引用类型的数组,并反转数组中的所有元素
public static
/** | |
* @author 尚硅谷 - 宋红康 | |
* @create 9:11 | |
*/ | |
public class Exer01 { | |
// 编写一个泛型方法,接收一个任意引用类型的数组,并反转数组中的所有元素 | |
public static <E> void method1( E[] arr){ | |
for(int min = 0,max = arr.length - 1;min < max; min++,max--){ | |
E temp = arr[min]; | |
arr[min] = arr[max]; | |
arr[max] = temp; | |
} | |
} | |
@Test | |
public void testMethod1(){ | |
Integer[] arr = new Integer[]{10,20,30,40}; | |
method1(arr); | |
for(Integer i : arr){ | |
System.out.println(i); | |
} | |
} | |
} |
# 4. 泛型在继承上的体现
- SuperA 是 A 类的父类(或者实现的接口),G 是泛型类 / 接口,那么 G
和 G之间没有继承或实现关系 ,二者是并列关系
比如:List
如何解决:通配符 “
?
”,见本文第 5 节
/** | |
* 测试情况 1:SuperA 是 Object,A 是 String,G 是 List 接口 | |
*/ | |
@Test | |
public void test1() { | |
List<Object> listObj = new ArrayList<>(); | |
List<String> listStr = new ArrayList<>(); | |
// List<Object > 和 List<String > 之间没有继承或实现关系,二者是并列关系 | |
// 报错:不兼容的类型: java.util.List<java.lang.String > 无法转换为 java.util.List<java.lang.Object> | |
// listObj = listStr; | |
} |
- SuperA 是 A 类的父类(或者实现的接口),T 是类型形参,那么 SuperA
和 A ,即 A之间是继承或实现关系 的实例可以赋值给 SuperA 的引用(多态)
比如:List
和 ArrayList ,ArrayList 的实例可以赋值给 List 的引用(多态)
/** | |
* 测试情况 2:SuperA 是 List 接口,A 是 ArrayList 类,T 是 String | |
*/ | |
@Test | |
public void test2() { | |
List<String> listStr = new ArrayList<>(); | |
ArrayList<String> arrayListStr = new ArrayList<>(); | |
// List<String > 和 ArrayList<String > 之间是继承或实现关系,即 ArrayList<String > 的实例可以赋值给 List<String > 的引用(多态) | |
listStr = arrayListStr; // 编译通过 | |
} |
思考:对比如下两段代码有何不同( Collection的泛型擦除为Object
VS Collection<Object>
) :
片段 1:Collection 形参未指定泛型,导致泛型被擦除为 Object,因此 Collection 形参可以接收任意类型的 Collection 实例
public void printCollection(Collection c) { | |
Iterator i = c.iterator(); | |
for (int k = 0; k < c.size(); k++) { | |
System.out.println(i.next()); | |
} | |
} |
片段 2:Collection 形参指定泛型为 Object,因此方法 2 的 Collection 形参只能接收 Collection
public void printCollection(Collection<Object> c) { | |
for (Object e : c) { | |
System.out.println(e); | |
} | |
} |
# 5. 通配符的使用
当我们声明一个变量 / 形参时,这个变量 / 形参的类型是一个泛型类或泛型接口,例如:Comparator
# 5.1 通配符的理解
使用类型通配符: ?
G<?>
可以看作是G<A>
类的父类,因此可以将G<A>
的实例赋值给G<?>
类型的引用
比如: List<?>
, Map<?,?>
List<?>
是List<String>
、List<Object>
等各种泛型 List 的父类。
List<?> list = null; List<Object> listObj = null; List<String> listStr = null;// List<?> 是 List< 任意类型 > 的父类,因此 List<?> 的引用可以指向 List<Object>、List<String > 的实例
list = listObj; list = listStr;
# 5.2 通配符的读与写
写操作❌:
将任意元素加入到其中会编译报错,因为不是类型安全的:
因为我们不知道 c 的元素类型,我们不能向其中添加对象。add 方法有类型参数 E 作为集合的元素类型。我们传给 add 的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。
唯一可以插入的元素是 null
,因为它是所有引用类型的默认值。
读操作✔:
另一方面,读取 List<?> 的对象 list 中的元素时,永远是安全的,使用 Object 类型变量接收即可,因为不管 list 的真实类型是什么,它包含的都是 Object。
举例:
@Test | |
public void test1() { | |
List<?> list = null; | |
List<Object> listObj = null; | |
List<String> listStr = null; | |
// List<?> 是 List< 任意类型 > 的父类,因此 List<?> 的引用可以指向 List<Object>、List<String > 的实例 | |
list = listObj; | |
list = listStr; | |
// 写操作:不能向 List<?> 中添加任何元素,因为不知道添加的元素是什么类型 | |
//list.add (123); // 报错:不兼容的类型: int 无法转换为 CAP#1,其中 CAP#1 是通配符类型 | |
list.add(null); // 特例:可以添加 null,因为 null 是所有引用类型的默认值 | |
// 读操作:可以从 List<?> 中读取元素,因为不知道读取的元素是什么类型,因此使用 Object 类型接收 | |
Object obj = list.get(0); | |
for (Object o : list) { | |
System.out.println(o); | |
} | |
} |
# 5.3 使用注意点
注意点 1:编译错误:不能用在 泛型方法的声明
上,返回值类型前面 <> 不能使用通配符
public static <?> void test(ArrayList<?> list){ // 编译错误 | |
} |
注意点 2:编译错误:不能用在 泛型类的声明
上
class GenericTypeClass<?>{ // 编译错误 | |
} |
注意点 3:编译错误:不能用在 new对象
上,右边属于创建集合对象
ArrayList<?> list2 = new ArrayList<?>(); |
# 5.4 有限制的通配符
<?>
- 允许所有泛型的引用调用
通配符指定上限:
<? extends 类/接口 >
- 使用时指定的类型必须是继承某个类,或者实现某个接口,即
<=
- 使用时指定的类型必须是继承某个类,或者实现某个接口,即
通配符指定下限:
<? super 类/接口 >
- 使用时指定的类型必须是操作的类或接口,或者是操作的类的父类或接口的父接口,即
>=
- 使用时指定的类型必须是操作的类或接口,或者是操作的类的父类或接口的父接口,即
说明:
<? extends Number> //(无穷小,Number]
// 只允许泛型为 Number 及 Number 子类的引用调用
<? super Number> //[Number , 无穷大)
// 只允许泛型为 Number 及 Number 父类的引用调用
<? extends Comparable>
// 只允许泛型为实现 Comparable 接口的实现类的引用调用
举例 1:有限制的通配符
class Creature{}
class Person extends Creature{}
class Man extends Person{}
class PersonTest {
public static <T extends Person> void test(T t){
System.out.println(t);
}
public static void main(String[] args) {
test(new Person());
test(new Man());
//The method test(T) in the type PersonTest is not
//applicable for the arguments (Creature)
test(new Creature());
}
}
举例 2:有限制的通配符
// 泛型的上限:此时的泛型?,必须是 Number 类型或者 Number 类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是 Number 类型或者 Number 类型的父类
public static void getElement2(Collection<? super Number> coll){}
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement1(list1);
getElement1(list2);// 报错
getElement1(list3);
getElement1(list4);// 报错
getElement2(list1);// 报错
getElement2(list2);// 报错
getElement2(list3);
getElement2(list4);
}
举例 3:Iterator 中
public static void printCollection1(Collection<? extends Person> coll) {
//Iterator 只能用 Iterator<?> 或 Iterator<? extends Person>.why?
Iterator<?> iterator = coll.iterator();
while (iterator.hasNext()) {
Person per = iterator.next(); // 因为?肯定≤Person,因此用 Person 接收是安全的(多态)
System.out.println(per);
}
}
public static void printCollection2(Collection<? super Person> coll) {
//Iterator 只能用 Iterator<?> 或 Iterator<? super Person>.why?
Iterator<?> iterator = coll.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next(); // 因为?≥Person,因此用 Object 接收是安全的(多态)
System.out.println(obj);
}
}
举例 4: 读、写情况
(难点,了解即可)
- <?
extends
A>- ✔读取,用
A类型
接收(多态) - ❌不可写,除了
null
。因为总可以找到比写入类型更小的类型,会报错。
- ✔读取,用
- <?
super
A>- ✔读取,用
Object类型
接收(多态) - ✔可写入
A类及其子类
的对象
- ✔读取,用
技巧:开发中遇到了带限制条件的通配符,在赋值时,如果没报错,那就正常使用。如果报错了,知道不能这样写。改改!
@Test | |
public void test1(){ | |
//List<Object> list1 = null; | |
List<Person> list2 = new ArrayList<Person>(); | |
//List<Student> list3 = null; | |
List<? extends Person> list4 = null; | |
list2.add(new Person()); | |
list4 = list2; | |
// 读取:可以读 | |
Person p1 = list4.get(0); | |
// 写入:除了 null 之外,不能写入 | |
list4.add(null); | |
// list4.add(new Person()); | |
// list4.add(new Student()); | |
} | |
@Test | |
public void test2(){ | |
//List<Object> list1 = null; | |
List<Person> list2 = new ArrayList<Person>(); | |
//List<Student> list3 = null; | |
List<? super Person> list5 = null; | |
list2.add(new Person()); | |
list5 = list2; | |
// 读取:可以实现 | |
Object obj = list5.get(0); | |
// 写入:可以写入 Person 及 Person 子类的对象 | |
list5.add(new Person()); | |
list5.add(new Student()); | |
} |
# 5.5 泛型应用举例
举例 1:泛型嵌套
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<Entry<String, ArrayList<Citizen>>> entrySet = map.entrySet(); | |
Iterator<Entry<String, ArrayList<Citizen>>> iterator = entrySet.iterator(); | |
while (iterator.hasNext()) { | |
Entry<String, ArrayList<Citizen>> entry = iterator.next(); | |
String key = entry.getKey(); | |
ArrayList<Citizen> value = entry.getValue(); | |
System.out.println("户主:" + key); | |
System.out.println("家庭成员:" + value); | |
} | |
} |
举例 2:个人信息设计
用户在设计类的时候往往会使用类的关联关系,例如,一个人中可以定义一个信息的属性,但是一个人可能有各种各样的信息(如联系方式、基本信息等),所以此信息属性的类型就可以通过泛型进行声明,然后只要设计相应的信息类即可。
interface Info{ // 只有此接口的子类才是表示人的信息 | |
} | |
class Contact implements Info{ // 表示联系方式 | |
private String address ; // 联系地址 | |
private String telephone ; // 联系方式 | |
private String zipcode ; // 邮政编码 | |
public Contact(String address,String telephone,String zipcode){ | |
this.address = address; | |
this.telephone = telephone; | |
this.zipcode = zipcode; | |
} | |
public void setAddress(String address){ | |
this.address = address ; | |
} | |
public void setTelephone(String telephone){ | |
this.telephone = telephone ; | |
} | |
public void setZipcode(String zipcode){ | |
this.zipcode = zipcode; | |
} | |
public String getAddress(){ | |
return this.address ; | |
} | |
public String getTelephone(){ | |
return this.telephone ; | |
} | |
public String getZipcode(){ | |
return this.zipcode; | |
} | |
@Override | |
public String toString() { | |
return "Contact [address=" + address + ", telephone=" + telephone | |
+ ", zipcode=" + zipcode + "]"; | |
} | |
} | |
class Introduction implements Info{ | |
private String name ; // 姓名 | |
private String sex ; // 性别 | |
private int age ; // 年龄 | |
public Introduction(String name,String sex,int age){ | |
this.name = name; | |
this.sex = sex; | |
this.age = age; | |
} | |
public void setName(String name){ | |
this.name = name ; | |
} | |
public void setSex(String sex){ | |
this.sex = sex ; | |
} | |
public void setAge(int age){ | |
this.age = age ; | |
} | |
public String getName(){ | |
return this.name ; | |
} | |
public String getSex(){ | |
return this.sex ; | |
} | |
public int getAge(){ | |
return this.age ; | |
} | |
@Override | |
public String toString() { | |
return "Introduction [name=" + name + ", sex=" + sex + ", age=" + age | |
+ "]"; | |
} | |
} | |
class Person<T extends Info>{ | |
private T info ; | |
public Person(T info){ // 通过构造器设置信息属性内容 | |
this.info = info; | |
} | |
public void setInfo(T info){ | |
this.info = info ; | |
} | |
public T getInfo(){ | |
return info ; | |
} | |
@Override | |
public String toString() { | |
return "Person [info=" + info + "]"; | |
} | |
} | |
public class GenericPerson{ | |
public static void main(String args[]){ | |
Person<Contact> per = null ; // 声明 Person 对象 | |
per = new Person<Contact>(new Contact("北京市","01088888888","102206")) ; | |
System.out.println(per); | |
Person<Introduction> per2 = null ; // 声明 Person 对象 | |
per2 = new Person<Introduction>(new Introduction("李雷","男",24)); | |
System.out.println(per2) ; | |
} | |
} |