可以缓存 getClass.hashCode() 吗?

huangapple go评论71阅读模式
英文:

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?
    }
}
  1. 我可以缓存 getClass().hashCode() 的值吗?
  2. 在 JVM 运行时是否存在值被更改的可能性?
  3. 这是一种过早优化的行为吗?

<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>

&gt; 1. Can I cache the value of the `getClass().hashCode()`?

You can, but ...

&gt; 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.

&gt; 3. Is this premature optimization?

Probably yes&lt;sup&gt;1&lt;/sup&gt;. 

However, using the hashcode of the object&#39;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&lt;sup&gt;2&lt;/sup&gt;.  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)`.

-------------

&lt;sup&gt;1 - I am assuming you have not done a bunch of performance analysis that you haven&#39;t told us about.  If you *had* already done the analysis, then maybe this is not a premature optimization.&lt;br&gt;
2 - I am assuming that the `Some::hashCode` method is not overridden to something more sensible by concrete subclasses of `Some`.&lt;/sup&gt;

</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(&#39;.&#39;, File.separatorChar) + &quot;.class&quot;;
    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(&quot;com.journaldev.&quot;)) {
      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&#39;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

huangapple
  • 本文由 发表于 2023年2月16日 12:49:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/75467974.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定