Java8-函数式编程
为什么java8 的Lambda 这么受关注?
Java8可以用Lambda表达式很简便的写出复杂的处理集合的逻辑。Lambda 可以理解为一种匿名函数的代替。使用它可以简化代码,提高开发效率。
函数编程演化历程
函数编程演化历程
- 1.将业务逻辑直接写死在代码里。
- 2.将单一维度的条件做为参数传入方法中。方法内根据参数进行业务逻辑实现。
- 3.将多个维度的条件做为参数传入方法中。业务实现需要根据不同的参数处理不同逻辑。
- 4.将业务逻辑封装为一个实体类,方法接受实体类为参数,方法内部调用实体类的处理逻辑。
- 5.调用方法时不在创建实体类,而是使用匿名函数的形式替代。
- 6.使用Lambda表达式替代匿名函数的形式,做为方法的参数。真正实现判断逻辑参数化传递。
函数编程
函数式编程是一种编程范式,我们常见的编程范式有命令式编程(Imperative programming),函数式编程,逻辑式编程,常见的面向对象编程是也是一种命令式编程。
命令式编程是面向计算机硬件的抽象,有变量(对应着存储单元),赋值语句(获取,存储指令),表达式(内存引用和算术运算)和控制语句(跳转指令),一句话,命令式程序就是一个冯诺依曼机的指令序列。而函数式编程是面向数学的抽象,将计算描述为一种表达式求值,一句话,函数式程序就是一个表达式。
函数式编程中的函数这个术语不是指计算机中的函数(实际上是Subroutine),而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如sqrt(x)函数计算x的平方根,只要x不变,不论什么时候调用,调用几次,值都是不变的。在函数式语言中,函数作为一等公民,可以在任何地方定义,在函数内或函数外,可以作为函数的参数和返回值,可以对函数进行组合。
Lambda表达式
Lambda表达式的两种形式
(parameters) -> expression
(parameters) ->{ statements; }
以下是lambda表达式的重要特征:可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
表达式实例
// 1. 不需要参数
() -> 5 ;
() -> System.out.println("hello world");
// 2. 接收一个参数
x -> 2 * x ;
() -> System.out.println("hello world");
// 3. 没有参数逻辑复杂
() -> {
System.out.println("hello world1");
System.out.println("hello world2");
}
// 3. 接受2个参数(数字)
BinaryOperator functionAdd = (x,y)-> x + y ;
Long result = functionAdd(1L,2L);
// 4. 接收2个int型整数,显示声明参数类型
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
以上就是lambda的介绍。
函数式接口介绍
通常Lambda表达式是用在函数式接口上使用的。从Java8开始引入了函数式接口,其说明比较简单:函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。 java8引入@FunctionalInterface 注解声明该接口是一个函数式接口。
语法定义
@FunctionalInterface
public interface ICollectionService {
void test();
}
常用的函数式接口
接口 | 参数 | 返回类型 | 描述 |
---|---|---|---|
Predicate |
T | boolean | 用于判别一个对象 |
Consumer |
T | void | 用于接收一个对象进行处理但没有返回 |
Function |
T | R | 转换一个对象为不同类型的对象 |
Supplier |
None | T | 提供一个对象 |
UnaryOperator |
T | T | 接收对象并返回同类型的对象 |
BinaryOperator |
(T,T) | T | 接收两个同类型的对象,并返回一个原类型的对象 |
Predicate
java.util.function.Predicate
例如
@FunctionalInterface
public interface PredicateTest {
boolean test(T t);
}
@SpringBootTest
class LambdaTestApplicationTests {
//借助Lambda 表达式实现Predicate test方法
@Test
public void test3(){
PredicateTest predicateTest = (str)->str.isEmpty()||str.trim().isEmpty();
System.out.println(predicateTest.test(""));
System.out.println(predicateTest.test(" "));
System.out.println(predicateTest.test(" as"));
}
}
Consumer
java.util.function.Consumer
@FunctionalInterface
public interface ConsumerTest {
void accept(T t);
}
/**
* 借助Lambda表达式实现Consumer accept方法
*/
@Test
public void test4(){
ConsumerTest consumerTest = collection ->{
if (!CollectionUtils.isEmpty(collection)){
for (Object o : collection) {
System.out.println(o);
}
}
};
List list = new ArrayList();
list.add("1");
list.add("2");
list.add("3");
consumerTest.accept(list);
}
Function
java.util.function.Function
@FunctionalInterface
public interface FunctionTest {
R apply(T t);
}
@Test
public void test5() {
FunctionTest functionTest = password -> Base64.getEncoder().encodeToString(password.getBytes());
System.out.println(functionTest.apply("123456"));
}
Supplier
java.util.function.Supplier
@FunctionalInterface
public interface SupplierTest {
T get();
}
@Test
public void test6(){
int[] arr = {100,0,-50,880,99,33,-30};
SupplierTest s = ()->{
int max = arr[0];
//遍历数组,获取数组中的其他元素
for (int i : arr) {
//使用其他的元素和最大值比较
if(i>max){
//如果i大于max,则替换max作为最大值
max = i;
}
}
//返回最大值
return max;
};
System.out.println(s.get().intValue());
}
UnaryOperator
java.util.function.UnaryOperator
这个接口,只接收一个泛型参数T,集成Function接口,也就是说,传入泛型T类型的参数,调用apply后,返回也T类型的参数;这个接口定义了一个静态方法,返回泛型对象的本身;
@Test
public void test7() {
UnaryOperator test = x -> x + 100;
System.out.println(test.apply(100));
}
BinaryOperator
java.util.function.BinaryOperator
/**
* 接收两个同类型的对象,并返回一个原类型的对象
*/
@Test
public void test8(){
BinaryOperator add = (n1, n2) -> n1 + n2;
//apply方法用于接收参数,并返回BinaryOperator中的Integer类型
System.out.println(add.apply(3, 4));
}
BiConsumer
这个接口接收两个泛型参数,跟Consumer一样,都有一个 accept方法,只不过,这里的,接收两个泛型参数,对这两个参数做下消费处理;使用这个函数式接口的终端操作有map的遍历;下面看下面的例子,两个参数消费数据的.可以看到,Map接口的终端操作,forEach的参数就是BiConsumer函数接口,对HashMap 的数据进行消费;BiConsumer函数接口还有一个默认函数,andThen,接收一个BiConsumer接口,先执行本接口的,再执行传入的参数。
Map map = new HashMap<>();
map.put("a", "a");
map.put("b", "b");
map.put("c", "c");
map.put("d", "d");
map.forEach((k, v) -> {
System.out.println(k);
System.out.println(v);
});
方法引用
有时候我们不是必须要自己重写某个匿名内部类的方法,我们可以可以利用 lambda表达式的接口快速指向一个已经被实现的方法。 调用特定方法的lambda表达式的一种快捷写法,可以让你重复使用现有的方法定义,并像lambda表达式一样传递他们。
语法:
方法归属者::方法名
静态方法的归属者为类名,普通方法归属者为对象
指向静态方法的方法引用
public void test(){
Consumer consumer = (String s) -> Integer.parseInt(s);
Consumer consumer1 = Integer::parseInt;
}
指向对象的实例方法引用
StringBuilder stringBuilder = new StringBuilder();
Consumer consumer = (String s) -> stringBuilder.append(s);
Consumer consumer1 = stringBuilder::append;