Java-基础
Java 基础
第一章
跨平台性
-
什么是跨平台性?
通过
Java语言编写的应用程序在不同的系统平台上都可以运行 -
原理是什么?
java程序的跨平台主要是指字节码文件可以在任何具有 JVM的计算机和电子设备上运 行, Java虚拟机中的 java解释器负责将字节码文件解释成特定的机器码进行运行。 Java语言本质上是不能跨平台的,真正实现跨平台的是 JVM,也就是Java虚拟机。写好的 Java源文件通过 Javac命令编译生成 class文件 (中间文件),然后 JVM对 class文件进行执行生成机器语言然后机器语言在平台中操作, Java在不同的平台下都有对应的不同版本的 JVM,JVM可以识别字节码文件从而运行。 跨平台性 
-
JDK、JRE、JVM之间的关系 -
JDK【Java Development Kit】: 是Java程序开发工具包, 包含 JRE和开发人员使用的工具,例如 java.exe 编译器和javadoc.exe可以自动为 java 的代码生成说明文档 -
JRE【Java Runtime Environment】:是Java程序的运行时环境, 包含 JVM和运行时所需要的核心类库 -
JVM【Java Virtual Machine】:Java虚拟机, 是运行所有 Java程序的假象计算机, 是 Java程序的运行环境, 是 Java最具有吸引力的特性之一, 我们编写的 Java代码, 都运行在 JVM之上。
JDK中包含JRE和 开发工具集JRE中包含JVM和JavaSE标准类库-
关系图
关系图 
-
JDK 安装
-
进入官网下载
下载流程 
-
安装
- 注意文件路径
不要出现中文、特殊符号,空格等
- 注意文件路径
-
环境变量配置
-
JAVA_HOME:JDK软件安装的 bin目录上一级 JAVA_HOME
-
CLASSPATH1
.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;
CLASSPATH
-
PATH1
2%JAVA_HOME%\bin
%JAVA_HOME%\jre\binPATH
-
检测是否安装成功
环境检测 
-
-
编写
HelloWorld.java步骤 编译 - 运行 1. 将 java 代码编写到扩展为 .java的文件中
2. 通过javac命令对该 java文件进行编译
3. 通过Java命令对生成的 class文件进行运行 
注释
-
注释分类
-
单行注释
// 单行注释 -
多行注释
/* 多行注释 */ -
文档注释
1
2
3
4
5
6
7
8
9
10/**
文档注释
@author: coder-itl
@version: 1.0.0
*/
// 文档生成指令: javadoc -d folder-name -author -version java-file-nale.java
// 文档注释的作用: 注释内容可以被 `JDK` 提供的`javadoc` 所解析,生成一套以网页文件形式体现的该程序的说明文档.
-
DOC 命令
-
常见的
doc命令 1
2
3
4
5
6
7cd ..
cd /
md 创建目录
rd 删除目录
del 删除文件
exit 退出 doc 命令行
dir 列出当前目录下的所有文件 -
制表符
\t- 在打印的时候,
把前面字符串的长度补齐到 8,或者8的整数倍。最少补 1个空格, 最多补 8个空格。
- 在打印的时候,
-
编码问题
CMD默认 GBK,可以将文件编码格式保存时设置为GBK- 运行时指定参数:
javac -encoding utf-8 Hello.java
内存分配
-
Java内存分配 -
栈:方法运行时使用的内存,
比如 main方法运行, 进入方法栈中执行 -
堆:存储对象或数组,
new来创建的, 都存储在堆内存 -
方法区: 存储可以运行的
class文件 -
本地方法栈:
JVM在使用操作系统功能的时候, 和我们无关的开发 -
寄存器: 给
CPU使用, 和我们开发无关
-
-
内存关系
内存关系 
-
内存分析
栈(basketball) 引用 堆(0x001) 
-
引用传递产生垃圾分析
垃圾产生
1
2
3
4
5
6
7
8
9A {
int price = 10;
}
A a = new A();
A b = new A();
a = b; // 引用传递
a.price = 1; // 修改内容在引用的关系中,
没有任何栈内存指向堆内存空间就将成为垃圾, 所有的垃圾空间会由 GC(Garbage Collector、GC即垃圾收集器) 回收并进行内存的释放, 但是频繁的 GC会引起严重的性能, 垃圾空间必须进行严格的控制, 尽量减少无用内存的开辟
第二章
计算机中的数据存储
- 计算机的存储规则
- 在计算机中,任意数据都是以
二进制的形式来存储的
- 在计算机中,任意数据都是以
- 二进制
- 由
0和 1组成, 代码中以 0b开头
- 由
关键字的定义和特点
- 关键字的定义: 是被
Java语言赋予了特殊含义,用做专门用途的 字符串【单词】 - 关键字的特点:
关键字中所有的字母都是小写
标识符
标识符的概念
java对各种 变量、方法和类等要素命名时使用的字符串序列称为标识符- 识别技巧:
凡是自己可以命名的地方都叫标识符
定义合法标识符的规则
如果不遵守如下的规则,编译不通过! 需要大家严格遵守
- 由
26个英文字母大小写, 0-9,_或 $ 组成 - 数字不可以开头
- 不可以使用关键字和保留字,
但能包含关键字和保留字 Java中严格区分大小写, 长度无限制 - 标识符不能包含空格
Java 中的命名规范
-
包名: 多单词组成时所有字母都小写 -
类名,: 多单词组成时,接口名 所有单词首字母大写 -
变量名、方法名: 多单词组成时,第一个单词首字母小写,第二个单词开始每个首字母大写 -
常量名: 所有字母都大写,多单词时每个单词用下划线连接 如果不遵守如上的规范,编译可以通过! 但是建议大家遵守
变量
-
变量的作用: 用于在内存中保留数据
-
使用:
数据类型 变量名 = 变量值;注意: 变量使用必须
先声明后使用,变量都定义在其作用域内, 在作用域内, 它是有效的, 出了作用域就无效了, 同一个作用域内,不可以声明两个同名的变量
数据类型
数据类型分类
-
分类
-
Java语言的数据类型分为: 基本数类型、引用数据类型 -
基本数据类型又分为四类八种
- 整数
byte的取值范围: -128 ~ 127
- 浮点数
- 字符
- 布尔
整数和小数取值范围大小关系:
double > float > long > int > short > byte - 整数
数据类型分类 
-
-
值传递与引用传递
- 基本数据类型: 所有的数据操作都是采用
值传递,相当于把数据给对方了一个副本信息, 副本信息的修改不影响原始的数据内容, 这些基本数据类型也是构成程序开发的基本环节 - 引用数据类型: 是进行内存的数据操作,给的内存使用权
- 基本数据类型: 所有的数据操作都是采用
-
成员变量与局部变量
在
方法体外,声明的变量称为类体内 成员变量在方法体内部声明的变量称为
局部变量成员变量与局部变量 
数据类型之间的运算规则
前提: 这里只讨论7 种基本数据类型变量间的运算,boolean
-
自动类型提升
结论: 当容量小的数据类型的变量与容量大的数据类型的变量做运算时,
结果自动提升为容量大的数据类型 byte、char、short–>int --> long --> float --> double特别的: 当
byte、char、short三种类型的变量做运算时结果为 int型 -
强制数据类型转换【
自动提升类型的逆运算】- 需要使用强制符
() - 注意点: 强制类型转化可能损失数据精度
- 需要使用强制符
-
字符串类型
String不是基本数据类型, 属于引用数据类型, 翻译为: 字符串- 声明
String类型的变量时, 使用一对双引号 " "
键盘录入
-
Java帮我们写好了一个类叫 Scanner,这个类就可以接受键盘输入的数字 -
使用步骤
-
导包
( 找到 Scanner 这个类在哪) -
创建对象
(表示我们要开始使用这个对象了) -
接受输入
1
2
3
4
5
6
7
8
9
10
11
12
13package org.example;
// 1. 导包
import java.util.Scanner;
public class ScannerDemo {
public static void main(String[] args) {
// 2. 创建对象
Scanner scanner = new Scanner(System.in);
// 3. 使用 -> 接受数据
int age = scanner.nextInt();
System.out.println("我的年龄是: " + age);
}
}
-
算数运算符
运算符概念
- 运算符是对字面量或者变量进行操作的符号
表达式
-
用
运算符把字面量或者变量连接起来,符合 Java语法的式子就可以称为表达式。不同运算符连接的表达式体现的是不同类型的表达式。 -
Eg:1
2
3
4
5
6public static void main(String[] args) {
int a = 10;
int b = 20;
// +: 运算符,并且是算数运算符, a + b: 是表达式,由于 + 是算数运算符, 所以这个表达式叫算数表达式
int c = a + b;
}
运算符分类
-
算数运算符:
+、-、x、/、%-
%(取模、取余数)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 符合小学数据运算规则: + - *
public static void main(String[] args) {
int a = 30;
int b = 10;
int sum = a + b;
int dif = a - b;
int mul = a * b;
int div = a / b;
int mod = a % b;
System.out.println("两数之和: " + sum); // 40
System.out.println("两数之差: " + dif); // 20
System.out.println("两数之积: " + mul); // 300
System.out.println("两数之商: " + div); // 3
System.out.println("两数取模: " + mod); // 30 /10 = 3...0【%: 0】
} -
练习: 键盘录入一个三位数、将其拆分为
个位、十位、百位,打印在控制台 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
/**
* 分析:
* 个位: 数值 % 10
* 十位: 数值 / 10 % 10
* 百位: 数值 / 100 % 10
*/
public class ScannerDemo {
public static void main(String[] args) {
// 键盘录入
Scanner scanner = new Scanner(System.in);
// 接受数据
System.out.println("请输入任意的三位数: ");
int num = scanner.nextInt();
// 表示num 除以 10 的余数,即个位上的数字。
int digits = num % 10;
// 表示num 除以 10 再整除 10 的余数,即十位上的数字。
int tDigits = num / 10 % 10;
// 表示num 除以 100 再整除 10 的余数,即百位上的数字。
int hDigits = num / 100 % 10;
System.out.println("个位数: " + digits);
System.out.println("十位数: " + tDigits);
System.out.println("白位数: " + hDigits);
}
}1 2 

