java1.8新特性_JDK1.8新特性(超详细)

Java函数式设计

实现方法:

@FunctionalInterface接口

Lambda语法

方法引用

接口default方法实现

一、lambda表达式

lambda表达式为匿名内部类的简写,类似于匿名内部类的语法糖;但又区别于匿名内部类(后文会讲解)。

匿名内部类特点:

基于多态(多数基于接口编程)

实现类无需名称

允许多个抽象方法

Lambda的语法简洁,没有面向对象复杂的束缚。

特点:

使用Lambda必须有接口,并且接口中有且仅有一个抽象方法。

只有当接口中的抽象方法存在且唯一时,才可以使用Lambda,但排除接口默认方法以及声明中覆盖Object的公开方法。

使用Lambda必须具有上下文推断。

也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。

备注:有且仅有一个抽象方法的接口,称为“函数式接口”。

标准格式由三部分组成:

一些参数

一个箭头

一段代码

格式:

(参数列表)->{一些重要方法的代码};

():接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数用逗号分隔。

->:传递:把参数传递给方法体{}

{}:重写接口的抽象方法的方法体

箭头操作符的左侧对应接口中参数列表(lambda表达式的参数列表),

箭头右侧为该抽象方法的实现(lambda表达式所需执行的功能)。

lambda优化写法:

可以推导的,都可以省略(凡是能根据上下文推导出来的内容,都可以省略不写):

(参数列表):括号中参数列表的数据类型,可以省略不写

(参数列表):括号中的参数只有一个,那么类型和()都可以省略

{一些代码} :如果{}中的代码只有一行,无论是否有返回值,都可以省略({},return,分号)

注意:要省略{},return,分号 必须一起省略

public class MyLambda {

public static void main(String[] args) {

new Thread(new Runnable() {

@Override

public void run() {

System.out.println(Thread.currentThread().getName()+"新线程创建了");

}

}).start();

//使用Lambda

new Thread(()->{

System.out.println(Thread.currentThread().getName()+"新线程创建了");

}).start();

//优化lambda

new Thread(()->System.out.println(Thread.currentThread().getName()+"新线程创建了")).start();

}

}

1.1无参数,无返回

()->System.out.println("hello lambda")

Cook:

public interface Cook {

public abstract void makeFood();

}

Demo01Cook:

public class Demo01Cook {

public static void main(String[] args) {

invokeCook(new Cook() {

public void makeFood() {

System.out.println("做饭。。。");

}

});

//使用Lambda

invokeCook(()->{

System.out.println("做饭。。。");

});

//优化lambda

invokeCook(()-> System.out.println("做饭。。。"));

}

public static void invokeCook(Cook cook){

cook.makeFood();

}

}

1.2 有参数,无返回

x->System.out.println("hello lambda")

import java.util.function.Consumer;

/**

* Created by hongcaixia on 2019/10/31.

*/

public class Demo2 {

public static void main(String[] args) {

Consumer consumer = x-> System.out.println(x);

consumer.accept("有参数无返回");

}

}

1.3 有参数,有返回

(Person p1,Person p2)->{ return p1.getAge()-p2.getAge(); }

package com.hcx.lambda;

import java.util.Arrays;

import java.util.Comparator;

/**

* Created by hongcaixia on 2019/10/26.

*/

public class MyArrays {

public static void main(String[] args) {

Person[] arr = {new Person("陈奕迅",40),

new Person("钟汉良",39),

new Person("杨千嬅",38)};

//对年龄进行排序

Arrays.sort(arr, new Comparator() {

@Override

public int compare(Person o1, Person o2) {

return o1.getAge()-o2.getAge();

}

});

//使用lambda

Arrays.sort(arr,(Person p1,Person p2)->{

return p1.getAge()-p2.getAge();

});

//优化lambda

Arrays.sort(arr,(p1,p2)->p1.getAge()-p2.getAge());

Arrays.sort(arr,Comparator.comparing(Person::getAge));

for (Person p:arr){

System.out.println(p);

}

}

}

二、函数式接口

2.1概念

函数式接口:有且仅有一个抽象方法的接口。

函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。

2.2格式

修饰符 interface 接口名称 {

public abstract 返回值类型 方法名称(可选参数信息);

// 其他非抽象方法内容

}

抽象方法的 public abstract 是可以省略的:

public interface MyFunctionalInterface {

void myMethod();

}

2.3@FunctionalInterface注解

与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。用于函数式接口类型声明的信息注解类型,这些接口的实例被Lambda表达式、方法引用或构造器引用创建。函数式接口只能有一个抽象方法,但排除接口默认方法以及声明中覆盖Object的公开方法:

@FunctionalInterface注解源码注释:

2f11f3c9e64a

image.png

2f11f3c9e64a

object下的public方法.png

源码注释:该注解是定义一个lambda表达式的基础, 即是否是函数式接口可以标注也可以不标注。

函数式接口必须有一个精确的抽象方法,但排除以下两种:

①java8的default,他们不属于抽象方法。

②如果该接口声明的一个抽象方法覆盖了任意一个object的方法,也排除掉。

@FunctionalInterface

public interface MyFunctionInterface {

//唯一个抽象方法

void method();

//排除用default修饰的方法

default void method1(){

}

//排除Ojbect下的方法

int hashCode();

}

