Java集合

集合(Collection和Map两种)

集合体系结构:

Collection代表单列集合,每个元素(数据)只包含一个值。

Map代表双列集合,每个元素包含两个值(键值对)。

Collection

List系列集合:添加的元素是有序可重复有索引

  • ArrayListLinekdList有序可重复有索引

Set系列集合:添加的元素是无序(添加数据的顺序和获取出的数据顺序不一致)、不重复无索引(不支持通过索引操作数据)。

  • HashSet无序不重复无索引
  • LinkedHashSet有序不重复无索引
  • TreeSet按照大小默认升序排序不重复无索引
Collection的常用方法

Collection是单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Collection<String> c = new ArrayList<String>();
c.add("A");
c.add("B");
System.out.println(c);//[A, B]

System.out.println(c.size());//2
System.out.println(c.isEmpty());//false
c.remove("B");//[A]
System.out.println(c.contains("A"));//true

Object[] arr = c.toArray();//把集合转换成数组
System.out.println(Arrays.toString(arr));//[A]

c.clear();//[] 清空集合

Collection<String> c1 = new ArrayList<>();
c1.add("A");
c1.add("srr");
c.addAll(c1);//把c1数据全部加入c中
System.out.println(c.toString() + c1);//[A, srr][A, srr]

Collection没有规定集合的索引,Collection不支持使用for循环进行遍历。

Collection的遍历方式

1.迭代器是用来遍历集合的专用方式(数组没有迭代器),在Java中迭代器的代表是Iterator

通过迭代器获取集合的元素,如果取元素越界会出现NoSuchElementException异常。

1
2
3
4
5
6
7
8
Collection<String> c2 = new ArrayList<>();
c2.add("AAA");
c2.add("BBB");

Iterator<String> it = c2.iterator();//1.从集合对象获取迭代器对象
while (it.hasNext()) {//使用循环和迭代器遍历集合
System.out.println(it.next());
}

2.增强for循环

增强for可以用来遍历集合或者数组。增强for遍历集合,本质就是迭代器遍历集合的简化写法

注意:修改增强for中的变量值不会影响到集合中的元素。

1
2
3
4
5
for(String ele: c2){//2.增强for遍历集合
System.out.println(ele);
ele = "aaa";//修改增强for中的变量值不会影响到集合中的元素
}
System.out.println(c2);//上述for循环中并没有通过ele修改集合的值

3.Lambda表达式遍历集合

1
default void forEach(Consumer<? super T> action) 	//结合lambda遍历集合
1
2
3
4
5
6
7
8
9
c2.forEach(new Consumer<String>() {//3.结合Lambda表达式遍历集合
@Override
public void accept(String s) {
System.out.println(s);
}
});
//简化版本:
c2.forEach(s-> System.out.println(s));
c2.forEach(System.out::println);
1
2
3
4
5
6
7
//forEach方法的源码:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {//底层使用了增强for循环实现
action.accept(t);
}
}

List

List集合支持的遍历方式:for循环(因为List集合有索引)、迭代器 、增强for循环、Lambda表达式。

1
2
3
4
5
6
7
8
9
10
11
List<String> list1 = new ArrayList<>();
list1.add("srr");
list1.add("18");

list1.add(2, "888");//在某个索引处插入元素
System.out.println(list1.remove(1));//删除某个索引处元素
System.out.println(list1.get(0));//只能用get方法访问,不能用[]
System.out.println(list1.set(1, "666"));//修改索引处元素
for (int i = 0; i < list1.size(); i++) {
System.out.println(list1.get(i));
}
ArrayList的底层原理:基于数组实现

特点:查询快,增删慢。

1
2
3
4
1.利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组。
2.添加第一个元素时,底层会创建一个新的长度为10的数组。
3.存满时,会扩容1.5倍。
4.如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准。
LinkedList的底层原理:基于双链表实现

特点:查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的。

LinkedList的应用场景:

1.设计队列。

