Java编程思想—读书笔记(更新中)

时间:2021-02-27 14:44:10

第1章 对象导论

1.4 被隐藏的具体实现

访问控制的原因

  1. 让客户端程序员无法触及他们不应该触及的部分(不是用户解决特定问题所需的接口的一部分)
  2. 允许库设计者可以改变类内容的工作方式而不用担心会影响到客户端程序员

访问权限

Java用三个关键字在类的内部设定边界,这些关键字决定了紧跟其后被定义的东西可以被谁使用

  1. public:表示紧随其后的元素对任何人都是可用的
  2. private:表示除类型创建者和类型的内部方法之外的任何人都不能访问
  3. protected:与private作用相当,区别在于继承的类可以访问protected成员,但不能访问private成员

默认的访问权限:包访问权限。没有使用任何指定的访问指定词时,在这种权限下,类可以访问在同一个包中其他类的成员,但在此包之外,这些成员就如同指定了private权限。

1.5 复用具体实现

最简单的复用就是直接使用该类的一个对象,同时也可以将那个类的一个对象置于某个新的类中。

组合:经常被视为”has-a“(拥有)关系;(一个长难句拆开理解)一个新类可以由任意数量、任意类型的其他对象,以任意可以实现新类中想要的功能的方式所组成,这样使用现有的类合成新的类的方式,称为组合。

新类中的成员对象通常要声明为private类型,使得新类的客户端程序员不能访问它们

1.6 继承

引出继承背景:在创建了一个类之后,即使另一个新类与其具有相似的功能,你还得是重新创建一个新类。如果我们能够以现有的类为基础,复制它,然后通过添加和修改这个副本来创建新类那就要好很多了。通过继承便可实现这样的效果。

例外:当源类(基类、父类或超类)发生变动时,被修改的“副本”(也就是子类、导出类或继承类)也会反映出相应的变动

核心思想:一个基类型包含其所有导出类型所共享的特性和行为,那么可以创建一个基类型来表示系统中某些对象的核心概念,从基类型中导出其他类型,来表示此核心可以被实现的各种不同方式。

当继承现有的类型时,就创造了一个新的类型。这个新类型不但包含了现有类型的所有成员(包括private成员,虽然不可访问),还复制了基类的接口。换句话说,就是所有能够发送给基类对象的消息,也能发送给导出类对象。

导出类的两种行为:如果只是简单继承一个类而不做其他任何事情,那么导出类对象没有什么特别的意义。有两种方法可以使基类与导出类产生差异:

  1. 直接在导出类添加新的方法
  2. 改变现有基类的方法,overring(覆盖或重写)

1.10 对象的创建和生命期

C++认为效率控制是最重要的,所以为了追求最大的执行速度,对象的存储空间和生命周期可以由程序员选择,通过将对象置于堆栈或静态存储区域内来实现。

  1. Java完全采用了动态内存分配方式。每当想要创建新对象时,就要使用new关键字来构建此对象的动态实例。
  2. Java“垃圾回收器”的机制,自动发现对象何时不再被使用,进而销毁它,释放对象占用的内存。(根据所有对象都是继承自单根基类Object只能以一种方式创建对象(即在堆上创建)这两个特性而来的)

第2章 一切都是对象

2.1 用引用操纵对象

String s; // 引用
String s = "asdasd"; // 创建引用并初始化

2.2 必须由你创建所有对象

存储的地方

  1. 寄存器:处理器内部,C/C++可以进行寄存器的分配方式
  2. 堆栈:RAM中,某些Java数据存储在堆栈中——比如对象引用,但一般Java对象并不存储在其中。基本数据类型的值存在堆栈中
  3. 堆:一种通用的内存池(也位于RAM中),用于存放Java对象。堆中编译器不知道存储的数据所存活的时间,而在堆栈中编译器知道。
  4. 常量存储:程序代码内部
  5. 非RAM存储:流对象和持久化对象。把对象转化成可以存放在其他媒介上的事物

2.4 字段和方法

基本成员默认值:若类的某个成员是基本数据类型,即使没有初始化,Java也会确保它获得一个默认值

boolean类型为false,char为null,byte、short、int、long、float、double为0(如0.0f、0.0d等)

需要注意,只有作为类的成员变量使用时,若为局部变量,如int x,则有可能为任意值,且为了防止产生程序错误,最好还是初始化变量。局部变量一定要初始化,Java会报错,C++编译器可能不报错。

2.6 构建一个Java程序

Java的类库命名:为了避免一个程序中的不同模块使用相同名字而导致的冲突,也就是避免与其他类中的方法名想冲突,Java采用域名倒置的命名方法,反转域名。

static关键字

  1. 作用于某个字段时:会改变数据创建的方式,因为一个static字段对每个类来说都只有一份存储空间,而非static字段则是对每个对象有一个存储空间。不同对象最终指向同一存储空间的字段。

    书中抛出的问题是,只想为某特定域分配单一存储空间,而不去考虑究竟要创建多少对象,甚至根本就不创建任何对象。

  2. 作用于某个方法时:在不创建任何对象的前提下就可以调用它。

    可以直接使用类名调用类方法和类变量

类名首字母大写,采用驼峰命名方法

第3章 操作符

3.7 关系操作符

测试对象的等价性

