前言

在之前讲解static静态内部类时,就给大家简单说过内部类的概念。但实际上,内部类并不是那么简单,所以今天我们需要对内部类进行专门地讲解和学习。


全文大约 【6500】字,不说废话,只讲可以让你学到技术、明白原理的纯干货!本文带有丰富的案例及配图,让你更好地理解和运用文中的技术概念,并可以给你带来具有足够启迪的思考

一. 内部类简介

1. 概念

在Java中,我们通常是把不同的类创建在不同的包里面,对于同一个包里的类来说,它们都是同一层次的。但其实还有另一种情况,有些类可以被定义在另一个类的内部,我们把在一个类里面定义的类称为内部类(InnerClass)或嵌套类,把外面定义的类称为外部类(OutClass)或宿主类。 也就是说,在类的内部既可以定义成员变量和方法,也可以定义其他的类。定义内部类的常见格式如下:

  1. class Outer {//外部类
  2. class Inner {//内部类
  3. //方法和属性
  4. }
  5. }

上面的代码中,Outer是普通的外部类,Inner就是内部类。它与普通外部类最大的不同,在于其实例对象不能单独存在,必须依附于一个外部类的实例对象。

内部类可以很好地实现隐藏,一般的非内部类是不允许有private 与 protected权限的,但内部类却可以,而且内部类还拥有外部类中所有元素的访问权限。总之,对内部类的很多访问规则都可以参考变量和方法。

但是要注意,虽然我们使用内部类可以使程序结构变得更加紧凑,但却在一定程度上破坏了面向对象的思想。

2. 优点

内部类的存在,具有如下优点:

● 内部类使得多继承的解决方案变得更完整:每个内部类都能独立的实现接口,无论外部类是否已经实现了接口或继承了父类,对于内部类都没有影响;

● 既可以方便地将存在一定逻辑关系的类组织在一起,又可以对外界隐藏;

● 方便各类编写事件驱动程序;

● 方便编写线程代码。

3. 分类

Java中的内部类可以分为如下几种类型:

成员内部类

静态内部类

局部内部类

匿名内部类

虽然大多数时候,内部类用得并不多,但我们也有必要了解它们是如何具体使用的。

4. 内部类的特点

内部类相比外部类,具有如下特点:

内部类可以访问外部类的私有成员,且不破坏封装性

内部类仍是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但前面会冠以外部类的类名和$符号,该文件名的格式是外部类名$内部类名.class;

因为内部类是外部类的一个成员,所以内部类不能用普通的方式访问,但内部类可以自由地访问外部类里的成员变量,无论是否被private修饰;

如果是静态内部类,我们不能随便访问外部类的成员变量,只能访问外部类的静态成员变量。

5. Java类的创建要求

我们在创建定义Java类时,应该遵循如下要求:

一个java文件中可以编写多个类,但只能有一个类使用public关键词进行修饰,这称之为主类;

主类名必须与文件名一致,在开发中,应尽量只在一个java文件中编写一个类;

外部类只有两种访问级别:public 和默认;内部类则有 4 种访问级别:public、protected、 private 和默认;

在外部类中,可以直接通过内部类的类名来访问内部类;

在外部类以外的其他类中,需要通过内部类的完整类名来访问内部类;

内部类与外部类不能重名。

接下来我们就针对上面提到的几种内部类,分别给大家讲解这几种内部类的用法。

二. 成员内部类

1. 概念

成员内部类就是指没有被static修饰的内部类,也可以称为非静态内部类。

2. 特点

成员内部类具有如下特点:

在早期的jdk版本中,成员内部类中只能定义非静态的属性和方法,除非同时使用final和static进行修饰;

在新版的jdk中,成员内部类中也可以定义静态的属性和方法;

成员内部类可以访问外部类的所有成员,包括私有和静态的成员,即使是多层嵌套时也如此;

外部类不能直接访问内部类的成员,必须通过内部类的实例对象访问;

在外部类的静态方法和外部类以外的其他类中,必须通过外部类的实例创建内部类的实例对象;

