JAVA入门-Collection单列集合体系
(一)Collection单列集合体系
1.Collection的常用方法?
Collection是单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的
方法名 | 说明 |
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数。 |
public Object[] toArray() | 把集合中的元素,存储到数组中 |
package Nums;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
public class CollectionTest1 {
public static void main(String[] args) {
Collection<String > c=new ArrayList<>(); //多态写法(Collection本身是一个接口,不能创建实例;可以创建其子类的实例())
// Collection c2=new HashSet(); 其他子类
//2.添加元素成功返回true : public boolean add(E e)
c.add("a");
c.add("cs");
c.add("b");
c.add("d");
System.out.println(c); //[a, cs, b, d]
//3.public void clear() 清空集合中所有的元素g
// c.clear();
// System.out.println(c);
//4.public boolean remove(E e) 把给定的对象在当前集合中删除
c.remove("b");
System.out.println(c); //[a, cs, d]
//5.public boolean contains(Object obj) 判断当前集合中是否包含给定的对象
System.out.println(c.contains("b"));//false
System.out.println(c.contains("a"));//true
//6.public boolean isEmpty() 判断当前集合是否为空
System.out.println(c.isEmpty());//false
//7.public int size() 返回集合中元素的个数。
System.out.println(c.size());//3
//8.public Object[] toArray() 把集合中的元素,存储到数组中
Object[] array = c.toArray();//toArray():将集合转换为数组,返回 Object[] 类型,因为泛型在运行时会被擦除。。
// System.out.println("转换为数组:" + array.toString());//不能直接打印数组,数组的 toString() 方法会输出内存地址,而非元素内容
System.out.println(Arrays.toString(array)); //Arrays.toString 将数组转换为可读性更好的字符串,元素之间用逗号分隔,便于调试。
//8.1也可以直接设置转换出的类型
String[] array2=c.toArray(new String[c.size()]); //创建一个 String 类型的数组,数组的长度为集合 c 的元素个数
//将这个数组作为参数传递给 toArray() 方法,这样 toArray() 方法会返回一个包含集合 c 中所有元素的 String 类型数组,并赋值给 array2 变量
System.out.println(Arrays.toString(array2));
System.out.println("----------------------------------------");
//讲一个集合全部的数据倒入另一个集合 addAll()
Collection<String> c1=new ArrayList<>();
c1.add("asa");
c1.add("asd");
Collection<String> c2=new ArrayList<>();
c2.add("asd2");
c2.add("asd3");
//c1内容倒入c2()
c2.addAll(c1);
System.out.println(c1);//[asa, asd]
System.out.println(c2);//[asd2, asd3, asa, asd]
}
}
(2)collection的遍历方式(迭代器,增强for,Lambda表达式)
1.使用迭代器获取
1、如何获取集合的迭代器? 迭代器遍历集合的代码具体怎么写?
2、通过迭代器获取集合的元素,如果取元素越界会出现什么异常?
(1)第一次创建迭代器时,并没有指向第一个元素,而是在第一个元素之前,向后移动迭代器才能指向第一个元素
(2)HasNext()检查是否还有下一个元素,不移动迭代器
(3)Next()将迭代器移动到下一个位置,并返回当前指向的元素,一定要先检查再移动
(4)Remove()删除当前迭代器指向的元素(需要在next()之后调用)
package Nums;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class GetCollection_1 {
public static void main(String[] args){
Collection<String > c=new ArrayList<>();
c.add("a");
c.add("cs");
c.add("b");
c.add("d");
Iterator<String> it=c.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
2.增强for
//2.增强for
for (String s : c) { //c.for 回车,直接生成增强for结构 (这种结构不适合需要根据索引查询的数组,程序内部直接指针下移获取)
System.out.println(s);
}
3.Lambda表达式
//3. Lambda表达式
c.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
//简化
c.forEach(s->System.out.println(s));
Lambda表达式的函数头 default void forEach(Consumer<? super T> action)
Consumer
是 Java 8 中引入的一个函数式接口,位于java.util.function
包中。Consumer
接口只有一个抽象方法accept(T t)
,该方法接受一个参数并执行某些操作,但不返回任何结果。? super T
是一个通配符类型,表示Consumer
可以接受T
类型或者T
的超类型。这里的T
是集合元素的类型。例如,如果集合Collection<String>
,? super String
可以表示String
本身,也可以表示Object
(因为Object
是String
的超类型)。这使得forEach
方法更加灵活,可以接受处理更宽泛类型的Consumer
。action
是一个具体的Consumer
实例,它定义了要对集合中的每个元素执行的操作。可以通过 Lambda 表达式或方法引用来创建Consumer
实例传递给forEach
方法。
PS:Lambda表达式的简化规则
(三) List类型
(1)存储 对象的特点
package MyCollection;
import java.util.ArrayList;
import java.util.List;
//collection 存储对象
public class CollectionTest2 {
public static void main(String[] args) {
List<Movie> movies=new ArrayList<>();
movies.add(new Movie("贷记卡就",98.0,"王兵"));
movies.add(new Movie("拿成就看到",95.80,"周围"));
movies.add(new Movie("王子的手机",97.05,"书店"));
movies.add(new Movie("竹地板",90.78,"瓦解"));
System.out.println(movies);
//遍历对象 for增强遍历
for (Movie movie : movies) {
System.out.println("电影名:"+movie.getName());
}
}
}
(2)List遍历方式
package MyList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
//遍历List类型数据
public class ListFor {
public static void main(String[] args) {
//1.创建一个ArrayList集合对象(有序,可重复,有索引)
List<String> lis=new ArrayList<>();
lis.add("蜘蛛男包");
lis.add("糖葫芦");
lis.add("灌汤包");
lis.add("大闸蟹");
System.out.println(lis);
//2.for循环遍历(因为有索引) 使用lis.fori 可以快速创建
for (int i = 0; i < lis.size(); i++) {
System.out.println("2:"+lis.get(i));
}
//3.迭代器
Iterator<String> it=lis.iterator();
while(it.hasNext()){
System.out.println("3:"+it.next());
}
//4.for增强 使用lis.for 可以快速创建
for (String li : lis) {
System.out.println("4:"+li);
}
//5.Lambda表达式 JDK1.8之后开始使用
lis.forEach(s->System.out.println("5:"+s));
}
}
(四)ArrayList类型(继承于List,数组实现,有序,可重复,有索引)
(2)适用场景
(五)LinkedList类型(双链表实现,有序,可重复,有索引)
package MyList;
import java.util.LinkedList;
//LinkedList
public class MyLinkedList {
public static void main(String[] args) {
//创建一个队列
LinkedList<String> q =new LinkedList<>();
q.addLast("1号");
q.addLast("2号");
q.addLast("3号");
q.addLast("4号");
q.push("5");
System.out.println(q);
//出队
System.out.println(q.removeFirst());
System.out.println(q);
//2.创建一个栈对象
LinkedList<String> stack=new LinkedList<>();
//压栈(push)
stack.push("导弹1号");
stack.push("导弹2号");
stack.push("导弹3号");
//压栈
System.out.println(stack.pop());
System.out.println(stack.removeFirst());
System.out.println(stack);
//获取第一个元素
System.out.println(stack.getFirst());
//获取最后一个元素
System.out.println(stack.getLast());
}
}
(三) Set类型(继承自Collection)
package MyList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
public class TreeSet {
public static void main(String[] args) {
//1.创建一个Set集合
Set<Integer> set1=new HashSet<>(); //多态写法
set1.add(111);
set1.add(222);
set1.add(333);
set1.add(444);
System.out.println(set1);
}
}
(3.1)HashSet类型(有序,不重复,无索引)
哈希的实现思想:查找的过程中,可以不通过任何的比较,而是让记录的关键码和记录的存储位置通过某种手段建立起一种一对一的映射关系,通过关键码直接就可以找到目标记录
package MySet;
public class ObjectHash {
public static void main(String[] args) {
Student s1=new Student("蜘蛛精",18,98);
Student s2=new Student("紫霞",22,188);
//查看每个对象的哈希值
System.out.println(s1.hashCode());
System.out.println(s1.hashCode()); //同一对象的哈希值不变
System.out.println(s2.hashCode());
//String 类型的hashCode查看
String t1=new String("asad");
String t2=new String("duadb");
System.out.println(t1.hashCode());
System.out.println(t1.hashCode());
}
}
注意:HashSet集合不能对内容一样的两个不同对象去重!!
比如内容一样的两个学生对象存入到HashSet集合中去 , HashSet集合是不能去重复的!
如何让HashSet集合能够实现对内容一样的两个不同对象也能去重复???
1. 如果希望 Set 集合认为 2 个内容相同的对象是重复的应该怎么办?重写对象的 hashCode 和 equals 方法。
package MySet;
import java.util.Objects;
public class Student {
String name;
int age;
double height;
public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
//------------------------重写的方法
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o; //将传入的对象强制转换为学生对象,
return age == student.age && Double.compare(height, student.height) == 0 && Objects.equals(name, student.name); //当两个对象的值相同时判定为相等
}
@Override
public int hashCode() {
return Objects.hash(name, age, height);
}
//---------------------------
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
public Student() {
}
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;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
}
package MySet;
import java.util.HashSet;
//重写对象中的equals()和hashCode() 实现HashSet对对象的排序
public class MyHashSet {
public static void main(String[] args) {
//创建一个HashSet数组,用于装学生对象
HashSet<Student> Sset=new HashSet<>();
Student s1=new Student("牛魔王",888,99.8);
Student s2=new Student("牛魔王",888,99.8);
// 计算 s1 的哈希码
int hashCode1 = s1.hashCode();
// 计算 s2 的哈希码
int hashCode2 = s2.hashCode();
//将他们都放入Set集合查看是否能够去重
Sset.add(s1);
Sset.add(s2);
System.out.println(Sset);
System.out.println("s1 的哈希码: " + hashCode1);
System.out.println("s2 的哈希码: " + hashCode2);
System.out.println("s1 和 s2 的哈希码是否相等: " + (hashCode1 == hashCode2));
}
}
计算哈希码:
- 当调用
s1.hashCode()
时,会执行Objects.hash(name, age, height)
。
- 对于
name
(字符串"Alice"
),String
类重写了hashCode
方法,它会根据字符串的字符内容计算哈希码。假设"Alice"
的哈希码为h1
。- 对于
age
(基本数据类型int
),Objects.hash
会直接使用age
的值作为其哈希码贡献。- 对于
height
(基本数据类型double
),Objects.hash
会先将double
转换为long
类型,然后根据long
类型的值计算哈希码。假设1.75
转换后的哈希码贡献为h2
。- 然后
Objects.hash
会将这些哈希码组合起来,假设组合后的哈希码为result1
。- 当调用
s2.hashCode()
时,同样的过程会再次执行。由于s2
的name
、age
和height
与s1
相同,所以"Alice"
的哈希码还是h1
,age
的值还是20
,1.75
转换后的哈希码贡献还是h2
,最终组合后的哈希码result2
会和result1
相等
(3.2)LinkedHashSet类型(有序(指插入顺序),不重复,无索引)
(1)底层原理
哈希表的详细流程
① 创建一个默认长度 16 ,默认加载因为 0.75 的数组,数组名 table② 根据元素的哈希值跟数组的长度计算出应存入的位置③ 判断当前位置是否为 null ,如果是 null 直接存入,如果位置不为 null ,表示有元素, 则调用 equals 方法比较属性值,如果一样,则不存,如果不一样,则存入数组。④ 当数组存满到 16*0.75=12 时,就自动扩容,每次扩容原先的两倍
package MySet;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
public class MyLinkedHashSet {
public static void main(String[] args) {
//1.创建一个Set集合
Set<Integer> set1=new LinkedHashSet<>(); //多态写法
set1.add(555);
set1.add(222);
set1.add(333);
set1.add(444);
System.out.println(set1); //[555, 222, 333, 444]
}
}
(3.3)TreeSet类型 (可排序(默认升序),不重复,无索引,基于红黑树实现)
package MySet;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
public class MyTreeSet {
public static void main(String[] args) {
//Type1 对于数值类型(Integer Double),默认按照数值本身的大小进行升序排序
Set<Integer> set1=new TreeSet<>();
set1.add(90);
set1.add(50);
set1.add(70);
set1.add(60);
System.out.println(set1);//[50, 60, 70, 90]
}
}
(一)让自定义的(学生)类实现Comparable接口,重写里面的compareTo方法来指定规则
(1)修改学生对象,让他继承Comparable<Student> 并重写compareTo(Student o)方法
class Student implements Comparable<Student> {
public int age;
public Student(int age) {
this.age = age;
}
@Override
public int compareTo(Student o) {
return this.age - o.age;
}
}
(2)此时将Student对象添加到TreeSet数组时会自动 调用compareTo()进行比较排序
package MySet;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
public class MyTreeSet {
public static void main(String[] args) {
//Type2 对于自定义的对象,有两种自定义排序规则可以使用
//方法1:让自定义的(学生)类实现Comparable接口,重写里面的compareTo方法来指定规则
//创建一个HashSet数组,用于装学生对象
TreeSet<Student> Sset=new TreeSet<>();
Student s1=new Student("牛魔王",888,99.8);
Student s2=new Student("牛魔王",888,99.8);
Student s3=new Student("小牛",86,60);
Student s4=new Student("孙悟空",689,291);
Sset.add(s1);
Sset.add(s2);
Sset.add(s3);
Sset.add(s4);
System.out.println(Sset);
}
}
(二)通过调用TreeSet集合的有参构造器,设置Comparator对象
package MySet;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
public class MyTreeSet {
public static void main(String[] args) {
//Type1 对于数值类型(Integer Double),默认按照数值本身的大小进行升序排序
Set<Integer> set1=new TreeSet<>();
set1.add(90);
set1.add(50);
set1.add(70);
set1.add(60);
System.out.println(set1);//[50, 60, 70, 90]
//Type2 对于自定义的对象,有两种自定义排序规则可以使用
//方法1:让自定义的(学生)类实现Comparable接口,重写里面的compareTo方法来指定规则
//创建一个HashSet数组,用于装学生对象
TreeSet<Student> Sset=new TreeSet<>();
Student s1=new Student("牛魔王",888,99.8);
Student s2=new Student("牛魔王",888,99.8);
Student s3=new Student("小牛",86,60);
Student s4=new Student("孙悟空",689,291);
Sset.add(s1);
Sset.add(s2);
Sset.add(s3);
Sset.add(s4);
System.out.println(Sset);//[Student{name='小牛', age=86, height=60.0}, Student{name='孙悟空', age=689, height=291.0}, Student{name='牛魔王', age=888, height=99.8}]
//Type3 通过调用TreeSet集合的有参构造器,设置Comparator对象
TreeSet<Student> set2=new TreeSet(new Comparator<Student>(){
@Override
public int compare(Student o1,Student o2){
//按照身高升序排序
return Double.compare(o1.getHeight(), o2.getHeight()); //降序只需要改为: Double.compare(o2.getHeight(), o1.getHeight())
}
});
set2.add(s1);
set2.add(s2);
set2.add(s3);
set2.add(s4);
System.out.println(set2); //[Student{name='小牛', age=86, height=60.0}, Student{name='牛魔王', age=888, height=99.8}, Student{name='孙悟空', age=689, height=291.0}]
}
}
注意:reeSet
优先采用 Comparator
对象的排序规则,是因为 Comparator
提供了更为灵活的外部排序方式,可在不修改元素类的情况下指定排序规则。
Student
类实现了Comparable
接口,重写了compareTo
方法,其排序规则是按年龄升序排列。- 在创建
TreeSet
时传入了Comparator
对象,重写了compare
方法,排序规则为按身高升序排列。- 运行代码后,输出结果会按照身高升序排列,这表明优先使用了
Comparator
对象的排序规则。
(3.4)集合的并发修改异常
package MySet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
//理解集合的并发修改异常,并解决
public class NumException {
public static void main(String[] args) {
List<String> lis=new ArrayList<>();
lis.add("王麻子");
lis.add("小李子");
lis.add("李毅");
lis.add("安居客等");
lis.add("小李");
lis.add("李明");
lis.add("李玉刚");
//需求:找到集合中全部带李的名字,并从集合中删除
Iterator<String> it=lis.iterator();
while(it.hasNext()){
String name=it.next();
if(name.contains("李")){
lis.remove(name);
}
}
System.out.println(lis);
}
}
上面代码会出现报错:
java.util.ConcurrentModificationException
异常,表示在使用迭代器遍历集合时,对集合的结构进行了修改(如添加、删除元素)而没有使用迭代器自身的修改方法时抛出。因为原始集合中元素为:[王麻子, 小李子, 李毅, 安居客等, 小李, 李明, 李玉刚]
当迭代器指向“小李子”时,调用集合自身的
remove
方法(如lis.remove(name)
)来移除元素时,集合的结构发生了改变。但是,迭代器内部维护的状态并没有得到相应的更新,这就导致了迭代器所认为的集合状态与实际的集合状态不一致。解决办法
1. 使用迭代器的
remove
方法迭代器的
remove
方法会正确地更新迭代器内部的状态,保证迭代器与集合的状态一致。Iterator<String> it = lis.iterator(); while (it.hasNext()) { String name = it.next(); if (name.contains("李")) { it.remove(); // 使用迭代器的 remove 方法移除元素 } }
(2)for循环同样会有并发修改异常,可以看到下面代码无法完整删除所有包含李的元素。因为i指向的是数组的下标。对于[王麻子, 小李子, 李毅, 安居客等, 小李, 李明, 李玉刚] 当指向小李子时(i=1),小李子被删除,此时后面的元素会往前移[王麻子, 李毅, 安居客等, 小李, 李明, 李玉刚] ,但是i继续执行i++ ,此时i=2,忽略了对”李毅“的判断,导致错误。
解决:在每次删除后执行i--;
for (int i = 0; i < lis.size(); i++) {
String name=lis.get(i);
if(name.contains("李")){
lis.remove(i);
// i--;
}
}
System.out.println(lis);//[王麻子, 李毅, 安居客等, 李明]
(二)Lambda表达式
(1)函数式接口?是指只包含一个抽象方法的接口。Java 中的 @FunctionalInterface
注解可以用来标记一个接口是函数式接口,但这个注解不是必需的,编译器会自动检查接口是否符合函数式接口的定义
// 使用 @FunctionalInterface 注解标记函数式接口
@FunctionalInterface
interface MyFunctionalInterface {
void doSomething();
}
(2)Lamda实现简化?只能简化函数式接口的匿名内部类。
TreeSet<Student> set2=new TreeSet<>(new Comparator<Student>(){
@Override
public int compare(Student o1,Student o2){
//按照身高升序排序
return Double.compare(o1.getHeight(), o2.getHeight());
}
});
简化为:
TreeSet<Student> set2=new TreeSet<>(( Student o1,Student o2)-> Double.compare(o1.getHeight(), o2.getHeight()));
(3)匿名内部类?是一种没有显式命名的内部类,在定义类的同时就创建了该类的一个实例。通常用于创建只需要使用一次的类的实例
// 定义一个接口
interface MyInterface {
void doSomething();
}
public class AnonymousInnerClassExample {
public static void main(String[] args) {
// 使用匿名内部类实现接口
MyInterface myInterface = new MyInterface() {
@Override
public void doSomething() {
System.out.println("Doing something using anonymous inner class.");
}
};
myInterface.doSomething();
}
}
(4)Java中,接口确实不能直接实例化(因为接口没有构造方法,也没有具体实现),但通过匿名内部类可以创建一个实现该接口的对象实例。
(5)抽象类可以有构造方法