画师:宮瀬まひろ     封面ID:78366862

2021.03.12 更新:

很巧啊,羊哥也更新了一遍关于对象创建的文章,我依旧贴出来吧:今天必须要完成一件大事!

0. 前言

我们常说:“ Java 程序员是不需要对象的,我们的对象都是 new 出来的”,这可不仅仅是一句玩笑话这么简单,其中还有一个隐藏含义:

  • 在 Java 中,可以使用 new 关键词来创建对象

那么问题来了,Java中还有其他方式可以创建对象吗?

题外话

中午问室友:“你知道 Java 中创建对象有几种方式吗?”,室友居然答不出?

大佬都答不出来,我觉得很有必要写一写了! 😏

PS: 我突然想起这个比有女朋友,不需要额外的对象了,单身狗留下了伤心的泪水! 😭

1. 创建对象的方法

假设我们先创建了一个 Student 类,这个类的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.yang.pojo;
/**
* @author 默烦
* 2020/9/5
*/
public class Student {
private String name;
private int age;

public Student() {
}

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

// 省略 Get/Set 方法, toString() 方法
}

接下来,我们围绕 Student 类来创建对象。

1.1 使用 new 关键词

这第一个应该都知道,在 Java 中可以通过 new 关键词来创建一个对象,比如:

1
Student stu = new Student("默烦", 18);

就这一句,一个 stu 对象就被我们创建成功了! 🎉

有一种玩笑观点: Java 已经不叫 Java 了,这种语言叫做 Spring !

这是什么意思呢?

简单来说,就是 Spring 这个框架对于 Java 这门语言来说占有举足轻重的地位。

因此,学 Java 的不学 Spring 就跟吃薯条不沾番茄酱一样离谱。

初学 Spring 时就知道在框架(业务代码)中采用 new 关键词创建一个对象会增加耦合度,业务代码讲究个啥?

高内聚,低耦合嘛 ~

那怎么搞?

Spring 咋搞的我们就咋搞!

Spring 怎么创建对象的?

究其本质,采用的反射的方式。

在 Java 中:

  • 万物皆是对象 (×)
  • 万物皆可“反射” (√)

1.2 使用反射创建对象

那怎么使用反射来创建对象呢?

很简单!利用 Class 类的 newinstance() 方法就可以了。

什么? 你还不知道啥叫反射?

小问题! 在本站搜索 【反射】关键词就可以查看有关反射的文章了。

刚刚说了,利用 Class 类的 newinstance() 方法就可以创建对象了。那问题又来了,怎么获取一个 Class 类实例呢?

其实在【注解与反射】一文中就讲述了一些方法,但是我们常用的就这三种:

  • 类名.class ,这种方式最为可靠,程序性能最高
  • 对象的 getClass() 方法
  • 通过Class类的静态方法获取 forName() 获取

说了这么多,那么怎么通过反射创建对象呢?

1
2
3
4
5
6
static void testReflection() throws Exception{
Class<?> stuClass = Class.forName("com.yang.pojo.Student");
// 调用默认的无参数构造器创建对象
Student stu = ((Student) stuClass.newInstance());
System.out.println(stu);
}

当然也可以使用其他方式去获取一个 Class 类的实例。

由于默认调用的无参数构造器创建的对象,因此这个对象的属性值都是没有指定的,可以使用 Set 方法进行指定。

除此之外,还可以使用使用 Constructor 类的 newInstance() 方法进行创建对象,这样的话就不用使用 Set 方法指定创建对象的属性值了,可以在创建对象时就利用构造器给各个属性赋值:

1
2
3
4
5
6
static void testReflection() throws Exception {
Class<?> stuClass = Class.forName("com.yang.pojo.Student");
Constructor<?> constructors = stuClass.getConstructor(String.class, int.class);
Student stu = (Student) constructors.newInstance("mofan", 18);
System.out.println(stu);
}

那么还有其他方式创建一个对象吗?

1.3 使用拷贝创建对象

还可以通过拷贝来创建一个对象! 那么 … 应该怎么做呢?

也不好说,直接上代码吧!

在拷贝之前,我们需要对我们的 Student 类改造一下,让这个类实现 Cloneable 接口,然后再重写 clone() 方法:

