精华内容
下载资源
问答
  • 文章目录1 内部类的基本概念1.1 内部类的定义1.2 内部类的优缺点2 创建内部类2.1 在外部类外部创建非静态内部类2.2 在外部类外部创建静态内部类2.3 在外部类内部创建内部类语法3 内部类的分类3.1 成员内部类3.2 静态...

    1. 内部类的基本概念

    1.1 内部类的定义

    内部类: 所谓内部类就是在一个类内部进行其他类结构的嵌套操作。

    class Outer{
        private String str ="外部类中的字符串";
        //************************** 
        //定义一个内部类
        class Inner{
            private String inStr= "内部类中的字符串";
            //定义一个普通方法
            public void print(){
                //调用外部类的str属性
                System.out.println(str);
            }
        }
        //************************** 
        //在外部类中定义一个方法,该方法负责产生内部类对象并调用print()方法
        public void fun(){
            //内部类对象
            Inner in = new Inner();
            //内部类对象提供的print
            in.print();
        }
    }
    public class Test{
        public static void main(String[] args)
        {
            //创建外部类对象
            Outer out = new Outer();
            //外部类方法
            out.fun();
        }
    }
    

    运行结果:外部类中的字符串
    但是如果去掉内部类:

    class Outer{
        private String outStr ="Outer中的字符串";
        public String getOutStr()
        {
            return outStr;
        }
        public void fun(){  //2
            //this表示当前对象
            Inner in = new Inner(this); //3
            in.print();                 //5
        }
    }
    class Inner{
        private String inStr= "Inner中的字符串";
        private Outer out;
        //构造注入
        public Inner(Outer out)  //3
        {
            this.out=out;       //4.为Inner中的out变量初始化
        }
        public void print(){    //6
            System.out.println(out.getOutStr()); //7
        }
    } 
    public class Test{
        public static void main(String[] args)
        {
            Outer out = new Outer();  //1.
            out.fun(); //2.
        }
    }
    

    执行结果:Outer中的字符串
    但是去掉内部类之后发现程序更加难以理解。

    1.2 内部类的优缺点

    内部类的优点:

    1. 内部类与外部类可以方便的访问彼此的私有域(包括私有方法、私有属性)。
    2. 内部类是另外一种封装,对外部的其他类隐藏。
    3. 内部类可以实现java的单继承局限。

    内部类的缺点:

    结构复杂。
    记录:使用内部类实现多继承:

    class A {
        private String name = "A类的私有域";
        public String getName() {
            return name;
        }
    }
    class B {
        private int age = 20;
        public int getAge() {
            return age;
        }
    }
    class Outter {
        private class InnerClassA extends A {
            public String name() {
                return super.getName();
        }
    }
        private class InnerClassB extends B {
            public int age() {
                return super.getAge();
        }
    }
        public String name() {
            return new InnerClassA().name();
        }
        public int age() {
            return new InnerClassB().age();
        }
    }
    public class Test2 {
            public static void main(String[] args) {
                Outter outter = new Outter();
                System.out.println(outter.name());
                System.out.println(outter.age());
            }
    }
    

    在这里插入图片描述

    2. 创建内部类

    2.1 在外部类外部 创建非静态内部类

    语法: 外部类.内部类 内部类对象 = new 外部类().new 内部类();
    举例: Outer.Inner in = new Outer().new Inner();

    2.2 在外部类外部 创建静态内部类

    语法: 外部类.内部类 内部类对象 = new 外部类.内部类();
    举例: Outer.Inner in = new Outer.Inner();

    2.3 在外部类内部创建内部类语法

    在外部类内部创建内部类,就像普通对象一样直接创建:Inner in = new Inner();

    3. 内部类的分类

    在Java中内部类主要分为成员内部类、静态内部类、方法内部类、匿名内部类

    3.1 成员内部类

    类比成员方法

    1. 成员内部类内部不允许存在任何static变量或方法 正如成员方法中不能有任何静态属性 (成员方法与对象相关、静态属性与类有关)
    class Outer {
        private String name = "test";
        public  static int age =20;
    
        class Inner{
            public static int num =10;
            public void fun()
            {
                System.out.println(name);
                System.out.println(age);
            }
        }
    }
    public class Test{
        public static void main(String [] args)
        {}
    }
    

    在这里插入图片描述
    2. 成员内部类是依附外部类的,只有创建了外部类才能创建内部类。

    3.2 静态内部类

      关键字static可以修饰成员变量、方法、代码块、其实还可以修饰内部类,使用static修饰的内部类我们称之为静态内部类,静态内部类和非静态内部类之间存在一个最大的区别,非静态内部类在编译完成之后会隐含的保存着一个引用,该引用是指向创建它的外围类,但是静态类没有。没有这个引用就意味着:
      1.静态内部类的创建不需要依赖外部类可以直接创建。
      2.静态内部类不可以使用任何外部类的非static类(包括属性和方法),但可以存在自己的成员变量。

    class Outer {
        public String name = "test";
        private static int age =20;
    
        static class Inner{
            private String name;
            public void fun()
            {
                System.out.println(name);
                System.out.println(age);
            }
        }
    }
    public class Test{
        public static void main(String [] args)
        {
            Outer.Inner in = new Outer.Inner();
        }
    }
    

    在这里插入图片描述

    3.3 方法内部类

    方法内部类顾名思义就是定义在方法里的类
    1.方法内部类不允许使用访问权限修饰符(public、private、protected)均不允许。

    class Outer{
        private int num =5;
        public void dispaly(final int temp)
        {
            //方法内部类即嵌套在方法里面
            public class Inner{
            }
        }
    }
    public class Test{
        public static void main(String[] args)
        {}
    }
    

    在这里插入图片描述
    2. 方法内部类对外部完全隐藏,除了创建这个类的方法可以访问它以外,其他地方均不能访问 (换句话说其他方法或者类都不知道有这个类的存在)方法内部类对外部完全隐藏,出了创建这个类的方法可以访问它,其他地方均不能访问。
    3. 方法内部类如果想要使用方法形参,该形参必须使用final声明(JDK8形参变为隐式final声明)

    class Outer{
        private int num =5;
        //普通方法
        public void dispaly(int temp)
        {
            //方法内部类即嵌套在方法里面
            class Inner{
                public void fun()
                {
                    System.out.println(num);
                    temp++;
                    System.out.println(temp);
                }
            }
            //方法内部类在方法里面创建
            new Inner().fun();
        }
    }
    public class Test{
        public static void main(String[] args)
        {
            Outer out = new Outer();
            out.dispaly(2);
        }
    }
    

    在这里插入图片描述

    3.4 匿名内部类

      匿名内部类就是一个没有名字的方法内部类,因此特点和方法与方法内部类完全一致,除此之外,还有自己的特点:
    1.匿名内部类必须继承一个抽象类或者实现一个接口。
    2.匿名内部类没有类名,因此没有构造方法。

    //匿名内部类
    //声明一个接口
    interface MyInterface {
        //接口中方法没有方法体
        void test();
    }
    class Outer{
        private int num = 5;
        public void dispaly(int temp)
        {
            //匿名内部类,匿名的实现了MyInterface接口
            //隐藏的class声明
            new MyInterface()
            {
                public void test()
                {
                    System.out.println("匿名实现MyInterface接口");
                    System.out.println(temp);
                }
            }.test();
        }
    }
    public class Test{
        public static void main(String[] args)
        {
            Outer out = new Outer();
            out.dispaly(3);
        }
    }
    

    在这里插入图片描述

    4. 内部类与外部类的关系

    • 对于非静态的内部类,内部类的创建依赖外部类的实例对象,在没有外部类实例之前是无法创建内部类的。
    • 内部类可以直接访问外部类的元素(包括私有域)—外部类在内部类之前创建,创建内部类时会将外部类的对象传入
    class Outer{
        //成员变量  与对象有关
        private String msg;
        private int age;
        //--------------------------
        class Inner{
            public void dispaly()
            {
                //此处有一个隐藏的Outer.this
                msg = "test";
                age = 20;
                System.out.println(msg);
                System.out.println(age);
            }
        }
        //--------------------------
        public void test()
        {
            Inner in = new Inner();
            in.dispaly();
        }
    }
    public class Test{
        public static void main(String[] args)
        {
            Outer out = new Outer();
            out.test();
        }
    }
    
    • 外部类可以通过内部类的引用间接访问内部类元素 – -要想访问内部类属性,必须先创建内部类对象
    class Outer{
        public void dispaly()
        {
            //外部类通过创建内部类的对象间接访问内部类元素
            Inner in = new Inner();
            in.dispaly();
        }
        class Inner{
            public void dispaly()
            {
                System.out.println("内部类");
            }
        }
    }
    public class Test1{
        public static void main(String[] args)
        {
            Outer out = new Outer();
            out.dispaly();
        }
    }
    
    • 内部类是一个相对独立的个体,与外部类没有关系。
    展开全文
  • 详解 Java 内部类

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

    *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

    前言

    内部类在 Java 里面算是非常常见的一个功能了,在日常开发中我们肯定多多少少都用过,这里总结一下关于 Java 中内部类的相关知识点和一些使用内部类时需要注意的点。
    从种类上说,内部类可以分为四类:普通内部类、静态内部类、匿名内部类、局部内部类。我们来一个个看:

    本文所有实践代码在 JDK1.8 版本进行验证。

    普通内部类

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

    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 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 = 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);
            }
        }
    
        public static void main(String[] args) {
            InnerClassTest outerObj = new InnerClassTest();
            // 不在外部类内部,使用:外部类对象. new 内部类构造器(); 的方式创建内部类对象
    //        InnerClassA innerObj = outerObj.new InnerClassA();
        }
    }
    

    这里的内部类就像外部类声明的一个属性字段一样,因此其的对象时依附于外部类对象而存在的,我们来看一下结果:
    在这里插入图片描述
    我们注意到,内部类对象可以访问外部类对象中所有访问权限的字段,同时,外部类对象也可以通过内部类的对象引用来访问内部类中定义的所有访问权限的字段,后面我们将从源码里面分析具体的原因。
    我们下面来看一下静态内部类:

    静态内部类

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

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

    结果:
    这里写图片描述

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

    匿名内部类

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

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

    来看看结果:
    这里写图片描述
    上面的代码中展示了常见的两种使用匿名内部类的情况:
    1、直接 new 一个接口,并实现这个接口声明的方法,在这个过程其实会创建一个匿名内部类实现这个接口,并重写接口声明的方法,然后再创建一个这个匿名内部类的对象并赋值给前面的 OnClickListener 类型的引用;
    2、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();
        }
    }
    

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

    内部类的嵌套

    内部类的嵌套,即为内部类中再定义内部类,这个问题从内部类的分类角度去考虑比较合适:
    普通内部类:在这里我们可以把它看成一个外部类的普通成员方法,在其内部可以定义普通内部类(嵌套的普通内部类),但是无法定义 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.classInnerClassTest$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,到这里问题都得到了解释:在非静态内部类访问外部类私有成员 / 外部类访问内部类私有成员 的时候,对应的外部类 / 外部类会生成一个静态方法,用来返回对应私有成员的值,而对应外部类对象 / 内部类对象通过调用其内部类 / 外部类提供的静态方法来获取对应的私有成员的值。

    内部类和多重继承

    我们已经知道,Java 中的类不允许多重继承,也就是说 Java 中的类只能有一个直接父类,而 Java 本身提供了内部类的机制,这是否可以在一定程度上弥补 Java 不允许多重继承的缺陷呢?我们这样来思考这个问题:假设我们有三个基类分别为 ABC,我们希望有一个类 D 达成这样的功能:通过这个 D 类的对象,可以同时产生 ABC 类的对象,通过刚刚的内部类的介绍,我们也应该想到了怎么完成这个需求了,创建一个类 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 <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 模式查看:
    这里写图片描述
    程序执行到 72 行代码,此时 72 行代码还未执行,因此 myComponent 引用和其对象还未创建,继续执行:
    这里写图片描述
    这里成功创建了一个 MyComponent 对象,但是其 create 方法还未执行,所以 myWindow 字段为 null,这里可能有小伙伴会问了,myComponent 对象的 clickListener 字段呢?怎么不见了?其实这和我们在代码中定义 clickListener 字段的形式有关,我们定义的是 static OnClickListener clickListener; ,因此 clickListener 是一个静态字段,其在类加载的完成的时候储存在 JVM 中内存区域的 方法区 中,而创建的 Java 对象储存在 JVM 的堆内存中,两者不在同一块内存区域。关于这些细节,想深入了解的小伙伴建议阅读《深入理解JVM虚拟机》。好了,我们继续执行代码:
    这里写图片描述
    myComponent.create 方法执行完成之后创建了 OnClickListener 内部类对象,并且为 myWindow 对象设置 OnCLickListener 单击事件监听。我们继续:
    这里写图片描述
    myComponent.destroy 方法执行完成之后,myWindow.removeClickListener 方法也执行完成,此时 myWindow 对象中的 clickListener 字段为 null。我们继续:
    这里写图片描述
    代码执行到了 80 行,在此之前,所有的代码和解释都没有什么难度,跟着运行图走,一切都那么顺利成章,其实这张图的运行结果也很好理解,只不过图中的文字需要思考一下:myComponent 引用指向的对象真的被回收了吗?要解答这个问题,我们需要借助 Java 中提供的内存分析工具 jvisualvm (以前它还不叫这个名字…),它一般在你安装 JDK 的目录下的 bin 子目录下:
    这里写图片描述
    我们运行这个程序:
    这里写图片描述
    在程序左边可以找到我们当前正在执行的 Java 进程,双击进入:
    这里写图片描述
    单击 tab 中的 监视 选项卡,可以看到当前正在执行的 Java 进程的一些资源占用信息,当然我们现在的主要目的是分析内存,那么们单击右上角的 堆 Dump
    这里写图片描述
    在这个界面,单击 选项卡,会出现当前 Java 进程中用到的所有的类,我们已经知道我们要查找的类的对象只创建了一个,因此我们根据右上角的 实例数 来进行排除:我们成功的找到了我们创建的对象!而这样也意味着当我们在上面代码中调用 JVM 的垃圾回收动作没有回收这三个对象,这其实就是一个真真切切的内存泄露!因为我们将 main 方法中的 myComponent 引用赋值为 null,就意味着我们已经不再使用这个组件和里面的一些子组件(MyWindow 对象),即这个组件和其内部的一些组件应该被回收。但是调用 JVM 的垃圾回收却并没有将其对应的对象回收。造成这个问题的原因在哪呢?
    其实就在于我们刚刚在 MyComponent 类中定义的 clickListener 字段,我们在代码中将其定义成了 static 类型的,同时这个字段又指向了一个匿名内部类对象(在 create 方法中 创建了一个 OnClickListener 接口对象,即通过一个匿名内部类实现这个接口并创建其对象),根据 JVM 寻找和标记无用对象的规则(可达性分析算法),其会将 clickListener 字段作为一个 “root” ,并通过它来寻找还有用的对象,在这个例子中,clickListener 字段指向一个匿名内部类对象,这个匿名内部类对象有一个外部类对象(MyComponent 类型的对象)的引用,而外部类对象中又有一个 MyWindow 类型的对象引用。因此 JVM 会将这三个对象都视为有用的对象不会回收。用图来解释吧:
    这里写图片描述

    Ok,通过这个过程,相信你已经理解了造成此次内存泄露的原因了,那么我们该如何解决呢?对于当前这个例子,我们只需要改一些代码:
    1、把 MyComponent 类中的 clickListener 字段前面的 static 修饰符去掉就可以了(static OnClickListener clickListener; -> OnClickListener clickListener;),这样的话 clickListener 指向的对象,就作为 MyComponent 类的对象的一部分了,在 MyComponent 对象被回收时里面的子组件也会被回收。同时它们之间也只是互相引用(MyComponent 外部类对象中有一个指向 OnClickListener 内部类对象的引用,OnClickListener 内部类对象有一个指向 MyComponent 外部类对象的引用),根据 JVM 的 “可达性分析” 算法,在两个对象都不再被外部使用时,JVM 的垃圾回收机制是可以标记并回收这两个对象的。
    虽然不强制要求你在 MyComponent 类中的 onDestroy 方法中将其 clickListener 引用赋值为 null,但是我还是建议你这样做,因为这样更能确保你的程序的安全性(减少发生内存泄露的机率,毕竟匿名内部类对象会持有外部类对象的引用),在某个组件被销毁时将其内部的一些子组件进行合理的处理是一个很好的习惯。
    2、你也可以自定义一个静态内部类或者是另外自定义一个类文件,并实现 OnClickListener 接口,之后通过这个类创建对象,这样就可以避免通过非静态内部类的形式创建 OnClickListener 对象增加内存泄露的可能性。

    避免内存泄漏

    那么我们在日常开发中怎么合理的使用内部类来避免产生内存泄露呢?这里给出一点我个人的理解:
    1、能用静态内部类就尽量使用静态内部类,从上文中我们也知道了,静态内部类的对象创建不依赖外部类对象,即静态内部对象不会持有外部类对象的引用,自然不会因为静态内部类对象而导致内存泄露,所以如果你的内部类中不需要访问外部类中的一些非 static 成员,那么请把这个内部类改造成静态内部类;

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

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

    好了,关于 Java 内部类的介绍就到这里了,通过这篇文章相信你对 Java 内部类已经有了一个比较深入的理解。
    如果博客中有什么不正确的地方,还请多多指点。如果这篇文章对您有帮助,请不要吝啬您的赞,欢迎继续关注我的其他文章。

    谢谢观看。。。

    展开全文
  • 初识JAVA内部类

    万次阅读 多人点赞 2021-03-21 22:22:28
    1 内部类概述 如果一个类存在的意义就是为指定的另一个类,可以把这个类放入另一个类的内部。 就是把类定义在类的内部的情况就可以形成内部类的形式。 A类中又定义了B类,B类就是内部类,B类可以当做A类的一个成员...

    1 内部类概述

    如果一个类存在的意义就是为指定的另一个类,可以把这个类放入另一个类的内部。
    就是把类定义在类的内部的情况就可以形成内部类的形式。
    A类中又定义了B类,B类就是内部类,B类可以当做A类的一个成员看待:
    只为A类服务,可以看做是外部类的一个特殊成员

    2 特点

    1) 内部类可以直接访问外部类中的成员,包括私有成员
    2) 外部类要访问内部类的成员,必须要建立内部类的对象
    3) 在成员位置的内部类是成员内部类
    4) 在局部位置的内部类是局部内部类

    3 练习 : 内部类入门案例

    创建包: cn.tedu.innerclass
    创建类: TestInner1.java

    package cn.tedu.innerclass;
    /*本类用作测试内部类的入门案例*/
    public class TestInner1 {
        public static void main(String[] args) {
            //3.创建内部类对象,使用内部类的资源
            /*外部类名.内部类名 对象名 = 外部类对象.内部类对象*/
            Outer.Inner oi = new Outer().new Inner();
            oi.delete();
            System.out.println(oi.sum);
            //4.调用外部类的方法--这样是创建了一个外部类的匿名对象,只使用一次
            new Outer().find();
        }
    }
    
    //1.创建外部类 Outer
    class Outer{
        //1.1创建外部类的成员变量
        String name;
        private int age;
        //1.2创建外部类的成员方法
        public void find(){
            System.out.println("Outer...find()");
            //6.测试外部类如何使用内部类的资源
            //System.out.println(sum);--不能直接使用内部类的属性
            //delete();--不能直接调用内部类的方法
            /*外部类如果想要使用内部类的资源,必须先创建内部类对象
            * 通过内部类对象来调用内部类的资源*/
            Inner in = new Inner();
            System.out.println(in.sum);
            in.delete();
        }
        //2.创建内部类Inner--类的特殊成员
        /*根据内部类位置的不同,分为:成员内部类(类里方法外)、局部内部类(方法里)*/
        class Inner{
            //2.1定义内部类的成员变量
            int sum = 10;
            //2.2定义内部类的成员方法
            public void delete(){
                System.out.println("Inner...delete()");
                //5.测试内部类是否可以使用外部类的资源
                /*结论:内部类可以直接使用外部类的资源,私有成员也可以!*/
                System.out.println(name);
                System.out.println(age);
                /*注意:此处测试完毕需要注释掉,否则来回调用
                * 会抛出异常StackOverFlowException栈溢出异常*/
                //find();
            }
        }
    }
    

    4 成员内部类

    4.1 练习 : 被private修饰

    创建包: cn.tedu.innerclass
    创建类: TestInner2.java

    package cn.tedu.innerclass;
    /**本类用来测试成员内部类被private修饰*/
    public class TestInner2 {
    	public static void main(String[] args) {
    		/**怎么使用内部类Inner2的资源?*/
    		//4.创建内部类Inner2对象进行访问
    		//Outer2.Inner2 oi = new Outer2().new Inner2();
    		//oi.eat();
    		
    		/**如果Inner2被private修饰,无法直接创建对象该怎么办?*/
    		//7.创建外部类对象,间接访问私有内部类资源
    		new Outer2().getInner2Eat();
    	}
    }
    //1.创建外部类Outer2
    class Outer2{
    	//6.提供外部类公共的方法,在方法内部创建Inner2内部类对象,调用内部类方法
    	public void getInner2Eat() {
    		Inner2 in = new Inner2();//外部类可以访问内部类的私有成员
    		in.eat();
    	}
    	//2.1创建成员内部类Inner2
    	/**成员内部类的位置:类里方法外*/
    	//5.成员内部类,被private修饰私有化,无法被外界访问
    	private class Inner2{
    		//3.创建内部类的普通成员方法
    		public void eat() {
    			System.out.println("我是Inner2的eat()");
    		}
    	}
    }
    
    总结:
    成员内部类被Private修饰以后,无法被外界直接创建创建对象使用
    所以可以创建外部类对象,通过外部类对象间接访问内部类的资源

    4.2 练习 : 被static修饰

    创建包: cn.tedu.innerclass
    创建类: TestInner3.java

    package cn.tedu.innerclass;
    /**本类用来测试成员内部类被static修饰*/
    public class TestInner3 {
    	public static void main(String[] args) {
    		/**如何访问内部类的show()?*/
    		//4.创建内部类对象访问show()
    		//方式一:按照之前的方式,创建内部类对象调用show()
    		//Outer3.Inner3 oi = new Outer3().new Inner3();
    		//oi.show();
    		//方式二:创建匿名内部类对象访问show()
    		//new Outer3().new Inner3().show();
    		
    		/**现象:当内部类被static修饰以后,new Outer3()报错*/
    		//6.用static修饰内部类以后,上面的创建语句报错,注释掉
    		//通过外部类的类名创建内部类对象
    		Outer3.Inner3 oi = new Outer3.Inner3();
    		oi.show();
    		
    		//7.匿名的内部类对象调用show()
    		new Outer3.Inner3().show();
    		
    		//9.访问静态内部类中的静态资源--链式加载
    		Outer3.Inner3.show2();
    	}
    }
    
    //1.创建外部类Outer3
    class Outer3{
    	//2.创建成员内部类Inner3
    	//5.内部类被static修饰—并不常用!浪费内存!
    	static class Inner3{
    		//3.定义成员内部类中普通的成员方法
    		public void show() {
    			System.out.println("我是Inner3类的show()");
    		}
    		//8.定义成员内部类的静态成员方法
    		static public void show2() {
    			System.out.println("我是Inner3的show2()");
    		}
    	}
    }
    
    总结:
    静态资源访问时不需要创建对象,可以通过类名直接访问
    访问静态类中的静态资源可以通过”. . . ”链式加载的方式访问

    5 局部内部类

    创建包: cn.tedu.innerclass
    创建类: TestInner4.java

    package cn.tedu.innerclass;
    /**本类用来测试局部内部类*/
    public class TestInner4 {
    	public static void main(String[] args) {
    		/**如何使用内部类的资源呢?
    		 * 注意:直接调用外部类的show()是无法触发内部类功能的
    		 * 需要再外部类中创建内部类对象并且进行调用,才能触发内部类的功能
    		 * */
    		//5.创建外部类对象调用show()
    		//7.当在外部类show()中创建局部内部类对象并且进行功能调用后,内部类的功能才能被调用
    		new Outer4().show();
    	}
    }
    //1.创建外部类Outer4
    class Outer4{
    	//2.创建外部类的成员方法
    	public void show() {
    		//3.创建局部内部类Inner4—不太常用!!!
    		/**位置:局部内部类的位置在方法里*/
    		class Inner4{
    			//4.创建局部内部类的普通属性与方法
    			String name;
    			int age;
    			public void eat() {
    				System.out.println("我是Inner4的eat()");
    			}
    		}
    		/**如何使用局部内部类的资源?*/
    		//6.在show()里创建内部类对象
    		Inner4 in = new Inner4();
    		in.eat();
    		System.out.println(in.name);
    		System.out.println(in.age);
    	}
    }
    

    6 匿名内部类

    创建包: cn.tedu.innerclass
    创建类: TestInner5.java

    package cn.tedu.innerclass;
    /*本类用于测试匿名内部类
    * 匿名内部类没有名字,通常与匿名对象结合在一起使用*/
    public class TestInner5 {
        public static void main(String[] args) {
            //传统方式:创建接口的实现类+实现类实现接口中的抽象方法+创建实现类对象+通过对象调用方法
            //3.创建接口一对应的匿名对象与匿名内部类,并调用实现了的方法save()
            new Inter1(){
                @Override
                public void save() {
                    System.out.println("save()...");
                }
                @Override
                public void get() { }
            }.save();
    
            //5.创建抽象类对应的匿名对象与匿名内部类
            new Inter2(){
                @Override
                public void drink() {
                    System.out.println("一人饮酒醉");
                }
            }.drink();
            //7.调用普通类的功能怎么调用?创建匿名对象直接调用
            new Inter3().powerUp();
            new Inter3().powerUp();//new了2次,所以是两个匿名对象
            /*如果想要多次使用实现后的功能,还是要创建普通的对象
            * 匿名对象只能使用一次,一次只能调用一个功能
            * 匿名内部类其实就充当了实现类的角色,去实现未实现的抽象方法,只是没有名字而已*/
            Inter3 in = new Inter3();
            in.study();
            in.study();
            in.study();
            in.study();
            in.study();
            in.study();
    
        }
    }
    
    //1.创建接口
    interface Inter1{
        //2.定义接口中的抽象方法
        void save();
        void get();
    }
    //4.创建抽象类
    abstract class Inter2{
        public void play(){
            System.out.println("Inter2...play()");
        }
        abstract public void drink();
    }
    //6.创建普通类
    class Inter3{
        public void study(){
            System.out.println("什么都阻挡不了我想学习赚钱的决心");
        }
        public void powerUp(){
            System.out.println("我们会越来越强的!");
        }
    }
    
    
    总结:
    匿名内部类属于局部内部类,而且是没有名字的局部内部类,通常和匿名对象一起使用
    展开全文
  • Java内部类和匿名内部类的用法

    万次阅读 多人点赞 2016-07-21 15:46:41
    Java内部类和匿名内部类的用法 【尊重原创,转载请注明出处】http://blog.csdn.net/guyuealian/article/details/51981163一、内部类: (1)内部类的同名方法 内部类可以调用外部类的方法,如果内部类有同名方法...

    Java内部类和匿名内部类的用法

        【尊重 原创,转载请注明出处 】http://blog.csdn.net/guyuealian/article/details/51981163
    一、内部类: 
          (1)内部类的同名方法
            内部类可以调用外部类的方法,如果内部类有同名方法必须使用"OuterClass.this.MethodName()"格式调用(其中OuterClass与MethodName换成实际外部类名及其方法;this为关键字,表示对外部类的引用);若内部类无同名方法可以直接调用外部类的方法。
            但外围类无法直接调用内部类的private方法,外部类同样无法直接调用其它类的private方法。注意:内部类直接使用外部类的方法与该方法的权限与是否static无关,它取决于内部类是否有同名方法。
    package innerclass;
    public class OuterClass {
    	private void outerMethod() {
    		System.out.println("It's Method of OuterClass");
    	}
    	public static void main(String[] args) {
    		OuterClass t = new OuterClass();
    		OuterClass.Innerclass in = t.new Innerclass();
    		in.innerMethod();
    	}
    
    	class Innerclass {
    		public void innerMethod() {
    		   OuterClass.this.outerMethod();// 内部类成员方法与外部类成员方法同名时,使用this调用外部类的方法
    		   outerMethod();// 内部类没有同名方法时执行外部类的方法
    		}
    		private void outerMethod() {
    			System.out.println("It's Method of Innerclass");
    		}
    	}
    }
         输出结果为:
    It's Method of OuterClass
    It's Method of Innerclass
        (2)内部类访问外部类的变量必须声明为final
          方法中的局部变量,方法结束后这个变量就要释放掉,final保证这个变量始终指向一个对象。
       首先,内部类和外部类其实是处于同一个级别,内部类不会因为定义在方法中就会随着方法的执行完毕而跟随者被销毁。问题就来了,如果外部类的方法中的变量不定义final,那么当外部类方法执行完毕的时候,这个局部变量肯定也就被GC了,然而内部类的某个方法还没有执行完,这个时候他所引用的外部变量已经找不到了。如果定义为final,java会将这个变量复制一份作为成员变量内置于内部类中,这样的话,由于final所修饰的值始终无法改变,所以这个变量所指向的内存区域就不会变。
         注意,若使用JDK1.8,方法中内部类的方法是可以直接访问外部类的方法的局部变量,并且不需要声明为final类型。
    public class OuterClass {
    	int num1 = 0;// 成员变量
    
    	private void outerMethod() {
    		int num2 = 0;// 方法内的局部变量
    		class Innerclass_1 {
    			public void innerMethod() {
    				System.out.println(num1);// 方法中内部类的方法,可以正常访问外部类的成员变量
    				System.out.println(num2);// JDK1.8以前,方法中内部类的方法,不能直接访问外部类的方法的局部变量,必须声明为final
    			}
    		}
    	}
    }
          如果使用JDK1.8以前的版本,Eclipse会出现如下错误提示:

        (3)内部类的实例化
         内部类实例化不同于普通类,普通类可以在任意需要的时候实例化,而内部类必须在外层类实例化以后方可实例化,并与外部类建立关系
         因此在外部类中的非static方法中,是可以实例化内部类对象
    	private void outerMethod() {
    		System.out.println("It's Method of OuterClass");
    		Innerclass in = new Innerclass();//在外部类的outerMethod方法中实例化内部类是可以啊
    	}
          但在static方法中,就要注意啦!!!! 不能在static方法中直接new内部类,否则出现错误:
         No enclosing instance of type OuterClass is accessible. Must qualify the allocation with an enclosing instance of type OuterClass (e.g. x.new A() where x is an instance of OuterClass).
         这是因为静态方法是在类实例化之前就可以使用的,通过类名调用,这时动态内部类都还没实例化呢,怎么用,总不能调用一个不存在的东西吧。
         如果想在Static方法中new内部类,可以把内部类声明为Static
    public class OuterClass {
    	private void outerMethod() {
    		System.out.println("It's Method of OuterClass");
    	}
    
    	public static void main(String[] args) {
    		Innerclass in = new Innerclass();
    		in.innerMethod();
    	}
    
    	static class Innerclass {//把内部类声明为static
    		public void innerMethod() {
    			System.out.println("It's Method of innerMethod");
    
    		}
    	}
    
    }
         当然,一般不使用static的方式,而是推荐这种方法:x.new A() ,其中 x是外部类OuterClass的实例,A是内部类Innerclass
    package innerclass;
    public class OuterClass {
    	private void outerMethod() {
    		System.out.println("It's Method of OuterClass");
    	}
    	public static void main(String[] args) {
    		OuterClass.Innerclass in = new OuterClass().new Innerclass();//使用x.new A()的方式
    		in.innerMethod();
    	}
    	class Innerclass {
    		public void innerMethod() {
    			System.out.println("It's Method of innerMethod");
    		}
    	}
    }
          x.new A() ,其中 x是外部类OuterClass的实例,A是类部类Innerclass,当然可以拆分如下,这样就显然很明白啦:
    	public static void main(String[] args) {
    		OuterClass out = new OuterClass();//外部实例
    		OuterClass.Innerclass in = out.new Innerclass();//外部实例.new 外部类
    		in.innerMethod();
    	}

        (4)什么情况下使用内部类
         典型的情况是,内部类继承自某个类或实现某个接口,内部类的代码操作创建其的外层类的对象。所以你可以认为内部类提供了某种进
    入其外层类的窗口。
        使用内部类最吸引人的原因是:每个内部类都能独立地继承自一个(接口的)实现,所以无论外层类是否已经继承了某个(接口的)实
    现,对于内部类都没有影响。如果没有内部类提供的可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角
    度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了“多重继承”。
       (5)在静态方法中实例化内部类例子:(内部类放在静态方法中)
    package javatest2;
    public class JavaTest2 {
    	public static void main(String[] args) {
    		class Boy implements Person {
    			public void say() {// 匿名内部类自定义的方法say
    				System.out.println("say方法调用");
    			}
    			@Override
    			public void speak() {// 实现接口的的方法speak
    				System.out.println("speak方法调用");
    			}
    		}
    		Person per = new Boy();
    		per.speak();// 可调用
    		per.say();// 不能调用
    	}
    }
    interface Person {
    	public void speak();
    }
           per.speak()可调用,而per.say()不能调用,这时因为per是Person对象,要想调用子类的方法,可以强制向下转型为:((Boy) per).say();或者直接改为Boy per = new Boy();。 从中可发现,要想调用内部类的自定义的方法,必须通过内部类的对象来调用。那么,匿名内部类连名字都没有,怎么调用内部类自定义的方法?
    (二)匿名内部类
          匿名内部类也就是没有名字的内部类正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写,但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口,但最多只能继承一个父类,或实现一个接口。
    关于匿名内部类还有如下两条规则:
        1)匿名内部类不能是抽象类,因为系统在创建匿名内部类的时候,会立即创建内部类的对象。因此不允许将匿名内部类定义成抽象类。
        2)匿名内部类不等定义构造器(构造方法),因为匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以定义实例初始化块,
        怎样判断一个匿名类的存在啊?看不见名字,感觉只是父类new出一个对象而已,没有匿名类的名字。
    先看段伪代码
    abstract class Father(){
    ....
    }
    public class Test{
       Father f1 = new Father(){ .... }  //这里就是有个匿名内部类
    }
       一般来说,new 一个对象时小括号后应该是分号,也就是new出对象该语句就结束了。但是出现匿名内部类就不一样,小括号后跟的是大括号,大括号中是该new 出对象的具体的实现方法。因为我们知道,一个抽象类是不能直接new 的,必须先有实现类了我们才能new出它的实现类。上面的伪代码就是表示new 的是Father的实现类,这个实现类是个匿名内部类。
        其实拆分上面的匿名内部类可为:
    class SonOne extends Father{
      ...       //这里的代码和上面匿名内部类,大括号中的代码是一样的
    }
    public class Test{
       Father f1 = new SonOne() ;
    }
    
         先看一个例子,体会一下匿名内部类的用法:

        运行结果:eat something
        可以看到,我们直接将抽象类Person中的方法在大括号中实现了,这样便可以省略一个类的书写。并且,匿名内部类还能用于接口上
    public class JavaTest2 {
    	public static void main(String[] args) {
    		Person per = new Person() {
    			public void say() {// 匿名内部类自定义的方法say
    				System.out.println("say方法调用");
    			}
    			@Override
    			public void speak() {// 实现接口的的方法speak
    				System.out.println("speak方法调用");
    			}
    		};
    		per.speak();// 可调用
    		per.say();// 出错,不能调用
    	}
    }
    
    interface Person {
    	public void speak();
    }
            这里per.speak()是可以正常调用的,但per.say()不能调用,为什么呢?注意Person per = new Person()创建的是Person的对象,而非匿名内部类的对象。其实匿名内部类连名字都没有,你咋实例对象去调用它的方法呢?但继承父类的方法和实现的方法是可以正常调用的,本例子中,匿名内部类实现了接口Person的speak方法,因此可以借助Person的对象去调用。
            若你确实想调用匿名内部类的自定义的方法say(),当然也有方法:
          (1)类似于speak方法的使用,先在Person接口中声明say()方法,再在匿名内部类中覆写此方法。
          (2)其实匿名内部类中隐含一个匿名对象,通过该方法可以直接调用say()和speak()方法;代码修改如下:
    public class JavaTest2 {
    	public static void main(String[] args) {
    		new Person() {
    			public void say() {// 匿名内部类自定义的方法say
    				System.out.println("say方法调用");
    			}
    
    			@Override
    			public void speak() {// 实现接口的的方法speak
    				System.out.println("speak方法调用");
    			}
    		}.say();// 直接调用匿名内部类的方法
    	}
    }
    interface Person {
    	public void speak();
    }











    展开全文
  • Java内部类

    千次阅读 2018-12-28 11:21:17
    Java内部类真的很难理解,但有必要搞懂,因为内部类让外部类更丰富多彩了,就好像一个人的心中还可以住着另外一个人。
  • java内部类创建方式

    千次阅读 2017-04-20 13:06:03
    内部类我们在程序开发中经常会用到,这里通过普通内部类,静态内部类和方法内部类三种方式来介绍一下内部类创建方式。  1.普通内部类创建: /** * * 普通内部类的定义 * */ class Outer { private String...
  • 主要介绍了Java创建内部类对象实例详解的相关资料,需要的朋友可以参考下
  • java内部类如何创建对象

    千次阅读 2016-11-04 09:08:08
    转载来自大牛博客:http://blog.csdn.net/huangbiao86/article/details/6364218 静态内部类可以直接创建对象new B.C(); 如果内部类不是静态的,那就得这样 B b = new B(); B.C c = b.new C();
  • java内部类之成员内部类、局部内部类和匿名内部类

    千次阅读 多人点赞 2018-07-15 16:13:30
    内部类概念 一个类中包含另外一个类。 分类 1、成员内部类。 2、局部内部类(包含匿名内部类)。 成员内部类 定义格式: 修饰符 class 类名称 { 修饰符 class 类名称 { //... } //... } 注意: 内部类...
  • 什么是JAVA内部类

    万次阅读 2019-04-23 13:58:14
    什么是JAVA内部类? 1、概念 存在于Java类的内部的Java类。 2、分类 成员内部类 格式 class OuterClass { class InnerClass {} //成员内部类 } 编译之后会生成两个class文件:OuterClass.class和...
  • JNI调用java方法, new java对象,new java内部类 1.new java对象 new java内部类 创建内部类,要先创建外部类实例 构造方法都是用 public class Test { public void test(){ Log.e("Test", "form java Class Test ...
  • Java 内部类和匿名内部类

    万次阅读 多人点赞 2017-11-01 19:07:21
    1、内部类 一个类定义在另外一个类的内部,这个该类就被称为内部类内部类分为成员内部类(定义在外部类的成员位置)和局部内部类(定义在外部类的方法里面)。 (1) 成员内部类 class Outer { class Inner { //...
  • java匿名内部类什么是匿名内部类?匿名内部类的使用场景?匿名内部类如何创建?匿名内部类的实现和使用例1(实现接口)例2(继承类) 什么是匿名内部类? 匿名内部类,顾名思义,就是不知道这个类到底是什么名字,也...
  • Java匿名内部类实现线程的创建

    千次阅读 2019-05-06 14:47:12
    Java匿名内部类实现线程的创建
  • 你好啊,我是内部类的方法....5
  • java 内部类与闭包

    万次阅读 多人点赞 2018-01-26 23:16:09
    首先,让我们看看什么是内部类Java 内部类,成员类,局部类,匿名类等): 根据内部类的位置不同,可将内部类分为 1. 成员内部类 2. 局部内部类 class C{ //成员内部类 class B{ } public void show1() { /...
  • Java 内部类综述

    千次阅读 多人点赞 2016-11-20 22:07:18
    可幸的是,Java 提供了两种方式让我们曲折地来实现多重继承:接口和内部类。其中,内部类的一个及其重要的作用就是实现多重继承。除此之外,内部类还可以很好的实现隐藏(私有成员内部类)。内部类共有四种类型,即...
  • Java创建内部类对象

    千次阅读 2018-05-24 13:39:59
    public class OutClass { ...如果我们要直接创建内部类对象的话,必须先创建外部类对象,然后通过外部类对象来创建内部类对象。 没有外部类对象不可能创建内部类对象。 如果是静态内部类,则不需要对外部对象的引用。
  • 全面掌握 Java 内部类

    千次阅读 2017-05-24 16:20:16
    一直以来以为自己对 java 基础甚是清楚,然而面试时却连内部类和静态内部类的区别都无法回答圆满,so~重新学习一遍,彻底掌握内部类内部类是一种非常有用的特性,它可以把一些逻辑相关的类组织在一起,并控制位于...
  • Java 内部类和异常类(详细版)】

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

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 710,901
精华内容 284,360
关键字:

java内部类怎么创建

java 订阅