首页 > 编程语言 >Java 8 函数式接口和Lambda表达式

Java 8 函数式接口和Lambda表达式

时间:2022-10-24 20:24:03浏览次数:53  
标签:Java 函数 int 接口 interface 表达式 Lambda

Java 一直是一种面向对象的编程语言。这意味着 Java 编程中的一切都围绕着对象(为了简单起见,除了一些基本类型)。我们不仅有 Java 中的函数,它们还是 Class 的一部分,我们需要使用 class/object 来调用任何函数。

函数式接口

当我们研究一些其他的编程语言时,比如C++JavaScript,它们被称为函数式编程语言,因为我们可以编写函数并在需要的时候使用它们。其中一些语言支持面向对象编程和函数式编程。

面向对象有很多优势,但是它也使得程序变得冗长。例如,假设我们必须创建Runnable的一个实例。通常我们使用如下的匿名类:

Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("This is Runnable");
    }
};

从上面的代码中我们可以发现,实际使用的部分是run()方法中的代码。剩下的所有代码都是因为Java程序结构化的方式。

Java 8函数式接口和Lambda表达式通过删除大量的固定代码,帮助我们编写更少、更简洁的代码。

Java 8 函数式接口

有且只有一个抽象方法的接口称为函数式接口

Java 8引入了@FunctionalInterface注解将接口标记为函数式接口。

不是使用@FunctionalInterface注解的接口才是函数式接口,使用它是为了检查函数式接口的正确性,使用它是一种规范,就像@Override用来检查重写父类或实现接口的方法的正确性。
例如,我们在一个接口之上使用了该注解,并在其中添加多个抽象方法,此时会引发编译器错误。

java.lang.Runnable就是使用单个抽象方法run()的函数式接口的一个很好的例子。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Java 8函数式接口的主要好处是,我们可以使用Lambda表达式来实例化它们,并避免使用笨重的匿名类实现。

Java 8 Collections API 已经被重写,并且引入了新的Stream API,其中使用了大量的函数式接口。Java 8在java.util.function包中定义了很多函数式接口,一些常用的函数式接口包括ConsumerSupplierFunctionPredicate等等。

下面是一些代码片段,以便我们更好的理解函数式接口

interface Test {
    boolean equals(Object obj);
}
//Test不是函数式接口,equals是Object对象的一个成员方法

interface Comparator<T> {
    boolean equals(Object obj);
    int compare(T o1, T o2);
}
//Comparator是函数式接口,Comparator只有一个抽象的非Object的方法

interface Test2 {
    int test2();
    Object clone();
}
//Test2不是函数式接口,因为Object.clone()方法不是public而是protected

interface X {
    int test(String str);
}
interface Y {
    int test(String str);
}
interface Z extends X, Y {
}
//Z是函数式接口,继承了两个相同签名相同返回值的方法

interface X {
    List test(List<String> list);
}
interface Y {
    List<String> test(List list);
}
interface Z extends X, Y {
}
//Z是函数式接口,Y.test 是一个 subsignature & return-type-substitutable
//关于subsignature & return-type-substitutable参考https://www.ssymon.com/archives/subsignature-return-type-substituable
//这里Y.test签名是X.test签名的subsignature,并且Y.test的返回值类型和X.test返回值类型可以兼容

interface X {
    int test(List<String> list);
}
interface Y {
    int test(List<Integer> list);
}
interface Z extends X, Y {
}
//Z不是函数式接口,两个抽象方法没有一个是subsignature
//虽然方法签名与泛型无关,但X.test和Y.test无法兼容,Z的编译就会出错

interface X {
    long test();
}
interface Y {
    int test();
}
interface Z extends X, Y {
}
//编译出错:methods have unrelated return types
//Z不是函数式接口,两个方法返回值不相关不兼容,没有一个是return-type-substitutable

interface A<T> {
    void test(T arg);
}
interface B<T> {
    void test(T arg);
}
interface C<X, Y> extends A<X>, B<Y> {
}
//编译错误:both methods have same erasure, yet neither overrides the other
//C不是函数式接口,两个方法签名不同,擦除之后变成相同的原生类型

Lambda表达式

