Java8其他新特性之Lambda表达式


  

引言:

Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。Java 8 是oracle公司于2014年3月发布,可以看成是自Java 5 以来最具革命性的版本。Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性。

一、Lambda表达式

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

public class Lambda {
    @Test
    public void test1() {
        //匿名内部类
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("哈哈哈哈哈");
            }
        };
        r1.run();

        //Lambda表达式
        Runnable r2 = () -> System.out.println("嘻嘻嘻嘻嘻");
        r2.run();
    }

    @Test
    public void test2() {
        Comparator<Integer> com1 = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
            }
        };
        int compara1 = com1.compare(12, 21);
        System.out.println(compara1);

        //Lambda表达式
        Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1, o2);
        int compara2 = com2.compare(32, 21);
        System.out.println(compara2);

        //方法引用
        Comparator<Integer> com3 = Integer::compare;
        int compara3 = com3.compare(32, 21);
        System.out.println(compara3);
    }
}

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

  • 左侧:指定了 Lambda 表达式需要的参数列表(其实就是接口中的抽象方法的形参列表,如果能自动进行类型推断,则形参类型可以省略)
  • 右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能。

Lambda表达式的本质:作为接口的实例

Lambda表达式语法:

  1. 语法格式一:无参,无返回值

    Runnable r1 = () -> {System.out.println(“Hello Lambda!”);};

  2. 语法格式二:Lambda需要一个参数,但是没有返回值

    Consumer<String> con = (String str) -> {System.out.println(str);};

  3. 语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”

    Consumer<String> con = (str) -> {System.out.println(str);};

  4. 语法格式四:Lambda若只有一个参数是,参数的小括号可以省略

    Consumer<String> con = str -> {System.out.println(str);};

  5. 语法格式五:Lambda需要两个或以上的参数,多条执行语句,并且可有返回值

    Comparator<Integer> com = (x, y) -> {System.out.println(“实现函数式接口方法!”);return Integer.compare(x,y);};

  6. 语法格式六:当Lambda体只有一条语句时,return与大括号若有,都可以省略

    Comparator<Integer> com = (x, y) -> Integer.compare(x,y);

类型推断

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

二、函数式(Functional)接口

  1. 只包含一个抽象方法的接口,称为函数式接口。
  2. 你可以通过 Lambda 表达式来创建该函数式接口的对象。(若 Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
  3. 我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
  4. 在java.util.function包下定义了Java 8 的丰富的函数式接口
  5. Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,也即java不但可以支持OOP还可以支持OOF(面向函数编程)
  6. 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口
  7. 简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示
  8. 所以以前用匿名实现类表示的现在都可以用Lambda表达式来写

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

函数式接口 参数类型 返回类型 用途
Consumer<T> 消费型接口 T void 对类型为T的对象应用操作,包含方法:void accept(T t)
Supplier<T> 供给型接口 T 返回类型为T的对象,包含方法:T get()
Function<T,R> 函数型接口 T R 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t)
Predicate<T> 断定型接口 T boolean 确定类型为T的对象是否满足某约束,并返回 boolean 值。包含方法:boolean test(T t)
/**
 * 消费型接口 Consumer<T>     void accept(T t)
 * 供给型接口 Supplier<T>     T get()
 * 函数型接口 Function<T,R>   R apply(T t)
 * 断定型接口 Predicate<T>    boolean test(T t)
 */
public class LambdaTest {
    /* 消费型接口使用测试 */
    @Test
    public void test1() {
        happyTime(500, new Consumer<Double>() {
            @Override
            public void accept(Double aDouble) {
                System.out.println("买水,花费:" + aDouble);
            }
        });

        happyTime(250, money -> System.out.println("买水,花费:" + money));
    }

    public void happyTime(double money, Consumer<Double> con) {
        con.accept(money);
    }

