✨你好啊,我是“ 罗师傅”,是一名程序猿哦。
🌍主页链接:楚门的世界 - 一个热爱学习和运动的程序猿
☀️博文主更方向为:分享自己的快乐 briup-jp3-ing
❤️一个“不想让我曾没有做好的也成为你的遗憾”的博主。
💪很高兴与你相遇,一起加油!

前言

目标:JVM、JDK新特性、JDK源码、高并发、MySql优化

本章目标:函数式接口、Lambda表达式、方法引用、Optional、Stream

接口方法

  • 整型常量数据可以用下划线分隔表示,在整数的值比较大的时候, 使数值更加直观
1
2
3
4
5
6
7
8
9
10
11
12
public class Test01_NumberUnderlineTest {
public static void main(String[] args) {
long a = 1000_000_000L;
int b = 0b00000000_00000000_00000000_00000111;
int c = 1234_5678_9;
int d = 123_456_789;
System.out.println("a = " + a); // a = 1000000000
System.out.println("b = " + b); // b = 7
System.out.println("c = " + c); // c = 123456789
System.out.println("d = " + d); // d = 123456789
}
}

默认方法

JDK1.8中,接口里面可以定义默认方法。

1
2
3
4
5
interface InterfaceName{
default returnType methodName(arg-list){
//代码实现
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface FirstInterface{
//传统定义,抽象方法,没有方法体。
void before();
//默认方法
default void test() {
System.out.println("Default method in FirstInterface");
}
}
class FirstClass implements FirstInterface{
//所有实现类必须实现接口中未实现的方法。
@Override
public void before() {
System.out.println("我是FirstInterface中的抽象方法,所有实现类必须实现!");
}
}
public class DefaultMethod {
public static void main(String[] args) {
FirstClass fc = new FirstClass();
fc.test(); //此处输出Default method in FirstInterface,对于默认方法,如果实现类中没有实现就是用默认的。
fc.before(); //此处输出我是FirstInterface中的抽象方法,所有实现类必须实现。
}
}

默认方法存在的两大优势:

  • 可以让接口更优雅的升级,减少使用人员操作的负担
  • 可以让实现类中省略很多不必要方法的空实现

(不必随着接口方法增加,从而修改实现代码,因为默认方法在字了中可以不用实现)

接口继承及默认方法冲突

方法调用的判断规则:

  • 类中声明的方法优先级最高
    • 类中,声明的方法要高于任何默认方法的优先级
  • 如果无法依据第一条进行判断,那么子接口的优先级更高
  • 最后,如果还是无法判断,那么继承了多个接口的类,必须通过实现(重写)方法来确定方法的调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 第一条规则
class A{
public void test(){
System.out.println("Default Method test in A");
}
}
interface B {
default void test(){
System.out.println("default method test in B");
}
}
class C extends A implements B{
public static void main(String[] args){
C c = new C();
c.test();
}
}
//运行结果:default method test in A

因为A是类,B是接口,A里边的test的优先级更高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 第二条
interface A{
default void test(){
System.out.println("Default Method test in A");
}
}
interface B extends A{
default void test(){
System.out.println("default method test in B");
}
}
class C implements A,B{
public static void main(String[] args){
C c = new C();
c.test();
}
}
// 运行结果:
// default method test in B

因为B重写了A中的默认方法

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
interface A{
default void test(){
System.out.println("Default Method test in A");
}
}
interface B {
default void test(){
System.out.println("default method test in B")
}
}
//如下代码编译会报错。
/*class C implements A,B{
public static void main(String[] args){
C c = new C();
c.test();
}
}*/
//如果C需要同时实现A和B接口,那么必须显示覆盖
class C implements A,B{
public void test(){
//如果在C中需要显示访问A/B的默认方法,可以使用接口
名.super.方法名();
A.super.test();
B.super.test();
//或者自己编写test方法的实现
}
}

静态方法

JDK1.8中,接口里面可以定义静态方法。

1
2
3
4
5
6
7
interface InterfaceName{
static returnType methodName(arg-list){
//代码实现
}
}
// 访问
InterfaceName.methodName();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface StaticMethod{
public static void test(){
System.out.println("我是StaticMethod接口中的静态方法!");
}
}
class A implements StaticMethod{}
class Test{
public static void main(String args[]){
//运行成功
StaticMethod.test();
//编译报错:
//Static method may be invoked on containing interface class only
//A.test();
}
}

注意,接口中的静态方法,只能使用当前接口的名字来调用

Lambda

Lambda表达式是JDK1.8新增的一种语法,以确保在java代码中可以支持函 数式编程,让代码的表示含义更简单。

理解lambda表达式之前,需要先理解行为参数化和函数式编程的概念。

行为参数化

在java代码中,我们如果需要运算,那么大部分我们的过程是确定的,但是实际参与运算的数却不确定。

1
2
3
4
5
6
7
8
public static int calculate(int a,int b, 此处传递一个计算行为)
{
int result = a;
for(int i = a+1;i<=b;i++){
调用计算行为,操作当前数据
}
return result;
}

我们可以将这个 计算行为,用接口的形式进行定义:

1
2
3
interface Action {
int action(int result, int i);
}

这时候, calculate 方法就可以这样进行定义:

1
2
3
4
5
6
7
public static int calculate(int a, int b, Action cal) {
int result = a;
for(int i = a+1; i<=b; i++) {
result = cal.action(result, i);
}
return result;
}

具体的使用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//定义累加的核心操作行为
class Add implements Action{
public int action(int result,int i){
return result+i;
}
}
//定义累乘的核心操作行为
class Multiply implements Action{
public int action(int result,int i){
return result*i;
}
}
// 测试方法
class Test{
public static void main(String[] args){
int result = 0;
result = calculate(3,5,new Add());
//[3,5]之间,累加的结果
System.out.println(result);
result = calculate(3,5,new Multiply());
//[3,5]之间,累乘的结果
System.out.println(result)
}
}

上述代码中,将我们要执行的核心计算操作,定义成一个参数,传给了calculate方法

我们可以通过这个参数,给方法传递不同的行为,来实现不同操作,这就是行为参数化

java中,不允许孤立的代码存在,我们要想将行为(核心操作代码)传递给 calculate 方法,就必须要将这些核心操作代码,包装在一个实现了 Action的类中。

为了减少声明和定义类,Java提供了匿名内部类的实现,来简化我们刚才的调用过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Test{
public static void main(String[] args){
int result = 0;
result = calculate(3,5,new Action(){
public int action(int result,int i){
return result+next;
}
});
//[3,5]之间,累加的结果
System.out.println(result);
result = calculate(3,5,new Action(){
public int action(int result,int i){
return result*next;
}
});
//[3,5]之间,累乘的结果
System.out.println(result)
}
}

但是仍然存在相同的代码:

  • new Action()
  • public int action(int result,int next){}

我们真正关心的是:

  • 方法的参数列表
  • 方法中的核心操作代码
  • 方法的返回类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test02_LambdaClass {
public static void main(String[] args) {
Action action1 = new Action() {
@Override
public int action(int result, int i) {
return result + i;
}
};
Action action2 = (result, i) -> result + i; // lambda表达式
System.out.println(action1.getClass()); // class com.briup.expand.chap01.Test$1
System.out.println(action2.getClass()); // class com.briup.expand.chap01.Test$$Lambda$1/1324119927
}

public static int calculate(int a, int b, Action cal) {
int result = a;
for (int i = a + 1; i <= b; i++) {
result = cal.action(result, i);
}
return result;
}
}

思考,对Action接口,分别使用匿名类和Lambda表达式进行实现,然后调 用俩个对象getClass方法,观察结果有何不同?

1
2
// class com.briup.expand.chap01.Test$1  匿名内部类.getClass()
// class com.briup.expand.chap01.Test$$Lambda$1/1324119927 Lambda表达式.getClass()
  • $$Lambda表示是Lambda表达式
  • Lambda$1表示是Lambda表达式中的第一个匿名内部类
  • /1324119927:唯一标识这个lambda表达式的内部类

函数式编程

函数式编程,和面向对象编程、以及面向过程编程一样,它们都是编程的一种方式

函数式编程时面向数学的抽象,将计算过程描述为一种表达式求值。f(x) = y

严格意义上的表达式,就是由数据和操作我按照一定的规则,组合再一切形成的序列,并且所有的表达式都是有返回结果的,这里所说的表达式和代码语句的最大区别。

但是在java中,是允许函数式编程中没有任何返回值的,因为java中有关键字void(也可以看做是一个结果),但是在其他一些专门的函数式编程语言中,是没有void的。

所以,jdk1.8中,可以把Action接口的实现,简写为:

1
2
3
4
5
6
7
/*
Action add = (int result,int next) -> {
return result + next;
};
*/
//简化写法
Action add = (result,next) -> result + next;

Lambda概述

Lambda表达式,可以用来表示一个函数,它只关注函数的参数列表, 函数主体、返回类型,并且可以将此函数作为一个参数,进行传递。

在java中,Lambda表达式还有另一个存在的意义,那就是作为一个接口的实现类对象

1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String[] args) {
Action a = (str) -> str.length(); // 作为接口的实例化对象
System.out.println(a.test("hello"));
}
}
interface Action{
public int test(String str);
}

