
软件:即一系列按照特定顺序组织的计算机数据和指令集合。分为:系统软件和应用软件
人机交互方式:图形化界面 vs 命令行方式
应用程序 = 算法 + 数据结构
常用 DOS 命令:
dir:列出点钱目录下的文件以及文件夹
md:创建目录
rd:删除目录
cd:进入指定目录
cd..:退回到上一级目录
cd/:退回到根目录
del:删除文件
exit:退出 DOS 命令行

path 环境变量:Windows 重装系统执行命令时所要搜寻的路径
为什么要配置 path:希望 Java 的开发工具在任何文件的文件路径下都可以执行成功
https://blog.csdn.net/weixin_43344151/article/details/118917382
https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html

注意问题:安装软件的路径中不能包含中文、空格




此电脑 --> 右击“属性” --> 点击“高级系统设置” --> 点击“环境变量”

双击系统变量的PATH

依次点击确定
打开cmd窗口
运行java ,javac ,java -version出现以下画面代表安装配置成功




创建一个 Java 源文件:HelloWorld.java
public class HelloWorld{
public static void main(String[] args){
System.out.println("Hello, World!");
}
}javac HelloWorld.javajava HelloWorld

API:application programming interface
习惯上:将语言提供的类库,都称为 API
API 文档:针对于提供的类库如何使用,给的一个说明书

定义:被 Java 语言赋予了特殊含义,用做专门用途的字符串(单词)
特点:关键字中所写字母都为小写
具体关键字:


