Java8新特性——Lambda表达式

Java8新特性——Lambda表达式

  • 一、Java8新特性
    • 1.1 Java8 新增特性
    • 1.2 Java8 前后的区别
  • 二、 lambda表达式简介
    • 2.1 什么是Lambda表达式?
    • 2.2 为什么要使用Lambda表达式?
    • 2.3 从匿名内部类到 Lambda 的转换
  • 三、Lambda基础语法
    • 3.1 语法一:无参无返回值,Lambda 体只需要一条语句
    • 3.2 语法二:传递一个参数,无返回值
    • 3.3 语法三:传递一个参数时,参数的小括号可以省略,无返回值
    • 3.4 语法四:传递两个参数,有返回值
    • 3.5 语法五:当 Lambda 体只有一条语句时,return 与大括号可以省略
  • 四、函数式接口
    • 4.1 什么是函数式接口?
    • 4.2 自定义函数式接口(普通接口)
    • 4.3 函数式接口中使用泛型
    • 4.4 Java 内置四大核心函数式接口
    • 4.5 其他接口
  • 五、Lambda表达式的规范
  • 六、方法引用、构造器引用与数组引用
    • 6.1 方法引用
    • 6.2 构造器引用
    • 6.3 数组引用

一、Java8新特性

Java8新特性(又称为 jdk1.8)是Java语言开发的一个主要版本。Oracle公司于2014年3月18日发布Java8,它支持函数式编程,新的JavaScript引擎,新的日期API,新的Stream API等。——摘自菜鸟教程

1.1 Java8 新增特性

  • Lambda 表达式 − Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。
  • 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有 Java 类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
  • 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
  • 新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
  • Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
  • Date Time API − 加强对日期与时间的处理。
  • Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
  • Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。