@FunctionalInterface

public interface MyFunctionalInterface {

void myMethod();

}

注意:@FuncationlInterface不能标注在注解、类以及枚举上。一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。

MyFunctionalInterface:

@FunctionalInterface

public interface MyFunctionalInterface {

//定义一个抽象方法

public abstract void method();

}

MyFunctionalInterfaceImpl:

public class MyFunctionalInterfaceImpl implements MyFunctionalInterface{

@Override

public void method() {

}

}

Demo:

/*

函数式接口的使用:一般可以作为方法的参数和返回值类型

*/

public class Demo {

//定义一个方法,参数使用函数式接口MyFunctionalInterface

public static void show(MyFunctionalInterface myInter){

myInter.method();

}

public static void main(String[] args) {

//调用show方法,方法的参数是一个接口,所以可以传递接口的实现类对象

show(new MyFunctionalInterfaceImpl());

//调用show方法,方法的参数是一个接口,所以我们可以传递接口的匿名内部类

show(new MyFunctionalInterface() {

@Override

public void method() {

System.out.println("使用匿名内部类重写接口中的抽象方法");

}

});

//调用show方法,方法的参数是一个函数式接口,所以我们可以Lambda表达式

show(()->{

System.out.println("使用Lambda表达式重写接口中的抽象方法");

});

//简化Lambda表达式

show(()-> System.out.println("使用Lambda表达式重写接口中的抽象方法"));

}

}

案例:

分析:函数式接口和普通方法的差异

import java.util.function.Supplier;

/**

* Created by hongcaixia on 2019/11/3.

*/

public class MyTest {

public static void main(String[] args) {

print1("hello,world");

print2(()->"hello world");

}

public static void print1(String message){

System.out.println(message);

}

public static void print2(Supplier message){

System.out.println(message.get());

}

}

以上代码会得到同样的结果,但使用了函数式接口相当于把数据进行了延迟加载。使用函数式接口,数据并没有完全确定,等到真正调用的时候才确定,类似推模型。

Demo01Logger:

public class Demo01Logger {

//定义一个根据日志的级别,显示日志信息的方法

public static void showLog(int level, String message){

//对日志的等级进行判断,如果是1级别,那么输出日志信息

if(level==1){

System.out.println(message);

}

}

public static void main(String[] args) {

//定义三个日志信息

String msg1 = "Hello";

String msg2 = "World";

String msg3 = "Java";

//调用showLog方法,传递日志级别和日志信息

showLog(2,msg1+msg2+msg3);

}

}

Demo02Lambda:

/*

使用Lambda优化日志案例

Lambda的特点:延迟加载

Lambda的使用前提,必须存在函数式接口

*/

public class Demo02Lambda {

//定义一个显示日志的方法,方法的参数传递日志的等级和MessageBuilder接口

public static void showLog(int level, MessageBuilder mb){

//对日志的等级进行判断,如果是1级,则调用MessageBuilder接口中的builderMessage方法

if(level==1){

System.out.println(mb.builderMessage());

}

}

public static void main(String[] args) {

//定义三个日志信息

String msg1 = "Hello";

String msg2 = "World";

String msg3 = "Java";

//调用showLog方法,参数MessageBuilder是一个函数式接口,所以可以传递Lambda表达式

/*showLog(2,()->{

//返回一个拼接好的字符串

return msg1+msg2+msg3;

});*/

showLog(1,()->{

System.out.println("不满足条件不执行");

//返回一个拼接好的字符串

return msg1+msg2+msg3;

});

}

}

MessageBuilder:

@FunctionalInterface

public interface MessageBuilder {

//定义一个拼接消息的抽象方法,返回被拼接的消息

public abstract String builderMessage();

}

分析:使用Lambda表达式作为参数传递,仅仅是把参数传递到showLog方法中,只有满足条件,日志的等级是1级才会调用接口MessageBuilder中的方法builderMessage,才会进行字符串的拼接;

如果条件不满足,日志的等级不是1级,那么MessageBuilder接口中的方法builderMessage也不会执行,所以拼接字符串的代码也不会执行,所以不会存在性能的浪费

2.4使用函数式接口作为方法的参数

Demo01Runnable:

