概念

一个类的定义出现在另外一个类,那么这个出现的类就叫内部类(Inner),内部类所在的类叫做外部类(Outer)。

类里面的内容可以写成员变量、成员方法、构造方法、静态成员、构造块和静态代码块、内部类。

说明类体的内容越来越复杂,我们可以往里面写的内容越来越多。

思考🤔:为什么我们需要内部类呢?在什么情况下可以把一个类写在另外一个类的内部呢?

应用

  • 当一个类对应提供的内容仅仅是为某一个类单独服务时,那就可以将这个类定义为所服务类中的内部类。
  • 既然是内部类,那也是外部类的一部分「相当于都是一家人」,所以也可以访问外部类的私有的成员变量,不需要创建对象实例,然后实例名.变量名或方法名引用,或调get和set方法。

分类

普通内部类

  • 直接将一个类的定义放在另外一个类的类体中。

内部类可以用public修饰

静态内部类

  • 使用static修饰的内部类,属于类层级。{可直接调用,不用实例化}

局部内部类

  • 直接将一个类的定义放在方法体的内部

局部变量,就是定义在方法内的变量。同样,局部内部类,就是定义在方法内的类。

匿名内部类

  • 就是指没有名字的内部类

没有名字的内部类,开发中用的比较多,也比较不好理解

普通内部类

和我们讲的成员方法和成员变量是一个平行关系

格式

  1. 访问修饰符 class 外部类的类名 {
  2. 访问修饰符 class 内部类的类名 {
  3. 内部类的类体;
  4. }
  5. }

内部类和平时写的类体没什么区别;只不过是放在类里面了。

调用方式

  1. 外部类名 外部类实例名 = new 外部类名();
  2. 外部类名.内部类名 内部类实例名 = 外部类实例名.new 内部类名();

特点

  • 普通内部类和普通类一样,定义成员变量、成员方法、构造方法
  • 普通内部类权限修饰符都可以用
  • 创建调用普通内部类,需要外部类实例对象来创建内部类对象
  • 内部类访问外部类的成员变量/方法,并且这些成员变量名和方法名有同名,那需要使用this关键字
  • final可修饰内部类

示例

  • 对应编写一个普通内部类
  1. public class InnerDemo {
  2. //成员变量
  3. private int id = 1;
  4. //定义普通内部类
  5. public class NormalInner{
  6. private String name = "inner";
  7. public NormalInner(String name) {
  8. this.name = name;
  9. System.out.println("普通内部类NormalInner的有参构造方法");
  10. }
  11. public NormalInner() {
  12. System.out.println("普通内部类NormalInner的无参构造方法");
  13. }
  14. public void show(){
  15. System.out.println("外部类InnerDemo的成员变量id值是:"+ id);
  16. System.out.println("普通内部类NormalInner的成员变量name值是:"+name);
  17. }
  18. //内部类不能写main方法
  19. /*public static void main(String[] args) {
  20. }*/
  21. }
  22. }
  • 内部类不能添加对应的static的main方法
  • 内部类可以直接访问外部类的成员变量
  • 内部类有构造方法

调用

我们发现对应的内部类声明,不能直接声明,如果直接声明外部类.内部类会报错,告诉我们需要把内部类使用static关键字进行修饰。那我们只想要调用的话,只能是先声明对应的外部类,然后再根据外部类的实例名.new 内部类才可以进行调用

  1. public class Test {
  2. public static void main(String[] args) {
  3. //声明InnerDemo
  4. InnerDemo innerDemo = new InnerDemo();
  5. //指向内部类
  6. InnerDemo.NormalInner normalInner = innerDemo.new NormalInner();
  7. // InnerDemo.NormalInner normalInner = new InnerDemo.NormalInner();
  8. //调用内部类的show()方法
  9. normalInner.show();
  10. }
  11. }
  1. 普通内部类NormalInner的无参构造方法
  2. 外部类InnerDemo的成员变量id值是:1
  3. 普通内部类NormalInner的成员变量name值是:inner

虽然现在我们在外面可以调用成功,但是我们对应内部类主要是为了外部类去使用的,所以我们内部类修饰符用private调用

面试考点

形参变量、内部类成员变量、外部类成员变量,三个都同名的情况下,怎么调用??
源代码:

  1. public class InnerDemo {
  2. //成员变量
  3. private int id = 1;
  4. //定义普通内部类
  5. public class NormalInner{
  6. private int id = 2;
  7. public void show2(int id){
  8. System.out.println("形参变量id:"+ id);//局部优先元素
  9. System.out.println("外部类InnerDemo的成员变量id值是:"+ InnerDemo.this.id);//外部类名.this.成员变量名
  10. System.out.println("普通内部类NormalInner的成员变量id值是:"+ this.id);
  11. }
  12. }
  13. }

调用方式:

  1. public class Test {
  2. public static void main(String[] args) {
  3. //声明InnerDemo
  4. InnerDemo innerDemo = new InnerDemo();
  5. //指向内部类
  6. InnerDemo.NormalInner normalInner = innerDemo.new NormalInner();
  7. //调用内部类的方法
  8. normalInner.show2(6);
  9. }
  10. }
  1. 形参变量id6
  2. 外部类InnerDemo的成员变量id值是:1
  3. 普通内部类NormalInner的成员变量id值是:2