定义:现有Java版本尚未使用,但以后版本可能会作为关键字使用。
具体哪些保留字:goto、const
注意:自己命名标识符时要避免使用这些保留字
定义:凡是自己可以起名字的地方都叫标识符
涉及到的结构:包名、类名、方法名、变量名、接口名、常量名
规则:(必须遵守,否则,编译不通过)
规范:(可以不遵守,不影响编译和运行,但是要求大家遵守
注意点:

详细说明:
‘’ ,内部只能写一个字符
数据类型 变量名 = 变量值;
或者
数据类型 变量名;
变量名 = 变量值;
除了 boolean 类型之外的其它7种
当容量小的数据类型的变量与容量大的数据类型的变量做运算时,结果 自动提升为容量大的数据类型
byte 、short 、char –> int –> long –> float –> double
特别地:当 byte 、short 、char 三种类型的变量做运算时,结果为 int 类型
说明:此时的容量大小指的是,表示数的范围的大小,比如,float 容量要大于 long 的容量
自动类型提升运算的逆过程
“”避免:
// 编译错误
String s = 123;
// 编译错误
int i = (int)"123";
代码
/*
运算符之一:算术运算符
+ - + - * / % (前)++ ++(后) (前)-- --(后) +
*/
public class AriTest {
public static void main(String[] args) {
// 除号:/
int num1 = 12;
int num2 = 5;
int result1 = num1 / num2;
// 2
System.out.println(result1);
int result2 = num1 / num2 * num2;
// 10
System.out.println(result1);
double result3 = num1 / num2;
// 2.0
System.out.println(result3);
// 2.0
double result4 = num1 / num2 + 0.0;
// 2.4
double result5 = num1 / (num2 + 0.0);
// 2.4
double result6 = (double) num1 + num2;
System.out.println(result5);
System.out.println(result6);
// 取余:%
// 结果数的符号与被模数相同
// 开发中,经常使用 % 来判断能否被除尽的情况
int m1 = 12;
int n1 = 5;
System.out.println("m1 % n1 = " + m1 % n1);
int m2 = -12;
int n2 = 5;
System.out.println("m1 % n1 = " + m2 % n2);
int m3 = 12;
int n3 = -5;
System.out.println("m1 % n1 = " + m3 % n3);
int m4 = -12;
int n4 = -5;
System.out.println("m1 % n1 = " + m4 % n4);
// (前)++:先自增1,后运算
// ++(后):先运算,后自增1
int a1 = 10;
int b1 = ++a1;
int a2 = 10;
int b2 = a2++;
System.out.println("a1 = " + a1 + ", b1 = " + b1);
System.out.println("a2 = " + a2 + ", b2 = " + b2);
int a3 = 10;
a3++;
int b3 = a3;
// 注意点:自增1不会改变本身变量的数据类型
short s1 = 10;
s1++;
System.out.println(s1);
byte b = 127;
b++;
System.out.println("b = " + b);
// (前)--:先自减1,后运算
// --(后):先运算,后自减1
int a4 = 10;
int b4 = --a4;
System.out.println("a4 = " + a4 + ", b4 = " + b4);
int a5 = 10;
int b5 = --a5;
System.out.println("a5 = " + a5 + ", b5 = " + b5);
}
}特别说明
代码
/*
* 运算符之二:赋值运算符
* = += -= *= /= %=
*/
public class SetValueTest {
public static void main(String[] args) {
// =:赋值符号
int i1 = 10;
int j1 = 10;
int i2, j2;
// 连续赋值
i2 = j2 = 10;
int i3 = 10, j3 =10;
int num1 = 10;
num1 += 2; // num1 = num1 + 2;
System.out.println(num1);
int num2 = 10;
num2 %= 5; // num2 = num2 % 5;
System.out.println(num2);
short s1 = 10;
// 不会改变变量本身的数据类型
s1 += 2;
System.out.println(s1);
// 开发中,如果希望变量实现+2的操作,有几种方法?(前提:int num = 10)
// 方式1:num = num + 2;
// 方式2:num += 2; (推荐)
// 开发中,如果希望变量实现+1的操作,有几种方法?(前提:int num = 10)
// 方式1:num = num + 1;
// 方式2:num += 1;
// 方式3:num++; (推荐)
// 练习1
int i = 1;
i *= 0.1;
// 0
System.out.println(i);
i++;
// 1
System.out.println(i);
// 练习2
int m = 2;
int n = 3;
n *= m++;
// 3
System.out.println("m = " + m);
// 6
System.out.println("n = " + n);
// 练习3
int n1 = 10;
n1 += (n1++) + (++n1);
// 32
System.out.println( n1);
}
}特别说明
代码
/*
* 运算符之三:比较运算符
* == != > < >= <= instanceof
* 结论:
* 1.比较运算符的结果是 boolean 类型
* 2.区分 == 和 =
*/
public class CompareTest {
public static void main(String[] args) {
int i = 10;
int j = 20;
System.out.println(i == j); // false
System.out.println(i = j); // 20
boolean b1 = true;
boolean b2 = false;
System.out.println(b2 == b1); // false
System.out.println(b2 = b1); // true
}
}特别说明
比较运算符的结果都是 boolean 类型
> < >= <= :只能使用在数值类型的数据之间
== != :不仅可以使用在数值类型数据之间,还可以使用在引用类型变量之间
Account acct1 = new Account(1000);
Account acct2 = new Account(2000);
boolean b1 = acct1 == acct2; // 比较两个 Account 是否是同一账户
boolean b2 = acct1 != acct2;代码
/**
* Filename : LogicTest.java
* Author : keke
* Creation time : 下午8:06:50 2021年10月30日
* Description : 运算符之四:逻辑运算符
* && & || | ! ^
* 说明:
* 1.逻辑运算符操作的都是 boolean 类型的变量
*/
public class LogicTest {
public static void main(String[] args) {
// 区分 & 与 &&
// 相同点:
// 1.& 与 && 的运算结果相同
// 2.当符号左边为 true 时,二者都会执行符号右边的运算
// 不同点:
// 当符号左边为 false 时,& 会执行符号右边的运算,&& 不会执行符号右边的运算
// 开发中,推荐使用 &&
boolean b1 = true;
b1 = false;
int num1 = 10;
if (b1 & (num1++ > 0)) {
System.out.println("我现在在北京");
}else {
System.out.println("我现在在南京");
}
System.out.println("num1 = " + num1);
boolean b2 = true;
b2 = false;
int num2 = 10;
if (b2 && (num2++ > 0)) {
System.out.println("我现在在北京");
}else {
System.out.println("我现在在南京");
}
System.out.println("num2 = " + num2);
// 区分:| 与 ||
// 相同点:
// 1.| 与 || 的运算结果相同
// 2.当符号左边为 false 时,二者都会执行符号右边的运算
// 不同点:
// 当符号左边为 true 时,| 会执行符号右边的运算,|| 不会执行符号右边的运算
// 开发中,推荐使用 ||
boolean b3 = false;
b3 = true;
int num3 = 10;
if (b3 | (num3++ > 0)) {
System.out.println("我现在在北京");
}else {
System.out.println("我现在在南京");
}
System.out.println("num3 = " + num3);
boolean b4 = false;
b4 = true;
int num4 = 10;
if (b4 || (num4++ > 0)) {
System.out.println("我现在在北京");
}else {
System.out.println("我现在在南京");
}
System.out.println("num4 = " + num4);
}
}特别说明
代码
/**
* Filename : BitTest.java
* Author : keke
* Creation time : 下午8:42:10 2021年10月30日
* Description : 运算符之五:位运算符(了解)& | ~ ^ << >> >>>
* 结论:
* 1.位运算符操作的都是整型的数据
* 2.<<:在一定范围内,向左移1位,相当于乘以2
* >>:在一定范围内,右移1位,相当于除以2
*
* 面试题:最高效的计算 2 * 8 ? 2 << 3 或 8 << 1
*/
public class BitTest {
public static void main(String[] args) {
int i = 21;
i = -21;
System.out.println("i << 2 = " + (i << 2));
System.out.println("i << 3 = " + (i << 3));
System.out.println("i << 26 = " + (i << 26));
System.out.println("i << 27 = " + (i << 27));
System.out.println("i >> 2 = " + (i >> 2));
int m = 12;
int n = 5;
System.out.println("m & n = " + (m & n));
System.out.println("m | n = " + (m | n));
System.out.println("m ^ n = " + (m ^ n));
// 练习:交换两个变量的值
int num1 = 10;
int num2 = 20;
System.out.println("num1 = " + num1 + ", num2 =" + num2);
// 方式一:定义临时变量 (推荐)
int temp = num1;
num1 = num2;
num2 = temp;
System.out.println("num1 = " + num1 + ", num2 =" + num2);
// 方式二:
// 好处:不用定义临时变量
// 弊端:
// 1、相加操作可能超出存储范围
// 2.有局限性:只能适用于数值类型
num1 = num1 + num2;
num2 = num1 - num2;
num1 = num1 - num2;
System.out.println("num1 = " + num1 + ", num2 =" + num2);
// 方式三:使用位运算符
num1 = num1 ^ num2;
num2 = num1 ^ num2;
num1 = num1 ^ num2;
System.out.println("num1 = " + num1 + ", num2 =" + num2);
}
}面试题:最高效的计算 2 * 8 ?
答案: 2 << 3 或 8 << 1
特别说明
1.位运算符操作的都是整型的数据
2.<<:在一定范围内,每向左移1位,相当于乘以2
>>:在一定范围内,每向右移1位,相当于除以2
代码
/**
* Filename : SanYuanTest.java
* Author : keke
* Creation time : 下午9:16:12 2021年10月30日
* Description : 运算符之六:三元运算符
* 1.结构:(条件表达式) ? 表达式1 : 表达式2
* 2.说明
* 1.条件表达式的结果为 boolean 类型
* 2.根据条件表达式真或假,决定执行表达式1,还是表达式2
* 如果表达式为 true,则执行表达式1
* 如果表达式为 false,则执行表达式2
* 3.表达式1和表达式2要求是一致的
* 4.三元运算符是可以嵌套的
* 3.凡是可以使用三元运算符的地方,都可以改写成 if-else
* 4.如果程序既可以用三元运算符,也可以用 if-else,那么优先选择三元运算符
* 原因:简洁、执行效率高
*/
public class SanYuanTest {
public static void main(String[] args) {
// 获取两个整数的较大值
int m = 12;
int n = 5;
int max = m > n ? m : n;
System.out.println(max);
double num = m > n ? 2 : 1.0;
// m > n ? 2 : "n大"; // 编译错误
// 不建议
String maxStr = m > n ? "m 大" : m == n ? "m 和 n 相等" : "n 大";
System.out.println(maxStr);
// 获取三个数的最大值
int n1 = 12;
int n2 = 30;
int n3 = -43;
max = n1 > n2 ? n1 > n3 ? n1 : n3 : n2;
System.out.println(max);
if (m > n) {
System.out.println(m);
}else {
System.out.println(n);
}
}
}特别说明
格式:
结构一
if(条件表达式){
执行表达式
}结构二
if(条件表达式){
执行表达式1
} else{
执行表达式2
}结构三
if(条件表达式){
执行表达式1
}else if(条件表达式){
执行表达式2
}
...
else{
执行表达式n
}说明:
格式:
switch(表达式){
case 常量1:
执行语句1;
// break;
case 常量2:
执行语句2;
// break;
...
default:
执行语句n;
// break;
}说明:
说明:通常情况下,循环结束都是因为循环条件返回 false 了
结构:
for(1;2;4){
3
}执行过程:1 --> 2 --> 3 --> 4 --> 2 --> 3 --> 4 --> … --> 2
结构:
1
while(2){
3;
4;
}执行过程:1 --> 2 --> 3 --> 4 --> 2 --> 3 --> 4 --> … --> 2
说明
for 和 while 循环总结:
结构:
1
do{
3
4;
}while(2);执行过程:1 -->3 --> 4 --> 2 --> 3 --> 4 --> 2 --> … --> 2
说明:
for(;;){
}
while(true){
}总结:如何结束一个循环结构?
2.说明
典型练习
package day05;
/**
* Filename : ForForTest.java
* Author : keke
* Creation time : 下午1:47:24 2021年11月1日
* Description :
* 嵌套循环的使用
* 1.嵌套循环:将一个循环结构 A 声明在另一个循环结构 B 的循环体中,就构成了嵌套循环
* 2.外层循环:循环结构 B
* 内层循环:循环结构 A
* 3.说明
* 1.内层循环结构遍历一遍,只相当于外层循环循环体执行了一次
* 2.假设外层循环需要执行 m 次,内层循环需要执行 n 次,此时内层循环的循环体一个执行了 m * n 次
* 3.外层循环控制行数,内层循环控制列数
*/
public class ForForTest {
public static void main(String[] args) {
for(int i = 1; i <= 4; i++) {
for(int j = 1; j <= 6; j++) {
System.out.print('*');
}
System.out.println();
}
for(int i = 0; i < 5; i++) {
for(int j = 0; j <= i; j++) {
System.out.print('*');
}
System.out.println();
}
for(int i = 0; i < 5; i++) {
for(int j = 1; j <= 5 - i; j++) {
System.out.print('*');
}
System.out.println();
}
System.out.println("==============================================");
for(int i = 0; i < 5; i++) {
for(int j = 0; j <= i; j++) {
System.out.print('*');
}
System.out.println();
}
for(int i = 0; i < 5; i++) {
for(int j = 2; j <= 5 - i; j++) {
System.out.print('*');
}
System.out.println();
}
}
}package day05;
/**
* Filename : NineNineTable.java
* Author : keke
* Creation time : 下午2:31:29 2021年11月1日
* Description :
* 九九乘法表
*/
public class NineNineTable {
public static void main(String[] args) {
for (int i = 1; i < 10; i++) {
for(int j = 1; j <= i; j++) {
System.out.print(j + " * " + i + " = " + i * j + "\t");
}
System.out.println();
}
}
}package day05;
/**
* Filename : PrimeNumberTest2.java
* Author : keke
* Creation time : 下午2:41:08 2021年11月1日
* Description :
* 100以内所有的质数
* 质数:只能被1和它本身整除的自然数,又叫素数 --> 从2开始,到这个数-1结束为止,都不能被这个数本身整除
* 最小的质数:2
*/
public class PrimeNumberTest2 {
public static void main(String[] args) {
// 获取当前时间的毫秒数
long start = System.currentTimeMillis();
label:
for(int i = 2; i <= 1000; i++) {
for(int j = 2; j <= Math.sqrt(i); j++) {
// i 被 j 除尽
if (i % j == 0) {
continue label;
}
}
// 能执行到此步骤的,都是质数
System.out.print(i + " ");
}
System.out.println();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}补充:衡量一个功能代码的优劣:
关键字 | 使用范围 | 循环中使用的作用 | 相同点 |
|---|---|---|---|
break | switch-case | ||
循环结构 | 结束当前循环 | 关键字后面不能声明执行语句 | |
continue | 循环结构 | 结束当次循环 | 关键字后面不能声明执行语句 |
带标签的 break 和 continue 关键字的使用
package day04;
import java.util.Scanner;
/**
* Filename : ScannerTest.java
* Author : keke
* Creation time : 下午3:23:13 2021年10月31日
* Description :
* 如何从键盘获取不同类型的变量:需要使用 Scanner 类
* 具体实现步骤:
* 1.导包:import java.util.Scanner;
* 2.Scanner 实例化:Scanner scanner = new Scanner(System.in);
* 3.调用 Scanner 类的相关方法(next() 和 nextXxx()),来获取指定类型的变量
* 注意:
* 需要根据相应的方法,来输入指定类型的值,如果深入到数据类型与要求的类型不匹配,则报异常 InputMismatchException
* 导致程序终止
*/
public class ScannerTest {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入你的姓名:");
String name = scanner.next();
System.out.println(name);
System.out.print("请输入你的年龄:");
int age = scanner.nextInt();
System.out.println(age);
System.out.print("请输入你的体重:");
double weight = scanner.nextDouble();
System.out.println(weight);
System.out.print("是否单身:");
boolean isLove = scanner.nextBoolean();
System.out.println(isLove);
// 对于 char 型的获取,Scanner 没有提供相关的方法。只能获取一个字符串
System.out.print("请输入你的性别:");
// 获取索引为0位置上的字符
char genderChar = scanner.next().charAt(0);
System.out.println(genderChar);
}
}数据结构:
算法:
// 1.一维数组的声明和初始化
int[] ids;
// 1.1 静态初始化:数组的初始化和数组元素的赋值操作同时进行
ids = new int[]{1001, 1002, 1003, 1004};
int dids = {1001, 1002, 1003, 1004}; // 类型推断
// 1.2 动态初始化:数组的初始化和数组元素的赋值操作分开进行
String[] names = new String[5];
// 错误的写法
// int[] arr1 = new int[];
// int[5] arr1 = new int[];
// int[] arr1 = new int[3]{1, 2, 3};通过角标的方式调用
// 数组的角标(或索引)从0开始,到数组的长度-1结束
names[0] = "王铭";
names[1] = "王赫";
names[2] = "张学良";
names[3] = "张居龙";
names[4] = "王宏志";length
System.out.println(names.length);
System.out.println(ids.length);注意:
for(int i = 0; i < names.length; i++){
System.out.print(names[i] + " ");
}
数组属于引用数据类型,数组的元素也可以是引用数据类型,一个一维数组的元素如果还是一个一维数组类型的,则此数组称为二维数组
// 静态初始化
int[][] arr1 = new int[][] {{1, 2, 3}, {4, 5}, {6, 7, 8}};
// 动态初始化
String[][] arr2 = new String[3][2];
String[][] arr3 = new String[3][];
// 错误的情况
// String[][] arr4 = new String[][3];
// 正确
int arr4[][] = new int[3][];
int[] arr5[] = new int[3][];
// 类型推断
int[] arr6[] = {{1, 2, 3}, {4, 5}, {6, 7, 8}};System.out.println(arr1[0][1]);
System.out.println(arr2[1][1]);
arr3[1] = new String[4];
System.out.println(arr3[1][0]);System.out.println(arr4.length);
System.out.println(arr4[1].length);for (int i = 0; i < arr6.length; i++) {
for (int j = 0; j < arr6[i].length; j++) {
System.out.print(arr6[i][j] + " ");
}
System.out.println();
}规定:二维数组分为外层数组的元素,内层数组的元素
针对于初始化方式一:比如:int[][] arr = new int[4][3];
外层元素的初始化值为:地址值内层元素的初始化值为:与一维数组初始化情况相同针对于初始化方式二:比如:int[][] arr = new int[4][];
外层元素的初始化值为:null内层元素的初始化值为:不能定义,否则报错
杨辉三角(二维数组)、回形数(二维数组)、6个数,1-30之间随机生成且不重复
最大值、最小值、总和、平均数等
int[] array1, array2;
array1 = new int[]{1, 2, 3, 4};array1 = array2;理解:将 array1 保存的数组的地址值赋给了 array2,使得 array1 和 array2 共同指向堆空间的同一个数组实体

array2 = new int[array1.length];
for (int i = 0; i < array2.length; i++) {
array2[i] = array1[i];
}理解:通过 new 的方式,给 array2 在堆空间中新开辟了数组的空间,将 array1 数组中的元素值一个一个的赋值到 array2 数组中

// 方式一:
for (int i = 0; i < arr1.length / 2; i++) {
String temp = arr1[i];
arr1[i] = arr1[arr1.length - i - 1];
arr1[arr1.length - i - 1] = temp;
}
// 方式二:
for(int i = 0, j = arr1.length - 1; i < j; i++, j--) {
String temp = arr1[i];
arr1[i] = arr1[j];
arr1[j] = temp;
}实现思路:通过遍历的方式,一个一个的数据进行比较、查找
适用性:具有普遍适用性
实现思路:每次比较中间值,折半的方式检索
适用性:(前提 :数组必须有序)
/**
* 折半查找法
* @param arr 有序的数组
* @param dest 要找的数字
*/
public void binarySearch(int[] arr, int dest) {
// 初始首索引
int head = 0;
// 初始末索引
int end = arr.length - 1;
boolean isFlag1 = true;
while (head <= end) {
int middle = (head + end) / 2;
if (dest == arr[middle]) {
System.out.println("找到了指定的元素,位置为:" + middle);
isFlag1 = false;
break;
}else if (arr[middle] > dest) {
end = middle - 1;
}else {
head = middle + 1;
}
}
if (isFlag1) {
System.err.println("很遗憾,没有找到");
}
}理解:
衡量排序算法的优劣
排序的分类
不同排序算法的时间复杂度
排序方法 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
|---|---|---|---|---|---|
插入排序 | O(n2) | O(n2) | O(n) | O(1) | 稳定 |
希尔排序 | O(n1.3) | O(n2) | O(n) | O(1) | 不稳定 |
选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 |
堆排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(1) | 不稳定 |
冒泡排序 | O(n2) | O(n2) | O(n) | O(1) | 稳定 |
快速排序 | O(nlog2n) | O(n2) | O(nlog2n) | O(nlog2n) | 不稳定 |
归并排序 | O(nlog2n) | O(nlog2n) | O(nlog2n) | O(n) | 稳定 |
计数排序 | O(n+k) | O(n+k) | O(n+k) | O(n+k) | 稳定 |
桶式排序 | O(n+k) | O(n2) | O(n) | O(n+k) | 稳定 |
基数排序 | O(n*k) | O(n*k) | O(n*k) | O(n+k) | 稳定 |
手写冒泡排序
public void bubbleSort(int[] arr){
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}java.util 包下package com.atguigu.java;
import java.util.Arrays;
/**
* Filename : ArraysTest.java
* Author : keke
* Creation time : 下午9:52:42 2021年11月2日
* Description :
* java.util.Arrays:操作数组的工具类,里面定义了很多操作数组的方法
*/
public class ArraysTest {
public static void main(String[] args) {
// 1.boolean equals(int[] a,int[] b):判断两个数组是否相等。
int[] arr1 = {1, 2, 3, 4};
int[] arr2 = {1, 3, 2, 4};
System.out.println(Arrays.equals(arr1, arr2));
// 2.String toString(int[] a):输出数组信息。
System.out.println(Arrays.toString(arr1));
// 3.void fill(int[] a,int val):将指定值填充到数组之中。
Arrays.fill(arr1, 10);
System.out.println(Arrays.toString(arr1));
// 4.void sort(int[] a):对数组进行排序。
Arrays.sort(arr2);
System.out.println(Arrays.toString(arr2));
// 5.int binarySearch(int[] a,int key):对排序后的数组进行二分法检索指定的值。
int[] arr3 = {-98, -34, 2, 34, 54, 66, 79, 105, 210, 333};
System.out.println(Arrays.binarySearch(arr3, 211));
}
}int[] arr = {1, 2, 3, 4, 5};
for (int i = 0; i <= arr.length; i++) {
System.out.println(arr[i]);
}
System.out.println(arr[-2]);// 情况一:
int[] arr1 = {1, 2, 3};
arr1 = null;
System.out.println(arr1[1]);
// 情况2:
int[][] arr2 = new int[4][];
System.out.println(arr2[0][0]);
// 情况3:
String[] arr3 = new String[] {null, "BB", "CC"};
System.out.println(arr3[0].toString());小知识:一旦程序出现异常,未处理时,就终止执行
“大处着眼,小处着手”
二者的关系:对象,是由类 new 出来的,派生出来的
补充:几个概念的使用说明
典型代码:
Person p1 = new Person();
Person p2 = new Person();
Person p3 = p1; // 没有新创建一个对象,共用一个堆空间的对象实体说明:
内存解析:


概念:创建的对象,没有显示地赋给一个变量名,即为匿名对象
特点:匿名对象只能调用一次
举例:
new Phone().sendEmail();
new Phone().playGame();
new Phone().price = 1999;
new Phone().showPrice();应用场景:
PhoneMall mall = new PhoneMall();
// 匿名对象的使用
mall.show(new Phone());
class PhoneMall{
public void show(Phone phone) {
phone.sendEmail();
phone.playGame();
}
}在 Java 语言范畴中,都将功能、结构等封装到类中,通过类的实例化,来调用类的具体功能结构
Scanner,String 等文件:File网络资源:URL涉及到 Java 语言与前端 Html、后端数据库交互时,前后端的结构在 Java 层面交互时,都体现为类、对象
编译完源程序以后,生成一个或多个字节码文件。使用 JVM 中的类的加载器和解释器对生成的字节码文件进行解释运行,意味着,需要将字节码文件对应的类加载到内存中,涉及到内存解析

类的设计中,两个重要的结构之一:属性
对比 属性 VS 局部变量
补充:回顾变量的分类


类的设计中,两个重要的结构之二:方法
方法的声明:
权限修饰符 返回值类型 方法名(形参列表){
方法体
}注意:static、final、abstract 来修饰的方法,后面再说
说明
方法的使用中,可以调用当前类的属性或方法
定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数 类型不同即可。
总结:“两同一不同”
同一个类,相同方法名参数列表不同:参数个数不同,参数类型不同Arrays 类中重载的 sort() binarySearch(),PrintStream 中的 println()
// 以下4个方法能构成重载
public void getSum(int i, int j) {
System.out.println(1);
}
public void getSum(double d1, double d2) {
System.out.println(2);
}
public void getSum(String s, int i) {
System.out.println(3);
}
public void getSum(int i, String s) {
System.out.println(4);
}
// 以下3个方法不能构成重载
public int getSum(int i, int j){
return 0;
}
public void getSum(int m, int n){
}
private void getSum(int i, int j){
}严格按照定义判断:两同一不同
跟方法的权限修饰符、返回值类型、形参变量名、方法体都没关系
方法名 --> 参数列表
面试题:方法的重载与重写的区别?
throws / throw
String / StringBuilder / StringBuffer
Collection / Collections
final / finally / finalize
sleep() / wait()
接口 / 抽象类
...public class MethodArgsTest {
public void show(int i) {
}
public void show(String s) {
System.out.println("MethodArgsTest.show(String s)");
}
public void show(String... strs) {
System.out.println("MethodArgsTest.show(String... strs)");
for (int i = 0; i < strs.length; i++) {
System.out.println(strs[i]);
}
}
// 不能与上一个方法同时存在
/*public void show(String[] strs) {
System.out.println("MethodArgsTest.show(String[] strs)");
}*/
public void show(int i, String... strs) {
}
}调用时:
public static void main(String[] args) {
MethodArgsTest test = new MethodArgsTest();
test.show(1);
test.show("Hello");
test.show("Hello", "World");
test.show();
test.show(new String[] {"Hello", "World", "!"});
}public class ValueTransferTest {
public static void main(String[] args) {
int m = 10;
int n = m;
System.out.println("m = " + m + ", n = " + n);
n = 20;
System.out.println("m = " + m + ", n = " + n);
Order o1 = new Order();
o1.orderId = 1001;
Order o2 = o1;
System.out.println("o1.orderId = " + o1.orderId + ", o2.orderId = " + o2.orderId);
// 赋值以后,o1 和 o2 的地址值相同,导致都指向堆空间的一个对象实体
o2.orderId = 1002;
System.out.println("o1.orderId = " + o1.orderId + ", o2.orderId = " + o2.orderId);
}
}
class Order{
int orderId;
}规则:
规则:
推广:
如果变量的引用数据类型,此时赋值的是变量所保存的数据的地址值

递归方法:一个方法体内调用它自身
public class RecursionTest {
// 例1:计算1-100之间所有自然数的和
public int getSum(int n) {
if (n == 1) {
return 1;
}
return n + getSum(n - 1);
}
// 例2:计算1-100之间所有自然数的乘积
public int getSum1(int n) {
if (n == 1) {
return 1;
}
return n * getSum1(n - 1);
}
/**
* 例3:已知有一个数列:f(0) = 1, f(1) = 4, f(n+2) = 2 * f(n+1) + f(n),
* 其中 n 是大于0的整数,求 f(10) 的值。
*/
public int f(int n) {
if (n == 0) {
return 1;
}else if (n == 1) {
return 4;
}
return 2 * f(n - 1) + f(n - 2);
}
// 例4:斐波那契数列
public int f1(int n) {
if (n == 1 || n == 2) {
return 1;
}
return f(n - 1) + f(n - 2);
}
}当创建一个类的对象后,可以通过“对象.属性”的方式,对对象的属性进行赋值,这里,赋值操作要受到属性的数据类型和存储范围的制约,除此之外,没有其它制约条件,但是,在实际问题中,往往需要给属性赋值,加入额外的限制条件,这个条件就不能在属性声明时体现,只能通过方法进行限制条件的添加,这时需要避免用户再使用“对象.属性”的方式对属性进行赋值,则需要将属性声明为私有的 (private) --> 此时,针对属性就体现了封装性
将类的属性私有化 (private),同时提供公共的 (public) 方法来获取 (getXxx) 和设置 (setXxx) 值
public class Circle{
private double radius;
public void setRadius(double radius){
this.radius = radius;
}
public double getRadius(){
return radius;
}
}不对外暴露的私有方法
单例模式(将构造器私有化)
如果不希望类在包外被调用,可以将类设置为缺省的
private < 缺省 < protected < public
修饰符 | 类内部 | 同包 | 不同包的子类 | 同一个过程 |
|---|---|---|---|---|
private | yes | |||
缺省 | yes | yes | ||
protected | yes | yes | yes | |
public | yes | yes | yes | yes |
具体的,4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
修饰类的话,只能使用:缺省 publicclass Person{
String name;
int age;
// 构造器
public Person() {
System.out.println("Person()..........");
}
public Person(String n) {
name = n;
}
public Person(String n, int a) {
name = n;
age = a;
}
public void eat() {
System.out.println("人吃饭");
}
public void study() {
System.out.println("人可以学习");
}
}总结:属性赋值的先后顺序
以上操作的先后顺序:1 --> 2 --> 3 --> 4
所谓 JavaBean,是指符合如下标准的 Java 类:
this 理解为:当前对象或当前正在创建的对象
举例一:

举例二:MVC 设计模式

import:导入

class A extends B{
}
java.lang.Object 类的理解java.lang.Objectjava.lang.Object 类之外)都直接或间接地继承于 java.lang.Objectjava.lang.Object 类声明的功能子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作
重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法
class Circle{
/**
* 求面积
*/
public double findArea(){}
}
class Cylinder extends Circle{
/**
* 求表面积
*/
public double findArea(){}
}
class Account{
public boolean withdraw(double amt){}
}
class CheckAccount extends Account{
public boolean withdraw(double amt){}
}方法的声明:
权限修饰符 返回值类型 方法名 (形参列表) throws 异常的类型 {
方法体
}约定俗称:子类中的叫重写方法,父类中的叫被重写的方法
子类和父类中的同名同参数的方法要么都声明为非 static 的(考虑重写), 要么都声明为非 static 的(不是重写)
如何区分方法的重写与重载?
父类的
子类继承父类以后,就获取了父中声明的属性和方法
创建子类的对象,在堆空间中,就会加载所有父类中声明的属性
当通过子类的构造器创建子类对象时,一定会直接和间接地调用其父类的构造器,进而调用父类的父类的构造器,直到调用了 java.lang.Object 类中的空参构造器为止,正因为加载过所有的父类的结构,所以才可以看到内存中有父类指定结构,子类对象才可以考虑进行调用

虽然创建子类对象时,调用了父类构造器,但是自始至终就创建过一个对象,即为 new 出来的子类对象

