函数式接口:
概念:
函数式接口在java中指:有且只有一个抽象方法的接口.
函数式接口,即使用于函数式编程场景的接口,而java中的函数式编程体现的就是Lambda,所以函数式接口就是
适用于Lambda使用的接口,只要有确保接口中有且仅有一个抽象方法,java中的Lambda才能顺利的进行推导下
去.
格式:
只要保证接口中仅有一个抽象方法就可以.
修饰符 interface 接口名称{
public abstract 返回值类型 方法名称(可选择参数信息);
}
接口中抽象方法的:public abstract 可以省略.
1.@FunctionalInterface注解(只能用于函数式接口)
与
@Override
注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解:@FunctionalInterface
1.1自定义函数式接口
代码如下:
MyFuntionalInterface接口:
@FunctionalInterface
public interface MyFunctionalInterface {
void lu();
}
对于刚好定义好的接口可以定义作为方法的参数:
package com.heima.hanshushi;
//作为方法的参数
public class Demo01 {
//使用自定义的函数式接口作为方法参数
private static void liang(MyFunctionalInterface inter){
inter.lu();//调用自定义的函数式接口方法
}
public static void main(String[] args) {
//调用使用函数式接口的方法
//无参数,无返回值
liang(()->{
System.out.println("我执行了.");
});
}
}
常用的函数式接口
1.Supplier接口(产生一个数据)
java.util.function.Supplier<T>
接口仅包含一个无参的方法:T get()
。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
package com.heima.hanshushi2;
import java.util.function.Supplier;
//函数式接口
public class Demo04 {
//无参数有返回值
private static String getString(Supplier<String> a){
return a.get();
}
public static void main(String[] args) {
String msgA="Hello";
String msgB="World";
System.out.println(getString(()->{return msgA+msgB;}));
}
}
2.求数组的元素的最大值
题目:使用Supplier接口作为方法参数类型,通过Lambda表达式求出Int数组中的最大值,
提示:接口的泛型请使用java.lang.Integer类
package com.heima.hanshushi2;
import java.util.function.Supplier;
public class Demo05 {
private static void printMax(Supplier<Integer> a) {
int max = a.get();
System.out.println(max);
}
public static void main(String[] args) {
//定义一个数组
int[] array = {10, 23, 22, 4333, 4, 44, 3, 3, 3};
printMax(() -> {
int max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
}
return max;
});
}
}
3.Consumer接口(消费一个数据)
java.util.function.Consumer<T>
接口则正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。
图解
抽象方法:accept
Consumer接口中包含抽象方法void accept(T t),意为消费一个指定的泛型的数据.
package com.heima.hanshushi2;
import java.util.function.Consumer;
//消费一个数据
public class Demo06 {
private static void consumeraccept(Consumer<String> a){
a.accept("Hello");
}
public static void main(String[] args) {
consumeraccept((String s)->{
System.out.println(s);
});
consumeraccept(System.out::println);//方法的引用
}
}
4.格式化打印信息
练习:格式化打印信息
题目:字符串数组当中存有多条信息,请按照格式“
姓名:XX。性别:XX。
”的格式将信息打印出来。要求将打印姓名的动作作为第一个Consumer
接口的Lambda实例,将打印性别的动作作为第二个Consumer
接口的Lambda实例,将两个Consumer
接口按照顺序“拼接”到一起。
package com.heima.hanshushi2;
import java.util.function.Consumer;
//消费接口
public class Demo07 {
private static void printInfo(Consumer<String> a,Consumer<String> b,String[] array){
for (String s : array) {
a.andThen(b).accept(s);
}
}
public static void main(String[] args) {
String[] array={"迪丽热巴,女\", \"古力娜扎,女\", \"马尔扎哈,男"};
printInfo(s-> System.out.print("姓名:"+s.split(",")[0]),
s-> System.out.print(".性别:"+s.split(",")[1]+"."),array
);
}
}
自定义函数式接口
1.无参数无返回值
2.有参数有返回值
Lambda表达式
执行流程图:
1.Lambda执行图解bda的延迟性:
案例:
性能的浪费的日志案例:
public class Demo01Logger {
private static void log(int level, String msg) {
if (level == 1) {
System.out.println(msg);
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, msgA + msgB + msgC);
}
}
解释:这段代码存在问题,无论级别是否满足,作为Log方法的第二个参数,和第三个参数一定首先会被并如传入方法内.然后进行判断,如果不符合那就白写了.
先进的Lambda的写法:
(抽象方法的参数类型 变量)->{}
2.Lambda使用情况:
2.1作为参数返回值
其实是:函数式接口作为方法的参数类型
函数式接口作为方法的返回值类型.
代码演示:函数式接口作为方法的参数类型.
package com.heima.hanshushi2;
/*
例如java.lang.Runnable接口就是一个函数式接口,
假设有一个startThread方法使用该接口作为参数,
那么就可以使用Lambda进行传参。
这种情况其实和Thread类的构造方法参数为Runnable没有本质区别。
*/
public class Demo01 {
private static void startThread(Runnable task){
new Thread(task).start();
}
public static void main(String[] args) {
startThread(()->System.out.println("线程执行任务"));
}
}
代码演示:函数式接口作为方法的返回值类型.
package com.heima.hanshushi2;
import java.util.Arrays;
import java.util.Comparator;
/*
类似地,如果一个方法的返回值类型是一个函数式接口,
那么就可以直接返回一个Lambda表达式。
当需要通过一个方法来获取一个
java.util.Comparator接口类型的对象作为排序器时:
*/
public class Demo02 {
private static Comparator<String> lu(){
return (a,b)->b.length()-a.length();
}
public static void main(String[] args) {
String[]array={"asdalk","asdas","sasahd"};
System.out.println(Arrays.toString(array));
Arrays.sort(array,lu());
System.out.println(Arrays.toString(array));
}
}
2.2冗余的Lambda场景
package com.heima.hanshushi2;
public class Demo03 {
private static void lu(Printable printable){
//lu方法需要让接口中调用print方法,至于print方法怎么执行的不需要管
printable.print("你好啊");//调用接口的抽象方法才会执行Lambda表达中的解决方案
//调用方法print方式最终的效果是把"你好啊"打印在控制台上
}
public static void main(String[] args) {
lu((String s)-> System.out.println(s));
}
}
通过方法引用来改进上面代码:
public static void main(String[] args){
lu(System.out::println);
}
方法的引用:
格式:双冒号 : : 为引用运算符,而他所在的表达式被叫文方法的引用.
什么时候使用方法引用用来替代Lambda表达式(方法的引用使用的场景).
1.如果Lambda表达式指定的解决方案已经在另一个方法中实现了,那么这个时候就可以使用方法的引用来替代.
2.简而言之:如果lambda表达式中的解决方案,就是调用另一个方法,那么这个时候就是可以使用方法的引用来替代.
语义分析:
Lambda表达式写法:
s -> System.out.println(s);
方法引用写法:
System.out::println
第一种语义是指:拿到参数之后经Lambda之手,继而传递给
System.out.println
方法去处理。第二种等效写法的语义是指:直接让
System.out
中的println
方法来取代Lambda。两种写法的执行效果完全一样,而第二种方法引用的写法复用了已有方案,更加简洁。
1.通过对象名引用成员方法
格式: 对象::方法名
成员方法:liang
package com.heima.biji.day01;
//方法
public class MethodRefObject {
public void liang(String str){
System.out.println(str.toUpperCase());//打印大写
}
}
函数式接口:
package com.heima.biji.day01;
@FunctionalInterface
public interface Printable {
void print(String str);
}
那么当需要使用这个liang这个成员方法来替代Printable接口的Lambda时候,已经具有了MethodRefObject类对象的实例,则可以通过对象名引用成员方法.'
package com.heima.biji.day01;
public class Demo01 {
private static void lu(Printable a){
a.print("Hello");
}
public static void main(String[] args) {
MethodRefObject methodRefObject = new MethodRefObject();
lu(methodRefObject::liang);
}
}
2.通过类名称引用静态方法
格式: 类名::静态方法名
函数表达式
package com.heima.biji.day03;
@FunctionalInterface
public interface Lu {
int calc(int num);
}
Lambda表达式书写
package com.heima.biji.day03;
public class Demo01 {
private static void liang(int num,Lu lambda){
System.out.println(lambda.calc(num));
}
//通过Lambda表达式
public static void main(String[] args) {
liang(-10,(int num)->Math.abs(num));
}
}
方法的引用
package com.heima.biji.day03;
public class Demo02 {
private static void liang(int num,Lu lambda){
System.out.println(lambda.calc(num));
}
public static void main(String[] args) {
liang(-10,Math::abs);
}
}
//- Lambda表达式:n -> Math.abs(n)
//- 方法引用:Math::abs
2.1通过对象名引用成员方法
格式: 对象名::方法名
类:Assistant其中有成员方法lu如下:
package com.heima.biji.day02;
public class Assistant {
public void lu(String a){
System.out.println("帮忙处理下文件:"+a);
}
}
函数式接口:liang 有参数无返回值
package com.heima.biji.day02;
//函数式接口
public interface liang {
void help(String b);
}
通过对象名引用成员方法的使用场景代码为:
package com.heima.biji.day02;
public class Demo01 {
private static void work(liang c){
c.help("机密文件");
}
public static void main(String[] args) {
Assistant assistant = new Assistant();
work(assistant::lu);
}
}
//打印结果:帮忙处理下文件:机密文件
类名称引用静态方法:
题目:
假设有一个助理类Assistant
,其中含有成员方法dealFile
如下:
package com.heima.biji.day04;
public class StringUtils {
public static boolean isBlank(String str){
return str==null||"".equals(str.trim());
}
}
函数表达式:
package com.heima.biji.day04;
@FunctionalInterface
public interface lu {
boolean liang(String str);
}
通过对象名引用成员方法的使用场景代码为:
package com.heima.biji.day04;
public class StringUtils {
public static boolean isBlank(String str){
return str==null||"".equals(str.trim());
}
}
3.通过super引用父类的成员方法
格式: supper::父类方法名
函数式接口
package com.heima.biji.day05;
public interface lu {
void liang();
}
父类中成员方法sayHello内容
package com.heima.biji.day05;
public class Fu {
public void sayHello(){
System.out.println("Hello");
}
}
子类中内容
package com.heima.biji.day05;
public class Demo01 extends Fu{
//重写父类中的方法
@Override
public void sayHello() {
method(()->super.sayHello());
method(super::sayHello);
}
private static void method(lu lambda){
//类名称.抽象方法名
lambda.liang();
System.out.println("I love you");
}
public static void main(String[] args) {
}
}
注意:
-
Lambda表达式:
() -> super.sayHello()
-
方法引用:
super::sayHello
4.通过this引用成员方法
格式: this::本类方法名
package com.heima.biji.day06;
//
@FunctionalInterface
public interface Richable {
void buy();
}
package com.heima.biji.day06;
public class Husband1 {
private void buyHouse() {
System.out.println("买套房子");
}
private void marry(Richable lambda) {
lambda.buy();
}
public void beHappy() {
marry(this::buyHouse);
}
}
5.类的构造器的引用
格式: 类名::new
但是通过构造器引用,有更好的写法
public class Husband {
private void buyHouse() {
System.out.println("买套房子");
}
private void marry(Richable lambda) {
lambda.buy();
}
public void beHappy() {
marry(this::buyHouse);
}
}
在这个例子中,下面两种写法是等效的:
-
Lambda表达式:
() -> this.buyHouse()
-
方法引用:
this::buyHouse
6.数组构造器的引用
格式: 数据类型::new ===> 数据类型[] :: new
方法的引用书写
-
Lambda表达式:
length -> new int[length]
-
方法引用:
int[]::new
-
注意:
数组的构造器引用,可以和Java 8的Stream API结合,在一定程度上“解决”集合中toArray
方法的泛型擦除问题。
public class Demo12ArrayInitRef {
private static int[] initArray(int length, ArrayBuilder builder) {
return builder.buildArray(length);
}
public static void main(String[] args) {
int[] array = initArray(10, int[]::new);
}
}
/*总结:
通过对象引用成员方法 : 对象名::方法名
当Lambda的解决方案是在调用一个对象的成员方法,这个时候就可以使用方法引用替换Lambda表达式通过类名引用静态方法 : 类名::方法名
当Lambda的解决方案是在调用一个类的静态方法,这个时候就可以使用方法引用替换Lambda表达式通过super引用父类方法: super::父类方法名
当Lambda的解决方案是在调用一个父类的方法,这个时候就可以使用方法引用替换Lambda表达式通过this引用本类方法: this::本类方法名
当Lambda的解决方案是在调用一个本类的方法,这个时候就可以使用方法引用替换Lambda表达式方法引用的场景:
如果Lambda表达式指定的解决方案已经在另一个方法中实现了,那么这个时候就可以使用方法引用替换Lambda表达式
简而言之:如果Lambda表达式中的解决方案,就是调用另一个方法,那么这个时候就可以使用方法引用替换Lambda表达式
*/