【问题标题】:Bloch Effective Java - favor static classes over nonstatic - how many instances?Bloch Effective Java - 喜欢静态类而不是非静态类 - 有多少实例?
【发布时间】:2014-09-17 04:03:15
【问题描述】:

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

引用 Joshua Bloch 的 Effective Java - 第 22 条*:优先考虑静态成员类而不是非静态成员类。

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

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

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

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

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

[*] 才发现是我的PDF版书的第18条

【问题讨论】:

  • 该类只有一份副本。但是您可以创建该类的许多实例。对于非静态类,从概念上讲,外部类的每个实例都有一个该类的副本(尽管出于效率原因实际上并未以这种方式实现)。
  • 我发现最容易将静态内部类视为一个普通类,其名称中包含外部类。外部类和内部类之间的唯一关系是访问修饰符。
  • Map只有一个,不管是不是静态的,但可以包含多个元素

标签: java oop nested-class static-class


【解决方案1】:

这是对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> {

我的TrieMap 类使用的Map.Entry 类不需要引用创建它的对象,因此可以将其设为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;
    }

StringWalker 对象中的CharacterIterator 指的是要迭代为s 的字符串,它在StringWalker 对象中只存在一次。因此,我可以创建许多 StringWalker 的迭代器,它们都遍历 same 字符串。


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

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

C,您可以(或至少曾经能够)这样做:

void doSomething () {
   static int x = 1;

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

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

概念是static 关键字表明该变量作用域 被其封闭块包围,但语义 被其父块包围。 IE。上面的代码大致相当于:

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 {

如果合法的话。

课程到此结束。

【讨论】:

    【解决方案2】:

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

    Map 的情况下,之所以选择它,是因为名称Map.Entry 清楚地表明EntryMap 相关。正如您所建议的,为此使用普通类是完全合理的。唯一的区别是你不能写Map.Entry

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

    【讨论】:

    • 没有静态内部类这样的东西。内部类是一个非静态的嵌套类。我还在我的答案的初始版本中声明了该术语错误。现在好了。
    【解决方案3】:

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

    当嵌套类是静态的时,您可以创建它的实例而无需封闭类的实例,这是好处之一,也是静态和非静态嵌套类之间的主要区别。

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

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

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

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

    另见:

    (1) Nested Classes

    (2) How can the JVM decide if a class is nested into another class?

    (3) Loading of a nested class by the JVM

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

    【讨论】:

      【解决方案4】:

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

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-07
        • 1970-01-01
        • 2011-06-11
        • 2014-02-03
        相关资源
        最近更新 更多