当前位置: 首页 > news >正文

Java基础-内部类

内部类

    • 引言
      • 内部类的共性
      • 成员内部类
        • 静态内部类
        • 非静态内部类
      • 局部内部类
      • 匿名内部类
      • 内部类的使用场景和好处

引言

Java不仅可以定义变量和方法,还可以定义类.
内部类允许你把一些逻辑相关的类组织在一起,并可以控制内部中类的可见性.
这么看来,内部类就像是代码一种隐藏机制:将类放在其他类的内部,从而隐藏名字和组织代码的模式.
根据定义方式的不同,分为四种类型: 静态内部类, 成员内部类,局部内部类,匿名内部类

内部类的共性

  • 依然是一个独立的类,在编辑之后内部类会被编辑成独立的.class文件,但是前面会添加外部类的类名和$符号
  • 声明为静态的,就不能随便访问外部类的成员变量了,此时内部类只能访问外部类的静态成员变量或方法
  • 外部类不能直接访问内部类的成员,但可以通过内部类对象来访问

内部类是外部类的一个成员,因此内部类可以自由访问外部类的成员变量,无论是否为private.
因为某个外围类对象创建内部类对象时,此内部类会捕获一个隐式引用,它引用了实例化内部对象的外围类对象,通过这个指针,可以访问外围类对象的全部状态.

实现原理
反编译内部类字节码,分析主要通过以下几步做到的:

  1. 编译器为内部类添加一个成员变量,它的类型和外部类的类型相同,这个成员变量就是指向外部类对象的引用
  2. 编译器为内部类的构造方法添加一个参数,参数类型是外部类类型,在构造方法内部使用这个参数为1中添加的成员变量赋值
  3. 在调用内部类的构造函数初始化内部类对象时,会默认传入外部类的引用

下面我们举个例子来看一下:

public class Outter {private Inner inner = null;public Outter() {}public Inner getInnerInstance() {if(inner == null)inner = new Inner();return inner;}protected class Inner {public Inner() {}}
}

在这里插入图片描述
反编译Outter$Inner.class文件得到下面信息:

E:\Workspace\Test\bin\com\cxh\test2>javap -v Outter$Inner
Compiled from "Outter.java"
public class com.cxh.test2.Outter$Inner extends java.lang.ObjectSourceFile: "Outter.java"InnerClass:#24= #1 of #22; //Inner=class com/cxh/test2/Outter$Inner of class com/cxh/tes
t2/Outterminor version: 0major version: 50Constant pool:
const #1 = class        #2;     //  com/cxh/test2/Outter$Inner
const #2 = Asciz        com/cxh/test2/Outter$Inner;
const #3 = class        #4;     //  java/lang/Object
const #4 = Asciz        java/lang/Object;
const #5 = Asciz        this$0;
const #6 = Asciz        Lcom/cxh/test2/Outter;;
const #7 = Asciz        <init>;
const #8 = Asciz        (Lcom/cxh/test2/Outter;)V;
const #9 = Asciz        Code;
const #10 = Field       #1.#11; //  com/cxh/test2/Outter$Inner.this$0:Lcom/cxh/t
est2/Outter;
const #11 = NameAndType #5:#6;//  this$0:Lcom/cxh/test2/Outter;
const #12 = Method      #3.#13; //  java/lang/Object."<init>":()V
const #13 = NameAndType #7:#14;//  "<init>":()V
const #14 = Asciz       ()V;
const #15 = Asciz       LineNumberTable;
const #16 = Asciz       LocalVariableTable;
const #17 = Asciz       this;
const #18 = Asciz       Lcom/cxh/test2/Outter$Inner;;
const #19 = Asciz       SourceFile;
const #20 = Asciz       Outter.java;
const #21 = Asciz       InnerClasses;
const #22 = class       #23;    //  com/cxh/test2/Outter
const #23 = Asciz       com/cxh/test2/Outter;
const #24 = Asciz       Inner;{
/看中间的代码
final com.cxh.test2.Outter this$0;   //注意这一行代码!!!
/看中间的代码
public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);Code:Stack=2, Locals=2, Args_size=20:   aload_01:   aload_12:   putfield        #10; //Field this$0:Lcom/cxh/test2/Outter;5:   aload_06:   invokespecial   #12; //Method java/lang/Object."<init>":()V9:   returnLineNumberTable:line 16: 0line 18: 9LocalVariableTable:Start  Length  Slot  Name   Signature0      10      0    this       Lcom/cxh/test2/Outter$Inner;
}

final com.cxh.test2.Outter this$0;
这是一个指向外部类对象的指针.
也就是说,编译器会默认成员内部类添加了一个指向外部类对象的引用

