最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

前言:

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

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

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

期望的效果

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

實(shí)現(xiàn)步驟

定義一個(gè)函數(shù)式接口, 用來(lái)接收l(shuí)ambda方法引用

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

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

定義一個(gè)工具類, 用來(lái)解析獲取屬性名稱

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對(duì)應(yīng)的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步 如果沒(méi)有找到對(duì)應(yīng)的字段應(yīng)該拋出異常
        if (field == null) {
            throw new RuntimeException("No such class 「"+ implClass +"」 field 「" + fieldName + "」.");
        }

        return field;
    }

    static Field getField(String fieldName, SerializedLambda serializedLambda) {
        try {
            // 獲取的Class是字符串,并且包名是“/”分割,需要替換成“.”,才能獲取到對(duì)應(yīng)的Class對(duì)象
            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之后的字符串并轉(zhuǎn)換首字母為小寫
        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);
        }
    }
}

測(cè)試

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í)行測(cè)試 輸出結(jié)果

原理剖析

為什么SFunction必須繼承Serializable

首先簡(jiǎn)單了解一下java.io.Serializable接口,該接口很常見,我們?cè)诔志没粋€(gè)對(duì)象或者在RPC框架之間通信使用JDK序列化時(shí)都會(huì)讓傳輸?shù)膶?shí)體類實(shí)現(xiàn)該接口,該接口是一個(gè)標(biāo)記接口沒(méi)有定義任何方法,但是該接口文檔中有這么一段描述:

概要意思就是說(shuō),如果想在序列化時(shí)改變序列化的對(duì)象,可以通過(guò)在實(shí)體類中定義任意訪問(wèn)權(quán)限的Object writeReplace()來(lái)改變默認(rèn)序列化的對(duì)象。

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

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

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

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

SFunction Class中的writeReplace方法

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

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

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

java8中可以通過(guò)硬編碼

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

例如:

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

-Djdk.internal.lambda.dumpProxyClasses=.

例如:

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

其中實(shí)現(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]);
    }
}

通過(guò)源碼得知 調(diào)用writeReplace方法是為了獲取到方法返回的SerializedLambda對(duì)象

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

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

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

答案是肯定有意義的?。?!因?yàn)橥环椒ㄖ械亩x的Function只會(huì)動(dòng)態(tài)的創(chuàng)建一次實(shí)現(xiàn)類并只實(shí)例化一次,當(dāng)該方法被多次調(diào)用時(shí)即可走緩存中查詢?cè)摲椒ㄒ脤?duì)應(yīng)的Field。

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

通過(guò)在剛才的示例代碼中添加一行, 就能說(shuō)明該問(wèn)題, 之前15號(hào)對(duì)應(yīng)的是getName, 而此時(shí)的15號(hào)class對(duì)應(yīng)的是getAge這個(gè)函數(shù)引用

我們?cè)偻ㄟ^(guò)代碼驗(yàn)證一下 剛才的猜想

參考資料:

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

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

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

相關(guān)文章

  • 基于SpringBoot解析和生成CSV文件

    基于SpringBoot解析和生成CSV文件

    Apache?Commons?CSV是Apache?Commons項(xiàng)目中的一個(gè)子項(xiàng)目,專門用于處理CSV(Comma-Separated?Values,逗號(hào)分隔值)文件的Java庫(kù),CSV是一種常見的數(shù)據(jù)交換格式,本文給大家介紹了基于SpringBoot解析和生成CSV文件,需要的朋友可以參考下
    2024-12-12
  • Java實(shí)現(xiàn)Excel表單控件的添加與刪除

    Java實(shí)現(xiàn)Excel表單控件的添加與刪除

    本文通過(guò)Java代碼示例介紹如何在Excel表格中添加表單控件,包括文本框、單選按鈕、復(fù)選框、組合框、微調(diào)按鈕等,以及如何刪除Excel中的指定表單控件,需要的可以參考一下
    2022-05-05
  • java二維數(shù)組遍歷的2種代碼

    java二維數(shù)組遍歷的2種代碼

    這篇文章主要介紹了java二維數(shù)組遍歷的2種代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-07-07
  • Maven優(yōu)雅的添加第三方Jar包的方法

    Maven優(yōu)雅的添加第三方Jar包的方法

    下面小編就為大家?guī)?lái)一篇Maven優(yōu)雅的添加第三方Jar包的方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-09-09
  • CompletableFuture創(chuàng)建及功能使用全面詳解

    CompletableFuture創(chuàng)建及功能使用全面詳解

    這篇文章主要為大家介紹了CompletableFuture創(chuàng)建及功能使用全面詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • JavaIO模型中的BIO,NIO和AIO詳解

    JavaIO模型中的BIO,NIO和AIO詳解

    這篇文章主要為大家詳細(xì)介紹了JavaIO模型中的BIO,NIO和AIO,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-02-02
  • Java Swing JLabel標(biāo)簽的使用方法

    Java Swing JLabel標(biāo)簽的使用方法

    這篇文章主要介紹了Java Swing JLabel標(biāo)簽的使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • 使用Springboot對(duì)配置文件中的敏感信息加密

    使用Springboot對(duì)配置文件中的敏感信息加密

    這篇文章主要介紹了使用Springboot對(duì)配置文件中的敏感信息加密方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • 教你幾個(gè)?Java?編程中使用技巧

    教你幾個(gè)?Java?編程中使用技巧

    枯燥的編程中總得有些樂(lè)趣,今天我們不談?wù)撃切└呱畹募寄?,教你幾個(gè)在編程中的奇技淫巧,說(shuō)不定在某些時(shí)候還能炫耀一番呢,今天小編教你幾個(gè)?Java?編程中使用技巧,感興趣的朋友參考下吧
    2022-12-12
  • SpringBoot自動(dòng)裝配之@Import深入講解

    SpringBoot自動(dòng)裝配之@Import深入講解

    由于最近的項(xiàng)目需求,需要在把配置類導(dǎo)入到容器中,通過(guò)查詢,使用@Import注解就能實(shí)現(xiàn)這個(gè)功能,@Import注解能夠幫我們吧普通配置類(定義為Bean的類)導(dǎo)入到IOC容器中
    2023-01-01

最新評(píng)論

远安县| 侯马市| 鄄城县| 夏河县| 平凉市| 香格里拉县| 台州市| 和林格尔县| 齐齐哈尔市| 金塔县| 同江市| 临桂县| 文化| 北宁市| 山丹县| 娄烦县| 博白县| 阿拉尔市| 若尔盖县| 民丰县| 交城县| 白城市| 宿州市| 莱阳市| 云梦县| 尤溪县| 吉首市| 洛宁县| 浦江县| 太仓市| 恭城| 裕民县| 宜昌市| 玉环县| 梓潼县| 建德市| 横峰县| 明水县| 于田县| 永兴县| 嘉义市|