【Java】坤础复习扫盲

写在前面

  • 根据以下两份,只摘出其中还不会或者不太熟练的部分,额外关注一下
  • Java基础入门(上).pdf

文件还没上传处理,先放着

  • Java基础入门(下).pdf

文件还没上传处理,先放着

由此始

Day1 Java基础语法

常见CMD命令

操作说明
盘符名称:盘符切换。E:回⻋,表⽰切换到E盘。
dir查看当前路径下的内容。
cd ⽬录进⼊单级⽬录。cd itheima
cd …回退到上⼀级⽬录。
cd ⽬录1\⽬录2…进⼊多级⽬录。cd itheima\JavaSE
cd \回退到盘符⽬录。
cls清屏。
exit退出命令提⽰符窗⼝。
  • CMD练习:qq.exe启动qq hhhhhhh

环境变量

  • 作⽤:如果我想要在CMD的任意⽬录下,都可以启动某⼀个软件,那么就可以把这个软件的路径配置到环境变量中的PATH⾥⾯。

  • 在启动软件的时候,操作系统会先在当前路径下找,如果在当前录课没有再到环境变量的路径中去找。如果都找不到就提⽰⽆法启动。

  • 在CMD中打开软件时,会先找当前路径,再找环境变量,在环境变量中是从上往下依次查找的,如果路径放在最上⾯查找的速度⽐较快。

Java版本

  • Java的版本向下兼容。新的版本只是在原有的基础上添加了⼀些新的功能⽽已。

  • 举例:

    • ⽤8版本开发的代码,⽤11版本能运⾏吗?必须可以的。
    • ⽤11版本开发的代码,⽤8版本能运⾏吗?不⼀定。
    • 如果11版本开发的代码,没有⽤到9-11的新特性,那么⽤8是可以运⾏的。
    • 如果11版本开发的代码,⽤到了9-11的新特性,那么⽤8就⽆法运⾏了。

Java三大平台

  • JavaSE、JavaME、JavaEE

JavaSE

是其他两个版本的基础

JavaME

Java语⾔的⼩型版,⽤于嵌⼊式消费类电⼦设备或者⼩型移动设备的开发。其中最为主要的还是⼩型移动设备的开发(⼿机)。渐渐的没落了,已经被安卓和IOS给替代了。但是,安卓也是可以⽤Java来开发的。

JavaEE

⽤于Web⽅向的⽹站开发(主要从事后台服务器的开发),在服务器领域,Java是当之⽆愧的⻰头⽼

⼤。

Java主要特性

  • ⾯向对象
  • 安全性
  • 多线程
  • 简单易⽤
  • 开源
  • 跨平台

Java语言跨平台的原理

  • 操作系统本⾝其实是不认识Java语⾔的。

  • 但是针对于不同的操作系统,Java提供了不同的虚拟机。

  • 虚拟机会把Java语⾔翻译成操作系统能看得懂的语⾔。

  • Java程序—javac编译→Class文件

    • →Windows版JVM虚拟机
    • →Linux版JVM虚拟机
    • →MacOS版JVM虚拟机

JRE和JDK

  • JVM(Java Virtual Machine),Java虚拟机

  • JRE(Java Runtime Environment),Java运⾏环境,包含了JVM和Java的核⼼类库(Java API)

  • JDK(Java Development Kit)称为Java开发⼯具,包含了JRE和开发⼯具

———————————————————————————————————————

| JDK |

| JVM 核心类库 开发工具 |

|————————————————————————————————————|

———————————————————————————————————————

| JRE |

| JVM 核心类库 运行工具 |

|————————————————————————————————————|

  • 只需安装JDK即可,它包含了java的运⾏环境和虚拟机

Day2 Java基础语法

Java语言数据类型的分类

  • 基本数据类型
  • 引用数据类型(在面向对象阶段深入)

基础数据类型的四类八种

数据类型关键字内存占用取值范围
整数byte1负的2的7次⽅ ~ 2的7次⽅-1(-128~127)
short2负的2的15次⽅ ~ 2的15次⽅-1(-32768~32767)
int4负的2的31次⽅ ~ 2的31次⽅-1
long8负的2的63次⽅ ~ 2的63次⽅-1
浮点数float41.401298e-45 ~ 3.402823e+38
double84.9000000e-324 ~ 1.797693e+308
字符char20-65535
布尔boolean1true, false
  • Java中整数默认是int类型,浮点数默认是double类型
  • 记忆以下几点:
    • byte 类型的取值范围:-128 ~ 127
    • int 类型的⼤概取值范围: -21亿多 ~ 21亿多
    • 整数类型和⼩数类型的取值范围⼤⼩关系: double > float > long > int > short > byte

最为常用的数据类型选择

  • 在定义变量的时候,要根据实际的情况来选择不同类型的变量
    • ⼈的年龄,可以选择 byte 类型
    • 地球的年龄,可以选择 long 类型
    • 如果整数类型中,不太确定范围,那么默认使⽤ int 类型
    • 如果⼩数类型中,不太确定范围,那么默认使⽤ double 类型
    • 如果要定义字符类型的变量,那么使⽤ char
    • 如果要定义布尔类型的变量,那么使⽤ boolean

定义8种基本数据类型变量

  • 注意点
    • 如果要定义 ⼀个整数类型的变量,不知道选择哪种数据类型了,默认使⽤ int。
    • 如果要定义 ⼀个⼩数类型的变量,不知道选择哪种数据类型了,默认使⽤ double。
    • 如果要定义⼀个 long 类型的变量,在数据值的后⾯需要加上 L 后缀。(⼤⼩写都可以,建议⼤写)
    • 如果要定义⼀个 float 类型的变量,在数据值的后⾯需要加上 F 后缀。(⼤⼩写都可以)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//1.定义byte类型的变量
//数据类型 变量名 = 数据值;
byte a = 10;

//2.定义short类型的变量
short b = 20;

//4.定义long类型的变量
long d = 123456789123456789L;

//5.定义float类型的变量
float e = 10.1F;

//8.定义boolean类型的变量
boolean h = true;

IDEA中层级结构介绍

结构分类

  • project(项⽬、⼯程)
  • module(模块)
  • package(包)
  • class(类)
结构介绍
  • 最基础的 project、module、class。

    • project(项⽬、⼯程)
      • 淘宝、京东、⿊⻢程序员⽹站都属于⼀个个项⽬,IDEA 中就是⼀个个的 Project。
    • module(模块)
      • 在⼀个项⽬中,可以存放多个模块,不同的模块可以存放项⽬中不同的业务功能代码。在⿊⻢程序员的官⽅⽹站中,⾄少包含了以下模块:
        • 论坛模块
        • 报名、咨询模块
        • 为了更好的管理代码,我们会把代码分别放在两个模块中存放。
    • package(包)
      • ⼀个模块中⼜有很多的业务,以⿊⻢程序员官⽅⽹站的论坛模块为例,⾄少包含了以下不同的业务。
      • 发帖
      • 评论
      • 为了把这些业务区分的更加清楚,就会⽤包来管理这些不同的业务。
    • class(类)
      • 就是真正写代码的地⽅。
  • 层级关系 project - module - package - class

    • 包含数量
      • project 中可以创建多个 module
      • module 中可以创建多个 package
      • package 中可以创建多个 class
      • 这些结构的划分,是为了⽅便管理类⽂件的。
IDEA代码步骤
  • 创建Project项目
  • 创建Module模块
  • 创建class类
  • 在类中编写代码
  • 完成编译运行
一些小Tips
  • 在IDEA中删除类,不走回收站,直接从硬盘中删掉
创建Module

PDF上部大约50页前后处

Day3 运算符

运算符与表达式

定义

  • 运算符:就是对常量或者变量进⾏操作的符号。⽐如: + - * /

  • 表达式:⽤运算符把常量或者变量连接起来的,符合Java语法的式⼦就是表达式。⽐如:a + b 这个整体就是表达式。⽽其中+是算术运算符的⼀种,所以这个表达式也称之为算术表达式

算术运算符注意点

  • /
    • 整数相除结果只能得到整数,如果结果想要是⼩数,必须要有⼩数参数
    • ⼩数直接参与运算,得到的结果有可能是不精确的

隐式转换

概念

  • 也叫⾃动类型提升

  • 就是把⼀个取值范围⼩的数据或者变量,赋值给另⼀个取值范围⼤的变量

  • 不需要额外写代码单独实现,程序⾃动完成

  • 简单记忆:小的给大的可以直接给

两种提升规则
  • 取值范围⼩的,和取值范围⼤的进⾏运算,⼩的会先提升为⼤的,再进⾏运算
  • byte、short、char三种类型的数据在运算的时候,都会直接先提升为int,然后再进⾏运算
  • 取值范围从⼩到⼤的关系:byte short int long float double

强制转换

概念

  • 如果要把⼀个取值范围⼤的数据或者变量赋值给另⼀个取值范围⼩的变量,不允许直接操作,需要加⼊强制转换

书写格式

  • ⽬标数据类型 变量名 = (⽬标数据类型)被强转的数据;

注意

  • 强制转换可能会导致数据发生错误(数据的精度丢失)

字符串的+

  • 字符串的连接符,产生一个新的字符串
  • 从左到右逐个执行
1
2
3
4
5
6
7
1 + "abc" + 1;    // "1abc1"
1 + 2 + "abc" + 2 + 1; // "3abc21"
// 第一步没有字符串参与,所以是整型相加

String name = "⿊默丁格";
System.out.println("我的名字是" + name);
// 字符串和变量相加→跟变量里的值进行拼接
  • 有字符串参与的步骤就是字符串拼接
  • 字符串只有+操作,没有其他操作

字符的+

规则

  • 当+操作中出现了字符,会拿着字符到计算机内置的ASCII码表中去查对应的数字,然后再进⾏计算
1
2
3
4
5
6
7
char c = 'a';
int result = c + 0;
System.out.println(result); //97
/*
'a' --- 97
'A' --- 65
*/

自增/自减运算符

  • 也就是a 和 a的区别问题了

  • 使⽤⽅式:

    • ++a(前缀递增)和a++(后缀递增)的主要区别在于它们增加变量的值的时间点以及返回的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   int a = 1;
int b = 1;

// 使用前缀递增
System.out.println("++a: " + (++a)); // 输出: ++a: 2
System.out.println("a after ++a: " + a); // 输出: a after ++a: 2

// 使用后缀递增
System.out.println("a++: " + (b++)); // 输出: a++: 1
System.out.println("b after a++: " + b); // 输出: b after a++: 2

// 在一个表达式中使用
int c = 3;
int result = ++c + c++; // 注意这里
// c=4 c=5
// 相当于int result = (++c) + (c++);
System.out.println("result: " + result); // 输出: result: 9
System.out.println("c after operations: " + c); // 输出: c after operations: 5

逻辑运算符

异或(^)

规则
  • 如果两边相同,结果为false,如果两边不同,结果为true
代码
1
2
3
4
5
//^    -//左右不相同,结果才是true,左右相同结果就是false
System.out.println(true ^ true); //false
System.out.println(false ^ false); //false
System.out.println(true ^ false); //true
System.out.println(false ^ true); //true

取反

  • 温馨提示:取反最多只⽤⼀个
1
2
System.out.println(!true);  //false
System.out.println(!!false); //注意点:取反最多只⽤⼀个

短路逻辑运算符

分类

  • &&
  • ||

逻辑核心

  • 当左边不能确定整个表达式的结果,右边才会执⾏
  • 当左边能确定整个表达式的结果,那么右边就不会执⾏了。从⽽提⾼了代码的运⾏效率

举例

  • ⽤⼾登录案例
    • ⽤⼾名正确 & 密码正确
      • 如果使⽤⼀个&,不管⽤⼾名是否正确都会去验证密码。
    • 思考:
      • 如果⽤⼾名输⼊正确了,那么我们再判断密码是否正确,是符合业务逻辑的。但是如果⽤⼾名输⼊错误了,那么现在还有必要去⽐较密码吗?没有不要了。
      • 如果使⽤⼀个&,那么左边和右边不管什么情况下,都会执⾏。
    • ⽤⼾名正确 && 密码正确
      • 如果⽤⼾名输⼊正确了,那么才会验证密码是否输⼊正确
      • 如果⽤⼾名输⼊错误了,那么就不会再去验证密码是否正确,最终的结果直接为false。从⽽提⾼了程序运⾏的效率

总结

  • &&和&、||和|的运行结果一样
  • 但是短路逻辑运算符可以提高程序的运行效率

三元运算符

格式

  • 关系表达式 ? 表达式1 : 表达式2;

  • 注意:三元运算符的最终结果⼀定要被使⽤,要么赋值给⼀个变量,要么直接打印出来

举例

1
2
3
// 求三个体重中的最大值
int temp = height1 > height2 ? height1 : height2;
int max = temp > height3 ? temp : height3;

运算符的优先级

  • 小括号优先于所有

Day4 判断和循环

switch的扩展知识

  • default的位置和省略情况

    • default可以放在任意位置,也可以省略
  • case穿透

    • 不写break会引发case穿透现象
  • switch在JDK12的新特性

1
2
3
4
5
6
7
int number = 10;
switch (number) {
case 1 -> System.out.println("⼀");
case 2 -> System.out.println("⼆");
case 3 -> System.out.println("三");
default -> System.out.println("其他");
}

举例

  • 键盘录入星期数,输出工作日、休息日。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//分析:
//1.键盘录⼊星期数
Scanner sc = new Scanner(System.in);
System.out.println("请输⼊星期");
int week = sc.nextInt(); //3

//2.利⽤switch进⾏匹配
// 利⽤case穿透简化代码
switch (week){
case 1:
case 2:
case 3:
case 4:
case 5:
System.out.println("⼯作⽇");
break;
case 6:
case 7:
System.out.println("休息⽇");
break;
default:
System.out.println("没有这个星期");
break;
}

// 利⽤JDK12简化代码书写
switch (week) {
case 1, 2, 3, 4, 5 -> System.out.println("⼯作⽇");
case 6, 7 -> System.out.println("休息⽇");
default -> System.out.println("没有这个星期");
}

Day5 循环高级和数组

Random

  • 作用:生成随机数
1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.Random;
// 导包的动作必须出现在类定义的上边。
Random r = new Random();
int number = r.nextInt(随机数的范围);
// 随机数范围的特点:从0开始,不包含指定值,即[0, n)


//2.创建对象
Random r = new Random();
//3.⽣成随机数
int number = r.nextInt(100);//包左不包右,包头不包尾
//0 ~ 99
System.out.println(number);

举例:逢七过

1
2
3
4
5
6
7
8
for (int i = 1; i <= 100; i++) {
//2.判断每⼀个数字,如果符合规则,就打印过,如果不符合规则就打印真实的数字
if(i % 10 == 7 || i / 10 % 10 == 7 || i % 7 == 0) {
System.out.println("过");
continue;
}
System.out.println(i);
}

举例:平方根

  • 需求:键盘录⼊⼀个⼤于等于2的整数 x ,计算并返回 x 的 平⽅根 。结果只保留整数部分 ,⼩数部分将被舍去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 10
// 1 * 1 = 1 < 10
// 2 * 2 = 4 < 10
// 3 * 3 = 9 < 10
// 4 * 4 = 16 > 10
//推断:10的平⽅根是在3~4之间。

// 思路
//在代码当中
//从1开始循环,拿着数字的平⽅跟原来的数字进⾏⽐较
//如果⼩于的,那么继续往后判断
//如果相等,那么当前数字就是平⽅根
//如果⼤于的,那么前⼀个数字就是平⽅跟的整数部分


//1.键盘录⼊⼀个整数
Scanner sc = new Scanner(System.in);
System.out.println("请输⼊⼀个整数");
int number = sc.nextInt();
//2.从1开始循环判断
//开始:1 结束: number
for (int i = 1; i <= number; i++) {
//⽤i * i 再跟number进⾏⽐较
if(i * i == number){
System.out.println(i + "就是" + number + "的平⽅根");
//⼀旦找到了,循环就可以停⽌了,后⾯的数字就不需要再找了,提⾼代码的运⾏效率。
break;
} else if(i * i > number){
System.out.println((i - 1) + "就是" + number + "平⽅根的整数部分");
break;
}
}

数组

概念

  • 指的是⼀种容器,可以同来存储同种数据类型的多个值。但是数组容器在存储数据的时候,需要结合隐式转换考虑

  • ⽐如:定义了⼀个int类型的数组。那么boolean、double类型的数据是不能存到这个数组中的,但是byte类型,short类型,int类型的数据是可以存到这个数组⾥⾯的

  • 建议:容器的类,和存储的数据类型保持⼀致

定义

  • 格式⼀:数据类型 [] 数组名

  • 格式⼆:数据类型 数组名 []

  • 都行

数组练习:打乱数据

  • 需求:定义⼀个数组,存⼊1~5。要求打乱数组中所有数据的顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1.定义数组存储1~5
int[] arr = {1, 2, 3, 4, 5};
// 2.循环遍历数组,从0索引开始打乱数据的顺序
Random r = new Random();
for (int i = 0; i < arr.length; i++) {
// ▷⽣成⼀个随机索引
int randomIndex = r.nextInt(arr.length);
// 拿着随机索引指向的元素 跟 i 指向的元素进⾏交换
int temp = arr[i];
arr[i] = arr[randomIndex];
arr[randomIndex] = temp;
}
// 当循环结束之后,那么数组中所有的数据已经打乱顺序了
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}

Day6 方法

方法重载

  • 概念:⽅法重载指同⼀个类中定义的多个⽅法之间的关系,满⾜下列条件的多个⽅法相互构成重载:

    • 多个⽅法在同⼀个类中
    • 多个⽅法具有相同的⽅法名
    • 多个⽅法的参数不相同类型不同或者数量不同
  • 注意:

    • 重载仅对应⽅法的定义,与⽅法的调⽤⽆关,调⽤⽅式参照标准格式
    • 重载仅针对同⼀个类中⽅法的名称与参数进⾏识别,与返回值⽆关,换句话说不能通过返回值来判定两个⽅法是否相互构成重载

重载-练习

  • 需求:使⽤⽅法重载的思想,设计⽐较两个整数是否相同的⽅法,兼容全整数类型(byte,short,int,long)

  • 思路:

    • 定义⽐较两个数字的是否相同的⽅法compare()⽅法,参数选择两个int型参数

    • 定义对应的重载⽅法,变更对应的参数类型,参数变更为两个long型参数

    • 定义所有的重载⽅法,两个byte类型与两个short类型参数

    • 完成⽅法的调⽤,测试运⾏结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class MethodTest {
public static void main(String[] args) {
// 调用方法
System.out.println(compare(10, 20));
System.out.println(compare((byte) 10, (byte) 20));
System.out.println(compare((short) 10, (short) 20));
System.out.println(compare(10L, 20L));
}

// int
public static boolean compare(int a, int b) {
System.out.println("int");
return a == b;
}

// byte
public static boolean compare(byte a, byte b) {
System.out.println("byte");
return a == b;
}

//short
public static boolean compare(short a, short b) {
System.out.println("short");
return a == b;
}

//long
public static boolean compare(long a, long b) {
System.out.println("long");
return a == b;
}
}

Day7 综合练习

练习三:验证码

  • 需求:定义⽅法实现随机产⽣⼀个5位的验证码
  • 验证码格式:⻓度为5,前四位是⼤写字⺟或者⼩写字⺟,最后⼀位是数字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.util.Random;

public class Test3 {
public static void main(String[] args) {
/* 需求:
定义⽅法实现随机产⽣⼀个5位的验证码
验证码格式:
⻓度为5
前四位是⼤写字⺟或者⼩写字⺟
最后⼀位是数字
*/

// 分析
// 1.大写字母和小写字母都放到数组中
char[] chs = new char[52];
for (int i = 0; i < chs.length; i ++) {
// ASCII表
if (i <= 25) {
// 添加小写字母
chs[i] = (char)(97 + i);
} else {
// 添加大写字母
chs[i] = (char)(65 + i - 26);
}
}

// 定义⼀个字符串类型的变量,⽤来记录最终的结果
String result = "";

// 2.随机抽取4次
// 随机抽取数组中的索引
Random r = new Random();
for (int i = 0; i < 4; i++) {
int randomIndex = r.nextInt(chs.length);
// 利⽤随机索引,获取对应的元素
// System.out.println(chs[randomIndex]);
result = result + chs[randomIndex];
}
// System.out.println(result);

// 3.随机抽取⼀个数字0~9
int number = r.nextInt(10);

// 拼接⽣成最终的结果
result = result + number;

// 打印最终结果
System.out.println(result);
}
}

练习八:抽奖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/* 解法一 */

import java.util.Random;

public class Test9 {
public static void main(String[] args) {
/* 需求:
⼀个⼤V直播抽奖,奖品是现⾦红包,分别有{2, 588 , 888, 1000, 10000}五个奖⾦。
请使⽤代码模拟抽奖,打印出每个奖项,奖项的出现顺序要随机且不重复。
打印效果如下:(随机顺序,不⼀定是下⾯的顺序)
888元的奖⾦被抽出
588元的奖⾦被抽出
10000元的奖⾦被抽出
1000元的奖⾦被抽出
2元的奖⾦被抽出*/

// 分析:

// 1.定义数组表⽰奖池
int[] arr = {2, 588, 888, 1000, 10000};

// 2.定义新数组⽤于存储抽奖的结果
int[] newArr = new int[arr.length];

// 3.抽奖
Random r = new Random();

//因为有5个奖项,所以这⾥要循环5次
for (int i = 0; i < 5; ) {
//获取随机索引
int randomIndex = r.nextInt(arr.length);
//获取奖项
int prize = arr[randomIndex];
//判断当前的奖项是否存在,如果存在则重新抽取,如果不存在,就表⽰是有效奖项
boolean flag = contains(newArr, prize);
if(!flag){
//把当前抽取到的奖项添加到newArr当中
newArr[i] = prize;
//添加完毕之后,移动索引
i++;
}
}

//4.遍历newArr
for (int i = 0; i < newArr.length; i++) {
System.out.println(newArr[i]);
}
}


// 判断prize在数组当中是否存在
// 存在:true
// 不存在:false
public static boolean contains(int[] arr,int prize){
for (int i = 0; i < arr.length; i++) {
if(arr[i] == prize){
return true;
}
}
return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* 解法二 */
import java.util.Random;

public class Test10 {
public static void main(String[] args) {
/* 需求:
⼀个⼤V直播抽奖,奖品是现⾦红包,分别有{2, 588 , 888, 1000, 10000}五个奖⾦。
请使⽤代码模拟抽奖,打印出每个奖项,奖项的出现顺序要随机且不重复。
打印效果如下:(随机顺序,不⼀定是下⾯的顺序)
888元的奖⾦被抽出
588元的奖⾦被抽出
10000元的奖⾦被抽出
1000元的奖⾦被抽出
2元的奖⾦被抽出*/

// 1.把奖池内所有奖项的顺序打乱
int[] arr = {2, 588, 888, 1000, 10000};
Random r = new Random();
for (int i = 0; i < arr.length; i ++) {
// 获取随机索引
int randomIndex = r.nextInt(arr.length);
// 拿着i与随机索引randomIndex上的值进行交换
int temp = arr[i];
arr[i] = arr[randomIndex];
arr[randomIndex] = temp;
}

// 2.遍历奖池,从0索引开始获取每一个奖项
for (int i = 0; i < arr.length; i ++) {
System.out.println(arr[i]);
}
}
}

Day8 面向对象

概念一些

    • 类的理解
      • 类是对现实⽣活中⼀类具有共同属性和⾏为的事物的抽象
      • 类是对象的数据类型,类是具有相同属性和⾏为的⼀组对象的集合
      • 简单理解:类就是对现实事物的⼀种描述
    • 类的组成
      • 属性:指事物的特征,例如:⼿机事物(品牌,价格,尺⼨)
      • ⾏为:指事物能执⾏的操作,例如:⼿机事物(打电话,发短信)
    • 类和对象的关系
      • 类:类是对现实⽣活中⼀类具有共同属性和⾏为的事物的抽象
      • 对象:是能够看得到摸的着的真实存在的实体
      • 简单理解:类是对事物的⼀种描述,对象则为具体存在的事物

对象内存图

  • 总结
  • 多个对象在堆内存中,都有不同的内存划分,成员变量存储在各⾃的内存区域中,成员⽅法多个对象共⽤的⼀份
  • 这个没太懂

封装

  • private修饰变量+getter&setter方法

构造方法

标准类制作

  1. 类名需要⻅名知意

  2. 成员变量使⽤private修饰

  3. 提供⾄少两个构造⽅法

    1. ⽆参构造⽅法
    2. 带全部参数的构造⽅法
  4. get和set⽅法

    • 提供每⼀个成员变量对应的setXxx()/getXxx()
  5. 如果还有其他⾏为,也需要写上

Day9 面向对象综合训练

文字版格斗游戏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
package com.itheima.test2;

import java.util.Random;

// 功能和人物信息等定义
public class Role {
private String name;
private int blood;
private char gender;
private String face;//长相是随机的


String[] boyfaces = {"风流俊雅", "气宇轩昂", "相貌英俊", "五官端正", "相貌平平", "一塌糊涂", "面目狰狞"};
String[] girlfaces = {"美奂绝伦", "沉鱼落雁", "婷婷玉立", "身材娇好", "相貌平平", "相貌简陋", "惨不忍睹"};


//attack 攻击描述:
String[] attacks_desc = {
"%s使出了一招【背心钉】,转到对方的身后,一掌向%s背心的灵台穴拍去。",
"%s使出了一招【游空探爪】,飞起身形自半空中变掌为抓锁向%s。",
"%s大喝一声,身形下伏,一招【劈雷坠地】,捶向%s双腿。",
"%s运气于掌,一瞬间掌心变得血红,一式【掌心雷】,推向%s。",
"%s阴手翻起阳手跟进,一招【没遮拦】,结结实实的捶向%s。",
"%s上步抢身,招中套招,一招【劈挂连环】,连环攻向%s。"
};


//injured 受伤描述:
String[] injureds_desc = {
"结果%s退了半步,毫发无损",
"结果给%s造成一处瘀伤",
"结果一击命中,%s痛得弯下腰",
"结果%s痛苦地闷哼了一声,显然受了点内伤",
"结果%s摇摇晃晃,一跤摔倒在地",
"结果%s脸色一下变得惨白,连退了好几步",
"结果『轰』的一声,%s口中鲜血狂喷而出",
"结果%s一声惨叫,像滩软泥般塌了下去"
};


public Role() {

public Role(String name, int blood, char gender) {
this.name = name;
this.blood = blood;
this.gender = gender;
//随机长相
setFace(gender);
}


public char getGender() {
return gender;
}

public void setGender(char gender) {
this.gender = gender;
}

public String getFace() {
return face;
}

public void setFace(char gender) {
Random r = new Random();
//长相是随机的
if (gender == '男') {
//从boyfaces里面随机长相
int index = r.nextInt(boyfaces.length);
this.face = boyfaces[index];
} else if (gender == '女') {
//从girlfaces里面随机长相
int index = r.nextInt(girlfaces.length);
this.face = girlfaces[index];
} else {
this.face = "面目狰狞";
}


}


public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getBlood() {
return blood;
}

public void setBlood(int blood) {
this.blood = blood;
}
}

//定义一个方法用于攻击别人
//思考:谁攻击谁?
//Role r1 = new Role();
//Role r2 = new Role();
//r1.攻击(r2);
//方法的调用者去攻击参数
public void attack(Role role) {
Random r = new Random();
int index = r.nextInt(attacks_desc.length);
String KungFu = attacks_desc[index];

//输出一个攻击的效果
System.out.printf(KungFu, this.getName(), role.getName());
System.out.println();

//计算造成的伤害 1 ~ 20
int hurt = r.nextInt(20) + 1;

//剩余血量
int remainBoold = role.getBlood() - hurt;
//对剩余血量做一个验证,如果为负数了,就修改为0
remainBoold = remainBoold < 0 ? 0 : remainBoold;
//修改一下挨揍的人的血量
role.setBlood(remainBoold);

//受伤的描述
//血量> 90 0索引的描述
//80 ~ 90 1索引的描述
//70 ~ 80 2索引的描述
//60 ~ 70 3索引的描述
//40 ~ 60 4索引的描述
//20 ~ 40 5索引的描述
//10 ~ 20 6索引的描述
//小于10的 7索引的描述
if (remainBoold > 90) {
System.out.printf(injureds_desc[0], role.getName());
}else if(remainBoold > 80 && remainBoold <= 90){
System.out.printf(injureds_desc[1], role.getName());
}else if(remainBoold > 70 && remainBoold <= 80){
System.out.printf(injureds_desc[2], role.getName());
}else if(remainBoold > 60 && remainBoold <= 70){
System.out.printf(injureds_desc[3], role.getName());
}else if(remainBoold > 40 && remainBoold <= 60){
System.out.printf(injureds_desc[4], role.getName());
}else if(remainBoold > 20 && remainBoold <= 40){
System.out.printf(injureds_desc[5], role.getName());
}else if(remainBoold > 10 && remainBoold <= 20){
System.out.printf(injureds_desc[6], role.getName());
}else{
System.out.printf(injureds_desc[7], role.getName());
}
System.out.println();


}


public void showRoleInfo() {
System.out.println("姓名为:" + getName());
System.out.println("血量为:" + getBlood());
System.out.println("性别为:" + getGender());
System.out.println("长相为:" + getFace());
}

}


// 应用部分
package com.itheima.test2;

public class GameTest {
public static void main(String[] args) {
//1.创建第一个角色
Role r1 = new Role("乔峰",100,'男');
//2.创建第二个角色
Role r2 = new Role("鸠摩智",100,'男');

//展示一下角色的信息
r1.showRoleInfo();
r2.showRoleInfo();

//3.开始格斗 回合制游戏
while(true){
//r1开始攻击r2
r1.attack(r2);
//判断r2的剩余血量
if(r2.getBlood() == 0){
System.out.println(r1.getName() + " K.O了" + r2.getName());
break;
}

//r2开始攻击r1
r2.attack(r1);
if(r1.getBlood() == 0){
System.out.println(r2.getName() + " K.O了" + r1.getName());
break;
}
}
}
}
  • 还有好几个有意思的,但是我懒了,后面实践有需要再看吧
  • 这些实践例子在上部的P192及以前

扩展:键盘录入

涉及方法

next() nextLine()

  • 可以接受任意数据,但是都会返回⼀个字符串

nextInt()

  • 只能接受整数

nextDouble()

  • 能接收整数和⼩数,但是都会看做⼩数返回。录⼊字⺟会报错

方法底层细节

第⼀个细节

  • next(),nextInt(),nextDouble()在接收数据的时候,会遇到空格,回⻋,制表符其中⼀个就会停⽌接收数据

第二个细节

  • next(),nextInt(),nextDouble()在接收数据的时候,会遇到空格,回⻋,制表符其中⼀个就会停⽌接收数据。但是这些符号 + 后⾯的数据还在内存中并没有接收。
  • 如果后⾯还有其他键盘录⼊的⽅法,会⾃动将这些数据接收
1
2
3
4
5
6
7
8
9
10
11
Scanner sc = new Scanner(System.in);
String s1 = sc.next();
String s2 = sc.next();
System.out.println(s1); // a
System.out.println(s2); // b

//此时值键盘录入一次a b(注意a和b之间用空格隔开)
//那么第一个next();会接收a,a后面是空格,那么就停止,所以打印s1是a
//但是空格+b还在内存中。
//第二个next会去掉前面的空格,只接收b
//所以第二个s2打印出来是b

第三个细节

  • nextLine()⽅法是把⼀整⾏全部接收完毕
1
2
3
4
5
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
System.out.println(s); // a b
//键盘录入a b(注意a和b之间用空格隔开)
//那么nextLine不会过滤前面和后面的空格,会把这一整行数据全部接收完毕。

混用引起的后果

1
2
3
4
5
Scanner sc = new Scanner(System.in);//①
int i = sc.nextInt();//②
String s = sc.nextLine();//③
System.out.println(i);//④
System.out.println(s);//⑤
  • 当代码运⾏到第⼆⾏,会让我们键盘录⼊,此时录⼊123。但是实际上我们录的是123+回⻋
  • ⽽nextInt是遇到空格,回⻋,制表符都会停⽌
  • 所以nextInt只能接受123
  • 回⻋还在内存中没有被接收。此时就被nextLine接收了
  • 所以,如果混⽤就会导致nextLine接收不到数据
  • 有时候就需要加一行nextLine()来吞回车

使用结论

  • 键盘录⼊分为两套:

    • next()、nextInt()、nextDouble()这三个配套使⽤。
    • 如果⽤了这三个其中⼀个,就不要⽤nextLine()。
  • nextLine()单独使⽤。

    • 如果想要整数,那么先接收,再使⽤Integer.parseInt进⾏类型转换
1
2
3
4
5
Scanner sc = new Scanner(System.in);
String s = sc.next();//键盘录入123
System.out.println("此时为字符串" + s);//此时123是字符串
int i = sc.nextInt();//键盘录入123
System.out.println("此时为整数:" + i);
1
2
3
4
5
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();//键盘录入123
System.out.println("此时为字符串" + s);//此时123是字符串
int i = Integer.parseInt(s);//想要整数再进行转换
System.out.println("此时为整数:" + i);

Day10 字符串

API

概念

  • API (Application Programming Interface) :应⽤程序编程接⼝

Java中的API

  • 指的就是 JDK 中提供的各种功能的Java类,这些类将底层的实现封装了起来

  • 不需要关⼼这些类是如何实现的,只需要学习这些类如何使⽤即可,我们可以通过帮助⽂档来学习这些API如何使⽤

  • 在API帮助文档中查找包、类、类在哪个包下、类的构造方法、类的成员方法

String类

概述

  • String 类代表字符串,Java 程序中的所有字符串⽂字(例如“abc”)都被实现为此类的实例
  • 也就是说,Java 程序中所有的双引号字符串,都是 String 类的对象
  • String 类在 java.lang 包下,所以使⽤的时候不需要导包

特点

  • 字符串不可变,它们的值在创建后不能被更改
  • 虽然 String 的值是不可变的,但是它们可以被共享
  • 字符串效果上相当于字符数组( char[] ),但是底层原理是字节数组( byte[] )

构造方法

方法名说明
public String()创建一个空白字符串对象,不含有任何内容
public String(char[] chs)根据字符数组的内容,来创建字符串对象
public String(byte[] bys)根据字节数组的内容,来创建字符串对象
String s = “abc”;直接赋值的方式创建字符串,内容就是abc
1
2
3
4
5
6
7
8
9
10
11
12
13
//public String():创建⼀个空⽩字符串对象,不含有任何内容
String s1 = new String();

//public String(char[] chs):根据字符数组的内容,来创建字符串对象
charchs = {'a', 'b', 'c'};
String s2 = new String(chs);

//public String(byte[] bys):根据字节数组的内容,来创建字符串对象
bytebys = {97, 98, 99};
String s3 = new String(bys);

//String s = “abc”; 直接赋值的⽅式创建字符串对象,内容就是abc
String s4 = "abc";

创建字符串对象两种方式的区别

  • 通过构造方法创建
    • 通过 new 创建的字符串对象,每⼀次 new 都会申请⼀个内存空间,虽然内容相同,但是地址值不同
  • 直接赋值方式创建
    • 以“”⽅式给出的字符串,只要字符序列相同(顺序和⼤⼩写),⽆论在程序代码中出现⼏次,JVM 都只会建⽴⼀个 String 对象,并在字符串池中维护

字符串的比较

==号的作用
  • ⽐较基本数据类型:⽐较的是具体的值
  • ⽐较引⽤数据类型:⽐较的是对象地址值
euqals方法的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 构造⽅法的⽅式得到对象
charchs = {'a', 'b', 'c'};
String s1 = new String(chs);
String s2 = new String(chs);

// 直接赋值的⽅式得到对象
// 其实地址是一样的吧
String s3 = "abc";
String s4 = "abc";

// ⽐较字符串对象地址是否相同
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s3 == s4);

// ⽐较字符串内容是否相同
System.out.println(s1.equals(s2));
System.out.println(s1.equals(s3));
System.out.println(s3.equals(s4));
  • 应用:用户登录案例
    • if (username.equals(rightUsername) && password.equals(rightPassword))

案例:遍历字符串案例

