内部类是封装的一种形式,是定义在类或接口中的类。
即定义的内部类作为外部类的一个普通成员(非static),就像下面这样:
public class Outer {
class Inner{
private String id = "夏天";
public String getId() {
return id;
}
}
public Inner returnInner(){
return new Inner();
}
public void show(){
Inner in = new Inner();
System.out.println(in.id);
}
}
我们通过以上一个简单的示例,可以得出以下几点:
可以看到,我们像使用正常类一样使用内部类,但实际上,内部类有许多奥妙,值得我们去学习。至于内部类的用处,我们暂且不谈,先学习它的语法也不迟。我们在另外一个类中再试着创建一下这个Inner对象吧:
class OuterTest{
public static void main(String[] args) {
//!false:Inner in = new Inner();
Outer o = new Outer();
o.show();
Outer.Inner in = o.returnInner();
//!false: can't access --System.out.println(in.id);
System.out.println(in.getId());
}
}
哦呦,有意思了,我们在另一个类OuterTest中再次测试我们之前定义的内部类,结果出现了非常明显的变化,我们陷入了沉思:
Inner in = new Inner();创建内部类实例。returnInner方法,来创建一个实例,成功!Outer.Inner,即外部类名.内部类名。getId方法访问吧,成功!说到这,我们大概就能猜测到:内部类的存在可以很好地隐藏一部分具有联系代码,实现了那句话:我想让你看到的东西你随便看,不想让你看的东西你想看,门都没有。
其实我们之前在分析ArrayList源码的时候,曾经接触过内部类。我们在学习迭代器设计模式的时候,也曾领略过内部类带了的奥妙之处。下面我通过《Java编程思想》上:通过一个内部类实现迭代器模式的简单案例做相应的分析与学习:
首先呢,定义一个“选择器”接口:
interface Selector {
boolean end();//判断是否到达终点
void next();//移到下一个元素
Object current();//访问当前元素
}
然后,定义一个序列类Sequence:
public class Sequence {
private Object[] items;
private int next = 0;
//构造器
public Sequence(int size) {
items = new Object[size];
}
public void add(Object x) {
if (next < items.length) {
items[next++] = x;
}
}
//该内部类可以访问外部类所有成员(包括私有成员)
private class SequenceSelector implements Selector {
private int i = 0;
@Override
public boolean end() {
return i == items.length;
}
@Override
public void next() {
if (i < items.length) {
i++;
}
}
@Override
public Object current() {
return items[i];
}
}
//向上转型为接口,隐藏实现的细节
public Selector selector() {
return new SequenceSelector();
}
}
SequenceSelector以private修饰,实现了Selector接口,提供了方法的具体实现。items,可以得出结论:内部类自动拥有对其外部类所有成员的访问权。当内部类是非static时,当外部类对象创建了一个内部类对象时,内部类对象会产生一个指向外部类的对象的引用,所以非static内部类可以看到外部类的一切。
Sequence的selector方法返回了一个内部类实例,意思就是用接口类型接收实现类的实例,实现向上转型,既隐藏了实现细节,又利于扩展。我们看一下具体的测试方法:
public static void main(String[] args) {
Sequence sq = new Sequence(10);
for (int i = 0; i < 10; i++) {
sq.add(Integer.toString(i));
}
//产生我们设计的选择器
Selector sl = sq.selector();
while (!sl.end()) {
System.out.print(sl.current() + " ");
sl.next();
}
}
我们稍微修改一下最初的Outer:
public class Outer {
String id = "乔巴";
class Inner{
private String id = "夏天";
public String getId() {
return id;
}
public String getOuterId(){
return Outer.this.id;
}
public Outer returnOuter(){
return Outer.this;
}
}
public static void main(String[] args) {
Outer o = new Outer();
System.out.println(o.new Inner().getId());//夏天
System.out.println(o.new Inner().getOuterId());//乔巴
}
}
return Outer.this;,即外部类名.this。Outer.this.id。我们来测试一波:
public static void main(String[] args) {
Outer.Inner oi = new Outer().new Inner();
System.out.println(oi.getId());//夏天
Outer o = oi.returnOuter();
System.out.println(o.id);//乔巴
}
Outer.Inner oi = new Outer().new Inner();,即在外部类对象后面用.new 内部类构造器。我们对内部类指向外部类对象的引用进行更加深入的理解与体会,我们会发现,上面的代码在编译之后,会产生两个字节码文件:
Outer$Inner.class和Outer.class。我们对Outer$Inner.class进行反编译:
确实,内部类在创建的过程中,依靠外部类对象,而且会产生一个指向外部类对象的引用。
即在方法作用域内创建一个完整的类。
public class Outer {
public TestOuter test(final String s){
class Inner implements TestOuter{
@Override
public void testM() {
//!false: s+="g";
System.out.println(s);
}
}
return new Inner();
}
public static void main(String[] args) {
Outer o = new Outer();
o.test("天乔巴夏").testM();//天乔巴夏
}
}
interface TestOuter{
void testM();
}
需要注意两点:
JDK1.8之后可以不用final显式修饰传入参数和局部变量,但其本身还是相当于final修饰的,不可改变。我们去掉final,进行反编译:
可以将内部类定义在任意的作用域内:
public class Outer {
public void test(final String s,final int value){
final int a = value;
if(value>2){
class Inner{
public void testM() {
//!false: s+="g";
//!false: a+=1;
System.out.println(s+", "+a);
}
}
Inner in = new Inner();
in.testM();
}
//!false:Inner i = new Inner();
}
public static void main(String[] args) {
Outer o = new Outer();
o.test("天乔巴夏",3);
}
}
同样需要注意的是:
即用static修饰的成员内部类,归属于类,即它不存在指向外部类的引用。
public class Outer {
static int a = 5;
int b = 6;
static class Inner{
static int value;
public void show(){
//!false System.out.println(b);
System.out.println(a);
}
}
}
class OuterTest {
public static void main(String[] args) {
Outer.Inner oi = new Outer.Inner();
oi.show();
}
}
需要注意的是:
new Outer.Inner()创建内部类对象。static final修饰)。
这个类型的内部类,看着名字就怪怪的,我们先看看一段违反我们认知的代码:
public class Outer {
public InterfaceInner inner(){
//创建一个实现InterfaceInner接口的是实现类对象
return new InterfaceInner() {
@Override
public void show() {
System.out.println("Outer.show");
}
};
}
public static void main(String[] args) {
Outer o = new Outer();
o.inner().show();
}
}
interface InterfaceInner{
void show();
}
真的非常奇怪,乍一看,InterfaceInner是个接口,而Outer类的inner方法怎么出现了new InterfaceInner()的字眼呢?接口不是不能创建实例对象的么?
确实,这就是匿名内部类的一个使用,其实inner方法返回的是实现了接口方法的实现类对象,我们可以看到分号结尾,代表一个完整的表达式,只不过表达式包含着接口实现,有点长罢了。所以上面匿名内部类的语法其实就是下面这种形式的简化形式:
public class Outer {
class Inner implements InterfaceInner{
@Override
public void show(){
System.out.println("Outer.show");
}
}
public InterfaceInner inner(){
return new Inner();
}
public static void main(String[] args) {
Outer o = new Outer();
o.inner().show();
}
}
interface InterfaceInner{
void show();
}
不仅仅是接口,普通的类也可以被当作“接口”来使用:
public class Outer {
public OuterTest outerTest(int value) {
//参数传给匿名类的基类构造器
return new OuterTest(value) {
@Override
public int getValue() {
return super.getValue() * 10;
}
};
}
public static void main(String[] args) {
Outer o = new Outer();
System.out.println(o.outerTest(10).getValue());//100
}
}
class OuterTest {
public int value;
OuterTest(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
需要注意的是:
内部类可以被继承,但是和我们普通的类继承有些出处。具体来看一下:
public class Outer {
class Inner{
private int value = 100;
Inner(){
}
Inner(int value){
this.value = value;
}
public void f(){
System.out.println("Inner.f "+value);
}
}
}
class TestOuter extends Outer.Inner{
TestOuter(Outer o){
o.super();
}
TestOuter(Outer o,int value){
o.super(value);
}
public static void main(String[] args) {
Outer o = new Outer();
TestOuter tt = new TestOuter(o);
TestOuter t = new TestOuter(o,10);
tt.f();
t.f();
}
}
我们可以发现的是:
class A extends Outer.Inner{}。o.super();,即都需要传入外部类对象作为参数。可以看到的一点就是,内部类内部的实现细节可以被很好地进行封装。而且Java中存在接口的多实现,虽然一定程度上弥补了Java“不支持多继承”的特点,但内部类的存在使其更加优秀,可以看看下面这个例子:
//假设A、B是两个接口
class First implements A{
B makeB(){
return new B() {
};
}
}
这是一个通过匿名内部类实现接口功能的简单的例子。对于接口而言,我们完全可以通过下面这样进行,因为Java中一个类可以实现多个接口:
class First implements A,B{
}
但是除了接口之外,像普通的类,像抽象类,都可以定义独立的内部类去单独继承并实现,使用内部类使“多重继承”更加完善。
由于后面的许多内容还没有涉及到,学习到,所以总结的比较浅显,并没有做特别深入,特别真实的场景模拟,之后有时间会再做系统性的总结。如果有叙述错误的地方,还望评论区批评指针,共同进步。
参考:
《Java 编程思想》
https://stackoverflow.com/questions/70324/java-inner-class-and-static-nested-class?r=SearchResults
原文:https://www.cnblogs.com/summerday152/p/12245849.html