Java 核心技术卷 1 笔记

第 2 章 Java 程序设计环境

2.1 安装 Java 开发工具包

JDK:包含虚拟机和编译器

JRE:只包含虚拟机

第 3 章 Java 的基本程序设计结构

3.3 数据类型

Java 是一种强类型语言。这就意味着必须为每一个变量声明一种类型。

float 类型的数值有一个后缀 F 或 f。没有后缀的浮点数值默认为 double 类型。

强烈建议不要再程序中使用 char 类型,除非确实需要处理 UTF-16 代码单元。最好将字符串作为抽象数据类型处理。

3.4 变量

在 Java 中,变量的声明尽可能地靠近变量第一次使用的地方,这是一种良好的程序编写风格。

在 Java 中,利用关键字 final 指示常量。

3.5 运算符

当参与 / 运算的两个操作数都是整数时,表示整数除法;否则表示浮点除法。

当整型数值转换为 float 类型时,将会得到同样大小的结果,但却失去了一定的精度。

四舍五入使用 Math.round()

3.6 字符串

api 后面为 python 对应语法

String
  • char charAt(int index) s[index]
    返回给指定位置的代码单元。除非对底层的代码单元感兴趣,否则不需要调用这个方法。

  • int compareTo(String other) s1 > s2 or s1 == s2 or s1 < s2
    按照字典顺序,如果字符串位于 other 之前,返回一个负数;入伙字符串位于 other 之后,返回一个正数;如果两个字符串相等,返回 0。

  • boolean equals(Object other) s1 == s2
    如果字符串与 other 相等,返回 true。

  • boolean equalsIgnoreCase(String other) s1.lower() == s2.lower()
    如果字符串与 other 相等(忽略大小写),返回 ture。

  • boolean startsWith(String prefix) s1.startswith("app")

  • boolean endsWith(String suffix) s1.endswith("ple")
    如果字符串以 suffix 开头或结尾,则返回 ture。

  • int indexOf(String str)

  • int indexOf(String str, int fromIndex)

    返回与字符串 str 或代码点 cp 匹配的第一个子串的开始位置。这个位置从索引 0 或 fromIndex 开始计算。如果在原始串中不存在 str,返回 - 1。

  • int lastIndexOf(String str)

  • int lastIndexOf(String str, int fromIndex)

    返回与字符串 str 或代码点 cp 匹配的最后一个子串的开始位置。这个位置从原始串尾端或 fromIndex 开始计算。

  • int length() len(s)
    返回字符串的长度。

  • String replace(CharSequence oldString, CharSquence newString) s.replace(old_str, new_str)
    返回一个新字符串。这个字符串用 newString 代替原始字符串中所有的 oldString。可以用 String 或 StringBuilder 对象作为 CharSequence 参数。

  • String substring(int beginIndex) s[begin:]

  • String substring(int beginIndex, int endIndex) s[begin:index]
    返回一个新字符串。这个字符串包含原始字符串中从 beginIndex 到串尾或 endIndex -1 的所有代码单元。

  • String toLowerCase() s.lower()

  • String toUpperCase() s.upper()
    返回一个新字符串。这个字符串将原始字符串中的大写字母改为小写,或者将原始字符串中的所有小写字母改成了大写字母。

  • String trim() s.split()
    返回一个新字符串。这个字符串将删除了原始字符串头部和尾部的空格。

  • String join(CharSequence delimiter, CharSequence… elements) 8 ''.join(list)

    返回一个新字符串,用给定的定界符连接所有元素。

StringBuilder

在这里插入图片描述

3.9 大数值

BigInteger 类实现了任意精度的整数运算,BigDecimal 实现了任意精度的浮点数运算。

3.10 数组

创建一个数字数组时,所有元素都初始化为 0;boolean 数组元素初始化为 false;对象数组元素初始化为 null,表示这些元素(还)未存放任何对象。

数组初始化 int[] a = {1, 2, 3};