  • 键盘录入一个字符串,使用程序实现在控制台遍历该字符串
1
2
3
4
5
6
7
8
// 有两个方法:
// charAt():会根据索引获取对应的字符
// length(): 会返回字符串的⻓度

for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
System.out.println(c);
}

案例:反转字符串

1
2
3
4
5
6
7
8
9
10
String result = reverse(str);

public static String reverse(String str){//abc
// 核⼼思想:倒着遍历并进⾏拼接就可以了
// fori :正着遍历 forr:倒着遍历
for (int i = str.length() - 1; i >= 0; i--)
// i 依次表⽰字符串⾥⾯的每⼀个索引(倒着的)
// 我们就可以拿到⾥⾯的每⼀个字符并拼接
s = s + str.charAt(i);
return s;

案例:金额转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import java.util.Scanner;

public class StringDemo9 {
public static void main(String[] args) {

// 1.键盘录入一个金额
Scanner sc = new Scanner(System.in);
int money;
while (true) {
System.out.println("请录入一个金额");
money = sc.nextInt();
if (money >= 0 && money <= 9999999) {
break;
} else {
System.out.println("金额无效");
}
}

// 定义一个变量用来表示钱的大写
String moneyStr = "";

// 2.得到money里面的每一位数字,再转换成中文
while (true) { // 2135
// 从右往左获取数据,因为右侧是数据的个位
int ge = money % 10;
String capitalNumber = getCapitalNumber(ge);
// 把转换之后的大写拼接到moneyStr当中
moneyStr = capitalNumber + moneyStr;
// 第一次循环:“伍” + "" = “伍”
// 第二次循环:“叁” + “伍” = “叁伍”
// 去掉刚才获取的数据
money = money / 10;

// 如果数字上的每一位全部获取到了,那么money记录为0,循环停止
if (money == 0) {
break;
}
}

// 在前面补0.补齐7位
int count = 7 - moneyStr.length();
for (int i = 0; i < count; i ++) {
moneyStr = "零" + moneyStr;
}
System.out.println(moneyStr); // 零零零叁壹叁伍

// 插入单位
// 定义一个数组表示单位
String[] arr = {"佰", "拾", "万", "仟", "佰", "拾", "元"};
// 零 零 零 叁 壹 叁 伍
// 遍历moneyStr, 依次得到零 零 零 叁 壹 叁 伍
// 然后把arr的单位插入进去

String result = "";
for (int i = 0; i < moneyStr.length(); i ++) {
char c = moneyStr.charAt(i);
// 把大写数字和单位拼接到result中
result = result + c + arr[i];
}

// 打印最终结果
System.out.println(result);
}
// 定义一个方法把数字变成大写的中文
// 1 -- 壹
public static String getCapitalNumber(int number) {
// 定义数组,让数字跟大写的中文产生一个对应关系
String[] arr = {"零", "一", "二", "三", "肆", "伍", "陆", "柒", "捌", "玖"};
// 返回结果
return arr[number];
}
}

案例:手机号屏蔽

  • 需求:以字符串的形式从键盘接受一个手机号,将中间四位号码屏蔽

  • 最终效果:111****1111

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Test8手机号屏蔽 {
public static void main(String[] args) {
/*以字符串的形式从键盘接受一个手机号,将中间四位号码屏蔽
最终效果为:131****9468*/

//1.键盘录入一个手机号码
Scanner sc = new Scanner(System.in);
System.out.println("请输入手机号码");
String phoneNumber = sc.next();//13112349408

//2.截取手机号码中的前三位
String star = phoneNumber.substring(0, 3);

//3.截取手机号码中的最后四位
//此时我用substring方法,是用1个参数的,还是两个参数的?1个参数的会更好
//因为现在我要截取到最后,所以建议使用1个参数的。
String end = phoneNumber.substring(7);

//4.拼接
String result = star + "****" + end;

System.out.println(result);
}
}

案例:敏感词替换

  • 需求:键盘录入一个字符串,如果字符串中包含(TMD),则使用***替换
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test9敏感词替换 {
public static void main(String[] args) {
//1.定义一个变量表示骂人的话
String talk = "后裔你玩什么啊,TMD";

//2.把这句话中的敏感词进行替换
String result = talk.replace("TMD", "***");

//3.打印
System.out.println(talk);
System.out.println(result);
}
}
  • 扩展:需要替换的敏感词比较多怎么办
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Test10多个敏感词替换 {
public static void main(String[] args) {
//实际开发中,敏感词会有很多很多

//1.先键盘录入要说的话
Scanner sc = new Scanner(System.in);
System.out.println("请输入要说的话");
String talk = sc.next();//后裔你玩什么啊,TMD,GDX,ctmd,ZZ

//2.定义一个数组用来存多个敏感词
String[] arr = {"TMD","GDX","ctmd","ZZ","lj","FW","nt"};

//3.把说的话中所有的敏感词都替换为***

for (int i = 0; i < arr.length; i++) {
//i 索引
//arr[i] 元素 --- 敏感词
talk = talk.replace(arr[i],"***");
}

//4.打印结果
System.out.println(talk);//后裔你玩什么啊,,,,

}
}
  • 也就是从单一屏蔽词匹配变成匹配词列表,对于输入语句进行遍历查找替换

案例:身份证信息查看

  • 身份证的每一位都是有固定的含义:

    • 1、2位:省份
    • 3、4位:城市
    • 5、6位:区县
    • 7-14位:出生年、月、日
    • 15、16位:所在地派出所
    • 17位:性别(奇数男性,偶数女性)
    • 18位:个人信息码(随机产生)
  • 要求打印内容方式如下:

    • 人物信息为:
    • 出生年月日:XXXX年X月X日
    • 性别为:男/女
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.itheima.stringdemo;

public class StringDemo11 {
public static void main(String[] args) {
//1.定义一个字符串记录身份证号码
String id = "321281202001011234";

//2.获取出生年月日
String year = id.substring(6, 10);
String month = id.substring(10, 12);
String day = id.substring(12, 14);


System.out.println("人物信息为:");
System.out.println("出生年月日:" + year + "年" + month + "月" + day + "日");

//3.获取性别
char gender = id.charAt(16);//'3' ---> 3
//利用ASCII码表进行转换
//'0' ---> 48
//'1' ---> 49
//'2' ---> 50
//'3' ---> 51
//'4' ---> 52
//'5' ---> 53
//'6' ---> 54
//'7' ---> 55
//'8' ---> 56
//'9' ---> 57

int num = gender - 48;
if(num % 2 == 0){
System.out.println("性别为:女");
}else{
System.out.println("性别为:男");
}
}
}

StringBuilder

  • 可以看成是一个容器,创建之后里面的内容是可变的
  • 在拼接字符串和反转字符串的时候会使用到
1
2
3
4
5
6
7
8
9
10
// 基本使用
StringBuilder sb = new StringBuilder("abc");
sb.reverse(); // 反转
int len = sb.length(); // 获取长度
System.out.println(len); // 并打印
System.out.println(sb);
/*
因为StringBuilder是Java已经写好的类,java在底层对他做了一些特殊处理。
打印对象不是地址值而是属性值。
*/

链式编程

1
2
3
4
5
6
7
8
9
10
11
// 链式编程
// 1.创建对象
StringBuilder sb = new StringBuilder();

// 2.添加字符串
sb.append("aaa").append("bbb").append("ccc").append("ddd");
System.out.println(sb); //aaabbbcccddd

// 3.再把StringBuilder变回字符串
String str = sb.toString();
System.out.println(str); //aaabbbcccddd

练习:对称字符串

  • 需求:键盘接受一个字符串,程序判断出该字符串是否是对称字符串,并在控制台打印是或不是
  • 对称字符串:123321、111
  • 非对称字符串:123123
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//使用StringBuilder的场景:
//1.字符串的拼接
//2.字符串的反转

//1.键盘录入一个字符串
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串");
String str = sc.next();

//2.反转键盘录入的字符串
String result = new StringBuilder().append(str).reverse().toString();

//3.比较
if(str.equals(result)){
System.out.println("当前字符串是对称字符串");
}else{
System.out.println("当前字符串不是对称字符串");

练习:拼接字符串

  • 需求:定义一个方法,把 int 数组中的数据按照指定的格式拼接成一个字符串返回。调用该方法,并在控制台输出结果。
  • 例如:数组为int[] arr = {1,2,3};
    • 执行方法后的输出结果为:[1, 2, 3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// main
//1.定义数组
int[] arr = {1,2,3};

//2.调用方法把数组变成字符串
String str = arrToString(arr);

System.out.println(str);

// arrToString
StringBuilder sb = new StringBuilder();
sb.append("[");

for (int i = 0; i < arr.length; i++) {
if(i == arr.length - 1){
sb.append(arr[i]);
}else{
sb.append(arr[i]).append(", ");
}
}
sb.append("]");

return sb.toString();

StringJoiner

  • StringJoiner跟StringBuilder一样,也可以看成是一个容器,创建之后里面的内容是可变的。
  • 作用:提高字符串的操作效率,而且代码编写特别简洁,但是目前市场上很少有人用。
  • JDK8出现的
1
2
3
4
5
6
7
8
9
10
// 基本使用

//1.创建一个对象,并指定中间的间隔符号
StringJoiner sj = new StringJoiner("---");

//2.添加元素
sj.add("aaa").add("bbb").add("ccc");

//3.打印结果
System.out.println(sj);//aaa---bbb---ccc
1
2
3
4
5
6
7
8
9
10
11
12
//1.创建对象
StringJoiner sj = new StringJoiner(", ","[","]");

//2.添加元素
sj.add("aaa").add("bbb").add("ccc");
int len = sj.length();
System.out.println(len);//15

//3.打印
System.out.println(sj);//[aaa, bbb, ccc]
String str = sj.toString();
System.out.println(str);//[aaa, bbb, ccc]

关于字符串的小扩展

字符串存储的内存原理

  • String s = “abc”;直接赋值
    • 特点:
      • 此时字符串abc是存在字符串常量池中的。
      • 先检查字符串常量池中有没有字符串abc,如果有,不会创建新的,而是直接复用。如果没有abc,才会创建一个新的。
      • 所以,直接赋值的方式,代码简单,而且节约内存。

new出来的字符串

  • 看到new关键字,一定是在堆里面开辟了一个小空间

  • String s1 = new String(“abc”);

  • String s2 = “abc”;

  • s1记录的是new出来的,在堆里面的地址值

  • s2是直接赋值的,所以记录的是字符串常量池中的地址值

==号比较的是什么

  • 如果比较的是基本数据类型:比的是具体的数值是否相等。

  • 如果比较的是引用数据类型:比 的是地址值是否相等。

  • 结论:==只能用于比较基本数据类型。不能比较引用数据类型。

Day11 集合&学生管理系统

ArrayList

  • 集合和数组的优势对比:
  1. 长度可变
  2. 添加数据的时候不需要考虑索引,默认将数据添加到末尾

构造

public ArrayList()

成员方法

  • public boolean add(要添加的元素)将指定的元素追加到此集合的末尾

  • public boolean remove(要删除的元素)删除指定元素,返回值表示是否删除成功

  • public E remove(int index)删除指定索引处的元素,返回被删除的元素

  • public E set(int index,E element)修改指定索引处的元素,返回被修改的元素

  • public E get(int index)返回指定索引处的元素

  • public int size()返回集合中的元素的个数

遍历

1
2
3
4
5
6
7
8
9
10
11
12
for (int i = 0; i < list.size(); i++) {
//i 依次表示集合里面的每一个索引
if(i == list.size() - 1){
//最大索引
System.out.print(list.get(i));
}else{
//非最大索引
System.out.print(list.get(i) + ", ");
}
}
System.out.print("]");
}

案例:查找用户索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 //1.创建集合对象
ArrayList<User> list = new ArrayList<>();

//2.创建用户对象
User u1 = new User("heima001", "zhangsan", "123456");
User u2 = new User("heima002", "lisi", "1234");
User u3 = new User("heima003", "wangwu", "1234qwer");

//3.把用户对象添加到集合当中
list.add(u1);
list.add(u2);
list.add(u3);

//4.调用方法,通过id获取对应的索引
int index = getIndex(list, "heima001");

System.out.println(index);

//1.我要干嘛? 根据id查找对应的学生信息
//2.我干这件事情需要什么才能完成? 集合 id
//3.方法的调用处是否需要继续使用方法的结果?
//要用必须返回,不要用可以返回也可以不返回
//明确说明需要有返回值 int
public static int getIndex(ArrayList<User> list, String id) {
//遍历集合得到每一个元素
for (int i = 0; i < list.size(); i++) {
User u = list.get(i);
String uid = u.getId();
if(uid.equals(id)){
return i;
}
}
//因为只有当集合里面所有的元素都比较完了,才能断定id是不存在的。
return -1;
}

学生管理系统

  • 案例需求

  • 综合案例:学生管理系统。该系统主要功能如下:

    • 添加学生:通过键盘录入学生信息,添加到集合中
    • 删除学生:通过键盘录入要删除学生的学号,将该学生对象从集合中删除
    • 修改学生:通过键盘录入要修改学生的学号,将该学生对象其他信息进行修改
    • 查看学生:将集合中的学生对象信息进行展示
    • 退出系统:结束程序
  • 实现步骤

    • 定义学生类,包含以下成员变量

    • ​ private String sid // 学生id

    • ​ private String name // 学生姓名

    • ​ private String age // 学生年龄

    • ​ private String address // 学生所在地

    • 学生管理系统主界面的搭建步骤

      • 用输出语句完成主界面的编写 用Scanner实现键盘输入 用switch语句完成选择的功能 用循环完成功能结束后再次回到主界面
    • 学生管理系统的添加学生功能实现步骤

      • 定义一个方法,接收ArrayList集合 方法内完成添加学生的功能 ①键盘录入学生信息 ②根据录入的信息创建学生对象 ③将学生对象添加到集合中 ④提示添加成功信息 在添加学生的选项里调用添加学生的方法
    • 学生管理系统的查看学生功能实现步骤

      • 定义一个方法,接收ArrayList集合 方法内遍历集合,将学生信息进行输出 在查看所有学生选项里调用查看学生方法
    • 学生管理系统的删除学生功能实现步骤

      • 定义一个方法,接收ArrayList集合 方法中接收要删除学生的学号 遍历集合,获取每个学生对象 使用学生对象的学号和录入的要删除的学号进行比较,如果相同,则将当前学生对象从集合中删除 在删除学生选项里调用删除学生的方法
    • 学生管理系统的修改学生功能实现步骤

      • 定义一个方法,接收ArrayList集合 方法中接收要修改学生的学号 通过键盘录入学生对象所需的信息,并创建对象 遍历集合,获取每一个学生对象。并和录入的修改学生学号进行比较.如果相同,则使用新学生对象替换当前学生对象 在修改学生选项里调用修改学生的方法
    • 退出系统

      • 使用System.exit(0);退出JVM
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// 选几处值得看的代码摘录
// 菜单选择部分
switch (choose) {
case "1" -> addStudent(list);
case "2" -> deleteStudent(list);
case "3" -> updateStudent(list);
case "4" -> queryStudent(list);
case "5" -> {
System.out.println("退出");
//break loop;
System.exit(0);//停止虚拟机运行
}
default -> System.out.println("没有这个选项");
}

// 添加学生部分
boolean flag = contains(list, id);
if(flag){
//表示id已经存在,需要重新录入
System.out.println("id已经存在,请重新录入");
}else{
//表示id不存在,表示可以使用
s.setId(id);
break;
}

// 删除学生部分
int index = getIndex(list, id);
//对index进行判断
//如果-1,就表示不存在,结束方法,回到初始菜单
if(index >= 0){
//如果大于等于0的,表示存在,直接删除
list.remove(index);
System.out.println("id为:" + id + "的学生删除成功");
}else{
System.out.println("id不存在,删除失败");
}

// 修改学生信息部分
String id = sc.next();

int index = getIndex(list, id);

if(index == -1){
System.out.println("要修改的id" + id + "不存在,请重新输入");
return;
}

//当代码执行到这里,表示什么?表示当前id是存在的。
//获取要修改的学生对象
Student stu = list.get(index);

//输入其他的信息并修改
System.out.println("请输入要修改的学生姓名");
String newName = sc.next();
stu.setName(newName);

System.out.println("请输入要修改的学生年龄");
int newAge = sc.nextInt();
stu.setAge(newAge);

System.out.println("请输入要修改的学生家庭住址");
String newAddress = sc.next();
stu.setAddress(newAddress);

System.out.println("学生信息修改成功");

//判断id在集合中是否存在
public static boolean contains(ArrayList<Student> list, String id) {
//循环遍历集合得到里面的每一个学生对象
/*for (int i = 0; i < list.size(); i++) {
//拿到学生对象后,获取id并进行判断
Student stu = list.get(i);
String sid = stu.getId();
if(sid.equals(id)){
//存在,true
return true;
}
}
// 不存在false
return false;*/
return getIndex(list,id) >= 0;
}

//通过id获取索引的方法
public static int getIndex(ArrayList<Student> list, String id){
//遍历集合
for (int i = 0; i < list.size(); i++) {
//得到每一个学生对象
Student stu = list.get(i);
//得到每一个学生对象的id
String sid = stu.getId();
//拿着集合中的学生id跟要查询的id进行比较
if(sid.equals(id)){
//如果一样,那么就返回索引
return i;
}
}
//当循环结束之后还没有找到,就表示不存在,返回-1.
return -1;
}
}

Day12 学生管理系统升级版

需求文档←

  • 需求
    • 为学生管理系统书写一个登陆、注册、忘记密码的功能。
    • 只有用户登录成功之后,才能进入到学生管理系统中进行增删改查操作。

登录界面:

1
2
System.out.println("欢迎来到学生管理系统");
System.out.println("请选择操作1登录 2注册 3忘记密码");

用户类:

属性:用户名、密码、身份证号码、手机号码

注册功能:

  1. 用户名需要满足以下要求:
  • 验证要求:
    • 用户名唯一
    • 用户名长度必须在3~15位之间
    • 只能是字母加数字的组合,但是不能是纯数字
  1. 密码键盘输入两次,两次一致才可以进行注册。

  2. 身份证号码需要验证。

验证要求:

长度为18位

不能以0为开头

前17位,必须都是数字

最后一位可以是数字,也可以是大写X或小写x

  1. 手机号验证。

验证要求:

长度为11位

不能以0为开头

必须都是数字

登录功能:

  1. 键盘录入用户名
  2. 键盘录入密码
  3. 键盘录入验证码

验证要求:

用户名如果未注册,直接结束方法,并提示:用户名未注册,请先注册

判断验证码是否正确,如不正确,重新输入

再判断用户名和密码是否正确,有3次机会

忘记密码:

  1. 键盘录入用户名,判断当前用户名是否存在,如不存在,直接结束方法,并提示:未注册
  2. 键盘录入身份证号码和手机号码
  3. 判断当前用户的身份证号码和手机号码是否一致,
    1. 如果一致,则提示输入密码,进行修改。

    2. 如果不一致,则提示:账号信息不匹配,修改失败。

验证码规则:

  1. 长度为5
  2. 由4位大写或者小写字母和1位数字组成,同一个字母可重复
  3. 数字可以出现在任意位置

比如:aQa1K

Day13 面向对象进阶(static&继承)

学习目标

  • 能够掌握static关键字修饰的变量调用方式
  • 能够掌握static关键字修饰的方法调用方式
  • 知道静态代码块的格式和应用场景
  • 能够写出类的继承格式
  • 能够说出继承的特点
  • 能够区分this和super的作用
  • 能够说出方法重写的概念
  • 能够说出方法重写的注意事项

封装

  1. 使用 private 关键字来修饰成员变量。
  2. 使用public修饰getter和setter方法

static

  • 有static修饰成员变量,说明这个成员变量是属于类的,这个成员变量称为类变量或者静态成员变量。 直接用 类名访问即可。因为类只有一个,所以静态成员变量在内存区域中也只存在一份。所有的对象都可以共享这个变量
  • 与静态成员变量一样,静态方法也是直接通过类名.方法名称即可访问

继承

1
2
3
class 父类 {...}

class 子类 extends 父类 {...}
  • 注意:Java是单继承的,一个类只能继承一个直接父类,跟现实世界很像,但是Java中的子类是更加强大的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// 有点忘了细节所以记一下

// 父类Human类
public class Human {
// 合理隐藏
private String name ;
private int age ;
// 合理暴露
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

// 子类Teacher类
public class Teacher extends Human {
// 工资
private double salary ;
// 特有方法
public void teach(){
System.out.println("老师在认真教技术!");
}

public double getSalary() {
return salary;
}

public void setSalary(double salary) {
this.salary = salary;
}
}

// 子类Student
public class Student extends Human{

}

// 子类BanZhuren类
public class Teacher extends Human {
// 工资
private double salary ;
// 特有方法
public void admin(){
System.out.println("班主任强调纪律问题!");
}
public double getSalary() {
return salary;
}

public void setSalary(double salary) {
this.salary = salary;
}
}

// 测试类
public class Test {
public static void main(String[] args) {
Teacher dlei = new Teacher();
dlei.setName("播仔");
dlei.setAge("31");
dlei.setSalary(1000.99);
System.out.println(dlei.getName());
System.out.println(dlei.getAge());
System.out.println(dlei.getSalary());
dlei.teach();
BanZhuRen linTao = new BanZhuRen();
linTao.setName("灵涛");
linTao.setAge("28");
linTao.setSalary(1000.99);
System.out.println(linTao.getName());
System.out.println(linTao.getAge());
System.out.println(linTao.getSalary());
linTao.admin();

Student xugan = new Student();
xugan.setName("播仔");
xugan.setAge("31");
//xugan.setSalary(1000.99); // xugan没有薪水属性,报错!
System.out.println(xugan.getName());
System.out.println(xugan.getAge());
}
}

子类不能继承的内容

  • 子类不能继承父类的构造方法。

  • 值得注意的是子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Demo03 {
public static void main(String[] args) {
Zi z = new Zi();
System.out.println(z.num1);
// System.out.println(z.num2); // 私有的子类无法使用
// 通过getter/setter方法访问父类的private成员变量
System.out.println(z.getNum2());

z.show1();
// z.show2(); // 私有的子类无法使用
}
}

class Fu {
public int num1 = 10;
private int num2 = 20;

public void show1() {
System.out.println("show1");
}

private void show2() {
System.out.println("show2");
}

public int getNum2() {
return num2;
}

public void setNum2(int num2) {
this.num2 = num2;
}
}

class Zi extends Fu {
}

继承后的特点——成员变量

成员变量重名
  • 子父类中出现了同名的成员变量时,子类会优先访问自己对象中的成员变量
  • 如果此时想访问父类成员变量→使用super关键字
super访问父类成员变量
  • 子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用super 关键字,修饰父类成员变量,类似于之前学过的 this

  • 注意:super代表的是父类对象的引用,this代表的是当前对象的引用

  • super.父类成员变量名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Fu {
// Fu中的成员变量。
int num = 5;
}

class Zi extends Fu {
// Zi中的成员变量
int num = 6;
public void show() {
int num = 1;
// 访问方法中的num
System.out.println("method num=" + num);
// 访问子类中的num
System.out.println("Zi num=" + this.num);
// 访问父类中的num
System.out.println("Fu num=" + super.num);
}
}

class Demo04 {
public static void main(String[] args) {
// 创建子类对象
Zi1 z = new Zi1();
// 调用子类中的show方法
z1.show();
}
}

演示结果:
method num=1
Zi num=6
Fu num=5
  • 如何访问父类的私有成员变量→在父类中提供公共的getXxx方法和setXxx方法
成员方法重名
  • 如果子类父类中出现重名的成员方法,则创建子类对象调用该方法的时候,子类对象会优先调用自己的方法

方法重写

  • 子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现

使用场景与案例

  • 子类继承了父类的方法,但是子类觉得父类的这方法不足以满足自己的需求,子类重新写了一个与父类同名的方法,以便覆盖父类的该方法

@Override重写注解

  • 这个注解标记的方法,就说明这个方法必须是重写父类的方法,否则编译阶段报错。
  • 建议重写都加上这个注解,一方面可以提高代码的可读性,一方面可以防止重写出错
1
2
3
4
5
6
7
8
public class Cat extends Animal {
// 声明不变,重新实现
// 方法名称与父类全部一样,只是方法体中的功能重写写了!
@Override
public void cry(){
System.out.println("我们一起学猫叫,喵喵喵!喵的非常好听!");
}
}

注意事项

  1. 方法重写是发生在子父类之间的关系。
  2. 子类方法覆盖父类方法,必须要保证权限大于等于父类权限。
  3. 子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样

继承后的特点——构造方法

  1. 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
  2. 构造方法的作用是初始化对象成员变量数据的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。(先有爸爸,才能有儿子
  • 继承后子类构方法器特点:子类所有构造方法的第一行都会默认先调用父类的无参构造方法

super()和this()

1
2
3
4
5
6
7
8
9
this.成员变量            --    本类的
super.成员变量 -- 父类的

this.成员方法名() -- 本类的
super.成员方法名() -- 父类的


super(...) -- 调用父类的构造方法,根据参数匹配确认
this(...) -- 调用本类的其他构造方法,根据参数匹配确认

super()

  • 注意:
    • -子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。
    • super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
    • super(…)是根据参数去确定调用父类哪个构造方法的。

this()

  • 默认是去找本类中的其他构造方法,根据参数来确定具体调用哪一个构造方法。
  • 为了借用其他构造方法的功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.itheima._08this和super调用构造方法;
/**
* this(...):
* 默认是去找本类中的其他构造方法,根据参数来确定具体调用哪一个构造方法。
* 为了借用其他构造方法的功能。
*/
public class ThisDemo01 {
public static void main(String[] args) {
Student xuGan = new Student();
System.out.println(xuGan.getName()); // 输出:徐干
System.out.println(xuGan.getAge());// 输出:21
System.out.println(xuGan.getSex());// 输出: 男
}
}

class Student{
private String name ;
private int age ;
private char sex ;

public Student() {
// 很弱,我的兄弟很牛逼啊,我可以调用其他构造方法:Student(String name, int age, char sex)
this("徐干",21,'男');
}

public Student(String name, int age, char sex) {
this.name = name ;
this.age = age ;
this.sex = sex ;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public char getSex() {
return sex;
}

public void setSex(char sex) {
this.sex = sex;
}
}

小结

  • 子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。
  • super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
  • super(…)和this(…)是根据参数去确定调用父类哪个构造方法的。
  • super(…)可以调用父类构造方法初始化继承自父类的成员变量的数据。
  • this(…)可以调用本类中的其他构造方法

继承的特点

  1. Java只支持单继承,不支持多继承。
  2. 一个类可以有多个子类。
  3. 可以多层继承。
1
2
3
class A {}
class C1 extends A {}
class D extends C1 {}

分享书写技巧:

​ 1.在大脑中要区分谁是父,谁是子

​ 2.把共性写到父类中,独有的东西写在子类中

​ 3.开始编写标准Javabean(从上往下写)

​ 4.在测试类中,创建对象并赋值调用

Day14 面向对象进阶(多态&包&final&权限修饰符&代码块)

学习目标

  • 能够说出使用多态的前提条件
  • 理解多态的向上转型
  • 理解多态的向下转型
  • 能够知道多态的使用场景
  • 包的作用
  • public和private权限修饰符的作用
  • 描述final修饰的类的特点
  • 描述final修饰的方法的特点
  • 描述final修饰的变量的特点

多态

  • 格式
1
2
父类类型 变量名 = new 子类/实现类构造器;
变量名.方法名();
  • 前提

    • 有继承关系,子类对象是可以赋值给父类类型的变量。
    • 例如Animal是一个动物类型,而Cat是一个猫类型。Cat继承了Animal,Cat对象也是Animal类型,自然可以赋值给父类类型的变量
  • 有了多态之后,方法的形参就可以定义为共同的父类Person

  • 注意

    • 当一个方法的形参是一个,我们可以传递这个类所有的子类对象
    • 当一个方法的形参是一个接口,我们可以传递这个接口所有的实现类对象
    • 而且多态还可以根据传递的不同对象调用不同类中的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 父类:
public class Person {
private String name;
private int age;

空参构造
带全部参数的构造
get和set方法

public void show(){
System.out.println(name + ", " + age);
}
}

// 子类1:
public class Administrator extends Person {
@Override
public void show() {
System.out.println("管理员的信息为:" + getName() + ", " + getAge());
}
}

// 子类2:
public class Student extends Person{

@Override
public void show() {
System.out.println("学生的信息为:" + getName() + ", " + getAge());
}
}

// 子类3:
public class Teacher extends Person{

@Override
public void show() {
System.out.println("老师的信息为:" + getName() + ", " + getAge());
}
}

// 测试类:
public class Test {
public static void main(String[] args) {
//创建三个对象,并调用register方法

Student s = new Student();
s.setName("张三");
s.setAge(18);

Teacher t = new Teacher();
t.setName("王建国");
t.setAge(30);

Administrator admin = new Administrator();
admin.setName("管理员");
admin.setAge(35);

register(s);
register(t);
register(admin);

}

//这个方法既能接收老师,又能接收学生,还能接收管理员
//只能把参数写成这三个类型的父类
public static void register(Person p){
p.show();
}
}
  • 定义

    • 多态是指同一行为,具有多个不同表现形式
  • 前提

    • 有继承或者实现关系
    • 方法的重写【意义体现:不重写,无意义】
    • 父类引用指向子类对象【格式体现】

    父类类型:指子类对象继承的父类类型,或者实现的父接口类型。

  • 运行特点

    • 调用成员变量时
      • 编译看左边,运行看左边
    • 调用成员方法时
      • 编译看左边,运行看右边
1
2
3
4
5
6
7
8
9
10
11
Fu f = new Zi();


//编译看左边的父类中有没有name这个属性,没有就报错
//在实际运行的时候,把父类name属性的值打印出来
System.out.println(f.name);


//编译看左边的父类中有没有show这个方法,没有就报错
//在实际运行的时候,运行的是子类中的show方法
f.show();
  • 弊端
    • 多态编译阶段是看左边父类类型
    • 如果子类有些独有的功能,此时多态的写法就无法访问子类独有功能了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Animal{
public void eat()
System.out.println("动物吃东西!")

}
class Cat extends Animal { public void eat() { System.out.println("吃鱼"); }
public void catchMouse() { System.out.println("抓老鼠"); }
}

class Dog extends Animal { public void eat() { System.out.println("吃骨头"); }
}

class Test{
public static void main(String[] args){
Animal a = new Cat();
a.eat();
a.catchMouse();//编译报错,编译看左边,Animal没有这个方法
}
}

引用类型转换

  • 当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误
  • 想要调用子类特有的方法,必须做向下转型
  • 基本数据类型转换
    • 自动转换: 范围小的赋值给范围大的.自动完成:double d = 5;
    • 强制转换: 范围大的赋值给范围小的,强制转换:int i = (int)3.14
  • 多态的转型分为向上转型(自动转换)与向下转型(强制转换)两种
向上转型(自动转换)
  • 当父类引用指向一个子类对象时,便是向上转型
1
2
父类类型  变量名 = new 子类类型();
如:Animal a = new Cat();
  • 原因
    • 父类类型相对与子类来说是大范围的类型,Animal是动物类,是父类类型。Cat是猫类,是子类类型。Animal类型的范围当然很大,包含一切动物。
    • 所以子类范围小可以直接自动转型给父类类型的变量
向下转型(强制转换)
  • 一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型
1
2
3
子类类型 变量名 = (子类类型) 父类变量名;
如:Aniaml a = new Cat();
Cat c =(Cat) a;
案例演示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 定义类
abstract class Animal {
abstract void eat();
}

class Cat extends Animal {
public void eat() {
System.out.println("吃鱼"); }
public void catchMouse() {
System.out.println("抓老鼠"); }
}

class Dog extends Animal {
public void eat() {
System.out.println("吃骨头"); }
public void watchHouse() {
System.out.println("看家"); }
}

// 定义测试类
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat

// 向下转型
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
}
}

转型的异常
1
2
3
4
5
6
7
8
9
10
11
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat

// 向下转型
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
}
}
  • 这段代码可以通过编译,但是运行时,却报出了 ClassCastException ,类型转换异常!
  • 明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的
instanceof关键字
  • 为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验
1
2
3
变量名 instanceof 数据类型 
如果变量属于该数据类型或者其子类类型,返回true
如果变量不属于该数据类型或者其子类类型,返回false
  • 用处:引用类型转换前,需要用instanceof做一个判断
1
2
3
4
5
6
7
8
9
10
11
// 向上转型        
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat

// 向下转型
if (a instanceof Cat) {
Cat c = (Cat)a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog) {
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
instanceof新特性
  • JDK14的时候提出了新特性,把判断和强转合并成了一行
1
2
3
4
5
6
7
8
9
10
//新特性
//先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
//如果不是,则不强转,结果直接是false
if(a instanceof Dog d) {
d.lookHome();
} else if(a instanceof Cat c) {
c.catchMouse();
} else {
System.out.println("没有这个类型,无法转换");
}

综合练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
需求:
1. 定义狗类
属性:年龄,颜色
行为:
eat(String something)(something表示吃的东西)
看家lookHome方法(无参数)
2. 定义猫类
属性:年龄,颜色
行为:
eat(String something)方法(something表示吃的东西)
逮老鼠catchMouse方法(无参数)
3. 定义Person类//饲养员
属性:姓名,年龄
行为:
keepPet(Dog dog,String something)方法
功能:喂养宠物狗,something表示喂养的东西
行为:
keepPet(Cat cat,String something)方法
功能:喂养宠物猫,something表示喂养的东西
生成空参有参构造,set和get方法
4. 定义测试类(完成以下打印效果):
keepPet(Dog dog,String somethind)方法打印内容如下:
年龄为30岁的老王养了一只黑颜色的2岁的狗
2岁的黑颜色的狗两只前腿死死的抱住骨头猛吃
keepPet(Cat cat,String somethind)方法打印内容如下:
年龄为25岁的老李养了一只灰颜色的3岁的猫
3岁的灰颜色的猫眯着眼睛侧着头吃鱼
5. 思考:
- Dog和Cat都是Animal的子类,以上案例中针对不同的动物,定义了不同的keepPet方法,过于繁琐,能否简化,并体会简化后的好处?
- Dog和Cat虽然都是Animal的子类,但是都有其特有方法,能否想办法在keepPet中调用特有方法?
6. 画图分析
- 基类Animal——子类1Dog
- 基类Animal——子类2Cat
- Person类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 部分代码
// 以下是Person类部分代码

// 想要一个方法,能接收所有的动物,包括猫,包括狗
// 方法的形参:可以写这些类的父类 Animal
public void keepPet(Animal a, String something) {
if(a instanceof Dog d){ // 运用的是instanceof的新特性,判断与类型强制转换同时进行
System.out.println("年龄为" + age + "岁的" + name + "养了一只" + d.getColor() + "颜色的" + d.getAge() + "岁的狗");
d.eat(something);
}else if(a instanceof Cat c){
System.out.println("年龄为" + age + "岁的" + name + "养了一只" + c.getColor() + "颜色的" + c.getAge() + "岁的猫");
c.eat(something);
}else{
System.out.println("没有这种动物");
}
}
}


  • 包在操作系统中其实就是一个文件夹。包是用来分门别类的管理技术,不同的技术类放在不同的包下,方便管理和维护
1
2
路径名.路径名.xxx.xxx
// 例如:com.itheima.oa
  • 包名一般是公司域名的倒写。例如:黑马是www.itheima.com,包名就可以定义成com.itheima.技术名称。
  • 包名必须用”.“连接。
  • 包名的每个路径名必须是一个合法的标识符,而且不能是Java的关键字

导包

  • 什么时候需要导包?

    • 情况一:在使用Java中提供的非核心包中的类时
    • 情况二:使用自己写的其他包中的类时
  • 什么时候不需要导包?

    • 情况一:在使用Java核心包(java.lang)中的类时
    • 情况二:在使用自己写的同一个包中的类时

使用不同包下的相同类

1
2
3
4
5
//使用全类名的形式即可。
//全类名:包名 + 类名
//拷贝全类名的快捷键:选中类名crtl + shift + alt + c 或者用鼠标点copy,再点击copy Reference
com.itheima.homework.demo1.Student s1 = new com.itheima.homework.demo1.Student();
com.itheima.homework.demo2.Student s2 = new com.itheima.homework.demo2.Student();

权限修饰符

  • public > protected > 默认 > private
publicprotected默认private
同一类中
同一包中的类
不同包的子类
不同包中的无关类

编写代码时,如果没有特殊的考虑,建议这样使用权限:

  • 成员变量使用private ,隐藏细节。
  • 构造方法使用public ,方便创建对象。
  • 成员方法使用public ,方便调用方法。

小贴士:不加权限修饰符,就是默认权限

final关键字

  • 子类可以在父类的基础上改写父类内容,比如方法重写。

  • 有一个方法不想别人去改写里面内容→final

  • final 关键字,表示修饰的内容不可变。

    • final: 不可改变,最终的含义。可以用于修饰类、方法和变量。
      • 类:被修饰的类,不能被继承。
      • 方法:被修饰的方法,不能被重写。
      • 变量:被修饰的变量,有且仅能被赋值一次。

使用方式

