在运行时创建DEEP不可变对象

问题描述 投票:4回答:2

我需要使用Java在运行时创建对象的不可变副本。我使用了org.springframework.cglib.beans.ImmutableBean,它可以使用CGLIB创建对象的不可变副本。

但问题在于它提供了“第一级”的不变性:它不允许更改输入对象的属性,但它允许更改内部对象(例如,获取集合并向其添加元素或获取内部对象并修改其参数等。 )

所以问题是:创建一个对象的深度(递归)不可变副本的正确方法是什么,这样一个人也不能更改内部对象(在任何嵌套级别)?

java immutability cglib
2个回答
5
投票

您可以遍历对象树并使用CGLIB通过使用跳过所需方法的拦截器使每个对象不可变。然而,困难的部分是确定修改对象状态的所有方法 - 对于树中的每个对象。

package ut.test;

import static org.junit.Assert.assertEquals;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

import com.google.common.collect.Lists;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class MyTest {

    public static class Inner {
        private String data = "hello";

        public Inner() {}

        public String getData() {
            return data;
        }

        public void setData(String data) {
            this.data = data;
        }

        @Override
        public String toString() {
            return data;
        }

    }

    public static class Outer {
        private List<Inner> list = Lists.newArrayList(new Inner());

        public Outer() {}

        public List<Inner> getList() {
            return list;
        }

        public void setList(List<Inner> list) {
            this.list = list;
        }
    }

    public static class GetOnlyDelegatingMethodInterceptor implements MethodInterceptor {
        private Object delegate;

        public GetOnlyDelegatingMethodInterceptor(Object delegate) {
            this.delegate = delegate;
        }

        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            if (method.getName().startsWith("get")) {
                return makeImmutable(proxy.invoke(delegate, args));
            }

            if (method.getName().equals("toString")) {
                return proxy.invoke(delegate, args);
            }

            if (method.getDeclaringClass().equals(Object.class)) {
                return proxy.invoke(delegate, args);
            }

            // you may check for other methods here

            // skip all others
            return null;
        }
    }

    private static Object makeImmutable(Object obj) {
        if (obj == null) {
            return obj;
        }

        Enhancer e = new Enhancer();
        e.setSuperclass(obj.getClass());
        e.setCallback(new GetOnlyDelegatingMethodInterceptor(obj));
        return e.create();
    }

    @Test
    public void testImmutable() {
        Outer outerImmutable = (Outer) makeImmutable(new Outer());

        // this is initial state
        assertEquals(outerImmutable.getList().toString(), "[hello]");

        // trying to set empty list
        outerImmutable.setList(new ArrayList<>());
        // but it's still the same
        assertEquals(outerImmutable.getList().toString(), "[hello]");

        // going deeper
        outerImmutable.getList().get(0).setData("bye!");
        // but still no changes
        assertEquals(outerImmutable.getList().toString(), "[hello]");
    }
}

0
投票

你可以使用ImmutableProxy库的reflection-util

例:

public class Inner
{
    private String data = "hello";
    // getters and setters
}
public class Outer
{
    private List<Inner> list = Arrays.asList(new Inner());
    // getters and setters
}
Outer outerImmutable = ImmutableProxy.create(new Outer());
Inner firstElement = outerImmutable.getList().get(0)

// this is initial state
assertThat(firstElement.getData()).isEqualTo("hello");

// throws UnsupportedOperationException
outerImmutable.setList(…);

// throws UnsupportedOperationException
firstElement.setData("bye!");
© www.soinside.com 2019 - 2024. All rights reserved.