【Java】一文学透四种内部类|保姆级详解,一看就会

news2024/11/25 10:45:42

在这里插入图片描述

文章目录

  • 一、什么是内部类?
  • 二、内部类的分类
    • 2.1:成员内部类(实例内部类)
      • 2.1.1:定义:
      • 2.1.2:特性
      • 2.1.3:实现原理:
      • 2.1.4:应用场景:
    • 2.2:静态内部类
      • 2.2.1:定义:
      • 2.2.2:特性
      • 2.2.3:实现原理:
      • 2.2.4:应用场景:
    • 2.3:方法内部类(局部内部类
      • 2.3.1:定义:
      • 2.3.2:特性
    • 2.4:匿名内部类
      • 2.4.1:定义:
      • 2.4.2:特性:
      • 2.4.3:实现原理:
      • 2.4.4:应用场景:
  • 三、内部类的好处

一、什么是内部类?

    所谓内部类,本质也是一个,主要是它的位置和我们平时所说的那种普通的类所在的位置不同。——在类/方法的内部.。且相对这个内部类而言,在外部的这个类叫作“外部类”。笼统的说,内部类的存在就是和类中其他所在的变量(non-staticstatic),一样,都是为外部类/外部类实例服务的!!

根据内部类所在的具体位置不同,我们可以将内部类进行分类,分成如下四种。

💡注意!:内部类虽然在编写代码时是在外部类内部的,但是编译之后实际上是编写在外部类外面的!所以,经过编译之后,内部类会形成单独的字节码文件!

二、内部类的分类

2.1:成员内部类(实例内部类)

2.1.1:定义:

实例内部类的地位和实例变量相同,都是依附于实例存在的。有实例才能实例化其成员——实例内部类。

public class Ourter {
    private int a;

    public class Innner {

    }
}

2.1.2:特性

  • 实例内部类对象创建的方法:

实例内部类的地位和实例变量相同,都是依附于实例存在的。有实例才能实例化其成员——实例内部类。

外部类名.内部类名 内部类对象 = 实例.new Inner()
public static void main(String[] args) {
        //创建外部类实例
        Outer outer = new Outer();
        //外部类名.内部类名 内部类对象 = 实例.new Inner()
        Outer.Inner inner = outer.new Inner();
    }
  • 实例内部类可以访问外部类成员的:

    • 外部类中任意访问修饰限定符的成员
    • 内部类成员和外部类成员同名时,就近原则
    • 访问同名外部类成员,使用外部类名.this.同名成员名
  • 和实例方法一样,实例内部类红不能定义静态变量&方法(final变量除外)

2.1.3:实现原理:

public class Outer {
    private int a;
	//实例内部类
    public class Inner {
        public void method() {
        	//访问外部类的实例变量
        System.out.println(a);
        }
    }
}

上面的代码经过编译后的大概的代码清单如下:


public class Outer {
	private final int a;
	static void access$0(Outer outer){
		return outer.a;
	}
}

public class Outer$Inner {
	final Outer outer;
	
	public Outer$Inner {
		final Outer$Inner (Outer outer){
			this.outer = outer;
		}
	
	public void method() {
		 System.out.println(Outer.access$0(outer));
	}
}
  • 内部类和外部类在编译后会生成两个类:OuterOuter$Inner,和两份独立的.class的字节码文件

  • 外部类中:

    • 为了保证在实例内部类中能访问外部类的private变量:外部类中会自动生成一个静态方法access$0,专门用来访问外部类的private变量:外部类名.access$0
    • 外部类的实例成员变量会自动加上final(线程安全,多线程访问&修改成员变量a,导致数据不一致,干脆当内部类访问成员变量时直接标上final,不让其访问)
  • 内部类中:

    • Outer&Inner 内中会自动生成一个外部类的实例变量final Outer outer(指向外部类实例)和并在并在构造方法中将其初始化(将外部类实例赋值给外部类的实例变量)。可以使用这个指向外部类实例的成员变量访问到外部类的非private成员。(否则就用上面的外部类名.access$0)
    • 无法对外部类中的成员变量进行修改(会被默认标记成final,保证线程安全)

如果外部类的非静态成员变量不是被内部类所访问,那么它们就不会被标记为 final。此外,如果需要在内部类中修改外部类的非静态成员变量,可以将这些成员变量声明为 volatile 或者使用 Atomic 类来保证线程安全。

2.1.4:应用场景:

  • 需要一个完整的类结构作为成员变量,且该内部类中需要访问到外部类的变量和方法
  • 外部类的一些方法的返回值是接口,可以定义一个private的内部类实现该接口并且返回,且对外完全隐藏
  • 举例:在Java API的类LinkedList中,两个方法ListIteratordescendingIterator的返回值都是接口Iterator(实现对链表遍历)。前者内部使用成员内部类ListItr,后者使用成员内部类DescendingIterator

2.2:静态内部类

2.2.1:定义:

静态内部类的地位和静态成员相同,都是依附于存在的。只需要类即可创建静态内部类

public class Ourter {
    private static int a;
	public static int b;
	
    public  static class StaticInnner {
		 System.out.println(a);
		 System.out.println(b);
    }
}

2.2.2:特性

  • 静态内部类对象创建的方法:

静态内部类是依附于类存在的,只要有类即可进行创建

外部类类名.内部类类名 内部类变量名 = new 外部类名.内部类名()
public class Outer {
    private static int a;
	public static int b;
	
    public static class StaticInner {
        public void method() {
            System.out.println(a);
            System.out.println(b);
        }
    }

    public static void main(String[] args) {
    //外部类类名.内部类类名 内部类变量名 = new 外部类名.内部类名()
        Outer.StaticInner staticInner = new Outer.StaticInner();
    }
}
  • 静态内部类可以访问外部类成员的:
    • 静态变量和静态方法

2.2.3:实现原理:

public class Outer {
    private static int a;
	public static int b;
	
    public static class StaticInner {
        public void method() {
            System.out.println(a);
            System.out.println(b);
        }
    }
}

上面代码经过编译之后的代码清单大致如下:

public class Outer {
    private static int a;
    public static int b;
	//自动生成静态方法,便于静态内部类访问静态成员变量
	static int access$0(){
		return a;
	}
}

public class Outer$StaticInner() {
	public void method(){
		System.out.println(Outer.access&0);
		 System.out.println(Outer.b);
	}
  • 内部类和外部类在编译后会生成两个类:OuterOuter$StaticInner

  • 外部类中:为了保证在实例内部类中能访问外部类的private变量:外部类中会自动生成一个静态方法access$0,专门用来访问外部类private变量

  • 内部类中:Outer&Inner内中会直接用类名.access$n()来访问外部类中的private变量。使用外部类名.变量名访问非private变量。在静态内部类中访问和修改外部类的静态变量时,它们不会被标记为 final,而是可以被随时访问和修改

需要注意多线程并发访问的问题。如果多个线程同时访问和修改静态变量,可能会出现数据不一致的问题。因此,需要采取适当的同步措施来保证线程安全。

2.2.4:应用场景:

  • Integer类中的私有静态内部类IntegerCache,支持整数的自动装箱
  • 表示链表的LinkedList内部有一个私有的静态内部类Node,表示链表中的每一个节点

2.3:方法内部类(局部内部类

2.3.1:定义:

使用场景较少,这里先简单介绍下,后续如果需要再把原理补上

class OutClass {
    int a = 10;

    public void method() {
        int b = 0;
        class InnerClass {
            public void method() {
                System.out.println(a);
                System.out.println(b);
            }
        }
        InnerClass innerClass = new InnerClass();
        innerClass.method();
    }

}

2.3.2:特性

  • 定义在方法体内部
  • 不能被publicstatic修饰
  • 只能在方法体内部创建和使用(为方法体服务)
  • 方法内部类也会有自己独立的字节码文件

2.4:匿名内部类

2.4.1:定义:

匿名内部类没有单独的定义,在创建对象的同时定义类

new 父类(参数列表){
	//匿名内部类的实现部分
}
new 父接口({
	//匿名内部类的实现部分
}

根据定义的语法规则可知,匿名内部类没有名字,但是一个具体的对象,且继承了一个类/实现了一个接口。

根据多态,可以用父类/接口,来接收该匿名内部类对象

父类名 匿名内部类对象名 = new 父类(参数列表){
		//匿名内部类的实现部分
	}
class Outer {
    public static void test(final int x, final int y) {
        //父类引用 = new 父类(参数列表){ 匿名内部类的实现}
        Point p = new Point(2, 3) {
            //计算test方法的(x,y)点到(2,3)点的距离
            @Override
            public double distance(double px, double py) {

                return super.distance(px, py);
            }
        };
        System.out.println(p.distance(x, y));
    }
}

public class Main {
    public static void main(String[] args) {
        Outer.test(3, 3);
    }
}

2.4.2:特性:

  • 匿名内部类只能被使用一次,用来创建一个对象
  • 没有名字和构造方法,但是可以根据参数列表调用其父类相应构造方法进行父类初始化
  • 像其他类一样,可以在其内部定义实例变量和方法
  • 可以有初始化代码块,起到构造(初始化)作用
  • 与方法内部类一样,匿名内部类可以访问外部类所有变量和方法,还有方法中的final参数和局部变量

2.4.3:实现原理:

class Outer {
    public static void test(final int x, final int y) {
        //父类引用 = new 父类(参数列表){ 匿名内部类的实现}
        Point p = new Point(2, 3) {
            //计算test方法的(x,y)点到(2,3)点的距离
            @Override
            public double distance(double px, double py) {

                return super.distance(px, py);
            }
        };
        System.out.println(p.distance(x, y));
    }
}

public class Main {
    public static void main(String[] args) {
        Outer.test(3, 3);
    }
}

上面代码经过编译后:

class Outer {
    public static void test(final int x, final int y) {
   		Point p = new Outer$1(this, 2, 3, x, y);
   		System.out.println(p.distance(x, y));
    }
}

class Outer$1 extends Point {
	//拿到外部类方法形参
	int x2;
	int y2;
	//外部类的引用
	Outer outer;
	Outer$1(Outer outer, x1, y1, x2, y2){
		super(x1, y1);//调用父类构造方法进行初始化
		this.outer = outer;//将外部类的引用赋值给成员变量
		this.x2 = x2;
		this.y2 = y2;
	}
  @Override
   	public double distance(double px, double py) {
		return super.distance(px, py);
   }
  • 内部类和外部类在编译后会生成两个类:OuterOuter$1
  • 外部类中:外部类的实例、方法参数x,y作为参数,传递给内部类的构造方法。以便在内部类中也可以访问到外部类的成员方法和变量以及方法参数。
  • 内部类中:内部类会将构造类的参传递给父类构造方法super(x,y),进行父类初始化。

2.4.4:应用场景:

在调用一些方法时,常需要传入一个接口参数来实现回调.比如Arrays.sort()方法。
在这里插入图片描述
前者是装有比较内容的数组,后者是接口参数。我们可以传入一个实现Compartor接口的匿名内部类进行传入

比如,实现一个对字符串数组不区分大小写的比较方法

public void sortIgnoreCase(String[] strings) {
        Arrays.sort(strings, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareToIgnoreCase(o2);
            }
        });
    }

Arrays.sort()当中传递Comparator对象(当中重写了compare方法),compare方法不是在写代码的时候调用,而是在sort排序方法中用到了需要比较方法的地方回过头来调用。也就是所说的回调
在这里插入图片描述

三、内部类的好处

实现更好的封装,简化代码


在这里插入图片描述

  • Java岛冒险记【从小白到大佬之路】

  • LeetCode每日一题–进击大厂

  • Go语言核心编程

  • 算法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/765939.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

字节跳动-抖音支付大量招聘实习生

字节跳动-抖音支付大量招聘实习生 字节跳动-抖音支付大量招聘实习生 要求&#xff1a;2024年6月毕业且有时间来实习&#xff08;大于3个月&#xff09;的在校学生&#xff08;本三&#xff0c;研二&#xff09;&#xff0c;日常和暑期都有转正机会&#xff5e; 职位描述&…

WPF 自定义控件完成库容表盘显示效果

先看一下显示效果&#xff1a; 需要注意的地方有以下几点&#xff1a; 表盘的刻度分部&#xff0c;长刻度和短刻度显示。在数值80W时&#xff0c;需要更改刻度盘的颜色渐变。在数值80W时&#xff0c;更改库容总数背景的显示&#xff0c;也是颜色渐变。刻度盘控件属性定义&…

印刷企业如何利用MES管理系统实现智能计划排产

在数字化时代&#xff0c;印刷企业面临着日益激烈的市场竞争和不断攀升的成本压力。为了提高生产效率和质量&#xff0c;印刷企业需要采用先进的生产管理系统。其中&#xff0c;MES生产管理系统已成为实现智能计划排产的重要工具。本文将探讨如何利用印刷MES管理系统实现印刷企…

界面控件DevExtreme PivotGrid,拥有新的HTML编码体验!

虽然DevExtreme刚刚发布了v23.1&#xff0c;但今天我们仍然要继续总结一下之前的主要更新&#xff08;v22.2&#xff09;中发布的一些与DevExtreme PivotGrid&#xff08;透视网格&#xff09;组件相关的重要特性。 DevExtreme拥有高性能的HTML5 / JavaScript小部件集合&#…

Django + Bootstrap - 【echart】 统计图表进阶使用-统计用户日活日增、月活月增等数据(二)

一. 前言 Bootstrap是一个流行的前端框架&#xff0c;而ECharts是一个流行的可视化库。 Bootstrap可以用来设计网站和应用程序的用户界面&#xff0c;而ECharts可以用来创建交互式和可视化的图表。 chart.js中文文档&#xff1a;http://www.bootcss.com/p/chart.js/docs/ 二. …

手把手教你搭建SpringCloud项目(六)Eureka实现服务发现

一、服务发现简介 各个微服务在启动时&#xff0c;将自己的网络地址等信息注册到服务发现组件上(eureka,zookeeper,Consul),服务发现组件会存储这些信息。服务消费者会从服务发现组件查询服务提供者的网络地址&#xff0c;然后根据该地址调用服务提供者的接口。各个微服务与服务…

centos升级龙蜥

centos升级龙蜥 龙蜥简介龙蜥官方社区centos升级龙蜥首先确认自己的centos版本下载迁移镜像源安装epel源迁移工具安装i686包查看执行迁移脚本结果查看重启机器查看系统信息 龙蜥简介 2021年10月19日的大会上&#xff0c;阿里云发布全新操作系统“龙蜥”并宣布开源。龙蜥操作系…

SAP与顺丰快递接口签名验证加密ABAP程序例子(MD5加密、转换为Base64字符串) <转载>

原文链接&#xff1a;http://www.baidusap.com/abap/7408 1, 顺丰平台数字签名简介 SAP系统和顺丰快递平台中的API接口对接时&#xff0c;需要将传输的JSON字符串进行数字签名加密。数字签名具体使用的是MD5方式&#xff0c;格式如下&#xff1a;msgData&#xff08;业务报文&a…

DOM编程

DOM编程 DOM树&#xff1a; 获取DOM对象的方式&#xff1a; 通过id直接获取 id禁止使用&#xff0c;因为项目都是css、html、js分离的 2、通过API&#xff0c;doucument.getElementById 3、通过class&#xff0c;doucument.getElementsByClassName 4、通过标签名称&#xff0…

【框架篇】Bean作用域和生命周期

Bean作用域和生命周期 一&#xff0c;Bean作用域 Bean作用域指的是在Spring框架中&#xff0c;定义了Bean实例的创建和销毁方式&#xff0c;以及可以访问该实例的范围&#xff0c;并决定了每次通过容器获取Bean时返回的是同一个实例还是不同的实例。 1.1&#xff0c;Bean作用…

mysql 2 -- 数据库基本操作、数据表的操作、mysql查询操作

一、数据库基本操作 1、数据库的登录及退出 连接数据库&#xff1a; mysql -u用户名 -h主机地址(省略代表本机) -p 密码&#xff08;格式为123...&#xff09;;注&#xff1a; 刚下载安装的时候需要通过管理员进入 退出数据库,以下三种方式都可以&#xff1a; exit quit …

Spring:表达式语言

Spring EL 概述使用概述 Spring 表达式(Spring EL) 是一种功能强大的表达式语言,以 #{ 表达式 } 作为定界符,用于在运行时对对象进行访问和操作。通过使用 Spring 表达式达到简化开发、减少逻辑或配置的编写的目的。 使用 Spring EL 主要可以引用 bean ,调用其属性和方…

苹果手机备忘录如何导入新手机?手机备忘录怎么转移?

一般来说&#xff0c;大多数手机用户更换手机的频率是3—5年&#xff0c;在一部手机使用了几年之后&#xff0c;就会出现内存不足、系统卡顿、电池续航时间较短等问题&#xff0c;这时候就需要更换新的手机了。有不少苹果手机用户在更换新手机的时候&#xff0c;都很发愁一个问…

Hugging News #0717: 开源大模型榜单更新、音频 Transformers 课程完成发布!

每一周&#xff0c;我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新&#xff0c;包括我们的产品和平台更新、社区活动、学习资源和内容更新、开源库和模型更新等&#xff0c;我们将其称之为「Hugging News」。本期 Hugging News 有哪些有趣的消息&#xff0…

TypeScript 学习笔记(六):索引签名类型、映射类型

一、索引签名类型 1. 索引类型查询操作符 keyof keyof可以用于获取某种类型的所有键&#xff0c;其返回类型是联合类型。 interface Info {name: string;age: number; } let infoProp: keyof Info; infoProp "name"; infoProp "age"; infoProp "…

知识普及:Boc-Hynic,133081-25-1,6-叔丁氧羰肼基-3-吡啶甲酸,

&#xff08;文章资料汇总来源于&#xff1a;陕西新研博美生物科技有限公司小编MISSwu&#xff09;​ Boc-Hynic&#xff0c;6-[2-(tert-Butoxycarbonyl)hydrazinyl]nicotinic acid&#xff0c;6-[2-(叔丁氧羰基)肼基]烟酸&#xff0c;6-叔丁氧羰肼基-3-吡啶甲酸 ------------…

追梦之旅【数据结构篇】——C语言手撕八大经典排序

追梦之旅【数据结构篇】——C语言手撕八大经典排序&#x1f60e; 前言&#x1f64c;排序的认识排序的稳定性&#xff1a;排序的时间复杂度和空间复杂度以及如何选择适合的排序&#xff1a; 优化版选择排序冒泡排序普通版冒泡排序升级版冒泡排序 直接插入排序希尔排序堆排序快速…

ChatGPT变现五个思路

一、前言 ChatGPT是一款AI聊天机器人&#xff0c;发布于2022年11月。凭借着在广泛的知识领域为消费者问题做出清晰、详尽解答的出色能力&#xff0c;其一经推出就引发全球轰动&#xff0c;自然也得到零售行业的高度关注。例如&#xff0c;消费者只要询问ChatGPT如何布置一个梦…

将Spring Boot项目打包部署到阿里云linux服务器

首先 你要保证自己的服务器上有java环境 如果没有可以参考我的文章 linux服务器中安装java JDK1.8版本 然后 我们打开我们的Spring Boot项目 双击 package 生命周期进行打包 打包完成之后 我们找到 target 下面会有一个jar包 然后 我们右键它 如下图操作 系统就会帮你打开它所…

javascript 导出表格的excel

一个php网站的表格,需要增加导出excel的功能, 因对web开发不甚了解,开始想着用php导出, 搜索一番发现比较复杂,而且我的表格里已经有数据了, 如果导出又要去库中获取一次,不是负担加倍, 可否把现有表格数据,直接导出来? 答案是肯定的,用js在前端导出 开源js组件…