public class Demo01Runnable {

//定义一个方法startThread,方法的参数使用函数式接口Runnable

public static void startThread(Runnable run){

//开启多线程

new Thread(run).start();

}

public static void main(String[] args) {

//调用startThread方法,方法的参数是一个接口,那么我们可以传递这个接口的匿名内部类

startThread(new Runnable() {

@Override

public void run() {

System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了");

}

});

//调用startThread方法,方法的参数是一个函数式接口,所以可以传递Lambda表达式

startThread(()->{

System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了");

});

//优化Lambda表达式

startThread(()->System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了"));

}

}

2.5使用函数式接口作为方法的返回值

Demo02Comparator:

import java.util.Arrays;

import java.util.Comparator;

/*

如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。

当需要通过一个方法来获取一个java.util.Comparator接口类型的对象作为排序器时,就可以调该方法获取。

*/

public class Demo02Comparator {

//定义一个方法,方法的返回值类型使用函数式接口Comparator

public static Comparator getComparator(){

//方法的返回值类型是一个接口,那么我们可以返回这个接口的匿名内部类

/*return new Comparator() {

@Override

public int compare(String o1, String o2) {

//按照字符串的降序排序

return o2.length()-o1.length();

}

};*/

//方法的返回值类型是一个函数式接口,所以我们可以返回一个Lambda表达式

/*return (String o1, String o2)->{

//按照字符串的降序排序

return o2.length()-o1.length();

};*/

//继续优化Lambda表达式

return (o1, o2)->o2.length()-o1.length();

}

public static void main(String[] args) {

//创建一个字符串数组

String[] arr = {"a","bb","ccc","dddd"};

//输出排序前的数组

System.out.println(Arrays.toString(arr));

//调用Arrays中的sort方法,对字符串数组进行排序

Arrays.sort(arr,getComparator());

//输出排序后的数组

System.out.println(Arrays.toString(arr));

}

}

每次都要声明一个接口,写一个抽象方法,然后再用这个接口作为参数去用lambda实现。。。当果不需要!这个新特性就是为了我们使用简单的呀,所以java已经内置了一堆函数式接口了。

先来个表格整体概览一下常用的一些:

函数式接口

参数类型

返回类型

用途

Supplier 供给型

T

返回类型为T的对象,方法:T get()

Consumer消费型

T

void

对类型为T的对象应用操作,方法:void accept(T t)

Predicate 断定型

T

boolean

确定类型为T的对象是否满足某种约束,返回布尔值,方法:boolean test(T t)

Function函数型

T

R

对类型为T的对象应用操作,并返回R类型的对象,方法:R apply(T,t)

2.6常用函数式接口

①提供类型:Supplier接口

特点:只出不进,作为方法/构造参数、方法返回值

java.util.function.Supplier接口仅包含一个无参的方法:T get()。

用来获取一个泛型参数指定类型的对象数据。

Supplier接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据

import java.util.function.Supplier;

/**

* Created by hongcaixia on 2019/10/29.

*/

public class MySupplier {

public static String getString(Supplier supplier){

return supplier.get();

}

public static void main(String[] args) {

getString(new Supplier() {

@Override

public String get() {

return null;

}

});

String s = getString(()->"Eason");

System.out.println(s);

}

}

GetMax:

import java.util.function.Supplier;

/**

* Created by hongcaixia on 2019/10/29.

*/

public class GetMax {

public static int getMaxNum(Supplier supplier){

return supplier.get();

}

public static void main(String[] args) {

int[] arr = {-1,0,1,2,3};

int maxValue = getMaxNum(()->{

int max = arr[0];

for(int i=0;i

if(arr[i]>max){

max = arr[i];

}

}

return max;

});

System.out.println("数组元素的最大值是:"+maxValue);

}

}

②消费类型:Consumer接口

特点:只进不出,作为方法/构造参数

java.util.function.Consumer接口则正好与Supplier接口相反,

它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。

Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。

Consumer接口是一个消费型接口,泛型执行什么类型,就可以使用accept方法消费什么类型的数据

至于具体怎么消费(使用),需要自定义

import java.util.function.Consumer;

/**

* Created by hongcaixia on 2019/10/29.

*/

public class MyConsumer {

public static void method(String name, Consumer consumer){

consumer.accept(name);

}

public static void main(String[] args) {

method("小哇", new Consumer() {

@Override

public void accept(String s) {

System.out.println("s是:"+s);

}

});

method("小哇",(name)->{

String s = new StringBuffer(name).reverse().toString();

System.out.println("s是:"+s);

});

}

}

andThen:

Consumer接口的默认方法andThen

作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,在对数据进行消费

import java.util.function.Consumer;

/**

* Created by hongcaixia on 2019/10/30.

*/

public class AndThen {

public static void method(String s, Consumer consumer1,Consumer consumer2){

// consumer1.accept(s);

// consumer2.accept(s);

//使用andThen方法,把两个Consumer接口连接到一起,在消费数据

//con1连接con2,先执行con1消费数据,在执行con2消费数据

consumer1.andThen(consumer2).accept(s);

}

public static void main(String[] args) {

method("Hello",

(t)-> System.out.println(t.toUpperCase()), //消费方式:把字符串转换为大写输出

(t)-> System.out.println(t.toLowerCase()));//消费方式:把字符串转换为小写输出

method("Hello", new Consumer() {

@Override

public void accept(String s) {

System.out.println(s.toUpperCase());

}},new Consumer() {

@Override

public void accept(String s1) {

System.out.println(s1.toUpperCase());

}

});

}

}

按照格式“姓名:XX。性别:XX。”的格式将信息打印

要求将打印姓名的动作作为第一个Consumer接口的Lambda实例,

将打印性别的动作作为第二个Consumer接口的Lambda实例,

将两个Consumer接口按照顺序“拼接”到一起。

public class DemoTest {

//定义一个方法,参数传递String类型的数组和两个Consumer接口,泛型使用String

public static void printInfo(String[] arr, Consumer con1,Consumer con2){

//遍历字符串数组

for (String message : arr) {

//使用andThen方法连接两个Consumer接口,消费字符串

con1.andThen(con2).accept(message);

}

}

public static void main(String[] args) {

//定义一个字符串类型的数组

String[] arr = { "陈奕迅,男", "钟汉良,男", "胡歌,男" };

//调用printInfo方法,传递一个字符串数组,和两个Lambda表达式

printInfo(arr,(message)->{

//消费方式:对message进行切割,获取姓名,按照指定的格式输出

String name = message.split(",")[0];

System.out.print("姓名: "+name);

},(message)->{

//消费方式:对message进行切割,获取年龄,按照指定的格式输出

String age = message.split(",")[1];

System.out.println(";年龄: "+age+"。");

});

}

}

③断定类型:Predicate接口

特点:boolean类型判断,作为方法/构造参数

java.util.function.Predicate接口

作用:对某种数据类型的数据进行判断,结果返回一个boolean值

Predicate接口中包含一个抽象方法:

boolean test(T t):用来对指定数据类型数据进行判断的方法

结果:

符合条件,返回true

不符合条件,返回false

import java.util.function.Predicate;

/**

* Created by hongcaixia on 2019/10/30.

*/

public class MyPredicate1 {

public static boolean validateStr(String str, Predicate predicate){

return predicate.test(str);

}

public static void main(String[] args) {

String str = "abcdef";

boolean b = validateStr(str,string->str.length()>5);

System.out.println(b);

boolean b1 = validateStr(str, new Predicate() {

@Override

public boolean test(String s) {

return s.length()>5;

}

});

System.out.println(b1);

}

}

and方法:

Predicate接口中有一个方法and,表示并且关系,也可以用于连接两个判断条件

default Predicate and(Predicate super T> other) {

Objects.requireNonNull(other);

return (t) -> this.test(t) && other.test(t);

}

MyPredicateAnd :

import java.util.function.Predicate;

/**

* Created by hongcaixia on 2019/10/30.

*/

public class MyPredicateAnd {

public static boolean validateStr(String str, Predicate pre1,Predicate pre2){

// return pre1.test(str) && pre2.test(str);

return pre1.and(pre2).test(str);

}

public static void main(String[] args) {

String s = "abcdef";

boolean b = validateStr(s,str->str.length()>5,str->str.contains("a"));

System.out.println(b);

}

}

or方法:

Predicate接口中有一个方法or,表示或者关系,也可以用于连接两个判断条件

default Predicate or(Predicate super T> other) {

Objects.requireNonNull(other);

return (t) -> test(t) || other.test(t);

}

MyPredicateOr:

package com.hcx.lambda;

import java.util.function.Predicate;

/**

* Created by hongcaixia on 2019/10/30.

*/

public class MyPredicateOr {

public static boolean validateStr(String s, Predicate pre1,Predicate pre2){

// return pre1.test(s) || pre2.test(s);

return pre1.or(pre2).test(s);

}

public static void main(String[] args) {

String s = "acdef";

boolean b = validateStr(s,str->str.length()>5,str->str.contains("a"));

validateStr(s, new Predicate() {

@Override

public boolean test(String str) {

return s.length()>5;

}

}, new Predicate() {

@Override

public boolean test(String str) {

return s.contains("a");

}

});

System.out.println(b);

}

}

negate方法:

Predicate接口中有一个方法negate,也表示取反的意思

default Predicate negate() {

return (t) -> !test(t);

}

MyPredicateNegate:

import java.util.function.Predicate;

/**

* Created by hongcaixia on 2019/10/30.

*/

public class MyPredicateNegate {

public static boolean validateStr(String s, Predicate pre){

// return !pre.test(s);

return pre.negate().test(s);

}

public static void main(String[] args) {

String s = "acde";

boolean b = validateStr(s,str->str.length()>5);

System.out.println(b);

}

}

MyTest:

package com.hcx.lambda;

import java.util.ArrayList;

import java.util.function.Predicate;

/**

* Created by hongcaixia on 2019/10/30.

*/

public class MyTest {

public static ArrayList filter(String[] arr, Predicate pre1, Predicate pre2) {

ArrayList list = new ArrayList<>();

for (String s : arr) {

boolean b = pre1.and(pre2).test(s);

if (b) {

list.add(s);

}

}

return list;

}

public static void main(String[] args) {

String[] array = {"迪丽热巴,女", "古力娜扎,女", "佟丽娅,女", "赵丽颖,女"};

ArrayList list = filter(array,

s -> s.split(",")[1].equals("女"),

s -> s.split(",")[0].length() == 4);

for(String s : list){

System.out.println(s);

}

}

}

④转换类型:Function接口

特点:有输入,有输出

java.util.function.Function接口用来根据一个类型的数据得到另一个类型的数据,

前者称为前置条件,后者称为后置条件。

Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。

使用的场景例如:将String类型转换为Integer类型。

package com.hcx.lambda;

import java.util.function.Function;

/**

* Created by hongcaixia on 2019/10/30.

*/

public class MyFunction {

public static void change(String str, Function function){

// Integer i = function.apply(str);

//自动拆箱 Integer自动转为int

int i = function.apply(str);

System.out.println(i);

}

public static void main(String[] args) {

String s = "1234";

change(s,str->Integer.parseInt(str));

int i = Integer.parseInt(s);

System.out.println(i);

}

}

andThen方法:

package com.hcx.lambda;

import java.util.function.Function;

/**

* 1.String转Integer,加10

* Function fun1 :Integer i = fun1.apply("123")+10;

* 2.Interger转String

* Function fun2 :String s = fun2.apply(i);

* Created by hongcaixia on 2019/10/31.

*/

public class MyFunctionTest {

public static void change(String str, Function fun1,Function fun2){

String string = fun1.andThen(fun2).apply(str);

System.out.println(string);

}

public static void main(String[] args) {

change("123",str->Integer.parseInt(str)+10,i->i+"");

}

}

自定义函数模型拼接Demo:

package com.hcx.lambda;

import java.util.function.Function;

/**

* String str = "赵丽颖,20";

* 1.将字符串截取数字年龄部分,得到字符串;

* 2.将上一步的字符串转换成为int类型的数字;

* 3.将上一步的int数字累加100,得到结果int数字。

* Created by hongcaixia on 2019/10/31.

*/

public class MyFunctionTest2 {

public static int change(String str, Function fun1,Function fun2,

Function fun3){

return fun1.andThen(fun2).andThen(fun3).apply(str);

}

public static void main(String[] args) {

int num = change("赵丽颖,32",str->str.split(",")[1],

str->Integer.parseInt(str),

i->i+100);

System.out.println(num);

}

}

注意:使用匿名内部类会编译后会多产生一个类,而使用lambda,底层是invokedynamic指令,不会有多余的类

三、方法引用

若lambda体中的内容,有方法已经实现了,则可以使用方法引用。方法引用是对lambda的简化

MyPrintable:

public interface MyPrintable {

void print(String str);

}

DemoPrint:

public class DemoPrint {

private static void printString(MyPrintable data){

data.print("Hello,World");

}

public static void main(String[] args) {

printString(s->System.out.println(s));

printString(new MyPrintable() {

@Override

public void print(String str) {

System.out.println(str);

}

});

}

}

改进:

public class DemoPrint {

private static void printString(MyPrintable data){

data.print("Hello,World");

}

public static void main(String[] args) {

//printString(s->System.out.println(s));

printString(System.out::println);

}

}

方法引用

双冒号::为引用运算符,而它所在的表达式被称为方法引用。

如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。

三种格式:

对象::实例方法名

类::静态方法名

类::实例方法名

分析

Lambda表达式写法: s -> System.out.println(s);

方法引用写法: System.out::println

以上两种写法完全等效:

第一种:拿到参数之后通过Lambda传递给 System.out.println 方法去处理。

第二种:直接让 System.out 中的 println 方法来取代Lambda。

注意:lambda体中调用的方法的参数列表和返回值类型要与函数式接口的抽象方法的参数列表与返回值类型一致。

Lambda 中 传递的参数一定是方法引用中的那个方法可以接收的类型,否则会抛出异常

2f11f3c9e64a

image.png

3.1 通过对象名引用成员方法

@Test

public void test(){

Person person = new Person();

Supplier supplier =() -> person.getName();

System.out.println(supplier.get());

Supplier supplier1 = person::getAge;

System.out.println(supplier1.get());

}

MyPrintable:

public interface MyPrintable {

void print(String str);

}

MethodReadObj:

public class MethodReadObj {

public void printUpperCaseString(String str){

System.out.println(str.toUpperCase());

}

}

MethodReference1:

public class MethodReference1 {

public static void printString(MyPrintable p){

p.print("hello");

}

public static void main(String[] args) {

printString(str-> {

MethodReadObj methodReadObj = new MethodReadObj();

methodReadObj.printUpperCaseString(str);

});

/**

* 使用方法引用:

* 1.MethodReadObj对象已经存在

* 2.成员方法printUpperCaseString已经存在

* 所以可以使用对象名引用成员方法

*/

MethodReadObj methodReadObj = new MethodReadObj();

printString(methodReadObj::printUpperCaseString);

}

}

3.2 通过类名称引用静态方法

类已经存在,静态方法已经存在,则可以通过类名直接引用静态成员方法

@Test

public void test1(){

Comparator comparator = (x,y)->Integer.compare(x,y);

Comparator comparator1 = Integer::compare;

}

MyCalc:

public interface MyCalc {

int calc(int num);

}

MethodRerference2:

public class MethodRerference2 {

public static int method(int num,MyCalc c){

return c.calc(num);

}

public static void main(String[] args) {

int number = method(-10, num -> Math.abs(num));

int number1 = method(-10, Math::abs);

System.out.println(number);

System.out.println(number1);

}

}

通过类名引用实例方法

@Test

public void test2(){

BiPredicate biPredicate = (x,y) -> x.equals(y);

BiPredicate biPredicate1 = String::equals;

}

注意:以上这种情况,需要满足一定的条件:lambda表达式中第一个参数是lambda体中的调用者,第二个参数是lambda体中的参数

3.3 通过super引用成员方法

如果存在继承关系,当Lambda中需要出现super调用时,也可以使用方法引用进行替代。

MyMeet :

@FunctionalInterface

public interface MyMeet {

void meet();

}

Parent:

public class Parent {

public void hello(){

System.out.println("hello,I'm Parent");

}

}

Child:

public class Child extends Parent{

@Override

public void hello() {

System.out.println("hello,I'm Child");

}

public void method(MyMeet myMeet){

myMeet.meet();

}

public void show(){

method(()->{

Parent parent = new Parent();

parent.hello();

});

//使用super关键字调用父类

method(()->super.hello());

/**

* 使用方法引用:使用super引用父类的成员方法:

* 1.super已经存在

* 2.父类的成员方法hello已经存在

* 可以直接使用super引用父类的成员方法

*/

method(super::hello);

}

public static void main(String[] args) {

new Child().show();

}

}

3.4 通过this引用成员方法

this代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么可以使用 this::成员方法的格式来使用方法引用

MyWallet:

@FunctionalInterface

public interface MyWallet {

void buy();

}

BuyThing:

public class BuyThing {

public void buyCar(){

System.out.println("买了一辆别摸我");

}

public void getSalary(MyWallet myWallet){

myWallet.buy();

}

public void method(){

getSalary(()->this.buyCar());

/**

* 1.this已经存在

* 2.本类的成员方法buyCar已经存在

* 所以可以直接使用this引用本类的成员方法buyCar

*/

getSalary(this::buyCar);

}

public static void main(String[] args) {

new BuyThing().method();

}

}

3.5 类的构造器引用

由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用 类名称::new 的格式表示。

public void test3(){

Supplier personSupplier = ()->new Person();

//构造器引用 此处引用的是无参构造器 ,因为Supplier中的get方法没有参数

Supplier personSupplier1 = Person::new;

}

public void test4(){

Function personFunction = (x)->new Person(x);

//构造器引用 此处引用的是整型的一个参数的构造器 ,因为Function中的apply方法只有一个参数

Function personFunction1 = Person::new;

}

注意:需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表一致

Person:

@Data

@NoArgsConstructor

@AllArgsConstructor

public class Person {

private String name;

}

PersonBuilder:

@FunctionalInterface

public interface PersonBuilder {

//根据传递的姓名,创建Perosn对象

Person builderPerson(String name);

}

Demo:

public class Demo {

//传递姓名和PersonBuilder接口,通过姓名创建Person对象

public static void printName(String name,PersonBuilder personBuilder){

Person person = personBuilder.builderPerson(name);

System.out.println(person.getName());

}

public static void main(String[] args) {

printName("hongcaixia",str->new Person(str));

/**

* 使用方法引用:

* 1.构造方法new Person(String name)已知

* 2.创建对象已知

* 可以使用Person引用new创建对象

*/

printName("hongcaixia",Person::new);

}

}

3.6 数组的构造器引用

数组也是 Object 的子类对象,所以同样具有构造器,只是语法稍有不同。

格式:Type[]::new

public void test5() {

Function function = (x) -> new String[x];

String[] strings = function.apply(10);

Function function1 = String[]::new;

String[] strings1 = function1.apply(10);

}

ArrayBuilder:

@FunctionalInterface

public interface ArrayBuilder {

//创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组

int[] builderArray(int length);

}

DemoArrayBuilder:

public class DemoArrayBuilder {

public static int[] createArray(int length,ArrayBuilder arrayBuilder){

return arrayBuilder.builderArray(length);

}

public static void main(String[] args) {

int[] arr1 = createArray(5,length -> new int[length]);

System.out.println(arr1.length);

/**

* 1.已知创建的是int[]数组

* 2.创建的数组长度也是已知的

* 使用方法引用,int[]引用new,根据参数传递的长度创建数组

*/

int[] arr2 = createArray(10,int[]::new);

System.out.println(Arrays.toString(arr2));

System.out.println(arr2.length);

}

}

四、StreamAPI

流是数据渠道,用于操作数据源(集合、数组等)生成的元素序列。

package com.hcx.stream;

import java.util.ArrayList;

import java.util.List;

/**

* Created by hongcaixia on 2019/10/31.

*/

public class MyStream1 {

public static void main(String[] args) {

List list = new ArrayList<>();

list.add("陈奕迅");

list.add("陈小春");

list.add("钟汉良");

list.add("陈七");

list.add("陈伟霆");

//筛选陈开头,名字是三个字的

List chenList = new ArrayList<>();

for(String item : list){

if(item.startsWith("陈")){

chenList.add(item);

}

}

List threeList = new ArrayList<>();

for(String item : chenList){

if(item.length()==3){

threeList.add(item);

}

}

//遍历输出符合条件的

for(String item : threeList){

System.out.println(item);

}

System.out.println("=====================");

//使用Stream流

list.stream().filter(str->str.startsWith("陈"))

.filter(str->str.length()==3)

.forEach(str-> System.out.println(str));

}

}

Stream(流)是一个来自数据源的元素队列

元素是特定类型的对象,形成一个队列。

数据源流的来源。 可以是集合,数组等。

Stream自己不会存储元素,而是按需计算。

Stream不会改变源对象,并且能返回一个持有结果的新流

Stream操作是延迟操作,意味着他们会等到需要结果的时候才执行

和以前的Collection操作不同, Stream操作还有两个基础的特征:

Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent

style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。

内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。

Stream操作的三个步骤:

创建Stream:一个数据源(如集合、数组),获取一个流

中间操作:一个操作链,对数据源的数据进行处理

终止操作:一个终止操作,执行中间操作链并产生结果

注意:“Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)。