可以看出,Lambda表达式,虽然可以通过(参数列表,函数主体,返回类型)三部分来标识一个具体的函数操作,但是它必须是依托一个接口才行,所以Lambda表达式就是对接口中抽象方法的实现

Lambda使用

函数式接口

有且只有一个抽象方法的接口,就是函数式接口。该接口中,也允许有其他的默认方法和静态方法

1
2
3
4
5
6
7
8
9
10
11
//这是函数式接口
public interface Action{
void action();
}
//这也是函数式接口,里边只有一个抽象的方法action
public interface DefaultAction{
void action();
default void test(){
System.out.println("我是默认实现");
}
}
1
2
3
4
5
6
7
8
9
10
11
//这是函数式接口
public interface Calculate{
int add(int a,int b);
}
//这【不是】函数式接口,因为接口SubCalculate中有俩个抽象方法,自
己一个,继承一个
public interface SubCalculate extends Calculate{
double add(double a,double b);
}
//这【不是】函数式接口,因为接口Noting中没有任何抽象方法
public interface Noting{}

JDK1.8中,针对函数式接口,新增了一个注解 @FunctionalInterface,用来检查被标注的接口,是不是一个函数式接口,如果不是,那么编译器会报错。

但是,该注解不是必须要用的,它只是会让编辑器帮我们检查一下而已,以免出现接口中抽象个数不是1的情况

