内部类的概念与使用实在是有些繁杂,因为他本身涉及到java内部一些很基础的知识,包括修饰符、作用域等等,在网上很难搜索到一篇全面、准确的关于内部类的总结,所以在这里抛砖引玉一下,希望能对自己、对大家都有所帮助。
本篇大概分成三部分内容,第一部分是对内部类概念性的介绍,第二部分是对内部类语法规则和特性方面的介绍,而第三部分则是对内部类用法、用途的简单介绍,第三部分的内容主要参考自《Effective Java 2nd. Edition》中的Item 22: Favor static member classes over nonstatic
一. 概念介绍
1. 什么是内部类
简单的说,内部(inner)类指那些类定义代码被置于其它类定义中的类;而对于一般的、类定义代码不嵌套在其它类定义中的类,称为顶层(top-level)类。对于一个内部类,包含其定义代码的类称为它的外部(outer)类。
2. 内部类都有哪几种
一般来讲,都是把内部类分为四种:静态成员类(static member classes)、非静态成员类(nonstatic member classes)、局部类(local classes)、匿名类(anonymous classes)
2.1. 静态成员类:所谓静态成员类,就是用static来修饰的,定义于外部类顶层的内部类
例:
注:继承自某个类的匿名类,和实现某个接口的匿名类,在表现形式上略有差别:
2.4.1 继承自某个类的匿名类:
package test;
public class OuterClass {
// 可以在外部类顶层定义静态成员类
public static class Inner1 {
}
}
2.2. 非静态成员类:顾名思义,就是没有用static来修饰的,定义于外部类顶层的内部类
例:
package test;
public class OuterClass {
// 可以在外部类顶层定义非静态成员类
public class Inner1 {
}
}
2.3. 局部类:定义于外部类的代码块或方法内部的内部类。局部类还可以进一步细分为两种:
2.3.1. 局部静态成员类:定义于外部类的静态方法或静态初始化代码段中的局部类
2.3.2. 局部成员类:定义于外部类的实例方法或实例初始化代码段中的局部类
例:
package test;
public class OuterClass {
public static void method1() {
// 局部静态成员类
class inner4 {
}
}
public void method2() {
// 局部成员类
class inner5 {
}
}
}
也就是说,局部类也有“静态”和“非静态”之分,取决于他所处的方法或代码段。
2.4. 匿名类:表面上来看,匿名类就是没有类名的局部类。但其实二者还是有区别的。局部类,是创建一个新的类;而匿名类,是针对已经定义好的接口或类,将其实现(接口)或扩展(类)并实例化。
例:
package test;
public class OuterClass {
public static void method1() {
// 继承OuterClass类,加入一个新的方法print(),然后实例化并调用print()方法
new OuterClass() {
public void print() {System.out.println("hi");}
}.print();
}
public static void main(String[] args) {
// 运行结果:hi
OuterClass.method1();
}
}
new class-name ( [ argument-list ] ) { class-body }
创建匿名类的示例时,argument-list(如果有的话)将作为参数被传入基类对应的构造函数
2.4.2. 实现某接口的匿名类:
new interface-name () { class-body }
二. 语法规则
1. 静态成员类:
1.1. 可以做的:
像静态方法或静态字段一样,可以用public, private, protected来修饰,不加则为默认(package)
可以访问外部类的任一静态字段或静态方法
可以用OuterClass.InnerClass的方式来引用
1.2. 不可以做的:
像外部类的静态方法一样,不可以访问外部类的非静态方法或非静态字段
2. 非静态成员类:
2.1. 可以做的:
可以用public, private, protected来修饰,不加则为默认(package)
可以访问外部类的任一字段和函数(不管是否static),这是因为非静态成员类的实例包含外部类的引用。
当非静态内部类中所定义的某个成员变量和外部类中的变量重名时,外部类的变量将被屏蔽,此时可以用OuterClass.this.来得到被屏蔽的外部类变量
有两种方式来创建非静态成员类的实例。第一种比较常用,就是在外部类的方法内部直接创建: InnerClass inner = new InnerClass(); 另外一种则是通过表达式enclosingInstance.new MemberClass(args)来创建。具体见2.2.
2.2. 不可以做的:
不能在非静态成员类内部定义static字段、方法或内部类。这是因为完全可以将这些东西移到外部类中去;但是可以定义static final常量
在第三方类里面(指非外部类的某个其他类)不可以用OuterClass.InnerClass inner = new OuterClass.InnerClass(); 的方法来创建非静态成员类的实例。这是因为非静态成员类实例是依赖于外部类实例而存在的(包含外部类的引用),但如果我们有了一个外部类的实例outerClass,则可以用下面的方法来创建:
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
3. 局部类:
3.1. 可以做的:
能且只能访问所属代码段中声明为final的变量。这是因为局部变量在其所属的代码段(譬如某个函数)执行完毕后就会被回收,而一个局部类的实例却可以在其类定义所属代码段执行完毕后依然存在,如果它可操控非final的局部变量,用户就可以通过该实例修改已不存在的局部变量,无意义。
能且只能被final, abstract修饰
3.2. 不可以做的:
不能被public, private, protected修饰,也不能被static修饰。这是因为局部类只在定义它的代码段中可见,不能在它所属代码段之外的代码中使用
同理,局部类内部不能定义static成员
不能以局部类形式定义一个接口。因为局部类只在其所属代码段中可见,定义这样的接口无意义
4.匿名类
当前仅当匿名类处于非静态代码段时,他会有外部类的引用(即可以访问外部类任意资源)。局部类也是如此,不同的是匿名类在声明处的同时被初始化。
匿名类还有一个限制,就是不能同时实现多个接口,也不能同时实现一个接口并扩展一个基类。这是因为匿名类的声明和实例化是同时发生的,它不像通常的类的声明那样,可以通过implement关键字指定多个接口,或者通过exteds关键字来指定基类。对一个匿名类而言,他只能通过指定接口或基类的名字,来告诉编译器说:现在我要实现这个接口(or扩展这个基类),因此一个匿名类能且只能实现一个接口或者扩展一个基类
三. 内部类的使用及其意义
1. 静态成员类
静态成员类的通常用法是作为一个public helper class,与他的OuterClass结合在一起使用,为OuterClass服务。
比如一个Calculator类,他有各种操作,这些操作就可以存储在他的静态内部类Operation里,客户端就可以通过Calculator.Operation.PLUS或者Calculator.Operation.MINUS等来指定各种操作
那一个private的静态成员类可以用来干什么呢?可以用来代表外部类所需要的某个组件。
比如,对于Map接口,他内部就有一个Entry类,一个Entry对象对应着一个键-值对,该对象上的方法(getKey, getValue, setValue)也不需要访问外部类的资源,将Entry定义为private的静态成员类类就是最合适的。
2. 非静态成员类
一种通常的用法是把非静态成员类作为一个Adapter,使外部类的实例看起来提供了某个不相关的类的实例的功能。
比如,collection接口的各种实现类,一般就是用非静态成员类来实现他们的iterator:
public class MySet<E> extends AbstractSet<E> {
public Iterator<E> iterator() {
return new MyIterator();
}
private class MyIterator implements Iterator<E> {
}
}
关于静态成员类和非静态成员类,有一点需要注意的是:如果你的内部类不需要访问外部类的成员变量和函数(尤其是指非static类型的),那么尽量用静态成员类。为什么?因为每个非静态成员类的实例都将保存他的外部类的引用,这不仅造成时空上的浪费,而且可能导致当外部类实际上已经可以被垃圾回收器回收的时候,却因为某个非静态成员类实例还保留着该外部类的引用而不能回收该外部类。
3. 匿名类
匿名类用的地方应该是这四种内部类中最多的,至少我是这样。我经常用匿名类的地方就是在事件响应的代码中。
Effective Java中总结了三种匿名类的用途:
3.1. To create function objects on the fly
3.2. To create process objects, such as Runnable, Thread, TimerTask instances
3.3. To use within static factory methods
总结,
1.当内部类需要在方法外部仍然可见时,使用成员类(静态or非静态);当内部类比较长,放在方法内部会影响程序可读性时,使用成员类(静态or非静态);
2. 如果需要在内部类内部定义静态成员,只能使用静态成员类(其他三个都不支持);如果成员类的每个实例都需要外部类的引用,定义为非静态的,否则,就要定义成静态的;
3. 假设我们需要在方法内部定义一个局部类或者匿名类,如果我们只需要在这一个位置使用内部类实例,并且已经有预先定义好的基类或接口,那就使用匿名类;否则,使用局部类
分享到:
相关推荐
### Java语言基础小结 #### 一、JDK, JRE, JVM的关系 - **JVM(Java Virtual Machine)**:Java虚拟机是Java程序跨平台运行的核心,它负责执行Java字节码(.class文件),确保Java程序可以在不同的平台上运行而...
#### 七、小结 内部类为Java程序设计提供了强大的灵活性和封装能力。理解不同类型的内部类以及它们之间的差异对于编写高质量的Java代码至关重要。通过合理使用内部类,开发者可以更好地组织代码逻辑,提高代码的...
下面是对"Java中类的继承学习小结"的详细讲解。 首先,类的继承使用关键字`extends`来实现。子类(也称为派生类)声明时,会指定一个父类(或超类): ```java public class DerivedClass extends BaseClass { //...
成员变量`a`被声明为`private`,这意味着它只能在类的内部被访问,不能从类的外部直接访问,这是Java中的封装特性。`print()`方法没有参数,而`print(double a)`方法接受一个`double`类型的参数,并打印出传入的值。...
Java中的匿名内部类是Java语言的一个独特特性,它允许我们在编写代码时,无需定义一个完整的类,而是直接在需要的地方创建一个类的实例。这种方式在处理简单且仅需使用一次的类实现时非常方便,可以有效减少代码量,...
### Java笔试题小结 #### 1. Java 字符串(String) **问题**: String 类的特点是什么?与 StringBuffer 的区别? **分析与解答**: `String` 类在 Java 中是不可变的,即一旦创建了一个字符串对象,其内容就不能...
### 小结 Java集合框架为开发者提供了丰富的数据结构选择,每种数据结构都有其特定的优势和适用场景。理解这些集合的内部实现机制和性能特点,对于编写高效、可维护的代码至关重要。当面临具体问题时,应根据数据的...
如果一个方法或类过于庞大,应考虑拆分为多个小方法或类,以增加复用性和可维护性。 3. **避免在生产环境中使用System.out.println()**:在调试期间,可以使用System.out.println(),但在部署时,应替换为日志框架...
`this` 也可以用来区分外部类的成员变量和内部类的局部变量,或者引用外部类的实例。 总的来说,`this` 关键字是 Java 中用于表示对象实例自身的一个关键字,它帮助我们访问成员变量、区分同名变量、在构造器间...
本文档“JAVA程序:接口小结”很可能是对Java接口的全面总结,包含了相关的代码示例,帮助学习者深入理解和运用接口。 接口在Java中的主要特性包括: 1. **接口是完全抽象的**:接口中的所有方法默认都是抽象的,...
对于更精确的数学计算,Java提供了`BigDecimal`类,它可以处理任意精度的浮点数运算。 #### 八、Unicode编码 Unicode是一种编码标准,用于统一世界上的所有字符集。它包括ASCII、ISO8859-1、GBK(GB2312)等编码,...
Java是世界上最流行的编程语言之一,尤其在企业级应用开发中占据主导地位。...这只是Java J2SE和J2EE学习中的一小部分,要成为一名出色的Java开发者,还需要不断深入学习和实践,掌握更多的高级特性、框架和最佳实践。
【Java试题小结】 1. **Applet生命周期与方法**: - 在Java中,Applet的生命周期包括初始化、启动、绘画、停止和销毁几个阶段。`init()`方法用于初始化Applet所需资源,`paint()`方法负责在屏幕上绘制图形,如线条...
此外,为了提高代码可读性和可维护性,通常会使用匿名内部类来创建监听器。Java 8引入了Lambda表达式,可以更简洁地编写监听器: ```java loginButton.addActionListener(e -> { String username = usernameField....
6.3 小结:Java其实是个类和对象的世界 152 6.4 习题 153 第7章 Java中的方法——给汽车丰富多彩的功能 154 教学视频:2小时55分钟 7.1 方法:让汽车动开动 154 7.1.1 引出问题:开动汽车 154 7.1.2 那么,...
在Java中,我们使用`abstract`关键字来声明一个类为抽象类。抽象类可以包含0个或多个抽象方法,这些方法只有方法签名,没有具体实现。例如: ```java abstract class AbstractClass { abstract public void ...
1. **封装**:封装是面向对象的基础,它通过限制对类内部数据的直接访问,保护数据的安全性。在 Java 中,我们通常使用访问修饰符如 `private`, `default`, `protected` 和 `public` 来控制类的成员的可见性。getter...
### 小结 本文详细介绍了Java类的一些高级特性,包括抽象类、内部类的不同形式(成员内部类、局部内部类、匿名内部类、静态内部类)、Class类与Java反射的应用,以及注解的基本概念。通过学习这些内容,可以帮助...