data type
In Java, data types can be divided into basic types and reference types. When the basic type is a global variable, it is stored in the stack, and when it is a local variable, it is stored in heap memory. Whether it is stored in the stack or heap, it is a specific value. For different reference types, it records an address, and then points to a specific memory area by reference.
For example, in m this method, the basic type a and reference type User used are stored in memory as shown in the following figure
public void m(){ int a = 128; User user = new User(); }
Assignment statement
In an application, object copying can generally be realized through assignment statements, such as the following
int a = 128; int b = a;
It can be considered that b copies a
For basic types, this is no problem, but for reference types, such as the following
User user1 = new User(); User user2 = user1;
Whether user1 or user2, as long as one attribute changes, both objects will change, which is usually not the result we want to see.
The assignment of the basic type is actually two objects in the stack
In fact, the assignment of reference type is only processed on the reference, and there is only one object in the heap
Deep and shallow copies
Conceptual understanding
- Shallow copy: if it is a basic type, copy the value directly and assign it to a new object. If it is a reference type, only the reference is copied, not the data itself.
- Deep copy: if it is a basic type, like shallow copy, if it is a reference type, it will not only copy the reference, but also copy the data itself.
Deep copy
Immutable object
There is a special class of objects. Although they are reference type objects, they can still ensure that after shallow copy, you get the object you want, that is, immutable objects.
For example, as shown below, str1 and str2 objects will not affect each other.
String str1 = "a"; String str2 = str1;
Or such a class
final class User { final String name; final String age; public User(String name, String age) { this.name = name; this.age = age; } }
For an immutable class, even if it is directly assigned, you can't modify it anyway, so it's safe.
User u1 = new User("Xiao Ming", "18"); User u2 = u1;
Clonable interface
In fact, the JDK also provides us with methods for the Object clone, that is, to implement the Cloneable interface. As long as the class that implements this interface indicates that the Object has the ability to allow clone. The Cloneable interface itself does not contain any methods, but only determines the behavior of the protected clone method implementation in the Object:
If a class implements the clonable interface, the clone method of the Object returns a copy of the Object, otherwise it throws a Java Lang. clonenotsupportedexception exception.
@Data @AllArgsConstructor @NoArgsConstructor class User implements Cloneable { private String name; private int age; /** * If the clonable interface is not implemented, call super The clone () method throws an exception * @return * @throws CloneNotSupportedException */ @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
If you think a class implements the clonable interface and calls super The clone () method can get the object you want, and you are wrong because super The clone () method is the same as the shallow copy. If the cloned object contains variable reference types, there is actually a problem.
When only basic and immutable types are included
public static void main(String[] args) throws CloneNotSupportedException { User u1 = new User("Xiao Ming", 18); User u2 = (User) u1.clone(); u2.setName("Xiao Wang"); u2.setAge(20); u1.setName("Xiao Hong"); u1.setAge(19); log.info("u1:{}", u1); log.info("u2:{}", u2); }
Because the User object has only the base type int and the immutable type String, Spuer. Is called directly There is no problem with the clone () method
u1:User(name=Xiao Hong, age=19) u2:User(name=Xiao Wang, age=20)
With reference type
Now we add a Role attribute to the User object
@Data @AllArgsConstructor @NoArgsConstructor class User implements Cloneable { private String name; private int age; private Role[] roles; /** * If the clonable interface is not implemented, call super The clone () method throws an exception * * @return * @throws CloneNotSupportedException */ @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } @Data @AllArgsConstructor @NoArgsConstructor class Role{ private String roleName; }
public static void main(String[] args) throws CloneNotSupportedException { User u1 = new User(); u1.setName("Xiao Ming"); u1.setAge(18); Role[] roles = new Role[2]; roles[0] = new Role("A system administrator"); roles[1] = new Role("B System general staff"); u1.setRoles(roles); log.info("u1:{}", u1); User u2 = (User) u1.clone(); u2.setName("Xiao Wang"); u2.setAge(20); Role[] roles2 = u2.getRoles(); roles2[0] = new Role("A System general staff"); roles2[1] = new Role("B system administrator"); u2.setRoles(roles2); log.info("u1:{}", u1); }
The problem occurred. I only modified the cloned u2 object, but the u1 object did not change.
u1:User(name=Xiao Ming, age=18, roles=[Role(roleName=A system administrator), Role(roleName=B System general staff)]) u1:User(name=Xiao Ming, age=18, roles=[Role(roleName=A System general staff), Role(roleName=B system administrator)])
Solve the problem of reference type
For the typical shallow copy problem, it is also very simple to solve this problem. Change it to the following
@Data @AllArgsConstructor @NoArgsConstructor class User implements Cloneable { private String name; private int age; private Role[] roles; /** * If the clonable interface is not implemented, call super The clone () method throws an exception * * @return * @throws CloneNotSupportedException */ @Override protected Object clone() throws CloneNotSupportedException { User user = (User) super.clone(); user.roles = roles.clone(); return user; } }
Execute again at this time, and the result is correct.
u1:User(name=Xiao Ming, age=18, roles=[Role(roleName=A system administrator), Role(roleName=B System general staff)]) u1:User(name=Xiao Ming, age=18, roles=[Role(roleName=A system administrator), Role(roleName=B System general staff)])
Problem extension
In fact, in some cases, the above processing method still has problems, such as the following
Now the object is HashMap
@Data @AllArgsConstructor @NoArgsConstructor class User implements Cloneable { private HashMap<String, Role> roleMap; @Override protected Object clone() throws CloneNotSupportedException { User user = (User) super.clone(); user.roleMap = (HashMap<String, Role>) roleMap.clone(); return user; } }
public static void main(String[] args) throws CloneNotSupportedException { User u1 = new User(); HashMap<String, Role> roleMap1 = new HashMap<>(); roleMap1.put("A", new Role("system administrator")); u1.setRoleMap(roleMap1); log.info("u1:{}", u1); User u2 = (User) u1.clone(); HashMap<String, Role> roleMap2 = u2.getRoleMap(); Role role = roleMap2.get("A"); role.setRoleName("Ordinary staff"); roleMap2.put("A", role); u2.setRoleMap(roleMap2); log.info("u1:{}", u1); }
u1:User(roleMap={A=Role(roleName=system administrator)}) u1:User(roleMap={A=Role(roleName=Ordinary staff)})
Why not? Because the cloning method provided by HashMap itself is a shallow copy...
/** * Returns a shallow copy of this <tt>HashMap</tt> instance: the keys and * values themselves are not cloned. * * @return a shallow copy of this map */ @SuppressWarnings("unchecked") @Override public Object clone() { HashMap<K,V> result; try { result = (HashMap<K,V>)super.clone(); } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } result.reinitialize(); result.putMapEntries(this, false); return result; }
Final solution
Byte stream
You can easily find the solution on Baidu. The most common is byte stream.
Like this.
@Data @AllArgsConstructor @NoArgsConstructor class User implements Serializable { private HashMap<String, Role> roleMap; public static <T extends Serializable> T clone(T obj) { T cloneObj = null; try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream); outputStream.writeObject(obj); outputStream.close(); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream); cloneObj = (T) inputStream.readObject(); inputStream.close(); } catch (Exception e) { e.printStackTrace(); } return cloneObj; } } @Data @AllArgsConstructor @NoArgsConstructor class Role implements Serializable { private String roleName; }
There is no problem calling again at this time.
public static void main(String[] args) { User u1 = new User(); HashMap<String, Role> roleMap1 = new HashMap<>(); roleMap1.put("A", new Role("system administrator")); u1.setRoleMap(roleMap1); log.info("u1:{}", u1); User u2 = User.clone(u1); HashMap<String, Role> roleMap2 = u2.getRoleMap(); Role role = roleMap2.get("A"); role.setRoleName("Ordinary staff"); roleMap2.put("A", role); u2.setRoleMap(roleMap2); log.info("u1:{}", u1); }
u1:User(roleMap={A=Role(roleName=system administrator)}) u1:User(roleMap={A=Role(roleName=system administrator)})
Re implementation
In fact, you can implement a set of clone methods, define it as a copy factory, or use some implemented third-party tool classes.
Like org springframework. BeanUtils class provided under beans package
public static void main(String[] args) { User u1 = new User(); HashMap<String, Role> roleMap1 = new HashMap<>(); roleMap1.put("A", new Role("system administrator")); u1.setRoleMap(roleMap1); log.info("u1:{}", u1); User u2 = new User(); // Using the copyProperties method BeanUtils.copyProperties(u1,u2); HashMap<String, Role> roleMap2 = u2.getRoleMap(); Role role = roleMap2.get("A"); role.setRoleName("Ordinary staff"); roleMap2.put("A", role); u2.setRoleMap(roleMap2); log.info("u1:{}", u1); }
Hutool Toolkit
// The name is almost the same as spring BeanUtil.copyProperties(u1, u2);
summary
In fact, you should have found that although the Object class provides us with the clone method, sometimes it can not be used well. It may need to be cloned one by one at multiple levels. Even if you forget to modify the clone method when adding a reference Object, it will bring some strange problems. Maybe we should never use it, but replace it in other ways.
From Alibaba Java development manual