学习资料
尚硅谷_Java零基础教程-java入门必备-初学者从入门到精通全套完整版(宋红康主讲)
java学习路线(备份).html
学习笔记
java基础
- 语言概述
- 基本语法
- 数组
- 面向对象编程上中下
- 异常处理
P37 注释
X:
文档注释:可以被 JDK 提供的工具 javadoc 解析的最终生成网页文件形式的说明文档。
Y:
代码:
$ javadoc -d HelloJava -author -version HelloJava.java
- HelloJava 是doc文件夹名称
- HelloJava.java 是要生成doc文件的类文件名
要生成文档注释,必须有一个类为
public类型。Z:
当类声明为
public后,该文件名要和声明public的类名一致。 一个.java文件可以声明多个类,但是只有一个类可以使用public修饰。
P40 第一个HelloWorld总结
- 一个
java文件可以包含多个类,javac执行后会生成多个字节码(.class)文件。 - JDK = JRE + Java开发工具
-
P46 标识符的命名规范
标识符命名规则
- 组成元素:26个英文字母大小写、0~9、_、$
- 首位要求:不可以是数字
- 不可以使用关键字和保留字(一模一样不行但是可以改动后使用)
- 严格区分大小写,长度无限制
- 标识符不可以包含空格
java命名规范:
- 包名:多单词组成时所有字母都小写,形如:xxxyyyzzz
- 类名、接口名:多单词组成时,所有字母首字母大写,形如:XxxYyyZzz
- 变量名、方法名:多单词组成时,第一个单词首字母小写,之后“驼峰”方式命名,形如:xxxYyyZzz
- 常量名:所有字母大写,单词间使用下划线连接,形如:XXX_YYY_ZZZ
P50 变量类型
X:
- 基本数据类型
- 数值型
- 整型(默认为int类型)
byte(256位,-128~127)shortintlong(赋值时需要以字母“l”或“L”结尾)
- 浮点型(默认为double类型)
float(尾数精确到7位小数)double(长度是float一倍,可以精确到14位小数,赋值时需要以字母“f”或“F”结尾,通常使用double类型)
- 整型(默认为int类型)
- 字符型
char - 布尔型
boolean
- 数值型
引用数据类型
- 类
class - 接口
interface - 数组
[]
- 类
自动类型递增
byte 、short 、char —> int —> long —> float —> double
低类型与高类型一起运算,最后输出结果以高类型为准 容量指的是表示的数的范围,不是指内存占用大小 特别的:byte/short/char 进行运算时,返回结果为 int 类型
Y:
long l = 123;System.out.println(l); // 123// 不是说 long 类型都要以 “l” 或 “L” 结尾吗?这里为什么编译器没有报错// 解:// 因为编译器把等号右侧的数值当成了 int 类型,// 自动类型提升为了long类型,所以不需要加字母“l”或“L”作为后缀// 过长(超出int类型)的数值不加字母l就会报错// float类型字母f是必须加的
P60 String
X: String 能和8种基本数据类型做连接运算(“+”),且运算结果都是 string 类型。
Y:
// String str1227 = 4; // 不兼容的类型: int无法转换为java.lang.String// String str1227 = (String)4; // 不兼容的类型: int无法转换为java.lang.String
P63 进制
X:
- 8进制:以数字0开头
- 16进制:以 “0x”或“0X” 开头,字母不区分大小写
- 计算机底层都采用 补码 的形式存储数据
- 二进制转八进制,每三位转换为一位8进制数
- 二进制转十六进制,每四位转换为一位16进制数
- 八进制转二进制,每一位转换为3位二进制数
- 十六进制转二进制,每一位转换成4位二进制数
P72 运算
X:
- 取余(“
%”)运算结果符号与被模数相同 - 自增/自减运算不会改变最终数值类型
- += / -= / *= / /= 不会改变最终运算结果类型
&、|两侧为数值类型时就是位运算符,当&、|两侧为布尔类型时就是逻辑运算符&和&&运算结果相同,|和||运算结果相同,也就是说无论使用哪种&或者哪种|都会返回相同的结果- 当
&&左侧为false则不再执行右边的表达式,同理,当||左侧为true则不再执行右边的表达式 - 位运算:右移最高位补0还是1与原来最高位是0还是1相同
- 无符号位移(
>>>),最高位都用0补 m ^ n ^ n = mn ^ m ^ n = (m ^ n) ^ n = m(两个异或运算可以相互换位置)
Y:
short s = 10;// s = s + 2; // 编译不通过,因为等号右边会被自动转换类型为int,将int类型赋值给short类型报错s += 2; // 12 编译通过,因为不会改变参数类型
// 两个数字互相换位置// 但是有限制:仅用于数值类型int num1 = 123;int num2 = 234;num1 = num1 ^ num2;num2 = num1 ^ num2;num1 = num1 ^ num2;
P95 Scanner
X:
- 获取键盘输入,无法获取
char类型获取,只能获取字符串类型数据 - 字符串有个API是获取指定位置的字符的,返回
char类型,char t = "test".charAt(2); // 'a' - 需要根据要求输入类型输入,如果违反就会抛出异常
Y:
import java.util.Scanner; // 引入 Scanner 类/*** 从键盘获取输入*/public class ScannerKeyboard {public static void main(String[] args) {// 实例 Scanner 类,记住 construct 函数需要传入 System.in 作为参数Scanner scanner = new Scanner(System.in);System.out.println("请输入一个数:");int num1524 = scanner.nextInt();System.out.println(num1524);}}
P98 if-else
X:
Math.random返回double类型,范围是 [0.0, 1.0)- 获得 [a, b] 之间的随机数公式:
(int)Math.random() * (b - a ``+ 1``) + a - 获得 [a, b) 之间的随机数公式:
(int)Math.random() * (b - a) + a 两个字符串判定是否相同,使用
str.equals("xxx")返回结果为布尔类型判断Y: ```java if (4 > 2) if (1 > 2)
System.out.println("123");
else // 就近原则(找最近的if) System.out.println(“234”);
// 输出结果:234
<br /><a name="kHDnZ"></a>### P103 switchX:- switch表达式只能是以下6中类型:byte、short、char、int、枚举(JDK5)、String(JDK7)<a name="7uyTp"></a>### P117 whileX:- `while(true)` 和 `for( ; ; )` 执行效果一样- 嵌套循环:外层控制X轴,内层循环控制Y轴<a name="ND02d"></a>### P125 breakX:- 多层循环在指定层for循环前添加标签,然后在 `break` 后面添加标签,则可以直接跳出指定层循环```javatest: for(;;) {for (int i = 0; i < 10; i++) {if (i == 5) {break test; 结束外层循环}}}test: for(;;) {for (int i = 0; i < 10; i++) {if (i == 5) {continue test; 本次循环结束,直接开始外层的下一次循环}}}
for和while循环是可以互换的Y:
练习一: ```java label: for(int i = 1; i <= 4; i++) { for (int j = 1; j <= 10; j++) {
if (j % 4 == 0) {continue label;}System.out.print(j);
} System.out.println(); }
// 运行结果: 123123123123 // 没有换行呦~
<a name="8twvi"></a>### P140 数组X:- 数组是引用类型的,但是数组里的元素既可以是基本类型也可以是引用类型- 要保证数组一旦初始化完成,那么数组的长度就是确定的- 数组长度**属性**:`length` ,如:`System.out.println(arr.length);`- `char` 类型数组每个元素默认初始值为0或者‘\u0000’,而不是‘0’,对应于ASCII中的null- 数组元素是引用类型的,默认初始化值为 `null` 而不是 字符串类型'null'- 数组内存存储原理分析:<br />Y:```javaint[] list; // 数组声明// 静态初始化,数组初始化和数组元素赋值同时进行list = new int[]{1, 2, 3};// 动态初始化,先初始化之后再赋值list = new int[3];// 错误写法:// ~~int[] list1 = new list[3]{1, 2, 3};~~
P149 二维数组
X:
- 二维数组的长度为”行”的长度
- 外层元素:”行”,如:
arr[0]、arr[1],直接输出 内层数组的地址值 - 内层元素:”列”,如:
arr[0][0]、arr[0][1] int[] x, y[]等效为int x[], y[][]连续赋值:
x = y = 1Y: ```java // 静态初始化 int[][] arr = new int[][]{{1, 2, 3}, {4, 5}};
// 动态初始化 String[][] arr1 = new String[3][2];
// 动态初始化,不声明列长度 // 列维度数组可以不声明数组长度,之后初始化的时候再指定长度, // 等于这个时候还没有初始化声明数组 String[][] arr2 = new String[3][];
// null 因为这个时候内层元素还没声明,所以输出null,而不是地址值 // 如果这个时候调用内层元素会抛出异常 System.out.println(arr2[0]);
// 二维数组第二行初始化成为一个长度为4的数组 arr2[1] = new String[4];
// 其他特殊示例 String[] arr3[] = new String[3][2]; String arr4[][] = new String[3][2];
// 类型推断 // 省略new int[][] // 必须声明和赋值在同一行之内才能构成类型推断 int[][] arr = {{1, 2, 3}, {4, 5}};
<a name="dceky"></a>### P160 数组元素的赋值Z:- 拓展题:创建一个长度为6的int型数组,要求数组元素的值都在1~30之间,且是随机赋值。同时,要求各个元素的值各不相同。```java/*** P160 158. 数组元素的赋值* 创建一个长度为6的int型数组,要求数组元素的值都在1~30之间,且是随机赋值。* 同时,要求各个元素的值各不相同*/public static void randomArray() {int[] arr = new int[6];for (int i = 0; i < arr.length; i++) {boolean flag = true;int randomNum = (int)(Math.random() * 30 + 1);loop:while(flag) {for (int j = 0; j < i; j++) {if (arr[j] == randomNum) {randomNum = (int)(Math.random() * 30 + 1);continue loop;}}flag = false;}arr[i] = randomNum;}for (int i = 0; i < arr.length; i++) {System.out.print(arr[i] + "\t");}}/** 方法二 */public static void randomArray1() {int[] arr = new int[6];for (int i = 0; i < arr.length; i++) {arr[i] = (int)(Math.random() * 30 + 1);for (int j = 0; j < i; j++) {if (arr[j] == arr[i]) {i--;break;}}}for(int i = 0; i < arr.length; i++) {System.out.print(arr[i] + "\t");}}
- 拓展题二:输出一个回数

