欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java通過Lambda函數(shù)的方式獲取屬性名稱

 更新時間:2023年10月20日 08:54:11   作者:張鐵牛  
這篇文章主要介紹了通過Lambda函數(shù)的方式獲取屬性名稱,實現(xiàn)步驟是通過定義一個函數(shù)式接口, 用來接收lambda方法引用,本文結合實例代碼給大家介紹的非常詳細,需要的朋友可以參考下

前言:

最近在使用mybatis-plus框架, 常常會使用lambda的方法引用獲取實體屬性, 避免出現(xiàn)大量的魔法值.

public List<User> listBySex() {
  LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
  // lambda方法引用
  queryWrapper.eq(User::getSex, "男");
  return userServer.list(wrapper);
}

那么在我們平時的開發(fā)過程中, 常常需要用到java bean的屬性名, 直接寫死屬性名字符串的形式容易產(chǎn)生bug, 比如屬性名變化, 編譯時并不會報錯, 只有在運行時才會報錯該對象沒有指定的屬性名稱. 而lambda的方式不僅可以簡化代碼, 而且可以通過getter方法引用拿到屬性名, 避免潛在bug.

期望的效果

String userName = BeanUtils.getFieldName(User::getName);
System.out.println(userName);
// 輸出: name

實現(xiàn)步驟

定義一個函數(shù)式接口, 用來接收lambda方法引用

注意: 函數(shù)式接口必須繼承Serializable接口才能獲取方法信息

@FunctionalInterface
public interface SFunction<T> extends Serializable {
  Object apply(T t);
}

定義一個工具類, 用來解析獲取屬性名稱

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.beans.Introspector;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class BeanUtils {
    private static final Map<SFunction<?>, Field> FUNCTION_CACHE = new ConcurrentHashMap<>();
 
    public static <T> String getFieldName(SFunction<T> function) {
        Field field = BeanUtils.getField(function);
        return field.getName();
    }
 
    public static <T> Field getField(SFunction<T> function) {
        return FUNCTION_CACHE.computeIfAbsent(function, BeanUtils::findField);
    }
 
    public static <T> Field findField(SFunction<T> function) {
        // 第1步 獲取SerializedLambda
        final SerializedLambda serializedLambda = getSerializedLambda(function);
        // 第2步 implMethodName 即為Field對應的Getter方法名
        final String implClass = serializedLambda.getImplClass();
        final String implMethodName = serializedLambda.getImplMethodName();
        final String fieldName = convertToFieldName(implMethodName);
        // 第3步  Spring 中的反射工具類獲取Class中定義的Field
        final Field field = getField(fieldName, serializedLambda);

        // 第4步 如果沒有找到對應的字段應該拋出異常
        if (field == null) {
            throw new RuntimeException("No such class 「"+ implClass +"」 field 「" + fieldName + "」.");
        }

        return field;
    }

    static Field getField(String fieldName, SerializedLambda serializedLambda) {
        try {
            // 獲取的Class是字符串,并且包名是“/”分割,需要替換成“.”,才能獲取到對應的Class對象
            String declaredClass = serializedLambda.getImplClass().replace("/", ".");
            Class<?>aClass = Class.forName(declaredClass, false, ClassUtils.getDefaultClassLoader());
            return ReflectionUtils.findField(aClass, fieldName);
        }
        catch (ClassNotFoundException e) {
            throw new RuntimeException("get class field exception.", e);
        }
    }

    static String convertToFieldName(String getterMethodName) {
        // 獲取方法名
        String prefix = null;
        if (getterMethodName.startsWith("get")) {
            prefix = "get";
        }
        else if (getterMethodName.startsWith("is")) {
            prefix = "is";
        }

        if (prefix == null) {
            throw new IllegalArgumentException("invalid getter method: " + getterMethodName);
        }

        // 截取get/is之后的字符串并轉換首字母為小寫
        return Introspector.decapitalize(getterMethodName.replace(prefix, ""));
    }

    static <T> SerializedLambda getSerializedLambda(SFunction<T> function) {
        try {
            Method method = function.getClass().getDeclaredMethod("writeReplace");
            method.setAccessible(Boolean.TRUE);
            return (SerializedLambda) method.invoke(function);
        }
        catch (Exception e) {
            throw new RuntimeException("get SerializedLambda exception.", e);
        }
    }
}

