JAVA8新特性-lambda表达式

接口

java8之前只允许提供 公共抽象方法( public abstract ) 和 静态常量(public static final)。

如果接口需要添加新方法,那么原有代码涉及到该接口的实现类都需要重写,提供实现类。

java8 提供的默认方法就成功解决了上面的问题,并使提高代码效率。

默认方法
  • 定义默认方法需要在方法前 添加关键字default,并提供对应的实现方法(可以是空方法)。
  • 默认方法访问权限默认是public
  • 一个接口中可以包含多个默认方法。
  • 如果是一个接口继承了另外一个接口,2个接口中也包含相同的默认方法,那么继承接口的版本具有更高的优先级。比如A继承了B接口,那么优先使用A接口里面的default方法;
  • 通过使用super,可以显式的引用被继承接口的默认实现,语法如下:InterfaceName.super.methodName()
public interface InterfaceTest {

    default void defaultMethod() {
        System.out.println("默认方法......");
    }
    
    void test();
}

测试:

        InterfaceTest obj = new InterfaceTest() {
            @Override
            public void test() {
                System.out.println("test..........");
                defaultMethod();
            }
        };
        obj.test();

test…
默认方法…

静态方法

JAVA8 接口的另一个特性支持静态方法

	public interface InterfaceDemo{
		public default void test(){
			
		}
		public static void test1(){
		}
	}

lambda

lambda 表达式是一种语法糖,用于简化代码,提高开发效率。

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

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。
// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)

有一个参数,无返回值

 Consumer<String> consumer = new Consumer<String>() {
     @Override
     public void accept(String s) {
         s.concat(" World");
     }
 };

// 存在一个参数,lambda表达式替换, 此时consumer只能接受Object类型参数
Consumer  consumer = (Consumer<String>) s -> s.concat(" World");
//编译无错,运行时错误。 java.lang.Integer cannot be cast to java.lang.String
//consumer.accept(3);
consumer.accept("Hello ");

//未指定泛型实际类型,此时编译出错。 s当做Object类型处理。 Cannot resolve method 'concat' in 'Object'
//Consumer consumer = s -> s.concat(" World");

// 此时consumer 只能接收 String类型参数,同时在方法体也识别到了参数是字符串类型
Consumer<String>  consumer = s -> s.concat(" World");
// 编译错误
//consumer.accept(1);
consumer.accept("Hello ");

无参数,有返回值。

 Supplier<String> supplier = new Supplier() {
     @Override
     public String get() {
         return "Hello";
     }
 };

//lambda表达式替换。 方法体中只包含一个语句, 省略大括号。同时把表达式作为结果返回。
Supplier<String> supplier = () -> "Hello";
lambda表达式与匿名内部类

以常用的Runnable接口为例,创建一个线程。

最原始的代码:

class MyRunnable implements Runnable{
    @Override
    public void run() {
        // 逻辑处理
    }
}
 Runnable  runnable  =new MyRunnable();
 Thread thread = new Thread(runnable); 

匿名内部类:

 Thread thread = new Thread(new Runnable() {
     @Override
     public void run() {
         //......处理....
     }
 });

lambda表达式:

    Thread thread = new Thread(() -> {
        //..........
    });

可以看到,匿名内部类和 lambda都是用于简化代码。

不过它们都有各自的限制:

  • 匿名内部类使用限制,必须存在无参构造函数。 可以对接口,抽象类,普通类(非final)使用
  • lambda 使用限制,必须是函数式接口(使用@FunctionalInterface注解)才能使用lambda表达式。
Lambda 访问外部变量
局部变量

匿名内部类、lambda表达式访问外部变量时,可能会出现编译错误:

  String str = "hello";
  str = ""; // 再次修改
  Supplier supplier = new Supplier<String>() {
      @Override
      public String get() {
          return str + " world";
      }
  };

Variable ‘str’ is accessed from within inner class, needs to be final or effectively final

    String str = "hello";
    str = "";
    Supplier supplier = (Supplier<String>) () -> str + " world";

Variable used in lambda expression should be final or effectively final

原因是,内部类、lambda表达式引用的局部变量需要时final或者事实final。

  • 声明为final类型。 保证了引用不变
  • 不声明final类型,但是必须保证不可被后面的代码修改。 隐性的具有 final 的语义


一个局部变量如果要在 Java 7/8 的匿名类或是 Java 8 的 Lambda 表达式中访问,那么这个局部变量必须是 final 的,即使没有 final 饰它也是 final 类型。如果访问的局部变量不是 final 类型的话,编译器自动加上 final 修饰符。

String str = "hello";
str = "";     //注释掉这行或下行中另一行才能编译通过
Supplier supplier = (Supplier<String>) () -> str + " world";  //这行让编译器决定给 version 加上 final 属性

为什么 Lambda 表达式(匿名类) 不能访问非 final 的局部变量呢?因为实例变量存在堆中,而局部变量是在栈上分配,Lambda 表达(匿名类) 会在另一个线程中执行。如果在线程中要直接访问一个局部变量,可能线程执行时该局部变量已经被销毁了,而 final 类型的局部变量在 Lambda 表达式(匿名类) 中其实是局部变量的一个拷贝。

Any local variable, formal parameter, or exception parameter used but not declared in a lambda expression must either be declared final or be effectively final (§4.12.4), or a compile-time error occurs where the use is attempted.

实例变量、静态变量

在 Lambda 表达式中对成员变量和静态变量可以正常读写。

调用接口默认方法

带有默认实现的接口方法,是不能在 lambda 表达式中访问的。

public interface InterfaceTest {

    default void defaultMethod() {
        System.out.println("默认方法......");
    }

    void test();
}

 InterfaceTest obj1= () -> {
     System.out.println("test..........");
     defaultMethod();
 };

