Java Programming Language
Tips: Poor typesetting(no space between Chinese and non-Chinese words) content exists, please mainly refer to the mind map.
Java编程基础
第一章:内容介绍
第二章:Java概述
第三章:变量
第四章:运算符
第五章:程序控制结构
第六章:数组、排序和查找
第七章:面向对象编程(初级)
7.1 类与对象
-
引出
-
单个声明每一个变量不利于数据的管理
-
使用数组不利于将意义与数据对应,效率低
-
-
类与对象的关系
- 类是对象的抽象,对象是类的实例化
-
类与对象的区别和联系
-
类是抽象的、概念的,代表一类事物,比如人类、猫类…,即类是数据类型
-
对象是具体的,实际的,代表一个具体事物,即是实例
-
类是对象的模板,对象是类的一个个体,对应一个实例
-
-
对象在内存中的存在形式
-
属性/成员变量/字段
-
基本介绍
-
从概念或叫法上看:成员变量和属性、字段是一个意思。
-
属性是类的一个组成部分,一般是基本数据类型,也可以是引用类型
-
-
注意事项的细节说明
-
属性的定义语法同变量
-
属性的定义类型可以为任意类型,包含基本类型或引用类型
-
属性如果不赋值,会有默认值,规则和数组是一致的。
具体说,int, short, byte, long 是 0,float 和 double 是 0.0,char 是 \u0000,boolean 是 false,String 是 null
-
-
-
如何创建对象
-
先声明再创建
- 先声明,再分配内存
-
直接创建
- 声明和创建放在一起
-
-
如何访问属性
- 运用点(.)运算符
-
类和对象的内存分配机制
-
Java 的内存结构分析
-
栈
- 一般存放基本数据类型(局部变量)
-
堆
- 存放对象、数组等
-
方法区
- 存放常量池,类加载信息
-
-
Java创建对象的流程简单分析
-
先加载类信息(属性和方法信息,对一个类只会加载一次)
-
在堆中分配空间,进行默认初始化
-
把地址赋给标识符,标识符就指向对象
-
进行指定初始化(即在类定义时已经书写的初始化)
-
-
7.2 成员方法
-
基本介绍
- 在某些情况下,我们需要定义成员方法(简称方法)。比如人类,除了有一些属性外,人类还有一些行为比如:说话、吃饭、学习。这时就要用成员方法才能完成。
-
方法的调用机制
-
成员方法的好处
-
提高代码的复用性
-
可以将实现的细节封装起来,然后供其他用户来调用即可
-
-
成员方法的定义
-
形参列表
- 表示成员方法输入
-
返回数据类型
- 表示成员方法的输出(返回值)
-
方法主体
- 实现某一功能的代码块
-
return 语句(对于 void 类型不必要)
-
-
注意事项和使用细节
-
访问修饰符
- 不写默认是默认修饰符
-
返回数据类型
-
一个方法最多有一个返回值(需要多个返回值可以返回数组)
-
返回类型可以为任意类型,包括基本类型和引用类型(数组对象字符串)
-
如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 语句;而且要求返回值类型和 return 的值类型一致或兼容
-
如果方法是 void,则方法体中可以没有 return 语句,或者只写 return;
-
-
方法名
- 遵循驼峰命名法,最好见名知义,表达出该功能的意思即可
-
形参列表
-
一个方法可以有 0 个参数,也可以有多个参数,中间用逗号隔开
-
参数类型可以为任意类型,包含基本类型和引用类型
-
调用带参数的方法时,一定对应着参数列表传入下相同类型或兼容类型的参数
-
方法定义时的参数称为形式参数,简称形参‘方法调用时传入的参数称为实际参数,简称实参,实参和形参的类型要一致或兼容,个数、顺序必须一致
-
-
方法体
- 几乎无限制,但注意方法体内不能再定义方法
-
方法调用
-
同一个类中的方法:直接调用
-
跨类中的方法调用:需要通过对象名调用,即要先创建对象才能调用
-
跨类方法调用还与方法的访问修饰符相关,后面会具体讲到
-
-
7.3 成员方法传参机制
-
基本数据类型传参是值拷贝,不会影响主调方法中的变量
-
引用数据类型传参是地址拷贝,会影响主调方法中的引用数据类型变量
7.4 方法递归调用
-
基本介绍
- 递归就是方法自己调用自己
-
例子
-
阶乘运算
-
猴子吃桃问题
-
迷宫问题
-
汉诺塔问题
-
八皇后问题
-
7.5 方法重载
-
基本介绍
- Java 中允许同一个类中,多个同名方法的存在,但要求形参列表不一致
-
重载的好处
-
减轻了起名的麻烦
-
减轻了记名的麻烦
-
-
注意事项和使用细节
-
方法名:必须相同
-
形参列表:必须不同。简单地说,要一眼看上去就能通过形参列表区分两个方法,这样才构成重载
-
返回类型:无要求
理解:形参列表要求不同那么返回类型肯定会有不同,所以返回类型无要求
-
7.6 可变参数
-
基本概念
- java 允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。
-
基本语法
- 访问修饰符 返回类型 方法名(数据类型… 形参名)
-
注意事项和使用细节
-
可变参数的实参可以为 0 个或任意多个
-
可变参数的实参可以为数组
-
可变参数的本质就是数组
-
可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
-
一个形参列表中只能出现一个可变参数
-
7.7 作用域
-
基本使用
-
在 Java 编程中,主要的变量就是属性和局部变量
-
我们说的局部变量一般是指在成员方法中定义的变量
-
Java 中作用域的分类
-
全局变量
- 也就是属性,作用域为整个类体
-
局部变量
- 也就是除了属性之外的其他变量,作用域为定义它的代码块中
-
-
全局变量可以不赋值,直接使用,因为有默认值。局部变量必须赋值后。才能使用,因为没有默认值
-
-
注意事项和细节使用
-
属性和局部变量可以重名,访问时遵循就近原则
-
在同一个作用域中,比如同一个成员方法中,两个局部变量不能重名
-
属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。局部变量生命周期较短,伴随着它的代码块的执行而创建,伴随这代码块的结束而销毁,即在一次方法调用过程中。
-
作用域范围不同
-
全局变量 / 属性:可以被本类使用,或被其他类调用(通过对象调用)
-
局部变量:只能在本类中对应的方法中使用
-
-
修饰符不同
-
全局变量 / 属性可以加修饰符
-
局部变量不可以加修饰符
-
-
7.8 构造器
-
基本介绍
- 构造器,是类的一种特殊的方法,它的主要作用是完成对新对象的初始化
-
基本语法
- 访问修饰符 方法名(形参列表){
方法体;
}-
注意
-
构造器的修饰符可以是默认,也可以是 public protected private
-
构造器没有返回值
-
方法名和类名必须一样
-
参数列表和成员方法是一样的规则
-
构造器的调用,由系统完成
-
-
- 访问修饰符 方法名(形参列表){
-
特点
-
方法名和类名相同
-
没有返回值
-
在创建对象时,系统会自动的调用该类的构造器完成对象的初始化
-
-
注意事项和使用细节
-
一个类可以定义多个不同的构造器,即构造器重载
-
构造器名和类名要相同
-
构造器没有返回值
-
构造器是完成对象的初始化,并不是创建对象
-
在创建对象时,系统自动地调用该类的构造器
-
如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器
-
一旦定义了自己的构造器,默认的构造器就被覆盖了,就不能再使用默认的无参构造器,除非显式地定义一下
-
7.9 对象创建流程分析
-
流程
-
加载类信息,且只会加载一次
-
在堆中分配空间(地址)
-
完成对象初始化
-
默认初始化
-
显式(定义中的)初始化
-
构造器的初始化
-
-
把对象在堆中的地址返回给标识符
-
7.10 this 关键字
-
什么是 this
-
Java 虚拟机会给每个对象分配 this,代表当前对象
-
简单地说,哪个对象调用,this 就代表哪个对象
-
-
this 的注意事项和使用细节
-
this 关键字可以用来访问本类的属性、方法、构造器
-
this 用于区别当前类的属性和局部变量
-
访问成员方法的语法,this.方法名(参数列表)
-
访问构造器语法:this(参数列表)。注意只能在构造器中访问另外一个构造器,且必须放在该构造器的第一条语句处
-
this 不能在类定义的外部使用,只能在类定义的方法中使用
-
第八章:面向对象编程(中级)
8.1~8.3 IDE - IDEA
-
常用快捷键
-
ctrl + d 删除当前行
-
ctrl + alt + 下箭头 复制当前行
-
alt + / 补全代码
-
ctrl + / 多行添加或删除注释
-
alt + enter 导入该行需要的类
-
ctrl + alt + L 快速格式化代码
-
alt + r 快速运行程序
-
alt + insert 生成构造器
-
ctrl + h 查看一个类的层级关系
-
ctrl + b 光标放在一个方法上时可以定位到方法处
-
.var 自动分配变量名
-
-
模板 / 自定义模板
- file -> settings -> editor -> Live templates
8.4 包
-
包的三大作用
-
区分相同名字的类
-
当类很多时,可以很好地管理类
-
控制访问范围
-
-
包的基本语法
-
package 包名
-
说明
-
package 关键字,表示打包
-
包名中的(.)用于表示子目录
-
-
-
包的本质分析
- 包的本质,实际上就是创建不同的文件夹 / 目录来保存类文件
-
包的命名
-
命名规则
- 只能包含数字、字母、下划线和小圆点,但不能用数字开头,不能有关键字或保留字
-
命名规范
-
一般是小写字母 + 小圆点
-
一般命名为 com.公司名.项目名.业务模块名
-
-
-
常用的包
-
java.lang.*
- lang 包是基本包,默认导入,不需要再导入
-
java.util.*
- util 包是系统提供的工具包
-
java.net.*
- 网络包,用于网络开发
-
java.awt.*
- GUI 界面包,用于做可视化界面
-
-
如何导入包
-
语法
- import 包名
-
引入一个包的主要目的是要使用该包下的类
-
建议需要哪个类就导入哪个类,不建议使用 * 导入
-
-
注意事项和使用细节
-
package 的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句 package
-
import 指令位置放在 package 下面,类定义的前面,可以有多句且顺序无要求
-
8.5 访问修饰符
-
基本介绍
-
Java 提供四种访问控制修饰符号,用于控制方法和属性的访问范围
-
public
- 对外公开
-
protected
- 对子类和同一个包中的类公开
-
默认
- 对同一个包中的类公开
-
private
- 只对本类公开,不对外公开
-
-
-
访问范围
-
使用的注意事项
-
访问修饰符可以用来修饰类中的属性、方法和类本身
-
只有默认和 public 修饰符可以修饰类,并且遵循上述访问权限的特点
-
子类的访问权限会在讲完继承后讲解
-
成员方法的访问规则和属性完全一样
-
8.6 面向对象编程三大特征
-
封装
-
继承
-
多态
8.7 封装
-
介绍
- 封装就是把抽象出的数据【属性】和对数据的操作【方法】封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作,才能对数据进行操作
-
优点
-
隐藏实现细节
-
可以对数据进行验证,保证安全合理
-
-
实现步骤
-
将属性私有化【使属性不能被直接修改】
-
提供一个 public 的 set 方法,用于对属性进行合法判断和赋值
-
提供一个 public 的 get 方法,用于验证权限和获取属性的值
-
8.8 继承
-
基本介绍和示意图
-
继承可以解决代码复用,让我们的编程更加靠近人类思维。当多个类存在相同的属性和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。
-
-
基本语法
-
class 子类 extends 父类
-
说明
-
子类会自动拥有父类定义的属性和方法
-
父类又叫超类、基类
-
子类又叫派生类
-
-
-
优点
-
代码的复用性提高了
-
代码的扩展性和可维护性提高了
-
-
继承的深入讨论和细节
-
子类继承了父类所有的属性和方法,其中非私有的属性和方法可以在子类中直接访问,但是私有的属性和方法不能再子类中直接访问,要通过父类提供的公共的方法去访问
-
子类的构造器中必须调用父类的构造器,以完成父类的初始化
-
当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成父类的初始化,否则编译不通过
-
如果希望指定去调用父类的某个构造器,则显式地调用一下
-
super 在使用时,必须放在构造器的第一行
-
super() 和 this() 都只能放在构造器的第一行,因此这两个方法不能在一个构造器中共存
-
Java 所有类都是 Object 类的子类,Object 类是所有类的父类
-
父类构造器的调用不限于直接父类!将一致往上追溯直到 Object 类
-
子类最多只能继承一个父类(指直接继承),即 Java 中是单继承机制
-
不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
-
-
继承的本质分析
-
本质
-
当子类对象创建好后,建立【查找】的关系
-
-
JVM 如何知道要访问哪个属性?
-
按照查找关系来返回信息
-
首先看当前类是否有该属性
-
如果当前类有,并且可以访问,则返回信息
-
如果当前类没有这个属性,就看这个类的父类有没有这个属性(如果这个类的父类有该属性,并且可以访问,就返回信息)
-
如果这个类的父类没有,则按照3的规则,继续找上级父类,直到 Object
-
-
对于方法的查找也是相同的规则
-
-
8.9 super关键字
-
基本介绍
- super 代表父类的引用,用于访问父类的属性、方法和构造器
-
基本语法
-
访问父类的属性,但不能访问 private 属性
super.属性名 -
访问父类的方法,但不能访问 private 的方法
super.方法名(参数列表) -
访问父类的构造器
super(参数列表)
只能放在构造器的第一句,只能出现一句
-
-
给编程带来的便利
- 分工明确,父类属性由父类初始化,子类属性由子类初始化
-
注意细节
-
当子类中有和父类中的属性和方法重名时,为了访问父类的属性和方法,必须通过 super。如果没有重名,使用 super,this,直接访问时一样的效果
-
super 的访问不限于直接父类,如果爷爷类和本类中有同名的属性或方法,也可以使用 super 去访问爷爷类的成员;如果多个上级类中都有重名的成员,使用 super 访问遵循就近原则,且遵守访问权限的相关规则
-
-
this 和 super 的比较
8.10 方法重写/覆盖
-
基本介绍
- 方法覆盖(重写)就是指子类有一个方法,和父类的某个方法的名称、返回类型、参数都一样。那么我们就说子类的这个方法覆盖了父类的方法
-
注意事项和使用细节
-
子类方法的形参列表、方法名称必须要和父类的完全一样
-
子类方法的返回类型和父类方法的返回类型一样,或者是父类返回类型的子类
比如父类返回 Object 类型,子类返回 String 类型,也构成方法重写 -
子类方法不能缩小父类方法的访问权限
-
-
重写与重载的比较
8.11 多态
-
引出
-
传统的方法存在代码的复用性不高,不利于代码维护等缺点
- 于是需要引入多态来解决
-
-
基本介绍
- 方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的
-
多态的具体体现
-
方法的多态
-
方法重写
-
方法重载
-
-
对象的多态
-
首先,几句前置知识
-
一个对象的编译类型和运行类型可以不一致
-
编译类型在定义对象时,就确定了,不能改变
-
运行类型是可以变化的
-
编译类型看定义时 = 的左边,运行类型看 = 的右边
-
-
-
-
多态的注意事项和细节
-
前提
- 对象多态的前提是:两个对象(类)存在继承关系
-
向上转型
-
本质:父类的引用指向了子类的对象
-
语法: 父类类型 引用名 = new 子类类型();
-
特点
-
编译类型看左边,运行类型看右边
-
可以调用编译类型中的所有成员(需要遵循访问权限)
-
不能调用运行类型中的特有成员
-
最终运行效果决定于运行类型的具体实现(即调用方法时,按照从运行类型开始查找方法的调用规则【同继承】来查找方法)
-
-
-
向下转型
-
语法: 子类类型 引用名 = (子类类型)父类引用;
-
特点
-
只是强转父类的引用,不是强转父类的对象(即只是引用类型被转变,具体对象在内存中的形式没有变化)
-
要求父类的引用必须指向的是当前强转类型的对象
-
当向下转型后,可以调用子类中所有成员(遵循访问限制)
-
-
-
属性没有重写一说!直接调用属性时具体属性的值取决于编译类型
-
instanceof 比较操作符,用于判断对象的【运行】类型是否为 XX 类型或者 XX 类型的子类
-
-
动态绑定机制
-
当调用对象方法的时候,该方法会和该对象的运行类型绑定(即调用对象方法时,总是调用该对象的运行类型的方法,若没有,才依次往上看)
-
当在方法中调用对象属性时,没有动态绑定机制,其返回的值遵循就近原则
-
-
应用
-
多态数组
- 数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
-
多态参数
- 方法定义的形参类型为父类类型,实参类型允许为子类类型
-
8.12 Object类详解
-
equals 方法
-
== 和 equals 对比
-
== 是一个比较运算符,equals 是一个方法
-
== 既可以判断基本类型,又可以判断引用类型
-
== 如果判断基本类型,判断的是值是否相等
-
== 如果判断引用类型,判断的是地址是否相等
-
equals 是 Object 类方法,只能判断引用类型
-
equals 默认判断的是地址是否相等,子类往往重写该方法,用于判断内容是否相等
-
-
重写 equals 方法
-
先判断是否是本身,是则返回 true
-
再判断类型是否匹配,是则判断值,不是则返回 false
-
类型如果匹配,则判断值,值都相等,则返回 false
-
如果上述均不满足,则返回 false
-
-
-
hashCode 方法
-
用处:提高具有哈希结构的容器的效率
-
两个引用,如果指向的是同一个对象,则哈希值肯定是一样的
-
两个引用,如果指向的是不同对象,则哈希值一般不同
-
哈希值主要是根据地址来算得的,但并不等价于地址
-
后面在集合中,hashCode 如果需要的话也会重写,在讲解集合时会说如何重写
-
-
toString 方法
-
基本介绍
- 默认返回:全类名 + @ + 哈希值的十六进制
子类往往重写 toString 方法,用于返回对象的属性信息
- 默认返回:全类名 + @ + 哈希值的十六进制
-
重写 toString 方法
- 是为了打印对象或拼接对象时,调用该对象的 toString 方法来方便开发
-
当直接输出一个对象时,toString 方法会被默认调用
-
-
finalize 方法
-
当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作
-
什么时候被回收:当某个对象没有任何引用时,则 JVM 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁对象前,会调用 finalize 方法
-
垃圾回收机制的调用,是由系统来决定(有自己的 GC 算法),也可以通过 System.gc() 来主动调用垃圾回收期
-
注:在实际开发中,几乎不会运用 finalize,所以更多是为了应付面试
-
8.13 断点调试
8.14 零钱通项目
第九章:房屋出租系统
分层模式设计
源码
-
HouseInterface.java
-
HouseService.java
-
Utility.java
-
House.java
-
HouseRentApp.java
第十章:面向对象编程(高级)
10.1 类变量和类方法
-
类变量
-
什么是类变量
- 类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。
-
如何定义类变量
- 访问修饰符 static 数据类型 变量名;
-
如何访问类变量
-
类名.类变量名
-
对象名.类变量名
-
-
类变量的内存布局
-
static 变量是同一个类所有对象共享
static 类变量,在类加载的时候就生成了
-
-
类变量使用注意事项和细节讨论
-
什么时候需要用类变量
- 当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量
-
类变量与实例变量的区别
- 类变量时该类的所有对象共享的,而实例变量是每个对象独享的
-
加上 static 称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
-
类变量可以通过类名.类变量名或者对象名.类变量名来访问,推荐使用前者方式访问【前提是:满足访问修饰符的访问权限和范围】
-
实例变量不能通过类名.类变量名来访问
-
类变量是在类加载时就初始化了,也就是说,即使没有创建对象,只要类加载了,就可以使用类变量了
-
类变量的生命周期是随类的加载开始,随着类消亡而销毁
-
-
-
类方法
-
基本介绍
- 类方法也叫静态方法,形式如下:
访问修饰符 static 数据返回类型 方法名(){ }
- 类方法也叫静态方法,形式如下:
-
调用
- 类名.类方法名或对象名.类方法名【前提要满足访问修饰符的访问权限和范围】
-
Best Practice
-
当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率
【即这个方法不跟对象捆绑使用,则就可以设计成静态方法】 -
在程序员实际开发中,往往会将一些通用的方法设计成静态方法,这样我们不需要创建对象就可以使用了
-
-
类方法使用注意事项和细节讨论
-
类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:
类方法中不能使用this关键字,但普通方法可以 -
类方法可以通过类名调用,也可以通过对象名调用
-
普通方法和对象有关,需要通过对象名调用,不能通过类名调用
-
类方法中不允许使用和对象有关的关键字,比如 this 和 super,普通方法可以
-
静态方法只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员【遵守访问权限】
-
-
10.2 理解 main 方法语法
-
深入理解 main 方法
-
main 方法是由 JVM 调用的
-
JVM 在执行 main() 方法时不必创建对象,所以该方法必须是 static
-
JVM 需要调用类的 main 方法,所以该方法的访问权限必须是 public
-
该方法接受 String 类型的数组参数,该数组中保存执行 Java 命令时所传递给所运行的类的参数
-
传值命令: java 执行的程序 参数1 参数2 参数3…
-
-
特别提示
-
在 main() 方法中,我们可以直接调用 main 方法所在类的静态方法或静态属性(静态成员)
-
但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
【即同 static 的访问规则】
-
-
在 idea 中如何传递参数
- run 旁边的三点-> run with parameters -> 在 program arguments 处添加
10.3 代码块
-
基本介绍
-
代码块又称初始化块,属于类中的成员,类似于方法将逻辑语句封装在方法体中,通过 {} 包围起来
-
但和方法不同,没有方法名,没有返回数据类型,没有参数,只有方法体,而且不用通过对象或类显式调用,而是在加载类或创建对象时隐式调用
-
-
基本语法
-
[修饰符] {
代码
}; -
注意
-
修饰符仅有 static,是可选项
-
代码块分为两类,使用 static 修饰的叫静态代码块,没有 static 修饰的,叫普通代码块
-
逻辑语句可以为任何语句
-
;可以写上,也可以省略
-
-
-
好处
-
相当于构造器的补充,可以帮助构造器做初始化的工作
-
如果多个构造器中都有重复的语句,可以抽取到代码块中,提高代码的重用性
-
-
使用注意事项和细节讨论
-
static 代码块也叫静态代码块,作用就是对类进行初始化,它随着类的加载而执行,并且只会执行一次。普通代码块,每创建一个对象就执行一次
-
类何时被加载?
-
创建对象实例时 (new)
-
创建子类对象实例,父类也会被加载
联想继承下的对象创建顺序 -
使用类的静态成员(静态属性或方法)时
-
-
普通的代码块,在创建对象实例时,会被隐式地调用,对象被创建一次,就会调用一次
如果只是使用类的静态成员,普通代码块不会被调用
-
-
创建一个对象时,在一个类中的调用顺序
-
完成类加载,调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化的优先级一样,如果有多个静态代码块和多个静态属性初始化,则按他们定义的顺序来依次调用)
-
完成对象属性(非静态属性)的初始化,即调用普通代码块和普通属性的初始化(注意事项同上)
-
调用构造器完成构造器中的初始化
-
-
构造器的最前面隐含了 super() 与普通代码块的调用和普通属性初始化(即在调用子类的构造器初始化之前,先完成父类的初始化和子类普通属性初始化(包括普通代码块调用)
静态代码块和静态属性初始化在类加载时就执行完毕,因此其执行顺序是优先于构造器和普通代码块的
-
具有继承关系的类创建对象时,静态代码块、属性,普通代码块、属性和构造器的调用顺序
-
父类的静态代码块和静态属性
-
子类的静态代码块和静态属性
-
父类的普通代码块和普通属性初始化
-
父类的构造器
-
子类的普通代码块和普通属性初始化
-
子类的构造器
-
-
静态代码块只能直接调用静态成员,普通代码块可以调用任意成员
10.4 单例设计模式
-
什么是设计模式
- 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式。
打个比方,设计模式就像是经典的棋谱,对于不同的棋局使用不同的棋谱,免去再思考和摸索的过程
- 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式。
-
什么是单例模式
-
单例(单个实例)(单个对象)
-
所谓类的单例模式,就是采取一定的方法保证在整个软件系统中,对某个类只存在一个对象实例,并且该类只提供一个取得其对象实例的方法
-
单例模式有两种方式:饿汉式和懒汉式
-
-
-
饿汉式和懒汉式单例模式的实现
-
将构造器私有化 -> 防止new生成更多对象
-
在类的内部创建静态对象
-
向外暴露一个静态的公共获取实例的方法
-
-
饿汉式和懒汉式的区别
-
二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载时就创建了对象实例,而懒汉式是在使用时才创建(具体是在调用 getInstance 方法时创建)
-
饿汉式不存在线程安全问题,懒汉式存在线程安全问题
-
饿汉式存在资源浪费的可能,而懒汉式不存在这个问题
-
JavaSE 中的 java.lang.Runtime 就是经典的单例模式
-
10.5 final 关键字
-
基本介绍
-
final 可以修饰类、属性、方法、局部变量和形参
-
有以下需求时,就会使用到 final
-
当不希望某个类被继承时,可以用 final 关键字修饰
-
当不希望父类的某个方法被子类重写时,可以用 final 关键字修饰
-
当不希望类的某个属性的值被修改,就可以用 final 关键字修饰
-
当不希望某个局部变量被修改时,就可以使用 final 关键字修饰
-
-
-
注意事项和细节讨论
-
final 修饰的属性又叫常量,一般用 XX_XXX_XX 来命名
-
final 修饰的属性在定义时必须赋初值,并且以后不能再修改,赋值可以在如下位置之一
(1)定义时
(2)在构造器中
(3)在代码块中 -
如果final修饰的属性是静态的,则赋初值的位置只能是
(1)定义时
(2)在静态代码块中
不能在构造器中赋值 -
final 类不能被继承,但能实例化对象
-
如果类不是 final 类,但是含有 final 类方法,则该方法虽然不能被重写,但是可以被继承
-
一般来说,如果一个类已经是 final 类了,就没有必要再将方法修饰成 final 方法
-
final 不能修饰构造器
-
final 和 static 往往搭配使用,用于在希望不进行类加载的情况下访问某个类的静态属性,这样会使效率更高,因为底层编译器对此做了优化
-
包装类和 String 也是 final 类
-
10.6 抽象类
-
为何需要?
- 在继承中,当父类的某些方法需要声明,但又不确定如何实现时,可以将其声明为抽象方法,该类成为抽象类
-
介绍
-
用abstract关键字来修饰一个类时,这个类就叫抽象类
-
用abstract关键字来修饰一个方法时,这个方法就是抽象方法
语法:访问修饰符 abstract 返回数据类型 方法名(参数列表); -
抽象类的价值更多在于设计,用于明晰子类所具有的方法和属性,并解决代码冗余问题
-
抽象类在框架和设计模式使用较多
-
-
使用注意事项和细节讨论
-
抽象类不能被实例化
-
抽象类不一定要包含抽象方法,也就是说抽象类可以没有抽象方法
-
一旦类包含了抽象方法,则这个类必须声明为抽象类
-
abstract关键字只能修饰类和方法,不能修饰属性和其他的
-
抽象类可以有任意成员【即抽象类的本质还是类,满足类的性质】
-
抽象方法不能有方法体({}和其中的语句),即不能实现
-
如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为抽象类
-
抽象方法不能使用private,final和static修饰,因为这些关键字与重写相违背
-
10.7 抽象类最佳实践:模板设计模式
-
Best Practice 模板设计模式
-
基本介绍
- 抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式
-
能解决的问题
-
当功能内部一部分实现时确定的,一部分时不确定的时,可以把不确定的部分暴露出去,让子类来实现
-
编写一个抽象父类,提供多个子类的通用方法,并把一个或多个方法留给其子类实现,这就是一种模板模式
-
-
10.8 接口
-
基本介绍
- 接口就是给出一些没有实现的方法,封装到一起,当某个类要使用的时候,再根据具体情况把这些方法写出来
-
语法
-
interface 接口名{
//属性
//抽象方法
} -
class 类名 implements 接口{
//自己属性
//自己方法
//必须实现的接口的抽象方法
}
-
-
深入理解接口
-
接口可用于设计规范,便于多人协作开发完成后将各个类组合起来使用
-
接口可以用来设计框架,明晰软件骨架
-
-
注意事项和细节
-
接口不能被实例化
-
接口中所有方法都是public方法,接口中的抽象方法可以不用abstract修饰
-
一个普通类实现接口,就必须将该接口的所有方法都实现
-
抽象类实现接口,可以不用实现接口的方法
-
一个类可以实现多个接口
-
接口中的属性,只能时final的,而且隐含public static final三个修饰符
-
接口中属性的访问方式:接口名.属性名
-
接口不能继承其他类,但是可以继承【多个】别的接口
-
接口的修饰符只能是public和默认,与类相同
-
1实现接口和继承类是同一层次的行为,如果一个类即继承了其他类,又实现了接口,父类和接口中存在同名属性时,在访问时就会产生混淆,所以必须指明是访问接口名.属性名还是super.属性名
-
-
实现类vs继承接口
-
继承:当子类继承了父类,就自动地拥有父类的功能
实现接口:如果子类需要扩展功能,可以通过实现接口的方式扩展
可以理解成:实现接口是对Java单继承机制的一种补充 -
接口和继承解决的问题不同
-
继承的价值主要在于:解决代码的复用性和可维护性
-
接口的价值主要在于:设计好各种规范(方法),让其他类去实现这些方法,更加灵活
-
接口比继承更加灵活,继承要满足is-a关系,接口只需满足like-a关系
-
接口在一定程度上可实现代码解耦(接口规范性+动态绑定机制)
-
-
-
接口的多态特性
-
多态参数
- 即方法的参数可以用接口数据类型的参数指向实现了该接口的类的对象,并在运行时动态绑定
-
多态数组
- 用法同前多态数组
-
多态传递
- 如果接口继承了其他接口,则可以用父类接口数据类型指向实现了子接口的类的对象,这称为接口的多态传递
-
10.9内部类
-
基本介绍
- 一个类的内部又完整地嵌套了另一个类结构,则被嵌套的类称为内部类,嵌套其的类称为外部类。内部类是类的第五大成员。内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系
-
基本语法
- class Outer{
class Inner{
}
}
- class Outer{
class Other{
}
-
分类
-
定义在外部类局部位置上(方法内和代码块内)
-
局部内部类(有类名)
-
匿名内部类(无类名)
-
-
定义在外部类的成员位置上(即定义类成员的地方)
-
成员内部类(无static修饰)
-
静态内部类(有static修饰)
-
-
-
局部内部类
-
使用
-
可以直接访问外部类的所有成员,包括私有
-
不能添加访问修饰符,因为它的地位就是一个局部变量,局部变量是不能使用修饰符的。但可以用final修饰,因为局部变量也可以用
-
作用域:仅仅在定义它的方法或代码块中
-
局部内部类访问外部类成员,直接访问
-
外部类访问局部内部类成员,通过创建对象访问
-
外部其他类不能访问局部内部类
-
如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)来访问
-
局部内部类本质仍然是一个类,所以适用于类的规定和特性
-
-
-
匿名内部类
-
理解
-
本质是类
-
是内部类
-
该类没有名字
-
同时还是一个对象
-
-
语法
- new 类或接口(参数列表){
类体
};
- new 类或接口(参数列表){
-
为何需要
- 在某些场景中,我们想使用一个接口并实现该接口并创建对象调用方法。使用传统方法会造成资源浪费,当只需使用一次该类时,就可以用内部匿名类来简化开发
-
基于接口的匿名内部类
-
语法:
接口名 对象引用 = new 接口(){
类体
};- 相当于定义一个实现了该接口的匿名内部类,类体为实现内容
-
底层细节
- 在执行new 接口(){};时,等价于执行class 外部类名$1234… implements 接口{};并立马创建了匿名内部类的实例返回给对象引用,随后这个匿名内部类就被销毁,即不能在后续代码中再通过new 外部类名$1234… 来创建新的对象实例
-
-
基于类的匿名内部类
-
语法:
父类名 对象引用 = new 类(参数列表){
类体
};- 相当于定义一个继承了该类的匿名内部类,类体为继承后的子类的特有内容或重写方法,参数列表中的参数传递给父类的构造器(如果有有参的话)
-
底层细节同上
-
-
细节
-
匿名内部类的语法比较奇特,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征
-
可以直接访问外部类的所有成员,包括私有
-
不能添加访问修饰符,因为它的地位就是一个局部变量
-
作用域:仅仅在定义它的方法或代码块中
-
匿名内部类访问外部类成员,直接访问
-
外部类访问匿名内部类成员,不能访问
-
外部其他类访问匿名内部类,不能访问
-
如果外部类和匿名内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)来访问
-
-
Best Practice
-
将匿名内部类当作实参直接传递,简洁高效省资源
-
f1(new IL() {
@Override
public void show() {
System.out.println(“这是一副名画~~…”);
}
});
-
-
-
成员内部类
-
定义:成员内部类时定义在外部类成员位置,并且没有static修饰的类
-
细节
-
可以直接访问外部类的所有成员,包括私有的
-
可以添加任意访问修饰符,因为它的地位就是一个成员
-
作用域是整个外部类
-
成员内部类访问外部类成员,直接访问
-
外部类访问成员内部类,先创建对象再访问
-
外部其他类访问成员内部类有两种方式
-
把new 内部类当作外部类的成员
Outer08 outer08 = new Outer08();
Outer08.Inner08 inner08 = outer08.new Inner08(); -
在外部类中编写一个方法,返回内部类的对象
public Inner08 getInner08Instance(){
return new Inner08();
}
-
-
如果外部类和成员内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)来访问
-
-
-
静态内部类
-
定义:静态内部类是定义在外部类的成员位置,并且有static修饰的类
-
细节
-
可以直接访问外部类的所有静态成员,包括私有,但不能访问非静态成员
-
可以添加任意访问修饰符,因为它的地位就是一个成员
-
作用域:整个外部类
-
静态内部类访问外部类静态成员,直接访问
-
外部类访问静态内部类成员,先创建对象,再访问
-
外部其他类访问静态内部类成员,有两种方式
-
因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
Outer10 outer10 = new Outer10();
Outer10.Inner10 inner10 = new Outer10.Inner10(); -
编写一个方法,可以返回静态内部类的对象实例
public static Inner10 getInner10_() {
return new Inner10();
}
-
-
-
第十一章:枚举和注解
枚举enumeration
-
应用场景
- 当定义的类中只客观存在几个有限的对象(比如季节)时,可以考虑将其设计为枚举类
-
枚举说明
-
枚举是一组常量的集合
-
可以这样理解:枚举属于一种特殊的类,里面只包含一组有限的特定的对象
-
-
两种实现方法
-
自定义类实现枚举
-
步骤
-
将构造器私有化,防止直接new新对象
-
本类内部创建一组对象供其他类调用
-
对外暴露对象(通过为对象添加public static final修饰符)
-
可以提供get方法获得对象属性,但不要提供set方法
-
-
-
使用enum关键字实现枚举
-
步骤
-
使用关键字enum替代class
-
区别于写public static final Season SPRING = new Season(“春天”, “温暖”);
可以直接写SPRING(“春天”,“温暖”),相当于系统帮你补充成了上面那一长串 -
如果枚举类中有多个对象,用“,"间隔即可,最后加上”;“
-
如果使用enum来实现枚举,确保在类的最前面定义创建常量对象
-
如果使用无参构造器创建常量对象,则括号可以省略
-
其他成员比如属性、构造器、方法都可以自行添加以丰富对象的使用
-
-
注意事项
-
当我们使用enum关键字来开发一个枚举类时,默认会继承Enum类,而且会将我们自己的类设为final类,不能再被继承
-
传统的 public static final Season2 SPRING = new Season2(“春天”, “温暖”); 简化成SPRING(“春天”, “温暖”), 这里必
须知道,它调用的是哪个构造器 -
如果使用无参构造器 创建 枚举对象,则实参列表和小括号都可以省略
-
当有多个枚举对象时,使用","间隔,最后由一个分号结尾
-
枚举对象必须放在枚举类的行首
-
-
继承自Enum的常用方法有哪些
-
toString
- Enum 类已经重写过了,返回的是当前对象
名,子类可以重写该方法,用于返回对象的属性信息
- Enum 类已经重写过了,返回的是当前对象
-
name
- 返回当前对象名(常量名),子类中不能重写
-
ordinal
- 返回当前对象的位置号,默认从 0 开始
位置号即为定义时的顺序号
- 返回当前对象的位置号,默认从 0 开始
-
values
- 返回当前枚举类中所有的常量(对象),是一个对象数组
-
valueOf
- 将字符串转换成枚举对象,要求字符串必须
为已有的常量名,否则报异常!
即搜索特定名字的枚举对象,搜不到的话就报错
- 将字符串转换成枚举对象,要求字符串必须
-
compareTo
- 比较两个枚举常量,比较的就是位置号
返回前一个对象的位置号 - 后一个对象的位置号
- 比较两个枚举常量,比较的就是位置号
-
-
-
enum实现接口
-
使用enum关键字后,就不能再继承其他类了,因为已经隐式继承Enum,而Java是单继承机制
-
enum实现的类本质上还是类,所以可以实现接口
-
-
注解
-
理解
-
注解(Annotation)也被称为元数据(Metadata),用于修饰解释 包、类、方法、属性、构造器、局部变量等数据信息
-
和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息
-
在 JavaSE 中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在 JavaEE 中注解占据了更重要的角
色,例如用来配置应用程序的任何切面,代替 java EE 旧版中所遗留的繁冗代码和 XML配置等
-
-
基本注解的介绍
-
@Override
-
限定某个方法是重写父类方法,该注解只能用于方法
-
使用说明
-
@Override表示指定重写父类的方法(从编译层面验证),如果父类没有其重写的方法,则会报错
-
如果不写@Override注解,仍然可以重写父类方法
-
@Override只能修饰方法
-
@Target是修饰注解的注解,称为元注解
-
-
-
@Deprecated
-
用于表示某个程序元素(类、方法等)已过时
-
可以修饰方法、类、字段、包、参数等等
-
@Deprecated可以起到帮助新旧版本的兼容和过渡的作用
-
-
@SuppressWarnings
-
抑制编译器警告
-
当我们不希望看到某种警告的时候,可以使用@SuppressWarnings来抑制(不显示)警告信息
-
在{”“}中,可以写入你希望抑制的警告信息
-
可以指定的警告类型有
- // all,抑制所有警告
// boxing,抑制与封装/拆装作业相关的警告
//cast,抑制与强制转型作业相关的警告
//dep-ann,抑制与淘汰注释相关的警告
//deprecation,抑制与淘汰的相关警告
//fallthrough,抑制与 switch 陈述式中遗漏 break 相关的警告
//finally,抑制与未传回 finally 区块相关的警告
//hiding,抑制与隐藏变数的区域变数相关的警告
//incomplete-switch,抑制与 switch 陈述式(enum case)中遗漏项目相关的警告
//javadoc,抑制与 javadoc 相关的警告
//nls,抑制与非 nls 字串文字相关的警告
//null,抑制与空值分析相关的警告
//rawtypes,抑制与使用 raw 类型相关的警告
//resource,抑制与使用 Closeable 类型的资源相关的警告
//restriction,抑制与使用不建议或禁止参照相关的警告
//serial,抑制与可序列化的类别遗漏 serialVersionUID 栏位相关的警告
//static-access,抑制与静态存取不正确相关的警告
//static-method,抑制与可能宣告为 static 的方法相关的警告
//super,抑制与置换方法相关但不含 super 呼叫的警告
//synthetic-access,抑制与内部类别的存取未最佳化相关的警告
//sync-override,抑制因为置换同步方法而遗漏同步化的警告
//unchecked,抑制与未检查的作业相关的警告
//unqualified-field-access,抑制与栏位存取不合格相关的警告
//unused,抑制与未用的程式码及停用的程式码相关的警告
- // all,抑制所有警告
-
作用范围与放置的位置相关
-
放置在方法上,则范围就是该方法
-
防止在类上,就是整个类
-
-
-
元注解
-
Retention
- 指定注解的范围:SOURCE, CLASS, RUNTIME
-
Target
- 指定注解可以在哪些地方使用
-
Documented
- 指定该注解是否会在javadoc体现
-
Inherited
- 使用后子类会继承父类的注解
第十二章:异常-Exception
12.1 引出
- 为了防止程序运行过程和编译过程可能导致程序崩溃的不是特别重要的代码段使程序崩溃而使程序功能无法执行,引入异常处理机制,即使发生了异常,程序也可以继续执行
12.2 异常介绍
-
基本概念
- Java语言中,将程序执行中发生的不正常情况称为“异常”(开发过程中的语法错误和逻辑错误不是异常)
-
分类
-
错误Error
- Java虚拟机无法解决的严重问题。例如JVM系统内部错误,资源耗尽(stackoverflow栈溢出和OOM out of memory)。Error是严重错误,程序会崩溃
-
异常Exception
- 其他因变成错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等。Exception分为两大类:运行时异常(程序运行时发生的异常)和编译时异常(编译时,编译器检查出的异常)
-
12.3 异常体系图
-
小结
-
异常分为两大类,运行时异常和编译时异常
-
运行时异常,编译器检查不出来。一般是指编程时的逻辑错误,是程序员应该避免其出现的异常
-
对于运行时异常,可以不做处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响
-
编译时异常,是编译器要求必须处置的异常
-
12.4 常见的运行时异常
-
NullPointerException
-
ArithmeticException
-
ArrayIndexOutOfBoundsException
-
ClassCastException
-
NumberFormatException
12.5 编译异常
-
介绍
- 编译异常是指在编译期间,就必须处理的异常,否则代码不能通过编译
-
常见的编译异常
-
SQLException操作数据库时,查询表可能发生的异常
-
IOException操作文件时,发生的异常
-
FileNotFoundException当操作一个不存在的文件时,发生异常
-
ClassNotFoundException加载类,而该类不存在时,发生异常
-
EOFException操作文件,到文件末尾,发生异常
-
IllegalArgumentException参数异常
-
12.6 异常处理
-
基本介绍
- 异常处理就是当异常发生时,对异常处理的一些方式
-
方式
-
try-catch-finally
程序员在代码中捕获发生的异常,再自行处理 -
throws
将发生的异常抛出给调用者,交给调用者(方法)来处理,最顶级的处理者是JVM
-
12.7 try-catch异常处理
-
说明
- Java提供try和catch块来处理异常。try块用于包含可能出错的代码。catch块用于处理try块中发生的异常。可以根据需要在程序中有多个try…catch块
-
基本语法
- try{
//可疑代码
}catch (异常名 e){
//对异常的处理
}
- try{
-
注意事项
-
如果异常发生了,则异常发生后面的代码不会执行,直接进入到catch块
-
如果异常没有发生,则顺序执行try代码块,不会进入到catch
-
如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等),则使用finally代码块
-
可以有多个catch语句,捕获不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前,如果发生异常,只会匹配一个catch
-
可以try-finally配合使用,这种用法相当于没有捕获异常,因此程序发生异常会直接崩溃。应用场景:执行一代代码,不管是否发生异常,都必须执行某个业务逻辑
-
-
小结
-
如果没有出现异常,则执行try块中所有语句,不执行catch块中语句,如果有finally,最后还要执行finally里面的语句
-
如果出现异常,则try块中异常发生后,try块剩下的语句不在执行。将执行catch语句块中的语句,如果有finally,最后还需要执行finally里面的语句
-
12.8 throws异常处理
-
基本介绍
-
如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显式地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
-
在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
-
-
注意事项和使用细节
-
对于编译异常,程序中必须处理,要么try-catch,要么throws
-
对于运行时异常,程序中如果没有处理,默认是throws方法处理
-
子类重写父类方法时,对抛出异常的规定:子类重写的方法所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出异常类型的子类
-
在throws过程中,如果有方法try-catch,就相当于处理异常,则可以不必throws
-
12.9 自定义异常
-
基本概念
- 当程序中出现了某些“错误”,但该错误并没有在Throwable子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息
-
自定义异常步骤
-
定义类,class 异常类名 extends Exception或者RuntimeException
-
如果继承Exception,则属于编译时异常
-
如果继承RuntimeException,属于运行时异常(一般都继承RuntimeException)
-
12.10 throw和throws的区别
-
一览表
第十三章:常用类
13.1 包装类
-
包装类的分类
-
针对八种基本数据类型相应的引用类型-包装类
-
有了类的特点,就可以调用类中的方法
-
八大包装类的类继承关系
-
-
包装类和基本数据的转换
-
JDK5之前是手动装箱和拆箱
手动装箱:int n1 = 100;
Integer integer = new Integer(n1);
Integer integer1 = Integer.valueOf(n1)
手动拆箱:int i = integer.intValue() -
JDK5(含)以后,是自动装箱和拆箱方式
自动装箱:int n2 = 200;
Integer integer2 = n2; //底层使用的是 Integer.valueOf(n2)
自动拆箱:int n3 = integer2; //底层仍然使用的是 intValue()方法 -
自动装箱底层调用的是valueOf方法,拆箱调用的是typeValue方法
-
其他包装类的用法类似,不一一举例
-
-
包装类型和String类型的相互转换
-
方式1:String str1 = i + “”;(i为包装类型)
-
方式2:String str2 =i.toString();
调用包装类的toString方法 -
方式3:String str3 = String.valueOf(i);
调用包装类的valueOf方法
-
-
Integer类和Character类常用方法
-
Integer
-
MIN_VALUE返回Integer类型的最小值
-
MAX_VALUE返回Integer类型的最大值
-
-
Character
-
isDigit判断是否是数字
-
isLetter判断是否是字母
-
isUpperCase判断是否是大写
-
isLowerCase判断是否是小写
-
isWhitespace判断是否是空格
-
toUpperCase转成大写
-
toLowerCase转成小写
-
-
-
Integer类自动装箱的特性
-
//1. 如果 i 在 IntegerCache.low(-128)~IntegerCache.high(127),就直接从数组返回
//2. 如果不在 -128~127,就直接 new Integer() -
比如
-
Integer i = new Integer;
Integer j = new Integer;
System.out.println(i == j);// False -
Integer m = 1; //底层 Integer.valueOf; -> 阅读源码
Integer n = 1;//底层 Integer.valueOf;
System.out.println(m == n); // True
-
-
如果包装类和对应的基本数据类型比较大小,则比较的是值,不是地址
-
13.2 String类
-
String类的理解和创建对象
-
理解
-
String对象用于保存字符串,也就是一组字符序列
-
字符串常量是用双引号括起的字符序列
-
字符串的字符使用Unicode字符编码,一个字符(不区分是字母还是汉字)占两个字节
-
String类有很多构造器,构造器的重载,常用的有
//String s1 = new String();
//String s2 = new String(String original);
//String s3 = new String(char[] a);
//String s4 = new String(char[] a,int startIndex,int count)
//String s5 = new String(byte[]
-
-
String类实现了接口Serializable(String可以串行化:可以在网络传输)
实现了接口Comparable(String对象可以比较大小) -
String是final类,不能被其他类继承
-
String有属性private final char value[],用于存放字符串常量的地址
-
一定要注意:value是一个final类型,不可以被修改,即value不能指向新的地址,但是可以修改value指向的地址中的内容
-
-
创建String对象的两种方式
-
方式1:直接赋值String s = “xxx”;
-
方式2:调用构造器String s = new String(“xxx”);
-
区别
-
方式一:先从常量池查看是否有"xxx"数据空间,如果有,直接让s指向该数据空间;如果没有,则创建,然后让s指向。s最终指向的是常量池的空间地址
-
方式二:先在堆中创建空间,里面维护了value属性,指向常量池的"xxx"空间。如果常量池中没有"xxx",则创建,再让value指向。最终s指向的是堆中value所存储的地址,通过value指向常量池中的字符串
-
-
内存布局
-
13.3 字符串的特性
-
String是一个final类,代表不可变的字符序列
-
字符串是不可变的。一个字符串对象一旦被分配,其指向内容是不可变的。改变指向对象实际上是新创建了一个String对象再重新指向该String对象
-
直接String s = “abc” + “efg"会被优化成
String s = “abcefg”,不会分别创建"abc"和"efg”,而只创建"abcefg"
所以只创建1个对象 -
String a = “1”;
String b = “2”;
String c = a + b;- 底层是创建StringBuilder sb = new StringBuilder(); sb.append(a); sb.append(b)
来创建String对象的
- 底层是创建StringBuilder sb = new StringBuilder(); sb.append(a); sb.append(b)
-
intern方法返回的是常量池中字符串常量的地址
13.4 String类的常用方法
-
小说明
- String类是保存字符串常量的,每次更新都需要重新开辟空间,效率较低,因此Java设计者还提供了StringBuilder和StringBuffer来增强String的功能,并提高效率
-
equals
- 判断字符串内容是否相等,区分大小写
-
equalsIgnoreCase
- 不区分大小写,判断字符串内容是否相等
-
length
- 获取字符串的长度,即字符的个数
-
indexOf
- 获取某个字符(串)在字符串中第一次出现位置的索引,索引从0开始,找不到则返回-1
-
lastIndexOf
- 获取某个字符(串)在字符串中最后一次出现位置的索引,索引从0开始,找不到则返回-1
-
substring
- 截取指定范围内的子串
-
trim
- 前后去空格
-
charAt
- 获取某索引处的字符,注意不能用s[index]这种方式来获取
-
toUpperCase
- 转成大写
-
toLowerCase
- 转成小写
-
concat
- 拼接另一个字符串到此字符串
-
replace
- 替换字符串中的所有某个字符(串)为某个字符(串),返回替换后的字符串
-
split
- 分割字符串,以某个字符为分割线,逢此字符就分割,返回一个字符串数组
-
compareTo
-
比较两个字符串的大小
-
如果长度相同,并且每个字符也相同,就返回0
-
如果长度相同或不相同,但在比较时可以区分大小,就返回if (c1 != c2) {
return c1 - c2;
} -
如果前面的部分都相同,就返回str1.length - str2.length
-
-
-
toCharArray
- 返回字符串的字符数组
-
format
- 格式化字符串,格式化形式类似于C语言的输出语句的格式化字符串形式,返回格式化后的字符串。静态方法
13.5 StringBuffer类
-
基本介绍
-
StringBuffer代表可变的字符序列,可以对字符串内容进行增删
-
很多方法与String相同,但StringBuffer是可变长度的
-
StringBuffer是一个容器
-
类继承关系
-
直接父类是AbstractStringBuilder
-
实现了Serializable,即StringBuffer对象可以串行化
-
在父类AbstractStringBuilder类中有属性char[] value,但不是final,示意可以改变
- 该value数组存放字符串内容,存放在堆中
-
StringBuffer是final类,不可被继承
-
因为StringBuffer字符内容是存储在char[] value中,所以改变字符串内容是不用每次都更换地址,所以效率高于String
-
-
-
String和StringBuffer比较
-
String保存的是字符串常量,里面的值不能更改,每次Sting类的更新实际上是创建新对象并更改地址,效率较低
-
StringBuffer保存的是字符串变量,里面的值可以更改,每次StringBuffer的更新实际上是更新内容,不用更新地址,效率较高
-
-
String和StringBuffer的相互转换
-
String转StringBuffer
-
使用构造器
- StringBuffer stringBuffer = new StringBuffer(str);
-
使用append方法
- StringBuffer stringBuffer1 = new StringBuffer();
stringBuffer1 = stringBuffer1.append(str);
- StringBuffer stringBuffer1 = new StringBuffer();
-
-
StringBuffer转String
-
使用StringBuffer类的toString方法
- String s = stringBuffer3.toString;
-
使用构造器
- String s1 = new String(stringBuffer);
-
-
-
常用方法
-
增
-
append
- 可以追加整数型,布尔型,浮点型,会被转换成字符串
-
-
删
-
delete
- 删除索引为[start, end)范围的字符
-
-
改
-
replace
- 使用字符串"xxx"替换索引[start, end)范围的字符
-
-
插
-
insert
- 在索引x处插入内容,x索引原内容后移
-
-
13.6 StringBuilder类
-
基本介绍
-
StringBuilder是一个可变的字符序列类。此类提供一个与StringBuffer兼容的API,但不保证同步(不是线程安全)。该类被设计用作StringBuffer的一个简易替换,用于字符串缓冲区被单个线程使用的时候。如果可能,建议优先采用该类,因为在大多数实现中,它比StringBuffer还快
-
在StringBuilder对象上的主要操作是append方法和insert方法,可重载这些方法,以接收任意类型的数据
-
类继承关系
- 与StringBuffer一样
-
-
常用方法
- 与StringBuffer一样
-
String、StringBuffer 和 StringBuilder 的比较
-
StringBuilder和StringBuffer非常类似,均代表可变的字符序列,而且方法也一样
-
String:不可变字符序列,效率低,但复用率高
-
StringBuffer:可变字符序列,效率较高,线程安全
-
StringBuilder:可变字符序列,效率最高,线程不安全
-
String使用注意说明:
String s = “a”;
s += “b”;实际上原来的"a"字符串对象已经被丢弃了,现在又产生了一个字符串"ab",如果多次执行这些改变串内容的操作,会导致大量的副本字符串对象留存在内容中,降低效率,如果这样的操作放到循环中,会极大影响程序的性能
于是===》如果有要对字符串内容做大量修改,不要使用String类
-
-
String、StringBuffer 和 StringBuilder 的选择
-
使用的原则
-
如果字符串存在大量的修改操作,一般使用StringBuffer和StringBuilder
-
如果字符串存在大量的修改操作,并在单线程的情况,一般使用StringBuilder
-
如果字符串存在大量的修改操作,并在多线程的情况,一般使用StringBuffer
-
如果字符串很少修改,被多个对象引用,使用String,比如配置信息等情况
-
-
13.7 Math类
-
Math类包含用于执行基本数学运算的方法,比如初等指数,对数,平方根和三角函数
-
基本方法
-
abs
- 绝对值
-
pow
- 幂
-
ceil
- 向上取整
-
floor
- 向下取整
-
round
- 四舍五入
-
sqrt
- 平方根
-
random
-
随机[0,1)之间的数
-
返回[a,b]之间的整数,用random实现
- (int)(a + Math.random() * (b-a +1) )
-
-
-
min, max
- 两个数中的最大值和最小值
-
13.8 Arrays类
-
包含一系列静态常用方法
-
toString
- 返回数组的字符串形式
-
sort
-
自然排序
- 从小到大排序数组元素
-
定制排序
-
可选择是从小到大还是从大到小
-
Arrays.sort(arr, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Integer i1 = (Integer) o1;
Integer i2 = (Integer) o2;
return i2 - i1;
}
}); -
接口编程
-
-
-
-
binarySearch
- 二分搜索法,需要数组是有序的
-
copyOf
-
从原数组的开头拷贝x个元素到新数组中,返回新数组
-
如果拷贝的长度大于原数组的长度,则在新数组后加null
-
如果拷贝长度小于0,抛异常NegativeArraySizeException
-
-
fill
-
数组元素填充
- 用某个数去填充数组中的所有元素
-
-
equals
- 判断两个数组的内容是否完全一致
-
asList
-
将数组转换成一个List集合
-
返回的 asList 编译类型是List(接口)
-
asList 运行类型是java.util.Arrays#ArrayList, 是 Arrays 类的
静态内部类 private static class ArrayListextends AbstractList
implements RandomAccess, java.io.Serializable
-
-
-
13.9 System类
-
类常用静态方法
-
exit
- 退出当前程序
-
arraycopy
-
复制数组元素,适合底层调用,一般不用
-
五个参数
- // src:源数组
// srcPos:从源数组的哪个索引位置开始拷贝
// dest : 目标数组,即把源数组的数据拷贝到哪个数组
// destPos: 把源数组的数据拷贝到 目标数组的哪个索引
// length: 从源数组拷贝多少个数据到目标数组
- // src:源数组
-
-
currentTimeMillens
- 返回当前时间距离1970-1-1的毫秒数
-
gc
- 运行垃圾回收机制
-
13.10 BigInteger类和BigDecimal类
-
应用场景
-
BigInteger适合保存很大的整形
-
BigDecimal适合保存精度很高的浮点型
-
-
常用方法
-
add
- 加
-
substract
- 减
-
multiply
- 乘
-
divide
-
除
-
System.out.println(bigDecimal.divide(bigDecimal2, BigDecimal.ROUND_CEILING));
- 在浮点数进行divide方法时,为了避免出现无限循环小数抛异常,常会规定输出精度
-
-
13.11 日期类
-
第一代日期类Date
-
介绍
- //1. 获取当前系统时间
//2. 这里的 Date 类是在 java.util 包
//3. 默认输出的日期格式是国外的方式, 因此通常需要对格式进行转换
- //1. 获取当前系统时间
-
输出格式转换
- SimpleDateFormat sdf = new SimpleDateFormat(“yyyy 年 MM 月 dd 日 hh:mm:ss E”);
String format = sdf.format(d1);
// format:将日期转换成指定格式的字符串
System.out.println(“当前日期=” + format);
- SimpleDateFormat sdf = new SimpleDateFormat(“yyyy 年 MM 月 dd 日 hh:mm:ss E”);
-
可以把一个格式化的String转成对应的Date
-
String s = “1996 年 01 月 01 日 10:20:30 星期一”;
Date parse = sdf.parse(s); -
得到 Date 仍然在输出时,还是按照国外的形式,如果希望指定格式输出,需要转换
-
-
在把 String -> Date , 使用的 sdf 格式需要和你给的 String 的格式一样,否则会抛出转换异常
-
第二代日期类Calendar
-
介绍
- Calendar 是一个抽象类, 并且构造器是private
可以通过 getInstance() 来获取实例
提供大量的方法和字段提供给程序员
Calendar 没有提供对应的格式化的类,因此需要程序员自己组合来输出(灵活)
如果我们需要按照 24 小时进制来获取时间, Calendar.HOUR ==改成=> Calendar.HOUR_OF_DAY
Calender 没有专门的格式化方法,所以需要程序员自己来组合显示
- Calendar 是一个抽象类, 并且构造器是private
-
-
第三代日期类(JDK8加入)LocalTime,LocalDate,LocalDateTime
-
介绍
-
使用 now() 返回表示当前日期时间的对象
LocalDateTime ldt = LocalDateTime.now(); //LocalDate.now();//LocalTime.now() -
使用 DateTimeFormatter 对象来进行格式化
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(“yyyy-MM-dd HH:mm:ss”);
String format = dateTimeFormatter.format(ldt);
-
-
方法使用时查看API文档
-
-
instant时间戳类
-
介绍
-
通过 静态方法 now() 获取表示当前时间戳的对象
Instant now = Instant.now(); -
通过 from 可以把 Instant 转成Date
Date date = Date.from(now); -
通过 date 的 toInstant() 可以把 date 转成 Instant 对象
Instant instant = date.toInstant();
-
-
第十四章:集合
14.1 集合的好处
-
可以动态保存任意多个对象,使用比较方便
-
提供了一系列方便的操作对象的方法:add, remove, set, get等
-
使用集合添加或删除新元素的代码简洁明了
14.2 集合的框架体系
-
单列集合(即元素是单个个体)
-
双列集合(即元素通常是键值对的形式)
14.3 Collection接口和常用方法
-
Collection接口实现类的特点
-
实现类可以存放多个元素,每个元素可以是Object
-
有些实现类可以存放重复的元素(List的实现类),有些不可以(Set的实现类)
-
有些实现类的元素是有序的(List的实现类),有些不是有序的(Set的实现类)
-
Collection接口没有直接的实现子类,子类通过它的子接口List和Set来实现Collection接口
-
-
常用方法
-
add
- 添加单个元素
-
remove
- 删除指定元素,可以指定索引或名称
-
contains
- 查找某元素是否存在
-
size
- 获取元素的个数
-
isEmpty
- 判断是否为空
-
clear
- 清空,不保留空间
-
addAll
- 添加多个元素,通常是添加另一个Collection的实现子类
-
containsAll
- 查找多个元素是否都存在,通常查找另一个Collection的实现子类中的元素是否都在
-
removeAll
- 删除多个元素,通常从该对象中删除另一个Collection实现子类中的含有的元素
-
-
实现了Collection接口的子类遍历元素的方式
-
使用迭代器(iterator)
-
基本介绍
-
Iterator对象成为迭代器,主要用于遍历Collection集合实现子类中的元素
-
所有实现了Collection接口的子类都有一个iterator()方法,用以返回一个迭代该子类的迭代器
-
Iterator的结构
-
Iterator仅用于遍历集合,本身并不存放对象
-
-
执行原理
-
其常用方法
-
hasNext
-
检查是否还存在下一个元素可以遍历
- 如果不进行判断,可能抛出NoSuchElementException
-
-
next
- 返回下一个遍历的元素
-
-
快捷键 itit
-
-
增强for循环
-
只能用于遍历集合或数组
-
快捷键 I
-
-
普通for循环
-
14.4 List 接口和常用方法
-
List接口基本介绍
-
List实现类中元素有序(即添加顺序和取出顺序一致),且可重复
-
List实现类中的每个元素都有其对应的顺序索引,即支持索引(索引从0开始)
-
List实现类中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
-
List接口的实现类常用的有ArrayList, LinkedList和Vector
-
-
List接口常用方法
-
add
- 添加元素,可以不带参数,默认添加到末尾,也可带参数,添加到索引处
-
addAll
- 添加另一个List中的元素到该List中,可以选择添加的位置
-
get
- 获取指定索引位置的元素
-
indexOf
- 返回参数对象在List中首次出现的位置
-
lastIndexOf
- 返回参数对象在List中末次出现的位置
-
remove
- 移除指定索引位置的元素,并返回该元素
-
set
- 设置指定索引位置的元素为参数对象,相当于是替换
-
subList
- 返回从第一个参数索引(含)到第二个参数索引(不含)的子List
-
-
List的三种遍历方式
-
使用迭代器
-
使用增强for
-
使用普通for
-
14.5 ArrayList 底层结构和源码分析
-
注意事项
-
permits all elements, including null,ArrayList可以加入null,并且可加入多个。
-
ArrayList是由数组来实现数据存储的
-
ArrayList基本等同于Vector,除了ArrayList是线程不安全(效率高)。在多线程情况下不建议使用
-
-
底层操作机制源码分析
-
ArrayList中维护了一个Object类型的数组elementData。
transient Object[] elementData;
transient表示瞬间,短暂的,表示该属性不会被序列化 -
当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData含量为0,第1次添加元素时,扩容到10,再需扩容时,扩容至1.5倍
-
如果使用的是指定大小的构造器,则初始elementData容量为构造器中指定大小,如果需要扩容,则直接扩容elementData为1.5倍。
-
14.6 Vector 底层结构和源码剖析
-
基本介绍
-
定义说明
public class Vector
extends AbstractList
implements List, RandomAccess, Cloneable, Serializable -
Vector底层也是一个对象数组,protected Object[] elementData;
-
Vector是线程安全的,操作方法带有synchronized
-
在开放中,需要线程同步安全时,考虑使用Vector
-
-
源码剖析
-
使用无参构造器创建时,默认给容量10
-
使用有参构造器创建时,容量为指定容量
-
当需要扩容时,两倍扩容
-
-
Vector和ArrayList比较
14.7 LinkedList 底层结构
-
全面说明
-
LinkedList底层实现了双向链表和双端队列特点
-
可以添加任意元素(可重复),包括null
-
线程不安全,没有实现同步
-
-
底层操作机制
-
LinkedList底层维护了一个双向链表
-
LinkedList中维护了两个属性first和last,分别指向首节点和尾节点
-
每个节点(Node对象)里面又维护了prev, next, item三个属性,其中通过prev指向前一个,通过next指向后一个节点,最终实现双向链表
-
所以LinkedList的元素的添加和删除不是通过数组来完成的,效率较高
-
14.8 ArrayList 和 LinkedList 比较
-
ArrayList和LinkedList比较及如何选择
14.9 Set 接口和常用方法
-
Set接口基本介绍
-
无序(添加和取出的顺序不一致),没有索引
-
不允许添加重复元素,所以最多包含一个null
-
常用Set实现类有HashSet, LinkedHashSet, TreeSet
-
-
常用方法
- 和List接口是一样,Set接口也是Collection的子接口,因此常用方法和Collection接口一样
-
Set接口实现类的遍历方式
-
使用迭代器
-
使用增强for
-
14.10 Set 接口实现类-HashSet
-
全面说明
-
HashSet实现了Set接口
-
HashSet底层实际上是HashMap,因为底层维护了一个HashMap,只不过将<K, V>对中的value全部都设为了同一个常量
-
可以存放null,但只能有一个null
-
HashSet不保证元素是有序的,取决于被hash后索引的位置
-
不能有重复元素/对象(因为是Set的实现类)
-
-
添加元素底层机制说明
-
(1)
-
HashSet的底层是HashMap,HashMap的底层是数组+链表+红黑树
-
HashSet底层是HashMap
-
添加一个元素时,先得到hash值,再转成数组索引值
-
找到存储数据表(数组table,是 HashMap 的一个数组,存储的数据类型是 Node)对应索引位置,看这个位置是否已经存放有元素
-
如果没有,直接加入
-
如果有,调用【equals】依次比较,如果比较结果相同,就放弃添加,如果不相同,则加到最后
-
在Java8中,如果一条链表的元素个数到达8,并且table中(包括链表中)已有元素个数大于64,该条链表就会树化成红黑树来存储数据
-
-
(2)
-
HashSet底层是HashMap,第一次添加时,数组table扩容到16,临界值是16 * 0.75 = 12
-
如果table数组达到了临界值,就会扩容到32,新的临界值是32 * 0.75 = 24
-
在java8中,如果一条链表的元素个数到达8,并且table数组大小达到64,就会树化,否则继续扩大table数组
-
-
14.11 Set 接口实现类-LinkedHashSet
-
全面说明
-
LinkedHashSet是HashSet的子类
-
LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表
-
LinkedHashMap根据元素的hash值来决定元素的存储位置,同时用双向链表维护元素的添加顺序,这使得元素看起来是以插入顺序保存的
-
不允许重复元素
-
14.12 Map 接口和常用方法
-
Map接口类的实现特点
-
Map与Collection并列存在,其用于保存具有映射关系的数据:Key-Value
-
Map中的Key和Value可以是任何的引用类型的数据,会封装到HashMap$Node对象中
-
Map中的Key不允许重复,原因和HashSet一样(底层源码的规定)(即此映射关系只能一对一或者一对多,不能多对一)
-
Map中的value可以重复(即一对多)
-
Map中的Key可以为null,value也可以为null,但Key中的null只有一个,value可以有多个
-
常用String类作为Map的Key
-
key和value之间存在单项一对一关系,即通过指定的key总能找到对应的value
-
Map存放数据的key-value示意图,一对k-v是放在一个HashMap$Node中的,又因为Node实现了Map.Entry接口,所以也可以叫做一对k-v就是一个Entry
KeySet存放了所有的key,Entry存放了所有的key-value键值对,这样做是为了方便遍历,因为Map接口没有迭代器,要间接通过Collection实现类来遍历
-
-
Map接口常用方法
-
put
- 添加键值对
-
remove
- 根据键删除相应键值对
-
get
- 根据索引获取value
-
size
- 获取元素个数
-
isEmpty
- 判断是否为空
-
clear
- 清空Map
-
containsKey
- 查找键是否存在
-
-
Map接口遍历方法
-
第一组
- 先取出所有的Key,通过key取出所有的Value
Set keyset = map.keySet();
然后使用迭代器或增强for遍历keySet来遍历值
- 先取出所有的Key,通过key取出所有的Value
-
第二组
- 把所有的values取出
Collection values = map.values();
然后使用迭代器或增强for遍历所有值
- 把所有的values取出
-
第三组
- 使用EntrySet来获取k-v
Set entrySet = map.entrySet();
然后用迭代器或增强for来遍历每个entry,通过Map.Entry自带的getKey和getValue方法来获得entry中的一对k-v
- 使用EntrySet来获取k-v
-
14.13 Map 接口实现类-HashMap
-
前述HashMap小结
-
Map接口的常用实现类:HashMap, Hashtable, Properties
-
HashMap是Map接口使用频率最高的实现类
-
HashMap是以键值对的形式来存储数据(类型为HashMap$Node)
-
key不能重复,但是值可以重复,允许使用null键和null值
-
如果添加相同的key,则会覆盖原来的k-v,等同于替换
-
与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的
-
HashMap没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有synchronized
-
-
底层机制及源码剖析
-
底层示意
-
扩容机制
-
HashMap底层维护了Node类型的数组table,默认为null
-
当创建对象时,将加载因子(loadfactor)初始化为0.75
-
当添加键值对时,通过key的hash值得到在table的索引,然后判断该索引处是有元素,如果没有则直接添加,如果有且相等则直接替换,如果有且不相等则需要判断后面是树结构还是表结构,做出比较判断,看要不要添加或替换。如果添加后发现容量达到临界值,则扩容
-
第一次添加,将table扩容至16,临界值12
-
以后再次扩容,则双倍扩容,临界值也变为原来的两倍
-
在Java8后,如果链表的元素个数超过8且table大小超过64,就会树化
-
-
14.14 Map 接口实现类-Hashtable
-
基本介绍
-
存放的元素是键值对:即K-V
-
Hashtable的键和值都不能为null,否则会抛出NullPointerException
-
Hashtable使用方法基本上和HashMap一样
-
Hashtable是线程安全的,HashMap是线程不安全的
-
-
Hashtable和HashMap对比
14.15 Map 接口实现类-Properties
-
基本介绍
-
Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存数据
-
它的使用特点和Hashtable类似
-
Properties还可以用于从后缀为properties的文件中加载数据到Properties类对象,并进行读取和修改
-
说明:工作时,properties文件通常作为配置文件,在IO流时具体学习
-
14.16 总结-开发中如何选择集合实现类
-
先判断存储的类型(一组对象【单列】或一组键值对【双列】
-
一组对象【单列】:Collection接口实现类
允许重复:List
增删多:LinkedList(双向列表)
改查多:ArrayList(数组)
不允许重复:Set
无序:HashSet(HashMap)
排序:TreeSet
插入和取出顺序一致:LinkedHashSet(数组+双向链表) -
一组键值对【双列】:Map接口实现类
键无序:HashMap
键排序:TreeSet
插入和取出顺序一致:LinkedHashMap(数组+双向链表) -
补充
-
TreeSet和TreeMap
-
TreeSet底层是TreeMap,在添加元素时,TreeMap会先判断添加顺序,然后再添加元素,所以可以实现键排序
-
- 构造器把传入的比较器对象,赋给了 TreeSet 的底层的 TreeMap 的属性 this.comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
- 构造器把传入的比较器对象,赋给了 TreeSet 的底层的 TreeMap 的属性 this.comparator
-
-
-
-
在 调用 treeSet.add(“tom”), 在底层会执行到
if (cpr != null) {//cpr 就是我们的匿名内部类(对象)
do {
parent = t;
//动态绑定到我们的匿名内部类(对象)compare
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else //如果相等,即返回 0,这个 Key 就没有加入
return t.setValue(value);
} while (t != null);
}- 通过在创建TreeMap对象时调用有参构造器传入匿名内部类Comparator,可以自定义添加键值对时的默认顺序,可以是根据字符串长度、字符串在字母表中顺序等来自定义。同样,如果根据添加规则发现有相同元素,就替换
14.17 Collections 工具类
-
介绍
-
Collections是一个操作Set,List和Map等集合的工具类
-
Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
-
-
排序操作
-
reverse(List)
- 反转List中元素的顺序
-
shuffle(List)
- 对List中元素进行随机排序(打乱)
-
sort(List)
- 根据元素的自然顺序(字母表顺序)对指定List中元素按升序排序
-
sort(List, Comparator)
- 按照指定的Comparator中的compare方法产生的顺序来排序
-
swap(List, int, int)
- 将指定List中的i处元素和j处元素交换
-
-
查找、替换操作
-
max(Collection)
- 根据元素的自然顺序,返回给定集合中的最大元素
-
max(Collection, Comparator)
- 根据自定义顺序,返回给定集合中的最大元素
-
min(Collection)
- 同上
-
min(Collection, Comparator)
- 同上
-
frequency(Collection, Object)
- 返回Object在集合中出现的次数
-
copy(List dest, List src)
- 将src中的内容复制到dest中
-
replace(List, Object oldVal, Object newVal)
- 将List中的所有oldVal元素替换为newVal
-
第十五章:泛型
15.1 2 泛型的理解和好处
-
传统方法的问题
-
不能对加入到集合中的数据类型进行约束
-
遍历的时候,需要进行类型转换,如果集合中的数据量较大,对效率有影响
-
-
泛型的好处
-
编译时,检查添加元素的类型,提高了安全性
-
减少了类型转换的次数,提高效率
-
不再提示编译警告
-
15.3 泛型介绍
-
泛——广泛,泛型——广泛的类型,泛化的类型
-
泛型又称为参数化类型,是JDK5.0出现的新特性,解决数据类型的安全性问题
-
在类声明或实例化时只要制定好需要的具体的类型即可
-
Java泛型可以保证如果程序在编译时没有发出警告,那么运行时就不会产生
-
泛型的作用是:可以在类声明时通过一个标识表示类中的某个属性的类型,或是某个方法的返回值的类型,或是参数类型
-
15.4 泛型的语法
-
泛型的声明
-
interface name
{}
class name <K,V>{}等-
其中,T、K、V不代表值,而是表示类型
-
任意字母都可以。常用T表示,是type的缩写
-
-
-
泛型的实例化
-
需要在类名或接口名后指定类型参数的值
- List
strList = new ArrayList<>();
Iteratoriterator = customers.iterator();
- List
-
-
泛型使用的注意事项和细节
-
T、V等字母代表的类型只能是引用类型
-
在给泛型指定具体的类型后,可以传入该类型或其子类类型
-
泛型使用形式,推荐写成如下形式:
Listlist = new ArrayList<>();
如果尖括号内不写内容,默认为Object
-
15.6 自定义泛型
-
自定义泛型类
-
基本语法
- class name <T, R…> {
成员
}
- class name <T, R…> {
-
注意细节
-
普通成员可以使用泛型
-
使用泛型的数组,不能初始化(因为编译器不知道该分配多大的空间)
-
静态方法中不能使用类的泛型(因为静态方法是跟类相关的,在类加载时,对象还没创建,泛型还没指定)
-
泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
-
如果在创建对象时,没有指定类型,默认为Object
-
-
-
自定义泛型接口
-
基本语法
- interface name <T, R…> {
…
}
- interface name <T, R…> {
-
注意细节
-
接口中,静态成员也不能使用泛型(与泛型类的规定一样)
-
泛型接口的类型,在继承接口或实现接口时确定
-
没有指定类型,默认为Object
-
-
-
自定义泛型方法
-
基本语法
- 修饰符 <T, R…> 返回类型 方法名(参数列表){
}
- 修饰符 <T, R…> 返回类型 方法名(参数列表){
-
注意细节
-
泛型方法,可以定义在普通类中,也可以定义在泛型类中
-
当泛型方法被调用时,类型会确定
-
注意泛型方法和使用了泛型的方法的区别
(泛型方法指调用方法时需要传泛型类型的方法,而使用了泛型的方法仅指返回值或参数类型有泛型的方法,其泛型不是在调用时指定的)
-
-
15.7 泛型的继承和通配符
-
说明
-
泛型不具备继承性,即
List - >:支持任意泛型类型
- extends A>:支持A类以及A的子类,规定了泛型的上限
- super A>:支持A类以及A的父类,不限于直接父类,规定了泛型的下限
-
15.9 JUnit
-
为什么需要?
-
一个类有很多功能代码需要测试,为了测试,就需要写入到main方法中
-
如果有多个功能代码测试,就需要来回注释掉,切换很麻烦
-
如果可以直接运行一个方法,就方便很多,并且可以给出相关信息
-
-
基本介绍
-
JUnit是一个Java语言的单元测试框架
-
多数Java的开发环境都已经继承了JUnit作为单元测试工具
-
第十七章:多线程基础
17.1 线程相关概念
-
程序
- 程序就是:为完成特定任务,用某种语言编写的一组指令的集合,简单地说就是我们写的代码
-
进程
-
进程是指运行中的程序,比如我们点开QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。
-
进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生,存在,和消亡的过程。
-
-
线程
-
线程由进程创建,是进程的一个实体(即进程通过线程来完成工作,线程是进程具体完成工作的实体,而进程是线程的抽象)
-
一个进程可以拥有多个线程(比如一个Java程序中就可以创建多个线程)
-
-
单线程
- 同一个时刻,只允许执行一个线程
-
多线程
- 同一个时刻,可以执行多个线程
-
并发
- 同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说,单核cpu实现的多任务就是并发
-
并行
- 同一个时刻,多个任务同时执行,多核cpu可以实现并行
17.2 线程的基本使用
-
创建线程的两种方式
-
继承Thread类,重写run方法
-
实现Runnable接口,重写run方法
-
Java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承Thread类的方法来创建线程已经不可能了
-
Java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程
-
-
-
start0,run和start
-
start0是本地方法,是JVM调用的方法,底层是c/c++实现,是真正实现多线程效果的方法
- start方法调用start0方法后,该线程并不一定会立马执行,只是将线程变成了可运行状态。具体什么时候执行,取决于CPU,由CPU统一调度
-
run,即线程运行时所执行的方法,如果单独调用而不是通过start调用,那就没有多线程效果,只是简单的方法调用
-
start,启动线程,程序不会阻塞,即实现了多线程的效果
-
17.3 继承Thread vs 实现Runnable
-
从Java设计来看,通过继承Thread和实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档可以看到,Thread类本身就实现了Runnable接口
-
实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable
17.4 线程终止
-
基本说明
-
当线程完成任务后,会自动退出
-
还可以通过使用变量来控制run方法退出的方式来停止线程,这种终止方式叫做通知方式
-
-
步骤
-
定义标记变量,默认为true
-
将标记变量设为循环条件
-
提供公共的set方法,用于更新loop
-
17.5 线程常用方法
-
setName
- 设置线程名称,与参数name相同
-
getName
- 返回该线程的名称
-
start
- 使该线程开始执行;Java虚拟机底层调用该线程的start0方法
-
run
- 调用线程对象的run方法
-
setPriority
- 更改线程的优先级
-
getPriority
- 获取线程的优先级
-
sleep
- 在指定的毫秒数内让当前正在执行的线程休眠
-
interrupt
- 中断线程
-
yield
- 线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
-
join
- 线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务,才执行当前线程的任务
-
用户线程和守护线程
-
用户线程
- 也叫工作线程,当线程的任务执行完或以通知方式通知则结束
-
守护线程
- 一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
典型例子:垃圾回收机制线程
- 一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
-
17.6 线程的生命周期
-
JDK中用Thread.State枚举表示了线程的几种状态
-
NEW
- 线程尚未启动
-
RUNNABLE
- 线程处于执行状态
-
BLOCKED
- 线程处于被阻塞状态
-
WAITING
- 线程处于等待状态
-
TIMED_WAITING
- 线程处于定时等待状态
-
TERMINATED
- 线程处于终止状态
-
-
线程状态转换图
-
查看线程状态
- getState()方法
17.7 线程的同步
-
为什么需要?
- 当多个线程同时操作共有资源时,会导致发生不期望的结果,于是需要某一时刻只能有一个线程进行操作,于是需要线程的同步
-
线程的同步,使用Synchronized关键字
17.8 Synchronized
-
线程同步机制
-
在多线程编程时,一些敏感数据不允许被多个线程同时访问,此时就需要同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性
-
也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
-
-
语法
-
同步代码块
- synchronized (对象) { // 得到对象的锁,才能 操作同步代码
}
- synchronized (对象) { // 得到对象的锁,才能 操作同步代码
-
同步方法
- public synchronized void name(…) {
}
- public synchronized void name(…) {
-
17.9 分析同步原理
-
同一时间只能有一个线程拿到锁,并操作同步代码,此时其他线程没有拿到锁,无法操作,直到线程操作完毕把锁归还,所有线程再一起竞争这把锁,再重复
17.10 互斥锁
-
基本介绍
-
Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
-
每个对象都对应与一个可称为“互斥锁”的标记,这个标记用来保证在任意时刻,只能有一个线程访问该对象
-
关键字synchronized用来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任意时刻只能由一个线程访问
-
同步的局限性:导致程序的执行效率降低
-
同步方法(非静态)的锁可以是this(当前对象),也可以是其他对象(要求多个线程对应的是同一对象)
-
同步方法(静态)的锁为当前类本身
-
同步代码块的锁是this对象
-
-
注意事项和细节
-
同步方法如果没有使用static修饰,默认锁对象为this
-
如果方法使用static修饰,默认锁对象为当前类.class
-
如何实现线程同步?
-
需要先分析上锁的代码
-
选择使用同步代码块或同步方法
-
要求多个线程的锁对象为同一个即可
-
-
17.11 线程的死锁
-
基本介绍
- 多个线程都占用了对方的锁的资源,都需要得到对方的锁才能进行下一步操作,此时就导致了死锁。这是在编程中必须要避免的!
-
举例
- a线程得到了x的锁,正在执行代码,到某个节点执行下一步代码需要得到y的锁;而同时b线程得到了y的锁,下一步执行需要得到x的锁才能继续执行。两者互不相让,导致线程死锁,程序不继续进行,也不退出。
17.12 释放锁
-
以下的操作会释放锁
-
当前线程的同步方法、同步代码块执行结束
-
当前线程在同步代码块、同步方法中遇到break, return
-
当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
-
当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
-
-
以下操作不会释放锁
-
线程执行同步方法、同步代码块时,程序调用Thread.sleep(), Thread.yield()方法暂停当前线程的执行时,不会释放锁
-
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,不会释放锁
-
第十九章:IO流
19.1 文件
-
什么是文件?
- 文件是保存数据的地方,它既可以是一张图片,也可以是一段视频,一段音频等等
-
文件流
-
文件在程序中是以流的形式来操作的
-
流
- 数据在数据源和程序之间的路径
-
输入流
- 数据从数据源到程序的路径
-
输出流
- 数据从程序到数据源的路径
-
19.2 常用的文件操作
-
创建文件对象的相关构造器的方法
-
构造器
- new File(String pathname)//根据绝对路径构建一个File对象
new File(File parent, String child)//根据父目录文件对象+子路径构建
new File(String parent, String child)//根据父目录+子目录贵贱
- new File(String pathname)//根据绝对路径构建一个File对象
-
方法
-
createNewFile
- 创建新文件
-
-
-
获取文件的相关信息
-
getName
- 获取文件名
-
getAbsolutePath
- 获取文件绝对路径
-
getParent
- 获取文件父级目录
-
length
- 获取文件大小(字节)
-
exists
- 判断文件是否存在
-
isFile
- 判断是不是一个文件
-
isDirectory
- 判断是不是一个目录
-
-
文件的操作和文件删除
-
mkdir
- 创建一级目录
-
mkdirs
- 创建多级目录,指如果文件当前所指位置之前有多个目录需要创建,则用此方法可以一次性创建多个目录,直到文件所指位置
-
delete
- 删除空目录或文件,不可删除非空的目录
-
19.3 IO流原理及流的分类
-
Java IO流的原理
-
I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理数据传输。如读写文件,网络通讯等
-
Java程序中,对于数据的输入和输出是以“流”的方式进行
-
java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过方法输入或输出数据
-
输入:读取外部数据(磁盘、光盘、网络等)到程序(内存)中
-
输出:将程序(内存)中的数据输出到外部源(磁盘,光盘,网络等)中
-
-
流的分类
-
按操作数据的单位分为
-
字节流
- 适合读写二进制文件
-
字符流
- 适合读写文本文件
-
-
按数据流的流向不同分为
-
输入流
-
输出流
-
-
按流的角色的不同分为
-
节点流
-
处理流/包装流
-
-
19.4 IO流体系图和常用的类
-
IO流体系图
-
文件和流的关系
-
FileInputStream
-
FileOutputStream
-
FileReader
-
相关方法
-
new FileReader(File/String)
- 构造器,可以用File对象或路径字符串
-
read()
- 每次读取单个字符,返回该字符,如果到文件末尾返回-1
-
read(char[])
- 批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1
-
new String(char[])
- 将char[]转换为String
-
new String(char[], off, len)
- 将char[]指定部分转换成String
-
-
-
FileWriter
-
相关方法
-
new FileWriter(File/String)
- 构造器,可以用File对象或路径字符串,覆盖模式
-
new FileWriter(File/String, true)
- 构造器,可以用File对象或路径字符串,追加模式
-
write(int)
- 写入单个字符
-
write(char[])
- 写入整个数组
-
write(char[], off, len)
- 写入数组的指定部分
-
write(String)
- 写入整个字符串
-
write(String, off, len)
- 写入字符串的指定部分
-
toCharArray()
- 将String转换为char[]
-
注意
- FileWriter使用后,必须要关闭(close)或者刷新(flush),否则数据还在内存中,没有写入到文件中
-
-
19.5 节点流和处理流
-
基本介绍
-
节点流可以从一个特定的数据源读写数据,如FileWriter, FileWriter
-
处理流(包装流)是“连接”在已存在的流之上,为程序提供更为强大的读写功能,也更加灵活。如BufferedReader, BufferedWriter
-
-
节点流和处理流的区别和联系
-
节点流是底层流/低级流,直接跟数据源相接
-
处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出
-
处理流(包装流)对节点流进行包装,使用了修饰器模式,不会直接与数据源相连
-
-
处理流的功能主要体现在以下两个方面
-
性能的提高:主要以增加缓冲的方式来提高输入输出的效率
-
操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便
-
-
BufferedReader
-
BufferedWriter
-
BufferedInputStream
-
BufferedOutputStream
-
对象流
-
为何需要?
-
有时我们希望保存到文件的数据既有值又有数据类型,这样我们在读取文件时可以直接把读取到的值赋值给对应数据类型的对象,免去了类型转换的步骤
-
-
序列化和反序列化
-
序列化就是在保存数据时,保存数据的值和数据类型
-
反序列化就是在恢复数据时,恢复数据的值和数据类型
-
需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:
- Serializable // 这是一个标记接口,没有方法
- Externalizable // 该接口有方法需要实现,所以一般实现上面的Serializable接口
-
-
介绍
-
功能
- 提供了对基本类型或对象类型的序列化和反序列化的方法
-
ObjectInputStream
-
提供反序列化功能
-
-
ObjectOutputStream
-
提供序列化功能
-
-
-
注意事项和细节说明
-
读写顺序要一致
-
要求序列化或反序列化的对象,要实现序列化接口
-
序列化的类中建议添加SerialVersionUID,以提高版本的兼容性
-
序列化对象时,默认将里面所有属性都进行序列化,除了static或transient修饰的成员
-
序列化对象时,要求里面属性的类型也实现了序列化接口
-
序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化
-
-
-
标准输入输出流
-
转换流
-
介绍
-
InputStreamReader
- Reader的子类,可以将InputStream(字节流)包装(转换)成Reader(字符流)
-
OutputStreamWriter
- Writer的子类,可以将OutputStream(字节流)转换成Writer(字符流)
-
当处理纯文本数据时,使用字符流效率更高,并且可以有效解决中文乱码问题,所以建议此时将字节流转换成字符流
-
可以在使用时指定编码格式(utf-8, gbk, gb2312, ISO8859-1)
-
-
19.6 打印流
-
打印流
-
PrintStream
-
PrintWriter
-
19.7 Properties类
-
使用场景
-
基本介绍
-
是专门用于读写后缀为properties的配置文件的集合类,配置文件的格式:
键=值
键=值
… -
注意,键值对中间没有空格,值不需要用引号括起来,默认值都为Sting类型
-
常用方法
-
load
- 加载配置文件的键值对到Properties对象
-
list
- 将数据显示到指定设备
-
getProperty(key)
- 根据键获取值
-
setProperty(key, value)
- 设置(更新)键值对到Properties对象
-
store
- 将Properties中的键值对存储到配置文件中,在idea中,保存信息到配置文件时,如果有中文,会存储为Unicode码
-
-
第二十一章:网络编程
21.1 网络的相关概念
-
网络通信
-
两台设备之间通过网络实现数据传输的过程
-
java.net包下提供了一系列的类或接口供程序员使用,来完成网络通信
-
-
网络
-
概念
- 两台或多台设备通过一定物理设备连接起来构成了网络
-
分类:根据网络的覆盖范围不同,对网络进行分类
-
局域网
-
城域网
-
广域网
-
-
-
ip地址
-
概念
- 用于唯一表示网络中的每台计算机/主机的标识
-
查看ip地址
- 控制台输入ipconfig
-
ip地址的表示形式
-
点分十进制
xxx.xxx.xxx.xxx -
每个十进制数的范围:0~255
-
-
ip地址的组成
- 网络地址+主机地址
-
IPv6
-
IPv6是互联网工程任务组设计的用于替代IPv4的下一代IP协议,其地址数量号称可以为全世界的每一粒沙子编上一个地址
-
由于IPv4最大的问题在于网络地址资源有限,严重制约了互联网的应用和发展。IPv6的使用,不仅能解决网络地址资源数量的问题,而且也解决了多种接入设备连入互联网的障碍
-
-
-
IPv4地址分类
-
域名
-
好处:可以方便记忆,省去了记IP的麻烦
-
概念:通过HTTP协议,可以将IP地址映射成域名,方便记忆
-
端口号
-
概念:用于标识计算机上某个特定的网络程序
-
表示形式:以整数形式表示,端口范围0~65535(两个字节表示)
-
0~1024已经被占用,比如ssh22, ftp21, smtp25, http80
-
常见的网络程序端口号
-
tomcat:8080
-
mysql:3306
-
oracle:1521
-
sqlserver:1433
-
-
-
协议(TCP/IP)
-
TCP/IP是Transmission Control Protocol/Internet Protocol的简写,中文译名为传输控制协议/因特网互联协议,又叫网络通讯协议,是互联网最基本的血祭,是国际互联网的基础。由网络层的IP协议和传输层的TCP协议组成
-
-
网络通信协议
-
TCP和UDP
-
TCP
-
传输控制协议
-
使用TCP协议前,需要先建立TCP连接,形成传输数据通道
-
传输前,采用“三次握手”方式,是可靠的
-
TCP协议进行通信的两个应用进程:服务端和客户端
-
在连接中可进行大数据量的传输
-
传输完毕,需要释放已经建立的连接,效率低
-
-
-
UDP
-
用户数据协议
-
将数据、源、目的封装成数据包,不需要建立连接
-
每个数据包的大小限制在64KB内,不适合传输大量数据
-
因无需连接,故是不可靠的
-
发送数据结束时无需释放资源(因为不是面向连接的),速度快
-
-
-
21.2 InetAddress类
-
相关方法
-
getLocalHost
- 获取本机的InetAddress对象
-
getByName
- 根据指定主机名/域名获取InetAddress对象
-
getHostName
- 获取InetAddress对象的主机名
-
getHostAddress
- 获取InetAddress对象的ip地址
-
21.3 Socket
-
基本介绍
-
使用套接字(Socket)开发网络应用程序被广泛采用,以至于称为事实上的标准
-
通信的两端都要有Socket,是两台机器间通信的端点
-
网络通信其实就是Socket之间的通信
-
Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输
-
一般主动发起通信的应用程序属于客户端,等待通信请求的为服务端
-
21.4 TCP网络通信编程
-
基本介绍
-
基于客服端-服务端的网络通信
-
底层使用的是TCP/IP协议
-
基于Socket的TCP编程
-
应用场景举例:客户端发送数据。服务端接收并显示在控制台上
-
-
举例
-
案例1
-
TCPSocket01Client.java
-
TCPSocket01Server.java
-
-
案例2
-
TCPSocket02Client.java
-
TCPSocket02Server.java
-
-
案例3
-
TCPSocket03Client.java
-
TCPSocket03Server.java
-
-
案例4
-
uploadFileClient.java
-
uploadFileServer.java
-
-
-
netstat指令
-
netstat -an 可以查看当前主机网络情况,包括端口监听情况和网络连接情况
-
netstat -an|more 可以分页显示
-
listening表示某个端口正在监听
如果有一个外部程序连接到该端口,就会显示一条连接信息
-
-
TCP网络通讯细节
- 当客户端连接到服务端后,实际上客户端也是通过一个端口和服务端进行通讯的,这个端口是TCP/IP来分配的,是不确定的,是随机的。
21.5 UDP网络编程
-
基本介绍
-
类DatagramSocket和DatagramPacket实现了基于UDP协议网络程序
-
UDP数据包DatagramPacket通过数据包套接字DatagramSocket发送和接收,系统不保证UDP数据包一定能够安全送到目的地,也不确定什么时候可以送达
-
DatagramPacket对象封装了UDP数据包,在数据包中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号
-
UDP协议中每个数据包都给出了完整的地址信息,因此无需建立发送方和接收方的连接
-
-
基本流程
-
核心的两个类/对象:DatagramSocket和DatagramPacket
-
建立发送端,接收端(没有服务端和客户端的概念)
-
发送数据前,建立数据包对象DatagramPacket
-
调用数据包套接字DatagramSocket的发送和接收方法
-
关闭DatagramSocket
-
-
案例
-
UDPReceiver.java
-
UDPSender.java
-
第二十三章:反射
23.1 引出背景
-
根据如下配置文件,创建Cat对象并调用方法hi
classfullpath=com.hspedu.Cat
method=hi -
利用现有技术,几乎不能完成,于是引入反射机制
-
这样的根据外部文件的配置,在不修改源码的情况下来控制程序用的非常多,也符合设计模式中的开闭原则(不修改源码,可扩展功能)
23.2 反射机制
-
基本介绍
-
反射机制允许程序在执行期间借助于反射相关API取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能创建对象并操作对象的属性及方法。反射在设计模式和框架底层都会用到
-
加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以形象地称之为反射
-
-
反射机制原理示意图
-
通过反射可以做到
-
在运行时判断任意一个对象所属的类
-
在运行时构造任意一个类的对象
-
在运行时得到任意一个类所具有的成员变量和方法
-
在运行时调用任意一个对象的成员变量和方法
-
生产动态代理
-
-
反射相关主要类
-
java.lang.Class
- 代表一个类,Class对象表示某个类加载后在堆中的对象
-
java.lang.reflect.Method
- 代表类的方法,Method对象本身某个类的方法
-
java.lang.reflect.Field
- 代表类的成员变量,Field对象表示某个类的成员变量
-
java.lang.reflect.Constructor
- 代表类的构造器,Constructor对象本身某个类的构造器
-
-
反射的优点和缺点
-
优点
- 可以动态地创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑
-
缺点
- 使用反射基本是解释执行,对执行速度有影响
-
-
反射调用优化——关闭访问检查
-
Method和Field、Constructor对象都有setAccessible()方法
-
setAccessible作用是启动和禁用访问安全检查的开关
-
参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示反射的对象执行访问检查
-
23.3 Class类
-
基本介绍
-
Class类也是类,因此也继承Object类
-
Class类对象不是new出来的,而是系统创建的
-
对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
-
每个类的实例都会记得自己是由哪个Class对象所生成的
-
通过Class对象可以通过API完整地得到一个类的完整结构
-
Class对象是存放在堆里的
-
类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名,访问权限等)
-
-
常用方法
-
forName(String name)
- 返回指定类名name的Class对象
-
newInstance
- 调用无参构造器,返回该Class对象的一个实例
-
getName
- 返回此Class对象所表示的实体(类、接口、数组、基本类型等)的全类名
-
getInterfaces
- 获取当前Class对象的所有实现接口的Class对象,返回Class数组
-
getClassLoader
- 返回该类的类加载器
-
getSuperclass
- 返回表示此Class所表示的实体的父类的Class
-
getConstructors
- 返回该Class对象的实例的所有公开的构造器
-
getDeclaredFields
- 返回该Class对象的实例的所有属性
-
getMethod(String name, paramTypes…)
- 根据name和形参类型返回该类的某个方法
-
getPackage
- 获得该Class对象的包名
-
23.4 获取Class类对象
-
情况1:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName(String fullName)获取,注意,可能抛出ClassNotFoundException
- 应用场景:多用于配置文件,读取类的全路径,加载Class对象
-
情况2:已知具体的类,可通过类的class获取,该方式最为安全可靠,程序性能最高。实例:Class cls = Cat.class;
- 应用场景:多用于参数传递,比如通过反射得到对应的构造器对象
-
情况3:已知某个类的实例,可调用该实例的getClass()方法获取Class对象
- 应用场景:有现成的创建好的对象实例
-
其他方式:
ClassLoader cl = 对象.getClass().getClassLoader();
class cls = cl.loadClass(String fullName); -
基本数据类型通过如下方法获取Class对象
Class cls = 基本数据类型.class; -
基本数据类型对应的包装类,可以通过.TYPE得到Class类对象
Class cls = 包装类.TYPE;
23.5 哪些类型有Class对象?
-
外部类、成员内部类、静态内部类、局部内部类、匿名内部类
-
接口
-
数组
-
枚举类
-
注解
-
基本数据类型
-
void
23.6 类加载
-
基本说明
-
反射机制是Java实现动态语言的关键,也就是通过反射实现类动态加载
-
静态加载
- 编译时加载相关的类,如果没有相关类则会报错,依赖性很强
-
动态加载
- 运行时加载需要的类,如果运行时不用该类,即使该类不存在,也不会报错,降低了依赖性
-
-
-
类加载时机
-
创建对象时(静态加载)
-
当子类被加载时,父类也会加载(静态加载)
-
调用类中的静态成员时(静态加载)
-
通过反射得到类的Class对象时(动态加载)
-
-
类加载过程图
-
类加载各阶段完成的任务
-
加载
-
将类的class文件读入内存,并位置创建一个java.lang.Class对象。此过程由类加载器完成
- 主要目的是将字节码从不同的数据源(可能是class文件、也可能是jar包,甚至是网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象
-
-
连接
-
将类的二进制数据合并到JRE中
-
三个阶段
-
验证
-
目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
-
验证包括:文件格式验证(是否以魔数oxcafebabe开头)、元数据验证、字节码验证和符号引用验证
-
可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机加载类的时间
-
-
准备
- JVM会在该阶段对静态变量分配内存并默认初始化(对应数据类型的默认初始值)。这些变量所使用的内存都将在方法区中进行分配
-
解析
- 虚拟机将常量池内的符号引用替换为直接引用的过程
-
-
-
-
初始化
-
JVM负责对类进行初始化,主要是静态成员的初始化
-
一直到初始化阶段,才真正开始执行类中的Java程序代码,初始化阶段是执行clinit()方法的过程
-
clinit()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并(即最终每个静态变量只会保留最后的赋值语句)
-
虚拟机会保证一个类的clinit()方法在多线程环境中被正确地加锁同步,不会出现多个线程同时去执行类的clinit()方法,导致初始化错误的情况。
-
-
-
23.7 通过反射获取类的结构信息
-
通过Class类获取
-
getName
- 获取全类名
-
getSimpleName
- 获取简单类名
-
getFields
- 获取本类以及父类所有public修饰的属性
-
getDeclaredFields
- 获取本类中的所有属性(不限修饰符)
-
getMethods
- 获取本类和父类的所有public修饰的方法
-
getDeclaredMethods
- 获取本类中的所有方法(不限修饰符)
-
getConstructors
- 获取本类所有的public修饰的构造器
-
getDeclaredConstructors
- 获取本类中所有构造器(不限修饰符)
-
getPackage
- 以Package形式返回包信息
-
getSuperClass
- 以Class形式返回父类Class对象
-
getInterfaces
- 以Class[]形式返回所有实现接口
-
getAnnotations
- 以Annotation[]形式返回注解信息
-
-
通过Feild类获取
-
getModifiers
-
以int的形式返回修饰符
- 0=默认,1=public,2=private,4=protected,8=static,16=final
有多个则相加
- 0=默认,1=public,2=private,4=protected,8=static,16=final
-
-
getType
- 以Class形式返回类型
-
getName
- 返回属性名
-
-
通过Method类获取
-
getModifiers同上
-
getReturnType
- 以Class形式返回 返回类型
-
getName
-
返回方法名
-
getParameterTypes
- 以Class[]形式返回参数类型数组
-
-
通过Constructor类获取
-
getModifiers同上
-
getName
- 返回构造器全名
-
getParameterTypes同上
-
23.8 通过反射创建对象
-
方式一
- 调用类中的public修饰的无参构造器
-
方式二
- 调用类中的指定构造器
-
相关的Class类方法
-
newInstance
- 调用类中的无参构造器,获取对应类的对象
-
getConstructor(class …clazz)
- 根据参数列表,获取对应的public构造器对象
-
getDeclaredConstructor(Class …clazz)
- 根据参数列表,获取对应的构造器对象(不限修饰符)
-
-
相关的Constructor类方法
-
setAccessible(true)
- 爆破
-
newInstance(Object …obj)
- 调用构造器,传入对应实参来初始化
-
23.9 通过反射访问类中的成员
-
访问属性
-
步骤
-
根据属性名获取Field对象
Field f = clazz.getDeclaredField(属性名) -
(可选)爆破
f.setAccessible(true) -
访问
f.set(对象实例,值)
f.get(对象实例) -
注意
- 如果访问的是静态属性,则set和get方法中的对象实例可以写成null
-
-
-
访问方法
-
步骤
-
根据方法名和参数列表获取Method对象
Method m = clazz.getDeclaredMethod(name, XX.class…) -
获取对象
Object o = clazz.newInstance(); -
爆破(可选)m.setAccessible(true)
-
访问
Object returnVal = m.invoke(对象实例,实参) -
注意
- 如果是静态方法,则invoke方法的对象实例可以写成null
-
-