15、泛型:
泛型引入:
先来看一个需求:
我们使用我们学过的传统方式来看。
package com.jiangxian.generic_;
import java.util.ArrayList;
/**
* @author JiangXian~
* @version 1.0
*/
// 1. 请编写程序,在ArrayList中,添加3个Dog;
// 2. Dog对象含有name和age,并输出name和age
@SuppressWarnings({"all"})
public class Generic01 {
public static void main(String[] args) {
ArrayList Dogs = new ArrayList();
Dogs.add(new Dog("black", 2));
Dogs.add(new Dog("white", 3));
Dogs.add(new Dog("yellow", 5));
// 假设我们不小心向其中添加了一只猫
Dogs.add(new Cat("red", 6));
// 遍历:
for(Object o : Dogs){ // 此处不能使用 for(Dog d : Dog),
// 向下转型,以使用Dog中的方法getName和getAge
Dog d = (Dog)o;
System.out.println(d.getName());
System.out.println(d.getAge());
}
}
}
class Dog{
public String name;
public 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;
}
}
class Cat{
public String name;
public int age;
public Cat(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;
}
}
使用传统方法的问题分析:
- 我们可以看出使用传统方法不能对加入到集合 ArrayList 中的数据类型进行约束(不安全);
- 遍历的时候,需要进行类型转换,若集合中的数据量较大,对效率有影响。(在没有使用泛型时,增强for循环只能接受Object)
泛型体验:
我们将原来的ArrayList Dogs = new ArrayList();,更改为ArrayList Dogs = new ArrayList();
这样子,我们只能往ArrayList中加入类型为Dog的对象,所以在我们试图传入Cat类型的对象时,会有报错。
并且,我们使用增强for循环时,不再需要类型转换(传入Object),而是可以直接传入 Dog 类型的对象了
package com.jiangxian.generic_;
import java.util.ArrayList;
/**
* @author JiangXian~
* @version 1.0
*/
// 1. 请编写程序,在ArrayList中,添加3个Dog;
// 2. Dog对象含有name和age,并输出name和age
@SuppressWarnings({"all"})
public class Generic01 {
public static void main(String[] args) {
// 1. 当我们 ArrayList<Dog> 表示存放到 ArrayList 集合中的元素是 Dog 类型
// 2. 若编译器发现添加的类型,不满足要求,就会报错
// 3. 在遍历的时候,可以直接取出Dog类型而不是 Object
// 4. public class ArrayList<E>{} E称为泛型,那么我们的代码中,是将 Dog -》E
ArrayList<Dog> Dogs = new ArrayList<Dog>();
Dogs.add(new Dog("black", 2));
Dogs.add(new Dog("white", 3));
Dogs.add(new Dog("yellow", 5));
// 假设我们不小心向其中添加了一只猫
Dogs.add(new Cat("red", 6));
// 遍历:
for(Dog d : Dogs){
System.out.println(d.getName());
System.out.println(d.getAge());
}
}
}
class Dog{
public String name;
public 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;
}
}
class Cat{
public String name;
public int age;
public Cat(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;
}
}
泛型的理解和好处:
泛型的好处:
- 编译时,检查添加元素的类型,提高了安全性;
- 减少了类型转换的次数,提高了效率,解释说明:
- 不使用泛型: Dog类型 ——加入——》 Object——取出——》Dog(存放到 ArrayList 中时,会先转换成 Object , 在取出时,还需要转换成Dog类型);
- 使用泛型:Dog类型——加入——》Dog——取出——》Dog,我们可以看到,存放和取出时,都不需要类型转换,提高了效率。
- 不再提示编译警告。
泛型介绍:
泛(广泛)型(类型)E——其能够表示一种数据类型,可以由我们指定(表示数据类型的一种数据类型)。
- 泛型又称参数化类型,是JDK5出现的新特性,结局数据类型的安全性问题;
- 在类声明或实例化时只要指定好需要的具体的类型即可;
- Java 泛型可以保证如果程序在编译时没有发生警告,允许时就不会产生ClassSetException异常,同时,代码更加简洁,健壮。
- 泛型的作用:可以在类声明时通过一个标识表示类中某个属性的类型,或者是某个方法返回值的类型,或者是参数类型。(举例如下文代码)
package com.jiangxian.generic_;
/**
* @author JiangXian~
* @version 1.0
*/
public class Generic03 {
public static void main(String[] args) {
Person<String> jiangXian = new Person<String>("JiangXian");
/*
上面的这个Person类是长这样的:
class Person<String>{
// E到底是什么数据类型呢?是我们在创建Person这个对象时指定的
// 即在编译期间就确定了E是什么类型了
String s;
public Person(String s) { // E可以在参数类型上体现
this.s = s;
}
public String f(){ // E也可以在返回类型上体现
return s;
}
*/
}
}
//class Person{
// String s; // 表示s的数据类型为String,但是我们有的时候希望其为Integer怎么办呢?
//}
//泛型的作用:可以在类声明时通过一个标识表示类中某个属性的类型
// ,或者是某个方法返回值的类型,或者是参数类型。
class Person<E>{
// E到底是什么数据类型呢?是我们在创建Person这个对象时指定的
// 即在编译期间就确定了E是什么类型了
E s;
public Person(E s) { // E可以在参数类型上体现
this.s = s;
}
public E f(){ // E也可以在返回类型上体现
return s;
}
}
泛型的语法:
泛型的声明:
接口:
interface 接口<T,…>{}
class类
class类<K,V,…>{}
说明:
- 其中,T,K,V不代表值,而是表示类型;
- 任意字母都可以。常用T表示,是Type的缩写;
- 接口和类都可以有多个泛型。
泛型的实例化:
要在类名后面指定类型参数的值(类型)。
package com.jiangxian.generic_;
import java.util.*;
/**
* @author JiangXian~
* @version 1.0
*/
public class GenericExercise01 {
public static void main(String[] args) {
/*
1. 创建3个学生对象
2.放入到HashSet中,使用
3.放入到HashMap中,要求,Key是String对象,Value是学生对象
4.使用两种方式遍历
*/
Student jiangXian = new Student("JiangXian", 21);
Student hsp = new Student("hsp", 40);
Student king = new Student("king", 100);
HashSet<Student> set = new HashSet<Student>();
HashMap<String,Student> map = new HashMap<String,Student>();
set.add(jiangXian);
set.add(hsp);
set.add(king);
map.put(jiangXian.getName(),jiangXian);
map.put(hsp.getName(),hsp);
map.put(king.getName(),king);
for(Student student : set){
System.out.println(student);
}
Iterator<Student> iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
// 使用Hashmao的keySet
Set<String> set1 = map.keySet();
for(String key : set1){
System.out.println(key + "-" + map.get(key));
}
Iterator<String> iterator1 = set1.iterator();
while(iterator1.hasNext()){
String key = iterator1.next();
System.out.println(key + "-" + map.get(key));
}
// 使用entrySet
Set<Map.Entry<String,Student>> set2 = map.entrySet();
for(Map.Entry<String,Student> entry : set2){
System.out.println(entry.getKey() + "-" + entry.getValue());
}
// 这段代码会填充是因为HashMap中被定义过了
/*
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
*/
Iterator<Map.Entry<String,Student>> iterator2 = set2.iterator();
}
}
class Student{
private String name;
private int age;
public Student(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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
使用细节和注意事项:
- interface List{},public class HashSet{}…等等
- 其中的T,E只能是引用类型!
- 在指定泛型具体类型后,可以传入该类型或者该类型的子类类型(其实很好想,我们之前没用泛型之前,默认是Object,而Object是所有的父类,所以可以传入所有的类型均是子类,但是要使用子类方法的时候需要向下转型才行)
- 泛型使用形式:
- List list1 = new ArrayList();
- List list2 = new ArrayList<>();——这个叫做简化形式,就是简写啦,为什么可以呢?因为编译器会进行类型推断,其会自己判断出<>内是我们填入的Integer,且推荐使用这种形式。
- List list3 = new ArrayList(); 默认给他的泛型是,E就是Object。(所以之前我们迭代的时候只能接受Object)
泛型课堂练习:
package com.jiangxian.generic_;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Objects;
/**
* @author JiangXian~
* @version 1.0
*/
@SuppressWarnings({"all"})
public class GenericExercise02 {
public static void main(String[] args) {
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee("JiangXian",20000, new MyData(2003,5,23)));
employees.add(new Employee("JiangXian",20000, new MyData(2003,4,23)));
employees.add(new Employee("JiangXian",20000, new MyData(2004,5,23)));
employees.add(new Employee("Hsp",50000, new MyData(1990,1,21)));
employees.add(new Employee("King",100000, new MyData(1900,1,1)));
employees.sort(new Comparator<Employee>() {
public int compare(Employee e1, Employee e2) {
// 按照姓名排序,自然顺序;
int n1 = (e1.getName()).compareTo(e2.getName());
if (n1 != 0) return n1;
// 若姓名相同,则按照出生年月排序
return (e1.getBirthday()).compareTo(e2.getBirthday());
}
});
System.out.println(employees);
}
}
class Employee{
private String name;
private double sal;
private MyData birthday;
public Employee(String name, double sal, MyData birthday) {
this.name = name;
this.sal = sal;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public MyData getBirthday() {
return birthday;
}
public void setBirthday(MyData birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", sal=" + sal +
", birthday=" + birthday +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Double.compare(sal, employee.sal) == 0 && Objects.equals(name, employee.name) && Objects.equals(birthday, employee.birthday);
}
@Override
public int hashCode() {
return Objects.hash(name, sal, birthday);
}
}
class MyData{
private int year;
private int month;
private int day;
public MyData(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public int compareTo(MyData other) {
int res = 0;
if(this.year == other.getYear() && this.month == other.getMonth() && this.day == other.getDay()){
res = 0;
}
if(this.year < other.getYear()){
res = -1;
}else if(this.year > other.getYear()){
res = 1;
}else if(this.month < other.getMonth()){
res = -1;
}else if(this.month > other.getMonth()){
res = 1;
} else if (this.day < other.getDay()) {
res = -1;
}else if(this.day > other.getDay()){
res = 1;
}
return res;
}
@Override
public String toString() {
return "MyData{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true; // 若两个对象指向同一个位置,自然相等
if (o == null || getClass() != o.getClass()) return false;
MyData myData = (MyData) o;
return year == myData.year && month == myData.month && day == myData.day;
}
@Override
public int hashCode() {
return Objects.hash(year, month, day);
}
}
自定义泛型类:
我们经常用的泛型一般都是别人写好的;
那什么是自定义泛型内,就是我们自己写的类,自己定义了泛型,上文其实已经有涉及了:
class Person<E>{
// E到底是什么数据类型呢?是我们在创建Person这个对象时指定的
// 即在编译期间就确定了E是什么类型了
E s;
public Person(E s) { // E可以在参数类型上体现
this.s = s;
}
public E f(){ // E也可以在返回类型上体现
return s;
}
}
基本语法:
class 类名<T,R,....>{
成员
}
注意细节:
- 普通成员可以使用泛型;
- 使用泛型的数组,不能初始化;(即不允许new进行实例化,因为类型不确定,不能确定开辟多大的空间)
- 静态方法中不能使用类的泛型;(静态是和类相关的,但是类型是在对象创建的时候才能确定的,所以没有办法使用)
- 泛型类的类型是在创建对象时确定的;
- 若在创建对象时,没有指定对象,默认为Object。
自定义泛型接口:
基本语法:
interface 接口名<T,R,…>{}
注意细节:
- 接口中,静态成员也不能使用泛型(这个和泛型类一样);——接口中,属性是public static final的,但是方法默认是 public abstract
- 泛型接口的类型,在被其它接口继承时或者实现接口时确定;
- 没有指定类型,默认为Object。
自定义泛型方法:
基本语法:
修饰符 <T,R,…> 返回类型 方法名(参数列表){}
注意细节:
- 可以定义在普通类和泛型类中;
- 当泛型方法被调用时,类型会确定;
- public void eat(E e){},修饰符后没有<>所以不是泛型方法,而只是使用了泛型;
- 泛型方法,除了可以使用自己的泛型外,也可以使用类的泛型。
package com.jiangxian.generic_;
/**
* @author JiangXian~
* @version 1.0
*/
public class GenericMethod {
public static void main(String[] args) {
Car car = new Car();
car.fly("宝马",1); // 当调用方法时,类型会被确定
car.fly(1,1);
}
}
class Car{
public void run(){}
// 1. 说明<T,R>为泛型标识符
// 2. 是提供给fly使用的
public<T,R> void fly(T t,R r){
System.out.println(t.getClass());
System.out.println(r.getClass());
}
}
class Fish<T, R>{
public void run(){} // 普通方法
public <U,M> void swim(){
U u;
M m;
} // 不能和泛型类的字母一样!需要区分开
public void eat(T t){}
}
这个有什么意义呢?此时我们传入的参数可以字节决定了,非常自由。
泛型的继承和通配符:
- 泛型不具备继承性;
- <?>:表示支持任意泛型类型;
- <? extends A>:支持 A 和 A 的子类,不限于直接子类,规定了泛型的上限;
- <? super A>:支持A和A的父类,不限于直接父类,规定了泛型的下限。
package com.jiangxian.generic_;
import java.util.ArrayList;
import java.util.List;
/**
* @author JiangXian~
* @version 1.0
*/
public class GenericExtends {
public static void main(String[] args) {
Object o = new String("xx");
// 泛型没有继承性:
// List<Object> list = new ArrayList<String>();
ArrayList<Object> objects = new ArrayList<>();
ArrayList<String> strings = new ArrayList<>();
ArrayList<AA> aas = new ArrayList<>();
ArrayList<BB> bbs = new ArrayList<>();
ArrayList<CC> ccs = new ArrayList<>();
// <?>
printCollection(strings);
printCollection(aas);
printCollection(bbs);
printCollection(ccs);
// <? extends AA>
printCollection2(aas);
printCollection2(bbs);
printCollection2(ccs);
// printCollection2(strings);
// <? super AA>
printCollection3(aas);
printCollection3(objects);
// printCollection3(bbs);
}
// <?> 表示任意的类型
public static void printCollection(List<?> c){
for(Object o : c){
System.out.println(o);
}
}
// 只能传入AA即AA的子类,也不限制直接与否
public static void printCollection2(List<? extends AA> c){
for(Object o : c){
System.out.println(o);
}
}
// 只能传入AA和AA的父类,但不限定于直接父类
public static void printCollection3(List<? super AA> c){
for(Object o : c){
System.out.println(o);
}
}
}
class AA{}
class BB extends AA{}
class CC extends BB{}
JUnit的使用:
为什么需要JUnit:
- 一个类有很多功能代码需要测试,为了测试,就需要写入到main方法中;
- 若有多个功能代码测试,就需要来回注销,切换很麻烦;
- 若可以直接允许一个方法,就方便很多,并且可以给出相关信息就好了——》JUnit。
基本介绍:
- JUnit 是一个Java语言的单元测试框架;
- 多数Java开发环境已经集成了Junit作为单元测试工具。
传统方式:
package com.jiangxian.junit_;
/**
* @author JiangXian~
* @version 1.0
*/
public class JUnit_ {
public static void main(String[] args) {
// 传统方式:
new JUnit_().m1();
new JUnit_().m2();
}
public void m1(){
System.out.println("m1 is working");
}
public void m2(){
System.out.println("m2 is working");
}
}
使用Junit
package com.jiangxian.junit_;
import org.junit.jupiter.api.Test;
/**
* @author JiangXian~
* @version 1.0
*/
public class JUnit_ {
public static void main(String[] args) {
// 传统方式:
// new JUnit_().m1();
// new JUnit_().m2();
}
// 先写@Test,然后输入 alt + Enter
// 下载失败的,点开setting,搜索http,Http proxy勾选No proxy即可
// 发现没有小箭头,重启下idea即可。
@Test
public void m1(){
System.out.println("m1 is working");
}
@Test
public void m2(){
System.out.println("m2 is working");
}
}
我将一些配置的问题和解决也写在代码的注释中了,有问题的注意看下。
Exercise
package com.jiangxian.exercise_;
import java.util.*;
/**
* @author JiangXian~
* @version 1.0
*/
public class HomeWork01 {
public static void main(String[] args) {
DAO<User> userDAO = new DAO<>();
User jiangXian = new User("1", "JiangXian", 21);
User hsp = new User("2", "hsp", 40);
User king = new User("3", "King", 100);
userDAO.save(jiangXian.getId(),jiangXian);
userDAO.save(hsp.getId(),hsp);
userDAO.save(king.getId(),king);
System.out.println(userDAO.list());
userDAO.update(hsp.getId(),new User("2", "Smith", 50));
System.out.println(userDAO.list());
System.out.println(userDAO.get(jiangXian.getId()));
}
}
class DAO<T>{
private Map<String,T> map = new HashMap<>();
public void save(String id, T entity){
map.put(id,entity);
}
public T get(String id){
return map.get(id);
}
public void update(String id, T entity){
map.put(id,entity);
}
public List<T> list(){
Collection<T> col = map.values();
ArrayList<T> ts = new ArrayList<>();
for(T t : col){
ts.add(t);
}
return ts;
}
public void delete(String id){
map.remove(id);
}
}
class User{ // 因为仅是作为value,所以可以不用重写equals和hashCode
private String id;
private String name;
private int age;
public User(String id, String name, int age){
this.id = id;
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
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;
}
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}