那这个引用如何赋初值呢?
下面接着看一下内部类的构造器
public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);
虽然我们定义的内部类的构造器说无参构造器,但是编译器还是默认添加一个参数,该参数类型为指向外部类对象的一个引用.
所以成员内部类中的Outter this&0指针便指向类外部类对象,因此可以在成员内部类中随意访问外部类的成员.
从这里也间接说明了成员内部类是依赖外部类的,如果没有创建外部类的对象,则无法对Outter this&0引用进行初始化赋值,也就无法创建成员内部类的对象了.

成员内部类

成员内部类像是外部类的一个成员,它定义在另一个类的内部
成员内部类分为两种:

  1. 静态成员内部类: 使用static修饰类
  2. 非静态成员内部类: 不实用static修饰类,在没说明是静态成员内部类时,默认成员内部类指的是非静态成员内部类
静态内部类

定义在类内部的静态类,就是静态内部类.
静态内部类不需要依赖外部类,这点和静态成员属性类似,并且它不能使用外部类的非Static成员变量和方法.
因为没有外部类对象的情况下,我们可以创建出静态内部类对象,这时候如果允许访问外部类的非Static成员就会产生矛盾,因为外部类的非Static成员必须依附于具体的对象.

public class Out {private static int a;private int b;public static class Inner {public void print() {System.out.println(a);}}
}

访问作用域: 可以访问外部类所有的静态变量和方法,即使是private也一样可以访问
与类不同点: 和一般类一致,可以定义静态变量,方法,构造方法等
使用的方法: 外部类.静态内部类。如:Out.Inner inner = new Out.Inner();inner.print();

非静态内部类

最普通的内部类,定义位于另一类的内部,如下面形式

class Circle {double radius = 0;public Circle(double radius) {this.radius = radius;}class Draw {     //内部类public void drawSahpe() {System.out.println("drawshape");}}
}

类Draw像是Circle的一个成员,Circle称为外部类.
成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)
但是外部类访问内部类成员,首先要创建一个成员内部类的对象,再通过指向这个对象的引用来访问

  • 内部类访问外部类