Arrays.toString(a) 打印数组的值,Arrays.deepToString() 打印二维数组的值

如果希望将一个数组的所有值拷贝到一个新的数组中去,就要使用 Arrays 类的 copyOf 方法 int[] copiedArray = Arrays.copyOf(array, arrays.length)

想要对数值型数组进行排序,可以使用 Arrays.sort(a)

Arrays

在这里插入图片描述

第 4 章 对象与类

4.1 面向对象程序设计概述

对于一些规模较小的问题,将其分解为过程的开发方式比较理想。

面向对象的设计风格更易于程序员掌握,也更容易找到 bug

封装不过是将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式。

应该尽可能地将相互依赖的类减到最少。

4.2 使用预定义类

一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。

在 Java 中,任何对象变量的值都是对存储在另外一个地方的一个对象的引用。new 操作符的返回值也是一个引用。

在 Java 中,必须使用 clone 方法获得对象的完整拷贝。

LocalDate

在这里插入图片描述

4.3 用户自定义类

类通常包括类型属于某个类类型的实例域(类的实例域包含对象:String、其他类等)

如果需要返回一个可变对象的引用,应该首先对它进行克隆(clone)

只要方法是私有的,类的设计者就可以确信:它不会被外部的其他类操作调用,可以将其删去。如果方
法是公有的,就不能将其删去,因为其他的代码很可能依赖它。

4.4 静态域与静态方法

在下面两种情况下使用静态方法:

  • 一个方法不需要访问对象状态,其所需参数都是通过显式参数提供(例如 : Math.pow)
  • 一个方法只需要访问类的静态域(例如 :Employee.getNextldh)

每一个类可以有一个 main 方法,这是一个常用于对类进行单元测试的技巧。(类似于 python init 方法)

4.5 方法参数

按值调用 (call by value) 表示方法接收的是调用者提供的值。而按引用调用( call by reference)表示方法接收的是调用者提供的变量地址。

Java 程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,特别是,方法不能修改传递给它的任何参数变量的内容。

Java 中方法参数的使用情况:

  • 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)
  • 一个方法可以改变一个对象参数的状态(Employee raiseSalary())
  • 一个方法不能让对象参数引用一个新的对象(因为传到方法内会拷贝一份引用,不会影响引用指向的对象)

4.6 对象构造

在构造器中应该显示地对域进行初始化,提高程序的可读性。

如果类中提供了至少一个构造器,但是没有提供无参数的构造器,则在构造对象时如果没有提供参数就会被视为不合法。

在执行构造器之前,先执行赋值操作。当一个类的所有构造器都希望把相同的值赋予某个特定的实例域时,这种方式特别有用。

如果构造器的第一个语句形如 this(…),这个构造器将调用同一个类的另一个构造器。采用这种方式使用 this 关键字非常有用,这样对公共的构造器代码部分只编写一次即可。

public Employee(double s) {
    // calls Employee(String, double)
    this("Employee #" + nextId, s);
    nextId++;
}

在析构器中,最常见的操作是回收分配给对象的存储空间。由于 Java 有自动的垃圾回收器,不需要人工回收内存,所以 Java 不支持析构器。

4.7 包

使用包的主要原因是确保类名的唯一性。

一个类可以使用所属包中的所有类,以及其他包中的共有类(public class)

4.10 类设计技巧

  1. 一定要保证数据私有(封装性)
  2. 一定要对数据初始化(可读性)
  3. 不要在类中使用过多的基本类型(用其他的类代替多个相关的基本类型的使用)
  4. 不是所有的域都需要独立的域访问器和域更改器(getter 和 setter 方法)
  5. 将职责过多的类进行分解
  6. 类名和方法名要能够体现它们的职责
  7. 优先使用不可变的类

更改对象的问题在于,如果多个线程试图同时更新一个对象,就会发生并发更改。其结果是不可预料的。如果类是不可变的,就可以安全地在多个线程间共享其对象。

