Java中6种创建对象的方式

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

1// 无参构造 2Test test1 = new Test(); 3// 有参构造 4Test 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// 1、类名.class 2Test.class 3 4// 2、对象名.getClass() 5test.getClass() 6 7// 3、Class.forName(全限定类名) 8Class.forName("com.project.obj.Test"); 9

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

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

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

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

1// 获取所有构造函数列表 2Constructor<?>[] constructors = Test.class.getDeclaredConstructors(); 3Test test5 = (Test)constructors[0].newInstance(); 4Test test6 = (Test)constructors[1].newInstance("小明",18); 5 6// 指定构造函数 7Constructor constructor = Test.class.getDeclaredConstructors(String.class, Integer.class); 8Test test7 = (Test)constructor.newInstance("小明",18);

3、克隆出一个对象

值类型:(值传递)

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

引用类型:(引用传递)

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

1、首先定义两个类:

1// 学生的所学专业 2public class Major { 3 private String majorName; // 专业名称 4 private long majorId; // 专业代号 5 6 // ... 其他省略 ... 7}
1// 学生 2public class Student { 3 private String name; // 姓名 4 private int age; // 年龄 5 private Major major; // 所学专业 6 7 // ... 其他省略 ... 8}

2、赋值

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

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

1public class Student implements Cloneable { 2 3 private String name; // 姓名 4 private int age; // 年龄 5 private Major major; // 所学专业 6 7 @Override 8 public Object clone() throws CloneNotSupportedException { 9 return super.clone(); 10 } 11 12 // ... 其他省略 ... 13 14}

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

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

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

1public class Major implements Cloneable { 2 3 @Override 4 protected Object clone() throws CloneNotSupportedException { 5 return super.clone(); 6 } 7 8 // ... 其他省略 ... 9}

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

1public class Student implements Cloneable { 2 3 @Override 4 public Object clone() throws CloneNotSupportedException { 5 Student student = (Student) super.clone(); 6 student.major = (Major) major.clone(); // 重要!!! 7 return student; 8 } 9 10 // ... 其他省略 ... 11}

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

1public class Student implements Serializable { 2 3 private String name; // 姓名 4 private int age; // 年龄 5 private Major major; // 所学专业 6 7 public Student clone() { 8 try { 9 // 将对象本身序列化到字节流 10 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 11 ObjectOutputStream objectOutputStream = 12 new ObjectOutputStream( byteArrayOutputStream ); 13 objectOutputStream.writeObject( this ); 14 15 // 再将字节流通过反序列化方式得到对象副本 16 ObjectInputStream objectInputStream = 17 new ObjectInputStream( new ByteArrayInputStream( byteArrayOutputStream.toByteArray() ) ); 18 return (Student) objectInputStream.readObject(); 19 20 } catch (IOException e) { 21 e.printStackTrace(); 22 } catch (ClassNotFoundException e) { 23 e.printStackTrace(); 24 } 25 26 return null; 27 } 28 29 // ... 其他省略 ... 30}

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

1public class Major implements Serializable { 2 3 // ... 其他省略 ... 4 5}

4、反序列出一个对象

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

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

1public class Student implements Serializable { 2 3 private String name; 4 private Integer age; 5 private Integer score; 6 7 @Override 8 public String toString() { 9 return "Student:" + '\n' + 10 "name = " + this.name + '\n' + 11 "age = " + this.age + '\n' + 12 "score = " + this.score + '\n' 13 ; 14 } 15 16 // ... 其他省略 ... 17}
1// 序列化 2public static void serialize( ) throws IOException { 3 4 Student student = new Student(); 5 student.setName("xiaoming"); 6 student.setAge( 18 ); 7 student.setScore( 1000 ); 8 9 ObjectOutputStream objectOutputStream = 10 new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) ); 11 objectOutputStream.writeObject( student ); 12 objectOutputStream.close(); 13 14 System.out.println("序列化成功!已经生成student.txt文件"); 15 System.out.println("=============================================="); 16}
1// 反序列化 2public static void deserialize( ) throws IOException, ClassNotFoundException { 3 ObjectInputStream objectInputStream = 4 new ObjectInputStream( new FileInputStream( new File("student.txt") ) ); 5 Student student = (Student) objectInputStream.readObject(); 6 objectInputStream.close(); 7 8 System.out.println("反序列化结果为:"); 9 System.out.println( student ); 10}

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

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

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

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

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

1private void readObject( ObjectInputStream objectInputStream ) throws IOException, ClassNotFoundException { 2 3 // 调用默认的反序列化函数 4 objectInputStream.defaultReadObject(); 5 6 // 手工检查反序列化后学生成绩的有效性,若发现有问题,即终止操作! 7 if( 0 > score || 100 < score ) { 8 throw new IllegalArgumentException("学生分数只能在0到100之间!"); 9 } 10}

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

1// 使用静态内部类创建单例 2public class Singleton implements Serializable{ 3 private static final long seriaVersionUID = -1576643344804979563L; 4 private Singleton(){ 5 } 6 7 // 静态内部类 8 private static class SingletonHolder{ 9 private static final Singleton singleton = new Singleton(); 10 } 11 12 public static synchronized Singleton getSingleton(){ 13 return SingletonHolder.singleton; 14 } 15}
1public class Test2 { 2 3 public static void main(String[] args) throws IOException, ClassNotFoundException { 4 5 ObjectOutputStream objectOutputStream = 6 new ObjectOutputStream( 7 new FileOutputStream( new File("singleton.txt") ) 8 ); 9 // 将单例对象先序列化到文本文件singleton.txt中 10 objectOutputStream.writeObject( Singleton.getSingleton() ); 11 objectOutputStream.close(); 12 13 ObjectInputStream objectInputStream = 14 new ObjectInputStream( 15 new FileInputStream( new File("singleton.txt") ) 16 ); 17 // 将文本文件singleton.txt中的对象反序列化为singleton1 18 Singleton singleton1 = (Singleton) objectInputStream.readObject(); 19 objectInputStream.close(); 20 21 Singleton singleton2 = Singleton.getSingleton(); 22 23 // 运行结果竟打印 false ! 24 System.out.println( singleton1 == singleton2 ); 25 } 26}

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

1public class Singleton implements Serializable{ 2 private static final long seriaVersionUID = -1576643344804979563L; 3 private Singleton(){ 4 } 5 6 private static class SingletonHolder{ 7 private static final Singleton singleton = new Singleton(); 8 } 9 10 public static synchronized Singleton getSingleton(){ 11 return SingletonHolder.singleton; 12 } 13 14 private Object readResolve(){ 15 return SingletonHolder.singleton; 16 } 17}

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

5、Unsafe

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

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

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

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

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

6、对象的隐式创建场景

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

2、字符串隐式对象创建

1// 定义一个String类型变量,会创建一个新的String对象 2String name = "小明"; 3 4// 字符串拼接,会创建新对象 5String str = str1 + str2;

3、自动装箱机制

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

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

1public double avg( int... nums ) { 2 double sum = 0; 3 int length = nums.length; 4 for (int i = 0; i<length; ++i) { 5 sum += nums[i]; 6 } 7 return sum/length; 8} 9 10// 传参过程中,会隐式地产生一个对应的数组对象进行计算 11avg( 2, 2, 4 ); 12avg( 2, 2, 4, 4 ); 13avg( 2, 2, 4, 4, 5, 6 );
点赞
收藏

评论区

加载中...

相关推荐

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

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

java 复制Map对象(深拷贝与浅拷贝)

java复制Map对象(深拷贝与浅拷贝)CreationTime2018年6月4日10点00分Author:Marydon1.深拷贝与浅拷贝  浅拷贝:只复制对象的引用,两个引用仍然指向同一个对象

java常用类(2)

三、时间处理相关类Date类:计算机世界把1970年1月1号定为基准时间,每个度量单位是毫秒(1秒的千分之一),用long类型的变量表示时间。Date分配Date对象并初始化对象,以表示自从标准基准时间(称为“历元”(epoch),即1970年1月1日08:00:00GMT)以来的指定毫秒数。示例:packagecn.tanjian

java虚拟机(三)

普通对象的创建(不包括数组和class对象):当虚拟机遇到new指令时,会在常量池中检查是否包含这个类的符号引用(全限定名),通过这个确定是否经过类加载的过程,如果true,为该对象分配内存,对象大小在类加载过程就已经确定。如果false,需要进行类加载。分配内存1、分配内存的方式:指针碰撞:如果内存