JavaSE笔记

Java语言跨平台原理

跨平台

Java程序可以在任意操作系统上运行

跨平台原理

在需要运行Java应用程序的操作系统上,安装一个与操作系统对应的Java虚拟机(JVM Java Virtual Machine)即可.

JRE和JDK

JRE(Java Runtime Environment)

JRE是Java程序的运行环境,包含JVM和运行时所需要的核心类库

我们要运行一个已有的Java程序,那么只需要JRE即可

JDK(Java Development Kit)

是Java程序开发工具包,包含JRE和开发人员使用的工具.

其中的开发工具:编译工具(javac.exe)和运行工具(java.exe).

JDK,JRE和JVM的关系

JDK包含了JRE和开发工具

JRE包含了JVM和核心类库

常用的DOS命令

操作 说明
盘符名称 盘符切换,D:回车,表示切换到D盘
dir 查看当前路径下的内容
cd 目录 进入单级目录,cd develop
cd … 回退到上一级目录
cd 目录1\目录2… 进入多级目录,cd develop\workspace
cd \ 回退到盘符目录
cls 清屏
exit 退出命令提示符窗口

HelloWorld案例

public class HelloWorld{
	public static void main(String[] args){
		System.out.println("HelloWorld");
	}
}

编译java程序: javac HelloWorld.java

运行java程序: java HelloWorld

注释

注释是在程序指定位置添加的说明信息,注释不参与编译与运行

注释分类
1.单行注释
 	格式://注释信息
2.多行注释
	格式:/*注释信息*/
3.文档注释
	格式:/**注释信息*/
例:
	/*
		类的定义格式:
		public class 类名{
		
		}
		这是我定义的HelloWorld类
	*/
    public class HelloWorld{
    	/*
    		这是main方法
    		main方法是程序的入口方法,代码的执行是从main方法开始的
    	*/
        public static void main(String[] args){
        	// 这是输出语句,输出内容是HelloWorld,输出内容可以改变
            System.out.println("HelloWorld");
        }
    }

关键字概述

关键字是被java语言赋予了特定含义的单词
  • 关键字的字母全部小写
  • 常用的代码编辑器,针对关键字有特殊的颜色

常量

在程序运行过程中,其值不可以发生改变的量

常量分类:

字符串常量:用双引号括起来的内容

整数常量:不带小数的数字

小数常量:带小数的数字

字符常量:用单引号括起来的内容,只能存放单个字符

布尔常量:布尔值,表示真假,true或者false

空常量:一个特殊的值,空值,null(空常量是不能直接输出的)

数据类型

基本数据类型

整数数据类型:byte,short,int,long

浮点型:float,double

字符型:char ,双引号括起来,只能保存单个字符

布尔型:boolean(默认值false)

引用数据类型

键盘输入对象

//1.导包,类的前面加载
import java.util.Scanner
//2.创建键盘输入对象
Scanner sc=new Scanner(System.in);
//3.用户输入数据
int num=sc.nextInt();
//4.将输入的数据打印在控制台
System.out.println(num);

标识符的命名规则

由$ , _ , 数字,字母组成,不能由数字开头,不能使用java定义的关键字,不宜过长

类型转换

隐式转换

小转大,可以直接赋值

特殊关注:byte,short,char运算时,不管是否有更高的数据类型,都会自动提升为int,再进行运算

强制转换

强制转换有可能造成精度损失,不建议使用

目标数据类型 变量名=(目标数据类型)变量值;
int a=130;
byte b=(byte)a;

运算符

算数运算符

+,-,* :

/,%

switch语句

语句格式

switch(表达式){
	case 值1 : 
				语句体1;
				break;
	case 值2:
				语句体2;
				break;
	......
	defalult:
				语句体n+1;
				break;
}
注意:在switch语句中,如果case控制的语句体后面不写break,将会出现穿透现象
现象:当开始case穿透,后续的case就不会匹配效果,内部的语句都会执行,直到看见       	break,或者将整体switch语句执行完毕,才会结束

循环语句

for循环语句
for循环格式:
for(初始化语句;条件判断语句;条件控制语句){
	循环体语句;
}
执行流程:
1.执行初始化语句
2.执行条件判断语句,看其结果是true还是false
	如果是false,循环结束
	如果是true,继续执行
3.执行循环体语句
4.执行条件控制语句
5.回到2继续
while循环语句
格式:
	初始化语句;
	while(条件判断语句){
		循环体语句;
		条件控制语句
	}
执行流程:
1.执行初始化语句
2.执行条件判断语句,看其结果是true还是false
	如果是false,循环结束
	如果是true,继续执行
3.执行循环体语句
4.执行条件控制语句
5.回到2继续

数组

定义一个数组

数据类型[] 数组名; int[] arr={};

数据类型 数组名[]; int arr[]={};

数组的动态初始化,初始化时只指定数组的长度,由系统分配初始值

数据类型[] 数组名=new 数据类型[数组长度]

例:申请一个长度为5的数组
 int[] arr=new int[5];
 只明确元素个数,不明确具体数值

数组的静态初始化,初始化时就可以指定数组要存储的元素,系统还会自动计算出该数组的长度

格式: 数据类型[] 变量名=new 数据类型[]{数据1,数据2,…}

明确元素个数与具体数值

java一个数组的内存分配

java大致分为栈内存,堆内存,方法区

方法区中有class字节码文件与main方法,在程序运行时main方法加载进栈内存,定义变量也在栈内存进行,每new一次会在堆内存中申请一块内存空间

方法

方法概述

​ 一段具有特定功能的代码块就是方法

​ 方法与方法之间是平级的关系,不能嵌套定义

方法的定义格式与调用:
1.无参方法的定义格式:
            public void 方法名(){
                方法体
            }
	方法的调用格式://直接调用,一般用来调用没有返回值的方法
            方法名();
2.带参方法的定义格式
			public void 方法名(数据类型 形参变量名,...){
                 方法体
             }
	方法的调用格式://直接调用,一般用来调用没有返回值的方法
            方法名(实参变量的值);
	形参:方法定义时的参数,作用是接收实参
	实参:调用方法时传递的真实数据

3.带返回值的方法定义
public 返回值的数据类型 方法名(数据类型 形参变量名){
                方法体
                return 返回值;
}
	方法的调用格式:  //赋值调用
                跟返回值相同的数据类型 变量名=方法名(实参值);
                System.out.println(变量名);
                //打印调用:必须调用有返回值的方法
                System.out.println(getSum(实参值));
    return: 返回返回值,结束方法

进制的转换

在java中,数值默认都是10进制,不需要加任何修饰
二进制:在java中以0b开头,b大小写都可以
八进制:数值前面以0开头
十六进制:数值前面以0x开头,x大小写都可以
书写时,虽然加入了进制的标识,但打印在控制台的都是10进制数据
这些的规定都是从jdk8开始的
任意进制到十进制的转换

公式:系数*基数的权次幂相加

系数:每一位上的数

基数:几进制就是几

权:从数值的右侧,以0开始,逐个+1增加

十进制到任意进制的转换

公式:除基取余

​ 使用源数据,不断的除以基数(几进制,基数就是几)得到余数,直到商为0,再讲余数倒着拼起来即可

原码反码补码

计算机中的数据都是以2进制补码的形式在运算,而补码则是通过反码和原码推算出来的

原码(可直观看出数据大小)

​ 就是二进制定点表示法,最高位为符号位,0为正,1为负,其余表示数值大小

反码

​ 正数的反码与其原码相同;负数的反码是对其原码按位取反,但符号位不变

补码

​ 正数的补码与其原码相同;负数的补码是在其原码的末位加1

面向对象基础

面向对象和面向过程的思想对比

面向对象是基于面向过程的

面向过程编程 POP

​ 是一种以过程为中心的编程思想.实现功能的每一步,都是自己实现

面向对象编程 OOP

​ 是一种以对象为中心的编程思想,通过指挥对象实现具体的功能

对象:

指客观存在的事物,都可以看成程序中的对象( 万物皆对象)

使用面向对象思想可以将复杂的问题简单化

将我们从执行者的位置,变成了指挥者

类和对象

类是对现实生活中一类具有共同属性和行为的事物的抽象

类是对事物,也就是对象的一种描述.

类是对象的设计图,对象是类的实例

根据类,可以创建出一个具体的对象

类的组成

属性: 该对象的各种特征

在代码中通过成员变量来体现,属性在类中方法外

行为:该对象存在的功能

通过成员方法来实现

public class Student {
    //属性:姓名,年龄
    //成员变量:跟之前定义变量的格式
   	private String name;
   	private int age;
    //行为:学习
    //成员方法:跟之前定义方法的格式一样,只不过去掉了static
    public void study(){
        System.out.println("学习");//这里可以实现一个功能
    }

对象的创建

public static void main(String[] args) {
        //对象的创建   类名 对象名=new 类名();
        //             Student s=new Student();
        Student student=new Student();
        /*
        因为成员变量的私有化,在其他类中不能直接用(对象名.变量名)调用
        调用对象     对象名.变量名
        System.out.println(student.name);
        System.out.println(student.age);
        成员变量的赋值
        student.name="张三";
        student.age=18;
        System.out.println(student.name);
        System.out.println(student.age);
        */
        //使用set和get方法来赋值或者调用成员变量
        System.out.println(student.getName());
        System.out.println(student.getAge());
        student.setName("张三");
        student.setAge(18);
        System.out.println(student.getName());
        System.out.println(student.getAge());
        student.show();
    }
垃圾回收机制

​ 当堆内存中,对象或数组产生的地址,通过任何方式都不能被找到后,就会被判定为内存中的**“垃圾”**,垃圾会被java垃圾回收器,空闲的时候自动进行清理

成员变量和局部变量

成员变量:类中方法外

局部变量:方法形参,方法内的变量

private关键字

​ 权限修饰符,私有化成员变量,成员变量只能在本类中直接访问

this关键字

局部变量和成员变量如果重名,Java使用的是就近原则

this关键字的作用:

可以调用本类的成员(变量,方法),解决局部变量和成员变量的重名问题

代表所在类对象的引用,方法被哪个对象调用,this就代表哪个对象

封装

面向对象三大特征之一(封装,继承,多态)

隐藏实现细节,仅对外暴露公共的访问方式

封装常见的体现:

​ 私有化成员变量,提供set和get方法

​ 将代码抽取到方法中,这是对代码的一种封装

​ 将属性抽取到类中,这是对数据的一种封装

构造方法

  • 构建,创造对象的时候,所调用的方法

  • 格式:

    ​ 方法名与类名相同,大小写也要一致

    ​ 没有返回值类型,连void也没有

    ​ 没有具体的返回值(不能由return带回结果数据)

  • 执行时机:

