12

我想知道封闭类可以创建多少个静态成员类的实例。我假设只有一个,但是下面的 Bloch 摘录对我来说没有意义。

引用 Joshua Bloch 的 Effective Java - 第 22 条*:偏爱静态成员类而不是非静态成员类。

私有静态成员类的一个常见用途是表示由其封闭类表示的对象的组件。例如,考虑一个 Map 实例,它将键与值相关联。许多 Map 实现对于 map 中的每个键值对都有一个内部 Entry 对象。虽然每个条目都与映射相关联,但条目上的方法(getKey、getValue 和 setValue)不需要访问映射。因此,使用非静态成员类来表示条目是很浪费的:私有静态成员类是最好的。如果不小心在入口声明中省略了 static 修饰符,地图仍然可以工作,但是每个入口都会包含对地图的多余引用,这会浪费空间和时间。

他指出,映射为映射中的每个键值对创建一个Entry 对象,即静态成员类的多个实例。

所以我的假设是错误的!这意味着我对静态成员类的理解是错误的。每个人都知道静态成员变量的行为方式,例如经典的静态最终字符串——对象只有一个实例。

这是否意味着在实例化封闭对象时实际上并未实例化静态成员类?

那么在这种情况下,Map 使用静态成员类作为 Entry 的意义何在?为什么不直接在 API 上使用接口?然后,每个其他 Collections 类都可以提供它自己的实现。

[*] 刚刚意识到这是我所拥有的 PDF 版本中的第 18 项

4

4 回答 4

9

这是对static关键字的常见误解。

当您使用static变量时,这意味着对于此类的所有对象或类似的对象,只有其中一个。

static Object thereWillBeOnlyOne = new Object();

但是,在内部类的上下文中,它意味着完全不同的东西。内部类static与封闭类的对象没有联系,而非静态内部类则有


一个static内部类:

public class TrieMap<K extends CharSequence, V> extends AbstractMap<K, V> implements Map<K, V> {

  private static class Entry<K extends CharSequence, V> implements Map.Entry<K, V> {

我的Map.Entry类使用的TrieMap类不需要引用创建它的对象,因此可以static保存不必要的引用。


static内部类:

public final class StringWalker implements Iterable<Character> {
  // The iteree
  private final String s;
  // Where to get the first character from.
  private final int start;
  // What to add to i (usually +/- 1).
  private final int step;
  // What should i be when we stop.
  private final int stop;

  // The Character iterator.
  private final class CharacterIterator implements Iterator<Character> {
    // Where I am.
    private int i;
    // The next character.
    private Character next = null;

    CharacterIterator() {
      // Start at the start.
      i = start;
    }

    public boolean hasNext() {
      if (next == null) {
        if (step > 0 ? i < stop : i > stop) {
          next = s.charAt(i);
          i += step;
        }
      }
      return next != null;
    }

对象内部CharacterIteratorStringWalker指要迭代的字符串,s它在对象中只存在一次StringWalker。因此,我可以创建许多 a 的迭代器,StringWalker它们都走同一个字符串。


为什么会出现这种奇怪现象?

这种看似不合逻辑的二元性源于static关键字 in的使用C

C你可以(或至少曾经能够)做:

void doSomething () {
   static int x = 1;

   if ( x < 3 ) {
   } else {
   }
   x += 1;
}

每次调用该函数时,x都将与上次离开时​​一样-在这种情况下递增。

这个概念是static关键字表明该变量被其封闭块在范围内封闭,但在语义被其父块封闭。即上面的代码大致相当于:

int x = 1;
void doSomething () {
   if ( x < 3 ) {
   } else {
   }
   x += 1;
}

x只允许在函数内部被引用。

把这个概念向前推进Java,现在事情变得更有意义了。内部类的行为与在类外部声明的static完全一样,而非static内部类与其封闭的实例更紧密地结合在一起——实际上它可以直接引用该实例。

还:

class Thing {
   static Object thereWillBeOnlyOne = new Object();

行为很像

Object thereWillBeOnlyOne = new Object();
class Thing {

如果它是合法的。

本课到此结束。

于 2014-07-25T10:40:10.570 回答
4

我认为 Java 团队搞砸了这个的命名。静态内部类(严格来说它们的正确名称是“静态嵌套类”)与普通类没有任何不同,只是它有一个花哨的名称(Something.MyClass而不是MyClass)并且可以设为私有(即不能从其他类实例化)。

在这种情况下Map,它之所以被选中,是因为名称Map.Entry清楚地表明Entry涉及到Map。正如您所建议的,为此使用普通类是完全合理的。唯一的区别是你不会写Map.Entry

我认为他们应该做的是class为静态嵌套类使用“非静态”内部类(即仅在封闭类中)的语法,而是发明一个新关键字来创建“非静态”内部类,因为正是这些行为与普通类不同。也许像attached class. AFAIKstatic选择关键字是为了避免保留关键字过多,但我认为它只会助长混淆。

于 2014-07-25T10:20:26.097 回答
3

是的,你可以有很多嵌套类的实例,不管嵌套类是静态的。

当嵌套类是静态的时,您可以在没有封闭类的实例的情况下创建它的实例,这是好处之一,并且基本上是静态和非静态嵌套类之间的主要区别。

这是否意味着在实例化封闭对象时实际上并未实例化静态成员类?

它在调用它的构造函数时被实例化。与非静态类没有任何不同。嵌套类本身由 JVM 加载,当代码第一次访问它时。同样,与其他课程相比,我认为这没有什么不同(虽然不是 100% 肯定,但您可以自己测试)。因此,我认为您将“通过 JVM 加载类”和“实例化类”这两个术语混为一谈。

那么在这种情况下,Map 使用静态成员类作为 Entry 的意义何在?为什么不直接在 API 上使用接口?

如前所述,创建静态嵌套类的实例更容易。您不需要一个封闭的实例,它有时(也许大多数时候)正是您想要的。

也可以看看:

(1) 嵌套类

(2)JVM如何判断一个类是否嵌套到另一个类中?

(3) JVM加载嵌套类

您可以按照这些思路搜索其他参考资料。
参考文献 (2) 似乎对您的问题来说是先进的和外围的。

于 2014-07-25T10:17:28.163 回答
2

Map 使用静态成员类作为 Entry 的意义何在?

那是因为,它使包结构在逻辑上是正确的。

为什么不直接在 API 上使用接口?

现在,这是一个没有人愿意被拖入的设计讨论。

于 2014-07-25T10:21:41.987 回答