Java内部类详解
1、概述
什么是内部类?
将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。
广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。下面就先来了解一下这四种内部类的用法。
内部类实现了更好的封装,我们知道,普通类(非内部类)的访问修饰符不能为private或protected,而内部类可以。当我们将内部类声明为private时,只有外部类可以访问内部类,很好地隐藏了内部类。
内部类可以继承(extends)或实现(implements)其他的类或接口,而不受外部类的影响。
内部类可以直接访问外部类的字段和方法,即使是用private修饰的,相反的,外部类不能直接访问内部类的成员。
原理:内部类是一个编译时的概念,编译后会生成两个独立的class文件
广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。
2、成员内部类
定义在类中方法外的类
定义格式:
class 外部类 {
class 内部类{
}
}
在描述事物时,若一个事物内部还包含其他事物,就可以使用内部类这种结构。比如,汽车类 Car 中包含发动机 类 Engine ,这时, Engine 就可以使用内部类来描述,定义在成员位置。
访问特点:
- 内部类可以直接访问外部类的成员,包括private成员和静态成员。
- 外部类要访问内部类的成员,必须要建立内部类的对象。
创建内部类对象格式:
外部类名.内部类名 对象名 = new 外部类型().new 内部类型();
或
外部类名 外部对象名 = new 外部类型();
外部类名.内部类名 内部对象名 = 外部对象名.get内部类方法();
示例:
public class Person {
private boolean live = true;
class Heart {
public void jump() {
// 直接访问外部类成员
if (live) {
System.out.println("心脏在跳动");
} else {
System.out.println("心脏不跳了");
}
}
}
public boolean isLive() {
return live;
}
public void setLive(boolean live) {
this.live = live;
}
public Heart getHeart(){
if(Heart==null){
Heart=new Heart;
}
return Heart;
}
}
public class InnerDemo {
public static void main(String[] args) { // 创建外部类对象
Person p = new Person();
//成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。
// 方法1 通过外部类来new
Person.Heart heart = p.new Heart();
//方法2 通过外部类的get方法(如果有这个get方法的话)来创建
//Person.Heart heart = p.getHeart();
// 调用内部类方法 heart.jump();
// 调用外部类方法 p.setLive(false);
// 调用内部类方法 heart.jump();
}
}
输出结果:
心脏在跳动
心脏不跳了
内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。比如上面的例子,如果成员内部类Heart用private修饰,则只能在外部类的内部访问,如果用public修饰,则任何地方都能访问;如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。这一点和外部类有一点不一样,外部类只能被public和包访问两种权限修饰。我个人是这么理解的,由于成员内部类看起来像是外部类的一个成员,所以可以像类的成员一样拥有多种权限修饰。
内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类名 和 符 号 。 比 如 , P e r s o n 符号 。 比如,Person 符号。比如,PersonHeart.class
3、局部内部类
局部内部类的使用和成员内部类的使用基本一致,只是局部内部类定义在外部类的方法或一个作用域中,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
局部内部类就像局部变量一样,并不是外部类的成员。局部内部类在方法外是无法访问到的,但它的实例可以从方法中返回,并且实例在不再被引用之前会一直存在。局部内部类也可以访问所在方法的局部变量、方法参数等,限制是局部变量或方法参数只有在声明为final时才能被访问。
class People{
public People() {
}
}
class Man{
public Man(){
}
public People getWoman(){
class Woman extends People{ //局部内部类
int age =0;
}
return new Woman();
}
}
局部内部类就像是方法里面的一个局部变量一样,是不能有 public、protected、private 以及 static 修饰符的。
4、匿名内部类(重要)
(1)概述
可以把匿名内部类想象成是没有类名的局部内部类,匿名内部类有以下特点:
- 匿名内部类不能有构造器,匿名内部类没有类名,因此肯定无法声明构造器,也正是没有类名,所以匿名内部类只能使用一次。
- 一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。指定给new的类型为匿名类的超类型,匿名类不能有显示的extends、implements或class关键字,也不能有任何修饰符(public,protected,private,static)。
- 匿名内部类和成员内部类、局部内部类一样,也不能声明静态成员和静态成员变量。
- 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
- 只能创建匿名内部类的一个实例。
匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为 Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。、
我们先来看一下不使用匿名内部类来实现抽象方法
abstract class Person {
public abstract void eat();
}
class Child extends Person {
public void eat() {
System.out.println("eat something");
}
}
public class Demo {
public static void main(String[] args) {
Person p = new Child();
p.eat();
}
}
**运行结果:**eat something
可以看到,我们用Child继承了Person类,然后实现了Child的一个实例,将其向上转型为Person类的引用。但是,如果此处的Child类只使用一次,那么将其编写为独立的一个类岂不是很麻烦?
这个时候就引入了匿名内部类
(2)继承父类
格式:
new 父类名或者接口名(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};
abstract class Person {
public abstract void eat();
}
public class Demo{
public static void main(String[] args){
/*
1.等号右边:是匿名内部类,定义并创建该接口的子类对象
2.等号左边:是多态赋值,父类引用指向子类对象 */
Person p=new Person(){
//省略类名,实现抽象类的方法
public void eat(){
System.out.println("eat something");
}
};
//调用 eat方法,执行重写后的方法
p.eat();
}
}
(3)实现接口
定义接口:
public abstract class FlyAble{
public abstract void fly();
}
创建匿名内部类,并调用:
public class InnerDemo {
public static void main(String[] args) {
/*
1.等号右边:是匿名内部类,定义并创建该接口的子类对象
2.等号左边:是多态赋值,接口类型引用指向子类对象 */
FlyAble f = new FlyAble(){
public void fly() {
System.out.println("我飞了~~~");
}
};
//调用 fly方法,执行重写后的方法
f.fly();
}
}
以上两步,也可以简化为一步,代码如下:
public class InnerDemo3 {
public static void main(String[] args) {
/*
创建匿名内部类,直接传递给showFly(FlyAble f) */
showFly( new FlyAble(){
public void fly() {
System.out.println("我飞了~~~");
}
});
}
public static void showFly(FlyAble f) {
f.fly();
}
}
即把可以把showFly方法括号中的参数看成flyAble
showFly(flyAble);
而flyAble就是以下匿名内部类的实例
new FlyAble(){
public void fly() {
System.out.println("我飞了~~~");
}
}
为什么可以直接省略掉flyAble?
答:创建匿名内部类时它会立即创建一个该类的实例。且该类的定义会立即消失,这也是为什么匿名内部类不能被重复使用的原因。
(4)Thread类的匿名内部类实现
匿名内部类最常用的情况就是在多线程的实现上,因为要实现多线程必须继承Thread类或是继承Runnable接口
public class Demo {
public static void main(String[] args) {
Thread t = new Thread() {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.print(i + " ");
}
}
};
t.start();
}
}
**运行结果:**1 2 3 4 5
(5)Runnable接口的匿名内部类实现
public class Demo {
public static void main(String[] args) {
Runnable r = new Runnable() {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.print(i + " ");
}
}
};
Thread t = new Thread(r);
t.start();
}
}
**运行结果:**1 2 3 4 5
4、静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
class Outter {
public Outter() {
}
static class Inner {
public Inner() {
}
}
}