Java基础知识08--lambda表达式与函数式接口详解

1.接口及函数式接口

1.1 接口

接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。

接口特性

  • 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。其中public abstract 可以省略
  • 接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。
  • 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
  • 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
  • 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
public interface Myinterface {
    //以下都可以
    public abstract void method();
    public void method1();
    abstract void method2();
    void method3();
}

1.2 什么是函数式接口?

函数式接口定义:一个接口有且只有一个抽象方法 。

接口中只有一个抽象方法的接口,称为函数式接口,可以用@FunctionalInterface修饰一下,这里需要注意的是:未使用 @FunctionalInterfaces注解的接口未必就不是函数式接口,一个接口是不是函数式接口的条件只有一条,即接口中只有一个抽象方法的接口(Object类中的方法不算)。而使用@FunctionalInterface注解修饰了的接口就一定是函数式接口,添加@FunctionalInterface注解可以帮助我们检查是否是函数式接口。

函数式接口(Functional Interface)是java8新增的特性,它是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为lambda表达式。

JDK中常见的函数式接口有:

package java.lang;

@FunctionalInterface
public interface Runnable {
    void run();
}
package java.util.concurrent;

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

2.lambda表达式基础语法

2.1.什么是Lambda表达式?

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。

lambda表达式,其实都依赖于接口的支持,lambda表达式的本质是对接口的一种实现。java 8 内部Lambda 表达式的实现方式在本质是以匿名内部类的形式的实现的。

Lambda表达式是接口的实现类的对象,因此Lambda需要依赖一个接口。Lambda的本质是java中接口的一个实例(接口的实现类的具体对象)。可通过下图形象理解。

 

2.2 Java8的Lambda语法及特性

2.2.1 lambda语法

java中,引入了一个新的操作符“->”,该操作符在很多资料中,称为箭头操作符,或者lambda操作符;箭头操作符将lambda分成了两个部分:

// 参数列表                                         箭头 方法体
( ParameterType1 param1,ParameterType2 param2... ) -> { ... }

左侧:lambda表达式的参数列表。这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明 也可不声明而由JVM隐含的推断。另外当只有一个推断类型时可以省略掉圆括号。

右侧:lambda表达式中所需要执行的功能,即lambda方法体。可以是表达式也可以代码块,是函数式接口里方法的实现。代码块可返回一个值或者什么都不返回,这里的代码块等同于方法的方法体。如果是表达式,也可以返回一个值或者什么都不返回。

2.2.2 lambda特性

以下是lambda表达式的重要特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值

1、Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
2、Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
3、使用 Lambda 表达式可以使代码变的更加简洁紧凑。

典型案例:

(1) 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。

Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );

这里e的类型就是由编译器推理得到的,不需要声明类型,当然也可以声明类型,比如:

Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );

(2) 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。

Arrays.asList( "a", "b", "d" ).forEach( e -> {
    System.out.print( e );
    System.out.print( e );
} );

(3) 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指明表达式返回了一个数值。并且如果Lambda表达式中的语句块只有一行,则可以不用使用return语句。

如下两个是一样的效果:

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );

2.3使用Lambda的限制条件

Lambda并不是任何地方都可以使用,Lambda表达式需要“函数式接口”的支持

3.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);
    }
}

lambda表达式语法可以进一步精简;

  • 参数类型可以省略,如果需要省略,每个参数的类型都要省略。
  • 参数的小括号里面只有一个参数,那么小括号可以省略
  • 如果方法体当中只有一句代码,那么大括号可以省略
  • 如果方法体中只有一条语句,其是return语句,那么大括号可以省略,且去掉return关键字。
