我爱学Java之内部类
2016-04-11 00:28
447 查看
在Java中,可以将一个类的定义放在另一个类的内部,这就是内部类。
内部类可以被private,default,protected,public修饰,且内部类是编译器全权负责的,虚拟机并不知道内部类与常规类有什么不同,一旦编译成功,就会与外部类成为完全不同的两类,所以内部类的成员变量和成员方法可以与外部类相同 。
内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类,下面就了解一下这四种内部类的用法:
1.成员内部类:位于另一个类的内部且没有用static修饰的最普通的内部类。
(1)成员内部类可以直接使用外部类的成员变量和方法,是因为用外围类创建内部类对象时,编译器会默认给内部类构造器添加一个参数,该参数的类型为指向外部类对象的一个引用。从这里也间接说明了成员内部类是依赖于外部类的,如果没有创建外部类的对象,也就无法创建成员内部类的对象了。
(2)外部类要访问成员内部类的所有成员变量和方法,需要通过成员内部类的对象来获取,如想从外部类非静态方法之外的任意位置创建某个成员内部类对象,则需要先创建外部类对象,再通过OutClass.InnerClass指命类型,再用OutClass.new完成成员内部类对象创建。
(3)在成员内部类中要引用外部类对象时,使用OuterClass.this来表示外部类对象。
(4)成员内部类不能含有static的变量和方法,原因是:非static的内部类在外部类加载的时候并不会加载它,而static类型的属性和方法,在类加载的时候就会存在于内存中,也就是说要使用某个类的static属性或者方法,那么这个类必须要加载到jvm中,所以说如果一个非static的内部类如果具有static的属性或者方法,那么就会出现内部类未加载但却试图在内存中创建static的属性和方法,因此它里面不能有静态变量或者静态方法。
2.局部内部类:是定义在一个方法或者一个作用域里面的类,也可以访问外部类成员,局部内部类也像别的类一样进行编译,但与成员内部类不同的是它的访问仅限于方法内或者该作用域内。局部内部类,就像局部变量一样,在定义的方法体外不能创建局部内部类的对象,并且不能有public、protected、private以及static修饰符的。
(1)定义在方法内:
(2)定义在作用域内:
3.嵌套内部类(静态内部类):就是修饰为static的内部类。
(1)静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
(2)声明为static的内部类,不需要内部类对象和外部类对象之间的联系,就是说我们可以直接引用outer.inner。
(3)普通内部类不能有static数据和static属性,也不能包含嵌套类,但嵌套类可以,并且嵌套类一般不声明为private,而声明为public,方便调用。
4.匿名内部类:定义类的最终目的是创建一个类的实例,但是如果某个类的实例只是用一次,则可以将类的定义与类的创建,放到与一起完成,或者说在定义类的同时就创建一个类,以这种方法定义的没有名字的类成为匿名内部类。
(1) 匿名内部类必须继承一个抽象类或一个普通类或实现一个接口,但匿名内部类不能同时实现一个接口和继承一个类,也不能实现多个接口。
(2) 由于匿名内部类没有名称,所以类体中不能定义构造方法,由于不知道类名也不能使用关键字来创建该类的实例。实际上匿名内部类的定义、构造、和第一次使用都发生在同样一个地方。
注:当所在的方法的形参需要被内部类里面使用时,该形参必须为final,但在jdk1.8中,final可以省略,但参数值还是不可变的,否则编译会报错。必须用final的原因是:
首先,内部类被编译的时候会生成一个单独的内部类的.class文件,这个文件并不与外部类在同一class文件中。 当外部类传的参数被内部类调用时,从Java程序的角度来看是直接的调用,但实际上并不是,内部类并不是直接调用方法传进来的参数,而是内部类将传进来的参数通过自己的构造器备份到了自己的内部,自己内部的方法调用的实际是自己的属性而不是外部类方法的参数。 这样理解就很容易得出为什么要用final了,如果内部类改掉了这些参数的值也不可能影响到原参数,然而这样却失去了参数的一致性,为了避免这种尴尬的问题存在,所以编译器设计人员把内部类能够使用的参数设定为必须是final来规避这种莫名其妙错误的存在。
(简单理解就是,拷贝引用,为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,于是用final来让该引用不可改变)
因为匿名内部类,没名字,是用默认的构造函数的,无参数的,那如果需要参数呢?则需要该类有带参数的构造函数:
而匿名内部类通过实例初始化,可以达到类似构造器的效果:
内部类继承:指内部类被普通类继承
子类的构造函数里面要使用父类的外部类对象.super();而这个对象需要从外面创建并传给形参。
“`
public class OuterClass{
}
class Son extends OuterClass.InnerClass{
}
内部类标识符:
每个类会产生一个.class文件,文件名即为类名,同样,内部类也会产生这么一个.class文件,但是它的名称却不是内部类的类名,而是有着严格的限制:外围类的名字,加上$,再加上内部类名字,而如果是匿名内部类,则是$再加上1,…..,n。
内部类的使用场景和好处:
1.每个内部类都能独立的继承一个接口,无论外部类是否已经继承了某个接口实现,对于内部类都没有影响。
2.方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
3.在实际应用中,需要多继承,如果是两个接口,那么可以直接实现两个接口,而如果是两个类,那么就只有使用内部类了, 因此,内部类使多重继承的解决方案变得更加完整。
内部类可以被private,default,protected,public修饰,且内部类是编译器全权负责的,虚拟机并不知道内部类与常规类有什么不同,一旦编译成功,就会与外部类成为完全不同的两类,所以内部类的成员变量和成员方法可以与外部类相同 。
内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类,下面就了解一下这四种内部类的用法:
1.成员内部类:位于另一个类的内部且没有用static修饰的最普通的内部类。
(1)成员内部类可以直接使用外部类的成员变量和方法,是因为用外围类创建内部类对象时,编译器会默认给内部类构造器添加一个参数,该参数的类型为指向外部类对象的一个引用。从这里也间接说明了成员内部类是依赖于外部类的,如果没有创建外部类的对象,也就无法创建成员内部类的对象了。
(2)外部类要访问成员内部类的所有成员变量和方法,需要通过成员内部类的对象来获取,如想从外部类非静态方法之外的任意位置创建某个成员内部类对象,则需要先创建外部类对象,再通过OutClass.InnerClass指命类型,再用OutClass.new完成成员内部类对象创建。
(3)在成员内部类中要引用外部类对象时,使用OuterClass.this来表示外部类对象。
(4)成员内部类不能含有static的变量和方法,原因是:非static的内部类在外部类加载的时候并不会加载它,而static类型的属性和方法,在类加载的时候就会存在于内存中,也就是说要使用某个类的static属性或者方法,那么这个类必须要加载到jvm中,所以说如果一个非static的内部类如果具有static的属性或者方法,那么就会出现内部类未加载但却试图在内存中创建static的属性和方法,因此它里面不能有静态变量或者静态方法。
public class OuterClass { class InnerClass{ public void test(){ System.out.println("HelloWorld"); } public OuterClass getOuter(){ return OuterClass.this; } } public InnerClass getInner(){ return new InnerClass(); } public static void main(String[] args){ OuterClass out = new OuterClass(); OuterClass.InnerClass inner = out.new InnerClass(); OuterClass.InnerClass inner1 = inner.getOuter().getInner();//也可以这样获取成员内部类对象 inner.test(); inner1.test(); } }//都输出HelloWorld
2.局部内部类:是定义在一个方法或者一个作用域里面的类,也可以访问外部类成员,局部内部类也像别的类一样进行编译,但与成员内部类不同的是它的访问仅限于方法内或者该作用域内。局部内部类,就像局部变量一样,在定义的方法体外不能创建局部内部类的对象,并且不能有public、protected、private以及static修饰符的。
(1)定义在方法内:
public class OuterClass { private String test = "HelloWorld"; public String getString(){ class InnerClass{ public String test(){ return test; } } return new InnerClass().test(); } public static void main(String[] args){ OuterClass outer = new OuterClass(); System.out.println(outer.getString()); } }//输出HelloWorld
(2)定义在作用域内:
public class OuterClass { private String test = "HelloWorld"; public String getString(boolean b){ if(b){ class InnerClass{ public String test(){ return test; } } return new InnerClass().test(); } return null; } public static void main(String[] args){ OuterClass outer = new OuterClass(); System.out.println(outer.getString(true)); } }//输出HelloWorld
3.嵌套内部类(静态内部类):就是修饰为static的内部类。
(1)静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
(2)声明为static的内部类,不需要内部类对象和外部类对象之间的联系,就是说我们可以直接引用outer.inner。
(3)普通内部类不能有static数据和static属性,也不能包含嵌套类,但嵌套类可以,并且嵌套类一般不声明为private,而声明为public,方便调用。
public class OuterClass { private static String test = "Hello"; public static class InnerClass{ private String s = "World"; private static String ss = "HelloWorld"; public void print(){ System.out.println(test+s); } public static void print1(){ System.out.println(ss); } } public static void main(String[] args){ OuterClass.InnerClass inner = new OuterClass.InnerClass(); inner.print(); System.out.println(OuterClass.InnerClass.ss); OuterClass.InnerClass.print1(); } }//输出三个HelloWorld
4.匿名内部类:定义类的最终目的是创建一个类的实例,但是如果某个类的实例只是用一次,则可以将类的定义与类的创建,放到与一起完成,或者说在定义类的同时就创建一个类,以这种方法定义的没有名字的类成为匿名内部类。
(1) 匿名内部类必须继承一个抽象类或一个普通类或实现一个接口,但匿名内部类不能同时实现一个接口和继承一个类,也不能实现多个接口。
(2) 由于匿名内部类没有名称,所以类体中不能定义构造方法,由于不知道类名也不能使用关键字来创建该类的实例。实际上匿名内部类的定义、构造、和第一次使用都发生在同样一个地方。
public class OuterClass { public InnerClass getInner(final String s){ return new InnerClass(){ public void print(){ System.out.println(s); } }; } public static void main(String[] args){ new OuterClass().getInner("HelloWorld").print(); } } interface InnerClass{ public void print(); }//输出HelloWorld
注:当所在的方法的形参需要被内部类里面使用时,该形参必须为final,但在jdk1.8中,final可以省略,但参数值还是不可变的,否则编译会报错。必须用final的原因是:
首先,内部类被编译的时候会生成一个单独的内部类的.class文件,这个文件并不与外部类在同一class文件中。 当外部类传的参数被内部类调用时,从Java程序的角度来看是直接的调用,但实际上并不是,内部类并不是直接调用方法传进来的参数,而是内部类将传进来的参数通过自己的构造器备份到了自己的内部,自己内部的方法调用的实际是自己的属性而不是外部类方法的参数。 这样理解就很容易得出为什么要用final了,如果内部类改掉了这些参数的值也不可能影响到原参数,然而这样却失去了参数的一致性,为了避免这种尴尬的问题存在,所以编译器设计人员把内部类能够使用的参数设定为必须是final来规避这种莫名其妙错误的存在。
(简单理解就是,拷贝引用,为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,于是用final来让该引用不可改变)
因为匿名内部类,没名字,是用默认的构造函数的,无参数的,那如果需要参数呢?则需要该类有带参数的构造函数:
public class OuterClass{ public InnerClass getInner(final String name){ return new InnerClass(name){ public String getName() { return name; } }; } public static void main(String[] args) { new OuterClass().getInner("HelloWorld").getName(); } } //不能是接口,可以为抽象类或普通类 abstract class InnerClass{ InnerClass(String name) { System.out.println(name); } abstract String getName(); } //输出HelloWorld
而匿名内部类通过实例初始化,可以达到类似构造器的效果:
public class OuterClass{ public InnerClass getInner(final String name){ return new InnerClass(){ private String sName; public String getName() { sName = name + "World"; return sName; } }; } public static void main(String[] args) { System.out.println(new OuterClass().getInner("Hello").getName()); } } //不能是接口,可以为抽象类或普通类 interface InnerClass{ String getName(); } //输出HelloWorld
内部类继承:指内部类被普通类继承
子类的构造函数里面要使用父类的外部类对象.super();而这个对象需要从外面创建并传给形参。
“`
public class OuterClass{
class InnerClass{ }
}
class Son extends OuterClass.InnerClass{
Son(OuterClass outer){ outer.super(); } public static void main(String[] args) { OuterClass outer = new OuterClass(); Son son = new Son(outer); }
}
内部类标识符:
每个类会产生一个.class文件,文件名即为类名,同样,内部类也会产生这么一个.class文件,但是它的名称却不是内部类的类名,而是有着严格的限制:外围类的名字,加上$,再加上内部类名字,而如果是匿名内部类,则是$再加上1,…..,n。
内部类的使用场景和好处:
1.每个内部类都能独立的继承一个接口,无论外部类是否已经继承了某个接口实现,对于内部类都没有影响。
2.方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
3.在实际应用中,需要多继承,如果是两个接口,那么可以直接实现两个接口,而如果是两个类,那么就只有使用内部类了, 因此,内部类使多重继承的解决方案变得更加完整。
相关文章推荐
- Eclipse使用技巧
- Java-单机版的书店管理系统(练习设计模块和思想_系列 四(1) )
- 20145101《Java程序设计》第6周学习总结
- 2016年最新spring4框架搭建视频教程
- java shop 介绍
- java 数组复制
- Java对象的序列化和反序列化
- Java语言基础(5)
- JavaSE核心——各种类
- Java中switch支持字符串原理
- Java启动时默认创建了多少线程
- Java官方版本动态代理
- Spring的意义
- 安装JavaDB(Derby)
- 20160411--相等测试与继承
- ReentrantLock源码分析(二)
- JDK
- javaEE答疑
- 利用java注解验证bean对象数据格式
- java 判断 double 数字 是否是整数