第 5 章 继承

利用继承,人们可以基于已存在的类构造一个新类。继承已存在的类就是复用(继承)这些类的方法和域。在此基础上,还可以添加一些新的方法和域,以满足新的需求。这是 Java 程序设计中的一项核心技术。

5.1 类、超类和子类

在设计类的时候,应该将通用的方法放在超类中,而将具有特殊用途的方法放在子类中。

子类不能直接访问父类的私有域,可以使用 super.method() 获取父类的私有属性。

关键字 this 有两个用途:一是引用隐式参数,二是调用该类其他的构造器,同样,super 关键字也有两个用途:一是调用超类的方法,二是调用超类的构造器。

public double getSalary() {
    return super.getSalary() + bonus;
}

super(args) // 子类构造方法中先实现父类构造方法再丰富自己的方法
this.bonus = 0;

一个对象变量(例如,变量 e ) 可以指示多种实际类型的现象被称为多态( polymorphism)。在运行时能够自动地选择调用哪个方法的现象称为动态绑定(dynamic binding)

“ is-a” 规则的另一种表述法是置换法则。它表明程序中出现超类对象的任何地方都可以用子类对象置换。

Employee e;

e = new Employee(…);

e = new Manager(…);

在 Java 程序设计语言中,对象变量是多态的。一个 Employee 变量既可以引用一个Employee 类对象,也可以引用一个 Employee 类的任何一个子类的对象。

所有数组都要牢记创建它们的元素类型,并负责监督仅将类型兼容的引用存储到数组中。

Manager[] managers = new Manager[10];
Employee[] staff = managers;
staff[0] = new Employee(...); // 有问题,staff[0] 没有子类特有的属性和方法
理解方法调用

假设要调用 x.f(args),隐式参数 x 声明为类 C 的一个对象

  1. 编译器查看对象的声明类型和方法名

编译器将会一一列举所有 C 类中名为 f 的方法其父类中访问属性为 public 且名为 f 的方法

  1. 编译器将查看调用方法时提供的参数类型(重载解析)
  2. private 方法、static 方法、final 方法或构造器时,编译器可以知道调用哪个方法,称为静态绑定,调用的方法依赖于隐式参数的实际类型并在运行时实现动态绑定
  3. 动态绑定时,虚拟机调用与 x 所引用对象的实际类型最合适的那个类的方法。

e.getSalary() 实现过程

1)虚拟机提取 e 的实际类型的方法表。可能是 Employee、Manager 或 Employee 其他子类的方法表

2)虚拟机搜索定义 getSalary 签名的类。此时虚拟机知道应该调用哪个方法

3)虚拟机调用方法

动态绑定有一个非常重要的特性:无需对现存的代码进行修改,就可以对程序进行扩展。(增加新的类可以自动调用新的类的对象的方法)

在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。

阻止继承:final 类和方法

将方法或类声明为 final 主要目的是:确保他们不会再子类中改变语义。

如果一个方法没有被覆盖并且很短,编译器就能对它进行优化处理,这个过程称为内联(inlining)

(父类转换为子类)进行类型转换之前,先查看一下是否能够成功地转换。这个过程使用 instanceof 操作符可以实现

if(staff[1] instanceof Manager) {
    boss = (Manager) staff[1];
}

在一般情况下,应该尽量少用类型转换和 instanceof 运算符

抽象类

除了抽象方法之外,抽象类还可以包含具体数据和具体方法。

建议尽量将通用的域和方法(不管是否是抽象的)放在超类(不管是否是抽象类)中。

类即使不含抽象方法,也可以将类声明为抽象类。抽象类不能被实例化。

需要注意,可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象。

Person p = new Student("Solejay");

5.2 Object:所有类的超类

可以使用 Object 类型的变量引用任何类型的对象

Object obj = new Student("Solejay");

在 Java 中,只有基本类型不是对象,例如数值、字符和布尔类型的值都不是对象。

equals 方法

