Java面试准备十:Java内部类
2017-04-13 19:08
225 查看
这里只是为了记录,由于自身水平实在不怎么样,难免错误百出,有错的地方还望大家多多指出,谢谢。
参考Java内部类详解
四种内部类
深入理解内部类
内部类的使用场景和好处
在Java中,将一个定义在另一个类里面或者一个方法里面的类称为内部类。一般来说,内部类包括:
成员内部类
局部内部类
匿名内部类
静态内部类
一些补充、总结
成员内部类
成员内部类是最普通的内部类,它的定义为位于另一个类的内部,举个例子:
这样看起来,类Draw像是Circle的一个成员,Circle称为外部类。成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员),如下:
如果成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
虽然成员内部类可以无条件的访问外部类的成员,而外部类想访问成员内部类的成员却必须先创建一个成员内部类的实例,再通过这个实例来访问内部类变量、方法。
成员内部类是依附在外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:
内部类可以拥有private、protected、public以及包访问权限。可把它当做是外部类的一个成员变量来看。
外部类只有public和包访问权限。
局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问权限仅限于方法内或者改作用域内。
注意,仅有abstract和final这两个修饰符被允许修饰局部内部类
匿名内部类
匿名内部类有以下特点:
(1)没有名字(因为没有名字,没有构造函数)。
(2)只能被实例化一次。
(3)通常被声明在方法或代码块的内部,以一个带有分号的带有花括号结尾。
(4)匿名内部类也是不能有访问修饰符和static修饰符的。
(5)匿名内部类不能访问外部内的成员变量和方法。
(5)匿名类的一个重要作用就是简化代码。
先看下面这个事件监听的例子:
普通的实现方式:
匿名内部类的实现方式
从上面可以看出,使用匿名内部类简化了代码的书写。一般一些操作不会被重复调用,只需要调用一次,就可以使用匿名内部类。
匿名内部类的使用场景
(1)事件监听(上面的例子)
(2)Thread类的匿名内部类实现
(2)Runnable接口的匿名内部类实现
静态内部类
(1)静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。
(2)静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似。
(3)并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在外部类没有被实例化的时候,可以创建静态内部类的对象,如果允许访问外部类的非static成员,这时非static成员可能还没被JVM分配内存,不能被使用。
一些补充、总结
为什么成员内部类可以无条件访问外部类的成员?
为什么局部内部类和匿名内部类只能访问局部final变量?
静态内部类有特殊的地方吗?
为什么成员内部类可以无条件访问外部类的成员?
因为编译器会默认为成员内部类添加了一个指向外部类对象的引用:final com.cxh.test2.Outter this0;虽然我们在定义的内部类里面的构造器是无参构造器,但是编译器还是会默认添加一个参数,所以就可以通过这个参数将外部类的引用传递进来,因此可以在成员内部类中随意访问外部类的成员。从这里也间接说明了成员内部类是依赖于外部类的,如果没有创建外部类的对象,则无法对Outterthis0引用进行初始化赋值,也就无法创建成员内部类的对象了。
为什么局部内部类和匿名内部类只能访问局部final变量?
先看下面一个例子:
这段代码会被编译成两个class文件:Test.class和Class1.class。默认情况下,编译器会为匿名内部类和局部内部类起名为Outter x.class(x为正整数)。
上段代码中,如果把变量a和b前面任一个final去掉,这段代码都编译不过。我们,可以考虑这样一个问题:
当test方法执行完毕之后,变量a的生命周期结束了,而这时Thread对象的声明周期可能还没有结束,那么在Thread的run方法里面继续访问变量a就变成不可能了,但是又要实现这样的效果,怎么办呢?Java采用了* 复制* 的手段来解决这个问题。
如果变量的值在编译期间可以确定,则编译器会在匿名内部类(局部内部类)创建一个拷贝。这样一来,匿名内部类使用的变量是另一个局部变量,只不过值和方法中的局部变量的值相等。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。如下:
那么新的问题又来了,既然在run方法中访问的变量a和test方法中的变量a不是同一个变量,当在run方法中改变变量a的值的话,会出现什么情况?
会造成数据不一致。
为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不允许对变量a进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。
到这里,想必大家应该清楚为何 方法中的局部变量和形参都必须用final进行限定了。
静态内部类有特殊的地方吗?
从前面可以知道,静态内部类是不依赖于外部类的,也就说可以在不创建外部类对象的情况下创建内部类的对象。另外,静态内部类是不持有指向外部类对象的引用的,这个读者可以自己尝试反编译class文件看一下就知道了,是没有Outter this&0引用的。
总结一下主要有一下四点:
(1)方便编写时间驱动程序。
(2)方便编写线程代码
(3)每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整(不太明白)
(4)方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
参考Java内部类详解
四种内部类
深入理解内部类
内部类的使用场景和好处
1. 四种内部类 |
成员内部类
局部内部类
匿名内部类
静态内部类
一些补充、总结
成员内部类
成员内部类是最普通的内部类,它的定义为位于另一个类的内部,举个例子:
public class Circle { double radius = 0; public Circle(double radius){ this.radius = radius; } //内部类 class Draw{ public void drawSahpe(){ System.out.println("drawshape"); } } }
这样看起来,类Draw像是Circle的一个成员,Circle称为外部类。成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员),如下:
public class Circle { private double radius = 0; public static int count = 1; public Circle(double radius){ this.radius = radius; } //内部类 class Draw{ public void drawSahpe(){ System.out.println(radius);//外部类的private成员 System.out.println(count);//外部类的静态成员 } } }
如果成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类.this.成员变量 外部类.this.成员方法
虽然成员内部类可以无条件的访问外部类的成员,而外部类想访问成员内部类的成员却必须先创建一个成员内部类的实例,再通过这个实例来访问内部类变量、方法。
public class Circle { private double radius = 0; public static int count = 1; public Circle(double radius){ this.radius = radius; } public void operate1(){ Draw draw = new Draw();//先创建内部类实例 draw.drawSahpe();//再调用实例方法 } //内部类 class Draw{ public void drawSahpe(){ System.out.println(radius);//外部类的private成员 System.out.println(count);//外部类的静态成员 } } }
成员内部类是依附在外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:
public class Circle { private Draw draw = null; public Draw getDrawInstance(){ if(draw==null){ draw = new Draw(); } return draw; } //内部类 class Draw{ public void drawShape(){ System.out.println("drawShape"); } } //主程序 public static void mian(String arg[]) { Circle circle = new Circle(); circle.getDrawInstance().drawShape(); //上面部分代码也可以如下代码 new Circle().new Draw().drawShape(); } }
内部类可以拥有private、protected、public以及包访问权限。可把它当做是外部类的一个成员变量来看。
外部类只有public和包访问权限。
局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问权限仅限于方法内或者改作用域内。
public class Door { public void alarm(){ class Alarm{ public void alarm(){ System.out.println("发出警报"); } } Alarm alarm = new Alarm();//在定义类之后才能创建 alarm.alarm(); } }
注意,仅有abstract和final这两个修饰符被允许修饰局部内部类
匿名内部类
匿名内部类有以下特点:
(1)没有名字(因为没有名字,没有构造函数)。
(2)只能被实例化一次。
(3)通常被声明在方法或代码块的内部,以一个带有分号的带有花括号结尾。
(4)匿名内部类也是不能有访问修饰符和static修饰符的。
(5)匿名内部类不能访问外部内的成员变量和方法。
(5)匿名类的一个重要作用就是简化代码。
先看下面这个事件监听的例子:
普通的实现方式:
public class WindowClosingAdapter extends WindowAdapter{ @Override public void windowClosing(WindowEvent e) { System.exit(0); } } ... addWindowListener(new WindowClosingAdapter());
匿名内部类的实现方式
addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { System.exit(0); } });
从上面可以看出,使用匿名内部类简化了代码的书写。一般一些操作不会被重复调用,只需要调用一次,就可以使用匿名内部类。
匿名内部类的使用场景
(1)事件监听(上面的例子)
(2)Thread类的匿名内部类实现
public class Demo { public static void main(String[] args) { Thread t = new Thread(){ public void run() { for(int i=0;i<10;i++){ System.out.println(i); } }; }; t.start(); } }
(2)Runnable接口的匿名内部类实现
public class Demo { public static void main(String[] args) { Runnable runnable = new Runnable() { @Override public void run() { for(int i=0;i<10;i++){ System.out.println("runnable: "+i); } } }; new Thread(runnable).start(); } }
静态内部类
(1)静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。
(2)静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似。
(3)并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在外部类没有被实例化的时候,可以创建静态内部类的对象,如果允许访问外部类的非static成员,这时非static成员可能还没被JVM分配内存,不能被使用。
public class Outter { public static class Inner{ public Inner(){ } } //主函数 public static void main(String[] args) { Outter.Inner inner = new Outter.Inner(); } }
一些补充、总结
———— | 访问修饰符(public、protected、 private和包权限修饰符) | 修饰符 | 对外部类的成员变量、方法的访问权限 | 备注 |
外部类 | public、默认是包权限 | static ×(从static存在的意义来理解) abstract、final √ | ||
成员内部类 | 都可以 | static、final、abstract | 能够无条件的访问外部类所有的成员变量、方法 | |
局部内部类 | × | final、abstract | 同上 | 局部内部类是定义在外部类的方法中 的,只有在方法中的局部变量(注意: 这里指的是方法中的局部变量,不是外 部类的成员变量)被标记为final或 局部变量是effectively final时,内部 类才能使用它们,详情请看深入理解 内部类的为什么局部内部类和匿名内部类只能访问局部final变量? |
匿名内部类 | × | × | 同上 | 同上 |
静态内部类 | √ | static(必须的)、final √ abstract × | 只能访问静态的成员变量、方法 |
2. 深入理解内部类 |
为什么局部内部类和匿名内部类只能访问局部final变量?
静态内部类有特殊的地方吗?
为什么成员内部类可以无条件访问外部类的成员?
因为编译器会默认为成员内部类添加了一个指向外部类对象的引用:final com.cxh.test2.Outter this0;虽然我们在定义的内部类里面的构造器是无参构造器,但是编译器还是会默认添加一个参数,所以就可以通过这个参数将外部类的引用传递进来,因此可以在成员内部类中随意访问外部类的成员。从这里也间接说明了成员内部类是依赖于外部类的,如果没有创建外部类的对象,则无法对Outterthis0引用进行初始化赋值,也就无法创建成员内部类的对象了。
为什么局部内部类和匿名内部类只能访问局部final变量?
先看下面一个例子:
class Test{ public static void main(String[] args) { } public void test(final int b){ final int a = 10; new Thread(){ public void run() { System.out.println(a); System.out.println(b); }; }.start(); } }
这段代码会被编译成两个class文件:Test.class和Class1.class。默认情况下,编译器会为匿名内部类和局部内部类起名为Outter x.class(x为正整数)。
上段代码中,如果把变量a和b前面任一个final去掉,这段代码都编译不过。我们,可以考虑这样一个问题:
当test方法执行完毕之后,变量a的生命周期结束了,而这时Thread对象的声明周期可能还没有结束,那么在Thread的run方法里面继续访问变量a就变成不可能了,但是又要实现这样的效果,怎么办呢?Java采用了* 复制* 的手段来解决这个问题。
如果变量的值在编译期间可以确定,则编译器会在匿名内部类(局部内部类)创建一个拷贝。这样一来,匿名内部类使用的变量是另一个局部变量,只不过值和方法中的局部变量的值相等。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。如下:
public class Test { public static void main(String[] args) { } public void test(final int a) { new Thread(){ public void run() { System.out.println(a); }; }.start(); } }
那么新的问题又来了,既然在run方法中访问的变量a和test方法中的变量a不是同一个变量,当在run方法中改变变量a的值的话,会出现什么情况?
会造成数据不一致。
为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不允许对变量a进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。
到这里,想必大家应该清楚为何 方法中的局部变量和形参都必须用final进行限定了。
静态内部类有特殊的地方吗?
从前面可以知道,静态内部类是不依赖于外部类的,也就说可以在不创建外部类对象的情况下创建内部类的对象。另外,静态内部类是不持有指向外部类对象的引用的,这个读者可以自己尝试反编译class文件看一下就知道了,是没有Outter this&0引用的。
3. 内部类的使用场景和好处 |
(1)方便编写时间驱动程序。
(2)方便编写线程代码
(3)每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整(不太明白)
(4)方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
相关文章推荐
- 我的Java工程师面试之准备
- java 基础面试准备资料 & 面试题
- 2014年面试准备--JAVA
- java面试准备3
- java面试准备2
- Java面试要准备的问题
- Java 面试准备
- java面试前本人需准备的内容
- 【java失业择业中】失业第四天:准备面试
- java面试准备5
- java面试准备之---Struts2体系知识点,系统复习,struts2原理,ognl,el支持.---随时更新
- 准备Java面试一个月,倒计时之Java基础02
- 准备Java面试一个月,倒计时之Java基础01
- java集群技术面试的一些知识准备
- 【java失业择业中】失业第三天:准备面试
- java程序员面试如何准备
- 准备Java面试一个月,倒计时之Java基础
- 【java失业择业中】失业第二天:准备面试
- java面试准备之基础排序——冒泡与选择排序
- Java面试及准备