搭建网站流程视频,微信公众号网站开发模板,2022年国内重大新闻事件,天津专业做网站公司Java8实战-总结9 Lambda表达式把Lambda付诸实践#xff1a;环绕执行模式第1步#xff1a;记得行为参数化第2步#xff1a;使用函数式接口来传递行为第3步#xff1a;执行一个行为第4步#xff1a;传递Lambda 使用函数式接口PredicateConsumerFunction原始类型特化 Lambda表… Java8实战-总结9 Lambda表达式把Lambda付诸实践环绕执行模式第1步记得行为参数化第2步使用函数式接口来传递行为第3步执行一个行为第4步传递Lambda 使用函数式接口PredicateConsumerFunction原始类型特化 Lambda表达式
把Lambda付诸实践环绕执行模式
通过一个例子看看在实践中如何利用Lambda和行为参数化来让代码更为灵活更为简洁。资源处理(例如处理文件或数据库)时一个常见的模式就是打开一个资源做一些处理然后关闭资源。这个设置和清理阶段总是很类似并且会围绕着执行处理的那些重要代码。这就是所谓的环绕执行(execute around)模式如下图所示。例如在以下代码中中间部分就是从一个文件中读取一行所需的模板代码(注意使用了Java 7中的带资源的try语句它已经简化了代码因为不需要显式地关闭资源了):
public static String processFile() throws IOException {//这就是做有用工作的那行代码try(BufferedReader br new BufferedReader(new FileReader(data.txt))) {return br.readLine();}
} 第1步记得行为参数化
现在这段代码是有局限的。只能读文件的第一行。如果想要返回头两行甚至是返回使用最频繁的词该怎么办呢?在理想的情况下要重用执行设置和清理的代码并告诉processFile方法对文件执行不同的操作。这听起来是不是很耳熟?是的需要把processFile的行为参数化。需要一种方法把行为传递给processFile,以便它可以利用BufferedReader执行不同的行为。
传递行为正是Lambda的拿手好戏。那要是想一次读两行这个新的processFile方法看起来又该是什么样的呢?基本上需要一个接收BufferedReader并返回String的Lambda。例如下面就是从BufferedReader中打印两行的写法
String result processFile((BufferedReader br) - br.readLine() br.readLine());第2步使用函数式接口来传递行为
前面解释过了Lambda仅可用于上下文是函数式接口的情况。需要创建一个能匹配BufferedReader - String,还可以抛出IOException异常的接口。把这一接口叫作BufferedReaderProcessor吧。 FunctionalInterfacepublic interface BufferedReaderProcessor {String process(BufferedReader b) throws IOException;}现在就可以把这个接口作为新的processFile方法的参数了 public static String processFile(BufferedReaderProcessor p) throws IOException {}第3步执行一个行为
任何BufferedReader - String形式的Lambda都可以作为参数来传递因为它们符合BufferedReaderProcessor接口中定义的process方法的签名。现在只需要一种方法在processFile主体内执行Lambda所代表的代码。请记住Lambda表达式允许直接内联为函数式接口的抽象方法提供实现并且将整个表达式作为函数式接口的一个实例。因此可以在processFile主体内对得到的BufferedReaderProcessor对象调用process方法执行处理
public static String processFile(BufferedReaderProcessor p) throws
IOException {try (BufferedReader br new BufferedReader(new FileReader(data.txt))) {//处理BufferedReaderreturn p.process(br);}
}第4步传递Lambda
现在你就可以通过传递不同的Lambda重用processFile方法并以不同的方式处理文件了。处理一行
String oneLine processFile((BufferedReader br)- br.readLine());处理两行
String twoLines processFile((BufferedReader br) - br.readLine() br.readLine());下图总结了所采取的使pocessFile方法更灵活的四个步骤
使用函数式接口
函数式接口定义且只定义了一个抽象方法。函数式接口很有用因为抽象方法的签名可以描述Lambda表达式的签名。函数式接口的抽象方法的签名称为函数描述符。所以为了应用不同的Lambda表达式需要一套能够描述常见函数描述符的函数式接口。Java API中已经有了几个函数式接口比如Comparable、Runnable和Callable。
Java 8的库设计师在java.util.function包中引入了几个新的函数式接口Predicate、Consumer和Function。
Predicate
java.util.function.PredicateT接口定义了一个名叫test的抽象方法它接受泛型T对象并返回一个boolean。这恰恰和先前创建的一样现在就可以直接使用了。在需要表示一个涉及类型T的布尔表达式时就可以使用这个接口。比如可以定义一个接受String对象的Lambda表达式如下所示 FunctionalInterfacepublic interface PredicateT {boolean test(T t);}public static T ListT filter(ListT list, PredicateT p) {ListT results new ArrayList();for(T s : list) {if(p.test(s)) {results.add(s);}}return results;}PredicatecString nonEmptyStringPredicate (String s) - !s.isEmpty();ListString nonEmpty filter(listofStrings, nonEmptyStringPredicate);如果去查Predicate接口的Javadoc说明可能会注意到诸如and和or等其他方法。现在不用太计较这些。
Consumer
java.util.function.ConsumerT定义了一个名叫accept的抽象方法它接受泛型T的对象没有返回(void)。如果需要访问类型T的对象并对其执行某些操作就可以使用这个接口。比如可以用它来创建一个forEach方法接受一个Integers的列表并对其中每个元素执行操作。在下面的代码中就可以使用这个forEach方法并配合Lambda来打印列表中的所有元素:
FunctionalInterface
public interface ConsumerT {void accept(T t);
}public static T void forEach(ListT list, ConsumerT c) {for(T i : list){c.accept(i);}
}//Lambda是Consumer中accept方法的实现
forEach(Arrays.asList(1,2,3,4,5),(Integer i) - System.out.println(i));Function
java.util.function.FunctionT,R接口定义了一个叫作apply的方法它接受一个泛型T的对象并返回一个泛型R的对象。如果需要定义一个Lambda,将输入对象的信息映射到输出就可以使用这个接口(比如提取苹果的重量或把字符串映射为它的长度)。在下面的代码中将展示如何利用它来创建一个map方法以将一个String列表映射到包含每个String长度的Integer列表。
FunctionalInterface
public interface PunctionT, R {R apply(T t);
}public static T,R ListR map(ListT list, FunctionT,R f) {ListR result new ArrayList();for(T s : list) {result.add(f.apply(s));}return result;
}//[7,2,6]
//Lambda是Punction接口的apply方法的实现
ListInteger 1 map(Arrays.asList(lambdas,in,action),(String s)- s.length());原始类型特化
三个泛型函数式接口PredicateT、ConsumerT和FunctionT, R。还有些函数式接口专为某些类型而设计。
Java类型要么是引用类型(比如Byte、Integer、Object、List),要么是原始类型(比如int、double、byte、char)。但是泛型(比如ConsumerT中的T)只能绑定到引用类型。这是由泛型内部的实现方式造成的。因此在Java里有一个将原始类型转换为对应的引用类型的机制。这个机制叫作装箱(boxing)。相反的操作也就是将引用类型转换为对应的原始类型叫作拆箱(unboxing)。Java还有一个自动装箱机制来帮助程序员执行这一任务装箱和拆箱操作是自动完成的。比如这就是为什么下面的代码是有效的(一个int被装箱成为Integer):
ListInteger list new ArrayList();for(int i 300; i 400; i) {list.add(i);}但这在性能方面是要付出代价的。装箱后的值本质上就是把原始类型包裹起来并保存在堆里。因此装箱后的值需要更多的内存并需要额外的内存搜索来获取被包裹的原始值。
Java 8为前面所说的函数式接口带来了一个专门的版本以便在输入和输出都是原始类型时避免自动装箱的操作。比如在下面的代码中使用IntPredicate就避免了对值1000进行装箱操作但要是用PredicateInteger就会把参数1000装箱到一个Integer对象中
public interface IntPredicate {boolean test(int t);
}//true(无装箱)
IntPredicate evenNumbers (int i) - i % 2 0;
evenNumbers.test(1000);//false(装箱)
PredicateInteger oddNumbers (Integer i) - i % 2 1;
oddNumbers.test(1000);
一般来说针对专门的输入参数类型的函数式接口的名称都要加上对应的原始类型前缀比如DoublePredicate、IntConsumer、LongBinaryoperator、IntFunction等。Function接口还有针对输出参数类型的变种ToIntFunctionT、IntToDoubleFunction等。
下表总结了Java API中提供的最常用的函数式接口及其函数描述符。请记得这只是一个起点。如果有需要可以自己设计一个。请记住(T, U) - R的表达方式展示了应当如何思考一个函数描述符。表的左侧代表了参数类型。这里它代表一个函数具有两个参数分别为泛型T和U,返回类型为R。
测验:函数式接口
对于下列函数描述符(即Lambda表达式的签名),请构造一个可以利用这些函数式接口的有效Lambda表达式(1)T - R
(2)(int, int) - int
(3)T - void
(4)() - T
(5)(T, U) - R答案如下。
(1)FunctionT,R不错。它一般用于将类型T的对象转换为类型R的对象(比如FunctionApple, Integer用来提取苹果的重量)。
(2)IntBinaryOperator具有唯一一个抽象方法叫作applyAsInt,它代表的函数描述符是(int, int)- int。
(3) ConsumerT具有唯一一个抽象方法叫作accept,代表的函数描述符是T - void。
(4)SupplierT具有唯一一个抽象方法叫作get,代表的函数描述符是() - T。或者CallableT具有唯一一个抽象方法叫作call,代表的函数描述符是() - T。
(5)BiFunctionT, U, R具有唯一一个抽象方法叫作apply,代表的函数描述符是(T, U) - R。下表总结了一些使用案例、Lambda的例子以及可以使用的函数式接口
异常、Lambda,还有函数式接口又是怎么回事呢?请注意任何函数式接口都不允许抛出受检异常(checked exception)。如果需要Lambda表达式来抛出异常有两种办法定义一个自己的函数式接口
并声明受检异常或者把Lambda包在一个try/catch块中。比如函数式接口BufferedReaderProcessor,它显式声明了一个IOException:
FunctionalInterface
public interface BufferedReaderProcessor {String process(BufferedReader b) throws IOException;
}
BufferedReaderProcessor p (BufferedReader br)- br.readLine();但可能是在使用一个接受函数式接口的API,比如FunctionT, R,没有办法自己创建一个。这种情况下可以显式捕捉受检异常
FunctionBufferedReader, String f (BufferedReader b) - {try {return b.readLine();}catch(IOException e) {throw new RuntimeException(e);}
};