可以理解为一个事物的多种形态
对象的多态性:父类的引用指向子类的对象(或子类对象赋给父类引用)
举例:
Person p = new Man();
Object obj = new Date();有了对象的多态性以后,在编译期,只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类的方法总结:编译看左边,运行看右边
// 举例一:
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.func(new Dog());
test.func(new Cat());
}
public void func(Animal animal) {
animal.eat();
animal.shout();
}
}
class Animal{
public void eat() {
System.out.println("动物进食");
}
public void shout() {
System.out.println("动物叫");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
@Override
public void shout() {
System.out.println("汪汪汪");
}
}
class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
@Override
public void shout() {
System.out.println("喵喵喵");
}
}
//举例二:
class Order{
public void method(Object obj) {
}
}
// 举例三:
class Driver{
public void doData(Connection conn) {
// 规范的步骤去操作数据
}
}对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
多态
为什么要使用向下转型:有了对象多态性以后,内存中实际上是加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法,子类特有的属性和方法不能调用, 如何才能调用子类特有的属性和方法?使用向下转型
如何实现向下转型:使用强制类型转换符
注意点:
a instanceof A 判断 a 是否是类 A 的实例,如果是,返回 true,如果不是,返回 falsea instanceof A 返回 true,则 a instanceof B 返回 true,其中类 B 是类 A 的父类a instanceof A 要求 a 所属的类与类 A 必须是子类和父类的关系,否则编译错误
public boolean equals(Object obj){}
JDBC:使用 Java 程序操作(获取数据库连接、CRUD)数据库(MySQL、Oracle、DB2、SQL Server)
运行时行为
证明:
package com.atguigu.test;
import java.util.Random;
//面试题:多态是编译时行为还是运行时行为?
//证明如下:
class Animal {
protected void eat() {
System.out.println("animal eat food");
}
}
class Cat extends Animal {
protected void eat() {
System.out.println("cat eat fish");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("Dog eat bone");
}
}
class Sheep extends Animal {
public void eat() {
System.out.println("Sheep eat grass");
}
}
public class InterviewTest {
public static Animal getInstance(int key) {
switch (key) {
case 0:
return new Cat ();
case 1:
return new Dog ();
default:
return new Sheep ();
}
}
public static void main(String[] args) {
int key = new Random().nextInt(3);
System.out.println(key);
Animal animal = getInstance(key);
animal.eat();
}
}java.lang.Object 类的说明java.lang.Object
equals() toString() getClass() hashCode() clone() finalize() wait() notify() notifyAll()
equals() 方法是一个方法,而非运算符
只能适用于引用数据类型
Object 类中 equals() 的定义
public boolean equals(Object obj) {
return (this == obj);
}说明:Object 类中定义的 equals() 方法和 == 的作用是相同的,比较两个地址值是否相同,即两个引用是否指向同一个对象实体
像 String、Date、File、包装类等都重写了 Object 类中的 equals() 方法,重写以后,比较的表示两个引用的地址是否相同,而是比较两个对象的“实体内容”是否相同
通常情况下,自定义的类如果使用 equals() 的话,也通常比较两个对象的“实体内容”是否相同,那么,就需要对 Object 的 equals() 进行重写,重写的规则:比较两个对象的实体内容是否相同
equals()手动重写举例:
public class Customer {
String name;
int age;
/**
* 重写的规则:比较两个对象的实体内容是否相同
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Customer) {
Customer cust = (Customer) obj;
// 比较两个对象的属性是否都相同
if (this.age == cust.getAge() && this.name.equals(cust.getName())) {
return true;
}
return this.age == cust.getAge() && this.name.equals(cust.getName());
}
return false;
}
}开发中如何实现:自动生成的
public class Customer {
String name;
int age;
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Customer other = (Customer) obj;
return age == other.age && Objects.equals(name, other.name);
}
}== 运算符的使用==:运算符
补充: == 符号使用时,必须保证符号左右两边的变量类型一致
toString() 方法public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}toString()举例:
@Override
public String toString() {
return "Customer [name=" + name + ", age=" + age + "]";
}equals() 的区别import org.junit.jupiter.api.Test;
说明:
为了使基本数据类型的变量具有类的特征,引入包装类
基本数据类型 | 包装类 |
|---|---|
int | Integer |
byte | Byte |
short | Short |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
基本数据类型、包装类和 String 类型

注意:转换时,可能会报 NumberFormatException
Vector 类中关于添加元素,只定义了形参为 Object 类型的方法
v.addElement(Object obj); // 基本数据类型 --> 包装类 --> 使用多态static:静态的
主要用来修饰类的内部结构
静态变量(类变量)

静态方法、类方法
举例一:Arrays、Collections、Math 等工具类
举例二:单例模式
举例三:
class Circle{
private double radius;
/**
* 自动赋值
*/
private int id;
/**
* 记录创建的圆的个数
*/
private static int total;
/**
* static 声明的属性被所有对象共享
*/
private static int init = 1001;
public Circle() {
id = init++;
total++;
}
public Circle(double radius) {
this();
this.radius = radius;
}
public static int getTotal() {
return total;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public int getId() {
return id;
}
public double findArea() {
return Math.PI * radius * radius;
}
}设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、 以及解决问题的思考方式。
23种经典的设计模式 GoF
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例
饿汉式1:
class Bank{
// 1.私有化类的构造器
private Bank() {
}
// 2.内部创建类的对象
// 4.要求此对象也必须声明为静态的
private static Bank instance = new Bank();
// 3.提供公共的静态方法,返回类的对象
public static Bank getInstance() {
return instance;
}
}饿汉式2:
class Bank{
// 1.私有化类的构造器
private Bank() {
}
// 2.内部创建类的对象
// 4.要求此对象也必须声明为静态的
private static Bank instance;
static{
instance = new Bank();
}
// 3.提供公共的静态方法,返回类的对象
public static Bank getInstance() {
return instance;
}
}懒汉式:
class Order{
// 1.私有化类的构造器
private Order() {
}
// 2.声明当前类对象,没有实例化
// 4.此对象必须声明为 static 的
private static Order instance = null;
// 3.声明 public static 的返回当前类对象的方法
public static Order getInstance() {
if (instance == null) {
instance = new Order();
}
return instance;
}
}饿汉式
坏处:对象加载时间过长 好处:饿汉式是线程安全的 懒汉式
* 好处:延迟对象的创建
* 目前的写法坏处:线程不安全 --> 到多线程内容时,再修改main() 方法作为程序的入口
main() 方法是一个普通的静态方法
main() 方法可以作为我们与控制台交互的方式(之前,使用 Scanner)
如何将控制台获取的数据传给形参:String[] args?
运行时:java 类名 “Tom” “123” “true” “Jerry”
sysout(args[0]); // "Tom"
sysout(args[2]); // "true"
sysout(args[4]); // 报异常小结:
public static void main(String[] args){
// 方法体
}又叫初始化块
用来初始化类和对象的信息
代码块如果有修饰的话,只能使用 static
静态代码块 VS 非静态代码块
涉及到父类、子类中静态代码块、非静态代码块构造器的加载顺序:由父及子,静态先行
LeafTest.java
package cn.tedu.java3;
class Root{
static{
System.out.println("Root 的静态初始化块");
}
{
System.out.println("Root 的普通初始化块");
}
public Root(){
System.out.println("Root 的无参数的构造器");
}
}
class Mid extends Root{
static{
System.out.println("Mid 的静态初始化块");
}
{
System.out.println("Mid 的普通初始化块");
}
public Mid(){
System.out.println("Mid 的无参数的构造器");
}
public Mid(String msg){
// 通过 this 调用同一类中重载的构造器
this();
System.out.println("Mid 的带参数构造器,其参数值:" + msg);
}
}
class Leaf extends Mid{
static{
System.out.println("Leaf 的静态初始化块");
}
{
System.out.println("Leaf 的普通初始化块");
}
public Leaf(){
// 通过 super 调用父类中有一个字符串参数的构造器
super("尚硅谷");
System.out.println("Leaf 的构造器");
}
}
public class LeafTest{
public static void main(String[] args){
new Leaf();
new Leaf();
}
}Son.java
package cn.tedu.java3;
class Father {
static {
System.out.println("11111111111");
}
{
System.out.println("22222222222");
}
public Father() {
System.out.println("33333333333");
}
}
public class Son extends Father {
static {
System.out.println("44444444444");
}
{
System.out.println("55555555555");
}
public Son() {
System.out.println("66666666666");
}
public static void main(String[] args) { // 由父及子 静态先行
System.out.println("77777777777");
System.out.println("************************");
new Son();
System.out.println("************************");
new Son();
System.out.println("************************");
new Father();
}
}执行的先后顺序: 1 - 2 / 5 - 3 - 4
类、方法、变量
abstract:抽象的
类、方法
举例一
public abstract class Vehicle{
public abstract double calcFuelEfficiency(); // 计算燃料效率的抽象方法
public abstract double calcTripDistance(); // 计算行驶距离的抽象方法
}
public class Truck extends Vehicle{
public double calcFuelEfficiency(){
//写出计算卡车的燃料效率的具体方法
}
public double calcTripDistance(){
//写出计算卡车行驶距离的具体方法
}
}
public class RiverBarge extends Vehicle{
public double calcFuelEfficiency() {
//写出计算驳船的燃料效率的具体方法
}
public double calcTripDistance() {
//写出计算驳船行驶距离的具体方法
}
}举例二:
public class Circle extends GeometricObject{
private double radius;
@Override
public double findArea() {
return Math.PI * radius * radius;
}
}
abstract class GeometricObject {
public abstract double findArea();
}举例三
IO 流中涉及到的抽象类:InputStream / OutputStream / Reader / Writer,在其内部定义了 read() / write()
在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽 象出来,供不同子类实现。这就是一种模板模式。
package cn.tedu.java;
public class TemplateTest {
public static void main(String[] args) {
Template template = new SubTemplate();
template.spendTime();
}
}
abstract class Template{
/**
* 计算某段代码所花费的时间
*/
public void spendTime() {
long start = System.currentTimeMillis();
// 易变的部分
code();
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));
}
public abstract void code();
}
class SubTemplate extends Template{
@Override
public void code() {
for(int i = 2; i < 10000000; i++) {
boolean isFlag = true;
for(int j = 2; j <= Math.sqrt(i); j++) {
if (i % j == 0) {
isFlag = false;
break;
}
}
if (isFlag) {
System.out.println(i);
}
}
}
}class AA extends BB implements CC, DD, EE {}

