Java基础
目录
JDK JRE JVM
JRE:java运行环境,运行最小的单位
JDK:java开发工具包-开发最小的单位
JVM:负责加载并运行对应的字节码文件。class
运行过程:我们编写的源文件都是以.java为后缀,通过编译生成.class文件,交给JVM虚拟机来执行
跨平台:只要在不同的操作系统上安装对应的虚拟机,就可以实现跨平台。一份代码,处处运行
基础语法
八大基本类型速查表
基础语法规则
关键字:
- 50个全小写的单子在Java中有特殊含义,还包含两个保留字:const goto
标识符
- 由字母,数字,下划线,美元符号组成,不能以数字开头,区分大小写,关键字不可以当作标识符
- 类名:Upper驼峰命名法,每个单词的首字母都要大写
- 方法名:Lower驼峰命名:除了第一个单词首字母小写,其他的单子首字母都要大写,见名知意
注释
- 单行注释,多行注释,文档注释
字面值规则
- 整数类型字面值类型是int,浮点数字面值类型是double。
- 三种字面值后缀:D,L,F
- 三种字面值前缀:0b-2进制,0-8进制,0x-16进制
运算规则
- 运算结果的数据类型与参与运算的最大类型保持一致 int+int->int double/int->double
- 整数运算会出现溢出的现象,一旦溢出,数据就不正确了
- 整数运算会出现溢出的现象,一旦溢出,数据就不正确了
- 浮点数的特殊值:Infinity NaN
- 浮点数运算不精确的问题
类型转换
- 口诀:小转大,直接转 大转小,强制转 浮变整,小数没
- 注意:布尔类型不参与类型转换
- 注意:基本类型之间能否转换,不取决于字节数,字节数只能做参考,取决于类型的取值范围
- 注意:我们这里所说的是基本类型之间的转换,引用类型之间的转换取决于是否有继承关系比如:你可以说小猫是小动物,但是不能说小猫是小汽车,不然后面的这种错误的情况会报:类型转换异常
变量
成员变量
- 类里方法外,类消失,成员变量才会消失
- 成员变量有自己的默认值,可以不手动赋值
局部变量
- 方法里/局部代码块里,当局部代码结束,局部变量也随之释放
- 局部变量使用的时候必须赋值
• 声明的时候赋值 Cat cat=new Cat();
• 先声明再赋值 Cat cat;cat=new Cat();
• 注意:基本类型保存的是值,引用类型保存的是地址值
变量的就近原则
- 离谁近,使用谁
• 如果想指定本类成员变量,需要使用this变量名指定
• 如果想使用父类成员变量,需要使用super.变量名指定
运算符
普通四则运算符
- + - * / ,普通的四则运算,并不能直接改变变量本身的值,除非 i = i*10+8
自增自减运算符
- 可以改变变量本身的值
- 前缀式: 符号在前,先改变变量本身的值(+1/-1),再使用(打印/参与运算…)
- 后缀式: 符号在后,先使用(打印/参与运算…),再改变变量本身的值(+1/-1)
- 注意:不管是前缀式还是后缀式,一定是会改变变量本身的值,区别在于执行的时机不同
比较运算符
- 比较运算符最终的结果是布尔类型的
逻辑运算符
- 双与/短路与/&& :判断逻辑与&一致,增加了短路的功能全真才真,有假则假
- 双或/短路或/| | :判断逻辑与|一致,增加了短路的功能全假才假,有真则真
- 注意:我们这里所说的短路,是指在某些情况下,表达式后半部分就不用计算了,因为我们已经知道了结果,也就是被短路了,短路可以提高程序的性能,但是短路不一定会用到
三目运算符
- 1 ? 2 : 3; 1是表达式,1真取2,1假取3
位运算符
- 主要参与的是二进制的运算
- ^异或:相同为0 不同为1
- ~ 非: 非0为1,非1为0
取余%
- 取余 % 6%4=2 6%3=0(余数为0表示整除)
双等==的解析
- String s/s2="song"
• 在编译String s1=“song"的时候其实是jvm在常量池中创建了一个内容为"song"的地址值,然后让s1去指向"song”,而不是把"song"直接赋值,这样s1和s2的地址值都是常量池中"song"的地址值,所以通过双等号的运算结果是true。
- String s2=new String("song")
• 执行String s2=new String(“song”)的时候,要记住每new一次就会出现一个新的对象,所以这种情况是直接在堆内存中开辟了一块新的空间去储存"song",所以此时s1和s2的地址值是不一样的,自然==的结果就为false。
流程控制
分支结构
- 单分支
• if( ){ }
- 多分枝
• if( ){ }else{ }
- 嵌套分支
• if( ){ }else if( ){ }else if( ){ }else{ }
选择结构
• 小括号中变量支持的类型:byte short char int String
• 注意: 如果配置了default默认选项,而且没有任何的case被匹配到,就会执行default后的操作
• case的个数 是否加break 是否加default全部都是可选的,根据自己的具体业务做决定
• 小括号中变量的类型必须与case后value的类型一致
• 执行顺序:先拿着变量的值,依次与每个case后的值做比较,如果相等,就执行case后的语句若这个case后没有break,就会继续向下执行下一个case,如果一直没有遇到break,就会发生穿透现象,包括default
循环结构
- for循环
•
• 注意:从哪开始 到哪结束 如何变化
• 注意:外层循环控制的是行数,内层循环控制的是列数
• 注意:外层循环控制的是轮数,内层循环控制的是在这一轮中执行的次数
- 高效for循环
•
• 优点:写法简单,效率高
• 缺点:只能从头到尾的遍历数据,不能进行步长的选择
- while循环
• 使用while通常用来写死循环,但是注意死循环必须要设置出口!!!
- do-while循环
• do-while循环一定会执行一次,然后再判断,如果符合条件,再执行后面的循环
关键字
- return
• return关键字除了可以帮我们返回方法的返回值以外 还可以直接结束当前的方法,如果遇到了return,本方法会直接结束
- break
• 结束当前,防止发生穿透现象
- defaut
• 保底选项
方法
格式: 修饰符 返回值类型 方法名(参数列表){ 方法体 }
如何确定我们要调用哪个方法呢?方法名+参数列表
如果一个方法被调用,就会执行这个方法里的内容,执行完毕,再返回到调用的位置,从哪里调用,就返回到哪里如果这个方法有返回值,我们有两种选择
- 选择只执行功能,不接收返回值,不再继续使用这个方法的结果
- 选择只执行功能,不接收返回值,不再继续使用这个方法的结果
方法的重载
- 定义:在同一个类出现多个方法名相同但参数列表不同的方法
- 注意:方法是否构成重载,取决于参数列表的个数与参数的类型,与参数名称无关
- 规则
• 被重载的方法必须改变参数列表
• 被重写的方法可以改变返回值类型
• 被重载的方法可以改变访问修饰符
• 被重载的方法可以声明新的或更深的检查异常
方法的重写
- 方法的重写要在继承的基础上,子类对父类的方法不满意实现功能的修改与拓展,也称方法的覆盖/覆写
- 重写遵循的规则
• 两同:方法名相同,参数列表相同
• 两小:子类的返回值类型<=父类的返回值类型,子类抛出的异常<=父类的抛出的异常
• 一大:子类的方法修饰符权限>=父类的方法修饰符权限
重写与重载的区别
- 重载是在同一个类中,重写在继承关系中
- 重写要求(方法名相同,参数列表不同,方法返回值任意),重载(方法名相同,参数列表相同,返回值类型相同)
- 重写访问修饰符任意,重载:子类的访问修饰符要大于父类父类的范围
方法的传值
- 如果方法的参数类型是基本类型,传入的是实际的字面值,如果是引用类型,传入的是地址值
- 实参:实际意义上的参数,比如我们的局部变量,比如成员变量,比如调用方法时传入的数字
- 形参:形式意义上的参数,比如方法参数列表的参数名,光看参数名是无法确定这个变量的值是多少的
拓展:方法的递归
数组
创建方式
- 静态创建:int[] a={1,2,3}
- 静态创建:int[] a=new int[]{1,2,3}
- 动态创建:int[] a=new int[5]
- 不管是什么样的创建方式,都需要指定数组的类型与长度
规则
- 数组长度:数组一旦创建,长度不可改变,长度指的是数组中元素的个数a.length,并且数组的长度允许为0:[ ]
- 我们可以通过数组的下标来操作数组中的元素
• 数组下标从0开始,最大下标是数组的长度-1,如果访问到了不是这个数组的下标,会出现数组下标越界异常
数组的创建过程
- 在内存中开辟一块连续的内存空间,长度为5
- 给数组完成初始化的过程,给每个位置上的元素赋予对应类型的默认值,此处是0
- 数组完成初始化后,会给数组分配一个唯一的地址值
- 把数组的地址值交给引用类型int[]的变量a来保存
- 后面可以根据下标给数组的每个元素赋值,比如1,2,3,4,5对应的下标是a[0]...a[4]
数组的工具类Arrays
- toString(数组名) : 除了char类型的数组以外,其他类型的数组想要查看具体元素,需要使用本方法,否则打印的是地址值
- sort(数组名) :对数组中的元素进行排序
- copyOf(原数组名,新数组的长度) : 用来实现数组的复制 扩容 缩容
冒泡排序
- 外层循环控制比较的轮数,所以循环变量从1到n-1轮,代表的是轮数
- 内层循环控制的是这一轮中比较的次数,而且是数组中两个相邻元素的比较,所以循环变量代表的是数组的下标
面向对象OOP
类与对象
定义类通过关键字class来定义,类是一类事物的抽象,它是抽象的,它是模板
创建对象通过new关键字触发构造函数生成,对象是根据类创建出来的具体的内容
一个类可以创建出多个对象,对象是根据类的设计来创建的,所以对象具有类的所有属性与功能
对象之间是相互独立的,互不影响。我们把创建对象也称作“实例化”
面向对象的三大特性
封装
- 前提:为了保证数据的安全,也为了程序的使用者能够按照我们预先设计好的方式来使用资源
- 类
• 类中包含成员变量
• 实例变量
• 实例变量依赖于对象,需要通过对象调用
• 静态变量
• 静态变量属于类,依赖于类,需要通过类名点调用
- 方法
• 用private修饰方法,被修饰的方法只能在本类中使用,所以我们在本类的公共方法里调用这个私有方法
• 外界如果想要使用这个私有方法的功能,只需要调用这个公共方法就可以了
• 减少代码的重复,有利于代码的维护,有利于团队协作
• 方法可以被反复调用多次
- 用private修饰我们的属性
• 为属性提供对应的getXxx()【获取属性值】与setXxx()【设置属性值】
- 方法权限修饰符速查表
•
继承
- 概念
• 继承是从已有的类中派生出新的类,新类能吸收已有类的数据属性和行为,并扩展新的能力.
- 特点
• 继承可以实现程序的复用性,减少代码的冗余
• extends关键字建立子类与父类的继承关系
• 继承是可以传递的
• Java的类是单继承的
• 父类的私有成员由于私有限制访问,所以子类不能使用父类的私有资
• 继承多用于功能的修改,子类可以在拥有父类功能的同时,进行功能拓展
• 继承相当于子类把父类的功能复制了一份,包括私有资源
• 注意:虽然私有资源继承了,但是私有资源不可用,原因是被private限制了访问,私有资源只能在本类使用
• 注意:构造方法不能继承,原因是:构造方法要求名字是本类的类名,我们不能在子类中出现父类名字的构造方法
- this&super
• this代表的是本类,super代表的是父类
• 当本类的成员变量与局部变量同名时,我们可以通过this.变量名指定本类的成员变量当父类的成员变量与子类的变量同名时,我们可以通过super.变量名指定父类的成员变量
• 注意
• this调用构造方法或者super调用构造方法,都必须出现在构造方法的第一行
• super的使用前提是继承,没有父子类关系,就没有super
• 如果父类没有无参构造,需要手动在子类构造方法的第一行调用其他的含参构造
• 构造函数的调用只有这一种方式,或者创建对象时被动触发,不能在外面自己主动调用
• 构造函数直接不能互相调用,否则会死循环
•
多态
- 前提
• 为了忽略子类型之间的差异,统一看作父类类型,写出更加通用的代码
- 概念
• 在同一时刻,同一个对象,代表的类型不同,拥有多种形态
- 要求
• 继承 + 重写
- 口诀
• 父类引用指向子类对象:父类型的引用类型变量保存的是子类对象的地址值
• 编译看左边,运行看右边:父类中定义的功能,子类才能使用,否则报错多态中,方法的定义看的是父类的,方法的实现看的是子类重写后的功能
- 多态中资源的使用
• 成员变量:使用的是父类的
• 成员方法:对于方法的定义看的都是父类的,对于方法实现,重写后使用的是子类的
• 静态资源:静态资源属于类资源,不存在重写的概念,在哪个类中定义的,就属于哪个类
- 向上造型与向下造型
• 这两种都属于多态,只不过是多态的两种不同的表现形式
• 向上造型【最常用】可以把不同的子类型都看作是父类型,比如Parent p = new Child();
• 比如:花木兰替父从军,被看作是父类型,并且花木兰在从军的时候,不能使用自己的特有功能,比如化妆
• 向下造型前提:必须得先向上造型,才能向下造型子类的引用指向子类的对象,但是这个子类对象之前被看作是父类类型,所以需要强制类型转换Parent p = new Child(); 然后:Child c = (Child) p;
• 比如:花木兰已经替她爸打完仗了,想回家织布,那么这个时候,一直被看作是父类型的花木兰必须经历“解甲归田”【强制类型转换】这个过程,才能重新被看作成子类类型,使用子类的特有功能为什么有向下造型:之前被看作是父类类型的子类对象,想使用子类的特有功能,那就需要向下造型
构造函数
格式
- 修饰符 类名当做方法名(){ } 注意:与类同名且没有返回值类型
作用
- 用于创建对象,每次new对象时,都会触发对应的构造函数,new几次,触发几
特点
- 一个类中默认存在无参构造,如果这个构造不被覆盖的话,我们可以不传参数,直接创建这个类的对象
- 如果这个类中提供了其他的构造函数,默认的无参构造会被覆盖,所以记得手动添加无参构
- 构造方法也存在重载的现象:无参构造 含参构造 全参构造【创建对象+给所有的属性赋值】
抽象类
特点
- 抽象类和抽象方法必须要用abstract修饰
- 抽象类不可被实例化
• 因为它是存在于一种概念而不非具体。
• 需要依靠子类采用向上转型的方式处理;
- 抽象类中不一定有抽象方法,但有抽象方法的必定是抽象类
- 抽象的子类
• 抽象类:”躺平“不实现父类的方法,继续当一个抽象类
• 具体类:必须实现父类的所有的抽象方法
- 既有变量,也有常量
- 有构造方法
- 严格来说,抽象类不可以直接实例化,但不代表不可以实例化,抽象类的引用必须指向继承它的子类的实例,所以常用于多态
- 面向抽象编程:后天重构的结果
思考
- 既然抽象类不可被实例化,那么要构造方法有什么用?
• 用于子类访问父类数据的初始化
• 子类对象实例化的时候,依然满足先执行父类构造,再执行子类构造的顺序
- 如果一个类没有抽象方法,却被定义为抽象了,有何意义?
• 为了不让创建对象
- abstract不能和哪些关键字共存?
• final 冲突
• 因为抽象类必须有子类,final修饰的类不能有子类
• private 冲突
• 因为被private不能被子类继承,子类便无法实现该方法
• static 无意义
接口
规则
- 接口不是类,定义接口关键字:interface
- 如果一个类想要实现接口定义的规则,需要使用implments与接口建立联系
• 如果有任何一个抽象方法没有实现,那么这个类就是抽象子类
- Jave8中接口中的所有方法都是抽象方法
- 接口中都是静态常量,没有普通变量,会自动拼接public static final
- 接口中的方法可以简写,会自动拼接public abstract
- 接口不可以被实例化
- 接口没有构造方法,实现类调用的是父类的构造方法,如果没有明确的父类,那么调用的就是objec的
- 接口更多的时规则的制定者,不做具体的实现
- 接口降低了项目的耦合性,更加方便项目的维护与拓展
- 接口时先天设计的结果,这样可以省去后续的多次重构,节省资源
接口于类的关系
- 类与类的关系
• Java的类只支持单继承,类与类之间就是继承关系,并且一个子类只能有一个父类
- 接口与接口的关系
• Java的接口不做限制,可以多继承
• interface Inter1 extends Inter2,Inter3{ } – Inter1 是子接口 Inter2 和 Inter3 都是父接口注意:如果是情况2的话,接口1的实现类需要实现这三个接口(Inter1,2,3)的所有抽象方法
- 接口与类的关系
• Java中的类对接口是多实现的,一个类可以实现多个接口
• class InterImpl implements Inter1,Inter2{}
接口与抽象类的区别
- 接口是一种interface定义的类型,抽象类是用class定义的类型
- 接口中的方法都是抽象方法,抽象类不做限制
- 接口中的都是静态常量,抽象类中可以有普通的成员变量
- 接口没有构造方法,不可被实例化,抽象类有构造方法,但也不可以被实例化
- 接口是先天设计的结果,抽象类是后天重构的结果
- 接口可以多实现,抽象类只能单继承
代码块
静态代码块 static{ }
- 位置
• 类里方法外
- 执行时机
• 随着类的加载而加载,最先加载到内存,优先于对象进行加载,直到类小消失,它才会消失
- 作用
• 一般用来加载那些只需要加载一次并且第一时间就需要加载资源,称作:初始化
构造代码块{ }
- 位置
• 类里方法外
- 执行时机
• 创建几次执行几次,并且优先执行于构造方法
- 作用
• 用于提取所有构造方法的共性功能
局部代码块{}
- 位置
• 方法里
- 执行时机
• 当其所处的方法被调用时就会触发
- 作用
• 为了限制变量的作用范围,出了局部代码块就会失效
static
被static修饰的资源统称为静态资源,可以用来修饰方法,类,属性
静态资源属于类资源,随着类的加载而加载,优先于对象加载,只加载一次,常用作初始化
静态资源可以不通过对象直接调用,不需要创建对象
静态资源只有一份,被全局所有对象共享
静态资源的调用关系:静态资源只能调用静态资源
静态资源是优先于对象的,所以不能被this和super所调用
Final
被Final修饰的类是最终类,成为叶子节点,不可被继承
final表示最终
被final修饰方法是最终实现,不能被重写
被final修饰的是常量,值不可以被改变,常量定义是必须负责
内部类
定义
- 我们可以把内部类看作外部类的一种特殊的资源
- 内部类可以调用外部类的所有资源,包括私有资源
- 如果外部类想要使用内部类的资源,则需要创建内部类对象才能使用
- 对象创建的普通方式
• Outer.Inner id=new Outer().new Inner()
成员内部类
- 位置:类里方法外
- 被private修饰的内部类在main()中是没有办法直接创建对象的,我们需要在外部类中创建公共的方法供外界调用,这个方法用来返回创建好的私有内部类对象
- 静态内部类可以不创建外部类对象,直接创建内部类对象,格式:Outer3.Inner3 oi = new Outer3.Inner3();
- 静态内部类中如果有静态方法,可以不创建对象,直接通过链式加载的方式调用:Outer3.Inner3.show2();//表示通过外部类名直接找到静态内部类,再找到静态方法
局部内部类
- 内部类不可以被private,public,static修饰
- 在外部类中不能创建内部类的使用
- 创建内部类的实例只能在包含他的方法中
- 内部类访问包含他的变量中必须用Final修饰
- 外部类不能访问局部内部类,只有在在方法体中访问局部内部类,且在局部内部类定义之后
- 局部内部类可以随意访问外部类,即使是私有资源
- 当内部类与外部类的成员变量和方法同名时,需要通过Outer.this来调用外部类的成员变量和方法
匿名内部类
- 位置:在可运行代码中,比如main
- 匿名内部类通常与匿名对象【没有名字的对象】一起使用
- new Inter1(){ 我这个大括号其实是一个匿名内部类,我来实现方法 }.eat();
- 如果只是想使用一次接口/抽象类的某个功能,可以使用匿名内部类匿名内部类+匿名对象的功能:创建实现类+实现方法+方法功能的一次调用【功能三合一】
API
基础API
object
- 所有类的超类,在Java中所有的类都间接或直接继承了Object
- 如果一个类没有明确指定父类,那么默认继承Object
- Object在java.long包下,不需要导包可以直接使用
- toString()
• 如果重写这个方法,那么打印出来的就是Object的默认实现,打印的时对象的地址值
• 如果重写以后,以重写的逻辑为准,比如String打印的是字符串的具体内容,ArrayList,打印的是[集合元素]
- hasCode()多用于返回对象对应的哈希码值
• 如果是一个对象重复调用这个方法,返回的是同一个哈希码值
• 如果是不通过的对象调用这个方法,返回的是对应的不同的哈希码值
- equals()比较当前对象与参数对象是否相等
• 重写前的默认实现比较的是对象的地址值
• 重写后比较的是具体的业务逻辑,比如String比较的是两个串的具体内容,自定义对象比较的是:类型和属性值
- equals()与hashCode()应该保持一致【要重写都重写】
• 解释:equals()底层默认实现比较的是==比较,地址值,重写后我们一般比较的是对象的类型+属性值
• hashCode()不同的对象生成的哈希码值不同,那么与equals()的逻辑不匹配,所以也应该重写
• 重写后,是根据对象的类型与属性值来生成哈希码值,这样二者就一致了
String
- 介绍
• String底层维护的是一个char[],而且String不可变,因为源码中的数组被final修饰了
- 创建方式
• char[] values={'a','b','c'}
• String a= new String(values);
• String a="abc"
• 这种方法有高效的效果,因为串存在堆中的常量池中,第二次调用就不要在创建了
- StringBuilder
• 特点
• StringBuilder是长度可变的字符串序列,在创建的时候会有一个长度为16的默认空间
• 当拼接字符串的是时候是在原对象的基础之上进行拼接,如果长度不够就扩容
• 创建方式
• StringBuilder sb=new StringBuilder(); 创建一个长度为16的StringBuilder对象
• StringBuilder sb = new StringBuilder(“abc”);//以指定字符串内容为“abc”的方式创建一个StringBuilder对象
• 优缺点
• 优点:在拼接的时候,就避免了拼接因拼接产生对象占用内存空间的问题,提高了程序的效率,使用的是append();
• 优点:每个对象都默认的缓冲区,当字符串大小没有超过容量时,不会分配新的容量,当超过容量时,会自动扩容
• 缺点对于字符串的操作不太方便
- String和StringBuffer和StringBuilder的区别
• String的值是不可变的,导致每次对String的操作都会产生一个新的对象,非常浪费内存空间
• 初始String值为“hello”,然后在这个字符串后面加上新的字符串“world”,这个过程是需要重新在栈堆内存中开辟内存空间的,最终得到了“hello world”字符串也相应的需要开辟内存空间,这样短短的两个字符串,却需要开辟三次内存空间,极大的浪费空间
• StringBuffer字符串变量、StringBuilder字符串变量
• StringBuffer 与 StringBuilder 中的方法和功能完全是等价的
• 和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
• StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
• 由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
• 区别
•
• 小结
• 多线程操作字符串缓冲区下操作大量数据 StringBuffer
• 单线程操作字符串缓冲区下操作大量数据StringBuilder
包装类
概念
- 基本类型只存值,没有丰富的功能,所以包装类型是对基本类型的包装,并提供了更多的方法,所以包装类型的对象是引用类型的对象
创建方式
- Integer i1 = Integer.valueOf();数据只要在-128~127有一个高效的效果
- Integer i2 = new Integer(4);没有高效的效果,只是创建了一个包装类的对象
自动装箱
- 编译器会自动把基本类型int5,包装成包装类型Integer
- 交给Integer类型的引用类型变量来保存
- 自动装底层发生的代码:Integer.valueOf(5)
- valueOf()的方向:int–>Integer
自动拆箱
- 编译器会自动把包装类型的i1拆掉”箱子“,变回基本类型的数据127
- 交给基本类型int的变量来保存
- 自动拆箱底层发生的代码:i1.intValue();
- intValue()的方向:Integer-> int
IO流
流的分类
- 字节输入流
• 抽象父类:InputStream 不能被实例化
• 子类:文件字节输入流FileInputStream
• 高效字节输入流BufferedInputStream
- 字节输出流
• 抽象父类:OutputStream
• 子类:文件字节输出流FileOutputStream
• 高效字节输出流:BufferedOutpuStream
- 字符输入流
• 抽象父类:Reader
• 子类:文件字符输入流FileReader
• 高效字符输入流:BufferrdFileReader
- 字符输出流
• 抽象父类:Writer
• 子类:文件字符输出流FileWriter
• 高效字符输出流:BufferedFileWriter
- 测试编码转换流
关于File的API
序列化与反序列化
- 作用
• 序列化与反序列化的作用就是对象的保存与传输
- 序列化
• 将内存中的对象通过序列化输出流到磁盘中(比如文件中)
• 使用的流是ObjectOutputStream【把数据写出到文件】
- 反序列化
• 将磁盘中的数据恢复成对象
• 用的流是ObjectInputStream【把之前写到文件里的数据读到程序中】
• 知识点
• 注意1:一个类的对象如果想被序列化,那么这个类必须实现可序列化接口
• 实现这个接口的目的是相当于给这个类做了一个标记,标记可以序列化
• 注意2:序列化时会自动生成一个UID,表示当前序列化输出的对象的版本信息
• 反序列化时会拿着当前的UID与之前序列化输出的UID做比较,一致,反序列化成功,不一致,报错
• 注意3: 所以,标准操作是一次序列化对应一次反序列化
• 如果目标对象所在的类没有做任何修改,一次序列化也可以对应多次反序列化(根本原因是UID没变)
集合
泛型
- 泛型不是指一种具体的类型,这里有个类型需要设置,那么后续具体需要什么类型,要看具体业务需要
- 泛型通常与集合一起使用,用来限制存入集合的元素类型,泛型具体设置成什么类型,那么这个集合也就只能存放这个类型的数据
- 泛型是一颗“语法糖”
• 泛型可以把报错的时机提前,在编译起检查集合的元素类型,只要不是泛型的类型就会报错,通不过编译
• 泛型只在编译时期起作用,编译通过后,说名语法符合规范,泛型就会被抛弃,编译生成的字节码文件没有泛型
- 泛型必须使用引用类型
- 泛型的方法:如果想要在方法上使用泛型就要在两处同时出现
• 一个是方法的参数列表中的参数类型
• 方法返回值类型前的泛型类型,表示这是一个泛型方法
Collection
- 集合的继承关系
•
- Collection是集合的跟接口
- Collection集合方法总结
List
- 特点
• List集合是有下标的
• List集合存放的数据是有序的
• List集合可以存放重复数据
- ArrayList
• 特点
• 1.是List的实现类
• 2.底层数据结构是数组,内存空间是有序的
• 3.可以通过下标进行操作
• 4.元素是有下标的,允许存放重复数据
• 5.增删慢,查询快[数据量较大的时候]
• 创建方式
- LinkedList
• 特点
• 1.是List的实现类
• 2.底层数据是链表,内存空间是不连续的
• 3.元素是有下标的,有序,可以存放重复数据
• 4.通常进行首位节点的操作比较多
• 5.增删快,查询慢[数据量较大时]
• 查询操作首位查询快
• 创建方式
Set
- 特点
• 1.Set集合中没有重复的数据
• 2.Set集合中的数据是无序的
• 3.Set集合可以存放null值,并且只能有一个
• 4.我们对象如果想去重,需要在自定义类中添加equals()和hashCode()
Map
- 特点
• 1.map集合的结构是:键值对、KEY与VALUE、Map.Entry<K,V>的映射关系
• 2.map中k的值不允许重复,如果重复对应的Value的值将会被覆盖
• 3.map没有迭代器,如果想要迭代要先转成set集合来迭代
- HashMap
• 1.Hashmap的结构是:数组+链表或者数组+红黑树的形式
• 2.HashMap的底层是Entry[]数组,初始容量为16,加载因子0.75f,约两倍扩容
• 3当存放数据时,会根据hash(key)%n算法来计算数据的存放位置,n就是数组的长度,其实也就是集合的容量
• 4.当计算到的位置之前没有存过数据的时候,会直接存放数据
• 5.当计算的位置,有数据时,会发生hash冲突/hash碰撞解决的办法就是采用链表的结构,在数组中指定位置处以后元素之后插入新的元素也就是说数组中的元素都是最早加入的节点
• 6.如果链表的长度>8并且数组长度>64时,链表会转为红黑树,当链表的长度<6时,会重新恢复成链表
异常
基础
- 异常层次结构中的根是Throwable
- Error:目前我们编码解决不了的问题
- Exception:异常
- 编译异常:未运行代码就报错了,强制要求处理
- 异常的解决方案
- 运行时异常RunTimeException:运行代码才报错,可以通过编译,不强制要求处理
- 向上抛出throws–交给别人解决,在方法定义的两个小括号之间throws,可抛出多个异常,用逗号隔开
- 不能直接把异常抛给main(),因为调用main()是JVM,没人解决了
- 注意:是否抛出异常取决于自己的业务,比如暂时不处理或者处理不了需要交给别人处理
- 捕获处理try-catch–自己解决
线程
串行,并行和并发
- CPU:电脑的核心处理器,类似于“大脑”
- 串行:是指同一时刻一个CPU只能处理一件事,类似于单车道
- 并行:相对来说资源比较充足,多个CPU可以同时处理不同的多件事,类似于多车道
- 并发:相对来说资源比较紧缺,多个进程同时抢占公共资源,比如多个进程抢占一个CPU
线程与进程的关系
- 线程是OS能够进行运算调度的最小单位
- 一个进程可以拥有多个线程,当然,也可以只拥有一个线程,只有一个线程的进程被称作单线程程序
- 注意:每个线程也有自己独立的内存空间,当然也有一部分公共的空间用于保存共享的数据
- 在宏观上,一个CPU看似可以同时处理多件事
- 在微观上,一个CPU同一时刻只能处理一件事
- 结论:线程的执行具有随机性,我们控制不了,是由OS底层的算法来决定的
线程有几种状态?它们是怎么转换的?
- 新建状态:new–申请PCB,进行资源的分配
- 就绪/可运行状态:万事俱备只欠CPU,其实是将创建好的线程对象加入到就绪队列中,等待OS选中,这个选择我们是控制不了的
- 执行/运行状态:就绪队列中的线程被OS选中了,正在执行
- 注意:只有就绪状态才能切换成执行状态
- 阻塞状态:线程在执行中遇到了问题:
- 锁阻塞、休眠阻塞、等待阻塞…问题解决后再加入到就绪队列中
- 终止状态:线程成功执行完毕,释放资源
- 线程的挂起:正在运行中的线程,由于CPU分配的时间片已经用完,所以需要冻结当前线程运行的状态与各项信息把它插入到就绪队列中,直到下次这个线程被调度执行时,重新恢复现场,继续执行
多线程 有几种实现方式?
- 继承Thread类 extends Thread
• 自定义一个多线程类用来继承Thread类
• 2)重写run()里的业务【这个业务是自定义的】
• 3)创建线程对象【子类对象】
• 4)通过刚刚创建好的自定义线程类对象调用start()
• 注意 ·
• 注意1:不能调用run(),因为这样调用只会把run()看作一个普通的方法,并不会以多线程的方式启动程序
• 而且调用start()时,底层JVM会自动调用run(),执行我们自定义的业务
• 注意2:我们除了可以调用默认的父类无参构造以外,还可以调用Thread(String name),给自定义的线程对象起名字,相当于super(name);
- 实现Runnable接口 implements Runnable
• 1)自定义一个类实现接口Runnable
• 2) 实现接口中唯一一个抽象方法run()
• 3) 创建接口实现类的对象,这个对象是作为我们的目标业务对象【因为这个自定义类中包含了我们的业务】
• 4)创建Thread类线程对象,调用的构造函数是Thread(Runnable target)
• 5)通过创建好的Thread类线程对象调用start(),以多线程的方式启动同一个业务target
• 注意点
• 注意1:由于Runnable是一个接口,无法创建对象,所以我们传入的目标业务类,也就是接口实现类的对象
• 注意2:只有调用start()才能把线程对象加入到就绪队列中,以多线程的方式启动,但是:
• 接口实现类与接口都没有这个start(),所以我们需要创建Thread类的对象来调用start(),并把接口实现类对象交给Thread(target);
• 大家可以理解成“抱大腿”,创建的是Thread类的线程对象,我们只需要把业务告诉Thread类的对象就好啦
- 实现Runnable相对于继承Thread的优势
• 1)耦合性不强,没有继承,后续仍然可以继承别的类
• 2)采用的是实现接口的方式,后续仍然可以实现别的接口
• 3)可以给所有的线程对象统一业务,业务是保持一致的
• 4)面向接口编程,有利于我们写出更加优雅的代码
- 创建线程池 Executors
• 1)创建线程池的工具类:Executors.newFixedThreadPool(int n);可以创建包含最多n个线程的线程池对象
• 2)创建好的线程池对象:ExecutorService
• 使用pool.excute()来讲线程池中的线程以多线程的方式启动,每次调用都会将一个线程对象加入到就绪队列之中
• 这个线程池对象负责: 新建/启动/关闭线程,而我们主要负责的是自定义的业务
• 注意:线程池是不关闭的,实现的效果就是线程池中线程对象的随取随用,这样就避免了频繁的创建与销毁线程,不会造成资源浪费
• 合理利用线程池可以拥有的优势:
• 1. 降低系统的资源消耗:减少系统创建与销毁线程对象的次数,每个线程都可以重复利用,执行多次任务
• 2. 提高响应速度:当任务到达时,任务可以不用等待线程创建就能立即执行
• 3. 提高线程的可管理性:可以根据系统的承受能力,调整线程池中线程的数目
• 防止因为创建多个线程消耗过多的内存导致服务器的崩溃
• 【每个线程大约需要1MB的内存,线程开的越多,消耗的内存也就越大,最后死机】
多线程数据的安全隐患及解决方案
- 问题
• 出现数据安全问题的原因:多线程程序 + 多个线程拥有共享数据 + 多条语句操作共享数据
- 同步和异步
• 同步
• 是多个线程抢占资源的效果,不排队,所以效率高,但是数据不安全
• 异步
• 每次只有一个线程独占资源,排队,所以效率低,但是安全,为了安全必要应该牺牲一部分资
- 解决方案:加锁synchronized同步关键字
• 同步方法
• 在方法的定义上添加synchronized[不常用]
• 同步代码块
• 格式
• synchronized(唯一的锁对象){可能会出现数据不安全问题的所有代码}
• 注意事项
• 注意1:锁对象必须唯一!!!
• 比如:如果是实现接口的方式,只需要创建一个接口实现类对象。而这个对象当做的是目标业务对象,类中定义的锁对象自然也只有一个
• 比如:如果是继承Thread类的方式,我们需要创建多个子类对象作为线程对象,那这个时候不同的线程对象间应该共享同一把锁,所以需要把锁设置为静态,被全局所有对象共享
• 建议,此种方式使用的锁对象是本类的字节码对象:类名.class
• 注意2:加锁时,同步代码块的范围需要考虑, 不能太大,太大效率太低;也不能太小,太小锁不住
• 注意3:加锁时,锁对象的类型不做限制,只要保证锁对象唯一即可
设计模式
概念
- 是一些前人总结出来的值得学习的编程“套路”,设计模式一共有23种
单例设计模式
- 设计理念
• 确保代码中本类的实例只有一个
- 设计思路
• 饿汉式
• 1)把本类的构造方法私有化–为了不让外界调用构造函数来创建对象
• 2)通过本类的构造方法创建对象,并把这个对象也私有化,为了防止外界调用
• 3)提供公共的全局访问点向外界返回本类的唯一的一个对象
• 注意事项
• 公共方法需要设置成静态–需要跳过对象,通过类名直接调用这个返回本类对象的公共方法
• 对象也需要设置成静态的–这个对象需要在静态方法中被返回,而静态只能调用静态
• 懒汉式
• 延迟加载思想
• 我们有的时候有些资源并不是需要第一时间就创建出来,所以需要延迟到需要时再创建,这样既可以提升性能,又可以节省资源
• 1)把本类的构造方法私有化–为了不让外界调用构造函数来创建对象
• 2)创建了一个本类类型的引用类型变量【这个变量后续用来保存创建出来的对象的地址值】
• 3)提供公共的全局访问点向外界返回本类的唯一的一个对象
• 注意事项
• 这个公共的方法里,需要做判断
• 如果引用类型的变量值为null,说明:之前没有创建过本类对象–创建后再赋值给引用类型变量,并把它返回
• 如果引用类型的变量值不为null,说明:
• 之前有创建过本类对象,这个引用类型变量保存就是地址值,本次不再新建对象,直接返回
反射
反射是框架设计的灵魂
- 使用前提条件,必须的到代表的字节码的Class,Class类用于表示.class文件(字节码文件)
概念
- JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
- 要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
- 反射就是把java类中的各种成分映射成一个个的Java对象
- Class对象的由来是将class文件读入内存,并为之创建一个Class对象。
反射的使用
注解
元注解5个
自定义注解
- 我们也可以根据自己的需求来定义个性化的注解
- 使用的是@interface 注解名来定义的,主要使用的就是上面的两个元注解
- 2. 除此之外,我们还可以给注解加功能,比如注解的属性:
- 格式:属性类型 属性名(); 比如:int age();
- 注意:定义了注解的普通属性以后,使用注解时必须给属性赋值,格式:@Rice(age=10)
- 如果给属性设置了默认值,那么使用注解时就不需要给属性赋值了,格式:int age() default 0;
- 3.我们还可以给注解添加特殊的属性value,注意这个属性名字必须是value,类型不作限制
- 注意:特殊属性如果不设置默认值,使用注解时也需要赋值,不过赋值可以简写,比如@Rice("apple")
- 特殊属性赋予默认值后,就可以直接使用注解了,赋予默认值的格式:String value() default "apple";
- 注意:如果有多个属性,并且都没有赋予默认值,那么使用注解时的格式:@Rice(value="apple",age=10)