    • 创建对象的时候调用,每创建一次对象,就会执行一次构造方法
    • 不能手动调用构造方法
构造方法的作用

​ 用于给对象的数据(属性)进行初始化

构造方法注意事项

​ 如果没有定义构造方法,系统将给出一个默认的无参构造方法

​ 如果定义了构造方法,系统将不在提供默认的构造方法

​ 构造方法的重载,如果定义了带参构造方法,还要使用无参构造方法,那就必须再写

​ 一个无参构造方法

专门封装数据的类 : JavaBean类

继承

描述:父类可以有多个子类,子类只能继承一个父类,继承是多态的前提

继承解决的主要问题:共性抽取

继承关系当中的特点:

​ 1.子类可以拥有父类的内容

​ 2.子类还可以拥有自己专有的内容

继承关键字: extends

设计原则:

​ 对于已经投入使用的类,尽量不要修改,推荐定义一个新的类,重复利用其中共性内容

​ 并且添加新内容。

super关键字的用法有三种
1. 在子类的成员方法中,访问父类的成员变量。
2. 在子类的成员方法中,访问父类的成员方法。
3. 在子类的构造方法中,访问父类的构造方法。
this关键字的用法

​ 1.在本类的成员方法中,访问本类的成员变量。

​ 2.在本类的成员方法中,访问本类的另一个成员方法。

​ 3.在本类的构造方法中,访问本类的另一个构造方法。

​ 在第三种用法当中要注意:

​ this(…)调用也必须是构造方法的第一个语句,唯一一个。

​ super和this两种构造调用,不能同时使用。

注意:

成员变量和成员方法的调用都遵循就近原则

方法重写()
继承的特点
1. Java语言是单继承,一个类的直接父类只能有唯一一个
2. Java语言可以多级继承。A --》B extends A --》C extends B
3. 但是一个父类可以拥有很多个子类。A --》 B extends A      C extends A  ......

抽象类和抽象方法

/*
    抽象方法:就是加上abstract关键字,去掉大括号,直到分号结束
    抽象类:抽象方法所在的类,必须是抽象类。在class之前加上abstract即可
    抽象类中也可以定义普通成员方法
    如何使用抽象类和抽象方法:
    1.不能直接new抽象类对象
    2.必须用一个子类来继承抽象父类
    3.子类必须覆盖重写父类当中的所有抽象方法
    覆盖(实现):子类去掉抽象方法的abstract关键字,补上大括号方法体
 */
public abstract class Animal {
    //这是一个抽象方法,代表吃东西,具体吃什么不知道
    public abstract void eat();
}

一个抽象类不一定含有抽象方法

只要保证抽象方法所在的类是抽象类,即可

没有抽象方法的抽象类,也不能直接创建对象,在一些特殊的场景下有用途

static关键字

被static修饰的成员,会被该类的所有对象所共享

被static修饰的成员,会随着类的加载而加载,优先于对象存在

多了一种调用方式,通过 类名.成员变量(方法) 调用

静态方法只能访问静态方法、静态变量;不能访问非静态

静态方法中不能有this关键字

分包思想与分类思想

分包思想

如果将所有类文件都放在同一个包下,不利于管理和后期维护

对于不同功能的类文件,可以放在不同的包下进行管理

包的存在

1、有利于类的查找与管理,按自己的需要给包分类,通常按功能分:vo类、dao类、工具类、service类、controller类等。

2、解决了类命名时相同的冲突,在同一文件夹里不能同时定义名字相同的两个文本文档,java中也是,不能在同一包里定义两个相同类名的类,但是不同包就可以。

3、保护类中成员变量及其方法。该变量和方法的使用范围取决于访问控制符。

包的命名规则:

​ 1、全部由小写字母
​ 2、包含多层次应用“.”分割
​ 3、一般由倒置的域名开头
​ 4、自定义包不能以java开头

package关键字定义包

package com.itheima.entry;

类与类之间的访问

同一个包下的访问

​ 不需要导包,直接使用

不同包下的访问

​ import导包后访问

​ 通过全类名(包名+类名)访问

注意:

​ package必须是程序的第一条可执行代码

​ import需要写在package下面

​ class需要在import下面

引包的情况:
1、在自定义类中使用在不同包中其他自定义类时
2、使用除java.lang包外,其他包中的jdk中的类需要引包
3、使用第三方jar包中的类或接口时需要引包

分类思想

分工协作,专人干专事

提高代码的维护性,可读性以及复用性

  • 实体类 标准类,封装数据的类
  • dao类 Dao(Data Access Object缩写),用于访问存储数据的数组或集合的类
  • service类 用来进行业务逻辑的处理
  • controller类 用来和用户打交道
用户 -请求-> controller --> service --> dao --> 数据集合或数组
用户 <-回复- controller <-- service <-- dao <-- 数据集合或数组

final关键字

final修饰基本数据类型的变量时,该变量只能初始化一次并且不能重新赋值

final修饰引用数据类型的变量时,该变量内的元素值可以改变,地址值不能被改变

final修饰的方法不能被子类重写

final修饰的类不能被继承,称为终态类

代码块

在Java中,使用{}括起来的都被称为代码块

分类:

  • 局部代码块
    • 位置:方法中定义
    • 作用:限定变量的生命周期,及早释放,提高内存利用率
  • 构造代码块
    • 位置:类中方法外
    • 特点:每次构造方法执行时,都会执行该代码块中的代码,并且在构造方法执行前执行
    • 作用:将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性
  • 静态代码块
    • 位置:类中方法外定义
    • 特点:需要通过static关键字修饰,随着类的加载而加载,并且只执行一次,多个静态代码块
      • 与一个静态代码块是一样的
    • 作用:在类加载的时候做一些数据初始化的操作

方法重写

在继承体系中,子类出现和父类一模一样的方法声明

方法重写的应用场景3

  • 当子类需要父类的功能,而功能主体子类有自己特有内容,可以重写父类当中的方法,即沿袭了父类的功能,又定义了子类特有的内容(方法名,参数列表,返回值类型必须一致)

方法重写注意事项

  • 父类私有方法不能被重写

  • 父类非静态方法,子类也必须通过非静态方法进行重写

  • 静态方法不能被重写!如果子类中,也存在一个和父类一模一样的静态方法,可以理解为,子类将父类中同名的方法,隐藏了起来,并非是方法重写

  • 子类重写父类方法时,访问权限必须大于等于父类

修饰符 同一个类中 同一个包中子类与无关类 不同包的子类 不同包的无关类
private 可访问
默认 可访问 可访问
protected 可访问 可访问 可访问
public 可访问 可访问 可访问 可访问

mysql数据库下载地址:https://download.mysql.com/archives/community/

接口

Java接口是一系列方法规范的集中说明,这些方法通常只有方法特征没有方法体

成员变量

​ 接口的成员变量默认加上了 public static final修饰,所以接口中的成员变量都是常量

构造方法

​ 接口中没有构造方法

成员方法

​ 接口中的方法访问权限(不写)默认都加上了public

​ jdk1.8之后接口中有抽象方法、默认方法、静态方法、私有方法

​ 默认方法解决了接口升级问题

​ 静态方法通过接口名调用

​ 私有方法是为了不同方法中相同代码块的抽取

public interface 接口名{}
public class 实现类名 implements 接口名 1,2,3,4...、{ }

定义:接口的存在是为了实现程序的可扩展性。

存在意义:如果你的类的结构体系中,某一个类要扩充功能怎么办?那么我们就要去修改这个类的父类甚至说是超类吗?这显然不合理。(而且如果你使用别人提供的类,根本就不可能去修改它)也许你要一直追溯到Object都不行。可是使用接口,你想给这个体系中的某个类扩充功能,只需要给这个类实现一个新的接口,自然就会提供新的功能,丝毫不会影响它的超类,而它的子类自动也扩充了它新增加的这个接口的方法(有点象C++多继承)。这使的软件的功能扩展变得更容易。设计模式中有一条开闭原则,说:软件实体必须都修改关闭,对扩展开放。使用接口,就可以满足这样的设计要求。

应用场景:该类下某些子类需要该方法而有一些则不需要,那么就在需要的子类下接入接口即可

多态

​ 多态的使用场景:有继承或者实现关系,有方法重写

​ 当定义一个方法,方法的参数写成父类,调用方法是可以传递任意的子类对象

​ 多态的含义:一个子类对象在不同时刻表现出的多种状态

多态的成员访问特点
成员变量

​ 编译看左边,运行看左边

成员方法

​ 编译看左边,运行看右边

​ 访问特点不一样的原因:成员变量无重写,方法有重写

多态的弊端与好处

​ 好处:提高了程序的扩展性,定义方法时,父类类型作为参数,该方法就可以接收任意的子类对象

​ 弊端:不能使用子类特有的内容

​ 解决:1.直接创建子类对象

​ 2.向下转型

​ 从父类类型转换回子类类型可能出现ClassCastException,避免出现问题可使用 instanceof关键字,判断关键字左边的对象引用是否是右边的对象类型,返回结果 是boolean类型的结果

内部类

1.成员内部类

​ 位置:类的成员位置

public class Outer{
	class Inner{
		public void show(){
			System.out.println("show.....");
		}
	}
}
public class Test{
	public static void main(String[] args){
		//创建成员内部类对象
		Outer.Inner oi=new Outer().new Inner();
		oi.show();
	}	
}
2.局部内部类(方法中)
3.匿名内部类(精通,当需要一个子类对象又不想定义子类时)

​ 前提:要有一个类/接口

​ 格式:

​ new 接口/类(){

​ 重写接口或类的抽象方法

​ }

​ 本质:匿名内部类的本质是对象(是接口或类的子类对象)

interface Calculator{
	public abstract int calc(int x,int y);
}

public class Test{
	public static void main(String[] args){
		int x=10;
		int y=20;
		userCalc(new Calculator(){//创建子类对象又不想定义子类
			public int calc(int a,int b){
				return a+b;
			}
		},x,y);
	}
	public static void useCalc(Calculator c,int x,int y){
		int sum=c.calc(x,y);
		System.out.println(sum);
	}
}

lambda表达式

作用:Lambda表达式可以对匿名内部类的代码进行简化

使用前提:

​ 要有函数式接口,并且只能有一个抽象方法

​ 把函数式接口作为方法参数,别人调用方法时就可以传递Lambda表达式

interface Calculator{
	public abstract int calc(int x,int y);
}

public class Test{
	public static void main(String[] args){
		int x=10;
		int y=20;
		//Lambda表达式
		userCalc((int a,int b)->{return a+b;},x,y);
		//简化写法,省略小括号中的数据类型,大括号,以及return和分号
		userCalc((a,b)->a+b);
		//如果接口方法只有一个参数
		//userCalc(s->System.out.println("好好学习"))
	}
	
	public static void useCalc(Calculator c,int x,int y){
		int sum=c.calc(x,y);
		System.out.println(sum);
	}
}

JavaAPI中常用的类

Math类:用于做数学运算
public static main(String[] args){
	 int a=-10;
        //求绝对值方法 abs(),返回值类型int
        int abs = Math.abs(a);
        //四舍五入方法round(),返回值类型long
        double b=3.44;
        double b1=3.55;
        long r1 = Math.round(b);
        long r2 = Math.round(b1);
        System.out.println(r1);//3
        System.out.println(r2);//4
        //向上取整 ceil(),
        double d1 = Math.ceil(b);//4.0
        System.out.println(d1);
//        floor: 向下取整
        double f1 = Math.floor(b1);
        System.out.println(f1);//3.0
//        max: 求最大值
        double max = Math.max(b, b1);
        System.out.println(max);//3.55
//        min: 求最小值
        double min = Math.min(b, b1);
        System.out.println(min);//3.44
//        random: 随机数
        double rad = Math.random();//范围[0,1)之间的小数
        System.out.println(rad);
}
System类:系统类
 public static void main(String[] args) {
//        currentTimeMillis: 获取当前系统时间的毫秒值(距离1970年1月1日0时0分0秒)
        long timeStart = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            System.out.println(i);
        }
        long timeEnd = System.currentTimeMillis();
        System.out.println(timeEnd-timeStart);
//        arraycopy: 把一个数组复制到另一个数组中
        String[] sArr={"h","e","l","l","o"};
        String[] sArr1=new String[10];
        System.arraycopy(sArr,0,sArr1,3,4);
        for (int i = 0; i < sArr1.length; i++) {
//            if (sArr1[i]!=null) {
                System.out.print(sArr1[i] + " ");//null null null h e l l null 															//null null
//            }
        }
//        exit:  退出虚拟机,非0为异常退出
        System.exit(0);
}
Object类:是所有类的父类,任何一个类都可以使用它的方法
//toString: 把对象转换为字符串,建议子类重写
	Student s1=new Student();
	String s3 = Objects.toString(s1);//不重写打印地址值
//equals:  比较的是对象的地址,如果不想比较地址子类可以重写。
	String str=new String("haha");
	StringBuilder sb=new StringBuilder("haha");
	System.out.println(str.equals(sb));//false
	System.out.println(sb.equals(str));//false,SB中没有equals方法,去Object找,比											//较地址
Objects类:是一个工具类,用于对对象进行判空等操作
	String s=null;
//isNull  等价于  i == null
	boolean b=Objects.isNull(s);//true
//nonNull 等价于  i !=null
	boolean b1=Objects.nonNull(s);//false
BigDecimal类:主要用于对数据进行精确运算
//add: 加法
	BigDecimal bd=new BigDecimal("10").add(new BigDecimal("20"));
	System.out.println(bd);//30
