5

In Effective JAVA by Joshua Bloch, when I was reading about static factory methods , there was a statement as follows

The ability of static factory methods to return the same object from repeated invocations allows classes to maintain strict control over what instances exist at any time. Classes that do this are said to be instance-controlled. There are several reasons to write instance-controlled classes. Instance control allows a class to guarantee that it is a singleton (Item 3) or noninstantiable (Item 4). Also, it allows an immutable class (Item 15) to make the guarantee that no two equal instances exist: a.equals(b) if and only if a==b. If a class makes this guarantee, then its cli- ents can use the == operator instead of the equals(Object) method, which may result in improved performance. Enum types (Item 30) provide this guarantee.

To investigate how == operator brings in performance improvements , I got to look at String.java

I saw this snippet

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String) anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                            return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

By performance improvement what does he mean here ? how it brings performance improvement .

Does he mean to say the following

If every class can assure that a.equals(b) if and only if a==b , it means it brings in an indirect requirement that there cannot be objects referring to 2 different memory spaces and still hold the same data , which is memory wastage . If they hold same data they are one and the same object .That is they point to same memory location.

Am I right in this inference ?

If I am wrong can you guide me in understanding this ?

4

6 回答 6

3

引用部分的意思是不可变类可以选择实习其实例。这很容易通过 Guava 实现Interner,例如:

public class MyImmutableClass {
    private static final Interner<MyImmutableClass> INTERN_POOL = Interners.newWeakInterner();
    private final String foo;
    private final int bar;

    private MyImmutableClass(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }

    public static MyImmutableClass of(String foo, int bar) {
        return INTERN_POOL.intern(new MyImmutableClass(foo, bar));
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(foo, bar);
    }

    @Override
    public boolean equals(Object o) {
        if (o == this)
            return true;        // fast path for interned instances
        if (o instanceof MyImmutableClass) {
            MyImmutableClass rhs = (MyImmutableClass) o;
            return Objects.equal(foo, rhs.foo)
                    && bar == rhs.bar;
        }
        return false;
    }
}

在这里,构造函数是私有的:所有实例都必须通过MyImmutableClass.of()工厂方法,该方法使用Interner确保如果新实例是equals()现有实例,则返回现有实例。

实习只能用于不可变对象,我指的是对象的可观察状态(即所有外部可访问方法的行为,特别是equals()and hashCode())在对象的生命周期内不会改变。如果您实习可变对象,则在修改实例时行为将是错误的。

正如许多其他人已经说过的那样,您应该仔细选择要实习的对象,即使它们是不可变的。仅当实习值集相对于您可能拥有的重复数而言较小时才这样做。例如,Integer一般不值得实习,因为有超过 40 亿个可能的值。但值得实习最常用的Integer值,实际上,Integer.valueOf()实习生值介于 -128 和 127 之间。另一方面,枚举非常适合实习生(根据定义,它们是实习生的),因为可能值的集合是小的。

一般来说,对于大多数类,您必须进行堆分析,例如通过使用jhat(或者,插入我自己的项目,fasthat)来确定是否有足够的重复项来保证实习。在其他情况下,保持简单,不要实习。

于 2013-11-17T05:29:45.897 回答
3

如果每个类都可以确保 a.equals(b) 当且仅当 a==b ,这意味着它带来了一个间接要求,即不能有对象引用 2 个不同的内存空间并且仍然保存相同的数据,即内存浪费。如果它们持有相同的数据,它们就是同一个对象。也就是说,它们指向相同的内存位置。

是的,这就是作者所追求的。

如果你可以(对于给定的类,这对所有人来说都是不可能的,特别是它不能用于可变类)调用==(这是单个 JVM 操作码)而不是equals(这是一个动态调度的方法调用),它保存(一些)开销。

例如,它以这种方式工作enums。

即使有人调用了该equals方法(这将是很好的防御性编程实践,恕我直言,您不想养成使用==对象的习惯),该方法也可以实现为简单的==(而不必考虑潜在的复杂性)对象状态)。

