Java中6种创建对象的方式

qchen
• 阅读 1764

1、使用关键字new创建对象

// 无参构造
Test test1 = new Test();
// 有参构造
Test test2 = new Test("小明", 18);

new对象过程中,底层发生了什么?

  1. 类加载 JVM检查先是否已经加载,没有则执行类加载过程

  2. 声明类型引用 声明一个Test类型的引用test

  3. 堆内存分配 类加载步骤中已确定对象所需内存大小,JVM在堆上为对象分配内存

  4. 属性“0”值初始化 int初始化0值是0,float初始化0值是0.0等,对象初始化0值是null

  5. 对象头设置 包括对象运行时数据(Hash码、分代年龄、锁状态标志、锁指针、偏向线程ID、偏向时间戳)以及类型指针

  6. 属性显示初始化 例如:private int age = 18; age属性的显示初始化

  7. 构造方法初始化 调用类的构造方法进行方法内描述的初始化动作

2、反射出一个对象

获取类的Class对象,通过反射机制来实例对象

首先,获取Class对象的三种方式:

// 1、类名.class
Test.class

// 2、对象名.getClass()
test.getClass()

// 3、Class.forName(全限定类名)
Class.forName("com.project.obj.Test");

然后调用newInstance()方法创建一个对象

Test test3 = (Test)Class.forName("com.project.obj.Test").newInstance();
Test test4 = Test.class.newInstance();

局限性:只能调用类的无参构造

解决:使用java.lang.relect.Constructor类的newInstance()方法来创建对象,获取所有构造函数列表,也可指定构造函数

// 获取所有构造函数列表
Constructor<?>[] constructors = Test.class.getDeclaredConstructors();
Test test5 = (Test)constructors[0].newInstance();
Test test6 = (Test)constructors[1].newInstance("小明",18);

// 指定构造函数
Constructor constructor = Test.class.getDeclaredConstructors(String.class, Integer.class);
Test test7 = (Test)constructor.newInstance("小明",18);

3、克隆出一个对象

值类型:(值传递)

  • 整型:long,int,short,byte
  • 浮点型:float,double
  • 字符型:char
  • 布尔型:boolean

引用类型:(引用传递)

  • 数组
  • 类Class
  • 枚举Enum
  • Integer包装类

1、首先定义两个类:

// 学生的所学专业
public class Major {
    private String majorName; // 专业名称
    private long majorId;     // 专业代号

    // ... 其他省略 ...
}
// 学生
public class Student {
    private String name;  // 姓名
    private int age;      // 年龄
    private Major major;  // 所学专业

    // ... 其他省略 ...
}

2、赋值

// 仅仅拷贝引用关系,并没有生成新的实际对象,都指向同一个Student对象
Student studeng1 = new Student();
Student studeng2 = studeng1;

3、浅拷贝 让被复制对象的类实现Cloneable接口,并重写clone()方法即可

public class Student implements Cloneable {

    private String name;  // 姓名
    private int age;      // 年龄
    private Major major;  // 所学专业

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    // ... 其他省略 ...

}

结果:克隆了一个新对象,修改studeng1的major,student2的major也受影响。

4、深拷贝 clone()方法默认是浅拷贝行为,若想实现深拷贝需覆写 clone()方法实现引用对象的深度遍历式拷贝,进行地毯式搜索。

如果想实现深拷贝,首先需要对更深一层次的引用类Major做改造,让其也实现Cloneable接口并重写clone()方法:

public class Major implements Cloneable {

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    // ... 其他省略 ...
}

其次我们还需要在顶层的调用类中重写clone方法,来调用引用类型字段的clone()方法实现深度拷贝:

public class Student implements Cloneable {

    @Override
    public Object clone() throws CloneNotSupportedException {
        Student student = (Student) super.clone();
        student.major = (Major) major.clone(); // 重要!!!
        return student;
    }

    // ... 其他省略 ...
}

5、利用反序列化实现深拷贝 这里改造一下Student类,让其clone()方法通过序列化和反序列化的方式来生成一个原对象的深拷贝副本:

public class Student implements Serializable {

    private String name;  // 姓名
    private int age;      // 年龄
    private Major major;  // 所学专业

    public Student clone() {
        try {
            // 将对象本身序列化到字节流
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream =
                    new ObjectOutputStream( byteArrayOutputStream );
            objectOutputStream.writeObject( this );

            // 再将字节流通过反序列化方式得到对象副本
            ObjectInputStream objectInputStream =
                    new ObjectInputStream( new ByteArrayInputStream( byteArrayOutputStream.toByteArray() ) );
            return (Student) objectInputStream.readObject();

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        return null;
    }

    // ... 其他省略 ...
}

要求被引用的子类(比如这里的Major类)也必须是可以序列化的,即实现了Serializable接口:

public class Major implements Serializable {

