您的位置:首页 > 编程语言

编写高质量代码之读书笔记1

2016-10-12 20:58 309 查看
背景:由于工作原因,经过好几个月的漫长时间,才将这本书看完,这里将阅读过程中觉得需要回味的内容记录,备份,以后经常看看,这里也分享给大家。

建议 9 : 少用静态导入
所谓静态导入就是类似:import static java.lang.Math.PI;
我们使用
          public final class Math {
public static final double E = 2.718281828459045;
public static final double PI = 3.141592653589793;
import之后,就会直接使用PI,而不需要使用Math.PI了,确实,如果直接使用PI的话,我们阅读代码时很难知道PI是哪里赋值的。

建议10 : 不要在本类中覆盖静态导入的变量和方法
这个是上面的延伸,如果本文件覆盖了静态导入的变量,则会使用本地的了,虽然编译不会报错,但可读性很差

建议11 :显示声明UID
这个就是在继承Serializable的时候,显示声明一下:private static final long serialVersionUID = XXXXXL;
显示声明是为了提高代码到健壮性,如果不显示声明,但发生版本不一致时,直接报invalidClassException,如果进行显示声明,则会进行        向上兼容,

建议12 :避免用序列化类在构造函数中为不变量赋值,
原因是反序列化时,构造函数不会执行(这个虽然没有使用过,但是如果面临这种情况,还是很容易犯错的)

建议13 :避免为final变量复杂赋值(主要还是针对序列化)
以下三种情况反序列化时final变量不会被重载
1.通过构造函数为final变量赋值
2.通过方法返回值为final变量赋值
3.final修饰的属性不是基本类型(没有序列化的基本类型)
建议14 :使用序列化类的私有方法巧妙解决部分属性持久化问题
添加:private void writeObject()   和    private void readObject()类似于parcelable的自己使用

建议16 :易变业务使用脚本语言编写
这个如果在android中使用的比较常用的事javaScript

建议18 :避免instanceof 非预期结果
instanceof两端必须是继承或实现关系,范型会作为Object类型

建议20 :不要只替换一个类
这个的意思就是不要只改一个jar包或发布包里的一个.class文件,这样会导致这个jar里面的class类中的final变量不会改变。使用ide编译        肯定不会有这个问题,

建议21 :用偶判断,不用奇判断
i % 2 == 0 ? "偶数":"奇数"

i % 2 == 1 ? "奇数":"偶数"
也许你会觉得这两个一样,但是第二个当输入负数的时候,都会返回偶数,不论什么值


建议22 :用整数类型处理货币
Log.v("XPC","oddOrEven="+(10.00 - 9.04));
结果是 0.9600000000000009   很奇怪吧,原因是二进制转化小数的时候,是不精确的,所以使用的时候,都可以使用整数,最后再转化          为小数


建议23 :不要让类型默默转换

int LIGTH_SPEED = 30 * 10000 * 10000;

long dis = LIGHT_SPEED * 60 * 8;//这个会先计算int,然后超过了int界限,变成负值,所以输出结果是负值,

long dis = 1L * LIGHT_SPEED * 60 * 8;//这个就没问题了,先强制转换成long,然后计算的时候,不会超出边界了,

建议24 :边界,边界,还是边界

if(order > 0 && order + cur <= LIMIT){

}

这个判断是存在问题的,假设order值为2147483647,那么这个就失效了,order>0满足条件,但是order + cur由于超出边界为负值,同 样满足条件,所以有时我们写的程序对于一般使用者好使,单独与高手来说,破解分分钟的事

建议26 :堤防包装类型的null值

List的类型中是可以是null的,每次取出需要注意是否为 null
public static int f(List<Integer> list){
int count = 0;
for(int i:list){
count +=i;
}
return count;
}
如果传入的list中有null值,则会报空指针错误,integer转换int的时候,需要调用intValue,如果包装类型为null,则会报错
建议28 :优先使用整数池

public static Integer valueOf(int i) {
return  i >= 128 || i < -128 ? new Integer(i) : SMALL_VALUES[i + 128];
}


从这个可以看出来,当我们使用valueOf当输入介于(-128,128]之间的时候,返回的直接是缓存池中的,所以是同一个对象,

建议29 :优先选择基本类型
当基本类型long和包装类Long同时作为参数是,会优先使用long的函数,这个涉及到装箱顺序问题,

建议 30 :不要随便设置随机种子
当设置了种子new Random(10)之后,同一台机器每次运行随即出来的数是固定的
不设置种子new Random(),随即出来的值每次运行才不同

建议 36:使用构造代码块精炼程序
public class Client {
{
System.out.pprintln("执行构造代码块");
}

我写的这段代码会在所有的构造函数之前被调用,如果有多个构造函数,需要初始化同一段代码,使用可以简化代码(不要介意构造函数调
用构造函数的问题,不会重复调用的,编译器会单独处理这种情况)

建议 38 :使用静态内部类提高封装性
静态内部类与普通内部类区别
1.静态内部类不持有外部类的引用,所以更干净
2.静态内部类不依赖外部类,更独立
3.普通内部类,不能声明static方法和变量

建议 41 :让多重继承成为现实
java中可以多重实现,但不能多重继承,但是我们可以通过内部类和匿名内部来来解决这个问题
Interface Father {
public int strong();
}

Interface Mother {
public int kind();
}
class FatherImpl implements Father{
public int strong(){
return 8;
}
}

class MotherImpl implements Mother{
public int kind(){
return 8;
}
}

1.通过内部类实现多继承
class son extends FatherImpl implements Mother{

@Override
public int kind(){
return new MotherSpecial().kind();
}

private class MotherSpecial extends MotherImpl{
public int kind(){
return super.kind() -1;
}

}
}
2.通过匿名类来实现

class son extends FatherImpl implements Mother{

@Override
public int kind(){
return new MotherImpl(){
@Override
public int kind(){
return super.kind() -1;
}

}.kind();
}
}
之前运用过类似的手法实现类似的需求,但是这里看到总结出来,感觉还是豁然开朗。

建议 42 :让工具类不可实例化
1.通过私有化构造函数
2.为了防止反射,在私有化中再加入抛出异常处理。

建议 43 :避免对象的浅拷贝
这里需要记住clone的几个特性
1.基本类型 :这个会拷贝其值
2.对象 :拷贝地址引用(所以拷贝出来的成员对象,如果更改的话都会改掉)
3.String :这个也是地址,但是由于string的特殊性,在修改时,会在字符串池中重新生成新的字符串,所以结果和基本类型一致。
所以综上:一般拷贝的时候,对象会有问题,这是我们就可以通过下面来实现深度拷贝
public person clone(){
Person p = null;
try{
p = (Person)super.clone();
p.setFather(new Person(***));
}catch(CloneNotSupportedException e){
}
}
其实就是我们在拷贝的时候,new一个新的对象,这样就不是一个地址了,也不会一改都改了

建议 44 :推荐使用序列化实现对象的拷贝

public class CloneUtils {

public static < T extends Serializable > T clone(T obj){
T cloneObj = null;

ObjectOutputStream oos = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
cloneObj = (T)ois.readObject();
ois.close();

} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}

}


这个可以方便的解决之前提到的浅拷贝问题,不需要每个对象都复写clone方法了

建议 47 :在equals中使用getClass进行类型判断(如果要是使用instanceof,就会发生违背传递性原则的问题)

建议 48 :覆写equals方法必须覆写hashCode方法
否则在获取HashMap中会出现问题,

建议 49 :推荐覆写toString方法
这个之所以纪录是因为覆写的格式,还是比较正规的,以后可以借鉴
publlic String toString(){
return String.format("%s.name=%s",this.getClass(),name);
}

建议 50 :使用package-info 类为包服务
这个之前确实是不知道,临时查看了一下,在android的framework中部分采用了这个文件进行讲解说明,这里记录一下,以后万一使用


建议 52 :推荐使用Stirng直接量赋值
即:String str = "ddd"; 而不是 String str = new String("ddd");
这里设计了字符串池的概念,
str.intern():是会检查当前对象在对象池中有没有,如果有,则返回池中对象,如果没有,则把当前string放入池中。

建议 53 :注意方法中传递的参数要求
输入:

String input = "好是好";
Log.v("XPC", "input.replaceAll="+input.replaceAll("好",""));
String input1 = "$是$";
Log.v("XPC", "input.replaceAll="+input1.replaceAll("$",""));
String input2 = "好是好";
Log.v("XPC", "input.replace="+input2.replace("好",""));
String input3 = "$是$";
Log.v("XPC", "input.replace="+input3.replace("$",""));
输出:
V/XPC: input.replaceAll=是
V/XPC: input.replaceAll=$是$
V/XPC: input.replace=是
V/XPC: input.replace=是


区别在于replaceAll参数要求是正则表达式而replace不需要

建议 54 :正确使用String,StringBuffer,StringBuilder
这是一个老生常谈的问题,
主要记住: StringBuffer:线程安全,但效率比StringBuilder低
StringBuilder:线程不安全,
二者其他的相同

建议 56 :自由选择字符串拼接方法
str += "c";
str = str.concat("c");
StringBuffer.append
三者效率越来越高,详情可查看各自源码

建议 57 :推荐在复杂字符串操作中使用正则表达式
这里我只引用一句话:正则表达式是恶魔,威力巨大,但难以控制

建议 58 :强烈建议使用UTF编码
Window默认编码是 GBK
Linux和Android和Mac则是 UTF
所以如果不统一,在显示的时候会出现问题。

建议 59 :对字符串排序持一种宽容的心态
汉子的排序有时是不准确的,这个如果汉子排序是核心算法(会有一些生僻字发生),建议使用开源项目(pinyin4j)

建议 60 :性能考虑,数组是首选
对基本类型进行求和计算时,数组的效率是集合的10倍

建议 61 :若有必要,使用变长数组
//每次增加addLen长度,这里并没有限制正负,最终返回一个新的数组

public static <T> T[] expandCapacity(T[] datas , int addLen){
int newLen = datas.length + addLen;
newLen = newLen > 0 : newLen ? 0;
return Arrays.copyOf(datas,newLen);
}


建议 62 :警惕数组的浅拷贝
Arrays.copyOf 都是数组的浅拷贝,更改拷贝后的数组,会影响源数组
注意,这里数组中如果是基本类型是没问题的,但是如果数组中是非基本类型,拷贝的其实是其地址。

建议 64 :多种最值算法,适时选择
这里想要说的是里面的一个小知识,

数组 ,List , TreeSet值之间的随意转换
Integer[] data = {};
List<Integer> dataList = Arrays.asList(data);
TreeSet<Integer> ts = new TreeSet<Integer>(dataList);//list转换为treeSet,注意这里的treeset自动排序
这里有一些隐藏的坑:

1. 数组转换List[注意啊,这里的data数组类型必须是装饰类,就是不能是8种基本数据类型]
2.这个dataList是长度不可变的了,这个大家要非常注意,如果add则会抛出异常UnsupportedOperationException();

建议 67 :不同列表选择不同的遍历方法
这里介绍两个List : ArrayList VS LinkedList
前者是继承RandomAccess 就是说明随机存取接口,
LinkedList则是双向链表形式的,前后值之间存在关联关系。
当我们使用ArrayList的时候,使用下标获取值的时候效率是更改的,
而在使用LinkedList的时候,使用foreach则是效率更高的。
所以排序算法可以如下形式:
public static int average(List<Integer> list){
int sum = 0;
if(list instanceof RandomAccess){
for(int i=0,size = list.size();i<size;i++){
sum += list.get(i);
}
} else {
for(int i:list){
sum += i;
}
}
return sum / list.size();
}

建议 68 : 频繁插入和删除时使用LinkedList,修改时使用ArrayList

建议 70 :自列表知识原列表的一个视图(并不会重新创建)

建议 71 : 推荐使用subList处理局部列表
因为subList修改的其实就是源列表,所以更加方便
例如删除一个列表下标从 20 - 30的值
List.subList(20,30).clear();

建议 72 :生成自列表后不要再操作原列表

建议 73 :使用Comparator进行排序(Comparable VS Comparator)

建议 74 :不推荐使用binarySearch对列表进行检索
binarySearch 与 indexOf 功能类似,都是返回某个值的下标,知识binarySearch是使用二分法查找,效率更高,
问题是,使用二分法,就需要对列表提前排序,1.使用者可能会忘记,2.加上排序的整体效率未必还有优势。
还有个不同就是indexOf是比较equals 来确定值的,而 binarySearch则是根据 compareTo

建议 76 :集合运算时使用更优雅的方法
这个确实在之前遇到过,但是我真的是使用for循环判断的,真是愚蠢。
1.并集 :把两个集合合起来(list1.addAll(list2));
2.交集:取两个集合中相同的值(list1.retainAll(list2));
3.差集:属于list1但不属于list2的(list1.removeAll(list2));
4.无重复的并集:list1 与 list2中相同的值,只能有一份
list2.removeAll(list1);
list1.addAll(list2);

建议 77 :使用shuffle打乱列表
Collecctions.shuffle(list1);//打乱列表的顺序

建议 78 :尽量让HashMap中的元素少量并简单
1.HashMap中存放的事Entry,
2.HashMap的扩容方式,
这两种会造成hashmap更容易发生outOfMemory

建议 80 :多线程使用Vector 或 HashTable
Vector是ArrayList的线程安全版本
HashTable是HashMap的线程安全版本
transient是类型修饰符,只能用来修饰字段。在对象序列化的过程中,标记为transient的变量不会被序列化
volatile也是变量修饰符,只能用来修饰变量。volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的

值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同
一个值。

建议 83 :推荐使用枚举定义常量
这里只介绍用枚举的优点吧:1.枚举常量更简单
2.枚举常量属于稳态型
3.枚举具有内置方法
4.枚举可以自定义方法

建议 84 :使用构造函数协助描述枚举项(谁知枚举的构造函数,增加枚举类型到丰富性)

建议 85 :小心switch带来的空值异常

建议 87 :使用valueOf前必须进行校验
如果使用枚举的valueOf没有值的时候,则会直接返回非法参数异常。

建议 88 :用枚举实现工厂方法模式更简洁
这里直接上例子,看了就知道啦

interface Car{

}

static class FordCar implements Car{

}

static class BuickCar implements Car{

}


这里是两款汽车

enum CarFactory{
FordCar {
public Car create(){
return new FordCar();
}
},
BuickCar{
public Car create(){
return new BuickCar();
}
};

public abstract Car create();

}

enum CarFactory1 {
FordCar,BuickCar;
public Car create(){
switch (this){
case FordCar:
return new FordCar();
case BuickCar:
return new BuickCar();
default:
throw new AssertionError("无效参数");
}
}
}

public static class CarFactory2 {
public static Car createCar(Class<? extends Car> c){
try {
return (Car)c.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}


这是三种工厂模式的实现方法,前两种是使用枚举方法

CarFactory.FordCar.create();
CarFactory1.BuickCar.create();

CarFactory2.createCar(FordCar.class);

这个是三种工厂模式的调用方法。

建议 89 :枚举项的数量限制在64个以内
详情需要查看源码(这个位操作还是比较麻烦的)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: