一. 面试题及剖析
1. 今日面试题
今天 壹哥 带大家看一道与内部类有关的面试题,如下:
你熟悉哪些Java内部类?
说一下Java内部类的使用场景有哪些?
2. 题目剖析
这道题目涉及到的内容,主要是面向对象中与类相关的内容,尤其是内部类相关的内容。回答这个问题的时候,我们可以从内部类的概念、作用、分类、特点、使用场景、具体使用、注意事项等角度进行回答。
二. 参考答案
1. 内部类概念
所谓内部类,简单的说,就是在一个类内部定义的类,与C++中的嵌套类(Nested Class)概念类似。
2. 内部类作用
那么内部类有什么作用呢?往下看。
我们知道Java是不支持多继承的,但是我们可以通过使用内部类来间接达到多继承的目的。比如我们可以让内部类继承一个父类,与此同时,又可以在内部类(除去静态内部类)中,直接使用其外部类的成员变量以及成员方法,这样就间接达到了多重继承的效果。
另外利用内部类还可以进行事件驱动,通过接口回调机制实现各种事件监听效果,比如实现常见的观察者模式,Android中的按钮点击事件等操作。
3. 内部类分类
Java内部类一般包括四种,如下:
成员内部类
局部内部类
匿名内部类
静态内部类
4. 内部类使用场景
我们在什么场景中可以使用内部类呢?其实使用内部类的地方还是挺多的,比如当我们遇到如下情况时:
想要隐藏某些类的实现;
想要实现多继承效果;
进行单元测试;
启动操作线程类;
解决闭包问题等...
注:
所谓闭包,简单来说,比如一个类已经继承了某个父类,然后这个类还想实现一个接口,但是这个父类和这个接口中有些方法或属性有冲突,如果我们想要把父类和接口的冲突功能都保留下来,这样解决的就是闭包问题。
5. 内部类本质
对于内部类,我们要清楚它与普通类的本质不同之处:
- 内部类体现了一种代码的隐藏机制和访问控制机制。在这一点上,它与C++中嵌套类的概念类似;
- 内部类包含有一个外部类的this指针。这是内部类特性中非常重要的一点,正是因为有了这个指针,内部类可以访问外部类的所有元素。
以上就是内部类的本质。
5.1 关于代码隐藏和访问控制机制的说明
- 如果内部类被声明为public的,那么外部类作用域之外的地方都是可以使用这个内部类的,但是使用的方式必须是:OuterClass.InnerClass。所以要想创建内部类实例,需要先有一个外部类实例,比如:
OuterClass.InnerClass InnerObject = OuterObject.new InnerClass();
这样,就可以在外部类作用域以外的地方得到一个内部类的实例了。而之所以需要这样做的原因,就在于内部类的实例必须含有一个外部类的this指针,如果没有这样一个外部类实例,哪来的这个this指针呢?
- 如果内部类被声明为private的,首先在Java中,普通类是不能被private修饰的,所以只有内部类才能被private所修饰;其次,如果被修饰成了private,那么内部类在外部类作用域之外的地方就不可见了,只有外部类才能使用内部类。这样,就实现了访问控制。
5.2 关于外部类this指针的说明
所谓可以访问外部类的所有元素,包括了外部类的public和private修饰的所有成员数据和方法。另一方面,外部类对于内部类的所有元素也都有访问权,包括内部类的私有成员和方法。
6. 注意事项
因为有多种内部类,在使用时我们需要注意可能存在的一些问题。
继承内部类时,如果使用默认的构造器可能会报错,因为内部类会默认获得指向其外部类对象的引用。所以继承内部类时应该在构造器参数中传递一个其外部类对象的引用(编译器要求你一定要这样做),然后在构造器中使用该外部类对象引用的super方法,该super方法调用的是这个外部类对象的内部类的构造方法。
如果父类和接口中都有一个相同名称的方法,那么子类中的这个方法,既是对父类方法的重写又是对接口方法的实现。
三. 示例代码
上面 壹哥 给各位介绍了内部类的一些概念,但是具体每种内部类都有什么特点,及各自的用法,我们还需要一下代码案例来进行佐证,所以接下来 壹哥 给各位设计了一些示例代码,我们来看看吧。
1. 成员内部类
1.1 成与内部类概念
成员内部类只能被它的外部类使用,不会被其他类使用,这种情况下,内部类依附于外部类而存在。之所以设计出成员内部类,原因如下:
1. 不可能有其他类使用该内部类;
2. 该内部类不能被其他类使用,否则可能会导致错误,很多内部类都这么使用。
1.2 成员内部类特点
- 外部类可以直接访问内部类的成员和方法,但是必须先创建一个内部类对象,再通过该内部类对象使用其成员和方法;
- 内部类可以访问外部类的成员和方法,但要注意,当内部类拥有和外部类相同的成员或方法时,会发生隐藏现象,默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式来访问:外部类.this.成员变量/方法;
- 内部类只是一个编译时的概念,一旦编译成功,就会成为两个完全不同的类。对于一个名为Outer的外部类和其内部定义的名为Inner的内部类,编译完后会生成Outer.class和Outer$Inner.class两个类;
- 成员内部类与普通的成员没什么区别,可以与普通成员一样进行修饰和限制。
1.3 示例代码
public class Outer01 {
private String name = "一一哥";
private int age = 20;
public class Inner {
private int age = 25;
public void func() {
System.out.println("Outer name=" + Outer01.this.name);
System.out.println("Outer age=" + Outer01.this.age);
System.out.println("Inner age=" + age);
}
}
public static void main(String[] args) {
//创建外部类对象
Outer01 outer = new Outer01();
//创建内部类对象
Inner inner = outer.new Inner();
inner.func();
}
}
2. 局部内部类
2.1 局部内部类概念
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
2.2 局部内部类特点
局部内部类就像方法里面的局部变量一样,不能有public、protected、private及static修饰符。
2.3 示例代码
public class Outer02 {
private String name = "一一哥";
private int age = 20;
public void func() {
class Inner {
private int age = 25;
//其他代码略
}
//创建局部内部类对象
Inner inner = new Inner();
System.out.println("Inner age=" + inner.age);
System.out.println("Outer name=" + this.name);
System.out.println("Outer age=" + this.age);
}
public static void main(String[] args) {
//创建外部类对象
Outer02 outer = new Outer02();
outer.func();
}
}
3. 静态内部类
3.1 静态内部类概念
静态内部类也是定义在另一个类里的类,只不过在类的前面多了一个static关键字。
3.2 静态内部类特点
- 静态内部类不需要依赖于外部类,它不持有指向外部类对象的引用this;
- 静态内部类也不能使用外部类的非static成员或方法。这点很好理解,因为在没有外部类对象的情况下,可以创建静态内部类对象。如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体对象。
- 静态内部类的唯一作用就是随着类的加载(而不是随着对象的产生)而产生。
3.3 示例代码
public class Outer03 {
private static String name = "一一哥";
static class Inner {
private int age = 18;
public Inner() {
System.out.println("Inner age=" + age);
}
public void showOutName() {
//调用外部类的静态成员变量
System.out.println("Outer name=" + name);
}
}
public static void main(String[] args) {
//创建静态内部类对象
Outer03.Inner inner = new Outer03.Inner();
inner.showOutName();
}
}
4. 匿名内部类
4.1 匿名内部类概念
顾名思义,所谓的匿名内部类就是没有名字的内部类,它与局部内部类很相似,不同的是它没有类名,如果某个局部类只需要用一次,那么就可以使用匿名内部类。匿名内部类是我们平时编写代码时用的最多的内部类形式,尤其是在编写事件监听代码时,匿名内部类不但方便,而且可以使代码更加容易维护。
4.2 匿名内部类特点
- 匿名内部类是唯一没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类只用于接口回调。
- 匿名内部类在编译的时候会由系统自动起名为Outer$1.class。
- 一般来说,匿名内部类用于集成其他类或者实现接口,并不需要增加额外的方法,只是对集成方法的实现或是重写。
- 匿名内部类可以使你的代码更加简洁,你可以在定义一个类的同时对其进行实例化。
4.3 示例代码
public class Outer04 {
private String name = "一一哥";
public void func() {
System.out.println("Outer name=" + name);
}
public static void main(String[] args) {
//定义匿名内部类对象
Outer04 inner = new Outer04() {
//匿名内部类的成员方法
@Override
public void func() {
System.out.println("inner name= syc");
}
};
//调用内部类对象的成员方法
inner.func();
//创建外部类对象
Outer04 outer = new Outer04();
outer.func();
}
}
四. 结论
因为内部类分类较多,每种内部类又有不同的特点,所以我们在开发时要仔细区分,所以面试时,面试官也喜欢在这里进行提问,以考察我们的Java基础是否足够扎实。
虽然面试题看起来问的是“内部类的使用场景”,但是如果我们想要拿到高分,还是要参考我这篇文章,从多个方面对内部类进行介绍。
今天的内容你学会了吗?评论区留言讨论一下吧。



