少女祈祷中...

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枚举类对象
1
2
3
4
5
6
7
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,内部调用
1
2
3
4
5
6
7
8
9
10
11
12
13
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()返回所有的枚举值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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…/
  • 可变参数,本质上是一个数组
1
2
3
4
5
6
7
8
9
10
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");
  • 一个方法只能有一个不定项参数,且必须位于参数列表的最后
1
2
3
4
//错误:一个方法不可以有多个可变参数
public static void print(String... args, int... irgs) {

}
  • 重载的优先级规则1:固定参数的方法,比可变参数优先级更高
1
2
3
4
5
//当只有一个参数时,本方法优先级更高
public static void print(String s)
{
System.out.println("I am another method");
}
  • 重载的优先级规则2:调用语句,同时与两个带可变参数的方法匹配,则报错
1
2
3
4
5
//错误:一个调用语句不能同时有2个带可变参数的方法适配
public static void print(String s1, String... args)
{

}

静态导入

  • import 导入程序所需要的类
1
2
3
4
5
6
7
8
9
10
11
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
1
2
3
4
5
Integer obj1 = 5;  //自动装箱
Integer obj2 = Integer.valueOf(5);

int a1 = obj1; //自动拆箱
int a2 = obj1.intValue();

注意事项

  • 装箱和拆箱是编译器的工作,在class中已经添加转化。虚拟机没有自动装箱和拆箱的语句
  • ==:基本类型是内容相同,对象是指针是否相同(内存同一个区域)
  • 基本类型没有空值,对象有null,可能触发NullPointerException
  • 当一个基础数据类型与封装类进行 ==、+、-、* 、/运算时,会将封装类进行拆箱,对基础数据类型进行运算
  • 谨慎使用多个非同类的数值类对象进行运算

多异常并列

  • 多个异常并列在一个catch中
    • 从JDK7.0开始引入,简化写法
1
2
3
4
5
6
7
try {
test();
}
catch(IOException | SQLException ex) {
//JDK7开始,支持一个catch写多个异常
//异常处理
}
  • 多个异常之间不能有(直接/间接)继承关系,如果有,则报错
1
2
3
4
5
6
try {
test2();
}
catch(IOException | FileNotFoundException ex) {
//异常处理
}

数值类型赋值优化

  • Java 7的新语法:整数类型用二进制数赋值
    • 避免二进制计算
    • byte/short/int/long
1
2
3
4
5
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
    • 下划线只能出现数字中间,前后必须有数字
    • 允许在二/八/十/十六进制的数字中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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方法
1
2
3
4
5
public interface NewAnimal {
public default void move() {
System.out.println("I can move.");
}
}

接口的静态方法

  • 该静态方法属于本接口的,不属于子类/子接口
  • 子类(子接口)没有继承该静态方法,只能通过所在的接口名来调用
1
2
3
4
5
public interface StaticAnimal {
public static void move() {
System.out.println("I can move");
}
}

接口的私有方法

  • Java 9推出,带实现
  • 解决多个默认方法/静态方法的内容重复问题
  • 私有方法属于本接口,只在本接口内使用,不属于子类/子接口
  • 子类(子接口)没有继承该私有方法,也无法调用
  • 静态私有方法可以被静态/默认方法调用,非静态私有方法被默认方法调用
1
2
3
4
5
6
7
8
9
10
11
12
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不再要求定义临时变量,可以直接使用外部资源变量
1
2
3
4
5
6
7
8
9
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方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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支持)
1
2
3
4
5
6
7
8
9
10
11
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支持)
1
2
3
4
5
6
7
8
9
10
11
12
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);
}