当前位置: 首页 > 工具软件 > Agile VM > 使用案例 >

《agile java》读书笔记

莫乐
2023-12-01
[align=center][img]http://images.china-pub.com/ebook30001-35000/30528/shupi.jpg[/img][/align]

这是一本学习驱动测试的java好书, 里面在讲java的各种知识均采用TDD的方式, 是一本不错的TDD实战书。 在掌握Java知识的同时也领略了TDD的开发方式。
这本书比较厚, 断断续续也看了很久。 今天总算看完了, 里面讲重构, 讲设计原则, 对平时的开发, 测试都有不错的借鉴意义。 比较遗憾的是没有按照里面的做法一一实践, 因此难免造成“纸上得来终觉浅”。
这本书我最大的收获就是: 除了开发代码需要重构, 测试代码也需要重构, 一开始不要做最完美的实现, 一开始的重复不可怕, 一开始的丑陋不可怕, 只有能实现我们需要的功能即可, 在有单元测试做保证之后再一一进行重构逐步得到理想的代码.

保持代码的简单, 干净:
确保测试是完备的, 而且总是运行成功
消除重复
保证代码是干净和富有表现力的
将类和方法的数量减少到最小

设计系统, 通过扩展来拥抱变化, 不要通过修改来拥抱变化

代码中不应该有大量的switch语句, 而且也不应该包含大量的多重if语句. 通常可以采用多态来消除switch语句.

使用递归一定要注意递归的终止条件, 如果递归中没有终止条件, 那么会导致无限递归调用. 所以递归中需要防卫子句.

[b]Math[/b]

BigDecimal对象是不可变的, 当发送add消息到BigDecimal时, 不会改变对象的值, add方法会取出参数, 与BigDecimal对象相加, 然后创建并返回一个新的表示总和的BigDecimal对象

使用位操作来存储多个标志位, 是一种经典的技术, 该技术在给定内存的条件下, 最大程度的进行了信息的压缩. 因为java通过数组或者其他包含boolean值的集合, 提供了更清晰简单的表示方法, 所以不体检, 也没有必要在java开发中使用该技术

异或运算是奇偶校验的基础, 在数据传输中, 可能会有若干数据位失真, 奇偶校验对发送的数据进行异或运算, 从而计算出校验和, 并将校验作为附加信息发送出去, 接收方针对数据进行同样的计算, 如果校验和不匹配, 那么发送方需要重新发送数据.

如果数据流中1的位数是偶数, 那么校验位就是偶数, 如果数据流中1的个数是奇数, 那么校验位就是奇数.

对偶数个1进行异或运算, 结果总是偶校验(0), 对奇数个1进行异或运算, 结果总是奇校验(1)

异或运算相当于对两个数字进行相加, 然后对2求余运算, 对2求余其结果要么是0, 要么是1

线程
分清线程或者线程的执行与Thread对象之间的区别, 是非常重要的, 线程是线程调度器所管理的一段控制流程. Thread是一个管理有关线程执行信息的对象. 一个Thread对象的存在并不意味着一个线程的存在, 知道Thread对象启动, 线程才会存在, 而且Thread对象可以在线程结束之后存在很长时间

对于需要频繁在列表尾部 的其他地方进行删除或插入操作的集合来说, LinkedList较ArrayList会提供更好的性能.

当线程阻塞在IO操作或者被挂起时, 可以通过显式的调用yield方法或者进入睡眠, 来让出时间, 比如在线程的run方法中每次迭代会调用yield方法来让其他线程得到机会执行
public void run(){
while(true){
if (queue.isNotEmpty()){
execute(queue.remove(0));
Thread.yield();
}
}
}


线程并不位于java语言级别, 而是位于语句被翻译后由VM执行的更底层的级别. 你在java中编写的大部分语句都是非原子的: 许多诸如增加计数器或者给一个引用赋值这样简单的事情, java编译器都可能会创建若干内部操作来对应这一条语句. 假定一个赋值语句会耗费两个内部操作. 第二操作在线程调度器在挂起该线程并执行第二个线程之前, 可能还没有完成. 所有这种代码的交错, 意味着你可能会遇到同步的问题, 如果两个线程均在同一时间访问同一段数据, 结果可能是你无法预料的.

synchronized相当于对一块代码片段加锁. java使用一种称为监视器的概念来保护数据, 每个对象都关联有一个监视器, 这个监视器保护对象的实例数据. 该监视器同每个类相关联, 他保护类的静态数据. 当你获得一个锁的时候, 你同时也获得了相关联的监视器, 任何给定的时刻, 只有一个线程可以获得一个锁. 通常在synchronized关键字后面的括号中指定一个对象来作为监视器.

java.util.concurrent包中提供了很多实用类来帮助我们解决很多有多线程的问题, 当然你应该首选使用这个库, 但他的存在并不意味着你不需要学习线程背后的基本原理和概念, 就如同你在使用计算器解决所有计算之前需要学习好乘法如何工作一样

BlockingQueue为队列添加了与同步相关的能力. 举例来说, 包括在取出下一个元素时等候其出现的能力, 以及在保存元素时等待有空间空闲的能力.

LinkedBlockingQueue的put方法将元素添加到队列的尾部. take方法从队列的开始处删除一个元素, 他还会等待, 直至队列中有元素存在.

