Garland +

Head First Java 阅读笔记

花了应该是小一个月时间终于把这本书看完了, 对于 Java 算了稍微入了门,这本书很全面的讲了 Java 这门语言的基础概念,例子生动形象,如果有其他语言的基础理解起来会快一点,回过头来看,Java 真的是一门设计的很不错的语言,python 狗想想自己就很辛苦。

我看的是这本[1],但是可能是新版,因为快 700 页,里面给的大部分的示例代码自己都敲了一遍,感觉还是不错。说到看书,由于自己开坑太多,看书不太专一,这样不好,这本书应该预计是每天花上一两个小时,然后两周左右读完的,希望后面自己看书能专一点,一本一本读,java 这块后面打算看下 Java编程思想 这本书了。

笔记放出来以当备忘。

过程

sample.java → javac sample.java → java sample.class

类存于源文件里面,方法存于类中,语句存于方法中

GC

所有的对象创建完毕都会被放在堆中。java 会根据对象的大小来分配内存空间,当某个对象被 jvm 发现不会再被使用到,该对象就会被标记成可回收的,如果内存不足, gc 就会启动来清理垃圾、回收内存空间。

在任何类中的任何程序都可以存取 public static 的方法,任何变量只要加上 public、static 和 final,基本都会变成全局变量取用的常数

基础数据类型:boolean char byte short int long float double

只有引用到对象的变量,对象引用变量保存的是存取对象的方法。类似指向对象的指针,或者可以说是地址。即变量有两种:主数据类型和引用

Dog myDog = new Dog(); 代表取得 Dog 对象的方法以字节形式放进变量中,对象本身并没有放进变量中

对基础数据类型中的变量来说,变量值就是所代表的值,对引用变量来说,变量值是取得特定对象的位表示法。

对象的声明创建和赋值

Dog myDog = new Dog()

  1. 声明引用变量:Dog myDog; 要求 java 虚拟机分配空间给引用变量,并将此变量命名为 myDog。此引用变量将永远被固定为 Dog 类型。
  2. 创建对象:new Dog(); 要求 java 虚拟机分配堆空间给新建立的 Dog 对象
  3. 连接对象和引用: = 将新的 Dog 赋值给 myDog 这个引用变量。

对于任意 java 虚拟机来说,所有的引用大小都一样。

数组是对象

隐式展开:byte 类型可以放进 int 数组

对象的实例变量表示对象的状态,对象的方法表示对象的行为

实参是传给方法的值,当它传入方法后就变成了形参。

java 是通过值传递,即拷贝传递,即方法无法改变调用的所传入的参数

Getter 返回实例变量的值。Setter 取用一个参数来设定实例变量的值,是一个封装的概念,隔离直接存取变量的操作。

封装的基本原则

将实例变量标记为私有的,并提供共有的 getter 和 setter 来控制存取动作。

实例变量永远都会有默认值,局部变量没有默认值,方法的参数由编辑器进行检查。

实例变量和局部变量的区别:

  1. 实例变量是声明在类中
  2. 局部变量声明在方法中
  3. 局部变量使用前必须初始化

使用 == 来比较两个 primitive 主数据类型,或者判断两个引用是否引用同一个对象;使用 equals() 来判断两个对象是否在意义上相等。

i++ 先运用变量值再自加,++i 先自加再运用变量值

子类继承父类,类的成员即实例变量和方法。父类可以通过存取权限来决定自类能否继承某些特定的成员,存取权限由小到大依次如下:

  1. private: private 类型成员不会被继承
  2. default
  3. protected
  4. public: public 类型的成员会被继承

继承可以

当定义出一组类的父型时,可以用子型的任何类来填补任何需要或期待父型的位置。

运用多态时,引用类型可以是实际对象类型的父类。

如何防止某个类被继承

  1. 存取控制,类可以不标记公有,非公有的类只能被同一个包的类继承
  2. 使用 final 修饰符,表示它是继承树的末端,不能被继承
  3. 让类只拥有 private 的构造程序

编译器会寻找引用类型来决定是否可以调用该引用的特定方法。但在执行期,Java 虚拟机寻找的并不是引用所指的类型,而是在堆上的对象。

覆盖父类方法的要求:参数必须要一样,且返回类型必须要兼容;不能降低方法的存取权限

重载

重载的意义是两个方法名称相同,但是参数不同,所以重载和多态没有关系。重载的目的是为了扩展性

接口与抽象类

接口是一种 100% 纯抽象的类,抽象类就是无法初始化的类

有的时候要限制只有它的子类才能够被初始化,比如 Animal 这种类,不需要一个 animal 对象,也就是这个类不能被 new 出来,可以通过标记类为抽象类,编译器就知道不管在哪儿,这个类不能创建实例。