外部类的实例与内部类实例是一对多的关系,即一个内部类实例只对应一个外部类实例,但一个外部类实例则可以对应多个内部类实例。

3. 语法

如果是在外部类中,创建成员内部类对象的基本语法格式如下:

内部类 对象名 = new 内部类();

如果是在外部的其他类中,或者是在外部类的静态方法中,创建成员内部类对象的基本语法格式如下:

内部类 对象名 = new 外部类().new 内部类();

4. 案例

4.1 定义成员内部类

  1. /**
  2. * 成员内部类
  3. */
  4. public class OuterClass {
  5. // 外部类的非静态成员
  6. String name = "一一哥";
  7. private String hobby = "撸码";
  8. static int age = 30;
  9. // 非静态方法
  10. public void show() {
  11. //这里的this是指OuterClass对象
  12. System.out.println("show方法...name="+this.name);
  13. //如果是在外部类里面创建内部类的对象,就不需要创建外部类实例,可以直接new 内部类()
  14. //InnerClass inner = new InnerClass();
  15. }
  16. // 定义一个成员内部类
  17. public class InnerClass {
  18. // 也可以定义私有属性
  19. private int a = 10;
  20. //在早期的JDK中,成员内部类中不能定义静态变量;但在新版JDK中,成员内部类中可以定义静态变量
  21. static int b = 20;
  22. // 非静态方法
  23. public void m1() {
  24. // 这里的this对象是InnerClass内部类对象
  25. System.out.println("成员内部类的成员变量:" + this.a);
  26. //外部类.this.属性或方法,这个this是外部类对象
  27. System.out.println("外部类的成员变量:" + OuterClass.this.name);
  28. //内部类中可以访问外部类的私有成员和静态成员
  29. System.out.println("外部类的私有成员变量:" + hobby);
  30. System.out.println("外部类的静态变量:" + age);
  31. }
  32. //在早期的JDK中,成员内部类中不能定义静态方法;但在新版JDK中,成员内部类中可以定义静态方法
  33. public static void m2() {
  34. System.out.println("调用成员内部类的静态变量:" + b);
  35. System.out.println("调用外部类的静态变量:" + age);
  36. //在静态方法中创建内部类对象,也要通过内部类 对象名 = new 外部类().new 内部类();的格式
  37. //InnerClass innerClass = new OuterClass().new InnerClass();
  38. }
  39. }
  40. }

我们要注意,在早期的JDK中,成员内部类中不能定义静态属性和方法;但在新版JDK中,成员内部类中可以定义静态的属性和方法。并且我们要搞清楚在不同的位置上,创建内部类对象的方式,以及this的具体含义。

4.2 定义测试类

我们在外部的其他类中,要想创建出一个成员内部类的对象,需要通过如下形式:

内部类 对象名 = new 外部类().new 内部类();

  1. public class InnerClassTest {
  2. public static void main(String[] args) {
  3. //在外部的其他类中,不能直接创建内部类对象,否则:
  4. //No enclosing instance of type OuterClass is accessible.
  5. //Must qualify the allocation with an enclosing instance of type OuterClass
  6. //(e.g. x.new A() where x is an instance of OuterClass).
  7. //InnerClass inner=new InnerClass();
  8. //在外部的其他类中创建内部类对象,需要通过如下格式:
  9. //内部类 对象名 = new 外部类().new 内部类();
  10. //InnerClass inner=new OuterClass().new InnerClass();
  11. //也可以拆分成如下格式:
  12. OuterClass outer=new OuterClass();
  13. InnerClass inner=outer.new InnerClass();
  14. inner.m1();
  15. InnerClass.m2();
  16. }
  17. }

5. 访问方式小结

学习到这里,你可能会被内部类与外部类之间的调用访问关系整蒙圈,所以给大家梳理了一下访问方式:

  1. 成员内部类 访问 外部类的成员(属性、方法),可以【直接访问使用】;

  2. 外部类 访问 成员内部类,需要【直接创建内部类对象后再访问】,即 new InnerClass();

  3. 外部的其他类 访问 成员内部类,需要【创建外部类对象,再创建内部类对象后访问】,即 InnerClass inner=new OuterClass().new InnerClass();

