MOOC 个人学习笔记
# 1.for-each 和枚举
# for vs for-each
- for-each 从 JDK5.0 开始引入
- for-each 语法更简洁
- for-each 避免越界错误
- for 可以删除元素,for-each 不可以删除 / 替换元素
- for-each 遍历的时候,是不知道当前元素的具体位置索引
- for-each 只能正向遍历,不能反向遍历
- for-each 不能同时遍历 2 个集合
- for 和 for-each 性能接近
# 枚举类型
- 枚举变量:变量的取值只在一个有限的集合内
- Java5 推出 enum 类型
- enum 关键字声明枚举类,且都是 Enum 的子类 (但不需写 extends)
- enum 内部有多少个值,就有多少个实例对象
- 不能直接 new 枚举类对象
public enum Size { | |
SMALL,MEDIUM,LARGE,EXTRA_LARGE; | |
} | |
Size s1=Size.SMALL; | |
Size s2=Size.SMALL; | |
System.out.println(s1 == s2); //true |
- 除了枚举的内容,还可以添加属性 / 构造函数 / 方法
- 构造函数只能是 package-private (default) 或者 private,内部调用
enum Fruit { | |
APPLE(10), ORANGE(8); | |
private int price; | |
Fruit(int price) { | |
this.price = price; | |
} | |
public int getPrice() { | |
return this.price; | |
} | |
} |
- 所有的 enum 类型都是 Enum 的子类,也继承了相应方法
- ordinal () 返回枚举值所在的索引位置,从 0 开始
- compareTo () 比较两个枚举值的索引位置大小
- toString () 返回枚举值的字符串表示
- valueOf () 将字符串初始化为枚举对象
- values () 返回所有的枚举值
enum Day { | |
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY; | |
} | |
Day d1 = Day.MONDAY; | |
Day d2 = Enum.valueOf(Day.class, "MONDAY"); | |
System.out.println(d1 == d2); //true | |
Day d3 = Enum.valueOf(Day.class, "TUESDAY"); | |
System.out.println(d1.compareTo(d3)); //MONDAY<TUESDAY | |
// 遍历所有的枚举值 | |
for(Day item : Day.values()) { | |
System.out.println(item.toString() + "," + item.ordinal()); | |
} |
# 2. 不定项参数和静态导入
# 不定项参数
- JDK 5 提供了不定项参数 (可变参数) 功能
- 类型后面加 3 个点,如 int.../double.../String.../
- 可变参数,本质上是一个数组
public static void print(String... args) { | |
System.out.println(args.length); | |
for (String arg : args) { | |
System.out.println(arg); | |
} | |
} | |
print(); | |
print("aaa"); | |
print("aaa", "bbb"); | |
print("aaa", "bbb", "ccc"); |
- 一个方法只能有一个不定项参数,且必须位于参数列表的最后
// 错误:一个方法不可以有多个可变参数 | |
public static void print(String... args, int... irgs) { | |
} |
- 重载的优先级规则 1:固定参数的方法,比可变参数优先级更高
// 当只有一个参数时,本方法优先级更高 | |
public static void print(String s) | |
{ | |
System.out.println("I am another method"); | |
} |
- 重载的优先级规则 2:调用语句,同时与两个带可变参数的方法匹配,则报错
// 错误:一个调用语句不能同时有 2 个带可变参数的方法适配 | |
public static void print(String s1, String... args) { | |
} |
# 静态导入
- import 导入程序所需要的类
import static java.lang.Math.pow; | |
import static java.lang.Math.sqrt; | |
import static java.lang.System.out; | |
public class ImportStaticTest { | |
public static void importMath() { | |
int a=3, b=4, c=0; | |
c = (int) sqrt(pow(a,2)+pow(b,2)); | |
out.println("c is " + c); | |
} | |
} |
- import static 导入一个类的静态方法和静态变量 (JDK5 引入)
- 少使用 * 通配符,不滥用,最好具体到静态变量或方法
- 静态方法名具有明确特征,如有重名,需要补充类名
# 3. 自动拆装箱,多异常并列,数值类型赋值优化
# 自动装箱和拆箱 (auto-boxing/auto-unboxing)
- 从 JDK5.0 开始引入,简化基本类型和对象转换的写法
- 基本类型:boolean/byte/char/int/short/long/float/double
- 对象:Boolean/Byte/Character/Integer/Short/Long/Float/Double
Integer obj1 = 5; // 自动装箱 | |
Integer obj2 = Integer.valueOf(5); | |
int a1 = obj1; // 自动拆箱 | |
int a2 = obj1.intValue(); |
# 注意事项
- 装箱和拆箱是编译器的工作,在 class 中已经添加转化。虚拟机没有自动装箱和拆箱的语句
- ==:基本类型是内容相同,对象是指针是否相同 (内存同一个区域)
- 基本类型没有空值,对象有 null,可能触发 NullPointerException
- 当一个基础数据类型与封装类进行 ==、+、-、* 、/ 运算时,会将封装类进行拆箱,对基础数据类型进行运算
- 谨慎使用多个非同类的数值类对象进行运算
# 多异常并列
- 多个异常并列在一个 catch 中
- 从 JDK7.0 开始引入,简化写法
try { | |
test(); | |
} catch(IOException | SQLException ex) { | |
//JDK7 开始,支持一个 catch 写多个异常 | |
// 异常处理 | |
} |
- 多个异常之间不能有 (直接 / 间接) 继承关系,如果有,则报错
try { | |
test2(); | |
} | |
catch(IOException | FileNotFoundException ex) { | |
// 异常处理 | |
} |
# 数值类型赋值优化
- Java 7 的新语法:整数类型用二进制数赋值
- 避免二进制计算
- byte/short/int/long
byte a1 = (byte) 0b00100001; | |
short a2 = (short) 0b1010000101000101; | |
int a4 = 0b101; | |
int a5 = 0B101; //B 可以大小写 | |
long a6 = 0b1010000101000101101000010100010110100001010001011010000101000101L; |
- Java 7 的新语法:在数值字面量 (literal) 中使用下划线
- 增加数字的可读性和纠错功能
- short/int/long/float/double
- 下划线只能出现数字中间,前后必须有数字
- 允许在二 / 八 / 十 / 十六进制的数字中使用
int a3 = 0b0111_1011_0001; // 二进制,0b 开头 | |
int a4 = 0_214; // 八进制,0 开头 | |
int a5 = 123___45; // 可以多个下划线 | |
int a6 = 0x7_B_1; // 十六进制 | |
float a7 = 3.56_78f; //float | |
double a8 = 1.3_45__67; //double | |
int b1 = 0b_123_4; //_必须在数字之间 | |
int b2 = 0123_4_; //_不能在末尾 | |
int b3 = _123; //_不能在开头 | |
int b4 = 0_x_123; // 不能拆开 0x | |
int b5 = 0x_51; //_必须在数字之间 | |
long b6 = 1000_L; //_必须在数字之间 | |
float b7 = 1.34f_; //_不能在末尾 |
# 4. 接口方法
- Java 8 推出接口的默认方法 / 静态方法 (都带实现的),为 Lambda 表达式提供支持
# 接口的默认方法
- 以 default 关键字标注,其他的定义和普通函数一样
- 规则 1:默认方法不能重写 Object 中的方法
- 规则 2:实现类可以继承 / 重写父接口的默认方法
- 规则 3:接口可以继承 / 重写父接口的默认方法
- 规则 4:当父类和父接口都有 (同名同参数) 默认方法,子类继承父类的默认方法,这样可以兼容 JDK7 及以前的代码
- 规则 5:子类实现了 2 个接口 (均有同名同参数的默认方法),那么编译失败,必须在子类中重写这个 default 方法
public interface NewAnimal { | |
public default void move() { | |
System.out.println("I can move."); | |
} | |
} |
# 接口的静态方法
- 该静态方法属于本接口的,不属于子类 / 子接口
- 子类 (子接口) 没有继承该静态方法,只能通过所在的接口名来调用
public interface StaticAnimal { | |
public static void move() { | |
System.out.println("I can move"); | |
} | |
} |
# 接口的私有方法
- Java 9 推出,带实现
- 解决多个默认方法 / 静态方法的内容重复问题
- 私有方法属于本接口,只在本接口内使用,不属于子类 / 子接口
- 子类 (子接口) 没有继承该私有方法,也无法调用
- 静态私有方法可以被静态 / 默认方法调用,非静态私有方法被默认方法调用
public interface PrivateAnimal { | |
private void move() { | |
// 非静态的私有方法 | |
System.out.println("I can move"); | |
System.out.println("I am growing"); | |
} | |
private static void move2() { | |
// 静态的私有方法 | |
System.out.println("I can move"); | |
System.out.println("I am growing"); | |
} | |
} |
# 接口 vs 抽象类
- 相同点 (截止至 Java 12 以前,接口和抽象类对比)
- 都是抽象的,都不能被实例化,即不能被 new
- 都可以有实现方法
- 都可以不需要继承者实现所有的方法
- 不同点 (截止至 Java 12 以前,接口和抽象类对比) 、
- 抽象类最多只能继承一个,接口可以实现多个
- 接口的变量默认是 public static final,且必须有初值,子类不能修改;而抽象类的变量默认是 default,子类可以继承修改
- 接口没有构造函数,抽象类有构造函数
- 接口没有 main 函数,抽象类可以有 main 函数
- 接口有 public/default/private 的方法,抽象类有 public/private/protected/ 不写关键字的 (default) 的方法
# 5.try-with-resource 和 Resource Bundle 文件加载
# try-with-resource
- JDK7 提供 try-with-resource,比 try-catch-finally 更简便
- 资源要求定义在 try 中。若已经在外面定义,则需要一个本地变量
- JDK9 不再要求定义临时变量,可以直接使用外部资源变量
String line; | |
//try-resource 语句,自动关闭资源 | |
try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream("c:/temp/abc.txt")))) { | |
while ((line = in.readLine()) != null) { | |
System.out.println(line); | |
} | |
} catch(Exception ex) { | |
ex.printStackTrace(); | |
} |
- try-with-resource 原理
- 资源对象必须实现 AutoCloseable 接口,即实现 close 方法
public class MyTryWithResourceTest { | |
public static void main(String[] args) { | |
// 将会自动调用 conn 的 close 方法 | |
try(MyConnection conn = new MyConnection()){ | |
conn.sendData(); | |
} catch(Exception ex) { | |
ex.printStackTrace(); | |
} | |
} | |
} | |
class MyConnection implements AutoCloseable { | |
public void sendData() throws Exception { | |
System.out.println("Send Data...."); | |
} | |
public void close() throws Exception { | |
System.out.println("Close...."); | |
} | |
} |
# ResourceBundle 文件加载
- Java 8 及以前,ResourceBundle 默认以 ISO-8859-1 方式加载 Properties 文件
- 需要利用 native2ascii 工具 (JDK 自带) 对文件进行转义
- JDK9 及以后, ResourceBundle 默认以 UTF-8 方式加载 Properties 文件
- JDK9 及以后,已经删除 native2ascii 工具
- 新的 Properties 文件可以直接以 UTF-8 保存
- 已利用 native2ascii 工具转化后的文件,不受影响。即 ResourceBundle 若解析文件不是有效的 UTF-8,则以 ISO-8859-1 方式加载
# 6.var 类型和 switch
# var 类型
- Java 以前一直是一种强类型的程序语言
- 每个变量在定义时就确定了类型
- 类型固定了,就不能更改
- Java 10 推出 var:局部变量推断
- 避免信息冗余
- 对齐了变量名
- 更容易阅读
- 本质上还是强类型语言,编译器负责推断类型,并写入字节码文件。因此推断后不能更改!!!
- var 的限制
- 可以用在局部变量上,非类成员变量
- 可以用在 for/for-each 循环中
- 声明时必须初始化
- 不能用在方法 (形式) 参数和返回类型
- 大面积滥用会使代码整体阅读性变差
- var 只在编译时起作用,没有在字节码中引入新的内容,也没有专门的 JVM 指令处理 var
# switch
- 支持的类型:byte/Byte, short/Short, int/Integer, char/Character, String (7.0), Enum 枚举 (5.0)
- 不支持 long/float/double
- 多分支合并,采用 -> 直接连接判定条件和动作 (JDK12 支持)
public static int judgeMonthDay12(String month) { | |
//this method works based on Java 12. | |
int result = 0; | |
switch(month) { | |
case "Jan","Mar","May","July","Aug","Oct","Dec" -> result = 31; | |
case "Apr","June","Sep","Nov" -> result = 30; | |
case "Feb" -> result = 28; | |
default -> result = -1; | |
} | |
return result; | |
} |
- switch 直接在表达式赋值 (JDK12 支持)
public static void testSwitchReturn() { | |
int num = 1; | |
int days = switch (num) { | |
case 1,3,5,7,8,10,12 -> 31; | |
case 4,6,9,11 -> 30; | |
default -> { | |
int result = 28; | |
break result; // 代码块中 break 返回结果 | |
} | |
}; | |
System.out.println(days); | |
} |