集合进阶
在这之前,我们已经学过了ArrayList集合,但这个集合只是Java中集合的冰山一角而已,在正式学习集合之前我们先来了解一下集合的体系结构。
集合总的可以分为两大类:单列集合和双列集合。简单的解释一下,单列集合就是一次只能添加一个数据,二双列集合一次能添加两个数据。
一.单列集合
我们先来学习一下单列集合:
要想要学好单列集合,就应当明确List集合与Set机和各自的特点.
对于List系列集合,它的特点是:添加的元素是有序的(指的是存和取的顺序是一样的),可重复,有索引。
而对于Set系列集合,他的特点是::添加的元素是无序的(指的是存和取的顺序是不一样的),不重复(在以后可利用这个特性进行元素的去重),无索引。
Collection集合
Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承和使用的。另外,因为Collection是一个接口,因此我们不能直接创建他的对象,只能创建他的其他实现类的对象,例如:ArrayList
Collection<String> coll=new ArrayList<>();
//ArrayList<String> coll=new ArrayList<>();
//常用方法:
public boolean add(E e) //添加元素
public void clear() //清空集合
public boolean remove(E e) //删除元素
public boolean contains(object obj) //判断当前集合是否包含给定对象
public boolean isEmpty() //判断当前集合是否为空
public int size() //返回集合的长度
接下来我们将学习Collection集合的通用遍历方式
1.迭代器遍历
迭代器在Java中的类时贴然投入,迭代器是集合专用的遍历方式。迭代器其实可以简单的看成C语言中的指针,好比一个箭头,默认指向0索引处。
例:
public class 集合迭代器遍历 {
public static void main(String[] args){
//创建一个collocation集合
Collection<String> coll=new ArrayList<>();
coll.add("aaskciomm");
coll.add("asd");
//获取迭代器
//若发生了越界则会报错:NoSuchElementException
//注:迭代器遍历完毕后不会复位,因此若我们想要二次遍历,则需要重新创建一个迭代器;迭代器遍历时,不能通过集合的方法进行添加或删除,需要使用迭代器中的方法删除
Iterator<String> it= coll.iterator();
while(it.hasNext()){
String st=it.next();
System.out.println(st);
}
}
}
2.增强for遍历
增强for的底层就是迭代器,是用于简化迭代器的代码书写的,其内部原理就是一个It儿啊投入迭代器。所有的单列集合和数组才能使用增强for进行遍历
例:
public class 增强for遍历集合 {
public static void main(String[] args){
//创建集合并添加元素
Collection<String> coll=new ArrayList<>();
coll.add("asd");
coll.add("qwe");
//快速生成方式:集合名+for 回车
//利用增强for来遍历集合
//s为第三方变量,用于获取遍历到的coll中的元素
for (String s:coll) {
System.out.println(s);
}
}
}
3.Lambda表达式遍历
在Java中,你可以使用Lambda表达式来遍历集合类,特别是在Java 8及以后的版本中,引入了Lambda表达式和Stream API,可以更为简洁和灵活地进行集合的遍历操作。
以下是使用Lambda表达式遍历集合的一些示例代码:
- 使用foreach循环:
List<String> list=Arrays.asList("A","B", "O");
list.forEach(item -> {
System.out.println(item);
});
上述代码使用Lambda表达式对列表中的每个元素进行遍历,并打印出每个元素的值。
- 使用Stream的forEach方法:
List<String> list = Arrays.asList("Apple", "Banana", "Orange");
list.stream().forEach(item -> {
System.out.println(item);
});
这里使用了Stream API中的forEach方法,先将列表转换为流,然后通过Lambda表达式对每个元素进行操作。
除了遍历集合,Lambda表达式还可以和其他Stream API方法结合使用,如filter、map、reduce等,以完成更复杂的集合操作。
需要注意的是,Lambda表达式可以根据需要自定义参数和返回值的类型,以满足不同的需求。
List集合
List集合的特点是:添加的元素是有序的(指的是存和取的顺序是一样的),可重复,有索引(可以通过索引来操作元素)
正如我们所知,List集合继承于Collection集合,因此Collection集合中的方法List也都继承了。又因为List集合有索引,因此又多了很多索引操作的方法。接下来,我们会着重的学习List集合中与Collection不同的方法。
这些方法均与Collection中的方法有不同的地方,更着重于对索引的使用,且返回值也略有不同
例:
public class List集合 {
public static void main(String[] args){
//创建集合
List<String> list=new ArrayList<>();
//添加元素
list.add("asd");
list.add("zxc");
list.add("qwe");
//插入元素
list.add(1,"fgh");
System.out.println(list);
//删除元素
String st=list.remove(1);
System.out.println(list+" 删除的元素为:"+st);
//修改指定索引的元素,并返回被修改的元素
st=list.set(1,"fgh");
System.out.println(list+" 修改的元素为:"+st);
//返回指定索引的方法
System.out.println(list.get(1));
//
//列表迭代器,不仅可实现遍历,也可以实现插入
ListIterator<String> it= list.listIterator();
while(it.hasNext()){
String St= it.next();
if(St.equals("qwe")){
it.add("rty");
}
}
System.out.println(list);
}
}
LinkedList集合
LinkedList集合的底层数据结构是双链表,查询慢,增删快
Set集合
Set系列集合的特点是:无序,不重复,无索引(因此无法使用普通for来进行遍历)。Set系列集合中又有三个不同的实现类,其中就包括:HashSet,LinkedHashSet,TreeSet。接下来,我们会依次来介绍这几个实现类的特点。同时Set集合继承于Collection集合,因此Collection集合中的方法Set也都继承了。
HashSet集合
特点:无序,不重复,无索引.
在HashSet中没有什么特别的方法,因此我们主要学习一下HashSet的底层原理.
HashSet集合的底层采用的是哈希表来存储数据。不同版本的JDK,哈希表的组成也不尽相同。在JDK8之前采用的是数组+链表,而在JDK8之后,采用的是数组+链表+红黑树的结构。另外,哈希表的精华就在于哈希值的计算。
哈希值是根据hashcode方法计算出来的int类型的整数,该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算,一般情况下,我们都会重写hashcode方法,利用对象内部的属性值计算哈希值。如果没有重写hashcode方法,不同对象计算出的哈希值是不同的,但如果已经重写了hashcode方法,不同对象只要属性值相同,那么计算出的哈希值就是一样的,另外在小部分情况下,不同属性或者不同的地址值计算出来的哈希值也有可能一样,这种情况被称为哈希碰撞,或者哈希冲突。
另外,我们还需要注意的是:在JDK8以后,当链表长度超过8,而且数组长度大于等于64时,会自动转换为红黑树。如果集合中存储的是自定义对象,则必须重写hashcode和equals方法
LinkedHashSet集合
特点:有序,不重复,无索引
LinkedHashSet集合的辈分比较低,它的父类就是HashSet,所以说我们在学习他的过程中几乎没有什么新的方法,主要学习他的底层原理。
上面这张图,就很好的解释了为什么LinkedHashSet集合为有序的
TreeSet集合
特点:不重复,无索引,可排序(按照元素的默认规则由小到大来排序)TreeSet集合的底层数据结构是红黑树。
排序例:
import org.jetbrains.annotations.NotNull;
import java.util.*;
public class TreeSet排序 {
public static void main(String[] args){
Student1 st1=new Student1("zhangsan" ,16);
Student1 st2=new Student1("lisi" ,17);
Student1 st3=new Student1("wangwu" ,18);
Student1 st4=new Student1("zaholiu" ,19);
//对集合实现排序
//法1.Student实现Comparable接口,重写里面的抽象方法,再指定比较规则
TreeSet<Student1> ts=new TreeSet<>();
ts.add(st1);
ts.add(st2);
ts.add(st3);
ts.add(st4);
//System.out.println(ts);
//法2.比较器排序
//1.创建集合
TreeSet<String> TS=new TreeSet<>(new Comparator<String>() {
//o1:表示当前要添加的元素
//o2:表示已经在红黑树存在的元素
//返回规则与法1一样
@Override
public int compare(String o1, String o2) {
int i=o1.length()-o2.length();
//如果一样长则按照默认的排序规则排序
i= i==0?o1.compareTo(o2):i;
return i;
}
});
TS.add("asd");
TS.add("qwe");
TS.add("as");
TS.add("zxc");
System.out.println(TS);
}
}
class Student1 implements Comparable<Student1> {
private String name;
private int age;
public Student1(){}
public Student1(String name,int age){
this.age=age;
this.name=name;
}
/**
* 获取
* @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 "Student1{name = " + name + ", age = " + age + "}";
}
@Override
public int compareTo(@NotNull Student1 o) {
//排序规则
//只看年龄,按照年龄的升序进行排序
//返回值:
//负数:表示要添加的元素是小的,存左边
//正数:表示要添加的元素是大的,存右边
//0:表示要添加的元素已经存在,舍弃
//this表示当前要添加的元素,o表示已经在红黑树中的元素
return this.age-o.age;
}
}
二.泛型深入
泛型是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
泛型的格式很简单:<数据类型> 。但我们需要注意的是,泛型只能存储引用数据类型,如果想要存储基本数据类型,则需要使用其对应的包装类。
泛型的好处在于可以统一数据类型,把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常。
另外,值得一提的是,Java中的泛型是伪泛型。说到这里,就不得不提起在JDK5之前时集合是如何存储数据的了:在JDK5之前我们不会给集合指定数据类型,默认所有的类型数据都是Object类型,此时可以往集合中添加任意的数据类型,但是这样也会带来弊端:无法使用子类中特有的行为,且如果想要通过强转来实现子类中的特有行为,那么就可能会导致类型转换异常(转换类型不匹配)问题。因此在这前提下推出了泛型,可以在添加数据的时候就把类型进行统一,而且在我们获取数据的时候也省略了强转,非常的方便。而之所以被称之为伪泛型,是因为这个泛型相当于只是起到检查类型是否匹配的作用,而当数据真正存储存储到集合当中时是以Object类型的方式来处理的,但当获取集合里面的数据时,集合的底层又会把这些数据按照泛型的类型进行一个强转。
1.泛型类
当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类
格式:
public class 类名<类型>{
}
//-----------
//E可以理解为变量,但是不是用来记录数据的,而是用来记录数据的类型,可以写成:T,E,K,V等
public class ArrayList <E>{
}
2.泛型方法
当方法中的形参类型不确定时,可以使用类名后面定义的泛型
public class ArrayList <E>{
public <E> boolean add(E e){
obj[size]=e;
size++
return true;
}
}
当方法中的形参类型不确定时,可以通过使用类名后面定义的泛型(所有方法都能用),或者在方法申明上定义自己的泛型(只有本方法能用)
3.泛型接口
格式:
修饰符 interface 接口名<类型>{
}
//例:
public interface List<E>{
}
泛型接口的两种使用方式:1.实现类给出具体的类型 2.实现类延续泛型,创建实现类对象时再确定类型
4.泛型的继承和通配符
泛型不具备继承性(即泛型里面写的是什么类型,那么只能传递什么类型的数据包括不能直接传递其子类和父类),但是数据具备继承性(即当调用add方法的时候是向下兼容的)。
但是如果想要能够传递某个继承体系中的若干种类(某对象的子类或者是父类),这时候就需要使用泛型的通配符了:
“ ?” 表示不确定的类型,但是与之前不同的是,它可以进行类型的限定。
1."? extends E ":表示可以传递E或者E所有的子类类型
2.“? super E”:表示可以传递E或者E所有的父类类型
注:此时,E必须为具体类型
例:
public class test {
public static void main(String[] args){
ArrayList<PersianCat> list1=new ArrayList<>();
ArrayList<LiHuaCat> list2=new ArrayList<>();
list2.add(new LiHuaCat("小花",1));
show(list2);
}
public static void show(ArrayList<? extends Cat> list){
Iterator<? extends Cat> l=list.iterator();
while(l.hasNext()){
l.next().eat();
}
}
}
class PersianCat extends Cat{}
class LiHuaCat extends Cat{}
class Cat{}
应用场景:
1.如果我们在定义类,方法,接口的时候,如果类型不确定,就可一定义泛型类,泛型方法,泛型接口
2.如果类型不确定,但能知道以后只能传递某个继承体系中的,就可以使用泛型的通配符
三.双列集合
在正式书写双列集合之前,我们先来认识一下双列集合的特点。
特点:双列集合一次需要村一对数据,分别为键和值,并且键不能重复,而值可以重复。键和值是一一对应的,每一个键只能找到自己对应的值。“键+值"这个整体我们称之为"键值对"或者"键值对对象”,在Java中叫做"Entry对象"。
双列集合的体系大致为:
Map集合
Map集合是双列集合的顶层接口,它的功能是全部双列集合都可以继承使用的,常见的方法有:
接下来我们用实例来一次展示一下上面的方法:
public class Map集合 {
public static void main(String[] args){
//创建Map集合的对象
Map<String,String> m=new HashMap<>();
//添加元素
String value=m.put("郭靖" ,"黄蓉");
//若键不存在,则将键值对添加进集合,返回null
//System.out.println(value);
m.put("温迪" ,"钟离");
m.put("小草神" ,"散兵");
//键若存在则发生覆盖操作,且返回被覆盖的值
System.out.println(m.put("温迪","摩拉克斯"));
//删除元素
//System.out.println(m.remove("黄蓉"));
//清空集合
//m.clear();
//判断是否包含
boolean keyResult=m.containsKey("郭靖");
System.out.println(keyResult);
boolean valueResult=m.containsValue("散兵");
System.out.println(valueResult);
//打印集合
System.out.println(m);
}
}
在了解了Map集合中的部分方法后,我们将学习Map集合的遍历方式。
Map的遍历方式主要有三种:1.键找值 2.键值对 3.Lambda表达式。接下来我们用一段程序来展示这三种遍历方式
public class Map集合的遍历 {
public static void main(String[] args){
//创建集合
Map<String,String> m=new HashMap<>();
//添加元素
String value=m.put("郭靖" ,"黄蓉");
//若键不存在,则将键值对添加进集合,返回null
m.put("温迪","摩拉克斯");
m.put("小草神" ,"散兵");
//键若存在则发生覆盖操作,且返回被覆盖的值
//1.通过键找值,获取所有的键,把这些键放入一个单列集合中
Set<String> s=m.keySet();
Iterator<String> it=s.iterator();
for (String s1 : s) {
System.out.println(s1+" = "+m.get(s1));
}
while(it.hasNext()){
String s1=it.next();
System.out.println(s1+" = "+m.get(s1));
}
s.forEach(s2-> {
System.out.println(s2+" "+m.get(s2));
}
);
//2.通过键值对对象进行遍历
//通过一个方法获取所有的键值对对象,返回一个Set集合
Set<Map.Entry<String,String>> entryies=m.entrySet();
Iterator<Map.Entry<String,String>> it1=entryies.iterator();
while(it1.hasNext()){
System.out.println(it1.next());
}
//遍历entryies这个集合,去得到里面的每一个键值对对象
for (Map.Entry<String, String> entry : entryies) {
String key=entry.getKey();
String Value=entry.getValue();
System.out.println(key+" "+Value);
}
//Lambda表达式遍历
m.forEach(new BiConsumer<String, String>() {
@Override
public void accept(String s, String s2) {
System.out.println(s+" "+s2);
}
}
);
}
}
HashMap集合
HashMap是Map里面的一个实现类,没有额外需要学习的特有方法,直接使用Map里面的方法即可。它的特点都是由键决定的:无序,不重复,无索引。另外,HashMap跟HashSet的底层原理是一模一样的,都是基于哈希表结构(数组+链表+红黑树)来实现的,依赖hashCode方法和equals方法保证键的唯一。
如果键存储的是自定义对象,需要重写hashCode和equals方法;如果如果值存储的是自定义对象,则不需要重写hashCode和equals方法
举个例子:
import java.util.HashMap;
public class HashMapCustomKeyExample {
public static void main(String[] args) {
// 创建一个新的HashMap
HashMap<Person, Integer> map = new HashMap<>();
// 创建自定义对象作为键
Person person1 = new Person("John", 25);
Person person2 = new Person("Alice", 30);
Person person3 = new Person("Bob", 35);
// 向map中添加键值对
map.put(person1, 1);
map.put(person2, 2);
map.put(person3, 3);
// 根据键检索值
int retrievedValue = map.get(person2);
System.out.println(retrievedValue); // 输出:2
// 迭代键值对
for (Person key : map.keySet()) {
int value = map.get(key);
System.out.println("Key: " + key.getName() + ", Value: " + value);
}
}
}
class Person{
private String name;
private int age;
public Person() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public Person(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 "Person{name = " + name + ", age = " + age + "}";
}
}
LinkedHasMap集合
特点(由键决定):有序,不重复,无索引
LinkedHashMap集合的辈分比较低,它的父类是HashMap,所以说我们在学习他的过程中几乎没有什么新的方法,主要学习他的底层原理(与LinkedHashSet基本相同,详见LinkedHashSet)。
TreeMap集合
特点(由键决定):可排序,不重复,无索引
TreeMap与TreeSet底层与阿里一样,都是红黑树结构。
代码书写两种排序规则:
1.实现Comparable接口,指定比较规则
2.创建集合时传递Comparator比较器对象,指定比较规则
注:如果代码中既有第一种也有第二种,则以第二种为准。
四.可变参数
可变参数(varargs)是Java中的一种特殊语法,允许方法接受可变数量的参数。通过使用省略号(…)来声明可变参数。可变参数的底层原理就是一个数组,只是不需要我们创建,Java会帮我们创建。
以下是一个示例,展示如何在Java中使用可变参数:
public class VarargsExample {
public static void main(String[] args) {
//可变参数:方法中形参的个数是可以发生变化的
//格式:属性类型...名字
//1.在方法的形参当中最多写一个可变参数
//2.在方法当中,如果还有其他的形参,那么可变参数要写在最后
printNames("John", "Alice", "Bob"); // 输出:John Alice Bob
printNames("Mary", "Tom"); // 输出:Mary Tom
printNames(); // 输出:
}
public static void printNames(String... names) {
for (String name : names) {
System.out.print(name + " ");
}
System.out.println();
}
}
在上面的示例中,printNames()
方法使用了可变参数String... names
,这意味着它可以接受任意数量的字符串参数。在main()
方法中,我们可以传递任意数量的参数给printNames()
方法,并且它会打印出所有传递的参数。
需要注意的是,可变参数必须是方法的最后一个参数。如果方法有多个参数,可变参数必须放在最后。例如,以下是一个示例:
public static void printInfo(String info, int... numbers) {
// ...
}
在上面的示例中,printInfo()
方法有两个参数,第一个是普通的String
类型参数info
,第二个是可变参数int... numbers
。这样的方法定义是合法的。
五.集合工具类Collections
Collections与Collection完全不同,Collections是位于"Java.util.Collections下的集合的工具类,观察其源码,你会发现,并且类中的方法几乎都是静态方法,因此我们不能直接创建Collections类的对象,但是可以直接通过类名来调用其中的方法。
六.不可变集合
不可变集合,顾名思义就是一旦创建成功后,就不能对集合里面的内容进行修改。目的就是为了不让别人修改集合中的内容。
七.Stream流
Stream流的作用在于结合了Lambda表达式,简化了集合,数组的操作。
Stream流的使用步骤分为三部:
1.先得到一条Stream流,把数据放上去
2.使用中间方法对流水线上的额数据进行操作
3.使用终结方法对流水线上的数据进行操作
接下来我们使用一段代码,依次来展示这三个步骤:
public class stream流 {
public static void main(String[] args) {
/*
单列集合 default Stream<E> stream() Collection中的默认方法
双列集合 无 无法直接使用stream流
数组 public static <T> Stream<T> stream(T[] array) Arrays工具类中的静态方法
一堆零散数据 public static <T> Stream<T> stream(T...values) Stream接口中的静态方法
*/
//1.单列集合中的Stream流
ArrayList<String> arrayList = new ArrayList<>();
Collections.addAll(arrayList, "a", "b", "c", "d");
arrayList.stream().forEach(s -> System.out.println(s));
//2.双列集合中的Stream流
HashMap<String, Integer> map = new HashMap<>();
map.put("aaa", 111);
map.put("bbb", 222);
map.put("ccc", 333);
//第一种获取Stream流
map.keySet().stream().forEach(s -> System.out.println(s));
//第二种获取Stream流
map.entrySet().stream().forEach(s -> System.out.println(s));
//3.数组中的Stream流
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
//获取Stream流
Arrays.stream(arr).forEach(s -> System.out.println(s));
//4.一堆零散数据获取Stream流
Stream.of("a", 1, 2, 3, 4, 'd', "e").forEach(s -> System.out.println(s));
//Stream流的中间方法
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "张无忌", "张强", "周芷若", "张三丰", "王五", "张亮");
ArrayList<String> list1 = new ArrayList<>();
Collections.addAll(list1, "asd-15", "qwe-20");
//filter 过滤 把张开头的留下
//1.中间方法,返回新的Stream流,原来的Stream流只能使用一次,建议使用链式编程
//2.秀爱Stream流中的数据,不会影响原来的集合或者数组中的数据
list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
//limit 获取前几个元素
list.stream().limit(2).forEach(s -> System.out.println(s));
//skip 跳过前几个元素
list.stream().skip(2).forEach(s -> System.out.println(s));
//distinct 元素去重,依赖hashcode方法和equals方法(也就是说,如果类型为自定义对象,则需要自己手动重写这两个方法)
System.out.println(list);
list.stream().distinct().forEach(s -> System.out.println(s));
//concat 合并a和b两个流为一个流,是位于Stream中的静态方法
Stream.concat(list.stream(),list1.stream()).forEach(s -> System.out.println(s));
//map 装换流中的数据类型
//apply的形参s:依次表示流里面的每一个数据
//返回值:表示转换之后的数据
//当map方法执行完毕后,流上的数据就变成了整数,所以在forEach当中,s一次表示流里面的每一个数据,这个数据就是整数了
list1.stream().map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
String[] arr=s.split("-");
String Int=arr[1];
int a=Integer.parseInt(Int);
return a;
}
}).forEach(s->System.out.println(s));
list1.stream().map(s -> Integer.parseInt(s.split("-")[1])).forEach(s->System.out.println(s));
//Stream流的终结方法
//遍历
list.stream().forEach(s -> System.out.println(s));
//统计
System.out.println(list.stream().count());
//toArray() 收集流中的数据,放到数组中
String[] array = list.stream().toArray(value -> new String[value]);
System.out.println(Arrays.toString(array));
//收集方法
//collect(Collector collector) 收集流中的数据,放到集合中(List,Map,Set)
//匿名内部类形式
Map<String,Integer> map1=list1.stream().collect(Collectors.toMap(new Function<String, String>() {
@Override
public String apply(String s) {
String a=s.split("-")[0];
return a;
}
}, new Function<String, Integer>() {
@Override
public Integer apply(String s) {
Integer i=Integer.parseInt(s.split("-")[1]);
return i;
}
}));
System.out.println(map1);
//Lambda表达式形式
Map<String,Integer> map2=list1.stream().collect(Collectors.toMap(
s -> s.split("-")[0],
s-> Integer.parseInt(s.split("-")[1])
));
System.out.println(map2);
}
}
Stream流主要有以下特点:
- 流是数据元素的序列,它可以是数组、集合、I/O通道等。
- 流操作可以是中间操作或者终端操作。中间操作返回一个新的流,可以进行连续的操作,而终端操作返回一个结果或者一个副作用。
- 流操作可以是串行的或者并行的。串行流在单个线程上执行,而并行流在多个线程上同时执行,提高了处理大量数据的效率。
- 流操作可以是无状态的或者有状态的。无状态操作不依赖于流中的其他元素,而有状态操作可能依赖于流中的其他元素。
使用Stream流可以进行各种集合数据的操作,如过滤、映射、排序、归约等。它可以大大简化集合数据的处理代码,并且提供了更好的性能。通过使用Stream流,我们可以更加简洁、清晰地表达集合数据的处理逻辑,提高代码的可读性和可维护性。同时,Stream流还提供了并行处理的能力,可以更好地利用多核处理器的性能。
八.方法引用
方法引用简言之就是把已经有的方法拿过来用,当做函数式接口中抽象方法的方法体。
当然,方法的引用必须要遵循一定的规则,不能够随意的去引用:
1.需要有函数式接口
2.被引用的方法必须已经存在
3.被引用的方法的形参和返回值需要跟抽象方法的形参和返回值保持一致
4.被引用的方法满足当前的需求
1.引用静态方法
格式: 类名::静态方法
例:Integer::parseInt
2.引用成员方法
引用成员方法可以分为三类: 1.引用其他类的成员方法
2.引用本类的成员方法
3.引用父类的成员方法
//引用静态方法: 类名::方法名
public class 成员方法的引用 {
public static void main(String[] args){
//创建集合
ArrayList<String> list=new ArrayList<>();
//添加数据
Collections.addAll(list,"张无忌","张三丰","周芷若","张强","赵敏");
//过滤数据
list.stream().filter(s -> s.startsWith("张")).filter(s->s.length()==3).forEach(s->System.out.println(s));
/*方法引用(引用成员方法)
格式
其他类: 其他类对象 :: 方法名
本类(非静态): this :: 方法名 (静态方法中是没有this 关键字的,因此要想使用方法引用则只能通过创建本类的对象来实现引用)
父类(非静态): super:: 方法名
*/
list.stream().filter(new 过滤方法()::StringJudge).forEach(s -> System.out.println(s));
}
3.引用构造方法
引用构造方法的目的就是为了创建该类的对象
public class 构造方法的引用 {
public static void main(String[] args){
/*
方法引用的规则
1.需要有函数式接口
2.被引用的方法必须已经存在
3.被引用的方法的形参和返回值需要跟抽象方法的形参和返回值保持一致
4.被引用的方法满足当前的需求
*/
//创建集合
ArrayList<String> list=new ArrayList<>();
//添加对象
Collections.addAll(list,"张无忌,15","周芷若,14","赵敏,13","张三丰,100","张翠山,40","张亮,20");
List<student> List=list.stream().map(s -> {
String name=s.split(",")[0];
int age=Integer.parseInt(s.split(",")[1]);
return new student(name,age);
}).collect(Collectors.toList());
System.out.println(List);
//格式: 类名::new
List<student> List1=list.stream().map(student::new).collect(Collectors.toList());
System.out.println(List1);
}
4.其他调用方式
1.使用类名引用成员方法
public class 类名引用成员方法 {
public static void main(String[] args){
/*方法引用的规则
1.需要有函数式接口
2.被引用方法必须已经存在
3.被引用方法的形参,需要跟抽象方法的第二个形参到最后一个形参保持一致
4.被引用方法的功能需要满足当前需求
抽象方法形参的详解
第一个参数:表示被引用方法的调用者,决定了可以引用那种类中的方法
在Stream流当中,第一个参数易班表示流里面的每一个数据
假设流里面的数据是字符串,那么使用这种方式进行方法引用,只能引用String这个类中的方法
第二个参数到最后一个参数:跟引用方法的形参保持一致,如果没有第二个参数,说明被引用的方法需要是午餐的成员方法
局限性:
不能引用所有类中的成员方法
是跟抽象方法的第一个参数有关,这个参数是什么类型,那么就只能引用这个类中的方法
*/
//创建集合
ArrayList<String> list=new ArrayList<>();
//添加元素
Collections.addAll(list,"aaa","bbb","ccc","ddd");
ArrayList<Integer> list1=new ArrayList<>();
Collections.addAll(list1,1,2,3,4,5);
//匿名内部类的形式
list.stream().map(new Function<String, String>() {
@Override
public String apply(String s) {
return s.toUpperCase();
}
}).forEach(s -> System.out.println(s));
//类名引用成员方法
//格式: 类名::成员方法
list.stream().map(String::toUpperCase).forEach(s->System.out.println(s));
list1.stream().map(Integer::bitCount).forEach(s->System.out.println(s));
}
}
2.引用数组的构造方法
public class 引用数组的构造方法 {
public static void main(String[] args){
//格式: 数据类型[]::new
//创建集合
ArrayList<Integer> list=new ArrayList<>();
Collections.addAll(list,1,2,3,4,5,6);
//匿名内部类的形式
// Integer[] arr=list.stream().toArray(new IntFunction<Integer[]>() {
// @Override
// public Integer[] apply(int value) {
// return new Integer[0];
// }
// });
//方法引用
Integer[] arr1=list.stream().toArray(Integer[]::new);
System.out.println(Arrays.toString(arr1));
}
}