Java基础七--内部类

在 Java 中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。

与之对应,包含内部类的类被称为外部类。

一般来说,内部类有四种:

  • 成员内部类
  • 局部内部类
  • 匿名内部类
  • 静态内部类

为什么要使用内部类

在《Think in java》中有这样一句话:使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。

在我们程序设计中有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。


内部类的优势

  • 内部类提供了更好的封装,可以把内部类隐藏在外部类之中,不允许同一个包中的其他类访问该类,更好的实现了信息隐藏。

  • 也是最吸引人的原因,每个内部类都能独立地继承一个接口,而无论外部类是否已经继承了某个接口。
    因此,内部类使多重继承的解决方案变得更加完整。
    在项目中,需要多重继承,如果是两个接口,那么好办,接口支持多重继承。
    如果是两个类呢?这时只有使用内部类了。


成员内部类

成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Circle {			//外部类
double r = 0;

public Circle(double r) {
this.r = r;
}

class Draw{ //内部类
public void drawSahpe(){
System.out.println("Draw Sahpe.");
}
}
}

这样看起来,Draw 类像是 Circle 类的一个成员,Circle 称为外部类,Draw 类称为 Circle 类的成员内部类。成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括 private 成员和静态成员)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Circle {
private double r = 0;
public static int count = 1;

public Circle(double r) {
this.r = r;
}

class Draw{
public void drawSahpe(){
System.out.println(r); //外部类的private成员
System.out.println(count); //外部类的静态成员
System.out.println("Draw Sahpe.");
}
}
}

虽然成员内部类可以无条件的访问外部类的成员,但是外部类想访问成员内部类的成员缺不是那么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Circle {
private double r = 0;

public Circle(double r) {
this.r = r;
getDrawInstance().drawSahpe(); //必须先创建成员内部类的对象,再进行访问
}

private Draw getDrawInstance(){
return new Draw();
}

class Draw{ //内部类
public void drawSahpe(){
System.out.println(r);//外部类的private成员
System.out.println("Draw Sahpe.");
}
}
}

成员内部类是依附于外部类而成在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Outter {

private Inner inner = null;
public Outter(){

}

public Inner getInnerInstance(){
if (inner == null){
inner = new Inner();
}
return inner;
}

class Inner{
public Inner(){

}
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Main {
public static void main(String[] args) {
//第一种方法:外部类对象.new 内部类
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner();
//第二种方法:外部类对象.获取方法
Outter.Inner inner1 = outter.getInnerInstance();
//第三种方法:new 外部类.new 内部类
Outter.Inner inner2 = new Outter().new Inner();
}
}

内部类可以拥有 private 访问权限、protected 访问权限、public 访问权限及包访问权限。比如上面的例子,如果成员内部类 Inner 用 private 修饰,则只能在外部类的内部访问,如果用 public 修饰,则任何地方都能访问;如果用 protected 修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。这一点和外部类有一点不一样,外部类只能被 public 和包访问两种权限修饰。我个人是这么理解的,由于成员内部类看起来像是外部类的一个成员,所以可以像类的成员一样拥有多种权限修饰。

小结

  • 内部类在外部使用时,无法直接实例化,需要借由外部类信息才能完成实例化
  • 成员内部类中不能存在任何static的变量和方法
  • 内部类的访问修饰符,可以任意,但是访问范围会受到影响
  • 内部类可以直接访问外部类的成员;如果出现同名属性,有限访问内部类中定义的
  • 可以使用外部类.this.成员的方法,访问外部类中同名的信息
  • 外部类访问内部类信息,需要通过内部类实例,无法直接访问
  • 内部类编译后.class文件命名:外部类$内部类.class
  • 内部类中可以包含与外部类相同方法签名的方法,内部类对象在调用的时候调用的是内部类中的方法

局部内部类

有这样一种内部类,它是嵌套在方法和作用域内的,对于这个类的使用主要是应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,到那时又不希望这个类是公共可用的,所以就产生了局部内部类,局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效。

1
2
3
4
5
6
7
8
9
10
class Outter {
public void function(){
//局部内部类 Inner
class Inner{
public void print(){
System.out.println("局部内部类...");
}
}
}
}

小结

  • 局部内部类类似方法的局部变量,所以在类外或者类的其他方法中不能访问这个内部类,但这并不代表局部内部类的实例和定义了它的方法中的局部变量具有相同的生命周期。

    局部内部类

    我们可以看到,这里的局部内部类 Inner 定义在了一个 if 条件作用域中,因此,在 if 之外的部分,尽管没有离开 function() 方法,就会编译报错,无法访问到 Inner 这个内部类。因此,还要广义的理解局部内部类的含义和访问条件,不可以片面的理解为仅仅是方法中定义的内部类。

  • 只能在方法内部,局部内部类定义之后使用,不存在外部可见性问题,因此没有访问修饰符,即 class 前面不可以添加public、private、protected、static

  • 不能再局部内部类中使用可变的局部变量

  • 类中不能包含静态成员

  • 类中可以包含final、abstract修饰的成员

综上,就是局部内部类的知识,其实局部内部类的名气远没有匿名内部类的名气大,但是作为一个匿名内部类的父概念,其定义了匿名内部类和普通的局部内部类的概念和特性,因此可以作为匿名内部类的知识补充。


匿名内部类

匿名内部类也就是没有名字的内部类。

正因为没有名字,所以匿名内部类只能用一次,它通常是用来简化代码编写

但使用匿名内部类还有一个前提条件:必须继承一个父类或实现一个接口

这应该是我最经常用的内部类了,写 Android 的时候,使用点击事件就会用到它,来举个例子吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

bt = findViewById(R.id.bt1);
//1.匿名内部类
bt.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Log.i("匿名内部类", "点击事件");
}
});
}

