How to deepclone Map with List as value - java

Is this the best way to deepclone this data structure example: Map<String, List<Object>>?
Map<String, List<Object>> mapB = new LinkedHashMap<String, List<Object>>();
for(String key: mapA.keySet()){
mapB.put(key, new ArrayList<Object>());
mapB.get(key).addAll(mapA.get(key));
}
Thanks for your time.

Is this the best way to deepclone this data structure
More or less, yes. You can make it a bit shorter using the ArrayList constructor that takes a source Collection as argument, and a bit more efficient (but more wordy) by iterating key-value pairs instead of looking up each key again, but it amounts to the same thing.
Map<String, List<Object>> mapB = new LinkedHashMap<>();
for (Map.Entry<String, List<Object>> entry : mapA.entrySet()) {
mapB.put(entry.getKey(), new ArrayList<>(entry.getValue()));
}

It's fine if you don't want deep copy of contaning objects.

An alternative method of deep cloning is to use serialization. The following method shows how this is done:
public Object deepClone(Object obj) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (IOException e) {
return null;
} catch (ClassNotFoundException e) {
return null;
}
}
The apache commons lang SerializationUtils offers a generic method to do this.
Both ways assume that all objects in your object graph implement serializable.

Related

Copy one Java object to another

What's the best way to copy one Java object of a class to another object of the same class? I tried BeanUtil.copyProperties but it didn't work for some reason. The class is a complex class. (class contains another class objects etc)
My aim is to populate values in order through hibernate function
Public Response getOrder(Order order, Ticket ticket) {
order = OrderManager.getOrderByTicket(ticket); //Hibernate function This doesn't work, order object gets a new reference
}
Tried doing this
Public Response getOrder(Order order, Ticket ticket) {
Order temp = OrderManager.getOrderbByTicket(ticket);
//Now I want to copy temp to order
}
To do a deep copy using Serialize / DeSerialize, you can use the code like below,
public Object deepCopy(Object input) {
Object output = null;
try {
// Writes the object
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(input);
// Reads the object
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
output = objectInputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return output;
}
If all the fields are serializable then you can use ObjectOutputStream and ObjectInputStream.
If You need special handling during the serialization and deserialization process then implement special methods writeObject() and readObject().
Please have a look at IO: Custom Reading and Writing with Serializable .
Sample code:
class MyClass implements Serializable {
private static final long serialVersionUID = 1L;
String str;
List<Integer> list = new ArrayList<Integer>();
public MyClass(String str) {
this.str = str;
}
}
MyClass obj1 = new MyClass("abc");
obj1.list.add(1);
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(buffer);
oos.writeObject(obj1);
oos.close();
byte[] rawData = buffer.toByteArray();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(rawData));
MyClass obj2 = (MyClass) ois.readObject();
System.out.println(obj2.str);
System.out.println(obj2.list.get(0));
I suppose you could use reflection if it was REALLY important or time consuming, but as I see it, there are two main choices
One
If you have access to the class, just implement Clonable and have the clone method produce a deep copy of the object and all its subobjects.
Two
Code it by hand. It may be time consuming and boring, but it works in all cases.
I believe you are talking about a 'deep' copy.
A similar question and various solutions are detailed here:
How do you make a deep copy of an object in Java?
The easiest way seems to be serialising the object and then deserialising it.
Better do it like this:
Public Response getOrder(Request request) {
Order temp = OrderManager.getOrderbByTicket(request.getTicket());
request.setOrder(temp);
//process the response
}
This will solve the problem of getting back the Order to the caller of the function. If you want that the caller gets a deep copy than serialize and deserialize it before seting it to the request

Storing a Map between Runtimes

