从名字上解释,内部类就是在一个类的内部又定义了一个类,相对来说外面的那个类就是外围类了。
为什么要这么做呢,内部类有自己的一些特点,能起到跟外围类不一样的效果:
①内部类可以访问定义该类定义所在的作用域中的数据,包括私有数据。前面一句有点拗口,就是内部类跟我们平时定义变量一样,有一个作用域,比如代码块A不能访问代码块B中的变量,内部类也是类似,可以访问自己所在作用域中的所有数据,包括私有,这样看起来就是一个类访问了另一个类的私有数据了。
②内部类可以对同一个包中的其他类不可见。内部类既然定义在一个类的内部,那么就跟类里面的阈和方法一样可以声明访问权限,private、默认、protected、public,而外围类只能是public公有可见性或者默认包可见,同一个包里的类总是相互可见的,而内部类就可以隐藏起来了。
③使用匿名内部类可以便捷编写回调函数(这里可以用lambda代替了)。当我们需要在一个方法里,用到一个实现某个接口的类的对象,而且只创建一个,使用匿名内部类可以直接创建这个对象,而不需要先去定义类再去new对象,下面会举例子来演示。
既然是定义在一个类内部的类,回想类内部变量的情况,有成员变量,也有方法体里的局部变量,内部类也是类似,根据内部类定义的位置不同,可以有成员内部类和局部内部类。
①成员内部类
我们假设在War3这个类里面有Human、Undead、Orc和NightElf四个内部类,War3这个类有一个状态就是当前游戏版本,四个内部类都有打印当前版本的方法,我们写代码测试一下;
package dailyprg0717;
public class War3 {
private String version;
public War3(String version){
this.version = version;
}
public class Human{
public void print(){
System.out.println(version);
}
}
protected class Undead{
public void print(){
System.out.println(version);
}
}
class Orc{
public void print(){
System.out.println(version);
}
}
private class NightElf{
public void print(){
System.out.println(version);
}
}
}
可见内部类可以声明为private,而且可以引用War3对象的域,那对于公有内部类,我们怎么从外部构造它的对象呢,我们接着试验,写一个Test,上面的Human是公有访问的。
package dailyprg0717;
public class Test {
public static void main(String[] args) {
War3 myWar3 = new War3("1.24e");
War3.Human human = myWar3.new Human();
human.print();
}
}
运行一下
没问题
为什么要这么定义?
我们在写的时候,比较智能的编辑器也会提醒
在外围类的作用域之外,要用OuterClass.InnerClass来引用内部类,也就是War3.Human
而在调用内部类构造函数的时候,用的是 外围类对象.new 内部类构造器(),为什么呢,前面说到内部类可以访问外围类对象的域,理解起来就是一个外围类可以有多个对象,一个内部类是跟哪个对象挂钩的呢,这里就需要一个外围类对象的引用,而且确实内部类的对象总有一个隐式引用,它指向了创建它的外围类对象。
此外,内部类中所有的static域都必须是final的,原因跟上面类似,一个静态域应该只有一个实例,外围类的多个对象new内部类出来,如果不是final的,静态域就可能不是唯一的。
内部类如果有静态方法,那么它只能访问外围类的静态域和方法。(这个不知道为啥)
②局部内部类
局部内部类是定义在一个方法中的类,局部内部类不能用public或者private进行声明。因为它的作用域就是在这个方法块中,还声明啥。
局部内部类跟成员内部类相比就是它能多访问这个方法块中的局部变量
package dailyprg0717;
public class War3 {
private String version;
public War3(String version){
this.version = version;
}
public void Begin(int level){
class Computer{
public void print(){
System.out.println("Game version is "+version);
System.out.println("Computer level is "+ level);
}
}
new Computer().print();
}
}
做下测试
package dailyprg0717;
public class Test {
public static void main(String[] args) {
War3 myWar3 = new War3("1.24e");
// War3.Human human = myWar3.new Human();
// human.print();
myWar3.Begin(3);
}
}
结果OK
这里有点就是在Java SE 8 之前,必须把从局部内部类访问的局部变量声明为final。使得局部变量与在局部变量内建立的拷贝保持一致。(这里不太清楚)
③匿名内部类
匿名内部类是局部内部类的另一种形式,免去定义类的过程,其语法格式为
new 父类或者接口(构造器参数){
内部类的域和方法
}
匿名类没有构造器,因为它没名字嘛,构造器需要跟类名一样,如果是父类就会把参数给父类构造器,如果是接口就不能有构造参数了。这里有点绕哈,就当做先创建了内部类然后再new的吧,只是名字你不知道罢了,但是要在声明的时候就创建对象哟,没有构造器以后就不知道怎么创建了。
④静态内部类
跟静态域或者说叫静态成员变量(我记得C++里面喜欢说成员变量)来比较下,静态域说的是这个域是属于类而不是属于对象的,前面说过内部类可以访问外围类对象的域,如果它设计的时候就不是为了访问外围类对象的域,就是想躲在里面罢了,那么就可以把这个内部类声明为static了,就是表明我跟外围类对象没关系。
这样静态内部类的实例就可以在它的外围类实例之外独立存在。
因为这个类是静态的,那么他就不随外围类的对象波动了,它就可以有静态域和静态方法了。
申明在接口中的类会自动成为static和public类,接口没有实例撒。
不过静态内部类是可以访问外围类的静态域或方法的,如果你非要这么干的话。
当然也只有内部类可以声明为static啦。
关于内部类的使用,参考《Effective Java》作者的建议,
内部类的存在应该只是为它的外围类提供服务,如果这个内部类可能会用于其他的某个环境中,为什么不将他定义为顶层类呢?
静态成员内部类的一种常见用法是作为公有的辅助类,通过外围类来引用这个静态类来对外围类提供服务。
非静态成员类的一种常见用法是定义一个Adapter,比如Iterator迭代器的实现。
匿名内部类的一种常见用法是动态的创建函数对象,而且匿名内部类除了在它们被声明的时候之外,是无法被实例化的。
局部内部类其实用的比较少,但它与其他三种内部类中的每一种都有一些共同的属性。
总之,如果一个内部类需要在单个方法之外可见,或者太长不适合放方法内部,就应该让它成为成员内部类。如果成员内部类依赖外围类的实例,那就把它做成非静态的,否则就做成静态的。再如果这个内部类属于一个方法内部,而且只需要一个实例,而且已经有一个类可以说明这个类的特征(父类或者接口),就做成匿名类,否则,就做成局部类。