6. 关于this的注意事项

在之前给大家讲过this的作用和用法,但在内部类中,关于this,我们需要注意以下两点:

● 如果同时存在外部类和内部类,那么this在哪个类中使用,this就代表哪个类的对象;

● 如果内部类想要通过this来调用外部类的属性和方法,需要使用外部类名.this.属性或者方法名。

三. 局部内部类

1. 概念

局部内部类是指在方法中定义的内部类。

2. 特点

局部内部类具有如下特点:

局部内部类只能在方法中定义和创建对象,也只在当前方法中有效;

局部内部类中可以访问外部类的所有成员;

局部内部类与局部变量一样,不能使用访问控制修饰符(public、private和protected)和static修饰符;

在jdk 7版本中,如果局部变量是在局部内部类中使用,必须显式地加上final关键字;在jdk 8版本中,会默认添加final关键字;

局部内部类只能访问当前方法中final类型的参数与变量。如果方法中的成员与外部类的成员同名,可以使用 .this. 的形式访问外部类成员;

局部内部类中还可以包含内部类,但这些内部类也不能使用访问控制修饰符(public、private 和 protected) 和 static修饰符;

局部变量在方法执行结束后会被销毁,而局部内部类的对象会等到内存回收机制进行销毁。如果是局部内部类里的常量,该常量会被存放在常量池中。

3. 语法

创建局部内部类对象的基本语法格式如下:

  1. public class PartClass {
  2. public void method() {
  3. //在方法中定义的内部类,就是局部内部类
  4. class Inner {
  5. //属性
  6. //方法
  7. }
  8. }
  9. }

4. 案例

4.1 定义局部内部类

我们来定义一个局部内部类的案例代码。

  1. /**
  2. *
  3. * 局部内部类---定义在方法中的内部类
  4. */
  5. public class PartOuterClass {
  6. //类的成员变量
  7. String name="一一哥";
  8. private int age=30;
  9. static String hobby="java";
  10. public void show() {
  11. //局部变量
  12. //JDK 7之前,匿名内部类和局部内部类中访问外部的局部变量时,该变量需要明确地带有final修饰符
  13. //final int num = 10;
  14. //Effectively final特性
  15. int num = 10;
  16. //局部内部类,类似于是方法中的局部对象
  17. class PartInnerClass{
  18. //内部可以正常定义方法
  19. public void m1() {
  20. //访问外部类的非静态成员,可以使用OuterClass.this.成员的格式,也可以直接访问
  21. //System.out.println("外部类的成员变量"+name);
  22. System.out.println("外部类的成员变量"+PartOuterClass.this.name);
  23. System.out.println("外部类私有的成员变量"+age);
  24. System.out.println("外部类的静态变量"+hobby);
  25. //局部内部类,可以直接访问方法中的局部变量
  26. System.out.println("访问局部变量"+num);
  27. }
  28. //在新版的jdk中,也可以定义静态的属性和方法,老版的jdk则不行
  29. static int b=10;
  30. public static void m2() {
  31. System.out.println("外部类的静态变量,hobby="+hobby+",b="+b);
  32. }
  33. }
  34. //创建局部内部类对象
  35. PartInnerClass inner = new PartInnerClass();
  36. inner.m1();
  37. //在当前类中,局部内部类可以直接访问静态成员
  38. PartInnerClass.m2();
  39. }
  40. }

在JDK 7之前,匿名内部类和局部内部类中访问外部的局部变量时,该变量需要明确地带有final修饰符。但从JDK 8之后,我们可以不带final修饰符,而是由系统默认添加了。

4.2 定义测试类

接下来我们对上面的案例进行测试。

  1. public class PartInnerClassTest {
  2. public static void main(String[] args) {
  3. //创建外部类对象,调用方法,执行局部内部类
  4. PartOuterClass outer=new PartOuterClass();
  5. outer.show();
  6. }
  7. }

4.3 Effectively final特性