I have been reading some posts on how to store data between runtimes, between HBase, Serialization, and other stuff, but is there a way to store a Map(Object, Set of difObject) easily? I have been watching videos and reading posts and I just havent been able to wrap my brain around it, also wherever I store the data cannot be human readable as it has personal information on it.
Use java.io.ObjectOutputStream and java.io.ObjectInputStream to persist Java objects (in your case: write/read the Map). Make sure that all objects you're persisting implement Serializable.
Example: writing data(marshalling)
Map<String, Set<Integer>> map = new HashMap<String, Set<Integer>>();
map.put("Foo", new HashSet<Integer>(Arrays.asList(1, 2, 3)));
map.put("Bla", new HashSet<Integer>(Arrays.asList(4, 5, 6)));
File file = new File("data.bin");
ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
try {
out.writeObject(map);
out.flush();
} finally {
out.close();
}
Reading the stored data (unmarshalling)
File file = new File("data.bin");
if (file.exists()) {
ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)));
try {
Map<String, Set<Integer>> read = (Map<String, Set<Integer>>) in.readObject();
for (String key : read.keySet()) {
System.out.print(key + ": ");
Set<Integer> values = read.get(key);
for (Integer value : values) {
System.out.print(value + " ");
}
System.out.println();
}
} finally {
in.close();
}
}

Reliably convert any object to String and then back again

Is there a reliable way to convert any object to a String and then back again to the same Object? I've seen some examples of people converting them using toString() and then passing that value into a constructor to reconstruct the object again but not all objects have a constructor like this so that method wont work for all cases. What way will?
Yes, it is called serialization!
String serializedObject = "";
// serialize the object
try {
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream so = new ObjectOutputStream(bo);
so.writeObject(myObject);
so.flush();
serializedObject = bo.toString();
} catch (Exception e) {
System.out.println(e);
}
// deserialize the object
try {
byte b[] = serializedObject.getBytes();
ByteArrayInputStream bi = new ByteArrayInputStream(b);
ObjectInputStream si = new ObjectInputStream(bi);
MyObject obj = (MyObject) si.readObject();
} catch (Exception e) {
System.out.println(e);
}
This is the code:
try {
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream so = new ObjectOutputStream(bo);
so.writeObject(stringList);
so.flush();
redisString = new String(Base64.encode(bo.toByteArray()));
}
catch (Exception e) {
e.printStackTrace();
}
try {
byte b[] = Base64.decode(redisString.getBytes());
ByteArrayInputStream bi = new ByteArrayInputStream(b);
ObjectInputStream si = new ObjectInputStream(bi);
List<String> stringList2 = (List<String>)si.readObject();
System.out.println(stringList2.get(1));
}
catch (Exception e) {
e.printStackTrace();
}
Serialize to byte array, convert to Base64. Then decode Base64 back to byte array and deserialize.
None will work in all cases. An object may, e.g., contain references to other JVMs handling their state, and this state may be not available for you to restore.
Additional problems you're going to meet will include open streams, listening sockets, and almost anything else from the outer world.
There's no need to repeat that most at least two of Java core engineers say that serialization was one of the greatest mistakes a single worst feature in Java, that is, after finalization. (I do love serialization nevertheless, it's handy. But it won't always work.)
One way is to use JSON. For implementation specific in Java, the answer might be given in this post:
java code corresponding to Newtonsoft.Json.JsonConvert.SerializeObject(Object source,Newtonsoft.Json.JsonSerializerSettings()) in .net?
Using JSON is reliable enough that it's used for web application development (Ajax).
Yes, it is Serialization You can use, ObjectInputStream.readObject and ObjectOutputStream.writeObject. Please see below example:
MyClass myClass = new MyClass();
FileOutputStream fileStream = new FileOutputStream("myObjectFile.txt");
ObjectOutputStream os = new ObjectOutputStream(fileStream);
os.writeObject(os);
os.close();
FileInputStream fileInStream = new FileInputStream("myObjectFile.txt");
ObjectInputStream ois = new ObjectInputStream(fileInStream);
MyClass myClass2 = ois.readObject();
ois.close();
You can use SerializationUtils from org.apache.commons.
It provides the methods serialize and deserialize

