immutable java 类_Immutable类生成器

狄雅珺
2023-12-01

在项目开发过程中,有时需要将类定义成不可变(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.Collections;

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 org.springframework.util.ObjectUtils;

import com.google.common.base.Function;

/**

* 不可变类(Immutable)生成工具类 功能: 1.支持原生类型、Date、BigDecimal、Map、Set、List

* 2.支持自定义类自动生成不可变类 3.支持集合类生成真正不可变集合

*

* 限制: 1.不支持不同包下的同名类 2.不支持多层参数集合类,例如List>,仅支持一层

*

* 使用方法: 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 unGeneratedClass = new HashSet();

private Set generatedClass = new HashSet();

private Set packageSet = new HashSet();

private Set> ignoreClass = new HashSet>();// 忽略的类

private Set ignoreProperty = new HashSet();// 忽略的属性名

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 final 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 final 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");

builder.append(" Assert.notNull(").append(paramName).append(",\"").append(paramName).append(" is null\");\n");

this.packageSet.add("org.springframework.util.Assert");

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 unGeneratedClassCopy = new HashSet();

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("ImmutableClassTool.clone(").append(getMethodExpr).append(")");

} 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(getMethodExpr);

builder.append(" == null ? null :");

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";

}

if (type.isAssignableFrom(List.class)) {

return "List) actualTypeArguments[0]) + ">";

}

if (type.isAssignableFrom(Set.class)) {

return "Set) 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 T[] transformArray(F[] fromArray, Class type, Function super F, ? extends T> function) {

if (ObjectUtils.isEmpty(fromArray)) {

return null;

}

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 List transformList(List fromList, Function super F, ? extends T> function) {

if (fromList == null) {

return Collections.emptyList();

}

List list = (fromList instanceof LinkedList) ? new LinkedList() : new ArrayList(fromList.size());

for (F f : fromList) {

list.add(function.apply(f));

}

return list;

}

public static Set transformSet(Set fromSet, Function super F, ? extends T> function) {

if (fromSet == null) {

return Collections.emptySet();

}

Set set = (fromSet instanceof TreeSet) ? new TreeSet() : new HashSet(fromSet.size());

for (F f : fromSet) {

set.add(function.apply(f));

}

return set;

}

public static Map transformMapValues(Map fromMap, Function super V1, V2> function) {

if(fromMap == null){

return Collections.emptyMap();

}

Map map = (fromMap instanceof TreeMap) ? new TreeMap() : new HashMap();

for (Entry entry : fromMap.entrySet()) {

map.put(entry.getKey(), function.apply(entry.getValue()));

}

return map;

}

public static Date clone(Date date) {

return date != null ? (Date) date.clone() : null;

}

}

 类似资料: