JAVA 内部类的总结

作者: 实例波 | 来源:发表于2017-12-10 23:33 被阅读39次

    前言

    随着工作经验的增加,越发感觉到基础的重要性,所以最近上下班地铁上都在看一些基础的东西,前几天看了拭心大哥讲的内部类,由于自己对概念的理解有误,造成了一些困惑,自己查了一上午才弄明白(正好早上开无聊的周例会,我就在那自己查资料,还拉着大学同学一起讨论),所以决定抽空把这部分再梳理一下,做个分享,也可以加深自己对这部分的理解。

    正文

    定义在一个类中或者方法中的类称为内部类。

    内部类可以分为以下四类:

    1. 成员内部类
    2. 局部内部类
    3. 静态内部类
    4. 匿名内部类

    我们来逐个分解:

    • 成员内部类
      最普通的内部类,它定义在一个类的内部中,就如同一个成员变量一样。
      代码形式如下:
    public class OuterClass {
    
        class InnerClass {
            private void method() {
                System.out.println("innerMethod");
            }
        }
    }
    

    调用方式:

        OuterClass out = new OuterClass();
        OuterClass.InnerClass in = out.new InnerClass();
        in.method();
    

    注意:不是直接 new outClass.InnerClass()
    成员内部类可以无条件的访问外部类的成员属性和成员方法(包括 private 和 static 类型的成员),这是因为在成员内部类中,隐式地持有了外部类的引用。

    使用场景:
    当类 A 需要使用类 B ,同时 B 需要访问 A 的成员/方法时,可以将 B 作为 A 的成员内部类。同时我们可以利用 private 内部类禁止其他类访问该内部类,从而做到将具体的实现细节完全隐藏。

    • 局部内部类
      在代码块或者方法中创建的类。局部内部类的作用域只能在其所在的代码块或者方法内,在其它地方无法创建该类的对象。可以把局部内部类理解为作用域很小的成员内部类。
      代码形式如下:
        public void test() {
            class PartInnerClass {
                private void method() {
                    System.out.println("partInnerMethod");
                }
            }
            new PartInnerClass().method();
        }
    

    使用场景:
    局部内部类只用于当前方法或者代码块中创建、使用,属于一次性产品,使用场景比较少。

    • 静态内部类
      使用 static 关键字修饰的内部类就是静态内部类,静态内部类不持有外部类的引用,可以看作是和外部类平级的类。
      代码形式如下:
    public class OuterClass {
    
        static class StaticInnerClass {
            private void method() {
                System.out.println("staticInnerMethod");
            }
        }
    }
    

    我们想在静态内部类中访问外部类的成员只能 new 一个外部类的对象,否则只能访问外部类的静态属性和静态方法。

    使用场景:
    1.当类 A 需要使用类 B,而 B 不需要直接访问外部类 A 的成员变量和方法时,可以将 B 作为 A 的静态内部类。

    2.单例的实现:

    public class Singleton {
    
        private Singleton() {}
    
        private static class InnerClass {
            private static final Singleton INSTANCE = new Singleton();
        }
    
        public static Singleton getInstance() {
            return InnerClass.INSTANCE;
        }
    }
    

    由于InnerClass是一个内部类,只在外部类Singleton的getInstance()中被使用,所以它被加载的时机也就是在getInstance()方法第一次被调用的时候。初始化的时候由ClassLoader来保证同步,确保 INSTANCE 变量只能初始化一次,使INSTANCE成为一个真·单例。

    • 匿名内部类
      匿名内部类需要重点说一下,之前就是因为对它理解不到位,才造成了困惑。
      概括一下匿名内部类的特点:
      1.必须继承一个父类或实现一个接口,并不需要增加额外的方法,只是对继承方法的实现或是覆盖。
      2.只是为了获得一个对象实例,并不需要知道其实际的类型。
      3.匿名内部类的匿名指的是没有类名,而不是没有引用指向它。
      4.匿名内部类不能有构造方法,只能有初始化代码块。因为编译器会帮我们生成一个构造方法然后调用。
      5.匿名内部类中使用到的参数是需要声明为 final 的,否则编译器会报错。
      我之前理解出现偏差就是在第3条,好吧,其实第3条是我自己补充的,为了避免大家出现跟我一样的错误(之前和几个同学讨论的时候,大家对这一点的理解都很模糊)。
      代码形式如下:
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                
            }
        });
    

    我们常用的按钮点击监听用到的就是匿名内部类。
    上面这个例子很好理解,我们再来看看下面这个:

        @SuppressLint("HandlerLeak")
        Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                ...
            }
        };
    

    没错,这个new出来的 Handler 也是一个匿名内部类,我之前看到这个的时候懵逼了,判断不出它究竟是哪种内部类。所以记住,匿名内部类的匿名指的是没有类名,而不是没有引用指向它。你可别告诉我它的类型就是Handler啊,它只是Handler的一个子类,一个没有类名的子类。
    顺便再说一下,这样的写法可能会造成内存泄露,因为这个匿名内部类会隐式地持有外部类的引用。举个例子:

        Message message = Message.obtain();
        message.what = 233;
        mHandler.sendMessageDelayed(message, 1000 * 60 * 10);
        finish();
    

    我们发送了一个延迟10分钟的Message,在Message发送出去之前,我们结束了当前Activity。Message在消息队列中延迟了10分钟,然后才处理该消息。而这个Message引用了Handler,Handler又隐性地持有了Activity的引用,当发生GC时因为 Message – Handler – Acitivity 的引用链导致Activity无法被回收,所以发生了内存泄露。
    解决办法就是使用弱引用WeakReference或者干脆将 Handler 设计为静态内部类

    那么为什么匿名内部类中使用参数需要声明为final呢?
    因为匿名内部类是创建一个对象并返回,这个对象的方法被调用的时机不确定,方法中有修改参数的可能,如果在匿名内部类中修改了参数,外部类中的参数是否需要同步修改呢?Java 为了避免这种问题,限制匿名内部类访问的变量需要使用 final 修饰,这样可以保证访问的变量不可变。

    使用场景:
    需要实现一个接口,但不需要持有它的引用。

    补充

    除了静态内部类,剩下的成员内部类、局部内部类、匿名内部类 都会默认隐式地持有外部类的引用。

    再来两道典型题目融会贯通一下:

    在?处填入合适的代码,使程序在控制台输出30,20,10。

    public class OuterClass {
    
        private int num = 10;
    
        class InnerClass {
            int num = 20;
    
            void show() {
                int num = 30;
                System.out.println(?);
                System.out.println(?);
                System.out.println(?);
            }
        }
    
        public static void main(String[] args) {
            InnerClass in = new OuterClass().new InnerClass();
            in.show();
        }
    }
    

    .
    .
    .
    .
    .
    答案:
    1.num
    2.this.num
    3.OuterClass.this.num

    补全代码 ,要求在控制台输出"HelloWorld"

    interface InterfaceTest {
        void show();
    }
    
    public class TestClass {
    
        //补全代码
    
        public static void main(String[] args) {
            TestClass.method().show();
        }
    }
    

    .
    .
    .
    .
    .
    答案:

    interface InterfaceTest {
        void show();
    }
    
    public class TestClass {
    
        //补全代码
        public static InterfaceTest method() {
            return new InterfaceTest() {
                @Override
                public void show() {
                    System.out.println("HelloWorld");
                }
            };
        }
    
        public static void main(String[] args) {
            TestClass.method().show();
        }
    }
    

    结语

    学海无涯,我们不仅要向前迈进,还得时常回过头去,看看来时的路。

    文中多有借鉴拭心大哥的文章,附上链接:
    http://blog.csdn.net/d29h1jqy3akvx

    相关文章

      网友评论

        本文标题:JAVA 内部类的总结

        本文链接:https://www.haomeiwen.com/subject/yhimixtx.html