1.2 Java8 前后的区别

  • Java8 之前
    interface之中可以定义变量和方法,变量必须是 public、static、final 修饰的,方法必须是 public、abstract 修饰的。(其中 public 类型和抽象方法需要子类实现。

    /**
     * @author QHJ
     * @date 2021/11/17  11:39
     * @description: Java8 接口
     */
    public interface JDK8Interface {
           
        // 定义变量
        public String userName = "zhangsan";
        static String password= "123456";
        final int age = 0;
    
        // 定义方法
        void get();
    }
    
    
    
    /**
     * @author QHJ
     * @date 2021/11/17  11:42
     * @description: Java8 实现类
     */
    public class JDK8InterfaceImpl implements JDK8Interface {
           
        @Override
        public void get() {
           
            System.out.println("这是public抽象方法的实现类...");
        }
    }
    
  • Java8 开始
    interface中的方法支持使用 static 和 default 修饰,可以写方法体,但不需要子类重写。(public类型和抽象方法仍然需要子类实现。

    /**
     * @author QHJ
     * @date 2021/11/17  11:39
     * @description: Java8 接口
     */
    public interface JDK8Interface {
           
        // 定义变量
        public String userName = "zhangsan";
        static String password= "123456";
        final int age = 0;
    
        // 定义方法
        void get();
    
        /**
         * 默认方法,可以写方法体
         */
        default void getDefault(){
           
            System.out.println("这是默认方法,可以写方法体...");
        }
    
        /**
         * 静态方法,可以写方法体
         */
        static void getStatic(){
           
            System.out.println("这是静态方法,可以写方法体...");
        }
    }
    
    
    
    /**
     * @author QHJ
     * @date 2021/11/17  11:42
     * @description: Java8 实现类
     */
    public class JDK8InterfaceImpl implements JDK8Interface {
           
        @Override
        public void get() {
           
            System.out.println("这是重写的public方法...");
        }
    
        /**
         * 默认和静态方法不需要重写·
         */
        /*@Override
        public void getDefault() {
            System.out.println("这是重写的默认方法...");
        }*/
    }
    

    小贴士:
    Java8 前
    接口定义的方法:使用public修饰的方法和抽象方法需要子类实现;
    接口定义的变量:使用public、static、final修饰。
    Java8 开始
    接口定义的方法:static、default修饰的为普通方法,可以有方法体,不需要子类重写;
    public、抽象方法没有方法体,需要子类重写;
    接口定义的变量:使用public、static、final修饰。

二、 lambda表达式简介

2.1 什么是Lambda表达式?

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递),从而可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使 Java 的语言表达能力得到了提升。

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

2.2 为什么要使用Lambda表达式?

Lambda 可以以非常简洁的形式调用我们的匿名函数接口。

有这样一个场景:
在 java 中我们很容易对一个变量进行赋值,比如:int a = 1;String b = “zs”;
但是如何将一段代码或者一个函数赋值给一个变量呢?这个变量应该是什么类型的?
Java8新特性——Lambda表达式_第1张图片

Java8 中的新特性很容易就解决了这个问题。

/**
    * Test--OrderService
    */
   System.out.println("======OrderService======");
   // 1、使用new的实现类的形式调用接口
   OrderService orderService = new OrderServiceImpl();
   orderService.addOrder();
   // 2、使用匿名内部类接口调用
   new OrderService(){
     
       @Override
       public void addOrder() {
     
           System.out.println("使用匿名内部类的形式调用接口...");
       }
   }.addOrder();
   // 3、使用lambda调用接口
   OrderService orderService1 = () -> System.out.println("使用lambda调用OrderService接口...");
   orderService1.addOrder();

2.3 从匿名内部类到 Lambda 的转换

package cn.qhj.lambda;
import java.util.Comparator;
import java.util.TreeSet;

/**
 * @author QHJ
 * @date 2021/11/21  11:36
 * @description:
 */
public class ToLambda {
     
    public static void main(String[] args) {
     
        // 匿名内部类
        Runnable runnable1 = new Runnable() {
     
            @Override
            public void run() {
     
                System.out.println("Hello!这是匿名内部类...");
            }
        };
        // Lambda表达式
        Runnable runnable2 = () -> System.out.println("Hello!这是Lambda表达式...");



        // 使用匿名内部类作为参数传递
        TreeSet<String> treeSet1 = new TreeSet<>(new Comparator<String>() {
     
            @Override
            public int compare(String o1, String o2) {
     
                return Integer.compare(o1.length(), o2.length());
            }
        });
        // 使用 Lmabda 表达式作为参数传递
        TreeSet<String> treeSet2 = new TreeSet<>(
                ((o1, o2) -> Integer.compare(o1.length(), o2.length()))
        );
    }
}

三、Lambda基础语法

Lambda 表达式在 Java 语言中引入了一个新的语法元素和操作符。这个操作符为 "->",该操作符被称为Lambda 操作符或箭头操作符。它将 Lmabda 分为两个部分:

左侧:指定了 Lambda 表达式需要的所有参数;
右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能。

根据参数传递情况和是否有返回值,Lambda 表达式有五种语法格式:

下述例子中的相关接口:

/**
 * @author QHJ
 * @date 2021/11/21  15:44
 * @description:
 */
@FunctionalInterface
public interface LambdaInterface {
     
    void get();
    void get(int i);
    void get(int i, int j);
    String get(int i);
    String get(int i, int j);
}

3.1 语法一:无参无返回值,Lambda 体只需要一条语句

() -> System.out.println("....")

/**
 * Test--LamdaInterface
 */
// 1、使用lambda调用无参方法
System.out.println("======LamdaInterface======");
LambdaInterface lambdaInterface = () -> {
     
    System.out.println("使用lambda表达式调用无参方法...");
};
lambdaInterface.get();
// 2、精简写法:如果方法体中只有一条语句的情况下,可以不需要()
LambdaInterface lambdaInterface1 = () ->
        System.out.println("使用lambda调用无参方法的精简写法...");
lambdaInterface1.get();

3.2 语法二:传递一个参数,无返回值

(args) -> System.out.println(args)

/**
 * Test--LamdaInterface(一个参数)
 */
System.out.println("======LambdaInterface======");
// 1、使用匿名内部类调用有参方法
String result = new LambdaInterface(){
     
    @Override
    public void get(int i) {
     
        System.out.println(i);
    }
}.get(1);
System.out.println("使用匿名内部类调用:" + result);
// 2、使用lambda调用有参方法
LambdaInterface lambdaInterface1 = (i) -> {
     
    System.out.println(i);
};
System.out.println("使用lambda表达式调用有参方法:" + lambdaInterface1.get(1));

3.3 语法三:传递一个参数时,参数的小括号可以省略,无返回值

args -> System.out.println(args)

/**
 * Test--LamdaInterface(传递一个参数时可省略())
 */
System.out.println("======LambdaInterface======");
// 2、使用lambda调用有参构造函数(一个参数时,可省略小括号)
LambdaInterface lambdaInterface = i -> {
     
    System.out.println(i);
};
System.out.println("使用lambda表达式调用有参方法:" + lambdaInterface.get(1));	

3.4 语法四:传递两个参数,有返回值

(i, j) -> { return i + "--" + j; }

/**
 * Test--LamdaInterface
 */
System.out.println("======LambdaInterface======");
// 1、使用匿名内部类调用有参方法
String result = new LambdaInterface(){
     
    @Override
    public String get(int i, int j) {
     
        return i + "--" + j;
    }
}.get(1, 2);
System.out.println("使用匿名内部类调用:" + result);
// 2、使用lambda调用有参构造函数
LambdaInterface lambdaInterface = (i, j) -> {
     
    return i + "--" + j;
};
System.out.println("使用lambda表达式调用有参方法:" + lambdaInterface.get(1, 2));

3.5 语法五:当 Lambda 体只有一条语句时,return 与大括号可以省略

(i, j) -> return i + "--" + j;

// 3、精简写法:如果方法体中只有一条return的情况下不需要写{}和return
LambdaInterface lambdaInterface = (i, j) -> i + "--" + j;
System.out.println("使用lambda调用有参方法:" + lambdaInterface.get(1, 2));

在这里插入图片描述

类型推断:
上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的 “类型推断”。

四、函数式接口

4.1 什么是函数式接口?

  • 只包含一个抽象方法的接口,称为函数式接口
  • 你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。
  • 我们可以在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。

4.2 自定义函数式接口(普通接口)

/**
 * @author QHJ
 * @date 2021/11/7  15:01
 * @description: 普通的函数式接口
 */
// 使用Lambda表达式,依赖于函数式接口。当接口中只有一个抽象方法时,默认此接口就是函数式接口
@FunctionalInterface  // 当有@FunctionalInterface注解时,表示标记该接口为函数接口
public interface MyFunctionalInterface {
     
    void add();

    /**
     * 使用default修饰为普通方法
     * 注意:在接口中只能有一个抽象方法,通过使用default关键字可将其修饰为普通方法
     */
    default void get(){
     
        System.out.println("这是默认的方法...");
    }

    /**
     * 可以定义object类中的方法
     * object父类中的方法可以在函数接口中重写
     * @return
     */
    String toString();
}
/**
 * Test--MyFunctionalInterface
 */
System.out.println("======MyFunctionalInterface======");
// 1、使用匿名内部类的方式调用接口
new MyFunctionalInterface(){
     
    @Override
    public void add() {
     
        System.out.println("这是使用匿名内部类时MyFunctionalInterface重写的add()方法...");
    }
}.add();
// 2、使用lambda调用接口
MyFunctionalInterface myFunctionalInterface = () -> System.out.println("使用lambda调用MyFunctionalInterface接口...");
myFunctionalInterface.add();

4.3 函数式接口中使用泛型

/**
 * @author QHJ
 * @date 2021/11/21  16:24
 * @description: 函数式接口中使用泛型
 */
@FunctionalInterface
public interface MyFunction<T> {
     
    public T getValue(T t);
}
public static String toUpperString(MyFunction<String> myFunction, String str){
     
    return myFunction.getValue(str);
}


/**
 * 作为参数传递 Lambda 表达式
 * 为了将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该 
 * Lambda 表达式兼容的函数式接口的类型。
 */
System.out.println("======作为参数传递 Lambda 表达式======");
String newStr = toUpperString(s -> s.toUpperCase(), "abCDfse");
System.out.println(newStr);

4.4 Java 内置四大核心函数式接口

函数式接口 参数类型 返回类型 用途
Consumer< T > 消费型接口 T void 对类型为T的对象应用操作。包含方法:void accept(T t);
Supplier< T > 供给型接口 T 返回类型为T的对象。包含方法:T get();
Function 函数型接口 T R 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t);
Predicate< T > 断定型接口 T boolean 确定类型为T的对象是否满足某约束,并返回boolean 值。包含方法boolean test(T t);

