Java - lambda表达式 ,函数式接口,stream流,枚举Enum
第十四章、Lambda、Stream、Enum
14.1、接口定义方式
在java8以后,接口中可以添加使用default或者static修饰的方法,
在这里我们只讨论default方法,default修饰方法只能在接口中使
用,在接口种被default标记的方法为普通方法,可以直接写方法
体。
jdk7 接口的特点:
1、接口中的方法都是抽象方法,接口中的常量都是静态常量
2、接口不能有构造方法,不能实例化
3、实现类必须实现接口中的抽象方法,如果不实现,那么该类必须定义成抽象类
4、shishii嫌累可以实现多个接口
static 方法特点:
1、用 static 修饰的方法可以有方法体
2、实现该接口的类不能重写用 static 修饰的方法
3、可以使用接口名直接调用被 static 修饰的方法
default 方法特点:
1、用 default 修饰的方法可以有方法体,实现该接口的类可以不重写用 default 修饰的方法,实现类会继承接口中的 default 方法;如果重写了 default 修饰的方法,那么调用的就是实现类重写的方法。
2、如果一个类同时实现接口 A 和 B ,接口 A 和 B 中有相同的 default 方法,这时,该类必须重写接口中的 default 方法
注意:为什么要重写呢?是因为,类在继承接口中的 default 方法时,不知道应该继承哪一个接口中的 default 方法
3、如果子类继承父类,父类中有 b 方法,该子类同时实现的接口中也有 b 方法(被default 修饰),那么子类会继承父类的 b 方法而不是继承接口中的 b 方法
案例代码:
public interface A {
public void show();
default void print(){
System.out.println("接口A中 default 修饰的 print()...");
}
static void th(){
System.out.println("接口th()...");
}
default void info(){
System.out.println("接口A中 default 修饰的 Ainfo()...");
}
default void something(){
System.out.println("接口A中 default修饰的something()...");
}
}
public interface B {
default void info(){
System.out.println("接口B中 default 修饰的 info()...");
}
}
public class Father {
public void something(){
System.out.println("父类something()...");
}
}
public class AImpl extends Father implements A,B{
//接口中的 public 正常方法,必须重写
@Override
public void show() {
System.out.println("重写后的show()...");
}
@Override
public void info() { //接口A,B中都有 default 修饰的同名 info()方法,必须重写
System.out.println("重写info()...");
}
@Override
public void something() {
super.something();
}
}
public class Test01 {
public static void main(String[] args) {
AImpl a = new AImpl();
a.show(); //重写后的show()...
//在 AImpl 中接口的 print 并未重写,但 AImpl 可以之间调用
a.print(); //接口A中 default 修饰的 print()...
a.info(); //重写info()...
a.something(); //父类something()...
}
}
14.2、Lambda表达式(函数式接口)
1)函数式编程思想概述
在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿
什么东西做什么事情 ”。相对而言,面向对象过分强调“ 必须通过对象的形式来做事情 ”,而函数式思想则尽量忽略面向对象的复杂语法—— 强调做什么,而不是以什么形式做。
做什么,而不是怎么做
我们真的希望创建一个匿名内部类对象吗?不。我们只是为了做这
件事情而不得不创建一个对象。我们真正希望做的事情是:将 run 方法体内的代码传递给 Thread 类知晓。
传递一段代码——这才是我们真正的目的。而创建对象只是受限于
面向对象语法而不得不采取的一种手段方式。
那,有没有更加简单的办法?如果我们将关注点从“怎么做”回归到
“做什么”的本质上,就会发现只要能够更好地达到目的,过程与形式其实并不重要。
2)Lambda 的优化
当需要启动一个线程去完成任务时,通常会通过 java.lang.Runnable 接口来定义任务内容,并使用 java.lang.Thread 类来启动该线程。
传统写法,代码如下:
public class Demo03Thread {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程任务执行!");
}
}).start();
}
}
本着“一切皆对象”的思想,这种做法是无可厚非的:首先创建一个Runnable 接口的匿名内部类对象来指定任务内容,再将其交给一个线程来启动。
代码分析:
对于 Runnable 的匿名内部类用法,可以分析出几点内容:
- Thread 类需要 Runnable 接口作为参数,其中的抽象 run 方法
是用来指定线程任务内容的核心; - 为了指定 run 的方法体,不得不需要 Runnable 接口的实现类;
为了省去定义一个 RunnableImpl 实现类的麻烦,不得不使用匿
名内部类; - 必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返
回值不得不再写一遍,且不能写错; - 而实际上,似乎只有 方法体才是关键 所在。
类似案例:
比较器:Comparator
public class Test {
public static void main(String[] args) {
ArrayList<Person> list = new ArrayList<>();
Collections.addAll(list,
new Person("柳岩",26),
new Person("高圆圆",34),
new Person("杨幂",37),
new Person("刘亦菲",23),
new Person("李聪聪",17));
//排序
/*Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.age - o2.age;
}
});*/
/*Collections.sort(list,(o1,o2)->{
return o1.age - o2.age;
});*/
Collections.sort(list,(o1,o2)->o1.age-o2.age);
System.out.println(list);
}
}
public class Person{
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
不再有“不得不创建接口对象”的束缚,不再有“抽象方法覆盖重写”的负担,就是这么简单!
总结:
1.函数式编程思想概念:
函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做
2.lambda表达式写法技巧:
只需要按照 匿名内部类 的形式写出代码,然后保留抽象方法的()和{},在()和{}之间添加一个->
3)Lambda 的格式 = 重点
标准格式:
Lambda 省去面向对象的条条框框,格式由3个部分组成:
- 一些参数
- 一个箭头
- 一段代码
lambda 表达式的标准格式为:
(参数类型 参数名称) -> {代码语句}
格式说明:
- 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔
- -> 是新引入的语法格式,代表指向动作。本质是方法参数的传递;注意:中间不能写空格,要连写
- 大括号内的语法与传统方法体要求基本一致
匿名内部类与 lambda 对比:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程任务执行!");
}
}).start();
仔细分析该代码中, Runnable 接口只有一个 run 方法的定义:
- public abstract void run();
即制定了一种做事情的方案(其实就是一个方法):
- 无参数:不需要任何条件即可执行该方案。
- 无返回值:该方案不产生任何结果。
- 代码块(方法体):该方案的具体执行步骤。
同样的语气体现在 Lambda 语法中,要更加简单:
() -> System.out.println("多线程任务执行!");
- 前面的一对小括号即 run 方法的参数(无),代表不需要任何条件;
- 中间的一个箭头表示将前面的参数传递给后面的代码;
- 后面的输出语句即业务逻辑代码。
4)参数和返回值:
语法格式:
(参数列表)->{...return xxx;}
在后面的案例中,使用3种书写方式做对比:
- 匿名内部类 对象的方式
- lambda 表达式的标准方式
- lambda 表达式的简化方式
案例:
List 集合存储多个 Person 对象,完成对 List 集合的排序,按照年龄升序排序。
public class Test {
public static void main(String[] args) {
ArrayList<Person> list = new ArrayList<>();
Collections.addAll(list,
new Person("柳岩",26),
new Person("高圆圆",34),
new Person("杨幂",37),
new Person("刘亦菲",23),
new Person("李聪聪",17));
//排序
/*Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.age - o2.age;
}
});*/
/*Collections.sort(list,(o1,o2)->{
return o1.age - o2.age;
});*/
Collections.sort(list,(o1,o2)->o1.age-o2.age);
System.out.println(list);
}
}
public class Person{
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
5)lambda 表达式的省略格式 = 重点
- 数据类型可以省略:(Person p1,Person p2):(p1,p2)
- () 中只有一个参数,() 可以省略:(Person p):省略格式(p) ,最简化的省略格式:p
- ->:不能省略
- {} 中如果只有一条语句:那么{},return,分号 都可以省略
但是要么全部省略,要么全部保留
6)lambda 表达式的前提 = 重点
(1)必须要有接口(函数式接口)
要求接口中只能有一个(必须要被覆盖重写的)抽象方法,可以有默认方法,可以有静态方法
(2)必须要有接口作为方法的参数
注意:lambda 表达式是对匿名内部类对象的简化书写格式
@Override: 检测是否是对父类方法的覆盖重写
@FunctionalInterface: 检测是否是函数式接口
14.3、函数式接口
1)概述
函数式接口在Java中是指:有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
2)格式
只要确保接口中有且只有一个抽象方法即可:
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
//其他非抽象方法内容
}
3)自定义函数式接口
lambda 表达式的前提,就是要使用函数式接口使用
比如:函数式接口作方法参数,函数式接口作方法返回值,函数式接口创建实现类时可以使用
格式:(抽象方法的参数)->{抽象方法的方法体};
注意:若抽象方法参数仅有一个时可以省略括号()
若抽象方法的方法体只有一条时,可以省略大括号{}和return;
案例:
@FunctionalInterface
interface test {
public void run(String string);
}
public class blog {
public static void main(String[] args) {
test t1 = new test() {
@Override
public void run(String string) {
System.out.println("匿名内部类创建实现类对象" + string);
}
};
test t2 = (string) -> {
System.out.println("lambda创建实现类对象" + string);
};
}
}
这就是lambda表达式与匿名内部类的区别:
匿名内部类需要写抽象方法的方法名等内容
而lambda只要写清抽象方法的参数和方法体即可
由于上面的t2实现类中只传入一个参数所以小括号可以取消,方法
体只有一条语句,大括号可以取消 :写成如下形式:
test t = string ->System.out.println(string);
练习:
自定义函数式接口:MyFunctionalInter,使用3种表达方式书写。
1)匿名内部类方式
2)lambda标准方式
3)lambda简化方式
@FunctionalInterface
public interface MyFunctionalInter {
//抽象方法
void method();
//静态方法
static void show(){
System.out.println("show()...");
}
//默认方法
default void printf(){
System.out.println("printf()...");
}
}
class TestDemoFun {
public static void main(String[] args) {
//1)匿名内部类方式
fun(new MyFunctionalInter(){
@Override
public void method() {
System.out.println("method()...");
}
});
System.out.println("=========================");
//2)lambda标准方式
fun(()->{
System.out.println("method()...");
});
System.out.println("=========================");
//3)lambda简化方式
fun(()->System.out.println("method()..."));
}
//定义一个方法,使用函数式接口作为参数
public static void fun(MyFunctionalInter mfi) {
mfi.method();
mfi.printf();
MyFunctionalInter.show();
}
}
4)常用函数式接口
JDK 提供了大量常用的函数式接口以丰富Lambda 的典型使用场景,它们主要在 java.util.function包中被提供。
下面是两个常用的函数式接口及使用示例。
A)消费型接口:Consumer = 必须掌握
java.util.function.Consumer<T>
接口,是消费一个数据,其数据类型由泛型参数决定。
抽象方法:
public abstract void accept(T t): 消费一个 T 类型的数据 t
什么叫做消费呢?
因为 accept 方法是抽象的,只要做覆盖重写{}后,就叫消费了,至于{}中写了哪些代码,不用管
案例:
public class TestConsumer {
public static void main(String[] args) {
/**
* 案例:
* 定义字符串:HelloWorld
* 定义方法,使用函数式接口Consumer作为参数
* 分别使用:
* 1、匿名内部类对象的方式 : 输出字符串内容
* 2、lambda表达式的标准方式:输出字符长度
* 3、lambda表达式的简化方式: 全部转换成大写
*/
//1、匿名内部类对象的方式 : 输出字符串内容
fun("HelloWorld", new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
//2、lambda表达式的标准方式:输出字符长度
fun("HelloWorld",s -> {
System.out.println(s.length());
});
//3、lambda表达式的简化方式: 全部转换成大写
fun("HelloWorld",s -> System.out.println(s.toUpperCase()));
}
////自定义方法使用consumer函数式接口
public static void fun(String str, Consumer<String> consumer){
// //消费接口方法
consumer.accept(str);
}
}
B)判断型接口:Predicate = 必须掌握
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate<T>
接口。
抽象方法:
public abstract boolean test(T t): 根据给定的方法参数 T 类型的 t,返回一个 boolean 类型的结果
public class TestPredicate {
public static void main(String[] args) {
/**
* 案例:
* 定义字符串:HelloWorld
* 定义方法,使用函数式接口Predicate作为参数
* 1、判断字符串长度是否大于5
* 2、判断字符串是否包含"H"
* 依然使用3种书写方式。
*/
//1、判断字符串长度是否大于5
boolean bool1 = exists("HelloWorld", new Predicate<String>() {
@Override
public boolean test(String s) {
return s.length() > 5;
}
});
System.out.println("bool1 = " + bool1);
//2、判断字符串是否包含"H"
boolean bool2 = exists("HelloWorld",s -> s.contains("H"));
}
//自定义方法使用 Predicate 函数式接口
public static boolean exists(String str, Predicate<String> predicate){
return predicate.test(str);
}
}
14.4、Lambda 中的方法引用
1、静态方法引用
格式:
类名::方法名
注意事项:
被引用的方法参数列表和函数式接口中抽象方法的参数一致!!
接口的抽象方法没有返回值,引用的方法可以有返回值也可以没有
接口的抽象方法有返回值,引用的方法必须有相同类型的返回值!!
public class TestFun2 {
public static void main(String[] args) {
/**
* 需求:把一个字符串,使用打印输出
*/
print("HelloWorld", new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
//lambda
print("HelloWorld",s -> {
PrintStream stream = System.out;
stream.println(s);
});
//静态方法引用
print("HelloWorld",s -> System.out.println(s));
//直接用静态方法,前面的参数作为后面静态方法的对象
print("HelloWorld",System.out::println);
}
public static void print(String str, Consumer<String> con){
con.accept(str);
}
}
2、对象方法引用
格式
==对象名::非静态方法名
注意事项与静态方法引用完全一致
public class TestFun3 {
public static void main(String[] args) {
/*Function<String,String> fun = new Function<String, String>() {
@Override
public String apply(String s) {
return new TestFun3().con(s);
}
};*/
/*Function<String,String> fun = s -> {
return new TestFun3().con(s);
};*/
//Function<String,String> fun = s -> new TestFun3().con(s);
Function<String,String> fun = new TestFun3()::con;
String ss = fun.apply("我是");
System.out.println("ss = " + ss);
}
public String con(String str){
return str.concat("对象方法引用");
}
}
3、构造方法引用
格式:
类名::new
注意事项:
被引用的类必须存在一个构造方法与函数式接口的抽象方法参数列表一致
interface test {
public Person run(String string);
}
class Person {
String name;
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
public class blog {
public static void main(String[] args) {
//实质代码: test t1 = (string) -> newPerson(string);
//实质代码: Person p = t1.run("张三");
test t2 = Person::new;
Person p2 = t2.run("李四");
System.out.println(p2); //输 出:Person{name='李四'}
}
}
由于函数式接口test中抽象方法,返回值是Person对象,且参数列表与Person类中的构造方法相同
则可以通过创建函数式接口的实现类对象,方法体通过调用类
中的构造方法创建对象
使用了构造方法引用写成了代码中t2的形式
4、数组构造方法引用
格式:
数据类型[]::new
interface test {
public String[] run(int length);
}
public class blog {
public static void main(String[] args) {
//实质代码: test t1 = (length) -> new
String[length];
test t2 = String[]::new;
String[] arr = t2.run(5);
}
}
5、特定类型的方法引用
格式:
类名::非静态方法
public class blog {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>
();
Collections.addAll(list,"d1wdaddq","ASDINAOSDN","
aa","AA");
//实质代码: Collections.sort(list,
(string1,string2)-
>string1.compareToIgnoreCase(string2));
Collections.sort(list,String::compareToIgnoreCase
);
System.out.println(list);
}
}
特定类型方法引用,在Comparator函数式接口的抽象方法中传入的参数有两个,
可是compareToIgnoreCase()方法参数只有一个,第一个传入的参数作调用对象
这就满足了特定类型的方法引用,所以可以简化成类名::非静态方法的形式
6、类中方法调用父类或本类方法引用
格式:
super::方法名
this::方法名
interface test {
public void itMethod();
}
class father {
public void buy() {
System.out.println("买东西");
}
}
class son extends father {
public void buy() {
System.out.println("买糖");
}
public void test() {
// 实质代码: test t = () -> buy();
test t = this::buy;
t.itMethod();
// 实质代码: test t2 = ()->super.buy();
test t2 = super::buy;
t2.itMethod();
}
}
public class blog {
public static void main(String[] args) {
son s = new son();
s.test(); //输出: 买糖 买东西
}
}
在有继承关系的类中,若方法想调用本类或父类的成员方法
在函数式接口抽象方法与成员方法参数列表相同,且返回值类型相同的情况下也可以使用this和super的方法引用来简写原本的lambda代码
14.5、Stream流
在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。
1、传统集合的多步遍历代码
几乎所有的集合(如 Collection接口或 Map接口等) 都支持直接或间接的遍历操作。而当我们需要对集合中的元素进行操作的时候,除了必须的添加、删除、获取外,最典型的就是集合遍历
public class Demo10ForEach {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
for (String name : list) {
System.out.println(name);
}
}
}
对集合中的每一个字符串都进行打印输出操作。
观察一下foreach的用法:
public class TestForeach {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
/* for (String name : list) {
System.out.println(name);
}*/
//lambda表达式标准方式:遍历迭代
//forEach: 表示使用lambda表达式实现迭代遍历。
list.forEach(s -> {
System.out.println(s);
});
System.out.println("===============");
//lambda表达式简化方式:遍历迭代
list.forEach(s -> System.out.println(s));
}
}
2、循环遍历的弊端
Java8 的Lambda 让我们可以更加专注于做什么(What),而不是怎么做(How)。
- for 循环的语法就是“怎么做”
- for 循环的循环体才是“做什么”
循环 是为了 遍历,遍历是指每一个元素逐一进行处理,而并不是从第一个到最后一个顺序处理的循环。前者是目的,后者是方式。
案例:
1. 首先筛选所有姓张的人;
2. 然后筛选名字有三个字的人;
3. 最后进行对结果进行打印输出。
循环遍历实现:
@Test
public void strPrintln(){
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张三丰","张无忌","谢逊","杨逍","周芷若","张三");
ArrayList<String> newList = new ArrayList<>();
ArrayList<String> nameList = new ArrayList<>();
//张
for (String name : list) {
if (name.startsWith("张")){
newList.add(name);
}
}
//3个字
for (String name : newList) {
if (name.length() == 3){
nameList.add(name);
}
}
System.out.println(nameList);
}
Stream 的更优写法
@Test
public void strPrintln2(){
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张三丰","张无忌","谢逊","杨逍","周芷若","张三");
Stream<String> stream = list.stream();
stream.filter(s -> s.startsWith("张")).filter(s -> s.length()==3).forEach(System.out::println);
}
3、什么是 Stream
流(Stream)到底是什么呢?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
“集合讲的是数据,流讲的是计算”
注意
- Stream 自己不会存储元素。
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新 Stream
- Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
4、Stream 操作的三个步骤
- 创建 Stream
一个数据源(如:集合、数组),获取一个流
- 中间操作
一个中间操作链,对数据源的数据进行处理
- 终止操作
一个终止操作。执行中间操作链,并产生结果
中间操作
中间操作只有终止操作时才执行。
- filter()
- map()
- flatMap()
- distinct()
- sorted()
- peek()
- limit()
- skip()
所有中间操作是懒执行,即直到实际需要处理结果时才会执行。执行中间操作实际上并不执行任何操作,而是创建一个新的流,当遍历该流时,它包含与给定谓词匹配的原始流的元素。因此在执行管道的终止操作之前,流的遍历不会开始。
这是非常重要的特性,对于无限流尤其重要——因为它允许我们创建只有在调用终止操作时才实际调用的流。
终止操作
终止操作可以遍历流生成结果或直接消费。终止操作执行后,可以认为管道流被消费了并不能再被使用。几乎在所有情况下,终端操作都是立即执行的,在返回之前完成对数据源的遍历和对管道的处理。
-
forEach()
-
forEachOrdered()
-
toArray()
-
reduce()
-
collect()
-
min()
-
max()
-
count()
-
anyMatch()
-
allMatch()
-
noneMatch()
每个这些操作都会触发所有的中间操作,然后终止
5、Stream流的常用语法
1)获取 Stream 流对象的方法
java.util.stream.Stream<T>
是 Java 8 新加入的最常用的流接口
1、方法1:通过集合
java.util.Collection<T>
接口
默认方法:必须由 Collection 接口的实现类(Arraylist/LinkedList/HashSet/LinkedhashedSet)对象调用
public default Stream<T> stream()
获取 Collection 集合对象对应的 Stream 流对象
案例:
public static List<Employee> getEmployeeDataList(){
List<Employee> list = new ArrayList<>();
list.add(new Employee(1,"张三",20,8500D,1));
list.add(new Employee(2,"李四",18,600D,1));
list.add(new Employee(3,"王五",21,5500D,3));
list.add(new Employee(4,"小白",30,8500D,2));
return list;
}
public static void main(String[] args) {
List<Employee> employees = getEmployeeDataList();
// 返回一个顺序流(串行流) (按照集合顺序获取)
Stream<Employee> stream = employees.stream();
// 返回一个并行流 (类似于线程去获取数据,无序)
Stream<Employee> parallelStream = employees.parallelStream();
}
2、通过数组
public static void main(String[] args) {
int[] arr = new int[]{1,2,3,4,5,6};
IntStream intStream = Arrays.stream(arr);
Employee e1 = new Employee(1, "张三", 20,8500D, 1);
Employee e2 = new Employee(2, "李四", 18, 600D, 1);
Employee[] employees = new Employee[]{e1,e2};
Stream<Employee> stream = Arrays.stream(employees);
}
3、通过 Stream 的 of 方法
java.util.stream.Stream<T>
接口
静态方法:
public static <T> Stream<T> of(T ... t): 把方法的可变参数指定的具体数据,转换成Stream流对象
参数:
T ... t: 可变参数 传递数组,参数列表
4、通过无限流
public static void main(String[] args) {
//迭代
Stream<Integer> stream4 = Stream.iterate(0, (i) -> ++i+i++);
stream4.forEach(System.out::println);
// 生成偶数
Stream.iterate(0,t- >t+2).limit(10).forEach(System.out::println);
// 生成10个随机数
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}
2)Stream 的 API 方法
便于后面获得 Stream流,这里提供方法获取集合
1、filter
接收 Lambda ,从流中排除某些元素
筛选工资大于8000的员工:
public static void main(String[] args) {
List<Employee> employees = getEmployeeDataList();
Stream<Employee> stream = employees.stream();
stream.filter(e -> e.getSalary() > 8000).forEach(t->{
System.out.println("工资大于八千的员工- >>>"+t);
});
}
2、limit
截断流,使其元素不超过给定数量
输出集合元素数量
public static void main(String[] args) {
List<Employee> employees = getEmployeeDataList();
employees.stream().limit(3).forEach(t-> System.out.println("输出集合元素数量->>>"+t));
}
3、skip
跳过元素,返回一个舍弃了前n个元素的流;若流中元素不足n个,则返回一个空流;与limit(n)互补
过滤掉前面的2个元素
public static void main(String[] args) {
List<Employee> employees = getEmployeeDataList();
employees.stream().skip(2).forEach(t-> System.out.println("过滤掉前面的2个元素->>>"+t));
}
4、distinct
筛选,通过流所产生的 hashCode() 与 equals() 去除重复元素
public static void main(String[] args) {
ArrayList<Employee> list = new ArrayList<>();
Collections.addAll(list,
new Employee("A001","张三",20,6500D),
new Employee("A002","王五",21,7500D),
new Employee("A002","王五",21,7500D),
new Employee("A003","小刘",17,4500D),
new Employee("A004","张飞",16,5500D),
new Employee("A005","王二",26,8500D),
new Employee("A005","王二",26,8500D),
new Employee("A005","王二",26,8500D),
new Employee("A006","小强",29,9500D),
new Employee("A006","小强",29,9500D));
//想把集合中,属性重复的数据去掉。
//distinct():去除重复元素,如果是对象类型,那么需要去重写equals和hashCode方法
list.stream().distinct().forEach(System.out::println);
}
5、map 映射
接收 Lambda,将元素转换为其他形式或提取信息;接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
大小写转换
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c", "d");
//把集合中的元素,转换为大写
//a -> A
list.stream().map(new Function<String, String>() {
@Override
public String apply(String s) {
return s.toUpperCase();
}
}).forEach(System.out::println);
//lambda
list.stream().map(s -> s.toUpperCase()).forEach(System.out::println);
//lambda+方法引用 类名::非静态方法名
list.stream().map(String::toUpperCase).forEach(System.out::println);
}
获取员工姓名大于3的员工姓名
public static void main(String[] args) {
ArrayList<Employee> list = new ArrayList<>();
Collections.addAll(list,
new Employee("A001","张三",20,6500D),
new Employee("A002","王五",20,7500D),
new Employee("A003","小刘",17,4500D),
new Employee("A004","张飞",16,5500D),
new Employee("A005","王二二强",17,8500D),
new Employee("A006","小强强强",29,9500D));
/* list.stream().sorted(new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
return o1.getAge()-o2.getAge();
}
}).forEach(System.out::println);*/
System.out.println("================================");
list.stream().sorted((o1,o2)->o1.getAge()-o2.getAge())
.forEach(System.out::println);
//练习:先对年龄进行升序排序,然后对工资进行降序排序 - 打印
list.stream().sorted((o1,o2)-> o1.getAge()== o2.getAge()?
(int) (o2.getSalary() - o1.getSalary()) : o1.getAge()- o2.getAge())
.forEach(System.out::println);
}
6、排序
- sorted():自然排序
@Test
public void test04(){
List<Integer> list = Arrays.asList(1,2,3,4,5);
list.stream()
.sorted() //comparaTo()
.forEach(System.out::println);
}
- sorted(Comparator c):定制排序
public static void main(String[] args) {
ArrayList<Employee> list = new ArrayList<>();
Collections.addAll(list,
new Employee("A001","张三",20,6500D),
new Employee("A002","王五",20,7500D),
new Employee("A003","小刘",17,4500D),
new Employee("A004","张飞",16,5500D),
new Employee("A005","王二二强",17,8500D),
new Employee("A006","小强强强",29,9500D));
/* list.stream().sorted(new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
return o1.getAge()-o2.getAge();
}
}).forEach(System.out::println);*/
System.out.println("================================");
list.stream().sorted((o1,o2)->o1.getAge()-o2.getAge())
.forEach(System.out::println);
//练习:先对年龄进行升序排序,然后对工资进行降序排序 - 打印
list.stream().sorted((o1,o2)-> o1.getAge()== o2.getAge()?
(int) (o2.getSalary() - o1.getSalary()) : o1.getAge()- o2.getAge())
.forEach(System.out::println);
}
7、匹配与查找
- allMatch
allMatch:检查是否匹配所有元素
判断员工年龄是否都大于18岁
public static void main(String[] args) {
List<Employee> list = getEmployeeDataList();
boolean allMatch = list.stream().allMatch(e ->
e.getAge() > 18);
System.out.println(allMatch);
}
//全部满足返回 true 、否则返回false
- anyMatch
anyMatch:检查是否至少匹配一个元素
是否存在有员工工资大于8000的
public static void main(String[] args) {
List<Employee> list = getEmployeeDataList();
boolean anyMatch = list.stream().anyMatch(employee -> employee.getSalary() > 8000);
System.out.println(anyMatch);
}
//存在一个元素条件满足即可返回true
- noneMatch
noneMatch:检查是否没有匹配的元素
查询是否有姓张的员工
public static void main(String[] args) {
List<Employee> list = getEmployeeDataList();
boolean noneMatch = list.stream().noneMatch(employee -> employee.getName().startsWith("张"));
System.out.println(noneMatch);
}
//返回false,说明有,否则没有
- findFirst
findFirst:返回第一个元素
public static void main(String[] args) {
List<Employee> list = getEmployeeDataList();
Optional<Employee> first = list.stream().findFirst();
System.out.println(first);
}
- findAny
findAny:返回当前流中的任意元素
public static void main(String[] args) {
List<Employee> list = getEmployeeDataList();
Optional<Employee> first = list.parallelStream().findAny();
System.out.println(first);
}
- count
count:返回流中元素的总个数
查询员工工资大于8000的人数
public static void main(String[] args) {
List<Employee> list = getEmployeeDataList();
long count = list.stream().filter(employee -> employee.getSalary() > 8000).count();
System.out.println(count);
}
- max
max:返回流中的最大值
查询最高的员工工资
public static void main(String[] args) {
List<Employee> list = getEmployeeDataList();
Stream<Double> doubleStream = list.stream().map(employee -> employee.getSalary());
Optional<Double> max = doubleStream.max(Double::compare);
System.out.println(max);
}
- min
min:返回流中的最小值
查询最低的员工工资
public static void main(String[] args) {
List<Employee> list = getEmployeeDataList();
Optional<Employee> min = list.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(min);
}
8、归约
reduce:可以将流中的元素反复结合起来,得到一个值
求出1到10的总和
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer reduce = list.stream().reduce(0, Integer::sum);
System.out.println(reduce);
}
//reduce的第一个参数0:代表初始值。
计算公司中所有员工的总和
public static void main(String[] args) {
List<Employee> list = getEmpList();
Optional<Double> sum = list.stream().map(Employee::getSalary).reduce(Double::sum);
System.out.println("sum.get() = " + sum.get());
}
9、收集 collect
collect 将流转换成其他形式;接收一个 Collector 接口的实现,用于给流中元素做汇总的方法
查找工资大于8000的员工,返回一个list或者set
public static void main(String[] args) {
List<Employee> list = getEmpList();
List<Employee> list1 = list.stream().filter(employee -> employee.getSalary() > 8000)
.collect(Collectors.toList());
System.out.println("list1 = " + list1);
}
14.6、枚举类型
1)概念
枚举:JDK1.5就引入
Java枚举:把某个类型的对象,全部列出来
枚举是一种类,是一种特殊的类,特殊在它的对象是有限的几个常量对象。
2)应用场景
当某个类型的对象是固定的,有限的几个,那么就可以选择使用枚举。
在整个系统的运行期间,有且只有这几个对象。
3)如何实现
讨论:JDK1.5之前,如果想要实现枚举的这种效果
(1)构造器私有化:
目的:在这个类的外面,无法随意的创建对象
(2)在这个类中,提前创建好几个对象,供别人使用
JDK1.5之后,就优化了枚举的语法:
如何声明枚举类型?
【修饰符】 enum 枚举类型名{
常量对象列表
}
【修饰符】 enum 枚举类型名{
常量对象列表;
其他的成员列表
}
说明:如果常量对象列表后面还有其他的成员,那么需要在常量对
象列表后面加;进行分割
4)枚举特点:
枚举类型不能继承其他类型,因为枚举类型有一个隐含的父类
java.lang.Enum
即Enum是所有 Java 语言枚举类型的公共基本类。
案例代码:
public enum Season {
/**
* 自定义枚举类:
* 1.对象只能获取,不能修改
* 2.属性没有set方法,只能get
* 3.构造方法都是private的
*/
//创建静态常量的对象属性
Spring("春天","春暖花开"),
Summer("夏天","烈日炎炎"),
Autumn("秋天","硕果累累"),
Winter("冬天","冰天雪地");
private String name; //名称
private String desc; //描述
Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
}
class Test{
public static void main(String[] args) {
Season autumn = Season.Autumn;
System.out.println("autumn = " + autumn);
Season autumn1 = Season.Autumn;
System.out.println(autumn == autumn1); //true
}
}
本文来自博客园,作者:Thecong,转载请注明原文链接:https://www.cnblogs.com/hwphwc/p/16591384.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)