Java程序员找对象攻虐

嗯?

在后台经常会收到这样一类私信,大致是这样描述的:

image

看来关于「程序员找对象」这个话题,非常有必要用一篇文章来专门梳理和归纳一下了。

image

择日不如撞日,今天就把这件事情给安排了吧。

可以说,方法多得很!


new一个对象

用关键字new进行对象的创建,几乎是写代码时最常用的操作之一了,比如:

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

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

表面上看,简简单单new一下对象就有了,但面试时如果仅仅答到这一层,大概率会扑街,因为比这个更重要的是new对象时的原理和流程,因为JVM这个牵线红娘在背后默默地帮我们做了很多工作。

说到new一个对象的具体流程,用一张图可大致描述成如下所示:

image
  1. 首先,当我们new一个对象时,比如Sheep sheep = new Sheep()JVM首先就回去检查Sheep这个符号引用所代表的类是否已经被加载过,如果没有就要执行对应类的加载过程;
  2. 声明类型引用很简单,比如Sheep sheep = new Sheep()就会声明一个Sheep类型的引用sheep
  3. 第一步类加载完成以后,对象所需的内存大小其实就已经确定下来了,接下来JVM就会在堆上为对象分配内存;
  4. 所谓的属性“0”值初始化非常好理解,即为实例化对象的各个属性赋上默认初始化“0”值,比如int的初始化0值就是0,而一个对象的初始化0值就是null;
  5. 接下来JVM会进行对象头的设置,这里面就主要包括对象的运行时数据(比如Hash码、分代年龄、锁状态标志、锁指针、偏向线程ID、偏向时间戳等)以及类型指针(JVM通过该类型指针来确定该对象是哪个类的实例);
  6. 属性的显示初始化也好理解,比如定义一个类的时候,针对某个属性字段手动的赋值,如:private String name = "codesheep"; 就在这时候给初始化上;
  7. 最后是调用类的构造方法来进行进行构造方法内描述的初始化动作。

应该说,经过了这一系列步骤,一个新的可用对象方才得以诞生。


反射出一个对象

学过Java反射机制的都知道,只要能拿到类的Class对象,就可以通过强大的反射机制来创造出实例对象了。

拿到Class对象有三种方式:

  • 类名.class
  • 对象名.getClass()
  • Class.forName(全限定类名)

有了Class对象之后,接下来就可以调用其newInstance()方法来创建一个对象,就像这样:

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

当然,这种方式的局限性也有目共睹,因为使用的是类的无参构造方法来创建的对象。

所以比这个更进一步的方式是通过java.lang.relect.Constructor这个类的newInstance()方法来创建对象,因为它可以明确指定某个构造器来创建对象。

比如,在我们拿到了类的Class对象后,就可以通过getDeclaredConstructors()函数来获取到类的所有构造函数列表,这样我们就可以调用对应的构造函数来创建对象了,就像这样:

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

而且,如果我们想明确获取类的某个构造函数,也可以在getDeclaredConstructors()函数里直接指定构造函数传参类型来精确控制,就像这样:

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

克隆出一个对象

对象克隆在我们日常写代码的时候基本上是刚性需求,基于一个对象克隆出另一个对象,这也是写Java代码时十分常见的操作。

关于对象拷贝这一知识点,之前我已经写过了,详细梳理过一篇:《一个工作三年的同事,居然还搞不清深拷贝、浅拷贝...》,里面详细梳理了对象赋值、拷贝、深拷贝、浅拷贝等系列知识点,本文便不再赘述了。


反序列化出一个对象

关于对象「序列化和反序列化」这个知识点,重要且有用,但听很多朋友反映初学时有点糊。当我们作序列化和反序列化操作时,背后也会创建对象,关于「序列化和反序列化」这个知识点的详细理解+梳理,之前我也写过了,链接在此:序列化/反序列化,我忍你很久了,淦!


Unsafe黑魔法

Unsafe类这个名字一听就有点悬了,的确,我们平时的业务代码里接触得好像并不多。

我们都知道写Java代码,很少会去操作位于底层的一些资源,比如内存等这些。而位于sun.misc.Unsafe包路径下的Unsafe类提供了一种直接访问系统资源的途径和方法,可以进行一些底层的操作。比如借助Unsafe我们就可以分配内存、创建对象、释放内存、定位对象某个字段的内存位置甚至并修改它等等。

可见这玩意误用时的破坏力是很大的,所以一般也都是受控使用的。业务代码里很少能看到它的身影,但是JDK内部的一些诸如ioniojuc等包中的代码里还是有不少关于它的身影存在的。

Unsafe类中有一个allocateInstance()方法,通过其就可以创建一个对象。为此我们只需要获取到一个Unsafe类的实例对象,我们自然就可以调用allocateInstance()来创建对象了。

那如何才能获取到一个Unsafe类的实例对象呢?

大致瞅一眼Unsafe类的源码我们就会发现,它是一个单例类,其构造方法是私有的,所以直接构造是不太现实了:

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;
        }
    }
    
    // ... 省略 ...
    
}

而且获取单例对象的入口函数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实例对象,很明显这一过程是JVM偷偷地背着我们干的。

字符串隐式对象创建

典型的,比如定义一个String类型的字面变量时,就可能会引起一个新的String对象的创建,就像这样:

String name = "codesheep";

还常见的比如String+号连接符也会隐式地导致新String对象的创建等:

String str = str1 + str2;

自动装箱机制

这种例子也有很多,比如在执行类似如下代码时:

Integer codeSheepAge = 18;

其触发的自动装箱机制就会导致一个新的包装类型的对象在后台被隐式地创建出来。

函数可变参数

比如像下面这样,当我们使用可变参数语法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 );

而背地里可能会隐式地产生一个对应的数组对象进行计算。


总而总之,很多场景下对象的隐式创建也是数见不鲜,我们最起码要做到心中大致有数。


后 记

所以看完文章,再回到文章开头提到的问题,你还觉得Java程序员搞对象是件难事吗?这么多花里胡哨的对象生成法还不够你用的么。

image

咳咳,玩笑归玩笑,这其实是面试时最常问到的基础问题之一。有时候面试官冷不丁问一句:“在Java里,你有哪些方式可以创建一个对象呢?”。所以针对该问题,这篇来好好梳理和归纳一下。

好啦,一个小小的对象创建就能扯出这么多的花样,好在经过一番梳理和总结,也更便于掌握和理解了。

就这样吧,下篇见。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容