java实体类空值lambda链式设置默认值

第一版

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.json.JSONUtil;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.experimental.Accessors;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.util.function.Function;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;

/**
 * @author wjh
 * @since 2022/2/8
 */

public class BeanUtil {

    @Data
    @Accessors(chain = true)
    public static class A{
        private String type;
        private B b;
    }
    @Data
    @Accessors(chain = true)
    public static class B{
        private String status;
    }

    public static void main(String[] args) {
        A a = new A().setType("2");
        BeanUtil.null2Default(a)
                .build(A::getType, "1")
                .build(A::getB, new B().setStatus("a"));

        Console.log(JSONUtil.toJsonStr(a));  // {"b":{"status":"a"},"type":"2"}
    }

    public static <T> Build<T> null2Default(T t){
        return new Build<>(t);
    }

    public static class Build<T>{
        private final T t;

        public Build(T t) {
            this.t = t;
        }

        @SneakyThrows
        public Build<T> build(MyFun<T, ?> k, Object v)  {
            // 读取lambda信息
            Method method = k.getClass().getDeclaredMethod("writeReplace");
            method.setAccessible(true);
            SerializedLambda serializedLambda = (SerializedLambda)method.invoke(k);

            // 读取字段类型 ()Ljava/lang/Long; -> java.lang.Long
            String implMethodSignature = serializedLambda.getImplMethodSignature()
                    .replace("()L", "")
                    .replace(";", "")
                    .replace("/", ".");

            Class<?> valueClass = v.getClass();
            Assert.isTrue(implMethodSignature.equals(valueClass.getTypeName()),
                    StrUtil.format("required value type {}, but current is {}", implMethodSignature, valueClass.getTypeName()));

            // 读取字段获取方法 -- getXxx
            String getter = serializedLambda.getImplMethodName();
            // 通过反射读取该字段值
            Class<?> beanClass = t.getClass();
            // null值判断 TODO empty 空串
            Object currentVal = beanClass.getDeclaredMethod(getter).invoke(t);

            if (currentVal == null) {
                // 通过setXx设置属性
                String setter = getter.replaceFirst("g", "s");
                Method declaredMethod = beanClass.getDeclaredMethod(setter, valueClass);
                method.setAccessible(true);
                declaredMethod.invoke(t, v);
            }

            return this;
        }
    }

    @FunctionalInterface
    public interface MyFun<T, R> extends Function<T, R>, Serializable { }
}

第二版:

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.lang.Console;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.experimental.Accessors;
import org.junit.jupiter.api.Test;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.*;
import java.util.*;
import java.util.function.Function;

/**
 * @author wang.jh
 * @since 2022/2/8
 */
public class BeanUtil {
    public static <T> Builder<T> null2Default(T t) {
        return new Builder<>(t, Builder.Full.NULL);
    }

    public static <T> Builder<T> empty2Default(T t) {
        return new Builder<>(t, Builder.Full.EMPTY);
    }

    public static <T> Builder<T> blank2Default(T t) {
        return new Builder<>(t, Builder.Full.BLANK);
    }

    @FunctionalInterface
    public interface MyFun<T, R> extends Function<T, R>, Serializable {
    }

    public static class Builder<T> {
        private final T target;
        private final Full full;
        private boolean strict = true;

        public Builder(T target, Full full) {
            this.target = target;
            this.full = full;
        }

        private boolean checkGenerics(Object value, Type[] types, String fieldName) {

            // 无泛型 or ( 泛型数量为1 and 泛型为Object类型 )
            if (types == null || types.length == 0 || (types.length == 1 && value.getClass().getTypeName().equals(Object.class.getTypeName()))) {
                return true;
            }

            if (value instanceof Iterable) {
                Iterable<?> iterable = (Iterable<?>) value;

                Class<?> typeClass = (Class<?>) types[0];

                for (Object item : iterable) {
                    Assert.isTrue(typeClass.isAssignableFrom(item.getClass()),
                            StrUtil.format("field {} the generic is <{}>, but there is a value of type {}", fieldName, typeClass.getTypeName(), item.getClass().getTypeName()));
                }
            } else if (value instanceof Iterator) {
                Class<?> typeClass = (Class<?>) types[0];

                Iterator<?> iterator = (Iterator<?>) value;
                while (iterator.hasNext()) {
                    Object item = iterator.next();
                    Assert.isTrue(typeClass.isAssignableFrom(item.getClass()),
                            StrUtil.format("field {} the generic is <{}>, but there is a value of type {}", fieldName, typeClass.getTypeName(), item.getClass().getTypeName()));
                }
            } else if (value instanceof Map) {
                Map<?, ?> map = (Map<?, ?>) value;

                if (map.size() == 0) {
                    return true;
                }

                if (!types[0].getTypeName().equals(Object.class.getTypeName())) {
                    Set<?> keySet = map.keySet();
                    Class<?> keyType = (Class<?>) types[0];

                    Iterator<?> iterator = keySet.iterator();
                    while (iterator.hasNext()) {
                        Object next = iterator.next();
                        Assert.isTrue(keyType.isAssignableFrom(next.getClass()),
                                StrUtil.format("field {} the generic 'key' type is <{}>, but there is a value of type {}", fieldName, keyType.getTypeName(), next.getClass().getTypeName()));
                    }
                }

                if (!types[1].getTypeName().equals(Object.class.getTypeName())) {
                    Collection<?> values = map.values();
                    Class<?> valueType = (Class<?>) types[1];

                    Iterator<?> iterator = values.iterator();
                    while (iterator.hasNext()) {
                        Object next = iterator.next();
                        Assert.isTrue(valueType.isAssignableFrom(next.getClass()),
                                StrUtil.format("field {} the generic 'value' type is <{}>, but there is a value of type {}", fieldName, valueType.getTypeName(), next.getClass().getTypeName()));
                    }
                }

                return true;
            }

            return true;
        }

        public T build() {
            return target;
        }

        public Builder<T> cancelStrictMode() {
            this.strict = false;
            return this;
        }

        private boolean whetherModify(Object currentVal, Full full) {
            if (currentVal == null) {
                return true;
            }
            switch (full) {
                case NULL:
                    return ObjectUtil.isNull(currentVal);
                case EMPTY:
                    return ObjectUtil.isEmpty(currentVal) || cn.hutool.core.bean.BeanUtil.isEmpty(currentVal);
                case BLANK:
                    return ObjectUtil.isEmpty(currentVal instanceof CharSequence ? StrUtil.trim((CharSequence) currentVal) : currentVal);
                default:
                    return false;
            }
        }

        @SneakyThrows
        public Builder<T> assign(MyFun<T, ?> k, Object v) {
            // 读取lambda信息
            Method method = k.getClass().getDeclaredMethod("writeReplace");
            this.setAccessible(method);
            SerializedLambda serializedLambda = (SerializedLambda) method.invoke(k);

            // 修改source Class对象
            Class<?> beanClass = target.getClass();

            // 读取字段getter方法 -- getXxx
            String getter = serializedLambda.getImplMethodName();

            Method getterMethod = beanClass.getMethod(getter);
            Class<?> returnType = getterMethod.getReturnType();

            Class<?> valueClass = v.getClass();

            Assert.isTrue(returnType.isAssignableFrom(valueClass),
                    String.format("required value type %s, but current type is %s", returnType.getTypeName(), valueClass.getTypeName()));

            Type[] genericsTypes = this.getGenericsType(getterMethod);

            String fieldName = this.lowerUpperCaseByFirst(getter.replace("get", ""), false);

            if (strict) {
                this.checkGenerics(v, genericsTypes, fieldName);
            }

            // 通过反射读取该字段值
            Object currentVal = beanClass.getDeclaredMethod(getter).invoke(target);

            // 修改值判断
            if (this.whetherModify(currentVal, full)) {
                Field field = beanClass.getDeclaredField(fieldName);
                this.setAccessible(field);
                field.set(target, v);
            }

            return this;
        }

        private Type[] getGenericsType(Method method) {
            Type returnType = method.getGenericReturnType();
            if (returnType instanceof ParameterizedType) {
                return ((ParameterizedType) returnType).getActualTypeArguments();
            }
            return null;
        }

        public <M extends AccessibleObject> void setAccessible(M accessibleObject) {
            if (null != accessibleObject && !accessibleObject.isAccessible()) {
                accessibleObject.setAccessible(true);
            }
        }

        private String lowerUpperCaseByFirst(String str, boolean toLower) {
            char[] cs = str.toCharArray();
            if (toLower)
                cs[0] -= 32;
            else
                cs[0] += 32;
            return String.valueOf(cs);
        }

        private enum Full {
            NULL, EMPTY, BLANK
        }
    }
}

class Tester {
    @Data
    @Accessors(chain = true)
    public static class A {
        public String aString;
        public String bString;
        public Integer cInteger;
        public Boolean dBoolean;
    }
    @Test
    public void testOk(){
        A a = new A().setAString("").setBString(" ");

        TimeInterval timer = DateUtil.timer();
        BeanUtil.null2Default(a)
                .assign(A::getAString, "a null2Default")
                .assign(A::getBString, "b null2Default")
                .assign(A::getDBoolean, false);
        Console.log("耗时: {}  --  {}", timer.intervalPretty(), JSONUtil.toJsonStr(a)); //{"aString":"","dBoolean":false,"bString":" "}

        timer.restart();
        BeanUtil.empty2Default(a)
                .assign(A::getAString, "a empty2Default")
                .assign(A::getBString,"b empty2Default")
                .assign(A::getCInteger, 2);
        Console.log("耗时: {}  --  {}", timer.intervalPretty(), JSONUtil.toJsonStr(a)); //{"aString":"a empty2Default","dBoolean":false,"bString":" ","cInteger":2}

        timer.restart();
        BeanUtil.blank2Default(a)
                .assign(A::getBString, "b blank2Default")
                .assign(A::getCInteger, 3);
        Console.log("耗时: {}  --  {}", timer.intervalPretty(), JSONUtil.toJsonStr(a)); //{"aString":"a blank2Default","dBoolean":false,"bString":"b blank2Default","cInteger":2}


        timer.clear();
    }

    @Data
    @Accessors(chain = true)
    public static class B {
        List<String> aListStr;
        List<Object> bListObj;
        List<Number> cListNum;
    }

    @Test
    public void testColl(){
        B b = new B();
        List<String> list_1 = CollUtil.newArrayList("1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
        List<Object> list_2 = CollUtil.newArrayList("1", "2", "3", "4", "5", "6", "7", "8", "9", 10);
        List<Object> list_3 = CollUtil.newLinkedList(1, 2, 3, 4L, 5, 6.6, 7, 8);

        TimeInterval timer = DateUtil.timer();
        BeanUtil.null2Default(b)
                .cancelStrictMode()
                .assign(B::getAListStr, list_1)
                .assign(B::getBListObj, list_2)
                .assign(B::getCListNum, list_3);

        //{"cListNum":[1,2,3,4,5,6.6,7,8],"aListStr":["1","2","3","4","5","6","7","8","9","10"],"bListObj":["1","2","3","4","5","6","7","8","9",10]}
        Console.log("耗时: {}  --  {}", timer.intervalPretty(), JSONUtil.toJsonStr(b));

        timer.clear();
    }


    @Data
    @Accessors(chain = true)
    public static class C {
        private Map<String, String> aMapStrStr;
        private Map<String, Integer> bMapStrInt;
        private Map<String,Serializable> cMapStrSerial;
    }

    @Test
    public void testMap(){
        Map<String, String> map_1 = MapUtil.builder("k1","v1").put("k2","v2").map();
        Map<String, Integer> map_2 = MapUtil.builder("k1",1).put("k2",2).map();
        Map<String,Serializable> map_3 = new LinkedHashMap<>();
        C c = new C().setCMapStrSerial(map_3);

        TimeInterval timer = DateUtil.timer();
        BeanUtil.null2Default(c)
                .assign(C::getAMapStrStr, map_1)
                .assign(C::getCMapStrSerial, map_3);

        Console.log("耗时: {}  --  {}", timer.intervalPretty(), JSONUtil.toJsonStr(c)); //{"cMapStrSerial":{},"aMapStrStr":{"k1":"v1","k2":"v2"}}


        map_3.put("k1", 123);
        timer.restart();
        BeanUtil.empty2Default(c)
                .assign(C::getCMapStrSerial, map_3)
                .assign(C::getBMapStrInt, map_2);
        //{"bMapStrInt":{"k1":1,"k2":2},"cMapStrSerial":{"k1":123},"aMapStrStr":{"k1":"v1","k2":"v2"}}
        Console.log("耗时: {}  --  {}", timer.intervalPretty(), JSONUtil.toJsonStr(c));

        timer.clear();
    }


    // -----------------------------------------------------------------------------------------------------------------
    // 反例
    @Data
    @Accessors(chain = true)
    public static class D {
        public String aString;
        public A bObj;

        public List<String> cListStr;

        private Map<String, String> dMapStrStr;
    }
    @Test
    public void test1(){

        D d = new D();

        try {
            BeanUtil.null2Default(d).assign(D::getAString, 1);
        } catch (Exception e) {
            Console.error(e.getMessage()); //required value type java.lang.String, but current type is java.lang.Integer
        }

        try {
            BeanUtil.null2Default(d).assign(D::getBObj, new B());
        } catch (Exception e) {
            Console.error(e.getMessage());//required value type com.wjh.utils.Tester$A, but current type is com.wjh.utils.Tester$B
        }

        try {
            BeanUtil.null2Default(d).assign(D::getCListStr, CollUtil.newArrayList("ok1", "ok2", 1));
        } catch (Exception e) {
            Console.error(e.getMessage());//field cListStr the generic is <java.lang.String>, but there is a value of type java.lang.Integer
        }

        try {
            BeanUtil.null2Default(d).assign(D::getDMapStrStr, MapUtil.of("str",2));
        } catch (Exception e) {
            Console.error(e.getMessage());//field dMapStrStr the generic 'value' type is <java.lang.String>, but there is a value of type java.lang.Integer
        }
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值