1
2
3
4
5
6
7
8
LinkedList<String> queue = new LinkedList<>();
//入队
queue.addLast("1");
queue.addLast("2");
System.out.println(queue);
//出队
queue.removeFirst();
System.out.println(queue);

2.设计栈。

1
2
3
4
5
6
7
8
9
10
11
LinkedList<String> stack = new LinkedList<>();
//入栈
stack.addFirst("1");
stack.addFirst("2");
stack.push("3");
stack.push("4");
System.out.println(stack);
//出栈
stack.removeFirst();
stack.pop();
System.out.println(stack);
1
2
3
4
//push方法源码:和使用addFirst一样
public void push(E e) {
addFirst(e);
}
1
2
3
4
//pop方法源码:和使用removeFirst一样
public E pop() {
return removeFirst();
}

Set

Set系列集合:添加的元素是无序(添加数据的顺序和获取出的数据顺序不一致)、不重复无索引(不支持通过索引操作数据)。

  • HashSet: 无序不重复无索引
  • LinkedHashSet: 有序不重复无索引
  • TreeSet按照大小默认升序排序不重复无索引

注意:Set要用到的常用方法,基本上就是Collection提供的。自己几乎没有额外新增一些常用功能。

1
2
3
4
5
6
7
8
//Set<String> set = new HashSet<>();//[222, 18, 888]
//Set<String> set = new LinkedHashSet<>();//[18, 222, 888]
Set<String> set = new TreeSet<>();//[18, 222, 888]
set.add("18");
set.add("222");
set.add("222");
set.add("888");
System.out.println(set);
HashSet

HashSet无序不重复无索引

哈希值:是一个int类型的数值,Java中每个对象都有一个哈希值。

Java中的所有对象,都可以调用Obejct类提供的hashCode方法,返回该对象自己的哈希值。

1
public int hashCode() //返回对象的哈希码值。 

同一个对象多次调用hashCode()方法返回的哈希值是相同的。

不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)。

HashSet集合的底层原理:基于哈希表实现。

哈希表:

  • JDK8之前,哈希表 = 数组+链表
  • JDK8开始,哈希表 = 数组+链表+红黑树

JDK8之前HashSet集合的底层原理,基于哈希表:数组+链表

1
2
3
4
5
6
7
8
9
1.创建一个默认长度16的数组,默认加载因子为0.75,数组名table
2.使用元素的哈希值对数组的长度求余计算出应存入的位置
3.判断当前位置是否为null,如果是null直接存入
4.如果不为null,表示有元素,则调用equals方法比较
比较相等,则不存;不相等,则存入数组
5.JDK8之前,新元素存入数组,占老元素位置,老元素挂下面
6.JDK8开始之后,新元素直接挂在老元素下面
如果链表过长,则进行扩容:
当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍。

JDK8开始HashSet集合的底层原理,基于哈希表:数组 + 链表 + 红黑树

1
2
JDK8开始,当链表长度超过8,且数组长度>=64时,自动将链表转成红黑树
红黑树是有排序的,通过哈希值确定元素的大小,小的哈希值是左子树,大的哈希值是右子树。

注意:HashSet集合默认不能对内容一样的两个不同对象去重复。比如内容一样的两个学生对象存入到HashSet集合中去 , HashSet集合是不能去重复的。

