精华内容
下载资源
问答
  • java内部类的作用
    2021-03-16 20:28:25

    Java内部类有什么作用 ?java内部类介绍。java内部类有什么好处?为什么需要内部类?

    1、举一个简单的例子,如果你想实现一个接口,但是这个接口中的一个方法和你构想的这个类中的一个方法的名称,参数相同,你应该怎么办?这时候,你可以建一个内部类实现这个接口。由于内部类对外部类的所有内容都是可访问的,所以这样做可以完成所有你直接实现这个接口的功能。

    2、真正的原因是这样的,java中的内部类和接口加在一起,可以解决常被C++程序员抱怨java中存在的一个问题:没有多继承。实际上,C++的多继承设计起来很复杂,而java通过内部类加上接口,可以很好的实现多继承的效果。

    java内部类的作用:

    1、隐藏你的操作,也即封装性。

    2、一个内部类对象可访问创建它的外部类对象的内容,甚至包括私有变量!

    内部类的功能:每个内部类都能独立的继承一个接口的实现,因此无论外围类是否已经继承了某个(接口的)实现,对于内部类是没影响的。内部类使多重继承的解决方案变得完整,而且内部类允许继承多个非接口类型(类或抽象类)。通过内部类分别继承一个基类,外部类创建内部类的对象,并使用内部类的方法,变相地实现了多继承。

    java静态内部类与非静态内部类的区别

    1、和非静态内部类相比,区别就在于静态内部类没有了指向外部的引用。

    2、在任何非静态内部类中,都不能有静态数据,静态方法或者又一个静态内部类(内部类的嵌套可以不止一层)。不过静态内部类中却可以拥有这一切。

    更多相关内容
  • java内部类的四大作用

    万次阅读 多人点赞 2019-02-15 14:00:34
    整理 什么是内部类:在类里面重新定义一个类,如下 public class OuterClass { private String name ; class InnerClass{ public InnerClass(){ name = &...内部类作用 1.内部类可以很好的...


    什么是内部类:定义在类中的类

    内部类的作用

    我们为什么需要内部类?或者说内部类为啥要存在?其主要原因有如下几点:

    • 内部类方法可以访问该类定义所在作用域中的数据,包括被 private 修饰的私有数据
    • 内部类可以对同一包中的其他类隐藏起来
    • 内部类可以解决 java 单继承的缺陷
    • 当我们想要定义一个回调函数却不想写大量代码的时候我们可以选择使用匿名内部类来实现
      举例说明如下:

    1.可以无条件地访问外围类的所有元素

    为什么可以引用:

    内部类虽然和外部类写在同一个文件中, 但是编译完成后, 还是生成各自的class文件,内部类通过this访问外部类的成员。

    1 编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象(this)的引用;

    2 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为内部类中添加的成员变量赋值;

    3 在调用内部类的构造函数初始化内部类对象时,会默认传入外部类的引用。

    编译指令 javac classpath(.java文件的路径)
    反编译指令 javap -v(详细信息) classpath(.class文件的路径)

    /**
     * 内部类无条件访问外部类元素
     */
    public class DataOuterClass {
    
        private String data = "外部类数据";
    
        private class InnerClass {
    
            public InnerClass() {
    
                System.out.println(data);
    
            }
    
        }
    
        public void getInner() {
    
            new InnerClass();
    
        }
    
        public static void main(String[] args) {
    
            DataOuterClass outerClass = new DataOuterClass();
    
            outerClass.getInner();
    
        }
    
    }
    

    打印

    外部类数据
    

    data这是在DataOuterClass定义的私有变量。这个变量在内部类中可以无条件地访问.

    2.实现隐藏

    关于内部类的第二个好处其实很显而易见,我们都知道外部类即普通的类不能使用 private protected 访问权限符来修饰的,而内部类则可以使用 private 和 protected 来修饰。当我们使用 private 来修饰内部类的时候这个类就对外隐藏了。这看起来没什么作用,但是当内部类实现某个接口的时候,在进行向上转型,对外部来说,就完全隐藏了接口的实现了

    接口

    public interface InnerInterface {
    
        void innerMethod();
    
    }
    

    具体类

    /**
     * 实现信息隐藏
     */
    public class OuterClass {
    
        /**
         * private修饰内部类,实现信息隐藏
         */
        private class InnerClass implements InnerInterface {
    
            @Override
            public void innerMethod() {
                System.out.println("实现内部类隐藏");
            }
    
        }
    
        public InnerInterface getInner() {
    
            return new InnerClass();
    
        }
    
    }
    

    调用程序

    public class Test {
    
        public static void main(String[] args) {
    
            OuterClass outerClass = new OuterClass();
    
            InnerInterface inner = outerClass.getInner();
    
            inner.innerMethod();
    
        }
    
    }
    

    打印

    实现内部类隐藏
    

    从这段代码里面我只知道OuterClass的getInner()方法能返回一个InnerInterface接口实例但我并不知道这个实例是这么实现的。

    而且由于InnerClass是private的,所以我们如果不看代码的话根本看不到这个具体类的名字,所以说它可以很好的实现隐藏。

    3.可以实现多重继承

    我们知道 java 是不允许使用 extends 去继承多个类的。内部类的引入可以很好的解决这个事情。
    我的理解 Java只能继承一个类这个学过基本语法的人都知道,而在有内部类之前它的多重继承方式是用接口来实现的。但使用接口有时候有很多不方便的地方。比如我们实现一个接口就必须实现它里面的所有方法。
    而有了内部类就不一样了。它可以使我们的类继承多个具体类或抽象类。如下面这个例子:

    类一

    public class ExampleOne {
    
        public String name() {
    
            return "inner";
    
        }
    
    }
    

    类二

    public class ExampleTwo {
    
        public int age() {
    
            return 25;
    
        }
    
    }
    

    类三

    public class MainExample {
    
       /**
        * 内部类1继承ExampleOne
        */
       private class InnerOne extends ExampleOne {
    
           public String name() {
    
               return super.name();
    
           }
    
       }
    
       /**
        * 内部类2继承ExampleTwo
        */
       private class InnerTwo extends ExampleTwo {
    
           public int age() {
    
               return super.age();
    
           }
    
       }
    
       public String name() {
    
           return new InnerOne().name();
    
       }
    
       public int age() {
    
           return new InnerTwo().age();
    
       }
    
       public static void main(String[] args) {
    
           MainExample mi = new MainExample();
    
           System.out.println("姓名:" + mi.name());
    
           System.out.println("年龄:" + mi.age());
    
       }
    
    }
    

    大家注意看类三,里面分别实现了两个内部类 InnerOne,和InnerTwo ,InnerOne类又继承了ExampleOne,InnerTwo继承了ExampleTwo,这样我们的类三MainExample就拥有了ExampleOne和ExampleTwo的方法和属性,也就间接地实现了多继承。

    4.通过匿名内部类来优化简单的接口实现

    关于匿名内部类相信大家都不陌生,我们常见的点击事件的写法就是这样的:
    不用再去实现OnClickListener对象了。

    ...
        view.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(){
                // ... do XXX...
            }
        })
    ...
    

    内部类与外部类的关系

    • 对于非静态内部类,内部类的创建依赖外部类的实例对象,在没有外部类实例之前是无法创建内部类的

    • 内部类是一个相对独立的实体,与外部类不是is-a关系

    • 创建内部类的时刻并不依赖于外部类的创建

    对于普通内部类创建方法有两种:

    public class ClassOuter {
        
        public void fun(){
            System.out.println("外部类方法");
        }
        
        public class InnerClass{
            
        }
    }
    
    public class TestInnerClass {
        public static void main(String[] args) {
            //创建方式1
            ClassOuter.InnerClass innerClass = new ClassOuter().new InnerClass();
            //创建方式2
            ClassOuter outer = new ClassOuter();
            ClassOuter.InnerClass inner = outer.new InnerClass();
        }
    }
    

    内部类的分类

    内部类可以分为:静态内部类(嵌套类)和非静态内部类。非静态内部类又可以分为:成员内部类、方法内部类、匿名内部类。

    静态内部类和非静态内部类的区别

    静态内部类非静态内部类
    是否可以有静态成员变量
    是否可以访问外部类的非静态变量
    是否可以访问外部类的静态变量
    创建是否依赖于外部类

    我们通过一个例子就可以很好的理解这几点区别:

    public class ClassOuter {
        private int noStaticInt = 1;
        private static int STATIC_INT = 2;
    
        public void fun() {
            System.out.println("外部类方法");
        }
    
        public class InnerClass {
            //static int num = 1; 此时编辑器会报错 非静态内部类则不能有静态成员
            public void fun(){
                //非静态内部类的非静态成员可以访问外部类的非静态变量。
                System.out.println(STATIC_INT);
                System.out.println(noStaticInt);
            }
        }
    
        public static class StaticInnerClass {
            static int NUM = 1;//静态内部类可以有静态成员
            public void fun(){
                System.out.println(STATIC_INT);
                //System.out.println(noStaticInt); 此时编辑器会报 不可访问外部类的非静态变量错
            }
        }
    }
    
    public class TestInnerClass {
        public static void main(String[] args) {
            //非静态内部类 创建方式1
            ClassOuter.InnerClass innerClass = new ClassOuter().new InnerClass();
            //非静态内部类 创建方式2
            ClassOuter outer = new ClassOuter();
            ClassOuter.InnerClass inner = outer.new InnerClass();
            //静态内部类的创建方式
            ClassOuter.StaticInnerClass staticInnerClass = new ClassOuter.StaticInnerClass();
        }
    }
    

    局部内部类

    定义

    如果一个内部类只在一个方法中使用到了,那么我们可以将这个类定义在方法内部,这种内部类被称为局部内部类。其作用域仅限于该方法。

    局部内部类有两点值得我们注意的地方:

    • 局部内类不允许使用访问权限修饰符 public private protected 均不允许
    • 局部内部类对外完全隐藏,除了创建这个类的方法可以访问它其他的地方是不允许访问的。
    • 局部内部类与成员内部类不同之处是他可以引用成员变量,但该成员必须声明为 final,并内部不允许修改该变量的值。(这句话并不准确,因为如果不是基本数据类型的时候,只是不允许修改引用指向的对象,而对象本身是可以被就修改的)
    public class ClassOuter {
        private int noStaticInt = 1;
        private static int STATIC_INT = 2;
    
        public void fun() {
            System.out.println("外部类方法");
        }
        
        public void testFunctionClass(){
            class FunctionClass{
                private void fun(){
                    System.out.println("局部内部类的输出");
                    System.out.println(STATIC_INT);
                    System.out.println(noStaticInt);
                    System.out.println(params);
                    //params ++ ; // params 不可变所以这句话编译错误
                }
            }
            FunctionClass functionClass = new FunctionClass();
            functionClass.fun();
        }
    }
    

    匿名内部类

    • 匿名内部类是没有访问修饰符的。
    • 匿名内部类必须继承一个抽象类或者实现一个接口
    • 匿名内部类中不能存在任何静态成员或方法
    • 匿名内部类是没有构造方法的,因为它没有类名。
    • 与局部内部类相同匿名内部类也可以引用局部变量。此变量也必须声明为 final
    public class Button {
        public void click(final int params){
            //匿名内部类,实现的是ActionListener接口
            new ActionListener(){
                public void onAction(){
                    System.out.println("click action..." + params);
                }
            }.onAction();
        }
        //匿名内部类必须继承或实现一个已有的接口
        public interface ActionListener{
            public void onAction();
        }
    
        public static void main(String[] args) {
            Button button=new Button();
            button.click();
        }
    }
    

    为什么局部变量需要final修饰呢

    因为局部变量和匿名内部类的生命周期不同。

    匿名内部类是创建后是存储在堆中的,而方法中的局部变量是存储在Java栈中,当方法执行完毕后,就进行退栈,同时局部变量也会消失。

    那么此时匿名内部类还有可能在堆中存储着,那么匿名内部类要到哪里去找这个局部变量呢?

    为了解决这个问题编译器为自动地帮我们在匿名内部类中创建了一个局部变量的备份,也就是说即使方法执结束,匿名内部类中还有一个备份,自然就不怕找不到了。

    但是问题又来了。

    如果局部变量中的a不停的在变化。
    那么岂不是也要让备份的a变量无时无刻的变化。
    为了保持局部变量与匿名内部类中备份域保持一致。
    编译器不得不规定死这些局部域必须是常量,一旦赋值不能再发生变化了。

    所以为什么匿名内部类应用外部方法的域必须是常量域的原因所在了。

    特别注意
    在Java8中已经去掉要对final的修饰限制,但其实只要在匿名内部类使用了,该变量还是会自动变为final类型(只能使用,不能赋值)。

    实际开发中内部类有可能会引起的问题

    内部类会造成程序的内存泄漏

    相信做 Android 的朋友看到这个例子一定不会陌生,我们经常使用的 Handler 就无时无刻不给我们提示着这样的警告。

    我们先来看下内部类为什么会造成内存泄漏。

    要想了解为啥内部类为什么会造成内存泄漏我们就必须了解 java 虚拟机的回收机制,但是我们这里不会详尽的介绍 java 的内存回收机制,我们只需要了解 java 的内存回收机制通过「可达性分析」来实现的。

    即 java 虚拟机会通过内存回收机制来判定引用是否可达,如果不可达就会在某些时刻去回收这些引用。

    那么内部类在什么情况下会造成内存泄漏的可能呢?

    如果一个匿名内部类没有被任何引用持有,那么匿名内部类对象用完就有机会被回收。

    如果内部类仅仅只是在外部类中被引用,当外部类的不再被引用时,外部类和内部类就可以都被GC回收。

    如果当内部类的引用被外部类以外的其他类引用时,就会造成内部类和外部类无法被GC回收的情况,即使外部类没有被引用,因为内部类持有指向外部类的引用)。

    public class ClassOuter {
    
        Object object = new Object() {
            public void finalize() {
                System.out.println("inner Free the occupied memory...");
            }
        };
    
        public void finalize() {
            System.out.println("Outer Free the occupied memory...");
        }
    }
    
    public class TestInnerClass {
        public static void main(String[] args) {
            try {
                Test();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        private static void Test() throws InterruptedException {
            System.out.println("Start of program.");
    
            ClassOuter outer = new ClassOuter();
            Object object = outer.object;
            outer = null;
    
            System.out.println("Execute GC");
            System.gc();
    
            Thread.sleep(3000);
            System.out.println("End of program.");
        }
    }
    

    运行程序发现 执行内存回收并没回收 object 对象,
    这是因为即使外部类没有被任何变量引用,只要其内部类被外部类以外的变量持有,外部类就不会被GC回收。

    我们要尤其注意内部类被外面其他类引用的情况,这点导致外部类无法被释放,极容易导致内存泄漏。

    在Android 中 Hanlder 作为内部类使用的时候其对象被系统主线程的 Looper (当然这里也可是子线程手动创建的 Looper)掌管的消息队列 MessageQueue 中的 Hanlder 发送的 Message 持有,当消息队列中有大量消息处理的需要处理,或者延迟消息需要执行的时候,创建该 Handler 的 Activity 已经退出了,Activity 对象也无法被释放,这就造成了内存泄漏。
    那么 Hanlder 何时会被释放,当消息队列处理完 Hanlder 携带的 message 的时候就会调用 msg.recycleUnchecked()释放Message所持有的Handler引用。

    在 Android 中要想处理 Hanlder 内存泄漏可以从两个方面着手:

    在关闭Activity/Fragment 的 onDestry,取消还在排队的Message:

    mHandler.removeCallbacksAndMessages(null);
    

    将 Hanlder 创建为静态内部类并采用软引用方式

    mHandler
       private static class MyHandler extends Handler {
    
            private final WeakReference<MainActivity> mActivity;
    
            public MyHandler(MainActivity activity) {
                mActivity = new WeakReference<MainActivity>(activity);
            }
    
            @Override
            public void handleMessage(Message msg) {
                MainActivity activity = mActivity.get();
                if (activity == null || activity.isFinishing()) {
                   return;
                }
                // ...
            }
        }
    
    展开全文
  • java内部类有什么作用

    千次阅读 2021-02-28 13:04:55
    Java内部类真的很难理解,但有必要搞懂,因为内部类让外部类更丰富多彩了,就好像一个人的心中还可以住着另外一个人。01、内部类的定义顾名思义,内部类就是放在另外一个类的内部定义的类。非常重要的一点是,内部类...

    Java内部类真的很难理解,但有必要搞懂,因为内部类让外部类更丰富多彩了,就好像一个人的心中还可以住着另外一个人。

    01、内部类的定义

    顾名思义,内部类就是放在另外一个类的内部定义的类。非常重要的一点是,内部类能够访问外部类的所有成员,包括private修饰的。

    来看程序清单1-1:

    public class Wanger {

    private int age;

    public Wanger(int age) {

    this.age = age;

    }

    class Thought {

    public void know() {

    System.out.println("沉默王二的年龄" + age);

    }

    }

    public Thought getThought() {

    return new Thought();

    }

    public static void main(String[] args) {

    Wanger wanger = new Wanger(29);

    Wanger.Thought thought = wanger.getThought();

    thought.know(); // 输出:沉默王二的年龄29

    // 使用.new的形式创建内部类对象

    Wanger.Thought thought1 = wanger.new Thought();

    thought1.know();

    }

    }

    程序清单1-1要表达什么意思呢?

    答案是:我,沉默王二,已经29岁了,89年出生(有人说89年出生明明是30岁)。上了年纪了,总想装点嫩,理解一下。我读书不多,但特别爱思考,于是我就给自己创建了一个会思考的内部类Thought。

    从程序清单1-1可以看得出,尽管Thought是内部类,但可以访问外部类Wanger的私有成员变量age。

    如果想创建内部类的对象,需要先指明对象引用的类型,格式为 OuterClassName.InnerClassName,就像main()方法中的Wanger.Thought那样。

    紧接着,就要来创建内部类对象了,有两种形式。第一种形式是先在外部类中定义一个方法Thought getThought(),返回使用new关键字创建的内部类对象,然后使用外部类对象调用该方法wanger.getThought();第二种形式是直接通过外部类对象.new创建wanger.new Thought()。

    02、匿名内部类

    以我的编程经验来看,匿名内部类使用最频繁的场合就是在创建线程的时候。

    来看程序清单2-1:

    public class Demo {

    public void test(String title) {

    Thread thread = new Thread(new Runnable() {

    @Override

    public void run() {

    // title = "我不要吃鸡";

    // 改变时会提示错误

    // 在封闭范围中定义的局部变量必须是final的。

    System.out.println(title);

    }

    });

    thread.start();

    }

    public static void main(String[] args) {

    for (int i = 0; i < 10; i++) {

    Demo demo = new Demo();

    demo.test("我要吃鸡" + i);

    }

    }

    }

    在程序清单2-1中,test()方法内部有一个线程对象thread,是通过new Thread()创建的。new Thread()可以接收一个实现了Runnable接口类型的对象,这个对象要怎么创建呢?可以通过匿名内部类的形式来创建——new Runnable() {public void run(){......}}——这段简短的代码等同于:

    // 实现Runnable接口

    class MyRunnable implements Runnable {

    @Override

    public void run() {

    }

    }

    // 向上转型

    Runnable myRunnable = new MyRunnable();

    匿名内部类的好处就在于不仅节省了定义实现类的过程,还能够自动向上转型。

    在程序清单2-1中,test()方法还有一个参数title,JDK1.8之前,编译器要求它必须是final类型的。但JDK1.8之后,如果我们在匿名内部类中需要访问局部变量,那么这个局部变量不再需要用final关键字修饰了。

    但如果想要在匿名内部类中改变局部变量的值,编译器就会提醒你不能这样做,它会提示:“在封闭范围中定义的局部变量必须是final的。”

    03、为什么需要内部类

    Java的内部类让我很容易的想起来JavaScript的闭包,闭包就是定义在一个函数内部的函数——这听起来和Java的内部类定义一样一样的。本质上,闭包是将函数内部与函数外部连接起来的桥梁。内部类一样,它是将内部类与外部类连接起来的桥梁。

    来看看什么是闭包吧:

    function wanger() {

    var age = 30;

    function know() {

    console.log(age);

    }

    }

    wanger();

    // 控制台输出30

    除此之外,内部类最引人注意的原因是:

    内部类可以独立地继承一个抽象类或者实现一个接口,无论外部类是否也这样做了,对内部类都没有影响。

    别瞅了,点赞就对了,你最美你最帅。

    展开全文
  • 详解 Java 内部类

    千次阅读 2021-03-06 18:10:33
    内部类Java 里面算是非常常见的一个功能了,在日常开发中我们肯定多多少少都用过,这里总结一下关于 Java内部类的相关知识点和一些使用内部类时需要注意的点。从种类上说,内部类可以分为四类:普通内部类、...

    内部类在 Java 里面算是非常常见的一个功能了,在日常开发中我们肯定多多少少都用过,这里总结一下关于 Java 中内部类的相关知识点和一些使用内部类时需要注意的点。

    从种类上说,内部类可以分为四类:普通内部类、静态内部类、匿名内部类、局部内部类。我们来一个个看:

    普通内部类

    这个是最常见的内部类之一了,其定义也很简单,在一个类里面作为类的一个字段直接定义就可以了,例:

    public class InnerClassTest {

    public class InnerClassA {

    }

    }

    在这里 InnerClassA 类为 InnerClassTest 类的普通内部类,在这种定义方式下,普通内部类对象依赖外部类对象而存在,即在创建一个普通内部类对象时首先需要创建其外部类对象,我们在创建上面代码中的 InnerClassA 对象时先要创建 InnerClassTest 对象,例:

    public class InnerClassTest {

    public int field1 = 1;

    protected int field2 = 2;

    int field3 = 3;

    private int field4 = 4;

    public InnerClassTest() {

    // 在外部类对象内部,直接通过 new InnerClass(); 创建内部类对象

    InnerClassA innerObj = new InnerClassA();

    System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");

    System.out.println("其内部类的 field1 字段的值为: " + innerObj.field1);

    System.out.println("其内部类的 field2 字段的值为: " + innerObj.field2);

    System.out.println("其内部类的 field3 字段的值为: " + innerObj.field3);

    System.out.println("其内部类的 field4 字段的值为: " + innerObj.field4);

    }

    public class InnerClassA {

    public int field1 = 1;

    protected int field2 = 2;

    int field3 = 3;

    private int field4 = 4;

    //        static int field5 = 5; // 编译错误!普通内部类中不能定义 static 属性

    public InnerClassA() {

    System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");

    System.out.println("其外部类的 field1 字段的值为: " + field1);

    System.out.println("其外部类的 field2 字段的值为: " + field2);

    System.out.println("其外部类的 field3 字段的值为: " + field3);

    System.out.println("其外部类的 field4 字段的值为: " + field4);

    }

    }

    public static void main(String[] args) {

    InnerClassTest outerObj = new InnerClassTest();

    // 不在外部类内部,使用:外部类对象. new 内部类构造器(); 的方式创建内部类对象

    //        InnerClassA innerObj = outerObj.new InnerClassA();

    }

    }

    这里的内部类就像外部类声明的一个属性字段一样,因此其的对象时依附于外部类对象而存在的,我们来看一下结果:

    2c2d58ac620e396478f7e4caed6931c9.png

    我们注意到,内部类对象可以访问外部类对象中所有访问权限的字段,同时,外部类对象也可以通过内部类的对象引用来访问内部类中定义的所有访问权限的字段,后面我们将从源码里面分析具体的原因。

    我们下面来看一下静态内部类。

    静态内部类

    我们知道,一个类的静态成员独立于这个类的任何一个对象存在,只要在具有访问权限的地方,我们就可以通过 类名.静态成员名 的形式来访问这个静态成员,同样的,静态内部类也是作为一个外部类的静态成员而存在,创建一个类的静态内部类对象不需要依赖其外部类对象。例:

    public class InnerClassTest {

    public int field1 = 1;

    public InnerClassTest() {

    System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");

    // 创建静态内部类对象

    StaticClass innerObj = new StaticClass();

    System.out.println("其内部类的 field1 字段的值为: " + innerObj.field1);

    System.out.println("其内部类的 field2 字段的值为: " + innerObj.field2);

    System.out.println("其内部类的 field3 字段的值为: " + innerObj.field3);

    System.out.println("其内部类的 field4 字段的值为: " + innerObj.field4);

    }

    static class StaticClass {

    public int field1 = 1;

    protected int field2 = 2;

    int field3 = 3;

    private int field4 = 4;

    // 静态内部类中可以定义 static 属性

    static int field5 = 5;

    public StaticClass() {

    System.out.println("创建 " + StaticClass.class.getSimpleName() + " 对象");

    //            System.out.println("其外部类的 field1 字段的值为: " + field1); // 编译错误!!

    }

    }

    public static void main(String[] args) {

    // 无需依赖外部类对象,直接创建内部类对象

    //        InnerClassTest.StaticClass staticClassObj = new InnerClassTest.StaticClass();

    InnerClassTest outerObj = new InnerClassTest();

    }

    }

    结果:

    5d32bf27f357287be3dbe0e7a97d315d.png

    可以看到,静态内部类就像外部类的一个静态成员一样,创建其对象无需依赖外部类对象(访问一个类的静态成员也无需依赖这个类的对象,因为它是独立于所有类的对象的)。但是于此同时,静态内部类中也无法访问外部类的非静态成员,因为外部类的非静态成员是属于每一个外部类对象的,而本身静态内部类就是独立外部类对象存在的,所以静态内部类不能访问外部类的非静态成员,而外部类依然可以访问静态内部类对象的所有访问权限的成员,这一点和普通内部类无异。

    匿名内部类

    匿名内部类有多种形式,其中最常见的一种形式莫过于在方法参数中新建一个接口对象 / 类对象,并且实现这个接口声明 / 类中原有的方法了:

    public class InnerClassTest {

    public int field1 = 1;

    protected int field2 = 2;

    int field3 = 3;

    private int field4 = 4;

    public InnerClassTest() {

    System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");

    }

    // 自定义接口

    interface OnClickListener {

    void onClick(Object obj);

    }

    private void anonymousClassTest() {

    // 在这个过程中会新建一个匿名内部类对象,

    // 这个匿名内部类实现了 OnClickListener 接口并重写 onClick 方法

    OnClickListener clickListener = new OnClickListener() {

    // 可以在内部类中定义属性,但是只能在当前内部类中使用,

    // 无法在外部类中使用,因为外部类无法获取当前匿名内部类的类名,

    // 也就无法创建匿名内部类的对象

    int field = 1;

    @Override

    public void onClick(Object obj) {

    System.out.println("对象 " + obj + " 被点击");

    System.out.println("其外部类的 field1 字段的值为: " + field1);

    System.out.println("其外部类的 field2 字段的值为: " + field2);

    System.out.println("其外部类的 field3 字段的值为: " + field3);

    System.out.println("其外部类的 field4 字段的值为: " + field4);

    }

    };

    // new Object() 过程会新建一个匿名内部类,继承于 Object 类,

    // 并重写了 toString() 方法

    clickListener.onClick(new Object() {

    @Override

    public String toString() {

    return "obj1";

    }

    });

    }

    public static void main(String[] args) {

    InnerClassTest outObj = new InnerClassTest();

    outObj.anonymousClassTest();

    }

    }

    来看看结果:

    b70bab7679679fe17d6c8f94e9387e84.png

    上面的代码中展示了常见的两种使用匿名内部类的情况:

    直接 new 一个接口,并实现这个接口声明的方法,在这个过程其实会创建一个匿名内部类实现这个接口,并重写接口声明的方法,然后再创建一个这个匿名内部类的对象并赋值给前面的 OnClickListener 类型的引用;

    new 一个已经存在的类 / 抽象类,并且选择性的实现这个类中的一个或者多个非 final 的方法,这个过程会创建一个匿名内部类对象继承对应的类 / 抽象类,并且重写对应的方法。

    同样的,在匿名内部类中可以使用外部类的属性,但是外部类却不能使用匿名内部类中定义的属性,因为是匿名内部类,因此在外部类中无法获取这个类的类名,也就无法得到属性信息。

    局部内部类

    局部内部类使用的比较少,其声明在一个方法体 / 一段代码块的内部,而且不在定义类的定义域之内便无法使用,其提供的功能使用匿名内部类都可以实现,而本身匿名内部类可以写得比它更简洁,因此局部内部类用的比较少。来看一个局部内部类的小例子:

    public class InnerClassTest {

    public int field1 = 1;

    protected int field2 = 2;

    int field3 = 3;

    private int field4 = 4;

    public InnerClassTest() {

    System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");

    }

    private void localInnerClassTest() {

    // 局部内部类 A,只能在当前方法中使用

    class A {

    // static int field = 1; // 编译错误!局部内部类中不能定义 static 字段

    public A() {

    System.out.println("创建 " + A.class.getSimpleName() + " 对象");

    System.out.println("其外部类的 field1 字段的值为: " + field1);

    System.out.println("其外部类的 field2 字段的值为: " + field2);

    System.out.println("其外部类的 field3 字段的值为: " + field3);

    System.out.println("其外部类的 field4 字段的值为: " + field4);

    }

    }

    A a = new A();

    if (true) {

    // 局部内部类 B,只能在当前代码块中使用

    class B {

    public B() {

    System.out.println("创建 " + B.class.getSimpleName() + " 对象");

    System.out.println("其外部类的 field1 字段的值为: " + field1);

    System.out.println("其外部类的 field2 字段的值为: " + field2);

    System.out.println("其外部类的 field3 字段的值为: " + field3);

    System.out.println("其外部类的 field4 字段的值为: " + field4);

    }

    }

    B b = new B();

    }

    //        B b1 = new B(); // 编译错误!不在类 B 的定义域内,找不到类 B,

    }

    public static void main(String[] args) {

    InnerClassTest outObj = new InnerClassTest();

    outObj.localInnerClassTest();

    }

    }

    同样的,在局部内部类里面可以访问外部类对象的所有访问权限的字段,而外部类却不能访问局部内部类中定义的字段,因为局部内部类的定义只在其特定的方法体 / 代码块中有效,一旦出了这个定义域,那么其定义就失效了,就像代码注释中描述的那样,即外部类不能获取局部内部类的对象,因而无法访问局部内部类的字段。最后看看运行结果:

    585b22d4371880ec74d2456d82e2e95c.png

    内部类的嵌套

    内部类的嵌套,即为内部类中再定义内部类,这个问题从内部类的分类角度去考虑比较合适:

    普通内部类:在这里我们可以把它看成一个外部类的普通成员方法,在其内部可以定义普通内部类(嵌套的普通内部类),但是无法定义 static 修饰的内部类,就像你无法在成员方法中定义 static 类型的变量一样,当然也可以定义匿名内部类和局部内部类;

    静态内部类:因为这个类独立于外部类对象而存在,我们完全可以将其拿出来,去掉修饰它的 static 关键字,他就是一个完整的类,因此在静态内部类内部可以定义普通内部类,也可以定义静态内部类,同时也可以定义 static 成员;

    匿名内部类:和普通内部类一样,定义的普通内部类只能在这个匿名内部类中使用,定义的局部内部类只能在对应定义域内使用;

    局部内部类:和匿名内部类一样,但是嵌套定义的内部类只能在对应定义域内使用。

    深入理解内部类

    不知道小伙伴们对上面的代码有没有产生疑惑:非静态内部类可以访问外部类所有访问权限修饰的字段(即包括了 private 权限的),同时,外部类也可以访问内部类的所有访问权限修饰的字段。而我们知道,private 权限的字段只能被当前类本身访问。然而在上面我们确实在代码中直接访问了对应外部类 / 内部类的 private 权限的字段,要解除这个疑惑,只能从编译出来的类下手了,为了简便,这里采用下面的代码进行测试:

    public class InnerClassTest {

    int field1 = 1;

    private int field2 = 2;

    public InnerClassTest() {

    InnerClassA inner = new InnerClassA();

    int v = inner.x2;

    }

    public class InnerClassA {

    int x1 = field1;

    private int x2 = field2;

    }

    }

    我在外部类中定义了一个默认访问权限(同一个包内的类可以访问)的字段 field1, 和一个 private 权限的字段 field2 ,并且定义了一个内部类 InnerClassA ,并且在这个内部类中也同样定义了两个和外部类中定义的相同修饰权限的字段,并且访问了外部类对应的字段。最后在外部类的构造方法中我定义了一个方法内变量赋值为内部类中 private 权限的字段。我们用 javac 命令(javac InnerClassTest.java)编译这个 .java 文件,会得到两个 .classs 文件。InnerClassTest.class 和 InnerClassTest$InnerClassA.class,我们再用 javap -c 命令(javap -c InnerClassTest 和 javap -c InnerClassTest$InnerClassA)分别反编译这两个 .class 文件,InnerClassTest.class 的字节码如下:

    0536fbd5a3007a497d7f5aeeed304d59.png

    我们注意到字节码中多了一个默认修饰权限并且名为 access$100 的静态方法,其接受一个 InnerClassTest 类型的参数,即其接受一个外部类对象作为参数,方法内部用三条指令取到参数对象的 field2 字段的值并返回。由此,我们现在大概能猜到内部类对象是怎么取到外部类的 private 权限的字段了:就是通过这个外部类提供的静态方法。

    类似的,我们注意到 24 行字节码指令 invokestatic ,这里代表执行了一个静态方法,而后面的注释也写的很清楚,调用的是 InnerClassTest$InnerClassA.access$000 方法,即调用了内部类中名为 access$000 的静态方法,根据我们上面的外部类字节码规律,我们也能猜到这个方法就是内部类编译过程中编译器自动生成的,那么我们赶紧来看一下 InnerClassTest$InnerClassA 类的字节码吧:

    703e267a92cae5e46b9d559db93e06b7.png

    果然,我们在这里发现了名为 access$000 的静态方法,并且这个静态方法接受一个 InnerClassTest$InnerClassA 类型的参数,方法的作用也很简单:返回参数代表的内部类对象的 x2 字段值。

    我们还注意到编译器给内部类提供了一个接受 InnerClassTest 类型对象(即外部类对象)的构造方法,内部类本身还定义了一个名为 this$0 的 InnerClassTest 类型的引用,这个引用在构造方法中指向了参数所对应的外部类对象。

    最后,我们在 25 行字节码指令发现:内部类的构造方法通过 invokestatic 指令执行外部类的 access$100 静态方法(在 InnerClassTest 的字节码中已经介绍了)得到外部类对象的 field2 字段的值,并且在后面赋值给 x2 字段。这样的话内部类就成功的通过外部类提供的静态方法得到了对应外部类对象的 field2 。

    上面我们只是对普通内部类进行了分析,但其实匿名内部类和局部内部类的原理和普通内部类是类似的,只是在访问上有些不同:外部类无法访问匿名内部类和局部内部类对象的字段,因为外部类根本就不知道匿名内部类 / 局部内部类的类型信息(匿名内部类的类名被隐匿,局部内部类只能在定义域内使用)。但是匿名内部类和局部内部类却可以访问外部类的私有成员,原理也是通过外部类提供的静态方法来得到对应外部类对象的私有成员的值。而对于静态内部类来说,因为其实独立于外部类对象而存在,因此编译器不会为静态内部类对象提供外部类对象的引用,因为静态内部类对象的创建根本不需要外部类对象支持。但是外部类对象还是可以访问静态内部类对象的私有成员,因为外部类可以知道静态内部类的类型信息,即可以得到静态内部类的对象,那么就可以通过静态内部类提供的静态方法来获得对应的私有成员值。来看一个简单的代码证明:

    public class InnerClassTest {

    int field1 = 1;

    private int field2 = 2;

    public InnerClassTest() {

    InnerClassA inner = new InnerClassA();

    int v = inner.x2;

    }

    // 这里改成了静态内部类,因而不能访问外部类的非静态成员

    public static class InnerClassA {

    private int x2 = 0;

    }

    }

    同样的编译步骤,得到了两个 .class 文件,这里看一下内部类的 .class 文件反编译的字节码 InnerClassTest$InnerClassA:

    cc910d8a657110ab1b7b485178a577bb.png

    仔细看一下,确实没有找到指向外部类对象的引用,编译器只为这个静态内部类提供了一个无参构造方法。

    而且因为外部类对象需要访问当前类的私有成员,编译器给这个静态内部类生成了一个名为 access$000 的静态方法,作用已不用我多说了。如果我们不看类名,这个类完全可以作为一个普通的外部类来看,这正是静态内部类和其余的内部类的区别所在:静态内部类对象不依赖其外部类对象存在,而其余的内部类对象必须依赖其外部类对象而存在。

    OK,到这里问题都得到了解释:在非静态内部类访问外部类私有成员 / 外部类访问内部类私有成员 的时候,对应的外部类 / 外部类会生成一个静态方法,用来返回对应私有成员的值,而对应外部类对象 / 内部类对象通过调用其内部类 / 外部类提供的静态方法来获取对应的私有成员的值。

    内部类和多重继承

    我们已经知道,Java 中的类不允许多重继承,也就是说 Java 中的类只能有一个直接父类,而 Java 本身提供了内部类的机制,这是否可以在一定程度上弥补 Java 不允许多重继承的缺陷呢?我们这样来思考这个问题:假设我们有三个基类分别为 A、B、C,我们希望有一个类 D 达成这样的功能:通过这个 D 类的对象,可以同时产生 A 、B 、C 类的对象,通过刚刚的内部类的介绍,我们也应该想到了怎么完成这个需求了,创建一个类 D.java:

    class A {}

    class B {}

    class C {}

    public class D extends A {

    // 内部类,继承 B 类

    class InnerClassB extends B {

    }

    // 内部类,继承 C 类

    class InnerClassC extends C {

    }

    // 生成一个 B 类对象

    public B makeB() {

    return new InnerClassB();

    }

    // 生成一个 C 类对象

    public C makeC() {

    return new InnerClassC();

    }

    public static void testA(A a) {

    // ...

    }

    public static void testB(B b) {

    // ...

    }

    public static void testC(C c) {

    // ...

    }

    public static void main(String[] args) {

    D d = new D();

    testA(d);

    testB(d.makeB());

    testC(d.makeC());

    }

    }

    程序正确运行。而且因为普通内部类可以访问外部类的所有成员并且外部类也可以访问普通内部类的所有成员,因此这种方式在某种程度上可以说是 Java 多重继承的一种实现机制。但是这种方法也是有一定代价的,首先这种结构在一定程度上破坏了类结构,一般来说,建议一个 .java 文件只包含一个类,除非两个类之间有非常明确的依赖关系(比如说某种汽车和其专用型号的轮子),或者说一个类本来就是为了辅助另一个类而存在的(比如说上篇文章介绍的 HashMap 类和其内部用于遍历其元素的 HashIterator 类),那么这个时候使用内部类会有较好代码结构和实现效果。而在其他情况,将类分开写会有较好的代码可读性和代码维护性。

    内部类和内存泄露

    在这一小节开始前介绍一下什么是内存泄露:即指在内存中存在一些其内存空间可以被回收的对象因为某些原因又没有被回收,因此产生了内存泄露,如果应用程序频繁发生内存泄露可能会产生很严重的后果(内存中可用的空间不足导致程序崩溃,甚至导致整个系统卡死)。

    听起来怪吓人的,这个问题在一些需要开发者手动申请和释放内存的编程语言(C/C++)中会比较容易产生,因为开发者申请的内存需要手动释放,如果忘记了就会导致内存泄露,举个简单的例子(C++):

    #include 

    int main() {

    // 申请一段内存,空间为 100 个 int 元素所占的字节数

    int *p = new int[100];

    // C++ 11

    p = nullptr;

    return 0;

    }

    在这段代码里我有意而为之:在为指针 p 申请完内存之后将其直接赋值为 nullptr ,这是 C++ 11 中一个表示空指针的关键字,我们平时常用的 NULL 只是一个值为 0 的常量值,在进行方法重载传参的时候可能会引起混淆。之后我直接返回了,虽然在程序结束之后操作系统会回收我们程序中申请的内存,但是不可否认的是上面的代码确实产生了内存泄露(申请的 100 个 int 元素所占的内存无法被回收)。这只是一个最简单不过的例子。我们在写这类程序的时候当动态申请的内存不再使用时,应该要主动释放申请的内存:

    #include 

    int main() {

    // 申请一段内存,空间为 100 个 int 元素所占的字节数

    int *p = new int[100];

    // 释放 p 指针所指向的内存空间

    delete[] p;

    // C++ 11

    p = nullptr;

    return 0;

    }

    而在 Java 中,因为 JVM 有垃圾回收功能,对于我们自己创建的对象无需手动回收这些对象的内存空间,这种机制确实在一定程度上减轻了开发者的负担,但是也增加了开发者对 JVM 垃圾回收机制的依赖性,从某个方面来说,也是弱化了开发者防止内存泄露的意识。当然,JVM 的垃圾回收机制的利是远远大于弊的,只是我们在开发过程中不应该丧失了这种对象和内存的意识。

    回到正题,内部类和内存泄露又有什么关系呢?在继续阅读之前,请确保你对 JVM 的在进行垃圾回收时如何找出内存中不再需要的对象有一定的了解,如果你对这个过程不太了解,你可以参考一下 这篇文章 中对这个过程的简单介绍。我们在上面已经知道了,创建非静态内部类的对象时,新建的非静态内部类对象会持有对外部类对象的引用,这个我们在上面的源码反编译中已经介绍过了,正是因为非静态内部类对象会持有外部类对象的引用,因此如果说这个非静态内部类对象因为某些原因无法被回收,就会导致这个外部类对象也无法被回收,这个听起来是有道理的,因为我们在上文也已经介绍了:非静态内部类对象依赖于外部类对象而存在,所以内部类对象没被回收,其外部类对象自然也不能被回收。但是可能存在这种情况:非静态内部类对象在某个时刻已经不在被使用,或者说这个内部类对象可以在不影响程序正确运行的情况下被回收,而因为我们对这个内部类的使用不当而使得其无法被 JVM 回收,同时会导致其外部类对象无法被回收,即为发生内存泄露。那么这个 “使用不当” 具体指的是哪个方面呢?看一个简单的例子,新建一个 MemoryLeakTest 的类:

    public class MemoryLeakTest {

    // 抽象类,模拟一些组件的基类

    abstract static class Component {

    final void create() {

    onCreate();

    }

    final void destroy() {

    onDestroy();

    }

    // 子类实现,模拟组件创建的过程

    abstract void onCreate();

    // 子类实现,模拟组件摧毁的过程

    abstract void onDestroy();

    }

    // 具体某个组件

    static class MyComponent extends Component {

    // 组件中窗口的单击事件监听器

    static OnClickListener clickListener;

    // 模拟组件中的窗口

    MyWindow myWindow;

    @Override

    void onCreate() {

    // 执行组件内一些资源初始化的代码

    clickListener = new OnClickListener() {

    @Override

    public void onClick(Object obj) {

    System.out.println("对象 " + obj + " 被单击");

    }

    };

    // 新建我的窗口对象,并设置其单击事件监听器

    myWindow = new MyWindow();

    myWindow.setClickListener(clickListener);

    }

    @Override

    void onDestroy() {

    // 执行组件内一些资源回收的代码

    myWindow.removeClickListener();

    }

    }

    // 我的窗口类,模拟一个可视化控件

    static class MyWindow {

    OnClickListener clickListener;

    // 设置当前控件的单击事件监听器

    void setClickListener(OnClickListener clickListener) {

    this.clickListener = clickListener;

    }

    // 移除当前控件的单击事件监听器

    void removeClickListener() {

    this.clickListener = null;

    }

    }

    // 对象的单击事件的监听接口

    public interface OnClickListener {

    void onClick(Object obj);

    }

    public static void main(String[] args) {

    MyComponent myComponent = new MyComponent();

    myComponent.create();

    myComponent.destroy();

    // myComponent 引用置为 null,排除它的干扰

    myComponent = null;

    // 调用 JVM 的垃圾回收动作,回收无用对象

    System.gc();

    System.out.println("");

    }

    }

    我们在代码中添加一些断点,然后采用 debug 模式查看:

    ca4e738bdf06a487f69e01df6a9aa0b9.png

    程序执行到 72 行代码,此时 72 行代码还未执行,因此 myComponent 引用和其对象还未创建,继续执行:

    e3b22d959981e90b683cf92f7581522c.png

    这里成功创建了一个 MyComponent 对象,但是其 create 方法还未执行,所以 myWindow 字段为 null,这里可能有小伙伴会问了,myComponent 对象的 clickListener 字段呢?怎么不见了?其实这和我们在代码中定义 clickListener 字段的形式有关,我们定义的是 static OnClickListener clickListener; ,因此 clickListener 是一个静态字段,其在类加载的完成的时候储存在 JVM 中内存区域的 方法区 中,而创建的 Java 对象储存在 JVM 的堆内存中,两者不在同一块内存区域。关于这些细节,想深入了解的小伙伴建议阅读《深入理解JVM虚拟机》。好了,我们继续执行代码:

    13d6856e38645a15f3de483d31fb5d11.png

    myComponent.create 方法执行完成之后创建了 OnClickListener 内部类对象,并且为 myWindow 对象设置 OnCLickListener 单击事件监听。我们继续:

    a773b0fd12a1e56645ae6100d78e82ee.png

    myComponent.destroy 方法执行完成之后,myWindow.removeClickListener 方法也执行完成,此时 myWindow 对象中的 clickListener字段为 null。我们继续:

    c77f768f9b694ae7ba6c0aa7e8d1c929.png

    代码执行到了 80 行,在此之前,所有的代码和解释都没有什么难度,跟着运行图走,一切都那么顺利成章,其实这张图的运行结果也很好理解,只不过图中的文字需要思考一下:myComponent 引用指向的对象真的被回收了吗?要解答这个问题,我们需要借助 Java 中提供的内存分析工具 jvisualvm (以前它还不叫这个名字…),它一般在你安装 JDK 的目录下的 bin 子目录下:

    5ae9d9ca57aa4445b6910b4d5015ff45.png

    我们运行这个程序:

    e743560a0bfe456f756fb3622c7856d3.png

    在程序左边可以找到我们当前正在执行的 Java 进程,双击进入:

    30bdca25e88dddd665240d30eab58b0f.png

    单击 tab 中的 监视 选项卡,可以看到当前正在执行的 Java 进程的一些资源占用信息,当然我们现在的主要目的是分析内存,那么们单击右上角的 堆 Dump :

    198397ce1aaa7106ea5ff0e811d04d11.png

    在这个界面,单击 类 选项卡,会出现当前 Java 进程中用到的所有的类,我们已经知道我们要查找的类的对象只创建了一个,因此我们根据右上角的 实例数 来进行排除:我们成功的找到了我们创建的对象!而这样也意味着当我们在上面代码中调用 JVM 的垃圾回收动作没有回收这三个对象,这其实就是一个真真切切的内存泄露!因为我们将 main 方法中的 myComponent 引用赋值为 null,就意味着我们已经不再使用这个组件和里面的一些子组件(MyWindow 对象),即这个组件和其内部的一些组件应该被回收。但是调用 JVM 的垃圾回收却并没有将其对应的对象回收。造成这个问题的原因在哪呢?

    其实就在于我们刚刚在 MyComponent 类中定义的 clickListener 字段,我们在代码中将其定义成了 static 类型的,同时这个字段又指向了一个匿名内部类对象(在 create 方法中 创建了一个 OnClickListener 接口对象,即通过一个匿名内部类实现这个接口并创建其对象),根据 JVM 寻找和标记无用对象的规则(可达性分析算法),其会将 clickListener 字段作为一个 “root” ,并通过它来寻找还有用的对象,在这个例子中,clickListener 字段指向一个匿名内部类对象,这个匿名内部类对象有一个外部类对象(MyComponent 类型的对象)的引用,而外部类对象中又有一个 MyWindow 类型的对象引用。因此 JVM 会将这三个对象都视为有用的对象不会回收。用图来解释吧:

    a5979db4dbf3bebfda98d5adf914b591.png

    Ok,通过这个过程,相信你已经理解了造成此次内存泄露的原因了,那么我们该如何解决呢?对于当前这个例子,我们只需要改一些代码:

    把 MyComponent 类中的 clickListener 字段前面的 static 修饰符去掉就可以了(static OnClickListener clickListener; -> OnClickListener clickListener;),这样的话 clickListener 指向的对象,就作为 MyComponent 类的对象的一部分了,在 MyComponent 对象被回收时里面的子组件也会被回收。同时它们之间也只是互相引用(MyComponent 外部类对象中有一个指向 OnClickListener 内部类对象的引用,OnClickListener 内部类对象有一个指向 MyComponent 外部类对象的引用),根据 JVM 的 “可达性分析” 算法,在两个对象都不再被外部使用时,JVM 的垃圾回收机制是可以标记并回收这两个对象的。 虽然不强制要求你在 MyComponent 类中的 onDestroy 方法中将其 clickListener 引用赋值为 null,但是我还是建议你这样做,因为这样更能确保你的程序的安全性(减少发生内存泄露的机率,毕竟匿名内部类对象会持有外部类对象的引用),在某个组件被销毁时将其内部的一些子组件进行合理的处理是一个很好的习惯。

    你也可以自定义一个静态内部类或者是另外自定义一个类文件,并实现 OnClickListener 接口,之后通过这个类创建对象,这样就可以避免通过非静态内部类的形式创建 OnClickListener 对象增加内存泄露的可能性。

    避免内存泄漏

    那么我们在日常开发中怎么合理的使用内部类来避免产生内存泄露呢?这里给出一点我个人的理解:

    能用静态内部类就尽量使用静态内部类,从上文中我们也知道了,静态内部类的对象创建不依赖外部类对象,即静态内部对象不会持有外部类对象的引用,自然不会因为静态内部类对象而导致内存泄露,所以如果你的内部类中不需要访问外部类中的一些非 static 成员,那么请把这个内部类改造成静态内部类;

    对于一些自定义类的对象,慎用 static 关键字修饰(除非这个类的对象的声明周期确实应该很长),我们已经知道,JVM 在进行垃圾回收时会将 static 关键字修饰的一些静态字段作为 “root” 来进行存活对象的查找,所以程序中 static 修饰的对象越多,对应的 “root” 也就越多,每一次 JVM 能回收的对象就越少。 当然这并不是建议你不使用 static 关键字,只是在使用这个关键字之前可以考虑一下这个对象使用 static 关键字修饰对程序的执行确实更有利吗?

    为某些组件(大型)提供一个当这个大型组件需要被回收的时候用于合理处理其中的一些小组件的方法(例如上面代码中 MyComponent 的 onDestroy 方法),在这个方法中,确保正确的处理一些需要处理的对象(将某些引用置为 null、释放一些其他(CPU…)资源)

    展开全文
  • java静态内部类作用

    千次阅读 2021-05-17 14:17:41
    1)首先,用内部类是因为内部类与所在外部类有一定的关系,往往只有该外部类调用此内部类。所以没有必要专门用一个Java文件存放这个类。 2)静态都是用来修饰类的内部成员的。比如静态方法,静态成员变量,静态常量...
  • Java内部类作用

    2013-01-21 16:28:40
    Java内部类作用
  • Java内部类以及使用场景

    千次阅读 2020-12-15 19:14:36
    1. 它体现了一种代码的隐藏机制和访问控制机制,内部类与所在外部类有一定的关系,往往只有该外部类调用此内部类,所以没有必要专门用一个Java文件存放这个类。 public class Outer {  private int num;  ...
  • java普通内部类和静态内部类的区别

    千次阅读 2022-03-12 17:41:19
    java普通内部类和静态内部类的区别
  • 本文主要介绍了Java内部类的基本原理,使用方法和各种细节。 有关内部类实现回调,事件驱动和委托机制的文章将在后面发布。 具体代码在我的GitHub中可以找到 https://github.com/h2pl/MyTech 文章首发于我的个人...
  • Java 内部类和异常类(详细版)】

    千次阅读 多人点赞 2021-04-08 17:24:17
    Java支持在一个类中声明另一个类,这样的类称作内部类,而包含内部类的类成为内部类的外嵌类。 内部类的类体中不可以声明类变量和类方法。外嵌类的类体中可以用内部类声明对象,作为外嵌类的成员。 内部类的使用规则...
  • java 内部类和静态内部类的区别

    千次阅读 2019-02-21 11:11:58
    今天我们分析内部类和静态内部类的区别,首先我们写一个 InnerClassTest 类,其中有一个内部类 InnerClass 和静态内部类 StaticInnerClass 。 ...有什么作用?...java内部类有什么好处?为什么需要内部类?
  • java中的内部类与使用场景

    千次阅读 2022-04-01 22:48:12
    java内部类分为:成员内部类、静态内部类、局部内部类、匿名内部类。 使用场景: 成员内部类:不想被其他类公开使用的类,因为外部类只能定义为public或者缺省。 静态内部类:一个类与另一个类关联性比较强,但是又...
  • Java 静态内部类作用

    千次阅读 2016-11-15 05:19:34
    需要了解2个概念:内部类和静态修饰符static 1)首先,用内部类是因为内部类与所在外部类有一定的关系,往往只有该外部类调用此内部类。所以没有必要专门用一个Java文件存放这个类。 2)静态都是用来修饰类的内部...
  • Java内部类序列化

    千次阅读 2018-12-27 15:10:02
    一个类能够序列化,需要满足两个条件: ​​​​​​​类本身实现序列化接口Serializable 类所有的成员属性实现序列化... inner class 内部类(非静态)  local class 本地类(定义在方法内部)  anonymous cla...
  • Java内部类(一篇就够)

    千次阅读 多人点赞 2020-08-14 16:05:06
    Java内部类,相信大家都用过,但是多数同学可能对它了解的并不深入,只是靠记忆来完成日常工作,却不能融会贯通,遇到奇葩问题更是难以有思路去解决。这篇文章带大家一起死磕Java内部类的方方面面。 友情提示:这篇...
  • 详细介绍了Java中的内部类的概念,及各种内部类的访问案例演示。包括非静态内部类、静态内部类、局部内部类、匿名内部类
  • Java内部类 (详细讲述java内部类

    千次阅读 2022-06-26 00:29:07
    深入浅出讲述java内部类以及使用。。。。
  • Java中的内部类如何理解——详解

    千次阅读 多人点赞 2022-05-01 10:13:02
    今天给大家讲解的是内部类以及匿名内部类,希望能够对你在Java的学习过程中起到一定的帮助。
  • Java 静态内部类 的详细解释

    千次阅读 2021-03-11 02:30:47
    写法格式: 所处的位置跟常规内部类一样,不同的是 在常规内部类前面加上static关键字,静态的形式如下: 01:静态内部类 的成员可以是 静态的数据域 和 静态的方法 ,因为这个类是静态的, 在加载的时候一并被加载...
  • Java静态内部类作用

    千次阅读 2019-02-22 05:00:22
    在一个类中创建另外一个类,叫做成员内部类。这个成员内部类可以静态...不过在特定的情况下,静态内部类也能够发挥其独特的作用。   一、静态内部类的使用目的。   在定义内部类的时候,可以在其前面加上一个权...
  • Java -- 成员内部类不能含有static成员(汇总)

    千次阅读 多人点赞 2020-11-15 14:14:51
    文章目录1、为什么内部类中...却可以有常量6、java内部类有什么好处?为什么需要内部类?静态内部类局部内部类匿名内部类7、Java静态内部类(static class) 1、为什么内部类中有static成员时内部类也必须声明为static &
  • java匿名内部类什么是匿名内部类?匿名内部类的使用场景?匿名内部类如何创建?匿名内部类的实现和使用例1(实现接口)例2(继承类) 什么是匿名内部类? 匿名内部类,顾名思义,就是不知道这个类到底是什么名字,也...
  • 什么是JAVA内部类

    万次阅读 2019-04-23 13:58:14
    什么是JAVA内部类? 1、概念 存在于Java类的内部的Java类。 2、分类 成员内部类 格式 class OuterClass { class InnerClass {} //成员内部类 } 编译之后会生成两个class文件:OuterClass.class和...
  • Java基础】浅析内部类

    千次阅读 2022-04-10 23:41:22
    静态内部类 package classLoader; /** * Created by IntelliJ IDEA. * User: luna * Date: 2022/4/10 */ public class StaticInnerClassTest { public static void main(String[] args) { double d[]=new ...
  • 内部类可以实现 java 单继承的缺陷 当我们想要定义一个回调函数却不想写大量代码的时候我们可以选择使用匿名内部类来实现 下面我们就一一来看这四点… 1.1 访问类作用域中的数据 当外部类的对象创建了一个内部类的...
  • 详谈Java内部类(超详细代码示例)

    万次阅读 多人点赞 2018-09-30 00:30:44
    内部类 废话不多上,先上图:
  • 1.开篇 在 Java 这个面向对象的编程语言中,我们几乎离不开“类”这个概念...在 Java 语言中,内部类是指在一个外部类的内部再定义一个类。而对于这个内部类来说,它可以是静态 static 的,也可以用(访问修饰符)..
  • Java内部类、修饰符

    千次阅读 2018-10-17 18:10:51
    内部类作用 内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。 内部内成员可以直接访问外部类的私有数据,因为内部类被当做外部内的成员,同一个类的成员之间可以...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 513,902
精华内容 205,560
关键字:

java内部类的作用