顺便说一句,即使对于“正常”的 equals 方法(例如 String 的),在它们的实现中首先检查对象身份然后查看对象状态的捷径可能是一个好主意(这就是 String#equals 所做的,就像你已经发现)。

于 2013-11-17T05:24:08.517 回答
1

为了回答你的问题...

通过性能改进,他在这里 [ String ] 是什么意思?它如何带来性能改进。

这不是布洛赫所说的一个例子。Bloch 是在谈论实例控制类,而String不是这样的类!

我的推论正确吗?

对,那是正确的。实例不可变的实例控制类可以确保“相同”的对象根据==运算符将始终相等。

不过有一些观察:

  • 这仅适用于不可变对象。或者更准确地说是突变不影响相等语义的对象。

  • 这仅适用于完全由实例控制的类。

  • 实例控制可能很昂贵。考虑由 String 类的intern方法和字符串池提供的(部分)实例控制的形式。

    • 字符串池实际上是对 String 对象的弱引用的哈希表。这会占用额外的内存。

    • 每次你实习一个字符串时,它都会计算字符串的哈希码并探测哈希表以查看是否已经实习过类似的字符串

    • 每次执行完整的 GC 时,字符串池中的弱引用都会为 GC 带来额外的“跟踪”工作,如果 GC 决定中断引用,则可能会做更多的工作。

    当您实现自己的实例控制类时,您通常会获得类似的开销。当您进行成本效益分析时,这些开销会影响更快的实例比较的好处。

于 2013-11-17T07:18:29.850 回答
1

如果您可以保证不存在一个对象的两个实例,使得它们的语义值是等价的(即,如果xy引用不同的实例 [ x != y],则x.equals(y) == false对于所有xy),那么这意味着您可以简单地比较两个引用的对象是否相等检查它们是否引用同一个实例,这是什么==

的实现==本质上只是比较两个整数(内存地址),并且通常比几乎所有非平凡的.equals().

值得注意的是,这不是可以为Strings 进行的跳转,因为您不能保证 a 的任何两个实例String都不等价,例如:

String x = new String("hello");
String y = new String("hello");

由于,仅检查是否相等x != y && x.equals(y)是不够的。x == y

于 2013-11-17T05:24:17.923 回答
0

在使用对不可变对象的引用来封装复杂值的情况下,比较两个引用时通常会出现三种情况:

  • 它们是对同一个对象的引用(非常快)

  • 它们是对封装不同值的不同对象的引用(通常很快,但有时很慢)

  • 它们是对封装相同值的不同对象的引用(通常总是很慢)

如果发现对象经常相等,则最小化情况 3 的频率可能具有重要价值。如果对象通常非常接近相等,则确保情况 2 的慢子情况也具有重要价值不要经常发生。

如果确定对于任何给定值永远不会有多个对象持有该值,则观察到两个引用标识不同对象的代码可能会推断它们封装了不同的值,而不必实际检查有问题的值。然而,这样做的价值通常是有限的。如果所讨论的对象是大型、复杂、嵌套的集合,有时会非常相似,则可以让每个集合计算并缓存其内容的 128 位散列;具有不同内容的两个集合不太可能具有匹配的哈希值,并且具有不同哈希值的集合可能很快被识别为不相等。另一方面,通常具有封装相同内容的引用识别到同一个对象,即使存在对相同集合的一些引用,也可以提高否则总是坏的“等于”情况的性能。

如果不想使用单独的实习集合,可以使用的一种方法是让每个对象保留一个long序列号,以便始终可以确定首先创建两个其他相同对象中的哪一个,以及对已知包含相同内容的最旧对象。要比较两个引用,首先要确定已知与每个引用等效的最旧对象。如果已知与第一个匹配的最旧对象与已知与第二个匹配的最旧对象不同,则比较对象的内容。如果它们匹配,一个将比另一个更新,并且该对象可以将另一个视为“已知匹配的最旧对象”。

于 2013-11-17T21:26:52.667 回答
0

我认为这意味着:

如果您需要测试两个复杂结构的相等性,您通常需要进行大量测试以确保它们相同。

但是,如果由于语言的某些技巧,您知道两个复杂但相等的结构不能同时存在,那么您可以验证它们是否在内存中的相同位置并返回 false ,而不是通过逐位比较它们来验证相等性他们不是。

如果任何人都可以创建对象,那么您不能保证不能创建两个相同但不同的实例的对象。但是如果您控制对象的创建并且只创建不同的对象,那么您不需要复杂的相等性测试。

于 2013-11-17T05:25:06.613 回答