【函数式编程】
数学:有输入和输出的一套计算方案(拿数据做操作)
面向对象:必须通过对象的形式做事情
函数编程:强调做什么,而不是以什么对象的形式去做(怎么做)
特点:
①可作为参数传递和赋值:函数与其它数据类型一样,可以赋值给变量,也就可以作为参数传入到另一个函数,还能作为别的函数的返回值。
②只用表达式不用语句:表达式(Expression)是一个单纯的运算过程并总是有返回值,语句(statement)是执行某种操作没有返回值。
③没有副作用:副作用指修改系统状态,意味着函数要保持独立,所有功能就是返回一个新值,没有其它行为尤其是不得修改外部变量的值。
④引用透明:即如果提供相同的输入,那么函数总是返回同样的结果。
优缺点:
代码简洁,易于并发编程(因为不修改变量所以不用担心线程的问题)
提高学习成本,维护性差。
【函数式接口 Functional Interface】
定义:
Java8 定义的新接口类型,只包含一个抽象方法的接口,也叫SAM(Single Abstract Method)类型接口。
说明:
① @FunctionalInterface 注解用于编译期间检查接口是否符合函数式接口的语法,防止后人修改使其变成非函数接口,当然也可不写。
②接口里可以有 static 方法和 default 方法,毕竟它们都已经是实现过的。
③接口里可以定义 Object 中的 public 方法,虽然它是抽象方法,实现类继承自Object 自然也就实现了。
@FunctionalInterface
interface A{
//包含单个抽象方法
void method();
//可以有默认方法
default void show(){...}
//可以有静态方法
static void run(){...}
//可以定义Object中的方法
public boolean equals(Object object);
}
实例创建:①Lambda表达式、②方法引用、③构造方法。
TODO...
【Lambda表达式】
Lambda表达式能赋值给一个变量,也就能当作参数传给函数。这个Lambda形式的变量/参数的类型是它所实现的那个接口,所包含的方法体便是这个接口抽象方法的实现。以后看到调用方法的参数是一个SAM类型接口的时候就可以考虑使用Lambda表达式替换匿名内部类来写。
作用:
①减少代码量,突出代码意图
②对集合数据 Collection 操作更简便
③使变量记住一段逻辑:
任务逻辑传递(传递一段运算逻辑给执行者)
回调逻辑传递(简化接口回调的时候 new匿名类后实现抽象方法的模版代码)
将一个方法写成Lambda表达式,只需要关注参数列表和方法体。
语法组成:
(参数类型 参数名) -> {
方法体;
return 返回值;
}
①形式参数:最前面的部分是一对括号,里面是参数,无参数就是一对空括号
②箭头:中间的是 -> ,用来分割参数和body部分,读作“ goes to”
③方法体:body部分可以是一个表达式或者一个代码块。
简写:
①可选类型声明:不用声明参数类型,编译器可以自动识别。
②可选参数括号:一个参数无需定义括号,多个参数需要定义。
③可选大括号:如果body部分只包含一个语句或表达式,就不需要使用大括号括起来。
④可选 return 关键字:如果body部分是一个表达式,表达式的值会被作为返回值返回;如果是代码块,需要用 return 指定返回值。
例如 Runnable 接口只有一个 run() 方法,像这样的接口都可以通过匿名函数实现。
public interface Runnable {
public abstract void run();
}
//【传统写法】匿名内部类,可读性差、不能引用外部非 final 变量等
//1.创建了一个没有名字的类
//2.实现了Runnable接口的抽象方法
//3.new了这个类的实例
//而我们关注的只是run()方法中要执行的代码
new Thread(new Runnable(){
public void run(){
System.out.println("hello");
}
}).start();
//【Java8写法】匿名函数,精简易读
//只需要将执行的代码放到函数(方法)中,Lambda就是一个匿名函数(方法)
new Thread( ()-> { System.out.println("hello"); }).start();
Java8 之前创建接口实现类总会有很多冗余的模版代码,接口中定义的抽象方法越多,每次实现的模版代码就越多,而很多时候这个接口实现类只需要用到一次。
变量作用域 :
①局部变量:引用的局部变量不可被修改值,即必须是final所修饰的,或者不被后面代码更改的(隐形final属性)。
②成员变量:可以修改类成员变量(非 final 修饰的)和静态变量。
③在Lambda表达式中,不允许声明一个与局部变量同名的参数或者局部变量。
④在Lambda表达式中,使用this调用的是该Lambda表达式所属方法的所属实例的this。
⑤在Lambda表达式中,无法访问到自身接口的默认方法(不存在实例)。
public class Test {
int aa = 3;
static int bb = 3;
public String show() {
return "GOOD!";
}
public void method(int num) {
int cc = 3; //可以不被final修饰但是值不能被后面代码更改
Supplier<Integer> supplier1 = () -> 2 + aa; //引用成员变量
Supplier<Integer> supplier2 = () -> 2 + bb; //引用静态成员变量
Supplier<Integer> supplier3 = () -> 2 + cc; //引用局部变量
Supplier<Integer> supplier4 = () -> 2 + num; //引用形参
Supplier<String> supplier5 = this::show; //调用的是表达式所属方法所属实例的this
}
}
Lambda与匿名内部类的区别:
①所需类型不同:
匿名内部类:接口、抽象类、具体类
Lambda:接口
②使用限制不同:
匿名内部类:接口中可以有多个抽象方法
Lambda:接口中只能有一个抽象方法
③实现原理不同:
匿名内部类:编译后会产生一个单独的.class字节码文件
Lambda:编译后没有单独的.class字节码文件,对应的字节码在运行的时候动态生成。①在类中新增一个方法,方法体就是Lambda中的代码。②还会新增一个匿名内部类,实现接口重写方法。③在重写方法中调用新生成的方法。
【内置函数接口】
为了减少自定义函数式接口的编写,JDK已经帮我们抽出几种常用的函数式接口,存放于 java.util.function包中。(例如写接口回调的时候,自定义的与自带的功能一样当然是用自带的)
函数式接口 | 名称 | 方法说明 | |
Supplier <T> | 供给型接口 | T get ( ) 无参数传入,返回一个结果 | |
Consumer <T> | 消费型接口 | void accept ( T t ) 接收一个参数,无需返回结果 | |
default Consumer<T> andThen(Consumer<? super T> after) 执行自身 this.apply()之后再执行参数 after.apply() | |||
Function <T,R> | 函数型接口 | R apply ( T t ) 接收 T 类型参数,返回 R 类型结果 | |
default <V> Function<T,V> andThen (Function<? super R,? extends V> after) 执行自身 this.apply() 之后再执行参数 after.apply() | |||
default <V> Function<V,R> compose (Function<? super V,? extends T> before) 执行自身 this.apply() 之前先执行参数 before.apply() | |||
Predicate <T> | 断言型接口 | boolean test ( T t ) 接收一个参数,返回 boolean 类型结果 | |
default Predicate<T> and(Predicate<? super T> other) 底层原理是:自身 this.test() && 参数 other.test() | |||
default Predicate<T> or(Predicate<? super T> other) 底层原理是:自身 this.test() || 参数 other.test() | |||
default Predicate<T> negate() 底层原理:! test() |
public class TestJava {
public static void main(String[] args) {
//获取数字3(无参数传入,返回一个结果)
// Supplier<Integer> supplier = () -> 3;
// int number3 = getNumber3(supplier);
int number3 = supplierGet(() -> 3); //3
//打印输入的数字(接收一个参数,无需返回结果)
consumerAccept(123, bb -> System.out.println(bb)); //123
//对传入的数字进行两次操作
consumerAndThen(3, it -> System.out.println(it += 1), it -> System.out.println(it += 1)); //打印两次
//输入字符串返回字数(接收 T 类型参数,返回 R 类型结果)
int strLength = functionApply("今天天气不错!", str -> str.length()); //7
//输入一个数字返回它是几位数
int num1 = functionAndThen(555, it -> it.toString(), it -> it.length()); //3
int num2 = functionCompose(555, it -> it.length(), it -> it.toString()); //3
//输入的数字是否等于100(接收一个参数,返回 boolean 类型结果)
boolean isNumber5 = predicateTest(45, cc -> cc == 100); //false
//对传入的字符串,用两个单独的判断进行操作
boolean b1 = predicateAnd("Hello World!", str -> str.contains("!"), str -> str.length() < 5); //true
boolean b2 = predicateOr("Hello World!", str -> str.contains("Nihao"), str -> str.contains("Shijie")); //false
//输入的数字是否小于50,对判断结果取反
boolean b3 = predicateNegate(55, num -> num < 50); //false
}
//【Supplier】
public static int supplierGet(Supplier<Integer> supplier) {
return supplier.get();
}
//【Consumer】
public static void consumerAccept(int num, Consumer<Integer> consumer) {
consumer.accept(num);
}
public static void consumerAndThen(int num, Consumer<Integer> consumer1, Consumer<Integer> consumer2) {
//常规写法
// consumer1.accept(num);
// consumer2.accept(num);
//andThen写法
consumer1.andThen(consumer2).accept(num);
}
//【Function】
public static int functionApply(String str, Function<String, Integer> it) {
return it.apply(str);
}
//function1 的输出类型要与 function2 的输入类型一致,方法返回值类型要与参数 function2 的输出类型一致
public static Integer functionAndThen(int num, Function<Integer, String> function1, Function<String, Integer> function2) {
//常规写法
// String str = function1.apply(num);
// Integer num2 = function2.apply(str);
//精简写法
// function2.apply(function1.apply(num));
//andThen写法
return function1.andThen(function2).apply(num);
}
//function1 的输入类型要与 function2 的输出类型一致,方法返回值类型要与 function1 的输出类型一致
public static Integer functionCompose(int num, Function<String, Integer> function1, Function<Integer, String> function2) {
return function1.compose(function2).apply(num);
}
//【Predicate】
public static boolean predicateTest(int num, Predicate<Integer> it) {
return it.test(num);
}
public static boolean predicateAnd(String str, Predicate<String> p1, Predicate<String> p2) {
//常规写法
// p1.test(str) && p2.test(str);
return p1.and(p2).test(str);
}
public static boolean predicateOr(String str, Predicate<String> p1, Predicate<String> p2) {
//常规写法
// p1.test(str) || p2.test(str);
return p1.or(p2).test(str);
}
public static boolean predicateNegate(int num, Predicate<Integer> it) {
//常规写法
// !it.test(num);
return it.test(num);
}
类似的多参数接口:
函数式接口 | 抽象方法 |
BiConsumer <T,U> | void accept ( T t, U u ) 接收 T 和 U 两个类型参数,无需返回结果 |
BiFunction <T,U,R> | R apply ( T t, U u ) 接收 T 和 U 两个类型参数,返回 R 类型结果 |
BiPredicate <T,U> | boolean test ( T t, U u ) 接收 T 和 R 两个类型参数,返回判断结果 |
UnaryOperator <T> | 一元操作 |
BinaryOperator <T> | 二元操作 |