这就是匿名内部类的使用。代码中需要给 Button 设置一个监听器对象,使用匿名内部类能够在实现父类或者接口的方法的情况下同时产生一个相应的对象,但是前提是父类或者这个接口的方法是存在的。当然,也可以按照下面这么写。

1
2
3
4
private void setListener()
{
bt.setOnClickListener(new Listener1());
}
1
2
3
4
5
6
7
class Listener1 implements View.OnClickListener{
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Log.i("匿名内部类", "点击事件");
}
}

这样的写法虽然能达到同样的效果,但是及冗长又难以维护,所以一般使用匿名内部类的方法来编写事件监听代码。同样的,匿名内部类也不能有访问修饰符和 static 修饰符的。

匿名内部类初始化

我们一般用构造器来完成某个实例的初始化工作,但是匿名内部类是没有构造器的,那怎么来初始化匿名内部类呢?答案是使用构造代码块!利用构造代码块能够达到为匿名内部类创建一个构造器的效果。

实例

匿名内部类有不能的表现形式,下面为大家展示一下:

继承式的匿名内部类

1
2
3
abstract class Car {
public abstract void drive();
}
1
2
3
4
5
6
7
8
9
10
11
public class Main {
public static void main(String[] args) {
Car car = new Car() {
@Override
public void drive() {
System.out.println("Driving another car");
}
};
car.drive();
}
}

运行结果

Driving another car

引用变量不是引用 Car 对象,而是 Car 匿名子类的对象

建立匿名内部类的关键是重写父类的一个或者多个方法,这里再强调一下,是重写父类的方法,而不是建立新的方法。因为父类的引用不可能调用父类本身没有的方法,所以建立新的方法是多余的。

接口式的匿名内部类

1
2
3
public interface Vehicle {
public void drive();
}
1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) {
Vehicle v = new Vehicle() {
@Override
public void drive() {
System.out.println("Driving a car");
}
};
}
}

运行结果

Driving a car

这段代码很奇怪,乍一看以为实例化了个接口。事实并非如此,接口式的匿名内部类是实现了一个接口的匿名类。而且只能实现一个借口。

参数式的匿名内部类

1
2
public interface Foo {
}
1
2
3
abstract class Bar {
void doStuff(Foo f){}
}
1
2
3
public class BarOne extends Bar {
void doStuff(Foo f){}
}
1
2
3
4
5
6
7
8
9
10
public class Main {
static void go(){
Bar b = new BarOne();
b.doStuff(new Foo() {
public void foo(){
System.out.println("foofy");
}
});
}
}

由上面3个例子可以看出,只要一个类是抽象的或者是一个接口,那么其子类中的方法都可以使用匿名内部类来实现。最常用的情况就是在多线程的实现上,因为要实现多线程必须继承 Thread 类或是实现 Runable 接口

Thread 类的匿名内部类实现

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Main {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.print(i + " ");
}
}
};
t.start();
}
}

Runnable 接口的匿名内部类实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main{
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.print(i + " ");
}
}
};
Thread t = new Thread(r);
t.start();
}
}

小结

  • 匿名内部类没有类型名称、实例对象名称
  • 编译后的文件命名:外部类$数字.class

  • 无法使用private、public、protected、abstract、static修饰,匿名内部类不能出现抽象方法

  • 无法编写构造方法,可以添加构造代码块
  • 不能出现静态成员
  • 匿名内部类可以实现接口也可以继承父类,但不可以兼得
  • 匿名内部类不能是抽象的,它必须要实现继承的类或者实现接口的所有抽象方法

静态内部类