    /* 断定型接口使用测试 */
    @Test
    public void test2() {
        List<String> list = Arrays.asList("北京", "南京", "天津", "东京", "西京", "普京");
        List<String> filterStrs1 = filterString(list, new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.contains("京");
            }
        });
        System.out.println(filterStrs1);

        List<String> filterStrs2 = filterString(list, s -> s.contains("京"));
        System.out.println(filterStrs2);
    }

    //根据给定的规则,过滤集合中的字符串。此规则是由 Predicate 的方法决定
    public List<String> filterString(List<String> list, Predicate<String> pre) {
        ArrayList<String> filterList = new ArrayList<>();
        for (String s : list) {
            if (pre.test(s)) {
                filterList.add(s);
            }
        }
        return filterList;
    }
}

  作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收Lambda表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。

其他函数式接口

函数式接口 参数类型 返回类型 用途
BiFunction<T,U,R> 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> T, U void 对类型为T, U 参数应用操作。包含方法为:void accept(T t, U u)
BiPredicate<T,U> T,U boolean 包含方法为:boolean test(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 类型的函数

三、方法引用(Method References)

  1. 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
  2. 方法引用可以看做是Lambda表达式深层次的表达。方法引用本质上就是Lambda表达式,而Lambda表达式作为函数式接口的实例。所以方法引用,也是函数式接口的实例。
  3. 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
  4. 格式:使用操作符 “::” 将类(或对象)方法名分隔开来。
    • 对象::实例方法名
    • 类::静态方法名
    • 类::实例方法名
public class Employee {
	private int id;
	private String name;
	private int age;
	private double salary;

	public String getName() {
		return name;
	}

	public Employee() {
		System.out.println("Employee().....");
	}

	public Employee(int id) {
		this.id = id;
		System.out.println("Employee(int id).....");
	}

	public Employee(int id, String name) {
		this.id = id;
		this.name = name;
	}

	public Employee(int id, String name, int age, double salary) {

		this.id = id;
		this.name = name;
		this.age = age;
		this.salary = salary;
	}

	@Override
	public String toString() {
		return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}';
	}
}

public class MethodReferencesTest {
    /** 情况一: 对象::实例方法
     * Consumer 中的 void accept(T t)
     * PrintStream 中的 void println(T t)
     */
    @Test
    public void test1() {
        //Lambda表达式
        Consumer<String> con1 = str -> System.out.println(str);
        con1.accept("北京");

        //方法引用
        PrintStream ps = System.out;
        Consumer<String> con2 = ps::println;
        con2.accept("成都");
    }
    /** 情况一: 对象::实例方法
     * Supplier 中的 T get()
     * Employee 中的 String getName()
     */
    @Test
    public void test2() {
        //Lambda表达式
        Employee emp = new Employee(1001, "Tom", 23, 5600);
        Supplier<String> sup1 = () -> emp.getName();
        System.out.println(sup1.get());

        //方法引用
        Supplier<String> sup2 = emp::getName;
        System.out.println(sup2.get());
    }

    /** 情况二: 类::静态方法
     * Comparator 中的 int compare(T t1, T t2)
     * Integer  中的 int compare(T t1, T t2)
     */
    @Test
    public void test3() {
        //Lambda表达式
        Comparator<Integer> com1 = (t1, t2) -> Integer.compare(t1, t2);
        System.out.println(com1.compare(12, 21));

        //方法引用
        Comparator<Integer> com2 = Integer::compare;
        System.out.println(com2.compare(31, 13));
    }
    /** 情况二: 类::静态方法
     * Function 中的 R apply(T t)
     * Math  中的 Long round(Double d)
     */
    @Test
    public void test4() {
        //lambda表达式
        Function<Double, Long> func1 = d -> Math.round(d);
        System.out.println(func1.apply(123.32));

        //方法引用
        Function<Double, Long> func2 = Math::round;
        System.out.println(func2.apply(123.32));
    }

