内部类
基本介绍
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类 (inner class),嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员思考:类的五大成员是哪些?【属性、方法、构造器、代码块、内部类】,内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系,注意:内部类是学习的难点,同时也是重点,后面看底层源码时,有大量的内部类.
package day04.innerClass;
public class InnerClass { // 外部其他类
public static void main(String[] args) {
}
}
class Outer{
private int n = 10; // 1.属性
public void hi(){ // 2.方法
System.out.println("hi...");
}
public Outer(int n) { // 3.构造器
this.n = n;
}
{ // 4.代码块
System.out.println("代码块...");
}
class Inner{ // 5.内部类
}
}
基本语法
class Outer{ // 外部类
class inner{ // 内部类
}
}
class Other{ // 外部其他类
}
内部类的分类【4个】
定义在外部类局部位置上(比如方法内):
- 局部内部类(有类名)
- 匿名内部类(没有类名,重点!!!!!!)
定义在外部类的成员位置上:
- 成员内部类(没用
static
修饰) - 静态内部类(使用
static
修饰)
局部内部类
- 可以直接访问外部类的所有成员,包含私有的
- 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用
final
修饰,因为局部变量也可以使用final
- 作用域:仅仅在定义它的方法或代码块中。
- 局部内部类 — 访问 — 外部类的成员【访问方式:直接访问】
- 外部类 — 访问 — 局部内部类的成员【访问方式:创建对象,再访问(注意:必须在作用域内)】
- 外部其他类 — 不能访问 — 局部内部类(因为局部内部类地位是一个局部变量)
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(
外部类名.this.成员
)去访问【演示】
注意:
- 局部内部类本质还是一个类,地位相当于方法的一个局部变量
- 定义在外部类的方法或代码块中
- 作用域:仅仅在定义它的方法或代码块中
package day04.innerClass;
public class InnerClass {
public static void main(String[] args) {
// 演示
Outer outer = new Outer();
outer.m2();
}
}
class Outer{ // 外部类
private int n = 10; // 外部类的属性
private void m1(){ // 外部类的方法
System.out.println("Outer m1()");
}
public void m2(){ // 外部类的方法
// 【局部内部类本质还是一个类,可以有自己的属性、方法、构造器、代码块、内部类】
// 1.局部内部类,定义在外部类的方法中
// 2.不能添加访问修饰符,但是可以添加 final
// 3.作用域:仅限于定义它的方法或代码块中
final class Inner{
public void m3(){
int a = 10; // 内部类的局部变量
// 4.局部内部类可以直接访问外部类的所有成员,包括私有成员
System.out.println("n = " + n);
m1();
}
}
// 5.在定义内部类的方法中,可以创建内部类对象,并调用内部类的方法
Inner inner = new Inner();
inner.m3();
}
}
输出结果:
package day04.innerClass;
public class InnerClass {
public static void main(String[] args) {
// 演示
Outer outer = new Outer();
outer.m2();
}
}
class Outer{ // 外部类
private int n = 10; // 外部类的属性
private void m1(){ // 外部类的方法
System.out.println("Outer m1()");
}
public void m2(){ // 外部类的方法
// 【局部内部类本质还是一个类,可以有自己的属性、方法、构造器、代码块、内部类】
// 1.局部内部类,定义在外部类的方法中
// 2.不能添加访问修饰符,但是可以添加 final
// 3.作用域:仅限于定义它的方法或代码块中
final class Inner{
private int n = 100;
public void m3(){
int a = 10; // 内部类的局部变量
// 4.局部内部类可以直接访问外部类的所有成员,包括私有成员
// 7.如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
// Outer.this 本质就是外部类的对象,即哪个对象调用了 m2(),Outer.this 就是哪个对象
System.out.println("n = " + n + " 外部类的 n = " + Outer.this.n);
m1();
}
}
// 5.在定义内部类的方法中,可以创建内部类对象,并调用内部类的方法
Inner inner = new Inner();
inner.m3();
}
}
输出结果:
匿名内部类
匿名内部类的使用
- 本质是类
- 同时还是一个对象
- 该类没有名字
- 内部类
- 匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名。
基本语法:
new 类或接口 (参数列表){
类体
};
基于接口的内部类案例:
package day04.AnonymousInnerClass;
public class AnnoymousInner {
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
class Outer{
private int n = 10;
public void method(){
// 1.需求:使用 IA 接口,并创建对象
// 2.传统方法:写一个类,实现该接口,并创建对象
// Tiger tiger = new Tiger();
// tiger.cry();
//
// Dog dog = new Dog();
// dog.cry();
// 3.需求更新:Tiger / Dog 类只使用一次,后面不再使用(即:Outer$1 不再能 new 对象,但是已经创建好的对象仍可以使用)
// 4.可以使用匿名内部类来简化开发
// 5.tiger 的编译类型?IA
// 6.tiger 的运行类型?就是匿名内部类 ===> Outer$1
/*
我们看底层,底层代码如下:会分配类名 Outer$1
public XXXX implements IA{ // 此处的 XXXX ===> Outer$1
@Override
public void cry() {
System.out.println("老虎叫唤...");
}
}
*/
// 7.以下代码解读:JDK 底层在创建匿名内部类 Outer$1 时, 立马就创建了 Outer$1 的实例,并把地址返回给 tiger
// 8.匿名内部类使用一次就不能在使用了,即 Outer$1 会消失,但是创建好实例对象依然可以使用。
IA tiger = new IA(){
@Override
public void cry() {
System.out.println("老虎叫唤...");
}
};
// 查看 tiger 的运行类型
System.out.println("tiger 的运行类型:" + tiger.getClass());
// 输出结果:tiger 的运行类型:class day04.AnonymousInnerClass.Outer$1
tiger.cry();
IA dog = new IA(){
@Override
public void cry() {
System.out.println("小狗汪汪...");
}
};
// 查看 dog 运行类型
System.out.println("dog 的运行类型:" + dog.getClass());
// 输出结果:dog 的运行类型:class day04.AnonymousInnerClass.Outer$2
dog.cry();
// 附:
// 可以总结一点:如果一个对象的运行类型是 xxxx$x,则一定是使用了匿名内部类
}
}
interface IA{
public void cry();
}
// 传统方法
//class Tiger implements IA{
// @Override
// public void cry() {
// System.out.println("老虎叫唤...");
// }
//}
//class Dog implements IA{
// @Override
// public void cry() {
// System.out.println("小狗汪汪...");
// }
//}
基于类的内部类案例
package day04.AnonymousInnerClass;
public class AnnoymousInner {
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
class Outer{
private int n = 10;
public void method(){
// 基于类的匿名内部类
// 分析
// 1. father 编译类型:Father
// 2. father 运行类型:Outer$$3
// 3. 底层会创建匿名内部类
/*
public Outer$3 extends Father{
@Override
public void test() {
System.out.println("匿名内部类重写了 test() 方法");
}
}
*/
// 4. JDK 底层创建 Outer$3 匿名内部类时,立马就创建了 Outer$3 的实例,并返回给 father
// 5. ("Gin") 参数列表会传递给构造器
Father father = new Father("Gin"){
@Override
public void test() {
System.out.println("匿名内部类重写了 test() 方法");
}
};
// 查看 father 的运行类型
System.out.println("father 的运行类型:" + father.getClass());
// 输出结果:father 的运行类型:class day04.AnonymousInnerClass.Outer$3
father.test();
// 基于抽象类的匿名内部类
Animal animal = new Animal(){
@Override
void eat() {
System.out.println("小狗吃骨头...");
}
};
animal.eat();
}
}
class Father{ // 类
public Father(String name) { // 构造器
System.out.println("接收到参数 " + name);
}
public void test(){ // 方法
}
}
abstract class Animal{
abstract void eat();
}
匿名内部类的细节
- 匿名内部类的语法比较奇特,请大家注意,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法。
- 可以直接访问外部类的所有成员,包含私有的
- 不能添加访问修饰符,因为它的地位就是一个局部变量。
- 作用域:仅仅在定义它的方法或代码块中。
- 匿名内部类 — 访问 — 外部类成员【访问方式:直接访问】
- 外部其他类 — 不能访问 — 匿名内部类(因为匿名内部类地位是个局部变量)
- 如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(
外部类名.this.成员
)去访问
package day04.AnonymousInnerClass;
public class AnonymousInner2 {
public static void main(String[] args) {
Outer1 outer1 = new Outer1();
outer1.m();
// Outer1.this 就是调用 m() 的对象
System.out.println("main Outer1 hashcode = " + outer1);
}
}
class Outer1{
private int n = 10;
public void m(){
// 1. 创建一个基于类的匿名内部类,将对象赋值给 p
Person p = new Person(){
private int n = 20;
@Override
public void hi() {
// 可以直接访问外部类的所有成员,包括私有成员
// 如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则
// 如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
System.out.println("匿名内部类重写了 hi() 方法 n = " + n);
System.out.println("访问外部类的重名成员变量 Outer1.n = " + Outer1.this.n);
// Outer1.this 就是调用 m() 的对象
System.out.println("Outer1.this hashCode = " + Outer1.this);
}
};
System.out.println("p 的运行类型是:" + p.getClass());
// 动态绑定,运行类型是 Outer1$1
p.hi();
// 也可以直接调用,匿名内部类本身返回的就是对象
new Person(){
@Override
public void ok(String name) {
super.ok(name);
}
}.ok("Gin");
}
}
class Person{
public void hi(){
System.out.println("Person hi()");
}
public void ok(String name){
System.out.println("Person ok() " + name);
}
}
输出结果:
匿名内部类的应用
由于匿名内部类本身也是一个对象,所以当作为实参直接传递时,简洁高效
案例一:
package day04.AnonymousInnerClass;
public class AnonymousInner3 {
public static void main(String[] args) {
// 使用匿名内部类创建对象,作为实参传递,简洁高效
m(new IL() {
@Override
public void show() {
System.out.println("匿名内部类:这是一幅名画...");
}
});
// 传统方法,new 一个 Mo 实例,作为参数传递
m(new Picture());
}
public static void m(IL il){
il.show();
}
}
interface IL{
void show();
}
// 传统方法:创建一个 类 实现 IL 接口 ===> 编程领域称这种风格为 “硬编码”
class Picture implements IL{
@Override
public void show() {
System.out.println("传统方法:这是一幅名画...");
}
}
案例二:
- 有一个铃声接口
Bell
,里面有个ring()
方法。 - 有一个手机类
Cellphone
,具有闹钟功能alarmclock()
,参数是Bell
类型 - 测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:
懒猪起床了...
- 再传入另一个匿名内部类(对象),打印:
小伙伴们上课了...
package day04.AnonymousInnerClass;
public class AnonymousInner {
public static void main(String[] args) {
CellPhone cellPhone = new CellPhone();
cellPhone.alarmClock(new Bell() {
@Override
public void ring() {
System.out.println("懒猪起床了...");
}
});
cellPhone.alarmClock(new Bell() {
@Override
public void ring() {
System.out.println("小伙伴们上课了...");
}
});
}
}
interface Bell{
void ring();
}
class CellPhone{
public void alarmClock(Bell bell){
bell.ring();
}
}
成员内部类
- 成员内部类是定义在外部类的成员位置,并且没有
static
修饰。 - 可以直接访问外部类的所有成员,包含私有的
- 可以添加任意访问修饰符(
public、protected、默认、private
),因为它的地位就是一个成员。 - 作用域:和外部类的其他成员一样,作用域是整个类体。比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法
- 成员内部类 — 访问 — 外部类(比如:属性)【访问方式:直接访问】
- 外部类 — 访问 — 内部类(说明)【访问方式:创建对象】,再访问
package day04.AnonymousInnerClass;
public class AnonymousInner5 {
public static void main(String[] args) {
Outer2 outer2 = new Outer2();
outer2.m();
}
}
class Outer2{
private int n = 10;
public String name = "Gin";
private void ok(){
System.out.println("外部类私有方法 ok()");
}
// 1. 成员内部类是定义在外部类的成员位置上
// 2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
public class Inner{ // 成员内部类
private double rate = 99.4;
public void say(){
// 3. 可以直接访问外部类的所有成员,包括私有的
System.out.println("n = " + n + ", name = " + name);
ok();
}
private void hello(){
System.out.println("内部类的私有方法:hello()");
}
}
// 4. 在外部类中定义方法来调用内部类
public void m(){
// 5. 创建内部类对象后,就可以使用内部类的所有成员,包括私有的
Inner inner = new Inner();
inner.say();
// 6. 可以访问内部类的私有成员
System.out.println("内部类的私有属性:" + inner.rate);
inner.hello();
}
}
- 外部其他类 — 访问 — 成员内部类【2 种方式】
- 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(
外部类名.this.成员
)去访问。
package day04.AnonymousInnerClass;
public class AnonymousInner5 {
public static void main(String[] args) {
Outer2 outer2 = new Outer2();
outer2.m();
System.out.println("===============");
// 外部其他类使用成员内部类的 2 中方式
// 第 1 种方式:相当于把 new Inner() 当作是 outer2 的成员
Outer2.Inner inner = outer2.new Inner();
inner.say();
System.out.println("===============");
// 第 2 种方式:
Outer2.Inner innerInstance = outer2.getInnerInstance();
inner.say();
}
}
class Outer2{
private int n = 10;
public String name = "Gin";
private void ok(){
System.out.println("外部类私有方法 ok()");
}
// 1. 成员内部类是定义在外部类的成员位置上
// 2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
public class Inner{ // 成员内部类
private double rate = 99.4;
private int n = 30;
public void say(){
// 3. 可以直接访问外部类的所有成员,包括私有的
System.out.println("内部类的 n = " + n + ", name = " + name);
ok();
System.out.println("外部类的 n = " + Outer2.this.n);
}
private void hello(){
System.out.println("内部类的私有方法:hello()");
}
}
// 4. 在外部类中定义方法来调用内部类
public void m(){
// 5. 创建内部类对象后,就可以使用内部类的所有成员,包括私有的
Inner inner = new Inner();
inner.say();
// 6. 可以访问内部类的私有成员
System.out.println("内部类的私有属性:" + inner.rate);
inner.hello();
}
// 创建 Inner内部类 对象的方法
public Inner getInnerInstance(){
return new Inner();
}
}
静态内部类
静态内部类的使用
静态内部类可以有静态成员,而非静态内部类则不能有静态成员
静态内部类是定义在外部类的成员位置,并且有static
修饰
- 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
- 可以添加任意访问修饰符(
public、protected、默认、private
),因为它的地位就是一个成员。 - 作用域:同其他的成员,为整个类体。
package day04.AnonymousInnerClass;
public class AnonymousInner6 {
public static void main(String[] args) {
Outer3 outer3 = new Outer3();
outer3.m();
}
}
class Outer3{
private int n = 10;
private static String name = "Gin";
// 静态内部类
// 1. static 修饰
// 2. 位置在外部类的成员位置
// 3. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
// 4. 作用域:同其他的成员,为整个类体。
public static class Inner{
public void say(){
// 5. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
System.out.println("name = " + name);
}
}
public void m(){
Inner inner = new Inner();
inner.say();
}
}
- 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
- 外部类访问内部类,先创建对象,再访问
- 外部其他类访问静态内部类:
new 外部类名.静态内部类名()
,前提,要满足静态内部类的访问权限
package day04.AnonymousInnerClass;
public class AnonymousInner6 {
public static void main(String[] args) {
Outer3 outer3 = new Outer3();
outer3.m();
System.out.println("========================");
// 外部其他类访问静态内部类:
// 方式1. new 外部类名.静态内部类名(),前提,要满足静态内部类的访问权限
Outer3.Inner inner = new Outer3.Inner();
inner.say();
System.out.println("========================");
// 方式2. 编写一个方法,可以返回静态内部类的对象实例
// 调用非静态方法
Outer3.Inner innerInstance = outer3.getInnerInstance();
innerInstance.say();
System.out.println("========================");
// 调用静态方法
Outer3.Inner instance_ = Outer3.getInstance_();
instance_.say();
}
}
class Outer3{
private int n = 10;
private static String name = "Gin";
// 静态内部类
// 1. static 修饰
// 2. 位置在外部类的成员位置
// 3. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
// 4. 作用域:同其他的成员,为整个类体。
public static class Inner{
public void say(){
// 5. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
System.out.println("name = " + name);
}
}
// 6. 外部类访问内部类,先创建对象,再访问
public void m(){
Inner inner = new Inner();
inner.say();
}
// 获取内部类对象的非静态方法
public Inner getInnerInstance(){
return new Inner();
}
// 获取内部类对象的静态方法
public static Inner getInstance_(){
return new Inner();
}
}
- 如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(
外部类名.成员
)去访问
package day04.AnonymousInnerClass;
public class AnonymousInner6 {
public static void main(String[] args) {
Outer3 outer3 = new Outer3();
outer3.m();
System.out.println("========================");
// 外部其他类访问静态内部类:
// 方式1. new 外部类名.静态内部类名(),前提,要满足静态内部类的访问权限
Outer3.Inner inner = new Outer3.Inner();
inner.say();
System.out.println("========================");
// 方式2. 编写一个方法,可以返回静态内部类的对象实例
// 调用非静态方法
Outer3.Inner innerInstance = outer3.getInnerInstance();
innerInstance.say();
System.out.println("========================");
// 调用静态方法
Outer3.Inner instance_ = Outer3.getInstance_();
instance_.say();
}
}
class Outer3{
private int n = 10;
private static String name = "Gin";
// 静态内部类
// 1. static 修饰
// 2. 位置在外部类的成员位置
// 3. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
// 4. 作用域:同其他的成员,为整个类体。
public static class Inner{
// 如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近原则.
// 如果想访问外部类的成员,则可以使用(外部类名.成员)去访问
private String name = "Vodka";
public void say(){
// 5. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
System.out.println("name = " + name);
System.out.println("外部类的 name = " + Outer3.name);
}
}
// 6. 外部类访问内部类,先创建对象,再访问
public void m(){
Inner inner = new Inner();
inner.say();
}
// 获取内部类对象的非静态方法
public Inner getInnerInstance(){
return new Inner();
}
// 获取内部类对象的静态方法
public static Inner getInstance_(){
return new Inner();
}
}
练习
判断输出结果
package day04.AnonymousInnerClass;
public class AnonymousInner7 {
public static void main(String[] args) {
Test test = new Test();
Test.Inner inner = test.new Inner();
System.out.println(inner.n);
}
}
class Test{
public Test() {
Inner inner1 = new Inner();
inner1.n = 10;
Inner inner2 = new Inner();
System.out.println(inner2.n);
}
class Inner{
public int n = 5;
}
}