-
不同数据类型之间的运算
1
2
3
4
5
6
7
8public static void main(String[] args) {
int a = 10;
double b = 20.3;
// double: 30.3
double c = a + b;
// 结论;小数据类型向大数据类型看齐
System.out.println("不同数据类型的两数相加: " + c);
} -
当
+操作中输出字符串, 这个 +是字符串连接符, 而不是 算数运算符了,会将前后的数据进行拼接, 并产生一个新的字符串。 1
2
3
4
5
6public static void main(String[] args) {
int a = 20;
String b = "23";
// 输出: 2023
System.out.println(a + b);
}1
2
3
4
5// 连续进行 + 操作时,从左到右逐个执行
public static void main(String[] args) {
// 输出: 43java
System.out.println(20 + 23 + "java");
}
-
-
自增自减运算符
-
++/--无论是放在变量的前边还是后边,单独写一行 结果是一样的 -
参与计算
-
后
++: 先用后加1
2
3
4
5
6
7
8
9
10public class ScannerDemo {
public static void main(String[] args) {
int a = 10, b = 20;
// 1. 先用: c = a
int c = a++;
// 2. (用)后 ++: a++ => 11
System.out.println(a);
}
} -
前
++: 先加后用1
2
3
4
5
6
7
8public class ScannerDemo {
public static void main(String[] args) {
// 先用: ++b => 21
int d = ++b;
// 21
System.out.println(b);
}
}
-
-
-
赋值运算符
符号 作用 说明 =赋值 int a = 10,将 10赋值给变量 a+=加后赋值 a+=b,将 a+b的值给 a*=乘后赋值 a*=b,将 a*b的值给 a+=、*=、/=、%=底层都隐藏了一个强制类型转换 -
关系运算符
(比较运算符) 符号 说明 ==a==b,判断a和 b的值是否相等, 成立为 true、不成立为false!=a!=b,判断a和 b的值是否 不相等,成立为 true、不成立为false>a>b,判断a是否大于 b,成立为 true、不成立为false>=a>=b,判断a是否大于等于 b的值, 成立为 true、不成立为false<a<b,判断a是否小于 b的值, 成立为 true、不成立为false<=a<=b,判断a是否小于等于 b的值, 成立为 true、不成立为false -
逻辑运算符
符号 作用 说明 &逻辑与 并且,两边都为真, 结果才是真 ` ` 逻辑或 ^逻辑异或 相同为 false,不同为true!逻辑非 取反 -
短路逻辑运算符
符号 作用 说明 &&短路与 结果和 &相同, 但是有短路效果 ` ` -
注意事项
&、|无论左边true、false,右边都要执行&&、||如果左边能确定整个表达式的结果,右边将不再执行 &&: 左边为false,右边不管是真是假,整个表达式的结果一定是 false||: 左边为true,右边不管是真是假,整个表达式的结果一定是 true
-
练习
-
需求: 数字
6是一个真正伟大的数字, 键盘录入两个整数。其中一个为 6,最终结果输出为true。如果他们的和为6的倍数。最终结果输出 true。其他情况都是false1
2
3
4
5
6
7public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int num1 = scanner.nextInt();
int num2 = scanner.nextInt();
boolean result = num1 == 6 || num2 == 6 || (num1 + num2) % 6 == 0;
System.out.println(result);
}
-
-
-
三元运算符
-
格式:
关系表达式?表达式 1: 表达式 2 -
范例: 求两个数的较大值
1
2
3
4
5
6public static void main(String[] args) {
int num1 = 20;
int num2 = 30;
int result = num1 > num2 ? num1 : num2;
System.out.println(result);
}执行流程 ( num2 = 30)
-
-
运算逻辑优先级
( )优先于所有
++ 运算符
-
前
++与后 ++1
2
3
4
5
6
7
8
9
10
11
12
13public class ReviewTest2 {
public static void main(String[] args) {
int a1 = 10;
int b1 = a1++; // = 属于赋值运算,所以经历了"先运算赋值 的操作"
// 结论: (后)++: 先运算 ,然后在自增
System.out.println(b1); // 输出: 10
int c1 = 10;
int d1 = ++c1; // = 属于赋值运算,所以经历了"自增 1, 在运算'赋值操作' "
// 结论: (前)++: 先自增 1,然后在运算
System.out.println(d1); // 输出: 11
}
}-
案例练习
案例练习 
-
-
逻辑运算符
操作符 描述 例子 && 称为逻辑 与运算符,当且仅当两个操作数都为真, 条件才为真 (A && B) 为假 || 称为逻辑 或运算符,如果任何两个操作数 任何一个为真,条件为真 (A || B) 为真 ! 称为逻辑 非运算符,用来 反转操作数的逻辑状态,如果条件为 true,则 逻辑非运算将得到false!(A&&B) 为真 -
三元运算符
三元运算符 
- 三元运算符与
if-else的联系与区别 - 三元运算符可以简化
if-else语句 - 三元运算符必须返回一个结果
if后的代码块可以有多个语句
- 三元运算符可以简化
- 三元运算符与
原码、反码、补码
-
原码
十进制数据的二进制表现形式,
最左边是符号位, 0为正, 1为负 原码 在线计算器 (http://www.atoolbox.net/Tool.php?Id=952) 

-
反码
正数的补码反码是其本身
(正数的补码和反码与原码一致), 的反码是符号位保持不变,负数 其余取反 负数以原码的补码形式表达,先计算正数的原码, 改变符号位, 就是负数的原码 正数的反码 ( +5)负数的反码 ( -5)5: 0101-5: 1010
-
补码
正数的补码是其本身
(原码形式), 负数的补码是在其反码的基础上 +1+5-50101
-
其他运算符
运算符 含义 说明 &逻辑与 0为 false,1为 true| 逻辑或 0为 false,1为 true<<左移 向左移动, 低位补 0>>右移 向右移动, 高位补 0(原来是正数) / 1(原来是负数)-
按位与
&,二进制为补码形式 
-
按位或
按位或 
-
<<左移 
-
>>右移 
-
流程控制语句
分类
- 顺序结构
- 分支结构
- 循环结构
顺序结构
-
顺序结构: 顺序结构是
Java程序默认的执行流程,按照代码的先后顺序, 从上到下依次执行 1
2
3
4
5
6
7// 输出依次是 1 2 3 4 顺序执行
public static void main(String[] args) {
System.out.println(1);
System.out.println(2);
System.out.println(3);
System.out.println(4);
}
分支结构
IF(判断)
-
语法与执行流程
1
2
3
4
5public static void main(String[] args) {
if (关系表达式) {
语句体;
}
}- 执行流程
- 首先计算关系表达式的值
- 如果关系表达式的值为
true就执行语句体 - 如果关系表达式的值为
false就不执行语句体 - 继续执行后面的其他语句
- 执行流程
-
需求:键盘录入女婿酒量,
如果大于 2斤, 老丈人给出回应, 反之不回应 1
2
3
4
5
6
7
8public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入: ");
int num = scanner.nextInt();
if (num > 2) {
System.out.println("酒水喝多了对身体不好啊,小伙子.................");
}
}- 注意点
- 大括号的开头可以另起一行书写,但是建议写在第一行的末尾
- 如果语句体中,
只有一句代码, 大括号可以省略, 但是不建议省略 - 如果对一个布尔类型的变量进行判断,
不要用 ==号, 直接把变量写在小括号内
- 注意点
-
闰年判断
-
格式二
1
2
3
4
5
6
7public static void main(String[] args) {
if(关系表达式){
语句体1;
}else {
语句体2;
}
}格式二 
- 执行流程
- 首先计算关系表达式的值
- 如果关系表达式的值为
true就执行语句体 1 - 如果关系表达式的值为
false就执行语句体 2 - 继续执行后面的其他语句
- 执行流程
-
第三种格式
第三种格式 
Switch(选择)
- break
可以在 switch- case 结构中, 表示一旦执行到此关键字就跳出 switch- case 结构 - switch- case
结构中的表达式, 只能执行如下六种 数据类型;如: byte、short、int、, 枚举类型、String 等类型 - case
之后只能声明常量, 不能声明范围 -
JDK12新特性写法 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// >=JDK12+
public static void main(String[] args) {
int number = 10;
switch (number) {
// 没有 case 穿透
case 1 -> {
System.out.println(1);
}
case 10 -> {
System.out.println(10);
}
default -> {
System.out.println("default.....");
}
}
}
循环控制结构
For
-
语法
1
2
3for (初始化语句; 条件判断; 条件控制语句) {
循环体语句;
}-
执行流程
流程 
-
描述
- 执行初始化语句
- 执行条件判断语句,看其结果是
true还是 false- 如果是
false,循环结束 - 如果是
true,执行循环体语句
- 如果是
- 执行条件控制语句
- 回到
②继续执行条件判断语句
-
-
for循环 -
练习:
使用 for循环遍历 100以内的 奇数, !并计算所有的奇数的和并输出 1
2
3
4
5
6
7
8
9public static void main(String[] args) {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if (i % 2 != 0) {
sum += i;
}
}
System.out.println(sum);
} -
流程
1
2
3
4
5
6
7
8
9
10
11for (int i = 0; i < 3; i++) {
System.out.println(i);
}
// 执行流程
int i = 0; // ①
i<3 // ②
System.out.println(i);// ③
i++; // ④
...
-
-
九九乘法口诀表
1
2
3
4
5
6
7// 已知九九乘法口诀表 9 行 9 列
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
System.out.print(j + "*" + i + "=" + i * j + "\t");
}
System.out.println();
} -
打标签
1
2
3
4
5
6
7flag:for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
if(i==2 && j==3)
break flag;
}
System.out.println();
}
While
-
语法
1
2
3
4
5
6
7public static void main(String[] args) {
初始化语句;
while (条件判断语句) {
循环体语句;
条件控制语句;
}
}-
执行流程
while流程 
-
Do While
-
语法
1
2
3
4
5
6
7
8public static void main(String[] args) {
初始化语句;
do {
循环体语句;
条件控制语句;
}
while (条件判断语句);
} -
执行流程
执行流程 
-
特点: 先执行后判断
For 和 While 的对比
-
相同点
相同点: 运行规则都是一样的
-
for和 while的区别 -
for循环中, 控制循环的变量, 因为归属 for循环的语法结构中, 在 for循环结束后, 就不能再次被访问到了 控制循环的变量 i
-
while循环中, 控制循环的变量, 对于 while循环来说不归属其语法结构中, 在 while循环结束后, 该变量还可以继续使用 控制循环的变量 ( flag)
-
-
练习
-
需求: 世界最高山峰是珠穆朗玛峰
( 8844.43m = 8844430mm),加入我们有一张足够大的纸, 它的厚度是 0.1mm,请问, 我折叠多少次, 可以折成珠穆朗玛峰的高度? 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public static void main(String[] args) {
// 珠穆朗玛峰高度
double height = 8844430;
// 纸张初始厚度
double paper = 0.1;
// 折纸次数
int count = 0;
while (paper < height) {
// 折纸一次,厚度翻倍
paper *= 2;
// 统计次数 ++
count++;
}
// 27
System.out.println(count);
} -
回文数判断
-
需求: 给你一个整数
x,如果 x是一个回文数, 打印 true,否则,返回 false -
回文数: 回文数是指正序
(从左向右) 和倒序 (从右向左) 读都是一样的整数 -
Eg: 121(true),123(false)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public static void main(String[] args) {
// 1. 定义一个数字
int x = 1213;
// 定义一个临时变量用于记录 x 原来的值,用于最后进行比较
int temp = x;
// 记录倒过来的结果
int num = 0;
while (x != 0) {
// 从右往左获取每一个数字
int ge = x % 10;
// 修改一下 x 记录的值
x = x / 10;
// 把当前获取到的数字拼接到最右边
num = num * 10 + ge;
}
// 比较
System.out.println(num == temp);
}理解 
-
-
求商和余数
-
需求: 给定两个整数,被除数和除数
( 都是整数,),且不超过 int 的范围 将两数相除, 要求 不使用乘法、除法和% 运算 1
2
3
4
5
6
7
8
9
10
11
12
13
14public static void main(String[] args) {
// 定义被除数
int diviend = 2;
// 定义除数
int divisor = 3;
// 定义 商
int count = 0;
while (diviend >= divisor) {
diviend -= divisor;
count++;
}
System.out.println("商: " + count);
System.out.println("余数: " + diviend);
}
-
-
循环高级练习
-
无限循环
1
2
3
4
5
6
7
8
9
10
11
12// 不好会停止
public static void main(String[] args) {
for (; ; ) {
System.out.println("无限循环体");
}
while (true) {
System.out.println("无限循环体");
}
do{
System.out.println("无限循环体");
}while (true);
} -
continue1
2
3
4
5
6
7
8
9public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
if (i == 3) {
// 结束本次循环,继续下次循环
continue;
}
System.out.println("小老虎正在吃第" + i + "个包子");
}
}continue
-
break1
2
3
4
5
6
7
8
9public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
System.out.println("小老虎正在吃第" + i + "个包子");
if (i == 3) {
// 结束整个循环
break;
}
}
}break
-
练习
-
逢
7过, continue -
求平方根: 键盘录入一个大于等于
2的整数 X,计算并返回 X的平方根。结果只保留整数部分, 小数部分将被舍去 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public static void main(String[] args) {
// 键盘录入一个大于等于`2`的整数`X`,计算并返回`X`的平方根。结果只保留整数部分, 小数部分将被舍去
Scanner scanner = new Scanner(System.in);
// 测试三组数据 4 10 20
int count = 0;
while (count < 3) {
System.out.print("请输入一个 2 的整数倍的数: ");
int num = scanner.nextInt();
for (int i = 1; i <= num; i++) {
if (i * i == num) {
System.out.println(i + " 就是 " + num + " 的平方根");
break;
} else if (i * i > num) {
System.out.println((i - 1) + " 就是 " + num + " 平方根的整数部分");
break;
}
}
count++;
}
}求平方根 
-
-
需求: 键盘录入一个正整数
x,判断该整数是否为一个质数 -
质数: 如果一个整数只能被
1和本身整数, 那么这个数就是质数。否则这个数叫做 合数-
实现一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入一个正整数: ");
int num = scanner.nextInt();
// 标记 num 最初为一个质数
boolean flag = true;
for (int i = 2; i < num; i++) {
// i: 依次表示这个范围内的每一个数字
if (num % i == 0) {
flag = false;
break;
}
}
// 当循环内的数字都判断了,此时才能说明这个数是一个 质数
if (flag) {
System.out.println(num + " 是一个质数");
}
} -
实现二
-
-
Random
-
生成随机数
1
2
3
4
5
6
7
8public static void main(String[] args) {
Random r = new Random();
for (int i = 0; i < 100; i++) {
// r.nextInt(10): 0 ~ 9
int anInt = r.nextInt(10);
System.out.print(anInt + " ");
}
}[0,10)
-
生成指定区间的数字
1
2
3
4
5
6
7
8
9
10
11
12// [a,b] => int round = b-a+1
public static void main(String[] args) {
Random r = new Random();
for (int i = 0; i < 100; i++) {
// [7 ~ 15]
int round = 15 - 7 + 1;
int start = 7;
// r.nextInt(round) => 0 ~ round, +start => (0 ~ round)+start
int anInt = r.nextInt(round) + start;
System.out.print(anInt + " ");
}
}生成指定区间的数字 
IDEA-模块 + 操作
-
模块导入
( ctrl+alt+shift+s)设置 
-
选择导入
点击加号, 选择导入 
-
选择覆盖
选择覆盖 
-
如果出现
SDK,选择如下SDK
-
模块移除
( 也可以直接按下)delete,只是项目移除, 磁盘中依然存在 remove
-
创建新模块
创建新模块 
第三章
数组
数组的概述
- 数组的理解: 数组
( Array),是多个相同数据类型按照一定的 顺序排列 的集合,并使用同一个名字命名; - 数组的特点:
数组是有序排列的 - 数组属于
引用类型数据的变量, 既可以是基本数据类型, 也可以是引用数据类型 - 创建数组对象会在内存中开辟一整块连续的空间
-
数组长度一旦确定, 就不能再修改 - 数组的下标从 0 开始
一维数组的使用
-
数组的遍历
-
数组遍历:将数组中所有的内容
取出来,取出来之后可以 ( 打印、求和、判断...)遍历: 指的是取出数据的过程
-
-
数组的声明和初始化
-
静态初始化: 数组的初始化和数组元素的赋值同时进行
初始化: 就是在内存中,
为数组容器开辟空间, 并将数据存入容器的过程 1
2
3
4
5
6
7public class ArrayTest {
public static void main(String[] args) {
// 静态初始化: 数组的声明和赋值同时进行
int[] arrays = new int[]{100, 90, 89, 40};
System.out.println(arrays);
}
} -
动态初始化: 数组的初始化和数组元素的赋值
分开进行1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class ArrayTest {
public static void main(String[] args) {
// 动态初始化: 数组的声明和赋值分开进行
String[] names = new String[4];
for (int i = 0; i < names.length; i++) {
// 数组赋值
names[i] = "names1" + i;
System.out.println(names[i]);
}
// 调用数组指定位置的元素 4
System.out.println("数组指定位置名称: " + names[3]);
// 获取数组的长度
int arrayLength = names.length;
System.out.println("数组长度的获取: " + arrayLength);
}
}
-
数组默认元素的初始值
-
基本数据类型
-
数组元素是
整型:0 -
数组元素是
浮点型:0.0 -
数组元素是
char:0 -
数组元素是
布尔类型:false
-
-
引用数据类型
数组元素是
String类型: 默认值为 null
数组中的常见操作
-
求数组中数组元素的
最大值、最小值、平均值、总和等-
最大值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public static void main(String[] args) {
Random r = new Random();
int[] arr = new int[]{0, 89, 5, 100};
// 遍历数组求最大值
int max = arr[0];
// i 从 1 开始,因为 max=arr[0],自己和自己没有任何影响,影响效率
for (int i = 1; i < arr.length; i++) {
// i: 是索引,arr[i] 是元素值
if (arr[i] > max) {
max = arr[i];
}
}
// 当循环结束后,记录的就是最大值
System.out.println(max);
} -
最小值
1
2
3
4
5
6
7
8
9
10
11
12
13
14public static void main(String[] args) {
Random r = new Random();
int[] arr = new int[]{0, 89, 5, 100};
// 遍历数组求最大值
int min = arr[0];
for (int i = 1; i < arr.length; i++) {
// i: 是索引,arr[i] 是元素值
if (arr[i] < min) {
min = arr[i];
}
}
// 当循环结束后,记录的就是最小值
System.out.println(min);
} -
遍历数组求和
-
求出所有数据的和
-
求出所有数据的平均数
-
统计有多少个数据比平均值小
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
39public static void main(String[] args) {
Random r = new Random();
int[] arr = new int[10];
// 1. 生成 10 个随机数
for (int i = 0; i < arr.length; i++) {
// 1 ~ 100
int num = r.nextInt(100) + 1;
arr[i] = num;
}
// 遍历数组
System.out.print("[");
for (int i = 0; i < arr.length; i++) {
if (i == arr.length - 1) {
System.out.print(arr[i]);
} else {
System.out.print(arr[i] + ",");
}
}
System.out.println("]");
// 2. 求平均数
int sum = 0, avg = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
avg = sum / arr.length;
System.out.println("所有数据的和: " + sum);
System.out.println("所有数据的平均数: " + avg);
// 3. 统计有多少个数据比平均值小
int count = 0;
for (int i = 0; i < arr.length; i++) {
if (arr[i] < avg) {
count++;
}
}
System.out.println("比平均数小的个数为: " + count);
}结果 
-
-
-
数组元素的反转
-
要求不能使用新数组,
就用原来唯一 一个数组 -
解析:
-
数组元素反转,
其实就是对称位置的元素交换 -
两个元素的交换流程图
数组元素的反转 
-
-
交换数组中的数据
-
需求: 定义一个数组,
存入 1、2、3、4、5按照要求交换索引对应的元素 -
交换前
1、2、3、4、5 -
交换后:
5、4、3、2、1 -
分析
定义两个变量 i,j,记录索引, 进行交换 
1
2
3
4
5
6
7
8
9
10
11
12public static void main(String[] args) {
int[] arr = {95, 80, 70, 18, 13, 39, 70, 71, 6, 58};
for (int i = 0, j = arr.length - 1; i < j; i++, j--) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
-
-
Java 的内存 【Java
的内存划分为5
个部分】
栈: 存放的都是方法中的局部变量(stack) (方法的运行一定要在栈当中运行) - 局部变量: 方法的参数或者是方法
{}内部的变量 - 作用域: 一旦超出作用域,
立刻从栈的内存中消失
- 局部变量: 方法的参数或者是方法
堆: 凡是(Heap) new出来的东西都在堆当中- 堆内存里面的东西都是地址值:
16进制 - 堆内存里面的数据都有默认值
- 堆内存里面的东西都是地址值:
方法区: 存储(Method area ) .class相关信息包含放法的信息 - 本地方法栈: 与操作系统相关
- 寄存器: 与
与CPU 相关
数组常见异常问题
-
数组越界
-
空指针异常
数组必须进行
new初始化才能使用其中的元素,如果只是赋值了一个 null,没有进行new创建,那么将引起空指针异常空指针异常 
- 引起的原因: 没有进行
new操作 - 解决方案: 补上
new
- 引起的原因: 没有进行
方法
-
方法注意事项
- 方法定义的先后顺序无所谓
- 方法的定义不能产生嵌套包含关系
- 方法定义好了之后,
不会执行的, 一定要进行方法的 调用
-
方法的重载
-
定义: 在同一个类中,
允许存在一个以上的`同名`方法, 只需要它们的参数类型不同即可 -
判断方法是否重载: 跟
方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系
-
-
方法的重写
-
重写: 子类继承父类以后、
可以对父类中同名同参数的方法、进行覆盖操作 -
应用: 重写以后,
当创建子类对象以后, 通过子类调用父子类中的同名同参数的方法时, 实际执行的是子类重写父类的方法
-
-
定义方法的完整格式
-
方法的声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18权限修饰符 返回值类型 方法名 (形参列表){
// 方法体
return 返回值;
}权限修饰符:
返回值类型: 也就是方法最终产生的数据结果是什么类型
方法名称: 方法的名字,小驼峰命名
参数类型: 进入方法的数据类型是什么类型
参数名称: 进入方法的数据对应的变量名称
约定俗称:
子类中的叫重写方法
父类中的叫被重写方法 -
子类中重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同 -
子类重写的方法的权限不小于父类中被重写的方法的权限修饰符
-
特殊情况: 子类不能重写父类中声明为 private 权限的方法
-
-
-
返回值类型
- 父类被重写的方法的返回值类型是
void,则子类重写的方法的返回值类型只能是void类型 - 父类被重写的方法是 的返回值类型是 A 类型,
则子类中重写的方法的返回值类型可以是 A 类型或者A类的子类 - 父类被重写的方法的返回值类型是
基本数据类型,则子类重写的方法的返回值类型必须是相同的。
- 父类被重写的方法的返回值类型是
-
return作用: - 停止当前方法
- 将后面的返回值还给调用处
-
方法重载和重写的区别
重载和重写的区别 
- 重载: 发生在同一个类中,
方法名必须相同, 参数类型不同, 个数不同, 顺序不同, 方法返回值和访问修饰符可以不同, 发生在编译时 - 重写: 发生在父子类中,
方法名 %}、参数列表 %}必须相同 、 返回值范围 %}小于等于`` 父类 %}, 抛出的异常范围小于等于父类, 如果父类方法访问修饰符为` private %} 则子类中不能重写该方法
- 重载: 发生在同一个类中,
-
方法调用的三种格式
-
单独调用:
方法名称(参数) -
打印调用:
System.out.println(方法名称(参数)) -
赋值调用:
数据类型 变量名称 = 方法名称(参数) 注意: 返回值类型固定为
void,这种方法只能单独调用, 或者不能进行打印调用 赋值调用void
-
-
方法执行的流程图解析
流程图解析 
方法参数的传值机制
基本数据类型和引用数据类型
-
基本数据类型内存图
变量中存储的是真实地数据值 
-
引用数据类型内存图
引用数据类型的变量存储的是地址值 
方法形参的传递机制
-
参数
-
形参:
用来接收调用该方法时传递的参数。只有在被调用的时候才分配内存空间,一旦调用结束,就释放内存空间。因此仅仅在方法内有效。 -
实参:
传递给被调用方法的值,预先创建并赋予确定值。
-
-
值传递机制
值传递机制 值传递机制 如果参数是基本数据类型, 此时实参赋给形参的是真实存储的 数据值如果变量是引用数据类型, 此时赋值的变量是所保存的数据的 地址值
第四章
类和对象
- 类:
类是一组相关属性和行为的集合,可以看作是一类事物的模板, 使用事物的属性特征和行为来描述该类事物 - 对象:
对象是类的一个实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。 - 类与对象的关系:
- 类是对一类事物的描述,
是抽象的 - 对象是一类事物的实例,
是具体的 - 类是对象的模板,
对象是类的实体
- 类是对一类事物的描述,
Java 类及类的成员
-
属性: 对应类中的成员变量
-
方法: 对应类中的成员方法
1
2
3
4
5
6
7
8
9
10
11public class Phone {
// 属性
String brand;
Double price;
// 行为
public static void call() {
System.out.println("我可以打电话");
}
}
定义类的规范
- 类名
首字母建议大写,需要见名知意,驼峰模式 - 一个
Java文件中可以定义多个 class类, 且只能一个类是 public修饰, 而且 public修饰的类名必须成为 代码的文件名 - 成员变量的完整定义格式是:
;一般无需指定初始化值,修饰符 数据类型 变量名称 = 初始值 存在默认值
封装
-
封装的意义: 在于明确表识出允许外部使用的所有成员函数和数据项内部细节对外调用透明,外部调用无需修改或者关心内部实现
JavaBen的属性私有, 提供 setter和 getter对外访问,因为属性的赋值或者获取逻辑只能由 javaben本身决定,而不能由外部随意修改 1
2
3
4
5private String name;
public void setName(String name){
// 该 name 有自己的命名规范,明显不能由外部直接赋值
this.name = "coder-itl_"+ name;
} -
详细原因说明
-
问题引出
1
2
3
4
5public class BookDemo {
private String bookName; // 书籍名称
private double bookPrice; // 书籍价格
// 省略 getter setter
}1
2
3
4
5
6
7
8
9
10
11
12
public class MainApplication {
public static void main(String[] args) {
BookDemo bookDemo = new BookDemo();
bookDemo.setBookName("Java从入门到精通");
// TODO: 这个价格被定义为了负数
bookDemo.setBookPrice(-99.99);
// 输出: 当前阅读书籍: Java从入门到精通 书籍的价格为: -99.99
System.out.println("当前阅读书籍: " + bookDemo.getBookName() + " 书籍的价格为: " + bookDemo.getBookPrice());
}
} -
private关键字 相当于秘密通道,目的是为了获取金库的 金子,要获取到金子,就要通过通道 setter、getter
-
this 关键字的使用
-
this当方法的局部变量和类的成员变量重名的时候,
根据就近原则, 优先使用局部变量, 如果需要访问本类中的成员变量, 需要使用格式: this.成员变量 使用 问题引出 

注意: 通过谁调用的方法,
谁就是 this
理解” 万事万物皆对象”
- 在
java语言范畴中,我们都将功能结构等封装到类中, 而通过类的实例化来调用具体的功能结构 - 涉及到
java语言与前端 HTML,后端的数据库交互时,前后端的结构在 Java层面交互时,都体现为 类、对象
构造方法
-
什么是构造方法
构造方法是专门用来创建对象的方法,
当我们通过关键字 new来创建对象时, 其实就是在调用构造方法 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// 无参
public class Phone {
public Phone() {
}
}
// 有参
public class Phone {
public Phone() {
}
public Phone(String brand, Double price) {
this.brand = brand;
this.price = price;
}
// 属性
private String brand;
private Double price;
// 行为
public static void call() {
System.out.println("我可以打电话");
}
} -
注意事项:
构造方法的名称和所在的类名称完全一样, 就连大小写都一样 - 构造方法不需要写返回的类型,
连 void都不写 - 构造方法不能
return一个具体的返回值 - 构造方法也可以重载
这个标准类也叫做
Java Bean
匿名对象
注意事项
-
理解: 我们创建的时候,
没有显示的赋给一个变量名, 即为匿名对象 匿名对象 
-
特征: 匿名对象只能调用一次
使用解析
-
使用
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
33package it.coderitl.scannertest;
import java.util.Scanner;
public class Anonymous {
// Anonymous 匿名
public static void main(String[] args) {
// 匿名对象的使用
System.out.println("请输入一个整形数字: ");
System.out.println(new Scanner(System.in).nextInt());
// 方法调用
testAnonymous(new Scanner(System.in));
// 将匿名对象作为返回值
Scanner sc = methodReturn();
int num = sc.nextInt();
System.out.println("输入的是: " + num);
}
// 将匿名对象作为参数传递
public static void testAnonymous(Scanner sc) {
System.out.println("请输入一个整型数值: ");
int num = sc.nextInt();
System.out.println("你输入的数据是: " + num);
}
// 将匿名对象作为返回值
public static Scanner methodReturn() {
return new Scanner(System.in);
}
}
标准类-JavaBean
- 所在的成员变量都要使用
private关键字修饰 - 为每一个成员变量编写一对
Getter/Setter方法 - 编写一个无参的构造方法
- 编写一个全参数的构造方法
可变个数的形参
JDK-5.0新增内容 - 具体使用:
- 格式:
数据类型 ...args - 当调用可变个数的形参的方法时,传入的参数个数可以是
0、1、2、······、个 - 可变个数形参的方法与本类中方法名相同,
形参不同的方法之间构成重载 -
可变个数形参的方法与本类中方法名相同, 形参类型也相同的数组之间不构成重载, 换句话说, 二者不能共存 - 可变个数形参在方法的形参中,
必须声明在 末尾 - 可变个数形参在方法的形参中,
最多只能声明一个可变形参
- 格式:
静态代码块
-
格式
1
2
3
4
5
6
7public class StaticArea {
static {
// 静态代码块内容
// 特点: 当第一次用到本类时,静态代码块执行唯一的一次
}
}静态代码块内容总是优先于非静态, 所以静态代码块比构造方法先执行 -
静态代码块的典型用途
用来一次性的对静态成员变量进行赋值
第五章 String
String-概述
-
字符串的特点:
- 字符串的内容用不可变
- 正是因为字符串不可改变,
所以字符串是可以共享使用的 - 字符串效果上相当于
char[ ]字符数组, 但是底层原理是 byte[ ]字符串数组
-
创建字符串的方式
-
直接创建
1
String name = "coder-itl";
-
new1
2
3
4
5
6
7
8
9
10
11public static void main(String[] args) {
char[] chars = {'a', 'b'};
// 字符数组
String s1 = new String(chars);
System.out.println(s1);
byte[] bytes = {97, 98};
// 字节数组
String s2 = new String(bytes);
System.out.println(s2);
}
-
-
字符串常量池
-
字符串常量池: 程序当中直接写上的双引号字符串就在字符串常量池中
字符串常量池 
-
对于基本数据类型来说:
==是进行 数值的比较1
2
3
4
5
6
7
8
9
10
11
12
13public static void main(String[] args) {
/************************************/
int a = 10;
int b = 20;
System.out.println(a == b); // false
System.out.println(10 == 20); // false
/************************************/
int c = 30;
int d = 30;
System.out.println(c == d); // true
System.out.println(30 == 30); // true
/************************************/
} -
对于引用数据类型来说:
==是进行 地址值的比较1
2
3
4
5
6
7
8
9
10
11public static void main(String[] args) {
/*************** 常量池中 *************/
String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2); // true
/************************************/
String s3 = new String("def");
String s4 = new String("def");
System.out.println(s3 == s4); // false(s3、s4指向了不同的地址)
/************************************/
}
-
-
字符串比较相关的方法
1
2
3
4
5
6
7// 字符串内容比较
public boolean equals(Object object){
}
// 参数可以是任何对象,只有参数是一个字符串并且内容相同的才会给 true, 否则返回 false; 注意事项:
-
任何对象都能用
Object接收 -
equals方法具有对称性也就是a.equals(b)和 b.equals(a)效果一样 -
如果比较双方一个是常量一个是变量,
推荐把常量字符串写在前面 1
2
3推荐: "abc".equals(str) 不推荐: str.equals("abc")
原因: String str = null; // 会出现空指针异常
-
-
键盘输入字符串问题
键盘输入字符串问题 
- 原因:
next()底层的字符串是 new出来的, 所以地址值不一样, 使用 ==比较结果就为 false
- 原因:
-
字符串的遍历
1
2
3
4
5
6
7
8public static void main(String[] args) {
String word = "\"C:\\Program Files\\Zulu\\zulu-17\\bin\\re\\IntelliJ IDEA 2022.2.3\\bin\"";
for (int i = 0; i < word.length(); i++) {
// i: 字符串的索引
char c = word.charAt(i);
System.out.print(c + " ");
}
} -
字符统计
-
键盘录入一个字符串,统计该字符串中大写字母字符,
小写字母字符,数字字符出现的次数 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一个字符串: ");
String str = scanner.next();
int bigCount = 0;
int smallCount = 0;
int numberCount = 0;
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c >= 'a' && c <= 'z') {
smallCount++;
} else if (c >= 'A' && c <= 'Z') {
bigCount++;
} else {
numberCount++;
}
}
System.out.println("小写字母个数为: " + smallCount);
System.out.println("大写字母个数为: " + bigCount);
System.out.println("数字个数为: " + numberCount);
}
-
-
将一个整数类型的数组按照指定格式输出
1
2
3
4
5
6
7
8
9
10
11
12
13public static void main(String[] args) {
int[] arr = {1, 2, 3};
String res = "[";
for (int i = 0; i < arr.length; i++) {
if (i == arr.length - 1) {
res = res + arr[i];
} else {
res = res + arr[i] + ", ";
}
}
res = res + "]";
System.out.println(res);
} -
字符串相关方法练习使用
练习 
-
字符串分割方法
练习 
-
字符串的相关练习
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
42package it.coderitl.splicetest;
public class StringSplit {
public static void main(String[] args) {
// 练习: 定义一个方法,把数组 {1,2,3}按照指定的格式拼接成一个字符串
/*
* 习题分析:
* 1. 定义一个方法 arrayToString
* 2. {1,2,3} --> int[] array
* 3. 拼接 concat | split
* 4. 定义方法的三要素
* 4.1 权限修饰符
* 4.2 方法的返回值
* 4.3 方法名称
* 格式: [world#1world#2world#3]
* */
int[] array = {1, 2, 3};
// 调用方法
String arrayStr = arrayToString(array);
System.out.println(arrayStr);
}
public static String arrayToString(int[] array) {
String str = "{";
// 遍历数组
for (int i = 0; i < array.length; i++) {
// 判断: 如果在数组的最后一个元素时
if (i == array.length - 1) {
// 数组的每个元素 array[i]
str += "world" + array[i] + "}";
} else {
// 数组的每个元素 array[i]
str += "world" + array[i] + "#";
}
}
// 该返回谁 {world1#world2#world3}
return str;
}
}
StringBuilder 概述
-
StringBuilder可以看成是一个容器, 创建之后里面的内容是可变的 -
作用: 提高字符串的操作效率
-
常用方法
-
append:添加数据,并返回对象本身 -
reverse:反转容器中的内容 -
length:返回长度(字符出现的个数) 1
2
3
4
5
6
7public static void main(String[] args) {
StringBuilder sb = new StringBuilder("abbc");
System.out.println(sb.length());
sb.append("d");
System.out.println(sb);
System.out.println(sb.reverse());
} -
toString: 就可以实现把StringBuilder转换为 String,就可以使用 String特有的方法了
-
StringJoiner
-
体验
1
2
3
4
5
6
7
8public static void main(String[] args) {
int[] arr = {1, 2, 3};
StringJoiner sj = new StringJoiner(",", "[", "]");
for (int i = 0; i < arr.length; i++) {
sj.add(arr[i] + "");
}
System.out.println(sj);
} -
构造方法
方法名 说明 public Stringjoiner(间隔符号)创建一个 StringJoiner对象, 指定拼接时的间隔符号 public StringJoiner(间隔符号,开始符号, 结束符号) 创建一个 StringJoiner对象, 指定拼接时的间隔符号、开始符号、结束符号 1
2
3
4
5
6
7
8
9public class TestStream {
public static void main(String[] args) {
// delimiter: 分隔符
StringJoiner sj = new StringJoiner("---");
sj.add("aa").add("bb").add("cc");
// aa---bb---cc
System.out.println(sj);
}
} -
成员方法
方法名 说明 public StringJoiner add(添加的内容)添加数据, 并返回对象本身 public int length()返回长度 (字符出现的个数) public String toString()返回一个字符串 (该字符串就是拼接之后的结果) 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 说明: 没有空参构造
package org.example;
import java.util.StringJoiner;
public class TestStream {
public static void main(String[] args) {
// delimiter: 分隔符
StringJoiner sj = new StringJoiner(",", "[", "]");
sj.add("aa").add("bb").add("cc");
System.out.println(sj);
// [aa,bb,cc] 的总体长度,包含符号
System.out.println(sj.length());
}
}
字符串相关原理
-
字符串存储的内存原理
- 直接赋值会复用字符串常量池中的
new出来不会复用, 而是开辟一个新的空间
-
==号比较的到底是什么 - 基本数据类型比较的是
数据值 - 引用数据类型比较的是
地址值
- 基本数据类型比较的是
-
字符串拼接的底层原理
-
右边没有变量
字符串拼接 
-
右边有变量
-
JDK8之前的原理 1
2
3String s1 = "a";
// 一个加号,堆内存中两个对象 JDK7: 1. StringBuilder 对象 2.String
String s2 = s1 + "b";两个对象 
-
JDk8之后 1
2
3
4
5
6
7
8
9
10public static void main(String[] args) {
String s1 = "a"; // length = 1
String s2 = "b"; // length = 1
String s3 = "c"; // length = 1
// JDK8: 字符串拼接原理
// 1. 预估长度 (预估总长度: 3)
// 2. 创建一个预估长度数组 存入对应的值 a b c
// 3. 再将这个数组转换为字符串
String s4 = s1 + s2 + s3;
}-
总结
-
如果没有变量参与,
都是字符串直接相加, 编译之后就是拼接之后的结果, 会复用串池中的字符串 1
2
3
4
5
6
7public static void main(String[] args) {
String s1 = "abc";
String s2 = "a" + "b" + "c";
// true: "a"+"b"+"c"运算过程中,未出现变量,在编译的时候, 就会将 "a"+"b"+"c" 拼接为 "abc"
System.out.println(s1 == s2); // true
} -
如果有变量参与,
会创建新的字符串, 浪费内存 1
2
3
4
5
6
7
8
9
10
11
12public class TestStream {
public static void main(String[] args) {
// 常量池中
String s1 = "abc";
String s2 = "ab";
String s3 = s2 + "c";
// false: 主要因为 s2 + "c"中的 '+',底层出现: new String
System.out.println(s1 == s3);
}
}如果很多字符串变量拼接,
不要直接 +。在底层会创建多个对象,浪费时间, 浪费性能 -
字符串拼接的时候,
如果有变量 JDK8以前: 系统底层会自动创建一个 StringBuilder对象, 然后在调用 append方法完成拼接, 拼接后, 在调用其 toString方法转换为 String类型, 而 toString方法的底层是直接 new了一个字符串对象 JDK8版本: 系统会预估要字符串拼接之后的总大小, 把要拼接的内容都放在数组中, 此时也是产生了一个 新的字符串
-
-
-
-
-
StringBuilder提高效率原因 所有要拼接的内容对都会往
StringBuilder中放, 不会创建很多无用的空间, 节约内存 -
StringBuilder源码分析 -
刚开始创建
StringBuilder时, 默认会创建一个容量为 16的 字节数组 -
添加的内容长度小于
16,直接存 -
添加的内容大于
16会扩容 ( (16)),老容量 *2+2 如果再次超出容量, 则扩容以实际容量为准 1
2
3// 存储 A ~ Z (26) 个字母
// 默认 16 不够用,进行扩容. 16*2+2=34
// 存储 A~Z 0~9: 26+10=36[第二次扩容是 34 还不够用,],[第三次]所以本次的容量是 36 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public static void main(String[] args) {
// 容量: 最多装多少
// 长度: 已经装了多少
StringBuilder sBuilder = new StringBuilder();
/**************************************/
// 获取容量
System.out.println("默认容量: " + sBuilder.capacity());
// 获取长度
System.out.println("默认长度: " + sBuilder.length());
/**************************************/
StringBuilder s2 = new StringBuilder();
s2.append("aa").append("bb").append("cc");
System.out.println("添加元素后: " + s2.capacity());
System.out.println("添加元素后: " + s2.length());
/**************************************/
}原码分析 
-
第六章 集合
集合
-
集合的体系结构
单列集合 
List系列集合: 添加的元素是有序的、可重复、有索引 Set系列的集合: 添加的元素是无序、不重复、无索引
-
Collection是单列集合的祖宗接口,他的功能是全部单列集合都可以继承使用的 方法名称 说明 add(E e)把给定的对象添加到集合中 clear()清空集合中所有的元素 remove(E e)把给定的对象在当前集合中删除 contains(Object obj)判断当前集合中是狗包含给定的对象 isEmpty()判断当前集合是否为空 size()返回集合中元素的个数 / 集合的长度 1
2
3// 判断元素是否包含
// 底层是依赖 equals 方法进行判断是否存在的
// 所以,如果集合中存储的是自定义对象, 也想通过 contains 方法来判断是否包含, 那么在 JavaBean 中, 一定要重写 equals 方法 -
集合自定义元素类型输出
1
2
3
4
5
6
7
8
9
10
11
12
13public static void main(String[] args) {
ArrayList<User> coll = new ArrayList<>();
coll.add(new User("a", "b", LocalDateTime.now(), LocalDateTime.now()));
coll.add(new User("b", "e", LocalDateTime.now(), LocalDateTime.now()));
coll.add(new User("c", "f", LocalDateTime.now(), LocalDateTime.now()));
// 需要重写 ToString,否则输出 [org.example.User@b0657722], 不能进行 .toString() 改变
System.out.println(coll);
Collection<String> str = new ArrayList<>();
str.add("aa");
// [aa]
System.out.println(str);
}User 中必须重写 ToString
-
集合的遍历
-
迭代器
方法名称 说明 Iterator<E> iterator()返回迭代器对象, 默认指向当前集合的 0索引 -
Iterator中的常用方法 方法名称 说明 boolean hasNext()判断当前位置是否有元素, 有元素返回 true,没有元素返回falseE next()获取当前位置的元素, 并讲迭代器对象移向下一个位置 1
2
3
4
5
6
7
8
9
10public static void main(String[] args) {
ArrayList<User> coll = new ArrayList<>();
coll.add(new User("a", "b", LocalDateTime.now(), LocalDateTime.now()));
coll.add(new User("b", "e", LocalDateTime.now(), LocalDateTime.now()));
coll.add(new User("c", "f", LocalDateTime.now(), LocalDateTime.now()));
Iterator<User> iterator = coll.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
-
-
增强
for- 增强
for的底层就是迭代器, 为了简化迭代器的代码书写的 - 它是
JDK5之后出现的, 其内部原理就是一个 Iterator迭代器 - 所有的单列集合和数组才能用增强
for进行遍历
- 增强
-
Lambda表达式遍历 1
2
3
4
5
6
7
8public static void main(String[] args) {
ArrayList<User> coll = new ArrayList<>();
coll.add(new User("a", "b", LocalDateTime.now(), LocalDateTime.now()));
coll.add(new User("b", "e", LocalDateTime.now(), LocalDateTime.now()));
coll.add(new User("c", "f", LocalDateTime.now(), LocalDateTime.now()));
// 遍历
coll.forEach(u-> System.out.println(u));
}
-
-
List-
特点
- 有序: 存和取的元素顺序一致
- 有索引: 可以通过索引操作元素
- 可重复: 存储的元素可以重复
-
方法
方法名称 说明 void add(int index,E element)在此集合中的指定位置插入指定的元素 E remove(int index)删除指定索引处的元素,返回被删除的元素 E set(int index,E element)修改指定索引处的元素, 返回被修改的元素 E get(int index)返回指定索引处的元素 1
2
3
4
5
6
7
8public static void main(String[] args) {
List<String> list = new ArrayList<>();
// TODO: 索引的位置从 0 开始,不能跳跃, 间隔
// 细节: 插入指定位置,如果该位置有元素, 会被后移
list.add(0, "aa");
list.add(1, "bb");
System.out.println(list);
}
-
-
ArrayList 集合
基础定义
-
由于数组的长度不可以发生改变,
但 ArrayList集合的长度是可以随意变化的 -
泛型: 也就是装在集合当中的所有元素,全部都是 统一的某种类型
集合常用的方法
-
public boolean add(E e): 向集合中添加元素,参数的类型和泛型一致 1
2
3
4
5
6注意: 对于 ArrayList 集合来说,add 添加动作一定是成功的, 所以返回值可以不用, 返回值代表添加是否成功
但是对于今后的其他类型来说: add 添加动作不一定成功 -
public E get(int index): 从集合当中获取元素,参数是索引编号, 返回值就是对应位置的元素 -
public E remove(int index):从集合中删除元素, 参数是索引编号, 返回值就是被删除掉的元素 -
public int size(): 获取集合的尺寸长度,返回值是集合中包含的元素个数。 -
练习
ArrayListArrayList 
存储基本数据类型
- 如果希望向
ArrayList集合中存储基本数据类型, 必须使用基本类型对应的 包装类
包装类
- 基本数据类型,使用起来非常方便,但是没有对应的方法来操作这些基本类型的数据,可以使用一个类,把基本数据类型的数据装起来,在类中定义一些方法,这个类叫做
包装类,我们可以使用类中这些方法来操作基本数据类型
| 基本数据类型 | 对应包装类Java.lang
|
|---|---|
byte |
Byte |
short |
Short |
int |
Integer |
long |
Long |
float |
Float |
double |
Double |
char |
Character |
boolean |
Boolean |
装箱与拆箱
- 基本类型与对应包装类对象之间,来回转换的过程称为
装箱与"拆箱" - 装箱:
从基本类型转换为对应的包装类对象 - 拆箱:
从包装类对象转换为对应的基本类型。
案例练习
-
使用练习
ArrayList 的增删改查 
从
JDK 1.5+开始, 支持自动装箱、自动拆箱 自动装箱: 基本类型 –> 包装类型自动拆箱: 包装类型 –> 基本类型
-
随机数与
ArrayList结合练习 | 练习 |
| :———————————————————-: |
|
|
-
自定义
自定义对象作为 ArrayList 数据类型 
将 ArrayList 作为方法的参数使用
| 练习 |
|---|
![]() |
用一个大集合存入 20 个随机数, 然后筛选其中的偶数元素, 放到小集合当中
| 练习 |
|---|
![]() |
-
ArrayList底层原理 -
利用空参创建的集合,在底层创建一个默认长度为
0的数组 -
添加第一个元素时,
底层会创建一个新的长度为 10的数组 -
存满时,
会扩容 1.5倍 -
如果依次添加多个元素,
1.5倍还放不下, 则新创建数组的长度以实际为准 1
2
3
4
5
6// ArrayList 空参构造
public ArrayList() {
// 底层数组名称: elementData
// private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 默认空数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
-
-
LinkedList集合 -
底层数据结构是
双链表,查询慢,增删快,但是如果操作的是首尾元素, 速度也是极快的 -
方法说明
方法 说明 public void addFirst(E e)在该列表开头插入指定的元素 public void addLast(E e)将指定的元素追加到此列表的末尾 public E getFirst()返回此列表中的第一个元素 public E getLast()返回此列表中的最后一个元素 public E removeFirst()从此列表中删除并返回第一个元素 public E removeLast()从此列表中删除并饭 hi 最后一个元素 -
链表
链表结构 
-
链表的基本实现
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
49package org.example;
class Node<T> { // 定义描述节点的结构类
private T data; // 保存所有数据
private Node next; // 描述下一个节点
public Node(T data) { // 所有节点一定要包裹数据
this.data = data;
}
public T getData() {
return this.data;
}
public void setNext(Node next) { // 设置下一个节点
this.next = next;
}
public Node getNext() { // 返回下一个节点
return this.next;
}
}
public class LinkApplication {
public static void main(String[] args) {
// 直接定义好了一个数组内容,这个时候数组需要通过索引下标进行访问
Node<String> nodeA = new Node<>("coder-itl");
Node<String> nodeB = new Node<>("coderitl.github.io");
Node<String> nodeC = new Node<>("coderitl");
// 设置关联关系
nodeA.setNext(nodeB);
nodeB.setNext(nodeC);
print(nodeA);
}
// 获取数据
public static void print(Node node) {
if (node == null) {
return;
}
System.out.println(node.getData());
// 递归调用
print(node.getNext());
}
}
-
关键字 static
-
理解
分析 
-
static 描述
- 如果一个成员变量使用了
static关键字,那么这个变量不再属于对象自己,而是属于所在类 - 一旦使用了
static修饰成员方法, 那么这就成为了 静态方法,,静态方法不属于对象 而是属于 类的 - 使用: 如果没有使用
static关键字, 那么必须首先创建对象, 然后通过对象才能使用变量或方法 - 对于静态方法来说,
可以通过对象名进行调用, 也可以直接通过类名称调用 - 推荐使用
类.静态方法名 | 类. 成员方法
注意事项:
- 静态不能直接访问非静态: 原因: 因为在内存当中是
先有静态内容,有非静态内容后 - 静态方法不能使用
this:原因:this代表当前对象, 通过谁调用的方法谁就是 this,调用过程全程使用类,矛盾
- 如果一个成员变量使用了
-
static内存图 注意: 根据
类名访问静态成员变量的时候,全程和对象就没关系, 只和类有关系 分析 
数组工具类
java.util.Arrays
-
public static String toString(数组): 将参数数组编程字符串,按照默认格式 ([元素 1, 元素 2,···]) -
public static void sort(数组): 将按照默认升序从小到大对数组元素进行排序 -
使用:
练习 
-
练习

数学工具类
![]() |
|---|
继承
基本使用
-
继承的格式
-
父类格式
1
public class 父类名称
{} -
子类格式
1
public class Zi extends Fu {}
-
-
继承中关于成员变量访问的特点

-
区分子类方法中重名的三种
1
2
3Fu: String name = "Fu";
Zi: String name = "Zi";
成员方法: String name = "methods name";- 使用
- 直接写:
System.out.println(name) --> 方法局部变量 - 关键字:
this.name -->本类的成员方法 super调用: Fu类的变量
- 直接写:
- 使用
-
继承中成员方法的访问特点
1
2
3在父子类关系中, 创建子类对象访问成员方法的规则
创建的对象是谁,就优先用谁, 如果没有则直接向上找,
构造方法
- 继承关系中: 父子类构造方法的访问特点
- 子类构造方法当中有一个默认隐含的
super()调用, 所以一定是优先调用的父类构造, 后执行的子类构造 - 子类构造可以通过
super关键字来调用父类构造 super的父类构造调用, 必须是子类构造方法的第一句, 不能一个子类构造调用多次 super构造
- 子类构造方法当中有一个默认隐含的
Super 关键字的三种用法
- 在子类的成员方法中,
访问父类的成员变量 - 在子类的成员方法中,
访问父类的成员方法 - 在子类的构造方法中,
访问父类的构造方法
this 关键字的三种用法
- 在本类中的成员方法中,
访问本类的成员变量 - 在本类的成员方法中,
访问本类的两一个成员方法 - 在本类的构造方法中,
访问本类的另一个构造方法
注意: this
super
Java 继承的三个特点
Java语言是 单继承Java语言可以 多级继承- 一个子类的直接父类是唯一的,
但是一个父类可以拥有很多个子类
抽象类
基本组成
-
抽象方法: 就是加上
abstract关键字, 然后去掉大括号, 直接分号结束 1
2
3
4
5// 抽象类
public abstract class Animal {
// 抽象方法
public abstract void run();
} -
抽象类: 抽象方法所在的类,
必须是抽象类才行, 在 class之前写上 abstract关键字即可。 1
2public abstract class Animal {
}
基本使用
-
不能直接创建
new抽象类对象 -
必须用一个子类
继承抽象父类 -
子类必须覆盖重写抽象父类当中的所有抽象方法注意: 覆盖重写的规则: 子类去掉抽象方法的
abstract关键字, 然后补上方法体 { }1
2
3
4
5
6
7
8
9
10
11
12public class Dog extends Animal {
public void run() {
}
public void eat() {
}
} -
创建子类对象进行使用
-
抽象类的使用过程
1
2
3
4
5
6
7
8package com.coderitl.learnabstract;
// 抽象类
public abstract class Animal {
// 抽象方法
public abstract void eat();
}1
2
3
4
5
6
7
8
9
10
11package com.coderitl.learnabstract;
// 继承 Animal
public class Cat extends Animal {
// 重写抽象方法
public void eat() {
System.out.println("猫吃鱼");
}
}1
2
3
4
5
6
7
8
9
10
11
12package com.coderitl.learnabstract;
public class Main {
public static void main(String[] args) {
// 实例化 Cat 对象
Cat cat = new Cat();
// 方法调用
cat.eat();
}
}-
抽象类使用的注意事项:
- 抽象类不能创建对象
- 抽象类中,
可以有构造方法, 是供子类创建对象时初始化父类成员使用的 - 抽象类中,不一定包含抽象方法,但是有抽象方法的类必须是抽象类
- 抽象类的子类必须重写抽象父类中所有的抽象方法
-
抽象类练习
-
需求
- 某加油站推出了
2种支付卡, 一种是预存了 10000的金卡, 后续加油享受 8,折优惠 另一种是预存了 5000的银卡, 后续加油享受 8.5折优惠 - 请分别实现
2种卡片进入收银系统后的逻辑, 卡片需要包含主人 名称、余额、支付功能
- 某加油站推出了
-
分析实现
- 创建一张卡片的父类: 定义属性包括主人名称、余额、支付功能
(具体实现交给子类) - 创建一张白金卡类: 重写支付功能,按照原价的
8折计算输出 - 创建一张银卡类: 重写支付功能,按照原价的
8.5折计算输出
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// 父类定义
package com.example;
public abstract class Card {
private String name;
private Double balance;
// 支付功能
public abstract void pay(Double money);
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getBalance() {
return balance;
}
public void setBalance(Double balance) {
this.balance = balance;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 金卡
package com.example;
public class GoldCard extends Card {
public void pay(Double money) {
System.out.println("您当前消费: " + money);
System.out.println("现有余额: " + getBalance());
Double rs = money * 0.8;
System.out.println(getName() + ": 您实际支付: " + rs);
// 更新账户余额
setBalance(getBalance() - rs);
System.out.println("您当前余额: " + getBalance());
}
}1
2
3
4
5
6
7
8
9
10
11// 测试
package com.example;
public class MainTest {
public static void main(String[] args) {
GoldCard goldCard = new GoldCard();
goldCard.setName("coder-itl");
goldCard.setBalance(10000.0);
goldCard.pay(90.0);
}
}抽象类使用 
- 创建一张卡片的父类: 定义属性包括主人名称、余额、支付功能
-
-
特征和注意事项
-
类有的成员
(成员变量、方法、构造器) 抽象类都具备 -
抽象类中不一定有抽象方法、有抽象方法的类一定是抽象类
-
一个类继承了抽象类必须重写抽象类的全部抽象方法,否则这个类也必须定义成抽象类
-
不能用
abstract修饰变量、代码块、构造器 -
最重要的特征: 得到了抽象方法,失去了创建对象的能力失去创建对象的能力 
-
抽象类不能创建对象,为什么
1
2
3public abstract class Card {
public abstract void pay(Double money);
}1
2
3// 反证法: 假设抽象类可以创建对象
Card card = new Card();
card.pay() // 方法体都不存在 -
final和abstract是什么关系 互斥abstract定义的抽象类作为模板让子类继承, final定义的类不能被继承 - 抽象方法定义通用功能让子类重写,
final定义的方法子类不能被重写
-
-
设计模式-模板方法模式
-
什么时候使用模板方法模式
- 使用场景说明: 当系统中出现同一个功能多处在开发,而该功能中大部分代码是一样的,只有其中部分可能是不同的时候
-
模板方法模式实现步骤
- 把功能定义成一个所谓的模板方法,放在抽象类中,模板方法中只定义通用且能确定的代码
- 模板方法中不能决定的功能定义成抽象方法让具体子类去实现
-
模板方法实现
-
需求
-
现在又两类学生,
一类是中学生, 一类是小学生, 他们都要写 我在学习java 这篇作文 -
要求每种类型的学生,标题第一段和最后一段,内容必须一样。正文部分自己发挥
-
请选择最优的面向对象方案进行设计
1
2
3
4
5
6
7
8
9
10
11
12
13package com.example;
public abstract class Student {
// 模板方法建议使用 final 修饰(更加专业)
public final void write() {
System.out.println("----------------------- 起始部分 -----------------------");
System.out.println(writeMain());
System.out.println("----------------------- 结束部分 -----------------------");
}
public abstract String writeMain();
}1
2
3
4
5
6
7
8
9
10// 小学生
package com.example;
public class StudentChild extends Student {
public String writeMain() {
return "我是小学生.................";
}
}1
2
3
4
5
6
7
8
9
10// 大学生
package com.example;
public class StudnetMiddle extends Student {
public String writeMain() {
return "我是大学生.................";
}
}1
2
3
4
5
6
7
8// 测试
public class MainTest {
public static void main(String[] args) {
StudentChild child = new StudentChild();
child.write();
}
}抽象类-模板模式 
-
-
-
模板方法模式解决了什么问题
提高了代码的复用性- 模板方法已经定义了通用结构,模板方法不能确定的部分定义成抽象方法,
交给子类实现, 因此,使用者只需要关心自己需要实现的功能即可
-
接口
定义:
- 接口就是多个类的公共规范
- 接口是一种引用数据类型,
最重要的内容就是其中的` 抽象方法 %}
如何定义一个接口的格式【大驼峰命名】
-
格式
1
2
3
4
5
6
7
8public interface interfacce-name {}
// 如果是 java7,那么接口中可以定义包含的内容有: 常量、抽象方法
// 如果是 java8,那么接口中可以额外包含内容有: 默认方法、静态方法
// 如果是 java9,那么接口中可以额外包含内容有: 私有方法
接口的抽象方法的定义:
-
在任何版本中的
java中, 接口都能定义抽象方法 -
抽象方法的定义格式:
1
public abstract 返回值类型 方法名称
(参数列表); - 注意事项:
- 在
接口当中的抽象方法修饰符必须是两个固定的关键字public abstract - 这两个关键字修饰符,
可以 选择性的省略 -
方法的三要素 可以根据业务发生改变
- 在
- 注意事项:
接口的使用步骤:
-
接口不能直接使用,
必须有一个 ` 实现类 %}来实现接口 1
public class 实现类名称 implements 接口名称 {}
-
接口的实现类
必须覆盖重写 接口中的所有的抽象方法 1
去掉 abstract 关键字 加上方法体的大括号 -
创建实现类的对象,进行使用
-
图示:

-
注意事项:
如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类
接口的默认方法
-
从
Java8开始、接口里允许定义默认方法 -
格式:
1
2
3
4
5public default 返回值类型 方法名称
(参数列表){
// 方法体
}备注: 接口当中的默认方法可以解决接口升级的问题
接口的静态方法的
-
从
Java8开始、接口里允许定义静态方法 -
格式:
1
2
3
4
5
6public static 返回值类型 方法名称
(参数列表){
// 方法体
}提示: 就是将 abstract 或者 default 替换成 static 再带上方法体
-
使用:
-
注意: 不能通过接口的实现类的对象来调用静态方法 -
正确做法:
接口名称. 静态方法名 (参数列表);
-
接口的私有方法
问题描述: 我们需要抽取一个共有的方法,
用来解决两个默认方法之间重复代码的问题,但是这个共有方法不应该让实现类使用, 应该是私有化的
-
解决方案
1
从 java9 开始: 接口当中允许定义私有话的方法 -
普通私有化方法:解决多个默认方法之间重复的代码 1
private 返回值类型 方法名称
(参数列表){} -
静态私有方法:解决多个静态方法之间重复的代码问题 1
private static 返回值类型 方法名名称
(参数列表){}
-
继承父类并实现多个接口
-
使用接口的时候需要注意
-
接口是没有
静态代码块或者构造方法的 -
一个类的直接父类是
唯一的,但是一个类可以同时实现多个接口1
2
3public class class-name implements interface1,interface2 {
// 覆盖重写父类所有的抽象方法
} -
如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
-
如果实现类没有覆盖重写所有接口当中的抽象方法,那么实现类就必须是一个抽象类
-
如果实现类实现的多个接口当中,存在重复的默认方法,那么实现类
一定要对冲突的默认方法进行覆盖重写 -
一个类如果
直接父类 当中的方法和接口当中的默认方法产生了冲突,优先 用父类当中的方法
-
注意事项:
- 多个父接口当中的抽象方法如果重复,没关系
- 多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,而且带着
default关键字
多态
基本使用
-
使用
1
2
3
4
5面向对象的三大特征: 封装、继承、多态
extends 或者 implements 实现是多态性的前提
一个对象拥有多种形态这就是对象的多态性 -
代码当中体现多态性,其实就是一句话
父类引用指向子类对象1
2
3
4
5
6
7
8父类名称 对象名 = new 子类名称 ();
Person p = new Man();
或者
接口名称 对象名 = new 实现类名称();
Person person = new PersonImpl();
对象的向上转型
-
向上转型,其实就是多态用法
1
2
3Animal animal = new Cat();
注意: 向上转型一定是安全的,从小范围转向了大范围,从小范围的猫,向上转型为更大范围的动物 -
对象的向下转型
1
2
3
4
5
6格式: 子类名称 对象名 = (子类名称) 父类对象;
Cat cat = (Cat)animal;
含义: 将父类对象,还原成为本来类的子类对象
Animal animal = new Cat(); // 本来是 猫,向上转型为了猫
Cat cat = (Cat)animal; // 本来是猫,已经被当作了动物了,还原成为本来的猫 - 注意事项
- 必须保证对象本来创建的时候,就是猫,才能向下转型为猫
- 如果对象创建的时候本来不是猫,现在非要它向下转型为猫,
就会报错
- 注意事项
-
instanceof关键字的使用 a instanceof A:判断对象 a 是否是 A 的实例, 如果返回是 true,否则返回 false使用情景: 为了避免向下转型时出现 `classException`的异常,我们向下转型之前先进行 ` instanceof `的判断, 一旦返回 `true`, 就进行向下转型,如果返回了 `false`, 不进行向下转型 + 如果 ` a instanceof A: `返回 `true`, 则 ` a instanceof B` 也返回` true`,{% emp 其中 类 B 是类 A 的父类
单元测试方法的使用
在
src同级目录下,新建文件夹 test将
test文件夹右键 –> Mark Directory as–>Test Resources Root创建
类,进行单元测试:- 此类必须是
public权限 - 此类的
返回值类型只能是void
- 此类必须是
此类中声明单元测试方法
1
2
3
4
5
6
7
8
9
10import org.junit.Test;
public class Order {
public void testEquals(){
System.out.println("测试 equals");
}
}注意: 方法的权限是 public ,没有返回值 (只能是 void) 没有形参 此单元测试方法上需要声明注解
1
并在单元测试中导入: import org.junit.Test 执行测试

final 关键字
final关键字代表最终,不可改变的 final关键字修饰类:1
2
3public final class 类名称
{} 含义: 当前这个类不能有任何的子类,但是有父类
注意: 一个类如果是 final 的,那么其中所有的成员方法都无法进行覆盖重写final修饰成员方法当
final关键字用修饰一个方法的时候,这个方法就是最终方法,不能进行覆盖重写1
2
3修饰类 final 返回值类型 方法名称 (参数列表){} 注意事项: 对于类、方法来说,abstract 关键字和 final 关键字不能同时使用 final 关键字修饰局部变量
一旦使用
final用来修饰局部变量,那么这个变量就不能进行更改,对于基本数据类型来说,不可变说的是变量当中的数据不可改变 对于引用类型来说,不可变说的是变量当中的地址值不可改变 final关键字修饰成员变量- 由于成员变量具有默认值,所以用了
final之后就必须手动赋值 - 对于
final的成员变量, 要么使用直接赋值,要么通过构造方法赋值 - 必须保证类当中所有重载的构造方法都最终会对
final的成员变量进行赋值
- 由于成员变量具有默认值,所以用了
四种权限修饰符
是否可访问 public protected default private 同一个类 yes yes yes yes 同一个包 yes yes yes X 不同包子类 yes yes X X 不同包非子类 yes X X X
常用 API
System
System也是一个工具类, 提供了一些与 系统相关的方法常用方法
方法名 说明 public static void exit(int status)终止当前运行的 Java虚拟机public static long currentTimeMillis()返回当前系统的时间毫秒值形式 arraycopy()拷贝数组
Runtime
Runtime表示当前虚拟机的运行环境 常用方法
方法 说明 public static Runtime getRuntime()当前系统 public void exit(int status)停止虚拟机 publi int availableProcessors()获得 CPU的线程数 publi long maxMemory()JVM能从系统中获取总内存大小 (单位 byte)public long totalMemory()JVM已经从系统中获取总内存大下 (单位 byte)
Object
Object的 toStrinh方法 方法名 说明 public String toString()默认是返回当前对象在堆内存中的地址信息: 类的全限定名 @ 内存地址 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
28package com.example;
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class Test {
public static void main(String[] args) {
Student student = new Student("coder-itl", 19);
// com.example.Student@6bc7c054
System.out.println(student);
}
}equalsequals默认比较地址值 1
2
3
4
5
6
7
8
9
10
11
12
13// == 引用类型比较地址值
class Test {
public static void main(String[] args) {
Student student1 = new Student("coder-itl", 19);
Student student2 = new Student("coder-itl", 19);
// ==: false
System.out.println(student1 == student2);
// equals: false(未重写equals 时)
System.out.println(student1.equals(student2));
}
}存在的意义: 父类
equals方法存在的意义就是为了被子类重写, 以便子类自己来定制比较规则
第七章
异常
throw 关键字
throw 关键字:
作用: 可以使用
throw关键字在指定的方法中抛出指定异常 使用格式:
throw new xxxException(“异常产生的原因”)注意:
throw关键字必须写在方法的内部 throw关键字后边的 new的对象必须是Exception或者Exception的子类对象 throw关键字抛出指定的异常对象,我们就必须处理这个异常对象 throw关键字后边创建的是RuntimeException或者是 RuntimeException的子类对象, (打印异常对象,中断程序)我们可以不处理,默认交给 JVM 处理 throw关键字后边创建的是 编译异常 (写代码时报错),我们就必须处理这个异常, 要么 thows,要么 try...catch
throw基本使用 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
29package com.coderitl;
public class Main {
public static void main(String[] args) {
// int[] array = null;
int[] array = {1, 2, 3};
int value = exceptionArray(array, 4);
System.out.println(value);
}
public static int exceptionArray(int[] array, int index) {
/*
* 我们可以对传递过来的参数数组,进行合法性校验
* 如果数组值是 null
* 我们就抛出空指针异常,告知方法的调用者 "传递的数组的值是 null"
* */
if (array == null) {
throw new NullPointerException("传递的数组的值是 null");
}
// 下标越界异常
if (index < 0 || index > array.length - 1) {
throw new IndexOutOfBoundsException("数组越界");
}
int value = array[index];
return value;
}
}
throws 关键字
throws 关键字:
作用:
当方法内部抛出异常对象的时候,
那么我们就必须处理这个异常对象 可以使用
throws关键字处理异常对象, 会把异常对象声明抛出给方法的调用者处理, 最终交给 JVM处理 –> 中断处理 使用格式:
1
2
3
4
5修饰符 返回值类型 方法名 (参数列表) throws AAAException,BBBException...{
throw new AAAException("产生原因");
throw new BBBException("产生原因");
...
}注意:
throws关键字必须卸载方法声明出throws关键字后变声明的异常必须是Exception或者是 Exception的子类 - 方法内部如果抛出了多个异常对象,那么直接声明父类异常即可,如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可
- 调用了一个声明抛出异常的方法,
我们就必须处理声明的异常,要么继续使用 throws 声明抛出,交给方法的调用者处理, 最终交给 JVM,要么try...catch自己处理异常
throws
基本使用 

try…catch(捕获异常)
1 | try{ |
自定义异常
自定义异常类: Java 提供的异常类,
不够我们使用, 需要自定义一些异常类
格式:
1 | public class XXXException extends Exception | RuntimeException{ |
1 | package com.coderitl; |
1 |
|
多线程
多线程的概念和优点
- 基本概念
- 程序: 是为了完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象
- 进程: 是程序的一次执行过程,是正在进行的一个程序
- 线程: 进程可进一步细化为线程,是一个程序内部的一条执行路径
- 多线程的优点
- 提高程序的响应,对图形化界面更具有意义
- 提高计算机流
CPU的利用率 - 改善程序结构,将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
多线程使用的方式一
实现步骤
- 创建一个继承于
Thread类的子类 重写Thread 类的run方法,主要针对方法体重写 - 创建
Thread类的子类对象 - 通过此对象调用
start()
- 创建一个继承于
具体实现
继承
Thread类 1
2
3
4
5
6
7
8
9
10
11
12
13package com.example;
// 1. 继承
public class MyThread extends Thread {
// 2. 重写 run 方法
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ": " + i);
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package com.example;
public class MainThread {
public static void main(String[] args) {
// 3. 创建 Thread 子类对象
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.setName("myThread1");
myThread2.setName("myThread2");
// 4. 通过子类对象调用 start 方法
myThread1.start();
myThread2.start();
}
}
问题一: 我们不能通过直接调用
run()的方式启动线程 问题二: 再启动一个线程,遍历
100以内的偶数,不可以让已经 start() 的线程去执行,会报 IllegalThreadStateException
线程的调度
- 调度策略:抢占式: 高优先级的线程抢占
CPU Java的调度方法 - 同优先级线程组成先进先出队列
(先到先服务), 使用时间片策略 - 对高优先级,使用优先调度的抢占式策略
- 同优先级线程组成先进先出队列
- 调度策略:抢占式: 高优先级的线程抢占
多线程使用的方式二
实现
Runnable接口 具体实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package com.coderitl.exer;
import java.util.ArrayList;
// 1. 创建 Runnable 的实现类
public class RunnableImpl implements Runnable {
// 2. 重写 run 方法
public void run() {
// 多线程执行的任务
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
list.add(i);
}
}
System.out.println(list);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package com.coderitl.exer;
public class RunnableMain {
public static void main(String[] args) {
// 3. 创建实现类的对象
RunnableImpl impl = new RunnableImpl();
// 4. 将此对象作为参数传递到 Thread 类的构造器中,创建Thread 类的对象
Thread t1 = new Thread(impl);
// 5. 通过 Thread 类的对象调用 start()
// start作用:
// 1. 启动线程
// 2. 调用当前线程的 run(),调用了 Runnable 类型的 target 的 run()
t1.start();
}
}
多线程使用的方式三
实现步骤
- 创建一个自定义的类
MyCallable实现 Callable接口 - 重写
call是有返回值的, 表示多线程运行的结果 - 创建
MyCallable的对象 (表示多线程要执行的任务) - 创建
FutureTask的对象 (作用:管理多线程运行的结果) - 创建
Thread类的对象, 并启动 (表示线程)
- 创建一个自定义的类
具体实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package com.example;
import java.util.concurrent.Callable;
// 1. 自定义类实现 Callable
public class MyCallable implements Callable {
// 2. 重写 call 方法,这个返回值类型是可以自定义的
public Integer call() throws Exception {
// 任务: 多线程执行的任务(求 1~100以内的和)
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package com.example;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MainThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 3. 创建 MyCallable 的对象(表示多线程要执行的任务)
MyCallable myCallable = new MyCallable();
// 4. 创建 FutureTask 的对象(作用: 管理多线程运行的结果)
FutureTask<Integer> ft = new FutureTask<>(myCallable);
// 5. 创建多线程的对象
Thread t1 = new Thread(ft);
t1.start();
// 6. 获取多线程运行的结果
Integer sum = ft.get();
System.out.println("1~100以内的和: " + sum);
}
}
多线程使用的方式四
背景: 经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现重复利用
好处
- 提高响应速度
(减少了创建线程的时间) - 降低资源消耗
(重复利用线程池中线程,不需要内次都创建) - 便于线程管理
corePoolSize: 核心池的大小maximumPoolSize: 最大线程数keepAliveTime: 线程没有任务时最多保持多长时间后会终止
- 提高响应速度
Executors工具类、线程池的工厂类,用于创建并返回不同类型的线程池 Executors.newCachedThreadPool(): 创建一个可根据需要创建创建新线程的线程池Executors.newFixedThreadPool(int num): 创建一个可重用固定线程数的线程池Executors.newSingleThreadExecutor(): 创建一个只有一个线程的线程池Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行
实现步骤
- 创建线程池
- 提交任务
- 所有的任务全部执行完毕,
关闭线程池
线程池的基本使用
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
30package com.coderitl.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class ThreadRunnableImpl implements Runnable {
public void run() {
int sum = 0;
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
sum += i;
}
}
System.out.println("10以内偶数计算总和为: " + sum);
}
}
// 线程池的使用
public class ThreadPoll {
public static void main(String[] args) {
// 1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
// 2. 执行指定的线程的操作 需要提供实现 Runnable 接口或 Callable接口实现类的对象
service.execute(new ThreadRunnableImpl()); // 适合适用于 Runnable
// 线程池的关闭
service.shutdown();
}
}线程池主要核心原理
- 创建一个池子,
池子中是空的 - 提交任务时,
池子会创建新的线程对象, 任务执行完毕,线程归还给池子, 下回再次提交任务时, 不需要创建新的线程,直接复用已有的线程即可 - 但是如果提交任务时,
池子中没有空闲线程, 也无法创建新的线程, 任务就会排队等待。
- 创建一个池子,
关于同步方法的总结
- 同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
- 非静态的同步方法,同步监视器是:
this - 静态的同步方法,
同步监视器是: 当前类本身
前三种线程使用如何选择
对比
实现方式 优点 缺点 继承 Thread类 编程比较简单, 可以直接使用 Thread类中的方法 可以扩展性较差,不能再继承其他的类 实现 Runnable接口 扩展性强, 实现该接口的同时还可以继承其他的类 编程相对复杂, 不能直接使用 Thread类中的方法 实现 Callable接口 扩展性强, 实现该接口的同时还可以继承其他的类 编程相对复杂, 不能直接使用 Thread类中的方法 如何理解
Callable接口的方式创建多线程比实现 Runnable接口创建多线程方式强大? call()可以有返回值call可以抛出异常,被外面的操作捕获,获取异常的信息callable是支持泛型的
线程常用的成员方法
线程常用方法
void start(): 启动线程,并执行对象的run()方法 run(): 线程在被调度时执行的操作String getName(): 返回线程的名称void setName(String name): 设置线程名Thread currentThread(): 返回当前线程,在Thread子类中就是 this,通常用于主线程和 Runnable实现类xxImplstatic void yield(): 设置线程名join(): 当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到 join()方法加入的 join线程执行完为止 static voidsleep(long millis): 指定时间:毫秒- 令当前活动线程在指定时间段内放弃对
CPU控制,使其他线程有机会被执行,时间到后重排对 - 抛出
InterruptedException stop(): 强制线程生命期结束,不推荐使用boolean isAlive(): 返回boolean,判断线程是否还活着
多线程的生命周期
新建:
当一个 Thread类 或其子类的对象被声明并创建时,新生的线程对象处于新建状态 就绪: 处于新建状态的线程被
start()后,将进入线程队列等待 CPU的时间片,此时它已具备了运行的条件,只是没有分配到CPU资源运行: 当就绪的线程被调度并获得
CPU资源时,便进入运行状态,run ()方法定义了线程操作和功能 阻塞:
在某种特殊情况下,被人为挂起或执行输入输出操作时,让 CPU并临时中止自己的执行,进入 阻塞状态死亡: 线程完成了它的全部或线程被提前强制性地中止或异常导致结束

疑问:
sleep方法会让线程睡眠, 睡眠时间到了之后, 立马就会执行下面的代码吗? 不会,因为等休眠时间到了之后会进入 就绪状态,是需要抢占 CPU资源的, 只有抢占到 CPU资源才能获得执行权
线程的安全问题
问题地题出
- 多个线程执行地不确定性引起执行结果的不稳定
- 多个线程对账户的共享,会造成操作的不完整性,会破坏数据
线程安全问题的解决措施
同步代码块
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/**
*
说明:
1. 操作共享数据的代码,即为需要同步的代码
2. 共享数据: 多个线程共同操作的变量,比如 ticket 就是共享数据
3. 同步监视器,所称: 锁 任何一个类的对象,都可以充当 锁
要求: 多个线程必须要共用同一把 锁
同步的方式,解决了线程安全的问题 -- 好处
操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低 --- 局限性
*/
package com.example;
// 1. 自定义类实现 Callable
public class SellingTickets implements Runnable {
int tickets = 0;
// 锁对象必须是唯一的
static Object object = new Object();
public void run() {
while (true) {
synchronized (SellingTickets.class) {
if (tickets < 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
++tickets;
System.out.println(Thread.currentThread().getName() + "正在售卖第 " + tickets + " 张票");
} else {
break;
}
}
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.example;
import java.util.concurrent.ExecutionException;
public class MainThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
SellingTickets window = new SellingTickets();
Thread t1 = new Thread(window);
t1.start();
t1.setName("windows1");
Thread t2 = new Thread(window);
t2.start();
t2.setName("windows2");
Thread t3 = new Thread(window);
t3.start();
t3.setName("windows3");
}
}同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的
1
2
3
4
5
6
7/**
* 同步方法的总结:
* 1. 同步方法任然涉及到同步监视器,只是不需要我们显示的声明
* 2. 非静态的同步方法,同步监视器是: this
* 静态的同步方法,同步监视器是: 当前类本身
*/特点
- 特点
1:同步方法是锁住方法里面所有的代码 - 特点
2:锁对象不能自己指定
- 特点
锁对象
- 如果方法是非静态的
锁对象就是 this - 如果方法是静态的
锁对象就是 当前类的字节码文件对象
- 如果方法是非静态的
语法
1
修饰符 synchronized 返回值类型 方法名 (方法参数) {} 实现
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
34package com.example;
// 1. 自定义类实现 Callable
public class SellingTickets implements Runnable {
int tickets = 0;
// 锁对象必须是唯一的
static Object object = new Object();
public void run() {
while (true) {
if (extracted()) break;
}
}
// 先使用同步代码块实现再修改为同步方法的实现
// 非静态的就是 this
private synchronized boolean extracted() {
if (tickets < 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
++tickets;
System.out.println(Thread.currentThread().getName() + "正在售卖第 " + tickets + " 张票");
} else {
return true;
}
return false;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.example;
import java.util.concurrent.ExecutionException;
public class MainThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
SellingTickets window = new SellingTickets();
Thread t1 = new Thread(window);
t1.start();
t1.setName("windows1");
Thread t2 = new Thread(window);
t2.start();
t2.setName("windows2");
Thread t3 = new Thread(window);
t3.start();
t3.setName("windows3");
}
}
synchronized于 lock的异同? - 相同: 二者都可以解决线程安全问题
- 不同:
synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器,lock需要手动的启动同步 ( lock( )),同时结束同步也需要手动的实现 ( unlock( ))
Lock-锁
使用
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
35package com.example;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// 1. 自定义类实现 Callable
public class SellingTickets implements Runnable {
int tickets = 0;
// 锁: Lock 是一个接口,ReentrantLock是一个实现类,使用的时候需要注意实现多线程的方式
Lock lock = new ReentrantLock();
public void run() {
while (true) {
// 手动上锁
lock.lock();
try {
if (tickets < 300) {
Thread.sleep(10);
tickets++;
System.out.println(Thread.currentThread().getName() + "正在售卖第 " + tickets + " 张票");
} else {
break;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
// 手动释放锁
lock.unlock();
}
}
}
}
死锁
- 死锁
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
- 解决方法
- 专门的算法、原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
线程的通信
涉及到的方法
wait( ): 一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器notify( ): 一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被 wait,就唤醒优先级高的notifyAll( ): 一旦执行此方法,就会唤醒所有被wait的线程
注意:
wait()、notify()、notifyAll()、三个方法必须使用在同步代码块或同步方法中wait()、notify()、notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器【Eg. 要么都是 this, 要么就是同一对象】,否则会出现 IllegalMonitorStateException异常 wait()、notify()、notifyAll()三个方法定义在 java.lang.Object类中
面试题
sleep()和 wait()的异同? - 相同点: 一旦执行此方法,都可以使得线程进入阻塞状态
- 不同点:
- 两个方法的声明位置不同,
Thread类中声明 sleep,Object类中声明 wait( ) - 调用的要求不同:
sleep( )可以在任何需要的场景下调用,wait( )方法必须在同步代码块中 - 关于是否释放同步监视器: 如果两个方法都使用在同步代码块或同步方法中,
sleep( )不会释放锁,wait( )会释放锁
- 两个方法的声明位置不同,
生产者和消费者
( 等待唤醒机制)控制生产者和消费者的执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package com;
/**
* 作用: 控制生产者和消费者的执行
*/
public class Desk {
// 是否有面条: 0: 没有 1: 有面条
public static int foodFlag = 0;
// 总个数
public static int count = 10;
// 锁对象
public static Object lock = new Object();
}消费者
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
46package com;
/**
* 消费者
*/
public class Foodie extends Thread {
/**
* 1. 循环
* 2. 同步代码块
* 3. 判断共享数据是否到了末尾(到了末尾)
* 4. 判断共享数据是否到了末尾(没有到末尾, 执行核心逻辑)
*/
public void run() {
// 1. 循环
while (true) {
// 2. 同步代码块
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
} else {
// 3. 判断共享数据是否到了末尾
if (Desk.foodFlag == 0) {
// 桌子上没有食物(等待), 厨师准备制作食物
try {
// 让当前线程跟锁进行绑定
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
// 桌子上有食物,吃货准备吃饭
Desk.count--;
// 核心逻辑
System.out.println("吃货还能吃" + Desk.count + "碗!!!");
// 告诉厨师需要做饭了
Desk.lock.notifyAll();
// 修改桌子的状态(桌子上再次没有食物了)
Desk.foodFlag = 0;
}
}
}
}
}
}生产者
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
35package com;
/**
* 生产者
*/
public class Cook extends Thread {
public void run() {
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
} else {
// 检查桌子上有食物吗
if (Desk.foodFlag == 1) {
try {
// 桌子上有食物,等待
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
// 如果没有就制作食物
System.out.println("厨师做了一碗苗条!!!");
// 修改桌子上食物的状态
Desk.foodFlag = 1;
// 唤醒消费者去吃
Desk.lock.notifyAll();
}
}
}
}
}
}启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14package com;
public class MainThread {
public static void main(String[] args) {
Foodie foodie = new Foodie();
foodie.setName("吃货一号");
foodie.start();
Cook cook = new Cook();
cook.setName("厨师");
cook.start();
}
}
线程的状态
没有运行状态
线程的状态 
线程问题总结
- 谈谈你对程序、进程、线程的理解
- 代码完成继承
Thread的方式创建分线程,并遍历100以内的自然数 - 代码完成实现
Runnable接口的方法创建分线程,并变量100以内的自然数 - 对比两种创建方式
- 谈谈你对
IDEA中Project和Module的理解
多线程练习
卖电影票
需求
一共有
1000张电影票, 可以在两个窗口领取, 假设每次领取的时间为 3000毫秒 要求: 请用多线程模拟卖票的过程并打印剩余电影票的数量
送礼品
需求
有
100份礼品, 两人同时发送, 当剩下的礼品小于 10份的时候则不再送出 利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来
打印奇数数字
需求
同时开启两个线程,
共同获取 1~100之间的所有数字 要求: 输出所有的奇数
抢红包
需求
假设:
100快, 分成了 3个包, 现在有 5个人去抢,其中, 红包就是共享数据, 5个人就是 5条线程。 打印结果如下:
xxx 抢到了
xxx 元 xxx 抢到了
xxx 元 xxx 抢到了
xxx 元 xxx
没抢到 xxx
没抢到
Lambda-表达式
基本使用
1
2
3
4
5
6
7
8
9public class LambdaTest {
public static void main(String[] args) {
// 简化 Runnable 实现多线程
new Thread(() -> {
// run方法体
System.out.println(Thread.currentThread().getName());
}).start();
}
}lambda解释 一些参数
一个箭头
一段代码
1
2
3
4
5格式: (参数列表) -> {一些重写方法的代码} 解释说明格式:
(): 接口中抽象方法的参数列表,没有参数, 就空着, 有参数就写出参数, 多个参数使用逗号分割
->: 传递的意思,把参数传递给方法体
{}: 重写接口的抽象的方法体
练习掌握
lambda1
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// 给定一个 Cook 接口,
内含唯一的抽象方法 makeFood,切无参数, 无返回值
---------------------- 接口 ------------------------------
package com.coderitl;
public interface Cook {
public abstract void makeFood();
}
---------------------- 实现 ------------------------------
package com.coderitl;
public class CookImpl {
public static void main(String[] args) {
invoCook(new Cook() {
public void makeFood() {
System.out.println("匿名-未做饭");
}
});
// lambda 实现上述操作
invoCook(() -> {
System.out.println("Lambda - 做饭了?");
});
}
private static void invoCook(Cook cook) {
cook.makeFood();
}
}年龄排序
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前提实现 JavaBin
package com.coderitl;
import java.util.Arrays;
import java.util.Comparator;
public class SortAge {
public static void main(String[] args) {
PersonInfo[] person = {
new PersonInfo("name11", 2),
new PersonInfo("name12", 19),
new PersonInfo("name13", 13)
};
// 对数组中的 PersonInfo 对象使用 Arrays的 sort 方法通过年龄进行降序 (o2 - 01 | -(o1-o2) ) 排序
Arrays.sort(person, new Comparator<PersonInfo>() {
public int compare(PersonInfo o1, PersonInfo o2) {
return -(o1.getAge() - o2.getAge());
}
});
// 遍历数组输出
for (PersonInfo p : person) {
System.out.println(p);
}
}
}1
2
3
4
5
6
7
8
9Arrays.sort(person,(PersonInfo o1, PersonInfo o2)->{
return -(o1.getAge() - o2.getAge());
})
遍历输出:
PersonInfo{name='name12', age=19}
PersonInfo{name='name13', age=13}
PersonInfo{name='name11', age=2}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
27package com.coderitl;
public class CalculatorImpl {
public static void main(String[] args) {
// TODO: 使用 Lambda 实现 有参有返回值
// 1. 使用匿名内部类实现
invokeCalc(10, 20, new Calculator() {
public int calc(int a, int b) {
return a + b;
}
});
// 2. 使用 Lambda 实现
invokeCalc(12, 20, (int a, int b) -> {
return a + b;
});
}
public static void invokeCalc(int a, int b, Calculator c) {
int result = c.calc(a, b);
System.out.println("方法调用的结果是: " + result);
}
}
第八章
Java 常用类
String - 字符串补充
String: 声明为 final 的,
String 实现了 Serializable 接口: 表示字符串是支持序列化的
实现了 Comparable 接口: 表示字符串
String 内部定义了 final char[] value
String 代表不可变的字符串序列。简称: 不可变性
String、StringBuffer、StringBuilder 三者的异同?
String: 不可变的字符串序列StringBuffer: 可变的字符串序列;线程安全的、效率低StringBuilder: 可变的字符串序列;jdk5.0新增,线程不安全,效率高 三者的相同点: 底层使用
char[ ]存储可变的基础理解
1
2
3
4
5
6
7
public void testStringBuffer() {
StringBuffer str = new StringBuffer("abc");
str.setCharAt(0, 'm');
System.out.println(str); // mbc
}
StringBuffer-StringBuilder 常用方法
常用方法
1
2
3
4
5
6
7
8
9/*
* StringBuffer 常用方法:
* append(xxx) 提供了很多的 append() 方法,用于进行字符串拼接
* delete(int start,int end) 删除指定位置的内容
* replace(int start,ent end,String str) 把 [start,end) 位置替换为 str
* insert(int offset,xxx) 在指定位置 插入: xxx
* reverse() 把当前字符串序列逆转
*
* */
JDK8-之前日期时间 API
System类中地 currentTimeMillis()1
2
3long time = System.currentTimeMillis();
// 返回当前时间与 1970 年 1 月 1 日0 时 0 分 0 秒之间以毫秒为单位的时间差 ,称为时间戳
System.out.println(time);Date1
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
public void date01() {
/* import java.util.Date;
* java.util.Date类
* |--- java.sql.Date类
* 1. 两个构造器地使用
* 2. 两个方法地使用
* > toString(); 显示当前年 月 日 时 分 秒
* > getTime(); 获取当前Date 对象地毫秒数 (时间戳)
* 3. Java.sql.Date 对应数据库中地日期类型地变量
* 怎么实例化对象?
* 如何将 java.util.Date 对象转换为 java.sql.Date 对象
* */
// 构造器一: Date() 创建一个对应当前时间地 Date 对象
Date date1 = new Date();
System.out.println(date1.toString()); // Sat Jul 24 16:51:56 CST 2021
System.out.println(date1.getTime());
// sqlDate 实例化
java.sql.Date sqlDate = new java.sql.Date(21832480385L);
System.out.println(sqlDate);
// 如何将 java.util.Date 对象转换为 java.sql.Date 对象
Date datec = new Date();
java.sql.Date dates = new java.sql.Date(datec.getTime());
System.out.println(dates);
}SimpleDateFormat1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26package com.coderitl.date;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class SimpleDateFormatTest {
public static void main(String[] args) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat();
Date date = new Date();
// 将日期转换为字符串
String formatDate = sdf.format(date);
System.out.println(formatDate);
// 将字符串转换为日期
Date parseDate = sdf.parse(formatDate);
System.out.println(parseDate);
// 指定格式
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String formatSelf = sdf1.format(date);
System.out.println(formatSelf);
}
}Calendar-日历1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package com.coderitl.date;
import java.util.Calendar;
public class CalendarTest {
public static void main(String[] args) {
// 1. 创建其子类(GregorianCalendar)
// 2. 调用其静态方法
Calendar calendar = Calendar.getInstance();
// 常用方法
// 1. get() 参数: Calendar.DAY_OF_YEAR 一年的第几天
int days = calendar.get(Calendar.DAY_OF_YEAR);
System.out.println(days);
// 2. set()
// 3. add()
// 4. getTime()
// 5. setTime()
}
}
JDK8-中新日期时间 API
LocalDate、LocalTime、LocalDateTime1
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
43import org.junit.Test;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
/**
* @Auther: coder-itl
* @Date: 2021/7/25 12:36
* @Description: PACKAGE_NAME
* @version: 1.0
*/
public class DateTest {
public void testLocalDate() {
LocalDate localDate = LocalDate.now();
// 获取当前日期
System.out.println("获取当前日期: " + localDate);
}
public void testLocalTime() {
LocalTime localTime = LocalTime.now();
// 获取当前时间
System.out.println("获取当前时间: " + localTime);
}
public void testLocalDateTime() {
// 常用方法
LocalDateTime localDateTime = LocalDateTime.now();
// 获取当前日期 + 时间
System.out.println(" 获取当前日期 + 时间: " + localDateTime);
// of(): 设置指定的年月日时分秒没有偏移量
LocalDateTime ofLocalDateTime = localDateTime.of(2020, 12, 10, 12, 12, 11);
System.out.println("设置指定时间: " + ofLocalDateTime);
// 获取当月第几天
System.out.println(localDateTime.getDayOfMonth());
System.out.println(localDateTime.getDayOfWeek());
// ... 上面两个具有相同方法用例
}
}
Instant 类的使用
Instant时间线上的一个瞬时点。这可能被用来记录应用程序中的事件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void testInstant() {
// 实例化 Instant now(): 获取本初子午线对应的标准时间
Instant instant = Instant.now();
System.out.println(instant);
// 根据地区时区差添加偏移量
OffsetDateTime offsetHours = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetHours);
// 获取子 1970年 1 月 1 日 0 时 0 分 0 秒 (UTC) 开始的毫秒数
long milli = instant.toEpochMilli();
System.out.println(milli);
}
Java 比较器的使用
1 |
|
Comparable 接口的使用举例:
- 像
String,包装类等实现了 Comparable 接口,重写了 compareTo(obj)方法,给出了比较两个对象大小的方式。 - 像
String、包装类等实现了compareTo()方法以后,进行了从小到大的排列 - 重写
compareTo(obj)的规则: - 如果当前对象
this大于形参对象 obj,则返回正整数 - 如果当前对象
this小于形参对象 obj,则返回负整数 - 如果当前对象
this等于形参对象 obj,则返回正零
- 如果当前对象
- 对于自定义类来说,如果需要排序,我们可以让自定义实现类实现
comparable接口, 重写 compareTo(),在 comparaTo(obj)方法中支名如何排序
自定义类实现 comparable 自然排序
1 | package com.coderitl.compareto; |
1 | package com.coderitl.compareto; |
1 |
|
Compareble Comparator 的使用对比:
Compareble接口的方式一旦一定, 保证 Comparable接口实现类的对象在任何位置都可以比较大小 Comparator接口属于临时性的比较
枚举类
枚举类
1 | package com.coderitl.enumdemo; |
使用 enum 关键字定义的枚举类实现接口
1 | package com.coderitl.enumdemo; |
1 | package com.coderitl.enumdemo; |
匿名内部类
内部类
说明: 内部类就是定义在一个类里面的类,里面的类可以理解成
(寄生), 外部类可以理解成 (宿主) 1
2
3
4
5
6
7
public class TestA {
public class TestB {
}
}内部类的使用场景、作用
- 当一个事物的内部、还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构可以选择使用内部类来设计
- 内部类通常可以方便访问外部类的成员,包括私有的成员
- 内部类提供了更好的封装性、内部类本身就可以用
pricvate protected等修饰,封装性可以做更多控制
内部类的分类
静态内部类
有
static修饰,属于外部类本身 它的特点和使用与普通类是完全一样的,类有的成分他都有,
只是位置在别人里面而已 1
2
3
4
5
6
7
8
9
10
11package com.example;
public class Out {
// 静态成员内部类
static class Inner {
void run() {
System.out.println("run...............");
}
}
}静态内部类的访问拓展
静态内部类中是否可以直接访问外部类的静态成员
可以,
外部类的静态成员只有一份可以被共享访问 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package com.example;
public class Out {
static final String NAME = "coder-itl";
// 静态成员内部类
static class Inner {
void run() {
System.out.println(NAME + " run...............");
}
}
}
class Test {
public static void main(String[] args) {
Out.Inner inner = new Out.Inner();
inner.run();
}
}静态内部类中是否可以直接访问外部类的实例成员?
不可以,外部类的实例成员必须用外部类对象访问
实例成员 
成员内部类:不能加
static修饰, 属于外部类对象的 无
static修饰, 属于外部类的对象 JDK16之前, 成员内部类中不能定义静态成员, JDK16开始也可以定义静态成员了 1
2
3
4
5
6
7public class Out {
class Inner {
void run() {
System.out.println("run...............");
}
}
}成员内部类创建对象的格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package com.example;
public class Out {
class Inner {
void run() {
System.out.println("run...............");
}
}
}
class Test {
public static void main(String[] args) {
// 成员内部类的调用
Out.Inner oIn = new Out().new Inner();
oIn.run();
}
}成员内部类的访问拓展
成员内部类中是否可以直接访问外部类的静态成员
可以,
外部类的静态成员只有一份可以被共享访问 成员内部类的实例方法中是否可以直接访问外部类的实例成员
可以的,
因为必须先有外部类对象, 才能有内部类对象, 所以可以直接访问外部类对象的实例成员 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package com.example;
public class Out {
static final String NAME = "coder-itl";
int age = 20;
class Inner {
void run() {
// 访问静态成员
System.out.println(NAME + "run...............");
// 访问外部类的实例成员
System.out.println("年龄是: " + age);
}
}
}
class Test {
public static void main(String[] args) {
// 成员内部类的调用
Out.Inner oIn = new Out().new Inner();
oIn.run();
}
}成员内部类 
面试题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.example;
public class Out {
private int hearbeat = 150;
class Inner {
private int hearbeat = 110;
public void show() {
int hearbeat = 78;
System.out.println(hearbeat); // 78
System.out.println(this.hearbeat); // 110
System.out.println(Out.this.hearbeat); // 150
}
}
}
class Test {
public static void main(String[] args) {
Out.Inner oIn = new Out().new Inner();
oIn.show();
}
}
局部内部类
匿名内部类* - 本质上是一个没有名字的局部内部类,
定义在方法中、代码块中等 - 作用: 方便创建子类对象,最终目的是为了简化代码的编写
- 特点
- 匿名内部类是一个没有名字的内部类
- 匿名内部类写出来就会产生一个匿名内部类的对象
- 匿名内部类的对象类型相当于是当前
new的那个类型的子类类型
- 本质上是一个没有名字的局部内部类,
第九章
集合
集合概述
集合: 集合是
java中提供的一种容器,可以用来存储多个数据 集合和数组都是容器,
他们的区别? - 数组的长度是固定的。集合的长度是可变的
- 数组中存储的是同一类型元素,
可以存储基本数据类型值。集合存储的都是对象,而对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储。
图示

集合特点
List集合: 有索引, 可以存储重复元素, 可以保证存取顺序 ArrayList:底层是数组实现的, 查询快、增删慢 LinkList:底层是链表实现的、查询慢、增删快
Set集合: 无索引、不可以存储重复元素、存取无序 HashSet: 底层是哈希表+(红黑树) 实现的、无索引、不可以存储重复元素、存取无序 LinkHashSet: 底层是哈希表+ 链表实现的、无索引、不可以存储重复元素、可以保证存取顺序 TreeSet: 底层是二叉树实现,一般用于排序
集合中的常用方法
[上述集合公用特性] 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
38package com.coderitl.collection;
import java.util.ArrayList;
import java.util.Collection;
public class CollectionMethods {
public static void main(String[] args) {
/*
* boolean add(E e); 向集合中添加元素
* boolean remove(E e); 删除集合中的某个元素
* void clear(); 清空集合所有的元素
* boolean contains(E e); 判断集合中是否包含某个元素
* boolean isEmpty(); 判断集合是否为空
* int size(); 获取集合的长度
* Object[] toArray(); 将集合转成一个数组
* */
// collection 实例化
Collection<String> str = new ArrayList<>();
// add 向集合中添加元素
str.add("Hello");
str.add("World");
str.add("coder-itl");
System.out.println(str);
// 在集合中删除元素
str.remove("Hello");
System.out.println(str);
Object[] objects = str.toArray();
for (int i = 0; i < objects.length; i++) {
System.out.println(objects[i]);
}
}
}小题总结
1
2
3ArrayList、LinkList、Vector 三者的异同?
相同点: 三个类都是实现了 List 接口,存储数据的特点相同: 存储有序的、可重复的数据
不同:
Iterator 接口
基本介绍
1
2
3
4
5
6
7
8java.util.Iterator
接口: 迭代器 (对集合进行遍历)
常用方法:
boolean hashNext() 判断集合中还有没有下一个元素,有就返回 true, 没有就返回 false
E next() 取出集合中的下一个元素
Iterator 迭代器,是一个接口,我们无法直接使用, 需要使用 Iterator 接口的实现类对象, 获取实现类的方式比较特殊,Collection 接口中有一个方法, 叫 iterator(), 这个方法返回
的就是迭代器的实现类对象Iterator
使用步骤 1
2
3
4迭代器的使用步骤 (重点):
1. 使用集合中的方法 iterator() 获取迭代器的实现类对象,使用 iterator 接口接受(多态)
2. 使用 Iterator 接口中的方法 hashNext() 判断还有没有下一个元素
3. 使用 Iterator 接口中的方法 next 取出集合中的下一个元素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
26import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class TestIterator {
public void testIterator() {
// 多态
Collection<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
// 获取迭代器的实现类对象,并且会把指针 (索引) 指向集合的 -1 索引
Iterator<String> it = list.iterator();
// 不确定次数的循环 使用 while
while (it.hasNext()) { // it.hashNext 判断集合中还有没有下一个元素
String next = it.next(); // it.next() 做了两件事: 1. 取出下一个元素 2. 会把指针向后移动一位
System.out.println(next);
}
}
}增强
for循环1
2
3
4
5
6
7
8
9
10
11
12
13增强 for 循环: 底层使用的也是 迭代器,使用 for 循环的格式, 简化了迭代器的书写, 是 JDK1.5
之后出现的新特性
Collection<E> extends Iterable(E): 所有的单列集合都可以使用增强 for
public interface Iterable<T> 实现这个接口语序对象称为 "foreach" 语句的目标
增强 for 循环: 用来遍历集合和数组
格式:
for(集合/ 数组的数据类型 变量名: 集合名 / 数组名){
System.out.println(变量名);
}1
2
3
4
5
6
7
public void testForEach() {
String[] str = {"10", "11", "20", "90"};
for (String strValue : str) {
System.out.println(strValue);
}
}习题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void testExer() {
List list = new ArrayList();
list.add(1); // index: 0
list.add(2);// index: 1
list.add(3);// index: 2 --> remove 掉
updateList(list);
System.out.println(list); // [2]
}
private void updateList(List list) {
list.remove(2); // 使用的是索引
list.remove(new Integer(1)); // 根据值删除
}集合每日一问
- 集合
Collection中存储的如果时自定义类的对象、需要自定义类重写那个方法? 为什么? ArrayList、LinkList、Vector三者的相同点与不同点? - list
接口的常用方法有那些?(增、删、改、查、插、长度、遍历) - 如何使用
iterator和 增强for循环遍历List Set存储数据的特点时什么? 常见的实现类有什么?说明一下彼此特点。
- 集合
Map 集合
基本方法使用
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
80
81
82
83
84
85
86
87
88package com.coderitl.hashmap;
import java.util.HashMap;
import java.util.Map;
public class HashMapTest {
public static void main(String[] args) {
// Map 的增删改基本使用
MapMethods();
System.out.println("-----------------------------");
// Map 查询方法
MapQuery();
}
public static void MapMethods() {
/*
* 添加、删除、修改:
* Object put(Object key,Object value): 将指定 key-value 添加到(或修改) 当前 map 对象中
* void putAll(Map m): 将 m 中的所有 key-value 对存放到当前 map 中
* Object remove(Object key): 移除指定 key的 key-value 对, 并返回 value
* void clear(): 清空当前 map 中所有的数据
* */
Map map = new HashMap();
// Object put(Object key,Object value): 将指定 key-value 添加到(或修改) 当前 map 对象中
map.put("Key1", "JavaSE");
map.put("Key2", "红黑树");
map.put("Key3", "数据结构");
// put(key) 修改
map.put("Key1", "JavaEE");
System.out.println(map);
// Object remove(Object key): 移除指定 key的 key-value 对, 并返回 value
map.remove("key1");
// void putAll(Map m): 将 m 中的所有 key-value 对存放到当前 map 中
Map map1 = new HashMap();
map1.put("key9", "JavaScript");
map1.put("key8", "JavaScript权威指南");
map.putAll(map1);
System.out.println(map);
// void clear(): 清空当前 map 中所有的数据
map1.clear(); // map = null? 会报空指针异常吗?
System.out.println(map1); // 没有出现空指针异常问题,反而输出: {}
}
public static void MapQuery() {
/*
* Object get(Object key): 获取指定 key 对应的 value
* boolean containskey(Object key): 是否包含指定的 key
* boolean containsValue(Object value): 是否包含指定的 value
* int size(): 返回 map 中 keu-value 对的个数
* boolean isEmpty(): 判断当前 map 是否为空
* boolean equals(Object obj): 判断当前 map 和参数对象 obj 是否相等
* */
Map map = new HashMap();
map.put("Key1", "JavaSE");
map.put("Key2", "红黑树");
map.put("Key3", "数据结构");
map.put(43, 45);
// Object get(Object key): 获取指定 key 对应的 value
Object key2 = map.get("Key2");
System.out.println("指定key 的 value: " + key2);
// boolean containskey(Object key): 是否包含指定的 key
boolean key4 = map.containsKey("key4");
System.out.println("这个key 将会是不存在的: " + key4);
// boolean containsValue(Object value): 是否包含指定的 value
boolean redTree = map.containsValue("红黑树");
System.out.println("红黑树这个value 存在: " + redTree);
// int size(): 返回 map 中 keu-value 对的个数
int mapSize = map.size();
System.out.println("map的长度: " + mapSize);
// boolean isEmpty(): 判断当前 map 是否为空
boolean empty = map.isEmpty();
System.out.println("判断map 是否为空: " + empty);
// boolean equals(Object obj): 判断当前 map 和参数对象 obj 是否相等
// map.equals(); // 这个怎么用?
}
}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
55import org.junit.Test;
import java.util.*;
public class TestHashMap {
public void testHashMap() {
Map map = new HashMap();
map.put("One", 1);
map.put("Two", 2);
map.put("Three", 3);
// Set keySet(): 返回所有的key 构成的 Set 集合
Set set = map.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
// 遍历所有的 key
System.out.println(iterator.next());
}
System.out.println();
// Collection values(): 返回所有的 value 构成的 Collection集合
Collection values = map.values();
for (Object obj : values) {
// 遍历所有的 value
System.out.println(obj);
}
// Set entrySet(): 返回所有的 key-value 对构成的 Set集合
Set entrySet = map.entrySet();
Iterator entryIterator = entrySet.iterator();
// 方式一: 遍历 key:value
while (entryIterator.hasNext()) {
Object obj = entryIterator.next();
Map.Entry entry = (Map.Entry) obj;
// 遍历 key:value
System.out.println(entry.getKey() + ":" + entry.getValue());
}
System.out.println("-----------------");
// 方式二: 遍历 key:value
Set set1 = map.keySet();
Iterator iterator1 = set.iterator();
while (iterator1.hasNext()) {
// 遍历所有的 key
Object next1 = iterator1.next();
System.out.println(next1 + ":" + map.get(next1));
}
}
}Map存储数据的特点是什么? 并指明 key、value、entry存储数据的特点 - 描述
HashMap的底层原理 ( JDK-8) Map中常用实现类有哪些? 各自有什么特点? - 如何遍历
Map中的 key-value对? 代码实现 Collection和 Collections的区别?
可变参数
基础使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package com.example;
public class MapDemo {
public static void main(String[] args) {
int res = getSum(1, 2, 3, 4);
System.out.println(res);
}
private static int getSum(int... num) {
int sum = 0;
for (int i : num) {
sum += i;
}
return sum;
}
}- 注意点
- 可变参数本质上是一个数组
- 可变参数只能只有一个且这个参数只能放在形参列表的最后
- 注意点
Collections
java.util.Collections: 是集合工具类作用:
Collections不是集合,而是集合的工具类 常用
API方法名称 说明 addAll只能对 单列结合进行批量添加元素shuffle捣乱 List集合元素的顺序 示例
1
2
3
4
5
6
7
8
9
10
11
12package com.example;
import java.util.ArrayList;
import java.util.Collections;
public class MapDemo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "1", "2", "3");
System.out.println(list);
}
}
不可变集合
使用场景
- 如果某个数据不能被修改,把他防御性地拷贝到不可变集合中是个很好的实践
- 或者当集合对象被不可信的库调用时,不可变是安全的
JDK8+可用
List1
2
3
4
5
6
7
8
9public class MapDemo {
public static void main(String[] args) {
// 存储的数据只能被读取
List<Integer> integers = List.of(1, 2, 3, 4, 5);
for (Integer num : integers) {
System.out.println(num);
}
}
}Set1
2
3
4
5
6
7
8
9
10
public class MapDemo {
public static void main(String[] args) {
Set<Integer> integers = Set.of(1, 2, 3, 4, 5);
for (Integer num : integers) {
System.out.println(num);
}
}
}Map1
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// Map 里面的 of 方法,参数是有上限的,最多只能传递 20 个参数,10
个键值对
package com.example;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapDemo {
public static void main(String[] args) {
Map<String, Object> map = new HashMap();
map.put("a1", "a1");
map.put("a2", "a2");
map.put("a3", "a3");
map.put("a4", "a4");
map.put("a5", "a5");
map.put("a6", "a6");
map.put("a7", "a7");
map.put("a8", "a8");
map.put("a9", "a9");
map.put("a10", "a10");
map.put("a11", "a11");
// 获取到所有键值对的 entry 对象
Set<Map.Entry<String, Object>> entries = map.entrySet();
// 把 entry 变成一个数组
Map.Entry[] arr1 = new Map.Entry[0];
// toArray(): 方法在底层会比较集合的长度跟数组长度两者的大小
// 如果集合的长度(11) > 数组的长度 (0): 数据在数组中放不下, 此时会根据实际数据的个数 (11), 重新创建数据
// 如果集合的长度(11) < 数组的长度 (假如 20):数据在数组中放得下, 此时不会创建新的数组, 而是直接使用 (20 作为长度)
Map.Entry[] arr2 = entries.toArray(arr1);
// 不可变的集合
Map ofEntries = Map.ofEntries(arr2);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// JDK10 出现了新方法简化上述
public class MapDemo {
public static void main(String[] args) {
Map<String, Object> map = new HashMap();
map.put("a1", "a1");
map.put("a2", "a2");
map.put("a3", "a3");
map.put("a4", "a4");
map.put("a5", "a5");
map.put("a6", "a6");
map.put("a7", "a7");
map.put("a8", "a8");
map.put("a9", "a9");
map.put("a10", "a10");
map.put("a11", "a11");
// 创建不可变的 map
Map<String, Object> stringObjectMap = Map.copyOf(map);
System.out.println(stringObjectMap);
}
}
Stream
使用步骤
先得到一条
Stram,并把数据放上去流 (流水线) 利用
Stream流中的 API进行各种操作 过滤 转换 中间方法:方法调用完毕后, 还可以调用其他方法 统计 打印 终结方法:最后一步,调用完毕之后,不能调用其他方法 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package com.example;
import java.util.ArrayList;
import java.util.Collections;
public class MapDemo {
public static void main(String[] args) {
// 单列集合
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张三", "张三丰", "李四", "王五");
list
// 获取流水线
.stream()
// 使用中间方法
.filter(name -> name.length() > 2)
// 使用终结方法结束
.forEach(res -> System.out.println(res));
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.example;
import java.util.HashMap;
import java.util.Map;
public class MapDemo {
public static void main(String[] args) {
// 双列结合使用 stream
Map<String, Integer> map = new HashMap<>();
map.put("aaa", 111);
map.put("bbb", 222);
map.put("ccc", 333);
map.put("ddd", 444);
// 第一种获取 stream 流的方式
map.keySet()
.stream()
.forEach(word -> System.out.println(word));
// 第二种获取 stream 流的方式
map.entrySet().stream().forEach(word -> System.out.println(word));
}
}1
2
3
4
5
6
7
8
9
10
11
12package com.example;
import java.util.Arrays;
public class MapDemo {
public static void main(String[] args) {
// 数组获取 stream
Integer[] arr = {1, 2, 3, 4};
Arrays.stream(arr).filter(num -> num > 2).forEach(num -> System.out.println(num));
}
}1
2
3
4
5
6
7
8
9
10
11package com.example;
import java.util.stream.Stream;
public class MapDemo {
public static void main(String[] args) {
// 一堆零散的数据使用 stream: 这堆零散的数据必须是同一种数据类型
Stream.of(1, 2, 3, 4, 5).filter(num -> num > 2).forEach(num -> System.out.println(num));
}
}
stream流的中间方法 名称 说明 filter过滤 limit获取前几个元素 skip跳过前几个元素 distinct元素去重, 依赖 hashCode和 equals 方法 concat合并 a和 b两个流为一个 map转换流中的数据类型 - 注意点
- 注意点一: 中间方法,返回新的
stream流,原来的 stream流只能使用一次, 建议使用链式编程 - 注意点二:修改
stream流中的数据, 不会影响原来集合或者数组中的数据
- 注意点一: 中间方法,返回新的
- 注意点
stream流的中间方法 名称 说明 forEach遍历 count统计 toArray收集流中的数据,放到数组中 collect收集流中的数据,放到集合中
第十章
泛型
泛型概念
概念
所谓泛型、就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时
(例如: 继承或者实现这个接口, 用到这个类型声明变量、创建对象时) 确定 (即传入实际的类型参数,也称为类型实参) 为什么要有泛型
- 解决元素存储的安全性问题
- 解决获取数据元素时,需要类型强制转换的问题
引入泛型
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
29package com.coderitl.generic;
import java.util.ArrayList;
public class GenericTest {
public static void testGengeic() {
// 在集合中使用泛型之前的情况
ArrayList score = new ArrayList();
// 需求: 存储成绩
score.add(90);
score.add(98);
score.add(59);
score.add(60);
// 不安全问题:
score.add("Java");
// 循环遍历:
for (Object obj : score) {
// 强转时可能出现 ClassCastException 异常
int scoreStu = (int) obj;
System.out.println(scoreStu);
}
}
public static void main(String[] args) {
testGengeic();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13// 使用泛型之后
ArrayList<Integer> score = new ArrayList<Integer>();
score.add(89);
score.add(98);
score.add(59);
score.add(99);
// 编译时就会报错,限制了数据类型传入
// score.add("java");
for (Integer sc : score) {
// 避免了数据类型转换
System.out.println(sc);
}
1
2
3
4
5
6
7
8
9
10
11
12// 使用泛型之后
ArrayList<Integer> score = new ArrayList<Integer>();
score.add(89);
score.add(98);
score.add(59);
score.add(99);
// 使用 Iterator 遍历输出
Iterator<Integer> iterator = score.iterator();
while (iterator.hasNext()) {
Integer next = iterator.next();
System.out.println(next);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void testGenHashMap() {
// Map比较特殊: 由于 public interface Map {} K:key V:value
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("英语", 20);
map.put("语文", 120);
map.put("数学", 150);
map.put("历史", 80);
// map 怎么使用 Iterator
Set<Map.Entry<String, Integer>> entries = map.entrySet();
// 泛型嵌套
Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> next = iterator.next();
System.out.println(next);
}
}
如何自定义泛型结构
泛型类
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
39package com.coderitl.generic;
public class GenericDemo<T> {
// 类的内部结构就可以使用类的泛型
T genericT;
// 自定义泛型类
private String name;
private int age;
// 无参构造
public GenericDemo() {
}
public GenericDemo(String name, int age, T genericT) {
this.name = name;
this.age = age;
this.genericT = genericT;
}
// 对泛型提供 getter 和 setter
public T getGenericT() {
return genericT;
}
public void setGenericT(T genericT) {
this.genericT = genericT;
}
public String toString() {
return "GenericDemo{" +
"genericT=" + genericT +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package com.coderitl.generic;
public class GenericDemoMain {
public static void main(String[] args) {
// 使用自定义泛型
GenericDemo demo = new GenericDemo(); // 未使用泛型
demo.setGenericT("ObjectG"); // Object genericT
// 使用泛型
GenericDemo<String> list = new GenericDemo<String>("StringGG", 12, "NoObj");
list.setGenericT("StringG"); // 会自动提示指定泛型类型 --> String genericT
}
}
public class SubGeneric extends GenericDemo<Interge>{
}
// new 子类对象时,不需要再添加泛型 --> 由于子类在继承带泛型的父类时, 指明了泛型类型. 则实例化子类对象时,不需要再指明泛型
泛型方法
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
34public class Edemo<String> {
public <E> List<E> ToArray(E[] arry) {
ArrayList<E> list = new ArrayList<E>();
// 遍历数组
for (E e : arry) {
// 集合添加遍历出的数据
list.add(e);
}
return list;
}
}
-----------------------------------------------------
package com.coderitl.generic;
import java.util.List;
public class EdemoMain {
public static void main(String[] args) {
// 类的实例化
Edemo<String> demo = new Edemo<>();
// 创建一个 Integer 类型数组
String[] list = new String[]{"Python", "java"};
System.out.println(list);
// 调用泛型方法: 方法是封装的循环遍历方法 <--- 认为: 规范了数据安全
List<String> integers = demo.ToArray(list);
System.out.println(integers);
}
}
// 泛型方法: 在方法中出现了泛型结构,泛型参数与类的泛型参数没有任何关系,也就是说,泛型方法所属的类不是泛型类都没有关系
// 泛型方法,可以声明为静态的,原因: 泛型参数是在调用时确定的,并非在实例化类时确定
File
1 | package com.coderitl.file; |
1 | package com.coderitl.file; |
1 | package com.coderitl.file; |
1 | package com.coderitl.file; |
listFiles- 当调用者
File表示的路径不存在时, 返回 null - 当调用者
File表示的路径是文件时, 返回 null - 当调用者
File表示的路径是一个空文件夹时, 返回一个长度为 0的数组 - 当调用者
File表示的路径是一个有内容的文件夹时, 将里面所有文件和文件夹的路径放在 File数组中返回 - 当调用者
File表示的路径是一个有隐藏文件的文件夹时, 将里面所有文件和文件夹的路径放在 File数组中返回, 包含隐藏文件 - 当调用者
File表示的路径是需要权限才能访问的文件夹时, 返回 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
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
69package com.coderitl.file;
import java.io.File;
import java.util.Scanner;
/*
* 1. 利用 File 构造器,new 一个文件目录 file
* 1.1 在其中创建多个文件和目录
* 1.2 编写方法,实现删除 file 中指定文件的操作
* 2. 判断指定目录下是否有后缀名未 .jpg 的文件,如果有, 就输出该文件名称
* 3. 遍历指定目录所有文件名称,包括子文件目录中的文件
* 拓展1: 并计算指定目录占用空间的大小
* 拓展2: 删除指定文件目录及其下的所有文件
* */
public class FileHomeWork {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入要操作的文件或目录路径: ");
String filePath = sc.next();
File file = new File(filePath);
// 1. 利用 File 构造器,new 一个文件目录 file
// * 1.1 在其中创建多个文件和目录
// * 1.2 编写方法,实现删除 file 中指定文件的操作
// 创建
CreateFolder(file);
DeleteFolder(file);
// * 2. 判断指定目录下是否有后缀名为 .jpg 的文件,如果有, 就输出该文件名称
GetJpgPrint(file); // 也可以调用 endsWith() 方法
// * 3. 遍历指定目录所有文件名称,包括子文件目录中的文件
// * 拓展1: 并计算指定目录占用空间的大小
// * 拓展2: 删除指定文件目录及其下的所有文件
}
public static void CreateFolder(File file) {
// 怎么同时创建多个文件或目录
try {
boolean newFile = file.createNewFile();
if (newFile) {
String name = file.getName();
System.out.println("名称为 " + name + "创建成功");
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void DeleteFolder(File file) {
if (file.exists()) {
String name = file.getName();
boolean delete = file.delete();
System.out.println("名称为 " + name + "删除成功");
}
}
public static void GetJpgPrint(File file) {
String name = file.getName();
String substring = name.substring(name.length() - 4, name.length()); // .jpg
if (substring.equals(".jpg")) {
// 输出包含 jpg 的name
System.out.println(name);
} else {
// 输出提示信息
System.out.println("文件中不存在后缀名为: .jpg的文件");
}
}
}
IO 流
IO-概念
IO流概述 IO流:存储和读取数据的解决方案 - 流: 是一种抽象概念,
是对数据传输的总称, 也就是说数据在设备间的传输称为流、流的本质是数据传输 IO流就是用来处理设备间数据传输的问题的 - 常见的应用:
文件复制、文件上传、文件下载
- 常见的应用:
IO-分类
IO流分类 分类 
- 纯文本文件
( 只能使用字符流操作): 使用Windwos自带的记事本打开就能读懂 - 字节流: 可以操作所有类型的文件
- 纯文本文件
字节流抽象基类
InputStream:这个抽象类是表示字节输入流的所有类的超类OutputStream: 这个抽象类是表示字节输出流的所有类的超类- 子类特点名称:
子类名称都是以其父类名作为子类名的后缀
IO-体系
IO流体系 体系 
FileReader 和 FileWriter
FileReader与 FileWriter使用 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// 说明:
// 1. read() 的理解: 返回读入的一个字符,如果达到文件末尾 返回 -1
// 2. 异常的处理: 为了保证流资源一定可以执行关闭操作,需要使用 try-catch-finally 处理
package com.coderitl.testio;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class IOTest {
public static void main(String[] args) {
FileReader fr = null;
try {
File file = new File("D:\\JavaCode\\src\\com\\coderitl\\testio\\Hello.txt");
fr = new FileReader(file);
// 数据的读入
int data;
while ((data = fr.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void testFileWrite() throws IOException {
/*
* 说明:
* 1. 输出操作,对应的 File 可以不存在,并不会报异常
* 2.
* File 对应的硬盘中的文件如果不存在,在输出的过程中, 会自动创建此文件:
* File对应的硬盘中的文件如果存在:
* 如果流使用的构造器是: FileWriter(file,false)/ FileWrite(file): 对原有文件覆盖
* 如果流使用的构造器是: FileWriter(file,true): 不会对原有文件覆盖,而是在原有文件后追加
* */
File file = new File("D:\\JavaCode\\src\\com\\coderitl\\testio\\Hello.txt");
// 写的时候存在异常
FileWriter fw = new FileWriter(file, true);
fw.write("我爱学习Java1");
fw.write("我爱学习Java2");
fw.write("我爱学习Java3");
fw.write("我爱学习Java4");
fw.write("我爱学习Java5");
fw.close();
}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 void testCoptText() {
FileWriter fw = null;
FileReader fr = null;
try {
File writeFile = new File("D:\\JavaCode\\src\\com\\coderitl\\testio\\LearnJava.txt");
File readFile = new File("D:\\JavaCode\\src\\com\\coderitl\\testio\\ReadJava.txt");
// 创建写 流
fw = new FileWriter(writeFile, true);
fr = new FileReader(readFile);
// 读取 字符数组
char[] cbuf = new char[5];
// 记录每次读入到 cbuf 数组中的字符个数
int len;
while ((len = fr.read(cbuf)) != -1) {
// 每次写出 len个字符
fw.write(cbuf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileInputStream 与FileOutputStream
FileInputStream与 FileOutputStream操作本地文件的字节输出流,
可以把程序中的数据写到本地文件中 使用步骤
创建字节输出流对象
- 细节一: 参数是字符串表示的路径或者是
File对象都是可以的 - 细节二: 如果文件不存在会创建一个新的文件,
但是要保证父级路径是存在的 - 细节三: 如果文件已经存在,
则会清空文件
- 细节一: 参数是字符串表示的路径或者是
写数据
- 细节:
write方法的参数是整数, 但是实际上写到本地文件中的是整数在 ASCII上对应的字符
- 细节:
释放资源
- 细节: 每次使用完流之后都要释放资源
1
2
3FileOutputStream fos = new FileOutputStream("./aa.txt");
fos.write(100);
fos.close();
换行写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("./aa.txt");
String str1 = "Hello World";
byte[] arr1 = str1.getBytes();
fos.write(arr1);
// 换行: \r\n(可以只写一个,底层会补全, 但是建议写全)
String wrap = "\r\n";
byte[] arr2 = wrap.getBytes();
fos.write(arr2);
String str3 = "Java NiuBi";
byte[] arr3 = str3.getBytes();
fos.write(arr3);
fos.close();
}续写
1
2// true: 续写开关,
开启即可
FileOutputStream fos = new FileOutputStream("./aa.txt",true);
操作本地文件的字节输入流,
可以把本地文件中的数据读取到程序中来。 书写步骤
创建字节输入流对象
细节: 如果文件不存在,
就直接报错 Java为什么会这么设计呢? 因为创建出来的文件是没有数据的,
没有任何意义. 所以就直接报错了。 程序中最重要的是:
数据
读数据
- 细节: 一次读一个字节,
读出来的就是数据在 ASCII上对应的数字 - 细节: 读取到文件末尾了,
read方法返回 -1
- 细节: 一次读一个字节,
释放资源
- 细节: 每次使用完流之后都要释放资源
循环读取
1
2
3
4FileInputStream fi = new FileInputStream("./aa.txt");
int data = fi.read();
// 一次只读取一个
System.out.println(data);1
2
3
4
5
6
7
8
9
10// 读取文件的所有内容(不合理写法)
public static void main(String[] args) throws IOException {
FileInputStream fi = new FileInputStream("./aa.txt");
// read: 表示读取数据,而且是读取一个数据就移动一次指针 (下面总共出现两个 read )
while (fi.read() != -1) {
int data = fi.read();
System.out.println((char) data);
}
fi.close();
}1
2
3
4
5
6
7
8
9// 正确写法,
应该定义一个变量
public static void main(String[] args) throws IOException {
FileInputStream fi = new FileInputStream("./aa.txt");
int data;
while ((data = fi.read()) != -1) {
System.out.print((char) data);
}
fi.close();
}正确读取输出 
文件拷贝
思路: 边读边写
实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public static void main(String[] args) throws IOException {
// 文件输入流(读)
FileInputStream fi = new FileInputStream("./aa.txt");
// 文件输出流(写)
FileOutputStream fos = new FileOutputStream("./bb.txt");
int data;
while ((data = fi.read()) != -1) {
// 将数据写如到 bb.txt [拷贝: 边读边写]
fos.write(data);
}
// 关闭原则: 先开后关
fos.close();
fi.close();
}
一次读取多个数据【缓冲数组
(1024 的整数倍)】 1
2
3
4
5
6
7
8
9
10
11
12
13public static void main(String[] args) throws IOException {
// 文件输入流(读)
FileInputStream fi = new FileInputStream("./aa.txt");
// 读取数据
byte[] bytes = new byte[2];
// 一次读取多个字节数据,具体读多少, 根数组的长度有关
// 返回值(len): 本地读取到了多少个字节数据
int len = fi.read(bytes);
// 读取 bytes,从 0 开始读取, 读 len 个
String s = new String(bytes, 0, len);
System.out.println(s);
fi.close();
}文件拷贝的优化【推荐】
1
2
3
4
5
6
7
8
9
10
11
12
13public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("./aa.txt");
FileOutputStream fos = new FileOutputStream("./bb.txt");
int len;
byte[] bytes = new byte[1024];
while ((len = fis.read(bytes)) != -1) {
// 拷贝
fos.write(bytes, 0, len);
}
// 释放资源
fos.close();
fis.close();
}
高级流
缓冲流
缓冲流体系
缓冲流体系 
字节缓冲流
常用方法
方法名称 说明 BufferedInputStream(InputStream is)把基本流包装成高级流, 提高读取数据的性能 BufferedOutputStream(OutputStream os)把基本流包装成高级流,提高写出数据的性能 提升性能的原理:底层自带了长度为
8192的缓冲区提高性能
转换流
转换流
转换流属于字符流
作用: 提供字节流与字符流之间的转换
编码解码
1
2解码: 字节、字节数组 转换为 字符数组、字符串
编码: 字符数组、字符串 转换为 字节、字节数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void testFileInputStream() {
InputStreamReader isr = null;
try {
FileInputStream fis = new FileInputStream("D:\\JavaCode\\src\\com\\coderitl\\testio\\Hello.txt");
isr = new InputStreamReader(fis,"UTF-8");
char[] cbuf = new char[20];
int len;
while((len=isr.read(cbuf))!=-1){
String str = new String(cbuf,0,len);
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}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 void testLiu() {
InputStreamReader isr = null;
OutputStreamWriter osw = null;
try {
// 1. 造文件
File file1 = new File("D:\\JavaCode\\src\\com\\coderitl\\testio\\Hello.txt");
File file2 = new File("D:\\JavaCode\\src\\com\\coderitl\\testio\\HelloGBK.txt");
// 2. 流
FileInputStream fis = new FileInputStream(file1);
FileOutputStream fos = new FileOutputStream(file2);
// 转换流
isr = new InputStreamReader(fis, "UTF-8");
osw = new OutputStreamWriter(fos, "GBK");
char[] cbuf = new char[20];
// 遍历
int len;
while ((len = isr.read(cbuf)) != -1) {
// 写入
osw.write(cbuf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (isr != null) {
isr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (osw != null) {
osw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
对象流
ObjectInputStream和 ObjectOutputStream- 用于存储和读取
基本数据类型 数据或对象 的处理流。它的强大之处就是可以把 Java中的对象写入到数据源中,也能把对象从数据源中还原回来
- 用于存储和读取
- 序列化: 用
ObjectOutputStream类保存基本类型数据或对象的机制 - 反序列化: 用
ObjectInputStream类读取基本类型数据或对象的机制 ObjectOutputStream和 ObjectInputStream不能序列化 static %}transient %}和 修饰的成员变量
字符集
- 在计算机中,
任意数据都是以二进制的形式来存储的 - 计算机中最小的存储单元是一个字节
ASCII字符集中, 一个英文占一个字节 - 简体中文版
Windows,默认使用GBK字符集 GBK字符集完全兼容 ASCII字符集 - 一个英文占一个字节,
二进制第一位是 0 - 一个中文占两个字节,
二进制高位字节的第一位是 1
- 一个英文占一个字节,
第十一章
网络编程
什么是网络编程
-
概念
在网络通信协议下,
不同计算机上运行的程序, 进行数据的传输 -
BS/CS架构的优缺点 BS CS 不需要开发客户端, 只需要开发服务端 画面可以做的非常精美, 用户体验好 用户不需要下载,打开浏览器就能使用 需要开发客户端,也需要开发服务端 如果应用过大,用户体验受到影响 用户需要下载和更新的时候太麻烦 -
网络编程三要素
- IP: 设备在网络中的地址,
是唯一标识 - 端口: 应用程序在设备中唯一的标识
- 协议: 数据在网络中传输的规则,
常见的协议有 UDP、TCP、http、https、ftp
- IP: 设备在网络中的地址,
socket
基础使用
-
服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package com.example;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
// 启动服务器
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器已经启动.............");
Socket socket = serverSocket.accept();
System.out.println("有客户端连接成功............");
}
} -
客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package com.example;
import java.io.IOException;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1", 8888);
System.out.println("服务器连接成功..........");
} catch (IOException e) {
System.out.println("服务器连接失败..............");
throw new RuntimeException(e);
}
}
}
多客户端 – 一个服务端
-
服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package com.example;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
// 启动服务器
ServerSocket serverSocket = new ServerSocket(8888);
int count = 0;
while (true) {
System.out.println("服务器已经启动.............");
Socket socket = serverSocket.accept();
count++;
System.out.println("有" + count + "连接成功");
}
}
} -
客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package com.example;
import java.io.IOException;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1", 8888);
System.out.println("服务器连接成功..........");
} catch (IOException e) {
System.out.println("服务器连接失败..............");
throw new RuntimeException(e);
}
}
}
通信
-
服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package com.example;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
// 启动服务器
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器已经启动.............");
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String msg = br.readLine();
System.out.println("客户端说: " + msg);
}
} -
客户端
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
28package com.example;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1", 8888);
// 流的准备
OutputStream os = socket.getOutputStream();
PrintStream printStream = new PrintStream(os, true);
// 流的操作
String msg = "吃饭了吗?";
// 发送消息
printStream.println(msg);
// 释放资源
printStream.close();
os.close();
} catch (IOException e) {
System.out.println("服务器连接失败..............");
throw new RuntimeException(e);
}
}
}-
输出
是否通信 
-
配合多线程
-
服务端
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
61package com.example;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public Server() throws Exception {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器已经启动成功.......");
while (true) {
// 等待客户端连接,如果有新的连接请求则返回一个新的套接字对象
Socket socket = serverSocket.accept();
System.out.println("客户端连接成功...............");
// 开始处理客户端的消息
ServerThread thread = new ServerThread(socket);
thread.start();
}
}
public static void main(String[] args) throws Exception {
new Server();
}
private class ServerThread extends Thread {
private Socket socket;
public ServerThread(Socket socket) {
this.socket = socket;
}
public void run() {
try {
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
while (true) {
// 有活就干
if (is.available() > 0) {
String msg = br.readLine();
System.out.println("客户端说: " + msg);
}
// 没活就休眠
try {
this.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
} -
客户端
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
27package com.example;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws IOException {
// 建立连接
try {
Socket socket = new Socket("127.0.0.1", 8888);
System.out.println("服务器连接成功.........");
Scanner sc = new Scanner(System.in);
String msg = sc.next();
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os, true);
ps.println(msg);
ps.close();
} catch (IOException e) {
System.err.println("服务器连接失败");
throw new RuntimeException(e);
}
}
}
类加载器
-
类加载
当程序要使用某个类时,如果该类还未被加载到内存中,
则系统会通过类的加载、类的连接、类的初始化这三个步骤来对类进行初始化。如果不出意外情况, JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称未类加载或者类初始化 加载过程 
-
类的加载
- 就是指将
class文件读入内存, 并为之创建一个 java.lang.Class对象 - 任何类被使用时,
系统都会为之建立一个 java.lang.Class对象
- 就是指将
-
类的连接
- 验证阶段: 用于校验被加载的类是否有正确的内部结构,
并和其他类协调一致 - 准备阶段: 负责为类的类变量分配内存,
并设置默认初始化值 - 解析阶段: 将类的二进制数据中的符号引用替换为直接引用
- 验证阶段: 用于校验被加载的类是否有正确的内部结构,
-
类的初始化
在给阶段,
主要就是对类变量进行初始化 - 类的初始化步骤
- 假如类还未被加载和连接,则程序先加载并连接该类
- 假如该类的直接父类还未被初始化,则先初始化器直接父类
- 假如类中有初始化语句,
则系统一次执行这些被初始化语句
- 类的初始化时机
- 创建类的实例
- 调用类的方法
- 访问类或者接口的类变量,或者为该类变量赋值
- 使用反射方式来强制创建某个类或接口对应的
java.lang.Class对象 - 初始化某个类的子类
- 直接使用
java.exe命令来运行某个主类
- 类的初始化步骤
-
-
类加载器的作用
- 负责将
.class文件加载到内存中, 并为之生成对应的 java.lang.Class对象
- 负责将
-
JVM的类加载机制 - 全盘负责: 就是当一个类加载负责加载某个
Class时, 该 Class所依赖的引用的其他 Class也将由该类加载器负责载入, 除非显示使用另外一个类加载器来载入 - 父类委托: 就是当一个类加载器负责加载某个
Class时, 先让父类加载器试图加载该 Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类 - 缓存机制: 保存所有加载过的
Class都会被缓存, 当程序需要使用某个 Class对象时, 类加载从缓存区中搜索该 Class,只由当缓存区中不存在该 Class对象时, 系统才会读取该类对应的二进制数据,并将其转换成 Class对象, 存储到缓存区
- 全盘负责: 就是当一个类加载负责加载某个
-
类加载器
ClassLoader: 是负责加载类的对象-
Java运行时具有以下内置类加载器 Bootstrap class loader: 它是虚拟机的内置类加载器,通常表示为 null,并且没有父nullPlatform class loader: 平台类加载器可以看到所有平台类,平台类包括由平台类加载器或其祖先定义的 JAVASE平台 API,其实现类和JDK特定的运行时类 Systemclass loader: 它也被称为应用程序类加载器,与平台类加载器不同。系统类加载器通常用于定义应用程序类路径,,模块路径和 JDK特定工具上的类
类加载器的继承关系:
System的父加载器为 Platform,而 Platforrm的父加载器为 Bootstrap -
Classloader中的两个方法 static ClassLoader getStstemClassLoader(): 返回用于委派的系统类加载器Classloader getParent(): 返回父类加载器进行委派
-
-
关于
java.lang.Class类的理解 -
类的加载过程
程序经过
java.exe命令以后, 会生成一个或多个字节码文件 ( .class结尾) 接着我们使用
java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到类内存中。此过程就称为类的加载。加载到内存中的类, 我们就称为运行时类, 此运行时的类,就作为 Class的一个实例 -
换句话说,Class 的实例就对应着一个运行时类 -
加载到内存中的运行时类, 会缓存一定的时间, 在此时间之内, 我们可以通过不同的方式来获取此运行时类
-
-
反射
-
反射概述
Java反射机制: 是指在运行时去获取一个类的变量和方法信息, 然后通过获取到的信息来创建对象,调用方法的一种机制,由于这种动态性,可以极大的增强程序的灵活性, 程序不用在编译期就完成确定,在运行期任然可以扩展 -
获取
Class类的对象 我们想要通过反射去使用一个类,首先我们要获取到该类的字节码文件对象,也就是类型为
Class类型的对象, 可以通过如下三种方式获取 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
43package com.example;
public class Student {
private Integer stuId;
private String stuName;
public Student() {
}
public Student(Integer stuId, String stuName) {
this.stuId = stuId;
this.stuName = stuName;
}
private Student(Integer stuId) {
this.stuId = stuId;
}
public Integer getStuId() {
return stuId;
}
public void setStuId(Integer stuId) {
this.stuId = stuId;
}
public String getStuName() {
return stuName;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
public String toString() {
return "Student{" +
"stuId=" + stuId +
", stuName='" + stuName + '\'' +
'}';
}
}-
使用类的
class属性来获取该类对应的 Class对象 1
2
3
4
5
6
7public static void main(String[] args) {
Class<Student> c1 = Student.class;
System.out.println(c1);
Class<Student> c2 = Student.class;
// 字节码只有一份: true
System.out.println(c2 == c1);
} -
调用对象的
getClass方法, 返回该对象所属类对应的 Class对象,该方法是 Object类中的方法, 所有的 java对象都可以调用该方法 1
2
3
4
5
6
7
8
9public static void main(String[] args) {
Class<Student> c1 = Student.class;
System.out.println(c1);
Student student = new Student();
Class<? extends Student> c2 = student.getClass();
// 字节码只有一份: true
System.out.println(c2 == c1);
} -
使用
Class类中的静态方法 forName(String className),该方法需要传入字符串参数, 该字符串参数的值是某个类的全路径,也就是完整包名的路径 1
2
3
4
5
6
7public static void main(String[] args) throws ClassNotFoundException {
Class<Student> c1 = Student.class;
System.out.println(c1);
Class<?> aClass = Class.forName("com.example.Student");
System.out.println(c1 == aClass);
}
-
-
反射获取构造方法并使用
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
27package com.example;
import java.lang.reflect.Constructor;
// 获取构造方法并使用
public class ReflectDemop {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
// 1. 获取 Class 对象
Class<Student> c1 = Student.class;
// 2. 获取构造方法并使用
// 返回一个包含 Constructor 对象的数组,Constructor对象反映了由该 Class 对象表示的类的所有公共构造函数
Constructor<?>[] constructors = c1.getConstructors();
for (Constructor<?> constructor : constructors) {
/**
* public com.example.Student()
* public com.example.Student(java.lang.Integer,java.lang.String)
*/
System.out.println(constructor);
}
System.out.println("-------------------------------------------------");
// 返回反应由该 Class 对象表示的类声明的所有构造函数的 Constructor 对象的数组
Constructor<?>[] declaredConstructors = c1.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
}
}输出所有构造 
1
2
3
4
5
6
7
8
9
10public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1. 获取 Class 对象
Class<Student> c1 = Student.class;
Constructor<Student> constructor = c1.getConstructor();
Student student = constructor.newInstance();
student.setStuId(1001);
student.setStuName("aa");
// Student{stuId=1001, stuName='aa'}
System.out.println(student);
} -
练习
-
需求: 通过反射实现
Student{stuId=1002, stuName='bb'} -
实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package com.example;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
// 获取构造方法并使用
public class ReflectDemop {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<Student> studentClass = Student.class;
// int 和 Integer的 class 不一样
Constructor<Student> constructor = studentClass.getConstructor(Integer.class, String.class);
Student student = constructor.newInstance(1002, "bb");
// Student{stuId=1002, stuName='bb'}
System.out.println(student);
}
}
-
-
反射获取成员变量并使用
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
59package com.example;
public class Student {
public String address;
int age;
private String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "Student{" +
"address='" + address + '\'' +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.example;
import java.lang.reflect.Field;
// 获取构造方法并使用
public class ReflectDemop {
public static void main(String[] args) {
Class<Student> studentClass = Student.class;
// Field[] getFields() 返回一个包含 Field 对象的数组,Field 对象反映由该 Class 对象表示的类或接口的所有可访问的公共字段
Field[] fields = studentClass.getFields();
for (Field field : fields) {
// public String address;
System.out.println(field);
}
System.out.println("------------------------------");
// Field[] 返回一个 Field 对象的数组,反映了由该 Class 对象表示的类或接口声明的所有字段
Field[] declaredFields = studentClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13// 成员变量赋值
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
Class<?> c = Class.forName("com.example.Student");
// 获取指定字段
Field addressField = c.getField("address");
// 获取无参构造
Constructor<?> conn = c.getConstructor();
Object obj = conn.newInstance();
// 给 obj 的成员变量 addressField 赋值为 广东
addressField.set(obj, "广东");
// Student{address='广东', age=0, name='null'}
System.out.println(obj);
}1
2
3
4
5
6
7
8
9
10// 成员变量赋值
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
Class<Student> c = Student.class;
Constructor<Student> conn = c.getConstructor();
Student student = conn.newInstance();
Field addressField = c.getField("address");
addressField.set(student, "nn");
// Student{address='nn', age=0, name='null'}
System.out.println(student);
} -
反射获取成员方法并使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public static void main(String[] args) {
// Method[] 返回一个包含方法对象的数组,方法对象反映由该 Class 对象表示的类或接口的所有公共方法, 包括由类或接口声明的对象以及从
// 超类和超级接口继承的类
Class<Student> c = Student.class;
Method[] methods = c.getMethods();
for (Method method : methods) {
System.out.println(method);
}
// Method[] 返回一个包好感方法对象的数组,方法对象反映由该 Class 对象表示的类或接口的所有声明方法, 包括 public,protected,default 访问和私有方法, 但不包括继承方法
Method[] declaredMethods = c.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod);
}
}1
2
3
4
5
6
7
8
9
10
11public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<Student> c = Student.class;
// 获取 save 方法
Method method = c.getMethod("save");
// 获取无参构造创建对象
Constructor<Student> cons = c.getConstructor();
Student student = cons.newInstance();
// invoke(obj,Object ...args) args方法的参数
// save...............
method.invoke(student);
}