4.5 其他接口

函数式接口 参数类型 返回类型 用途
BiFunction T, U R 对类型为 T, U 参数应用操作, 返回 R 类型的结 果。包含方法为R apply(T t, U u);
UnaryOperator< T > (Function子接口) T T 对类型为T的对象进行一元运算, 并返回T类型的结果。包含方法为T apply(T t);
BinaryOperator< T >(BiFunction 子接口) T, T T 对类型为T的对象进行二元运算,并返回T类型的结果。包含方法为T apply(T t1, T t2);
BiConsumer T, U void 对类型为T, U 参数应用操作。包含方法为void accept(T t, U u)
ToIntFunction< T >、ToLongFunction< T >,、ToDoubleFunction< T > T int,、long,、double 分 别 计 算 int 、 long 、double、值的函数
IntFunction< R >、LongFunction< R > DoubleFunction< R > int、long、double R 参数分别为int、long、double 类型的函数

五、Lambda表达式的规范

使用 Lambda 表达式,就依赖于函数式接口:
1、在接口中只能够允许有一个抽象方法;
2、在函数式接口中定义 object 类中的方法;
3、使用默认或者静态方法;
4、@FunctionalInterface 表示该接口为函数式接口。

Java 中使用 Lambda 表达式的规范,必须是为函数式接口!

JDK 中自带的函数式接口:
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener

出了这些自带的函数式接口,我们也可以使用 @FunctionalInteface 修饰为函数式接口!