測試

public class Test {
    public static void main(String[] args) {
        SFunction<User> user = User::getName;
        final String fieldName = BeanUtils.getFieldName(user);
        System.out.println(fieldName);
    }

    @Data
    static class User {
        private String name;

        private int age;
    }
}

執(zhí)行測試 輸出結果

原理剖析

為什么SFunction必須繼承Serializable

首先簡單了解一下java.io.Serializable接口,該接口很常見,我們在持久化一個對象或者在RPC框架之間通信使用JDK序列化時都會讓傳輸?shù)膶嶓w類實現(xiàn)該接口,該接口是一個標記接口沒有定義任何方法,但是該接口文檔中有這么一段描述:

概要意思就是說,如果想在序列化時改變序列化的對象,可以通過在實體類中定義任意訪問權限的Object writeReplace()來改變默認序列化的對象。

代碼中SFunction只是一個接口, 但是其在最后必定也是一個實現(xiàn)類的實例對象,而方法引用其實是在運行時動態(tài)創(chuàng)建的,當代碼執(zhí)行到方法引用時,如User::getName,最后會經(jīng)過

java.lang.invoke.LambdaMetafactory
java.lang.invoke.InnerClassLambdaMetafactory

去動態(tài)的創(chuàng)建實現(xiàn)類。而在動態(tài)創(chuàng)建實現(xiàn)類時則會判斷函數(shù)式接口是否實現(xiàn)了Serializable,如果實現(xiàn)了,則添加writeReplace方法

也就是說我們代碼BeanUtils#getSerializedLambda方法中反射調用的writeReplace方法是在生成函數(shù)式接口實現(xiàn)類時添加進去的.

SFunction Class中的writeReplace方法

從上文中我們得知 當SFunction繼承Serializable時, 底層在動態(tài)生成SFunction的實現(xiàn)類時添加了writeReplace方法, 那這個方法有什么用?

首先 我們將動態(tài)生成的類保存到磁盤上看一下

我們可以通過如下屬性配置將 動態(tài)生成的Class保存到 磁盤上

java8中可以通過硬編碼

 System.setProperty("jdk.internal.lambda.dumpProxyClasses", ".");

例如:

jdk11 中只能使用jvm參數(shù)指定,硬編碼無效,原因是模塊化導致的

-Djdk.internal.lambda.dumpProxyClasses=.

例如:

執(zhí)行方法后輸出文件如下:

其中實現(xiàn)類的類名是有具體含義的

其中Test$Lambda$15.class信息如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package test.java8.lambdaimpl;

import java.lang.invoke.SerializedLambda;
import java.lang.invoke.LambdaForm.Hidden;
import test.java8.lambdaimpl.Test.User;

// $FF: synthetic class
final class Test$$Lambda$15 implements SFunction {
    private Test$$Lambda$15() {
    }

    @Hidden
    public Object apply(Object var1) {
        return ((User)var1).getName();
    }

    private final Object writeReplace() {
        return new SerializedLambda(Test.class, "test/java8/lambdaimpl/SFunction", "apply", "(Ljava/lang/Object;)Ljava/lang/Object;", 5, "test/java8/lambdaimpl/Test$User", "getName", "()Ljava/lang/String;", "(Ltest/java8/lambdaimpl/Test$User;)Ljava/lang/Object;", new Object[0]);
    }
}

通過源碼得知 調用writeReplace方法是為了獲取到方法返回的SerializedLambda對象

SerializedLambda: 是Java8中提供,主要就是用于封裝方法引用所對應的信息,主要的就是方法名、定義方法的類名、創(chuàng)建方法引用所在類。拿到這些信息后,便可以通過反射獲取對應的Field。

值得注意的是,代碼中多次編寫的同一個方法引用,他們創(chuàng)建的是不同F(xiàn)unction實現(xiàn)類,即他們的Function實例對象也并不是同一個。