Object 类中的 equals 方法用于检测一个对象是否等于另一个对象。

下面给出编写一个完美的 equals 方法的建议:

  1. 显式参数命名为 otherObject,稍后需要将它转换成另一个叫做 other 的变量。
  2. 检测 this 与 otherObject 是否引用同一个对象
  3. 检测 otherObject 是否为 null,如果为 null,返回 false
  4. 比较 this 与 otherObject 是否属于同一个类。
  5. 将 otherObject 转换为相应的类类型变量
  6. 使用 == 比较基本域,使用 equals 比较对象域。都匹配返回 true,否则返回 false
public boolean equals(Object otherObject) {
    if(this == otherObject)     return true;
    if(otherObject == null)     return false;
    if(getClass() != otherObject.getClass())    return false;

    Employee other = (Employee)otherObject;

    return Objects.equals(this.getName(), other.getName()) && salary == other.salary && Objects.equals(hireDay, other.hireDay);
}
hashCode 方法

字符串的散列码是由内容导出的。

最好使用 null 安全的方法 Objects.hashCode

在这里插入图片描述

toString 方法

在调用 x.toString() 的地方可以用 ""+x 替代。

强烈建议为自定义的每一个类增加 toString 方法。这样做不仅自己受益,而且所有使用这个类的程序员也会从这个日志记录中受益匪浅。

public String toString() {
    return getClass().getName() + "[name=" + super.getName() + ", salary=" + salary + ", hireDay=" + hireDay + "]";
}

在这里插入图片描述

5.3 泛型数组列表

ArrayList 是一个采用类型参数的泛型类

ArrayList<Employee> staff = new ArrayList<>(100); // Employee 就是类型参数,初始容量 100
ArrayList<String> sites = new ArrayList<String>();
sites.add("Google"); // add(e) 添加元素
sites.add("Runoob");
sites.get(1); // get(index) 访问元素
sites.set(1, "Wiki"); // set(index, e) 修改元素
sites.remove(3); // remove(index) 删除元素
sites.remove("Google"); // remove(e) 删除元素
sites.size(); // 计算大小
5.4 对象包装器与自动装箱

有时,需要将 int 这样的基本类型转换为对象。所有的基本类型都有一个与之对应的类。

自动装箱(autoboxing)和自动拆箱

ArrayList<Integer> list = new ArrayList<>();
list.add(3); // 自动变成 list.add(Integer.valueOf(3));
int n = list.get(0); // 自动变成 list.get(i).intValue();

基本类型的引用类型进行比较时使用 equals 而不是 ==

由于包装器类引用可以为 null,所以自动装箱有可能会抛出一个 NullPointerException 异常

Integer n = null;
System.out.println(2 * n);

装箱和拆箱是编译器认可的,而不是虚拟机。

Integer
在这里插入图片描述

5.6 枚举类

在比较两个枚举类型的值时,永远不需要调用 equals,而直接使用 == 就可以了。

5.7 反射

能够分析类能力的程序称为反射(reflective),反射机制可以用来:

  • 在运行时分析类的能力
  • 在运行时查看对象,例如,编写一个 toString 方法供所有类使用
  • 实现通用的数组操作代码
  • 利用 Method 对象,这个对象很像 C++ 中的函数指针
Class 类

如同用一个 Employee 对象表示一个特定的雇员属性一样,一个 Class 对象将表示一个特定类的属性。

获得 Class 类对象的三种方法

Random generator = new Random();
Class cl = generator.getClass();

String className = "java.util.Random";
Class cl = cl.forName(className);

Class cl = Random.class;
Class cl = int.class;

一个 Class 对象实际上表示的是一个类型,而这个类型未必一定是一种类。

可以利用 == 运算符实现两个类对象比较的操作 if(e.getClass() == Employee.class)

将 forName 与 newInstance 配合起来使用,可以根据存储在字符串中的类名创建一个对象。