package cn.tedu.java1;
public class USBTest {
public static void main(String[] args) {
Computer com = new Computer();
// 1.创建了接口的非匿名实现的非匿名对象
Flash flash = new Flash();
com.transforData(flash);
// 2.创建了接口的非匿名实现的匿名对象
com.transforData(new Printer());
// 3.创建了接口的匿名实现的非匿名对象
USB phone = new USB() {
@Override
public void stop() {
System.out.println("手机结束工作");
}
@Override
public void start() {
System.out.println("手机开始工作");
}
};
com.transforData(phone);
// 4.创建了接口的匿名实现的匿名对象
com.transforData(new USB() {
@Override
public void stop() {
System.out.println("mp3 结束工作");
}
@Override
public void start() {
System.out.println("mp3 开始工作");
}
});
}
}
class Computer{
public void transforData(USB usb) {
usb.start();
System.out.println("具体传输数据的细节");
usb.stop();
}
}
interface USB{
void start();
void stop();
}
class Flash implements USB{
@Override
public void start() {
System.out.println("U盘开启工作");
}
@Override
public void stop() {
System.out.println("U盘结束工作");
}
}
class Printer implements USB{
@Override
public void start() {
System.out.println("打印机开启工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}体会:

面向接口编程,在应用程序中,调用的结构都是 JDBC 中定义的接口,不会出现具体某一个数据库厂商的 API
接口中定义的静态方法,只能通过接口来调用
通过实现类对象,可以调用接口中的默认方法如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法,那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法 --> 类优先原则
如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错 --> 接口冲突,这就需要我们必须在实现类中重写此方法
如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
public void myMethod() {
method3(); // 调用自己定义的重写的方法
super.method3(); // 调用父类中声明的
// 调用接口中的默认方法
CompareA.super.method3();
CompareB.super.method3();
}代理模式是 Java 开发中使用较多的一种设计模式。代理设计就是为其 他对象提供一种代理以控制对这个对象的访问。
public class NetWorkTest {
public static void main(String[] args) {
Server server = new Server();
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.browse();
}
}
interface NetWork{
void browse();
}
// 被代理类
class Server implements NetWork{
@Override
public void browse() {
System.out.println("真实的服务器访问网络");
}
}
// 代理类
class ProxyServer implements NetWork{
private NetWork work;
public ProxyServer(NetWork work) {
this.work = work;
}
@Override
public void browse() {
check();
work.browse();
}
public void check() {
System.out.println("联网之前的检查工作");
}
}比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理 模式,当需要查看图片时,用 proxy 来进行大图片的打开。
实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
Java 中允许将一个类 A 声明在另一个类 B 中,则类 A 就是内部类,类 B 就是外部类
如何实例化成员内部类的对象
public static void main(String[] args) {
// 创建 Dog 实例(静态成员内部类)
Dog dog = new Person.Dog();
// 创建 Bird 实例(非静态成员内部类)
Person p = new Person();
Bird bird = p.new Bird();
}如何在成员内部类中区分调用外部类的结构
class Person{
String name = "小明";
public void eat(){
}
// 非静态成员内部类
class Bird{
String name = "杜鹃";
public void display(String name) {
System.out.println(name); // 方法形参
System.out.println(this.name); // 内部类属性
System.out.println(Person.this.name); // 外部类属性
// Person.this.eat();
}
}
}public class InnerClassTest1 {
// 开发中很少见
public void method() {
class AA{
}
}
/**
* 返回一个实现了 Comparable 接口的类对象
*/
public Comparable getComparable() {
// 创建一个实现了 Comparable 接口的类:局部内部类
/*class MyComparable implements Comparable{
@Override
public int compareTo(Object o) {
// TODO Auto-generated method stub
return 0;
}
}
return new MyComparable();*/
return new Comparable() {
@Override
public int compareTo(Object o) {
// TODO Auto-generated method stub
return 0;
}
};
}
}注意点:
在局部内部类的方法中,如果调用局部内部类所声明的方法中的局部变量话,要求此局部变量声明为 final 的
public class InnerClassTest {
public void method() {
// 局部变量
int num = 10;
class AA{
public void show() {
// num = 10;
System.out.println(num);
}
}
}
}成员内部类和局部内部类,在编译以后,都会生成字节码文件
格式
操作 | 作用 |
|---|---|
step into 跳入(F5) | 进入当前行所调用的方法中 |
step over 跳过(F6) | 执行完当前行的语句,进入下一行 |
step return 跳回(F7) | 执行完当前行所在的的方法,进入下一行 |
drop to frame | 回到当前行所在方法的第一行 |
resume 恢复 | 执行完当前行所在断点的所有代码,进入下一个断点,如果没有就结束 |
Terminate 停止 | 停止 JVM,后面程序不会再执行 |
java.lang.Throwable
|--- java.lang.Error:一般不编写针对性的代码进行处理
|--- java.lang.Exception:可以进行异常的处理
|--- 编译时异常(checked)
|--- IOException
|--- FileNotFoundException
|--- ClassNotFoundException
|--- 运行时异常(unchecked)
|--- NullPointerException
|--- ArrayIndexOutOfBoundsException
|--- ClassCastException
|--- NumberFormatException
|--- InputMismatchException
|--- ArithmeticException

编译时异常:执行 javac.exe 命令时,可能出现的异常
运行时异常:执行 java.exe 命令时,出现的异常
public class ExceptionTest {
// NullPointerException
@Test
public void test1() {
int[] arr = null;
System.out.println(arr);
String str = null;
System.out.println(str.charAt(0));
}
// IndexOutOfBoundsException
@Test
public void test2() {
// ArrayIndexOutOfBoundsException
int[] a = new int[10];
System.out.println(a[10]);
// StringIndexOutOfBoundsException
String str = "abc";
System.out.println(str.charAt(3));
}
// ClassCastException
@Test
public void test3() {
Object obj = new Date();
String str = (String) obj;
}
// NumberFormatException
@Test
public void test4() {
String str = "abc";
int parseInt = Integer.parseInt(str);
}
// InputMismatchException
@Test
public void test5() {
int score = new Scanner(System.in).nextInt();
System.out.println(score);
}
// ArithmeticException
@Test
public void test6(){
int a = 1 / 0;
}
//
// @Test
// public void test7() {
// File file = new File("hello.txt");
// FileInputStream fileInputStream = new FileInputStream(file);
// int read = fileInputStream.read();
// while (read != -1) {
// System.out.print((char)read);
// read = fileInputStream.read();
// }
// fileInputStream.close();
// }
}try{
// 可能出现异常的代码
} catch(异常类型1 变量名1){
// 处理异常的方式1
} catch(异常类型2 变量名2){
// 处理异常的方式2
} catch(异常类型3 变量名3){
// 处理异常的方式3
}
...
finally{
// 一定会执行的代码
}总结:如何看待代码中的编译时异常和运行时异常?
类似:
结构不相似:
"throws + 异常类型"写在方法声明出,指明此方法执行时,可能会抛出的异常类型,一旦当方法体执行时,出现异常,仍然会在异常代码处生成一个异常类的对象。此对象满足 throws 后异常类型时,就会被抛出,异常代码后续的代码,就不再执行
补充:
方法重写的规则之一:子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
在程序执行中,除了自动抛出异常对象的情况之外,还可以手动 throw 一个异常类的对象
class Student{
private int id;
public void regist(int id) {
if (id > 0) {
this.id = id;
}else {
// System.out.println("您输入的数据非法");
// 手动抛出异常对象
// throw new RuntimeException("输入的数据非法");
// throw new Exception("输入的数据非法");
throw new MyException("不能输入负数");
}
}
@Override
public String toString() {
return "Student [id=" + id + "]";
}
}public class MyException extends RuntimeException{
static final long serialVersionUID = 12345678921234L;
public MyException() {
}
public MyException(String msg) {
super(msg);
}
}是为完成特定任务、用某种语言编写的一组指令的集合。即指一 段静态的代码。
是程序的一次执行过程,或是正在运行的一个程序。
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
进程可进一步细化为线程,是一个程序内部的一条执行路径。
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小

内存结构:

进程可以细化为多个线程
每个线程拥有自己独立的:栈、程序计数器
多个线程共享同一个进程中的结构:方法区、堆
说明两个问题:
public class Tread implements Runnable线程的通信:wait()、notify()、 notifyAll():此三个方法定义在 Object 类中的

说明:
创建三个窗口买票,总票数为100张,使用实现 Runnable 接口的方式
在 Java 中,通过同步机制,来解决线程的安全问题
方式一:同步代码块
synchronized(同步监视器){
// 需要被同步的代码
}方式二:同步方法
方式三:Lock 锁 — JDK 5.0新增
优先使用顺序:Lock –> 同步代码块(已经进入了方法体,分配了相应资源) –> 同步方法(在方法体之外)
class Bank{
private static Bank instance = null;
private Bank(){
}
public static Bank getInstance() {
// 方式一:效率稍差
/*synchronized (Bank.class) {
if (instance == null){
instance = new Bank();
}
return instance;
}*/
// 方式二:效率更高
if (instance == null){
synchronized (Bank.class) {
if (instance == null){
instance = new Bank();
}
}
}
return instance;
}
}面试题:写一个线程安全的单例模式
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println("s1 = " + s1);
System.out.println("s2 = " + s2);
}
}
}
}.start();
new Thread(() -> {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println("s1 = " + s1);
System.out.println("s2 = " + s2);
}
}
}).start();
}
}java.lang.Object 类中sleep() 和 wait() 的异同
创建线程的方式三:实现 Callable 接口 — JDK 5.0新增
步骤
public class ThreadNew {
public static void main(String[] args) {
NumTread numTread = new NumTread();
FutureTask future = new FutureTask(numTread);
new Thread(future).start();
try {
// get() 返回值即为 FutureTask 构造器形参 Callable 实现类重写的 call() 的返回值
Object sum = future.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class NumTread implements Callable{
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}说明:
创建线程的方式四:使用线程池
步骤
public class ThreadPool {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor executor = (ThreadPoolExecutor) service;
// 设置线程池属性
// System.out.println(service.getClass());
// executor.setCorePoolSize(15);
// executor.setKeepAliveTime();
// 适合使用于 Runnable
service.execute(new NumberThread());
service.execute(new NumberThread1());
// service.submit(); 适合使用于 Callable
service.shutdown();
}
}
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}好处
面试题:Java 中多线程的创建有几种方式?四种
String:字符串,使用一对""引起来表示
Serializable 接口:表示字符串是支持序列化的
String 实现了 Comparable 接口:表示 String 可以比较大小
final char[] value 用于存储字符串数据
@Test
public void test1(){
// 字面量的定义方式
String s1 = "abc";
String s2 = "abc";
s1 = "hello";
// 比较 s1 和 s2 的地址值
System.out.println("s1 == s2 = " + (s1 == s2));
System.out.println("s1 = " + s1);
System.out.println("s2 = " + s2);
String s3 = "abc";
s3 += "def";
System.out.println("s3 = " + s3);
System.out.println("s2 = " + s2);
String s4 = "abc";
String s5 = s4.replace('a', 'm');
System.out.println("s4 = " + s4);
System.out.println("s5 = " + s5);
}
@Test
public void test2(){
// 声明在方法区中的字符串常量池中
String s1 = "JavaEE";
String s2 = "JavaEE";
// 保存的地址值是数据在堆空间中开辟以后对应的地址值
String s3 = new String("JavaEE");
String s4 = new String("JavaEE");
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s1 == s4); // false
System.out.println(s3 == s4); // false
}
@Test
public void test3(){
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4); // true
System.out.println(s3 == s5); // false
System.out.println(s3 == s6); // false
System.out.println(s3 == s7); // false
System.out.println(s5 == s6); // false
System.out.println(s5 == s7); // false
System.out.println(s6 == s7); // false
// 返回值得到的 s8 使用的常量值中已经存在的"javaEEhadoop"
String s8 = s5.intern();
System.out.println(s8 == s4); // true
}@Test
public void test4(){
String s1 = "javaEEhadoop";
String s2 = "javaEE";
String s3 = s2 + "hadoop";
System.out.println(s1 == s3);
final String s4 = "javaEE";
String s5 = s4 + "hadoop";
System.out.println(s1 == s5);
}int length():返回字符串的长度: return value.length
char charAt(int index): 返回某索引处的字符 return value[index]
boolean isEmpty():判断是否是空字符串:return value.length == 0
String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
String trim():返回字符串的副本,忽略前导空白和尾部空白
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与 equals 方法类似,忽略大小写
String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
int compareTo(String anotherString):比较两个字符串的大小
String substring(int beginIndex): 返回一个新的字符串, 它是此字符串的从 beginIndex 开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从 beginIndex 开始截取到 endIndex (不包含)的一个子字符串。
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
indexOf 和 lastIndexOf 方法如果未找到都是返回-1
String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
String replace(CharSequence target, CharSequence replacement):使 用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
String replaceAll(String regex, String replacement) :使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement) :使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过 limit 个,如果超过了,剩下的全部都放到最后一个元素中。
@Test
public void test1(){
String str1 = "123";
int num = Integer.parseInt(str1);
String str2 = String.valueOf(num);
String str3 = num + "";
System.out.println(str1 == str3);
}@Test
public void test2(){
String str1 = "abc123";
char[] charArray = str1.toCharArray();
for (int i = 0; i < charArray.length; i++) {
System.out.println(charArray[i]);
}
char[] arr = new char[]{'h', 'e', 'l', 'l', 'o'};
String str2 = new String(arr);
System.out.println(str2);
}编码:字符串 --> 字节
解码:编码的逆过程 字节 --> 字符串
说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码
@Test
public void test3() throws UnsupportedEncodingException {
String str1 = "abc123中国";
// 使用默认的字符集进行转换
byte[] bytes = str1.getBytes();
System.out.println(Arrays.toString(bytes));
// 使用 gbk 进行编码
byte[] gbks = str1.getBytes("gbk");
System.out.println(Arrays.toString(gbks));
String str2 = new String(bytes);
System.out.println(str2);
String str3 = new String(gbks, "gbk");
System.out.println(str3);
}public String myTrim(String str) {
if (str != null) {
// 用于记录从前往后首次索引位置不是空格的位置的索引
int start = 0;
// 用于记录从后往前首次索引位置不是空格的位置的索引
int end = str.length() - 1;
while (start < end && str.charAt(start) == ' ') {
start++;
}
while (start < end && str.charAt(end) == ' ') {
end--;
}
if (str.charAt(start) == ' ') {
return "";
}
return str.substring(start, end + 1);
}
return null;
}/**
* 方式一:转换为 char[]
* @param str
* @param startIndex
* @param endIndex
* @return
*/
public String reverseChar(String str, int startIndex, int endIndex){
if (str != null) {
char[] arr = str.toCharArray();
for(int x = startIndex, y = endIndex; x < y; x++, y--){
char temp = arr[x];
arr[x] = arr[y];
arr[y] = temp;
}
return new String(arr);
}
return null;
}
/**
* 方式二:使用 String 的拼接
* @param str
* @param startIndex
* @param endIndex
* @return
*/
public String reverseString(String str, int startIndex, int endIndex){
if (str != null) {
String reverseStr = str.substring(0, startIndex);
for(int i = endIndex; i >= startIndex; i--){
reverseStr += str.charAt(i);
}
reverseStr += str.substring(endIndex + 1);
return reverseStr;
}
return null;
}
/**
* 方式三:使用 StringBuffer / StringBuilder 替换 String
* @param str
* @param startIndex
* @param endIndex
* @return
*/
public String reverseStringBuilder(String str, int startIndex, int endIndex){
if (str != null) {
StringBuilder builder = new StringBuilder(str.length());
builder.append(str.substring(0, startIndex));
for(int i = endIndex; i >= startIndex; i--){
builder.append(str.charAt(i));
}
builder.append(str.substring(endIndex + 1));
}
return null;
}/**
* 获取 subStr 在 mainStr 中出现的次数
* @param mainStr
* @param subStr
* @return
*/
public int getCount(String mainStr, String subStr){
int mainLength = mainStr.length();
int subLength = subStr.length();
int count = 0;
int index = 0;
if (mainLength >= subLength){
// 方式一
/*while ((index = mainStr.indexOf(subStr)) != -1){
count++;
mainStr = mainStr.substring(index + subStr.length());
}*/
// 方式二
while ((index = mainStr.indexOf(subStr, index)) != -1){
count++;
index += subLength;
}
}
return count;
}/**
* 如果存在多个长度相同的最大相同子串
* 此时先返回String[],后面可以用集合中的ArrayList替换,较方便
* @param str1
* @param str2
* @return
*/
public String[] getMaxSameSubString(String str1, String str2) {
if (str1 != null && str2 != null) {
StringBuffer sBuffer = new StringBuffer();
String maxString = (str1.length() > str2.length()) ? str1 : str2;
String minString = (str1.length() > str2.length()) ? str2 : str1;
int len = minString.length();
for (int i = 0; i < len; i++) {
for (int x = 0, y = len - i; y <= len; x++, y++) {
String subString = minString.substring(x, y);
if (maxString.contains(subString)) {
sBuffer.append(subString + ",");
}
}
// System.out.println(sBuffer);
if (sBuffer.length() != 0) {
break;
}
}
String[] split = sBuffer.toString().replaceAll(",$", "").split("\\,");
return split;
}
return null;
}以 StringBuffer 为例
String str = new String(); // char[] value = new char[0];
String str = new String("abc"); // char[] value = new char[]{'a', 'b', 'c'};
StringBuffer sb1 = new StringBuffer(); // char[] value = new char[16];底层创建了一个长度是16的数组
sb1.append('a') // value[0] = 'a';
sb1.append('b') // value[1] = 'b';
StringBuffer sb2 = new StringBuffer("abc"); // char[] value = new char["abc".length() + 16];StringBuffer(int capacity) 或 StringBuilder(int capacity)
StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接StringBuffer delete(int start,int end):删除指定位置的内容StringBuffer replace(int start, int end, String str):把[start,end)位置替换为strStringBuffer insert(int offset, xxx):在指定位置插入 xxxStringBuffer reverse():把当前字符序列逆转int indexOf(String str)String substring(int start,int end):返回一个 从 start 开始到 end 索引结束的左闭右开区间的子字符串int length()char charAt(int n)void setCharAt(int n ,char ch)总结:
append(xxx)delete(int start, int end)setCharAt(int n ,char ch) / replace(int start, int end, String str)charAt(int n)insert(int offset, xxx)length()for + charAt() / toString()System 类中的 currentTimeMillis():返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差,称为时间戳

