提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
一、函数式编程的概述
函数式编程是一种编程范式,我们常见的编程范式有命令式编程(Imperative programming),函数式编程,逻辑式编程,常见的面向对象编程是也是一种命令式编程。
命令式编程是面向计算机硬件的抽象,有变量(对应着存储单元),赋值语句(获取,存储指令),表达式(内存引用和算术运算)和控制语句(跳转指令)。
而对于函数式编程,它是将事务与事物之间的运算过程进行抽象,形成了数学中函数的映射关系,即,输入值能对应输出值的y=f(x),用来描述数据、函数之间的映射关系。
二、Lambda表达式
事实上,Lambda表达式就是函数式编程思想的一种实现,也是匿名内部类的简化(关于内部类,请见本专栏《Java基础》一文)。
Lambda表达式是JAVA8中提供的一种新的特性,是一个匿名函数方法。可以把Lambda表达式理解为一段可以传递的代码(允许将函数作为一个方法的参数,使得代码像数据一样传递),可以写出更简洁、更灵活的代码。
(一)使用步骤
1.使用条件
Lambda表达式可认为是对匿名内部类的一种简化,但并非所有的匿名内部类都可简化为Lambda表达式,只有函数式接口(需要加上@Functionalinterface进行声明,后面会写)的匿名内部类才可使用Lambda表达式进行简化。
函数式接口不同于普通接口,其接口当中只有一个抽象方法需要去实现,Lambda就可完成这一实现。
2.基本语法
(参数列表)->{函数体}
其中,"->"作为分隔符来分隔参数和Lambda表达式的主体。
重要特征:
- 不需要声明参数类型,编译器会自动识别。即:
int(x,int y)->{return x+y;}
//等价于
(x,y)->{return x+y;}
- 一个参数无需定义圆括号,但无参数、多个参数需要定义。
//单个参数可不用圆括号
msg->{System.out.println(msg);}
//多个参数、无参数需要使用圆括号
(msg1,msg2)->System.out.println(""+msg1+msg2);
- 若函数体中只有一个语句,就可不写花括号。
(msg)->System.out.println(msg)
- 若函数体只有一个语句,且该语句的结果就是函数返回值,则可不使用return语句。
(x,y)->x+y;
3.作用域
Lambda表达式可以看做是匿名内部类实例化的对象,其访问权限和匿名内部类一样,可以访问外层函数局部变量、局部引用、静态变量、实例变量。
其中,外层函数局部变量有着特殊的要求,即,只能引用标记了final的外层局部变量,或是不在Lambda的内部对外层的局部变量进行修改(隐形的final),否则会编译错误,且,不允许在Lambda表达式内部声明一个与外层同名的参数或变量例:
public class TestFinalVariable {
interface VarTestInterface{
Integer change(String str);
}
public static void main(String[] args) {
//局部变量不使用final修饰
Integer tempInt = 1;
VarTestInterface var = (str -> Integer.valueOf(str+tempInt));
//再次修改,不符合隐式final定义
tempInt =2;
Integer str =var.change("111") ;
System.out.println(str);
}
}
以上代码会出现编译错误,应当改成:
public class TestFinalVariable {
interface VarTestInterface{
Integer change(String str);
}
public static void main(String[] args) {
//局部变量不使用final修饰
Integer tempInt = 1;
VarTestInterface var = (str -> Integer.valueOf(str+tempInt));
Integer str =var.change("111") ;
System.out.println(str);
}
}
原因:
局部变量是保存在栈帧中的。而在Java的线程模型中,栈帧中的局部变量是线程私有的,如果允许Lambda表达式访问到栈帧中的变量地址(可改变的局部变量),则会可能导致线程私有的数据被并发访问,造成线程不安全问题。
对于引用类型的局部变量,因为Java是值传递,又因为引用类型的指向内容是保存在堆中,是线程共享的,因此Lambda表达式中可以修改引用类型的局部变量的内容,而不能修改该变量的引用。
对于基本数据类型的变量,在 Lambda表达式中只是获取到该变量的副本,且局部变量是线程私有的,因此无法知道其他线程对该变量的修改,如果该变量不做final修饰,会造成数据不同步的问题。
但是实例变量,静态变量不作限制,因为实例变量,静态变量是保存在堆中(Java8之后),而堆是线程共享的。在Lambda表达式内部是可以知道实例变量,静态变量的变化。
4.Lambda表达式和匿名内部类的对比
类型不同:
- 匿名内部类:可以实现接口、抽象类,还可是具体的类。
- Lambda表达式:只能实现接口。
使用限制不同: - Lambda表达式实现的接口需要使用@Functionalinterface注解进行声明。
- 匿名内部类实现接口并无限制。
实现原理不同: - Lambda表达式编译后并无单独的字节码文件,它对应的字节码文件在运行时自动生成。
- 匿名内部类:编译后会产生对应的字节码文件。
(二)案例
案例1
//Man.java函数式接口
package org.example.polo;
@FunctionalInterface
public interface Man {
void say(String words);
}
//测试类Demo
package org.example.SimpleCode;
import org.example.polo.Man;
import org.junit.Test;
public class Demo {
@Test
public void test(){
//使用匿名内部类直接实现函数式接口
Man man=new Man() {
@Override
public void say(String words) {
System.out.println(words);
}
};
//使用Lambda表达式简化实现函数式接口
Man man1=(words)->{
System.out.println(words);
};
man.say("Hello World");
man1.say("Hello World");
}
}
案例2
//精简之前:
Arrays.sort(grade,new Comparator<Integer>(){
@Override
public int compare(Integer o1,Integer o2){
return o2 -o1;
}
});
//精简之后:
Arrays.sort(grade,(Integer o1,Integer o2) -> {
return o2 -o1;
});
三、函数式接口
函数式接口是一类特殊类型的接口,这类接口中只定义了唯一的抽象方法,但可有多个静态方法和默认方法,故而,称为函数类型接口(即,接口的功能和其中的唯一抽象函数是对应的)。
且,函数式接口可作为函数参数进行传递(使用时该方法时就需要实现该接口),这也是函数式编程的特点。
可在任意函数式接口上使用@FunctionalInterface注解,来检查是否是一个函数式接口。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
Java常用的函数式接口一共有五种:
- 自定义函数式接口
- Consumer:消费型接口,包含方法accept。
- Supplier:供给型接口 ,包含方法get。
- Function<T,R>:函数型接口,对类型为T的对象应用操作并返回R类型的结果,包含方法apply。
- Predicate:断定型接口,确定类型为T的对象是否满足条件约束,包含方法test。
(一)自定义函数式接口
使用注解可自定义一个函数式接口,其可作为参数传入方法。
函数式接口SingleAbstractMethodInterface.java
@FunctionalInterface
public interface SingleAbstractMethodInterface {
public abstract void singleMethod();
}
定义test类,并将函数式接口实现后作为参数传入方法:
public class Test {
public void testMethod(SingleAbstraMethodInterface single){
System.out.println("即将执行函数式接口外部定义方法");
single.singleMethod();
}
public static void main(String[] args) {
Test test = new Test();
test.testMethod(new SingleAbstraMethodInterface() {
@Override
public void singleMethod() {
System.out.println("执行函数式接口定义方法");
}
});
}
}
事实上,亦可使用Lambda表达式:
@Test
public void test() {
SingleAbstractMethodInterface singleAbstractMethodInterface=()-> System.out.println("执行函数式接口定义方法");
singleAbstractMethodInterface.singleMethod();
}
(二)Consumer函数式接口
源码:
@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.函数方法accept
void accept(T t);
作用:消费一个数据,并执行相关操作
例:
public void reverse(String Str, Consumer<String>consumer){
//声明一个方法,需要传入函数式接口的实现作为参数,来完成相关操作
consumer.accept(Str);
}
@Test
public void test() {
String str="ABCD";
reverse(str,s -> {
String res="";
for(int i=s.length()-1;i>=0;i--)res+=s.charAt(i);
System.out.println("字符串反转后为:"+res);
});
}
2.默认方法andThen
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
作用:默认方法andThen,需要两个Consumer接口,返回值是Consumer接口类型,可将两个Consumer接口组合到一起再消费数据,且,谁先写前面,谁先被消费。
例:
public void reverse(String Str1, Consumer<String>consumer1,Consumer<String>consumer2){
//andThen先执行本consumer中的accept方法,再调用第二个consumer中的方法,且,两个consumer均是对同一数据进行操作
//先执行consumer1,再执行consumer2,且均对Str1进行操作
consumer1.andThen(consumer2).accept(Str1);
}
@Test
public void test() {
String str="ABCD";
reverse(str,s -> {
//实现consumer1中的方法函数
String res="";
for(int i=s.length()-1;i>=0;i--)res+=s.charAt(i);
System.out.println("Consumer1中是字符串反转:"+res);
},t->{
//实现consumer2中的方法函数
System.out.println("Consumer2中是直接输出"+t);
});
}
(三)Supplier函数式接口
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
1.函数方法accept
只包含一个无参的方法,称为生产型接口,指定接口的泛型,get方法就会产生相应类型的数据。
例:
//定义一个方法,用于获取int类型数组中元素的最大值,方法的参数传递Supplier接口,泛型使用Integer
public int getMax(Supplier<Integer> sup){
return sup.get();
}
@Test
public void test1() {
int arr[] = {100,23,456,-23,-90,0,-678,14};
//调用getMax方法,方法的参数Supplier是一个函数式接口,所以可以传递Lambda表达式
int maxValue = getMax(() -> {
int max = arr[0];
for (int i : arr) {
if (i > max) {
max = i;
}
}
//返回最大值
return max;
});
System.out.println("数组中元素的最大值:"+maxValue);
}
(四)Predicate函数式接口
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
@SuppressWarnings("unchecked")
static <T> Predicate<T> not(Predicate<? super T> target) {
Objects.requireNonNull(target);
return (Predicate<T>)target.negate();
}
}
对某种数据类型的数据进行判断,并得到一个Boolean值结果。
1.函数方法test
boolean test(T t);
用于得到一个数据并进行判断。
public boolean judge(String str,Predicate<String>predicate){
return predicate.test(str);
}
@Test
public void test1() {
String str="ABC";
if(judge(str,s->s.equals("ABD"))){
System.out.println("str的值为ABD");
}else {
System.out.println("str的值不为ABD");
}
}
2.默认方法or
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
```
参数为函数式接口Predicate,用于对结果进行与操作,例:
```java
public boolean judge(String str,Predicate<String>predicate1,Predicate<String>predicate2){
return predicate1.or(predicate2).test(str);
}
@Test
public void test1() {
String str="ABC";
if(judge(str,s->"ABD".equals(str),t->"ABE".equals(str))){
System.out.println("str即不为ABD,亦不为ABE");
}else {
System.out.println("str为ABD或ABE");
}
}
(五)Function函数式接口
用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
源码
@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));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
1.抽象方法apply
import java.util.function.Function;
public class Demo01Function {
public static void change(String s, Function<String,Integer> fun) {
Integer in = fun.apply(s);
System.out.println(in);
}
public static void main(String[] args) {
String s = "1234";
change(s,(String str)->{
return Integer.parseInt(str); //1234
});
change(s,str->Integer.parseInt(str)); //1234
}
}
2.默认方法andThen
用来进行组合操作。
import java.util.function.Function;
public class Demo02AndThen {
public static void change(String s,Function<String,Integer> fun1,Function<Integer,String> fun2) {
String ss = fun1.andThen(fun2).apply(s);
System.out.println(ss);
}
public static void main(String[] args) {
String s = "123";
change(s,(String str)->{
return Integer.parseInt(s)+10;
},(Integer i)->{
return i+""; //133
});
//优化
change(s,str->Integer.parseInt(s)+10,i->i+""); //133
}
}
四、方法引用
方法引用可认为是Lambda表达式的一种特殊形式,Lambda表达式可让开发者自定义抽象方法的实现代码,而方法引用则可让开发者直接引用已存在的实现方法,作为Lambda表达式的Lambda体。
使用条件
- 被引用方法必须已经存在。
- 被引用处必须是函数式接口。
- 被引用方法的形参和返回值必须和抽象方法保持一致。
- 被引用的方法的功能需要满足当前条件。
- 被引用方法不能是静态方法。
语法格式
类名::方法名
//其中,"::"称为方法引用符
例:
package org.example.SimpleCode;
import java.util.*;
import java.util.function.*;
import java.util.stream.Stream;
public class Demo {
public static void main(String[] args) {
Integer[] arr={3,5,4,1,6,2};
//匿名内部类写法
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
//Lambda表达式写法
Arrays.sort(arr,(Integer o1,Integer o2)->{
return o2-o1;
});
//注:函数式接口会转化为Lambda表达式,此处参数列表提示了使用函数式接口,自然可使用Lambda表达式
//Lambda表达式简化格式
Arrays.sort(arr,(o1,o2)->o2-o1);
//方法引用
Arrays.sort(arr,Demo::subtraction);
}
public static int subtraction(int num1,int num2){
return num2-num1;
}
}
方法引用的分类
引用静态方法
格式:
类名::静态方法
//例:Integer::parseInt
练习:集合中有以下数字:“1”、“2”、“3”、“4”、“5”,要求将它们变为int类型。
package org.example.SimpleCode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Function;
public class Demo {
public static void main(String[] args) {
ArrayList<String>arrayList=new ArrayList<>();
Collections.addAll(arrayList,"1","2","3","4","5");
//若不使用函数式接口
arrayList.stream().map(new Function<String, Integer>() {
//此处Function接口是函数式接口,故可使用方法引用
@Override
public Integer apply(String s){
return Integer.parseInt(s);
}
}).forEach(s-> System.out.println(s));
//若使用函数式接口
arrayList.stream().map(Integer::parseInt).forEach(s-> System.out.println(s));
}
}
引用成员方法
格式:
对象::成员方法
①引用其他类
其他类对象::方法名
例:只要以"张"开头,且名字是3个字的字符串。
StringOperation.java
public class StringOperation{
public boolean stringJudge(String s){
return s.startsWith("张")&&s.length()==3;
}
}
测试类
package org.example.SimpleCode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Function;
import java.util.function.Predicate;
public class Demo {
public static void main(String[] args) {
ArrayList<String>arrayList=new ArrayList<>();
Collections.addAll(arrayList,"张无忌","张强","张三丰","周芷若");
//链式编程写法
arrayList.stream().filter(s->s.startsWith("张")).filter(s->s.length()==3).forEach(s-> System.out.println(s));
//使用函数式接口
arrayList.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("张")&&s.length()==3;
}
}).forEach(s-> System.out.println(s));
//使用对象成员方法
arrayList.stream().filter(new StringOperation()::stringJudge).forEach(s-> System.out.println(s));
}
}
②引用本类
this::方法名
注:静态方法中是无this的,故而只能通过创建本类对象来引用。即,引用处不能是静态方法。
例:
package org.example.SimpleCode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Predicate;
public class Demo {
public boolean stringJudge(String s){
return s.startsWith("张")&&s.length()==3;
}
public static void main(String[] args) {
ArrayList<String>arrayList=new ArrayList<>();
Collections.addAll(arrayList,"张无忌","张强","张三丰","周芷若");
//链式编程写法
arrayList.stream().filter(s->s.startsWith("张")).filter(s->s.length()==3).forEach(s-> System.out.println(s));
//使用函数式接口
arrayList.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("张")&&s.length()==3;
}
}).forEach(s-> System.out.println(s));
//使用对象成员方法
arrayList.stream().filter(new Demo()::stringJudge).forEach(s-> System.out.println(s));
}
}
③引用父类
super::方法名
引用构造方法
格式:
类名::new
练习:集合中存储姓名和年龄的字符串,如,“张无忌,15”,要求将数据封装成Student对象并收集到List集合中。
package org.example.SimpleCode;
import org.example.polo.Student;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Demo {
public static void main(String[] args) {
ArrayList<String>arrayList=new ArrayList<>();
Collections.addAll(arrayList,"张无忌,15","张强,16","张三丰,20","周芷若,18");
//常规写法
List<Student>list=arrayList.stream().map(new Function<String, Student>() {
@Override
public Student apply(String s){
String[] arr=s.split(",");
String name=arr[0];
int age=Integer.parseInt(arr[1]);
return new Student(name,age);
}
}).collect(Collectors.toList());
//方法引用,由于此时Student类中并无参数只有一个String的构造方法,故需要添加:public Student(String s);
/*
* public Student(String s){
String[] arr=s.split(",");
this.Name=arr[0];
this.age=Integer.parseInt(arr[1]);
}
* */
arrayList.stream().map(Student::new).forEach(s-> System.out.println(s));
}
}
注,本文部分参考:https://blog.csdn.net/huangjhai/article/details/107110182