1
2
3
4
@FunctionalInterface
public interface Action{
void run();
}

Lambda语法

Lambda表达式的格式为:() -> {}

  • ()表示参数列表
  • -> 后面跟的式函数主体
  • {} 函数主题,表达式的返回值,由这个函数主体中代码来决定

函数式接口中,抽象方法常见的有以下几种情况:

  • 无参无返回值
1
2
3
4
5
6
7
8
9
10
Action action1 = () -> {};
Action action2 = () -> System.out.println("hello");
Action action3= () -> {
int a = 1;
int b = 2;
System.out.println(a+b);
};
interface Action{
public void run();
}
  • 有参无返回值
1
2
3
4
5
Action action1 = (a,b) -> {};
//抽象方法是多个参数的情况
interface Action{
public void run(int a,int b); // 两个参数
}
  • 有参有返回值
1
2
3
4
Action action = () -> 1;
interface Action {
public int run();
}
  • 无参有返回值
1
2
3
4
5
6
7
8
Action action1 = (a,b) -> a + b;
Action action2 = (a,b) -> {
int num = a+b;
return (int)(Math.random()*num);
};
interface Action{
public int run(int a,int b);
}

注意,Lambda表达式中的参数列表,里面的参数可以不写类型,因为JVM在运行时会自动推断,当然,如果直接手写上去,也完全没有问题

常用接口

JDK1.8中,已经定了一些会常用到的函数式接口,这些函数式接口都定义 在 java.lang.function 包中,例如 Predicate 、 Consumer 、 Function 、 Supplier 、 UnaryOperator 和 BinaryOperator 等。

注意,如果需要,也可以自己定义类似的函数式接口,并不是必须要使用 这些定义好的接口。

Predicate

1
2
3
4
5
// java.util.function.Predicate<T>
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t); // T -> boolean
}
  • 定义一个方法,用来过滤数组中所有符合要求的数据,选出大于50 的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test3_PredicateTest {
public static void main(String[] args) {
Integer[] arr = {1, 54, 3, 4, 6, 8, 455, 8, 35, 835, 98};
arr = Test3_PredicateTest.filter(arr, e -> e > 50);
System.out.println(Arrays.toString(arr));
}

public static Integer[] filter(Integer[] arr, Predicate<Integer> p) {
List<Integer> list = new ArrayList<>();
for (Integer integer : arr) {
// 判断当前数据是否满足要求
if (p.test(integer)) {
list.add(integer);
}
}
// return list.toArray(new Integer[list.size()]);
// 这里使用new Integer[0]即可,主要是为了告诉JVM我要转数组的类型
// toArray()会进行自动扩容
return list.toArray(new Integer[0]);
}
}
  • and
1
2
3
4
5
6
7
//条件1,数据大于10
Predicate<Integer> p1 = e -> e>10;
//条件2,数据小于50
Predicate<Integer> p2 = e -> e<50;
//俩个条件同时成立
Predicate<Integer> p = p1.and(p2);
arr = t.filter(arr,p);
  • or
1
2
3
4
5
6
7
//条件1,数据小于10
Predicate<Integer> p1 = e -> e<10;
//条件2,数据大于50
Predicate<Integer> p2 = e -> e>50;
//俩个条件成立一个即可
Predicate<Integer> p = p1.or(p2);
arr = t.filter(arr,p);
  • negate
1
2
3
4
5
//条件1,数据大于10
Predicate<Integer> p1 = e -> e>10;
//获取条件1相反的数据
Predicate<Integer> p = p1.negate();
arr = t.filter(arr,p);

JDK1.8中,给 Collection 集合增加了默认方法: removeIf

Consumer

java.util.function.Consumer 接口:

1
2
3
4
5
6
7
8
9
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after)
{
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
  • 定义一个方法,用来对学生对象进行操作
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
public class Test04_ConsumerTest {
public static void main(String[] args) {
Test04_ConsumerTest t = new Test04_ConsumerTest();
Student stu = new Student("peter");
// 操作1、给stu对象的name属性值加前缀
Consumer<Student> consumer1 = (student) -> student.name = "TangClan_" + student.name;
// 操作2、给stu对对象的name属性值添加后缀
Consumer<Student> consumer2 = student -> student.name = student.name + "_" + System.currentTimeMillis();
// 操作3、给stu对象的name属性值,先加前缀,再加后缀
Consumer<Student> consumer3 = consumer1.andThen(consumer2);
//如果传入consumer1,表示只加前缀
//如果传入consumer2,表示只加后缀
//如果传入consumer3,表示先加前缀,再加后缀
// t.operStu(stu, consumer1);
// t.operStu(stu, consumer2);
t.operStu(stu, consumer3);
System.out.println(stu.name);
System.out.println("------------------------------------------------");

Collection<String> col = new ArrayList<>();
col.add("lwsj");
col.add("peter");
col.add("parker");
// 去掉中间变量,直接把Lambda表达式作为当前参数传入到forEach方法中
col.forEach(res -> System.out.println(res));
}

public void operStu(Student stu, Consumer<Student> consumer) {
consumer.accept(stu);
}
}

class Student {
String name;

public Student(String name) {
this.name = name;
}
}

Function

java.util.function.Function 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
//Returns a function that always returns its input argument.
static <T> Function<T, T> identity() {
return t -> t;
}
}
  • 将字符串按照指定条件转为集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test05_FunctionTest {
public static void main(String[] args) {
String str = "a-b-c-a-b-c";
// 传入字符串,返回数组,操作为把字符串按照 "_" 进行分割为字符串数组
// "a-b-c-a-b-c" 转换为 {"a","b","c","a","b","c"}
Function<String, String[]> f1 = s -> s.split("-");
// 传入字符串数组,返回Set<String>集合,目的是去除数组中重复的数据,存放把结果存放到Set集合中
// {"a","b","c","a","b","c"} 转换为集合 [a,b,c]
Function<String[], Set<String>> f2 = arr -> {
Set<String> set = new HashSet<>();
set.addAll(Arrays.asList(arr));
return set;
};
// 刚好,f1函数的结果,作为f2函数的参数,f1和f2组合成f3函数
// f3函数表示传入字符串,最后返回Set<String>集合
// 其实内部是先讲字符串交给f1函数转换数组,再将数组交给f2函数转换Set集合
Function<String, Set<String>> f3 = f1.andThen(f2);
Set<String> set = f3.apply(str);
System.out.println(set);
}
}

理解了andThen方法,那么compose方法也就理解:f1.andThen(f2), f1.compose(f2)

俩个方法的区别是,把f2操作放在f1操作之前还是之后的问题

静态方法 identity ,API中给出的注释为:

1
2
3
4
5
6
7
8
9
/**
* Returns a function that always returns its input argument.
*
* @param <T> the type of the input and output objects to the function
* @return a function that always returns its input argument
*/
static <T> Function<T, T> identity() {
return t -> t;
}

可以看出,该方法可以直接返回一个Function对象,传入一个参数,直接把该参数返回,不做任何操作

需要注意的式,这是一个泛型方法,因为泛型参数T是在这个方法上定义的

1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
Function<String,String> f = Function.identity();
//传入hello,返回hello
String result = f.apply("hello");
System.out.println(result);
}
}

具体的使用场景后面遇到再说哈~~

Supplier

java.util.function.Supplier 接口

1
2
3
4
5
6
7
8
9
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
  • 输出10个1-100之间的随机的奇数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test08_SupplierTest {
public static void main(String[] args) {
// 生成1-100直接的随机的奇数
Supplier<Integer> supplier = () -> {
int num;
do {
num = (int) (Math.random() * 100 + 1);
} while ((num & 1) == 0);
return num;
};
for (int i = 0; i < 10; i++) {
System.out.println(supplier.get());
}
}
}

Predicate 、 Consumer , Function , Supplier ,这些接口都是带泛型的接口,泛型的类型只能是引用类型,那么如果需要操作基本类型的数据,这时候就会做自动装箱和拆箱。而大量的装箱和拆箱是比较消耗性能的,所以JDK1.8中,还专门定义了一 些针对基本类型的函数式接口,例如:

类型推断

使用Lambda表达式,相当于给函数式接口生成一个实例,但是Lambda表达 式本身,并不包含这个接口的任何信息,例如:

1
2
3
4
5
6
7
8
9
10
public class Test {
public static void main(String[] args) {
Test t = new Test();
//()->{} 这个表达式中并没有包含任何Runnable接口的信息
//但是编译和运行都是成功的
t.test(()->{});
}
public void test(Runnable run){
}
}

之所以Lambda表达式中没有接口的任何信息,JVM还能将其和接口匹配上,那是因为:

  • 我们在使用Lambda表达式的时候,JVM是会通过上下文自动推断它所属接口类型的
  • 并且接口中只有一个抽象方法,自然也能匹配成功该表达式所对应实现的抽象方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {
public static void main(String[] args) {
Test t = new Test();
//JVM根据上下文运行环境自动推断出 ()-{} 表达式对应的是接口是Runnable
t.test1(()->{});
//JVM根据上下文运行环境自动推断出 ()-{} 表达式对应的是接口是Action
t.test2(()->{});
}
public void test1(Runnable run){
}
public void test2(Action action){
}
}
interface Action{
void run();
}

