Java创建对象的四种方式

article/2025/10/21 12:16:24

1. new
2. clone
3. 通过反射newInstance
4. 反序列化
5. String s = “abc”(这个是比较特殊的)

以String类为例

String string = null;

Class class1 = String.class;// 该方法最为安全可靠,程序性能更高。
Class class2 = string.getClass();
Class class3 = Class.forName(“java.lang.String”);// 可能抛出ClassNotFoundException异常

一旦获取了该类所对应的Class对象之后,就可以通过调用Class对象的方法来获得该对象和该类的真实信息了

1. new 出一个对象

例:

String s = new String("abc");

2. 利用反射创建对象

1.创建对象

通过反射来生成对象有如下两种方式:

(1)使用Class对象的newInstance()方法来创建该Class对象对应类的实例。但是这种方式要求该Class对象的对应类有默认的构造器,而执行newInstance()方法时实际上是利用默认构造器来创建该类的实例。

(2)先使用Class对象获取指定的Constructor对象,再调用Construtor对象的newInstance()方法来创建该Class对象对应类的实例。通过这种方式可以选择使用某个类的指定构造器来创建实例。

另外,如果我们不想利用默认构造器来创建java对象,而想利用指定的构造器来创建java对象,则需要利用Construtor对象,每个Construtor对应一个构造器,为了利用指定构造器来创建java对象,需要如下三个步骤:

(1)获取该Class对象;

(2)利用该Class对象的getConstrutor方法来获取指定的构造器;

(3)调用Construtor的newInstance方法来创建Java对象。

2.调用方法

获取到某个类的Class对象之后,可以通过该Class对象的getMethods方法或者getMethod方法获取全部或指定方法。

每个Method对象对应一个方法,获得Method对象后,程序就可通过该Method来调用对应的方法,在Method里包含一个invoke方法,该方法签名如下:

Object invoke(Object obj,Object… args);该方法中的obj是执行该方法的主调,后面的args是执行该方法时传入该方法的实参。

当通过Method的invoke方法来调用对应的方法时,Java会要求程序必要要有调用该方法的权限。如果程序确实需要调用该对象的私有方法,则可先调用Method对象的:

setAccessible(boolean flag);方法,将Method对象的accessoble标志设置为指示的布尔值。

布尔值为true,则表示该Method在使用时应该取消Java语言访问权限检查;

布尔值为false,则表示该Method在使用时应该实施Java语言访问权限检查;

3.访问属性值

通过Class对象的getFields或getField方法可以获取该类所包括的全部Field(属性)或指定Field,Field提供了如下两组方法来访问属性:

getXxx(Object obj);获取obj对象该Field的属性值,此处的Xxx对应8个基本类型,如果该属性的类型是引用类型则取消get后面的Xxx。

setXxx(Object obj,Xxx val);将obj对象的该Field设置成val值,此处的Xxx对应8个基本类型,如果该属性的类型是引用类型则取消set后面的Xxx。

4.示例代码如下:

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class ClassTest {public static void main(String[] args) throws Exception {Object object;Class cl = Class.forName("TestMe");Method method = cl.getDeclaredMethod("print", new Class[]{String.class});Constructor constructor = cl.getDeclaredConstructor(new Class[]{String.class});object = constructor.newInstance(new Object[]{"Hello"});method.invoke(object, new Object[]{"zhouxianli"});}
}
class TestMe {private String str;public TestMe(String str) {this.str = str;System.out.println("In Constructor str = " + str);}public void print(String name) {System.out.println("In print str = " + str + " and name = " + name);}
} 

3. 利用clone创建对象

什么是"clone"?

在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能 会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。在 Java语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需求虽然有很多途径,但实现clone()方法是其中最简单,也是最高效的手段。

Java的所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clone()。JDK API的说明文档解释这个方法将返回Object对象的一个拷贝。要说明的有两点:一是拷贝对象返回的是一个新对象,而不是一个引用。二是拷贝对象与用 new操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。

