IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java内部类 -> 正文阅读

[Java知识库]Java内部类

1 内部类的含义

内部类:
   把类定义在其他类的内部,这个类就被称为内部类。(类可以嵌套定义,方法不可以嵌套定义)
   编译成字节码文件:Outer.class(外部类), Outer$Inner.class(内部类)

为什么需要内部类?
   外部类       内部类
     PC   --      CPU
   a. 保护CPU
   b. 方便PC和CPU的交互

      人     心脏
   a. 保护心脏
   b. 方便心脏和其他器官的交互

   a. 保护内部类
   b. 方便内部类和外部类的交互

访问特点(重点):
   内部类访问外部类:直接访问, 可以访问外部类私有的成员
   外部类访问内部类:创建对象,可有访问内部类私有的成员

分类:
   成员位置:成员内部类, 静态内部类
   局部位置:局部内部类, 匿名内部类(掌握)
public class Outer {
    // 成员内部类,可以编译运行
    class Inner {
    }

    public static void main(String[] args) {
    }
}

2 成员内部类

成员内部类:(外部类对象所有)
    a.内部类访问外部类成员:
        直接访问(好像定义在自己的类中一样),并且可以访问外部类的私有成员。
        如果内部类成员隐藏了外部类成员,那么该如何访问外部类成员?
            使用外部类名.this。Caution!!!
    b.外部类访问内部类成员:
        创建内部类对象,通过该对象访问内部类成员,并且可以访问内部类的私有成员。
    c.其它类访问内部类成员:
        创建内部类对象来访问,并且受访问权限修饰符限制。
            此时创建内部类对象的格式:外部类名.内部类名 对象名 = 外部类对象.new 内部类名();

    注意:一般情况下,我们会将内部类用private修饰,以保护起来。

为什么内部类可以直接访问外部类成员?为什么创建内部类对象,需要一个外部类对象?
    对Outer1$Inner1.class(内部类)文件反编译之后:
        final Outer1 this$0; // 外部类类型

        // 构造方法
        Outer1$Inner1() {
            this$0 = Outer1.this; // 外部类的对象赋值给上面的成员变量this$0
            super();
        }
    发现内部类秘密捕获了外部类对象,Outer1.this就代表创建内部类对象的那个外部类对象。
public class Outer1 {
    private int a = 10;
    public int b = 20;

    // 成员内部类
    class Inner1 {
        int a = 100;
        int b = 200;
        private int num1 = 1000;
        private int num2= 2000;

        public void show() {
            // 内部类访问外部类成员,直接访问
            System.out.println(a); // 100
            System.out.println(b); // 200
            // System.out.println(Outer1.a); // 错误,a是非静态,不能通过类名访问。如果外部类a为静态,则可以编译运行。
            // System.out.println(Outer1.b); // 同上
            System.out.println(Outer1.this.a); // 10 内部类成员隐藏了外部类成员,使用 外部类名.this 来访问外部类成员。
            System.out.println(Outer1.this.b); // 20 同上
        }
    }

    public void method() {
        // 外部类访问内部类成员,必须要先创建对象
        // System.out.println(num1); // 还没有内部类对象,报错
        // System.out.println(num2); // 同上
        Inner1 inner = new Inner1();
        System.out.println(inner.num1); // 1000
        System.out.println(inner.num2); // 2000
    }

    public static void main(String[] args) {
        Outer1 outer = new Outer1();
        outer.method();
        // 运行结果:1000, 2000

        // 错误,因为内部类会秘密捕获外部类对象Outer1.this,this是非静态的,不能出现在静态上下文中。
        // Inner1 inner = new Inner1();
        Inner1 inner = outer.new Inner1();
        inner.show();
        // 运行结果:100, 200, 10, 20
    }
}

其他类访问成员内部类成员:创建对象,受访问权限限制

public class Demo1 {
    public static void main(String[] args) {
        // 创建成员内部类对象,需要先创建外部类对象。
        Outer1.Inner1 inner = new Outer1().new Inner1();
        // System.out.println(inner.num1); // 受访问权限限制
        System.out.println(inner.num2);
    }
}

3 静态内部类