一般情况下,Java中的局部内部类和匿名内部类访问局部变量时,该变量必须由 final修饰,以保证内部类和外部类的数据一致性。但从 Java 8开始,我们可以不加 final修饰符,而是由系统默认添加,当然这在 Java 8以前是不允许的。Java将这个新的特性称为 Effectively(有效的、实际的) final 功能

另外在 Lambda表达式中,使用局部变量时也要求该变量必须是 final 修饰的,所以 effectively final特性在 Lambda表达式的上下文中非常有用。

其实effectively final特性,只是让我们不用显式地把变量声明为final修饰的,它给我们自动添加了final修饰词,但并没有取消final,主要是减少了一点不必要的操作,给开发节省了点时间。

四. 匿名内部类

1. 概念

匿名内部类就是指没有类名的内部类,必须在创建时使用 new 语句来声明。匿名内部类不能在Outer Class外部类中定义,而是要在某个方法的内部,通过匿名类(Anonymous Class)的形式来定义。 匿名内部类本身就是一个对象。

通常情况下,如果一个方法的参数是接口类型,且该接口只需要实现一次,那么我们就可以通过匿名内部类的形式来进行定义。另外如果该接口的实现每次都不同,也可以使用匿名内部类的形式进行定义。我们也可以把这种定义形式叫做 “接口回调” 。匿名内部类的代码格式使得代码更加简洁、紧凑,模块化程度也更高。

2. 特点

匿名内部类具有如下特点:

匿名内部类本身就是一个对象;

一般在匿名内部类中不会定义属性和方法,因为没有意义;

匿名内部类的父类一般都是抽象类或者是接口;

匿名内部类和局部内部类一样,可以访问外部类的所有成员;

如果匿名内部类位于方法中,则该类只能访问方法中 final 类型的局部变量和参数;

匿名内部类中允许使用非静态代码块对成员进行初始化操作;

匿名内部类的非静态代码块会在父类的构造方法之后被执行。

3. 语法

通常匿名内部类有两种实现方式:

继承一个类,重写其方法;

实现一个或多个接口,并实现其方法。

创建匿名内部类对象的基本语法格式如下:

new <类或接口> (){

重写类或接口的方法

}

4. 案例

为了给大家演示匿名内部类的用法,接下来壹哥设计一个用于模拟按钮点击事件的案例。当我们进行安卓等设备开发时,面板上有个按钮,点击该按钮,如何监听点击事件?在Android系统中提供了各种对应的按钮点击监听事件。所以这里壹哥就通过实现接口的形式来定义匿名内部类,模拟一个单击事件。

4.1 定义接口

首先我们需要定义一个接口,表示单击监听,内部有个点击事件。

  1. /**
  2. * 点击监听事件
  3. */
  4. public interface OnClickListener {
  5. //点击事件
  6. void onClick();
  7. }

4.2 定义Button按钮类

然后定义一个Button按钮类,给Button按钮安排一个点击监听方法。

  1. /**
  2. *
  3. * 局部内部类---定义在方法中的内部类
  4. */
  5. public class Button {
  6. //处理案例点击的监听事件
  7. public void setOnClickListener(OnClickListener listener) {
  8. listener.onClick();
  9. }
  10. }

4.3 定义测试类

接下来我们就测试运行上面的代码。

  1. /**
  2. * 匿名内部类测试
  3. */
  4. public class AnonyInnerClassTest {
  5. public static void main(String[] args) {
  6. //外部变量
  7. int num=20;
  8. //测试匿名内部类
  9. Button btn=new Button();
  10. //模拟处理按钮的点击事件
  11. btn.setOnClickListener(new OnClickListener() {//这里就是一个匿名内部类
  12. //在匿名内部类中,可以允许使用非静态代码块进行成员初始化操作。
  13. int i;
  14. { // 非静态代码块,在构造方法之后执行
  15. i = 100; //成员初始化
  16. }
  17. @Override
  18. public void onClick() {
  19. System.out.println("按钮被点击啦...i="+i+",num="+num);
  20. }
  21. });
  22. }
  23. }

根据上面的案例可知:

● 在匿名内部类中,可以允许使用非静态代码块进行成员初始化操作;

● 匿名内部类的非静态代码块,会在构造方法之后执行;

