这篇文章主要讲述Java 内部类的相关知识,主要讲解下面的知识点。
- 内部类的概念
- 内部类的特点与使用
- 多种形式内部类
- 为什么要使用内部类
内部类的概念
内部类是指在一个类的内部定义了另一个类。例如下面的代码中例子,就是一个简单的内部类。
public class A {
private int a;
public class B{
private int b;
}
}
在这个类中,我们可以看出内部类B就像A的成员一样。所以我们对内部类的修饰也可以用很多种修饰符,比如我们是不可能对一个非内部类用private修饰符和protected修饰符,但是如果这个类是内部类,我们可以对他用private和protected访问修饰符以及static和final修饰符等等,就像对一个成员添加修饰符一样。当我们对B类添加多种修饰符以后,我们就可以把B类看成隐藏在A类中,像是一种代码隐藏机制。这样的内部类有什么特点以及我们如何去使用他呢?
内部类的特点与使用
在上面的例子中,我们把B类看成是A类的一个成员,那么这个成员与普通的成员变量和成员方法有什么区别呢? 我们可以很容易的在B类访问到A类中任何成员。但是如果我们想在A类中去访问B类的成员就不这么轻松了。如果B类是一个非静态类,那我们就要在外部类中声明一个B类的引用去调用B类中的成员。如下例子所示:
public class A {
private int a;
public B getB(){
return new B();
}
public void show(){
System.out.println(getB().b);
}
protected class B{
public int b;
public void print(){
System.out.println(a);
}
}
public static void main(String[] args) {
new A().show();
}
}
在这个例子中,我们可以看到B类中,是很随意就能访问到A类中的private成员变量a,但是A类中要访问B类中的public成员变量b也是要先定义一个B类的对象,通过这个对象去访问b。这就是内部类一个典型的特点。外部类的一切对内部类都是透明的,但是内部类确可以对外部类隐藏实现的细节。造成这样的原因是因为当某个外部类对象创建一个内部类对象的时候,此内部类的对象必定会秘密的捕获一个指向外部类的引用。这些都是编译器帮你做的。但是如果B类用static修饰也就是变成一个静态类,那么B类中也只能访问A类中的静态成员。
注意,如果上面的show()方法,如果show()是一个静态的方法,那么就不能直接new B()一个对象而是要用a的对象去创建B的对象。
那如果在B类中使用this指的是哪个对象的?按照this的定义显然指向的当前对象B,如果我们要在B类中显式调用A类的对象,我们就要用到一个特殊的语法 . this
显式调用A的对象就是A.this
那如果我们要在一个其他类中的创建B类对象呢?那我们需要用到一个新的语法.new
。代码如下:
A a = new A();
A.B b = a.new B();
这样就在其他类中创建了一个B的对象。在A类中就不需要这样麻烦了,因为会默认省略一个this,所以看起来只是new B()这样简单。
当然,前面说了B类是可以用static修饰,这个时候情况就变了。首先在其他类中创建一个B的对象,我们就不能用A的对象去创建B了 因为B是一个静态类不能用对象去引用,这是我们可以用如下代码去创建B的对象
A a = new A();
A.B b = new A.B();
或者直接用A.B去调用B类中的静态方法。A.B表示访问A类中的B类
其次,如果我们在A类中写一个静态方法,那么就不需要去A.B,直接用B去调用B类中静态成员。代码如下:
public class A {
public static class B{
public int a = 1;
public int bb;
public static String print(){
return "1";
}
}
public static void main(String[] args) {
B.print();
}
}
注意一点,只有B类是静态类才能由静态成员。
多种形式的内部类
我们上面已经了解到了内部类的特点以及如何使用内部类,接下来我们了解一些内部类的类型,Java语言并没有规定内部类必须出现在哪里。所以内部类可以出现在方法内部,出现在作用域中,还可能还是匿名内部类.
方法内部的内部类——局部内部类
我们可以在方法内部定义一个类,这在一定时候是有必要的。这个时候类不能有修饰符。例子如下:
public class A {
public C getC(){
class B extends C{
@Override
public void show() {
System.out.println("我是B");
}
}
return new B();
}
public static void main(String[] args) {
A a = new A();
a.getC().show();
}
}
abstract class C{
public abstract void show();
}
作用域中的内部类
这里指的是内部类出现在方法中的一个作用域内,这样说可能是难懂,比如方法中有一个if语句,内部类写在这个if语句中,这就是内部类出现在一个作用域内。虽然这不常见,但是Java语言是支持这种语法的。
public class A {
public C getC(boolean flag) {
if (flag) {
class B extends C {
@Override
public void show() {
System.out.println("我是B");
}
}
return new B();
}
return null;
}
public static void main(String[] args) {
A a = new A();
a.getC(true).show();
}
}
abstract class C{
public abstract void show();
}
这个例子,不代表B类的生成是有条件的,相反,B类和A类在编译的时候就一起生成了。
匿名内部类
接下来,我们改写一下局部内部类的例子,让他看上去是这样的。
public class A {
public C getC() {
return new C() {
@Override
public void show() {
System.out.println("我的匿名内部类");
}
};
}
public static void main(String[] args) {
A a = new A();
a.getC().show();
}
}
abstract class C{
public abstract void show();
}
这样看上去就简洁很多,也怪异一些。返回值的生成与表示这个返回值的类的定义结合在一起。这个类的定义还没有名字。这个语法就叫做匿名内部类。
创建一个继承自C的匿名类的对象。通过new表达式返回的引用被自动向上转型为对C的引用。
匿名内部类,主要的功能就是创建一个类的子类,然后复写父类中的方法,然后将子类的对象作为返回值返回。这个常见于接口的回调,也就是C类不在是抽象类而是一个接口。我们无法返回接口的对象,所以只能返回实现了接口的类的对象,这时用匿名内部类的形式来表示这个类的对象是再好不过了。但是这也有缺点,因为匿名内部类无法第二次使用。我们需要不止一个该类的对象的时候,就很尴尬了。我们接下来继续了解匿名内部类的特点,当了解完我们就清楚什么时候该用匿名内部类啦。
上面的代码中是通过默认的构造器来new一个C的对象,我们如果需要一个有参数的构造器来创建C对象,我们就可以直接C类中定义一个有参数的构造器,通过new C的时候直接把参数传入。
public class A {
public C getC() {
return new C(2) {
@Override
public void show() {
System.out.println(a);
System.out.println("我的匿名内部类");
}
};
}
public static void main(String[] args) {
A a = new A();
a.getC().show();
}
}
abstract class C{
int a;
public C(int a){
this.a = a;
}
public abstract void show();
}
有的时候我们的匿名内部类所在的方法需要一个形参,而这个形参则在匿名内部类中被使用,这个时候我们需要指定这个形参为final类型。直接看这个简单的例子就可以。
public class OuterClass {
public void display(final String name,String age){
class InnerClass{
void display(){
System.out.println(name);
}
}
}
}
这种一定要加final的原因是因为内部类和外部类完全是两个不同的类,也就是说我们在内部类改变了形参的东西,但是对于外部类而言,这个形参没有改变,因为这个形参对于内部类和外部类是两个东西。但是对于程序员而言这就是一个参数,所以为了避免这种尴尬的事情,就加上final来使形参不发生改变。还有另一种解释,就是加上final 形参,则表示给了内部类一个该形参的副本,让内部类可以对他进行任意操作。
有的时候,如果我们要给我们的匿名内部类加一个构造器来进行一些初始化的时候,就很尴尬了,因为我们并不能添加构造器,这时可以用初始化块的形式来进行一些初始化,但是要注意一点,初始化块不能是静态的,而且匿名内部类也不能用静态的成员。
所以到这里,我们也可以发现不用匿名内部类的一个理由,当我们需要一个有名字的构造器的时候,我们就不能用匿名内部类。
为什么使用内部类
如果只是需要实现一个接口或者继承一个抽象类那么直接用外部类就可以了,不需要内部类,但是如果我们需要多继承呢? 内部类最引人的地方就是
每个内部类都能独立地继承一个类或者一个接口的实现,所以无论外部类是否已经继承了类或者实现了一个接口,都不影响内部类。
所以内部类可以是多继承变得更加完整。而且使用内部类还会有更多新特性。
- 内部类可以有多个实例,每个实例都有自己的状态。与外部类互相独立。
- 在一个外部类里可以写多个内部类去实现同一个接口或者继承同一个类而有不同的实现。
- 在外部类中创建内部类的对象,并不依赖于外部类对象的创建。
- 在基于回调的事件的时候,匿名内部类是个很好的选择。