静态内部类:(外部类所有,成员位置)
    a.内部类访问外部类成员:
        直接访问,包括私有成员,但是只能访问静态的。
            注意:内部类成员隐藏外部类成员怎么办?
                只能通过外部类名去访问,不能使用Outer.this,因为静态内部类中不能访问非静态的this。
    b.外部类访问内部类成员:
        创建内部类对象,通过该对象访问,包括内部类私有成员。
    c.其他类访问内部类成员:
        创建内部类对象,Outer.Inner inner = new Outer.Inner(),通过该对象访问,并受访问权限限制。
public class Outer2 {
    private int a = 10;
    public int b = 20;
    private static int c = 30;
    public static int d = 40;

    // 静态内部类
    static class Inner2 {
        int c = 300; // 静态内部类中可以有非静态成员,但是不能访问外部类的非静态成员。
        int d = 400;
        private int num1 = 1;
        public int num2 = 2;

        public void show() {
            // System.out.println(a); // 报错,静态内部类只能访问外部类的静态成员,a是非静态的。
            // System.out.println(b); // 同上
            System.out.println(c); // 300 静态内部类可以直接访问外部类的静态成员,并且内部类成员覆盖了外部类成员。
            System.out.println(d); // 400 同上
            // System.out.println(Outer2.this.c); // 静态上下文中不能有this
            // System.out.println(Outer2.this.d); // 静态上下文中不能有this
            System.out.println(Outer2.c); // 30 内部类成员覆盖了外部类成员,需要用外部类名.成员去访问。
            System.out.println(Outer2.d); // 40 同上
        }
    }

    public static void main(String[] args) {
        Inner2 inner = new Inner2(); // 静态内部类不依赖于外部类对象,创建时不会秘密捕获Outer.this,所以可在静态方法中直接创建。
        inner.show();
        System.out.println(inner.num1); // 1 外部类访问内部类成员,需要创建内部类对象来访问,并且可以访问私有的。
        System.out.println(inner.num2); // 2 同上
    }
}

其他类访问静态内部类成员:创建对象,受访问权限限制

public class Demo2 {
    public static void main(String[] args) {
        // 创建静态内部类对象,不需要依赖外部类对象
        Outer2.Inner2 inner = new Outer2.Inner2();
        // System.out.println(inner.num1); // 受访问权限限制,不能访问。
        System.out.println(inner.num2);
    }
}

4 局部内部类

局部内部类:
    位置:局部位置,方法内或方法声明上,相当于局部变量。
    a.内部类访问外部类:
        直接访问,并可以访问外部类私有成员。
        访问局部变量:直接访问,但是该局部变量必须是final修饰的,如果没有添加final,系统会默认添加上final。
    b.外部类访问内部类:
        创建内部类对象,并且可以访问内部类的私有成员,但是只能在方法内部访问,因为局部内部类相当于局部变量,方法外不可访问。
    c.其他类访问内部类:
        不能!局部内部类只能在方法中被访问。

问题:为什么内部类访问外部类的局部变量时,该局部变量不能发生改变呢?
    因为该局部变量必须是final修饰,如果没有,系统会默认添加上final。
    对下面的Outer3编译后生成的字节码文件反编译之后,得到:
        class Outer3$1Inner3 {
            final int val$num1;
            final Outer3 this$0;

            Outer3$1Inner3() {
                this$0 = final_outer3;
                val$num1 = I.this;
                super();
            }
    }
    发现在局部内部类中会把局部变量num1声明为final修饰的成员变量。
public class Outer3 {
    private int a = 10;
    public int b = 20;

