01 Java程序基础
1、类名规则:必须以英文字母开头,后接字母、数字及下划线
2、基本数据类型:
整数类型:byte,short,int,long
浮点数类型:float,double
字符类型:char
布尔类型:boolean
3、var关键字:有些类型的名字太长,可以用var进行简化
例如:var sb = new StringBuffer();
4、溢出:整数运算存在范围,溢出后不会出错,但会得到一个奇怪的值
5、整数运算永远精确,浮点运算常常无法精确表示,比较两浮点数是否相等通常比较他们的绝对值之差是否小于某一个特定值(0.0001)。
6、浮点数强制转型为整数时,小数部分会被直接砍掉。 12.7→12;12.3→12。
7、整数和浮点数一起运算时,会被自动提升为浮点数,但是两个整数之间的运算不会被自动提升。
例如:double x = 1.2 + 24/5 结果为5.2
8、布尔运算是短路运算,即能提前确定结果时,后续的运算将不再执行。
9、char类型占用两个字节,无论中文还是英文的。
10、String字符串和其他数据类型“+”连接时,会自动将其他数据类型转换为字符串。
11、null和“”空字符串有区别,空字符串是一个有效的字符串对象,不等价于null。
12、Java数组的特点:
数组所有元素初始化为默认值,整型都是0,浮点型是0.0,布尔型是false;
数组一旦创建后,大小就不可改变。
数据索引从0开始。
02 流程控制
1、格式化输出采用System.out.printf,%表示占位符,%%表示符号%本身。
2、”==“表示引用是否相等,即是否指向同一个对象。
例如:两个值相同的String对象,但在“==”下判断结果为false。
判断引用类型的内容是否相等,必须使用equal()方法。
3、使用switch时,case语句具有“穿透性”,漏写break会将匹配的case全部执行。
4、switch进行字符串匹配时,是比较内容相等。
5、Java12中,switch可以使用模式匹配的方式,保证只有一条路径被执行。
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");
}
}
}
6、遍历数组中的每个元素,用for each更简单。
public class Main {
public static void main(String[] args) {
int[] ns = { 1, 4, 9, 16, 25 };
for (int n : ns) {
System.out.println(n);
}
}
}
7、break通常配合if使用,跳出自己所在的那一层循环。
03 数据操作
1、for循环可以访问数据索引,for each循环直接迭代每个数组元素,但无法获取索引。
2、标准库Arrays.toString()可以快速打印一维数组内容。
例如: int[] ns = {1, 2, 3};
System.out.println(Arrays.toString(ns));
3、标准库Arrays.sort()可以排序。
4、标准库Arrays.deepToString()可以快速打印多维数据内容。
5、main方法是Java程序的入口,可以接收一个命令行参数,是一个String[]数组。
04 面向对象基础
1、为了避免field被外部外码直接访问,通常用private修饰field,来保持封装性。
2、类中的方法可以用private修饰,由类内部方法调用。
3、如果有局部变量和字段重复,则字段必须加上this从而区分。
4、可变参数,相当于数组类型。
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"); // 传入3个String
g.setNames("Xiao Ming", "Xiao Hong"); // 传入2个String
g.setNames("Xiao Ming"); // 传入1个String
g.setNames(); // 传入0个String
5、如果为类自定义了一个构造方法,那么编译器将不再自动创建默认构造方法。
6、没有在构造方法中初始化的字段,引用类型的字段默认是null,数值类型的字段用默认值,int类型默认值是0,布尔类型默认值是false
7、方法重载是指多个方法的方法名相同,但是参数不同。
8、Java中使用extends来实现继承。
9、Java只允许单继承,一个class继承一个父类。只有object特殊,没有父类,所有类的根类都是object类。
10、子类无法访问父类用private修饰的字段,但是可以访问protected修饰的字段。
11、super关键字表示父类,子类引用父类的字段时,可以使用super.fieldname。
某些情况下,比如使用super关键字。
例如:
public class Main {
public static void main(String[] args) {
Student s = new Student("Xiao Ming", 12, 89);
}
}
class Person {
protected String name;
protected int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
this.score = score;
}
}
上面程序会出现编译错误,即在Student的构造方法中,无法调用Person的构造方法。
在Java中,任何class的构造方法中,第一句必须是调用父类的构造方法,如果没有编译器会自动加一句super(),但是Person中没有无参数构造方法,所以编译错误。
正确程序如下:
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
super(name, age); // 调用父类的构造方法Person(String, int)
this.score = score;
}
}
12、子类不会继承父类的任何构造方法,都是由编译器自动生成的。
13、向下转型时,不能将父类转为子类。
14、Java提供instanceof操作符,来判断某个实例是否是某种类型。
Person p = new Student();
if (p instanceof Student) {
// 只有判断成功才会向下转型:
Student s = (Student) p; // 一定会成功
}
15、覆写:在继承关系中,子类中定义一个与父类方法完全相同(参数也相同)的方法,即为覆写;加上@Override可以让编译器帮助检查是否覆写正确,但不是必需的。
16、Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。
public class Main {
public static void main(String[] args) {
Person p = new Student();
p.run(); // 应该打印Person.run还是Student.run?
}
}
class Person {
public void run() {
System.out.println("Person.run");
}
}
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run”); //调用的是Student的run方法!!!!
}
}
17、多态:针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。
18、所有类都继承自Object类,其定义了几个重要方法:
toString():把instance输出为String;
equals():判断两个instance是否逻辑相等;
hashCode():计算一个instance的哈希值。
19、super:在子类中,如果要调用父类的被覆写的方法,可以通过super来调用。
20、final:如果一个父类不允许子类对它的某个方法进行覆写,则用final修饰。final修饰的方法无法被Override。
用final修饰的实例化字段,不能再次被初始化。
21、abstract:一个方法用abstract修饰,则其为抽象方法,没有具体的执行语句;其所在的类也必须声明为抽象类,无法被实例化。
22、抽象类本身被设计成只能用于继承,子类必须实现其定义的抽象方法,否则编译会报错。
23、接口:如果一个抽象类没有字段,所有方法都是抽象方法,则可以将其改为接口。其所有方法默认是public abstract的,可写可不写。
24、在Java中,一个类不能继承自多个类,但是可以实现多个接口。
25、接口继承:一个接口可以继承另一个接口,使用extends关键字。
26、接口中的default方法:实现类可以不覆写default方法。
27、静态字段:用static修饰的字段,被所有实例所共享。推荐用类用来访问静态字段,例如Person.number;
28、静态方法:静态方法不属于class实例,其内部不能访问this变量和实例字段,只能访问静态字段。只能用类名来调用。
29、interface不能有实例字段,但可以有静态字段,且必须为final类型,例如public static final int MALE = 1;
30、真正完整的类名是包名.类名.
31、Java编译器最终编译出的.class文件只使用完整类名,因此,在代码中,当编译器遇到一个class名称时:
如果是完整类名,就直接根据完整类名查找这个class;
如果是简单类名,按下面的顺序依次查找:
查找当前package是否存在这个class;
查找import的包是否包含这个class;
查找java.lang包是否包含这个class
32、编写class的时候,编译器会自动帮我们做两个import动作:
默认自动import当前package的其他class;
默认自动import java.lang.*。
33、为了避免名字冲突,我们需要确定唯一的包名。推荐的做法是使用倒置的域名来确保唯一性。例如:
org.apache
com.liaoxuefeng.sample
34、Java中,修饰符用来限制访问域。
Public:class和interface,可以被任何类访问;method和field在有访问class的权限下,可以被任何类访问。
Private:method和field无法被其他类访问;但是嵌套的内部类有访问private的权限。
Protected:作用于继承关系,可以被子类或子类的子类访问。
Default:允许访问同一package内没有public、private修饰的class,以及没有public、private、protected修饰的method和field。
35、局部变量:方法内部定义的变量称为局部变量,其作用于从变量声明处开始到对应的块结束。
36、final作用域:
修饰class防止被继承。
修饰method防止被覆写。
修饰field防止被重新赋值。
修饰局部变量防止被重新赋值。
37、一个.java文件只能包含一个public类,但可以包含多个非public类;如果有public类,文件名必须与类名相同。
38、创建jar包:压缩文件制作一个zip,将后缀.zip改为.jar。
05 Java核心类
1、String:内部通过char[]数组来实现。
2、字符串内容比较必须使用equals()而不能使用==。
Java在编译期会将所有相同的字符串当做一个对象放入常量池,所以使用==可能碰巧会正确,但实质上并不相同。
要忽略大小写比较使用equalsIgnoreCase()方法。
3、String类下常用方法:
搜索子串
// 是否包含子串:
"Hello".contains("ll"); // true
"Hello".indexOf("l"); // 2
"Hello".lastIndexOf("l"); // 3
"Hello".startsWith("He"); // true
"Hello".endsWith("lo"); // true
提取子串
"Hello".substring(2); // "llo"
"Hello".substring(2, 4); "ll"
去除首尾空白字符
" \tHello\r\n ".trim(); // “Hello"
"\u3000Hello\u3000".strip(); // "Hello"
" Hello ".stripLeading(); // "Hello "
" Hello ".stripTrailing(); // " Hello”
"".isEmpty(); // true,因为字符串长度为0
" ".isEmpty(); // false,因为字符串长度不为0
" \n".isBlank(); // true,因为只包含空白字符
" Hello ".isBlank(); // false,因为包含非空白字符
替换子串
String s = "hello";
s.replace('l', 'w'); // "hewwo",所有字符'l'被替换为'w'
s.replace("ll", "~~"); // "he~~o",所有子串"ll"被替换为"~~”
String s = "A,,B;C ,D";
s.replaceAll("[\\,\\;\\s]+", ","); // "A,B,C,D"
分隔字符串
String s = "A,B,C,D";
String[] ss = s.split("\\,"); // {"A", "B", "C", "D"}
拼接字符串
String[] arr = {"A", "B", "C"};
String s = String.join("***", arr); // "A***B***C"
类型转换
String.valueOf(123); // "123"
String.valueOf(45.67); // "45.67"
String.valueOf(true); // "true"
String.valueOf(new Object()); // 类似
int n1 = Integer.parseInt("123"); // 123
int n2 = Integer.parseInt("ff", 16); // 按十六进制转换,255
boolean b1 = Boolean.parseBoolean("true"); // true
boolean b2 = Boolean.parseBoolean("FALSE"); // false
Integer.getInteger("java.version"); // 版本号,11
4、字符编码:Java的String和char在内存中总是以Unicode编码表示。转换为byte[]时,始终优先考虑UTF-8编码。
5、String内容是无法修改的,每次重新赋值都是将旧的字符串扔掉,创建新的字符串对象,绝大多数字符串都是临时对象,不但浪费内存,还会影响GC效率。
6、StringBuilder是一个可变对象,可以预分配缓冲区,新增字符时不会创建新的临时对象。
// 链式操作
public class Main {
public static void main(String[] args) {
var sb = new StringBuilder(1024);
sb.append("Mr ")
.append("Bob")
.append("!")
.insert(0, "Hello, ");
System.out.println(sb.toString());
}
}
7、链式操作的实现:定义的每个方法都返回this,这样就可以不断调用自身的其他方法。
public class Main {
public static void main(String[] args) {
Adder adder = new Adder();
adder.add(3)
.add(5)
.inc()
.add(10);
System.out.println(adder.value());
}
}
class Adder {
private int sum = 0;
public Adder add(int n) {
sum += n;
return this;
}
public Adder inc() {
sum ++;
return this;
}
public int value() {
return sum;
}
}
8、高效拼接字符串数组,用StringJoiner,指定分隔符、开头和结尾。
public class Main {
public static void main(String[] args) {
String[] names = {"Bob", "Alice", "Grace"};
var sj = new StringJoiner(", ", "Hello ", "!");
for (String name : names) {
sj.add(name);
}
System.out.println(sj.toString());
}
}
不需要开头和结尾时,用String.join()更方便。
String[] names = {"Bob", "Alice", "Grace"};
var s = String.join(", ", names);
9、包装类型
int和Integer相互转换
int i = 100;
Integer n = Integer.valueOf(i);
int x = n.intValue();
10、通常把一组对应的读方法(getter)和写方法(setter)称为属性(property)。例如,name属性:
对应的读方法是String getName()
对应的写方法是setName(String)
只有getter的属性称为只读属性(read-only),例如,定义一个age只读属性:
对应的读方法是int getAge()
无对应的写方法setAge(int)
类似的,只有setter的属性称为只写属性(write-only)。
11、枚举类:为了让编译器检查某个值在枚举的集合内,不同用途的枚举需要用不同的类型来标记,不能混用,用enum定义枚举类。
12、引用类型的比较都要用equals(),但是enum类例外,每个常量在JVM中只有一个实例,可以用==比较。
13、枚举
public class Main {
public static void main(String[] args) {
Weekday day = Weekday.SUN;
if (day.dayValue == 6 || day.dayValue == 0) {
System.out.println("Today is " + day + ". Work at home!");
} else {
System.out.println("Today is " + day + ". Work at office!");
}
}
}
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;
}
}
也可以用switch
public class Main {
public static void main(String[] args) {
Weekday day = Weekday.SUN;
switch(day) {
case MON:
case TUE:
case WED:
case THU:
case FRI:
System.out.println("Today is " + day + ". Work at office!");
break;
case SAT:
case SUN:
System.out.println("Today is " + day + ". Work at home!");
break;
default:
throw new RuntimeException("cannot process " + day);
}
}
}
enum Weekday {
MON, TUE, WED, THU, FRI, SAT, SUN;
}
14、BigInteger可以计算超过long范围的整数。
BigInteger i1 = new BigInteger("1234567890");
BigInteger i2 = new BigInteger("12345678901234567890");
BigInteger sum = i1.add(i2); // 12345678902469135780
15、BigDecimal可以表示一个任意大小且精度完全准确的浮点数。
16、Java提供的常用工具类有:
Math:数学计算
Random:生成伪随机数
SecureRandom:生成安全的随机数
06 异常处理
Java使用异常来表示错误,并通过try ... catch捕获异常;
Java的异常是class,并且从Throwable继承;
Error是无需捕获的严重错误,Exception是应该捕获的可处理的错误;
RuntimeException无需强制捕获,非RuntimeException(Checked Exception)需强制捕获,或者用throws声明;
不推荐捕获了异常但不进行任何处理。
1、从继承关系可知: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及其子类。
2、捕获异常使用try...catch语句,把可能发生异常的代码放到try {...}中,然后使用catch捕获对应的Exception及其子类