【Java】内部类

可以将一个类的定义放在另一个类的定义内部,这就是内部类。

使用内部类的的原因主要有三点:

  • 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
  • 内部类可以对同一个包中的其他类隐藏起来。
  • 当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous)内部类比较便捷

使用内部类访问对象状态

public class TalkingClock {
private int interval;
private boolean beep; public TalkingClock(int interval, boolean beep) {...}
public void start() {...} public class TimePrinter implements ActionListener {
Date now = new Date();
System.out.println("At the tone, this time is" + now);
if (beep) Toolkit.getDefaultToolkit().beep();
}
}

内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。

外围类的引用在构造器中设置。编译器修改了所有的内部类的构造器,添加一个外围类引用的参数。因为TimePrinter类没有定义构造器,所以编译器为这个类生成了一个默认的构造器,其代码如下:

public TimePrinter(TalkingClock clock) {
outer = clock;
}

请再注意一下,outer不是Java的关键字。我们只是用它说明内部类中的机制。

当在start方法中创建了TimePrinter对象后,编辑器就会将this引用传递给当前的语音始终的构造器。

ActionListener listener = new TimePinter(this); //parameter automatically added

内部类的特殊语法规则

使用外围类引用的正规语法如下。表达式:

OuterClass.this

表示外围类引用。例如,可以像下面这样编写TimePrinter内部类的actionPerformed方法:

public void actionPerformed(ActionEvent event) {
if (TalkingClock.this.beep) Toolkit.getDefaultToolkit().beep();
}

反过来,可以采用下列语法格式更加明确地编写内部对象的构造器:

outerClass.new InnerClass(construction parameters)
例如
Actionlistner listener = this.new TimePrinter();

在这里,最新构造的TimePrinter对象的外围类引用被设置为创建内部类对象的方法中的this引用。这是一种最常见的情况。通常,this限定词是多余的。不过,可以通过显式地命名将外围类引用设置为其他的对象。例如,如果TimePrinter是一个共有内部类,对于任意的语音时钟都可以构造一个TimePrinter:

TalkingClock jabberer = new TalkingClock(1000, true);
Talking.TimePrinter listener = jabber.new TimePrinter();

需要注意,在外围类的作用域之外,可以这样引用内部类:

OuterClass.InnerClass

要想直接创建内部类的对象,你不能按照你想象的方式,去引用外部类的名字,而必须使用外部类的对象来创建该内部类对象。在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗地连接到创建它的外部类对象上。


内部类是否有用、必要和安全

  • 内部类是一种编译器现象,与虚拟机无关。编译器会把内部类翻译成用$(美元符号)分隔外部类与内部类名的常规类文件,而虚拟机一无所知。例如,在TalkingClock类内部的TimePrinter类将被翻译成TalkingClock$TimePrineter.class。
  • 如果一个类是匿名内部类,那么clas文件名称是OuterClass$(1,2,3).class
  • 编译器为了引用外部类,生成了一个附加的实例域this$0(名字this$0是由编译器合成的,在自己编写的代码中不能够引用)。

局部内部类

假设TimePrinter这个类名字只在start方法中创建这个类型的对象时使用了一次,那么可以像下面这样使用:

