个人记录 转自r2coding

new一个对象

  1. Sheep sheep1 = new Sheep();
  2. Sheep sheep2 = new Sheep( "codesheep", 18, 65.0f );

通过new的方式,我们可以调用类的无参或者有参构造方法来实例化出一个对象

其中的过程:

  1. 类加载 - 》new一个对象时,查看该对象的引用所代表的类是否已经被加载过了
  2. 声明类引用 -》声明对象的类型,Sheep 类型的 sheep1 的引用
  3. 堆内存分配 - 》jvm分配内存
  4. 属性默认值初始化 - 》实例化对象的各个属性值为默认值,例如 int 是0 ,对象就是null
  5. 对象头设置 - 》jvm进行对象头设置,这里面就主要包括对象的运行时数据(比如Hash码、分代年龄、锁状态标志、锁指针、偏向线程ID、偏向时间戳等)以及类型指针(JVM通过该类型指针来确定该对象是哪个类的实例);
  6. 属性显式初始化 -》属性的显式初始化也好理解,比如定义一个类的时候,针对某个属性字段手动的赋值,如:private String name = “codesheep”; 就在这时候给初始化上;
  7. 构造方法初始化 -》调用构造

    反射一个对象

    拿到class类的方式

  8. 类.class

  9. 对象名.getClass();
  10. class.forName(“全类名”);

使用class类的 newInstance( ) 方法进行获取对象
构造无参的对象

Sheep sheep3 = (Sheep) Class.forName( "cn.codesheep.article.obj.Sheep" ).newInstance();
Sheep sheep4 = Sheep.class.newInstance();

构造有参的对象

Constructor<?>[] constructors = Sheep.class.getDeclaredConstructors();
Sheep sheep5 = (Sheep) constructors[0].newInstance(); 
Sheep sheep6 = (Sheep) constructors[1].newInstance( "codesheep", 18, 65.1f );

指定构造参数类型

Constructor constructor = Sheep.class.getDeclaredConstructor( String.class, Integer.class, Float.class );
Sheep sheep7 = (Sheep) constructor.newInstance( "codesheep", 18, 65.2f );

克隆出一个对象

利用Object的clone方法进行对象克隆
涉及到浅拷贝深拷贝的问题

值类型和引用类型

Java中,像数组、类Class、枚举Enum、Integer包装类等等,就是引用类型;
诸如int这些基本类型,称它为值类型;

java中只有值传递

值传递是传递实参副本,函数修改不会影响实参
引用传递是传递实参地址,函数修改会影响实参。
对于对象作为实参传入后,对象只是它的属性被修改,对象的地址其实没有改变,对于函数来说,对象的实参是这个地址

赋值 vs 浅拷贝 vs 深拷贝

赋值:

Student codeSheep = new Student();
Student codePig = codeSheep;

这种不是对象拷贝,因为拷贝的仅仅只是引用关系,并没有生成新的实际对象:
Java创建对象 - 图1

浅拷贝

浅拷贝属于对象克隆方式的一种,重要的特性体现在这个 「浅」 字上。
比如我们试图通过studen1实例,拷贝得到student2,如果是浅拷贝这种方式,大致模型可以示意成如下所示的样子:
Java创建对象 - 图2
值类型的字段会复制一份,引用类型的字段拷贝的仅仅是引用地址

String 存在于堆内存、常量池;这种比较特殊, 本身没有实现 Cloneable, 传递是引用地址;
由本身的final性, 每次赋值都是一个新的引用地址,原对象的引用和副本的引用互不影响。
因此String就和基本数据类型一样,表现出了”深拷贝”特性.

深拷贝

深拷贝相较于上面所示的浅拷贝,除了值类型字段会复制一份,引用类型字段所指向的对象,会在内存中也创建一个副本,就像这个样子:
Java创建对象 - 图3


反序列化出一个对象

Unsafe黑魔法

sun.misc.Unsafe包路径下的Unsafe类提供了一种直接访问系统资源的途径和方法,可进行底层的操作。
分配内存、创建对象、释放内存、定位对象某个字段的内存位置并修改等等
Unsafe类中allocateInstance()方法,可以创建一个对象。
获取Unsafe类的实例对象,调用allocateInstance()来创建对象

public final class Unsafe {

    private static final Unsafe theUnsafe;

    // ... 省略 ...

    private static native void registerNatives();

    private Unsafe() {
    }

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }

    // ... 省略 ...

}

可以看到Unsafe是单例的,无法通过构造来new,且getUnsafe()上也做了特殊标记,只能从引导加载的类才可以调用,该方法是供JVM内部使用的,外部代码直接使用会报异常

Exception in thread "main" java.lang.SecurityException: Unsafe

可以通过反射来创建Unsafe实例

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

接下来我们就可以愉快地利用它来创建对象了

Sheep sheep8 = (Sheep) unsafe.allocateInstance( Sheep.class );

对象的隐式创建场景

除了几种显式地对象创建之外,还有没有进行手动对象创建的隐式场景,举例:

Class类实例隐式创建

JVM虚拟机在加载一个类时,也会创建该类对应的一个class实例

字符串隐式创建

String string ="";
或者
String str = str1+str2;

自动装箱机制

Integer codeSheepAge = 18; //int类型自动装箱,Integer被隐式的创建了出来

函数可变参数

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 );
// 隐式地产生一个对应的数组对象进行计算。