第4章 类和接口
类和接口是Java程序设计语言的核心,它们也是Java语言的基本抽象单元。
初学Java的时候感觉类和接口都好简单,后续慢慢发现类和接口的设计并非是想象总的那么简单的,还有好多需要学习。
而这一章作者就阐述了一些指导原则,指导我们设计出更加有用、健壮和灵活的类和接口,很有意义。
第13条 使类和成员的可访问性最小化
设计良好的模块对外部而言总是隐藏了所有的细节
模块之间中通过它们的API进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装,是软件设计的基本原则之一(还是封装好听啊,信息隐藏好low啊)
封装可以有效解除各个模块之间的耦合关系,是现在模块化开发的基础,使模块可以独立地开发、测试、优化、使用、理解和修改。
尽可能地使每个类或者成员不被外界访问。
Java中可以通过包、访问修饰符(private protected public)控制类、接口和成员的可访问性(accessibility)
除了应该暴露的API之外,我们应该尽可能少的开放访问权。
因为一旦暴露给外界,可能会有风险,另外还需要保证一直维护与兼容。
可以想象一下,当我们使用一个第三方库的时候,如果它暴露了一个不该暴露的类(假设为BitmapUtil),而我们恰好用了它里面的方法,结果它一升级,把方法改了,或者把方法去掉了,那我们不是懵了?
所以不要去暴露那些不该暴露的类、接口、成员
小结
作者在最后给了小结:应该始终尽可能地降低可访问性。应该防止把任何散乱的类、接口和成员变成API的一部分。除了公有静态final域的特殊情形之外,共有类都不应该包含公有域。并且要确保公有静态final域所引用的对象都不是可变的
第14条 在共有类中使用访问方法而非公有域
假设有一个类:
public class Point{
public double x;
public double y;
}
作者的意思是不应该直接暴露x y
,要为它们提供getter
、setter
方法,这样有利于添加约束条件,辅助行为。
本人表示道理我懂,但是现在一般的类都不愿意去写getter setter了,真心觉得好烦啊,虽然方法可以用AS自动生成,我还是不太愿意去写,直接public就是那么任性
第15条 使可变性最小化
不可变类:实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期内固定不变。如:String、基本类型的包装类、BigInteger和BigDecimal
不可变类比可变类更加易于设计、实现和使用。它们不容易出错,且更加安全。
不可变类的五条规则:
- 不要提供任何会修改对象状态的方法。
- 保证类不会被扩展。 一般用final修饰
- 使所有的域都是final的。
- 使所有的域都成为私有的。(降低访问权限)
- 确保对于任何可变组件的互斥访问。(什么意思呢?)
不可变类的优缺点
线程安全
不要求同步,无惧多线程并发访问
不可变对象可以被自由地共享
所以不需要保护性拷贝(如String类的拷贝构造器)
也可以重复利用,如:Boolean.FALSE/TRUE
不可变对象为其他对象提供了大量的构建(building blocks)
不是很懂
唯一的缺点:对于每个不同的值都需要一个单独的对象
如FALSE TRUE ,不过如果值少,到也没什么关系
但是如String这样的不可变类,我们需要注意,多用StringBuilder(可变,性能好)
小结
除非有很好的理由要让类成为可变的类,否则就应该是不可变的。
如果类不能被做成是不可变的,仍然应该尽可能地限制它的可变性。(降低出错的可能性)
第16条 复合优先于继承
在读HeadFirstDesignPattern的时候已经看到太多次了
这里的继承是指
实现继承
(implementation inheritance)也即extends
而不是接口继承
复合(composition)也应该是常听到的组合
继承打破了封装性 子类依赖于超类中特定功能的实现细节 当超类发生改变,子类可能会遭到破坏
比如随着版本的发布,超类需要新增方法,但是这些方法不是所有子类需要的,那么就破坏了子类!
复合(组合)
复合,即使用包装类(wrapper class),其实这也就是设计模式中的装饰者模式
另外值得一提的是,复合以及转发并不是委托(delegation)
装饰者模式的优缺点不多说了,可以看设计模式的笔记
小结
继承功能确实强大,但它也存在很多问题,比如违背了封装原则(是不是很矛盾?),对于两个类,它们确实有is-a
的关系时候才使用继承!
所以使用继承的时候要考虑清楚
第17条 要么为继承而设计,并提供文档说明,要么就禁止继承
好的API文档应该描述一个给定的方法做了什么工作,而不是描述它是如何做到的
如标题,恩,我可是连注释都懒得写的人,怎么会写文档。。。
第18条 接口优于抽象类
Java提供两种机制用来定义允许多个实现的类型:接口
和抽象类
。
区别
接口和抽象类的区别有很多,其中 最为明显 的区别是: 抽象类可以包含某些方法的实现,而接口不允许,即接口都是抽象方法
而另外还有一个 更重要 的区别是:为了实现由抽象类定义的类型,类必须成为抽象类的一个子类。而Java是单继承的,所以抽象类作为类型定义受到了极大的限制,而接口没有这个限制。
接口的优势
现有的类可以很容易被更新,以实现新的接口
当需要增加方法的时候只需要implements
具体的接口即可,非常方便,而如果通过抽象类来实现,则需要在抽象类里新增方法,而这会导致其他继承该抽象类的类也被强制加上额外的方法!接口是定义mixin(混合类型)的理想选择
mixin是指主要的类型: 类除了实现它的"基本类型"之外,还可以实现这个mixin类型(一脸懵逼!这翻译的什么玩意?)Comparable
是一个mixin接口
我的理解是:一个类,利用实现多个接口可以达到混合类型的目的,而利用抽象类只能继承一个类,则不能达到混合类型的效果!
- 接口允许我们构造非层次结构的类型框架
第19条 接口只用于定义类型
我们知道当类实现接口时,我们可以把该类的类型当做是接口的类型来使用,这是我们定义接口的唯一目的,也即接口 只应该用来定义类型
看到这里,可能你会跟我一样奇怪,接口不就是用来定义类型的吗,还能用来干嘛?
有一种接口被称为 常量接口 ,就是没有方法,只有常量的接口,这常量接口模式是对接口的不良使用,因为它没什么卵用还会污染实现类
Java中有几个常量接口,如java.io.ObjectStreamConstants
,千万别学啊!~
建议常量用工具类
或者枚举
或者@IntDef
注解来实现
第20条 类层次优于标签类
标签类,书中对它的定义说得很拗口。
我的理解是一个类,拥有多个风格,通过一个属性来区分不同的风格,类里充斥着if else
或者switch case
举个例子:
class Person{
boolean isMan;
String sayHi(){
if (isMan) {
return "Yo hi man!~";
}else{
return "Hello";
}
}
}
Person类,通过isMan
属性来区分是男的还是女的,sayHi()
方法针对男女有不同的表现,这个就是一个非常简单的标签类
标签类的缺点非常明显,当你要表现的风格非常多样的时候,你需要写大量的判断语句,非常容易出错,而且当你需要修改某一个风格的时候,你需要在一大堆代码里找出你要改的地方,很有可能引入bug,非常难以维护。
这个时候,将标签类转变成类层次就非常方便了:
abstract class Person{
abstract String sayHi();
}
class Man extends Person{
@Override
String sayHi() {
return "Yo hi man!~";
}
}
class Woman extends Person{
@Override
String sayHi() {
return "Hello";
}
}
抽象出一个Person
类,定义Man
和Woman
类继承它,根据自己的需求实现sayHi()
方法,当需要修改Man
的行为时,你不需要也不用担心会破坏Woman
的代码,代码可读性,可维护性一下子高了很多,有木有?!
第21条 用函数对象表示策略
函数对象
的概念:如果一个类仅仅导出这样的一个方法(执行其他对象(这些对象被显示传递给这些方法)上的操作),它的实例实际上就等同于一个指向该方法的指针.
这样的实例被称为函数对象(function object
).
如:
class StringLengthComparator{
public int compare(String s1,String s2){
return s1.length()-s2.length();
}
}
StringLengthComparator
类即是一个典型的函数对象,指向StringLengthComparator
对象的引用可以被当做是一个指向该比较器的函数指针(function pointer)
,它也是一个用于字符串比较操作的具体策略,这个策略为策略模式中的策略,并且函数指针的主要用途就是实现策略模式.(关于策略模式这里就不多讲,推荐看<<Head First Design Pattern>>,如果你不着急,可以等我出笔记~)
PS:指针的英语是 pointer
第22条 优先考虑静态成员类
嵌套类
(nested class)是指被定义在另一个类的内部的类,它存在的目的应该只是为它的外围类(enclosing class)提供服务.
嵌套类分为四种:
- 静态成员类(static member class) (不是内部类)
- 非静态成员类(nonstatic member class)
- 匿名类(anonymous class)
- 局部类(local class)
除了第一种之外,其他三种都被称为 内部类(inner class)
静态成员类 & 非静态成员类
最简单的嵌套类,最好把它看成普通的类,只是碰巧被声明在另一个类的内部而已(挺不错的解释),所以它可以脱离外部类单独存在。
它可以访问外部类的所有成员,包括private
修饰的,也可以被访问修饰符修饰,来控制它的可见性。
作用:静态成员类一般是用来辅助外部类的,比如CoordinatorLayout
类中的Behavior
类,它定义了一系列的行为用于辅助它的外部类CoordinatorLayout
,例子有很多,不多说了。
静态成员类 & 非静态成员类 的差别
虽然它们只差了一个static
修饰符,但是其实它们差别巨大。
非静态成员类的实例都隐含持有一个外部类的实例(enclosing instance)
这不仅仅会消耗更多的空间,还可能会导致外部类的实例泄漏,内存泄漏,而静态成员类并不会。
这在Android中很常见,比如我们使用Handler
的时候,AS都会提示我们这可能造成内存泄漏,让我们使用静态成员类。
所以通常情况更推荐静态成员类,书中有一句话:如果声明成员类不要求访问外围实例,就要始终把static修饰符放在它的声明中,使它成为静态成员类,而不是非静态成员类
匿名类 & 局部类
匿名类,没有名字,也不是外部类的成员,它是在使用的同时被声明和实例化
需要注意的是:当匿名类出现在非静态的环境中时,它会持有外部类的实例,所以它可能引起内存泄漏。
匿名类的作用:通常用于创建函数对象(见21条),比如Thread
,Runnable
局部类非常少用,自己没用过,在源码里也没留意到它的存在,就不多写了。
小结
虽然本条推荐静态成员类,不过每个嵌套类都有自己的用途,还是得按实际情况去抉择。
本章小结
本章的内容在Java中非常重要,如果要提升架构能力,那么本章的学习必不可少!