2014-05-19 2 views
0

String.intern()과 동일한 기능을 구현하려고하지만 다른 객체는 구현하려고합니다. 내 목표는 다음과 같습니다. 개체 A를 직렬화 한 다음 비 직렬화 할 것입니다. 어딘가에 A라는 또 다른 참조가있는 경우, deserialization의 결과가 같은 참조가되기를 원합니다.다른 객체에 대해 String.intern()과 동일한 기능 구현

내가 예상하는 것의 한 예입니다.

MyObject A = new MyObject(); 
A.data1 = 1; 
A.data2 = 2; 
byte[] serialized = serialize(A); 
A.data1 = 3; 
MyObject B = deserialize(serialized); // B!=A and B.data1=1, B.data2=2 
MyObject C = B.intern(); // Here we should have C == A. Consequently C.data1=3 AND C.data2=2 

내 구현 atm입니다. (실패)

public abstract class InternableObject { 

private static final AtomicLong maxObjectId = new AtomicLong(); 
private static final Map<Long, InternableObject> dataMap = new ConcurrentHashMap<>(); 
private final long objectId; 

public InternableObject() { 
    this.objectId = maxObjectId.incrementAndGet(); 

    dataMap.put(this.objectId, this); 
} 

@Override 
protected void finalize() throws Throwable { 
    super.finalize(); 
    dataMap.remove(this.objectId); 
} 

public final InternableObject intern() { 
    return intern(this); 
} 

public static InternableObject intern(InternableObject o) { 
    InternableObject r = dataMap.get(o.objectId); 

    if (r == null) { 
     throw new IllegalStateException(); 
    } else { 
     return r; 
    } 
} 
} 

내 단위 테스트합니다 (MyObject 클래스 InternableObject 확장)

private static class MyData extends InternableObject implements Serializable { 

    public int data; 

    public MyData(int data) { 
     this.data = data; 
    } 
} 

@Test 
public void testIntern() throws Exception { 
    MyData data1 = new MyData(7); 

    ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
    ObjectOutputStream oos = new ObjectOutputStream(baos); 
    oos.writeObject(data1); 
    oos.flush(); 
    baos.flush(); 
    oos.close(); 
    baos.close(); 
    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); 
    ObjectInputStream ois = new ObjectInputStream(bais); 
    MyData data2 = (MyData) ois.readObject(); 

    Assert.assertTrue(data1 == data2.intern()); // Fails here 
} 

실패가 역 직렬화되면 InternableObject 생성자가 호출되고 있다는 사실에 기인하고, 따라서 objectId는 2입니다 (일련화된 데이터에 "1"이 포함되어 있어도)

이 특정 문제를 해결하는 방법이나 상위 수준의 문제를 처리하는 다른 방법에 대한 아이디어가 있습니까?

고마워요

+2

문자열은 '불변'이므로이를 이해하는 것이 바람직합니다. 왜 당신은 임의의 객체에 대해이 기능을 원할 것입니까? –

+0

영감을 얻기 위해 '통화'의 출처를 살펴 보는 것이 좋습니다. – chrylis

답변

2

인스턴스를 생성하는 데 생성자를 사용하지 마십시오. 인스턴스가 이미 먼저 존재하는지 검사하고 이미 일치하는 인스턴스가없는 경우 인스턴스 만 작성하는 팩토리 메소드를 사용하십시오.

직렬화를 협업하려면 클래스가 readResolve()/writeReplace()를 사용해야합니다. http://docs.oracle.com/javase/7/docs/platform/serialization/spec/serial-arch.html#4539

생성자를 구현 한 방식에 따라 생성 중에 참조가 누출되어 문제를 해결하기가 어려울 수 있습니다. 또한 인스턴스 맵은 잠금에 의해 보호되지 않으므로 스레드가 저장되지 않습니다.

0

일반적으로 intern()의 모양 인을 형성하며 더 복잡한 별자리에서의 사용을 너무 제한하는 기본 클래스로 구현하면 안됩니다. 은 "같은"개체를 공유

1 :

는 두 가지 측면이 있습니다.

개체를 내부화하면 여러 개체를 같은 개체로 "내부화"할 수 있습니다. 내 생각에, 그 InternalableObjecte. 새로운 일련 번호로는 충분하지 않습니다. 클래스가 피팅 equals와 hashCode를 정의한다는 점이 더욱 중요합니다.

그럼 당신은 신원 Map<Object, Object>을 수행 할 수 있습니다

public class InternMap { 
    private final Map<Object, Object> identityMap = new HashMap<>(); 
    public static <I extends Internalizable<?>> Object intern(I x) { 
     Object first = identityMap.get(x); 
     if (first == null) { 
      first = x; 
      identityMap.put(x, x); 
     } 
     return first; 
    } 
} 

InternMap은 모든 클래스에 사용될 수 있지만, 우리는 일을 Internalizable하는 데 제한보다.

2. 동적으로 생성 된 비공유 개체를 .intern()으로 바꿉니다.자바 8 인터페이스에 defualt 방법으로 실현 될 수

:

interface Internalizable<T> { 
    public static final InternMap interns = new InternMap(); 
    public default T intern(Class<T> klazz) { 
     return klazz.cast(internMap.intern(this)); 
    } 

class C implements Internalizable<C> { ... } 

C x = new C(); 
x = x.intern(C.class); 

때문에 형의 삭제의 필요한 Class<T> 매개 변수입니다. 동시성은 여기에서 무시됩니다.

Java 8 이전에는 빈 인터페이스 인 Internalizable을 _marker : interface로 사용하고 정적 InternMap을 사용하십시오.