《practical Java》读书笔记
题记:
花了一周把Peter Haggar的《practical Java》看了遍,有所感悟,年纪大了,
写下笔记,方便日后查看.也希望有缘之人可以看看,做个渺小的指路人。
不足之处还望指正。
概述:
全书分为六个部分,包括一般技术、对象与相等性、异常处理、性能、多线程、对象。
一般技术:举例了几个java常见错误用法的说明和解释,诸如array和vector的选择,多态与instanceof等等
对象和相等性则:针对equals的详细说明,是迄今本人见过对equals理解最深的一本书了,其中不乏java的一些规范
异常处理:主要介绍了java异常机制的使用细节,其中有一点就是return后的逻辑一律不执行在try finally模式里头是无效的
性能:介绍了java常用的一些优化细节,诸如使用栈变量来代替堆变量,减少同步化,使用arraycopy方法来代替自己的循环复制数组等等
多线程:简要的说明了java线程使用中一些常见的知识点,如果对java多线程有兴趣的,可以看看《Java并发编程实战》
对象:介绍了接口与继承的关系与使用,对深入学习java框架源码有一定的帮助,感兴趣的可以多思考下其中的奥义
正题:
正题中将挑选自己觉得比较有用的一些知识点进行说明,但并不代表其他知识点就不重要(因人而异,尽信书 不如无书)
最后我会将代码工程打包好,感兴趣的可以去下载下来看看。
1.一般技术
实践1:参数以by value 方式而非by reference 方式传递
1 /** 2 * java有值(基础类型变量value)传递和引用(reference)传递 3 * 两者的区别是引用将会随着调用方法体的逻辑而发生改变 4 * 一个非常基础的java知识点 5 * 献给千千万万徘徊在java门口的求学者 6 * @author lwx 7 * TODO 8 * 参考: 9 * 2014-5-13 上午9:28:38 10 */ 11 public class Lesson1 { 12 13 public static void valueTest(int value){ 14 15 value=value+5; 16 System.out.println("valueTest-->"+value); 17 } 18 19 20 public static void referenceTest(StringBuffer obj){ 21 22 obj.append("123"); 23 24 System.out.println("referenceTest-->"+obj.toString()); 25 } 26 27 public static void main(String[] args) { 28 int value=2; 29 30 valueTest(value); 31 System.out.println(value);//2 32 StringBuffer obj =new StringBuffer("0"); 33 referenceTest(obj); 34 System.out.println(obj.toString());//0123 35 36 } 37 }
实践2、3:final的用法 略
实践4:在arrays和vectors之间慎重选择
数组在java中使用率远远超过vector,因此这里就要明白何时应该使用vector
首先vector是线程安全的,这样就保证了他可以同步控制对象,
其次vector内部实现也是数组,只不过是泛型对象的数组(数组更多时候存储的是基础类型)
最后就是数组的大小是无法自行拓展的,而vector是可以通过System.arraycopy()方法进行复制扩展vector的容量
实践5:多态优于instanceof
这里必须明白java多态和instanceof用法,简单说下两者的概念
所谓的多态就是不同对象对同一个消息作出不同的响应,java中的多态包含了重载和重写(覆盖)
instanceof 运算符是用来在运行时指出对象是否是特定类的一个实例,通过返回一个布尔值来指出,这个对象是否是这个特定类或者是它的子类的一个
先看下面一个例子
1 /** 2 * 尽量使用多态而非instanceof 3 * @author lwx 4 * TODO 5 * 参考: 6 * 2014-5-13 上午10:39:41 7 */ 8 public class Lesson5 { 9 public static void main(String[] args) { 10 Employee mgr=new Manager(); 11 Employee pgr=new Programmer(); 12 System.out.println("经理的工资-->"+calcSalary(mgr)); 13 System.out.println("程序员的工资-->"+calcSalary(pgr)); 14 } 15 16 public static int calcSalary(Employee e){ 17 int salary=e.salary(); 18 if(e instanceof Programmer) 19 salary+=((Programmer)e).bonus(); 20 return salary; 21 } 22 } 23 interface Employee{ 24 public int salary(); 25 } 26 class Manager implements Employee{ 27 private static final int mgrSal=10000; 28 @Override 29 public int salary() { 30 // TODO Auto-generated method stub 31 return mgrSal; 32 } 33 } 34 class Programmer implements Employee{ 35 private static final int pgrSal=6500; 36 private static final int pgrBonus=1000; 37 @Override 38 public int salary() { 39 // TODO Auto-generated method stub 40 return pgrSal; 41 } 42 //程序员除了工资 还有项目奖金哦 那个公司有 求收留 43 public int bonus() { 44 // TODO Auto-generated method stub 45 return pgrBonus; 46 } 47 }
上面例子中 程序员在计算工资的时候是需要考虑奖金的,因此通过Instanceof来判定传给
calcSalary方法的参数是否是Programmer类,如果是 则在原有工资计算方法上加上bonus()方法
表面上看,这样的逻辑没有问题,但是我们是需要考虑拓展的,加入现在还有产品经理,他的工资也有奖金 另外还有其他福利,在加上其他岗位 那么每增加一个岗位的变动
我们都需要去修改calcSalary方法,而这样的设计明显是不符合java的规范的
书本作者给出的方案是让经理也有奖金的方法,这不过这个奖金是0 从而避免了instanceof的产生,具体做法看书本,此处略
实践6:必要时才需要instanceof
java支持父类向下转型,即使是错误的向下转型 在编译的时候是不会报错的,因此容易让开发人员带来干扰
1 /** 2 * 必要时才用instanceof 3 * 必要的时候指的是 你需要父类向下转子类 4 * @author lwx 5 * TODO 6 * 参考: 7 * 2014-5-13 上午11:18:32 8 */ 9 public class Lesson6 { 10 11 public static void main(String[] args) { 12 13 Shape circle=new Lesson6.Circle(); 14 Object triangle=new Lesson6.Triangle(); 15 16 //Lesson6.Triangle tri1=(Lesson6.Triangle )circle;//编译通过 但是执行会报错 java.lang.ClassCastException 17 if(circle instanceof Lesson6.Triangle){ 18 19 Lesson6.Triangle tri1=(Lesson6.Triangle )circle; 20 } 21 Lesson6.Triangle tri2=(Lesson6.Triangle )triangle; 22 } 23 24 static class Shape{} 25 static class Circle extends Shape{} 26 static class Triangle extends Shape{} 27 28 }
实践7:一旦不再需要object reference,就将它设为null
接触java的都明白java自带的虚拟机有垃圾回收机制,不愿太操心内存问题,其实作为一名合格的javaer也是需要考虑内存泄露的
况且java确实有存在,当然这里不再我们的讨论话题中,为什么没用的引用尽量要手动的去触发unusefulObj=null呢
其实就是减轻JVM的工作量,gc不是随时触发的 这个应该要懂得
实例中的例子已经很不错了
1 /** 2 * 手动去设置无用的引用为null 3 * @author lwx 4 * TODO 5 * 参考: 6 * 2014-5-13 下午1:56:03 7 */ 8 public class Lesson7 { 9 10 11 12 public static void main(String[] args) { 13 14 //testGC();//GC测试CPU性能 15 16 Customers customers=new Customers(""); 17 18 //执行一堆逻辑 此处省略 19 20 /* 21 1. 22 无用时候释放对象 23 customers=null;// 24 */ 25 26 27 /*2. 28 * 上面的情况 存在一个问题 假如customers我们还需要 29 * 只是他的数据我们不需要引用了 或则customers生命周期跟系统应用一个周期 30 * 那么我们就只需要释放custIdArray内存就可以达到效果了 31 * */ 32 //由于我们没有办法直接接触 因此需要开放方法给我们去触发 加入一个unrefCust()方法 33 customers.unrefCust();// 34 35 } 36 37 private static void testGC() { 38 Runtime rt=Runtime.getRuntime(); 39 long mem=rt.freeMemory(); 40 System.out.println("空闲CUP==>"+mem/1024/1024); 41 System.gc();//手动去触发虚拟机回收垃圾 42 mem=rt.freeMemory(); 43 System.out.println("忙时CUP==>"+mem/1024/1024); 44 } 45 46 } 47 48 class Customers{ 49 50 private int []custIdArray; 51 52 public Customers(String db){ 53 int num=queryDB(db); 54 custIdArray=new int[num]; 55 for (int i = 0; i < num; i++) { 56 custIdArray[i]=i; 57 } 58 } 59 int queryDB(String sql){ 60 61 return 5; 62 } 63 64 public void unrefCust(){ 65 66 custIdArray=null; 67 } 68 69 70 }
2.对象与相等性
实践8:区别reference类别和primitive型别
这个可能比较拗口很难理解字面的意思,其实也是实践1所说的基础类型和对象引用之间的区别
这里主要是介绍下java1.5的一个新特性:拆箱和装箱
Integer i1=100;//等同于new Integer(100) 这里就是一个自动装箱的过程
int k=i1;// 自动拆箱的过程
更多关于拆箱与装箱 可以移步http://www.cnblogs.com/danne823/archive/2011/04/22/2025332.html
关于包装类的缓存:http://blog.csdn.net/yaoweijq/article/details/6021706
/** * 基础类型和引用类型对象区别 * 1.5新特性 装箱和拆箱 * @author lwx * TODO * 参考: * 2014-5-13 下午2:15:45 */ public class Lesson8 { public static void main(String[] args) { //八大基础类型和对应的装箱类 /* boolean char byte short int float long double 对应的对象为 Boolean Character Byte Short Integer Float Long Double*/ Integer i1=100;//等同于new Integer(100) Integer i2=100; System.out.println(i1==i2);//true i1=1000; i2=1000; System.out.println(i1==i2);//false } }
实践9:区分==和equals
这个是java经常碰到的一个基础知识点,即"=="和"equals"区别,何时使用==何时使用equals
总结起来可以这么说:== 对于基础类型 比较的是vlaue,而引用类型比较的是地址,当对象不需要单纯的比较地址
而需要你自己DIY的时候,请重写equals方法吧
至于何时使用,可以这么说:==经常是基础类型在用,引用类型的基本不用
equals最常见,而且多数情况下你是需要重写的
有点以偏概全,希望拍砖
实践10:不要依赖equals()的缺省实现
不啰嗦了,直接上代码
1 /** 2 * 3 * 重写父类equals方法(默认重写的是Object的equals方法) 4 * @author lwx 5 * TODO 6 * 参考: 7 * 2014-5-13 下午3:46:15 8 */ 9 public class Lesson10 { 10 11 public static void main(String[] args) { 12 13 14 BasketBall b1=new BasketBall("brand",20.0); 15 BasketBall b2=new BasketBall("brand",20.0); 16 System.out.println(b1.equals(b2));//不重写 则调用Object equals的方法 17 } 18 19 20 } 21 22 23 class BasketBall{ 24 25 private String brand; 26 27 private double price; 28 29 public BasketBall(){} 30 31 32 public BasketBall(String brand, double price) { 33 super(); 34 this.brand = brand; 35 this.price = price; 36 } 37 38 @Override 39 public boolean equals(Object obj) { 40 41 if(null!=obj&&obj.getClass()==getClass()){ 42 43 BasketBall ball= (BasketBall) obj; 44 if(brand.equals(ball.brand)&&price==ball.price){ 45 46 return true; 47 } 48 } 49 return false; 50 } 51 52 }
上面算是比较正常的一个重写equals的方法,后续书本作者也提到了一个情况
就是对象的变量如果不是基础类型,也是引用类型的话,就需要额外处理了(举个例子,比如brand改成Stringbuffer类型)
作者给出了四个解决办法:
1.不使用Stringbuffer,继续使用String
2.比较的时候 先将Stringbuffer对象转出String(调用toString()方法)
3.继承Stringbuffer,重写equals方法 让变量变成重写类的类型(有点拗口)
4.放弃equals改用compare()方法
例子书本上都有,感兴趣的都可以去看看
关于何时重写equals,上一节已经表述了自己的观点,这里补充下原作者的观点:
实践11~15 略
3.异常处理
实践16:认识【异常控制流】机制
记住一个模式:try { //do something }catch(Exception e){// when exception happen to do }finally{//不管有无异常 都将执行 不受return 影响}
顺序为:先执行try中的逻辑,如果正常执行,则跳转到finally块中执行,如果异常了,则会终止try块中的逻辑,转移到
catch块中执行,最后还是会在finally完成最后的操作
1 /** 2 * @author lwx 3 * TODO 4 * 参考: 5 * 2014-5-13 下午4:40:36 6 */ 7 public class Lesson16 { 8 9 10 public static void main(String[] args) { 11 12 int i =0; 13 int k=2; 14 int addResult=0; 15 int divideResult=0; 16 try { 17 divideResult=k/i; 18 addResult=k+i;//这里将不会执行 19 } catch (Exception e) { 20 System.out.println("异常了-->"+e.getMessage()); 21 }finally{ 22 23 addResult=1; 24 divideResult=0; 25 } 26 System.out.println("addResult-->"+addResult); 27 System.out.println("divideResult-->"+divideResult); 28 29 } 30 31 32 }
实践17:绝对不可轻视异常
当发生程序异常的时候,我们有哪些处理方式
1.捕获并处理,防止它进一步传播
2.捕获并在此抛出它,传播给它的调用者
3.捕获它,并抛出一个新的异常给调用者
4.不捕获这个异常,任由它传播
这里需要用到引用一个新的概念:抛出异常,通过throws来完成
正常情况下,第一个处理方式是最常见的,实践16中也是采用了第一种处理方式
后续三种我们将在实践18~20中一一介绍
1 public class Lesson17 { 2 public static void main(String[] args) { 3 try { 4 test(); 5 } catch (Exception e) { 6 // TODO Auto-generated catch block 7 e.printStackTrace(); 8 System.out.println("异常处理提示"); 9 } 10 } 11 12 static void test () throws Exception{ 13 14 System.out.println(2/0); 15 16 throw new Exception(); 17 18 } 19 20 }
实践18:千万不要遮掩异常
在处理try块汇总的异常时,如果catch获取finally中又抛出异常,那么之前的异常会被覆盖
优先级:finally>catch>try
我们知道,finally是不受之前是否异常影响的,都将会执行,但是特殊情况下finally语句照样
也会产生异常,那到底要如何处理呢?那就是将异常存放在Vector中
1 /** 2 * 3 * 如何捕获所有的异常 -->将异常对象存放在容器对象中 4 * 5 * @author lwx TODO 参考: 2014-5-13 下午5:02:51 6 */ 7 public class Lesson18 { 8 9 public static void main(String[] args) { 10 11 Hidden hidden = new Hidden(); 12 try { 13 hidden.readFile(); 14 } catch (FileNotFoundException e) { 15 // TODO Auto-generated catch block 16 e.printStackTrace(); 17 } catch (IOException e) { 18 // TODO Auto-generated catch block 19 e.printStackTrace(); 20 } 21 //改进过的捕获异常的方式 22 NotHidden notHidden=new NotHidden(); 23 24 try { 25 notHidden.readFile(); 26 } catch (ReadFileException e) { 27 // TODO Auto-generated catch block 28 e.printStackTrace(); 29 //捕获到的异常存放到容器中 30 System.out.println(e.exceptionVector().size()); 31 32 } 33 } 34 35 } 36 37 class ReadFileException extends IOException { 38 private Vector excVector; 39 40 public ReadFileException(Vector v) { 41 excVector = v; 42 43 } 44 45 public Vector exceptionVector() { 46 47 return excVector; 48 } 49 50 } 51 52 class Hidden { 53 54 void readFile() throws FileNotFoundException, IOException { 55 56 BufferedReader br1 = null; 57 BufferedReader br2 = null; 58 FileReader fr = null; 59 60 try { 61 fr = new FileReader("test.txt"); 62 br1 = new BufferedReader(fr); 63 int i = br1.read(); 64 65 fr = new FileReader("test2.txt"); 66 br2 = new BufferedReader(fr); 67 i = br2.read(); 68 69 } finally { 70 71 if (null != br1) { 72 73 br1.close(); 74 } 75 if (null != br2) { 76 77 br2.close(); 78 } 79 80 } 81 82 } 83 84 } 85 86 class NotHidden { 87 88 void readFile() throws ReadFileException { 89 90 BufferedReader br1 = null; 91 BufferedReader br2 = null; 92 FileReader fr = null; 93 Vector excVec = new Vector(2); 94 95 try { 96 fr = new FileReader("test.txt"); 97 br1 = new BufferedReader(fr); 98 int i = br1.read(); 99 100 fr = new FileReader("test2.txt"); 101 br2 = new BufferedReader(fr); 102 i = br2.read(); 103 } catch (FileNotFoundException e) { 104 // TODO Auto-generated catch block 105 e.printStackTrace(); 106 excVec.add(e); 107 } catch (IOException e) { 108 // TODO Auto-generated catch block 109 e.printStackTrace(); 110 excVec.add(e); 111 } finally { 112 113 if (null != br1) { 114 try { 115 br1.close(); 116 } catch (IOException e) { 117 // TODO Auto-generated catch block 118 e.printStackTrace(); 119 excVec.add(e); 120 } 121 } 122 if (null != br2) { 123 124 try { 125 br2.close(); 126 } catch (IOException e) { 127 // TODO Auto-generated catch block 128 e.printStackTrace(); 129 excVec.add(e); 130 } 131 } 132 if(excVec.size()>0){ 133 throw new ReadFileException(excVec); 134 } 135 } 136 137 } 138 139 }
实践19:明确throws字据的缺点
我们在开发过程中,经常会调用一些公用的函数(作者给了一个很通俗的名称:工蜂型函数),而这些函数
有可能会产生异常(比如调用数据库连接的方法),这时候处理异常有两种方式
一种是函数自身处理,一个是调用端来处理.个人偏好是函数本身来处理,否则有10处地方调用这个函数
就要捕获10次
实践20:细致而全面的理解throws子句
看的不是很懂,但是根据作者的demo,我理解的是作者想表达的意图是当重写函数的时候
函数抛出的异常范围不能大于该函数抛出范围,当然也可以不抛出异常(子类抛出异常的范围不能大于父类)
1 /** 2 * 3 * 重写父类带有异常的方法 4 * 则子类中的方法抛出的异常必须是该异常或则该异常的父类(抛出异常范围更大) 5 * 当然 也可以不抛出异常 6 * @author lwx 7 * TODO 8 * 参考: 9 * 2014-5-13 下午7:02:57 10 */ 11 public class Lesson20 { 12 13 14 public static void main(String[] args) throws IOException { 15 16 17 ChildClass childClass =new ChildClass(); 18 childClass.test(); 19 childClass.test2(); 20 childClass.test3(); 21 } 22 } 23 24 25 class SubClass{ 26 27 28 29 public void test ()throws FileNotFoundException{ 30 31 } 32 public void test2 ()throws FileNotFoundException{ 33 34 } 35 public void test3 ()throws FileNotFoundException{ 36 37 } 38 39 40 } 41 class ChildClass{ 42 43 44 45 public void test ()throws FileNotFoundException{ 46 47 } 48 public void test2 ()throws IOException{ 49 50 } 51 public void test3 (){ 52 53 } 54 }
实践21:使用finally避免资源泄露
简单的描述就是在异常处理中,java规范是通过finally来做一些善后的事情(包括释放资源等)
实践23:将try/catch区段置于循环之外
弦外音:不能循环调用try/catch区段,而应该在一个try/catch中调用循环
实践27:抛出异常前线将对象恢复为有效状态
弦外音:将状态变量等处理放在可能处理异常之后,保证状态不受异常影响
4.性能
实践31:如欲进行字符串结合,StringBuffer优于String
弦外音:对于需要频繁处理字符拼接组合的地方,请使用StringBuffer,或则对于稍微复杂的操作,请使用StringBuffer
实践33:慎防未使用的对象
弦外音:如果一个判断两选一,请不要都创建之,然后根据if else 来选择其中一个对象
换句话说,使用的时候才去创建对象
实践34:将同步化降至最低
弦外音:尽量少用同步操作,诸如synchronized等,除非你需要同步资源
实践35:尽可能使用stack变量
弦外音:尽量使用局部变量(stack)来代替全局变量(heap)
实践36:使用static,final和Privatae函数以促成inlining
略,后续在认真看
5.多线程
略,对多线程感兴趣的朋友可以看看,都比较基础
6.对象
实践59.运用接口来支持多继承
弦外音:java虽然不支持多继承(class a extends b,c)但是却可以通过接口
来完成多实现(class a implements a,b ,c......)
实践60.避免接口函数发生冲突
弦外音:假如类A实现了B,C两个接口,但是B和C都有方法test()
这样A要实现B还是C的test()方法呢,作者给出了解决方式,个人觉得方法很赞
java中很多库都使用了类似的方式来解决这类问题
即让D继承B接口(重命名接口,避免冲突),然后class A Implements D,C
实践61、62:
关于继承和接口 抽象类等知识点
实践63~66
关于类引用之间的操作 包括浅克隆 深克隆等
实践68:在构造函数内调用non-final函数是要当心
这个记得初学java时候,比较难搞懂的一道题目,了解其机制
需要明白java对一个类的初始过程
我们结合代码来看下
1 /** 2 * 构建子类构造的时候会调用父类的构造函数 3 * 而父类的构造函数中有调用lookup() 注意 此时的loopup()调用的是子类的loopup() 4 * 而此时子类中的变量都还没初始化 都是默认值 因此num=0调用之后val也是0 5 * 下面打印的语句顺序可以参考 就明白了 6 * @author lwx 7 * @TODO 参考: 8 * @createtime 2014-5-13 下午7:44:35 9 */ 10 public class Lesson68 { 11 12 public static void main(String[] args) { 13 14 Derived derived=new Derived(); 15 System.out.println(derived.value()); 16 17 18 } 19 20 } 21 22 class Base { 23 24 private int val; 25 26 public Base() { 27 System.out.println(" Base()"); 28 val = lookup(); 29 } 30 31 public int lookup() { 32 System.out.println(" Base lookup"); 33 // TODO Auto-generated method stub 34 return 5; 35 } 36 37 public int value() { 38 System.out.println(" Base value"); 39 return val; 40 } 41 } 42 43 class Derived extends Base{ 44 private int num=10; 45 public int lookup(){ 46 System.out.println(" Derived lookup"); 47 System.out.println(" Derived lookup num=="+num); 48 return num; 49 50 } 51 public Derived(){ 52 System.out.println(" Derived()"); 53 System.out.println(" Derived Derived() num=="+num); 54 } 55 56 }
PDF和笔记源码下载地址:http://download.csdn.net/detail/draem0507/7342879