Java 笔记
一、基础概念
1、JDK
1.1.1 什么是JDK
JDK : Java Development Kits
Java开发工具箱【Java开发必备】
可以从Oracle的官网上下载: http://www.oracle.com
下载JDK的时候需要注意:JDK的版本,不同的操作系统需要安装不同版本的JDK。
1.1.2 Java 分类
J2SE:【Java的标准版本】J2SE是SUN公司为java程序员准备的一套“基础类库”,这套基础类库学习之后,可以完成最基本的操作,例如,文件的读写、线程的控制…
J2EE:【Java的企业版本】这是SUN公司为程序员专门准备的一套“类库”,这套类库可以协助程序员完成企业级软件的开发。企业级软件:OA办公系统、进销存系统、超市系统…
J2ME:【Java的微型版本】这是SUN公司为java程序员专门准备的另一套“类库”,这套类库可以协助程序员完成微型设备的嵌入式开发,Java最初就是做微型设备嵌入式开发的。
2005年,java诞生十周年的时候,以上的三大模块改名了:
- JavaSE
- JavaEE
- JavaME
1.1.3 JDK JRE JVM 三者的关系
-
JDK【Java开发工具箱】
-
JRE【Java的运行时环境】
-
JVM【Java虚拟机】
三者之间的关系:JDK 中包含JRE,JRE中包含JVM。
2、Java 语言
1.2.1 Java 语言的特性
跨平台/可移植 :有一种特殊的机制:JVM。Java程序并没有和底层的操作系统直接交互,java程序实际上运行在jvm当中,JVM屏蔽了操作系统之间的差异,但是有一个前提:不同的操作系统中必须安装不同版本的JVM。在可移植性方面表现非常好,一次编译,到处运行。但是为了达到可移植,必须提前在操作系统中安装JRE,JRE有了之后才会有JVM。【JVM不能单独安装】这方面体验不是特别好。
Java号称:开源、免费、跨平台、纯面向对象:开源:开发源代码,SUN公司编写的java类库的源代码普通程序员能看到。众人拾柴火焰高。这样java程序会很健壮。很少的BUG【漏洞/陷阱】。跨平台:依靠JVM机制【java程序不和操作系统交互,java程序运行在JVM中,JVM和操作系统交互。】不同的操作系统有不同版本的JVM。面向对象:人类在认识现实世界的时候多数是以面向对象的方式认知的。
java支持多线程,简单,具有垃圾回收机制。
1.2.2 Java 的加载与执行
Java开发的整个生命周期,包括两个重要的阶段,分别是:编译阶段和运行阶段。
编译生成的程序被称为:字节码程序。编译生成的文件是:xxx.class文件
编译和运行可以在不同的操作系统中完成。
程序执行原理:
- java.exe命令执行会启动:JVM
- JVM启动之后,马上启动“类加载器—Class Loader”
- ClassLoader负责去硬盘的“某个位置”上搜索“类名.class”字节码文件。
- 找不到这个.class文件,一定会出现程序异常现象。
- 找到了这个.class文件之后将.class文件转换成"二进制",操作系统可以直接识别二进制,操作系统执行二进制码和底层的硬件平台进行交互。
1.2.3 什么是类名
- 假设硬盘上有一个文件,叫做Hello.class,那么类名就叫做:Hello
- 假设硬盘上有一个文件,叫做Student.class,那么类名就叫做:Student
- 假设硬盘上有一个文件,叫做User.class,那么类名就叫做:User
- 假设硬盘上有一个文件,叫做Product.class,那么类名就叫做:Product
1.2.4 配置环境变量
原理:windows操作系统在查找某个命令的时候是怎么查找的?
- 首先会从当前目录下找这个命令
- 当前目录下不存在这个命令的话,会去环境变量path指定的路径当中查找该命令。
- 还是找不到则出现错误提示信息。
path环境变量隶属于windows操作系统,和java无关,这个环境变量主要用来指定命令的搜索路径。
环境变量怎么配置?
计算机 --> 点击右键 --> 属性 --> 高级系统设置 --> 环境变量
环境变量配置包括用户级别和系统级别,任何一个环境变量都有变量名和变量值,例如path环境变量:
变量名是:path
值:路径【多个路径之间必须采用分号隔开,而且要求分号必须是半角分号】
二、Java 语言基础
1、第一个程序HelloWorld
2.1.1 书写程序HelloWorld
文件名: HelloWorld.java
public class HelloWorld{
public static void main(String[] args){
System.out.println("Hello World");
}
}
2.1.2 程序的执行
打开DOS窗口,切换到对应文件的目录下,输入: javac HelloWorld.java
生成字节码文件,接着输入:java HelloWorld
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HRdUf28Q-1628072464526)(C:\Users\浮游\AppData\Roaming\Typora\typora-user-images\image-20210130190403656.png)]
2.1.3 对HelloWorld.java程序的解释
public class HelloWorld{ // 这是一个公开的类,类名叫做HelloWorld
// 这是类体
// 类体中不允许直接编写java语句,除声明变量
public static void main(String[] args){ //表示定义一个公开的静态的主方法
// 方法体
// 方法体
// 方法体
// args 可以随便写
System.out.println("Hello World");
}
}
2、注释
2.2.1 单行注释
单行注释:双斜线
// 单行注释,只注释当前行
2.2.2 多行注释
多行注释:
/*
多行注释
多行注释
多行注释
…
*/
2.2.3 javadoc 注释
/**
*/
注意:这种注释是比较专业的注释,该注释信息会被javadoc.exe工具解析提取并生成帮助文档。
3、public class 和 class 的区别
- 一个java源文件当中可以定义多个class。
- 一个java源文件当中public的class不是必须的。
- 一个class会生成一个xxx.class字节码文件。
- 一个java源文件当中定义公开的类的话,只能有一个,并且该类名称必须和java源文件名称一致。
- 每一个class当中都可以编写main方法,都可以设定程序的入口,想执行B.class中的main方法:java B,想执行X.class当中的main方法:java X
- 注意:当在命令窗口中执行java Hello,那么要求Hello.class当中必须有主方法。没有主方法会出现运行阶段的错误。
4、Java 语言中的标识符
2.4.1 什么是标识符
在java源程序中凡是程序员自己命名的单词都是标识符。
标识符可以表示的元素:类名、变量名、方法名、接口名、常量名…
2.4.2 标识符的命令规则
一个标识符只能由:数字、字母、下划线、美元符组成。
不能以数组开头,严格区分大小写,关键字不能做标识符,理论上长度不限制。
2.4.3 标识符的命名规范
只是一种规范,不是 java 的语法,不规范命名,编译器不会报错。
- 遵守驼峰命名规范
- 类名、接口名 :首字母大写,后面的单词的首字母大写。
- 变量名、方法名 :首字母小写,后面每个单词首字母大写。
- 常量名:全部大写。
5、Java 语言中的关键字
在Java语言中的关键字都是小写的。
class | extends | implements | interface | import |
---|---|---|---|---|
package | break | case | continue | default |
do | if | else | for | return |
swtich | while | fase | turn | null |
boolean | byte | char | short | int |
long | float | double | try | catch |
throw | throws | finally | abstract | fianl |
bnative | private | protected | public | static |
synchronized | transient | volatile | instanceof | new |
super | void | assert | enum | goto |
const |
6、Java 语言中的字面值
字面值是以人可读的语言形式出现的固定值,如数值1就是一个字面值,字面值也称常量,而且大部分字面值和它们的用法都是非常直观的,在Java中字面值可以是任何基本数据类型,每一个字面值都依赖它的数据类型。
7、Java 语言中的变量
在同一个"作用域"中,不能重复定义变量,在不同的作用域中变量名可以相同。
赋值运算符“=”的运算特点是:等号右边先执行,执行完之后赋值给左边的变量。
变量的分类:
- 局部变量:在方法(函数)体中声明的变量叫局部变量,在栈中开辟内存空间。
- 成员变量:在类体内声明的变量叫成员变量或实例变量,在堆中开辟内存空间。
- 静态变量:用 static 修饰的变量,在方法区开辟内存空间。
public class HelloWorld {
int i = 10; //成员变量:类体中的变量
public static void main(String[] args) {
System.out.println("Hello World!");
int i = 20; //局部变量: 方法中变量
System.out.println(i); //输出 20
}
}
8、Java 语言中的数据类型
2.8.1 数据类型分类
Java 语言中数据类型分为两类:
- 基本数据类型
- 引用数据类型:数组、接口、类 等。
基本数据类型包括四大类八小种:
-
第一类:整数型
byte short int long
-
第二类:浮点型
float double
-
第三类:布尔型
boolean
-
第四类:字符型
char
字符串类型不属于基本数据类型,属于"引用数据类型"。
2.8.2 基本数据类型占的字节数和默认值
类型描述 | 关键字 | 字节数 | 取值范围 | 默认值 |
---|---|---|---|---|
字节型 | byte | 1 | -128~127 | 0 |
短整型 | short | 2 | -32768~32767 | 0 |
整型 | int | 4 | -2147483647~2147483648 | 0 |
长整型 | long | 8 | -263~263 -1 | 0 |
单精度 | float | 4 | ± 3.40282374E+38F | 0.0f |
双精度 | double | 8 | ± 1.797693134862311570E+308 | 0.0d |
布尔型 | boolean | 1 | true/false | fasle |
字符型 | char | 2 | 0~65535 | ‘\u0000’ |
2.8.4 基本数据类型在计算机的存储
例如:一个 byte 类型在计算机的存储。
注意:数据在计算机都以补码形式存储。
byte 类型在计算机中最大值是:01111111
byte 类型的数据占一个字节,一个字节占8个比特位,数据有正负之分,一个数字在二进制中表示有符号位,符号位在最左边,0表示正数,1表示负数。
在32位计算机中,可以最多处理4个字节的数据,所以 01111111 存储为:
00000000 00000000 00000000 01111111
在64位计算机中存储,64位计算机最多处理8个字节的数据:
00000000 00000000 00000000 00000000 00000000 00000000 00000000 01111111
其余数据类型在计算机中表示类似,但是 char 字符型在计算机中存储需要字符编码。
char 类型不能直接用二进制表示,char 类型表示的现实生活中的文字,在计算机中存储就需要文字与二进制的对应关系,这种对应关系就叫做"字符编码"。
计算机最初只支持英文,最早的字符编码就是:ASCII 码,文字到二进制是解码,而二进制到文字就是编码。
’ a ’ ------(按照ASCII 解码)---------> 01100001
01100001------(按照ASCII 码编码)------> ’ a ’
2.8.5 字符编码
ASCII 码 | 采用一个字节编码,主要对英文编码 |
---|---|
ISO-8859-1码 | 支持西欧语言,向上兼容ASCII编码,不支持中文 |
GB2312 < GBK < GB18030 | 开始支持中文、日文、韩文等 |
Unicode 码 | 全球统一的编码 |
Unicode 编码的具体体现:
------ UTF - 8
------ UTF - 16
------ UTF - 32
…
Java语言采用的是Unicode编码,即标识符可以采用中文书写。
2.8.6 转义字符
转义字符出现在特殊字符前面,会将特殊字符转换成普通字符。
\n :换行符
\t :制表符
\ ’ :普通单引号
\ \ :普通反斜杠
\ " :普通双引号
public class HelloWorld {
public static void main(String[] args) {
char c = '\\';
System.out.println(c); //输出 \
char a = '\'';
System.out.println(a); //输出 '
char b = '\"';
System.out.println(b); //输出 "
}
}
2.8.7 整数类型
Java 语言中的整数类型字面值默认被做 int 处理,要让这个整数值被当做 long 来处理需要在整数值字面值的后面加上 l/L。
Java 语言中整数类型字面值有三种表示方法:
- 第一种:八进制,以 0 开头
- 第二种:十进制
- 第三种:十六进制,以0x开头
public class DateTypeTest1
{
public static void main(String [] args){
int a = 15; //十进制
int b = 011; //八进制
int c = 0x25; //十六进制
byte e = 10; //正确编译可以通过,虽然10是int类型占4个字节,但可以编译通过
byte f = a / 5; //编译错误,精度损失
long x = 456; //java把 456 当成 int 类型的字面值来处理,但是x 仍然是 long 类型
long y = 2147483648;//出错超过 int 的范围
long z = 2147483648L;//正确
}
}
大容量的数据不能直接赋值给小容量,会出现编译报错,大容量转换成小容量需要强制类型转换,加上强制类型转换符,但是可能会出现精度损失。谨慎使用
public class DateTypeTeat2
{
public static void main(String[] args){
long x = 100L;
// int y = x; //编译报错
int y = (int) x ; // 强制类型转换
long m = 2147483648L;
int n = (int) m; //精度损失变成负数
}
}
long m = 2147483648L;在计算机中存储(补码)为:
00000000 00000000 00000000 00000000 10000000 00000000 00000000 00000000
强制转换成 int 类型在计算机中存储变成:
10000000 00000000 0000000 00000000
这是一个补码,也是一个负数。
2.8.8 浮点类型
浮点数据类型:
- float 单精度【占4个字节】
- double 双精度【占8个字节】
Java语言中还提供了精度更高的引用数据类型是: java.math.BigDecimal
在 java 语言中所有浮点型数据类型字面值,默认被当做 double 类型来处理,想要该字面值被当做 float 类型来处理需要在后面加 f/F。
public class DateTypeTest3
{
public static void main(String[] args){
double d = 3.14; //3.14是double类型,d 也是 double 类型
float f = 3.14F; //定义 float 类型
}
}
2.8.9 布尔类型
布尔数据类型:boolean 【占1个字节】
boolean 数据类型只有两个值:true false
实际存储时 false 在底层是0 ,true 在底层是 1。
public class DateTypeTest4
{
public static viod main(String[] args){
//boolean flag = 1;//编译错误
boolean longinSuccess = true ;//正确
}
}
2.8.10 数据类型互相转换
转换规则:
-
八种基本数据类型除布尔类型之外其余的七种数据类型都可以互相转换。
-
小容量向大容量转换,称为自动类型转换,从小到大排序:
byte < short (char) < int < long < float < double
注意:任何浮点型数据都比整数容量大。
- byte short char 混合运算时,各自转换成 int 类型在做运算。
- 多种数据类型在做混合运算时,先转换成最大的那种数据类型再做运算。
public static void main(String[] args) throws IOException { int a = 97; // 强制类型转化把 int 转换成 char char c = (char) a; System.out.println(c); }}
9、Java 语言中的运算符
2.9.1 运算符的分类
- 算数运算符
- 关系运算符
- 逻辑运算符
- 位运算符
- 字符串连接运算符
- 三元运算符
- 赋值运算符
2.9.2 算数运算符
- 求和 +
- 相减 -
- 乘积 *
- 商 /
- 求余 【取模】 %
2.9.3 关系运算符
- 大于 >
- 大于等于 >=
- 小于 <
- 小于等于 <=
- 不等于 !=
- 等于 ==
关系运算符的结果一定是布尔类型:true/false
2.9.4 逻辑运算符
- 逻辑与 & 【两边都是 true,结果才是 true】
- 逻辑或 | 【两边有一个是 true,结果就是 true】
- 逻辑非 ! 【取反】
- 逻辑异或 ^ 【两边只要不一样就是 true】
- 短路与 &&
- 短路或 ||
1、逻辑运算符要求两边的数据都是布尔类型,并且逻辑运符最终结果也是一个布尔类型。
2、短路与和逻辑与的结果一样,只不过会发生短路现象。
3、短路或和逻辑或的结果一样,只不过会发生短路现象。
短路与和逻辑与的区别?
- 短路与, 从第一个操作数推断结果,只要有一个为fase,不再计算第二个操作数。
- 逻辑与,两个操作数都计算。
短路或与逻辑或的区别?
- 短路或, 从第一个操作数推断结果,只要有一个为true,不再计算第二个操作数。
- 逻辑或,两个操作数都计算。
短路运算更加智能,由于后面的语句不执行,所以短语运算执行效率更高,短路运算更加常用。
2.9.5 赋值运算符
赋值运算符分为两类:基本运算符和扩展运算符。
基本运算符:
- 基本赋值运算符: =
扩展运算符:
- +=
- -=
- *=
- /=
- %=
public class OperatorTest1{ public static void main(Sting[] args){ byte i = 10; i += 5; //等同于: i = (byte)(i+5) } }
**扩展的赋值运算符不改变运算结果的类型,**假设最初这个变量的类型是 byte 类型,无论怎么追加或追减,最终的结果还是 byte 类型 ,可能会损失精度。
2.9.6 字符串连接符
关于 java 语言中的 “+” 运算符:
1、"+" 运算符的作用:
- 加法运算:两边都是数字的话,一定是加法求和运算。
- 字符串连接:两边只要有一个是字符串,就一定会进行字符串连接运算,结果还是字符串。
2、优先级:
在一个表达式有好几个加号,没有小括号的前提下,从左向右进行运算。
public class Operator2{ public static void main(Sting[] args){ int a = 10; int b = 20; System.out.println(a + b); //求和 System.out.println("10 + 20 = " + a + b ); //"10 + 20 = 1020" //都是字符串 System,out.println(a + "+" + b + " = " + ( a + b ))// a + b = 30 }}
2.9.7 三目运算符
三目运算符的语法规则:
布尔表达式 ? 表达式 1 : 表达式 2
当布尔表达式的结果是 true 时,选择表达式 1 执行的结果作为该式子的值;当布尔表达式是 false 时,选择表达式 2 执行的结果作为该式子的值。
public class OperatorTeat3{ public static void main(String[] args){ boolean sex = true; char c = sex ? '男' : '女'; System.out.println(c); //男 // char c1 = sex ? "男" : '女'; // 语法错误 System.out.println(sex ? "男" : '女'); //正确 输出男 }}
10、Java 语言中的流程控制语句
2.10.1 if 语句
if 语句的四种类型:
第一种:
if (条件表达式){ 执行语句;}
第二种:
if (条件表达式){ 执行语句;}else (条件表达式){ 执行语句;}
第三种:
if (条件表达式){ 执行语句;}else if (条件表达式){ 执行语句;}......
第四种:
if (条件表达式){ 执行语句;}else if (条件表达式){ 执行语句;}else if (条件表达式){ 执行语句;}......else { 执行语句;}
2.10.2 switch 语句
switch (int 或 String 类型的字面值或者变量){ case int 或 String 类型的字面值或者变量 : java 语句; break; case int 或 String 类型的字面值或者变量 : java 语句; break; case int 或 String 类型的字面值或者变量 : java 语句; break; ....... default : java 语句;}
public class SwitchTest{ long x = 100L; switch( x ) //编译错误,精度会损失 { }}
2.10.3 for 语句
for ( 初始化表达式 ; 布尔表达式 ; 更新表达式 ){}
// 打印九九乘法表public class HelloWorld{ public static void main(String[] args){ for(int i = 1;i <= 9;i++){ for(int j = 1;j <= i;j++){ System.out.print(j + " * " + i + " = " + j*i + " "); } System.out.println(); } }}
找到100以内的素数,每打印8个换一行public class HelloWorld{ public static void main(String[] args) { int count = 0; for(int i =2;i<100;i++) { boolean isSuShu = true;// 是素数 for (int j = 2; j < i; j++) { if (i % j == 0) { //余数等于0证明不是素数 isSuShu = false; break; } } if(isSuShu) { count++; System.out.print(i + " "); if(count == 8) //每打印8个数换一行 { System.out.println(); count = 0; } } } }}
2.10.4 while 语句
while (布尔表达式){ 循环语句;}
2.10.5 do - while 语句
do{ 循环语句;}while(布尔表达式);
11、Java语言中的方法
2.11.1 方法的本质
方法就是一段独立的代码片段,并且可以完成某个独立的功能,可以重复使用。
方法的英语单词:Method
方法也叫函数:Function
方法定义在类体当中,一个类中可以定义多个方法,方法定义的位置随意。
2.11.2 方法的定义
Java语言中方法的定义:
修饰符列表 返回值类型 方法名(形式参数列表){}
2.11.3 修饰符列表
2.11.4 方法的调用
方法的修饰符列表有 static 关键字调用:类名 . 方法名(实际参数列表);
有方法名有static关键字 类名. 可以省略不写。注意:两个方法在同一个类中。
2.11.5 返回值类型
Java 语言中要具体指定返回值的类型,返回值类型包括基本数据类型和引用数据类型,没有返回值,要写上 void。
返回值类型若不是 void ,表示这个方法执行之后必须返回一个具体的值,否则编译器报错。返回的数据必须和返回值类型保持一致。
可以编写 return; 这样的语句,强行终止方法。
//以下程序会出错,缺少返回语句public class MethodTest { public static void main(String[] args) { MethodTest.add(10,20); } public static int add(int a,int b) { a = 10; b = 20; if(a < b ){ //条件成立时执行返回语句,条件不成立时缺少返回语句 return a; } }}
2.11.6 方法在JVM中的内存分配
- 方法只定义,不调用,在JVM中不会分配运行所属的内存空间。
- 在JVM中的三块主要内存空间:方法区内存 堆内存 栈内存
先加载类到方法区内存,Java虚拟机调用main()方法,main()方法所属的内存空间在栈内存分配,【局部变量也在栈中分配内存】,接着main()中调用的其他方法,其他方法在栈中分配内存空间。一直运行到最后调用的一个方法碰到 return 语句,逐一返回值。
凡是 new 出来的对象都在堆内存中存放。
2.11.7 方法的重载
方法重载机制/Overload 的条件:
- 方法名相同
- 方法的参数类型,个数,顺序至少有一个不同
- 方法的返回类型可以不同(不依靠返回类型来区分重载)
- 方法的修饰符可以不同,因为方法重载和修饰符没有任何关系
- 方法重载只出现在同一个类中
public class OverloadTest{ public static void main(Sting[] args){ //方法重载调用 add(10); add(100,200); add(100L); add(3.14); } //方法重载 public static int add(int a) { return a; } public static int add(int a,int b) { return a+b; } public static long add(long a) { return a; } public static double add(double a) { return a; }}
2.11.8 方法递归
方法递归:指的是方法自己调用自身。
递归很耗费栈内存,每次方法调用一次自身就在栈内存开辟一次内存空间。发生栈溢出错误时,JVM就停止工作,所以递归必须有终止条件。
//递归求和public class RecursionTest{ public static void main(String[] args) { int num = sum(100); System.out.println(num); } public static int sum(int n) { if(n == 1 ) { return 1; } return n + sum( n - 1 ); }}
三、面向对象
1、面向过程和面向对象
3.1.1 面向过程
面向过程最重要的是每一步与每一步的因果关系和注重步骤,注重的是实现这个功能的步骤,另外面向过程也注重实现功能的因果关系。
3.1.2 面向对象
面向对象就是将现实世界分割成不同的单元,然后每一个单元都实现成对象,然后给一个环境驱动一下,让各个对象之间协作起来形成一个系统。
3.1.3 面向对象的术语
- OOA: 面向对象分析
- OOD: 面向对象设计
- OOP: 面向对象编程
整个软件开发的过程,都是采用OO进行贯穿的。
实现一个软件的过程:分析(A) --> 设计(D) --> 编程§
3.1.4 面向对象的三大特征
-
封装
-
继承
-
多态
2、类和对象
3.2.1 类和对象的概念
类:类实际上在现实世界当中是不存在的,是一个抽象的概念,是一个模板。类本质上是现实世界当中某些事物具有共同特征,将这些共同特征提取出来形成的概念就是个“类”,“类”就是一个模板。
对象:对象是实际存在的个体。(真实存在的个体)
在java语言中,要想得到“对象”,必须先定义“类”,“对象”是通过“类”这个模板创造出来的。
类就是一个模板:类中描述的是所有对象的“共同特征信息”,对象就是通过类创建出的个体。
3.2.2 相关术语
类:不存在的,人类大脑思考总结一个模板(这个模板当中描述了共同特征。)
对象:实际存在的个体。
实例:对象还有另一个名字叫做实例。
实例化:通过类这个模板创建对象的过程,叫做:实例化。
抽象:多个对象具有共同特征,进行思考总结抽取共同特征的过程。
类 --【实例化】–> 对象(实例)
对象 --【抽象】–> 类
类 = 属性 + 方法
属性来源于:状态
方法来源于:动作
3.3.3 类的定义
定义一个类的语法格式:
[修饰符列表] class 类名 { //类体 = 属性 + 方法 // 属性在代码上以“变量”的形式存在(描述状态) // 方法描述动作/行为 }
注意:修饰符列表可以省略。
变量根据出现的位置分类为:
- 在方法体中声明的变量:局部变量。
- 在方法体外声明的变量:成员变量。(这里的成员变量就是“属性”)
成员变量没有手动赋值时,系统会默认赋值。
//创建一个学生类public class Student{ // 三个属性 (成员变量) String name; int age; char sex; }
3.3.4 创建对象
//创建一个对象public class StudentTest{ public static void main(String[] args){ Student s1 = new Student(); //创建一个对象使用 new 运算符,s1 是局部变量 /* new Student(); 会在堆区开辟内存空间,即:String name;int age;char sex;会在堆中存 s1 是局部变量,会在栈区开辟内存空间。 不能通过类名访问类中的属性,例如:Student.name;//错误 */ }}
s1 是一个变量名,类型是 Student ,引用类型。s1 不是对象,s1 是引用。s1 在栈区存储指向了在堆内存中存放的对象地址。new Student 是对象,每次使用 new 运算符都在堆区重新开辟内存空间。
文件 Student.java 和 StudentTest.java 两个文件必须在一个目录下。
3.3.5 访问属性
public class Student{ // 三个属性 String name; int age; char sex; public static void main(String[] args){ Student s1 = new Student(); //访问属性 s1.name = "张三"; s1.age = 20; s1.sex = '男';}}
3.3.6 空指针异常
public class NullPointerTest{ public static void main(String[] args){ Customer c = new Customer(); c.id = 1001; c = null; //null 小写 System.out.print(c.id); //空指针异常 }}class Customer{ int id;}
3.3.7 方法调用时参数调用问题
//基本数据类型传值public class Test { public static void main(String[] args) { int i =10; add(i); System.out.println(i); // 10 } public static void add(int i ){ i++; System.out.println(i); // 11 }}/*main()方法中的 i 和 add() 方法中的 i 是两个 i ,当执行到 add(i) 时,会把 main() 方法中的 i = 10;再复制一份,把 i = 10;传入add()函数中,当执行完add()方法后 ,就会释放 add() 方法占的内存空间,接着执行 main() 方法中剩下的代码。 */
//引用数据类型传地址public class Test { public static void main(String[] args) { Person p = new Person(); p.age = 20; add(p); System.out.println(p.age); // 21 } public static void add(Person p){ p.age++; System.out.println(p.age); // 21 }}class Person{ int age;}
3、构造方法
3.3.1 什么是构造方法
构造方法就是一个比较特殊的方法,通过构造方法可以完成对象的创建,以及实例变量的初始化,即构造方法是用来创建对象的,对属性进行默认赋值。
当一个类没有提供任何构造方法,系统会默认提供一个无参的构造方法。这个无参数的构造方法叫缺省构造器。
当一个类自定义了构造方法,系统就不会提供无参数的构造方法。
构造方法是支持方法重载的,在一个类当中构造方法可以有多个。并且所有的构造方法名字都是一样的。
3.3.2 构造方法的调用
使用 new 运算符来调用构造方法。
语法格式:
new 构造方法名;
3.3.4 构造方法的语法结构
【修饰符列表】 构造方法名 (形式参数列表){ 构造方法体;}
- 构造方法名和类名一值
- 构造方法不需要指定返回值类型,也不能写 void
public class MethodTest { public static void main(String[] args) { new Person(); //无参数的构造方法调用 new Person(10);//有参数的构造方法调用 Person p = new Person(); //无参数的构造方法也会调用 }}class Person{ int age; public Person(){ //无参数的构造方法 System.out.println("无参数的构造方法"); } public Person(int i){ //无参数的构造方法 System.out.println("有参数的构造方法"); }}
无论是有参数的构造方法还是无参数的构造方法都会给属性赋值。在创建对象的时候就会给属性赋值。
3.3.5 用构造方法给属性赋值
public class MethodTest { public static void main(String[] args) { Person p = new Person("张三",20); //调用构造方法 }}class Person{ String name; int age; public Person(String n,int a){ name = n; age = a; System.out.println("有参数的构造方法给属性赋值"); }}
四、三大特征
1、封装
4.1.1 封装的作用
封装的作用有两个:
第一个作用:保证内部结构的安全。
第二个作用:屏蔽复杂,暴露简单。
在代码级别上,封装有什么用?
一个类体当中的数据,假设封装之后,对于代码的调用人员来说,不需要关心代码的复杂实现,只需要通过一个简单的入口就可以访问了。另外,类体中安全级别较高的数据封装起来,外部人员不能随意访问,来保证数据的安全性。
4.1.2 封装的步骤
第一步:属性私有化(使用private关键字进行修饰。)
第二步:对外提供简单的操作入口。
4.1.3 有 static 和没有 static 的方法的调用
没有 static 的方法也被称为实例方法(或对象方法),必须通过对象.来调用
public class Test { public static void main(String[] args){ add(); //没有static 方法的调用 Test.add(); //或者类名. 来调用 Test t = new Test(); t.subtract(); //必须通过对象. 来调用 } //带有static的方法,表示类级别的行为 public static void add(){ System.out.println("有static的方法"); } //没有 static 的方法 也被称为实例方法(对象方法,对象的行为) public void subtract(){ System.out.println("没有static的方法"); }}
4.1.4 get 和 set 方法
1个属性对外提供两个set和get方法。外部程序只能通过set方法修改,只能通过get方法读取,***set和get方法都是实例方法,不能带static。不带static的方法称为实例方法,实例方法的调用必须先new对象。***
set和get方法写的时候有严格的规范要求:
set方法长这个样子: public void set+属性名首字母大写(1个参数){ //限制语句; xxx = 1个参数; }get方法长这个样子: public 返回值类型 get+属性名首字母大写(无参){ return xxx; }
4.1.5 封装的实现
第一步:属性私有化。
第二步:1个属性对外提供两个set和get方法。外部程序只能通过set方法修改,只能通过get方法读取,可以在set方法中设立关卡来保证数据的安全性。
public class Person { private int age; //属性私有化 //set 方法 public void setAge(int a){ if( a < 0 ){ //限制语句 System.put.println("年龄不合法!"); return; } age = a; } //get 方法 public int getAge(){ return age; }}
public class Test { public static void main(String[] args){ Person p = new Person(); System.out.println(p.getAge()); //输出0 p.setAge(20); System.out.println(p.getAge()); //输出20 }}
4.1.6 static 关键字
static修饰的统一都是静态的,都是类相关的,不需要new对象。直接采用“类名.”访问
-
加 static 关键字的变量叫:静态变量
-
加 static 关键字的方法叫:静态方法
静态变量在类加载的时候就会赋默认值。(没有自定义参数的情况会赋默认值)
没有 static 关键字的变量叫实例变量,没有 static 的方法叫实例方法。采用对象访问。
public class Test { public static void main(String[] args){ Person1 p = new Person1(); //类名. 调用静态 Person1.b = 10; Person1.struct(); //对象. 调用实例 p.a = 20; p.add(); }}class Person1{ //实例 int a; public void add(){ System.out.println("实例!"); } //静态 static int b; public static void struct(){ System.out.println("静态!"); }}
4.1.6.1 静态代码块
静态代码块语法:
static{java 语句;}
static 代码块的执行:
静态代码块和静态变量都在类加载的时候执行,并且只执行一次,注意:在main()方法执行前面执行,从上往下执行。
静态代码块的作用:可以编写日志,记录时间。
public class Test { // 一个类可以写多个静态代码块 static { System.out.println(" 静态代码块1!"); } static { System.out.println(" 静态代码块2!"); } static { System.out.println(" 静态代码块3!"); } public static void main(String[] args){ }}
静态代码块可以访问静态变量,不能访问实例变量。
public class Test { // 静态代码块可以访问静态成员 static int i = 10; int j = 20; static { System.out.println("i = " + i); //可以访问 i = 10 //System.out.println("j = " + j); //不能访问 j } public static void main(String[] args){ }}
4.1.7 this 关键字
this 实际上是一个变量,是一个引用。保存当前对象的内存地址,保存在堆区,指向自身。即:this 就代表了当前对象,一个对象就有一个 this。
this 只能使用在实例方法中,谁调用实例方法,this就指向谁。this 不能用在静态方法中。
4.1.7.1 this 关键字的使用
在使用构造方法进行赋值时使用 this 关键字,区分实例变量和局部变量。
public class Test { public static void main(String[] args){ //调用无参数的构造方法 Student stu1 = new Student(); stu1.setName("李四"); stu1.setNum(1001); System.out.println(stu1.getName()); System.out.println(stu1.getNum()); //调用有参数的构造方法 Student stu2 = new Student("张三",1002); System.out.println(stu2.getName()); System.out.println(stu2.getNum()); }}class Student{ private String name; private int num; //没有参数的构造方法 Student(){ } //有参数的构造方法 Student(String name,int num){ this.name = name; this.num = num; } // get 和 set 方法 public void setName(String name){ this.name = name; } public String getName(){ return name; } public void setNum(int num){ this.num = num; } public int getNum(){ return num; }}
4.1.7.2 通过 this 关键字调用构造方法
this(参数列表); 这种写法必须在用一个类中,并且必须写在第一行。
public class Test { public static void main(String[] args){ Date date1 = new Date(); date1.show(); Date date2 = new Date(2021,2,7); date2.show(); }}class Date{ private int years; private int mouth; private int days; //默认的无参数构造方法打印1970年1月1日 public Date(){ //必须写在第一行 this(1970,1,1); //调用有参数的构造方法 } //有参数的构造方法 public Date(int years,int mouth,int days){ this.years = years; this.mouth = mouth; this.days = days; } public void show(){ System.out.println(this.years+"年"+this.mouth+"月"+this.days+"日"); } public void setYears(int years){ this.years = years; } public int getYears(){ return years; } public void setMouth(int mouth){ this.mouth = mouth; } public int getMouth(){ return mouth; } public void setDays(int days){ this.days = days; } public int getDays(){ return days; }}
2、继承
4.2.1 继承的作用
继承的作用:
基本作用:子类继承父类,代码可以得到复用。(这个不是重要的作用,是基本作用。)
主要(重要)作用:因为有了继承关系,才有了后期的方法覆盖和多态机制。
关键字:extcends
4.2.2 继承的相关特性
① B类继承A类,则称A类为超类(super class)、父类、基类, B类则称为子类(subclass)、派生类、扩展类。 class A{} class B extends A{} 我们平时聊天说的比较多的是:父类和子类。 superclass 父类 subclass 子类
② java 中的继承只支持单继承,不支持多继承,C++中支持多继承, 这也是 java 体现简单性的一点,换句话说,java 中不允许这样写代码: class B extends A,C{ } 这是错误的。③ 虽然 java 中不支持多继承,但有的时候会产生间接继承的效果, 例如:class C extends B,class B extends A,也就是说,C 直接继承 B, 其实 C 还间接继承 A。④ java 中规定,子类继承父类,除构造方法不能继承之外,剩下都可以继承。 但是私有的属性无法在子类中直接访问。(父类中private修饰的不能在子类中 直接访问。可以通过间接的手段来访问。)⑤ java 中的类没有显示的继承任何类,则默认继承 Object类,Object类是 java 语言提供的根类(老祖宗类),也就是说,一个对象与生俱来就有 Object类型中所有的特征。⑥ 继承也存在一些缺点,例如:CreditAccount 类继承 Account 类会导致它 们之间的耦合度非常高,Account 类发生改变之后会马上影响到 CreditAccount 类
4.2.3 子类对象调用父类方法
public class Test { public static void main(String[] args){ Dog dog = new Dog(); dog.name = "狗"; dog.show(); //子类调用父类方法 }}class Animal{ String name = "动物"; public void show(){ System.out.println(this.name); }}class Dog extends Animal{ //子类继承父类}
4.2.4 println()方法的解释
System.out.println("HelloWorld!");
解释:
- out 是一个变量名。
- System 是一个系统类。
- println(); 是一个方法。
4.2.5 Object 类提供的方法
\jdk\lib\src\java.base\java\lang
Object.java 文件提供了方法。
3、多态
4.3.1 方法覆盖
子类继承父类之后,有一些方法不需要改进,但是有一些方法不能满足子类的业务需求,就需要对方法进行修改。方法覆盖也叫方法重写,override。
注意:方法覆盖不是方法重载。
方法覆盖:把父类的方法直接复制到子类中,修改方法体,其余不建议修改。
public class Test { public static void main(String[] args){ Dog dog = new Dog(); dog.show(); //覆盖了父类的方法,调用自己的方法 }}class Animal{ String name = "动物"; public void show(){ System.out.println("这是动物"); }}class Dog extends Animal{ public void show(){ //直接复制父类的方法,只修改方法体,覆盖父类方法 System.out.println("这是狗"); }}
重写的方法和之前的方法具有:
- 相同的返回值类型
- 相同的方法名
- 相同的参数列表
注意:重写之后方法的修饰符列表不能更低,可以更高。
重写的方法不能比之前的方法抛出更多的异常,可以更少。
①方法覆盖只是针对于方法,和属性无关。
②私有方法无法覆盖。
③构造方法不能被继承,构造方法也不能被覆盖。
④方法覆盖只是针对实例方法,静态方法覆盖没有意义。
4.3.2 多态的基础语法
Java语法中支持向上转型和向下转型,但是必须有继承关系。
向上转型:子 ---------> 父
父亲的引用指向子类的对象。
向下转型:父 ---------> 子
什么时候需要向下转型:访问子类中特有的方法,就需要向下转型。
多态:编译时一种形态,运行时一种形态。
public class Test { public static void main(String[] args){ //向上转型 Animal a1 = new Dog(); //父类的引用指向子类的对象 nwe Dog();是对象 a1.show(); //输出 这是狗 //a1.Run(); //错误,在Animal中找不到Run();方法 Dog dog = (Dog) a1; //向下转型 ,是一种强制类型转换 dog.Run(); }}class Animal{ public void show(){ System.out.println("这是动物"); }}class Dog extends Animal{ public void show(){ System.out.println("这是狗"); } public void Run(){ System.out.println("跑"); }}
向下转型带来的问题:向下转型实际上是一种强制类型转换,容易出现 java.lang.ClassCastExceptoin 类型转换异常。
为了避免类型转换异常需要使用运算符:instanceof
- instanceof 可以在运行阶段动态的判断指向对象的类型。
- instanceof 的运算结果只能是:true/false
- 语法: 引用 instanceof 类型
public class Test { public static void main(String[] args){ //父类的引用指向子类的对象 nwe Dog();是对象 Animal a1 = new Dog(); a1.show(); //输出 这是狗 //a1.Run(); //错误,在Animal中找不到Run();方法 if(a1 instanceof Dog){ //向下转型需要insctanceof Dog dog = (Dog)a1; dog.Run(); } }}class Animal{ public void show() { System.out.println("这是动物"); }}class Dog extends Animal{ @Override public void show(){ System.out.println("这是狗"); } public void Run() { System.out.println("跑"); }}
4.3.3 多态在开发的作用
软件开发的最基本原则:开闭原则
开闭原则:对扩展开放(可以额外的添加),对修改关闭(最好很少的修改的现有的程序)。在软件的扩展过程中,修改的越少越好。
多态在开发中的作用:降低程序的耦合度,提高程序的扩展能力。
// 面向抽象编程public class Test { public static void main(String[] args) { Master master = new Master(); //降低耦合度 Dog dog = new Dog(); master.WeiYang(dog); Cat cat = new Cat(); master.WeiYang(cat); }}class Master{ public void WeiYang(Animal animal){ animal.eat(); }}class Animal{ public void eat(){ }}class Dog extends Animal{ public void eat(){ System.out.println("狗在吃骨头!"); }}class Cat extends Animal{ public void eat(){ System.out.println("猫在吃小鱼!"); }}
五、进阶部分
1、super
5.1.1 super 概述
super 是一个关键字,super 能出现在实例方法和构造方法中,不能使用在静态方法中。
super 的语法还是:super. super()
super 代表的是"当前对象父类型特征’’。
super 不是引用,也不保存内存地址,也不指向任何对象。
super 只能出现在构造方法中的第一行,通过当前的构造方法区调用"父类"中的构造方法,目的是:创建子类对象的时候的,先初始化父类型的特征。
重要的结论:在一个构造方法的第一行,即没有 this() 又没有 super() 时,默认会有一个super(),表示通过当前子类调用的构造方法调用父类的无参数构造方法,所以必须保证父类的无参数构造方法存在。 父类的构造方法无论怎么样都会先执行。
注意:this() 和 super() 只能有一个出现在构造函数的的第一个行。
/*以下程序输出:A 类中的无参数构造方法B 类中的无参数构造方法*/public class Test { public static void main(String[] args) { B b = new B(); }}class A{ public A( ){ //这里默认有一个 super();调用Object类的构造方法 System.out.println("A 类中的无参数构造方法"); }}class B extends A{ public B(){ //这里默认有一个 super();调用A中的构造方法 System.out.println("B 类中的无参数构造方法"); }}
5.1.2 super(实参)用法
父类中的私有属性继承到子类中,在子类就不能访问父类的私有属性。可以使用 super() 传递实参,调用父类的构造方法进行赋值。
public class Test { public static void main(String[] args) { Man man1 = new Man(); System.out.println(man1.getSex()+man1.getName()+man1.getAge()); Man man2 = new Man("张三",20,"男"); System.out.println(man2.getName()+man2.getAge()+man2.getSex()); }}class Person{ private String name; private int age; Person(){ } Person(String name,int age){ this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}class Man extends Person{ private String sex; Man(){ } /* 父类私有方法不能访问会报错 Man(String sex,String name,int age){ this.sex = sex; this.name = name; this.age = age; } */ Man(String name,int age,String sex ){ super (name,age); //通过super()传递实参调用父类的构造方法 this.sex = sex; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; }}
5.1.3 this 和 super
如果父类和子类中有同名属性,在子类中想访问父类的属性就必须有 super. 访问,this. 访问的是当前对象的属性。
public class Test { public static void main(String[] args) { Man man = new Man("张三"); man.show(); }}class Person{ String name; Person(){} Person(String name){ this.name = name; }}class Man extends Person{ String name; Man(){} Man(String name){ super(name); //调用父类的构造方法 //这里默认会给 Man 类中的 name = null } public void show(){ System.out.println(this.name); //输出null 表示输出当前对象的name System.out.println(super.name);//输出张三 表示输出父类的name System.out.println(name); //输出null 等价于 this.name }}
5.1.4 super 调用父类方法
在父类和子类中有相同名称的属性和方法,在子类访问父类的属性和方法就需要 super.
public class Test { public static void main(String[] args) { Dog dog = new Dog(); dog.show(); }}class Animal{ public void move(){ System.out.println("Animal 移动!"); }}class Dog extends Animal{ //方法重写 public void move(){ System.out.println("Dog 移动"); } public void show(){ this.move();//访问子类的方法 super.move();//访问父类的方法 }}
2、final 关键字
5.2.1 final 关键字的含义
- final 表示最终的,不可变的。
- 采用final修饰的类不能被继承。
- 采用final修饰的方法不能被覆盖(重写)。
- 采用final修饰的变量不能被修改。
- final修饰的变量必须显示初始化。
- 如果修饰的引用,那么这个引用只能指向一个对象,也就是说这个引用不能再次赋值,但被指向的对象是可以修改的,但是对象内部的数据可以被修改。
- 构造方法不能被final修饰。
- 会影响JAVA类的初始化:final定义的静态常量调用时不会执行java的类初始化方法,也就是说不会执行static代码块等相关语句,这是由java虚拟机规定的。
public class Test { public static void main(String[] args) { final int a = 10; // a = 20; 报错 final Cat cat = new Cat(); //cat = new Cat(); 报错 }}final class Animal{ //没有子孙 public void show(){ System.out.println("这个类不能在被继承"); }}class Cat{ public final void eat(){ System.out.println("这个方法在继承的类中不能被重写"); }}
5.2.2 final 修饰实例变量
final 修饰实例变量,必须手动赋值,不能采用程序的默认值。
public class Test { public static void main(String[] args) { }} class Animal{ final int age = 20; //必须赋值}
在构造方法中赋值也可以:
public class Test { public static void main(String[] args) { }} class Animal{ final int age; public Animal(){ this.age = 20; //在构造方法中赋值也可以 }}
5.2.3 常量
实例变量每次在堆中开辟内存,每 new 一个对象就会开辟一次空间,可以用 final 修饰实例变量,不用每次开辟内存。
常量:static final 联合起来修饰变量称为常量。
常量的值不能被改变,常量和静态变量都是储存在方法区,并且都是在类加载的时候的初始化。常量名称要求全部大写,并且单词之间用下划线隔开。
public class Test { public static void main(String[] args) { }} class Preson{ //常量 static final String COUNTRY = "中国"; }
3、抽象类
5.3.1 什么是抽象类
类和类之间有共同的特征,将这些具有共同特征的类再进一步抽象形成抽象类。由于类本身是不存在的,所以抽象类不能创建对象。
抽象类属于基本数据类型。
5.3.2 定义抽象类
语法:
[修饰符列表] abstract class 类名{ 类体;}
抽象的类存在就是要被继承的。final 和 abstract 不能同时出现。
抽象类的子类也可以是抽象类,抽象类可以有方法,方法可以被子类继承。
public class Test { public static void main(String[] args) { // Account act = new Account() ; 不能创建对象 }}abstract class Account{}abstract class CreditAccount extends Account{ //继承之后在抽象也可以}
5.3.3 抽象方法
抽象类中定义抽象方法,抽象方法是没有具体实现的方法,只是进行了声明。
在抽象类中可以定义非抽象方法,也可以定义抽象方法,但是抽象方法只能定义在抽象类中。
public class Test { public static void main(String[] args) { }}abstract class Account{ public void show(){ //定义非抽象方法 } public abstract void player(); //定义抽象方法}
5.3.4 抽象方法的实现
抽象类中的抽象方法必须在继承的类中实现,但是继承的子类如果是抽象的可以不实现。
public class Test { public static void main(String[] args) { //向上转型 使用多态,面向抽象编程 Animal animal = new Dog(); animal.name(); }}abstract class Animal{ //抽象方法 public abstract void name();}class Dog extends Animal{ //必须将抽象类中的抽象方法实现 public void name(){ System.out.println("这是子类实现抽象方法"); } }
4、接口
5.4.1 接口的基础语法
- 接口也是一种引用数据类型。
- 接口是完全抽象的。
- 接口编译之后也是一个 class 字节码文件。
- 接口之间可以继承,支持多继承。
- 接口中只能包含两部分:常量和抽象方法。
- 接口中所有的元素都是:public
- 接口中定义抽象方法 public abstract 可以省略。
- 接口中定义常量 public static final 可以省略。
// 定义的语法[修饰符列表] interface 类名{}
public class Test { public static void main(String[] args) { }}interface A{ // 常量 public static final double PI = 3.1415926; //省略 public static final double PI2 = 3.14; // 抽象方法 public abstract void show(); // 省略 public abstract int add();}interface B{}//多继承interface C extends A,B{}
5.4.2 接口在开发中的作用
- 类和类之间是继承(extends)。
- 类和接口之间叫实现(implements)。
- 当一个类实现接口中的方法时,必须将接口中的所有抽象方法进行重写。
public class Test { public static void main(String[] args) { Math math = new MyMath(); System.out.println(math.sub(20,10)); System.out.println(math.sum(20,10)); System.out.println(math.PI); }}interface Math{ double PI = 3.14; int sum(int a,int b); int sub(int a,int b);}// 接口到类叫实现class MyMath implements Math{ //重写方法 public int sum(int a,int b){ return a + b; } public int sub(int a,int b){ return a - b; }}
5.4.3 一个类实现多个接口
public class Test { public static void main(String[] args) { //使用多态 A a = new D(); B b = new D(); C c = new D(); }}interface Math{ double PI = 3.14; int sum(int a,int b); int sub(int a,int b);}// 一个类实现多个接口interface A{ void m1();}interface B{ void m2();}interface C{ void m3();}//一个类实现多个接口,类似与多继承class D implements A,B,C{ public void m1(){ } public void m2(){ } public void m3(){ }}
5、包机制
5.5.1 package
package 是 java 中的包机制。包机制的作用是为了方便程序的管理,不同功能的类放在了不同的包下。(按照功能划分,不同的软件包具有不同的功能。)
package 用法:
- package 是一个关键字后面加包名。
- package 只能出现在 java 源程序的第一行。
包名命名规范:一般按照公司域名倒序的方式(因为公司域名具有唯一性。)
命名方式:公司域名倒序 + 项目名 + 模块名 + 功能名
5.5.2 import
import 可以将需要的包进行导入。当两个类不在同一个包下时,在一个类中需要另外一个类就需要 import 进行导包。同一个包下不需要导包。
java.lang.*; 这个包底下的文件不需要导入。 例如:System,String
import 语句只能出现在 package 语句的下面,class 声明语句的上面。import 语句可以使用 * 的的方式。
5.5.3 Scanner
不进行导包进行输入:
package com.xingjunjun.javase;public class Test{ public static void main(String[] args){ // java.unil 是 Scanner 类的包名 java.util.Scanner s = new java.util.Scanner(System.in); String str = s.next(); System.out.println("您输入的字符串是:"+ str); }}
导包进行输入:
import java.util.Scanner;public class Test{ public static void main(String[] args){ Scanner s = new Scanner(System.in); String str = s.next(); System.out.println("您输入的字符串是:"+ str); }}
6、访问控制权限
5.6.1 访问控制权限的种类
- private 私有
- protected 受保护的
- public 公开
- 默认
5.6.2 private
private 修饰的元素只能在本类里面进行访问,出了类就不能访问。
5.6.3 protected
protected 修饰的元素只能在本类、同一个包和子类中访问。
5.6.4 public
任意位置都可以访问。
5.6.5 默认
默认修饰的元素可以在本类和同包中访问。
5.6.6 访问控制权限修饰的元素
- 属性(4个都可以用)
- 方法(4个都可以用)
- 类 (只能用 public 和默认)
- 接口(只能用 public 和默认)
7、object 类
5.7.1 object 类介绍
object 类是所有类的祖宗类,任何类都继承 object 类。
5.7.2 object 类常用方法
查找方法:源代码查询和 API 帮助文档。
5.7.3 toString 方法
public String toString(){ return getClass().getName + "@" + Integer.toHexString(hashCode());}
toString() 方法设计的目的是:通过调用这个方法可以将一个 java 对象转换成字符串形式。建议子类都从写此方法。
5.7.4 equals 方法
public boolean equals (object obj){ return (this == obj);}
equal () 方法是判断两个对象是否相等。判断两个对象是否相等不能使用" == “,”=="判断的是两个对象的内存地址是否相等,每次 new 对象都是不同的内存地址。子类需要重写 equals
5.7.5 finalize 方法
protected void finalize () throws Throwable{}
当一个 java 对象即将被垃圾回收器回收时,JVM 垃圾回收器自动调用这个方法。finalize()方法可以重写。
8、内部类
在类的内部又定义一个类,称为内部类。
5.8.1 内部类的分类
- 静态内部类:类似于静态变量
- 实例内部类:类似于实例变量
- 局部内部类:类似于局部变量
使用内部类代码的可读性很差,应减少使用。
5.8.2 匿名内部类
匿名内部类是局部内部类的一种,因为这个类没有名字所以叫匿名内部类。
六、数组
1、数组
-
Java 语言中的数组是引用数据类型,数组的父类是 object。数组实际上一个容器,可以同时存放多个元素,是一个数据的集合。
-
数组中可以存放基本数据类型的数据,也可以存放引用数据类型的数据。数组可以 new 对象,数组对象存放在堆中。
-
数组中如果存储的是"java对象",实际上存放的是对象的"引用"(内存地址)
-
在 java 语言中规定,数组一旦创建,长度就不可以改变。
-
所有的数组对象都有 length 属性,用来获取数组元素的个数。
-
数组中元素的内存地址都是连续的。
数组的优点:
- 数组检索效率很高。数组中查找元素不会一个一个找,而是通过数学表达式计算出来的。计算的是内存地址。
数组的缺点:
- 随机增删元素,效率较低。增删元素会导致剩余元素先前或向后移动。
- 数组不适合存放大量的数据。很难找到一块很大的内存空间。
2、一维数组
6.2.1 声明一维数组
package com.JavaSE.array;public class ArrayTest { public static void main(String[] args) { // 一维数组的声明 int[] array1; double[] array2; boolean[] array3; String[] array4; Object[] array5; }}
6.2.2 初始化一维数组
一维数组的初始化包括:静态初始化和动态初始化。
package com.JavaSE.array;public class ArrayTest { public static void main(String[] args) { // 静态初始化 int[] array1 = {100,200,300}; //动态初始化 int[] array2 = new int[5]; //5 表示有5个元素,每个元素的默认值是0 String[] array3 = new String[6];//初始化长度为6的String类型数组,默认值是null }}
6.2.3 一维数组的基本操作
package com.JavaSE.array;public class ArrayTest { public static void main(String[] args) { int[] array = {1,2,3,4,5,}; System.out.println("数组元素的个数:"+array.length);//输出5 //取数组元素 System.out.println(array[0]); System.out.println(array[4]); //改数组元素 array[0] = 100; array[3] = 200; // 数组的遍历 for(int i = 0; i < array.length; i++){ System.out.print(" "+array[i]); } }}
6.2.4 动态初始化一维数组
package com.JavaSE.array;public class ArrayTest { public static void main(String[] args) { //动态初始化创建数组 int[] array = new int[5]; //默认值是0 for(int i = 0;i < array.length;i++){ array[i] = i; System.out.println(array[i]); } Object[] objects = new Object[3];//默认值是null for(int i = 0;i < objects.length; i++){ System.out.println(objects[i]); } }}
6.2.4 方法的参数是数组
package com.JavaSE.array;public class ArrayTest { public static void main(String[] args) { int[] arr1 = {1,2,3,4,5}; //静态 //调用方法 printArray(arr1); int[] arr2 = new int[5];//动态 //调用方法 printArray(arr2); } //定义方法 public static void printArray(int[] array){ for(int i = 0;i<array.length;i++){ System.out.println(array[i]); } }}
6.2.5 main() 方法中的String[ ] 数组
JVM 调用 main() 方法的时候,会传递一个 String 数组。
main() 方法中的 String 数组留给用户使用的,用户可以在控制台上面输入。
package com.JavaSE.array;public class ArrayTest { public static void main(String[] args) { //数组对象创建了,但是数组的长度是0 System.out.println(args.length);//输出0 }}
6.2.6 一维数组的扩容
Java 中数组的扩容是先建立一个大容量的数组,在将小容量的数组拷贝进去。
结论:数组的拷贝效率较低,应该减少该操作。
package com.JavaSE.array;public class ArrayTest { public static void main(String[] args) { int[] array = {1,2,3,4,5}; int[] test = new int[8]; /* *第一个参数:源数组 *第二个参数:源数组拷贝的起始下标 *第三个参数:目标数组 *第四个参数:目标数组开始存放的下标 *第五个参数:存放的长度 */ System.arraycopy(array,0,test,2,5); for (int i = 0; i < test.length; i++) { System.out.print(" " + test[i]); } }}
3、二维数组
6.3.1 二维数组简介
二维数组是一个特殊的一维数组,特殊在这个一维数组当中的每一个元素都是一个一维数组。
6.3.2 二维数组的声明
package com.JavaSE.array;public class ArrayTest { public static void main(String[] args) { int[][] a ={ {1,5}, {2,6,8,9}, {5,6,0} }; }}
6.3.3 二维数组的 length 解释
package com.JavaSE.array;public class ArrayTest { public static void main(String[] args) { int[][] a ={ {1,5}, {2,6,8,9}, {5,6,0} }; System.out.println(a.length);// 3 表示有几个一维数组 System.out.println(a[0].length);// 2 表示第一行元素的个数 System.out.println(a[1].length);// 4 表示第二行元素的个数 System.out.println(a[2].length);// 3 表示第三行元素的个数 }}
6.3.4 二维数组元素的访问
package com.JavaSE.array;public class ArrayTest { public static void main(String[] args) { int[][] a = { {3,5,6,7}, {1,1,2,9,3}, {0} }; //取出二维数组的第一个元素 /* * a[0] 表示一个整体,第一个一维数组,后一个[0] * 表示第一个一维数组的第一个元素 * */ System.out.println(a[0][0]); }}
6.3.5 二维数组的遍历
package com.JavaSE.array;public class ArrayTest { public static void main(String[] args) { int[][] a = { {3,5,6,7}, {1,1,2,9,3}, {0} }; //a.length 表示行数 //a[i].length 表示每一行的元素个数 for (int i = 0; i < a.length; i++) { for (int j = 0; j < a[i].length; j++) { System.out.print(a[i][j]+ " "); } System.out.println(); } }}
6.3.6 方法的参数是一个二维数组
package com.JavaSE.array;public class ArrayTest { public static void main(String[] args) { int[][] arrays = { {2,4,6,5,7}, {5,8,9,1}, {0,2,3,3,5} }; printArray(arrays); } public static void printArray(int[][] array){ for (int i = 0; i < array.length; i++) { for (int j = 0; j < array[i].length; j++) { System.out.print(array[i][j]+" "); } System.out.println(); } }}
4、Arrays 工具类
Arrays 封装了数组常用算法,常用的是排序和二分查找算法。
import java.util.Arrays;public class ArrayTest { public static void main(String[] args) { int[] arrays = {1,5,4,6,8,9,6,3,2,4}; //排序 Arrays.sort(arrays); //输出 for (int i = 0; i < arrays.length; i++) { System.out.print(" "+arrays[i]); } //二分法查找 int index = Arrays.binarySearch(arrays,8); System.out.println(index == -1 ? "该元素不存在" : "该元素的下标是:"+ index); }}
七、数据结构与算法
1、数组实现栈数据结构
栈:先进后出的数据结构。实现 push 和 pop方法。
public class ArrayTest { public static void main(String[] args) { MyStack stack = new MyStack(); Object[] objects = new Object[10]; objects[0] = "abc"; objects[1] = 100; objects[2] = "C"; objects[3] = "C ++"; objects[4] = "Java"; objects[5] = "Python"; objects[6] = "C#"; objects[7] = 2; objects[8] = 4; objects[9] = 0; //调用进栈 for (int i = 0; i < 10; i++) { stack.push(objects[i]); } //调用出栈 Object object = stack.pop(); System.out.println(object); }}class MyStack{ //Object是所有类的父类,可以存放任何类型 private Object[] elements; // 设计栈帧,默认栈没有元素 private int index = -1; public MyStack(){ //默认初始化容量是10 this.elements = new Object[10]; } public MyStack(Object[] elements) { this.elements = elements; } //压栈方法 public void push(Object obj){ if(this.index >= this.elements.length - 1){ System.out.println("栈已满!"); return; } //栈没有满 this.index++; this.elements[index] = obj; System.out.println("压栈 "+obj+"成功,栈指向"+ index); } //出栈方法 public Object pop(){ if(index < 0 ){ System.out.println("栈已空!"); return null; } System.out.print("出栈元素 "+elements[index]); index--; System.out.println("栈帧指向"+index); return elements[index]; } public Object[] getElements() { return elements; } public void setElements(Object[] elements) { this.elements = elements; }}
2、冒泡排序算法
public class ArrayTest { public static void main(String[] args) { int[] array = {1,5,7,9,3,4,6,8}; for (int i = 0; i < array.length - 1; i++) { for (int j = 0; j < i; j++) { int temp; if(array[j] > array[j+1]){ temp = array[j]; array[j] = array[j+1]; array[j+1] = temp; } } } System.out.println("冒泡排序后:"); for (int i = 0; i < array.length; i++) { System.out.print(" " + array[i]); } }}
3、选择排序算法
效率比冒泡排序高,每次在参与比较的数据中找到最小值,放到最前面。所以每一次的比较都是有意义的。
/** 选择排序的关键在与找到每次比较最小的元素* 每次都假设第一个元素最小* */public class ArrayTest { public static void main(String[] args) { int[] array = {1,5,7,9,3,4,6,8}; for (int i = 0; i < array.length - 1; i++) { int min = i; for (int j = i; j < array.length; j++) { if(array[j]<array[i]){ //如果后面的元素比第一个元素还小,更新最小下标 min = j; } } /* 当 i 和 min 相等时,表示第一个元素最小 * 当 i 和 min 不相等时,表示有最小的元素 * */ if(min != i){ int temp; temp = array[min]; array[min] = array[i]; array[i] = temp; } } System.out.println("选择排序:"); for (int i = 0; i < array.length; i++) { System.out.print(" "+array[i]); } }}
4、按照顺序查找算法
public class ArrayTest { public static void main(String[] args) { int[] array = {1,5,7,9,3,4,6,8}; int index = arraySeach(array,8); System.out.println("该元素的下标是:"+index); } public static int arraySeach(int[] array,int flage){ for (int i = 0; i < array.length; i++) { if(array[i] == flage ){ System.out.println("找到元素该元素"); return i; } } System.out.println("没有找到该元素"); return -1; }}
5、二分法查找算法
二分法查找的方式是建立在排序的基础之上的。
public class ArrayTest { public static void main(String[] args) { int[] array = {1,2,3,4,5,6,7,8}; // 找到元素为 7 的下标 int index = binarrySeach(array,8); System.out.println("下标为:"+ index); } private static int binarrySeach(int[] array, int flage) { int begin = 0; int end = array.length -1; while (begin <= end) { int mid = (begin+end) / 2; if (array[mid] == flage) { System.out.println("找到了该元素"); return mid; } else if (array[mid] < flage) { //查找元素在右边 begin = mid + 1; } else { //查找元素在左边 end = mid - 1; } } System.out.println("没有找到该元素"); return -1; }}
八、常用类
1、String 类
8.1.1 String 类简介
- String 表示的是一个字符串类型,属于引用数据类型。
- 在 Java 中顺便用双引号括起来的都是 String 类对象,是不可变的。
- 双引号括起来的都是存储在字符串常量池中。
- 每次用双引号括起来的字符串,就要创建对象,但是已经创建过的就存储在在常量池中
8.1.2 String 类的常用构造方法
package com.JacaSE.String;public class StringTest { public static void main(String[] args) { // 创建字符串对象常用方式 String s1 = "Hello World"; // 方式1 byte[] bytes = {97,98,99};//对应 a b c String s2 = new String(bytes);//传递数组 //方式2 String s3 = new String(bytes,1,2); System.out.println(s3);//bc //把 char 数组转成字符串 char[] chars = {'我','是','中','国','人'}; String s4 = new String(chars); System.out.println(s4); }}
8.1.3 String 类中的 charAt 方法
该方法返回指定位置的值。
public class StringTest { public static void main(String[] args) { // 注意用的是双引号,是字符串 char c = "中国人".charAt(1); System.out.println(c);// 国 }}
8.1.4 String 类判断字符串是否以某个字符串结尾
public class StringTest { public static void main(String[] args) { System.out.println("Hello World".endsWith("ld"));// true System.out.println("One More String".endsWith("abc"));//false }}
8.1.5 String 类把字符串对象转换成字节数组
public class StringTest { public static void main(String[] args) { byte[] bytes = "abcdefgh".getBytes(); //转换成的数字 for (int i = 0; i < bytes.length; i++) { System.out.println(bytes[i]); } }}
8.1.6 String 类中字符串拼接
package com.JacaSE.String;public class StringTest { public static void main(String[] args) { StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append("Hello"); stringBuffer.append(" World"); System.out.println(stringBuffer.toString()); }}
2、包装类
8.2.1 包装类型
Java 对 8 中基本数据类型提供了 8 中基本包装类,8 中基本数据类型都是引用数据类型,父类是 Object.
8.2.2 8 种基本数据类型的包装类型名
基本数据类型 | 包装类型 |
---|---|
byte | java.lang.Byte |
short | java.lang.Short |
int | java.lang.Integer |
long | java.lang.Long |
float | java.lang.Float |
double | java.lang.Double |
boolean | java.lang.Boolean |
char | java.lang.Character |
8.2.3 装箱和拆箱
装箱:将基本数据类型转换成引用数据类型。
拆箱:将引用数据类型转换成基本数据类型。
public class StringTest { public static void main(String[] args) { //装箱 Integer i = new Integer(123); //拆箱 float f = i.floatValue(); System.out.println(f);//123.0 }}
8.2.4 Integer 类的构造方法
Integer(int); 和 Integer(String);
3、Java 中对日期的处理
8.3.1 获取当前系统时间
import java.text.SimpleDateFormat;import java.util.Date;public class DateTest { public static void main(String[] args) { // 获取当前系统的时间 Date nowTime = new Date(); System.out.println(nowTime);//Fri Feb 26 10:43:37 CST 2021 // 以固定格式转换 /* * yyy : 年 * MM :月 * dd :日 * HH :时 * mm :分 * ss :秒 * SSS :毫秒 (1000 毫秒是 1 秒) * */ SimpleDateFormat sdfFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS "); String nowTimeStr = sdfFormat.format(nowTime); System.out.println(nowTimeStr);// 2021-02-26 10:53:57:013 }}
8.3.2 把日期字符串转换成时间
import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;public class DateTest { public static void main(String[] args) throws ParseException { // 格式必须一致 String time = "2008-08-08 08:00:00 888"; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); Date dateTime = sdf.parse(time); System.out.println(dateTime);//Fri Aug 08 08:00:00 CST 2008 }}
4、相关数据
8.4.1 高精度数据
import java.math.BigDecimal;public class DateTest { public static void main(String[] args) { // 精度极高的数据 100 和 200 BigDecimal v1 = new BigDecimal(100); BigDecimal v2 = new BigDecimal(200); //加减乘除 需要调方法 BigDecimal v3 = v1.add(v2); System.out.println(v3); }}
8.4.2 随机数
import java.util.Random;public class DateTest { public static void main(String[] args) { Random random = new Random(); // 产生 int 范围内的随机数 // 产生 0 ~ 100 之间的随机数 int num2 = random.nextInt(101); System.out.println(num2); }}
5、枚举类型
8.5.1 枚举类型介绍
当一个方法执行结果是有限个,可以使用枚举类型,表示一枚一枚可以列举出来。枚举编译会也是生成 class 文件,也是一个引用数据类型,枚举中的每个值可以看做是一个常量。
8.5.2 枚举类型的使用
public class DateTest { public static void main(String[] args) { // 计算两个 int 类型数据的商,成功返回 SUCCESS 否则返回 FAIL Result r = divide(10,2); System.out.println(r == Result.SUCCESS ? "计算成功":"计算失败"); } public static Result divide(int a,int b){ try{ int c = a / b; return Result.SUCCESS; }catch (Exception e){ return Result.FAIL; } }}enum Result{ // SUCCESS 和 FAIL 可以当做常量 SUCCESS,FAIL;}
九、异常
1、异常介绍
当程序执行时发生了不正常的情况,而这种不正常的情况叫做:异常。通常 JVM 会把异常情况打印到控制台上,供程序员参考,修改异常提高程序的健壮性。***所有异常都是发生在运行阶段的。***
异常在 Java 中都是以类的形式存在的,每一个异常都可以创建对象。
2、异常的分类
异常分为编译时异常和运行时异常。
9.2.1 编译时异常
编译时异常发生的概率很高,也叫受检异常或受控异常。
9.2.2 运行时异常
运行时异常发生的概率较低,可以不做处理。
3、异常的处理方式
9.3.1 throws
使用 throws 上抛异常对象,如果调用者不能解决异常再继续上抛,到 main 方法,如果 main 方法不能解决,那么 JVM 就会终止该程序。
public class ExceptionTest { // 调用 doSome() 方法在继续上抛 public static void main(String[] args) throws ClassNotFoundException { doSome(); } /* * doSome 方法后面写了:throws ClassNotFoundException * 这个表示 doSome() 方法在执行的时候可能会发生 ClassNotFoundException 异常 * 在调用doSome() 方法时必须处理该异常,否则就报错,属于编译时异常。 * */ public static void doSome() throws ClassNotFoundException{ System.out.println("doSome!!!!"); }}
9.3.2 try - catch
使用 try - catch 捕捉异常,捕捉异常对象,进行解决。
public class ExceptionTest { public static void main(String[] args) { try { doSome(); }catch (ClassNotFoundException e){ e.printStackTrace(); } } public static void doSome() throws ClassNotFoundException{ System.out.println("doSome!!!!"); }}
9.3.3 try - catch - finally
finally 语句是最后执行的,并且一定会执行,即使 try 语句中出现了异常,fially 语句不能单独编写。
public class ExceptionTest { public static void main(String[] args) { try { doSome(); } catch (ClassNotFoundException e) { e.printStackTrace(); }finally { System.out.println("类没有找到异常!"); } } public static void doSome() throws ClassNotFoundException{ System.out.println("doSome!!!!"); }}
十、IO 流
1、IO 流简介
把硬盘上的文件放到内存上,叫做输入流(InputStream)也叫做输入或者读(Read)。
把内存上的文件放到硬盘上,叫做输出流(OutputStream)也叫做输出或者写(Write)。
2、IO 流的分类
一种分类方式是按照流的方向进行分类:以内存为参照物。
- 往内存中去叫做输入(Intput),或者读。
- 从内存中出来叫做输出(Output),或者写。
另一种分类方式是按照数据的方式不同来读取:
- 有的是按照字节的方式来进行读取文件,一次读取1个字节。可以读取:文本文件、图片、声音文件等等。
- 有的流是按照字符的方式来进行读取的,一次读取有个字符。只能读取纯文本文件。
3、Java IO 流的四大家族
- java.io.InputStream 输入字节流
- java.io.OutputStream 输出字节流
- java.io.Reader 字符输入流
- java.io.Writer 字符输出流
以 Stream 结尾的都是字节流,以 Read/Write 结尾的都是字符流。
这四大家族都是抽象类。使用完流一定要用 close()方法关闭。
所有的 输出流 使用完要用 flush() 方法刷新,清空管道,防止丢失数据。
4、java.io 包下需要掌握的16个流
文件专属: java.io.FileInputStream |
---|
java.io.FileOutputSteam |
java.io.FileReader |
java.io.FileWriter |
转换流: java.io.InputStramReader |
java.io.OutputStreamWriter |
缓冲流专属**: java.io.BufferedReader |
java.io.BufferedWriter |
java.io.BufferedInputStream |
java.io.BufferedOutputStream |
数据流专属 : java.io.DataInputStream |
java.io.DataOutputStream |
对象流专属: java.io.ObjectInputStream |
java.io.ObjectOutputStream |
标准输出流: java.io.PrintWrite |
java.io.PrintStream |
5、java.io.FileInputStream
import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;public class FileIntputStream { public static void main(String[] args) throws IOException { FileInputStream fis = null; try { //文件中的内容是:abcdef fis = new FileInputStream("F:\\Java_IDEA_Code\\temp.txt"); // 开始读文件一次读一个字符 while(true){ int readDate = fis.read();// read()该方法返回的是整数 if(readDate == -1){ break; } System.out.println(readDate); } } catch (FileNotFoundException e) { e.printStackTrace(); }finally { if (fis != null) { fis.close(); } } }}
6、采用 byte[ ] 数组读取多个字节
import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;public class FileIntputStream { public static void main(String[] args) throws IOException { FileInputStream fis = null; File file; try { // IDEA 默认当前的文件路径在该工程文件下,文件内容 abcdef fis = new FileInputStream("file.txt"); // 设置4个字节的byte数组 byte[] bytes = new byte[4]; int readerDate = fis.read(bytes); //第一次读取 4 个字节 System.out.println(new String(bytes));// abcd /* * 再次使用 readerDate = fis.read(bytes); 读取会覆盖 * 数组前面读取的字节内容 * */ readerDate = fis.read(bytes); System.out.println(new String(bytes));// efcd } catch (FileNotFoundException e) { e.printStackTrace(); }finally { if(fis !=null){ try{ fis.close(); }catch (IOException e){ e.printStackTrace(); } } } }}
7、java.io.OutputStream
package com.JavaSE.IO;/** 从内存到硬盘负责写* */import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;public class FileIntputStream { public static void main(String[] args) { FileOutputStream fos = null; File file; try { // 先将源文件清空在写入 fos = new FileOutputStream("myfile.txt"); // 开始写 byte[] bytes1 = {97,98,99,100}; fos.write(bytes1); // 全部写入 fos.write(bytes1,0,2); // 写入前两个数字 /* * 以追加的方式写入,不会清空文件 * */ fos = new FileOutputStream("file.txt",true); byte[] bytes2 = {97,98,99,100}; fos.write(bytes2); // 全部写入 fos.write(bytes2,0,2); // 写入前两个数字 fos.write(bytes2,1,2); // 把字符串写入文件 String s = "我是中国人"; // getBytes() 方法将字符串转换成数组 byte[] byte3 = s.getBytes(); fos.write(byte3); } catch (FileNotFoundException e) { e.printStackTrace(); }catch (IOException e) { e.printStackTrace(); } finally { if(fos != null){ try{ fos.close(); }catch(IOException e){ e.printStackTrace(); } } } }}
8、文件的复制
文件的复制是先将硬盘上的文件输入到内存,再写入到硬盘,是一边读一边写。
package com.JavaSE.IO;/** 拷贝一个文件* */import java.io.*;public class FileIntputStream { public static void main(String[] args) { FileInputStream fis = null; FileOutputStream fos = null; try { File file; fis = new FileInputStream("E:\\Java视频\\Video\\01\\1.1.1什么是Java.mp4"); fos = new FileOutputStream("F:\\1.1.1什么是Java.mp4"); byte[] bytes = new byte[1024*1024]; int readDate = 0; readDate = fis.read(bytes); while(readDate != -1){ fos.write(bytes,0,readDate); } // 刷新 fos.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (Exception e){ e.printStackTrace(); } finally { if(fos != null){ try{ fos.close(); }catch(IOException e){ e.printStackTrace(); } } if(fis != null){ try{ fis.close(); }catch(IOException e){ e.printStackTrace(); } } } }}
十一、多线程
1、进程和线程
进程是一个动态执行的应用程序。
线程是一个进程的执行场景 / 执行单元。
一个进程可以启动多个线程。
对于 一 个JAVA 程序执行时,会先启动JVM,而JVM就是一个进程,JVM再启动一个主线程调用main() 方法,同时在启动一个垃圾回收线程负责看护,回收垃圾,至少会启动这两个进程。
进程之间内存不共享,线程之间会共享内存,但是线程 A 和线程 B 之间堆内存和方法区内存共享,但是**栈内存独立,一个线程一个栈**,各自执行各自的这就是多线程并发。
线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、死亡。
2、实现多线程
11.2.1 编写一个类直接继承 java.lang.Thread,重写run方法
start() 方法的作用是启动一个分支线程,在JVM中开辟一个新的内存空间,这个start()方法执行瞬间就结束了,线程就开始启动了,启动成功后就会调用 run() 方法,run()方法在分支栈的底部进行压栈。并且 run() 方法和 main() 方法是平级的。
/** 实现线程编写一个类直接继承 java.lang.Thread,* 重写run方法* */public class ThreadTest { public static void main(String[] args) { // 新建一个分支线程对象 MyTehread myTehread = new MyTehread(); // 启动线程调用 start() 方法 myTehread.start(); // 在主线程中运行数组 for (int i = 0; i < 1000; i++) { System.out.println("主线程----->"+ i); } }}class MyTehread extends Thread{ // 必须重写 run() 方法 public void run(){ for (int i = 0; i < 1000; i++) { System.out.println("分支线程----->" + i); } }}
11.2.2 编写一个类,实现 java.lang.Runnable 接口,实现 run 方法
定义一个可以运行的类,在创建线程对象,启动线程。
/** 实现线程编写一个类实现java.lang.Runnable接口* 重写run方法* */public class ThreadTest { public static void main(String[] args) { // 创建一个可运行的对象 MyRunnable myRunnable = new MyRunnable(); // 将可以运行的对象封装成一个可以运行的线程对象 Thread t = new Thread(myRunnable); // 启动线程 t.start(); //主线程 for (int i = 0; i < 100; i++) { System.out.println("主线程---->"+ i); } }}// 这是一个了运行的类class MyRunnable implements Runnable{ public void run(){ for (int i = 0; i < 100; i++) { System.out.println("分支线程---->" + i); } }}
11.2.3 实现 Callable 接口
import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;//实现 Callable 接口public class ThreadTest { public static void main(String[] args) throws ExecutionException, InterruptedException { // 第一步:创建一个“未来任务类对象” FutureTask tesk = new FutureTask(new Callable(){ public Object call() throws Exception { System.out.println("begin!"); Thread.sleep(1000*10); System.out.println("end!"); return null; } }); // 创建一个线程对象 Thread t = new Thread(tesk); t.start(); Object obj = tesk.get(); }}
3、线程相关
11.3.1 设置线程名字
public class ThreadTest { public static void main(String[] args) { MyThread myThread = new MyThread(); // 设置线程名字为 T myThread.setName("T"); String tName = myThread.getName(); System.out.println(tName);// T // 启动线程 myThread.start(); }}class MyThread extends Thread{ public void run(){ for (int i = 0; i < 100; i++) { System.out.println("分支线程---->"+ i); } }}
11.3.2 获取当前线程对象
// 这两行代码在哪个地方获取的就是哪个线程对象Thread t = Thread.currentThread();System.out.println(t);
public class ThreadTest { public static void main(String[] args) { //获取当前线程对象 // 这个代码出现在 main()方法中,所以当前对象就是main() Thread t = Thread.currentThread(); System.out.println(t); MyThread myThread = new MyThread(); // 启动线程 myThread.start(); }}class MyThread extends Thread{ public void run(){ Thread t = Thread.currentThread(); System.out.println(t); // Thread-0 for (int i = 0; i < 100; i++) { System.out.println("分支线程---->"+ i); } }}
11.3.3 线程的 sleep() 方法
- static void sleep(long millis) 是一个静态方法
- 参数是毫秒
- 作用:出现在哪里就让当前线程进入睡眠状态。
- 可以间隔固定的时间执行特定的代码
public class ThreadTest { public static void main(String[] args) { // 睡眠 5 秒钟 try { Thread.sleep(1000*5); } catch (InterruptedException e) { e.printStackTrace(); } // 5 秒钟后显示Hello World System.out.println("Hello World"); // 每隔 1 秒 显示 for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "---->" + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }}
11.3.4 终止 sleep() 方法
public class ThreadTest { public static void main(String[] args) { Runnable target; Thread t = new Thread(new MyRunnable()); t.setName("T"); t.start(); // 主线程睡眠 5 秒 try { Thread.sleep(1000*5); } catch (InterruptedException e) { e.printStackTrace(); } // 终止 T 线程的睡眠 t.interrupt(); }}class MyRunnable implements Runnable{ public void run(){ System.out.println(Thread.currentThread().getName()+"---> begin"); //run() 方法中的异常只能使用 try -catch 不能使用 throws // 因为子类不能比父类抛出更多的异常 try { // 睡眠一年 Thread.sleep(1000*60*60*24*365); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"---> end"); }}
11.3.5 终止线程
stop() 方法直接终止进程,不安全,相当于直接断电,数据会丢失。
public class ThreadTest { public static void main(String[] args) { Runnable target; Thread t = new Thread(new MyRunnable()); t.setName("T"); t.start(); // 主线程睡眠 5 秒 try { Thread.sleep(1000*5); } catch (InterruptedException e) { e.printStackTrace(); } // 强行终止 T 线程 t.stop(); // 以过时,不建议使用容易丢失数据,相当于直接杀死进程 }}class MyRunnable implements Runnable{ public void run(){ for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"--->"+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }}
合理的终止线程的方式:
public class ThreadTest { public static void main(String[] args) { MyRunnable myRunnable = new MyRunnable(); Thread t = new Thread(myRunnable); t.setName("T"); t.start(); // 主线程睡眠 5 秒 try { Thread.sleep(1000*5); } catch (InterruptedException e) { e.printStackTrace(); } // 终止进程 myRunnable.run = false; }}class MyRunnable implements Runnable{ boolean run = true; public void run(){ for (int i = 0; i < 10; i++) { if(run){ System.out.println(Thread.currentThread().getName()+"--->" + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }else{ return; } } }}
4、线程调度(了解)
11.4.1 线程调度概述
常见的线程调度模型:抢占式模型和均分式模型。Java 采用的是抢占式调度模型。
11.4.2 线程优先级
- 最高:10
- 最小:1
- 默认:5
public class ThreadTest { public static void main(String[] args) { System.out.println("最高优先级:" + Thread.MAX_PRIORITY); //10 System.out.println("最低优先级:" + Thread.MIN_PRIORITY); // 1 System.out.println("默认优先级:" + Thread.NORM_PRIORITY);// 5 }}
5、线程安全(重点)
11.5.1 线程安全概述
程序放到服务器执行,线程的创建和启动服务器会完成,重要的是要保证程序数据安全。
什么时候数据在多线程并发的情况下回存在安全问题呢? 以下三个条件
- 多线程并发
- 有共享数据
- 共享数据有修改的行为
11.5.2 解决线程安全问题
线程排队执行,不能并发,用线程排队执行解决线程安全问题这种机制被称为:线程同步。
线程同步就是线程排队,会牺牲程序执行的效率。
11.5.3 同步编程模型和异步编程模型
异步编程模型:线程A和线程B,各自执行各自的,谁也不需要等谁。其实就是多线程并发。
同步编程模型:线程A和线程B,在执行过程中,一个必须等待另一个执行结束,才能执行,构成排队形式,执行效率较慢。
11.5.4 实现线程同步
线程同步机制的语法:
synchronized(){// 线程同步代码块}/** synchronized 后面的小括号里面的数据时相当关键的,是需要排队线程的 "共享对象"* 假设有 t1,t2,t3,t4,t5 有5个线程,想让 t1,t2,t3 排队,就需要在 synchronized 后面* 的括号里面写 t1,t2,t3 这3个线程的共享对象。*/
在 Java 语言中,任何一个对象都有 “一把锁”,锁就是标记。
1、假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
2、假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,
找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是占有这把锁的。直到同步代码块代码结束,这把锁才会释放。
3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后t2占有这把锁之后,进入同步代码块执行程序。
6、死锁
11.6.1 死锁概述
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。死锁很难调试。
11.6.2 死锁举例
public class ThreadTest { public static void main(String[] args) { Object o1 = new Object(); Object o2 = new Object(); // t1 和 t2 两个线程共享 o1,o2 Runnable target; Thread t1 = new MyThread1(o1,o2); Thread t2 = new MyThread2(o1,o2); // 线程启动 t1.start(); t2.start(); }}class MyThread1 extends Thread{ Object o1; Object o2; public MyThread1(Object o1,Object o2){ this.o1 = o1; this.o2 = o2; } public void run(){ synchronized (o1){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o2){ } } }}class MyThread2 extends Thread{ Object o1; Object o2; public MyThread2(Object o1,Object o2){ this.o1 = o1; this.o2 = o2; } public void run(){ synchronized (o2){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o1){ } } }}
7、守护线程
11.7.1 守护线程概述
Java 语言中线程分为两大类:
- 用户线程 例如:main() 方法
- 守护线程(后台线程)
其中最具有代表性的就是:垃圾回收线程(守护线程)。
守护线程的特点:守护线程是一个死循环,所有的用户线程结束了,守护线程才会结束。
11.7.2 实现守护线程
public class ThreadTest { public static void main(String[] args) { Thread t = new BakDateThread(); t.setName("守护线程"); // 启动线程之前,将线程设置为守护线程 t.setDaemon(true); // 线程启动 t.start(); // 主线程 for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"--->"+ i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }}class BakDateThread extends Thread{ public void run(){ int i = 0; while(true){ System.out.println(Thread.currentThread().getName() + "--->"+(++i)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }}
8、定时器
11.8.1 定时器的作用
定时器的作用:间隔特定的时间,执行特定的程序。
类库:java.util.Time
11.8.2 实现定时器
import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;import java.util.Timer;import java.util.TimerTask;/*使用定时器指定定时任务 */public class ThreadTest { public static void main(String[] args) throws ParseException { // 创建定时器任务 Timer timer = new Timer(); // 指定定时任务 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date firsTime = sdf.parse("2021-2-28 17:40:00"); timer.schedule(new LogTimerTest(),firsTime,1000*10);// 每隔10秒钟执行一次 }}// 编写一个定时任务类class LogTimerTest extends TimerTask{ @Override public void run() { System.out.println("运行"); }}
十二、反射机制
1、反射机制
12.1.1 反射机制概述
通过 java 语言中的反射机制可以操作字节码文件。通过反射机制可以操作代码片段。让程序更加灵活。
反射机制在:java.lang.reflect.*; 包下。
12.1.2 反射机制相关的类
java.lang.Class // 代表字节码文件java.lang.reflect.Method // 代表字节码中的方法字节码java.lang.reflect.Constructor // 代表字节码中的构造方法字节码java.lang.reflect.Field //代表字节码中的属性字节码
2、获取字节码文件
12.2.1 第一种方式
Class.forName() 方法介绍:
- 是一个静态方法
- 方法的参数是一个字符串
- 字符串需要一个完整的类名
- 完整类名必须带有包名
12.2.2 第二种方式
Class c = 对象.getClass();
import java.util.Date;public class ReflectTest { public static void main(String[] args) { try { Class c1 = Class.forName("java.lang.String");// c1 代表String.class 文件 或者String类型 Class c2 = Class.forName("java.util.Date"); // c2 代表Date类型 } catch (ClassNotFoundException e) { e.printStackTrace(); } // java 中任何一个对象都有一个方法:getClass() String s = "abc"; Class x = s.getClass(); Date d = new Date(); Class y = d.getClass(); }}
12.2.3 第三种方式
在 java 语言中任何一个类型都有 class() 属性。
import java.util.Date;public class ReflectTest { public static void main(String[] args) { Class s = String.class; Class d = Date.class; Class i = int.class; Class e = Double.class; }}