zoukankan      html  css  js  c++  java
  • 内部类

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

    一、内部类的作用

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

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

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

    为什么可以引用:

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

    1. 编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象(this)的引用;
    2. 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为内部类中添加的成员变量赋值;
    3. 在调用内部类的构造函数初始化内部类对象时,会默认传入外部类的引用。
    /**
     * 内部类无条件访问外部类元素
     */
    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定义的私有变量。这个变量在内部类中可以无条件地访问.

    1.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的,所以我们如果不看代码的话根本看不到这个具体类的名字,所以说它可以很好的实现隐藏。

    1.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的方法和属性,也就间接地实现了多继承。

    1.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();
        }
    }
    

    三、内部类的分类

    3.1、普通内部类

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

    public class InnerClassTest {
    
        public class InnerClassA {
            
        }
    }
    

    在这里 InnerClassA 类为 InnerClassTest 类的普通内部类,在这种定义方式下

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

    public class InnerClassTest {
        public int outField1 = 1;
        protected int outField2 = 2;
        int outField3 = 3;
        private int outField4 = 4;
    
    
        public void tt(){
            System.out.println("测试方法");
        }
        /**
         * 在外部类中通过内部类的对象的引用访问内部类的成员变量
         */
        public InnerClassTest() {
            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 = 5;
            protected int field2 = 6;
            int field3 = 7;
            private int field4 = 8;
    //      static int field5 = 5; // 编译错误!普通内部类中不能定义 static 属性
    
            //在内部类中可以直接访问外部类的成员变量
            public InnerClassA() {
                System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
                System.out.println("其外部类的 outField1 字段的值为: " + outField1);
                System.out.println("其外部类的 outField2 字段的值为: " + outField2);
                System.out.println("其外部类的 outField3 字段的值为: " + outField3);
                System.out.println("其外部类的 outField4 字段的值为: " + outField4);
                tt();
            }
        }
    
        public static void main(String[] args) {
            InnerClassTest outerObj = new InnerClassTest();
            // 不在外部类内部,使用:外部类对象. new 内部类构造器(); 的方式创建内部类对象
            //InnerClassA innerObj = outerObj.new InnerClassA();
        }
    }
    

    我们注意到,内部类对象可以访问外部类对象中所有访问权限的字段,同时,外部类对象也可以通过内部类的对象引用来访问内部类中定义的所有访问权限的字段

    3.2、局部内部类

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

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

    • 局部内类不允许使用访问权限修饰符 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();
        }
    }
    

    3.3、匿名内部类

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

    • 匿名内部类是没有访问修饰符的。
    • 匿名内部类必须继承一个抽象类或者实现一个接口
    • 匿名内部类中不能存在任何静态成员或方法
    • 匿名内部类是没有构造方法的,因为它没有类名。
    • 与局部内部类相同匿名内部类也可以引用局部变量此变量也必须声明为 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类型(只能使用,不能赋值)。

    3.4、静态内部类

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

    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();
        }
    }
    
    

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

    四、内部类

    四、内部类的嵌套

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

    • 普通内部类:在这里我们可以把它看成一个外部类的普通成员方法,在其内部可以定义普通内部类(嵌套的普通内部类),但是无法定义 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;
        }
    }
    

    我们用 javac 命令(javac InnerClassTest.java)编译这个java 文件,会得到两个 classs 文件:InnerClassTest.class 和InnerClassTest$InnerClassA.class,

    我们再用 javap -c 命令(javap -c InnerClassTestjavap -c InnerClassTest$InnerClassA)分别反编译这两个 .class 文件,InnerClassTest.class 的字节码如下:

    我们注意到字节码中多了一个默认修饰权限并且名为 access$100 的静态方法,其接受一个 InnerClassTest 类型的参数,即其接受一个外部类对象作为参数,方法内部用三条指令取到参数对象的 field2 字段的值并返回。由此,我们现在大概能猜到内部类对象是怎么取到外部类的 private 权限的字段了:就是通过这个外部类提供的静态方法
    类似的,我们注意到 24 行字节码指令 invokestatic ,这里代表执行了一个静态方法,而后面的注释也写的很清楚,调用的是 InnerClassTest$InnerClassA.access$000 方法,即调用了内部类中名为 access$000 的静态方法,根据我们上面的外部类字节码规律,我们也能猜到这个方法就是内部类编译过程中编译器自动生成的,那么我们赶紧来看一下 InnerClassTest$InnerClassA 类的字节码吧:

    果然,我们在这里发现了名为 access$000 的静态方法,并且这个静态方法接受一个 InnerClassTest$InnerClassA 类型的参数,方法的作用也很简单:返回参数代表的内部类对象的 x2 字段值。
    我们还注意到编译器给内部类提供了一个接受 InnerClassTest 类型对象(即外部类对象)的构造方法,内部类本身还定义了一个名为 this$0InnerClassTest 类型的引用,这个引用在构造方法中指向了参数所对应的外部类对象。
    最后,我们在 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

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

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

    六、内部类和内存泄露

    在这一小节开始前介绍一下什么是内存泄露:即指在内存中存在一些其内存空间可以被回收的对象因为某些原因又没有被回收,因此产生了内存泄露,如果应用程序频繁发生内存泄露可能会产生很严重的后果(内存中可用的空间不足导致程序崩溃,甚至导致整个系统卡死)。
    听起来怪吓人的,这个问题在一些需要开发者手动申请和释放内存的编程语言(C/C++)中会比较容易产生,因为开发者申请的内存需要手动释放,如果忘记了就会导致内存泄露,举个简单的例子(C++):

    #include <iostream>
    
    int main() {
    	// 申请一段内存,空间为 100 个 int 元素所占的字节数
    	int *p = new int[100];
    	// C++ 11
    	p = nullptr;
    	return 0;
    }
    

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

    #include <iostream>
    
    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 模式查看:

    参考博客:https://blog.csdn.net/Hacker_ZhiDian/article/details/82193100?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param

  • 相关阅读:
    ASP.NET缓存全解析(系列)
    updateprogress用法技巧
    text与img对齐
    9款Firefox插件提高设计开发效率
    ASP.NET页面实时进行GZIP压缩优化
    如何收缩和删除SQL日志文件
    闲扯加班
    与大家分享一点游戏管理晋升的心得(完整版)
    FDS (Flex Data Services)
    和我老婆去旅游
  • 原文地址:https://www.cnblogs.com/jdy1022/p/13897215.html
Copyright © 2011-2022 走看看