在类的声明前面加上抽象类关键字 abstract 就好, abstract class Human extends Animal

可以赋值子类对象给父类的引用,即使父类是抽象的

抽象类只有被继承的作用,不是抽象的类就被称为具体类

抽象的类代表此类必须要被 extend 过,抽象的方法代表此方法一定要被覆盖过。

抽象的方法没有实体 public abstract void eat(); 没有方法体,直接以分号结束。

不能在非抽象类中拥有抽象方法。

抽象方法没有内容,只是为了标记出多态而存在,表示在继承树结构下第一个具体类必须要实现出所有的抽象方法。即必须以相同的函数签名和相同的返回类型创建出非抽象方法。

抽象类可以带有抽象和非抽象的方法

Java 中所有的类都是从 Object 这个类继承出来的,它是所有类的父类。

没有直接继承过其他类的类会是隐含的继承对象

任何从 ArrayList 取出的东西都会被当作 Object 类型的引用而不管它原来是什么

编译器是根据引用类型来判断有哪些 method 可以调用,而不是根据 Object 确实的类型。

可以通过 Dog d = (Dog) 0; 将 Object 类型转换为 Dog

Java 没有多继承,Java 使用 interface 来实现多继承,Java 接口就像是 100% 的纯抽象类

接口定义:public interface Pet {}

接口实现:public class Dog extends Canine implements Pet {}

类可以实现多个接口

调用父类的方法,super.run()

构造器与垃圾回收

堆:对象的生存空间

栈:方法调用及局部变量的生存空间

实例变量是声明在类方法之外的变量,局部变量是生命在方法或者方法的参数上

对象引用变量和 primitive 主数据类型变量都是放在栈上

实例变量存在于对象所属的堆空间上

构造函数会在对象被赋值给引用前执行,构造函数没有返回类型,构造函数不会被继承。

如果类有一个以上的构造函数,则参数一定要不一样,也就是重载构造函数的意思

在创建新对象时,所有继承下来的构造函数都会执行。构造函数在执行的时候,第一件事是去执行它的父类的构造函数,会连锁到 Object 这个类位置。

编译器会自动加上 super() 的调用,父类构造函数是无参数版本。

可以用 this() 来从某个构造函数调用同一个类的另一个构造函数,this() 只能用在构造函数中,且必须是第一行语句,super() 和 this() 不能同时使用

最后一个引用消失时,对象就会变成可回收的

释放对象引用的三种方法:

  1. 引用永久性的离开它的范围
  2. 引用被赋值到其他对象上
  3. 直接将引用设定为 null

静态方法

静态方法:一种不依靠实例变量也就不需要对象的行为

可以用私有的构造函数来限制非抽象类被初始化

静态方法不能调用非静态的变量,也不能调用非静态的方法

静态变量:被同类的所有实例共享的变量,同一类的所有实例共享一份静态变量

静态变量会在该类的任何对象创建之前就完成初始化

静态变量会在该类的任何静态方法执行之前就初始化

一个被标记为 final 的变量代表它一旦被初始化之后就不会改动

public static final double PI = 3.14;

静态 final 变量初始化:

  1. 大写并以下划线字符分割
  2. 加入静态初始化程序 static {}

final 的变量表示不能改变值

final 的 method 表示不能被覆盖

final 的类表示不能继承该类

Java 5.0 开始加入 autoboxing 能自动将 primitive 主数据类型转换成包装过的对象

单例模式实现

异常处理

当需要抛出异常时需要在函数声明抛出什么异常

异常是 Exception 类型对象

编译器不管运行期间的异常,即 RuntimeException 类型的异常,RuntimeException 不需要被声明或者包在 try/catch 块中

内部类

场景:在一个类中实现同一个接口多次,如 GUI 编程里的事件处理

通用的说法是:任何时候需要一个独立却又好像另一个类成员之一的类时就会需要内部类

序列化和文件输入输出

当对象被序列化时,被该对象引用的实例变量也会被序列化,且所有被引用的对象也会被序列化。

Serializable 接口:类声明中有实现这个接口的类以及它的子类都是可以被序列化的

不能或者不应该被序列化的实例变量需要被标记为 transient

当对象被还原且它的父类不可序列化时,父类的构造函数会跟创建新的对象一样的执行。

关于变量被标记为 transient 时反序列化的状态问题

当把某个对象序列化,transient 引用的实例变量会以 null 返回,而不管它当时存储的值是什么,恢复的时候需要自己做默认值处理或者新起一个变量在序列化的时候进行保存

对于同一个对象的两个引用的序列化,序列化只存储一个该对象