关系操作符!=和==也适用于所有对象,但是是比较的对象的引用,下面举个例子:

Integer n1 = new Integer(47);
Integer n2 = new Integer(47);
System.out.println(n1 == n2);
System.out.println(n1 != n2);
/**Output:
false
true
*/

上述两个Integer对象的内容相同,但是引用却是不同的。然而基本类型数据可以直接使用关系操作符进行比较。

要比较两个对象的内容,要使用特殊方法equals()。(P45 书中提到自定义类使用equals()报错,需要学习第7章和第17章的内容,先挖个坑)

3.8 逻辑操作符

Treating an int as a boolean is not legal Java

注意与C/C++的不同:不可将一个非布尔值当作布尔值在逻辑表达式中使用。与或非 只能应用于布尔值操作。

例如:i和j为65和75,print(i && j) 就是非法的。

短路现象:在逻辑表达式中,一旦能够明确无误地确定整个表达式的值,就不再计算表达式余下部分。

截尾和舍入:强制将floatdouble转型为整数型时,如(int) x,x为2.7,总是对数字进行截尾操作,转换为2。若要进行取舍操作,则使用lang.Math库中round()

Java没有sizeof:在C/C++中需要进行移植,然而不同数据类型在不同的机器上可能有不同的大小,所以必须有一些与存储空间有关的运算,以方便程序员获悉哪些类型具体有多大。而Java中所有数据类型在所有机器中的大小都是相同的,不需要考虑移植问题

第4章 控制执行流程

4.1 true和false

Java中不允许我们将一个数字作为布尔值使用,虽然这在C/C++里是允许的。比如在if(a)中,应当使用if(a != 0) ,用一个条件表达式将其转换成布尔值。

4.7 goto语句

goto是Java中的一个保留字,但在语言中并未使用它。Java也能实现goto语句这样类似于跳转的操作,这与break和continue关键字有关。

Java中需要使用标签的唯一理由就是因为有循环嵌套存在,进行循环嵌套的跳转控制,就需要使用标签+breakcontinue组合。

  1. break本身只能中断最内层的循环(continue同样也是如此)。
  2. return,中断循环大法。
  3. 带标签的continue会到达标签的位置,并重新进入紧接在那个标签后面的循环,break带标签则是中断并跳出标签所指的循环。

书中给出了一个比较好的例子,对于理解如何使用break和continue加上标签实现跳转操作有帮助:i初始为0

Java编程思想—读书笔记(更新中)

第13章 字符串

13.1 不可变String

String对象是不可变的。每当把String对象作为方法的参数时,都会复制一份引用,而该引用所指的对象其实一直待在单一的物理位置上,从未动过。这里举下面一段代码示例:

Java编程思想—读书笔记(更新中)

参数是为方法提供信息的,而不是想让该方法改变自己的。

13.2 重载“+”与StringBuilder

String对象是不可改变的,可以给这个String对象加任意多的别名,因为这个对象只具有只读特性,所以指向它的任何引用都不可能改变它的值。

Java中仅有的两个重载过的操作符分别是:++=。而Java是不允许程序员重载任何操作符。

操作符重载:一个操作符在应用于特定的类时,会被赋予特殊的意义。比如操作符 “ + ” 可以用来连接String。

当我们在使用String对象拼接一个字符串的时候,编译器可能会重复地创建多个StringBuilder对象,最后再调用toString()生成结果。

需要指出,当我们在使用String对象的时候,编译器会自动优化性能,在对应的字节码文件中会转换为StringBuilder对象。显示创建StringBuilder能够允许预先指定其大小,这样可以避免多次进行重新分配缓冲,往往只需要生成很少的StringBuilder对象。

书中指出:如果要在toString()方法中使用循环,最好使用StringBuilder对象。

13.3 无意识的递归

引出问题:

Java编程思想—读书笔记(更新中)

打印结果会出现一串非常长的异常。注意观察红线部分,发生了自动切换,在String对象后跟了一个 + ,所以编译器将this试着转换为一个String对象,而Java中的类都是继承于Object类,标准容器类也不例外,且都有一个toString()方法,强制调用this上的toString()方法,导致异常,发生了递归调用。

如果真要打印对象的内存地址,应该调用Object.toString(),即使用super.toString()。

13.4 String上的操作

  1. 当需要改变字符串的内容时,String类的方法都会返回一个新的String对象。
  2. 如果内容没有改变,String的方法只是返回指向原对象的引用。

第16章 数组

数组与其他种类的容器之间的区别体现在三方面:效率、类型和保存基本类型的能力。

在Java中,数组是一种效率最高的存储和随机访问对象引用序列的方式。数组是一个简单的线性序列,因此访问元素的速度十分快,可是这样的代价是数组对象的大小被固定,且不可改变。

数组可以持有基本类型,而泛型之前的容器不能。有了泛型之后,就可以给容器指定并检查它们所持有对象的类型,在自动包装机制下,可以使得容器看起来是能够持有基本数据类型的。

对象数组基本类型数组在使用上几乎是相同的,唯一的区别就是对象数组保存的是引用,基本类型数组直接保存基本类型的值

length只表示数组能够容纳多少元素,而不是实际保存的元素个数。

基本类型数组的值在不进行显示初始化的情况下,会被自动初始化。对象数组会被初始化为null。

作者强烈建议使用容器,而不是数组。