1 什么是内部类
内部类是指定义在一个类或者方法中的类。
2 为什么使用内部类
- 将一些属性、方法封装成一个类,代码更加容易维护
- 把内部类隐藏在外部类中,不允许其他类直接访问这个内部类,增强封装性
3 外部类、内部类的修饰符
- 外部类的修饰符:
public
或默认修饰符 - 内部类的修饰符:
private
、protected
、public
及默认修饰符
能够修饰外部类的只有public
和default
,protected
不可以修饰外部类(public
和default
已经可以满足包外访问和包内访问了)。
对于成员变量和方法:
4 内部类的分类
- 实例内部类:直接定义在类当中的一个类(没有
static
) - 静态内部类:在内部类前面加上一个
static
- 局部内部类:定义在方法中类
- 匿名内部类:属于局部内部类的一种特殊情况
4.1 实例内部类
不使用static
修饰的内部类,与变量、方法类似,是属于实例的,不属于类,因此叫做实例内部类:
public class Outter {
public Outter() {
}
class Inner {
public Inner() {
}
}
}
成员内部类是依附外部类而存在的,也就是说,如果要创建实例内部类的对象,前提是必须存在一个外部类的对象。 创建成员内部类对象的方式如下:
public class Outter {
public Outter() {
}
class Inner {
public Inner() {
}
}
}
class Test {
public static void main(String[] args) {
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner();
}
}
在实例内部类中存在着对外部类的引用。意思就是说在实例内部类的堆内存中不仅存放着自己的成员变量、方法和this
地址,还有外部类的地址。因此,内部类可以访问外部类中的成员变量和方法:
public class Outter {
private double radius = 0;
public static int count = 1;
public static final double PIE = 3.14;
public Outter() {
}
private void method() {
}
class Inner {
public Inner() {
System.out.println(radius);
System.out.println(count);
System.out.println(PIE);
method();
}
}
}
实例内部类的堆内存:
但是,外部类不能直接访问内部类当中的成员变量或方法:
如果想在外部类访问成员内部类的成员,必须先创建一个实例内部类的对象,再通过这个对象的来访问:
public class Outter {
public Outter() {
System.out.println(new Inner().a);
}
class Inner {
public int a = 3;
public Inner() {
}
}
}
需要注意的是,当实例内部类有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是实例内部类的成员变量或方法。 如果要访问外部类的同名成员, 需要以下面的形式进行访问:
// 外部类.this.成员变量
// 外部类.this.成员方法
public class Outter {
private double radius = 0;
public Outter() {
}
public void method() {
}
class Inner {
private double radius = 0;
public Inner() {
System.out.println(radius);
System.out.println(Outter.this.radius);
method();
Outter.this.method();
}
public void method() {
}
}
}
要把实例内部类看作对象,而不是类,因此内部类中的成员变量和方法不能使用static
修饰,因为static
是属于类的字段和方法,而实例内部类是对象。 如果强行写上,编译器会报错:
4.2 静态内部类
静态内部类就是在实例内部类前面加上static
。在静态内部类的堆内存总没有存放外部类的地址,也就是说,在静态内部类当中, 没有外部类引用:
静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似。在没有外部类的对象的情况下也可以创建静态内部类对象:
public class Outter {
public Outter() {
}
static class Inner {
public Inner() {
}
}
}
class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
静态内部类只能使用外部类的static
成员变量或者方法,因为在没有外部类的对象的情况下,也可以创建静态内部类的对象,如果允许访问外部类的非static
成员就会产生矛盾,外部类的非static
成员必须依附于具体的对象。如果想要使用外部类的非静态成员变量可以通过创建外部类对象来调用:
静态内部类当中不仅可以定义静态成员也可以定义非静态成员, 其调用方法如下:
public class Outter {
public Outter() {
}
static class Inner {
static int a = 3;
int b = 4;
public Inner() {
}
}
}
class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
System.out.println(Outter.Inner.a);
System.out.println(new Outter.Inner().b);
}
}
4.3 局部内部类
局部内部类是定义在方法中的内部类,只能在方法中使用,其内部不能定义静态变量或静态方法(局部内部类是属于方法的,而static
是属于类的,两者矛盾):
局部内部类当中可以包含局部变量,使用的局部变量的本质是final
变量。在方法中new
出局部类,会在堆中开辟空间,若局部内部类使用了栈中该方法栈帧的成员变量,一旦方法栈帧出栈,堆中的成员变量就无法找到指向的元素,会空指针异常为了避免这种情况,只能将方法中的成员变量设为static
或final
,但是static
又不能设置,所以只能设置为final
。
注意,局部内部类就像是方法里面的一个局部变量一样,是不能有public
、protected
、private
以及static
修饰符的。
4.4 匿名内部类
匿名内部类是没有名字的局部内部类,没有类名也没有构造器,一个文件对应一个类。如果一个类只使用一次,就使用匿名内部类。
使用匿名内部类有一个前提条件:必须继承一个抽象类或实现一个接口。
继承一个类:
abstract class Person {
public abstract void eat();
}
class Test {
public static void main(String[] args) {
Person p = new Person() {
@Override
public void eat() {
}
};
}
}
可以看到,直接将抽象类Person
中的方法在大括号中实现了,这样便可以省略一个类的书写。
匿名内部类还能用于接口上:
interface Person {
public void eat();
}
class Test {
public static void main(String[] args) {
Person p = new Person() {
@Override
public void eat() {
}
};
}
}
由上面的例子可以看出,只要一个类是抽象的或是一个接口,那么其子类中的方法都可以使用匿名内部类来实现。
匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class
。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
下面这段代码是一段Android
事件监听代码:
scan_bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
history_bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
其中的new OnClickListner(){...}
使用的就是匿名内部类。当然像下面这种写法也是可以的,跟上面使用匿名内部类达到效果相同:
private void setListener(){
scan_bt.setOnClickListener(new Listener1());
history_bt.setOnClickListener(new Listener2());
}
class Listener1 implements View.OnClickListener{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
class Listener2 implements View.OnClickListener{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
这种写法虽然能达到一样的效果,但是既冗长又难以维护,所以一般使用匿名内部类的方法来编写事件监听代码。
参考
https://www.cnblogs.com/dolphin0520/p/3811445.html
https://zhuanlan.zhihu.com/p/49893350
https://blog.csdn.net/weixin_42762133/article/details/82890555