JavaSe
1.程序
1.1什么是程序
程序就是模拟现实世界解决现实问题而编写的一系列的有序的指令集和
2.Java的版本
版本后面打了对勾的是支持LTS(LTS是指你会在一段时间内版本会受到安全、维护,LTS版本是最稳定的版本,他经历了广泛的测试)所以大部分都是使用支持LTS的版本。至2023年java版本已经更新到的java20
3.Java技术的特点
1.java语言是面向对象的一门语言(面向对象的思维,更加贴近现实世界的思维模式)(oop)
2.java语言是健壮的,是强类型语言、拥有自动的垃圾回收机制、异常处理等是java语言健壮的关键
3.java语言的简单性,舍去了c语言当中的指针,手动垃圾回收机制
4.解释性语言和编译性语言
4.1什么是解释性语言和编译性语言
首先电脑执行程序的时候只能够识别二进制然后进行执行,所以在执行编程语言的时候需要先将编程语言翻译成电脑能够识别的二进制在进行执行。
所有语言其实都有解释器,因为要执行代码只能先将源码解释成二进制码计算机才能够执行
解释语言:使用解释性语言编写代码的时候,在执行解释型语言的时候他是逐步自上而下的执行,解释器翻译一行代码电脑就执行一行代码
编译性语言:则是将编写好的代码通过编译器将编写好的代码编译成一个二进制文件,然后电脑可以进行执行,这个二进制文件可以重复使用就是只需要编译一次,下次执行的时候直接运行就可以了
解释型语言举例:javascript、python
编译性语言举例:C、C++
4.2解释性语言和编译性语言的优缺点
解释性语言:解释性语言不同平台拥有不同的解释器,这样可以实现跨平台的特性。
编译性语言:编译性语言则是先将代码通过编译器编译成二进制文件,然后拿着二进制文件在执行,特点就是不可跨平台,但是执行效率高。
4.3Java是怎么执行的
首先Java先编译好源代码,通过javac的指令将java源代码编译成.Class文件,将编译好的.Class文件交给JVM,JVM将.Class进行类加载,然后再把.Class文件解释成二进制交给计算机系统运行
4.4Java为什么可以跨平台
Java能够跨平台的核心是在于JVM,而不是Java能够跨平台,主要是JVM能够跨平台,我们知道不同系统上面操作系统的API是不一样的,那么如果我们想写一段代码调用系统的声音设备,就需要针对底层对代码编译二进制的解释器做出不同的版本。而Java引入了字节码的概念,底层的JVM只能认识字节码,并将它们解释到系统API调用。针对不同的系统有不同的JVM实现。MacOs有MacOs系统实现的JVM,而Windows也有Windows对应的JVM实现,但是在两个不同的版本编写相同的Java源代码在两个不同的平台生成的是相同的.Class文件。引用前面的例子,相同的Java源代码生成.Class相同但是在不同的操作系统运行但是通过底层的JVM会调用不同系统的API来实现播放音乐的功能这样也就实现了跨平台
例:再比如写一个Java的程序,先将这个java源文件编译成.Class文件(在任何平台上面生成的.Class文件都是相同的),然后交给JVM(每个版本都有不同的JVM)然后JVM对.Class文件做加载调用API再将.Class文件转换成二进制交给计算机运行。要注意的是只要是相同的源代码在任何平台上面生成的.Class文件都是相同的只是不同的jvm不针对不同的操作系统调用相同功能的API。
5.JDK
5.1JDK的基本简介
(1) JDK(Java程序开发工具包)
JDK当中包含了jre(Java的运行环境) + Java的开发工具如:javac(Java的编译工具 是我们将Java程序源码编译成.Class文件的工具)、java.exe(运行已经编译好的.Class文件)、javadoc(是生成你的项目的API)、javap(对java的源码进行反汇编)等。
jdk默认分配物理内存的1/8
(2)JRE(Java的运行环境)
JRE当中包括了核心类库+JVM
(3)JVM虚拟机
主要作用就是用来对.Class文件进行类加载、调用API、再将.Class文件转换成二进制交给操作性系统。也是Java能够跨平台的关键
(4)三者之间的关系
JDK包含了JRE包含了JVM
他们三者之间是包含的关系,也就是说只要安装了JDK就等于安装了这些内容
但是如果你的电脑只运行java程序但是并不编写java代码就可以只安装JRE,因为只要拥有JRE就可以运行Java了
5.2 JDK的安装
(1)环境变量path的配置
(1.1)为什么要配置环境变量path?
(1.2)配置环境变量有什么好处?
(1.2)为什么没有配置Java在Path的路径在黑窗口却还是可以输出java?
答:在最开始还没有接触代码编写工具的时候我们要使用记事本来进行编写代码,然后再黑窗口运行,但是在黑窗口运行java程序会遇到一个问题如下图,我们可以看到在黑窗口不是内部名,原因是在黑窗口会在当前C:\Administrat下查找java和javac这两个运行程序,而如果在这个文件下找不到这些运行程序则在用户变量和系统变量当中的path中去查找看有没有对应的exe运行文件。
答:配置环境变量可以直接在黑窗口输入exe运行文件的名称这样直接就可以运行程序,前期在没有学习过专业的java编写工具的时候就会使用黑窗口来对java程序进行编译和运行,而配置环境变量的好处就是在任何目录下都可以访问到java.exe的运行文件。配置环境变量也是为了在任何目录文件下都可以运行java程序
答:为什么没有配置java在path环境中的变量输出java却还是有结果。是因为在你安装java的时候他会在C盘中Windows当中System32的位置复制一份java.exe、javaw、javaws在里面,所以在你输入java的时候才会显示java输出的内容
5.3 环境变量的配置
首先先右击此电脑打开属性打开高级系统设置点击环境变量会出现下面这张图片用户变量的意思当前windows系统上登录的用户上创建的变量,在用户变量创建的变量只在当前这个用户上面可以使用,如果换到其他用户上就不可以使用了。而系统变量的意思是在当前的系统上都可以使用就算是切换用户也可以使用在系统变量当中创建的变量,所以我们一般把java的变量配置在环境变量当中
首先我们先在系统变量当中新建一个JAVA的变量,变量名称是JAVA_HOME变量值是你安装JDK的根目录,变量名称和变量值是下图圈出来的
上一步仅仅是在系统的变量中添加了JDK的根目录,还需要在系统变量中的path当中添加bin目录,引用在系统变量当中添加的JAVA_HOME变量
按照这样的操做JDK就安装好了可以在黑窗口测试一下,如下图
现在我们可以看到无论是输入java还是javac指令都有内容的输出这样我们的java环境变量就配置好了
6.运行java程序
6.1.java代码的运行
首先我们要在黑窗口找到java源文件,找到java源文件后才能够运行
先使用cd命令切换到对应java文件的目录下
然后使用javac指令对java文件进行编译
最后使用java指令进行运行
6.2.编译时遇到的问题
举例:
我们可以看到他这里显示编译错误的,原因是编码格式不匹配
原因是:在记事本上面使用的编码格式是UTF-8的编码格式,到JVM使用的却是GBK的编码格式,
所以在这个步骤上面就会出错。
因为JDK是国际版的,所以在编译的时候如果你不设置JDK的编码格式的话(-encoding),JDK会按照操作系统的默认编码格式对java源文件进行编译。整体流程:在编译java源文件的时候,如果过不指定编码格式的话JDK会获取操作系统file.encoding的属性(一般值为GBK),然后JDK按照我们java源程序的编码格式转换为JDK的默认编码格式(如果源程序的编码格式和JDK默认的编码格式不一致的话就会报错),然后JDK再把转化为默认编码格式后在转换为java内部默认的unicode编码放到内存当中,之后javac再把unicode编码转换为class文件,此时.class文件是unicode编码的,最后JDK再把.class文件保存在操作系统中。
解决编码格式错误的方法:
(1)指定JDK的编码格式
javac -encoding utf-8 Test.java
javac -encoding [编码格式] java源文件
(2)改变java源程序的编码格式
7.Java的开发的注意事项和细节
7.1.java的main方法是由固定格式的写法的
这是java的main方法的固定编写格式,main方法也是程序的主入口
public static void main(String[] args){
//.......
}
7.2.Java语言是严格区分大小写的
System.out,printlN();如果这段输出语句的最后一个字母大写的话则这段程序是会报错的
会出现以下错误
7.3.java是由一条条语句构成的,所以每句语句后面都要加分号,如果不加分号的话时会报错的
7.4.一个源文件当中最多只能有一个public类,其他类的个数不限(是在一个文件当中只能由同一个公开的类 其他的类也可以写在这个文件当中但是不能加public访问修饰符)。
7.5.如果一个类当中有其他的类,编译当前这个类的话会将当中的所有类都编译成.Class文件
在这里可以看到我么们一共写了三个类,一个是公开类,剩下两个是不加修饰符的类

