JAVA内部类优先设置成静态的

文章也上传到

github

(欢迎关注,欢迎大神提点。)


ITEM24 内部类优先设置成静态的


嵌套类指的是定义在其他类中的类。
当类仅仅只对另一个类服务时(而不对其他类服务)才应该被设计成嵌套类。如果一个类对多个类服务,那么它就应该被设计成顶层类。
嵌套类分为四种:

  • 静态的
  • 非静态的
  • 匿名类
  • 局部类

除了第一种之外都可以叫做内部类。这个章节告诉你何时以及如何选择嵌套类的使用。
你可以将嵌套类理解成一个声明在其他类中的普通类,可以访问包含它的类的任何成员(即使是private成员也不例外)。
一个静态成员类拥有像其他静态成员一样的可访问性。如果它被声明称private的,那么就只有它的宿主类能够访问它等等。
一种常见使用静态成员类是将其声明为public的helper类,仅仅当它和外部类一起使用才有用。例如,Item34中一个表示计算器运算规则的枚举。Operation枚举是Calculator类中的一个public static的成员类。客户端可以通过Calculator.Operation.PLUS 和 Calculator.Operation.MINUS来调用。

在语法上,静态和非静态成员类的区别仅仅是在声明静态成员类时在前面添加了static关键字。即使语法上看起来很相似,但是这两种类其实有很大的不同。

非静态成员类的实例对象隐含的与包含它的类实例对象相关联。在非静态成员类的对象方法中,你可以调用它宿主类的方法,或者可以在构造方法中引用一个包含它的类的实例对象。

如果一个嵌套类的实例可以与宿主类的实例独立开来,那么这个嵌套类就应该被成定义成静态的成员类:因为非静态的成员类对像是不可能在脱离它的宿主类的情况下被创建的。

当非静态类的对象被创建的时候,它和宿主类对象的关系就被确立了,并且之后不能再被改变了。一般来说,当类对象调用它的内部静态类的对象方法时,这种关系就被自动的建立了。尽管很少见,但可以使用表达式enclosingInstance.new MemberClass(args)手动建立关联。但同时,这种关联会占用非静态成员类的空间和构造时间。
通常使用非静态成员类的例子是Adapter(允许外部类对象被看作与它不相关的类对象)。例如,Map接口的实现类HashMap,通常会使用非静态成员类例如Values、KeySet、EntrySet等来实现collection的内容。

public Collection<V> values() {
        Collection<V> vs = values;
        if (vs == null) {
            vs = new Values();
            values = vs;
        }
        return vs;
    }

    final class Values extends AbstractCollection<V> {
        public final int size()                 { return size; }
        public final void clear()               { HashMap.this.clear(); }
        public final Iterator<V> iterator()     { return new ValueIterator(); }
        public final boolean contains(Object o) { return containsValue(o); }
        public final Spliterator<V> spliterator() {
            return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
        }
        public final void forEach(Consumer<? super V> action) {
            Node<K,V>[] tab;
            if (action == null)
                throw new NullPointerException();
            if (size > 0 && (tab = table) != null) {
                int mc = modCount;
                // Android-changed: Detect changes to modCount early.
                for (int i = 0; (i < tab.length && modCount == mc); ++i) {
                    for (Node<K,V> e = tab[i]; e != null; e = e.next)
                        action.accept(e.value);
                }
                if (modCount != mc)
                    throw new ConcurrentModificationException();
            }
        }
    }

类似的还有例如Set和List,使用非静态成员类实现自己的迭代器:


public class MySet<E> extends AbstractSet<E> { ... // Bulk of the class omitted @Override public Iterator<E> iterator() { return new MyIterator(); } private class MyIterator implements Iterator<E> { ... } }

如果你声明了一个成员类不需要访问宿主类的对象,那么应该将其声明称static类型的。如果你忘记添加static标志,那么这个类就会隐式的被宿主类对象引用。正如前面说的,这个引用会消耗时间和空间。更严重的是,可能造成宿主对象在达到垃圾回收条件时不能被正常的回收(Item7),这会造成内存泄漏的风险,而且这很难被检查出来,因为这个引用是不可见的。

私有的内部静态类通常用来表示宿主类的一个组件。例如Map对象中的表示Keys和Values的关联关系的组件。Map中的每一对key-value的关联关系都会有一个Entry的对象来表示。即使每一个entry都是和map相关的,但是entry中的方法(getKey, getValue, 和 setValue)都不需要调用map类。因此使用私有静态类来表示entry是最好的。如果你忘记将其设置成静态类,它也能工作,但是会造成每个entry都会隐式的包含map的引用,这将造成很大的空间和时间的浪费。

在一个public或者protected类中,选择使用静态或非静态的类显得更加重要。因为成员类会成为API的一部分,为了向后兼容,将来不能随意更换现有的类从非静态转化为静态的。

匿名类指的是没有名字的类,它不是宿主类的成员,它不和其他成员一起被声明。而是在使用时同时被声明和初始化的。匿名类在书写正确的前提下可以出现在代码的任何一个地方。匿名类仅当他们出现在非静态区域时才会拥有宿主类的对象。匿名类出现在静态区域时,除了常量之外他们也不会包含任何静态成员。

匿名类有很多适应性的限制:
– 置能声明他们的时候初始化;
– 不能调用任何需要类名的操作,例如instanceOf等;
– 不能用匿名类实现多个接口;
– 不能用匿名类继承一个类的同时实现一个接口;
– 匿名类的客户端不能调用任何除了它从父类继承来的方法。
– 因为匿名类出现在表达式中间,应该保持简短(10行或更少),否则可读性将会受影响。

在lambdas被添加到Java之前(Chapter6),匿名类是创建小函数对象和执行间断逻辑的首选方案,但是现在lambdas是首选方案(Item42)。匿名类的另一种常见用途是静态工厂方法(Item20中的intArrayAsList)。

局部类是四种嵌套类中最少被使用的。一个局部类可以被声明在可以声明任何局部变量的地方,并且遵守同样的可访问权限。局部类有一些类似于其他嵌套类的特性,比如像成员类一样,他们有名字可以被重复的使用;就像匿名类一样,他们被定义在非静态区域时会有宿主类的对象,并且不会包含静态成员,他们要保持简短以保证良好的可读性。
总结一下,如果一个嵌套类需要在一个方法外部可见,或者太长以至于放在一个方法中不合适,那么应该使用一个成员类。如果没有成员类的对象都需要引用一个宿主类的对象,就将其设置成非静态的,否则设置成静态的。假设一个类在一个方法内部,如果你只需要一个这样的实例并且已经有可以表示这个类类型,那么将其设置成匿名类,否则将其设置成本地类。

发表评论

关闭菜单