1. Clone&Copy
假设现在有一个Employee对象,Employee tobby =new Employee(“CMTobby”,5000),通
常我们会有这样的赋值Employee cindyelf=tobby,这个时候只是简单了copy了一下reference,cindyelf和tobby都指向内存中同一个object,这样cindyelf或者tobby的一个操作都可能影响到对方。打个比方,如果我们通过cindyelf.raiseSalary()方法改变了salary域的值,那么tobby通过getSalary()方法得到的就是修改之后的salary域的值,显然这不是我们愿意看到的。我们希望得到tobby的一个精确拷贝,同时两者互不影响,这时候我们就可以使用Clone来满足我们的需求。Employee cindy=tobby.clone(),这时会生成一个新的Employee对象,并且和tobby具有相同的属性值和方法。
2. Shallow Clone&Deep Clone
Clone是如何完成的呢?Object在对某个对象实施Clone时对其是一无所知的,它仅仅是简单地执行域对域的copy,这就是Shallow Clone。这样,问题就来了咯,以Employee为例,它里面有一个域hireDay不是基本型别的变量,而是一个reference变量,经过Clone之后就会产生一个新的Date型别的reference,它和原始对象中对应的域指向同一个Date对象,这样克隆类就和原始类共享了一部分信息,而这样显然是不利的,过程下图所示:
在这里插入图片描述
这个时候我们就需要进行deep Clone了,对那些非基本型别的域进行特殊的处理,例如本例中的hireDay。我们可以重新定义Clone方法,对hireDay做特殊处理,如下代码所示:

class Employee implements Cloneable  {  public Object clone() throws CloneNotSupportedException  {  Employee cloned = (Employee) super.clone();  cloned.hireDay = (Date) hireDay.clone()  return cloned;  }  
} 

3. Clone()方法的保护机制

在Object中Clone()是被申明为protected的,这样做是有一定的道理的,以Employee

类为例,通过申明为protected,就可以保证只有Employee类里面才能“克隆”Employee对象,原理可以参考我前面关于public、protected、private的学习笔记。

4. Clone()方法的使用

Clone()方法的使用比较简单,注意如下几点即可:

a. 什么时候使用shallow Clone,什么时候使用deep Clone,这个主要看具体对象的域是什么性质的,基本型别还是reference variable

b. 调用Clone()方法的对象所属的类(Class)必须implements Clonable接口,否则在调用Clone方法的时候会抛出CloneNotSupportedException。

4. 利用反序列化创建对象

1、为什么要进行序列化

再介绍之前,我们有必要先了解下对象的生命周期,我们知道Java中的对象都是存在于堆内存中的,而堆内存是可以被垃圾回收器不定期回收的。从对象被创建到被回收这一段时间就是Java对象的生命周期,也即Java对象只存活于这个时间段内。

对象被垃圾回收器回收意味着对象和对象中的成员变量所占的内存也就被回收,这意味着我们就再也得不到该对象的任何内容了,因为已经被销毁了嘛,当然我们可以再重新创建,但这时的对象的各种属性都又被重新初始化了。所以如果我们需要保存某对象的状态,然后再在未来的某段时间将该对象再恢复出来的话,则必须要在对象被销毁即被垃圾回收器回收之前保存对象的状态。要保存对象状态的话,我们可以使用文件、数据库,也可以使用序列化,这里我们主要介绍对象序列化。我们很有必要了解这方面的内容,因为对象序列化不仅在保存对象状态时可以被用到(对象持久化),在Java中的远程方法调用RMI也会被用到,在网络中要传输对象的话,则必须要对对象进行序列化,关于RMI有机会我会再专门开贴介绍。

简单总结起来,进行对象序列化的话的主要原因就是实现对象持久化和进行网络传输,这里先只介绍怎样通过对象序列化保存对象的状态。

下面我们通过一个简单的例子来介绍下如何进行对象序列化。

2、怎样进行对象序列化

假设我们要保存Person类的某三个对象的name、age、height这三个成员变量,当然这里只是简单举例