类似的,JVM还能自动推断出Lambda表达式中参数的类型,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test {
public static void main(String[] args) {
Test t = new Test();
//这俩种写法的效果是一样的,JVM根据环境自动推断出参数a和b的类型
t.test((int a,int b)->a+b);
t.test((a,b)->a+b);
}
public void test(Action action){
}
}
interface Action{
int run(int a,int b);
}

重载解析

如果类中的方法进行了重载,那么在使用Lambda表达式的时候,很可能给它的类型推断带来问题。

  • 编译报错
1
2
3
4
5
6
7
8
9
10
public class Test {
public static void main(String[] args) {
//编译报错,因为俩个方法都符合
test(1,num -> num>0);
}
public static void test(int a,Predicate<Integer> p){ // T -> boolean
}
public static void test(int a,Function<Integer,Boolean> f){ // T -> R
}
}
  • 类型转换
1
2
3
4
5
6
7
8
9
10
public class Test {
public static void main(String[] args) {
//编译通过,用强转的方式指定了表达式的对应的接口类
test(1,(Predicate<Integer>)(num->num>0));
}
public static void test(int a,Predicate<Integer> p){
}
public static void test(int a,Function<Integer,Boolean> f){
}
}

局部变量

如果在Lambda表达式中,使用了局部变量,那么这个局部变量就一定要使 用 final 修饰符进行修饰,这方面的语法要求,和之前学习的匿名内部类 保持一致。

1
2
3
4
5
6
7
8
9
10
11
public class Test {
public static void main(String[] args) {
int a = 1;
Runnable run = ()->{
//这里访问局部变量a之后,a就自动变成了final修饰的常量(JDK1.8)
//也可以手动给局部变量a加上final修饰符
//变量a的值将不可被再次赋值,变为了常量
System.out.println(a);
};
}
}

方法引用

静态方法引用

1
类名::静态方法名

注意:方法名后面一定没有小括号,因为这里不是在调用方法,而是在引用这个方法的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {
public static void main(String[] args) {
//只要函数的参数列表是String类型,函数的返回值是int类型,就可以作为Action接口的具体实现
Action a1 = str -> 1;
//使用 类名::静态方法名 的形式来引用当前类中的len方法
Action a2 = Test::len;
System.out.println(a1.run("hello"));//输出1
System.out.println(a2.run("hello"));//输出5
}
public static int len(String str){
return str.length();
}
}
interface Action{
int run(String str);
}

实例方法引用

1
类名::非静态方法名

注意,这里也是使用类名来进行引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test {
public static void main(String[] args) {
/**
* Action a = new Action() {
* @Override
* public int run(MyHandler handler,int i, String str,
List<Integer> list) {
* return handler.test(i,str,list);
* }
* };
*/
Action a = MyHandler::test;
}
}
interface Action{
int run(MyHandler handler, int i, String str, List<Integer> list);
}
class MyHandler{
public int test(int i, String str, List<Integer> list){
return 0;
}
}

规律:方法参数列表中第一个参数作为对象,从第二个参数开始,后面的都作为该对象的参数列表,返回值类型要一致

使用对象引用方法

1
对象::非静态方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {
public static void main(String[] args) {
MyHandler handler = new MyHandler();
Action a1 = str -> str;
//这里表示,使用handler对象的test方法,来对Action接口进行实现
//因为test方法的参数列表和返回类型,恰好是和run方法的参数列表和返回类型保持一致
Action a2 = handler::test; // 这个时候表示a2指向了handler对象中的test方法
System.out.println(a2.run("tom"));//输出:hello! tom
}
}
interface Action{
String run(String str);
}
class MyHandler{
public String test(String name){
return "hello! "+name;
}
}

构造方法引用

1
类名::new

无参构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {
public static void main(String[] args) {
Action a1 = ()->new Student();
//这里表示,使用Student类的无参构造函数,来对Action接口进行实现
//因为Student类的无参构造函数,恰好是和run方法的参数列表和返回类型保持一致
Action a2 = Student::new;
System.out.println(a2.run());
}
}
interface Action{
Student run();
}
class Student{
}

有参构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {
public static void main(String[] args) {
Action a1 = name->new Student(name);
//这里表示,使用Student类的有参构造函数,来对Action接口进行实现
//因为Student类的有参构造函数,恰好是和run方法的参数列表和返回类型保持一致
Action a2 = Student::new;
System.out.println(a2.run("tom"));
}
}
interface Action{
Student run(String name);
}
class Student{
private String name;
public Student(String name){
this.name = name;
}
}

数组构造

1
数组类型::new
  • 根据给定的长度,创建任意类型的数组对象
