Java8实战笔记(一)

34 阅读4分钟

1、行为参数化:让方法接受多种行为作为参数,并在内部使用它,来完成不同的行为。

测验2.1:编写灵活的prettyPrintApple方法 编写一个prettyPrintApple方法,它接受一个Apple的List,并可以对它参数化,以 多种方式根据苹果生成一个String输出(有点儿像多个可定制的toString方法)。例如,你 可以告诉prettyPrintApple方法,只打印每个苹果的重量。此外,你可以让 prettyPrintApple方法分别打印每个苹果,然后说明它是重的还是轻的。解决方案和我们 前面讨论的筛选的例子类似。

// 需要一种表示接受Apple并返回一个格式String值的方法
public interface AppleFormatter {
    String accept(Apple a);
}
// 通过实现AppleFormatter方法,来表示多种格式行为
public class AppleFancyFormatter implements AppleFormatter {
    public String accept(Apple apple) {
        String characteristic = apple.getWeight() > 150 ? "heavy" : "light";
        return "A " + characteristic + " " + apple.getColor() + " apple";
    }
}
public class AppleSimpleFormatter implements AppleFormatter {
    public String accept(Apple apple) {
        return "An apple of " + apple.getWeight() + "g";
    }
}
// 需要告诉prettyPrintApple方法接受AppleFormatter对象,并在内部使用它们
public static void prettyPrintApple(List<Apple> inventory, AppleFormatter formatter){ 
    for(Apple apple: inventory){ 
        String output = formatter.accept(apple); 
        System.out.println(output); 
    } 
} 
// 可以给prettyPrintApple方法传递多种行为
prettyPrintApple(inventory, new AppleFancyFormatter());
prettyPrintApple(inventory, new AppleSimpleFormatter());

2、Lambda:它没有名称,但它 有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表,可以在函数式接口上使用Lambda表达式,其基本语法是(parameters) -> expression 或 (parameters) -> { statements; }

3、函数式接口:只定义一个抽象方法的接口,Lambda表达式允许直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的示例(具体来说,是函数式接口一个具体实现的实例)。

// 使用Lambda
Runnable r1 = () -> System.out.println("Hello world 1");
// 使用匿名类
Runnable r2 = new Runnable() {
    public void run() {
        System.out.println("Hello world 2")
    }
}
public static void process(Runnable r) {
    r.run();
}
// 打印Hello world 1
process(r1);
// 打印Hello world 2
process(r2);
// 利用直接传递的Lambda打印Hello world 3
process(() -> System.out.println("Hello world 3"));

4、Lambda类型检查

image-20241107163038190

类型检查过程可以分解为如下所示:

  • 首先,找出filter方法的声明
  • 第二,要求它是Predicate(目标类型)对象的第二个正式参数
  • 第三,Predicate是一个函数式接口,定义了一个叫做test的抽象方法
  • 第四,test方法描述了一个函数描述符,它可以接受一个Apple,并返回一个boolean
  • 最后,filter的任何实际参数都匹配这个要求

5、Lambda使用局部变量

Lambda可以没有限制地捕获(也就是在其主体中引用)实例变量和静态变量。但局部变量必须显式声明为final, 或事实上是final。换句话说,Lambda表达式只能捕获指派给它们的局部变量一次。(注:捕获 实例变量可以被看作捕获最终局部变量this。)例如,下面的代码无法编译,因为portNumber 变量被赋值两次:

int portNumber = 1137;
// 错误:Lambda表达式引用的局部变量必须是最终的(final)或事实上最终的 
Runnable r = () -> System.out.println(protNumber);
portNumber = 31337;

为什么局部变量有这些限制?

因为:实例变量和局部变量背后的实现有一个关键不同,实例变量都存储在堆中,而局部变量则保存在栈中。如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程可能会在分配该变量的线程将这个变量收回之后去访问该变量。因此,Java在访问自由变量时,实际上是在访问它的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没什么区别了。

6、方法引用

方法引用主要有三类:

  • 指向静态方法的引用(例如Integer的parseInt方法,写作Integer :: parseInt)

  • 指向任意类型实例方法的方法引用(例如String的length方法,写作 String :: length)

  • 指向现有对象的实例方法的方法引用

    public class Transaction {
        private double value;
    
        public Transaction(double value) {
            this.value = value;
        }
    
        public double getValue() {
            return value;
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            // 创建一个Transaction对象
            Transaction expensiveTransaction = new Transaction(1000.0);
    
            // 使用方法引用
            Supplier<Double> valueSupplier = expensiveTransaction::getValue;
    
            // 调用getValue方法
            double value = valueSupplier.get();
            System.out.println("Transaction value: " + value);
        }
    }