如果你想要演变的容易性比功能、灵活性更重要,而且你接受抽象类的局限性,就用抽象类。
接口->骨架实现类->简单实现类。
19 接口只用于定义类型
除此之外,为了任何其他的目的而定义接口是不恰当的。
说白了,定义接口是为了接口的行为。
有一种接口是常量接口,这是对接口的不良使用。因为常量是实现细节,不应该泄漏出去。对类的用户来说没有价值,反而会干扰。而且如果非final类实现了常量接口,它的所有子类的命名空间也会被接口中的常量所“污染”。
定义常量应该用枚举或者不可实例化的工具类。
20 类层次优于标签类
想到了自己项目里 群聊创建模块。 原本就是一个标签类,改造成了类层次。
类层次好处:
简单清晰,没有样板代码,不受无关数据域的拖累,多个程序员可独立扩展层次结构。而且类层次可以反映类型之间本质上的层次关系,有助于增强灵活性。
标签类有很多缺点:
-
可读性差
-
内存占用增加因为实例承担着属于其他风格的不相关的域。
-
域不能是final的。
总之它过于冗长,容易出错,效率低下。
其实标签类是对类层次的一种简单的效仿。
将标签类转化成类层次:
-
为标签类的每个方法都定义一个包含抽象方法的抽象类,这里的每个方法的行为都依赖于 标签值。
-
如果有行为不依赖于标签值的方法,就将这些方法放在抽象类里。
-
如果所有的方法都用到了某些域,则将域也放在抽象类里。
-
为每种原始标签书写相应的子类。
21 用函数对象表示策略
如果一个类,它只有一个方法,这个方法执行其他对象(通过参数传入)上的操作。
这个类的实例实际上等同于一个指向该方法的指针。
这样的实例称之为函数对象。也是一个具体策略类。
我们在设计具体的策略类时,还需要定义一个策略接口。
总结:函数指针的主要用途就是实现策略模式。
为了在java中实现这个模式,要声明一个接口来表示该策略,并且为每个具体策略实现该接口。
当具体策略只使用一次时,通常用匿名内部类来做。
当重复使用时,通常实现为某个类私有的静态成员类,并通过 公有 静态 final 域 被导出,域类型是策略接口类型。
22 优先考虑静态成员类
嵌套类指定义在另一个类内部的类。目的是 只为它的外围类服务。
如果嵌套类将来可能用于其他的环境中,它就应该是顶层类。
嵌套类四种:
-
静态成员类(优先)
-
非静态成员类
-
匿名类(不能拥有静态成员,如果只有一处创建的地方)
-
局部类(有多处创建的地方)
后三种被称为内部类。
静态成员类是最简单的一种嵌套类,最好把它看作是普通的类,只是碰巧被声明在另一个类的内部而已。
如果声明内部类不要求访问外围实例,就要声明称 静态成员类。
内部类会包含一个额外的指向外围对象的引用,保存这份引用要消耗时间和空间,也会影响GC.
匿名类的常见用法:
-
动态创建函数对象。(21条)
-
创建过程对象,比如Runnable、Thread。
-
静态工厂方法的内部。
第五章 泛型
出错之后应该尽快发现,最好是在编译时就发现。
23 请不要在新代码中使用原生态类型、
List<T>
对应的原生态类型就是List
,如果使用原生态类型,就丢掉了泛型在安全性和表述性方面的所有优势。它只是为了兼容遗留代码的。
无限制的通配符类型:List<?>
,等于List
,它们和List<Object>
都不相等。
在类文字 中必须使用原生态类型。List.class String[].class ini.class
都合法,List<String>.class 和List<?>.class
都不合法。
24 消除非受检警告
尽可能地消除每一个非受检警告。 如果消除所有警告,则可以确保代码是类型安全的。意味着在运行时不会出现ClassCastException异常。
如果无法消除警告,同时可以证明引起警告的代码是类型安全的,只有在这种情况之下,才可以用一个@SuppressWarnings(“unchecked”)注解来禁止这条警告。
应该在尽可能小的范围里使用该注解。并添加注释解释为什么这么做是安全的。
25 列表优先于数组
数组与泛型相比,有两个重要的不同:
一 数组是协变的,泛型是不可变的。
如果Sub是Super的子类型,那么Sub[]就是Super[]的子类型。
然而泛型则不是。对于任意两个不同的类型Type1,Type2,List<Type1>和List<Type
>不可能是子类型或父类型的关系。
实际上,这不是泛型的缺陷,反而是数组的缺陷:
//fails at runtime!
Object[] objectArray = new Long[1];
objectArray[0] = “I don’t fit in”;//Throws ArrayStoreException
//Won’t compile
List listObject = new ArrayList();//Incompatible types
上述代码,无论哪种方法,都不能将String放入Long容器中。但是利用列表,可以在编译时发现错误。
二 数组是具体化的 在运行时才知道自己的类型,泛型通过擦除,在运行时丢弃类型信息
一般来说,数组和泛型不能很好地混合使用。
如果混合使用出现了编译时错误,应该用列表替代数组。
26 优先考虑泛型
不能直接创建 new T[16],可以通过创建 (T[])new Object[16];强转。也可以在 取出数据时强转。
有些泛型如ArrayList,必须在数组上实现。
为了提升性能,其他泛型如HashMap也在数组上实现。
27 优先考虑泛型方法
静态工具方法尤其适用于泛型化。
泛型方法一个显著特征,可以配合 类型推导,无需明确指定类型参数的值。
28 利用 有限制通配符 来提升API的灵活性
PECS表示,producer(取)-extends,consumer(存)-super。
还要记住所有的comparable和comparator都是消费者。
为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数上使用通配符类型。
如果某个输入参数 即是生产者又是消费者,则通配符类型对你没有好处,需要严格的类型匹配。
不要用通配符作为返回类型。
如果类的用户必须考虑通配符类型,类的API或许就会出错。
显式的类型参数:
Set numbers = Union.union(integers,doubles);
一个复杂的例子:
public static <T extends Comparable<? super T>> T max(List<? extends T> list)
类型参数和通配符之间具有双重性,许多方法都可以利用其中一个或者另一个进行声明。
一般来说,如果类型参数只在方法声明中出现一次,就可以用通配符取代它。
在公共API中,推荐第二种。
例如:
//类型参数和通配符之间具有双重性,许多方法都可以利用其中一个或者另一个进行声明
//一般来说,如果类型参数只在方法声明中出现一次,就可以用通配符取代它。
public static void swap1(List list, int i, int j) {
}
public static void swap2(List<?> list, int i, int j) {
}
public static void swap3(E list, int i, int j) {
}
//这个是错误的, 因为E不是类型参数
/* public static void swap4(? list, int i, int j) {
}*/
29 优先考虑 类型安全 的 异构容器
类型安全:当你向它请求String时,它从来不会返回一个Integer给你
异构容器:不像普通的map,它的所有key都是不同类型的。
泛型最常用于集合。
当一个类的字面文字(Class<T>
)被用在方法中,来传达编译时和运行时的类型信息时,就被称作type token
。
注解API广泛利用了有限制的类型令牌。 被注解的元素,本质上是个类型安全的异构容器。
第六章 枚举和注解
1.5中增加了 两个新的引用类型:枚举类型、注解类型。
30 用enum代替 int 常量。
枚举易读,提供了编译时的类型安全。
允许添加任意的方法和域(本质上是个类)。
如果枚举具有普适性,应该成为一个顶层类。如果用在特定顶层类中,应该成为该顶层类的一个成员类。
但枚举有小小的性能缺点,装载和初始化枚举时会有空间和时间的成本。
31 用实例域 代替序数(ordinal())
永远不要根据枚举的序数导出与它关联的值,而是要将它保存在一个实例域中。
最好完全避免ordinal方法的使用。
32 用EnumSet 代替位域
位域有更多缺点,打印时翻译位域更困难,遍历位域也困难。
EnumSet内部用单个long保存,(枚举类型小于等于64个)
枚举类型要用在集合中,所以没有理由用位域来表示它。
用EnumSet,简洁、性能也有优势。
public static void main(String[] args) {
EnumSet
System.out.println(bold);
}
enum Style {
BOLD, ITALIC, UNDERLINE;
}
//[BOLD, ITALIC]
33 用EnumMap 代替序数(ordinal())索引
按Enum分组索引,key是Enum。
最好不要用序数来索引数组,而要使用EnumMap。
34 用接口模拟可伸缩的枚举
用接口模拟可伸缩枚举有个小小的不足,无法从一个枚举类型继承到另一个枚举类型。
虽然无法便携可扩展的枚举类型,却可以通过编写接口以及实现该接口的基础枚举类型,对它进行模拟
35注解优先于 命名模式
命名模式缺点:
-
文字拼写错误会导致失败,且没有任何提示。
-
无法确保它们只用于相应的程序元素上。
-
没有提供将参数值与程序元素关联起来的好办法。
36坚持使用Override注解
37用标记接口定义类型
标记接口是没有包含方法声明的接口,而只是知名一个类实现了具有某种属性的接口。
例如Serializable。
如果想要定义类型,一定要使用接口
第七章方法
38检查参数的有效性
应该在文档中清楚的指明参数的限制 以及 抛出的异常。
并在方法体的开头检查参数。
这是“应该在发生错误之后尽快检测出错误”原则的具体情形。
检查构造器参数的有效性是非常重要的。
39必要时进行保护性拷贝
编写一些面对客户的不良行为时仍能保持健壮性的类,是非常值得的。
对构造器的每个可变参数进行保护性拷贝是必要的。
保护性拷贝是在检查参数的有效性(上一条38)之前进行的,并且有效性检查是针对拷贝之后的对象,而不是针对原始的对象。 这样做可以避免在“危险阶段”(从检查参数开始,直到拷贝参数之间的时间段)期间从另一个线程改变类的参数。
对于参数类型可以被不可信任方子类化的参数,请不要使用clone方法进行保护性拷贝。
返回可变内部域的保护性拷贝
总结:
如果拷贝的成本受到限制,并且类信任它的客户端不会不恰当的修改组件,就可以在文档中说明客户端的职责,代替保护性拷贝。
40谨慎设计方法签名
本条目是若干API设计技巧的总结。
-
**谨慎地选择方法的名称。**易于理解、风格一致、大众认可,可参考Java类库。
-
**不要过于追求提供便利的方法。**方法太多会使类难以学习、使用、文档化、测试和维护。为每个动作提供一个功能齐全的方法,只有当一项操作被经常用到的时候,才考虑为它提供快捷方式,如果不确定,还是不提供快捷为好。
-
**避免过长的参数列表。**目标是四个参数或更少。 相同类型的长参数序列格外有害。(拆分、辅助类、Builder)
-
对于参数类型,要优先使用接口而不是类。
-
**对于boolean参数,要优先使用两个元素的枚举类型。**易于阅读和编写,更易于后期添加更多的选项。
41 慎用重载
重载是发生在编译时的,所以严格的说,它并不是多态。
要调用哪个重载方法是在编译时做出决定的。
对于重载方法的选择是静态(编译时的对象类型决定)的,对于覆盖的方法的选择则是动态(运行时的对象类型决定)的。
避免胡乱地使用重载机制。
安全而保守的策略是,永远不要导出两个具有相同参数数目的重载方法。
如果方法使用可变参数,保守的策略是根本不要重载它。
例如,考虑ObjectOutputStream类,对于不同类型,提供诸如writeBoolean(boolean)、writeInt(int)方法,好处还可以提供对应的readBoolean()readInt()方法。
导致混乱主要是由于 参数是 有继承关系的类型。应该避免 同一组参数只许经过类型转换就可以被传递给不同的重载方法。
对于每一对重载方法,至少有一个对应的参数在两个重载方法中具有“根本不同”的类型。
但是当自动装箱和泛型成了Java语言的一部分之后,谨慎重载显得更加必要了。
例如List的remove()方法。如果配合Integer,会搞不清是要按下标删除还是删除对应的元素。
如果被迫违反本规则,要保证让更具体化的重载方法把调用转发给更一般化的重载方法。
例如:
public boolean contentEquals(StringBuffer sb) {
return contentEquals((CharSequence)sb);
}
42慎用可变参数
可变参数方法,接受,0个或多个指定类型的参数。
可变参数也会影响性能,每次方法调用都会导致数组进行一次分配和初始化。
43返回零长度的数组或者集合,而不是null
除非分析表明这个方法正是造成性能问题的真正源头。
且返回同一零长度数组是可能的,因为零长度数组不可变,可以被自由共享(15条)。
不可变的空集合:Collections.emptySet(),Collections.emptyList(),Collections.emptyMap(),
44为所有导出的API元素编写文档注释
第八章 通用程序设计
45将局部变量的作用域最小化
本条目与13条(使类和成员的可访问性最小化)本质上是类似的。
-
在第一次使用它的地方声明。
-
如果在循环终止之后不再需要循环变量的内容,for循环优先于while循环
-
使方法小而集中。
46for-each循环优先于传统的for循环
集合数组,以及任意实现Iterable接口的对象,都能用for-each。
嵌套迭代时,优势更明显。
三种常见情况无法使用for-each:
-
过滤—-遍历集合时要删除选定的元素,需要用迭代器。
-
转换—-遍历列表或数组时,需要取代它部分或者全部的元素值。需要用迭代器或者数组索引。
-
平行迭代—–故意的嵌套迭代(很少使用)
47:了解和使用类库
48:如果需要精确的答案,请 避免使用 float和double
它们尤其不适合于货币就算。
使用BigDecimal、int、long进行货币计算
49:基本类型优先于装箱基本类型
它们的区别:
-
基本类型只有值,装箱基本类型是引用
-
基本类型只有值,而装箱基本类型还有null
-
基本类型通常更节省时间、空间
对装箱基本类型运用 == 操作符,几乎总是错误的。
当在一项操作中混合使用基本类型和装箱基本类型时,装箱基本类型就会 自动拆箱。 null被拆箱就抛出NPE。
合理使用装箱基本类型的地方:
-
集合中的元素、键、值。因为基本类型不能放在集合中。
-
参数化类型中
-
反射的方法调用时
50:如果其他类型更适合,则尽量避免使用字符串。
-
字符串不适合代替其他的值类型。(从网络接受一段数据)
-
字符串不适合代替枚举类型
-
字符串不适合代替聚集类型。例如
String key = className + "#" + i.next();
应该用一个类来描述这个数据集,通常是一个私有的静态成员类。 -
字符串也不适合代替能力表。
51:当心字符串连接的性能
为连接n个字符串而重复地使用字符串连接操作符,需要n的平方级的时间。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
受一段数据)
-
字符串不适合代替枚举类型
-
字符串不适合代替聚集类型。例如
String key = className + "#" + i.next();
应该用一个类来描述这个数据集,通常是一个私有的静态成员类。 -
字符串也不适合代替能力表。
51:当心字符串连接的性能
为连接n个字符串而重复地使用字符串连接操作符,需要n的平方级的时间。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-cLP9wlDo-1710930011273)]
[外链图片转存中…(img-kJ9mQRhl-1710930011274)]
[外链图片转存中…(img-xAM701nY-1710930011274)]
[外链图片转存中…(img-FjMMb4l0-1710930011274)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-QjPkzXTV-1710930011275)]