美文网首页Java 杂谈
Immutable类生成器

Immutable类生成器

作者: 蚊子squirrel | 来源:发表于2019-01-28 22:56 被阅读133次

    在项目开发过程中,有时需要将类定义成不可变(Immutable)类型,例如在一些暴露给第三方的接口参数对象,对于复杂多层次的自定义类,手工编写Immutable类是个繁琐且容易出错的工作,为此写了一个Immutable自动生成工具。

    1. mutable(可变)和immutable(不可变)类型的区别
    • 可变类型的对象:提供了可以改变其内部数据值的操作,其内部的值可以被重新更改。
    • 不可变数据类型:其内部的操作不会改变内部的值,一旦试图更改其内部值,将会构造一个新的对象而非对原来的值进行更改。
      例如Java中String类就是一个Immutable对象
    String var = "hello world";  
    var.toUpperCase();  
    

    toUpperCase()方法不会改变var中包含的数据“Hello word”。而是创建一个新的String对象并将其初始化为“HELLO WORLD”,然后返回这个新对象的引用。

    2.mutable和immutable类型的优缺点
    • mutable 优点:减少数据的拷贝次数,从而其效率 要高于immutable
      缺点:可变类型由于其内部数据可变,所以其风险更大
    • immutable 缺点: 由于内部数据不可变,所以对其频发修改会产生大量的临时拷贝,浪费空间。
      优点:内部数据的不可变导致其更加安全,可以用作多线程的共享对象而不必考虑同步问题
    3.如何构造一个immutable类
    • 用private final修饰所有fileds中的成员;private保证内部成员不会被外部直接访问;final确保在成员被初始化之后不会被重新assigned
    • 不提供改变成员的方法如setter
    • 使用final修饰自定义类,确保类中的所有方法不会被重写。
    4.复杂自定义类Immutable生成器

    简单的类,可以通过第3节方法手动编写,但如果field中有大量自定义类,这些自定义类还包含很多自定义类filed,那么手工编写Immutable将会变得非常繁琐,会充斥大量的get方法以及赋值操作,很容易出错,另外与Array、List、Map等类型时,对象的深度拷贝都需要特殊处理。为此写一个Imuutable类的自动生成工具,具体代码如下:

    package com.wwb.utils;
    
    import java.beans.IntrospectionException;
    import java.beans.PropertyDescriptor;
    import java.lang.reflect.Array;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
    import java.math.BigDecimal;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Set;
    import java.util.TreeMap;
    import java.util.TreeSet;
    
    import org.springframework.util.ClassUtils;
    
    import com.google.common.base.Function;
    
    /**
     * 不可变类(Immutable)生成工具类 功能:
     * 1.支持原生类型、Date、BigDecimal、Map、Set、List
     * 2.支持自定义类自动生成不可变类 3.支持集合类生成真正不可变集合
     * 
     * 限制:
     * 1.不支持不同包下的同名类
     * 2.不支持多层参数集合类,例如List<List<CustomizeClass>>,仅支持一层
     * 
     * 使用方法:
     * 1.ImmutableClassTool tool = new ImmutableClassTool();
     * 2.tool.addIngoreClass(xxx.class);//指定忽略的类型
     * 3.tool.addIngoreProperty("zzz");//指定忽略的属性名 4.String classStr = tool.generate(CustomizeClass.class);
     * 
     * @author wwb 2019-1-19
     */
    public class ImmutableClassTool {
        private static final String CLASS_PREFIX = "Immutable";
        private static final String LINE_BREAK = "\n";
        private Set<String> unGeneratedClass = new HashSet<String>();
        private Set<String> generatedClass = new HashSet<String>();
        private Set<String> packageSet = new HashSet<String>();
    
        private Set<Class<?>> ignoreClass = new HashSet<Class<?>>();// 忽略的类
        private Set<String> ignoreProperty = new HashSet<String>();// 忽略的属性名
    
        public void addIngoreClass(Class<?> cls) {
            ignoreClass.add(cls);
        }
    
        public void addIngoreProperty(String propertyName) {
            ignoreProperty.add(propertyName);
        }
    
        private String generataImmutaleClass(Class<?> cls) throws ClassNotFoundException, IntrospectionException {
            if (generatedClass.contains(cls.getName())) {
                return LINE_BREAK;
            }
            StringBuilder builder = new StringBuilder();
            String srcClassName = cls.getSimpleName();
            String className = CLASS_PREFIX + srcClassName;
            builder.append("\n@Getter\n");
            builder.append("public class ");
            builder.append(className).append(" {\n");
    
            /* generate all fileds */
            builder.append(this.generateFields(cls));
    
            /* generate constructor */
            builder.append(this.generateConstructor(cls));
            builder.append("  }\n");
            generatedClass.add(cls.getName());
            unGeneratedClass.remove(cls.getName());
            return builder.toString();
        }
    
        public String generate(Class<?> cls) throws ClassNotFoundException, IntrospectionException {
            StringBuilder builder = new StringBuilder();
            String srcClassName = cls.getSimpleName();
            String className = CLASS_PREFIX + srcClassName;
            builder.append("\n@Getter\n");
            builder.append("public class ");
            builder.append(className).append(" {\n");
    
            builder.append(generateFields(cls));
            builder.append(generateConstructor(cls));
            builder.append(generateInnerImmutableClass());
    
            builder.insert(0, generateComment(cls));
            builder.insert(0, generatePackageExpr(cls));
            builder.append("}\n");
            return builder.toString();
        }
    
        /**
         * generate fields
         * 
         * @param cls
         * @return
         */
        private String generateFields(Class<?> cls) {
            /* generate all fileds */
            StringBuilder builder = new StringBuilder();
            Field[] fields = cls.getDeclaredFields();
            for (Field field : fields) {
                if (hasGetMethod(field, cls)) {
                    builder.append("  private final ");
                    builder.append(getFieldType(field));
                    builder.append(" ");
                    builder.append(field.getName()).append(";\n");
                }
            }
            return builder.toString();
        }
    
        /**
         * generate constructor
         * 
         * @param cls
         * @return
         * @throws IntrospectionException
         */
        private String generateConstructor(Class<?> cls) throws IntrospectionException {
            StringBuilder builder = new StringBuilder();
            String srcClassName = cls.getSimpleName();
            String className = CLASS_PREFIX + srcClassName;
            Field[] fields = cls.getDeclaredFields();
    
            String paramName = srcClassName.substring(0, 1).toLowerCase() + srcClassName.substring(1);
            builder.append("  public ").append(className).append("(").append(srcClassName).append(" ").append(paramName)
                    .append("){\n");
            for (Field filed : fields) {
                if (hasGetMethod(filed, cls)) {
                    builder.append("     this.").append(filed.getName()).append(" = ");
                    builder.append(generateGetExpr(filed, paramName, cls)).append(";\n");
    
                }
            }
            builder.append("    }\n");
            return builder.toString();
        }
    
        private String generateInnerImmutableClass() throws ClassNotFoundException, IntrospectionException {
            /* generate immutable class */
            StringBuilder builder = new StringBuilder();
            while (unGeneratedClass.size() > 0) {
                HashSet<String> unGeneratedClassCopy = new HashSet<String>();
                unGeneratedClassCopy.addAll(unGeneratedClass);
                for (String clsName : unGeneratedClassCopy) {
                    builder.append(generataImmutaleClass(Class.forName(clsName)));
                }
            }
            return builder.toString();
        }
    
        private boolean isImmutable(Class<?> type) {
            if (ClassUtils.isPrimitiveOrWrapper(type) || type.isAssignableFrom(String.class)
                    || type.isAssignableFrom(BigDecimal.class) || type.isAssignableFrom(Date.class)
                    || type.isAssignableFrom(Map.class) || type.isAssignableFrom(List.class)
                    || type.isAssignableFrom(Set.class)) {
                return true;
            } else if (type.isArray() && isImmutable(type.getComponentType())) {
                return true;
            } else if (this.ignoreClass.contains(type)) {
                return true;
            }
            return false;
        }
    
        private boolean hasGetMethod(Field filed, Class<?> cls) {
            String filedName = filed.getName();
            try {
                PropertyDescriptor descriptor = new PropertyDescriptor(filedName, cls);
                return descriptor.getReadMethod() != null;
            } catch (IntrospectionException e) {
                return false;
            }
    
        }
    
        private String generateGetExpr(Field field, String paramName, Class<?> cls) throws IntrospectionException {
            StringBuilder builder = new StringBuilder();
            String filedName = field.getName();
    
            Class<?> type = field.getType();
            String getMethodExpr = null;
            PropertyDescriptor descriptor = new PropertyDescriptor(filedName, cls);
            Method readMethod = descriptor.getReadMethod();
            if (readMethod != null) {
                getMethodExpr = paramName + "." + readMethod.getName() + "()";
            }
    
            if (ClassUtils.isPrimitiveOrWrapper(type) || type.isAssignableFrom(String.class)
                    || type.isAssignableFrom(BigDecimal.class) || this.ignoreProperty.contains(field.getName())) {
                builder.append(getMethodExpr);
            } else if (type.isAssignableFrom(Date.class)) {
                builder.append("(Date)").append(getMethodExpr).append(".clone()");
            } else if (type.isArray()) {
                builder.append(getArrayTransformMethod(type, getMethodExpr));
            } else if (type.isAssignableFrom(Map.class)) {
                builder.append("ImmutableMap.copyOf(");
                Type genericType = field.getGenericType();
                if (genericType instanceof ParameterizedType) {
                    Class<?> paramClass = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[1];
                    if (isImmutable(paramClass)) {
                        builder.append(getMethodExpr);
                    } else {
                        builder.append(getGuavaTransformMethod(paramClass, getMethodExpr, Map.class));
                    }
                }
                builder.append(")");
                this.packageSet.add("com.google.common.collect.ImmutableMap");
            } else if (type.isAssignableFrom(List.class)) {
                builder.append("ImmutableList.copyOf(");
                Type genericType = field.getGenericType();
                if (genericType instanceof ParameterizedType) {
                    Class<?> paramClass = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
                    if (isImmutable(paramClass)) {
                        builder.append(getMethodExpr);
                    } else {
                        builder.append(getGuavaTransformMethod(paramClass, getMethodExpr, List.class));
                    }
                }
                builder.append(")");
                this.packageSet.add("com.google.common.collect.ImmutableList");
            } else if (type.isAssignableFrom(Set.class)) {
                builder.append("ImmutableSet.copyOf(");
                Type genericType = field.getGenericType();
                if (genericType instanceof ParameterizedType) {
                    Class<?> paramClass = (Class<?>) ((ParameterizedType) genericType).getActualTypeArguments()[0];
                    if (isImmutable(paramClass)) {
                        builder.append(getMethodExpr);
                    } else {
                        builder.append(getGuavaTransformMethod(paramClass, getMethodExpr, Set.class));
                    }
                }
                builder.append(")");
                this.packageSet.add("com.google.common.collect.ImmutableSet");
            } else {
                builder.append("new ");
                builder.append(CLASS_PREFIX);
                builder.append(field.getType().getSimpleName());
                builder.append("(").append(getMethodExpr).append(")");
            }
            return builder.toString();
        }
    
        private String generatePackageExpr(Class<?> cls) {
            StringBuilder builder = new StringBuilder();
            builder.append("package ").append(cls.getPackage().getName()).append(";\n\n");
            for (String pack : this.packageSet) {
                builder.append("import ").append(pack).append(";\n");
            }
            builder.append("import lombok.Getter;\n");
            return builder.toString();
        }
    
        private String getFieldType(Field field) {
            Class<?> type = field.getType();
            if (ClassUtils.isPrimitiveOrWrapper(type)) {
                return type.getSimpleName();
            } else if (type.isAssignableFrom(String.class) || type.isAssignableFrom(BigDecimal.class)
                    || type.isAssignableFrom(Date.class)) {
                this.packageSet.add(type.getName());
                return type.getSimpleName();
            } else if (type.isAssignableFrom(Map.class) || type.isAssignableFrom(List.class)
                    || type.isAssignableFrom(Set.class)) {
                this.packageSet.add(type.getName());
                Type genericType = field.getGenericType();
    
                if (genericType instanceof ParameterizedType) {
                    Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
                    if (type.isAssignableFrom(Map.class)) {
                        Class<?> keyClass = (Class<?>) actualTypeArguments[0];
                        Class<?> valueClass = (Class<?>) actualTypeArguments[1];
                        return "Map<" + keyClass.getSimpleName() + "," + getParameterClass(valueClass) + ">";
                    }
                    if (type.isAssignableFrom(List.class)) {
                        return "List<" + getParameterClass((Class<?>) actualTypeArguments[0]) + ">";
                    }
                    if (type.isAssignableFrom(Set.class)) {
                        return "Set<" + getParameterClass((Class<?>) actualTypeArguments[0]) + ">";
                    }
                }
                return genericType.toString();
            } else if (this.ignoreProperty.contains(field.getName())) {
                this.packageSet.add(type.getName());
                return type.getSimpleName();
            } else {
                return getParameterClass(type);
            }
        }
    
        private String getParameterClass(Class<?> parameterClass) {
            if (isImmutable(parameterClass)) {
                return parameterClass.getSimpleName();
            } else {
                String clsName = parameterClass.isArray() ? parameterClass.getComponentType().getName()
                        : parameterClass.getName();
                this.unGeneratedClass.add(clsName);
                this.packageSet.add(clsName);
                return CLASS_PREFIX + parameterClass.getSimpleName();
            }
        }
    
        private String getGuavaTransformMethod(Class<?> paramClass, String getMethodExpr, Class<?> collectionType) {
            StringBuilder builder = new StringBuilder();
            if (collectionType.equals(List.class)) {
                builder.append("ImmutableClassTool.transformList(");
            } else if (collectionType.equals(Set.class)) {
                builder.append("ImmutableClassTool.transformSet(");
            } else if (collectionType.equals(Map.class)) {
                builder.append("ImmutableClassTool.transformMapValues(");
            } else {
                return "";
            }
    
            builder.append(getMethodExpr);
            builder.append(", new Function<");
            builder.append(paramClass.getSimpleName());
            builder.append(",");
            builder.append(getParameterClass(paramClass));
            builder.append(">(){\n     @Override\n     public " + getParameterClass(paramClass) + " apply("
                    + paramClass.getSimpleName() + " input) {\n       return new " + getParameterClass(paramClass)
                    + "(input);\n     }})");
            this.packageSet.add("com.google.common.base.Function");
            this.packageSet.add(this.getClass().getName());
            this.packageSet.add(paramClass.getName());
            return builder.toString();
        }
    
        private String getArrayTransformMethod(Class<?> paramClass, String getMethodExpr) {
            if (paramClass.isArray()) {
                Class<?> elementType = paramClass.getComponentType();
                if (!this.isImmutable(elementType)) {
                    StringBuilder builder = new StringBuilder();
                    builder.append("ImmutableClassTool.transformArray(");
                    builder.append(getMethodExpr);
                    builder.append(",");
                    builder.append(getParameterClass(elementType));
                    builder.append(".class, new Function<");
                    builder.append(elementType.getSimpleName());
                    builder.append(",");
                    builder.append(getParameterClass(elementType));
                    builder.append(">(){\n     @Override\n     public " + getParameterClass(elementType) + " apply("
                            + elementType.getSimpleName() + " input) {\n       return new " + getParameterClass(elementType)
                            + "(input);\n     }})");
                    this.packageSet.add("com.google.common.base.Function");
                    this.packageSet.add(elementType.getName());
                    this.packageSet.add(this.getClass().getName());
                    return builder.toString();
                }
            }
            return getMethodExpr;
        }
    
        private String generateComment(Class<?> cls) {
            StringBuilder builder = new StringBuilder();
            builder.append("\n/**\n * Immutable class of ");
            builder.append(cls.getName());
            builder.append("\n * generated by ImmutableClassTool\n */");
            return builder.toString();
        }
    
        @SuppressWarnings("unchecked")
        public static <F, T> T[] transformArray(F[] fromArray, Class<T> type, Function<? super F, ? extends T> function) {
            T[] toArray = (T[]) Array.newInstance(type, fromArray.length);
            for (int i = 0; i < fromArray.length; i++) {
                toArray[i] = function.apply(fromArray[i]);
            }
            return toArray;
        }
    
        public static <F, T> List<T> transformList(List<F> fromList, Function<? super F, ? extends T> function) {
            List<T> list = (fromList instanceof LinkedList) ? new LinkedList<T>() : new ArrayList<T>(fromList.size());
            for (F f : fromList) {
                list.add(function.apply(f));
            }
            return list;
        }
    
        public static <F, T> Set<T> transformSet(Set<F> fromSet, Function<? super F, ? extends T> function) {
            Set<T> set = (fromSet instanceof TreeSet) ? new TreeSet<T>() : new HashSet<T>(fromSet.size());
            for (F f : fromSet) {
                set.add(function.apply(f));
            }
            return set;
        }
    
        public static <K, V1, V2> Map<K, V2> transformMapValues(Map<K, V1> fromMap, Function<? super V1, V2> function) {
            Map<K, V2> map = (fromMap instanceof TreeMap) ? new TreeMap<K, V2>() : new HashMap<K, V2>();
    
            for (Entry<K, V1> entry : fromMap.entrySet()) {
                map.put(entry.getKey(), function.apply(entry.getValue()));
            }
            return map;
        }
    
    }
    
    
    

    相关文章

      网友评论

        本文标题:Immutable类生成器

        本文链接:https://www.haomeiwen.com/subject/xvhmjqtx.html