不new,还不能创建对象了?

一、概述

Java中,对象是程序的核心,几乎所有的功能和数据都是通过对象来表示和操作的。

Java对象的创建对于实现面向对象编程的特性、封装数据和行为、实现类的实例化以及内存管理等方面都具有重要性。它是构建Java程序的基础,对于编写可维护、可重用和高效的代码至关重要。

graph LR A(Java对象创建的重要性) B(封装数据和行为) C(实现面向对象编程的特性) D(实现类的实例化) E(内存管理) F(支持动态性和灵活性) A ---> B A ---> C A ---> D A ---> E A ---> F style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px style E fill:#98FB98,stroke:#98FB98,stroke-width:2px style F fill:#B2FFFF,stroke:#B2FFFF,stroke-width:2px

那么,Java都可以怎么创建对象呢?

二、创建对象的过程

在Java中,创建对象的过程如下:

sequenceDiagram participant JVM as Java虚拟机 participant ClassLoader as 类加载器 participant Heap as 堆内存 participant Object as 对象实例 participant Constructor as 构造方法 JVM->>ClassLoader: 根据类的全限定名加载类字节码文件 ClassLoader->>JVM: 返回加载后的类信息 JVM->>Heap: 为对象分配内存空间 Heap->>JVM: 返回对象内存地址 JVM->>Object: 初始化对象的实例变量为零值 Object->>JVM: 零值初始化完成 JVM->>Constructor: 调用构造方法 Constructor->>Object: 执行构造方法逻辑 Object->>Constructor: 构造方法执行完毕 JVM->>Object: 返回对象引用 Note right of Object: 新创建的对象实例
  1. Java虚拟机 (JVM) 接收到对特定类的实例化请求,该类的全限定名由代码中的调用指定。

  2. 类加载器负责根据类的全限定名查找、加载和验证类的字节码文件。它首先检查类是否已经加载到内存中,如果没有,则根据类路径等规则加载字节码文件。

  3. 类加载器将加载后的类信息返回给JVM,包括类的结构、方法、字段等。

  4. JVM为对象在堆内存中分配空间。堆内存是Java运行时数据区域之一,用于存储对象实例。

  5. JVM对分配的内存空间进行清零操作,将对象的实例变量初始化为默认值(例如,数值类型为0,对象类型为null,布尔类型为false)。

  6. JVM调用对象的构造方法进行初始化。构造方法是一个特殊的方法,负责初始化对象的状态和执行其他必要的逻辑。构造方法可以有多个重载形式,根据调用的构造方法不同,对象的初始化过程也会有所区别。

  7. 构造方法执行完毕后,对象的实例化过程完成。

  8. JVM返回对象的引用,该引用可以用于操作和访问对象的实例变量和方法。

三、创建对象的方式

在Java中,我们可以使用多种方式创建对象。包括以下这六种:

graph LR A(Java对象创建的方式) B(使用new关键字创建对象) C(使用Class的newInstance方法创建对象) D(使用Constructor的newInstance方法创建对象) E(使用clone方法创建对象) F(使用反序列化创建对象) G(使用Unsafe创建对象) A ---> B A ---> C A ---> D A ---> E A ---> F A ---> G style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px style E fill:#98FB98,stroke:#98FB98,stroke-width:2px style F fill:#B2FFFF,stroke:#B2FFFF,stroke-width:2px style G fill:#ADD8E6,stroke:#ADD8E6,stroke-width:2px

3.1 使用new关键字创建对象:

这种大家都是耳熟能详的,不需要多做赘述。

java 复制代码
MyClass myObject = new MyClass();

3.2 使用Class的newInstance()方法创建对象(已过时)

newInstance方法默认会抛出InstantiationExceptionIllegalAccessException异常,必须要进行处理

java 复制代码
public class Test {

