梳理搭建地基「需要明确的知识点」
访问权限
- 顶层(类的可见性):public、包可见
- 方法/字段:public、包可见、private、protect
最佳方式是一个类文件一个类。想要实现一个文件里多个类,有两种方式选择:多个顶级类or嵌套类「内部类(非静态嵌套类)、静态嵌套类」
一个类文件中可以有很多顶级类,但只有那个与文件名同名的类允许被public修饰;嵌套类不允许被public修饰,只能是默认的包可见。
内部类和静态嵌套类
- 内部类是非static的,是“外部类的真正内部类”,可以直接访问外部类的成员字段/方法,“相当于外部类的一个成员”,非static与对象绑定,想要实例化,必须实例化
out之后用out.new; - 静态嵌套类是static,“相当于外部类的一个静态成员(与静态方法同类)”,使用与静态字段、静态方法相同,直接用即可
思维锚点:使用static标识符意味着和任何外部的实例对象脱钩,要确保其内部调用的所有实例成员都是自己重新实例化而来、静态成员都是
类.成员或者直接调用成员而来,与外界无调用关系- 内部类是非static的,是“外部类的真正内部类”,可以直接访问外部类的成员字段/方法,“相当于外部类的一个成员”,非static与对象绑定,想要实例化,必须实例化
在外包访问时,顶级类、内部类、静态嵌套类各自的可见性?
有几种情况:
- 包下面有很多的类文件,每个文件一个类。
- 包下面有一个类文件,文件中有很多顶级类
- 包下面有一个类文件,只有一个顶级类,类中有很多的内部类
- 第一种情况下,只要每个文件的类,是用public的,就可以被包外访问
- 第二种情况下,顶级类中,可以用public修饰的只有文件同名类,其他顶级类只允许是包可见。这种情况下,外部包永远不可能访问到其他顶级类
- 第三种情况下,(在顶级类修饰为public情况下):嵌套类设置为public,就可以包外可见;设置为默认(包可见),就包外不可见。和是否有static修饰符无关,static 只影响“要不要依赖外部类实例”,不影响访问权限
从语义上看,静态嵌套类更像“放在 Outer 名字空间里的(其他)顶级类”
- 两者相同:
- 调用public顶级类的静态字段/方法:可以直接
类.字段/方法调用 - 调用public顶级类的实例字段/方法:必须实例化这个public顶级类后,用
对象.字段/方法调用
- 调用public顶级类的静态字段/方法:可以直接
- 区别:
- 同个文件的各个顶级类,是包的成员;而静态嵌套类,是外部类的成员
- 各个顶级类,只有顶层访问权限(public或者包可见),意味着它一定对包内所有类可见 静态嵌套类,因为是外部类的成员,拥有成员访问权限(四种),意味着可以做到包内其他类不可见
- 静态嵌套类在外是
外部类.静态嵌套类,命名空间永远是在外部类之下。意味着永远表达:是外部类的附庸含义 - 「关键」静态嵌套类作为一个附庸的“特权”:外部类的
private static 成员,静态嵌套类是可以访问到的,其他顶级类不行
「单文件+多个顶级类」和「单文件+多个静态嵌套类」场景对比
「单文件+多个内部类」表示强绑定关系了,是外部类的对象的成员(实例成员)。这里对两个绑定关系没那么强的两个进行对比
- 当想要用private更细粒度控制访问权限时:用静态嵌套类模式
- 属于从属关系:静态嵌套类模式 属于有些许关联的特用工具类关系:多个顶级类模式
- 单文件+多个顶级类形式增加的类,一定是包可见的,外包无法读取。
综合考虑:多个文件存放、单个文件存放+多个顶级类、单个文件存放+多个静态嵌套类、单个文件存放+多个内部类
首先,最佳实践/常规做法是一个类一个文件,在包下多文件存放。
| 方案 | 类型“挂在谁名下” | 表达的关系 | 典型角色 | 优点 | 缺点 / 风险 |
|---|---|---|---|---|---|
| A 多个文件存放 | 每个顶级类是 包的成员,文件名和类同名 | 类型之间是“并列”的,同属一个包 | 包级别的业务对象、服务、工具类 | 结构清晰、符合主流规范;IDE / 构建工具友好;易读、易维护 | 文件会多,但正常项目这不是问题 |
| B 单文件+多个顶级类 | 这些顶级类也都是 包的成员(只是挤在同一文件) | 语义上仍是“并列的包内类型”,只是多了些许相关性,不太值得每个单开一个文件「极少用」 | 小 demo、小工具、一簇强相关但简单的类型 | 减少文件数量;非常小的模块写起来方便 | 不符合大多数代码规范;文件容易变长;阅读时不容易一眼看出“主角是谁” |
| C 单文件+多个静态嵌套类 | 静态嵌套类是 外部类的成员:Outer.Nested | 不会被同包的其他类复用表达“从属于某个类”的关系(子类型 / 实现细节) | Builder、Entry、内部实现类、只给某个类用的 helper | 能访问外部类的 private 成员;依附关系表达强;可以 private static 完全藏起来 | 层级变深,类膨胀易变 God class;别的类想复用时不自然(Outer.Helper) |
| D 单文件+多个内部类(非 static) | 内部类是 外部类实例的成员 | 不会被同包的其他类复用表达“和具体对象 强绑定”的关系 | 迭代器实现、状态机、回调对象、某个实例的子角色 | 直接访问外部对象的实例字段;封装“某种行为/状态”到独立类里 | 测试不便;容易产生隐式依赖;误用会导致内存泄漏风险;不适合做通用工具 |
- 其实没那么纠结(笑):
- 会被同包其他类复用
- 多个文件存放
- 不会,只服务一个类。「从概念上就属于这个类」
- 为了实现“类层级”的某个逻辑
- 单文件+多个静态嵌套类
- 为了实现“实例对象层级”的某个逻辑
- 单文件+多个内部类
- 当你需要一个“小对象”,而它需要频繁访问外部实例的字段/方法时 → 成员内部类特别合适。
- 为了实现“类层级”的某个逻辑
- 单文件+多个顶级类?
- 规范的话很少用。
- 不太值得单开一个文件/与public顶级类有些许关联/可能会被同包其他文件复用
- 在仅通过修改访问权限的前提下,用这种写法增加的类,是唯一一种永远不可能被外包读取的写法「他们访问类型只允许有一种:package-private」。看到这种情况可以直接认定是包可见的。
- 会被同包其他类复用