解决办法:如果希望Set集合认为2个内容一样的对象是重复的,必须重写对象的hashCodeequals方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Student {
private String name;
private int age;

public Student(String name, int age) {
this.name = name;
this.age = age;
}
//重写equals方法
@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);
}
//重写hashCode方法
@Override
public int hashCode() {
return Objects.hash(name, age);
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
1
2
3
4
5
6
Set<Student> students = new HashSet<>();
students.add(new Student("srr", 18));
students.add(new Student("scb", 222));
students.add(new Student("scb", 222));
System.out.println(students);//[Student{name='srr', age=18}, Student{name='scb', age=222}]
//若不重写hashCode方法和equals方法,则会出现重复的scb:[Student{name='srr', age=18}, Student{name='scb', age=222}, Student{name='scb', age=222}]
LinkedHashSet

LinkedHashSet有序不重复无索引

LinkedHashSet的底层原理:基于哈希表(数组、链表、红黑树)实现。但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置

TreeSet

TreeSet按照大小默认升序排序不重复无索引。底层是基于红黑树实现的排序

注意:

1.对于数值类型:Integer , Double,默认按照数值本身的大小进行升序排序。

2.对于字符串类型:默认按照首字符的编号升序排序。

3.对于自定义类型如Student对象,TreeSet默认是无法直接排序的。

解决方法:TreeSet集合存储自定义类型的对象时,必须指定排序规则,支持如下两种方式来指定比较规则。

1.让自定义的类(如学生类)实现Comparable接口,重写里面的compareTo方法来指定比较规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Student implements Comparable<Student> {
private String name;
private int age;
private double score;

public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}

//重写compareTo方法来指定比较规则
@Override
public int compareTo(Student o) {
return this.age - o.age;//注意:若两个对象的age则相等返回0,即使name和score不一样,也会被认定为相同被去重
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
public double getScore() {
return score;
}
}
1
2
3
4
5
6
7
Set<Student> set1 = new TreeSet<>();
set1.add(new Student("sll", 48, 88.8));
set1.add(new Student("srr", 18, 99.8));
set1.add(new Student("scb", 18, 96.8));
System.out.println(set1);
//[Student{name='srr', age=18, score=99.8}, Student{name='sll', age=48, score=88.8}]
//注意:因为compareTo规定age相等返回0,此时Treeset集合只会保留一个元素,认为两者重复。所以后加入的scb即使name和score和srr不一样,但是两者age一样,所以scb会被去重。

2.通过调用TreeSet集合有参数构造器,可以设置Comparator对象(比较器对象),用于指定比较规则。

1
public TreeSet(Comparator<? super E> comparator)

注意:如果类本身有实现Comparable接口,TreeSet集合同时也自带比较器,默认使用集合自带的比较器排序

1
2
3
4
5
6
7
8
9
10
11
12
13
Set<Student> set2 = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return Double.compare(o1.getScore(), o2.getScore());//按照score大小升序排序
}
});
//简化格式:
//Set<Student> set2 = new TreeSet<>((o1, o2) -> Double.compare(o1.getScore(), o2.getScore()));
set2.add(new Student("sll", 48, 88.8));
set2.add(new Student("srr", 18, 99.8));
set2.add(new Student("scb", 18, 96.8));
System.out.println(set2);
//[Student{name='sll', age=48, score=88.8}, Student{name='scb', age=18, score=96.8}, Student{name='srr', age=18, score=99.8}]

集合的并发修改异常

1.使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误。

2.由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误。

集合的并发修改异常报错:java.util.ConcurrentModificationException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
List<String> list2 = new ArrayList<>();
list2.add("srr");
list2.add("小李子");
list2.add("李爱花");
list2.add("张三");
list2.add("李玉刚");
System.out.println(list2);

//使用for循环删除带"李"的名字
/*
for (int i = 0; i < list2.size(); i++) {
String name = list2.get(i);
if(name.contains("李")){
list2.remove(name);
}
}
System.out.println(list2);
*/

//使用迭代器删除带"李"的名字,这和使用for循环的原理是一样的,但是使用迭代器删除会报错,这是因为会遇到删除不全的情况
Iterator<String> it2 = list2.iterator();
while(it2.hasNext()){
String name = it2.next();
if(name.contains("李")){
list2.remove(name);//只要使用集合的remove方法删除集合元素,就会报错java.util.ConcurrentModificationException
}
}
System.out.println(list2);

保证遍历集合同时删除数据时不出bug:

1.使用迭代器遍历集合,但用迭代器自己的删除方法删除数据即可。不要使用集合的删除方法删除数据。使用迭代器的remove方法删除数据当前遍历到的数据,每删除一个数据后,相当于在底层做了i--

1
2
3
4
5
6
7
8
Iterator<String> it2 = list2.iterator();
while(it2.hasNext()){
String name = it2.next();
if(name.contains("李")){
it2.remove();//删除迭代器当前遍历到的数据,每删除一个数据后,相当于在底层做了i--
}
}
System.out.println(list2);

2.如果能用for循环遍历时:可以倒着遍历并删除;或者从前往后遍历,但删除元素后做i--操作。

3.使用增强for循环forEach的Lambda表达式(底层是使用增强for循环实现的)遍历集合并删除数据,没有办法解决并发修改异常

可变参数

可变参数是一种特殊形参,定义在方法、构造器的形参列表里,格式是:数据类型...参数名称

可变参数可以不传数据给它;可以传一个或者同时传多个数据给它;也可以传一个数组给它。可变参数常常用来灵活的接收数据。

注意:

1.可变参数在方法内部就是一个数组

2.一个形参列表中可变参数只能有一个

3.可变参数必须放在形参列表的最后面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
public static void main(String[] args) {
test(10);//不传数据给可变形参
test(10, 100);//传一个数据
test(10, 1,2,3,4);//传多个数据
test(10, new int[]{1,2,3,4,5});//传一个数组

}

public static void test(int age, int...nums){
System.out.println(nums.length);
System.out.println(Arrays.toString(nums));
System.out.println("---------------------");
}
}

Collections

Collections是一个用来操作集合的工具类。

1
2
3
4
List<String> names = new ArrayList<>();
Collections.addAll(names, "srr", "scb", "sll", "szz");//批量添加
Collections.shuffle(names);//打乱
Collections.sort(names);//排序:[scb, sll, srr, szz]

注意:Collections只能支持对List集合进行排序。

1
2
3
4
5
6
//排序方式1:
public static <T> void sort(List<T> list) //对List集合中元素按照默认规则排序
//本方法可以直接对自定义类型的List集合排序,但自定义类型必须实现了Comparable接口,指定了比较规则才可以。

//排序方式2:
public static <T> void sort(List<T> list,Comparator<? super T> c) //对List集合中元素,按照比较器对象指定的规则进行排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//Student类使用上述set->TreeSet中定义的Student类
List<Student> list3 = new ArrayList<>();
list3.add(new Student("srr", 18, 99.3));
list3.add(new Student("scb", 16, 96.3));
list3.add(new Student("sll", 48, 92.3));

//方式1:实现Comparable接口按照年龄排序
Collections.sort(list3);
//[Student{name='scb', age=16, score=96.3}, Student{name='srr', age=18, score=99.3}, Student{name='sll', age=48, score=92.3}]

//方式2:按照比较器对象指定的规则(按照成绩)进行排序
Collections.sort(list3, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return Double.compare(o1.getScore(), o2.getScore());
}
});
//简化写法:
Collections.sort(list3, (o1, o2) -> Double.compare(o1.getScore(), o2.getScore()));
//[Student{name='sll', age=48, score=92.3}, Student{name='scb', age=16, score=96.3}, Student{name='srr', age=18, score=99.3}]

Map

Map集合称为双列集合,格式:{key1=value1 , key2=value2 , key3=value3 , ...}, 一次需要存一对数据做为一个元素。

Map集合的每个元素key=value称为一个键值对/键值对对象/一个Entry对象,Map集合也被叫做键值对集合

Map集合的所有键是不允许重复的,但值可以重复,键和值是一一对应的,每一个键只能找到自己对应的值。

注意:Map系列集合的特点都是由键决定的,值只是一个附属品,值是不做要求的。

  • HashMap(由键决定特点):无序、不重复、无索引。
  • LinkedHashMap(由键决定特点):由键决定的特点:有序、不重复、无索引。
  • TreeMap(由键决定特点):按照大小默认升序排序、不重复、无索引。
