先看一段代码:
public String processFile() throws IOException{
try(BufferedReader reader = new BufferedReader(new FileReader("data.txt"))){
return reader.readLine();
}
}
这段代码只读取data.txt文件中的第一行数据,但它是有局限的,如果有需求想返回头两行,甚至是返回高频词该怎么办呢?
按照上节所学的,把processFile的行为参数化:把行为传递给processFile,以便它利用BufferedReader执行不同的行为。
定义函数式接口
@FunctionalInterface
public interface BufferedReaderProcessor{
String process(BufferedReader reader) throws IOException;
}
只有一个函数 :)
现在就可以把这个接口作为参数传给processFile()了:
public String processFile(BufferedReaderProcessor p) throws IOException{
......
}
2. 执行一个行为
public String processFile(BufferedReaderProcessor p) throws IOException{
try(BufferedReader reader = new BufferedReader(new FileReader("data.txt"))){
return p.processor(reader); // 行为动作交给BufferedReaderProcessor.process()函数处理,你不用care它的实际动作,只需要把对象reader给它即可。
}
}
但这种写法与Lambda一点鸟关系都没有,它反而是我们这种大叔级别的程序员所欢喜的。
3. 传递Lambda
String oneLine = processFile((BufferedReader br) -> br.readLine());
其中红色字体:(BufferedReader br) -> br.readLine(),其功能与等价于:
try(BufferedReader reader = new BufferedReader(new FileReader("data.txt"))){
return reader.readLine();
}
也可以读取两行内容:
String twoLines = processFile((BufferedReader br) -> br.readLine() + br.readLine());
再举几个例子,加深一下印象:
Predicate
@FunctionalInterface
public interface Predicate<T>{
boolean test(T t);
}
public <T> List<T> filter(List<T> srcList, Predicate<T> p){
List<T> results = new ArrayList<>(); // 定义一个结果集合
for(T item: srcList){ // 遍历原集合
if(p.test(item)){ // 执行函数式接口逻辑,满足者添加到结果集合中
results.add(item);
}
}
return results; // 返回结果集合
}
Lambda使用示例:
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty(); // 可理解为,定义了函数式接口Predicate实例nonEmptyStringPredicate
List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate); // 让这个函数式接口实例去做过滤动作
备注:这里(String s) -> !s.isEmpty()其实就是接口Predicate中test方法的一个实现。
2. Consumer
@FunctionalInterface
public interface Consumer<T>{
void accept(T t);
}
public static <T> void forEach(List<T> srcList, Consumer<T> c){
for(T t : srcList){ // 遍历srcList,通过Consumer.accept()处理符合条件的数据
c.accept(i);
}
}
Lambda使用示例:
forEach(Arrays.asList(1,2,3,4,5,6), (Integer i) -> System.out.println(i));
备注:这里(Integer i) -> System.out.println(i) 其实就是函数式接口Consumer<T>中accept方法的一个具体实现。
3. Function
@FunctionalInterface
public interface Function<T,R>{
R apply(T t);
}
public static <T, R> List<R> map(List<T> srcList, Function<T, R> f){
List<R> result = new ArrayList<>();
for(T s : srcList){
result.add(f.apply(s));
}
return result;
}
Lambda使用示例:
List<Integer> intList = map(Arrays.asList("lambdas", "in", "action", "demo"), (String s) -> s.length());
备注:这里(String s) -> s.length() 其实就是函数式接口Function<T, R>中map方法的一个具体实现。
经过上面的例子,大家应该了解了Lambda表达式如何使用。不管了,反正我已了解 :)
有人说Java是真正面向对象的语言,但它从它的支持的类型来看,Java还是有遗留尾巴的。
引用类型:诸如Byte、Integer、Double、List等
原始类型:诸如byte、int、double、char等
如果把原始类型转换为对应的引用类型(int -> Integer、byte -> Byte、double -> Double),这叫装箱; 反之,叫拆箱。 当然Java有自动装拆箱机制,让Java人员使用起来更方便一些:
List<Integer> list = new ArrayList<>();
for (int i = 30; i < 400; i++){
list.add(i); // 这里i本来是原始类型,但自动装箱后添加到list中。但这要付出性能代价的
}
另外上面的例子中,有3个接口Predicate<T>、Consumer<T>、Function<T, R>,它们均是泛型函数式接口,这里就涉及到一个暗约束:泛型只能绑定到引用类型,不能绑定到原始类型,这是由泛型内部的实现方式造成的。
Java8为是否自动装箱做了一个专门的版本,请看下面的例子:
@FunctionalInterface
public interface IntPredicate{ // 定义函数式接口
boolean test(int t);
}
// 声明一个IntPredicate类型的实例
IntPredicate evenNums = (int i) -> i % 2 == 0; // 标红部分即为接口IntPredicate的一个实现
evenNums.test(1000); // 这里无需装箱,也没有发生自动装箱动作
Predicate<Integer> oddNums = (Integer i) -> i % 2 == 1; // 上面的函数式接口
oddNums.test(1000); // 这里会自动装箱
一般来说,针对专门的输入参数的函数式接口的名称都要加上对应的原始类型前缀,比如DoublePredicate、IntConsumer、LongBinaryOperator等。Function接口还有针对输出参数类型的变种:ToIntFunction<T>、IntToDoubleFunction等。
反正好多接口,也没有必要记忆和背诵。