String s = "java.util.Random";
Object m = Class.forName(s).newInstance();
捕获异常

抛出异常比终止程序要灵活的多,这是因为可以提供一个“捕获”异常的处理器(handler)对异常情况进行处理。

异常有两种类型:未检查异常和已检查异常。

利用反射分析类的能力

java.lang.reflect 包中三个类 FieldMethodConstructor 描述类的域(属性)、方法和构造器(构造函数),可以使用 Modifier 类分析 getModifiers 方法返回的整型数值得到各个描述符

package com.test;

import java.lang.reflect.*;
import java.util.*;

/**
 * 这个程序使用反射来打印类的所有特性
 */
public class ReflectionTest {
    public static void main(String[] args) {
        // 从命令行参数或用户输入读取类名
        String name;
        if (args.length > 0) name = args[0];
        else {
            Scanner in  = new Scanner(System.in);
            System.out.println("Enter a class name (e.g. java.util.Date): ");
            name = in.next();
        }

        try{
            //打印类名和父类名(if != Object)
            Class cl = Class.forName(name);
            Class supercl = cl.getSuperclass();
            String modifiers = Modifier.toString(cl.getModifiers()); // getModifiers() 描述修饰符情况;Modifier 类分析返回的整型数值
            if (modifiers.length() > 0) System.out.print(modifiers + " "); // public final
            System.out.print("class " + name); // 类名
            if (supercl != null && supercl != Object.class ){
                System.out.print(" extends " + supercl.getName()); // 父类名
            }
            System.out.print("\n{\n");
            printConstructors(cl); // 打印构造函数
            System.out.println();
            printMethods(cl); // 打印方法
            System.out.println();
            printFields(cl); // 打印属性(域)
            System.out.println("}");

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.exit(0);
    }

    /**
     * 打印类的所有构造函数
     * @param cl
     */
    // public final java.lang.Double(double);
    public static void printConstructors(Class cl) {
        Constructor[] constructors = cl.getConstructors(); // 返回构造器数组
        for (Constructor c : constructors){
            String name = c.getName();
            System.out.print("  ");
            String modifiers = Modifier.toString(cl.getModifiers());//修饰符字符串
            if (modifiers.length() > 0){
                System.out.print(modifiers + " "); // public
            }
            System.out.print(name + "(");

            //打印参数类型
            Class[] parameterType = c.getParameterTypes(); // 显式参数(形参)数组
            for (int j = 0; j < parameterType.length; j++) {
                if (j > 0) System.out.print(", ");
                System.out.print(parameterType[j].getName());
            }
            System.out.println(");");
        }
    }

    /**
     * 打印一个类的所有方法
     * @param cl
     */
    // public final double sum(double, double);
    public static void printMethods(Class cl) {
        Method[] methods = cl.getDeclaredMethods(); // 返回类中声明的全部方法
        for (Method m : methods){
            Class returnType = m.getReturnType(); // 方法返回类型
            String name = m.getName();
            System.out.print("  ");
            //打印修饰符,返回类型,以及方法名
            String modifiers = Modifier.toString(cl.getModifiers());
            if (modifiers.length() > 0) System.out.print(modifiers + " ");
            System.out.print(returnType.getName() + " " + name + "(");
            //打印参数类型
            Class[] parameterTypes = m.getParameterTypes();
            for (int j = 0; j < parameterTypes.length; j++) {
                if (j > 0) System.out.print(", ");
                System.out.print(parameterTypes[j].getName());
            }
            System.out.println(");");
        }
    }

    /**
     * 打印类的所有字段
     * @param cl
     */
    // public static final double MAX_VALUE;
    public static void printFields(Class cl) {
        Field[] declaredFields = cl.getDeclaredFields();
        for (Field f : declaredFields){
            Class type = f.getType();
            String name = f.getName();
            System.out.print("  ");
            //打印 修饰符 类型名 变量名
            String modifiers = Modifier.toString(f.getModifiers());
            if (modifiers.length() > 0) System.out.print(modifiers + " ");
            System.out.println(type.getName() + " " + name + ";");
        }
    }

}

Class

在这里插入图片描述

在这里插入图片描述

5.8 继承的设计技巧

