Stay hungry. Stay foolish.

0%

给我来一杯JAVA不加糖

  记录廖雪峰老师Java教程的一些注意点。

Java快速入门

  • 引用型:指向,相等判断
  • JAVA12以后的新特性:switch语句
  • for each循环

面向对象编程

面向对象基础

  • 基本类型与引用类型参数绑定的区别
  • 如果父类没有默认的构造方法,子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法
  • 向上转型与向下转型:一个引用类型为Person的变量,能否指向Student类型的实例?
    instanceof操作符,可以先判断一个实例究竟是不是某种类型,举个例子:
1
2
3
4
5
Object obj = "hello";
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.toUpperCase());
}
  • Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。这个非常重要的特性在面向对象编程中称之为多态。举例:
Polymorphic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class Main {
public static void main(String[] args) {
// 给一个有普通收入、工资收入和享受国务院特殊津贴的小伙伴算税:
Income[] incomes = new Income[] {
new Income(3000),
new Salary(7500),
new StateCouncilSpecialAllowance(15000)
};
System.out.println(totalTax(incomes));
}

public static double totalTax(Income... incomes) {
double total = 0;
for (Income income: incomes) {
total = total + income.getTax();
}
return total;
}
}

class Income {
protected double income;

public Income(double income) {
this.income = income;
}

public double getTax() {
return income * 0.1; // 税率10%
}
}

class Salary extends Income {
public Salary(double income) {
super(income);
}

@Override
public double getTax() {
if (income <= 5000) {
return 0;
}
return (income - 5000) * 0.2;
}
}

class StateCouncilSpecialAllowance extends Income {
public StateCouncilSpecialAllowance(double income) {
super(income);
}

@Override
public double getTax() {
return 0;
}
}
  • 无法实例化的抽象类有什么用?因为抽象类本身被设计成只能用于被继承,因此,抽象类可以强迫子类实现其定义的抽象方法,否则编译会报错。因此,抽象方法实际上相当于定义了“规范”。(抽象类可以有非抽象方法)
  • 所谓interface,就是比抽象类还要抽象的纯抽象接口,因为它连字段都不能有,当一个具体的class去实现一个interface时,需要使用implements关键字。
  • 在Java中,一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个interface

抽象类和接口的对比如下:

abstract class interface
继承 只能extends一个class 可以implements多个interface
字段 可以定义实例字段 不能定义实例字段
抽象方法 可以定义抽象方法 可以定义抽象方法
非抽象方法 可以定义非抽象方法 可以定义default方法
  • default方法和抽象类的普通方法是有所不同的。因为interface没有字段,default方法无法访问字段,而抽象类的普通方法可以访问实例字段
  • 实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都会共享该字段
  • 因为静态方法属于class而不属于实例,因此,静态方法内部,无法访问this变量,也无法访问实例字段,它只能访问静态字段
  • interface是可以有静态字段的,并且静态字段必须为final类型
  • 如果小军写了一个Arrays类,恰好JDK也自带了一个Arrays类,如何解决类名冲突?在Java中,我们使用package来解决名字冲突
  • 为了避免名字冲突,我们需要确定唯一的包名。推荐的做法是使用倒置的域名来确保唯一性
  • 一个.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同
  • 从Java 9开始引入的模块,主要是为了解决“依赖”这个问题。如果a.jar必须依赖另一个b.jar才能运行,那我们应该给a.jar加点说明啥的,让程序在编译和运行的时候能自动定位到b.jar,这种自带“依赖关系”的class容器就是模块
  • 使用模块可以按需打包JRE;使用模块对类的访问权限有了进一步限制(现在用的jdk1.8不支持模块,准备在服务器上做打包模块和JRE的实验)

Java核心类