● 匿名内部类也可以直接使用外部类的成员。

五. 静态内部类

1. 概念

静态内部类和成员内部类的定义类似,但要使用static修饰,所以称为静态内部类(Static Nested Class)。

静态内部类和成员内部类有很大的不同,它不再依附于Outer的实例,而是一个完全独立的类,因此无法引用Outer.this的方式调用。但它可以访问Outer类的private静态字段和静态方法,如果我们把静态内部类移到Outer类之外,就失去了访问private的权限。

2. 特点

● 静态内部类中可以定义非静态的属性和方法,也可以定义静态的属性和方法;

● 静态内部类中只能访问静态外部类的静态属性和方法。

3. 语法

创建静态内部类对象的基本语法格式如下:

内部类 对象名 = new 外部类.内部类();

4. 案例

4.1 定义静态内部类

这里我们先简单定义一个静态内部类,后面我们在学习内部类时再专门讲解。在这个静态内部类中,定义了一个方法,来访问外部类中的普通属性和静态属性。我们要记住以下几点:

静态内部类访问外部类的成员变量时,需要先创建外部类对象;

非静态内部类可以直接访问使用外部类的成员变量,如同使用本类中的变量;

所有的内部类访问外部类的静态变量时,可以直接通过"外部类.静态变量"的形式访问。

  1. /**
  2. * 外部类和内部类
  3. */
  4. public class OuterClass {
  5. //普通属性,属于外部类
  6. static int outerNum=10;
  7. //定义一个静态的内部类,如果不带static,就是一个普通的内部类。
  8. //内部类的使用,和普通类一样,里面可以正常定义属性、方法、构造方法等。
  9. //static前面可以带public等任意访问修饰符,也可以不带!
  10. static class InnerClass{
  11. //私有属性无法在类的外部直接访问
  12. //private int innerNum=20;
  13. int innerNum=20;
  14. public void printNum() {
  15. //定义外部类对象
  16. OuterClass outer=new OuterClass();
  17. //这里的this是指InnerClass内部类对象!
  18. System.out.println("innerNum="+this.innerNum+",outerAge="+outer.outerAge+",outerNum="+OuterClass.outerNum);
  19. }
  20. }
  21. }

对于静态内部类而言,static前面可以带public等任意访问修饰符,也可以不带!

4.2 定义测试类

我们再定义一个测试类,看看内部类对象是怎么调用的。

  1. /**
  2. * 测试访问内部类
  3. */
  4. public class InnerClassTest {
  5. public static void main(String[] args) {
  6. //创建内部类对象,格式为“外部类.内部类 对象名 = new 外部类.内部类的构造方法”
  7. OuterClass.InnerClass inner = new OuterClass.InnerClass();
  8. //调用内部类的方法
  9. inner.printNum();
  10. //访问外部类属性
  11. System.out.println("outerNum="+OuterClass.outerNum);
  12. //访问内部类属性
  13. System.out.println("innerNum="+inner.innerNum);
  14. }
  15. }

5. 访问方式小结

对于静态内部类的访问要求,给大家总结如下:

  1. 静态内部类中可以直接访问外部类的所有静态方法,包含私有的,但不能直接访问非静态成员;

  2. 静态内部类可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员;

  3. 如果静态内部类 访问 外部类 的静态属性、静态方法等,访问方式是【直接访问】;

  4. 如果外部类或外部的其他类来 访问 静态内部类,访问方式是【外部类.内部类 对象名 = new 外部类.内部类的构造方法】,创建出内部类对象后再访问。


六. 结语

至此我们就把内部类给大家详细地介绍了,现在你学会了吗?我们来总结一下内部类的重点内容吧:

内部类分为成员内部类、局部内部类、匿名内部类和静态内部类;

成员内部类和匿名内部类在本质上是相同的,都必须依附于外部类的实例,会隐含地持有Outer.this实例,并拥有外部类的private访问权限;

静态内部类是独立类,但拥有外部类的private访问权限;

如果外部类和内部类中的成员重名时,内部类访问时默认会遵循就近原则;如果想访问外部类的成员,则可以用【外部类名.成员】的形式来访问。