import java.util.*;

import java.util.function.Predicate;

/**

* Created by hongcaixia on 2019/10/31.

*/

public class MyPredicate {

public static void main(String[] args) {

List nums = Arrays.asList(10,20,3,-5,-8);

Collection positiveNum = filter(nums,num->num>0);

Collection negativeNum = filter(nums,num->num<0);

System.out.println(positiveNum);

System.out.println(negativeNum);

}

private static Collection filter(Collection source, Predicate predicate){

List list = new ArrayList<>(source);

Iterator iterator = list.iterator();

while (iterator.hasNext()){

E element = iterator.next();

if(!predicate.test(element)){

iterator.remove();

}

}

return Collections.unmodifiableList(list);

}

}

4.1获取流

java.util.stream.Stream是Java8新加入的常用的流接口。(不是函数式接口)

获取一个流有以下几种常用的方式:

①根据Collection获取流

java.util.Collection接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。

stream():获取串行流

parallelStream():获取并行流

package com.hcx.stream;

import java.util.*;

import java.util.stream.Stream;

/**

* Created by hongcaixia on 2019/10/31.

*/

public class GetStreamFromCollection {

public static void main(String[] args) {

List list = new ArrayList<>();

Stream stream1 = list.stream();

Set set = new HashSet<>();

Stream stream2 = set.stream();

Vector vector = new Vector<>();

Stream stream3 = vector.stream();

}

}

