Skip to content

Java

进度表

  • Ch0. 基础概念
  • Ch1. 安装
  • Ch2. 对象
  • Ch3. 运算符
  • Ch4. 控制流
  • Ch5. 初始化和清理
  • Ch6. 封装
  • Ch7. 复用

IntellJ Idea 快捷键

  • Shift+Cmd+A/Shift+Ctrl+A find action
  • Shift+Shift search
  • Alt+Enter fix 修复
  • F2 下个修复处
  • Cmd+1/Alt+1 project 项目栏
    • Esc 回到编辑器
  • Cmd+E/Ctrl+E 最近文件
  • Cmd+B/Ctrl+B def 定义
    • Cmd/Ctrl+Alt+Left 回去
  • Option+Cmd+B/Ctrl+Alt+B 实现
  • Alt+F7 usage 使用
  • Ctrl+Ctrl run anything 运行
  • Option+Up,Option+Down/Ctrl+W,Shift+Ctrl+W select 选区
  • ++cmd+/++/++ctrl+/++ comment 注释
    • 添加 Option/Shift 块注释
  • Shift+Cmd+Enter/Shift+Ctrl+Enter complete 补全语句
  • Option+Cmd+L/Ctrl+Alt+L reformat 格式化
  • Ctrl+T/Shift+Ctrl+Alt+T refactor 重构

Ch1. 基础概念

  • 抽象
  • 接口
  • 服务提供
  • 封装
    • Java 的三个访问权限关键字:public, private, protected
      • default (包访问):该权限下的资源可以被同一包(库组件)中其他类的成员访问。
  • 复用
  • 继承
    • "是一个"与"像是一个"的关系
  • 多态
    • 我们在处理类的层次结构时,通常把一个对象看成是它所属的基类,而不是把它当成具体类。通过这种方式,我们可以编写出不局限于特定类型的代码。
    • 这样的代码不会受添加的新类型影响,并且添加新类型是扩展面向对象程序以处理新情况的常用方法。
    • 面向对象语言使用后期绑定的概念。当向对象发送信息时,被调用的代码直到运行时才确定。
    • 在 Java 中,动态绑定是默认行为,不需要额外的关键字来实现多态性。
    • 把子类当成其基类来处理的过程叫做“向上转型”(upcasting)。
  • 单继承结构
    • 在 Java 中,所有的类都应该默认从一个基类 Object 继承。
    • 所有对象都具有一个公共接口,因此它们最终都属于同一个基类。
    • 单继承的结构使得垃圾收集器的实现更为容易。
  • 集合
    • 在 Java 中“集合”(Collection)的使用率比数组更高。(也可称之为“容器”,但“集合”这个称呼更通用。)
    • 集合的选择:
      • 集合可以提供不同类型的接口和外部行为。
      • 不同的集合对某些操作有不同的效率。
      • 例如,List 的两种基本类型:ArrayList 和 LinkedList。虽然两者具有相同接口和外部行为,但是在某些操作中它们的效率差别很大。
    • 参数化类型机制(Parameterized Type Mechanism)称之为“泛型”(Generic)
  • 生命周期
    • Java 使用动态内存分配。
    • 在堆内存(Heap)中动态地创建对象,直到程序运行我们才能确定需要创建的对象数量、生存时间和类型。什么时候需要,什么时候在堆内存中创建。 因为内存的占用是动态管理的,所以在运行时,在堆内存上开辟空间所需的时间可能比在栈内存上要长(但也不一定)。在栈内存开辟和释放空间通常是一条将栈指针向下移动和一条将栈指针向上移动的汇编指令。开辟堆内存空间的时间取决于内存机制的设计。
    • 合理假设:对象通常是复杂的,相比于对象创建的整体开销,寻找和释放内存空间的开销微不足道。
    • 在堆内存创建对象的话,编译器是不知道它的生命周期的。
    • Java 的内存管理是建立在垃圾收集器上的,它能自动发现对象不再被使用并释放内存。
  • 异常处理
    • 异常处理机制将程序错误直接交给编程语言甚至是操作系统。
    • “异常”(Exception)是一个从出错点“抛出”(thrown)后能被特定类型的异常处理程序捕获(catch)的一个对象。它不会干扰程序的正常运行,仅当程序出错的时候才被执行。