静态内部类

一说静态,大家是不是就会想到我们前面说的static关键字。如果加上static关键字,就代表着它修饰的是类层级,对应类层级的内容就可以直接类名.调用

格式

  1. 访问修饰符 class 外部类的类名 {
  2. 访问修饰符 static class 内部类的类名 {
  3. 内部类的类体;
  4. }
  5. }

静态内部类和普通内部类就是多了一个static关键字

调用方式

  1. 外部类名.内部类名 内部类实例名 = new 外部类名.内部类名();

特点

  • 静态内部类 不能直接访问外部类的非静态成员
  • 静态内部类 可以直接创建对象
  • 如果静态内部类访问外部类中与本类内同名的成员变量或方法时,需要使用类名.的方式访问

示例

  • 对应编写一个静态内部类
  1. public class StaticInnerDemo {
  2. private int id = 1;
  3. private static int sid = 2;
  4. /**
  5. * 定义静态内部类
  6. */
  7. public static class StaticInner{
  8. private int sinId = 3;
  9. private static int sid = 4;
  10. public StaticInner() {
  11. System.out.println("静态内部类StaticInner的构造方法");
  12. }
  13. public void show(){
  14. System.out.println("外部类StaticInnerDemo的静态成员变量sid值是:"+ sid);
  15. System.out.println("静态内部类StaticInner的成员变量sinId值是:"+ sinId);
  16. //访问外部类的id是否可以访问
  17. // System.out.println("外部类StaticInnerDemo的成员变量id值是:"+ id);//不能访问Error:静态上下文中不能访问非静态成员,此时还没有创建对象
  18. //不能在静态区域访问非静态资源
  19. }
  20. public void show2(int sid){//就近原则
  21. System.out.println("形参变量sid:"+ sid);//局部优先元素 形参值
  22. //内部类的成员id
  23. System.out.println("静态内部类StaticInner的静态成员变量sid值是:"+ StaticInner.sid);
  24. //外部类的成员id
  25. System.out.println("外部类StaticInnerDemo的静态成员变量sid值是:"+ StaticInnerDemo.sid);
  26. }
  27. }
  28. }
  • 静态内部类和普通内部类一样,都可以创建成员变量和方法
  • 静态内部类有构造方法
  • 静态内部类可以直接访问外部类的静态变量

调用

我们发现对应的静态内部类声明,可以直接外部类.内部类声明,因为static修饰的内容都是类层级的,可以直接调用。

  1. public class Test {
  2. public static void main(String[] args) {
  3. System.out.println("-------静态内部类-------");
  4. //1.声明静态内部类的引用指向该类型的对象
  5. StaticInnerDemo.StaticInner staticInner = new StaticInnerDemo.StaticInner();
  6. //2.内部类调用方法
  7. staticInner.show();
  8. System.out.println("-------静态内部类静态成员变量-------");
  9. staticInner.show2(9);
  10. }
  11. }
  1. -------静态内部类-------
  2. 静态内部类StaticInner的构造方法
  3. 外部类StaticInnerDemo的静态成员变量sid值是:4
  4. 静态内部类StaticInner的成员变量sinId值是:3
  5. -------静态内部类静态成员变量-------
  6. 形参变量sid9
  7. 静态内部类StaticInner的静态成员变量sid值是:4
  8. 外部类StaticInnerDemo的静态成员变量sid值是:2

可以看到对应的静态内部类和外部类的成员变量(static修饰)同一个名字的情况下,调用的时候需要说明对应类名.变量名

面试考点

内部类、外部类方法,同名的情况下,怎么调用??
源代码:

  1. public class StaticInnerDemo {
  2. //...
  3. public void show(){
  4. System.out.println("外部类的show方法");
  5. }
  6. /**
  7. * 定义静态内部类
  8. */
  9. public static class StaticInner{
  10. //...
  11. public void show(){
  12. System.out.println("内部类的show方法");
  13. }
  14. public void show2(int sid){
  15. //...
  16. show();
  17. }
  18. }
  19. }

内部类show2()调用的是内部类的show()方法,如果想要调用外部类的show()方法,需要用类名.的方式去访问

StaticInnerDemo.show()这个时候会报错,对应的外部类的show方法添加static关键字可以解决

调用方式:

如果是不想方法上添加static调用,则只能new StaticInnerDemo().show()这样调用

总结

  • 有static就是类层级,类名.调用
  • 没有static就是对象层级,引用.调用

都是static关键字修饰的那几条

局部内部类

看到这个词大家应该会联想到我们前面说的局部变量,什么是局部变量呢?就是在方法体内定义的变量就是局部变量

而且局部变量只在方法体内生效,出了方法体这个变量就失效了。

局部内部类又叫方法内部类,顾名思义写在方法体的内部就叫局部内部类。