一個方法引用創(chuàng)建一個實現(xiàn)類,他們是不同的對象,那么BeanUtils中將SFunction作為緩存key還有意義嗎?

答案是肯定有意義的?。。∫驗橥环椒ㄖ械亩x的Function只會動態(tài)的創(chuàng)建一次實現(xiàn)類并只實例化一次,當該方法被多次調用時即可走緩存中查詢該方法引用對應的Field。

通過內部類實現(xiàn)類的類名規(guī)則我們也能大致推斷出來, 只要申明lambda的相對位置不變, 那么對應的Function實現(xiàn)類包括對象都不會變。

通過在剛才的示例代碼中添加一行, 就能說明該問題, 之前15號對應的是getName, 而此時的15號class對應的是getAge這個函數(shù)引用

我們再通過代碼驗證一下 剛才的猜想

參考資料:

https://blog.csdn.net/u013202238/article/details/105779686

https://blog.csdn.net/qq_39809458/article/details/101423610

到此這篇關于通過Lambda函數(shù)的方式獲取屬性名稱的文章就介紹到這了,更多相關Lambda函數(shù)獲取屬性名稱內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 了解Java線程池執(zhí)行原理

    了解Java線程池執(zhí)行原理

    那么有沒有一種辦法使得線程可以復用,就是執(zhí)行完一個任務,并不被銷毀,而是可以繼續(xù)執(zhí)行其他的任務?在Java中可以通過線程池來達到這樣的效果。下面我們來詳細了解一下吧
    2019-05-05
  • Java深入淺出說流的使用

    Java深入淺出說流的使用

    這篇文章主要介紹了Java深入淺出說流的使用,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-09-09
  • IDEA中GitLab的使用詳解

    IDEA中GitLab的使用詳解

    這篇文章主要介紹了IDEA中GitLab的使用,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-07-07
  • Spring DATA JPA 中findAll 進行OrderBy方式

    Spring DATA JPA 中findAll 進行OrderBy方式

    這篇文章主要介紹了Spring DATA JPA 中findAll 進行OrderBy方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • RocketMQ中消費者概念和消費流程詳解

    RocketMQ中消費者概念和消費流程詳解

    這篇文章主要介紹了RocketMQ中消費者概念和消費流程詳解,RocketMQ是一款高性能、高可靠性的分布式消息中間件,消費者是RocketMQ中的重要組成部分,消費者負責從消息隊列中獲取消息并進行處理,需要的朋友可以參考下
    2023-10-10
  • 解決Swagger2返回map復雜結構不能解析的問題

    解決Swagger2返回map復雜結構不能解析的問題

    這篇文章主要介紹了解決Swagger2返回map復雜結構不能解析的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Java基礎教程之實現(xiàn)接口

    Java基礎教程之實現(xiàn)接口

    這篇文章主要介紹了Java基礎教程之實現(xiàn)接口,也可以說是實施接口,因為接口只是定義,最終要實現(xiàn)它,本文就專門講解接口的實現(xiàn),需要的朋友可以參考下
    2014-08-08
  • java集合類遍歷的同時如何進行刪除操作

    java集合類遍歷的同時如何進行刪除操作

    這篇文章主要介紹了java集合類遍歷的同時如何進行刪除操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Java實現(xiàn)線程的暫停和恢復的示例詳解

    Java實現(xiàn)線程的暫停和恢復的示例詳解

    這幾天的項目中,客戶給了個需求,希望我可以開啟一個任務,想什么時候暫停就什么時候暫停,想什么時候開始就什么時候開始,所以本文小編給大家介紹了Java實現(xiàn)線程的暫停和恢復的示例,需要的朋友可以參考下
    2023-11-11
  • SpringMVC實現(xiàn)全局異常處理器的經(jīng)典案例

    SpringMVC實現(xiàn)全局異常處理器的經(jīng)典案例

    文章介紹了如何使用@ControllerAdvice和相關注解實現(xiàn)SpringMVC的全局異常處理,通過統(tǒng)一的異常處理類和自定義業(yè)務異常類,可以將所有控制器的異常集中處理,并以JSON格式返回給前端,感興趣的朋友一起看看吧
    2025-03-03

最新評論