Java基础 -- 嵌套类(非静态嵌套类、静态嵌套类)
可以将一个类的定义放在另一个类的内部定义,这样的类就被称为嵌套类,包含嵌套类的类被称为外部类(outer class),也可以叫做封闭类。
嵌套类可以分为两种:
- 静态嵌套类(Static Nested Classes):使用static声明,一般称为嵌套类(Nested Classes);
- 非静态嵌套类(Non-static Nested Classes):非static声明,一般称为内部类(Inner Classes);
嵌套类是它的外部类的成员。非静态嵌套类(内部类)可以访问外部类的其他成员,即使该成员是私有的,而静态嵌套类只能访问外部类的静态成员。
嵌套类作为外部类的一个成员,可以被声明为:private,包访问,protected,public
(注意:外部类只能被声明为
public
或者包范围)。
使用嵌套类的主要优点有以下几个方面:
- 嵌套类可以访问外部类的数据成员和方法,即使它是私有的;
- 提高可读性和可维护性:因为如果一个类只对另外一个类可用,那么将它们放在一起,这更便于理解和维护;
- 提高封装性:给定两个类A和B,如果需要访问A类中的私有成员,则可以将B类封装在A类中,这样不仅可以使得B类可以访问A类中的私有成员,并且可以在外部隐藏B类本身;
- 减少代码的编写量;
静态嵌套类可以使用外部类的名称来访问它。例如:
1 | OuterClass.StaticNestedClass |
如果要为静态嵌套类创建对象,则使用以下语法:
1 2 | OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass(); |
非静态嵌套类包含三种:
- 成员内部类(Member inner clss);
- 局部内部类(Local inner class);
- 匿名内部类(Anonymous inner class);
描述 | |
---|---|
成员内部类 | 在类中和方法外部创建的类 |
匿名内部类 | 为实现接口或扩展类而创建的类,它的名称由编译器决定 |
局部内部类 | 在方法内部创建的类 |
静态嵌套类 | 在类中创建的静态类 |
嵌套接口 | 在类中或接口中创建的接口 |
一 非静态嵌套类(内部类)
非静态嵌套类也就是内部类,它有以下几个特点:
- 实例化内部类必须先实例化一个外部类;
- 内部类实例与外部类实例相关联(内部类可以访问外部类的成员),所有不能在内部类中定义任何静态成员;
- 内部类是非静态的;
1、成员内部类
示例一
在外部类中并且在外部类的方法外创建的非静态嵌套类,称为成员内部类。说白了成员内部类就是外部类的一个非静态成员而已。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class Parcel1 { class Contents{ private int i = 11 ; public int value() { return i;} } class Destination{ private String label; Destination(String whereTo){ label = whereTo; } String readLable() { return label;} } public void ship(String dest) { Contents c = new Contents(); Destination d = new Destination(dest); System.out.println(d.readLable()); } public static void main(String[] args) { Parcell p = new Parcel1(); p.ship( "Zhengy" ); } } |
在ship()方法中使用成员内部类,与使用普通类没有什么区别,在这里,实际的区别就是内部成员类的定义是嵌套在Parcel1中。
示例二
下面我们尝试在外部类中定义一个方法,该方法返回一个指向成员内部类的引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public class Parcel2 { class Contents{ private int i = 11 ; public int value() { return i;} } class Destination{ private String label; Destination(String whereTo){ label = whereTo; } String readLable() { return label;} } public Destination to(String s) { return new Destination(s); } public Contents contents() { return new Contents(); } public void ship(String dest) { Contents c = new Contents(); Destination d = new Destination(dest); System.out.println(d.readLable()); } public static void main(String[] args) { Parcel2 p = new Parcel2(); p.ship( "南京" ); Parcel2 q = new Parcel2(); Parcel2.Destination d = q.to( "北京" ); Parcel2.Contents c = q.contents(); } } |
如果想在类外创建某个成员内部类的对象,必须像main()方法那样,具体指明这个对象的类型:OuterClassName.InnerClassName。
示例三
当创建一个成员内部类的对象时,此对象与创建它的外部类对象之间就有了一种联系,所以它能够访问其外部类对象的所有成员,而不需要任何特殊条件。下面的例子说明了这一点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | //类似迭代器 interface Selector{ boolean end(); //判断序列是否迭代结束 Object current(); //获取当前元素 void next(); //下一个元素 } //序列类(外部类) public class Sequence { //数组用于保存元素 private Object[] items; //当前追加索引 private int next = 0 ; public Sequence( int size) {items = new Object[size];} //向数组追加元素 public void add(Object x) { if (next < items.length) items[next++] = x; } //成员内部类:迭代器(可以访问外部类对象的所有成员),这里用来迭代序列中所有元素 private class SequenceSelector implements Selector{ private int i = 0 ; public boolean end() { return i == items.length;} public Object current() { return items[i];} public void next() { if (i < items.length) i++; } } //创建迭代器对象 public Selector selector() { return new SequenceSelector(); } public static void main(String[] args) { Sequence sequence = new Sequence( 10 ); for ( int i= 0 ;i< 10 ;i++) { sequence.add(Integer.toString(i)); } Selector selector = sequence.selector(); while (!selector.end()) { System.out.print(selector.current() + " " ); selector.next(); } } } |
输出结果如下:
1 | 0 1 2 3 4 5 6 7 8 9 |
上面演示了一个:"迭代器"设计模式的例子。
示例四
在成员内部类中如果需要创建对外部类对象的引用,可以使用外部类的名字后面跟着.this。下面演示如何使用.this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | ///使用.this public class DotThis { void f() { System.out.println( "DotThis.f()" ); } //成员内部类 public class Inner{ public DotThis outer() { return DotThis. this ; } } public Inner inner() { return new Inner(); } public static void main(String[] args) { DotThis dt = new DotThis(); DotThis.Inner dti = dt.inner(); dti.outer().f(); } } |
输出如下:
1 | DotThis.f() |
示例五
想要直接创建成员内部类的对象,不可以直接引用外部类的名字,而是必须使用外部类的对象来创建该成员内部类对象。在拥有外部类对象之前是不可能创建成员内部类对象的,这是因为成员内部类对象会暗暗地连接到创建它的外部类对象上。
但是,如果创建的是静态内部类,那么它就不需要对外部类对象的引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 | ///.new public class DotNew { public class Inner{ public void f(){ System.out.print( "test" ); } } public static void main(String[] args) { DotNew dn = new DotNew(); DotNew.Inner dni = dn. new Inner(); dni.f(); } } |
示例六
当将成员内部类(private修饰)向上转型为其基类,尤其是转为一个接口(public或者"包访问")时,由于成员内部类使用private修饰,因此可以隐藏其内部的具体实现,但是由于把成员内部类声明为private,因此不能访问该成员内部类任何新增的原本不属于公共接口的方法,所以扩展接口是没有意义的。下面演示一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | interface Destination{ String readLabel(); } interface Contents{ int value(); } class Parcel4{ //私有类,隐藏其具体实现 private class PContents implements Contents{ private int i = 11 ; public int value() { return i;} } protected class PDestination implements Destination{ private String label; private PDestination(String whereTo) { label = whereTo; } public String readLabel(){ return label; } } public Destination destination(String s) { return new PDestination(s); } public Contents contents(){ return new PContents(); } } public class TestParcel { public static void main(String[] args) { Parcel4 p = new Parcel4(); Contents c = p.contents(); Destination d = p.destination( "北京" ); } } |
示例七
一个成员内部类被嵌套多少层并不重要,它能透明的访问所有它所嵌入的外部类的所有成员,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class MNA{ private void f() {} //第一个内部类 class A{ private void g() {} //第二个内部类 public class B{ void h() { g(); f(); } } } } public class MultiNestingAccess { public static void main(String[] args) { MNA mna = new MNA(); MNA.A mnaa = mna. new A(); MNA.A.B mnab = mnaa. new B(); } } |
成员内部类的工作方式:
Java每个类都会产生一个.class文件,所以Java 编译器还会创建一个成员内部类OuterClass$MemberInnerClass.class
,成员内部类文件名格式为外部类名$成员内部类名
。
2 、局部内部类
局部内部类(Local inner class)通常定义在一个块中。所以通常你会在一个方法块中找到局部内部类。
正如局部变量那样,局部内部类的作用域受到方法的限制。它可以访问外部类的所有成员,和它所在的局部方法中所有局部变量。
如果你想调用局部内部类中的方法,你必须先在局部方法中实例化局部内部类。
局部内部类的一些规则:
- 无法从方法外部调用局部内部类;
- 局部内部类不能被声明为
private, public, protected;
- 在
JDK1.7
之前局部内部类不能访问非final
的局部变量,但是在JDK1.8
及之后是可以访问非final
的局部变量的;
示例一
在一个方法中定义一个局部内部类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class Parcel5 { public Destination destination(String s) { class PDestination implements Destination{ private String label; private PDestination(String whereTo) { label = whereTo; } public String readLabel(){ return label; } } return new PDestination(s); } public static void main(String[] args) { Parcel5 p = new Parcel5(); Destination d = p.destination( "北京" ); } } |
示例二
下面我们尝试在一个作用域内定义一个局部内部类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class Parcel6 { private void internalTracking( boolean b) { if (b) { class TrackingSlip{ private String id; TrackingSlip(String s){ id = s; } String getSlip() { return id; } } TrackingSlip ts = new TrackingSlip( "slip" ); String s = ts.getSlip(); } } public void track() { internalTracking( true ); } public static void main(String[] args) { Parcel6 p = new Parcel6(); p.track(); } } |
TrackingSlip类被嵌入在if语句的作用域中,这并不是说该类的创建是有条件的,它其实与别的类一起编译过了。然而,在定义TrackingSlip的作用域之外,它是不可用的,除此之外,它和普通的类一样。
局部内部类的工作方式:
Java每个类都会产生一个.class文件,所以Java 编译器还会创建一个名为localInner$Local.class
的文件。
3、匿名内部类
在 Java 中没有命名的内部类称为匿名内部类,当我们需要重写类或接口中的方法时,都会使用到它。匿名内部类在使用的时候将同时声明和实例化。
匿名类可以通过以下两种方式进行创建:
- 类(可能是抽象类或者其他类);
- 接口;
实例一
下面将会创建一个匿名类,该匿名类是接口Contents的实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | ///匿名内部类 public class Parcel7 { // public Contents contents() { //返回一个匿名类对象,该类是Contents接口的实现 return new Contents() { private int i = 11 ; public int value() { return i; } }; } public static void main(String[] args) { Parcel7 p = new Parcel7(); Contents c = p.contents(); } } |
上述代码中通过new表达式返回的引用 被自动向上转为对Contents类型的引用。上述匿名内部类的语法是下述代码的简化形式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | ///匿名内部类 public class Parcel7 { // public Contents contents() { // //返回一个匿名类对象,该类是Contents接口的实现 // return new Contents() { // private int i = 11; // public int value() { // return i; // } // }; // } class PContents implements Contents{ private int i = 11 ; public int value() { return i; } } public Contents contents() { return new PContents(); } public static void main(String[] args) { Parcel7 p = new Parcel7(); Contents c = p.contents(); } } |
实例二
在示例一匿名内部类中,使用的是默认构造函数,但是如果基类需要一个有参数的构造器,我们只需要简单的传递合适的参数给基类的构造器即可,下面演示一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class Wrapping{ private int i; //需要一个参数 public Wrapping( int x) { i = x; System.out.println( "基类构造器:" +x); } public int value() { return i; } } public class Parcel8 { public Wrapping wrapping( int x) { //一个匿名类 实现类Wrapping类,并传递x给基类的构造器 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 ); } } |
Wrapping是一个具有具体实现的简单类。我们将x传递进new Wrapping(x),这个x最终会传递给基类Wrapping的构造函数。
实例三
如果在匿名类中定义字段,还可以对其执行初始化,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | ///匿名类中初始化字段 public class Parcel9 { //dest用来初始化匿名类字段,必须声明为final 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( "北京" ); } |
如果我们我们定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是final,就像上面这个例子一样。
实例四
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了