编译无法通过,提示

Cannot resolve method ‘defaultMethod’ in ‘Test’

小结

可能会把捕获外部变量的 Lambda 表达式称为闭包,那么 Java 8 的 Lambda 可以捕获什么变量呢?

  1. 捕获实例或静态变量是没有限制的(可认为是通过 final 类型的局部变量 this 来引用前两者)
  2. 捕获的局部变量必须显式的声明为 final实际效果的的 final 类型

Stack Overflow 问题及解决

lambda 表达式必须为常量的原因

函数式接口

a functional interface has exactly one abstract method. Since default methods have an implementation, they are not abstract

If an interface declares an abstract method overriding one of the public methods of java.lang.Object, that also does not count toward the interface’s abstract method count since any implementation of the interface will have an implementation from java.lang.Object or elsewhere.

Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.

函数式接口的概念(Functional Interface)就是精确包含一个抽象方法的接口。 这类接口可以使用 lambda表达式方法引用构造函数引用

如果这个接口定义的抽象方法覆盖了 Object类的某个public方法,这个抽象方法不会被统计为抽象方法。

前面提到JAVA8以后接口支持默认方法,默认方法并非是抽象方法(有方法体),所以不会影响lambda表达式使用。

@FunctionalInterface, 该注解就是标识接口是函数式接口。使用该注解是一种语法规范,约束该接口只有一个抽象方法,如果该类存在多个抽象方法,编译器会立刻抛出错误提示。 但是不代表不使用该注解的接口就一定标记为非函数式接口

比如java.util.function 包下的Function 接口,

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

测试:

  Function<String,Integer> function = new Function<String, Integer>() {
      @Override
      public Integer apply(String o) {
          return Integer.valueOf(o);
      }
  };
  System.out.println(function.apply("123"));

使用lambda表达式简化后

 Function<String,Integer> function = s -> Integer.valueOf(s);
引用类的构造器及方法

JAVA8 中可以使用关键字 :: 来引用类的方法(静态)构造函数

1、引用方法

上面的例子,可以进一步简化,结果如下:

 Function<String,Integer> function = s -> Integer.valueOf(s);
Function<String,Integer> function = Integer::valueOf;

上面示例用到的是引用静态方法,当然也可以引用实例方法。

如下,将字符串转化为Integer。 下面的代码用到了java8的另一个新特性 Stream

  Function<String, Integer> function = s -> Integer.valueOf(s);
  Stream.of("12").map(function::apply).forEach(System.out::println);


上面的都是一个参数,如果存在两个参数呢。

比如,方法: reduce(BinaryOperator accumulator)。 参数是BinaryOperator 抽象方法: R apply(T t, U u)

        Integer[] integers = new Integer[]{1, 2, 3, 4, 5};
        Integer maxValue = Stream.of(integers).reduce(Integer::sum).get();
        System.out.println(maxValue);

上面两个参数,但是调用的静态方法。 如果多个参数调用 实例方法呢。

        Integer[] integers = new Integer[]{1, 2, 3, 4, 5};
        Integer maxValue = Stream.of(integers).max(Integer::compareTo).get();
        System.out.println(maxValue);

总结:

  1. 如果传入的方法是一个类型的静态方法,而且参数匹配,使用“类的静态方法引用”;
  2. 如果传入的方法是一个实例的成员方法,而且参数匹配,使用“实例的成员方法”;
  3. 如果传入的方法是一个类型T的实例的成员方法,而且参数为N-1个,缺少了一个T类型的参数,那么就使用“T类型的实例方法”。

参考文档: java lambda方法引用总结——烧脑吃透

2、引用构造函数

定义一个实体类,存在有参和无参两个构造函数。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class EntityTest {
    private String key;
}

下面用到Java8中Map的新方法computeIfAbsent。

Map<String, EntityTest> map = new HashMap<>();
map.computeIfAbsent("test", EntityTest::new);// 如果key对应的value不存在,则会创建一个实体并返回。存在则直接返回对应的value
System.out.println(map);

输出结果:

{test=EntityTest(key=test)}

EntityTest::new调用构造函数, Java 编译器能够根据上下文选中正确的构造器去实现。根据返回结果也可以看出,实际调用的是有参构造函数。

当然了根据computeIfAbsent方法定义,可以看到调用的是Function,Function是函数型接口,只有一个抽象方法 apply。apply方法的入参,也是EntityTest构造函数的入参。

    default V computeIfAbsent(K key,
            Function<? super K, ? extends V> mappingFunction)


	@FunctionalInterface
	public interface Function<T, R> {
	    /**
	     * Applies this function to the given argument.
	     *
	     * @param t the function argument
	     * @return the function result
	     */
	    R apply(T t);
	}
JAVA8 内置的函数型接口
接口抽象方法描述
FunctionR apply(T t)一个参数,并返回结果
Predicateboolean test(T t)一个参数,返回boolean值
Consumervoid accept(T t)一个参数,无返回值
SupplierT get()无参,有返回值

上面接收参数的接口,仅能接收一个。 添加Bi前缀,接收两个参数。

接口抽象方法描述
BiFunctionR apply(T t, U u)接收两个参数,并返回结果
BiPredicateboolean test(T t, U u)接收两个参数,返回boolean值
BiConsumervoid accept(T t, U u)接收两个参数,无返回值

这些函数式接口在stream中被广泛使用。

除此之外,还贴心的提供了基本类型的操作。只是将参数类型或者返回类型,定义为具体类型。 如果类型不匹配那么会有编译错误。

@FunctionalInterface
public interface IntFunction<R> {
    R apply(int value);
}


@FunctionalInterface
public interface IntToLongFunction {
    long applyAsLong(int value);
}


@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
    public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
    }

    public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
    }
}

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值