第 1 章 IDEA
1.1 IDEA 快捷键
说明 | 快捷键 |
---|---|
使用 xx 块环绕 surround with | Ctrl + Alt + t |
生成 getter / setter / 构造器等类内部结构 | Alt + Insert |
复制指定行代码 | Ctrl + d |
删除指定行代码 | Ctrl + y |
向上 / 向下移动代码 | Alt + Shift + ⬆ / ⬇ |
批量修改变量名、方法名、类名 | Shift + F6 |
重写父类的方法 | Ctrl + o |
实现接口的方法 | Ctrl + i |
查看指定代码的源码 | Ctrl + 选中指定内容 / Ctrl + n |
1.2 IDEA 调试程序(Debug)
- 添加断点
在源代码文件中,在想要设置断点的代码行的前面的标记行处,单击鼠标左键就可以设置断点,在相同位置 再次单击即可取消断点。
- 启动调试
启动调试后,代码会自动运行到断点行处。因此我们一般将断点设置在疑似出错方法的前面。
- 单步执行
Step Over(F8):进入下一步,如果当前行断点是调用一个方法,则不进入当前方法体内
Step Into(F7):进入下一步,如果当前行断点是调用一个自定义方法,则进入该方法体内
Step Out :跳出当前方法体
Resume Program(F9):使程序停在下一个断点上
Stop:结束调试
View Breakpoints:查看所有断点
Mute Breakpoints:使得当前代码后面所有的断点失效, 一下执行到底
- 观察变量和执行流程
1.3 IDEA 集成 Git
点击 「VCS」-> 「Share Project on GitHub」-> 「Add account」->「Log In via GitHub…」-> 「浏览器点击 Authorize in GitHub」-> 「认证成功后回到 IDEA」-> 「Share」
之后提交代码「Git」-> 「Push」,拉取代码 「Git」-> 「Fetch」,下载别人的项目「Git」-> 「Clone」。
1.4 IDEA 集成 Gitee
安装 Gitee 插件 -> 点击 「VCS」-> 「Share Project on Gitee」-> 「Add account」-> 「Log In via Gitee…」-> 「浏览器点击 Authorize in GitHub」-> 「认证成功后回到 IDEA」-> 「Share」
第 2 章 变量与运算符
2.1 关键字
权限修饰关键字 | 说明 |
---|---|
private | ![]() |
protected | |
public | |
循环关键字 | 说明 |
break | 一旦执行,就结束当前循环结构 |
continue | 一旦执行,就跳出当次循环开始下一次循环 |
继承关键字 | 说明 |
this | this 调用该类中的属性、方法、构造器 |
super | super 调用父类中的属性、方法、构造器 |
2.1.1 static
- static + 成员变量 = 类变量。类变量被类的所有实例共享,只在类加载的准备阶段分配一次内存空间
- static + 成员方法 = 静态方法。静态方法不属于任何实例对象,所以不能使用 this 调用
- static + 代码块 = 静态代码块。静态代码块在类加载的初始化阶段执行
- static + 内部类 = 静态内部类。静态内部类可以访问外部类中的静态成员
个数 | 内存位置 | 加载时间 | 调用者 | 消亡时间 | |
---|---|---|---|---|---|
静态变量 | 内存空间只有一份 | 堆 | 随着类的加载而加载 | 可以被类和使用对象调用 | 随着类的卸载而消亡 |
实例变量 | 每一个实例对象都有一份 | 堆的对象实体中 | 随着对象的创建而加载 | 只能被使用对象调用 | 随着对象的消亡而消亡 |
2.1.2 final
- final 修饰类时,该类不能被继承
- final 修饰方法时,该方法不能被重写
- final 修饰变量时,一旦获得了初始值,该变量的值就不能被重新赋值(所以 Java 语法规定 final 修饰的成员变量必须由程序员显示的指定初始值)
- static 和 final 共同修饰的常量称为全局常量
2.2 标识符
标识符是 Java 中变量、方法、类等命名时使用的字符序列。
2.2.1 标识符的命名规则
- 英文字母大小写,数字 0-9,下划线 _ 或 $ 组成
- 不可以直接使用关键字和保留字
- 不可以以数字开头
- 不可以包含空格
- 严格区分大小写
2.2.2 标识符的命名规范
- 包名:多单词组成时所有字母都小写 xxxyyyzzz
- 类名、接口名:多单词组成时首字母大写 XxxYyyZzz (大驼峰)
- 变量名、方法名:多单词组成时第一个单词首字母小写,之后首字母都大写 xxxYyyZzz(小驼峰)
- 常量名:所有字母都大写,多单词组成时用下划线连接 XXX_YYY_ZZZ
2.3 变量
变量是内存中的一个存储区域,该区域的数据可以在同一类型范围内不断变化。变量只在作用域内是有效的,出了作用域就失效了。Java 中变量声明格式为:数据类型 变量名 = 变量值; Java 中变量按照数据类型来区分:
2.3.1 基本数据类型(8种)
基本数据类型 | 位数 | 包装器类型 | 说明 |
---|---|---|---|
byte | 1个字节(byte),8位(bit) | Byte | -128 ~ 127 |
short | 2 个字节,16 位 | Short | |
int | 4 个字节,32 位 | Integer | 整型常量默认都是 int 类型 |
long | 8 个字节,64 位 | Long | 声明 long 类型变量时,后缀为 L 或者 l |
float | 4 个字节,32 位 | Float | |
double | 8 个字节,64 位 | Double | 浮点型常量默认都是 double 类型 |
char | 2 个字节,16 位 | Character | 使用单引号括起来的单个字符 |
boolean | Boolean | 只有 true 和 false 两种值 |
注意: 我们不谈 boolean 类型占用的内存空间大小,但在真正内存中 boolean 在编译之后都使用 JVM 中的 int 数据类型代替(true 用 1 表示,false 用 0 表示),所以是 4 个字节。
2.3.2 基本数据类型变量间的运算规则
- 自动类型提升
通常来说,数据范围小的变量与数据范围大的变量做运算时,结果自动转换为数据范围大的数据类型。
// byte、short、char 类型之间的变量做运算,结果为 int 类型
byte、short、char → int → long → float → double
- 强制类型转换
如果需要将数据范围大的变量转换为数据范围小的变量数据类型,需要使用强制类型转换 () 。在 () 指明要转换为的数据类型。
2.3.3 装箱和拆箱
装箱就是基本数据类型转换为包装器类型,拆箱就是包装器类型转换为基本数据类型。
-
基本数据类型: Java 将基本数据类型创建的变量值直接存储在栈中,更加高效。
-
包装器类型:根据 Java “万物皆对象” 的思想,为了让基本数据类型也具备对象的特性,因此使用包装器类型将基本数据类型给封装起来,并为其添加了属性和方法。包装器类型的对象放在堆中。
- 包装器类型可以定义泛型,基本数据类型不行
- 包装器类型都实现了 Serializable 接口,支持序列化和反序列化
- 基本数据类型的初始值为对应数据类型的零值,而包装类型的初始值为 null
2.3.4 ASCII 码表
字符 | 十进制 | 二进制 |
---|---|---|
0 | 48 | 00110000 |
9 | 57 | 00111001 |
A | 65 | 01000001 |
Z | 90 | 01011010 |
a | 97 | 01100001 |
z | 122 | 01111010 |
第 3 章 流程控制语句
3.1 switch case
switch(表达式){
case 常量值1:
语句1;
break;
case 常量值2:
语句2;
break;
...
default:
语句n;
}
注意:
- switch 中的表达式只能是如下几种数据类型:byte / short / char / int / String / 枚举
- case 后必须是常量,而不能是判断表达式
- break 用于跳出当前 switch-case 结构,如果没有 break 会继续执行之后的 case 语句,直到遇到 break 或结束
3.2 for 循环
// for 循环执行过程:初始化条件 → 循环条件 → 循环体 → 循环迭代
for(初始化条件;循环条件;循环迭代){
循环体;
}
3.3 while 循环
// while 循环执行过程:初始化条件 → 循环条件 → 循环体 → 循环迭代
初始化条件;
while(循环条件){
循环体;
循环迭代; // 注意 while 的循环迭代条件一定不能忘,否则进入死循环
}
3.4 do - while 循环
// do-while 循环执行过程:初始化条件 → 循环体 → 循环迭代 → 循环条件
初始化条件;
do{
循环体;
循环迭代;
}while(循环条件)
第 4 章 数组
4.1 数组特点
- 数组本身是引用数据类型,而数组中的元素可以是任何数据类型,包括基本数据类型和引用数据类型
- 创建数组对象会在内存中开辟一整块连续的空间,数组名中引用的是这块连续空间的首地址。占据空间大小,取决于数组的长度和数组中元素的类型
- 数组中的元素在内存中是依次紧密排列的,有序的
- 数组一旦初始化完成,其长度就不能修改
4.2 一维数组
- 一维数组的静态初始化:数组变量的初始化和数组元素的赋值操作同时进行
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3,...};
2. 一维数组的动态初始化:数组变量的初始化和数组元素的赋值操作分开进行
数据类型[] 数组名 = new 数据类型[数组长度];
3. 一维数组的遍历
public class ArrayTest {
public static void main(String[] args) {
int[] arr = new int[]{1, 2, 3, 4, 5};
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
public class ArrayTest {
public static void main(String[] args) {
int[] arr = new int[]{1, 2, 3, 4, 5};
for (int j : arr) {
System.out.println(j);
}
}
}
4. 一维数组的内存解析
① main 方法进入方法栈中执行
② JVM 在堆内存中开辟空间,存储该数组
③ 数组有自己的内存地址,以十六进制数表示
④ JVM 将内存首地址赋值给引用类型变量 arr (arr 保存的是数组内存地址,所以是引用类型变量)
4.3 二维数组
- 二维数组的静态初始化
数据类型[][] 数组名 = new 数据类型[][]{{元素1,元素2,元素3},{元素4,元素5,元素6},...};
2. 二维数组的动态初始化
数据类型[][] 二维数组名 = new 元素的数据类型[m][n];
3. 二维数组的遍历
public class ArrayTest {
public static void main(String[] args) {
int[][] arr = new int[3][5];
for(int i = 0; i<arr.length; i++){
for(int j = 0; j < arr[i].length; j++){
System.out.print(arr[i][j]);
}
System.out.println();
}
}
}
public class ArrayTest {
public static void main(String[] args) {
int[][] arr = new int[3][5];
for (int[] ints : arr) {
for (int anInt : ints) {
System.out.print(anInt);
}
System.out.println();
}
}
}
4.4 Arrays 工具类
Arrays 静态方法 | 功能 |
---|---|
binarySearch(Object[] a, Object key) | 有序数组中查找 key 是否存在,如果存在返回第一次找到的下标,不存在返回负数 |
copyof(object[] original, int n) | 新建一个数组,元素为原数组 original 的前 n 个元素 |
copyofRange(object[] original,int from,int to) | 新建一个数组,元素为原数组 original 的 [from,to) 范围内的元素 |
equals(Object[] a1,Object[] a2) | 判断两个数组是否相等,相等为 true |
deepToString(Object[] arr) | 返回二维数组 arr 中一维数组中的值 |
fill(Object[] arr, Object val) | 数组 arr 中所有元素赋值为 val |
fill(Object[] arr, int from, int to, Object val) | 数组 arr 中索引 from 到 to 的元素赋值为 val |
sort(Object[] arr) | 根据指定顺序对 arr 中的元素进行排序 |
setAll(T[] array, IntFunction generator) | arr 每一个元素替换为给定操作后的元素 |
toString(Object[] arr) | 返回一维数组 arr 中的值 |
// Arrays.setAll
public class ArrayTest {
public static void main(String[] args) {
double[] arr = {1.0, 2.0, 3.0};
// 求数组中元素的平方
Arrays.setAll(arr, i -> arr[i] * arr[i]);
System.out.println(Arrays.toString(arr));
}
}
第 5 章 String
5.1 字符串常量池
字符串常量保存在堆空间的字符串常量池中。字符串常量池底层是一个固定大小的 Hashtable,保证字符串常量池中不允许存放两个相同的字符串。
// 在堆中的字符串常量池中新建 "a" 字符串
String s1 = "a";
// 没有新建对象,将 "a" 的引用给 s2,s1 = s2
String s2 = "a";
// 新建两个对象,先在字符串常量池中新建 "b" 字符串,再在堆内存中新建一个存储 "b" 的对象地址,地址引用给 s3
String s3 = new String "b";
// 新建一个对象,常量池已经有 "a" 了,只在堆内存中新建一个存储 "a" 的对象地址,地址引用给 s4。s4 和 s1、s2 都不相等
String s4 = new String "a";
5.2 字符串拼接
- 字符串常量和常量的拼接结果在常量池中
- 只要有一个是字符串变量,拼接的结果就在堆中
- String.intern() 是一个 Native 方法。拼接结果调用 intern() 方法,如果字符常量池中已经包含一个等于此 String 对象的字符串,则返回常量池中字符串的引用;否则将新的字符串放入常量池,并返回新字符串的引用
5.3 String 与其他结构间的转换 API
- 字符串 -> 基本数据类型、包装类: Integer 包装类的 public static int parseInt(String s) 可以将由“数字”字符组成的字符串转换为整型。Byte、Short、Long、Float、Double类似。
- 基本数据类型、包装类 -> 字符串:
String 类的 public String valueOf(int n) 可将 int 类型转换为字符串。valueOf(byte b)、valueOf(long l)、valueOf(double d)、valueOf(boolean b)类似。
- 字符数组 -> 字符串:
char chars[] = {'a', 'b', 'c', 'd', 'e'};
String s1 = new String(chars); // s1 = "abcde"
String s2 = new String(chars, 0, 3); // s2 = "abc"
4. 字符串 -> 字符数组:
String s = "hello";
char[] chars = s.toCharArray();
5. 字符串 -> 字节数组:
String s = "hello";
byte[] bytes = s.getBytes();
5.4 String 常见 API
String s = "hello";
获取 API | 功能 |
---|---|
s.length() | 返回字符串的长度 |
s.charAt(int index) | 返回 index 处字符 |
s.indexOf(String str) | 返回字符串 str 在字符串中第一次出现的索引,若不存在返回 -1 |
s.lastIndexOf(String str) | 与 indexOf() 类似,但是是从字符串后面向前查找 |
s.substring(int beginIndex) | 返回索引 beginIndex 开始的子串 |
s.substring(int beginIndex int endIndex) | 返回索引 beginIndex 开始到 endIndex - 1 的子串 |
s.subSequence(int beginIndex, int endIndex) | 返回索引 beginIndex 开始到 endIndex - 1 的子序列 |
s.compareTo(String str) | 比较 s 和 str 的字典顺序,如果小于返回一个负数;等于返回 0;大于返回一个正数 |
s.compareToIgnoreCase(String str) | 忽略大小写,比较 s 和 str 的字典顺序 |
判断 API | 功能 |
---|---|
s.contains(CharSequence str) | 是否包含指定字符序列 str |
s.isEmpty() | 字符串 s 是否为空 |
s.isBlank() | 字符串 s 是否为空或长度为 0 或由空白符构成 |
s.startsWith(String prefix) | 字符串是否是以指定内容 prefix 开头 |
s.endsWith(String suffix) | 字符串是否是以指定内容 suffix 结尾 |
s.equals(Object anObject) | 字符串内容是否相同 |
s.equalsIgnoreCase(String str) | 字符串内容是否相同,并忽略大小写 |
s.contentEquals(StringBuffer sb) | 字符串与指定的 StringBuffer 内容是否相同 |
s.matches(String regex) | 字符串是否包含给定的正则表达式 regex |
转换 API | 功能 |
---|---|
s.toUpperCase() / s.toLowerCase() | 将字符串 s 转换成大写 / 小写 |
s.trim() | 将字符串 s 两端的多余空格去除 |
s.replace(char oldChar, char newChar) | s 中所有 oldChar 字符用 newChar 字符替换,并返回替换后的新字符串 |
s.replaceFirst(String regex, String replacement) | s 中第一个出现的正则表达式 regex 字符用 replacement 字符替换 |
s.replaceAll(String regex, String replacement) | 将 s 的每个正则表达式 regex 替换为 replacement |
s.split(String regex, int limit) | 将 s 按照正则表达式分隔符 regex 分割成 limit 个字符串 |
5.5 StringBuffer 和 StringBuilder
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。StringBuffer 和 StringBuilder 类的字符串对象能够被多次修改,并且不产生新的字符串对象。
- StringBuffer:可变的字符序列;线程安全(方法有 synchronized 修饰),效率低;
- StringBuilder :可变的字符序列;线程不安全,效率高;底层使用 char[] 数组存储,默认容量 capacity = 16 ,扩容后容量默认是 2n + 2。如果开发中明确要操作字符个数,在构造时最好直接指定容量。
StringBuilder sb = new StringBuilder();
API | 功能 |
---|---|
sb.append(Object o) | sb 后面拼接 Object 类型参数 o,Object 可以是 int、long、double、char[]、String 等类型 |
sb.charAt(int index) | 返回 sb 中索引 index 处的 char 值 |
sb.delete(int start, int end) | 删除 sb 中索引 start 到 end - 1 的字符 |
sb.deleteCharAt(int index) | 删除 sb 中索引 index 处的字符 |
sb.insert(int offset, Object o) | sb 中索引 offset 处插入 Object 类型参数 o |
sb.indexOf(String s) | 返回给定字符串 s 在 sb 中第一次出现的索引 |
sb.lastIndexOf(String s) | 返回给定字符串 s 在 sb 中最后一次出现的索引 |
sb.length() | 返回 sb 的字符数量 |
sb.reverse() | 反转 sb |
sb.replace(int start, int end, String s) | 给定字符串 s 中的字符替换 sb 中索引 start 到 end - 1 的字符 |
sb.setCharAt(int index, char ch) | 设置索引 index 处的字符为 ch |
sb.setLength(int newLength) | 设置 sb 的长度 |
sb.substring(int start, int end) | 返回 sb 中索引 start 到 end - 1 的子串 |
sb.subSequence(int start, int end) | 返回 sb 中索引 start 到 end - 1 的子序列 |
sb.toString() | 返回 sb 的字符串形式 |
第 6 章 面向对象编程(基础)
类 (Class) 和对象 (Object) 是面向对象的核心概念。类是具有相同特征的事物的抽象描述,是抽象的定义。对象是具体实际存在的该类事物的每个个体,也称为实例 (instance) 。
6.1 成员变量和局部变量的区别
- 声明位置和方式 (1)实例变量:在类中方法外 (2)局部变量:在方法体 {} 中或方法的形参列表、代码块中
- 在内存中存储的位置不同 (1)实例变量:堆 (2)局部变量:栈
- 生命周期 (1)实例变量:和对象的生命周期一样,随着对象的创建而存在,随着对象被 GC 回收而消亡,而且每一个对象的实例变量是独立的 (2)局部变量:和方法调用的生命周期一样,每一次方法被调用而在存在,随着方法执行的结束而消亡,而且每一次方法调用都是独立
- 作用域 (1)实例变量:通过对象就可以使用,本类中直接用 this 调用,其他类中通过 “对象.实例变量” 调用 (2)局部变量:出了作用域就不能使用
- 默认值 (1)实例变量:有默认值 (2)局部变量:没有默认值,必须手动初始化
6.2 方法的内存解析
- 方法没有被调用的时候,都在方法区中的字节码文件中存储
- 方法被调用的时候,需要进入到栈内存中运行。方法每调用一次就会在栈中有一个入栈动作,即给当前方法开辟一块独立的内存区域,用于存储当前方法的局部变量的值
- 当方法执行结束后,会释放该内存,称为出栈,如果方法有返回值,就会把结果返回调用处,如果没有返回值,就直接结束,回到调用处继续执行下一条指令
6.3 方法重载
方法重载:在同一个类中允许存在一个以上的同名方法,只要它们的参数列表不同(参数个数、参数类型或参数顺序不同)即可。方法重载与修饰符、返回值类型无关。
6.4 值传递
Java 里方法的参数传递方式只有值传递一种,即将实际参数值的副本传入方法内,而参数本身不受影响。
- 形参是基本数据类型:将实参基本数据类型变量的 “数据值” 传递给形参
- 形参是引用数据类型:将实参引用数据类型变量的 “地址值” 传递给形参
6.5 构造器
构造器的作用有两个,一个是搭配 new 关键字创建类的对象,另一个是在创建对象的同时,给对象相关属性赋值。构造器的一些注意事项如下:
- 构造器名必须与它所在的类名必须相同
- 构造器的修饰符只能是权限修饰符,不能被 static、final、synchronized、abstract、native 修饰,它没有返回值,不能有 return 语句返回值
- 没有显式声明类中的构造器时,系统会默认提供一个无参的构造器并且该构造器修饰符默认与类修饰符相同
- 构造器是可以重载的,一个类中可以声明多个构造器
public class Student {
private String name;
private int age;
// 无参构造
public Student() {}
// 有参构造
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
6.6 封装性
面向对象的开发原则要遵循 “高内聚、低耦合”。高内聚即类的内部数据操作细节自己完成,不允许外部干涉。低耦合即类仅暴露少量的方法给外部调用。
6.7 继承性
6.7.1 子类与父类的关系
- 子类会继承父类所有的实例变量和实例方法
- 子类虽然会继承父类私有的成员变量,但子类不能对继承的私有成员变量和私有方法直接进行访问,要通过继承的 getter 方法进行访问
- 子类在继承父类以后,还可以定义自己特有的方法,可以看做是对父类功能上的扩展
- 顶层父类是 Object 类。所有的类默认继承 Object,作为父类
6.7.2 继承语法(extends 关键字)
说明:Student 类继承了父类 Person 的所有属性和方法,并增加了一个属性 school。Person 中的属性和方法,Student 都可以使用。
6.7.3 继承的好处
- 继承的出现减少了代码冗余,提高了代码的复用性
- 继承的出现,更有利于功能的扩展
- 继承的出现让类与类之间产生了 is-a 的关系(所属关系),为多态的使用提供了前提
6.7.4 方法的重写
父类的所有方法子类都会继承,但是如果觉得父类原来的方法不适合于当前子类,子类可以对从父类中继承来的方法进行重写 (override)。方法重写的要求如下:
- 子类重写的方法必须和父类的方法具有相同的方法名和形参列表
- 子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
- 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限(子类不能重写父类中修饰权限为private 的方法)
- 子类方法抛出的异常不能大于父类被重写方法的异常
- 构造方法不能被重写。因为构造方法要和类同名,如果重写构造方法,则子类和父类必须同名,这是不允许的
6.8 多态性
在 Java 中,对象的多态性体现在父类的引用指向子类的对象。多态的使用前提:① 类的继承关系 ② 方法的重写。在多态场景下调用方法时,编译时认为是左边声明的父类方法,实际执行的是右边子类重写方法。多态使用格式为:父类类型 变量名 = 子类对象;
6.8.1 向上转型和向下转型
-
向上转型:左边变量的类型(父类) > 右边对象 / 变量的类型(子类)
- 语法为:父类 对象名 = new 子类()
- 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,但是运行时,仍然是对象本身的类型,所以执行的方法是子类重写的方法体
- 向上转型一定是安全的,而且也是自动完成的
-
向下转型:左边变量的类型(子类)< 右边对象 / 变量的类型(父类)
- 语法为:子类 对象名 = (子类) 父类
- 此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了。但是,运行时,仍然是对象本身的类型
- 向下转型可能会发生 ClassCastException ,为了安全可以通过 isInstanceof 关键字进行判断
6.9 == 和 equals
-
==:判断基本数据类型的数值是否相等,或者判断引用数据类型的对象内存地址是否相同
-
equals:
- 自定义的类在没有重写 Object.equals() 的情况下,调用的就是 Object.equals(),比较两个对象的引用地址是否相同
- 对于 String 、Date、File 、包装类等,都重写了 Object.equals(),用于比较两个对象的值是否相同(适用于开发中自定义类)
第 7 章 面向对象编程(高级)
7.1 代码块
如果成员变量想要初始化的值不是一个常量值,而是需要通过读取文件、读取运行环境信息等方式才能获取的一些值,此时使用代码块(或初始化块)。
7.1.1 静态代码块
- 静态代码块可以有输出语句,可以对类的属性、类的声明进行初始化操作
- 若有多个静态的代码块,那么按照从上到下的顺序依次执行
- 静态代码块的执行要先于非静态代码块,所以静态代码块不可以调用非静态的属性和方法
- 静态代码块随着类的加载而加载,且只执行一次
static {
System.out.println("静态代码块");
}
{
System.out.println("非静态代码块");
}
7.1.2 非静态代码块
- 非静态代码块可以有输出语句,可以对类的属性、类的声明进行初始化操作
- 非静态代码块除了调用非静态结构外,还可以调用静态的变量或方法
- 若有多个非静态的代码块,那么按照从上到下的顺序依次执行
- 每次创建对象的时候,都会执行一次,且先于构造器执行
{
System.out.println("非静态代码块");
}
7.2 类中属性赋值的过程
① 默认初始化 ② 显式初始化 或 代码块初始化 ③ 构造器中初始化 ④ 通过 “对象.属性” 或 “对象.方法” 的方式,给属性赋值
其中 ①、②、③ 在对象创建过程中只执行一次。④ 是在对象创建后执行的,可以根据需求多次执行。执行顺序为:
7.3 抽象类(abstract 关键字)
- 抽象类:被 abstract 修饰的类
- 抽象方法:被 abstract 修饰的没有方法体的方法
- 抽象类不能创建对象,如果创建,编译无法通过而报错
- 抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类
- 抽象类中是有构造器的,因为其子类创建对象时需要调用到父类的构造器
- 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类
注意: 不能用 abstract 修饰变量、代码块、构造器、private 修饰的私有方法、static 修饰的静态方法、final 修饰的方法、final 修饰的类。
7.4 接口(interface 关键字)
接口就是规范,定义的是一组规则。继承实现的是 “是不是” 的 is-a 关系,而接口实现的是 “能不能” 的 has-a 关系。
7.4.1 接口内部结构
接口内部可以声明:
- 属性:一定是全局变量(public static final 修饰)
- 方法:JDK8 之前只能声明抽象方法(public abstract 修饰);JDK8 之后可以声明静态方法和默认方法
// 声明接口
public interface A {
public abstract void showA();
}
public interface B {
public abstract void showB();
}
注意: 接口内部不可以声明构造器、代码块等
7.4.2 类实现接口类实现接口时,需要重写接口中所有的抽象方法。
// C 是接口 A、B 的实现类
public class C implements A, B {
@Override
public void showA() {
System.out.println("showA");
}
@Override
public void showB() {
System.out.println("showB");
}
}
7.4.3 接口的多继承
interface AA{
public abstract void showAA();
}
interface BB{
public abstract void showBB();
}
interface CC extends AA, BB{}
7.4.4 接口的新特性
JDK8 中的静态方法只能被接口调用。接口中的默认方法(default 修饰)可以被实现类继承。如果实现类没有重写该方法时调用接口声明的默认方法。如果实现类重写该方法,调用实现类重写的方法。
7.5 抽象类和接口
- 抽象类作为系统中多个子类的共同父类,体现的是一种模板式设计。抽象类作为多个子类的父类,可以被当作系统实现过程中的中间产品。一个子类只能继承一个抽象类,子类必须实现父类的抽象方法
注意: 包含抽象方法的一定是抽象类,但是抽象类不一定含有抽象方法。
- 接口是对行为的抽象,是一种行为规范。一个类可以实现多个接口。对于接口的实现者来说,接口规定了实现者必须向外提供哪些服务。对于接口的调用者而言,接口规定了调用者可以调用哪些服务。接口是多个模块间的耦合标准
7.6 内部类
将一个类 A 定义在另一个类 B 里面,里面的那个类 A 就称为内部类,类 B 则称为外部类。具体来说,当一个事物 B 的内部,还有一个部分需要一个完整的结构 A 进行描述,而这个内部的完整的结构 A 又只为外部事物 B 提供服务,不在其他地方单独使用,那么整个内部的完整结构 A 最好使用内部类。
第 8 章 异常处理
异常指的是程序在执行过程中,出现的非正常情况,如果不处理最终会导致 JVM 的非正常停止。异常体系的根父类是 java.lang.Throwable。Throwable 分为 Error 和 Exception 两类。
8.1 错误 Error 和异常 Exception
-
错误 Error 表示程序无法处理的错误,Error 绝大多数是程序运行时不允许出现的状况,例如栈内存溢出(StackOverflowError)和堆内存溢出(OOM)
-
Exception 表示用户程序可能捕捉或者是可以处理的异常,分为检查性异常和运行时异常。
- 检查性异常是程序员无法预见的异常。例如要打开一个不存在文件时,就发生了检查性异常。检查性异常在编译时不能被忽略
- 运行时异常是程序员可以避免的异常。运行时异常可以在编译时被忽略
运行时异常 | 异常 |
---|---|
NullPointerException | 空指针异常 |
ArrayIndexOutOfBoundsException | 数组越界异常 |
ClassCastException | 类型转换异常 |
NoSuchMethodException | 方法不存在异常 |
NumberFormatException | 数字格式异常 |
InputMismatchException | 输入类型不匹配异常 |
8.2 异常处理机制
在编写程序时,经常要在可能出现错误的地方加上检测的代码,如进行 x / y 运算时,要检测分母为 0,数据为空,输入的不是数据而是字符等。过多的 if-else 分支会导致程序的代码加长、臃肿,可读性差,因此 Java 采用异常处理机制,将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护。
8.2.1 try-catch-finally
try{
...... // 可能产生异常的代码
}
catch(异常类型1 e){
...... // 当产生异常类型1型异常时的处置措施
}
catch(异常类型2 e){
...... // 当产生异常类型2型异常时的处置措施
}
finally{
...... // 无论是否发生异常,都无条件执行的语句
}
- 如果 try 块中的代码没有发生异常,catch 块中所有分支语句都不会执行
- 如果 try 块中的代码发生了异常,根据异常对象的类型从上到下选择第一个匹配的 catch 分支执行
注意:
- catch 中常用异常处理的方式一般为 printStackTrace():打印异常的跟踪栈信息并输出到控制台。包含了异常的类型、异常的原因、还包括异常出现的位置
- 运行时异常即使没有使用 try 和 catch 捕获,Java 自己也能捕获并且编译通过 。所以对于运行时异常,可以不作处理;如果抛出的异常是编译时异常,则必须捕获,否则编译错误。也就是说,我们必须使用 catch 捕获编译时异常,然后将其转化为运行时异常
- 我们通常将一定要被执行的代码声明在 finally 中,例如数据库连接、输入流输出流、Socket 连接、Lock 锁的关闭等
8.2.2 throws
如果在编写普通方法体的代码时,代码可能发生某个编译时异常,但是在当前方法体中可能不适合处理或无法给出合理的处理方式,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。throws 语句如下:
// throws 后面的异常类型可以是方法中产生的异常类型,也可以是它的父类
修饰符 返回值类型 方法名(参数) throws 异常类1, 异常类2 …{}
8.2.3 try-catch-finally 和 throws 两种异常处理的选择
- 如果程序代码中,涉及到资源的调用(流、数据库连接、网络连接等),则必须考虑使用 try-catch-finally 来处理,保证不出现内存泄漏
- 如果父类被重写的方法没有 throws 异常类型,则子类重写的方法中如果出现异常,只能考虑使用 try-catch-finally 进行处理,不能 throws
- 开发中,方法 a 中依次调用了方法 b,c,d 等方法,方法 b,c,d 之间是递进关系。此时,如果方法 b,c,d 中有异常,我们通常选择使用 throws,而方法 a 中通常选择使用 try-catch-finally
8.3 throw 手动抛出异常对象
在实际开发中,如果出现不满足具体场景的代码问题,我们需要通过 throw 语句手动抛出一个自定义异常类型的异常对象。例如年龄负数问题,考试成绩负数问题,throw 语句使用格式:
throw new 异常类名("...");
8.4 throws 和 throw 的区别
- throws 声明在方法体外,是一种异常处理的方法
- throw 声明在方法体内,用于抛出一个异常对象,并未对异常进行处理