1
2
3
4
5
6
7
8
9
10
11
public class Student implements Cloneable {
private String name;
private int age;

// 省略其他代码

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

这下准备工作就完事了,我们利用拷贝来创建对象试试!

1
2
3
4
5
6
7
8
static void testClone() throws CloneNotSupportedException {
Student mofan = new Student("默烦", 20);
// 重写的 clone() 方法的修饰符是 protected
// 为了这里能够顺利调用,我们将其改为 public
Student stu = ((Student) mofan.clone());

System.out.println(mofan == stu);
}

我们发现,上述创建对象的方法就是调用了重写的 clone() 方法,那这样真的是创建了一个新的对象吗?

那就来测试一下吧!

拷贝创建对象测试

看看这打印结果! false ! 表明了啥?

使用 clone() 方法是可以创建一个对象的! 😎

浅拷贝与深拷贝

我们上述使用的 clone() 方法默认是浅拷贝的,如果 Student 类中还有一个引用类型的字段,然后我们修改克隆对象引用类型字段的属性值,那么原对象对应的属性值也会发生变化,这是不安全的,这也体现了浅拷贝中的 一字。

那么浅拷贝和深拷贝有什么区别呢?如果在面试的时候面试官这么问你,你可以这么回答:

  • 浅拷贝:只复制当前对象的基本数据类型及引用变量,没有复制引用变量指向的实际对象。修改克隆对象可能会影响原对象,不安全。
  • 深拷贝: 完全拷贝基本数据类型和引用数据类型,安全。

好像理解了一点浅拷贝与深拷贝的区别,但是有些地方又不是很理解,能详细说一下吗?

我就难得写了,羊哥这篇文章写得很好,我也是看的这篇:

一个工作三年的同事,居然还搞不清深拷贝/浅拷贝,被老大批了

众所周知,bilibili 是一个学习网站,羊哥流弊! 🐮

1.4 使用反序列化创建对象

在上述羊哥的文章后面还提到了可以使用反序列化来实现深拷贝 ,其实这也是一种创建对象的方式。

那么什么是序列化?什么又是反序列化?

计算机能理解的只有 01 代码,但是对于我们人类来说就不是这样了,我们有多种语言,一种语言甚至还有多种方言。如果要想让计算机理解我们的语言,我们就需要将我们的语言进行 “翻译” ,这个翻译就是将我们认识的字符转换为计算机认识的字节。

如果我们想将数据保存到磁盘(游戏存档)或者让数据在网络上传播,就需要将数据转换成字节,序列化 (Serialization) 是将对象的状态信息转换为可以存储或传输的形式的过程,反序列化则是相反。

简单来说:

  • 序列化:把 Java 对象转换成字节序列
  • 反序列化:把字节序列恢复成原来的 Java 对象

这样就产生了以下问题:

  • 怎么进行序列化(或反序列化)呢? 在类上实现个 Serializable 接口就完事了?

  • Serializable 接口有什么用?

  • 在有些类中经常看到一个名为 serialVersionUID 变量,这个变量前有一个 serial ,是否与序列化有关呢?

  • 序列化有没有啥特殊情况?

  • 序列化或反序列化时有没有什么需要注意呢?

很巧啊,羊哥对这个问题也写过一篇文章了,我也懒得写了😜:

序列化/反序列化,我忍你很久了!

不得不说,羊哥真的牛! 🐂


说了这么多,好像有点跑偏了,不是要介绍怎么使用反序列化创建对象嘛,怎么讲到序列化和反序列化了?

理解了序列化和反序列化后,再利用反序列创建一个对象那不是手到擒来?😁

使用反序列化创建对象,就是将一个对象序列化到磁盘上,然后采用反序列化将磁盘上的对象信息转化到内存中。

前面使用的 Student 类已经实现了 Cloneable 接口,我们就不用这个类了。重新创建一个类 Person ,让这个类实现接口 Serializable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @author 默烦
* 2020/9/5
*/
public class Person implements Serializable {
private String name;
private int gender;
private int age;

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

// 省略 Get/Set 方法, toString() 方法
}

然后我们给这个类编写一个名为 createObject() 的方法,让这个类的实例化的对象可以通过反序列化得到一个新的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Person createObject(File file) {
try {
// 将对象本身序列化成字节流
FileOutputStream fileOutputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(this);

// 反序列化对象
FileInputStream fileInputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
return ((Person) objectInputStream.readObject());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}

接下来就是测试了,看看是不是真的可以通过反序列化创建一个对象,测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
static void testSerializable() {
Person mofan = new Person("默烦", 1, 20);
File file = new File("D:\\mofan.txt");
Person person = mofan.createObject(file);

System.out.println(mofan == person);
System.out.println(mofan);
System.out.println(person);
person.setName("mofan");
System.out.println(person);
}

运行测试代码,控制台打印结果是:

反序列化创建对象测试

根据第一行打印结果的 false 就可以明白反序列化是成功创建了一个全新的对象的,同时修改新创建对象的值也不会影响原对象。

同时,反序列化创建的对象也属于一种深拷贝,有兴趣可以自己测试一下。

序列化更多相关内容可以查阅:Lambda 与序列化


以上就是 Java 中创建对象的 4 种方式,以后别再说自己没对象了,我这都 4 种创建对象的方法了!

Java 程序员从不说自己没对象(仰起单身🐶高贵的头颅😭)