Java语言程序设计
计算机系统组成结构
1、Java简介
1.1、语言特性
-
简单性
相对而言的,例如:Java中不再支持多继承,c++是支持多继承的,多继承比较复杂;c++中有指针,Java中屏蔽了指针的概念;Java中低层有c++实现的,不是c语言。
-
面向对象
Java是纯面向对象的,更加符合人的思维模式,更容易理解。
-
可移植性
Java程序可以做到一次编译到处运行,也即是说Java程序可以在Windows操作系统上运行,不做任何修改,同样的Java程序可以直接放到Linux操作系统上运行,这就是Java程序的可移植性,或者叫跨平台性。由Java虚拟机(JVM)实现跨平台功能。
-
多线程
-
健壮性
和自动垃圾回收机制有关,自动垃圾回收机制简称GC机制;Java语言运行过程中产生的垃圾时自动回收的,不需要程序员关心。
-
安全性
1.2、Java的加载与执行
-
Java程序的运行包括两个阶段:
-
编译阶段(源文件.Java —> 字节码文件.class)
检查Java源程序是否符合Java语法,符合语法则生成字节码文件.class;字节码文件不是纯粹的二进制文件;编译方法为使用jdk中的javac.exe命令。
-
运行阶段(字节码文件.class—> 二进制文件,由操作系统执行)
java.exe命令会启动Java虚拟机(JVM),JVM会启动类加载器ClassLoader,ClassLoader会去硬盘上搜索.class文件,找到该文件则将该字节码装载到JVM中,JVM将.class文件解释成二进制数据。
-
1.3、Java中的注释
-
单行注释//
-
多行注释/*多行注释*/
-
javadoc注释/**javadoc注释*/
1.4、public class 和class的区别
- 一个Java源文件中可以定义多个class;
- 一个Java源文件中不一定有public class;
- 一个(多个)class会定义生成一个(多个).class字节码文件;
- 一个Java源文件中定义的公开类的话(只能有一个公开类public class),并且该类名必须与文件名一致;
- 每一个class当中都可以编写main方法,都可以设定程序入口,而且每个class中必须有main方法作为程序入口。
1.5、IDEA常用快捷键
功能 | 快捷键 |
---|---|
运行 | Ctrl +Shift + F10 |
复制行 | Ctrl + D |
删除行 | Ctrl + Y |
行注释 | Ctrl + / |
块注释 | Ctrl + Shift + / |
撤销 | Ctrl + Z |
取消撤销 | Ctrl +Shift + Z |
格式化代码 | Ctrl + Alt + L |
智能提示 | Alt + Enter |
重命名 | Shift + F6 |
光标上方插入一行 | Ctrl + Alt +Enter |
光标下方插入一行 | Shift +Enter |
补全分号/括号 | Ctrl + Shift +Enter |
向上移动当前行代码 | Ctrl + Shift + ↑ |
向下移动当前行代码 | Ctrl + Shift + ↓ |
批量修改变量 | Ctrl + Shift + Alt + J |
快速引入变量 | Ctrl + Alt + V |
查找类 | Ctrl + N |
查找类中的方法 | Ctrl + F12 |
新增类/方法 | Alt + Insert |
块操作 | 连接 |
重写方法 | Ctrl + O |
实现方法 | Ctrl + I |
展开/折叠全部方法 | Ctrl + Shift + +/- |
窗口最大化 | Ctrl + Shift +F12 |
关闭标签 | Ctrl + F4 |
切换标签 | Alt + 左/右箭头 |
方法间跳转 | Alt + 上/下箭头 |
提示方法参数 | Ctrl + P |
定位下一个错误 | F2 |
定位上一个错误 | Shift + F2 |
查看类的继承结构 | Ctrl + H |
2、Java基础程序设计
2.1、 标识符
- 标识符可以表示类名、方法名、变量名、接口名、常量名等;
- 标识符的命名规则:标识符只能由字母、数字、下划线和美元符号构成,不能以数字开头,且不能是关键字;
- 标识符命名规范:
- 遵守驼峰式命名方式
- 类名、接口名:首字母大写,后面每个单词首字母大写
- 变量名、方法名:首字母小写,后面每个单词后首字母大写
- 常量名:所有字母大写,两个单词之间用下划线连接
- 遵守驼峰式命名方式
2.2、关键字
- Java中保留的50个关键字:abstract,double,int,super,assert,else,interface,switch,boolean,enum,long,synchronized,break,extends,native,this,byte,final,new,throw,case,finally,package,throws,catch,float,private,transient,char,for,protected,try,class,goto,public,void,const,if,return,volatile,continue,implements,short,while,default,import,static,do,instanceof,strictfp
2.3、字面值
- 10,100属于整型字面值
- 3.14属于浮点型字面值
- true,false属于布尔型字面值
- “abc”属于字符串型字面值
- ‘A’属于字符型字面值
2.4、变量
-
变量包括三部分:数据类型、变量名和字面值(数据)。
-
变量须声明再赋值方能访问。
-
根据变量声明的位置分类:
-
局部变量
在方法体中声明的变量叫局部变量。
-
在方法体外(类体之内)声明的叫成员变量;
成员变量没有赋值系统会默认赋值(局部变量不会)
-
2.5、数据类型
2.5.1、基本数据类型
-
整数型:byte、short、int、long
-
浮点型:float、double
-
布尔型:boolean
-
字符型:char
类型名 取值范围 存储大小 默认值 byte [-128~127] 8位带符号位(1个字节) 0 short [-32768~32767] 16位带符号位(2个字节) 0 int [-231~231-1] 32位带符号位(4个字节) 0 long [-263~263-1] 64位带符号位(8个字节) 0 float 32位(4个字节) 0.0 double 64位(8个字节) 0.0 char [0~65535] 16位(2个字节) \u000 boolean [true,false] 8位(1个字节) false
2.5.2、基础知识
long num = 2147483648;//错误,因为Java中整数自动按int型处理,数字不能超过整型的最大范围
long num = 2147483648L;//正确
- Java中整数自动按int型处理,浮点数自动按double型处理
- double和float在计算机中二进制存储都是近似值
- 八种基本数据类型除布尔类型剩下的7种都可以相互转换
- 小容量向大容量转换称为自动类型转换,容量从小到大排序:byte<short<int<long<float<double
- 大容量转换成小容量,叫做强制类型转换,需要加强类型和转换符,但是可能损时精度,需谨慎使用
- 当一个整数字面值没有超出byte,short,char的取值范围,这个字面值可以直接复制给byte,short,插入类型的变量
- byte,short,char混合运算的时候,各自转换成int型再作运算
- 多种数据类型混合运算,先转换成容量最大的那种类型再作运算
2.5.3、强制类型转换原则
-
强制类型转换原理,将精度以外(左边)全部取消掉
//n的补码0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 long n = 2147483648L; //强制类型转换后,将n的前32位全部取消,变成1000 0000 0000 0000 0000 0000 0000 0000,将其转换成原码后数值为-2147483648,损失精度严重 int k = (int)n;
-
当一个整数字面值没有超出byte,short,char的取值范围,这个字面值可以直接复制给byte,short,char类型的变量(理论上数值按整型处理,将其赋值给上述类型以后会损失精度),这种机制sun允许,目的是为了方便程序员的编程。
2.6、字符串
-
Java中随便使用双引号括起来的都是String对象,例如"abc","def"都是String对象
-
String对象一旦创建无法改变
-
字符串都是直接存放在方法区的字符串常量池当中
//hello字符串存放在方法区常量池当中,s1和s2均指向它,内存地址相同,返回true String s1 = "hello"; String s2 = "hello"; System.out.println(s1 == s2); //hello字符串存放在方法区常量池当中,堆内存中分配的String类型两个不同的对象指向它, //str1和str2分别指向这两个对象,返回false String str1 = new String("hello"); String str2 = new String("hello");
-
String类的常用方法
返回值 | 方法 | 描述 |
---|---|---|
int | s.length() | 返回字符串s中的字符数 |
char | s.charAt(index) | 返回字符串s中指定位置的字符 |
String | s1.concat(s2) | 将字符串s1和字符串s2连接,返回一个新字符串 |
boolean | s1.contains(s2) | 判断s2是字符串s1的子字符串 |
boolean | s1.endswith(s2) | 判断字符串s1是否以字符串s2结尾 |
boolean | s1.startswith(s2) | 判断字符串s1是否以字符串s2开始 |
boolean | s1.equals(s2) | 判断字符串s1和s2的内容是否相等 |
byte[] | s.getBytes() | 将字符串s转换成字节数组 |
int | s1.indexOf(s2) | 返回字符串s2在字符串s1当中第一次出现的索引 |
静态 | String.valueOf(s) | 将非字符串s准换成字符串 |
boolean | s.isEmpty() | 判断字符串s是否为空 |
String | s.replace(s1,s2) | 将字符串s中的s1部分用s2进行替换,返回一个新的字符串 |
String[] | s1.split(s2) | 将字符串s1以s2为基准进行分割,返回一个字符串数组 |
String | s.substring(begin) | 返回在字符串的子串,从特定位置begin的字符开始到字符串的结尾 |
String | substring(begin,end) | 返回在字符串的子串,从特定位置begin的字符开始到下标为end-1的字符 |
char[] | s.toCharArray() | 将字符串s转换成字符数组 |
String | trim() | 返回一个新的字符串,去掉两边的的空白字符 |
-
StringBuffer
//创建一个容量为16的byte[]数组(字符串缓冲区对象) StringBuffer stringBuffer = new StringBuffer(); for (int i = 0; i < 20; i++) { //append在方法低层进行追加的时候,如果byte[]数组满了,会自动扩容 stringBuffer.append(i); } //创建一个容量为100的byte[]数组(字符串缓冲区对象) StringBuffer stringBuffer = new StringBuffer(100);
-
与StringBuilder区别:
StringBuffer中的方法有synchronized关键词修饰,表示StringBuffer在多线程环境下运行是安全的
StringBuilder中的方法没有synchronized关键词修饰,表示StringBuilder在多线程环境下运行是不安全的
2.7、运算符
2.7.1、算术运算符
- +(加法运算符)
- -(减法法运算符)
- *(乘法运算符)
- /(除法运算符)
- %(取余/求模运算符)
- ++(自增运算符)
- –(自减运算符)
2.7.2、关系运算符
- >(大于)
- >=(大于等于)
- <(小于)
- <=(小于等于)
- ==(等于)
- !=(不等于)
2.7.3、逻辑运算符
-
&(逻辑与)
-
|(逻辑或)
-
!(逻辑非)
-
^(逻辑异或)
-
&&(短路与)
-
||(短路或)
-
逻辑与&和短路与&&运算结果一样,短路与更智能一些
-
在某些业务逻辑中,要求运算符的两边算子必须全部执行,此时必须使用逻辑与,不能使用短路与,使用短路与可能导致右边的表达式不执行
-
第一个表达式执行结果为true,会发生短路或;
第一个表达式执行结果为false,会发生短路与;
int x = 10; int y = 8; //由于x<y,表达式恒为false,但是逻辑与会执行后面的判断,x变为11 System.out.println(x < y & ++x < y); System.out.println(x); int a = 10; int b = 8; //由于a<b,表达式恒为false,短路与不执行后面的判断,a变为10 System.out.println(a < b && ++a < b); System.out.println(a);
2.7.4、赋值运算符
-
基本赋值运算符
-
=(赋值运算符)
-
增强赋值运算符
-
+=(加法运算符)
-
-=(减法运算符)
-
*=(乘法运算符)
-
/=(除法运算符)
-
%=(取余/求模运算符)
-
-
增强赋值运算符(+=,-=,*=,/=,%=)不改变运算结果的数据类型,底层实现采用强制类型转换,可能会损失精度
byte a = 0; //错误,a+5的类型是int,a变量的数据类型是byte,大容量像小容量转换需要加强制类型转换符 a = a + 5; //正确 a = (byte) (a + 5); //正确,a += 5等同于a = (byte) (a + 5),不等同于a = a + 5 a += 5; //正确,等同于a = (byte) (a + 5),强转后会损失精度 a += 128;
2.7.5、字符串的连接运算符
-
+(字符串连接运算符)
-
"+"的作用:加法运算求和,字符串的连接运算
-
当"+"的两边都是数字时,进行加法运算
-
当"+"的两边只要有一个字符串,便进行字符串连接运算
-
在一个表达式中可以出现多个"+",在没有添加小括号的前提下,遵循自左向右的结合顺序
int a = 10; int b = 20; //要求在控制台上输出“10 + 20 = 30” System.out.println(a + " + " + b + " = " + (a + b));
2.7.6、三目运算符
-
格式:布尔表达式 ? 表达式1 : 表达式2
布尔表达式结果为true,则执行表达式1;
布尔表达式结果为false,则执行表达式2;
2.7.7、位运算符
- &按位与(全1则1,否则为0)
- |按位或(全0为0,否则为1)
- ~按位非(遇0则1,遇1则0)
- ^按位异或(相同为0,不同为1)
- <<左移(算数左移,符号位不变,高位丢弃,低位补0)
- >>右移(算数右移,符号位不变,高位正数补0、负数补1,低位丢弃)
- >>>无符号右移(逻辑右移,高位补0,低位丢弃)
2.7.8、定点数表示
-
假定机器字长为8位
-
原码和反码的真值0有+0和-0两种形式,补码的真值0只有一种形式
-
原码号位"0/1"对应"正/负"
-
定点整数(19 = 16 + 2 +1)
真值 符号 26 25 24 23 22 21 20 +19 0 0 0 1 0 0 1 1 -19 1 0 0 1 0 0 1 1 -
定点小数(0.75 = 0.5 + 0.25 )
真值 符号 2-1 22 2-3 2-4 2-5 2-6 2-7 +0.75 0 1 1 0 0 0 0 0 -0.75 1 1 1 0 0 0 0 0
-
-
反码:若符号位为0,则反码与原码相同;若符号位为1,则数值位全部取反
+19的反码:0,00010011
-19的反码:1,1101100
+0.75的反码:0,1100000
-0.75的反码:1,0011111
-
补码:正数的补码就是原码,负数的补码等于反码末尾加1
+19的补码:0,00010011
-19的补码:1,1101101
+0.75的补码:0,1100000
-0.75的补码:1,0100000
-
补码的定义:模减去真值的绝对值
求-128的补码:机器字长为8位,模长为28=256(100000000),-128的绝对值为128(10000000),故-128的补码为256-128=128(10000000)
2.7.9、移位运算
-
以-20为例
原码:1,0010100
反码:1,1101011
补码:1,1001100
2.7.9.1、算数移位
-
符号位保持不变,仅对数值位进行移位
-
正数的原码、反码、补码添补代码为0,
负数的原码添补代码为0;
负数的反码添补代码为1;
负数的补码添补代码左移添0,右移添1(即添补符号位)
-
原码算数移位
-
**左移:符号位不变,低位补0,高位舍弃。**若舍弃的位为0,相当于乘以2,若舍弃的位不为0,则出现严重误差
-20原码:1,0010100
-20左移1位:1,0101000(-40)
-20左移2位:1,1010000(-80)
-20左移1位:1,0100000(-32)高位1被舍弃产生严重误差
-
**右移:符号位不变,高位补0,低位舍弃。**若舍弃的位为0,相当于除以2,若舍弃的位不为0,则会损失精度;
-20原码:1,0010100
-20右移1位:1,0001010(-10)
-20右移1位:1,0000101(-5)
-20右移1位:1,0000010(-2)低位1被舍弃,损失精度
-
-
反码算数移位
-
正数反码算数移位与原码算数移位相同
- 左移:符号位不变,低位补0,高位舍弃。
- 右移:符号位不变,高位补0,低位舍弃。
-
负数反码算数移位
-
左移:符号位不变,低位补1,高位舍弃
-20反码:1,1101011
-20左移1位:1,1010111(-40)
-
右移:符号位不变,高位补1,低位舍弃
-20反码:1,1101011
-20右移1位:1,1110101(-10)
-
-
-
补码算数移位
-
正数补码算数移位与原码算数移位相同
- 左移:符号位不变,低位补0,高位舍弃。
- 右移:符号位不变,高位补0,低位舍弃。
-
负数补码算数移位
-
规则:负数补码中,最右边的1及其右边同原码,最右边的1的左边同反码
-
左移(同原码):符号位不变,低位补0,高位舍弃
-20补码:1,1101100
-20左移1位:1,1011000(-40)
-
右移(同反码):符号位不变,高位补1,低位舍弃
-20补码:1,1101100
-20右移1位:1,1110110(-10)
-
-
2.7.9.2、逻辑移位
-
可以视为无符号数的移位
-
逻辑左移:低位补0,高位舍弃
-
逻辑右移:高位补0,低位舍弃
2.8、控制语句
-
控制选择结构语句
-
if语句、else语句(分支语句/条件控制语句)
-
switch语句
case穿透:当某个case语句中缺少了break关键字,恰巧该case语句匹配正确时,则执行该语句后不会跳出分支结构,而是继续执行后面的代码,直到遇上break。
-
-
控制循环结构语句
-
for语句
-
while语句
-
do-while语句:至少执行一次
//死循环后有代码无法访问,编译报错 while (true){ System.out.println("test"); } System.out.println("test"); //当关系运算符两侧的运算对象为变量时,只有在运行时才会计算运算结果,程序未运行时编译器无法知道运算结果 int i = 5; int j = 9; while (i < j){ System.out.println("test"); } System.out.println("test"); //编译报错 while (5 < 9){ System.out.println("test"); } System.out.println("test");
-
-
改变控制语句顺序
- break语句:跳出循环体,终止循环的执行
- continue语句:终止本次循环
2.9、方法
-
方法在执行过程中,在JVM中的内存是如何分配,内存是如何变化:
- 方法只定义,不调用,是不会执行的,并JVM 也不会分配内存空间,只有在调用方法的时候才会动态的给方法分配所属内存空间
- JVM内存划分上的三块主要内存空间:方法区内存、堆内存、栈内存
- 方法代码片段存属于.class文件的的一部分,字节码文件在类加载的时候将其放在了方法区内存当中,所以JVM中三块主要的内存空间中方法区内存最先有数据,存放了代码片段
- 代码片段虽然在方法区内存当中只有一份,但是可以被重复调用。每一次调用这个方法的时候需要给该方法分配独立的活动场所,在栈内存中分配。
- 方法在调用的时候会给该方法分配独立的的内存空间,在栈中分配,此时发生入栈操作,方法执行结束之后,给该方法分配的内存空间全部释放,此时发生出栈操作
- 局部变量在方法体中声明,局部变量在运行阶段内存在栈中分配
-
方法内存图示例
public class MethodTest{ public static void main(String[] args){ int a = 10; int b = 20; int value = sumInt(a, b); System.out.println(value); } public static int sumInt(int i, int j){ int result = i + j; int num = 3; int value = divide(result, num); return value; } public static int divide(int x, int y){ int result = x / y; return result; } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UbaCXmAB-1665645907875)(java/method.png)]
-
- 方法重载称为overload
-
功能相似,方法名相同,参数列表不同(数量不同,顺序不同,类型不同)
-
方法递归
- 极其耗费栈内存
- 递归必须有终止条件,没有终止条件会发生栈内存溢出错误
- 递归即使有终止条件,即使终止条件是正确的,也可能发生栈内存溢出错误,因为递归的太深了
2.10、数组
-
数组一旦创建,长度不可变
-
//静态初始化 int[] array = {0, 1, 2, 3, 4, 5}; //动态初始化,默认值为0 int[] array = new int[10];
-
//静态数组传参 printArray(new int[]{1, 2, 3}); //动态数组传参 printArray(new int[3]);
-
main方法中String数组
- JVM调用main方法会传一个String数组(String[] args)过来,该数组长度默认为0(args不为null)
-
这个数组是留给用户的,用户在控制台上输入参数,这个参数会被自动转换为"String[] args",例如这样运行程序java Test abc edf xyz,那么JVM会自动将abc edf xyz通过空格的方式进行分离,分离完成后,自动放到String数组当中,所以main方法中String的数组主要是用来接收用户输入参数的
-
二维数组的初始化
//静态初始化 int[][] a = { {1, 2, 3}, {10, 20, 30,}, {100, 200, 300}, {1000, 2000, 3000} }; //动态初始化,创建一个四行三列的二维数组 int[][] a = new int[4][3];
-
二维数组的长度
//表示a中含有一维数组的个数,输出4 System.out.println(a.length); //表示a[0]这个一位数组的元素个数,输出3 System.out.println(a[0].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(); } for (int[] i : a) { for (int j : i) { System.out.print(j + " "); } System.out.println(); }
3、面向对象程序设计
3.1、类和对象
-
对象内存示例图
class Student{ int id; String name; int age; } public class ObjectTest{ public static void main(String[] args) { Student stundet = new Student(); } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SnjXNoRK-1665645907879)(java/object.png)]
-
new运算符在堆内存中开辟的内存空间称为对象
-
引用是一个变量(可以是局部变量,也可以是成员变量),该变量保存的内存地址指向了堆内存当中的对象
-
局部变量在栈内存中存储,成员变量中的实例在堆内存的Java对象内部存储
-
什么时候程序在运行的时候会出现空指针异常:
- **空引用访问实例相关的数据,**因为实例相关的数据就是对象相关的数据,这是在访问时,必须有对象的参与,当空引用的时候,对象不存在,访问这些实例数据一定会出现空指针异常
- 实例相关的数据包括:实例变量和实例方法
3.2、JVM内存总结
-
JVM主要包括三块内存空间:栈内存,堆内存和方法区内存
-
堆内存和方法区内存各有一个,一个线程一个栈内存
-
方法在调用时该方法所需要的内存空间在栈内存中分配,称为入栈;方法之行结束之后该方法所属的内存空间释放,称为出栈
-
栈内存中主要存储方法体中的局部变量
-
方法的代码片段以及整个类的代码片段都被存储到方法区内存当中,在类加载的时候,这些代码片段会被载入
-
在程序执行过程中使用new运算符创建的Java对象存储在堆内存中,对象内部有实例变量,所以实例变量存储在堆内存当中
-
变量分类:
-
局部变量(方法体中声明,存储在栈内存中)
-
成员变量(方法体外声明)
-
实例变量(前边修饰符没有static,存储在堆内存中)
成员变量之实例变量,属于对象级别的变量,这种变量必须现有对象才能有实例变量
实例变量没有手动赋值的时候,系统会在构造方法执行过程中开辟内存空间,完成初始化
系统在默认赋值的时候也是在构造方法的执行当中完成赋值
- 静态变量(前边修饰符有static,存储在方法区内存当中)
-
-
三块内存区当中变化最频繁的是栈内存,最先有数据的是方法区内存,垃圾回收器主要针对的是堆内存
-
垃圾回收器(自动垃圾回收机制,JC机制)什么时候会考虑将某个Java对象的内存回收:当堆内存中的Java对象成为垃圾数据时候会被垃圾回收器回收。
什么时候堆内存中的Java对象会变成垃圾:没有更多的引用指向该对象的时候,这个对象无法被访问,因为访问对象只能通过引用的方式访问
3.3、封装
-
封装之后,对于事物来说,看不到这个事物比较复杂的一面,只能看见事物简单的一面,复杂性封装,对外提供简单的操作入口
-
封装之后才形成真正的对象,真正的独立体
-
封装就意味着以后的程序可以重复使用,并且这个事物适应性比较强在任何场所都可以使用
-
封装之后,对于事物本身提高了安全性
3.4、访问修饰符
-
public对所有类可见,使用对象:类、接口、变量、方法。
被public修饰的成员 ,可以在任何一个类中被调用,不管同包或不同包,是权限最高的一个修饰符。
-
protected对同一包内的类和所有子类可见,使用对象:变量、方法,不能修饰外部类。
被protected修饰的成员,能在定义它们的类中,同包的类中被调用。如果有不同包的类想调用它们,那么这个类必须是定义它们的类的子类。
-
default(缺省,什么也不写)在同一包内可见,不使用任何修饰符,使用对象:类、接口、变量、方法。
默认权限即同包权限,同包权限的元素只能在定义它们的类中,以及同包的类中被调用。
-
private在同一类内可见,使用对象:变量、方法,不能修饰外部类。
被private修饰的成员,只能在定义它们的类中使用,在其他类中不能调用。 -
访问权限图示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gmVKAWYA-1665645907884)(java/Modifier.png)]
3.5、构造方法
-
构造方法无返回值,构造方法的方法名与类名保持一致
-
构造方法存在的意义是通过构造方法的调用可以创建对象,构造方法一旦调用就会创建对象
-
构造方法调用:new 构造方法名(实参列表)
//User()为无参数的构造方法,new User()先执行,创建对象并返还给User对象 User user = new User();
-
构造方法实际上执行结束后有返回值,但是不用return语句返回,由系统自动返回,返回值类型是构造方法所在类的类型
-
一个类中若不定义构造方法,该类中会隐含定义一个方法体为无参数构造方法,这个构造方法称为默认构造方法,当且仅当类中没有明确定义任何构造方法系统才会自动调用它
-
当一个类显示的将构造方法定义出来了,那么系统将不再提供默认构造方法,建议开发中手动的为当前类提供无参数构造方法
-
构造方法支持重载机制,在一个类当中可以编写多个构造方法
-
构造方法的作用:
- 创建对象
- 创建对象的同时,初始化实例变量的内存空间(给实例变量赋值)
-
对象和引用的概念
- 对象:在使用new运算符在堆内存中开辟的内存空间称为对象
- 引用:是一个变量,不一定是局部变量,还可能是成员变量,引用保存了内存地址,指向了堆内存当中的对象
- 所有访问实例相关的数据,都需要通过"引用."的方式访问,因为只有通过引用才能找到对象
- 只有一个空的引用,访问对象的实例相关数据会出现空指针异常
3.6、this关键字
-
this是一个引用,this是一个变量,this变量中保存了内存地址指向了自身,this存储在JVM堆内存Java对象中
-
创建100个Java对象,每一个对象都有this,也就是说有100个不同的this
-
this可以出现在实例方法(修饰符没有static)中,this指向当前正在执行这个动作的对象(this代表当前对象)
-
this在多数情况下可以省略不写
-
this不能使用静态方法(修饰符含有static)当中
-
this不能省略的情况:用来区分局部变量和实例变量的时候,this.不能省略
-
this方法调用构造方法
public Student(String name, int age, boolean sex) { this.name = name; this.age = age; this.sex = sex; } //这种方式不会创建新的Java对象,但同时又可以达到调用其他的构造方法 //this()只能出现在构造方法第一行 public Student() { this("张三", 20, true); }
-
this可以使用在哪里
- 可以使用在实例方法当中代表当前对象(this.变量)
- 可以使用在构造方法当中,通过当前的构造方法调用其他的构造方法(this(实参)),this()只能出现在构造方法第一行
-
静态方法和实例方法相互调用实例
public class ThisTest { //实例变量num int num; public static void method1(){ //带有static方法中调用带有static方法(静态方法中调用静态方法) //完整方式调用doSome Test.doSome(); //省略方式调用doSome doSome(); //带有static方法中调用不带static方法(静态方法中调用实例方法) //调用doOther ThisTest thisTest = new ThisTest(); thisTest.doOther(); //带有static方法(静态方法)中访问实例变量 //访问变量i ThisTest thisTest1 = new ThisTest(); int i = thisTest1.num; System.out.println("method1 is invoked"); } public void method2(){ //不带static方法中调用带有static方法(实例方法中调用静态方法) //完整方式调用doSome Test.doSome(); //省略方式调用doSome doSome(); //不带static方法中调用不带static方法(实例方法中调用实例方法) //完整方式调用doOther this.doOther(); //省略方式调用doOther doOther(); //不带static方法(实例方法)中访问实例变量 //完整方式访问i int i1 = this.num; //省略方式访问i int i2 = num; System.out.println("method2 is invoked"); } public static void doSome(){ System.out.println("doSome method is invoked"); } public void doOther() { System.out.println("doOther method is invoked"); } public static void main(String[] args) { //完整方式调用method1 ThisTest.method1(); //省略方式调用method1 method1(); //调用method2 ThisTest thisTest = new ThisTest(); thisTest.method2(); } }
-
//带有static的方法,其实既可以采用"类名."的方式访问,也可以采用"引用."的方式访问,但是即使采用"引用."的方式去访问,实际上执行的时候和引用指向的对象无关 ThisTest thisTest = null; //doSome()方法是静态方法,不会报空指针异常 thisTest.doSome();
3.7、static关键字
-
在带有static的方法中不能直接访问实例变量和实例方法,因为实例变量和实例方法都需要对象的存在,而static方法中是没有this的,也就是说当前对象是不存在的,自然也是无法访问当前对象的实例变量和实例方法
-
静态变量在类加载的时候初始化,内存在方法区中开辟
-
什么时候声明为实例变量:
-
所有对象都有这个属性,但是这个属性的值会随着对象的变化而变化
-
什么时候声明为静态变量:
-
所有对象都有这个属性,并且所有对象的这个属性的值是一样的,建议定义为静态变量,节省内存开销
-
静态代码块
-
可以使用static关键字来定义静态代码块
static{ //java语句 }
-
静态代码块在类加载时执行,并且只执行一次
-
静态代码块在一个类中可以编写多个,并且遵循自上而下的顺序依次执行
-
静态代码块的作用:静态代码块是Java为程序员准备的一个特殊时刻,这个特殊时刻被称为类加载时刻,若希望在此时刻执行一段特殊的程序,这端程序可以直接放在静态代码当中
-
通常在静态代码块当中完成预备工作,先完成数据的准备工具,例如:初始化连接池,解析XML配置文件
-
-
实例代码块
{ //java语句 }
- 实例代码块可以编写多个,并且遵循自上而下的顺序依次执行
- 实例代码块在构造方法执行之前执行,构造方法执行一次,实例代码块就执行一次
- 实例代码跨也是Java语言为程序员准备的一个特殊时刻,这个特殊时刻被称为;对象初始化时刻
-
方法什么时候声明为静态:
- 方法描述的是动作,当所有的对象都执行这个动作的时候,最终产生的影响是一样的,那么这个动作已经不再属于某一个对象动作了,可以将这个动作提升为类级别的动作,模板级别的动作
3.8、继承
-
继承的基本作用是:代码复用
-
继承的重要作用是:有了继承才有了以后的方法覆盖和多态
-
Java语言中只支持单继承,一个类不能同时继承很多类
-
B类继承于A类,则称A类为父类、基类,超类、superclass;称B类为子类、派生类、subclass
-
私有的不支持继承,构造方法不支持继承,其他均可继承
-
Java语言中只支持单继承,但是一个类也可以间接继承其他类,例如:C继承B,B继承A,A继承T,C间接继承B、A、T类
-
Java语言中若一个类没有显示的继承任何类,那么该类默认继承JavaSE中提供的java.lang.object类
3.9、方法重写
-
方法重载
- 方法重载又称overload
- 方法重载什么时候使用:
- 当一个类当中,方法完成的功能是相似的,建议方法名相同,这样方便编程,就像在调用一个方法似的,代码美观
- 什么条件满足之后构成方法重载
- 在同一类当中
- 方法名相同
- 参数列表(类型,顺序,个数)不同
- 方法重载和什么无关
- 和方法的返回值类型无关
- 和方法的修饰符列表无关
-
方法重写
- 方法重写(override)又称方法覆盖
- 什么时候使用方法重写
- 当父类中的方法已经无法满足当前子类的业务需求,子类有必要将父类中继承过来的方法进行重新编写,这个重新编写的过程称为方法重写
- 代码满足什么条件后,就构成了方法的覆盖:
- 方法重写发生具有继承关系的父子类当之间
- 方法名相同、返回值类型相同、形参列表相同
- 访问权限不能更低,可以更高(private<default<protected<public)
- 抛出异常不能更多,可以更少
- 私有方法不能继承,所以不能重写;构造方法不能继承,所以不能重写,静态方法不存在重写
3.10、多态
-
相关概念
- 向上转型(upcasting)
- 子类型转换成父类型,又被称为自动类型转换
- 向下转型(downcasting)
- 父类型转换成子类型,又被称为强制类型转换,需要加强制类型转换符
- 无论是向上转型还是向下转型,两种类型之间必须有继承关系
- 向上转型(upcasting)
-
多态语法实例
public class PolymorphismSyntax { public static void main(String[] args) { /* 1、Animal和Cat之间存在继承关系,Animal是父类,Cat是子类 2、new Cat()创建的对象类型是Cat,a引用的数据类型是Animal,可见他们进行了类型转换(子类型->父类型) 3、子类型转换成父类型,称为向上转型,属于自动类型转换 4、Java中允许这种语句:父类型引用指向子类型对象 */ Animal a = new Cat(); /* 1、Java程序分为编译期和运行期 2、先分析编译阶段,再分析运行阶段,编译不通过无法运行 3、编译阶段编译器检查a变量的引用的数据类型为Animal,由于Animal.class字节码文件中有move方法,所以编译通过,这个过程称为静态绑定,编译阶段绑定,只有静态绑定成功后才有后续的运行 4、在程序运行阶段,JVM堆内存当中真实创建的对象是Cat对象,程序在运行时调用的方法是Cat对象的move方法此时发生了程序的动态绑定,运行阶段绑定 5、无论Cat类中有没有重写move方法,运行阶段一定调用的是Cat对象的move方法,因为底层真实对象是Cat对象 6、父类型引用指向子类型对象这种机制导致程序在编译阶段的绑定和运行阶段的绑定呈现出两种不同的形态,这种机制称为一种多态语法机制 7、编译期a.move()调用的是Animal中的move方法,a属于Animal类型的对象 8、运行期a.move()调用的是Cat中的move方法,a指向的是Cat类型对象 */ a.move(); /* 无法调用的原因: 编译阶段编译器检测到a的类型是Animal类型,在Animal.class字节 * 码文件中找不到catchMouse方法,静态绑定失败,导致编译失败,无法运行 */ a.catchMouse(); /* 需求:让a执行catchMouse方法 将a强制类型转换成Cat类型 1、a的类型是Animal(父类),转换成Cat类型(子类),称为向下转型 2、向下转型也需要两种类型之间具有继承关系,不然编译报错,强制类型转换需要加强制类型转换符 3、什么时候需要使用向下转型: 当调用的方法或访问的属性是子类型特有的,在父类型当中不存在,必须进行向下转型性 */ Cat c = (Cat) a; c.catchMouse(); /* 1、编译没有问题,因为编译器检测的b的数据类型是Animal,Animal和Cat之间存在继承关系,父类型(Animal)转换成子类型 2、在运行阶段会报错,因为JVM堆内存当中真实存在的对象是Bird类型,Bird对象无法转换成Cat类型,此时出现了异常:java.lang.ClassCastException(类型转换异常),这种异常总是在向下转型(强制类型转换)时发生 3、向上转型(自动类型转换)只要编译通过,运行就不会报错 4、向下转型(强制类型转换)编译通过,运行可能报错 */ Animal b = new Bird(); Cat c1= (Cat) b; /* 5、怎么避免java.lang.ClassCastException(类型转换异常):使用instanceof运算符可以避免类型转换异常 6、instanceof运算符: 6.1、语法格式:(引用 instanceof 数据类型名) 6.2、执行结果为布尔类型(true/false) 6.3、例如:a instanceof Animal 结果为true:a指向的对象是Animal类型 结果为false:a指向的对象不是Animal类型 7、Java规范中要求:在进行强制类型转换之前,建议用instanceof进行判断,避免类型转换异常的发生 */ if (b instanceof Cat){ Cat cat = (Cat) b; cat.catchMouse(); } if (b instanceof Bird){ Bird bird = (Bird) b; bird.eatWorm(); } } } class Animal{ public void move(){ System.out.println("动物在移动"); } } class Cat extends Animal{ @Override public void move() { System.out.println("猫在走路"); } public void catchMouse(){ System.out.println("猫在捉老鼠"); } } class Bird extends Animal{ @Override public void move() { System.out.println("鸟在飞翔"); } public void eatWorm(){ System.out.println("鸟在吃虫子"); } }
-
多态的作用:
-
降低程序的耦合度,提高程序的扩展力
-
能使用多态就使用多态,父类型引用指向子类型对象
-
面向抽象编程,尽量不要面向具体编程
-
实例
/** * 多态的作用实例: * 主人养宠物问题: * 主人类:主人可以喂养宠物 * 宠物类:宠物可以吃东西 */ public class PolymorphismTest { public static void main(String[] args) { Owner owner = new Owner(); Cat cat = new Cat(); Dog dog = new Dog(); //Pet pet = cat = new Cat() owner.feed(cat); //Pet pet = dog = new Dog() owner.feed(dog); } } /** * 主人类面向的是一个抽象的Pet,不再面向具体的宠物 * 提倡面向抽象编程,不要面向具体编程 * 面向抽象编程的好处是,耦合度低,扩展力强 */ class Owner{ public void feed(Pet pet){ pet.eat(); } } class Pet{ public void eat(){ System.out.println("宠物吃东西"); } } class Cat extends Pet{ @Override public void eat(){ System.out.println("猫吃鱼"); } } class Dog extends Pet{ public void eat(){ System.out.println("狗吃肉"); } }
-
3.11、super关键字
- super和this对比:
- this:
- this能出现在实例方法和静态方法当中
- this的方法是"this.“和"this()”
- this不能使用在静态方法当中
- "this."大部分情况下可以省略,在区分局部变量和实例变量的时候不可以省略
- "this()"只能出现在构造方法第一行,通过当前的构造方法去调用本类中的其他构造方法,目的是代码复用
- super:
- super能出现在实例方法和静态方法当中
- super的方法是"super.“和"super()”
- super不能使用在静态方法当中
- "super."大部分情况下可以省略,当父类和子类有同名属性,并且想通过子类访问父类属性时不可以省略
- "super()"只能出现在构造方法第一行,通过当前的构造方法去调用父类中的构造方法,目的是创建子类对象的时候先初始化父类型特征
- this:
- this()和super()不能共存,只能有一个出现在构造方法第一行
- super不是引用,super也不保存内存地址,super也不指向任何对象,super只是代表当前父类型的特征
3.12、final关键字
-
final是一个关键字,表示最终的、不可变的
-
final修饰的类无法继承
-
final修饰的方法无法被覆盖(重写)
-
final修饰的变量一旦赋值之后,不可重新赋值
-
final修饰的实例变量必须手动赋值,不能采用系统默认值
//两种方式等价 //第一种方式 final int num = 10; //第二种方式 final int num; public public Constructor(){ this.num 10; }
-
final修饰的实例变量是不可变的,这种变量一般和static联合使用,被称为"常量",
常量的定义语法格式:public static final 类型 常量名 = 值
3.13、抽象类
-
实例图示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5ZvKx489-1665645907888)(java/abstract.png)]
-
**抽象类无法实例化,无法创建对象,抽象类是用来被子类继承的:**抽象类是类和类之间有共同特征,将这些具有共同特征的类再进一步抽象形成了抽象类,由于类本身是不存在的,所以抽象类无法创建对象
-
抽象类和抽象类之间可能会有共同特征,还可以进一步再抽象
-
抽象类属于引用数据类型
-
final和abstract不能联合使用,这两个关键字是对立的
-
抽象类的子类可以是抽象的
-
抽象类虽然无法实例化,但是抽象类有构造方法,这个构造方法供子类使用
-
抽象方法表示没有实现的方法,没有方法体的方法
-
抽象类中不一定有抽象方法,但是抽象方法必须出现在抽象类当中
-
一个非抽象的类继承抽象类,必须将抽象类中的抽象方法实现
3.14、接口
-
接口也是一种引用数据类型
-
接口是完全出抽象的,抽象类是半抽象的,也可以说借口是特殊的抽象类
-
接口可以多继承,一个接口可以继承多个接口
-
一个类可以同时实现多个接口(类似于多继承),弥补了单继承带来的缺陷
-
接口中只包含两部分内容:常量和抽象方法
-
接口中所有的元素都是public修饰的
-
接口中的抽象方法定义时public abstract修饰符可以省略
-
接口中的常量定义时的public static final修饰符可以省略
-
一个非抽象的类实现接口的时候,必须将接口中的所有方法加以实现
-
接口和接口之间虽然没有继承关系,但是写代码的时候可以互转,编译没有问题,但是运行是可能会出类转换异常(java.lang.ClassCastException)
public class InterfaceTest { public static void main(String[] args) { A a = new D(); if (a instanceof B){ B b = (B)a; b.method2(); } } } interface A { void method1(); } interface B { void method2(); } interface C { void method3(); } class D implements A, B, C { @Override public void method1() { System.out.println("method1"); } @Override public void method2() { System.out.println("method2"); } @Override public void method3() { System.out.println("method3"); } }
-
继承和实现可以同时存在,extends在前,implements在后
-
接口可以解耦合,解的是调用者和实现者之间的耦合
package com.java.basic; /** * 菜单是一个接口,顾客面向菜单点菜,调用接口,厨师负责把菜单上的菜做好,实现接口 * 菜单的存在,让顾客和厨师解耦合 */ public class InterfaceTest { public static void main(String[] args) { FoodMenu chineseChef = new ChineseChef(); FoodMenu westernChef = new WesternChef(); Customer customer1 = new Customer(chineseChef); customer1.orderFood(); Customer customer2 = new Customer(westernChef); customer2.orderFood(); } } interface FoodMenu { void DanChaoFan(); void ShuiZhuYu(); } class ChineseChef implements FoodMenu { @Override public void DanChaoFan() { System.out.println("中餐厨师做的蛋炒饭"); } @Override public void ShuiZhuYu() { System.out.println("中餐厨师做的水煮鱼"); } } class WesternChef implements FoodMenu { @Override public void DanChaoFan() { System.out.println("西方厨师做的蛋炒饭"); } @Override public void ShuiZhuYu() { System.out.println("西方厨师做的水煮鱼"); } } class Customer { private FoodMenu foodMenu; public Customer() { } public Customer(FoodMenu foodMenu) { this.foodMenu = foodMenu; } public FoodMenu getFoodMenu() { return foodMenu; } public void setFoodMenu(FoodMenu foodMenu) { this.foodMenu = foodMenu; } public void orderFood() { foodMenu.DanChaoFan(); foodMenu.ShuiZhuYu(); } }
-
类型和类型之间的关系:
- is a
- cat is an animal,凡是能够满足is a的表示继承关系
- A extends B
- has a
- I have a pen,凡是能够满足has a的表示关联关系,关联关系通常以属性方式存在
- A{B b;}
- like a
- chef like a FoodMenu,凡是能够满足like a的表示实现关系,实现关系通常是类实现接口
- A implements B
- is a
-
抽象类和接口区别
- 抽象类是半抽象的,接口是抽象的
- 抽象类中有构造方法,接口中没有构造方法
- 接口和接口之间支持多继承,类和类之间只支持单继承
- 一个类可以同时实现多个接口,一个抽象类只能继承一个类
- 接口中只允许出现常量和抽象方法
3.15、Object类中的常用方法
-
toString()方法
-
源代码
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
-
所有类中的toString方法需要重写,重写规则越简单越好
-
System.out.prinln()这里会自动调用引用的toString()方法
-
-
euqals()方法
-
源代码
public boolean equals(Object obj) { return (this == obj); }
-
所有类中的equals()方法需要重写,object类中的equlas()方法比较的是两个对象的内存地址,而需要比较的是内容
-
基本数据类型判断是否相等用"==",引用数据类型判断相等需要equlas()方法
-
重写equals方法的基本格式
class Date{ int year; int month; int day; public boolean equals(Object o) { if (o == null || !(o instanceof Data)) return false; if (this == o) return true; Date date = (Date) o; return year == date.year && month == date.month && day == date.day; } }
-
-
finalize()方法(JDK9以后被弃用)
-
源代码
protected void finalize() throws Throwable { }
-
finalize()方法是protected修饰的
-
这个方法不需要程序员调用,JVM的垃圾回收器负责调用
-
finalize()方法实际上是SUN公司为Java程序员提供的一个时机,叫垃圾销毁时机,如果希望在对象销毁时机执行一段代码的话,这段代码需要写在finalize()方法当中
-
-
hashcode()方法
- hashcode()方法返回的是哈希码,实际上就是一个Java对象的内存地址,经过哈希算法得出的一个值,所以hashcode()方法的执行结果可以等同于看做一个Java对象的内存地址
-
clone()方法
3.16、内部类
-
内部类:在类的内部有定义了一个新的类,被称为内部类
-
内部类的分类:
- 静态内部类(类似于静态变量)
- 实例内部类(类似于实例变量)
- 局部内部类(类似于局部变量)
-
匿名内部类是局部内部类的一种,因为这个类没有名字而得名
-
匿名内部类实例
public class InnerClassTest { public static void main(String[] args) { MyMath myMath = new MyMath(); myMath.mySum(new Compute() { @Override public int sum(int a, int b) { return a + b; } }, 100, 200); } } interface Compute { int sum(int a, int b); } class MyMath { public void mySum(Compute compute, int a, int b) { System.out.println(a + " + " + b + " = " + compute.sum(a, b)); }
4、Java进阶
4.1、常见类
4.1.1、String类
4.1.2、八种包装类
-
八种包装类(java.lang包下)
- Byte,Short,Integer,Long,Float,Double,Character,Boolean
-
拆箱和装箱
//基本数据类型转换为引用数据类型称为装箱 Integer i = new Integer(123); //引用数据类型转换为基本数据类型称为拆箱 int value = i.intValue();
-
负责拆箱的方法
byteValue(),doubleValue(),floatValue(),intValue(),longValue(),
shortValue()
-
自动拆箱和自动装箱
//自动装箱:基本数据类型自动转换成包装类 Integer x = 100; //自动拆箱:包装类自动转换为基本数据类型 int y = x;
-
//a和b为两个不同对象,保存两个不同的内存地址,==比较的是内存地址,输出false Integer a = 1000; Integer b = 1000; // "=="不会触发自动拆箱机制,只有+、-、*、/、等才会触发 System.out.println(a == b);
-
//Java为了提高程序的执行效率,将-128到127之间所有的包装对象都创建好,放到方法区的整数型常量池当中,目的是为了只要使用这个区间的数据不需要创建对象了,直接从整数型常量池当中使用即可 Integer c = 127; Integer d = 127; //输出true System.out.println(c == d);
-
//将字符串转换成整数型 int f = Integer.parseInt("123");
-
String,int,Integer相互转换
//String转换成int int g = Integer.parseInt("100"); //int转换成String String h = 200 + ""; //int转换成Integer Integer i = 300; //Integer转换成int int j = i; //String转换成Integer Integer k = Integer.valueOf("400"); //Integer转换成String String l = String.valueOf(500);
4.1.3、日期相关类
public class DataTest {
public static void main(String[] args){
Date date = new Date();
/*
*yyyy年 MM月 dd日 HH时 mm分 ss秒 SSS毫秒
* HH大写为24小时制,hh小写为12小时制
*/
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日,EEEE,HH时mm分ss秒SSS毫秒");
String dataFormat = simpleDateFormat.format(date);
System.out.println(dataFormat);
//将字符串转化成Date类型
String dateString = "2008-08-08 08:08:08 888";
SimpleDateFormat simpleDateFormat1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
try {
Date date1 = simpleDateFormat1.parse(dateString);
System.out.println(date1);
} catch (ParseException e) {
e.printStackTrace();
}
//获取从1970年1月1日0时0分0秒到当前时间的总毫秒数
System.out.println(System.currentTimeMillis());
//测试所花费的时间
long begin = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
}
long end = System.currentTimeMillis();
System.out.println(end - begin);
}
}
4.1.4、数字相关类
public class DecimalFormatTest {
public static void main(String[] args) {
//#代表任意数字 ,代表千分位 .代表小数点
//加入千分位,保留两位小数
DecimalFormat decimalFormatTest = new DecimalFormat("#,###.##");
System.out.println(decimalFormatTest.format(12355545.65));
//加入千分位,保留四位小数,不够补0
DecimalFormat decimalFormatTest1 = new DecimalFormat(",###.0000");
System.out.println(decimalFormatTest1.format(12355545.65));
//精度极高的大数据BigDecimal
System.out.println(0.1 + 0.2);
BigDecimal bigDecimal1 = new BigDecimal("10.3");
BigDecimal bigDecimal2 = new BigDecimal("20.6");
System.out.println(bigDecimal1.add(bigDecimal2));
System.out.println(bigDecimal1.subtract(bigDecimal2));
System.out.println(bigDecimal1.multiply(bigDecimal2));
System.out.println(bigDecimal1.divide(bigDecimal2));
System.out.println(bigDecimal1.abs());
}
}
4.1.5、Random类
public class RandomTest {
public static void main(String[] args) {
Random random = new Random();
//随机产生一个int范围内的数
System.out.println(random.nextInt());
//随机产生一个0到100范围内的数,不包括101
System.out.println(random.nextInt(101));
//生成5个不重复的随机数[0 - 4]
int[] array = new int[5];
Arrays.fill(array, -1);
array[0] = random.nextInt(5);
for (int i = 1; i < array.length; i++) {
int rand = random.nextInt(5);
int j = 0;
for (; j < i; j++) {
if (array[j] == rand) {
i--;
break;
}
}
if (j == i){
array[i] = rand;
}
}
for (int i : array) {
System.out.println(i);
}
}
}
4.1.6、枚举类型
- 如果一个方法的执行结果有多个且有限,可以一一列举出来的,此时需要枚举类型
public class EnumTest {
public static void main(String[] args) {
System.out.println(divide(0, 3) ? "计算成功" : "计算失败");
System.out.println(divide(3, 0) ? "计算成功" : "计算失败");
System.out.println(divideByEnum(3, 0) == Result.SUCESS ? "计算成功" : "计算失败");
System.out.println(divideByEnum(0, 3) == Result.SUCESS ? "计算成功" : "计算失败");
}
public static boolean divide(int a, int b) {
try {
int c = a / b;
return true;
} catch (Exception e) {
return false;
}
}
public static Result divideByEnum(int a, int b) {
try {
int c = a / b;
return Result.SUCESS;
} catch (Exception e) {
return Result.FAIL;
}
}
}
/*
* 枚举变异之后也是class文件
* 枚举属于引用数据类型
* 枚举中的每一个只可以看作是常量
*/
enum Result{SUCESS,FAIL}
4.2、异常处理
-
异常在Java中以类的形式存在,每一个异常类都可以创建对象
-
Object下有Throwable(可抛出的)
- Throwable下有两个分支:Error(不可处理的)和Exception(可处理的)
- Exception下有两个分支:编译时异常(Exception的其他子类 )和运行时异常(RuntimeException)
- Throwable下有两个分支:Error(不可处理的)和Exception(可处理的)
-
编译时异常比徐在编译(编写)阶段预先处理,如果不处理编译器会报错,因而得名
-
编译时异常和运行时异常都发生在运行阶段,编译阶段异常是不会发生的。程序只有运行阶段才可以new对象,因为异常的发生就是new异常对象
-
编译时异常和运行时异常的区别
- 编译时异常一般发生的概率比较高
- 运行时异常一般发生的概率比较低
-
异常的处理方式:
- 在方法生命的位置上使用throws关键字
- 使用try-catch语句捕捉异常
-
只要异常没有捕捉,采用上报的方式,此方法的后续代码不会执行
-
try语句块中的某一行出现异常,该行后面的代码不会执行,try-catch捕获异常之后后续代码可以执行
-
catch后面的小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类型
-
catch可以写多个,但是在写多个的时候从上到下遵循从子类到父类的原则
-
try { FileInputStream fileInputStream = new FileInputStream("D:\\test.txt"); fileInputStream.read(); } catch (FileNotFoundException e) { System.out.println("文件不存在"); } catch (IOException e) { System.out.println("读取文件时出现错误"); }
-
处理编译时异常,上报和捕捉的选择标准:如果希望调用者来处理选择throws,其他方式使用捕捉(try-catch)的方式
-
异常对象的两个重要方法:
- getMessage()获取异常简单的描述信息
- printStackTrace()打印异常追踪的堆栈信息
-
打印异常堆栈追踪信息的时候才用了异步线程的方式打印
-
在finally子句中的代码是最后执行的,并且是一定执行的,即使try语句块中的 代码出现了异常,finally语句必须和try一起出现,不能单独编写
-
退出JVM(System.exit(0))后finally语句不执行
-
public class ExceptionTest { public static void main(String[] args) { //输出结果100 System.out.println(m()); } //方法体中的语句必须遵循自上而下的执行顺序, public static int m() { int i = 100; try { return i; } finally { i++; } } /* 反编译结果 * int i = 100; * int j = i; * i++; * return j; */ }
-
final,finally,finalize区别:
- final是一个关键字,表示不变的,最终的
- finally是一个关键字,和try联合使用,使用在异常处理机制中,在finally语句块中的代码是一定会执行的
- finalize是标识符,finalize()是Object类中的一个方法,作为方法名出现
-
自定义异常
public class ExceptionTest { public static void main(String[] args) { MyException1 myException1 = new MyException1("用户名不能为空"); System.out.println(myException1.getMessage()); myException1.printStackTrace(); MyException1 myException2 = new MyException1("用户名不能为空"); System.out.println(myException2.getMessage()); myException2.printStackTrace(); } } class MyException1 extends Exception{ public MyException1() { } public MyException1(String message) { super(message); } } class MyException2 extends RuntimeException{ public MyException2() { } public MyException2(String message) { super(message); } }
-
异常在实际开发中的应用
public class StackTest { public static void main(String[] args) { Stack stack = new Stack(10); for (int i = 0; i < 11; i++) { try { stack.Push(new Object()); } catch (MyStackOperationException e) { System.out.println(e.getMessage()); } } for (int i = 0; i < 11; i++) { try { stack.Pop(); } catch (MyStackOperationException e) { System.out.println(e.getMessage()); } } } } class Stack { private final Object[] element; private int index; public Stack() { //默认初始化栈容量为20 this.element = new Object[20]; this.index = -1; } public Stack(int size) { this.element = new Object[size]; this.index = -1; } public void Push(Object obj) throws MyStackOperationException { if (index >= element.length - 1) { throw new MyStackOperationException("栈已满,无法进栈"); } element[++index] = obj; System.out.println(obj + "已进栈,栈顶元素指向" + index); } public Object Pop() throws MyStackOperationException { if (index == -1) { throw new MyStackOperationException("栈已空,无法出栈"); } System.out.println(element[index] + "已出栈,栈顶指针指向" + (index - 1)); return element[index--]; } } class MyStackOperationException extends Exception{ public MyStackOperationException() { } public MyStackOperationException(String message) { super(message); } }
-
重写之后的方法不能比重写之前的方法抛更多的异常,可以更少
4.3、集合
4.3.1、概述
-
集合中不能直接存储基本数据类型,集合也不能直接存储Java对象,集合当中存储的都是Java对象的内存地址
-
Collection集合继承结构图
-
Collection继承图注释
-
Iterable,接口,所有集合元素都是可迭代(遍历)的
- iterator()方法
-
Iterator,接口,集合的迭代器对象
- booelan hasNext()方法,如果仍有元素可以迭代,返回true
- Object next()方法,返回迭代的下一个元素
- remove()方法
-
Collection,接口,所有集合继承Interable的含义是,所有集合都是可迭代的
Iterator it = “Collection对象”.iterator()//it是迭代器对象
-
List,接口,存储的元素特点是有序可重复,集合中的元素有下标
-
Set,接口,存储的元素特点是无序不可重复,集合中的元素没有下标
-
ArrayList,类,底层采用了数组这种数据结构,是非线程安全的
-
LinkedList,类,底层采用了双向链表这种数据结构
-
Vector,类,底层采用了数组这种数据结构,是线程安全的,但是效率低,使用较少,保证线程安全有别的解决方案
-
HashSet,类,底层实际上new了一个HashMap集合,HashMap集合底层采用了哈希表这种数据结构
-
SortedSet,接口,存储的元素特点是无序不可重复,但是集合中的元素可排序
-
TreeSet,类,底层实际上new了一个TreeMap集合,TreeMap集合底层采用了二叉树这种数据结构
-
-
Map集合继承结构图
-
Map继承图注释
- Map,接口,Map集合以key和value键值对的方式存储元素,key和value都是存储Java对象的内存地址,所有Map集合的key的特点是无序不可重复
- HashMap,类,HashMap集合底层采用了哈希表这种数据结构,是非线程安全的
- Hashtable,类,底层采用了哈希表这种数据结构,是线程安全的,但是效率低,使用较少,保证线程安全有别的解决方案
- SortedMap,接口,存储的元素特点是无序不可重复,但是集合中key部分的元素可排序
- Properties,类,以key和value键值对的方式存储元素,而且key和value只支持String类型
- TreeMap,类,TreeMap集合底层采用了二叉树这种数据结构
4.3.2、Collection接口
-
常用方法:
-
Collections.synchronized(),将集合转换成线程安全的
-
boolean add(Object o),往集合中添加元素
-
int size(),获取集合中元素个数
-
void clear(),清空集合
-
boolean contains(Object o),判读集合中是否包含该元素
-
contains()方法会调用equals()方法
-
存放在集合中的元素需要重写equals()方法
public class CollectionTest { public static void main(String[] args) { Collection<Object> c = new ArrayList<Object>(); User1 user1 = new User1("zhangsan"); User1 user2 = new User1("zhangsan"); c.add(user1); //重写的话返回true,调用重写的equals方法 //未重写比较内存地址,两个不同的对象内存地址不同,返回false System.out.println(c.contains(user2)); } } class User1 { private String username; public User1() { } public User1(String username) { this.username = username; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User1 user1 = (User1) o; return Objects.equals(username, user1.username); } }
-
-
boolean remove(Object o),删除集合中的某个元素,在迭代集合元素的过程中,不能调用集合对象的remove()方法
-
remove()方法会调用equals()方法
String s1 = "abc"; String s2 = "abc"; c.add(s1); //s1被移除 c.remove(s2);
-
-
booelan isEmpty(),判断集合是否为空
-
Object[] toArray(),将集合转换成Object数组
-
-
集合迭代(遍历)
-
Iterator,接口,集合的迭代器对象
- booelan hasNext()方法,如果仍有元素可以迭代,返回true
- Object next()方法,返回迭代的下一个元素
- remove()方法,
-
迭代代码
public class CollectionTest { public static void main(String[] args) { Collection<Object> c = new ArrayList<Object>(); c.add("abcd"); c.add(123); c.add(true); c.add(3.14); c.add(new Object()); for (Object o : c) { System.out.println(o); } } }
-
4.3.3、List接口
-
List集合存储元素特点:有序可重复,有序就是List集合中的元素有下标,以0开始,以1递增,可重复就是可以存储相同元素,存储1之后还可以存储1
-
List特有方法:
- void add(int index,Object element),在指定位置添加元素
- Object get(int index) ,获取指定位置元素
- Object set(int index, Object element),修改指定位置的元素
- Object remove(int index),移除指定位置的元素
-
List遍历
for(int i = 0; i < list.size(); i++){ System.out.println(list.get(i)); }
4.3.4、ArrayList集合
- ArrayList集合初始化容量是10(底层先创建了一个长度为0的数组,当添加第一个元素的时候初始化容量为10)
- ArrayList集合底层是Object类型的数组
- ArrayList集合扩容为原容量的1.5倍
- 数组优点:检索效率比较高,缺点:随机增删元素效率比较低
- 使用最多的集合ArrayList
4.3.5、LinkedList
- LinkedList集合底层采用了双向链表数据结构
- 对于链表数据结构来说,随机增删效率较高,检索效率低
- 链表中的元素在空间存储上内存地址不连续
4.3.6、Vector
- 底层是一个数组
- 初始化容量为10,扩容为原容量的2倍
4.3.7、泛型
-
自动类型推断机制
List<Integer> list = new ArrayList<>();
-
自定义泛型时,<>尖括号中的是一个标识符,可以自定义,一般使用比较多的是E(Element)和T(Type)
4.3.8、HashSet集合
-
HashSet集合元素特点:无序且不可重复
Set<String> hashset = new HashSet<>(); hashset.add("abc"); hashset.add("def"); hashset.add("hig"); //重复元素不会存储 hashset.add("abc"); for (String s : hashset){ System.out.println(s); }
4.3.9、TreeSet集合
-
HashSet集合元素特点:无序且不可重复,但是存储的元素可以自动按照大小顺序排序,称为可排序集合
Set<String> treeset = new TreeSet<>(); treeset.add("hig"); treeset.add("def"); treeset.add("abc"); treeset.add("abc"); //输出abc,def,hig,已自动排序 for (String s : treeset){ System.out.println(s); }
4.3.10、Map集合
-
Map集合以key和value的方式存储数据,key和value都是引用数据类型,其中key起主导作用
-
常用方法:
- V put(K key,V value),向Map集合中添加键值对
- V get(Object key),通过key获取value
- void clear(),清空Map集合
- boolean containsKey(Object key),判断Map集合中是否包含某个key
- boolean containsValue(Object value),判断Map集合中是否包含某个value
- boolean isEmpty(), 判断Map集合中的元素个数是否为0
- Set keySet(),获取Map集合中所有的key
- Collection values(),获取Map集合中所有的value
- V remove(Object key),通过key删除键值对
- int size(),获取Map集合中键值对的个数
- Set<Map.Entry<K,V>> entrySet(),将Map集合转换成Set集合
-
实例代码
public class MapTest { public static void main(String[] args) { HashMap<Integer, String> hm = new HashMap<>(); hm.put(1,"a"); hm.put(2,"b"); hm.put(3,"c"); //获取所有key Set<Integer> keyset = hm.keySet(); for (var v : keyset){ System.out.println(v); } //获取所有value Collection<String> valueset = hm.values(); for (var v : valueset){ System.out.println(v); } //遍历HashMap for (var v : keyset){ System.out.println(v + ":" + hm.get(v)); } //将HashMap转换成Set集合 Set<Map.Entry<Integer, String>> entries = hm.entrySet(); for (var v : entries){ System.out.println(v); } } }
4.3.11、HashMap集合
-
哈希表/散敛表数据结构:
-
哈希表是一个数组和单向链表的结合体
- 数组:查询方面效率很高,随机增删方面效率很低
- 单向链表:查询方面效率很低,随机增删方面效率很高
哈希表将以上两种数据结构融合在一起,充分发挥它们各自的优点
-
-
map.put(k,v)方法的实现原理:
- 先将k和v封装到Node节点中,底层会调用k的hashCode()方法,得出哈希值,通过哈希算法/哈希函数转换成数组下标,如果下标位置上没有任何元素,就把Node添加到该位置上,如果下标位置上有链表,会用该节点的K与链表中每个节点的K进行equals比对,如果返回的都是false,则把该节点插入到链表的尾部,如果有一个节点返回true,那么这个节点的value会被覆盖
-
map.get(k)方法的实现原理:
- 先调用k的hashCode()方法得出哈希值,通过哈希算法转化成数组下标,通过数组下标快速定位到某个位置上,如果这个位置上什么也没有返回null,如果这个位置上有链表,那么会用该节点的K与链表中每个节点的K进行equals比对,所有equals方法返回false,那么get方法返回null,只要有一个节点的K与该节点的equals方法返回true,get方法返回匹配节点的value
-
HashMap集合的key的特点:
- 无序,不可重复
- 无序,因为通过hashCode()方法计算后,分配到哪个链表中是未知的
- 不可重复,equals方法比较节点的K值来保证HashMap集合中key不可重复,如果key重复了,value会被覆盖
- HashMap集合key部分的元素存放到HashSet集合中,所系HashSet集合中的元素也需要同时重写hashCode()方法和equals()方法
- 无序,不可重复
-
HashMap使用不当时无法发挥性能:
- 假设将所有的hashCode()方法返回固定的某个值,那么会导致底层哈希表变成了一个纯单向链表,这种情况称为散敛分布不均匀
- 假设将所有的hashCode()方法返回不一样的值,那么会导致底层哈希表变成了一个纯数组,没有链表,也是散敛分布不均匀
-
HashMap集合的默认初始化容量是16,默认加载因子是0.75,默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开扩容
-
HashMap集合初始化容量必须是2的幂次,这样可以达到散敛均匀提高HashMa集合的存取效率
-
向Map集合中存取元素都是先调用hashCode方法,然后在调用equals方法,equals方法有可能调用,也有可能不调用
- 拿put(k,v)举例,什么时候equals方法不会调用:
- k.hasCode()方法返回哈希值,哈希值通过哈希算法转换成数组下标,数组下标位置上如果是null,equals方法不需要执行
- 拿get(k)举例,什么时候equals方法不会调用:
- k.hasCode()方法返回哈希值,哈希值通过哈希算法转换成数组下标,数组下标位置上如果是null,equals方法不需要执行
- 拿put(k,v)举例,什么时候equals方法不会调用:
-
如果一个类的equals()方法重写了,那么hashCode()方法必须重写,并且equals()方法返回如果是true,hashCode()方法返回的值必须一样
-
放在HashMap集合的key部分,以及放在hashSet集合中的元素,需要同时重写hashCode()方法和equals()方法
-
如果哈希表单向链表中的元素超过8个,单向链表会转换红黑树,当红黑树上的节点小于6时,会把红黑树转换成单向链表
-
HashMap和HashTable的区别:
- HashTable的key和value都是不能为null的,HashMap的key和value都是可以为null的
- HashTable都带有synchronized,是线程安全的,HashMap不带有synchronized,是线程不安全的
- HashTable的初始化容量是11,HashMap初始化容量是16,默认加载因子都是是0.75
4.3.12、properties类
- properties的key和value都是String类型的
- properties被称为属性对象
- properties是线程安全的
- properties的存和取:
- properties.setProperty(“username”,“root”)
- properties.setProperty(“password”,“1234”)
- properties.getProperty(“username”)
- properties.getProperty(“password”)
4.3.13、TreeSet集合
-
TreeSet集合底层实际上就是一个TreeMap,TreeMap底层是一个二叉树
-
放到TreeSet集合中的元素,等同于放到TreeMap集合key部分
-
TreeSet集合中的元素,无序不可重复,但是可以按照元素的大小顺序自动排序,称为可排序集合
-
实现自定义类的排序
public class MapTest { public static void main(String[] args) { Person person1 = new Person("zhangsan", 20); Person person2 = new Person("lisi", 20); Person person3 = new Person("wangwu", 22); //第一种方式,实现Comparable TreeSet<Person> t = new TreeSet<>(); //第二种方式,使用匿名内部类Comparator TreeSet<Person> t1 = new TreeSet<>(new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { return o1.getAge() - o2.getAge(); } }); t.add(person1); t.add(person2); t.add(person3); for (var a : t) { System.out.println(a); } } } class Person implements Comparable<Person> { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } @Override public int compareTo(Person o) { if (age == o.age) { return name.compareTo(o.name); } else { return age - o.age; } } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
-
Comparable和Comparator怎么选择:
- 当比较规则不会发生变化时,或者说当比较规则只有一个的时候建议实现Comparable接口
- 如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口
4.4、I/O流
-
IO流的分类:
-
按照流的方向分类,分为输入和输出,或者叫读和写
-
按照读取数据方式分类,分为按字节读取(万能流,什么类型文件都可以读取)和按字符读取(只能读取纯文本文件,不能读取图片,word等)
-
Java中的IO流:
- java.io.InputStream,抽象类,字节输入流
- java.io.OutputStream,抽象类,字节输出流
- java.io.Reader,抽象类,字符输入流
- java.io.Writer,抽象类,字符输出流
Java中类名以Stream结尾的是字节流,以Reader结尾的是字符流
-
所有的流都实现了java.io.Closeable接口,都是可关闭的,都有close方法
所有的输出流都实现了java.io.Flushable接口,都是可刷新的,都有flush方法
-
IO流实现类:
- 文件专属:
- java.io.FileInputStream
- java.io.FileOutputStream
- java.io.FileReader
- java.io.FileWriter
- 转换流(将字节流转换成字符流):
- java.io.InputStreamReader
- java.io.OutputStreamWriter
- 缓冲流专属:
- java.io.BufferedReader
- java.io.BufferedWriter
- java.io.BufferedInputStream
- java.io.BufferedOutputStream
- 数据流专属:
- java.io.DataInputStream
- java.io.DataOutputStream
- 标准输出流:
- java.io.PrinterWriter
- java.io.PrinterStream
- 对象专属流:
- java.io.ObjectInputStream
- java.io.ObjectOutputStream
- 文件专属:
-
IDEA的默认路径是工程Project的根目录,"./"表示父级目录
4.5、多线程
-
进程是一个应用程序,线程是一个进程中的执行单元,一个进程可以启动多个线程
-
进程A和进程B的内存独立不共享,线程A和线程B在方法区内存和堆内存共享,在栈内存独立,一个线程对应一个栈
-
实现线程的方法
- 直接继承java.lang.Thread类,重写run方法
- 编写一个类,实现java.lang.Runnable接口,实现run方法(比较常用,实现接口还可以继承)
-
线程的生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OQZRqjLS-1665645907890)(java\ThreadLife.jpg)]
-
线程安全
-
什么情况下存在安全问题:
- 多线程并发
- 有共享数据
- 共享数据有修改的行为
-
解决线程安全问题:线程排队执行(不能并发),这种机制称为线程同步机制
-
线程同步和线程异步
- 线程同步:线程1和线程2,在线程2执行的时候,必须等待线程1执行结束,两个线程之间发生等待关系,效率较低,线程排队执行
- 线程异步:线程1和线程2,各自执行各自的,谁也不许要等谁,其实就是多线程并发,效率较高
-
变量的线程安全问题:
- 局部变量永远不存在线程安全问题,因为局部变量不共享
- 实例变量和静态变量可能存在线程安全问题
-
synchronized的三种写法:
-
同步代码块(灵活)
synchronized(线程共享对象){同步代码块}
-
实例方法上使用synchronized,表示共享对象一定是this,并且同步代码块是整个方法体
-
静态方法上使用synchronized,表示找类锁,类锁永远只有1把,计算创建了100个对象,那类锁也只有1把
-
-
死锁
-
怎么解决线程安全问题:
- 尽量使用局部变量代替实例变量和静态变量
- 如果必须是实例变量那么可以考虑创建多个对象,这样实例变量的内存就不共享了
- 如果不使用局部变量,对象也不能创建多个,这是由选用synchronized线程同步机制
-
-
守护线程
- Java中线程分为两大类:用户线程和守护线程(后台线程),其中最有代表性的就是垃圾回收线程(守护线程)
- 守护线程的特点
- 一般守护线程是一个死循环,所有用户线程只要结束,守护线程自动结束
- 主线程main是一个用户线程
-
定时器:间隔特定的时间去执行特定的程序
-
Object类中的wait和notify方法(生产者和消费者问题)
-
wait和notify方法建立在synchronized线程同步的基础之上,wait方法会对象上活动的线程进入等待状态并且释放之间占用的对象锁,notify只负责通知,不会释放之前占用的对象锁
-
wait方法的作用:
- Object o = new Object();o.wait()表示让正在0对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止,wait方法的调用会让当前线程进入等待状态
-
notify方法的作用:
- o.notify,唤醒正在o对象上等待的线程
- o.notifyAll,唤醒正在o对象上等待的所有线程
-
4.6、反射机制
-
通过反射机制可以操作字节码文件
-
获取绝对路径
//getResource的根路径是src String path = Thread.currentThread().getContextClassLoader().getResource("log.txt").getPath();
4.7、类加载器
-
jdk三个类加载器
-
启动类加载器
首先通过启动类加载器加载,加载jre\lib\rt.jar,rt.jar中都是JDK最核心的库
-
扩展类加载器
如果启动类加载器加载不到的时候,会通过扩展类加载器加载,加载jre\lib\ext*.jar
-
应用类加载器
如果扩展类加载器加载不到的时候,会通过应用类加载器加载,加载classpath中的jar包(class文件)
-
-
双亲委派机制
Java中为了保证类加载器的安全,使用了双亲委派机制,优先从启动类加载器中加载,这个称为父,父加载不到,再从扩展类加载器中加载,这个称为母,如果都加载不到才会从应用类加载器加载public