Unable to access Inner class issue in java - java

local variables referenced from an inner class must be final or effectively final error is shown in the code below:
public Vector<Map<String, Object>> newsFeedConnection(String var, Hashtable punishment) {
ConnectionRequest connectionRequest;
connectionRequest = new ConnectionRequest() {
#Override
protected void readResponse(InputStream input) throws IOException {
JSONParser p = new JSONParser();
results = p.parse(new InputStreamReader(input));
punishment = (Hashtable) results.get("punishment");
}
}
}
But when i change change it into final (code below), it gives "cannot assign a value to final variable punishment" error again.
public Vector<Map<String, Object>> newsFeedConnection(String var, final Hashtable punishment) {
ConnectionRequest connectionRequest;
connectionRequest = new ConnectionRequest() {
#Override
protected void readResponse(InputStream input) throws IOException {
JSONParser p = new JSONParser();
results = p.parse(new InputStreamReader(input));
punishment = (Hashtable) results.get("punishment");
}
}
}
How do I solve this problem?If I set a global variable, I cannot access the value from the method in other classes.

You are reinitiating a final variable which is conceptually not acceptable, just change the values inside punishment without creating it again and that will solve your problem.

Pass by Value vs. Pass by Reference - When you pass an object reference you are doing pass by reference. When you do so you can change the state of the object by invoking appropriate methods on the object but you cannot change the reference of the object itself. For example:
public class TestPassByReference {
public static void main(String[] args){
StringBuilder stringBuilder = new StringBuilder("Lets Test!");
changeStringDoesNotWork(stringBuilder);
System.out.println(stringBuilder.toString());
changeString(stringBuilder);
System.out.println(stringBuilder.toString());
}
static void changeString(StringBuilder stringBuilder){
stringBuilder.append(" Yeah I did it!");
}
static void changeStringDoesNotWork(StringBuilder stringBuilder){
stringBuilder = new StringBuilder("This will not work!");
}
}
The output:
Lets Test! //Value did not change
Lets Test! Yeah I did it!
I hope now you can co-relate what you are trying to do clashes with this basic aspect and hence incorrect.
What you can do however is this:
HashTable tempHashTable = (Hashtable) results.get("punishment");
punishment.clear();
punishment.putAll(tempHashTable);
Also why use HashTable? There are better threadsafe collection classes out there which give better performance.

You can solve it by updating you punishment variable:
public Vector<Map<String, Object>> newsFeedConnection(String var, final Hashtable punishment) {
ConnectionRequest connectionRequest;
connectionRequest = new ConnectionRequest() {
#Override
protected void readResponse(InputStream input) throws IOException {
JSONParser p = new JSONParser();
results = p.parse(new InputStreamReader(input));
punishment.putAll((Hashtable) results.get("punishment"));
}
}
}
}

Related

Creating an object of Class, other than via the constructor