②根据Map获取流

java.util.Map 接口不是Collection的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况:

package com.hcx.stream;

import java.util.HashMap;

import java.util.Map;

import java.util.stream.Stream;

/**

* Created by hongcaixia on 2019/10/31.

*/

public class GetStreamFromMap {

public static void main(String[] args) {

Map map = new HashMap<>();

Stream> stream1 = map.entrySet().stream();

Stream stream2 = map.keySet().stream();

Stream stream3 = map.values().stream();

}

}

③根据数组获取流

如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法of :

package com.hcx.stream;

import java.util.stream.Stream;

/**

* Created by hongcaixia on 2019/10/31.

*/

public class GetStreamFromArray {

public static void main(String[] args) {

String[] array = {"陈奕迅","钟汉良","杨千嬅"};

Stream stream = Stream.of(array);

}

}

④获取无限流

public void test6() {

Stream stream = Stream.iterate(0, x -> x + 2);

}

public void test7() {

Stream stream = Stream.generate(() -> Math.random());

}

注意:of 方法的参数是一个可变参数,所以支持数组。

总结:

所有的 Collection 集合都可以通过stream默认方法获取流;

Stream 接口的静态方法of可以获取数组对应的流

4.2 流常用方法

方法可以被分成两种:

延迟方法:返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)

