编程基础
计算机硬件介绍
1.CPU
中央处理器从内存获取指令,然后执行这些指令
每台计算机都有一个内部时钟,以固定速度发射电子脉冲,时钟速度越快,在给定的时间段内可以执行更多的指令(计量单位为HZ)
2.计算机发展规律
- 摩尔定律
- 安迪-比尔定律
- 反摩尔定律
3.存储设备
内存中的信息在断电后丢失,所有我们要将程序和数据永久保存在存储设备上
存储设备有下面三种
磁盘驱动器
光盘驱动器
USB闪存驱动器
4.内存
计算机最基本的存储单元是字节(byte),每个字节有8个比特(bit)构成
内存(RAM)用于存储程序及程序所需要的数据
一个程序和它的数据在被CPU执行前必须移到计算机内存中
5.通信设备
电脑可以通过通信设备
操作系统
操作系统是运行在计算机上最重要的程序,管理和控制计算机的活动
万维网
万维网即world wide web,简称web
web客户端可以访问浏览web服务器上的页面
web通过一个全局统一资源标识符(URL)标识
web通过超文本传输协议(http)传输给用户
Java语言概述
Java语言应用场景
1.JavaEE(spring)
2.大数据(spark)
3.安卓开发
Java开发介绍
1.软件开发介绍
(1)软件开发
软件集一系列按照特定顺序组织的计算机数据和指令的集合。有系统软件和应用软件之分
(2)人机交互方式
- 图形化界面:GUI
- 命令行方式:CLI
2.Java技术体系平台
- Java SE:面向桌面级应用的Java平台
- Java EE:面向web应用程序
- Java ME:面向移动终端
3.Java语言特点
- 面向对象:类与对象
- 健壮性:提供一个相对安全的内存管理和访问机制
- 跨平台性:java语言编写的应用程序可以运行在不同的系统平台上(通过jvm实现)
4.Java两种核心机制
- Java虚拟机(JVM):JVM是一个虚拟计算机,具有指令集使用不同的存储区域,负责执行指令,管理数据,内存,寄存器(屏蔽底层运行平台的差别,实现“”一次编译,处处运行“)
- 垃圾收集机制:java系统级线性跟踪存储空间的分配情况,并在JVM空闲时,检测并释放可被释放的存储空间
5.Java环境
- JDK:Java开发工具包,其中包含了JRE,其中包含了编译工具(javac)和打包工具(jar)
- JRE:java运行环境,包括了JVM和java的核心类库
- 使用JDK开发java程序,交给JRE运行
- JDK > JRE > JVM
Java基本语法
关键字与保留字
1.关键字
定义:在java语言中用作专门用途的字符串(单词)
特点:关键字所有字母为小写
功能:定影数据类型,定义流程控制,定义访问权限修饰,定义类函数变量修饰符,定义类与类之间关系,定义建立引用判断实例,异常处理,包
2.保留字
现有Java版本尚未使用,但后续版本可能作为关键字使用,命名标识符时避免使用这些保留字
标识符
对变量,方法和类的命名使用的字符序列,称为标识符
合法标识符规则,命名规范
变量
变量即内存中一个存储区域,该区域的数据可以在同一类型范围内不断变化
变量时程序最基础的存储单元,包含变量类型,变量名和存储的值
Java的每个变量必须先声明后使用
1.数据类型
(1)基本数据类型
数值型:整数类型(byte,short,int,long),浮点类型(float,double)
字符型(char)
布尔型(boolean)
(2)引用数据类型
类(class)(字符串变量的声明就是用类来声明)
接口(interface)
数组(array)
(3)声明位置分类变量
- 成员变量:方法体外,类体内声明的变量
- 局部变量:方法体内部声明的变量
2.整型类型
整型默认使用int型,当遇到不足以表达较大的数,才使用long,声明long类型常量,需要后加”L”
3.浮点数类型
浮点数类型常量默认使用double型,声明float型常量,需要后加”F”
4.基本数据类型转换
- 自动转换类型:容量小的类型自动转换为容量大的数据类型
- byte,short,char -> int -> long -> float -> double
- 在多种类型数据混合运算时,系统首先自动间=将所有数据转换成容量最大的数据类型,然后进行计算
- 强类型转换:自动类型转换的逆过程,可以将容量大的数据类型转换成容量小的数据类型(如将字符串转换为int类)
5.引用数据类型
引用数据类型包括:类(如String类),接口,数组
String类:
- String属于引用数据类型
- 声明String类型变量使用一对””
- String可以和8种基本数据类型做运行(字符串连接运算)
6.变量赋值
- 如果变量是基本数据类型,此时赋值的变量是所保存的数据值
- 如果变量是引用数据类型,此时赋值的变量所保存的数据的地址值
运算符
1.算术运算符
加减乘除,取余,自增,自减,字符串连接
2.赋值运算符
- 当”=“号两侧数据类型不一致时,可以使用自动类型转换或者使用强制类型转换原则进行处理
- 支持连续赋值
- 可以使用扩展赋值运算符:+=,-=,*=,/=,%=
3.比较运算符
比较运算符的结果都是Boolean型
Java有个特殊的比较运算符:instanceof,可以检查是否为类的对象
4.逻辑运算符
- 与或非等等
- 逻辑运算符操作的都是boolean类型的变量
5.位运算符
类型:
- 左移右移
- 与运算
- 或运算
- 取反
交换两数的方法:
- 定义临时变量法
- 相加法
- 位运算符法
6.三元运算符
- 结构:(条件表达式)? 表达式1 : 表达式2
- 返回结果为Boolean类型
- 凡是可以使用三元运算符的地方,都可以转换为if-else,但是优先使用三元运算符
程序流程控制
1.顺序结构
程序从上到下逐条执行,中间没有任何判断和跳转
2.分支结构
(1)根据条件,选择性执行某段代码
- if-elseif-else
- switch-case
- switch-case可以转换为if-elseif-else
,优先使用switch-case
(2)switch-case-default:
- 根据switch表达式中的值,依次匹配各个case中的常量,一旦匹配成功跳转到相应的case语句中
- 一旦执行到break,跳出switch-case结构
- switch的表达式中,只能是如下的6种数据类型之一:byte,short,char,int,String类型
3.循环结构
(1)根据循环条件,重复性执行某段代码
- for循环
- while循环
- do-while循环
(2)循环语句四个组成部分:
- 初始化部分
- 循环条件部分
- 循环体部分
- 迭代部分
执行过程:初始化 -> 循环条件 -> 循环体 -> 迭代 -> 循环->条件 -> 循环体 -> 迭代 -> …… -> 循环条件
(3)for循环结构
1 | for(初始化; 循环条件; 迭代) {循环体} |
不在循环条件内限制次数的结构:for( ; ; )
(4)while循环结构
1 | 初始化 |
不在循环条件内限制次数的结构:while(true)
(5)do-while循环结构
1 | 初始化 |
do-while的执行过程与for,while不同,会至少执行一次循环体
执行过程: 初始化 -> 循环体 -> 迭代-> 循环条件-> 循环体 -> 迭代-> ……
(6)结束循环方式
- 循环条件中返回false
- 循环体中执行break:结束当前循环
- 循环体中执行continue:结束当次循环
- break和coninue在多重循环中,可以通过指定标识结束循环(默认跳出包裹关键字的最近一层循环)
4.Scanner输入
从用户输入处获得不同类型的变量,需要用到Scanner类
- 导入Scanner的包
- Scanner实例化
- 调用Scanner类相关方法来获取指定类型的变量
数组
1.数组概述
(1)数组是多个相同类型数据按照一定顺序排列的集合,并使用一个名字命名,通过编号的方式对数据进行统一管理
(2)数组相关概念:数组名,元素,索引,数组长度
(3)数组特点:
- 数组本身是引用数据类型,数组中的元素可以是任意数据类型
- 创建数据对象会在内存中开辟一整块连续的空间,而数组名中引用的是这块连续空间的首地址
- 数组的长度一旦确定,就不能修改
2.一维数组的使用
(1)数组的声明与初始化
1 | // 声明 |
(2)调用数组指定位置元素
(3)获取数组长度
(4)数组遍历
(5)数组元素默认初始化值
(6)数组的内存解析
内存结构:
- 栈(局部变量)
- 堆(对象,数组)
- 方法区(常量池,静态域)
3.多维数据的使用
Java语言里提供多维数组的语法
从数组底层运行机制来看,其实没有多维数组
(1)二维数组的声明与初始化
(2)调用数组指定位置的元素
(3)获取数组长度
(4)遍历二维数组
(5)数组元素默认初始化
外层数组初始化为地址值
内存元素初始化与一维数组初始化情况相同
(6)二维数组内存解析
4.数组算法
(1)数组元素赋值
二维数组打印杨辉三角
(2)数组元素计算
- 求最值
- 求和
- 求平均值
(3)数组复制,反转,查找
- java的数组间可以直接赋值,相当于把原数组地址备份了。这两个数组地址值相同,都指向了堆空间唯一的数组实体,这样不能称作数组的复制
- 数组的赋值,备份数组应当申请出与原数组相同大小的空间后,一一赋值
- 数组反转,使正数n的数与倒数n的数交换
- 查找:线性查找,二分查找(必须有序)
(4)数组排序
排序算法分类:
- 内部排序:排序过程都在内存中完成
- 外部排序:数据量巨大,必须借助外部存储器完成排序
5.数组工具类
Arrays即操作数组的工具类,包含了用来操作数组的各种方法
- 判断数组相等
- 输出数组
- 指定值填充到数组中
- 对数组进行排序
- 二分查找数组
6.数组常见异常
- 数组角标越界异常
- 空指针异常
Java面向对象
Java面向对象学习的三条主线:
- Java类及类的成员:属性,方法,构造器,代码块,内部类
- 面向对象三大特征:封装性,继承性,多态性(抽象性)
- 关键字
封装性
1.面向过程与面向对象
面向过程:强调功能行为以函数为最小单位,考虑怎么做
面向对象:将功能封装到对象中,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做
面向过程中为执行者,面向对象中为指挥者
2.类与对象
(1)概念
- 类是对一类事物的描述,是抽象的,概念上的定义
- 对象是事情存在的该类事物的个体,因而被称为实例(instance)
(2)类的设计
设计类其实就是设计类的成员
类的基本成员:
- 属性(field):对应类中成语变量
- 行为(method):对应类中的成员方法
- 除了上面两个常用的外,还有构造器,代码块,内部类
(3)对象
- 创建类的对象即类的实例化(new)
- 调用对象的属性,方法( 对象.属性 对象.方法 )
- 一个类的多个对象,每个对象都独立拥有一套类的属性。
- 将对象赋值给另一个对象,它们存有相同的对象地址值指向堆空间同一对象实体
(4)对象内存解析
- 堆:存放对象实例
- 栈:指虚拟机栈,用于储存局部变量
- 方法区:用于存储已被虚拟机加载的类的信息,常量,静态变量(即编译器编译后的代码等数据)
(5)成员变量与局部变量
成员变量(属性)定义在类中
局部变量是声明在方法内,方法形参,代码块内,构造器形参,构造器内部的变量
成员变量(属性)可以在声明时使用权限修饰符指明其权限(封装性)
局部变量不可以使用权限修饰符
成员变量(属性)根据其类型都有默认初始化值
局部变量没有初始化值,所以在调用之前,一定要显式赋值
成员变量(属性)加载到堆空间(非static时)
局部变量加载到栈空间
(6)对象数组(数组中存储对象的地址)
(7)匿名对象的使用
- 创建的对象,没有显式赋给一个变量名,即为匿名对象
- 特征:匿名对象只能使用一次
- 使用:可以把一个对象当作一个对象方法的形参,从而达到对象的匿名
3.类与方法
(1)方法:描述类应该有的功能(java内部类中提供了很多方法)
(2)方法声明:
1 | // 权限修饰符 返回值类型 方法名(形参列表){方法体} |
(3)权限修饰符
(4)返回值类型:return后返回指定的数据类型
(5)形参列表:方法可以申请多个形参
(6)方法的使用中,可以调用当前类的属性和方法,也可以实现递归调用
4.方法进阶
(1)方法重载
- 重载的概念:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或参数类型不同即可
- 重载的特定:与返回值类型无关,只看参数列表
- 两同一不同:1.同一个类,相同方法名 2.参数列表个数不同,参数类型不同
- 可变个数形参:允许直接定义和多个实参相匹配的形参,使用格式:
数据类型... 变量名
,形参会以数组的形式传入 - 可变个数形参必须声明在末尾,而且只能声明一个
(2)方法参数的值的传递机制
形参:方法定义时,声明的小括号内的参数
实参:方法调用时,实际传递给形参的数据
值传递机制:
- 参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值(实参和形参都在栈中,都是数据本身)
- 参数是引用数据类型,此时实参赋给形参的是变量所保存数据的地址值(实参和形参都在栈中,存储的是数据的地址,数据在堆中)
(3)递归方法
递归方法:一个方法体内调用它自身
- 方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制
- 递归一定要向已知方向递归,否则会变成无穷递归,类似于死循环
5.封装与隐藏
(1)封装的引出
程序设计追求:“高内聚,低耦合”:
高内聚:类的内部数据操作细节自己完成,不允许外部干涉(相同方法高度集中在一个类中)
低耦合:仅对外暴露少量方法用于使用(类与类之间的依赖关系降低)
(2)封装设计思想
隐藏该隐藏的,暴露该暴露的
- 对于类内某个属性使用private修改符,类外对该属性的操作,只能通过类内设置的方法(隐藏私有属性,暴露公共方法)
- 不对外暴露私有方法,单例模式
(3)访问权限修饰符
以上四种权限都可以用来修饰类的内部结构,属性,方法,构造器,内部类
修饰类只能使用:缺省,public
6.构造器
构造器(constructor)又称构造方法
(1)构造器作用
- 创建对象
- 初始化对象信息(属性方法)
(2)构造器说明
- 如果没有显示定义的构造器,系统默认提供一个空参构造器。一旦定义了构造器,系统不再提供空参构造器
- 定义构造器格式:权限修饰符 类名(形参列表){},无返回值
- 一个类中定义多个构造器,彼此构成重载
- 一个类中,至少会有一个构造器
7.JavaBean
JavaBean是可重用组件:
类是公共的
有个无参的公共构造器
有属性,且有对应的get,set方法
8.this
this表示当前对象,可以调用类的属性,方法,构造器
(1)this含义
- this在方法内部使用,即这个方法所属对象的引用
- this在构造器内部使用,表示该构造器正在初始化的对象
(2)this修饰属性和方法
- 照类的方法中,可以使用this调用当前对象属性或方法
- 可以用this来区分局部变量和属性
(3)this调用构造器
- 在类的构造器中,可以显式使用
this(形参列表)
的方式,调用本类中指定的其他构造器 - 构造器不能调用自己
9.package与import的使用
(1)package
- 为了更好实现项目中类的管理,提供包的概念
- 使用packae声明类或接口所属的包,声明在源文件的首行
- 同一个包下,不能命名同名接口和类
- JDK提供了包,其中包含常用的类和接口
(2)MVC设计模式
模型层model:主要用于处理数据
控制层controller:处理业务逻辑
视图层view:显示数据
(3)import关键字
- 在源文件中使用import可以导入指定包下的类,接口
- 声明在包声明和类声明之间
- 如果类和接口是在java.lang或本包下定义的,则可以省略import结构
继承性
1.继承性概念
(1)继承性的优点:
- 减少代码冗余,提高代码复用性
- 便于功能扩展
- 是多态性的继承
(2)继承性格式 class A extends B {}:
A:子类,派生类
B:父类,基类
子类继承父类后,就可以获得父类中声明的结构,属性,方法
子类可以在父类的基础上声明自己的属性或方法,实现功能的拓展
(3)继承规则
- 子类继承父类后,仍然获得了父类中私有的属性和方法,但是由于封装性,子类不能直接调用父类的结构
- 子类对象“拥有”父类对象的私有属性,但无法直接操作,不属于“继承“
- 支持单继承和多继承:一个子类只要一个父类,一个父类可以派生多个子类
- 子父类是相对的概念
- 如果没有显式声明一个类的父类,则此类继承于java.lang.Object类
2.方法的重写
子类根据要求对父类中继承来的方法进行改造,子类方法覆盖父类方法这个过程称为方法的重写
(1)重写:子类继承父类后,可以对父类中同名同参数的方法,进行覆盖操作
(2)重载与重写的区别
(3)重写规则
方法的声明:权限修饰符 返回值类型 方法名(形参列表){方法体}
子类重写的方法的方法名和形参列表要与父类被重写的方法相同
子类重写的方法的权限修饰符要不小于父类被重写的方法修饰符
子类不能重写父类中声明为private权限的方法
子类重写的方法返回值要与父类被重写的返回值相同
3.spuer
与super对应的是this,this代指本对象,super则代指本对象的父类
super的使用:
- super理解为:父类的
- super可以调用:属性,方法,构造器
- 在子类的方法或构造器中,可以通过super调用父类中声明的属性或方法
- super调用构造器,必须在子类构造器首行进行声明
- 在类的构造器中,this和super只能二选一(默认情况下调用的是父类中的空参构造器,即super())
4.子类对象实例化过程
- 创建子类对象,在堆空间,就会加载所有父类中声明的属性
- 通过子类构造器构造子类对象时,就会直接或间接调用了其父类的构造器,直至调用到java.lang.Object的空参构造器
- 虽然创建子类对象时调用了父类的构造器,但是自始至终就创建了一个对象,即new出来的子类实例
5.多态性
对象的多态性:父类的引用指向子类的对象(子类的对象赋给父类引用),可以直接应用到抽象类和接口上。方便传参时,可根据new的对象的不同传入不同的对象
多态就是同一个接口,使用不同的实例而执行不同操作
(1)多态的使用
- 有了对象多太性以后,我们在编译期,只能调用父类中的声明的方法,但在运行期,我们实际执行了子类重写父类的方法
- 编译看左(父类),运行看右(子类)
- 多态性使用前提:类的继承关系,方法重写
- 多态性主要用于方法,不适用于属性
(2)虚拟方法的调用
- 在多态的情况下,子类定义了父类同名同参数的方法,此时父类的方法被称为虚拟方法。
- 父类根据赋给它的不同子类对象,动态调用属于子类的该方法
- 这样的方法时无法在编译期确定的,只有到方法调用那一刻,解释运行器才会确定所要调用的具体方法(”动态绑定“)
6.向下转型
在多态中可知,虽然内存加载了子类的方法,但是由于类型是父类,所以编译时无法调用子类的属性和方法,只能调用父类中声明的属性和方法
为了调用子类特有的属性和方法,我们需要用到强制类型转换符,向下转型
(1)instanceof操作符
- 检验对象是否为某类的对象,返回值为Boolean
- 为了避免在向下转型时出现ClassCastException的异常,在向下转型前进行instanceof判断,为true才能进行向下转型
(2)向下转型规则
Object类的使用
1.==和equals的区别
(1)==运算符
- 可以使用在基本数据类型和引用数据类型中
- 如果比较的是基本数据类型,比较两个变量保存的数据是否相等(不一定类型相同)
- 如果比较的是引用数据类型,比较两个对象的地址值是否相同(两个引用是否指向同一个对象实体)
(2)equals()方法
- 是一个方法而非运算符
- 只适用于引用数据类型
- Object类中定义的equals()和==的作用是一样的,即比较比较两个对象的地址值是否相同
- 但是String,Date,File等类重写了Object类中的equals()方法,重写以后就是比较两个对象的“实体内容”是否相同了
2.toString()方法
- 当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
- String,Date,File等类重写了Object类中的toString()方法。使得在调用对象的toString时,返回“实体内容”信息
包装类的使用
1.包装类介绍
针对八种基本数据类型有相应的引用类型——包装类(封装类)
基本数据类型,包装类,String三者的相互转换:
2.数据类型转换
- 基本数据类型 > 包装类:调用包装类的构造器
- 包装类 > 基本数据类型:调用包装类的xxxValue()
- 基本数据类型,包装类 > String类型:1.使用字符串的连接运算 2.调用String的ValueOf()方法
- String类型 > 基本数据类型,包装类 :调用包装类的parseXxx(String s)方法
- 在JDK5.0后,基本数据类型和包装类已经可以实现自动拆箱与自动装箱
Static
static让类的实例共享一个属性或方法,无论新建多少个对象都会有这个静态的属性或方法
1.使用方法
- static:静态的
- static可以用来修饰:属性,方法,代码块,内部类
2.修饰属性
实例变量:每个对象都独立拥有一套类中的非静态属性,当修改其中一个对象的非静态属性时,不会导致其他对象相同属性值修改
静态变量:多个对象共享同一个静态变量,,当修改其中一个对象的静态属性时,其他对象的静态变量也随之改变
静态变量随着类的加载而加载,静态变量的加载要早于对象的创建
因为类只会加载一次,所以静态变量在内存中只会存在一份(方法区的静态域中)
实例变量与类变量的内存解析
3.修饰方法
- 静态方法,只能调用静态的方法或属性;非静态方法既可以调用非静态的方法或属性,也可以调用静态的方法或属性
- 静态方法随着类的加载而加载
4.静态的使用
- 确定一个属性用静态:属性可以被多个对象共享,不会随对象不同而不同
- 确定一个方法用静态:1.操作静态属性的方法用静态 2.工具类的方法,习惯上声明为静态
- 单例设计模式:对某个类只能存在一个对象实例(使用静态让其可以永久驻留内存),减少了系统性能开销
代码块
1.代码块概念
- 代码块的作用:用来初始化类,对象
- 代码块只能用static修饰
2.静态代码块
- 内部可以有输出语句
- 随着类的加载而执行,而且只执行一次
- 作用:初始化类的信息
- 一个类可以定义多个静态代码块,而且按声明先后顺序执行
- 静态代码块的执行要优先于非静态
- 静态代码块内只能调用静态的属性,静态的方法,不能调用非静态结构
3.非静态代码块
- 内部可以有输出语句
- 随着对象的创建而执行
- 每创建一个对象,就执行一次非静态代码块
- 作用:可以在创建对象时,对对象属性等进行初始化
- 一个类可以定义多个非静态代码块,而且按声明先后顺序执行
- 非静态代码块内可以调用静态的属性,静态的方法或非静态的属性,非静态的方法
2.属性赋值总结
- 默认初始化
- 显式初始化
- 在代码块中赋值
- 构造器初始化
- 通过
对象.属性
的方式赋值
按照由上到下的顺序先后执行
final
1.final概念
final意为最终的,可以禁止对修饰对象进一步的更改修饰
final可以用来修饰类,方法,变量
2.final修饰
- 被修饰的类不能被其他类所继承
- 被修饰的方法不能被重写
- 被修饰的变量被称为常量
- 被修饰的变量为属性:其可以被赋值的位置有:显式初始化,代码块中初始化,构造器中初始化
- 被修饰的变量为局部变量:尤其是修饰形参时,表名此时形参是一个常量。当我们调用此方法时,给常量形参赋一个实参,一旦赋值以后,就只能在方法体内使用此形参,但不能重新赋值
- static final 修饰属性:全局常量
抽象类与抽象方法
父类在设计中要求更一般,更通用。但一个父类设计得非常抽象时,以至于它没有任何具体实例,这样得类称为抽象类
1.abstract的使用
- abstract意为抽象的
- abstract可以修饰的结构:类,方法
- abstract不能修饰私有方法,静态方法,final的方法,final的类
2.抽象类特点
- abstract修饰的类不能实例化
- 抽象类中一定有构造器,便于子类实例化时调用
- 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关操作
- 抽象类的匿名子类
3.抽象方法特点
- 抽象方法只有方法声明,没有方法体
- 包含抽象方法的类,一定是一个抽象类
- 只有子类重写了父类所有的抽象方法后,此子类才能实例化(否则子类也是个抽象类)
4.抽象类应用
- 模板方法设计模式:抽象类作为多个子类的通用模板,子类在抽象类基础上进行拓展,改造
- 模板方法设计可以让固定通用的步骤在父类中写好,然后将易变的部分抽象出来,供不同的子类实现
接口
1.接口概述
- 有时必须从几个类中派生出一个子类,让子类继承它们所以的属性和方法,但是Java是不支持多重继承的(继承中子类只能有一个父类),这时候就需要用到接口
- 有时必须从几个类中抽取一些共同的行为特征。,而它们之间不存在继承关系,这时候也要用到接口
- 继承是一个“是不是”的关系,而接口则是“能不能”的关系(体现了如果你是/要……则必须能……)
- 接口的本质是契约,标准,规范
2.接口的使用
- 类和接口时并列的两个结构
- 接口使用interface来定义
- 接口中可以定义全局变量,抽象方法,静态方法,默认方法
- 接口中不能定义构造器,这意味着接口不能实例化
- 接口中定义的静态方法只能通过接口调用
- 通过实现类的对象,可以调用接口中的默认方法(而不是像抽象方法那样要先重写抽象类才能实例化,实现类也可以重写接口的默认方法)
- 接口可以通过让类去实现(implements)的方式来使用,如果实现类覆盖了接口所有抽象方法,则实现类可以实例化,如果没有则仍为一个抽象类
- Java可以实现多个接口,弥补了Java单继承性的局限性,格式:
class AA extends BB implements CC,DD,EE
- 接口与接口之间也可以继承,而且是多继承
3.接口的优点
- 接口的具体使用,体现多态性
- 接口实际上可以看作是一种规范,实现其接口的类必须按步骤(规范)实现相关的方法才能运行
- 接口的主要用途就是被实现类实现
4.接口应用
(1)代理模式:
为其他对象提供一种代理以控制对这个对象的访问
安全代理:可以屏蔽真实对象的直接访问
远程代理:通过代理类处理远程方法调用
延迟加载:先加载轻量级的代理对象,真正需要再加载真实的对象
(2)工厂设计模式
- 实现创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的
- 简单工厂模式:用来生产同一等级结构中任意产品(对于增加新产品,需要修改已有代码)
- 工厂方法模式:用来生成同一等级结构中的固定产品(支持增加任意产品)
- 抽象工厂模式:用来生产不同产品族的全部产品(对于增加新的产品,无能为力,支持增加产品族)
内部类
在A类中定义了另一个类B,则A为外部类,B为内部类
内部类有两种:成员内部类和局部内部类(此类在开发中很少被使用)
1.成员内部类
(1)作为外部类的成员
- 调用外部类的结构
- 可以被static修饰
- 可以被4种不同的权限修饰
(2)作为一个类
- 可以在类内定义属性,方法,构造器
- 可以被final修饰,表示此类不能被继承
- 可以被abstract修饰
(3)调用
- 实例化成员内部类的对象:(通过点运算符:
Person.Dog dog = new Person.Dog()
) - 在成员内部类种区分调用外部类的结构(属性方法):通过this
异常处理
异常体系结构
异常:程序执行中发生的不正常情况被称为“异常”(开发过程中的语法错误和逻辑错误不是异常)
1.异常分类
(1)Error型:Java虚拟机无法解决的严重问题,一般不编写针对性代码进行处理
- JVM系统内部错误
- 资源耗尽
(2)Exception型:其他因编程原因或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理
- 空指针访问
- 试图读取不存在的文件
- 网络连接中断
- 数组角标越界
Exception分类:编译异常,运行异常
2.解决方法
- 遇到错误终止程序运行
- 编写程序时,错误检测,错误消息的提示,以及错误的处理
- 捕获异常最理想的是在编译期间,但是有的错误只有在运行时才能发生(Exception分类:编译异常,运行异常)
- 异常处理中,要在可能出现错误的地方加上检测代码
- 过多的if-else封装会导致代码臃肿,可读性差,因此采用异常处理机制
- 在开发中运行异常比较常见,所以我们往往针对编译时的异常编写异常捕捉代码
- 手动生成一个异常对象,并用throw方法抛出
3.异常处理
(1)try-catch-finally
- try{可能出现异常的代码}catch{异常类型 变量名}finally{一定会执行的代码}
- finally是可选的
- try中的代码执行过程中一旦出现异常就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配
- 一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常处理,一旦处理完成就跳出当前try-catch结构,有finally则进入finally中
- 常用的异常对象处理方式(放在catch中):1.String getMessage():打印出异常信息 2.printStackTrace():打印整个错误对象信息(比较常用)
- 在try中声明的变量,再出了try结构后,就不能再被调用
try-catch相当于将一个编译时可能出现的异常,延迟到运行时出现
(2)finally解析
- finally声明的是一定会被执行的代码,无论有没有捕捉到异常都会执行
- 先数据库连接,输入输出流,网络编程Socket等资源,JVM是不能自动的回收,所以我们需要手动的进行资源的释放。此时的资源释放就要声明到finally中
(3)throws + 异常处理
- throws + 异常处理写在方法声明处,指明此方法执行时,可能会抛出的异常类型,一旦当方法体执行时,出现异常,仍然会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出
- try-catch-finally是真正的将异常处理掉了,throws的方式只是将异常抛出
多线程
多线程基础
1.基本概念
(1)程序与进程
- 程序是为了完成特定任务,用某种语言编写的一组指令的集合,即指一段静态代码,静态对象
- 进程是程序的一次执行过程,是一个动态的过程:有自身的产生,存在和消亡的过程
- 程序是静态的,进程是动态的。进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
(2)线程与进程
- 进程可以细化为线程,是一个程序内部的一条执行路径
- 一个Java应用程序至少有三个线程:main()主线程,gc()垃圾回收线程, 异常处理线程
- 线程作为调度和执行的单位,每个线程都拥有独立的运行栈和程序计数器,线程切换的开销小
- 一个进程中的多个线程共享相同的内存单元/内存地址空间(从同一堆中分配对象,可以访问相同变量和对象),这使线程间通信更加简洁,高效,但是也带来了安全隐患
(3)并发与并行
- 并行:多个CPU同时执行多个任务
- 并发:一个CPU同时执行多个任务
2.多线程的应用
(1)多线程的优点
- 提高应用程序的响应,对图形化界面更有意义
- 提高计算机相同CPU的利用率
- 改善程序结构,将长而复杂的进程分为多个线程独立运行,利于理解和修改
(2)多线程使用场景
- 程序需要同时执行多个任务
- 程序需要实现一些需要等待的任务(用户输入,文件读写操作,网络操作,搜索)
- 需要一些后台运行的程序
3.线程的创建与使用
(1)多线程的创建(继承Thread类)
创建一个继承于Tread类的子类
重写Thread类的run():声明此线程执行的操作
创建Thread类的子类对象
通过此对象调用start():启动当前线程;调用当前线程的run()
不能直接调用run()的方式启动线程
4.线程常用方法
start():启动线程,调用当前线程的run()
run():线程被调度时执行的操作
getName():返回线程名称
setName(String name) :设置线程名称
yield():释放当前CPU的执行权
join():在线程a中调用线程b的jion(),线程a进入阻塞状态直到线程b执行完
sleep(long millitime):让当前线程睡眠,当前线程是阻塞状态
isAlive():判断当前线程是否存活
stactic Thread currentThread():返回当前线程
5.线程优先级设置
(1)线程的调度
- 调度策略:抢占式,高优先级的线程抢占CPU
- 调度方法:对同优先级线程组成先进先出队列,使用时间片策略; 对高优先级,使用优先调度的抢占式策略
(2)线程优先级
- 线程优先级等级:MAX_PRIORITY(10),MIN_PRIORITY(1),NORM_PRIORITY(5)
- 涉及的方法:getPriority():返回线程优先级;setPriority(int newPriority):改变线程优先级
- 线程创建时继承父线程的优先级
- 低优先级只是获得调度的概率低,并非一定是高优先级线程后才被调用
6.创建多线程方式
(1)继承Thread方式
- 多线程时可用静态变量,让每个线程处理的数据都是在同一个地方,而不是又重新创建一个数据对象
(2)创建多线程的方式二:实现Runnable接口
- 创建一个实现Runnable接口的类(该实现类定义的变量也是线程公用的)
- 实现类去实现Runnable中的抽象方法:run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start()
(3)创建多线程的新方法:实现Callable接口
- call()可以有返回值
- call()可以抛出异常,被外面的操作捕获,获得异常的信息
- Callable支持泛型
(4)创建多线程的新方法:使用线程池
- 经常创建销毁,使用量特别大的资源,对性能影响很大
- 提前创建多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁,实现重复利用
- 好处:提高了响应速度; 降低资源消耗; 便于线程管理;
(5)比较两种创建多线程的方式
- 开发中优先现在实现Runnable接口的方式
- 原因:1.实现方式没有类的单继承性的局限性 2.实现的方式更适合来处理多个线程有共享数据的情况
- 联系:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中
线程安全问题
1.线程的生命周期
(1)线程状态
- 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它具备运行的条件,只是没有分配到CPU资源
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
- 阻塞:线程被人为挂起或执行输入输出操作时,让出CPU并临时中止执行的执行进入阻塞状态
- 死亡:线程完成了全部工作或线程被提前强制性中止或出现异常导致结束
(2)线程状态转换
2.线程同步
(1)多线程存在问题
多个线程执行的不确定性引起执行结构的不稳定
多个线程对数据的共享,会造成操作的不完整性,会破坏数据
线程的安全问题:(数据重复,数据错误)
线程安全问题出现的原因:当某个线程在操作数据时,尚未完成操作,其他线程就参与进来,也操作了车票
(2)线程安全问题的解决
当一个线程在操作共享数据时,其他线程不能参与进来,直到该线程完成操作(即时出现了阻塞也不能改变)
通过同步机制解决线程安全问题:1.同步代码块; 2.同步方法;
同步代码块:synchroized(同步监视器){需要同步的代码},操作共享数据的代码,即为需要同步的代码
同步监视器就是我们所熟悉的锁(任何一个类的对象都可以充当锁),多个线程必须共用一把锁
同步方法:在方法中使用synchroized修饰方法,然后再调用
同步方法:1.同步方法仍涉及到同步监视器,只是不需要显式声明; 2.非静态的同步方法监视器是this; 3.静态的同步方法监视器是当前类本身
操作同步代码时实质上是单线程过程,效率低(一个线程操作,其他线程等待)
3.线程死锁问题
(1)死锁原理
- 不同线程分别占用了对方需要的同步资源,都在等待对方放弃自己需要的同步资源,就会形成线程的死锁
- 出现死锁后,不会出现异常或错误提示,只是所有线程都处在阻塞状态,无法继续
(2)解决方法
- 专门的算法,原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
4.Lock(锁)
Java提供了更加强大的线程同步机制——通过显式定义同步锁对象来实现同步,其同样可以解决线程安全问题
(1)Lock与synchroized的异同
- 同:两者都可以解决线程问题
- 异:synchroized执行同步代码后,自动释放同步监视器; Lock需要手动启动同步(lock()),手动结束同步(unlock())
- Lock只有代码块锁,synchroized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并具有更好的扩展性
(2)使用顺序
Lock -> 同步代码块-> 同步方法
5.线程通信
(1)常用方法
- wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
- notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的线程
- notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
以上三个方法必须用在同步代码块或同步方法中
(2)sleep()和wait()的异同
- 同:一旦执行方法,都可以使得当前得线程进入阻塞状态
- 异:1.声明位置不同 2.调用要求不同 3.是否释放同步监视器
Java常用类
String类
1.String概述
- String类代表字符串
- Stirng是一个final类( 不可被继承),代表不可变的字符串序列
- 字符串是常量,值在创建后不能更改
- String对象的字符内容是存储在一个字符数组value[]中的
- 通过字面量的方式(区别于new)给一个字符串赋值,此时字符串声明在字符串常量池中(方法区)
- 字符串常量池不会存储相同内容的字符串,故栈中不同的局部变量如果赋相同字面量,实际指向方法区同一位置(地址相同)
2.String实现接口
- Serializable接口:表示字符串支持序列化
- Comparable接口:表示字符串可以比较大小
3.不可变性体现
- 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值
- 对现有的字符串进行连接操作、修改操作时,也需要重新指定内存区域赋值
4.String实例化
(1)String实例化方式:
- 通过字面量定义(数据存储在在方法区字符串常量池中,字符串作为常量,各变量共享一个数据空间):
String s1 = "2333"
- 通过new + 构造器(字符串数据仍然存储在常量池中,但是字符串的引用值存储在堆中,每创建一个字符串都会在堆中开辟新的空间存储字符串的引用值):
String s3 = new String("javaEE")
(2)String s3 = new String(“test”)对象个数
- 两个对象:一个是堆空间中的new结构,另一个char[]对应变量池中的数据
(3)字符串的拼接
常量与常量的拼接结果在常量池,且常量池中不会存在相同内容的变量
只要拼接的两个对象中其中一个为变量,结果位于堆中
拼接结果调用intern方法,返回值就在常量中
5.String常用方法
- length():返回字符串长度
- charAt():返回某索引处的字符
- isEmpty():判断是否为空字符串
- toLowerCase():转换为小写
- toUpperCase():转换为大写
- trim():返回字符串副本,忽略空白处
- equals():比较字符串内容是否相同
- concat():将指定字符串连接到此字符串结尾
- compareTo:比较两个字符串大小
- substring():截取出新的字符串
- endsWith:测试字符串是否以指定的后缀结束
- startsWith:测试此字符串是否以指定的前缀开始
- contains:字符串是否包含指定char值序列
- indexOf:返回指定子字符串第一次出现处的索引
- rplace(): 替换掉字符串中指定的子字符串
- replaceAll():替换掉字符串所匹配正则表达式的子字符串
- matches():判断字符串是否匹配给定的正则表达式
- split():根据给定的正则表达式的匹配拆分此字符串
6.String数据类型转换
(1)String与基本数据类型,包装类的转换
- String –> 基本数据类型,包装类:调用包装类的静态方法:parseXxx(str)
- 基本数据类型,包装类 –> String:调用String重载的valueOf(xxx)
(2)String与char[]的转换
- String –> char[]:调用String的toCharArray()
- char[] –> String:调用String的构造器
7.String,StringBuffer,StringBuilder
(1)比较
- String:不可变的字符序列,底层使用char[]存储
- StringBuffer:可变的字符序列,线程安全,效率低,底层使用char[]存储
- StringBuffer:可变的字符序列,线程不安全,效率高,底层使用char[]存储
- 执行效率:StringBuilder > StringBuffer > String,开发中推荐使用StringBuffer
(2)StringBuffer常用方法
- append():提供很多append()方法,用于进行字符串拼接
- delete():删除指定位置的内容
- replace():替换指定索引位置的内容
- insert():在指定位置插入xxx
- reverse():把当前字符序列逆转
Java比较器
Java中的对象,正常情况下只能进行比较:== 或 != ,不能使用 > 或 < 的。但是在开发场景中我们需要对多个对象进行排序,就要用到两个接口:Comparable 或 Comparator
1.Comparable接口
(1)Comparable接口使用
- 像String,包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出比较两个对象大小的方式
- 可以重写compareTo(obj)的规则
(2)自定义类实现Comparable自然排序
- 对于自定义类,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法,在其中指明如何排序
- 可以重写compareTo(obj)的规则:
- 如果当前对象大于obj,返回正整数
- 如果当前对象小于obj,返回负整数
- 如果当前对象大于obj,返回零
2.Comparator接口
- 重写compare(Object o1,Object o2)方法,比较o1和o2大小
- 方法返回正整数,则表示o1大于o2
- 返回0,表示相等
- 返回负整数,表示o1小于o2
- Comparable接口的实现类的对象在任何位置都可以比较大小
- Comparator接口属于临时性的比较
其他常用类
1.时间类
2.System类
3.Math类
4.BigInteger和BigDecimal
枚举类与注解
枚举类
1.枚举类的使用
- 类的对象只能是有限个,确定的,我们称此类为枚举类
- 当需要定义一组常量时,强烈建议使用枚举类
- 如果枚举类只有一个对象,则可以作为单例模式实现
2.枚举类的定义
方式一:jdk5之前,自定义枚举类
- 声明对象属性(private final修饰)
- 私有化类的构造器
- 提供当前多个枚举类的多个对象
- 其他诉求:获得枚举类对象的属性,提供toString()
方式二:jdk5,可以使用enum关键字定义枚举类
创建当前枚举类的对象,多个对象用“,”隔开,末尾对象”;”结束
声明对象属性(private final修饰)
提供当前多个枚举类的多个对象
其他诉求:获得枚举类对象的属性
3.Enum类中常用方法
values:返回枚举类型的对象数组
valueOf(String str):可以把一个字符串转为对应枚举类对象
toString():返回当前枚举类对象常量名称
4.枚举类实现接口
- 实现接口,在enum类中实现抽象方法
- 让枚举类的对象分别实现接口中的抽象方法
注解
1.注解说明
- 注解是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行响应操作。
- 利用注解,我们可以在不改变原有逻辑的情况下,在源文件嵌入一些补充信息。框架 = 注解 + 反射 + 设计模式
2.注解示例
- 生成文档的相关注解
- 在编译时进行格式检查
- 跟踪代码依赖性,实现替代配置文件功能
- spring框架中关于“事务”的管理
- Junit单元测试中也有大量注解的使用
3.JDK内置基本注解
- @Override:限定重写父类方法,在编译前校验该方法是否是重写父类的
- @Deprecated:用于表示所修饰的元素(已过时)。通常是因为所修饰的结构危险或者存在更好的选择
- @SuppressWarnings:抑制编译器警告
4.自定义注解
- 注解声明为@interface
- 内部定义成员,通常用value表示
- 可以指定成员的默认值,使用default定义
- 如果自定义注解没有成员,表明是一个标识作用
- 如果注解有成员,在使用注解时,需要指明成员的类
- 自定义注解必须配送注解的信息处理流程(使用反射)才用意义
- 自定义注解通过都非指明两个元注解:Retention,Target
5.基本元注解
元注解:对现有的注解进行解释说明的注解
- Rentention:指定所修饰的Annotation的生命周期,只有生命为RUNTIME生命周期的注解才能通过反射获取
- Target:用于指定被修饰的Annotation能用于修饰哪些程序元素
- Documented:表示所修饰的注解被javadoc解析时,保留下来
- Inherited:被它修饰的Annotation将具有继承性
通过反射可以获取注解信息
集合
集合,数组都是对多个数据进行存储操作的结构,简称Java容器(这里的存储主要是指内存层面的存储。不涉及到持久化的存储)
Java集合就像一种容器,可以动态地把多个对象的引用放入容器中
数组简介
1.数组存储缺点
- 一旦初始化以后,其长度就不可修改了
- 数组中提供的方法非常有限,对于添加,删除,插入数据等操作,非常不便,效率不高
- 获取数据实际元素个数没有现成的属性和方法
- 数组存储数据特点:有序,可重复。对于无序,不可重复的需求,不能满足
Collection接口
单列数据,定义了存储一组对象的方法集合
1.Collection接口继承树
2.Collection接口常用方法
- add(Object e):将元素e添加到集合coll中
- size():获得添加的元素个数
- addAll(Collection coll1):将coll1集合中的元素添加到当前的集合中
- isEmpty():判断当前集合是否为空
- clear():清空集合元素
- contains(Object obj):判断当前集合是否包含obj
- containsAll(Collecion coll1):判断colls中的所有元素是否都存在于当前集合中
- remove(Object obj):从当前集合中移除obj元素
- remove(Collecion coll1):从当前集合中移除coll1中所有元素(移除差集)
- retain(Collecion coll1):获取当前集合与coll1集合的交集,并返回给当前集合
- equals(Object obj):要想返回true,需要当前集合和形参集合的元素都相同
- hasCode():返回当前对象的哈希值
- toArray():集合—->数组(数组—>集合:调用Araays类的静态方法asList())
3.集合遍历
(1)迭代器Iterator接口
- Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合的元素
- 迭代器模式:提供一种方法访问一个容器对象中各个元素,而又不需要暴露该对象的内部细节(迭代器,为容器而生)
- 迭代器Iterator内部方法:hasNext()和next()
- 迭代器执行原理:hasNext()判断是否还有下个元素; next()让指针下移
- 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都集合在第一个元素之前
- remove()可以在遍历时,删除集合中的元素(区别于Collection接口的remove方法,这个要先调用next() )
(2)foreach循环遍历集合元素
- 其内部仍然调用了迭代器
- for(集合元素的类型 局部变量:集合对象)
- foreach可以遍历数组和集合
4.List接口
存储有序的,可重复的数据(“动态”数组)
(1)List三个实现类ArrayList,LinkedList,Vector的异同
- 同:都实现了List接口,存储数据特点相同:存储有序的,可重复的数据
- 异:
- ArrayList:底层使用Object[] elementData存储;作为List接口主要实现类,线程不安全,效率高;
- LinkList:底层使用双向链表存储;对于频繁的插入,删除操作,使用此类效率比ArrayList高;
- Vector:List接口古老实现类;线程安全,效率低;底层使用Object[] elementData存储
(2)List常用方法
- 增:add(Object obj)
- 删:remove(int index)
- 改:set(int index, Object ele)
- 查:get(int inedx)
- 插:add(int index, Object ele)
- 长度:size()
- 遍历方法:
- Iterator迭代器方法
- 增强for循环
- 普通循环
5.Set接口
存储无序的(set的底层也是数组,但是存储的物理顺序是在申请空间的随机位置),不可重复的数据(数学概念的“集合”)
Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法
(1)Set接口的实现类
- HashSet:作为Set接口的主要实现类,线程不安全,可以存储null值
- LinkedHashSet:作为HashSet的子类,遍历其内部数据时,可以按照添加顺序遍历
- TreeSet:可以按照添加对象的指定属性进行排序(底层为红黑树)
(2)Set解析
- 无序性:不等同于随机性,存储的数据在底层数组中并发按照数组索引的顺序添加,而是根据数据的哈希值决定的
- 不可重复性:相同的元素只能添加一个
(3)HashSet元素插入过程
Map接口
双列数据,保存具有映射关系“key-value”的集合( y=f(x) )
1.Map实现
有两种实现类:HashMap(主要)和Hashtable(古老)
- HashMap:Map的主要实现类,线程不安全,效率高,可以存储null的key和value(底层为:数组+链表+红黑树)
- LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历(有一对指向前和指向后节点的指针,对频繁的遍历操作,执行效率高于HashMap)
- TreeMap:保证按照添加的key-value对进行排序,实现排序遍历(key自然排序或自定义排序,底层为红黑树 )
- Hashtable:作为古老的实现类,线程安全,效率低,不能存储null的key和value
- Properties:常用于处理配置文件,key 和 value都是String类型
2.Map接口继承树
3.Map结构理解
- Map中的key:无序的,不可重复的,使用Set存储所有的key ———–>key所在的类要重写equals()和hashCode()
- Map中的value:无序的,可重复的,使用Collection存储所有的value————> value所在类要重写equals()
- 一个键值对:key-value构成一个Entry对象
- Map中的entry:无序的,不可重复的,使用Set存储所有的entry
4.HashMap底层实现原理
(1)jdk7
HashMap map = new HashMap()
:实例化后,底层创建了长度为16的一维数组Entry[] tablemap.put(key1,value1)
:首先调用key所在;类的hasCode计算key哈希值,得到在Entry数组的存放位置- 如果此位置上的数据为空,则添加成功
- 如果此位置上的数据不为空,比较key和已存在的数据的哈希值
- 如果key的哈希值与已存在的数据哈希值都不相同,则添加成功
- 如果与其中一个数据哈希值相同,继续比较其value值:如果返回false,则添加成功,如果返回true,则使用替换相同key的value值
(2)jdk8
数组+链表+红黑树
5.Map常用方法
(1)添加,删除,修改操作
- Object put(Object key, Object value):将指定的key-value添加到当前map对象中
- void putAll(Map m):将m中所有的key-value放到当前map中
- Object remove(Object key):移除指定key-value对,并返回value
- void clear():清空当前map中所有数据
(2)元素查询操作
- Object get(Object key):获取指定key对应的value
- boolean containsKey(Object key):是否包含指定key
- boolean containsValue(Object Value):是否包含指定Value
- int size():返回map中key-value个数
- boolean isEmpty():判断当前map是否为空
- boolean equals(Object obj):判断当前map和参数对象obj是否相等
(3)元视图操作的方法
- Set keySet():返回所有key构成的Set集合
- Collection values():返回所有value构成的Collection集合
- Set entrySet():返回所有key-value对构成的Set集合
6.TreeMap
用于需要排列的key-value,可以定制排序
Collections工具类
Collections是一个操作Set,List和Map等集合的工具类
1.常用方法
(1)排序操作(抽象方法)
- reverse(List):反转List中元素的顺序
- shuffle(List):对List集合元素进行随机排序
- sort(List):根据元素的自然顺序对指定List集合元素按升序排序
- sort(List, Comparator):根据指定的Comparator产生的顺序对List集合进行排序
- swap(List, int i, int j ):将指定list集合的i和j处元素进行交换
(2)查找替换
- Object max(Collection):根据自然顺序返回最大元素
- Object max(Collection, Comparator):根据Comparator指定顺序,返回最大元素
- Object min(Collection)
- Object min(Collection, Comparator)
- int frequency(Collection, Object):返回指定集合元素出现次数
- void copy(List dest, List src):将src中的内容复制到dest中
- boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换List对象的所有旧值
(3)同步控制
Collections类提供多个synchromizedXxx()方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发范围集合时的线程安全问题
将线程不安全的集合类型转换为线程安全的类型
数据结构
1.概述
(1)数据间的逻辑关系:
- 集合
- 线性关系(对应Java中的顺序表,链表,栈,队列)
- 树形结构(对应java中的二叉树)
- 网状结构(对应java中的图)
2.真实结构
- 顺序表(静态数据结构):Array,ArrayList
- 链表(动态数据结构):LinkedList
3.抽象结构
- 栈
- 队列
- 树
- 图
泛型与File
泛型
1.泛型概述
(1)泛型的出现
- 泛型相当于标签,来确定一个容器实际存放什么类型的对象
- 把元素的类型设计为一个参数,这个类型参数叫做泛型
(2)泛型说明
- 泛型允许在定义类,接口时通过一个标识表示类中某个属性的类型或者某个方法的返回值及参数类型
- List
表明List只能存放字符串类型对象
(3)泛型相对Object
- 解决元素储存的安全性问题
- 解决获取数据元素时,需要类型强制转换的问题
2.泛型的使用
(1)在集合中使用泛型
- ArrayList
list = new ArrayList () - 编译时就会进行类型检查,保证数据安全
- 避免强转操作
(2)泛型使用总结
在实例化集合类时,可以指明具体的泛型类型
指明完以后,在集合类或接口中函数定义类或接口时,内部结构使用到类的泛型的位置,都指定为实例化时的泛型类型
泛型的类型必须是类,不能是基本数据类型,需要用到基本数据类型的位置用包装类代替
3.泛型结构
(1)泛型类
- 类的内部结构就可以使用类的泛型
- 如果定义了泛型类,实例化时没有指明类的泛型,默认为Object类型
- 如果定义的类带泛型,建议在实例化时指明类的泛型
- 子类在继承泛型的父类时指明了泛型类型,则实例子类对象时,不在需要指明泛型
- 泛型不同的引用不能互相赋值
- 静态方法中不能使用类的泛型
- 子类除了指定后保留父类的泛型,也可以增加自己的泛型
(2)泛型方法
- 在该方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系(泛型方法所属的类是不是泛型类都没有关系)
- 泛型方法在调用时,指明泛型参数的类型
- 泛型方法,可以声明为静态
(3)通配符
File类
File类属于IO流的内容,可以新建,删除,重命名文件和目录,但是File列不能访问文件本身,访问文件内容需要输入\输出流
1.常用方法
(1)获取功能
(2)重命名功能
(3)判断功能
(4)创建,删除方法
IO流
IO流原理与分类
1.IO原理
- I/O技术用于处理设备之间的数据传输,如读写文件,网络通讯
- 在 Java中,数据的输入输出以流(stream)的方式进行
- java.io包下提供各种“流”类和接口,以获得不同种类的数据,并通过标准的方法输入或输出数据
2.流的分类
操作数据单位分类:字节流(InputStream/OutputStream),字符流(Reader/Writer)
流向分类:输入流,输出流
流的角色分类:节点流,处理流(应用上为缓冲流)
文件流操作
1.读取文件
从硬盘文件读入数据到内存
实例化File类对象,指明操作文件
提供具体流
数据的读入方法与结束判断
1. read():返回读入的一个字符,如果达到文件末尾,返回-1)
2. read(char[] cbuf ):read()方法的重载,返回每次读入cbuf数组的字符个数,如果达到文件末尾返回-1
流的关闭(往往要通过异常处理保证流资源一定可以执行关闭操作)
读入的文件一定要存在,否则会报出异常
2.写出文件
从内存中写出数据到硬盘文件
- 提供File类对象,指明写出到的文件
- 如果File对应文件不存在,则自动创建此文件
- 如果File对应文件存在,则有覆盖和追加两种形式的操作
- 提供FileWriter的对象,用于数据写出
- 流的构造器为:FileWriter(file,false) / FileWriter(file),则对原有文件进行覆盖
- 流的构造器为:FileWriter(file, ture),则在源文件上追加内容
- 写出操作
- 流资源关闭
3.字符流与字节流
- 操作数据单位分类:字节流(InputStream/OutputStream),字符流(Reader/Writer)
- 字符流不能处理非文本文件,对于文本文件使用字符流处理
- 字节流不能处理文本文件,对于非文本文件使用字节流处理
4.缓冲流
- 缓冲流比于节点流,提供了流的读取,写入的速度
- 使用缓冲流要比使用节点流,多一步造缓冲流的操作(在节点的基础上制造缓冲流)
- 资源关闭上,先关闭外层的流,再关闭内层的流(关闭外层流的同时,内层流可以自动关闭,内层流的关闭程序可以省略)
- 缓冲流可以提高文件读入写出速度,因为内部提供了一个缓冲区
5.随机存取文件流
- RandomAceessFile类实现了DataInput、DataOutput两个接口,该类既可以读也可以写
- RandomAceessFile类支持随机访问的形式,程序可以直接跳到文件任意地方来读写文件
- RandomAceessFile对象包含一个记录指针,用于标识当前读写位置
6.NIO2中文件读写
- NIO2是对Java原有IO流的升级,可以以更加高效的方式进行文件读写操作
- Java API提供了两套NIO,一套是针对标准输入输出的NIO,另一套是网络编程NIO
- File类访问文件功能比较有限,后来又引入类Path接口对File类进行了升级
- 在NIO.2中还提供了Files、Paths工具类
转换流
1.转换流概述
- 转换流为处理流的一种,提供了在字节流和字符流之间的转换
- Java API提供了两个转换流:
- InputStreamReader:将InputStream转换为Reader
- OutputStreamWriter:将Writer转换为OutputStream
- 字节流中的数据都是字符时,转换成字符流操作更高效
- 转换流常用来处理文件乱码问题,实现编码和解码的功能
2.转换流的使用
- InputStreamReader:将一个字节的输入流转换为字符的输入流
- OutputStreamWriter:将一个字符的输出流转换为字节的输出流
- 提供了在字节流和字符流之间的转换,用于解码、编码
对象流
1.对象流概述
ObjectinputStream和ObjectOutputSteam
用于存储和读取基本数据类型数据或对象的处理流,可以把Java的对象写入数据源中,也能把对象从数据源中还原回来
序列化:ObjectinputStream保存基本数据类型或对象的机制
反序列化:ObjectOutputSteam读取基本数据类型或对象的机制
ObjectinputStream和ObjectOutputSteam不能序列化static和transient修饰的成员变量
2.对象的序列化
- 对象序列化机制把内存中的Java对象转换成平台无关的二进制流,从而允许把二进制流保存在磁盘上或通过网络传输
- 程序获取二进制流,可以反序列化恢复为Java对象
- 序列化的好处在于可以将任何实现了Serializable接口的对象转换为字节数据,保证其保存和传输时可被还原
其他基本流
1.标准输入输出流
- System.in:标准的输入流,默认从键盘输入
- System.out:标准的输出流,默认从控制台输出
- 可以通过setIn(InputStream is) / setOut(PrintStream ps)方式重新指定输出和输入的默认设备
2.打印流
- 实现将基本数据类型转化成字符串输出
- 打印流PrintStream和PrintWriter
- 提供了一系列重载的print()和println()方法,用于多种数据类型输出
3.数据流
方便操作(读取和写出)Java语言的基本类型和 String的数据
DataInputStream 和 DataOutputStream分别套接在InputStream和OutputStream子类的流上
网络编程
基础内容
1.网络编程定义
- 网络编程的目的:直接或间接通过网络协议与其他计算机实现数据交换,进行通讯
- 定位主机上特定的应用(IP和端口号)
- 可靠高效数据运输(通过网络通信协议TCP/IP)
TCP网络编程
UDP网络编程
URL网络编程
反射机制
反射机制概述
1.反射的含义
(1)含义
- 反射是动态语言的关键,反射机制允许程序在执行期间借助Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法
- 加载完类后,堆内存的方法区中就会产生一个Class类型的对象,通过该对象我们可以看到类的结构信息,则就是反射机制的本质
(2)动态语言与静态语言
- 动态语言在运行时可以根据条件改变自身结构(PHP,Python)
- 静态语言运行时结构不可变(Java,C, C++)
- Java是准动态语言,我们可以利用反射机制,字节码操作获得类似动态语言的特性,让编程更加灵活
2.反射功能
通过反射可以调用类的私有结构,反射常常用于对象不确定的情况下(反射的动态性)
- 运行时判断任意对象所属类
- 运行时构造任意一个类的对象
- 运行时判断一个类所具有的成员变量和方法
- 运行时获取泛型信息
- 运行时任意调用对象的成员变量和方法
- 运行时处理注解
- 生成动态代理
3.反射主要API
- java.lang.Class:代表一个类
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造器
Class类的理解
1.类的加载过程
- 程序经过javac.exe命令后,会生成一个或多个字节码文件(.class结尾)接着我们使用java.exe命令对某个字节码文件进行解释运行,相当于将某个字节码文件加载到内存中,此过程就被称为类的加载
- 加载到内存中的类,我们称为运行时类,此运行时类就作为Class的一个实例(类是Class的对象)
- Class的实例对应着一个运行时类
2.获得Class实例的方法
调用运行类时的属性:.class
通过运行时类的对象,调用getClass
调用Class静态方法:forName(String classPath)(常用)
使用类的加载器:ClassLoader
3.Class实例对应结构
有哪些类型有Class对象
- class:外部类,成员,局部内部类,匿名内部类
- interface:接口
- []:数组
- enum:枚举
- annotation:注解
- primitive type:基本数据类型
- void
类的加载的理解
1.类的加载过程
2.ClassLoader的理解
(1)类的加载器流程位置
(2)类的加载器的作用
- 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后再堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口
- 类缓存:一旦某个类被加载到类加载器中,它会维持(缓存)一段时间,不够JVM垃圾回收机制可以回收这些Class对象
(3)Java定义的类的加载器类型
(4)ClassLoader加载配置文件
读取配置文件的方法:
- 使用properties集合+IO流读取
- 使用ClassLoader读取
创建运行时类的对象
平常我们一般使用类的构造器(new)创建类的对象,我们也可以通过反射创建对应的运行时类的对象
1.创建流程
- 调用newInstance()方法,创建对应的运行时类的对象
- 内部调用了运行时类的空参构造器
2.创建要求
- 运行时类必须提供空参的构造器
- 空参过的构造器的访问权限通常设置为public
- 便于通过反射,创建运行时类的对象
- 便于子类继承此运行类时,默认调用super()时,保证父类有此构造器
获得运行时类的结构
1.获得属性结构
- getFields():获取当前运行时类及其父类中声明public访问权限的属性
- getDeclaredFields():获取当前运行时类中声明的所有属性(不包含父类中声明的属性)
2.获取方法结构
- getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
- getDeclaredMethods():获取当前运行时类中声明的所有方法(不包含父类中声明的方法)
3.获取方法的内部结构
- 获取方法声明的注解
- 权限修饰符
- 返回值类型
- 方法名
- 形参列表
4.获取构造器结构
- getConstructors():获取当前运行时类中声明为public的构造器
- getDeclaredConstructors():获取当前运行时类中声明的所有构造器
5.其他获取内容
- 获得运行时类的父类及父类泛型
- 获得运行类的实现的接口,所在包,注解
调用运行时类的结构
1.调用指定属性
- 创建运行时类的对象
- 获得指定的属性(通常用getDeclaredFields(),因为可以获得私有属性)
- 保证当前属性是可访问的(setAccessible方法)
- 设置当前属性的值(set方法)
- 获取当前属性的值(get方法)
- 获取当前属性的值
2.调用指定方法
- 创建运行时类的对象
- 获取指定的某个方法
- 保证当前方法是可访问的(setAccessible方法)
- 调用invoke方法执行
3.调用指定构造器
- 获得指定构造器
- 保证此构造器可访问
- 调用此构造器创建运行时类的对象
反射应用:动态代理
1.代理设计模式原理
使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理
最好可以通过一个代理类完成全部的代理功能
2.动态代理概述
动态代理是指用户通过代理类来调用其他对象的方法,并且是在程序运行时更加动态创建 目标类的代理对象
动态代理使用场合:
- 调试
- 远程方法调用
动态代理相比静态代理的优点:抽象角色中接口声明的所有方法都被转移到调用处理器一个集中的方法中处理(可以更加灵活和统一的处理众多的方法)
3.动态代理与AOP
Java8的其他特性
Lambda表达式
Lambda表达式是一个 ,可以将其理解为一段可以传递的代码,其本质是作为函数式接口的实例
1.Lambda表达式的使用
- -> :lambda操作符或箭头操作符
- -> 左边:lambda形参列表(即接口中的抽象方法的形参列表)
- -> 右边:lambda体(即重写抽象方法的方法体)
- 如果lamba形参列表只有一个参数,其一对()也可以省略
- lambda体应该使用一对{}包裹,如果lambda只有一条执行语句,可以省略{}和return
2.lambda使用分类
- 无参,无返回值
1 | Runnable r1 = ()-> {System.out.println("Hello,Lambda");}; |
- 需要一个参数,无返回值
1 | Consumer<String> con = (String str)->{System.out.println(str);}; |
- 数据类型可以省略,可由编译器推断得出(类型推断)
1 | Consumer<String> con = (str)->{System.out.println(str);}; |
- 只需要一个参数时,参数的小括号可以省略
1 | Consumer<String> con = str->{System.out.println(str);}; |
- 需要两个或两个以上的参数,多条执行语句,并且可以有返回值
1 | Comparator<Integer> com = (x,y)->{ |
- 当Lambda只有一条语句时,return与大括号可以省略
1 | Comparator<Integer> com = (x,y)->Integer.compare(x,y); |
函数式接口
1.函数式接口含义
如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口
我们可以通过Lambda表达式创建该接口的对象
使用@Functionallnterface注解,可以检测是否为函数式接口
2.Java内置函数式接口
Consumer
:消费型接口 Supplier
:供给型接口 Function<T,R>:函数型接口
Predicate
:断定型接口
方法引用与构造器引用
1.方法引用介绍
- 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用
- 方法引用就是Lambada表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法
- 实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致
- 有以下三种主要使用情况
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
2.构造器引用
- 与方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致
- 抽象方法的返回值类型即构造器所属的类的类型
Stream API
1.Stream概述
- Stream是Java中处理集合的关键抽象概念,它可以对集合进行复杂的查找,过滤,映射数据等操作(类似于SQL之于数据库)
- NoSQL的数据(MongDB,Radis等)需要在Java层面处理,所有需要Stream API
- Stream 与 Collection集合的区别:Collection是一种静态的内存数据结构,其面向内存用于数据存储;Stream有关计算,其面向CPU通过CPU实现计算
2.Stream须知事项
- Stream自己不会存储元素
- Stream不会改变源对象,相反,它们会返回一个持有结果的新Stream
- Stream操作时延迟执行的,他们会等到需要结果时才执行
3.Stream操作过程
创建Stream:一个数据源(如:集合,数组)获取一个流
中间操作:一个中间操作链,对数据源的数据进行处理
终止操作:一旦执行终止操作,就执行中间操作链,并产生结果,后面不会再被使用
4.Stream实例化
(1)创建Stream
- 通过集合创建
- 通过数组创建
- 通过Stream的of()创建
- 创建无限流
(2)中间操作
- 筛选与切片
- 映射
- 排序
(3)Stream的终止操作
- 匹配与查找
- 归约
- 收集
Optional类
1.Optional类概述
Optional
类是一个容器类,可以保存类型T的值,代表这个值存在(或者仅仅保存null,表示这个值不存在) Optional可以更好表达这个概念,并且可以避免空指针异常