  1. 将公共操作和域放在超类
  2. 不要使用受保护(protected)的域
  3. 使用继承关系实现“is-a”关系(不是“is-a”的也不要强制继承)
  4. 除非所有继承的方法都有意义,否则不要使用继承
  5. 在覆盖方法时,不要改变预期的行为
  6. 使用多态,而非类型信息
// action1 和 action2 是相同概念的话,为这个概念定义一个父类,将该方法放置在两个类的父类或接口中
// 调用 x.action()
if(x is of type 1)
	action1(x);
else if(x is of type 2)
	action2(x);

使用多态方法或接口编写的代码比使用对多种类型进行检测的代码更加易于维护和扩展。

  1. 不要过多地使用反射

第 6 章 接口、lambda 表达式与内部类

6.1 接口

在 Java 程序设计语言中,接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。

接口中的所有方法自动地属于 public。因此,在接口中声明方法时,不必提供关键字 public。

接口中绝不能含有实例域,提供实例域和方法实现的任务应该由实现接口的那个类来完成。

类实现接口的步骤:

  1. 将类声明为实现给定的接口 class Employee implements Comparable
  2. 对接口中的所有方法进行定义

接口中,不写修饰符,默认为 public,其余类中方法不加修饰符默认为 default,可在同包类及同包子类中使用。在实现接口时必须把方法声明为 public。

为什么不在类中直接实现方法而是实现接口呢?主要原因在于 Java 是强类型(strongly typed)语言。在调用方法的时候,编译器会检查这个方法是否存在。实现接口必然提供了方法定义。

接口的特性
x = new Comparable(...); // 错误 接口不是类,不能用 new 运算符实例化
Comparable x; //正确 可以用接口声明变量
x = new Employee(...); // 接口变量必须引用实现了接口的类对象

double SPEED_LIMIT = 95; // 虽然在接口中不能包含实例域或静态方法,但却可以包含常量

尽管每个类只能拥有一个超类(父类),但却可以实现多个接口。

class Employee implements Cloneable, Comparable
接口与抽象类

使用抽象类表示通用属性存在一个问题:每个类只能扩展一个类;但是每个类可以实现多个接口

C++ 允许一个类有多个超类,称为多重继承,但是会让语言本身变得非常复杂。

接口可以提供多重继承的大多数好处,同时还能避免多重继承的复杂性和低效性。

静态方法和默认方法

在 Java SE 8 中,允许在接口中增加静态方法。

可以为接口方法提供一个默认实现,必须用 default 修饰符标记这样一个方法。

public interface Comparable<T> {
    default int compareTo(T other) {return 0;}
}

默认方法的一个重要用法是“接口演化”,解决增加的接口方法和之前的实现类之间的矛盾。

解决默认方法冲突原则:

  1. 超类优先(实现类和接口)
  2. 子接口优先(接口和接口)
  3. 类优先(子类继承父类且实现接口,父类和接口冲突时)

6.2 接口示例

Comparator 接口

自定义比较方法时可以实现 Comparator 接口的 compare 方法进行比较

public class LengthComparator implements Comparator<String> {
    @Override
    public int compare(String s1, String s2) { // 按照字符串长度比较
        return s1.length() - s2.length();
    }
}

String[] friends = {"Peter", "Paul", "Mary"};
Arrays.sort(friends, new LengthComparator()); //传入实现接口的对象进行比较
对象克隆

浅拷贝可能会发生问题。

如果原对象和浅克隆对象共享的子对象(非基本数据类型) 是不可变的,那么这种共享就是安全的。

Cloneable 接口是标记接口。标记接口不包含任何方法,它唯一的作用就是允许在类型中查询使用 instanceof

if(obj instanceof Cloneable)

即使 clone 的默认(浅拷贝)实现能够满足要求,还是需要实现 Cloneable 接口,将 clone 重新定义为 public,再调用 super.clone()

因为如果不继承自 Cloneable 接口,当调用 clone () 时会抛出 CloneNotSupportedException 异常

clone() 方法属于 Object,修饰符为 protected,但是因为 Object 在 java.lang 包,不在同一个包下的子类也无法调用 clone(),所以需要声明为 public

可以建立深拷贝来克隆对象中可变的实例域。

当心子类的克隆。子类可能添加可变的实例域,这样又会变成浅拷贝(除非修正 clone 方法)

所有数组类型都有一个 public 的 clone 方法,而不是 protected。可用这个方法建立一个新数组,包含原数组所有元素的副本。

int[] a = {2, 3, 4, 5};
int[] cloned = a.clone();

6.3 lambda 表达式

lambda 表达式是一个可传递的代码块,可以在以后执行一次或多次。将代码块传递到某个对象,这个代码块会在将来某个时间调用。

lambda 表达式的语法
// 参数,箭头(->)以及一个表达式
(variable...) -> {statement}
函数式接口

对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个 lambda 表达式。这种接口称为函数式接口(functional interface)

// Comparator 只有一个 compare 方法的接口
Arrays.sort(words, (first, second) -> first.length() - second.length());
方法引用

已经有现成的方法可以完成想要传递到其他代码的某个动作。

Timer t = new Timer(1000, event -> System.out.println(event));
Timer t = new Timer(1000, System.out::println);

用 :: 操作符分隔方法名与对象或类名的三种情况:

  • object::instanceMethod System.out::println 等价于 x -> System.out.println(x)
  • Class::staticMethod Math::pow 等价于 (x, y) -> Math.pow(x, y)
  • Class::instanceMethod String::compareToIgnoreCase 等价于 (x, y) -> x.compareToIgnoreCase(y)

第 7 章 异常、断言和日志

7.1 处理错误

异常处理的任务就是将控制权从错误产生的地方转移给能够处理这种情况的错误处理器。

  • 为什么要有异常处理?

并不是在任何情况下异常都能够返回一个错误码。有可能无法明确将有效数据与无效数据加以区分;一个返回整形的方法就不能简单地通过返回 -1 表示错误

异常分类

在 Java 程序设计语言中,异常对象都是派生于 Throwable 类的一个实例。

在这里插入图片描述

Error 类层次结构描述了 Java 运行时系统的内部错误和资源耗尽错误

Exception 层次分解为 RuntimeException其他异常

由程序错误导致的异常属于 RuntimeException;程序本身没有问题,但由于像 I/O 错误这类问题导致的异常属于其他异常

  • 派生于 RuntimeException 异常情况

错误的类型转换

数组访问越界

访问 null 指针

  • 不是派生于 RuntimeException 异常

试图在文件尾部后面读取数据

试图打开一个不存在的文件

试图根据给定的字符串查找 Class 对象,而这个字符串表示的类并不存在

“如果出现 RuntimeException 异常,那么就一定是你的问题”

派生于 Error 类或 RuntimeException 类的所有异常称为 非受查(unchecked)异常,所有其他的异常称为 受查(checked)异常

声明受查异常

如果遇到无法处理的情况, Java 的方法可以抛出一个异常

方法应该在其首部声明所有可能抛出的异常。这样可以从首部反映出这个方法可能抛出哪类受查异常。

public FileInputStream(String name) throws FileNotFoundException

应该抛出异常的四种情况