终结方法:返回值类型不再是 Stream接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调用。终结方法包括 count 和 forEach 方法。

①逐一处理:forEach

void forEach(Consumer super T> action);

该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理。

Consumer是一个消费型的函数式接口,可传递lambda表达式,消费数据

package com.hcx.stream;

import java.util.stream.Stream;

/**

* Created by hongcaixia on 2019/10/31.

*/

public class StreamForEach {

public static void main(String[] args) {

Stream stream = Stream.of("张三", "李四", "王五");

stream.forEach(str-> System.out.println(str));

}

}

②过滤:filter

可以通过 filter 方法将一个流转换成另一个子集流。方法签名:

Stream filter(Predicate super T> predicate);

该接口接收一个Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。

java.util.stream.Predicate函数式接口唯一的抽象方法为: boolean test(T t);

该方法将会产生一个boolean值结果,代表指定的条件是否满足:

如果结果为true,那么Stream流的filter方法将会留用元素;

如果结果为false,那么filter 方法将会舍弃元素。

public class StreamFilter {

public static void main(String[] args) {

Stream stream = Stream.of("陈奕迅", "陈伟霆", "陈七", "钟汉良");

Stream stream1 = stream.filter(str -> str.startsWith("陈"));

stream1.forEach(str-> System.out.println(str));

}

}