边长为3的回数
边长为5的回数
边长为7的回数
/*** 输出回数* arr[Y][X]* ————> X* |* ⬇* Y*/public static void roomArray() {Scanner scan = new Scanner(System.in);System.out.println("请输入回型数边长");int side = scan.nextInt();int count = 0;int[][] arr = new int[side][side];int minX = 0,maxX = side - 1,minY = 0,maxY = side - 1;while(minX <= maxX) {for (int i = minX; i <= maxX; i++) {arr[minY][i] = ++count;}minY++;for (int j = minY; j <= maxY; j++) {arr[j][maxX] = ++count;}maxX--;for (int i = maxX; i >= minX; i--) {arr[maxY][i] = ++count;}maxY--;for (int j = maxY; j >= minY; j--) {arr[j][minX] = ++count;}minX++;}for (int i = 0; i < arr.length; i++ ) {for(int j = 0; j < arr[i].length; j++) {System.out.print(arr[i][j] + "\t");}System.out.println();}}
数组算法
X:
- 【P166】二分查找:前提条件数组是有序的
【P172】数组两种异常:
- 数组角标越界异常:
ArrayIndexOutOfBoundsException
- 数组角标越界异常:
- 空指针异常:
NullPointerException
- 空指针异常:
Y:
【P171】数组帮助类
Arrays```java /**- 认识 数组帮助类 Arrays
如何引入? - import java.util.Arrays; */ public static void arrays() { // 1. 判断两个数组是否相等 // boolean equals(int[] a, int[] b) int[] arr1 = {1, 2, 3, 4}, arr2 = {1, 3, 2, 4};
System.out.println(Arrays.equals(arr1, arr2));
// 2. 输出数组信息 // String toString(int[] a) System.out.println(Arrays.toString(arr1));
// 3. 将指定值填充到数组之中 // void fill(int[] a, int val) Arrays.fill(arr1, 10); System.out.println(Arrays.toString(arr1));
// 4. 对数组进行排序// void sort(int[] a)int[] arr3 = {123, 34, -23, -1, 2, 23, 34, -23};System.out.println(Arrays.toString(arr3));Arrays.sort(arr3);System.out.println(Arrays.toString(arr3));// 5. 对排序后的数组进行二分法检索指定的值(未找到则返回小于0的值,因为索引都是 >= 0的所以没问题)// int binarySearch(int[] a, int key)System.out.println(Arrays.binarySearch(arr3, 2));System.out.println(Arrays.binarySearch(arr3, 3));
}
<br /><a name="5NrAO"></a>### 面向对象X: <br />- 类和类的成员<br />- 属性、方法、构造器、代码块、内部类- 三大特征- 封装、继承、多态- 其他关键字- this、super、static、final、abstract、interface、package、import<br />- new 同一对象的不同实例(除static外)各自拥有不同的储存空间,互相独立- 方法中不能再包含方法- `return` 后面不能声明执行语句- 【P203】匿名对象的使用:`System.out.println(new Person().showAge());`- 【P205】函数重载(overload):同名函数中 **参数个数 **或者 **参数类型不同**,有可能会出现自动类型提升- 【P209】可变个数形参- _但是如果有确定数量参数个数的函数,则优先考虑确定数量参数个数的函数_- 也就是参数个数范围 >= 0- `...` 和传入一个 相同类型的数组 不能共存- 即使真的传入一个数组,`...` 也是可以接收的- 可变个数形参必须且只能声明在 **形参末尾**- 可变个数形参最多只能声明 **一个**- 【P212】引用数据类型- **封装性体现**: 将类的属性声明为 `private` ,然后声明 `get` 和 `set` 方法修改该属性- private < 缺省 < protect < public (【P225】)> - 修饰类只能用 public 或者 缺省> - 同一包下不能创建相同名字的类> - 类声明如果class前缺省的话只能同一个包内调用- 如果没有定义类中的构造器的话,系统会默认创建一个空参的**构造器**,函数名与类名相同。 [P227]- 没有返回值- 一旦我们定义了构造器,系统就不再创建默认的构造器了- 一个类中至少有一个构造器- JavaBean:- 1. 公共类- 2. 有一个无参的公共构造器- 3. 有属性,且有对应的get和set方法- this:- 可以修饰 属性、方法、构造器 [P233]- 构造器内调用构造器,使用 `this();` 调用 **其他 **构造器。且 `this()` 只能放在构造器首行(也就是说只能调用一次,调用两次都不算是在首行了)。[P234]- package:- 为了更好的实现项目的管理引入了包的概念- 源文件的首行- 包属于标识符,需要遵循标识符规范- 每点“.”一次就代表一层文件目录- import:- 导入指定目录下的类或者接口- 可以使用“`.*`”的方式导入指定包下所有结构- 如果使用的类或接口是 `java.lang` 包下的定义的,则可以省略 import- 如果使用的类或接口是本包下定义的,则可以省略 import- 如果使用了不同包下同名类,则其中至少有一个类必须使用 全类名方式- 即使使用了"`.*`",子包依然需要 import 导入- `import static` 可以导入指定类和接口中的静态结构:属性或方法- 只要创建对象就会使用构造器 [P242]- 继承:- 减少了代码冗余,便于扩展,为多态打下了基础- 父类、基类、超类(superclass)子类(subclass)- 子类继承父类,就获取到了父类的所有属性和方法,只是因为修饰符封装性的限制,有些私有属性或私有方法无法直接访问- 一个类只能有一个父类,一个父类可以有多个子类- 多层继承时,子类获取了所有直接父类和间接父类的属性和方法- 所有java类(除java.lang.Object外)都直接或间接继承Object类- 重写(覆盖):- 函数名和形参列表相同构成重写- 子类**不能**重写父类中 `private` 修饰的方法- 父类被重写的方法返回值类型为void的,子类重写的方法返回值也只能是void- 父类方法返回值类型为A类型的,子类重写的方法返回值可以是A类型或者是A类型的子类- 父类方法返回值为基本类型的(如double),子类重写的方法也必须返回相同的基本数据类型(必须是相同的double)- 这里不是赋值操作时可以自动变量提升,这里该是什么类型就是什么类型,不能使用其他类型- 子类抛出的异常类类型不能大于父类抛出的异常( throws Exception )- 子类和父类同名同参的方法要么都声明为非 static(考虑重写),要么都声明为 static 的(不是重写)- super:- super如果在构造器中使用,必须在首行- 在构造器中 `this` 和 `super` 因为都必须在首行,所以二者不能同时出现,只能最多出现一个。- 当构造器中没有显式声明this或者super,则默认为 “super()” 调用父类的空参构造器- 在类的多个构造器中至少有一个构造器调用了父类中的构造器(super)- 子类对象实例化过程- 子类构造器创建子类对象时,一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器...直到调用到 java.lang.Object 空参构造器为止。- 虽然调用了多个构造器,但是实际上对象只有一个,就是new创建的那个Y:- 【P209】可变个数形参```java/** 明确参数个数的方法 */public void show(String s) {System.out.println("show: " + s);}/*** 可变个数形参* 有点类似JavaScript es6中的扩展运算符(...)* @param strs 实际上是一个字符串数组*/public void show(String ... strs) {System.out.println("strs: " + strs);for (String item: strs) {System.out.println("item: " + item);}}// void show(String[] strs) {} // 编译器报错,不构成重载,相当于是重复声明同一个函数
构造器 [P227]
class Person {private String name;/*** 构造器,与类名相同*/public Person() {System.out.println("哈哈哈");}public Person(String str) {name = str;}}
Z:
- 针对 [P205] 重载:同名不同参
多态
- 多态的定义:父类的引用指向不同的对象,(子类的对象赋给父类的引用)
Person person = new Man();Person person = new Woman(); - 多态的使用:当子类和父类声明了同名同参的方法时,(这个方法覆盖了父类的方法) 实际执行的是子类的方法—— 虚拟方法调用
- 编译期,只能调用父类声明的方法,但在运行期,我们实际执行的是子类重写的父类的方法。—— 编译看左边,执行看右边
- 多态性前提:1. 继承关系 2. 方法重写
- 多态性只适用于方法,不适用于属性(编译和运行都看左边)
- 多态是运行时行为
- 重载是静态的编译前就已经确定了类型,多态是动态的运行时才能确定类型
- 向下转型:
Man newMan = (Man)person;- 当使用多态无法调用子类特别声明的方法时,可以使用向下转型,这个时候(newMan)就可以调用子类的不同于父类声明的方法了。
- 为了避免向下转型异常,我们在向下转型之前需要进行 instanceof 判断 ```java Person person2 = new Man();
// 只有当 person2 是Man类型时,才能发生向下转型 if (person2 instanceof Man) { Man man2 = (Man)person2; }
// 下列代码同样可以编译和运行正确,没问题 Object obj = new Man(); Man man3 = (Man)obj;
// 下列代码是错误的不能运行的 Person person3 = new Person(); Man man4 = (Man)person3; // ❌,无法运行,因为父类根本不具备子类的特别的方法
- 当 `a instanceof A` 为 `true`,且 `a instanceof B` 也为 `true`,则 A 和 B 一定是父子类关系- 向下转型和变量类型自动提升关系:<a name="TwusK"></a>#### Object 类- == 和 equals 区别:- == 使用于 _基本数据类型变量_ 和 _引用数据类型变量 _(基本数据类型运算不能和boolean比较)- == 比较基本数据类型比的是存储的数据是否相等(有可能数据类型不同)- == 必须保证左右两边变量类型一致(不然编译都不通过)```javaint i = 10;char c = 10;System.out.println(i == c); // true
- equals 是一个方法,而不是运算符
equals 只适用于引用数据类型
- Object中的equals :
public boolean equals(Object obj) {return (this == obj);}
- Object中的equals :
String、Date、File、包装类 等都重写了Object中的equals方法,比的不再是实体对象,实际比较的是属性
- toString 方法:
- 调用一个对象的引用时,实际上调用的是当前对象的 toString() 方法
包装类
- 自动装箱: ```java int num = 10; Integer interger = num; // 自动装箱:自动将基本数据类型转换为 Integer 类类型
boolean flag = true; Boolean bool = flag; // 同理的自动装箱
- 自动拆箱:```javaInteger integer = 20;int num2 = integer; // 自动拆箱:自动将 Integer 类类型的变量变为基本数据类型
- 基本数据类型/包装类 ——>
String- 方法一:
20 + "" - 方法二:使用
String.valueOf(基本数据类型/包装类)String.valueOf(20)String.valueOf(new Integer(20))
- 方法一:
- String ——> 基本数据类型/包装类
- 两个没有任何关系的类型强转一定会编译报错,所以说String转换为基本数据类型或者包装类不能使用强转 (
(int)) 这种方式 - 实际使用方法,如 Integer.parseInt(“123”)
- 但是使用这种类型转换方法有可能报错:类型转换错误,比如转换 “123a” 就会报错
- 两个没有任何关系的类型强转一定会编译报错,所以说String转换为基本数据类型或者包装类不能使用强转 (
- Integer 类内有一个 IntegerCache 结构,这一结构定义了一个Integer数组,缓存了从 -128 ~ 127 得整数,我们可以使用自动装箱的方式,给 Integer 赋值时在 -128 ~ 127 的整数不需要再次
new Integer了,可以直接从数组中取出使用,已经是定义好了的。
static
- static修饰的变量会变成静态变量,当在一个对象实例中修改静态变量,会导致其他所有对象实例访问到的静态变量都发生改变。
- 静态变量随着类的加载而加载
- 静态变量加载早于对象的创建
- 由于类只加载一次,所以静态变量在内存中只存一份

- 静态方法中只能调用静态的方法和属性,不能调用非静态的方法和属性
- 非静态方法中既可以调用非静态方法和属性,也可以调用静态方法和属性
- 静态方法中不能使用
this和super关键字 - 静态方法中调用静态属性可以省略类名
- 静态方法用途体现:
- 操作静态属性的方法一般声明为静态方法
- 工具类中的方法,一般声明为静态方法
单例模式
- 懒汉式和饿汉式区别
- 饿汉式弊端:导致对象加载时间过长
- 饿汉式优点:饿汉式是线程安全的
- 懒汉式弊端:线程是不安全的
- 懒汉式优点:延迟对象的创建
代码在项目中:
[baseCode.P325] [baseCode.P326]
代码块
- 语法格式:
{} - 作用:初始化类、对象
- 可以添加 static 修饰,其他都不能加
- 静态代码块:
- 内部可以有输出语句
- 随着类的加载而执行,自动执行且只执行一次,不需要调用
- 可以定义多个静态代码块,按照静态代码块顺序执行
- 静态代码块执行优先于非静态代码块执行
- 只可以调用非静态属性和方法
- 非静态代码块:
- 内部可以有输出语句
- 随着类的创建而执行,没创建一个对象就执行一次非静态代码块
- 可以在创建对象时,对对象属性进行初始化
- 可以声明多个非静态代码块,按照非静态代码块先后顺序执行
- 可以调用静态和非静态属性和方法
- 执行顺序:
- 由父及子,静态先行(1. 执行完父类已经父类的父类…的静态代码块, 2. 执行 父类的父类(如果有的话)… 的非静态代码块,3. 执行 父类的父类的构造器 4. 依次往下执行,最后才是执行当前类)
- 属性赋值执行顺序:
- 默认初始化(赋一个默认值,比如Number类型赋为0,boolean赋为false)
- 显式初始化(赋一个指定的值) / 在代码块中初始化 (哪个在前就先执行哪个)
- 构造器中初始化
- 对象.属性初始化
final
可以用来修饰:类、方法、变量
- 类:代表此类不能被其他类继承,比如:String、System、StringBuffer
- 方法:表明此方法不能被重写
- 变量:此时“变量”就称为一个常量
- 属性:可以考虑的赋值位置有:显式初始化、非静态代码块、构造器(但要注意一点当一个构造器赋值后,其他所有构造器也得保证final修饰的属性能够初始化)
形参也可以用final修饰,一旦使用final修饰形参,表明形参是一个常量,一旦赋值后不能再修改。
- 使用 static 和 final 一起修饰 属性:全局常量
- 使用 static 和 final 一起修饰 方法:不能被重写需要使用类调用
abstract
- 抽象类
- abstract 修饰的类就变成了抽象类,这个抽象类不能再实例化了(就是不能
new 抽象类()) - 抽象类中一定有构造器,子类实例化时调用
- 会提供抽象类的子类进行实例化
- abstract 修饰的类就变成了抽象类,这个抽象类不能再实例化了(就是不能
- 抽象方法
- 抽象方法只有方法名,没有方法体
public abstract void test(); - 包含抽象方法的类一定是抽象类(保证类没法直接调用,如果类能调用,这个方法就不对了,所以含抽象方法的类一定是抽象类),抽象类可以没有抽象方法
- 若子类重写了抽象父类所有的抽象方法后,子类才能实例化
- 若子类没有重写抽象父类中所有抽象方法,则此子类依然是一个抽象类,需要使用abstract修饰,直至有一个子类重写了所有抽象方法才能将那个重写了所有抽象方法的子孙类实例化。
- 抽象方法只有方法名,没有方法体
- abstract 不能修饰属性和构造器,不能修饰私有方法(因为子类没有权限重写抽象方法,就会一直报错无法编译),不能修饰静态方法,final修饰的方法、final修饰的类
- 抽象类可以有构造器
接口 interface
- 为了实现类的多继承,所以引入“接口”概念
- 接口和类是并列结构
- 接口成员:
- JDK7及以前:全局常量和抽象方法
- 全局常量:
public static final(但是在接口中可以省略不写) - 抽象方法:
public abstrac
- 全局常量:
- JDK8中除了JDK7的内容,还额外增加了 静态方法 和 默认方法
- 静态方法:接口中定义的静态方法,只能通过接口调用
- 默认方法:default 关键字修饰后,对象实例可以调用默认方法
- 如果实现类重写了接口中的默认方法,调用时,调用实现类里重写的方法
- 如果子类(或实现类)继承的父类和实现接口声明了同名同参数的方法,那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。 ——> 类优先原则
- 如果实现类实现了多个接口,而多个接口定义了同名同参数的默认方法,那么实现类没有重写此方法的情况下,报错 ——> 接口冲突,必须在实现类中重写此方法
- 实现类调用指定接口中的默认方法
接口名.super.接口中的默认方法(InterfaceTest.super.method();)
- JDK7及以前:全局常量和抽象方法
- 接口不能定义构造函数,意味着接口不能实例化
- 接口都通过类实现(implements)接口功能
- 如果 实现类 实现了接口中所有抽象方法,则该实现类可以被实例化,反之此实现类仍为一个抽象类(该类必须添加 abstract 关键字修饰),无法实例化。
- 类可以实现多个接口 ——> 弥补了java单继承性的局限性
class AA extends BB implements CC,DD,EE- 接口和接口之间可以继承,且可以多继承
- 开闭原则:对扩展开放,对修改封闭
- 接口和抽象类异同点:
- 都不能实例化,都可以定义抽象方法
- 接口可以多实现多继承,而抽象类只能单继承单实现。抽象类还可以定义构造函数。
内部类
- 分类:成员内部类、局部内部类(方法内、代码块内、构造器内)
- 成员内部类:
- 非静态成员内部类可以调用外部类的结构
- 可以被 static 修饰
- 问题:
- 如何实例化成员内部类的对象?
- 如何在成员内部类中区分调用外部类的结构?
- 开发中局部内部类的使用?
异常处理
java.lang.Throwable
- java.lang.Error : 一般不编写针对性代码进行处理
- java.lang.Exception : 可以进行异常的处理
- 编译异常(checked)
- IOException
- FileNotFoundException
- ClassNotFoundException
- IOException
- 运行时异常(unchecked)
- NullPointerException
- ArrayIndexOutOfBoundsException
- ClassCastException
- NumberFormatException
- InputMismatchException
- ArithmaticException
- 编译异常(checked)
处理异常两种方式
- try-catch-finally
- throws
- 常见的异常处理方式:
- String getMessage() 打印错误方法
- printStackTrace() 异常追踪
- 自定义exception类:
extends Exception代表编译时就会报错extends RuntimeException代表运行时才会报错
throws 异常类型写在方法声明处,指明此方法执行的时候抛出的异常类型。一旦方法执行时,出现异常,会在异常处生成一个异常类的对象,此对象满足throws后的异常类型就会被抛出。异常后续的代码就不再执行了。- 如何选择哪种处理异常方式:
- 如果父类被重写的方法没有
throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch方式处理。 - 执行的方法A中,先后又调用了另外几个方法,这几个方法是递进关系,这几个递进关系的方法使用throw的方式处理,在方法A中使用
try-catch处理。
- 如果父类被重写的方法没有
java高级
- 多线程
- 常用类
- 枚举和注解
- 集合
- 泛型
- IO流
- 网络编程
- 反射
- java8新特性
- java9~11新特性
多线程
一、基本概念:程序、进程、线程
- 基本概念:
- 程序是静态的代码
- 进程是运行的程序
- 线程调度和执行的基本单位,每个线程拥有独立的运行栈和程序计数器。一个进程内可以有多个线程,由这多个线程共享相同的内存空间。
二、线程的创建和使用
- 直接调用子类重写的
run方法并不会创建新线程,所以说 start 还有个作用就是启用新线程,不可以再次 start 已经 start 的线程,只能新建一个对象实例来创建新线程 - 常用方法
Thread.``_currentThread_``().getName()获取当前执行的线程的名字Thread.currentThread().setName()设置线程名称对象实例.setName()设置线程名称- 也可以在 Thread 子类构造器中传入 String 类型来作为线程名称
yield表示释放当前CPU的执行,有可能过一会再次执行join表示将join的线程优先级置顶,待join的线程全部完成才会去执行其他线程sleep使得当前线程进入阻塞状态一些时间(具体时间以毫秒为单位),即使过了指定时间后也不是立即执行,要等待CPU分配isAlive判断当前线程是否存活
- 线程的优先级
- MAX_PRIORITY: 10(最高优先级10) MIN_PRIORITY: 1(最低优先级1) NORM_PRIORITY: 5(正常优先级5)
- 获取及设置优先级:
getPriority()获取线程优先级setPriority(int p)设置线程优先级
- 高优先级并不意味着优先执行,也不意味高优先级执行完才能执行低优先级,高优先级只是指执行概率高。
- 创建多线程方式二:(实现Runnable接口)[P424、P425]
- 创建一个 Runnable 接口的实现类
- 实现类实现
Runnable中的run方法 - 创建实现类对象
- 将此对象作为参数传递到
Thread类的构造器中,创建一个Thread类对象 - 通过 Thread 类对象调用
start方法
- 两种创建线程方式比较:
- 开发中优先使用
Runnable方式创建多线程- 没有单继承局限性
- 更适合处理多个线程有共享数据的情形
- 都要重写run方法,执行start方法
- 开发中优先使用
三、线程的声明周期
- 线程分为两类:守护线程和用户线程
- 线程生命周期:
- 新建:线程新创建
- 就绪:线程创建并start后,将进入线程队列等待CPU时间片,已具备运行条件,只是没有分配CPU资源
- 运行:线程被调度且被赋予了CPU资源
- 阻塞:人为挂起或者执行输入输出操作,使得CPU临时中止执行
- 死亡:线程完成全部工作或者出现异常结束执行或者被提前强制性终止
四、线程的同步
- 解决线程安全问题:
- 方案:当线程a操作一个共享数据时,其他任意线程不能操作这一共享数据,直至线程a处理完共享数据,即使其间线程a进入阻塞状态。
- 方式一:同步代码块(synchronized)
- 在 Runnable 实现类中,我们可以使用 this 充当 同步监视器。
- 在 extends 继承 Thread 类中,慎用this充当同步监视器,可以新建一个static对象,或者最好使用当前类来充当同步监视器
[className].class```java /**
【P433】
- 同步监视器(俗称:锁)任何一个类的对象都可以充当锁
要求多个线程必须共用同一把锁 */ synchronized(同步监视器) { // 需要被同步的代码,即操作共享数据的代码,多个线程共同操作的变量 } ```
- 方式二:同步方法
- 如果操作共享数据都在一个方法内,我们不妨将方法声明为同步的
- 同步方法仍然涉及到同步监视器,只是不需要我们显式声明了
- 非静态同步方法,同步监视器是 this
- 静态同步方法,同步监视器是 类本身
- 方式三:JDK5的
Lock方法 ```java // 1. 实例化 ReentrantLock 类 private ReentrantLock lock = new ReentrantLock();
- 方式二:同步方法
@Override
public void run() {
try {
// 2. 启动同步锁
lock.lock();
// ...... 同步方法} finally {// 3. 解开同步锁lock.unlock();}
}
- 方式二和方式三区别:- synchronized 在执行完相应的同步代码后,自动释放同步监视器- lock 需要手动启动同步,同时结束时也需要手动结束- 使用顺序建议:lock --> 同步代码块 --> 同步方法- 线程的死锁问题- 两个线程都互相等待对方所占用的同步资源- 出现死锁后不会报异常,不会提示<a name="uDMSz"></a>#### 五、线程的通信- 涉及到的三种方法- `wait` 执行此方法当前线程进入阻塞状态,并释放同步监视器(锁)- `notify` 唤醒被 `wait` 的**一个**线程,如果有多个线程被wait,则唤醒优先级高的那个- `notifyAll` 唤醒所有被wait的线程- _注意_:以上三个方法只能出现在 **同步代码块** 或者 **同步方法** 中,lock中都不行- 注意:以上三个方法的调用者必须是同步代码块或者同步方法中的同步监视器(锁),否则编译器会报错:IllegalMonitorStateException- 这三个方法在非静态中省略了 **this**,在静态中省略了 **类.**- 这三个方法都定义在java.lang.Object类中,为的是所有继承于Object的类都可以充当同步监视器- wait 和 sleep 的异同?- 相同点:都会使线程进入阻塞状态- 不同点:- 两个方法定义的位置不同,wait定义在 Object 类中,sleep 定义在 Thread 类中- 调用的地方不同,wait只能在同步方法和同步代码块中调用,sleep可以随处调用- wait执行后线程会释放同步监视器,而sleep并不释放同步监视器<a name="namSt"></a>#### 六、线程新增的创建方式- Callable- call 方法可以有返回值- call 方法可以抛出异常,可被捕获获取异常信息- Callable 支持泛型- 线程池- 提高响应速度- 降低资源消耗- 便于线程管理```javaNumberThread numberThread = new NumberThread();NumThread numThread = new NumThread();FutureTask futureTask = new FutureTask(numThread);// 1. 提供指定线程数量的线程池ExecutorService executorService = Executors.newFixedThreadPool(10);// 2. 执行指定的线程操作。需要提供Runnable或Callable接口实现类的对象executorService.execute(numberThread); // 适合适用于 Runnable// 2. 执行指定的线程操作。需要提供Runnable或Callable接口实现类的对象executorService.submit(futureTask); // 适合适用于 Callabletry {// 6. 获取 Callable 中 call 方法的返回值Object sum = futureTask.get();System.out.println("总和为:" + sum);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}// 4. 关闭连接池executorService.shutdown();
Z
- 创建多线程步骤:
- 继承
thread类,
- 继承
- 重写
run方法,
- 重写
- 调用
start方法启动多线程
- 调用
常用类
一、字符串
- 通过字面量给一个字符串赋值,此时字符串声明在字符串常量池内
- 字符串常量池中不会存储相同内容的字符串(也就是说字符串常量池中字符串都是唯一的)
- 当字符串重新赋值,只会重写指定内存区赋值,不能使用原有的value进行赋值

- 常量与常量字符串拼接结果仍存放于常量池,且常量池没有相同内容得常量,只要其中有一个变量,字符串拼接结果就存放在“堆”中,地址就变得不同了
substring取一个左闭右开区间([))String与char[]互相转换:String —> char[]
char[] charArray = "123456".toCharArray();
char[] —> String
new String(new char[]{'a', 'b', 'c'});
String 与 byte[] 互相转换:
- String —> byte[] ```java byte[] bytes = “123456”.getBytes(), byte[] gbks;
try { gbks = str1.getBytes(“gbk”); } catch (UnsupportedEncodingException e) { e.printStackTrace(); }
- byte[] --> String```javanew String(bytes);new String(gbks, "gbk");
- String : 不可变字符序列
- StringBuffer:可变,线程安全,效率偏低
- Stringbuilder:可变,线程不安全,效率高,(JDK5.0新增)
- 开发中建议使用
StringBuffer
二、JDK8前日期时间API
- java.util.Date
- java.sql.Date
/*** 日期格式中“mm”表示的是秒,表示月份要用“MM”* 不然就会出现月份无法识别,变成了默认的1月*/SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");try {Date date2 = sdf2.parse("2020-09-08");System.out.println(sdf2.format(date2));/*** util.Date --> sql.Date* 通过中间变量:时间戳作为桥梁*/java.sql.Date sqlDate = new java.sql.Date(date2.getTime());System.out.println(sqlDate);} catch (ParseException e) {e.printStackTrace();}
Calendar日历类(抽象类) 的使用 ```java // 1. 实例化 // 1.1 创建其子类(GregorianCalendar)的对象 // 1.2 创建其静态方法 getInstance()
Calendar calendar = Calendar.getInstance(); System.out.println(calendar.getClass());
// 2. 常用方法 // get/set/add/getTime/setTime int days = calendar.get(Calendar.DAY_OF_MONTH); System.out.println(days); System.out.println(calendar.get(Calendar.DAY_OF_YEAR));
calendar.set(Calendar.DAY_OF_MONTH, 22); System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
calendar.add(Calendar.DAY_OF_MONTH, 3); System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
Date date = calendar.getTime(); System.out.println(date);
Date date1 = new Date(); calendar.setTime(date1); days = calendar.get(Calendar.DAY_OF_MONTH); System.out.println(days);
<a name="jc5SC"></a>#### 三、JDK8新日期时间API- java.time 包含值对象的基础包- java.time.chrono 提供对不同的日历系统的访问- java.time.format 格式化和解析时间和日期- java.time.temporal 包括底层框架和扩展特性- java.time.zone 包含时区支持的类```java/*** LocalDate* LocalTime* LocalDateTime*//*** now 获取挡墙日期、时间、日期时间*/LocalDate localDate = LocalDate.now();LocalTime localTime = LocalTime.now();LocalDateTime localDateTime = LocalDateTime.now();// of 设定指定年月日时分秒,没有偏移量LocalDateTime localDateTime1 = LocalDateTime.of(2020, 9, 14, 9, 47, 35);// getSystem.out.println(localDateTime.getDayOfMonth());System.out.println(localDateTime.getDayOfWeek());System.out.println(localDateTime.getMonth());System.out.println(localDateTime.getMonthValue());System.out.println(localDateTime.getMinute());// 体现了不可变性// with 设置相关的属性LocalDate localDate1 = localDate.withDayOfMonth(22);LocalDateTime localDateTime2 = localDateTime.withHour(4);LocalDateTime localDateTime3 = localDateTime.plusMonths(4);LocalDateTime localDateTime4 = localDateTime.minusDays(6);
—— 代码位于 P485、P486、P487 内
- java.util.Date / java.sql.Date —> Instant
- SimpleDateFormatter —> DateTimeFormatter
- Calendar —> LocalDate、LocalTime、LocalDateTime
四、java比较器
java对象,正常情况只能 == 或者 != 比较。不能使用 > 或 <。
需要对多个对象进行排序,需要比较对象的大小
有 Comparable 和 Comparator 两种方式
Comparable
- String/包装类 实现了 Comparable 接口,重写了 compareTo 方法,给出了比较两个对象大小方式
- 从小到大的排列
- 重写 compareTo() 的规则:
当前的对象this大于形参对象obj,返回正数
相等,返回 0
反之,返回负数 - 自定义类需要排序,实现Comparable接口,重写compareTo(obj) 方法
Comparator
- 定制排序,在类没有实现Comparable接口,且不方便修改代码,或者实现了Comparable,但是不适合新的排序需求,可以考虑使用 Comparator
- 重写 compare(Object o1, Object o2) 方法,o1 < o2 返回正整数,o1 == o2 返回 0,o1 > o2 返回负整数
临时性的比较
Arrays.sort(goods, new Comparator() {public int compare(Object o1, Object o2) {if (o1 instanceof Goods && o2 instanceof Goods) {Goods g1 = (Goods)o1;Goods g2 = (Goods)o2;if (g1.getName().equals(g2.getName())) {return Double.compare(g1.getPrice(), g2.getPrice());} else {return g1.getName().compareTo(g2.getName());}}throw new RuntimeException("数据类型不一致");}});
五、System类
六、Math类
七、BigInteger与BigDecimal
BigInteger bigInteger = new Bigteger(12313123);BigDecimal bigDecimal = new BigDecimal(123.45);
枚举类和注解
枚举类
- 枚举类默认继承于 java.lang.Enum
- Enum类的常用方法:
valueOf(String objName): 返回枚举类中对象名为objName的对象toString: 返回当前枚举对象常量的名称- values() : 获取枚举对象数组
public class EnumTest {public static void main(String[] args) {Season summer = Season.SUMMER;System.out.println(summer); // 常量名System.out.println(Season.class.getSuperclass());}}enum Season {// 1. 提供当前枚举类的对象,多个对象之间用“,”分隔,末尾对象以 “;” 结束SPRING("春天", "春困"),SUMMER("夏天", "夏懒"),AUTUMN("秋天", "秋乏"),WINTER("冬天", "冬眠");// 2. 对象属性使用 private finalprivate final String seasonName;private final String seasonDesc;// 3. 私有化构造器,并为对象属性赋值private Season(String seasonName, String seasonDesc) {this.seasonName = seasonName;this.seasonDesc = seasonDesc;}// 4. 获取枚举类对象的属性public String getSeasonName() {return seasonName;}public String getSeasonDesc() {return seasonDesc;}}
注解
- @override : 限定重写父类方法,仅用于方法,(编辑器会强制校验子类是否构成重写,如果没有就会报错)
- @Deprecated : 表示所修饰的类或者方法已过时
- @SuppressWarning : 一直编辑器警告
- 自定义注解:
- 注解声明为 @interface
- 内部定义成员,通常使用value表示
- 可以指定成员的默认值,使用default定义
- 可以自定义注解没有成员,表明是一个标识作用
如果注解有成员,在使用注解时,需要指明成员的值
public @interface MyAnnotation {String value() default "hello";}
jdk提供的4种元注解:用来修饰其他注解的定义
- Retention : (常用)指定所修饰的注解的生命周期:SOURCE、CLASS(默认)、RUNTIME(只有声明为此类型的注解才能通过反射获取)
- Target : (常用)指定被修饰的注解能修饰哪些元素
- Documented : 所修饰的注解在被javadoc解析时,会保留下来
- Inherited : 所修饰的注解在被父类使用后,其子类会自动拥有Inherited所修饰的注解
jdk 8 中注解的新特性:可重复注解、类型注解
可重复注解
- 在 MyAnnotation 上声明 @Repeatable ,成员值为 MyAnnotations.class
- MyAnnotation的 Target 等元注解 和 Retention 和 MyAnnotations 相同
类型注解
- ElementType.TYPE_PARAMTER 表示注解能写在类型变量的声明语句中
- ElementType.TYPE_USE 表示注解能写在使用类型的任何语句中
集合
概述
集合、数组对多个数据进行存储操作的结构,简称Java容器
说明:此时的存储,主要指的是内存里面的存储,不涉及持久化的存储
- 2.1 数组优点
- 初始化后,长度确定
- 数组定义好,元素的类型就确定了,只能操作制定类型的数据
- 2.2 数组缺点
- 数组长度确定后无法修改
- 数组提供方法有限,对于添加、删除、插入等操作不方便
- 无法直接获取数组实际有效元素
- 无法满足对于无序、不可重复的需求
Collection接口:单列集合,用来存储一个一个的对象
- List 接口:存储有序的,可重复的数据
- ArrayList 作为List接口的主要实现类,线程不安全,效率高,底层使用 Object[] 存储
- LinkedList 对于频繁的插入、删除操作,使用此类效率比ArrayList高,底层使用双向链表
- Vector 作为List接口的古老实现类,线程安全,效率低
- Set 接口:存储无序的,不可重复的数据
- 三个实现类:
- HashSet : 主要实现类,线程不安全,可以存储 null 值
- LinkedHashSet : 遍历其内部数据时,可以按照添加的顺序遍历(不是有序)
- TreeSet : 可以按照添加对象的指定属性排序
- HashSet : 主要实现类,线程不安全,可以存储 null 值
- Set 接口没有额外定义新的方法,使用的都是Collection中声明过的方法
- HashSet(HashMap)
- 特性:
- 无序性:存储的数据在底层数组中不是按照数组索引的顺序添加的,根据数据的哈希值
- 不可重复性:保证添加的元素按照equals() 判断时,不能返回 true,即:相同的元素只能添加一个
- 添加元素过程,以HashSet (底层:数组 + 链表)为例:
- 计算元素a的哈希值,计算a在HashSet底层数组中的存放位置,
- 此位置没有其他元素,则a添加成功 —> 情况一
- 如果此位置上有元素b, 比较元素a和元素b的hash值:
- hash值不相同,a元素添加成功 —> 情况二
- hash值相同,进而需要元素 a 所在类的equals方法:
- equals() 返回 true ,元素a添加失败
- 否则就是添加成功 —> 情况三
- 对于添加成功的情况二和情况三,元素a已经存在指定索引位置上数据以链表方式存储。jdk7中元素a放到数组中,指向原来的元素;jdk8:原来的数组依然在数组中,指向a元素(7上8下)
- 计算元素a的哈希值,计算a在HashSet底层数组中的存放位置,
- 要求:
- 向Set中添加的数据,其所在的类一定要重写 hashCode() 和 equals()
- 重写的hashCode() 和 equals() 尽可能保持一致性:相等的对象必须具有相同的哈希值
- 小技巧:对象中用作 equals() 方法比较的 Field , 都应该用来计算 hashCode 值
- 特性:
- LinkedHashSet
- 作为 HashSet 的子类,再添加数据的同时,还维护了每个数据的两个引用,记录数据的前一个引用和后一个引用
- 优点:对于频繁的遍历操作, LinkedHashSet 效率高于 HashSet
- TreeSet
- 向 TreeSet 添加的数据,要求是相同类的对象
- 两种排序方式:自然排序(实现Comparable接口) 和定制排序(Comparator)
- 自然排序中,比较两个对象是否相同的标准:compareTo() 返回 0,不再是 equals()
- 定制排序中,比较两个对象是否相同的标准:compare() 返回0,不再是 equals()
- 三个实现类:
常用方法
- Collection
- contains(Object obj) 判断集合中是否包含obj
- 会在判断时调用当前类的 equals() 方法
- contains(Object obj) 判断集合中是否包含obj
- Collection
集合 Collection 存储的是对象,对象需要重写的方法有:
Map接口分类:双列集合,用来存储键值对
HashMap作为Map的主要实现类,线程不安全,效率高;存储null的key和value- 底层:数组+链表(jdk7及以前)数组+链表+红黑树(jdk 8)
LinkedHashMap- 保证遍历map元素时,可以按照添加的顺序实现遍历。
- 在原有HashMap底层结构基础上,增加了一对指针,分别指向前一个和后一个
- 对于频繁便利的操作,此类执行效率高于HashMap
TreeMap- 保证按照添加key-value对进行排序,实现排序遍历(按照key的自然排序或者定制排序)
- 底层使用红黑树
- Hashtable 作为古老的实现类,线程安全,效率低,不能春初null的key和value
- Properties 常用来处理配置文件。key和value都是String类型
- Map结构
- Map中的key:无序的,不可重复的,使用Set存储所有的key。(至于是哪种Set,则要看Map是哪种Map)(为了避免自定义类出现无法比较的问题,要求key所在的类要重写 equals() 和 hashCode() 方法)(以HashMap为例)
- Map中的value:无序的,可重复的,使用Collection存储所有的value
- 一个键值对:key-value构成了一个Entry对象
- Map中的Entry:无序的、不可重复的,使用Set存储所有的entry
HashMap的底层实现原理?以jdk7为例:
HashMap map = new HashMap();- 实例化以后,底层创建一个长度16的一维数组 Entry[] table
map.put(key1, value1);- …可能执行了多次put…
- 首先,调用key1所在类的
hashCode()计算key1的哈希值,此哈希值经过 某种算法计算 后,得到 Entry 数组中的存放位置- 如果此位置上数据为空,此时的key1-value1(Entry)添加成功 —> 情况一
- 如果此位置上数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值
- 如果key1的哈希值于已经存在的数据的哈希值都不相同,此时的key1-value1(Entry)添加成功 —> 情况二
- 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的
equals(key2)- 如果 equals() 返回 false:此时的key1-value1(Entry)添加成功 —> 情况三
- 如果 equlas() 返回 true:使用 value1 替换 value2
- 补充:关于情况二和情况三:此时的key1-value1和原来的数据以链表的方式存储
- 在不断添加的过程中,默认扩容方式:扩容为原来容量的2呗,并将原有的数据复制过来
jdk8 与 jdk7 底层实现区别:
- new HashMap() : 底层没有创建长度为16的数组初始化时
- jsk8底层数组是 Node[] , 不是 Entry[]
- 首次调用 put 方法时,底层创建长度为16的数组
- jdk7底层只有数组+链表。jsk8底层:数组+链表+红黑树
- 当数组某一个索引位置上的元素以 链表形式存在的数据个数 > 8 且当前 数组的长度 > 64 时,此时此索引位置上的所有数据改为使用红黑树存储(主要为了查询效率优化)
DEFAULT_INITIAL_CAPACITY : HashMap 的默认容量 16
- DEFAULT_LOAD_FACTOR : HashMap 的默认加载因子 0.75
- threshold : 扩容的临界值 = 容量 填充因子 (16 0.75 = 12)
- TREEIFY_THRESHOLD : Bucket中链表长度大于该默认值,转化为红黑树 8
- MIN_TREEIFY_CAPACITY 桶中的Node呗树化时最小的hash表容量 64
- 常用方法总结:
- 添加: put
- 删除:remove
- 修改:put
- 查询:get
- 长度:size
- 遍历:keySet() / values() / entrySet()
Collections:类似Arrays的集合帮助类
- Collection 和 Collections 的区别?一个是集合接口,另一个是帮助类
- copy 方法注意事项: ```java // 为了避免copy报错,所以这里需要使用以下语句规避,撑开dest数组 // 下面这行代码的结果:制造了一个和list有效元素数量相同长度的数组,但是每个值都是 null List dest = Arrays.asList(new Object[list.size()]);
Collections.copy(dest, list);
3. synchronizedXxx() 方法:该方法可将指定集合包装成线程同步的集合1. synchronizedCollection1. synchronizedList1. synchronizedMap1. synchronizedSet1. synchronizedSortedMap1. synchronizedSortedSet<a name="QMZTp"></a>### 泛型1. 集合中使用泛型1. 集合接口或集合类在jdk5.0时都改为带泛型的结构1. 实例化集合类时,可以指名具体的泛型类型1. 指名完以后,集合类或者接口中凡是定义类或接口时,内部结构使用到泛型的位置,都指定为实例化的泛型类型1. 如果没有指明泛型类型,则默认为 `java.lang.Object` 类型2. 注意点1. 泛型不同的引用不能相互赋值1. 静态方法中不能使用类的泛型1. 异常类不能声明为泛型1. catch 中不能使用泛型3. 泛型方法1. 方法中出现了泛型的结构,泛型参数和类的泛型参数没有任何关系1. 泛型方法和所属的类是不是泛型类没有关系1. 泛型方法在调用时,指明泛型参数的类型1. 泛型方法可以声明为静态的,泛型参数是在调用方法时确定的,不是再实例化类时确认```java/*** 如果这个类是泛型类,记得不能和类里面声明的那个泛型变量一样,要不同* <E> 返回类型<E> 方法名(E 形参名)*/public <E> List<E> copy(E[] arr) {ArrayList<E> list = new ArrayList<>();for(E e: arr) {list.add(e);}return list;}// useOrderList<Integer> list = order.copy(arr);
- 通配符:?
- 类A是类B的父类,G和G没有关系,二者共同的父类:
G<?> - 通配符只能添加null,无法添加其他数据
- 允许读取数据,数据类型为 Object
<? extends A>类型<=A<? super B>类型>=B
- 类A是类B的父类,G和G没有关系,二者共同的父类:
Z:
- 泛型不能是基本数据类型,所以如果需要使用基本数据类型要使用包装类
- 添加了泛型后,编译时就会进行类型检查,保证数据的安全
- 当使用与泛型相同类型时,可以省去强转操作。
- 泛型继承
- 由于子类继承泛型的父类时指明了泛型类型,实例化子类对象时,不再需要指名泛型
- 如果子类继承泛型的父类没有指明泛型类型,则子类依然为泛型类
public class SubClassName<T> extends ClassName<T> {}
IO流
FIle类
File类的使用
IO流分类
- 操作数据单位:字节流、字符流
Stream结尾的是字节;Reader或Writer结尾的是 字符流
- 数据的流向:输入流、输出流
- 流的角色:节点流、处理流
- 操作数据单位:字节流、字符流
- 流的体系结构 | 抽象基类 | 节点流(或文件流) | 缓冲流(处理流的一种) | | —- | —- | —- | | InputStream | FileInputStream | BufferedInputStream | | OutputStream | FileOutputStream | BufferedOutputStream | | Reader | FileReader | BufferedReader | | Writer | FileWriter | BufferedWriter |
文件处理相关函数内throws好还是try-catch好?
- try-catch比throws好,因为throws在指定位置遇到问题就会直接抛出,即使后面有
fileReader.close()也不会执行,会造成系统资源浪费,所以还是手动处理可以及时关闭流,避免资源占用比较好。 - 为了保证流资源一定可以执行关闭操作
- try-catch比throws好,因为throws在指定位置遇到问题就会直接抛出,即使后面有
文件操作
- 读取操作(P588)
- File类的实例化
- FileReader流的实例化
- 读入的操作
- 流的关闭
- 写出操作(P589)
- 当文件不存在时就创建此文件
- 当文件存在时,append 设置为 true 则在原有文件后添加,如果为 false 就直接覆盖整个文件(默认为false)
- 使用范围:
- 对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
- 对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt),使用字节流处理
- 使用 FileInputStream 处理文本文件可能出现乱码
- 读取操作(P588)
- 缓冲流
- 提高读写速度,(内部提供了一个缓冲区)
- 转化流
- 属于 字符流
- 分类
- InputStreamReader 字节输入流转换为字符的输入流
- OutputStreamWriter 字符的输出流转换为字节的输出流
- 提供字节流和字符流之间的转换
- 对象流
- 序列化: ObjectOutputStream 类保存基本类型数据或对象的机制
- 将内存中的java对象保存到磁盘中或通过网络传输
- 反序列化:ObjectInputStream 类读取基本数据或对象的机制
- 如果想让一个对象可以序列化,需要实现接口
Serializable- Serializable 标识接口,实际上没有任何变量和方法
- 需要实现 Serializable 的类提供一个全局常量:
public static final long ``_serialVersionUID _``= ``xxxL``; // long类型- 如果不提供上面的全局常量,java会自动设置一个默认值,但是这个默认值会随着类的修改而变更,进而导致序列化后无法反序列化
- 必须保证类内的所有属性都必须是可序列化的(默认情况下,基本数据类型可序列化)
- ObjectOutputStream / ObjectInputStream 不能序列化static和transient修饰成员变量
- 序列化: ObjectOutputStream 类保存基本类型数据或对象的机制
- RandomAccessFile
- 直接继承于java.lang.Object类,实现了 DataInput和DataOutput
- 既可以作为输入流又可以作为输出流
- mode:
r只读rw读写rwd读写,并同步文件内容的更新rws读写,同步文件内容和元数据的更新
- 写出文件时,如果不存在就创建,如果存在就对文件原有内容覆盖(默认情况下从头覆盖)
- 通过代码上的处理,可以实现“插入”效果
网络编程
- 网络通信协议
- 物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
- TCP/IP参考模型:物理和数据链路层(Link)、网络层(IP、ICMP、ARP)、传输层(TCP、UDP)、应用层(HTTP、FTP、Telnet、DNS)
- 网络编程要素:
- IP和端口号
- 通信协议
- 通信要素
- IP: 唯一标识Internet上的计算机
- Java中使用 InetAddress 类代表IP
- IP分类:ipv4、ipv6; 万维网、局域网;
- 本地地址:127.0.0.1 对应着:localhost
- 常用方法
- (静态方法)
getLocalHost() - (静态方法)
getByName() getHostName()getHostAddress
- (静态方法)
- 端口号:正在计算机上运行的进程
- 不同的进程有不同的端口号
- 范围从 0 ~ 65535
- 端口号和IP组成一个网络套接字:socket
反射
重难点:
- 获取Class实例
- 创建运行时类的对象
调用运行时类的指定结构
Class的实例对应着一个运行时类
- 加载到内存中的运行时类,会缓存一段时间,在此时间内,可以通过不同的方式来获取此运行时类。
- 获取 Class 的实例的方式(以下三种情况重要)
```java
// 方式一:调用运行时类的属性 .class
Class
class1 = Person.class;
// 方式二:通过运行时类的对象 Person p1 = new Person(); Class class2 = p1.getClass();
// 方式三:调用class的静态方法:forName(String classPath) Class class3 = Class.forName(“baseCode.P642.Person”);
4. 自定义类,使用系统类加载器加载;系统加载器的getParent(),获取扩展类加载器;调用扩展类加载器的getParent():无法获取引导类加载器,引导类加载器主要负责加载java的核心类库,无法加载自定义类。5. `newInstance()`1. newInstance创建了对应的运行时类的对象,实际上调用了类的空参构造器1. 如果类没有空参构造器,就会报错1. 权限设置必须设置为public1. javabean 中要求提供一个public的空参构造器,原因如下:1. 便于通过反射,创建运行时类的对象1. 便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器6. `getFields` 获取当前运行时类及其父类中声明为public访问权限的属性<br />`getDeclaredFields` 获取当前运行时类中声明的所有属性(不包含父类中声明的属性)<a name="NQ6HQ"></a>#### 调用运行时类中指定的结构:属性、方法、构造器<a name="rmoFF"></a>### JDK8新特性目录结构1. Lambda表达式1. 函数式接口1. 方法引用与构造器引用1. Stream API1. Optional类<a name="kaDMZ"></a>#### Lambda1. 匿名函数1. 举例:`(o1, o2) -> Integer.compare(o1, o2);`1. 格式:1. `->` : Lambda操作符 或 箭头操作符1. 左边:Lambda形参列表(接口中的抽象方法的形参列表)1. 参数类型可以省略,类型推断1. 如果只有一个参数,小括号可以省略3. 右边:Lambda体(重写抽象方法的方法体)1. 只有一条执行语句,可以省略 {} 和 return4. Lambda本质:作为 **函数式**_**接口**_** **的实例<a name="wrA8H"></a>#### 函数式接口1. 定义:如果一个接口中,只声明了一个抽象方法的接口,称为函数式接口1. 要想使用 Lambda 表达式,必须且只能用于 函数式接口1. 可以在一个接口上使用 @**`FunctionalInterface`** 注解,可以检查该接口是否是 函数式接口。如果违反了函数式接口的定义规则,该注解就会报错。1. 四大核心函数式接口1. 消费型接口 `Consumer` `void accept(T t)`1. 断定型接口 `Predicate` `boolean test(T t)`1. 供给型接口 `Supplier T` `get()`1. 函数型接口 `Function<T, R>` `R apply(T t)`<a name="nndTK"></a>#### 方法引用::1. 当要传递给 Lambda 体的操作,已经有实现的方法,可以直接使用方法引用1. 本质上就是 Lambda 表达式,而Lambda表达式作为函数式接口的实例,所以方法引用,也是函数式接口的实例。1. 使用格式: 类(或对象) :: 方法名1. 具体分为以下三种情况:1. 对象 :: 非静态方法1. 类 :: 静态方法1. 类 :: 非静态方法5. 要求:1. 要求接口中抽象方法的形参列表和返回值类型与 _方法引用_ 的方法的形参列表和返回值类型相同(针对 **4.a ** 和 **4.b**)<a name="2b6f1470"></a>#### 构造器引用和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。抽象方法的返回值类型即为构造器所属的类的类型。```javaBiFunction<Integer, String, Employee> func1 = (id, name) -> new Employee(id, name);System.out.println(func1.apply(1001, "Tom"));BiFunction<Integer, String, Employee> func2 = Employee::new;System.out.println(func2.apply(1002, "Jerry"));
数组引用
可以把数组看作一个特殊的类,则数组引用就和构造器引用一致了
Function<Integer, String[]> func1 = length -> new String[length];String[] arr1 = func1.apply(5);System.out.println(Arrays.toString(arr1));Function<Integer, String[]> func2 = String[]::new;String[] arr2 = func2.apply(10);System.out.println(Arrays.toString(arr2));
Stream API
- Stream关注数据的运算,与CPU相关。集合(Collection)关注的是数据的存储,与内存打交道。
- 特点
- Stream不会存储元素
- 不会改变源对象。相反,会返回一个持有结果的新Stream
- 操作是延迟执行的,等到需要结果的时候才执行
- 执行流程
- 实例化
- 中间操作(过滤、映射、。。。)
- 种植操作
- 说明
- 一个中间操作链,对数据源的数据进行处理
- 一旦执行终止操作,就执行中间操作链,并产生结果。之后不会再被使用
- 创建Stream
- 通过集合
default Stream<E> stream()返回一个顺序流default Steam<E> parallelStream()返回一个并行流
- 通过数组
- 调用 Arrays 类的 static
Stream stream(T[] array) 返回一个流 IntStream stream = Arrays.``_stream_``(arr)``;
- 调用 Arrays 类的 static
- Stream.of
Stream<Integer> stream = Stream.``_of_``(``1``, ``2``, ``3``, ``4``, ``5``, ``6``)``;
- 无限流
Stream.``_iterate_``(``0``, ``t -> t + ``2``).limit(``10``).forEach(System.``_out_``::println)``;Stream.``_generate_``(Math::``_random_``).limit(``10``).forEach(System.``_out_``::println)``;
- 通过集合
- Stream中间操作
- 筛选与切片
- filter
- skip
- limit
- distinct 去重
- 映射
- map 接受一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射为一个新的元素
- flapMap 将流中的每个流转换为另一个流,并把所有流连接成一个流
- 排序
- sorted 产生一个新流,按自然顺序排序
- sorted(Comparator com) 产生一个新流,按自然顺序排序
- 筛选与切片
- Stream终止操作
- 匹配与排序
- allMatch 检查是否匹配所有元素
- anyMatch 检查是否至少匹配了一个元素
- noneMatch 检查是否没有匹配所有元素
- findFirst 返回第一个元素
- findAny 返回当前流中任意一个元素
- max
- min
- forEach
- 规约
- reduce 将流中元素反复结合起来,得到一个值
- 收集
- 将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法 ```java // 内部迭代(内部使用迭代器遍历) employees.stream().forEach(System.out::println);
- 匹配与排序
// 使用集合的遍历操作(外部使用指针遍历) employees.forEach(System.out::println);
<a name="FTnyk"></a>#### Optional 类1. `Optional.of(T t)` 创建一个Optional实例,t必须非空1. `Optional.empty()` 创建一个空的Optional实例1. `Optional.ofNullable(T t)` t可以为null<a name="Tvugs"></a>### JDK9新特性<a name="CwT5m"></a>#### jshell<a name="Vsb9r"></a>#### 模块化<a name="nXoE7"></a>### JDK10新特性<a name="Uu1zn"></a>#### 局部变量类型推断var 后面必须赋值,不然无法推断类型。```javavar num = 10;var list = new ArrayList<Integer>();