//subtract: 减法
	BigDecimal bd1=new BigDecimal("10").subtract(new BigDecimal("20"));
	System.out.println(bd1);//-10
//multiply: 乘法
	BigDecimal bd2=new BigDecimal("10").multiply(new BigDecimal("20"));
	System.out.println(bd2);//200
//divide: 除法
	BigDecimal bd3=new BigDecimal("10").divide(new BigDecimal("20"));
	System.out.println(bd3);//0.5
	
	BigDecimal b1=new BigDecimal("10");
	BigDecimal b2=new BigDecimal("3");
	BigDecimal b3=b1.devide(b2,2,BigDecimal.ROUND_HALF_UP);//3.33
	double dob=3.1415926;
	BigDecimal bd4=BigDecimal.valueOf(dob);//转换一个 double成 BigDecimal ,使用 						//double通过所提供的规范的字符串表示 Double.toString(double)方法
基本数据包装类

Byte,Short,Integer,Long,Float,Double,Character,Boolean

Integer

​ 构造方法已经过时

​ 使用valueOf()方法创建对象

//注意:valueOf(),如果参数大于127,会重新创建对象
		System.out.println(127);
        System.out.println(127);
        System.out.println(i1==i2);//true
        
        Integer i3 = Integer.valueOf(128);
        Integer i4 = Integer.valueOf("128");
        System.out.println(i3==i4);//false
//主要用于字符串和基本数据类型的转换,【口诀:想要转换为什么类型,就找什么类型的包装类 	    parseXxx的方法】
		String s="1243";
		int i=Integer.parseInt(s);
