Java创建对象的几种方式
前言:原先一直没怎么关注Java创建对象的方式,因为基本上只知道用new😂,直到58面试官问我Java有哪些方式可以创建对象。所以稍微整理了一下几种创建对象的方式。
1.使用new关键字创建对象
这是Java中最常用,且简单的方式创建对象。其实这种方式是通过调用构造函数(有参、无参……)创建对象的。如:String name = new String("hello"); 那执行这条语句时JVM做了什么?
- 首先在方法区的常量池中查看是否有new 后面参数(也就是类名)的符号引用,并检查是否有类的加载信息也就是是否被加载解析和初始化过。如果已经加载过了就不在加载,否则执行类的加载全过程。
- 加载完类后,大致做了如下三件事:
a、给实例分配内存:此内存中存放对象自己的实例变量和从父类继承过来的实例变量(即使这些从超类继承过来的实例变量有可能被隐藏也会被分配空间),同时这些实例变量被赋予默认值(零值); b、调用构造函数,初始化成员字段:在Java对象初始化过程中,主要涉及三种执行对象初始化的结构,分别是实例变量初始化、实例代码块初始化以及构造函数初始化; c、user对象指向分配的内存空间: 注意:new操作不是原子操作,b和c的顺序可能会调换。
2.使用clone方法创建对象
顾名思义,clone就是克隆,也就是复制;使用某个对象的clone()方法时(前提是此对象的对应的类中已经实现clone方法),JVM根据被拷贝的对象分配内存、创建新的对象,然后会把被clone的对象的值全都拷贝进去;clone()方法是属于Object类的,clone是在堆内存中用二进制的方式进行拷贝,重新分配给对象一块内存;Object类的clone方法是一个native方法,它的注释中写着:Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object. The general intent is that, for any object {@code x}, the expression:
- 要想让一个对象支持clone,必须让这个对象对应的类实现Cloneable接口(标识接口),同时此类中也要重写clone方法;其实Cloneable接口相当于一个合法的证明,表明此类可以合法的进行clone;Cloneable接口的注释中写着:A class implements the
Cloneable
interface to indicate to the {@link java.lang.Object#clone()} method that it is legal for that method to make a field-for-field copy of instances of that class. - 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@4554617c createobject.Animal@74a14482 可见,两个对象的属性值一样,而地址不一样,所以clone dog对象成功;
3.使用反射创建对象
3.1Java反射机制
首先解释一下什么是Java的反射机制;Java 反射机制在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态的获取信息以及动态调用对象的方法的功能称为java 的反射机制。 反射机制很重要的一点就是“运行时”,其使得我们可以在程序运行时加载、探索以及使用编译期间完全未知的 .class 文件。换句话说,Java 程序可以加载一个运行时才得知名称的 .class 文件,然后获悉其完整构造,并生成其对象实体、或对其 fields(变量)设值、或调用其 methods(方法)。
3.2使用反射创建对象
主要包括两个步骤:
- 获取类的Class对象实例,获取方式主要有:
- Class.forName("类全路径");
- 类名.class; 如:Animal.class;
- 对象名.getClass();
- 通过反射创建类对象的实例对象;获取Class对象实例后,可以通过Java反射机制创建类对象实例对象;主要有两种方式:
- Class.newInstance():调用无参的构造方法,必需确保类中有无参数的可见的构造函数,否则将会抛出异常;
- 调用类对象的构造方法:
- 强制转换成用户所需类型;
举个简单的例子:
public class Apple {
private int price;
protected String color;
public String name;
public Apple(){}
public Apple(String name, int price, String color){
this.name = name;
this.price = price;
this.color = color;
}
public int getPrice() {
return price;
}
public void setPrice(int price){
this.price = price;
}
public String getColor(){
return color;
}
public void setColor(String color){
this.color = color;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public String printMsg(){
return "name:" + name + ", price:" + price + ", color:" + color;
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//正常new对象
Apple apple1 = new Apple("Apple1", 6666, "red");
System.out.println("Apple1:" + apple1);
System.out.println("Apple1: " + apple1.printMsg());
//使用反射创建对象
//获取类的Class对象实例
Class cls = Class.forName("reflect.Apple");
//根据Class对象实例获取Constructor对象
Constructor appleConstructor = cls.getConstructor();
//根据Constructor对象的newInstance方法获取反射类对象
Object appleObject = appleConstructor.newInstance();
//强制类型转换
Apple apple2 = (Apple)appleObject;
System.out.println("Apple2:" + apple2);
// System.out.println("Apple2:" + appleObject);
System.out.println("Apple2: " + apple2.printMsg());
}
}
运行结果为: Apple1:reflect.Apple@4554617c Apple1: name:Apple1, price:6666, color:red Apple2:reflect.Apple@74a14482 Apple2: name:null, price:0, color:null apple1和apple2保存的地址不一样,成功反射一个Apple对象,反射时使用的是无参构造函数,所以apple2引用指向的对象属性值为默认值;关于反射详细的内容后续单独写篇文章介绍;
4.使用反序列化创建对象
-
序列化:
说起反序列化,首先得介绍序列化;什么是序列化呢?我们把变量从内存中变成可存储或传输的过程称之为序列化(廖雪峰老师官网给出的解释),其实就是把对象写入IO流中;Java中要序列化的类必须实现Serializable接口;
-
序列化场景:
- 所有可在网络上传输的对象都必须是可序列化的;如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自己生成的对象,不通过构造方法生成;
转载自:https://juejin.cn/post/6844904054775087117