内部类
一个类的内部又完整的嵌套了另一个类结构,被嵌套的类称为内部类,嵌套其他类的类称为外部类。内部类是类的第五大成员。
基本语法
class Outer{//外部类
class Inner {//内部类
}
}
没有嵌套和被嵌套关系的类称为 外部其他类。
内部类的分类
- 定义在外部类的局部位置上(比如某个方法中)
- 局部内部类(有类名)
- 匿名内部类(没有类名)
- 定义在外部类的成员位置上
- 成员内部类(没有static修饰的类)
- 静态内部类(有static修饰的类)
局部内部类
局部内部类定义在外部类的局部位置上,通常在方法中,且有类名
-
可以直接访问外部类的所有成员,包含私有的
-
不能添加访问修饰符,因为它的地位就是一个局部变量。
局部变量不能使用修饰符,但是可以使用final修饰
-
作用域:仅仅在定义它的方法或代码块中
-
局部内部类 访问 外部类的成员:直接访问
-
外部类 访问 局部内部类的成员:创建对象再访问(必须在作用域内)
-
外部其他类 不能访问 局部内部类(因为局部内部类地位和局部变量一样)
-
如果外部类 和局部内部类的成员重名时,遵循就近原则。
- 如果想访问外部类的成员,可以使用
外部类名.this.成员
去访问
- 如果想访问外部类的成员,可以使用
//localInnerClass.java
package com.learnJava.innerClass;
public class localInnerClass {
public static void main(String[] args) {
//创建外部类的对象并调用 m1方法
Outer outer = new Outer();
outer.m1();
/** 调用m1后的运行过程
* 创建 Inner 类 -> 发现内部需要创建 Inner对象 -> 创建对象
* -> 调用 f1()-> 调用 m2()
*/
//用Hashcode 检验 outer 和 Outer.this是否为同一个对象
System.out.println("outer对象 的Hashcode:" + outer);
}
}
class Outer {
private int n1 = 100;//私有属性
private void m2(){
System.out.println("Outer 的 m2()方法");
};//私有方法
public void m1(){//局部内部类通常定义在方法中
final class Inner{//2. 不能添加修饰符但可以用final修饰
private int n1 = 800;//内部类中定义一个和外部类属性同名的属性n1
public void f1(){//1. 可以直接访问外部类的包括私有在内的所有成员
System.out.println("n1=" + n1);//4. 访问外部类方式:直接访问。但是第7点内部类定义了一个重名的属性,此处遵循就近原则变为访问内部类的n1
System.out.println("外部类的n1:" + Outer.this.n1);//外部类的n1改成用 外部类名.this.n1 来访问
/** 解释为什么用Outer.this:
* 1. 存在访问冲突时,需要准确找到属性
* 2. Outer.this 本质上是Outer类的对象,即哪个对象调用了 m1, 哪个对象就是 Outer.this
* 3. 主函数创建了 Outer对象 outer, 并调用了m1, 所以outer就是这里的 Outer.this
*/
m2();
System.out.println("Outer.this 的Hashcode:" + Outer.this);
}
}
/** 如果用final修饰了class Inner,那么就不能在方法中再继承Inner类了
* class Inner02 extends Inner { }
*/
//5. 外部类访问内部类方式:在作用域中,通过创建内部类对象来访问成员
//创建在方法外则编译器找不到内部类在哪
Inner inner = new Inner();
inner.f1();
}
{//3. 作用域为定义它的方法或代码块中
class Inner03 {}
}
}
输出结果
n1=800
外部类的n1:100
Outer 的 m2()方法
Outer.this 的Hashcode:com.learnJava.innerClass.Outer@6d03e736
outer对象 的Hashcode:com.learnJava.innerClass.Outer@6d03e736
匿名内部类
匿名内部类定义在外部类的局部位置中
- 是一个类
- 是一个内部类
- 没有名字
- 是一个对象
基本语法
new 类或接口(参数列表){
类体
};
基本使用
下面给出了基于接口和类的使用方式。基于抽象类时记得实现内部的抽象方法。
//AnonymousInnerClass.java
package com.learnJava.innerClass;
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.method();
}
}
class Outer02 {
private int n1 = 10;
public void method(){//方法
/** 一、基于接口的匿名内部类
* 需求:使用IA接口创建对象
* 1. 传统方式:写一个类实现该接口并创建对象
* 2. 匿名方式: 如果一个类只使用一次,则可以选择用匿名方式
*/
IA tiger1 = new Tiger();//传统方式:创建类再创建对象
tiger1.cry();
//匿名方式:不能直接创建接口对象,但是可以后面跟着实现该接口,而不用单独创建一个类
IA tiger2 = new IA(){//编译类型:IA, 运行类型:匿名内部类
@Override
public void cry() {
System.out.println("匿名-老虎叫");
}
};//记得加分号
tiger2.cry();
System.out.println("tiger2的运行类型:" + tiger2.getClass());//用来查看匿名内部类返回的类型
/**
* 匿名内部类底层实现方式也相当于创建了一个类来实现接口,创建完后把地址返回给tiger2
* 但是对于开发者而言效率更高了
* class Outer02$1 implements IA(){
* @Override
* public void cry() {
* System.out.println("匿名-老虎叫");
* }
* }
*
*/
/*
二、基于类的匿名内部类
*/
Father father = new Father("jack"){//此处 father的编译类型是 Father, 但运行类型不是 Father,而是 匿名内部类
@Override
public void test() {
System.out.println("匿名内部类重写了Father类中的test()");
}
};
System.out.println("father 的运行类型" + father.getClass());
/*
* 类的匿名内部类底层实现:
* class Outer02$2 extends Father(){
* @Override
* public void test() {
* System.out.println("匿名内部类重写了Father类中的test()");
* }
* }
*/
}
}
interface IA{//接口
public void cry();
}
class Tiger implements IA{//传统方式
@Override
public void cry() {
System.out.println("对象-老虎叫");
}
}
class Father{//类
public Father(String name) {//构造器
}
public void test(){//方法
}
}
输出结果
对象-老虎叫
匿名-老虎叫
tiger2的运行类型:class com.learnJava.innerClass.Outer02$1
father 的运行类型class com.learnJava.innerClass.Outer02$2
注意事项
和局部内部类相似处:
- 不能添加访问修饰符,因为它地位等同于局部变量
- 作用域:仅仅在定义它的方法或代码块中
- 匿名内部类 直接访问 包括私有在内 的外部类成员
- 外部其他类 不能访问 匿名内部类
- 如果外部类 和局部内部类的成员重名时,遵循就近原则。
- 如果想访问外部类的成员,可以使用
外部类名.this.成员
去访问
- 如果想访问外部类的成员,可以使用
特点:
匿名内部类既是一个类的定义,同时本身也是一个对象。
//AnonymousInnerClassDetail.java
public class AnonymousInnerClassDetail {
public static void main(String[] args) {
OuterDetail outerDetail = new OuterDetail();
outerDetail.f1();
}
}
class Person{
public void hi(){
System.out.println("Person 的 hi()");
};
}
class OuterDetail{
private int n1 = 99;
public void f1(){
Person p = new Person(){
@Override
public void hi(){
System.out.println("匿名内部类 的 hi()");
}
};
System.out.println(p.getClass());
p.hi();//动态绑定,运行类型为OuterDetail$1
//也可以这样直接调用方法
new Person(){
@Override
public void hi(){
System.out.println("匿名内部类 的 hi()");
}
}.hi();
}
}
实践
-
匿名内部类可以当做实参直接传递。创建的对象只使用一次时,会更方便。(表现在代码上即:函数的括号中直接
new
对象出来) -
起床案例:
- 有一个铃声接口
Bell
,里面有ring
方法 - 有一个手机类
Cellphone
,闹钟功能alarmClock
,参数是Bell类型 - 测试手机类的闹钟功能,通过匿名内部类作为参数,打印:
“起床了"
- 再传入另一个匿名内部类,打印:
“上课了"
- 有一个铃声接口
//AnonymousInnerClassExercise.java
public class AnonymousInnerClassExercise {
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() {//每次调用alarmClock(),参数会进行动态绑定
@Override
public void ring() {
System.out.println("上课了");
}
});
}
}
interface Bell {
void ring();
}
class Cellphone {
public void alarmClock(Bell bell) {
bell.ring();
}
}
成员内部类
成员内部类是定义在外部类的成员位置的,且没有static修饰
- 可以直接访问外部类的所有成员
- 可以添加任意访问修饰符,也即地位和成员相同
- 作用域:整个外部类体中都可使用
- 成员内部类 直接访问 外部类
- 外部类 创建对象再访问 内部类
- 外部其他类 三种访问 内部类
- 如果外部类 和局部内部类的成员重名时,遵循就近原则。
- 如果想访问外部类的成员,可以使用
外部类名.this.成员
去访问
- 如果想访问外部类的成员,可以使用
//MemberInnerClass.java
package com.learnJava.innerClass;
public class MemberInnerClass {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
System.out.println("外部类调用结果:");
outer08.t1();//外部类通过调用方法来使用成员内部类
System.out.println("\n外部其他类调用结果:");
//外部其他类如何创建内部类?(注意这里是外部其他类,不是外部类)
//1. 通过创建对象来new一个
Outer08.Inner08 inner08 = outer08.new Inner08();
inner08.say();
//2. 在外部类编写一个方法,用来返回内部类
Outer08.Inner08 inner08instance = outer08.getInner08Instance();
inner08instance.say();
//3. 将1和2合起来,熟练之后可以用
new Outer08().new Inner08().say();
}
}
class Outer08{
private int n1 = 10;
public String name = "张三";
//以下成员内部类定义在和属性、方法平级的位置上
class Inner08 {
public void say(){
//可以直接访问外部类的所有成员
System.out.println("n1=" + n1 + " name=" + name);
}
}
//写一个方法用来返回Inner08实例,对应主函数中的第2点
public Inner08 getInner08Instance(){
return new Inner08();
}
//写一个方法来使用成员内部类
public void t1(){
Inner08 inner08 = new Inner08();
inner08.say();
}
}
输出结果
外部类调用结果:
n1=10 name=张三
外部其他类调用结果:
n1=10 name=张三
n1=10 name=张三
n1=10 name=张三
静态内部类
静态内部类是定义在外部类的成员位置的,有static修饰
-
可以直接访问外部类的所有静态成员,但不能直接访问非静态成员
-
可以添加任意访问修饰符
-
作用域为整个类
-
访问方式和成员内部类相似,但要加上静态成员相关的限制