单身狗的福音——如何获得一个对象
画师:宮瀬まひろ 封面ID:78366862
2021.03.12 更新:
很巧啊,羊哥也更新了一遍关于对象创建的文章,我依旧贴出来吧:今天必须要完成一件大事!
0. 前言
我们常说:“ Java 程序员是不需要对象的,我们的对象都是 new
出来的”,这可不仅仅是一句玩笑话这么简单,其中还有一个隐藏含义:
- 在 Java 中,可以使用
new
关键词来创建对象
那么问题来了,Java中还有其他方式可以创建对象吗?
题外话
中午问室友:“你知道 Java 中创建对象有几种方式吗?”,室友居然答不出?
大佬都答不出来,我觉得很有必要写一写了! 😏
PS: 我突然想起这个比有女朋友,不需要额外的对象了,单身狗留下了伤心的泪水! 😭
1. 创建对象的方法
假设我们先创建了一个 Student 类,这个类的结构如下:
1 | package com.yang.pojo; |
接下来,我们围绕 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 | static void testReflection() throws Exception{ |
当然也可以使用其他方式去获取一个 Class
类的实例。
由于默认调用的无参数构造器创建的对象,因此这个对象的属性值都是没有指定的,可以使用 Set 方法进行指定。
除此之外,还可以使用使用 Constructor
类的 newInstance()
方法进行创建对象,这样的话就不用使用 Set 方法指定创建对象的属性值了,可以在创建对象时就利用构造器给各个属性赋值:
1 | static void testReflection() throws Exception { |
那么还有其他方式创建一个对象吗?
1.3 使用拷贝创建对象
还可以通过拷贝来创建一个对象! 那么 … 应该怎么做呢?
也不好说,直接上代码吧!
在拷贝之前,我们需要对我们的 Student 类改造一下,让这个类实现 Cloneable
接口,然后再重写 clone()
方法:
1 | public class Student implements Cloneable { |
这下准备工作就完事了,我们利用拷贝来创建对象试试!
1 | static void testClone() throws CloneNotSupportedException { |
我们发现,上述创建对象的方法就是调用了重写的 clone()
方法,那这样真的是创建了一个新的对象吗?
那就来测试一下吧!
看看这打印结果! false
! 表明了啥?
使用 clone()
方法是可以创建一个对象的! 😎
浅拷贝与深拷贝
我们上述使用的 clone()
方法默认是浅拷贝的,如果 Student 类中还有一个引用类型的字段,然后我们修改克隆对象引用类型字段的属性值,那么原对象对应的属性值也会发生变化,这是不安全的,这也体现了浅拷贝中的 浅 一字。
那么浅拷贝和深拷贝有什么区别呢?如果在面试的时候面试官这么问你,你可以这么回答:
- 浅拷贝:只复制当前对象的基本数据类型及引用变量,没有复制引用变量指向的实际对象。修改克隆对象可能会影响原对象,不安全。
- 深拷贝: 完全拷贝基本数据类型和引用数据类型,安全。
好像理解了一点浅拷贝与深拷贝的区别,但是有些地方又不是很理解,能详细说一下吗?
我就难得写了,羊哥这篇文章写得很好,我也是看的这篇:
众所周知,bilibili 是一个学习网站,羊哥流弊! 🐮
1.4 使用反序列化创建对象
在上述羊哥的文章后面还提到了可以使用反序列化来实现深拷贝 ,其实这也是一种创建对象的方式。
那么什么是序列化?什么又是反序列化?
计算机能理解的只有 01 代码,但是对于我们人类来说就不是这样了,我们有多种语言,一种语言甚至还有多种方言。如果要想让计算机理解我们的语言,我们就需要将我们的语言进行 “翻译” ,这个翻译就是将我们认识的字符转换为计算机认识的字节。
如果我们想将数据保存到磁盘(游戏存档)或者让数据在网络上传播,就需要将数据转换成字节,序列化 (Serialization) 是将对象的状态信息转换为可以存储或传输的形式的过程,反序列化则是相反。
简单来说:
- 序列化:把 Java 对象转换成字节序列
- 反序列化:把字节序列恢复成原来的 Java 对象
这样就产生了以下问题:
-
怎么进行序列化(或反序列化)呢? 在类上实现个
Serializable
接口就完事了? -
Serializable
接口有什么用? -
在有些类中经常看到一个名为
serialVersionUID
变量,这个变量前有一个 serial ,是否与序列化有关呢? -
序列化有没有啥特殊情况?
-
序列化或反序列化时有没有什么需要注意呢?
很巧啊,羊哥对这个问题也写过一篇文章了,我也懒得写了😜:
不得不说,羊哥真的牛! 🐂
说了这么多,好像有点跑偏了,不是要介绍怎么使用反序列化创建对象嘛,怎么讲到序列化和反序列化了?
理解了序列化和反序列化后,再利用反序列创建一个对象那不是手到擒来?😁
使用反序列化创建对象,就是将一个对象序列化到磁盘上,然后采用反序列化将磁盘上的对象信息转化到内存中。
前面使用的 Student 类已经实现了 Cloneable
接口,我们就不用这个类了。重新创建一个类 Person ,让这个类实现接口 Serializable
。
1 | /** |
然后我们给这个类编写一个名为 createObject()
的方法,让这个类的实例化的对象可以通过反序列化得到一个新的对象:
1 | public Person createObject(File file) { |
接下来就是测试了,看看是不是真的可以通过反序列化创建一个对象,测试代码如下:
1 | static void testSerializable() { |
运行测试代码,控制台打印结果是:
根据第一行打印结果的 false
就可以明白反序列化是成功创建了一个全新的对象的,同时修改新创建对象的值也不会影响原对象。
同时,反序列化创建的对象也属于一种深拷贝,有兴趣可以自己测试一下。
序列化更多相关内容可以查阅:Lambda 与序列化
以上就是 Java 中创建对象的 4 种方式,以后别再说自己没对象了,我这都 4 种创建对象的方法了!
Java 程序员从不说自己没对象(仰起单身🐶高贵的头颅😭)