英文:
Can I cache getClass.hashCode()?
问题
无论出于何种原因,我在我的抽象类中实现了以下 `hashCode`。
```java
@MappedSuperclass
abstract Some {
@Override
public boolean equals(final Object obj) {
// ...
}
@Override
public int hashCode() {
return getClass().hashCode(); // TODO: cache, maybe?
}
}
- 我可以缓存
getClass().hashCode()
的值吗? - 在 JVM 运行时是否存在值被更改的可能性?
- 这是一种过早优化的行为吗?
<details>
<summary>英文:</summary>
Whatever the reason, I have the following `hashCode` implemented in my abstract class.
@MappedSuperclass
abstract Some {
@Override
public boolean equals(final Object obj) {
// ...
}
@Override
public int hashCode() {
return getClass().hashCode(); // TODO: cache, maybe?
}
}
1. Can I cache the value of the `getClass().hashCode()`?
2. Is there any possibility of the value being changed while running in a JVM?
3. Is this some kind of the premature optimization shit?
</details>
# 答案1
**得分**: 3
1. 我可以缓存`getClass().hashCode()`的值吗?
可以,但是...
2. 在JVM中运行时,该值有可能被更改吗?
没有可能性。Java对象的类不能更改,而Java的`Class`对象的哈希码(它是`getClass()`的结果类型)也不能更改。
3. 这是过早优化吗?
可能是<sup>1</sup>。
然而,从性能的角度来看,使用对象的类的哈希码作为对象的哈希码是一个非常糟糕的主意。
这意味着该类的所有实例(例如,`Some`)将具有相同的哈希码<sup>2</sup>。这将导致大量哈希碰撞,使大多数`HashSet`和`HashMap`操作的时间复杂度变为`O(N)`或`O(logN)`(取决于您的Java版本),而不是`O(1)`。
<sup>1 - 我假设您尚未进行了大量的性能分析,如果您已经进行了分析,那么也许这不是过早的优化。</sup>
<sup>2 - 我假设`Some::hashCode`方法没有被具体子类重写为更明智的内容。</sup>。
<details>
<summary>英文:</summary>
> 1. Can I cache the value of the `getClass().hashCode()`?
You can, but ...
> 2. Is there any possibility of the value being changed while running in a JVM?
No possibility. The class of a Java object cannot change, and the hashcode of a Java `Class` object (which is the result type for `getClass()`) cannot change.
> 3. Is this premature optimization?
Probably yes<sup>1</sup>.
However, using the hashcode of the object's class as the hashcode of an object is **a very bad idea** from a performance perspective.
Doing that means that all instances of the class (e.g. `Some`) will have the same hashcode<sup>2</sup>. That will lead to a bazillion hash collisions, and make most `HashSet` and `HashMap` operations `O(N)` or `O(logN)` (depending on your Java version) rather than `O(1)`.
-------------
<sup>1 - I am assuming you have not done a bunch of performance analysis that you haven't told us about. If you *had* already done the analysis, then maybe this is not a premature optimization.<br>
2 - I am assuming that the `Some::hashCode` method is not overridden to something more sensible by concrete subclasses of `Some`.</sup>
</details>
# 答案2
**得分**: 1
Stephen C的答案是一个很好的答案。然而,为了完整起见,我觉得需要补充一点,如果一个类被不同的类加载器加载,那么对于该类,`getClass().hashCode()`会返回两个不同的值。
为了验证这一点,我编写了一个程序,并使用System类加载器加载了一个类,然后使用自定义的类加载器加载了相同的类。它们都返回不同的`hashCode()`:
首先是我从https://www.digitalocean.com/community/tutorials/java-classloader 复制的自定义类加载器的代码:
```java
package com.journaldev.classloader;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io InputStream;
/**
* 我们的自定义类加载器,用于加载类。com.journaldev包中的任何类都将使用此类加载器加载。
* 对于其他类,它将委托请求给其父类加载器。
*/
public class CCLoader extends ClassLoader {
/**
* 此构造函数用于设置父类加载器
*/
public CCLoader(ClassLoader parent) {
super(parent);
}
/**
* 从文件系统加载类。类文件应该位于文件系统中。名称应该是相对于获取文件位置的名称
*
* @param name 类的完全限定名称,例如,com.journaldev.Foo
*/
private Class getClass(String name) throws ClassNotFoundException {
String file = name.replace('.', File.separatorChar) + ".class";
byte[] b = null;
try {
// 这从文件中加载字节码数据
b = loadClassFileData(file);
// defineClass继承自ClassLoader类
// 它将字节数组转换为类。defineClass是Final的
// 所以我们不能覆盖它
Class c = defineClass(name, b, 0, b.length);
resolveClass(c);
return c;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* 每个对类的请求都经过这个方法。如果类在com.journaldev包中,我们将使用此类加载器,否则将请求委托给父类加载器。
*
* @param name 完整的类名
*/
@Override
public Class loadClass(String name) throws ClassNotFoundException {
if (name.startsWith("com.journaldev.")) {
return getClass(name);
}
return super.loadClass(name);
}
/**
* 读取文件(.class)到一个字节数组中。文件应该作为资源可访问,确保它不在类路径中,以避免任何混淆。
*
* @param name 文件名
* @return 从文件中读取的字节数组
* @throws IOException 如果读取文件时出现异常
*/
private byte[] loadClassFileData(String name) throws IOException {
InputStream stream = getClass().getClassLoader().getResourceAsStream(
name);
int size = stream.available();
byte buff[] = new byte[size];
DataInputStream in = new DataInputStream(stream);
in.readFully(buff);
in.close();
return buff;
}
}
然后是Test
类和我们将用于使用不同类加载器加载Test
类的主方法:
package com.journaldev.test;
import com.journaldev.classloader.CCLoader;
public class Test {
public static void main(String[] args) throws Exception {
System.out.println(new CCLoader(ClassLoader.getSystemClassLoader())
.loadClass(Test.class.getCanonicalName()).hashCode());
System.out.println(ClassLoader.getSystemClassLoader()
.loadClass(Test.class.getCanonicalName()).hashCode());
}
}
输出显示了相同的类使用不同的类加载器加载时的不同哈希码:
1554547125
1072591677
英文:
Stephen C's answer is a good answer. However, for the sake of completeness, I feel the need to add to that, that if a class is loaded by, let's say, two different class loaders, then for that class getClass().hashcode()
will return two different values.
For verifying that, I wrote a program and loaded a class with System class loader, and then with my own custom class loader. Both return different hashcode()
:
First the code for Custom class loader I copied from https://www.digitalocean.com/community/tutorials/java-classloader:
package com.journaldev.classloader;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
/**
* Our Custom ClassLoader to load the classes. Any class in the
* com.journaldev package will be loaded using this ClassLoader.
* For other classes, it will delegate the request to its Parent
* ClassLoader.
*/
public class CCLoader extends ClassLoader {
/**
* This constructor is used to set the parent ClassLoader
*/
public CCLoader(ClassLoader parent) {
super(parent);
}
/**
* Loads the class from the file system. The class file should be located in
* the file system. The name should be relative to get the file location
*
* @param name Fully Classified name of the class, for example, com.journaldev.Foo
*/
private Class getClass(String name) throws ClassNotFoundException {
String file = name.replace('.', File.separatorChar) + ".class";
byte[] b = null;
try {
// This loads the byte code data from the file
b = loadClassFileData(file);
// defineClass is inherited from the ClassLoader class
// that converts byte array into a Class. defineClass is Final
// so we cannot override it
Class c = defineClass(name, b, 0, b.length);
resolveClass(c);
return c;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* Every request for a class passes through this method. If the class is in
* com.journaldev package, we will use this classloader or else delegate the
* request to parent classloader.
*
* @param name Full class name
*/
@Override
public Class loadClass(String name) throws ClassNotFoundException {
if (name.startsWith("com.journaldev.")) {
return getClass(name);
}
return super.loadClass(name);
}
/**
* Reads the file (.class) into a byte array. The file should be
* accessible as a resource and make sure that it's not in Classpath to avoid
* any confusion.
*
* @param name Filename
* @return Byte array read from the file
* @throws IOException if an exception comes in reading the file
*/
private byte[] loadClassFileData(String name) throws IOException {
InputStream stream = getClass().getClassLoader().getResourceAsStream(
name);
int size = stream.available();
byte buff[] = new byte[size];
DataInputStream in = new DataInputStream(stream);
in.readFully(buff);
in.close();
return buff;
}
}
Then the Test
class and main method which we will use for loading the Test
class with different class loaders:
package com.journaldev.test;
import com.journaldev.classloader.CCLoader;
public class Test {
public static void main(String[] args) throws Exception {
System.out.println(new CCLoader(ClassLoader.getSystemClassLoader())
.loadClass(Test.class.getCanonicalName()).hashCode());
System.out.println(ClassLoader.getSystemClassLoader()
.loadClass(Test.class.getCanonicalName()).hashCode());
}
}
Output shows different hashcodes for the same class loaded with different Class Loaders:
1554547125
1072591677
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论