In Java, given
Class c = ...
We can make an object of this class by first obtaining a constructor. For example, if we want to use the default (no parameters) constructor,
c.getConstructor().newInstance()
This seems straightforward, and seems to match how things are done in Java source code.
But, curiously, it is not how things are done in JVM byte code. There, creating an object is done in two steps: new to actually create the object, then invokespecial to call an appropriate constructor.
Is there a way to bypass the constructor when what you have is a Class (with the actual class to be determined at runtime)? If not, was the rationale for the difference between how this works, and how the byte code works, ever documented?
You wanna allocate an uninitialized object.
You can try the library named Objenesis.
Otherwise, you can create an object by serialization. This is a widely used method to create a uninitialized object.
public class Serialization {
static class TestSerialization implements Serializable {
int val = 0;
public TestSerialization() {
System.out.println("constructor");
val = 1;
}
#Override
public String toString() {
return "val is " + val;
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
TestSerialization testSerialization = new TestSerialization();
// constructor
// val is 1
System.out.println(testSerialization);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(testSerialization);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
Object obj = ois.readObject();
// val is 1
System.out.println(obj);
}
}
One step closer, you can use ReflectionFactory to create an empty uninitialized object.
public class Main {
static class TestClass {
public int val = 0;
public TestClass() {
val = 1;
}
#Override
public String toString() {
return "value is " + val;
}
}
public static void main(String[] args) throws Exception {
// by constructor
TestClass obj = new TestClass();
// value is 1
System.out.println(obj);
// by reflect
Constructor<TestClass> constructor = TestClass.class.getConstructor();
obj = constructor.newInstance();
// value is 1
System.out.println(obj);
// by ReflectionFactory
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
Constructor<Object> objectConstructor = Object.class.getDeclaredConstructor();
Constructor<?> targetConstructor = reflectionFactory.newConstructorForSerialization(TestClass.class, objectConstructor);
obj = (TestClass) targetConstructor.newInstance();
// value is 0
System.out.println(obj);
}
}

relate toString with object creation

I have a fairly basic Java class with some class variables. I have overwridden toString() to provide me with a string output (which will eventually be output to a text file).
I am trying to elegantly create a way for me to use this string output to recreate the object with all of the variables set as before. The class looks something like this:
public class Report {
private String itemA;
private String itemB;
private String itemC;
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Items are::");
sb.append("\nItem A is: ").append(itemA);
sb.append("\nItem B is: ").append(itemB);
sb.append("\nItem C is: ").append(itemC);
return sb.toString();
}
}
this is how I can potentially tackle it using reflection:
public class Report {
private String itemA;
private String itemB;
private String itemC;
private final Map<String, String> MAPPING = new HashMap<>();
public Report(String itemA, String itemB, String itemC) {
this.itemA = itemA;
this.itemB = itemB;
this.itemC = itemC;
MAPPING.put("Item A is: ", "itemA");
MAPPING.put("Item B is: ", "itemB");
MAPPING.put("Item C is: ", "itemC");
}
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Items are::");
MAPPING.entrySet().forEach(entry -> {
sb.append("\n").append(entry.getKey()).append(BeanUtils.getProperty(this, entry.getValue()));
});
return sb.toString();
}
public Report createReportFromString(String reportString) {
List<String> reportLines = Arrays.asList(reportString.split("\n"));
HashMap<String, String> stringObjectRelationship = new HashMap<>();
reportLines.forEach(reportLine -> {
Optional<String> matchingKey = MAPPING.keySet().stream().filter(reportLine::contains).findFirst();
matchingKey.ifPresent(key -> {stringObjectRelationship.put(MAPPING.get(key), reportLine.split(key)[1]);});
});
stringObjectRelationship.forEach((variableName, variableValue) -> BeanUtils.setProperty(this, variableName, variableValue));
return this;
}
}
I basically want to relate the key in the report ("Item A is: ") to the name of the corresponding variable ("itemA") and use this relationship in both the toString() method and the createReportFromString(String string) method. Now when doing this there are a lot of possible exceptions that can be thrown and need to either be handled or thrown - and it then looks a lot less elegant than I would like.
I don't know if this is possible to do without reflection - or perhaps I could rearrange this class to make this possible?
What I can`t change is the structure of the string output in the toString().
Reflection bears multiple features:
Automatic discovery of features of a program at runtime
Support for dealing with features unknown at compile-time
Provide an abstraction of program features (e.g. methods or fields)
Your approach suggests that you don’t want an automatic discovery, as you are specifying the three elements explicitly. This is a good thing, as it makes your program more robust regarding future changes, as dealing with automatically discovered, potentially unknown program elements will destroy any help from the compiler, as it can’t tell you when there are mismatches.
You only want the third point, an abstraction over the elements of your report. You can create such an abstraction yourself, tailored to your use case, without Reflection, which will be more robust and even more efficient:
public class Report {
static final class Element {
final String header;
final Function<Report,String> getter;
final BiConsumer<Report,String> setter;
final Pattern pattern;
Element(String header,
Function<Report, String> getter, BiConsumer<Report, String> setter) {
this.header = header;
this.getter = getter;
this.setter = setter;
pattern = Pattern.compile("^\\Q"+header+"\\E(.*?)$", Pattern.MULTILINE);
}
}
static final List<Element> ELEMENTS = List.of(
new Element("Item A is: ", Report::getItemA, Report::setItemA),
new Element("Item B is: ", Report::getItemB, Report::setItemB),
new Element("Item C is: ", Report::getItemC, Report::setItemC));
private String itemA, itemB, itemC;
public Report(String itemA, String itemB, String itemC) {
this.itemA = itemA;
this.itemB = itemB;
this.itemC = itemC;
}
#Override public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Items are:");
ELEMENTS.forEach(e ->
sb.append('\n').append(e.header).append(e.getter.apply(this)));
return sb.toString();
}
public static Report createReportFromString(String reportString) {
return new Report("", "", "").setValuesFromString(reportString);
}
public Report setValuesFromString(String reportString) {
Matcher m = null;
for(Element e: ELEMENTS) {
if(m == null) m = e.pattern.matcher(reportString);
else m.usePattern(e.pattern).reset();
if(!m.find())
throw new IllegalArgumentException("missing \""+e.header+'"');
e.setter.accept(this, m.group(1));
}
return this;
}
public String getItemA() {
return itemA;
}
public void setItemA(String itemA) {
this.itemA = itemA;
}
public String getItemB() {
return itemB;
}
public void setItemB(String itemB) {
this.itemB = itemB;
}
public String getItemC() {
return itemC;
}
public void setItemC(String itemC) {
this.itemC = itemC;
}
}
This works with Java’s out-of-the-box features, not requiring another library to simplify the operation.
Note that I changed the code pattern, as createReportFromString is a misleading name for a method modifying an already existing object. I used the name for a factory method truly creating a new object and added a another method for setting the values of the object (as a direct counter-part to toString).
If you are still using Java 8, you can replace List.of(…) with Arrays.asList(…) or better Collections.unmodifiableList(Arrays.asList(…)).
You can also remove the .reset() call in the setValuesFromString method. When you remove it, the elements in the input string are required to be in the same order as the toString() method produces. This makes it a bit less flexible, but also more efficient if you expand the code to have a lot more elements.
#JimboMcHiggins assuming I can change the toString output how exactly would you tie together serialization and deserialization with some common mapping?
I would leave the toString unchanged and move the responsibility of serialization to java.io.Serializable. Correct me if this is not an acceptable approach. The mapping would be defined by the class fields of your Report pojo. This would also allow you to change your toString without breaking deserialization of existing objects.
import java.io.Serializable;
public class Report implements Serializable {
private static final long serialVersionUID = 1L;
private String itemA;
private String itemB;
private String itemC;
public Report(String itemA, String itemB, String itemC) {
this.itemA = itemA;
this.itemB = itemB;
this.itemC = itemC;
}
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Items are::");
sb.append("\nItem A is: ").append(itemA);
sb.append("\nItem B is: ").append(itemB);
sb.append("\nItem C is: ").append(itemC);
return sb.toString();
}
}
Example Usage
public class Test1 {
public static void main(String[] args) {
Report report = new Report("W", "O", "W");
System.out.println(report);
String filename = "file.ser";
// Serialization
try
{
//Saving of report in a file
FileOutputStream file = new FileOutputStream(filename);
ObjectOutputStream out = new ObjectOutputStream(file);
// Method for serialization of report
out.writeObject(report);
out.close();
file.close();
System.out.println("Report has been serialized");
}
catch(IOException ex)
{
System.out.println("IOException is caught");
}
Report report1 = null;
// Deserialization
try
{
// Reading the report from a file
FileInputStream file = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(file);
// Method for deserialization of report
report1 = (Report)in.readObject();
in.close();
file.close();
System.out.println("Report has been deserialized ");
System.out.println(report1);
}
catch(IOException ex)
{
System.out.println("IOException is caught");
}
catch(ClassNotFoundException ex)
{
System.out.println("ClassNotFoundException is caught");
}
}
}
Output
Items are::
Item A is: W
Item B is: O
Item C is: W
Report has been serialized
Report has been deserialized
Items are::
Item A is: W
Item B is: O
Item C is: W

gson problems with empty array when it expects an object

I'm trying to process a json file using gson, but I'm running into a weird error. The json I'm reading from (and can't modify) has a weird way of dealing with null fields. It puts an [] in places where there is no data, causing gson to think it's an array when it's expecting a object.
An example from the gson:
//non-empty field
"prizes":[
{
"year":"1902",
"category":"physics",
"share":"2",
"motivation":"\"in recognition of the extraordinary service they rendered by their researches into the influence of magnetism upon radiation phenomena\"",
"affiliations":[
{
"name":"Leiden University",
"city":"Leiden",
"country":"the Netherlands"
}
]
}
]
//empty field
"prizes":[
{
"year":"1903",
"category":"physics",
"share":"4",
"motivation":"\"in recognition of the extraordinary services they have rendered by their joint researches on the radiation phenomena discovered by Professor Henri Becquerel\"",
"affiliations":[
[]
]
}
]
And this is my code for processing the json:
public static void main(String[] args) throws IOException {
// Get Gson object
Gson gson = new Gson();
// read JSON file data as String
String fileData = new
String(Files.readAllBytes(Paths.get("laureates.json")));
// parse json string to object
Example laur = gson.fromJson(fileData, Example.class);
// print object data
System.out.println("\n\nLaureates Object\n\n" + laur);
}
And I have all my classes set up, i believe it will work once this issue is resolved.
The error I'm getting is "Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 3401" (column 3401 is the exact location of the first [])
The correct way to set the empty object is without the brackets. You know that. :-)
"prizes":[
{
"year":"1903",
"category":"physics",
"share":"4",
"motivation":"\"in recognition of the extraordinary services they have rendered by their joint researches on the radiation phenomena discovered by Professor Henri Becquerel\"",
"affiliations":[
]
}
]
You maybe make a workaround removing the brackets.
fileData = fileData.replaceAll("\\[]", "");
I hope this helps.
Looks like gson is expecting an object but array is returned
Try changing Example to an array as follows.
Example[] emps= gson.fromJson(yourJson, Example
[].class);
Also see related GSON throwing "Expected BEGIN_OBJECT but was BEGIN_ARRAY"?
You can always use a type adapter to adapt bad-designed but well-formed JSON documents. For example, the following type adapter fixes your case:
final class EmptyListFixTypeAdapterFactory
implements TypeAdapterFactory {
private static final TypeAdapterFactory instance = new EmptyListFixTypeAdapterFactory();
private EmptyListFixTypeAdapterFactory() {
}
static TypeAdapterFactory get() {
return instance;
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
// If it's not a list, then just let Gson pass through the rest of the type adapters chain
if ( !List.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
// Get the original List adapter - we'll use it below
#SuppressWarnings("unchecked")
final TypeAdapter<List<Object>> delegateTypeAdapter = (TypeAdapter<List<Object>>) gson.getDelegateAdapter(this, typeToken);
// Wrap it
#SuppressWarnings("unchecked")
final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) EmptyListFixTypeAdapter.get(delegateTypeAdapter);
return typeAdapter;
}
private static final class EmptyListFixTypeAdapter<E>
extends TypeAdapter<List<E>> {
// JsonParser as of Gson 2.8.2 holds no state
private static final JsonParser jsonParser = new JsonParser();
private final TypeAdapter<List<E>> delegateTypeAdapter;
private EmptyListFixTypeAdapter(final TypeAdapter<List<E>> delegateTypeAdapter) {
this.delegateTypeAdapter = delegateTypeAdapter;
}
private static <E> TypeAdapter<List<E>> get(final TypeAdapter<List<E>> delegateTypeAdapter) {
return new EmptyListFixTypeAdapter<>(delegateTypeAdapter)
.nullSafe(); // A convenient method to add null-checking automatically
}
#Override
public void write(final JsonWriter out, final List<E> value)
throws IOException {
// In case if you need to produce document with this quirks
if ( value.isEmpty() ) {
out.beginArray();
out.beginArray();
out.endArray();
out.endArray();
return;
}
delegateTypeAdapter.write(out, value);
}
#Override
public List<E> read(final JsonReader in) {
final JsonElement jsonElement = jsonParser.parse(in);
final JsonArray array = jsonElement.getAsJsonArray();
// Is it [[]]?
if ( array.size() == 1 ) {
final JsonElement element = array.get(0);
if ( element.isJsonArray() && ((JsonArray) element).size() == 0 ) {
// Yes, detected
return new ArrayList<>();
}
}
// No, proceed with the delegate type adapter
return delegateTypeAdapter.fromJsonTree(array);
}
}
}
Now suppose you have the following mappings:
final class Laureate {
final List<Prize> prizes = new ArrayList<>();
}
final class Prize {
final int year = Integer.valueOf(0);
final String category = null;
final List<Affiliation> affiliations = new ArrayList<>();
}
final class Affiliation {
final String name = null;
final String city = null;
final String country = null;
}
And then:
private static final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(EmptyListFixTypeAdapterFactory.get())
.create();
private static final Type laureatesType = new TypeToken<List<Laureate>>() {
}.getType();
public static void main(final String... args)
throws IOException {
try ( final JsonReader jsonReader = Resources.getPackageResourceJsonReader(Q49603826.class, "laureates.json") ) {
gson.<List<Laureate>>fromJson(jsonReader, laureatesType)
.stream()
.flatMap(laureate -> laureate.prizes.stream())
.peek(prize -> System.out.println("Prize: " + prize.year + " " + prize.category))
.flatMap(prize -> prize.affiliations.stream())
.peek(affiliation -> System.out.println("\tAffiliation: " + affiliation.name + " " + affiliation.city + " " + affiliation.country))
.forEach(affiliation -> {
});
}
}
Output:
Prize: 1902 physics
........Affiliation: Leiden University Leiden the Netherlands
Prize: 1903 physics

NoSuchFieldException when using getDeclaredField() and field exists

java.lang.NoSuchFieldException: c
at java.lang.Class.getDeclaredField(Unknown Source)
at ru.onlymc.OnlyMZ.CustomEntityType.getPrivateStatic(CustomEntityType.java:177)
Method:
private static Object getPrivateStatic(Class clazz, String f) throws Exception {
Field field = clazz.getDeclaredField(f);
field.setAccessible(true);
return field.get(null);
}
Calling:
private static void a(Class paramClass, String paramString, int paramInt) {
try {
((Map) getPrivateStatic(sg.class, "c")).put(paramString, paramClass);
//...
} catch (Exception exc) {
exc.printStackTrace();
}
}
sg.class (from decompile to sure about required fields really exists):
private static Map c = new HashMap();
private static Map d = new HashMap();
private static Map e = new HashMap();
private static Map f = new HashMap();
private static Map g = new HashMap();
public static HashMap a = new LinkedHashMap();
Sorry, I can't reproduce this one.
Here's the full source code that I ran:
import java.lang.reflect.*;
import java.util.*;
#SuppressWarnings("unchecked")
public class ReflectionTest {
private static Object getPrivateStatic(Class clazz, String f) throws Exception {
Field field = clazz.getDeclaredField(f);
field.setAccessible(true);
return field.get(null);
}
private static void a(Class paramClass, String paramString, int paramInt) {
try {
((Map) getPrivateStatic(sg.class, "c")).put(paramString, paramClass);
//...
} catch (Exception exc) {
exc.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
a(String.class, "test", 0);
sg.printC();
}
}
class sg {
private static Map c = new HashMap();
private static Map d = new HashMap();
private static Map e = new HashMap();
private static Map f = new HashMap();
private static Map g = new HashMap();
public static HashMap a = new LinkedHashMap();
public static void printC() {
System.out.println(c);
}
}
This was the output I got when I ran it:
{test=class java.lang.String}
Given that you haven't specified the full decompiled source of the sg class, I can only guess at a couple of things that may have happened:
There is more than one class named sg, your code is using one of them but your decompiled output comes from a different one.
The fields exist within an inner class inside sg.
EDIT: the class sg you linked to below appears to contain a static field c, and contains no inner class, so I would expect to be able to use reflection to access this field. I cannot use your sg class with the ReflectionTest class I wrote above because it depends on numerous other classes with obfuscated names such as xk.
I can only conclude that you have some confusion about exactly which class you are attempting to access the field c from. I suggest altering your getPrivateStatic method to the following, which may provide a more helpful error message, including the name of the class and all of the fields within it:
private static Object getPrivateStatic(Class clazz, String f) throws Exception {
try {
Field field = clazz.getDeclaredField(f);
field.setAccessible(true);
return field.get(null);
}
catch (NoSuchFieldException e) {
// Throw a more helpful exception.
throw new NoSuchFieldException(
"Could not find field named '" + f + "' in class '" + clazz +
"'. All fields: " + Arrays.asList(clazz.getDeclaredFields()));
}
}
You are trying to get the field of the object null. See documentation on Field.get(Object)
You need to provide an object to get its field content. Or you need to provide a class to get a static fields content.
So you should write the following:
private static Object getPrivateStatic(Class clazz, String f) throws Exception {
Field field = clazz.getDeclaredField(f);
field.setAccessible(true);
return field.get(clazz);
}
btw. It is not considered good programming style to use reflection in production code since it e.g. makes refactoring problematic.
If you still need to use reflection (or are not working on production-code), use a framework for this kind of stuff (accessing private static field). With e.g. PrivilegedAccessor this method would be a 1-liner:
PA.getValue(clazz, f);

Implementing an equivalent to String.intern() for other objects

I'm trying to implement an equivalent to String.intern(), but for other objets.
My goal is the following:
I've an object A which I will serialize and then deserialize.
If there is another reference to A somewhere, I want the result of the deserialization to be the same reference.
Here is one example of what I would expect.
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
Here is my implementation atm. (the MyObject class extends InternableObject)
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;
}
}
}
My unit test (which fails):
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
}
The failure is due to the fact that, when deserializing, the constructor of InternableObject is called, and thus objectId will be 2 (even if the serialized data contains "1")
Any idea about how to solve this particular problem or, another approach to handle the high level problem ?
Thanks guys
Do not use the constructor to create instances. Use a factory method that checks if an instance already exists first, only create an instance if there isn't already a matching one.
To get serialization to cooperate, your class will need to make use of readResolve() / writeReplace(). http://docs.oracle.com/javase/7/docs/platform/serialization/spec/serial-arch.html#4539
The way you implemented your constructor, you're leaking a reference during construction, which can lead to very hard to nail down problems. Also, your instance map isn't protected by any locks, so its not thread save.
Typically intern() forms an aspect, and maybe should not be realized as a base class, maybe too restricting its usage in a more complex constellation.
There are two aspects:
1. Sharing the "same" object.
Internalizing an object only gives a profit, when several objects can be "internalized" to the same object. So I think, that InternalableObjecte. with a new sequential number is not really adequate. More important is that the class defines a fitting equals and hashCode.
Then you can do an identity 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 could be used for any class, but above we restrict it to Internalizable things.
2. Replacing a dynamically created non-shared object with it's .intern().
Which in Java 8 could be realised with a defualt method in an interface:
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);
The Class<T> parameter needed because of type erasure. Concurrency disregarded here.
Prior to Java 8, just use an empty interface Internalizable as _marker: interface, and use a static InternMap.

Categories

Resources