0%

Java核心技术(卷1)读书笔记

老夫一个寒假就干了这么点事(王老师:怪我咯……

3 Java的基本程序设计架构

  • 整形(int)的范围与机器无关,都是32位、4字节、-20亿~+20亿。解决了移植性的问题。
  • long的后缀是l,16进制前缀是0x,8进制前缀0(少用),2进制前缀0b,并且从Java7开始可以在数字中加入下划线,增强易读性。
  • Java没有无符号类型。
  • float,占4个字节,后缀F,单精度数,少用。double,占8个字节,后缀D,双精度数,常用。
  • 在数值计算中若要不含任何误差,应该使用BigDecimal类。
  • strictfp关键字表征了在该方法体中所有浮点运算都将使用严格的浮点计算。
  • 与运算在第一个表达式为假时不会继续计算后续的表达式;或运算在第一个表达式为真的时候不会继续后续的表达式。
  • &、|运算法作用于布尔值时,同&&、||,只是没有短路的说法,会计算完所有的表达式。
  • 移位运算中,若移位位数多于该数的总位数,会循环移位,因此需要在移位运算中对于移位数进行模运算。
  • String是不可修改其中某一位的。是不可变字符串。只能修改对于该字符串的引用,变为对于另外字符串的引用。这样的好处是可以将相同的字符串进行共享。虚拟机一般情况下只将常量字符串共享,拼接(+、substring)得到的字符串将不会共享。当然可以设置虚拟机将所有的字符串共享。
  • Scanner是一个标准的读取输入方法,用法为:
1
Scanner in = new Scanner(System.in);
  • Console也是一个标准的读取输入方法。用法为:
1
2
Console con = System.console();
String read = con.readLine("hint:");
  • 在使用break、continue语句时,最好在break、continue跳出、重复的循环体之前加上标签。
1
2
3
4
5
read_data:
for(int i = 0; i<5; i++){
if(a[i]>50) break read_data;
}
//then go to here

#4 对象与类

  • 类与类之间的关系有:

    • 依赖(Use-a)如果一个类的方法操纵另一个类的对象,则称一个类依赖于另一个类。我们应该尽量使类之间的相互依赖减到最小,如果A与B之间没有依赖,那么意味着A、B的改变不会造成任何Bug,即让类之间的耦合度最小。
    • 聚合(Has-a)类A的对象中包含了类B的对象。
    • 继承(Is-a)
  • Java中虽有的对象变量都是对堆中的对象的引用。

  • 不要编写返回可变对象的访问器方法。如果需要返回一个可变对象的引用,应该首先对他进行clone。

  • 一个方法可以访问所属类的所有对象的私有数据。如类A的对象a中的方法可以访问同为类A的另一个对象b的私有数据。

  • 所有类的对象会共享同一个静态引用。

  • 在类中添加Main方法,是一个进行单元测试的好方法。

  • 传入方法的参数总是按值调用。即,方法得到的是所有参数值的一个拷贝,方法不能修改传递给他的任何参数变量的内容。但是方法的传入参数包括了基本数据类型和对象引用,当传入参数是对象引用的时候,虽然该引用地址不能被更改,但是该引用的对象是可以被修改的。

  • 实际上对象引用进行的依旧是值传递,我们可以操作的是传入的对象引用指向的对象,而不能操作该对象引用。

  • 数据域若是没有被构造器赋值,那么会被默认的将数值置0,布尔置false,对象引用值null。

  • 当且仅当类中没有提供任何构造器时,系统才会默认的提供一个没有输入参数的构造器。若有,那么系统将不会提供0参数的构造器,若在这个时候调用0参数的构造器,将会报错。

  • 在类中的构造器调用之前,会先对预设的值进行赋值。

  • Java中又三种初始化数据域的方法:

    • 在声明中初始化
    • 在初始化块中初始化
    • 在构造函数中初始化

    从上到下依次执行。

  • 可以使用静态代码块对静态变量进行初始化。

  • 静态导入可以方便的使用某个类中的静态方法。如Math类。

5 继承

  • Object…args等同于Object[] args,在输入任意多参数的时候会被自动装箱成Object[] args。
  • 枚举类的toString方法可以得到该枚举对象的String,反过来,通过枚举对象的String,可以通过Enum.valueOf(枚举类.class,枚举字符串)得到对应的枚举对象。
  • 枚举对象的ordinal()方法可以得到该枚举常量的位置。

6 接口与内部类

  • 虽然不能new一个接口对象,但是可以用一个接口变量引用任何实现了该接口的类对象。
  • 一样的可以同instanceof来检查某个对象是否实现了某个接口;接口依然可以被继承;接口的定义中虽然不能实现具体的方法,但是可以定义常量(static final)
  • 抽象类与接口最大的区别在于,一个子类只能继承于一个抽象类,却可以实现数个接口。(Java是没有多继承的)
  • 即使默认的clone已经满足要求,也应该实现Clonable接口,将clone定义为publi,并调用super.clone()实现浅拷贝,若要进行深拷贝,则需要在浅拷贝的基础上,对于拷贝出来的对象中每一个引用对象进行深拷贝。
  • 所有的数组类型均包含一个public的克隆方法。可以方便的进行数组对象的克隆。
  • 内部类可以访问自身的数据域,月可以访问创建它的外围类对象的数据域。内部类中总是有一个隐式引用,指向其外部类。

11 异常、断言、日志与调试

  • 由程序导致的是runtimeException,由IO错误导致的错误会返回ioException。

  • 对于所有可能抛出异常、会被别人调用的方法,在声明的时候,最好都要加上throw语句以提示使用者做出反应。

  • 子类中不能抛出比超类中更普适的异常。

  • throws用于方法名声明时,throw用于抛出具体错误

  • Java中,throw只能抛出Throwable对象,而不像在cpp中可以抛出任何值。

  • 对于自己定义的Throwable类型,需要实现两个构造器,其中一个是默认构造器,另一个是带有描述信息的构造器,通过调用super的构造器将这些信息打印出来。

  • 在进行错误的处理的时候,最好的策略是,对于知道如何处理的异常进行捕获;对于不知道该如何处理的异常进行传递,将该异常抛给调用这个方法的人,再上一层进行异常处理。

  • 异常传递的时候,可以在新异常中使用initCause方法将原异常作为原因传递给新异常,那么在新异常抛出的时候就可以获取到原始愿意。

  • 在try/finally语句块中使用return需要注意:在try中的return调用之前,会调用finally中的语句块,如果finally语句块中也有return,那么会出现一些很奇妙的错误。

  • 建议独立使用try/finally语句块和try/catch语句块,例如在try/catch中放进try/finlly语句块,这样,外层的catch语句就可以保证捕捉到try语句和finally语句中的异常,并且finally也能被正确的执行。

  • 使用异常的技巧:

    • 只在异常情况下使用异常。使用异常进行测试是非常耗时间的。
    • 不要过分细化异常。尽量使用多个catch对可能的数个异常进行捕获。
    • 利用异常层次结构。不要只抛出runtimeException,不要只捕获throwable异常。
    • 早抛出,晚捕获。早抛出异常可以更加明显的得到异常的类型及信息;晚捕获可以让高层的用户得知发生了错误并做出更加正确的处理动作。
  • 断言机制允许在测试期间向代码中插入一些检查语句,当代码发布的时候,这些语句将被移除。

  • 断言会对给定的条件进行检查,检查结果如果为false,将会抛出一个AssertionError的对象。assert语句有两种形式:

    • assert 条件;

    • assert 条件:表达式;

    其中,表达式的作用仅仅是产生一个消息字符串,传入AssertionError构造器中,转换成一个消息字符串。

  • 使用以下语句启用\禁用断言:

    java -enableassertions MyApp

  • 断言失败是致命的,是不可回复的错误;断言一般只用于开发、检测阶段。

12 泛型程序设计

  • 泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用。

  • 泛型类可以看做普通类的工厂,使用需要的数据类型对泛型类进行初始化,即完成了私有类的生成。

  • 泛型方法定义如下:

    public static T method(T… a)

    调用时,将类名放入尖括号即可。在定义泛型方法的时候,可能需要对于泛型进行限定:

    public static <T extends Comparable & Serializable> T method(T… a)

    这样的限定相当于限定了该泛型必须调用哪些方法。

13 集合

  • Java中对于数据结构的类库是将接口与实现分离的,每一个实现都可以通过一个实现了某一个数据类型的接口类型进行标示。对其进行引用的时候,只需要使用接口作为引用变量,引用任何一种实现了该接口的数据结构类型即可。
  • 在API中也有很多以Abstract开头的类,在自己想要实现某种类型的数据结构的时候,继承这些类比直接实现某个接口来的方便很多(虽然基本不会用到)
  • Collection是基本的集合接口,其中有两个基本方法:add和iterator。add用于添加一个元素,成功返回true,失败返回false;iterator用于返回一个迭代器,迭代器是一个实现了Iterator接口的对象,其中包括三个方法:next,hasNext,remove。迭代器用于遍历collection,在Java SE 5.0之后可以优雅的以foreach完成。
  • foreach在遍历arrayList的时候是顺序访问,在遍历HashSet的时候是以一种随机的方式进行访问。
  • iterator中的remove是用于删除上次调用next方法时返回的元素。在调用remove之前必须调用next,否则将出错。迭代器所处的位置是两个元素之间。
  • Java中的链表(LinkedList)都是双向链表。
  • 调用链表的迭代器中的remove需要注意,当先调用next的时候,将会删除左侧节点,当调用previous的时候,将删除右侧节点,即remove总是删除刚刚经历过的节点。
  • vector所有的方法都是同步的,而Arraylist的方法不是。因此在不需要同步的时候使用ArrayList以节省时间,只在需要同步的时候使用Vector。
  • Java中hashTable用链表数组实现,每个列表被称为【桶】,桶的个数为总数的75%~150%,最好将桶数设置为一个素数。在插入元素的时候,首先对该元素计算HashCode,然后使用桶数对其取余,得到的数就是插入位置,如果该位置已经有元素,则称发生了【散列冲突】,此时将比较该桶中所有元素,查看是否有重复,若无则插入在桶中最后一个元素的后面。当该散列表中,一定比例的桶已经填充完毕,则需要进行【再散列】,决定是否进行再散列的因子被称为【装填因子】,一般取75%。
  • TreeSet使用红黑树实现有顺序的散列表,每一次将元素添加到树中的时候,都会被放置到正确的位置上。
  • 优先级队列使用堆完成,堆是一个可以自我调整的二叉树,可以自动的使最小的元素移动到根,而不用花时间排序。
  • Java中对于映射表也有两个实现:HashMap和TreeMap,均实现了Map接口。此两者均为对键值的操作,并不会按照关联的值进行操作。HashMap对于键值进行散列;TreeMap对于键值进行顺序排列,同样的,hash会比tree更快一些。

14 多线程

  • 多线程:一个程序同时执行多个任务。每一个任务称为一个线程(Thread)。

  • 多线程与多进程的区别在于:每个进程拥有自己的一整套变量,而线程之间则共享数据。

  • synchronized关键字可以防止代码块收到并发访问的干扰。

  • 也可以使用ReentrantLock保护代码块:

    1
    2
    3
    4
    5
    6
    7
    myLock.lock();
    try{
    //code here
    }
    finally{
    myLock.unlock;//放在finally中是很重要的事,否则一旦出错,其他线程将永远被阻塞下去。
    }

    当使用锁的时候,不能使用带资源的try语句。

  • 从Java1.0开始,加入了synchronized关键字,起作用等同于对于整块代码进行保护的进程锁,免去了显式的对方法进行进程锁。同样的,在synchronized标示的方法中,同样可以使用条件锁,响应的语句为wait和notifyAll。