LINUX------------------
一、操作系统
1. 三大组成部分
- kernel内核
管理硬件设备,内存,文件系统 - shell
- 用户与kernel之间的接口,相当于一个命令解释器
- shell有多种产品,Linux默认shell为bash,Mac默认为zsh
- File System文件系统
- Linux文件系统目录为树形结构,目录是固定好的,存放不同的内容
- 系统常见目录及作用
二、文件系统
-
文件的后缀名只是为了方便系统去识别文件改用什么应用程序去打开
-
Windows系统中的路径分隔符,使用/或者\都可以,但是\需要转义一下
-
Linux下用/
-
window : 文件后缀名 .exe 表示程序 运行 操作
-
window : 盘符区分储存位置
-
linux : 文件后缀名 .bashrc.bak 表示文件备份,不以后缀名区分运行方式
-
linux : 固定好的目录,放置不同内容
1. 路径
常用命令
/
表示根目录~
表示家目录- 回到家目录
cd
- 回到根目录
cd /
- 显示当前目录
pwd
- 清屏
clear
- 帮助手册
man ls
绝对路径
- 以系统中根目录开头的路径,都是绝对路径
例如:/bin、/home/briup、/opt等
相对路径
- 在当前路径下,去描述另一个路径,表示两个路径的相对位置。
例如,当前路径在/home/briup下,查看另一个/home/test目录中的hello.txt文件
cat ../test/hello.txt
2. 用户管理
-
$
普通用户 -
#
root用户 -
sudo
临时提升权限成为管理员 -
创建一个新的用户swift,用户登录后使用bash
sudo useradd -mk /home/swift -s/bin/bash swift
-
给swift用户设置密码
sudo passwd swift
-
将用户添加到sudo组
sudo gpasswd -a swift sudo
-
切换到swift用户
su - swift
-
查看所属组
id
-
从sudo组中删除swift用户(先切换到briup用户)
su - briup
sudo gpasswd -d swift sudo
-
删除swift用户及对应的家目录
sudo userdel -r -f swift
-
普通用户修改密码
passwd
-
root用户修改密码
sudo passwd root
-
切换到root用户
su
-
退出root用户
exit
3. 权限管理
drwxrwxr-x 2 briup briup 4096 7月 27 11:51 a
-rw-rw-r-- 1 briup briup 0 7月 27 12:25 a.txt
-
首位表示类型
d
表示目录-
表示文件
-
后面表示权限
三位一组,按次序为拥有者,同组人,其他人- u:拥有者
- g:同组人
- o:其他人
- a:所有者
- r:读
- w:写
- x:执行
- -:没有权限
--- 000 0--x 001 1-w- 010 2-wx 011 3r-- 100 4r-x 101 5rw- 110 6rwx 111 7
-
掩码
- 查看掩码值
umask
- 修改掩码值
umask 022
- 查看掩码值
-
默认权限
- 新建目录权限 775
777去除002权限
去除非相减 - 新建文件权限 664
666去除003权限
去除非相减
- 新建目录权限 775
-
更改权限
- 方式一
chmod u+x b.txt
chmod g-r b.txt
- 方式二
chmod 777 b.txt
- 方式一
4. 文件操作
- 查看目录和文件列表(ls)
ls -a
显示隐藏文件和目录ls -R
递归显示文件子目录ls -l
显示详细信息ls -F
显示文件类型 目录/ 可执行* 普通文件ls -al
参数放在一起写
- 查看文件内容(cat)
一次全部显示呢 - 查看文件内容(more)
分页显示- b
向上翻页 - f
向下翻页 - q
退出当前内容查看页面 - v
进入只读vi模式
- b
- 创建文件(touch)
touch a.txt
- 创建目录(mkdir)
mkdir test test1 test2
创建多个平级目录mkdir - p test/test1/test2
创建多级子目录
- 拷贝(cp)
cp a.txt b.txt
cp a.txt b.txt test
cp -r test test2
拷贝文件及其内容到test2cp -r test test4/temp
拷贝到test4并重命名
- 移动(mv)
mv a.txt test
mv a.txt b.txt
修改文件的名字mv dir dir2
修改目录的名字mv dir dir2
将目录移动到dir2(dir2存在时)
- 删除(rm)
rm a.txt
rm a b c
rmdir test
删除空目录rm -r test
删除非空目录rm -rf test
/rm -r -f test
强制删除非空目录
5. 特殊符号
|
管道符,把第一个命令的结果,交给第二个命令作为参数进行操作
ls -R | more
*
表示通配,可以代表0~n个字符
ls *.txt
?
表示任意的一个字符
ls a?.txt
;
表示在一行语句中连续执行多个命令,命令之间使用分号进行分割
cd;ls
>
输出重定向,是覆盖操作
echo "hello" > a
>>
输出重定向,是追加操作
date >> a
grep
grep命令进行对结果进行过滤筛选
cat /etc/passwd | grep "briup"
6. 环境变量
临时环境变量
- 设置临时环境变量
name=tom
- 使用$获取临时变量的值
echo $name
用户级环境变量
vi .bashrc
添加两条语句name=tom
export name
source .bashrc
配置生效
特殊环境变量
PATH
7. 归档
-
把若干个文件或目录简单的合并在一起,不对文件进行压缩,就是将多个文件前后连接在一起,形成一个大文件。
-
另外还会额外的添加一些信息,所以归档后的总文件的大小,反而往往比之前文件之和还要大一些。
-
把指定文件和目录进行归档
tar -cvf work.tar a b c test1 test2
-
解除归档文件到指定目录中
tar -xvf work.tar -c dir1
8. 压缩
- 压缩,是一种通过特定的算法来减小计算机文件大小的机制,Linux中的文件压缩命令有多种,其中最常用的命令就是gzip。
- 将归档好的文件进行压缩
gzip work.tar
- 在tar进行归档之后直接进行压缩:
tar -zcvf work.tar.gza b c test1 test2
9. 解压
- 对gz文件进行解压,解压到当前目录
gunzip work.tar.gz
- 对tar.gz文件进行解压并解除归档
tar -zxvf work.tar.gz -C dir2
三、网络传输
telnet
远程登录服务器ifconfig
查看ip地址ps -ef | grep bash
查看用户进程信息
1. SSH协议
- XShell客户端
- linux 安装 ssh 服务器
sudo apt-get install openssh-server
2. FTP协议
- 文件传输协议
四、虚拟机
1. 网络连接模式
NAT模式
2. 快照
即系统备份
JAVA-------------------
一、JVM
- JVM是一种规范,可以使用软件来实现,也可以使用硬件来实现,就是一个虚拟的用于执行bytecodes字节码的计算机。他也定义了指令集、寄存器集、结构栈、垃圾收集堆、内存区域。
1. 内存管理结构
栈
-
线程私有,不能实现线程间共享
-
系统分配,连续内存空间,速度快
-
本地方法栈
- 是JVM自身运行所需要的,C++代码
-
虚拟机栈
- 方法调用,局部变量,局部变量放在所属方法对应的栈中
堆
- 存放new出来的结构,包括对象,数组
- 不连续内存空间,分配灵活,速度慢
方法区(Method Area)
- 所有线程所共享
- 包括类的加载信息,字符串常量池,静态域(静态常量)。
2. 类的加载过程
- 加载:
- 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象.
- 链接:
- 将Java类的二进制代码合并到JVM的运行状态之中的过程。
- 验证:确保加载的类信息符合JVM规范,没有安全方面的问题准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
- 初始化:
- 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
什么时候会发生类初始化?
- 类的主动引用(一定会发生类的初始化)
- 当虚拟机启动,先初始化main方法所在的类
- new一个类的对象调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
- 类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
- 通过数组定义类引用,不会触发此类的初始化
- 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
3. 垃圾回收器
- java代码中,开辟要使用的内存空间,使用new关键字即可完成。
- 使用完之后,对内存的释放,在JVM中,由垃圾回收器(Garbage Collection,GC)来完成
- 不同类型的GC,在JVM中,会根据不同的算法,对不同的内存区域内标记为垃圾的空间,进行回收释放。在这个过程中,是不需要编程人员干预的,它自己会主动的完成
- 在代码中,我们也可以调用JavaSE-API提供的方法,通知GC现在去进行垃圾回收的工作:
java.lang.System.gc();
java.lang.Runtime.gc();
- 虽然可以主动通知,但是最后GC并不一定会真的的立刻执行,因为这个垃圾回收的过程什么时候执行,最终还是要根据GC的具体算法和当前内存的使用情况来确定的
字节码验证
- 编写好的java代码,编译成class文件(字节码)后,再被JVM加载到内存中的时候,是需要做字节码验证的
- 一个class文件被加载到JVM内存之后,首先要经过字节码验证,主要包含:
- 检查当前class文件的版本和JVM的版本是否兼容
- 检查当前代码是会破坏系统的完整性
- 检查当前代码是否有栈溢出的情况
- 检查当前代码中的参数类型是否正确
- 检查当前代码中的类型转换操作是否正确
- 验证通过过,再确定哪些代码是解释执行的,哪些代码是JIT即时编译执行的
- 解释执行
class文件内容,需要让JVM进行解释,解释成计算机可以执行的代码。整体效果就是JVM解释一行代码就执行一行代码。所以如果java代码全是这样的运行方式的话,效率会稍低一些。 - JIT(Just In Time)即时编译
JVM可以把java中的热点代码直接编译成计算机可以运行的代码,这样接下来再调用这个热点代码的时候,就可以直接使用编译好的代码让计算机直接运行,可以提高运行效率。
- 解释执行
二、编译和运行
- 编写代码–》.java --压缩-- 》src.zip --编译–》.class --归档–》rt.jar
- java代码编译后,需要经过JVM进行解释后,再进行运行。
1. 常用的命令
javac
编译命令java
运行命令javadoc
生成API文档命令javap
反解析命令,可以解析出class字节码文件的内容jar
打包命令
2. package
- java.lang 下所有类 默认导入 不需要使用关键字import
- 命名规则 : 使用公司/社区/组织/的域名反写
src.zip
java源代码压缩包rt.jar
源代码编译后文件的归档-d
在指定位置自动创建和包名对应的目录结构-cp
表示执行当前java命令的时候,临时指定classpath
mkdir java_demomkdir -p src/com/briup/day01 jar binvi src/com/briup/day01/Hello.java#指定bin路径作为字节码文件存放路径javac -d bin src/com/briup/day01/Hello.java #指定bin路径下搜索字节码文件java -cp bin com.briup.day01.Hi#到bin目录中查找Hello.class字节码文件,将编译好的Hi.class存放到 bin目录下 javac -d bin -cp bin src/com/briup/day02/Hi.java
常用包
- java.lang
最常用的一个包,里面的类可以在我们的程序中直接使用,不需要import导入 - java.awt、javax.swing、java.awt.even
图形化界面 - java.io
输入输出流 - java.net
网络编程。例如把计算机A和计算机B之间建立网络连接,然后进行信息传输 - java.util
常用工具类 - java.text
包含了一些java格式化相关的类 - java.sql
包含了java进行JDBC数据库编程的相关类/接口
问题一
一个指定package的类,编译后该如何运行?
packagecom.briup.test
javac Hello.java
java Hello
报错,这个错误的原因是,Hello这个类是定义在指定的包中的,那么就需要在把生成的class文件存放到和包名相对于的文件夹中java Hello
报错,这是因为,一个类一旦指定的包,那么在运行它的时候,就一定要带上它的包名java com.briup.test.Hello
3.jar(归档)
- 了解就好
- 将class文件归档
jar cvf jar/hello.jar bin/com/briup/day01/Hello.class
- 无bin目录的归档方式
jar cvf jar/hello.jar -C bin .
- 依赖一个jar包的字节码文件运行
java -cp bin:jar/hello.jar com.briup.day01.Hello
- 依赖jar包运行
java -jar Hello.jar
4. 类加载器
- jvm中通过类加载器实现将字节码文件加载到内存。
- JavaSE-API中,有这么一个类: java.lang.ClassLoader ,它就表示JVM中的类加载器。
分类
- 启动类加载器 bootstrapClassLoader,非java语言实现
- 路径:jre/lib/rt.jar
- 扩展类加载器 ExtClassLoader,java语言实现,是ClassLoader类型的对象
- 路径:jre/lib/ext/*.jar
- 应用类加载器 AppClassLoader,java语言实现,是ClassLoader类型的对象
- 路径:CLASSPATH,用户自定义路径
我们最常使用的就是应用类加载器,因为它可以通过CLASSPATH中的路径,去加载程序员自己编写并编译的class文件到内存中。
大多数情况下,即使我们需要用到其他jar中的代码,也一般会把jar所在的路径配置到CLASSPATH中,让应用类加载器进行加载,这样会更加方便统一管理项目中使用的所有jar
加载顺序
java -verbose : 输出类加载详细信息
问题一
- 用户是否可以通过自定义的伪系统类来替代javaAPI中的类
不可以。jvm自己可以通过安全的方式去验证要加载的类是否是自定义的,从而保证类加载的安全性
5. 双亲委托机制
向上请求,向下加载
例如:java com.briup.test.Hello 命令
- 现在要加载Hello.class文件中的类
- 首先加载任务就交给了AppClassLoader
- 然后AppClassLoader把这个任务委托给自己的父加载器,也就是ExtClassLoader
- 然后ExtClassLoader把这个任务委托给自己的父加载器,也就是bootstrapClassLoader
- 然后bootstrapClassLoader就尝试去加载Hello这个类,但是在指定路径下并没有找到
- 然后任务又交回给了ExtClassLoader,ExtClassLoader尝试加载Hello这个类,但是在指定路径中没找到
- 然后任务又交回给了AppClassLoader,尝试加载这个类
- 最后AppClassLoader从CLASSPATH中指定的路径里面找到并加载了这个Hello类,完成类加载的过程
三、基本数据类型
1. 机器码
机器数
- 一个数在计算机中的二进制表示形式, 叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0, 负数为1.
+3 -- 00000011
-3 -- 10000011
真值
- 带符号位的机器数对应的真正数值称为机器数的真值。
0000 0001的真值 = +000 0001 = +1
1000 0001的真值 = –000 0001 = –1
原码
- 原码就是符号位加上真值的绝对值
- 8位二进制数的取值范围就是:
[1111 1111 , 0111 1111] 即 [-127 , 127]
- 原码是人脑最容易理解和计算的表示方式
反码
-
正数的反码是其本身
-
负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.
[+1] = [00000001]原 = [00000001]反
[-1] = [10000001]原 = [11111110]反
-
如果一个反码表示的是负数, 人脑无法直观的看出来它的数值
补码
- 正数的补码就是其本身
- 负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1
[+1] = [00000001]原 = [00000001]反 = [00000001]补
[-1] = [10000001]原 = [11111110]反 = [11111111]补
- 对于负数, 补码表示方式也是人脑无法直观看出其数值的
转换
负数原码—>按位取反加1变为补码
负数补码->按位取反加1变回原码
为何使用
- 既然原码才是被人脑直接识别并用于计算表示方式, 为何还会有反码和补码呢?
- 首先, 因为人脑可以知道第一位是符号位, 在计算的时候我们会根据符号位, 选择对真值区域的加减,但是对于计算机, 加减乘数已经是最基础的运算, 要设计的尽量简单. 计算机辨别"符号位"显然会让计算机的基础电路设计变得十分复杂!
- 于是人们想出了将符号位也参与运算的方法.
- 我们知道, 根据运算法则减去一个正数等于加上一个负数, 即: 1-1 = 1 + (-1) = 0 , 所以机器可以只有加法而没有减法, 这样计算机运算的设计就更简单了.
- 于是人们开始探索 将符号位参与运算, 并且只保留加法的方法
- 原码(带符号计算)
1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2
- 反码(带符号计算)
1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0
会有[0000 0000]原和[1000 0000]原两个编码表示0. - 补码(带符号计算)
1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原
- 使用补码, 不仅仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数. 这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127].
(-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补
强转
十进制1000强制转化为byte类型
1000 --> 0011 1110 1000
程序输入是数值
计算机采用补码运算,因为是正数,直接在数值前面加符号位0,然后只保留后边8位,默认第一位为符号位,原数值变为负数
Java中的进制问题
//我们在计算机中输入的输出的都是补码的形式,因为数在计算机中都是以补码的形式存储的System.out.println(0b10000000_00000000_00000000_00000001);//-2147483647System.out.println(0x80000001);//-2147483647System.out.println(Integer.toBinaryString(-2147483647));//10000000000000000000000000000001
2. 字节
计算机中,数据传输大多是以“位”(bit,比特)为单位,一位就代表一个0或1(二进制),每8个位(bit)组成一个字节(Byte)
3. boolean
布尔类型占1个字节(8位),它的的值,必须是true或者false,在JVM中会转换为1(true)或者0(false)
4. char
- char类型占2个字节(16位),用来表示字符,是基本数据类型,
- String表示字符串,是类类型。一个String是由0~n个char组成
char c2 = 0; // 10进制数表示 char c3 = '0'; //字符0 char c4 = '\u0000'; //字符 NUL 空字符char c = 0b111_101_001;
5. 整型
- byte 8位
1字节范围:负2的7次方~2的7次方减1 - short 16位
2字节范围:负2的15次方~2的15次方减1 - int 32位
4字节范围:负2的31次方~2的31次方减1 - long 64位
8字节范围:负2的63次方~2的63次方减1 - 整数类型的默认类型是
int
- long类型的字面值在后面加
L
- 1e4=10000
byte b1=97;//十进制byte b2=0141;//八进制byte b3=0x61;//十六进制byte b4=0b01100001;//二进制
6. 浮点型
- float 32位
1符号位+8指数位+23尾数位 - double 64位
1符号位+11指数位+52尾数位 - float和double的精度是由尾数的位数来决定的。浮点数在内存中是按科学计数法来存储的
- float的精度为7位左右有效数字double的精度为16位左右有效数字
- 浮点型的默认类型是double
浮点数的精度丢失问题
1.0 - 0.66
BigDecimal
float f=10.5f;//后面加f或者FDdoub led=10.5d//后面加d或者System.out.println(1.1d==1.1f);//false
7. 类型转换
特殊类型转换
byte + byte = intbyte + int = intshort + short = intshort + int = intbyte + short = intchar + char = intchar + int = int
byte a = 1 + 1;//小于byte即可byte a2 = a + 1;//报错byte a += 1;byte a3 = a + a2;//报错short b = 1 + 1;//小于short的范围即可short b2 = b + 1;//报错short += 1;short b3 = b + b2;、、报错
System.out.println('a'+1);// 98System.out.println('a'+'1');//146char z = 'a'+1;System.out.println(z);//b System.out.println((char)('a'+1));// bSystem.out.println("a"+"b"+1+2);//ab12System.out.println(1+2+"a"+"b");//3ab
- 三目运算法类型转换
char x='x';int i=1;System.out.println(true?98:'a');//bSystem.out.println(true?1.0:'a');//1.0System.out.println(true?i:x);//1System.out.println(true?97:x);//aSystem.out.println(true?i:'x');//1
System.out.println(false^false);//true
自动类型转换
-
byte/short/char --> int --> float -->double
-
小类型用大类型接收,自动转换(引用类型)
-
小类型与大类型进行运算(基本类型)
-
自动类型转换可能发生的精度丢失
int,long转换为float类型时float b = 123456789;System.out.println(b);//1.23456794E9
8. 运算符
位运算
- 位运算以补码的形式进行运算
&
与
有0为0|
或
有1为1^
异或
相同为0,不同为1~
取反
5 0000 0101(补码)3 0000 0011(补码)5&3 0000 0001(补码) = 13|5 0000 0111(补码) = 73^5 0000 0110(补码) = 6~5 1111 1010(补码)—>1000110(原码) = -6System.out.println(false^false);//true
移位运算符
>>
算数右移(带符号的右移)
右移一位,相当于除以2
正数用0填充,负数用1填充>>
算数左移
左移一位,相当于乘以2>>>
逻辑右移(不带符号的右移)
缺失的位值用0来填充
-5>> = -35>> = 2
instanceof
Student s = new Student();Student s2 = (Student) new Object();//运行错误:java.lang.ClassCastException : 类型转换异常运行异常 System.out.println(s instanceof Student);//trueSystem.out.println(s instanceof String);// 编译错误 :引用类型与 String类型没有任何关系System.out.println(s instanceof Object);//trueObject o2 = "hello";System.out.println(o2 instanceof Student);//falseSystem.out.println(o2 instanceof String);//true
- 应用场景
Object o = new Student();//自动转换//先使用instanceof 逻辑判断 //然后在进行强制类型转换,防止类型转换的错误if(o instanceof String) {//false String s4 = (String)o;}
9. 流程控制
lable标签
test1:for(inti=0;i<3;i++){//外层循环 test2:for(intj=0;j<5;j++){//内层循环 if(j==2){ break test1; } System.out.println("i="+i+",j="+j); } System.out.println("----------------------");}
四、引用类型
1. 类类型
OOP和POP
- 面向过程编程(OOP)
- demo(String name,String sex,int age){}
- 解决问题所涉及到的参数要全部作为形参传入
- 面向对象编程(POP)
- 将各种参数抽象成对象
- 函数式编程(FP)
- demo(test(){})
- 将函数作为参数
方法调用内存解析
- 一个对象调用一个方法
- 首先JVM运行一个class文件时,使用类加载器先将Demo类加载到方法区,然后main方法压栈(入栈)。
- 在栈中运行main方法,当jvm看到Phone时,会自动把Phone类加载到方法区;当看到局部变量p时,会在栈中开辟一块空间;当看到new Phone()时,会在堆内存中开辟空间,并将堆内存中的对应地址0x123赋值给p;还会拿到方法区的地址值指向方法区。
- 在main方法中运行到给对象p的属性赋值时,通过地址去堆内存中找到相应属性并赋值,运行p.sendMessage()这一步时,也是根据地址值去堆内存中找到相应的对象,再用对象去方法区中找到sendMessage()方法,然后将sendMessage()方法压到栈中(入栈),调用完毕sendMessage()方法会出栈。
- main方法运行结束后会出栈。
- 通过以上步骤描述,我们可以理解,在栈内存中运行的方法,遵循"先进后出,后进先出"的原则。变量p指向堆内存中的空间,寻找方法信息,去执行该方法
- 两个对象调用一个方法
对象调用方法时,根据对象中方法标记(地址值),去类中寻找方法信息。这样哪怕是多个对象,方法信息只保存一份,节约内存空间 - 引用作为参数传递到方法中
引用类型作为参数,传递的是地址值
方法参数的值传递机制
- Java里方法的参数传递方式只有一种:值传递。没有引用传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响
- 形参是基本数据类型:将实参基本数据类型变量的
数据值
传递给形参 - 形参是引用数据类型:将实参引用数据类型变量的
地址值
传递给形参.传递过去的引用之所以可以修改引用实参的成员属性,是因为两者指向的是同一个内存地址 - 引用传递会更改对象属性值,不同于c语言
对象储存地址
Student s = new Student();System.out.println(s);输出为com.briup.day03@d716361d716361表示对象储存地址的哈希值
关键字:this
- 调用本类构造器,必须放在构造器首行,
this(形参列表)
- 它在方法内部使用,即这个方法所属对象的引用;
- 它在构造器内部使用,表示该构造器正在初始化的对象。
关键字:super
- 调用父类构造器时,必须放在子类构造器首行
- 在Java类中使用super来调用父类中的指定操作:
- super可用于访问父类中定义的属性
- super可用于调用父类中定义的成员方法
- super可用于在子类构造器中调用父类的构造器
- 子类中所有的构造器默认都会访问父类中空参数的构造器
- 当父类中没有空参数的构造器时,子类的构造器必须通过this(参数列表)或者super(参数列表)语句指定调用本类或者父类中相应的构造器。同时,只能”二选一”
this和super区别
区别点 | this | super |
---|---|---|
访问属性 | 访问本类中的属性,如果本类没有此属性则从父类中继续查找 | 直接访问父类中的属性 |
调用方法 | 访问本类中的方法,如果本类没有此方法则从父类中继续查找 | 直接访问父类中的方法 |
调用构造器 | 调用本类构造器,必须放在构造器的首行 | 放在子类构造器的首行 |
子类对象的实例化过程
- 1.从结果上来看:(继承性)
- 子类继承父类以后,就获取了父类中声明的属性或方法。创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
- 2.从过程上来看:
- 当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。
- 默认情况下,子类构造器的第一行缺省为的父类无参构造器
- 虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
匿名对象
我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。
- 如:new Person().shout();
重载和重写
- 重载,编译器根据方法不同的参数表,对同名方法的名称做修饰,这些同名方法就成了不同的方法,它们的调用地址在编译期就绑定了。这称为“早绑定”或“静态绑定”;
- 多态,子类重写父类方法,只有等到方法调用的那一刻,解释运行器才会确定对象的类型,从而确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。
多态
package com.briup;public class Animal { public void eat() { System.out.println("animal eat"); }}
package com.briup;public class Cat extends Animal{ public void eat() { System.out.println("cat eat"); } public void run() { System.out.println("cat run"); }}
package com.briup;public class Demo { public static void main(String[] args) { Animal animal = new Cat(); animal.eat();//cat eat animal.run();//报错 //下面的例子不符合实际需要,只是作为了解 Cat cat = (Cat)animal; cat.eat(); cat.run();//cat run }}
为什么需要多态
- 提高了代码的通用性,常称作接口重用
- 方法的形参定义为父类类型,就可以接受全部子类的实参
多态的前置条件
- 需要存在继承或者实现关系
- 有方法的重写
多态的定义
- Java引用变量有两个类型:编译时类型和运行时类型。
- 编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定
- 简称:编译时,看左边;运行时,看右边。
- 若编译时类型和运行时类型不一致,就出现了对象的多态性
- 编译时:要查看引用变量所对应的类中是否有所调用的方法。
- 运行时:调用实际new的对象所属的类中的重写方法。
总结
- 不能访问子类中添加的属性和方法
- 父类的引用变量指向子类的对象
- 成员变量:不具备多态性,由引用变量所声明的类决定。
虚方法
- 虚方法的作用:实现多态
- 子类中重写了父类的方法,在多态情况下,将此时父类的方法称为虚方法
对象类型转换
- 强制类型转换(向下转型也称造型)
- 子类引用接收父类型强制转换的子类型,是合法的
- 无继承关系的引用类型间的转换是非法的
- 在造型前可以使用instanceof操作符测试一个对象的类型
- 自动类型转换(向上转型)
- 父类引用接收子类对象(多态)
多态和instanceof的应用
class Animal{}class Cat extends Animal{ public void run(){}}class Fish extends Animal{ public void swim(){}}public class Test{ public static void method(Animal an){ if(an instanceof Cat){ (Cat)an.run; } if(an instanceof Fish){ (Fish)an.swim; } } public static void main(String[] args){ method(new Cat()); method(new Fish()); }}
关键字:static
- 使用范围:
- 修饰属性、方法、代码块、内部类
- 被修饰后的成员具备以下特点:
- 随着类的加载而加载
- 被所有对象所共享
- 可以直接被类调用
理解main()
- 由于Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
- 又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的
- 该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。
- 又因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
关键字:final
- final标记的类不能被继承。提高安全性,提高程序的可读性。
- String类、System类、StringBuffer类
- final标记的方法不能被子类重写,但是可以被继承。
- 比如:Object类中的getClass()。
- final常量。名称大写,且只能被赋值一次。
- 在声明时或在每个构造器中或代码块中显式赋值
- static final静态常量
- 在声明时或代码块中显式赋值
关键字:native
- 使用 native 关键字说明这个方法是原生函数,也就是这个方法是用 C/C++等非Java 语言实现的,并且被编译成了 DLL,由 java 去调用。
- (1)为什么要用 native 方法
- java 使用起来非常方便,然而有些层次的任务用 java 实现起来不容易,或者我们 对程序的效率很在意时,问题就来了。例如:有时 java 应用需要与 java 外面的 环境交互。这是本地方法存在的主要原因,你可以想想 java 需要与一些底层系 统如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制: 它为我们提供了一个非常简洁的接口,而且我们无需去了解 java 应用之外的繁 琐的细节。
- (2)native 声明的方法,对于调用者,可以当做和其他 Java 方法一样使用
- 一个 native method 方法可以返回任何 java 类型,包括非基本类型,而且同样可以进行异常控制。
- native method 的存在并不会对其他类调用这些本地方法产生任何影响,实际上 调用这些方法的其他类甚至不知道它所调用的是一个本地方法。JVM 将控制调用 本地方法的所有细节。
- 如果一个含有本地方法的类被继承,子类会继承这个本地方法并且可以用 java 语言重写这个方法(如果需要的话)。
代码块
- 对Java类或对象进行初始化
静态代码块(static)
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 调用非静态的属性和方法。
- 按照从上到下的顺序执行。
- 执行先于非静态代码块。
- 随着类的加载而加载,且只执行一次。
非静态代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 除了调用非静态的结构外,还可以调用静态的变量或方法。
- 按照从上到下的顺序依次执行。
- 每次创建对象的时候,都会执行一次。且先于构造器执行。
创建和初始化对象的过程
- Student s = new Student();以这句代码为例进行说明:
- 对Student类进行类加载,同时初始化类中静态的属性赋默认值
- 给静态方法分配内存空间执行类中的静态代码块
- 堆区中分配对象的内存空间,同时初始化对象中的非静态的属性赋默认值
- 调用Student的父类构造器
- 对Student中的属性进行显示赋值,例如public int age = 20;
- 执行匿名代码块
- 执行构造器代码=号赋值操作,把对象的内存地址赋给变量s
// 静态代码块Fu// 静态代码块Zi// 构造代码块Fu// 构造方法Fu// 构造代码块Zi// 构造方法Zipublic class Test { public static void main(String[] args) { Zi z = new Zi(); }}class Fu { static { System.out.println("静态代码块Fu"); } { System.out.println("构造代码块Fu"); } public Fu() { System.out.println("构造方法Fu"); }} class Zi extends Fu { static { System.out.println("静态代码块Zi"); } { System.out.println("构造代码块Zi"); } public Zi() { System.out.println("构造方法Zi"); }}
// 构造块// 构造块// 静态块// 构造块public class B { public static B t1 = new B(); public static B t2 = new B(); { System.out.println("构造块"); } static { System.out.println("静态块"); } public static void main(String[] args) { B t = new B(); }}
内部类
为什么使用
- 接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。
- 假设现在已经确定了要使用内部类,那么一般情况下,该如何选择?
- 1.考虑这个内部类,如果需要反复的进行多次使用(必须有名字)
- 如果需要定义静态的属性和方法,选择使用静态内部类
- 如果需要访问外部类的非静态属性和方法,选择使用成员内部类
- 2.考虑这个内部类,如果只需要使用一次(可以没有名字)
- 选择使用匿名内部类
- 3.局部内部类,几乎不会使用
内部类基础
-
可以对外围类OuterClass的属性进行无缝的访问
-
当我们在创建某个外围类的内部类对象时,此时内部类对象必定会捕获一个指向那个外围类对象的引用,只要我们在访问外围类的成员时,就会用这个引用来选择外围类的成员。
-
内部类是个编译时的概念,一旦编译成功后,它就与外围类属于两个完全不同的类(当然他们之间还是有联系的)。
-
对于一个名为OuterClass的外围类和一个名为InnerClass的内部类,在编译成功后,会出现这样两个class文件:
-
OuterClass.class
-
OuterClass$InnerClass.class。
-
如何来引用内部类
public class OuterClass { private String name ; private int age; /**省略getter和setter方法**/ public class InnerClass{ public InnerClass(){ name = "chenssy"; age = 23; } //访问外部类属性 public void display(){ System.out.println("name:" + getName() +" ;age:" + getAge()); } } public static void main(String[] args) { OuterClass outerClass = new OuterClass(); OuterClass.InnerClass innerClass = outerClass.new InnerClass(); innerClass.display(); }}
- 生成对外部类对象的引用
public class OuterClass { public void display(){ System.out.println("OuterClass..."); } public class InnerClass{ public OuterClass getOuterClass(){ return OuterClass.this; } } public static void main(String[] args) { OuterClass outerClass = new OuterClass(); OuterClass.InnerClass innerClass = outerClass.new InnerClass(); innerClass.getOuterClass().display(); }}
成员内部类
- 可以无限制的访问外围类的所有 成员属性和方法,尽管是private的,
- 外围类要访问内部类的成员属性和方法需要通过内部类实例来访问。
- 成员内部类中不能存在任何static的变量和方法;
- 成员内部类是依附于外围类的,所以只有先创建了外围类才能够创建内部类。
public class OuterClass { private String str; public void outerDisplay(){ System.out.println("outerClass..."); } public class InnerClass{ public void innerDisplay(){ //使用外围内的属性 str = "chenssy..."; System.out.println(str); //使用外围内的方法 outerDisplay(); } } /*推荐使用getxxx()来获取成员内部类,尤其是该内部类的构造函数无参数时 */ public InnerClass getInnerClass(){ return new InnerClass(); } public static void main(String[] args) { OuterClass outer = new OuterClass(); OuterClass.InnerClass inner = outer.getInnerClass(); inner.innerDisplay(); }}
局部内部类(几乎不使用)
-
它是嵌套在方法和作用域内的
-
应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,到那时又不希望这个类是公共可用的
-
局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法和属性中被使用
-
如果在局部内部类中访问所在方法的变量,那么该变量默认用final修饰,必须赋值初始化
public void display(){ //String sex;//报错 String sex = "csdc"; class InnerClass2{ String s = sex; /* 非静态内部类中不能存在静态成员 */ public String _name2 = "chenssy_inner"; /* 非静态内部类中可以调用外围类的任何成员,不管是静态的还是非静态的 */ public void display(){ System.out.println("OuterClass name:" + name); } }}
-
定义在方法里
public class Parcel5 { public Destionation destionation(String str){ //局部内部类 class PDestionation implements Destionation{ private String label; private PDestionation(String whereTo){ label = whereTo; } public String readLabel(){ return label; } } return new PDestionation(str); } public static void main(String[] args) { Parcel5 parcel5 = new Parcel5(); Destionation d = parcel5.destionation("chenssy"); }}
-
定义在作用域里
public class Parcel6 { private void internalTracking(boolean b){ if(b){ //内部类 class TrackingSlip{ private String id; TrackingSlip(String s) { id = s; } String getSlip(){ return id; } } TrackingSlip ts = new TrackingSlip("chenssy"); String string = ts.getSlip(); } } public void track(){ internalTracking(true); } public static void main(String[] args) { Parcel6 parcel6 = new Parcel6(); parcel6.track(); }}
匿名内部类(使用最多)
-
匿名内部类是没有访问修饰符的。
-
new 匿名内部类,这个类首先是要存在的。如果将InnerClass接口注释掉,就会出现编译出错。
-
当所在方法的形参需要被匿名内部类使用,那么这个形参就必须为final。
-
匿名内部类是没有构造方法的。因为它连名字都没有何来构造方法。
-
优点:简化了创建子类
-
创建匿名内部类
new 父类构造器(实参列表)|实现接口(){ 类体部分}
public class OuterClass { public InnerClass getInnerClass(final int num,String str2){ //匿名内部类 return new InnerClass(){ int number = num + 3; public int getNumber(){ return number; } }; } public static void main(String[] args) { OuterClass out = new OuterClass(); InnerClass inner = out.getInnerClass(2, "chenssy"); System.out.println(inner.getNumber()); }}interface InnerClass { int getNumber();}
静态内部类
- 使用static修饰的内部类我们称之为静态内部类,或嵌套内部类。
- 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。没有这个引用就意味着:
- 它的创建是不需要依赖于外围类的。
- 它不能使用任何外围类的非static成员变量和方法。
public class OuterClass { private String sex; public static String name = "chenssy"; /** *静态内部类 */ static class InnerClass1{ /* 在静态内部类中可以存在静态成员 */ public static String _name1 = "chenssy_static"; /* 在静态内部类中可以存在非静态成员 */ public int age = 3; public void display(){ /* * 静态内部类只能访问外围类的静态成员变量和方法 * 不能访问外围类的非静态成员变量和方法 */ System.out.println("OutClass name :" + name); } } /** * 非静态内部类 */ class InnerClass2{ /* 非静态内部类中不能存在静态成员 */ public String _name2 = "chenssy_inner"; /* 非静态内部类中可以调用外围类的任何成员,不管是静态的还是非静态的 */ public void display(){ System.out.println("OuterClass name:" + name); } } /** * @desc 外围类方法 * @author chenssy * @data 2013-10-25 * @return void */ public void display(){ /* 静态内部类 可以直接创建实例不需要依赖于外围类 */ new InnerClass1().display(); /* 外围类访问静态内部类的静态成员 */ System.out.println(InnerClass1._name1); /* 外围类访问静态内部类的非静态成员*/ System.out.println(new InnerClass1().age); /* 非静态内部的创建需要依赖于外围类 */ OuterClass.InnerClass2 inner2 = new OuterClass().new InnerClass2(); /* 外围类访问非静态内部类的成员:使用非静态内部类的实例 */ System.out.println(inner2._name2); inner2.display(); } public static void main(String[] args) { OuterClass outer = new OuterClass(); outer.display(); }}
类与类之间的关系
依赖关系(Dependency)
-
依赖表示类之间的是 一种“临时、短暂”关系,这种关系是不需要保存的.
-
代码中一般指由局部变量、函数参数、返回值建立的对于其他对象的调用关系
-
用带箭头的虚线表示,箭头指向被依赖的类(B,C,D,E)
-
A类依赖了 B,C,D,E 类
class A{ public B method(C c,D d){ E e= new E(); ... B b= new B(); ... return b; }}
关联关系(Association)
-
关联表示类之间的“持久”关系,这种关系一般表示一种重要的业务之间的关系,需要保存到数据库中的或者说持久化
-
对象之间一种引用关系,+ 代码中通过实例变量(类的属性)来表示
-
单向导航,用带箭头的实线表示,箭头指向被引用的类
-
双向导航,用不带箭头的实线表示
-
Employee引用Computer
class Employee{ private int eid;//员工编号 private String name;//员工姓名 private Computer coumputer;//员工所使用的电脑 //....}class Computer{}
-
Husband和Wife双向引用
class Husband{ private Wife wife;}class Wife{ private Husband husband; }
聚合(Aggregation)
-
聚合表示整体与个体的关系,has-a的关系
-
代码中通过实例变量(类的属性)来表示
-
用带空心菱形的实线表示,空心菱形指向被引用的类
-
汽车是整体,引擎和轮胎是个体
class Car{ private Engine engine;//引擎 private Tyre[] tyres;//轮胎}
组合(Composite)
-
表示 contains-a 的关系,是一种强烈的引用关系
-
对象A包含对象B,对象B离开对象A没有实际意义。人包含手,手离开人的躯体就失去了它应有的作用。
-
用带实心菱形的实线表示,实心菱形指向被引用的类
-
菜单,滑动条,工作区离开Window就没有意义
class Window{ private Menu menu;//菜单 private Slider slider;//滑动条 private Panel panel;//工作区}
继承(Generalization)泛化
- 类与类的继承关系,接口与接口的继承关系,is-a 的关系
- 带空心三角的实线表示,空心三角指向被继承的类
实现
- 类与接口的实现关系,is-a 的关系
- 带空心三角的虚线表示,空心三角指向被实现的类
UML类图
Account-balance : double+Account(init_balance : double)+getBalance() : double+deposit(amt : double)+withdraw(amt : double)
- 属性: “:”前是属性名,“:”后是属性的类型
- 构造器:
方法有下划线
+
表示 public 类型,-
表示 private 类型,#
表示protected类型- 方法的写法:
方法的类型(+、-)方法名(参数名:参数类型):返回值类型
javabean(POJO)
- JavaBean是一种Java语言写成的可重用组件。
- 所谓javaBean,是指符合如下标准的Java类:
- 类是公共的
- 有一个无参的公共的构造器
- 有属性,且有对应的get、set方法
2. 接口类型
抽象类与抽象方法
- 用abstract关键字来修饰一个类,这个类叫做抽象类。
- 用abstract来修饰一个方法,该方法叫做抽象方法。
- 抽象方法:只有方法的声明,没有方法的实现。
public abstract void talk();
- 抽象方法:只有方法的声明,没有方法的实现。
- 含有抽象方法的类必须被声明为抽象类。
- 抽象类不一定包含抽象方法
- 抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
接口(interface)
- 接口(interface)是抽象方法和常量值定义的集合。
- 接口的特点:
- 用interface来定义。
- 接口中的所有成员变量都默认是由public static final修饰的。
- 接口中的所有抽象方法都默认是由public abstract修饰的。
- 接口中没有构造器。
- 接口采用多继承机制。
- 接口最终也会被编译成.class文件,但一定要明确接口并不是类,而是另外一种引用数据类型
接口和抽象类
区别 | 抽象类 | 接口 |
---|---|---|
定义 | 包含抽象方法的类 | 主要是抽象方法和全局常量的集合 |
组成 | 构造方法、抽象方法、普通方法、常量、变量 | 常量、抽象方法、(jdk8.0:默认方法、静态方法) |
使用 | 子类继承抽象类(extends) | 子类实现接口(implements) |
关系 | 抽象类可以实现多个接口 | 接口不能继承抽象类,但允许继承多个接口 |
设计模式 | 模板方法 | 简单工厂、工厂方法、代理模式 |
对象 | 对象的多态性产生实例化对象 | 对象的多态性产生实例化对象 |
局限 | 抽象类有单继承的局限 | 接口没有此局限 |
实际 | 作为一个模板 | 作为一个标准或是表示一种能力 |
选择 | 优先使用接口,因为避免单继承的局限 | 优先使用接口,因为避免单继承的局限 |
Java 8接口的改进
- Java 8中,你可以为接口添加静态方法和默认方法。
- 静态方法:
- 使用 static 关键字修饰。可以通过接口直接调用静态方法,。标准库中找到像Collection/Collections或者Path/Paths这样成对的接口和类。
- 默认方法:
- 默认方法使用 default 关键字修饰。可以通过实现类对象来调用。比如:java 8 API中对Collection、List、Comparator等接口提供了丰富的默认方法。
- 接口冲突
- 若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口
- 解决办法:实现类必须覆盖接口中同名同参数的方法,来解决冲突。
- 类优先原则
- 若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题。接口中的默认方法会被忽略。
3. 数组类型
声明
- type[] var;
初始化
- 静态初始化
在定义数组的同时就为数组元素分配空间并赋值。
int[] a = new int[]{1,2,3};
- 动态初始化
数组声明且为数组元素分配空间与赋值的操作分开进行
int[] a = new int[3];
a[0] = 1; a[1] = 2; a[2] = 3;
默认初始化
数组为引用类型,数组元素相当于成员变量,同样会被隐式初始化
数组存储地址
- System.out.println(new int[]{1,2,3});
- [I@15db9742
- 注意数组地址前面有
[
符合来区分对象地址
可变个数的形参
- JavaSE 5.0 中提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。
- JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量
public static void test(int a ,String[] books);
- JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量
public static void test(int a ,String…books);
- 可变参数和普通参数同时出现,可变参数放在最后面
数组拷贝
- 数组对象的长度确定之后便不能修改,但可以通过复制数组的内容变通实现改变数组长度
- 在java.lang.System类中提供一个名为arraycopy的方法可以实现复制数组中元素的功能
public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length)//参数1,需要被复制的目标数组//参数2,从目标数组的哪一个位置开始复制//参数3,需要把数据复制到另外一个新的数组中//参数4,把数据复制到新数组的时候,需要把数据从新数组的什么位置开始复制进去//参数5,目标数组的长度
工具类Arrays
- java.util.Arrays类,里面有很多静态方法可以直接调用
- toString()
- copyOf()
- sort()
- binarySearch()
- 根据值查找下标,需要先进行排序
- asList()
- 转换为List
五、异常处理
1. 异常概述与异常体系结构
- Throwable
- Error
- VirtuMachineError
- StackOverFlowError
- OutOfMenmoryError
- AWTError
- VirtuMachineError
- Exception
- 编译时异常
- IOException
- FileNotFoundException
- SQLException
- IOException
- RuntimeException
- ArithmeticException(算术异常)
- MissingResourceException(丢失资源)
- ClassNotFoundException(找不到类)
- NullPointerException(空指针异常)
- IllegalArgumentException
- ArrayIndexOutOfBoundsException(数组下标越界)
- UnkownTypeException
- 编译时异常
- Error
Error
- Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。
- Error类对象由Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关
Exception
- 因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。
运行时异常
- RuntimeException
- 这些类的异常的特点是:即使没有使用try和catch捕获,Java自己也能捕获,并且编译通过,但运行时会发生异常使得程序运行终止。
编译时异常
- IOException
- SQLException
- …
- 编译时异常,必须捕获,转化为运行时异常,否则编译错误。
受检异常
- 在正确的程序运行过程中,很容易出现的、情理可容的异常状况,在一定程度上这种异常的发生是可以预测的,并且一旦发生该种异常,就必须采取某种方式进行处理。
不受检异常
- 包括RuntimeException及其子类和Error。
2. try-catch-finally(捕获并处理异常)
- 程序不会终止,try-catch-finally块之后的代码可以正常执行
- 如果不使用try-catch-finally处理,程序遇到异常会终止执行
- try块中,异常语句后面的代码不再执行,可以放到finally里面保证执行
3. throws(传递异常)
- 用于向上传递异常,当前函数不再需要处理异常
- 只能声明在方法后不能声明在类上
4. throw(抛出异常)
5. 用户自定义异常类
- 一般地,用户自定义异常类都是RuntimeException的子类。
- 自定义异常类通常需要编写几个重载的构造器。
- 自定义异常需要提供serialVersionUID
- 自定义的异常通过throw抛出。
- 自定义异常最重要的是异常类的名字,当异常出现时,可以根据名字判断异常类型。
七、Java常用类
1. Object类
clone()方法
- protected native Object clone() throws CloneNotSupportedException;
创建对象的方式
- 使用new操作符创建一个对象
- new操作符的本意是分配内存。
- 程序执行到new操作符时,首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。
- 分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。
- 使用clone方法复制一个对象
- clone在第一步是和new相似的,都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,
- 然后再使用原对象中对应的各个域,填充新对象的域,填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部
Shallow Clone 与 Deep Clone
-
Shallow Clone(对象的域是基本类型)
- clone()方法
- Object在对某个对象实施Clone时对其是一无所知的,它仅仅是简单地执行域对域的copy。
- 以Employee为例,它里面有一个域不是基本数据类型的变量,而是一个reference变量,经过Clone之后就会产生一个新的Date型的reference,它和原始对象中对应的域指向同一个Date对象,这样克隆类就和原始类共享了一部分信息,而这样显然是不利的。
-
deep Clone(对象的域是引用类型)
- 进行deep Clone了,对那些非基本类型的域进行特殊的处理
class Employee implements Cloneable{ public Objectclone() throws CloneNotSupportedException { Employee cloned=(Employee)super.clone(); cloned.hireDay=(Date)hireDay.clone(); return cloned; }}
hashCode()方法
- public native int hashCode();
- 返回该对象的哈希码值。
- 该方法用于哈希查找,可以减少在查找中使用equals的次数,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。
- 一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash Code() == obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价
==操作符
- 基本类型比较值:只要两个变量的值相等,即为true。
- 引用类型比较引用(是否指向同一个对象,比较的是地址):只有指向同一个对象时,==才返回true。
equals方法
- 只能比较引用类型,其作用与“==”相同
- 当用equals()方法进行比较时,对类File、String、Date及包装类来说,仅比较类型及内容而不考虑引用的是否是同一个对象;
- 原因:在这些类中重写了Object类的equals()方法。
- 重写equals()方法时必须重写hashcode(方法),因为equals()方法中会调用hashcode()方法
toString() 方法
- toString()方法在Object类中定义,其返回值是String类型,返回类名和它的引用地址。
- 在进行String与其它类型数据的连接操作时,自动调用toString()方法
- 基本类型数据转换为String类型时,调用了对应包装类的toString()方法
2. 包装类
- 基本类型转换为包装类(装箱)
- 自动装箱
Integer i = 10;
- 构造器
Integer i = new Integer(10);
- 自动装箱
- 包装类转换为基本类型(拆箱)
- 自动拆箱
int a = new Integet(10);
- xxxValue()
Integer i = new Integer(10);
int a = i.intValue();
- 自动拆箱
- 基本类型转换为String类
- ValueOf()
String s = String.ValueOf(10);
- ValueOf()
- String类转换为基本类型
- parseXxx(String)
int a = Integer.parseInt("10");
- parseXxx(String)
- 包装类转换为String类
- toString()
Integer i = new Integer(10);
String s = i.toString();
- toString()
- String类转换为包装类
- 构造器
Integer i = new Integer("384");
- 构造器
- 数组转换为字符串
Arrays.toString(new int[]{1,2,3});
- 字符串转换为char数组
char[] c = new String("abcdefg").toCharArray();
整型缓存常量池
- Byte,Short,Long,Integer 内部有一个-128到127的缓存池,静态内部类
private static class IntegerCache{}
定义了缓存数组
Integer a=127;Integer b=127;Integer c=128;Integer d=128;a==b //true c==d //false
3. 时间API
- java.util.Date
- java.sql.Date
- java.sql.Time
- java.sql.Timestamp
- java.util.Calendar
- java.util.GregorianCalendar
- java.util.TimeZone
- java.text.DateFormat
- java.text.SimpleDateFormat
Date类
SimpleDateFormat
Calendar类
4. JDK8新时间API
java.time – 包含值对象的基础包
java.time.chrono – 提供对不同的日历系统的访问
java.time.format – 格式化和解析时间和日期
java.time.temporal – 包括底层框架和扩展特性
java.time.zone – 包含时区支持的类
5. String相关类
String类
-
String对象的内容是储存在一个字符数组value[]中
-
字符串常量储存在字符串常量池,目的是共享
-
字符串非常量储存在堆中
-
常量与常量的拼接结果在常量池,常量池不会存在相同内容的常量
-
只要有一个是变量,拼接结果就在堆中
-
如果拼接的结果调用intern()方法,就返回常量池中的字符串(要求常量池中还没有该字符串)
-
String s = “abc”;
- 在常量池创建字符串,s直接指向方法区的字符串常量池的字符串
String a = "abc";String b = "abc";System.out.println(a.hashCode()==b.hashCode());//true
-
String s = new String();
- s直接指向堆中的字符数组value[]
-
String s = new String(“abc”);
- 在常量池创建字符串,堆中的字符数组value[]指向常量池,然后s再指向堆中的字符数组
- 创建了两个对象
- 第一次创建是因为传进的参数“abc”会创建一个对象,相当于String param=“abc”,
- 第二次创建是因为使用new String() 来创建字符串,此时将第一次创建的对象param=”abc”,作为参数传给String的构造方法。相当于 String str = new String(param)
-
char arr[] = {a,b,c};
String s = new String(arr);- s指向堆中的字符数组value[]
StringBuilder和StringBuffer
- String 字符串常量
- StringBuffer 字符串变量(线程安全)
- StringBuilder 字符串变量(非线程安全)
- 执行速度: StringBuilder > StringBuffer > String
正则表达式
6. File类
7. BigInteger与BigDecimal
8. System类
9.Scanner类
Scanner ss = new Scanner(System.in);int key = ss.nextInt();ss.close();
10. Random类
java.lang.Math
- Math.Random()返回带正号的double值,该值大于等于0.0且小于1.0,即取值范围是[0.0,1.0)的左闭右开区间
java.util.Random
-
Random():创建一个新的随机数生成器
Random rand=new Random();//生成[0,100)内随机整数序列int i=rand.nextInt(100);System.out.println(i);
-
Random(long seed):使用单个 long 种子创建一个新的随机数生成器
Random ran1=new Random(25);System.out.println("使用种子为25的Random对象生成[0,100)内随机整数序列:");for(int i=0;i<10;i++){ System.out.print(ran1.nextInt(100)+" ");}System.out.println();
-
double nextDouble():
返回下一个伪随机数,它是取自此随机数生成器序列的、在0.0和1.0之间均匀分布的 double值 -
int nextInt():
返回下一个伪随机数,它是此随机数生成器的序列中均匀分布的int值,非0到1之间,完全随机的整数 -
int nextInt(int n):
返回一个伪随机数,它是取自此随机数生成器序列的、[0,n)之间均匀分布的int值
12. 枚举
概述
- 枚举,是JDK1.5引入的新特性,可以通过关键字enum来定义枚举类。
- 枚举类是一种特殊的类,它和普通类一样可以使用构造器、定义成员变量和方法,也能实现一个或多个接口。
- 但是枚举类不能继承其他类
public enum Color { BLACK, WHITE}//public enum Color,表示这是一个枚举类型,名字叫Color//BLACK, WHITE表示这个枚举类型有俩个固定的对象,一个叫BLACK,另一个叫WHITE
Color c;//引用指向对象c = Color.BLACK;//默认调用toString方法,父类中重写了toString方法,返回枚举对象的名字System.out.println(c);
- 使用javap命令对Color.class文件进行反向解析,从运行结果中,可以看出
- 枚举其实也是一种类,同时还是一个final修饰的类
- 枚举类型都会默认继承一个父类型:java.lang.Enum,这还是一个抽象的泛型类
public abstract class Enum<E extends Enum<E>>{}
- 枚举中所定义的对象,其实就是类里面的public static final修饰的常量,并且这些常量会在静态代码块中做初始化
- 枚举类型中还一个默认的私有构造器,说明我们在外面并不能自己去创建枚举类型的对象
- 枚举类型中还有默认添加进来的方法
- values()方法,可以返回这个枚举类型的所有对象,返回类型是数组
- value0f(String str)方法,通过一个字符串可以返回枚举对象,这个字符串参数就是枚举对象的名字
- 枚举类型会从父类中继承过来一些方法(具体可以查看其固定的父类型),例如
- String name(),返回这个枚举对象的名字
- int ordinal(),返回这个枚举对象的编号,默认从0开始
意义
- 有的类只需要固定个数的对象
- 表示性别的类型Gender,就会有且只有俩个对象,MALE和FEMAL
获取枚举对象
Color c = Color.BLACK;
Color c = Color.valueOf("BLACK");
枚举中定义属性和方法
- 枚举类型中的第一行代码,要求一定是指定枚举对象的个数和名字,同时最后面加分号;
- 在这行代码下,才可以定义枚举类型的属性和方法
枚举中定义构造器
- 枚举中的构造器,只能使用private修饰,或者不写修饰符,那么默认也是private,同时还可以构造器重载
枚举中定义抽象方法
- 这时候代码编译会报错。需要在定义每一个枚举对象的时候,将抽象方法进行实现,
- 因为枚举类型是final修饰的类,不可能有子类型来实现这个抽象方法,所以就必须是自己的对象去实现这个抽象方法
public enum Gender{ MALE(){ @Override public void test(){ System.out. println("男生测试")} }, FEMALE(){ @Override public void test(){ System.out. println("女生测试") }; } public abstract void test();}
枚举类型实现接口
- 方式一,在枚举中,统一实现这个接口中的抽象方法
public enum Gender implements Action{ MALE, FEMALE; @Override public void run(){ System.out.println("枚举中统一实现接口中的抽象方法"); } interface Action{ void run(); }}
- 方式二,在每个枚举对象中,分别实现这个接口中的抽象方法
public enum Gender implements Action{ MALE(){ @Override public void run(){ System.out.println("男生对象中,单独实现接口中的抽象方法"); },FEMALE(){ @Override public void run(){ System.out.println("女生对象中,单独实现接口中的抽象方法"); } }; interface Action{ void run(); }}
七、泛型
- 泛型是JDK1.5及以上才可以使用的特性/语法,它的本质是类型参数化
- 如果没有传这个泛型参数,那么这个参数T就默认是Object类型
- 在类上面添加泛型参数后,在类中的任何可以使用类型的地方,都可以先使用这个泛型参数
在集合中使用泛型
- 在使用Collection接口的时候,如果没有给泛型参数传值,那么这个E就默认表示为Object,add方法就可以接收任意类型的对象
- 在这种情况下,集合中如果存储的数据类型不一致,就会在强制转换的时候出现类型转换异常
- 如果在使用Collection接口的时候,给泛型参数指定了具体类型,那么就会防止出现类型转换异常的情况,因为这时候集合中添加的数据已经有了一个规定的类型,其他类型是添加不进来的
- 传入泛型参数后,add方法只能接收String类型的参数,其他类型的数据无法添加到集合中,同时在遍历集合的时候,也不需要我们做类型转换了,直接使用String类型变量接收就可以了,JVM会自动转换的
泛型的种类
- 泛型类
publicclassPoint<T>{...}
- 泛型接口
publicinterfaceAction<T>{...}
- 泛型方法
public <T> T test(T t) {return t; }
泛型的类型
- =号俩边的所指定的泛型类型,必须是要一样的
//编译失败//错误信息:ArrayList<Integer>无法转为ArrayList<Object>//在编译期间,ArrayList<Integer>和ArrayList<Object>是俩个不同的类型,并且没有子父类型的关系ArrayList<Object> list=new ArrayList<Integer>();
通配符
- test方法可以接收泛型是任意类型的 Collection集合对象
public voidt est(Collection<?>c){}
- 使用通配符(?)所带来的问题
Collection<?>c;c=new ArrayList<String>();//编译报错//因为变量c所声明的类型是Collection,同时泛型类型是通配符(?)//那么编译器也不知道这个?将来会是什么类型,因为这个?只是一个通配符//所以,编译器不允许使用变量c来向集合中添加新数据。c.add("hello")
泛型边界
List<? extends Number> list
可以接收泛型是Number或者Number子类型的List集合对象List<? super Number> list
可以接收泛型是Number或者Number父类型的List集合对象
类型擦除
- 泛型类型仅存在于编译期间,编译后的字节码和运行时不包含泛型信息,所有的泛型类型映射到同一份字节码。
- 由于泛型是JDK1.5才加入到Java语言特性的,Java让编译器擦除掉关于泛型类型的信息,这样使得Java可以向后兼容之前没有使用泛型的类库和代码,所以在字节码(class)层面是没有泛型概念的
3.自定义泛型结构
八、Java集合
- Collection
- List(接口)
- Vector
- ArrayList
- LinkedList
- Set(接口)
- HashSet(需重写hashCode和equals)
- LinkedHashSet
- SortedSet(接口)
- TreeSet(可定制排序)
- HashSet(需重写hashCode和equals)
- Queue(接口)
- Dueue(接口)
- List(接口)
- Map
- Hashtable
- Properties
- HashMap(需重写hashCode和equals,)
- LinkedHashMap
- SortedMap(接口)
- TreeMap(可定制排序)
- Hashtable
1. Java集合框架概述
- Java 集合可分为 Collection 和 Map 两种体系
- Collection接口:单列数据,定义了存取一组对象的方法的集合
- List:元素有序、可重复的集合
- Set:元素无序、不可重复的集合
- Map接口:双列数据,保存具有映射关系“key-value对”的集合
2. List接口
- List是一种有序(插入,获取,储存有序)的集合
- List是一种带索引的集合
- List是一种可以存放重复数据的集合
3. Set接口
- Set是一种无序(插入,获取,储存无序)的集合
- Set是一种不带下标索引的集合
- Set是一种不能存放重复数据的集合
- HashSet当存放的数据类型是自定义类型,要重写自定义类型的equals()方法和hashcode方法,否则无法保证唯一性,如果需要打印还可以重写toString()方法
4. Map接口
-
Map类型集合中,每次需要存一对数据,key-value(键值对)
-
key键必须是唯一的,value值允许重复
-
键(key)和值(value)一映射,一个key对应一个value
-
HashMap:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。(重要,最常用)
-
HashTable:和之前List集合中的Vector的功能类似,可以在多线程环境中,保证集合中的数据的操作安全,类中的方法大多数使用了synchronized修饰符进行加锁。(线程安全)
-
TreeMap:该类是Map接口的子接口SortedMap下面的实现类,和TreeSet类似,它可以对key值进行排序,同时构造器也可以接收一个比较器对象作为参数。支持key值的自然排序和比较器排序俩种方式。(支持key排序)
-
LinkedHashMap:该类是HashMap的子类,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;(存入顺序就是取出顺序
5. Map的遍历
方法一:keySet()
- 返回Map集合中所有的key值的集合
- 拿到Set集合后,就可以遍历Set集合拿到Map中每个key值,通过key值可以获取到对应的value值(get方法)
方法二:values()
- 返回Map集合中所有的value值的集合
- 拿到Collection集合后,可以可以遍历Collection集合拿到Map中每一个value值,但是这时候拿不到相应的key
方法三:entrySet()
-
Map接口中有定义了一个内部接口Entry:(类似于内部类)
public interface Map<K,V>{ interface Entry<K,V>{ K getKey(); V getValue(); }}
-
一个Entry类型的对象,可以代表Map集合中的一组key-value(键值对),并且提供了获取key值和value值的方法。
-
Map接口中的entrySet()方法,就是将Map集合中的每一组key-value(键值对)都封装成一个Entry类型对象,并且把这些个Entry对象存放到Set集合中,并返回。
Map map=new LinkedHashMap();map.put(4,"mary");map.put(2,"jack");map.put(1,"tom");map.put(3,"lucy");//形式一Set<Entry> entrySet=map.entrySet();for(Entry entry : entrySet){ System.out.println(entry.getKey()+" : "+entry.getValue());}
//形式二Iterator<Entry<String, Double>> iterator = map.entrySet().iterator();while(iterator.hasNext()){ Entry<String, Double> entry = iterator.next(); System.out.println(entry.getKey()+" : "+entry.getValue());}
6. foreach循环
Collection<String> c1=new ArrayList<>();c1.add("hello1");c1.add("hello2");c1.add("hello3");for(Objecto:c1){ System.out.println(o); }
7. Iterator迭代器接口
- ava.lang.Iterable接口中,定义了获取迭代器的方法Iterator iterator()
- java.util.Collection接口继承了java.lang.Iterable接口
- java.util.Iterator接口中,主要定义俩个方法
- boolean hasNext()//判断当前迭代器中是否还有下一个对象
- Object next()//获取迭代器中的下一个对象
Collection c1 =new ArrayList(); c1.add("hello1");c1.add("hel1o2");c1.add("hello3");//获取c1集合的迭代器对象Iterator iterator = c1.iterator();//判断迭代器中,是否还有下一个元素while(iterator.hasNext()){ //如果有的话,就取出来 Object obj= iterator.next(); System.out.println(obj);}
8. Collections工具类
9. Java比较器
- 什么是自定义class: 如 public class Person{ String name; int age }.
- 当我们有这么一个personList,里面包含了person1, person2, persion3…, 我们用Collections.sort(personList), 是得不到预期的结果的.
- 那为什么可以排序一个字符串list呢,那是因为 String 这个对象已经帮我们实现了 Comparable接口 , 所以我们的 Person 如果想排序, 也要实现一个比较器。
自然排序:java.lang.Comparable
- 实现Comparable接口要覆盖compareTo方法, 在compareTo方法里面实现比较:
- 需要修改源代码,修改后自定义类就拥有了自定义规则的比较能力
- 当return 0 表示相等,正数表示交换位置,负数表示不交换位置
- 新添加的元素调用compareTo(),参数为已有的元素
class Teacher implements Comparable<Teacher> { public String id; public String name; public int age; public Teacher(String id, String name, int age) { this.id = id; this.name = name; this.age = age; } @Override public int compareTo(Teacher t) { // TODO Auto-generated method stub if (this.name.compareTo(t.name) > 0) { return 1; } else if (this.name.compareTo(t.name) < 0) { return -1; } else if (this.age != t.age) { return this.age - t.age; } else if (this.id.compareTo(t.id) > 0) { return 1; } else if (this.id.compareTo(t.id) < 0) { return -1; } return 0; } public String toString() { return "id:" + this.id + "\t" + "姓名" + this.name +"\t"+ "年龄" + this.age; }}
定制排序:java.util.Comparator
- 实现Comparator需要覆盖 compare 方法:
- 不需要修改源代码
- 用户可以自己实现复杂的可以通用的逻辑,使其可以匹配一些比较简单的对象
- 当return 0 表示相等,正数表示交换位置,负数表示不交换位置
- 定制排序优先级大于自然排序
TreeSet<Teacher> treeSet2 = new TreeSet<>(new Comparator<Teacher>(){ @Override public int compare(Teacher t1, Teacher t2) { if (t1.name.compareTo(t2.name) > 0) { return 1; } else if (t1.name.compareTo(t2.name) < 0) { return -1; } else if (t1.age != t2.age) { return t1.age - t2.age; } else if (t1.id.compareTo(t2.id) > 0) { return 1; } else if (t1.id.compareTo(t2.id) < 0) { return -1; } return 0; } });
九、IO流
字符编码
- Java语言对文本字符采用Unicode编码
- ASCII(美国标准)
- 单字节编码系统,它只用一个字节的7位,一共表示128个字符。
- ISO-8859-1(西欧标准)
- 用一个字节(8位)来为字符编码,与ASCII字符编码兼容。
- 所谓兼容,是指对于相同的字符,它的ASCII字符编码和ISO-8859-1字符编码相同。
- GB2312(简体中文)
- 与ASCII兼容
- GBK(简体中文)
- 对GB2312的扩展,兼容GB2312,两位表示一个中文
- Unicode(只是一个字符集)
- 收录了全世界所有语言文字中的字符,是一种跨平台的字符编码
- 用2个字节(16位)编码,被称为UCS-2, Java语言采用
- 4个字节(32位)编码,被称为UCS-4
- Unicode不完美,这里就有三个问题,
- 一个是,我们已经知道,英文字母只用一个字节表示就够了,
- 第二个问题是如何才能区别Unicode和ASCII?计算机怎么知道两个字节表示一个符号,而不是分别表示两个符号呢?
- 第三个,如果和GBK等双字节编码方式一样,用最高位是1或0表示两个字节和一个字节,就少了很多值无法用于表示字符,不够表示所有字符。Unicode在很长一段时间内无法推广,直到互联网的出现
- UTF(Unicode的编码规则)
- UTF标准出现,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了
- 能够把Unicode编码转换为操作系统支持的编码,常见的UTF字符编码包括UTF-8、UTF-16、UTF-32
- UTF-8,使用一至四个字节为每个字符编码,三位表示一个中文
- UTF-16,使用二或四个字节为每个字符编码
- UTF-32,使用四个字节为每个字符编码
- 在Unicode出现之前,所有的字符集都是和具体编码方案绑定在一起的(即字符集≈编码方式),都是直接将字符和最终字节流绑定死了
- Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。推荐的Unicode编码是UTF-8和UTF-16
操作同一个文件
//情形一FileInputStream fis = new FileInputStream("1.txt");//新建一个文件替代原文件FileOutputStream fos = new FileOutputStream("1.txt");//输出-1,因为文件内容已经被覆盖掉了System.out.println(fis.read());//解决方式:将input的全部read操作放到新建output之前
//情形二FileInputStream fis = new FileInputStream("1.txt");//设置追加文件内容参数FileOutputStream fos = new FileOutputStream("1.txt",true);//正常输出,System.out.println(fis.read());//解决方式二:在FileOutputStream多参构造函数
编码问题
- 转换流
1. IO流概念及流的分类
-
数据以二进制的形式在程序与设备之间流动传输,就像水在管道里流动一样,所以就把这种数据传输的方式称之为输入流、输出流。这里描述的设备,可以是文件、网络、内存等
-
以java程序本身作为参照点,如果数据是从程序“流向”文件,那么这个流就是输出流,如果数据是从文件“流向”程序,那么这个流就是输入流
-
按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)
-
按数据流的流向不同分为:输入流,输出流
-
按流的角色的不同分为:节点流,处理流
-
在代码中,使用流操作数据的的基本步骤是:
- 1.声明流
- 2.创建流
- 3.使用流
- 4.关闭流
抽象基类 | 输入流 | 输出流 |
---|---|---|
字节流 | InputStream | OutputStream |
字符流 | Reader | Writer |
- io流体系
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
文件流 | FilelnputStream | FileOutputStream | FileReader | FileWriter |
数组流 | ByteArraylnputStream | ByteArrayOutputStream | CharArrayReader | CharrayWriter |
管道流 | PipedlnputStream | PipedOutputStream | PipedReader | PipedWriter |
字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedlnputStream | BufferedOuputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | OutputStreamWriter | ||
对象流 | ObjectinputStream | ObjectOutputStream | ||
过滤流 | FilterlnputStream | FilterOutputStream | FilterReader | FilterWriter |
打印流 | PrintStream | PrintWriter | ||
推回输入流 | PushbacknputStream | PushbackReader | ||
数据流 | DatalnputStream | DataOutputStream |
2.节点流
- 字节流和字符流,都属于节点流。
- 它们的特点是,可以【直接】读取某一个地方的数据,或者【直接】把数据写入到某一个地方
字节流
概述
计算机中,存储一切数据(文本、图片、视频等),都是以二进制的形式进行的,最终都是一个一个的字节,所以使用流,进行传输数据的时候,也是一个一个的字节进行的
- 字节输入流java.io.InputStream
- 字节输出流java.io.OutputStream
控制台
- 这个子类中重写的read方法,会让线程阻塞,等待用户在控制台中的输入,用户输入并按下回车,程序中的read方法就从阻塞状态恢复过来,从而读取到用户输入的内容
public class Test { public static void main(String[] args) { //1.声明流 InputStream in=null; OutputStream out=null; //2.创建流,这里使用System中已经创建好的流对象 in=System.in; out=System.out; //3.使用流 int data=-1; try{ data=in.read(); out.write(data); out.flush();//刷新缓冲区,强制所有数据写出去 } catch (IOException e) { e.printStackTrace(); }finally { //4.关闭流 if(in!=null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if(out!=null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } }}
字节数组
- 使用字节流,从字节数组中读取数据,以及向字节数组中写数据。
java.io.ByteArrayInputStream
负责从字节数组中读取数据java.io.ByteArrayOutputStream
负责把数据写入到字节数组中
文件
- java.io.File类,是java中对文件和目录的抽象表示,主要用于文件和目录的创建、查找和删除等操作
网络
- 使用字节流,可以从网络中读取数据,以及向网络中写数据。这部分内容,在后面的网络编程章节进行学习
字符流
3.转换流
概述
- 转换流,可以在把一个字节流转换为字符的同时,并指定转换的字符编码
输出
- java.io.OutputStreamWriter,可以将字节输出流转换为字符输出流,并指定编码
输入
- java.io.InputStreamReader,可以将字节输入流转换为字符输入流,并指定编码
4.数据流
概述
- 字节流操作数据的时候,以字节文件单位,一个个的进行读写,很多时候这样并不方便。
- 我们希望读出来的若干个字节,自动转换为指定类型的数据,例如int、float、char等。
- 类似的,我们也希望每次能直接把一个数据,自动转成字节再写出去
输出
- java.io.DataOutputStream,可以将指定类型的数据转换为字节,并写出去
输入
- java.io.DataInputStream,可以将读取到的字节自动转化为指定类型的数据,一般需要和DataOutputStream配合使用
5.缓冲流
概述
- 缓冲流,可以在创建流对象时,设置一个默认大小的缓冲区数组,通过缓冲区进行读写,减少系统磁盘的IO次数,从而提高读写的效率
字节缓冲流
- java.io.BufferedInputStream,负责给字节输入流提供缓冲功能
- java.io.BufferedOutputStream,负责给字节输出流提供缓冲功能
字符缓冲流
- java.io.BufferedReader,负责给字符输入流提供缓冲功能
- java.io.BufferedWriter,负责给字符输出流提供缓冲功能
public class Test { public static void main(String[] args) { // 声明 BufferedInputStream bis = null; BufferedOutputStream bos = null; try { // 建立 bis = new BufferedInputStream(new FileInputStream("1.txt")); bos = new BufferedOutputStream(new FileOutputStream("2.txt")); // 操作 int data = -1; while ((data = bis.read()) != -1) { bos.write(data); } bos.flush(); } catch (Exception e) { e.printStackTrace(); } finally { // 关闭 if (bis != null) { try { bis.close(); } catch (IOException e) { e.printStackTrace(); } } if (bos != null) { try { bos.close(); } catch (IOException e) { e.printStackTrace(); } } } }}
6.对象流
概述
- Java 提供了一种对象序列化的机制,可以将对象和字节序列之间进行转换:
- 序列化
- 将对象转换为字节序列
- 反序列化
- 读取字节序列转化为对象
要求
- 并非所有对象都可以进行序列化和反序列化,而是只有实现了java.io.Serializable接口的对象才可以
对象输出流
- java.io.ObjectOutputStream,将Java对象转换为字节序列,并输出到内存、文件、网络等地方
- 具体输出到什么地方,要看ObjectOutputStream “包裹”的是哪一个节点流
对象输入流
- java.io.ObjectInputStream,从某一个地方读取出对象的字节序列,并生成对应的对象。
- 具体是从输什么地方读取字节,要看ObjectInputStream“包裹”的是哪一个节点流
transient
- java中的关键字transient,可以修饰类中的属性,它是让对象在进行序列化的时候,忽略掉指定的属性。
- 常用在一些敏感属性的修饰,例如对象中的password属性
- String和集合类也定义类对象输入流和输出流方法,在调用序列化和反序列化方法时也会调用这些方法,将容器的属性进行序列化
序列化版本号
- Serializable 接口给需要序列化的类,提供了一个序列版本号:serialVersionUID
- 该版本号的目的,是用于验证序列化的对象和对应类是否版本匹配
从控制台输入教师基本信息,数据之间通过“ - ”分割(每一行,为一个老师的信息)当输入exit时结束从控制台获取教师信息后创建教师对象,将对象添加到集合中将集合写到文件中再从文件中读取集合进行遍历输出public class Test { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); ArrayList<Teacher> list = new ArrayList<>(); while (true) { String data = scanner.nextLine(); if (data.equals("exit")) { break; } String[] split = data.split("-"); list.add(new Teacher(Integer.parseInt(split[0]), split[1], Integer.parseInt(split[2]))); } // 声明 ObjectOutputStream oos = null; ObjectInputStream ois = null; try { oos = new ObjectOutputStream(new FileOutputStream("1.txt")); oos.writeObject(list); oos.flush(); //对同一个文件操作的流,创建要分开 ois = new ObjectInputStream(new FileInputStream("1.txt")); Object readObject = ois.readObject(); System.out.println(readObject); } catch (Exception e) { e.printStackTrace(); } finally { if (oos != null) { try { oos.close(); } catch (IOException e) { e.printStackTrace(); } } scanner.close(); } }}class Teacher implements Serializable { private int id; private String name; private int salary; public Teacher(int id, String name, int salary) { this.id = id; this.name = name; this.salary = salary; } @Override public String toString() { return "Teacher [id=" + id + ", name=" + name + ", salary=" + salary + "]"; }}## 7. 随机访问流+ 概述java.io.RandomAccessFile是JavaAPI中提供的对文件进行随机访问的流+ 它并没有继承之前介绍到的那四个抽象父类型+ 随机访问流,它的对象即可用作文件内容的读,又可以用作文件内容的写,+ 同时它还可以任意定位到文件的某一个文件进行读或者写操作```java//往文件指定位置写内容public class Test { public static void main(String[] args) { RandomAccessFile raf = null; int replacePos = 1; String replaceContent = "briup"; try { raf = new RandomAccessFile("1.txt", "rw"); raf.seek(replacePos); raf.write(replaceContent.getBytes()); raf.seek(0); int len = -1; byte[] data = new byte[10]; while ((len = raf.read(data)) != -1) { System.out.write(data, 0, len); } System.out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { if (raf != null) { try { raf.close(); } catch (IOException e) { e.printStackTrace(); } } } }}
8. 管道流
- java.io.PipedInputStream负责从管道中读取数据
- java.io.PipedOutputStream负责将数据写入到管道中
public class Test { public static void main(String[] args) { PipedInputStream bis = null; PipedOutputStream bos = null; try { bos = new PipedOutputStream(); bis = new PipedInputStream(); bis.connect(bos); Thread t1 = new OutputThread(bos); Thread t2 = new InputThread(bis); t1.start(); t2.start(); } catch (Exception e) { e.printStackTrace(); } }}class InputThread extends Thread { InputStream in = null; FileOutputStream fos = null; public InputThread(InputStream in) { this.in = in; } @Override public void run() { int len = -1; try { byte[] data = new byte[10]; fos = new FileOutputStream("1.txt"); while ((len = in.read(data)) != -1) { fos.write(data, 0, len); fos.flush(); } } catch (Exception e) { e.printStackTrace(); } finally { if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } }}class OutputThread extends Thread { OutputStream out = null; public OutputThread(OutputStream out) { this.out = out; } @Override public void run() { Scanner scanner = new Scanner(System.in); byte[] data = scanner.nextLine().getBytes(); try { out.write(data); out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { scanner.close(); if (out != null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } }}
9. 随机访问流
9. NIO
- Java NIO (New IO,Non-Blocking IO)是从Java 1.4版本开始引入的一套新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
- Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO
10. NIO.2
- 随着JDK 7 的发布,Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称他们为NIO.2
- 早期的Java只提供了一个File类来访问文件系统,但File类的功能比较有限,所提供的方法性能也不高。而且,大多数方法在出错时仅返回失败,并不会提供异常信息。
- NIO. 2为了弥补这种不足,引入了Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。Path可以看成是File类的升级版本,实际引用的资源也可以不存在
- NIO.2在java.nio.file包下还提供了Files、Paths工具类,Files包含了大量静态的工具方法来操作文件;Paths则包含了两个返回Path的静态工厂方法
十、多线程
1. 基本概念
-
进程:进程就是在系统中,运行一个应用程序的基本单位
-
线程:线程是进程中的一个代码执行单元,负责当前进程中代码程序的执行,一个进程中有一个或多个线程
-
线程的并发执行,是指在一个时间段内,俩个或多个线程,使用一个CPU,进行交替运行。
-
线程的并行执行,是指在同一时刻,俩个或多个线程,各自使用一个CPU,同时进行运行
-
一个进程的线程都是串行(用户态多线程中),不同的进程可以并行(多核处理器中)。在用户态多线程中同一个进程下的多个线程不可以并行运行,不管多少核处理器,它的线程只能交替顺序运行
-
时间片
- 当前一个线程要使用CPU的时候,CPU会分配给这个线程一小段时间(毫秒级别),这段时间就叫做时间片,也就是该线程允许使用CPU运行的时间,在这个期间,线程拥有CPU的使用权。
- 如果在一个时间片结束时,线程还在运行,那么这时候,该线程就需要停止运行,并交出CPU的使用权,然后等待下一个CPU时间片的分配
- 所以,在宏观上,一段时间内,我们感觉俩个线程在同时运行代码,其实在微观中,这俩个线程在使用一个CPU的时候,它们是交替着运行的,每个线程每次都是运行一个很小的时间片,然后就交出CPU使用权,只是它们俩个交替运行的速度太快了,给我们的感觉,好像是它们俩个线程在同时运行
-
线程调度
- 当俩个或多个线程使用一个CPU来运行代码的时候,在操作系统的内核中,就会有相应的算法来控制线程获取CPU时间片的方式,从而使得这些线程可以按照某种顺序来使用cpu运行代码,这种情况被称为线程调度
- 时间片轮转
- 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
- 抢占式调度
- 系统会让优先级高的线程优先使用CPU(提高抢占到的概率),但是如果线程的优先级相同,那么会随机选择一个线程获取当前CPU的时间片
- JVM中的线程,使用的为抢占式调度
-
main线程
- 使用java命令来运行一个类的时候,首先会启动JVM(进程),JVM会在创建一个名字叫做main的线程,来执行类中的程序入口(main方法)
2. 线程的创建和使用
方式一:继承Thread类
- 1.定义Thread类的子类(可以是匿名内部类),并重写Thread类中的run方法,run方法中的代码就是线程的执行任务
- 2.创建Thread子类的对象,这个对象就代表了一个要独立运行的新线程,可以使用匿名内部类
- 3.调用线程对象的start方法来启动该线程
方式二:实现Runnable接口
-
1.直接创建Thread对象,
-
2.在调用构造器的时候,传一个Runnable接口的实现类对象进来
-
3.调用线程对象的start方法来启动该线程
-
优点:
- 可以把相同的一个执行任务(Runnable接口的实现),交给不同的线程对象去执行
- 可以避免java中的单继承的局限性。
- 线程和执行代码各自独立,实现代码解耦
方式三:实现Callable接口
- 和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。
- Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务
- 创建步骤
1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
4】调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
FutureTask<Integer> future = new FutureTask<Integer>(new Callable<Integer>() { @Override public Integer call() throws Exception { return 5; }});Thread thread = new Thread(future);
方式四:线程池
- Executor接口中之定义了一个方法execute(Runnable command),该方法接收一个Runable实例,它用来执行一个任务,任务即一个实现了Runnable接口的类
- ExecutorService接口继承自Executor接口,它提供了更丰富的实现多线程的方法
- Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
- ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,便进入关闭状态,此时意味着ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当素有已经提交了的任务执行完后,便到达终止状态。如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可
Runnable runnable = new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "线程被调用了"); }};ExecutorService executorService = Executors.newCachedThreadPool();executorService.execute(runnable);
start():
- 启动线程,并执行对象的run()方法
run():
- 线程在被调度时执行的操作
- 表示线程开始运行
getName():
- 返回线程的名称
setName():
- 设置该线程名称
currentThread():
- 返回当前线程
yield():
- 线程让步
- 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
- 若队列中没有同优先级的线程,忽略此方法
join():
- 当某个程序执行流中调用其他线程的join() 方法时,调用线程将被阻塞,直到join() 方法加入的join 线程执行完为止
- 低优先级的线程也可以获得执行
sleep():
- 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
- 抛出InterruptedException异常
stop():
- 强制线程生命期结束,不推荐使用
isAlive():
- 判断线程是否还活着
interrupt()
- 本来t1线程调用了sleep方法进入了阻塞状态,需要100后才会恢复的,但是我们在主线程中调用了t1线程对象的打断方法interrupt,那么此时Thread.sleep(100000);这句代码就会抛出被打断的异常,同时t1线程从阻塞状态恢复到RUNNABLE状态,继续执行代码
- 即使是线程从就绪状态被打断,thread.isInterrupted()输出结果也为true
//创建一个线程程序,将该线程睡眠,并且打断睡眠状态,查看是否被打断public static void main(String[] args) { Thread thread = new Thread() { @Override public void run() { try { sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }; thread.start(); try { thread.join(1000); } catch (InterruptedException e) { e.printStackTrace(); } thread.interrupt(); System.out.println(thread.isInterrupted());}
3.线程的优先级
- 默认情况下,线程的优先级都是5
- 线程的优先级使用int类型数字表示,最大是10,最小是1,默认的优先级是5
- 优先级无法保证线程执行顺序,优先级高只是概率大,不是一定执行
t1.setPriority(Thread.MAX_PRIORITY);t2.setPriority(Thread.MIN_PRIORITY);
4.线程组
- Java中使用java.lang.ThreadGroup类来表示线程组,它可以对一批线程进行管理,对线程组进行操作,同时也会对线程组里面的这一批线程操作
- 只有在创建线程对象的时候,才能指定其所在的线程组,线程运行中途不能改变它所属的线程组
ThreadGroupgroup=newThreadGroup("我的线程组");Threadt=newThread(group,"t线程");//指定线程所属的线程组ThreadGroupthreadGroup=t.getThreadGroup();
5. 线程的生命周期
-
线程生命周期
-
线程七种状态
-
1、新建状态(New):新创建了一个线程对象。
-
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权,即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。
-
3、运行状态(Runnable):就绪状态的线程获取了CPU,执行程序代码。
-
4、阻塞状态:阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
- 无限期等待(WAITING):
运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒, - 锁阻塞(BLOCKED):
运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。 - 有限期等待(TIMED_WAITING):
运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时,或者I/O处理完毕时,线程重新转入就绪状态。
- 无限期等待(WAITING):
-
5、死亡状态(TERMINATED):线程执行完了或者因异常退出了run()方法,该线程结束生命周期
6. 线程的同步
- 当使用多个线程访问同一个共享变量的时候,并且线程中对变量有写的操作,这时就容易出现线程安全问题。
- Java中提供了线程同步的机制,来解决上述的线程安全问题。
synchronized(锁)
- 线程同步的效果,就是一段加锁的代码,每次只能有一个拿到锁的线程,才有资格去执行,没有拿到的锁的线程,只能等拿到锁的线程把代码执行完,再把锁给释放了,它才能去拿这个锁然后再运行代码
synchronized修饰代码块
-
格式:
synchronized (锁对象){ //操作共享变量的代码,这些代码需要线程同步,否则会有线程安全问题 //...}
-
只要多个线程的锁对象一致,就可以保证代码块的安全性
-
可以用""空字符串作为锁对象
public class Test{ public static void main(String[] args){ MyData myData = new MyData(); Thread t1 = new Thread("t1"){ @Override public void run(){ String name = Thread.currentThread().getName(); synchronized(myData){ for (int i=0;i<10;i++){ myData.num =i; System.out.println(name + ":" + myData.num); } } } }; t1.start(); }}class MyData{ int num;}
- 对应这样加锁的代码,如果俩个线程进行并发访问的话:
- 假设线程t1是第一个这段代码的线程,那么它会率先拿到这把锁,其实就是在这个锁对象中写入自己线程的信息,相当于告诉其他线程,这把锁现在是我的,你们都不能使用。
- 这时候t1线程拿着锁,就可以进入到加锁的代码块中,去执行代码,执行很短的一个时间片,然后退出,但是锁并不释放,也就意味着,即使下次是t2线程抢到CPU的使用权,它也无法运行代码,因为t2没有拿到锁。
- 就这样,t1线程开心的拿着锁,抢到CPU的执行权,抢到了就去执行,抢不到也不用担心,因为没有其他线程可以“偷偷”的执行这段代码,因为其他线程拿不到锁。
- 而对于t2线程来说,即使有一次抢到了CPU执行权,来到了代码面前,要执行的时候才发现,锁被t1线程拿走了,自己无法进入代码块中执行,这时候t2线程就会从运行状态进入阻塞状态,直到t1运行完,把锁释放了,t2线程才会恢复到RUNNABLE状态,抢到CPU执行权,再拿到锁,然后进入代码块中执行
- 注意,这时候t2线程的阻塞状态,和之前学习的调用sleep或join方法进入的阻塞不同,这种阻塞属于锁阻塞,需要等待另一个线程把锁释放了,t2线程才能恢复。如果t2线程处于这种阻塞,那么调用线程对象的getState方法返回的状态名称为:BLOCKED
synchronized修饰非静态方法
- 默认使用this当做锁对象,并且不能自己另外指定
synchronized修饰静态方法
- 默认使用当前类的Class对象当做锁对象,并且不能自己另外指定
方法声明明为synchronized将会影响效率
Lock(锁)
- 从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
- ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
class A{ private final ReentrantLock lock = new ReenTrantLock(); public void m(){ lock.lock(); try{ //保证线程安全的代码; }finally{ lock.unlock(); //如果同步代码有异常,要将unlock()写入finally语句块 } }}
- synchronized与Lock的对比
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
- 优先使用顺序:
- Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)
7. 线程的通信
- Object类中有三个方法:wait()、notify()、notifyAll
- 当一个对象,在线程同步的代码中,充当锁对象的时候,在synchronized同步的代码块中,就可以调用这个锁对象的这三个方法了。
- 三个核心点:
- 任何对象中都一定有这三个方法
- 只有对象作为锁对象的时候,才可以调用
- 只有在同步的代码块中,才可以调用
wait()
- 令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行
- synchronized关键字,虽然可以达到线程同步的效果,但是太“霸道”了,只要一个线程拿到了锁对象,那么这个线程无论是在运行状态,还是时间片用完,回到就绪状态,还是sleep休眠,这个线程都是死死的拿着这个锁对象不释放,只有这个线程把线程同步的代码执行完,才会释放锁对象让别的线程使用。
notify() 和 notifyAll()
- notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
- notifyAll():唤醒正在排队等待资源的所有线程结束等待
//编写两个线程,一个线程打印1-52的整数,另一个线程打印字母A-Z。打印顺序为12A34B56C....5152Z。//即按照整数和字母的顺序从小到大打印,并且每打印两个整数后,打印一个字母,交替循环打印,直到打印到整数52和字母Z结束public static void main(String[] args) { Runnable runnable = new Runnable() { @Override public void run() { synchronized ("") { for (int i = 1; i < 53; i++) { "".notify(); System.out.print(i+" "); if (i % 2 == 0) { try { "".wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } "".notify(); } } }; Runnable runnable2 = new Runnable() { @Override public void run() { synchronized ("") { for (char i = 'A'; i <= 'Z'; i++) { "".notify(); System.out.println(i); try { "".wait(); } catch (InterruptedException e) { e.printStackTrace(); } } "".notify(); } } }; new Thread(runnable).start(); new Thread(runnable2).start();}
8.死锁
- 简单的描述死锁就是:俩个线程t1和t2,t1拿着t2需要等待的锁不释放,而t2又拿着t1需要等待的锁不释放,俩个线程就这样一直僵持下去。
- 在程序中要尽量避免出现死锁情况,一旦发生那么只能手动停止JVM的运行,然后查找并修改产生死锁的问题代码
public static void main(String[] args) { Thread thread = new Thread() { @Override public void run() { synchronized ("t1") { try { sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized ("t2") { } } } }; Thread thread2 = new Thread() { @Override public void run() { synchronized ("t2") { try { sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized ("t1") { } } } }; thread.start(); thread2.start();}
9. JDK5.0新增线程创建方式
新增方式一:实现Callable接口
- 与使用Runnable相比,Callable功能更强大些
- 相比run()方法,可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 获取返回结果(需要借助FutureTask类)
- Future接口
- 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
- FutrueTask是Futrue接口的唯一的实现类
- FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
新增方式二:使用线程池
-
背景:
- 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
-
思路:
- 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
-
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
-
JDK 5.0起提供了线程池相关API:ExecutorService和Executors
-
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
- Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
- void shutdown() :关闭连接池
-
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
- Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
- Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
- Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
- Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
十一、注解
1. 概述
- 注解(Annotation),是jdk5.0引入的技术,用它可以对java中的某一个段程序进行说明或标注,并且这个注解的信息可以被其他程序使用特定的方式读取到,从而完成相应的操作
- 注解和注释的区别
- 注解是给其他程序看的,通过参数的设置,可以在编译后class文件中【保留】注解的信息,其他程序读取后,可以完成特定的操作
- 注释是给程序员看的,无论怎么设置,编译后class文件中都是【没有】注释信息,方便程序员快速了解代码的作用或结构
2. 格式
-
没有属性的注解:
public @interface 注解名称{
-
有属性,但没有默认值的注解:
public @interface 注解名称{ public 属性类型 属性名();}
-
有属性,有默认值的注解:
public @interface 注解名称{ 属性类型 属性名() default 默认值;}
3. 范围
-
注解的使用范围,都定义在了一个枚举类
public enum ElementType{}
-
TYPE,使用在类、接口、注解、枚举等类型上面
-
FIELD,使用在属性上面
-
METHOD,使用在方法上面
-
PARAMETER,使用在方法的参数前面
-
CONSTRUCTOR,使用在构造器上面(了解)
-
LOCAL_VARIABLE,使用在局部变量上面(了解)
-
ANNOTATION_TYPE,使用在注解类型上面
-
PACKAGE,使用在包上面(了解)
- 包注解只能写在package-info.java文件中
-
TYPE_PARAMETER,使用在声明泛型参数前面,JDK1.8新增(了解)
-
TYPE_USE,使用在代码中任何类型前面,JDK1.8新增(了解)
4. 声明周期
-
SOURCE,注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃
-
CLASS,注解被保留到class文件,但jvm加载class文件时候被遗弃
-
RUNTIME,注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
-
如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解,比如@Deprecated使用RUNTIME注解。
-
如果要在编译时进行一些预处理操作,比如生成一些辅助代码,就用 CLASS注解;
-
如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,使用SOURCE 注解
-
注意,因为RUNTIME的生命周期最长,所以其他俩种情况能作用到的阶段,使用RUNTIME也一定能作用到
-
注解的三种保留策略,都定义在了一个枚举类中
public enum RetentionPolicy{}
5. 元注解
- 在我们进行自定义注解的时候,一般会使用到元注解,来设置自定义注解的基本特点。
- 所以,元注解也就是对注解进行基本信息设置的注解。
- 常用到的元注解有:
- @Target,用于描述注解的使用范围,例如用在类上面还是方法上面
- @Retention,用于描述注解的保存策略,是保留到源代码中、Class文件中、还是加载到内存中
- @Documented,用于描述该注解将会被javadoc生产到API文档中
- @Inherited,用于表示某个被标注的类型是被继承的,如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类
6. 自定义注解
@Target({ElementType.METHOD})@Retention(RetentionPolicy. RUNTIME) public @interface Role {//角色的名称String name() default "";//name属性的别名,使用value可以简化配置String value() default "";
7. 使用注解
public class Service{ @Role(name = "admin") public void delete(longid){ //.. } @Role("admin")//@Role(value="admin"),等价于@Role("admin") public void find(long id){ //.. }}
十二、Java反射机制
1. 概述
- 概述反射是java中提供的一种机制,它允许我们在程序运行的时候,动态获取一个类中的基本信息,并且可以调用类中的属性、方法、构造器
2. Class类型
- java.lang.Class是API中提供的一个类,它可以表示java中所有的类型,包括基本类型和引用类型
- 在java中,每种类型(基本类型和引用类型)加载到内存之后,都会在内存中生成一个Class类型对象,这个对象就代表这个具体的java类型,并且保存这个类型中的基本信息。
- java中的每种类型,都有且只有唯一的一个Class类型对象与之对应!并且在类加载的时候自动生成
3. 获取Class对象
- 获取基本类型的Class对象:
- 只有一种方式:
- Class c = int.class;
- 只有一种方式:
- 获取接口类型的Class对象:
- 有俩种方式:
- Class c1 = Action.class;
- Class c2 = Class.forName(“com.briup.demo.Action”);
- 有俩种方式:
- 获取数组类型的Class对象:
- 有俩种方式:
- Class c1 = int[].class;
- int[] arr = new int[4]; Class c2 = arr.getClass();
- 有俩种方式:
- 获取类类型的Class对象:
- 有三种方式:
- Class c1 = Student.class;
- Class c2 = Class.forName(“com.briup.demo.Student”);
- Student stu = new Student(); Class c3 = stu.getClass();
- 有三种方式:
4. 获取类的信息
获取类的基本信息
Student stu = new Student();Class c = stu.getClass();//获取类的名字,全限定名System.out.println(c.getName());//获取类的名字,简单类名System.out.println(c.getSimpleName());//获取类所属包的名字System.out.println(c.getPackage().getName());//获取类的修饰符System.out.println(Modifier.toString(c.getModifiers()));//获取类的父类型的名字System.out.println(c.getSuperclass().getName());//获取类实现的所有接口System.out.println(Arrays.toString(c.getInterfaces()));Class c2=Object.class;Class c3=Action.class;Class c4=Mark.class;Class c5=String.class;//判断c2代表的类型是不是c代表类型的父类型System.out.println(c2.isAssignableFrom(c));//判断c3代表的类型是不是c代表类型的父类型System.out.println(c3.isAssignableFrom(c));//判断c4代表的类型是不是c代表类型的父类型System.out.println(c4.isAssignableFrom(c));//判断c5代表的类型是不是c代表类型的父类型System.out.println(c5.isAssignableFrom(c));
获取类中声明的属性
- java.lang.reflect.Field
- 获取类中的public修饰的属性,也包含从父类中继承过来的public属性
- Field[] getFields()
- Field getField(String name)
- 获取类中声明的属性(包含私有的),但是不能获取从父类中继承过来的属性
- Field[] getDeclaredFields()
- Field getDeclaredField(String name)
Classc=Class.forName("com.briup.demo.Student");Field[] fields=c.getDeclaredFields();for(Fieldf : fields){ System.out.println("属性的修饰符:"+Modifier.toString(f.getModifiers())); System.out.println("属性的类型:"+f.getType().getName()); System.out.println("属性的名称:"+f.getName()); System.out.println("---------------------"); }
获取类中声明的方法
- java.lang.reflect.Method
- 获取当前类中的public方法,包含从父类中继承的public方法
- Method[] getMethods()
- Method getMethod(String name, Class<?>… parameterTypes)
- 获取当前类中声明的方法(包含私有的),但是不能获取从父类中继承过来的方法
- Method[] getDeclaredMethods()
- Method getDeclaredMethod(String name, Class<?>… parameterTypes)
Student stu=new Student();Class c=stu.getClass();//获取类中声明的所有方法Method[] methods=c.getDeclaredMethods();for(Methodm : methods){ //获取方法的修饰符 System.out.println(Modifier.toString(m.getModifiers())); //获取方法的返回类型 System.out.println(m.getReturnType().getSimpleName()); //获取方法的名字 System.out.println(m.getName()); System.out.println("方法参数个数:"+m.getParameterCount()); //获取方法的参数列表 Class[] paramArr=m.getParameterTypes(); //输出方法的参数列表 System.out.println("\t"+Arrays.toString(paramArr)); //获取方法所抛出的异常类型 Class[] exceptionArr=m.getExceptionTypes(); System.out.println("方法抛出异常个数:"+exceptionArr.length); //输出方法所抛出的异常列表 System.out.println("\t"+Arrays.toString(exceptionArr)); System.out.println("-------------------------------"); }
获取类中声明的构造器
- java.lang.reflect.Constructor
- 获取当前类中的public构造器
- public Constructor<?>[] getConstructors()
- public Constructor getConstructor(Class<?>… parameterTypes)
- 获取当前类中的所有构造器,包含私有的
- public Constructor<?>[] getDeclaredConstructors()
- public Constructor getDeclaredConstructor(Class<?>… parameterTypes)
Student stu=newStudent();Class c=stu.getClass();//获取类中所有的public构造器Constructor[] constructors=c.getConstructors();for(Constructor constructor : constructors){ //构造器的修饰符 System.out.println(Modifier.toString(constructor.getModifiers())); //构造器的名字 System.out.println(constructor.getName()); //构造器的参数列表 Class[] paramList=constructor.getParameterTypes(); System.out.println(java.util.Arrays.toString(paramList)); //构造器的抛出异常 Class[] exceptionList=constructor.getExceptionTypes(); System.out.println(java.util.Arrays.toString(exceptionList)); System.out.println("-----------------------------"); }
5. 反射操作属性
Student stu=new Student();Class c=stu.getClass();//获取类中名字叫name的属性Field f1=c.getDeclaredField("name");//设置私有属性可以被访问,否则报错f1.setAccessible(true);//用反射的方式,给指定对象的name属性赋值//相当于之前的stu.name = "tom";f1.set(stu,"tom");//用反射的方式,获取这个属性的值//相当于之前的stu.nameSystem.out.println(f1.get(stu));System.out.println("----------------------");//获取类中名字叫age的属性Field f2=c.getDeclaredField("age");//用反射的方式,给指定对象的age属性赋值//相当于之前的stu.age = 20;f2.set(stu,20);//用反射的方式,获取这个属性的值//相当于之前的stu.ageSystem.out.println(f2.get(stu));System.out.println("----------------------");//获取类中名字叫num的属性Field f3=c.getDeclaredField("num");//用反射的方式,给静态属性num赋值,不需要对象//相当于之前的Student.num = 99;f3.set(null,99);//用反射的方式,获取这个静态属性的值,不需要对象//相当于之前的Student.numSystem.out.println(f3.get(null));
6. 反射操作方法
Student stu=new Student();Classc=stu.getClass();//获取类中的toString方法,没有参数,这是从父类继承的方法Method m1=c.getMethod("toString", null);//反射的方式,调用stu对象中的这个方法,没有参数,并接收执行结果//相当于之前的:Object result = stu.toString();Object result=m1.invoke(stu,null);//输出执行结果System.out.println(result);System.out.println("-------------------");//获取类中的sayHello方法,需要一个String类型的参数,这是自己定义的方法Method m2=c.getMethod("sayHello", String.class);//反射的方式,调用stu对象中的这个方法,参数是"tom",并接收执行结果//相当于之前的:Object result = stu.sayHello("tom");result=m2.invoke(stu,"tom");//输出执行结果System.out.println(result);
7. 反射创建运行时类对象
-
反射调用类中无参构造器创建对象
Class c=Student.class;//默认调用类中的无参构造器来创建对象//相当于之前的:Object obj = new Student();Object obj=c.newInstance();System.out.println(obj);
-
反射调用类中有参构造器创建对象
Class c = Student.class;//获取类中的俩参构造器Constructor constructor=c.getConstructor(String.class, int.class);//调用有参构造器创建对象,并传入对应的参数值//相当于之前的:Object obj = new Student("tom",20);Object obj = constructor.newInstance("tom",20);System.out.println(obj);
8. 反射获取注解
-
获取Service类上面的【所有】注解类型
Class<?> c=Service.class;//获取该类上面的所有注解Annotation[] annotations=c.getAnnotations();
-
获取Service类上面的【指定】注解类型
Class<?> c=Service.class;//获取类上指定类型的注解,没有则返回nullTest t=c.getAnnotation(Test.class);//判断类上面是否使用了指定注解System.out.println(c.isAnnotationPresent(Test.class));
-
获取方法上的注解,以及注解的属性值
Class<?> c=Service.class;//获取类中所有声明的方法Method[] methods=c.getDeclaredMethods();for(Method m : methods){ System.out.println(m.getName()); //判断当前方法上是否使用了Role注解 if(m.isAnnotationPresent(Role.class)){ //获取方法上的Role注解 Role role=m.getAnnotation(Role.class); //获取注解中的属性值 String name=role.name(); String value=role.value(); System.out.println("\tname="+name); System.out.println("\tvalue="+value); } System.out.println(); }
9. 反射操作泛型
- Java采用泛型擦除的机制来引入泛型 , Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题 , 但是 , 一旦编译完成 , 所有和泛型有关的类型全部擦除
- 为了通过反射操作这些类型 , Java新增了 ParameterizedType , GenericArrayType , TypeVariable和 WildcardType 几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型.
- ParameterizedType : 表示一种参数化类型,比如Collection
- GenericArrayType : 表示一种元素类型是参数化类型或者类型变量的数组类型
- TypeVariable : 是各种类型变量的公共父接口WildcardType : 代表一种通配符类型表达式
10. 静态 VS 动态语言
- 动态语言
- 是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
- 主要动态语言:Object-C、C#、JavaScript、PHP、Python等
- 静态语言
- 与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。
- Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制获得类似动态语言的特性。Java的动态性让编程的时候更加灵活
十三、网络编程
1. 软件结构
- Client/Server(C/S结构),表示客户端/服务器的软件结构,
- 例如QQ、微信、网盘客户端等,只要是需要我们下载安装,并且和服务器通信的这一类软件,都属于C/S的软件结构。
- Browser/Server(B/S结构),表示浏览器/服务器的软件结构,
- 例如淘宝网、京东商城等,只要是需要使用浏览器,并且和服务器通信的这一类软件,都属于B/S的软件结构
- C/S和B/S各有优势:
- C/S在图形的表现能力上以及运行的速度上肯定是强于B/S的
- C/S/需要运行专门的客户端,并且它不能跨平台,用c++在windows下写的程序肯定是不能在linux下运行
- B/S不需要专门的客户端,只要系统中安装了浏览器即可访问,方便用户的使用。
- B/S是基于网页语言的、与操作系统无关,所以跨平台也是它的优势
2. 通信要素1:IP和端口号
IP地址
- 唯一的标识Internet 上的计算机(通信实体)
- 本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost
- IP地址分类方式1:IPV4和IPV6
- IPV4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已经用尽。以点分十进制表示,如192.168.0.1
- IPV6:128位(16个字节),写成8个无符号整数,每个整数用四个十六进制位表示,数之间用冒号(:)分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
- IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)。
- 192.168.开头的就是私有址址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用
- 特点:不易记忆
- Internet上的主机有两种方式表示地址:
- 域名(hostName):www.atguigu.com
- IP地址(hostAddress):202.108.35.210
- 域名解析
- 域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成IP地址,这样才能和主机建立连接。
//InetAdress类//InetAddress类没有提供公共的构造器,而是提供了如下几个静态方法来获取InetAddress实例public static InetAddress getLocalHost();public static InetAddress getByName(String host);//InetAddress提供了如下几个常用的方法public String getHostAddress():返回IP 地址字符串(以文本表现形式)。public String getHostName():获取此IP 地址的主机名public boolean isReachable(int timeout):测试是否可以达到该地址
端口号
- 标识正在计算机上运行的进程(程序)
- 不同的进程有不同的端口号
- 被规定为一个16 位的整数0~65535。
- 端口分类:
- 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
- 注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。+ 动态/私有端口:49152~65535。
网络套接字
- 端口号与IP地址的组合得出一个网络套接字:Socket
3. 通信要素2:网络协议
- OSI参考模型:模型过于理想化,未能在因特网上进行广泛推广
- TCP/IP参考模型(或TCP/IP协议):事实上的国际标准
- TCP协议:
- 使用TCP协议前,须先建立TCP连接,形成传输数据通道 》 传输前,采用“三次握手”方式,点对点通信,是可靠的
- TCP协议进行通信的两个应用进程:客户端、服务端。
- 在连接中可进行大数据量的传输
- 传输完毕,需释放已建立的连接,效率低
- UDP协议:
- 将数据、源、目的封装成数据包,不需要建立连接
- 每个数据报的大小限制在64K内
- 发送不管对方是否准备好,接收方收到也不确认,故是不可靠》可以广播发送
- 发送数据结束时无需释放资源,开销小,速度快
4. TCP网络编程
概述
- 在TCP通信协议下,能实现两台计算机之间的数据交互,并且它们要严格区分客户端(Client)与服务端(Server)
- 客户端和服务端通信的步骤:
- 服务端先进行启动,并占用一个指定的端口号,等待客户端的连接
- 客户端主动发起服务端的连接,在连接成功之后,就可以进行数据发送了
- 注意,在整个过程中,服务端不能主动连接客户端,必须由客户端先行发起连接才行
- 在java中,对于这样基于TCP协议下连接通信的客户端和服务端,分别进行了抽象:
- java.net.Socket类表示客户端
- java.net.ServerSocket类表示服务端
- 使用Socket和ServerSocket进行的编程,也称为套接字编程。
5. UDP网络编程
概述
- 在UDP通信协议下,两台计算机之间进行数据交互,并不需要先建立连接,客户端直接往指定的IP和端口号上发送数据即可,但是它并不能保证数据一定能让对方收到。
- UDP编程中使用到的俩个类,客户端和服务器端都使用这个俩类
- java.net.DatagramSocket负责接收和发送数据
- java.net.DatagramPacket负责封装要发送的数据和接收到的数据
- 注意,UDP协议下的编程,作为扩展和了解即可
- 注意,即使不启动服务器,客户端也可以发送数据,因为UDP不提前建立连接,也不保证数据会让对方收到
6. URI和URL
- URI(uniform resource identifier),统一资源标识符,用来唯一的标识一个资源。
- URL(uniform resource locator),统一资源定位符,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何定位这个资源。
- 例如,http://127.0.0.1/hello,这就是一个URL,它不仅标识了一个资源,还能定位这个资源。
- 例如,/hello ,这就是一个URI,它只是标识了一个资源
- java.net.URL可以表示一个URL地址,使用URL对象打开一个连接后,可以获取这个网络资源返回的内容
public static void main(String[] args){ try { URLurl =newURL("https://www.jd.com"); //打开这个URL的连接,强制为HttpURL的连接对象 HttpURLConnection httpConn = (HttpURLConnection) url.openConnection(); //设置请求方式 httpConn.setRequestMethod("GET"); //模拟浏览器发送的请求的情况 httpConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0;Win64;x64; rv:79.0) Gecko/20100101 Firefox/79.0"); //获取这个URL连接的输入流,准备读取数据 InputStream is = httpConn.getInputstream(); //字节流转换为字符流 Reader in =new InputStreamReader(is); char[] cbuf = new char[1024]; int len=-1; while ((len = in.read(cbuf))!=-1){ System.out. print(new String(cbuf,0,len)); } catch (IOException e){ e.printStackTrace();
MySql-----------------
数据库基本操作
update user set password=password('123456')where user='root';-- 修改密码flush privileges;-- 刷新数据库show databases;-- 显示所有数据库use dbname;-- 打开某个数据库show tables;-- 显示数据库mysql中所有的表describe user;-- 显示表mysql数据库中user表的列信息create databasename;-- 创建数据库use databasename;-- 选择数据库exit;-- 退出Mysql?-- 命令关键词:寻求帮助-- 表示注释
- MySQL有两种常用的引擎类型:MyISAM和InnoDB。目前只有InnoDB引擎类型支持外键约束
- 反引号用于区别mysql保留字与普通字符而引入的
create database db_test character set utf8;
create table tb_emp6(id int(11) primary key,name varchar(25) not null comment '姓名',-- comment相当于文档注释,查看创建语句时可以看到deptid int(11),salary float)engine innodb charset utf8;
设计模式--------------
一、七大原则
单一职责原则
-
对类来说的,即一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2。当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2
-
1)降低类的复杂度,一个类只负责一项职责。
-
2)提高类的可读性,可维护性
-
3)降低变更引起的风险
-
4)通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;
只有类中方法数量足够少,可以在方法级别保持单一职责原则
接口隔离原则
接口隔离原则(Interface Segregation Principle, ISP)表明客户端不应该被强迫实现一些他们不会使用的接口,应该把胖接口中的方法分组,然后用多个接口替代它,每个接口服务于一个子模块。