Map的常用方法

Map是双列集合的祖宗,它的功能是全部双列集合都可以继承过来使用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Map<String, Integer> map = new HashMap<>();
map.put("srr", 180);
map.put("srr", 200);//重复时,后面的值会替换前面的值
map.put("scb", 200);
map.put("sll", 100);
map.put(null, null);
System.out.println(map);//{null=null, srr=200, sll=100, scb=200}

System.out.println(map.size());//集合的大小
System.out.println(map.isEmpty());//判断集合是否为空
System.out.println(map.get("srr"));//根据键获取对应值
System.out.println(map.remove("sll"));//根据键删除元素,返回键值
System.out.println(map.containsKey("srr"));//判断是否包含某个键,分大小写严格匹配
System.out.println(map.containsValue(100));//判断是否保号某个值

Set<String> key = map.keySet();//获取所有键
System.out.println(key);//[null, srr, scb]

Collection<Integer> value = map.values();//获取所有值
System.out.println(value);//[null, 200, 200] 相同的值也会重复显示

Map<String, Integer> map1 = new HashMap<>();
map1.put("java", 1);
map1.put("python", 2);
map1.put("srr", 888);
map.putAll(map1);//将map1的元素全部放入map中,map1中的srr和map重复,map1中的srr的值会替换掉map中的
System.out.println(map);//{null=null, srr=888, python=2, scb=200, java=1}
System.out.println(map1);//{python=2, srr=888, java=1}

map.clear();//清空集合
System.out.println(map);//{}
Map的遍历方式

1.键找值:先获取Map集合全部的键,再通过遍历键来找值。

1
2
public Set<K> keySet()	//获取所有键的集合
public V get(Object key) //根据键获取其对应的值
1
2
3
4
5
6
7
8
9
Map<String, Double> mymap = new HashMap<>();
mymap.put("srr", 100.8);
mymap.put("scb", 99.9);
mymap.put("sll", 80.8);

Set<String> mykey = mymap.keySet();//1.键找值
for (String k : mykey) {
System.out.println(k + "->" + mymap.get(k));
}

2.键值对:把键值对看成一个整体Map.Entry<K, V>进行遍历。

1
2
3
4
Set<Map.Entry<String, Double>> entries = mymap.entrySet();//2.键值对
for (Map.Entry<String, Double> entry : entries) {
System.out.println(entry.getKey() + "->" + entry.getValue());
}

3.Lambda表达式

1
default void forEach(BiConsumer<? super K, ? super V> action)	//结合lambda遍历Map集合
1
2
3
4
5
6
7
8
mymap.forEach(new BiConsumer<String, Double>() {//3.Lambda表达式
@Override
public void accept(String s, Double d) {
System.out.println(s + "->" + d);
}
});
//简化写法:
mymap.forEach((k, v) -> System.out.println(k + "->" + v));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//forEach方法的源码:顶层就是使用了第2种方法把键值对看成一个整体Map.Entry<K, V>进行遍历
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch (IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
HashMap

HashMap(由键决定特点):无序、不重复、无索引。

HashMap集合的底层原理HashMapHashSet的底层原理一模一样,都是基于哈希表实现的。HashSet的底层原理就是HashMap

注意:Set系列集合的底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。

HashMap在存储时,会把键值对封装成Entry对象,然后利用键计算哈希值,跟值无关。其余存储的流程HashSet一模一样。

注意:

1.HashMap的键依赖hashCode方法和equals方法保证键的唯一。

2.如果键存储的是自定义类型的对象,可以通过重写hashCodeequals方法,这样可以保证多个对象内容一样时,HashMap集合就能认为是重复的。这一点和HashSet类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Student {
private String name;
private int age;
private double score;

public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}

@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 && Double.compare(score, student.score) == 0 && Objects.equals(name, student.name);
}

