十五、泛型(完结)
十五、泛型
15.1 泛型的引入
15.1.1 集合添加指定类型元素问题
需求:请编写程序,在 ArrayList
中,添加3个 Dog
对象,Dog
对象含有 name
和 age
,并输出 name
和 age
(要求使用getXxx()
)
15.1.2 使用传统方法解决
package com.hspedu.generic;
import java.util.ArrayList;
/**
* @author Carl Zhang
* @description
* @date 2021/12/12 13:17
*/
public class Generic {
public static void main(String[] args) {
//需求 请编写程序,在ArrayList中,添加3个Dog对象
//Dog对象含有name和age,并输出name和age(要求使用getXxx())
//传统方法:
ArrayList arrayList = new ArrayList();
arrayList.add(new Dog("二狗", 2));
arrayList.add(new Dog("小白", 1));
arrayList.add(new Dog("老黑", 5));
arrayList.add("赵四"); //
for (Object o : arrayList) {
Dog o1 = (Dog) o; //遍历到赵四的时候,抛异常 ClassCastException
System.out.println("name = " + o1.getName() + ", age = "
+ o1.getAge());
}
//问题:1. 无法对集合里传入的元素类型做限制
// 2. 每次使用都要进行类型转换,数据量大的时候影响效率
}
}
class Dog {
private String name;
private int age;
public Dog(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;
}
}
15.1.2 传统方法的弊端
- 无法对集合里传入的元素类型做限制
- 每次使用都要进行类型转换,数据量大的时候影响效率
15.1.3 使用泛型解决
package com.hspedu.generic;
import java.util.ArrayList;
/**
* @author Carl Zhang
* @description
* @date 2021/12/12 20:39
*/
public class Generic02 {
public static void main(String[] args) {
//用泛型解决问题
ArrayList dogs = new ArrayList<>();
dogs.add(new Dog("二狗", 2));
dogs.add(new Dog("小白", 1));
dogs.add(new Dog("老黑", 5));
//dogs.add("赵四"); //添加别的类型编译报错
for (Dog dog :dogs) { //可以直接获取Dog 类型元素
System.out.println("name = " + dog.getName() + ", age = "
+ dog.getAge());
}
//好处:
//1. 可以在编译时对集合里的元素类型进行限制
//2. 使用时不用每次都向下转型,可以直接获取对应类型元素,效率提高
}
}
15.2 泛型的理解与好处
15.2.1 泛型的好处
- 编译时可以对添加的元素类型进行检查,提高安全性,防止
ClassCastException
异常 - 使用时不用每次都向下转型,可以直接获取对应类型元素,效率提高
15.2.2 泛型的介绍
- 泛型又称参数化类型,是
Jdk5.0
出现的新特性,解决数据类型的安全性问题 - 在类声明或实例化时只要指定好需要的具体的类型即可。
Java
泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException
异常。同时,代码更加简洁健壮- 泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,或者是某个方法的返回值的类型,或者是参数类型。
package com.hspedu.generic;
/**
* @author Carl Zhang
* @description
* @date 2021/12/12 20:59
*/
public class Generic03 {
public static void main(String[] args) {
//解读:
//1. class Person 中 E 表示泛型标识,可以接收任意类型,E也可以用其他字母表示
//2. new Person(); 表示将String类型传递给E , 指定E的类型为String
//3. E 的具体类型在创建对象的时候就指定,即在编译阶段就会确定 E 的类型
Person person = new Person();
person.name = "赵四";
//person.name = 33; //传入不同的类型,编译报错
person.show();
}
}
//泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,
// 或者是某个方法的返回值的类型,或者是参数类型
class Person { //在类声明时通过标识符 E 表示类中对应变量的类型
E name; //属性name的类型是 E,E的具体类型在实例化Person对象的时候指定
public void p(E e) { //参数e 的类型是E
System.out.println(e);
}
public E p2() { //返回值的类型是E
return name;
}
public void show() {
System.out.println(name.getClass()); //通过getClass() 查看name的具体类型
}
}
15.3 泛型的语法
15.3.1 泛型的语法
- 声明语法:
interface 接口
、{} class 类
{} T
、K
、V
不代表具体值,只表示类型- 任意字母、任意数量。常用
T
,Type
缩写
- 实例化语法:在类名后面指定类型参数的值
- 例:
HashSet
students = new HashSet ();
- 例:
15.3.2 案例
需求:
- 创建3个学生对象
- 放入到
HashSet
中学生对象,使用 - 放入到
HashMap
中,要求Key
是String name
,Value
就是学生对象 - 使用两种方式遍历
package com.hspedu.generic;
import javafx.scene.chart.ValueAxis;
import java.util.*;
/**
* @author: Carl Zhang
* @create: 2021-12-13 09:23
*/
public class GenericExercise01 {
public static void main(String[] args) {
//1.创建3个学生对象
Student stu1 = new Student("赵四", 25);
Student stu2 = new Student("马大帅", 23);
Student stu3 = new Student("德彪", 26);
//2.放入到HashSet中学生对象,使用.
HashSet students = new HashSet();
students.add(stu1);
students.add(stu2);
students.add(stu3);
//forEach遍历
System.out.println("forEach遍历");
for (Student stu : students) {
System.out.println(stu);
}
System.out.println();
//Iterator 遍历
System.out.println("Iterator 遍历");
Iterator iterator = students.iterator();
while (iterator.hasNext()) {
Student next = iterator.next();
System.out.println(next);
}
System.out.println();
//3.放入到HashMap中,要求Key 是String name,Value就是学生对象
HashMap studentHashMap = new HashMap();
studentHashMap.put(stu1.getName(), stu1);
studentHashMap.put(stu2.getName(), stu2);
studentHashMap.put(stu3.getName(), stu3);
//4.使用两种方式遍历
//通过entrySet遍历
System.out.println("通过entrySet遍历");
Set> stuEntries = studentHashMap.entrySet();
/*
//new 对象的时候指定了K、V的类型,所以Set>编译会通过,通过.var会自动填充
public Set> entrySet() {
Set> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
final class EntrySet extends AbstractSet> {.....} AbstractSet 实现了 Set接口
*/
System.out.println("stuEntries.getClass() = " + stuEntries.getClass()); //HashMap$EntrySet
for (Map.Entry stuEntry : stuEntries) {
System.out.println(stuEntry.getValue());
}
System.out.println();
//通过values遍历
System.out.println("通过values遍历");
Collection values = studentHashMap.values();
/*
public Collection values() {
Collection vs = values;
if (vs == null) {
vs = new Values();
values = vs;
}
return vs;
}
*/
Iterator iterator1 = values.iterator();
/*
//debug查看源码:最终返回的是一个HashMap$ValueIterator类型的iterator,V = Student
public final Iterator iterator() { return new ValueIterator(); }
final class ValueIterator extends HashIterator
implements Iterator {
public final V next() { return nextNode().value; }
}
* */
System.out.println("iterator1.getClass() = " + iterator1.getClass()); //HashMap$ValueIterator
while (iterator1.hasNext()) {
Student next = iterator1.next();
System.out.println(next);
}
System.out.println();
//查看运行类型
System.out.println("stuEntries.getClass() = " + stuEntries.getClass()); //HashMap$EntrySet
}
}
15.3.3 泛型使用细节
- 泛型标识符只能接收引用类型
- 在给泛型指定具体类型后,可以传入该类型或者其子类类型
- 泛型的使用形式
HashMap
studentHashMap = new HashMap (); Map
studentHashMap = new HashMap (); HashMap
推荐studentHashMap = new HashMap<>();
- 不指定类型,默认类型是
Object
ArrayList arrayList = new ArrayList();
等同于ArrayList
15.4 泛型使用案例
package com.hspedu.generic;
import java.util.ArrayList;
import java.util.Comparator;
/**
* @author: Carl Zhang
* @create: 2021-12-13 10:57
*/
public class GenericExercise02 {
public static void main(String[] args) {
//5.1 new Employee(name, sal, birthday)*3 将元素添加到集合 new ArrayList
//5.2 arrayList.sort(new Comparator(){
// public int compare(Employee emp1, Employee emp2) {
// //声明变量保存排序结果
// int res = -1;
// //调用String的compareTo()方法
// if ((res = emp1.getName().compareTo(emp2.getName())) != 0)
// return res
// //比较生日日期 --> 实现Comparable接口,重写compareTo方法
// //比较规则:按照年月日顺序比较,返回年月日的差值
// return emp1.getBirthday().compareTo(emp2.getBirthday())
// }
// })
ArrayList employees = new ArrayList<>();
employees.add(new Employee("赵四", 20000, new MyDate("1999", "01", "01")));
employees.add(new Employee("本山", 10000, new MyDate("1999", "01", "01")));
employees.add(new Employee("刘能", 20000, new MyDate("1999", "01", "01")));
employees.add(new Employee("赵四", 80000, new MyDate("1998", "01", "01")));
employees.add(new Employee("刘能", 30000, new MyDate("1999", "02", "01")));
employees.sort(new Comparator() {
@Override
public int compare(Employee emp1, Employee emp2) {
//声明变量保存排序结果
int res = -1;
//调用String的compareTo()方法比较name
//res = emp1.getName().compareTo(emp2.getName());
if ((res = emp1.getName().compareTo(emp2.getName())) != 0)
return res; //name不同就返回name的比较结果
//比较生日日期 --> 实现Comparable接口,重写compareTo方法
//比较规则:按照年月日顺序比较,返回年月日的差值
//封装后,将来可维护性和复用性,就大大增强
return emp1.getBirthday().compareTo(emp2.getBirthday());
}
});
for (Employee o :employees) {
System.out.println(o);
}
}
}
package com.hspedu.generic;
import java.util.Objects;
/**
* @author: Carl Zhang
* @create: 2021-12-13 11:04
*/
public class MyDate implements Comparable {
//MyDate类 ,year,month,day属性 继承LocalDate类
//重写MyDate类的hashCode() ,equals()
private String year;
private String month;
private String day;
public MyDate(String year, String month, String day) {
this.year = year;
this.month = month;
this.day = day;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MyDate)) return false;
MyDate myDate = (MyDate) o;
return Objects.equals(year, myDate.year) &&
Objects.equals(month, myDate.month) &&
Objects.equals(day, myDate.day);
}
@Override
public int hashCode() {
return Objects.hash(year, month, day);
}
@Override
public String toString() {
return
"year:" + year + '\'' +
", month:'" + month + '\'' +
", day:'" + day + '\'' +
'}';
}
//比较生日日期 --> 实现Comparable接口,重写compareTo方法
//比较规则:按照年月日顺序比较,返回年月日的差值
@Override
public int compareTo(MyDate o) {
int res = -1;
int year1 = Integer.parseInt(year);
int year2 = Integer.parseInt(o.year);
//如果年不同,就return year 1 - year2
if ((res = year1 - year2) != 0)
return res;
//比较月
int month1 = Integer.parseInt(month);
int month2 = Integer.parseInt(o.month);
if ((res = month1 - month2) != 0)
return res;
//比较日
int day1 = Integer.parseInt(day);
int day2 = Integer.parseInt(o.day);
return day1 - day2;
}
}
public class Employee {...}
15.5 自定义泛型
15.5.1 自定义泛型类
语法:class Tiger
要点:
Tiger
后面泛型,所以我们把Tiger
就称为自定义泛型类T
、R
、M
泛型的标识符,一般是单个大写字母- 泛型标识符可以有多个
- 普通成员可以使用泛型 (属性、方法)
- 泛型类型不能直接实例化
- 静态成员不能使用类的泛型(方法、属性)
- 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
- 如果在创建对象时,没有指定类型,默认为
Object
package com.hspedu.generic;
/**
* @author: Carl Zhang
* @create: 2021-12-13 13:28
*/
public class CustomGeneric {
public static void main(String[] args) {
Tiger tiger = new Tiger<>();
}
}
//1. Tiger 后面泛型,所以我们把 Tiger 就称为自定义泛型类
//2, T, R, M 泛型的标识符, 一般是单个大写字母
//3. 泛型标识符可以有多个.
class Tiger {
String name;
//4. 普通成员可以使用泛型 (属性、方法)
T t;
public T m1(R r, M m) {
//5. 泛型类型不能直接实例化
// 数组:因为new对象的时候不知道具体类型,无法初始化对应空间
//T[] ts = new T[3]; //提示:类型参数'T'不能直接实例化
return t;
}
//6. 静态成员不能使用类的泛型(方法、属性)
// 因为静态成员是类相关的,如果静态成员使用了泛型,就无法初始化
//static T t2;
//public static void m2(T t, R r) { }
}
//7. 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
//8. 如果在创建对象时,没有指定类型,默认为Object
15.5.2 自定义泛型接口
语法:interface IA
要点:
- 接口中,静态成员也不能使用泛型
- 泛型接口的类型, 在继承接口或者实现接口时确定
- 没有指定类型,默认为
Object
package com.hspedu.customgeneric;
/**
* @author: Carl Zhang
* @create: 2021-12-13 14:10
*/
public class CustomGenericInterface {
}
//1. 接口中,静态成员也不能使用泛型
interface IA {
String D = "无名氏";
//T T1 = "test"; //接口的属性默认是public static final
void getT(T t); //接口中普通方法可以使用泛型
void getR(R r);
}
//2. 泛型接口的类型, 在继承接口或者实现接口时确定
class AA implements IA { //表示类AA实现接口IA时,指定了T是String类型,R是Double类型
@Override
public void getT(String s) {
System.out.println(s.getClass());
}
@Override
public void getR(Double d) {
System.out.println(d.getClass());
}
}
//3. 没有指定类型,默认为 Object
interface BB extends IA { //接口BB继承接口IA,没有指定T,R的类型,默认用Object
@Override
void getT(Object o);
@Override
void getR(Object o);
}
15.5.3 自定义泛型方法
语法:修饰符
要点:
- 泛型方法,可以定义在普通类中,也可以定义在泛型类中
- 当泛型方法被调用时传入参数,类型会确定
public void eat(T t, E e)
,修饰符后没有
,eat
方法不是泛型方法,而是使用了泛型
package com.hspedu.customgeneric;
import com.hspedu.generic.Student;
/**
* @author: Carl Zhang
* @create: 2021-12-13 14:29
*/
public class CustomGenericMethod {
public static void main(String[] args) {
AAA aaa = new AAA();
//2.当泛型方法被调用时传入参数,类型会确定
aaa.methodA("赵四", 11); //t的类型class java.lang.String,
//e的类型class java.lang.Integer
String s = aaa.methodB("赵小四", 11);
System.out.println(s);
BBB bbb = new BBB<>();
bbb.eat("炒粉", 33d);
bbb.methodA("本山", 22); //q的类型class java.lang.String,
// w的类型class java.lang.Integer
String s1 = bbb.methodB("河粉", 2d);
System.out.println(s1);
}
}
//1.泛型方法,可以定义在普通类中,也可以定义在泛型类中
class AAA {
public void methodA(T t, E e) { //定义在普通类的泛型方法
System.out.println("t的类型" + t.getClass() + ", e的类型" + e.getClass());
}
public String methodB(T t, E e) { //定义在普通类的泛型方法
return "t的类型" + t.getClass() + ", e的类型" + e.getClass();
}
}
class BBB {
public void methodA(Q q, W w) { //定义在泛型类的泛型方法
System.out.println("q的类型" + q.getClass() + ", w的类型" + w.getClass());
}
//可以使用自己声明的泛型,也可以使用类声明的泛型
public String methodB(T t, E e) { //定义在泛型类的泛型方法
return "t的类型" + t.getClass() + ", e的类型" + e.getClass();
}
//3.public void eat(Ee)0,修饰符后没有eat方法不是泛型方法,而是使用了泛型
public void eat(T t, E e) { //修饰符后没有 eat方法不是泛型方法,而是使用了泛型
System.out.println("t的类型" + t.getClass() + ", e的类型" + e.getClass());
}
}
15.6 泛型的继承和通配符
15.6.1 泛型的继承和通配符介绍
- 泛型没有继承性
<?>
表示任意类型都能接收<? extends A>
表示能接收A
类型及A
的子类类型,规定了泛型的上限<? super A>
表示能接收A
类型及A
的父类类型,规定了泛型的上限
15.6.2 案例
package com.hspedu.genericextend;
import java.util.ArrayList;
import java.util.List;
/**
* @author: Carl Zhang
* @create: 2021-12-13 15:08
*/
public class GenericExtend {
public static void main(String[] args) {
//1. 泛型没有继承性
//List
15.7 JUnit 使用
15.7.1 JUnit 引入
- 一个类有很多功能代码需要测试,为了测试,就需要写入到
main
方法中 - 如果有多个功能代码测试,就需要来回注销,切换很麻烦
- 如果可以直接运行一个方法,就方便很多,并且可以给出相关信息,就好了->
JUnit
15.7.2 JUnit介绍
JUnit
是一个Java
语言的单元测试框架- 多数
Java
的开发环境都已经集成了JUnit
作为单元测试的工具
15.7.3 使用案例
package com.hspedu.junit_;
import org.junit.jupiter.api.Test;
/**
* @author: Carl Zhang
* @create: 2021-12-13 16:47
*/
public class JUnit_ {
public static void main(String[] args) {
//传统方法 每次使用就要创建对象,并且注释其他地方
//new JUnit_().method1();
//new JUnit_().method2();
}
@Test //第一次添加@Test会报红,Alt + Enter 弹出提示,选中5.xx版本,点确定自动从仓库下载并配置
public void method1() {
System.out.println("method1被调用");
}
@Test
public void method2() {
System.out.println("method2被调用");
}
}
15.7.4 泛型作业
package com.hspedu.homework;
import java.util.*;
/**
* @author Carl Zhang
* @description 定义个泛型类DAO,在其中定义一个Map成员变量,Map的键为String类型,值为T类型。
* @date 2021/12/13 21:15
*/
@SuppressWarnings("AlibabaClassNamingShouldBeCamel")
public class DAO {
Map map = new HashMap<>();
/**
* 分别创建以下方法:
* (1)public void save(String id,T entity):保存T类型的对象到Map成员变量中
* put(id, entity)
* */
public boolean save(String id, T entity) {
//返回添加结果
return map.put(id, entity) == null;
}
/**
* (2)public T get(String id):从map中获取id 对应的对象
* get(id)
* */
public T get(String id) {
return map.get(id);
}
/**
* (3)public void update(String id,T entity):替换map 中key为id的内容,改为entity 对象
* put(id, entity)
*/
public boolean update(String id, T entity) {
//返回修改结果
return map.put(id, entity) == entity;
}
/**
* (4)public List list):返回map中存放的所有T对象
* keySet()
*/
public List list() {
////class java.util.HashMap$Values
// System.out.println(map.values().getClass());
// java.util.HashMap$Values cannot be cast to java.util.List
//return (List) map.values();
//方式二:通过keySet() 获取每个key,遍历keySet,通过key获取value,封装到List集合里
Set keySet = map.keySet();
List ts = new LinkedList<>();
for (String o :keySet) {
T t = map.get(o);
ts.add(t);
}
return ts;
}
/**
* (5)public void delete(String id):删除指定id对象
* remove(key)
*/
public void delete(String id) {
map.remove(id);
}
}
package com.hspedu.homework;
/**
* @author Carl Zhang
* @description 该类包含:private成员变量(int类型)id,age;(String 类型)name。
* @date 2021/12/13 21:15
*/
public class User {
private int id;
private int age;
private String name;
public User(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
package com.hspedu.homework;
import org.junit.jupiter.api.Test;
import java.util.List;
@SuppressWarnings("AlibabaClassNamingShouldBeCamel")
/**
* 创建 DAO类的对象,分别调用其 save、get、update、list、delete 方法来操作User对象,
* 使用Junit 单元测试类进行测试。
*/
class DAOTest {
@Test
public void testMethod() {
DAO userDAO = new DAO<>();
userDAO.save("101", new User(101, 33, "赵四儿"));
userDAO.save("102", new User(102, 22, "刘能儿"));
userDAO.save("103", new User(103, 24, "范德彪"));
userDAO.save("105", new User(103, 24, "谢广坤"));
userDAO.save("106", new User(103, 24, "马大帅"));
User user = userDAO.get("105");
System.out.println("user = " + user);
userDAO.update("101", new User(101, 26, "谢大脚"));
System.out.println(userDAO.get("101"));
//java.util.HashMap$Values cannot be cast to java.util.List
List list = userDAO.list();
for (User o :list) {
System.out.println(o);
}
System.out.println();
userDAO.delete("102");
System.out.println("删除之后:");
for (User o :list) {
System.out.println(o);
}
}
}