  • 调用一个抛出 受查异常 的方法,例如,FileInputStream 构造器
  • 程序运行过程中发现错误,并且利用 throw 语句抛出一个受查异常
  • 程序出现错误,例如,a[–1]=0 会抛出一个 ArrayIndexOutOfBoundsException 这样的非受查异常
  • Java虚拟机和运行时库出现的内部错误

一个方法必须声明所有可能抛出的 受查异常,而非受查异常要么不可控制(Error),要么就应该避免发生(RuntimeException)。

如果方法没有声明所有可能发生的受查异常,编译器就会发出一个错误信息。

7.2 捕获异常

如果某个异常发生的时候没有在任何地方进行捕获,那程序就会终止执行,并在控制台上打印出异常信息,其中包括异常的类型和堆栈的内容。

如果调用了一个抛出受查异常的方法,就必须对它 进行处理,或者 继续传递。通常,应该捕获那些知道如何处理的异常,而将那些不知道怎样处理的异常继续进行传递。如果想传递一个异常,就必须在方法的首部添加一个throws说明符,以便告知调用者这个方法可能会抛出异常。

finally 子句

不管是否有异常被捕获,finally子句中的代码都被执行

当发生异常时,恰当地关闭所有数据库的连接是非常重要的

7.3 使用异常机制的技巧

  • 异常处理不能代替简单的测试
  • 不要过分地细化异常
  • 利用异常层次结构
  • 不要压制异常
  • 在检测错误时,“ 苛刻” 要比放任更好
  • 不要羞于传递异常

7.4 使用断言

断言机制允许在测试期间向代码中插入一些检查语句。当代码发布时,这些插入的检测语句将会被自动地移走。

断言的两种形式

  • assert 条件; assert x >= 0
  • assert 条件 : 表达式; assert x >=0 : x

第 9 章 集合

9.1 Java集合框架

可以使用接口类型存放集合的引用。利用这种方式,一旦改变了想法,可以轻松地使用另外一种不同的实现。 只需要对程序的一个地方做出修改,即调用构造器的地方。

Queue<Customer> expressLane = new CircularArrayQueue<>();
Queue<Customer> expressLane = new LinkedListQueue<>();
Collection 接口

在 Java 类库中,集合类的基本接口是 Collection 接口

迭代器

Iterator 接口的 4 个方法

public interface Iterator<E> {
    E next();
    boolean hasNext();
    void remove();
    default void forEachRemaining(Consumer<? supre E> action);
}

编译器简单地将“ for each” 循环翻译为带有迭代器的循环。“ for each” 循环可以与任何实现了Iterable接口的对象一起工作,这个接口只包含一个抽象方法:Iterator<E> iterator();

查找操作与位置变更是紧密相连的。 查找一个元素的唯一方法是调用 next,而在执行查找操作的同时,迭代器的位置随之向前移动。

对 next 方法和 remove 方法的调用具有互相依赖性。 如果调用 remove 之前没有调用next将是不合法的。

集合框架中的接口

在这里插入图片描述

9.2 具体的集合

在这里插入图片描述

链表

绝对不应该使用这种让人误解的随机访问方法来遍历链表。每次查找一个元素都要从列表的头部重新开始搜索。 LinkedList对象根本不做任何缓存位置信息的操作。

for(int i = 0; i < list.size(); i++)

使用链表的唯一理由是尽可能地减少在列表中间插入或删除元素所付出的代价。如果列表只有少数几个元素,就完全可以使用 ArrayList。

我们建议避免使用以整数索引表示链表中位置的所有方法。如果需要对集合进行随机访问,就使用数组 ArrayList,而不要使用链表。

数组列表

建议在不需要同步时使用 ArrayList,而不要使用 Vector。

散列集

如果不在意元素的顺序,可以有几种能够快速查找元素的数据结构。其缺点是无法控制元素出现的次序。

在 Java 中,散列表用链表数组实现

树集

正如 TreeSet 类名所示,排序是用树结构完成的(当前实现使用的是红黑树(red-black tree))

优先级队列

优先级队列使用了一个优雅且高效的数据结构,称为堆(heap)。

使用优先级队列的典型示例是任务调度。每一个任务有一个优先级,任务以随机顺序添加到队列中。每当启动一个新的任务时,都将优先级最高的任务从队列中删除。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值