  // ... 其他省略 ...

}

4、反序列出一个对象

序列化:把java对象转换为字节序列 反序列化:把字节序列恢复为原先的java对象

例子:将Student类对象序列到一个名为student.txt的文本文件中,然后再通过文本文件反序列化成Student类对象。

public class Student implements Serializable {

    private String name;
    private Integer age;
    private Integer score;

    @Override
    public String toString() {
        return "Student:" + '\n' +
        "name = " + this.name + '\n' +
        "age = " + this.age + '\n' +
        "score = " + this.score + '\n'
        ;
    }

    // ... 其他省略 ...
}
// 序列化
public static void serialize(  ) throws IOException {

    Student student = new Student();
    student.setName("xiaoming");
    student.setAge( 18 );
    student.setScore( 1000 );

    ObjectOutputStream objectOutputStream = 
        new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) );
    objectOutputStream.writeObject( student );
    objectOutputStream.close();

    System.out.println("序列化成功!已经生成student.txt文件");
    System.out.println("==============================================");
}
// 反序列化
public static void deserialize(  ) throws IOException, ClassNotFoundException {
    ObjectInputStream objectInputStream = 
        new ObjectInputStream( new FileInputStream( new File("student.txt") ) );
    Student student = (Student) objectInputStream.readObject();
    objectInputStream.close();

    System.out.println("反序列化结果为:");
    System.out.println( student );
}

Serializable接口的作用: 进入源码,发现是一个空接口,不包含任何方法 Java中6种创建对象的方式 不实现Serializable接口,抛出异常: Java中6种创建对象的方式 由源码一直跟到ObjectOutputStream的writeObject0()方法底层: Java中6种创建对象的方式 如果一个对象既不是字符串、数组、枚举,而且也没有实现Serializable接口的话,在序列化时就会抛出NotSerializableException异常!所以,Serializable接口也仅仅只是做一个标记用!

serialVersionUID的作用: 1、serialVersionUID是序列化前后的唯一标识符 2、默认如果没有人为显式定义过serialVersionUID,那编译器会为它自动声明一个

建议: 凡是implements Serializable的类,都最好人为显式地为它声明一个serialVersionUID明确值,IDEA中可使用alt+Enter快捷键为类自动添加serialVersionUID字段

两种特殊情况: 1、被static修饰过的字段不会被序列化(因为序列化保存的是对象的状态而非类的状态) 2、被transient修饰符修饰的字段不会被序列化

序列化的受控和加强: 局限性:从序列化到反序列化的过程中,如果拿到中间字节流,即可对其状态进行修改,具有一定风险。 解决:在可序列化的类中,自行编写readObject()函数,用于对象的反序列化构造。

private void readObject( ObjectInputStream objectInputStream ) throws IOException, ClassNotFoundException {

    // 调用默认的反序列化函数
    objectInputStream.defaultReadObject();

    // 手工检查反序列化后学生成绩的有效性,若发现有问题,即终止操作!
    if( 0 > score || 100 < score ) {
        throw new IllegalArgumentException("学生分数只能在0到100之间!");
    }
}

单例模式增强: 注意:可序列化的单例类有可能不单例

// 使用静态内部类创建单例
public class Singleton implements Serializable{
    private static final long seriaVersionUID = -1576643344804979563L;
    private Singleton(){
    }

    // 静态内部类
    private static class SingletonHolder{
        private static final Singleton singleton = new Singleton();
    }

    public static synchronized Singleton getSingleton(){
        return SingletonHolder.singleton;
    }
}
public class Test2 {

    public static void main(String[] args) throws IOException, ClassNotFoundException {

        ObjectOutputStream objectOutputStream =
                new ObjectOutputStream(
                    new FileOutputStream( new File("singleton.txt") )
                );
        // 将单例对象先序列化到文本文件singleton.txt中
        objectOutputStream.writeObject( Singleton.getSingleton() );
        objectOutputStream.close();

        ObjectInputStream objectInputStream =
                new ObjectInputStream(
                    new FileInputStream( new File("singleton.txt") )
                );
        // 将文本文件singleton.txt中的对象反序列化为singleton1
        Singleton singleton1 = (Singleton) objectInputStream.readObject();
        objectInputStream.close();

        Singleton singleton2 = Singleton.getSingleton();

        // 运行结果竟打印 false !
        System.out.println( singleton1 == singleton2 );
    }
}