我们先看下Person类,要序列化某个类的对象的话,则该类必要实现Serializable接口,从Java API中我们发现该接口是个空接口,即该接口中没声明任何方法。

import java.io.Serializable;  
public class Person implements Serializable {  
int age;  
int height;  
String name;  
public Person(String name, int age, int height){  
this.name = name;  
this.age = age;  
this.height = height;  
}  
}  

下面我们看一下如何来进行序列化,这其中主要涉及到 Java 的 I/O 方面的内容,主要用到两个类 FileOutputStream 和 ObjectOutputStream , FileOutputStream 用于将字节输出到文件, ObjectOutputStream 通过调用 writeObject 方法将对象转换为可以写出到流的数据。所以整个流程是这样的: ObjectOutputStream 将要序列化的对象转换为某种数据,然后通过 FileOutputStream 连接某磁盘文件,再对象转化的数据转化为字节数据再将其写出到磁盘文件。下面是具体代码:

import java.io.FileOutputStream;  
import java.io.IOException;  
import java.io.ObjectOutputStream;  
public class MyTestSer {  
/** * Java对象的序列化与反序列化 */  
public static void main(String[] args) {  
Person zhangsan = new Person("zhangsan", 30, 170);  
Person lisi = new Person("lisi", 35, 175);  
Person wangwu = new Person("wangwu", 28, 178);  
try {  
//需要一个文件输出流和对象输出流;文件输出流用于将字节输出到文件,对象输出流用于将对象输出为字节  
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));  
out.writeObject(zhangsan);  
out.writeObject(lisi);  
out.writeObject(wangwu);  
out.close();  
} catch (IOException e) {  
e.printStackTrace();  
}  
}  
}  

3、对象的反序列化
我们存储的目的主要是为了再恢复使用,下面我们来看下加上反序列化后的代码:

import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.IOException;  
import java.io.ObjectInputStream;  
import java.io.ObjectOutputStream;  
public class MyTestSer {  
/** * Java对象的序列化与反序列化 */  
public static void main(String[] args) {  
Person zhangsan = new Person("zhangsan", 30, 170);  
Person lisi = new Person("lisi", 35, 175);  
Person wangwu = new Person("wangwu", 28, 178);  
try {  
//需要一个文件输出流和对象输出流;文件输出流用于将字节输出到文件,对象输出流用于将对象输出为字节  
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));  
out.writeObject(zhangsan);  
out.writeObject(lisi);  
out.writeObject(wangwu);  
} catch (IOException e) {  
e.printStackTrace();  
}  
try {  
ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"));  
Person one = (Person) in.readObject();  
Person two = (Person) in.readObject();  
Person three = (Person) in.readObject();  
System.out.println("name:"+one.name + " age:"+one.age + " height:"+one.height);  
System.out.println("name:"+two.name + " age:"+two.age + " height:"+two.height);  
System.out.println("name:"+three.name + " age:"+three.age + " height:"+three.height);  
} catch (Exception e) {  
e.printStackTrace();  
}  
}  
}  

输出结果如下:

name:zhangsan age:30 height:170  
name:zhangsan age:35 height:175  
name:zhangsan age:28 height:178  

从添加的代码我们可以看到进行反序列化也很简单,主要用到的流是FileInputstream和ObjectInputstream正好与存储时用到的流相对应。另外从结果顺序我们可以看到反序列化后得到对象的顺序与序列化时的顺序一致。
4、总结

进行对象序列化主要目的是为了保存对象的状态(成员变量)。

进行序列化主要用到的流是FileOutputStream和ObjectOutputStream。FileOutputStream主要用于连接磁盘文件,并把字节写出到该磁盘文件;ObjectOutputStream主要用于将对象写出为可转化为字节的数据。

要将某类的对象序列化,则该类必须实现Serializable接口,该接口仅是一个标志,告诉JVM该类的对象可以被序列化。如果某类未实现Serializable接口,则该类对象不能实现序列化。

保存状态的目的就是为了在未来的某个时候再恢复保存的内容,这可以通过反序列化来实现。对象的反序列化过程与序列化正好相反,主要用到的两个流是FileInputstream和ObjectInputStream。

反序列化后得到的对象的顺序与保存时的顺序一致。

5、补充

补充一:上面我们举得例子很简单,要保存的成员变量要么是基本类型的要么是String类型的。但有时成员变量有可能是引用类型的,这是的情况会复杂一点。那就是当要对某对象进行序列化时,该对象中的引用变量所引用的对象也会被同时序列化,并且该对象中如果也有引用变量的话则该对象也将被序列化。总结说来就是在序列化的时候,对象中的所有引用变量所对应的对象将会被同时序列化。这意味着,引用变量类型也都要实现Serializable接口。当然其他对象的序列化都是自动进行的。所以我们只要保证里面的引用类型是都实现Serializable接口就行了,如果没有的话,会在编译时抛出异常。如果序列化的对象中包含没有实现Serializable的成员变量的话,这时可以使用transient关键字,让序列化的时候跳过该成员变量。使用关键字transient可以让你在序列化的时候自动跳过transient所修饰的成员变量,在反序列化时这些变量会恢复到默认值。

补充二:如果某类实现了Serializable接口的话,其子类会自动编程可序列化的,这个好理解,继承嘛。

补充三:在反序列化的时候,并不会调用对象的构造器,这也好理解,如果调用了构造器的话,对象的状态不就又重新初始化了吗。

补充四:我们说到对象序列化的是为了保存对象的状态,即对象的成员变量,所以静态变量不会被序列化。


http://chatgpt.dhexx.cn/article/XoqAUUUx.shtml

相关文章

c# Topshelf创建linux与Windows服务

目录 Topshelf安装Topshelf包代码如下,简单粗暴卸载服务安装服务 Topshelf 讨厌创建.net 服务时的窗体怎么办?讨厌调试.net服务怎么办?调试.net服务还要自己建控制台怎么办? Topshelf 它来了!!! Topshelf 是一个开源…

htop与top命令

安装htop yum -y install htop htop 类似于 top 命令,但可以让你在垂直和水平方向上滚动,所以你可以看到系统上运行的所有进程,以及他们完整的命令行。可以不用输入进程的 PID 就可以对此进程进行相关的操作 (killing, renicing)。 与 Lin…

使用Quartz.net实现多线程任务定时执行,动态配置Job,结合Topshelf构建Windows服务

几个月前有这么个需求:需要执行一些Job,这些Job会各自按照不同的时间频次执行,且它们做的事情也不同,有的是监控站点,有的是监控服务器存储情况,有的是监控报表PROCEDURE的执行状况… OK,当看到…

top命令参数详解

简介 top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。 top显示系统当前的进程和其他状况,是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如果在前台执行该命令,它将独占前台,直…

top命令参数详解(linux top命令的用法详细详解)

通过top命令可以有效的发现系统的缺陷出在哪里。是内存不够、CPU处理能力不够、IO读写过高。 top命令输出长这样: top命令参数详解(linux top命令的用法详细详解) 以下解析一下各个字段的意思: VIRT:virtual memory …

Topas命令详解

执行topas命令后如图所示: #topas 操作系统的最全面动态,而又查看方便的性能视图就是topas命令了,下面以topas输出为例,对AIX系统的性能监控做简要描述,供运维工程师和系统管理员们参考。 另:1.操作系统报…

Topshelf 打包部署Windows服务

1 创建项目(例:控制台程序) Nuget 引入Topshelf类库 using System; using System.Threading; using System.Threading.Tasks; using Topshelf;namespace LoginTypeInherit {public class Program{private static readonly log4net.ILog log log4net.LogManager.G…

Linux top命令参数详解

Linux top命令参数详解 生产环境系统运行慢,出现无法响应通常原因主要还在于分析CPU、内存、磁盘使用率情况,并结合命令查找出具体进程,并在进程中进一步分析主要因子情况,渗透到对于其中包含线程占用情况的分析。一般而言对于ja…

C#之TopShelf启动Windows服务

写了一两天,才发现组长给的原始代码原本就有Topshelf,还是写出来提示我topshelf不明确哪个版本的使用,莫名尴尬。 1、项目的主要运行代码 HostFactory.Run(x >{x.RunAsLocalSystem();x.SetDescription("topshelf测试");x.SetDi…

Linux下top命令用法详解

一、命令介绍 Linux top命令用于实时显示 process (进程)的动态。它用于监控正在运行系统负荷的信息,包括系统负载、CPU利用分布情况、内存使用、每个进程的资源占用情况等。 使用权限:所有使用者 二、命令详解 在命令行下输入…

【Linux】Top命令参数解释

TOP命令 这是一个Linux系统下 top 命令所输出的进程监控信息。以下是各列含义: top - 09:52:15:当前时间。 up 27 min:系统已经运行的时长。 2 users:当前有2个用户登录到系统上。 load average: 0.97, 0.41, 0.21:系…

Topshelf的使用

一、简介 Topshelf可用于创建和管理Windows服务。其优势在于不需要创建windows服务,创建控制台程序就可以。便于调试。 二、官方地址: 1、官网:http://topshelf-project.com/ 2、官方文档:https://topshelf.readthedocs.io/en/lat…

topshelf

topshelf和quartz topshelf和quartz内部分享 阅读目录: 介绍基础用法调试及安装可选配置多实例支持及相关资料quartz.net 上月在公司内部的一次分享,现把PPT及部分交流内容整理成博客。 介绍 topshelf是创建windows服务的一种方式,相比原生实现ServiceBa…

使用Quartz.net + Topshelf完成服务调用

概述: Quartz.NET 是一个开源作业调度库,可用于在 .NET 应用程序中调度和管理作业。它提供了一个灵活而强大的框架,用于调度作业在特定的日期和时间或以固定的时间间隔运行,并且还支持复杂的调度场景,例如 cron 表达式…

Linux中top命令参数详解

top命令用法 top命令经常用来监控linux的系统状况,是常用的性能分析工具,能够实时显示系统中各个进程的资源占用情况。 top的使用方式 top [-d number] | top [-bnp] 参数解释: -d:number代表秒数,表示top命令显示…

linux 命令:top 详解

注:以下文档根据2019年10月的官方文档翻译。 名称:top - 展示linux进程信息 用法:top -hv|-bcEHiOSs1 -d secs -n max -u|U user -p pid -o fld -w [cols] 在top进程运行过程中,两个最重要的功能是查看帮助(h 或 &…

Topshelf 搭建 Windows 服务

C# Topshelf 搭建 Windows 服务 Topshelf 是一个用来部署基于.NET Framework 开发的服务的框架。简化服务创建于部署过程,并且支持控制台应用程序部署为服务。本文基于 .net core 控制台应用程序部署为服务(.net Framework 可用)。 第一步&…

TOP命令详解

TOP命令详解 top命令经常用来监控linux的系统状况,是常用的性能分析工具,能够实时显示系统中各个进程的资源占用情况。 top的使用方式 top [-d number] | top [-bnp]参数解释: -d:number代表秒数,表示top命令显示的页…

【转】Topshelf入门

windows服务用处多多啊,wcf宿主服务、webapi宿主服务、定时任务等都会用到,最近写的一套呼叫中心,也最终要做成windows服务,以前都是用vs里面自带的windows服务来写,但感觉总是不够方便,最近了解到了topshe…

使用Topshelf创建Windows服务

概述 Topshelf是创建Windows服务的另一种方法,老外的一篇文章Create a .NET Windows Service in 5 steps with Topshelf通过5个步骤详细的介绍使用使用Topshelf创建Windows 服务。Topshelf是一个开源的跨平台的宿主服务框架,支持Windows和Mono&#xff0…