    public void show() {
        // Inner3 inner = new Inner3(); // 局部内部类必须定义在前面,然后才能创建对象。

        int num1 = 100; // 局部变量,系统默认添加一个final来修饰

        // 局部内部类
        class Inner3 {
            private int num = 300;
            public int num2 = 200; // 局部内部类的成员变量

            // 局部内部类的成员方法
            public void method() {
                System.out.println(a); // 10 局部内部类访问外部类成员,直接访问,包括私有。
                System.out.println(b); // 20 同上
                System.out.println(num1); // 100 局部内部类访问局部变量,直接访问。
                System.out.println(num2); // 200 局部内部类访问自己的成员变量
                a = 1; // 外部类的成员变量,可以修改值
                b = 2; // 同上
                // num1 = 3; // 报错,因为num1是局部变量,默认已被final修饰,不可再次修改值。
                num2 = 4; // 局部内部类的成员变量,可以修改值。
                System.out.println(a); // 1
                System.out.println(b); // 2
                System.out.println(num1); // 100
                System.out.println(num2); // 4
            }
        }

        Inner3 inner = new Inner3(); // 局部内部类定义在前,创建对象在后。
        inner.method();
        System.out.println(inner.num); // 300 外部类访问局部内部类成员,必须要创建对象,而且可以访问私有,但是只能在该方法中访问。
        System.out.println(inner.num2); // 4 同上,因为num2的值在method()方法中发生了修改。
    }

    public static void main(String[] args) {
        // Inner3 inner = new Inner3(); // 局部内部类相当于局部变量,只能在定义的方法中使用。
        Outer3 outer = new Outer3();
        outer.show();
    }
}

如果不把局部变量声明成final类型,则程序可能出现问题,看下面的例子就知道了:

局部内部类访问的局部变量必须是常量

如果局部变量a没有final修饰,那么a会在栈中,outer.getSing()方法返回Inner4对象后,栈释放,a的内存也释放,之后再调用sing()方法,由于多态,会访问局部内部类Inner4中的sing()方法,需要打印a,这时由于a的内存已释放,无法访问到,所以报错。但是将局部变量a默认用final修饰后,变量a就会存放到堆中的常量池,这样尽管getSing()栈释放后,再在sing()中访问a,直接去常量池中访问,程序不会报错。注意:JDK1.7以前,常量池在方法区中,JDK1.7开始,把常量池放到了堆中。

关于局部内部类的另外两个代码案例:

interface Sing {
    void sing(); // 默认public abstract修饰
}

public class Outer4 {
    public Sing getSing() {
        int a = 10; // 默认被final修饰

        // 局部内部类
        class Inner4 implements Sing {
            @Override
            public void sing() {
                System.out.println("a = " + a); // a = 10
            }
        }
        return new Inner4();
    }

    public static void main(String[] args) {
        Outer4 outer = new Outer4();
        Sing sing = outer.getSing(); // 多态
        sing.sing();
    }
}
public class Outer5 {
    int a = 1;

    public void show() {
        int a = 10; // 局部变量,默认用final修饰

        // 局部内部类
        class Inner5 {
            int a = 100; // 局部内部类的成员变量

            public void method() {
                int a = 1000; // 局部变量,但是这个局部变量没有final修饰。
                System.out.println(a); // 1000
                System.out.println(this.a); // 100
                System.out.println(Outer5.this.a); // 1
                // 等于10的那个a无法访问到
            }
        }

        Inner5 inner = new Inner5(); // 只能在方法中访问局部内部类
        inner.method();
    }

    public static void main(String[] args) {
        new Outer5().show();
    }
}

5 匿名内部类

匿名内部类:
    前提:存在一个类或者接口。
        类:可以是普通类或抽象类,但是不能是final修饰的最终类,因为匿名内部类相当于创建该类的子类对象,最终类不能被继承,没有子类。
        接口:相当于创建该接口的实现类对象。
    格式:new 类名或接口名() {重写方法;}

    本质:创建了一个继承或实现该类或接口的子类对象。
        new Sing() {
         @Override
         public void sing() {
            System.out.println("sing,sing,sing...");
         }
      };
       我要创建一个Sing类型的对象, 发现它是一个接口, 需要它的子类, 子类还没有定义,则可以使用匿名内部类对这个子类进行定义。
interface Sing2 {
    void sing();
}

// final class Dance { // 匿名内部类前提存在的类不能是一个final修饰的类,但可以是普通类或抽象类。
class Dance {
    public void dance() {
        // 给一个空实现
    }
}

public class Outer6 {

    public Sing2 getSing() {
        // 局部内部类实现
        /*class Inner6 implements Sing2 {
            @Override
            public void sing() {
                System.out.println("sing, sing, sing...");
            }
        }
        return new Inner6();*/

        // 匿名内部类实现,比局部内部类更见简洁。
        return new Sing2() {
            @Override
            public void sing() {
                System.out.println("sing, sing, sing...");
            }
        };
    }

