簡介
lambda表達式,又稱閉包(Closure)或稱匿名方法(anonymous method)。將Lambda表達式引入JAVA中的動機源於一個叫“行為參數”的模式。這種模式能夠解決需求變化帶來的問題,使代碼變得更加靈活。在JAVA8之前,參數模式十分啰嗦。Lambda表達式通過精簡的方式使用行為模式克服了這個缺點
解決什么問題
- 傳遞行為。它允許我們將函數當成參數傳遞給某個方法,或者把代碼本身當作數據處理,變成了一等公民。解決重復的
代碼片段
和代碼包裹
問題。 - 內置抽象行為。把常見的行為定義成接口,可以直接使用,
減少重復思考和代碼
。都在java.util.function包里 - 更少的代碼。通過類型推斷,方法引用,可以讓代碼更優雅。
背后思想
- 函數式編程
內容說明
作用域
this
在內部類中,this指向當前內部類對象自己,而在lambda表達式中,this指向的是表達式外部的類對象。
public class ScopeTest {
@Test
public void test_scope(){
Runnable runnable = () -> {
this.print();
};
runnable.run();
}
private void print(){
System.out.println("I can print");
}
}
final
labmda表達式使用外部的變量時,不可修改,默認定義成final


函數式接口
只有一個抽象方法的接口我們就稱之為功能性接口,又簡稱 SAM 類型,即 Simple Abstract Method。會寫上注釋
@FunctionalInterface
//用這個注解來表示功能性接口
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
常見的內置函數如下:
name | function |
---|---|
java.lang.Runnable | 執行動作 |
java.util.function.Predicate <T> | 接收T對象並返回boolean |
java.util.function.Consumer<T> | 接收T對象,不返回值 |
java.util.function.Function<T,R> | 接收T對象,返回R對象 |
java.util.function.Supplier<T> | 提供T對象(例如工廠),不接收值 |
方法引用
方法引用有很多種,它們的語法如下:
- 靜態方法引用:ClassName::methodName
- 實例上的實例方法引用:instanceReference::methodName
- 超類上的實例方法引用:super::methodName
- 類型上的實例方法引用:ClassName::methodName
- 構造方法引用:Class::new
- 數組構造方法引用:TypeName[]::new
@Test
public void test_instance(){
Set<String> girls = new HashSet<>();
Set<String> names = new HashSet<>();
names.stream()
//實例::methodName
.filter(girls::contains)
.collect(Collectors.toList());
}
@Test
public void test_this(){
Set<String> names = new HashSet<>();
names.stream()
//this::methodName
.filter(this::hasAuth)
.collect(Collectors.toList());
}
private boolean hasAuth(String authKey){
return true;
}
類型推斷
Map<String,Person> map = new HashMap<>();
map.forEach((String name,Person person)->{
person.fly();
System.out.println(name+":"+person);
});
map.forEach((name,person)->{
// 無須判斷類型,自動根據上下文推斷
person.fly();
System.out.println(name+":"+person);
});
實踐
最佳實踐
消滅代碼片段
/**
* 正常的代碼
*/
@Test
public void test_person(){
CnResult<Person> result = null;
try {
// 只有這里取值是不同的,其他的處理是一樣的,包括try,catch,打日志,定義異常等
Person entity = this.getPerson();
result = CnResult.success(entity);
}catch (CnException e){
logger.error(e.getMessage(),e);
result = CnResult.error(e.getErrorCode(),e.getMessage());
}
catch (Exception e) {
logger.error(e.getMessage(),e);
result = CnResult.error("1-1-1-1",e.getMessage());
}
Assert.assertNotNull(result);
}
@Test
public void test_animal(){
CnResult<Animal> result = null;
try {
// 只有這里取值是不同的,其他的處理是一樣的,包括try,catch,打日志,定義異常等
Animal entity = this.getAnimal();
result = CnResult.success(entity);
}catch (CnException e){
logger.error(e.getMessage(),e);
result = CnResult.error(e.getErrorCode(),e.getMessage());
}
catch (Exception e) {
logger.error(e.getMessage(),e);
result = CnResult.error("1-1-1-1",e.getMessage());
}
Assert.assertNotNull(result);
}
/**
* lambda代碼
*/
@Test
public void test_lambda(){
//屏蔽所有細節,只把獲取對象的邏輯傳遞進去
CnResult<Person> person = WapperUtils.wapper(this::getPerson);
Assert.assertNotNull(person);
CnResult<Animal> animal = WapperUtils.wapper(this::getAnimal);
Assert.assertNotNull(animal);
}
public class WapperUtils {
private static final Logger logger = LoggerFactory.getLogger(WapperUtils.class);
/**
* 包裹
*
* @param supplier
* @param <T>
* @return
*/
public static <T> CnResult<T> wapper(Supplier<T> supplier){
try {
T entity = supplier.get();
CnResult<T> cnResult = CnResult.success(entity);
return cnResult;
}catch (CnException e){
logger.error(e.getMessage(),e);
return CnResult.error(e.getErrorCode(),e.getMessage());
}
catch (Exception e) {
logger.error(e.getMessage(),e);
return CnResult.error("1-1-1-1",e.getMessage());
}
}
}
- 只要是代碼片段,找到變點化,抽象成lambda,把固定的代碼統一封裝,就可以使用行為的復用,減少代碼片段和代碼包裹。
明確語義
@Test
public void test_first() {
List<Person> persons = new ArrayList<>();
persons.stream()
/**
* 沒有明確的語義。需要根據過程計算推理得出結果,也不清楚背后的業務和場景
*
*/
.filter(person -> person.getAge() >= 18)
.collect(Collectors.toList());
}
@Test
public void test_second() {
List<Person> persons = new ArrayList<>();
persons.stream()
.filter(person -> {
/**
* 如果職責變更,得修改代碼結構,而且代碼越來越難理解
*/
if (person.getAge() >= 18) {
return true;
}
if (person.getName().startsWith("庄")) {
return true;
}
return false;
})
.collect(Collectors.toList());
}
@Test
public void test_third() {
List<Person> persons = new ArrayList<>();
persons.stream()
/**
* 隨着業務變更需要不斷添加filter
*/
.filter(person -> person.getAge() >= 18)
.filter(person -> person.getName().startsWith("庄"))
.collect(Collectors.toList());
}
@Test
public void test_fourth() {
List<Person> persons = new ArrayList<>();
persons.stream()
/**
* 隨着業務變更需要不斷添加filter
*/
.filter(Person::isAdult)
.filter(Person::belongToZhuang)
.collect(Collectors.toList());
}
@Test
public void test_final() {
List<Person> persons = new ArrayList<>();
/**
* 有明確的語義,不用再面向細節加工一下,而且也方便后面的擴展
*/
persons.stream()
.filter(Person::hasAuth)
.collect(Collectors.toList());
}
最佳反例
隨意取名
@Test
public void test_name_bad(){
List<Person> persons = new ArrayList<>();
/**
* lambda一多的時候,沒有明確的參數,計算和理解起來非常吃力。
*/
persons.stream()
.filter(e->e.getAge()>18)
.map(e->e.getName())
.filter(e->e.startsWith("庄"))
.collect(Collectors.toList());
}
@Test
public void test_name(){
List<Person> persons = new ArrayList<>();
persons.stream()
.filter(person->person.getAge()>18)
.map(person->person.getName())
.filter(name->name.startsWith("庄"))
.collect(Collectors.toList());
}
- 參數需要有明確語義
邏輯復雜
@Test
public void test_bad() {
List<Integer> values = new ArrayList<>();
int result = values.stream().mapToInt(e -> {
int sum = 0;
for (int i = 0; i < e; i++) {
if (e % i == 0) {
sum += i;
}
}
return sum;
}).sum();
System.out.println(result);
}
@Test
public void test_() {
List<Integer> values = new ArrayList<>();
int result = values.stream().mapToInt(this::toInt).sum();
System.out.println(result);
}
private Integer toInt(int e) {
int sum = 0;
for (int i = 0; i < e; i++) {
if (e % i == 0) {
sum += i;
}
}
return sum;
}
- 難以讀懂
- 用途不明
- 難以測試
- 難以復用
建議把多行lambda表達式主體轉移到一個命名函數中,然后使用方法引用
思考
- 和內部類的區別
- 和AOP的區別