public void start() {
class TimePrinter implements ActionListner {
public void actionPerformed(ActionEvent event) {
Date now = new Date();
System.out.println("At the tone, the time is " + now);
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listner);
t.start();
}

局部类不能用public或private访问说明符进行声明。它的作用域被限定在声明这个局部类的块中。

局部类有一个优势,即对外部世界可以完全地隐藏起来。即使TaklingClock类中的其他代码也不能访问。除start方法之外,没有任何方法知道TimePrinter类的存在。


由外部方法访问final变量

与其他内部类比较,局部类还有一个优点。它们不仅能够访问包含它们的外部类,还可以访问局部变量。不过,那些局部变量必须被声明为final。如:

public void start(int interval, final bolean beep) {
class TimePrinter implements ActionListner {
public void actionPerformed(ActionEvent event) {
Date now = new Date();
System.out.println("At the tone, the time is " + now());
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
}

编译器实现内部类访问局部变量的方式是这样的:在内部类中为每一个要访问的局部变量设置数据域,然后在构造函数中将这些数据域初始化为要访问的局部变量值。

匿名内部类

将局部内部类的使用再深入一步。假如只创建这个类的一个对象,就不必命名了。这种类被称为匿名内部类(annoymous inner class)

public void start(int interval, final boolean beep) {
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent event) {
Date now = new Date();
System.out.println("At the tone, the time is" + now());
if (beep) Toolkit.getDefaultToolkit().beep();
}
}
Timer t = new Timer(interval, listner);
t.start();
}

它的含义是:创建一个实现ActionListner接口的类的新对象,需要实现的方法actionPerformed定义在括号{}内。

通常的语法格式为:

    new SuperType(construction parameters) {
inner class methods and data
}

其中,SuperType可以是ActionListner这样的接口,于是内部类就要实现这个接口。SuperType也可以是一个类,于是内部类就要扩展它。

如果你的基类需要一个有参数的构造器,应该怎么办:

public classs Parcel8 {
public Wrapping wrapping(int x) {
//Base constructor call
return new Wrapping(x) {
public int value() {
return super.value() * 47;
}
};
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Wrapping w = p.wrapping(10);
}
}

只需要简单地传递合适的参数给基类的构造器即可,这里是将x传进new Wrapping(x)。尽管Wrapping 只是一个具有具体实现的普通类,但它还是被其导出类当做公共“接口”来使用

    public class Wrapping {
private int i;
public Wrapping (int x) { i = x;}
public int value() { return i; }
}

你会注意到, Wrapping拥有一个要求传递一个参数的构造器,这使得事情变的更有趣了。

在匿名类中定义定义字段时,还能够对其执行初始化操作:

    public class Parcel9 {
public Destination destination(final String dest) {
return new Destination() {
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel9 p = new Parcel9();
Destination d = p.destination(“Tesmania”);
}
}

如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,name编译器会要求其参数引用是final的,就像你在destinaion()的参数中看到的那样。如果你忘记了,将会得到一个错误信息。

如果只是简单地给一个字段赋值,那么此例中的方法是很好的。但是,如果想做一些类似构造器的行为。在匿名类中不可能有命名的构造器,但通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果,就像这样:

abstract class Base {
public Base(int i) {
print("Base conctructor, i=" + 1);
}
} public class AnonymousConstructor {
public class Base getBase(int i){
return new Base(i){
print("Inside instance initializer"); public void f() {
print("In anonymous f()");
}
}
} public static void main(String[] args) {
Base base = getBase(47);
base.f();
}
}/*
Base constructor, i = 47;
Inside instance initializer
In anonymous f()

在此例中,不要求变量i一定是final的。以为i被传递给匿名内部类的积累的构造器,它并不会在你你们内部类被直接使用。

下例是带实例化的“parcel”形式。注意destination()参数必须是final的,因为它们是在匿名内部类使用的:

public class Parcel10 {
public Destination destination(final String dest, final float price) {
return new Destination(){
private int cost;
//Instance initialization for each object
{
cost = Math.round(price);
if (cost > 100) {
System.out.println("Over budget");
}
}
private String label = dest; public String readLabel() {
return label;
}
}; } public static void main(String[] args) {
Parcel10 p = new Parcel10();
Destination d = p.destination("Tasmania", 101.395.F);
}
}
//
Over budget;
匿名内部类的重点

1.使用匿名内部类时,必须继承或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口

2. 匿名内部类是不能定义构造函数的

3. 匿名内部类是不能存在任何的静态成员变量和静态方法

4. 匿名内部类为局部内部类,所有局部内部类的所有限制对匿名内部类生效

5. 匿名内部类不能使抽象的,它必须要实现继承的类或者实现接口的所有抽象方法

嵌套类

  • 如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为staic。这通常称为嵌套类

  • 普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象。然而,当内部类是static的时,就不是这样了,嵌套类意味着:

    1)要创建嵌套类的对象,不需要其外围类的对象。

    2)不能从嵌套类的对象中访问非静态的外围类对象。

  • 嵌套类和普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类。但是嵌套类可以包含这些东西。

闭包和回调

  • 闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外围类的对象的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括private成员。

内部类的重点

  1. 非静态内部类可以访问外部类的数据域,包括私有的
  2. 局部内部类和匿名内部类可以访问方法中的参数,不过参数必须为final
  3. 匿名内部类,必须要继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或实现一个接口。
  4. 匿名内部类不能定义构造函数
  5. 匿名内部类中不能存在任何的静态成员变量和静态方法。

【Java】 内部类的更多相关文章

  1. Java内部类final语义实现

    本文描述在java内部类中,经常会引用外部类的变量信息.但是这些变量信息是如何传递给内部类的,在表面上并没有相应的线索.本文从字节码层描述在内部类中是如何实现这些语义的. 本地临时变量 基本类型 fi ...

  2. Java内部类详解

    Java内部类详解 说起内部类这个词,想必很多人都不陌生,但是又会觉得不熟悉.原因是平时编写代码时可能用到的场景不多,用得最多的是在有事件监听的情况下,并且即使用到也很少去总结内部类的用法.今天我们就 ...

  3. 黑马----JAVA内部类

    黑马程序员:Java培训.Android培训.iOS培训..Net培训 黑马程序员--JAVA内部类 一.内部类分为显式内部类和匿名内部类. 二.显式内部类 1.即显式声明的内部类,它有类名. 2.显 ...

  4. java 内部类 *** 最爱那水货

    注: 转载于http://blog.csdn.net/jiangxinyu/article/details/8177326 Java语言允许在类中再定义类,这种在其它类内部定义的类就叫内部类.内部类又 ...

  5. java内部类和匿名内部类

    内部类即是包含在类里面的又一个类. java内部类分为: 成员内部类.静态嵌套类.方法内部类.匿名内部类 . 内部类的共性 (1).内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.clas ...

  6. Java内部类小程序(成员内部类,静态内部类,匿名内部类)

    /** * 测试java内部类(成员内部类,静态内部类,匿名内部类) * 局部内部类不常用,就不写了. * @package :java05 * @author shaobn * @Describe ...

  7. [转] Java内部类详解

    作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本博客中未标明转载的文章归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置 ...

  8. java内部类的作用分析

    提起Java内部类(Inner Class)可能很多人不太熟悉,实际上类似的概念在C++里也有,那就是嵌套类(Nested Class),关于这两者的区别与联系,在下文中会有对比.内部类从表面上看,就 ...

  9. 9)Java内部类(Inner Class)

      内部类:不可以有静态数据,静态方法或者又一个静态内部类      内部类的优点:隐藏类的细节,内部类可以声明为私有.内部类可以访问外部类的对象(包括private) 静态内部类:可以有静态数据,静 ...

  10. JAVA内部类(转)

    源出处:JAVA内部类 在java语言中,有一种类叫做内部类(inner class),也称为嵌入类(nested class),它是定义在其他类的内部.内部类作为其外部类的一个成员,与其他成员一样, ...

随机推荐

  1. springboot+mybatis+springmvc整合实例

    以往的ssm框架整合通常有两种形式,一种是xml形式,一种是注解形式,不管是xml还是注解,基本都会有一大堆xml标签配置,其中有很多重复性的.springboot带给我们的恰恰是“零配置”,&quo ...

  2. Java中static、final、static final的区别【转】

    说明:不一定准确,但是最快理解. final: final可以修饰:属性,方法,类,局部变量(方法中的变量) final修饰的属性的初始化可以在编译期,也可以在运行期,初始化后不能被改变. final ...

  3. Linux系统远程连接服务器命令行模式

    导读 对于很多新手来说,如何用Windows远程Linux操作系统,是个前进的大问题.如果这个问题前进不了,其他更别说了. Linux或Max OS X系统电脑,登录步骤为 1.打开ssh客户端 2. ...

  4. 【Codeforces 111C】Petya and Spiders

    Codeforces 111 C 题意:给\(n\times m\)的网格,每个点上有一个蜘蛛,每个蜘蛛可以向上.下.左.右走一步或者不动,问最多能存在多少没有蜘蛛的点. 思路1: 首先因为\(n\) ...

  5. Android BaseAdapter加载多个不同的Item布局时出现UncaughtException in Thread main java.lang.ArrayIndexOutOfBoundsException: length=15; index=15

    java.lang.ArrayIndexOutOfBoundsException: length=15; index=15 异常出现的场景:在做聊天界面时,需要插入表情,图片,文字,名片,还有几种较为 ...

  6. Omi框架学习之旅 - 插件机制之omi-router及原理说明

    先来看看官网的介绍吧:https://github.com/AlloyTeam/omi/tree/master/plugins/omi-router 其实我推荐直接看官网的介绍.我所写的,主要给个人做 ...

  7. grep配置颜色显示

    查日志时候必须要用的命令,为了在终端方便显示查看,可以加颜色和高亮等设置. 自己习惯用的: GREP_COLOR='a;b'  a=4表示下划线,b=41表示红色背景高亮 在~/.bashrc文件中加 ...

  8. ASP.NET的生命周期

    我主要参考了这些文章 ASP.NET应用程序与页面生命周期, IIS处理Asp.net请求和 Asp.net页面生命周期 asp.net页面的生命周期 页面生命周期开始 (一)页面生命周期的主要阶段包 ...

  9. Luogu1979 NOIP2013D2T3 华容道 搜索、最短路

    题目传送门 题意:给出一个$N \times M$的棋盘,棋盘上有一些块可以移动,有一些块无法移动.$Q$次询问,每一次询问给出三个块$a,b,c$,将$a$块变为空格,空格旁边可移动的块可以与空格交 ...

  10. Vue-嵌套路由

    一个被渲染组件同样可以包含自己的嵌套 <router-view>.同样要有vue-router的三个要素:路由map .路由视图.路由导航. 举个在"/apple" 下 ...