下面就是编译当前这个类生成的.Class文件
我们可以看到只要是在MyFirstJava当中编写的类都生成了对应.Class文件
8.Java的转义字符
Java当中常见的转义字符 | 主要功能 |
---|---|
\t | 补全当前字符串的字节的长度为8的整倍数,可以实现对齐 |
\n | 代表换行的意思 |
\r | 代表回车键,将光标移入到当前行并将后面的字进行替换 |
\ | 可以对转义的字符再次进行转义 |
public static Test{
public static void main(String[] args){
// \t,是一个制表符,可以实现对齐功能
//比如我想让四大名著写在一行,然后之间用空格隔开
//最终的结果是
//三国演义 水浒传 西游记 红楼梦
//我们会发现三国演义和其他的名著之间的空格距离不相同
// \t是使用空格补全他前面字符串的字节大小为8的整倍数
//中文字符是一个字符占两个字节,所以三国演义是8个字节,因为是要补全为8的整倍数所以三国演义后面补 上了8个空格
//而水浒传这三个字他的字节为6,所以后面只需要补全两个空格
System.out.println("三国演义\t水浒传\t西游记\t红楼梦");
//我们可以看下面这个例子
//输出结果为:1111 111
//这个输出结果是四个空格,原因是\t前面字符的字节为4,所以为8的整倍数所以只空了4个空格
System.out.println("1111\t111");
}
}
下面是\n的代码演示
public class Test{
public static void mian(String[] args){
//输出结果是:jack
// MeiXi
// XiaoMing
//他会对输出结果进行换行
System.out.println("jack\nMeiXi\nXiaoMing");
}
}
下面是\r的代码演示
public class Test{
public static void main(String[] args){
// \r的意思是回车,先输出语句,到\r后光标会回到本行的首位并且\r后面的字符将替换掉前面的字符
// \r也是按照字节大小来进行替换的,如果\r后面的是数字或者字母他只会按照字节去替换
//比如\r后面是两个数字,\r以前是中文字符,\r后面的两个字节可以替换掉前面的两个字节一个字符才可以替 换掉前面的一个中文字符
//如果后面是一个字节的字符而前面是一个两个字节的中文,他也会进行替换他还会补一个空格
System.out.println("北京是个好地方\r陕西");
}
}
\转义字符
public class Test{
public static void mian(String[] args){
// \的意思是对特殊的字符进行转义
//比如说我想输出\这个特殊的字符,但是如果直接写\的话他会认为这个\是个转义字符或报错
//所以我们需要在\后面再加一个\才可以正常输出
System.out.println("\\");
//再比如说我想输出“”但是如果在“”直接写“”的话会出错,所以需要在““前面加上\来对”“进行转义
System.out.println("\"\"");
}
}
9.注释(Comment)
注释是一个程序员必需要具备的良好编程习惯,当代码编写的多了就能体现出注释的好处了,不仅能帮助自己理解代码更能帮助接手你代码的程序员更快的理解项目
9.1.注释都有哪些
(1)单行注释:只能对一行进行注释
语法: //注释内容
(2)多行注释: 可以对多行进行注释
/**/
1)注释里的内容不会被JVM解释执行
2)多行注释不能进行嵌套
(3)文档注释:是在类中编写文档注释,然后可以通过javadoc生成对应注释以网页的形式保存进行展示
语法:/***/
生成javadoc的语法:javadoc -d 将javadoc生成在哪个盘符目录当中(d:\temp) -xxx -yyy(这里是指使用到的javadoc中的标签) java程序名称
文档注释一般用于注释类和方法,再细分下去代码逻辑的层面就是用单行和多行注释就可以了
下面这个就是生成的注释的网页
10.Dos的原理及使用
DOS主要是用来处理windows系统文件的
是对文件进行管理的
(1)基本原理
DOS系统接收用户输入的指令,然后对指令进行解析,在执行指令就是对windows的文件进行管理
(2)基本的DOS命令
DOS指令 | 主要作用 | 举例 |
---|---|---|
md 盘符:\文件名称 | 创建文件夹 | md d:\bfwb |
rd 盘符:\文件名称 | 删除文件夹 | rd d:\bfwb |
cd 盘符名称:\文件名称 | 在DOS窗口切换盘符或文件夹 | cd d:\bfwb |
dir 盘符名称:\文件名称 | 查看当前目录下有什么哪些文件 | dir d\bfwb |
tree 盘符名称:\文件名称 | 以树的形式查看当前目录下的所有文件 | tree d: |
值得注意的是想让cd指令切换盘符的话必须要加入/d指令、
举例:
比如目前是在c盘下,想要切换到d盘下的bfwb的目录文件下
cd /d d:\bfwb
11.相对路径和绝对路径
11.1.相对路径
定义 : 相对路径是相对于当前所在的目录位置,进行定位
(1)…\是返回上一级文件夹
说明:可以看到我们当前实在d盘下bfwb的文件夹当中,输入cd …\指令后返回了上一级
(2).\是当前目录的意思
可以以当前目录为参考向其他目录进行定位
说明:相对于当前目录向上返回一级再切换到cc文件夹当中
(3)\是切换到根目录的意思
说明:我们可以看到当前实在d盘下bfwb文件夹下,然后输入了cd \的指令就切换到了当前的根目录
11.2.绝对路径
定义:绝对路径是在顶级目录进行定位,一般就是从盘符开始定位
绝对定位
以下图举例
此时在Test1中的A文件夹下,要去访问Test2下的B文件夹的b.txt文件使用相对路径应该怎么写
使用DOS指令应该怎么写
使用相对路径
cd …\…\\Test2\B //此时到达Teast2文件夹,然后再访问b.txt文件
b.txt
使用绝对路径
cd \d\Test2\B
使用相对路径的好处:因为程序是有可能拿到别的操作系统去执行,所以其他操作系统肯能是没有这个盘符这个时候使用绝对路径可能就会出错,所以都是最好使用相对路径
DOS窗口实例
以下是DOS使用相对路径的方式完成了访问
以下是DOS使用绝对路径的方式完成访问0000000
12.变量
什么是变量?
变量是程序的组成部分,变量字面意思就是可以变化的数值(数据),不同于字面量是不可以改变的,当我们希望变量随着程序的运行而发生改变的话变量的作用就体现出来了,但是字面量是不会随着程序的变化而变化。
12.1.变量的组成部分
变量是由三个部分组成的,是由数据类型、变量名、值这三个部分组成
举例: int a = 10; int代表数据类型 a为变量名称 10为变量的值
声明变量之后,a实质上存储的不是数据,而是一个地址(栈内存当中地址),这个地址才是存储变量值的地方,这个a变量只是引用到这个地址上去。
12.2变量的声明
变量有三种赋值的方式:
1.直接声明直接赋值
2.先声明在赋值
3.声明多个变量
//直接声明直接赋值
int a = 100;//
//先声明在赋值
int b;//开辟了一块整数空间
b = 200;//将整数赋值给这个变量
int c,d,e,f=10;//在这里声明了多个变量,并且给f附上了值
如果你只声明了变量,然后直接输出这个变量的话会报一个尚未初始化变量
变量不能再统一作用域下声明两个相同的变量名称
注意:java是强类型语言,所以数据类型和数据的类型必须一致
程序当中+号的使用
(1)数字与数字相加的话则得出两个数相加的结果
(2)如果字符串加上数字则是两个值做拼接,最终类型是字符
13.数据类型
基本数据类型有8种:[byte、short、int、long、float、double、char、boolean]
整数空间大小
byte类型===>1字节
short类型===>2字节
int类型===>4字节
long类型===>8字节
浮点数空间大小
float===>4字节
double===>8字节
字符型空间大小
chart===>2字节
布尔类型空间大小
boolean===>1字节
为什么要对数据类型再做细分呢:因为存储空间是非常稀缺的资源,如果一个很小的值使用了很大的存储空间就会很浪费所以我们要对数据类型做细分
13.1.整数型
在java中,默认的整数类型是int
在java当中基本类型都是有符号的,也就说在二进制的表示当中第一位是用来充当符号位的,也是可以当作数字
每位代表的具体值 | 128(符号位) | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
---|---|---|---|---|---|---|---|---|
二进制数值 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
用这个表格可以迅速的求出二进制与十进制之间的转换,这个表格是用来表示byte的取值范围
最头头的位置既代表数字也代表符号位,因为java中的整数是由符号的,0代表整数,1代表负数
在不带符号位的二进制计算当中,则每个位置的数字相加
所以在这个表格当中128位置如果是1的话就代表负数,其余的位置看二进制的数值进行相加,如果是1的话则进行相加
0则不加,按照上面这个例子来说的话除了128位置上没有1其他位相加的话,是64+32+16+8+4+2+1=127
如果符号位是1的话则代表这个数是个负数
如果是下面这种情况的话则代表
每位代表的具体值 | 128(符号位) | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
---|---|---|---|---|---|---|---|---|
二进制数值 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
首先符号位是1说明这个负数,其他位置都有数字说明这个数在负数当中是无限趋近于0的负数
如果使用long类型的话,附上的值如果超过了int的取值范围后面的值就必须加上L
我们以后的变量都会存储在这个slot槽当中,这个槽只能最多存储四个字节,如果需要存储八个字节的话就需要使用两个空间,这两个槽是需要捆绑使用的,将这个超出int的散列存储,所以需要加上L告诉编译器需要使用两个solt进行存储
代码举例:
public class Int{
public static void main(String[] args){
//整数的默认类型是int为什么可以直接赋值给byte
//因为编译器知道这个常量在byte的取值范围之内所以,所以java是允许这样赋值的
byte b = 100;
//这段代码是会报错的,原因是虽然我们肉眼看i1的值是符合byte的取值范围,但是由于i1是变量而编译器无法确定这个变量在程序的运行当中是否会产生超出byte的取值范围,所以编译器这在里是不给通过的会报错。
int i1 = 111;
byte b1 = i1;
//但是两个相同的变量类型,使用变量给变量赋值的话是可以的
byte b2 = 10;
byte b3 = b2;
//两个变量相加赋值给第三个变量
byte x = 10;
byte y = 10;
byte z = x + y;//错误原因是编译器在编译的过程中不知道这两个变量的值相加是否超过了byte取值范围
//两个值相加赋值给变量
byte q = 5 + 5;//result:10 编译器是确定这两个常量数值相加不会超过byte的取值范围所以是允许的
byte w = 126 + 5;//error,原因是两个数值相加超过了byte的取值范围,就会报错了
//两个值相加,超过byte范围但是使用强制转换看看结果
byte e = (byte)(127 + 5);//result:-124 相加超出范围强转会往回传-128 -127 -126 -125 -124
byte r = 10;
r = r + 10;//错误,因为有个变量与常量相加,还是不确定是否会超出byte的取值范围所以报错
r+=10;//正常运行,因为+=底层有个默认的强转,所以这个可以正常运行
//
long l = 99999999999999;//这句话错误是因为已经超出了int能够表示的范围了
long l2 = 99999999999999L;//只需要后面加上L就可以了,这样代表告诉编译器我这个long类型
}
}
13.2.浮点数
浮点数在计算机当中存储方式:
比如float:
float四字节:32位,1位做于存储符号,8位用于存储指数,剩下的23为表示位数部分
double八字节:64位,1位作为存储符号,11位用于存储指数,剩下52表示位数部分
浮点数是一个近似数,在有限的内存空间当中可以描述更多的数值,但是精确度会有损失
在你使用浮点数的时候精度已经丢失了
浮点数的默认类型是Double类型
使用float类型的话数值后面需要加f或F
代码举例:
public class floating{
public static void main(String[] args){
System.out.println(0.1+0.2);//0.30000000000000004
//是这个结果,如果我们用来表示银行当中的货币,那就是个问题了,如果一直累加的话就会导致银行亏钱
//所以我们说当使用浮点数的时候精度就已经丢失了
}
}
为什么在整数当中默认的类型是int,但是常量值可以直接赋值给byte类型不需要做任何操作。但是浮点数默认是double用常量值给float赋值需要加f。
首先浮点数中默认类型是double,在你将常量值赋值给float的时候,编译器并不知道这个常量后面有多少位小数,所以这个时候你就要告诉编译器(加f)我要强转我不怕精度丢失,加上f就是告诉编译器我要强转。
举个更好理解的例子:比如说整型,整形的默认类型是int,虽然byte b = 130,也是常量编译器也认识但是这样赋值编译器就会报错,因为编译器知道130这个数值已经超过了byte的取值范围,但是如果你这样写byte b = (byte)130,就不会报错了,因为这样写就告诉编译器我知道这个值超出了byte的取值范围,但是我不怕精度丢失,我告诉编译器我要强转。而float也是这个原理,编译器并不知道赋值给float这个数值后面小数点后面有几位,所以就要告诉编译器我不怕精度丢失我要强转。
代码举例:
puoblic class Test{
public static void main(String[] args){
float f = 3.14;//error,因为你没有告诉编译器我要进行强制类型转换,所以这里报错
float f1 = 3.12f;
float f2 = (float)3.33;//这两种方式都是可以的,因为在这里都告诉了编译器我要进行强转
}
}
浮点数的使用细节
在使用浮点数运算后进行比较的话会出现错误
代码举例:
public class floting{
public static void main(String[] args){
double d1 = 2.7;
double d2 = 8.1 / 3;//我们正常运算的结果是2.7 但是我们这个结果只会无限趋近于2.7这个结果
//所以两者进行比较的话就不会相等的
if(d1 == d2){
System.out.println("相等");
}
//不过我们也可以进行干预
//如果
}
}
13.3.布尔类型
类型 | 字节 | 取值范围 | 描述 |
---|---|---|---|
boolean | 1字节 | true/false | 仅可用来描述真或假 |
但实际上使用一位就可以用来描述boolean的取值了,但是在java的规定当中存储空间的分配必须是8的n次方位数,以便于java的自动垃圾回收机制对垃圾进行回收
boolean不能进行运算
可以直接赋值true或false,或者使用表达式的结果位boolean进行赋值
代码演示
public class Test{
public static void main(String[] args){
//使用直接赋值的形式对boolean进行赋值
boolean b1 = true;
//使用表达式的结果为boolean进行赋值
boolean b2 = 3 > 2;//赋值上的结果true
}
}
13.4.char类型
类型 | 字节 | 范围 | 字符编码 |
---|---|---|---|
char(字符类型) | 2字节 | 0~65535 | Unicode字符集(万国码) |
Unicode编码支持ASCII编码(美国标准信息交换码),Unicode编码是包含ASCII编码的
Unicode当中所有字符都有对应的十进制数,从而可以使用多种方式来进行赋值
char是一个无符号数
public class TestChar{
public static void main(String[] args){
//使用字符的方式直接赋值
char c1 = 'A';
//使用整数的方式进行赋值
char c2 = 65;//使用整数赋值的话则是找到这个十进制数在字符集当中找到对应的字符赋值
//char类型是不运行这样赋值的
short s1 = 65;
char c1 = s1//首先short是有符号数,short是可以用来表示负数的的,但是char不可以 char是没有符号的 char的编码顺序是从0开始的,而且还有一个问题就是不确定s1在运行的过程当中是否会超出c1能接受的范围
}
}
什么是编码,什么是解码呢?
编码就是基于一个编码表(GBK,BIG5…)文字按照这张表转成对应的十进制在转化成对应的二进制。
而解码正是相反的过程基于相同的一张编码表按照对应的二进制找到对应的十进制然后找到文字。
14.Packge包
作用:类似于文件夹,用于管理字节码文件(.class)文件。·
语法: package 包名; 在类的第一行写上。
带包编译:javac -d .\ (包生成的位置.\的意思是相对于当前.java文件的位置生成包) java源文件
带包运行:java 包名.类名
包名是全部小写
举例: 带包编译:javac -d . TestPackage.java
带包运行:java xxx.xxx.xxxClass
包名命名规则一般是使用域名倒置的方式来进行命名: www.baidu.com ===> com.baidu.www
举例: com.company.department.group.project.module.xxxClass
15.类型转换与类型提升
15.1.自动类型转换
将类型较小的变量存入类型较大的变量
1)两种类型互相兼容
2)目标类型大于源类型
代码举例:
public class Test{
public static void main(String[] args){
//目标类型是int而源类型是byte,符合上面两个条件,所以直接做了类型的转换
byte b = 127;
int i = b;
}
}
15.2.强制类型转换
1)两种类型要互相兼容
2)目标类型要小于源类型
使用特定的语法来进行强制类型的转换
需要注意的一点是,做强制类型转换的话会使得数据的精度有所丢失
举例:
public class Test{
public static void main(String[] args){
short s = 258;//二进制的表现形式 0000 0001 0000 0010
byte b = (byte)i;//强转后的二进制表现形式 0000 0010
//我们可以看到byte只有16位而short有32位,所以short的值超出了byte能够表示的范围,所以就需要进行截断,从128位开始,只有2位置有1,所以最后强转下来结果为2,所以我们说在精度上会有损失
}
}
public class Test{
public static void main(String[] args){
short s = 129;
byte b = (byte)s;
//为什么这个b最后的结果是-127,因为他所做的强转实际上是将short所能表示的范围按照byte的所能取值到的范围进行截取
System.out.println(b);
//short此时所表示的二进制:0000 0000 1000 0001
//注意此时short最大的位是0,代表这个数值是一个正数
//现在做了强转short要截取byte所最大能够表示的范围,所以byte的二进制表示就是
//1000 0001 当截取以后byte的最大位数是1代表这个数是负数,计算机在计算的时候使用的都是补码进行计算的而负数的补码跟负数的正码是不一样的,所以最终算出是-127,这也就能够解释为什么129强转给byte的hi-127的结果了
}
}
15.3.表达式
使用运算符连接字面值或变量,并且可以得到一个最终结果
1 + 2;
1 * 2; 1/2;
int a = 2; a - 1;
15.4.自动类型提升
在两个做运算变量当中,最终的类型会是两个做运算的变量的较大的类型为最终类型。
但是在byte和short这两个变量当中做运算,最终的类型会是int
代码举例:
public class Test{
public static void main(String[] args){
byte b1 = 10;
short s1 = 10;
int i = b1 + s1;//在这里如果使用short接收的话会报错的,所以要使用int类型要进行接受
byte b2 = 10;
double d1 = 10;
System.out.println(b2+d1);//这里最终的类型会是double类型
0
}
}
16.四种进制
这四种进制分别是二进制八进制十进制十六进制
16.1.二进制
进位规则:逢二进一
16.1.1.二进制转十进制
有两种方式:
第一种:表格式
举例:给一个二进制数0101010
使用表格形式来算出对应的十进制
数值 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
---|---|---|---|---|---|---|---|---|
二进制 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
第一行代表数值
第二行对应二进制数,把对应的2进制有1的数字与对应的数值相加即可得出对应的十进制
2+8+32=42;
第二种:计算式
方法简介:
举例: 用这个二进制0101010举例
用二进制位置乘以2的位置-1的对应次方并相加
1*2^5 + 0*2^4 + 1*2^3 + 0*2^2 + 1*2^1 + 0*2^0 = 2+8+32=42;
次方是二进制对应的位数作为参考
2是因为是二进制所以要乘以2
16.1.2.二进制转八进制
使用三位一组的方式转换成八进制即可
举例: 二进制:011 010 101
三位一组转换八进制
101==>5
010==>2
011==>3
所以最终的转换结果为 0325
16.1.3.二进制转十六进制
使用四位一组的方式转换成十六进制即可
举例: 1100 1011 0010 1000
四位一组:
1000==>8
0010==>2
1011==>B
1100==>C
16.2.八进制
进位规则:逢8进1
16.2.1.八进制转十进制
用八进制对应的位数乘以8对应位置的次方-1
先算^
0234
4*8^0 + 3*8^1 + 2*8^2 = 4 + 24 +128 = 156
16.2.2.八进制转二进制
取一位八进制的数,转换成三位对应的二进制
0234
4==>100
3==>011
2==>010
010011100
16.3.十六进制
进位规则:逢16进1
10使用A表示
11使用B表示
12使用C表示
13使用D表示
14使用E表示
15使用F表示
16.3.1.十六进制转换成十进制
举例: 0X23A转换成十进制
A代表10
所以 10*16^0 + 3*16^1 + 2*16^2 = 10+48+512=570
16.3.2.十六进制转换成二进制
举例:0X23A转换成二进制
A==>1010
3==>0011
2==>0010
十六进制使用四位二进制数来表示,所以最终结果为0010 0011 1010
17.运算符
17.1.算术运算符
使两个数字进行运算
操作符 | 描述 |
---|---|
+ | 加、使两个数进行相加 |
- | 减、使两个数进行相减 |
* | 乘、使两个数进行相乘 |
/ | 除、使两个数进行相除 |
% | 模、求两个数相除的余数 |
public class Test{
public static void main(String[] args){
System.out.println(3+1);//结果为4
System.out.println(3-1);//结果为2
System.out.println(2*2);//结果为2
System.out.println(6/2);//结果为3
System.out.priintln(10%2);//结果为0
}
}
一元运算符(操作一个数字)
操作符 | 描述 |
---|---|
++ | 递增,变量值加一 |
– | 递减,变量值减一 |
++在前和在后的优先级别不同,++在前的话优先级要高于++在后。++在前是先自增一,然后赋值给变量,++在后是赋
值给变量然后给变量自增一。
举例说明:
public class Test{
public static void main(String[] args){
int a = 10;
//++在变量的后面,也就是说a++ = 10,a = 11。
System.out.println(a++);//结果为10
System.out.println(a);//结果为11
//++在变量的前面,也就是说拥有更高的优先级,所以++a = 11,a = 11
int b = 10;
System.out.println(++b);//结果为11
System.out.println(b);//结果为11
}
}
17.2.赋值运算符
赋值运算符:等号右边的值赋值给等号的左边。
操作符 | 描述 |
---|---|
= | 直接赋值 |
+= | 求和后赋值 |
-= | 求差后赋值 |
*= | 相乘后赋值 |
/= | 相除后赋值 |
%= | 取余后赋值 |
举例:
public class Test{
public static void main(String[] args){
//=,直接赋值 将12这个值赋值给a这个变量
int a = 12;
//+=是先求和在赋值
int b = 10;
b += 10;//可以按照 b = b+10理解,但是两者并不相同。+=在底层会做一个强转的操作,所以可以按照这种形式理解但是两者不一样
//举例:
byte c1 = 10;
c1 = c1+10;//这段代码实际上会报错,因为编译器不能保证这个变量与一个常量值相加是否会超出范围
c1 += 10;//这段代码实际上不会报错,就是因为他在底层做了强制类型转化
//我们可以使用代码来进行验证
byte d = 127;
d += 1;//正常来说相加超出范围会报错,但是结果却是-128说明底层做了一个强制类型的转化,才导致的这个结果
}
}
17.3.关系运算符
关系运算符:使两个数进行比较,返回的结果是boolean类型的值
操作符 | 描述 |
---|---|
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
== | 等于 |
!= | 不等于 |
举例:
public class Test{
public static void main(String[] args){
int a = 10;
int b = 12;
//变量a大于变量b吗?
//结果为false,错误的 a不大于b
System.out.println(a > b);
//a小于b吗?
//结果为true,是的,a小于b
System.out.println(a < b);
//a大于或者等于b,只要这两个条件满足其中一个就是true
//但是a并不大于b也不等于b,所以结果为false
System.out.println(a >= b);
//a小于或者等于b吗?
//a小于b,所以结果为true
System.out.println(a <= b);
//a等于b吗?
//a与b相等吗?相等结果为true,不相等结果为false
//a与b不相等所以最终的结果为false
System.out.println(a==b);
//a与b不相等吗?
//是的a与不相等,所以最终的结果为true
System.out.println(a!=b);
}
}
17.4.逻辑运算符
逻辑运算符:两个boolean类型的操作数或表达式进行逻辑比较
操作符 | 语义 | 描述 |
---|---|---|
&& | 与(并且) | 两个表达式,都为真,最终的结果为真 |
|| | 或(或者) | 两个表达式,其中有一个是真,则结果为真 |
! | !(取反) | 为最终的表达式结果取反值 |
举例:
public class Test{
public static void main(String[] args){
int a = 10;
int b = 12;
int c = 20;
//先来看第一个表达式 a > b不成立,结果为false
//第二个表达式 a < c成立,结果为true
//但是由于 && 这个符号是必须要左右两边的表达式都为true,最终的结果才为true,所以这个表达式最终的结果为false
System.out.println((a > b) && (a<c));
//先看第一个表达式 a > b不成立,结果为false
//第二个表达式 a<c 成立,结果为true
//因为||这个符号两个表达式之中其中一个为true,则最终的结果为true,所以这个表达式的最终结果为true
System.out.println((a > b) || (a < c));
//这个表达式的原本结果应该是false,但是!是对最终的结果取反,所以最终的结果为true
System.out.println(!(a > b));
}
}
17.5.三元运算符
操作符 | 语义 | 描述 |
---|---|---|
? : | 布尔表达式 ? 结果1:结果2 | 如果布尔表达式的结果为真,取结果1 如果布尔表达式的结果为假,取结果2 |
举例:
public class Test{
public static void main(String[] args){
int a = 20 > 10 ? 1 : 2;
System.out.println(a);//这个布尔类型的表达式结果为true,所以取结果1
int b = 20 < 10 ? 1 : 2;
System.out.println(b);//这个布尔类型的表达式结果为false,所以取结果2
}
}
17.6.位运算符
17.6.1.原码、反码、补码
1.最高位是这个二进制的符号位,1代表负数0代表正数
2.正数的原码、反码、补码都一样(写成原码就可以了)
3.负数的反码是除了符号位外其他位取反
4.负数的补码是该负数的反码+1
5.补码转换为原码,是先转换为反码(先-1),-1之后变成了反码,反码转换原码只需要除符号位外取反就可以了
6.我们看结果的话是看补码对应的原码
因为计算机的cpu只能做加法运算,所以正数的反码补码都一样
计算机的cpu只能做加法,而且做加法的话是使用补码来进行运算的,存储数据也是使用补码的形式来进行存储的
17.6.2.位运算符
这些操作符都是在补码的基础上进行操作的
操作符 | 语义 | 描述 |
---|---|---|
& | 按位与 | 两个对应位的数都为1,最终的结果才为1 |
| | 按位或 | 两个对应位的数都为0,最终的结果才为0 |
~ | 按位取反 | 0变成1,1变成0(包括符号位) |
^ | 按位异或 | 两个对应的值相同为0,相异为1 |
>> | 右移 | 整体向右移动,相当于/2 |
<< | 左移 | 整体向左移动,相当于*2 |
左移右移是运算效率最快的
如果有面试题说怎么使2以最快效率得到8,就可以用2向左移动2位
举例:
public class Test{
public static void main(String[] args){
//13&7=
//结果为5
//计算过程
//先得出13的补码
//13补码为: 00000000 00001101
//7的补码为 00000000 00000101
//&是两个都为1,才为1所以
//00000000 00000101
//因为结算结果是整数,所以最终的结果为5
System.out.println(13&7);
//5|4=
//结果为5
//计算过程
//先得出5的补码 00000000 00000101
//再得出4的补码 00000000 00000100
//-----------------------------------------
//得出的结果 00000000 00000101
//因为按位或是两个都为0最终结果才为0
//因为是正数所以三码(正码、反码、补码)都一致不需要再做转换
System.out.println(5|4);
//~-5=
//结果为4
//计算过程
//因为是负数,所以得出补码之前要先得出-5的原码
//-5的原码 10000000 00000101
//-5的反码 11111111 11111010
//-5的补码 11111111 11111011
//拿到补码之后因为是按位取反的操作,所以所有的数字都要取反(包括符号位)
//取反后的结果 00000000 00000100
System.out.println(~-5);
//2^3=
//结果为1
//计算过程
//分别得出2和3的补码
//2的补码 00000000 00000010
//3的补码 00000000 00000011
//--------------------------------
//2异或3 00000000 00000001
//因为是相异才为1,相同为0,所以得出这个结论
System.out.println(2^3);
//2向左右移两位
//语法: 数值 << 移动几位
//计算过程:
//得出2的补码 00000000 00000010
// 00000000 00001000
//得出的结果是8
System.out.println(2 << 2);
//2向右移动两位
//语法: 数值 >> 移动几位
//计算过程
//得出2的补码 00000000 00000010
// 00000000 00000000
//得出的结果是0
System.out.println(2 >> 2);
}
}
18.控制台录入
程序在运行中可在控制台手动输入数据,再让程序继续运行
导报语法: import 包名.类名//将外部的class功能引入自身文件当中。
使用顺序:
导入 :import java.util.Scanner //导入java包下util下的Scanner类
导入还有一种语法 java.util.* //这句话的意思将util下的所有类都导入
声明Scanner类型的变量
使用Scanner类型的函数(区分类型)
.nextInt(); //接收整数
.nextDouble(); //接收小数
.next() //接收字符串
.next.charAt(0); //获得单个字符
注意:如果输入不匹配的数据则会报出 java.util.InputMismatchException
import java.util.Scanner;
public class Test{
public static void main(String[] args){
//声明一个Scanner类型的变量
Scanner input = new Scanner(System.in);
//有好的提示一下
System.out.println("请输入一个数字:");
//通过这个类的方法来接收控制台的数据
int i = input.nextInt();
//打印控制台接收的数字
System.out.println("控制台接收的数字是:"+i);
}
}
19.if选择结构
19.1.基本if选择结构
if(布尔表达式){
//代码块
}
如果布尔表达式的结果为true的话则执行if当中的代码块
代码举例:
public class Test{
public static void main(String[] args){
//如果考试成绩等于100分的话则奖励一部XiaoMi13pro
//在局部变量当中声明一个成绩变量
double score = 100.0;
//接下来再对成绩进行判断,如果满足100.0的条件的话则奖励手机一部
if(score == 100.0){
//如果满足条件,则执行if结构当中的代码
System.out.println("成绩满足,奖励XiaoMi13pro一部");
}
}
}
19.2.if-else结构
if(布尔表达式){
//代码块
}else{
//代码块
}
先判断if当中布尔表达式的结构如果是true的话就会执行if当中的代码块,如果是false的话则执行else当中的代码
代码举例:
public class Test{
public static void main(String[] args){
//如果考到了100.0分的话则奖励XiaoMi13pro一部,如果没有考到的话则提示继续努力,
//在局部变量当中声明一个成绩变量
double score = 99.9;
//接着对成绩这个变量进行判断
//如果满足条件的话则执行if的代码块,如果不满足的话则执行else当中的代码
if(score == 100.0){
System.out.println("奖励XiaoMi13pro一部");
}else{
System.out.println("继续努力!!!");
}
}
}
19.3.多重if选择结构
if(布尔表达式){
//代码块
}else if(布尔表达式){
//代码块
}else if(布尔表达式){
//代码块
}else{
//代码块
}
先对if当中布尔表达式进行判断,满足了则执行,不满足则在对else if进行判断满足则执行,不满足则继续向下判断直到满足else if或者没有条件满足执行else
代码举例:
public class Test{
public static void main(String[] args){
//汽车销售推荐
/*
如果预算是500万,宾利
否则,如果预算是300万,奔驰G63 AMG
否则,如果预算是100万,宝马x6
否则,如果预算是50万,奥迪A6L
否则,如果预算是20万,帕萨特
否则,如果预算是10万,宝来
否则,如果预算是5万,五菱宏光s
否则,如果预算是1万,小鸟电动车
否则,捷安特
*/
int money = 50;//单位万
if(money >= 500){
System.out.println("为您推荐宾利");
}else if(moeny >= 300){
System.out.println("为您推荐奔驰G63 AMG");
}else if(moeny >= 100){
System.out.println("为您推荐宝马X6");
}else if(money >= 50){
System.out.println("为您推荐奥迪A6L");
}else if(money >= 20){
System.out.println("为您推荐帕萨特");
}else if(moeny >= 10){
System.out.println("为您推荐宝来");
}else if(moeny >= 5){
System.out.println("为您推荐五菱宏光S");
}else if(money >= 1){
System.out.println("为您推荐小鸟电动车");
}else{
System.out.println("捷安特"):
}
}
}
19.4.嵌套if结构
if(布尔表达式){
if(布尔表达式){
//代码块
}
}
如果满足外层的if判断,则进入然后再对内层的if进行判断,如果满足则执行内层if的代码块
代码举例:
public class Test{
public static void main(String[] args){
//百米赛跑,进入13秒的可以进行总决赛
double timer = 10.43;
char sex = '男';
if(timer <= 13.0){
if(sex == '男'){
System.out.println("恭喜你进入男子组决赛");
}else{
System.out.println("恭喜你进入女子组决赛");
}
}else{
System.out.println("很遗憾,您被淘汰");
}
}
}
20.switch分支结构
语法:
switch(变量|表达式){
case 值1:
代码块;
break;
case 值2:
代码块;
break;
case 值n:
代码块;
break;
default:
为满足逻辑代码;
break;
}
每个case代码块后面要跟上一个break,不加break的话,匹配到case就会一直执行下去
JDK8的时候变量的类型就支持String、byte、short、int、枚举、char
代码举例:
public class Test{
public static void main(String[] args){
char num = '一';
switch(num){
case '一':
System.out.println("周一吃:快餐");
break;
case '二':
System.out.println("周二吃:炸酱面");
break;
case '三':
System.out.println("周三吃:泡面");
break;
case '四'
System.out.println("周四吃:手抓饼");
break;
case '五':
System.out.println("周五吃:大米饭");
break;
default:
System.out.println("输入错误,请重新输入");
break;
}
}
}
通过代码举例我们可以看到switch用来做精准数值匹配是比较合适的,if判断更适合做区间的判断
21.局部变量
概念:局部变量一定是定义在函数的内部的,必须先赋值在使用
作用范围:从声明变量开始,到所在的代码块结束
注意:多个变量,在重合的作用范围内,不可以出现重名(命名冲突)
局部变量不是由自动垃圾回收进行回收的,而是出了所在代码块结束而立即回收
22.循环结构
22.1.While循环
语法结构
while(布尔表达式){
//循环操作
}
循环是由四部分组成的
1.初始化部分
2.循环条件
3.循环操作
4.迭代操作
public class Test{
public static void main(String[] args){
int i = 1;
while(i<=100){
System.out.println(i);
i++;
}
}
}
22.2.DoWhile循环
语法结构:
do{
}while(条件判断);
dowhile循环无论怎么样先做一遍
dowhile循环适合先不对条件进行判断先做一遍,然后再进行判断是否接续执行
dowhile比如最一些账号的登录,因为用户第一次要进行账号密码的输入,然后再做判断,比如说输入正确就不进行循环让用户输入了,但是输入错误的话就需要重新输入
dowhile适合一些循环次数不明确的,需要先做一遍在进行条件的判断的场景
while循环适合一些循环次数明确的场景
import java.util.Scanner;
public class TestDoWhile{
public static void main(String[] args){
Scanner input = new Scanner(System.in);
char answer;
do{
}while(answer != "y");
}
}
22.3.for循环
语法结构:
for(; 😉{
}
public class Test{
public static void main(String[] args){
// 1.初始化操作 2.条件判断 4.迭代操作
for(int i = 1;i<=100;i++){
Systetm.out.println("for...");//3.循环操作
}
}
}
注意:for循环只要在括号当中把分号带上了就可以了
23.函数
我们为什么要使用函数
回答这个问题以前我们先假设一个场景
使用java代码实现上面这个结果
方法一:直接打印
public class TestPrint{
public static void main(String[] args){
System.out.println("床前明月光,");
System.out.println("----------");
System.out.println("疑是地上霜。");
System.out.println("----------");
System.out.println("举头望明月,");
System.out.println("----------");
System.out.println("低头思故乡。");
System.out.println("----------");
}
}
打印分割线的这些与数据是不是就代码冗余了,而且如果修改起来也很不方便,比如我想打印15个分隔符,那是不是每一条输出语句都要加上,这样就不便于代码的维护
方法二:使用for循环
public class TestPrint{
public static void main(String[] args){
System.out.println("床前明月光,");
for(int i = 1;i<=10;i++){
System.out.print("-");
}
System.out.println();
System.out.println("疑是地上霜。");
for(int i = 1;i<=10;i++){
System.out.print("-");
}
System.out.println();
System.out.println("举头望明月,");
for(int i = 1;i<=10;i++){
System.out.print("-");
}
System.out.println();
System.out.println("低头思故乡。");
for(int i = 1;i<=10;i++){
System.out.print("-");
}
System.out.println();
}
}
虽然修改起来容易了很多,但是还不够灵活,而且这么写代码很冗余,而且读起来很费劲
所以我们可以使用函数的形式,使代码更加灵活,修改起来更加灵活,更加的便于维护
所以我们总结下来使用函数有以下几点好处
1.可以反复调用
2.减少代码的冗余
3.代码更便于维护
定义语法:public static void 函数名称(形式参数1,…){}
调用语法:函数名(),如果函数有参数的话则,函数名(实际参数1,…)
举例:
public class TestPrint{
public static void main(String[] args){
System.out.println("床前明月光,");
//调用语法,函数名(实际参数1,......);
printSign(10);
System.out.println("疑是地上霜。");
printSign(15);
System.out.println("举头望明月,");
printSign(20);
System.out.println("低头思故乡。");
printSign(25);
}
public static void printSign(int number){
for(int i = 1;i<=number;i++){
System.out.print("-");
}
System.out.println();
}
}
调用方法的实参必须和方法声明参数的个数、顺序、类型做匹配
经验:一个函数只做一件事(单一职能原则)
如果一个函数只做一件事请的话就更加大大的提升了代码的复用性
比如你这个函数当中实现的功能很多,就不便于进行复用,因为有时候我只想用一个简单的小功能,如果调用的话就会出现我本不想出现的功能
23.1返回值与返回值类型
在方法当中是可以有返回值的
作用就是当一个函数结束以后,我们需要拿着这个函数的运行结果进行后续的操作的话,我们就需要返回值
语法:函数声明的时候需要加上返回值类型
public static 返回值类型 函数名称(形参1,…){}
如果函数声明了返回值的类型,那么这个函数里面就一定要有return 数据;这个数据的数据类型要跟函数声明的返回值类型一致。
23.1.1.return的两种用法
1.直接使用return关键字结束方法,这个时候的函数声明返回值类型应该是void
2.带着函数的执行结果进行返回,这个时候就需要renturn后面跟着数据
public class TestReturn{
public static void main(String[] args){
int resulte = sum(5,10);
}
public static int sum(int num1,int num2){
return num1+num2;
}
public static void method(){
//逻辑代码......
return;
}
}
23.2.函数的高级应用(递归)
把一个函数细分再细分,直到到了一个已知值的时候在逐步向上返回
把一个最大的问题逐步简化,知道他成为一个已知值
编写递归的时候,要记住给这个递归函数设置程序出口,不然就会导致栈内存溢出
代码举例
public class TestFibonacci{
public static void main(String[] args){
fibonacci(9);
}
public static void fibonacci(int n){
//给递归设置一个有效出口
if(n == 1){
return 1;
}else if(n == 0){
return 0;
}
return fibonacci(n-1) + fibonacci(n-2);
}
}
24.数组
数组:是一组连续空间,存储多个相同数据类型的值
数组的特点:他是一个类型相同,长度固定的
数组的创建:
语法: 数据类型[] 变量名 = new 数据类型[数组长度];
对数组进行访问,数组是通过下标来对数组的元素进行访问
语法:
读数组里的元素: 变量名[下标]
对数组的元素进行赋值: 变量名[下标] = 值
数组的有效下标: 0~数组.length-1,在这个范围内都是可以正常访问数组元素的
数组的遍历:我们可以通过循环的方式来对数组进行遍历
public class TestArray{
public static void main(String[] args){
int[] arr = new int[3];
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
for(int i = 0;i<arr.length;i++){
System.out.println(arr[i]);
}
}
}
24.1.数组的创建语法
public class CreateArr{
public static void main(String[] args){
//1.先声明,再分配空间
int[] arr;
arr = new int[5];
//2.声明并分配空间
int[] arr1 = new int[5];
//3.声明并赋值(繁)
int[] arr2 = new int[]{"value1","value2","......"};
//4.声明并赋值(简)
int[] arr3 = {"value1","value2","......"}
//声明并赋值(繁)和生命并赋值(简)的区别
//如果有一个场景需要将数组的引用提出来,那么我使用声明并赋值(简)就会报错
//先使用繁的方式
int[] arr4;
arr4 = new int[]{1,2,3,4};
//在使用简的方式
//注意:这样写在这里语法是错误的,如果使用简的形式先声明在赋值,他就无法进行类型的推导了
int[] arr5;
arr5 = {1,2,3,4};
}
}
重点:声明并赋值(繁)和声明并赋值(简)是有区别的。简的形式无法进行类型推导
24.2.数组元素的默认值
数组当中不同类型,他的默认值是不同的
int类型的数组,它的默认值就是 “0”
double类型的数组,他的元素的默认值就是“0.0”
char类型的数组,他的元素默认值就是“\u0000”在Acsill编码中代表空格
String类型的数组,他的元素默认值就是null
还有引用数据类型的数组,他的元素默认值也是null
24.3.数组的工具
1.数组的复制
System.arraycopy(原数组,原数组起始,新数组,新数组长度,长度);
public class TestCopyArr{
public static void main(String[] args){
int[] arr = new int[]{11,22,33,44};
int[] arr2 = new int[4];
//第一个参数:原数组的引用
//第二个参数:从原数组第几个下标开始考
//第三个参数:新数组的引用
//第四个参数:拷到新元素组的第几个下标位置
//第五个参数:拷多少个元素到新数组当中
System.arraycopy(arr,0,arr2,0,arr.length);
}
}
2.数组的扩容及复制
java.util.Arrays.copyOf(原数组,新长度)
这个方法是有返回值的,你传入什么类型的数组,他就给你返回什么类型的新数组
public class TestCopyOf{
public static void main(String[] args){
int[] arr = new int[]{11,22,33,44,55};
//这个方法是返回一个新的数组,长度是你传入的第二个参数
//这个新数组的内容应该是 11 22 33 44 55 0 0 0 0 0
int newArray = java.util.Arrays.copyOf(arr,arr.length*2);
}
}
24.4.数组的高级应用
24.4.1.数组的插入、移除、替换、扩容
public class TestArray{
//定义一个数组
static int[] arr = new int[5];
//定义数组的有效个数值
static int size = 4;
public static void main(String[] args){
//向数组中插入数值
arr[0] = 11;
arr[1] = 22;
arr[2] = 33;
arr[3] = 44;
}
//向数组中插入元素的方法
public static void InsertElement(int position,int value){
if(position < arr.length && position >= 0){
//如果数组的有效值满了,也就是代表数组当中的值存储满了,那么我就要进行扩容
if(arr.length == size){
expend();
}
for(int i = size;i>position;i--){
arr[i] = arr[i-1];
}
arr[position] = value;
size++;
}else{
System.out.println("position的值应该在0~"+size+"之间");
}
}
//从数组当中删除元素
public static void remove(int position){
if(position < arr.length && position >= 0){
for(int i = position;i<size-1;i++){
arr[i] = arr[i+1];
}
size--;
}else{
System.out.println("position的值应该在0~"+size+"之间");
}
}
//给数组扩容的方法
public static void expend(){
//给数组扩大至原先的两倍
arr = java.util.Arrays.copyOf(arr,arr.length*2);
}
//打印数组的方法
public static void print(){
for(int i = 0;i<size;i++){
System.out.print(i+"\t");
}
}
}
25.基本类型和引用类型的区别
1.基本数据类型当中保存的是数值
2.引用类型保存的则是你实际存储空间的位置(地址)
基本类型给基本类型赋值相当于数值的复制,一方改变另一方不会发生改变
而引用类型之间的互相赋值,则是地址的复制,一方当中改变地址中的数值,则另一方也会发生改变

代码举例:
public class TestRefernce{
public static void main(String[] args){
int a = 10;
//基本类型给基本类型赋值则是值的复制
int b = a;
a++;
//a = 11;b = 10。因为他们只是数值的复制
System.out.println(a);
System.out.println(b);
int[] oa = new int[5];
//引用的赋值则是将地址复制给另一方,由于这个空间是存储在堆内存当中,所以一方对当中的内容做操作,另一方引用也是可以看到的
int[] na = oa;
oa[0] = 10;
//他们打印出来的值是一样的,因为读取的都是一块空间
System.out.println(oa[0]);
System.out.println(na[0]);
}
}
引用类型也可以当做方法的形式参数,要求调用者在调用方法的时候对其进行赋值
也可以作为一个方法的返回值作为返回
26.可变长参数
在方法声明的形参列表当中使用,并且可可变长参数必须要是最后一个形参
用法实际上跟数组没有区别
代码举例:
public class TestChangeValue{
public static void main(String[] args){
printValue(1,2,3,4,5);
}
public static void printValue(int... values){
for(int i = 0;i<values.length;i++){
System.out.print(values[i]+"\t");
}
}
}
27.二维数组
为什么要使用二维数组?
如果说当时使用一维数组的原因是将数值放在一个组当中,便于控制和访问的话,那么二位数组就是为了方便操作多个组的数组。
当时有这么一个场景问题就是,保存100名同学的成绩,给这100位同学的成绩都加1,是一维数组的应用场景的话,那么如果要保存3个班100位同学的成绩的话,二维数组的应用场景就显现出来了。比如这三个班的每名同学的成绩都要加1,而且为了方便管理的话就最好使用二维数组了。
二维数组的概念:数组当中的元素还是数组
我们看到高维数组的元素存储的是地址,也就是说高维数组当中存储的是低维数组
27.1.二维数组的创建方式
1.声明并分配空间
语法 : 数据类型[][] 数组名= new 数据类型[高维数组的长度][低维数组的长度];
2.先声明、再分配空间
语法 : 数据类型[][] 数组名;
数组名= 数据类型[高维数组长度][低维数组长度];
3.声明并赋值(繁)
语法 : 数据类型[][] 数组名 = new 数据类型[高维数组长度][];不规则数组,自行new低维数组
4.声明并赋值(简)
数据类型[] 数组名 = {{v1,v2,v3},{v4,v5,v6},{v7,v8,v9}}
最外层的这个大括号代表了高维数组,里面这个小括号代表了高维数组的元素,也代表了低维数组
代码举例:
public class TestYH{
public static void main(String[] args){
//声明了一个二维数组,高维长度为3,也就是说可以存储3个int类型的数组
//低维的长度也为3则意味着每个低维数组都可以存储3个int类型的整数
int[][] arr = new int[3][3];
arr[0][0] = 10;
arr[0][1] = 20;
arr[0][2] = 30;
arr[1][0] = 100;
arr[1][1] = 200;
arr[1][2] = 300;
arr[2][0] = 1000;
arr[2][1] = 2000;
arr[2][2] = 3000;
for(int i = 0;i<arr.length;i++){
for(int j = 0j<arr[i].length;j++){
System.out.print(arr[i][j]);
}
System.out.println();
}
}
}
27.2.二维数组练习题(杨辉三角)
代码举例:
public class TestYH{
public static void main(String[] args){
//定义一个变量用来保存这个杨辉三角是几行的
int row = 7;
int[][] yh = new int[row][row];
for(int i = 0;i<yh.length;i++){
yh[i][0] = 1;
}
for(int i = 1;i<yh.length;i++){
for(int j = 1;j<=i;j++){
yh[i][j] = yh[i-1][j-1] + yh[i-1][j];
}
}
for(int i = 0;i<yh.length;i++){
for(int j = 0;j<=i;j++){
System.out.print(yh[i][j]+" ");
}
System.out.println();
}
}
}
28.面向对象
程序是做什么的?
程序一定是为了解决现实当中的某个问题,某个现实当中的痛点。
什么是面向对象?
面向对象就是一切客观存在的事物都是对象,万物皆对象
基于这个万物皆对象的思想之后,在你面向编程的时候会基于这个思想而进行编程(抽取对象共性封装成类,然后在进行创建对象)
任何对象,一定具有自己的特征和行为
对象特征:指的是对象具有的一些属性,一般是名词,代表对象有什么
对象行为:称为方法,一般是动词,代表对象能做什么
什么是类?
类是模板,在一组相同或类似的对象当中,抽取共性的特征和行为,保留关注部分(对现实当中的对象进行共性的抽取提取出来就是类),然后类在实例化就是对象
什么是对象?
对象就是模板的实例化,就是类的实例化
29.定义类及创建对象
如何定义类?
一般我们都是对现实当中的相同或类似对象做共性的抽取,提取出来后当作类的属性或者类的方法
如何创建对象?
我们只要在程序当中定义了类,那么这个类就是一个类型,可以被创建
语法 :Dog myDog = new Dog();
29.1.实例变量和局部变量的区别
局部变量 | 实例变量 | |
---|---|---|
定义位置 | 方法或方法内的结构当中 | 定义在类的内部方法的外部 |
默认值 | 无默认值 | 和数组当中的默认值是一样的 |
适用范围 | 从定义变量开始到结构结束 | 本类有效 |
命名冲突 | 不能重名 | 可与局部变量重名,当重名时优先局部变量 |
30.方法的重载
重载 : 在同一个类中定义多个名称相同但是参数不同的方法我们称之为方法的重载
要求:方法名称相同
参数列表不同(类型、顺序、个数)
与访问修饰符、返回值类型无关
重载的应用场景在哪里呢
比如说一个类当中需要有一个打印的方法,这个打印方法必须要什么数据类型都可以进行一个打印
这个时候如果你使用不同的方法名称就会很麻烦,因为以后再需要打印的话就要记住对应的数据类型的方法名称
但是如果使用重载的话根据传入的参数不同而调用不同的方法就可以了,这是最简便的一个方式
public class TestOverload{
public static void main(String[] args){
}
}
class Printer{
public void show(int content){
System.out.println();
}
public void show(double content){
System.out.println(content);
}
public void show(String[] content){
System.out.println(content);
}
public void show(char content){
System.out.println(content);
}
public void show(boolean content){
System.out.println(content);
}
}
31.构造方法
构造方法是一个特殊的方法,它的作用就是用来创建对象的
为什么说它是一个特殊的方法,因为他没有返回值(是根本没有void),他是通过new关键字进行调用的
在你没有定义构造方法的时候,编译器会给你默认提供一个无参的构造方法
构造方法也是可以进行重载的
如果你写了有参的构造方法,编译器就不会默认再提供无参的构造方法了,这个时候你再调用无参构造方法的时候就会报错
public class Test{
public static void main(String[] args){
//使用无参构造方法创建对象
Teacher t1 = new Teacher();
//使用有参构造方法,并且在创建的时候为该对象的各个属性进行赋值
Teacher t2 = new Teacher("Marry",28,5000);
}
}
class Teacher{
String name;
int age;
double salary;
//无参构造方法
public Teacher(){}
//构造方法也是可以进行重载的
//我们也想利用在创建对象的时候为该对象的各个属性赋值
public Teacher(String n,int a,double s){
//第一步初始化变量
//第二部执行构造方法当中的代码
//第三步就是回到方法调用处将创建好的该对象的地址赋值给变量
name = n;
age = a;
salary = s;
}
}
32.this关键字
this指的是当前对象(实例),this是一个地址,指向的是当前对象的地址
使用方式一 : 可以用来指定当前对象的属性和方法
使用方式二 : 可以用来调用构造方方法
注意:this()只能在构造方法的第一行进行调用
注意:构造方法只能是构造方法进行调用,实例方法是不能调用构造方法的
问题一:为什么实例方法不能调用构造方法呢?
因为初始化实例属性只用执行一次,如果使用实例方法调用构造方法的话就说明这个对象已经创建出来了已经执行过初始化属性的这个操作
问题二:为什么this()只能写在构造方法的第一行
因为到后面会学到继承,所以必须要先完成父类的初始化
public class Test{
public static void main(String[] args){
}
}
class Student{
String name;
int age;
double score;
public Student(){}
//我想使用有参构造方法在创建对象的时候为该对象的属性进行赋值
//但是我们的类可能不是我们自己进行创建,所以我们要在有参的构造方法当中也要做到见名知意
//但是就会出现一个问题在赋值的时候会出现重名冲突所以我们要使用this关键字进行区分
//this指的是当前对象的
public Student(String name,int age,double score){
//局部变量赋值给实例变量
this.name = name;
this.age = agae;
this.score;
}
}
this()使用方法
pubic class Test{
public static void main(String[] args){
//现在有这么个应用场景
//这个老师对象,有些老师是实习老师,是没有工资的,但是想要再创建对象的时候就将该对象的属性赋值
//这个时候三参的构造方法就不能使用了或者说使用起来不方便
//所以我们对构造方法进行重载,再写一个两参的构造方法
//但是写两参的构造方法就有一个问题,三参和二参的构造方法有赋值代码的冗余
//所以这个时候我们就可以在三参的和构造方法当中调用二参的构造方法可以避免一些代码的冗余
}
}
class Teacher{
String name;
int age;
double salary;
public Teacher(){}
public Teacher(String name,int age){
this.name = name;
this.age = age;
}
public Teacher(String name,int age,double salary){
//在本类当中构造方法调用其他参数的构造方法就可以进行对构造方法的复用
this(name,age);
this.salary = salary;
}
}
33.面向对象的三大特性
33.1.封装
33.1.1.为什么要进行封装?
在对象的外部,为对象的属性赋值,可能会存在非法数值的录入,所以我们要使用封装来对外部的赋值进行干预
33.1.2.什么是封装?
封装就是对对象的内部的实现细节进行隐藏,控制用户的访问权限或是说访问方式来保证数据的安全性和合理性
33.1.3.怎么使用封装呢?
对属性使用private关键字进行修饰保证该属性只能在本类进行访问
如果如果不使用封装的话用户便可以对数据直接进行访问,这样的话数据没有所谓的安全性了。所以需要使用private对属性进行修饰,使用private修饰属性的话就只能通过我们所给的方式来对属性进行访问。我们可以提供get/set方法来进行对属性的赋值取值的操作,而在get/set方法当中我们可以对赋值进行干预进行一个合理值的判断
如果在一些特殊的情况下我们可以只提供get方法,或者在一些场景下我们两个都不提供
public class Test{
public static void main(String[] args){
Person p = new Person();
//使用封装以后我们就只能通过公共的方法来对属性进行赋值和取值
p.setName("张三");
}
}
class person{
private String name;
private int age;
private double salary;
public void setName(String name){
this.name = name
}
public String getName(){
return this.name;
}
public void setAge(int age){
this.age = age
}
public int getAge(){
return this.age;
}
public void setSalary(String salary){
this.salary = salary;
}
public String getSalary(){
return this.salary;
}
}
33.2.继承
33.2.1.什么是继承?
在我们现实生活当中,继承就是直系亲属或者亲属的一种赠与与获得
在我们程序当中,继承就是类与类之间行为与特征赠与与获得
那满足什么情况下,我们认为是可以继承的,满足"is a"关系的情况下我们认为是可以继承的
狗是一种动物(成立) ,这个来说,我们就可以认为狗可以继承动物类,
33.2.2.怎么选择父类?
在生活当中很多情况都是满足"is a"的关系,比如狗是一种生物,狗是一种物质
所以我们要在当中选择出最合适的父类来进行继承
功能越精细,重合点越多,越贴近直接父类
功能越粗略,重合点越少,越贴近Object类(万物皆对象的概念)
什么是功能越精细,意思就是对子类的行为描述的越细致
33.2.3.怎么定义父类
在实战中对,可根据使用到的多个具体的类来进行共性的抽取,进而定义父类
对一组相同或相似的类进行共性的抽取,来定义父类的属性和方法实现重用
33.2.4.怎么使用继承
语法: class Dog extends 父类名{}
应用:产生继承关系后子类可以直接使用父类的属性和方法,也可定义子类的独有属性和方法
好处:及提高了代码的复用性,有提高了代码的可扩展性
java是单继承,可以进行多级继承
public class Test{
public static void main(String[] args){
}
}
class Dog extends Animal{
String breend
String furColor;
public void run(){}
}
class Animal{
String age;
String sex;
public void eat(){}
public void sleep(){}
}
33.2.5.不可继承
父类当中有一些是不能被子类所继承的
1.使用private所修饰的属性和方法是不能被子类所继承
2.父类当中的构造方法也是不能被子类所继承的
3.子类跟父类不在同一个包中父类当中使用default修饰的属性和方法子类也是继承不到的
java当中的修饰符
访问修饰符 | 本类 | 同包 | 非同包子类 | 其他 |
---|---|---|---|---|
private | √ | X | X | X |
default | √ | √ | X | X |
protected | √ | √ | √ | X |
public | √ | √ | √ | √ |
我们发现自上到下是由严到宽这么一个过程
33.2.6.重写(OverWrite)
为什么要重写
在程序当中,父类当中的方法不能详细对每一个子类的实现进行详细的实现
此时就需要子类对父类的方法进行重写
简单来说就是父类方法满足不了子类的需求,此时子类就需要对方法进行重写
重写的原则
方法的方法名参数列表返回值必须要与父类的一样
访问修饰符必须要大于等于父类的访问修饰符
子类的访问修饰符为什么要大于等于子类的访问修饰符?
假设父类当中一个方法的访问修饰符是protected,子类重写父类方法的访问修饰符是default,我再写一个测试类它跟父类不在同一个包下,此时在这个测试类当中写了一段父类引用指向另外那个子类的对象。按照引用来说本来是可以调用那个方法,但是基于子类重写父类后的方法的访问修饰符小于父类方法的访问修饰符,导致访问不到子类重写父类后的方法
里氏代换原则:
任何父类可以出现的地方,子类一定可以出现。如果子类的访问修饰符小于父类的话,那就不符合里氏替换原则
33.2.7.super关键字
用于区分父类属性与子类属性重名时区分的作用,还可以用来在继承时子类对父类方法的覆盖但是还要对父类的方法进行复用的情况下使用
场景一:对父类的方法进行复用,然后添加上自己的实现
public class Test{
public static void main(String[] args){
}
}
class A{
public void upload(){
//一百行上传文件的代码
}
}
class B extends A{
public void upload(){
//对父类的功能不满意,想要再添加一个功能
//这个时候就要调用父类的上传文件的方法,再在自己的upload方法当中添加自己的实现
super.upload();
//一行修改文件名称的代码
}
}
场景二:父类和子类当中有重名的属性,需要使用super关键字加以区分
public Test{
public static void main(String[] args){
}
}
class A{
String name;
}
class B extends A{
public setName(String name){
super.name = name;
}
}
继承中的对象的创建
在具有继承关系的对象创建的时候,构建子类对象时会先去构建父类对象
由父类共性内容,叠加子类独有内容,组合成了完整的子类对象
super调用父类构造方法
super()表示调用父类的无参构造方法,如果没有书写,则隐式存在与子类的构造方法首行
因为在构建子类对象之前要先确保父类对象的构建
代码举例:
public class Test{
public static void main(){
C c = new C();
}
}
class A{
int aFiled;
public A(){
super();
System.out.println("初始化aFiled"+aFiled);
}
}
class B{
int bFiled;
public B(){
super();
System.out.println("初始化bFiled"+bFiled);
}
}
class C{
int cFiled;
public C{
super();//构建父类对象,这个super是创建父类的对象,构建完了之后以后再使用super关键字就是对这个构建好的父类的对象(地址)进行访问
System.out.println("初始化cFiled");
}
}
super和this都是指向的地址
super指向的是父类构建好对象的地址
this是指向该类的实例的地址
按照这个代码案例和这个图片来讲解
首先在测试类当中创建一个C对象,而C这个类它是由对象的所以在构建C对象以前要先构建B对象,等要构建B对象的时候发现B类还有一个父类,所以又先去构建A对象,所以就先开始了A对象的构建,其次在构建B对象,最后在构建C对象
通过super()构建了父类的对象,以后子类使用super关键字的时候就是访问开始super()创建的那个对象。而通过super()构建的对象的地址存储在子类当中。所以我们说super实际上指向的就是一个地址
33.3.多态
生活当中的多态
站在不同的视角观察同一个对象,所关注的特征和行为不同
比如作为一个学生,老师和家长所对学生的关注点是不同的
家长关心学生有没有吃饱穿暖
而老师则是关心你的成绩和心理
我们java程序当中的多态是什么?
父类引用指向子类对象,从而实现多种形态
Aniaml a = new Dog();
二者具有直接或者间接的继承关系时,父类引用可以指向子类对象,即形成多态
而当我们以父类的视角关注子类对象时,我们所能见到的就只能是父类当中所定义的子类所继承的属性和方法,不能调用子类当中的独有属性和方法
多态当中的方法覆盖
如果子类覆盖了父类当中的方法,以父类的引用调用此方法的时候,优先执行父类还是子类中的方法?
回答:在实际运行过程中,依旧是遵照覆盖原则,如果子类覆盖了父类的方法,则执行子类覆盖后的方法,如果没有覆盖则执行父类中的方法
为什么使用多态?
在程序当中我们可以是使用多态的形式(父类引用指向子类对象)的形式使方法与参数不再是那么密不可分的一一绑定的关系,解耦。使方法参数变得宽泛。
屏蔽子类之间的差距,解耦,使程序变得更灵活
为什么我们说他屏蔽了子类之间的差距呢,因为当父类以任何方式作为引用时,父类的所有子类都可以作为父类引用的对象
多态的应用
应用1:当作方法当中的形式参数使用
代码案例:
public class Test{
public static void main(String[] args){
}
}
class employee{
String name;
//员工回家需要借助交通工具回家,所以我的goHome方法的形参需要定义一个类型,借助这个类实现回家
//可是这个形参如果只填某一个交通工具下的子类的话就说明只能借助这个工具回家,没有这个方法就不能回家
//还有一个方法就是重载,但是如果后期在添加交通工具的子类的时候就需要再次写一个类型的方法进行重载
//这样的话就形成了方法和类型一一绑定的关系了,耦合性就高了
//public void goHome(Car car){}
//public void goHome(Bus){}
//我们可以在方法参数当中填写父类的类型,这样只要是属于Vehicle的子类都可以当作方法的参数传进去,这样也就不需要再写多个方法进行重载,也方便管理和维护了
public void goHome(Vehicle veh){
System.out.print("下班了"this.name+"乘坐");
veh.run();
}
}
class Vehicle{
String type;
double price;
int speed;
public void run(){
System.out.println("一辆"+price+"的"+type+"正在以"+speed+"/H的速度行驶")
}
}
class Car extends Vehicle{
String brand;
public void run(){
System.out.println("一辆"+brand+"品牌的"+type+"正在以"+speed+"/H的速度行驶");
}
}
class Bus extends Vehicle{
int seatNum;
public void run(){
System.out.println("一辆拥有"+seatNum+"个座位的"+type+"正在以"+speed"/H的速度行驶");
}
}
class Bicycle extends Vehicle{
String color;
public void run(){
System.out.println("一辆"+color+"色的"+type+"正在以"+speed+"/H的速度行驶");
}
}
应用2:当作方法的返回值使用
代码案例:
public class Test{
public static void main(String[] args){
employee emp = new employee();
Vehicle veh = emp.buyVeh(1000);
}
}
class employee{
//买车方法
public Vehicle buyVeh(int money){
Vehicle veh = null;
if(money >= 1000000){
veh = new Bus();
}else if(money >= 500000){
veh = new Car();
}else{
veh = new Bicycle;
}
return veh;
}
}
class Vehicle{
String type;
double price;
int speed;
public void run(){
System.out.println("一辆"+price+"的"+type+"正在以"+speed+"/H的速度行驶")
}
}
class Car extends Vehicle{
String brand;
public void run(){
System.out.println("一辆"+brand+"品牌的"+type+"正在以"+speed+"/H的速度行驶");
}
}
class Bus extends Vehicle{
int seatNum;
public void run(){
System.out.println("一辆拥有"+seatNum+"个座位的"+type+"正在以"+speed"/H的速度行驶");
}
}
class Bicycle extends Vehicle{
String color;
public void run(){
System.out.println("一辆"+color+"色的"+type+"正在以"+speed+"/H的速度行驶");
}
}
33.3.1.装箱与拆箱
装箱是指对象向上转型,即父类引用指向子类对象
拆箱则是指吧这个引用强转成他的本身类型
代码举例:
class Test{
public static void main(String[] args){
//这个是向上转型,父类的引用指向的则是子类的对象,这个就是装箱
Animal a = new Dog();
//这个即是将a引用强制转换成他的本身类型,也就是狗对象,这个就是拆箱
//但是如果父类的引用当中不是这个对象就会报错
//ClassCastException,一个类型转换错误的异常
Dog d = (Dog)a;
//我们为了避免这个错误,引入了instanceof关键字,查看某个父类当中的引用是不是当前这个对象
//使用方式
//引用 instanceof 对象类型
//instanceof关键字的返回值是一个boolean类型的值
//我们可以对引用进行判断,如果引用当中是这个对象则转换对应的类型
if(a instanceof Dog){
Dog d2 = (Dog)a;
}else if(a instanceof Cat){
Cat c = (Cat)a;
}
}
}
class Animal{
}
class Dog extends Animal{
}
class Cat extends Animal{
}
34.三大修饰符
34.1.abstract修饰符
程序是模拟现实世界解决现实问题的,在我们现实生活当中是不存在父类的具体对象,存在的只有父类的子类对象,即在我们的程序当中父类是不应该被创建出来的,我们可以使用abstract修饰类,修饰类以后这个类就不能被创建来了。比如说动物,在现实生活当中是不存在动物的具体类型的,存在的是动物的具体子类类型,比如说猫狗对象
1.使用abstract修饰类
被abstract修饰的类我们称为抽象类,抽象类意味不完整的类、不够具体的类,抽象类是不能被创建出来的抽象类不能独立存在的,是需要依附于子类存在的。
作用:
1.可被子类继承,提供共性属性和方法
2.可声明为引用,强制使用多态。为什么说强制使用多态,因为使用了abstract修饰类以后该类不能被创建了作为引用只能存放子类对象,所以这就强制多态了
经验:
抽象父类,可作为子类的组成部分,依附于子类对象存在,由父类共性+子类独有组成完整的子类对象
2.修饰方法
继承后我们认为父类的有些方法写方法体有些鸡肋,满足不了子类需求,但是父类的方法声明我们认为是有必要的我们需要有这个方法存在这样才可以进行多态
产生继承关系后,子类必须覆盖父类中的所有抽象方方法,否则子类还是抽象类
abstract修饰类:不能new对象,但是可以声明引用。
abstract修饰方法:只有方法声明,没有方法实现。(需包含在抽象类中)
抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类
子类继承抽象类后,必须覆盖父类所有的抽象方法,否则子类还是抽象类
代码举例:
class TestAbstract{
public static void main(Stirng[] args){
//Animal a = new Animal()被抽象后该对象不能创建对象
}
}
//将父类修饰成抽象类
abstract class Aniaml{
public abstract void eat();
}
class Dog extends Aniaml{
//由于继承了Animal这个抽象类,所以要重写他当中的所有抽象方法
public void eat(){
System.out.println("狗在吃骨头");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("猫在吃鱼");
}
}
34.2.static修饰符
实例属性是每个对象各自持有的独立空间(多份),对象单方面修改,不会影响其他对象
什么是静态
概念:
静态可以修饰属性和方法,称为静态属性、静态方法
静态成员是全类所有对象共享的成员。
在全类只有一份,不因创建多个对象而产生多份
不需要创建对象,使用类名点的形式访问
由static修饰的属性和方法会放在方法区当中进行保存
静态属性和方法会在类加载的时候,被保存到方法区
注意:
静态方法不能被重写
静态方法不能调用非静态属性
静态方法不能使用this和super关键字
静态方法可以继承,不能继承,没有多态
为什么静态不能使用非静态属性呢?
因为你的静态方法执行的时候,你无法保证该类的对象被创建出来了,即便被创建出来了你也无法指明是哪个对象的实例属性。this和super也是同理
如果父子类出现了同名的静态方法,也是不构成覆盖的,此时使用父类引用指向子类对象调用静态方法,此时会调用父类版本的静态方法,因为静态方法不能重写所以执行的是父类的版本,在引用的视角来看子类与父类重名的静态方法是子类独有的方法
代码举例:
class TestStatic{
public static void main(String[] args){
//静态属性可以使用类名点的方式来进行访问
A.name = "张三";
//静态方法也可以使用类名点来进行调用
A.staticMethod();
A a = new B();
//实际上调用的是父类的静态方法
a.staticMethod();
A a2 = new A();
a2.staticMethod();//--->这个引用的调用会转换成A.staticMethod()的形式
}
}
class A{
static String name;
public static void staticMethod(){
System.out.println("My-A-staticMethod");
}
}
class B extends A{
public static void staticMethod(){
System.out.println("My-B-staticMethod");
}
}
java种方法调用分为两种
1.静态分派(静态方法中,允许参数列表不同的重名方法,指静态方法之间的重载)
2.动态分派(在具有继承关系的情况下,调用实例方法时,自底向上查找可用的方法版本,指实例方法的覆盖)
java虚拟机方法调用的5个指令
invokespacial 调用私有方法,构造方法
invokeinterface 调用接口方法
invokestatic 调用静态方法
invokevirtual 调用虚方法(父类定义的,日后被覆盖的方法)
invokedynamic 调用动态链接方法
34.2.1类加载
JVM首次使用某个累的时候,需要通过CLASSPATH查找该类的.class文件
将.class文件中对类的描述信息加载到内存当中,进行保存
如:报名、类名、父类、属性、方法、构造方法
加载时机(什么时候进行类加载)
1.创建对象的时候
2.创建子类对象
3.访问静态属性
4.调用静态方法
5.Class.forName(“全限定名”)
动态代码块和静态代码块
动态代码块
使用{}编写在类里面的都叫做动态代码块
class Test{
public static void main(String[] args){
new TestBlock();
}
}
class TestBlock{
{
//动态代码块的执行在实例属性初始化之后在构造方法代码块执行之前
System.out.println("动态代码快执行");
}
}
静态代码块
静态代码块就是在{}前面加上static关键字
静态代码块的执行时机就是在类加载的时候
注意:静态属性和静态代码块的执行顺序是取决于他们的书写位置,如果静态属性写在前面则先执行静态属性的初始化如果静态代码块写在前面则先执行静态代码块
class TestStaticBlock{
public static void main(String[] args){
}
}
class Super{
static String sFiled = "静态属性";
static{
System.out.println(sFiled);
System.out.println("静态代码块执行");
}
}
如果有继承关系的话先执行父类的静态属性初始化,静态代码块然后是子类的静态属性初始化子类的静态代码块
静态的操作只会执行一遍,那就是在触发类加载的时候
完整的构建顺序是
父类的静态属性初始化———>父类的静态代码块———>子类的静态属性初始化———>子类的静态代码块执行
父类的实例属性初始化———>父类的动态代码快———>父类的构造方法的执行———>子类的实例属性初始化
子类的动态代码块执行———>子类的构造方法
34.3.Final修饰符
final代表最终的最后的版本
可以用来修饰方法、类、变量
final修饰类的时候代表最终类,这个类不能被继承
final修饰方法的时候代表这个方法不能被重写
final修饰变量的时候代表这个值不能改变
使用final修饰基本类型的时候,说明这个变量的数值不能发生改变
修饰引用数据类型的时候其地址不能发生改变
使用final修饰的变量没有初始值,是没有默认值的,只允许赋值一次
final修饰属性后的赋值时点
局部常量:显式初始化
实例常量:显式初始化、动态代码快、构造方法
静态常量:显式初始化、静态代码块
代码举例:
class TestFinal{
public static void main(String[] args){
final int[] nums = new int[5];
//这里是可以的,因为这里是对它引用当中的数值改变,并不会改变它的地址
nums[0] = 10;
}
}
final class Super{
//既可以在这里直接为常量赋值,也可以在其他时点为常量赋值
final String name = "张三";
final int age;
final double salary;
{
age = 18;
}
public Super(){
salary = 10000.0;
}
}
//error,错误的,这里如果继承了就会报错
class sub extends Super{
}
35.接口
接口相当于特殊的抽象类,定义方式、组成部分与抽象类类似
使用interface关键字定义接口
接口当中只能定义公开静态常量和公开抽象方法
35.1.接口与抽象类的异同
相同点:
1.接口也会生成.class文件
2.接口不能被创建出来
3.具备object中所定义的方法
不同点:
1.抽象类当中有构造方法,接口没有构造方法
在jdk1.8版本接口当中可以定义静态方法,也可以定义非静态方法
接口当中定义的静态方法只能通过接口名.静态方法名的形式调用
接口当中定义的非静态方法只能通过default修饰
为什么实现了接口,接口当中有静态方法只能通过接口名来调用呢?
不能通过创建实现类的引用来调用接口当中的静态方法,首先接口当中是没有构造方法的,不能通过实现类来进行构建就更不能通过实现类的引用来指向接口的地址找到静态方法
35.2.什么是接口
微观概念:接口是一种能力和约定
接口的定义:代表了某种能力
方法的定义:能力的具体要求
经验:java为单继承,当父类的方法种类无法满足子类需求时,可实现接口扩充子类能力
接口支持多实现
接口的规范
任何类在实现接口时,必须实现接口中的所有抽象方法,否则此类还是抽象类
实现接口中的抽象方法是,访问修饰符必须是public
35.3.接口回调
宏观概念:接口是一种约束是一种约定,规则
在接口回调当中有三个角色,分别是接口、接口的使用者、接口的实现者
比如说我们在后面学习jdbc的时候,java来指定接口也就是规则,我们则是作为接口的使用者,而数据库厂商则是作为接口的实现者,对java提供的接口进行实现以便可以通过java这门语言来对数据库进行操作
我们说接口回调是一种思想,在后面的学习当中还可以体会到接口回调的好处
在java中有Arrays.sort这么一个方法,对数组当中元素进行排序,但是这个方法需要实现Comparable接口这个接口的作用就是来指定排序规则,这个Comparable接口时jdk1.2的时候就已经定义了的,还有这个sort方法,在你没有实现这个接口的时候,sort方法当中就已经通过Comparable这个接口调排序的方法了,也就是说sort方法在这个里面充当接口调用的角色,而我们想要使用sort方法这个工具,就需要实现comparable这个接口,在这里我们就充当接口的实现者这么以一个角色
我们认为实现接口可以是设计和实现分离,因为定义接口后,使用者可以直接使用接口来调用接口当中方法,不需要去关系接口的实现者
36.内部类
概念 : 内部类就是在一个类的内部在编写一个类
特点:编译之后也会生成独立的字节码文件
内部类可以直接访问外部类的私有成员,而不破坏封装
可为外部类提供必要功能组件
36.1.成员内部类
在类的内部定义与实例属性、方法同级别的类
在创建成员内部类对象的时候,必须要依赖外部类对象
成员内部类不能定义静态成员(静态属性、方法、代码块)都不行
public class TestMember{
public static void main(String[] args){
//而成员内部类的创建需要依赖外部类对象,所以要先创建外部类对象
Outer out = new Outer();
Outer.Inner inn = out.new Inner();
inn.m1();
}
}
class Outer{
int a = 10;
class Inner{
int a = 20;
public void m1(){
System.out.println(a);
//当外部类和内部类的实例变量重名是,需要使用类名.this.属性名的形式来进行区分
System.out.println(Outer.this.a);
}
}
}
36.2.静态内部类
不依赖外部类对象,可直接创建或通过类名访问,可声明静态成员
只能直接访问静态内部类当中定义的静态成员(实例成员需要实例化静态内部类才可以)
创建外部类的时候,静态内部类不会被类加载
public class TestStaticInnerClass{
public static vodi main(String[] args){
//可以通过外部类.静态内部类.属性来进行访问
System.out.println(Outer.Inner.a);
//但是当你访问静态内部类的实例属性的时候,就需要先创建静态内部类
Outer.Inner inn = new Outer.Inner();
System.out.println(inn.b);
}
}
class Outer{
static class Inner{
static int a = 10;
int b = 20;
}
}
public class TestBank {
public static void main(String[] args) {
}
}
class Bank{
public void login() throws ClassNotFoundException {
Class.forName("Informaction");
}
//单一职能原则,上升到类与内部类之间,模块与模块之间
//这个静态内部类是为了完成在银行对象创建的时候就完成了用户的初始化
//内部类:可为外部类提供必要功能组件
private static class Informaction{
static final User[] users = new User[5];
private Informaction(){}
static {
//在静态代码块当中完成对数组值的初始化
users[0] = new User();
users[1] = new User();
users[2] = new User();
users[3] = new User();
}
}
}
class User{
}
36.3.局部内部类
就是在类当中方法体内定义类
语法:
public class TestPartInnerClass{
public void run(){
//这就是局部内部类的定义语法,局部内部类也能像其他类一样进行继承和实现
class PartInnerClass{
}
}
}
注意点:就是局部内部类在访问外部类局部变量的时候,该局部变量需要加上final关键字,但是现在编译器已经隐式的加上了final关键字,但是为什么要加final关键字呢,是因为对象的回收时机和普通局部变量的回收时机不一致,局部变量当方法体结束的时候该局部变量就会被回收掉,但是类不会。所以变量需要加上final关键字加长变量的回收时机
我们看该局部内部类实现了接口,类当中也定义了以接口为类型的引用,所以后面其他方法使用到类当中的方法,而这个方法当中又有局部变量,又要保证在其它方法访问该变量的时候不能导致变量被回收而不能被使用,所以该局部变量就要被加上了final关键字,至于这里为什么没有显示写,是因为编译器默认加上了,看下图
当我们改变这个变量的值的时候,他就会报错,就是因为这个关键字被编译器默认加上的原因,导致不能改变这个变量的值
代码案例
public class TestSchool {
}
class School{
public void getTeacher(int number){
//为什么这里要使用局部内部类呢(隐藏类)
//因为在这里需要隐藏类的信息,我不希望外部可以直接创建该类
//所以我在使用局部当中定义了这两个类
//我们认为这样设计更加合理
class beginnerTeacher extends Teacher{
@Override
public void teaching() {
System.out.println("初级老师在上课...");
}
}
class advancedTeacher extends Teacher{
@Override
public void teaching() {
System.out.println("高级老师正在上课...");
}
}
Teacher t = null;
//根据奇偶数来返回老师的等级,来进行授课
if(number % 2 != 0) {
t = new beginnerTeacher();
}else {
t = new advancedTeacher();
}
}
}
abstract class Teacher{
public abstract void teaching();
}
36.4.匿名局部内部类
没有类名的局部内部类(一切特征都与内部类相同)
必须继承一个父类或者实现一个接口
定义类、实现类、创建对象的语法合并
优点:减少代码量
缺点:可读性较差
public class InnerClass{
public static void main(String[] args){
//这里编写了一个实现Runnable接口的匿名内部类
Runnable run = new Runnable(){
@Override
public void run(){
}
};
}
}
37.Object
Object是所有类的直接或间接的父类,位于继承树的最顶层
任何类,如果没有书写extends显示继承某个类,都默认继承Object类,否则为间接继承
Object当中定义的方法,是所有对象都具备的方法
Object类型可以存储任何对象
作为参数,可以接受任何对象
作为返回值,可以返回任何对象
37.1.getClass方法
返回引用中存储的实际对象类型
应用:通常用于判断两个引用中实际存储的对象类型是否一致
public class TestObjectMethods{
public static void main(String[] args){
//创建Object类
Object o = new Object();
//返回o对象的全线命名
//class java.lang.Object
System.out.println(o.getClass());
Animal a1 = new Dog();
Animal aw2 = new Cat();
if(a1.getClass() == a2.getClass()){
System.out.println("这两个引用的真实类型是一致的");
}else{
System.out.println("这两个引用的真实类型不一致");
}
}
}
class Cat extends Animal{}
class Dog extends Animal{}
class Animal{}
class Fish extends Animal{}
37.2.hashCode方法
public int hashCode(){}
返回该对象的十进制的哈希值
哈希算法根据对象地址(物理地址)或字符串或数字计算出来的int类型的数值
哈希码并不唯一,可保证相同对象返回相同哈希码,尽量保证不同对象返回不同哈希码
public class Test{
public static void main(String[] args){
Object o = new Object();
//返回该对象的哈希码
System.out.println(o.hashCode());
}
}
37.3.toString方法
toString就是打印对象的全线命名+@+十六进制的哈希码
返回该丢i想的字符串表示形式
可以根据程序需求覆盖该方法,如:展示对象各个属性值
public class Studnet{
int age;
String name;
double salary;
@Override
public String toString(){
//我们一般对Object的toString做覆盖,来当作于属性的展示
return "Student [age=" + age + ", name=" + name + ", salary=" + salary + "]";
}
}
37.4.equals方法
public boolean equals(Object obj){}
在Object类当中的默认实现为(this == obj),比较两个对象地址是否相同
可进行覆盖,比较两个对象的属性是否相同
我们现在有一个场景:就是我想比较两个对象是否一致,一致的依据是什么,依据就是两个对象当中的属性值都一样那么我就认为这两个对象他是一致的,那么我们无论使用==或者使用Object当中提供的equals方法都是无法实现的,但是我们可以对Object类当中equals方法进行覆盖
我们认为在进行覆盖equals方法的时候要按照这五个步骤走
1.判断这两个对象地址是否一致,一致的话说明这是两个一样的对象,直接返回true,不需要进行接下来的判断了
2.判断传入的参数是否是null,如果是null的话就说明这个引用当中是没有地址的,就直接返回false就好,不需要后续的比较了
3.判断这两个对象的类型是否一致,如果不一致就说明这两个对象没有比较的意义了,也就没有价值直接返回false
4.如果执行到第四步了的话就说明这两个对象的类型是一致的,为了后续能够拿到该对象的属性,就需要对该对象进行转型了,如果不转型的话传进行参数的引用是Object类型,父类是不可见子类的独有属性和方法的,所以这一步我们要进行转型
5.到这一步就要进行两个对象当中的值是否一致了,必须每个属性的值都要相同我们最终的结果才能返回true,所以这属性值的比较是and的关系,需要注意的是如果是字符串类型的话要使用equals方法去进行比较
代码举例
public class TestStudent {
public static void main(String[] args) {
Student stu = new Student(20,"张三",5000,"男");
Student stu2 = new Student(20,"张三",5000,"男");
Student stu3 = new Student(19,"李四",2000,"女");
System.out.println(stu.equals(stu3));
}
}
class Student{
int age;
String name;
double salary;
String sex;
public Student() {}
public Student(int age,String name,double salary,String sex) {
this.age = age;
this.name = name;
this.salary = salary;
this.sex = sex;
}
@Override
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(obj == null) {
return false;
}
if(this.getClass() != obj.getClass()) {
return false;
}
Student s = (Student)obj;
if(this.age == s.age && this.name.equals(s.name) && this.salary == s.salary && this.sex.equals(s.sex)) {
return true;
}
return false;
}
}
37.5.finalize方法
finalize是用来标记垃圾对象的方法,当对象被判定为垃圾对象时,有JVM自动调用此方法,用于标记垃圾对象
垃圾对象:没有有效引用只想此对象时,为对垃圾对象
垃圾回收:由GC销毁垃圾对象,释放数据存储空间
自动门回收机制:JVM的内存耗尽,一次性回收所有垃圾对象
手动回收机制:使用System.gc():通知JVM执行垃圾回收
finalize方法只会被执行一次,如果没有标记成功的话(比如说在覆盖finalize方法后是其他类的引用指向当前该对象,如果这样的话该对象就不会被再次调用finalize方法了),该对象就不会被回收掉
做垃圾回收也是会占用资源的,所以最好不要自己手动去释放
JAVA使用的是可达性分析算法
有的语言使用的是引用计数器
38.包装类
38.1.包装类的类型
基本数据对应的引用数据类型
Object可同意所有数据,包装类的默认值是null
8中包装类提供不同类型间的转换方式
Number父类中提供的6个共性方法
parseXXX()静态方法
valueOf()静态方法
注意:需保证类型兼容,否则抛出NumberFormatException异常
基本数据类型对应的包装类分别为
基本数据类型 | 引用数据类型 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
六个数字的包装类的常用方法,以Byte为例进行讲解
static parseByte(String s):将字符串类型的参数,转换成byte类型
static ValueOf(byte b):将byte类型转换成Byte类型
shotValue():将该对象当前的值转换成short类型
…
构造方法
Byte(String s):通过构造方法在创建对象的时候将字符串转换成Byte类型
Byte(byte b):通过构造方法在创建对象的时候将byte类型转换成Byte
代码举例:
public static void main(String[] args){
public static void main(String[] args){
Byte b = new Byte(10);
Byte b2= new Byte("20");
Byte b3 = Byte.parseByte("30");
byte b4 = Byte.ValueOf(40);
byte b5 = b.byteValue();
}
}
38.2.自动装箱自动拆箱
案例题
public class TestPacking{
public static void main(String[] args){
Short s1 = 100;
Short s2 = 100;
Short s3 = 200;
Short s4 = 200;
System.out.println(s1 == s2);//true
System.out.println(s3 == s4);//false
//为什么第一个结果是true,第二个结果却是false
}
}
上述案例是因为java默认在底层进行了自动装箱的过程调用了对应包装类的ValueOf方法,而此方法会对你的值进行判断,如果在-128~127之间的话,Short的内部类会做一个缓存,他会对这些范围的对象做一个缓存,如果下次使用的话拿到的就是相同的对象,所以上述案例再进行判断的时候100会是true而200会是false。至于为什么会是byte的取值范围呢,因为刚好这些值是较为高频的使用数字范围,这样的话如果是这个范围的值的话,就不需要重复创建对象,而这些范围的值则可以重复使用,避免重复创建
这样写可以大大的节省空间,在使用常用值的时候可以不用重复创建对象
pubic class Short{
//在使用字面值为Short对象赋值的时候,java底层会调用这个方法来对你的值进行判断
public static Short valueOf(short s) {
final int offset = 128;
int sAsInt = s;
//如果你的值在这个范围内,就将ShortCache这个内部类的数组对应的值进行返回
if (sAsInt >= -128 && sAsInt <= 127) { // must cache
return ShortCache.cache[sAsInt + offset];
}
return new Short(s);
}
private static class ShortCache {
//将构造方法私有,我这个对象不能被创建
private ShortCache(){}
//定义常用数字的范围
static final Short cache[] = new Short[-(-128) + 127 + 1];
//使用静态代码块来对对象进行循环创建,初始化
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Short((short)(i - 128));
}
}
}
39.String类
字符串是常量,创建之后不可改变
Stirng底层就是一个字符组数来对字符串进行保存的
字符串字面值存储在字符串池中,可以共享
String s = “Hello”:产合适呢个一个对象,字符串池中存储
String s = new String(“Hello”)😕/产生两个对象,堆、池各存储一个
public class TestString{
public static void main(String[] args){
//第一个abc这个值存储在字符串池当中
String s1 = "abc";
//因为第一个值是常量,存储在常量池当中,所以第二个是从池当中拿的,进行复用的
String s2 = "abc";
//两者比较使用等号,比较的是地址,二这两个对象都是从相同的串池当中拿的对象,所以两者比较的结果为true
System.out.println(s1 == s2);//true
//第一个new的时候会在串池当中存储一份,还会再堆当中生成一个对象
String s3 = new String("Hello");
//第二个对象则是从串池当中取值
String s4 = "Hello";
//但是比较的时候仍是比较的地址,一个是堆当中的地址一个是串池当中的地址,所以两者比较的结果为false
System.out.println(s3 == s4);//false
}
}
String当中常用的方法
public char CharAt(int index):根据下标获取字符
public boolean contains(String str)判断当前字符串中是否包含str
public char[] toCharArray():将字符串转换成字符数组
public int indexOf(String str):查找str首次出现的下标,存在则返回改下标,不存在则返回-1
public int lastIndexOf(String str):查找该字符最后一次出现的下标索引并返回
public int length():返回字符串的长度
public String trim():将字符串前后的空格去掉
public String toUpperCase():将小写转成大写
public String toLowerCase():将字符所有字母都转换成小写
public boolean endWith(String str):判断字符串是否以str结尾
public boolean startWith(Stirng str):判断字符串是否以str开始
public String replace(char oldChar,char newChar):讲究字符串替换成新字符串
public String[] split(String str):根据str做拆分,拆分后不包含该字符
40.StringBuilder和StringBuffer
因为String是常量可以被服用,但是如果使用拼接字符,对String进行拼接的话,它会在字符串常量池当中再次生成一个对象。举例
TestString{
public static void main(String[] args){
//在字符串常量当中生成abc这个常量对象,以便后续的复用
String str = "abc";
//这里拼接后字符串常量当中会再生成一个abcdef的字符串对象
str+="def";
//但是如果频繁的要做字符串拼接的话,就会在字符串常量池当中生成多个对象,这样的话会造成空间的浪费
//使用StringBuffer和StringBuilder的话就不会这样,因为他会们会对字符数组进行扩容,不会因为拼接而生成多个对象
//StringBuffer和StringBuilder使用append方法来对字符串进行拼接,则不会在常量池当中生成对象
StringBuilder sbuilder = new StringBuilder("abc");
//使用append方法进行拼接的话便不会生成新对象
sbuilder.append("edf");
}
}
StringBufer:可变长字符串,JDK1.0提供,运行效率慢,线程安全
StringBuilder:可变长字符串,JDK5.0提供,运行效率快、线程不安全
可变长字符串不会在字符串常量池中生成对象,只会在堆中生成对象
注意:在JDK1.5的时候,你使用String做+=拼接的时候,底层会创建一个StringBuilder使用append来对字符串进行拼接,然后在调用toString方法进行返回,你就拿到了是通过Stringbuilder拼接后的字符串,这是java底层对String做的优化
41.BigDecimal
在我们使用float和double的时候计算出来的结果都是近似值,不够精确所以我们以后使用小数加减的操作的话就是用BigDecimal来进行运算
BigDecimal的构造方法是重载的
构造方法 | 含义 |
---|---|
BigDecimal(int val) | 创建一个具有参数苏指定的整数值对象 |
BigDecimal(double val) | 创建一个具有参数所指定的双精度的对象(不推荐使用,使用双精度构造BigDecimal对象进行计算的话会存在精度丢失问题) |
BigDecimal(long val) | 创建一个具有参数所制定长整数的对象 |
BigDecimal(String val) | 创建一个具有参数所指定以字符串表示的数值对象(推荐使用) |
BigDecimal常用方法
方法 | 含义 |
---|---|
add(BigDecimal) | BigDecimal对象中的值相加,返回BigDecimal对象 |
subtract(BigDecimal) | BigDecimal对象中的值相减,返回BigDecimal对象 |
multiply(BigDecimal) | BigDecimal对象中的值相乘,返回BigDecimal对象 |
divide(BigDecimal,scale,roundingMode) | BigDecimal对象中的值相除,返回BigDecimal对象(但是可能会会出现除不尽的情况,这个时候会报错,所以需要填写参数,参数二:保留几位小数,参数三:按照四舍五入的方式进行保留) |
42.集合
42.1.Collection父接口
特点:代表一组任意类型的对象,无序、无下标
方法:
boolena add(Object obj)//向集合当中添加一个对象
boolean addAll(Collection)//将一个集合中所有的对象添加到此集合当中
voif clear()//清空集合中所有对象
boolean contains(Object o)//检查此集合中是否包含o对象
boolean equals(Object o)//比较此集合是否与指定对象相等
boolena isEmpty()//判断此集合是否为空
boolean remove(Object o)//在此集合中移除o对象
int size()//返回此集合当中的元素个数
Object[] toArray()//将此集合转换成数组
42.2.List子接口
特点:有序、有下标、元素可以重复
是Collection的子接口
方法:
void add(int index,Object o)//在index位置插入对象
boolean addAll(int index,Collection c)//将一个集合中的元素添加到此集合中的index位置
Object get(int index)//返回集合中指定位置的元素
List subList(int fromIndex,int toIndex)//返回formIndex和toIndedx之间的集合元素
42.2.1.ArrayList
ArrayList是List接口的实现类
因为List接口是有序有下标的所以ArrayList是List实现类所以ArrayList也具备这些特性
ArrayList的特性是:查询快,增删慢
ArrayList在jdk1.6的时候数组的默认长度是10,但是在1.7的时候默认值就变成了0
注意ArrayList当中的toArray时返回集合当中数组的副本
public class TestArrayList{
public static void main(String[] args){
ArrayList a = new ArrayList();
a.add(99D);
a.add(95D);
a.add(98D);
a.add(97D);
a.add(96D);
int sum = 0;
for(int i = 0;i<a.size();i++){
sum += (double)a.get(i);
}
}
}
42.2.2.Vector
使用方法与ArrayList相同
与之不同的是Vector是线程安全的集合,但是效率相较于ArrayList来说较慢
42.2.3.LinkedList
实现方式是通过链表的形式来实现的的
与ArrayList不同就是实现方式LinkedList是通过链表实现,而ArrayList是通过数组实现的
链表能够利用散列的空间进行存储
为什么说链表的增删快,因为链表是通过上一个对象来进行关联的,你如果要增删的话可以直接更改上一个对象的链接,链接到要插入的对象,而访问慢的原因是读取的话要一个一个通过对象查找下去,这样会导致效率很慢
但是数组访问元素就不一样了,数组可以直接通过游标的形式来对数组下标进行访问,他是直接进行定位的,但是如果是增删的话,增加一个元素往后的所有元素都要向后位移以为一位,效率会特别的慢
42.3.Collections工具类
概念:集合工具类,定义了除了存取以外的集合常用方法
方法:
public static void reverse(List<?> list)//反转集合中元素的顺序
public static void shuffle(List<?> list)//随即冲值几何元素的顺序
publi static void sort(List<?> list)//升序排序(元素类型必须实现Comparable接口)
42.4.Set子接口
特点:无序、无下标、元素不可重复
方法:全部继承自Collection中的方法
42.4.1.HashSet实现类
HashSet基于HashCode实现元素不重复的
当存入元素的哈希码相同时,会调用equals进行确认,如结果为true,则拒绝后者存入
但是使用哈希码进行验证有个问题,比如我们想验证对象不同但是对象的属性相同的,此时父类提供哈希码的实现类就不是那么好用了,因为相同属性地址不同的对象哈希码极大概率是不会重复的,所以哈希码不同但是属性相同的两个对象就不会进行equals的比较了
不过此时我们可以选择使用对象属性的哈希码进行拼接作为整体的哈希码进行返回,此时如果哈希码相同,我们就认为这两个极有可能属性相等,所以我们就有必要去调用equals方法进行验证
HashSet的无参构造方法
public HashSet(){}构建HashSet对象,默认初始容量16,加载因子0.75(扩容大小)
代码举例:
package Collection;
import java.util.HashSet;
public class TestHashSet{
public static void main(String[] args){
Student s1 = new Student("张三",20,'男',50.0);
Student s2 = new Student("李四",23,'男',1000.0);
Student s3 = new Student("张三",20,'男',50.0);
HashSet<Student> s = new HashSet();
s.add(s1);
s.add(s2);
s.add(s3);
}
}
class Student{
Integer age;
String name;
Double money;
Character sex;
public Student() {}
public Student(String name, Integer age, Character sex, Double money) {
super();
this.name = name;
this.age = age;
this.sex = sex;
this.money = money;
}
@Override
public int hashCode(){
//如果这种情况两个对象的哈希码都一致的话,那么我们认为有必要调用equals来进行进一步的认证
return name.hashCode()+age.hashCode()+sex.hashCode()+money.hashCode();
}
@Override
public boolean equals(Object o){
System.out.println(this.name);
if(this == o)return true;
if(o == null)return false;
if(this.getClass() != o.getClass())return false;
Student s = (Student)o;
if(this.name.equals(s.name) && this.age.equals(s.age) && this.sex.equals(s.sex) && this.money.equals(s.money))return true;
return false;
}
}
42.4.2.LinkeHashSet实现类
Set本身无序,但是基于LinkedList是通过链表的形式实现的,所以就变得的有序了,插入顺序
代码举例:
public class TestLinkedList{
public static void main(String[] args){
Linked<Stirng> li = new LinkedHashSet();
li.add("C");
li.add("D");
li.add("E");
li.add("F");
//会按照你插入的顺序来进行输出,因为它通过链表的形式保存了你的顺序
for(String str : li){
System.out.println(str);
}
}
}
42.4.3.TreeSet实现类
基于排序实现元素不重复
实现了SortSet接口,对集合元素自动排序
元素对象的类型必须实现Comparable口,指定排序规则
通过CompareTo方法确定是否为重复元素
compareTo方法返回-1说明让元素靠前,返回1说明让元素靠后,返回0说明两个元素相等
class TestTreeSet{
public static void main(String[] args){
TreeSet<Teacher> t = new TreeSet<Teacher>();
for(Teacher te : t) {
System.out.println(te);
}
}
}
class Teacher implements Comparable<Teacher>{
String name;
Double salary;
Integer age;
Character sex;
public Teacher() {}
public Teacher(String name, Double salary, Integer age, Character sex) {
super();
this.name = name;
this.salary = salary;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Teacher [name=" + name + ", salary=" + salary + "]";
}
@Override
public int compareTo(Teacher t) {
if(this.salary > t.salary) {
return 1;
}else if(this.salary < t.salary){
return -1;
}else {
if(this.age > t.age) {
return 1;
}else if(this.age < t.age) {
return -1;
}else {
if(this.sex.compareTo(t.sex) == 1) {
return 1;
}else if(this.sex.compareTo(t.sex) == -1) {
return -1;
}else {
return this.name.compareTo(t.name);
}
}
}
}
}
42.5.Map接口
Map接口的特点:
1.用于存储任意键值对(Key-Value)
2.键:无序无、下标、元素不允许重复(唯一)
3.值:无序、无下标、允许元素重复
方法:
V put(K key,V value)//将对象存入到集合中,关联键值。key重复则覆盖原值
Object get(Object key)//根据对应的key获取对应的值
Set keySet//返回所有的key
Collection values()//返回包含所有的值的Collection集合
Set<Map.Entry<K,V>> entrySet()//键值匹配的Set集合
42.5.1.HashMap实现类
基于哈希表的Map接口的实现,键和值是允许使用null作为他们的值的
此类不保证映射顺序
特点:线程不安全,访问效率高
public class TestHashMap{
public static void main(String[] args){
HashMap<String,String> h = new HashMap();
h.put("CN","中国");
h.put("JP","日本");
h.put("US","美国");
//获取当前Map集合的所有键
Set<String> keys = h.keySet();
for(String key : keys){
System.out.println(key);
}
//获取当前Map集合的所有值
Collection<String> values = h.values();
for(String value : values){
System.out.println(value);
}
//获取当前集合所有的键值对
//这个Map.Entry是map集合当中一个存储键值对的一个静态内部类
//它用来代表一个键值对
Set<Map.Entry<String,String>> entrys = h.entrySet();
for(Map.Entry<String,String> entry : entrys){
system.out.println(entry);
}
}
}
HashMap的实现原理
JDK1.7:数组+链表
JDK1.8:数组+链表+红黑树
首先插入一个值的时候,他会对key做一个哈希值的计算(hash方法)再将这个哈希值,对hashmap的长度取余,计算出存储于数组位置,也会出现对长度取余后出现与之前计算后相同的位置,此时就会转换成链表进行存储,当链表的长度大于8的时候就会转变成红黑树,当红黑树的节点小于6时就会有转换成链表
42.5.2.HashTable实现类
HashTable和HashMap的使用方法是一致的,
它们两者的主要区别的在于HashTable的线程安全但是效率低
42.5.3.Properties类
他继承了HashTable类
该类没有泛型,他主要作用就是用来读取properties文件后缀的配置文件
这个类主要服务字符串,要求key和value都是String类型
这个类在设计的时候就有缺陷,因为他继承了HashTable他就有了put该方法,这个方法的参数都是泛型,但是由于properties不支持泛型,导致put方法可以放一些Object对象,导致了不安全
我们一般就使用它提供的setproperties(String,String)方法对这个集合进行存值
使用getproperties从这个集合当中读取值
public class TestProperties{
public static void main(String[] args){
properties p = new properties();
p.setProperties("UserName","jack");
p.setProperties("password","123456");
System.out.println(p.getProperties("UserName"));
}
}
42.5.4.TreeMap实现类
基于Comparable接口实现对键的排序,也基于这个接口才能实现键的不重复
存入这个集合的键必须实现Comparable接口,否则出现类型转换的异常
TreeMap是sortedSet接口的实现类
42.5.5.集合的总结
实际上HashSet和TreeSet都是对HashMap和TreeMap的复用
HashSet类当中有一个HashMap的属性,当你存值的时候它实际上实在操作HashMap,当你存值的时候它实际上就存在了HashMap的键上面,这是一种很高级的代码复用的一种思想
集合概念:
对象的容器,存储对象的对象 ,定义了对多个对象进行操作的常用方法
List集合:
有序有下标,元素可重复(ArrayList、LinkedList、Vector)
Set集合:
无序无下标,元素不可重复(HashSet、TreeSet、LinkedHashSet)
Map集合:
存储一对数据,无序无下标、键不可重复、值可重复(HashMap、HashTable)
Collections:
集合工具类,定义了除了存取以外的集合常用方法
43.泛型
参数化类型,规范数据类型,使集合更加安全
好处:使我们的程序更加具有安全性
使用泛型之后不需要再做强制类型的转换
应用场景:可以规范集合的数据类型,不需要担心取值的时候类型转换的问题
我们可以给类加上泛型,可以在创建的时候给泛型的的类型,泛型还可以应用在方法当中是我们的方法更加灵活
注意:泛型的指明只能是引用数据类型,不能是基本数据类型
代码举例:
public class TestGenercity{
public static void main(String[] args){
//指明泛型的类型,这个集合就只能存放Integer类型的数据
List<Integer> list = new ArrayList();
Person p = new Person();
p.m1(new Object());
}
}
class Person<E>{
public void m1(E e){}
}
静态泛型
如果是静态方法需要对参数做规范的取值使用泛型的话,就不能使用类声明的泛型了,因为静态方法是不需要依赖对象创建的时候才能调用,但是类的泛型是需要创建对象的时候指明类型的,所以静态方法不能使用类声明的泛型
但是有静态的泛型可以为我们使用,静态泛型的声明我们声明在static关键字后面
代码举例:
public class TestStaticGenercity{
public static void main(String[] args){
}
}
class Animal{
//对参数类型有了限制和要求,必须是继承Animal类,并且实现Comparble接口的类
//注意:如果同时对继承类和实现接口有要求的话,类要先写在前面
public static<T extends Animal & Comparable> void m1(T t){}
}
44.异常
概念:程序在运行过程中出现的特殊状况
异常处理的必要性:任何程序都可能存在大量的未知问题,错误;如果不对这些问题进行正确处理,则可能导致程序的中断,造成不必要的损失
异常的分类:
Throwable:可抛出的,一切错误或异常的父类,位于java.lang包中
|-Error:JVM、硬件、执行逻辑错误,不能手动处理
|-Exception:程序在运行和配置中产生的问题,可处理
|-RuntimeException:运行时异常,可处理,可不处理
|-CheckedException:受查时异常,必须处理
运行时异常一般都有:ClassCastException、 IndexOutOfBoundsException…
1.异常的产生:
自动抛出异常:当程序在运行时遇到不符合规范的代码或结果时,会产生异常
手动抛出异常:语法throw new异常类型(“实际参数”)
产生异常结果:相当于遇到return语句,导致程序因异常而终止
2.异常的处理
第一种方式:
使用try-catch的结构来对异常进行捕获处理
try{
可能出现的异常代码
}catch(Exception e){
异常处理的相关代码,如getMessage(),printStackTrace()
}
try-catch这种形式就是要对异常进行处理掉
try{
可能出现异常的代码
}catch(){
异常处理的相关代码,如getMessage()、printStackTrace()
}finally{
无论是否出现异常,都需执行的代码结构,通常用于释放资源
}
finally的优先级要大于return关键字的优先级
所以在执行方法的时候如果执行到return关键字的话就会先执行finally里的内容
但是如果想写的最终必须执行的代码写不写在finally最终的结果都一样
第二种方式:
使用throws的形式向上报告,告知调用者此方法可能出现的异常
写在方法参数的后面throws 异常类型
throws一般是自己抛出的异常,然后对外调用我这个方法的告知,我这个方法可能会出现异常,你要处理
所以自己抛出的异常一般都会是对外进行声明
3.异常的传递
异常会按照方法的调用顺序进行反向的传递,如果在程序的入口还没有处理这个异常的话,那么最后就由JVM进行处理,JVM处理的方式是打印堆栈信息
public class TestRuntimeException {
//这是最后的执行结果,我们可以看到
//main-start
//m1-start
//m2-start
//m3-start
//m2-end
//m1-end
//main-end
//java.lang.Exception: 奇数不能打印菱形
//at Exception.TestRuntimeException.m3(TestRuntimeException.java:29)
//at Exception.TestRuntimeException.m2(TestRuntimeException.java:19)
//at Exception.TestRuntimeException.m1(TestRuntimeException.java:13)
//at Exception.TestRuntimeException.main(TestRuntimeException.java:8)
public static void main(String[] args) throws Exception{
System.out.println("main-start");
m1(7);
System.out.println("main-end");
}
public static void m1(int num) throws Exception {
System.out.println("m1-start");
m2(num);
System.out.println("m1-end");
}
public static void m2(int num){
System.out.println("m2-start");
try {
m3(num);
}catch(Exception e) {
e.printStackTrace();
}
System.out.println("m2-end");
}
public static void m3(int num) throws Exception{
System.out.println("m3-start");
if(num %2 != 0) {
//说明传过来的这个数是奇数,我们认为奇数是不能进行打印的,所以要抛出一个异常
throw new Exception("奇数不能打印菱形");
}
System.out.println("打印菱形");
System.out.println("m3-end");
}
}
4.常见的异常处理结构
try{}catch{}
try{}catch{}finally{}
try{}catch{}catch{}finally{}
try{}finally{}
注:多重catch,遵循从子(小)到父(大)的顺序,父类一场在最后
44.1.自定义异常
需继承Exception或者Exception的子类RuntimeException
继承Exception得到受查时异常,继承RuntimeException得到运行时异常
必要提供的构造方法
无参构造方法
String message的参数构造方法
方法覆盖
带有异常声明的方法覆盖
方法名、参数列表、返回值类型必须和父类相同
子类的访问修饰符合父类相同或是比夫雷更宽
子类中的方法,不能抛出比父类或接口更宽泛的异常
一个异常的代码举例:
public class TestException{
public static void main(String[] args){
int i = m1();
//最终的输出结果是30
System.out.println(i);
}
public static void m1(){
int a = 10;
try{
a = 20;
throw new RuntimeException();
}catch(Exception e){
a = 30;
return a;
}finally{
a = 40;
}
}
}
详细解释图