@Test
public void test2(){
// 构造器一:Date():创建一个对应当前时间的 Date 对象
Date date1 = new Date();
System.out.println(date1);
System.out.println(date1.getTime());
// 构造器二:Date(long date):创建指定毫秒数的 Date 对象
Date date2 = new Date(1637809383273L);
System.out.println(date2);
java.sql.Date date3 = new java.sql.Date(99999323820232L);
System.out.println(date3);
Date date4 = new Date(221223445L);
// java.sql.Date date5 = (java.sql.Date) date4;
java.sql.Date date5 = new java.sql.Date(date3.getTime());
}SimpleDateFormat 对日期 Date 类的格式化和解析
// 按照指定的方式格式化和解析:调用带参数的构造器
// SimpleDateFormat sdf1 = new SimpleDateFormat("yyyyy.MMMMM.dd GGG hh:mm aaa");
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
// 格式化
System.out.println(sdf1.format(date));
// 解析:要求字符串必须符合 SimpleDateFormat 识别的格式(通过构造器参数体现),否则抛异常
System.out.println(sdf1.parse(sdf1.format(date)));练习:
/**
* 练习一:字符串“2020-09-08”转换为 java.sql.Date
* 练习二:三天打鱼两天晒网 1990-01-01 xxxx-xx-xx 打鱼 晒网
* 总天数 % 5 == 1, 2, 3 : 打鱼
* 总天数 % 5 == 4, 0 : 晒网
* 总天数的计算
* 方式一:(date2.getTime() - date1.getTime()) / (1000 * 60 * 60 * 24)
* 方式二:1990-01-01 --> 2019-12-31 + 2020-01-01 --> 2020-09-08
*/
@Test
public void testExer() throws ParseException {
String birth = "2020-09-08";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date date = simpleDateFormat.parse(birth);
java.sql.Date birthDate = new java.sql.Date(date.getTime());
System.out.println(birthDate);
}@Test
public void testCalendar(){
// 1.实例化
// 方式一:创建其子类(GregorianCalendar)的对象
// 方式二:调用其静态方法 getInstance()
Calendar calendar = Calendar.getInstance();
// System.out.println(calendar.getClass());
// 2.常用方法
// get()
int days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);
System.out.println(calendar.get(Calendar.DAY_OF_YEAR));
// set()
// Calender 可变性
calendar.set(Calendar.DAY_OF_MONTH, 22);
System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
// add()
calendar.add(Calendar.DAY_OF_MONTH, -3);
System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
// getTime():日历类 --> Date
Date date = calendar.getTime();
System.out.println(date);
// setTime():Date --> 日历类
Date date1 = new Date();
calendar.setTime(date1);
System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
}第一代:JDK1.0 Date 类
第二代:JDK1.1 Calendar 类,一定程度上替代了 Date 类
第三代:JDK1.8 提出了一套新的 API
说明:大多数开发者只会用到基础包和format包,也可能会用到temporal包。因此,尽管有68个新的公开类型,大多数开发者,大概将只会用到其中的三分之一。
方法 | 描述 |
|---|---|
now() / * now(ZoneId zone) | 静态方法,根据当前时间创建对象/指定时区的对象 |
of() | 静态方法,根据指定日期/时间创建对象 |
getDayOfMonth() / getDayOfYear() | 获得月份天数(1-31) /获得年份天数(1-366) |
getDayOfWeek() | 获得星期几(返回一个 DayOfWeek 枚举值) |
getMonth() | 获得月份, 返回一个 Month 枚举值 |
getMonthValue() / getYear() | 获得月份(1-12) /获得年份 |
getHour() / getMinute() / getSecond() | 获得当前对象对应的小时、分钟、秒 |
withDayOfMonth() / withDayOfYear() / withMonth() / withYear() | 将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象 |
plusDays() / plusWeeks() / plusMonth() / plusYears() / plusHours() | 向当前对象添加几天、几周、几个月、几年、几小时 |
minusMonths() / minusWeeks() / minusDays() / minusYears() / minusHours() | 从当前对象减去几月、几周、几天、几年、几小时 |
方法 | 描述 |
|---|---|
now() | 静态方法,返回默认UTC时区的Instant类的对象 |
ofEpochMilli(long epochMilli) | 静态方法,返回在1970-01-01 00:00:00基础上加上指定毫秒 数之后的Instant类的对象 |
atOfSet(ZoneOfSet ofSet) | 结合即时的偏移来创建一个 OffsetDateTime |
toEpochMilli() | 返回1970-01-01 00:00:00到当前时间的毫秒数,即为时间戳 |
时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。
特别的:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
// 方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(formatter3.format(localDateTime));
TemporalAccessor accessor = formatter3.parse("2021-11-25 21:53:44");
System.out.println(accessor);/**
* ZoneId:类中包含了所有时区信息
*/
@Test
public void test4(){
// getAvailableZoneIds():获取对应的 ZoneId
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
for (String zoneId : zoneIds) {
System.out.println(zoneId);
}
System.out.println();
// 获取"Asia/Tokyo"时区对应的时间
System.out.println(LocalDateTime.now(ZoneId.of("Asia/Tokyo")));
}
/**
* ZonedDateTime:带时区的日期时间
*/
@Test
public void test5(){
// now():获取本时区的 ZonedDateTime 对象
System.out.println(ZonedDateTime.now());
// now(ZoneId id):获取指定时区的 ZonedDateTime 对象
ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
}用于计算两个“时间”间隔,以秒和纳秒为基准
方法 | 描述 |
|---|---|
between(Temporal start, Temporal end) | 静态方法,返回 Duration 对象,表示两个时间的间隔 |
getNano() / getSeconds() | 返回时间间隔的纳秒数 / 返回时间间隔的秒数 |
toDays() / toHours() / toMinutes() / toMillis() / toNano() | 返回时间间隔的天数、小时数、分钟数、毫秒数、纳秒数 |
@Test
public void test6(){
//Duration:用于计算两个“时间”间隔,以秒和纳秒为基准
LocalTime localTime = LocalTime.now();
LocalTime localTime1 = LocalTime.of(15, 23, 32);
//between():静态方法,返回Duration对象,表示两个时间的间隔
Duration duration = Duration.between(localTime1, localTime);
System.out.println(duration);
System.out.println(duration.getSeconds());
System.out.println(duration.getNano());
LocalDateTime localDateTime = LocalDateTime.of(2016, 6, 12, 15, 23, 32);
LocalDateTime localDateTime1 = LocalDateTime.of(2017, 6, 12, 15, 23, 32);
Duration duration1 = Duration.between(localDateTime1, localDateTime);
System.out.println(duration1.toDays());
}用于计算两个“日期”间隔,以年、月、日衡量
方法 | 描述 |
|---|---|
between(LocalDate start, LocalDate end) | 静态方法,返回 Period 对象,表示两个本地日期的间隔 |
getYears() / getMonths() / getDays() | 返回此期间的年数,月数、天数 |
withYears(int years) / withMonths(int months) / withDays(int days) | 返回设置间隔指定年、月、日数以后的 Period 对象 |
@Test
public void test7(){
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = LocalDate.of(2028, 3, 18);
Period period = Period.between(localDate, localDate1);
System.out.println(period);
System.out.println(period.getYears());
System.out.println(period.getMonths());
System.out.println(period.getDays());
Period period1 = period.withYears(2);
System.out.println(period1);
}@Test
public void test8(){
// 获取当前日期的下一个周日是哪天?
TemporalAdjuster temporalAdjuster = TemporalAdjusters.next(DayOfWeek.SUNDAY);
LocalDateTime localDateTime = LocalDateTime.now().with(temporalAdjuster);
System.out.println(localDateTime);
// 获取下一个工作日是哪天?
LocalDate localDate = LocalDate.now().with(new TemporalAdjuster() {
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate date = (LocalDate) temporal;
if (date.getDayOfWeek().equals(DayOfWeek.FRIDAY)) {
return date.plusDays(3);
} else if (date.getDayOfWeek().equals(DayOfWeek.SATURDAY)) {
return date.plusDays(2);
} else {
return date.plusDays(1);
}
}
});
System.out.println("下一个工作日是:" + localDate);
}Java 中的对象,正常情况下,只能进行比较:== 或 != ,不能使用 > 或 < 的,但是在开发场景中,需要对多个对象进行排序,言外之意,就需要比较对象的大小,如何实现?使用两个接口中的任何一个:Comparable 或 Comparator
public class Goods implements Comparable{
private String name;
private double price;
public Goods() {
}
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
/**
* 指明商品比较大小的方式:按照价格从低到高排序,再按照产品名称从低到高排序
* @param o
* @return
*/
@Override
public int compareTo(Object o) {
if (o instanceof Goods){
Goods goods = (Goods) o;
// 方式一
if (this.price > goods.price){
return 1;
}else if (this.price < goods.price){
return -1;
}else {
return this.name.compareTo(goods.name);
}
// 方式二
// return Double.compare(this.price, goods.price);
}
throw new RuntimeException("传入的数据类型不一致");
}
}Comparator comparator = new Comparator() {
/**
* 按照字符串从大到小排序
* @param o1
* @param o2
* @return
*/
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof String && o2 instanceof String){
String s1 = (String) o1;
String s2 = (String) o2;
return -s1.compareTo(s2);
}
throw new RuntimeException("输入的数据类型不一致");
}
};native long currentTimeMillis():该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。
void exit(int status): 该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表 异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。
void gc():该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则 取决于系统中垃圾回收算法的实现以及系统执行时的情况。
String getProperty(String key):该方法的作用是获得系统中属性名为key的属性对应的值。系统中常见的属性名以及属性的作用如下表所示:
属性名属性说明java.versionJava 运行时环境版本java.homeJava安装目录os.name操作系统的名称os.version操作系统的版本user.name用户的账户名称user.home用户的主目录user.dir用户的当前工作目录java.lang.Math 提供了一系列静态方法用于科学计算。其方法的参数和返回值类型一般为 double 型。
说明:
代码举例:
@Test
public void testBigInteger() {
BigInteger bi = new BigInteger("12433241123");
BigDecimal bd = new BigDecimal("12435.351");
BigDecimal bd2 = new BigDecimal("11");
System.out.println(bi);
// System.out.println(bd.divide(bd2));
System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP));
System.out.println(bd.divide(bd2, 25, BigDecimal.ROUND_HALF_UP));
}JDK5.0之前,自定义枚举类
// 自定义枚举类
class Season{
// 1.声明 Season 对象的属性,private final 修饰
private final String seasonName;
private final String seasonDesc;
// 2.私有化类的构造器
private Season(String seasonName, String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
// 3.提供当前枚举类的多个对象:public static final 的
public static final Season SPRING = new Season("春天", "春暖花开");
public static final Season SUMMER = new Season("夏天", "夏日炎炎");
public static final Season AUTUMN = new Season("秋天", "秋高气爽");
public static final Season WINTER = new Season("冬天", "冰天雪地");
// 4.其它诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
// 4.其它诉求2:提供 toString()
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}// 使用 enum 关键字定义枚举类
enum Season1{
// 1.提供当前枚举类的对象,多个对象之间用逗号隔开
SPRING("春天", "春暖花开").
SUMMER("夏天", "夏日炎炎"),
AUTUMN("秋天", "秋高气爽"),
WINTER("冬天", "冰天雪地");
// 2.声明 Season 对象的属性,private final 修饰
private final String seasonName;
private final String seasonDesc;
// 3.私有化类的构造器
Season1(String seasonName, String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
// 4.其它诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}继承于 java.lang.Enum 类
values():返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。toString():返回当前枚举类对象常量的名称public static void main(String[] args) {
Season1 summer = Season1.SUMMER;
System.out.println(summer);
System.out.println(Season1.class.getSuperclass());
// values()
Season1[] values = Season1.values();
for (Season1 value : values) {
System.out.println(value);
}
for (Thread.State value : Thread.State.values()) {
System.out.println(value);
}
// valueOf(String objName):返回枚举类中对象名是 objName 的对象
// 如果没有 objName 的枚举类对象,则抛异常 IllegalArgumentsException
Season1 winter = Season1.valueOf("WINTER");
System.out.println(winter);
}interface Info{
void show();
}
// 使用 enum 关键字定义枚举类
enum Season1 implements Info{
// 1.提供当前枚举类的对象,多个对象之间用逗号隔开
SPRING("春天", "春暖花开"){
@Override
public void show() {
System.out.println("春天在哪里");
}
},
SUMMER("夏天", "夏日炎炎") {
@Override
public void show() {
System.out.println("宁夏");
}
},
AUTUMN("秋天", "秋高气爽") {
@Override
public void show() {
System.out.println("秋天不回来");
}
},
WINTER("冬天", "冰天雪地") {
@Override
public void show() {
System.out.println("大约在冬季");
}
};
// 2.声明 Season 对象的属性,private final 修饰
private final String seasonName;
private final String seasonDesc;
// 3.私有化类的构造器
Season1(String seasonName, String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
// 4.其它诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}框架 = 注解 + 反射机制 + 设计模式
参照 @SuppressWarings 定义
@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.TYPE,
ElementType.FIELD,
ElementType.METHOD,
ElementType.LOCAL_VARIABLE,
ElementType.TYPE_PARAMETER,
ElementType.TYPE_USE
})
public @interface MyAnnotation {
String value() default "hello";
}说明:
元注解:对现有的注解进行解释说明的注解
通过反射获取注解信息
前提:要求此注解的元注解 Retention 中声明的生命周期状态为:RUNTIME
集合、数组都是对多个数据进行存储操作的结构,简称 Java 容器 说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt, .jpg, .avi,数据库中)
|---- Collection 接口:单列集合,用来存储一个一个的数据
|---- List 接口:存储有序的、可重复的数据 --> “动态"数组
|---- ArrayList
|---- LinkedList
|---- Vector
|---- Set 接口:存储无序的、不可重复的数据 --> 高中讲的"集合“
|---- HashSet
|---- LinkedHashSet
|---- TreeSet
|---- Map 接口:双列集合,用来存储一对(key : value)一对的数据 --> 高中函数: y = f(x)
|---- HashMap
|---- LinkedHashMap
|---- TreeMap
|---- HashTable
|---- Properties|---- Collection 接口:单列集合,用来存储一个一个的数据
|---- List 接口:存储有序的、可重复的数据 --> “动态"数组
|---- ArrayList
|---- LinkedList
|---- Vector
|---- Set 接口:存储无序的、不可重复的数据 --> 高中讲的"集合“
|---- HashSet
|---- LinkedHashSet
|---- TreeSet对应图示:

add(Object obj):将元素 obj 添加到元素集合中addAll(Collection collection):将 collection 集合中的元素添加到当前的集合中size():获取添加的元素的个数isEmpty():判断当前集合是否为空clear():清空集合元素contains(Object obj):判断当前集合中是否包含 obj,在判断时会调用 obj 对象所在类的 equals()containsAll(Collection collection):判断形参 collection 中的所有元素是否都存在于当前集合中remove(Object obj):从当前集合中移除 obj 元素removeAll(Collection collection):差集,从当前集合中移除 collection 中所有的元素retainsAll(Collection collection):交集,获取当前集合和 collection 集合的交集,并返回给当前集合equals(Object obj):要想返回 true,就要判断当前集合和形参集合元素都相同hashCode():返回当前对象的哈希值toArray():集合转换为数组iterator()返回此集合中的元素的迭代器// 8.toArray():集合转换为数组
Object[] arr = collection.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
// 扩展:数组转换为集合:调用 Arrays 类的静态方法 asList()
List<String> list = Arrays.asList(new String[] {"aa", "bb", "cc"});
System.out.println(list);java.utils 包下定义的迭代器接口:Iterator遍历集合 Collection 元素
collection.iterator() 返回一个迭代器实例
Iterator iterator = collection.iterator();
// hasNext():判断是否会有下一个元素
while (iterator.hasNext()){
// next():指针下移;将下移以后集合位置上的元素返回
System.out.println(iterator.next());
}
// 如果还未调用 next() 或在上一次调用 next 方法之后已经调用了 remove 方法,
// 再调用 remove 都会报 IllegalStateException。
// 内部定义了 remove(),可以在遍历的时候,删除集合中的元素,此方法不同于集合直接调用 remove()
@Test
public void test3() {
Collection collection = new ArrayList();
collection.add(123);
collection.add(456);
collection.add(new Person("Jerry", 20));
collection.add(new String("Tom"));
collection.add(false);
Iterator iterator = collection.iterator();
// 删除集合中 “Tom” 数据
while (iterator.hasNext()){
Object obj = iterator.next();
if ("Tom".equals(obj)){
iterator.remove();
}
}
iterator = collection.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}@Test
public void test1() {
Collection collection = new ArrayList();
collection.add(123);
collection.add(456);
collection.add(new Person("Jerry", 20));
collection.add(new String("Tom"));
collection.add(false);
// for(集合中元素的类型 局部变量 : 集合对象)
// 内部仍然调用了迭代器
for(Object obj : collection){
System.out.println(obj);
}
}说明:内部仍然调用了迭代器
@Test
public void test2(){
int[] arr = new int[]{1, 2, 3, 4, 5, 6};
for (int a : arr){
System.out.println(a);
}
}存储有序的、可重复的数据
add(Object obj)remove(int index) / remove(Object obj)set(int index, Object ele)get(int index)add(int index, Object ele)size()|---- Collection 接口:单列集合,用来存储一个一个的数据
|---- List 接口:存储有序的、可重复的数据 --> “动态"数组,替换原有数组
|---- ArrayList
作为 List 接口的主要实现类
线程不安全,效率高
底层使用 Object[] elementData 存储
|---- LinkedList
对于频繁的插入和删除,使用此类效率比 ArrayList 高
底层使用双向列表存储
|---- Vector
作为 List 接口的古老实现类
线程安全,效率低
底层使用 Object[] elementData 存储ArrayList 的源码分析
LinkedList 的源码分析
LinkedList list = new LinkedList(); // 内部声明了 Node 类型的 first 和 last 属性,默认值为 null
list.add(123); // 将123封装到 Node 中,创建了 Node 对象
其中,Node 定义为:体现了 LinkedList 的双向链表的说法
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}Vector 的源码分析
通过 Vector() 构造器创建对象时,底层都创建了长度为10的数组,在扩容反码,默认扩容为原来数组长度的2倍
添加的对象所在的类要重写 equals()
面试题:ArrayList、LinkedList、Vector 三者的异同?
具体的:
以 HashSet 为例说明
以 HashSet 为例
Set 接口中没有额外定义新的方法,使用的都是 Collection 中声明过的方法
|---- Collection 接口:单列集合,用来存储一个一个的数据
|---- Set 接口:存储无序的、不可重复的数据 --> 高中讲的"集合“
|---- HashSet
作为 Set 接口的主要实现类
线程不安全的
可以存储 null 值
|---- LinkedHashSet
作为 HashSet 的子类,在添加数据的同时,每个数据还维护了两个引用,
记录此数据的前一个数据和后一个数据
遍历其内部数据时,可以按照添加的顺序遍历
对于频繁的遍历操作:LinkedHashSet 效率高于 HashSet
|---- TreeSet
可以按照添加对象的指定属性,进行排序自然排序
@Test
public void test1(){
TreeSet set = new TreeSet();
// 失败:不能添加不同类的对象
// set.add(456);
// set.add(123);
// set.add("AA");
// set.add("CC");
// set.add(new User("Tom", 12));
// set.add(34);
// set.add(-34);
// set.add(43);
// set.add(11);
// set.add(8);
set.add(new User("Tom", 12));
set.add(new User("Jerry", 32));
set.add(new User("Jim", 22));
set.add(new User("Mike", 65));
set.add(new User("Jack", 33));
set.add(new User("Jack", 56));
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}定制排序
@Test
public void test2() {
Comparator comparator = new Comparator() {
/**
* 按照年龄从小到大排列
* @param o1
* @param o2
* @return
*/
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof User && o2 instanceof User){
User u1 = (User) o1;
User u2 = (User) o2;
return Integer.compare(u1.getAge(), u2.getAge());
}
throw new RuntimeException("输入的数据类型不匹配");
}
};
TreeSet set = new TreeSet(comparator);
set.add(new User("Tom", 12));
set.add(new User("Jerry", 32));
set.add(new User("Jim", 22));
set.add(new User("Mike", 65));
set.add(new User("Jack", 33));
set.add(new User("Jack", 56));
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}|---- Map:双列数据,存储 key-value 对的数据 --> 类似于高中函数:y = f(x)
|---- HashMap
作为 Map 的主要实现类
线程不安全的,效率高
存储 null 的 key 和 value
|---- LinkedHashMap
保证在遍历 map 元素时,可以按照添加的顺序实现遍历
原因:在原有的 HashMap 底层结构基础上,添加了一对指针,指向前一个和后一个元素
对于频繁的遍历操作,此类执行效率高于 HashMap
|---- TreeMap
保证按照添加的 key-value 对进行排序,实现排序遍历,此时考虑 key 的自然排序和定制排序
底层使用红黑树
|---- Hashtable
作为 Map 的古老实现类
线程安全的,效率低
不能存储 null 的 key 和 value
|---- Properties
常用来处理配置文件
key 和 value 都是 String 类型HashMap 的底层:
面试题:
Map 中的 key:无序的、不可重复的,使用 Set 存储所有的 key --> key 所在的类要重写 equals() 和 hashCode()
Map 中的 value:有序的、可重复的,使用 Collection 存储所有的 value --> value 所在的类要重写 equals()
一个键值对:key-value 构成了一个 Entry 对象
Map 中的 Entry:无序的、不可重复的,使用 Set 存储所有的 entry
图示:

