Java Function
- 1.Lambda表达式的语法
- 2.函数式接口
- 1.Collection接口
Lambda 表达式是 Java8 中一个重要的新特性。lambda 表达式允许你通过 表达式 来代替功能接口。 lambda 表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码 块)。 Lambda 表达式(Lambda expression)可以看作是一个匿名函数,基于数学中的λ演算得名,也可称为闭包(Closure)
- Lambda 表达式是对函数接口进行简单的实现
(parameters) -> expression
或
(parameters) ->{ statements; }
- 1.paramaters:类似方法中的形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明 也可不声明而由JVM隐含的推断。另外当只有一个推断类型时可以省略掉圆括号。
- 2.->:可理解为“被用于”的意思
- 3.方法体:可以是表达式也可以代码块,是函数式接口里方法的实现。代码块可返回一个值或者什么都不反 回,这里的代码块块等同于方法的方法体。如果是表达式,也可以返回一个值或者什么都不反回。
要了解Lambda
表达式,首先需要了解什么是函数式接口,
一个接口有且只有一个抽象方法 。
注意:
- 1.如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口
- 2.如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口,这样如果有两个抽象方法,程序编译就会报错的。所以,从某种意义上来说,只要你保证你的接口 中只有一个抽象方法,你可以不加这个注解。加上就会自动进行检测的。
@FunctionalInterface
interface NoParameterNoReturn {
//注意:只能有一个抽象方法
void test();
}
但是这种方式也是可以的:
@FunctionalInterface
interface NoParameterNoReturn {
void test();
default void test2() {
System.out.println("JDK1.8新特性,default默认方法可以有具体的实现");
}
}
首先,我们实现准备好几个接口:
@FunctionalInterface
interface NoParameterNoReturn {
//注意:只能有一个抽象方法
void test();
}
//无返回值一个参数
@FunctionalInterface
interface OneParameterNoReturn {
void test(int a);
}
//无返回值多个参数
@FunctionalInterface
interface MoreParameterNoReturn {
void test(int a, int b);
}
//有返回值无参数
@FunctionalInterface
interface NoParameterReturn {
int test();
}
//有返回值一个参数
@FunctionalInterface
interface OneParameterReturn {
int test(int a);
}
//有返回值多参数
@FunctionalInterface
interface MoreParameterReturn {
int test(int a, int b);
}
我们在上面提到过,Lambda
表达式本质是一个匿名函数,函数的方法是:返回值 方法名 参数列表 方法体。在,Lambda表达式中我们只需要关心:参数列表
方法体。
具体使用见以下示例代码:
@FunctionalInterface
interface NoParameterNoReturn {
//注意:只能有一个抽象方法
void test();
}
//无返回值一个参数
@FunctionalInterface
interface OneParameterNoReturn {
void test(int a);
}
//无返回值多个参数
@FunctionalInterface
interface MoreParameterNoReturn {
void test(int a, int b);
}
//有返回值无参数
@FunctionalInterface
interface NoParameterReturn {
int test();
}
//有返回值一个参数
@FunctionalInterface
interface OneParameterReturn {
int test(int a);
}
//有返回值多参数
@FunctionalInterface
interface MoreParameterReturn {
int test(int a, int b);
}
public class TestDemo2 {
public static void main(String[] args) {
NoParameterNoReturn noParameterNoReturn = () -> {
System.out.println("无参数无返回值");
};
//test方法的主体内容在上述括号内
noParameterNoReturn.test();
OneParameterNoReturn oneParameterNoReturn = (int a) -> {
System.out.println("无参数一个返回值:" + a);
};
oneParameterNoReturn.test(10);
MoreParameterNoReturn moreParameterNoReturn = (int a, int b) -> {
System.out.println("无返回值多个参数:" + a + " " + b);
};
moreParameterNoReturn.test(20, 30);
NoParameterReturn noParameterReturn = () -> {
System.out.println("有返回值无参数!");
return 40;
};
//接收函数的返回值
int ret = noParameterReturn.test();
System.out.println(ret);
OneParameterReturn oneParameterReturn = (int a) -> {
System.out.println("有返回值有参数!");
return a;
};
ret = oneParameterReturn.test(50);
System.out.println(ret);
MoreParameterReturn moreParameterReturn = (int a, int b) -> {
System.out.println("有返回值多个参数!");
return a + b;
};
ret = moreParameterReturn.test(60, 70);
System.out.println(ret);
}
}
- 参数类型可以省略,如果需要省略,每个参数的类型都要省略。
- 参数的小括号里面只有一个参数,那么小括号可以省略
- 如果方法体当中只有一句代码,那么大括号可以省略
- 如果方法体中只有一条语句,其是return语句,那么大括号可以省略,且去掉return关键字。
Lambda 表达式中存在变量捕获 ,了解了变量捕获之后,我们才能更好的理解Lambda 表达式的作用域 。Java当中的匿名类中,会存在变量捕获。
下面我们来讲下在Lambda当中也可以进行变量的捕获,具体我们看一下代码:
@FunctionalInterface
interface NoParameterNoReturn {
void test();
}
public class TestDemo2 {
public static void main(String[] args) {
int a = 10;
NoParameterNoReturn noParameterNoReturn = () -> {
/*
注意此处不能够修改a的值,与匿名内部类中相同
a = 99;
*/
System.out.println("捕获变量:" + a);
};
noParameterNoReturn.test();
}
}
执行结果
捕获变量:10
为了能够让Lambda和Java的集合类集更好的一起使用,集合当中,也新增了部分接口,以便与Lambda表达式对接。
对应接口 | 新增方法 |
---|---|
Collection | removeIf() spliterator() stream() parallelStream() forEach() |
List | replaceAll() sort() |
Map | getOrDefault() forEach() replaceAll() putIfAbsent() remove() replace() computeIfAbsent() computeIfPresent() compute() merge() |
注意:Collection的forEach()方法是从接口 java.lang.Iterable 拿过来的。
forEach() 方法演示
该方法在接口 Iterable 当中,原型如下:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
**forEach()**方法表示:对容器中的每个元素执行action指定的动作
可以看到我们的参数 Consumer 其实是一个函数式接口: 这个函数式接口中有一个抽象方法accept:
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
public class TestDemo2 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
list.add("bit");
list.add("hello");
list.add("lambda");
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
//简单遍历集合中的元素
System.out.println(s);
}
});
}
}
我们可以修改为如下代码:
public class TestDemo2 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
list.add("bit");
list.add("hello");
list.add("lambda");
list.forEach((String s) -> {
System.out.println(s);
});
}
}
可以简化代码:
public class TestDemo2 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
list.add("bit");
list.add("hello");
list.add("lambda");
list.forEach(s -> System.out.println(s));
// 再简化
list.forEach(System.out::println);
}
}
public class PrintLog {
public static void showLog(int level, String msg){
if(level==1){
System.out.println(msg);
}
}
public static void main(String[] args) {
String msg1 = "Hello";
String msg2 = "Java";
String msg3 = "heroC";
showLog(1,msg1 + msg2 + msg3);
}
}
此打印日志程序,存在性能浪费,如果在main方法中,调用showLog方法,传入的等级不是1,那么字符串依然会被拼接,字符串拼接之后判断等级不是1,就白拼接了,也浪费了资源和性能。
函数式接口:
// 创建一个函数式接口
@FunctionalInterface
public interface PrintLogInterface {
public abstract String printLog();
}
测试类:
public class PrintLog {
public static void showLog(int level, PrintLogInterface printLogInterface){
if(level == 1){
System.out.println(printLogInterface.printLog());
}
}
public static void main(String[] args) {
String msg1 = "Hello";
String msg2 = "Java";
String msg3 = "heroC";
int level = 1;
showLog(level,() -> msg1 + msg2 + msg3);
}
}
通过使用Lambda表达式,Lambda表达式有一个很大的有点就是延迟执行,调用showLog方法,只将参数传递进去了,先判断是否等于1,判断了如果等于1才会去调用Lambda表达式实现的接口类,进行拼接字符串,然后输出,否则不会拼接字符串。
- 使用Lambda作为参数
// 写了一个函数式接口
@FunctionalInterface
public interface MyFunInterface {
public abstract void method();
}
// 测试类
public class Test{
// 函数式接口作为方法参数
public static void show(MyFunInterface myFunInterface){
myFunInterface.method();
}
public static void main(String[] args){
show(() -> System.out.println("通过Lambda,实现函数式接口"));
}
}
- 使用Lambda作为返回值
public class Test {
public static Comparator<String> newComparator(){
return (a, b) -> a.length() - b.length(); // 这里就是作为返回值
};
public static void main(String[] args) {
String[] arr = {"aaa","bb","c"};
Arrays.sort(arr,newComparator());
System.out.println(Arrays.toString(arr));
// 输出 [c, bb, aaa]
}
}
主要在java.util.function
包中
java.util.function Interface Supplier<T>
接口中包含了一个抽象方法 T get()
。用来获取一个泛型参数指定类型的对象数据。
该接口被称为生产型接口,指定接口是什么类型,那么接口中的get方法就会产生什么类型的数据。
public class SupplierTest {
public static String getString(Supplier<String> sup){
return sup.get();
}
public static int getInt(Supplier<Integer> sup){
return sup.get();
}
public static void main(String[] args) {
String str = getString(() -> "heroC");
/*getString(new Supplier<String>() {
@Override
public String get() {
return "heroC";
}
});*/
System.out.println(str);
int strInt = getInt(() -> 1130);
System.out.println(strInt);
}
}
public class ArrayMaxNum {
public static int getMax(Supplier<Integer> sup){
return sup.get();
}
public static void main(String[] args) {
int[] arr = {245,54,2366,23,6543,1130};
int max1 = getMax(() -> {
int max = arr[0];
for (int i = 0; i < arr.length-1; i++) {
if (arr[i] < arr[i+1]) {
max = arr[i+1];
}
}
return max;
});
System.out.println(max1);
}
}
java.util.function Interface Consumer<T>
接口正好与Supplier接口相反,它不是生产一个数据,而是消费(使用)一个数据,其数据类型由泛型决定。
抽象方法:void accept(T t)
,意为消费一个数据类型。
public class ConsumerInterfaceImp {
public static void consumerString(String str, Consumer<String> con){
con.accept(str);
}
public static void main(String[] args) {
consumerString("heroC",(str) -> {
// 将字符串反转
String s = new StringBuffer(str).reverse().toString();
System.out.println(s);
});
}
}
默认方法:default Consumer<T> andThen(Consumer<? super T> after)
需要两个Consumer接口,就可以把两个Consumer接口组合在一起,在对数据进行消费。
// Consumer默认方法源码
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
eg:
public class ConsumerInterfaceImp {
public static void consumerAndThen(String str, Consumer<String> con1, Consumer<String> con2){
// 两个Consumer接口对象,使用默认方法andThen,调用accept方法
// 执行步骤:con1.accept(str); con2.accept(str); 谁在前面谁先对数据进行消费
// 如果由多个Consumer对象,可以一直链式调用andThen方法
con1.andThen(con2).accept(str);
}
public static void main(String[] args) {
consumerAndThen("heroC",
(str) -> System.out.println(str.toUpperCase()),
(str) -> System.out.println(str));
}
}
下面字符串数组中,存储了很多信息,按照姓名:xxx,性别:xxx格式打印出来。使用Consumer接口的Lambda表达式。
public static void main(String[] args){
String[] array = {"heroC,男", "欧阳娜娜,女"};
}
解答:
public class ConsumerInterfaceExample {
public static void printInfo(String[] arr, Consumer<String> con1, Consumer<String> con2){
for(String str : arr){
con1.andThen(con2).accept(str);
}
}
public static void main(String[] args) {
String[] array = {"heroC,男", "欧阳娜娜,女"};
printInfo(array, str ->{
System.out.print("姓名:" + str.split(",")[0] + ",");
}, str ->{
System.out.println("性别:" + str.split(",")[1]);
});
}
}
代码简洁,开发迅速 方便函数式编程 非常容易进行并行计算 Java 引入 Lambda,改善了集合操作
代码可读性变差 在非并行计算中,很多计算未必有传统的 for 性能要高 不容易进行调试