HashMap serialization and deserialization changes

We are working with an in memory data grid (IMDG) and we have a migration tool. In order to verify that all the objects are migrated successfully, we calculate the chucksum of the objects from its serialized version.
We are seeing some problems with HashMap, where we serialize it, but when we deserialize it the checksum changes. Here is a simple test case:
#Test
public void testMapSerialization() throws IOException, ClassNotFoundException {
TestClass tc1 = new TestClass();
tc1.init();
String checksum1 = SpaceObjectUtils.calculateChecksum(tc1);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput out = null;
byte[] objBytes = null;
out = new ObjectOutputStream(bos);
out.writeObject(tc1);
objBytes = bos.toByteArray();
out.close();
ByteArrayInputStream bis = new ByteArrayInputStream(objBytes);
ObjectInputStream in = new ObjectInputStream(bis);
TestClass tc2 = (TestClass) in.readObject();
String checksum2 = SpaceObjectUtils.calculateChecksum(tc2);
assertEquals(checksum1, checksum2);
}
The TestClass looks like this:
class TestClass implements Serializable {
private static final long serialVersionUID = 5528034467300853270L;
private Map<String, Object> map;
public TestClass() {
}
public Map<String, Object> getMap() {
return map;
}
public void setMap(Map<String, Object> map) {
this.map = map;
}
public void init() {
map = new HashMap<String, Object>();
map.put("name", Integer.valueOf(4));
map.put("type", Integer.valueOf(4));
map.put("emails", new BigDecimal("43.3"));
map.put("theme", "sdfsd");
map.put("notes", Integer.valueOf(4));
map.put("addresses", Integer.valueOf(4));
map.put("additionalInformation", new BigDecimal("43.3"));
map.put("accessKey", "sdfsd");
map.put("accountId", Integer.valueOf(4));
map.put("password", Integer.valueOf(4));
map.put("domain", new BigDecimal("43.3"));
}
}
And this is the method to calculate the checksum:
public static String calculateChecksum(Serializable obj) {
if (obj == null) {
throw new IllegalArgumentException("The object cannot be null");
}
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance("MD5");
} catch (java.security.NoSuchAlgorithmException nsae) {
throw new IllegalStateException("Algorithm MD5 is not present", nsae);
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput out = null;
byte[] objBytes = null;
try {
out = new ObjectOutputStream(bos);
out.writeObject(obj);
objBytes = bos.toByteArray();
out.close();
} catch (IOException e) {
throw new IllegalStateException(
"There was a problem trying to get the byte stream of this object: " + obj.toString());
}
digest.update(objBytes);
byte[] hash = digest.digest();
StringBuilder hexString = new StringBuilder();
for (int i = 0; i < hash.length; i++) {
String hex = Integer.toHexString(0xFF & hash[i]);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
If you print the maps of tc1 and tc2, you can see that the elements are not in the same place:
{accessKey=sdfsd, accountId=4, theme=sdfsd, name=4, domain=43.3, additionalInformation=43.3, emails=43.3, addresses=4, notes=4, type=4, password=4}
{accessKey=sdfsd, accountId=4, name=4, theme=sdfsd, domain=43.3, emails=43.3, additionalInformation=43.3, type=4, notes=4, addresses=4, password=4}
I would like to be able to serialize the HashMap and get the same checksum when I deserialize it. Do you know if there is a solution or if I'm doing something wrong?
Thanks!
Diego
You are doing nothing wrong, it just can't be done with a HashMap. In a HashMap, order is not guaranteed. Use a TreeMap instead.
Hash table based implementation of the
Map interface. This implementation
provides all of the optional map
operations, and permits null values
and the null key. (The HashMap class
is roughly equivalent to Hashtable,
except that it is unsynchronized and
permits nulls.) This class makes no
guarantees as to the order of the map;
in particular, it does not guarantee
that the order will remain constant
over time.
Source: Hashmap
Your check sum cannot depend on the order of entries as HashMap is not ordered. An alternative to using TreeMap is LinkedHashMap (which retains an order), but the real solution is to use a hashCode which doesn't depending on the order of the entries.
Use LinkedHashMap which is order one.
TreeMap is not ordered. TreeMap is sorted map.
TreeMap sorts elements irrespective of insertion order.

How to encode a Map<String,String> as Base64 string?

i like to encode a java map of strings as a single base 64 encoded string. The encoded string will be transmitted to a remote endpoint and maybe manipulated by a not nice person. So the worst thing that should happen are invaild key,value-tuples, but should not bring any other security risks aside.
Example:
Map<String,String> map = ...
String encoded = Base64.encode(map);
// somewhere else
Map<String,String> map = Base64.decode(encoded);
Yes, must be Base64. Not like that or that or any other of these. Is there an existing lightweight solution (Single Utils-Class prefered) out there? Or do i have to create my own?
Anything better than this?
// marshalling
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(map);
oos.close();
String encoded = new String(Base64.encodeBase64(baos.toByteArray()));
// unmarshalling
byte[] decoded = Base64.decodeBase64(encoded.getBytes());
ByteArrayInputStream bais = new ByteArrayInputStream(decoded);
ObjectInputStream ois = new ObjectInputStream(bais);
map = (Map<String,String>) ois.readObject();
ois.close();
Thanks,
my primary requirements are: encoded string should be as short as possible and contain only latin characters or characters from the base64 alphabet (not my call). there are no other reqs.
Use Google Gson to convert Map to JSON. Use GZIPOutputStream to compress the JSON string. Use Apache Commons Codec Base64 or Base64OutputStream to encode the compressed bytes to a Base64 string.
Kickoff example:
public static void main(String[] args) throws IOException {
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");
String serialized = serialize(map);
Map<String, String> deserialized = deserialize(serialized, new TypeToken<Map<String, String>>() {}.getType());
System.out.println(deserialized);
}
public static String serialize(Object object) throws IOException {
ByteArrayOutputStream byteaOut = new ByteArrayOutputStream();
GZIPOutputStream gzipOut = null;
try {
gzipOut = new GZIPOutputStream(new Base64OutputStream(byteaOut));
gzipOut.write(new Gson().toJson(object).getBytes("UTF-8"));
} finally {
if (gzipOut != null) try { gzipOut.close(); } catch (IOException logOrIgnore) {}
}
return new String(byteaOut.toByteArray());
}
public static <T> T deserialize(String string, Type type) throws IOException {
ByteArrayOutputStream byteaOut = new ByteArrayOutputStream();
GZIPInputStream gzipIn = null;
try {
gzipIn = new GZIPInputStream(new Base64InputStream(new ByteArrayInputStream(string.getBytes("UTF-8"))));
for (int data; (data = gzipIn.read()) > -1;) {
byteaOut.write(data);
}
} finally {
if (gzipIn != null) try { gzipIn.close(); } catch (IOException logOrIgnore) {}
}
return new Gson().fromJson(new String(byteaOut.toByteArray()), type);
}
Another possible way would be using JSON which is a very ligthweight lib.
The the encoding then would look like this:
JSONObject jso = new JSONObject( map );
String encoded = new String(Base64.encodeBase64( jso.toString( 4 ).toByteArray()));
Your solution works. The only other approach would be to serialize the map yourself (iterate over the keys and values). That would mean you'd have to make sure you handle all the cases correctly (for example, if you transmit the values as key=value, you must find a way to allow = in the key/value and you must separate the pairs somehow which means you must also allow this separation character in the name, etc).
All in all, it's hard to get right, easy to get wrong and would take a whole lot more code and headache. Plus don't forget that you'd have to write a lot of error handling code in the parser (receiver side).

Categories