put(Object key,Object value)remove(Object key)put(Object key,Object value)get(Object key)size()keySet()values()entrySet()关于情况2和情况3:此时的 key1-value1 和原来的数据以链表的方式存储
在不断的添加过程中,会涉及到扩容问题,默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来
LinkedHashMap 底层使用的结构与 HashMap 相同,因为 LinkedHashMap 继承于 HashMap,区别就在于:LinkedHashMap 内部提供了 Entry,替换 HashMap 中的 Node
HashMap 中的 Node:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}LinkedHashMap 中的 Entry
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after; // 能够记录添加元素的先后顺序
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}向 TreeMap 中添加 key-value,要求 key 必须是有同一个类创建的对象,因为要按照 key 进行排序:自然排序 定制排序
常用于处理属性文件,key 和 value 都是字符串类型
public static void main(String[] args) {
FileInputStream fileInputStream = null;
try {
Properties prop = new Properties();
fileInputStream = new FileInputStream("jdbc.properties");
prop.load(fileInputStream);
String name = prop.getProperty("name");
String password = prop.getProperty("password");
System.out.println("name = " + name);
System.out.println("password = " + password);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}操作 Collection 和 Map 的工具类
reverse(List):反转 List 中元素的顺序shuffle(List):对 List 集合元素进行随机排序sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素Object min(Collection):根据元素的自然顺序,返回给定集合中的最小元素Object min(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最小元素int frequency(Collection,Object):返回指定集合中指定元素的出现次数void copy(List dest, List src):将 src 中的内容复制到 dest 中boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
说明:ArrayList 和 HashMap 都是线程不安全的,如果程序要求线程安全,可以将 ArrayList 和 HashMap转换为线程安全的,使用 synchronizedList(List list) 和 synchronizedMap(Map map)
Collection 和 Collections 的区别
数据结构(Data Structure)是一门和计算机硬件与软件都密切相关的学科,它的研究重点是在计算机的程序设计领域中探讨如何在计算机中组织和存储数据并进行高效率的运用,涉及的内容包含:数据的逻辑关系、数据的存储结构、排序算法(Algorithm)、查找(或搜索)等。
**序能否快速而高效地完成预定的任务,取决于是否选对了数据结构,而程序是否能清楚而正确地把问题解决,则取决于算法。**算法是计算机处理信息的本质,因为计算机程序本质上是一个算法来告诉计算机确切的步骤来执行一个指定的任务。
所以大家认为:“Algorithms + Data Structures = Programs”(出自:Pascal之父Nicklaus Wirth)
总结:算法是为了解决实际问题而设计的,数据结构是算法需要处理的问题载体。

说明:
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类 型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如, 继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实 际的类型参数,也称为类型实参)。
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的 对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来 解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于 这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个 参数,这个类型参数叫做泛型。Collection,List,ArrayList 这个就是类型参数,即泛型。
@Test
public void test1(){
ArrayList list = new ArrayList();
list.add(78);
list.add(77);
list.add(89);
list.add(88);
// 问题一:类型不安全
// list.add("Tom");
for (Object score : list) {
// 问题二:强转时,可能出现 ClassCastException
int stuScore = (int) score;
System.out.println(stuScore);
}
}图示:

@Test
public void test2(){
// ArrayList<Integer> list = new ArrayList<Integer>();
// JDK7 新特性:类型推断
ArrayList<Integer> list = new ArrayList<>();
list.add(78);
list.add(87);
list.add(99);
list.add(65);
// 编译时,就会进行类型检查,保证数据的安全
// list.add("65");
/*for(Integer score : list){
// 避免了强转操作
int stuScore = score;
System.out.println(stuScore);
}*/
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
int stuScore = iterator.next();
System.out.println(stuScore);
}
}图示:

@Test
public void test3(){
Map<String, Integer> map = new HashMap<>();
map.put("Tom", 87);
map.put("Jerry", 87);
map.put("Jack", 67);
// map.put(123, "67");
Set<Map.Entry<String, Integer>> entry = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();
while (iterator.hasNext()){
Map.Entry<String, Integer> e = iterator.next();
String key = e.getKey();
Integer value = e.getValue();
System.out.println(key + " --- " + value);
}
}[Order.java]
public class Order<T> {
String orderName;
int orderId;
// 类的内部结构就可以使用类的泛型
T orderT;
public Order(){
// 编译不通过
// T[] arr = new T[10];
// 编译通过
T[] arr = (T[]) new Object[10];
}
public Order(String orderName, int orderId, T orderT) {
this.orderName = orderName;
this.orderId = orderId;
this.orderT = orderT;
}
public String getOrderName() {
return orderName;
}
public void setOrderName(String orderName) {
this.orderName = orderName;
}
public int getOrderId() {
return orderId;
}
public void setOrderId(int orderId) {
this.orderId = orderId;
}
public T getOrderT() {
return orderT;
}
public void setOrderT(T orderT) {
this.orderT = orderT;
}
@Override
public String toString() {
return "Order{" +
"orderName='" + orderName + '\'' +
", orderId=" + orderId +
", orderT=" + orderT +
'}';
}
// 静态方法中不能使用类的泛型
/*public static void show(){
System.out.println(orderT);
}*/
public void show(){
// 编译不通过
/*try {
}catch (T t){
}*/
}
// 泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系
// 换句话说,泛型方法所属的类是不是泛型类都没有关系
// 泛型方法可以声明为静态的,原因:泛型参数是在调用方法时确定的,并非在实例化类时确定
public static <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for (E e : arr) {
list.add(e);
}
return list;
}
}[SubOrder.java]
public class SubOrder extends Order<Integer> {
public static <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for (E e : arr) {
list.add(e);
}
return list;
}
}[SubOrder1.java]
public class SubOrder1<T> extends Order<T>{
}测试:
public class GenericTest1 {
@Test
public void test1(){
// 如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为 Object 类型
// 要求:如果定义了类是带泛型的,建议在实例化时要指明类的泛型
Order order = new Order();
order.setOrderT(123);
order.setOrderT("123");
// 建议:实例化时指明类的泛型
Order<String> order1 = new Order<>("orderAA",
1001, "order:AA") ;
order1.setOrderT("AA:hello");
}
@Test
public void test2(){
// 由于子类在继承带泛型的父类时,指明了泛型类型,则实例化子类对象时,不再需要指明泛型
SubOrder subOrder = new SubOrder();
subOrder.setOrderT(1122);
SubOrder1<String> subOrder1 = new SubOrder1<>();
subOrder1.setOrderT("order2...");
}
@Test
public void test3(){
// 泛型不同的引用不能相互赋值
ArrayList<String> list1 = null;
ArrayList<Integer> list2 = new ArrayList<>();
// list1 = list2;
Person p1 = null;
Person p2 = null;
p1 = p2;
}
// 测试泛型方法
@Test
public void test4(){
Order<String> order = new Order<>();
Integer[] arr = {1, 2, 3, 4};
// 泛型方法在调用时,指明泛型参数的类型
List<Integer> list = order.copyFromArrayToList(arr);
System.out.println(list);
}
}结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型
[DAO.java]:定义了操作数据库中的表的通用操作。ORM 思想(数据库中的表和 Java 中的类对应)
public class DAO<T> {
// 添加一条记录
public void add(T t){
}
// 删除一条记录
public boolean remove(int index){
return false;
}
// 修改一条记录
public void update(int index, T t){
}
// 查询一条记录
public T getIndex(int index){
return null;
}
// 查询多条记录
public List<T> getForList(int index){
return null;
}
public <E> E getValue(){
return null;
}
}[CustomerDAO.java]
public class CustomerDAO extends DAO<Customer>{
}[StudentDAO.java]
public class StudentDAO extends DAO<Student>{
}虽然类 A 是类 B 的父类,但是 G 和 G 二者不具备子父类关系,二者是并列关系
补充:类 A 是类 B 的父类,A 是 B 的父类
@Test
public void test1(){
Object obj = null;
String str = null;
obj = str;
Date date = new Date();
// 编译不通过
// str = date;
Object[] arr1 = null;
String[] arr2 = null;
arr1 = arr2;
List<Object> list1 = null;
List<String> list2 = new ArrayList<>();
// 此时的 list1 和 list2 的类型不具备子父类关系
// 编译不通过
// list1 = list2;
/*
反证法
假设 list1 = list2; // 导致混入非 String 的数据,出错
*/
show(list1);
// show(list2);
}
public void show(List<Object> list){
}
@Test
public void test2(){
AbstractList<String> list1 = null;
List<String> list2 = null;
ArrayList<String> list3 = null;
list1 = list3;
list2 = list3;
}通配符:?
类 A 是类 B 的父类,G 和 G 是没有关系的,二者共同的父类是:G<?>
@Test
public void test3() {
List<Object> list1 = null;
List<String> list2 = null;
List<?> list = null;
list = list1;
list = list2;
// print(list1);
// print(list2);
List<String> list3 = new ArrayList<>();
list3.add("AA");
list3.add("BB");
list3.add("CC");
list = list3;
// 添加(写入):对于 List<?> 就不能向其内部添加数据,除了添加 null 之外
// list.add("DD");
list.add(null);
// 获取(读取):允许读取数据,读取的数据类型为 Object
Object o = list.get(0);
System.out.println(o);
}
public void print(List<?> list){
Iterator<?> iterator = list.iterator();
while (iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
}@Test
public void test3() {
List<Object> list1 = null;
List<String> list3 = new ArrayList<>();
list3.add("AA");
list3.add("BB");
list3.add("CC");
list = list3;
// 添加(写入):对于 List<?> 就不能向其内部添加数据,除了添加 null 之外
// list.add("DD");
list.add(null);
// 获取(读取):允许读取数据,读取的数据类型为 Object
Object o = list.get(0);
System.out.println(o);
}@Test
public void test4(){
List<? extends Person> list1 = null;
List<? super Person> list2 = null;
List<Student> list3 = new ArrayList<>();
List<Person> list4 = new ArrayList<>();
List<Object> list5 = new ArrayList<>();
list1 = list3;
list1 = list4;
// list1 = list5;
// list2 = list3;
list2 = list4;
list2 = list5;
// 读取数据
list1 = list3;
Person person = list1.get(0);
// 编译不通过
// Person person1 = list1.get(0);
list2 = list4;
Object obj = list2.get(0);
// 写入数据:
// list1.add(new Student());
list2.add(new Person());
list2.add(new Student());
}File(String filepath)File(String parentPath, String childPath)File(File parentFile, String childPath)说明:
public String getAbsolutePath():获取绝对路径
public String getPath():获取路径
public String getName():获取名称
public String getParent():获取上层文件目录路径。若无,返回 null
public long length():获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified():获取最后一次的修改时间,毫秒值
如下的两个方法适用于文件目录
public String[] list():获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles():获取指定目录下的所有文件或者文件目录的 File 数组
public boolean renameTo(File dest):把文件重命名为指定的文件路径public boolean isDirectory():判断是否是文件目录public boolean isFile():判断是否是文件public boolean exists():判断是否存在public boolean canRead():判断是否可读public boolean canWrite():判断是否可写public boolean isHidden():判断是否隐藏public boolean createNewFile():创建文件。若文件存在,则不创建,返回falsepublic boolean mkdir():创建文件目录。如果此文件目录存在,就不创建了。 如果此文件目录的上层目录不存在,也不创建。public boolean mkdirs():创建文件目录。如果上层文件目录不存在,一并创建注意事项:如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目 路径下。
public boolean delete():删除文件或者文件夹
删除注意事项:Java中的删除不走回收站。 要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录
图示:

分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
|---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
访问字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | OutPutStreamWriter | ||
对象流 | ObjectInputStream | ObjectOutputStream | ||
打印流 | PrintStream | PrintWriter | ||
推回输入流 | PushbackInputStream | PushbackReader | ||
特殊流 | DataInputStream | DataOutputStream |
抽象基类 | 文件流 | 缓冲流 |
|---|---|---|
InputStream | FileInputStream(read(byte[] buffer)) | BufferedInputStream(read(byte[] buffer)) |
OutputStream | FileOutputStream(write(byte[] buffer, 0, len)) | BufferedInputStream(write(byte[] buffer, 0, len) / flush()) |
Reader | FileReader(read(char[] cbuf)) | BufferedReader(read(char[] cbuf) / readLine()) |
Writer | FileWriter(write(char[] cbuf, 0, len)) | BufferedWriter(write(char[] cbuf, 0, len) / flush()) |
说明:程序中出现的异常需要使用 try-catch-finally 处理
write(char[] / byte[] buffer, 0, len)说明:程序中出现的异常需要使用 try-catch-finally 处理
说明点:
@Test
public void testFileReader1() {
FileReader fileReader = null;
try {
// 1.File 类的实例化
File file = new File("hello.txt");
// 2.FileReader 流的实例化
fileReader = new FileReader(file);
// 3.读入的操作
// read(char[] cbuf):返回每次读入 cbuf 数组中的字符的个数,如果达到文件末尾,返回-1
char[] cbuf = new char[5];
int len = 0;
while ((len = fileReader.read(cbuf)) != -1){
// 错误的写法
/*for (int i = 0; i < cbuf.length; i++) {
System.out.print(cbuf[i]);
}*/
// 正确的写法
/*for (int i = 0; i < len; i++) {
System.out.print(cbuf[i]);
}*/
// 错误的写法
// System.out.print(new String(cbuf));
// 正确的写法
System.out.print(new String(cbuf, 0, len));
}
} catch (IOException e){
e.printStackTrace();
} finally{
// 4.资源的关闭
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}说明:
@Test
public void testFileWriter(){
FileWriter fileWriter = null;
try {
// 1.提供 File 类的对象,指明写出到的文件
File file = new File("hello1.txt");
// 2.提供 FileWriter 的对象,用于数据的写出
fileWriter = new FileWriter(file, false);
// 3.写出的操作
fileWriter.write("I have a dream!\n");
fileWriter.write("you need to have a dream!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4.流资源的关闭
if (fileWriter != null) {
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}@Test
public void testFileReaderFileWriter() {
FileReader fileReader = null;
FileWriter fileWriter = null;
try {
// 1.创建 File 类的对象,指明读入和写出的文件
// File srcFile = new File("hello.txt");
// File destFile = new File("hello2.txt");
// 不能使用字符流来处理图片等字节数据
File srcFile = new File("爱情与友情.png");
File destFile = new File("爱情与友情1.png");
// 2.创建输入流和输出流的对象
fileReader = new FileReader(srcFile);
fileWriter = new FileWriter(destFile);
// 3.数据的读入和写出操作
char[] cbuf = new char[5];
// 记录每次读入到 cbuf 数组中的字符的个数
int len = 0;
while ((len = fileReader.read(cbuf)) != -1){
// 每次写出 len 个字符
fileWriter.write(cbuf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4.关闭流资源
if (fileWriter != null) {
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}/**
* 实现对图片的复制
*/
@Test
public void testFileInputOutputStream(){
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
File srcFile = new File("爱情与友情.png");
File destFile = new File("爱情与友情2.png");
fileInputStream = new FileInputStream(srcFile);
fileOutputStream = new FileOutputStream(destFile);
// 复制的过程
byte[] buffer = new byte[5];
int len = 0;
while ((len = fileInputStream.read(buffer)) != -1){
fileOutputStream.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}注意点:相对路径在 IDEA 和 Eclipse 中使用的区别?
作用:提高流的读取、写入的速度
提高读写速度的原因:内部提供了一个缓冲区,默认情况下是8kb
public class BufferedInputStream extends FilterInputStream {
private static int DEFAULT_BUFFER_SIZE = 8192;
}处理非文本文件
/**
* 实现文件复制的方法
*/
public void copyFileWithBuffered(String srcPath, String destPath){
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
// 造文件
File srcFile = new File(srcPath);
File destFile = new File(destPath);
// 2.造流
// 2.1.造节点流
FileInputStream fileInputStream = new FileInputStream(srcFile);
FileOutputStream fileOutputStream = new FileOutputStream(destFile);
// 2.2.造缓冲流
bufferedInputStream = new BufferedInputStream(fileInputStream);
bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
// 3.复制的细节
byte[] buffer = new byte[1024];
int len = 0;
while ((len = bufferedInputStream.read(buffer)) != -1){
bufferedOutputStream.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4.资源关闭
// 要求:先关闭外层的流,再关闭内层的流
if (bufferedOutputStream != null) {
try {
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedInputStream != null) {
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 说明:在关闭外层流的同时,内层流也会自动进行关闭,对于内层流的关闭,可以省略
// fileOutputStream.close();
// fileInputStream.close();
}
}处理文本文件
/**
* 使用 BufferedReader 和 BufferedWriter 实现文本文件的复制
*/
@Test
public void testBufferedReaderBufferedWriter(){
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
try {
// 创建文件和相应的流
bufferedReader = new BufferedReader(new FileReader(new File("dbcp.txt")));
bufferedWriter = new BufferedWriter(new FileWriter(new File("dbcp1.txt")));
// 读写操作
// 方式一,使用 char[] 数组
/*char[] cbuf = new char[1024];
int len = 0;
while ((len = bufferedReader.read(cbuf)) != -1){
bufferedWriter.write(cbuf, 0, len);
}*/
// 方式二:使用 String
String data;
while ((data = bufferedReader.readLine()) != null){
// 方法一:
// data 中不包含换行符
// bufferedWriter.write(data + "\n");
// 方法二:
bufferedWriter.write(data);
// 提供换行的操作
bufferedWriter.newLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedWriter != null) {
try {
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}属于字符流
说明:编码决定了解码的方式
提供字节流与字符流之间的转换

@Test
public void test1(){
InputStreamReader inputStreamReader = null;
try {
FileInputStream fileInputStream = new FileInputStream("dbcp.txt");
// 使用系统默认的字符集
// InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
// 参数2指明了字符集:具体使用哪个字符集,取决于文件 dbcp.txt 保存时使用的字符集
inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8);
char[] cbuf = new char[20];
int len = 0;
while ((len = inputStreamReader.read(cbuf)) != -1){
System.out.print(new String(cbuf,0, len));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStreamReader != null) {
try {
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}/**
* 综合使用 InputStreamReader 和 OutputStreamWriter
*/
@Test
public void test2(){
InputStreamReader inputStreamReader = null;
OutputStreamWriter outputStreamWriter = null;
try {
File file1 = new File("dbcp.txt");
File file2 = new File("dbcp_gbk.txt");
FileInputStream fileInputStream = new FileInputStream(file1);
FileOutputStream fileOutputStream = new FileOutputStream(file2);
inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8);
outputStreamWriter = new OutputStreamWriter(fileOutputStream, "gbk");
char[] cbuf = new char[20];
int len = 0;
while ((len = inputStreamReader.read(cbuf)) != -1){
outputStreamWriter.write(cbuf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (inputStreamReader != null) {
try {
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStreamWriter != null) {
try {
outputStreamWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}文件编码的方式,决定了解析时使用的字符集
客户端 / 游览器端 <—> 后台(Java, Go, Python, Node.js, PHP) <—> 数据库
要求前前后后使用的字符集都要统一:UTF-8
修改默认的输入和输出行为:System 类的 setIn(InputStream InputStream) / setOut(OutputStream outputStream) 方式重新指定输入和输出的流
说明:
作用:用于读取或写出基本数据类型的变量或字符串
示例代码:
/**
* 数据流:DataInputStream 和 DataOutputStream
* 1.作用:用于读取或写出基本数据类型的变量或字符串
*/
@Test
public void test3(){
DataOutputStream dataOutputStream = null;
try {
dataOutputStream = new DataOutputStream(new FileOutputStream("data.txt"));
dataOutputStream.writeUTF("刘建辰");
dataOutputStream.writeInt(23);
dataOutputStream.writeBoolean(true);
// 刷新操作,将内存中的数据写入文件
dataOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (dataOutputStream != null) {
try {
dataOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}/**
* 将文件中存储的基本数据类型变量和字符串读取到内存中
* 注意点:读取不同类型数据的顺序要以当初写入文件时,保存的数据的顺序一致
*/
@Test
public void test4(){
DataInputStream dataInputStream = null;
try {
dataInputStream = new DataInputStream(new FileInputStream("data.txt"));
System.out.println(dataInputStream.readUTF());
System.out.println(dataInputStream.readInt());
System.out.println(dataInputStream.readBoolean());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (dataInputStream != null) {
try {
dataInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}ObjectOutputStream:内存中的对象 --> 存储中的文件、通过网络传输出去:序列化过程
ObjectInputStream:存储中的文件、通过网络接收过来 --> 内存中的对象:反序列化过程
对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的 Java 对象
@Test
public void testObjectOuputStream(){
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(new FileOutputStream("object.dat"));
objectOutputStream.writeObject(new String("我爱北京天安门"));
// 刷新操作
objectOutputStream.flush();
objectOutputStream.writeObject(new Person("王铭", 23));
objectOutputStream.flush();
objectOutputStream.writeObject(new Person("张学良", 23, 1001, new Account(5000)));
objectOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (objectOutputStream != null) {
try {
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}@Test
public void testObjectInputStream(){
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(new FileInputStream("object.dat"));
Object obj = objectInputStream.readObject();
String str = (String) obj;
System.out.println(str);
Person person = (Person) objectInputStream.readObject();
System.out.println(person);
Person person1 = (Person) objectInputStream.readObject();
System.out.println(person1);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (objectInputStream != null) {
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}补充:ObjectOutputStream 和 ObjectInputStream 不能序列化 static 和 transient 修饰的成员变量
seek(int pos)@Test
public void test1(){
RandomAccessFile randomAccessFile1 = null;
RandomAccessFile randomAccessFile2 = null;
try {
randomAccessFile1 = new RandomAccessFile(new File("爱情与友情.png"), "r");
randomAccessFile2 = new RandomAccessFile(new File("爱情与友情1.png"), "rw");
byte[] buffer = new byte[1024];
int len = 0;
while ((len = randomAccessFile1.read(buffer)) != -1){
randomAccessFile2.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (randomAccessFile1 != null) {
try {
randomAccessFile1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (randomAccessFile2 != null) {
try {
randomAccessFile2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}@Test
public void test3() {
RandomAccessFile randomAccessFile = null;
ByteArrayOutputStream byteArrayOutputStream = null;
try {
randomAccessFile = new RandomAccessFile("hello.txt", "rw");
// 将指针调到角标为3的位置
randomAccessFile.seek(3);
// 保存指针3后面的所有数据到 ByteArrayOutputStream / StringBuilder 中
byteArrayOutputStream = new ByteArrayOutputStream();
/*StringBuilder builder = new StringBuilder(
(int) new File("hello.txt").length());*/
byte[] buffer = new byte[20];
int len = 0;
while ((len = randomAccessFile.read(buffer)) != -1){
// builder.append(new String(buffer, 0, len));
byteArrayOutputStream.write(buffer, 0, len);
}
randomAccessFile.seek(3);
randomAccessFile.write("xyz".getBytes());
// randomAccessFile.write(builder.toString().getBytes());
randomAccessFile.write(byteArrayOutputStream.toByteArray());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (byteArrayOutputStream != null) {
try {
byteArrayOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (randomAccessFile != null) {
try {
randomAccessFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}Path 替换原有的 File 类
Paths 类提供的静态 get() 方法用来获取 Path 对象:
static Path get(String first, String … more): 用于将多个字符串串连成路径static Path get(URI uri): 返回指定 uri 对应的 Path 路径String toString(): 返回调用 Path 对象的字符串表示形式boolean startsWith(String path): 判断是否以 path 路径开始boolean endsWith(String path): 判断是否以 path 路径结束boolean isAbsolute(): 判断是否是绝对路径Path getParent(): 返回Path对象包含整个路径,不包含 Path 对象指定的文件路径Path getRoot(): 返回调用 Path 对象的根路径Path getFileName(): 返回与调用 Path 对象关联的文件名int getNameCount(): 返回 Path 根目录后面元素的数量Path getName(int idx): 返回指定索引位置 idx 的路径名称Path toAbsolutePath(): 作为绝对路径返回调用 Path 对象Path resolve(Path p): 合并两个路径,返回合并后的路径对应的 Path 对象File toFile(): 将 Path 转化为 File 类的对象操作文件或文件目录的工具类
Path copy(Path src, Path dest, CopyOption … how): 文件的复制Path createDirectory(Path path, FileAttribute<?> … attr): 创建一个目录Path createFile(Path path, FileAttribute<?> … arr): 创建一个文件void delete(Path path): 删除一个文件/目录,如果不存在,执行报错void deleteIfExists(Path path): Path对应的文件/目录如果存在,执行删除Path move(Path src, Path dest, CopyOption…how): 将 src 移动到 dest 位置long size(Path path): 返回 path 指定文件的大小boolean exists(Path path, LinkOption … opts): 判断文件是否存在
boolean isDirectory(Path path, LinkOption … opts): 判断是否是目录
boolean isRegularFile(Path path, LinkOption … opts): 判断是否是文件
boolean isHidden(Path path): 判断是否是隐藏文件
boolean isReadable(Path path): 判断文件是否可读
boolean isWritable(Path path): 判断文件是否可写
boolean notExists(Path path, LinkOption … opts): 判断文件是否不存在
SeekableByteChannel newByteChannel(Path path, OpenOption…how): 获取与指定文件的连接,how 指定打开方式。DirectoryStream<Path> newDirectoryStream(Path path): 打开 path 指定的目录InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象OutputStream newOutputStream(Path path, OpenOption…how): 获取 OutputStream 对象此类的一个对象表着一个 IP 地址
实例化:
getByName(String host)getLocalhost()常用方法:
getHostName()getHostAddress()端口号与 IP 地址的组合得出一个网络套接字:Socket



例子1:客户端发送信息给服务端,服务端将数据显示在控制台上
public class TCPTest1 {
/**
* 客户端
*/
@Test
public void client(){
Socket socket = null;
OutputStream outputStream = null;
try {
// 1.创建 Socket 对象,指明服务器的 IP 和端口号
InetAddress inet = InetAddress.getByName("127.0.0.1");
socket = new Socket(inet, 8899);
// 2.获取一个输出流,用于输出数据
outputStream = socket.getOutputStream();
// 3.写出数据的操作
outputStream.write("你好,我是客户端mm".getBytes());
} catch (IOException e) {
e.printStackTrace();
}finally {
// 4.资源的关闭
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 服务端
*/
@Test
public void server(){
ServerSocket serverSocket = null;
Socket socket = null;
InputStream inputStream = null;
ByteArrayOutputStream byteArrayOutputStream = null;
try {
// 1.创建服务器端的 ServerSocket,指明自己的端口号
serverSocket = new ServerSocket(8899);
// 2.调用 accept() 表示接收来自于客户端的 socket
socket = serverSocket.accept();
// 3.获取输入流
inputStream = socket.getInputStream();
// 不建议,有乱码
/*byte[] buffer = new byte[1024];
int len = 0;
while ((len = inputStream.read(buffer)) != -1){
System.out.print(new String(buffer, 0, len));
}*/
// 4.获取输入流中的数据
byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[5];
int len = 0;
while ((len = inputStream.read(buffer)) != -1){
byteArrayOutputStream.write(buffer, 0, len);
}
System.out.println(byteArrayOutputStream);
System.out.println("收到了来自于:" + socket.getInetAddress().getHostAddress() + " 的数据");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5.资源的关闭
if (byteArrayOutputStream != null) {
try {
byteArrayOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}例题2:客户端发送文件给服务端,服务端将文件保存在本地
public class TCPTest2 {
@Test
public void client(){
Socket socket = null;
OutputStream outputStream = null;
FileInputStream fileInputStream = null;
try {
socket = new Socket(InetAddress.getByName("127.0.0.1"), 9090);
outputStream = socket.getOutputStream();
fileInputStream = new FileInputStream("beauty.png");
byte[] buffer = new byte[1024];
int len = 0;
while ((len = fileInputStream.read(buffer)) != -1){
outputStream.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void server(){
ServerSocket serverSocket = null;
Socket socket = null;
InputStream inputStream = null;
FileOutputStream fileOutputStream = null;
try {
serverSocket = new ServerSocket(9090);
socket = serverSocket.accept();
inputStream = socket.getInputStream();
fileOutputStream = new FileOutputStream("beauty1.png");
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inputStream.read(buffer)) != -1){
fileOutputStream.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}例子3:从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端。并关闭相应的连接。
public class TCPTest3 {
@Test
public void client(){
Socket socket = null;
OutputStream outputStream = null;
InputStream inputStream = null;
FileInputStream fileInputStream = null;
ByteArrayOutputStream byteArrayOutputStream = null;
try {
socket = new Socket(InetAddress.getByName("127.0.0.1"), 9090);
outputStream = socket.getOutputStream();
fileInputStream = new FileInputStream("beauty.png");
byte[] buffer = new byte[1024];
int len = 0;
while ((len = fileInputStream.read(buffer)) != -1){
outputStream.write(buffer, 0, len);
}
// 关闭数据的输出
socket.shutdownOutput();
// 接收来自于服务器端的数据,并显示到控制台
inputStream = socket.getInputStream();
byteArrayOutputStream = new ByteArrayOutputStream();
byte[] bufferr = new byte[20];
int len1 = 0;
while ((len1 = inputStream.read(bufferr)) != -1){
byteArrayOutputStream.write(bufferr, 0, len1);
}
System.out.println(byteArrayOutputStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (byteArrayOutputStream != null) {
try {
byteArrayOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void server(){
ServerSocket serverSocket = null;
Socket socket = null;
InputStream inputStream = null;
FileOutputStream fileOutputStream = null;
OutputStream outputStream = null;
try {
serverSocket = new ServerSocket(9090);
socket = serverSocket.accept();
inputStream = socket.getInputStream();
fileOutputStream = new FileOutputStream("beauty2.png");
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inputStream.read(buffer)) != -1){
fileOutputStream.write(buffer, 0, len);
}
System.out.println("图片传输完成");
// 服务器给予客户端反馈
outputStream = socket.getOutputStream();
outputStream.write("你好,美女,照片我已收到,非常漂亮".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}public class UDPTest {
/**
* 发送端
*/
@Test
public void sender(){
DatagramSocket socket = null;
try {
socket = new DatagramSocket();
String str = "我是 UDP 方式发送的导弹";
byte[] data = str.getBytes();
InetAddress inet = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(data, 0, data.length, inet, 9090);
socket.send(packet);
} catch (IOException e) {
e.printStackTrace();
} finally {
socket.close();
}
}
/**
* 接收端
*/
@Test
public void receiver(){
DatagramSocket socket = null;
try {
socket = new DatagramSocket(9090);
byte[] buffer = new byte[100];
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
socket.receive(packet);
System.out.println(new String(packet.getData(), 0, packet.getLength()));
} catch (IOException e) {
e.printStackTrace();
} finally {
socket.close();
}
}
}Uniform Resource Locator:统一资源定位符,对应着互联网的某一资源地址
http://localhost:8080/examples/beauty.png?p=629&spm_id_from=pageDriver 协议 主机名 端口号 资源地址 参数列表
URL url = new URL(“http://localhost:8080/examples/beauty.png?p=629&spm_id_from=pageDriver”);
public String getProtocol():获取该 URL 的协议名public String getHost():获取该 URL 的主机名public String getPort():获取该 URL 的端口号public String getPath():获取该 URL 的文件路径public String getFile():获取该 URL 的文件名public String getQuery():获取该 URL 的查询名public class URLTest1 {
public static void main(String[] args){
HttpURLConnection urlConnection = null;
InputStream inputStream = null;
FileOutputStream fileOutputStream = null;
try {
URL url = new URL("http://localhost:8080/examples/beauty.png");
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.connect();
inputStream = urlConnection.getInputStream();
fileOutputStream = new FileOutputStream("JavaSenior\\day10\\beauty3.png");
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inputStream.read(buffer)) != -1){
fileOutputStream.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
}Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期 借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内 部属性及方法。
框架 = 反射 + 注解 + 设计模式
@Test
public void test2(){
for (int i = 0; i < 100; i++) {
int num = new Random().nextInt(3);
String classPath = "";
switch (num){
case 0:
classPath = "java.util.Date";
break;
case 1:
classPath = "java.lang.Object";
break;
case 2:
classPath = "cn.tedu.java.Person";
break;
}
System.out.println(getInstance(classPath));
}
}
/**
* 创建一个指定类的对象
* @param classPath 指定类的全类名
* @return
* @throws Exception
*/
public Object getInstance(String classPath){
try {
return Class.forName(classPath).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}java.lang.Class:反射的源头java.lang.reflect.Methodjava.lang.reflect.Fieldjava.lang.reflect.Constructor/**
* 获取 Class 的实例的方式(前三种需要掌握)
*/
@Test
public void test3() throws ClassNotFoundException {
// 方式一:调用运行时类的属性:.class
Class<Person> clazz1 = Person.class;
System.out.println(clazz1);
// 方式二:通过运行时类的对象,调用 getClass()
Person p1 = new Person();
Class<? extends Person> clazz2 = p1.getClass();
System.out.println(clazz2);
// 方式三:调用 Class 的静态方法:forname(String classPath)
Class<?> clazz3 = Class.forName("cn.tedu.java.Person");
// clazz3 = Class.forName("java.lang.String");
System.out.println(clazz3);
System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);
// 方式四:使用类的加载器:ClassLoader(了解)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class<?> clazz4 = classLoader.loadClass("cn.tedu.java.Person");
System.out.println(clazz4);
}
java.lang.Class 对象,作为 方法区中类数据的访问入口。


@Test
public void test2() throws IOException {
Properties properties = new Properties();
// 此时的文件默认在当前的 Module 下
// 读取配置方式一:
// FileInputStream fileInputStream = new FileInputStream("jdbc.properties");
// FileInputStream fileInputStream = new FileInputStream("src\\jdbc1.properties");
// properties.load(fileInputStream);
// 读取配置方式二:使用 ClassLoader
// 配置文件默认识别为:当前 module 的 src 下
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream resourceAsStream = classLoader.getResourceAsStream("jdbc1.properties");
properties.load(resourceAsStream);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
System.out.println("user = " + user + ", password = " + password);
}@Test
public void test1() throws Exception{
Class<Person> clazz = Person.class;
Person person = clazz.newInstance();
System.out.println(person);
}newInstance():调用此方法,创建对应的运行时类的对象,内部调用了运行时类的空参构造器通过反射,获取对应的运行时类中所有的属性、方法、构造器、父类、接口、父类的泛型、包、注解、异常等
@Test
public void test1(){
Class<Person> clazz = Person.class;
// 获取属性结构
// getFeilds():获取当前运行时类及其父类声明为 public 访问权限的属性
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println();
// getDeclaredFields():获取当前运行时类中声明的所有的属性(不包含父类中声明的属性)
for (Field declaredField : clazz.getDeclaredFields()) {
System.out.println(declaredField);
}
}@Test
public void test1(){
Class<Person> clazz = Person.class;
// getMethods():获取当前运行时类及其父类声明为 public 访问权限的方法
Method[] methods = clazz.getMethods();
for (Method m : methods) {
System.out.println(m);
}
System.out.println();
// getDeclaredMethods():获取当前运行时类中声明的所有的方法(不包含父类中声明的方法)
for (Method declaredMethod : clazz.getDeclaredMethods()) {
System.out.println(declaredMethod);
}
}/**
* 获取构造器
*/
@Test
public void test1(){
Class<Person> clazz = Person.class;
// getConstructors():获取当前运行时类中声明为 public 的构造器
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
System.out.println();
// getDeclaredConstructors():获取当前运行时类中声明为 public 的构造器
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
}
/**
* 获取运行时类的父类
*/
@Test
public void test2() {
Class<Person> clazz = Person.class;
Class<? super Person> superclass = clazz.getSuperclass();
System.out.println(superclass);
}
/**
* 获取运行时类的带泛型的父类
*/
@Test
public void test3() {
Class<Person> clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
}
/**
* 获取运行时类的带泛型的父类的泛型
*/
@Test
public void test4() {
Class<Person> clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
// 获取泛型类型
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
// System.out.println(actualTypeArgument.getTypeName());
System.out.println(((Class) actualTypeArgument).getName());
}
}
/**
* 获取运行时类实现的接口
*/
@Test
public void test5(){
Class<Person> clazz = Person.class;
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println(anInterface);
}
System.out.println();
// 获取运行时类父类实现的接口
for (Class<?> anInterface : clazz.getSuperclass().getInterfaces()) {
System.out.println(anInterface);
}
}
/**
* 获取运行时类所在的包
*/
@Test
public void test6(){
Class<Person> clazz = Person.class;
Package pack = clazz.getPackage();
System.out.println(pack);
}
/**
* 获取运行时类声明的注解
*/
@Test
public void test7(){
Class<Person> clazz = Person.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotations);
}
}@Test
public void testFeild(){
try {
Class<Person> clazz = Person.class;
// 创建运行时类的对象
Person p = clazz.newInstance();
// 1.getDeclaredField(String name):获取运行时类中指明变量名的属性
Field name = clazz.getDeclaredField("name");
// 2.保证当前属性是可访问的
name.setAccessible(true);
// 3.获取或设置指定属性的值
name.set(p, "Tom");
System.out.println(name.get(p));
} catch (NoSuchFieldException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}@Test
public void testMethod(){
try {
Class<Person> clazz = Person.class;
// 创建运行时类的对象
Person p = clazz.newInstance();
/**
* 1.获取指定的某个方法
* getDeclaredMethod():参数1:指明获取的方法的名称,参数2:指明获取的方法的形参列表
*/
Method show = clazz.getDeclaredMethod("show", String.class);
show.setAccessible(true);
/**
* invoke():参数1:方法的调用者 参数2:给方法的形参赋值的实参
* invoke() 的返回值即为对应类中定义的方法的返回值
*/
Object returnValue = show.invoke(p, "CHN");
System.out.println(returnValue);
Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
// 如果调用的运行时类中的方法没有返回值,则此 invoke() 返回 null
// Object returnVal = showDesc.invoke(Person.class);
Object returnVal = showDesc.invoke(null);
System.out.println(returnVal);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}@Test
public void testConstructor(){
try {
Class<Person> clazz = Person.class;
// 创建运行时类的对象
Person p = clazz.newInstance();
// 1.获取指定的构造器
// getDeclaredConstructor():参数:指明构造器的参数列表
Constructor<Person> constructor = clazz.getDeclaredConstructor(String.class);
// 2.保证此构造器是可访问的
constructor.setAccessible(true);
// 3.调用此构造器创建运行时类的对象
Person tom = constructor.newInstance("Tom");
System.out.println(tom);
} catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
}使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原 始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
实现 Runnable 接口的方法创建多线程
// 被代理类
class MyThread implements Runnable{
}
// 代理类
class Thread implements Runnable{
}
public class Main{
public static void main(String[] args){
MyThread t = new MyThread();
Thread thread = new Thread(t);
// 启动线程;调用线程的 run()
thread.start();
}
}动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时 根据需要动态创建目标类的代理对象。
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
// proxyInstance:代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
// 当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
System.out.println(proxyInstance.getBelief());
proxyInstance.eat("四川麻辣烫");
System.out.println();
NickClothFactory nickClothFactory = new NickClothFactory();
ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nickClothFactory);
proxyClothFactory.produceCloth();
}
}
interface Human{
String getBelief();
void eat(String food);
}
/**
* 被代理类
*/
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly";
}
@Override
public void eat(String food) {
System.out.println("我喜欢吃" + food);
}
}
class HumanUtil{
public void method1(){
System.out.println("通用方法一");
}
public void method2(){
System.out.println("通用方法二");
}
}
class ProxyFactory{
/**
* 调用此方法,返回一个代理类的对象,解决问题一
* @param obj 被代理类的对象
* @return
*/
public static Object getProxyInstance(Object obj){
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
handler);
}
}
class MyInvocationHandler implements InvocationHandler{
/**
* 需要使用被代理类的对象进行赋值
*/
private Object obj;
public void bind(Object obj){
this.obj = obj;
}
/**
* 当通过代理类的对象,调用方法 a 时,就会自动的调用如下的方法:invoke()
* 将被代理类要执行的方法 a 的功能声明在 invoke() 中
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
HumanUtil humanUtil = new HumanUtil();
humanUtil.method1();
// method:即为代理类对象调用的方法,此方法也就做完了被代理对象要调用的方法
// obj:被代理类的对象
Object returnValue = method.invoke(obj, args);
humanUtil.method2();
// 上述方法的返回值就作为当前类中的 invoke() 的返回值
return returnValue;
}
}反射的动态性

举例一:
@Test
public void test1(){
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("我爱北京天安门");
}
};
r1.run();
System.out.println();
Runnable r2 = () -> System.out.println("我爱北京故宫");
r2.run();
}举例二:
@Test
public void test2() {
Comparator<Integer> com1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
int compare1 = com1.compare(12, 21);
System.out.println(compare1);
System.out.println();
// Lambda 表达式的写法
Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1, o2);
int compare2 = com2.compare(32, 21);
System.out.println(compare2);
System.out.println();
// 方法引用
Comparator<Integer> com3 = Integer::compare;
int compare3 = com3.compare(32, 21);
System.out.println(compare3);
}

总结六种情况:
@FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。
具体使用:
函数式接口 | 参数类型 | 返回值类型 | 用途 |
|---|---|---|---|
Consumer 消费型接口 | T | void | 对类型为 T 的对象应用操作,包含方法:void accept(T t) |
Supplier 供给型接口 | 无 | T | 返回类型为 T 的对象,包含方法:T get() |
Function<T, R>函数型接口 | T | R | 对类型为 T 的对象应用操作,并返回结果。结果为 R 类型的对象。包含方法:R apply(T t) |
Predicate 断定型接口 | T | boolean | 确定类型为 T 的对象是否满足某约束,并返回 boolean 值。包含方法:boolean test(T t) |
当需要对一个函数式接口实例化的时候,可以使用 Lambda 表达式
如果开发中需要定义一个函数式接口,首先看看已有的 JDK 提供的函数式接口是否提供了能满足需求的函数式接口,如果有,则直接调用即可,不需要自己再自定义了
方法引用可以看做是 Lambda 表达式深层次的表达。换句话说,方法引用就是 Lambda 表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法。
当要传递给 Lambda 体的操作,已经有实现的方法了,可以使用方法引用
类或对象 :: 方法名
如果给函数式接口提供实例,恰好满足方法引用的使用情境,就可以考虑给函数式接口提供实例,如果不熟悉方法引用,那么还可以使用 Lambda 表达式
// 情况一:对象 :: 实例方法
// Consumer 中的 void accept(T t)
// PrintStream 中的 void println(T t)
@Test
public void test1() {
Consumer<String> con1 = str -> System.out.println(str);
con1.accept("北京");
PrintStream ps = System.out;
Consumer<String> con2 = ps::println;
con2.accept("北京");
}
// Supplier 中的 T get()
// Employee 的 String getName()
@Test
public void test2() {
Employee emp = new Employee(1001, "Tom", 23, 5000);
Supplier<String> sup1 = () -> emp.getName();
System.out.println(sup1.get());
Supplier<String> sup2 = emp::getName;
System.out.println(sup2.get());
}
// 情况二:类 :: 静态方法
// Comparator 中的 int compare(T t1, T t2)
// Integer 中的 int compare(T t1, T t2)
@Test
public void test3() {
Comparator<Integer> com1 = (t1, t2) -> Integer.compare(t1, t2);
System.out.println(com1.compare(12, 21));
Comparator<Integer> com2 = Integer::compare;
System.out.println(com2.compare(32, 21));
}
// Function 中的 R apply(T t)
// Math 中的 Long round(Double d)
@Test
public void test4() {
Function<Long, Double> fun1 = d -> (double) Math.round(d);
// Function<Long, Double> fun2 = Math::round;
}
// 情况三:类 :: 实例方法
// Comparator中的int comapre(T t1,T t2)
// String中的int t1.compareTo(t2)
@Test
public void test5() {
Comparator<String> com1 = (s1, s2) -> s1.compareTo(s2);
Comparator<String> com2 = String::compareTo;
}
// BiPredicate 中的 boolean test(T t1, T t2);
// String 中的 boolean t1.equals(t2)
@Test
public void test6() {
BiPredicate<String, String> pre1 = (s1, s2) -> s1.equals(s2);
BiPredicate<String, String> pre2 = String::equals;
System.out.println(pre1.test("'", "a"));
System.out.println(pre1.test("'", "a"));
}
// Function 中的 R apply(T t)
// Employee 中的 String getName();
@Test
public void test7() {
Function<Employee, String> func1 = e -> e.getName();
System.out.println(func1.apply(new Employee(1001, "Jerry", 23, 6000)));
Function<Employee, String> func2 = Employee::getName;
System.out.println(func2.apply(new Employee(1001, "Jerry", 23, 6000)));
}和方法引用类似,函数式接口的抽象方法的参数列表和构造器的形参列表一致,抽象方法的返回值类型即为构造器所属的类的类型
// 构造器引用
// Supplier 中的 T get()
@Test
public void test1(){
Supplier<Employee> sup1 = () -> new Employee();
Supplier<Employee> sup2 = Employee::new;
System.out.println(sup2.get());
}
// Function 中的 R apply(T t)
@Test
public void test2(){
Function<Integer, Employee> func1 = id -> new Employee(id);
Employee employee = func1.apply(1001);
System.out.println(employee);
Function<Integer, Employee> func2 = Employee::new;
System.out.println(func2.apply(1002));
}
// BiFunction 中的 R apply(T t,U u)
@Test
public void test3(){
BiFunction<Integer, String, Employee> func1 = (id, name) -> new Employee(id, name);
BiFunction<Integer, String, Employee> func2 = Employee::new;
}// 数组引用
// Function 中的 R apply(T t)
@Test
public void test4(){
Function<Integer, String[]> func1 = length -> new String[length];
String[] arr1 = func1.apply(5);
System.out.println(Arrays.toString(arr1));
Function<Integer, String[]> func2 = String[]::new;
String[] arr2 = func2.apply(5);
System.out.println(Arrays.toString(arr2));
}/**
* 创建方式一:通过集合
*/
@Test
public void test1(){
List<Employee> employees = EmployeeData.getEmployees();
// default Stream<E> stream() : 返回一个顺序流
Stream<Employee> stream = employees.stream();
// default Stream<E> parallelStream() : 返回一个并行流
Stream<Employee> parallelStream = employees.parallelStream();
}
/**
* 创建方式二:通过数组
*/
@Test
public void test2(){
int[] arr = new int[]{1, 2, 3, 4, 5, 6};
// static <T> Stream<T> stream(T[] array): 返回一个流
IntStream stream = Arrays.stream(arr);
Employee e1 = new Employee(1001, "Tom");
Employee e2 = new Employee(1001, "Jerry");
Employee[] employees = {e1, e2};
Stream<Employee> employeeStream = Arrays.stream(employees);
}
/**
* 创建方式三:通过 Stream 的 of()
*/
@Test
public void test3(){
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
}
/**
* 创建方式四:创建无限流
*/
@Test
public void test4(){
// 迭代 public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
// 遍历前10个偶数
Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);
// 生成 public static<T> Stream<T> generate(Supplier<T> s)
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}为了解决 Java 中的空指针问题而生
Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表 这个值存在。或者仅仅保存 null,表示这个值不存在。原来用 null 表示一个值不 存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
Optional.of(T t) : 创建一个 Optional 实例,t必须非空;Optional.empty() : 创建一个空的 Optional 实例Optional.ofNullable(T t):t可以为nullboolean isPresent(): 判断是否包含对象void ifPresent(Consumer<? super T> consumer)**:**如果有值,就执行Consumer 接口的实现代码,并且该值会作为参数传给它。T get(): 如果调用对象包含值,返回该值,否则抛异常T orElse(T other):如果有值则将其返回,否则返回指定的other对象。T orElseGet(Supplier<? extends T> other):如果有值则将其返回,否则返回由 Supplier 接口实现提供的对象。T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果有值则将其返 回,否则抛出由Supplier接口实现提供的异常。@Test
public void test1(){
// empty():创建的 Optional 对象内部的 value = null
Optional<Object> op1 = Optional.empty();
// Optional 封装的数据是否包含数据
if (op1.isPresent()) {
System.out.println("数据为空");
}
// 如果 Optional 封装的数据 value 为空,则 get() 报错,否则,value 不为空时,返回 value
// System.out.println(op1.get());
System.out.println(op1);
System.out.println(op1.isPresent());
}
@Test
public void test2(){
String str = "hello";
// of(T t):封装数据 t 生成 Optional 对象,要求 t 必须非空,否则报错
Optional<String> op1 = Optional.of(str);
// get() 通常与 of() 方法搭配师用,用于获取内部的封装的数据 value
String str1 = op1.get();
System.out.println(str1);
}
@Test
public void test3(){
// ofNullable(T t):封装数据 t 赋给 Optional 内部的 value,不要求 t 非空
Optional<String> op1 = Optional.ofNullable("beijing");
// orElse(T t1):如果 Optional 内部的 value 非空,则返回此 value 值,如果 value 为空,则返回 t1
String str2 = op1.orElse("shanghai");
System.out.println(str2);
}能确保如下的方法执行中不会出现空指针异常
public String getGirlName2(Boy boy){
Optional<Boy> boyOptional = Optional.ofNullable(boy);
Boy boy1 = boyOptional.orElse(new Boy(new Girl("迪丽热巴")));
Girl girl = boy1.getGirl();
Optional<Girl> girlOptional = Optional.ofNullable(girl);
Girl girl1 = girlOptional.orElse(new Girl("古力娜扎"));
return girl1.getName();
}
@Test
public void test5(){
Boy boy = null;
boy = new Boy();
boy = new Boy(new Girl("苍老师"));
String girlName = getGirlName2(boy);
System.out.println(girlName);
}