格式:

  1. 访问修饰符 class 外部类的类名 {
  2. 访问修饰符 返回值类型 成员方法名(形参列表) {
  3. class 内部类的类名 {
  4. 内部类的类体;
  5. }
  6. }
  7. }

局部内部类没有访问权限修饰符,只有一个class+类名

类的定义挪到了方法体里面

调用方式

  1. 方法内调用:
  2. 局部内部类类名 局部内部类实例名 = new 局部内部类类名();
  3. 局部内部类实例名.内部类方法名();

特点

  • 局部内部类只能在该方法的内部可以使用。
  • 局部内部类可以在方法体内部直接创建对象。
  • 局部内部类不能使用访问控制符和static关键字修饰符。
  • 局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致。

示例

  • 对应编写一个局部内部类
  1. public class AreaInnerDemo {
  2. private int aid = 2;
  3. public void show(){
  4. //定义局部内部类 局部内部类:只在当前方法体的内部使用 不能用访问修饰符
  5. class AreaInner{
  6. private int id = 1;
  7. public AreaInner() {
  8. System.out.println("局部内部类AreaInner的构造方法");
  9. }
  10. public void testArea(){
  11. System.out.println("局部内部类AreaInner的id的值:"+id);
  12. System.out.println("AreaInnerDemo的aid的值:"+aid);
  13. }
  14. }
  15. //声明局部内部类的引用指向局部内部类的对象
  16. AreaInner areaInner = new AreaInner();
  17. areaInner.testArea();
  18. }
  19. }

调用

我们发现对应的局部内部类声明,可以直接外部类实例名.内部类所在方法声明,因为局部内部类是声明在方法里面的,直接调用方法就会调用该局部内部类。

  1. public class Test {
  2. public static void main(String[] args) {
  3. System.out.println("-------局部内部类-------");
  4. AreaInnerDemo areaInnerDemo = new AreaInnerDemo();
  5. areaInnerDemo.show();
  6. }
  7. }
  1. -------局部内部类-------
  2. 局部内部类AreaInner的构造方法
  3. 局部内部类AreaInnerid的值:1
  4. AreaInnerDemoaid的值:2

可以看到对应的局部内部类只能在方法内调用,在外面调用方法的时候,其实是不知道里面有局部内部类的

匿名内部类

在讲匿名内部类的时候,我们先来讲一个概念,什么叫回调模式

回调模式的概念

  • 回调模式是指——如果一个方法的参数是接口类型,则在调用该方法时,
    需要创建并传递一个实现此接口类型的对象;而该方法在运行时会调用
    到参数对象中所实现的方法(接口中定义的)。

创建接口

该接口为了当方法参数提供

  1. public interface AnonyInterface {
  2. public abstract void show();
  3. }

创建接口实现类

  1. public class AnonyInterfaceImpl implements AnonyInterface{
  2. @Override
  3. public void show() {
  4. System.out.println("接口实现类的show方法");
  5. }
  6. }

调用

  1. public class AnonyInterfaceTest {
  2. //假设已有下面方法
  3. /**
  4. *
  5. * @param anonyInterface
  6. * 形参是接口体的引用,调用接口的show方法
  7. * 方法的参数是接口类型,则在调用该方法时,需要创建并传递一个实现此接口类型的对象
  8. * 该方法在运行时会调用到参数对象中所实现的方法
  9. */
  10. //接口类型的引用指向实现类型的对象,形成了多态
  11. public static void test(AnonyInterface anonyInterface){
  12. //编译阶段调用父类版本,运行调用实现类重写的版本
  13. anonyInterface.show();
  14. }
  15. public static void main(String[] args) {
  16. // AnonyInterfaceTest.test(new AnonyInterface() );//接口不能实例化
  17. AnonyInterfaceTest.test(new AnonyInterfaceImpl());
  18. }
  19. }

分享

  • 当接口/类类型的引用作为方法的形参时,实参的传递方式有两种:
    • 自定义类实现接口/继承类并重写方法,然后创建该类对象作为实参传递;
    • 使用上述匿名内部类的语法格式得到接口/类类型的引用即可;

匿名内部类

格式(重点)

  1. 接口/父类类型 引用变量名 = new 接口/父类类型() { 方法的重写 };
  1. package top.testeru.innerp;
  2. /**
  3. * @Package: top.testeru.innerp
  4. * @author: testeru.top
  5. * @Description: 测试已有的抽象方法
  6. * @date: 2022年02月21日 11:35 AM
  7. */
  8. public class AnonyInterfaceTest {
  9. public static void main(String[] args) {
  10. AnonyInterface anonyInterface = new AnonyInterface() {
  11. @Override
  12. public void show() {
  13. System.out.println("匿名内部类,虽然很抽象~");
  14. }
  15. };
  16. AnonyInterfaceTest.test(anonyInterface);
  17. }
  18. }

new完这个对象,内存空间就可以销毁了,对应的不占空间

这是Java8 以前

//Java8以后lambda表达式,简化代码
(参数列表)->{方法体}

  1. AnonyInterface anonyInterface1 = ()->System.out.println("匿名内部类lambda,虽然很抽象~");
  2. AnonyInterfaceTest.test(anonyInterface1);