1.泛型
1.1泛型概述
-
泛型的介绍
泛型是JDK5中引入的特性,它提供了编译时类型安全检测机制
-
泛型的好处
- 把运行时期的问题提前到了编译期间
- 避免了强制类型转换
-
泛型的定义格式
- <类型>: 指定一种类型的格式.尖括号里面可以任意书写,一般只写一个字母.例如:
- <类型1,类型2…>: 指定多种类型的格式,多种类型之间用逗号隔开.例如: <E,T> <K,V>
多学一点:
java中的泛型是伪泛型,只有在编译的阶段检查一下,输入到集合中的元素是否是泛型对应的类型,真正添加进去后,还是认为是Object类型,使用这些数据的时候,强转成对应的泛型
泛型的细节:
- 泛型中不能写基本数据类型,因为要通过Object来接收,基本数据类型,Object类无法接受
- 指定泛型的具体类型后,传递数据时,可以传入该类类型或者其子类类型,多态
- 如果不写泛型,类型默认是object
1.2自定义泛型
1.2.1 泛型类
自定义一个泛型类:
1.2.2 泛型方法
1.2.3 泛型接口
- 实现类给出具体类型
- 实现类延续泛型,创建对象时再确定
1.3 泛型的通配符
泛型不具备继承性,但是数据具备继承性
-
此时,泛型里面写的是什么类型,那么只能传递什么类型的数据。
- 弊端:
利用泛型方法有一个小弊端,此时他可以接受任意的数据类型
Ye Fu Zi Student - 希望:本方法虽然不确定类型,但是以后我希望只能传递Ye Fu Zi
- 弊端:
-
此时我们就可以使用泛型的通配符:
- ? 也表示不确定的类型
- 他可以进行类型的限定 * ? extends E: 表示可以传递E或者E所有的子类类型
- ? super E:表示可以传递E或者E所有的父类类型
- 应用场景:
- 如果我们在定义类、方法、接口的时候,如果类型不确定,就可以定义泛型类、泛型方法、泛型接口。
- 如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以泛型的通配符
关键点:可以限定类型的范围。
-
extends:可以看到Ye以及Ye的子类型都可以传递进来,但是其他类型不行
-
super:可以看到Zi以及Zi的父类型都可以传递进来,但是其他类型不行
1.4 泛型综合练习
抽象动物
__________|__________
| |
抽象狗 抽象猫
____| ____ ____| ____
| | | |
波斯猫 狸花猫 泰迪 哈士奇
要求1函数就应该写ArrayList<? extends Cat>
要求2函数就应该写ArrayList<? extends Dog>
要求3函数就应该写ArrayList<? extends Animal>
2.Set集合
2.1Set集合概述和特点【应用】
回顾一下Collection
2.2Set集合的使用【应用】
存储字符串并遍历
public class Main {
public static void main(String[] args) {
//1.创建set集合
Set<Integer> set = new HashSet<>();
//2.添加元素
//如果当前元素是第一次添加,那么可以添加成功,返回true
//如果当前元素是第二次添加,那么添加失败,返回false
System.out.println(set.add(1));//true
System.out.println(set.add(2));//true
System.out.println(set.add(3));//true
System.out.println(set.add(1));//false
//3.打印集合
System.out.println(set);//可能是无序的
//4.三种方式遍历set
//4.1 迭代器
Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()){
int num = iterator.next();
System.out.println(num);
}
//4.2 增强for循环
for (Integer i : set) {
System.out.println(i);
}
//4.3 lambda表达式
set.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
set.forEach(num-> System.out.println(num));
}
}
3.HashSet集合
3.1HashSet集合概述和特点【应用】
3.2HashSet集合的基本应用【应用】
存储字符串并遍历
public class HashSetDemo {
public static void main(String[] args) {
//创建集合对象
HashSet<String> set = new HashSet<String>();
//添加元素
set.add("hello");
set.add("world");
set.add("java");
//不包含重复元素的集合
set.add("world");
//遍历
for(String s : set) {
System.out.println(s);
}
}
}
3.3哈希值【理解】
-
哈希值简介
是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
-
如何获取哈希值
Object类中的public int hashCode():返回对象的哈希码值
-
哈希值的特点
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同
-
如果没有重写hashCode()方法
public class Student { private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } /** * 获取 * @return name */ public String getName() { return name; } /** * 设置 * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * @return age */ public int getAge() { return age; } /** * 设置 * @param age */ public void setAge(int age) { this.age = age; } public String toString() { return "Student{name = " + name + ", age = " + age + "}"; } }
public class Main { public static void main(String[] args) { Student stu1 = new Student("张三",19); Student stu2 = new Student("张三",19); System.out.println(stu1.hashCode());//557041912 System.out.println(stu2.hashCode());//1134712904 } }
-
如果重写hashCode()方法
public class Student { private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } /** * 获取 * @return name */ public String getName() { return name; } /** * 设置 * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * @return age */ public int getAge() { return age; } /** * 设置 * @param age */ public void setAge(int age) { this.age = age; } 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); } }
public class Main { public static void main(String[] args) { Student stu1 = new Student("张三",19); Student stu2 = new Student("张三",19); System.out.println(stu1.hashCode());//557041912 System.out.println(stu2.hashCode());//1134712904 } }
3.4哈希表结构【理解】
-
JDK1.8以前
数组 + 链表
加载因子=0.75
如果数组中包含了18*0.75=12个元素,则会扩充到原来的两倍,变成32个元素 -
JDK1.8以后
-
节点个数少于等于8个
数组 + 链表
-
节点个数多于8个,且数组长度大于等于64个
数组 + 红黑树
-
3.5 HashSet的三个问题
-
问题1:HashSet为什么存和取的顺序不一样?
因为遍历HashSet的时候,是按照数组的顺序去遍历,遍历到一个数据后,继续遍历之后的链表,这样的遍历顺序和添加顺序是不同的。
-
问题2:HashSet为什么没有索引?
由于HashSet是由数组、链表、红黑树组成,无法给每个都分配一个索引。 -
问题2:HashSet是利用什么机制保证数据去重的?
利用HashCode方法和equals方法确认,如果HashCode相同,则比较equals方法,因此,自定义类一定要重写这两个方法,如果是Stirng,Integer这类java已经写好的,就不用重写这两个方法了。
3.6 小练习
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
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);
}
}
public class Main {
public static void main(String[] args) {
Student stu1 = new Student("张三",19);
Student stu2 = new Student("张三",19);
Student stu3 = new Student("李四",19);
Student stu4 = new Student("王五",19);
HashSet<Student> students = new HashSet<>();
System.out.println(students.add(stu1));//true
System.out.println(students.add(stu2));//false
System.out.println(students.add(stu3));//true
System.out.println(students.add(stu4));//true
System.out.println(students);//[Student{name = 王五, age = 19}, Student{name = 张三, age = 19}, Student{name = 李四, age = 19}]
}
}
4.LinkedHashSet集合
4.1 LinkedHashSet底层原理
遍历的时候,直接是从第一个节点开始遍历,沿着双向链表开始遍历。
实现:
public class Main {
public static void main(String[] args) {
Student stu1 = new Student("张三",19);
Student stu2 = new Student("张三",19);
Student stu3 = new Student("李四",19);
Student stu4 = new Student("王五",19);
LinkedHashSet<Student> students = new LinkedHashSet<>();
System.out.println(students.add(stu1));//true
System.out.println(students.add(stu2));//false
System.out.println(students.add(stu3));//true
System.out.println(students.add(stu4));//true
System.out.println(students);//[Student{name = 张三, age = 19}, Student{name = 李四, age = 19}, Student{name = 王五, age = 19}]
}
}
5.TreeSet集合
5.1TreeSet集合概述和特点【应用】
- 不可以存储重复元素
- 没有索引
- 可以将元素按照规则进行排序
- TreeSet():根据其元素的自然排序进行排序
- TreeSet(Comparator comparator) :根据指定的比较器进行排序
5.2TreeSet集合基本使用【应用】
存储Integer类型的整数并遍历
public class TreeSetDemo01 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Integer> ts = new TreeSet<Integer>();
//添加元素
ts.add(10);
ts.add(40);
ts.add(30);
ts.add(50);
ts.add(20);
ts.add(30);
//遍历集合
for(Integer i : ts) {
System.out.println(i);
}
}
}
10
20
30
40
50
5.3 排序
5.3 自然排序Comparable的使用【应用】
-
案例需求
-
存储学生对象并遍历,创建TreeSet集合使用无参构造方法
-
要求:按照年龄从小到大排序
import java.util.Objects; public class Student implements Comparable<Student>{ private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } /** * 获取 * @return name */ public String getName() { return name; } /** * 设置 * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * @return age */ public int getAge() { return age; } /** * 设置 * @param age */ public void setAge(int age) { this.age = age; } public String toString() { return "Student{name = " + name + ", age = " + age + "}"; } @Override public int compareTo(Student o) { //如果只看年龄来升序排列 int result = this.getAge()-o.getAge(); return result; } }
public class Main { public static void main(String[] args) { TreeSet<Student> students = new TreeSet<>(); Student stu1 = new Student("zhangsan", 18); Student stu2 = new Student("lisi", 17); Student stu3 = new Student("wangwu", 19); System.out.println(students.add(stu1)); System.out.println(students.add(stu2)); System.out.println(students.add(stu3)); System.out.println(students);//[Student{name = lisi, age = 17}, Student{name = zhangsan, age = 18}, Student{name = wangwu, age = 19}] } }
-
5.4 比较器排序Comparator的使用【应用】
-
实现步骤
- 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
- 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
-
代码实现
public static void main(String[] args) { TreeSet<String> strings = new TreeSet<>(new Comparator<String>() { @Override public int compare(String o1, String o2) { //按照长度排序 int result = o1.length() - o2.length(); //如果一样长则按首字母排序 result = result == 0 ? o1.compareTo(o2) : result; return result; } }); strings.add("c"); strings.add("ab"); strings.add("df"); strings.add("qwer"); System.out.println(strings); }
练习题:
-
自然排序实现:
import java.util.Objects; public class Student implements Comparable<Student>{ private String name; private int age; private int chinese; private int math; private int english; private int sum; public Student() { } public Student(String name, int age, int chinese, int math, int english) { this.name = name; this.age = age; this.chinese = chinese; this.math = math; this.english = english; countSum(); } private void countSum(){ this.sum = this.chinese+this.math+this.english; } /** * 获取 * @return name */ public String getName() { return name; } /** * 设置 * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * @return age */ public int getAge() { return age; } /** * 设置 * @param age */ public void setAge(int age) { this.age = age; } /** * 获取 * @return chinese */ public int getChinese() { return chinese; } /** * 设置 * @param chinese */ public void setChinese(int chinese) { this.chinese = chinese; countSum(); } /** * 获取 * @return math */ public int getMath() { return math; } /** * 设置 * @param math */ public void setMath(int math) { this.math = math; countSum(); } /** * 获取 * @return english */ public int getEnglish() { return english; } /** * 设置 * @param english */ public void setEnglish(int english) { this.english = english; countSum(); } public String toString() { return "Student{name = " + name + ", age = " + age + ", chinese = " + chinese + ", math = " + math + ", english = " + english + ", sum="+sum+"}"; } @Override public int compareTo(Student o) { //先按照总分排序 int result = o.sum-this.sum; //如果总分相同,则按照语文成绩排序 result=result==0?o.getChinese()-this.getChinese():result; //如果语文也相同,则按照数学成绩排序 result=result==0?o.getMath()-this.getMath():result; //如果数学也相同,则按照英语成绩排序 result=result==0?o.getEnglish()-this.getEnglish():result; //如果英语也相同,则按照年龄排序 result=result==0?o.getAge()-this.getAge():result; //如果年龄也相同,则按照姓名字母顺序排序 result=result==0?o.getName().compareTo(this.getName()):result; return result; } }
import java.util.*; import java.util.function.Consumer; public class Main { public static void main(String[] args) { //1. 创建学生对象 Student s1 = new Student("zhangsan",18,23,5,6); Student s2 = new Student("zhangsan",19,56,34,32); Student s3 = new Student("zhangsan",17,3,5,6); Student s4 = new Student("zhangsan",17,93,93,93); //2. 创建集合 TreeSet<Student> students = new TreeSet<>(); //3. 添加元素 students.add(s1); students.add(s2); students.add(s3); students.add(s4); for (Student student : students) { System.out.println(student); } } }
-
比较器排序实现:
import java.util.Objects;
public class Student{
private String name;
private int age;
private int chinese;
private int math;
private int english;
private int sum;
public int getSum() {
return sum;
}
public Student() {
}
public Student(String name, int age, int chinese, int math, int english) {
this.name = name;
this.age = age;
this.chinese = chinese;
this.math = math;
this.english = english;
countSum();
}
private void countSum(){
this.sum = this.chinese+this.math+this.english;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
/**
* 获取
* @return chinese
*/
public int getChinese() {
return chinese;
}
/**
* 设置
* @param chinese
*/
public void setChinese(int chinese) {
this.chinese = chinese;
countSum();
}
/**
* 获取
* @return math
*/
public int getMath() {
return math;
}
/**
* 设置
* @param math
*/
public void setMath(int math) {
this.math = math;
countSum();
}
/**
* 获取
* @return english
*/
public int getEnglish() {
return english;
}
/**
* 设置
* @param english
*/
public void setEnglish(int english) {
this.english = english;
countSum();
}
public String toString() {
return "Student{name = " + name + ", age = " + age + ", chinese = " + chinese + ", math = " + math + ", english = " + english + ", sum="+sum+"}";
}
}
public class Main {
public static void main(String[] args) {
//1. 创建学生对象
Student s1 = new Student("zhangsan",18,23,5,6);
Student s2 = new Student("zhangsan",19,56,34,32);
Student s3 = new Student("zhangsan",17,3,5,6);
Student s4 = new Student("zhangsan",17,93,93,93);
//2. 创建集合
TreeSet<Student> students = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
//先按照总分排序
int result = o2.getSum()-o1.getSum();
//如果总分相同,则按照语文成绩排序
result=result==0?o2.getChinese()-o1.getChinese():result;
//如果语文也相同,则按照数学成绩排序
result=result==0?o2.getMath()-o1.getMath():result;
//如果数学也相同,则按照英语成绩排序
result=result==0?o2.getEnglish()-o1.getEnglish():result;
//如果英语也相同,则按照年龄排序
result=result==0?o2.getAge()-o1.getAge():result;
//如果年龄也相同,则按照姓名字母顺序排序
result=result==0?o2.getName().compareTo(o1.getName()):result;
return result;
}
});
//3. 添加元素
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
for (Student student : students) {
System.out.println(student);
}
}
}
5.5两种比较方式总结【理解】
- 两种比较方式小结
-
自然排序: 自定义类实现Comparable接口,重写compareTo方法,根据返回值进行排序
-
比较器排序: 创建TreeSet对象的时候传递Comparator的实现类对象,重写compare方法,根据返回值进行排序
-
在使用的时候,默认使用自然排序,当自然排序不满足现在的需求时(一般都是,已经写好的类按照自然排序已经实现了,但是不满足我们的要求),必须使用比较器排序
-
如果两种方式都实现了,则以比较器为准
-
- 两种方式中关于返回值的规则
- 如果返回值为负数,表示当前存入的元素是较小值,存左边
- 如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
- 如果返回值为正数,表示当前存入的元素是较大值,存右边