函数式编程(lambda表达式)
函数式编程中的函数指的不是程序中的函数(方法),而是数学中的函数即映射关系,如:y=sin(x),描述数据(函数)之间的映射,同时需要保证相同的输入始终能得到相同的输出。
函数式编程不会保留计算中间的结果,因此也可以说是无状态的。函数式编程可以将一个函数的执行结果再交由另一个函数去处理。
Java8通过Lambda表达式与方法引用等,实现函数式编程,可将其可定义为一种简洁、可传递的匿名函数。
作用:简化代码
前提:有且仅有一个抽象方法的接口(函数式接口)
举例:
Runnable runnable1 = new Runnable() { //原始
@Override
public void run() {
System.out.println("没用");
}
};
runnable1.run();
System.out.println("****************************************");
Runnable runnable2 = () -> System.out.println("用了Lambda表达式"); //使用Lanbda后
runnable2.run();
格式:
(parameters) -> expression
(parameters) ->{ statements; }
- 左边:类似方法中的形参列表,这里的参数是函数式接口里的参数,多个参数逗号隔开
- ->:Lambda修饰符,可以理解为被用于
- 右边:Lambda体(就是重写的抽象方法的方法体)
简化:
左边:Lambda形参列表的参数类型可以省略(类型推断);如果Lambda形参列表只有一个参数,其一对()也可以省略。
右边:Lambda体应该使用一对包裹;如果Lambda体只有一条执行语句(可能是return语句),可以省略这一对{ }和return关键字。
检验是否为函数式接口:@FunctionalInterface
常用函数式接口:
- Supplier
对外提供”一个符合泛型类型的对象数据。
@FunctionalInterface
public interface Supplier<T> {
T get();
}
举例:
import java.util.function.Supplier;
public class Demo08Supplier {
private static String getString(Supplier<String> function) {
return function.get();
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
System.out.println(getString(() ‐> msgA + msgB));
}
}
2.Consumer
正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
import java.util.function.Consumer;
public class Demo09Consumer {
private static void consumeString(Consumer<String> function) {
function.accept("Hello");
}
public static void main(String[] args) {
consumeString(s ‐> System.out.println(s));
}
}
3.Predicate
对某种类型的数据进行判断,从而得到一个boolean值结果。
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
import java.util.function.Predicate;
public class Demo15PredicateTest {
private static void method(Predicate<String> predicate) {
boolean veryLong = predicate.test("HelloWorld");
System.out.println("字符串很长吗:" + veryLong);
}
public static void main(String[] args) {
method(s ‐> s.length() > 5);
}
}
4.Function
用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
import java.util.function.Function;
public class Demo11FunctionApply {
private static void method(Function<String, Integer> function) {
int num = function.apply("10");
System.out.println(num + 20);
}
public static void main(String[] args) {
method(s ‐> Integer.parseInt(s));
}
}
方法引用&构造器、数组引用
方法引用:
- 对象::实例方法
- 类::静态方法
- 类::实例方法
- 对象::实例方法
作用:调用某个对象的某个具体实例方法
注意:实现抽象方法的参数列表, 必须与方法引用方法的参数列表保持一致!
格式:使用操作符 “::” 将方法名和对象名字分隔开来。
@Test
public void test2() {
Consumer<String> con1 = (x) -> System.out.println(x);
con1.accept("con1");
Consumer<String> con2 = System.out::println;
con2.accept("con2");
}
- 类::静态方法
这里与上面的对象::实例方法基本一致,用于引用类的静态方法
@Test
public void test3() {
Comparator<Integer> com1 = (o1, o2) -> Integer.compare(o1, o2);
Comparator<Integer> com2 = Integer::compare;
}
- 类::实例方法
对于这个格式,我之前刚看见的时候觉得有点奇怪,其实这个格式中的参数列表与前两个方法不一样,这种格式用于引用第一个对象的方法,将后面的对象作为方法的参数列表。
具体如下所示,"::“前面是第一个对象的类,”::"后面为对象的实例方法。
@Test
public void test4() {
BiPredicate<String, String> bp1 = (o1, o2) -> o1.equals(o2);
BiPredicate<String, String> bp2 = String::equals;
}
构造器引用:
格式: ClassName::new
注意:可以把有参构造器引用赋值给定义的方法, 与构造器参数列表要与接口中抽象方法的参数列表一致!
这个例子中,分别使用了无参构造和有参构造,有参构造在调用接口实例化方法时传参。
@Test
public void test5() {
Supplier<String> sup1 = () -> new String("aaa");
Supplier<String> sup2 = String::new;
Function<byte[], String> fun = String::new;
System.out.println(sup1.get());
System.out.println(sup2.get());
byte[] byteArr = {'a', 'b', 'c'};
System.out.println(fun.apply(byteArr));
}
数组引用:
数组引用与构造器引用基本一致
格式: type[] :: new
@Test
public void test6(){
Function<Integer, String[]> fun1 = (x) -> new String[x];
Function<Integer, String[]> fun2 = String[]::new;
String[] apply1 = fun1.apply(10);
String[] apply2 = fun2.apply(100);
System.out.println(apply1.length);
System.out.println(apply2.length);
}
stream流的数据操作
主要用于对集合和SQL的操作。(过滤,排序,计算)
操作过程: 创建(获取数据源)–>中间操作(对数据进行处理)–>终止操作(结束,只有执行了终止操作,才会有中间操作)
创建方式:
@Test
public void test7() {
// 1.通过Collection集合的方法获取
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
// 2.数组:通过Arrays的静态方法stream()
String[] strArr = new String[2];
Stream<String> stream1 = Arrays.stream(strArr);
// 3.通过Stream的静态方法of()
Stream<String> a = Stream.of("a", "b", "c");
// 4.创建无限流
// 4.1 迭代
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 1);
stream2.limit(5).forEach(System.out::print);
System.out.println();
// 4.2 生成
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
}
前3种应该比较好理解,个人认为最常见的还是第一种,对于无限流,创建时写入创建元素的方法,那么流的长度就是无穷大,为了功能需求,一般通过中间操作limit()来实现限制流的长度:
中间操作:
arrayList.stream().filter(e ->e>100).distinct().forEach(System.out::print);
终止操作: