茂名网站制作计划,月嫂云商城网站建设,中山网上房地产,贸易公司做网站有优势吗转载自 Java8学习笔记#xff08;1#xff09; -- 从函数式接口说起希望本文能够成为Java8 Lambda表达式的快速入门指南。
函数式接口
理解Functional Interface#xff08;函数式接口#xff0c;以下简称FI#xff09;是学习Java8 Lambda表达式的关键所在#xff0c;所…转载自 Java8学习笔记1 -- 从函数式接口说起希望本文能够成为Java8 Lambda表达式的快速入门指南。
函数式接口
理解Functional Interface函数式接口以下简称FI是学习Java8 Lambda表达式的关键所在所以放在最开始讨论。FI的定义其实很简单任何接口如果只包含唯一一个抽象方法那么它就是一个FI。为了让编译器帮助我们确保一个接口满足FI的要求也就是说有且仅有一个抽象方法Java8提供了FunctionalInterface注解。举个简单的例子Runnable接口就是一个FI下面是它的源代码
FunctionalInterface
public interface Runnable { public abstract void run();
} Lambda语法糖
为了能够方便、快捷、幽雅的创建出FI的实例Java8提供了Lambda表达式这颗语法糖。下面我用一个例子来介绍Lambda语法。假设我们想对一个ListString按字符串长度进行排序那么在Java8之前可以借助匿名内部类来实现
ListString words Arrays.asList(apple, banana, pear);
words.sort(new ComparatorString() { Override public int compare(String w1, String w2) { return Integer.compare(w1.length(), w2.length()); } });
上面的匿名内部类简直可以用丑陋来形容唯一的一行逻辑被五行垃圾代码淹没。根据前面的定义并查看Java源代码可知Comparator是个FI所以可以用Lambda表达式来实现
words.sort((String w1, String w2) - { return Integer.compare(w1.length(), w2.length());
});
代码变短了好多仔细观察就会发现Lambda表达式很像一个匿名的方法只是圆括号内的参数列表和花括号内的代码被-分隔开了。垃圾代码写的越少我们就有越多的时间去写真正的逻辑代码不是吗是的圆括号里的参数类型是可以省略的
words.sort((w1, w2) - { return Integer.compare(w1.length(), w2.length());
});
如果Lambda表达式的代码块只是return后面跟一个表达式那么还可以进一步简化
words.sort( (w1, w2) - Integer.compare(w1.length(), w2.length())
);
注意表达式后面是没有分号的如果只有一个参数那么包围参数的圆括号可以省略
words.forEach(word - { System.out.println(word);
});
如果表达式不需要参数呢好吧那也必须有圆括号例如
Executors.newSingleThreadExecutor().execute( () - {/* do something. */} // Runnable
); 方法引用
有时候Lambda表达式的代码就只是一个简单的方法调用而已遇到这种情况Lambda表达式还可以进一步简化为方法引用Method References。一共有四种形式的方法引用第一种引用静态方法例如
ListInteger ints Arrays.asList(1, 2, 3);
ints.sort(Integer::compare);
第二种引用某个特定对象的实例方法例如前面那个遍历并打印每一个word的例子可以写成这样
words.forEach(System.out::println);
第三种引用某个类的实例方法例如
words.stream().map(word - word.length()); // lambda
words.stream().map(String::length); // method reference
第四种引用类的构造函数例如
// lambda
words.stream().map(word - { return new StringBuilder(word);
});
// constructor reference
words.stream().map(StringBuilder::new); 什么时候用Lambda表达式
既然Lambda表达式这么好用那么可以在哪些地方使用呢如果你真正明白了什么是FI很容易应该立刻就能给出答案任何可以接受一个FI实例的地方都可以用Lambda表达式。比如虽然上面给出的例子都是把Lambda表达式当作方法参数传递但实际上你也可以定义变量
Runnable task () - { // do something
}; ComparatorString cmp (s1, s2) - { return Integer.compare(s1.length(), s2.length());
}; 预定义函数式接口
Java8除了给RunnableComparator等接口打上了FunctionalInterface注解之外还预定义了一大批新的FI。这些接口都在java.util.function包里下面简单介绍其中的几个。
FunctionalInterface
public interface PredicateT { boolean test(T t);
}
Predicate用来判断一个对象是否满足某种条件比如单词是否由六个以上字母组成
words.stream() .filter(word - word.length() 6) .count();
Function表示接收一个参数并产生一个结果的函数
FunctionalInterface
public interface FunctionT, R { R apply(T t);
}
下面的例子将集合里的每一个整数都乘以2
ints.stream().map(x - x * 2);
Consumer表示对单个参数进行的操作前面例子中的forEach()方法接收的参数就是这种操作
FunctionalInterface
public interface ConsumerT { void accept(T t);
} 对原有API的增强
为了充分发挥Lambda的威力Java8对很多老的类库进行了增强给它们配备了Lambda武器。比如前面例子中用到的forEach()方法实际上是添加到Iterable接口中的。而多次出现的stream()方法则是添加在了Collection接口里。用过RubyScalaGroovy等语言的Java程序员可能已经对在这些语言里很好实现的外部迭代器模式垂涎很久了。虽然Google的Guava可以在一定程度上弥补Java的这种缺陷但是Java8的Lambda才真正让Java朝着函数式编程迈进了一大步。
接口的默认方法
细心的读者可能会发现一个问题给Iterable和Collection等接口增加方法岂不是会破坏接口的向后兼容性是的为了保证API的向后兼容性Java8对接口的语法进行了较大的调整增加了默认方法Default Methods。下面是forEach()方法的实现代码
public interface IterableT { ... default void forEach(Consumer? super T action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } ...
} 接口的静态方法
除了抽象方法和默认方法从Java8开始接口也可以有静态static方法了。有了这个语法我们就可以把和接口相关的帮助方法Helper Methods直接定义在接口里了。比如Function接口就定义了一个工厂方法indentity()
public interface FunctionT, R { ... /** * Returns a function that always returns its input argument. * * param T the type of the input and output objects to the function * return a function that always returns its input argument */ static T FunctionT, T identity() { return t - t; }
...
} 变量捕获
和内部类一样Lambda也可以访问外部词法作用域变量规则基本一样。Java8之前内部类只能访问final类型的变量Java8放宽了这种限制只要变量实际上不可变effectively final就可以。换句话说如果你给变量加上final关键字编译器也不报错那么去掉final关键字后它就是effectively final的。看下面的例子
int a 100;
Runnable x new Runnable() { Override public void run() { System.out.println(a); } };
在Java8之前a必须是final的才能被x看到。下面用Lambda表达式重写上面的例子
int a 100;
Runnable x () - { System.out.println(a);
}; 结论
可以看到为了支持Lambda表达式Java8对Java语言做了很大的调整。但Lambda表达式并非只是Java语法糖而是由编译器和JVM共同配合来实现的这一点我会在下一篇文章里详细介绍。