通过Lambda表达式,我们可以在面向Java对象的世界中可视化函数式编程。对象是Java编程语言的基础,没有对象就没有函数,这就是为什么Java语言只支持在函数式接口中使用Lambda表达式。由于函数式接口中只有一个抽象函数,因此将Lambda表达式应用于该方法时不会出现混淆。

什么是Lambda表达式:Lambda表达式是一个匿名函数,即没有函数名的函数。

在Java中:所有函数都是类的成员,被称为方法。要创建方法,您需要定义其所属的类。

Lambda表达式语法:箭头前面部分是方法的参数列表,后一部分方法主体

(parameters) -> expression
或
(parameters) -> { statements; }

Lambda表达式使得我们可以使用非常简洁的语法定义一个类和单个方法,以实现具有单个抽象方法的接口,即函数式接口

下面我们用一些代码来弄明白Lambda表达式如何简化和缩短代码,并使得代码更具备可读性和可维护性。

实现接口:在Java 8之前,如果要创建线程,首先要定义一个实现可运行接口的类。即函数式接口java.lang.Runnable,其抽象方法run()不接受任何参数。我们需要定义一个实现类来实现它。

public class TestRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("TestRunnable is running");
    }
    public static void main(String[] args) {
        TestRunnable r = new TestRunnable();
        Thread thread = new Thread(r);
        thread.start();
    }
}

在此示例中,我们实现了run()方法将一串字符串打印在控制台。然后创建一个名为 r 的实例对象,并将其传递给线程类的构造函数来创建一个线程对象,并调用线程的start方法。

匿名内部类:我们对上面的代码进行一些改进,使用匿名内部类的方式来实现。

public static void main(String[] args) {
    Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("Runnable inner class is running");
        }
    });
    thread1.start();
}

相比实现接口的方式,匿名内部类的方式更加简洁,无序额外新增实现类。

使用Lambda表达式:在Java 8中,使用Lambda重构的代码更简洁,更具可读性。

public static void main(String[] args) {
    Thread thread = new Thread(() -> System.out.println("Runnable lambda is running"));
    thread.start();
}

从上方的Lambda表达式中我们可以发现:

  • Runnable是一个函数式接口,所以我们可以使用Lambda表达式来创建它的实例。
  • run()方法没有参数,所以Lambda表达式也没有参数。
  • 因为方法体中只有一条语句,所以可以不使用大括号({})。对于多个语句,则必须像其他方法一样使用大括号,就像if-else块一样。

为什么需要Lambda表达式

  1. 代码行数减少,通过对以上的代码对比我们可以发现使用Lambda表达式可以减少需要编写的代码量以及减少必须创建和维护的自定义类的数量。

    比如要实现只使用一次的接口,那么创建另一个代码文件或另一个命名类并不总是很有意义。Lambda表达式可以定义一次匿名实现,以供一次性使用,并显着简化代码。

  2. 行为参数化,通过行为参数化来传递代码。

    举个例子,假如我们对列表中符合给定条件的数字进行求和,使用谓词方式如下:

    public static int sumWithCondition(List<Integer> numbers, Predicate<Integer> predicate) {
        return numbers.parallelStream()
                .filter(predicate)
                .mapToInt(i -> i)
                .sum();
    }
    

    使用实例:

    List<Integer> numbers = new ArrayList<>();
    for (int i = 0; i < 20; i++) {
        numbers.add(i);
    }
    //对所有数字求和
    sumWithCondition(numbers, n -> true);
    //对偶数数字求和
    sumWithCondition(numbers, i -> i % 2 == 0);
    //对大于5的数字求和
    sumWithCondition(numbers, i -> i > 5);
    

    再举个例子,求出列表中3到11范围内的最大奇数并返回它的平方:

    public static int findSquareOfMaxOdd(List<Integer> numbers) {
        return numbers.stream()
                .filter(FunctionalInterfaceTest::isOdd)
                .filter(FunctionalInterfaceTest::isGreaterThan3)
                .filter(FunctionalInterfaceTest::isLessThan11)
                .max(Comparator.naturalOrder())
                .map(i -> i * i)
                .get();
    }
    
    public static boolean isOdd(int i) {
        return i % 2 != 0;
    }
    
    public static boolean isGreaterThan3(int i) {
        return i > 3;
    }
    
    public static boolean isLessThan11(int i) {
        return i < 11;
    }
    

    (::)是Java 8的方法引用(即把这个方法作为值),如上面的FunctionalInterfaceTest::isOdd,意思是将isOdd()方法传递给filter()方法。它是Lambda表达式i -> isOdd(i)的简写形式。

    什么是谓词(Predicate)?
    谓词指的是条件表达式的求值返回true或false的过程,它接受一个参数值并返回true或false。

  3. 在Stream API中使用,后续再详细介绍Stream API

