Java 嵌套类基础详解
1. 什么是嵌套类?
在 Java 语言中允许在另外一个类中定义一个类,这样的类被称为嵌套类。包含嵌套类的类称为外部类(outer class),也可以叫做封闭类,在本文中统一叫做外部类。
内部类的语法:
class OuterClass {
// code
class NestedClass {
// code
}
}
嵌套类分为两类:
- 静态嵌套类( Static nested classes ):使用
static
声明,一般称为嵌套类( Nested Classes )。- 非静态嵌套类( Non-static Nested Classes ):非
static
声明,一般称为内部类( Inner Classes )。
class OuterClass {
...
static class StaticNestedClass {
...
}
class InnerClass {
...
}
}
嵌套类是它的外部类的成员,非静态嵌套类(内部类)可以访问外部类的其他成员,即使该成员是私有的。而静态嵌套类只能访问外部类的静态成员。
嵌套类作为外部类的一个成员,可以被声明为:private
,public
,protected
或者包范围(注意:外部类只能被声明为public
或者包范围)。
2. 为什么要使用嵌套类?
使用嵌套类的主要优点有以下几个方面:
- 嵌套类可以访问外部类的所有数据成员和方法,即使它是私有的。
- 提高可读性和可维护性:因为如果一个类只对另外一个类可用,那么将它们放在一起,这更便于理解和维护。
- 提高封装性:给定两个类A和B,如果需要访问A类中的私有成员,则可以将B类封装在A类中,这样不仅可以使得B类可以访问A类中的私有成员,并且可以在外部隐藏B类本身。
- 减少代码的编写量。
3. 嵌套类的类型
前文已经介绍了嵌套类分为静态嵌套类和非静态嵌套类,而非静态嵌套类又称为内部类(内部类是嵌套类的子集)。
静态嵌套类可以使用外部类的名称来访问它。例如:
OuterClass.StaticNestedClass
如果要为静态嵌套类创建对象,则使用以下语法:
OuterClass.StaticNestedClass nestedObject =
new OuterClass.StaticNestedClass();
非静态嵌套类(内部类)又可以分为以下三种:
- 成员内部类(Member inner class)
- 匿名内部类(Anonymous inner class)
- 局部内部类(Local inner class)
类型 | 描述 |
---|---|
成员内部类 | 在类中和方法外部创建的类 |
匿名内部类 | 为实现接口或扩展类而创建的类,它的名称由编译器决定 |
局部内部类 | 在方法内部创建的类 |
静态嵌套类 | 在类中创建的静态类 |
嵌套接口 | 在类中或接口中创建的接口 |
4. 静态嵌套类
静态嵌套类其实就是在顶级类中封装的一个顶级类,它的行为和顶级类一样,它被封装在顶级类中其实就是为了方便打包。
- 静态嵌套类是外部类的静态成员,它可以直接使用
外部类名.静态嵌套类名
访问自身。 - 它可以访问外部类的静态成员和静态私有成员。
- 与其他类一样,静态嵌套类不能访问非静态成员。
静态嵌套类的语法如下:
class OuterClass{
// 外部类的静态数据成员
static int data = 30;
// 外部类中的静态嵌套类
static class StaticNestedClass {
// 静态嵌套类中的实例方法
void getData() {System.out.println("data is "+data);}
}
public static void main(String args[]){
// 实例化静态嵌套类(创建对象)
OuterClass.StaticNestedClass obj = new OuterClass.StaticNestedClass();
obj.getData();
}
}
输出结果:
data is 30
在上面的例子中,你需要先创建一个静态嵌套类的实例,因为你需要访问它的实例方法getData()
。但是你并不需要实例化外部类,因为静态嵌套类相对于外部类来说它是静态的,可以直接使用外部类名.静态嵌套类名
访问。
由编译器生成的静态嵌套类:
import java.io.PrintStream;
static class OuterClass$Inner
{
OuterClass$StaticNestedClass(){}
void getData(){
System.out.println((new StringBuilder()).append("data is ")
.append(OuterClass.data).toString());
}
}
从上面的代码可以看出,静态嵌套类实际上是直接通过OuterClass.data
来访问外部类的静态成员的。
使用静态嵌套类的静态方法示例:
如果静态嵌套类中有静态成员,则不需要实例化静态嵌套类即可直接访问。
class OuterClass2{
static int data = 10;
static class StaticNestedClass{
// 静态嵌套类的静态方法
static void getData() {System.out.println("data is "+data);}
}
public static void main(String args[]){
// 不需要创建实例可以直接访问
OuterClass2.StaticNestedClass.getData();
}
}
输出结果:
data is 10
5. 非静态嵌套类
非静态嵌套类也就是内部类,它有以下几个特点:
- 实例化内部类必须先实例化一个外部类。
- 内部类实例与外部类实例相关联,所有不能在内部类中定义任何静态成员。
- 内部类是非静态的。
下面看详细介绍↓
5.1 成员内部类
在外部类中并且在外部类的方法外创建的非静态嵌套类,称为成员内部类。说白了成员内部类就是外部类的一个非静态成员而已。
语法如下:
class OuterClass{
//code
class MemberInnerClass{
//code
}
}
成员内部类的示例:
在下面的示例中,我们在成员内部类中创建了getData()
和getTest()
这两个实例方法,并且这两个方法正在访问外部类中的私有数据成员和成员方法。
class OuterClass {
private int data = 30;
public void test() {System.out.println("我是外部类成员方法");}
class MemberInnerClass {
// 访问外部类的私有数据成员
void getData() {System.out.println("data is "+data);}
// 访问外部类的成员方法
void getTest() {OuterClass.this.test();}
}
public static void main(String args[]) {
// 首先必须实例化外部类
OuterClass obj = new OuterClass();
// 接着通过外部类对象来创建内部类对象
OuterClass.MemberInnerClass in = obj.new MemberInnerClass();
in.getData();
in.getTest();
}
}
输出结果:
data is 30
我是外部类成员方法
访问外部类的成员方法时,可以直接通过外部类名.this.外部类成员方法()
来调用,或者直接使用外部类成员方法()
来调用。
成员内部类的工作方式:
Java 编译器创建了两个.class
文件,其中一个是成员内部类OuterClass$MemberInnerClass.class
,成员内部类文件名格式为外部类名$成员内部类名
。
如果你想创建成员内部类的实例,你必须先实例化一个外部类,接着,通过外部类的实例来创建成员内部类的实例。因此成员内部类的实例必须存在于一个外部类的实例中。另外,由于内部类的实例与外部类的实例相关联,因此它不能定义任何静态成员。
由编译器生成的成员内部类代码:
在下面的例子中,编译器创建了一个名为OuterClass$MemberInnerClass
的内部类文件,成员内部类具有外部类的引用,这就是为什么它可以访问包括私有在内的所有外部类成员。
import java.io.PrintStream;
class OuterClass$MemberInnerClass {
final OuterClass this$0;
OuterClass$MemberInnerClass() {
super();
this$0 = OuterClass.this;// 引用了外部类
}
void getData() {
System.out.println((new StringBuilder()).append("data is ")
.append(OuterClass.access$000(OuterClass.this)).toString());
}
}
5.2 局部内部类
局部内部类(Local inner class)通常定义在一个块中。所以通常你会在一个方法块中找到局部内部类。
正如局部变量那样,局部内部类的作用域受到方法的限制。它可以访问外部类的所有成员,和它所在的局部方法中所有局部变量。
如果你想调用局部内部类中的方法,你必须先在局部方法中实例化局部内部类。
请看下面示例:
public class localInner {
private int data = 30;// 实例变量
// 实例方法
void display() {
// 局部内部类
class Local{
void msg() {System.out.println(data);}
}
// 访问局部内部类中的方法
Local l = new Local();
l.msg();
}
public static void main(String args[]){
localInner obj = new localInner();
obj.display();
}
}
输出结果
30
由编译器生成的局部内部类代码:
此时编译器创建了一个名为localInner$Local
的类,它具有外部类的引用
import java.io.PrintStream;
class localInner$Local {
final localInner this$0; // 自动生成的局部变量
localInner$Local() {
super();
this$0 = localInner.this;
}
void msg()
{
System.out.println(localInner.access$000(localInner.this));
}
}
局部内部类的一些规则:
- 无法从方法外部调用局部内部类
- 局部内部类不能被声明为
private, public, protected
- 在
JDK1.7
之前局部内部类不能访问非final
的局部变量,但是在JDK1.8
及之后是可以访问非final
的局部变量的。
局部内部类和局部变量的示例:
class localInner2 {
private int data = 30;//实例变量
void display() {
int value = 50;//在 Jdk1.7 之前局部变量必须被声明为'final'
class Local{
void msg() {System.out.println("value: "+value);}
}
Local l = new Local();
l.msg();
}
public static void main(String args[]){
localInner2 obj = new localInner2();
obj.display();
}
}
输出结果
value: 50
5.3 匿名内部类
在 Java 中没有命名的内部类称为匿名内部类,当我们需要重写类或接口中的方法时,都会使用到它。匿名内部类在使用的时候将同时声明和实例化。
匿名类可以通过以下两种方式进行创建:
- 类(可能是抽象类或者其他类)
- 接口
使用类创建匿名内部类:
abstract class Person {
abstract void eat();
}
public class TestAnonymousInner {
public static void main(String[] args){
// 使用匿名内部类实例化抽象类Person
Person p = new Person() {
void eat() {System.out.println("nice fruits");}
};
p.eat();
}
}
输出结果
nice fruits
上面代码中有以下一段:
Person p = new Person() {
void eat() {System.out.println("nice fruits");}
};
它主要有两个作用:
- 创建一个匿名内部类,它的名称由编译器决定,并给出了
Person
类中eat()
方法的实现。 - 创建匿名类的对象,并使用了类型为
Person
的引用变量p
来引用。
由编译器生成的匿名内部类代码:
import java.io.PrintStream;
static class TestAnonymousInner$1 extends Person
{
TestAnonymousInner$1(){}
void eat() {
System.out.println("nice fruits");
}
}
从以上代码,我们可以看出,编译器实际上将匿名内部类命名为TestAnonymousInner$1
,并继承了抽象类Person
。
使用接口创建匿名内部类:
interface Eatable{
void eat();
}
class TestAnnonymousInner1{
public static void main(String args[]){
// 创建匿名内部类来实现接口Eatable
Eatable e = new Eatable() {
public void eat(){System.out.println("nice fruits");}
};
e.eat();
}
}
输出结果
nice fruits
上面代码有以下一段:
Eatable e = new Eatable() {
public void eat(){System.out.println("nice fruits");}
};
它主要有两个作用:
- 创建一个匿名类,它的名称由编译器决定,并给出了
eat()
方法的实现。 - 创建匿名类的对象,并使用了类型为
Person
的引用变量p
来引用。
由编译器生成的匿名内部类代码:
import java.io.PrintStream;
static class TestAnonymousInner1$1 implements Eatable
{
TestAnonymousInner1$1(){}
void eat(){System.out.println("nice fruits");}
}
从以上代码我们可以看出,编译器实际上创建了一个名为TestAnonymousInner1$1
的类,并使用该类实现了Eatable
接口。
6. 嵌套接口
前文主要介绍了嵌套类相关的知识,Java 中嵌套接口与嵌套类类似,下面我们来了解一下关于嵌套接口方面的知识。
嵌套接口(Nested Interface)指的是在一个类或接口中创建的接口。嵌套接口用于将相关的接口进行分组以便于维护。嵌套接口必须由外部接口或外部类引用,它不能直接访问。
需要记住的关于嵌套接口的要点:
这里给出了 Java 程序员应该记住的关于嵌套接口的要点:
- 在接口中声明的嵌套接口,默认修饰符为
public
,也必须为public
,但在类中声明嵌套接口则可以使用任何访问修饰符。 - 嵌套接口都被隐世声明为静态的。
在接口中声明嵌套接口:
interface interface_name{
...
interface nested_interface_name{
...
}
}
在类中声明嵌套接口:
class class_name{
...
interface nested_interface_name{
...
}
}
在接口中声明嵌套接口的示例:
在下面这个例子中,我们将学习如何声明嵌套接口以及如何使用它。
interface Showable{
void show();
// 声明嵌套接口
interface Message{
void msg();
}
}
class TestNestedInterface1 implements Showable.Message{
// 实现嵌套接口Message中的msg()方法
public void msg() {System.out.println("Hello nested interface");}
public static void main(String args[]){
// 实例化TestNestedInterface1类,向上转型为Showable.Message接口类型
Showable.Message message = new TestNestedInterface1();
message.msg();
}
}
输出结果:
Hello nested interface
正如上面的示例中所看到的,我们不能直接访问嵌套接口Message
,但是我们可以通过外部接口Showable
来访问它。
就好比房间中的衣柜,我们不能直接拿到衣柜中的衣服,我们必须先进入房间才行。
由编译器生成的嵌套接口Message的代码:
public static interface Showable$Message
{
public abstract void msg();
}
从上面代码可以看出,嵌套接口实际被声明为静态的了。
在类中声明嵌套接口的示例:
class A{
interface Message{
void msg();
}
}
class TestNestedInterface2 implements A.Message{
// 实现Message接口中的msg()方法
public void msg() {System.out.println("Hello nested interface");}
public static void main(String args[]){
// 实例化TestNestedInterface2类,并向上转型为A.Message类型
A.Message message = new TestNestedInterface2();
message.msg();
}
}
输出结果:
Hello nested interface
最后思考一下,我们可以在接口中定义一个类吗?
答案是可以的。当我们在接口中定义类时,Java 编译器会自动创建一个静态嵌套类。
interface M{
class A{}
}
Java 嵌套类基础详解的更多相关文章
- Kotlin——最详细的抽象类(abstract)、内部类(嵌套类)详解
如果您对Kotlin很有兴趣,或者很想学好这门语言,可以关注我的掘金,或者进入我的QQ群大家一起学习.进步. 欢迎各位大佬进群共同研究.探索QQ群号:497071402 进入正题 在前面几个章节中,详 ...
- Java Garbage Collection基础详解------Java 垃圾回收机制技术详解
最近还是在找工作,在面试某移动互联网公司之前认为自己对Java的GC机制已经相当了解,其他面试官问的时候也不存在问题,直到那天该公司一个做搜索的面试官问了我GC的问题,具体就是:老年代使用的是哪中垃圾 ...
- java Object类源代码详解 及native (转自 http://blog.csdn.net/sjw890821sjw/article/details/8058843)
package java.lang; public class Object { /* 一个本地方法,具体是用C(C++)在DLL中实现的,然后通过JNI调用.*/ private static na ...
- Java常用类StringBuffer详解
内容多为最近学习的自我总结,可能有些地方写的不严谨,甚至会有错误的地方,仅供参考,如发现错误敬请指出,谢谢! 灰色字体为补充扩展内容,多为帮助自己理解. StringBuffer概述: 线程安全的可变 ...
- Java常用类object详解
1.Object概述: 类Object是类层次结构的根类.每个类都使用Object作为超类.所有对象(包括数组)都实现这个类的方法. 2.构造方法详细信息: Object只有一个无参构造方法,因为ob ...
- Java的类的详解
首先呢,我承认上一次我理解的有误. 1.构造方法的作用:是初始化一个对象,而不是成员变量,它和get和set方法都有给成员变量赋值的功能. 2.下来说一下JVM调用main方法的过程: a.静态变量赋 ...
- 11-02 Java Object类使用详解
Object 作为超类 Object是类层次结构的根类,所有的类都直接或者间接的继承自Object类. Object类的构造方法有一个,并且是无参构造,这其实就是理解当时我们说过,子类构造方法默认访 ...
- Java工具类DateFormatUtils详解
日期和时间格式化实用程序和常量public static String format(Calendar calendar, String pattern) 说明:将日历格式化为特定的模式:参数:cal ...
- Properties类使用详解
Java Properties类使用详解 概述 Properties 继承于 Hashtable.表示一个持久的属性集,属性列表以key-value的形式存在,key和value都是字符串. Pr ...
随机推荐
- 设计模式 --> (12)装饰模式
装饰模式 时常会遇到这样一种情况,我已经设计好了一个接口,并且也有几个实现类,但是这时我发现我设计的时候疏忽了,忘记了一些功能,或者后来需求变动要求加入一 些功能,最简单的做法就是修改接口,添加函数, ...
- [poj3280]Cheapest Palindrome_区间dp
Cheapest Palindrome poj-3280 题目大意:给出一个字符串,以及每种字符的加入代价和删除代价,求将这个字符串通过删减元素变成回文字符串的最小代价. 注释:每种字符都是小写英文字 ...
- 测试对bug如何分析和定位
如何去区分一个功能测试工程师的水平高和低? 可以从很多个方面去检查,比如测试的思路, 比如测试用例的覆盖度?,比如测试出bug是否能够定位到根因? 上面说的各个方面都很合理,那我们平常如何如更深的定位 ...
- Java中的序列化与反序列化
序列化定义 将对象转换为字节流保存起来,并在以后还原这个对象,这种机制叫做对象序列化. 将一个对象保存到永久存储设备上称为持久化. 一个对象要想能够实现序列化,必须实现java.io.Serializ ...
- OC实现单选和多选按钮
本代码库暂时有OC封装,改天有空在补一个Swift封装的,主要是因为swift不是那么熟,怕出错,半天找不到问题多尴尬呀! 先附上demo下载地址CSDN:http://download.csdn.n ...
- Notepad++中实现Markdown语法高亮与实时预览
Notepad ++是一个十分强大的编辑器,除了可以用来制作一般的纯文字说明文件,也十分适合编写计算机程序代码.Notepad ++不仅有语法高亮度显示,也有语法折叠功能,并且支持宏以及扩充基本功能的 ...
- 集合源码(一)之hashMap、ArrayList
HashMap 一.HashMap基本概念: HashMap是基于哈希表的Map接口的实现.此实现提供所有可选的映射操作,并允许使用null值和null键.此类不保证映射的顺序,特别是它不保证该顺序恒 ...
- Java基础学习笔记二十二 网络编程
络通信协议 通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样.在计算机网络中,这些连接和通信的规则 ...
- hibernate框架学习笔记10:HQL查询详解
HQL语句中不可以出现与表有关的内容,而是对象的属性 实体类(注意配置文件): package domain; import java.util.HashSet; import java.util.S ...
- 如何在http请求中使用线程池(干货)
这段时间对网络爬虫比较感兴趣,实现起来实际上比较简单.无非就是http的web请求,然后对返回的html内容进行内容筛选.本文的重点不在于这里,而在于多线程做http请求.例如我要实现如下场景:我有N ...