1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
public static void main(String[] args) {
Action a1 = len -> new int[len];
//这里表示,使用int数组的构造函数,来对Action接口进行实现
//因为int数组的构造函数,恰好是和run方法的参数列表和返回类型保持一致
Action a2 = int[]::new;
System.out.println(a2.run(5));
}
}
interface Action{
int[] run(int len);
}

可以看出,无论是构造器,还是普通方法,在Lambda表达式中,都有一个可以引用过来,当做一个函数,这个函数有参数列表、函数主体、返回类型,并且把这个函数当做一个函数式接口中抽象方法的实现!

Optional

java.util.Optional 类,是用来防止NullPointerException异常的辅助类型, Optional 对 象中封装的值,可以是 null ,也可以不是 null

在Java8之前,一个函数可能因为代码逻辑问题,最终返回一个null,这时候程序中很可能出现空指针异 常。而在Java8中,不推荐返回 null ,而是返回 Optional

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class Test {
public static void main(String[] args) {
/**
* of方法 为非null的值创建一个Optional对象
* 如果传入参数为null,则抛出NullPointerException
*/
Optional<String> op1 = Optional.of("hello");
/**
* ofNullable方法
* ofNullable与of方法相似,唯一的区别是可以接受参数为null的情况
*/
Optional<String> op2 = Optional.ofNullable(null);
/**
* isPresent方法 如果值存在返回true,否则返回false
* get方法 如果Optional有值则将其返回,否则抛出NoSuchElementException
*/
if(op1.isPresent()){
System.out.println(op1.get());
}
if(op2.isPresent()){
System.out.println(op2.get());
}
/**
* ifPresent方法 如果Optional实例有值则为其调用Consumer接口中的方法,否则不做处理
* Consumer:
* public void accept(T t);
*/
op1.ifPresent(str->System.out.println(str));
op2.ifPresent(str->System.out.println(str));//这个不执行 因为op2里面的值是null
/**
* orElse方法 如果有值则将其返回,否则返回指定的其它值
*/
System.out.println(op1.orElse("如果op1中的值为null则返回这句话"));
System.out.println(op2.orElse("如果op2中的值为null则返回这句话"));
/**
* orElseGet方法 orElseGet与orElse方法类似,区别在于得到的默认值的方式不同
* Supplier:
* public T get();
*/
System.out.println(op1.orElseGet(()->"自己定义的返回值"));
System.out.println(op2.orElseGet(()->"自己定义的返回值"));
/**
* map方法 如果有值,则调用mapper的函数处理并得到返回值
* 返回值并且依然Optional包裹起来,其泛型和你返回值的类型一致
* public <U> Optional<U> map(Function<? super T, ? extends U> mapper)
*/
Optional<Integer> map1 = op1.map(str->1);
System.out.println(map1.orElse(0));
Optional<Double> map2 = op2.map(str->1.2);
System.out.println(map2.orElse(0D));
/**
* flatMap方法 如果有值,则调用mapper的函数返回Optional类型返回值,否则返回空
Optional
* flatMap与map方法类似,区别在于flatMap中的mapper返回值必须是Optional
* 调用结束时,flatMap不会对结果用Optional封装,需要我们自己把返回值封装为Optional
* public <U> Optional<U> flatMap(Function<? super T,Optional<U>>
mapper);
*/
Optional<String> flatMap = op1.flatMap(str->Optional.of(str+"_briup"));
System.out.println(flatMap.get());
/**
* filter方法 如果有值并且满足断言条件返回包含该值的Optional,否则返回空Optional
* public Optional<T> filter(Predicate<? super T> predicate);
*/
op1 = op1.filter(str->str.length()<10);
System.out.println(op1.orElse("值为null"));
op1 = op1.filter(str->str.length()>10);
System.out.println(op1.orElse("值为null"));
}
}

Stream

概述

Stream操作分为中间操作或者最终操作两种:

  • 中间操作:返回Stream本身,这样就可以将多个操作依次串起来
1
map、flatMap、filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered
  • 最终操作:返回一特定类型的计算结果
1
forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Collections.addAll(list,1,2,3,4,5,6,7,8,9,10);
list = list.stream() //把集合list变为stream
.filter(e->e%2==0) //过滤,保留偶数
.sorted((e1,e2)->e2-e1) //排序,倒序
.collect(Collectors.toList()); //结果收集到一个新的List集合中并返回
System.out.println(list);
}
}
// 运行结果:
// [10, 8, 6, 4, 2]

优雅、简洁

其他转Stream

可以将现有的数据,转换为Stream对象,然后再使用Stream的API对数据进行一些系列操作

1
2
3
4
5
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("a", "b", "c");
}
}

数组

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
String[] arr = {"a", "b", "c"};
Stream<String> stream1 = Arrays.stream(arr);
Stream<String> stream2 = Stream.of(arr);
}
}

集合

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list,"a", "b", "c");
Stream<String> stream = list.stream();
}
}

注意,只要是Collection类型的集合,都可以调用stream()方法,将集合转换为Stream对象

基本类型