注意:Stream属于管道流,只能被消费一次

Stream流调用完毕方法,数据就回流到下一个Steam上,

而这时第一个Stream流已经使用完毕,就会关闭了,

所以第一个Stream流就不能再调用方法了。

③映射:map

接收lambda,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。即将流中的元素映射到另一个流中。

方法签名:

Stream map(Function super T, ? extends R> mapper);

该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。

java.util.stream.Function 函数式接口,其中唯一的抽象方法为: R apply(T t);

这可以将一种T类型转换成为R类型,而这种转换的动作,就称为“映射”。

public class StreamMap {

public static void main(String[] args) {

Stream stream = Stream.of("1", "2", "3");

Stream integerStream = stream.map(str -> Integer.parseInt(str));

integerStream.forEach(i-> System.out.println(i));

}

}

public void test8() {

Person person = new Person("hcx",24);

Person person1 = new Person("hcx2",24);

List list = new ArrayList<>();

list.add(person);

list.add(person1);

list.stream().map(Person::getName).forEach(System.out::println);

}

④flatMap

接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。即对流扁平化处理,浅显一点解释就是把几个小的list转换到一个大的list

如:[['a','b'],['c','d']] - > ['a','b','c','d']

如果我们使用常用的map()方法获取的lowercaseWords数据结构为:[['a','b','c'],['m','d','w'],['k','e','t']]。如果我们要得到如:['a','b','c','m','d','w','k','e','t']这样数据结构的数据,就需要使用flatMap()方法。

