Java8笔记(3)
从匿名类到 Lambda 表达式的转换
例子:
创建Runnable对象的匿名类
public class M1 {
// 将实现单一抽象方法的匿名类转换为Lambda//表达式
public static void main(String[] args) {
//传统的方式,//使用匿名类
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("runnable1");
}
};
runnable1.run();
System.out.println("========================");
// 新的方式,使用//Lambda表达式
Runnable runnable2 = () ->
{
System.out.println("runnable2");
};
runnable2.run();
}
}
但是某些情况下,将匿名类转换为Lambda表达式可能是一个比较复杂的过程.
首先,匿名类和Lambda表达式中的 this 和 super 的含义是不同的。在匿名类中, this 代表的是类自身,但是在Lambda中,它代表的是包含类。其次,匿名类可以屏蔽包含类的变量,而Lambda表达式不能(它们会导致编译错误)
例子:
int a = 10;
Runnable r1 = () -> {
int a = 2;//编译错误!
System.out.println(a);
};
Runnable r2 = new Runnable(){
public void run(){
int a = 2;
System.out.println(a);//一切正常
}
};
在涉及重载的上下文里,将匿名类转换为Lambda表达式可能导致最终的代码更加晦涩。实际上,匿名类的类型是在初始化时确定的,而Lambda的类型取决于它的上下文
例子:
// 假设你用与 Runnable 同样的签名声明了一个函数接口
interface Task{
public void execute();
}
public static void doSomething(Runnable r){ r.run(); }
public static void doSomething(Task a){ a.execute(); }
再传递一个匿名类实现的 Task ,不会碰到任何问题:
doSomething(new Task() {
public void execute() {
System.out.println("Danger danger!!");
}
});
将这种匿名类转换为Lambda表达式时,就导致了一种晦涩的方法调用,因为 Runnable和
Task 都是合法的目标类型:
// 麻 烦 来 了 : doSome-thing(Runnable) 和doSomething(Task)都匹配该类型
doSomething(() -> System.out.println("Danger danger!!"));
可以对 Task 尝试使用显式的类型转换来解决这种模棱两可的情况:
doSomething((Task)() -> System.out.println("Danger danger!!"));
从 Lambda 表达式到方法引用的转换
Lambda表达式非常适用于需要传递代码片段的场景。不过,为了改善代码的可读性,也请
尽量使用方法引用。因为方法名往往能更直观地表达代码的意图
例子:
// 按照食物的热量级别对菜肴进行分类
public static void main(String[] args) {
List<Dish> menu = Data.create();
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel =
menu.stream()
.collect(
Collectors.groupingBy(dish ->
{
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
})
);
System.out.println(dishesByCaloricLevel);
}
可以将Lambda表达式的内容抽取到一个单独的方法中,将其作为参数传递给 groupingBy
方法。变换之后,代码变得更加简洁,程序的意图也更加清晰了:
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel_1 =
// 将Lambda表达式//抽取到一个方法内
menu.stream().collect(groupingBy(Dish::getCaloricLevel));
除此之外,我们还应该尽量考虑使用静态辅助方法,比如 comparing 、 maxBy 。这些方法设计之初就考虑了会结合方法引用一起使用
// 你需要考虑如何实现比较算法
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
// 读起来就像问题描述,非常清晰
inventory.sort(comparing(Apple::getWeight));
多通用的归约操作,比如 sum 、 maximum ,都有内建的辅助方法可以和方法引用结合使用。比如,在我们的示例代码中,使用 Collectors 接口可以轻松得到和或者最大值,与采用Lambada表达式和底层的归约操作比起来,这种方式要直观得多
对比:
int totalCalories =
menu.stream().map(Dish::getCalories)
.reduce(0, (c1, c2) -> c1 + c2);
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));
从命令式的数据处理切换到 Stream
下面的命令式代码使用了两种模式:筛选和抽取,这两种模式被混在了一起,这样的代码结构迫使程序员必须彻底搞清楚程序的每个细节才能理解代码的功能。此外,实现需要并行运行的程序所面对的困难也多得多
List<String> dishNames = new ArrayList<>();
for(Dish dish: menu){
if(dish.getCalories() > 300){
dishNames.add(dish.getName());
}
}
替代方案使用Stream API,采用这种方式编写的代码读起来更像是问题陈述,并行化也非常
容易
menu.parallelStream()
.filter(d -> d.getCalories() > 300)
.map(Dish::getName)
.collect(toList());
使用 Lambda 重构面向对象的设计模式
策略模式
策略模式代表了解决一类算法的通用解决方案,你可以在运行时选择使用哪种方案
策略模式包含三部分内容:
- 一个代表某个算法的接口(它是策略模式的接口)
- 一个或多个该接口的具体实现,它们代表了算法的多种实现
- 一个或多个使用策略对象的客户
假设你希望验证输入的内容是否根据标准进行了恰当的格式化(比如只包含小写字母或
数字)。你可以从定义一个验证文本(以 String 的形式表示)的接口入手:
public interface ValidationStrategy {
boolean execute(String s);
}
public class IsAllLowerCase implements ValidationStrategy{
@Override
public boolean execute(String s) {
return s.matches("[a-z]+");
}
}
public class IsNumeric implements ValidationStrategy{
@Override
public boolean execute(String s) {
return s.matches("\\d+");
}
}
public class Validator {
private final ValidationStrategy strategy;
public Validator(ValidationStrategy strategy) {
this.strategy = strategy;
}
public boolean validate(String s){
return strategy.execute(s);
}
public static void main(String[] args) {
Validator numericValidator = new Validator(new IsNumeric());
boolean b1 = numericValidator.validate("aaaa");
System.out.println(b1);
System.out.println("======================================");
Validator lowerCaseValidator = new Validator(new IsAllLowerCase());
boolean b2 = lowerCaseValidator.validate("bbbb");
System.out.println(b2);
System.out.println("======================================");
// 使用Lambda表达式
Validator numericValidator_1 =
new Validator(
(String s) ->
s.matches("[a-z]+")
);
boolean b1_1 = numericValidator.validate("aaaa");
Validator lowerCaseValidator_1 =
new Validator(
(String s) ->
s.matches("\\d+")
);
boolean b2_1 = lowerCaseValidator_1.validate("bbbb");
}
}
模板方法
如果你需要采用某个算法的框架,同时又希望有一定的灵活度,能对它的某些部分进行改进,那么采用模板方法设计模式是比较通用的方案
假设你需要编写一个简单的在线银行应用。通常,用户需要输入一个用户账户,之后应用才能从银行的数据库中得到用户的详细信息,最终完成一些让用户满意的操作。不同分行的在线银行应用让客户满意的方式可能还略有不同,比如给客户的账户发放红利,或者仅仅是少发送一些推广文件
abstract class OnlineBanking {
public void processCustomer(int id){
Customer c = Database.getCustomerWithId(id);
makeCustomerHappy(c);
}
abstract void makeCustomerHappy(Customer c);
}
processCustomer 方法搭建了在线银行算法的框架:获取客户提供的ID,然后提供服务让
用户满意。不同的支行可以通过继承 OnlineBanking 类,对该方法提供差异化的实现
使用Lambda表达式
你想要插入的不同算法组件可以通过Lambda表达式或者方法引用的方式实现
这里我们向 processCustomer 方法引入了第二个参数,它是一个 Consumer<Customer> 类
型的参数,与前文定义的 makeCustomerHappy 的特征保持一致
public void processCustomer(int id, Consumer<Customer> makeCustomerHappy){
Customer c = Database.getCustomerWithId(id);
makeCustomerHappy.accept(c);
}
现在,你可以很方便地通过传递Lambda表达式,直接插入不同的行为,不再需要继承
OnlineBanking 类了:
new OnlineBankingLambda().processCustomer(1337, (Customer c) ->
System.out.println("Hello " + c.getName());
观察者模式
观察者模式是一种比较常见的方案,某些事件发生时(比如状态转变),如果一个对象(通
常我们称之为主题)需要自动地通知其他多个对象(称为观察者),就会采用该方案。创建图形用户界面(GUI)程序时,你经常会使用该设计模式
public interface Observer {
// 你需要一个观察者接口,它将不同的观察者聚合在一起。它仅有一个名为 notify 的
//方法,一旦接收到一条新的新闻,该方法就会被调用
void notify(String tweet);
}
public class NYTimes implements Observer{
@Override
public void notify(String tweet) {
if(tweet != null && tweet.contains("money")){
System.out.println("Breaking news in NY! " + tweet);
}
}
}
public class LeMonde implements Observer{
@Override
public void notify(String tweet) {
if(tweet != null && tweet.contains("wine")){
System.out.println("Today cheese, wine and news! " + tweet);
}
}
}
public class Guardian implements Observer{
@Override
public void notify(String tweet) {
if(tweet != null && tweet.contains("queen")){
System.out.println("Yet another news in London... " + tweet);
}
}
}
public interface Subject {
// Subject 使用 registerObserver 方法可以注册一个新的观察者,使用 notifyObservers
//方法通知它的观察者一个新闻的到来
void registerObserver(Observer o);
void notifyObservers(String tweet);
}
public class Feed implements Subject {
// Feed 类在内部维护了一个观察者列表,一条新闻到达时,它就
//进行通知
private final List<Observer> observers = new ArrayList<>();
@Override
public void registerObserver(Observer o) {
this.observers.add(o);
}
@Override
public void notifyObservers(String tweet) {
observers.forEach(o->o.notify(tweet));
}
public static void main(String[] args) {
Feed f = new Feed();
f.registerObserver(new NYTimes());
f.registerObserver(new Guardian());
f.registerObserver(new LeMonde());
f.notifyObservers("The queen said her favourite book is Java 8 in Action!");
}
}
使用Lambda表达式
Observer 接口的所有实现类都提供了一个方法: notify 。新闻到达时,它们都只是对同一段代码封装执行。Lambda表达式的设计初衷就是要消除这样的僵化代码。使用Lambda表达式后,你无需显式地实例化三个观察者对象,直接传递Lambda表达式表示需要执行的行为即可
f.registerObserver((String tweet) -> {
if(tweet != null && tweet.contains("money")){
System.out.println("Breaking news in NY! " + tweet);
}
});
f.registerObserver((String tweet) -> {
if(tweet != null && tweet.contains("queen")){
System.out.println("Yet another news in London... " + tweet);
}
});
责任链模式
责任链模式是一种创建处理对象序列(比如操作序列)的通用方案。一个处理对象可能需要
在完成一些工作之后,将结果传递给另一个对象,这个对象接着做一些工作,再转交给下一个处理对象,以此类推
public abstract class ProcessingObject<T> {
// 在抽象类中会定义一个字//段来记录后续对象。一旦对象完成它的工作,处理对象就会将它的工作转交给它的后继
protected ProcessingObject<T> successor;
public void setSuccessor(ProcessingObject<T> successor){
this.successor = successor;
}
// handle 方法提供了如何进行
//工作处理的框架。不同的处理对象可以通过继承 ProcessingObject 类,提供 handleWork 方法
//来进行创建。
public T handle(T input){
T r = handleWork(input);
if(successor != null){
return successor.handle(r);
}
return r;
}
abstract protected T handleWork(T input);
}
public class HeaderTextProcessing extends ProcessingObject<String>{
@Override
protected String handleWork(String text) {
return "From Raoul, Mario and Alan: " + text;
}
}
public class SpellCheckerProcessing extends ProcessingObject<String>{
@Override
protected String handleWork(String text) {
return text.replaceAll("labda", "lambda");
}
}
public class M1 {
public static void main(String[] args) {
ProcessingObject<String> p1 = new HeaderTextProcessing();
ProcessingObject<String> p2 = new SpellCheckerProcessing();
// 将两个处理对//象链接起来
p1.setSuccessor(p2);
String result = p1.handle("Aren't labdas really sexy?!!");
System.out.println(result);
}
}
使用Lambda表达式
public class M2 {
public static void main(String[] args) {
UnaryOperator<String> headerProcessing =
(String text) -> "From Raoul, Mario and Alan: " + text;
UnaryOperator<String> spellCheckerProcessing =
(String text) -> text.replaceAll("labda", "lambda");
// 将 两 个 方 法//结合起来,结//果 就 是 一 个//操作链
Function<String, String> pipeline =
headerProcessing.andThen(spellCheckerProcessing);
String result = pipeline.apply("Aren't labdas really sexy?!!");
System.out.println(result);
}
}
工厂模式
使用工厂模式,你无需向客户暴露实例化的逻辑就能完成对象的创建
假定你为一家银行工作,他们需要一种方式创建不同的金融产品:贷款、期权、股票,等等
public class ProductFactory {
// 创建一个工厂类,它包含一个负责实现不同对象的方法
public static Product createProduct(String name){
switch(name){
case "loan": return new Loan();
case "stock": return new Stock();
case "bond": return new Bond();
default: throw new RuntimeException("No such product " + name);
}
}
//这里贷款( Loan )、股票( Stock )和债券( Bond )都是产品( Product )的子类。
//createProduct 方法可以通过附加的逻辑来设置每个创建的产品。但是带来的好处也显而易
//见,你在创建对象时不用再担心会将构造函数或者配置暴露给客户
}
客户创建产品时更加简单
Product p = ProductFactory.createProduct("loan");
使用Lambda表达式
下面就是一个引用贷款( Loan )构造函数的示例:
Supplier<Product> loanSupplier = Loan::new;
Loan loan = loanSupplier.get();
网友评论