Lambda表达式示例

() -> {}                     // No parameters; void result

() -> 42                     // No parameters, expression body
() -> null                   // No parameters, expression body
() -> { return 42; }         // No parameters, block body with return
() -> { System.gc(); }       // No parameters, void block body

// Complex block body with multiple returns
() -> {
  if (true) return 10;
  else {
    int result = 15;
    for (int i = 1; i < 10; i++)
      result *= i;
    return result;
  }
}                          

(int x) -> x+1             // Single declared-type argument
(int x) -> { return x+1; } // same as above
(x) -> x+1                 // Single inferred-type argument, same as below
x -> x+1                   // Parenthesis optional for single inferred-type case

(String s) -> s.length()   // Single declared-type argument
(Thread t) -> { t.start(); } // Single declared-type argument
s -> s.length()              // Single inferred-type argument
t -> { t.start(); }          // Single inferred-type argument

(int x, int y) -> x+y      // Multiple declared-type parameters
(x,y) -> x+y               // Multiple inferred-type parameters
(x, final y) -> x+y        // Illegal: can't modify inferred-type parameters
(x, int y) -> x+y          // Illegal: can't mix inferred and declared types

方法引用和构造方法引用示例

System::getProperty
System.out::println
"abc"::length
ArrayList::new
int[]::new
关注微信公众号

标签:Java,函数,int,接口,interface,表达式,Lambda
From: https://www.cnblogs.com/ssymon/p/16822661.html

相关文章

  • 正则表达式
    正则表达式(regularexpression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。构......
  • 面试题JavaScript基础
    原博客地址01.如何开启js严格模式?js严格模式有什么特点?参考点:js基础知识参考答案://全局开启'usestcict'//局部开启functionfu(){'usestrict'}/*1......
  • Java反射获取方法参数名
      正常环境下,获取不到参数的名称,使用java反射时,第一个参数名是arg0,第二个参数是arg1,与我们代码中写的对不上。java反射过程中,需要我们做好判断:if(!parameter.isName......
  • 常见的JavaScript的循环处理数据方法
    1.for循环letarr=[1,2,3];for(leti=0;i<arr.length;i++){ console.log(i,arr[i])}//01    //12   //232.forin循环letobj={name:'zhou......
  • Java Apache POI 小记(读取Word通过模板创建PPT)
    @目录起因过程确定工具功能拆分读取Word文件通过PPT模板创建PPT并填充内容将PPT转为图片总结起因近期身边的一位朋友来寻求帮助,她在日常工作时,总是需要做一些重复的事情,......
  • 面试 个人摸底监测 考察JavaScript基础 (第三天)
    01,如何开启JS严格模式?JS严格模式有什么特点?两种方式全局开启在js开头加上'usestrict'局部开启,在作用域开头加上functionfn(){'usestrict'}特点:1,全局变量必须......
  • Java并发编程学习10-任务执行与Executor框架
    任务执行何为任务?任务通常是一些抽象且离散的工作单元。大多数并发应用程序都是围绕着“任务执行”来构造的。而围绕着“任务执行”来设计应用程序结构时,首先要做的......
  • JavaScript 设计模式之策略模式
    什么是设计模式?为什么需要学习设计模式?学习设计模式的目的是:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。设计模式使代码编写真正工程化;设计模式是软件工......
  • springcloud学习记录day06--在Java中使用elasticsearch
    RestClient查询文档发起查询请求以matchall为例代码解读:第一步,创建SearchRequest对象,指定索引库名第二步,利用request.source()构建DSL,DSL中可以包含查询、分页......
  • java-文件上传
    一、图片上传文件上传一般使用云服务器,常见的云服务器有阿里云,七牛云服务器。这里使用七牛云`publicclassQiniuUtils{publicstaticStringaccessKey="你自......