java 计算机语言
java 最大特性
跨平台
java语言开发的程序 在任何操作系统的电脑上都能够运行;
前提是; 该电脑上安装了 jvm java虚拟机
jvm java vitrul machine java虚拟机
jre java runtime envrionment java运行时环境
jdk java development kit java 开发工具包
jdk > jre > jvm
jdk 包含 jre 和 开发工具
jre 包含jvm 和核心类库
安装jdk 一路下一步
配置环境变量
--------------------------------
第一个java程序
1 新建一个文本文档 , 后缀名改成功 java
2 写代码
public class 类名{
public static void main(String[] args){
System.out.println("Hello World!!!");
}
}
3 打开黑窗口, 进入.java文件所在目录
1 window + r
2 输入 cmd
3 进入目录
4 编译 javac 类名.java
5 运行 java 类名
0 idea
工程 project 模块 module 包 package 类 class
1 运算符
1 算术运算符
+ - * / %
注意: int类型的数据做除法 / ,得到的结果一定还是int类型了
int x = 10;
int y = 4;
int z = x/y;
double d = x/y;
% 3 % 5 = 3
3 / 5 = 0...3
2 赋值运算符
= += -= *= /=
int x = 3;
x += 2; // x = x+2;
short s = 3;
s += 2;
s = (short)(s+2);
//隐含了强制数据类型转换
3 ++ --
对变量 自增 或 自减1
int x = 3;
//x++;
//++x;
sout(x);
x++ ++x 这个表达式本身也是有值得;
本身的值多少呀 ? x++ 这个表达式的值就是x原来的值 ++x 就是自增1之后的值;
4 关系运算符 (比较运算符)
== != > < >= <=
1 == 和 = 区别;
2 关系运算符链接的表达式结果一定是 boolean类型, true false
5 逻辑运算符
& | ! ^
&& ||
1 短路和单的相同点 最终结果一定一样
2 前边表达式的结果已经能够决定总体了,短路 链接的话,后边就不判断了,代码就不执行了,
单 链接的话,后边依然判断,代码依然执行,
实际开发中一般使用 短路
1 && 2 && 3 && 4
1 | 2 | 3 | 4
6 三元运算符
数据类型 变量名 = boolean结果的表达式? 结果1 : 结果2;
获取键盘输入的值
1 导包 import java.util.Scanner;
2 Scanner sc = new Scanner(System.in);
3 int x = sc.nextInt();
流程控制
顺序结果
分支结构 if switch
if(结果一定是boolean类型){
语句体
}
if(结果一定是boolean类型){
}else{
}
if(){
}else if(){
}else if(){
}else{
}
循环结果 for while do...while
1 switch
switch(表达式){
case 值:
......
break;
case 值:
......
break;
case 值:
......
break;
}
sout("over")
1 switch(表达式) byte short int char String 枚举
2 表达式的结果类型 和 case 值得类型必须一致;
3 case 后边的值不能重复
4 一般case 后会跟着break, 除非需要用到break穿透
2 循环结果
1 初始化
2 条件判断
3 循环语句
4 条件控制
for(1 初始化 ;2 条件判断;3 条件控制){
4 循环语句
}
执行流程 1 2 4 3 2 4 3 2 4 3.....
//1 - 100
for(int i=1;i<=100;i++){
sout(i)
}
初始化
while(条件判断){
循环语句
条件控制
}
初始化
do{
循环语句
条件控制
}while(条件判断)
三种循环的相同和不同
1 for while 先判断 后执行 do{} while() 先执行 后判断
2 for 和 while 可以互换,一般知道具体循环次数的时候建议使用for循环,
不知道循环次数 建议使用while
死循环
for(;;){}
while(true){
}
两个关键字
continue: 循环体中剩余的代码就不执行了,继续循环
break 直接跳出当前循环
循环嵌套
if(条件){
if()
switch(){}
for(){
if(){}
while(){}
}
}
1 循环嵌套
打印几行几列的矩形
外循环控制行 内循环控制列
九九乘法表
2 数组
什么是数组? 数组就是 用来保存 一堆相同类型数据的容器;
1 定义数组
int[] arr;
int arr[];
2 初始化数组
动态初始化
int[] arr = new int[5];
静态初始化
int[] arr = new int[]{13,4,5,6}
int[] arr = {3,4,5,6};
数组一旦定义,数组中存放的元素类型就就确定了;
数组一旦初始化, 数组的长度就确定了
3 数组内存图
new 出来的变量(对象) 一定在堆内存中开辟了空间
4 常见的两种错误
数组索引越界 AarryIndexOutofBoundException
空指针异常 NullPointerException
5 常见操作
1 数组遍历
2 获取最值
3 查找元素
4 数组反转
5 数组排序
1 数组
用来保存一堆 相同类型数据的容器
数组定义和初始化
定义 String[] arr; String arr[];
初始化
动态
int[] arr = new int[3];
静态
int[] arr2 = new int[]{1,2,3}
数组一旦定义,数组中存放的元素类型就确定了;
数组一单初始化,数组的长度就确定,
2 如何操作数组?
通过索引操作数组中的元素
int x = arr2[1];
arr2[1] = 88;
3 两个常见问题
数组索引越界 ArrayIndexOutofBoundsException;
空指针异常 NullPointerException
4 数组的常见操作
1 遍历
2 获取最值
3 查找元素
4 反转数组
5 数组排序 (选择排序)
--------------------
函数 方法
1 概念: 把 实现了某功能的代码封装起来,起个名字,然后通过名字就可以调用这一段代码;
修饰符 返回值类型 方法名(参数列表){
方法体;
return 结果;
}
1 修饰符 public static
2 返回值类型 这个方法执行完毕,返回结果的类型 byte short int long float double boolean char String int[]
3 方法名 驼峰命名法, 我们可以通过名称调用方法
4 参数列表 定义方法需要指定形式参数,调用方法传递实际参数
5 方法体 就是根据方法名调用时,具体执行的代码
6 return 如果定义方法时,定义了返回值类型,那么方法中必须返回与之对应的结果,
即便方法上没有定义返回值类型,方法中也可以出现return,作用直接跳出方法,后边不能跟结果;
方法
修饰符 返回值类型 方法名(参数列表){
方法体
return 结果;
}
1 修饰符 public static
2 方法执行完毕返回结果的类型, 如果没有返回值,写void
3 方法名 首字母小写
4 参数列表 定义方法-形式参数,定义方式参考变量, 方法名(int x,double y,String ss)
调用方法 实际参数 具体的值 或变量 方法名(12,8.88,ss) String ss ="";
5 方法体 调用方法要执行的具体代码
6 return 跳出方法,不论任何一定返回一个于返回值类型一致的结果,
方法上返回值类型是void, 方法中依然可以有return关键字用来跳出方法,不过return后边就不需要结果了;
方法重载
1 同一个类中
2 方法名相同
3 参数列表不同 (参数个数,参数类型) 与参数名称没有关系
4 与返回值类型无关
class AA{
public static void add(int a,int b){}
public static void add(int x,int v){}
}
不同类型的参数传递
基本数据类型的参数传递是副本传递,方法中对参数进行修改,对原来的变量没有任何影响
引用数据类型的参数传递是地址传递,方法中对参数进行修改,对原来的变量有影响
class BB{
psvm(){
int a = 3;
changeNum(x);
sout(a);
int[] arr = {2,3,4};
changeArr(arr);
sout(arr[0])
}
public static void changeNum(int x){x=22;}
public static void changeArr(int[] arr){arr[0]=22;}
}
1 类 和 对象
对象: 是具体的,万物皆对象
类: 是抽象的 对一组相似事物共性内容(属性,行为)的抽取
2 创建类
通过变量描述属性
通过方法描述行为
public class Phone{
String color;
String type;
//构造方法
public void call(){}
public void sendMessage(){}
}
创建对象
类名 变量名 = new 类名();
变量名.属性;
变量名.方法();
3 对象内存图
1 只要new 一定会在堆内存中开辟空间
2 把对象直接赋值给对象,那么这两个对象就指向了堆内存中的同一块空间,
对任何一个对象的修改,都会影响另外一个
Student s1 = new Student();
s1.name = "aa";
s1.age = 23;
Student s2 = s1;
s2.name = "bb";
s2.age = 66;
sout(s1.name+","+s1.age)
4 封装
封装三步走
1 私有属性 private
2 提供get set方法 get获取值 有返回值没有参数 set 设置值 没有返回值有参数
3 可以在get set方法中添加存取限制
5 this
一个类的方法中,访问到该类的普通成员变量 或者是成员方法的时候,其实前边省略了一个关键this.
this. 代表当前对象
创建对象调方法; this. 就代表调用方法的对象;
6 构造方法
1 格式
修饰符 类名(参数列表){
//初始代码
this.name = name;
this.age = age;
}
2 功能
对象初始化 初始化就指给对象的属性赋值;
3 执行时机
对象一创建,就会调用与之相对应的构造函数
4 注意事项
1 如果一个类中没有明确定义构造函数,那么系统会自动创建一个空参数的构造
2 如果我们手动创建了构造函数,系统就不会创建了
3 建议,一个类中既创建空参数构造,有创建多参数构造;
5 构造方法重载
同一个类中 方法名相同 参数列表不同 与返回值类型无关
6 构造方法和普通方法的区别
执行时机
普通方法, 被调用的时候就会执行, 随时调用,随时执行
构造方法,只会在对象创建的时候执行一次;
1 一个标准类的写法
1 成员变量
私有 private String name
2 成员方法
set/get
3 构造方法
public Student(){}
public Student(String name,int age){
//this.name = name;
//this.age = age;
}
psvm(){
Student s1 = new Student("aaa",23);
}
2 如何 查询API
java API jdk > jre > jvm
Scanner sc = new Scanner(System.in)
String ss = sc.next();
Random
String ss = "caadaac"
1 char c = ss.charAt( 5 )
2 int index = ss.indexOf("aa") ;
3 int index = ss.indexOf("aa",2) ;
4 char[] cs = ss.toCharArray();
5 int index = ss.lastIndexOf("aa",3);
6 int chang = ss.length();
7 String s1 = ss.substring(2)
8 String s1 = ss.substring(2,6)
9 String s1 = ss.concat("cc"); //ss + "cc";
10 boolean flag = ss.equals("vv");
11 String s1 = ss.replace("","");
12 ss.trim();
13 boolean flag = ss.contains("cc");
14 ss.toLowerCase();
15 ss.toUpperCase();
16 boolean flag = ss.startsWith("abc")
17 ss.endsWith("");
18 ss.getBytes()
19 ss.getChars();
20 equalsIgnorecase("")
21 int num = ss.compareTo("abc"); //a b c de
22 int num = ss.compareToIgnorecase("abc");
23 ss.isEmpty(); ss.length() == 0;
24 String[] arr = ss.split("#");
25 repaceAll("","")
26 repaceFirst("","");
1 static 静态的
1 static 修饰成员变量
public static String onlineNum = 23;
多了一种调用方式
类名.onlineNum;
对象.onlineNum;
加了static的成员变量 ,在内存中只保留一份,只要一个对象对其修改,会影响到所有对象
2 static 修饰成员方法
静态方法 多了一种调用方法
类名.方法名() 推荐
实例方法
一个类中的方法什么情况下应该定义成静态的?
当方法中没有访问到类中的实例成员的时候,就可以定义成静态了
工具类中的方法都是 静态方法
1 静态只能访问静态
2 实例都可以访问
3 静态方法中不能出现 this关键字
3 静态代码块
1 static{
}
2 执行时机
随着类的加载而执行,并且只执行一次
3 作用
给类中的静态成员进行初始化;
4 static应用
单例模式
饿汉式
1 私有构造
2 提供一个public static 的本类对象 = new 对象;
/** a、定义一个单例类 */
public class SingleInstance {
/** c.定义一个静态变量存储一个对象即可 :属于类,与类一起加载一次 */
public static SingleInstance instance = new SingleInstance ();
/** b.单例必须私有构造器*/
private SingleInstance (){ System.out.println("创建了一个对象");}
}
懒汉式
1 私有构造
2 提供一个private static 的本类对象; 没有new
3 对外提供一个public static 方法返回对象
/** 定义一个单例类 */
class SingleInstance{
/** 定义一个静态变量存储一个对象即可 :属于类,与类一起加载一次 */
public static SingleInstance instance ;
/** 单例必须私有构造器*/
private SingleInstance(){}
/** 必须提供一个方法返回一个单例对象 */
public static SingleInstance getInstance(){return ...; }
}
//饿汉式
// public static Student s = new Student();
// private Student() {
// }
//懒汉式
private static Student s ;
private Student() {
}
public static Student getStuden() {
if (s == null) {
s = new Student();
}
return s;
}
2 继承
1 java 类中 只支持单继承
2 java 类中 只支持单继承 但是可以多层继承
3 java 子类中可以继承父类非私有的属性和方法
4 java中的任何类都直接或间接的继承自Object
有争议 私有属性方法不管能不能继承,但都不能使用,
公共的静态成员, 不管能不能继承,但都能使用,
包
方便管理类, 把功能相同的类放到同一个包下;
创建包
包名都小写, 一般公司域名倒过来写 com.baidu.mapper
导入包
import java.utils.Scanner;
一个类中使用了不同包下同名的类
只能导一个
另外一个需要通过包名进行访问
权限修饰符
修饰类 public 缺省
修饰类中成员 public > protected > 缺省 > private
本类 本包其它类 其他包子类 其他包无关类
private √ × × ×
缺省 √ √ × ×
protected √ √ √ ×
public √ √ √ √
final 最终的
可以修饰 方法 变量 类
修饰方法 方法不能被重写
修饰变量 该变量不能重新赋值,
基本数据类型
引用数据类型 地址不能变,对象的属性可以变
常量
被final修饰的变量就是常量
枚举
我们可以通过枚举指定选项;
抽象类
被abstract修饰的类就是抽象类
1 一个类中具备某功能,就应该定义方法
2 但是不知道方法的具体实现,不写方法体,定义成抽象方法,
3 如果一个类中有抽象方法的话,这个类也必须是抽象的;
4 抽象类就是要被继承的,创建子类,子类要么实现抽象类中的所有抽象方法,要么自己也是抽象的;
5 创建子类对象调方法,
6 抽象类和普通类的不同;
抽象类是不能实例化对象,
7 为了不让通过对象调用抽象方法,干脆就不让创建对象了
8 如果一个类是抽象类,不一定有抽象方法
9 和abstract互斥的三个关键字
final
static
private
final 修饰类 该类不能被继承
static
private 私有化 子类不可以重写
声明static说明可以直接用类名调用该方法
声明abstract说明需要子类重写该方法
如果同时声明static和abstract,用类名调用一个抽象方法肯定不行
------------------------------
this() super();
this()
1 在本类的构造方法中,调用与之相对应的本类构造方法;
2 this() 只能是构造方法的第一条执行语句
3 this() 在构造方法中不能互相调用和循环调用,总有一个构造方法中是没有this();
super()
1 在本类的构造方法中,调用与之相对应的父类构造方法;
2 super() 只能是构造方法的第一条执行语句
3 子类的构造方法第一行,如果我们没有写 系统也会自动生成 super() 调用父类空参数构造
(除非第一行有this() )
4 不管如何,创建子类构造的时候一定会调用到父类构造;
模板方法模式 : 使用场景说明:当系统中出现同一个功能多处在开发,而该功能中大部分代码是一样的,只有其中部分可能不同的时候。
模板方法模式实现步骤
把功能定义成一个所谓的模板方法,放在抽象类中,模板方法中只定义通用且能确定的代码。
模板方法中不能决定的功能定义成抽象方法让具体子类去实现。
1 接口
接口是一种规范,是一种能力
什么时候应该继承? 什么属于什么! Cat Animal
什么时候应该实现? 什么具备什么能力, Dog extends Animal 接飞盘,导盲
1 接口中成员
常量 固定修饰符 可以省略 final public static
抽象方法 public abstract
2 一个类可以实现多个接口 (在继承了一个 类的同时还可以实现多个接口)
3 接口和接口 是继承关系, 接口之间可以多继承
jdk1.8以后接口中可以定义 default修饰的方法 和 static方法
1.9 以后接口中就可以定义私有方法 private
2 多态
1 多态的概念
一个对象的多种形态
2 多态的前提
在继承 / 实现关系中
3 多态的表现形式
左边定义父类/接口对象 右边new子类/实现类
Animal a1 = new Dog();
IUsb u1 = new Mouse();
4 多态的使用注意事项
1 调用方法 编译看左边 运行看右边
2 调用变量 都看左边
5 多态的典型应用
1 定义方法时,参数类型是父类, 调用方法就可以传递任何子类对象进来了;
2 定义方法时,返回值类型是父类,方法中就可以返回任何子类对象了;
6 多态转型
自动类型转换/强制类型转换 (向上转型/向下转型)
自动 范围小的赋值给范围大的 强制 范围大的变量或值 赋值给范围小的变量
父类 大 子类 小
Animal a1 = new Cat(); 自动类型转换 向上转型
Cat c1 = (Cat)a1; 强制类型转换 向下转型
向下转型,一定要保证 要转成的类型 和 对象的本来类型一致,如果不一致会报错,ClassCastException
7 instanceof 运算符
if(a1 instanceof Cat){}
3 内部类
静态内部类
成员内部类
局部内部类
匿名内部类
接口 抽象方法
创建接口实现类,实现抽象方法,
创建实现类对象,调用实现以后的方法;
假如只需要创建一次对象的话,可以通过匿名内部类
IUsb work()
psvm{
IUsb u1 = new IUsb(){
@Override
public void work(){
}
};
u1.work();
}
4 常用API
Object
toString()
equals();
1 常用API
Object 所有类的老祖宗
String toString();
boolean equals(Object obj);
hashCode();
notify();
wait();
方法重写规则
1 同名 同参数 同返回值类型
2 重写方法的权限不能小于父类
3 子类重写方法不能抛出比父类更多的异常;
Objects
static equals(obj1,obj2);
isNull();
Math
向上取整 ceil()
向下取整 floor()
四舍五入 round() Math.round(-1.5) // -1
pow(2,3)
System
exit(0)
currentTimeMillis()
BigDecimal
进行精度运算
sout(0.01 + 0.09);
BigDecimal.valueOf("0.01");
加减乘除
1 把BigDecimal 类型的变两个转成 double
double d = bigDecimal.doubleValue()
BigDecimal b1 = 10.0;
BigDecimal b2 = 3.0;
2 b1.divide(b2,?,?)
第一个? 保留几位小数 第二个?
时间类型的
Date
Date d1 = new Date();
Date d2 = new Date(long类型的毫秒值);
d2.getTime(); 获取时间对象对应的毫秒值
before after;
SimpleDateFormat
SimpleDateFormat s1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss S");
1 把date对象转成想要格式的字符串
String ss = s1.format(date)
2 把固定格式的字符串转成时间对象
Date d1 = s1.parse("date")
Calendar
单独获取年月日 时分秒
Calendar c1 = Calendar.getInstance();
包装类
int-- Integer char --> Character
int x = 10;
Integer i = x;
int xx = i;
//把字符串解析成数字
Integer.parseInt("123");
Double.parseDouble("8.88")
正则表达式
特殊字符标识的数据格式
Arrays
操作数组
Arrays.toString(arr)
Arrays.sort(arr)
Arrays.sort(arr,Comartor<>)
一、 Arrays工具类
public static?int?binarySearch?(int[]?a, int?key)
二分搜索数组中的数据,存在返回索引,不存在返回-1
必须是排好序的数组
二、算法
1、冒泡排序
每次从数组中找出最大值放在数组的后面去。 相邻位置相比
确定总共需要做几轮: 数组的长度-1.
每轮比较几次:
当前位置大于后一个位置则交换数据
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
2、 选择排序
每轮选择当前位置,开始找出后面的较小值与该位置交换 挨个比较
确定总共需要选择几轮: 数组的长度-1.
控制每轮从以前位置为基准,与后面元素选择几次。
for (int i = 0; i < arr.length - 1; i++) {
for (int j = i + 1; j < arr.length; j++) {
if (arr[i] > arr[j]) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
System.out.print(i + "--" + j);
}
System.out.println();
}
3、 二分法查找
基本查找 在数据量特别大的时候,基本查找从前往后寻找的性能是很差的!
二分查询性能好,二分查找的前提是必须是排好序的数据。
二分查找相当于每次去掉一半的查找范围
结论:二分查找正常的检索条件应该是开始位置min <= 结束位置max
数组的二分查找的实现步骤是什么样的?
定义变量记录左边和右边位置。
使用while循环控制查询(条件是左边位置<=右边位置)
循环内部获取中间元素索引
判断当前要找的元素如果大于中间元素,左边位置=中间索引+1
判断当前要找的元素如果小于中间元素,右边位置=中间索引-1
判断当前要找的元素如果等于中间元素,返回当前中间元素索引。
public static int search(int[] arr,int n){
int min = 0;
int max = arr.length - 1;
while (min <= max){
int minIndex = (min + max) / 2;
if (n > arr[minIndex]) {
min = minIndex + 1;
}else if (n < arr[minIndex]){
max = minIndex - 1;
}else {
return minIndex;
}
}
return -1;
}
三、 lambda表达式
Lambda表达式是JDK 8开始后的一种新语法形式。
作用:简化匿名内部类的代码写法。
简化格式
(匿名内部类被重写方法的形参列表) -> { 被重写方法的方法体代码。}
注:-> 是语法形式,无实际含义
注意:Lambda表达式只能简化函数式接口的匿名内部类的写法形式
什么是函数式接口?首先必须是接口、其次接口中有且仅有一个抽象方法的形式
1、Lambda表达式的基本作用?
简化函数式接口的匿名内部类的写法。
2、Lambda表达式有什么使用前提?
必须是接口的匿名内部类,接口中只能有一个抽象方法
注意:通常我们见到的函数式接口上都有一个@FunctionalInterface注解,
标记该接口必须是满足函数式接口。
Lambda表达式的省略写法(进一步在Lambda表达式的基础上继续简化)
参数类型可以省略不写。
如果只有一个参数,参数类型可以省略,同时()也可以省略。
如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写,同时要省略分号!
如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写。
此时,如果这行代码是return语句,必须省略return不写,同时也必须省略";"不写
四、
集合的特点
集合的大小不固定,启动后可以动态变化,类型也可以选择不固定。集合更像气球。
集合非常适合做元素的增删操作。
1、数组和集合的元素存储的个数问题。
数组定义后类型确定,长度固定
集合类型可以不固定,大小是可变的。
2、数组和集合存储元素的类型问题。
数组可以存储基本类型和引用类型的数据。
集合只能存储引用数据类型的数据。
3、数组和集合适合的场景
数组适合做数据个数和类型确定的场景。
集合适合做数据个数不确定,且要做增删元素的场景
Collection单列集合,每个元素(数据)只包含一个值。
Map双列集合,每个元素包含两个值(键值对)。
注意:前期先掌握Collection集合体系的使用。
Collection集合特点
List系列集合:添加的元素是有序、可重复、有索引。
ArrayList、LinekdList :有序、可重复、有索引。
Set系列集合:添加的元素是无序、不重复、无索引。
HashSet: 无序、不重复、无索引;LinkedHashSet: 有序、不重复、无索引。
TreeSet:按照大小默认升序排序、不重复、无索引。
注意:集合和泛型都只能支持引用数据类型,不支持基本数据类型,所以集合中存储的元素都认为是对象。
1、集合的代表是?
Collection接口。
2、Collection集合分了哪2大常用的集合体系?
List系列集合:添加的元素是有序、可重复、有索引。
Set系列集合:添加的元素是无序、不重复、无索引。
3、如何约定集合存储数据的类型,需要注意什么?
集合支持泛型。
集合和泛型不支持基本类型,只支持引用数据类型
Collection集合
Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。
public boolean add(E e) 把给定的对象添加到当前集合中
public void clear() 清空集合中所有的元素
public boolean remove(E e) 把给定的对象在当前集合中删除
public boolean contains(Object obj) 判断当前集合中是否包含给定的对象
public boolean isEmpty() 判断当前集合是否为空
public int size() 返回集合中元素的个数。
public Object[] toArray() 把集合中的元素,存储到数组中
遍历集合
迭代器遍历概述
遍历就是一个一个的把容器中的元素访问一遍。
迭代器在Java中的代表是Iterator,迭代器是集合的专用遍历方式。
Iterator<E> iterator()
返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引
boolean hasNext()
询问当前位置是否有元素存在,存在返回true ,不存在返回false
E next()
获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界
迭代器的默认位置在哪里。
Iterator<E> iterator():得到迭代器对象,默认指向当前集合的索引0
集合对象每次调用iterator()方法都会得到一个全新的迭代器对象
2、迭代器如果取元素越界会出现什么问题。
会出现NoSuchElementException异常。
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
增强for循环
内部仍是调用迭代器
for(集合元素数据类型 变量名 : 数组或者Collection集合) {
//在此处使用变量即可,该变量就是元素
增强for可以遍历哪些容器?
既可以遍历集合也可以遍历数组。
for(String s : list){
System.out.println(s);
}
Lambda表达式
default void forEach(Consumer<? super T> action):
class Myconsumer implements Consumer<String>{
@Override
public void accept(String s) {
System.out.println("---"+s);
}
}
Myconsumer m1 = new Myconsumer();
list.forEach(m1);
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println("----"+s);
}
});
list.forEach((String s) -> {
System.out.println("-----"+s);
});
list.forEach( s -> System.out.println("------"+s));
数据结构概述
数据结构是计算机底层存储、组织数据的方式。是指数据相互之间是以什么方式排列在一起的。
通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率
常见的数据结构
栈 可以想像成弹匣 先压得子弹后发射
栈数据结构的执行特点
后进先出,先进后出
数据进入栈模型的过程称为:压/进栈
数据离开栈模型的过程称为:弹/出栈
队列 可以想象成 排队买东西 先排队先走
常见数据结构之队列
先进先出,后进后出
数据从后端进入队列模型的过程称为:入队列
数据从前端离开队列模型的过程称为:出队列
数组 数组是一种查询快,增删慢的模型
查询速度快:查询数据通过地址值和索引定位,查询任意数据耗时相同。(元素在内存中是连续存储的)
删除效率低:要将原始数据删除,同时后面每个数据前移。
添加效率极低:添加位置后的每个数据后移,再添加元素。
链表
分单向列表 和 双向列表
链表的特点
链表中的元素是在内存中不连续存储的,每个元素节点包含数据值和下一个元素的地址。
链表中的元素是游离存储的,每个元素节点包含数据值和下一个元素的地址。
链表查询慢。无论查询哪个数据都要从头开始找
链表增删相对快
二叉树
只能有一个根节点,每个节点最多支持2个直接子节点。
节点的度: 节点拥有的子树的个数,二叉树的度不大于2 叶子节点 度为0的节点,也称之为终端结点。
高度:叶子结点的高度为1,叶子结点的父节点高度为2,以此类推,根节点的高度最高。
层:根节点在第一层,以此类推
兄弟节点 :拥有共同父节点的节点互称为兄弟节点
二叉查找树
二叉查找树又称二叉排序树或者二叉搜索树。
特点:
1,每一个节点上最多有两个子节点
2,左子树上所有节点的值都小于根节点的值
3,右子树上所有节点的值都大于根节点的值 目的:提高检索数据的性能。
规则:
小的存左边
大的存右边
一样的不存
平衡二叉树
二叉树查找存在的问题:
问题:出现瘸子现象,导致查询的性能与单链表一样,查询速度变慢!
平衡二叉树是在满足查找二叉树的大小规则下,让树尽可能矮小,以此提高查数据的性能。
平衡二叉树的要求
任意节点的左右两个子树的高度差不超过1,任意节点的左右两个子树都是一颗平衡二叉树
平衡二叉树在添加元素后可能导致不平衡
基本策略是进行左旋,或者右旋保证平衡。
平衡二叉树-旋转的四种情况
左左
当根节点左子树的左子树有节点插入,导致二叉树不平衡
将二叉树右旋 同时查看里面的节点是否符合二叉查找树规则
左右
当根节点左子树的右子树有节点插入,导致二叉树不平衡
将左子树左旋 然后整体右旋 同时查看里面的节点是否符合二叉查找树规则
右右
当根节点右子树的右子树有节点插入,导致二叉树不平衡
将二叉树左旋 同时查看里面的节点是否符合二叉查找树规则
右左
当根节点右子树的左子树有节点插入,导致二叉树不平衡
将右子树右旋 然后整体左旋 同时查看里面的节点是否符合二叉查找树规则
红黑树
红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构。
1972年出现,当时被称之为平衡二叉B树。1978年被修改为如今的"红黑树"。
每一个节点可以是红或者黑;红黑树不是通过高度平衡的,它的平衡是通过“红黑规则”进行实现的。
黑红规则
每一个节点或是红色的,或者是黑色的,根节点必须是黑色。
如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)。
对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
添加节点
添加的节点的颜色,可以是红色的,也可以是黑色的。
默认用红色效率高。
添加的节点为根节点时,直接变成黑色就可以了
其父节点为黑色,则不需要做任何操作。
其父节点为红色,叔叔节点也是红色
将“父节点23”设为黑色,将“叔叔节点18”设为黑色
将“祖父节点20”设为“红色”。
如果祖父节点为根节点,则将根节点再次变成黑色。
红黑树小结
红黑树不是高度平衡的,它的平衡是通过"红黑规则"进行实现的
规则:
每一个节点或是红色的,或者是黑色的,根节点必须是黑色
如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的;
如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
红黑树增删改查的性能都很好
各种数据结构的特点和作用是什么样的
队列:先进先出,后进后出。
栈:后进先出,先进后出。
数组:内存连续区域,查询快,增删慢。
链表:元素是游离的,查询慢,首尾操作极快。
二叉树:永远只有一个根节点, 每个结点不超过2个子节点的树。
查找二叉树:小的左边,大的右边,但是可能树很高,查询性能变差。
平衡查找二叉树:让树的高度差不大于1,增删改查都提高了。
红黑树(就是基于红黑规则实现了自平衡的排序二叉树)
List系列集合特点
ArrayList、LinekdList :有序,可重复,有索引。
有序:存储和取出的元素顺序一致
有索引:可以通过索引操作元素
可重复:存储的元素可以重复
ArrayList
当代码 创建ArrayList对象并添加第一个元素的时候,系统会默认初始化一个长度为10的数组
当添加的元素超过数组长度的时候,会自动进行扩容,每次扩容后的长度是原来长度的1.5倍
void add(int index,E element) 在此集合中的指定位置插入指定的元素
E remove(int index) 删除指定索引处的元素,返回被删除的元素
E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
E get(int index) 返回指定索引处的元素
List系列集合
Collection
接口list set 继承接口Collection
接口list与set区别 list有序 可重复 有索引 set 无序 不可重复 无索引
实现类 Arraylist与Linkedlist 实现接口list;
ArrayList集合底层原理
第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组。
添加超过十个 增加数组是默认长度的1.5倍
ArrayList 底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作。 (查询快,增删慢.)
LinkedList 底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API。 (查询慢,增删快,尤其是首尾的增删)
public void addFirst?(E e) 在该列表开头插入指定的元素
public void addLast?(E e) 将指定的元素追加到此列表的末尾
public E getFirst?() 返回此列表中的第一个元素
public E getLast?() 返回此列表中的最后一个元素
public E removeFirst?() 从此列表中删除并返回第一个元素
public E removeLast?() 从此列表中删除并返回最后一个元素
集合异常问题
从集合中找出某个元素并删除的时候可能出现一种并发修改异常问题
//ConcurrentModificationException
迭代器遍历不能通过集合方法进行删除 需要用迭代器内方法remove进行删除
增强for循环和Lambda表达式遍历都不可解决并发修改异常问题.
普通for循环遍历并删除元素需要用递减
泛型
泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
泛型的格式:<数据类型>; 注意:泛型只能支持引用数据类型。
集合体系的全部接口和实现类都是支持泛型的使用的。
泛型的好处:
数据类型的统一。
把运行的时候出现的问题提前到编译的时候,不会出现强制类型转换异常问题,已经确定了编译阶段时的数据类型。
泛型可以定义到哪里?
类后面 泛型类 泛型类的格式:修饰符 class 类名<泛型变量>{ } 编译阶段可以指定数据类型
把出现泛型变量的地方全部替换成传输的真实数据类型
方法申明上 泛型方法 泛型方法的格式:修饰符 <泛型变量> 返回值类型 方法名称(形参列表){} 方法中可以使用泛型接收一切实际类型的参数
把出现泛型变量的地方全部替换成传输的真实数据类型。
接口后面 泛型接口 泛型接口的格式:修饰符 interface 接口名称<泛型变量>{} 泛型接口可以让实现类选择当前功能需要操作的数据类型
实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。
通配符: ?
? 可以在“使用泛型”的时候代表一切类型。
? extends Car: ?必须是Car或者其子类 泛型上限
? super Car : ?必须是Car或者其父类 泛型下限
Set系列集合特点
无序:存取顺序不一致
不重复:可以去除重复
无索引:没有
带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。
Set集合实现类特点
HashSet : 无序、不重复、无索引。
LinkedHashSet:有序、不重复、无索引。
TreeSet:可排序、不重复、无索引。
Set集合的功能上基本上与Collection的API一致
HashSet底层原理
HashSet集合底层采取哈希表存储的数据。
哈希表是一种对于增删改查数据性能都较好的结构。
哈希表的组成
JDK8之前的,底层使用数组+链表组成
JDK8开始后,底层采用数组+链表+红黑树组成。
哈希值 :是JDK根据对象的地址,按照某种规则算出来的int类型的数值。
Object类的API:public int hashCode?():返回对象的哈希值
对象的哈希值特点:同一个对象多次调用hashCode()方法返回的哈希值是相同的
默认情况下,不同对象的哈希值是不同的。
HashSet -------- 1.7版本原理解析:数组 + 链表 +(结合哈希算法)
步骤
1.创建一个默认长度16的数组,数组名table
2.根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)
3.判断当前位置是否为null,如果是null直接存入
4.如果位置不为null,表示有元素,则调用equals方法比较
5.如果一样,则不存,如果不一样,则存入数组
JDK 7新元素占老元素位置,指向老元素
JDK 8中新元素挂在老元素下面
结论:哈希表是一种对于增删改查数据性能都较好的结构。
JDK1.8版本开始HashSet原理解析
底层结构:哈希表(数组、链表、红黑树的结合体)
当挂在元素下面的数据过多时,查询性能降低,从JDK8开始后,当链表长度超过8的时候,自动转换为红黑树。
结论:JDK8开始后,哈希表对于红黑树的引入进一步提高了操作数据的性能。
哈希表的详细流程
创建一个默认长度16,默认加载因为0.75的数组,数组名table
根据元素的哈希值跟数组的长度计算出应存入的位置
判断当前位置是否为null,如果是null直接存入,如果位置不为null,表示有元素,则调用equals方法比较属性值,如果一样,则不存,如果不一样,则存入数组。
当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍
问题:如果希望Set集合认为2个内容相同的对象是重复的应该怎么办?
重写对象的hashCode和equals方法。
LinkedHashSet集合概述和特点
有序、不重复、无索引。
这里的有序指的是保证存储和取出的元素顺序一致
原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序
每次存元素都会有双向链表所以LinkedHashSet集合是有序的
LinkedHashSet集合的特点和原理是怎么样的?
有序、不重复、无索引
底层基于哈希表,使用双链表记录添加顺序。
TreeSet集合概述和特点
不重复、无索引、可排序
可排序:按照元素的大小默认升序(有小到大)排序。
TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
注意:TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。
TreeSet集合默认的规则
对于数值类型:Integer , Double,官方默认按照大小 从小到大 进行升序排序。
对于字符串类型:默认按照首字符的编号升序排序。
对于自定义类型如Student对象,TreeSet无法直接排序。
结论:想要使用TreeSet存储自定义类型,需要制定排序规则
自定义排序规则
TreeSet集合存储对象的的时候有2种方式可以设计自定义比较规则
方式一
让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则。
让类implements Comparable<>在类中重写compareTo方法
例如
public int compareTo(Prod o) {
int c = Double.compare(this.price, o.price);
if (c == 0) {
return this.name.compareTo(o.name);
}
return c;
}
方式二
TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则。
例如
TreeSet<Prod> set = new TreeSet<Prod>((o1,o2) -> {
int c = Double.compare(o1.getPrice(), o2.getPrice());
if (c == 0) {
return -(o1.getName().compareTo(o2.getName()));
}
return c;
});
两种方式中,关于返回值的规则:
如果认为第一个元素大于第二个元素返回正整数即可。
如果认为第一个元素小于第二个元素返回负整数即可。
如果认为第一个元素等于第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。
注意:如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序。
TreeSet集合的特点是怎么样的?
可排序、不重复、无索引
底层基于红黑树实现排序,增删改查性能较好
TreeSet集合自定义排序规则有几种方式
2种。
类实现Comparable接口,重写比较规则。
集合自定义Comparator比较器对象,重写比较规则。
1.如果希望元素可以重复,又有索引,索引查询要快?
用ArrayList集合,基于数组的。(用的最多)
2.如果希望元素可以重复,又有索引,增删首尾操作快?
用LinkedList集合,基于链表的。
3.如果希望增删改查都快,但是元素不重复、无序、无索引。
用HashSet集合,基于哈希表的。
4.如果希望增删改查都快,但是元素不重复、有序、无索引。
用LinkedHashSet集合,基于哈希表和双链表
5.如果要对对象进行排序。
用TreeSet集合,基于红黑树。后续也可以用List集合实现排序
可变参数
可变参数用在形参中可以接收多个数据。
可变参数的格式:数据类型...参数名称
可变参数的作用
传输参数非常灵活,方便。可以不传输参数,可以传输1个或者多个,也可以传输一个数组
可变参数在方法内部本质上就是一个数组。
可变参数的注意事项:
1.一个形参列表中可变参数只能有一个
2.可变参数必须放在形参列表的最后面
例如:
public static void main(String[] args) {
System.out.println(sum(1,2,3,4,5));
}
public static int sum(int...x){
int sum = 0;
for (int i = 0; i < x.length; i++) {
sum+=x[i];
}
return sum;
}
Collections集合工具类
java.utils.Collections:是集合工具类
作用:Collections并不属于集合,是用来操作集合的工具类。
/*
public static <T> boolean addAll(Collection<? super T> c, T... elements) 给集合对象批量添加元素
public static void shuffle(List<?> list) 打乱List集合元素的顺序
public static <T> void sort(List<T> list) 将集合中元素按照默认规则排序
public static <T> void sort(List<T> list,Comparator<? super T> c) 将集合中元素按照指定规则排序
*/
Collections常用的API
public static <T> boolean addAll(Collection<? super T> c, T... elements) 给集合对象批量添加元素
public static void shuffle(List<?> list) 打乱List集合元素的顺序
Collections排序相关API
使用范围:只能对于List集合的排序。
排序方式1:
public static <T> void sort(List<T> list) 将集合中元素按照默认规则排序
注意:本方式不可以直接对自定义类型的List集合排序,除非自定义类型实现了比较规则Comparable接口
排序方式2:
public static <T> void sort(List<T> list,Comparator<? super T> c) 将集合中元素按照指定规则排序
Map集合体系
使用最多的Map集合是HashMap。
重点掌握HashMap , LinkedHashMap , TreeMap。其他的后续理解。
Map集合的键无序、不重复的
Map集合的值值不做要求可以重复
Map集合体系特点
Map集合的特点都是由键决定的。
Map集合的键是无序,不重复的,无索引的,值不做要求(可以重复)。
Map集合后面重复的键对应的值会覆盖前面重复键的值。
Map集合的键值对都可以为null。
Map集合实现类特点
HashMap:元素按照键是无序,不重复,无索引,值不做要求。(与Map体系一致)
LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求。
TreeMap:元素按照建是可排序,不重复,无索引的,值不做要求。
常用方法
V put(K key,V value) 添加元素 返回的是替代的值,如果没有替代返回null
V remove(Object key) 根据键删除键值对元素
void clear() 移除所有的键值对元素
boolean containsKey(Object key) 判断集合是否包含指定的键
boolean containsValue(Object value) 判断集合是否包含指定的值
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中键值对的个数
get(key)
Map集合的遍历方式有:3种。
方式一:键找值的方式遍历:先获取Map集合全部的键,再根据遍历键找值。
获取Map集合的全部键的Set集合。
遍历键的Set集合,然后通过键提取对应值。
Set<String> str = map.keySet();
for(String s : str){
Integer val = map.get(s);
System.out.print(s + "=" + val + ",");
}
方式二:键值对的方式遍历,把“键值对“看成一个整体,难度较大。
先把Map集合转换成Set集合,Set集合中每个元素都是键值对实体类型了。
遍历Set集合,然后提取键以及提取值。
Set<Map.Entry<K,V>> entrySet() 获取所有键值对对象的集合
Set<Map.Entry<String, Integer>> entry = map.entrySet();
Iterator<Map.Entry<String, Integer>> it = entry.iterator();
while (it.hasNext()) {
Map.Entry<String, Integer> e = it.next();
String key = e.getKey();
Integer value = e.getValue();
System.out.print(key + "=" + value + ",");
}
方式三:JDK 1.8开始之后的新技术:Lambda表达式。
default void forEach(BiConsumer<? super K, ? super V> action) 结合lambda遍历Map集合
map.forEach((key,value) -> System.out.print(key + "=" + value + ","));
HashMap的特点和底层原理
由键决定:无序、不重复、无索引。HashMap底层是哈希表结构的。
依赖hashCode方法和equals方法保证键的唯一。
如果键要存储的是自定义对象,需要重写hashCode和equals方法。
基于哈希表。增删改查的性能都较好。
LinkedHashMap集合概述和特点
由键决定:有序、不重复、无索引。
这里的有序指的是保证存储和取出的元素顺序一致
原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。
TreeMap集合概述和特点
由键决定特性:不重复、无索引、可排序
可排序:按照键数据的大小默认升序(有小到大)排序。只能对键排序。
注意:TreeMap集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序
TreeMap跟TreeSet一样底层原理是一样的。
TreeMap集合自定义排序规则有2种
类实现Comparable接口,重写比较规则。
集合自定义Comparator比较器对象,重写比较规则
TreeMap集合的特点是怎么样的?
根据键可排序、不重复、无索引
底层基于红黑树实现排序,增删改查性能较好
TreeMap集合自定义排序规则有几种方式
2种。
类实现Comparable接口,重写比较规则。
集合自定义Comparator比较器对象,重写比较规则。
Map集合实现类特点
HashMap:元素按照键是无序,不重复,无索引,值不做要求,基于哈希表(与Map体系一致)
LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求,基于哈希表
TreeMap:元素只能按照键排序,不重复,无索引的,值不做要求,可以做排序
什么是不可变集合?
不可变集合,就是不可被修改的集合。
集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。否则报错。
为什么要创建不可变集合?
如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。
或者当集合对象被不可信的库调用时,不可变形式是安全的。
如何创建不可变集合?
在List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合
例如:list.of("a","b","c);
这个集合不能添加,不能删除,不能修改。
不可变集合的特点?
定义完成后不可以修改,或者添加、删除
如何创建不可变集合?
List、Set、Map接口中,都存在of方法可以创建不可变集合。
什么是Stream流?
在Java 8中,得益于Lambda所带来的函数式编程, 引入了一个全新的Stream流概念。
目的:用于简化集合和数组操作的API。
Stream流式思想的核心:
先得到集合或者数组的Stream流(就是一根传送带)
把元素放上去
然后就用这个Stream流简化的API来方便的操作元素
Stream流的作用是什么,结合了什么技术?
简化集合、数组操作的API。结合了Lambda表达式。
说说Stream流的思想和使用步骤。
先得到集合或者数组的Stream流(就是一根传送带)。
把元素放上去。
然后就用这个Stream流简化的API来方便的操作元素。
Stream流的三类方法
获取Stream流
创建一条流水线,并把数据放到流水线上准备进行操作
中间方法
流水线上的操作。一次操作完毕之后,还可以继续进行其他操作。
终结方法
一个Stream流只能有一个终结方法,是流水线上的最后一个操作
Stream
简化 集合 和 数组的操作;
传送带: 就是把集合中的元素复制一份放到传送带上;进行层层过滤,过滤以后可以在转变成一个容器;
1 有一个stream 流对象
ArrayList<String> list = new ArrayList<>();
Stream<String> s1 = list.stream();
set.stream();
String[] arr = {};
Arrays.stream(arr);
Stream.of(arr)
2 过滤 中间方法 链式调用
filter();
skip(2);
limit(3)
distinct();去重
concat();拼接两个流
3 终结方法
forEach();遍历
count();元素个数
get();获取Stream流
Stream流的收集操作
收集Stream流的含义:就是把Stream流操作后的结果数据转回到集合或者数组中去。
Stream流:方便操作集合/数组的手段。
集合/数组:才是开发中的目的。
R collect?(Collector collector) 开始收集Stream流,指定收集器
public static <T> Collector toList?() 把元素收集到List集合中
public static Collector toMap?(Function keyMapper , Function valueMapper) 把元素收集到Map集合中
收集Stream流的作用 ?
Stream流是操作集合/数组的手段
操作的结果数据最终要恢复到集合或者数组中去。
什么是异常?
异常是程序在“编译”或者“执行”的过程中可能出现的问题,注意:语法错误不算在异常体系中。
比如:数组索引越界、空指针异常、 日期格式化异常,等…
为什么要学习异常?
异常一旦出现了,如果没有提前处理,程序就会退出JVM虚拟机而终止.
研究异常并且避免异常,然后提前处理异常,体现的是程序的安全, 健壮性
异常是代码在编译或者执行的过程中可能出现的错误。
异常分为几类?
编译时异常、运行时异常。
编译时异常:没有继承RuntimeExcpetion的异常,编译阶段就会出错。
运行时异常:继承自RuntimeException的异常或其子类,编译阶段不报错,运行可能报错。
学习异常的目的?
避免异常的出现,同时处理可能出现的异常,让代码更稳健。
异常体系
Throwable
|----Error 系统级别问题、JVM退出等,代码无法控制。
|----Exception:java.lang包下,称为异常类,它表示程序本身可以处理的问题
|-----RuntimeException及其子类:运行时异常,编译阶段不会报错。 (空指针异常,数组索引越界异常)
|-----除RuntimeException之外所有的异常:编译时异常,编译期必须处理的,否则程序不能通过编译。 (日期格式化异常)。
运行时异常
直接继承自RuntimeException或者其子类,编译阶段不会报错,运行时可能出现的错误。
运行时异常示例
数组索引越界异常: ArrayIndexOutOfBoundsException
空指针异常 : NullPointerException,直接输出没有问题,但是调用空指针的变量的功能就会报错。
算术异常/数学操作异常:ArithmeticException
类型转换异常:ClassCastException
数字转换异常: NumberFormatException
输入不匹配 InputMismatchException
编译时异常的特点
编译时异常:继承自Exception的异常或者其子类
编译阶段报错,必须处理,否则代码不通过。
异常的默认处理流程
默认会在出现异常的代码那里自动的创建一个异常对象:ArithmeticException。
异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机。
虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。
直接从当前执行的异常点干掉当前程序。
后续代码没有机会执行了,因为程序已经死亡。
默认的异常处理机制并不好,一旦真的出现异常,程序立即死亡
编译时异常是编译阶段就出错的,所以必须处理,否则代码根本无法通过
处理编译时异常的三种方式
出现异常直接抛出去给调用者,调用者也继续抛出去。
throws:用在方法上,可以将方法内部出现的异常抛出去给本方法的调用者处理。
这种方式并不好,发生异常的方法自己不处理异常,如果异常最终抛出去给虚拟机将引起程序死亡
一直向上抛出异常 最终程序仍然不能向下运行
格式:
方法 throws 异常1 ,异常2 ,异常3 ..{
}
规范做法: 代表可以抛出一切异常,
方法 throws Exception{ }
出现异常自己捕获处理,不麻烦别人。
异常处理方式2 —— try…catch
监视捕获异常,用在方法内部,可以将方法内部出现的异常直接捕获处理。
这种方式还可以,发生异常的方法自己独立完成异常的处理,程序可以继续往下执行。
格式:
try{
// 监视可能出现异常的代码!}
catch(异常类型1 变量){
// 处理异常
}catch(异常类型2 变量){
// 处理异常 }...
建议格式:
try{
// 可能出现异常的代码!
}catch (Exception e){
e.printStackTrace(); // 直接打印异常栈信息
}
Exception可以捕获处理一切异常类型!
前两者结合,出现异常直接抛出去给调用者,调用者捕获处理。
异常处理方式3 —— 前两者结合
方法直接将异通过throws抛出去给调用者
调用者收到异常后直接捕获处理。
异常处理的总结
在开发中按照规范来说第三种方式是最好的:底层的异常抛出去给最外层,最外层集中捕获处理。
实际应用中,只要代码能够编译通过,并且功能能完成,那么每一种异常处理方式似乎也都是可以的。
自定义异常的必要?
Java无法为这个世界上全部的问题提供异常类。
如果企业想通过异常的方式来管理自己的某个业务问题,就需要自定义异常类了
自定义异常的好处:
可以使用异常的机制管理业务问题,如提醒程序员注意。
同时一旦出现bug,可以用异常的形式清晰的指出出错的地方。
自定义异常的分类
1、自定义编译时异常
定义一个异常类继承Exception.
重写构造器。
在出现异常的地方用throw new 自定义对象抛出,
作用:编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理!!
2、自定义运行时异常
定义一个异常类继承RuntimeException.
重写构造器。
在出现异常的地方用throw new 自定义对象抛出!
作用:提醒不强烈,编译阶段不报错!!运行时才可能出现!!
自定义异常的必要?
Java无法为这个世界上全部的问题提供异常类。
如果企业想通过异常的方式来管理自己的某个业务问题,就需要自定义异常类了。
自定义异常的好处:
可以使用异常的机制管理业务问题,如提醒程序员注意。
同时一旦出现bug,可以用异常的形式清晰的指出出错的地方。
throw 和 throws区别:
throw 表示抛出一个异常类的对象,生成异常对象的过程。声明在方法体内。
throws 属于异常处理的一种方式,声明在方法的声明处。
自定义异常的分类
1、自定义编译时异常
定义一个异常类继承Exception.
调用父类构造器。 例如:super(message);
在出现异常的地方用throw new 自定义对象抛出,
作用:编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理!!
public class AgeExcption extends Exception{
public AgeExcption() {}
public AgeExcption(String message){
super(message);
}
}
Student{
public void setAge(int age) throws AgeExcption {
if (age < 0) {
throw new AgeExcption(age+"年龄不能小于零");
}
this.age = age;
}
}
2、自定义运行时异常
定义一个异常类继承RuntimeException.
重写构造器。
在出现异常的地方用throw new 自定义对象抛出!
作用:提醒不强烈,编译阶段不报错!!运行时才可能出现!!
日志框架
程序中的日志: 程序中的日志可以用来记录程序运行过程中的信息,并可以进行永久存储。
输出语句的弊端
信息只能展示在控制台
不能将其记录到其他的位置(文件,数据库)
想取消记录的信息需要修改代码才可以完成
多线程 性能较差
日志技术具备的优势
可以将系统执行的信息选择性的记录到指定的位置(控制台、文件中、数据库中)。
可以随时以开关的形式控制是否记录日志,无需修改源代码。
多线程 性能较好
日志规范:一些接口,提供给日志的实现框架设计的标准。
日志框架:牛人或者第三方公司已经做好的日志记录实现代码,后来者直接可以拿去使用。
因为对Commons Logging的接口不满意,有人就搞了SLF4J。因为对Log4j的性能不满意,有人就搞了Logback。
日志的规范是什么,常见的有几种形式。
日志规范大多是一些接口,提供给实现框架去设计的。
常见的规范是:
Commons Logging
Simple Logging Facade for Java
日志的实现框架有哪些常见的?
Log4J
Logback(我们重点学习的,其他的都大同小异)
Logback主要分为三个技术模块:
logback-core: logback-core 模块为其他两个模块奠定了基础,必须有。
logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API。
logback-access 模块与 Tomcat 和 Jetty 等 Servlet 容器集成,以提供 HTTP 访问日志功能
日志级别
级别程度依次是:TRACE< DEBUG< INFO<WARN<ERROR ; 默认级别是debug(忽略大小写),对应其方法。
作用:用于控制系统中哪些日志级别是可以输出的,只输出级别不低于设定级别的日志信息。
ALL 和 OFF分别是打开全部日志信息,及关闭全部日志信息。
File类
1、先要定位文件
File类可以定位文件:进行删除、获取文本本身信息等操作。
但是不能读写文件内容。
2、读写文件数据
IO流技术可以对硬盘中的文件进行读写
3、今日总体学习思路
先学会使用File类定位文件以及操作文件本身
然后学习IO流读写文件数据。
File类概述
File类在包java.io.File下、代表操作系统的文件对象(文件、文件夹)。
File类提供了诸如:定位文件,获取文件本身的信息、删除文件、创建文件(文件夹)等功能。
File类创建对象 三种构造器
public File?(String pathname) 根据文件路径创建文件对象
public File?(String parent, String child) 从父路径名字符串和子路径名字符串创建文件对象
public File?(File parent, String child ) 根据父路径对应文件对象和子路径名字符串创建文件对象
File对象可以定位文件和文件夹
File封装的对象仅仅是一个路径名,这个路径可以是存在的,也可以是不存在的。
1、File类的作用?
创建对象定位文件,可以删除、获取文件信息等。但是不能读写文件内容。
2、File类构建对象的方式 ?
File file = new File(“文件/文件/绝对路径/相对路径”);
3、绝对路径和相对路径是什么样的?
绝对路径是带盘符的,依赖当前系统。
相对路径是不带盘符的,默认相对到工程下开始寻找文件。
public boolean createNewFile() 创建一个新的空的文件
public boolean mkdir() 只能创建一级文件夹
public boolean mkdirs() 可以创建多级文件夹
public boolean delete() 删除由此抽象路径名表示的文件或空文件夹
public boolean isDirectory() 测试此抽象路径名表示的File是否为文件夹
public boolean isFile() 测试此抽象路径名表示的File是否为文件
public boolean exists() 测试此抽象路径名表示的File是否存在
public String getAbsolutePath() 获取绝对路径
public String getPath() 获取路径
public String getName() 获取最后修改时间
public String[] list() 转换为String数组
public File[] listFiles() 转换为File数组
listFiles方法注意事项:
当调用者不存在时,返回null
当调用者是一个文件时,返回null
当调用者是一个空文件夹时,返回一个长度为0的数组
当调用者是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回
当调用者是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏内容
当调用者是一个需要权限才能进入的文件夹时,返回null
递归
什么是方法递归?
方法直接调用自己或者间接调用自己的形式称为方法递归( recursion)。
递归做为一种算法在程序设计语言中广泛应用。
递归的形式
直接递归:方法自己调用自己。
间接递归:方法调用其他方法,其他方法又回调方法自己。
方法递归存在的问题?
递归如果没有控制好终止,会出现递归死循环,导致栈内存溢出现象.
递归的两个条件
可以通过递归调用来缩小问题规模,且新问题与原问题有着相同的形式。(自身调用)
存在一种简单情境,可以使递归在简单情境下退出。(递归出口)
递归三要素:
一定有一种可以退出程序的情况;
总是在尝试将一个问题化简到更小的规模
父问题与子问题不能有重叠的部分
递归算法三要素大体可以总结为:
递归的公式: f(n) = f(n-1) * n;
递归的终结点:f(1)
递归的方向必须走向终结点:
递归求阶乘
public static int jiecheng(int n){
if (n == 1) {
return 1;
}
return n*jiecheng(n-1);
}
递归解决猴子吃桃问题
public static int peaNum(int n) {
int tao;
if(n == 0){
return 1;
}else {
tao = (peaNum(n-1)+1)*2;
}
return tao;
}
递归删除文件夹
public static void mulu(File file){
File[] files = file.listFiles();
System.out.println(file.getName());
file.delete();
for (File f : files) {
if (f.isDirectory()) {
mulu(f);
}else {
System.out.println(f.getName());
f.delete();
}
file.delete();
}
}
字符集基础知识:
计算机底层不可以直接存储字符的。计算机中底层只能存储二进制(0、1)
二进制是可以转换成十进制的
结论:计算机底层可以表示十进制编号。计算机可以给人类字符进行编号存储,这套编号规则就是字符集。
ASCII使用1个字节存储一个字符,一个字节是8位,总共可以表示128个字符信息,对于英文,数字来说是够用的
GBK:window系统默认的码表。兼容ASCII码表,也包含了几万个汉字,并支持繁体汉字以及部分日韩文字。
Unicode码表:
unicode(又称统一码、万国码、单一码)是计算机科学领域里的一项业界字符编码标准。
容纳世界上大多数国家的所有常见文字和符号。
由于Unicode会先通过UTF-8,UTF-16,以及 UTF-32的编码成二进制后再存储到计算机,其中最为常见的就是UTF-8。
注意
Unicode是万国码,以UTF-8编码后一个中文一般以三个字节的形式存储。
UTF-8也要兼容ASCII编码表。
技术人员都应该使用UTF-8的字符集编码。
编码前和编码后的字符集需要一致,否则会出现中文乱码。
字符串常见的字符底层组成是什么样的?
英文和数字等在任何国家的字符集中都占1个字节
GBK字符中一个中文字符占2个字节
UTF-8编码中一个中文1般占3个字节
编码前的字符集和编码好的字符集有什么要求?
必须一致,否则会出现中文字符乱码
英文和数字在任何国家的编码中都不会乱码
String编码 用的是方法
byte[] getBytes?() 使用平台的默认字符集将该 String编码为一系列字节,将结果存储到新的字节数组中
byte[] getBytes?(String charsetName) 使用指定的字符集将该 String编码为一系列字节,将结果存储到新的字节数组中
String解码 用的是构造器
String?(byte[] bytes) 通过使用平台的默认字符集解码指定的字节数组来构造新的 String
String?(byte[] bytes, String charsetName) 通过指定的字符集解码指定的字节数组来构造新的 String
IO流
I表示intput,是数据从硬盘文件读入到内存的过程,称之输入,负责读。
O表示output,是内存程序的数据从内存到写出到硬盘文件的过程,称之输出,负责写。
1.流的分类
* 1.操作数据单位:字节流、字符流
* 2.数据的流向:输入流、输出流
* 3.流的角色:节点流、处理流
1.字符输出流换行~
windows \n
linux \r\n
mac \r
2.追加写入~
FileOutputStream(String name, boolean append)
创建文件输出流以指定的名称写入文件。
如果第二个字节为True,则字节写入文件末尾而不是开头.
Writer w1 = new FileWriter(file);
上边的代码会在本地创建file文件;如果存在,删除再创建
字符流 写出的数据必须刷新以后才能到本地文件,
flush() 刷新以后 流还可以使用
close() 刷新并关闭流,close以后流就不能再使用了
char[] cs ={'a','b','c','d','e'};
w1.write(cs,2,3);
上边重载的write方法,只写出了cs字符数组的一部分,从索引2开始,写三个字符.
字节输入流:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流称为字节输入流。
字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流称为字节输出流。
字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流称为字符输入流。
字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流称为字符输出流。
1、字节流 - 读取二进制文件使用
inputStream/OutputStream //抽象类 读-写
FileInputStream/FileOutputStream//实现类 读和写
BufferedInputStream/BufferedOutputStream//带缓冲的类 -封装了以上两个类的功能
byte数组的最大长度是61858764,将近60M。
2、字符流
Reader/Writer //抽象类
FileReader/FileWriter //实现类
BufferedReader/BufferedWriter //扩展的实现类
InputStreamReader/InputStreWriter//在实际应用时会出现字节与字符的转换
在java.io包中,java.io.InputStream表示字节输入流,java.io.OutputStream表示字节输出流,它们都是抽象类,不能被实例化。
InputStream类提供了一系列和读取数据有关的方法:
read(): 从输入流读取数据:有三种重载形式:
a)int read(): 从输入流读取一个8位的字节,把它转换为0-255之间的整数,并返回这一整数。例如,如果读到的字节为9,则返回9,如果读到
的字节为-9,则返回247。如果遇到输入流的结尾,则返回-1;
b)int read(byte[] b): 从输入流读取若干个字节,把它们保存到参数b指定的字节数组中。返回的整数表示读取的字节数。如果遇到输入流的结尾,则返回-1;
c)int read(byte[] b, int off, int len): 从输入流读取若干个字节,把它们保存到参数b指定的字节数组中。 返回的整数表示读取的字节数。参数off指定在字节数组中
开始保存数据的起始下标,参数len指定读取的字节数目。返回的整数表示实现读取的字节数。如果遇到输入流的结尾,则返回-1;以上第一个read方法从输入流读取一个字
节,而其余两个read方法从输入流批量读取若干字节。在从文件或键盘读数据时,采用后面两个read方法可以减少进行物理读文件或键盘的次数,因此能提高I/O操作的效率
。
void close(): 关闭输入流,InputStream类本身的close()方法不执行任何操作。它的一些子类覆盖了close()方法,在close()方法中释放和流有关的系统资源。
int available(): 返回可以从输入流中读取的字节数目;
skip(long): 从输入流中跳过参数n指定数目的字节。
boolean markSupported(),void mark(int),void reset(): 如果要从流中重复读入数据,先用markSupported()方法来判断这个流是否支持重复读入数据,如果返回true
,则表明可以在流上设置标记。接下来调用mark(int readLimit)方法从流的当前位置开始设置标记。最后调用reset()方法,该方法使输入流重新定位到刚才做了标记的起
始位置。这样就可以重复读取做过标记的数据了。
OuputStream类提供了一系列和写数据有关的方法:
write(): 向输出流写入数据:有三种重载形式:
a)void write(int b):向输出流写入一个字节;
b)void write(byte[] b): 把参数b指定的字节数组中的所有字节写到输流;
c)void write(byte[] b, int off, int len): 把参数b指定的字节数组中的所有字节写到输出流,参数off指定字节数组的起始下标,从这个位置开始输出由参数len指定数
目的字节; 以上第一个write方法从输出流写入一个字节,而其余两个write方法从输出流批量写出若干字节。在向文件或控制台写数据时,采用后面两个write方法可以减
少进行物理读文件或键盘的次数,因此能提高I/O操作的效率。
void close(): 关闭输出流,OutputStream类本身的close()方法不执行任何操作。它的一些子类覆盖了close()方法,在close()方法中释放和流有关的系统资源。
void flush(): OutputStream类本身的flush()方法不执行任何操作,它的一些带有缓冲区的子类(比如BufferedOutputStream和PrintStream类)覆盖了flush()方法。
通过带缓冲区的输出流写数据时,数据先保存在缓冲区中,积累到一定程度才会真正写到输出流中。缓冲区通常用字节数组实现,实际上是指一块内存空间。
flush()方法强制把缓冲区内的数据写到输出中。
缓冲流概述
缓冲流也称为高效流、或者高级流。之前学习的字节流可以称为原始流。
作用:缓冲流自带缓冲区、可以提高原始字节流、字符流读写数据的性能
缓冲流的作用?
缓冲流自带缓冲区、可以提高原始字节流、字符流读写数据的性能
缓冲流有几种?
字节缓冲流
字节缓冲输入流: BufferedInputStream
字节缓冲输出流:BufferedOutputStream
字符缓冲流
字符缓冲输入流:BufferedReader
字符缓冲输出流:BufferedWriter
字节缓冲流性能优化原理:
字节缓冲输入流自带了8KB缓冲池,以后我们直接从缓冲池读取数据,所以性能较好。
字节缓冲输出流自带了8KB缓冲池,数据就直接写入到缓冲池中去,写数据性能极高了。
字节缓冲流为什么提高了操作数据的性能?
字节缓冲流自带8KB缓冲区
可以提高原始字节流、字符流读写数据的性能
字节缓冲流的功能如何调用?
public BufferedOutputStream?(OutputStream os)
public BufferedInputStream?(InputStream is)
功能上并无很大变化,性能提升了。
建议使用字节缓冲输入流 、字节缓冲输出流,结合字节数组的方式,目前来看是性能最优的组合。
字符缓冲流为什么提高了操作数据的性能?
字符缓冲流自带8K缓冲区
可以提高原始字符流读写数据的性能
字符缓冲流的功能如何使用?
public BufferedReader?(Reader r)
public BufferedReader?(Reader r)
性能提升了,多了readLine()按照行读取的功能
public BufferedWriter?(Writer w)
性能提升了,多了newLine()换行的功能
1、如果代码编码和文件编码不一致,使用字符流直接读取还能不乱码吗?
会乱码。
2、如果如何解决呢?
使用字符输入转换流
可以提取文件(GBK)的原始字节流,原始字节不会存在问题。
然后把字节流以指定编码转换成字符输入流,这样字符输入流中的字符就不乱码
字符输入转换流InputStreamReader作用:
可以解决字符流读取不同编码乱码的问题
public InputStreamReader(InputStream is,String charset):
可以指定编码把原始字节流转换成字符流,如此字符流中的字符不乱码。
字符输出转换流OutputStreamWriter的作用?
public OutputStreamWriter(OutputStream os,String charset)
一个字符的输出流转换为字节的输出流
转换流也是一种处理流,它提供了字节流和字符流之间的转换。在Java IO流中提供了两个转换流:
InputStreamReader 和 OutputStreamWriter,这两个类都属于字符流。其中InputStreamReader将字节输入流转为字符输入流,
继承自Reader。OutputStreamWriter是将字符输出流转为字节输出流,继承自Writer。
众所周知,计算机中存储的数据都是二进制的数字,我们在电脑屏幕上看到的文字信息是将二进制转换之后显示的,
两者之间存在编码与解码的过程,其互相转换必须遵循某种规则,即编码和解码都遵循同一种规则才能将文字信息正常显示,
如果编码跟解码使用了不同的规则,就会出现乱码的情况。
InputStreamReader是字节流到字符流的桥梁:它读取字节,并使用指定的字符集将其解码为字符。
它的字符集可以由名称指定,也可以接受平台的默认字符集。
OutputStreamWriter是字符流通向字节流的桥梁:用指定的字符集将字符编码为字节。
它的字符集可以由名称指定,也可以接受平台的默认字符集。
编码:字符、字符串(能看懂的)--字节(看不懂的)
解码:字节(看不懂的)-->字符、字符串(能看懂的)
序列化
把对象转换为字节序列的过程称为对象的序列化。ObjectOutputStream
把字节序列恢复为对象的过程称为对象的反序列化。ObjectInputStream
对象序列化:
作用:以内存为基准,把内存中的对象存储到磁盘文件中去,称为对象序列化。
使用到的流是对象字节输出流:ObjectOutputStream
对象反序列化:
使用到的流是对象字节输入流:ObjectInputStream
作用:以内存为基准,把存储到磁盘文件中去的对象数据恢复成内存中的对象,称为对象反序列化
对象序列化的含义是什么?
把对象数据存入到文件中去。
对象序列化用到了哪个流?
对象字节输出流ObjectOutputStram
public void writeObject(Object obj)
序列化对象的要求是怎么样的?
对象必须实现序列化接口
对象反序列化的含义是什么?
把磁盘中的对象数据恢复到内存的Java对象中。
ObjectOutputStream:内存中的对象--->存储中的文件、通过网络传输出去或存进磁盘中:序列化过程
ObjectInputStream:存储中的文件、通过网络接收过来或从进磁盘中读取出 --->内存中的对象:反序列化过程
打印流
打印流
作用:打印流可以实现方便、高效的打印数据到文件中去。打印流一般是指:PrintStream,PrintWriter两个类。
可以实现打印什么数据就是什么数据,例如打印整数97写出去就是97,打印boolean的true,写出去就是true
构造方法:
public PrintStream(OutputStream out) :输出的目的地为字节输出流
public PrintStream(String fileName) :输出到指定地址的文件
public PrintStream(File file) :输出到指定文件
PrintWriter(File file)
使用指定文件创建不具有自动行刷新的新 PrintWriter。
PrintWriter(File file, String csn)
创建具有指定文件和字符集且不带自动刷行新的新 PrintWriter。
PrintWriter(OutputStream out)
根据现有的 OutputStream 创建不带自动行刷新的新 PrintWriter。
PrintWriter(OutputStream out, boolean autoFlush)
通过现有的 OutputStream 创建新的 PrintWriter。
PrintWriter(String fileName)
创建具有指定文件名称且不带自动行刷新的新 PrintWriter。
PrintWriter(String fileName, String csn)
创建具有指定文件名称和字符集且不带自动行刷新的新 PrintWriter。
PrintWriter(Writer out)
创建不带自动行刷新的新 PrintWriter。
PrintWriter(Writer out, boolean autoFlush)
创建新 PrintWriter。
append(char c)
将指定字符添加到此 writer。
PrintWriter append(CharSequence csq)
将指定的字符序列添加到此 writer。
PrintWriter append(CharSequence csq, int start, int end)
将指定字符序列的子序列添加到此 writer。
boolean checkError()
如果流没有关闭,则刷新流且检查其错误状态。
protected void clearError()
清除此流的错误状态。
void close()
关闭该流并释放与之关联的所有系统资源。
void flush()
刷新该流的缓冲。
PrintWriter format(Locale l, String format, Object... args)
使用指定格式字符串和参数将一个格式化字符串写入此 writer 中。
PrintWriter format(String format, Object... args)
使用指定格式字符串和参数将一个格式化字符串写入此 writer 中。
void print(boolean b)
打印 boolean 值。
void print(char c)
打印字符。
void print(char[] s)
打印字符数组。
void print(double d)
打印 double 精度浮点数。
void print(float f)
打印一个浮点数。
void print(int i)
打印整数。
void print(long l)
打印 long 整数。
void print(Object obj)
打印对象。
void print(String s)
打印字符串。
PrintWriter printf(Locale l, String format, Object... args)
使用指定格式字符串和参数将格式化的字符串写入此 writer 的便捷方法。
PrintWriter printf(String format, Object... args)
使用指定格式字符串和参数将格式化的字符串写入此 writer 的便捷方法。
void println()
通过写入行分隔符字符串终止当前行。
void println(boolean x)
打印 boolean 值,然后终止该行。
void println(char x)
打印字符,然后终止该行。
void println(char[] x)
打印字符数组,然后终止该行。
void println(double x)
打印双精度浮点数,然后终止该行。
void println(float x)
打印浮点数,然后终止该行。
void println(int x)
打印整数,然后终止该行。
void println(long x)
打印 long 整数,然后终止该行。
void println(Object x)
打印 Object,然后终止该行。
void println(String x)
打印 String,然后终止该行。
protected void setError()
指示已发生错误。
void write(char[] buf)
写入字符数组。
void write(char[] buf, int off, int len)
写入字符数组的某一部分。
void write(int c)
写入单个字符。
void write(String s)
写入字符串。
void write(String s, int off, int len)
写入字符串的某一部分。
PrintStream和PrintWriter的区别
打印数据功能上是一模一样的,都是使用方便,性能高效(核心优势)
PrintStream继承自字节输出流OutputStream,支持写字节数据的方法。
PrintWriter继承自字符输出流Writer,支持写字符数据出去。
打印流的优势是什么?
两者在打印功能上都是使用方便,性能高效(核心优势)
输出语句重定向
属于打印流的一种应用,可以把输出语句的打印位置改到文件。
代码案例
PrintStream out = System.out; 记录原始流向控制台的定向
设置重定向文件路径
PrintStream ps = new PrintStream("M0329/bbb.txt");
System.setOut(ps);设置定向
System.out.println("abc123");
System.setOut(out);
System.out.println("abcd");
ps.close();
Properties属性集对象
其实就是一个Map集合,但是我们一般不会当集合使用,因为HashMap更好用
Properties核心作用:
Properties代表的是一个属性文件,可以把自己对象中的键值对信息存入到一个属性文件中去。
属性文件:后缀是.properties结尾的文件,里面的内容都是 key=value,后续做系统配置信息的。
Properties的常用方法:
1.setProperty(String key, String value)
调用 Hashtable 的方法 put。
2.
getProperty(String key)
用指定的键在此属性列表中搜索属性
3.
getProperty(String key, String defaultValue)
用指定的键在属性列表中搜索属性。
4.
load(InputStream inStream)
从输入流中读取属性列表(键和元素对)。
5.
load(Reader reader)
按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。
6.
loadFromXML(InputStream in)
将指定输入流中由 XML 文档所表示的所有属性加载到此属性表中。
7.store(OutputStream out, String comments)
以适合使用 load(InputStream) 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。
8.store(Writer writer, String comments)
以适合使用 load(Reader) 方法的格式,将此 Properties 表中的属性列表(键和元素对)写入输出字符。
9.storeToXML(OutputStream os, String comment)
发出一个表示此表中包含的所有属性的 XML 文档。
10.storeToXML(OutputStream os, String comment, String encoding)
使用指定的编码发出一个表示此表中包含的所有属性的 XML 文档。
下面通过代码的形式了解Properties的具体用法(通过JUnit单元测试的形式运行代码):
Properties的作用?
可以存储Properties属性集的键值对数据到属性文件中去:
void store?(Writer writer, String comments)
可以加载属性文件中的数据到Properties对象中来:
void load?(Reader reader)
commons-io概述
commons-io是apache开源基金组织提供的一组有关IO操作的类库,可以提高IO功能开发的效率。
commons-io工具包提供了很多有关io操作的类。有两个主要的类FileUtils, IOUtils
String readFileToString(File file, String encoding) 读取文件中的数据, 返回字符串
void copyFile(File srcFile, File destFile) 复制文件
void copyDirectoryToDirectory(File srcDir, File destDir) 复制文件夹
FileUtils.deleteDirectory(File directory) // 删除文件夹,包括文件夹和文件夹里面所有的文件
FileUtils.cleanDirectory(File directory) // 清空文件夹里面的所有的内容
多线程
多线程是指从软硬件上实现多条执行流程的技术。
Thread类
Java是通过java.lang.Thread 类来代表线程的。
按照面向对象的思想,Thread类应该提供了实现多线程的方式。
为什么不直接调用了run方法,而是调用start启动线程。
直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。
只有调用start方法才是启动一个新的线程执行。
如果把主线程任务放在子线程之前了。
这样主线程一直是先跑完的,相当于是一个单线程的效果了。
方式一:继承Thread类的方式:
* 1. 创建一个继承于Thread类的子类
* 2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
* 3. 创建Thread类的子类的对象
* 4. 通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
for (int i = 0; i < 1000;i++) {
System.out.println("+++++++" + i);
}
}
方式一优缺点
优点:编码简单
缺点:存在单继承的局限性,线程类继承Thread后,不能继承其他类,不便于扩展。
说明两个问题:
问题一:我们启动一个线程,必须调用start(),不能调用run()的方式启动线程。
问题二:如果再启动一个线程,必须重新创建一个Thread子类的对象,调用此对象的start().
方式二:实现Runnable接口的方式:
* 1. 创建一个实现了Runnable接口的类
* 2. 实现类去实现Runnable中的抽象方法:run()
* 3. 创建实现类的对象
* 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5. 通过Thread类的对象调用start()
代码示例
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("--------" + i);
}
}
});
t2.start();
Thread t3 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("=========" + i);
}
});
t3.start();
for (int i = 0; i < 100; i++) {
System.out.println("*******" + i);
}
}
第二种方式的优点
优点:线程任务类只是实现了Runnale接口,可以继续继承和实现。
缺点:如果线程有执行结果是不能直接返回的。
两种方式的对比:
* 开发中:优先选择:实现Runnable接口的方式
* 原因:1. 实现的方式没类的单继承性的局限性
* 2. 实现的方式更适合来处理多个线程共享数据的情况。
*
* 联系:public class Thread implements Runnable
* 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
目前两种方式,要想启动线程,都是调用的Thread类中的start()。
多线程的实现方案三:利用Callable、FutureTask接口实现。
1、得到任务对象
2、定义类实现Callable接口,重写call方法,封装要做的事情。
3、用FutureTask把Callable对象封装成线程任务对象。
4、把线程任务对象交给Thread处理。
5、调用Thread的start方法启动线程,执行任务
6、线程执行完毕后、通过FutureTask的get方法去获取任务执行的结果。
代码示例
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i = 1; i <= 100; i++){
sum += i;
System.out.println("---" + i);
}
return sum;
}
}
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<Integer> f = new FutureTask<Integer>(mc);
Thread t = new Thread(f);
t.start();
System.out.println("over");
int sum = f.get();
System.out.println(sum);
}
}
方式三优缺点:
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
可以在线程执行完毕后去获取线程执行的结果。
缺点:编码复杂一点。
Thread类中的常用的方法:
* 1. start():启动当前线程;调用当前线程的run()
* 2. run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
* 3. currentThread():静态方法,返回执行当前代码的线程
* 4. getName():获取当前线程的名字
* 5. setName():设置当前线程的名字
* 6. yield():释放当前cpu的执行权
* 7. join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
* 8. stop():已过时。当执行此方法时,强制结束当前线程。
* 9. sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
* 10. isAlive():判断当前线程是否存活
线程安全问题
多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。
线程安全问题出现的原因?
存在多线程并发
同时访问共享资源
存在修改共享资源
解决线程安全问题
线程同步的核心思想
加锁,把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。
线程同步解决安全问题的思想是什么?
加锁:让多个线程实现先后依次访问共享资源,这样就解决了安全问题
方式一
同步代码块
作用:把出现线程安全问题的核心代码给上锁
原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。
synchronized(同步锁对象) {
操作共享资源的代码(核心代码)
}
锁对象的规范要求
规范上:建议使用共享资源作为锁对象。
对于实例方法建议使用this作为锁对象。
对于静态方法建议使用字节码(类名.class)对象作为锁对象。
同步代码块是如何实现线程安全的?
对出现问题的核心代码使用synchronized进行加锁
每次只能一个线程占锁进入访问
同步代码块的同步锁对象有什么要求?
对于实例方法建议使用this作为锁对象。
对于静态方法建议使用字节码(类名.class)对象作为锁对象。
方式二
同步方法
作用:把出现线程安全问题的核心方法给上锁。
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。
修饰符 synchronized 返回值类型 方法名称(形参列表) {
操作共享资源的代码
}
同步方法底层原理
同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
如果方法是实例方法:同步方法默认用this作为的锁对象。但是代码要高度面向对象!
如果方法是静态方法:同步方法默认用类名.class作为的锁对象
是同步代码块好还是同步方法好一点?
同步代码块锁的范围更小,同步方法锁的范围更大。
方式三:
Lock锁
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便。
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象。
void lock()获取锁
void unlock()释放锁,建议放在finally中 避免出现异常导致死锁的发生。
方式一:同步代码块
*
* synchronized(同步监视器){
* //需要被同步的代码
*
* }
* 说明:1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
* 2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
* 3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
* 要求:多个线程必须要共用同一把锁。
*
* 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
* 方式二:同步方法
* 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
* 关于同步方法的总结:
* 1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
* 2. 非静态的同步方法,同步监视器是:this
* 静态的同步方法,同步监视器是:当前类本身
方式三:Lock锁 --- JDK5.0新增
*
* 1. 面试题:synchronized 与 Lock的异同?
* 相同:二者都可以解决线程安全问题
* 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
* Lock需要手动的启动同步(lock(),同时结束同步也需要手动的实现(unlock())
使用的优先顺序:
* Lock ---> 同步代码块(已经进入了方法体,分配了相应资源 ) --->? 同步方法(在方法体之外)
3.利弊
同步的方式,解决了线程的安全问题。---好处
操作同步代码时,只能一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
什么是线程通信、如何实现?
所谓线程通信就是线程间相互发送数据,线程间共享一个资源即可实现线程通信。
线程通信常见形式
通过共享一个数据的方式实现。
根据共享数据的情况决定自己该怎么做,以及通知其他线程怎么做
线程通信实际应用场景
生产者与消费者模型:生产者线程负责生产数据,消费者线程负责消费生产者产生的数据。
要求:生产者线程生产完数据后唤醒消费者,然后等待自己,消费者消费完该数据后唤醒生产者,然后等待自己。
线程通信的前提:线程通信通常是在多个线程操作同一个共享资源的时候需要进行通信,且要保证线程安全。
void wait?() 让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或 notifyAll()方法
void notify?() 唤醒正在等待的单个线程
void notifyAll?() 唤醒正在等待的所有线程
注意
上述方法应该使用当前同步锁对象进行调用。
什么是线程池?
线程池就是一个可以复用线程的技术。
不使用线程池的问题
如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。
谁代表线程池
JDK 5.0起提供了代表线程池的接口:ExecutorService
如何得到线程池对象
方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
ThreadPoolExecutor构造器的参数说明
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数一:指定线程池的线程数量(核心线程): corePoolSize 不能小于0
参数二:指定线程池可支持的最大线程数: maximumPoolSize 最大数量 >= 核心线程数量
参数三:指定临时线程的最大存活时间: keepAliveTime 不能小于0
参数四:指定存活时间的单位(秒、分、时、天): unit 时间单位
参数五:指定任务队列: workQueue 不能为null 大于零
参数六:指定用哪个线程工厂创建线程: threadFactory 不能为null
参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler 不能为null
线程池常见面试题
临时线程什么时候创建啊?
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
什么时候会开始拒绝任务?
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。
void execute(Runnable command) 执行任务/命令,没有返回值,一般用来执行 Runnable 任务
Future<T> submit(Callable<T> task) 执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务
void shutdown() 等任务执行完毕后关闭线程池
List<Runnable>?shutdownNow() 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务
新任务拒绝策略
ThreadPoolExecutor.AbortPolicy 丢弃任务并抛出RejectedExecutionException异常。是默认的策略
ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法
ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务 然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy 由主线程负责调用任务的run()方法从而绕过线程池直接执行
线程池如何处理Runnable任务
使用ExecutorService的方法:
void execute(Runnable target)
ExecutorService的常用方法
void execute(Runnable command) 执行任务/命令,没有返回值,一般用来执行 Runnable 任务
Future<T> submit(Callable<T> task) 执行Callable任务,返回未来任务对象获取线程结果
void shutdown() 等任务执行完毕后关闭线程池
List<Runnable>?shutdownNow() 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务
线程池如何处理Callable任务,并得到任务执行完后返回的结果。
使用ExecutorService的方法:
Future<T> submit(Callable<T> command)
方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象
Executors得到线程池对象的常用方法
Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。
public static?ExecutorService?newCachedThreadPool()
线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉。
public static?ExecutorService?newFixedThreadPool?(int?nThreads)
创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。
public static?ExecutorService?newSingleThreadExecutor ()
创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。
public static?ScheduledExecutorService?newScheduledThreadPool?(int?corePoolSize)
创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。
注意:Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的。
Mycallable c1 = new Mycallable(3);
ExecutorService pool = Executors.newFixedThreadPool(3);
Future<Integer> submit1 = pool.submit(c1);
System.out.println(submit1.get());
System.out.println("---over");
Executors使用可能存在的陷阱
大型并发系统环境中使用Executors如果不注意可能会出现系统风险。
public static?ExecutorService?newFixedThreadPool?(int?nThreads) 允许请求的任务队列长度是Integer.MAX_VALUE,可能出现OOM
public static?ExecutorService?newSingleThreadExecutor() 错误( java.lang.OutOfMemoryError )
public static?ExecutorService?newCachedThreadPool() 创建的线程数量最大上限是Integer.MAX_VALUE,
public static?ScheduledExecutorService?newScheduledThreadPool?(int?corePoolSize) ( java.lang.OutOfMemoryError )
定时器
定时器是一种控制任务延时调用,或者周期调用的技术。
作用:闹钟、定时邮件发送
定时器的实现方式
方式一:Timer
public Timer() 创建Timer定时器对象
public?void?schedule?(TimerTask?task, long?delay, long?period) 开启一个定时器,按照计划处理TimerTask任务
Timer定时器的特点和存在的问题
1、Timer是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入。
2、可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行。
方式二: ScheduledExecutorService
Executors的方法
ScheduledExecutorService是 jdk1.5中引入了并发包,目的是为了弥补Timer的缺陷, ScheduledExecutorService内部为线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 得到线程池对象
ScheduledExecutorService的方法
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period,TimeUnit unit)
周期调度方法
ScheduledExecutorService的优点
1、基于线程池,某个任务的执行情况不会影响其他定时任务的执行
并发与并行
正在运行的程序(软件)就是一个独立的进程, 线程是属于进程的,多个线程其实是并发与并行同时进行的。
并发的理解:
CPU同时处理线程的数量有限。
CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。
并行的理解:
在同一个时刻上,同时有多个线程在被CPU处理并执行。
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事
简单说说并发和并行的含义
并发:CPU分时轮询的执行线程。
并行:同一个时刻同时在执行
线程的生命周期
线程的状态
线程的状态:也就是线程从生到死的过程,以及中间经历的各种状态及状态转换。
理解线程的状态有利于提升并发编程的理解能力。
Java总共定义了6种状态
6种状态都定义在Thread类的内部枚举类中
NEW(新建) 线程刚被创建,但是并未启动。
Runnable(可运行) 线程已经调用了start()等待CPU调度
Blocked(锁阻塞) 线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态;
Waiting(无限等待) 一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒
Timed Waiting(计时等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡
新建状态( NEW ) 创建线程对象
就绪状态( RUNNABLE ) start方法
阻塞状态( BLOCKED ) 无法获得锁对象
等待状态( WAITING ) wait方法
计时等待( TIMED_WAITING ) sleep方法
结束状态( TERMINATED ) 全部代码运行完毕
# 今日内容
1. Junit单元测试
2. 反射
3. 注解
## Junit单元测试:
* 测试分类:
1. 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值。
2. 白盒测试:需要写代码的。关注程序具体的执行流程。
* Junit使用:白盒测试
* 步骤:
1. 定义一个测试类(测试用例)
* 建议:
* 测试类名:被测试的类名Test CalculatorTest
* 包名:xxx.xxx.xx.test com.ufox.test
2. 定义测试方法:可以独立运行
* 建议:
* 方法名:test测试的方法名 testAdd()
* 返回值:void
* 参数列表:空参
3. 给方法加@Test
4. 导入junit依赖环境
* 判定结果:
* 红色:失败
* 绿色:成功
* 一般我们会使用断言操作来处理结果
* Assert.assertEquals(期望的结果,运算的结果);
* 补充:
* @Before:
* 修饰的方法会在测试方法之前被自动执行
* @After:
* 修饰的方法会在测试方法执行之后自动被执行
## 反射:框架设计的灵魂
* 框架:半成品软件。可以在框架的基础上进行软件开发,简化编码
* 反射:将类的各个组成部分封装为其他对象,这就是反射机制
* 好处:
1. 可以在程序运行过程中,操作这些对象。
2. 可以解耦,提高程序的可扩展性。
* 获取Class对象的方式:
1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象
* 多用于配置文件,将类名定义在配置文件中。读取文件,加载类
2. 类名.class:通过类名的属性class获取
* 多用于参数的传递
3. 对象.getClass():getClass()方法在Object类中定义着。
* 多用于对象的获取字节码的方式
* 结论:
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
* Class对象功能:
* 获取功能:
1. 获取成员变量们
* Field[] getFields() :获取所有public修饰的成员变量
* Field getField(String name) 获取指定名称的 public修饰的成员变量
* Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
* Field getDeclaredField(String name)
2. 获取构造方法们
* Constructor<?>[] getConstructors()
* Constructor<T> getConstructor(类<?>... parameterTypes)
* Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
* Constructor<?>[] getDeclaredConstructors()
3. 获取成员方法们:
* Method[] getMethods()
* Method getMethod(String name, 类<?>... parameterTypes)
* Method[] getDeclaredMethods()
* Method getDeclaredMethod(String name, 类<?>... parameterTypes)
4. 获取全类名
* String getName()
* Field:成员变量
* 操作:
1. 设置值
* void set(Object obj, Object value)
2. 获取值
* get(Object obj)
3. 忽略访问权限修饰符的安全检查
* setAccessible(true):暴力反射
* Constructor:构造方法
* 创建对象:
* T newInstance(Object... initargs)
* 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
* Method:方法对象
* 执行方法:
* Object invoke(Object obj, Object... args)
* 获取方法名称:
* String getName:获取方法名
* 案例:
* 需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
* 实现:
1. 配置文件
2. 反射
* 步骤:
1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
2. 在程序中加载读取配置文件
3. 使用反射技术来加载类文件进内存
4. 创建对象
5. 执行方法
## 注解:
* 概念:说明程序的。给计算机看的
* 注释:用文字描述程序的。给程序员看的
* 定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
* 概念描述:
* JDK1.5之后的新特性
* 说明程序的
* 使用注解:@注解名称
?
* 作用分类:
①编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
②代码分析:通过代码里标识的注解对代码进行分析【使用反射】
③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
* JDK中预定义的一些注解
* @Override :检测被该注解标注的方法是否是继承自父类(接口)的
* @Deprecated:该注解标注的内容,表示已过时
* @SuppressWarnings:压制警告
* 一般传递参数all @SuppressWarnings("all")
* 自定义注解
* 格式:
元注解
public @interface 注解名称{
属性列表;
}
* 本质:注解本质上就是一个接口,该接口默认继承Annotation接口
* public interface MyAnno extends java.lang.annotation.Annotation {}
* 属性:接口中的抽象方法
* 要求:
1. 属性的返回值类型有下列取值
* 基本数据类型
* String
* 枚举
* 注解
* 以上类型的数组
2. 定义了属性,在使用时需要给属性赋值
1. 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
2. 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
3. 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
* 元注解:用于描述注解的注解
* @Target:描述注解能够作用的位置
* ElementType取值:
* TYPE:可以作用于类上
* METHOD:可以作用于方法上
* FIELD:可以作用于成员变量上
* @Retention:描述注解被保留的阶段
* @Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
* @Documented:描述注解是否被抽取到api文档中
* @Inherited:描述注解是否被子类继承
* 在程序使用(解析)注解:获取注解中定义的属性值
1. 获取注解定义的位置的对象 (Class,Method,Field)
2. 获取指定的注解
* getAnnotation(Class)
//其实就是在内存中生成了一个该注解接口的子类实现对象
public class ProImpl implements Pro{
public String className(){
return "cn.ufox.annotation.Demo1";
}
public String methodName(){
return "show";
}
}
3. 调用注解中的抽象方法获取配置的属性值
* 案例:简单的测试框架
* 小结:
1. 以后大多数时候,我们会使用注解,而不是自定义注解
2. 注解给谁用?
1. 编译器
2. 给解析程序用
3. 注解不是程序的一部分,可以理解为注解就是一个标签
XML、XML解析、设计模式等
XML概述
XML是可扩展标记语言(eXtensible Markup Language)的缩写,它是是一种数据表示格式,可以描述非常复杂的数据结构,常用于传输和存储数据。
XML的几个特点和使用场景
一是纯文本,默认使用UTF-8编码;二是可嵌套;
如果把XML内容存为文件,那么它就是一个XML文件。
XML的使用场景:XML内容经常被当成消息进行网络传输,或者作为配置文件用于存储系统的信息。
XML是什么?
XML的全称为(EXtensible Markup Language),是一种可扩展的标记语言。
它是是一种数据表示格式,可以用于自定义数据格式。
XML的作用是什么?
用于进行存储数据和传输数据
作为软件的配置文件
XML的创建
就是创建一个XML类型的文件,要求文件的后缀必须使用xml,如hello_world.xml
XML的语法规则
XML文件的后缀名为:xml
文档声明必须是第一行
<?xml version="1.0" encoding="UTF-8" ?>
version:XML默认的版本号码、该属性是必须存在的
encoding:本XML文件的编码
XML的标签(元素)规则
标签由一对尖括号和合法标识符组成: <name></name>,必须存在一个根标签,有且只能有一个。
标签必须成对出现,有开始,有结束: <name></name>
特殊的标签可以不成对,但是必须有结束标记,如:<br/>
标签中可以定义属性,属性和标签名空格隔开,属性值必须用引号引起来<student id = “1”></name>
标签需要正确的嵌套
XML的其他组成
XML文件中可以定义注释信息:<!– 注释内容 -->
XML文件中可以存在以下特殊字符
< < 小于
> > 大于
& & 和号
' ' 单引号
" " 引号
XML文件中可以存在CDATA区: <![CDATA[ …内容… ]]>
XML的组成格式要求是什么样的?
文件后缀必须是xml
文档声明必须是第一行
必须存在一个根标签,有且只能有一个
XML文件中可以定义注释信息:<!– 注释内容 -->
标签必须成对出现,有开始,有结束标签: <name></name>
必须能够正确的嵌套
什么是文档约束?
由于XML文件可以自定义标签,导致XML文件可以随意定义,程序在解析的时候可能出现问题
文档约束:是用来限定xml文件中的标签以及属性应该怎么写
文档约束的分类
DTD
需求:利用DTD文档约束,约束一个XML文件的编写。
分析:
①:编写DTD约束文档,后缀必须是.dtd
②:在需要编写的XML文件中导入该DTD约束文档
③:按照约束的规定编写XML文件的内容。
XML的文档约束-DTD的作用和问题?
可以约束XML文件的编写。
不能约束具体的数据类型。
schema
schema可以约束具体的数据类型,约束能力上更强大。
schema本身也是一个xml文件,本身也受到其他约束文件的要求,所以编写的更加严谨
Schema文件用来约束一个xml文件同时也被别的文件约束着
需求:利用schema文档约束,约束一个XML文件的编写。
分析:
①:编写schema约束文档,后缀必须是.xsd,具体的形式到代码中观看。
②:在需要编写的XML文件中导入该schema约束文档
③:按照约束内容编写XML文件的标签。
XML的文档约束-schema的优点?
可以约束XML文件的标签内容格式,以及具体的数据类型。
本身也是xml文件,格式更严谨。
XML的数据的作用是什么,最终需要怎么处理?
存储数据、做配置信息、进行数据传输。
最终需要被程序进行读取,解析里面的信息
什么是XML解析
使用程序读取XML中的数据
两种解析方式
SAX解析
DOM解析
Dom常见的解析工具
JAXP SUN公司提供的一套XML的解析的API
JDOM JDOM是一个开源项目,它基于树型结构,利用纯JAVA的技术对XML文档实现解析、生成、序列化以及多种操作。
dom4j 是JDOM的升级品,用来读写XML文件的。具有性能优异、功能强大和极其易使用的特点,
它的性能超过sun公司官方的dom 技术,同时它也是一个开放源代码的软件,Hibernate也用它来读写配置文件。
jsoup 功能强大DOM方式的XML解析开发包,尤其对HTML解析更加方便
DOM解析解析文档对象模型
Document对象:整个xml文档
Element对象:标签
Attribute对象:属性
Text对象:文本内容
Node对象: 除了Document对象 其他三个都包括
需求:使用Dom4J把一个XML文件的数据进行解析
分析:
下载Dom4j框架,官网下载。
在项目中创建一个文件夹:lib
将dom4j-2.1.1.jar文件复制到 lib 文件夹
在jar文件上点右键,选择 Add as Library -> 点击OK
在类中导包使用
Dom4j解析XML-得到Document对象
SAXReader类
public SAXReader() 创建Dom4J的解析器对象
Document read(String url) 加载XML文件成为Document对象
示例:
SAXReader reader = new SAXReader();
return reader.read(new FileReader(path));
Document类
Element getRootElement() 获得根元素对象
示例:
Document doc =XmlUtils.getDoc(path);
Element rootElement = doc.getRootElement();
Dom4j解析XML的元素、属性、文本
List<Element> elements() 得到当前元素下所有子元素
List<Element> elements(String name) 得到当前元素下指定名字的子元素返回集合
Element element(String name) 得到当前元素下指定名字的子元素,如果有很多名字相同的返回第一个
String getName() 得到元素名字
String attributeValue(String name) 通过属性名直接得到属性值
String elementText(子元素名) 得到指定名称的子元素的文本
String getText() 得到文本
Dom4J的解析思想?
得到文档对象Document,从中获取元素对象和内容。
Dom4J的解析后的数据形式。
通常数据会封装成Java的对象,如单个对象,或者集合对象形式。
XPath介绍
XPath在解析XML文档方面提供了一独树一帜的路径思想,更加优雅,高效
XPath使用路径表达式来定位XML文档中的元素节点或属性节点。
需求:使用Dom4J把一个XML文件的数据进行解析
分析:
导入jar包(dom4j和jaxen-1.1.2.jar),Xpath技术依赖Dom4j技术
通过dom4j的SAXReader获取Document对象
利用XPath提供的API,结合XPath的语法完成选取XML文档元素节点进行解析操作。
Document中与Xpath相关的API如下:
Node selectSingleNode("表达式") 获取符合表达式的唯一元素
List<Node> selectNodes("表达式") 获取符合表达式的元素集合
Xpath的四大检索方案
绝对路径
相对路径
全文检索
属性查找
Xpath作用,四大类。
检索XML文件中的信息
绝对路径: /根元素/子元素/孙元素
相对路径:./子元素/孙元素
全文检索://contact
属性查找://@属性名 、//元素[@属性名]、//元素//[@属性名=‘值’]
设计模式:工厂模式
什么是工厂设计模式?
之前我们创建类对象时, 都是使用new 对象的形式创建,在很多业务场景下也提供了不直接new的方式 。
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一, 这种类型的设计模式属于创建型模式,它提供了一种获取对象的方式。
工厂设计模式的作用:
工厂的方法可以封装对象的创建细节,比如:为该对象进行加工和数据注入。
可以实现类与类之间的解耦操作(核心思想)。
工厂设计模式的作用
对象通过工厂的方法创建返回,工厂的方法可以为该对象进行加工和数据注入
可以实现类与类之间的解耦操作(核心思想)
什么是装饰设计模式?
创建一个新类,包装原始类,从而在新类中提升原来类的功能。
装饰设计模式的作用:
作用:装饰模式指的是在不改变原类的基础上, 动态地扩展一个类的功能。
InputStream(抽象父类)
FileInputStream(实现子类,读写性能较差)
BufferedInputStream(实现子类,装饰类,读写性能高)
定义父类。
定义原始类,继承父类,定义功能。
定义装饰类,继承父类,包装原始类,增强功能!!
装饰设计模式的作用?
装饰模式指的是在不改变原类的基础上, 动态地扩展一个类的功能