Arrays类:是一个操作数组的工具类,排序、查找等。
int[] array = {1,2,3,4,5,6};
//想对数组排序
Arrays.sort(array);
//想对数组进行二分查找(要求元素必须排序、元素不能重复)
int index = Arrays.binarySearch(array,5);
//把数组变成字符串,方便打印
String s = Arrays.toString(array);
二分查找
public static int chaZhao(int[] arr,int num){
        //定义查找范围
        int min=0;
        int max=arr.length-1;
        //不知道循环次数,使用while循环,满足max>=min时执行循环体代码
        while(max>=min) {
            //每次都要查找中间的元素,定义一个变量mid为数组中间索引
            int mid=(max+min)/2;
            //如果中间元素比要查找的元素大,那说明右边的元素都比num大
            if (arr[mid]>num){
                max=mid-1;
            }
            //如果中间元素比要查找的元素小,那说明左边的元素都比num小
            if (arr[mid]
冒泡排序
public static void bubbleSort(int[] array){
	for (int i = 0; i < array.length-1; i++) {
        for (int j = 0; j < array.length-i-1; j++) {
            if (array[j] > array[j+1]) {
                int temp=array[j];
                array[j]=array[j+1];
                array[j+1]=temp;
            }
        }
	}
}
快速排序
    public static void kuaiPai(int[] array, int left, int right) {
        if (left>right){
            return;
        }
        int left0=left;
        int right0=right;
        //确定基准数
        int baseIndex=array[left0];
        while(left!=right){

            //从右往左找比基准数小的
            while(array[right]>=baseIndex&&left

时间日期类

JDK1.7中的时间类

date类

Date代表一个精确的时间,精确到毫秒。创建一个Date对象,其实就表示时间的对象

Date date1=new Date(); //当前系统时间
Date date2=new Date(0L); //1970年1月1日8点0时0分0秒(中国,东八区,加了8小时)

获取和设置时间

Date date=new Date(); //当前系统时间
//设置时间为
date.setTime(1000L*3600*24);//1970-01-02 08-00-00

//获取时间
long time=date.getTime();//获取计算机原点到所设置时间的毫秒值
System.out.println(time);//毫秒值
SimpleDateFormat类

SimpleDateFormat类可以对日期进行格式化和解析的操作

SimpleDateFormat在进行日期格式化或者解释时,需要识别一些特定符号
	yyyy:	年  			1999年
	MM:		年中的月	  8月
	dd:		月中的天	  28日
	HH:		天中的小时	 16时
	mm:		时中的分	  20分
	ss:		分中的秒      18秒
	SS:		毫秒
	
//在进行日期格式化和解析之前,必须使用以上的符号创建SimpleDateFormat对象,来指定日期或者时间的格式
SimpleDateFormat sdf=new SimpleDateFormat("yyyy年MM月dd日 HH时mm分s秒");

下面是格式化和解析的代码

SimpleDateFormat sdf=new SimpleDateFormat("yyyy年MM月dd日 HH时mm分s秒");
//1.日期格式化:Date对象-->String
String str=sdf.format(new Date());//把当前时间格式化为字符串
//2.日期解析:String-->Date对象
Date date=sdf.parse("1992年08月09日 16时24分18秒");

JDK8新增的日期类

LocalDateTime类 可以表示时间的(年月日时分秒)
public static LocalDateTime now()
    获取当前的系统时间LocalDateTime对象
public static LocalDateTime of(int y,int m,int d,int h,int m,int s)
    获取指定时间的LocalDateTime对象
LocalDateTime的获取方法
public int getYear()
    获取年
public int getMonthValue()
    获取月
public int getDayOfMonth()
    获取月中的天
public int getDayOfYear()
    获取年中的天
public DayOfWeek getDayOfWeek()
    获取星期中的第几天
public int getHour()
    获取小时
public int getMinute()
    获取分钟
public int getSecond()
    获取秒
LocalDateTime的转换方法
三者的区别
	LocalDateTime: 包含年、月、日、时、分、秒
	LocalDate: 包含年、月、日
	LocalTime:  包含时、分、秒
	
可以通过LocalDateTime转换为LocalDate和LocalTime
	public LocalDate toLocalDate()
        把LocalDateTime转换为LocalDate
    public LocalTime toLocalTime()
        把LocalDateTime转换为LocalTime
LocalDateTime的格式化和解析
public String format(DateTimeFormatter formatter)  
    格式化:把LocalDateTime转换为String
    
public static LocalDateTime parse(
    CharSequence text, DateTimeFormatter formatter)  
    解析:把字符串转换为LocalDateTime对象
  • 日期格式化代码演示
//获取当前时间的LocalDateTime对象
LocalDateTime localDateTime = LocalDateTime.now();

//创建日期格式化器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

//格式化
String str=localDateTime.format(formatter);
System.out.println(str);
  • 日期解析代码演示
//创建日期格式化器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

//日期解析
LocalDateTime localDateTime= LocalDateTime.parse("2020-08-23 15:19:39",formatter);

//...后面就可以使用LocalDateTime的方法,对时间进行操作 
LocalDateTime加减运算方法
public LocalDateTime plusYears(int n)
    加、减年
public LocalDateTime plusMonths(int n)
	加、减月
public LocalDateTime plusDays(int n)
	加、减日
public LocalDateTime plusHours(int n)
	加、减时
public LocalDateTime plusMinutes(int n)
	加、减分
public LocalDateTime plusSeconds(int n)
	加、减秒
LocalDateTime设置时间的方法
public LocalDateTime withYear(int n)
    设置年
public LocalDateTime withMonth(int n)
    设置月
public LocalDateTime withDay(int n)
    设置日
public LocalDateTime withHour(int n)
    设置时
public LocalDateTime withMinute(int n)
    设置分
public LocalDateTime withSecond(int n)
    设置秒

时间间隔类

Period类

计算两个时间间隔的年、月、日。

//生日的时间
LocalDate localDate1 = LocalDate.of(1996, 6, 23);
//今天的时间
LocalDate localDate2 = LocalDate.now();

//获取时间间隔对象
Period period = Period.between(localDate1,localDate2);

//获取相隔的年
int years = period.getYears();
System.out.println(years);

...
Duration类
//生日的时间
LocalDateTime localDate1 = LocalDateTime.of(1996, 6, 23,13,34,22);
//今天的时间
LocalDateTime localDate2 = LocalDateTime.of(1996, 7, 13,06,34,11);

//获取时间间隔对象
Duration duration = Duration.between(localDate1,localDate2);

//获取相隔的小时
long years = duration.toHours();
//获取相隔的分钟
long minutes = duration.toMinutes();
//获取相隔的秒
long seconds = duration.toSeconds();
//相隔的毫秒
long millis = duration.toMillis();

异常

异常其实就是程序在编译和运行时期的一些不正常的异常。 Java的工程师们,把一些常见的问题都封装了异常类 ,在API中给你列举出来了。当我们使用Java语言写代码的时候,可能会出现这些问题,当出现这些问题的时候,就可以去API中找,哪里出现了问题。

异常类的继承体系

Throwable 是所有异常和错误的根类
	Error: 错误,不能解决的问题。
	Exception: 异常,可以解决的问题。
		RuntimeException: 运行时异常,在运行时才出现问题
		非RuntimeException:编译时异常,编译时有问题。

异常处理

  • 虚拟机自己处理
直接停止程序运行,然后把异常信息打印在控制台
  • try…catch手动处理
try{
    //检查代码中是否有异常
}catch(异常类 e){
    //如果try中有异常,就会被catch捕获到
}catch(异常类1 e){
	...
}finally{
	不管是否有异常,最终都会执行
}

好处:处理之后的代码还可以继续执行
  • throws声明异常

throws用在方法上,用来声明一个方法它可能会有异常

//如果一个方法后面有throws声明异常,那么调用方法的时候就需要处理异常。
public static void method(String str) throws ParseException {
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
    Date date = sdf.parse(str);
    System.out.println(date);
}

Throwable的方法

public  String getMessage()  
    获取异常信息
public String toString()  
    返回异常类名+异常信息
public void printStackTrace() 
    打印详细的异常信息

throw 产生一个异常

如果你是写API的人,在写方法的时候,会考虑到方法的调用者传递非法的参数。你必须先对这些参数左一些合法性的校验,如果不通过,告诉调用者一个异常。

public static void main(String[] args) {
    sale(7);
}

public static void sale(int money){
    if(money==5||money==10){
        System.out.println("钱是真的,可以正常买东西");
    }else{
        throw new RuntimeException("钱是假的,我报警了,等着!!!");
    }
}

自定义异常

如果在写一个方法的时候,API中提供的异常类,还不足以说明程序的问题。你就可以自己定义一个异常类

自定义异常类的步骤:
	1.写一个类继承Exception或者RuntimeException
	2.自动生成两个构造方法
	
注意:自定义异常,名称一定要见名知意(最好是能够使用异常类的名称,来说明问题)
    ArrayIndexOutOfBoundsException //数组索引超过边界一场
    NullPointerException //空指针一场

Collection

集合与数组的区别:

数组在创建时长度固定,集合存储元素的个数可变

Collection是所有单列集合的顶级接口,没有具体的实现类,有两个重要的子接口分别是list接口和set接口

Collection接口中一些公有的方法,Collection下的实现类都可以使用

add(具体元素)  向集合末尾添加元素(对象)的方法
remove(具体元素) 删除集合中具体的元素(对象),返回值boolean类型的
clear() 清空集合
contains(具体元素) 如果集合中包含参数元素(对象),则返回true
isEmpty() 判断集合是否为空,为空返回true
size() 获取集合中元素(对象)的个数
equals()  判断集合中元素(对象)与指定元素(对象)是否相等
集合的遍历方式
一、普通for循环遍历,只能遍历有索引的集合
for (int i = 0; i < linkedList.size(); i++) {
	System.out.println(linkedList.get(i));
}
二、使用Iterator迭代器对象遍历
Iterator it = linkedList.iterator();
while (it.hasNext()){
	String s = it.next();
	System.out.println(s);
}
三、使用增强for循环遍历
for (String s : linkedList) {
	System.out.println(s);
}

数据结构

数据的组织方式就叫做数据结构,不同的组织方式就形成不同的数据结构,每一种数据结构的特点不同

栈结构:先进后出
队列结构:先进先出
数组:查询快,增删慢,一个固定的容器,
链表:查询慢,增删快,分为单向链表和双向链表

List接口

List接口继承了Collection接口

特点:List下的集合都是有序的(有索引),集合元素允许重复

List接口的一些公有方法,List的实现类都可以使用

add(index,e)  将元素e(对象)插入此列表的指定index位置
get(index) 获取集合索引位置的元素
remove(index) 删除指定索引的元素,返回值删除的元素
set(index,e) 将指定索引处的元素修改为e元素(对象),返回值为修改前元素
ArrayList集合

ArrayList集合是List接口的实现类,查询快,增删慢

ArrayList集合底层原理:

当创建ArrayList集合并且第一次添加元素时,底层会创建一个长度为10的数组

元素会储存到数组中

底层数组元素存满后,再存储元素时,集合底层会对数组进行自动扩容为原数组的1.5倍

将原数组的元素复制到新数组中,再把新元素添加到新数组位置的后面

LinkedList集合

LinkedList是List接口的实现类,查询慢,增删快

LinkedList集合的底层原理

LinkedList是在底层属于双向链表结构,在LinkedList底层会有两个属性,用来记录头结点和尾结点

first:记录头结点的位置

last:记录尾结点的位置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2zbVEUPv-1643358422476)(D:\BaiduNetdiskDownload\JavaSE进阶\day07\重点截图\链表的图解.png)]

-----------------------------------------------------------------------------------------------------------------------------------------

泛型

概述:在定义方法或者类时,有不确定的数据类型时,使用<任意大写字母>定义

泛型类
public class 类名{}
	泛型类的泛型数据类型在创建类的对象时确定
	比如:类名 引用名=new 类名<>();
泛型方法
public  void 方法名(T t){}
	方法中的泛型定义在返回值的前面
	泛型的数据类型在调用方法传递参数时,传递的参数是什么类型T就是什么类型
泛型接口
public interface 接口名{}
	接口上的泛型在接口的实现类上介意确定
		如:public class InterfaceImpl implements 接口名<具体的数据类型>{}
	把泛型沿用到实现类中,它的实现类就是泛型类,可以在创建泛型类对象时确定具体的数据类型
泛型的通配符
	指定类型和指定类型的子类
	指定类型和指定类型的父类
 			 任意类型

TreeSet

set集合的特点:无索引、元素不可重复、存和取的顺序不一样(无序)

TreeSet是Set的实现类:可以对元素按照规则进行排序

给TreeSet指定排序规则

自然排序:让元素实现Comparable接口,复写compareTo方法,底层会自动根据该方法的结果是正数、负数、			或者0还决定顺序。
如:
public class Student implements Comparable{
	private String name;
	private int age;
	...
	@override
	public int compareTo(Student o){
		int result = this.age-o.age;
		result=result==0?this.name.compareTo(o.name):result;
		return result;
	}
}
比较器排序:创建TreeSet对象时,指定Comparator比较器,由比较器提供排序规则
如:
	TreeSet ts=new TreeSet<>(new Comparator(){
		@Override
		public int compare(Student o1,Student o2){
			//o1:即将添加到集合的元素
			//o2:集合中已有的元素
			int result=o1.getAge()-o2.getAge();
			result=result==0?o1.getName().compareTo(o2.getName()):result;
			return result;
		}
	});

优先使用Comparable,如果Comparable不满足要求,才使用比较器Comparator

二叉树结构

二叉树:二叉树的每一个节点的度都不大于2

二叉查找树(二叉排序树、二叉搜索树):对于任意的一个节点,左边的子节点都比这个数小,右子节点都比这个数大。

二叉平衡树:每一个节点,左右两个子树的高度差不超过1
	
如果一个二叉树不平衡,如何达到平衡?
	左旋:如果右子树比左子树的高度差大于1,就需要左旋
	右旋:如果左子树比右子树的高度差大于1,就需要右旋
	
破坏平衡二叉树的几种情况,如何再次达到平衡
	左左:在左子树的左边添加元素,直接右旋即可
	左右:在左子树的右边添加元素,先左旋再右旋

​	右右:在右子树的右边添加元素,直接左旋即可
​	右左:在右子树的左边添加元素,先右旋再左旋

红黑树

红黑树
	红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构
	红黑树不是高度平衡的,它的平衡是通过“红黑规则“进行实现的
红黑规则:
	每一个节点或是红色的,或是黑色的
	根节点必须是黑色
	如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节	 	点都是黑色的
	如果一个节点是红色的,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
	对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点;
如何保证红黑规则
	添加数据时,根节点直接变为黑色
	父节点为红色时
			叔叔节点为红色:1.将父节点设为黑色,将叔叔节点设为黑色
						 2.将祖父节点设为红色
						 3.如果祖父节点为根节点,则将根节点再次变成黑色
			叔叔节点为黑色:1.将父节点设为黑色
						 2.将祖父节点设为红色
						 3.以祖父节点为支点进行旋转

HashSet集合概述和特点

特点:

​ 底层数据结构是哈希表

​ 不能保证存储和取出的顺序完全一致

​ 没有带索引的方法,所以不能使用普通for循环遍历

​ 元素是唯一的,不重复

哈希值:是JDK根据对象的地址或者属性值,算出来的int类型的整数

​ Object类中有一个hashCode方法可以获取对象的哈希值,根据对象的地址计算出来的哈希值

对象哈希值的特点

  • 如果没有重写hashCode方法,那么是根据对象的地址值计算出的哈希值

    同一个对象多次调用hashCode方法返回的哈希值是相同的

    不同对象的哈希值是不一样的

  • 如果重写了hashCode方法,一般都是通过对象的属性值计算出哈希值

    如果不同的对象属性值是一样的,那么计算出来的哈希值也是一样的

常见数据结构值哈希表

​ JDK8以前,底层是采用数组+链表实现

​ JDK8以后,底层进行了优化,由数组+链表+红黑树实现

HashSet---JDK8版本原理解析
	1.创建一个默认长度16,默认加载因子为0.75的数组,数组名table
	2.根据元素的哈希值跟数组的长度计算出应存入的位置
	3.判断当前元素是否为null,如果是null直接存入
	4.如果位置不为null,表示有元素,则调用equals方法比较属性值
	5.如果属性值一样,则不存,不一样,则存入数组,老元素挂在新元素下面,组成链表,链表长度为8,再		添加自动转成红黑树

Map双列集合

特点:Map K:键的数据类型 V:值的数据类型

键不能重复,值可以重复

键和值是一一对应的,每一个键只能找到自己对应的值

​ 键+值:这个整体成为键值对对象,在Java中叫“Entry对象”

常用方法

put(K key,V value);	添加元素
remove(Object key); 删除键值对元素
clear()  清空所有键值对元素
containsKey() 判断集合是否包含指定的键
containsValue() 判断集合是否包含指定的值
isEmpty()  判断集合是否为空
size()  集合中键值对的个数

Map集合的遍历
	一、使用keySet方法,获取Map集合中所有的键,使用Map集合中的get(K key)获取键所对应的值
	Map map=new HashMap()<>;
 	set keys=map.keySet();
	for(K key:keys){
		V value=map.get(key);
	}
	二、使用entryset方法获取键值对的对象(Entry),存入Set集合,使用getKey方法获取键,使		用getValue方法获取键所对应的值
	Set>  entries=map.entrySet();
	for(Map.Entry entry:entries){
		K key=entry.getKey();
		V value=entry.getValue();
	}
	三、使用forEach遍历
	map.forEach((K key,V value)->{
		System.out.println(key+".."+value);
	})

HashMap

​ HashMap底层是哈希表结构的

​ 依赖hashCode方法和equals方法保证键的唯一

​ 如果键要存储自定义对象,需要重写hashCode和equals方法

TreeMap

​ TreeMap底层是红黑树结构

​ 依赖自然排序(键对应的类实现Comparable接口,重写compareTo方法)

​ 比较器排序(在TreeMap集合对象创建时给出Comparator比较器规则)

​ 如果键存储的是自定义对象,需要实现Comparable接口,重写compareTo方法或者

​ 在TreeMap集合对象创建时给出Comparator比较器规则

可变参数

​ 可变参数:就是形参的个数是可以变化的

格式: 修饰符 返回值类型 方法名(数据类型... 变量名){}
例子:public static void sum(int...a){}

可变参数注意事项
	这里的变量其实是一个数组
	如果一个方法有多个参数,包括可变参数,可变参数要放到最后

创建不可变集合

  • 在List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合
  • 这个集合的元素不能添加、删除或者修改,只能查询数据
  • 可以结合集合的带参构造方法,实现批量添加
ArrayList list=new ArrayList(List.of(t1,t2,t3,t4...));
HashMap map=new HashMap<>(Map.of(k1,v1,k2,v2...));

在Map集合中还存在一个ofEntries方法可以提高代码的阅读性

​ 首先会把键值封装成一个Entry对象,再把这个Entry对象添加到集合中

Stream流

概述:Stream流是一种不同于IO流的数据流,它是用来操作集合、数组等数据类型的流,它不能直接修改源集合、数组中的数据

Stream流的获取
单列集合:
	通过Collection接口中的stream方法来获取单列集合的Stream流
	ArrayList list=new ArrayList<>();
	list.stream()
双列集合:
	间接获取:通过keySet方法获取集合中的键或者entrySet方法获取键值对对象,存入Set集合,再获取stream流
	HashMap hm=new HashMap<>();
	Set keys=hm.keySet();
	keys.stream().forEach(key->{
		System.out.println(key+hm.get(key));
	});
	Set> entries=hm.entrySet();
	entries.stream().forEach(entry->{
		System.out.println(entry.getKey()+entry.getValue());
	});
数组:
	通过Arrays数组工具类中的静态stream方法生成流
	Integer[] arr={1,2,3,4,5,6};
	Arrays.stream(arr);
同种数据类型的多个数据
	通过Stream中的of方法获取流
	Stream.of(1,2,3,4,5,6);
Stream流的中间方法
filter()  过滤方法
skip(long n) 从流的头部跳过n个元素
limit(long n) 从流的头部截取n个元素
distinct() 去除流中重复的元素(依赖hashCode和equals方法)
concat(Stream a,Stream b) 合并a和b成为一个新的Stream流
Stream流的终结方法
forEach();		获取流中每一个元素,并对元素进行操作
count();		返回流中的元素个数
Stream流的数据收集操作
Stream流的收集方法(也是Stream流的终结方法):	collect(Collector collector);
工具类Collectors提供了具体的收集方法
	toList()  将元素收集到List集合中
	toSet()	  将元素收集到Set集合中
	toMap()	  将元素收集到Map集合中

File类

表示文件或文件夹的路径

绝对路径:从盘符开始的路径

相对路径:从项目根目录开始的路径

File常用方法
获取文件或文件夹路径的方法:
	getAbsolutePath()  获取文件或文件夹的绝对路径,返回值字符串
	getAbsoluteFile()  获取文件或文件夹的绝对路径,返回值File对象
	
	getParent()		获取文件或文件夹的父路径,返回字符串
	getParentFile()	获取文件或文件夹的父路径,返回File对象
	
	getPath()		获取File对象封装路径
	length()		获取文件的大小(单位:字节)
判断方法:
	isDirectory()	判断文件夹是否存在
	isFile() 		判断文件是否存在
	exists() 		判断文件或文件夹是否存在
创建文件或文件夹
	mkdir() 	创建单级文件夹
	mkdirs()	创建多级文件夹
	createNewFile()	创建文件
	delete() 	删除文件或文件夹,只能删除空文件夹

IO流

IO流是用来对数据进行读和写操作的

按流向分类:输入流:用来读取数据

输出流:用来写入数据

字节流

可以读取、写入任何类型的文件(文本、音频、视频…)

输入流:读取字节数据
	FileInputStream: 
		1.创建文件输入对象
       	FileInputStream fis = new FileInputStream("a.txt");
		2.调用方法读取文件中数据
		int len=0;//一个字节一个字节的读取
        while((len=fis.read())!=-1){
            System.out.print((char) len);
        }
		3.关闭流对象
		fis.close();
		
	//将字节放入byte数组读取
		FileInputStream fis = new FileInputStream("a.txt");
        byte[] bytes = new byte[1024];
        int len;
        //将每次读取到的数据放到bytes数组中,并判断是否满足循环条件
        //len=fis.read(bytes))的意思是从输入流中读取bytes大小的字节,并将其存储再bytes里面返回读取的字节数len
        //每循环一次bytes就被重新赋值一次
        while((len= fis.read(bytes))!=-1){
            //将bytes数组中的数据转换为字符串
            String s = new String(bytes, 0, len);
            System.out.println(s);
        }
        fis.close();
输出流:写入字节数据
	FileOutputStream:
		1.创建文件输出流对象
		FileOutputStream fos=new FileOutputStream("a.txt",true);//true表示不覆盖之前的文件内容
		2.向文件中写入数据
		fos.write(int整数);  //写一个字节
		fos.write(byte数组);  //写多个字节
		fos.write(byte数组, 起始索引, 个数);  //写数组的一部分
		fos.write(字符串.getBytes());   //把字符串转换为字节数组,写入文件
		3.关闭流对象
		fos.close();
复制文件【重点掌握】
		//创建输入、输出流
        FileInputStream fis = new FileInputStream("day11\\a.txt");
        FileOutputStream fos = new FileOutputStream("day11\\b.txt");

        //一边读,一边写
        byte[] bytes = new byte[1024];
        int len; //记录每次读取个有效个数
        while ((len = fis.read(bytes)) != -1) {
            //把每次读取到的有效数据,写入到目标文件中
            fos.write(bytes, 0, len);
        }

        //释放资源
        fos.close();
        fis.close();

字节缓冲流

字节缓冲输出流:BufferedOutputStream
字节缓冲输入流:BufferedInputStream
构造方法:
	BufferedOutputStream(OutputStream out);
	BufferedInputStream(InputStream in);
构造方法需要字节流的原因:字节缓冲流仅仅提供缓冲区。而真正的读写数据还得依靠基本的字节流对象进行操作
BufferedInputStream bis=new BufferedInputStream(new FileInputStream(文件路径));
int len;
while((len=bis.read())!=-1){
	System.out.print(len);
}
bis.close();
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(文件路径));
bos.write(byte数组或者单个字节);
bos.close();

编码表

基础知识:

  • 计算机中存储的信息都是用二进制数表示的
  • 按照某种规则,将字符编程二进制,再存储到计算机中,称为编码
  • 按照同样的规则,将存储在计算机中的二进制数解析显示出来,称为解码
  • 编码和解码的方式必须一致,否则会导致乱码
字符串中的编码解码问题
编码
	byte[] getBytes();  使用平台的默认字符集将该String编码为一系列字节,讲结果储存到新的字节数组中
	byte[] getBytes(String charsetName); 使用指定的字符集将该String编码为一系列字节,将结果存储到新的字节数组中
	
解码
	String(byte[] bytes):通过使用平台的默认字符集解码指定的字节数组来构造新的String
	String(byte[] bytes,String charsetName); 通过指定的字符集解码指定的字节数组来构造新的String

字符流

只能读取和写入纯文本文件

字符流的写入与输出
字符输入流
FileReader()
	read();
字符输出流
FileWriter()
	writer();
	flush();

字符缓冲流

BufferedWriter: 可以将数据高效的写出(输出缓冲流)
	writer();
	newLine();
	flush();
BufferedReader:	可以将数据搞笑的读取到内存(输入缓冲流)
	read();
	readLine();

转换流

将字节流转换为字符流

输入流:InputStreamReader(InputStream in)
输出流:OutputStreamWriter(OutputStream out)

对象操作流

可以把对象以字节的形式写到本地文件,直接打开文件,是读不懂的,需要再次用对象操作流读到内存中

序列化流(对象操作输出流):
	ObjectOutputStream
		writerObject(对象);
	//如果一个类想要序列化,那么这个类必须要实现一个接口 Serializable
	实现了这个接口,就表示这个类的对象可以被序列化
反序列化(对象操作输入流):
	ObjectInputStream
		readObject();
序列号不一致问题:自己手动给出
	private static final long serialVersionUID=1L;
如果一个类的某个成员变量不想被序列化,给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程

Properties集合

Properties是Map集合的实现类

Properties pro=new Properties();
	setProperty(String key,String value);
	getProperty(String key); 返回值是value
	stringPropertyNames(); 返回键的集合(不可修改)
//将本地文件中的键值对读取到集合
	pro.load(InputStream inStream);
	pro.load(Reader reader)
//将集合中的键值对数据保存到本地文件
	pro.store(OutputStream out,String comments);
	pro.store(Writer writer,String comments);

多线程

多个线程在CPU上不停切换执行的过程

并发:同一时刻,多个线程执行在单个CPU上

并行:同一时刻,多个线程执行在多个CPU上

进程:正在运行的软件(独立性,动态性,并发性)

线程:进程中的单个顺序控制流,是一条执行路径,一个应用程序包含多个线程,这样的线程叫做多线程程序

创建线程的方式

继承Thread类
1.创建一个类继承Thread类
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程开启了"+i);
        }
    }
}
2.在main方法中创建类的对象
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
3.调用start方法开启线程,自动调用run方法
mt1.start();
mt2.start();
实现Runnable接口
1.创建一个类实现Runnable接口,重写run方法
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程开启了"+i);
        }
    }
}
2.创建接口实现类的对象,并且作为参数传进Thread类的构造方法中
MyRunnable mr=new MyRunnable();
Thread t1=new Thread(mr);
3.开启线程
t1.start();
实现Callable<>接口
1.创建一个类实现Callable<>接口,重写call方法
class MyRunnable implements Callable{
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程开启了"+i);
        }
        return "线程执行完成了";
    }
}
2.创建接口实现类的对象,并且作为参数传进FutureTask类的对象构造方法参数中,并将FutureTask对象作为参数传进Thread类的对象构造方法参数中
//线程开启之后执行call方法
MyCallable mc=new MyCallable();
//可以获取线程执行完毕之后的结果,也可作为参数传递给Thread对象
FutureTask ft=new FutureTask<>(mc);
Thread t1=new Thread(ft);
3.开启线程
t1.start();
String s=ft.get();//get只能在线程开启之后执行
System.out.println(s);
三种实现方式的优劣

