将一个类的定义放在另一个类的内部,就是内部类。内部类与组合是完全不同的概念,最初看来内部类仿佛是一种代码隐藏机制,但内部类不仅了解外围类,并能与之通信,借助内部类可以写出更加优雅的代码。
1.内部类与外部类的通信
public class OuterClass {
private Object[] objects;
public OuterClass(int size){
objects=new Object[size];
}
public InnerClass getInnerClass(){
return new InnerClass();
}
public static InnerClass innerClass(){
OuterClass.InnerClass innerClass= new OuterClass(10).new InnerClass();
return innerClass;
}
class InnerClass{
public OuterClass getOuterClass(){
return OuterClass.this;
}
public Object getObject(int i){
if(i>objects.length){
return null;
}
return objects[i];
}
}
}
2.匿名内部类
2.1匿名内部类竟可以扩展类(实现抽象类),也可以实现接口,但是不能两者兼备,而且如果是实现接口,只能实现一个接口,不能实现多个;
interface InnerClass{
String getStr();
}
public class OuterClass {
public InnerClass getInnerClass(final String str){
return new InnerClass(){
private String string=str;
@Override
public String getStr() {
return string;
}
};
}
public static void main(String[] args){
OuterClass oc=new OuterClass();
InnerClass ic=oc.getInnerClass("test");
System.out.println(ic.getStr());
}
}
2.2如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数是final修饰的;
其实不仅仅匿名内部类,方法和作用域内的内部类内部使用的外部变量也必须是final修饰的;后面匿名内部类与方法和作用域内的内部类统称局部内部类(但是从java8开始,可以不加final修饰符,由系统默认添加。java将这个功能称为:Effectively final 功能);
interface InnerClass{
Integer addXYZ();
}
public class OuterClass {
public InnerClass getInnerClass(final Integer x){
final Integer y=100;
return new InnerClass(){
private Integer z=100;
@Override
public Integer addXYZ() {
return x+y+z;
}
//public void changeY(){y+=1;}
};
}
public static void main(String[] args){
OuterClass oc=new OuterClass();
InnerClass ic=oc.getInnerClass(100);
System.out.println(ic.addXYZ());
}
}
为什么必须用final修饰呢?
问题的关键在于局部变量(方法参数)的生命周期与局部内部类的对象的生命周期的不一致性,而用final修饰就是为了保证局部变量与匿名内部类中取到的变量值的一致性;
从内存角度简单分析:程序运行在JVM中时,方法相当于线程栈的栈帧,方法的局部变量位于该栈帧上,而内部类对象实例位于堆上。当方法结束时,栈帧被弹出,该变量消失。但是,定义在这个类中的内部类对象仍然存活在堆上,所以两者生命周期不同导致了这种不一致性。
那么局部内部类又是怎样实现访问局部变量的呢?
实际上java是将局部变量作为参数传给了局部内部类的构造函数,而将其作为内部类的成员属性封装在了类中。相当于是做了一份拷贝,所以我们看到的内部类访问局部变量实际上只是访问了自己的成员属性而已。
因此当栈帧中的真正的局部变量死亡时,局部内部类对象仍可以访问局部变量(其实访问的是拷贝之后的成员属性),给人的感觉像是局部变量的"生命期"延长了。
同时为了避免外部方法修改局部变量而导致内部类得到的拷贝与之不一致 或者内部类修改拷贝而导致外部方法的参数值与之不一致。于是就用 final 来让该形参不可改变。保证原始值与拷贝值的一致性(基本数据类型值一致,引用类型引用一致)。
3.匿名内部类实现工场模式
interface Service{
void method();
}
interface ServiceFactory{
Service getService();
}
class Implementation1 implements Service{
@Override
public void method() {
System.out.println("Implementation1");
}
public static ServiceFactory factory=new ServiceFactory(){
@Override
public Service getService() {
return new Implementation1();
}
};
}
class Implementation2 implements Service{
@Override
public void method() {
System.out.println("Implementation2");
}
public static ServiceFactory factory=new ServiceFactory(){
@Override
public Service getService() {
return new Implementation2();
}
};
}
public class Factories {
public static void main(String[] args){
Implementation1.factory.getService().method();
Implementation2.factory.getService().method();
}
}
4.嵌套类
简单来说,嵌套类就是static修饰的内部类。普通的内部类对象隐式的保存了一个引用指向创建它的外围类对象,而嵌套类不是这样。
4.1创建嵌套类对象并不需要通过其外围类对象来创建;
4.2嵌套类的对象中不能访问非静态的外围类对象;
4.3可以包含static方法和字段,不能使用外围类的this引用;
5.内部类class文件命名规则
interface InnerClass {
Integer addXYZ();
}
public class OuterClass {
public InnerClass getInnerClass(final Integer x) {
final Integer y = 100;
return new InnerClass() {
private Integer z = 100;
@Override
public Integer addXYZ() {
return x + y + z;
}
};
}
public InnerClass getInnerClass2(final Integer x) {
final Integer y = 100;
return new InnerClass() {
private Integer z = 100;
@Override
public Integer addXYZ() {
return x + y + z;
}
};
}
class InnerClassTest {
}
public void test() {
class MethodInner {}
}
public void test2() {
class MethodInner {}
}
{
class MethodInner {}
}
public static class staticClass{}
/*public static void main(String[] args) {
OuterClass oc = new OuterClass();
InnerClass ic = oc.getInnerClass(100);
System.out.println(ic.addXYZ());
}*/
}
5.1嵌套类/内部类的class文件命名是:外围类名+$+内部类名
5.2匿名内部类的class文件命名是:外围类名+$+(1、2、3...)
5.3方法和作用域内的内部类命名:外围类名+$+(1、2、3...)+内部类名
6.内部类优缺点
优点:可以封装隐藏操作代码,精简代码;匿名内部类使用方便;多个内部类实现不同接口,间接实现多继承;
缺点:代码结构重用率低,会生成多个class文件;