Java中6种创建对象的方式

qchen 等级 264 0 0

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 );
收藏
评论区

相关推荐

使用jsp直接执行定时任务
使用jsp直接执行定时任务servicehtml<%@ page import"com.leasing.emogo.framework.util.ApplicationContextUtils" %<%@ page import"job.dsc.GetInfoByAssetPackageJob" %<%@ page contentType
JAVA 中的反射(reflact)
**获取反射加载类(获取类的字节码)的3种方式:** * Class class1=Class.forName("lession\_svc.lession\_svc.reflact.Person"); * Class class2 =new Person().getClass(); * Class class3\=Person.class;
JAVA中的IO
FILE类常量 import java.io.*; class hello{ public static void main(String[] args) { System.out.println(File.separator);//输出"/" Syste
Java 中的堆和栈
Java 中的堆和栈 简单的说: Java把内存划分成两种:一种是栈内存,一种是堆内存。          在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。          当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的
Java 中的方法
定义一个方法的语法是: **访问修饰符  返回值类型  方法名(参数列表){** **方法体;** **}** 其中: 1、 **访问修饰符**:方法允许被访问的权限范围, 可以是 public、protected、private 甚至可以省略 ,其中 public 表示该方法可以被其他任何代码调用 , protected 只有子类可用 , pr
Java中ArrayList的使用
ArrayList类是一个特殊的数组--动态数组。来自于System.Collections命名空间;通过添加和删除元素,就可以动态改变数组的长度。 优点: 1、支持自动改变大小 2、可以灵活的插入元素 3、可以灵活的删除元素 局限: 比一般的数组的速度慢一些; **用法** 一、初始化: 1、不初始化容量 ArrayList
Java中的Map集合
Map接口简介 ------- Map接口是一种双列集合,它的每个元素都包含一个键对象Key和值对象Value,键和值对象之间存在一种对应关系,称为映射。从Map集合中访问元素时,只要指定了Key,就能找到对应的Value, Map中的键必须是唯一的,不能重复,如果存储了相同的键,后存储的值会覆盖原有的值,简而言之就是键相同,值覆盖。 **Map常用
Java中的多线程
**线程安全和同步** * **线程安全**:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果,如不加事务的转账代码: void transferMoney(U
Java中的异常
异常的概述 ----- 异常就是不正常的意思,Java语言中主要指程序在运行阶段产生的错误 Throwable(可抛出、可扔出的) Java.lang.Throwable类是Java程序所有错误或异常的超类 #### 主要有两个子类:   Error:主要描述比较严重的错误,无法通过编程来解决的重大的错误   Exception:主要描述比较轻量
Java中的数组(Array)
数组对于每一门编程语言来讲都是最重要的数据结构之一,当然不同的编程语言对数组的实现以及处理也不尽相同。 **数组的概念:** 把有限个相同类型元素变量放在一个整体,这个整体就叫做数组。数组中的每一个元素被称为数组元素,通常可以通过数组元素的索引(也叫下标,可以理解为一种编号,从0开始)来访问数组元素,包括数组元素的赋值(set)和取值(get)。
Java中的管程
> 并发编程这个技术领域已经发展了半个世纪了,相关的理论和技术纷繁复杂。那有没有一种核心技术可以很方便地解决我们的并发问题呢?这个问题如果让我选择,我一定会选择**管程**技术。Java 语言在 1.5 之前,提供的唯一的并发原语就是管程,而且 1.5 之后提供的 SDK 并发包,也是以管程技术为基础的。除此之外,C/C++、C# 等高级语言也都支持管程。可
Java中的集合
Java Collections Framework是Java编程语言的核心部分之一。集合几乎用于任何编程语言中。大多数编程语言都支持各种类型的集合,例如List, Set, Queue, Stack等。 ### 1.什么是Java Collections Framework? 集合就像容器一样,将多个项目组合在一个单元中。例如,一罐巧克力,一组名称等。
java中的daemon thread
java中的daemon thread java中有两种类型的thread,user threads 和 daemon threads。 User threads是高优先级的thread,JVM将会等待所有的User Threads运行完毕之后才会结束运行。 daemon threads是低优先级的thread,它的作用是为User Thread提供服
java中的锁
记录一下**公平锁,非公平锁,可重入锁(递归锁),读写锁,**自旋锁****的概念,以及一些和锁有关的java类。 **公平锁**与**非公平锁**: 公平锁就是在多线程环境下,每个线程在获取锁时,先查看这个锁维护的队列,如果队列为空或者自身就是等待队列的第一个,就占有锁。否则就加入到等待队列中,按照FIFO的顺序依次占有锁。 非公平锁会一上来就试图占
java开发中的重中之重
* 介绍:   mysql是目前世界上最流行的关系型数据库,在国内大的互联网公司都在使用mysql数据库,mysql经常被我们这样概述,“mysql是轻量级关系型数据库”,其实轻量级并不是说mysql是中小型数据库,在项目开发中,存储数据的量往往是一个架构问题,如果配合架构,mysql也是可以存储海量数据的。并且海量数据并没有一个明确的标准。说mysq

热门文章

如何正确使用float和double?枚举Enum的使用Java8函数式编程

最新文章

Java8函数式编程如何正确使用float和double?枚举Enum的使用