Java内部类的使用介绍详解的更多相关文章

  1. Java内部类与final关键字详解

    一.内部类的几种创建方法: 1.成员内部类 class Outer{ private int i = 1; class Inner{ public void fun() {System.out.pri ...

  2. “全栈2019”Java第一百一十章:局部内部类与匿名内部类区别详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  3. “全栈2019”Java第九十八章:局部内部类访问作用域成员详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  4. “全栈2019”Java第七十七章:抽象内部类与抽象静态内部类详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  5. “全栈2019”Java第六十七章:内部类、嵌套类详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  6. java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET

    java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET 亲,“社区之星”已经一周岁了!      社区福利快来领取免费参加MDCC大会机会哦    Tag功能介绍—我们 ...

  7. Elasticsearch java api 基本搜索部分详解

    文档是结合几个博客整理出来的,内容大部分为转载内容.在使用过程中,对一些疑问点进行了整理与解析. Elasticsearch java api 基本搜索部分详解 ElasticSearch 常用的查询 ...

  8. (转)Java并发包基石-AQS详解

    背景:之前在研究多线程的时候,模模糊糊知道AQS这个东西,但是对于其内部是如何实现,以及具体应用不是很理解,还自认为多线程已经学习的很到位了,贻笑大方. Java并发包基石-AQS详解Java并发包( ...

  9. java对象池commons-pool-1.6详解(一)

    自己的项目中用到了 对象池 commons-pool: package com.sankuai.qcs.regulation.protocol.client; import com.dianping. ...

  10. Java网络编程和NIO详解开篇:Java网络编程基础

    Java网络编程和NIO详解开篇:Java网络编程基础 计算机网络编程基础 转自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我们是幸运的,因为 ...

随机推荐

  1. Verilog教程

    1. 简介 当用 Verilog 设计完成数字模块后进行仿真时,需要在外部添加激励,激励文件叫 testbench. Verilog 的主要特性: 可采用 3 种不同的方式进行设计建模:行为级描述-- ...

  2. 第一个程序,Hello,World!

    Hello World 创建一个文件夹,存放代码 新建一个java文件 后缀名为.java 编写代码 public class Hello{    public static void main(st ...

  3. 【git入门】基于阿里云搭建git

    本文旨在说明基本的git使用流程,分为以下几个部分: 1.安装git环境 2.注册 3.git基本操作 一.安装git环境 第一次使用git,需要先安装配置git环境,windows版下载地址http ...

  4. apt-get install 出现could not open lock file /var/lib/dpkg/lock错误问题

    apt-get install 经常出现 could not open lock file /var/lib/dpkg/lock -open 错误问题 一种解决办法 1.切换到root用户 su ~ ...

  5. C 系列的暂停

    由于Mooc上有关C 的课程并不是很全面,网络上有关于C 的消息过于杂糅,所以暂时停止C的学习,重启时间暂定,等什么时候需要的时候再做重启.

  6. 1022 Digital Library (30分)

    本题题意很好读,看上去也不难写 写完运行才发现输出title只有一个单词... 后来把cin >> t换成了getline(cin, t) 还有一个坑点: Line #1: the 7-d ...

  7. 图的基本操作 (c语言)

    图的基本操作:创建 删除 遍历 创建:邻接矩阵和邻接表 十字链表 下面代码是邻接矩阵的定义和邻接表的创建 遍历:深度优先遍历(一条路走到黑) 广度优先遍历(树的层次遍历) 具体代码: #include ...

  8. This will upgrade your R installation.

    sudo add-apt-repository ppa:marutter/rrutter sudo apt update sudo apt full-upgrade

  9. Linux防火墙相关命令

    查看以开放端口 firewall-cmd --list-ports 开启端口如6379 firewall-cmd --zone=public --add-port=6379/tcp --permane ...

  10. MobaXterm注册认证版,亲测可用,操作简单(本机已安装python3环境)

    去github地址下下载代码 解压后在该目录下打开CMD 执行MobaXterm-Keygen.py <UserName> <Version>命令 生成的文件放在安装目录下,我 ...