参考教程
一些基本知识
语言类型
Java介于编译型语言和解释型语言之间,将代码编译成一种“字节码”,针对不同平台编写虚拟机,不同平台的虚拟机负责加载字节码并执行,这样就实现了“一次编写,到处运行”的效果
- 编译型语言如C、C++,代码是直接编译成机器码执行,需要考虑跨平台问题
- 解释型语言如Python、Ruby,可以由解释器直接加载源码然后运行,代价是运行效率太低
EE/SE/ME
- Java SE:Standard Edition,标准版,包含标准的JVM和标准库
- Java EE:Enterprise Edition,在Java SE的基础上加上了大量的API和库,以便方便开发Web应用、数据库、消息服务等
Java ME:Micro Edition,针对嵌入式设备的“瘦身版”,Java SE的标准库无法在Java ME上使用,Java ME的虚拟机也是“瘦身版”
Java EE > Java SE > Java ME,Java EE的应用使用的虚拟机和Java SE完全相同,而Java SE的标准库无法在Java ME上使用
1
2
3
4
5
6
7
8
9┌───────────────────────────┐
│Java EE │
│ ┌────────────────────┐ │
│ │Java SE │ │
│ │ ┌─────────────┐ │ │
│ │ │ Java ME │ │ │
│ │ └─────────────┘ │ │
│ └────────────────────┘ │
└───────────────────────────┘
JDK/JRE/JSR/JCP
JDK:Java Development Kit,JDK除了包含JRE,还提供了编译器、调试器等开发工具
JRE:Java Runtime Environment,JRE就是运行Java字节码的虚拟机
1
2
3
4
5
6
7
8
9
10
11┌─ ┌──────────────────────────────────┐
│ │ Compiler, debugger, etc. │
│ └──────────────────────────────────┘
JDK ┌─ ┌──────────────────────────────────┐
│ │ │ │
│ JRE │ JVM + Runtime Library │
│ │ │ │
└─ └─ └──────────────────────────────────┘
┌───────┐┌───────┐┌───────┐┌───────┐
│Windows││ Linux ││ macOS ││others │
└───────┘└───────┘└───────┘└───────┘JSR规范:Java Specification Request
- JCP组织:Java Community Process
Jdk中的命令
- java:这个可执行程序其实就是JVM,运行Java程序,就是启动JVM,然后让JVM执行指定的编译后的代码
- javac:这是Java的编译器,它用于把Java源码文件(以
.java后缀结尾)编译为Java字节码文件(以.class后缀结尾) - jar:用于把一组
.class文件打包成一个.jar文件,便于发布 - javadoc:用于从Java源码中自动提取注释并生成文档
- jdb:Java调试器,用于开发阶段的运行调试
编写代码
Google代码规范:Google Java Code Style
阿里巴巴代码规约IDE插件:alibaba/p3c
创建文件
- 一个
.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同。 - 使用
javac可以将.java源码编译成.class字节码 - 使用
java可以运行一个已编译的Java程序,参数是类名,如java HelloWorld而不是java HelloWorld.class或java HelloWorld.java
基本结构
- Java是面向对象的语言,一个程序的基本单位就是
class,class是关键字 - class命名使用大驼峰
- Java入口程序规定的方法必须是静态方法,方法名必须为
main,括号内的参数必须是String数组
基本数据类型
整数类型:byte(1Byte),short(2Byte),int(4Byte),long(8Byte)
浮点数类型:float(4Byte),double(8Byte)
字符类型:char(2Byte)
布尔类型:boolean
理论上存储布尔类型只需要1 bit,但JVM内部会把
boolean表示为4字节整数定义变量的时候,如果加上
final修饰符,这个变量就变成了常量。常量名通常全部大写- 有些时候,类型的名字太长,写起来比较麻烦。如果想省略变量类型,可以使用
var关键字(java 10+)
运算
- 整数的除法对于除数为0时运行时将报错,但编译不会报错;浮点数除
0时,不会报错,但会返回几个特殊值:NaN,0.0/0,表示Not a NumberInfinity,正数除以0,表示无穷大-Infinity,负数除以0,表示负无穷大
- 优先级顺序(可通过加括号确保运算顺序不出错)
()!~++--*/%+-<<>>>>>&|+=-=*=/=
- 运算结果应以运算的成员中较高精度的为准,否则编译错误
- 强制转换应该考虑溢出的问题
- 整型数可以精确表示,浮点数不行
由于浮点数存在运算误差,所以通过判断两个浮点数之差的绝对值是否小于一个很小的数来判断两浮点数是否相等,例如
Math.abs(x - 0.1) < 0.00001浮点数强转
int则舍去小数部分,若超出范围,则赋值为整数最大值- 三元运算
b ? x : y后面的类型必须相同
字符和字符串
Java在内存中总是使用Unicode表示字符
字符串是不可变的变量,变量名只是对应字符串的引用。基本类型的变量是“持有”某个数值,引用类型的变量是“指向”某个对象。
数组
- 数组所有元素初始化为默认值,整型都是
0,浮点型是0.0,布尔型是false; - 数组一旦创建后,大小就不可改变。
Arrays.toString(list)快速打印一维数组,Arrays.deepToString()打印多维数组
面向对象编程
构造函数
- 如果我们自定义了一个构造方法,那么,编译器就不再自动创建默认构造方法
- 可以在无参构造方法的第一行使用
this("Unnamed");的方式调用含参构造方法
继承和多态
- Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有
Object特殊,它没有父类。 - 任何
class的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super(); protected允许子类访问父类的字段和方法- 子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。
instanceof操作符,可以先判断一个实例究竟是不是某种类型overload重载,指对同一个函数名通过控制参数不同重载为不同函数;方法名相同,并且返回值也相同,就是Override覆写;方法名相同,方法参数相同,但方法返回值不同,也是不同的方法。在Java程序中,出现这种情况,编译器会报错。Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。虽然声明时使用的是父类,实例化用的子类,调用方法时仍然会使用子类重载后(如果有)的方法。这种运行期才能动态决定调用的子类方法的特性称为多态。
函数参数中的三点
func(int… intList)意为传入参数时可以使用func(a,b,c)去代替func(new int[]{a, b, c})的方式传入。- 用
final修饰的方法不能被Override
面向抽象编程和接口
通过
abstract定义的方法是抽象方法,它只有定义,没有实现。抽象方法定义了子类必须实现的接口规范1
2
3abstract class Person {
public abstract void run();
}
定义了抽象方法的class必须被定义为抽象类,从抽象类继承的子类必须实现抽象方法
interface,就是比抽象类还要抽象的纯抽象接口,因为它连字段都不能有。因为接口定义的所有方法默认都是public abstract的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18interface Person {
void run();
String getName();
}
class Student implements Person {
private String name;
public void run() {
System.out.println(this.name + " run");
}
public String getName() {
return this.name;
}
}一个
interface可以继承自另一个interface。interface继承自interface使用extends,它相当于扩展了接口的方法在接口中,可以定义
default方法。实现类可以不必覆写default方法。default方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。(JDK>=1.8)
静态字段和静态方法
- 用
static修饰的字段,称为静态字段。静态字段只有一个共享“空间”,所有实例都会共享该字段。使用类名.静态字段的方式访问静态字段。 - 使用
static修饰类的方法函数则该方法为静态方法
包
- 使用
package关键字声明类所在的包名称,如package thomstrong 位于同一个包的类,可以访问包作用域的字段和方法。不用
public、protected、private修饰的字段和方法就是包作用域。例如,Person和Main在同一个包下,Main中就可以直接new Person()创建实例。如果两个类不在同一个包下,则需要写出完整类名或通过
import关键字进行引用编译器查找类名方法
- 如果是完整类名,就直接根据完整类名查找这个
class 如果是简单类名,按下面的顺序依次查找:
- 查找当前
package是否存在这个class - 查找
import的包是否包含这个class - 查找
java.lang包是否包含这个class
- 查找当前
以上均为找到,编译报错
- 如果是完整类名,就直接根据完整类名查找这个
class的时候,编译器会自动帮我们做两个import动作
- 默认自动
import当前package的其他class - 默认自动
import java.lang.*
- 默认自动
当有两个
class名称相同时,其中一个可以使用import方法,另外一个只能写完整类名- 为了避免名字冲突,我们需要确定唯一的包名。推荐的做法是使用倒置的域名来确保唯一性。例如:
- org.apache
- org.apache.commons.log
- com.liaoxuefeng.sample
作用域
- 定义为
public的class、interface可以被其他任何类访问 - 定义为
private的field、method无法被其他类访问 - 定义为
protected作用于继承关系。定义为protected的字段和方法可以被子类访问,以及子类的子类 package作用域允许访问同一个package的没有public、private修饰的class,以及没有public、protected、private修饰的字段和方法。包没有父子关系,com.apache和com.apache.abc是不同的包- 尽可能把局部变量的作用域缩小,尽可能延后声明局部变量
final可以阻止class被继承、阻止方法被覆写、阻止变量被赋值
classpath 和 jar
classpath是JVM用到的一个环境变量,它用来指示JVM如何搜索class不要把任何Java核心库添加到classpath中,JVM根本不依赖classpath加载核心库
MANIFEST.MF文件可以提供jar包的信息,如Main-Class,这样可以直接运行jar包- JVM自带的标准库rt.jar不要写到classpath中,写了反而会干扰JVM的正常运行
核心类
String
String内部是通过一个char[]数组表示的,CharSequence是String的父类
Java编译器在编译期,会自动把所有相同的字符串当作一个对象放入常量池,因此相同字符串赋值的变量会指向相同的对象
Java的
String和char在内存中总是以Unicode编码表示常用函数
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// 判断字符串相等
"hello".equals("HELLO".toLowerCase()); //true
"Hello".equalsIgnoreCase("HELLO"); //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",去除了英文空格等空白符号
//java 11+
"\u3000Hello\u3000".strip(); // "Hello",除了trim的功能还将类似中文的空格字符\u3000也移除
" Hello ".stripLeading(); // "Hello "
" Hello ".stripTrailing(); // " Hello"
// 判空
"".isEmpty(); // true,因为字符串长度为0
" ".isEmpty(); // false,因为字符串长度不为0
// java 11+
" \n".isBlank(); // true,因为只包含空白字符
" Hello ".isBlank(); // false,因为包含非空白字符
// 替换子串
"abc".replace('a', 'c'); // cbc
"abc".replace("ab", "c"); // cc
"abc".replaceAll("[abc]", "z"); //zzz
// 分割字符串
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"
Integer.parseInt("123"); // 123
Integer.parseInt("ff", 16); // 按十六进制转换,255
Boolean.parseBoolean("FALSE"); // false
"Hello".toCharArray();// String -> char[]
new String(cs); // char[] -> String
// 转码
byte[] b2 = "Hello".getBytes("UTF-8"); // 按UTF-8编码转换
String s2 = new String(b2, StandardCharsets.UTF_8); // 按UTF-8转换
byte[] b2 = "Hello".getBytes("GBK"); // 按GBK编码转换
String s1 = new String(b2, "GBK"); // 按GBK转换
StringBuilder/ StringJoiner
使用循环加的方式拼接字符串时,需要频繁的创建新的字符串对象扔掉旧的字符串,浪费内存。因此使用
StringBuilder类常用函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {
sb.append(',');
sb.append(i);
}
sb.append("Bob")
.append("!")
.insert(0, "Hello, ");
String s = sb.toString();
sb.delete(0, 2); // 删除第一和第二个字符
// 使用joiner进行高效拼接,String.join()方法即使用stringJoiner实现
StringJoiner stringJoiner = new StringJoiner(",", "hi~", "!"); //StringJoiner(delimiter, [prefix, suffix])
stringJoiner.add("ming").add("hong").add("gang");
System.out.println(stringJoiner.toString()); // hi~ming,hong,gang!
枚举类 enum
使用
enum关键字来定义枚举类型枚举类型是一个
class,不能与非枚举类进行比较或向类型转换枚举类型变量也是引用类型变量,但是可以使用
==去判断相等,且可以用在switch语句中枚举类型无法被继承
常用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 一般定义
enum Weekday {
SUN, MON, TUE, WED, THU, FRI, SAT;
}
String s = Weekday.SUN.name(); // "SUN"
int n = Weekday.MON.ordinal(); // 返回MON的位置,1
// 带public方法的定义
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;
}
}
Weekday day = Weekday.SUN;
System.out.println(day.dayValue); // 0
包装类型
基本类型不可赋值为null,而引用类型可以
直接把
int变为Integer的赋值写法Integer n = 100,称为自动装箱(Auto Boxing),反过来,把Integer变为int的赋值写法int i = n,称为自动拆箱(Auto Unboxing)。自动装箱和自动拆箱只发生在编译阶段,目的是为了少写代码。装箱和拆箱会影响代码的执行效率,因为编译后的
class代码是严格区分基本类型和引用类型的。并且,自动拆箱执行时可能会报NullPointerException对于
Integer对象,若值较小,由于缓存优化的原因,可能会使得==判断为true
JavaBean
- 一种特殊的遵循一定编程原则的Java类,它通常用来实现一些比较常用的简单功能,并可以很容易的被重用或者是插入其他应用程序中去
- Bean的编写规范包括Bean类的构造方法、定义属性和访问方法(
getter/setter)编写规则
Java常用工具包
Math:数学计算
1
Math.random(); // 生成[0,1)间的随机数
Random:生成伪随机数
1
2Random rd = new Random(); // 不指定随机种子,以当前时间戳为种子,得到与运行时间相关的固定随机序列
Random rd = new Random(1234); // 指定随机种子,获得固定的随机序列
SecureRandom:生成安全的随机数
SecureRandom的安全性是通过操作系统提供的安全的随机种子来生成随机数。这个种子是通过CPU的热噪声、读写磁盘的字节、网络流量等各种随机事件产生的“熵”1
2
3SecureRandom sr = new SecureRandom();
System.out.println(sr.nextInt(100));
SecureRandom.getInstanceStrong(); // 获取高强度安全随机数生成器
异常处理
Throwable是异常体系的根,它继承自Object。Throwable有两个体系:Error和Exception,Error表示严重的错误,程序对此一般无能为力
- 必须捕获的异常或者用
throws声明,包括Exception及其子类,但不包括RuntimeException及其子类,这种类型的异常称为Checked Exception。 - 不需要捕获的异常,包括
Error及其子类,RuntimeException及其子类。 - 可以用
try ... catch捕获。把可能发生异常的语句放在try { ... }中,然后使用catch捕获对应的Exception及其子类。多个catch语句只有一个能被执行,因此编写代码时需要考虑好catch的顺序,将子类写在前面,父类写在后面。 finally语句不是必须的,可写可不写;finally总是最后执行且无论是否发生异常,因此有finally存在return将以finally中的return为准。- 当
catch和finally都抛出了异常时,catch中的异常将会被屏蔽掉 - 通过
printStackTrace()可以打印出方法的调用栈 BaseException需要从一个适合的Exception派生,通常建议从RuntimeException派生- 要执行
assert语句,必须给Java虚拟机传递-enableassertions(可简写为-ea)参数启用断言。断言只应该用于开发和测试阶段。多数时候用在单元测试中。 Logger logger = Logger.getGlobal();可以使用这样的方式声明java jdk 自带的logger,但是大多情况下不使用使用这种方法,更多使用Log4j