@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) {

        //方法参数有多个且方法体中无返回值,则可以省略参数类型
        MoreParameterNoReturn moreParameterNoReturn = (a, b) -> {
            System.out.println("无返回值多个参数,省略参数类型:" + a + " " + b);
        };
        moreParameterNoReturn.test(20, 30);


        //方法中只有一个参数,那么小括号可以省略
        OneParameterNoReturn oneParameterNoReturn = a -> {
            System.out.println("方法中只有一个参数,那么小括号可以省略:" + a);
        };
        oneParameterNoReturn.test(10);


        //无参数无返回值,方法体中只有 一行代码的时候,可以去掉方法体的大括号
        NoParameterNoReturn noParameterNoReturn = () -> System.out.println("无参数无返回值,方法体中只有 一行代码");
        noParameterNoReturn.test();

        //方法体中只有一条语句,且是return语句,且无参数
        NoParameterReturn noParameterReturn = () -> 40;
        int ret = noParameterReturn.test();
        System.out.println(ret);
    }
}

4.1 无参数,无返回值

 () -> System.out.println("hello lambda");

Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("hello runnable");
    }
};
r.run();
Runnable r1 = () -> System.out.println("hello lambda");
r1.run();

此时,如果右边的代码简单,只有一行代码时,{}可以省略。

4.2 有一个参数,无返回值

public class CalculatorTest {

    public void print(Consumer<String> msg) {
        System.out.println(msg);
    }

    public void doPrint(String msg) {
        print((str) -> System.out.println(msg));
        print(str -> System.out.println(msg));  // 简写

    }
}

此时,左边的()可以省略。

4.3 有两个参数,Lambda体内只有一条语句,且有返回值,return可省略

public void test3() {
        BinaryOperator<Integer> binary = (x, y) -> x + y;
        System.out.println(binary.apply(1, 2));// 3
    }

4.4 多个参数,多行语句

    public void test4() {
        // 无返回值lambda函数体中用法
        Runnable r1 = () -> {
            System.out.println("hello lambda1");
            System.out.println("hello lambda2");
            System.out.println("hello lambda3");
        };
        r1.run();

        // 有返回值lambda函数体中用法
        BinaryOperator<Integer> binary = (x, y) -> {
            int a = x * 2;
            int b = y + 2;
            return a + b;
        };
        System.out.println(binary.apply(1, 2));// 6

    }

5.典型函数式接口的使用

Consumer<T> : 消费型接口(无返回值,有去无回)
         void accept(T t);
 Supplier<T> : 供给型接口
         T get();
         
 Function<T,R> : 函数型接口
        R apply(T t);
        
 Predicate<T> : 断言型接口
        boolean test(T t);
        
四大核心接口的-->扩展子接口

Predicate接口适合用于过滤,测试对象是否符合某个条件,Predicate接口源码如下:

@FunctionalInterface
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);
    }
}

可以看到,Predicate接口待实现的唯一抽象方法是 boolean test(T t) 方法。我们用Predicate接口实现从整数型数组中过滤正数:

    public static void main(String[] args)
    {  
        List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
        filter(numbers, n -> n > 0);
    }
    
    public static void filter(List<Integer> numbers, Predicate<Integer> condition)
    {
        for (Integer number : numbers)
        {
            if (condition.test(number))
            {
                System.out.println("Eligible number: " + number);
            }
        }
    }

运行结果如下:

Eligible number: 4
Eligible number: 5

对数组的迭代,还可以使用Stream API的方式:

    public static void main(String[] args)
    {
        List<Integer> numbers = Arrays.asList(-1, -2, 0, 4, 5);
        numbers.stream().filter(n -> n > 0).forEach(n -> System.out.println("Eligible number: " + n));
    }
上面的代码采用Stream API + Predicate接口 + Consumer接口的方式实现了同样的功能,代码量大大减少。Stream API(java.util.stream)同样是java8的新特性,将真正的函数式编程风格引入到java语言中,进一步简化了代码。

 

参考文献:https://www.jianshu.com/p/8d7f98116693

https://www.cnblogs.com/wuhenzhidu/p/lambda.html

https://blog.csdn.net/qq_28410283/article/details/80961022

https://www.runoob.com/java/java8-lambda-expressions.html

https://blog.csdn.net/Mynewclass/article/details/80169476--推荐

https://www.jb51.net/article/236453.htm---五星推荐

https://blog.csdn.net/weixin_40391011/article/details/104031152

posted @ 2021-09-01 11:29  雨后观山色  阅读(170)  评论(0编辑  收藏  举报