    public Dance getDance() {
        return new Dance() {
            @Override
            public void dance() {
                System.out.println("dance, dance, dance...");
            }
        };
    }

    public static void main(String[] args) {
        Outer6 outer = new Outer6();
        Sing2 sing = outer.getSing();
        sing.sing(); // sing, sing, sing...
        Dance dance = outer.getDance();
        dance.dance(); // dance, dance, dance...
    }
}

6 练习

练习一:补齐程序(注意:内部类和外部类如果没有使用extends,则没有继承关系)

class Outer {
	public int num = 10;

	class Inner {
		public int num = 20;

         public void show() {
			int num = 30;
             System.out.println(...);
             System.out.println(...);
             System.out.println(...);
		}
      }
   }
使得在控制分别输出:30,20,10。
class Outer {
    public int num = 10;

    // 成员内部类
    class Inner {
        public int num = 20;

        public void show() {
            int num = 30;
            System.out.println(num); // 30
            System.out.println(this.num); // 20
            System.out.println(Inner.this.num); // 20 与上面一行代码等价
            System.out.println(Outer.this.num); // 10
        }
    }
}

public class Test1 {
    public static void main(String[] args) {
        Outer.Inner inner = new Outer().new Inner(); // 其他类访问成员内部类时,创建内部类对象的格式。受访问权限限制。
        inner.show();
    }
}

练习二:看程序写结果

abstract class Person {
    public abstract void show();
}

class PersonDemo {
    public Person method() {
        // 局部内部类,继承了其他类
        /*class Student extends Person {
            @Override
            public void show() {
                System.out.println("show");
            }
        }

        return new Student();*/

        // 上面的代码可以用匿名内部类实现,结果相同。
        return new Person() {
            @Override
            public void show() {
                System.out.println("show");
            }
        };
    }
}

public class Test2 {
    public static void main(String[] args) {
        PersonDemo pd = new PersonDemo();
        Person person = pd.method(); // 多态
        person.show(); // show
    }
}

练习三:按照要求,补齐代码

interface Inter { void show(); }
class Outer { //补齐代码 }
class OuterDemo {
	public static void main(String[] args) {
		Outer.method().show();
	}
}
要求在控制台输出"HelloWorld"
interface Inter2 {
    void show();
}

class Outer2 {
    // 补齐代码
    public static Inter2 method() {
        return new Inter2() {
            @Override
            public void show() {
                System.out.println("Hello world");
            }
        };
    }
}

public class Test3 {
    public static void main(String[] args) {
        Outer2.method().show(); // Hello world
    }
}

7 作业

/*
根据注释填写(1), (2), (3)处的代码
public class Test {
    public static void main(String[] args){
           // 初始化Bean1
           (1)
           bean1.I++;
           // 初始化Bean2
           (2)
           bean2.J++;
           // 初始化Bean3
           (3)
           bean3.K++;

           System.out.println(bean1.I);
           System.out.println(bean2.J);
           System.out.println(bean3.K);
    }

    // 成员内部类
    class Bean1 {
           public int I = 0;
    }

    // 静态内部类
    static class Bean2 {
           public int J = 0;
    }
}


class Bean {
    // 成员内部类
    class Bean3 {
           public int K = 0;
    }
}
 */

public class Test {
    public static void main(String[] args){
        // 初始化Bean1
        /*(1)*/ Test.Bean1 bean1 = new Test().new Bean1();
        bean1.I++;
        // 初始化Bean2
        /*(2)*/ Test.Bean2 bean2 = new Test.Bean2();
        bean2.J++;
        // 初始化Bean3
        /*(3)*/ Bean.Bean3 bean3 = new Bean().new Bean3();
        bean3.K++;

        System.out.println(bean1.I); // 1
        System.out.println(bean2.J); // 1
        System.out.println(bean3.K); // 1
    }

    // 成员内部类
    class Bean1 {
        public int I = 0;
    }

    // 静态内部类
    static class Bean2 {
        public int J = 0;
    }
}


class Bean {
    // 成员内部类
    class Bean3 {
        public int K = 0;
    }
}
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-11-14 21:29:54  更:2021-11-14 21:30:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 1:40:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码