我工作中很少使用过内部类,以下的想法是觉得应该知道这些知识,以便以后用到的时候扩充知识。。然后网上搜索知识学习总结。
主要参考资料:《thinking of java》
在Java中内部类主要分为成员内部类、局部内部类、匿名内部类、静态内部类。
以下是大一的时候写得,有不对的地方不要介意,个人的一些理解!!!
以下是大一的时候写得,有不对的地方不要介意,个人的一些理解!!!
以下是大一的时候写得,有不对的地方不要介意,个人的一些理解!!!
目录
成员的内部类
先来一段代码,内部类的基本的使用
public class Person {
private String a;
private String s;
public class Man{ //内部类
private String a;
private String s;
public Man(){
System.out.println("person -> man...");
}
public Man(String a, String s){
this.a = a;
this.s = s;
}
@Override
public String toString() {
return "Man [a=" + a + ", s=" + s + "]";//内部类使用自己的私有成员变量
}
public void userOutclass(){
System.out.println(per);//内部类使用外部类的私有成员变量
}
}
public static void doWork(){//写一个静态方法顺便说明一下static关键字
System.out.println("person...");
}
public Person(){
}
public Person(String a, String s){
this.a = a;
this.s = s;
}
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
@Override
public String toString() {
return "Person [a=" + a + ", s=" + s + "]";
}
}
public class A extends Person{
//静态方法可以被继承,也可以被重写成静态的方法--但是不能体现多态性,即上溯造型之后,不会覆盖父类的方法
public static void doWork(){
System.out.println("A...");
}
}
public class Main {
public static void main(String [] args){
A a = new A();
a.doWork();//==A.doWork()
Person p = new A();
p.doWork();//==Person.doWork()
Person p1 = new Person("person0","person1");
Person.Man man = p1.new Man("man0","man1");
System.out.println(p1.toString());
System.out.println(man.toString());
man.userOutclass();//内部类使用外部类的私有成员变量
}
}
输出结果:
A...
person...
Person [a=person0, s=person1]
Man [a=man0, s=man1]
由以上Main中Person中的内部类Man的创建实例的方法可知,内部类的实例创建必须要在其外部类创建实例后才能完成,,上诉的创建实例代码相对看着可能有些不顺眼,也可以在外部类中使用get方法获取Man对象的实例,这样就比较大众化了
内部类和外部类有什么区别?
外部类的静态成员(包括静态方法和静态初始化块 )中不能使用非静态内部类,因为静态成员不能访问非静态成员 。
非静态的内部类中不能用static修饰成员。不同的是类不能用static修饰。。static如果要修饰一个类只能修饰内部类
内部类中重要的一个特点是:普通类下的每一个内部类都能独立的继承类和实现接口(其规则和普通类一样),且不会受到外部类继承,实现关系的任何影响------使用这个特点可以变相的实现多继承
因为此类在一个类的内部,所以可以使用private,protected修饰--普通类(外部类)不能
内部类的使用注意:
非静态内部类的成员可以访问外部类的 private 及以下权限成员,但反过来就不成立了。非静态内部类的成员只在非静态内部类范围内是可知的,并不能被外部类直接使用 。 如果外部类需要访问非静态内部类的成员,则必须显式创建非静态内部类对象来调用访问其实例成员
Java 不允许在非静态内部类里定义静态成员。(静态方法、静态成员变量、静态初始化块)
内部类是编译时的概念,编译完成后将产生两个class文件,但有一定的联系(如继承关系一样),非静态内部类对象中保留外部类对象的引用,当在非静态内部类的方法内访问某个变量时,系统优先在该方法内查找是否存在该名字的局部变量, 如果存在就使用该变量:如果不存在,则到该方法所在的内部类中查找是否存在该名字的成员变量 ,如果存在则使用该成员变量 ;如果不存在,则到该内部类所在的外部类中查找是否存在该名字的成员变量,如果存在则使用该成员变量:如果依然不存在,系统将出现编译错误:提示找不到该变量。
静态的内部类
使用static修饰的内部类我们称之为静态内部类,或则是嵌套内部类。和static修饰的成员变量、方法、代码块一样,它不属于该类的任何一个对象,且静态内部类编译完成后不会保留一个指向其外部类的引用
静态内部类的创建不依赖于外部类,是外部类的一个静态成员
静态内部类可以包含静态成员,也可以包含非静态成员 。根据静态成员不能访问非静态成员的规则 ,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员 。 即使是静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员 。非静态成员可以访问静态成员
外部类依然不能直接访问静态内部类的成员,但可 以使用静态 内部类的类名作为调用者来访问静态
内部类的类成员,也可以使用静态内部类对象作为调用者来访问静态内 部类的实例成员。
接口里面也可以定义内部类,但只允许定义静态内部类和接口(jdk1.8的Map接口中有一个内部接口,有兴趣的可以研究一下),且只能使用public static 修饰-默认是public static||接口里可以定义静态常量static final;接口里面定义的非行为必须可脱离改接口独立存在(不依赖于改接口的实例-接口不能创建实例)
外部使用静态内部类:
若Person类包含静态内部类Man
Person.Man man = new Person.Man();
此处并没有实例化一个Person,只是实例化了一个Man
对比:Person.Man man = new Person. new Man();
局部的内部类
如果把一个内部类放在方法里定义,则这个内部类就是一个局部内部类,局部内部类仅在该方法里有效。由于局部内部类不能在外部类的方法以外的地方使用,因此局部内部类也不能使用访问控制符(包括public)和static 修饰符修饰 。
对于局部成员而言 , 不管是局部变量还是局部内部类 , 它们的上一级程序单元都是方法,而不是类,使用 static 修饰它们没有任何意义 。 因此,所有的局部成员都不能使用 static修饰 。 不仅如此,因为局部成员的作用域是所在方法,其他程序单元永远也不可能访问另一个方法中的局部成员,所以所有的局部成员都不能使用访问控制符修饰 。
使用局部内部类(包括匿名内部类的局部使用)需要注意的一点:java要求所有被局部内部类访问的局部变量都是用final修饰?
对于普通局部变量他的作用域就是该方法内,当方法结束该局部变量就随之消失;但局部内部类可能产生隐式的“闭包”,闭包将使得局部变量脱离他所在的方法继续存在。
如下:原文地址:http://blog.youkuaiyun.com/hanghangde/article/details/50620619
public class ClosureTest {
public static void main(String[] args) {
final String str = "Java"; //定义一个局部变量
//在内部类里访问局部变量str
new Thread(new Runnable(){ //此线程和Mian并不是同一个线程
public void run(){
for (int i = 0; i < 100 ; i++ ){ //此处将一直可以访问到str局部变量
System.out.println(str + " " + i);
try { //暂停0.1秒 //此时Main线程会执行然后Main线程就结束了
Thread.sleep(100);
} catch (Exception ex){
ex.printStackTrace();
}
}
}}).start(); //①
//执行到此处,main方法结束
}
}
//个人觉得此例子非常有意思,因为其创建匿名局部内部类的地方是再main方法里-----又可以了解一些有意思的相关知识
按照大众的理解,那么main是主线程,为什么Main都结束了,其他线程还能继续执行?
答案此处:http://lc87624.iteye.com/blog/1885984
总结一点:为什么必须都是final修饰的变量?(个人并没有理解--可能需要更底层的知识,,以后补充。。)
这是个人比较认同的说法:
上面定义了一个局部变量str。正常情况下,当程序执行完①行代码之后,main方法的生命周期就结束了,局部变量str的作用域也会随之结束。但只要新线程里run方法没有执行完,匿名内部类的生命周期就没有结束,将一直可以访问str局部变量的值,这个就是内部类会扩大局部变量作用域的实例。
由于内部类可能扩大局部变量的作用域,如果再加上这个被内部类访问的局部变量没有使用final修饰,也就是说这个变量的值可以随时改变,此变量已经消失不能修改了①,因此java编译器要求所有被内部类访问的局部变量必须使用final修饰符修饰。
①处的变量消失了,如果局部变量不是如上的基本类型的变量-而是引用类型,比如一个类的实例对象-那么所指向的对象并不会消失,而内部类存在引用,依然可以访问,本人不是很理解这句话的意思
以下不是很认同和理解的说法:
之前不知道在哪里看的这篇文章部分内容,不能给出转载地址,抱歉。。
“这是一个编译器设计的问题,如果你了解java的编译原理的话很容易理解。
首先,内部类被编译的时候会生成一个单独的内部类的.class文件,这个文件并不与外部类在同一class文件中。
当外部类传的参数被内部类调用时,从java程序的角度来看是直接的调用例如:
public void dosome(final String a,final int b){
class Dosome{public void dosome(){System.out.println(a+b)}};
Dosome some=new Dosome();
some.dosome();
}
从代码来看好像是那个内部类直接调用的a参数和b参数,但是实际上不是,在java编译器编译以后实际的操作代码是
class Outer$Dosome{
public Dosome(final String a,final int b){
this.Dosome$a=a;
this.Dosome$b=b;
}
public void dosome(){
System.out.println(this.Dosome$a+this.Dosome$b);
}
}}
从以上代码看来,内部类并不是直接调用方法传进来的参数,而是内部类将传进来的参数通过自己的构造器备份到了自己的内部,自己内部的方法调用的实际是自己的属性而不是外部类方法的参数。
这样理解就很容易得出为什么要用final了,因为两者从外表看起来是同一个东西,实际上却不是这样(以下仅当值传递是发生,即传递基本类型,如果是引用传递,依然会改变)如果内部类改掉了这些参数的值也不可能影响到原参数,然而这样却失去了参数的一致性,因为从编程人员的角度来看他们是同一个东西,如果编程人员在程序设计的时候在内部类中改掉参数的值,但是外部调用的时候又发现值其实没有被改掉,这就让人非常的难以理解和接受,为了避免这种尴尬的问题存在,所以编译器设计人员把内部类能够使用的参数设定为必须是final来规避这种莫名其妙错误的存在。”
我不知道,原作者此处何意,,传参数的机制都是拷贝机制,仅存在拷贝值还是拷贝地址的方式,只是复制了一个引用或一个值作为自己的成员变量,并不存在参数一致性的问题,和其他类型的传参并无区别吧
以下的相对比较赞同,可是也不是很明白http://blog.youkuaiyun.com/onisland/article/details/5807637
简单的来说是作用域的问题。就好像方法外面做的事情并不能改变方法内才定义的变量,因为你并不知道方法里面这个时候已经存在了这个局部变量了没有。在这个内部类中方法里面的本地变量是失效的,也就是不在作用域内,所以是不能够访问的
但是为什么这里用final却又可以访问呢?
因为Java采用了一种copy local variable的方式来实现,也就是说把定义为final的局部变量拷贝过来用,而引用的也可以拿过来用,只是不能重新赋值。从而造成了可以access local variable的假象,而这个时候由于不能重新赋值,所以一般不会造成不可预料的事情发生
三、如果定义一个局部内部类,并且局部内部类使用了一个在其外部定义的对象,为什么编译器会要求其参数引用是final呢?
注意:局部内部类,包括匿名内部类。
原因如下:
abstract class ABSClass{
public abstract void print();
}
public class Test2{
public static void test(final String s){//一旦参数在匿名类内部使用,则必须是final
ABSClass c=new ABSClass(){
public void print(){
System.out.println(s);
}
};
c.print();
}
public static void main(String[] args){
test("Hello World!");
}
}
VM中每个进程都会有多个根,每个static变量,方法参数,局部变量,当然这都是指引用类型.基础类型是不能作为根的,根其实就是一个存储地址.垃圾回收器在工作时先从根开始遍历它引用的对象并标记它们,如此递归到最末梢,所有根都遍历后,没有被标记到的对象说明没有被引用,那么就是可以被回收的对象(有些对象有finalized方法,虽然没有引用,但JVM中有一个专门的队列引用它们直到finalized方法被执行后才从该队列中移除成为真正没有引用的对象,可以回收,这个与本主题讨论的无关,包括代的划分等以后再说明).这看起来很好.
但是在内部类的回调方法中,s既不可能是静态变量,也不是方法中的临时变量,也不是方法参数,它不可能作为根,在内部类中也没有变量引用它,它的根在内部类外部的那个方法中,如果这时外面变量s重指向其它对象,则回调方法中的这个对象s就失去了引用,可能被回收,而由于内部类回调方法大多数在其它线程中执行,可能还要在回收后还会继续访问它.这将是什么结果?
而使用final修饰符不仅会保持对象的引用不会改变,而且编译器还会持续维护这个对象在回调方法中的生命周期.所以这才是final变量和final参数的根本意义.
如果不使用final修饰可能出现一种情况:将方法内的局部变量(对象1)传入其局部内部类中,内部类中就又来对象1的一个引用。。,
但是根据局变量的生存周期,当方法结束后或对象1的非内部类中的引用改变指向时,由内部类里存在的此引用并不会使对象1呈现有引用的状态,导致对象1没有引用了,局部变量就会出现弱引用状态(当系统垃圾回收机制运行时,回收该对象所占用的内存),然后被回收了,但是内部类之中依然需要它,--所以需要final修饰符
局部内部类把需要访问的外部变量作为一个隐藏的字段,这样得到了一个变量的引用拷贝
使用final修饰符不仅会保持对象的引用不会改变,而且编译器还会持续维护这个对象在回调方法中的生命周期.所以这才是final变量和final参数的根本意义.
因为编译器不会给非final变量进行拷贝,那么内部类引用的变量就是非法的!
不是很理解的原因:
方法结束只会销毁存在栈中的引用,而没有权利去销毁堆中的对象,堆中的对象只有想失去引用的时候回可能被回收,但内部类中是有一个指向其的引用,所有就算方法中的局部变量(引用)销毁之后,其对象依然存在与堆中,
在函数中定义的一些基本类型的变量数据和对象的引用变量都在函数的栈内存中分配。当在一段代码块定义一个变量时,Java在栈中为这个变量分配内存空间,当该变量退出其作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。
对于局部变量,引用变量所引用的对象一样是存在于堆中,即使变量消失,其指向的对象并不会消失,对象的消失由是否有引用决定。。。
本人能理解的部分就是:
内部类能扩大局部变量的作用域
(用final来让该引用不可改变)--比如:
final可以保证引用不可被自己无意间改变,不能保证其引用的对象实例的属性不被其他该对象实例的引用改变 ---final关键字只能保证引用不变不能保证其对象实例不变,一个对象实例可以有多个引用
总之都是为了保证内部类操作的引用不被自己改变,和自己始终都在操作同一个对象,即引用指向的对象---
--保证内部类和外部函数对变量“认识”的一致性。
我以为主要的原因还是因为:局部变量的生命周期原因。
jdk1.8对编译器做了一个大的改动--使用局部内部类的时候不需要手动加final关键字--会默认加上此关键字,如接口里面定义内部类|接口一样默认加上public static。。。
使用一个方法获取接口的实例:--与直接使用匿名内部类效果一样--匿名内部类更加简洁
public interface People {
void doWork(){
System.out.println("default");
}
}
public class Main {
public static void main(String args[]){
People S = getPerson();
S.doWork();
}
public static People getPerson(){
String str = "测试局部内部类";
class S implements People{
private String str1;
public S(String s){
this.str1 = s;
}
public void doWork(){
System.out.println(str+"p/.."+str1+"pe/.......");
}
}
//str = "测试局部内部类1";①
return new S("ceshi..");
}
}
测试局部内部类p/..ceshipe/.......
以上使用str变量并没有添加final-->
如果在方法中添一句①,会报错,提示你将str加上final关键字,添加final之后就会正常的提示你str不可修改了。难道jdk1.8会选择性加上final关键字
《thinking of java》中提到:
局部内部类是一个非常“鸡肋”的语法,在实际开发中很少定义局部内部类,这是因为局部内部类的作用域太小了:只能在当前方法中使用 。大部分时候,定义一个类之后,当然希望多次复用这个类,但局部内部类无法离开它所在的方法,因此在实际开发 中很少使用局部内部类。
匿名的内部类
匿名内部类适合创建那种只需要一次使用的类。匿名内部类的语法有点奇怪,创建匿名 内部类时会立即创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用 。
匿名内部类属于局部局部内部类,局部内部类的所有限制对匿名内部类生效
匿名内部类和其他内部类的不同之处:
匿名内部类不能是抽象类:因为系统在创建匿名内部类是后会立即创建对象实例,
匿名内部类不能有构造器:因为其没有类名,但可以通过初始块来实现构造器的功能
使用匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口,因为没有class关键字且需要new 一个接口或类--也只能new一个
public interface People {
void doWork(){
System.out.println("default");
}
}
public static void main(String args[]){
String st="ceshi";
People S = getPerson(st);
S.doWork();
}
public static People getPerson(String s){
String str = "测试匿名内部类";
return new People(){//接口和抽象类没有构造器,不能直接传递参数
private String str1;
{
this.str1 = s;
}
public void doWork(){
System.out.println(str+"p/.."+str1+"pe/.......");
}
};
}
测试匿名内部类p/..ceshipe/.......
请仔细比较此例和上例的区别。。。
匿名内部类在Swing编程中非常常见和监听器使用非常有效
今天看设计模式-单例模式,突然发现一个问题,静态内部类可以延迟加载。。。
先看下面这个问题:Java非静态内部类为什么不能有静态成员?
网络扫荡一片发现一些具有误导性的的答案是:
首先内部类作为外部类的一个非静态成员,它的加载必须有外部类的对象实例(此时非静态成员已经加载),而当你加载该对象实例的时候,内部类里的静态成员并未被加载,这就违背了“所有的静态变量必须在所有的非静态变量之前加载”这一原则,所以这本身就是一个矛盾。而如果内部类是静态的话,就不会存在该矛盾了,因为访问Class Inner 是不需要Class Outer的实例的。
我开始也是这样想的,有点道理,回来学到单例模式,静态内部类的知识,发现:内部类不能看做外部类的成员,不然外部类加载的时候,静态内部类也应当被加载,然而答案并不是这样,静态内部类会延迟加载,只有在使用到的时候才会加载。也就是在外部类调用的时候在加载==》内部类的加载时机必然在外部类加载之后
那么java非静态内部类为什么不能有静态成员?网上看到一个比较认同的观点就直接复制了
http://blog.sina.com.cn/s/blog_c05e68340101f8w5.html
我们知道线程安全很大一部分原因是用于共享变量造成的,而所有的静态变量都是必须共享的,所以我们在编程的时候有这样的一种思维:我只有在每个线程中new一个对象就没有线程安全问题啦,因为new一个对象就会新开辟一个堆内存来存放。关键的部分来了:
如果我们在一个线程中:
Out out=new Out();
那么out.In这个东西你会不会认为完全是堆内存中的一个东西呢
如果再另一个线程中:
Out out2=new Out();
你会不会认为我又在堆中搞出了一个out2.In呢
那么看来我们无论怎么在两个线程中操作out.In和out2.In好像都没有任何线程安全问题。但是如果In中有一个static的变量呢?是不是out.In和Out2.In操作了一个共享对象是不是在你认为没有任何线程安全问题的时候他就出现线程安全问题了?java作为一门伟大而严禁的语言是不允许出现这种情况的!
关于加上final修饰就可以的问题应该很好理解,final修饰的东西是个常量,谁都动不了,也就没有线程安全问题了!
非静态内部类可以使用static关键字,但必须使用static 修饰常量如:static final String str = "inner class";
java类包括成员变量和成员方法:final修饰的叫做final变量
内部类的重写和覆盖
1.继承外部类
首先给出一个class One 其中有一个One_inner内部类
public class One {
public String str;
public One(){
str = "oneclass";
}
public class One_inner{
public String str;
public One_inner(){
str = "one_inner class";
}
public String toPrint() {
return "One_inner [str=" + str + "]";
}
}
protected void re(){
System.out.println("父类的protected权限方法");
}
}
然后class Two 继承了One
public class Two extends One{
public static void main(String args[]){
Two t = new Two();
System.out.println(t.new One_inner().toPrint());
===>print:One_inner [str=one_inner class] :内部类可以如何成员一样被继承
One t1 = new Two();
System.out.println(t1.new One_inner().toPrint());
===>print:One_inner [str=one_inner class] :内部类可以如何成员一样被继承
}
}
现在重写内部类
public class Two extends One{
public static void main(String args[]){
Two t = new Two();
System.out.println(t.new One_inner().toPrint());
===>print:One_inner [str=two_inner class] :子类可以重写父类的内部类
One t1 = new Two();
System.out.println(t1.new One_inner().toPrint());
===>print:One_inner [str=one_inner class] :子类不可以覆盖父类的内部类
}
private class One_inner{
public String str;
public One_inner(){
str = "two_inner class";
}
public String toPrint() {
return "One_inner [str=" + str + "]";
}
}
注意:在重写内部类时Two将其的访问权限缩小到了priavte,这里与重写方法有很大的差异,它不遵循重写的规定
2直接继承内部类
public class Two extends One{
public static void main(String args[]){
Two t = new Two();
System.out.println(t.new Two_inner().toPrint());
===>print:
One_inner [str=one_inner class] :内部类可以直接被继承
}
public class Two_inner extends One.One_inner{
}
}
public class Two extends One{
public static void main(String args[]){
Two t = new Two();
One.One_inner s = t.new Two_inner();
System.out.println(s.toPrint());
===>print:
Two_inner class内部类覆盖 :子类内部类被上塑造型到父类内部类时可以覆盖内部类的方法
}
public class Two_inner extends One.One_inner{
public Two_inner(){
str = "Two_inner class";
}
public String toPrint(){
return str+"内部类覆盖";
}
}
}