我们发现在执行到catch块中的时候要执行完return语句的时候因为finally优先级要高所以方法不会立即结束进行返回而是将返回值的这个值保存在局部变量表当中,而当finally执行后在进行返回,所以最终的返回值是30,但是返回的时候a的值是40的
上面这张详细解释图是下面这张图JVM栈的微观图
45.进程与线程
45.1.什么是进程
程序是静止的,只有真正运行起来的程序,才称为进程
单核cpu在任何时间点上,只能运行一个进程;宏观并行微观串行(但是现在一般的cpu都是多核的)
解释一下什么是宏观并行,如果是单核cpu的情况下,运行着多个应用程序,因为是单核cpu的原因所以在同一时间内只能运行一个进程,他是在一个时间段内执行一个进程,这个时间段执行完了之后再去执行其他的进程,正是因为这样所以你会感觉他在执行很多进程,因为cpu的执行效率很快所以你感觉不到了,我们在宏观的层面上来看他是并行的。但是为什么说微观下是串行的呢,是因为单核的cpu它同一时间只能执行一个进程,所以我们微观的看的话就是cpu执行完当前分配的时间片的线程后紧接着可能会去执行其他进程的线程所以我们看来的话是串行的
45.2.什么是线程
线程是轻量级的进程,程序中的一个顺序控制流程,同时也是cpu的基本调度单位。进程由多个线程组成,彼此之间完成不同的工作,交替执行,称为多线程
线程是cpu的基本调度单位
线程的组成
任何一个线程都具有基本的组成部分
·CPU时间片:操作系统(OS)会为每个线程分配执行时间,cpu运行线程的时间(cpu来对时间片进行分配)
·运行数据
堆空间:存储线程需使用的多个对象,多个线程可以共享堆中的对象
栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈
·线程的逻辑代码
45.3.创建线程
创建线程有两种方式
45.3.1.通过继承Thread类
代码举例:
public class TestTherad{
public static void main(String[] args){
//3.创建对象
MyThread t = new MyThread();
//4.调用start方法
t.start();
}
}
//1.继承Thread类
class MyThread extends Thread{
//2.覆盖run方法
@Override
public void run(){
for(int i = 1;i<=50;i++)
Syste.out.println("MyThread-"+i);
}
}
}
45.3.2.通过实现Runnable接口
代码举例:
public class TestImplementsRunnable{
public static void main(String[] args){
//3.创建线程任务对象
MyRunnable mb = new MyRunnable();
//4.创建线程对象,把任务对象交给线程对象
Thread t = new Thread(mb);
//5.使用Thread去调用start方法
t.start();
}
}
//1.实现Runnable接口
class MyRunnable implements Runnable{
//2.重写run方法
@Override
public void run(){
for(int i = 1;i<=50;i++){
System.out.println("MyRunnable-"+i);
}
}
}
45.4.线程常见方法
·休眠
public static void sleep(long millis){}
当前线程主动休眠millis毫秒,当前线程休眠期间其他线程继续执行
·放弃
public static void yeild()
当前线程主动放弃时间片,回到就绪状状态,竞争下一次时间片
·结合
public final void join()
允许其他线程加入到当前线程,一旦其他线程进入到当前线程的话必须要加入的线程执行完后才能执行当前线程
45.5.线程安全
什么是线程不安全,会导致什么问题
线程不安全就是,当多线程并发访问临界资源时,如果破坏了原子操作,导致数据不一致
临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保其正确性
原子操作:不可分割的多步操作,被视为一个整体,其顺序步骤不可打乱或缺省
代码举例:
public class TestThreadSafe {
public static void main(String[] args) {
Account acc = new Account("040212","123456",2000D);
Thread t1 = new Thread(new Husband(acc));
Thread t2 = new Thread(new Wife(acc));
t1.start();
t2.start();
}
}
class Account{
String carNo;
String password;
double balance;
public Account(String carNo,String pwd,double balance) {
this.carNo = carNo;
this.password = pwd;
this.balance = balance;
}
public void Withdrawal(String carNo,String pwd,double money) {
if(this.carNo.equals(carNo) && this.password.equals(pwd)) {
System.out.println("验证成功请稍等");
if(money < balance) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
balance -= money;
System.out.println("取款成功账户还剩:"+balance);
}else {
System.out.println("取款失败,余额不足");
}
}else {
System.out.println("验证失败,账号或密码错误!");
}
}
}
class Husband implements Runnable{
Account acc;
public Husband(Account acc) {
this.acc = acc;
}
@Override
public void run() {
acc.Withdrawal("040212", "123456", 1200D);
}
}
class Wife implements Runnable{
Account acc;
public Wife(Account acc) {
this.acc = acc;
}
@Override
public void run() {
acc.Withdrawal("040212", "123456", 1200D);
}
}
很有可能导致账户中的余额取完款后成为负数,这是我们不可以接受的
同步方式(1)
·同步代码块
synchronized(临界资源对象){//对临界资源对象加锁
//代码(原子操作)
}
注:
每个对象都有一个互斥锁标记,用来分配给线程的
只有拥有互斥锁标记的线程,才能进入该对象加锁同步代码块
线程退出同步代码块时,会释放相应的互斥锁标记
45.6.线程的状态
线程一共有六种状态
注:JDK5之后就绪、运行统称为Runnable
45.7.线程死锁
什么是线程死锁问题?
当线程A拥有对象B的锁标记,并等待A对象的锁标记,此时线程B拥有A对象的锁标记,并且等待B对象的锁标记,此时两个线程都不会释放掉自身所持有的锁标记,就产生了死锁的问题
此时需要两个线程进行通信才可以解决这个问题
45.8.线程通信
void wait()
这个方法必须要写在同步代码块中,调用obj.wait()时,此线程会释放自身持有的所有锁标记,同时此线程因obj处在无限期等待的状态中,进入等待队列
void notify()
void notifyAll()
必须在obj加锁的同步代码块中,从obj中wating中释放一个或所有线程,对自身没有任何影响
45.9.消费者与生产者
生产者消费者面向同一个容器根据这个容器状态产生通信
public class TestProducerAndConsumer {
public static void main(String[] args) {
Queue que = new Queue();
Producer1 p1 = new Producer1(que);
Consumer1 c1 = new Consumer1(que);
Producer2 c2 = new Producer2(que);
p1.start();
c1.start();
c2.start();
}
}
class Queue{
List list = new ArrayList();
int max = 4;
public synchronized void offer(Object obj) {
while(list.size() >= max) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
list.add(obj);
}
public synchronized Object pool() {
while(list.size() <= 0) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.notifyAll();
return list.remove(0);
}
}
//生产者1
class Producer1 extends Thread{
Queue q;
public Producer1() {}
public Producer1(Queue q) {
this.q = q;
}
@Override
public void run() {
for(char ch = 'A';ch<='E';ch++) {
q.offer(ch);
System.out.println("存入元素"+ch);
}
}
}
//生产者2
class Producer2 extends Thread{
Queue q;
public Producer2() {}
public Producer2(Queue q) {
this.q = q;
}
@Override
public void run() {
for(char ch = 'F';ch<='J';ch++) {
q.offer(ch);
System.out.println("存入元素"+ch);
}
}
}
//消费者1
class Consumer1 extends Thread{
Queue q;
public Consumer1() {}
public Consumer1(Queue q) {
this.q = q;
}
@Override
public void run() {
for(int i = 1;i<=10;i++) {
System.out.println("取出元素"+q.pool());
}
}
}
需要注意的一点是notifyAll来了也会释放同类型的线程,比如在生产者的线程里notifyAll的时候也会释放掉其他生产者
还有一点就是判断里写成while循环是因为该线程可能是同类型线程在wait那步苏醒,后续操作可能就会继续删除元素,导致线程不安全
45.10.线程池
45.10.1.线程池的概念
现有问题:
·线程是宝贵资源,单个线程约占1MB空间,过多分配容易造成内存溢出
·频繁的创建及销毁线程会增加虚拟的回收频率(做垃圾回收会导致用户线程暂停)、资源开销,造成性能下降
线程池:
·线程容器,可设定县城分配的数量上限
·将预先创建的线程对象存入池中,并重用线程池中的线程对象
·避免频繁的创建和销
45.10.2.线程池原理
我们在上图可以看到线程池中的线程被复用了,这样一个线程就可以完成很多任务了,线程也就不用被频繁的的创建和销毁了
45.10.3.Callable接口
public interface Callable{
public V call() throws Exception;
}
JDK5加入,与Runnable接口类似。实现之后代表一个线程任务
Callable具有泛型返回值,可以声明异常
45.10.4.Future接口
概念:异步接收ExecutorService.submit()所返回的状态结果,当中包含了call的返回值
方法:V get()以阻塞形式等待Future中的异步处理结果(call()方法的返回值)
异步变同步:执行到有返回值的线程后,会从异步的形式转变成同步的形式等待线程返回值的返回
45.10.5.获取线程池
常用线程池接口和类
·Executor:线程池的顶级接口
·ExcutorServicec:线程池接口,可通过submit(Runnable task)提交任务代码
·Executors工厂类:通过此类可获得一个线程池
·通过newFixedThreadPool(int nThreads)获取固定数量的线程池。参数:指定线程池中线程的数量
·通过newCacheThreadPool()获取动态数量的的线程池,如不够则创建新的,没有上限
public class TestThreadPool {
public static void main(String[] args) {
Task1 t1 = new Task1();
ExecutorService es = Executors.newFixedThreadPool(3);
java.util.concurrent.Future<Integer> f = es.submit(t1);
try {
Integer i1 = f.get();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Task1 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i = 1;i<=50;i++) {
sum += i;
}
return sum;
}
}
45.11.Lock接口
JDK5加入,与synchronized比较,显示定义,结构更灵活
提供更多实用性方法,功能更强大,性能更优越
·常用方法
void lock()//获取锁,如锁被占用,则等待
boolran tryLock()//尝试获取锁(成功返回true,失败false,不阻塞)
void unlock()//释放锁
45.11.1.ReentrantLock重入锁
ReentrantLock:Lock接口的实现类,与synchronized一样具有互斥锁的功能
ReentrantLock支持重入(支持递归调用)
需要开启锁,也需要释放锁,最好将释放锁写在finally代码块当中
public class TestLock{
public static void main(String[] args){
}
}
class MyList{
private lock locker = new ReentrantLock();
private String[] strs = {"A","B","",""};
private int count = 2;//元素个数
public void add(String value){
locker.lock();
try{
strs[count] = value;
try{
}catch(IntereuptedException e){
}
count++;
}finally{
locker.unlock();
}
}
}
45.11.2.ReentrantReadWriteLock读写锁
ReentrantReadWriteLock他在java.util.concurrent.locks.ReentrantReadWriteLock;包下、
获取读锁和写锁的方法:
通过ReentrantReadWriteLock对象获取
利用ReentrantReadWriteLock对象当中readLock()和writeLock()获取读锁和写锁
public class TestReentrantReadWriteLock {
ReentrantReadWriteLock rlk = new ReentrantReadWriteLock();
ReadLock rl = rlk.readLock();
WriteLock wl = rlk.writeLock();
public static void main(String[] args) {
}
}
注意:ReadLock和WriteLock是ReentrantReadWriteLock的内部类
一共有两把锁:一把读锁,一把写锁
互斥规则
读读之间不互斥
读写之间互斥(当多个线程同时访问同一份数据资源时,可能会导致数据一致性的问题,比如竞态条件(race condition),即两个线程对同一数据进行了未预期的交替访问。为了保证数据的一致性和完整性,防止破坏原有的状态,就需要对读写操作实施互斥控制。)
也就是说如果有多个读写线程的时候对数据进行访问时,如果此时读写不互斥,可能会导致多个线程读出来的数据不一致
写写之间互斥
package Lock;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.*;
public class TestReentrantReadWriteLock {
public static void main(String[] args) {
long time = System.currentTimeMillis();
Student stu = new Student();
Callable<Object> readTask = new Callable<Object>(){
@Override
public Object call() {
stu.getValue();
return null;
}
};
Callable<Object> writeTask = new Callable<Object>() {
@Override
public Object call() {
stu.setValue(11);
return null;
}
};
ExecutorService es = Executors.newFixedThreadPool(20);
for(int i = 0;i<2;i++) {
es.submit(writeTask);
}
for(int i = 0;i < 18;i++) {
es.submit(readTask);
}
es.shutdown();
while(!es.isTerminated()) {
}
System.out.println(System.currentTimeMillis() - time);
}
}
class Student{
ReentrantReadWriteLock rlk = new ReentrantReadWriteLock();
ReadLock rl = rlk.readLock();
WriteLock wl = rlk.writeLock();
private int value;
public void setValue(int value) {
try {
wl.lock();
this.value = value;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
wl.unlock();
}
}
public int getValue() {
try {
rl.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return value;
}finally {
rl.unlock();
}
}
}
读操作比写操作多的情况下可以使用读写锁可以大大提升效率
46.线程安全的集合
46.1.线程安全的List集合
加强版读写分离
CopyOnWriteArrayList:是一个List线程安全的集合使用方法和ArrayList无异
它的读写不互斥是因为他的特殊的设计,这样大大的提升了效率
下面是他的add方法的源代码
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
首先获取自己类内部的一个数组,获取这个数组的长度,再将这个数组的内容复制并扩容,把这个引用赋值给这个新数组,再把要添加的元素给添加到这个新数组当中,最后这个新数组把引用赋值给旧数组
读线程在写的时候,还没有写完的时候你就读了,读出来的数据不是具备时效性的
比如往集合当中连续写入三个值,当你读的时候写线程刚好写好第二个值,所以你只能读到第二个值上面
但是CopyOnWriteArrayList就可以也很好的避免这个问题,他是先将值存入到新数组当中,最后再进行替换引用的操作这样就可以避免这个问题了(要么读到的是最新的数据要么读到的是旧版本的数据,不会存在新数据读不完整的情况)
46.2.线程安全的Set集合
是对CopyOnWriteArrayList的复用。在CopyOnWriteArraySet内部有一个CopyOnWriteArrayList对象的实例,在对线程安全的Set添加元素时,会调用线程安全的List集合当中的添加方法,拿出旧数组的值跟要存入的值进行比较,如果相同,则返回false则表示存入失败则丢弃这个元素,如果不相同则旧数组当中的一个元素放入到新数组当中。
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
同样也是完成了读写不互斥的操作
46.3.线程安全的Map集合
初始容量默认为16段(Segment),使用分段锁设计
不对整个Map加锁,而是为每个Segment加锁
当多个对象存入同一个Segment时,才需要互斥
最理想状态为16个对象分别存入16个Segment,并行数量16
使用方式与HashMap无异
46.4.Queue接口
Collection的子接口,表示队列FIFO(First In First Out)
常用方法
·抛出异常
boolean offer(E e)//顺序添加一个元素(到达上限后,再添加则会抛出异常)
E remove()//获取第一个元素并移除(如果队列没有元素时,则抛出异常)
E element()//获得第一个元素但不移除(如果队列没有元素时,则抛出异常)
·返回特殊值:推荐使用
boolean offer(E e)//顺序添加一个元素(到达上限后,在添加则会返回false)
E poll()//获得第一个元素并移除(如果队列没有元素时,则返回null)
E peek()//获得第一个元素但不移除(如果队列没有元素时,则返回null)
46.5.线程安全的Queue集合
46.5.1.ConcurrentLinkedQueue
·线程安全,可高效读写的队列,高并发下最好的队列
·无锁、CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)
·V:要更新的变量、E:预期值 N:新值
只有当V==E时,V=N;否则表示已被更新过,放弃当前操作
public class TestConcurrentLinkedQueue{
public static void main(String[] args){
Queue<String> queue = new ConcurrentLinkedQueue<String>();
queue.offer("Hello");//插入
queue.offer("World");//插入
queue.poll();//删除Hello
queue.peek();//获得World
}
}
46.5.2.阻塞队列
·Queue的子接口,阻塞队列,增加了两个线程状态为无限期等待的方法
方法
·void put(E e)//将指定元素查如此队列中,如果没有可用空间,则等待
·E take()//获取并移除队列头部元素,如果没有可用元素,则等待
可用于解决生产、消费者问题
·ArrayBlockingQueue:
数组结构实现,有界队列(手工固定上限)
public class TestArrayBlockingQueue{
public static void main(String[] args){
BlockingQueue<String> abq = new ArrayBlockingQueue<String>(10);
}
}
·LinkedBlockingQueue
链表结构实现,无界队列(Integer.MAX_VALUE)
public class TestLinkedBlockingQueue{
public static void main(String[] args){
BlockingQueue lbq = new LinkedBlockingQueue<String>();
}
}
47.IO/流
·概念:内存与存储设备之间传输数据的通道
再次过程中,以家庭为参照物
生活用水------>家庭中:流入
家庭废水------>下水道:流出
水解主管道传输;数据借助流传输
·按方向【重点】
·输入流:将<存储设备>中的内容读入到<内存>中
·输出流:将<内存>中的内容写入到<存储设备>中
·按单位
·字节流 :以字节为单位,可以读写所有数据。
·字符流:以字符为单位,只能读写文本数据。
·按功能
·节点流:具有实际传输数据的读写功能
·过滤流:在节点流的基础之上增加功能
字节流
·字节流的父类(抽象类):
|------------>public int read(){}
InputStream:字节输入流---------------->public int read(byte[] b){}
|------------>pubilc int read(byte[] b,int off,int len){}
|------------->pubic void write(int n){}
OutputStream:字节输出流----------------->public void write(byte[] b)
|-------------->public void write(byte[] b,int off,int len)
47.1.字节节点流
47.1.1.·FileOutputStream(实现类):
public void write(byte[] b)//一次写多个字节,将b数组中所有字节,写入输出流,后面参数还可以追加一个boolean类型的值,这个值代表是否追加,如果是true就是追加,不进行覆盖
·FileInputStream(实现类):
public int read(byte[] b)//从流中读取多个字节,将读到的内容存入b数组,返回实际读到的字节数;如果达到文件的的尾部,则返回-1。一次性读取b数组长度的数据,如果读满了但是还有数据的的话就需要继续读取,此时就需要循环读取了
public class TestOutputStream{
public static void main(String[] args){
//往target文件里面写入,内容进行追加
OutputStream fos = new FileOutputStream("target.txt",true);
//向文件里面写入数据
fos.write(65);
fos.write(66);
//创建一个输入流
//文件路径最好写成相对路径
InputStream fis = new FileInputStream("target.txt");
//创建一个数组,把数据读入到数组当中
byte[] bs = new byte[2];
//死循环,等到读取到-1没有数据的时候在进行退出
while(true) {
//把数据读取到数组当中
int count = fis.read(bs);
//判断如果数据读取没有了就退出循环
if(count == -1) {
break;
}
//循环打印数组中的东西
for(int i = 0;i<count;i++) {
System.out.println(bs[i]);
}
}
}
}
47.2.字节过滤流
47.2.1.缓冲流:BufferedOutputStream/BufferedInputStream
提高IO效率,减少访问磁盘的次数
数据存储在缓冲区中,flush是将缓存取得的内容写入文件中,也可以直接close。
因为它是过滤流所以不能直接传输数据,需要节点流的辅助
在构造缓冲流的时候需要使用节点流作为缓冲流参数对象的构造
其中close方法会级联的调用flush方法
public class TestBufferedStream {
public static void main(String[] args) throws IOException{
FileOutputStream fos = new FileOutputStream("File\\target.txt",true);
BufferedOutputStream bos = new BufferedOutputStream(fos);
bos.write(65);
bos.write(66);
bos.write(67);
bos.flush();
bos.write(68);
bos.write(69);
bos.write(70);
bos.close();
}
}
47.2.2.对象流:ObjectOutputStream/ObjectInputStream
增强了缓冲区功能
增强了读写8中基本数据类型和字符串功能
|----------------->readObject()从流中读取一个对象
增强了读写对象的的功能----
|----------------->writeObject(Object obj)像流中写入一个对象
使用流传输对象的过程称为序列化、反序列化。
向外写出的对象都必须实现Serializable接口
不想让属性序列化的话就是用transinet关键字修饰属性
public class TestObjectStream{
public static void main(String[] args) throws IOException, ClassNotFoundException{
FileOutputStream fos = new FileOutputStream("obj.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Student stu = new Student("TOM",18,'男');
Student stu2 = new Student("BOB",20,'女');
oos.writeObject(stu);
oos.writeObject(stu2);
FileInputStream fis = new FileInputStream("obj.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
while(true) {
try {
Student obj = (Student)ois.readObject();
System.out.println(obj.name+" "+obj.age+" "+obj.sex);
}catch(EOFException e) {
break;
}
}
}
}
class Student implements Serializable{
String name;
Integer age;
Character sex;
public Student(String name,Integer age,Character sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
}
注意:再进行循环读取对象的时候,如果读完了则会抛出一个EOFException的异常,此时我们可以选择捕获异常,并且处理异常,而处理的方式是则是跳出循环
47.3.字符编码
ISO-8859-1:收录出ASCII外,还包括西欧、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号
UTF-8:针对Unicode的可变长度字符编码
GB2312:简体中文
GBK:简体中文、扩充
BIG5:台湾,繁体中文
public class TestString {
public static void main(String[] args) throws UnsupportedEncodingException {
String str = new String("你好世界");
//对字符串进行编码,按照GBK的形式
byte[] bs = str.getBytes("GBK");
//对字符串进行解码,按照big5的形式
String str2 = new String(bs,"BIG5");
//打印输出后发现乱码,是因为编码和解码的格式不同
System.out.println(str2);
//重新对字符串进行编码
byte[] bs2 = str2.getBytes("BIG5");
//再按照原先的编码格式进行解码
String str3 = new String(bs2,"GBK");
//这样打印出来就是正确的文字
System.out.println(str3);
}
}
47.4.字符流
字符流的父类(抽象类):
|-------------->public int read(){}
Reader:字符输入流------------------->publilc int read(char[] c)
|-------------->public int read(char[] b,int off,int len)
|--------------->public void write(int n)
Writer:字符输出流---------------------->public void write(String str)
|--------------->public void writer(char[] c)
47.4.1.字符节点流
FileWriter:
public void write(String str)//一次写多个字符,将数组中的所有字符,写入输出流中
FileReader:
public int read(char[] c)//从流中读取多个字符,将读到内容存入c数组,返回实际读到的字符数;如果达到文件的尾部,则返回-1
public class TestFileWriterReader {
public static void main(String[] args) throws IOException {
Writer w = new FileWriter("TestWriter.txt");
w.write("你好呀");
w.flush();
char[] cs = new char[2];
Reader r = new FileReader("TestWriter.txt");
int i = r.read(cs);
System.out.println(i);
System.out.println(cs[0]+" "+cs[1]);
}
}
47.4.2.字符过滤流
缓冲流:BufferedWriter/BufferedReadedr
支持输入换行符
可一次写一行、读一行。
PrintWriter:
封装了print()/println()方法,支持写入后换行
public class TestBufferedWriter {
public static void main(String[] args) throws IOException {
Writer w = new FileWriter("TestBuffered.txt");
//BufferedWriter虽然可以换行,但是需要单独写一个newLine方法,所以可以使用PrintWriter和BufferedReadder配合进行使用
BufferedWriter bw = new BufferedWriter(w);
bw.write("你");
bw.newLine();
bw.write("好");
bw.flush();
Reader r = new FileReader("TestBuffered.txt");
BufferedReader br = new BufferedReader(r);
String s = br.readLine();
System.out.println(s);
String s2 = br.readLine();
System.out.println(s2);
}
}
47.4.3.字符过滤流
桥转换流:InputStreamReadedr/OutputStreamWriter
·可将字节流转换为字符流
·可设置字符的的编码方式
public class TestInputStreamReader {
public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
OutputStream os = new FileOutputStream("bb.txt");
OutputStreamWriter osw = new OutputStreamWriter(os);
InputStream is = new FileInputStream("bb.txt");
InputStreamReader isr = new InputStreamReader(is,"GBK");
}
}
47.5.使用try块处理IO异常
public class TestIOTry {
public static void main(String[] args) {
FileOutputStream fos = null;
BufferedOutputStream bos = null;
try {
//关闭流的时候可以直接关闭过滤流,因为关闭过滤流的时候他会级联的关闭节点流
fos = new FileOutputStream("obj.txt");
bos = new BufferedOutputStream(fos);
}catch(IOException e) {
e.printStackTrace();
}finally {
try {
if(bos != null) {
bos.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
48.File类
文件类,代表一个文件,代表一个文件夹。
案例:判断一个磁盘中的.class文件案例
package IO;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
public class TestFile {
public static void main(String[] args){
showALL(new File("D:\\"));
}
public static void showALL(File dir) {
File[] files = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
if(file.isDirectory()) {
return true;
}
if(file.isFile()) {
if(file.getName().endsWith(".class")) {
return true;
}
}
return false;
}
});
if(files != null) {
for(File file : files) {
if(file.isFile()) {
System.out.println(file.getName());
}else {
showALL(file);
}
}
}
}
}
File类的常用方法
creatNewFile()//创建一个新文件
Mkdir()//创建一个新目录
Delete()//删除文件或空目录
Exists()//判断File对象所代表的对象是否存在
getAbsolutePath()//获取文件的的绝对路径
getName()//取得名字
getParent()//获取文件/目录所在的的目录
isDirectory()//是否是目录
isFile()//是否是文件
length()//获得的文件的的长度
listFiles()//列出木链路中所有的内容
renameTo()//修改文件名为
49.反射
类的的对象:基于某个类new出来的对象,也成为实例对象
类对象:类加载的产物,封装了一个类的所有信息(类名、父类、接口、属性、方法、构造方法)
·通过类的对象,获取类对象
·Student s = new Student();
·Class c = s.getClass();
·通过类名获取对象(静态属性)
·Class c = 类名.class;
·通过静态方法获取类对象
·Class c = Class.forName(“包名.类名”);
一个Class对象代表了一个.class文件
常用方法
·public String getName()
·public Package getPackage()
·public Class<? super T> getSuperclass()
·public Class<?>[] getInterfaces()
·public Field[] getFields()
·public Method[] getMethods()
·public Constructor<?>[] getConstructors()
·public T newInstance()
使用反射完成通用编程
·工厂创建类
package Class;
public class TestClass {
public static void main(String[] args) {
Object obj = getObj("Class.Student");
System.out.println(obj.getClass());
}
public static Object getObj(String className) {
Object obj = null;
try {
Class objclass = Class.forName(className);
obj = objclass.newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
return obj;
}
}
}
class Student{
}
·使用反射调用方法
package Class;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class InvokeMethod {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Properties map = new Properties();
invokeMethod(map,"setProperty",new Class[] {String.class,String.class},"中国","CH");
System.out.println(map.getProperty("中国"));
}
public static void invokeMethod(Object obj,String methodName,Class[] cls,Object... objs) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Class objcls = obj.getClass();
Method m = objcls.getDeclaredMethod(methodName, cls);
m.setAccessible(true);
m.invoke(obj, objs);
}
}
50.单例模式
饿汉式(天生没有线程安全问题,但是占用空间)
class Person{
//饿汉式
private Person() {}
private static final Person p = new Person();
public static Person getPerson() {
return p;
}
}
懒汉式(有线程安全问题,用的时候才创建)
//懒汉式
class Singleton{
private Singleton() {}
private static Singleton s = null;
public static synchronized Singleton getSingleton() {
if(s == null) {
s = new Singleton();
}
return s;
}
}
懒汉式(线程安全)
class Singleton{
private Singleton() {}
private static class innerClass{
static final Singleton s = new Singleton();
}
public static Singleton getSingleton() {
return innerClass.s;
}
}
t n)
Writer:字符输出流---------------------->public void write(String str)
|--------------->public void writer(char[] c)
47.4.1.字符节点流
FileWriter:
public void write(String str)//一次写多个字符,将数组中的所有字符,写入输出流中
FileReader:
public int read(char[] c)//从流中读取多个字符,将读到内容存入c数组,返回实际读到的字符数;如果达到文件的尾部,则返回-1
public class TestFileWriterReader {
public static void main(String[] args) throws IOException {
Writer w = new FileWriter("TestWriter.txt");
w.write("你好呀");
w.flush();
char[] cs = new char[2];
Reader r = new FileReader("TestWriter.txt");
int i = r.read(cs);
System.out.println(i);
System.out.println(cs[0]+" "+cs[1]);
}
}
47.4.2.字符过滤流
缓冲流:BufferedWriter/BufferedReadedr
支持输入换行符
可一次写一行、读一行。
PrintWriter:
封装了print()/println()方法,支持写入后换行
public class TestBufferedWriter {
public static void main(String[] args) throws IOException {
Writer w = new FileWriter("TestBuffered.txt");
//BufferedWriter虽然可以换行,但是需要单独写一个newLine方法,所以可以使用PrintWriter和BufferedReadder配合进行使用
BufferedWriter bw = new BufferedWriter(w);
bw.write("你");
bw.newLine();
bw.write("好");
bw.flush();
Reader r = new FileReader("TestBuffered.txt");
BufferedReader br = new BufferedReader(r);
String s = br.readLine();
System.out.println(s);
String s2 = br.readLine();
System.out.println(s2);
}
}
47.4.3.字符过滤流
桥转换流:InputStreamReadedr/OutputStreamWriter
·可将字节流转换为字符流
·可设置字符的的编码方式
public class TestInputStreamReader {
public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
OutputStream os = new FileOutputStream("bb.txt");
OutputStreamWriter osw = new OutputStreamWriter(os);
InputStream is = new FileInputStream("bb.txt");
InputStreamReader isr = new InputStreamReader(is,"GBK");
}
}
47.5.使用try块处理IO异常
public class TestIOTry {
public static void main(String[] args) {
FileOutputStream fos = null;
BufferedOutputStream bos = null;
try {
//关闭流的时候可以直接关闭过滤流,因为关闭过滤流的时候他会级联的关闭节点流
fos = new FileOutputStream("obj.txt");
bos = new BufferedOutputStream(fos);
}catch(IOException e) {
e.printStackTrace();
}finally {
try {
if(bos != null) {
bos.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
48.File类
文件类,代表一个文件,代表一个文件夹。
案例:判断一个磁盘中的.class文件案例
package IO;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
public class TestFile {
public static void main(String[] args){
showALL(new File("D:\\"));
}
public static void showALL(File dir) {
File[] files = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
if(file.isDirectory()) {
return true;
}
if(file.isFile()) {
if(file.getName().endsWith(".class")) {
return true;
}
}
return false;
}
});
if(files != null) {
for(File file : files) {
if(file.isFile()) {
System.out.println(file.getName());
}else {
showALL(file);
}
}
}
}
}
File类的常用方法
creatNewFile()//创建一个新文件
Mkdir()//创建一个新目录
Delete()//删除文件或空目录
Exists()//判断File对象所代表的对象是否存在
getAbsolutePath()//获取文件的的绝对路径
getName()//取得名字
getParent()//获取文件/目录所在的的目录
isDirectory()//是否是目录
isFile()//是否是文件
length()//获得的文件的的长度
listFiles()//列出木链路中所有的内容
renameTo()//修改文件名为
49.反射
类的的对象:基于某个类new出来的对象,也成为实例对象
类对象:类加载的产物,封装了一个类的所有信息(类名、父类、接口、属性、方法、构造方法)
·通过类的对象,获取类对象
·Student s = new Student();
·Class c = s.getClass();
·通过类名获取对象(静态属性)
·Class c = 类名.class;
·通过静态方法获取类对象
·Class c = Class.forName(“包名.类名”);
一个Class对象代表了一个.class文件
常用方法
·public String getName()
·public Package getPackage()
·public Class<? super T> getSuperclass()
·public Class<?>[] getInterfaces()
·public Field[] getFields()
·public Method[] getMethods()
·public Constructor<?>[] getConstructors()
·public T newInstance()
使用反射完成通用编程
·工厂创建类
package Class;
public class TestClass {
public static void main(String[] args) {
Object obj = getObj("Class.Student");
System.out.println(obj.getClass());
}
public static Object getObj(String className) {
Object obj = null;
try {
Class objclass = Class.forName(className);
obj = objclass.newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
return obj;
}
}
}
class Student{
}
·使用反射调用方法
package Class;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class InvokeMethod {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Properties map = new Properties();
invokeMethod(map,"setProperty",new Class[] {String.class,String.class},"中国","CH");
System.out.println(map.getProperty("中国"));
}
public static void invokeMethod(Object obj,String methodName,Class[] cls,Object... objs) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Class objcls = obj.getClass();
Method m = objcls.getDeclaredMethod(methodName, cls);
m.setAccessible(true);
m.invoke(obj, objs);
}
}
50.单例模式
饿汉式(天生没有线程安全问题,但是占用空间)
class Person{
//饿汉式
private Person() {}
private static final Person p = new Person();
public static Person getPerson() {
return p;
}
}
懒汉式(有线程安全问题,用的时候才创建)
//懒汉式
class Singleton{
private Singleton() {}
private static Singleton s = null;
public static synchronized Singleton getSingleton() {
if(s == null) {
s = new Singleton();
}
return s;
}
}
懒汉式(线程安全)
class Singleton{
private Singleton() {}
private static class innerClass{
static final Singleton s = new Singleton();
}
public static Singleton getSingleton() {
return innerClass.s;
}
}