    /** 情况三: 类::非静态方法
     * Comparator 中的 int compare(T t1, T t2)
     * String  中的 int t1.compareTo(t2)
     */
    @Test
    public void test5() {
        //Lambda表达式
        Comparator<String> com1 = (s1, s2) -> s1.compareTo(s2);
        System.out.println(com1.compare("ab", "abc"));

        //方法引用
        //当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数(或无参数)时:ClassName::methodName
        Comparator<String> com2 = String::compareTo;
        System.out.println(com2.compare("abc", "ab"));
    }
    /** 情况三: 类::非静态方法
     * BiPredicate 中的 boolean test(T t1, T t2)
     * String  中的 boolean t1.equals(t2)
     */
    @Test
    public void test6(){
        //Lambda表达式
        BiPredicate<String, String> pre1 = (s1, s2) -> s1.equals(s2);
        System.out.println(pre1.test("ab", "abc"));

        //方法引用
        BiPredicate<String, String> pre2 = String::equals;
        System.out.println(pre2.test("abc", "abc"));
    }
    /** 情况三: 类::非静态方法
     * Function 中的 R apply(T t)
     * Employee 中的 String toString()
     */
    @Test
    public void test7() {
        //Lambda表达式
        Employee emp = new Employee(1002, "Jerry", 23, 6500);
        Function<Employee, String> func1 = e -> e.toString();
        System.out.println(func1.apply(emp));

        //方法引用
        Function<Employee, String> func2 = Employee::toString;
        System.out.println(func2.apply(emp));
    }
}

四、构造器引用

  1. 格式: ClassName::new
  2. 与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象。
    public class ConstructorReferencesTest {
        /**
         * Supplier 中的 T get()
         * Employee 中的空参构造器 Employee()
         */
        @Test
        public void test1() {
            Supplier<Employee> sup = new Supplier<Employee>() {
                @Override
                public Employee get() {
                    return new Employee();
                }
            };
    
            Supplier<Employee> sup1 = () -> new Employee();
            System.out.println(sup1.get());
    
            Supplier<Employee> sup2 = Employee::new;
            System.out.println(sup2.get());
        }
    
        /**
         * Function 中的 R apply(T t)
         */
        @Test
        public void test2() {
            Function<Integer, Employee> func1 = id -> new Employee(id);
            System.out.println(func1.apply(1002));
    
            Function<Integer, Employee> func2 = Employee::new;
            System.out.println(func2.apply(1003));
        }
    
        /**
         * BiFunction 中的 R apply(T t, U u)
         */
        @Test
        public void test3() {
            BiFunction<Integer, String, Employee> bfunc1 = (id, name) -> new Employee(id, name);
            System.out.println(bfunc1.apply(1002, "Tom"));
    
            BiFunction<Integer, String, Employee> bfun2 = Employee::new;
            System.out.println(bfun2.apply(1003, "Jerry"));
        }
    }

五、数组引用

  1. 格式:type[]::new
  2. 可以把数组看作是一个特殊的类,则写法与构造器引用一致。
    public class ArrayReferencesTest {
        /** 数组引用
         * Function 中的 R apply(T t)
         */
        @Test
        public void test1() {
            Function<Integer, String[]> func1 = Length -> new String[Length];
            String[] arr1 = func1.apply(5);
            System.out.println(Arrays.toString(arr1));
    
            Function<Integer, String[]> func2 = String[]::new;
            String[] arr2 = func2.apply(10);
            System.out.println(Arrays.toString(arr2));
        }
    }

文章作者: YangChongZhi
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 YangChongZhi !
评论
 上一篇
Java8其他新特性之Stream_API Java8其他新特性之Stream_API
   引言: Java8中有两大最为重要的改变。一个是Lambda表达式;另一个则是Stream API。 一、Stream API 说明 Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java
2021-01-20
下一篇 
KMP模式匹配算法 KMP模式匹配算法
   引言: KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主
2021-01-16
  目录