内部类介绍
Java中的内部类是一个定义在另一个类内部的类。
内部类可以访问外部类的所有成员(包括私有成员),而外部类则不能直接访问内部类的私有成员(除非通过内部类的公共接口)。
内部类是类的第五大成员。(类的五大成员:属性、方法、构造器、代码块、内部类)
声明内部类
class 外部类类名{//外部类
class 内部类类名{//内部类
}
}
class 外部其他类类名{//外部其他类
}
class Outer {
public int num = 10;//变量
public void show() {//方法
System.out.println("方法");}
public Outer(int num) {//构造器
this.num = num;}
{//代码块
System.out.println("代码块");
}
class Inter {//内部类
}
}
class Other {//外部其他类
}
内部类的分类
定义在外部类局部位置上
一、局部内部类(有类名)
1、局部内部类就是定义在外部类的局部位置上,比如方法内,并且有类名。
class Outer {//外部类
public void show() {//方法
class Inter {//局部内部类
} } }
2、局部内部类可以访问外部类的所有成员,包括私有成员。
class Outer {//外部类
private int num1 = 10;//私有变量
private void method1() {}//私有方法
public void show() {//方法
class Inter {//局部内部类
public void method2(){
System.out.println(num1);
//调用外部类私有变量
method1();//调用外部类私有方法
} } } }
3、不能添加访问权限修饰符,但可使用final修饰。因为局部内部类的地位是一个局部变量,局部变量是不能使用修饰符的,局部内部类仍可被继承,但被final修饰后便不可再被继承。
4、局部内部类的作用域仅仅在定义他的方法或者代码块中。
5、不同于局部内部类可直接访问外部类的所有成员,外部类访问局部内部类需在类内创建内部类的实例后再访问内部类的方法或字段。
class Outer {//外部类
private int num1 = 10;//私有变量
private void method1() {//私有方法
}
public void show() {//方法
class Inter {//局部内部类
String message = "内部类的字段";
public void method2() {
int num2 = num1;//调用外部类私有变量
method1();//调用外部类私有方法
}
}
Inter A = new Inter();//实例化内部类
A.method2();//调用局部内部类的方法
System.out.println(A.message);//调用局部内部类的字段
//错误,外部类不能调用局部内部类的变量
System.out.println(num2);
}
}
6、外部其他类不能访问局部内部类。(因为局部内部类的地位是一个局部变量)
class Outer {//外部类
public void show() {//方法
class Inter {//局部内部类
}
}
}
class Other1 extends Outer {//外部其他类
Inter A = new Inter();//错误
}
class Other2 extends Outer {//外部其他类
Inter A = new Inter();//错误
}
7、如果外部类和局部内部类的成员重名时,默认遵守就近原则。 如需在局部内部类中访问外部类成员,可使用 外部类名.this.成员 来访问。
public class go {//主方法
public static void main(String[] args) {
Outer myclass = new Outer();
myclass.show();
}}
class Outer {//外部类
private int num1 = 10;//私有变量
public void show() {//方法
class Inter {//局部内部类
int num1=20;
}
Inter A = new Inter();//实例化局部内部类
System.out.println(A.num1);//调用内部类的num1
System.out.println(Outer.this.num1);//调用外部类的num1
}
}
输出结果:
20
10
外部类名.this.成员 本质就是外部类的对象,即谁调用了局部内部类所在的方法,外部类名.this 就是哪个对象。本例中因为局部内部类所在的方法是show方法,所以主方法中调用show方法的对象myclass就是Outer.this。
二、匿名内部类(无类名)重点
匿名内部类就是定义在外部类的局部位置上,比如方法内,并且无需名字,系统会默认给他分配类名,通常用于定义只需要使用一次的类。匿名内部类通常与接口或抽象类一起使用。匿名内部类同时还是个对象。而匿名内部类又分为基于接口的和基于类的两种,我们依次来看:
基于接口的匿名内部类
由于难以理解,我们使用一个示例来引入匿名内部类:有一个接口API,又有多个类需要实现该接口的功能,同时这些实现接口的类,我们仅需调用一次。我们先按照之前所学的知识来实现:
public class go {//主方法
public static void main(String[] args) {
Outer myclass = new Outer();
myclass.method1();
}}
class Outer {//通过该类实例化所需的类
public void method1() {
INF a1 = new dog();//实例化dog类
a1.show();
//......实例化N个类
}}
interface INF {//接口
public void show();}
class dog implements INF {//第一个类dog
@Override
public void show() {
System.out.println("小狗在汪汪叫");
}}
//......第N个类
执行结果:
小狗在汪汪叫
.......//其余多个类执行结果
此时随着类的增多,代码会变得非常冗余,我们就可以通过匿名内部类来解决这一问题。
public class go {//主方法
public static void main(String[] args) {
Outer myclass = new Outer();
myclass.method1();
}}
class Outer {
public void method1() {
INF dog=new INF() {//创建匿名内部类
@Override
public void show() {
System.out.println("小狗在汪汪叫");
}};
//定义多个匿名内部类
}}
interface INF {//接口
public void show();
}
执行结果是相同的
我们看这句代码:INF dog=new INF() 根据之前所学的知识"="左边的是编译类型,右边是运行类型,我们可以判断出对象dog的编译类型为接口INF,但运行类型却不能这样判断,其运行类型为匿名内部类。
"匿名内部类”虽然在我们编写代码时不用为其命名,但系统内部会自动为其补充名字,命名规则为 外部类名称 $ 数字,数字依次增长。也就是说系统在我们看不见的地方对代码进行了补充:
class Outer {
public void method1() {
INF dog=new INF() {
@Override
public void show() {
System.out.println("小狗在汪汪叫");
}};
}}
//系统内部代码
class Outer$1 implements INF {
@Override
public void show() {
System.out.println("小狗在汪汪叫");
}
}
也就是说在创建匿名内部类时,系统会立刻创建Outer$1实例,并将其地址返回给对象dog。
因为整个过程都在系统内部进行,无法人为修改,所以每个匿名内部类(如Outer$1)只能使用一次。但对象(如dog)是可以反复使用的。
基于类的匿名内部类
我们先来看这一段代码:
class Outer{//外部类
public void method(){
Farher f1 = new Farher("jack"){};//创建f1实例
Farher f2 = new Farher("jack");//创建f2实例
}
class Farher{//内部类
public Farher(String name) {
}
}
}
主方法中的两行代码都创建了Father实例,且只有一处不同:有无方法体(大括号) ,但实际上却大不相同:f1 是一个匿名内部类的实例,而 f2 只是一个普通的 Father 类实例。两者的编译类型相同,都是Father类,但运行类型不同,分别为 Outer$2 和 Father,Outer$2正是系统内部的命名:class Outer$2 extends Farher。
同样的,系统在内部也会为其补充名字,命名规则与 "基于接口的匿名内部类" 相同:
class Outer{//外部类
public void method(){
Farher f1 = new Farher("jack"){};//创建f1实例
}
class Farher{//内部类
public Farher(String name) {
}}}
//系统内部代码
class Outer04{
public void method(){
class Outer$2 extends Farher {
public Outer$2(String name) {
super(name);
}
}
}
class Farher{
public Farher(String name) {
}
}
}
如果该匿名内部类继承的类中存在某些方法,在匿名内部类中可选择是否重写其方法。同样的,如果继承的类为抽象类,则必须重写该方法。
小结
我们用一篇例子来理清思路吧,并加深对上文的理解:
public class go {//主方法
public static void main(String[] args) {
Outer myclass = new Outer();
myclass.method();
}
}
class Outer {//外部类
public void method() {
Father f1 = new Father(){//创建匿名内部类
@Override
public void show() {//重写show方法
System.out.println("重写了 show 方法");
}
};
f1.show();
}
}
class Father{//外部其他类
public void show(){
System.out.println("父类中的show方法");
}
}
执行结果:
重写了 show 方法
在本例中,f1的编译类型为Father,运行类型为 Outer$1 (class Outer$1 extends Farher) ,且f1方法体中重写了show方法,系统在执行 f1.show(); 语句时,因为在编译类型"Father" 中含有show方法,编译通过,而在执行过程中,因其运行类型Outer$1(隐藏在Father f1 = new Father(){语句中)重写了 show 方法,所以实际执行的是匿名内部类中的 show 方法。
但该写法还可以简化,如果我们在实际需求中,只需调用一次 Outer$1 中的 show 方法,还可以对其简化:
//主方法保持不变
class Outer {
public void method() {
new Father(){//只创建实例,而不创建对象接收该实例
@Override
public void show() {
System.out.println("重写了show方法");
}
}.show();//直接调用show方法
}
}
//外部类保持不变
在该写法中我们只创建了类Outer$1的实例,但不接收,而这种写法仅能调用一个方法一次。
我们还可以在其基础上再加一层:将匿名内部类当作实参直接传递
public class go {//主方法
public static void main(String[] args) {
//调用静态方法f1
f1(new API() {
@Override
public void show() {
System.out.println("重写了show方法");
}
});//直接将创建接口API的实例当作实参
}
//定义静态方法f1,其形参为接口类型
public static void f1(API test1){
test1.show();
}
}
interface API{//接口
void show();
}
其大大减小了代码量和系统执行的负担,缺点则是削弱了代码的复用性,所以在只需执行一次该操作时使用该方法更合适,但如果需执行多次时,仍建议按照
定义接口 > 定义类实现接口 > 定义方法形参为该类 > 调用方法实参为该类
的顺序来编写更合适。
而其其他如内部类和外部类的访问关系、访问权限修饰符、作用域,重名时的访问规则等等,匿名内部类等同于局部内部类,在此不再赘述。
定义在外部类的成员位置上
一、成员内部类(无static)
1、成员内部类就是定义在外部类的成员位置上,并且没有static修饰。
public class go {//主方法
public static void main(String[] args) {
}
}
class Outer {//外部类
class Inter{//成员内部类
}
}
与局部内部类不同的是,成员内部类不在外部类的方法或代码块之中,而是像变量、方法这些成员一样存在,因此得名成员内部类。
2、成员内部类可以使用四大成员修饰符来修饰,因为其不同于局部内部类,他也是一个成员,自然也像其他成员一样可以使用权限修饰符。
3、成员内部类可直接访问外部类的所有成员,外部类可通过创建对象实例,用该对象来调用成员内部类的所有成员。而外部其他类访问成员内部类共两种方式:
(1)第一种:先创建一个外部类的对象,再通过该对象去创建一个内部类的对象
public class go {//主方法
public static void main(String[] args) {
Outer myouter = new Outer();//创建外部类对象实例
Outer.Inter myinter =myouter.new Inter();//创建内部类对象实例
myinter.show();//调用内部类的成员
}
}
class Outer {//外部类
class Inter {//内部类
public void show(){}
}
}
在此方法中,我们可以将 new Inter() 看作外部类实例对象 myouter 的一个成员,Outer.Inter代表 myinter 的类为内部类Inter,myouter. 是指创建新实例是在对象 myouter 中进行的操作,而new Inter() 则是创建Inter类的实例。
(2)第二种:在外部类中编写一个方法,通过该方法返回内部类的实例
public class go {//主方法
public static void main(String[] args) {
Outer myouter = new Outer();//创建外部类对象实例
//创建内部类对象,并通过外部类的方法接收内部类实例
Outer.Inter myInter=myouter.getInter();
myInter.show();//调用内部类的成员
}
}
class Outer {//外部类
class Inter {//内部类
public void show(){
}
}
public Inter getInter(){//该方法返回内部类的实例
return new Inter();//创建内部类的实例并返回
}
}
与方法一不同的是,它将实例化内部类对象这一工作交给了外部类的方法getInter()来做。
4、如果外部类和局部内部类的成员重名时,默认遵守就近原则。 如需在局部内部类中访问外部类成员,可使用 外部类名.this.成员 来访问。
二、静态内部类(有static)
1、静态内部类与成员内部类的最大区别就是多了个static修饰符
public class go {//主方法
public static void main(String[] args) {
}
}
class Outer {//外部类
static class Inter {//静态内部类
}
}
2、静态内部类和成员内部类一样,可以使用四大成员修饰符来修饰。
3、不同与其他成员类,静态内部类的作用域是整个类体
4、因为静态内部类为静态属性,所以其可以直接访问外部的所有静态成员,但不能直接访问非静态成员。外部类可通过创建对象实例,用该对象来调用成员内部类的所有成员。而外部其他类访问成员内部类共两种方式:
(1)第一种:直接通过外部类.内部类来创建静态内部类实例
public class go {//主方法
public static void main(String[] args) {
Outer.Inter myinter = new Outer.Inter();
myinter.show();//调用静态内部类属性
}
}
class Outer {//外部类
static class Inter {//静态内部类
public void show(){
}
}
}
(2)第二种:在外部类中编写一个方法,通过该方法返回内部类的实例
public class go {//主方法
public static void main(String[] args) {
Outer myouter = new Outer();//创建外部类对象实例
//创建内部类对象,并通过外部类的方法接收内部类实例
Outer.Inter myinter =myouter.getInter();
myinter.show();//调用内部类的成员
}
}
class Outer {//外部类
static class Inter {//静态内部类
public void show(){
}
}
public Inter getInter(){//该方法返回内部类的实例
return new Inter();//创建内部类的实例并返回
}
}
也可以通过静态方法来返回静态内部类的实例
public class go {//主方法
public static void main(String[] args) {
Outer.Inter myinter =Outer.getInter();
myinter.show();
}
}
class Outer {//外部类
static class Inter {//静态内部类
public void show(){
}
}
public static Inter getInter(){
return new Inter();
}
}
5、如果外部类和静态内部类的成员重名时,静态内部类访问时遵守就近原则。 如需在静态内部类中访问外部类成员,可使用 外部类名.成员 来访问(无需this)。
总结
1、内部类共四种:局部内部类、匿名内部类、成员内部类、静态内部类
2、匿名内部类最为重要
new 类/接口(参数列表){
};
3、成员内部类和静态内部类放在外部类的成员位置,实质上就是外部类的一个成员