1、使用new关键字
这是我们最常见的也是最简单的创建对象的方式,通过这种方式我们还可以调用任意的构造函数(无参的和有参的)。
User user = new User();
在执行上述语句的时候JVM做了什么?
- 首先在方法区的常量池中查看是否有new 后面参数(也就是类名)的符号引用,并检查是否有类的加载信息也就是是否被加载解析和初始化过。如果已经加载过了就不在加载,否则执行类的加载全过程。
- 加载完类后,大致做了如下三件事:
a、给实例分配内存:此内存中存放对象自己的实例变量和从父类继承过来的实例变量(即使这些从超类继承过来的实例变量有可能被隐藏也会被分配空间),同时这些实例变量被赋予默认值(零值);
b、调用构造函数,初始化成员字段:在Java对象初始化过程中,主要涉及三种执行对象初始化的结构,分别是实例变量初始化、实例代码块初始化以及构造函数初始化;
c、user对象指向分配的内存空间: 注意:new操作不是原子操作,b和c的顺序可能会调换。
Java中类的加载及初始化实例化详细过程可参见这位大佬的博客:类的初始化与实例化、加载时机与加载过程
2、使用反射机制
Java 反射机制在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态的获取信息以及动态调用对象的方法的功能称为java 的反射机制。
运用反射手段,调用Java.lang.Class
或者java.lang.reflect.Constructor
类的newInstance()
实例方法。
1)使用Class类的newInstance方法
可以使用Class
类的newInstance
方法创建对象。这个newInstance
方法调用无参的构造函数创建对象。
//创建方法1
User user = (User)Class.forName("根路径.User").newInstance();
//创建方法2(用这个最好)
User user = User.class.newInstance();
2)使用Constructor类的newInstance方法
和Class
类的newInstance
方法很像, java.lang.reflect.Constructor
类里也有一个newInstance
方法可以创建对象。我们可以通过这个newInstance
方法调用有参数的和私有的构造函数。
Constructor<User> constructor = User.class.getConstructor();
User user = constructor.newInstance();
这两种newInstance
方法就是大家所说的反射。事实上Class的newInstance
方法内部调用Constructor
的newInstance
方法。
3、使用clone方法
无论何时我们调用一个对象的clone
方法,jvm就会创建一个新的对象,将前面对象的内容全部拷贝进去。用clone
方法创建对象并不会调用任何构造函数。clone()
方法是属于Object
类的,clone
是在堆内存中用二进制的方式进行拷贝,重新分配给对象一块内存;Object
类的clone
方法是一个native
方法。
要使用clone
方法,我们需要先实现Cloneable
接口并实现其定义的clone
方法。
Employee emp4 = (Employee) emp3.clone();
Object
类的clone()
方法是线程不安全的;
clone()
有浅拷贝和深拷贝两种模式;
- 浅拷贝是拷贝被拷贝对象的值(表层),若被拷贝对象的属性有引用类型的,则只拷贝引用的地址;
- 深拷贝是拷贝被拷贝对象的所有值(深层),若有被拷贝对象有引用类型的属性,则也要拷贝其引用类型的属性所对应的对象;深拷贝还有彻底深拷贝和未彻底深拷贝等情况,其实彻底深拷贝是很难的;
clone详情可查阅大佬博客:详解Java中的clone方法 举个简单的clone对象的例子:
public class Animal implements Cloneable{
private String name = null;
private int age = 0;
public Animal(){ }
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public int getAge(){
return age;
}
public void setAge(int age){
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Animal animal = (Animal) super.clone();
animal.name = new String(name);
return animal;
}
public static void main(String[] args) throws CloneNotSupportedException {
Animal dog = new Animal("Dog", 1);
Animal smallDog = (Animal)dog.clone();
System.out.println("dog:" + dog.getName() + ", " + dog.getAge());
System.out.println("smallDog:" + smallDog.getName() + ", " + smallDog.getAge());
System.out.println(dog);
System.out.println(smallDog);
}
}
程序运行结果:
dog:Dog, 1
smallDog:Dog, 1
createobject.Animal@63c12fb0
createobject.Animal@b1a58a3
可见,两个对象的属性值一样,而地址不一样,所以clone dog对象成功;
4、使用反序列化
把变量从内存中变成可存储或传输的过程称之为序列化。
序列化场景:
- 所有可在网络上传输的对象都必须是可序列化的;如RMI(remote method invoke,即远程方法调用),传入的参数或返回的对象都是可序列化的,否则会出错;
- 所有需要保存到磁盘的java对象都必须是可序列化的;通常建议:程序创建的每个
JavaBean类都实现Serializeable
接口;
序列化与反序列化实现方式:序列化的对象所对应的类必须实现Serializable
接口或Externalizable
接口;
Serializable
接口序列化举例:Serializable
接口是一个标记接口,不用实现任何方法。一旦实现了此接口,该类的对象就是可序列化的;当然还有Externalizable
接口序列化方式,详细的情况另行介绍;- 反序列化:从IO流中恢复对象;
import java.io.Serializable;
/**
* 需序列化的对象对应的类
*/
public class Person implements Serializable {
private String name;
private int age;
//没有无参构造方法
public Person(String name, int age){
// System.out.println("反序列化,你调用我了么?");
this.name = name;
this.age = age;
}
@Override
public String toString(){
return "Person{ " + "name = '" + name + '\'' + ", age = " + age + '}';
}
}
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
/**
* 序列化步骤:
* ①创建一个ObjectOutputStream输出流
* ②调用ObjectOutputStream对象的writeObject输出可序列化对象
*/
public class WriteObject {
public static void main(String[] args){
try{
//创建ObjectOutputStream输出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("person.txt"));
//将对象序列化到文件s
Person person = new Person("张三", 25);
objectOutputStream.writeObject(person);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("success~");
}
}
import java.io.FileInputStream;
import java.io.ObjectInputStream;
/**
* 反序列化步骤:
* ①创建一个ObjectInputStream输入流
* ②调用ObjectInputStream对象的readObject()得到序列化的对象
*/
public class ReadObject {
public static void main(String[] args){
try{
//创建一个ObjectInputStream输入流
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("person.txt"));
Person person = (Person)objectInputStream.readObject();
System.out.println(person);
}catch (Exception e){
e.printStackTrace();
}
}
}
反序列化时可以在对应类的构造方法中添加输出语句测试反序列化时有没有调用构造方法;结果表明,反序列化并不会调用构造方法;反序列的对象是由JVM自己生成的对象,不通过构造方法生成;