六、方法引用、构造器引用与数组引用

6.1 方法引用

结合 Lambda 表达式能够让代码变的更加精简。

当要传递给 Lambda 体的操作,已经有实现的方法了,可以使用方法引用!

方法引用规则:实现抽象方法的参数列表和返回类型,必须与方法引用方法的参数列表和返回类型保持一致!

方法引用:使用操作符 “::” 将方法名和对象或类的名字分隔开来。

方法引用主要有三种使用情况:

  • 静态方法引用

    类 :: 静态方法

    public class Test{
           
    	/**
    	 * 静态方法引用
    	 */
    	public static void getStaticMethod(){
           
    	    System.out.println("这是静态方法引用...");
    	}
    
    
    	public static void main(String[] args){
           
    		// 1、使用lambda的方式调用get()方法
    	    JDK8Interface jdk8Interface1 = () -> {
           
    	        JDK8Interface.getStatic();
    	    };
    	    jdk8Interface1.get();
    	    // 2、使用方法引用调用方法
    	    // 必须满足:方法引用的方法必须和函数接口中的方法参数列表/返回值保持一致(也就是get()方法和getStaticMethod()方法要一致)
    	    JDK8Interface jdk8Interface2 = Test::getStaticMethod;
    	    jdk8Interface2.get();
    	}
    }
    
  • 对象方法引用

    类 :: 实例方法

    public class Test{
           
    	/**
         * 对象方法引用
         * @return
         */
        public String objGet(UserEntity userEntity) {
           
            return "qhj";
        }
    
    
    	public static void main(String[] args){
           
    		/**
             * Test--对象方法引用
             */
            System.out.println("======对象方法引用======");
            // 1、使用匿名内部类的形式
            ObjectService objectService = new ObjectService() {
           
                @Override
                public String get(UserEntity userEntity) {
           
                    return userEntity.getName() + ":" + userEntity.getAge();
                }
            };
            System.out.println(objectService.get(new UserEntity("qhj", 23)));
            // 2、使用lambda的形式
            ObjectService objectService1 = (userEntity -> userEntity.getName() + ":" + userEntity.getAge());
            System.out.println(objectService1.get(new UserEntity("qhj", 22)));
            // 3、对象方法引用  这里的Test::objGet--->test.objGet()
            ObjectService objectService2 = Test::objGet;
            System.out.println(objectService2.get(new UserEntity("qhj", 22)));
    	}
    }
    
  • 实例方法引用

    对象 :: 实例方法

    public class Test{
           
    	/**
         * 实例方法引用
         * @param a
         */
        public void get(Integer a) {
           
            System.out.println("实例方法引用get方法:" + a);
        }
    
    
    	public static void main(String[] args){
           
    		/**
             * Test--实例方法引用
             */
            System.out.println("======实例方法引用======");
            Test test = new Test();
            // 1、匿名内部类的方式
            MessageInterface messageInterface = new MessageInterface() {
           
                @Override
                public void get(Integer a) {
           
                    test.get(a);
                }
            };
            messageInterface.get(1);
            // 2、lambda的方式
            MessageInterface messageInterface1 = (a) -> {
           
                test.get(a);
            };
            messageInterface1.get(2);
            // 3、实例方法引用的方式
            MessageInterface messageInterface2 = test::get;
            messageInterface2.get(3);
    		}
    }
    

6.2 构造器引用

className :: new

与函数式接口相结合,自动与函数式接口中的方法兼容。可以把构造器引用赋值给定义的方法,构造器参数列表和返回类型要与接口中抽象方法的参数列表和返回类型完全一致!

/**
 * Test--构造器引用
 * 遵循规范:函数接口返回类型::new
 */
System.out.println("======构造器引用=====");
// 1、lambda方式
UserInterface userInterface = () -> new UserEntity("qhj", 22);
System.out.println(userInterface.getUser());
// 2、构造函数引入
UserInterface userInterface = UserEntity::new;
System.out.println(userInterface.getUser());

6.3 数组引用

type[] :: new

/**
 * 数组引用
 */
System.out.println("======数组引用======");
// 1、匿名内部类的方式
Function<Integer, String[]> function1 = new Function<Integer, String[]>() {
     
    @Override
    public String[] apply(Integer t) {
     
        return new String[t];
    }
};
String[] array1 = function1.apply(5);
System.out.println(Arrays.toString(array1));
// 2、Lambda 表达式的方式
Function<Integer, String[]> function2 = length -> new String[length];
String[] array2 = function2.apply(5);
System.out.println(Arrays.toString(array2));
// 3、数组引用的方式
Function<Integer, String[]> function3 = String[] :: new;
String[] array3 = function3.apply(5);
System.out.println(Arrays.toString(array3));

你可能感兴趣的