Ch2. 安装

Gradle 命令:

  • 编译并运行具体示例程序:gradlew <chapter>:<name>,如 gradlew objects:HelloDate

Java 编译系统:

  • 创建源代码文件 Hello.java
  • 编译源代码文件 javac Hello.java
  • 目录下生成 Hello.class
  • 运行程序 java Hello

Ch3. 对象

对象操作

  • 我们所操纵的标识符实际上只是对对象的“引用”

举例:我们可以用遥控器(引用)去操纵电视(对象)。只要拥有对象的“引用”,就可以操纵该“对象”。

数据存储

  • 寄存器
  • 栈内存
    • Java 系统必须知道栈内保存的所有项的生命周期。这种约束限制了程序的灵活性。
    • 在栈内存上存在一些 Java 数据(如对象引用),但Java 对象本身的数据却是保存在堆内存的。
  • 堆内存
    • 通用的内存池(也在 RAM 区域),所有 Java 对象都存在于其中。
    • 与栈内存不同,编译器不需要知道对象必须在堆内存上停留多长时间。因此,用堆内存保存数据更具灵活性。
    • 分配和清理堆内存要比栈内存需要更多的时间。
    • 随着时间的推移,Java 的堆内存分配机制现在已经非常快。
  • 常量存储
    • 直接放在程序代码中
  • 非 RAM 存储
    • 序列化对象:对象被转换为字节流,通常被发送到另一台机器。
    • 持久化对象:对象被放置在磁盘上,即使程序终止,数据依然存在。

基本类型

基本类型 大小 最小值 最大值 包装类型
boolean Boolean
char 16 bits Unicode 0 Unicode \(2^{16} -1\) Character
byte 8 bits -128 +127 Byte
short 16 bits \(- 2^{15}\) \(+ 2^{15} -1\) Short
int 32 bits \(- 2^{31}\) \(+ 2^{31} -1\) Integer
long 64 bits \(- 2^{63}\) \(+ 2^{63} -1\) Long
float 32 bits IEEE754 IEEE754 Float
double 64 bits IEEE754 IEEE754 Double
void Void
  • 基本类型有自己对应的包装类型:如果你希望在堆内存里表示基本类型的数据,就需要用到它们的包装类。
char c = 'x';
Character ch = new Character(c);
  • 自动装箱自动拆箱机制

  • 高精度(非基本类型): BigIntegerBigDecimal

数组

  • 数组使用前需要被初始化,并且不能访问数组长度以外的数据。
    • 以每个数组上少量的内存开销及运行时检查下标的额外时间为代价
    • 当我们创建对象数组时,实际上是创建了一个引用数组,并且每个引用的初始值都为 null
    • 如果我们尝试使用为 null 的引用,则会在运行时报错。

对象清理

作用域:

  • 作用域是由大括号 {} 的位置决定的。
  • 在 C/C++ 中将一个较大作用域的变量"隐藏"起来的做法,在 Java 中是不被允许的。

对象作用域:

  • 当我们使用 new 关键字来创建 Java 对象时,它的生命周期将会超出作用域。
  • Java 的垃圾收集器会检查所有 new 出来的对象并判断哪些不再可达,继而释放那些被占用的内存,供其他新的对象使用。

类的创建

类的描述:

class ATypeName {
 // 这里是类的内部
}

对象的创建:

ATypeName a = new ATypeName();

类中的内容:方法(method)和字段(field)

  • 字段:可以是基本类型,也可以是引用类型。

    • 基本类型初始化
    基本类型 初始值
    boolean false
    char \u0000 (null)
    byte (byte) 0
    short (short) 0
    int 0
    long 0L
    float 0.0f
    double 0.0d
    • 引用必须初始化
  • 方法:方法决定对象能接收哪些消息。方法的基本组成部分包括名称、参数、返回类型、方法体。

    [返回类型] [方法名](/*参数列表*/){
        // 方法体
    }
    
    • 方法只能作为类的一部分创建。它只能被对象所调用,并且该对象必须有权限来执行调用。
    [对象引用].[方法名](参数1, 参数2, 参数3);
    
    • 参数传递: 我们并没有直接处理对象,而是在传递对象引用

