一天中的好时光!
这篇文章是在另一位作者发表在
“您可能不了解Java的事情”一书之后撰写的,我将其归类为“面向初学者”。 通过阅读和评论它,我意识到我学到了很多相当有趣的东西,已经用Java编程了一年多。 也许这些事情对其他人似乎很好奇。
从我的角度来看,这一事实可能对初学者有用,因此我删除了“破坏者”。 对于一些经验丰富的人来说,有些事情可能仍然很有趣。 例如,我自己直到编写Boolean.hashCode(true)== 1231和Boolean.hashCode(false)== 1237时才知道。
对于初学者- Boolean.hashCode(true)== 1231
- Boolean.hashCode(false)== 1237
- Float.hashCode(值)== Float.floatToIntBits(值)
- Double.hashCode(值)-第一个和第二个32位半字的Double(双精度)Double.doubleToLongBits(值)
Object.hashCode()不再是内存中对象的地址
免责声明:这是Oracle(HotSpot)的jvm详细信息。
很久以前就是这样。从jdk1.2.1 / docs / api / java / lang / Object.html#hashCode():
在合理可行的范围内,由Object类定义的hashCode方法确实为不同的对象返回不同的整数。 (通常通过将对象的内部地址转换为整数来实现,但是JavaTM编程语言不需要此实现技术。)
然后他们拒绝了。 这就是
javadoc对jdk 12的描述 。
vladimir_dolzhenko建议可以使用-XX恢复旧行为:hashCode = 4。 行为更改本身几乎来自Java 1.2版本。
Integer.valueOf(15)== Integer.valueOf(15); Integer.valueOf(128)!= Integer.valueOf(128)
免责声明:这是
jls的一部分。
显然,将两个包装器与==(!=)运算符进行比较时,不会发生自动装箱。 一般而言,这是混淆的第一个平等。 事实是对于整数值i:-129 <i <128整数包装器对象被缓存。 因此,对于此范围内的i,Integer.valueOf(i)不会每次都创建一个新对象,而是返回一个已经创建的对象。 对于不属于此范围的i,Integer.valueOf(i)始终创建一个新对象。 因此,如果您不密切监视精确比较的内容以及比较的精确程度,则可以编写似乎有效并且甚至包含测试的代码,但同时包含此类“耙”。
在Oracle(HotSpot)的jvm中,可以通过属性
“ java.lang.Integer.IntegerCache.high”更改缓存的上限。
在某些情况下,另一个类的原始或字符串最终静态字段的值在编译时解析
这听起来令人困惑,并且声明有点长。 意思是这个。 如果我们有一个类将原始类型或字符串的常量定义为具有立即初始化的最终静态字段,
class AnotherClass { public final static String CASE_1 = "case_1"; public final static String CASE_2 = "case_2"; }
当在其他类中使用时,
class TheClass {
这些常量的值(“ case_1”,“ case_2”)在编译时解析。 它们作为值而不是链接插入到代码中。 也就是说,如果我们从库中使用了此类常量,然后获得了其中常量值已更改的库的新版本,则应重新编译项目。 否则,旧的常量值可能会继续在代码中使用。
在必须使用常量表达式(例如,switch / case)的所有位置都可以观察到此行为,或者允许编译器将表达式转换为常量,并且他可以执行此操作。
一旦通过将初始化转移到静态块中删除了立即初始化,就不能在常量表达式中使用这些字段。
对于初学者在某些情况下,垃圾收集器可能永远不会运行。
结果,将不会启动finalize()。 因此,您不应编写依赖finalize()始终有效的事实的代码。 是的,如果对象在程序结束之前进入垃圾箱,则很可能收集器不会收集该对象。
特定对象的finalize()方法只能被调用一次。
在finalize()中,我们可以使对象再次可见,并且垃圾收集器这次不会“删除”该对象。 当该对象再次掉入垃圾桶时,它将被“编译”,而无需调用finalize()。 如果在finalize()中引发了异常,并且该对象仍然对任何人都不可见,则它将被“汇编”。 Finalize()将不再被调用。
事先不知道将在其中调用finalize()的流
仅保证该线程没有主程序可见的锁。
对象上存在覆盖的finalize()方法会减慢垃圾收集过程
表面上是需要仔细检查对象的可用性-在调用finalize()之前一次,在以下垃圾回收之一中运行一次。
最终确定()时,很难与僵局作斗争
在非平凡的finalize()中,可能有必要使用锁,鉴于上述特定情况,锁很难调试。
自Java版本9以来,Object.finalize()已被标记为已弃用!
考虑到上述细节,这不足为奇。 经典的懒惰单例初始化:需要双重锁定
在此主题上存在一个误解,即看起来很合逻辑的以下方法(双重检查惯用语)总是可行的:
public class UnsafeDCLFactory { private Singleton instance; public Singleton get() { if (instance == null) {
我们查看是否创建了对象(读取1,检查1)。 如果是这样,则将其返回。 如果不是,则设置锁,确保未创建对象,创建对象(删除锁),然后返回对象。
由于以下原因,该方法不起作用。
(读取1,检查1)和(读取3)不同步。 根据Java内存模型的概念,在另一个线程中所做的更改可能直到我们同步才对我们的线程可见。 感谢
mk2的评论,这是问题的正确描述:
是的,read1和read3没有同步,但是问题不在另一个线程中。 不同步的读数可以重新排序的事实,即 read1!= null,但read3 == null。 同时,由于“ instance = new Singleton();”,我们可以在对象完全构建之前获得对该对象的引用,这实际上是与另一个线程的同步问题,但不是read1和read3,而是read3和access实例成员。
通过在读取过程中添加同步,或通过标记指向单例链接的变量是易变的,可以对其进行处理。 (使用volatile的解决方案仅适用于Java 5+。在此之前,Java的内存模型在这种情况下具有不确定性。)这是一个可行的版本(具有附加的优化功能-添加了局部变量`res`以减少从volatile字段读取的次数)。
public class SafeLocalDCLFactory { private volatile Singleton instance; public Singleton getInstance() { Singleton res = instance;
该代码是
从此处获取的 ,来自Alexei Shipilev的站点。 可以找到有关此问题的更多详细信息。
“按需初始化持有人惯用语”-非常漂亮的“懒惰”单例初始化
java仅在必要时初始化类(Class对象),当然也仅初始化一次。 您可以利用这一优势! 按需初始化的持有人惯用机制就是这样做的。 (代码
从这里开始 。)
public class Something { private Something() {} private static class LazyHolder { static final Something INSTANCE = new Something(); } public static Something getInstance() { return LazyHolder.INSTANCE; } }
LazyHolder类将仅在首次调用Something.getInstance()时初始化。 Jvm将确保这种情况仅发生一次,而且非常高效-如果该类已经初始化,则不会有开销。 因此,LazyHolder.INSTANCE也将被初始化一次,“惰性”且线程安全。
有关开销的规范如果此初始化过程正常完成,并且Class对象已完全初始化并且可以使用,则不再需要调用初始化过程,并且可以从代码中删除它(例如,通过打补丁或以其他方式重新生成代码) 。
来源 一般来说,单调不被视为最佳实践。
材料尚未结束。 因此,如果需要“伸出手”和已经写过的东西,我将在这个话题上以其他方式写更多。
感谢您的建设性意见。 感谢
sergey-gornostaev ,
vladimir_dolzhenko ,
OlehKurpiak ,
mk2 ,扩大了本文中的几个位置。