学习廖雪峰的JAVA入门教程的笔记第一部分。
准备工作、JAVA面向对象编程、JAVA核心类、异常处理
准备工作 第一个JAVA程序 1 2 3 4 5 public class Hello { public static void main (String[] args) { System.out.println("Hello world" ); } }
执行代码
1 2 3 4 // 编译 javac Hello.java // 执行 java Hello
JAVA程序基础 变量和数据类型 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 int a = 1 ;float f1 = 3.14f ;float f2 = 3.14e38f ; double d = 1.79e308 ;double d2 = -1.79e308 ;double d3 = 4.9e-324 ; boolean b1 = true ;boolean b2 = false ;boolean isGreater = 5 > 3 ; int age = 12 ;boolean isAdult = age >= 18 ; char a = 'A' ;char zh = '中' ;String s = "hello" ;
定义变量的时候,如果加上final
修饰符,这个变量就变成了常量:final double PI = 3.14; // PI是一个常量
整数运算 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 int x = 12345 / 67 ; int y = 12345 % 67 ; n += 100 ; n -= 100 ; int n = 3300 ;n++; n--; int n = 7 ; int a = n << 1 ; int b = n << 2 ; int c = n << 28 ; int d = n << 29 ; int n = -536870912 ;int a = n >> 1 ; int b = n >> 2 ; int c = n >> 28 ; int d = n >> 29 ; int n = -536870912 ;int a = n >>> 1 ; int b = n >>> 2 ; int c = n >>> 29 ; int d = n >>> 31 ; n = 0 & 0 ; n = 0 | 1 ; n = ~0 ; n = 0 ^ 1 ;
如果参与运算的两个数类型不一致,那么计算结果为较大类型的整型。
浮点数运算 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 double r = Math.abs(x - y);if (r < 0.00001 ) { } else { } double d = 1.2 + 24 / 5 ; int n1 = (int ) 12.3 ; int n2 = (int ) 12.7 ; int n2 = (int ) -12.7 ; int n3 = (int ) (12.7 + 0.5 ); int n4 = (int ) 1.2e20 ;
布尔运算 布尔运算是一种关系运算,包括以下几类:
比较运算符:>
,>=
,<
,<=
,==
,!=
与运算 &&
或运算 ||
非运算 !
三元运算符
三元运算符b ? x : y
字符和字符串 字符类型 1 2 3 4 5 6 7 8 char c1 = 'A' ;char c2 = '中' ;int n1 = 'A' ; int n2 = '中' ; char c3 = '\u0041' ; char c4 = '\u4e2d' ;
字符串类型 1 2 3 4 5 6 7 String s = "" ; String s1 = "A" ; String s2 = "ABC" ; String s3 = "中文 ABC" ; String s = "abc\"xyz" ;
常见的转义字符包括:
\"
表示字符"
\'
表示字符'
\\
表示字符\
\n
表示换行符
\r
表示回车符
\t
表示Tab
\u####
表示一个Unicode编码的字符
Java的编译器对字符串做了特殊照顾,可以使用+
连接任意字符串和其他数据类型,这样极大地方便了字符串的处理。
从Java 13开始,字符串可以用"""..."""
表示多行字符串(Text Blocks),多行字符串前面共同的空格会被去掉
1 2 3 4 5 6 String s = "" " SELECT * FROM users WHERE id > 100 ORDER BY name DESC " "" ;
字符串不可变
引用类型的变量可以指向一个空值null
,它表示不存在,即该变量不指向任何对象。
数组类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int [] ns = new int [5 ];ns[0 ] = 68 ; ns[1 ] = 79 ; ns[2 ] = 91 ; ns[3 ] = 85 ; ns[4 ] = 62 ; int [] ns = { 68 , 79 , 91 , 85 , 62 };System.out.println(ns.length); int [] ns = new int [] { 68 , 79 , 91 , 85 , 62 };public class Main { public static void main (String[] args) { String[] names = {"ABC" , "XYZ" , "zoo" }; String s = names[1 ]; names[1 ] = "cat" ; System.out.println(s); } }
流程控制 格式化输出
1 2 3 4 double d = 3.1415926 ;System.out.printf("%.2f\n" , d); System.out.printf("%.4f\n" , d);
java中的占位符
占位符
说明
%d
格式化输出整数
%x
格式化输出十六进制整数
%f
格式化输出浮点数
%e
格式化输出科学计数法表示的浮点数
%s
格式化字符串
连续两个%%表示一个%字符本身。
1 2 int n = 12345000 ;System.out.printf("n=%d, hex=%08x" , n, n);
输入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.util.Scanner;public class Main { public static void main (String[] args) { Scanner scanner = new Scanner(System.in); System.out.print("Input your name: " ); String name = scanner.nextLine(); System.out.print("Input your age: " ); int age = scanner.nextInt(); System.out.printf("Hi, %s, you are %d\n" , name, age); } }
if判断 1 2 3 4 5 6 7 if (n < 60 ) { } else if (n < 90 ) { } else { }
判断引用类型是否相等
判断引用类型的变量是否相等时,==
表示“引用是否相等”,或者说,是否指向同一个对象。
判断引用类型的值是否相等必须使用equals函数。(s1.equals(s2))
switch多重选择 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 57 58 59 60 61 62 63 64 int option = 1 ;switch (option) { case 1 : System.out.println("Selected 1" ); break ; case 2 : System.out.println("Selected 2" ); break ; case 3 : System.out.println("Selected 3" ); break ; default : System.out.println("No fruit selected" ); break ; } public class Main { public static void main (String[] args) { String fruit = "apple" ; switch (fruit) { case "apple" -> System.out.println("Selected apple" ); case "pear" -> System.out.println("Selected pear" ); case "mango" -> { System.out.println("Selected mango" ); System.out.println("Good choice!" ); } default -> System.out.println("No fruit selected" ); } } } public class Main { public static void main (String[] args) { String fruit = "apple" ; int opt = switch (fruit) { case "apple" -> 1 ; case "pear" , "mango" -> 2 ; default -> 0 ; }; System.out.println("opt = " + opt); } } public class Main { public static void main (String[] args) { String fruit = "orange" ; int opt = switch (fruit) { case "apple" -> 1 ; case "pear" , "mango" -> 2 ; default -> { int code = fruit.hashCode(); yield code; } }; System.out.println("opt = " + opt); } }
编译检查
使用IDE时,可以自动检查是否漏写了break
语句和default
语句,方法是打开IDE的编译检查。
在Eclipse中,选择Preferences
- Java
- Compiler
- Errors/Warnings
- Potential programming problems
,将以下检查标记为Warning:
‘switch’ is missing ‘default’ case
‘switch’ case fall-through
在Idea中,选择Preferences
- Editor
- Inspections
- Java
- Control flow issues
,将以下检查标记为Warning:
Fallthrough in ‘switch’ statement
‘switch’ statement without ‘default’ branch
当switch
语句存在问题时,即可在IDE中获得警告提示。
循环 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 while (条件表达式) { 循环语句 } do { 执行循环语句 } while (条件表达式); for (初始条件; 循环检测条件; 循环后更新计数器) { } for (int n : ns) { System.out.println(n); }
在循环过程中,可以使用break
语句跳出当前循环。
continue
则是提前结束本次循环,直接继续执行下次循环。
数组操作 遍历数组 1 2 3 4 5 6 7 8 9 10 11 12 13 import java.util.Arrays;int [] ns = { 1 , 4 , 9 , 16 , 25 };for (int i=0 ; i<ns.length; i++) { int n = ns[i]; System.out.println(n); } for (int n : ns) { System.out.println(n); } System.out.println(Arrays.toString(ns));
排序 1 2 int [] ns = { 28 , 12 , 89 , 73 , 65 , 18 , 96 , 50 , 8 , 36 };Arrays.sort(ns);
对数组排序实际上修改了数组本身。
多维数组 1 2 3 4 5 6 7 int [][] ns = { { 1 , 2 , 3 , 4 }, { 5 , 6 , 7 , 8 }, { 9 , 10 , 11 , 12 } }; System.out.println(Arrays.deepToString(ns));
命令行参数 1 2 3 4 5 6 7 8 9 10 public class Main { public static void main (String[] args) { for (String arg : args) { if ("-version" .equals(arg)) { System.out.println("v 1.0" ); break ; } } } }
JAVA面向对象编程 面向对象基础 1 2 3 4 5 6 7 public class Person { public String name; public int age; } Person ming = new Person(); ming.name = "小明" ; ming.age = 12 ;
数据封装 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 public class Person { private String name; private int age; public Person (String name, int age) { this .name = name; this .age = age; } public Person (String name) { this (name,18 ); } public void setName (String name) { this .name = name; } public String getName () { return this .name; } class Group { private String[] names; public void setNames (String... names) { this .names = names; } } Group g = new Group(); g.setNames("Xiao Ming" , "Xiao Hong" , "Xiao Jun" ); g.setNames("Xiao Ming" , "Xiao Hong" ); g.setNames("Xiao Ming" ); g.setNames(); }
构造方法的初始化顺序:
初始化字段
没有赋值的字段初始化为默认值:基本类型=0;引用类型=null;布尔类型=false;数值类型的字段用默认值;
再执行构造方法的代码
继承和多态 JAVA只允许class继承自一个类
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 57 58 59 60 61 public class Person { private String name; private int age; public void run () {} } public class Student extends Person { private int score; public Student () { super (); } @Override public void run () { super .run(); }; public void setScore (int score) {}; } Person p = new Person(); Student s = new Student(); Person ps = new student(); Object o1 = p; Object o2 = s; Student s = (Student) p; Person p = new Person(); System.out.println(p instanceof Person); System.out.println(p instanceof Student); Student s = new Student(); System.out.println(s instanceof Person); System.out.println(s instanceof Student); Student n = null ; System.out.println(n instanceof Student); Person p = new Student(); if (p instance Student){ Student s = (Student) p; pass; } if (p instance Student s){ pass; }
1 2 3 4 5 ob=>operation: Object person=>operation: Person student=>operation: Student student->person->ob
阻止继承
1 2 3 public sealed class Shape permits Rect ,Circle ,Triangle {}
多态
多态是指针对某个类型的方法调用,其真正执行的方法取决于运行时实际类型的方法
对某个类型调用方法,执行的方法可能是某个子类的方法
利用多态,允许添加更多类型的子类实现功能扩展
Object类定义的几个重要方法
1 2 3 4 5 6 7 8 9 public class Person { ... @Overrride public String toString () {} @Override public boolean equals (Object o) {} @Override public int hashCode () {} }
final
用final修饰的方法不能被Override
用final修饰的类不能被继承
用final修饰的字段在初始化后不能被修改
1 2 3 4 5 6 7 public class Person { public final void setName (String name) {} } public final class Student extends Person { private final int score; }
抽象类和接口 抽象类
抽象方法所在的类必须声明为抽象类。
抽象类可以
1 2 3 public abstract class Person { public abstract void run () ; }
接口
如果一个类没有字段,所有方法全部是抽象方法,就可以把该抽象类改写为接口
因为接口定义的所有方法默认都是public abstract
的,所以这两个修饰符不需要写出来(写不写效果都一样)。
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 interface Person { void run () ; String getName () ; } class Student implements Person { private String name; public Student (String name) { this .name = name; } @Override public void run () { System.out.println(this .name + " run" ); } @Override public String getName () { return this .name; } } class Student implements Person , Hello { ... }
接口继承
一个interface
可以继承自另一个interface
。interface
继承自interface
使用extends
,它相当于扩展了接口的方法。例如:
1 2 3 4 5 6 7 8 interface Hello { void hello(); } interface Person extends Hello { void run(); String getName(); }
default方法
在接口中,可以定义default
方法。例如,把Person
接口的run()
方法改为default
方法:
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 public class Main { public static void main (String[] args) { Person p = new Student("Xiao Ming" ); p.run(); } } interface Person { String getName () ; default void run () { System.out.println(getName() + " run" ); } } class Student implements Person { private String name; public Student (String name) { this .name = name; } public String getName () { return this .name; } }
default
方法和抽象类的普通方法是有所不同的。因为interface
没有字段,default
方法无法访问字段,而抽象类的普通方法可以访问实例字段。
静态字段和静态方法 static field
类的所有实例共享
1 2 3 4 5 6 class Person { public String name; public int age; public static int number; }
静态方法使用类名就可以调用。在静态方法中无法访问this变量,也无法访问实例字段,只能访问静态字段。
接口的静态字段
1 2 3 4 5 6 7 8 9 10 public interface Person { public static final int MALE = 1 ; public static final int FEMALE = 2 ; } public interface Person { int MALE = 1 ; int FEMALE = 2 ; }
包和作用域 1 2 3 4 5 6 7 8 9 10 11 package ming; public class Person {} package mr.jun; public class Arrays {}
包没有父子关系。java.util
和java.util.zip
是不同的包,两者没有任何继承关系。
包作用域
位于同一个包的类,可以访问包作用域的字段和方法。不用public
、protected
、private
修饰的字段和方法就是包作用域。例如,Person
类定义在hello
包下面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package hello;public class Person { void hello () { System.out.println("Hello!" ); } } package hello;public class Main { public static void main (String[] args) { Person p = new Person(); p.hello(); } }
import
1 2 3 4 5 6 7 mr.jun.Arrays arrays = new mr.jun.Arrays(); import mr.jun.Arrays;Arrays arrays = new Arrays(); import static java.lang.System.*;
作用域
定义为public
的class
、interface
可以被其他任何类访问
private
访问权限被限定在class
的内部,而且与方法声明顺序无关 。
protected
作用于继承关系。定义为protected
的字段和方法可以被子类访问,以及子类的子类
Java支持嵌套类,如果一个类内部还定义了嵌套类,那么,嵌套类拥有访问private
的权限
包作用域是指一个类允许访问同一个package
的没有public
、private
修饰的class
,以及没有public
、protected
、private
修饰的字段和方法。
final
用final
修饰class
可以阻止被继承
用final
修饰method
可以阻止被子类覆写
用final
修饰field
可以阻止被重新赋值
用final
修饰局部变量可以阻止被重新赋值
1 2 3 protected void hi (final int t) { t = 1 ; }
如果不确定是否需要public
,就不声明为public
,即尽可能少地暴露对外的字段和方法。
把方法定义为package
权限有助于测试,因为测试类和被测试类只要位于同一个package
,测试代码就可以访问被测试类的package
权限方法。
一个.java
文件只能包含一个public
类,但可以包含多个非public
类。如果有public
类,文件名必须和public
类的名字相同。
内部类
定义在另一个类内部的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Main { public static void main (String[] args) { Outer outer = new Outer("Nested" ); Outer.Inner inner = outer.new Inner () ; inner.hello(); } } class Outer { private String name; Outer(String name) { this .name = name; } class Inner { void hello () { System.out.println("Hello, " + Outer.this .name); } } }
匿名类(Anonymous class)
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 public class Main { public static void main (String[] args) { Outer outer = new Outer("Nested" ); outer.asyncHello(); } } class Outer { private String name; Outer(String name) { this .name = name; } void asyncHello () { Runnable r = new Runnable() { @Override public void run () { System.out.println("Hello, " + Outer.this .name); } }; new Thread(r).start(); } } import java.util.HashMap;public class Main { public static void main (String[] args) { HashMap<String, String> map1 = new HashMap<>(); HashMap<String, String> map2 = new HashMap<>() {}; HashMap<String, String> map3 = new HashMap<>() { { put("A" , "1" ); put("B" , "2" ); } }; System.out.println(map3.get("A" )); } }
静态内部类
用static
修饰的内部类和Inner Class有很大的不同,它不再依附于Outer
的实例,而是一个完全独立的类,因此无法引用Outer.this
,但它可以访问Outer
的private
静态字段和静态方法。如果把StaticNested
移到Outer
之外,就失去了访问private
的权限。
1 2 3 4 5 6 7 8 9 10 class Outer { private static String NAME = "OUTER" ; private String name; static class StaticNested { void hello () { System.out.println("Hello, " + Outer.NAME); } } }
classpath和jar classpath
是JVM用到的一个环境变量,它用来指示JVM如何搜索class
。
启动JVM时设定:java -classpath .;C:\work\project1\bin;C:\shared abc.xyz.Hello
jar包的使用:java -cp ./hello.jar abc.xyz.Hello
因为jar包就是zip包,所以,直接在资源管理器中,找到正确的目录,点击右键,在弹出的快捷菜单中选择“发送到”,“压缩(zipped)文件夹”,就制作了一个zip文件。然后,把后缀从.zip
改为.jar
,一个jar包就创建成功。
模块
java9开始,JDK中引入了模块(Module)。来解决不同jar包之间的依赖问题。
个人理解,模块主要用于减小JRE发布的体积。
模块之间的依赖项写入单独的文件(module-info.java)。
1 2 3 4 5 6 7 8 9 10 11 module hello.world{ requires java.base; requires java.xml; exports java.xml; exports javax.xml.catalog; exports javax.xml.datatype; }
创建模块的步骤
编译字节码javac -d bin src/module-info.java src/com/itranswarp/sample/*.java
创建jar包:jar --create --file test.jar --main-class com.mmm.Main -C bin .
创建模块:jmod create --class-path test.jar test.jmod
(必要时创建环境变量)
运行模块:java --module-path test.jar --module test
打包JRE
jlink --module-path test.jmod --add-modules java.base,java.xml,test --output jre/
JAVA核心类 String 两个字符串比较,必须总是使用equals()
方法。
要忽略大小写比较,使用equalsIgnoreCase()
方法。
String
类还提供了多种方法来搜索子串、提取子串。常用的方法有:
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 "Hello" .contains("ll" ); "Hello" .indexOf("l" ); "Hello" .lastIndexOf("l" ); "Hello" .startsWith("He" ); "Hello" .endsWith("lo" ); "Hello" .substring(2 ); "Hello" .substring(2 , 4 ); "ll" sb.delete(sb.length() - 2 , sb.length()); " \tHello\r\n " .trim(); "\u3000Hello\u3000" .strip(); " Hello " .stripLeading(); " Hello " .stripTrailing(); "" .isEmpty(); " " .isEmpty(); " \n" .isBlank(); " Hello " .isBlank(); String s = "hello" ; s.replace('l' , 'w' ); s.replace("ll" , "~~" ); String s = "A,,B;C ,D" ; s.replaceAll("[\\,\\;\\s]+" , "," ); String s = "A,B,C,D" ; String[] ss = s.split("\\," ); String[] arr = {"A" , "B" , "C" }; String s = String.join("***" , arr); String[] names = {"Bob" , "Alice" , "Grace" }; var sj = new StringJoiner(", " );for (String name : names) { sj.add(name); } System.out.println(sj.toString()); String s = "Hi %s, your score is %d!" ; System.out.println(s.formatted("Alice" , 80 )); String.valueOf(123 ); String.valueOf(45.67 ); String.valueOf(true ); String.valueOf(new Object()); int n1 = Integer.parseInt("123" ); int n2 = Integer.parseInt("ff" , 16 ); boolean b1 = Boolean.parseBoolean("true" ); boolean b2 = Boolean.parseBoolean("FALSE" ); Integer.getInteger("java.version" ); char [] cs = "Hello" .toCharArray(); String s = new String(cs); byte [] b1 = "Hello" .getBytes(); byte [] b2 = "Hello" .getBytes("UTF-8" ); byte [] b2 = "Hello" .getBytes("GBK" ); byte [] b3 = "Hello" .getBytes(StandardCharsets.UTF_8); byte [] b = ...String s1 = new String(b, "GBK" ); String s2 = new String(b, StandardCharsets.UTF_8);
StringBuilder 为了能高效拼接字符串,Java标准库提供了StringBuilder
,它是一个可变对象,可以预分配缓冲区,这样,往StringBuilder
中新增字符时,不会创建新的临时对象:
1 2 3 4 5 6 7 8 9 10 11 StringBuilder sb = new StringBuilder(1024 ); for (int i = 0 ; i < 1000 ; i++) { sb.append(',' ); sb.append(i); sb.append("Mr " ) .append("Bob" ) .append("!" ) .insert(0 , "Hello, " ); } String s = sb.toString();
对于普通的字符串+
操作,并不需要我们将其改写为StringBuilder
,因为Java编译器在编译时就自动把多个连续的+
操作编码为StringConcatFactory
的操作。在运行期,StringConcatFactory
会自动把字符串连接操作优化为数组复制或者StringBuilder
操作。
包装类型 引用类型可以赋值为null
,表示空,但基本类型不能赋值为null
java核心库中为每种基本类型都提供了对应的包装类型
基本类型
对应的引用类型
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
1 2 3 4 5 6 7 8 int i = 100 ;Integer n1 = new Integer(i); Integer n2 = Integer.valueOf(i); Integer n3 = Integer.valueOf("100" ); System.out.println(n3.intValue());
Auto Boxing
自动拆包和解包只发生在编译阶段:
1 2 Integer n = 100 ; int x = n;
不变性
所有的包装类型都是不变类。private final class Integer{private final int value;}
进制转换
1 2 3 4 5 6 7 8 9 int x1 = Integer.parseInt("100" ); int x2 = Integer.parseInt("100" , 16 ); Integer.toString(100 ); System.out.println(Integer.toString(100 )); System.out.println(Integer.toString(100 , 36 )); System.out.println(Integer.toHexString(100 )); System.out.println(Integer.toOctalString(100 )); System.out.println(Integer.toBinaryString(100 ));
包装类型中的静态变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Boolean t = Boolean.TRUE; Boolean f = Boolean.FALSE; int max = Integer.MAX_VALUE; int min = Integer.MIN_VALUE; int sizeOfLong = Long.SIZE; int bytesOfLong = Long.BYTES; Number num = new Integer(999 ); byte b = num.byteValue();int n = num.intValue();long ln = num.longValue();float f = num.floatValue();double d = num.doubleValue();
在Java中,并没有无符号整型(Unsigned)的基本数据类型。byte
、short
、int
和long
都是带符号整型,最高位是符号位。
1 2 3 4 byte x = -1 ;byte y = 127 ;System.out.println(Byte.toUnsignedInt(x)); System.out.println(Byte.toUnsignedInt(y));
JavaBean 如果读写方法符合以下这种命名规范:
1 2 3 4 // 读方法: public Type getXyz() // 写方法: public void setXyz(Type value)
那么这种class
被称为JavaBean
.boolean
字段比较特殊,它的读方法一般命名为isXyz()
.
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 import java.beans.*;public class Main { public static void main (String[] args) throws Exception { BeanInfo info = Introspector.getBeanInfo(Person.class); for (PropertyDescriptor pd : info.getPropertyDescriptors()) { System.out.println(pd.getName()); System.out.println(" " + pd.getReadMethod()); System.out.println(" " + pd.getWriteMethod()); } } } class Person { private String name; private int age; public String getName () { return name; } public void setName (String name) { this .name = name; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } }
枚举类 1 2 3 4 5 6 7 8 9 10 enum Weekday { SUN, MON, TUE, WED, THU, FRI, SAT; } Weekday day = Weekday.SUN; if (day == Weekday.SAT || day == Weekday.SUN) { System.out.println("Work at home!" ); } else { System.out.println("Work at office!" ); }
enum
类型的每个常量在JVM中只有一个唯一实例,所以可以直接用==
比较
1 2 int n = Weekday.MON.ordinal();
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 enum Weekday { MON(1 ), TUE(2 ), WED(3 ), THU(4 ), FRI(5 ), SAT(6 ), SUN(0 ); public final int dayValue; private Weekday (int dayValue) { this .dayValue = dayValue; } } enum Weekday { MON(1 , "星期一" ), TUE(2 , "星期二" ), WED(3 , "星期三" ), THU(4 , "星期四" ), FRI(5 , "星期五" ), SAT(6 , "星期六" ), SUN(0 , "星期日" ); public final int dayValue; private final String chinese; private Weekday (int dayValue, String chinese) { this .dayValue = dayValue; this .chinese = chinese; } @Override public String toString () { return this .chinese; } }
记录类 使用String
、Integer
等类型的时候,这些类型都是不变类,一个不变类具有以下特点:
定义class时使用final
,无法派生子类;
每个字段使用final
,保证创建实例后无法修改任何字段。
从Java 14开始,引入了新的Record
类。我们定义Record
类时,使用关键字record
。
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 public class Main { public static void main (String[] args) { Point p = new Point(123 , 456 ); System.out.println(p.x()); System.out.println(p.y()); System.out.println(p); } } public record Point (int x, int y) {}public final class Point extends Record { private final int x; private final int y; public Point (int x, int y) { this .x = x; this .y = y; } public int x () { return this .x; } public int y () { return this .y; } public String toString () { return String.format("Point[x=%s, y=%s]" , x, y); } public boolean equals (Object o) { ... } public int hashCode () { ... } }
如果构造类需要检查参数:
1 2 3 4 5 6 7 public record Point (int x, int y) { public Point { if (x < 0 || y < 0 ) { throw new IllegalArgumentException(); } } }
1 2 3 4 5 6 7 8 9 10 public record Point (int x, int y) { public static Point of () { return new Point(0 , 0 ); } public static Point of (int x, int y) { return new Point(x, y); } } var z = Point.of();var p = Point.of(123 , 456 );
BigInteger java.math.BigInteger
就是用来表示任意大小的整数。BigInteger
内部用一个int[]
数组来模拟一个非常大的整数
1 2 3 BigInteger i = new BigInteger("123456789000" ); System.out.println(i.longValue()); System.out.println(i.multiply(i).longValueExact());
BigDecimal BigDecimal
可以表示一个任意大小且精度完全准确的浮点数。
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 BigDecimal d1 = new BigDecimal("123.4500" ); BigDecimal d2 = d1.stripTrailingZeros(); System.out.println(d1.scale()); System.out.println(d2.scale()); BigDecimal d1 = new BigDecimal("123.4500" ); BigDecimal d2 = d1.stripTrailingZeros(); System.out.println(d1.scale()); System.out.println(d2.scale()); BigDecimal d3 = new BigDecimal("1234500" ); BigDecimal d4 = d3.stripTrailingZeros(); System.out.println(d3.scale()); System.out.println(d4.scale()); BigDecimal d1 = new BigDecimal("123.456789" ); BigDecimal d2 = d1.setScale(4 , RoundingMode.HALF_UP); BigDecimal d3 = d1.setScale(4 , RoundingMode.DOWN); BigDecimal d1 = new BigDecimal("123.456" ); BigDecimal d2 = new BigDecimal("23.456789" ); BigDecimal d3 = d1.divide(d2, 10 , RoundingMode.HALF_UP); BigDecimal d4 = d1.divide(d2);
总是使用compareTo()比较两个BigDecimal的值,不要使用equals()!
常用工具类 Math 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Math.abs(-100 ); Math.max(100 , 99 ); Math.min(1.2 , 2.3 ); Math.pow(2 , 10 ); Math.sqrt(2 ); Math.exp(2 ); Math.log(4 ); Math.sin(3.14 ); Math.cos(3.14 ); Math.tan(3.14 ); Math.asin(1.0 ); Math.acos(1.0 ); double pi = Math.PI; double e = Math.E; Math.sin(Math.PI / 6 ); Math.random();
Random 1 2 3 4 5 6 7 Random r = new Random(098); r.nextInt(); r.nextInt(10 ); r.nextLong(); r.nextFloat(); r.nextDouble();
SecureRandom(真随机) 1 2 SecureRandom sr = new SecureRandom(); System.out.println(sr.nextInt(100 ));
需要使用安全随机数的时候,必须使用SecureRandom,绝不能使用Random!
异常处理 Java的异常 Throwable
是异常体系的根,它继承自Object
。Throwable
有两个体系:Error
和Exception
,Error
表示严重的错误,程序对此一般无能为力,例如:
OutOfMemoryError
:内存耗尽
NoClassDefFoundError
:无法加载某个Class
StackOverflowError
:栈溢出
而Exception
则是运行时的错误,它可以被捕获并处理。
某些异常是应用程序逻辑处理的一部分,应该捕获并处理。例如:
NumberFormatException
:数值类型的格式错误
FileNotFoundException
:未找到文件
SocketException
:读取网络失败
还有一些异常是程序逻辑编写不对造成的,应该修复程序本身。例如:
NullPointerException
:对某个null
的对象调用方法或字段
IndexOutOfBoundsException
:数组索引越界
Exception
又分为两大类:
RuntimeException
以及它的子类;
非RuntimeException
(包括IOException
、ReflectiveOperationException
等等)
Java规定:
必须捕获的异常,包括Exception
及其子类,但不包括RuntimeException
及其子类,这种类型的异常称为Checked Exception。
不需要捕获的异常,包括Error
及其子类,RuntimeException
及其子类。
捕获异常 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 import java.io.UnsupportedEncodingException;import java.util.Arrays;public class Main { public static void main (String[] args) { byte [] bs = toGBK("中文" ); System.out.println(Arrays.toString(bs)); try { process1(); process2(); process3(); } catch (IOException | NumberFormatException e) { System.out.println("Bad input" ); } catch (Exception e) { System.out.println("Unknown error" ); } finally { System.out.println("END" ); } } static byte [] toGBK(String s) throws UnsupportedEncodingException { return s.getBytes("GBK" ); static byte [] toGBK(String s) { try { return s.getBytes("GBK" ); } catch (UnsupportedEncodingException e) { System.out.println(e); return s.getBytes(); } } }
printStackTrace()
可以打印出方法的调用栈。
抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void process1 (String s) { try { process2(); } catch (NullPointerException e) { throw new IllegalArgumentException(e); } } void process2 (String s) { if (s==null ) { throw new NullPointerException(); } }
在catch
中抛出异常,不会影响finally
的执行。JVM会先执行finally
,然后抛出异常。
在极少数的情况下,我们需要获知所有的异常。如何保存所有的异常信息?方法是先用origin
变量保存原始异常,然后调用Throwable.addSuppressed()
,把原始异常添加进来,最后在finally
抛出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Main { public static void main (String[] args) throws Exception { Exception origin = null ; try { System.out.println(Integer.parseInt("abc" )); } catch (Exception e) { origin = e; throw e; } finally { Exception e = new IllegalArgumentException(); if (origin != null ) { e.addSuppressed(origin); } throw e; } } }
自定义异常 Java标准库定义的常用异常包括:
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 Exception │ ├─ RuntimeException │ │ │ ├─ NullPointerException │ │ │ ├─ IndexOutOfBoundsException │ │ │ ├─ SecurityException │ │ │ └─ IllegalArgumentException │ │ │ └─ NumberFormatException │ ├─ IOException │ │ │ ├─ UnsupportedCharsetException │ │ │ ├─ FileNotFoundException │ │ │ └─ SocketException │ ├─ ParseException │ ├─ GeneralSecurityException │ ├─ SQLException │ └─ TimeoutException
自定义异常通常从RuntimeException
派生:public class BaseException extends RuntimeException {}
自定义的BaseException
应该提供多个构造方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class BaseException extends RuntimeException { public BaseException () { super (); } public BaseException (String message, Throwable cause) { super (message, cause); } public BaseException (String message) { super (message); } public BaseException (Throwable cause) { super (cause); } }
NullPointerException NullPointerException
是一种代码逻辑错误,遇到NullPointerException
,遵循原则是早暴露,早修复,严禁使用catch
来隐藏这种编码错误.
1 2 3 4 5 6 public class Person { private String name = "" ; }
断言 1 2 3 4 5 6 7 public static void main (String[] args) { double x = Math.abs(-123.45 ); assert x >= 0 ; assert x >= 0 : "x must >= 0" ; System.out.println(x); }
断言失败时会抛出AssertionError
,导致程序结束退出。因此,断言不能用于可恢复的程序错误,只应该用于开发和测试阶段。
对于可恢复的程序错误,不应该使用断言。
idea中开启断言的方法
在VM optons中 加入 -ea或-enableassertions
实际开发中,很少使用断言。更好的方法是编写单元测试,后续我们会讲解JUnit
的使用。
JDK Logging java.util.logging
需要在JVM启动时传递参数-Djava.util.logging.config.file=<config-file-name>
。
Java标准库内置的Logging使用并不是非常广泛。
1 2 3 4 5 6 7 8 9 10 11 12 13 import java.util.logging.Level;import java.util.logging.Logger;public class Hello { public static void main (String[] args) { Logger logger = Logger.getGlobal(); logger.info("start process..." ); logger.warning("memory is running out..." ); logger.fine("ignored." ); logger.severe("process will be terminated..." ); } }
JDK的Logging定义了7个日志级别,从严重到普通:
SEVERE
WARNING
INFO
CONFIG
FINE
FINER
FINEST
Commons Logging Commons Logging是一个第三方日志库,它是由Apache创建的日志模块。
Commons Logging可以挂接不同的日志系统,并通过配置文件指定挂接的日志系统。默认情况下,Commons Loggin自动搜索并使用Log4j(Log4j是另一个流行的日志系统),如果没有找到Log4j,再使用JDK Logging。
1 2 3 4 5 6 7 8 9 10 import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;public class Person { protected final Log log = LogFactory.getLog(getClass()); void foo () { log.info("foo" ); } }
Commons Logging的日志方法,例如info()
,除了标准的info(String)
外,还提供了一个非常有用的重载方法:info(String, Throwable)
1 2 3 4 5 try { ... } catch (Exception e) { log.error("got exception!" , e); }
Log4j 配置文件
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 // log4j2.xml <?xml version="1.0" encoding="UTF-8"?> <Configuration > <Properties > <Property name ="log.pattern" > %d{MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36}%n%msg%n%n</Property > <Property name ="file.err.filename" > log/err.log</Property > <Property name ="file.err.pattern" > log/err.%i.log.gz</Property > </Properties > <Appenders > <Console name ="console" target ="SYSTEM_OUT" > <PatternLayout pattern ="${log.pattern}" /> </Console > <RollingFile name ="err" bufferedIO ="true" fileName ="${file.err.filename}" filePattern ="${file.err.pattern}" > <PatternLayout pattern ="${log.pattern}" /> <Policies > <SizeBasedTriggeringPolicy size ="1 MB" /> </Policies > <DefaultRolloverStrategy max ="10" /> </RollingFile > </Appenders > <Loggers > <Root level ="info" > <AppenderRef ref ="console" level ="info" /> <AppenderRef ref ="err" level ="error" /> </Root > </Loggers > </Configuration >
SLF4J和Logback SLF4J类似于Commons Logging,也是一个日志接口,而Logback类似于Log4j,是一个日志的实现。
在Commons Logging中,我们要打印日志,有时候得这么写:
1 2 3 int score = 99 ;p.setScore(score); log.info("Set score " + score + " for Person " + p.getName() + " ok." );
拼字符串是一个非常麻烦的事情,所以SLF4J的日志接口改进成这样了:
1 2 3 int score = 99 ;p.setScore(score); logger.info("Set score {} for Person {} ok." , score, p.getName());
如何使用SLF4J?它的接口实际上和Commons Logging几乎一模一样:
1 2 3 4 5 6 import org.slf4j.Logger;import org.slf4j.LoggerFactory;class Main { final Logger logger = LoggerFactory.getLogger(getClass()); }
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 // logback.xml <?xml version="1.0" encoding="UTF-8"?> <configuration > <appender name ="CONSOLE" class ="ch.qos.logback.core.ConsoleAppender" > <encoder > <pattern > %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern > </encoder > </appender > <appender name ="FILE" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <encoder > <pattern > %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern > <charset > utf-8</charset > </encoder > <file > log/output.log</file > <rollingPolicy class ="ch.qos.logback.core.rolling.FixedWindowRollingPolicy" > <fileNamePattern > log/output.log.%i</fileNamePattern > </rollingPolicy > <triggeringPolicy class ="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy" > <MaxFileSize > 1MB</MaxFileSize > </triggeringPolicy > </appender > <root level ="INFO" > <appender-ref ref ="CONSOLE" /> <appender-ref ref ="FILE" /> </root > </configuration >