程序编写

  • 命名可见性
    • 为一个类库生成一个明确的名称,Java 创建者希望我们反向使用自己的网络域名,因为域名通常是唯一的。反转域名后,. 用来代表子目录的划分。
    • 整个包名都是小写的。
    • 所有文件都自动存在于自己的命名空间中,文件中的每个类都具有唯一标识符。这样,Java 语言可以防止名称冲突。
    • 空目录:例如在 com.mindviewinc.utility.foibles 这样的目录结构中,我们创建了 commindviewinc 空目录。它们存在的唯一目的就是用来表示这个反向的 URL。
  • 组件使用
    • import指示编译器导入一个包,也就是一个类库(在其他语言中,一个库不仅包含类,还可能包括函数和数据,但请记住 Java 中的所有代码都必须写在类里)。
    • 大多数时候,我们都在使用 Java 标准库中的组件。有了这些构件,你就不必写一长串的反转域名。如: import java.util.*;
    • 使用通配符 * 导入其中类
  • 静态
    • 当我们说某个事物是静态时,就意味着该字段或方法不依赖于任何特定的对象实例 。
    • 关键字: static
    • 情况:
      • 你只想为特定字段(注:也称为属性、域)分配一个共享存储空间,而不去考虑究竟要创建多少对象,甚至根本就不创建对象。
      • 创建一个与此类的任何对象无关的方法。也就是说,即使没有创建对象,也能调用该方法。
    • 即使你创建了两个对象,但是静态变量仍只占一份存储空间。
    • 引用:我们通过一个对象来定位它,例如 st2.i。我们也可以通过类名直接引用它,这种方式对于非静态成员则不可行。
    • static 关键字的这些特性对于应用程序入口点的 main() 方法尤为重要。
  • 习惯

    • 代码第一行都是注释行,包含:文件的路径信息(比如本章的目录名是 objects),后跟文件名
    • 有一些类库已经默认自动导入到每个文件里了。例如:java.lang 包。
    • 每个 java 源文件中允许有多个类。同时,源文件的名称必须要和其中一个类名相同,否则编译器将会报错。
    • 每个独立的程序应该包含一个 main() 方法作为程序运行的入口。其方法签名和返回类型如下。
    public static void main(String[] args) {
    }
    

编码风格

  • 类名:要求类名的首字母大写。 如果类名是由多个单词构成的,则每个单词的首字母都应大写(不采用下划线来分隔)。
  • 字段(成员变量)和对象引用名都采用驼峰命名的方式,但是它们的首字母不需要大写。
  • 大括号不换行。

Ch4. 运算符

  • Java没有sizeof:Java 本身就是一种“与平台无关”的语言。

Ch5. 控制流

Ch6. 初始化和清理

C++ 引入了构造器的概念,这是一个特殊的方法,每创建一个对象,这个方法就会被自动调用。Java 采用了构造器的概念,另外还使用了垃圾收集器(Garbage Collector, GC)去自动回收不再被使用的对象所占的资源。

对象的创建过程

构造器是静态方法

  1. 定位类对象:在首次创建、访问 Dog 类型对象或静态方法、属性时,Java 解释器在类路径中查找,定位 Dog.class 。
  2. 加载类对象:加载 Dog.class (创建 Class 对象)后,所有静态初始化的动作都执行完毕。静态初始化只在加载时执行一次。
  3. 分配空间:使用 new 创建对象时,在堆中分配空间,并清零,设置为默认值
  4. 初始化
  5. 构造器

初始化

注意初始化的顺序,不应当向前引用未初始化的数据。

构造器

  • Java 中使用了与 C++ 同样的方式:构造器名称与类名相同。
