Java 四大内部类总结
1. 内部类
1.1 内部类概念
一个类的内部又完整的嵌套了另一个类结构。
被嵌套的类称为内部类(inner class)
嵌套其他类的类称为外部类(outer class)
特点:内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。
1.2 内部类语法
class Outer{ // 外部类
class Inner{ // 内部类
}
}
class Other{ // 其他类
}
1.3 四种内部类
-
定义在外部类的局部位置上(比如方法/代码块内)
-
局部内部类(有类名)
-
匿名内部类(没有类名)🌟
-
-
定义在外部类的成员位置上(本质就是一个成员)
-
成员内部类(没用static修饰)
-
静态内部类(使用static修饰)
-
(1) 局部内部类:定义在外部类的局部位置上(通常在方法/代码块内),且[有]类名
👿(1)局部内部类定义在方法中/代码块 (2) 作用域在方法体或者代码块中 (3)本质仍然是一个类!!!
-
可以直接访问外部类的所有成员,包含私有的
-
不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。
但是可以使用final 修饰,因为局部变量也可以使用final.(final就可以让类不可以被继承) -
作用域:仅仅在定义它的方法或代码块中
-
局部内部类—访问---->外部类的成员【访问方式:直接访问】
外部类—访问---->局部内部类的成员【访问方式:创建对象,再访问(注意:必须在作用域内)】
外部其他类—不能访问----->局部内部类【因为 局部内部类地位是一个局部变量】
-
如果外部类和局部内部类的成员重名时,默认遵循就近原则,
如果想访问外部类的成员,则可以使用(外部类名.this.成员
)去访问 :
Outer02.this
本质就是外部类的对象,于是就相当于用对象.n1
去访问类的属性,就是Outer02.this.n1
public class LocalInnerClass {// public static void main(String[] args) { //演示一遍 Outer02 outer02 = new Outer02(); outer02.m1(); System.out.println("outer02 的 hashcode=" + outer02); // 对象outer02就是Outer02.this } } class Outer02 { //外部类 private int n1 = 100; private void m2() { //私有方法 System.out.println("Outer02 m2()"); } public void m1() { //方法 //1.局部内部类是定义在外部类的局部位置,通常在方法 //3.不能添加访问修饰符,但是可以使用final 修饰 //4.作用域 : 仅仅在定义它的方法或代码块中 final class Inner02 { //局部内部类(本质仍然是一个类) //2.可以直接访问外部类的所有成员,包含私有的 private int n1 = 800; public void f1() { //5. 局部内部类可以直接访问外部类的成员,比如下面可以直接访问:外部类n1 和 m2() //7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则, // 如果想访问外部类的成员,使用 外部类名.this.成员 去访问 // Outer02.this 本质就是外部类的对象, 即哪个对象调用了m1,Outer02.this就是哪个对象, // 于是就相当于对象.n1去访问类的属性,就是 Outer02.this.n1 System.out.println("n1=" + n1 + " 外部类的 n1=" + Outer02.this.n1); System.out.println("Outer02.this hashcode=" + Outer02.this); m2(); } } //6. 外部类访问内部类:外部类在方法中,可以创建Inner02对象,然后调用方法即可 Inner02 inner02 = new Inner02(); inner02.f1(); } } class Outer01{}// 外部其他类 不能访问 局部内部类
(2) 匿名内部类:定义在外部类的局部位置上(通常在方法/代码块内),且[没有]类名
👿(1)定义在方法中 / 代码块;
(2) 作用域在 方法体 / 代码块 中;
(3)本质仍然是一个类;
(4)该类没有名字(但其实在系统底层分配有名字);
(5)同时还是一个对象;
匿名内部类—访问---->外部类的成员【访问方式:直接访问,包含私有的】
如果外部类和匿名内部类的成员重名时,默认遵循就近原则,
如果此时匿名内部类想访问外部类的成员,则可以使用(外部类名.this.成员
)去访问 :
Outer02.this
本质就是外部类的对象,于是就相当于用对象.n1
去访问类的属性,就是 Outer02.this.n1
外部类—访问---->匿名内部类的成员【访问方式:创建对象,再访问(注意:必须在作用域内)】
外部其他类—不能访问----->匿名内部类【因为 匿名内部类地位是一个局部变量】
不能添加访问修饰符,因为其本身相当于一个局部变量
-
基本语法:
因为众所周知 抽象类和接口无法创建对象,所以这里面的new,其实是new的他们的实现类(子类 )对象,类名由系统分配
匿名内部类大括号内其本质上就是一个实现类(子类),new就会立即创建出一个实现类(子类)对象
new 抽象类名/接口名(参数列表){
重写抽象类或者接口中的抽象方法
};
-
基于接口的匿名内部类
package com.hspedu.innerclass; /* 接口匿名内部类的使用 */ public class AnonymousInnerClass { public static void main(String[] args) { Outer outer = new Outer(); outer.method(); } } class Outer{ // 外部类 private int n1 = 10; // 外部类属性 public void method(){ // 外部类方法 // 【基于接口的匿名内部类】 需求:想使用IA接口并创建对象,传统方式就是使用一个类实现该接口并创建对象 // Tiger tiger = new tiger(); // tiger.cry(); // 但是Tiger类只用一次,后面不再使用了,每次想要实现IA接口就定义一个类很繁琐 // 于是可以使用【匿名内部类】简化开发(既不创建Tiger类 又可以让老虎叫) // tiger的编译类型是:IA // tiger的运行类型是:匿名内部类XXX => Outer$1 /* 底层会分配一个类名 XXX => Outer$1 class XXX implements IA{ @Override public void cry() { System.out.println("老虎叫唤aowuaowu"); } } */ // new 出来的就是接口的实现类对象 即:class XXX implements IA // jdk底层创建匿名内部类Outer$1后,立马就创建了Outer$1的对象,把地址返回给tiger(多态:接口类型的变量tiger 可以指向 实现了IA接口类的对象实例) // 匿名内部类创建完对象后,这个匿名内部类就没有了,但对象还在,可以反复使用。 IA tiger = new IA(){ // new了就是一个实现类对象 @Override // 大括号内本质上就是一个实现类 public void cry() { System.out.println("老虎叫唤aowuaowu"); } }; System.out.println("tiger 的运行类型" + tiger.getClass()); // 这里使用getClass获取对象 tiger 的运行类型 Outer$1 tiger.cry(); } } interface IA{ // 接口 public void cry(); } //class Tiger implements IA{ // 繁琐 // @Override // public void cry() { // System.out.println("老虎叫唤aowuaowu"); // } //}
-
基于类的匿名内部类
package com.hspedu.innerclass;
/*
匿名内部类的使用
*/
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
class Outer{ // 外部类
private int n1 = 10; // 外部类属性
public void method(){ // 外部类方法
// 【基于类的匿名内部类】 需求:想使用Father类并创建对象,传统方式就是使用这个类并创建对象↓
// Father father = new Father("爹名");
// 这里打了大括号{},那么{}中就相当于是抽象类的实现类(或者就说子类),new了就是new出来一个实现类(子类)对象,之后用类的引用指向这个实现类(子类)
// father的编译类型是:Father
// father的运行类型是:匿名内部类XXX => Outer$2 按顺序编号
// 底层会创建匿名内部类 class Outer$2 extends Father(){},并且直接返回了匿名内部类Outer$2的对象
// 参数列表默认会传递给类的构造器
Father father = new Father("爹名"){
@Override
public void test() {
System.out.println("匿名内部类重写了test方法,要输出一个" + name);
}
};
System.out.println("father 的运行类型" + father.getClass()); // 这里使用getClass获取对象 father 的运行类型 Outer$2
father.test();
}
}
abstract class Father{ // 类
public String name;
public Father(String name) { // 类的构造器
this.name = name;
}
public abstract void test();// 抽象方法
}
-
一些其他解释
-
匿名内部类—访问---->外部类的成员【访问方式:直接访问,包含私有的】
如果外部类和匿名内部类的成员重名时,默认遵循就近原则,
如果此时匿名内部类想访问外部类的成员,则可以使用(
外部类名.this.成员
)去访问 :
Outer05.this
本质就是外部类的对象,于是就相当于用对象.n1
去访问类的属性,就是Outer05.this.n1
-
外部类—访问---->匿名内部类的成员【访问方式:创建对象,再访问(注意:必须在作用域内)】
-
外部其他类—不能访问----->匿名内部类【因为 匿名内部类地位是一个局部变量】
-
不能添加访问修饰符,因为其本身相当于一个局部变量
public static void main(String[] args) { Outer05 outer05 = new Outer05(); outer05.f1(); System.out.println("main outer05 hashcode=" + outer05); } } class Outer05 { private int n1 = 99; public void f1() { //创建一个基于类的匿名内部类 //不能添加访问修饰符,因为它的地位就是一个局部变量 //作用域 : 仅仅在定义它的方法或代码块中 Person p = new Person(){ private int n1 = 88; @Override public void hi() { //可以直接访问外部类的所有成员,包含私有的 //如果外部类和匿名内部类的成员重名时,匿名内部类访问的话, //默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.this.成员)去访问 System.out.println("匿名内部类重写了 hi 方法 n1=" +n1 + "外部内的n1=" + Outer05.this.n1 ); //和主函数中打印的一样 //Outer05.this 就是调用 f1 的对象,即:谁调用的f1 这个Outer05.this 就是谁————即为主函数中的outer05 System.out.println("Outer05.this hashcode=" + Outer05.this) //>>>>> 会输出:调用 f1 的对象的hashcode } }; p.hi();//调用时候:动态绑定, 运行类型是 Outer05$1 // >>>>> 会输出:匿名内部类重写了 hi 方法 n1=88 外部内的n1=99 } """''''''''''''''' 下面的这模块代码是和上面的代码或者关系存在演示的 //调用时候:也可以直接调用, 匿名内部类本身也是返回对象 new Person(){ @Override public void hi() { System.out.println("匿名内部类重写了 hi 方法,哈哈...");//>输出匿名内部类重写了 hi 方法,哈哈...【不输出Person hi()】 } @Override public void ok(String str) { super.ok(str); } }.ok("jack"); // >>>>> 会输出Person ok()jack """'''''''''''''''''''' class Person { //类 public void hi() { System.out.println("Person hi()"); } public void ok(String str) { System.out.println("Person ok()" + str); } }
-
匿名内部类使用场景
【传统的方法】 public class AnonymousInnerClass01 { public static void main(String[] args) { A a = new A(); // 创建实现类的对象 f1(a); } public static void f1(IL il){ il.show(); } } interface IL{ void show(); } class A implements IL{ //类实现IL接口,硬编码方式 @Override public void show() { System.out.println("showwwww"); } } 【匿名内部类的使用】 public class AnonymousInnerClass01 { public static void main(String[] args) { f1(new IL() { // 大括号里面就相当于是接口的实现类,给f1传入的就是接口的实现类对象 !!!! @Override public void show() { System.out.println("showwwww"); } }); } public static void f1(IL il){ // 需要传入接口的实现类对象 il.show(); } } interface IL{ void show(); }
-
(3) 成员内部类:定义在外部类的成员位置上(属性 方法这些地方),且[没有]static修饰
- 成员内部类—访问---->外部类的成员【访问方式:直接访问,包含私有的】
如果外部类和内部类的成员重名时,默认遵循就近原则,如果此时内部类想访问外部类的成员,则可以使用 (外部类名.this.成员)去访问
- 外部类—访问---->成员内部类的成员【访问方式:创建成员内部类的对象,再访问(注意:必须在作用域内)】
- 外部其他类—访问----->成员内部类【两种方法】【外部类对象.new 内部类名()】
- 可以添加任意的访问修饰符(public protected 默认 private),因为其本身相当于一个成员,其作用域范围也和外部类的其他成员一样
public class MemberInnerClass01 {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.t1();
// 方式一 外部其他类调用成员内部类的方式
Outer08.Inner inner = outer08.new Inner(); // 用外部类的对象new内部类Inner
inner.say();
// 方式二 在外部类中写一个编写一个方法可以返回一个Inner08 的对象实例
Outer08.Inner innerinstance = outer08.getInnerInstance();
innerinstance.say();
}
}
class Outer08{ // 外部类
private int n1 = 10;
public String name = "张三";
public class Inner{ // 成员内部类
private int n1 = 66;
public void say(){
// 成员内部类可以访问外部类的所有成员,包括私有的
System.out.println("Outer08的name=" + name);
System.out.println("内部类Inner 的n1=" + n1);
System.out.println("外部类Outer08 的n1=" + Outer08.this.n1); // 重名的时候,使用外部类名.this访问外部类属性
}
}
public Inner getInnerInstance(){ // 方法二 ,返回一个Inner 对象
return new Inner();
}
// 再写一个方法 外部类访问成员内部类 创建对象访问
public void t1(){
Inner inner = new Inner();
inner.say();
}
}
(4) 静态内部类:定义在外部类的成员位置上(通常在方法/代码块),且[有]static修饰
- 静态内部类—访问---->外部类的成员【访问方式:直接访问,包含私有的】【🔴但是只能访问静态成员!!!!】
如果外部类和内部类成员重名时,默认遵循就近,如果此时内部类想访问外部类成员,可以使用 (外部类名.成员)去访问 【因为成员是静态的,类名调用,就不用加this了】
- 外部类—访问---->静态内部类的成员【访问方式:创建内部类的对象,再访问(注意:必须在作用域内)】
- 外部其他类—访问----->成员内部类【因为静态内部类是 可以通过类名直接访问(前提满足访问权限),所以new一个Outer08.Inner()对象,在调用方法】
- 可以添加任意的访问修饰符(public protected 默认 private),因为其本身相当于一个成员,其作用域范围也和外部类的其他成员一样。
public class MemberInnerClass01 {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.m1();
// 外部其他类使用静态内部类 因为静态内部类是 可以通过类名直接访问(前提满足访问权限),所以new一个Outer08.Inner()对象,在调用方法
new Outer08.Inner().say();
}
}
class Outer08{ // 外部类
private int n1 = 10;
public String name = "张三";
private static int n2 = 99;
public static class Inner{ // 静态内部类 有static修饰
private int n2 = 1;
public void say(){
System.out.println("外部类的静态属性n2=" + Outer08.n2); // 重名访问外部类的属性 直接通过外部类名.成员访问
System.out.println("静态内部类的属性n2 = " +n2); // 因为外部类的n1和name都是非静态的,所以不能访问
}
}
public void m1(){ // 外部类访问静态内部类的成员:创建对象在访问
new Inner().say();
}
}