文章目录
- 一、什么是内部类?
- 二、内部类的分类
- 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-static
和static
),一样,都是为外部类/外部类实例服务的!!
根据内部类所在的具体位置不同,我们可以将内部类进行分类,分成如下四种。
💡注意!:内部类虽然在编写代码时是在外部类内部的,但是编译之后实际上是编写在外部类外面的!所以,经过编译之后,内部类会形成单独的字节码文件!
二、内部类的分类
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));
}
}
-
内部类和外部类在编译后会生成两个类:
Outer
和Outer$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
中,两个方法ListIterator
和descendingIterator
的返回值都是接口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);
}
-
内部类和外部类在编译后会生成两个类:
Outer
和Outer$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:特性
- 定义在方法体内部
- 不能被
public
、static
修饰 - 只能在方法体内部创建和使用(为方法体服务)
- 方法内部类也会有自己独立的字节码文件
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);
}
- 内部类和外部类在编译后会生成两个类:
Outer
和Outer$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语言核心编程
-
算法