局限性:反序列化后的singleton1和singleton2并不是一个对象。 解决:在单例类中手写readResolve()函数,直接返回单例对象。

public class Singleton implements Serializable{
    private static final long seriaVersionUID = -1576643344804979563L;
    private Singleton(){
    }

    private static class SingletonHolder{
        private static final Singleton singleton = new Singleton();
    }

    public static synchronized Singleton getSingleton(){
        return SingletonHolder.singleton;
    }

    private Object readResolve(){
        return SingletonHolder.singleton;
    }
}

当反序列化从流中读取对象时,readResolve()会被调用,用其中返回的对象替代反序列化新建的对象。

5、Unsafe

介绍:sun.misc.Unsafe包下的Unsafe类提供了一种直接访问系统资源的途径和方法,可进行一些底层操作,比如:分配内存、创建对象、释放内存、定位对象某个字段的内存并修改它等。

首先使用反射获取Unsafe类的实例:

Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe)f.get(null);

然后使用allocateInstance()方法创建对象:

Test test8 = (Test)unsafe.allocateInstance(Test.class);

6、对象的隐式创建场景

1、Class类实例隐式场景 JVM在加载类时,都会“偷偷”创建一个类对应的Class实例对象

2、字符串隐式对象创建

// 定义一个String类型变量,会创建一个新的String对象
String name = "小明";

// 字符串拼接,会创建新对象
String str = str1 + str2;

3、自动装箱机制

// 一个新的包装类型的对象在后台被创建
Integer age = 18;

4、函数可变参数 当我们使用可变参数语法int... nums来描述一个函数的入参时:

public double avg( int... nums ) {
    double sum = 0;
    int length = nums.length;
    for (int i = 0; i<length; ++i) {
        sum += nums[i];
    }
    return sum/length;
}

// 传参过程中,会隐式地产生一个对应的数组对象进行计算
avg( 2, 2, 4 );
avg( 2, 2, 4, 4 );
avg( 2, 2, 4, 4, 5, 6 );
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
java 复制Map对象(深拷贝与浅拷贝)
java复制Map对象(深拷贝与浅拷贝)CreationTime2018年6月4日10点00分Author:Marydon1.深拷贝与浅拷贝  浅拷贝:只复制对象的引用,两个引用仍然指向同一个对象
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
java常用类(2)
三、时间处理相关类Date类:计算机世界把1970年1月1号定为基准时间,每个度量单位是毫秒(1秒的千分之一),用long类型的变量表示时间。Date分配Date对象并初始化对象,以表示自从标准基准时间(称为“历元”(epoch),即1970年1月1日08:00:00GMT)以来的指定毫秒数。示例:packagecn.tanjian
Java开发面试高频考点学习笔记(每日更新)
Java开发面试高频考点学习笔记(每日更新)1.深拷贝和浅拷贝2.接口和抽象类的区别3.java的内存是怎么分配的4.java中的泛型是什么?类型擦除是什么?5.Java中的反射是什么6.序列化与反序列化7.Object有哪些方法?8.JVM内存模型9.类加载机制10.对象的创建和对象的布局11.Java的四种引用
Wesley13 Wesley13
3年前
java虚拟机(三)
普通对象的创建(不包括数组和class对象):当虚拟机遇到new指令时,会在常量池中检查是否包含这个类的符号引用(全限定名),通过这个确定是否经过类加载的过程,如果true,为该对象分配内存,对象大小在类加载过程就已经确定。如果false,需要进行类加载。分配内存1、分配内存的方式:指针碰撞:如果内存
九路 九路
4年前
2 Java对象的创建过程
JAVA中创建对象直接new创建一个对象,对么对象的创建过程是怎样的呢?程序运行过程中有许多的对象被创建出来.那么对象是如何创建的呢?一对象创建的步骤1遇到new指令时,检查这个指令的参数是否能在常量池中找到一个符号引用2如果找到符号引用,就检查这个符号引用是否已经被加载,解析和初始化过3如果没有加载过,则执行类加载过程
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
unity将 -u4E00 这种 编码 转汉字 方法
 unity中直接使用 JsonMapper.ToJson(对象),取到的字符串,里面汉字可能是\\u4E00类似这种其实也不用转,服务器会通过类似fastjson发序列化的方式,将json转对象,获取对象的值就是中文但是有时服务器要求将传参中字符串中类似\\u4E00这种转汉字,就需要下面 publ
qchen
qchen
Lv1
谁家玉笛暗飞声,散入春风满洛城。
文章
4
粉丝
2
获赞
8