继承Thread类之后不能继承其他类

实现Runnable接口,可以继承其他类,扩展性更高

实现Callable接口,线程执行完之后可以返回执行的结果

Thread类中的方法
static currentThread()		返回正在执行线程对象的引用
getName()		获取当前线程的对象的引用名
setName()		设置指定线程的对象的引用名
getPriority()	获取线程优先级
setPriority()	设置线程优先级
static sleep(毫秒数)	  设置线程停留时间
start()			导致此线程开始执行,调用线程run方法
setDaemon(boolean)		设置守护线程
线程调度模型

分时调度模型:

​ 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片

抢占式调度模型:

​ 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取CPU时间片相对多一些

守护线程:当普通线程执行完毕了,守护线程也会随之消亡

线程安全问题

原因

​ 计算机系统资源分配的单位为进程,同一个进程中允许多个线程并发执行,并且多个线程会共享进程范围内的资源:例如内存地址

当多个线程并发访问同一个内存地址并且内存地址保存的值是可变的时候可能会发生线程安全问题,因此需要内存数据共享机制来保证线程安全问题

解决方法

同步代码块

synchronized(锁对象){//锁对象可以是任意对象,但是要保证唯一性
    多个线程访问共享数据的代码
}

同步方法

public synchronized void method(){//这里的锁对象是this,如果方法加上static,那么锁对象是本类的字节码对象
    多个线程访问共享数据的代码
}

Lock锁

ReentrantLock lock=new ReentrantLock();
//上锁
lock.lock(); //一个线程执行到这里时,其他线程就必须在这里等
	有可能产生安全问题的代码
//解锁
lock.unlock(); //一个线程执行完,其他线程才能进来执行

等待与唤醒机制

作用:让线程之间协同工作,生产者消费者就是协同工作的体现

wait();  让线程等待,使用锁对象来调用
notify();	唤醒等待的线程中的一个
notifyAll();	唤醒所有等待的线程

阻塞队列实现等待唤醒机制

BlockingQueue的核心方法:
	put(anObject):将参数放入队列,如果放不进去会阻塞
	take():去除第一个数据,取不到会阻塞
常见类
	ArrayBlockingQueue:底层是数组,有界
	LinkedBlockingQueue:底层是链表,无界。但不是真正的无界,最大值为int的最大值

死锁产生原因:

  1. 互斥,共享资源 X 和 Y 只能被一个线程占用;
  2. 占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;
  3. 不可抢占,其他线程不能强行抢占线程 T1 占有的资源;
  4. 循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。

多线程高级

线程状态
NEW 	创建线程对象,并未启动时

RUNNABLE	调用start方法时,JVM中执行的线程处于就绪状态

BLOCKED		阻塞状态,等待监视器(锁)的状态

WAITING		无限等待状态,一个线程锁对象调用wait方法时线程处于此状态,直到另一个线程锁对象(唯一)调用notify方法唤醒它

TIMED_WAITING 	正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。

TERMINATED 		已退出的线程处于此状态。 
线程池

概述:

​ 线程池是一个线程管理容器,我们需要执行任务交给线程池就行了

线程池的创建,API中Executors类中提供了创建线程池的方法

public static  ExecutorService newCachedThreadPool() 
	创建一个可缓存的线程池,线程池没有长度限制。线程池长度超过处理需要,可灵活回收线程
public static ExecutorService newFixedThreadPool(int nThreads)
	创建一个指定长度的线程池,可控制线程最大并发数,超出的线程在队列中等待
public static ExecutorService newSingleThreadExecutor() 
	创建一个单线程的线程池,一次只能执行一个任务
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 
	创建一个定长的线程池,并且支持定时执行和周期性执行任务
	ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
               System.out.println(Thread.currentThread().getName()+"执行了");
            }
        },3,1, TimeUnit.SECONDS);
ThreadPoolExecutor类

API中提供的线程池是用ThreadPoolExecutor来实现的

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
       int corePoolSize:核心线程数
       int maximumPoolSize:最大线程数
       long keepAliveTime,临时线程存活时间
       TimeUnit unit:临时线程存活时间单位
       BlockingQueue workQueue:阻塞队列,可以等待的任务数
       ThreadFactory threadFactory:创建线程工厂
       RejectedExecutionHandler handler:超过最大线程数加阻塞队列任务数时执行的拒绝策略
	四种拒绝策略:
   		ThreadPoolExecutor.AbortPolicy
   				总是抛出RejectedExecutionException。 
   		ThreadPoolExecutor.CallerRunsPolicy
   				调用任务的run方法绕过线程池直接执行
   		ThreadPoolExecutor.DiscardOldestPolicy
   				抛弃队列中等待最久的任务,然后把当前任务加入队列中
   		ThreadPoolExecutor.DiscardPolicy
   				丢弃任务,但是不抛出异常,不推荐
线程安全的细节问题
原子性:

​ 多个操作是不可分割的原子项,要么同时成功,要么同时失败

原子性体现:每一个线程都会有自己的一片栈空间,共享堆内存中的数据,如果多个线程在访问共享数据时,可能有安全问题

1)从主内存中拷贝变量副本到线程的栈内存中
2)在线程的栈内存中对数据进行操作
3)再把数据写回给主内存

这三个步骤是不可分割的,但是由于CPU的随机性可能只执行了2步,执行权就被抢走了,就破坏了原子性。
volatile关键字

​ 只能保证每次获取的是主内存中最新的数据,并不能保证安全性

volatile与synchronized的区别:

​ synchronized:保证原子性,把前面的三个步骤都锁死

​ volatile:不能保证原子性,前面三个步骤可能会被其他线程破坏

AtomicXxxx类

保证数据原子性的类,java中现在有12个Atomic类

原子标量:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
更新类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
复合变量类:AtomicMarkableReference,AtomicStampedReference
如:
	AtomicInteger类:原子性的整数,提供了一些方法对整数进行运算,保证原子性
	public int incrementAndGet()	自增并获取新值,返回值为自增后的值
	public int getAndIncrement()	获取值并自增,返回值为自增前的值
	public int getAndAdd(int delta)	获取值并与实参相加,返回旧值
	public int addAndGet(int delta)	旧值与实参相加,返回新值
	public int getAndSet(int newValue)	设置值为实参,返回旧值
