记录廖雪峰老师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 ; } } 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的特性,我们才能在运行期根据条件加载不同的实现类