@Override
public int hashCode() {
return Objects.hash(name, age, score);
}
}
1
2
3
4
5
6
Map<Student, String> map2 = new HashMap<>();
map2.put(new Student("srr", 18, 100), "szu");
map2.put(new Student("scb", 16, 99.8), "gzu");
map2.put(new Student("scb", 16, 99.8), "gzu");
System.out.println(map2);//{Student{name='srr', age=18, score=100.0}=szu, Student{name='scb', age=16, score=99.8}=gzu}
//若不重写hashCode方法和equals方法,则会出现重复的scb:{Student{name='srr', age=18, score=100.0}=szu, Student{name='scb', age=16, score=99.8}=gzu, Student{name='scb', age=16, score=99.8}=gzu}
LinkedHashMap

LinkedHashMap(由键决定特点):由键决定的特点:有序、不重复、无索引。

LinkedHashMap集合的底层原理:基于哈希表实现,只是每个键值对元素又额外的多了一个双链表的机制记录元素顺序(保证有序)。LinkedHashSet集合的底层原理就是LinkedHashMap

LinkedHashMap在存储时,会把键值对封装成Entry对象,然后利用键计算哈希值,跟值无关。其余存储的流程LinkedHashSet一模一样。

1
2
3
4
5
Map<Student, String> map3 = new LinkedHashMap<>();//和HashMap的创建类似,区别是LinkedHashMap有序
map3.put(new Student("srr", 18, 100), "szu");
map3.put(new Student("scb", 16, 99.8), "gzu");
map3.put(new Student("scb", 16, 99.8), "gzu");
System.out.println(map3);//{Student{name='srr', age=18, score=100.0}=szu, Student{name='scb', age=16, score=99.8}=gzu}
TreeMap

TreeMap(由键决定特点):按照大小默认升序排序、不重复、无索引。

TreeMap集合的底层原理TreeMapTreeSet集合的底层原理是一样的,都是基于红黑树实现的排序。TreeSet的底层原理就是TreeMap

TreeMap集合同样也支持两种方式来指定排序规则:(否则put时会报错:java.lang.ClassCastException

1.让类实现Comparable接口,重写比较规则。

2.TreeMap集合有一个有参数构造器,支持创建Comparator比较器对象,以便用来指定比较规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.itheima.Map_;

import java.util.Objects;

public class Student implements Comparable<Student> {
private String name;
private int age;
private double score;

@Override
public int compareTo(Student o) {
return this.age - o.age;
}

public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}

public double getScore() {
return score;
}

@Override
public String toString() {
return "Student{" + "name='" + name + '\'' + ", age=" + age + ", score=" + score + '}';
}

@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 && Double.compare(score, student.score) == 0 && Objects.equals(name, student.name);
}

@Override
public int hashCode() {
return Objects.hash(name, age, score);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//方式1:实现Comparable接口
Map<Student, String> map4 = new TreeMap<>();
map4.put(new Student("srr", 18, 100), "szu");
map4.put(new Student("scb", 16, 99.8), "gzu");
map4.put(new Student("scb", 16, 99.8), "gzu");
System.out.println(map4);
//按照age比较:{Student{name='scb', age=16, score=99.8}=gzu, Student{name='srr', age=18, score=100.0}=szu}

//方式2:创建Comparator比较器对象
Map<Student, String> map5 = new TreeMap<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return Double.compare(o1.getScore(), o2.getScore());
}
});
//简化写法:
//Map<Student, String> map5 = new TreeMap<>((o1, o2) -> Double.compare(o1.getScore(), o2.getScore()));
map5.put(new Student("srr", 18, 100), "szu");
map5.put(new Student("scb", 16, 99.8), "gzu");
map5.put(new Student("scb", 16, 99.8), "gzu");
System.out.println(map5);
//按照score比较:{Student{name='scb', age=16, score=99.8}=gzu, Student{name='srr', age=18, score=100.0}=szu}

集合的嵌套