字符串和编码

  • Java字符串的一个重要特点就是字符串不可变。这种不可变性是通过内部的private final char[]字段,以及没有任何修改char[]的方法实现的
  • 当我们想要比较两个字符串是否相同时,要特别注意,我们实际上是想比较字符串的内容是否相同。必须使用equals()方法而不能用==
  • 通过new String(char[])创建新的String实例时,它并不会直接引用传入的char[]数组,而是会复制一份,所以,修改外部的char[]数组不会影响String实例内部的char[]数组,因为这是两个不同的数组
  • 从String的不变性设计可以看出,如果传入的对象有可能改变,我们需要复制而不是直接引用,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Main {
public static void main(String[] args) {
int[] scores = new int[] { 88, 77, 51, 66 };
Score s = new Score(scores);
s.printScores();
scores[2] = 99;
s.printScores();
}
}

class Score {
private int[] scores;
public Score(final int[] scores) {
this.scores = scores;
}

public void printScores() {
System.out.println(Arrays.toString(scores));
}
}

此处已经更改加上final,但是结果并不是预期的那样,原因是对final理解错误。final是引用不能改,并不是内容不能改,即无法重新指向,但是可以更改其内容。正确改法:this.scores = scores.clone();

StringBuilder

  • 为了能高效拼接字符串,Java标准库提供了StringBuilder,它是一个可变对象,可以预分配缓冲区,这样,往StringBuilder中新增字符时,不会创建新的临时对象

StringJoiner

包装类型

基本类型 对应的引用类型
boolean java.lang.Boolean
byte java.lang.Byte
short java.lang.Short
int java.lang.Integer
long java.lang.Long
float java.lang.Float
double java.lang.Double
char java.lang.Character

JavaBean

  • JavaBean是一种符合命名规范的class,它通过getter和setter来定义属性,使用Introspector.getBeanInfo()可以获取属性列表

枚举类

  • enum类型的每个常量在JVM中只有一个唯一实例,所以可以直接用==比较
  • 可以为enum编写构造方法、字段和方法;enum的构造方法要声明为private,字段强烈建议声明为final;enum适合用在switch语句中

BigInteger & BigDecimal

  • 总是使用compareTo()比较两个BigDecimal的值,不要使用equals()

常用工具类

  • 如果我们要生成一个区间在[MIN, MAX)的随机数,可以借助Math.random()实现,或者直接用Random实例方法XXXnext(指定)
  • 如果我们在创建Random实例时指定一个种子,就会得到完全确定的随机数序列,即所谓伪随机数,是指只要给定一个初始的种子,产生的随机数序列是完全一样的
  • 需要使用安全随机数的时候,必须使用SecureRandom,绝不能使用Random

异常处理

  • Java规定:必须捕获的异常,包括Exception及其子类,但不包括RuntimeException及其子类,这种类型的异常称为Checked Exception;不需要捕获的异常,包括Error及其子类,RuntimeException及其子类
  • 在方法定义的时候,使用throws Xxx表示该方法可能抛出的异常类型。调用方在调用的时候,必须强制捕获这些异常,否则编译器会报错
  • 所有异常都可以调用printStackTrace()方法打印异常栈,这是一个简单有用的快速打印异常的方法
  • 存在多个catch的时候,catch的顺序非常重要:子类必须写在前面
  • 为了能追踪到完整的异常栈,在构造异常的时候,把原始的Exception实例传进去,新的Exception就可以持有原始Exception信息
  • 通常不要在finally中抛出异常。如果在finally中抛出异常,应该原始异常加入到原有异常中。调用方可通过Throwable.getSuppressed()获取所有添加的Suppressed Exception
  • Java断言的特点是:断言失败时会抛出AssertionError,导致程序结束退出。因此,断言不能用于可恢复的程序错误,只应该用于开发和测试阶段
  • JVM默认关闭断言指令,即遇到assert语句就自动忽略了,不执行。要执行assert语句,必须给Java虚拟机传递-enableassertions(可简写为-
  • 日志就是Logging,它的目的是为了取代System.out.println() 需作进一步了解

反射

  • 反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息,反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法
  • 由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。这种通过Class实例获取class信息的方法称为反射(Reflection)
  • 通常情况下,我们应该用instanceof判断数据类型,因为面向抽象编程的时候,我们不关心具体的子类型。只有在需要精确判断一个类型是不是某个class的时候,我们才使用==判断class实例
  • 动态加载class的特性对于Java程序非常重要。利用JVM动态加载class的特性,我们才能在运行期根据条件加载不同的实现类