概述
将一个类的定义放在一个类的内部,这就是内部类。内部类可以实现将一些逻辑相关的类组合在一起,并且可以控制类中的类的可视性。
举个栗子,在面向对象思维中,一个类即是一种类型,用类可以定义对象,现实中任何事物都可以被抽象为类与对象,例如,我们平时用的计算机,可以抽象为computer
类,如果想要一台计算机,则new
一个即可,这里我们要关注的是这个类的组成,现实中,计算机由各种零件组成(CPU、内存、硬盘、显示器等),这些零部件之间相互通信各自完成自己的任务,最终使得你可以使用计算机。那么我们该如何更好的抽象出这种内外嵌套的结构呢?
没错,对于计算机内部的零部件,也可以作为类,而内部类的特点正好符合这种逻辑,也就是说,计算机必须依赖这些零部件,而内部类(这里指普通内部类)的特点之一也是必须与外围类对象直接关联。对于计算机内部的零部件你通常情况下是接触不到的,而内部类也同样可以进行限制其对外的可视性,即通过private
等关键字实现。
内部类最常见的实现形式是成员内部类,其次还有静态内部类、局部内部类、匿名内部类。
内部类特点解析
以最常见的成员内部类为例,其特性如下:
- 内部类对象可访问外围类所有成员
- 内部类方法通过
.this
获取外部类的引用 - 外围类对象通过
.new
创建内部类对象(此处内部类必须为public
)
内部类对象访问外部类成员
public class Sequence {
private Object [] items;
private int count = 0;
public Sequence(int size) {
items = new Object[size];
}
public void add(Object obj) {
items[count++] = obj;
}
public class SequenceSelector {
private int i = 0;
public boolean isEnd() {
return i == items.length;
}
public Object current() {
return items[i];
}
public void next() {
if(i < items.length) {
i++;
}
}
}
public static void main(String [] args) {
Sequence seq = new Sequence(10);
for(int i=0; i<10; i++) {
seq.add(Integer.toString(i));
}
//此处seq.new的用法在后面解释,现在就当做是普通new即可
SequenceSelector selector = seq.new SequenceSelector();
while(!selector.isEnd()) {
System.out.println(selector.current());
selector.next();
}
}
}
上述代码中,我们实现了一个可存放任意对象的列表Sequence
,并在其内部实现了一个迭代器SequenceSelector
,创建迭代器对象即可实现对Sequence
对象中元素的遍历,即items
是private
的。
很显然,这样设计并不好,因为列表的种类很多的话,我们在使用某个列表并且需要其迭代器来遍历时,必须要知道该列表的迭代器类。思考如何进行设计优化?
.this与.new的使用
在上一部分代码中已经使用到了.new
,如果你单单只用new
来创建内部类对象,编译器就会报错,为什么呢?其实很好理解,以开头计算机的栗子说,就是如果没有计算机,单独给你个 CPU 是没有任何意义的,也就是说内部类的对象必须要与外围类对象建立联系,独立存在的内部类对象是毫无意义的(静态内部类除外,后面会讲)。
言归正传,我们来瞧瞧.this
和.new
的使用:
public class test1 {
public void fun(){
System.out.println("fun()");
}
public class inner {
//此方法返回外围对象的引用
public test1 DoThis() {
return test1.this;
}
}
pubic static void main(String [] args) {
test1 t = new test1();
//使用.new创建内部类
test1.inner dti = t.new inner();
dti.DoThis().fun();
}
}
/*Output:
fun()
*/
内部类对象的向上转型
在开头的第一块代码中我们实现了一个列表Sequence
,但是使用时却发现并没有想象中这么好用,因为不同类型的列表的迭代器不同,在使用时就必须要知道特定类的迭代器名称,如何才能改进呢?
理想状态应该是所有列表的类都共用一个迭代器类型,但是各自又可以实现对迭代器的个性化,由此可以想到继承,考虑到迭代器脱离列表就没有意义,因此将迭代器抽象成接口,接着在每个列表类中以内部类的形式实现各自的迭代器。
详见改进后的代码:
interface Selector {
public boolean isEnd();
public Object current();
public void next();
}
public class Sequence {
private Object [] items;
private int count = 0;
public Sequence(int size) {
items = new Object[size];
}
public void add(Object obj) {
items[count++] = obj;
}
private class SequenceSelector implements Selector{
private int i = 0;
public boolean isEnd() {
return i == items.length;
}
public Object current() {
return items[i];
}
public void next() {
if(i < items.length) {
i++;
}
}
}
public Selector getSelector() {
return new SequenceSelector();
}
public static void main(String [] args) {
Sequence seq = new Sequence(10);
for(int i=0; i<10; i++) {
seq.add(Integer.toString(i));
}
Selector selector = seq.getSelector();
while(!selector.isEnd()) {
System.out.println(selector.current());
selector.next();
}
}
}
上述代码中,新增了Selector
接口,作为迭代器的抽象接口,并且将内部类私有化,新增getSelector()
方法获取指定对象的迭代器。
在获得迭代器对象时,是无法确切知道迭代器的具体类型的,这对于客户端程序员使用来说,很好的隐藏了迭代器的实现细节,毕竟客户端程序员并不需要关心实现细节。
静态内部类和局部内部类
到此为止,我们讲到的都是内部类最普通的用法,在类的内部,除了成员变量的位置,还有方法内部。在方法内部的内部类称作局部内部类。
普通的内部类加上static
关键字修饰后,称为静态内部类。
静态内部类
如果不需要内部类对象和外部类对象有关联,那么将内部类设为静态的即可,这被称为嵌套类
区别于普通内部类,嵌套类中可以存在static
变量和方法,在使用时不需要存在外部类的对象。因此你可以理解为普通内部类中隐藏了外部类对象的引用(即.this
),嵌套类中没有这个引用。
那么嵌套类的作用是什么呢?
1.作为接口内部的类
正常情况下,接口内部是不允许存在任何代码的,但是可以存在嵌套类,因为嵌套类是static
的,只是将嵌套类置于接口的命名空间内,不违反接口的规则。
public interface testInterface {
public void fun();
class testInterfaceImpl implements testInterface {
void fun() {
System.out.println("fun");
}
}
}
2.从多层嵌套中访问外部类成员
一个内部类被嵌套了多层后,它依然能够访问所有外部类的所有成员。
class MNA {
private void fun() {
System.out.println("fun");
}
class A {
private void gun() {
System.out.println("gun");
}
public class B {
void hun() {
System.out.println("hun");
fun();
gun();
}
}
}
}
public class test6 {
public static void main(String [] args) {
MNA mna = new MNA();
MNA.A mnaa = mna.new A();
MNA.A.B mnaab = mnaa.new B();
mnaab.hun();
}
}
/*Output:
hun
fun
gun
*/
局部内部类
局部内部类的作用范围是局部的,例如,在某个方法中定义了局部内部类,那么方法以外的地方是不允许使用该内部类的。
interface Iinner {
String readLabel();
}
public class test2 {
public Iinner getInner(String s) {
class inner implements Iinner {
private String label;
private inner(String whereTo) {
label = whereTo;
}
public String readLabel() {
return label;
}
}
return new inner(s);
}
public static void main(String [] args) {
test2 t = new test2();
Iinner in = t.getInner("abc");
System.out.println(in.readLabel());
}
}
/*Output:
abc
*/
匿名内部类
如果我们只需要某个接口的一个对象,那么就可以使用匿名内部类来简化代码的编写。
interface Contents {
int value();
}
public class test3 {
class MyContents implement Contents {
private int i = 11;
public int value() {
return i;
}
}
public Contents getContents() {
return new MyContents();
}
public static void main(String [] args) {
test3 t3 = new test3();
System.out.println(t3.getContents().value());
}
}
假设上述代码中的内部类MyContents
只存在于getMyContents()
方法中使用,那么我们可以使用匿名内部类的形式将其简化:
interface Contents {
int value();
}
public class test3 {
public Contents getContents() {
return new Contents(){
private int i = 11;
public int value() {
return i;
}
};
}
public static void main(String [] args) {
test3 t3 = new test3();
System.out.println(t3.getContents().value());
}
}
通过例子,可以发现,匿名内部类是一个没有类名的类,通过new
关键字创建的对象被自动向上转型为基类的引用。
思考一下,如果创建匿名类对象时,需要参数该怎么做呢?
class Iinner {
private String label;
Iinner(String s) {
label = s;
}
String readLabel(){
return label;
}
}
public class test4 {
public Iinner getInner(String s) {
return new Iinner(s) {
public String readLabel() {
//调用父类方法获取成员变量
return super.readLabel() + 10;
}
};
}
public static void main(String [] args) {
test4 t = new test4();
Iinner in = t.getInner("abc");
System.out.println(in.readLabel());
}
}
通过在父类中创建对应的构造方法,可以在创建匿名类对象时进行传参。
需要注意的是,定义一个类,在类的末尾不需要分号,这里创建匿名类对象时末尾的分号是指new
语句的结尾,而不是类的结束符。
方法中使用匿名类创建对象时,若匿名类使用了方法中的参数,则参数必须为 final
class Iinner {
private String label;
Iinner(String s) {
label = s;
}
String readLabel(){
return label;
}
}
public class test5 {
public Iinner getInner(String s, final int i) {
return new Iinner(s) {
public String readLabel() {
return super.readLabel() + i;
}
};
}
public static void main(String [] args) {
test5 t = new test5();
Iinner in = t.getInner("abc", 10);
System.out.println(in.readLabel());
}
}
上例中可以发现,在匿名内部类中的方法中使用外部类方法的参数,参数必须为final
,但是参数作为匿名内部类的构造方法参数则不需要声明为final
,因为这个参数并没有在匿名内部类里面使用。
匿名内部类改造工厂模式
通过使用匿名内部类,工厂模式的实现代码变得更加优雅了。
interface Service {
void fun1();
void fun2();
}
interface ServiceFactory {
Service getService();
}
class ServiceImpl1 implements Service {
public static ServiceFactory factory = new ServiceFactory(){
public Service getService() {
return new ServiceImpl1();
}
};
private ServiceImpl1() {}
public void fun1() {
System.out.println("ServiceImpl1-fun1");
}
public void fun2() {
System.out.println("ServiceImpl1-fun2");
}
}
class ServiceImpl2 implements Service {
//每个服务类通过匿名内部类的方式得到特定工厂类
public static ServiceFactory factory = new ServiceFactory(){
public Service getService() {
return new ServiceImpl2();
}
};
private ServiceImpl2() {}
public void fun1() {
System.out.println("ServiceImpl2-fun1");
}
public void fun2() {
System.out.println("ServiceImpl2-fun2");
}
}
public class test {
//消费者,从工厂得到服务类
public static void serviceConsumer(ServiceFactory factory) {
Service s = factory.getService();
s.fun1();
s.fun2();
}
public static void main(String [] args) {
serviceConsumer(ServiceImpl1.factory);// 将特定服务类的工厂交给消费者
serviceConsumer(ServiceImpl2.factory);
}
}