public void test9() {

List list = Arrays.asList("a","b","c");

Stream> streamStream = list.stream().map(MethodReference::filterCharacter);

streamStream.forEach((stream)->stream.forEach(System.out::println));

//使用flatMap

Stream characterStream = list.stream().flatMap(MethodReference::filterCharacter);

characterStream.forEach(System.out::println);

}

public static Stream filterCharacter(String str){

List list = new ArrayList<>();

for(Character c : str.toCharArray()){

list.add(c);

}

return list.stream();

}

⑤规约reduce

将流中元素反复结合起来,得到一个值

import java.util.stream.Stream;

/**

* Created by hongcaixia on 2019/10/31.

*/

public class StreamReduce {

public static void main(String[] args) {

sum(1,2,3,4,5);

}

private static void sum(Integer... nums){

Stream.of(nums).reduce(Integer::sum).ifPresent(System.out::println);

}

}

@Test

public void test10() {

List list = Arrays.asList(1,2,3,4,5);

Integer sum = list.stream().reduce(0,(x,y)->x+y);

System.out.println(sum);

}

⑥统计个数:count

终结方法

流提供 count方法来数一数其中的元素个数

该方法返回一个long值代表元素个数:

package com.hcx.stream;

import java.util.ArrayList;

import java.util.stream.Stream;

/**

* Created by hongcaixia on 2019/10/31.

*/

public class StreamCount {

public static void main(String[] args) {

ArrayList list = new ArrayList<>();

list.add(1);

list.add(2);

list.add(3);

Stream stream = list.stream();

long count = stream.count();

System.out.println(count);

}

}

⑦取用前几个:limit

limit 方法可以对流进行截取,只取用前n个。方法签名:

Stream limit(long maxSize);

参数是一个 long型,如果集合当前长度大于参数则进行截取;否则不进行操作

延迟方法,只是对流中的元素进行截取,返回的是一个新的流,还可以继续调用Stream中的其他方法

public class StreamLimit {

public static void main(String[] args) {

String[] str = {"1","2","3","4","5"};

Stream stream = Stream.of(str);

Stream limitStream = stream.limit(3);

limitStream.forEach(string-> System.out.println(string));

}

}

⑧跳过前几个:skip

如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流:

Stream skip(long n);

如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流

public class StreamSkip {

public static void main(String[] args) {

String[] str = {"1","2","3","4","5"};

Stream stream = Stream.of(str);

Stream skipStream = stream.skip(3);

skipStream.forEach(string-> System.out.println(string));

}

}

⑨组合:concat

如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat

static Stream concat(Stream extends T> a, Stream extends T> b)

注意:这是一个静态方法,与 java.lang.String 当中的 concat 方法不同

public class StreamConcat {

public static void main(String[] args) {

Stream stream1 = Stream.of("陈奕迅", "陈伟霆", "陈七", "钟汉良");

String[] arr = {"1","2","3"};

Stream stream2 = Stream.of(arr);

Stream concatStream = Stream.concat(stream1, stream2);

concatStream.forEach(str-> System.out.println(str));

}

}

⑩排序:sorted

sorted() 自然排序

sorted(Comparator com) 定制排序

allMatch 检查是否匹配所有元素

anyMatch 检查是否至少匹配一个元素

noneMatch 检查是否没有匹配所有元素

findFirst 返回第一个元素

findAny 返回当前流中的任意元素

count 返回流中元素的总个数

max 返回流中最大值

min 返回流中最小值

public class StreamSort {

public static void main(String[] args) {

Integer[] nums = {2,9,0,5,-10,90};

Stream numsStream = Stream.of(nums);

Stream sortedStram = numsStream.sorted();

sortedStram.forEach(num -> System.out.println(num));

}

}

⑪Collect

将流转换为其他形式。接收一个Collector的实现,用于给stream中元素做汇总的方法

import java.util.LinkedList;

import java.util.List;

import java.util.stream.Collectors;

import java.util.stream.Stream;

/**

* Created by hongcaixia on 2019/10/31.

*/

public class StreamCollect {

public static void main(String[] args) {

List list = Stream.of(1,2,3,4,5).collect(Collectors.toList());

List list1 = Stream.of(1,2,3,4,5).collect(LinkedList::new,List::add,List::addAll);

System.out.println(list.getClass());//class java.util.ArrayList

System.out.println(list1.getClass());//class java.util.LinkedList

}

}

@Test

public void test11() {

Person person = new Person("hcx",24);

Person person1 = new Person("hcx2",24);

List list = new ArrayList<>();

list.add(person);

list.add(person1);

List collect = list.stream().map(Person::getName).collect(Collectors.toList());

}

4.3 并行流和顺序流

并行流是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。

通过parallel()与sequential()可以实现并行流和顺序流之间的切换。

2f11f3c9e64a

Fork/Join框架.png

Fork/Join框架:在必要的情况下,将一个大任务进行拆分(fork)成若干小任务(拆到不可再拆时),再将一个个小任务运算的结果进行join汇总。

@Test

public void test12() {

//顺序流

LongStream.rangeClosed(0,100).reduce(0,Long::sum);

//并行流

long reduce = LongStream.rangeClosed(0, 100).parallel().reduce(0, Long::sum);

//5050

System.out.println(reduce);

}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值