class Rock {
    Rock() { // 这是一个构造器
        System.out.print("Rock ");
    }
}

构造器需要注意的事项:

  • 默认构造器:没有指定的情况下编译器会自动创建

  • 构造器嵌套:通过 this 调用一次构造器,避免代码重复

Flower(String s, int petals) { 
 this(petals);
 //- this(s); // Can't call two! 
 this.s = s; // Another use of "this"
}

this 关键字

  • 只能在非静态方法内部使用
  • 一般用于在类的一个方法内调用其他方法、返回对象本身的引用

方法的重载

  • 根据方法名和参数列表对方法进行重载
  • 基本类型可以自动升级:char-byte-short-int-long-float-double
  • 但不可以自动降级,你必须显式强制转换

垃圾回收器

  • 仅回收new 创建的对象的内存
  • 对象不一定被回收
    • 如果 JVM 并未耗尽内存,它很可能不执行垃圾回收
  • 不等同于析构
  • 垃圾回收只处理内存相关,不管其他事情

垃圾回收器的工作模式:

  • 引用计数:当某对象引用计数为0时释放空间
    • 问题:两个对象循环引用
  • 引用链条:

==todo==

finalize() 方法:

  • 调用时间:当垃圾收集器准备回收内存时调用,在下一轮回收动作时回收内存

  • 用途:在对象被回收前,检查对象是否符合可以被回收的条件,及时抛出异常

@Override
protected void finalize() throws Throwable { 
    if (checkedOut) { 
        System.out.println("Error: checked out");
} 
// Normally, you'll also do this: 
// super.finalize();     // Call the base-class version 

代码注释:

  • @ 是注解,告诉编译器这不是偶然地重定义 finalize() 方法。这个方法存在于基类中。
  • System.gc() 强制进行垃圾回收动作。

==todo==

Ch7. 封装

本章学习:将类库组件置于同一个包下

约定

修改类库中的一个类时,不会移除已有的方法,保证客户端的代码不受破坏。

访问控制权限(Access Control)

依次是:public, protected, (包访问权限Package Access)没有关键字, private

包内含一组类,放置在一个命名空间下。

例:java.util 中含有一个类 ArrayList

  • 访问

一般情况下访问某个类需要输入全名: java.util.ArrayList list = new java.util.ArrayList()

使用 import 关键字导入某个类,或使用 .* 导入包中所有的类。

  • 默认包(未命名,default package)
  • 编译单元(一个 Java 源代码文件)

每个编译单元中只能有一个 public 类,它的名字必须与文件名相同,否则编译器不接受。包外无法访问其他非 public 类。

代码组织

编译一个 .java 文件会得到一组 .class 文件,文件名分别和每个类的类名相同。

Java 中,可运行程序是一组 .class 文件,可以打包压缩成 .jar 文档文件。Java 解释器负责查找、加载和解释这些文件。

package 语句必须是文件中的第一行代码。

package hiding;
  • 命名习惯:包名一律小写,即使中间的单词也要小写。

Ch8. 复用

本章学习通过创建已有类的对象(组合)、创建已有类的子类(继承)两种方式达到复用的目的。

组合语法

初始化类内的引用有四种方法:

  1. 定义:在调用构造函数前初始化
  2. 构造函数
  3. 使用前:在创建对象开销大、 不需要每次创建对象时,可以减少开销
  4. 用实例初始化(Instance Initialization Block, IIB)
  5. They are typically placed above the constructors within braces.
  6. Initialization blocks are executed whenever the class is initialized and before constructors are invoked.

继承语法

Java中的类总是继承的,要么显示地继承某个类,要么隐式地继承标准根对象 Object

继承的方式是 extends + 基类名称。

public class Detergent extends Cleanser{}
  • 类的测试

可以在每个类中都放置 main(),唯一运行的只有命令行调用的 main()main() 一般是 public static 的。因此,即使不是公共类,即使类只有包访问权限,也可以访问 main()

调用 main(args) 可以传递相同的命令行参数。