    public static void main(String[] args) {
        try {
            // newInstance默认会抛出InstantiationException和IllegalAccessException异常,必须要处理
            Math math = Math.class.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

}

3.3 使用Constructor的newInstance()方法创建对象:

这种方式默认抛出的异常更多,也是需要我们捕获并进行合理的处理的。

java 复制代码
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Test {

    public static void main(String[] args) {
        try {
            Constructor<Math> constructor = Math.class.getConstructor();
            Math myObject = constructor.newInstance();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

}

3.4 使用clone()方法创建对象(实现Cloneable接口)

当使用 clone() 方法创建对象时,需要确保被克隆的类实现了 Cloneable 接口,并在类中重写 clone() 方法。

以下是一个示例:

  • 实现案例
java 复制代码
public class Person implements Cloneable {

    private String name;

    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 重写clone()方法
     */
    @Override
    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}
  • 创建方法
java 复制代码
public class Test {

    public static void main(String[] args) {
        try {
            // 使用new的方式创建对象
            Person person1 = new Person("李四", 25);
            // 使用clone()方法创建对象
            Person person2 = person1.clone();

            System.out.println("原始的: " + person1.getName() + ", " + person1.getAge());
            System.out.println("Cloned: " + person2.getName() + ", " + person2.getAge());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }

}

注意:clone() 方法在 Object 类中定义,但是它的访问修饰符是 protected,因此需要在类内部进行重写。在重写的方法中,我们调用了 super.clone() 方法,并将其强制转换为 Person 类型,以获得克隆对象。

3.5 使用反序列化创建对象(实现Serializable接口)

当使用反序列化创建对象时,需要确保被反序列化的类实现了 Serializable 接口。

以下是一个示例:

  • 实现类
java 复制代码
import java.io.Serializable;

public class Person implements Serializable {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}
  • 创建方法
java 复制代码
import java.io.*;

public class Test {

    public static void main(String[] args) {
        try {
            Person person1 = new Person("王五", 25);
            // 将对象进行序列化
            FileOutputStream fileOut = new FileOutputStream("person.ser");
            ObjectOutputStream out = new ObjectOutputStream(fileOut);
            out.writeObject(person1);
            out.close();
            fileOut.close();

            // 将对象进行反序列化
            FileInputStream fileIn = new FileInputStream("person.ser");
            ObjectInputStream in = new ObjectInputStream(fileIn);
            Person person2 = (Person) in.readObject();
            in.close();
            fileIn.close();

            System.out.println("new的: " + person1.getName() + ", " + person1.getAge());
            System.out.println("通过序列化创建的: " + person2.getName() + ", " + person2.getAge());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}

注意:使用反序列化创建对象时,需要注意类的版本控制。如果序列化和反序列化过程中类的版本不一致,可能会导致 InvalidClassException 异常。为了避免这种情况,可以使用 serialVersionUID 字段显式地指定类的版本号,并在类的结构发生变化时进行更新。

另外,反序列化也需要注意安全性问题。反序列化过程中会执行类的构造方法,因此需要谨慎处理来自不可信源的序列化数据,以避免潜在的安全漏洞。

3.6 使用Unsafe创建对象

Java中,sun.misc.Unsafe类是一个提供了直接操作内存和对象的低级别API的工具类。尽管它是一个强大的工具,但它是不安全 的,因此在使用时需要谨慎,并且只应该在特殊情况下使用

使用Unsafe类创建对象的过程相对复杂,需要手动执行以下步骤:

graph LR 获取Unsafe实例 --> 分配内存空间 --> 手动初始化对象 --> 返回对象的引用
  • 定义一个实体类
java 复制代码
public class Person {

    private String name;

    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}
  • 创建实现方式
java 复制代码
import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class Test {

    public static void main(String[] args) {
        try {
            /* 获取Unsafe实例:由于Unsafe类没有公共的构造函数,因此无法直接实例化它。
            可以通过Unsafe.getUnsafe()方法获取Unsafe的实例,但是该方法只能在由引导类加载器加载的类中调用,因此在大多数情况下无法使用它。
            另一种方式是通过反射获取Unsafe实例,如下所示: */
            Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
            unsafeField.setAccessible(true);
            Unsafe unsafe = (Unsafe) unsafeField.get(null);

            /* 分配内存空间:使用allocateInstance(Class<?> cls)方法来分配内存空间,但不会调用构造函数进行初始化。这意味着对象的字段将保持其默认值。 */
            Person myObject = (Person) unsafe.allocateInstance(Person.class);

            /* 手动初始化对象:由于allocateInstance()方法不会调用构造函数,因此需要手动初始化对象的字段。 */
            unsafe.putInt(myObject, unsafe.objectFieldOffset(Person.class.getDeclaredField("age")), 25);
            unsafe.putObject(myObject, unsafe.objectFieldOffset(Person.class.getDeclaredField("name")), "小二");

            /* 返回对象的引用:完成字段的初始化后,可以使用创建的对象进行后续操作。 */
            System.out.println(myObject.toString());
        } catch (NoSuchFieldException | IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
    }

}

注意:使用Unsafe类创建对象需要对Java的内存模型有深入的了解,并且需要小心处理内存分配和字段初始化等细节。由于Unsafe类是非标准的API,并且在不同的Java版本和实现中可能会有差异,因此它不是常规的对象创建方式,应该谨慎使用,并且仅在特定的情况下才考虑使用它。

四、总结

在Java中,对象是基于类的实例化,是程序的基本构建块。对象是具有状态和行为的实体,它们可以存储数据和执行操作。

学习并掌握对象的各种创建方式,对于后续的设计模式学习和开发中使用有很大的帮助。

希望本文对您有所帮助。如果有任何错误或建议,请随时指正和提出。

同时,如果您觉得这篇文章有价值,请考虑点赞和收藏。这将激励我进一步改进和创作更多有用的内容。

感谢您的支持和理解!

相关推荐
Ylucius33 分钟前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
凡人的AI工具箱44 分钟前
AI教你学Python 第11天 : 局部变量与全局变量
开发语言·人工智能·后端·python
是店小二呀1 小时前
【C++】C++ STL探索:Priority Queue与仿函数的深入解析
开发语言·c++·后端
七夜zippoe1 小时前
分布式系统实战经验
java·分布式
canonical_entropy1 小时前
金蝶云苍穹的Extension与Nop平台的Delta的区别
后端·低代码·架构
是梦终空1 小时前
JAVA毕业设计176—基于Java+Springboot+vue3的交通旅游订票管理系统(源代码+数据库)
java·spring boot·vue·毕业设计·课程设计·源代码·交通订票
落落落sss1 小时前
sharding-jdbc分库分表
android·java·开发语言·数据库·servlet·oracle
码爸1 小时前
flink doris批量sink
java·前端·flink
我叫啥都行2 小时前
计算机基础知识复习9.7
运维·服务器·网络·笔记·后端
工业互联网专业2 小时前
毕业设计选题:基于springboot+vue+uniapp的驾校报名小程序
vue.js·spring boot·小程序·uni-app·毕业设计·源码·课程设计