对象被反序列化时,Java 虚拟机会通过尝试在堆上创建新的对象,让它维持与序列化时有相同的状态来恢复对象的原状态。具体流程如下

  1. 对象从 stream 中读出来
  2. Java 虚拟机通过存储的信息判断出对象的 class 类型
  3. Java 虚拟机尝试寻找和加载对象的类。如果找不到或者无法加载该类,就会抛出异常
  4. 新的对象会被放在堆上,并不会执行构造函数,即对象回到被存储时的状态。
  5. 如果对象在继承树上有个不可序列化的祖先类,则该不可序列化类以及在它之上的类的构造函数就会执行,也就是从第一个不可序列化的父类开始,都会重新初始状态。
  6. 对象的实例变量会被还原成序列化时的状态值,transient 变量会被赋值 null 或者基本类型默认为 0、false 等。

网络与线程

每个线程都有独立的执行空间,每个 Java 应用都会启动一个主线程,Java 虚拟机负责主线程的启动(以及 GC 所需要的系统用线程),大概流程如下:

  1. Java 虚拟机调用 main()
  2. main() 启动新的线程,新的线程启动期间 main 的线程会暂时停止执行
  3. Java 虚拟机会在线程与原来的祝线程之间切换知道两者都完成为止。

任务需要实现一下 Runnable 这个接口

Synchronized 关键字代表线程需要一把钥匙来存取被同步化(synchronized)过的线程。使用场景是那些不可分割的原子单元操作

集合与泛型

使用泛型可以创建类型安全的集合

泛型的类代表类的声明用到类型参数。泛型的方法代表方法的声明特征用到类型参数。

extend 代表 extend 或 implement,也就是类和接口都适用

引用相等性:引用到堆上同一个对象的两个引用,用 == 比较

对象相等性:需要覆盖从 Object 继承下来的 hashCode() 和 equials() 方法

  1. 如果两个对象相等,则 hashcode 必须相等
  2. 如果两个对象的 hashcode 相同,两个对象不一定相等
  3. 如果 a.equals(b) 则 b.equals(a)
  4. 如果 equals() 被覆盖过,则 hashCode() 必须被覆盖
  5. hashCode() 的默认行为是对在 heap 上的对象产生独特的值,如果没有 override 过 hashcode(),则该 class 的两个对象是不可能会相等
  6. equals() 的默认行为是执行 == 的比较,也就是去测试两个引用是否匹配上 heap 上的同一个对象。如果 equals() 没有被覆盖过,两个对象永远都不会被视为相同的。
  7. a.equals(b) 必须与 a.hashCode() == b.hashCode() 相等
  8. 不同的对象会存在相同的 hashCode 的可能

hashCode 缩小查找范围,最后必须得用 equals() 确认是否真的找到

TreeSet: item 必须是 Comparable 或者使用重载、取用 Comparator 参数的构造函数来创建 TreeSet

如果把方法声明成取用 ArrayList,它就只会去用 ArrayList 参数,其他参数都不可以

数组的类型是运行期间检查的,集合的类型检查只会发生在编译期间。

对于 ArrayList<? extends Animal> 写法,编译器会阻止任何可能破坏引用参数所指集合的行为,也就是可以操作集合元素,但是不能新增集合元素

包、jar存档文件和部署

sun 建议把包名加上域名称,com.headfirstjava.projects.Chart

manifest.txt 描述哪个类带有 main()

创建 JAR: jar -cvfm manifest.txt MyJar.jar com

执行 JAR: java -jar MyJar.jar

远程部署的 RMI

和 rpc 的概念类似,客户端的辅助设施成为 stub,服务端的辅助设施成为 skeleton

创建远程服务步骤:

  1. 创建 remote 接口:定义了客户端可以调用的方法,是个作为服务的多态化类, stub 和服务都会实现此接口
  2. 实现 Remote:实现出定义在该接口上的方法
  3. 用 rmic 产生 stub 与 skeleton
  4. 启动 RMI registry
  5. 启动远程服务:启动后,实现服务的类会启动服务的实例并向 RMI registry 注册

创建远程接口

  1. 继承 java.rmi.Remote
  2. 声明所有的方法都会抛出 RemoteException
  3. 确定参数和返回值都是 primitive主数据类型或 Serializable

实现远程接口

  1. 实现 Remote 接口
  2. 继承 UnicastRemoteObject
  3. 编写声明 RemoteException 的无参数构造函数
  4. 向 RMI registry 注册服务

产生 stub 和 skeleton

  1. 对实现出来的类执行 rmic
  2. 执行 rmiregistry
  3. 启动服务

本文同步发于豆瓣

REF

言:

Blog

Thoughts

Project