1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String[] args) {
IntStream stream1 = IntStream.of(new int[]{1, 2, 3});
//[1,3)
IntStream stream2 = IntStream.range(1, 3);
//[1,3]
IntStream stream3 = IntStream.rangeClosed(1, 3);
}
}

Stream转其他

使用Stream的API对数据操作后,还可以把结果转换为其他类型

数组

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","briup");
/**
* <A> A[] toArray(IntFunction<A[]> generator);
*
* public interface IntFunction<R> {
* R apply(int value);
* }
*/
String[] strArray = stream.toArray(String[]::new);
}
}

集合

1
2
3
4
5
6
7
8
9
10
11
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","briup");
List<String> list1 = stream.collect(Collectors.toList());
// 创建指定类型的List
// List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set<String> set3 = stream.collect(Collectors.toSet());
// 创建指定类型的Set
//Set<String> set4 = stream.collect(Collectors.toCollection(HashSet::new));
}
}

注意,一个Stream在代码中,只能使用一次,再次使用就会报错

字符串

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello", "world", "truman");
String result = stream.collect(Collectors.joining("-"));
System.out.println(result); // hello-world-truman
}
}

Stream操作

最终操作

  • iterator , 返回迭代器对象
1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","briup");
Iterator<String> it = stream.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
  • forEach ,将调Stream中的每个元素,交给一个Consumer函数处理
1
2
3
4
5
6
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","briup");
stream.forEach(System.out::println);
}
}
  • count : 统计流中的元素数,并返回结果

  • max : 返回流中基于comparator所指定的比较规则,比较出的最大值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","briup");
/**
*
* Optional<String> max = stream.max(new Comparator<String>() {
* @Override
* public int compare(String o1, String o2) {
* return o1.compareTo(o2);
* }
* });
*/
Optional<String> max = stream.max(String::compareTo);
System.out.println(max.get());
}
}
  • min ,返回流中基于comparator所指定的比较规则,比较出的最小值

  • toArray : 使用调用流中的元素,生成数组返回。

  • collect ,将元素收集到一个可以修改的容器中,并返回该容器

1
2
3
4
5
6
7
8
9
10
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","briup");
List<String> list1 = stream.collect(Collectors.toList());
//List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
//Set<String> set3 = stream.collect(Collectors.toSet());
//Set<String> set4 = stream.collect(Collectors.toCollection(HashSet::new));
//String result = stream.collect(Collectors.joining("-"));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("test", "hello", "world", "java", "tom", "C", "javascript");
//把Stream中的元素,按照字符串长度进行分组,长度相同算是一组,并存放到同一个集合中
//map的key是字符串的长度,value是同一组的数据
Map<Integer, List<String>> map = stream.collect(Collectors.groupingBy(String::length));
map.forEach((k, v) -> System.out.println(k + " : " + v));
}
}
// 1 : [C]
// 3 : [tom]
// 4 : [test, java]
// 5 : [hello, world]
// 10 : [javascript]
1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("test", "hello", "world", "java", "tom", "C", "javascript");
//把Stream中的元素,按照指定条件分割成俩组,条件返回true是一组,条件返回false是另一组
//map的key是true或者false,value是对应的数据
//按照数据中是否包含"java"字符串来进行划分
Map<Boolean, List<String>> map = stream.collect(Collectors.partitioningBy(s -> s.indexOf("java") != -1));
map.forEach((k, v) -> System.out.println(k + " : " + v));
}
}
// false : [test, hello, world, tom, C]
// true : [java, javascript]
  • Match ,匹配操作,Stream中提供了多种匹配模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("test","hello","world","java","tom","C","javascript");
//所有元素匹配成功才返回true 否则返回false
boolean allMatch = stream.allMatch((s)->s.startsWith("j"));
System.out.println(allMatch);
//任意一个匹配成功就返回true 否则返回false
boolean anyMatch = stream.anyMatch((s)->s.startsWith("j"));
System.out.println(anyMatch);
//没有一个匹配的就返回true 否则返回false
boolean noneMatch = stream.noneMatch((s)->s.startsWith("j"));
System.out.println(noneMatch);
}
}

注意,这些操作不能同时执行,因为一个Stream只能使用一次

  • findFirst ,返回 Stream的第一个元素
1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("test","hello","world","java","tom","C","javascript");
Optional<String> first = stream.findFirst();
System.out.println(first.get());
}
}

中间操作

  • filter , 过滤方法,返回满足predicate指定的条件的所有元素的一个新流
  • map , 对调用流中的元素,应用Function所指定的操作,然后返回一个新流
1
2
3
4
5
6
7
Stream<String> nameStream = Stream.of("lwsj", "truman", "peter", "parker");
// 把字符串流转为 -> length流 -> List<Integer>
// map生成的是个1:1映射,每个输入元素,都按照规则转换成为另外一个元素
List<Integer> collect = nameStream.map(s -> s.length()).collect(Collectors.toList());
for (Integer integer : collect) {
System.out.println(integer);
}

map 方法可以和 reduce 方法配合使用, reduce 方法是将一组数据俩俩合并,最后得出一个结果:

