《Thinking in Java》第四版读书笔记(持续更新中)

2019-01-14  本文已影响0人  Yufail

第一章 对象导论、第二章 一切都是对象

状态标识内部数据,行为表示函数,标识表示对象在内存中的唯一地址

接口确定了对某一个特定对象所能发出的请求,即确定了某一对象可以调用的方法

高内聚的原则强调在类中组合合适的方法,不要将所有的方法都强行塞到一个类中去

组合关系通常用户形容类的包含关系,如汽车包含引擎

ArrayList具有固定的访问特定元素的时间,而LinkedList访问元素的时间取决于索引的大小;ArrayList在插入和删除新元素方面比LinkedList逊色。最好的解决方案是在最初构建程序时使用LinkedList,而在性能优化时使用ArrayList

Java官方文档中并未给出boolean类型的具体大小。《Java虚拟机规范》中这样解释:虚拟机会将boolean变量当作int解释,也就是四个字节。而将boolean数组中的每个元素当作byte解释,也就是一个字节。

第三章 操作符

一元减号只是为了给原有数字取反而已,而一元加号存在的意义是只是为了与一元减号相对应,但不知道为什么中文书《Java编程思想(第四版)》P43会有这样一句话:“它唯一的作用就是将较小的数据类型提升为int”,而英文原著中并不存在这句话

效果是不相同的,第一句输出0,第二句输出2

上面的用法便是错误的

从最后一行便可以看出b>>>10得到的结果为int型变量

在进行窄化转换时,必须注意截取和舍入问题,将float直接转为int是截取,而通过java.lang中的round()(float转int,double转long)方法则是舍入

比int小的类型会被转化为int,当float和double一起运算时会转为double,int和long运算时会转为long

C和C++有sizeof()函数的原因是,不同机器上基本数据类型的字节数可能不同,为了进行移植而必须使用这个函数,而Java的数据类型在所有机器中都是相同的

  1. boolean型变量从本质上来说不属于整形变量,这一点与C和C++差别较大,因此对boolean类型可以进行的运算非常有限,有基本的逻辑运算 = == != && || !和位运算& | ^ &= |= ^=(不支持按位非),且不支持所有的与整形,浮点型类型的类型转换
  2. 整形变量基本支持除了布尔逻辑运算(&& || !)之外的所有运算,且支持整形之间和浮点数之间的相互转换
  3. 浮点运算支持的运算少于整形变量,在整形变量的基础上,不支持移位运算和按位运算等

第四章 控制执行流程

通过逗号操作符,可以在for循环里定义多个变量,但是它们必须具有相同的类型。在一个控制表达式中,定义多个变量这种能力只限于for循环使用,在其他任何选择或迭代语句中都不能使用这种方式

以上的range函数用于产生合适的下标索引数组,可以增加可读性,但它的效率会降低

第五章 初始化与清理

解释:如果传入数据类型正好符合函数形式参数,则会调用,如果传入数据类型小于函数形式参数,则会扩展实际数据到最接近实际数据数据类型大小的类型,此处应注意数据类型的转换顺序和规则。可以看出,针对立即数5,其被解读的方式为int,当不存在int类型时依次会转换为long->float->double;而byte转换顺序为short->int->long->float->double;short转换顺序为int->long->float->double;long转换顺序为float->double;而float则会转为double。char类型有点特殊,如果无法找到恰好接受char参数的方法,就会把char类型直接提升为int,进而long->float->double

  1. 为了区分不同对象调用相同的函数,编译器将所操作对象的引用作为第一个参数传给了将要调用的函数
  2. 为了良好的编程风格和可读性,无需在类内部相互调用函数时处处写上this,这个工作由编译器完成即可。我们只在必须用到this的地方使用这个关键词,如获得当前对象的引用return this
  3. this关键字还可用来在构造函数中调用其他的构造函数。但是需要注意使用this只能同时调用一个构造函数,并且需要将this构造函数调用置于函数的开始处,否则编译器会报错
  4. this关键字可以在类变量和其他变量重名时发挥作用,如this.s代表类变量s,这是一种相当常见的用法
  1. 在C++中,如果程序没有缺陷,对象一定会被销毁,然而在Java中,GC只会处理由new关键字创建的对象,一些特殊对象无法得到回收,如某个对象在创建过程中将自己绘制到屏幕上,如果不是明确地从屏幕上将其擦除,它永远得不到清理,在这个时刻,如果在finalize()中加入了清理方法,当垃圾回收发生时,finalize()会被调用,则被回收,如果没有发生,图像会一直保留下来。因为垃圾回收finalize()方法的执行是由一个优先级较低的Finalizer线程来完成,所以finalize()是否会被调用取决于程序的执行情况,非人为因素可以干预,所以说:Java中某些对象可能不会被垃圾回收
  2. 垃圾回收不等于C++中析构函数
  3. 造成Java中某些对象无法被垃圾回收的原因跟本地方法调用有关,如通过调用本地C方法中的malloc申请的内存,除非在finalize()中调用free,否则永远不会被回收

在类的内部,变量定义的先后决定了初始化的顺序,即使变量定义散布在方法定义之间,他们仍然会在任何方法(包括构造器)被调用之前得到初始化

  1. 类加载时,静态变量按照其在类中的排列顺序依次进行初始化,注意静态变量只初始化一次且先于非静态变量的初始化,如果在类已经加载之后构造类的对象,则静态变量不会重新初始化
  2. 构造类的对象时,进行非静态变量的初始化,这个时刻晚于静态变量的初始化,且每次构造新的对象都会重新进行非静态变量的初始化
  3. 在程序运行之前,所有类都没有被加载,在一个类没有被加载之前,新建这个类的对象(new Cupboard()),使用关于类的任何变量和函数(Cupboard.staticVar,Cupboard.staticMethod())都会触发类的加载过程,但是要注意新建类的对象包括加载类和新建对象两个过程,而使用静态变量或者函数则只包括加载类的过程
  1. 定位Dog.class。首次创建类的对象,或者首次访问Dog类的静态方法/静态域时,Java解释器查找该类的路径,以定位Dog.class文件
  2. 加载Dog.class(这个操作将会创建一个Class对象),有关静态初始化的所有动作都会执行,而且,静态初始化只在Class对象首次被加载时进行一次
  3. 使用new操作符时,首先会在堆上为Dog分配足够的储存空间
  4. 这段储存空间会被清零,基本数据类型为0,引用类型为null
  5. 执行所有非静态字段的初始化动作
  6. 执行构造器

尽管上面的代码看上去像个方法,但它实际上只是一段跟在static关键字后面的代码,与其他静态初始化动作一样,这段代码仅执行一次,就是发生在类加载之后

非静态初始化实质上就是非静态字段的初始化动作,发生在申请空间清零之后,构造函数调用之前

与switch语句是绝佳的搭配,注意了解内置方法values()和ordinal()

第六章 访问控制权限

  1. 可以通过静态工厂的方法生成类的实例,这种方式在某些方面(如控制实例数量)比直接调用构造函数更有优势
  2. 通过将构造函数私有化,可以完成单例模式

第七章 复用类(组合和继承)

通过代理可以拥有更多的控制力,可以只选择成员对象中所有方法中的某个子集,比继承更具有灵活度

与C++不同,在子类中引入新的重载方法时无需屏蔽父类的原有重载方法,基类子类中的方法全部都可以正常使用
@Override用于标注覆盖的方法,覆盖则要求与父类方法函数签名和返回类型全部一致,并且加了这个标记后如果是无效的覆盖,编译器则会报错

尽管面向对象中继承是多次强调的,但是应该慎用这一技术,是否需要选择继承,一个最清晰的判断方法是问一问自己是否需要从新类基类进行向上转型,如果必须向上转型,则继承是必要的

  1. final数据。一个永不改变的编译期常量,在编译时就可以执行计算的常量,减少了运行时负担,但不是所有的final数据都能在编译时期计算其值,如第三行,仍然需要运行时确定。且非基本数据类型仍需要运行时赋值。另外,final类型的引用数据类型只是确定了一个不变的引用,其指向的对象仍有方法可以改变
  2. final参数。这意味着无法在方法中修改参数引用所指向的对象,这一特性主要用于向匿名内部类传递数据
  3. final方法。禁止继承类的覆盖,声明为private的方法自动也属于final
  4. final类,无法被继承
  1. 当创建类的第一个对象,或者使用类的静态变量或静态方法时,就会加载类,静态变量的初始化动作按顺序发生,非静态变量的初始化动作则在新建对象的时候发生
  2. 存在继承关系时,如果使用new操作,则先分别按照层级加载所有父类,最后加载本身,进而按照父子顺序执行非静态变量的初始化和构造器
  3. 当只引用静态变量时,看静态变量在哪个类里,将那个类的所有父类加载,与那个类的任何子类无关
  4. 针对这个例子,new AAA操作会首先调用A所有非静态成员的初始化动作,接着调用A的构造函数,进而AA,最后AAA

