Java 内部是如何处理判断一个对象是不是被实例化的?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 内部是如何处理判断一个对象是不是被实例化的?相关的知识,希望对你有一定的参考价值。

User user = um.showUserById(JSONObject.getNames(uid)[0]);
比如这句话,来源于sina微博API。

我觉得左边user仅仅是一个引用,就像c/c++里int *a一样,仅仅是个(指针)指向,没有实例化一个4字节的int型变量。
但是有人说这在java里叫做声明+实例化,就是说这句话等于先,User user = new User(),再user = um.showUserById(JSONObject.getNames(uid)[0]);。
但我觉得这样的话,内存里会存在2个相同的对象,原对象等号右边的对象,新对象,user对象复制了一份 等号右边的对象。
那么执行了这句话后,内存究竟有几个对象?
如果 最上面的一句话 仅在内存在申请了一个对象的话岂不是节省内存?

没有搞过C语言,但是在Java里实例化是指内存创建了一个类型的实例(给它开一个内存空间),这个过程被叫做实例化,也就是说内存创建了这个类的实例,那就是被实例化了,没有创建就没有被实例化

1. 所以按照这个逻辑来讲,若你这个user == null,那也就是说这个对象没有被实例化

2. 而user其实只是一个引用,也可以叫指针吧,他指向了一个内存中真实的User实例,但是若um.showUserById(JSONObject.getNames(uid)[0])返回的是一个null,那这个user指针只是一个空指针而已,你可以把这个表达式分开写

User user;
um.showUserById(JSONObject.getNames(uid)[0]);

第一行可以叫创建了一个User类实例的指针,名字叫user

第二行创建了一个User的实例,但是由于实例没有被任何一个已经申明的指针引用到,所以第二行代码执行后,虽然在内存中已经创建了一个User的实例,但是由于没有被任何指针引用到,所以这个新建的实例没有用,最后被在这个代码块结束后被GC回收掉内存


这样分开写后,应该可以看到其实User user =um.showUserById(JSONObject.getNames(uid)[0]); 内存只会生成一个实例,而这个实例有指针user指向它而已

参考技术A 首先明确一个概念,Java里只有“值传递”,没有“引用传递”。好吧,我知道很多人听到这个会觉得三观有点动摇,我来解释一下。
Java有一个分裂的类型系统,包括用户自定义类型,也就是大家都熟悉的Object及其各种子类,还有基本类型,也叫“值类型”:包括int、long、char等,以及一个没名字但是一般都被大家称之为reference的类型。Java中不能自定义值类型。
在Java中,无论是参数传递、函数返回值、赋值、以及参与表达式运算的所有东西,只能是值类型。也就是说对于f(x)这样一个函数调用,x是一个类型为reference的值类型,不是对象本身,返回值一样也只能是值类型。同样,a=b+c这样的表达式中,a、b、c也都只能是值类型。
值类型,顾名思义,只能是值传递的,也就是说,形参接受的实参永远是值的拷贝,所以f(x)这样的函数调用实际的含义是,将实参x的值拷贝一份,绑定给函数f定义中声明的形参。
如果之前一段真的让你的三观动摇了,下面我来把它们稳定一下,毕竟,稳定压倒一切,世界不会毁灭,下个月你也不需要那张船票。
如果Java中只有值传递。那么一个对象是怎样传递的呢?还记得我们刚才说过的么,Java中有一个没名字但是被大家称之为reference的值类型,当你写“SomeType obj=...”的时候,obj的类型其实并不是SomeType,而是一个只能兼容SomeType的reference(这里的兼容指obj只能用来引用SomeType及其子类,如果SomeType是一个interface,那么obj只能用来引用实现了SomeType的对象)。
依靠这个reference,Java实现了对象的传递。
如果此时你一恢复了神志,那么我们来解释一下题目中的代码:
User user = um.showUserById(JSONObject.getNames(uid)[0]);
user是一个兼容User类型的reference,“=”是一个赋值操作,这句话的含义就是,把=右边表达式返回的值赋给user这个reference,记住“=”右边返回的不可能是一个对象,只能是一个值,当然在这里这个值是一个reference,它可能指向一个对象,在这种情况下,该语句执行完之后,user和“=”右边返回的reference值相等,也就是说它们指向同一个对象。

Lua 表在内存中是如何处理的?

【中文标题】Lua 表在内存中是如何处理的?【英文标题】:How are Lua tables handled in memory? 【发布时间】:2015-07-07 19:59:01 【问题描述】:

lua 如何处理表的增长?

它相当于Java中的ArrayList吗? IE。一个需要连续内存空间的,当它比已经分配的空间大时,内部数组被复制到另一个内存空间。

有没有聪明的方法来引导它?

我的问题是,表是如何存储在内存中的?我不是问如何在 Lua 中实现数组。

【问题讨论】:

我们在 Lua 中简单地通过使用整数索引表来实现数组。因此,数组没有固定的大小,而是根据需要增长。 lua.org/pil/11.1.html @RobertHarvey 我已经编辑了我的问题,我怀疑 Lua 如何处理这是“记忆视觉”。 “数组”是一个“幕后”术语。 Lua 表是一组键值对,其中键和值都不能是nil。如果表具有连续的正整数键并且从 1 开始,则称该表“具有序列”。长度运算符# 仅适用于序列(或“空序列”),ipairs 在第一个缺失的正整数键之前结束。实现的可能性由此而来。 【参考方案1】:

(假设您指的是最新版本的 Lua;描述 5.3 的行为应该(几乎?)与 5.0-5.2 相同。)

在底层,一个表包含一个数组和一个哈希部分。两者都(独立地)以二次幂的方式增长和缩小,如果不需要,两者都可能不存在。

大多数键值对将存储在哈希部分。但是,所有正整数键(从 1 开始)都是存储在数组部分中的候选者。数组部分只存储值而不存储键(因为它们等价于元素在数组中的位置)。最多允许一半的分配空间为空(即包含nils - 作为间隙或作为尾随空闲槽)。 (会留下太多空槽的数组候选将被放入散列部分。如果数组部分已满但散列部分有剩余空间,则任何条目都将进入散列部分。)

对于数组和哈希部分,插入可以触发调整大小,如果之前已经删除了足够多的条目,则可以调整到下一个较大的 2 次方或下降到任何较小的 2 次方。 (实际上触发缩小大小并非易事:rehash 是唯一调整表格大小的地方(并且两个部分同时调整大小),并且只能从 luaH_newkey 调用 if 两部分中的任何一个都没有足够的空间1。)

更多信息,你可以看The Implementation of Lua 5.0的第4章,或者查看源代码:基本上所有相关的都发生在ltable.c,有趣的阅读起点是rehash (in ltable.c)(调整大小功能),以及主解释器循环 luaV_execute (in lvm.c) 或更具体地说是 luaV_settable (also there)(在表中存储键值对时会发生什么情况)。


1例如,为了缩小包含大数组部分且没有散列的表,您必须清除所有数组条目,然后将条目添加到散列部分(即使用非整数键,值可以是任何包括 nil),最终得到一个不包含数组部分和一个单元素散列部分的表。 如果两个部分都包含条目,则必须首先清除哈希部分,然后向数组部分添加足够的条目以填充数组和哈希的组合(触发调整大小,这将为您留下一个包含大数组部分的表和没有哈希),然后如上所述清除数组。2(首先清除数组,然后哈希将不起作用,因为在清除这两个部分后,您将没有数组和一个巨大的哈希部分,并且您无法触发调整大小,因为任何条目都只会进入散列。)

2实际上, 把桌子扔掉换一张新桌子要容易得多。为了确保缩小表,您需要知道实际分配的容量(不是当前条目数,并且 Lua 不会告诉你,至少不会直接告诉你) ,然后让所有步骤和所有大小都恰到好处 - 混淆步骤的顺序或无法触发调整大小,你最终会得到一个巨大的表,如果你将它用作数组,它甚至可能执行得更慢…(存储在哈希中的候选数组也存储它们的键,例如缓存行中有用数据量的一半。)

【讨论】:

我相信只有插入才能触发调整大小。这就是为什么允许在迭代期间使用next() 清除现有字段,但如果添加字段则未定义。 首先,感谢您的帮助,但对不起,我在 C 语言中很笨,我已经阅读了 Itable.c 文件,但我不明白一些事情:如果密钥散列会发生什么相撞?如果散列部分增长(或缩小),是否将其复制到内存的另一部分(以保持连续分配)?我找不到关于那个的详细解释 @JohnnyWiller:散列在实施文件的第 4 章(见第 8 页,该章的最后一段)中(简要)解释,更多信息可以在the paper referenced there 中找到。至于调整大小:您是否阅读了实施文件?那里有描述(第 7 页,第 2 段)——是复制的,是连续的。 @siffiejoe 对,完全跳过了这部分的思考——感谢您的关注!现在应该修复,经过更多的阅读和测试,我很确定我现在所描述的确实是发生了什么。【参考方案2】:

从 Lua 5.0 开始,表是哈希表和数组的混合体。来自The Implementation of Lua 5.0:

用于优化用作数组的表的新算法:

与其他脚本语言不同, Lua 不提供数组类型。相反,Lua 程序员使用 具有整数索引的常规表来实现数组。 Lua 5.0 使用了一个新的 检测表是否被用作数组并自动检测的算法 将与数字索引关联的值存储在实际数组中, 而不是将它们添加到哈希表中。该算法在 第 4 节。

以前的版本只有哈希表。

【讨论】:

@JohnnyWiller 怎么样? GC 会扫描表的数组部分和哈希部分。 @ColonelThirtyTwo 是的,我知道。我在问表哈希部分是否增长,是否将其复制到内存的另一部分,以保持连续分配(就像 java 一样)? @JohnnyWiller After computing the new sizes, Lua creates the new parts and re-inserts the elements from the old parts into the new ones 所以我猜它们在增长后仍然是连续的。阅读我链接的文件的第 4 节,他们解释得比我好:-)

以上是关于Java 内部是如何处理判断一个对象是不是被实例化的?的主要内容,如果未能解决你的问题,请参考以下文章

Java虚拟机是如何处理异常的?

Java虚拟机是如何处理异常的?

面试官:tomcat是如何处理http请求的?

Simple JavaJava是如何处理别名(aliasing)的

VB.NET 表单变量是如何处理的

分析类加载的链接阶段-准备 属性是如何处理的