原理:CAS算法
CAS简介

CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

CAS(Compare And Swap)意为比较并且交换,CAS它是一个原子操作。CAS操作涉及到三个值:当前内存中的值V,逾期内存中的值E和待更新的值U。如果当前内存中的值V等于预期值E,则将内存中的值更新为U,CAS操作成功。否则不更新CAS操作失败。

悲观锁和乐观锁
悲观锁:
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中的synchronized锁和ReentrantLock都是悲观的锁。
乐观锁:
每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。
以往学习的一些类的总结

概述:以往学习的类有些是线程安全的,有些是线程不安全的
1.ArrayList和Vector
ArrayList: 底层是数组结构,线程不安全的,效率高
Vector: 底层是数组结构,线程安全的,效率低
2.HashMap和Hashtable、ConcurrentHashMap
HashMap:哈希表结构(数组+链表)、线程不安全的,效率高
Hashtable: 哈希表结构(数组+链表)、线程安全的(synchronized),效率低
ConcurrentHashMap: 哈希表结构(数组+链表+红黑树),线程安全(synchronized、CAS都用到了)

3.StringBuillder 和 StringBuffer
	StringBuillder:线程不安全的
	StringBuffer:线程安全的

网络编程

在网络通信协议下,不同计算机上运行的程序,可以进行数据传输

TCP/IP对OSI(指的是开放式系统互联,是设计和描述计算机网络通信的基本框架。)的网络模型层进行了如下划分

OSI层 功能 设备 对应的TCP/IP协议
应用层 用户接口。应用程序(文件传输,电子邮箱,虚拟终端) 网关 HTTP,DNS、FTP
表示层 数据的标识,压缩和加密(数据格式化、代码转换、数据加密) 网关 没有协议
会话层 会话的建立和结束(解除或建立与别的节点的联系) 网关 没有协议
传输层 提供端对端的接口 网关 TCP、UDP
网络层 为数据包选择路由,寻址 路由器 IP等
数据链路层 保证无差错的数据链路,传输有地址的帧以及错误检测功能 交换机 SLIP、CSLIP、ARP
物理层 传输比特流,以二进制数据形式在物理媒体上传输数据 集中线,中继器 ISO2110、GBK等
网络编程三要素
IP地址:
	设备在网络中的地址,是唯一标识	IPV4:4个字节,32个比特位,点分十进制表示法:					192.168.67.20
	IPV6:16个字节,2个字节为一组,一共128个比特位,冒分16进制表示法
				2409:8970:91c:19b3:90d0:c916:dc04:ff8c
端口号:
	应用程序在设备中唯一的标识
	两个字节表示的整数,取值范围0~65535,0~1023之间的端口号用于一些知名的网络服务或应用。一个端口只能被一个应用程序使用
协议:
	数据在网络中传输的规则,常见的协议有UDP和TCP协议
	UDP(用户数据包协议):面向无连接的通信协议,速度快,有大小限制,一次最多发送64k,数据不安全,容易丢失
	TCP(传输控制协议):面向连接的通信协议,速度慢,没有大小限制,数据安全

InetAddress类

​ 对IP地址获取和操作,Java提供了这个类

InetAddress常用方法:
InetAddress getByName	获取主机名称的IP地址,主机名可以是机器名或IP地址
String getHostName		获取此IP地址的主机名
String getHostAddress	返回文本显示中的IP地址字符串

UDP通信程序

发送数据
1.创建发送端DatagramSocket对象
2.创建数据并把数据打包(DatagramPacket)
3.调用DatagramSocket对象的方法发送数据
4.释放资源
例:
		DatagramSocket datagramSocket =null;
        try {
            //创建数据发送和接收数据的对象
            datagramSocket = new DatagramSocket();
            //创建数据包对象
            DatagramPacket datagramPacket = new DatagramPacket(
                    "键盘侠留笔".getBytes(),
                    "键盘侠留笔".getBytes().length,
                    InetAddress.getByName("224.0.1.1"),
                    10000
            );
            //数据交给socket对象发送
            datagramSocket.send(datagramPacket);

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //释放资源
            datagramSocket.close();
        }
接收数据
1.创建接收端的DatagramSocket对象
2.创建一个数据包Datagrampacket对象,用于接收数据
3.调用DatagramSocket的方法接收数据并将数据放入箱子里
4.解析数据包,使用数据包的getData方法获取数据
5.释放资源
例:
		//创建DatagramSocket数据的接收与发送对象
        DatagramSocket datagramSocket=new DatagramSocket(10086);
        //创建一个数据包对象用于接收传过来数据
        byte[] bytes=new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
        //使用DatagramSocket对象接收数据包中的数据
        datagramSocket.receive(datagramPacket);
        //获取包中的数据
        byte[] data = datagramPacket.getData();
        int length = datagramPacket.getLength();
        String str=new String(data,0,length);
        System.out.println(str);
        //释放资源
        datagramSocket.close();

UDP的三种发送方式:

单播:

组播:

组播地址范围:224.0.0.0~239.255.255.255,其中224.0.0.0~224.0.0.255为预留的组播地址
	发送端IP为组播地址
	接收端创建的是MulticastSocket对象,把当前的计算机绑定一个组播地址,表示添加到一组中,使用joinGroup(InetAddress.getByName(组播地址))方法实现

广播:

广播地址:255.255.255.255
InetAddress.getByName("255.255.255.255");

TCP通信

TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象

客户端:Socket
1.创建Socket对象,
2.使用对象获取输出流,写入数据到服务器
3.释放资源
例:
 		//创建socket对象
        Socket socket = new Socket("127.0.0.1",10086);
        //获取网络输出流
        BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(socket.getOutputStream());
        //使用IO流读取本地文件数据
        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("day15_inter\\a.jpg"));
        int len;
        while ((len=bufferedInputStream.read())!=-1){
            bufferedOutputStream.write(len);
        }
        bufferedInputStream.close();
        socket.shutdownOutput();
        //接收服务器发回的响应
        BufferedInputStream bufferedInputStream1 = new BufferedInputStream(socket.getInputStream());
        int len1;
        byte[] bytes=new byte[1024];
        while((len1=bufferedInputStream1.read(bytes))!=-1){
            System.out.print(new String(bytes,0,len1));
        }
        //释放资源
        socket.close();
服务器:ServerSocket
1.创建ServerSocket对象
2.监听客户端连接,返回一个Socket对象
3.使用Socket对象获取一个输入流,读取客户端发送过来的数据
4.释放资源
例:
		//创建服务端对象
        ServerSocket serverSocket=new ServerSocket(10086);
        //创建socket监听
        Socket accept = serverSocket.accept();
        //创建网络输入流
        BufferedInputStream bufferedInputStream=new BufferedInputStream(accept.getInputStream());
        //创建IO输出流,向文件中添加数据
        String fileName= UUID.randomUUID().toString()+".jpg";
        BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(new FileOutputStream(new File("C:\\Users\\soulboy\\Desktop\\aaa",fileName)));
        int len;
        while((len=bufferedInputStream.read())!=-1){
            bufferedOutputStream.write(len);
        }
        bufferedOutputStream.close();

        //向客户端发送响应
        BufferedOutputStream bufferedOutputStream1 = new BufferedOutputStream(accept.getOutputStream());
        bufferedOutputStream1.write("发送成功了".getBytes());
        bufferedOutputStream1.flush();
        accept.shutdownOutput();
        //释放资源
        accept.close();
        serverSocket.close();
TCP三次握手,四次挥手

三次握手:保证客户端与服务器之间的连接畅通

​ 第一次:客户端向服务器发出连接请求,等待服务器确认

​ 第二次:服务器向客户端返回一个响应,告诉客户端收到了请求

​ 第三次:客户端向服务器再次发出确认信息,连接建立

四次挥手:客户端与服务器取消两者之间连接时,用来保证成功终止这个连接

​ 第一次:客户端向服务器发出取消连接请求

​ 第二次:服务器向客户端返回一个响应,表示收到客户端取消请求

​ 第三次:服务器向客户端发出确认取消请求

​ 第四次:客户端再次发送确认消息,连接取消

服务端优化
循环
//创建服务端对象
        ServerSocket serverSocket=new ServerSocket(10086);

        while (true) {
            //创建socket监听
            Socket accept = serverSocket.accept();
            //创建网络输入流
            BufferedInputStream bufferedInputStream=new BufferedInputStream(accept.getInputStream());
            //创建IO输出流,向文件中添加数据
            String fileName= UUID.randomUUID().toString()+".jpg";
            BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(new FileOutputStream(new File("C:\\Users\\soulboy\\Desktop\\aaa",fileName)));
            int len;
            while((len=bufferedInputStream.read())!=-1){
                bufferedOutputStream.write(len);
            }
            bufferedOutputStream.close();

            //向客户端发送响应
            BufferedOutputStream bufferedOutputStream1 = new BufferedOutputStream(accept.getOutputStream());
            bufferedOutputStream1.write("发送成功了".getBytes());
            bufferedOutputStream1.flush();
            accept.shutdownOutput();
            //释放资源
            accept.close();
        }
UUID类
生成随机文件名
String fileName= UUID.randomUUID().toString()+".jpg";
线程优化与线程池优化
服务器:

//创建服务器对象
        ServerSocket serverSocket =null;
        try {
            serverSocket = new ServerSocket(10086);
            //创建线程池
            ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(3,5,60
                    , TimeUnit.SECONDS,new ArrayBlockingQueue<>(10)
                    , Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
            while (true) {
                Thread.sleep(1000);
                //创建客户端监听
                Socket accept = serverSocket.accept();
                UnloadRunnable unloadRunnable = new UnloadRunnable(accept);
                threadPoolExecutor.submit(unloadRunnable);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//        serverSocket.close();



线程类
public class UnloadRunnable implements Runnable {
    private Socket socket;

    public UnloadRunnable(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        FileOutputStream fileOutputStream = null;
        try {
            //获取网络输入流读取客户端发送过来的数据
            InputStream inputStream = socket.getInputStream();
            //创建IO字节输出流对象,向服务端文件夹写入数据
            String files = "D:\\aaa\\";
            String fileName = UUID.randomUUID().toString() + ".jpg";
            fileOutputStream = new FileOutputStream(new File(files, fileName));
            byte[] bytes = new byte[1024];
            int len;
            while ((len = inputStream.read(bytes)) != -1) {
                fileOutputStream.write(bytes, 0, len);
            }
            //获取网络输出流向客户端发送响应
            OutputStream outputStream = socket.getOutputStream();
            String str = "发送成功了";
            outputStream.write(str.getBytes());
            outputStream.flush();
            //输出结束标记
            socket.shutdownOutput();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

类加载器

类加载器负责将物理(.class)文件加载到内存当中

类加载时机:
1.创建类的实例
2.调用类的类方法
3.访问类或者接口的类变量,或者为该变量赋值
4.使用反射方式来强制创建某个类或者接口对应的java.lang.Class对象
5.初始化某个类的子类
6.直接使用java.exe命令来运行某个主类
总结:用到就加载,不用不加载
类加载的过程:

加载–>验证–>准备–>解析–>初始化

加载:

​ 通过一个类的全限定名来获取定义此类的二进制字节流

​ 将这个字节流所代表的静态存储结构转化为运行时数据结构

​ 在内存中生成一个代表这个类的java.lang.Class对象

​ 注意:任何类被使用时,系统都会为之建立一个java.lang.Class对象

链接(验证–>准备–>解析)

​ 验证:为了确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全

​ 准备:负责为类的类变量(被static修饰的变量)分配内存,并设置默认初始化值

​ 解析:将类的二进制数据流中的符号引用替换为直接应用,如果本类中用到了其他的类,此时就需要找到对应的类

初始化

通过程序制定的主观计划去初始化类变量和其他资源,静态变量赋值及初始化其他资源

类加载器的分类

​ 启动类加载器(Bootstrap ClassLoader):虚拟机内置的类加载器

​ 平台类加载器(Platform ClassLoader):负责加载JDK中一些特殊的模块

​ 系统类加载器(System ClassLoader):负责加载用户类路径上所指定的类库

​ 自定义类加载器(User ClassLoader)

类加载器的双亲委派模型

双亲委派模型工作过程:

​ 如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时,子加载器才会尝试自己去加载

为什么需要双亲委派模型

黑客自定义一个java.lang.String类,该String类具有系统的String类一样的功能,只是在某个函数稍作修改。比如equals函数,这个函数经常使用,如果在这这个函数中,黑客加入一些“病毒代码”。并且通过自定义类加载器加入到JVM中。此时,如果没有双亲委派模型,那么JVM就可能误以为黑客自定义的java.lang.String类是系统的String类,导致“病毒代码”被执行。

而有了双亲委派模型,黑客自定义的java.lang.String类永远都不会被加载进内存。因为首先是最顶端的类加载器加载系统的java.lang.String类,最终自定义的类加载器无法加载java.lang.String类。

或许你会想,我在自定义的类加载器里面强制加载自定义的java.lang.String类,不去通过调用父加载器不就好了吗?确实,这样是可行。但是,在JVM中,判断一个对象是否是某个类型时,如果该对象的实际类型与待比较的类型的类加载器不同,那么会返回false。

类加载器的方法

public static ClassLoader getSystemClassLoader()	获取系统类加载器
public InputStream getResourceAsStream(String name)	加载某一个资源文件

反射机制

概述:java反射机制是在运行状态中对于任意一个类,都能够知道这个类的所有属性和方法

​ 对于任意一个对象,都能够调用它的任意属性和方法

​ 这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制

反射获取class对象
1)Class clazz1 = Class.forName("类的全路径名");
2)Class clazz2 =类名.class;
3)Class clazz3 =对象名.getClass();
注意:不管使用哪一种方式,只要类是同一个,字节码也是同一个
反射获取构造方法并创建对象
public Constructor[] getConstructors()
			获取多个公共的Constructor对象
public Constructor getConstructor(Class... parameterTypes)
			获取公共的一个Constructor对象
public Constructor[] getDeclaredConstructors()
			获取多个包括私有在内的所有Constructor对象
public Constructor getDeclaredConstructor()
			获取私有的一个Constructor对象
		
获取到构造方法之后,如何创建对象?
			Object obj = con.newInstance();  //注意:构造方法有参数,就需要在()里面传递具体数值
反射获取成员方法,并执行成员方法
Method[] getMethods()
			获取多个公有的成员方法(父类方法也能获取到)
Method getMethod(String name, Class... parameterTypes)
			获取一个公有的成员方法, 第一个参数:方法名,第二个参数:参数列表
Method[] getDeclaredMethods()
			获取多个包括私有在内私有所有方法(不包括父类方法)
Method getDeclaredMethod(String name, Class... parameterTypes)
			获取一个私有的成员方法, 第一个参数:方法名,第二个参数:参数列表
	
获取到成员方法之后,如何让方法执行?
			m.invoke(对象,参数);  //注意:参数根据需要可以写一个,也可以写多个,也可以没有
反射获取成员属性
public Field[] getFields()
			获取多个成员变量(public修饰的)
public Field getField(String name)
			获取某一个成员变量(public修饰的)

public Field[] getDeclaredFields()
			获取所有的成员变量(包括私有的)
public Field getDeclaredField(String name)
			获取一个成员变量(可以获取私有的)
		
获取到成员变量之后,如何让给变量赋值和获取值?
			f.set(对象,值);  //注意:设置什么值,写什么值。
			Object value = f.get(对象);  //获取值

xml配置文件

xml概述:
XML的全称为(EXtensible Markup Language),是一种可扩展的标记语言

标记语言:通过标签来描述数据的一门语言(标签有时我们也将其称之为元素)
可扩展:标签的名字是可以自定义的

作用:用于存储数据和传输数据、作为软件的配置文件
xml标签规则
标签由一对尖括号和合法标识符组成
标签必须成对出现
特殊的标签可以不成对,但是必须有结束标记
标签中可以定义属性,属性和标签名空格隔开,属性值必须用引号引起来
标签要正确嵌套
xml语法规则
xml文件的后缀名为:xml
文档声明必须是第一行第一列

	version:该属性必须存在
	encoding:该属性不是必须的
			 打开当前xml文件的时候应该是使用什么字符编码表
	standalone:该属性不是必须的
			 描述xml文件是否依赖其他xml文件,取值yes/no
必须存在一个根标签,有且只能有一个
xml解析
开源组织提供了一套xml的解析的API-dom4j(Dom For Java)
Document对象:整个xml文档
Element对象:所有标签
Attribute对象:所有属性
Text对象:所有文本内容

解析代码例子:
	xml文件:
    
    
        
            张三
            23
        
        
            李四
            24
        
    
   解析简单代码实现:
   		//获取XML解析器
        SAXReader saxReader=new SAXReader();
        //解析XML文件
        Document read = saxReader.read(new File("day17\\XML\\student.xml"));
        //获取根标签
        Element rootElement = read.getRootElement();
        //获取子标签
        List list = rootElement.elements("student");

        ArrayList arrList=new ArrayList<>();
        for (Element element : list) {
            //获取子标签的属性
            String id = element.attribute("id").getValue();
            //获取子标签下的文本内容
            String name = element.element("name").getText();
            String age = element.element("age").getText();
            //将获取到的值设置到对象
            Student student = new Student(id, name, Integer.parseInt(age));
            //讲对象添加到集合
            arrList.add(student);
        }
约束

概述:用来限定xml文件中可使用的标签以及属性

DTD约束(已经极少使用,了解)
创建一个文件,这个文件的后缀名为dtd
看xml文件中使用了哪些元素
	可以定义元素
	可以定义元素的属性
判断元素是简单元素还是复杂元素
	简单元素:没有子元素
	复杂元素:有子元素的元素
约束引入xml文件:三种方式
引入本地dtd
	
在xml文件内部引入
	
引入网络dtd
	
schema约束
schema约束文件也是一个xml文件,符合xml的语法
文件的后缀名为xsd
一个xml可以引入多个xsd约束文件,多个schema使用名称空间区分
schema可以支持很多个数据类型
schema文件用来约束一个xml文件,同时也被别的文件约束着
schema文件的创建
1.创建一个文件,这个文件的后缀名为xsd
2.定义文档声明
3.schema文件的根标签为
4.在中定义属性:
xmlns=http://www.w3.org/2001/XMLSchema
5.在中定义属性
targetNamespace=唯一的url定制
指定当前这个shcema文件的名称空间
6.在中定义属性
elementFormDefault="qualified"
表示当前schema文件是一个质量良好的文件
7.通过element定义元素
8.判断当前元素是简单元素还是复杂元素

schema文件的编写,例子:




    
        
        
            
            
                
                    
                    
                        
                        
                            									
                            									
                        
                        
                        
                    
                
            
        
    

schema文件的引入
1.在根标签上定义属性
	xmlns="http://www.w3.org/2001/XMLSchema-instance"
2.通过xmlns引入约束文件的名称空间
3.给某一个xmlns属性添加一个标识,用于区分不同的名称空间
	格式为:	xmlns:标识="名称空间地址"
		标识可以是任意的,但一般取值都是xsi
4.通过xs:schemaLocation指定名称空间所对应的约束文件路径
格式:xs:schemaLocation="名称空间url schema文件路径"

枚举

概述:

​ 如果程序中需要表示几个固定的值,就可以使用枚举,是指将变量的值一一列举出来,变量的值只限于列举出来的范围内

使用枚举的好处:可以使代码更加简洁,不同类型的数据值直接见名知意,安全

枚举的特点及常用方法
格式:
	public enum 枚举名{
        枚举项1,枚举项2,枚举项3;
	}
枚举的特点:
	1.所有的枚举类都是Enum的子类
	2.可以通过“枚举类名.枚举项名”去访问指定的枚举项
	3.每一个枚举项就是该枚举的对象
	4.枚举也是一个类,也可以定义成员变量
	5.枚举类的第一行上必须是枚举项,最后一个枚举项后的分号可以省略
		但枚举类有其他东西,这个分号就不能省略
	6.枚举类可以有构造器,但必须是private的,它默认也是private
	7.枚举类也可以有抽象方法,但是枚举项必须重写该方法
枚举类的常用方法
	String name();			获取枚举项的名称
	int ordinal();			返回枚举项在枚举类中的索引
	int compareTo(Enum o)	比较两个枚举项,返回索引的差值
	String toString()		返回枚举常量的名称
	static  T valueOf(Class type,String name)	
	获取指定枚举类的指定名称的枚举值
	values()	获取所有的枚举项

注解

注解的主要作用:对我们的程序进行标注和解释

内置的注解

Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。

作用在代码的注解:
@Override
检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
@Deprecated
标记过时方法。如果使用该方法,会报编译警告。
@SuppressWarnings
指示编译器去忽略注解中声明的警告
作用在其他注解的注解(或者说 元注解)是:
@Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
@Documented - 标记这些注解是否包含在API文档中。
@Target - 标记这个注解应该是哪种 Java 成员。
@Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)
从 Java 7 开始,额外添加了 3 个注解:
@SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
@FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
@Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。
注解的定义格式
public @interface Anno{
    //定义一个基本类型的属性
    int a() default 23;
    //定义一个String类型的属性
    public String name();
    //定义一个Class类型的注解
    public Class clazz() default Object.class;
    //定义一个注解类型的注解
    public Anno1 anno() default @Anno1;
    //定义一个枚举类型的注解
    public EnumTest enumTest() default EnumTest.SPRING;
   //定义数组...
   
   //注意:
   //特殊值value,后期使用注解,如果只需要给value属性赋值
   //那么value就可以省略不写
}
在使用注解时如果注解里面的属性没有指定默认值
那么我们就需要手动给出注解属性的设置值
注解的解析
	1.注解可以使用在类上、注解在运行时期有效
	2.写一个测试类,在测试类上贴一个@Anno注解指定要运行的类和方法
	3.使用反射获取Anno注解上的类名和方法名,并执行该方法。
	@Retention(RetentionPolicy.RUNTIME) //注解在运行时期有效
	@Target(ElementType.TYPE) //注解可以使用在类上
	public @interface Anno {
		public String classname();
		public String methodname();
	}
	
//需求:修改注解的属性值,可以执行任意一个类的任意方法。
@Anno(classname = "配置你要执行的类全路径名",methodname = "配置你要执行的方法名")
public class Demo5 {
     public static void main(String[] args) throws Exception {
        //获取Demo5类上的注解Anno
        Anno anno = Demo5.class.getAnnotation(Anno.class);

        //获取Anno注解的属性值
        String classname = anno.classname(); //类名
        String methodname = anno.methodname(); //方法名

        //通过反射获取classname对应的字节码
        Class clazz = Class.forName(classname);
        //创建类的对象
        Object obj = clazz.getConstructor().newInstance();

        //通过反射获取methodname对应的方法
        Method method = clazz.getMethod(methodname);
        //执行方法
        method.invoke(obj);
    }
}

单元测试

junit测试工具使用流程
1.将junit的jar包导入到工程中
2.编写测试方法该测试方法必须是公共的无参数无返回值的非静态方法
3.在测试方法上使用@Test注解标注该方法是一个测试方法
4.选中测试方法右键通过junit运行该方法
@Before 在测试方法的方法前运行
@After	在测试方法的方法后运行

日志技术

输出语句与日志技术对比
输出语句:
	需要修改代码,灵活性比较差
	只能在控制台输出
	和业务代码处于一个线程中
日志技术:
	不需要修改代码,灵活性比较好
	可以将日志信息写入到文件或者数据库中
	多线程方式记录日志,不影响业务代码的性能

log4j

log4j是Apache的一个开源项目

通过log4j,可以控制日志信息输送的目的地(控制台,文件)等位置,控制每一条日志的输出格式

log4j使用步骤
1.导入jar包
2.编写配置文件
	先把配置文件复制到src目录下,配置文件的名称一定要叫`log4j.properties
	# debug: 日志级别
	# my:输出目的的名称1:ConsoleAppender控制台
	# fileAppender: 输出目的的名称2:FileAppender文件
	log4j.rootLogger=debug,my,fileAppender

	# 往控制台输出
    log4j.appender.my=org.apache.log4j.ConsoleAppender
    # 立即刷新
    log4j.appender.my.ImmediateFlush = true
    # 打印流
    log4j.appender.my.Target=System.out
    # 日志输出模式可以灵活指定
    log4j.appender.my.layout=org.apache.log4j.PatternLayout
    # 日志输出模式细节
    log4j.appender.my.layout.ConversionPattern=%d %t %5p %c{1}:%L - %m%n 


    # 往文件输出
    log4j.appender.fileAppender=org.apache.log4j.FileAppender
    # 立即刷新
    log4j.appender.fileAppender.ImmediateFlush = true
    # 消息追加到文件中,不被覆盖
    log4j.appender.fileAppender.Append=true
    # 消息输出到指定文件中
    log4j.appender.fileAppender.File=C:/itheima/log4j-log.log
    # 日志输出模式可以灵活指定
    log4j.appender.fileAppender.layout=org.apache.log4j.PatternLayout
    # 日志输出模式细节
    log4j.appender.fileAppender.layout.ConversionPattern=%d %5p %c{1}:%L - %m%n
    
3.在代码中获取日志的对象
	//使用log4j的api来获取日志的对象
	//弊端:如果以后我们更换日志的实现类,那么下面的代码就需要跟着改
	private static final Logger LOG=Logger.getLogger(本类的字节码对象);
	//使用slf4j里面的api来获取日志对象
	//推荐使用
	private static final Logger LOG=LoggerFactory.getLogger(本类的字节码对象);
	LOG.info(要输出的内容);
4.按照日志级别设置日志信息
log4j组成
  • Loggers(记录器) 日志的级别(DEBUG,INFO,WARN,ERROR,FATAL),只输出级别不低于设定级别的日志信息
  • Appenders(输出源) 日志要输出的地方
  • Layouts(布局) 日志要输出的方式

Logback日志

logback使用步骤
1.导入logback相关jar包
2.编写配置文件,在src目录下创建名为 `logback.xml` 的配置文件
	

    
    
    
    
        
            
            %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n
        
    
    
    
        
            
            ${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log
            
            30
        

        
        
            10MB
        

        
            %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{56}.%method:%L -%msg%n
        
    

    
    
        
        
    


3.编写代码
	 private static final Logger LOG = LoggerFactory.getLogger(LogbackDemo.class);

    public static void main(String[] args) {
        //System.out.println("程序开始运行");
        LOG.debug("程序开始运行");

        //求1-10之和
        int sum = 0;
        for (int i = 1; i <= 10; i++) {
            //System.out.println("第" + i + "次循环");
            LOG.debug("第" + i + "次循环");
            sum += i;
        }
        System.out.println("1-10之和为:" + sum);

        //System.out.println("程序结束运行");
        LOG.debug("程序结束运行");
    }
logback日志组件
Logger(记录器)
【TRACE】: 是追踪的意思,随着程序推进以下,你可以写个trace输出,所以trace应该会特别多。
        不过没关系,我们可以设置最低日志级别不让他输出.
【Debug】: 指出对调试应用程序是非常有帮助的信息(细粒度)
【INFO】:  消息在粗粒度级别上突出强调应用程序的运行过程.
【WARN】:  输出警告及WARN以下级别的日志.
【Error】: 输出错误信息日志.

此外【OFF】表示关闭全部日志,【ALL】表示开启全部日志。

等级从低到高分别是 TRACE < DEBUG < INFO < WARN < ERROR
注意:只输出不低于设定级别的日志信息

获取记录器的方式

private static final Logger logger = LoggerFactory.getLogger(Tester.class); 
Appenders(输出源)
  • 把日志输出到不同的地方
  • 常用类
ch.qos.logback.core.ConsoleAppender(控制台)
ch.qos.logback.core.FileAppender(文件)
ch.qos.logback.core.rolling.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
Layouts(布局)

logback可以在Appenders的后面附加Layouts来完成这个功能。Layouts提供好几种日志输出样式,如根据HTML样式、自由指定样式等信息的样式。

  • 常用类
ch.qos.logback.classic.html.HTMLLayout(以HTML表格形式布局)
ch.qos.logback.classic.PatternLayout(可以灵活地指定布局模式)
patternLayout布局模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TK3xqiYW-1643358422477)(D:\typorafile\images\1638420987447.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JGFsSGud-1643358422478)(D:\typorafile\images\1638422033573.png)]

动态代理实现步骤

代理分为动态代理和静态代理

1.定义目标接口和实现类

JDK动态代理要求目标类(被代理的类)必须实现一个接口。如下面的TestDaoImpl类是实现了TestDao接口,这样TestDaoImpl就可以成为动态代理的目标类。

public interface TestDao {
    public void save();
    public void delete();
    public void update();
}

public class TestDaoImpl implements TestDao {
    @Override
    public void save() {
        System.out.println("保存");
    }

    @Override
    public void delete() {
        System.out.println("删除");
    }

    @Override
    public void update() {
        System.out.println("修改");
    }
}

2.代理类的调用处理程序

JDK提供了一个InvocationHandler接口和一个Proxy类。Proxy类用于为TestDao的实现类生成代理对象,InvocationHandler用于处理代理对象调用方法时如何进行功能增强。

public class TestDaoHandler implements InvocationHandler {
    TestDao testDao;
    public TestDaoHandler(TestDao testDao) {
        this.testDao = testDao;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("执行"+method.getName()+"方法功能增强");
        //执行目标方法
        Object obj = method.invoke(testDao,args);
        System.out.println("执行"+method.getName()+"方法后功能增强\n");
        //返回方法的结果
        return obj;
    }
}

3.生成代理对象

JDK提供了一个InvocationHandler接口和一个Proxy类。Proxy类用于为TestDao的实现类生成代理对象,InvocationHandler用于处理代理对象调用方法时如何进行功能增强。

public class JDKDynamicProxyDemo {
    public static void main(String[] args) {
        //1.创建目标对象
        TestDao testDao=new TestDaoImpl();
        //2.创建TestDao的功能增强处理器
        TestDaoHandler handler=new TestDaoHandler(testDao);
        //3.为TestDao创建代理对象
        TestDao testDaoProxy = (TestDao) Proxy.newProxyInstance(
                ClassLoader.getSystemClassLoader(),
                testDao.getClass().getInterfaces(),
                handler);
        //4.使用代理对象调用save
        testDaoProxy.save();
        testDaoProxy.delete();
        testDaoProxy.update();
    }
}

4.动态代理代码示例

package com.itcast.proxy1;
/*
动态代理举例
 */

import com.itcast.proxy.StudentDao;
import com.itcast.proxy.StudentImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Human{
    String getBelief();
    void eat(String food);
}
//被代理类
class SuperMan implements Human{

    @Override
    public String getBelief() {
        return "i believe i can fly";
    }

    @Override
    public void eat(String food) {
        System.out.println("超人喜欢吃"+food);
    }
}
/*
    实现动态代理,需要解决的问题
    问题一:如何根据加载到内存中的被代理类,动态创建一个代理类及其对象
    问题二:当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法
 */
class ProxyFactory{
    //调用此方法,返回一个代理类的对象,解决问题一
    public static Object getProxyInstance(Object obj){//obj:被代理类对象
        MyInvocationHandler handler = new MyInvocationHandler(obj);
        //返回一个代理类对象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),handler);
    }
}
class MyInvocationHandler implements InvocationHandler{
    private Object obj;//赋值时,需要使用被代理类的对象进行赋值
    private Logger LOG= LoggerFactory.getLogger(MyInvocationHandler.class);
    public MyInvocationHandler(Object obj){
        this.obj=obj;
    }
    //当我们通过代理类的对象调用方法a时,就会动态的调用如下的方法:invoke()
    //将被代理类要执行的方法a的功能声明在invoke中
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //method即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
        //obj:被代理类的对象
        LOG.debug("代理类方法开始执行了");
        Object result = method.invoke(obj, args);
        LOG.debug("代理方法结束了");
        return result;
    }
}


public class ProxyTest {
    public static void main(String[] args) {
        //创建被代理类的对象
        SuperMan superMan = new SuperMan();
        //创建代理类的对象
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        //当通过代理类对象调用此方法时,会自动调用被代理类中同名的方法
        String belief = proxyInstance.getBelief();
        System.out.println(belief);
        proxyInstance.eat("蟹特");
        System.out.println("------------------------------");
        StudentImpl student = new StudentImpl();
        StudentDao proxyInstance1 = (StudentDao) ProxyFactory.getProxyInstance(student);
        proxyInstance1.save();
        proxyInstance1.select();
        proxyInstance1.delete();
        proxyInstance1.update();
    }
}

rintln(“执行”+method.getName()+“方法功能增强”);
//执行目标方法
Object obj = method.invoke(testDao,args);
System.out.println(“执行”+method.getName()+“方法后功能增强\n”);
//返回方法的结果
return obj;
}
}


#### 3.生成代理对象

JDK提供了一个InvocationHandler接口和一个Proxy类。Proxy类用于为TestDao的实现类生成代理对象,InvocationHandler用于处理代理对象调用方法时如何进行功能增强。

```java
public class JDKDynamicProxyDemo {
    public static void main(String[] args) {
        //1.创建目标对象
        TestDao testDao=new TestDaoImpl();
        //2.创建TestDao的功能增强处理器
        TestDaoHandler handler=new TestDaoHandler(testDao);
        //3.为TestDao创建代理对象
        TestDao testDaoProxy = (TestDao) Proxy.newProxyInstance(
                ClassLoader.getSystemClassLoader(),
                testDao.getClass().getInterfaces(),
                handler);
        //4.使用代理对象调用save
        testDaoProxy.save();
        testDaoProxy.delete();
        testDaoProxy.update();
    }
}

4.动态代理代码示例

package com.itcast.proxy1;
/*
动态代理举例
 */

import com.itcast.proxy.StudentDao;
import com.itcast.proxy.StudentImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Human{
    String getBelief();
    void eat(String food);
}
//被代理类
class SuperMan implements Human{

    @Override
    public String getBelief() {
        return "i believe i can fly";
    }

    @Override
    public void eat(String food) {
        System.out.println("超人喜欢吃"+food);
    }
}
/*
    实现动态代理,需要解决的问题
    问题一:如何根据加载到内存中的被代理类,动态创建一个代理类及其对象
    问题二:当通过代理类的对象调用方法时,如何动态的去调用被代理类中的同名方法
 */
class ProxyFactory{
    //调用此方法,返回一个代理类的对象,解决问题一
    public static Object getProxyInstance(Object obj){//obj:被代理类对象
        MyInvocationHandler handler = new MyInvocationHandler(obj);
        //返回一个代理类对象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),handler);
    }
}
class MyInvocationHandler implements InvocationHandler{
    private Object obj;//赋值时,需要使用被代理类的对象进行赋值
    private Logger LOG= LoggerFactory.getLogger(MyInvocationHandler.class);
    public MyInvocationHandler(Object obj){
        this.obj=obj;
    }
    //当我们通过代理类的对象调用方法a时,就会动态的调用如下的方法:invoke()
    //将被代理类要执行的方法a的功能声明在invoke中
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //method即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
        //obj:被代理类的对象
        LOG.debug("代理类方法开始执行了");
        Object result = method.invoke(obj, args);
        LOG.debug("代理方法结束了");
        return result;
    }
}


public class ProxyTest {
    public static void main(String[] args) {
        //创建被代理类的对象
        SuperMan superMan = new SuperMan();
        //创建代理类的对象
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        //当通过代理类对象调用此方法时,会自动调用被代理类中同名的方法
        String belief = proxyInstance.getBelief();
        System.out.println(belief);
        proxyInstance.eat("蟹特");
        System.out.println("------------------------------");
        StudentImpl student = new StudentImpl();
        StudentDao proxyInstance1 = (StudentDao) ProxyFactory.getProxyInstance(student);
        proxyInstance1.save();
        proxyInstance1.select();
        proxyInstance1.delete();
        proxyInstance1.update();
    }
}

你可能感兴趣的:(java)