修饰类
  • final修饰的类,不能被继承

  • final class 类名 {
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    - 查询API发现像 `public final class String` 、`public final class Math` 、`public final class Scanner` 等,很多我们学习过的类,都是被final修饰的,目的就是供我们使用,而不让我们所以改变其内容

    ##### 修饰方法

    - final修饰的方法,不能被重写

    ```java
    修饰符 final 返回值类型 方法名(参数列表){
    //方法体
    }
修饰变量-局部变量
局部变量-基本类型
  • 基本类型的局部变量,被final修饰后,只能赋值一次,不能再更改
1
2
3
4
5
6
7
8
9
10
// 写法1
final int c = 0;
for (int i = 0; i < 10; i++) {
c = i;
// 报错

// 写法2
for (int i = 0; i < 10; i++) {
final int c = i;
// 不报错,因为每次循环都是一次新的变量c
修饰变量-成员变量
  • 成员变量涉及到初始化的问题,初始化方式有显示初始化和构造方法初始化,只能选择其中一个
  • 显示初始化(在定义成员变量的时候立马赋值)(常用)
1
2
3
public class Student {
final int num = 10;
}
  • 构造方法初始化(在构造方法中赋值一次)(不常用,了解即可)
1
// 注意:每个构造方法中都要赋值一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Student {
final int num = 10;
final int num2;

public Student() {
this.num2 = 20;
// this.num2 = 20;
}
public Student(String name) {
this.num2 = 20;
// this.num2 = 20;
}
}
// 啥意思没搞懂= =

被final修饰的常量名称,一般都有书写规范,所有字母都大写

Day15 面向对象进阶(抽象类&接口&内部类)

  • 能够写出抽象类的格式
  • 能够写出抽象方法的格式
  • 能说出抽象类的应用场景
  • 写出定义接口的格式
  • 写出实现接口的格式
  • 说出接口中成员的特点
  • 能说出接口的应用场景
  • 能说出接口中为什么会出现带有方法体的方法
  • 能完成适配器设计模式

抽象类

  • 父类可能知道子类应该有哪个功能,但是功能具体怎么实现父类是不清楚的(由子类自己决定),父类只需要提供一个没有方法体的定义即可,具体实现交给子类自己去实现

  • 把没有方法体的方法称为抽象方法

  • Java语法规定,包含抽象方法的类就是抽象类

    • 抽象方法 : 没有方法体的方法。
    • 抽象类:包含抽象方法的类

abstract使用

抽象方法
  • 抽象方法只包含一个方法名,而没有方法体
1
2
3
修饰符 abstract 返回值类型 方法名 (参数列表);

public abstract void run()
抽象类
  • 如果一个类包含抽象方法,那么该类必须是抽象类。【才知道= =】
  • 注意:抽象类不一定有抽象方法,但是有抽象方法的类必须定义成抽象类
1
2
3
4
5
6
7
abstract class 类名字 { 

}

public abstract class Animal {
public abstract void run()
}
抽象类的使用
  • 要求:继承抽象类的子类必须重写父类所有的抽象方法
  • 否则,该子类也必须声明为抽象类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 父类,抽象类
abstract class Employee {
private String id;
private String name;
private double salary;
public Employee() {
}
public Employee(String id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
// 抽象方法
// 抽象方法必须要放在抽象类中
abstract public void work();
}

// 定义一个子类继承抽象类
class Manager extends Employee {
public Manager() {
}
public Manager(String id, String name, double salary) {
super(id, name, salary);
}
// 2.重写父类的抽象方法
@Override
public void work() {
System.out.println("管理其他人");
}
}

// 定义一个子类继承抽象类
class Cook extends Employee {
public Cook() {
}
public Cook(String id, String name, double salary) {
super(id, name, salary);
}
@Override
public void work() {
System.out.println("厨师炒菜多加点盐...");
}
}

// 测试类
public class Demo10 {
public static void main(String[] args) {
// 创建抽象类,抽象类不能创建对象
// 假设抽象类让我们创建对象,里面的抽象方法没有方法体,无法执行.所以不让我们创建对象
// Employee e = new Employee();
// e.work();
// 3.创建子类
Manager m = new Manager();
m.work();
Cook c = new Cook("ap002", "库克", 1);
c.work();
}
}
  • 此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法

抽象类的特征

  • 总结起来可以说是 有得有失

    • 有得:抽象类得到了拥有抽象方法的能力。

    • 有失:抽象类失去了创建对象的能力。

    • 其他成员(构造方法,实例方法,静态方法等)抽象类都是具备的

抽象类的细节

  • 不需要背,只要当idea报错之后,知道如何修改即可

  • 理解抽象的本质,无需死记硬背

  1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。

理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。

  1. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

  1. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。

  1. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则子类也必须定义成抽象类,编译无法通过而报错。

理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。

  1. 抽象类存在的意义是为了被子类继承

理解:抽象类中已经实现的是模板中确定的成员,抽象类不确定如何实现的定义成抽象方法,交给具体的子类去实现。

抽象类存在的意义

  • 抽象类存在的意义是为了被子类继承,否则抽象类将毫无意义
  • 抽象类可以强制让子类,一定要按照规定的格式进行重写

接口

  • 接口中全部是抽象方法
  • 接口不能创建对象
1
2
3
4
5
6
7
//接口的定义格式:
interface 接口名称{
// 抽象方法
}

// 接口的声明:interface
// 接口名称:首字母大写,满足“驼峰模式”

接口成分的特点

抽象方法
  • 接口中的抽象方法默认会自动加上public abstract修饰,无需自己手写
常量
  • 在接口中定义的成员变量默认会加上: public static final修饰

  • 在接口中定义的成员变量实际上是一个常量。使用public static final修饰后,变量值就不可被修改

  • 并且是静态化的变量可以直接用接口名访问,所以也叫常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface InterF {
// 抽象方法!
// public abstract void run();
void run();

// public abstract String getName();
String getName();

// public abstract int add(int a , int b);
int add(int a , int b);


// 它的最终写法是:
// public static final int AGE = 12 ;
int AGE = 12; //常量
String SCHOOL_NAME = "黑马程序员";

}

基本的实现

  • 类与接口的关系为实现关系,即类实现接口,该类可以称为接口的[实现类],也可以称为接口的[子类]
1
2
3
4
5
6
/**接口的实现:
在Java中接口是被实现的,实现接口的类称为实现类。
实现类的格式:*/
class 类名 implements 接口1,接口2,接口3...{

}
类实现接口的要求和意义
  1. 必须重写实现的全部接口中所有抽象方法。
  2. 如果一个类实现了接口,但是没有重写完全部接口的全部抽象方法,这个类也必须定义成抽象类。
  3. 意义:接口体现的是一种【规范】,接口对实现类是一种【强制性的约束】,要么全部完成接口申明的功能,要么自己也定义成抽象类。这正是一种强制性的规范
类实现接口基本案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
接口:接口体现的是规范。
*/
public interface SportMan {
void run(); // 抽象方法,跑步。
void law(); // 抽象方法,遵守法律。
String compittion(String project); // 抽象方法,比赛。
}

/**
*接口的实现:
*在Java中接口是被实现的,实现接口的类称为实现类。
*实现类的格式:
* class 类名 implements 接口1,接口2,接口3...{...}
**/
public class PingPongMan implements SportMan {
@Override
public void run() {
System.out.println("乒乓球运动员稍微跑一下!!");
}

@Override
public void law() {
System.out.println("乒乓球运动员守法!");
}

@Override
public String compittion(String project) {
return "参加" + project + "得金牌!";
}
}

// 测试代码
public class TestMain {
public static void main(String[] args) {
// 创建实现类对象。
PingPongMan zjk = new PingPongMan();
zjk.run();
zjk.law();
System.out.println(zjk.compittion("全球乒乓球比赛"));

}
}
类与接口多实现案例
  • 类与接口之间的关系是多实现的,一个类可以同时实现多个接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/** 法律规范:接口*/
public interface Law {
void rule();
}

/** 这一个运动员的规范:接口*/
public interface SportMan {
void run();
}

/**
*Java中接口是可以被多实现的:
*一个类可以实现多个接口: Law, SportMan
*
**/
public class JumpMan implements Law ,SportMan {
@Override
public void rule() {
System.out.println("尊长守法");
}

@Override
public void run() {
System.out.println("训练跑步!");
}
}

/**
*Java中接口是可以被多实现的:
*一个类可以实现多个接口: Law, SportMan
*
**/
public class JumpMan implements Law ,SportMan {
@Override
public void rule() {
System.out.println("尊长守法");
}

@Override
public void run() {
System.out.println("训练跑步!");
}
}

接口与接口的多继承(没太懂= =)

  • 接口与接口之间是可以多继承的

  • 注意:类与接口是实现关系,接口与接口是继承关系

  • 接口继承接口就是把其他接口的抽象方法与本接口进行了合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface Abc {
void go();
void test();
}

/** 法律规范:接口*/
public interface Law {
void rule();
void test();
}

*
*总结:
* 接口与类之间是多实现的。
* 接口与接口之间是多继承的。
* */
public interface SportMan extends Law , Abc {
void run();
}

扩展:接口的细节

  • 会用就行,报错时懂得修改
  1. 当两个接口中存在相同抽象方法的时候,该怎么办?

只要重写一次即可。此时重写的方法,既表示重写1接口的,也表示重写2接口的。

  1. 实现类能不能继承A类的时候,同时实现其他接口呢?

继承的父类,就好比是亲爸爸一样

实现的接口,就好比是干爹一样

可以继承一个类的同时,再实现多个接口,只不过,要把接口里面所有的抽象方法,全部实现。

  1. 实现类能不能继承一个抽象类的时候,同时实现其他接口呢?

实现类可以继承一个抽象类的同时,再实现其他多个接口,只不过要把里面所有的抽象方法全部重写。

  1. 实现类Zi,实现了一个接口,还继承了一个Fu类。假设在接口中有一个方法,父类中也有一个相同的方法。子类如何操作呢?

处理办法一:如果父类中的方法体,能满足当前业务的需求,在子类中可以不用重写。

处理办法二:如果父类中的方法体,不能满足当前业务的需求,需要在子类中重写。

  1. 如果一个接口中,有10个抽象方法,但是我在实现类中,只需要用其中一个,该怎么办?

可以在接口跟实现类中间,新建一个中间类(适配器类)

让这个适配器类去实现接口,对接口里面的所有的方法做空重写。

让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。

因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象

内部类

  • 将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类
  • 可以把内部类理解成寄生,外部类理解成宿主

概述

  • 什么时候用内部类:
  • 一个事物内部还有一个独立的事物,内部的事物脱离外部的事物无法独立使用
    1. 人里面有一颗心脏。
    2. 汽车内部有一个发动机。
    3. 为了实现更好的封装性。

分类

  • 按定义的位置来分
  1. **成员内部类,类定义在了成员位置 (类中方法外称为成员位置,无static修饰的内部类)
  2. 静态内部类,类定义在了成员位置 (类中方法外称为成员位置,有static修饰的内部类)
  3. 局部内部类,类定义在方法内
  4. 匿名内部类,没有名字的内部类,可以在方法中,也可以在类中方法外。

成员内部类

特点
  • 无static修饰的内部类,属于外部类对象的
  • 宿主:外部类对象
使用格式
1
外部类.内部类 // 访问内部类的类型都是用 外部类.内部类
获取成员内部类对象的两种方式

方式一:外部直接创建成员内部类的对象

1
外部类.内部类 变量 = new 外部类().new 内部类();

方式二:在外部类中定义一个方法提供内部类的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
方式一:
public class Test {
public static void main(String[] args) {
// 宿主:外部类对象。
// Outer out = new Outer();
// 创建内部类对象。
Outer.Inner oi = new Outer().new Inner();
oi.method();
}
}

class Outer {
// 成员内部类,属于外部类对象的。
// 拓展:成员内部类不能定义静态成员。
public class Inner{
// 这里面的东西与类是完全一样的。
public void method(){
System.out.println("内部类中的方法被调用了");
}
}
}


方式二:
public class Outer {
String name;
private class Inner{
static int a = 10;
}
public Inner getInstance(){
return new Inner();
}
}

public class Test {
public static void main(String[] args) {
Outer o = new Outer();
System.out.println(o.getInstance());
}
}

成员内部类的细节

  • 编写成员内部类的注意点:
  1. 成员内部类可以被一些修饰符所修饰,比如: private,默认,protected,public,static等
  2. 在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量。
  3. 创建内部类对象时,对象中有一个隐含的Outer.this记录外部类对象的地址值。

成员内部类面试题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test {
public static void main(String[] args) {
Outer.inner oi = new Outer().new inner();
oi.method();
}
}

class Outer { // 外部类
private int a = 30;

// 在成员位置定义一个类
class inner {
private int a = 20;

public void method() {
int a = 10;
System.out.println(???); // 10 答案:a
System.out.println(???); // 20 答案:this.a
System.out.println(???); // 30 答案:Outer.this.a
}
}
}
  • 注意:内部类访问外部类对象的格式是:外部类名.this

成员内部类内存图

  • 此处缺少一张图
  • 目前技术还不到位

静态内部类

  • 一种特殊的成员内部类
  • 有static修饰,属于外部类本身的
  • 总结:静态内部类与其他类的用法完全一样。只是访问的时候需要加上外部类.内部类
  • 拓展1:静态内部类可以直接访问外部类的静态成员
  • 拓展2:静态内部类不可以直接访问外部类的非静态成员,如果要访问需要创建外部类的对象
  • 拓展3:静态内部类中没有银行的Outer.this

创建格式

1
外部类.内部类  变量 = new  外部类.内部类构造器;

调用方法

  • 调用非静态方法的格式:先创建对象,用对象调用
  • 调用静态方法的格式:外部类名.内部类名.方法名();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 外部类:Outer01
class Outer01{
private static String sc_name = "黑马程序";
// 内部类: Inner01
public static class Inner01{
// 这里面的东西与类是完全一样的。
private String name;
public Inner01(String name) {
this.name = name;
}
public void showName(){
System.out.println(this.name);
// 拓展:静态内部类可以直接访问外部类的静态成员。
System.out.println(sc_name);
}
}
}

public class InnerClassDemo01 {
public static void main(String[] args) {
// 创建静态内部类对象。
// 外部类.内部类 变量 = new 外部类.内部类构造器;
Outer01.Inner01 in = new Outer01.Inner01("张三");
in.showName();
}
}

局部内部类

  • 定义在方法中的类
1
2
3
4
5
6
7
8
9
10
class 外部类名 {
数据类型 变量名;
修饰符 返回值类型 方法名(参数列表) {
// …
class 内部类 {
// 成员变量
// 成员方法
}
}
}

【▷】匿名内部类

  • 开发中最常用到的内部类
1
2
3
new 类名或者接口名() {
重写方法;
};
  • 包含了:
    • 继承或实现关系
    • 方法重写
    • 创建对象

匿名内部类使用场景

  • 希望定义一个只使用一次的类
  • 本质:简化代码
  • 之前使用接口的步骤:
1
2
3
4
1. 定义子类
2. 重写接口中的方法
3. 创建子类对象
4. 调用重写后的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Swim {
public abstract void swimming();
}

// 1. 定义接口的实现类
class Student implements Swim {
// 2. 重写抽象方法
@Override
public void swimming() {
System.out.println("狗刨式...");
}
}

public class Test {
public static void main(String[] args) {
// 3. 创建实现类对象
Student s = new Student();
// 4. 调用方法
s.swimming();
}
}
  • 更快的实现以上四个步骤的方式:匿名内部类

匿名内部类前提和格式

  • 必须继承一个父类或者实现一个父接口
1
2
3
4
5
6
7
new 父类名或者接口名(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};

使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
interface Swim {
public abstract void swimming();
}

public class Demo07 {
public static void main(String[] args) {
// 使用匿名内部类
new Swim() {
@Override
public void swimming() {
System.out.println("自由泳...");
}
}.swimming();

// 接口 变量 = new 实现类(); // 多态,走子类的重写方法
Swim s2 = new Swim() {
@Override
public void swimming() {
System.out.println("蛙泳...");
}
};

s2.swimming();
s2.swimming();
}
}

特点

  1. 定义一个没有名字的内部类
  2. 这个类实现了父类,或者父类接口
  3. 匿名内部类会创建这个没有名字的类的对象

使用场景

  • 通常在方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
interface Swim {
public abstract void swimming();
}

public class Demo07 {
public static void main(String[] args) {
// 普通方式传入对象
// 创建实现类对象
Student s = new Student();
goSwimming(s);
// 匿名内部类使用场景:作为方法参数传递
Swim s3 = new Swim() {
@Override
public void swimming() {
System.out.println("蝶泳...");
}
};
// 传入匿名内部类
goSwimming(s3);

// 完美方案: 一步到位
goSwimming(new Swim() {
public void swimming() {
System.out.println("大学生, 蛙泳...");
}
});

goSwimming(new Swim() {
public void swimming() {
System.out.println("小学生, 自由泳...");
}
});
}

// 定义一个方法,模拟请一些人去游泳
public static void goSwimming(Swim s) {
s.swimming();
}
}
  • 有点没看懂= =

Day16 面向对象综合练习(上)-- 拼图游戏

  • 利用Java的图形化界面,写一个项目,知道前面学习的知识点在实际开发中的应用场景

实现思路

  • 先写游戏主界面,实现步骤如下:
  1. 完成最外层窗体的搭建。
  2. 再把菜单添加到窗体当中。
  3. 把小图片添加到窗体当中。
  4. 打乱数字图片的顺序。
  5. 让数字图片可以移动起来。
  6. 通关之后的胜利判断。
  7. 添加其他额外的功能。

主界面搭建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 创建App类,编写main方法,作用: 程序的主入口

// 召唤主界面
JFrame jFrame = new JFrame();

//设置主界面的大小
jFrame.setSize(514,595);

//将主界面设置到屏幕的正中央
jFrame.setLocationRelativeTo(null);

//将主界面置顶
jFrame.setAlwaysOnTop(true);

//关闭主界面的时候让代码一起停止
jFrame.setDefaultCloseOperation(3);

//给主界面设置一个标题
jFrame.setTitle("拼图游戏单机版 v1.0");

//2.让主界面显示出来
jFrame.setVisible(true);
  • 注意事项:jFrame.setVisible(true);必须要写在最后一行

利用继承改进代码

  • **需求:**如果把所有的代码都写在main方法中,那么main方法里面的代码,就包含游戏主界面的代码,登录界面的代码,注册界面的代码,会变得非常臃肿后期维护也是一件非常难的事情,所以我们需要用继承改进,改进之后,代码就可以分类了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//登录界面
public class LoginJFrame extends JFrame {
//LoginJFrame 表示登录界面
//以后所有跟登录相关的代码,都写在这里

public LoginJFrame(){
//在创建登录界面的时候,同时给这个界面去设置一些信息
//比如,宽高,直接展示出来
this.setSize(488,430);
//设置界面的标题
this.setTitle("拼图 登录");
//设置界面置顶
this.setAlwaysOnTop(true);
//设置界面居中
this.setLocationRelativeTo(null);
//设置关闭模式
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//让显示显示出来,建议写在最后
this.setVisible(true);
}
}


//注册界面
public class RegisterJFrame extends JFrame {
//跟注册相关的代码,都写在这个界面中
public RegisterJFrame(){
this.setSize(488,500);
//设置界面的标题
this.setTitle("拼图 注册");
//设置界面置顶
this.setAlwaysOnTop(true);
//设置界面居中
this.setLocationRelativeTo(null);
//设置关闭模式
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//让显示显示出来,建议写在最后
this.setVisible(true);

getContentPane();
}
}

//游戏主界面
public class GameJFrame extends JFrame {

public GameJFrame() {
//设置界面的宽高
this.setSize(603, 680);
//设置界面的标题
this.setTitle("拼图单机版 v1.0");
//设置界面置顶
this.setAlwaysOnTop(true);
//设置界面居中
this.setLocationRelativeTo(null);
//设置关闭模式
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//取消默认的居中放置,只有取消了才会按照XY轴的形式添加组件
this.setLayout(null);
//让界面显示出来,建议写在最后
this.setVisible(true);
}
}
  • 注意:this表示当前窗体

菜单制作

  • 在菜单中有:JMenuBar、JMenu、JMenuItem

  • 其中JMenuBar是整体,一个界面中一般只有一个JMenuBar(顶栏操作栏)。而JMenu是菜单中的选项,可以有多个。JMenuItem是选项下面的条目,也可以有多个

  • 代码步骤

    • 创建JMenuBar对象
    • 创建JMenu对象
    • 创建JMenuItem对象
    • 把JMenuItem添加到JMenu中
    • 把JMenu添加到JMenuBar中
    • 把整个JMenuBar设置到整个界面中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//创建一个菜单对象
JMenuBar jMenuBar = new JMenuBar();
//设置菜单的宽高
jMenuBar.setSize(514, 20);
//创建一个选项
JMenu jMenu1 = new JMenu("功能");
//创建一个条目
jMenuItem1 = new JMenuItem("重新游戏");

//把条目添加到选项当中
jMenu1.add(jMenuItem1);
//把选项添加到菜单当中
jMenuBar.add(jMenu1);
//把菜单添加到最外层的窗体当中
this.setJMenuBar(jMenuBar);

添加图片

  • 使用到的Java类

    • ImageIcon:描述图片的类,可以关联计算中任意位置的图片。但一般会把图片拷贝到当前项目中。
    • JLabel:用来管理图片,文字的类。可以用来设置位置,宽高。
  • 坐标

    • 界面左上角的点可以看做是坐标的原点,横向的是X轴,纵向的是Y轴。
    • 图片的位置其实取决于图片左上角的点,在坐标中的位置。
    • 如果是(0,0)那么该图片会显示在屏幕的左上角
  • 步骤

  1. 取消整个界面的默认居中布局
  2. 创建ImageIcon对象,并制定图片位置。
  3. 创建JLabel对象,并把ImageIcon对象放到小括号中。
  4. 利用JLabel对象设置大小,宽高。
  5. 将JLabel对象添加到整个界面当中。

打乱图片的位置

  • 每一张图片都对应1~15之间的数字,空白处为0,打乱图片实际上就是把数字打乱,添加图片的时候按照打乱的图片添加即可
预先练习:打乱数组中的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class Test1 {
public static void main(String[] args) {
//需求:
//把一个一维数组中的数据:0~15 打乱顺序
//然后再按照4个一组的方式添加到二维数组当中


//1.定义一个一维数组
int[] tempArr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};

//2.打乱数组中的数据的顺序
//遍历数组,得到每一个元素,拿着每一个元素跟随机索引上的数据进行交换
Random r = new Random();
for (int i = 0; i < tempArr.length; i++) {
//获取到随机索引
int index = r.nextInt(tempArr.length);
//拿着遍历到的每一个数据,跟随机索引上的数据进行交换
int temp = tempArr[i];
tempArr[i] = tempArr[index];
tempArr[index] = temp;
}

//3.遍历数组
for (int i = 0; i < tempArr.length; i++) {
System.out.print(tempArr[i] + " ");
}
System.out.println();


//4.创建一个二维数组
int[][] data = new int[4][4];


//5.给二维数组添加数据
//解法一:
//遍历一维数组tempArr得到每一个元素,把每一个元素依次添加到二维数组当中
for (int i = 0; i < tempArr.length; i++) {
data[i / 4][i % 4] = tempArr[i]; // 这行妙啊
}

//遍历二维数组
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
System.out.print(data[i][j] + " ");
}
System.out.println();
}
}
}
打乱图片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// 打乱图片

public class GameJFrame extends JFrame {
//JFrame 界面,窗体
//子类呢?也表示界面,窗体
//规定:GameJFrame这个界面表示的就是游戏的主界面
//以后跟游戏相关的所有逻辑都写在这个类中

//创建一个二维数组
//目的:用来管理数据
//加载图片的时候,会根据二维数组中的数据进行加载
int[][] data = new int[4][4];

public GameJFrame() {
//初始化界面
initJFrame();

//初始化菜单
initJMenuBar();

//初始化数据(打乱)
initData();

//初始化图片(根据打乱之后的结果去加载图片)
initImage();

//让界面显示出来,建议写在最后
this.setVisible(true);

}

//初始化数据(打乱)
private void initData() {
//1.定义一个一维数组
int[] tempArr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};

//2.打乱数组中的数据的顺序
//遍历数组,得到每一个元素,拿着每一个元素跟随机索引上的数据进行交换
Random r = new Random();
for (int i = 0; i < tempArr.length; i++) {
//获取到随机索引
int index = r.nextInt(tempArr.length);
//拿着遍历到的每一个数据,跟随机索引上的数据进行交换
int temp = tempArr[i];
tempArr[i] = tempArr[index];
tempArr[index] = temp;
}

//4.给二维数组添加数据
//遍历一维数组tempArr得到每一个元素,把每一个元素依次添加到二维数组当中
for (int i = 0; i < tempArr.length; i++) {
data[i / 4][i % 4] = tempArr[i];
}
}

//初始化图片
//添加图片的时候,就需要按照二维数组中管理的数据添加图片
private void initImage() {
//外循环 --- 把内循环重复执行了4次。
for (int i = 0; i < 4; i++) {
//内循环 --- 表示在一行添加4张图片
for (int j = 0; j < 4; j++) {
//获取当前要加载图片的序号
int num = data[i][j];
//创建一个JLabel的对象(管理容器)
JLabel jLabel = new JLabel(new ImageIcon("C:\\Users\\moon\\IdeaProjects\\basic-code\\puzzlegame\\image\\animal\\animal3\\" + num + ".jpg"));
//指定图片位置
jLabel.setBounds(105 * j, 105 * i, 105, 105);
//把管理容器添加到界面中
this.getContentPane().add(jLabel);
}
}

private void initJMenuBar() {
//创建整个的菜单对象
JMenuBar jMenuBar = new JMenuBar();

//创建菜单上面的两个选项的对象 (功能 关于我们)
JMenu functionJMenu = new JMenu("功能");
JMenu aboutJMenu = new JMenu("关于我们");

//创建选项下面的条目对象
JMenuItem replayItem = new JMenuItem("重新游戏");
JMenuItem reLoginItem = new JMenuItem("重新登录");
JMenuItem closeItem = new JMenuItem("关闭游戏");

JMenuItem accountItem = new JMenuItem("公众号");

//将每一个选项下面的条目天极爱到选项当中
functionJMenu.add(replayItem);
functionJMenu.add(reLoginItem);
functionJMenu.add(closeItem);

aboutJMenu.add(accountItem);

//将菜单里面的两个选项添加到菜单当中
jMenuBar.add(functionJMenu);
jMenuBar.add(aboutJMenu);

//给整个界面设置菜单
this.setJMenuBar(jMenuBar);
}

private void initJFrame() {
//设置界面的宽高
this.setSize(603, 680);
//设置界面的标题
this.setTitle("拼图单机版 v1.0");
//设置界面置顶
this.setAlwaysOnTop(true);
//设置界面居中
this.setLocationRelativeTo(null);
//设置关闭模式
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//取消默认的居中放置,只有取消了才会按照XY轴的形式添加组件
this.setLayout(null);

}
}

事件

  • 事件是可以被组件识别的操作

事件的三个核心要素

  • 事件源:按钮 图片 窗体
  • 事件:某些曹组
  • 绑定监听:当事件源上发生了某个时间,则执行某段代码‘

常见三种事件监听

  • 键盘监听 KeyListener
  • ⿏标监听 MouseListener
  • 动作监听 ActionListener

事件监听

  • 包含:
    • 鼠标左键点击
    • 空格
事件的三种实现方式
  • 定义实现类实现接口
  • 匿名内部类
  • 本类实现接口
方式一:实现类
  • 定义实现类实现ActionListener接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Test3 {
public static void main(String[] args) {
JFrame jFrame = new JFrame();
//设置界面的宽高
jFrame.setSize(603, 680);
//设置界面的标题
jFrame.setTitle("事件演示");
//设置界面置顶
jFrame.setAlwaysOnTop(true);
//设置界面居中
jFrame.setLocationRelativeTo(null);
//设置关闭模式
jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//取消默认的居中放置,只有取消了才会按照XY轴的形式添加组件
jFrame.setLayout(null);

//创建一个按钮对象
JButton jtb = new JButton("点我啊");
//设置位置和宽高
jtb.setBounds(0,0,100,50);
//给按钮添加动作监听
//jtb:组件对象,表示你要给哪个组件添加事件
//addActionListener:表示我要给组件添加哪个事件监听(动作监听包含鼠标左键点击,空格)
//参数:表示事件被触发之后要执行的代码
jtb.addActionListener(new MyActionListener());

//把按钮添加到界面当中
jFrame.getContentPane().add(jtb);

jFrame.setVisible(true);
}
}


public class MyActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按钮被点击了");
}
}
方式二:匿名内部类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Test3 {
public static void main(String[] args) {
JFrame jFrame = new JFrame();
//设置界面的宽高
jFrame.setSize(603, 680);
//设置界面的标题
jFrame.setTitle("事件演示");
//设置界面置顶
jFrame.setAlwaysOnTop(true);
//设置界面居中
jFrame.setLocationRelativeTo(null);
//设置关闭模式
jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//取消默认的居中放置,只有取消了才会按照XY轴的形式添加组件
jFrame.setLayout(null);


//创建一个按钮对象
JButton jtb = new JButton("点我啊");
//设置位置和宽高
jtb.setBounds(0,0,100,50);
//给按钮添加动作监听
//jtb:组件对象,表示你要给哪个组件添加事件
//addActionListener:表示我要给组件添加哪个事件监听(动作监听包含鼠标左键点击,空格)
//参数:表示事件被触发之后要执行的代码

jtb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("达咩~不要点我哟~");
}
});


//把按钮添加到界面当中
jFrame.getContentPane().add(jtb);


jFrame.setVisible(true);
}
}
方式三:本类实现接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class MyJFrame extends JFrame implements ActionListener {

//创建一个按钮对象
JButton jtb1 = new JButton("点我啊");
//创建一个按钮对象
JButton jtb2 = new JButton("再点我啊");

public MyJFrame(){
//设置界面的宽高
this.setSize(603, 680);
//设置界面的标题
this.setTitle("拼图单机版 v1.0");
//设置界面置顶
this.setAlwaysOnTop(true);
//设置界面居中
this.setLocationRelativeTo(null);
//设置关闭模式
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//取消默认的居中放置,只有取消了才会按照XY轴的形式添加组件
this.setLayout(null);


//给按钮设置位置和宽高
jtb1.setBounds(0,0,100,50);
//给按钮添加事件
jtb1.addActionListener(this);


//给按钮设置位置和宽高
jtb2.setBounds(100,0,100,50);
jtb2.addActionListener(this);


//那按钮添加到整个界面当中
this.getContentPane().add(jtb1);
this.getContentPane().add(jtb2);

//让整个界面显示出来
this.setVisible(true);
}

@Override
public void actionPerformed(ActionEvent e) {
//对当前的按钮进行判断

//获取当前被操作的那个按钮对象
Object source = e.getSource();

if(source == jtb1){
jtb1.setSize(200,200);
}else if(source == jtb2){
Random r = new Random();
jtb2.setLocation(r.nextInt(500),r.nextInt(500));
}
}
}

Day17 面向对象综合练习(下)-- 拼图游戏

美化界面

  1. 将15张小图片移动到界面的中央偏下方
  2. 添加背景图片
  3. 添加图片的边框
  4. 优化路径

小图片居中

  • 设计让图片居中
  • 这样需要给每张图片在x和y都进行一个偏移
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (int i = 0; i < 4; i++) {
//内循环 --- 表示在一行添加4张图片
for (int j = 0; j < 4; j++) {
//获取当前要加载图片的序号
int num = data[i][j];
//创建一个JLabel的对象(管理容器)
JLabel jLabel = new JLabel(new ImageIcon(path + num + ".jpg"));
//指定图片位置,并进行适当的偏移
jLabel.setBounds(105 * j + 83, 105 * i + 134, 105, 105);
//给图片添加边框
//0:表示让图片凸起来
//1:表示让图片凹下去
jLabel.setBorder(new BevelBorder(BevelBorder.LOWERED));
//把管理容器添加到界面中
this.getContentPane().add(jLabel);
}
}

添加背景图片

  • 细节:代码中后添加的,塞在下方
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
for (int i = 0; i < 4; i++) {
//内循环 --- 表示在一行添加4张图片
for (int j = 0; j < 4; j++) {
//获取当前要加载图片的序号
int num = data[i][j];
//创建一个JLabel的对象(管理容器)
JLabel jLabel = new JLabel(new ImageIcon("F:\\JavaSE最新版\\day17-面向对象综合练习(下)\\代码\\" + num + ".jpg"));
//指定图片位置
jLabel.setBounds(105 * j + 83, 105 * i + 134, 105, 105);
//给图片添加边框
//0:表示让图片凸起来
//1:表示让图片凹下去
jLabel.setBorder(new BevelBorder(BevelBorder.LOWERED));
//把管理容器添加到界面中
this.getContentPane().add(jLabel);
}
}


//添加背景图片
JLabel background = new JLabel(new ImageIcon("F:\JavaSE最新版\day17-面向对象综合练习(下)\代码\puzzlegame\\image\\background.png"));
background.setBounds(40, 40, 508, 560);
//把背景图片添加到界面当中
this.getContentPane().add(background);

添加图片的边框

1
2
3
4
5
6
7
8
9
//给图片添加边框
//括号中也可以写0或者1
//要注意,这个凸凹跟大家自己理解的可能会有偏差
//0:表示让图片凸起来,图片凸起来,边框就会凹下去
//1:表示让图片凹下去,图片凹下去,边框就会凸起来
//但是0和1不好记,所以Java中就定义了常亮表示,方便记忆
//BevelBorder.LOWERED:表示1
//BevelBorder.RAISED:表示0
jLabel.setBorder(new BevelBorder(BevelBorder.LOWERED));

优化路径

  • 之前写的完整路径:F:\JavaSE最新版\day17-面向对象综合练习(下)\代码\puzzlegame\image\animal\animal3\1.jpg
    • 坏处1:路径太长,阅读不方便
    • 坏处2:项目非本地运行时,如果环境中没有对应文件夹,就找不到图片资源
计算机中的两种路径
  • 绝对路径&相对路径

  • 目前在idea中,相对路径时相对当前项目而言

  • 例:aaa\\bbb\\a.txt,先找当前项目,在当前项目下找aaa,在aaa里面找bbb,在bbb里面找a.txt

1
2
3
4
5
6
// 将之前项目的绝对路径改写为相对路径
// 添加背景图片
JLabel background = new JLabel(new ImageIcon("puzzlegame\\image\\background.png"));
background.setBounds(40, 40, 508, 560);
// 把背景图片添加到界面当中
this.getContentPane().add(background);

上下左右移动的逻辑

业务分析

  • 上下左右的我们看上去就是移动空白的方块,实则逻辑跟我们看上去的相反:

    • 上移:是把空白区域下方的图片上移。
    • 下移:是把空白区域上方的图片下移。
    • 左移:是把空白区域右方的图片左移。
    • 右移:是把空白区域左方的图片右移。
  • 移动时需要注意的问题

    • 如果空白区域已经在最上面了,此时x=0,那么就无法再下移了。
    • 如果空白区域已经在最下面了,此时x=3,那么就无法再上移了。
    • 如果空白区域已经在最左侧了,此时y=1,那么就无法再右移了。
    • 如果空白区域已经在最右侧了,此时y=3,那么就无法再左移了。

实现步骤

  1. 本类实现KeyListener接口,并重写所有抽象方法
  2. 给整个界面添加键盘监听事件
  3. 统计一下空白方块对应的数字0在二维数组中的位置
  4. 在keyReleased方法当中实现移动的逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//松开按键的时候会调用这个方法
@Override
public void keyReleased(KeyEvent e) {
//对上,下,左,右进行判断
//左:37 上:38 右:39 下:40
int code = e.getKeyCode();
if (code == 37) {
System.out.println("向左移动");
//逻辑:
//把空白方块右方的数字往左移动
data[x][y] = data[x][y + 1];
data[x][y + 1] = 0;
y++;
//调用方法按照最新的数字加载图片
initImage();
} else if (code == 38) {
System.out.println("向上移动");
//逻辑:
//把空白方块下方的数字往上移动
//x,y 表示空白方块
//x + 1, y 表示空白方块下方的数字
//把空白方块下方的数字赋值给空白方块
data[x][y] = data[x + 1][y];
data[x + 1][y] = 0;
x++;
//调用方法按照最新的数字加载图片
initImage();
} else if (code == 39) {
System.out.println("向右移动");
//逻辑:
//把空白方块左方的数字往右移动
data[x][y] = data[x][y - 1];
data[x][y - 1] = 0;
y--;
//每移动一次,计数器就自增一次。
initImage();
} else if (code == 40) {
System.out.println("向下移动");
//逻辑:
//把空白方块上方的数字往下移动
data[x][y] = data[x - 1][y];
data[x - 1][y] = 0;
x--;
//调用方法按照最新的数字加载图片
initImage();
}
}

查看完整图片

业务分析

  • 在玩游戏的过程中,我想看一下最终的效果图,该怎么办呢?
  • 此时可以添加一个功能,当我们长按某个键(假设为A),不松的时候,就显示完整图片,松开就显示原来的图片

实现步骤

  1. 给整个界面添加键盘事件
  2. 在keyPressed中书写按下不松的逻辑
  3. 在keyReleased中书写松开的逻辑

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//按下不松时会调用这个方法
@Override
public void keyPressed(KeyEvent e) {
int code = e.getKeyCode();
if (code == 65){ // 把按键信息转换为ASCII码值
//把界面中所有的图片全部删除
this.getContentPane().removeAll();
//加载第一张完整的图片
JLabel all = new JLabel(new ImageIcon(path + "all.jpg"));
all.setBounds(83,134,420,420);
this.getContentPane().add(all);
//加载背景图片
//添加背景图片
JLabel background = new JLabel(new ImageIcon("puzzlegame\\image\\background.png"));
background.setBounds(40, 40, 508, 560);
//把背景图片添加到界面当中
this.getContentPane().add(background);
//刷新界面
this.getContentPane().repaint();
}
}


//松开按键的时候会调用这个方法
@Override
public void keyReleased(KeyEvent e) {
...
else if(code == 65){
initImage();
}
....
}

作弊码

业务分析

不想玩了,想要一键通关

实现步骤

  1. 给整个界面添加键盘事件
  2. 在keyReleased中书写松开的逻辑,当按下W的时候一键通关。
  • **备注:**可以改写这段逻辑,当按下W的时候,可以将数据排列成还需要走这么两三步才能一键通关的,这样你在跟好基友PK的时候,操作是不是更加隐秘呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//松开按键的时候会调用这个方法
@Override
public void keyReleased(KeyEvent e) {
...
else if(code == 87){
data = new int[][]{
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}
};
initImage();
}
....
}

判断胜利

业务逻辑

  • 当游戏的图标排列正确了,需要有胜利图标显示。
  • 每次上下左右移动图片的时候都需要进行判断。
  • 在keyReleased中方法一开始的地方就需要写判断是否胜利

实现步骤

  1. 定义一个正确的二维数组win。
  2. 在加载图片之前,先判断一下二维数组中的数字跟win数组中是否相同。
  3. 如果相同展示正确图标
  4. 如果不同则不展示正确图标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class GameJFrame extends JFrame implements KeyListener,ActionListener{
...
//定义一个二维数组,存储正确的数据
int[][] win = {
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}
};

private void initImage() {
//清空原本已经出现的所有图片
this.getContentPane().removeAll();

if (victory()) {
//显示胜利的图标
JLabel winJLabel = new JLabel(new ImageIcon("C:\\Users\\moon\\IdeaProjects\\basic-code\\puzzlegame\\image\\win.png"));
winJLabel.setBounds(203,283,197,73);
this.getContentPane().add(winJLabel);
}
...
}

//判断data数组中的数据是否跟win数组中相同
//如果全部相同,返回true。否则返回false
public boolean victory(){
for (int i = 0; i < data.length; i++) {
//i : 依次表示二维数组 data里面的索引
//data[i]:依次表示每一个一维数组
for (int j = 0; j < data[i].length; j++) {
if(data[i][j] != win[i][j]){
//只要有一个数据不一样,则返回false
return false;
}
}
}
//循环结束表示数组遍历比较完毕,全都一样返回true
return true;
}
}

计步功能

业务分析

  • 左上角放一个计步器,每移动一次,计步器就需要自增一次

实现步骤

  1. 定义一个变量用来统计已经玩了多少步。
  2. 每次按上下左右的时候计步器自增一次即可。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
public class GameJFrame extends JFrame implements KeyListener,ActionListener{
...
//定义变量用来统计步数
int step = 0;
//初始化图片
//添加图片的时候,就需要按照二维数组中管理的数据添加图片
private void initImage() {

//清空原本已经出现的所有图片
this.getContentPane().removeAll();

if (victory()) {
//显示胜利的图标
JLabel winJLabel = new JLabel(new ImageIcon("C:\\Users\\moon\\IdeaProjects\\basic-code\\puzzlegame\\image\\win.png"));
winJLabel.setBounds(203,283,197,73);
this.getContentPane().add(winJLabel);
}


JLabel stepCount = new JLabel("步数:" + step);
stepCount.setBounds(50,30,100,20);
this.getContentPane().add(stepCount);


//路径分为两种:
//绝对路径:一定是从盘符开始的。C:\ D:\
//相对路径:不是从盘符开始的
//相对路径相对当前项目而言的。 aaa\\bbb
//在当前项目下,去找aaa文件夹,里面再找bbb文件夹。

//细节:
//先加载的图片在上方,后加载的图片塞在下面。
//外循环 --- 把内循环重复执行了4次。
for (int i = 0; i < 4; i++) {
//内循环 --- 表示在一行添加4张图片
for (int j = 0; j < 4; j++) {
//获取当前要加载图片的序号
int num = data[i][j];
//创建一个JLabel的对象(管理容器)
JLabel jLabel = new JLabel(new ImageIcon(path + num + ".jpg"));
//指定图片位置
jLabel.setBounds(105 * j + 83, 105 * i + 134, 105, 105);
//给图片添加边框
//0:表示让图片凸起来
//1:表示让图片凹下去
jLabel.setBorder(new BevelBorder(BevelBorder.LOWERED));
//把管理容器添加到界面中
this.getContentPane().add(jLabel);
}
}


//添加背景图片
JLabel background = new JLabel(new ImageIcon("puzzlegame\\image\\background.png"));
background.setBounds(40, 40, 508, 560);
//把背景图片添加到界面当中
this.getContentPane().add(background);


//刷新一下界面
this.getContentPane().repaint();


}

//松开按键的时候会调用这个方法
@Override
public void keyReleased(KeyEvent e) {
//判断游戏是否胜利,如果胜利,此方法需要直接结束,不能再执行下面的移动代码了
if(victory()){
//结束方法
return;
}
//对上,下,左,右进行判断
//左:37 上:38 右:39 下:40
int code = e.getKeyCode();
System.out.println(code);
if (code == 37) {
System.out.println("向左移动");
if(y == 3){
return;
}
//逻辑:
//把空白方块右方的数字往左移动
data[x][y] = data[x][y + 1];
data[x][y + 1] = 0;
y++;
//每移动一次,计数器就自增一次。
step++;
//调用方法按照最新的数字加载图片
initImage();

} else if (code == 38) {
System.out.println("向上移动");
if(x == 3){
//表示空白方块已经在最下方了,他的下面没有图片再能移动了
return;
}
//逻辑:
//把空白方块下方的数字往上移动
//x,y 表示空白方块
//x + 1, y 表示空白方块下方的数字
//把空白方块下方的数字赋值给空白方块
data[x][y] = data[x + 1][y];
data[x + 1][y] = 0;
x++;
//每移动一次,计数器就自增一次。
step++;
//调用方法按照最新的数字加载图片
initImage();
} else if (code == 39) {
System.out.println("向右移动");
if(y == 0){
return;
}
//逻辑:
//把空白方块左方的数字往右移动
data[x][y] = data[x][y - 1];
data[x][y - 1] = 0;
y--;
//每移动一次,计数器就自增一次。
step++;
//调用方法按照最新的数字加载图片
initImage();
} else if (code == 40) {
System.out.println("向下移动");
if(x == 0){
return;
}
//逻辑:
//把空白方块上方的数字往下移动
data[x][y] = data[x - 1][y];
data[x - 1][y] = 0;
x--;
//每移动一次,计数器就自增一次。
step++;
//调用方法按照最新的数字加载图片
initImage();
}else if(code == 65){
initImage();
}else if(code == 87){
data = new int[][]{
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}
};
initImage();
}
}
...
}

其他功能

业务分析

  • 完成重新开始、关闭游戏、关于我们。这三个都是在菜单上的,所以可以一起完成
  • 重新开始:点击之后,重新打乱图片,计步器清零
  • 关闭游戏:点击之后,全部关闭
  • 关于我们:点击之后出现公众号二维码

实现步骤

  1. 给菜单上的每个选项添加点击事件
  2. 在actionPerformed方法中实现对应的逻辑即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Override
public void actionPerformed(ActionEvent e) {
//获取当前被点击的条目对象
Object obj = e.getSource();
//判断
if(obj == replayItem){
System.out.println("重新游戏");
//计步器清零
step = 0;
//再次打乱二维数组中的数据
initData();
//重新加载图片
initImage();
}else if(obj == reLoginItem){
System.out.println("重新登录");
//关闭当前的游戏界面
this.setVisible(false);
//打开登录界面
new LoginJFrame();
}else if(obj == closeItem){
System.out.println("关闭游戏");
//直接关闭虚拟机即可
System.exit(0);
}else if(obj == accountItem){
System.out.println("公众号");

//创建一个弹框对象
JDialog jDialog = new JDialog();
//创建一个管理图片的容器对象JLabel
JLabel jLabel = new JLabel(new ImageIcon("puzzlegame\\image\\about.png"));
//设置位置和宽高
jLabel.setBounds(0,0,258,258);
//把图片添加到弹框当中
jDialog.getContentPane().add(jLabel);
//给弹框设置大小
jDialog.setSize(344,344);
//让弹框置顶
jDialog.setAlwaysOnTop(true);
//让弹框居中
jDialog.setLocationRelativeTo(null);
//弹框不关闭则无法操作下面的界面
jDialog.setModal(true);
//让弹框显示出来
jDialog.setVisible(true);
}
}

游戏完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
public class GameJFrame extends JFrame implements KeyListener,ActionListener{
//JFrame 界面,窗体
//子类呢?也表示界面,窗体
//规定:GameJFrame这个界面表示的就是游戏的主界面
//以后跟游戏相关的所有逻辑都写在这个类中

//创建一个二维数组
//目的:用来管理数据
//加载图片的时候,会根据二维数组中的数据进行加载
int[][] data = new int[4][4];

//记录空白方块在二维数组中的位置
int x = 0;
int y = 0;

//定义一个变量,记录当前展示图片的路径
String path = "puzzlegame\\image\\animal\\animal3\\";

//定义一个二维数组,存储正确的数据
int[][] win = {
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}
};

//定义变量用来统计步数
int step = 0;

//创建选项下面的条目对象
JMenuItem replayItem = new JMenuItem("重新游戏");
JMenuItem reLoginItem = new JMenuItem("重新登录");
JMenuItem closeItem = new JMenuItem("关闭游戏");
JMenuItem accountItem = new JMenuItem("公众号");

public GameJFrame() {
//初始化界面
initJFrame();

//初始化菜单
initJMenuBar();

//初始化数据(打乱)
initData();

//初始化图片(根据打乱之后的结果去加载图片)
initImage();

//让界面显示出来,建议写在最后
this.setVisible(true);

}

//初始化数据(打乱)
private void initData() {
//1.定义一个一维数组
int[] tempArr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
//2.打乱数组中的数据的顺序
//遍历数组,得到每一个元素,拿着每一个元素跟随机索引上的数据进行交换
Random r = new Random();
for (int i = 0; i < tempArr.length; i++) {
//获取到随机索引
int index = r.nextInt(tempArr.length);
//拿着遍历到的每一个数据,跟随机索引上的数据进行交换
int temp = tempArr[i];
tempArr[i] = tempArr[index];
tempArr[index] = temp;
}

/*
*
* 5 6 8 9
* 10 11 15 1
* 4 7 12 13
* 2 3 0 14
*
* 5 6 8 9 10 11 15 1 4 7 12 13 2 3 0 14
* */

//4.给二维数组添加数据
//遍历一维数组tempArr得到每一个元素,把每一个元素依次添加到二维数组当中
for (int i = 0; i < tempArr.length; i++) {
if (tempArr[i] == 0) {
x = i / 4;
y = i % 4;
}
data[i / 4][i % 4] = tempArr[i]; // 这行是把一维数组的数据分配到二维数组的指定位置去
}
}

//初始化图片
//添加图片的时候,就需要按照二维数组中管理的数据添加图片
private void initImage() {

//清空原本已经出现的所有图片
this.getContentPane().removeAll();

if (victory()) {
//显示胜利的图标
JLabel winJLabel = new JLabel(new ImageIcon("C:\\Users\\moon\\IdeaProjects\\basic-code\\puzzlegame\\image\\win.png"));
winJLabel.setBounds(203,283,197,73);
this.getContentPane().add(winJLabel);
}

JLabel stepCount = new JLabel("步数:" + step);
stepCount.setBounds(50,30,100,20);
this.getContentPane().add(stepCount);

//路径分为两种:
//绝对路径:一定是从盘符开始的。C:\ D:\
//相对路径:不是从盘符开始的
//相对路径相对当前项目而言的。 aaa\\bbb
//在当前项目下,去找aaa文件夹,里面再找bbb文件夹。

//细节:
//先加载的图片在上方,后加载的图片塞在下面。
//外循环 --- 把内循环重复执行了4次。
for (int i = 0; i < 4; i++) {
//内循环 --- 表示在一行添加4张图片
for (int j = 0; j < 4; j++) {
//获取当前要加载图片的序号
int num = data[i][j];
//创建一个JLabel的对象(管理容器)
JLabel jLabel = new JLabel(new ImageIcon(path + num + ".jpg"));
//指定图片位置
jLabel.setBounds(105 * j + 83, 105 * i + 134, 105, 105);
//给图片添加边框
//0:表示让图片凸起来
//1:表示让图片凹下去
jLabel.setBorder(new BevelBorder(BevelBorder.LOWERED));
//把管理容器添加到界面中
this.getContentPane().add(jLabel);
}
}

//添加背景图片
JLabel background = new JLabel(new ImageIcon("puzzlegame\\image\\background.png"));
background.setBounds(40, 40, 508, 560);
//把背景图片添加到界面当中
this.getContentPane().add(background);

//刷新一下界面
this.getContentPane().repaint();
}

private void initJMenuBar() {
//创建整个的菜单对象
JMenuBar jMenuBar = new JMenuBar();
//创建菜单上面的两个选项的对象 (功能 关于我们)
JMenu functionJMenu = new JMenu("功能");
JMenu aboutJMenu = new JMenu("关于我们");

//将每一个选项下面的条目添加到选项当中
functionJMenu.add(replayItem);
functionJMenu.add(reLoginItem);
functionJMenu.add(closeItem);

aboutJMenu.add(accountItem);

//给条目绑定事件
replayItem.addActionListener(this);
reLoginItem.addActionListener(this);
closeItem.addActionListener(this);
accountItem.addActionListener(this);

//将菜单里面的两个选项添加到菜单当中
jMenuBar.add(functionJMenu);
jMenuBar.add(aboutJMenu);

//给整个界面设置菜单
this.setJMenuBar(jMenuBar);
}

private void initJFrame() {
//设置界面的宽高
this.setSize(603, 680);
//设置界面的标题
this.setTitle("拼图单机版 v1.0");
//设置界面置顶
this.setAlwaysOnTop(true);
//设置界面居中
this.setLocationRelativeTo(null);
//设置关闭模式
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//取消默认的居中放置,只有取消了才会按照XY轴的形式添加组件
this.setLayout(null);
//给整个界面添加键盘监听事件
this.addKeyListener(this);
}

@Override
public void keyTyped(KeyEvent e) {

}

//按下不松时会调用这个方法
@Override
public void keyPressed(KeyEvent e) {
int code = e.getKeyCode();
if (code == 65){
//把界面中所有的图片全部删除
this.getContentPane().removeAll();
//加载第一张完整的图片
JLabel all = new JLabel(new ImageIcon(path + "all.jpg"));
all.setBounds(83,134,420,420);
this.getContentPane().add(all);
//加载背景图片
//添加背景图片
JLabel background = new JLabel(new ImageIcon("puzzlegame\\image\\background.png"));
background.setBounds(40, 40, 508, 560);
//把背景图片添加到界面当中
this.getContentPane().add(background);
//刷新界面
this.getContentPane().repaint();
}
}

//松开按键的时候会调用这个方法
@Override
public void keyReleased(KeyEvent e) {
//判断游戏是否胜利,如果胜利,此方法需要直接结束,不能再执行下面的移动代码了
if(victory()){
//结束方法
return;
}
//对上,下,左,右进行判断
//左:37 上:38 右:39 下:40
int code = e.getKeyCode();
System.out.println(code);
if (code == 37) {
System.out.println("向左移动");
if(y == 3){
return;
}
//逻辑:
//把空白方块右方的数字往左移动
data[x][y] = data[x][y + 1];
data[x][y + 1] = 0;
y++;
//每移动一次,计数器就自增一次。
step++;
//调用方法按照最新的数字加载图片
initImage();

} else if (code == 38) {
System.out.println("向上移动");
if(x == 3){
//表示空白方块已经在最下方了,他的下面没有图片再能移动了
return;
}
//逻辑:
//把空白方块下方的数字往上移动
//x,y 表示空白方块
//x + 1, y 表示空白方块下方的数字
//把空白方块下方的数字赋值给空白方块
data[x][y] = data[x + 1][y];
data[x + 1][y] = 0;
x++;
//每移动一次,计数器就自增一次。
step++;
//调用方法按照最新的数字加载图片
initImage();
} else if (code == 39) {
System.out.println("向右移动");
if(y == 0){
return;
}
//逻辑:
//把空白方块左方的数字往右移动
data[x][y] = data[x][y - 1];
data[x][y - 1] = 0;
y--;
//每移动一次,计数器就自增一次。
step++;
//调用方法按照最新的数字加载图片
initImage();
} else if (code == 40) {
System.out.println("向下移动");
if(x == 0){
return;
}
//逻辑:
//把空白方块上方的数字往下移动
data[x][y] = data[x - 1][y];
data[x - 1][y] = 0;
x--;
//每移动一次,计数器就自增一次。
step++;
//调用方法按照最新的数字加载图片
initImage();
} else if(code == 65){
initImage();
}else if(code == 87){
data = new int[][]{
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}
};
initImage();
}
}

//判断data数组中的数据是否跟win数组中相同
//如果全部相同,返回true。否则返回false
public boolean victory(){
for (int i = 0; i < data.length; i++) {
//i : 依次表示二维数组 data里面的索引
//data[i]:依次表示每一个一维数组
for (int j = 0; j < data[i].length; j++) {
if(data[i][j] != win[i][j]) {
//只要有一个数据不一样,则返回false
return false;
}
}
}
//循环结束表示数组遍历比较完毕,全都一样返回true
return true;
}

@Override
public void actionPerformed(ActionEvent e) {
//获取当前被点击的条目对象
Object obj = e.getSource();
//判断
if(obj == replayItem){
System.out.println("重新游戏");
//计步器清零
step = 0;
//再次打乱二维数组中的数据
initData();
//重新加载图片
initImage();
}else if(obj == reLoginItem){
System.out.println("重新登录");
//关闭当前的游戏界面
this.setVisible(false);
//打开登录界面
new LoginJFrame();
}else if(obj == closeItem){
System.out.println("关闭游戏");
//直接关闭虚拟机即可
System.exit(0);
}else if(obj == accountItem){
System.out.println("公众号");

//创建一个弹框对象
JDialog jDialog = new JDialog();
//创建一个管理图片的容器对象JLabel
JLabel jLabel = new JLabel(new ImageIcon("puzzlegame\\image\\about.png"));
//设置位置和宽高
jLabel.setBounds(0,0,258,258);
//把图片添加到弹框当中
jDialog.getContentPane().add(jLabel);
//给弹框设置大小
jDialog.setSize(344,344);
//让弹框置顶
jDialog.setAlwaysOnTop(true);
//让弹框居中
jDialog.setLocationRelativeTo(null);
//弹框不关闭则无法操作下面的界面
jDialog.setModal(true);
//让弹框显示出来
jDialog.setVisible(true);
}
}
}

Day18 API(常见API,对象克隆)

学习目标

  • 能够熟练使用Math类中的常见方法
  • 能够熟练使用System类中的常见方法
  • 能够理解Object类的常见方法作用
  • 能够熟练使用Objects类的常见方法
  • 能够熟练使用BigInteger类的常见方法
  • 能够熟练使用BigDecimal类的常见方法

Math类(了解内容)

  • Math类所在包为java.lang包,因此在使用的时候不需要进行导包。并且Math类被final修饰了,因此该类是不能被继承的
  • Math类包含:执行基本数字运算的方法,使用Math类完成基本的数学运算
  • 不能直接通过new关键字去创建Math类的对象(API文档中没有体现可用的构造方法)
  • 但是文档显示Math类中所有方法都是静态的→可以直接通过类名Math去调用

常见方法

1
2
3
4
5
6
7
8
public static int abs(int a)          // 返回参数的绝对值
public static double ceil(double a) // 返回大于或等于参数的最小整数
public static double floor(double a) // 返回小于或等于参数的最大整数
public static int round(float a) // 按照四舍五入返回最接近参数的int类型值
public static int max(int a,int b) // 获取两个int值中的较大值
public static int min(int a,int b) // 获取两个int值中的较小值
public static double pow (double a,double b) // 计算a的b次幂的值
public static double random() // 返回一个[0.0,1.0)的随机值

案例演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class MathDemo01 {

public static void main(String[] args) {

// public static int abs(int a) 返回参数的绝对值
System.out.println("-2的绝对值为:" + Math.abs(-2)); // 2
System.out.println("2的绝对值为:" + Math.abs(2)); // 2

// public static double ceil(double a) 返回大于或等于参数的最小整数
System.out.println("大于或等于23.45的最小整数位:" + Math.ceil(23.45)); // 24.0
System.out.println("大于或等于-23.45的最小整数位:" + Math.ceil(-23.45)); // -23.0

// public static double floor(double a) 返回小于或等于参数的最大整数
System.out.println("小于或等于23.45的最大整数位:" + Math.floor(23.45)); // 23.0
System.out.println("小于或等于-23.45的最大整数位:" + Math.floor(-23.45)); // -24.0

// public static int round(float a) 按照四舍五入返回最接近参数的int
System.out.println("23.45四舍五入的结果为:" + Math.round(23.45)); // 23
System.out.println("23.55四舍五入的结果为:" + Math.round(23.55)); // 24

// public static int max(int a,int b) 返回两个int值中的较大值
System.out.println("23和45的最大值为: " + Math.max(23, 45)); // 45

// public static int min(int a,int b) 返回两个int值中的较小值
System.out.println("12和34的最小值为: " + Math.min(12 , 34)); // 12

// public static double pow (double a,double b)返回a的b次幂的值
System.out.println("2的3次幂计算结果为: " + Math.pow(2,3)); // 8.0

// public static double random()返回值为double的正值,[0.0,1.0)
System.out.println("获取到的0-1之间的随机数为: " + Math.random()); // 0.7322484131745958
}

}

算法小题(质数)

  • 需求:判断一个数是否为一个质数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MathDemo2 {
public static void main(String[] args) {
//判断一个数是否为一个质数
System.out.println(isPrime(997));
//997 2~996 995次
}

public static boolean isPrime(int number) {
int count = 0;
for (int i = 2; i <= Math.sqrt(number); i++) {
count++;
if (number % i == 0) {
return false;
}
}
System.out.println(count);
return true;
}
}

算法小题(自幂数)

  • 自幂数,一个n位自然数等于自身各个数位上数字的n次幂之和
    • 举例1:三位数 1^3 + 5^3 + 3^3 = 153
    • 举例2:四位数 1^4 + 6^4 + 3^4 + 4^3 = 1634
  • 如果自幂数是:
    • 一位自幂数,也叫做:独身数
    • 三位自幂数:水仙花数 四位自幂数:四叶玫瑰数
    • 五位自幂数:五角星数 六位自幂数:六合数
    • 七位自幂数:北斗七星数 八位自幂数:八仙数
    • 九位自幂数:九九重阳数 十位自幂数:十全十美数
  • 要求1:统计一共有多少个水仙花数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//水仙花数:100 ~ 999
int count = 0;
//得到每一个三位数
for (int i = 100; i <= 999; i++) {
//个位 十位 百位
int ge = i % 10;
int shi = i / 10 % 10;
int bai = i / 100 % 10;
//判断:
//每一位的三次方之和 跟本身 进行比较。
double sum = Math.pow(ge, 3) + Math.pow(shi, 3) + Math.pow(bai, 3);
if (sum == i) {
count++;
//System.out.println(i);

System.out.println(count);
}
}

课后练习

  • 要求2:(课后作业)证明没有两位的自幂数。
  • 要求3:(课后作业)分别统计有多少个四叶玫瑰数和五角星数。(答案:都是3个)

System类

  • System类所在包为java.lang包,因此在使用的时候不需要进行导包。并且System类被final修饰了,因此该类是不能被继承的
  • System包含了系统操作的一些常用的方法
  • 在API文档中没有体现可用的构造方法,因此我们就不能直接通过new关键字去创建System类的对象。
  • System类中的方法都是静态的,因此在使用的时候我们可以直接通过类名去调用(Nested Class Summary内部类或者内部接口的描述)

常见方法

1
2
3
public static long currentTimeMillis()      // 获取当前时间所对应的毫秒值(当前时间为0时区所对应的时间即就是英国格林尼治天文台旧址所在位置)
public static void exit(int status) // 终止当前正在运行的Java虚拟机,0表示正常退出,非零表示异常退出
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length); // 进行数值元素copy

案例演示

案例1:演示currentTimeMillis方法
1
2
3
4
5
6
7
8
public class SystemDemo01 {
public static void main(String[] args) {
// 获取当前时间所对应的毫秒值
long millis = System.currentTimeMillis();
// 输出结果
System.out.println("当前时间所对应的毫秒值为:" + millis);
}
}
  • 获取到当前时间的毫秒值的意义
    • 常常来需要统计某一段代码的执行时间。
    • 此时就可以在执行这段代码之前获取一次时间,在执行完毕以后再次获取一次系统时间,然后计算两个时间的差值
    • 这个差值就是这段代码执行完毕以后所需要的时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class SystemDemo2 {
public static void main(String[] args) {
//计算某一段代码的执行时间

long start = System.currentTimeMillis();

for (int i = 1; i <= 100000; i++) {
boolean flag = isPrime2(i);
if (flag) {
System.out.println(i);
}
}
long end = System.currentTimeMillis();
//获取程序运行的总时间
System.out.println(end - start); //方式一:1514 毫秒 方式二:71毫秒
}

//以前判断是否为质数的方式
public static boolean isPrime1(int number) {
for (int i = 2; i < number; i++) {
if (number % i == 0) {
return false;
}
}
return true;
}

//改进之后判断是否为质数的方式(效率高)
public static boolean isPrime2(int number) {
for (int i = 2; i <= Math.sqrt(number); i++) {
if (number % i == 0) {
return false;
}
}
return true;
}
}
案例2:演示exit方法
1
2
3
4
5
6
7
8
9
10
public class SystemDemo01 {
public static void main(String[] args) {
// 输出
System.out.println("程序开始执行了.....");
// 终止JVM
System.exit(0);
// 输出
System.out.println("程序终止了..........");
}
}
  • 控制台只输出了"程序开始了…",由于JVM终止了,因此输出"程序终止了…"这段代码没有被执行
案例3:演示arraycopy方法
  • 方法参数说明
1
2
3
4
5
6
// src:          源数组
// srcPos: 源数值的开始位置
// dest: 目标数组
// destPos: 目标数组开始位置
// length: 要复制的元素个数
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SystemDemo01 {
public static void main(String[] args) {
// 定义源数组
int[] srcArray = {23 , 45 , 67 , 89 , 14 , 56 } ;
// 定义目标数组
int[] desArray = new int[10] ;

// 进行数组元素的copy: 把srcArray数组中从0索引开始的3个元素,从desArray数组中的1索引开始复制过去
System.arraycopy(srcArray , 0 , desArray , 1 , 3);

// 遍历目标数组
for(int x = 0 ; x < desArray.length ; x++) {
if(x != desArray.length - 1) {
System.out.print(desArray[x] + ", ");
}else {
System.out.println(desArray[x]);
}
// 0, 23, 45, 67, 0, 0, 0, 0, 0, 0
}
}
}
  • 也可以用这个函数完成数组元素的删除操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 第一次尝试
public class SystemDemo02 {
public static void main(String[] args) {
// 定义一个数组
int[] srcArray = {23 , 45 , 67 , 89 , 14 , 56 } ;
// 删除数组中第3个元素(67):要删除67这个元素,我们只需要将67后面的其他元素依次向前进行移动即可
System.arraycopy(srcArray , 3 , srcArray , 2 , 3);
// 遍历srcArray数组
for(int x = 0 ; x < srcArray.length ; x++) {
if(x != desArray.length - 1) {
System.out.print(srcArray[x] + ", ");
}else {
System.out.println(srcArray[x]);
}
}
}
}
// 得到结果:23, 45, 89, 14, 56, 56
// 多了一个56 所以要手动把最后一位设置为0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 第二次尝试(第一次尝试的完善)
public class SystemDemo02 {
public static void main(String[] args) {
// 定义一个数组
int[] srcArray = {23 , 45 , 67 , 89 , 14 , 56 } ;
// 删除数组中第3个元素(67):要删除67这个元素,我们只需要将67后面的其他元素依次向前进行移动即可
System.arraycopy(srcArray , 3 , srcArray , 2 , 3);
// 将最后一个位置的元素设置为0
srcArray[srcArray.length - 1] = 0 ;
// 遍历srcArray数组
for(int x = 0 ; x < srcArray.length ; x++) {
if(x != srcArray.length - 1 ) {
System.out.print(srcArray[x] + ", ");
}else {
System.out.println(srcArray[x]);
}
}
}
}
// 元素"67"已经被删除掉了。67后面的其他元素依次向前进行移动了一位
arraycopy方法底层细节
  1. 如果数据源数组和目的地数组都是基本数据类型,那么两者的类型必须保持一致,否则会报错
  2. 在拷贝的时候需要考虑数组的长度,如果超出范围也会报错
  3. 如果数据源数组和目的地数组都是引用数据类型,那么子类类型可以赋值给父类类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
public class SystemDemo3 {
public static void main(String[] args) {
//public static void arraycopy(数据源数组,起始索引,目的地数组,起始索引,拷贝个数) 数组拷贝
//细节:
//1.如果数据源数组和目的地数组都是基本数据类型,那么两者的类型必须保持一致,否则会报错
//2.在拷贝的时候需要考虑数组的长度,如果超出范围也会报错
//3.如果数据源数组和目的地数组都是引用数据类型,那么子类类型可以赋值给父类类型

Student s1 = new Student("zhangsan", 23);
Student s2 = new Student("lisi", 24);
Student s3 = new Student("wangwu", 25);

Student[] arr1 = {s1, s2, s3};
Person[] arr2 = new Person[3];
//把arr1中对象的地址值赋值给arr2中
System.arraycopy(arr1, 0, arr2, 0, 3);

//遍历数组arr2
for (int i = 0; i < arr2.length; i++) {
Student stu = (Student) arr2[i];
System.out.println(stu.getName() + "," + stu.getAge());
}
}
}

class Person {
private String name;
private int age;

public Person() {
}

public Person(String name, int age) {
this.name = name;
this.age = age;
}

/**
* 获取
*
* @return name
*/
public String getName() {
return name;
}

/**
* 设置
*
* @param name
*/
public void setName(String name) {
this.name = name;
}

/**
* 获取
*
* @return age
*/
public int getAge() {
return age;
}

/**
* 设置
*
* @param age
*/
public void setAge(int age) {
this.age = age;
}

public String toString() {
return "Person{name = " + name + ", age = " + age + "}";
}
}


class Student extends Person {

public Student() {
}

public Student(String name, int age) {
super(name, age);
}
}

Runtime

  • Runtime表示Java中运行时对象,可以获取到程序运行时设计到的一些信息

常见方法

1
2
3
4
5
6
7
public static Runtime getRuntime()    //当前系统的运行环境对象
public void exit(int status) //停止虚拟机
public int availableProcessors() //获得CPU的线程数
public long maxMemory() //JVM能从系统中获取总内存大小(单位byte)
public long totalMemory() //JVM已经从系统中获取总内存大小(单位byte)
public long freeMemory() //JVM剩余内存大小(单位byte)
public Process exec(String command) //运行cmd命令
  • 代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class RunTimeDemo1 {
public static void main(String[] args) throws IOException {
/*
public static Runtime getRuntime() 当前系统的运行环境对象
public void exit(int status) 停止虚拟机
public int availableProcessors() 获得CPU的线程数
public long maxMemory() JVM能从系统中获取总内存大小(单位byte)
public long totalMemory() JVM已经从系统中获取总内存大小(单位byte)
public long freeMemory() JVM剩余内存大小(单位byte)
public Process exec(string command) 运行cmd命令
*/

//1.获取Runtime的对象
//Runtime r1 =Runtime.getRuntime();

//2.exit 停止虚拟机
//Runtime.getRuntime().exit(0);
//System.out.println("看看我执行了吗?");


//3.获得CPU的线程数
System.out.println(Runtime.getRuntime().availableProcessors());//8
//4.总内存大小,单位byte字节
System.out.println(Runtime.getRuntime().maxMemory() / 1024 / 1024);//4064
//5.已经获取的总内存大小,单位byte字节
System.out.println(Runtime.getRuntime().totalMemory() / 1024 / 1024);//254
//6.剩余内存大小
System.out.println(Runtime.getRuntime().freeMemory() / 1024 / 1024);//251

//7.运行cmd命令
//shutdown :关机
//加上参数才能执行
//-s :默认在1分钟之后关机
//-s -t 指定时间 : 指定关机时间
//-a :取消关机操作
//-r: 关机并重启
Runtime.getRuntime().exec("shutdown -s -t 3600");
}
}

案例:恶搞好基友

  • **需求:**界面上方按钮默认隐藏,界面中间有一个提示文本和三个按钮,当你的好基友点击中间三个按钮的时候就在N秒之后关机,不同的按钮N的值不一样,任意一个按钮被点击之后,上方了按钮出现。当点击上方按钮之后取消关机任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
public class Test {
public static void main(String[] args) {
new MyJframe();
}
}
public class MyJframe extends JFrame implements ActionListener {

JButton yesBut = new JButton("帅爆了");
JButton midBut = new JButton("一般般吧");
JButton noBut = new JButton("不帅,有点磕碜");
JButton dadBut = new JButton("饶了我吧!");

//决定了上方的按钮是否展示
boolean flag = false;

public MyJframe() {
initJFrame();

initView();

//显示
this.setVisible(true);
}

private void initView() {

this.getContentPane().removeAll();

if (flag) {
//展示按钮
dadBut.setBounds(50, 20, 100, 30);
dadBut.addActionListener(this);
this.getContentPane().add(dadBut);
}

JLabel text = new JLabel("你觉得自己帅吗?");
text.setFont(new Font("微软雅黑", 0, 30));
text.setBounds(120, 150, 300, 50);

yesBut.setBounds(200, 250, 100, 30);
midBut.setBounds(200, 325, 100, 30);
noBut.setBounds(160, 400, 180, 30);

yesBut.addActionListener(this);
midBut.addActionListener(this);
noBut.addActionListener(this);

this.getContentPane().add(text);
this.getContentPane().add(yesBut);
this.getContentPane().add(midBut);
this.getContentPane().add(noBut);

this.getContentPane().repaint();
}

private void initJFrame() {
//设置宽高
this.setSize(500, 600);
//设置标题
this.setTitle("恶搞好基友");
//设置关闭模式
this.setDefaultCloseOperation(3);
//置顶
this.setAlwaysOnTop(true);
//居中
this.setLocationRelativeTo(null);
//取消内部默认布局
this.setLayout(null);
}

@Override
public void actionPerformed(ActionEvent e) {
Object obj = e.getSource();
if (obj == yesBut) {
//给好基友一个弹框
showJDialog("xxx,你太自信了,给你一点小惩罚");
try {
Runtime.getRuntime().exec("shutdown -s -t 3600");
} catch (IOException ioException) {
ioException.printStackTrace();
}
flag = true;
initView();

} else if (obj == midBut) {
System.out.println("你的好基友点击了一般般吧");

//给好基友一个弹框
showJDialog("xxx,你还是太自信了,也要给你一点小惩罚");

try {
Runtime.getRuntime().exec("shutdown -s -t 7200");
} catch (IOException ioException) {
ioException.printStackTrace();
}

flag = true;
initView();

} else if (obj == noBut) {
System.out.println("你的好基友点击了不帅");

//给好基友一个弹框
showJDialog("xxx,你还是有一点自知之明的,也要给你一点小惩罚");

try {
Runtime.getRuntime().exec("shutdown -s -t 1800");
} catch (IOException ioException) {
ioException.printStackTrace();
}

flag = true;
initView();
} else if (obj == dadBut) {
//给好基友一个弹框
showJDialog("xxx,这次就饶了你~");

try {
Runtime.getRuntime().exec("shutdown -a");
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}

public void showJDialog(String content) {
//创建一个弹框对象
JDialog jDialog = new JDialog();
//给弹框设置大小
jDialog.setSize(200, 150);
//让弹框置顶
jDialog.setAlwaysOnTop(true);
//让弹框居中
jDialog.setLocationRelativeTo(null);
//弹框不关闭永远无法操作下面的界面
jDialog.setModal(true);

//创建Jlabel对象管理文字并添加到弹框当中
JLabel warning = new JLabel(content);
warning.setBounds(0, 0, 200, 150);
jDialog.getContentPane().add(warning);

//让弹框展示出来
jDialog.setVisible(true);
}
}

Object类

  • Object类所在包是java.lang包
  • Object 是类层次结构的根,每个类都可以将 Object 作为超类。所有类都直接或者间接的继承自该类;换句话说,该类所具备的方法,其他所有类都继承了
  • Object类中提供了一个无参构造方法Object()
  • 类Object是类层次结构的根类,每个类都使用Object作为超类,所有对象(包括数组)都实现这个类的方法

常见方法

1
2
3
4
public String toString()    // 返回该对象的字符串表示形式(可以看做是对象的内存地址值)
// 比较两个对象地址值是否相等;true表示相同,false表示不相同
public boolean equals(Object obj)
protected Object clone() // 对象克隆

案例演示

案例1:toString方法
  • 实现步骤:
  1. 创建一个学生类,提供两个成员变量(name , age);并且提供对应的无参构造方法和有参构造方法以及get/set方法
  2. 创建一个测试类(ObjectDemo01),在测试类的main方法中去创建学生对象,然后调用该对象的toString方法获取该对象的字符串表现形式,并将结果进行输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Student {

private String name ; // 姓名
private String age ; // 年龄

// 无参构造方法和有参构造方法以及get和set方法略
...
}

public class ObjectDemo01 {

public static void main(String[] args) {

// 创建学生对象
Student s1 = new Student("itheima" , "14") ;

// 调用toString方法获取s1对象的字符串表现形式
String result1 = s1.toString();

// 输出结果
System.out.println("s1对象的字符串表现形式为:" + result1);
}
}

// 得到结果:
// s1对象的字符串表现形式为:com.itheima.api.system.demo04.Student@3f3afe78
1
2
3
4
5
6
7
// Object类中toString方法的源码↓↓
public String toString() { // Object类中toString方法的源码定义
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

// 其中getClass().getName()对应的结果就是:com.itheima.api.system.demo04.Student;
// Integer.toHexString(hashCode())对应的结果就是3f3afe78
  • 将"com.itheima.api.system.demo04.Student@3f3afe78"这一部分称之为对象的内存地址值

  • 一般情况下获取对象的内存地址值没有太大的意义。获取对象的成员变量的字符串拼接形式才算有意义

  • 需要在Student类中重写Object的toString方法

在idea中实现Object类的toString方法重写
  1. 在空白处使用快捷键:alt + insert。此时会弹出如下的对话框
  2. 选择toString,此时会弹出对话框
  3. 同时选择name和age属性,点击OK。此时就会完成toString方法的重写
1
2
3
4
5
6
7
8
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
// 这段代码就是把Student类中的成员变量进行了字符串的拼接
  • 重写toString方法的意义就是以良好的格式,更方便的展示对象中的属性值
小结
  1. 在通过输出语句输出一个对象时,默认调用的就是toString()方法
  2. 输出地址值一般没有意义,我们可以通过重写toString方法去输出对应的成员变量信息(快捷键:atl + insert , 空白处 右键 -> Generate -> 选择toString)
  3. toString方法的作用:以良好的格式,更方便的展示对象中的属性值
  4. 一般情况下Jdk所提供的类都会重写Object类中的toString方法
案例2:演示equals方法
  • 在测试类(ObjectDemo02)的main方法中,创建两个学生对象,然后比较两个对象是否相同
1
2
3
4
5
6
7
8
9
10
11
12
public class ObjectDemo02 {

public static void main(String[] args) {
// 创建两个学生对象
Student s1 = new Student("itheima" , "14") ;
Student s2 = new Student("itheima" , "14") ;

// 比较两个对象是否相等
System.out.println(s1 == s2); // false
}
}
// 因为"=="号比较的是对象的地址值,而我们通过new关键字创建了两个对象,它们的地址值是不相同的。因此比较结果就是false
  • 尝试调用Object类中的equals方法进行比较
1
2
3
4
5
6
// 调用equals方法比较两个对象是否相等
boolean result = s1.equals(s2);

// 输出结果
System.out.println(result);
// false
  • Object类中equals方法的源码
1
2
3
public boolean equals(Object obj) {     // Object类中的equals方法的源码
return (this == obj);
}
  • 默认情况下equals方法比较的也是对象的地址值。比较内存地址值一般情况下是没有意义的,我们希望比较的是对象的属性,如果两个对象的属性相同,我们认为就是同一个对象
  • 那么要比较对象的属性,我们就需要在Student类中重写Object类中的equals方法
在idea中实现Object类的equals方法重写
  1. 在空白处使用快捷键:alt + insert。此时会弹出对话框
  2. 选择equals() and hashCode()方法,此时会弹出对话框
  3. 点击next,会弹出对话框
  4. 选择name和age属性点击next,此时就会弹出对话框;取消name和age属性(因为此时选择的是在生成hashCode方法时所涉及到的属性,关于hashCode方法后期再做重点介绍)
  5. 点击Finish完成生成操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 生成的equals方法和hashCode方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name) && Objects.equals(age, student.age); // 比较的是对象的name属性值和age属性值
}
// 返回:true

@Override
public int hashCode() {
return 0;
}
小结
  1. 默认情况下equals方法比较的是对象的地址值
  2. 比较对象的地址值是没有意义的,因此一般情况下我们都会重写Object类中的equals方法
案例3:对象克隆
  • 把A对象的属性值完全拷贝给B对象,也叫对象拷贝,对象复制
对象克隆的分类

浅克隆和深克隆

  • 浅克隆

    • 不管对象内部的属性是基本数据类型还是引用数据类型,都完全拷贝过来
    • 基本数据类型拷贝过来的是具体的数据,引用数据类型拷贝过来的是地址值。
    • Object类默认的是浅克隆
  • 深克隆

    • 基本数据类型拷贝过来,字符串复用,引用数据类型会重新创建新的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package com.itheima.a04objectdemo;

public class ObjectDemo4 {
public static void main(String[] args) throws CloneNotSupportedException {
// protected object clone(int a) 对象克隆

//1.先创建一个对象
int[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0};
User u1 = new User(1, "zhangsan", "1234qwer", "girl11", data);

//2.克隆对象
//细节:
//方法在底层会帮我们创建一个对象,并把原对象中的数据拷贝过去。
//书写细节:
//1.重写Object中的clone方法
//2.让javabean类实现Cloneable接口
//3.创建原对象并调用clone就可以了
//User u2 =(User)u1.clone();

//验证一件事情:Object中的克隆是浅克隆
//想要进行深克隆,就需要重写clone方法并修改里面的方法体
//int[] arr = u1.getData();
//arr[0] = 100;

//System.out.println(u1);
//System.out.println(u2);


//以后一般会用第三方工具进行克隆
//1.第三方写的代码导入到项目中
//2.编写代码
//Gson gson =new Gson();
//把对象变成一个字符串
//String s=gson.toJson(u1);
//再把字符串变回对象就可以了
//User user =gson.fromJson(s, User.class);

//int[] arr=u1.getData();
//arr[0] = 100;

//打印对象
//System.out.println(user);

}
}

package com.itheima.a04objectdemo;

import java.util.StringJoiner;


//Cloneable
//如果一个接口里面没有抽象方法
//表示当前的接口是一个标记性接口
//现在Cloneable表示一旦实现了,那么当前类的对象就可以被克降
//如果没有实现,当前类的对象就不能克隆
public class User implements Cloneable {
private int id;
private String username;
private String password;
private String path;
private int[] data;

public User() {
}

public User(int id, String username, String password, String path, int[] data) {
this.id = id;
this.username = username;
this.password = password;
this.path = path;
this.data = data;
}

/**
* 获取
*
* @return id
*/
public int getId() {
return id;
}

/**
* 设置
*
* @param id
*/
public void setId(int id) {
this.id = id;
}

/**
* 获取
*
* @return username
*/
public String getUsername() {
return username;
}

/**
* 设置
*
* @param username
*/
public void setUsername(String username) {
this.username = username;
}

/**
* 获取
*
* @return password
*/
public String getPassword() {
return password;
}

/**
* 设置
*
* @param password
*/
public void setPassword(String password) {
this.password = password;
}

/**
* 获取
*
* @return path
*/
public String getPath() {
return path;
}

/**
* 设置
*
* @param path
*/
public void setPath(String path) {
this.path = path;
}

/**
* 获取
*
* @return data
*/
public int[] getData() {
return data;
}

/**
* 设置
*
* @param data
*/
public void setData(int[] data) {
this.data = data;
}

public String toString() {
return "角色编号为:" + id + ",用户名为:" + username + "密码为:" + password + ", 游戏图片为:" + path + ", 进度:" + arrToString();
}


public String arrToString() {
StringJoiner sj = new StringJoiner(", ", "[", "]");

for (int i = 0; i < data.length; i++) {
sj.add(data[i] + "");
}
return sj.toString();
}

@Override
protected Object clone() throws CloneNotSupportedException {
//调用父类中的clone方法
//相当于让Java帮我们克隆一个对象,并把克隆之后的对象返回出去。

//先把被克隆对象中的数组获取出来
int[] data = this.data;
//创建新的数组
int[] newData =new int[data.length];
//拷贝数组中的数据
for (int i = 0; i < data.length; i++) {
newData[i] = data[i];
}
//调用父类中的方法克隆对象
User u=(User)super.clone();
//因为父类中的克隆方法是浅克隆,替换克隆出来对象中的数组地址值
u.data =newData;
return u;
}
}

Objects类

  • Objects类所在包是在java.util包下,因此在使用的时候需要进行导包
  • 并且Objects类是被final修饰的,因此该类不能被继承
  • Objects类提供了一些对象常见操作的方法。比如判断对象是否相等,判断对象是否为null等等
  • Objects类中无无参构造方法,因此我们不能使用new关键字去创建Objects的对象
  • 可以发现Objects类中所提供的方法都是静态的。因此我们可以通过类名直接去调用这些方法

常见方法

  • 重点学习的Objects类中的常见方法
1
2
3
4
public static String toString(Object o)       // 获取对象的字符串表现形式
public static boolean equals(Object a, Object b) // 比较两个对象是否相等
public static boolean isNull(Object obj) // 判断对象是否为null
public static boolean nonNull(Object obj) // 判断对象是否不为null
  • 了解的Objects类中的常见方法
1
2
3
4
5
public static <T> T requireNonNull(T obj)  // 检查对象是否不为null,如果为null直接抛出异常;如果不是null返回该对象;
public static <T> T requireNonNullElse(T obj, T defaultObj) // 检查对象是否不为null,如果不为null,返回该对象;如果为null返回defaultObj值
public static <T> T requireNonNullElseGet(T obj, Supplier<? extends T> supplier) // 检查对象是否不为null,如果不为null,返回该对象;如果 // 为null,返回由Supplier所提供的值

// T可以理解为是Object类型

案例演示

案例1:演示重点学习方法
  • 实现步骤:
    1. 创建一个学生类,提供两个成员变量(name , age);并且提供对应的无参构造方法和有参构造方法以及get/set方法,并且重写toString方法和equals方法
    2. 创建一个测试类(ObjectsDemo01), 在该类中编写测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class Student {

private String name ; // 姓名
private String age ; // 年龄

// 其他代码略
...
}

public class ObjectsDemo01 {

public static void main(String[] args) {

// 调用方法
method_04() ;

}

// 测试nonNull方法
public static void method_04() {

// 创建一个学生对象
Student s1 = new Student("itheima" , "14") ;

// 调用Objects类中的nonNull方法
boolean result = Objects.nonNull(s1);

// 输出结果
System.out.println(result);

}

// 测试isNull方法
public static void method_03() {

// 创建一个学生对象
Student s1 = new Student("itheima" , "14") ;

// 调用Objects类中的isNull方法
boolean result = Objects.isNull(s1);

// 输出结果
System.out.println(result);

}

// 测试equals方法
public static void method_02() {

// 创建两个学生对象
Student s1 = new Student("itheima" , "14") ;
Student s2 = new Student("itheima" , "14") ;

// 调用Objects类中的equals方法,比较两个对象是否相等
boolean result = Objects.equals(s1, s2); // 如果Student没有重写Object类中的equals方法,此处比较的还是对象的地址值

// 输出结果
System.out.println(result);

}

// 测试toString方法
public static void method_01() {

// 创建一个学生对象
Student s1 = new Student("itheima" , "14") ;

// 调用Objects中的toString方法,获取s1对象的字符串表现形式
String result = Objects.toString(s1); // 如果Student没有重写Object类中的toString方法,此处还是返回的对象的地址值

// 输出结果
System.out.println(result);

}

}
案例2:演示需要了解的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class ObjectsDemo02 {

public static void main(String[] args) {

// 调用方法
method_03();

}

// 演示requireNonNullElseGet
public static void method_03() {

// 创建一个学生对象
Student s1 = new Student("itheima" , "14") ;

// 调用Objects对象的requireNonNullElseGet方法,该方法的第二个参数是Supplier类型的,查看源码我们发现Supplier是一个函数式接口,
// 那么我们就可以为其传递一个Lambda表达式,而在Supplier接口中所定义的方法是无参有返回值的方法,因此具体调用所传入的Lambda表达式如下所示
Student student = Objects.requireNonNullElseGet(s1, () -> {
return new Student("itcast", "14");
});

// 输出
System.out.println(student);

}

// 演示requireNonNullElse
public static void method_02() {

// 创建一个学生对象
Student s1 = new Student("itheima" , "14") ;

// 调用Objects对象的requireNonNullElse方法
Student student = Objects.requireNonNullElse(s1, new Student("itcast", "14"));

// 输出
System.out.println(student);

}

// 演示requireNonNull
public static void method_01() {

// 创建一个学生对象
Student s1 = new Student("itheima" , "14") ;

// 调用Objects对象的requireNonNull方法
Student student = Objects.requireNonNull(s1);

// 输出
System.out.println(student);
}
}
  • 不是很明白喵= =

BigInteger类

  • 存储整数的时候,Java中默认是int类型,int类型有取值范围:-2147483648 ~ 2147483647
  • 如果数字过大,我们可以使用long类型,但当long类型也表示不下↓
  • 用BigInteger
  • 可以理解为:大的整数。有多大呢?理论上最大到42亿的21亿次方,基本上在内存撑爆之前,都无法达到这个上限

常见方法

构造方法
1
2
3
4
5
6
public BigInteger(int num, Random rnd)//获取随机大整数,范围:[0 ~ 2的num次方-1]
public BigInteger(String val) //获取指定的大整数
public BigInteger(String val, int radix) //获取指定进制的大整数

下面这个不是构造,而是一个静态方法获取BigInteger对象
public static BigInteger valueOf(long val) //静态方法获取BigInteger的对象,内部有优化
  • 构造方法小结:
    • 如果BigInteger表示的数字没有超出long的范围,可以用静态方法获取。
    • 如果BigInteger表示的超出long的范围,可以用构造方法获取。
    • 对象一旦创建,BigInteger内部记录的值不能发生改变。
    • 只要进行计算都会产生一个新的BigInteger对象
常见成员方法
1
2
3
4
5
6
7
8
9
public BigInteger add(BigInteger val)                   //加法
public BigInteger subtract(BigInteger val) //减法
public BigInteger multiply(BigInteger val) //乘法
public BigInteger divide(BigInteger val) //除法
public BigInteger[] divideAndRemainder(BigInteger val) //除法,获取商和余数
public boolean equals(Object x) //比较是否相同
public BigInteger pow(int exponent) //次幂、次方
public BigInteger max/min(BigInteger val) //返回较大值/较小值
public int intValue(BigInteger val) //转为int类型整数,超出范围数据有误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package com.itheima.a06bigintegerdemo;

import java.math.BigInteger;

public class BigIntegerDemo1 {
public static void main(String[] args) {
/* public BigInteger(int num, Random rnd) 获取随机大整数,范围:[0~ 2的num次方-1] →?
public BigInteger(String val) 获取指定的大整数
public BigInteger(String val, int radix) 获取指定进制的大整数

public static BigInteger valueOf(long val) 静态方法获取BigInteger的对象,内部有优化

细节:
对象一旦创建里面的数据不能发生改变。*/


//1.获取一个随机的大整数
/* Random r=new Random();
for (int i = e; i < 100; i++) {
BigInteger bd1 = new BigInteger(4,r);
System.out.println(bd1);//[@ ~ 15]}
}
*/

//2.获取一个指定的大整数,可以超出long的取值范围
//细节:字符串中必须是整数,否则会报错
/* BigInteger bd2 = new BigInteger("1.1");
System.out.println(bd2);
*/

/*
BigInteger bd3 = new BigInteger("abc");
System.out.println(bd3);
*/

//3.获取指定进制的大整数
//细节:
//1.字符串中的数字必须是整数
//2.字符串中的数字必须要跟进制吻合。
//比如二进制中,那么只能写0和1,写其他的就报错。
BigInteger bd4 = new BigInteger("123", 2);
System.out.println(bd4);

//4.静态方法获取BigInteger的对象,内部有优化
//细节:
//1.能表示范围比较小,只能在long的取值范围之内,如果超出long的范围就不行了。
//2.在内部对常用的数字: -16 ~ 16 进行了优化。
// 提前把-16~16 先创建好BigInteger的对象,如果多次获取不会重新创建新的。
BigInteger bd5 = BigInteger.valueOf(16);
BigInteger bd6 = BigInteger.valueOf(16);
System.out.println(bd5 == bd6);//true


BigInteger bd7 = BigInteger.valueOf(17);
BigInteger bd8 = BigInteger.valueOf(17);
System.out.println(bd7 == bd8);//false


//5.对象一旦创建内部的数据不能发生改变
BigInteger bd9 =BigInteger.valueOf(1);
BigInteger bd10 =BigInteger.valueOf(2);
//此时,不会修改参与计算的BigInteger对象中的借,而是产生了一个新的BigInteger对象记录
BigInteger result=bd9.add(bd10);
System.out.println(result);//3
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.itheima.a06bigintegerdemo;

import java.math.BigInteger;

public class BigIntegerDemo2 {
public static void main(String[] args) {
/*
public BigInteger add(BigInteger val) 加法
public BigInteger subtract(BigInteger val) 减法
public BigInteger multiply(BigInteger val) 乘法
public BigInteger divide(BigInteger val) 除法,获取商
public BigInteger[] divideAndRemainder(BigInteger val) 除法,获取商和余数
public boolean equals(Object x) 比较是否相同
public BigInteger pow(int exponent) 次幂
public BigInteger max/min(BigInteger val) 返回较大值/较小值
public int intValue(BigInteger val) 转为int类型整数,超出范围数据有误
*/

//1.创建两个BigInteger对象
BigInteger bd1 = BigInteger.valueOf(10);
BigInteger bd2 = BigInteger.valueOf(5);

//2.加法
BigInteger bd3 = bd1.add(bd2);
System.out.println(bd3);

//3.除法,获取商和余数
BigInteger[] arr = bd1.divideAndRemainder(bd2);
System.out.println(arr[0]);
System.out.println(arr[1]);

//4.比较是否相同
boolean result = bd1.equals(bd2);
System.out.println(result);

//5.次幂
BigInteger bd4 = bd1.pow(2);
System.out.println(bd4);

//6.max
BigInteger bd5 = bd1.max(bd2);


//7.转为int类型整数,超出范围数据有误
/* BigInteger bd6 = BigInteger.valueOf(2147483647L);
int i = bd6.intValue();
System.out.println(i);
*/

BigInteger bd6 = BigInteger.valueOf(200);
double v = bd6.doubleValue();
System.out.println(v);//200.0
}
}

底层存储方式

  • 对于计算机而言,其实是没有数据类型的概念的,都是0101010101,数据类型是编程语言自己规定的,所以在实际存储的时候,先把具体的数字变成二进制,每32个bit为一组,存储在数组中。
    • 数组中最多能存储元素个数:21亿多
    • 数组中每一位能表示的数字:42亿多
  • 理论上,BigInteger能表示的最大数字为:42亿的21亿次方。但是还没到这个数字,电脑的内存就会撑爆,所以一般认为BigInteger是无限的。

BigDecimal类

引入

1
2
3
4
5
6
public class BigDecimalDemo01 {

public static void main(String[] args) {
System.out.println(0.09 + 0.01); // 0.09999999999999999
}
}
  • 一个丢失精度的结果
  • 使用float或者double类型的数据在进行数学运算的时候,很有可能会产生精度丢失问题

精度丢失问题

  • 计算机底层在进行运算的时候,使用的都是二进制数据;
  • 当我们在程序中写了一个十进制数据 ,在进行运算的时候,计算机会将这个十进制数据转换成二进制数据,然后再进行运算,计算完毕以后计算机会把运算的结果再转换成十进制数据给我们展示;
  • 如果我们使用的是整数类型的数据进行计算,那么在把十进制数据转换成二进制数据的时候不会存在精度问题
  • 如果我们的数据是一个浮点类型的数据,有的时候计算机并不会将这个数据完全转换成一个二进制数据,而是将这个将其转换成一个无限的趋近于这个十进数的二进制数据
  • 这样使用一个不太准确的数据进行运算的时候, 最终就会造成精度丢失;
  • 为了提高精度,Java就给我们提供了BigDecimal供我们进行数据运算

概述

  • BigDecimal所在包是在java.math包下,因此在使用的时候就需要进行导包
  • 可以使用BigDecimal类进行更加精准的数据计算

常见方法

构造方法

1
2
3
BigDecimal(int val)  // 将int转换为BigDecimal
BigDecimal(long val) // 将long转换为BigDecimal
BigDecimal(String val) // 将BigDecimal的字符串表示形式转换为BigDecimal

常见成员方法

  • 使用最多的还是提供的进行四则运算的方法
1
2
3
4
public BigDecimal add(BigDecimal value)       // 加法运算
public BigDecimal subtract(BigDecimal value) // 减法运算
public BigDecimal multiply(BigDecimal value) // 乘法运算
public BigDecimal divide(BigDecimal value) // 触发运算
案例1:基本四则运算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BigDecimalDemo01 {

public static void main(String[] args) {

// 创建两个BigDecimal对象
BigDecimal b1 = new BigDecimal("0.3") ;
BigDecimal b2 = new BigDecimal("4") ;

// 调用方法进行b1和b2的四则运算,并将其运算结果在控制台进行输出
System.out.println(b1.add(b2)); // 进行加法运算 4.3
System.out.println(b1.subtract(b2)); // 进行减法运算 -3.7
System.out.println(b1.multiply(b2)); // 进行乘法运算 1.2
System.out.println(b1.divide(b2)); // 进行除法运算 0.075
}
}
案例2:除法的特殊情况
  • 如果使用BigDecimal类型的数据进行除法运算的时候,得到的结果是一个无限循环小数,那么就会报错:ArithmeticException
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class BigDecimalDemo02 {

public static void main(String[] args) {

// 创建两个BigDecimal对象
BigDecimal b1 = new BigDecimal("1") ;
BigDecimal b2 = new BigDecimal("3") ;

// 调用方法进行b1和b2的除法运算,并且将计算结果在控制台进行输出
System.out.println(b1.divide(b2));

}

}

// 运行程序进行测试,控制台输出:
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.base/java.math.BigDecimal.divide(BigDecimal.java:1716)
at com.itheima.api.bigdecimal.demo02.BigDecimalDemo02.main(BigDecimalDemo02.java:14)
除法特殊情况的解决
  • 需要使用到BigDecimal类中另外一个divide方法
1
BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
  • 上述divide方法参数说明:

divisor: 除数对应的BigDecimal对象;

scale: 精确的位数;

roundingMode: 取舍模式;

取舍模式被封装到了RoundingMode这个枚举类中(关于枚举我们后期再做重点讲解),在这个枚举类中定义了很多种取舍方式。最常见的取舍方式有如下几个:

UP(直接进1) , FLOOR(直接删除) , HALF_UP(4舍五入),我们可以通过如下格式直接访问这些取舍模式:枚举类名.变量名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class BigDecimalDemo02 {

public static void main(String[] args) {

// 调用方法
method_03() ;

}

// 演示取舍模式HALF_UP
public static void method_03() {

// 创建两个BigDecimal对象
BigDecimal b1 = new BigDecimal("0.3") ;
BigDecimal b2 = new BigDecimal("4") ;

// 0.3 / 4 的准确值是 0.075
// 使用HALF_UP舍入模式时,如果需要舍入的数字是5,则向最接近的数字舍入;如果两边一样近,则向上舍入
// 因此,0.075保留两位小数后会变成0.08


// 调用方法进行b1和b2的除法运算,并且将计算结果在控制台进行输出
System.out.println(b1.divide(b2 , 2 , RoundingMode.HALF_UP));

}

// 演示取舍模式FLOOR
public static void method_02() {

// 创建两个BigDecimal对象
BigDecimal b1 = new BigDecimal("1") ;
BigDecimal b2 = new BigDecimal("3") ;

// 调用方法进行b1和b2的除法运算,并且将计算结果在控制台进行输出
System.out.println(b1.divide(b2 , 2 , RoundingMode.FLOOR));

// 1 / 3 的准确值是 0.333...
// 使用FLOOR舍入模式时,总是向下舍入到最接近的数字
// 因此,0.333...保留两位小数后会变成0.33

}

// 演示取舍模式UP
public static void method_01() {

// 创建两个BigDecimal对象
BigDecimal b1 = new BigDecimal("1") ;
BigDecimal b2 = new BigDecimal("3") ;

// 调用方法进行b1和b2的除法运算,并且将计算结果在控制台进行输出
System.out.println(b1.divide(b2 , 2 , RoundingMode.UP));

// 1 / 3 的准确值是 0.333...
// 使用UP舍入模式时,总是向上舍入
// 因此,0.333...保留两位小数后会变成0.34
}
}
  • 小结:后期在进行两个数的除法运算的时候,常常使用可以设置取舍模式的divide方法

底层存储方式

  • 把数据看成字符串,遍历得到里面的每一个字符,把这些字符在ASCII码表上的值,都存储到数组中

Day19 API(正则表达式)

学习目标

  • 能够理解正则表达式的作用
  • 能够使用正则表达式的字符类
  • 能够使用正则表达式的逻辑运算符
  • 能够使用正则表达式的预定义字符类
  • 能够使用正则表达式的限定符
  • 能够使用正则表达式的分组
  • 能够在String的split方法中使用正则表达式

正则表达式

  • 正则表达式就是用来验证各种字符串的规则。它内部描述了一些规则,可以验证用户输入的字符串是否匹配这个规则

体验

不使用正则表达式验证的例子

  • 下面的程序让用户输入一个QQ号码,我们要验证:
  • QQ号码必须是5–15位长度
  • 必须全部是数字
  • 首位不能为0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.itheima.a08regexdemo;

public class RegexDemo1 {
public static void main(String[] args) {
/* 假如现在要求校验一个qq号码是否正确。
规则:6位及20位之内,日不能在开头,必须全部是数字。
先使用目前所学知识完成校验需求然后体验一下正则表达式检验。
*/

String qq ="1234567890";
System.out.println(checkQQ(qq));

// 使用正则表达式
System.out.println(qq.matches("[1-9]\\d{5,19}"));

}

// 不使用正则表达式
public static boolean checkQQ(String qq) {
//规则:6位及20位之内,0不能在开头,必须全部是数字 。
//核心思想:
//先把异常数据进行过滤
//下面的就是满足要求的数据了。
int len = qq.length();
if (len < 6 || len > 20) {
return false;
}
//0不能在开头
if (qq.startsWith("0")) {
return false;
}
//必须全部是数字
for (int i = 0; i < qq.length(); i++) {
char c = qq.charAt(i);
if (c < '0' | c > '9') {
return false;
}
}
return true;
}
}

使用正则表达式验证

1
2
3
4
5
6
public class Demo {
public static void main(String[] args) {
String qq ="1234567890";
System.out.println(qq.matches("[1-9]\\d{5,19}"));
}
}

正则表达式-字符类

1
2
3
4
5
6
7
8
// 语法示例
[abc]:代表a或者b,或者c字符中的一个。
[^abc]:代表除a,b,c以外的任何字符。
[a-z]:代表a-z的所有小写字符中的一个。
[A-Z]:代表A-Z的所有大写字符中的一个。
[0-9]:代表0-9之间的某一个数字字符。
[a-zA-Z0-9]:代表a-z或者A-Z或者0-9之间的任意一个字符。
[a-dm-p]:a 到 d 或 m 到 p之间的任意一个字符。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.itheima.a08regexdemo;

public class RegexDemo2 {
public static void main(String[] args) {
//public boolean matches(String regex):判断是否与正则表达式匹配,匹配返回true
// 只能是a b c
System.out.println("-----------1-------------");
System.out.println("a".matches("[abc]")); // true
System.out.println("z".matches("[abc]")); // false

// 不能出现a b c
System.out.println("-----------2-------------");
System.out.println("a".matches("[^abc]")); // false
System.out.println("z".matches("[^abc]")); // true
System.out.println("zz".matches("[^abc]")); //false
System.out.println("zz".matches("[^abc][^abc]")); //true

// a到zA到Z(包括头尾的范围)
System.out.println("-----------3-------------");
System.out.println("a".matches("[a-zA-z]")); // true
System.out.println("z".matches("[a-zA-z]")); // true
System.out.println("aa".matches("[a-zA-z]"));//false
System.out.println("zz".matches("[a-zA-Z]")); //false
System.out.println("zz".matches("[a-zA-Z][a-zA-Z]")); //true
System.out.println("0".matches("[a-zA-Z]"));//false
System.out.println("0".matches("[a-zA-Z0-9]"));//true


// [a-d[m-p]] a到d,或m到p
System.out.println("-----------4-------------");
System.out.println("a".matches("[a-d[m-p]]"));//true
System.out.println("d".matches("[a-d[m-p]]")); //true
System.out.println("m".matches("[a-d[m-p]]")); //true
System.out.println("p".matches("[a-d[m-p]]")); //true
System.out.println("e".matches("[a-d[m-p]]")); //false
System.out.println("0".matches("[a-d[m-p]]")); //false

// [a-z&&[def]] a-z和def的交集。为:d,e,f
System.out.println("----------5------------");
System.out.println("a".matches("[a-z&[def]]")); //false
System.out.println("d".matches("[a-z&&[def]]")); //true
System.out.println("0".matches("[a-z&&[def]]")); //false

// [a-z&&[^bc]] a-z和非bc的交集。(等同于[ad-z])
System.out.println("-----------6------------_");
System.out.println("a".matches("[a-z&&[^bc]]"));//true
System.out.println("b".matches("[a-z&&[^bc]]")); //false
System.out.println("0".matches("[a-z&&[^bc]]")); //false

// [a-z&&[^m-p]] a到z和除了m到p的交集。(等同于[a-lq-z])
System.out.println("-----------7-------------");
System.out.println("a".matches("[a-z&&[^m-p]]")); //true
System.out.println("m".matches("[a-z&&[^m-p]]")); //false
System.out.println("0".matches("[a-z&&[^m-p]]")); //false

}
}

正则表达式-逻辑运算符

1
2
3
4
// 语法示例:
&&:并且
| :或者
\ :转义字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 代码示例
// |和&&的使用

public class Demo {
public static void main(String[] args) {
String str = "had";
//1.要求字符串是小写辅音字符开头,后跟ad
String regex = "[a-z&&[^aeiou]]ad";
System.out.println("1." + str.matches(regex));
//2.要求字符串是aeiou中的某个字符开头,后跟ad
regex = "[a|e|i|o|u]ad";//这种写法相当于:regex = "[aeiou]ad";
System.out.println("2." + str.matches(regex));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 代码示例
// 转义字符\

package com.itheima.a08regexdemo;

public class RegexDemo3 {
public static void main(String[] args) {
// \ 转义字符 改变后面那个字符原本的含义
//练习:以字符串的形式打印一个双引号
//"在Java中表示字符串的开头或者结尾

//此时\表示转义字符,改变了后面那个双引号原本的含义
//把他变成了一个普普通通的双引号而已。
System.out.println("\"");

// \表示转义字符
//两个\的理解方式:前面的\是一个转义字符,改变了后面\原本的含义,把他变成一个普普通通的\而已。
System.out.println("c:Users\\moon\\IdeaProjects\\basic-code\\myapi\\src\\com\\itheima\\a08regexdemo\\RegexDemo1.java");
}
}

正则表达式-预定义字符

1
2
3
4
5
6
7
8
// 语法示例:
"." : 匹配任何字符。
"\d":任何数字[0-9]的简写;
"\D":任何非数字[^0-9]的简写;
"\s": 空白字符:[ \t\n\x0B\f\r] 的简写
"\S": 非空白字符:[^\s] 的简写
"\w":单词字符:[a-zA-Z_0-9]的简写
"\W":非单词字符:[^\w]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Demo {
public static void main(String[] args) {
//.表示任意一个字符
System.out.println("你".matches("..")); //false
System.out.println("你".matches(".")); //true
System.out.println("你a".matches(".."));//true

// \\d 表示任意的一个数字
// \\d只能是任意的一位数字
// 简单来记:两个\表示一个\
System.out.println("a".matches("\\d")); // false
System.out.println("3".matches("\\d")); // true
System.out.println("333".matches("\\d")); // false

//\\w只能是一位单词字符[a-zA-Z_0-9]
System.out.println("z".matches("\\w")); // true
System.out.println("2".matches("\\w")); // true
System.out.println("21".matches("\\w")); // false
System.out.println("你".matches("\\w"));//false

// 非单词字符
System.out.println("你".matches("\\W")); // true
System.out.println("-----------------------------------------");
// 以上正则匹配只能校验单个字符。


// 必须是数字 字母 下划线 至少 6位
System.out.println("2442fsfsf".matches("\\w{6,}"));//true
System.out.println("244f".matches("\\w{6,}"));//false

// 必须是数字和字符 必须是4位
System.out.println("23dF".matches("[a-zA-Z0-9]{4}"));//true
System.out.println("23 F".matches("[a-zA-Z0-9]{4}"));//false
System.out.println("23dF".matches("[\\w&&[^]]{4}"));//true
System.out.println("23_F".matches("[\\w&&[^_]]{4}"));//false
}
}

正则表达式-数量词

1
2
3
4
5
6
7
// 语法示例:
X? : 0次或1
X* : 0次到多次
X+ : 1次或多次
X{n} : 恰好n次
X{n,} : 至少n次
X{n,m}: n到m次(n和m都是包含的)
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo {
public static void main(String[] args) {
// 必须是数字 字母 下划线 至少 6位
System.out.println("2442fsfsf".matches("\\w{6,}"));//true
System.out.println("244f".matches("\\w{6,}"));//false

// 必须是数字和字符 必须是4位
System.out.println("23dF".matches("[a-zA-Z0-9]{4}"));//true
System.out.println("23 F".matches("[a-zA-Z0-9]{4}"));//false
System.out.println("23dF".matches("[\\w&&[^]]{4}"));//true
System.out.println("23_F".matches("[\\w&&[^_]]{4}"));//false
}
}

正则表达式练习1

  • 需求:
    • 请编写正则表达式验证用户输入的手机号码是否满足要求。
    • 请编写正则表达式验证用户输入的邮箱号是否满足要求。
    • 请编写正则表达式验证用户输入的电话号码是否满足要求。
    • 验证手机号码 13112345678 13712345667 13945679027 139456790271
    • 验证座机电话号码 020-2324242 02122442 027-42424 0712-3242434
    • 验证邮箱号码 3232323@qq.com zhangsan@itcast.cnn dlei0009@163.com dlei0009@pci.com.cn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.itheima.a08regexdemo;

public class RegexDemo4 {
public static void main(String[] args) {
/*
需求
请编写正则表达式验证用户输入的手机号码是否满足要求。请编写正则表达式验证用户输入的邮箱号是否满足要求。请编写正则表达式验证用户输入的电话号码是否满足要求。
验证手机号码 13112345678 13712345667 13945679027 139456790271
验证座机电话号码 020-2324242 02122442 027-42424 0712-3242434
验证邮箱号码 3232323@qq.com zhangsan@itcast.cnn dlei0009@163.com dlei0009@pci.com.cn
*/

//心得:
//拿着一个正确的数据,从左到右依次去写。
//13112345678
//分成三部分:
//第一部分:1 表示手机号码只能以1开头
//第二部分:[3-9] 表示手机号码第二位只能是3-9之间的
//第三部分:\\d{9} 表示任意数字可以出现9次,也只能出现9次
String regex1 = "1[3-9]\\d{9}";
System.out.println("13112345678".matches(regex1));//true
System.out.println("13712345667".matches(regex1));//true
System.out.println("13945679027".matches(regex1));//true
System.out.println("139456790271".matches(regex1));//false
System.out.println("-----------------------------------");

//座机电话号码
//020-2324242 02122442 027-42424 0712-3242434
//思路:
//在书写座机号正则的时候需要把正确的数据分为三部分
//一:区号@\\d{2,3}
// 0:表示区号一定是以0开头的
// \\d{2,3}:表示区号从第二位开始可以是任意的数字,可以出现2到3次。
//二:- ?表示次数,0次或1次
//三:号码 号码的第一位也不能以0开头,从第二位开始可以是任意的数字,号码的总长度:5-10位
String regex2 = "0\\d{2,3}-?[1-9]\\d{4,9}";
System.out.println("020-2324242".matches(regex2));
System.out.println("02122442".matches(regex2));
System.out.println("027-42424".matches(regex2));
System.out.println("0712-3242434".matches(regex2));

//邮箱号码
//3232323@qq.com zhangsan@itcast.cnn dlei0009@163.com dlei0009@pci.com.cn
//思路:
//在书写邮箱号码正则的时候需要把正确的数据分为三部分
//第一部分:@的左边 \\w+
// 任意的字母数字下划线,至少出现一次就可以了
//第二部分:@ 只能出现一次
//第三部分:
// 3.1 .的左边[\\w&&[^]]{2,6}
// 任意的字母加数字,总共出现2-6次(此时不能出现下划线)
// 3.2 . \\.
// 3.3 大写字母,小写字母都可以,只能出现2-3次[a-zA-Z]{2,3}
// 我们可以把3.2和3.3看成一组,这一组可以出现1次或者两次
_ String regex3 = "\\w+@[\\w&&[^_]]{2,6}(\\.[a-zA-Z]{2,3}){1,2}";
System.out.println("3232323@qq.com".matches(regex3));
System.out.println("zhangsan@itcast.cnn".matches(regex3));
System.out.println("dlei0009@163.com".matches(regex3));
System.out.println("dlei0009@pci.com.cn".matches(regex3));


//24小时的正则表达式
String regex4 = "([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d";
System.out.println("23:11:11".matches(regex4));

String regex5 = "([01]\\d 2[0-3])(:[0-5]\\d){2}";
System.out.println("23:11:11".matches(regex5));
}
}
  • 问题:

    • //三:号码 号码的第一位也不能以0开头,从第二位开始可以是任意的数字,号码的总长度:5-10位
    • String regex2 = “0\d{2,3}-?[1-9]\d{4,9}”;
    • 5-10位→d{4,9} 这个点没太懂
  • 这个地方还需要实际练习T T 后面座机电话和邮箱电话的例子没咋看懂T T

正则表达式练习2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class RegexDemo5 {
public static void main(String[] args) {
/*
正则表达式练习:
需求
请编写正则表达式验证用户名是否满足要求。要求:大小写字母,数字,下划线一共4-16位
请编写正则表达式验证身份证号码是否满足要求。
简单要求:
18位,前17位任意数字,最后一位可以是数字可以是大写或小写的x
复杂要求:
按照身份证号码的格式严格要求。

身份证号码:
41080119930228457x
510801197609022309
15040119810705387X
130133197204039024 I
430102197606046442
*/

//用户名要求:大小写字母,数字,下划线一共4-16位
String regex1 = "\\w{4,16}";
System.out.println("zhangsan".matches(regex1));
System.out.println("lisi".matches(regex1));
System.out.println("wangwu".matches(regex1));
System.out.println("$123".matches(regex1));


//身份证号码的简单校验:
//18位,前17位任意数字,最后一位可以是数字可以是大写或小写的x
String regex2 = "[1-9]\\d{16}(\\d|x|x)";
String regex3 = "[1-9]\\d{16}[\\dXx]";
String regex5 = "[1-9]\\d{16}(\\d(?i)x)";

System.out.println("41080119930228457x".matches(regex3));
System.out.println("510801197609022309".matches(regex3));
System.out.println("15040119810705387X".matches(regex3));
System.out.println("130133197204039024".matches(regex3));
System.out.println("430102197606046442".matches(regex3));


//忽略大小写的书写方式
//在匹配的时候忽略abc的大小写
String regex4 = "a((?i)b)c";
System.out.println("------------------------------");
System.out.println("abc".matches(regex4));//true
System.out.println("ABC".matches(regex4));//false
System.out.println("aBc".matches(regex4));//true


//身份证号码的严格校验
//编写正则的小心得:
//第一步:按照正确的数据进行拆分
//第二步:找每一部分的规律,并编写正则表达式
//第三步:把每一部分的正则拼接在一起,就是最终的结果
//书写的时候:从左到右去书写。

//410801 1993 02 28 457x
//前面6位:省份,市区,派出所等信息,第一位不能是0,后面5位是任意数字 [1-9]\\d{5}
//年的前半段: 18 19 20 (18|19|20)
//年的后半段: 任意数字出现两次 \\d{2}
//月份: 01~ 09 10 11 12 (@[1-9]|1[0-2])
//日期: 01~09 10~19 20~29 30 31 (0[1-9]|[12]\\d|3[01])
//后面四位: 任意数字出现3次 最后一位可以是数字也可以是大写x或者小写x \\d{3}[\\dXx]
String regex6 = "[1-9]\\d{5}(18|19|20)\\d{2}(@[1-9]|1[0-2])(@[1-9]|[12]\\d|3[01])\\d{3}[\\dxXx]";

// 其实更严谨一点的话,还要判断是不是闰年以及2月的29号是否合理

System.out.println("41080119930228457x".matches(regex6));
System.out.println("510801197609022309".matches(regex6));
System.out.println("15040119810705387X".matches(regex6));
System.out.println("130133197204039024".matches(regex6));
System.out.println("430102197606046442".matches(regex6));

}
}

本地数据爬取

  • Pattern:表示正则表达式

  • Matcher:文本匹配器,作用按照正则表达式的规则去读取字符串,从头开始读取。在大串中去找符合匹配规则的子串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.itheima.a08regexdemo;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexDemo6 {
public static void main(String[] args) {
/* 有如下文本,请按照要求爬取数据。
Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11,
因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台
要求:找出里面所有的JavaXX
*/

String str = "Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11," +
"因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";


//1.获取正则表达式的对象
Pattern p = Pattern.compile("Java\\d{0,2}");
//2.获取文本匹配器的对象
//拿着m去读取str,找符合p规则的子串
Matcher m = p.matcher(str);

//3.利用循环获取
// ?
while (m.find()) {
String s = m.group();
System.out.println(s);
}
}

private static void method1(String str) {
//Pattern:表示正则表达式
//Matcher: 文本匹配器,作用按照正则表达式的规则去读取字符串,从头开始读取。
// 在大串中去找符合匹配规则的子串。

//获取正则表达式的对象
Pattern p = Pattern.compile("Java\\d{0,2}");
//获取文本匹配器的对象
//m:文本匹配器的对象
//str:大串
//p:规则
//m要在str中找符合p规则的小串
Matcher m = p.matcher(str);

//拿着文本匹配器从头开始读取,寻找是否有满足规则的子串
//如果没有,方法返回false
//如果有,返回true。在底层记录子串的起始索引和结束索引+1
// 0,4
boolean b = m.find();

//方法底层会根据find方法记录的索引进行字符串的截取
// substring(起始索引,结束索引);包头不包尾
// (0,4)但是不包含4索引
// 会把截取的小串进行返回。
String s1 = m.group();
System.out.println(s1);


//第二次在调用find的时候,会继续读取后面的内容
//读取到第二个满足要求的子串,方法会继续返回true
//并把第二个子串的起始索引和结束索引+1,进行记录
b = m.find();

//第二次调用group方法的时候,会根据find方法记录的索引再次截取子串
String s2 = m.group();
System.out.println(s2);
}
}
  • 没太懂= =

网络数据爬取(了解)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class RegexDemo7 {
public static void main(String[] args) throws IOException {
/* 扩展需求2:
把连接:https://m.sengzan.com/jiaoyu/29104.html?ivksa=1025883i
中所有的身份证号码都爬取出来。
*/

//创建一个URL对象
URL url = new URL("https://m.sengzan.com/jiaoyu/29104.html?ivk sa=1025883i");
//连接上这个网址
//细节:保证网络是畅通
URLConnection conn = url.openConnection();//创建一个对象读取网络中的数据
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
//获取正则表达式的对象pattern
String regex = "[1-9]\\d{17}";
Pattern pattern = Pattern.compile(regex);//在读取的时候每次读一整行
while ((line = br.readLine()) != null) {
//拿着文本匹配器的对象matcher按照pattern的规则去读取当前的这一行信息
Matcher matcher = pattern.matcher(line);
while (matcher.find()) {
System.out.println(matcher.group());
}
}
br.close();
}
}

爬取数据练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.itheima.a08regexdemo;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexDemo8 {
public static void main(String[] args) {
/*
需求:把下面文本中的座机电话,邮箱,手机号,热线都爬取出来。
来黑马程序员学习Java,
手机号:18512516758,18512508907或者联系邮箱:boniu@itcast.cn,
座机电话:01036517895,010-98951256邮箱:bozai@itcast.cn,
热线电话:400-618-9090 ,400-618-4000,4006184000,4006189090

手机号的正则表达式:1[3-9]\d{9}
邮箱的正则表达式:\w+@[\w&&[^_]]{2,6}(\.[a-zA-Z]{2,3}){1,2}座机电话的正则表达式:θ\d{2,3}-?[1-9]\d{4,9}
热线电话的正则表达式:400-?[1-9]\\d{2}-?[1-9]\\d{3}

*/

String s = "来黑马程序员学习Java," +
"电话:18512516758,18512508907" + "或者联系邮箱:boniu@itcast.cn," +
"座机电话:01036517895,010-98951256" + "邮箱:bozai@itcast.cn," +
"热线电话:400-618-9090 ,400-618-4000,4006184000,4006189090";

System.out.println("400-618-9090");

String regex = "(1[3-9]\\d{9})|(\\w+@[\\w&&[^_]]{2,6}(\\.[a-zA-Z]{2,3}){1,2})" +
"|(0\\d{2,3}-?[1-9]\\d{4,9})" +
"(400-?[1-9]\\d{2}-?[1-9]\\d{3})";

//1.获取正则表达式的对象
Pattern p = Pattern.compile(regex);

//2.获取文本匹配器的对象
//利用m去读取s,会按照p的规则找里面的小串
Matcher m = p.matcher(s);
//3.利用循环获取每一个数据 while(m.find()){
String str = m.group();
System.out.println(str);

}
}

按要求爬取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class RegexDemo9 {
public static void main(String[] args) {
/*
有如下文本,按要求爬取数据。
Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11,因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台


需求1:爬取版本号为8,11.17的Java文本,但是只要Java,不显示版本号。
需求2:爬取版本号为8,11,17的Java文本。正确爬取结果为:Java8 Java11 Java17 Java17
需求3:爬取除了版本号为8,11.17的Java文本,
*/
String s = "Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11," +
"因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";

//1.定义正则表达式
//?理解为前面的数据Java
//=表示在Java后面要跟随的数据
//但是在获取的时候,只获取前半部分
//需求1:
String regex1 = "((?i)Java)(?=8|11|17)";
//需求2:
String regex2 = "((?i)Java)(8|11|17)";
String regex3 = "((?i)Java)(?:8|11|17)";
//需求3:
String regex4 = "((?i)Java)(?!8|11|17)";

Pattern p = Pattern.compile(regex4);
Matcher m = p.matcher(s);
while (m.find()) {
System.out.println(m.group());
}
}
}

贪婪爬取和非贪婪爬取

1
2
3
4
5
6
7
8
9
10
只写+和表示贪婪匹配,如果在+和后面加问号表示非贪婪爬取
+? 非贪婪匹配
*? 非贪婪匹配
贪婪爬取:在爬取数据的时候尽可能的多获取数据
非贪婪爬取:在爬取数据的时候尽可能的少获取数据

举例:
如果获取数据:ab+
贪婪爬取获取结果:abbbbbbbbbbbb
非贪婪爬取获取结果:ab
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class RegexDemo10 {
public static void main(String[] args) {
/*
只写+和*表示贪婪匹配

+? 非贪婪匹配
*? 非贪婪匹配

贪婪爬取:在爬取数据的时候尽可能的多获取数据
非贪婪爬取:在爬取数据的时候尽可能的少获取数据

ab+:
贪婪爬取:abbbbbbbbbbbb
非贪婪爬取:ab
*/
String s = "Java自从95年问世以来,abbbbbbbbbbbbaaaaaaaaaaaaaaaaaa" +
"经历了很多版木,目前企业中用的最多的是]ava8和]ava11,因为这两个是长期支持版木。" +
"下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";

String regex = "ab+";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(s);

while (m.find()) {
System.out.println(m.group());
}
}
}

String的split方法中使用正则表达式

  • String类的split()方法原型
1
2
public String[] split(String regex)
//参数regex表示正则表达式。可以将当前字符串中匹配regex正则表达式的符号作为"分隔符"来切割字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
/*
有一段字符串:小诗诗dqwefqwfqwfwq12312小丹丹dqwefqwfqwfwq12312小惠惠
要求1:把字符串中三个姓名之间的字母替换为vs
要求2:把字符串中的三个姓名切割出来*/

String s = "小诗诗dqwefqwfqwfwq12312小丹丹dqwefqwfqwfwq12312小惠惠";
//细节:
//方法在底层跟之前一样也会创建文本解析器的对象
//然后从头开始去读取字符串中的内容,只要有满足的,那么就切割。
String[] arr = s.split("[\\w&&[^_]]+");
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}

String类的replaceAll方法中使用正则表达式

  • String类的replaceAll()方法原型
1
2
public String replaceAll(String regex,String newStr)
//参数regex表示一个正则表达式。可以将当前字符串中匹配regex正则表达式的字符串替换为newStr。
1
2
3
4
5
6
7
8
9
10
11
/*
有一段字符串:小诗诗dqwefqwfqwfwq12312小丹丹dqwefqwfqwfwq12312小惠惠
要求1:把字符串中三个姓名之间的字母替换为vs
要求2:把字符串中的三个姓名切割出来*/

String s = "小诗诗dqwefqwfqwfwq12312小丹丹dqwefqwfqwfwq12312小惠惠";
//细节:
//方法在底层跟之前一样也会创建文本解析器的对象
//然后从头开始去读取字符串中的内容,只要有满足的,那么就用第一个参数去替换。
String result1 = s.replaceAll("[\\w&&[^_]]+", "vs");
System.out.println(result1);

正则表达式-分组括号()

  • 细节:如何识别组号?
    • 只看左括号,不看右括号,按照左括号的顺序,从左往右,依次为第一组,第二组,第三组等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//需求1:判断一个字符串的开始字符和结束字符是否一致?只考虑一个字符
//举例: a123a b456b 17891 &abc& a123b(false)
// \\组号:表示把第X组的内容再出来用一次
String regex1 = "(.).+\\1";
System.out.println("a123a".matches(regex1));
System.out.println("b456b".matches(regex1));
System.out.println("17891".matches(regex1));
System.out.println("&abc&".matches(regex1));
System.out.println("a123b".matches(regex1));
System.out.println("--------------------------");


//需求2:判断一个字符串的开始部分和结束部分是否一致?可以有多个字符
//举例: abc123abc b456b 123789123 &!@abc&!@ abc123abd(false)
String regex2 = "(.+).+\\1";
System.out.println("abc123abc".matches(regex2));
System.out.println("b456b".matches(regex2));
System.out.println("123789123".matches(regex2));
System.out.println("&!@abc&!@".matches(regex2));
System.out.println("abc123abd".matches(regex2));
System.out.println("---------------------");


//需求3:判断一个字符串的开始部分和结束部分是否一致?开始部分内部每个字符也需要一致
//举例: aaa123aaa bbb456bbb 111789111 &&abc&&
//(.):把首字母看做一组
// \\2:把首字母拿出来再次使用
// *:作用于\\2,表示后面重复的内容出现日次或多次
String regex3 = "((.)\\2*).+\\1";
System.out.println("aaa123aaa".matches(regex3));
System.out.println("bbb456bbb".matches(regex3));
System.out.println("111789111".matches(regex3));
System.out.println("&&abc&&".matches(regex3));
System.out.println("aaa123aab".matches(regex3));

分组练习

1
2
3
4
5
6
7
8
9
10
11
12
String str = "我要学学编编编编程程程程程程";

//需求:把重复的内容 替换为 单个的
//学学 学
//编编编编 编
//程程程程程程 程
// (.)表示把重复内容的第一个字符看做一组
// \\1表示第一字符再次出现
// + 至少一次
// $1 表示把正则表达式中第一组的内容,再拿出来用
String result = str.replaceAll("(.)\\1+", "$1");
System.out.println(result);

忽略大小写的写法

1
2
3
4
5
6
7
//(?i) :表示忽略后面数据的大小写
//忽略abc的大小写
String regex = "(?i)abc";
//a需要一模一样,忽略bc的大小写
String regex = "a(?i)bc";
//ac需要一模一样,忽略b的大小写
String regex = "a((?i)b)c";

非捕获分组

  • 非捕获分组:分组之后不需要再用本组数据,仅仅是把数据括起来
1
2
3
4
5
6
7
8
9
10
11
12
//身份证号码的简易正则表达式
//非捕获分组:仅仅是把数据括起来
//特点:不占用组号
//这里\\1报错原因:(?:)就是非捕获分组,此时是不占用组号的。


//(?:) (?=) (?!)都是非捕获分组//更多的使用第一个
//String regex1 ="[1-9]\\d{16}(?:\\d|x|x)\\1";
String regex2 ="[1-9]\\d{16}(\\d Xx)\\1";
//^([01]\d|2[0-3]):[0-5]\d:[@-5]\d$

System.out.println("41080119930228457x".matches(regex2));

正则表达式练习

1
2
3
4
5
6
7
8
9
10
11
12
手机号码:1[3-9]\\d{9}
座机号码:0\\d{2,3}-?[1-9]\\d{4,9}
邮箱号码:\\w+@[\\w&&[^_]]{2,6}(\\.[a-zA-Z]{2,3}){1,2}
24小时:([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d
([01]\\d|2[0-3])(:[0-5]\\d){2}
用户名: \\w{4,16}
身份证号码,简单校验:
[1-9]\\d{16}(\\d|X|x)
[1-9]\\d{16}[\\dXx]
[1-9]\\d{16}(\\d(?i)X)
身份证号码,严格校验:
[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9|[12])\\d|3[01])\\d{3}[\\dXx]

Day20 API(时间,包装类,练习)

学习目标

  • 能够使用日期类输出当前日期
  • 能够使用将日期格式化为字符串的方法
  • 能够使用将字符串转换成日期的方法
  • 能够说出8种基本类型对应的包装类名称
  • 能够说出自动装箱、自动拆箱的概念
  • 能够将字符串转换为对应的基本类型
  • 能够将基本类型转换为对应的字符串
  • 能够完成课题上讲解的所有练习

Date类

  • java.util.Date类 表示特定的瞬间,精确到毫秒

  • 多个过时的构造函数

    • public Date(): 从运行程序的此时此刻到时间原点经历的毫秒值,转换成Date对象,分配Date对象并初始化此对象,以表示分配它的时间(精确到毫秒)
    • public Date(long date):将指定参数的毫秒值date,转换成Date对象,分配Date对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即1970年1月1日00:00:00 GMT)以来的指定毫秒数

    tips: 由于中国处于东八区(GMT+08:00)是比世界协调时间/格林尼治时间(GMT)快8小时的时区,当格林尼治标准时间为0:00时,东八区的标准时间为08:00

  • 使用无参构造,可以自动设置当前系统时间的毫秒时刻;指定long类型的构造参数,可以自定义毫秒时刻

1
2
3
4
5
6
7
8
9
10
import java.util.Date;

public class Demo01Date {
public static void main(String[] args) {
// 创建日期对象,把当前的时间
System.out.println(new Date()); // Tue Jan 16 14:37:35 CST 2020
// 创建日期对象,把当前的毫秒值转成日期对象
System.out.println(new Date(0L)); // Thu Jan 01 08:00:00 CST 1970
}
}

tips:在使用println方法时,会自动调用Date类中的toString方法。Date类对Object类中的toString方法进行了覆盖重写,所以结果为指定格式的字符串。

Date类常用方法

  • 多数方法已经过时:
    • public long getTime() 把日期对象转换成对应的时间毫秒值
    • public void setTime(long time) 把方法参数给定的毫秒值设置给日期对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DateDemo02 {
public static void main(String[] args) {
//创建日期对象
Date d = new Date();
//public long getTime():获取的是日期对象从1970年1月1日 00:00:00到现在的毫秒值
//System.out.println(d.getTime());
//System.out.println(d.getTime() * 1.0 / 1000 / 60 / 60 / 24 / 365 + "年");

//public void setTime(long time):设置时间,给的是毫秒值
//long time = 1000*60*60;
long time = System.currentTimeMillis();
d.setTime(time);

System.out.println(d);
}
}

小结:Date表示特定的时间瞬间,我们可以使用Date对象对时间进行操作

SimpleDateFormat类

  • java.text.SimpleDateFormat 是日期/时间格式化类,我们通过这个类可以帮我们完成日期和文本之间的转换,也就是可以在Date对象与String对象之间进行来回转换
    • 格式化:按照指定的格式,把Date对象转换为String对象
    • 解析:按照指定的格式,把String对象转换为Date对象

构造方法

  • 由于DateFormat为抽象类,不能直接使用,所以需要常用的子类java.text.SimpleDateFormat。这个类需要一个模式(格式)来指定格式化或解析的标准

  • 构造方法:

    • public SimpleDateFormat(String pattern):用给定的模式和默认语言环境的日期格式符号构造SimpleDateFormat。参数pattern是一个字符串,代表日期时间的自定义格式

格式规则

标识字母(区分大小写)含义
y
M
d
H
m
s

更详细的格式规则,可以参考SimpleDateFormat类的API文档

常用方法

  • DateFormat类
    • public String format(Date date):将Date对象格式化为字符串
    • public Date parse(String source):将字符串解析为Date对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.itheima.a01jdk7datedemo;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class A03_SimpleDateFormatDemo1 {
public static void main(Stringargs) throws ParseException {
/*
public simpleDateFormat() 默认格式
public simpleDateFormat(String pattern) 指定格式
public final string format(Date date) 格式化(日期对象 ->字符串)
public Date parse(string source) 解析(字符串 ->日期对象)
*/

//1.定义一个字符串表示时间
String str = "2023-11-11 11:11:11";
//2.利用空参构造创建simpleDateFormat对象
// 细节:
//创建对象的格式要跟字符串的格式完全一致
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse(str);
//3.打印结果
System.out.println(date.getTime());//1699672271000
}

private static void method1() {
//1.利用空参构造创建simpleDateFormat对象,默认格式
SimpleDateFormat sdf1 = new SimpleDateFormat();
Date d1 = new Date(0L);
String str1 = sdf1.format(d1);
System.out.println(str1);//1970/1/1 上午8:00

//2.利用带参构造创建simpleDateFormat对象,指定格式
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年MM月dd日HH:mm:ss");
String str2 = sdf2.format(d1);
System.out.println(str2);//1970年01月01日 08:00:00

//课堂练习:yyyy年MM月dd日 时:分:秒 星期
}
}

小结:DateFormat可以将Date对象和字符串相互转换

练习1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
假设,你初恋的出生年月日为:2000-11-11
请用字符串表示这个数据,并将其转换为:2000年11月11日

创建一个Date对象表示2000年11月11日
创建一个SimpleDateFormat对象,并定义格式为年月日把时间变成:2000年11月11日
*/

//1.可以通过2000-11-11进行解析,解析成一个Date对象
String str = "2000-11-11";
//2.解析
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf1.parse(str);
//3.格式化
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年MM月dd日");

// DateFormat可以将Date对象和字符串相互转换
String result = sdf2.format(date);
System.out.println(result);

练习2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* 需求:
秒杀活动开始时间:2023年11月11日 0:0:0(毫秒值)
秒杀活动结束时间:2023年11月11日 0:10:0(毫秒值)

小贾下单并付款的时间为:2023年11月11日 0:01:0
小皮下单并付款的时间为:2023年11月11日 0:11:0
用代码说明这两位同学有没有参加上秒杀活动?
*/

//1.定义字符串表示三个时间
String startstr = "2023年11月11日 0:0:0";
String endstr = "2023年11月11日 0:10:0";
String orderstr = "2023年11月11日 0:01:00";
//2.解析上面的三个时间,得到Date对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日HH:mm:ss");
Date startDate = sdf.parse(startstr);
Date endDate = sdf.parse(endstr);
Date orderDate = sdf.parse(orderstr);

//3.得到三个时间的毫秒值
long startTime = startDate.getTime();
long endTime = endDate.getTime();
long orderTime = orderDate.getTime();

//4.判断
if (orderTime >= startTime && orderTime <= endTime) {
System.out.println("参加秒杀活动成功");
} else {
System.out.println("参加秒杀活动失败");
}

Calendar类

  • java.util.Calendar类表示一个“日历类”,可以进行日期运算。它是一个抽象类,不能创建对象,我们可以使用它的子类:java.util.GregorianCalendar类
  • 有两种方式可以获取GregorianCalendar对象:
    • 直接创建GregorianCalendar对象;
    • 通过Calendar的静态方法getInstance()方法获取GregorianCalendar对象【√】

常用方法

方法名说明
public static Calendar getInstance()获取一个它的子类GregorianCalendar对象
public int get(int field)获取某个字段的值。field参数表示获取哪个字段的值, 可以使用Calender中定义的常量来表示: Calendar.YEAR : 年 Calendar.MONTH :月 Calendar.DAY_OF_MONTH:月中的日期 Calendar.HOUR:小时 Calendar.MINUTE:分钟 Calendar.SECOND:秒 Calendar.DAY_OF_WEEK:星期
public void set(int field,int value)设置某个字段的值
public void add(int field,int amount)为某个字段增加/减少指定的值

get方法示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Demo {
public static void main(String[] args) {
//1.获取一个GregorianCalendar对象
Calendar instance = Calendar.getInstance();//获取子类对象

//2.打印子类对象
System.out.println(instance);

//3.获取属性
int year = instance.get(Calendar.YEAR);
int month = instance.get(Calendar.MONTH) + 1;//Calendar的月份值是0-11
int day = instance.get(Calendar.DAY_OF_MONTH);

int hour = instance.get(Calendar.HOUR);
int minute = instance.get(Calendar.MINUTE);
int second = instance.get(Calendar.SECOND);

int week = instance.get(Calendar.DAY_OF_WEEK);//返回值范围:1--7,分别表示:"星期日","星期一","星期二",...,"星期六"

System.out.println(year + "年" + month + "月" + day + "日" +
hour + ":" + minute + ":" + second);
System.out.println(getWeek(week));

}

//查表法,查询星期几
public static String getWeek(int w) {//w = 1 --- 7
//做一个表(数组)
String[] weekArray = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
// 索引 [0] [1] [2] [3] [4] [5] [6]
//查表
return weekArray[w - 1];
}
}

set方法示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Demo {
public static void main(String[] args) {
//设置属性——set(int field,int value):
Calendar c1 = Calendar.getInstance();//获取当前日期

//计算班长出生那天是星期几(假如班长出生日期为:1998年3月18日)
c1.set(Calendar.YEAR, 1998);
c1.set(Calendar.MONTH, 3 - 1);//转换为Calendar内部的月份值
c1.set(Calendar.DAY_OF_MONTH, 18);

int w = c1.get(Calendar.DAY_OF_WEEK);
System.out.println("班长出生那天是:" + getWeek(w));

}
//查表法,查询星期几
public static String getWeek(int w) {//w = 1 --- 7
//做一个表(数组)
String[] weekArray = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
// 索引 [0] [1] [2] [3] [4] [5] [6]
//查表
return weekArray[w - 1];
}
}

add方法示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Demo {
public static void main(String[] args) {
//计算200天以后是哪年哪月哪日,星期几?
Calendar c2 = Calendar.getInstance();//获取当前日期
c2.add(Calendar.DAY_OF_MONTH, 200);//日期加200

int y = c2.get(Calendar.YEAR);
int m = c2.get(Calendar.MONTH) + 1;//转换为实际的月份
int d = c2.get(Calendar.DAY_OF_MONTH);

int wk = c2.get(Calendar.DAY_OF_WEEK);
System.out.println("200天后是:" + y + "年" + m + "月" + d + "日" + getWeek(wk));

}
//查表法,查询星期几
public static String getWeek(int w) {//w = 1 --- 7
//做一个表(数组)
String[] weekArray = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
// 索引 [0] [1] [2] [3] [4] [5] [6]
//查表
return weekArray[w - 1];
}
}

JDK8时间相关类

JDK8时间类类名作用
ZoneId时区
Instant时间戳
ZoneDateTime带时区的时间
DateTimeFormatter用于时间的格式化和解析
LocalDate年、月、日
LocalTime时、分、秒
LocalDateTime年、月、日、时、分、秒
Duration时间间隔(秒,纳,秒)
Period时间间隔(年,月,日)
ChronoUnit时间间隔(所有单位)

ZoneId 时区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
static Set<string> getAvailableZoneIds() 获取Java中支持的所有时区
static ZoneId systemDefault() 获取系统默认时区
static Zoneld of(string zoneld) 获取一个指定时区
*/

//1.获取所有的时区名称
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
System.out.println(zoneIds.size());//600
System.out.println(zoneIds);// Asia/Shanghai

//2.获取当前系统的默认时区
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId);//Asia/Shanghai

//3.获取指定的时区
ZoneId zoneId1 = ZoneId.of("Asia/Pontianak");
System.out.println(zoneId1);//Asia/Pontianak

Instant 时间戳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*
static Instant now() 获取当前时间的Instant对象(标准时间)
static Instant ofXxxx(long epochMilli) 根据(秒/毫秒/纳秒)获取Instant对象
ZonedDateTime atZone(ZoneIdzone) 指定时区
boolean isxxx(Instant otherInstant) 判断系列的方法
Instant minusXxx(long millisToSubtract) 减少时间系列的方法
Instant plusXxx(long millisToSubtract) 增加时间系列的方法
*/
//1.获取当前时间的Instant对象(标准时间)
Instant now = Instant.now();
System.out.println(now);

//2.根据(秒/毫秒/纳秒)获取Instant对象
Instant instant1 = Instant.ofEpochMilli(0L);
System.out.println(instant1);//1970-01-01T00:00:00z

Instant instant2 = Instant.ofEpochSecond(1L);
System.out.println(instant2);//1970-01-01T00:00:01Z

Instant instant3 = Instant.ofEpochSecond(1L, 1000000000L);
System.out.println(instant3);//1970-01-01T00:00:027

//3. 指定时区
ZonedDateTime time = Instant.now().atZone(ZoneId.of("Asia/Shanghai"));
System.out.println(time);


//4.isXxx 判断
Instant instant4=Instant.ofEpochMilli(0L);
Instant instant5 =Instant.ofEpochMilli(1000L);

//5.用于时间的判断
//isBefore:判断调用者代表的时间是否在参数表示时间的前面
boolean result1=instant4.isBefore(instant5);
System.out.println(result1);//true

//isAfter:判断调用者代表的时间是否在参数表示时间的后面
boolean result2 = instant4.isAfter(instant5);
System.out.println(result2);//false

//6.Instant minusXxx(long millisToSubtract) 减少时间系列的方法
Instant instant6 =Instant.ofEpochMilli(3000L);
System.out.println(instant6);//1970-01-01T00:00:03Z

Instant instant7 =instant6.minusSeconds(1);
System.out.println(instant7);//1970-01-01T00:00:02Z

ZoneDateTime 带时区的时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*
static ZonedDateTime now() 获取当前时间的ZonedDateTime对象
static ZonedDateTime ofXxxx(。。。) 获取指定时间的ZonedDateTime对象
ZonedDateTime withXxx(时间) 修改时间系列的方法
ZonedDateTime minusXxx(时间) 减少时间系列的方法
ZonedDateTime plusXxx(时间) 增加时间系列的方法
*/
//1.获取当前时间对象(带时区)
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);

//2.获取指定的时间对象(带时区)1/年月日时分秒纳秒方式指定
ZonedDateTime time1 = ZonedDateTime.of(2023, 10, 1,
11, 12, 12, 0, ZoneId.of("Asia/Shanghai"));
System.out.println(time1);

//通过Instant + 时区的方式指定获取时间对象
Instant instant = Instant.ofEpochMilli(0L);
ZoneId zoneId = ZoneId.of("Asia/Shanghai");
ZonedDateTime time2 = ZonedDateTime.ofInstant(instant, zoneId);
System.out.println(time2);


//3.withXxx 修改时间系列的方法
ZonedDateTime time3 = time2.withYear(2000);
System.out.println(time3);

//4. 减少时间
ZonedDateTime time4 = time3.minusYears(1);
System.out.println(time4);

//5.增加时间
ZonedDateTime time5 = time4.plusYears(1);
System.out.println(time5);

DateTimeFormatter 用于时间的格式化和解析

1
2
3
4
5
6
7
8
9
10
11
/*
static DateTimeFormatter ofPattern(格式) 获取格式对象
String format(时间对象) 按照指定方式格式化
*/
//获取时间对象
ZonedDateTime time = Instant.now().atZone(ZoneId.of("Asia/Shanghai"));

// 解析/格式化器
DateTimeFormatter dtf1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm;ss EE a");
// 格式化
System.out.println(dtf1.format(time));

LocalDate 年、月、日

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//1.获取当前时间的日历对象(包含 年月日)
LocalDate nowDate = LocalDate.now();
//System.out.println("今天的日期:" + nowDate);
//2.获取指定的时间的日历对象
LocalDate ldDate = LocalDate.of(2023, 1, 1);
System.out.println("指定日期:" + ldDate);

System.out.println("=============================");

//3.get系列方法获取日历中的每一个属性值//获取年
int year = ldDate.getYear();
System.out.println("year: " + year);
//获取月//方式一:
Month m = ldDate.getMonth();
System.out.println(m);
System.out.println(m.getValue());

//方式二:
int month = ldDate.getMonthValue();
System.out.println("month: " + month);


//获取日
int day = ldDate.getDayOfMonth();
System.out.println("day:" + day);

//获取一年的第几天
int dayofYear = ldDate.getDayOfYear();
System.out.println("dayOfYear:" + dayofYear);

//获取星期
DayOfWeek dayOfWeek = ldDate.getDayOfWeek();
System.out.println(dayOfWeek);
System.out.println(dayOfWeek.getValue());

//is开头的方法表示判断
System.out.println(ldDate.isBefore(ldDate));
System.out.println(ldDate.isAfter(ldDate));

//with开头的方法表示修改,只能修改年月日
LocalDate withLocalDate = ldDate.withYear(2000);
System.out.println(withLocalDate);

//minus开头的方法表示减少,只能减少年月日
LocalDate minusLocalDate = ldDate.minusYears(1);
System.out.println(minusLocalDate);


//plus开头的方法表示增加,只能增加年月日
LocalDate plusLocalDate = ldDate.plusDays(1);
System.out.println(plusLocalDate);

//-------------
// 判断今天是否是你的生日
LocalDate birDate = LocalDate.of(2000, 1, 1);
LocalDate nowDate1 = LocalDate.now();

MonthDay birMd = MonthDay.of(birDate.getMonthValue(), birDate.getDayOfMonth());
MonthDay nowMd = MonthDay.from(nowDate1);

System.out.println("今天是你的生日吗? " + birMd.equals(nowMd));//今天是你的生日吗?

LocalTime 时、分、秒

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 获取本地时间的日历对象。(包含 时分秒)
LocalTime nowTime = LocalTime.now();
System.out.println("今天的时间:" + nowTime);

int hour = nowTime.getHour();//时
System.out.println("hour: " + hour);

int minute = nowTime.getMinute();//分
System.out.println("minute: " + minute);

int second = nowTime.getSecond();//秒
System.out.println("second:" + second);

int nano = nowTime.getNano();//纳秒
System.out.println("nano:" + nano);
System.out.println("------------------------------------");
System.out.println(LocalTime.of(8, 20));//时分
System.out.println(LocalTime.of(8, 20, 30));//时分秒
System.out.println(LocalTime.of(8, 20, 30, 150));//时分秒纳秒
LocalTime mTime = LocalTime.of(8, 20, 30, 150);

//is系列的方法
System.out.println(nowTime.isBefore(mTime));
System.out.println(nowTime.isAfter(mTime));

//with系列的方法,只能修改时、分、秒
System.out.println(nowTime.withHour(10));

//plus系列的方法,只能修改时、分、秒
System.out.println(nowTime.plusHours(10));

LocalDateTime 年、月、日、时、分、秒

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 当前时间的的日历对象(包含年月日时分秒)
LocalDateTime nowDateTime = LocalDateTime.now();

System.out.println("今天是:" + nowDateTime);//今天是:
System.out.println(nowDateTime.getYear());//年
System.out.println(nowDateTime.getMonthValue());//月
System.out.println(nowDateTime.getDayOfMonth());//日
System.out.println(nowDateTime.getHour());//时
System.out.println(nowDateTime.getMinute());//分
System.out.println(nowDateTime.getSecond());//秒
System.out.println(nowDateTime.getNano());//纳秒
// 日:当年的第几天
System.out.println("dayofYear:" + nowDateTime.getDayOfYear());
//星期
System.out.println(nowDateTime.getDayOfWeek());
System.out.println(nowDateTime.getDayOfWeek().getValue());
//月份
System.out.println(nowDateTime.getMonth());
System.out.println(nowDateTime.getMonth().getValue());

LocalDate ld = nowDateTime.toLocalDate();
System.out.println(ld);

LocalTime lt = nowDateTime.toLocalTime();
System.out.println(lt.getHour());
System.out.println(lt.getMinute());
System.out.println(lt.getSecond());

Duration 时间间隔(秒,纳秒)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 本地日期时间对象。
LocalDateTime today = LocalDateTime.now();
System.out.println(today);

// 出生的日期时间对象
LocalDateTime birthDate = LocalDateTime.of(2000, 1, 1, 0, 0, 0);
System.out.println(birthDate);

Duration duration = Duration.between(birthDate, today);//第二个参数减第一个参数
System.out.println("相差的时间间隔对象:" + duration);

System.out.println("============================================");
System.out.println(duration.toDays());//两个时间差的天数
System.out.println(duration.toHours());//两个时间差的小时数
System.out.println(duration.toMinutes());//两个时间差的分钟数
System.out.println(duration.toMillis());//两个时间差的毫秒数
System.out.println(duration.toNanos());//两个时间差的纳秒数

Period 时间间隔(年、月、日)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 当前本地 年月日
LocalDate today = LocalDate.now();
System.out.println(today);

// 生日的 年月日
LocalDate birthDate = LocalDate.of(2000, 1, 1);
System.out.println(birthDate);

Period period = Period.between(birthDate, today);//第二个参数减第一个参数

System.out.println("相差的时间间隔对象:" + period);
System.out.println(period.getYears());
System.out.println(period.getMonths());
System.out.println(period.getDays());

System.out.println(period.toTotalMonths());

ChronoUnit 时间间隔(所有单位)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 当前时间
LocalDateTime today = LocalDateTime.now();
System.out.println(today);
// 生日时间
LocalDateTime birthDate = LocalDateTime.of(2000, 1, 1,0, 0, 0);
System.out.println(birthDate);

System.out.println("相差的年数:" + ChronoUnit.YEARS.between(birthDate, today));
System.out.println("相差的月数:" + ChronoUnit.MONTHS.between(birthDate, today));
System.out.println("相差的周数:" + ChronoUnit.WEEKS.between(birthDate, today));
System.out.println("相差的天数:" + ChronoUnit.DAYS.between(birthDate, today));
System.out.println("相差的时数:" + ChronoUnit.HOURS.between(birthDate, today));
System.out.println("相差的分数:" + ChronoUnit.MINUTES.between(birthDate, today));
System.out.println("相差的秒数:" + ChronoUnit.SECONDS.between(birthDate, today));
System.out.println("相差的毫秒数:" + ChronoUnit.MILLIS.between(birthDate, today));
System.out.println("相差的微秒数:" + ChronoUnit.MICROS.between(birthDate, today));
System.out.println("相差的纳秒数:" + ChronoUnit.NANOS.between(birthDate, today));
System.out.println("相差的半天数:" + ChronoUnit.HALF_DAYS.between(birthDate, today));
System.out.println("相差的十年数:" + ChronoUnit.DECADES.between(birthDate, today));
System.out.println("相差的世纪(百年)数:" + ChronoUnit.CENTURIES.between(birthDate, today));
System.out.println("相差的千年数:" + ChronoUnit.MILLENNIA.between(birthDate, today));
System.out.println("相差的纪元数:" + ChronoUnit.ERAS.between(birthDate, today));

包装类

  • Java提供了两个类型系统,基本类型引用类型使用基本类型在于效率,然而很多情况,会创建对象使用,因为对象可以做更多的功能,如果想要我们的基本类型像对象一样操作,就可以使用基本类型对应的包装类
基本类型对应的包装类(位于java.lang包中)
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

Integer类

  • 概述:包装一个对象中的原始类型 int 的值
Integer类构造方法及静态方法
方法名说明
public Integer(int value)根据 int 值创建 Integer 对象(过时)
public Integer(String s)根据 String 值创建 Integer 对象(过时)
public static Integer valueOf(int i)返回表示指定的 int 值的 Integer 实例
public static Integer valueOf(String s)返回保存指定String值的 Integer 对象
static string tobinarystring(int i)得到二进制
static string tooctalstring(int i)得到八进制
static string toHexstring(int i)得到十六进制
static int parseInt(string s)将字符串类型的整数转成int类型的整数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//public Integer(int value):根据 int 值创建 Integer 对象(过时)
Integer i1 = new Integer(100);
System.out.println(i1);

//public Integer(String s):根据 String 值创建 Integer 对象(过时)
Integer i2 = new Integer("100");
//Integer i2 = new Integer("abc"); //NumberFormatException
System.out.println(i2);
System.out.println("--------");

//public static Integer valueOf(int i):返回表示指定的 int 值的 Integer 实例
Integer i3 = Integer.valueOf(100);
System.out.println(i3);

//public static Integer valueOf(String s):返回保存指定String值的Integer对象
Integer i4 = Integer.valueOf("100");
System.out.println(i4);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
public static string tobinarystring(int i) 得到二进制
public static string tooctalstring(int i) 得到八进制
public static string toHexstring(int i) 得到十六进制
public static int parseInt(string s) 将字符串类型的整数转成int类型的整数
*/

//1.把整数转成二进制,十六进制
String str1 = Integer.toBinaryString(100);
System.out.println(str1);//1100100

//2.把整数转成八进制
String str2 = Integer.toOctalString(100);
System.out.println(str2);//144

//3.把整数转成十六进制
String str3 = Integer.toHexString(100);
System.out.println(str3);//64

//4.将字符串类型的整数转成int类型的整数
//强类型语言:每种数据在java中都有各自的数据类型
//在计算的时候,如果不是同一种数据类型,是无法直接计算的。
int i = Integer.parseInt("123");
System.out.println(i);
System.out.println(i + 1);//124
//细节1:
//在类型转换的时候,括号中的参数只能是数字不能是其他,否则代码会报错
//细节2:
//8种包装类当中,除了Character都有对应的parseXxx的方法,进行类型转换
String str = "true";
boolean b = Boolean.parseBoolean(str);
System.out.println(b);

装箱与拆箱

  • 基本类型对应的包装类对象之间,来回转换的过程称为”装箱“与”拆箱“:
    • 装箱:从基本类型转换为对应的包装类对象。
    • 拆箱:从包装类对象转换为对应的基本类型。
1
2
3
4
5
6
// 基本数值---->包装对象
Integer i = new Integer(4);//使用构造函数函数
Integer iii = Integer.valueOf(4);//使用包装类中的valueOf方法

// 包装对象---->基本数值
int num = i.intValue();

自动装箱与自动拆箱

1
2
3
4
5
// 经常要做基本类型与包装类之间的转换,从Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成

Integer i = 4;//自动装箱。相当于Integer i = Integer.valueOf(4);
i = i + 5;//等号右边:将i对象转成基本数值(自动拆箱) i.intValue() + 5;
//加法运算完成后,再次装箱,把基本数值转成对象。

基本类型与字符串之间的转换

基本类型转换为String
  • 转换方式
    • 方式一:直接在数字后加一个空字符串
    • 方式二:通过String类静态方法valueOf()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class IntegerDemo {
public static void main(String[] args) {
//int --- String
int number = 100;

//方式1:直接在数字后加一个空字符串
String s1 = number + "";
System.out.println(s1);

//方式2:通过String类静态方法valueOf()
//public static String valueOf(int i)
String s2 = String.valueOf(number);
System.out.println(s2);
System.out.println("--------");
}
}
String转换成基本类型
  • 除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型

    • public static byte parseByte(String s):将字符串参数转换为对应的byte基本类型。
    • public static short parseShort(String s):将字符串参数转换为对应的short基本类型。
    • public static int parseInt(String s):将字符串参数转换为对应的int基本类型。**
    • public static long parseLong(String s):将字符串参数转换为对应的long基本类型。**
    • public static float parseFloat(String s):将字符串参数转换为对应的float基本类型。
    • public static double parseDouble(String s):将字符串参数转换为对应的double基本类型。
    • public static boolean parseBoolean(String s):将字符串参数转换为对应的boolean基本类型。
  • 代码使用(仅以Integer类的静态方法parseXxx为例)

    • 转换方式
      • 方式一:先将字符串数字转成Integer,再调用valueOf()方法
      • 方式二:通过Integer静态方法parseInt()进行转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class IntegerDemo {
public static void main(String[] args) {
//String --- int
String s = "100";

//方式1:String --valueOf-- Integer --- int
Integer i = Integer.valueOf(s);
//public int intValue()
int x = i.intValue();
System.out.println(x);

//方式2
//public static int parseInt(String s)
int y = Integer.parseInt(s);
System.out.println(y);
}
}

注意:如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出java.lang.NumberFormatException异常

底层原理

  • 建议:获取Integer对象的时候不要自己new,而是采取直接赋值或者静态方法valueOf的方式
  • 因为在实际开发中,-128~127之间的数据,用的比较多。如果每次使用都是new对象,那么太浪费内存了。
  • 所以,提前把这个范围之内的每一个数据都创建好对象,如果要用到了不会创建新的,而是返回已经创建好的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//1.利用构造方法获取Integer的对象(JDK5以前的方式)
/*Integer i1 = new Integer(1);
Integer i2 = new Integer("1");
System.out.println(i1);
System.out.println(i2);*/

//2.利用静态方法获取Integer的对象(JDK5以前的方式)
Integer i3 = Integer.valueOf(123);
Integer i4 = Integer.valueOf("123");
Integer i5 = Integer.valueOf("123", 8);

System.out.println(i3);
System.out.println(i4);
System.out.println(i5);

//3.这两种方式获取对象的区别(掌握)
//底层原理:
//因为在实际开发中,-128~127之间的数据,用的比较多。
//如果每次使用都是new对象,那么太浪费内存了
//所以,提前把这个范围之内的每一个数据都创建好对象
//如果要用到了不会创建新的,而是返回已经创建好的对象。
Integer i6 = Integer.valueOf(127);
Integer i7 = Integer.valueOf(127);
System.out.println(i6 == i7);//true

Integer i8 = Integer.valueOf(128);
Integer i9 = Integer.valueOf(128);
System.out.println(i8 == i9);//false

//因为看到了new关键字,在Java中,每一次new都是创建了一个新的对象
//所以下面的两个对象都是new出来,地址值不一样。
/* Integer i10 = new Integer(127);
Integer i11 = new Integer(127);
System.out.println(i10 == i11);

Integer i12 = new Integer(128);
Integer i13 = new Integer(128);
System.out.println(i12 == i13);*/

算法小题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
练习一
需求:键盘录入一些1~10日之间的整数,并添加到集合中。直到集合中所有数据和超过200为止。

// 节选
System.out.println("请输入一个整数");
String numStr = sc.nextLine();
int num = Integer.parseInt(numStr);//先把异常数据先进行过滤

//1.创建一个集合用来添加整数
ArrayList<Integer> list = new ArrayList<>();
//在添加数据的时候触发了自动装箱
list.add(num);
//统计集合中所有的数据和
int sum = getSum(list);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
练习二
需求:自己实现parseInt方法的效果,将字符串形式的数据转成整数。要求:字符串中只能是数字不能有其他字符最少一位,最多10位, 0不能开头

//1.定义一个字符串
String str = "123";

//2.校验字符串
//习惯:会先把异常数据进行过滤,剩下来就是正常的数据。
if (!str.matches("[1-9]\\d{0,9}")) {
//错误的数据
System.out.println("数据格式有误");
} else {
//正确的数据
System.out.println("数据格式正确");

//3.定义一个变量表示最终的结果
int number = 0;

//4.遍历字符串得到里面的每一个字符
for (int i = 0; i < str.length(); i++) {
int c = str.charAt(i) - '0'; //把每一位数字放到number当中
number = number * 10 + c;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
练习三
需求:定义一个方法自己实现toBinaryString方法的效果,将一个十进制整数转成字符串表示的二进制

public static String tobinarystring(int number) {//6
//核心逻辑:
//不断的去除以2,得到余数,一直到商为日就结束。
//还需要把余数倒着拼接起来

//定义一个StringBuilder用来拼接余数
StringBuilder sb = new StringBuilder();
//利用循环不断的除以2获取余数
while (true) {
if (number == 0) {
break;
}
//获取余数 %
int remaindar = number % 2;//倒着拼接
sb.insert(0, remaindar); // 这里有问题,这一行为什么可以实现倒着拼接??
//除以2 /
number = number / 2;
}
return sb.toString();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
练习四
需求:请使用代码实现计算你活了多少天,用JDK7和JDK8两种方式完成

//请使用代码实现计算你活了多少天,用JDK7和JDK8两种方式完成
//JDK7
//规则:只要对时间进行计算或者判断,都需要先获取当前时间的毫秒值
//1.计算出生年月日的毫秒值
String birthday = "2000年1月1日";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
Date date = sdf.parse(birthday);
long birthdayTime = date.getTime();
//2.获取当前时间的毫秒值
long todayTime = System.currentTimeMillis();
//3.计算间隔多少天
long time = todayTime - birthdayTime;
System.out.println(time / 1000 / 60 / 60 / 24);

//JDK8
LocalDate ld1 = LocalDate.of(2000, 1, 1);
LocalDate ld2 = LocalDate.now();
long days = ChronoUnit.DAYS.between(ld1, ld2);
System.out.println(days);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
练习五
需求:判断任意的一个年份是闰年还是平年要求:用JDK7和JDK8两种方式判断提示:二月有29天是闰年一年有366天是闰年

/*
判断任意的一个年份是闰年还是平年要求:用JDK7和JDK8两种方式判断提示:
二月有29天是闰年一年有366天是闰年
*/

//jdk7
//我们可以把时间设置为2000年3月1日
Calendar c = Calendar.getInstance();
c.set(2000, 2, 1);
//月份的范围:0~11
//再把日历往前减一天
c.add(Calendar.DAY_OF_MONTH, -1);
//看当前的时间是28号还是29号?
int day = c.get(Calendar.DAY_OF_MONTH);
System.out.println(day);


//jdk8
//月份的范围:1~12
//设定时间为2000年的3月1日
LocalDate ld = LocalDate.of(2001, 3, 1);
//把时间往前减一天
LocalDate ld2 = ld.minusDays(1);
//获取这一天是一个月中的几号
int day2 = ld2.getDayOfMonth();
System.out.println(day2);

//true:闰年
//false:平年
System.out.println(ld.isLeapYear());
}
  • Copyrights © 2024-2025 brocademaple
  • 访问人数: | 浏览次数:

      请我喝杯咖啡吧~

      支付宝
      微信