class Circle {private double radius = 0;public static int count =1;public Circle(double radius) {this.radius = radius;}class Draw {     //内部类public void drawSahpe() {System.out.println(radius);  //外部类的private成员System.out.println(count);   //外部类的静态成员}}
}
  • 外部类访问内部类
class Circle {private double radius = 0;public Circle(double radius) {this.radius = radius;getDrawInstance().drawSahpe();   //必须先创建成员内部类的对象,再进行访问}private Draw getDrawInstance() {return new Draw();}class Draw {     //内部类public void drawSahpe() {System.out.println(radius);  //外部类的private成员}}
}

注意:
成员内部类和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员.
如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类.this.成员变量
外部类.this.成员方法

局部内部类

定义在一个方法或者一个作用域里面的内,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内.

class People{public People() {}
}class Man{public Man(){}public People getWoman(){class Woman extends People{   //局部内部类int age =0;}return new Woman();}
}

注意: 局部内部类就像是方法里面的一个局部变量一样,是不能有public,protected,private以及static修饰符的.

匿名内部类

这个应该是我们编写代码时用的最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更容易维护.

public class AnonymousInnerClassExample {  public static void main(String[] args) {  // 创建一个订单处理系统实例  OrderProcessingSystem system = new OrderProcessingSystem();  // 创建一个订单实例  Order order = new Order("123", 100.0);  // 使用匿名内部类实现一个打印订单详情的处理器  system.process(order, new OrderProcessor() {  @Override  public void processOrder(Order order) {  System.out.println("Processing order: " + order);  }  });  // 使用另一个匿名内部类实现一个打折处理订单的逻辑  system.process(order, new OrderProcessor() {  @Override  public void processOrder(Order order) {  double discount = 0.1; // 假设打10%的折扣  double discountedAmount = order.getAmount() * (1 - discount);  System.out.println("Processing discounted order: " + order + " with discounted amount: " + discountedAmount);  }  });  }  
}

使用匿名内部类的好处显而易见

  1. 比较简洁,只需要实现一个接口的简单任务,使用它可以避免创建额外命名类
  2. 可以访问其外部类所有成员(包括私有成员),在某些场景下非常灵活

内部类的使用场景和好处

为什么Java需要内部类呢?总结有下面四点:

  • 每个内部类都能独立的继承一个接口的实现,无论外部类是否已经继承了某个(接口)实现,对于内部类都没有影响.
    解释一下:
    Java本身不支持类的多继承,但可以通过内部类来实现接口的多继承效果.我们可以在一个外部类中定义多个内部类,每个内部类都可以实现不同的接口,从而达到类类似多继承的效果.
    举个简单的代码例子来说明这个概念:
// 定义一个接口A  
interface InterfaceA {  void methodA();  
}  // 定义另一个接口B  
interface InterfaceB {  void methodB();  
}  // 外部类  
class OuterClass {  // 外部类可以继承自一个类,这里我们假设它继承自Object(实际上所有类都隐式继承自Object)  // 内部类1,实现接口A  private class InnerClassA implements InterfaceA {  @Override  public void methodA() {  System.out.println("Implementing methodA from InterfaceA in InnerClassA");  }  }  // 内部类2,实现接口B  private class InnerClassB implements InterfaceB {  @Override  public void methodB() {  System.out.println("Implementing methodB from InterfaceB in InnerClassB");  }  }  // 可以提供获取内部类实例的方法  public InterfaceA getInnerClassA() {  return new InnerClassA();  }  public InterfaceB getInnerClassB() {  return new InnerClassB();  }  
}  // 使用示例  
public class Main {  public static void main(String[] args) {  OuterClass outer = new OuterClass();  // 获取并调用内部类A的方法  InterfaceA innerA = outer.getInnerClassA();  innerA.methodA();  // 获取并调用内部类B的方法  InterfaceB innerB = outer.getInnerClassB();  innerB.methodB();  }  
}
  1. 方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏
  2. 方便编写事件驱动程序
  3. 方便编写线程代码

相关文章:

  • Excel中筛选合并单元格后,只显示第一行怎么办?
  • [递归、搜索、回溯]----递归
  • 将List转换为数组或者将数组转换为List,如果改变了原始值,转换后的数据会发生改变吗?
  • 设计模式大题做题记录
  • 安装系统后,如何单个盘空间扩展多个盘空间?
  • ChatGPT的安全警告
  • 一篇搞定分布式缓存
  • 低空经济20人|卓翼智能任雪峰:以技术驱动市场,引领无人机细分领域创新
  • 找出单身狗1,2
  • 航测管家:智能化革新航测作业流程
  • bugku-misc隐写
  • 混合云构建-VPN打通阿里云和AWS云
  • 20 卷积层里的填充和步幅【李沐动手学深度学习v2课程笔记】
  • 无限debugger的几种处理方式
  • Node.js与Webpack笔记(一)
  • @angular/forms 源码解析之双向绑定
  • 《Javascript数据结构和算法》笔记-「字典和散列表」
  • 【vuex入门系列02】mutation接收单个参数和多个参数
  • 0x05 Python数据分析,Anaconda八斩刀
  • Angular Elements 及其运作原理
  • canvas绘制圆角头像
  • ES6系统学习----从Apollo Client看解构赋值
  • fetch 从初识到应用
  • flask接收请求并推入栈
  • java8-模拟hadoop
  • 线上 python http server profile 实践
  • LIGO、Virgo第三轮探测告捷,同时探测到一对黑洞合并产生的引力波事件 ...
  • # Panda3d 碰撞检测系统介绍
  • #laravel 通过手动安装依赖PHPExcel#
  • #我与Java虚拟机的故事#连载17:我的Java技术水平有了一个本质的提升
  • (31)对象的克隆
  • (JSP)EL——优化登录界面,获取对象,获取数据
  • (TOJ2804)Even? Odd?
  • (附源码)ssm跨平台教学系统 毕业设计 280843
  • (含react-draggable库以及相关BUG如何解决)固定在左上方某盒子内(如按钮)添加可拖动功能,使用react hook语法实现
  • (四)模仿学习-完成后台管理页面查询
  • (一)使用IDEA创建Maven项目和Maven使用入门(配图详解)
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • (原創) 未来三学期想要修的课 (日記)
  • (转) RFS+AutoItLibrary测试web对话框
  • (转) 深度模型优化性能 调参
  • (转)ABI是什么
  • (转)Java socket中关闭IO流后,发生什么事?(以关闭输出流为例) .
  • *** 2003
  • ***linux下安装xampp,XAMPP目录结构(阿里云安装xampp)
  • *Django中的Ajax 纯js的书写样式1
  • .NET BackgroundWorker
  • .NET 反射 Reflect
  • .NET 中 GetHashCode 的哈希值有多大概率会相同(哈希碰撞)
  • .net2005怎么读string形的xml,不是xml文件。
  • .NET程序员迈向卓越的必由之路
  • .Net通用分页类(存储过程分页版,可以选择页码的显示样式,且有中英选择)
  • @RunWith注解作用
  • @基于大模型的旅游路线推荐方案
  • [ Linux 长征路第二篇] 基本指令head,tail,date,cal,find,grep,zip,tar,bc,unname