1.什么是内部类
简单的说就是在一个类、接口或者方法的内部创建另一个类。这样理解的话创建内部类的方法就很明确了。
package com.gissky.innerClass; public class Path { public class Start{ public Start(String start){ this.start=start; } private String start; public String getStart() { return start; } } public class End{ public End(String end){ this.end=end; } private String end; public String getEnd() { return end; } } }
2.内部类的实例化
2.1 在外部类的非静态方法内部实例化,这个很简单,和我们平时做的一样。
public Start start(String start){ return new Start(start); } public End end(String end){ return new End(end); }
2.2 在外部类的非静态方法外部的任意位置创建内部类对象的时候,必须具体指明这个对象的类型,即:OuterClassName.InnerClassName,并且必须在有外部类对象的前提下进行,使用类似:外围类对象.new 内部类();这样的语法。
public static void main(String[] args){ Path path=new Path(); Path.Start start=path.new Start("my home"); Path.End end=path.new End("fu zhou"); path.Go(start, end); }
3. 内部类有一个指向外部类的对象的引用
内部类拥有其外部类的所有元素的访问权。这是如何做到的呢?修改一下上面的程序:
public class Path { private double startTime; private double endTime; public class Start{ public Start(String start){ startTime=new Date().getTime(); this.start=start; } private String start; public String getStart() { return start; } } public class End{ public End(String end){ this.end=end; endTime =new Date().getTime(); } private String end; public String getEnd() { return end; } } public void Go(Start start,End end){ System.out.println("start to end"); System.out.println(endTime-startTime); } public Start start(String start){ return new Start(start); } public End end(String end){ return new End(end); } }
这是编译器已经帮我们生成了3个.class文件:
前两个便是编译器生成的内部类的.class文件,奇怪的是它们是以其外部类开头再加上$和自己类的名字。我们用javap反编译工具查看一下:
由此我们便可以知道,内部类中访问外部类的成员变量是通过一个调用外部类的一个静态方法进行的,该静态方法须传入一个外围类的引用。
到这里应该明白2.2中提到的“在外部类的非静态方法外部的任意位置创建内部类对象的时候,必须具体指明这个对象的类型,即:OuterClassName.InnerClassName,并且必须在有外部类对象的前提下进行”的原因了吧。那是因为,内部类要连接到创建它的外部类对象上。
这样做不是存在安全风险吗?这种担心是很有道理的。任何人都可以通过调用access$0方法很容易地读取到私有域beep。当然,access$0不是Java的合法方法名。但熟悉类文件结构的黑客可以使用十六进制编辑器轻松地创建一个用虚拟机指令调用那个方法的类文件。由于隐秘地访问方法需要拥有包可见性,所以攻击代码需要与被攻击类放在同一个包中。
总而言之,如果内部类访问了私有数据域,就有可能通过附加在外围类所在包中的其他类访问它们,但做这些事情需要高超的技巧和极大的决心。程序员不可能无意之中就获得对类的访问权限,而必须刻意地构建或修改类文件才有可能达到这个目的。
4. 静态内部类
首先静态内部类没有像普通内部类那么多的限制了,它就不需要对外部类对象的引用。
有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为static,以便取消产生的引用。
注释:在内部类不需要访问外围类对象的时候,应该使用静态内部类。有些程序员用嵌套类(nested class)表示静态内部类。
注释:声明在接口中的内部类自动成为static和public。
5. 内部类与向上转型
public interface Fly { public void fly(); }
public class Animal { private class Bird implements Fly{ @Override public void fly() { System.out.println("Bird.flu()"); } } public Fly Bfly(){ return new Bird(); } public static void main(String[] args) { Animal animal=new Animal(); Fly fly=animal.Bfly(); fly.fly(); } }
由上述代码可知,我们无法将Fly接口向下转型,因为我们无法知道内部类Bird的名字(它是private)。因此private内部类可以完全阻止任何依赖于类型的编码。客户端不能访问到任何新加在内部类中的接口,所以扩展接口是没有价值的(阻止了is-like-a形式的出现)。并且由于内部类是private的,因此它对于客户端来说是完全不可见的,并且不可用。所得到的知识指向基类或接口的引用,方便的隐藏了实现细节。
6. 局部内部类
即,定义在方法和作用域内的内部类。使用理由:在你的方法或作用域内由于某种原因要使用一个类来辅助你的程序,但是你又不想这个类是公用的。
如,这样一个需求:我进行一个评价工作,我选取了指标,然后根据不同的指标让用户选择不同的模型来计算该指标的评价值。这样一来,我就需要记录:指标名称、模型名称、模型参数。然后通过反射来计算。实际工作中我这样设计:
public InfModel Execute(InfModel infModel, String measure, Users user) { //region 内部类--用于构造各个指标所使用的模型及参数 class Model{ public Method execute; public Object intance; public String modelParam; /**构造器 * @param execute 模型执行的方法接口 * @param intance 模型实例 * @param modelParam 构造模型所需参数 */ public Model(Method execute,Object intance, String modelParam){ this.modelParam=modelParam; this.execute=execute; this.intance=intance; } } //endregion //后续工作 }
局部类不能用public或private访问说明符进行声明。它的作用域被限定在声明这个局部类的块中。
局部类有一个优势,即对外部世界可以完全地隐藏起来。除Execute方法之外,没有任何方法知道Model类的存在。
7.匿名内部类
public class AnonymousInnerClass { public Destination destination(final String dest){ return new Destination() { @Override public String readLabel() { return dest; } }; } public static void main(String[] args){ AnonymousInnerClass aic=new AnonymousInnerClass(); System.out.println(aic.destination("天涯海角").readLabel()); } }
由上面的代码可以看出,匿名内部类也就是没有类名的一个内部类。因此不要向匿名内部类中添加其基类中没有的接口(因为加上也是白加,外面是访问不到的)。但是为了实现的需要可以添加一些私有的成员。同时我们也注意到,内部类内部希望使用一个在其外部定的对象时,编译器则要求其参数应用必须是final的。
为什么这个参数必须为final的呢?为此我们仔细地考查一下控制流程。
- 调用destination方法;
- 调用new Destination() 创造内部类;
- destination方法结束,此时,destination方法的dest参数已经不存在了。
- 然后,在以后的某个地方Destination调用readLabel方法,return dest。
这时在内部类中产生了“闭包”,闭包将使得dest脱离了它所在的方法继续存在,这样在readLabel方法中就可以任意的修改dest了,但是外部类对此却全然不知。因此,dest必须是final的。
其实为了能够让readLabel方法工作,Destination类(这里是一个匿名内部类,编译器给它取的名字为AnonymousInnerClass$1.class)在dest域释放之前将dest域在自己的构造器中进行备份。
上面的例子对于接口或者一个还有无参构造器的类来说是可行的。但是当一个类的构造器有参数的时候该怎么办呢?很简单,就像我们new一个有参构造器的类一样来构造即可:
public abstract class Base { { System.out.println("Base instane initializer"); } public Base(int i){ System.out.println("Base constructor, i="+i); } public abstract void f(); }
public class AnonymousInnerClass { public static Base getBase(int i){ return new Base(i) { { System.out.println("Inside instane initializer"); } @Override public void f() { System.out.println("In AnonymousInnerClass f()"); } }; } public static void main(String[] args){ Base base=getBase(5); base.f(); } }
我们发现这里的变量i没有要求必须是final的,这是为什么呢?因为,这是变量i不是在内部类内部使用,而是被传递给了其基类的构造函数,它并不会在匿名内部类中被直接使用。
(☞'ω')☞注:要不要加final就是看局部内部类的外围方法结束后,其参数是不是还要被局部内部类使用,如果还要被使用,则要加final否则就不用加了。