第八章 多态

为什么要将某个方法生命为final呢,就是为了防止该方法被覆盖,同时,这样做也可以有效的“关闭”动态绑定,是编译器为final方法调用生成更有效的代码,然而,这对性能并没有什么有效的改观,所以是否使用final应该根据设计方案,而不是处于提高性能的目的来使用它

如果覆盖私有方法,因为私有方法默认final,无法被子类继承,所以在子类中覆盖基类的私有方法相当于在子类中定义新方法,所以这个新方法不具有多态的性质。因此,应养成良好的编程习惯,不要在子类中方法出现与父类private方法重名的情况

如果使用对象来调用静态方法,那么方法的调用只跟引用的类型有关,而与具体指向的对象无关,则可以根据引用所属的类型在编译期就可以确定调用的函数,无需等到运行期,所以不需要动态绑定

第九章 接口

  1. 抽象方法只有定义没有实现,当一个类中含有一个或多个抽象方法时,类本身应该也被声明为abstract
  2. 抽象类不能用于产生对象,如果要产生对象,需要通过继承新建子类对象,且子类需要实现父抽象类所有的抽象方法,否则,子类本身也应该被定义为abstract
  3. 不包含任何抽象方法的类也可以被声明为abstract,在这种情况下,这种方式只有一个作用,那就是阻止这个类产生对象
  1. 接口中的变量默认static和final
  2. 接口中方法默认public,当在具体类中实现接口中的方法时,也应该设置访问权限为public,应为要遵循Java向下转型过程中方法权限不能减小的规则

第十章 内部类

  1. 如果需要在外部的static方法中创建内部类对象,则需要使用外部类的对象来创建内部类对象
  2. 通过使用外部对象.new操作可以生成内部类的对象,通过在内部类中return Outer.this可以获得外部对象的引用
  3. 必须使用外部对象来创建内部对象,通过.new操作或者使用外部类的非静态方法。但是如果内部类是静态内部类,则不需要外部对象来创建
  4. 外部类可以访问内部类的private元素
  5. 普通的内部类对象默认保存外部类对象的一个引用,因此不能拥有static数据和static方法

当某个外部类创建一个内部类对象时,此内部类对象会获得一个指向那个外部类对象的引用。在访问外部类成员时,就是通过这个引用来访问

  1. static类型的内部类。此时外部类对象与内部类对象没有什么关系,这通常被称为嵌套类。普通的内部类对象隐式的保存了一个指向外部类对象的引用,而嵌套类并不是这样
  2. 要创建嵌套类的对象,不需要外部类的对象
  3. 不能从嵌套类对象中访问非静态的外部类对象
  4. 普通的内部类不能包含static数据和static方法,但嵌套类可以
  1. 一般来说,内部类用于继承自某个类或实现某个接口,内部类的代码操作其外部类对象的数据,可以认为内部类提供了某种进入外部类对象的窗口
  2. 可以使用内部类解决多重继承问题
```java
class A{ }
abstract class B{}
class AB extends A{
    B makeB(){ return new B() {}; }
}
public class AnonymousClassFactory {
    static void takeA(A a){}
    static void takeB(B b){}
    public static void main(String[] args) {
        AB ab = new AB();
        takeA(ab);
        takeB(ab.makeB());
    }
}
```
  1. 在单个外部类中,可以让多个内部类以不同的方式实现同一个接口,或是继承同一个类
  2. 创建内部类对象的时刻并不依赖于外部类对象的创建

闭包是一个可调用的对象,它记录了一些信息,这些信息来自创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包

第十一章 持有对象

foreach只能用于数组或其他任何的Iterable对象,因为所有的Collection都是Iterable对象,所以所有的Collection均可以使用foreach,但是如果自定义一个类可以使用foreach循环来迭代,则需要实现Iterable接口

第十二章 通过异常处理错误

也可以打印整个StackTraceElement,也包含其他的信息

无论try中是否有return,finally中语句都会被执行,finally中一般完成关闭文件或网络连接,处理屏幕上的图形

上一篇下一篇

猜你喜欢

热点阅读