1、使用关键字new创建对象
// 无参构造
Test test1 = new Test();
// 有参构造
Test test2 = new Test("小明", 18);
new对象过程中,底层发生了什么?
类加载 JVM检查先是否已经加载,没有则执行类加载过程
声明类型引用 声明一个Test类型的引用test
堆内存分配 类加载步骤中已确定对象所需内存大小,JVM在堆上为对象分配内存
属性“0”值初始化 int初始化0值是0,float初始化0值是0.0等,对象初始化0值是null
对象头设置 包括对象运行时数据(Hash码、分代年龄、锁状态标志、锁指针、偏向线程ID、偏向时间戳)以及类型指针
属性显示初始化 例如:private int age = 18; age属性的显示初始化
构造方法初始化 调用类的构造方法进行方法内描述的初始化动作
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接口的作用: 进入源码,发现是一个空接口,不包含任何方法 不实现Serializable接口,抛出异常: 由源码一直跟到ObjectOutputStream的writeObject0()方法底层: 如果一个对象既不是字符串、数组、枚举,而且也没有实现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 );