1
2
3
4
5
6
7
//1~10之间的数字累加
IntStream stream = IntStream.rangeClosed(1, 10);
//reduce方法需要提供一个起始值(种子)
//然后依照运算规则,和Stream中的第一个数据进行操作,得出结果
//再将这个结果和Stream中的第二个数据进行操作,再得出结果,依次类推,直到得出最终结果
int result = stream.reduce(0, (a, b) -> a + b);
System.out.println(result);
1
2
3
4
5
6
7
Stream<String> stream = Stream.of("tom", "mary", "lucy");
//map方法中,让每一个数据加上前缀
//reduce方法中,将每个数据使用|拼接合并在一起
//reduce方法,也可以没有起始值,直接对Stream中的数据进行俩俩操作
Optional<String> result = stream.map(str -> "Truman_" + str)
.reduce((s1, s2) -> s1 + "|" + s2);
System.out.println(result.get());
  • sorted , 排序
1
2
3
Stream<String> stream = Stream.of("hello","world","briup");
//默认自然排序
stream.sorted().forEach(System.out::println);
1
2
3
Stream<String> stream = Stream.of("hello","world","briup");
//比较器排序,注意Lambda表达式中返回的值前加了符号 (自定义比较规则)
stream.sorted((o1, o2) -> - o1.compareTo(o2)).forEach(System.out::println);
  • limit ,返回 Stream 的前面 n 个元素
1
2
Stream<String> stream = Stream.of("test","javap","hello","world","java","tom","C","javascript");
stream.limit(5).forEach(System.out::println);
  • skip , 跳过前 n 个元素只要后面的元素
1
2
Stream<String> stream = Stream.of("test","javap","hello","world","java","tom","C","javascript");
stream.skip(5).forEach(System.out::println);
  • distinct ,去除重复数据
1
2
Stream<String> stream = Stream.of("test","test","hello","world","java","java","C","C");
stream.distinct().forEach(System.out::println)

静态方法

  • concat ,拼接两个流
1
2
3
4
Stream<String> stream1 = Stream.of("hello","world");
Stream<String> stream2 = Stream.of("tom","mary");
Stream<String> result = Stream.concat(stream1, stream2);
result.forEach(System.out::println);
  • Stream.generate ,通过Supplier接口,可以自己来控制数据的生成
1
2
3
public static<T> Stream<T> generate(Supplier<T> s) {
//...
}
1
2
3
4
5
6
7
8
9
final Random random = new Random();
//生成100个随机数,并输出
Stream.generate(()->random.nextInt(100))
.limit(100)
.forEach(System.out::println);
//生成100个随机数,并存放到集合中
List<Integer> list = Stream.generate(()->random.nextInt(100))
.limit(100)
.collect(Collectors.toList());
  • Stream.iterate
1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String[] args) {
// UnaryOperator:一元运算符
// 生成一个等差数列,公差为3,从0开始获取前10个数字
Stream.iterate(0, n -> n+3)
.limit(10)
.forEach(System.out::println);
}
}

IO流和Stream

在IO流中,也有方法,可以将读取到的数据转换为Stream对象

1
2
3
4
5
6
public class BufferedReader extends Reader {
//@since 1.8
public Stream<String> lines() {
//..
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main(String[] args) throws FileNotFoundException {
BufferedReader br = null;
br = new BufferedReader(new FileReader("src/dir/a.txt"));
int maxLen = br.lines() //io流转为Stream
.mapToInt(String::length) //每行字符串转换为它的字符长度
.max() //获取最大长度的数字
.getAsInt();//返回int类型的结果
System.out.println(maxLen);

}
}

并行流

Stream有串行和并行两种。串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多 个线程上同时执行。

创建并行Stream的两种方式:

  • 调用串行Stream的 parallel()方法,可以将其转换并行Stream
1
2
Stream<String> stream = Stream.of("truman", "peter", "parker");
Stream<String> parallel = stream.parallel();
  • 调用集合对象的parallelStream方法,之后获取并行Stream
1
2
3
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "truman","perter","parker");
Stream<String> stream = list.parallelStream();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main {
public static void main(String[] args) throws FileNotFoundException {
//生成200万个不同的字符串放到集合中
int maxx = 2000000;
List<UUID> collect = Stream.generate(UUID::randomUUID)
.limit(maxx)
.collect(Collectors.toList());

long startTime = System.currentTimeMillis();
// 串行Stream
// long count = collect.stream().sorted().count(); // 822ms
// 并行Stream
long count = collect.parallelStream().sorted().count(); // 381ms
long endTime = System.currentTimeMillis();
System.out.println("-----costTime: " + (endTime - startTime) + "ms");
}
}

可以看出,在数据量较大的特定场景下,并行Stream比串行Stream的效率要高一些

❤️❤️❤️忙碌的敲代码也不要忘了浪漫鸭!

立志欲坚不欲锐,成功在久不在速——宋.张孝祥 《论治体札子》💪