解决[b]死锁[/b]的方案:
1.在要上锁的对象排定次序, 并保证在获得锁时使用相同的次序
2.锁住一个共同的对象.

[b]泛型[/b]
java的泛型采用了一种叫擦拭法, 不同于创建一个独立的类型定义, java擦拭了参数化类型的信息, 并创建一个单一的等效类型, 每个类型参数与一个成为它的上限(upper bound)的约束相关联, 缺省是Object. 客户端的绑定信息被擦除, 并替换为适当的强制转型类型.

类似这样的定义List<Object> list, 通过将list绑定到Object, 你会限定它只能保存Object类型的对象, 而且不能是Object的任何子类型, 比如不能将List<String>的引用赋值给list

如果对参数类型是采用的通配符表示的, 比如List<?> 或List<? extends Number>, 会存在一个缺点, 你不能调用类型参数对象中的方法.

对于这样的方法pad(List<?> list, Object element)用来将element插入到list中, java会有编译错误, 因为通配符?指示了一个未知的类型, 这样pad方法对于传入的对象的特定类型并不知情. 这样调用方可能传入非法类型破坏类型安全.

由于采用擦拭法的原因, java无法判断某个给定的操作是否是安全的, 因此它只有全部禁止他们, 一般的经验法则是, 只有从数据结构读取时, 你可以使用有界(bounded)的通配符

而采用泛型方法可以解决这样的问题, 比如<T> pad(List<T> list, T element), 编译器可以根据传递给pad的参数, 提取或推断出T的类型, 他能够从参数中推断出的最确切的类型.

jdk中一个比较复杂的泛型
public static <T extends Comparable<? super T>> T max(Collection<? extends T> c)

max接受一个任意类型的对象的集合, 但这个类型必须实现了Comparable接口, 且这个类型必须是在集合中存储的元素所绑定的类型或者父类. 经过擦拭之后得到:
public static Comparable max(Collection c)

而我们知道早期的非泛型版本是
public static Object max(Collection c)

因此破坏了兼容性, 但使用附加界限能有效解决这个问题:
public static <T extends Object&Comparable<? super T>> T max(Collection<? extends T> c)

因为J2SE规范中规定, 一个类型变量会被擦除为他最左边的界限, 这里就是Object, 而不是Comparable. 不过返回的对象定义了附加的限制, 即收集的对象必须实现适当的Comparable接口.

由于擦拭法的原因, 参数化类型的对象没有关于绑定类型的信息. 对于你可以和不可做什么, 产生了许多隐含的影响.
比如这样:
public class NumberList<T extends Number>{
public void init(){
T zero = new T(0);
...
}
}
擦拭意味着裸类型(T)擦去了其上限(Number), 在多数情况下, 上限是一个抽象类, 因此创建这样的类型变量无意义或非法. java简单的禁止了这种操作. 而且T也不能参与instanceof 操作符处理.

[b]注解[/b]
一个关于复式注解的例子
classIgnoreDateTest{
@Ignore
(
initialize = TestRunnerTest.IGNORE_INITIALS,
date = @Date(month = 1, day = 2, year = 2010)
)
public void test(){...}
}

其定义为:
public @interface Date{ int month(); int day(); int year();}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Ignore{String[] reasons(); String initialize(); Date date();}

因为Date注解类型只是作为另一个注解的一部分, 不需要指定保留度(retention)或目标(target)

关于注解的额外注意事项
[list]
[*]没有指定target的注解可以修饰任何java类型
[*]在注解类型声明中, 唯一你可以返回的参数化类型是Class类型
[*]@Document元注解类型可以让你声明将某个注解类型包含在由javadoc等工具产生的api文档ongoing
[*]@Inherited元注解意味着一个注解类型是被所有子类型继承的. 如果你调用Method或Class对象的getAnnotation方法而不是getDeclareAnnotation方法, 便可以返回他.
[*]你不能将null作为注解的值
[*]你不能用一个给定的注解来重复的修饰某个元素.
[*]为了在内部支持注解类型, sun修改了Array类, 加入了toString和hashCode()方法的实现
[/list]

谨慎地使用注解类型, 注解类型是一个接口, 并且应该表示一种稳定的抽象, 同其他接口一样, 确保你已经仔细考虑在系统中引入注解类型所带来的影响.

[b]正则表达式[/b]
从Pattern对象中, 你可以为指定的输入String得到Matcher对象, 在你得到Matcher对象之后, 你可以调用它的find方法来得到下一个子串, 如果发现匹配, find方法返回true, 否则返回false.

Matcher实例保存了有关上一个被发现的子串的信息, 你可以调用Matcher对象的start和end方法来得到描绘匹配子串的索引, 调用Matcher的group方法可以得到匹配的子串文本.

Matcher的matches方法, 如果整个输入字符串匹配了某个表达式, 它会返回true, 你还可以调用lookingAt, 当输入字符串的开始处和正则表达式匹配时, 它会返回true.

native表示这个方法是在jvm中实现的, 而不是java中实现的.

协变(covariance)指的是这样一种能力: 子类方法返回一个对象, 其类型为父类定义的返回类型的子类, clone是java协变的一种典型示例.
 类似资料: