菜鸟之路Day09一一集合进阶(二)
作者:blue
时间:2025.1.27
[TOC]
0.概述
内容学习至黑马程序员BV17F411T7Ao,无论如何,今天是值得铭记的一天,我们终于完结了200集的上部。下部漫漫,我们再接再厉。
1.泛型
1.1泛型概述
泛型:是JDK5中引入的新特性,可以在编译阶段约束操作的数据类型,并进行检查
泛型的格式:<数据类型>
注意:泛型只能支持引用数据类型(原因:数据在装入集合的过程中,会做一个泛型的擦除,也就是说在集合中实际存储的是Object类型的数据,而引用数据类型是继承Object的,这里利用了多态,所以这里必须使用引用数据类型。指定了泛型的具体类型后,传入数据时,可以传入该类类型或其子类类型)
泛型的好处:1.统一了数据类型;2.把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能够确定下来
如果不写泛型,类型默认是Object
1.2泛型类
使用场景:当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类
比如下面我们手写一个MyArrayList
import java.util.Arrays;
public class MyArrayList<E> {
Object[] obj = new Object[10];
int size;
public boolean add(E e){
obj[size] = e;
size++;
return true;
}
public E get(int index) {
return (E)obj[index];}
@Override
public String toString() {
return Arrays.toString(obj);
}
}
使用一下我们自己写的MyArrayList
public class GenericsDemo1 {
public static void main(String[] args) {
MyArrayList<Integer> list = new MyArrayList<>();
list.add(123);
list.add(234);
list.add(345);
list.add(456);
System.out.println(list.get(3));
}
}
1.3泛型方法
方法中形参类型不确定时:方案①:使用类名后面定义的泛型(所有方法都能用);方案②:在方法申明上定义自己的泛型(只有本方法能用)
泛型方法的格式:
/*
修饰符 <类型> 返回值类型 方法名(类型 变量名){
方法体
}
*/
练习:定义一个工具类:ListUtil
类中定义一个静态方法addAll,用来添加多个集合的元素
package Generics;
import java.util.ArrayList;
public class ListUtil {
private ListUtil(){
}//工具类,私有化其构造方法
//静态方法的话,泛型写在static之后
public static<E> void addAll(ArrayList<E> list,E e1,E e2,E e3,E e4){
list.add(e1);
list.add(e2);
list.add(e3);
list.add(e4);
}
}
1.4泛型接口
/*修饰符 interface 接口名<类型>{
}*/
泛型接口的重点在于:如何使用一个带泛型的接口
方式1:实现类给出具体的类型
public class MyArrayList implements List<String>
方式2:实现类延续泛型,创建对象时再确定
public class MyArrayList<E> implement List<E>
MyArrayList<String> list = new MyArrayList<>();
1.5泛型通配符
/*
?也表示不确定类型
他可以进行类型的限定
? extends E:表示可以传递E或者E所有的子类类型
? super E:表示可以传递E或者E所有父亲类型
应用场景:
1.如果我们在定义类,方法,接口的时候,如果类型不确定,就可以定义泛型类,泛型方法,泛型接口
2.如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以使用泛型的通配符
泛型的通配符:
关键点:可以限定类型的范围
*/
package Generics;
import java.util.ArrayList;
public class GenericsDemo2 {
public static void main(String[] args) {
ArrayList<Ye> list1 = new ArrayList<>();
ArrayList<Fu> list2 = new ArrayList<>();
ArrayList<Zi> list3 = new ArrayList<>();
method(list1);
method(list2);
method(list3);
//method(new ArrayList<Integer>());这个就不行
}
public static void method(ArrayList<? extends Ye> list){
}
}
class Ye {
}
class Fu extends Ye{
}
class Zi extends Fu {
}
2.Set系列集合
Set系列集合:添加的元素是无序,不重复,无索引的
Set集合的实现类特点
名字 | 特点 |
---|---|
HashSet | 无序,不重复,无索引 |
LinkedHashSet | 有序,不重复,无索引 |
TreeSet | 可排序,不重复,无索引 |
2.1遍历方式
Set还是属于Collection系列的所以它的API与Collection是一样,不同是其遍历方式,因为它是无索引的,所以只能用Collection通用的遍历方式,迭代器,增强for,Lambada表达式
package SetDemo;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.function.Consumer;
public class SetDemo1 {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("aaa");
set.add("aaa");//利用set去重
set.add("bbb");
set.add("ccc");
set.add("ddd");
//迭代器遍历
Iterator<String> it = set.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
//增强for
for(String i:set){
System.out.println(i);
}
//Lambda表达式
set.forEach((String s)->{
System.out.println(s);
});
}
}
2.2HashSet
HashSet集合的底层原理采取哈希表存取数据
哈希表是一种对于增删改查数据性能都较好的结构
哈希值
根据hashCode方法算出来的int类型的整数整数
该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
一般情况下,会重写hashCode方法,利用对象内部属性值计算哈希值
注意:
如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
如果重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
在小部分情况下,不同属性值或者不同地址值计算出来的哈希值也有可能一样。(哈希碰撞)
练习:
需求
package SetDemo;
import java.util.HashSet;
public class HashSetDemo1 {
public static void main(String[] args) {
HashSet<Student> set = new HashSet<>();
Student stu1 = new Student("zhangsan",11);
Student stu2 = new Student("lisi",12);
Student stu3 = new Student("wangwu",13);
set.add(stu1);
set.add(stu2);
set.add(stu3);
//这个添加是失败的,因为他是根据hashCode来去重的,我们重写了hashCode方法,使得属性一样的hashCode是一致的
Student stu4 = new Student("zhangsan",11);
System.out.println(set.add(stu4));//false
System.out.println(stu1.hashCode());
System.out.println(stu4.hashCode());
for(Student i : set){
System.out.println(i);
}
}
}
2.3LinkedHashSet
有序,不重复,无索引
这里的有序指的是保证存储和取出的元素顺序是一致的,因为有序的要求更复杂,所以它的效率比HashSet的效率低一点
原理:底层数据结构依然是哈希表,只是每个元素又额外多了一个双链表的机制记录存储的顺序
打印出来的顺序是,按照我们添加的顺序
public class LinkedHashSetDemo1 {
public static void main(String[] args) {
LinkedHashSet<Student> set = new LinkedHashSet<>();
Student stu1 = new Student("zhangsan",11);
Student stu2 = new Student("lisi",12);
Student stu3 = new Student("wangwu",13);
set.add(stu1);
set.add(stu2);
set.add(stu3);
System.out.println(set);
}
}
2.4TreeSet
TreeSet,不重复,无索引,可排序
可排序:按照元素的默认规则(有小到大)排序
TreeSet集合的底层是基于红黑树的数据结构实现排序的,增删改查的性能都比较好
//基本使用
package SetDemo;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.function.Consumer;
public class TreeSetDemo1 {
public static void main(String[] args) {
TreeSet<Integer> ts = new TreeSet<>();
ts.add(2);
ts.add(1);
ts.add(5);
ts.add(4);
ts.add(3);
System.out.println(ts);//自动排序
//增强for
for(Integer i : ts){
System.out.print(i+" ");
}
System.out.println();
//迭代器
Iterator<Integer> it = ts.iterator();
while(it.hasNext()){
System.out.print(it.next()+" ");
}
System.out.println();
//Lambada
ts.forEach((Integer i)->{
System.out.print(i+" ");
});
}
}
TreeSet集合默认的规则
对于数值类型:Integer,Double,默认按照从小到大的顺序排序
对于字符,字符串类型:按照字符在ASCII码表中的数字升序进行排序
自定义类型如何排序
方式一:自然排序/默认排序:javabean类实现Comparable接口指定比较规则
package SetDemo;
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;
}
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 int compareTo(Student o) {
//按照年龄大小排序
return this.getAge()-o.getAge();
}
}
package SetDemo;
import java.util.TreeSet;
public class TreeSetDemo2 {
public static void main(String[] args) {
Student stu1 = new Student("zhangsan",11);
Student stu2 = new Student("lisi",12);
Student stu3 = new Student("wangwu",13);
TreeSet<Student> ts = new TreeSet<>();
ts.add(stu1);
ts.add(stu2);
ts.add(stu3);
System.out.println(ts);
}
}
方式二:比较器排序,创建TreeSet对象的时候,传递比较器Comparator指定规则
package SetDemo;
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSetDemo3 {
public static void main(String[] args) {
//按照长度排序,如果一样长则按照首字母排序
TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
@Override
/*
o1:表示当前要添加的元素
o2:表示已经在红黑树中的元素
返回值的规则和之前是一样的
*/
public int compare(String o1, String o2) {
//按照长度排序
int i = o1.length()-o2.length();
//如果一样长则按照首字母排序
i=i==0?o1.compareTo(o2):i;//String的内置比较方法
return i;
}
});
ts.add("c");
ts.add("ab");
ts.add("df");
ts.add("qwer");
}
}
改为Lambada
package SetDemo;
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSetDemo3 {
public static void main(String[] args) {
//按照长度排序,如果一样长则按照首字母排序
TreeSet<String> ts = new TreeSet<>(
(String o1, String o2) ->{
//按照长度排序
int i = o1.length()-o2.length();
//如果一样长则按照首字母排序
i=i==0?o1.compareTo(o2):i;//String的内置比较方法
return i;
}
);
ts.add("c");
ts.add("ab");
ts.add("df");
ts.add("qwer");
for (String i:ts){
System.out.println(i);
}
}
}