1
2
3
4
5
6
7
8
9
10
11
12
Map<String, List<String>> citymap = new HashMap<>();
List<String> cities1 = new ArrayList<>();
Collections.addAll(cities1, "南京", "苏州");
citymap.put("江苏", cities1);

List<String> cities2 = new ArrayList<>();
Collections.addAll(cities2, "广州", "深圳", "珠海");
citymap.put("广东", cities2);

citymap.forEach((k, v) -> System.out.println(k + "->" + v));
//广东->[广州, 深圳, 珠海]
//江苏->[南京, 苏州]

Stream

获取Stream流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//Student类
public class Student {
private String name;
private int age;
private double height;

@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 && Double.compare(student.height, height) == 0 && Objects.equals(name, student.name);
}

@Override
public int hashCode() {
return Objects.hash(name, age, height);
}

public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}

public double getHeight() {
return height;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 1、获取List集合的Stream流
List<String> names = new ArrayList<>();
Collections.addAll(names, "张三丰","张无忌","周芷若","赵敏","张强");
Stream<String> stream = names.stream();

// 2、获取Set集合的Stream流
Set<String> set = new HashSet<>();
Collections.addAll(set, "刘德华","张曼玉","蜘蛛精","马德","德玛西亚");
Stream<String> stream1 = set.stream();
stream1.filter(s -> s.contains("德")).forEach(s -> System.out.println(s));

// 3、获取Map集合的Stream流
Map<String, Double> map = new HashMap<>();
map.put("古力娜扎", 172.3);
map.put("迪丽热巴", 168.3);
map.put("马尔扎哈", 166.3);
map.put("卡尔扎巴", 168.3);

Set<String> keys = map.keySet();
Stream<String> ks = keys.stream();

Collection<Double> values = map.values();
Stream<Double> vs = values.stream();

Set<Map.Entry<String, Double>> entries = map.entrySet();
Stream<Map.Entry<String, Double>> kvs = entries.stream();
kvs.filter(e -> e.getKey().contains("巴"))
.forEach(e -> System.out.println(e.getKey()+ "-->" + e.getValue()));

// 4、获取数组的Stream流
String[] names2 = {"张翠山", "东方不败", "唐大山", "独孤求败"};
Stream<String> s1 = Arrays.stream(names2);
Stream<String> s2 = Stream.of(names2);
Stream流的中间方法

中间方法指的是调用完成后会返回新的Stream流,可以继续使用(支持链式编程)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
List<Double> scores = new ArrayList<>();
Collections.addAll(scores, 88.5, 100.0, 60.0, 99.0, 9.5, 99.6, 25.0);
// 需求1:找出成绩大于等于60分的数据,并升序后,再输出。
scores.stream().filter(s -> s >= 60).sorted().forEach(s -> System.out.println(s));

List<Student> students = new ArrayList<>();
Student s1 = new Student("蜘蛛精", 26, 172.5);
Student s2 = new Student("蜘蛛精", 26, 172.5);
Student s3 = new Student("紫霞", 23, 167.6);
Student s4 = new Student("白晶晶", 25, 169.0);
Student s5 = new Student("牛魔王", 35, 183.3);
Student s6 = new Student("牛夫人", 34, 168.5);
Collections.addAll(students, s1, s2, s3, s4, s5, s6);
// 需求2:找出年龄大于等于23,且年龄小于等于30岁的学生,并按照年龄降序输出.
students.stream().filter(s -> s.getAge() >= 23 && s.getAge() <= 30)
.sorted((o1, o2) -> o2.getAge() - o1.getAge())
.forEach(s -> System.out.println(s));

// 需求3:取出身高最高的前3名学生,并输出。
students.stream().sorted((o1, o2) -> Double.compare(o2.getHeight(), o1.getHeight()))
.limit(3).forEach(System.out::println);
System.out.println("----------------------------------------------------------------");

// 需求4:取出身高倒数的2名学生,并输出。 s1 s2 s3 s4 s5 s6
students.stream().sorted((o1, o2) -> Double.compare(o2.getHeight(), o1.getHeight()))
.skip(students.size() - 2).forEach(System.out::println);

// 需求5:找出身高超过168的学生叫什么名字,要求去除重复的名字,再输出。
students.stream().filter(s -> s.getHeight() > 168).map(Student::getName)
.distinct().forEach(System.out::println);

// distinct去重复,自定义类型的对象(希望内容一样就认为重复,重写hashCode,equals)
students.stream().filter(s -> s.getHeight() > 168)
.distinct().forEach(System.out::println);

Stream<String> st1 = Stream.of("张三", "李四");
Stream<String> st2 = Stream.of("张三2", "李四2", "王五");
Stream<String> allSt = Stream.concat(st1, st2);
allSt.forEach(System.out::println);
Stream流的终结方法

终结方法指的是调用完成后,不会返回新Stream了,没法继续使用流了。

收集Stream流:就是把Stream流操作后的结果转回到集合或者数组中去返回。

Stream流:方便操作集合/数组的手段。集合/数组:才是开发中的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
List<Student> students = new ArrayList<>();
Student s1 = new Student("蜘蛛精", 26, 172.5);
Student s2 = new Student("蜘蛛精", 26, 172.5);
Student s3 = new Student("紫霞", 23, 167.6);
Student s4 = new Student("白晶晶", 25, 169.0);
Student s5 = new Student("牛魔王", 35, 183.3);
Student s6 = new Student("牛夫人", 34, 168.5);
Collections.addAll(students, s1, s2, s3, s4, s5, s6);
// 需求1:请计算出身高超过168的学生有几人。
long size = students.stream().filter(s -> s.getHeight() > 168).count();
System.out.println(size);

// 需求2:请找出身高最高的学生对象,并输出。
Student s = students.stream().max((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight())).get();
System.out.println(s);

// 需求3:请找出身高最矮的学生对象,并输出。
Student ss = students.stream().min((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight())).get();
System.out.println(ss);

// 需求4:请找出身高超过170的学生对象,并放到一个新集合中去返回。
// 流只能收集一次。
List<Student> students1 = students.stream().filter(a -> a.getHeight() > 170).collect(Collectors.toList());
System.out.println(students1);

Set<Student> students2 = students.stream().filter(a -> a.getHeight() > 170).collect(Collectors.toSet());
System.out.println(students2);
/*
注意:上面这两段代码不能简化为:
Stream<Student> stream = students.stream().filter(a -> a.getHeight() > 170);
List<Student> students1 = stream.collect(Collectors.toList());
System.out.println(students1);

Set<Student> students2 = stream.collect(Collectors.toSet());
System.out.println(students2);
报错:java.lang.IllegalStateException: stream has already been operated upon or closed
流只能收集一次,所以这次使用了下次就不能再使用了。
*/

// 需求5:请找出身高超过170的学生对象,并把学生对象的名字和身高,存入到一个Map集合返回。
Map<String, Double> map =
students.stream().filter(a -> a.getHeight() > 170)
.distinct().collect(Collectors.toMap(a -> a.getName(), a -> a.getHeight()));
//toMap(a -> a.getName(), a -> a.getHeight()))这里是声明键key和值value
System.out.println(map);
/*
注意:这里没有distinct()的话会报错:java.lang.IllegalStateException: Duplicate key 蜘蛛精 (attempted merging values 172.5 and 172.5)
因为toMap方法不能自动去掉重复的键,所以需要加distinct()来去掉重复的键。
*/

// Object[] arr = students.stream().filter(a -> a.getHeight() > 170).toArray();
Student[] arr = students.stream().filter(a -> a.getHeight() > 170).toArray(len -> new Student[len]);
System.out.println(Arrays.toString(arr));

Java集合
http://surourou8.github.io/2024/09/25/Java集合/
作者
Su Rourou
发布于
2024年9月25日
许可协议