1.1什么是编程
服务器开发、安卓应用开发、鸿蒙应用开发、网页、人工智能、深度学习、大数据...
怎么学习
- 多写代码
- 多思考
- 多做笔记
什么是编程
- 编程就是写代码
- 编程就是为了和计算机交流、给计算机发布任务
- 早期的编程语言:机器语言,都是用0和1来表达,学习成本很高
- 高级语言的出现,更加贴近人类语言的习惯,容易学习(C、Java、Python、C++...),Pyhon更易上手,但也会牺牲某些性能
编程语言
- 人类用语言交流:可以直接理解模糊的需求,不需要告诉对方每一步该怎么做,但是对方也知道该怎么达成目标
- 人和计算机交流:需要描述每一步应该做什么
以叫室友打包螺蛳粉为例:
-
我们和室友:帮我打包一份螺蛳粉
-
我们和计算机:开门、下楼、右拐、直走、螺蛳粉点、点餐、付钱、回宿舍
-
人类通过编程语言和计算机沟通
学编程的两个重点:
语法+处理特定的事情(需求)
- 程序的基本写法
- 告诉对方需求是什么
学习编程语言中的不同"词汇",就能让计算机帮我们处理不同的功能需求
1.2搭建Java开发环境搭建
链接: JDK下载地址
- 从Java 8以后的版本基本上都是开始商用,JDK17目前也是免费的。
- JDK为我们提供了Java开发的基本环境,以及运行Java必备的一些工具和库。
- 工具和库可以理解为编程语言的"词汇量",学会的库越多,就能通过Java让计算机帮我们做越多的事情。
- 一般建议使用JDK 17及以上的版本
安装步骤
- 打开安装包开始安装,并且记住安装路径
- 配置环境变量
a.右键"此电脑",选择"属性",打开"高级系统设置",打开"环境变量"
b.新建系统变量JAVA_HOME,并填写值,注意替换成安装的路径:C:\Program Files\Java\jdk-17
c.打开Path(如果没有就点击新建),新建一个值,填写:%JAVA_HOME%\bin
d.新建系统变量CLASSPATH,填写值:.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar - win+R打开运行窗口,输入cmd打开命令行终端
- 输入Java或Java -version,测试是否安装成功
1.3IDEA的安装使用
- 链接: IDEA下载地址
我这里安装到D盘,写代码的软件或其他工具建议安装到C盘外的其他盘。
勾选创建桌面快捷方式和将"bin"文件夹添加到PATH
新建Java项目:FistProject
主要关注src下的Main.java
External Libraries是当前项目所依赖的开发环境和一些库
- 修改字体大小:File > Setting > Editor > Font
- 修改页面主题:File > Setting > Editor > Color Scheme(建议默认或不要改太多,最多改下字体,快捷键也是建议用默认的)
- 查看或修改快捷键:File > Setting > Keymap
- 运行当前程序:Run图标或者Shift + F10快捷键
- 根据当前的操作场景做预测:Alt + Enter
- 控制台(consle)面板显示或隐藏:Alt + 4
- 撤销上一步操作:Ctrl + Z
- 代码提示:Ctrl + Alt + 空格
- 删除文件:选中文件,可右键打开操作菜单,找到Delete删除或键盘上的Delete按键
- 如果出现了红色波浪,说明这行代码需要检查是否有异常
- 按住Shift + 上下键,可以多选代码行数
- 注释选中行: Ctrl + /
- 多行注释(一般用在头部注释或者方法注释):在头部输入/**,再按下回车,可以使用多行注释
- 文件重命名:选中文件后Shift + F6,或者右键Rename
- 代码格式化:Ctrl + Alt + L
- 复制当前行的内容到下一行:Ctrl+D
1.4Hello World
选中src右击New一个Java Class,名叫Hello
java
public class Hello {
}
默认是以上一个单纯的Java代码文件,如果想要去运行,先要去打一个叫做main函数,这个是程序的主入口
java
public class Hello {
public static void main(String[] args) {
}
}
正式开始写代码是在main的大括号{}里面
java
public class Hello {
public static void main(String[] args) {
System.out.print("Hello World\n"); //在控制台输出Hello World,并且通过\n换行
System.out.println("123456"); //在控制台输出123456,并直接换行
}
}
多熟悉IDEA的用法,比如代码自动补齐、错误排查
D:\Java Code\FistProject\src\Hello.java:4:37 java: 需要';'
代码左侧小灯泡或者Alt + Enter根据当前的操作场景做预测
1.5使用Scanner获取输入
new:新建一个东西,以便实现某些功能,先这样理解
java
import java.util.Scanner;
public class Hello {
public static void main(String[] args) {
System.out.println("请你输入一些内容:");
//创建一个Scanner对象,并且起名叫y
Scanner y = new Scanner(System.in);
//读取用户输入的一整行字符串
System.out.println("你输入了:" + y.nextLine());
//Java会把字符串后面的内容都当成是字符串处理
System.out.println("计算一下1+2的结果是多少:" + 1 + 2);
System.out.println("计算一下1+2的结果是多少:" + (1 + 2));
System.out.println(1 + 2 + "计算一下1+2的结果是多少:" + (1 + 2));
}
}
java
"C:\Program Files\Java\jdk-17\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2025.2.4\lib\idea_rt.jar=14091" -Dfile.encoding=UTF-8 -classpath "D:\Java Code\FistProject\out\production\FistProject" Hello
请你输入一些内容:
TEST20251110
你输入了:TEST20251110
计算一下1+2的结果是多少:12
计算一下1+2的结果是多少:3
3计算一下1+2的结果是多少:3
Process finished with exit code 0
1.6变量
新建一个Java工程 File>New>Project,名叫CourseProject
java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int a = scanner.nextInt();
int b = scanner.nextInt();
System.out.println("计算一下" + a + "-" + b + "的结果是多少:" + (a - b));
}
}
java
1
0
计算一下1-0的结果是多少:1
定义变量可以不给值,也可以给一个默认值
java
变量定义规则:<变量类型> <变量名>
int a;
int b = 1; //变量b被赋值为1
int d, e, f;
int g, h = 9, j = 6;
变量名命名规范
- 只能用字母、数字、下划线
- 数字不能出现在第一个位置
- Java的关键字不能当成变量名
Java中的关键字
- 关键字:Java当中已经被设定的词
java
abstract、assert、boolean、break、byte、case、catch、char、class、continue、default、do、double、else、enum、extends、final、finally、float、for、if、implements、import、int、interface、instanceof、long、native、new、package、private、protected、public、return、short、static、strictfp、super、switch、synchronized、this、throw、throws、transient、try、void、volatile、while、true、false、null、goto、const
变量注意事项
- 没有初始化的变量不能被使用
java
public class Main {
public static void main(String[] args) {
int d, e = 8, f = 5;
System.out.println("d:" + d);
}
}
java
D:\Java Code\CourseProject\src\Main.java:4:35
java: 可能尚未初始化变量d
1.7赋值与常量
- C语言之后的绝大多数编程语言都把=设定为赋值
- 初始化:第一次被赋值的时候
- 变量的值可以被改变
java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
int d =1;
d = 5;
System.out.println("d:" + d);
}
}
java
"C:\Program Files\Java\jdk-17\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2025.2.4\lib\idea_rt.jar=12890" -Dfile.encoding=UTF-8 -classpath "D:\Java Code\CourseProject\out\production\CourseProject" Main
d:5
Process finished with exit code 0
- 变量前加final进行修饰,表示常量
java
final int D = 1; //使用final定义了一个名为D的常量,它的值不能被改变
- 按照约定俗成的编码习惯,一般会使用大写字母、数字、下划线来定义常量
1.8浮点数
新建一个Java工程 File>New>Project,名叫Demo_2
java
import java.util.Scanner;
/**
* 输入两个数,并做除法运算
*/
public class Demo_2 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入除数");
int a = scanner.nextInt();
System.out.println("请输入被除数");
int b = scanner.nextInt();
System.out.println("结果等于:" + a / b);
}
}
java
"C:\Program Files\Java\jdk-17\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2025.2.4\lib\idea_rt.jar=11005" -Dfile.encoding=UTF-8 -classpath "D:\Java Code\Demo_2\out\production\Demo_2" Demo_2
请输入除数
3
请输入被除数
2
结果等于:1
Process finished with exit code 0
- 在java当中,两个整数相除,也只能是整数
- 浮点数:用来表示小数
java
import java.util.Scanner;
/**
* 输入两个数,并做除法运算
*/
public class Demo_2 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入除数");
int a = scanner.nextInt();
System.out.println("请输入被除数");
double b = scanner.nextDouble();
System.out.println("结果等于:" + a / b);
}
}
java
请输入除数
3
请输入被除数
2
结果等于:1.5
- 在计算机当中 ,浮点数double不能去做太精确的计算
java
System.out.println("1.2-1.1的结果是" + (1.2 - 1.1));
java
1.2-1.1的结果是0.09999999999999987
- int用得比较多,int的运算速度也会比浮点数更快
1.9八大基本数据类型
| 分类 | 数据名称 | 关键字 | 占用内存 | 默认值 | 数据范围 |
|---|---|---|---|---|---|
| 整数 | 字节型 | byte | 1字节 | 0 | -128~127 |
| 整数 | 短整型 | short | 2字节 | 0 | -32768~32767 |
| 整数 | 整型 | int | 4字节 | 0 | -2147483648~2147483647 |
| 整数 | 长整型 | long | 8字节 | 0L | -9223372036854775808~9223372036854775807 |
| 浮点型 | 单精度浮点型 | float | 4字节 | 0.0F | 1.4E-45~3.4028235E38(范围较大) |
| 浮点型 | 双精度浮点型 | double | 8字节 | 0.0D | 4.9E-324~1.7976931348623157E308(范围较大) |
| 字符型 | 字符型 | char | 2字节 | \u0000(空字符) | \u0000~\uFFFF |
| 布尔型 | 布尔型 | boolean | 1字节或4字节,没有明确定义 | false | false或者true |
- 每当定义一个类型的变量,计算机就会在内存中开辟对应的内存空间,比如定义一个byte就会占用1个字节,换算成位数就是8位
- 每一个变量都会有属于自己的内存空间
- 上面写的demo_2虽然没有独立界面,只在控制台显示结果,但也能称为"程序",因为只要运行,计算机都会分配内存
- 轻量级设备特别会注意数据类型的定义,因为内存特别的小,为了增加Java的性能或者效率故此有8大数据类型
java
byte b =128;
System.out.println("b:"+b);
运行
java
java: 不兼容的类型: 从int转换到byte可能会有损失
java
//强制类型转换 ->溢出
byte b = (byte) 128;
System.out.println("b:"+b);
运行
java
b:-128
java
byte b = (byte) 129;
System.out.println("b:"+b);
运行
java
b:-127
- 强转有可能会发生溢出问题,从而影响最终结果的数据异常
- 超过了数据类型的表达范围,就不要使用byte,正常的使用short或int
java
int b = 129;
System.out.println("b:"+b);
运行
java
b:129
-
byte常用于一些大型数组的使用场景,比如说实用程序读取图片,一般都会把图片转成byte处理,可以节省空间提升效率
-
浮点型不建议用在需要精确计算的应用场景下
-
字符型可以存储任何一个字符。如果强转成int,可以得到对应的十进制
ASCII码:美国信息交换标准代码
大写字母A等于65,通过这个特性可以做很多字符的处理
强制转换数据类型,会发生一些问题,所以要谨慎使用
-
在计算机中可以使用false和true,或者使用0或1来表示假的、真的。比如在C语言,常用int =0 或1表示真假。在Java中boolean有可能被计算机转换为int,占4个字节,有的时候又会转换成byte,占1字节。很多人认为boolean多数时候占4字节,但官方并没有明确过,所以不需要太过纠结。
java
/**
* Java当中的基本数据类型
*/
public class Demo_2 {
public static void main(String[] args) {
//强制类型转换:byte b = (byte)129;
//字节
byte b =127;
System.out.println("b:"+b);
//短整型
short s =127;
System.out.println("s:"+s);
//整型
int i = 2147483647;
System.out.println("i:"+i);
//长整型
long l =2147483647L;
System.out.println("l:"+l);
//单精度
float f =3.14f;
System.out.println("f:"+f);
//双精度
double d =3.14d;
System.out.println("d:"+d);
//字符型
char c ='A';
int ic =(int)c; //强转
System.out.println("c:"+c);
System.out.println("ic:"+ic);
//布尔 ==== int false(0) true(1)
boolean bool = false;
System.out.println("bool:"+bool);
}
}
java
b:127
s:127
i:2147483647
l:2147483647
f:3.14
d:3.14
c:A
ic:65
bool:false
- 数据范围不用死记,通过固定的代码可以查看不同类型的属性
java
public class Demo_2 {
public static void main(String[] args) {
//byte
System.out.println("基本类型:byte 二进制位数:"+Byte.SIZE);
System.out.println("包装类:java.lang.Byte");
System.out.println("最小值:Byte.MIN_VALUE="+Byte.MIN_VALUE);
System.out.println("最大值:Byte.MAX_VALUE="+Byte.MAX_VALUE);
System.out.println();
//short
System.out.println("基本类型:short 二进制位数:"+Short.SIZE);
System.out.println("包装类:java.lang.Short");
System.out.println("最小值:Short.MIN_VALUE="+Short.MIN_VALUE);
System.out.println("最大值:Short.MAX_VALUE="+Short.MAX_VALUE);
System.out.println();
//int
System.out.println("基本类型:int 二进制位数:"+Integer.SIZE);
System.out.println("包装类:java.lang.Integer");
System.out.println("最小值:Short.MIN_VALUE="+Integer.MIN_VALUE);
System.out.println("最大值:Short.MAX_VALUE="+Integer.MAX_VALUE);
System.out.println();
//long
System.out.println("基本类型:long 二进制位数:"+Long.SIZE);
System.out.println("包装类:java.lang.Long");
System.out.println("最小值:Short.MIN_VALUE="+Long.MIN_VALUE);
System.out.println("最大值:Short.MAX_VALUE="+Long.MAX_VALUE);
System.out.println();
//float
System.out.println("基本类型:float 二进制位数:"+Float.SIZE);
System.out.println("包装类:java.lang.Float");
System.out.println("最小值:Short.MIN_VALUE="+Float.MIN_VALUE);
System.out.println("最大值:Short.MAX_VALUE="+Float.MAX_VALUE);
System.out.println();
//double
System.out.println("基本类型:double 二进制位数:"+Double.SIZE);
System.out.println("包装类:java.lang.Double");
System.out.println("最小值:Short.MIN_VALUE="+Double.MIN_VALUE);
System.out.println("最大值:Short.MAX_VALUE="+Double.MAX_VALUE);
System.out.println();
}
}
运行
java
基本类型:byte 二进制位数:8
包装类:java.lang.Byte
最小值:Byte.MIN_VALUE=-128
最大值:Byte.MAX_VALUE=127
基本类型:short 二进制位数:16
包装类:java.lang.Short
最小值:Short.MIN_VALUE=-32768
最大值:Short.MAX_VALUE=32767
基本类型:int 二进制位数:32
包装类:java.lang.Integer
最小值:Short.MIN_VALUE=-2147483648
最大值:Short.MAX_VALUE=2147483647
基本类型:long 二进制位数:64
包装类:java.lang.Long
最小值:Short.MIN_VALUE=-9223372036854775808
最大值:Short.MAX_VALUE=9223372036854775807
基本类型:float 二进制位数:32
包装类:java.lang.Float
最小值:Short.MIN_VALUE=1.4E-45
最大值:Short.MAX_VALUE=3.4028235E38
基本类型:double 二进制位数:64
包装类:java.lang.Double
最小值:Short.MIN_VALUE=4.9E-324
最大值:Short.MAX_VALUE=1.7976931348623157E308
某些类型后面为什么要加L、f、d?
- 在Java中,当声明一个long类型的变量并给它赋值时,在数值后面加上"L",这是为了明确告诉编译器这个是一个long类型的值,而不是int类型的值。
- Java对于变量类型是大小写敏感的。因此,单从字面上看,数值"123"会被视为int型,而"123L"则会被视为long类型。尽管在Java5之后,编译器已经足够智能,能够在不添加"L"或"l"的情况下自动推断出数值是long类型而不是int型,但是按照Java的编码规范,为了明确和统一,仍然推荐加上L,f,d。
1.10运算符
java
算符运算符:+ - * / ++ -- %
赋值运算符:=
不需要太过于记忆
和数学中的运算符大致相同
| 优先级 | 运算符 | 类 | 结合性 |
|---|---|---|---|
| 1 | () | 括号运算符 | 由左至右 |
| 1 | [] | 方括号运算符 | 由左至右 |
| 2 | ! +(正号)、-(负号) | 一元运算符 | 由右至左 |
| 2 | ~ | 位逻辑运算符 | 由右至左 |
| 2 | ++、-- | 递增与递减运算符 | 由右至左 |
| 3 | *、/、% | 算数运算符 | 由左至右 |
| 4 | +、- | 算数运算符 | 由左至右 |
| 5 | <<、>> | 位左移、右移运算符 | 由左至右 |
| 6 | >、>=、<、<= | 关系运算符 | 由左至右 |
| 7 | ==、!= | 关系运算符 | 由左至右 |
| 8 | &(位运算符AND) | 位逻辑运算符 | 由左至右 |
| 9 | (位运算符号XOR) | 位逻辑运算符 | 由左至右 |
| 10 | (位运算符号OR) | 位逻辑运算符 | 由左至右 |
| 11 | && | 逻辑运算符 | 由左至右 |
| 12 | ‖ | 逻辑运算符 | 由左至右 |
| 13 | ?: | 条件运算符 | 由右至左 |
| 14 | = | 赋值运算符 | 由右至左 |
java
public class Demo_1 {
public static void main(String[] args) {
int result = 10 * -3; //一元运算符符号,相当于10*(-3)
int result1 = (int) (7 / 2.5); //int强转,且后面也需加括号,特殊一点,知道就行
System.out.println("result:" + result);
System.out.println("result1:" + result1);
}
}
1.11关系运算符
代码可以用来模拟,抽象真实需求需要一些想象力
抽象:把具体的对象,使用代码抽象化。比如用一个变量替代价格...
java
import java.util.Scanner;
public class Demo_3 {
public static void main(String[] args) {
//欢迎词提示
System.out.println("====欢迎来到胖明螺蛳粉店====");
System.out.println("====一碗经典螺蛳粉只要15块钱====");
System.out.println("====请出示您付款金额====");
//付款
Scanner scanner = new Scanner(System.in);
int money = scanner.nextInt();
//判断钱给的够不够
System.out.println("判断一下钱给的够不够:" + (money >= 15)); //关系运算符>=
//计算价格并找零
int balance = money - 15;
System.out.println("找您" + balance + "元,并祝您嗦粉愉快!");
}
}
阅读别人的代码,静下心来每一行去分析做了什么事情,结合运行的结果和需求来分析别人为什么这样编写代码
先从简单的代码开始
学编程,多想、多试、多写...
java
//涉及运算符的优先级
System.out.println(10 > 9 == 7 < 7); // true == false false
System.out.println(3 == 4 == false); //true
System.out.println(3 == (4 == false)); //报错 int和boolean之间不能使用关系运算符
System.out.println(3 ==4 ==5); //报错 int之间也不能像数学那样做多次判断 false == 5
System.out.println(3 == true < false); //报错,boolean值没有大小之分,不能比较
System.out.println(0.1 == (1.3-1.2)); // false
//浮点数比大小的方法:判断两数的差值,是否比某个极小数还要小?
boolean b =Math.abs(0.1 - (1.3-1.2)) < 0.0000001; //Math.abs():获取一个数的绝对值
System.out.println(b);
小技巧:选中多行已注释的代码左边箭头可以折叠起来
2.0控制语句-if
java
import java.util.Scanner;
public class Demo_3 {
public static void main(String[] args) {
//欢迎词提示
System.out.println("====欢迎来到胖明螺蛳粉店====");
System.out.println("====一碗经典螺蛳粉只要15块钱====");
System.out.println("====请出示您付款金额====");
//付款
Scanner scanner = new Scanner(System.in);
int money = scanner.nextInt();
//判断钱给的够不够
System.out.println("判断一下钱给的够不够:" + (money >= 15)); //关系运算符>=
//if语句(判断条件是否成立,成立的话就执行大括号里的代码)
if (money >= 15) {
//计算价格并找零
int balance = money - 15;
System.out.println("找您" + balance + "元,并祝您嗦粉愉快!");
} else {
//提示钱不够
System.out.println("非常抱歉,您的钱没给够!");
}
System.out.println("欢迎您下次光临!");
}
}
java
//写法1
if(判断条件是否成立:true或者false){
//true条件成立就执行这里的代码
}
//写法2
if(判断条件是否成立:true或者false){
//true条件成立就执行这里的代码
}else{
//false条件不成立就执行这里的代码
}
//写法3:一般我们认为一个;号就是一个语句,这种写法只不过分成了两行来写,只有一个;号,可以理解是一个语句 (一般不建议这样写)
if(判断条件是否成立:true或者false)
System.out.println("欢迎下次光临!");
2.1断点调试
使用IDEA提供的调试功能(debug)可以跟踪代码的执行流程
- 在需要断点的代码行号边上点击,可以打上一个红色的断点,表示代码执行到这个地方需要停下来,等我们操作
- 点击Debug运行按钮,代码开始运行
- 代码会停在断点位置,并且弹出断点调试的观察页面,等待我们操作
-
此时"Threads & Variables"面板可以观察当前的变量值,以及一些其他数据的变化过程
-
可以执行下一步、停止等功能
-
代码页面,每一行的尾部也可以观察数据的变化过程
-
点击console,可以回到控制台查看输出结果
-
如果代码比较多,调试的地方比较多,可以多打几个断点
-
多使用debug可以帮助排查错误、理解代码,对程序员的帮助很大
if的嵌套使用
- 必备技能:分析、拆解需求
- 想清楚需求、再选择合适的技术手段
java
import java.util.Scanner;
/**
* 1.输入两个数,比较大小,并输出较大的那个
* 1.输入两个数(scanner) 2.比较大小(关系运算符)3.输出较大的数(println、算数运算符)
*/
public class Demo_5 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入两个整数:");
int a = scanner.nextInt();
int b = scanner.nextInt();
if (a > b) {
System.out.println("较大的数是:" + a);
} else {
System.out.println("较大的数是:" + b);
}
}
}
java
import java.util.Scanner;
/**
* 输入3个数,比较大小,并输入最大的那个
*/
public class Demo_6 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入三个整数:");
int a = scanner.nextInt();
int b = scanner.nextInt();
int c = scanner.nextInt();
if (a > b) {
if (a > c) {
System.out.println("较大的数是:" + a);
} else {
System.out.println("较大的数是:" + c);
}
} else {
if (b > c) {
System.out.println("较大的数是:" + b);
} else {
System.out.println("较大的数是:" + c);
}
}
}
}
//嵌套使用:else总是和最近的if匹配
2.3级联else if语句
java
import java.util.Scanner;
/**
* 用代码实现分段函数
* f(x) 当x<0,f(x)=1;当x=0,f(x)=x+2;当x>0,f(x)=3x-1
*/
public class Demo_7 {
public static void main(String[] args) {
//获取了一个x
System.out.println("输入一个整数x:");
Scanner scanner = new Scanner(System.in);
int x = scanner.nextInt();
if (x < 0) {
System.out.println("f(x)=" + 1);
} else if (x == 0) {
System.out.println("f(x)=" + (x + 2));
} else {
System.out.println("f(x)=" + (3 * x - 1));
}
}
}
- 多做注释,是个好习惯
- 可以多刷算法题,多写代码
java
import java.util.Scanner;
/**
* 用代码实现分段函数
* f(x) 当x<0,f(x)=1;当x=0,f(x)=x+2;当x>0,f(x)=3x-1
*/
public class Demo_8 {
public static void main(String[] args) {
//获取了一个x
System.out.println("输入一个整数x:");
Scanner scanner = new Scanner(System.in);
int x = scanner.nextInt();
int f;
if (x < 0) {
f = 1;
} else if (x == 0) {
f = x + 2;
} else {
f = 3 * x - 1;
}
System.out.println("f(x)=" + f);
}
}
//以上写法代码更易维护更简洁
- 建议始终在if语句中使用大括号
- else:总是和最近的if匹配
2.4逻辑运算符
如果有多个地方用到同个值,可以写成变量
布尔值取反:!
且:左右两个式子都要为true,最后结果就是真;只要有一个是false,那么结果就是false
多数编程语言的或且非都是一样的写法: || && !
a && b && c && d 支持多个条件拼接,遵循从左到右的运算顺序
java
import java.util.Scanner;
/**
* 用代码实现分段函数
* * f(x) 当0<x<5,f(x)=1;当x>=5或者<=0,f(x)=x+3
*/
public class Demo_9 {
public static void main(String[] args) {
//获取了一个x
System.out.println("输入一个整数x:");
Scanner scanner = new Scanner(System.in);
int x = scanner.nextInt();
int f;
//不支持写成0<x<5,语法错误,可以拆分为两个条件
if (x > 0 && x < 5) {
f = 1;
System.out.println("f(x)=" + f);
} else {
f = x + 3;
System.out.println("f(x)=" + f);
}
}
}
java
boolean b = !(x<0); //取反操作、非 (一元运算符)
true && true //结果为ture
true && false //结果为false
false && false //结果为false
true || true //结果为true
true || false //结果为true
false || false //结果为false
2.5控制语句-switch
将生活中的事物,用代码抽象地表达出来
case:添加一个控制分支
break:打破、跳出当前控制语句
default:类似于else的作用,考虑剩余的情况
这里使用的是整型变量,也可以用枚举类型,也可以在switch中使用
case后面一定是常量(固定的数)
default可以省略不写
遇到break会退出当前switch,如果没有break,则会不断往下执行
java
switch(i){
case 0:
case 1:
case 100:
//当i等于0,1,100时执行这里的代码...
break; //根据实际情况来写代码,在合适的地方添加break
}
java
import java.util.Scanner;
/**
* 请输入您要进行的选项:
* 1(开始游戏) 2(暂停游戏) 3(查看回放) 4(保存游戏进度) 5(退出游戏) 6输入其他(提示异常)
*/
public class Demo_10 {
public static void main(String[] args) {
System.out.println("请输入您要进行的选项:");
Scanner scanner = new Scanner(System.in);
int a = scanner.nextInt();
switch (a) {
case 1: //case后面一定是常量(固定的数)
System.out.println("游戏已开始");
break;
case 2:
System.out.println("游戏已暂停");
break;
case 3:
System.out.println("正在查看回放");
break;
case 4:
System.out.println("当前进度已保存");
break;
case 5:
System.out.println("正在退出游戏");
break;
default:
System.out.println("您的输入错误");
break;
}
}
}
java
//语法
switch(控制表达式){
case 常数1:
//如果控制表达式与常数1匹配,就执行这里的代码
break;
case 常数2:
//如果控制表达式与常数2匹配,就执行这里的代码
break;
default:
//如果控制表达式找不到相匹配的case,就执行这里的代码
break;
}
3.0循环语句-while
只要条件成立,循环就会不断执行
如果固定写false,这段代码就永远不会被执行
写代码的过程总会有很多漏洞(bug)
小技巧:当点击大括号时,匹配的大括号也会显示相同的标记
java
//while的用法
while(控制循环的表达式){
//只要控制表达式为true,机会不断执行这里的代码
}
//死循环
while(true){
}
java
import java.util.Scanner;
public class Demo_11 {
public static void main(String[] args) {
int pay = 0;
while (true) {
//欢迎词提示
System.out.println("====欢迎来到胖明螺蛳粉店====");
System.out.println("====一碗经典螺蛳粉只要15块钱====");
System.out.println("====请出示您付款金额====");
//付款
Scanner scanner = new Scanner(System.in);
int money = scanner.nextInt();
pay = pay + money;
//if语句(判断条件是否成立,成立的话就执行大括号里的代码)
if (pay >= 15) {
//计算价格并找零
int balance = pay - 15;
System.out.println("找您" + balance + "元,并祝您嗦粉愉快!");
pay = 0;
} else {
//提示钱不够
System.out.println("非常抱歉,您的钱没给够!");
}
}
}
}
3.1使用while计数
每次循环,都通过一些方式改变控制表达式的条件,就能控制循环的继续或者是跳出
java
/**
* 实现倒计时10s下班
*/
public class Demo_12 {
public static void main(String[] args) {
int count = 10;
while (count > 0) {
System.out.println("倒计时" + count + "s,准备下班!");
count = count - 1;
}
System.out.println("下班啦!干饭!");
}
}
3.2循环语句-do while
java
//do while的用法
do{
//只要控制表达式为true,就会不断执行这里的代码
}while(控制表达式);
do while和while的区别
- while需要先判断表达式是否成立,只有成立才会执行循环体里面的代码
- do while,无论如何都会先做一次循环,做完一次循环之后再去判断条件是否成立
- 循环体:大括号括起来的代码
- 使用恰当的代码,可以是事半功倍
java
import java.util.Scanner;
/**
* 用户输入数字,然后计算用户输入数字的总和。如果用户输入了0,循环将停止
*/
public class Demo_13 {
public static void main(String[] args) {
int total = 0;
int number;
do {
System.out.println("请输入数字(输入0停止):");
Scanner scanner = new Scanner(System.in);
number = scanner.nextInt();
total = total + number;
} while (number != 0);
System.out.println("输入数字的总和为:" + total);
}
}
3.3循环语句-for
第一部分:一般用来初始化控制循环的变量
第二部分:循环条件
第三部分:每一步都会执行的动作
循环的三个设定可以都不写,就是无限循环(死循环)
java
for(初始化操作;进入循环的条件;每一次循环都会执行的动作){
// 循环体
}
//for里面的表达式可以被省略(死循环)
for(; ;){}
for可以不写大括号,但是不建议
for(; ;)语句;
java
/**
* 输出1-100之间的所有整数,并计算总和
*/
public class Demo_14 {
public static void main(String[] args) {
int total = 0;
//number开始的时候等于1,当它<=100的时候,进入到循环体,每次循环执行完毕后执行加1
for (int number = 1; number <= 100; number = number + 1) {
System.out.println(number);
total = total + number;
}
System.out.println("total:" + total);
}
}
- 初始化操作:设定一个变量,类似于是一个计数器,用来控制循环是否要继续,或者是退出;
- int number =1;
- 一般控制循环的变量,不会在循环体外面使用 ,所以多数时候都在for里面定义。
- 循环的条件:和while类似,只有条件满足的时候才会进入循环体,不满足的时候直接跳出循环;
- 步进(每一步都会执行的动作):每一轮循环后,都会执行这里的动作,一般情况都在这里控制循环的变量,多数时候加减。
如何选择循环语句
- 知道固定的循环次数,用for;
- 不知道循环次数,但是知道循环终止的条件,用while;
- 必须执行一次循环体,do while,哪怕第一次循环判断是false。
3.4递增运算符的区别
for循环通常使用递增运算符的写法,例如:
java
for (int i = 1; i <= 100; i++) {
//循环体
}
小技巧:写代码的过程中,为了让结果看得更清楚,可以多打印一些容易分辨的特殊符号出来...
java
System.out.println("===========================");
- 把上面代码跟下面代码做一个分割,这样就知道下面是我做的测试代码,做一些小模块的调试,也不用重复繁琐的去创建很多文件
java
/**
* 输出1-100之间的所有整数,并计算总和
*/
public class Demo_14 {
public static void main(String[] args) {
int total = 0;
//number开始的时候等于1,当它<=100的时候,进入到循环体,每次循环执行完毕后执行加1
//number = number + 1可以写成number++
for (int number = 1; number <= 100; number = number + 1) {
System.out.println(number);
total = total + number;
//total = total + number可以写成total += number
}
System.out.println("total:" + total);
System.out.println("===========================");
int i = 5;
int a = i++; //i++是在表达式计算完之后再做自增操作,i遇到赋值运算符=先把5赋值给了a,自身i的值再加1;所以a的值为5,i的值为6
System.out.println("i= " + i);
System.out.println("a = i++的结果是:" + a);
}
}
java
===========================
i= 6
a = i++的结果是:5
java
public class Demo_14 {
public static void main(String[] args) {
int i = 5;
int a = ++i; //++i是在表达式计算之前就做自增操作,i先自增为6再赋值给了a
System.out.println("i= " + i);
System.out.println("a = ++i的结果是:" + a);
}
}
java
i= 6
a = ++i的结果是:6
java
//i++ 在表达式计算完之后再做自增操作
//++i 在表达式计算之前就做自增操作
sum += number; //等同于sum = sum + number;
//同样还有 -= , *= , /= 都是一样的用法,这种写法称为复合赋值
- 增加难度,加深理解:
java
public class Demo_14 {
public static void main(String[] args) {
int i = 5;
int a = ++i + i;
System.out.println("i= " + i);
System.out.println("a = ++i的结果是:" + a);
}
}
java
i= 6
a = ++i + i的结果是:12
java
public class Demo_14 {
public static void main(String[] args) {
int i = 5;
int a = i++ + i;
System.out.println("i= " + i);
System.out.println("a = ++i的结果是:" + a);
}
}
java
i= 6
a = i++ + i的结果是:11
3.5使用for循环求素数1
- 余数如果为0,说明被整除
- 编程是抽象的,但是我们可以让他具体一点...用一个较小的数来带入想象
- 只在一个地方做结果处理。这算是一种程序设计思想,可以多借鉴
- 流程比较抽象的,都建议打断点用debug跟踪调试下
java
import java.util.Scanner;
/**
* 求素数(质数):大于1的自然数,除了1和它本身之外,不能被其他的自然数整除
* 2~int最大值 这个范围内的整数
* 7: 7/2 7/3 7/4 /7/5/7/6 for (编程是抽象的,但是我们可以让他具体一点...用一个较小的数来带入想象)
* 余数如果为0,说明被整除 取余%
* scanner 输入
*/
public class Demo_15 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一个整数");
int num = scanner.nextInt();
boolean isprime = true; //先假设它一定是素数
if (num <= 1) {
isprime = false;
} else {
for (int i = 2; i < num; i++) {
//num % i 取余的结果为0,证明被整除,不是素数
if (num % i == 0) {
isprime = false;
break; //跳出整个循环
}
}
}
//isPrime已经是布尔值,所以if里不需要写成:if(isPrime == true)
if (isprime) {
System.out.println(num + "是素数");
} else {
System.out.println(num + "不是素数");
}
}
}
3.6嵌套for循环
println:输出+换行
print:只做输出
手动换行: \n换行符
不当使用嵌套循环,会引起性能问题
小技巧:把光标放到break,可以看到跳出的是哪个for循环
java
/**
* 输出1-100的所有素数
* 1. 1-100总共有100个数
* 2. 每一个数,都需要做多次判断,判断是够能被除了1和本身的数整除
*/
public class Demo_16 {
public static void main(String[] args) {
//嵌套循环
for (int i = 1; i < 101; i++) {
//默认这个数是素数
boolean isprime = true;
//判断是不是素数
for (int j = 2; j < i; j++) {
if (i % j == 0) {
isprime = false;
break;
}
}
if (isprime) {
System.out.print(i + " ");
}
}
}
}
break和continue
java
if(true){
//可以终止当前的for、while、do while循环,并且不再执行后续的循环
break;
}
if(true){
//可以终止本次的for、while、do while循环,并且直接开始下一次的循环
continue;
}
STOP:
for(初始化操作;循环的条件;每一步都会执行的动作){
//循环体
for(初始化操作;循环的条件;每一步都会执行的动作){
if (true){
//这样可以控制最外层的循环,相当于给最外层的for循环命名为STOP
break STOP;
}
}
}
4.0数组
索引、下标:用来查找位置
number[5] :表示索引为5,顺序为6的数,数组第一位的索引为0
数组多数时候与for循环是好搭档
java
import java.util.Scanner;
/**
* 用户输入数字,然后计算用户输入数字的总和。
* 输出比平均数小的数字
* 如果用户输入了0,循环将停止
*/
public class Demo_17 {
public static void main(String[] args) {
int sum = 0; //求和
int[] numbers = new int[10];
System.out.println("请输入一个数字(最多可以输入10个)");
Scanner scanner = new Scanner(System.in);
for (int i = 0; i < 10; i++) {
numbers[i] = scanner.nextInt();
sum += numbers[i];
}
int ave = sum / numbers.length;
for (int i = 0; i < numbers.length; i++) {
if (numbers[i] < ave) {
//如果比平均值小,输入
System.out.println(numbers[i] + " ");
}
}
System.out.println("\nnumbers.lenth:" + numbers.length);
System.out.println("输入数字的总和为:" + sum);
}
}
4.1数组的特性
java
//语法
<类型>[]<数组名> = new <类型>[长度];
int[] numbers = new int[10];
必须定义数组的长度,长度可以是个变量,也可以是个正整数,甚至还可以是用户输入的值,具体看应用场景
java
int sum =10;
int[] numbers = new int[sum];
java
Scanner scanner = new Scanner(System.in);
int[] numbers = new int[scanner.nextInt()];
数组还有另外一种定义方式,适用于已经知道这个数组要存的内容了
java
//已经知道要存多少数据了
int[] numbers = new int[]{1, 2, 3, 4, 5};
数组存储的数据必须是同类型 (回顾一下八大数据类型)
java
byte b =1;
//byte,int都是整型是可以存储在同一个数组的
int[] numbers = new int[]{1, 2, 3, 4, b};
int[] numbers1 = new int[5];
numbers1[4] = b;
System.out.println(numbers[4]);
System.out.println(numbers1[4]);
//如果是double,可以用强制转换也是可以的,不强转就会报错
double b = 5.0;
numbers1[3] = int(b);
编码的过程,需要经常对异常做预防处理
运行时异常:程序运行后发生崩溃报错
java
int[] numbers1 = new int[5];
numbers1[9] = 1; //超出了数组的长度,编译器不会检查数组真实长度,所以一般都需要自己做防护,否则容易引起运行时异常(下标越界)
数组的第一个索引(下标)是0,最后一个就是length-1
数组的长度是固定的,第一次给了它长度,后续就不能更改其长度了
java
//存
number[0] = 6;
//取
int i = number[1];
4.2数组变量与普通变量的区别
new: 新建了一个数组对象
new出来的数组对象,和变量直接赋值含义不同
左边的部分只是变量名,我们可以通过变量名访问到内存中数组的区域
普通变量初始化,会单独开辟各自的内存区域,a、b之间即使赋值也是独立关系
java
public class Demo_18 {
public static void main(String[] args) {
int[] number = new int[]{1, 2, 3, 4, 5};
int[] number1 = number;
number1[1] = 6;
System.out.println("number1[1] = " + number1[1]);
System.out.println("number[1] = " + number[1]);
System.out.println("=============");
int a = 3;
int b = a;
b = 4;
System.out.println("a = " + a);
System.out.println("b = " + b);
}
}
java
number1[1] = 6
number[1] = 6
=============
a = 3
b = 4
数组的赋值操作,其实是让数组变量取持有右边的数组本身,让变量成为这个数组的管理者
java
public class Demo_18 {
public static void main(String[] args) {
int[] number = new int[]{1, 2, 3, 4, 5};
int a = 3;
int b = a;
System.out.println("a = " + a);
System.out.println("b = " + b);
if(a == b){
System.out.println("a和b是相等的");
}else {
System.out.println("a和b是不相等的");
}
int[] number0 =new int[]{1, 2, 3, 4, 5};
if(number0 ==number){
System.out.println("number0和number是相等的");
}else {
System.out.println("number0和number是不相等的");
}
}
}
java
a = 3
b = 3
a和b是相等的
number0和number是不相等的
new会新建一个数组对象,即使数据完全相同,但在内存的所谓位置不同,所以不相等
数组做==操作,对比的是在内存中的存储位置是否相同,而不是比较值是否相同
也可以手写判断,比较麻烦
java
public class Demo_18 {
public static void main(String[] args) {
int[] number = new int[]{1, 2, 3, 4, 5};
int[] number0 = new int[]{1, 2, 3, 4, 5};
boolean isEquals = true; //假设是相等的
if (number.length == number0.length) {
for (int i = 0; i < number0.length; i++) {
if (number0[i] != number[i]) {
isEquals = false;
break;
}
}
}
if (isEquals) {
System.out.println("number0和number是相等的");
} else {
System.out.println("number0和number是不相等的");
}
}
}
不能直接做比较,如果想要对比数组是否相等,除了手写判断外,Java也提供了对比数组是否相等的方法
java
//写法
boolean b = Arrays.equals(数组1, 数组2);
java
import java.util.Arrays;
public class Demo_18 {
public static void main(String[] args) {
int[] number = new int[]{1, 2, 3, 4, 5};
int[] number0 = new int[]{1, 2, 3, 4, 5};
boolean isEquals = Arrays.equals(number0,number);
if (isEquals) {
System.out.println("number0和number是相等的");
} else {
System.out.println("number0和number是不相等的");
}
}
}
如果需要获取一个相同的数组,不能直接赋值,需要循环赋值
java
import java.util.Arrays;
public class Demo_18 {
public static void main(String[] args) {
int[] number = new int[]{1, 2, 3, 4, 5};
int[] number1 = new int[number.length];
for (int i = 0;i< number.length;i++){
number1[i] =number[i];
}
number1[1] = 6;
System.out.println("number1[1] = " + number1[1]);
System.out.println("number[1] = " + number[1]);
}
}
java
number1[1] = 6
number[1] = 2
4.3二维数组
小tips:常犯的错误:使用中文输入法输入符号,比如:括号、逗号、分号...
Java编译器不会因为缩进、换行、空格等代码格式,就改变程序结果
java
public class Demo_19 {
public static void main(String[] args) {
int[] number = new int[]{1, 2, 3, 4, 5};
//定义了一个5行4列的二维数组
int[][] number1 = new int[5][4];
//4行5列的二维数组
int[][] number2 = new int[][]{
{1, 2, 3, 4, 5},
{2, 4, 6, 8, 10},
{4, 56, 78, 2, 1},
{0, 7, 89, 4, 2},
};
System.out.println(number1[4][3]); //输出5行4列的值
System.out.println(number2[3][4]); //输出4行5列的值
}
}
java
0
2
数组的一个特性,如果没有指定值,默认值就是当前这个类型的的默认数据,int整数类的默认数据就是0
java
//如果布尔类型,默认数据就是false
boolean[][] number1 = new int[5][4];
二维数组通过两个索引确定一个位置
java
public class Demo_19 {
public static void main(String[] args) {
//4行5列的二维数组
int[][] number2 = new int[][]{
{1, 2, 3, 4, 5},
{2, 4, 6, 8, 10},
{4, 56, 78, 2, 1},
{0, 7, 89, 4, 2},
};
//number2.length的意思是number2的行数
//number2[i].length,虽然这里都是5行,但习惯这样写,意思是number2第i行的列数
for (int i = 0; i < number2.length; i++) {
for (int j = 0; j < number2[i].length; j++) {
System.out.println("number2[" + i + "][" + j + "] = " + number2[i][j]);
}
}
}
}
java
number2[0][0] = 1
number2[0][1] = 2
number2[0][2] = 3
number2[0][3] = 4
number2[0][4] = 5
number2[1][0] = 2
number2[1][1] = 4
number2[1][2] = 6
number2[1][3] = 8
number2[1][4] = 10
number2[2][0] = 4
number2[2][1] = 56
number2[2][2] = 78
number2[2][3] = 2
number2[2][4] = 1
number2[3][0] = 0
number2[3][1] = 7
number2[3][2] = 89
number2[3][3] = 4
number2[3][4] = 2
java
//二维数组的用法
<类型>[][] <数组名> = new <类型>[行数][列数];
int[][] number = new int[5][4];
//3行5列的二维数组
int[][] number = new int[][]{
{1,3,3,4,45},
{2,1,4,3,2},
{1,1,1,1,1},
}
//取
int a = number[1][2];
//存
number[0][1] = 8;
//循环遍历二维数组
for (int i = 0; i < number2.length; i++) {
for (int j = 0; j < number2[i].length; j++) {
System.out.println(number2[i][j]);
}
}
5.0Unicode字符
Unicode
由于计算机在美国发明,大家更多考虑的是如何在计算机中表示。从而得到ASCII码表,表示英文字母总共26个,加上特殊字符,128个字符。后来其他国家也陆续有了自己的编码表,标准不统一,于是就会产生一些问题,于是Unicode诞生了,它可以表示非常多的字符,包括数字、符号、英文、汉字、德文、日文等等,总共可以表示65536个字符。
同时,Java支持Unicode和各种编码规则,所以不同编码规范之间可以通过一些算法进行转换,甚至一些编码都是通用的。比如说,在Unicode中,ASCII码的字符被分配到0x00-0x7f的范围内,所以我们也可以认为,ASCII码是Unicode码的一个子集。
链接:ASCII码对照表
- Unicode和其他的一些编码可以相通,或是转换
- 稍微了解一下就好,实际上在开发当中并不需要考虑这么复杂的场景
- 只需要知道Java支持各种编码,Unicode、ASCII码、甚至是中国的编码标准 UTF-8
字符型
java
public class Demo_20 {
public static void main(String[] args) {
char c = 'A';
char c1 = '中';
char c2 = '6';
char c3 = '\u0041'; //使用在线Unicode编码转换,A转Unicode得到
//这种写法我们认为得到的是对应Unicode的编码值 A 65 中 20013
System.out.println((int) c);
System.out.println((int) c1);
System.out.println((int) c2);
System.out.println(c3);
}
}
java
65
20013
54
A
- ASCII是Unicode的子集,Unicode包含ASCII
- 纠正一下叫法:A转成的65叫做ASCII码值,\u0041才叫做Unicode编码
java
public class Demo_20 {
public static void main(String[] args) {
System.out.println('\u4e2d');
System.out.println('\u263a'); //聊天用的emji表情
}
}
java
中
☺
我想要得到一个B
java
public class Demo_20 {
public static void main(String[] args) {
System.out.println('A' + 1);
}
}
输出了B对应的ASCII码的值66
java
66
java
public class Demo_20 {
public static void main(String[] args) {
System.out.println((char)('A' + 1)); //输出B
//快捷生成变量
//char b = (char)('A' + 1);
//System.out.println(b);
}
}
小技巧-快捷生成变量:Ctrl + Alt + V,例如选中(char)('A' + 1),再按快捷键即可生成变量
char做比较运算,比较的是码点值,也就是Unicode或者ASCII中的位置
java
public class Demo_20 {
public static void main(String[] args) {
Boolean b1 = 'A' > 'a'; //比较的是Unicode编码值的大小
System.out.println(b1); //输出的结果是false
//通过一些特性,做大小写转换
char a = 'a';
char a1 = (char) ('a' + 'A' - 'a');
System.out.println(a1); //输出的结果是A
//原理:'a' + 'A'与'a'的差值,得到了A所在的Unicode值,再去转换成char类型
}
}
5.1逃逸字符
| 字符 | 含义 |
|---|---|
| \b | 回退一格 |
| \t | 切换到下个表格位 |
| \n | 换行符 |
| \r | 回车 |
| \" | 双引号 |
| \' | 单引号 |
| \\ | 反斜杠 |
不用刻意记忆,用到的时候自然会想到加个\
java
public class Demo_20 {
public static void main(String[] args) {
//在char里面显示一个单引号
char c4 = '\'';
System.out.println(c4); //输出单引号
char c5 = '\\';
System.out.println(c5); //输出反斜杠
}
}
5.2字符串
双引号里的:字符串
String:字符串类
小技巧-项目全局搜索快捷键:Ctrl +Shift + F
搜索array回顾数组的代码
字符串变量.equals();
java
import java.util.Scanner;
public class Demo_21 {
public static void main(String[] args) {
//字符串类
String helloWorld = "hello world"; //更常用,这种定义方式称为"字面量"
String hello1 = new String("hello world"); //也可以这样写,编译器也会提醒你这样写是多余的,但是不会报错
System.out.println(helloWorld);
System.out.println(hello1);
//读取用户输入的字符串
Scanner scanner = new Scanner(System.in);
System.out.println("请输入字符串:");
String next = scanner.next(); //读取空格、回车、\t之前的数据
String nextLine = scanner.nextLine(); //一整行
System.out.println("next:"+next);
System.out.println("nextLine"+nextLine);
//new会创建新的对象
String hello2 = new String("hello world");
String hello3 = new String("hello world");
//对比的是hello2和hello3的内存地址,换句话说,是在对比,这两个变量指向的是不是同一个对象?
System.out.println(hello2 == hello3); //结果是false,不同的两个对象
//单纯的对比hello2和hello3的内容是否相等可以用equals方法
System.out.println(hello2.equals(hello3)); //结果是true
}
}
- 字符串被创建后可以理解为是一个字符串对象,左边的变量只是当前字符串的管理者;
- 字符串比较内容是否相等需要用equals(),而不能使用==。
5.3字符串常量池
字符串常量池:为了优化性能,Java中拿来存放常量的一片内存区域
这种歌特性对实际开发没有特别大的影响,但这属于高频面试题
java
import java.util.Scanner;
public class Demo_21 {
public static void main(String[] args) {
//使用字面量的方式去定义字符串,这种形式会被Java当成是常量,会被放在常量池当中,如果下次有相同的,就会被做相同的引用
String hello4 = "hello world";
String hello5 = "hello world";
System.out.println(hello4 == hello5); //结果是true
}
}
两个字符串是否相等?
对于写代码来说,这个问题没什么好纠结的,因为==来比较两个字符串的场景几乎不存在,但这算是一个面试高频问题,因此记录一下。
在Java中,当你使用 == 关系运算符来比较两个字符串对象时,实际上是在比较它们的引用地址,而不是它们的值。如果两个字符串对象引用的是内存中的同一个对象、同一个内存地址,那么 == 操作符将返回 true。否则,即使两个字符串的内容完全相同, == 操作符也会返回false。
然而,从Java 7开始,Java引入了一种叫做字符串字面量池(String Literal Pool)或者叫字符串常量池(String Constant Pool)的机制。当使用字面量(即直接在代码中写下的字符串值)来创建一个字符串对象时,Java会检查字符串常量池中是否有一个相同内容的字符串对象存在。如果存在,Java就会返回对这个已存在对象的引用,而不是创建一个新的对象。
例如:
java
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); //输出true
在这个例子中,s1和s2都是通过字面量创建的,所以它们引用的是字符串常量池中的同一个对象,因此 s1 == s2的结果是true。
但是,如果使用new关键字来显示的创建一个新的字符串对象,那么即使这个对象的内容与字符串常量池中的某个对象相同,它们也不会被认为是相同的对象。
例如:
java
String s1 = "hello";
String s2 = new String("hello");
System.out.println(s1 == s2); //输出false
在这个例子中,s2是通过new关键字创建的,所以它引用的是一个全新的对象,而不是字符串常量池中的对象。因此s1 == s2的结果是false。
使用equals()方法来比较字符串的值通常是一个更好的选择。equals()会比较连个字符串的内容是否相同,而不是比较它们的引用是否相同。
例如:
java
String s1 = "hello";
String s2 = new String("hello");
System.out.println(s1.equals(s2)); //输出true
5.4字符串常见操作
单纯的String,就是一个类
new创建出来的就叫对象
equals存在于String类当中,只要有String对象,就能引用到equals
方法:可以理解为快速处理某件事情的方法
对象.会显示出各种方法,选择合适的方法就能处理对应的需求
编程是个技术活,熟能生巧
java
import java.util.Locale;
import java.util.Scanner;
public class Demo_21 {
public static void main(String[] args) {
String hello6 = "hello world";
System.out.println(hello6.length()); //输出字符串长度
System.out.println(hello6.indexOf("o")); //查找o所在的索引,输出的是第一个o的索引 4
System.out.println(hello6.indexOf("p")); //不存在,返回-1
System.out.println(hello6.indexOf("wor")); //输出的是第一个字符开始的索引 6
//获取o在hello6中的索引
//在hello6中的第o + 1的位置开始查找,获取o的索引
int o = hello6.indexOf("o");
System.out.println(hello6.indexOf("o", o + 1));
//获取hello6中,下标为2(位置是3)的字符
System.out.println(hello6.charAt(2));
//使用ld!替换ld
String hello7 = hello6.replace("ld", "ld!");
System.out.println("hello7 = " + hello7); //输出hello7 = hello world!
//截取指定索引的子字符串
String subString = hello6.substring(3); //截取索引3开始的子字符串
String subString1 = hello6.substring(3,6); //索引3开始索引6结束(不包含索引6)
System.out.println("subString = "+subString);
System.out.println("subString1 = "+subString1);
//大小写转换
String upperCase = hello6.toUpperCase(); //小写字母转为大写
System.out.println(upperCase);
String lowerCase = upperCase.toLowerCase(); //大写字母转为小写
System.out.println( lowerCase);
}
}
有些方法处理完毕后,只返回新的对象,而不会改变原对象的值
字符串的一些操作不会改成原来的字符串,而是会产生新的字符串
关于类、对象、变量的称呼
在实际开发中,大家口头上都是叫String或则叫字符串,在这里适当做个区分,如果觉得费劲,不必纠结,后面慢慢就知道了:
- 如果是没有被使用,只是单纯的提起Java中的String或者是Scanner,那指的就是字符串或则Scanner这个类;
- 像是"hello world"、new String("hello world"); 、new Scanner(System.in)这样新建出来的东西,可以称呼为字符串对象、scanner对象;
- String h = "hello world"; 左边的h,可以认为是一个字符串变量,变量名是h。
5.5包裹类型
Integer:可以理解为Java中int类型所对应的类
valueOf只是不需要我们写成new的形式,但原理还是new
基本数据类型对应的包裹类型或者包装类型
Integer是int类型对应的包裹类型
为了方便我们对基本数据类型做处理,Java为8种基本数据类型分别提供了对应的包装类型,通过包装类型可以很方便的对基本类型做一些操作,加快编程效率。
java
public class Demo_22 {
public static void main(String[] args) {
int i = 9;
//Integer integer = Integer.valueOf(i); 可以不用这么写,可以直接写成Integer integer = i;
//包裹类型 把一个变量直接赋值给integer对象,可以理解为是自动装箱的操作
Integer integer = i;
Integer integer1 = 100;
int i1 = integer1; //把一个Integer转成int,可以理解为是自动拆箱的操作
//int对应 Integer
System.out.println(Integer.max(7, 8)); //输出最大值
int x = Integer.parseInt("9"); //把String对象转成Integer对象
System.out.println(x);
Integer num = 123;
String string = num.toString(); //将Integer对象转换为String对象
System.out.println(string);
//double对应 Double
System.out.println(Double.sum(1.0, 2.1)); //计算和
//float对应 Float
System.out.println(Float.min(1.0f, 2.1f)); //输出最小值
//boolean对应Boolean
System.out.println(Boolean.logicalOr(true, false)); //或运算
//char对应 Character
System.out.println(Character.isLowerCase('A')); //判断是否小写
//byte对应 Byte
//short对应 Short
//long对应 Long
}
}
包裹类型创建后是一个对象,而不是基本数据类型,对象才可以提供各种各样的方法...
5.6API文档的使用
想要熟悉Java的常见类的用法:
多写、多模仿
多看文档
链接(有其他看的习惯的文档也可以,看得懂就行):JDK17官方文档
例如搜索String,翻看该类的更多方法的使用;
前期不要一个个的看过去,只关注学过的类就可以了,看多了也容易忘,而且还会增加学习难度!
学有余力时,也可以多翻看文档,学习Java的更多用法。
6.1方法的定义
方法:实现一些特定的需求
java
public class Demo_23 {
public static void main(String[] args) {
//使用math里面的max获取两个数中比较大的那个
int max = Math.max(7, 8);
String result = "7和8之中更大的数是:" + max;
System.out.println(result);
//使用Math里的addExact计算两个数的和
int i = Math.addExact(7, 8);
System.out.println("7+8的和是" + i);
//使用String里面的length获取当前这个字符串的长度
int length = result.length();
System.out.println("result的字符串长度是" + length);
}
}
java
public class Demo_23 {
public static void main(String[] args) {
int a = addExct5(1, 2, 3, 4, 5);
System.out.println("result=" + a);
}
/**
* 计算5个整型的和
*
* @param a 这是第1个参数
* @param b 这是第2个参数
* @param c 这是第3个参数
* @param d 这是第4个参数
* @param e 这是第5个参数
* @return 5个数的和
*/
public static int addExct5(int a, int b, int c, int d, int e) {
int result = a + b + c + d + e;
return result;
}
}
6.2方法的使用
void:无类型、没有声明类型
方法也可以叫函数,可以帮我们实现一些特定的需求。如果系统方法无法满足,也可以自定义方法
java
public class demo_24 {
public static void main(String[] args) {
sum(1000,100);
sum(200,3000);
}
/**
* 计算start-end之间所有整数的和
* @param start 开始
* @param end 结束
*/
public static void sum(int start, int end) {
//防止前者比后者大,交换顺序
if (start > end) {
int temp = start;
start = end;
end = temp;
}
int b = 0;
for (int j = start; j<=end; j++){
b += j;
}
System.out.println(start + "-" + end + "直接所有整数的和等于" + b);
}
}
java
/**
* 计算start-end之间所有整数的和
* @param start 开始
* @param end 结束
*/
public static 返回值类型 方法名(int start, int end) {
//这里是方法体
//防止前者比后者大,交换顺序
if (start > end) {
int temp = start;
start = end;
end = temp;
}
int b = 0;
for (int j = start; j<=end; j++){
b += j;
}
System.out.println(start + "-" + end + "直接所有整数的和等于" + b);
//return 0; 如果有指定返回类型,就需要return,如果是void.就不需要
}
6.3参数列表
调用方法的时候,需要严格按方法的参数列表来传值,匹配对应的数量和类型。
方法参数列表能接收的参数:
- 字面量(固定值)
- 变量名
- 其他方法的返回值
- 直接计算的结果
调用时传递的参数类型比参数列表定义的类型更小(窄)的时候,编译器会自动匹配、转换;(例如参数定义的是int,传的是char, 'A')
如果更大(宽),就需要做取舍,做强制转换;
(例如参数定义的是int,传的是double,强转为int,int(3.4),值只有3)
如果相互之间没办法转换,就无法使用
每个方法都有自己相对独立的变量空间,和其他方法各自独立。
小灯泡提示:IDEA会根据当前场景给出几个选择方案,但具体如何处理需要自行判断。
注意:传递8大基本数据类型,仅仅只是在传递值
java
public class demo_24 {
public static void main(String[] args) {
//使用math里面的max获取两个数中比较大的那个
int max = Math.max(7, 8);
String result = "7和8之中更大的数是:" + max;
System.out.println(result);
//使用Math里的addExact计算两个数的和
int i = Math.addExact(7, 8);
System.out.println("7+8的和是" + i);
//方法参数列表能接收的参数:字面量(固定值)、变量名、其他方法的返回值、直接计算的结果
sum(200, 3000);
sum(max, i);
sum(addExct5(1, 2, 3, 4, 5), 6);
sum(1 + 1, 2 + 3);
//每个方法都有自己相对独立的变量空间,和其他方法各自独立
int e =1;
int d =2;
swap(e,d); //输出结果为d=1,e=2 注意:传递8大基本数据类型,仅仅只是在传递值
System.out.println("d=" + d + ",e=" + e); //输出结果为d=2,e=1
}
/**
* 交换两个参数的值
*
* @param e 参数1
* @param d 参数2
*/
public static void swap(int e, int d) {
int temp = e;
e = d;
d = temp;
System.out.println("d=" + d + ",e=" + e);
}
/**
* 计算5个整型的和
*
* @param a 这是第1个参数
* @param b 这是第2个参数
* @param c 这是第3个参数
* @param d 这是第4个参数
* @param e 这是第5个参数
* @return 5个数的和
*/
public static int addExct5(int a, int b, int c, int d, int e) {
int result = a + b + c + d + e;
return result;
}
}
6.4返回值
如果有声明方法的返回类型,那么就必须用return返回对应类型的值,这个值可以是基本类型,也可以是某个对象类型;
一个方法可以声明多个return,但如果遇到其中一个,就会提前结束方法的执行。
java
public static int test() {
Scanner scanner = new Scanner(System.in);
int test = scanner.nextInt();
if (test == 0) {
return 100;
} else if (test == 1) {
return 200;
}
return 500;
}
即使是返回void的方法,也可以return来提前结束当前方法。
java
public static void swap(int e, int d) {
if (e == d) {
System.out.println("e和f相等,不需要交换");
return;
}
int temp = e;
e = d;
d = temp;
System.out.println("d=" + d + ",e=" + e);
}
小技巧:调试时,想要点进调用的方法体里面,可以点击"Step Into"
java
int test1 =test1(); //Debug到这行代码时,可以点击"Step Into",进入test1()方法
System.out.println("test1 = "+test1);
6.5本地变量
方法每次被调用,计算机都会为它分配一片独立的内存区域,作用域只在这个方法里面,里面的变量,称为本地变量。
- 作用域:顾名思义,就是能够起作用的范围是哪些区域。本地变量的作用域也是大括号里面;
- 每个变量也都会有自己的生存期,在被创建的时候诞生,在作用域消失的时候就没了;
- 进入到每个作用域之前,里面的变量是不会被创建的。
参数列表、方法里新定义的都是本地变量;
不同作用域的本地变量,可以重名,不冲突。但是一个大括号里面的下一级出现同名就会冲突。
本地变量不会被自动初始化,但是在使用前必须被初始化。
可以Debug代码感受变量的变化以感受以上特性。
java
import java.util.Scanner;
public class demo_24 {
public static void main(String[] args) {
sum(20000, 3000);
}
/**
* 计算start-end之间所有整数的和
*
* @param start 开始
* @param end 结束
*/
public static void sum(int start, int end) {
//防止前者比后者大,交换顺序
if (start > end) {
int temp = start;
start = end;
end = temp;
}
int b = 0;
for (int j = start; j <= end; j++) {
b += j;
}
System.out.println(start + "-" + end + "直接所有整数的和等于" + b);
}
}
7.0类的封装
一辆公交车有哪些属性或者功能呢?
属性:长宽高、轮胎数量、座位数量、是否有空调、是不是新能源、广告商、票价、车龄...
功能:开关门、投币、开车、停车、接广告、加油、按喇叭、信息播报...
面向对象:一种编程思维
用合适的变量类型来表达一类对象的某个属性
定义方法,来处理对象的某个功能
把某一类对象使用class包装、抽象成代码
java
public class Bus {
int width;
int height;
int seating;
double price;
double balance;
String number;
boolean isOpen;
boolean isRuning;
public void stop() {
isRuning = false;
System.out.println("已停车!");
}
public void start() {
isRuning = true;
System.out.println("正在行驶!");
}
public void regist(String no) {
number = no;
System.out.println("注册成功!您的车牌号是" + number);
}
public void pay(double money) {
if (money < price) {
System.out.println("钱没给够!");
} else {
balance += money;
seating -= 1;
System.out.println("欢迎乘坐!");
}
}
public void openDoor() {
isOpen = true;
System.out.println("开门!");
}
public void closeDoor() {
isOpen = false;
System.out.println("关门!");
}
}
7.1类的使用
万物皆对象
在对象b处打断点Debug时,出现的@xxxx:在运行内存中的某个地址(门牌号)
java
public class Bus {
int width;
int height;
int seating;
double price;
double balance;
String number;
boolean isOpen;
boolean isRuning;
public void stop() {
isRuning = false;
System.out.println("已停车!");
}
public void start() {
isRuning = true; //this的用法:this.isRuning = true; (允许省略this)
System.out.println("正在行驶!");
}
public void regist(String no) {
number = no;
System.out.println("注册成功!您的车牌号是" + number);
}
public void pay(double money) {
if (money < price) {
System.out.println("钱没给够!");
} else {
balance += money;
seating -= 1;
System.out.println("欢迎乘坐!");
}
}
public void openDoor() {
isOpen = true;
System.out.println("开门!");
}
public void closeDoor() {
isOpen = false;
System.out.println("关门!");
}
public static void main(String[] args) {
Bus b = new Bus();
b.regist("粤B000000");
b.start();
b.stop();
b.openDoor();
b.pay(0);
b.start();
Bus b1 = new Bus();
b1.regist("粤B000001");
b1.start();
b1.stop();
b1.openDoor();
b1.pay(0);
b1.start();
Bus b2 = new Bus();
b2.regist("粤B000002");
b2.start();
b2.stop();
b2.openDoor();
b2.pay(0);
b2.start();
}
}
- 使用new关键字,可以把类实例化(创建)成一个对象,并交给变量b来持有;
- 通过b.,可以访问这个对象里的方法或成员变量
7.2成员变量
new:初始化、实例化、创建、新建...
成员变量:直接写在类的大括号下的变量
在对象b处打断点Debug:
变量b持有一个bus对象,这个对象的内存地址是@719
找一个方法Step Into进去,可以看到 this ={Bus@719} :代表着代码运行调试到Bus类的内部时,所创建的对象对应着@719。this 指向当前操作的这个对象。
this的一些用法:
java
public void stop() {
this.isRuning = false; //(允许省略this.)
System.out.println("正在行驶!");
this.start()
}
java
public void regist(String number) {
this.number = number;
System.out.println("注册成功!您的车牌号是" + number);
}
成员变量:定义在类内部但在方法外部的变量,通常用于存储与对象状态相关的信息。成员变量可以是任何数据类型,包括基本数据类型(比如int、double、char等)或引用类型(比如其他类的对象、数组等)
成员变量会有一个默认的值;(因为数据类型会有默认值,回顾一下)
每个对象内的成员变量是相互独立的:
- 所以需要使用对应的对象名(b.)来访问。
- 如果是在类里面的方法访问成员变量,可以用this关键字,或者直接用变量名访问。
- 被static修饰过的方法,叫静态方法,例如main方法,静态方法不属于任何一份实例对象。所以,不能在静态方法里面直接访问成员变量。
7.3构造方法
把bus类里面的main方法,移到新的文件一个新的类里
java
public class Demo_25 {
public static void main(String[] args) {
Bus b = new Bus();
b.regist("粤B000000");
b.start();
b.stop();
b.openDoor();
b.pay(0);
b.start();
Bus b1 = new Bus();
b1.regist("粤B000001");
b1.start();
b1.stop();
b1.openDoor();
b1.pay(0);
b1.start();
Bus b2 = new Bus();
b2.regist("粤B000002");
b2.start();
b2.stop();
b2.openDoor();
b2.pay(0);
b2.start();
}
}
在Bus b = new Bus();的下一行打断点Debug,新创建的b这个bus对象,可以看到每一个成员变量都给了一个默认的值,根据成员变量的类型给一个默认的值。(原理:因为数据类型会有默认值,回顾一下)
String字符串的默认值为null:空的、不存在的,表示空的内容或对象
也可以在bus类里先直接给成员变量赋值
java
double price = 2;
也有其他写法:
java
public class Bus {
//...
double price = setPrice();
public double setPrice(){
return 2.0;
}
在Bus b = new Bus()这行打断点Debug,利用Step Into以及Step Over观察
还可以用构造方法:名字必须与类名完全相同,并且不能有返回类型
java
public class Bus {
int width;
int height;
int seating;
double price;
double balance;
String number;
boolean isOpen;
boolean isRuning;
public Bus() {
price = 2;
}
同样在Bus b = new Bus()这行打断点Debug,利用Step Into以及Step Over观察
还以使用带参数的构造方法
java
public class Bus {
int width;
int height;
int seating;
double price;
double balance;
String number;
boolean isOpen;
boolean isRuning;
public Bus(String n,double p){
price = p;
regist(n);
} //构造函数
public void regist(String no) {
number = no;
System.out.println("注册成功!您的车牌号是" + number);
}
java
public class Demo_25 {
public static void main(String[] args) {
Bus b = new Bus("粤B000000",2); //传参
b.start();
b.stop();
b.openDoor();
b.pay(0);
b.start();
Bus b1 = new Bus("粤B042400",1);
b1.start();
b1.stop();
b1.openDoor();
b1.pay(0);
b1.start();
Bus b2 = new Bus("粤B24fd0",5);
b2.start();
b2.stop();
b2.openDoor();
b2.pay(0);
b2.start();
}
}
有时候new对象时不想传参,可以多写一个构造方法解决
java
public class Bus {
double price;
//...此处省略代码
public Bus() {
} //多个构造方法
public Bus(int seating, double price, String number) {
this.seating = seating;
this.price = price;
this.number=number; //加this.才能指定是Bus类里面的成员变量
}
public Bus(String n,double p){
price = p;
regist(n);
}
//...此处省略代码
这样就不会报错
java
public class Demo_25 {
public static void main(String[] args) {
Bus b = new Bus("粤B000000",2);
b.regist("粤B000000");
b.start();
b.stop();
b.openDoor();
b.pay(0);
b.start();
//...
Bus b3 = new Bus(); //不会报错
Bus b4 = new Bus(12, 2, "粤B66666");
}
}
递归调用:你调我,我调你...
java
public Bus() {
this("粤B123446",2);
// this("粤B12344",2); 只能调一次,不能多次调用,报错
}
public Bus(String n,double p){
// this(); 这里不能再递归调用回去,报错
price = p;
regist(n);
}
构造方法(也称为构造函数)是一种特殊类型的方法,用于初始化新创建的对象。构造方法的名字必须与类名完全相同,并且不能有返回类型(甚至不能是void)。当你创建一个新对象时,Java会自动调用这个类的构造方法:
- 每个类都可以有多个构造方法,它们有不同的参数列表(称为构造方法的重载);
- 名称需要和类名完全相同(包括大小写也要一致);
- 构造方法没有返回类型,即使没有写出void声明;
- 构造方法可以有参数,也可以没有参数;
- 构造方法不能被直接调用,必须通过new关键字创建对象的时候自动调用。
7.4包的使用
新建一个工程New Project,名叫PackageProject,不勾选Add sample code
Src-New-Package,名叫yansu
在yansu的下面再创建一个名为Main的Java文件
java
package yansu;
public class Main {
public static void main(String[] args) {
}
}
分包管理代码文件:类似于文件夹,提高可读性、可维护性。
包下面再继续创建包,会出现两个包显示在一起的情况,
选择目录层级右上角的三个点-Appearance,去掉Compact Middle Packages即可解决。
包还可以帮助控制类的访问权限。
- 不同的包,允许有重名的文件;
- 包可以有多个层级;
7.5权限修饰符
对人类做一个封装,把每一个人都有一些属性姓名、年龄等,然后还定义一些功能方法比如设置名字、获取年龄等,通过一些具体的动作来访问这里面的数据,这种行为在Java当中被称为封装。
java
package yansu.hello.world;
public class People {
//姓名
private String name;
//年龄
private int age;
//性别
private int gender;
public People(String name, int age, int gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public People(String name) {
this.name = name;
}
public People() {
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
}
java
package yansu.hello.world;
public class Test2 {
public static void main(String[] args) {
People people= new People();
people.setName("严肃");
// people.age = 27; //错误: age 在 yansu.hello.world.People 中是 private 访问控制
people.setAge(30);
}
}
- public和private都是Java里面的权限修饰符
- 权限修饰符只能给成员变量以及成员方法所使用
- private:只能用于当前类
- public:工程中所有地方都可直接访问
- 不写权限修饰符(默认的):同一个包下可访问
- protected:一个包下面可以访问,不同包下面的子类可访问
文件名People与类名People一致,需要要public来修饰这个类
类名与文件名不一致就不能用public来修饰
一个文件下面就只能有一个public修饰的类,其他类可以不用public修饰就不会报错
7.6静态变量
java
package yansu.hello.test333;
/**
* x学生信息类
*/
public class Student {
//姓名
private String name;
//年龄
private int age;
//学号
private static int number; //静态变量
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 int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
//静态方法
public static void main(String[] args) {
// name ="哈哈"; //错误,name是属于某一个对象的,不是属于这个类的。 非静态字段"name"不能从静态上下文中引用
number = 444; //可以,应为都是属于类本身的,都是被static修饰的,不是属于某个对象
// setName("小花"); //不可以,除非被static修饰 public static void setName(String name){}
Student xiaoming = new Student();
xiaoming.setName("小明");
xiaoming.setNumber(10);
Student xiaohong = new Student();
xiaohong.setName("小红");
System.out.println("学生" + xiaoming.getName() + "的学号是" + xiaoming.getNumber());
System.out.println("学生" + xiaohong.getName() + "的学号是" + xiaohong.getNumber());
xiaohong.number = 19; //使用某一个对象去访问静态变量
System.out.println("学生" + xiaoming.getName() + "的学号是" + xiaoming.getNumber());
System.out.println("学生" + xiaohong.getName() + "的学号是" + xiaohong.getNumber());
Student.number = 20; //使用类名去访问
System.out.println("学生" + xiaoming.getName() + "的学号是" + xiaoming.getNumber());
System.out.println("学生" + xiaohong.getName() + "的学号是" + xiaohong.getNumber());
}
}
static:用于描述类级别的一个变量
使用某一个对象去访问静态变量
使用类名去访问
7.7静态方法
static修饰过的成员变量或成员方法,就叫静态变量(类变量)、静态方法(类方法),因为它是属于类本身的,而不是属于任何一个被实例的对象;
- 想要访问静态变量、静态方法,可以通过类名直接访问,也可以通过某一个被实例的对象访问;
- 静态变量、静态方法他是所有实例对象共同的变量(方法);
- 静态变量只会在类被加载的时候被初始化一次;
- 静态方法里不能直接访问成员变量、成员方法,因为成员变量和方法属于具体的对象。
8.0ArrayList
容器
用来存放一串数据的东西可以被称为容器,java中除了数组,还有其他对象类型的容器,ArrayList、HashSet。可以根据应用场景来选择合适的容器。
File-New-ListProject
src-New-Package(com)--New-Package(ys)
java
package com.ys;
public class Main {
public static void main(String[] args) {
//定义一个长度为3的数组,并且存放3个学生的信息
String[] stu =new String[3];
stu[0] = "小花";
stu[1] = "小诏";
stu[2] = "小严";
// stu[3] = "小李"; //数组下标越界
// stu[4] = "小刘";
System.out.println("第一位是"+stu[0]);
System.out.println("第二位是"+stu[1]);
System.out.println("第三位是"+stu[2]);
}
}
复习:数组的长度是固定的
ArrayList也需要指定存放数据的类型
package com.ys;
import java.util.ArrayList;
public class Main {
//定义了一个ArrayList
ArrayList<String> student = new ArrayList<String>();
student.add("小花");
student.add("小诏"); //添加一个数据
student.add("小严");
student.add("小李");
student.add("小刘");
student.add(0, "小张"); //把小张插到第一位。这个方法会让原有数据往后移
System.out.println("第一位是" + student.get(0));
System.out.println("第一位是" + student.get(1));
System.out.println("第一位是" + student.get(2));
System.out.println("第一位是" + student.get(3));
System.out.println("第一位是" + student.get(4));
System.out.println("student的长度是" + student.size()); //获取student的长度
String string = student.remove(5); //移除指定下标的内容,并返回这条数据
System.out.println("移除的数据是" + string);
}
}
在ArrayList student = new ArrayList();打断点Debug观察
除了.size()获取长度,其他用法可去Java Api文档搜索ArrayList查看,java.util.ArrayList。还可以直接在IDEA代码上Ctrl+点击ArrayList查看源码,或鼠标放到方法上查看用法。
培养阅读文档的好习惯。
容器的遍历
java
package com.ys;
import java.util.ArrayList;
public class Main {
//定义了一个ArrayList
ArrayList<String> student = new ArrayList<String>();
student.add("小花");
student.add("小诏"); //添加一个数据
student.add("小严");
student.add("小李");
student.add("小刘");
student.add(0, "小张"); //把小张插到第一位。这个方法会让原有数据往后移
System.out.println("第一位是" + student.get(0));
System.out.println("第一位是" + student.get(1));
System.out.println("第一位是" + student.get(2));
System.out.println("第一位是" + student.get(3));
System.out.println("第一位是" + student.get(4));
System.out.println("student的长度是" + student.size()); //获取student的长度
String string = student.remove(5); //移除指定下标的内容,并返回这条数据
System.out.println("移除的数据是" + string);
System.out.println("===========================");
//第一种:for循环,如果需要处理位置索引,用这种比较合适
for (int i = 0; i < student.size(); i++) {
System.out.println("第" + (i + 1) + "位是" + student.get(i));
}
System.out.println("===========================");
//第二种:for each循环 单纯的循环遍历
for (String str : student) {
System.out.println(str);
}
//第三种:只查看里面的数据,可以直接打印容器对象
System.out.println(student);
//第四种:还可以使用迭代器对象,list、set都可以获取到iterator
Iterator<String> iterator1 = student.iterator(); /使用快捷生成变量名:Ctrl+Alt+v
while (iterator1.hasNext()){
String next1 = iterator1.next(); /使用快捷生成变量名:Ctrl+Alt+v
System.out.println(next1);
}
}
}
8.1HashSet
一个HashSet容器中不允许有两个相同元素,最新的元素会替换旧元素;
HashSet没有小标索引这一说法,因为他对位置没有概念,只知道自己的内部存放了哪些数据。
iterator:迭代器
java
package com.ys;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
//定义一个HashSet
HashSet<String> set = new HashSet<String>();
//往里面存数据
set.add("小花");
set.add("小李");
set.add("小花");
System.out.println(set);
System.out.println("===========================");
//将HashSet转换为数组,在通过数组的形式处理里面的元素
String[] strs = set.toArray(new String[0]);
for (int i = 0; i < strs.length; i++) {
System.out.println(strs[i]);
}
System.out.println("===========================");
//获取HashSet的迭代器,使用迭代器对象来循环遍历处理里面的元素
Iterator<String> iterator = set.iterator(); //使用快捷生成变量名:Ctrl+Alt+v
//hasNext方法会返回一个布尔值,用来判断是否还有下一个元素可以被遍历,如果有则返回true,如果已经到了末尾,则返回false
while (iterator.hasNext()) {
String next = iterator.next(); //使用next获取下一个元素
System.out.println(next);
}
}
}
8.2在类中构造tostring方法
1.定义学生信息类,存放学生的属性和操作
2.通过学号来查询对应学生的信息
小技巧- 快捷生成构造方法:Alt + insert,部分电脑需要Fn + Alt + insert,选择Constructor,再选择创建构造方法需要几个参数,按住Ctrl可多选
java
package com.ys;
import java.util.Scanner;
/**
*
* 学生信息类
*/
public class Student {
int number;
int age;
String name;
//1表示男生 0表示女生
int gender;
//boolean male;
// 快捷生成构造方法:Alt + insert
public Student(int number, int age, String name, int gender) {
this.number = number;
this.age = age;
this.gender = gender;
this.name = name;
}
public Student(int number) {
this.number = number;
}
public Student() {
}
public static void main(String[] args) {
Student xiaozhao = new Student(1, 18, "小赵", 1);
Student xiaowang = new Student(2, 19, "小王", 1);
Student xiaoyan = new Student(3, 20, "小严", 1);
Student xiaomei = new Student(4, 21, "小梅", 0);
System.out.println("请输入学号");
Scanner scanner = new Scanner(System.in);
int i = scanner.nextInt();
if (i == xiaozhao.number) {
System.out.println("您要查询的学生信息是:" + xiaozhao);
} else if (i == xiaowang.number) {
System.out.println("您要查询的学生信息是:" + xiaowang);
}
}
}
java
请输入学号
1
您要查询的学生信息是:com.ls.Student@7530d0a
扩展:console里输出的@7530d0a才是真实的内存地址,而debug模式里看到的@955实际上只是简化版,只是一个标识。
快捷键:Alt+Insert,选择toString
java
package com.ys;
import java.util.Scanner;
/**
*
* 学生信息类
*/
public class Student {
int number;
int age;
String name;
//1表示男生 0表示女生
int gender;
//boolean male;
// 快捷生成构造方法:Alt + insert,选择Constructor
public Student(int number, int age, String name, int gender) {
this.number = number;
this.age = age;
this.gender = gender;
this.name = name;
}
public Student(int number) {
this.number = number;
}
public Student() {
}
// 快捷生成构造方法:Alt + insert,选择toString
@Override
public String toString() {
return "Student{" +
"number=" + number +
", age=" + age +
", name='" + name + '\'' +
", gender=" + gender +
'}';
}
public static void main(String[] args) {
Student xiaozhao = new Student(1, 18, "小赵", 1);
Student xiaowang = new Student(2, 19, "小王", 1);
Student xiaoyan = new Student(3, 20, "小严", 1);
Student xiaomei = new Student(4, 21, "小梅", 0);
System.out.println("请输入学号");
Scanner scanner = new Scanner(System.in);
int i = scanner.nextInt();
if (i == xiaozhao.number) {
System.out.println("您要查询的学生信息是:" + xiaozhao);
} else if (i == xiaowang.number) {
System.out.println("您要查询的学生信息是:" + xiaowang);
}
}
}
java
请输入学号
1
您要查询的学生信息是:Student{number=1, age=18, name='小赵', gender=1}
8.3HashMap
和HashSet类似,HashMap只允许存在1个唯一的Key,新添加的Key如果已存在,会把旧的元素替换。
java
package com.ys;
//import java.security.Key;
//import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;
import java.util.Set;
/**
*
* 学生信息类
*/
public class Student {
int number;
int age;
String name;
//1表示男生 0表示女生
int gender;
//boolean male;
// 快捷生成构造方法:Alt + insert,选择Constructor
public Student(int number, int age, String name, int gender) {
this.number = number;
this.age = age;
this.gender = gender;
this.name = name;
}
public Student(int number) {
this.number = number;
}
public Student() {
}
// 快捷生成构造方法:Alt + insert,选择toString
@Override
public String toString() {
return "Student{" +
"number=" + number +
", age=" + age +
", name='" + name + '\'' +
", gender=" + gender +
'}';
}
//也可以自定义
public String show(){
return "姓名:" + name + ",学号:" + number + ",年龄:" + age + ",性别是" + gender;
}
public static void main(String[] args) {
Student xiaozhao = new Student(1, 18, "小赵", 1);
Student xiaowang = new Student(2, 19, "小王", 1);
Student xiaoyan = new Student(3, 20, "小严", 1);
Student xiaomei = new Student(4, 21, "小梅", 0);
System.out.println("请输入学号");
Scanner scanner = new Scanner(System.in);
int i = scanner.nextInt();
//定义一个HashMap
//k:key 查询关键值 v:value具体的数据
HashMap<Integer, Student> hashMap = new HashMap<>();
//往里面添加数据,第一个参数指定key值,第二个参数指定数据
hashMap.put(xiaozhao.number, xiaozhao);
hashMap.put(xiaowang.number, xiaowang);
hashMap.put(xiaoyan.number, xiaoyan);
hashMap.put(xiaomei.number, xiaomei);
//通过key查询数据,返回对应的数据类型
Student student = hashMap.get(i);
System.out.println("您要查询的学生信息是:" + student);
//获取hashmap的长度
int size = hashMap.size();
System.out.println("这个班级总共有" + size + "人");
//获取hashmap中所有key的一个集合
Set<Integer> keySet = hashMap.keySet();
System.out.println("KeySet:" + keySet);
//通过key的集合遍历hashmap
for (Integer key : keySet) {
Student student1 = hashMap.get(key);
System.out.println("您要查询的学生信息是:" + student1);
}
// if来判断输出
// if (i == xiaozhao.number) {
// System.out.println("您要查询的学生信息是:" + xiaozhao);
// } else if (i == xiaowang.number) {
// System.out.println("您要查询的学生信息是:" + xiaowang);
// }
// //数组列表容器+for each来循环判断输出
// ArrayList<Student> students = new ArrayList<>();
// students.add(xiaozhao);
// students.add(xiaowang);
// students.add(xiaoyan);
// students.add(xiaomei);
//
// //增强型for循环(也称for-each循环)的写法,用于遍历数组或集合中的元素
// for (Student student : students) {
// if (student.number == i) {
// System.out.println("您要查询的学生信息是:" + student);
// break;
// }
// }
}
}
8.4通过一个类来管理多个对象
java
package com.ys;
import java.util.ArrayList;
public class School {
ArrayList<Student> students =new ArrayList<>();
public void add(Student student){
students.add(student);
}
public void query(){
for(Student student:students){
System.out.println("学生信息查询:"+student);
}
}
public static void main(String[] args) {
School school = new School();
Student xiaozhao = new Student(1, 18, "小赵", 1);
Student xiaowang = new Student(2, 19, "小王", 1);
Student xiaoyan = new Student(3, 20, "小严", 1);
Student xiaomei = new Student(4, 21, "小梅", 0);
school.add(xiaozhao);
school.add(xiaowang);
school.add(xiaoyan);
school.add(xiaomei);
school.query();
}
}
java
package com.ys;
public class Teacher {
int age;
int name;
int gender; //1代表男 0代表女
int experience; //教龄
public Teacher(int age, int gender, int experience, int name) {
this.age = age;
this.gender = gender;
this.experience = experience;
this.name = name;
}
public String show() {
return "姓名:" + name + ",教龄:" + experience + ",年龄:" + age + ",性别是" + gender;
}
}
java
package com.ys;
import java.util.ArrayList;
public class School {
ArrayList<Student> students = new ArrayList<>();
ArrayList<Teacher> teachers = new ArrayList<>();
public void add(Student student) {
students.add(student);
}
public void add(Teacher teacher) {
teachers.add(teacher);
}
public void query() {
for (Student student : students) {
System.out.println("学生信息查询:" + student.show());
}
for (Teacher teacher : teachers) {
System.out.println("老师信息查询:" + teacher.show());
}
}
public static void main(String[] args) {
School school = new School();
Student xiaozhao = new Student(1, 18, "小赵", 1);
Student xiaowang = new Student(2, 19, "小王", 1);
Student xiaoyan = new Student(3, 20, "小严", 1);
Student xiaomei = new Student(4, 21, "小梅", 0);
school.add(xiaozhao);
school.add(xiaowang);
school.add(xiaoyan);
school.add(xiaomei);
Teacher xiaohua =new Teacher(25,1,3,"小华");
Teacher xiaoyi =new Teacher(35,0,13,"小亿");
Teacher xiaolei =new Teacher(45,1,23,"小雷");
school.add(xiaohua);
school.add(xiaoyi);
school.add(xiaolei);
school.query();
}
}
9.0使用extends继承
小技巧-按住Ctrl+单击:跳转到指定方法
java
package com.ys;
public class People {
public String show() {
return null;
}
}
java
package com.ys;
/**
*
* 学生信息类
*/
public class Student extends People {
//关键修改,继承People类
}
java
package com.ys;
public class Teacher extends People{
//关键修改,继承People类
}
java
package com.ys;
import java.util.ArrayList;
public class School {
ArrayList<People> peoples=new ArrayList<>();
public void add(People people){
peoples.add(people);
}
public void query(){
for (People people:peoples){
System.out.println("信息查询:" + people.show());
}
}
public static void main(String[] args) {
School school = new School();
Student xiaozhao = new Student(1, 18, "小赵", 1);
Student xiaowang = new Student(2, 19, "小王", 1);
Student xiaoyan = new Student(3, 20, "小严", 1);
Student xiaomei = new Student(4, 21, "小梅", 0);
school.add(xiaozhao);
school.add(xiaowang);
school.add(xiaoyan);
school.add(xiaomei);
Teacher xiaohua =new Teacher(25,1,3,"小华");
Teacher xiaoyi =new Teacher(35,0,13,"小亿");
Teacher xiaolei =new Teacher(45,1,23,"小雷");
school.add(xiaohua);
school.add(xiaoyi);
school.add(xiaolei);
school.query();
}
}
9.1extends与super
父类:被继承的类
子类:使用extends的类
super:超类、父类,调用父类的方法
使用this和super调用构造方法,需要写在第一行
属性在哪里定义就在哪里做初始化
private和protected建议优先使用private
java
package com.ys;
public class People {
int age;
private String name; //private
int gender; //1表示男 0表示女
public People(int age, String name, int gender) {
this.age = age;
this.name= name ;
this.gender = gender;
}
public String getName(){
return this.name;
}
public String show() {
return null;
}
}
java
package com.ys;
import java.util.HashMap;
import java.util.Scanner;
import java.util.Set;
/**
*
* 学生信息类
*/
public class Student extends People {
int number;
// 快捷生成构造方法:Alt + insert,选择Constructor
public Student(int number, int age, String name, int gender) {
super(age,name,gender); //super
this.number = number;
}
// 快捷生成构造方法:Alt + insert,选择toString
@Override
public String toString() {
return "Student{" +
"number=" + number +
", age=" + age +
", name='" + getName() + '\'' +
", gender=" + gender +
'}';
}
public String show(){
return "姓名:" + getName() + ",学号:" + number + ",年龄:" + age + ",性别是" + gender; //使用getName()
}
//......
}
}
java
package com.ys;
public class Teacher extends People{
int experience; //教龄
public Teacher(int age, int gender, int experience, String name) {
super(age,name,gender); //super
this.experience = experience;
}
public String show() {
return "姓名:" + getName() + ",教龄:" + experience + ",年龄:" + age + ",性别是" + gender;
}
}
9.2向上转型和向下转型
子类对象可被当成父类对象来使用,叫做向上转型
对象类型,都有这种特性
java
People P1 = new Student(1, 18, "小赵", 1); //向上转型
System.out.println(P1.show());
子类变量无法接收管理父类对象
把父类的变量强制转换成子类变量,称为向下转型
java
People P1 =new Student(1, 18, "小赵", 1);
System.out.println(P1.show());
Student stu_1 = (Student) P1; //把父类的变量强制转换成子类变量,称为向下转型
int number = stu_1.number;
System.out.println("stu_1 number = "+number);
9.3多态
Student和Teacher里都有一个show(),可以通过父类自动的访问到子类的方法,这个特性在Java当中叫多态。
java
public String show() {
return null;
}
public class Student extends People {
//子类重写父类方法
@Override
public String show(){
return "姓名:" + getName() + ",学号:" + number + ",年龄:" + age + ",性别是" + gender;
}
}
//这里实际调用的是子类show的方法
public void query(){
for (People people:peoples){
System.out.println("信息查询:" + people.show());
}
}
实现多态有三个必要条件:
- 必须要有继承的关系
- 父类的变量持有了子类的对象
- 在子类中重写父类的方法
@override:注解,标注,重写
还有其他注解,例如@Deprecated 代表弃用
重写
要成功重写父类中的方法,需要满足以下条件:
- 方法名称、参数列表必须相同:子类重写父类的方法时,方法名称和参数列表(包括参数类型、参数顺序和参数数量)必须与父类中被重写的方法完全相同。
- 访问权限不能更低:子类重写父类的方法时,访问权限(public、protected、默认(无修饰符)或private)不能低于父类中被重写的方法的访问权限。例如,如果父类中的方法是protected的,那么子类中的重写方法不能是private的。
- 返回类型要兼容:子类重写父类的方法时,返回类型必须与父类中被重写的方法的返回类型相同或是其子类。在Java 5及以后的版本中,如果父类中的方法返回类型是泛型,子类重写的方法可以返回该泛型的子类。
- 子类重写的方法不能抛出比父类方法更广泛的检查型异常:检查型异常是编译器在编译时可以检查出来的异常。子类重写父类的方法时,可以抛出与父类方法相同的检查型异常,或者是父类方法抛出的检查型异常的子类。但是,子类重写的方法不能抛出新的检查型异常,除非这个异常是运行时异常(RuntimeException)或其子类。
- 父类中的方法不能为final:如果父类中的方法被声明为final,那么子类不能重写该方法。
- 父类中的方法不能为static:如果父类中的方法是静态的(static),那么子类不能重写该方法。但是,子类可以定义与父类静态方法相同名称和参数列表的静态方法,这被称为隐藏(Hiding),而不是重写。
9.4判断两个对象是否相等
Object类
Object是所有对象类型的父类,比如自己定义的Student、People,或者JDK提供的String、Scanner等等。
判断两个对象是否相等
不可以直接使用equals或==来判断,而要重写equals,自己定义判断条件。
java
//在Student类中重写equals
@Override
public boolean equals(Object obj) {
//判断obj是不是Student类型的对象
if(obj instanceof Student){
Student stu = (Student) obj;
return number == stu.number && getName().equals(stu.getName());
}else {
return false;
}
}
java
package com.ys;
public class Test1 {
public static void main(String[] args) {
Student stu1 = new Student(10, 18, "小严", 1);
Student stu2 = new Student(10, 18, "小严", 1);
People p1 = stu1; //实际开发过程中这种情况比较少,用两个变量指向同一个东西
// // 这种情况用== 和默认的eqeals都不可取 因为内存地址不一样
// if (stu1.equals(stu2)){
// System.out.println(stu1 == p1);
//使用重写后的equals判断两个条件是否相等
if (stu1.equals(stu2)) {
System.out.println("stu1等于stu2!");
}else {
System.out.println("stu1不等于stu2!");
}
}
}
9.5抽象
抽象类
抽象类定义一个群体(子类)的基础框架结构
小技巧:上方右键关闭所有tabs,Close All Tabs
abstract:抽象
使用抽象类需要注意的
- 可以让子类强制实现抽象方法
- 抽象类中,可以同时定义抽象方法和普通方法
- 子类必须实现父类中所有的抽象方法,除非子类本身也是抽象类
- 抽象类不能被实例化
- 如果类里面包含抽象方法,那个这个类也必须是抽象的
为什么要使用抽象
- 代码重用:通过继承抽象类,子类可以重用父类中定义的属性和方法,减少了代码的重复;
约束和强制:抽象类可以定义一组子类必须实现的方法,从而强制子类遵循约定实现特定的方法; - 扩展性:抽象类允许在保持已有代码稳定的同时,通过添加新的子类来扩展系统的功能。
- 多态性:子类继承了父类,所以可以使用父类类型的变量持有子类对象,实现运行时多态。
- 模板方法模式:抽象类可以作为一种模板,提供算法的框架和一些默认实现,让子类在不改变算法结构的情况下,重新定义某些步骤的具体实现;
- 如果是一些简单的类,可以不需要抽象。
java
package com.ys.abs;
public abstract class Animal {
public abstract void eat(); //抽象方法
public void makeSound(){
// System.out.println("我是一个动物,我会哇哇叫");
} //普通方法
}
java
package com.ys.abs;
//继承Animal抽象类
public class Dog extends Animal{
@Override
public void makeSound() {
System.out.println("我是一只狗子,我会汪汪汪");
}
@Override
public void eat() {
System.out.println("我是一只狗子,我正在吃骨头");
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.makeSound();
dog.eat();
}
}
java
package com.ys.abs;
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("我是一只猫,我会喵喵喵");
}
@Override
public void eat() {
System.out.println("我是一只猫,我在吃鱼");
}
public static void main(String[] args) {
Cat cat = new Cat();
cat.makeSound();
cat.eat();
}
}
java
package com.ys.abs;
public class AnimalTest {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat();
dog.makeSound();
Cat cat = new Cat();
cat.eat();
cat.makeSound();
// Animal animal = new Animal(); //报错, Animal是抽象的; 无法实例化
}
}
9.6接口
接口是一种特殊的抽象类,接口中的所有成员方法都是抽象方法,所有成员变量都是public static final:
- 使用implements关键字可以实现接口,但必须强制重写所有方法;
- 可以不需要使用public abstract修饰方法,并且权限只能是public;
- 可以不需要使用public static final修饰成员变量;
- 接口不能直接被实例化,也没有构造方法;
- 接口不可以实现另一个接口,但是接口可以支持多继承的关系;
- 在Java8以后可以使用default或者staic在接口中实现方法体,Java9后可以使用private。
在创建Java类时就可以选Interface
接口:使用interface定义
接口中的所有成员方法都是抽象的方法
接口是不能被直接实例化
implements:实现
JDK8以后,放宽了接口的限制
接口不能实现其他接口
接口可以有继承关系
接口的多重继承,可让代码更具有扩展性
java
package com.ys.inter;
//定义一个接口
public interface Animal {
public static final int a = 1; //public static final不用写
void sleep();
void eat();
//Java8以后可以用
default void game(){
test();
System.out.println("来打游戏");
}
//Java8以后可以用
static void run(){
System.out.println("动物是可以跑的");
}
//Java9以后可以用,只能在接口Animal的内部使用
private void test(){
}
}
java
package com.ys.inter;
//使用一个接口
public class Dog implements Animal{
//快捷键Alt+Enter Implement methods 快速写出接口的实现方法
@Override
public void sleep() {
System.out.println("狗子在睡觉");
}
@Override
public void eat() {
System.out.println("狗子在啃骨头");
}
}
java
package com.ys.inter;
public class AnimalTest {
public static void main(String[] args) {
// Animal animal = new Animal(); //报错,接口不能被实例化
int a = Animal.a;
Dog dog = new Dog();
dog.sleep();
dog.eat();
dog.game();
Animal.run();
}
}
java
package com.ys.inter;
//错误,接口不能实现其他接口
//public interface ATest implements Animal {
//}
public interface ATest extends Animal {
void onTest();
void onClick();
}
java
package com.ys.inter;
public class BTest implements ATest{
@Override
public void onTest() {
}
@Override
public void onClick() {
}
@Override
public void sleep() {
}
@Override
public void eat() {
}
}
9.7内部类
成员内部类
- 和外部类的成员变量、成员方法处于同一个级别上;
- 创建成员内部内部类的对象时,需要借助外部类的对象;
静态内部类
- 静态内部类使用static关键字定义,它属于外部类本身,而不属于外部类的某个实例;
- 创建静态内部类的对象时,不需要外部类的对象作为参数;
局部内部类
- 局部内部类定义在外部类的方法中;
- 局部内部类的作用域仅限于该方法内;
- 局部内部类不可以有访问修饰符和static修饰符。
匿名内部类
- 匿名内部类是没有名字的内部类;
- 匿名内部类通常用于实现接口或继承其它类,并立即创建该类的对象;
- 通常用于线程、事件监听器等场景。
内部类的常见场景
- 内部类可以将一组紧密相关的类组织在一起,形成一个逻辑上的单元,从而提高了代码的可读性和可维护性;
- 内部类可以隐藏不需要被外部类直接访问的实现细节。通过把内部类设为私有(private),可以确保它不会被外部类直接访问,只能通过外部类提供的方法来间接访问;
- 使用内部类可以简化代码结构。
java
package com.ys.inside;
public class Restaurant {
private String name;
private String address;
public void test(){
MenuItem rice = new MenuItem("炒饭", 12);
String riceName = rice.name;
}
public void ShowSpicy(String spicy){
//局部内部类,作用域仅限该方法内
class LocalClass{
public void display(){
System.out.println("辣度是:" + spicy);
}
}
LocalClass localClass = new LocalClass();
localClass.display();
}
public void createMenuItem(String name, double price){
MenuItem menuItem = new MenuItem(name, price);
}
//静态内部类
public static class MenuItem{
private String name;
private double price;
public MenuItem(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
}
java
package com.ys.inside;
public class RestaurantMain {
public static void main(String[] args) {
//需要static修饰MeunItem才能访问 需要内部类是public public static class MenuItem{} 称为静态内部类
Restaurant.MenuItem noodle = new Restaurant.MenuItem("干拌面", 17);
//通过一个Restaurant实例直接访问内部类 需要内部类是public public class MenuItem{}
Restaurant restaurant = new Restaurant();
// Restaurant.MenuItem noodle = restaurant.new MenuItem("干拌面", 17);
//
//访问一个private的成员内部类 private class MenuItem{}
restaurant.createMenuItem("干拌面",17);
restaurant.ShowSpicy("特辣");
}
}
9.8泛型
使用泛型,可以编写灵活的、可重用的组件(类、接口、方法),这些组件可以处理多种类型,而不必为每种数据类型编写单独的代码。
java
package com.ys.generic;
public class Box {
private int data;
//快捷键:Alt + Insert
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
}
java
package com.ys.generic;
public class GenericMain {
public static void main(String[] args) {
//快捷键Ctrl + Alt +v
Box box = new Box();
box.setData(9);
int data = box.getData();
System.out.println("data = " + data);
}
}
使用泛型:
type的首字母,使用其他字母也可以,比如E、K等
java
package com.ys.generic;
public class Box<T> {
private T data;
//快捷键:Alt + Insert
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
java
package com.ys.generic;
public class GenericMain {
public static void main(String[] args) {
Box<Integer> boxInteger = new Box<>();
boxInteger.setData(9);
System.out.println("这是一个int类型,值是:" + boxInteger.getData());
Box<String> boxString = new Box<>();
boxString.setData("哈哈哈");
System.out.println("这是一个String类型,值是:" + boxString.getData());
// ArrayList<String> objects = new ArrayList<>();
// objects.add("test");
}
}
进一步改进代码
java
package com.ys.generic;
//定义泛型接口
public interface Printable<T> {
void print(T data);
}
java
package com.ys.generic;
//实现接口
public class IntegerPrintable implements Printable<Integer>{
@Override
public void print(Integer data) {
System.out.println("这是一个Int类型,值是:" + data);
}
}
java
package com.ys.generic;
//实现接口
public class StringPrintable implements Printable<String>{
@Override
public void print(String data) {
System.out.println("这是一个String类型,值是:" + data);
}
}
java
package com.ys.generic;
//在类中使用泛型
public class Box<T> {
// 这行代码的意思是:这个Box对象里要存放的数据类型是T
// T是什么类型?在创建Box对象时决定
private T data; // 只能是T类型
// 这行代码的意思是:这个Box对象有一个打印器
// 这个打印器专门用来打印T类型的数据
private Printable<T> printable; // 保证能处理T类型
// 构造方法:创建Box对象时必须传入一个打印器
// 这个打印器必须能处理T类型的数据
public Box(Printable<T> printable) {
this.printable = printable;
}
// 这个方法:让打印器来打印Box里存放的数据
public void printData(){
// 把data交给printable去打印
printable.print(data); // 100%类型匹配
}
//快捷键:Alt + Insert
// 获取Box里存放的数据
public T getData() {
return data;
}
// 往Box里存放数据
public void setData(T data) {
this.data = data;
}
}
//整个流程的比喻
//把Box想象成一个快递包裹:
//
//T = 包裹里装的是什么(书、衣服、食品...)
//
//data = 包裹里的实际物品
//
//Printable<T> = 专门的拆包员(图书拆包员、服装拆包员...)
//
//printData() = 让对应的拆包员打开包裹并展示内容
//
//关键规则: 图书拆包员只能拆图书包裹,服装拆包员只能拆服装包裹!
//为什么需要Printable<T>?
//想象一下,如果没有泛型:
//// 糟糕的设计:什么类型都能传,容易出错
//public class BadBox {
// private Object data; // 可以是任何类型
// private Printable printable; // 不知道处理什么类型
//
// public void printData(){
// printable.print(data); // 可能类型不匹配!
// }
//}
java
package com.ys.generic;
import java.util.ArrayList;
public class GenericMain {
//在方法中使用泛型
public static <T> void printList(ArrayList<T> list){
for (T data : list){
System.out.println("数据 = " + data);
}
}
public static void main(String[] args) {
//使用Box处理不同类型的数据
// 创建阶段
Box<Integer> boxInteger = new Box<>(new IntegerPrintable());
// 此时:T = Integer
// data 是 Integer 类型
// printable 是 Printable<Integer> 类型(IntegerPrintable实现了这个接口)
// 使用阶段
boxInteger.setData(9); // 存入整数9
boxInteger.printData(); // 打印:这是一个Int类型,值是:9
// 内存中的情况:
// boxInteger对象:
// ├── data: Integer(9)
// └── printable: IntegerPrintable对象
// 创建阶段
Box<String> boxString = new Box<>(new StringPrintable());
// 此时:T = String
// data 是 String 类型
// printable 是 Printable<String> 类型(StringPrintable实现了这个接口)
// 使用阶段
boxString.setData("哈哈哈"); // 存入字符串"哈哈哈"
boxString.printData(); // 打印:这是一个String类型,值是:哈哈哈
// boxString对象:
// ├── data: String("哈哈哈")
// └── printable: StringPrintable对象
// ArrayList<String> objects = new ArrayList<>();
// objects.add("test");
ArrayList<String> list = new ArrayList<>();
list.add("hello world");
list.add("你好!");
printList(list);
}
}
//T 是什么?
//T 是一个占位符,表示"某种类型"
//
//创建对象时,用具体的类型替换T:
//
//Box<Integer> → 把所有的T都换成Integer
//
//Box<String> → 把所有的T都换成String
10.1异常处理机制
在程序当中,经常会出现一些异常与错误,Java为了我们能够更好的处理异常,也提供了异常处理机制,这可以让代码变得更加健壮:
try:捕捉异常
使用Alt+Enter:快速添加try catch
java
//尝试捕获一个异常
try {
// 可能会抛出异常的代码
} catch (IndexOutOfBoundsException e) {
// 处理异常的代码
}finally{
// 不管会不会发生异常,这里的代码都会被执行,有点像switch里的default
}
//尝试在try中捕捉多个异常
try{
// 可能会抛出异常的代码
}catch(异常类型 异常变量名){
// 处理异常的代码
}catch(异常类型 异常的变量名){
// 处理异常的代码
}catch(异常类型 异常的变量名){
// 处理异常的代码
}
//抛出自己的异常
public void check(int i) {
if (i < 0) {
throw new IllegalArgumentException("传入了1个小于0的数");
}
}
//将异常传递到外部
public void readFile(String filePath) throws IOException,NullPointerException {
}
java
package com.ys.excp;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
try{
check(-1);
}catch (IllegalArgumentException e){
String message = e.getMessage();
System.out.println(message);
}
ArrayList<Integer> ints = new ArrayList<>();
// ArrayList<Integer> ints = null; //空指针异常
getData(ints); //数组下标越界
}
public static void check(int i) throws IllegalArgumentException {
if (i < 0) {
throw new IllegalArgumentException("i<0,它是不合法的");
} else {
System.out.println("i是合法的");
}
}
public static void getData (ArrayList < Integer > ints) {
try {
Object object = null;
System.out.println("object = " + object.toString());
ints.get(0);
} catch (IndexOutOfBoundsException e) {
System.out.println("您要访问的位置在ints中不存在");
} catch (NullPointerException e) {
System.out.println("object这个对象是空的");
} finally {
System.out.println("执行了finally里面的代码");
}
System.out.println("hello world");
}
}
检查时异常和运行时异常
- 运行时异常不会强制要求使用try catch捕捉,也不强制要求需要throws
- 检查型异常要求需要try catch,也要求throws
java
Throwable
├── Error
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── ...
├── Exception
│ ├── RuntimeException ( unchecked )
│ │ ├── NullPointerException
│ │ ├── ArrayIndexOutOfBoundsException
│ │ ├── IllegalArgumentException
│ │ └── ...
│ └── 其他Exception ( checked )
│ ├── IOException
│ ├── SQLException
│ ├── ClassNotFoundException
│ └── ...
异常在哪里使用
- 根据自身业务场景的需要,在容易出现问题的地方做异常处理,可以增强稳定性、可靠性;
- 尽量不要过度使用异常,而是尽量精准的使用;
- 善于使用finally,例如在finally里面做一些资源释放。
常见的异常类型
| 异常 | 描述 |
|---|---|
| ArithmeticException | 当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。 |
| ArrayIndexOutOfBoundsException | 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。 |
| ArrayStoreException | 试图将错误类型的对象存储到一个对象数组时抛出的异常。 |
| ClassCastException | 当试图将对象强制转换为不是实例的子类时,抛出该异常。 |
| IllegalArgumentException | 抛出的异常表明向方法传递了一个不合法或不正确的参数。 |
| IllegalMonitorStateException | 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。 |
| IllegalStateException | 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。 |
| IllegalThreadStateException | 线程没有处于请求操作所要求的适当状态时抛出的异常。 |
| IndexOutOfBoundsException | 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 |
| NegativeArraySizeException | 如果应用程序试图创建大小为负的数组,则抛出该异常。 |
| NullPointerException | 当应用程序试图在需要对象的地方使用 null 时,抛出该异常。 |
| NumberFormatException | 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。 |
| SecurityException | 由安全管理器抛出的异常,指示存在安全侵犯。 |
| StringIndexOutOfBoundsException | 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。 |
| UnsupportedOperationException | 当不支持请求的操作时,抛出该异常。 |
| 异常 | 描述 |
|---|---|
| ClassNotFoundException | 应用程序试图加载类时,找不到相应的类,抛出该异常。 |
| CloneNotSupportedException | 当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。 |
| IllegalAccessException | 拒绝访问一个类的时候,抛出该异常。 |
| InstantiationException | 当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。 |
| InterruptedException | 一个线程被另一个线程中断,抛出该异常。 |
| NoSuchFieldException | 请求的变量不存在 |
| NoSuchMethodException | 请求的方法不存在 |
10.2自定义异常
java
package com.ys.excp;
import java.io.IOException;
//声明自定义异常
public class StringLengthException extends IOException {
int length;
public StringLengthException(int length) {
this.length = length;
}
public int getLength() {
return length;
}
}
java
package com.ys.excp;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
try {
checkString();
} catch (StringLengthException e) {
System.out.println("当前这个字符的长度是"+e.getLength());
}
}
public static void checkString() throws StringLengthException {
String str;
Scanner scanner = new Scanner(System.in);
str = scanner.nextLine();
int length = str.length();
if (length < 5) {
throw new StringLengthException(length);
} else {
System.out.println("字符串str的长度是:" + length);
}
}
}
10.3写入本地文件
每一个程序都需要输入和输出功能,除了面向控制台的输入、输出,Java还提供了面向与本地文件的读写操作。
写入本地文件这个过程就是借助FileOutputStream,把内存当中hello里的内容输出到文件。
java
package com.ys.filerw;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class WriteMain {
public static void main(String[] args) {
//需要写入的内容
String hello = "hello world!";
//需要写入的文件路径,如果只写文件名,那就默认是当前工程的根目录
String filePath = "text.txt";
//创建一个FileOutputStream对象,用于处理文件的写入操作
try (FileOutputStream fileOutputStream = new FileOutputStream(filePath)) {
//将hello转为byte数组,以便fileOutputStream处理写入操作
byte[] bytes = hello.getBytes();
//调用写入方法write(),写入文件
fileOutputStream.write(bytes);
System.out.println("写入文件" + filePath + "成功!");
} catch (FileNotFoundException e) {
System.out.println("找不到文件!");
} catch (IOException e) {
System.out.println("异常:文件写入失败!");
}
}
}
10.4读取本地文件
读取的操作实际就是借助FileInputStream,把文件读取到程序的内存当中。
read(byte[] b):
读取最多b.length字节的数据到字节组中
返回:读入缓冲区的总字节数,如果没有更多数据,则返回-1
利用read方法读到最后会返回-1的特性,结合while循环,就可以实现分段、多次读取
StringBuilder
java
package com.ys.filerw;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class ReadMain {
public static void main(String[] args) {
//需要读取的文件路径,如果只写文件名,那就默认是当前工程的目录
String filePath = "D:\\Java Code\\test.txt";
//创建一个FileInputStream对象,用于处理文件的写入操作
//再创建一个InputStreamReader来封装fis,isr可以帮助我们处理文本信息的编码、以及一些细节
try (FileInputStream fis = new FileInputStream(filePath);
//创建isr时指定编码格式,在国内一般都是UTF-8编码集
InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8)) {
//创建一个临时的缓冲区,每次读写1024的内容长度
char[] chars = new char[1024];
//创建一个StringBuilder,在每一次读取后,都往sb中存入读取的数据
StringBuilder sb = new StringBuilder();
//用于记录每次读取的实际长度
int charLength;
//在循环中使用read读取文件,如果已经没有新的数据可以读,就会返回-1,跳出循环
while ((charLength = isr.read(chars)) != -1) {
//将每次读取到的内容存入sb,并且指定每次存入的数据长度
sb.append(chars, 0, charLength);
}
//将sb转为字符串对象
String s = sb.toString();
System.out.println("读取到的内容是:" + s);
} catch (IOException e) {
System.out.println("文件" + filePath + "操作异常!");
}
}
}
10.5流的概念
流的特点:单向的一维操作
"流"(Stream)是一个抽象的概念,用于处理数据序列。Java中的流主要有两种类型:
输入输出流(Input、Output)
IO:Input和Output的缩写
- I/O流用于读取(输入)和写入(输出)数据,
- 它们通常与数据源交互,比如网络传输、文件、控制台等数据交互;
- 常见的I/O流有FileInputStream,FileOutputStream,BufferedReader,BufferedWriter;
- I/O流是Java I/O库的一部分,它提供了丰富的类和方法来处理各种输入和输出任务,只需要在合适的场景选择对应的流即可。
Stream API 函数式编程流
(感兴趣的可以自行扩展)
- JDK8中,允许我们以声明的方式处理数据集合(如List、Set等),可以提高代码的可读性和可维护性;
- Stream API基于函数式编程的概念,提供了一系列针对于容器、集合的map、filter、reduce等操作方法,可以在不修改原始数据集合的情况下,对里面的数据进行转换、过滤和聚合等操作,可以提升编程效率;
- 与传统的for-each循环相比,Stream API通常能写出更简洁、更易于理解的代码;
- Stream API和IO流不一样,Stream API主要用于处理内存中的数据集合,而不是与数据源直接交互。
java
package com.ys.filerw;
import java.util.Arrays;
import java.util.List;
public class TestMain {
public static void main(String[] args) {
//使用Stream API,在不改变numbers这个字符串列表的前提下,使用里面的数据,直接转成int并求和
List<String> numbers = Arrays.asList("1", "2", "3", "4", "5");
int sum = numbers.stream()
.mapToInt(Integer::parseInt)
.sum();
System.out.println(sum); // 输出 15
}
}
11.进程与线程
线程:操作系统实现任务调度的最基本单位
在绝大多数的操作系统环境中,线程只能依赖于进程
- 多数情况下,一个进程可以理解为对应一个程序,但是随着计算机的发展,一个应用有可能会有多个进程(多个应用也可能只共享一个进程。比如我们经常听到的微服务、分布式,就有可能是一个程序对应多个进程,但这些目前不是我们需要关心的。我们只要知道:进程是操作系统分配资源的基本单位;
- 1个进程可以有多个线程,线程是操作系统实现任务调度的基本单位,程序里的各种任务就是由一个个线程来实现的任务调度;
- 每一进程都有属于自己的存储空间和系统资源,而同一进程下的线程可以共享这个进程的内存与资源;
- 在C C++这种相对底层的语言,会直接提供进程、线程的操作方法,但Java通常只需要关心线程的使用;
线程的创建
继承Thread
java
package com.ys.thread;
public class MyThread extends Thread{
@Override
public void run() {
//执行线程里的代码
System.out.println("HelloWorld");
}
}
java
package com.ys.thread;
public class ThreadMain {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); //启动线程
}
}
实现Runnable接口
扩展:运行java程序时,会默认开启一个主线程(初始线程),而main方法,就是在主线程中被调用。因此,main方法中的所有操作,也都是在主线程。
java
package com.ys.thread;
public class MyRunable implements Runnable{
@Override
public void run() {
//线程执行的代码
System.out.println("MyRunable HelloWorld");
}
}
java
package com.ys.thread;
public class ThreadMain {
public static void main(String[] args) {
MyRunable myRunable = new MyRunable();
myRunable.run(); // 这种写法默认是在主线程运行(主线程:启动Java应用程序的线程)
//常用的是通过这种形式启动一个新的线程
Thread thread1 = new Thread(myRunable);
thread1.start();
}
}
这种方式可以允许多个线程共享1个runnable实例,好处是可以让线程间共享数据和代码。
11.2使用Callable启动线程
匿名内部类
java
package com.ys.thread;
public class ThreadMain
/**
* 匿名内部类
*/
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
//执行线程里的代码
System.out.println("thread:HelloWorld");
}
};
thread.start();
Runnable runnable = new Runnable() {
@Override
public void run() {
//执行线程里的代码
System.out.println("runnable:HelloWorld");
}
};
Thread thread1 = new Thread(runnable);
thread1.start();
}
}
run没有返回值
call有返回值
java
package com.ys.thread;
import java.util.concurrent.Callable;
//实现callable接口
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("call 开始了");
Thread.sleep(3000); //让当前线程进入睡眠状态,3秒后再继续运行
int a = 10;
int b = 20;
int c = a + b;
return c;
}
}
java
package com.ys.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadMain {
/**
* callable的用法
* @param args
*/
public static void main(String[] args) {
MyCallable callable = new MyCallable();
////或者写成匿名内部类的形式
// Callable<Integer> callable = new Callable<>() {
//
// @Override
// public Integer call() throws Exception {
// System.out.println("call 开始了");
//
// Thread.sleep(3000); //让当前线程进入睡眠状态,3秒后再继续运行
// int a = 10;
// int b = 20;
// int c = a + b;
// return c;
// }
// };
//借助FutureTask包装callable
FutureTask<Integer> futureTask = new FutureTask<>(callable);
//在新的线程中使用callable
Thread thread = new Thread(futureTask);
thread.start();
//取消当前线程的执行
//futureTask.cancel(true);
try {
//使用get获取结果,注意:这会调用get的线程被阻塞等待结果返回
int integer = futureTask.get();
System.out.println("c = " + integer);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}
三种方式怎么选择?
- 如果只是单纯的想要开辟一个线程做一个事情,可以直接使用Thread;
- 如果想要多个线程共同操作一些数据,可以使用Runnable;
- 如果需要线程处理完返回结果,可以使用callable。
11.3为什么需要多线程
- 充分利用多核处理器,提升程序的性能
- 避免主线程阻塞等待,提升用户体验
扩展:
1核理论上可同时处理1个线程,
但现在也有技术可以让1核同时处理2个以上的线程
主线程当中,代码只能按顺序执行
多线程允许程序同时执行多个任务
不同业务逻辑相互隔离,便于代码维护
java
package com.ys.thread;
/**
* 多个线程同时工作
* @param args
*/
public class ThreadMain {
public static void main(String[] args) {
Thread thread1 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("thread1:" + i);
}
}
};
Thread thread2 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("thread2:" + i);
}
}
};
Thread thread3 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("thread3:" + i);
}
}
};
for (int i = 0;i<100;i++){
System.out.println("主线程:" + i);
}
thread1.start();
thread2.start();
thread3.start();
}
}
11.4Thread的常见操作
java
package com.ys.thread;
public class ThreadMain {
/**
* 多个线程同时工作
*
* @param args
*/
public static void main(String[] args) {
Thread thread1 = new Thread() {
@Override
public void run() {
Thread thread11 = Thread.currentThread(); //获取当前的线程
long id = thread11.getId(); //通过对象调用获取线程的id
thread11.setName("thread1"); //通过对象调用设置线程的名字
String name = thread11.getName(); //通过对象调用获取线程的名字
for (int i = 0; i < 100; i++) {
System.out.println("当前的线程名是:" + name + ",线程Id是:" + id + ",i = " + i);
}
}
};
Thread thread2 = new Thread() {
@Override
public void run() {
Thread thread11 = Thread.currentThread();
Thread.yield(); //表示该线程愿意放弃其当前对处理器的使用
long id = thread11.getId();
thread11.setName("thread2");
String name = thread11.getName();
for (int i = 0; i < 100; i++) {
System.out.println("当前的线程名是:" + name + ",线程Id是:" + id + ",i = " + i);
}
}
};
Thread thread3 = new Thread() {
@Override
public void run() {
Thread thread11 = Thread.currentThread();
Thread.yield();
long id = thread11.getId();
thread11.setName("thread3");
String name = thread11.getName();
for (int i = 0; i < 100; i++) {
System.out.println("当前的线程名是:" + name + ",线程Id是:" + id + ",i = " + i);
}
}
};
// Thread thread11 = Thread.currentThread();
// long id = thread11.getId();
// for (int i = 0; i < 100; i++) {
// System.out.println("主线程:当前的线程名是:" + thread11.getName() + ",线程Id是:" + id + ",i = " + i);
// }
thread1.setPriority(Thread.MAX_PRIORITY); //设置线程的优先级为最大
thread2.setPriority(Thread.MIN_PRIORITY);
thread3.setPriority(Thread.MIN_PRIORITY);
thread1.start(); //开始执行线程,这个方法会让run方法被触发
thread2.start();
thread3.start();
}
}
11.5让线程停止执行
- 自己定义一个状态来控制;
- 使用interrupt()方法:如果当前线程正被阻塞(sleep、wait)的时候被interrupt,那么就会触发InterruptedException异常,通常我们会在异常代码块中处理资源的释放,需要注意,抛出InterruptedException异常后,中断的状态会被清除。另外,如果当前线程没有被阻塞,那么就会正常中断线程操作;
- 如果是Callable的形式,可以使用futureTask.cancel(true);
java
package com.ys.thread.stop;
public class StopThread extends Thread{
boolean stop;
@Override
public void run() {
//执行线程里的代码
for (int i = 0; i < 100; i++) {
if (i == 50){
stop = true;
System.out.println("StopThread已停止操作");
return;
}
System.out.println("StopThread i = " + i);
}
}
}
java
package com.ys.thread.stop;
public class StopMain {
public static void main(String[] args) {
StopThread stopThread = new StopThread();
stopThread.start();
}
}
通过自己的变量来控制线程是否继续工作
java
package com.ys.thread.stop;
public class StopThread extends Thread{
boolean stop;
@Override
public void run() {
//执行线程里的代码
for (int i = 0; i <Integer.MAX_VALUE; i++) {
//Integer.MAX_VALUE:int类型的上限
if (stop){
System.out.println("StopThread已停止操作");
return;
}
System.out.println("StopThread i = " + i);
}
}
public void setStop(boolean stop){
this.stop = true;
}
}
java
package com.ys.thread.stop;
public class StopMain {
/**
* 通过自己的变量来控制线程是否继续工作
*
* @param args
*/
public static void main(String[] args) {
StopThread stopThread = new StopThread();
stopThread.start();
try {
Thread.sleep(3000); //让当前线程睡眠指定时间,单位是毫秒
stopThread.setStop(true);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
wait、sleep等操作,会让当前线程被阻塞(停滞)
使用interrupt()方法
java
package com.ys.thread.stop;
public class InterruptThread extends Thread {
@Override
public void run() {
//执行线程里的代码
for (int i = 0; i < Integer.MAX_VALUE; i++) {
//int类型的上限
if (isInterrupted()) {
System.out.println("StopThread已停止操作");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("我本来正在sleep,但是我被停止操作了!");
return;
}
System.out.println("StopThread i = " + i);
}
}
}
java
package com.ys.thread.stop;
public class StopMain {
public static void main(String[] args) {
InterruptThread interruptThread = new InterruptThread();
interruptThread.start();
try {
Thread.sleep(3000);
//中断当前interruptThread线程
interruptThread.interrupt();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
11.6 Synchronized同步锁
java
package com.ys.thread.security;
public class MovieTicket {
private int ticket = 100;
public void sellTicket() {
while (ticket > 0) {
ticket--;
System.out.println("卖了一张票,库存剩余:" + ticket);
}
}
}
java
package com.ys.thread.security;
public class SecurityMain {
public static void main(String[] args) {
MovieTicket movieTicket = new MovieTicket();
Thread thread = new Thread() {
@Override
public void run() {
movieTicket.sellTicket();
}
};
thread.start();
}
}
线程安全
当多个线程同时访问共享资源时,可能会导致数据不一致、重复等问题。Java也提供了解决线程安全的方案
java
package com.ys.thread.security;
public class MovieTicket implements Runnable {
private int ticket = 100;
public void sellTicket() {
while (ticket > 0) {
ticket--;
System.out.println("卖了一张票,这张票的序号是:" + ticket + ",库存剩余:" + ticket);
}
}
@Override
public void run() {
sellTicket();
}
}
java
package com.ys.thread.security;
public class SecurityMain {
public static void main(String[] args) {
MovieTicket movieRunnable = new MovieTicket();
Thread thread1 = new Thread(movieRunnable);
Thread thread2 = new Thread(movieRunnable);
Thread thread3 = new Thread(movieRunnable);
thread1.start();
thread2.start();
thread3.start();
}
}
多线程的场景下,需要注意数据的安全
Synchronized关键字
Synchronized同步锁,解决了多线程的数据安全问题。可当线程很多时,因为每个线程都会判断其他线程是否是有锁,这是很耗费资源的,会降低程序的运行效率。互斥:线程排斥
java
package com.ys.thread.security;
public class MovieTicket implements Runnable {
private int ticket = 100;
public void sellTicket() {
synchronized (this) {
//this:当前被实例化的MovieTicket对象。对象锁
while (ticket > 0) {
ticket--;
System.out.println("卖了一张票,这张票的序号是:" + ticket + ",库存剩余:" + ticket);
}
}
}
@Override
public void run() {
sellTicket();
}
}
还有其他写法
java
public synchronized void sellTicket() {
while (ticket > 0) {
ticket--;
System.out.println("卖了一张票,这张票的序号是:" + ticket + ",库存剩余:" + ticket);
}
}
11.7死锁
LOCK
Lock是一个接口,也可以用来解决线程同步问题,我们可以使用实现了Lock接口的ReentrantLock,可以比synchronized更精细的控制锁,并且还可以做很多扩展操作。
java
package com.ys.thread.security;
import java.util.concurrent.locks.ReentrantLock;
public class MovieTicketLock implements Runnable {
private int ticket = 100; //表示有100张电影票
private final ReentrantLock reentrantLock = new ReentrantLock();
public void sellTicket() {
while (ticket > 0) {
//上锁
reentrantLock.lock();
try {
if (ticket > 0) {
ticket--;
System.out.println("卖了一张票,这张票的序号是:" + ticket + ",库存剩余:" + ticket);
} else {
System.out.println("在其他窗口卖完了 ,库存剩余:" + ticket);
}
} finally {
//不管是否发生异常,都执行解锁操作
reentrantLock.unlock();
}
}
}
@Override
public void run() {
sellTicket();
}
public static void main(String[] args) {
MovieTicketLock movieRunnable = new MovieTicketLock();
Thread thread1 = new Thread(movieRunnable);
Thread thread2 = new Thread(movieRunnable);
Thread thread3 = new Thread(movieRunnable);
thread1.start();
thread2.start();
thread3.start();
}
}
两种形式如何选择?
- 多数简单的场景下使用synchronized,因为JDK6以后,synchronized的性能和lock不相上下;
- 需要做更多复杂的功能,就使用lock。
注意不要产生死锁
死锁
每个线程都在等待其他线程释放资源,从而导致它们都无法继续执行。死锁通常发生在多线程环境中,当两个或更多的线程无限期地等待一个系统资源(如内存锁),而每个线程又在等待被另一个线程持有的资源时,就会发生死锁。
常见的死锁场景
- 嵌套锁:当一个线程获取了一个锁,然后又试图获取第二个锁,而第二个锁已经被另一个线程持有,同时这个线程又持有第一个线程需要的锁;
- 锁顺序不一致:当多个线程尝试以不同的顺序获取多个锁时,可能会发生死锁;
- 可重入锁的不当使用:如果一个线程在没有释放锁的情况下重复请求同一个锁,而其他线程正在等待这个锁,那么也可能导致死锁;
- 超时等待:如果线程在尝试获取锁时设置了超时,并且超时时间太短,那么它可能在等待其他线程释放锁之前就放弃了,这可能导致死锁,特别是当多个线程都这样做时。
如何避免死锁
- 避免嵌套锁:尽量确保线程按照一致的顺序获取锁;
- 使用锁超时:为线程尝试获取锁设置超时时间,以避免无限期等待;
- 使用并发库:目前我们虽然只学过Synchronized和Lock,但Java还提供了许多高级同步工具,如CountDownLatch、CyclicBarrier、Semaphore和Exchanger等,这些工具可以帮助减少死锁的风险,后续也会陆续接触到。
11.8wait与notify
notify、notifyAll 和 wait 是 Java 中用于多线程间通信的方法,它们都属于 Object 类的方法,用于在对象的监视器(monitor)上等待或通知线程。而 sleep 是 Thread 类的一个静态方法,用于让当前线程暂停执行一段时间。
注意:notify、notifyAll 和 wait 都只能在同步代码快或者同步方法里被使用,意思就是他们必须在拥有某个对象所的线程里被调用。
wait
当线程需要等待某个条件成立时,可以调用 wait 方法将自己挂起,并释放当前持有的监视器锁。当其他线程改变了条件并调用 notify 或 notifyAll 时,被挂起的线程可能会被唤醒。
notify 和 notifyAll
多个线程需要等待某个条件成立才能继续执行时,可以使用 wait 将线程挂起,并在条件成立时使用 notify 或 notifyAll 唤醒等待的线程。这种做法可以让线程之间能够基于某个条件进行同步。
注意:notify 只会随机唤醒一个等待在该对象上的线程,而 notifyAll 会唤醒所有等待在该对象上的线程。
wait和sleep
如果你需要在多线程之间进行基于某个条件的同步和通信,使用 wait、notify 和 notifyAll。
如果你只是想让当前线程暂停执行一段时间,而不涉及与其他线程的同步或通信,使用 sleep。
java
package com.ys.thread.security;
public class TicketSeller {
private int tickets = 100; //总票数
private final Object lock = new Object();
//卖票
private void sellTicket() {
synchronized (lock) {
while (tickets <= 0) {
try {
System.out.println("票卖完了,请等待放票...");
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
tickets--;
System.out.println(Thread.currentThread().getName() + "卖出了一张票,剩余票数:" + tickets);
}
}
private void addTicket() {
synchronized (lock) {
tickets += 100;
System.out.println("添加了100张票,当前的票数是:" + tickets);
lock.notifyAll(); //通知多有线程解除wait
}
}
private static class TicketSellingThread extends Thread {
TicketSeller seller;
public TicketSellingThread(TicketSeller ticketSeller) {
seller = ticketSeller;
}
@Override
public void run() {
while (true) {
seller.sellTicket();
}
}
}
public static void main(String[] args) {
TicketSeller ticketSeller = new TicketSeller();
for (int i = 0; i < 3; i++) {
new TicketSellingThread(ticketSeller).start();
}
try {
Thread.sleep(10000); //10S加一次票
ticketSeller.addTicket();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
11.9线程的生命周期
┌─────────────────────────────────────────────────────────────┐
│ 线程生命周期状态图 │
└─────────────────────────────────────────────────────────────┘
┌─────────────┐
│ 新建状态 │
│ (NEW) │
└─────────────┘
│
↓ (调用start()方法)
┌─────────────┐
│ 可运行状态 │
│ (RUNNABLE) │
└─────────────┘
│
┌─────────────────────────┼─────────────────────────┐
↓ ↓ ↓
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 阻塞状态 │ │ 等待状态 │ │ 超时等待状态 │
│ (BLOCKED) │ │ (WAITING) │ │(TIMED_WAITING)│
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ (获取到锁) │ (被notify/notifyAll唤醒) │ (等待时间到/被唤醒)
↓ ↓ ↓
┌─────────────┐
│ 可运行状态 │
│ (RUNNABLE) │
└─────────────┘
│
↓ (线程执行完成)
┌─────────────┐
│ 终止状态 │
│(TERMINATED) │
└─────────────┘
线程的6种状态
1. 新建状态(NEW):
- 当一个线程对象被创建,但尚未调用其start()方法时,该线程处于新建状态。此时,线程对象已经存在,但系统并未为其分配资源,因此它还没有开始执行。
2. 就绪状态(RUNNABLE):
-
当线程调用start()方法后,它进入就绪状态。这意味着线程已经准备好运行,但是否真正执行取决于操作系统的调度。就绪状态包括了"就绪"和"运行"两种细分状态:
-
就绪:线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权。
-
运行:当线程获得CPU资源并执行任务时,它处于运行中状态,正在执行其run()方法中的代码。
3. 阻塞状态(BLOCKED):
- 当线程等待获取一个锁以进入或重新进入同步代码块时,它进入阻塞状态。这意味着线程正在等待一个资源或条件,以便能够继续执行。
4. 等待状态(WAITING):
- 当线程被另一个线程所阻塞,等待某个条件成立或获得某个对象的监视器锁时,它处于等待状态。例如,当线程调用无参数的wait()方法时,它会进入等待状态。在这种情况下,线程会等待另一个线程的操作完成或者等待notify/notifyAll消息。
5.定时等待状态(TIMED_WAITING):
- 当线程等待另一个线程执行特定操作或等待指定时间后继续执行时,它处于定时等待状态。这通常通过调用Thread类的sleep()方法或使用java.util.concurrent包中的工具类来实现。在指定的时间过去之后,线程会自动返回就绪状态。
6.终止状态(TERMINATED):
- 当线程完成执行任务或因异常终止时,它处于终止状态。此时,线程已经不再运行,并且无法再次被启动。线程的消亡是不可逆的,不能对处于终止状态的线程再次执行start()方法。
这6种状态定义在Thread类的State枚举中,并且可以通过调用线程的getState()方法来获取当前线程的状态。这些状态涵盖了线程从创建到消亡的整个生命周期,并且是线程调度和同步机制的基础。
11.10线程池
线程池是Java对线程的一种管理模式,它可以预先定义一定数量的线程,允许我们通过复用已有的线程来处理任务,而不需要为每个任务都创建一个新的线程。这减少了线程创建和销毁的开销,提高了系统的效率和稳定性。
优点
- 降低资源消耗:通过复用线程,减少了线程创建和销毁的开销。
- 提高响应速度:当任务到达时,可以立即从线程池中获取线程执行任务,而不需要等待线程的创建。
- 提高线程的可管理性:线程池允许我们开启多个任务而不用为每个线程设置属性值,便于管理和调优。
使用场景
- 高并发场景:当有大量任务需要并发执行时,使用线程池可以避免频繁创建和销毁线程的开销。
- 任务执行时间短:对于执行时间较短的任务,使用线程池可以提高系统的吞吐量。
- 需要控制最大并发数:通过线程池可以限制同时运行的线程数,从而避免系统资源的过度消耗。
线程池的使用
Java提供了多种类型的线程池,newFixedThreadPool(固定线程数线程池)、newSingleThreadExecutor(单线程化线程池)、newCachedThreadPool(可缓存线程池)等,它们适用于不同的使用场景。
newFixedThreadPool
newFixedThreadPool可以指定固定大小的线程池,可以限制同时执行的线程数量,防止系统资源耗尽。
java
package com.ys.thread.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExample {
public static void main(String[] args) {
int size = 10; //核心线程数
//创建一个固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(size);
for (int i = 0; i < 20; i++) {
int task = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("task:" + task + ",当前正在运行的线程是:" + Thread.currentThread().getName());
try {
Thread.sleep(1000); //模拟工作1秒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
executorService.submit(runnable); //提交任务到线程池
}
//关闭线程池
executorService.shutdown();
while (!executorService.isTerminated()) {
//等待所有任务完成
}
System.out.println("当前任务已全部完成...");
}
}
newSingleThreadExecutor
newSingleThreadExecutor创建一个只有单个线程对象的线程池,如果需要按顺序执行一系列的任务,就可以使用这种形式,比如按顺序读写文件、再处理文件。
java
package com.ys.thread.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 20; i++) {
int task = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("task:" + task + ",当前正在运行的线程是:" + Thread.currentThread().getName());
try {
Thread.sleep(1000); //模拟工作1秒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
executorService.submit(runnable); //提交任务到线程池
}
//关闭线程池
executorService.shutdown();
while (!executorService.isTerminated()) {
//等待所有任务完成
}
System.out.println("当前任务已全部完成...");
}
}
newCachedThreadPool
newCachedThreadPool只要有任务执行,但线程池中没有空闲线程,就会新建一个线程来执行任务,适合需要大量短生命周期任务同时运行的场景。
java
package com.ys.thread.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CachedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
int task = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("task:" + task + ",当前正在运行的线程是:" + Thread.currentThread().getName());
//模拟短生命周期任务
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
executor.submit(runnable); //提交任务到线程池
}
//关闭线程池(这里使用优雅关闭)
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("当前任务已全部完成...");
}
}
12.1Lambda表达式
被@FunctionalInterface注解标记过的功能接口,可以使用Lambda表达式的写法。前提是功能接口并且是只有一个抽象方法的接口。
java
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
java
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
在线程中的写法
java
package com.ys.lambda;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MainTest {
public static void main(String[] args) {
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("HelloWorld");
}
};
Thread thread1 = new Thread(runnable1);
thread1.start();
Runnable runnable2 = () -> System.out.println("HelloWorld");
Thread thread2 = new Thread(runnable2);
thread2.start();
Runnable runnable3 = () -> {
System.out.println("HelloWorld");
System.out.println("HelloWorld1");
System.out.println("HelloWorld2");
};
Thread thread3 = new Thread(runnable3);
thread3.start();
new Thread(() -> System.out.println("HelloWorld3")).start();
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("HelloWorld4");
}
};
thread.start();
Thread thread4 = new Thread(() -> {
System.out.println("HelloWorld5");
});
thread4.start();
Callable<String> callable = new Callable<>() {
@Override
public String call() throws Exception {
int i = 1 + 2;
return "计算结果: " + i;
}
};
Callable<String> callable1 = () -> {
int i = 1 + 2;
return "计算结果: " + i;
};
FutureTask<String> futureTask = new FutureTask<>(callable1);
new Thread(futureTask);
}
}
12.2自定义Lambda表达式
java
package com.ys.lambda;
//不带参数,不带返回值
@FunctionalInterface
public interface Greeter {
void greet();
}
java
package com.ys.lambda;
public class MainAuto {
public static void main(String[] args) {
// Greeter greeter = new Greeter() {
// @Override
// public void greet() {
//
// }
// };
// greeter.greet();
Greeter greeter1 = () -> System.out.println("hello");
greeter1.greet();
}
}
java
package com.ys.lambda;
//带1个参数
@FunctionalInterface
public interface Greeter1 {
void greet(String name);
}
java
Greeter1 greeter2 = (String name) ->System.out.println("hello1");
Greeter1 greeter3 = name ->System.out.println("hello2");
java
package com.ys.lambda;
//带参数和返回值
@FunctionalInterface
public interface Greeter2 {
int greet(String name1,int i);
}
java
Greeter2 greeter4 = (name1,i) ->{
System.out.println("hell03");
return 0;
};