静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字 static 。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非 static 成员变量或方法。这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非 static 成员就会产生矛盾,因为外部类的非 static 必须依附于具体的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Outter {
private static int age = 12;
private String name = "外部类";

public void say(){
System.out.println("这是外部类的静态方法");
}

static class Inner{
public static void say(){
System.out.println("这是内部类的静态方法");
}

public String print(){
new Outter().say();
return new Outter().name + "到此一游";
}
}
}
1
2
3
4
5
6
public class Main{
public static void main(String[] args) {
Outter.Inner in = new Outter.Inner();
System.out.println(in.print());
}
}

运行结果

这是外部类的静态方法
外部类到此一游

可以看到,如果用 static 将内部类静态化,那么内部类就只能访问外部类的静态成员变量,具有局限性。

其次,因为内部类被静态化,因此 Outter.Inner 可以当做一个整体看,可以直接 new 出内部类的对象。(因为类名访问 static ,生不成外部类对象都没关系)

小结

  • 静态内部类中,只能直接访问外部类的静态成员,如果需要调用非静态成员,可以通过对象实例

    new Outter().say(); 这样调用

  • 静态内部类对象实例时,可以不依赖于外部类对象

  • 可以通过外部类.内部类.静态成员的方式,访问内部类中的静态成员

  • 当内部类属性与外部类属性同名时,默认直接调用内部类中的成员

  • 如果需要访问外部类中的静态属性,则可以通过外部类.属性的方式

  • 如果需要访问外部类中的非静态属性,则可以通过new 外部类().属性的方式


接口中的内部类

我们在实际开发中,如果想要创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,那么接口内部的嵌套类会显得很方便。也就是说,在接口中可以含有内部类。

  • 首先创建接口,接口中定义了普通内部类 InnerClass 和抽象内部类 AbInnerClass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public interface IOuterInterface {
int TEMP = 100;//常亮
void abMethod();//抽象方法
public default void deMethod(){//jdk1.8后可添加
System.out.println("接口中的静态方法");
}
public static void stMethod(){//jdk1.8后可添加
System.out.println("接口中的静态方法");
}

//普通内部类
public class InnerClass{
public void show(){
System.out.println("接口中可定义普通成员内部类");
}
}

//抽象内部类
public abstract class AbInnerClass{
public abstract void abInfo();
public void info(){
System.out.println("接口中可以定义抽象成员内部类");
}
}

}
  • 普通成员内部类的实例化
1
2
3
4
5
6
7
8
9
10
11
12
public class ClassDemo implements IOuterInterface{

@Override
public void abMethod() {
System.out.println("实现类");
}

//获取接口中内部类方法
public InnerClass getInner(){
return new InnerClass();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main{
public static void main(String[] args) {
//第一种实例化对象方式
//通过 接口名.类名 进行实例化
IOuterInterface.InnerClass innerClass = new IOuterInterface.InnerClass();
innerClass.show();

//第二种实例化对象方式
//通过在实现类中创建接口中内部类获取方法
//用实现类对象调用获取方法
ClassDemo demo = new ClassDemo();
demo.getInner().show();

}
}

运行结果

接口中可定义普通成员内部类
接口中可定义普通成员内部类

  • 抽象成员内部类的实例化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class AbClassDemo implements IOuterInterface {
@Override
public void abMethod() {

}
//继承抽象类AbInnerClass
public class AbDemo extends AbInnerClass{

@Override
public void abInfo() {
System.out.println("重写了接口中国抽象类中的抽象方法");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Main{
public static void main(String[] args) {
//第一种实例化对象方式
//通过 接口名.类名 进行实例化
//但是对于抽象类而言,不能直接实例化,所以这里可以使用匿名内部类的方式
IOuterInterface.AbInnerClass abInner = new IOuterInterface.AbInnerClass() {
@Override
public void abInfo() {
System.out.println("重写抽象类中的抽象方法");
}
};
abInner.abInfo();
abInner.info();
System.out.println();
//第二种实例化对象方式
//在实现类中定义内部类继承接口中的抽象内部类
IOuterInterface.AbInnerClass abInnerOne = new AbClassDemo().new AbDemo();
abInnerOne.abInfo();
abInnerOne.info();

}
}

运行结果

重写抽象类中的抽象方法
接口中可以定义抽象成员内部类

重写了接口中国抽象类中的抽象方法
接口中可以定义抽象成员内部类

小结

这里只是提供给大家几种实例化接口中内部类的思路和方式,大家也可以用其他方式去进行对象实例化,当然前提是要满足代码规则。

-------------本文结束感谢您的阅读-------------

本文标题:Java基础七--内部类

文章作者:Cui Zhe

发布时间:2018年10月22日 - 22:10

最后更新:2018年10月23日 - 17:10

原始链接:https://cuizhe1023.github.io/2018/10/22/Java基础七-内部类/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。