JavaSE学习笔记

Java SE

文章目录

java基础语法

hello world

注释

多行注释 /* */

单行注释 //

关键字

即有特殊含义的单词

关键字的字母全部小写

常用的编辑器对关键字有颜色提示

常量

在程序运行过程中,数值不可发生变化。

字符串常量str 双引号 “A”

整数常量int

小数常量double

字符常量 单引号 ‘A’ 不可直接输出

布尔常量bool true and false

空常量 null 不可直接输出

数据类型

数据类型:

基本书籍类型:

​ 数值型:

​ 整数:byte,short,int,long

​ 浮点数:float,double

​ 字符:char

​ 非数值型:布尔型(boolean)

引用数据类型:

​ 类class

​ 接口interface

​ 数组[]

数据类型内存占用和取值范围

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L3alrnIg-1628504909896)(C:\Users\ccc\Desktop\Markdown学习笔记\img\QQ截图20201226144604.jpg)]

变量

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

注意事项:

  • 变量名称不能重复
  • 变量未赋值,不能直接使用
  • long类型的变量定义的时候,为防止整数过大,后面要加L
  • float类型的变量定义的时候,为了防止类型不兼容,后面要加F

标识符

定义规则:

  1. 由数字字母下划线和美元符号组成
  2. 不能以数字开头
  3. 不能是关键字
  4. 区分大小写

小驼峰命名法,针对方法变量命名

name 只有一个单词,首字母小写

fileName 多个单词,首字母小写其他单词首字母大写

大驼峰命名法:类的命名

Student 只有一个单词,首字母大写

GoodStudent 多个单词组成,每个单词首字母都大写

类型转换

自动类型转换

把一个表示数据范围小的数值或者变量赋值给另一个标识范围大的变量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bl9wDVgh-1628504909899)(C:\Users\ccc\Desktop\Markdown学习笔记\img\QQ截图20201226150738.jpg)]

强制类型转换

int i;

i = (int)99.99;

运算符&分支语句

运算符和表达式

算数运算符:+ - * / % 加减乘除取余

字符的加操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4RUG1caR-1628504909900)(C:\Users\ccc\Desktop\Markdown学习笔记\img\QQ截图20201226161000.jpg)]

算数表达式中包含多个基本数据类型的时候,整个算数表达式的类型会自动进行提升。

提升规则:byte,short,char=>int=>long=>float=>double

字符串的加操作

也就是字符串的拼接。

字符串+int类型 = 字符串

赋值运算符

= 赋值符号 给变量赋值

+= 隐含强制转换符号

i+=1 推荐用法

i=i+1

扩展的赋值运算符

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0XkucPMA-1628504909902)(C:\Users\ccc\Desktop\Markdown学习笔记\img\QQ截图20201226162430.jpg)]

自增自减运算符

++

单独使用 i++ 和 ++i 效果一样

在运算符中使用

int j = i++; 先把i赋值给j,然后i自身在进行++操作,j的结果是自加之前的值

int j = ++i; i先进行自加操作,再把结果赋值给j,j的结果是自加之后的值。

同样的道理

自加自减通常是单独使用。

关系运算符

关系成立返回为True 不成立返回值为False

等于 ==

不等于 !=

大于 >

大于等于 >=

小于 <

小于等于 <=

逻辑运算符

  1. & 逻辑与 一假则假
  2. | 逻辑或 一真则真
  3. ^ 逻辑异或 相同为假,不同为真
  4. ! 逻辑非 结果取反
//	逻辑与&:一假则假
System.out.println((i>j) & (i>k));	//false & false = false
System.out.println((i<j) & (i>k));	//true & false = false
System.out.println((i<j) & (i<k));	//true & true = true

//	逻辑或|:一真则真
System.out.println((i>j) | (i>k));	//false & false = false
System.out.println((i<j) | (i>k));	//true & false = true
System.out.println((i<j) | (i<k));	//true & true = true

//	逻辑异或^:相同为假,不同为真
System.out.println((i>j) ^ (i>k));	//false & false = false
System.out.println((i<j) ^ (i>k));	//true & false = true
System.out.println((i<j) ^ (i<k));	//true & true = false
		
//	逻辑非!:结果与过程相反
System.out.println(!(i>j));	//!false = true
System.out.println(!(i<j));	//!true = false

短路逻辑运算符

短路与 &&

与逻辑与作用相同,但是增加了短路效果,条件1 && 条件2 只要左边的条件1为false,条件2不进行判定,直接跳过条件2输出结果false。

短路或 ||

与逻辑或作用相同,但是增加了短路效果,条件1 || 条件2 只要左边的条件1位true,则条件2不进行判定,直接跳过条件2输出结果true。

最常用的逻辑运算符:&& || ! (短路与,短路或,非)

三元运算符△

格式:关系表达式?表达式1:表达式2;

范例:a>b?a:b;

计算规则:

首先计算关系表达式的值

如果值为true,则表达式1的值就是运算结果

如果值为false,则表达式2的值为运算结果。

数据输入

Scanner函数使用步骤

导包 import java.util.Scanner;

创建对象 Scanner scanner = new Scanner(System.in)

接收数据 int i = scanner.nextInt();

分支语句

流程控制

  • 顺序结构 由上到下
  • 分支结构 if,switch
  • 循环结构 for,while,do…while

分支结构:

格式1 if…

if(表达式){

	语句体;

}

如果表达式为真,执行语句

格式2 if…else…

if(表达式){

	语句体1;

}else{

	语句体2;

}

如果表达式为真,执行语句体1,否则执行语句体2.

格式3 if…else if…else

if(表达式1){

	语句体1;

}else if(表达式2){

	语句体2;

}else{

	语句体3;

}

switch&循环语句

switch语句

switch(表达式){
    case 值1:
        语句体1;
        break;
    case 值2:
        语句体2;
        break;
    ...
    default:	//表示所有情况都不匹配的情况下执行该处内容。
        语句体n+1;
        break;
}

case穿透现象

switch (i) {
			case 1:
			case 2:
			case 12:
				System.out.print("冬天啊");
				break;
			case 3:
			case 4:
			case 5:
				System.out.print("春天啊");
				break;
			case 6:
			case 7:
			case 8:
				System.out.print("夏天啊");
				break;
			case 9:
			case 10:
			case 11:
				System.out.print("秋天啊");
				break;
			default:
				System.out.print("输入数字有误");
		}

for循环语句

for(初始化语句;条件判断语句;条件控制语句){

​ 循环体语句;

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9qYsQ8eS-1628504909904)(C:\Users\ccc\Desktop\Markdown学习笔记\img\微信截图_20201226214136.png)]

while循环语句

while(条件判断语句){

​ 循环体语句;

}

do…while循环语句

do{

​ 循环体语句;

}while(条件判断语句);

int j = 1;
do {
    System.out.println("Hello World!");
    j++;
}while(j<=5);

跳转控制语句

continue 跳过此次循环,循环仍会继续

break 直接结束该循环

循环嵌套

循环套循环

for(条件语句){

​ for(条件语句){

​ 循环体;

​ }

}

死循环

for(;😉{

​ 循环体;

}

while(true){

​ 循环体;

}

Random

一个随机数函数

  1. 导包 import java.util.Random;
  2. 创建对象 Random r = new Random();
  3. 获取随机量 int i = r.nextInt(数值); 产生的随机数不包含该数值;

数组

数组定义格式

  1. int[] arr
  2. int arr[]

数组初始化

动态初始化

int[] arr = new int[3];

静态初始化

一般格式 int[] arr = new int[]{1,2,3};

简化格式 int[] arr = {1,2,3};

数组操作中两个常见小问题

数组索引越界。

空指针异常。

数组常见操作

遍历

获取数组长度 数组名.length 返回数组长度

获取最值

for(int i=0;i< arr3.length;i++){
    System.out.println(arr3[i]);
    if(arr3[i]>max){
        max = arr3[i];
    }
    if(arr3[i]<min){
        min = arr3[i];
    }
}

方法

方法概述

方法是将具有独立功能的代码块组织成为一个整体,使其成为具有特殊功能的代码集。

方法必须县创建才可以使用,该过程成为方法定义。

方法创建后并不是直接运行的,需要手动使用后才执行,该过程称为方法调用。

方法定义

格式

public static void function(){

​ // 方法体

}

调用

function();

带参数的方法定义

格式

public static void function(参数){

​ 方法体;

}

形参和实参

isMax(22,35);	//实参

// 需求:定义一个方法,用于打印两个数字中的较大数
public static void isMax(int a,int b){	//	a b 是形参
    if(a>b){
        System.out.println(a);
    }else if(a<b){
        System.out.println(b);
    }else{
        System.out.println("相等");
    }
}

带返回值方法调用

boolean s = Function();

public static boolean Function(){

​ return False;

}

方法注意事项

方法不能嵌套定义

void可以无返回值

如果写return 后面不能跟值

方法的通用格式

public static 返回值类型 方法名(参数){

​ 方法体;

​ return 数据;

}

public static 修饰符

定义方法时要做到两个明确:

  • 明确返回值类型:主要明确方法操作完毕后是否有返回值数据返回,如果没有,写void;如果有,就写对应的数据类型。
  • 明确参数:主要是明确参数的类型和数量。

调用方法时:

  • void类型的方法,直接调用就行
  • 非void类型的方法,推荐用变量接受调用。

方法重载

方法重载指同一个类中定义的多个方法之间的关系,满足下列条件的多个方法相互构成重载。

  • 多个方法在同一个类中
  • 多个方法具有相同的方法名。
  • 多个方法的参数不相同,类型不同或者数量不同。

方法重载特点:

重载仅对应方法的定义,与方法的调用无关,调用方式参照标准格式。

重载仅针对同一个类中方法的名称与参数进行识别,与返回值无关,换句话说不能通过返回值来判定两个方法是否相互构成重载。

方法的参数传递

方法传递基本类型:

Debug操作流程

如何加断点:在代码前面点击加断点

如何debug:鼠标右键debug运行

看哪里:看debug窗口,看console窗口

怎样让程序继续运行:按f7或者点f7,点stop结束。

个位不等于c: x%10!=c

十位不等于c: x/10%10!=c

面向对象

面向对象学习-理论

1.1 什么是对象?

万物皆对象,客观存在的事物皆为对象。

1.2 什么是面向对象?

1.3 什么是类?

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

类的特点:类是对象的数据类型。类是具有相同属性和行为的一组对象的集合。

1.4 什么是对象的属性?

对象具有的各个特征,每个对象的每个属性都拥有特定的值。

1.5 什么是对象的行为?

行为:对象能够执行的操作。

1.6 类和对象的关系

  • 类:类是对现实生活中一类具有共同属性和行为的事物的抽象。
  • 对象:是能够看得到摸得着的真实存在的实体。

类的定义

类的重要性:类是Java程序的基本组成单位。

类的组成:属性和行为

属性:在类中通过成员变量来体现(类中方法外的变量)

行为:在类中通过成员方法来体现(和前面的方法相比去掉static关键字即可)

定义类

public class 类名{
//成员变量
变量1的数据类型 变量1;
变量2的数据类型 变量2;

//成员方法
方法1;
方法2;

}

对象的使用

创建对象

格式 类名 对象名 = new 类名();

范例 Phone p = new Phone();

使用对象

<1>、使用成员变量

格式 对象名.变量名;

范例 p.brand;

<2>、使用成员方法

格式 对象名.方法名();

范例 p.call();

new出来的物体,是存储在堆中,是有默认值的。

案例:学生

需求:首先定义一个学生类,然后定义一个学生测试类,在学生测试类中通过对象完成成员变量和成员方法的使用。

(1)定义一个学生类

类名 Student

成员变量 name age

成员方法 study() doHomework()

(2)定义学生测试类

类名 StudentDemo

因为要做测试,所以有一个主方法:main()

(3)在学生测试类中通过对象完成成员变量和成员方法的使用给成员变量赋值,输出成员变量的值

调用成员方法

主函数main、对象、方法存放在栈中

对象的属性值存放在堆中

new出来的对象是一个指向的地址值。

成员变量和局部变量

成员变量:在类中,方法外的变量叫成员变量。

局部变量:方法中的变量。

成员变量和局部变量的区别
区别 成员变量 局部变量
类中位置不同 类中方法外 方法内或者方法声明上
内存中位置不同 堆内存 栈内存
生命周期不同 随着对象的存在而存在, 随着方法的调用而存在,
随着对象的消失而消失 随着方法的调用完毕而消失
初始值不同 有默认的初始化值 没有默认的初始化值,必须先定义 赋值,才能使用。

封装

private关键字

是一个权限修饰符
可以修饰成员(成员变量和成员方法)
作用是保护成员不被别的类使用,被private修饰的成员只在本类中才能访问。

针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作
提供"get变量名()"方法,用于获取成员变量的值,方法用public修饰
提供"set变量名(参数)"方法,用于设置成员变量的值,方法用public修饰。

private关键字的使用

一个标准类的编写:

把成员变量用private修饰

提供对应的getXxx()/setXxx()方法

this关键字

this修饰的变量用于指代成员变量

(1)方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量。

(2)方法的形参如果没有与成员变量同名,不带this修饰的变量指的是成员变量。

什么时候使用this?

解决局部变量隐藏成员变量。

this:代表所在类的对象引用

记住:方法被那个对象调用,this就代表那个对象。

封装

<1>.封装概述
封装是面向对象的三大特征之一(封装、继承、多态),是面向对象编程语言对客观世界的模拟。客观世界里成员变量都是隐藏在对象内部的,外界是无法直接操作的。

<2>.封装原则

将类的某些信息隐藏在类内部,不允许外部程序员直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问成员变量private,提供对应的get/set方法。

<3>.封装好处

通过方法来控制成员变量的操作。提高了代码的安全性。

把代码用方法进行封装,提高了代码的复用性。

构造方法

构造方法概述

构造方法是一种特殊的方法

作用:创建对象

格式:

public class 类名{

​ 修饰符 类名(参数){

​ }

}

功能:主要是完成对象数据的初始化。

构造方法的注意事项

(1)构造方法的创建

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

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

(2)构造方法的重载

如果自定义了带参构造方法,还要使用无参构造方法,就必须再写一个无参构造方法。也就是方法的重载。

(3)推荐的使用方式

无论是否使用,都手动给出无参构造方法。

标准类制作

(1)成员变量

使用private修饰

(2)构造方法

提供一个无参构造方法

提供一个带多个参数的构造方法

(3)成员方法

提供一个成员变量对应的set/get方法

提供一个显示对象信息的show()方法

(4)创建对象并为其成员变量赋值的两种方式

无参构造方法创建对象后使用set方法赋值

使用带参构造方法直接创建带有属性值的对象。

day08 字符串

字符串

API概述
应用程序编程接口。
Java API:指的就是JDK中提供的各种功能的Java类
这些类将底层的实现封装了起来,我们不需要关心这些类是如何实现的。
只需要学习这些类如何使用即可,我们可以通过帮助文档来学习这些API如何使用。

API对象使用注意事项
调用方法的时候,如果方法有明确的返回值,我们用变量接收
可以手动完成,也可以使用快捷键完成(Ctrl+Alt+V)

StringBuilder构造方法

public StringBuilder(),创建一个空白可变字符串对象,不含有任何内容
public StringBuilder(String str),根据字符串的内容,来创建可变字符串对象

StringBuilder 的添加和反转方法

public StringBuilder append(任意类型) 添加数据,并返回对象本身
public StringBuilder reverse() 返回相反的字符序列

StringBuilder 和 String 相互转化

StringBuilder 转化为String
public String toString():通过头String()就可以实现把StringBuilder转化为String
String转换为StringBuilder
public StringBuilder(String s):通过构造方法就可以实现把String转换为StringBuilder

案例:拼接字符串
需求:定义一个方法,把int数组中的数据按照指定的格式拼接成一个字符串返回,调用该方法,并在控制台
输出结果。例如,数组为int[] arr={1,2,3};执行方法后的输出结果为:{1,2,3}。

案例:字符串反转
需求:定义一个方法,实现字符串反转,键盘录入一个字符串,调用该方法后,在控制台输出结果。
例如,键盘录入abc,输出结果cba

集合

1.1 集合概述

编程的时候如果要存储多个数据,使用长度固定的的数组存储格式,不一定满足我们
的需求,更适应不了变化的需求,那么,此时该如何选择呢?
集合类的特点:提供一种存储空间可变的存储模型,存储的数据容量可以发生改变
集合类有很多,目前我们先学习一个:ArrayList
ArrayList<E>:
    可调整大小的数组实现
    <E>:是一种特殊的数据类型,泛型
在出现E的地方我们使用引用数据类型代替即可
ArrayList<String>,ArrayList<Student>

1.2 ArrayList构造方法和添加方法

创建一个空的集合对象
public ArrayList()
将指定的元素追加到此集合的末尾
public boolean add(E e)
在集合中的指定位置插入指定的元素
public void add(int index,E element)

1.3 ArrayList 集合常用方法

删除指定的元素,返回删除是否成功  public boolean remove(Object o)
删除指定索引的元素,返回被删除的元素  public E remove(int index)
修改指定索引的元素,返回被修改的元素  public E set(int index,E element)
返回指定索引的元素    public E get(int index)
返回集合中元素的个数  public int size()

案例:存储学生对象并遍历

需求:创建一个存储学生对象的集合,存储三个学生对象,实用程序实现在控制台遍历该集合

----学生管理系统
-项目演示
------欢迎来到学生管理系统------
1 添加学生  add
2 删除学生  remove
3 修改学生  set
4 查看所有学生    travel
5 退出    break
请输入你的选择:

继承

day10 继承
面向对象的三大特征:封装性、继承性、多态性。
继承是多态的前提,如果没有继承,就没有多态。
继承主要解决的问题是:共性抽取。
被继承类:父类
继承类:子类
继承关系当中的特点:
1、子类可以拥有父类的"内容"
2、子类还可以拥有自己专有的内容。
定义父类的格式:一个普通的类定义
public class 父类名{}
定义子类的格式:
public class 子类名称 extends 父类名称{}
在父子类的继承关系中,如果成员变量重名,则创建子类对象时,访问有两种方式;
直接通过子类对象访问成员变量
    等号左边是谁,就优先用谁,没有则向上找
间接通过成员方法访问成员变量
    该方法属于谁,就优先用谁的变量,没有则向父类找。
三种变量重名的情况下如何区分:
    局部变量:直接写成员变量名
    本类的成员变量:this.成员变量名
    父类的成员变量:super.成员变量名
在父子类的继承关系当中,创建子类对象,访问成员方法的规则
    创建的对象是谁,就优先用谁,如果没有则向上找。
注意事项:
    无论成员方法还是成员变量,如果没有,都是向上找父类,绝不会向下找子类。
方法的重写Override   覆盖重写
    在继承关系中,方法的名称一样,参数列表也一样
重写(Override):方法的名称一样,参数列表也一样。
重载(Overload):方法的名称一样,参数列表不一样。
方法的覆盖重写特点:
    创建的是子类对象,则优先使用子类方法。
方法覆盖重写的注意事项:
    1、必须保证父子类之间方法的名称相同,参数列表也相同。
    @Override:写在方法前面,用来检测是不是有效的正确覆盖重写。
    建议写上Override
    2、子类方法的返回值必须小于父类方法的返回值类型。
    Object类是所有类的公共最高父类(祖宗),java.lang.String 就是Object的子类。
    3、子类方法的权限必须大于等于父类方法的权限修饰符
    权限排行:public >  protected  >  (default)  >private
    (default)不是关键字default,而是什么都不写,留空。
类的设计原则:
    对于已经投入使用的类,尽量不要进行修改,推荐定义一个新的类,来重复利用其中共性内容,
    并且添加改动新内容。
继承关系中,父子类构造方法的访问特点
    1、子类构造方法中当中有一个默认隐含的super()调用
    使用子类的构造方法就必须调用父类构造方法
    2、子类构造可以通过super()关键字来调用父类重载构造。
    只有子类构造方法,才能调用父类构造方法。
    3、super的父类构造调用,必须是子类构造方法的第一个语句,不能一个子类构
    造调用多次super构造。
    总结:
    子类必须调用父类构造方法,不写则默认有一个super()。写了则用写的指定的super调用。
    而且super只能有一个,还必须是第一个。
super关键字的三种用法
   1 在子类的成员方法中,访问父类的成员方法
   2 在子类的成员方法中,访问父类的成员方法
   3 在子类的构造方法中,访问父类的构造方法
super关键字用来访问父类内容,而this关键字用来访问本类内容。
this关键字的用法也有三种:
    1、在本类的成员方法中,访问本类的成员变量
    2、在本类的成员方法中,访问本类的另一个成员方法
    3、在本类的构造方法中,访问本类的另一个构造方法
    在第三种用法当中要注意
    A、this()调用也必须是构造方法的第一个语句,唯一一个。
    B、super和this两种构造调用,不能同时使用。
Java语言是 单继承的。
    一个类的直接父类只能有唯一一个。
Java语言可以 多级继承。
    C继承B,B继承A。
抽象的概念
    如果父类当中的方法不确定如何进行{}方法体实现,那么这个方法就是抽象方法。
抽象方法和抽象类的定义格式
    抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束
    例如: public abstract void eat();
    抽象类:抽象方法所在的类,必须是抽象类才行,在class之前写上abstract即可。
    例如: public abstract class Animal{}
如何使用抽象类和抽象方法步骤:
1、不能直接创建new抽象类对象
2、必须用一个子类类继承抽象父类
3、子类必须覆盖重写抽象父类当中所有的抽象方法
覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后不上方法体大括号。
4、创建子类对象进行使用
注意事项
1、抽象类不能创建对象,如果创建,编译无法通过而报错,只能创建其非抽象子类的对象。
2、抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
3、抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
4、抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错,除非该子类也是抽象类。
发红包案例
群主发普通红包,某群有多名成员,群主给成员发普通红包,普通红包规则:
1、群主的一笔金额,从群主的余额中扣除,平分成n等分,让成员领取
2、成员领取红包后,保存到成员的余额中。
请根据描述,完成案例中所有类的定义以及指定类之间的继承关系,并完成发红包的操作。

多态 抽象类 接口

1 多态
多态概述
    同一个对象,在不同时刻表现出来的不同形态
    举例:猫
    我们可以说猫是猫:猫 cat = new 猫();
    我们也可以说猫是动物:动物 animal = new 猫():
    这里猫在不同时刻表现出来了不同的形态,这就是多态。
多态的前提和体现
    有继承/实现关系
    有方法重写
    有父类引用指向子类对象
多态中成员的访问特点
    成员变量:编译看左边,执行看左边
    成员方法:编译看左边,执行看右边
    为什么成员变量和成员方法的访问不一样?
        因为成员方法有重写,而成员变量没有
多态的优点和缺点
    多态的好处:提高了程序的扩展性
    具体体现:定义方法的时候,使用父类型作为参数,将来再使用的时候,使用具体的子类参与操作
    多态的弊端:不能使用子类的特有功能
多态中的转型
    向上转型
        从子到父
        父类引用指向子类对象
    向下转型
        从父到子
        父类引用转为子类对象
1 抽象类
    1.1 抽象类概述
    在Java中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须
    定义为抽象类
        抽象方法必须加abstract关键字
        抽象方法只能存在于抽象类中
        抽象类必须用abstract关键字修饰
        抽象类不能创建对象!
    抽象类注意事项:
        子类继承抽象类两种方式:
            1 重写抽象类的所有抽象方法
            2 子类也是抽象类
    1.3 抽象类的成员特点
    成员变量
        可以是变量
        也可以是常量
    构造方法
        有构造方法,但是不能实例化
        构造方法的作用是用于子类访问父类数据的初始化
    成员方法
        可以有抽象方法:限定子类必须完成某些动作
        也可以有非抽象方法:提高代码复用性
1 demo9
1.1 接口概述
1.2 接口的特点
    接口用关键字interface修饰
        public interface 接口名{}
    类实现接口用关键字implements表示
        public class 类名 implements 接口名{}
    接口不能实例化
        接口如何实例化呢?参照多态的方式,通过实现类对象实例化,这叫接口多态
        多态的形式:具体类多态,抽象类多态,接口多态。
    接口的实现类
        要么重写接口中的所有抽象方法
        要么是抽象类
1.3 接口的成员特点
    成员变量
        只能是常量   public static final
        默认修饰符 public static final
    构造方法
        接口没有构造方法,因为接口主要是对行为进行抽象的,是没有具体存在
        一个类如果没有父类,会默认继承Object 类 祖宗类
    成员方法
        接口的成员方法只能是抽象方法
        自带默认修饰符 public abstract
1.4 类和接口的关系
    类和类的关系
        继承关系    只能单继承   但是可以多层继承
    类和接口的关系
        实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口。
    接口和接口的关系
        继承关系,可以单继承,也可以多继承
1.5 抽象类和接口的区别
    成员区别
        抽象类 变量,常量;有抽象方法,也有非抽象方法
        接口  常量,抽象方法
    关系区别
        类与类 继承,单继承
        类与接口    实现,可以单实现也可以多实现
        接口和接口   继承,单继承,多继承
    设计理念区别
        抽象类 对类抽象,包括属性,行为
        接口  对行为抽象,主要是行为

内部类&API

day12 内部类&API
1 形参和返回值
    1.1 类名作为形参和返回值
        - 方法的形参是类名,其实需要的是该类的对象
        - 方法的返回值是类名,其实返回的是该类的对象
    1.2 抽象类名作为形参和返回值
        - 方法的形参是抽象类名,其实需要的是该抽象类的子类对象
        - 方法的返回值是抽象类名,其实返回的是该抽象类的子类对象。
    1.3 接口名作为形参和返回值
        - 方法的形参是接口名,其实需要的是该接口的实现类对象
        - 方法的返回值是接口名,其实返回的是该接口的实现类对象
1 内部类
    1.1 内部类概述
    内部类:就是在一个类中定义一个类,例如在一个类A的内部定义一个类B,类B就被称为内部类。
    内部类的定义格式:
        - 格式
            public class 类名{
                修饰符 class 类名{
                }
            }
        - 范例
            public class Outer{
                public class Inner{
                }
            }
        内部类的访问特点
        - 内部类可以直接访问外部类的成员,包括私有
        - 外部类要访问内部类的成员,必须创建对象。
    1.2 成员内部类
    按照内部类在类中定义的位置不同,可以分为以下两种形式
        - 在类的成员位置:成员内部类
        - 在类的局部位置:局部内部类
    成员内部类,外界如何创建对象使用呢?
        - 格式:外部类名.内部类名 对象名 = 外部类名.内部类对象;
        - 范例:Outer.Inner oi = new Outer().new Inner();
    1.3 局部内部类
    局部内部类是在方法中定义的类,所以外界是无法直接使用的,需要在方法内部创建对象并使用
    该类可以直接访问外部类的成员(变量和方法都可以访问),也可以访问方法内的局部变量。
    1.4 匿名内部类
    前提:存在一个类或者接口,这里的类可以是具体类也可以是抽象类。
        - 格式
        new 类名或者接口名{
            重写方法;
        }
        - 范例
        new Inter{
            public void show(){
            }
        }
        本质:是一个继承了该类或者实现了该接口的子类匿名对象
    1.5 匿名内部类在开发中的使用
1 Math
    Math包含执行基本基本数字运算的方法
    没有构造方法,如何使用类中的成员?
    看成员是否都是静态的,如果是 ,通过类名就可以直接调用。
    1.2 Math类的常用方法
        public static int abs(int a)    返回参数的绝对值
        public static double ceil(double a)     返回大于或等于参数的最小double值,等于一个整数
        public static double floor(double a)    返回小于或等于参数的最大double值,等于一个整数
        public static int round(float a)    按照四舍五入返回最接近参数的int
        public static int min(int a,int b)  返回两个int值中的较大值
        public static int man(int a,int b)  返回两个int值中的较小值
        public static double pow(double a,double b) 返回a的b次幂的值
        public static double random()   返回值为double的正值,取值范围为[0.0,1.0)
2 System
    2.1 System 类的概述
    System.exit(int a)  改参数作为状态代码,按照惯例,非零状态码表示异常终止
    System.currentTimeMillis()  返回当前时间和1970年之间的差值,用毫秒表示
3 Object
    3.1 Object类的概述
    Object是类层次结构的根,每个类都可以将Object作为超类,所有类都直接或间接的继承自该类
    构造方法:public Object()
    回想面向对象中,为什么说子类的构造方法默认访问的是父类的无参构造方发?
    因为他们的顶级父类只有无参构造方发
    - 查看方法的源码   选中方法 Ctrl + B
    toString()  返回对象的字符串表示形式,一般来说,toString方法返回一个"toString代表"这个对象的字符串。
    结果应该是一个简明扼要的表达,容易让人阅读,这个方法返回一个等于下列值的字符串。
    建议所有子类,重写toString()方法
    3.2 Object 类的常用方法
    public String toString()    返回对象的字符串表示形式,建议所有子类重写该方法,自动生成。
    public boolean equals(Object obj)   比较对象是否相等,默认比较地址,重写可以比较内容,自动生成。
4 Arrays
    4.1 冒泡排序
    排序:将一组数据按照固定的规则进行排列
    冒泡排序:一种排序的方式,对要进行排序的数据中相邻的数据进行两两比较,将较大的数据放在后面,一次对所有的数据进行操作,
    直至所有数据按要求完成排序。
    - 如果有n个数据进行排序,总共需要比较n-1次
    - 每一次比较完毕,下一次的比较就会少一个数据参与。
    4.2 Arrays类的概述和常用方法
    Arrays类包含用于操作数组的各种方法
    public static String toString(int[] a)  返回指定数组的内容的字符串表示形式
    public static void sort(int[] a)    按照数字顺序排列指定的数组

    工具类的设计思想:
    - 构造方法用private修饰    是为了防止外界创建对象
    - 成员用public static修饰    是为了让使用类名来访问该成员方法。

常用API异常

2021/7/17 PM

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EbRYPPF2-1628504909905)(C:\Users\ccc\AppData\Roaming\Typora\typora-user-images\image-20210717153949075.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PV3GPoEb-1628504909907)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717154641427.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JdFbXuaK-1628504909908)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717154750416.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eP7saCMV-1628504909910)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717154900713.png)]

通过try{}catch(){}处理异常

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B5pO9VIB-1628504909911)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717155228791.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TmCrSSp0-1628504909912)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717155643648.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aCURR8ns-1628504909913)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717161142220.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tgHh7goI-1628504909914)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717161222584.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-thpFXgC7-1628504909915)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717161952765.png)]

总结:

运行时异常:程序可以运行,但是会报错,运行到报错位置后后序代码无法继续运行。

编译时异常:程序无法运行,需要处理才可以运行。

任何异常都可以使用throws操作将异常抛出,但是最终如果想要程序可以正常执行,依然需要try{}catch(){}操作,将异常捕获。

编译时异常必须try{}catch(){}后才可以正常运行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-79jokTEV-1628504909916)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717162110696.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qhDnA1bM-1628504909916)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717170510392.png)]

例子:

首先自定义一个异常类 ScoreException继承Exception异常父类,之后定义两个异常方法,有参和无参。

package demo2;

public class ScoreException extends Exception {
    public ScoreException(){

    }

    public ScoreException(String message){
        super(message);
    }
}

然后定义一个老师类,并在老师类中定义checkScore方法检查分数是否正常,如果分数有误则使用throw抛出异常。

package demo2;

import java.util.Scanner;

public class Teacher {
    public void checkScore(int score) throws ScoreException {
        if(score < 0 || score > 100){
            throw new ScoreException("分数有误,请输入0——100之间的值");
        }else{
            System.out.println("分数正常");
        }
    }
}

最后定义一个测试类TeacherTest,在该类中测试分数,但是Teacher对象调用checkScore方法时,要trycatch。

package demo2;

import java.util.Scanner;

public class TeacherTest {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入分数:");
        int score = sc.nextInt();

        Teacher t = new Teacher();
        try {
            t.checkScore(score);
        } catch (ScoreException e) {
            e.printStackTrace();
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LczJJlWI-1628504909917)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717170953294.png)]

集合

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oFgQ1Jtz-1628504909919)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717171403789.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cmTtStrI-1628504909920)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717171507765.png)]

单列集合Collectoin和双列集合Map

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wvx3vfgJ-1628504909920)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717171639976.png)]

Collection集合中的List集合(元素可重复)和Set集合(元素不可重复)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bc393rU0-1628504909921)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717171906575.png)]

接口和实现类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NTt03REr-1628504909922)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717172007022.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7BCnQGbj-1628504909923)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717172719848.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fpZULQML-1628504909923)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717173041373.png)]

查看类中方法:View > Tool Windows > Structure

上述操作快捷键:alt+7

集合的泛型 ArrayList 指定集合内的元素类型为E

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tQYwdL9A-1628504909924)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717174828511.png)]

使用迭代器Iterator遍历一个集合的步骤:

首先创建一个集合对象

Collection<String> c = new ArrayList<String>();

下一步在集合内添加元素

//添加元素
c.add("hello");
c.add("world");
c.add("java");

下一步创建迭代器对象

Iterator<String> it = c.iterator();

下一步使用迭代器的next()方法输出迭代器中的元素,但前提是使用hasNext()方法判断是否有下一个元素。

while(it.hasNext()){
    String s = it.next();
    System.out.println(s);
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Zsg6dmv-1628504909925)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717195401933.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V5XwbWyn-1628504909926)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717195456591.png)]

①定义学生类

public class Student {
    private int id;
    private String name;

    public Student(){
        this.id = id;
        this.name = name;
    }
    public Student(int id, String name){
        this.id = id;
        this.name = name;
    }
    
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class CollectionStu {
    public static void main(String[] args) {
    	//②创建集合对象
        Collection<Student> stu = new ArrayList<Student>();

        //③创建学生对象
        Student s1 = new Student(1,"刘德华");
        Student s2 = new Student(2,"周星驰");
        Student s3 = new Student(3,"林青霞");
        Student s4 = new Student(4,"成龙");

        //④将学生添加到ArrayList
        stu.add(s1);
        stu.add(s2);
        stu.add(s3);
        stu.add(s4);

        //⑤创建迭代器Iterator
        Iterator<Student> it = stu.iterator();

        //⑥遍历集合(迭代器方式)
        while (it.hasNext()){
            Student s = it.next();
            System.out.println(s.getId()+" "+s.getName());
        }

    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5SVSnEDA-1628504909926)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717201013155.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0j520cIs-1628504909927)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717201459945.png)]

注意事项:切勿索引越界

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nAfnZccV-1628504909928)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717203257924.png)]

和用Collection集合类似

import java.util.ArrayList;
import java.util.List;

public class ListStu {
    public static void main(String[] args) {
        //创建集合
        List<Student> li = new ArrayList<>();

        //创建学生对象
        Student s1 = new Student(1,"刘德华");
        Student s2 = new Student(2,"周星驰");
        Student s3 = new Student(3,"林青霞");
        Student s4 = new Student(4,"成龙");

        //往集合中添加学生对象
        li.add(s1);
        li.add(s2);
        li.add(s3);
        li.add(s4);

        //使用for循环遍历List集合
        for (int i = 0;i<li.size();i++){
            Student s = li.get(i);
            System.out.println(s.getId()+" "+s.getName());
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v1ihs4Yq-1628504909929)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210717210238963.png)]

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ListException {
    public static void main(String[] args) {
        //创建list对象
        List<String> li = new ArrayList<>();

        //添加元素
        li.add("hello");
        li.add("world");
        li.add("java");

        //遍历集合,判断是否有world,如果有就添加一个javaee字符串
        Iterator<String> it = li.iterator();

        /*while(it.hasNext()){
            String s = it.next();
            if(s.equals("world")){
                li.add("javaee");
                //与其修改异常
            }
        }*/

        for (int i = 0;i<li.size();i++){
            String s = li.get(i);
            if (s.equals("world")){
                li.add("javaee");
            }
        }
        System.out.println(li);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BU4p3JUF-1628504909929)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718192405009.png)]

列表迭代器有一项特别的方法,add方法,可以在使用迭代器遍历过程中进行添加操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oPcoPIzy-1628504909930)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718193620959.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sco6jCWQ-1628504909931)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718194259702.png)]

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ListStu2 {
    public static void main(String[] args) {
        //创建集合
        List<Student> list = new ArrayList<>();

        //创建学生对象
        Student s1 = new Student(1,"刘德华");
        Student s2 = new Student(2,"周星驰");
        Student s3 = new Student(3,"林青霞");
        Student s4 = new Student(4,"成龙");

        //将学生对象添加到List集合中
        list.add(s1);
        list.add(s2);
        list.add(s3);
        list.add(s4);

        //使用Iterator迭代器遍历
        Iterator<Student> it = list.iterator();
        while (it.hasNext()){
            Student stu = it.next();
            System.out.println(stu.getId()+" "+stu.getName());
        }

        //使用普通for循环遍历
        for (int i = 0;i< list.size();i++){
            Student stu = list.get(i);
            System.out.println(stu.getId()+" "+stu.getName());
        }

        //使用增强for循环遍历
        for (Student stu : list){
            System.out.println(stu.getId()+" "+stu.getName());
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dqMUETio-1628504909932)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718200606188.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QkyVLAii-1628504909933)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718200524985.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fIgRYpRU-1628504909933)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718200710401.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fYcqa106-1628504909934)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718200857433.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mxok0OIX-1628504909935)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718201034117.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9pvtLmIf-1628504909936)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718201902274.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DBlhaFCa-1628504909936)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718202704746.png)]

import java.util.LinkedList;

public class LinkedListDemo {
    public static void main(String[] args) {
        LinkedList<String> li = new LinkedList<>();

        li.add("java");
        li.add("javase");
        li.add("javaweb");
        System.out.println(li);

        //addFirst 在列表开头插入指定元素
        li.addFirst("hello");
        System.out.println("添加首元素"+li);

        //addLast 在列表结尾追加指定元素
        li.addLast("javaee");
        System.out.println("添加尾元素"+li);

        //getFirst 获取列表第一个元素
        System.out.println("first"+li.getFirst());
        System.out.println(li);

        //getLast 获取列表最后一个元素
        System.out.println("last"+li.getLast());
        System.out.println(li);

        //removeFirst 删除列表第一个元素并返回
        System.out.println("removeFirst"+li.removeFirst());
        System.out.println(li);

        //removeLast 删除列表最后一个元素并返回
        System.out.println("removeLast"+li.removeLast());
        System.out.println(li);

    }
}

Set

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bc9MYzfu-1628504909937)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718203730570.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SdSF6RqH-1628504909938)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718205309723.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JyYNmlok-1628504909938)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718205437465.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-As7hUsLl-1628504909939)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718210552841.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3lJCsxZJ-1628504909940)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718210755477.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X7gV3mL9-1628504909941)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718211040052.png)]

哈希表实现原理:数组+链表

存储过程:

  1. 获取要存元素的哈希值
  2. 对该哈希值使用哈希表长度取余
  3. 查看余数位置是否已存在元素
  4. ①如果已存在元素,则对该元素先比较哈希值是否相同,哈希值不同则存入后序链表中;如果哈希值相同则使用equals方法比较两个元素是否相等,如果不相等则将该元素存入后序链表中,如果相等则不存储。②如果不存在元素,则直接将该元素存入此位置。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OXxPCUqk-1628504909941)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718212536927.png)]

#如果要保证hashset中每个元素的唯一性,需要重写equals和hashCode方法

在对象类中自动生成重写方法,Alt+insert 选择hashCode和equals 然后next next next fanish

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i6p4ito9-1628504909942)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718212844348.png)]

因为LinkedHashSet集合同样继承Set集合,所以它不能同时存在相同的元素。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dlya5Oqm-1628504909943)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718213552527.png)]

自动排序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OmHqWgPq-1628504909943)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718215530393.png)]

使用自然排序的前提是使用的对象要实现Comparable接口,然后重写conpareTo方法,返回0时不添加元素,返回正值时在后面添加元素,负值时在前面添加元素。

import java.util.Objects;

//第一步实现Comparable接口
public class Student implements Comparable<Student> {
    private int id;
    private String name;

    public Student(){
        this.id = id;
        this.name = name;
    }

    public Student(int id, String name){
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return id == student.id &&
                Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }

	//第二步,重写compareTo方法
    @Override
    public int compareTo(Student o) {
        int num = this.id - o.id;
        int num2 = num==0?this.name.compareTo(o.name):num;
        return num2;
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CQgaKPMB-1628504909944)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210718220314818.png)]

public class TreeSetDemo3 {
    public static void main(String[] args) {
        //创建集合对象
        TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student s1, Student s2) {
                //this s1
                int num = s1.getId() - s2.getId();
                int num2 = num==0?s1.getName().compareTo(s2.getName()):num;
                return num2;
            }
        });

        //创建学生对象
        Student s1 = new Student(1,"xishi");
        Student s2 = new Student(3,"wangzhaojun");
        Student s3 = new Student(2,"diaochan");
        Student s4 = new Student(4,"yangyuhuan");

        Student s5 = new Student(4,"zhoujielun");
        Student s6 = new Student(4,"zhoujielun");

        //添加学生到集合中
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);

        ts.add(s5);
        ts.add(s6);

        //遍历
        for (Student stu:ts){
            System.out.println(stu.getId()+" "+stu.getName());
        }

    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oxfmynod-1628504909945)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210719103952678.png)]

代码位置:day15>SetStuScoreDemo and SetStuScoreDemo2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CLzXy85s-1628504909945)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210719111420298.png)]

day15>RandomDemo

泛型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dA46mHUF-1628504909946)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210719112521838.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EsIR1GR9-1628504909947)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210719115120828.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yHJM4YON-1628504909947)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210719120046762.png)]

使用:

public class GenericDemo3 {
    public static void main(String[] args) {
        GenericFunc g = new GenericFunc();
        g.show("林青霞");
        g.show(30);
        g.show(true);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5KabhPUU-1628504909948)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210719120625405.png)]

步骤:

首先定义一个泛型接口

public interface GenericInterface<T> {
    void show(T t);
}

然后定义该接口的实现类,并重写接口方法

public class GenericImp<T> implements GenericInterface<T> {
    @Override
    public void show(T t) {
        System.out.println(t);
    }
}

之后,在测试类中测试泛型方法

public class GenericDemo4 {
    public static void main(String[] args) {
        GenericImp<String> g1 = new GenericImp<>();
        g1.show("林青霞");
        GenericImp<Integer> g2 = new GenericImp<>();
        g2.show(30);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7P7QZj2v-1628504909949)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210719121451960.png)]

public class GenericDemo5 {
    public static void main(String[] args) {
        //类型通配符 <?>
        List<?> li1 = new ArrayList<Object>();
        List<?> li2 = new ArrayList<String>();
        List<?> li3 = new ArrayList<Integer>();
        System.out.println("-------------------");

        //类型通配符上限 <?extends 类型>
        List<?extends Number> li4 = new ArrayList<Number>();
        List<?extends Number> li5 = new ArrayList<Integer>();
        System.out.println("--------------------");

        //类型通配符下限
        List<?super Number> li6 = new ArrayList<Number>();
        List<?super Number> li7 = new ArrayList<Object>();
       //编译时异常 List<?super Number> li8 = new ArrayList<Integer>();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fiIrRHxi-1628504909950)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210719122915699.png)]

如果一个方法有多个参数,而且包含可变参数,那么可变参数要放在最后才不会报错。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DOJWuv8k-1628504909950)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210719124541906.png)]

jdk9之后才支持Set.of和List.of方法,1.8会报错找不到该方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R3QjtZJ0-1628504909951)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210719162712215.png)]

Map集合添加元素的方法:put(K,V),同时也具有Map集合的修改功能。使用方法:当Map集合已经有某个Key时,再次添加该键值和Value值,则会对原本的Key=Value进行覆盖。达到修改的作用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EzbggC32-1628504909952)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210719163313764.png)]

使用remove删除集合中元素时,会返回删除值的内容。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PYfvkf3A-1628504909953)(C:/Users/ccc/AppData/Roaming/Typora/typora-user-images/image-20210719164315399.png)]

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapDemo01 {
    public static void main(String[] args) {
        Map<String , String> map = new HashMap<String,String>();

        //添加元素
        map.put("班长","王境泽");
        map.put("学委","张无忌");
        map.put("班主任","雷电法王");

        //.get方法,根据键获取值
        System.out.println(map.get("班长"));
        System.out.println(map.get("嘻嘻嘻"));

        //.keySet获取所有键的集合
        Set<String> keySet = map.keySet();
        for (String key:keySet){
            System.out.println(key);
        }

        //.values()获取所有值的集合
        Collection<String> val = map.values();
        for(String v:val){
            System.out.println(v);
        }
    }
}

Map集合的遍历

方法一:先把所有的键放到一个集合中,然后再通过键去获取Map中的值

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Demo2 {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();
        //添加元素
        map.put("班长","王境泽");
        map.put("学委","张无忌");
        map.put("班主任","雷电法王");

        //第一步,获取所有键的集合 keySet方法
        Set<String> set = map.keySet();
        //第二部,根据已经得到的键的集合,使用get方法遍历值
        for (String str:set){
            String s = map.get(str);
            System.out.println(str+":"+s);
        }
    }
}

方法二:使用entrySet()方法,把map中的键值对对象放在一个集合中,然后使用getKey和getValue方法获取k和v

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Demo3 {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();
        //添加元素
        map.put("班长","王境泽");
        map.put("学委","张无忌");
        map.put("班主任","雷电法王");

        //遍历map方法二:使用entrySet()方法获取键值对对象
        //第一步:获取所有键值对对象集合
        Set<Map.Entry<String,String>> est = map.entrySet();
        //遍历键值对对象集合,得到没个键值对对象
        for (Map.Entry<String,String> me:est){
            String k = me.getKey();
            String v = me.getValue();
            System.out.println(k+":"+v);
        }
    }
}

案例:HashMap集合存储学生对象并遍历

需求:创建一个HashMap对象,k是学好,v是学生对象,存储三个键值对元素,并使用两种方法遍历

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Demo4 {
    public static void main(String[] args) {
        Map<String,Student> map = new HashMap<String,Student>();

        //创建学生对象
        Student s1 = new Student("张三",21);
        Student s2 = new Student("李四",22);
        Student s3 = new Student("王五",33);

        //添加学生对象
        map.put("001",s1);
        map.put("002",s2);
        map.put("003",s3);

        //方法一遍历
        Set<String> keySet = map.keySet();
        for (String str:keySet){
            Student stu = map.get(str);
            System.out.println(str+" "+stu.getName()+" "+stu.getAge());
        }

        System.out.println("-----------------");

        //方法二遍历
        Set<Map.Entry<String,Student>> et = map.entrySet();
        for (Map.Entry<String,Student> me:et){
            String id = me.getKey();
            Student stu = me.getValue();
            System.out.println(id+" "+stu.getName()+" "+stu.getAge());
        }

    }
}

案例:HashMap集合存储学生对象并遍历

需求:创建一个HashMap集合,k是学生对象,v是居住地。要求:保证键的唯一性

  • 定义学生类
  • 创建HashMap集合对象
  • 创建学生对象
  • 将学生对象添加到集合
  • 遍历集合
  • 在学生类中重写两个方法保证键的唯一性hashCode() equals()
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Demo5 {
    public static void main(String[] args) {
        //创建HM对象
        Map<Student,String> hm = new HashMap<>();
        //创建学生对象
        Student s1 = new Student("张三",21);
        Student s2 = new Student("李四",22);
        Student s3 = new Student("王五",33);
        Student s4 = new Student("王五",33);

        //添加学生对象
        hm.put(s1,"西安");
        hm.put(s2,"北京");
        hm.put(s3,"杭州");
        hm.put(s4,"北京");
        //遍历集合
        Set<Student> set = hm.keySet();
        for (Student stu:set){
            String address = hm.get(stu);
            System.out.println(stu.getName()+" "+stu.getAge()+" "+address);
        }
    }
}

案例:ArrayList集合存储HashMap元素并遍历

需求:创建一个ArrayList集合,存储三个元素,每个元素都是HashMap,每个HashMap的键和值都是String,最后遍历。思路:

  • 创建ArrayList集合
  • 创建HashMap集合,并添加键值对元素
  • 把HashMap作为元素添加到ArrayList集合
  • 遍历ArrayList
import java.util.*;

public class Demo6 {
    public static void main(String[] args) {
        ArrayList<HashMap<String,String>> lhm = new ArrayList<>();
        //创建hashmap集合
        HashMap<String,String> hm1 = new HashMap<>();
        hm1.put("11","aa");
        hm1.put("12","ab");
        HashMap<String,String> hm2 = new HashMap<>();
        hm2.put("21","ba");
        hm1.put("22","bb");
        HashMap<String,String> hm3 = new HashMap<>();
        hm3.put("31","ca");
        hm1.put("32","cb");

        //将HashMap添加到ArrayList中
        lhm.add(hm1);
        lhm.add(hm2);
        lhm.add(hm3);

        //遍历ArrayList
        for (HashMap<String,String> hs:lhm){
            Set<String> k = hs.keySet();
            for (String key:k){
                String val = hs.get(key);
                System.out.println(key+" "+val);
            }
        }
    }
}

案例:HashMap集合存储ArrayList元素并遍历

需求:创建一个HashMap集合,存储三个键值对元素,每个键值对元素的键是String,值是ArrayList,每个ArrayList元素是String,最后遍历。思路:

  • 创建HashMap集合
  • 创建ArrayList集合,并添加元素
  • 将ArrayList集合作为元素添加到HashMap中
  • 遍历HashMap集合
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;

public class Demo7 {
    public static void main(String[] args) {
        //创建HashMap集合
        HashMap<String, ArrayList<String>> hm = new HashMap<>();
        //创建ArrayList集合
        ArrayList<String> arr1 = new ArrayList<>();
        arr1.add("a1");
        arr1.add("a2");
        ArrayList<String> arr2 = new ArrayList<>();
        arr2.add("b1");
        arr2.add("b2");
        ArrayList<String> arr3 = new ArrayList<>();
        arr3.add("c1");
        arr3.add("c2");
        
        //添加元素
        hm.put("001",arr1);
        hm.put("002",arr2);
        hm.put("003",arr3);

        //遍历hashmap
        Set<String> keySet = hm.keySet();
        for (String k:keySet){
            String key = k;
            ArrayList<String> val = hm.get(k);
            for (String v:val){
                System.out.println(key+" "+v);
            }
        }

    }
}

案例:统计字符串中每个字符出现的次数

需求:键盘录入一个字符串,要求统计字符串中每个字符出现的次数。举例:aababc 输出:a(3)b(3)c(1)。如果要想输出的结果有序,将HashMap集合换成TreeMap集合即可

import java.util.HashMap;
import java.util.Scanner;
import java.util.Set;

public class Demo8 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个字符串:");
        String line = sc.nextLine();

        //创建HashMap集合
        HashMap<Character,Integer> hm = new HashMap<>();

        //遍历字符串,得到没个字符
        for (int i=0;i<line.length();i++){
            char k = line.charAt(i);
            Integer value = hm.get(k);
            if (value == null) {
                hm.put(k,1);
            }else{
                value++;
                hm.put(k,value);
            }
        }
        Set<Character> keySet = hm.keySet();
        for (Character key:keySet){
            Integer i = hm.get(key);
            System.out.println(key+"("+i+")");
        }
    }
}

Collections

Collections是针对集合操作的工具类

Collections类常用方法

  • sort(List<T> list);将指定列表按升序排列
  • reverse(List<?> list);反转指定列表元素的顺序
  • shuffle(List<?> list);使用默认的随机源随机排列指定的列表
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public class Demo9 {
    public static void main(String[] args) {
        //创建集合对象
        List<Integer> list = new ArrayList<>();

        //添加元素
        list.add(22);
        list.add(222);
        list.add(444);
        list.add(221);
        list.add(11);

        //Collections.sort()排序方法
        Collections.sort(list);
        //反转指定列表
        Collections.reverse(list);
        //shuffle随机置换方法,洗牌
        Collections.shuffle(list);
        System.out.println(list);
    }
}

案例:ArrayList存储学生对象并排序

需求:ArrayList存储学生对象,使用Collections对ArrayList进行排序

要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序。采用匿名内部类实现比较器

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;

public class Demo10 {
    public static void main(String[] args) {
        ArrayList<Student> al = new ArrayList<>();

        //创建学生对象
        Student s1 = new Student("li",20);
        Student s2 = new Student("wang",22);
        Student s3 = new Student("zhao",20);

        //添加学生对象
        al.add(s1);
        al.add(s2);
        al.add(s3);

        //使用Collections排序,采用匿名内部类实现比较器
        Collections.sort(al, new Comparator<Student>() {
            @Override
            public int compare(Student s1, Student s2) {
                //主要条件
                int num = s1.getAge()-s2.getAge();
                int num2 = num==0?s1.getName().compareTo(s2.getName()):num;
                return num2;
            }
        });

        //遍历集合
        for (Student stu:al){
            System.out.println(stu.getAge()+" "+stu.getName());
        }
    }
}

案例:模拟斗地主

需求:通过程序实现斗地主过程中的洗牌,发牌和看牌。思路:

  • 创建一个牌盒,也就是定义一个集合对象,用ArrayList实现
  • 往牌盒里面装牌,也就是添加元素
  • 洗牌,用Collections的shuffle方法
  • 发牌,遍历集合给三个玩家发牌
  • 看牌,也就是三个玩家分别遍历自己的牌
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;

public class Demo11 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();

        //往牌盒里装牌
        String[] colors = {"♦","♥","♠","♣"};
        String[] numbers = {"2","3","4","5","6","7","8","9","10","J","Q","K","A"};
        for (String color:colors){
            for (String number:numbers){
                list.add(color+number);
            }
        }
        list.add("小王");
        list.add("大王");

        //使用Collections的shuffle方法洗牌
        Collections.shuffle(list);

        //发牌,遍历集合给3个玩家发牌
        ArrayList<String> u1 = new ArrayList<>();
        ArrayList<String> u2 = new ArrayList<>();
        ArrayList<String> u3 = new ArrayList<>();
        ArrayList<String> dp = new ArrayList<>();   //底牌

        for (int i=0;i<list.size();i++){
            String poke = list.get(i);
            if(i>=list.size()-3){
                dp.add(poke);
            }else if (i%3==0){
                u1.add(poke);
            }else if (i%3==1){
                u2.add(poke);
            }else if (i%3==2){
                u3.add(poke);
            }
        }

        //给手牌排序
        Collections.sort(u1);
        Collections.sort(u2);
        Collections.sort(u3);
        Collections.sort(dp);
        //看牌
        lookPoke("小马",u1);
        lookPoke("小赵",u2);
        lookPoke("小张",u3);
        lookPoke("底",dp);
    }

    public static void lookPoke(String name,ArrayList<String> list){
        System.out.print(name+"牌是:");
        for (String poke:list){
            System.out.print(poke+" ");
        }
        System.out.println();
    }
}
/*
	小马牌是:♠7 ♠K ♣10 ♣3 ♣5 ♣6 ♣7 ♣K ♥2 ♥3 ♥5 ♥8 ♦5 ♦7 ♦9 ♦Q 大王 
	小赵牌是:♠6 ♠8 ♠9 ♠J ♣4 ♣8 ♣A ♣J ♣Q ♥A ♥J ♥Q ♦10 ♦4 ♦8 ♦A ♦K 
	小张牌是:♠10 ♠2 ♠3 ♠4 ♠5 ♠A ♣2 ♥10 ♥4 ♥6 ♥7 ♥9 ♥K ♦2 ♦3 ♦J 小王 
	底牌是:♠Q ♣9 ♦6 
*/

案例:模拟斗地主2,使用TreeMap代替Collections的sort方法进行排序

import com.sun.javafx.image.IntPixelGetter;

import javax.naming.PartialResultException;
import java.lang.reflect.Array;
import java.util.*;

public class Demo12 {
    public static void main(String[] args) {
        //创建HashMap,键是编号,值是牌
        HashMap<Integer,String> hm = new HashMap<>();
        //创建ArrayList存储编号
        ArrayList<Integer> list = new ArrayList<>();

        //往牌盒里装牌
        String[] colors = {"♦","♥","♠","♣"};
        String[] numbers = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
        int index = 0;
        for (String number:numbers){
            for (String color:colors){
                hm.put(index,color+number);
                list.add(index);
                index++;
            }
        }
        hm.put(index,"小王");
        list.add(index);
        index++;
        hm.put(index,"大王");
        list.add(index);

        //使用Collections的shuffle方法洗牌
        Collections.shuffle(list);

        //发牌,遍历集合给3个玩家发牌
        TreeSet<Integer> ts1 = new TreeSet<>();
        TreeSet<Integer> ts2 = new TreeSet<>();
        TreeSet<Integer> ts3 = new TreeSet<>();
        TreeSet<Integer> dp = new TreeSet<>();
        for (int i=0;i<list.size();i++){
            if (i>=list.size()-3){
                dp.add(list.get(i));
            }else if(i%3 == 0){
                ts1.add(list.get(i));
            }else if(i%3 == 1){
                ts2.add(list.get(i));
            }else if(i%3 == 2){
                ts3.add(list.get(i));
            }
        }

        System.out.println("----------模拟斗地主起牌----------");
        lookPoke("小马",ts1,hm);
        lookPoke("小赵",ts2,hm);
        lookPoke("小张",ts3,hm);
        lookPoke("底",dp,hm);
    }
    //看牌
    public static void lookPoke(String name, TreeSet<Integer> treeSet,HashMap<Integer,String> hashMap){
        System.out.print(name+"牌是:");
        for (Integer key:treeSet){
            String poke = hashMap.get(key);
            System.out.print(poke+" ");
        }
        System.out.println();
    }
}

IO流

File

File类概述和构造方法

File是文件和目录路径名的抽象表示

  • 文件和目录是可以通过File封装成对象的
  • 对于File而言,其封装的并不是一个真正的文件,仅仅是一个路径名而已,它是可以存在的,也可以是不存在的。将来是要通过具体的操作把这个路径的内容转换为具体存在的

构造方法

  • File(String pathName) 通过将给定的路径名字符串转化为抽象路径名来创建新的File实例
  • File(String parent,String child) 从父路径名字符串和子路径名字符串创建新的File实例
  • File(File parent,String child) 从父抽象路径名和子路径名字符串创建新的File实例
import java.io.File;

public class Demo1 {
    public static void main(String[] args) {
        //File的三种构造方法
        File f1 = new File("E:\\Code\\JAVA\\IO\\ioTest01.txt");
        System.out.println(f1);
        File f2 = new File("E:\\Code\\JAVA","IO\\ioTest01.txt");
        System.out.println(f2);
        File f3 = new File("E:\\Code");
        File f4 = new File(f3,"JAVA\\IO\\ioTest01.txt");
        System.out.println(f4);
    }
}
File类创建功能
  • public boolean creatNewFile() 当具有该名称的文件不存在时,创建一个由该抽象路径名命名的新空文件,如果目录不存在会报异常找不到路径
  • public boolean mkdir() 创建由此抽象路径名命名的目录
  • public boolean mkdirs() 创建由此抽象路径名命名的目录,包括任何必须但不存在的父目录
import java.io.File;
import java.io.IOException;

public class Demo2 {
    public static void main(String[] args) throws IOException {
        /*
        mkdir方法,创建一个目录
            如果目录不存在就创建一个目录,返回true
            如果目录存在就不创建目录,并返回false
        creatNewFile(),创建一个文件
            如果文件不存在,就创建一个文件,并返回true
            如果文件存在,就不创建文件,返回false
        mkdirs(),创建目录
            如果目录父路径不存在,直接创建所需的所有路径,并返回ture
            如果该目录已存在,则不创建返回false
        */
        File f1 = new File("E:\\MYZ\\Code\\JAVA\\IO");
        System.out.println(f1.mkdir());

        //在f1中创建一个文件
        File f2 = new File(f1,"ioTest01.txt");
        System.out.println(f2.createNewFile());

        File f3 = new File("E:\\MYZ\\Code\\JAVA\\IO\\JavaWeb\\HTML");
        System.out.println(f3.mkdirs());
    }
}

注意:如果一个目录下已经有一个目录,则在该目录创建相同名称的文件返回false

File类判断和获取功能
  • public boolean isDirectory() 判断抽象路径名表示的File是否为目录
  • public boolean isFile() 判断抽象路径名表示的FIle是否为文件
  • public boolean exists() 判断抽象路径名表示的File是否存在
  • public String getAbsolutePath() 返回此抽象路径名的绝对路径字符串
  • public String getPath() 将此抽象路径名转换路径名字符串
  • public String getName() 返回此抽象路径名表示的文件或目录的名称
  • public String[] list() 返回此抽象路径名表示的目录中的文件和目录的名称字符串数组
  • public File[] listFile() 返回此抽象路径名表示的目录中的文件和目录的File对象数组
import java.io.File;

public class Demo3 {
    public static void main(String[] args) {
        File f = new File("E:\\MYZ\\Code\\JAVA\\IO");
        System.out.println(f+"是否为目录:"+f.isDirectory());
        System.out.println(f+"是否为文件:"+f.isFile());
        System.out.println(f+"是否存在:"+f.exists());
        System.out.println(f.getAbsolutePath());
        System.out.println(f.getPath());
        System.out.println(f.getName());

        String[] s = f.list();
        for (String str:s){
            System.out.println(str);
        }

        File[] f2 = f.listFiles();
        for (File file:f2){
            //加入判断,只输出文件名
            if (file.isFile()){
                System.out.println(file.getName());
            }
        }
    }
}
File类删除
  • public boolean delete() 删除由此抽象路径名表示的文件或目录

绝对路径和相对路径

  • 绝对路径:完整的路径名,不需要任何其他信息就可以定位它所表示的文件
  • 相对路径:必须使用取自其他路径名的信息进行解释

注意:如果删除一个目录的下面有文件,则无法删除。必须先删除目录下文件才可以删除父级目录

递归

概述:以编程的角度来看,递归指的是方法定义中调用方法本身的现象

递归解决问题的思路:

  • 把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
  • 递归策略只需要少量的程序就可描述出解题过程所需要的多次重复计算

递归解决问题要找到两个内容:

  • 递归出口,否则会出现内存溢出
  • 递归规则,与原问题相似的规模较小的问题

案例:递归求阶乘

需求:用递归求一个数的阶乘,并把结果在控制台输出

public class Demo5 {
    public static void main(String[] args) {
        int i = f(6);
        System.out.println(i);
    }
    public static int f(int n){
        if (n==1){
            return 1;
        }
        return n*f(n-1);
    }
}

案例:遍历目录

需求:给定一个路径,通过递归完成遍历该目录下的所有内容,并把所有文件的绝对路径输出

import java.io.File;

public class Demo6 {
    public static void main(String[] args) {
        File file = new File("E:\\MYZ\\Code\\C");
        getpath(file);
    }

    public static void getpath(File file){
        File[] fileArr = file.listFiles();
        if (fileArr != null){
            for (File f:fileArr){
                if (f.isFile()){
                    System.out.println(f.getPath());
                }else{
                    getpath(f);
                }
            }
        }
    }
}

流的体系结构

一般情况下在程序中使用缓冲流

抽象基类

  • InputStream
  • OutputStream
  • Reader
  • Writer

文件流

  • FileInputStream
  • FileOutputStream
  • FileReader
  • FileWriter

缓冲流:提高程序的流读写速度。分为处理字节和处理字符的缓冲流。

  • 能够提高读写速度的原因:缓冲流提供了一个缓冲区,将读到的字节存放在缓冲区中,直到缓冲区达到一定的量后一次性写入文件,减少了写入次数,所以提高了写入速度。

flush方法,刷新缓冲区,当调用这个方法时,无论缓冲区存放了多少字节,都写出并清空该缓冲区

  • BufferedInputStream 处理字节
  • BufferedOutputStream 处理字节
  • BufferedReader 处理字符
  • BufferedWriter 处理字符

总结

	  抽象基类			   文件流												缓冲流(处理流的一种)
字节流	InputStream			FileOutputStream (read(byte[] bytes))			   BufferedInputStream (read(byte[] bytes))
	  OutputStream		  FileInputStream (writer(byte[] bytes,0,len))		 BufferedOutputStream (read(byte[] bytes,0,len))
字符流	Reader				FileReader (read(char[] chars))					   BufferedReader (char(char[] chars) / readLine())
	  Writer			  FileWriter (writer(char[] chars,0,len))			 BufferedWriter (char(char[] chars,0,len))

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u8gs71CQ-1628504909955)(image/image-20210802210650736.png)]

字节流

IO流概述和分类
  • IO:输入输出InputOutput
  • 流:是一种抽象概念,是对数据传输的总称。
  • IO流就是用来处理设备间数据传输问题,常见的应用有:文件复制、文件上传、文件下载
  • 输入流:读数据
  • 输出流:写数据
  • 字节流:字节输入流,字节输出流
  • 字符流:字符输入流,字符输出流

一般来说,我们说IO流的分类是按照数据类型来分的

  • 如果数据通过windows自带的记事本打开,我们还可以读懂里面的内容,就是用字符流,否则使用字节流。如果你不知道该使用哪种类型的流,就是用字节流。
字节流写数据

字节流抽象基类

  • InputStream:表示字节输入流的所有类的超类
  • OutputStream:表示字节输出流的所有类的超类
  • 子类名特点:子类名称都是以其父类作为子类名的后缀

使用字节输出流写数据的步骤:

  • 创建输出流对象
  • 调用字节输出流对象写数据方法
  • 释放资源

FileOutputStream:文件输出流用于将数据写入File,创建文件输入流以指定的名称写入文件

构造方法

  • FileOutputStream(String name):创建文件输出流以指定的名称写入文件,等价于FileOutputStream(new File(String string))
  • FileOutputStream(File file):创建文件输出流写入由指定的File对象表示的文件

操作方法

  • void write(int):将指定的字节写入文件输出流对象
  • void write(bytep[] b):将b.length字节从指定的字节数组写入输出流对象,一次写一个字节数组数据
  • void write(byte[] b,int off,int len):将len字节从指定的字节数组开始,从偏移量off开始写入输出流对象,一次写一个字节的数组的部分数据
  • close():关闭文件输出流并释放与此相关的任何系统资源
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;

public class Demo7 {
    public static void main(String[] args) throws IOException {
        //创建输出流对象
        FileOutputStream fos1 = new FileOutputStream("fos1.txt");
        File file = new File("fos2.txt");
        FileOutputStream fos2 = new FileOutputStream(file);
        FileOutputStream fos3 = new FileOutputStream(new File("fos3.txt"));

        //write(int)
        fos1.write(99);

        //write(byte[])
        byte[] b = {1,2,3,4,5,5,6,76,2,1};
        fos2.write(b);

        //getbyte方法,将字符串数据转化为字节数
        b = "abcdef".getBytes();
        fos2.write(b);

        //write(byte[] b,int off,int len)
        fos3.write(b,2,2);

        fos1.close();
        fos2.close();
        fos3.close();

    }
}

字节流写数据换行和追加

  • 换行:windows\r \n linux\n mac \r
  • 追加写入只需要将FileOutputStream加入第二个参数true就可以,FileOutputStream(byte[],true)
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo8 {
    public static void main(String[] args) throws IOException {
        //字节流写入换行和追加
        FileOutputStream fos = new FileOutputStream("fos1.txt",true);
        //换行:windows\r \n linux\n mac \r
        for (int i=0;i<5;i++){
            fos.write("hello\n".getBytes());
        }
        fos.close();
    }
}
字节流写数据异常处理

finally:在异常处理时提供finally块来执行所有清除操作,比如说IO流中的释放资源。

特点:被finally控制的语句一定会执行,除非JVM退出

try{
	可能出现异常的代码
}catch(异常类名 变量名){
	异常的处理代码
}finally{
	执行所有清空操作
}
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo9 {
    public static void main(String[] args) {
        //字节流写数据加异常处理
        FileOutputStream fos = null;
        try{
            fos = new FileOutputStream("fos1.txt");
            fos.write("hello".getBytes());
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
字节流读数据

需求:把文件fos.txt中的内容读取出来在控制台输出

使用字节输入流读数据的步骤:

  • 创建字节输入流对象
  • 调用字节输入流对象的读数据方法
  • 释放资源

方法:

int read(byte[] b);从该输入流读取最多b.length个字节的数据到字节数组,返回实际读取的数据的长度

FileInput/OutputStream
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class Demo10 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("fos1.txt");

        byte[] bys = new byte[1024];//一般为1024及整数倍
        int len;
        while((len=fis.read(bys))!=-1){
            System.out.println(new String(bys,0,len));
        }
        fis.close();
    }
}

案例:把fos1复制到沁园春中,一次读取一个字节数据

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo11 {
    public static void main(String[] args) throws IOException {
        //根据数据源创建字节流输入对象
        FileInputStream is = new FileInputStream("fos1.txt");
        //创建字节流输出对象
        FileOutputStream os = new FileOutputStream("沁园春.txt");

        int l;
        while((l=is.read())!=-1){
            os.write(l);
        }
        is.close();
        os.close();
    }
}

案例:把fos1复制到fos2中,一次读取一个数组数据

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo12 {
    public static void main(String[] args) throws IOException {
        //根据数据源创建字节流输入对象
        FileInputStream is = new FileInputStream("fos1.txt");
        //创建字节流输出对象
        FileOutputStream os = new FileOutputStream("fos2.txt");

        int l;
        byte[] b = new byte[1024];
        while((l=is.read(b))!=-1){
            os.write(b);
        }
        is.close();
        os.close();
    }
}

案例:复制图片

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo13 {
    public static void main(String[] args) throws IOException {
        //创建字节流输入对象
        FileInputStream is = new FileInputStream("Maozedong.png");
        //创建字节流输出对象
        FileOutputStream os = new FileOutputStream("Jiaoyuan.png",true);

        byte[] b = new byte[1024];
        int len;
        while((len=is.read(b))!=-1){
            os.write(b);
        }
        is.close();
        os.close();
    }
}

案例:复制单层文件夹到模块目录下。思路:

  • 创建File对象
  • 获取File对象的名称 itcast
  • 创建目的地目录File对象
  • 判断目的地目录对应FIle是否存在,如果不存在就创建
  • 获取数据源目录下所有文件的File数组
  • 遍历File数组,得到每一个File对象
  • 获取数据源文件File对象的名称
  • 创建目的地文件File对象,并将其写入
import java.io.*;

public class Demo15 {
    public static void main(String[] args) throws IOException {
        //创建源File对象
        File fo = new File("E:\\MYZ\\Code\\JAVA\\IO\\ts");
        //创建目的地File对象,路径为当前模块下,文件夹名和源文件夹名一样
        File fd = new File(fo.getName());
        //判断是否存在该文件夹,如果不存在则创建
        if (!fo.exists()){
            fd.mkdir();
        }
        //对源File对象使用listFiles方法,获取下属的文件对象
        File[] files = fo.listFiles();
        //使用增强for循环遍历我们要复制的File对象数组
        for (File srcfile:files){
            //创建目的文件对象路径为当前目录ts下,名称为源file对象名
            File destfile = new File(fo.getName(),srcfile.getName());
            //我们自定义复制文件函数,传入值为源文件对象和目的文件对象
            copyFile(srcfile, destfile);
        }
    }

    private static void copyFile(File srcfile, File destfile) throws IOException {
        //创建字节输入流对象
        FileInputStream is = new FileInputStream(srcfile);
        //创建字节输出流对象
        FileOutputStream os = new FileOutputStream(destfile);
        //定义字节数组
        byte[] bytes = new byte[1024];
        int l;
        //将文件写入
        while ((l=is.read(bytes))!=-1){
            os.write(bytes);
        }
        is.close();
        os.close();
    }
}

需求:复制多级目录。

import java.io.*;

public class Demo16 {
    //案例:复制多级文件夹
    public static void main(String[] args) throws IOException {
        File srcfile = new File("E:\\MYZ\\Code\\JAVA\\IO");
        File destfile = new File(".\\");
        copy(srcfile,destfile);
    }
    public static void copy(File srcfile, File destfile) throws IOException {
        //首先判断
        if (srcfile.isDirectory()){
            String srcfileName = srcfile.getName();
            File newFolder = new File(destfile,srcfileName);
            if (!newFolder.exists()){
                newFolder.mkdir();
            }
            File[] files = srcfile.listFiles();
            for (File file:files){
                //递归
                copy(file,newFolder);
            }
        }else{
            //如果是文件
            File newFile = new File(destfile,srcfile.getName());
            copyFile(srcfile,newFile);
        }
    }
    //字节流缓冲复制文件
    private  static void copyFile(File srcfile,File destfile) throws IOException {
        BufferedInputStream is = new BufferedInputStream(new FileInputStream(srcfile));
        BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(destfile));
        byte[] bytes = new byte[1024];
        int len;
        while ((len=is.read(bytes))!= -1 ) {
            os.write(bytes,0,len);
        }
        is.close();
        os.close();
    }
}
BufferedInput/OutputStream
import java.io.*;

public class Demo1 {
    public static void main(String[] args) throws IOException {
        //创建File对象
        String srcpath = "jiaoyuan.png";
        String destpath = "jiaoyuan3.png";
        String destpath2 = "jiaoyuan4.png";
        copyStart(srcpath,destpath);
        copyBuffer(srcpath,destpath2);
    }
    public static void copyBuffer(String srcpath,String destpath){
        long start = System.currentTimeMillis();
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try{
            //创建File对象
            File file = new File(srcpath);
            File file2 = new File(destpath);

            //创建文件流
            FileInputStream fis = new FileInputStream(file);
            FileOutputStream fos = new FileOutputStream(file2);
            //创建缓冲流
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);

            //写入
            byte[] bytes = new byte[10];
            int len;
            while((len = bis.read(bytes))!=-1){
                bos.write(bytes,0,len);
            }
        }catch (IOException e) {
            e.printStackTrace();
        }finally {
            //关闭外层流的同时,内层流也会自动进行关闭,所以只需要关闭bos和bis就行
            if (bos!=null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bis!=null){
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("缓冲流复制花费时间:"+(end-start));
    }
    public static void copyStart(String srcpath,String destpath) throws IOException {
        long start = System.currentTimeMillis();
        File file = new File(srcpath);
        File file2 = new File(destpath);
        FileInputStream fis = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(file2);
        byte[] bytes = new byte[10];
        int len;
        while((len = fis.read(bytes))!=-1){
            fos.write(bytes,0,len);
        }
        fos.close();
        fis.close();
        long end = System.currentTimeMillis();
        System.out.println("文件流复制花费时间:"+(end-start));
    }
}

字符流

  • Reader
  • Writer
FileReader/Writer

练习:使用FileReader和Writer复制文件

注意:字符流和字节流的使用过程是一样的,唯一不同的地方在于字节流是使用byte数组接受读取的数据,而字符流是使用char数组接受读取的数据

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;

public class Demo2 {
    public static void main(String[] args) throws IOException {
        //创建第一个文件
        File srcfile = new File("沁园舂 雪.txt");
        srcfile.createNewFile();
        String string = "沁园春·雪\n" +
                "作者:毛泽东\n" +
                "\n" +
                "北国风光,千里冰封,万里雪飘。\n" +
                "望长城内外,惟余莽莽;大河上下,顿失滔滔。\n" +
                "山舞银蛇,原驰蜡象,欲与天公试比高。\n" +
                "须晴日,看红装素裹,分外妖娆。\n" +
                "江山如此多娇,引无数英雄竞折腰。\n" +
                "惜秦皇汉武,略输文采;唐宗宋祖,稍逊风骚。\n" +
                "一代天骄,成吉思汗,只识弯弓射大雕。\n" +
                "俱往矣,数风流人物,还看今朝。";
        //在第一个文件中写入数据
        FileWriter fw = new FileWriter(srcfile);
        fw.write(string);
        fw.close();

        //创建第二个文件
        File destfile = new File("沁园春.txt");
        //创建读入流和写出流
        FileReader fr = new FileReader(srcfile);
        FileWriter fw2 = new FileWriter(destfile);
        //字符流需要用char字符数组进行读写
        char[] chars = new char[1024];
        int len;
        while((len=fr.read(chars))!=-1){
            fw2.write(chars);
        }
        //关闭流
        fr.close();
        fw2.close();
    }
}
BufferedReader/Writer

字符缓冲流,性质和用法与字节缓冲流相似

练习:使用字符缓冲流复制文件

import java.io.*;

public class Demo3 {
    public static void main(String[] args) throws IOException {
        //创建File对象
        File srcfile = new File("沁园舂 雪.txt");
        File destfile = new File("沁园春 梅.txt");
        //创建FileReader和FIleWriter,然后创建BufferedReader和BufferedWriter
        BufferedReader br = new BufferedReader(new FileReader(srcfile));
        BufferedWriter bw = new BufferedWriter(new FileWriter(destfile));
        //读写
        //方法一使用数组读写
        char[] chars = new char[10];
        int len;
        while ((len=br.read(chars))!=-1){
            bw.write(chars,0,len);
        }
        bw.close();
        br.close();

        //方法二使用readLine方法
        File srcfile2 = new File("沁园舂 雪.txt");
        File destfile2 = new File("沁园春 兰.txt");
        BufferedReader br2 = new BufferedReader(new FileReader(srcfile2));
        BufferedWriter bw2 = new BufferedWriter(new FileWriter(destfile2));
        String data;
        while((data=br2.readLine())!=null){
            bw2.write(data);
            bw2.newLine();
        }
        //关闭流
        bw2.close();
        br2.close();
    }
}

案例:集合到文件

需求:键盘录入5个学生信息(姓名,语文,数学,英语成绩)。要求按照成绩总分从高到低写入文本文件

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.Buffer;
import java.util.Comparator;
import java.util.Scanner;
import java.util.TreeSet;

public class Demo14 {
    public static void main(String[] args) throws IOException {
        //创建TreeSet集合,通过比较器排序
        TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                int num1 = o2.getSum() - o1.getSum();
                int num2 = num1==0?o2.getYuwen()-o1.getYuwen():num1;
                int num3 = num2==0?o2.getShuxue()-o1.getShuxue():num2;
                int num4 = num3==0?o2.getName().compareTo(o1.getName()):num3;
                return num4;
            }
        });
        //键盘录入数据
        for (int i=0;i<3;i++){
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入第"+(i+1)+"个同学的信息:");
            System.out.print("姓名:");
            String name = sc.nextLine();
            System.out.print("语文成绩:");
            int chinese = sc.nextInt();
            System.out.print("数学成绩:");
            int math = sc.nextInt();
            System.out.print("英语成绩:");
            int english = sc.nextInt();

            Student stu = new Student(name,chinese,math,english);
            ts.add(stu);
        }

        //创建字符缓冲输出流对象
        BufferedWriter bw = new BufferedWriter(new FileWriter("achievement.txt"));

        //遍历集合得到每个学生对象
        String str = "姓名 语文 数学 英语 总分\n";
        bw.write(str);
        bw.flush();
        for (Student stu:ts){
            StringBuilder sb = new StringBuilder();
            sb.append(stu.getName()+" ").append(stu.getYuwen()+" ").append(stu.getShuxue()+" ").append(stu.getYingyu()+" ").append(stu.getSum());
            bw.write(sb.toString());
            bw.newLine();//换行
            bw.flush();
        }
        //释放资源
        bw.close();
    }
}

练习:分别使用节点流:FileInputStream、FIleOutputStream和缓冲流:BufferedInputStream、BufferedOutputStream实现文本文件/图片/视频的肤质,并比较在数据复制方面的效率

import java.io.*;

public class Demo4 {
    public static void main(String[] args) throws IOException {
        File src = new File("齐天大圣.mp4");
        File dest = new File("美猴王.mp4");
        File dest2 = new File("弼马温.mp4");
        copy1(src,dest);
        copy2(src,dest2);
    }

    private static void copy2(File src, File dest) throws IOException {
        long start = System.currentTimeMillis();
        FileInputStream fis = new FileInputStream(src);
        FileOutputStream fos = new FileOutputStream(dest);
        BufferedInputStream bis = new BufferedInputStream(fis);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        byte[] bytes = new byte[1024];
        int len;
        while ((len=bis.read(bytes))!=-1){
            bos.write(bytes);
        }
        long end = System.currentTimeMillis();
        System.out.println("缓冲(处理)流的复制速度为:"+(end-start));
    }

    public static void copy1(File src, File dest) throws IOException {
        long start = System.currentTimeMillis();
        FileInputStream fis = new FileInputStream(src);
        FileOutputStream fos = new FileOutputStream(dest);
        byte[] bytes = new byte[1024];
        int len;
        while ((len=fis.read(bytes))!=-1){
            fos.write(bytes);
        }
        fos.close();
        fis.close();
        long end = System.currentTimeMillis();
        System.out.println("文件流的复制速度为:"+(end-start));
    }

}
/*
文件流的复制速度为:197
缓冲(处理)流的复制速度为:44
*/

练习:实现图片加密操作

import java.io.*;
import java.nio.Buffer;

public class Demo5 {
    public static void main(String[] args) throws IOException {
        int pass = 2;
        File src = new File("jiaoyuan.png");
        File dest = new File("教员.png");
        encryption(src,dest);
    }

    //加密复制一个图片
    private static void encryption(File src, File dest) throws IOException {
        FileInputStream fis = new FileInputStream(src);
        FileOutputStream fos = new FileOutputStream(dest);
        BufferedInputStream bis = new BufferedInputStream(fis);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        int date;
        while((date=bis.read())!=-1){
            bos.write(date*5);
        }
        bis.close();
        bos.close();
    }
}

练习:获取文本上每个字符出现的次数

提示:遍历文本的每一个字符,以及出现的次数保存在Map中,将Map中数据写入文件

import sun.awt.SunHints;

import java.io.*;
import java.nio.BufferOverflowException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public class Demo6 {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("沁园春.txt");
        FileWriter fw = new FileWriter("统计.txt");
        BufferedReader br = new BufferedReader(fr);
        BufferedWriter bw = new BufferedWriter(fw);
        //创建TreeMap
        Map<Character,Integer> map = new HashMap<>();
        int i=0;
        while((i = br.read())!=-1){
            char c = (char) i;
            if (map.get(c)==null){
                map.put(c,1);
            }else{
                map.put(c,map.get(c)+1);
            }
        }
        Set<Map.Entry<Character,Integer>> setKey = map.entrySet();
        for (Map.Entry<Character, Integer> key:setKey){
            switch (key.getKey()){
                case ' ':
                    bw.write("空格:"+key.getValue());
                    break;
                case '\t':
                    bw.write("tab:"+key.getValue());
                    break;
                case '\n':
                    bw.write("换行:"+key.getValue());
                    break;
                case '\r':
                    bw.write("回车:"+key.getValue());
                    break;
                default:
                    bw.write(key.getKey()+":"+key.getValue());
                    break;
            }
            bw.newLine();
        }
        br.close();
        bw.close();
    }
}

转换流

InputStreamReader和OutputStreamWriter 属于字符流,使用char[]数组接受数据

  • 转换流提供了字节流和字符流之间的转换
  • JavaAPI提供了两个转换流:
    • InputStreamReader:将InputStream转换为Reader,将一个字节的输入流转换为字符的输入流
    • OutputStreamWriter:将Writer转换为OutputStream,将一个字符的输出流转换为字节的输出流
  • 字节流中的数据都是字符时,转换成字符流操作更高效
  • 很多时候我们使用转换流来处理文件乱码问题。实现编码和解码的功能
InputStreamReader

将FileInputStream byte[] 的输入转化为char[] 的输入

import java.io.*;

/*
    @author 小码农
    @create 2021年8月2日20:02:28
 */
public class Demo7 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("沁园春.txt");
        //InputStreamReader 第二个参数为字符集类型
        InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
        char[] chars = new char[20];
        int len;
        while ((len=isr.read(chars))!=-1){
            String str = new String(chars,0,len);
            System.out.println(str);
        }
        isr.close();
    }
}
OutputStreamWriter

可以将FileWriter 的char[] 转化为 FileOutputStream的byte[]输出

字符集

  • ASCii:美国标准信息交换码
  • Unicode:国际标准码表
  • iso-8859-1:拉丁码表
  • UTF-8:变长的编码方式。它可以使用1~4个字节表示一个符号
  • GBK:汉字内码扩展规范

其它流

标准输入输出流
  • System.in:标准输入流,默认从键盘输入
  • System.out:标准输出流,默认从键盘输出

可以使用System类的setIn(InputStream is)/setOut(OtputStream os)重新指定输入和输出的流

练习:从键盘输入字符串,要求将读取到的整行字符串转化成大写输出。然后继续进行输入操作

输入exit退出

方法一:使用Scanner实现

方法二:使用System.in实现。System.in>>>InputStreamReader>>>BufferedReader 的readLine

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Demo9 {
    public static void main(String[] args) throws IOException {
        InputStreamReader isr = null;
        try{
            isr = new InputStreamReader(System.in);
            BufferedReader br = new BufferedReader(isr);
            while(true){
                String data = br.readLine();
                if ("exit".equalsIgnoreCase(data)){
                    System.out.println("程序结束");
                }else {
                    String upperCase = data.toUpperCase();
                    System.out.println(upperCase);
                }
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            isr.close();
        }
    }
}
打印流
  • PrintStream 字节流
  • PrintWriter 字符流

数据流

  • DataInputStream
  • DataOutputStream
  • 为了方便地操作Java语言的基本数据类型和String的数据,可以使用数据流
  • 用于读取或写出基本数据类型的变量或字符串
import java.io.*;

/*
    @author 小码农
    @create 2021年8月2日21:31:20
 */

public class Demo10 {
    public static void main(String[] args) {
        DataOutputStream dos = null;
        try {
            dos = new DataOutputStream(new FileOutputStream("data.txt"));
            dos.writeUTF("刘星");
            dos.flush();
            dos.writeInt(13);
            dos.flush();
            dos.writeBoolean(true);
            dos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (dos != null) {
                try {
                    dos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        //读取的顺序要和写入的顺序对应
        DataInputStream dis = null;
        try {
            dis = new DataInputStream(new FileInputStream("data.txt"));
            try {
                String s = dis.readUTF();
                int i = dis.readInt();
                boolean b = dis.readBoolean();
                System.out.println("name:"+s);
                System.out.println("age:"+i);
                System.out.println("bool:"+b);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            try {
                dis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

IO流总结

流的三种分类方式

  • 流向:输入流、输出流
  • 数据单位:字节流(byte)、字符流(char)
  • 流的角色:节点流(File)、处理流(Buffer)

写出4个IO流中的抽象基类,4个文件流,4个缓冲流

  • 4个抽象基类:InputStream、OutputStream、Reader、Writer
  • 4个文件流:FileInputStream、FileOutputStream、FileReader、FileWriter
  • 4个缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter

字节流与字符流的区别和选择

  • 字节流:read(byte[] b) /read() 处理非文本文件时使用/如果只是对文本文件的搬运(不看),字节流也可以
  • 字符流:read(char[] c) / read() 处理文本文件时使用

缓冲流和文件流的区别:缓冲流把读入数据先放在缓冲区中,累计够量后才写入文件,减少了写入次数,从而提高文件复制的速度。

转换流是哪两个类,分别的作用是什么?

  • InputStreamReader:将一个字节输入转化为字符输入。 解码
  • OutputStreamWriter:将输出的字符流转化为输出的字节流。 编码
  • InputStreamReader isr = new InputStreamReader(new FileInputStream(path),“源文件编码类型”);
  • OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(path),“目标文件编码类型”);

复习

输入过程
  1. 创建File对象,指明读取的数据的来源,源文件一定要存在
  2. 创建相应的输入流,将File对象作为参数,传入流的构造器中
  3. 使用对应的byte或char数组读入数据
  4. 关闭流资源,程序中出现的异常要用try catch finally处理
输出过程
  1. 创建File对象,指明输出的文件地址
  2. 创建相应的输出流,将File对象作为参数,传入构造器中
  3. 使用write方法,将存放在byte或char数组中的数据写入流对象中。write(byte/char[] b/c,0,len)
  4. 关闭流资源,程序中出现的异常要用try catch finally处理
FileReader
  1. 创建File对象,指明读取的数据的来源,源文件一定要存在
  2. 创建FileReader字符输入流,将File对象作为参数,传入流的构造器中
  3. 使用char数组接受数据
  4. 关闭流资源
FileWriter
  1. 创建File对象,指明写入文件的位置,文件不是必须存在,如果不存在则创建新文件
  2. 创建FileWriter字符输出流,将File对象作为参数,传入流的构造器中,FileWriter有两个参数,第一个参数是写出的File对象,第二个参数是是否追加写入。true追加写入,false覆盖。FileWriter(File file,true/false)
  3. 使用write方法对writer对象写入数据
  4. 关闭流资源
FileInputStream
  1. 创建File对象,指明读取的数据的来源,源文件一定要存在
  2. 创建FileInputStream字节输入流,将File对象作为参数,传入流的构造器中
  3. 使用byte数组接受数据
  4. 关闭流资源
FileOutputStream
  1. 创建File对象,指明写入文件的位置,文件不是必须存在,如果不存在则创建新文件
  2. 创建FileOutputStream字符输出流,将File对象作为参数,传入流的构造器中
  3. 使用write方法对FileOutputStream对象写入数据
  4. 关闭流资源
BufferedInputStream
  1. 创建File对象,指明读取的数据的来源,源文件一定要存在
  2. 创建FileInputStream字节输入流,将File对象作为参数,传入流的构造器中
  3. 创建BufferedInputStream缓冲流对象,将FIleInputStream对象作为参数传入
  4. 使用byte数组接受读入数据
  5. 关闭资源
BufferedOutputStream
  1. 创建File对象,指明写入文件的位置,文件不是必须存在,如果不存在则创建新文件
  2. 创建FileOutputStream字符输出流,将File对象作为参数,传入流的构造器中
  3. 创建BufferedOutputStream缓冲流对象,将FileOutputStream对象作为参数传入
  4. 使用write方法对BufferedOutputStream对象写入数据,或者使用readLine方法一次读取一行数据
  5. 关闭资源
BufferedReader
  1. 创建File对象,指明读取的数据的来源,源文件一定要存在
  2. 创建FileReader字节输入流,将File对象作为参数,传入流的构造器中
  3. 创建BufferedReader缓冲流对象,将FileReader对象作为参数传入
  4. 使用char数组接受读入数据
  5. 关闭资源
BufferedWriter
  1. 创建File对象,指明写入文件的位置,文件不是必须存在,如果不存在则创建新文件
  2. 创建FileWriter字符输出流,将File对象作为参数,传入流的构造器中
  3. 创建BufferedWriter缓冲流对象,将FileWriter对象作为参数传入
  4. 使用write方法对BufferedWriter对象写入数据
  5. flush方法,使用该方法直接将缓冲区的数据写出,并清空缓冲区。缓冲区在存满后自动会flush,所以可以不写。
  6. 关闭资源
InputStreamReader

转换流,将一个字节的输入流转换为字符的输出流。解码

  1. 创建File对象,指明读取的数据的来源,源文件一定要存在

  2. 创建FileInputStream字节输入流,将File对象作为参数,传入流的构造器中

  3. 创建InputStreamReader转换输入流,将FIleInputStream对象传入,并传入源文件的编码类型。

    InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
    
  4. 使用char数组存放读取的数据

  5. 关闭流资源

OutputStreamWriter

讲一个字符的输入流转换为字节的输出流。编码

  1. 创建File对象,指明读取的数据的来源,源文件一定要存在

  2. 创建FileOutputStream字节输出流,将File对象作为参数,传入流的构造器中

  3. 创建OutputStreamWriter转换输出流,将FileOutputStream对象传入,并传入目标文件的编码类型。

    OutputStreamWriter osw = new OutputStreamWriter(fos,"GBK");
    
  4. 使用write方法对输出流对象写入数据

  5. 关闭流资源

System.in

标准的输入流,默认从键盘输入,可以使用setIn(InputStream is)方法修改重新指定输入流。

System.out

标准输出流,默认控制台输出。可以使用setOut(PrintSteam ps)方法修改重新指定输出流。

Print-Stream/Writer

提供了一些列重载的print()和println()的方法,用于多种类型的输出

System.out返回的是PrintStream的实例

Data-Input/Output-Stream

用于读取或写出基本数据类型的变量或字符串

2021年8月3日14:55:05

对象流

ObjectInputStream/ObjectOutputStream

**作用:**用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

  • 序列化:用ObjectOutputStream类保存基本类型数据或对象的机制
  • 反序列化:用ObjectInputStream类读取基本类型数据或对象的机制
  • ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
对象序列化机制

对象序列化机制允许把内存中的Java对象转化成二进制流,然后把这种二进制流保存在本地磁盘上或通过网络将这种二进制流传输到另一个网络节点。当其他程序获取了这种二进制流,就可以恢复成原来的Java对象。

import java.io.*;

/**
 * 对象流的使用
 * ObjectInputStream ObjectOutputStream
 * @author: 小码农
 * @create: 2021-08-03 15:51
 **/
public class Demo1 {
    public static void main(String[] args) {
        //序列化过程:将内存中的对象保存到磁盘中或通过网络传输出去
        ObjectOutputStream oos = null;
        //反序列化
        ObjectInputStream ois = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
            String s = new String("Hello World");
            oos.writeObject(s);
            oos.flush();
            //反序列化过程
            ois = new ObjectInputStream(new FileInputStream("object.txt"));
            Object obj = ois.readObject();
            String str = (String) obj;
            System.out.println(str);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            if (oos!=null){
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (ois!=null){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

可序列化对象需要满足的要求

  • 序列化对象类需要实现Serializable接口,Serializable接口是一个表示类,实现该接口的类都可以实现序列化

  • 在被序列化的类中,需要当前类提供一个全局常量:serialVersionUID,值随意

    public/private static final long serivalVersionUID = 235252461L;
    
  • 被序列化的类中如果使用到其他的自定义类,这些自定义类都必须是可序列化的

注意:ObjectOutputStream和ObjectInputStream无法序列化static和transient修饰的成员变量

自定义Person类序列化

import java.io.*;

/**
 * @author: 小码农
 * @create: 2021-08-03 17:21
 **/
public class Demo2 {
    public static void main(String[] args) {
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
            ois = new ObjectInputStream(new FileInputStream("object.txt"));
            Person p = new Person();
            //序列化
            p.setAge(11);
            p.setName("张飞");
            oos.writeObject(p);
            oos.flush();
            //反序列化
            Object o = ois.readObject();
            System.out.println(o.toString());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            if (oos!=null){
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (ois!=null){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Person类

import java.io.Serializable;

/**
 * @author: 小码农
 * @create: 2021-08-03 17:20
 **/
public class Person implements Serializable {
    private static final long serivalVersionUID = 235125125L;
    private String name;
    private int age;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

随机存取文件流

  • RandomAccessFile直接继承java.long.Object类,实现了DataInput、DataOutput两个接口,这个类既可以读也可以写。
  • RandomAccessFile支持随机访问的方式,程序可以直接跳到文件的任意地方来读、写文件,支持只访问文件的部分内容,可以向已存在的文件追加内容
  • RandomAccessFile对象包含一个记录指针,用以标示当前读写处的位置
  • RandomAccessFile对象可以自由移动记录指针。包含两个方法:long getFilePointer()获取文件记录指针的当前位置。void seek(long pos)将文件记录指针定位到pos位置
  • 构造器:public RandomAccessFile(File file,String mode),public RandomAccessFile(String name,String mode)
  • 创建RandomAccessFile类需要指定一个mode参数,改参数指定RandomAccessFile的访问模式
    • r:以只读方式打开
    • rw:打开以便读取和写入
    • rwd:打开以便读取和写入;同步文件内容的更新
    • rws:打开以便读取和写入;同步文件内容和元数据的更新
  • 如果模式为只读r,则不会创建文件,而是会去读取一个已经存在的文件,如果文件不存在会报异常。如果模式为rw读写,如果文件不存在则会创建文件,存在则不会创建。
  • 如果RandomAccessFile作为输出流时,写出的文件如果不存在,则在执行过程中自动创建文件,如果写出的文件存在,则会对原有的文件内容进行覆盖。(默认为从头覆盖)
  • seek方法,可以指定文件中添加数据的位置。seek(int i)
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * @author: 小码农
 * @create: 2021-08-03 18:17
 **/
public class Demo3 {
    public static void main(String[] args) {
        RandomAccessFile raf1= null;
        RandomAccessFile raf2= null;
        try {
            raf1 = new RandomAccessFile("毛主席.png","r");
            raf2 = new RandomAccessFile("毛主席2.png","rw");
            byte[] bytes = new byte[1024];
            int len;
            while ((len=raf1.read(bytes))!=-1){
                raf2.write(bytes,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (raf1!=null){
                try {
                    raf1.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (raf2!=null){
                try {
                    raf2.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * @author: 小码农
 * @create: 2021-08-03 18:17
 **/
public class Demo4 {
    public static void main(String[] args) {
        RandomAccessFile raf1= null;
        RandomAccessFile raf2= null;
        try {
            raf1 = new RandomAccessFile("Hello.txt","r");
            raf2 = new RandomAccessFile("Hello2.txt","rw");
            byte[] bytes = new byte[1024];
            int len;
            while ((len=raf1.read(bytes))!=-1){
                raf2.write(bytes,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (raf1!=null){
                try {
                    raf1.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (raf2!=null){
                try {
                    raf2.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

使用指针seek,插入内容到指定位置

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * @author: 小码农
 * @create: 2021-08-03 19:41
 **/
public class Demo5 {
    public static void main(String[] args) {
        String src = "Hello.txt";
        insert(src);
    }
    //覆盖方法
    public static void fugai(String path){
        RandomAccessFile raf1 = null;
        try {
            raf1 = new RandomAccessFile(path,"rw");
            raf1.seek(3);   //将指针调到第3个的位置,追加只需要把3改为raf1.length即可
            raf1.write("xyz".getBytes());
        }catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                raf1.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    //插入方法
    public static void insert(String path){
        RandomAccessFile raf1 = null;
        try {
            raf1 = new RandomAccessFile(path,"rw");
            raf1.seek(3);
            StringBuilder sb = new StringBuilder((int) new File(path).length());
            byte[] b = new byte[1024];
            int len;
            while ((len = raf1.read(b))!=-1){
                sb.append(new String(b,0,len));
            }
            //调回指针,将需要添加内容插入
            raf1.seek(3);
            raf1.write("abcde".getBytes());
            /*插入完成后,指针默认在尾部,直接使用write方法写入我们保存的原始
            数据,实现插入的效果*/
            raf1.write(sb.toString().getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (raf1!=null){
                try {
                    raf1.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

线程、同步

Thread类

  • getName()获取线程的名称
  • currentThread()方法返回当前线程
  • setName(String name)设置线程名称

创建多线程程序的方法一:继承类Thread

/**
 * @author: 小码农
 * @create: 2021-08-03 20:22
 **/
public class Demo6 {
    public static void main(String[] args) {
        Thread_test t1 = new Thread_test();
        t1.setName("小狗熊");
        t1.start();
        Thread_test t2 = new Thread_test();
        t2.start();
    }
}
/**
 * @author: 小码农
 * @create: 2021-08-03 20:47
 **/
public class Thread_test extends Thread {
    //重写run方法
    @Override
    public void run() {
        System.out.println(Thread.currentThread());
        System.out.println(this.getName());
    }
}

Runnable接口

创建多线程程序的方法二:实现Runnable接口

步骤:

  1. 创建一个Runable接口的实现类
  2. 在实现类中重写Runable接口的run方法,设置线程任务
  3. 创建一个Runnable接口的实现类对象
  4. 创建Thread对象,构造方法中传递Runnable接口的实现类对象
  5. 调用Thread类中的start方法,开启新的线程执行run方法
/**
 * @author: 小码农
 * @create: 2021-08-05 13:33
 **/
public class RunnableImpl implements Runnable {
    @Override
    public void run() {
        for (int i=0;i<20;i++){
            System.out.println(Thread.currentThread().getName()+"---->"+i);
        }
    }
}
/**
 * @author: 小码农
 * @create: 2021-08-05 13:34
 **/
public class Demo1 {
    public static void main(String[] args) {
        RunnableImpl ri = new RunnableImpl();
        Thread t = new Thread(ri);
        t.start();

        for (int i=0;i<20;i++){
            System.out.println(Thread.currentThread().getName()+"---->"+i);
        }
    }
}

继承Thread类和实现Runnable接口创建多线程的区别:

  • 一个类只能继承一个父类,如果继承Thread类,就无法继承其它父类
  • 如果实现Runnable接口,则还可以继续继承其它类

如果使用多线程,一般使用实现Runnable接口的方法

匿名内部类

匿名内部类:写在其它类的内部,没有名字的类

使用格式:

new 父类/接口(){
	重写父类/接口的方法;
}
/**
 * @author: 小码农
 * @create: 2021-08-05 13:42
 **/
public class Demo2 {
    public static void main(String[] args) {
        //使用Thread创建匿名内部类
        new Thread(){
            @Override
            public void run(){
                for (int i=0;i<20;i++){
                    System.out.println(Thread.currentThread().getName()+"---->"+i);
                }
            }
        }.start();
        //使用Runnable接口的匿名实现类
        new Thread(new Runnable(){
            @Override
            public void run() {
                for (int i=0;i<20;i++){
                    System.out.println(Thread.currentThread().getName()+"---->"+i);
                }
            }
        }).start();
    }
}

线程安全

多线程程序访问共同的数据源,有可能会出现线程安全问题。

线程同步

  • 当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。
  • 要解决多线程并发访问一个资源的安全性问题,java提供了同步机制来解决
  • 有三种方式完成同步操作:
    • 同步代码块
    • 同步方法
    • 锁机制

同步代码块:synchronized关键字可以用于方法中的某个区块中,表示对这个区块的资源实行互斥访问。同一时间只有一个线程可以获取锁对象执行run方法。当其中一个线程获取锁对象执行run方法时,其它线程执行run方法没有锁对象会进入阻塞状态,直到上一个线程执行完run方法归还锁对象,下一个线程才获取锁对象并执行run

synchronized(同步锁对象){
	需要同步的代码;
}
import java.util.concurrent.SynchronousQueue;

/**
 * @author: 小码农
 * @create: 2021-08-05 13:53
 * 使用同步代码块解决线程安全问题
 **/
public class Runnable3 implements Runnable {
    private int ticket = 20;
    //创建一个锁对象
    Object o = new Object();
    //买票
    @Override
    public void run() {
        while (true) {
            synchronized (o){
                if (ticket>0){
                    System.out.println("票号:"+ticket);
                    ticket--;
                }
            }
        }
    }
}

同步方法:通过synchronized修饰符修饰方法,同步方法是将实现类的对象锁住,谁调用run方法就将该对象锁住

synchronized 返回值类型 方法名(参数){
	方法内容;
}
/**
 * @author: 小码农
 * @create: 2021-08-05 13:53
 * 使用同步方法解决线程安全问题
 **/
public class Runnable4 implements Runnable {
    private int ticket = 20;
    //买票
    @Override
    public void run() {
        sell();
    }
    //定义一个同步方法
    public synchronized void sell(){
        while (true) {
            if (ticket>0){
                System.out.println("票号:"+ticket);
                ticket--;
            }
        }
    }
}
/**
 * @author: 小码农
 * @create: 2021-08-05 15:10
 **/
public class Demo4 {
    public static void main(String[] args) {
        Runnable4 r = new Runnable4();
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();
    }
}

Lock锁

Lock锁机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有。
Lock锁也成为了同步锁,加锁与释放锁的方法:

  • public void lock():加同步锁
  • public void unlock():释放同步锁

使用步骤:

  • 在成员位置创建一个ReentrantLock对象
  • 在可能会出现安全问题的代码前调用lock接口中的lock方法获取锁
  • 在可能会出现安全问题的代码前后调用lock接口中的unlock方法释放锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author: 小码农
 * @create: 2021-08-05 13:53
 * Lock锁
 * lock()获取锁
 * unlock()释放锁
 **/
public class Runnable5Lock implements Runnable {
    private int ticket = 20;
    Lock l = new ReentrantLock();
    //买票
    @Override
    public void run() {
        sell();
    }
    //定义一个同步方法
    public synchronized void sell(){
        while (true) {
            //加lock锁
            l.lock();
            if (ticket>0){
                System.out.println(Thread.currentThread().getName()+"票号:"+ticket);
                ticket--;
            }
            //释放lock锁
            l.unlock();
        }
    }
}
/**
 * @author: 小码农
 * @create: 2021-08-05 15:10
 **/
public class Demo5 {
    public static void main(String[] args) {
        Runnable5Lock r = new Runnable5Lock();
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();
    }
}

线程状态

  • New:新建状态,至今尚未启动的线程处于这种状态
  • Runnable:运行状态,正在Java虚拟机中执行的线程处于这种状态
  • Blocked:阻塞状态,当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程获得锁时,将变成Runnable状态
  • Waiting:无限等待状态,一个线程在等待另一个线程执行一个唤醒动作时,该线程进入Waiting状态,进入这个状态后无法自动唤醒,必须等待另一个线程调用notify或者notifyAll方法才能唤醒
  • TimedWaiting:和waiting一样,有几个方法有超时参数,调用他们将进入Timed Waiting状态,该状态将一直保持到超时期满或者收到唤醒通知。带有超时参数的常用方法有Thread.sleep、Object.wait
  • Terminated:被终止,因为run方法正常退出而死亡,或因为没有捕获的异常终止了run方法而死亡

练习:线程的睡眠sleep()和唤醒notify()

/**
 * @author: 小码农
 * @create: 2021-08-05 15:44
 * 线程状态练习:等待--唤醒
 **/
public class Demo6 {
    public static void main(String[] args) {
        //创建对象
        Object o = new Object();
        //使用匿名内部类创建顾客线程
        new Thread(){
            @Override
            public void run() {
                while (true){
                    synchronized (o){
                        System.out.println("顾客:一个肉包");
                        try {
                            o.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //唤醒后执行的代码
                    System.out.println("顾客:开吃");
                    System.out.println("-- -- -- -- -- -- -- --");
                }
            }
        }.start();

        //创建老板线程
        new Thread(){
            @Override
            public void run() {
                while (true) {
                    //做包子5s
                    try {
                        System.out.println("老板:做包子需要5秒钟");
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (o){
                        System.out.println("老板:包子做好了");
                        o.notify();
                    }
                }
            }
        }.start();
    }
}

线程进入TimeWaiting(计时等待)的两种方式

  • sleep(long m):在毫秒值结束后,自动唤醒进入Runnable或Blocked状态
  • wait(long m):如果在毫秒值结束后,还没有被notify唤醒,就会自动醒来,进入Runnable或Blocked状态

唤醒线程的方法:

  • notify():唤醒一个线程
  • notifyAll():唤醒所有等待的线程

网络编程

概述

网络编程三要素:IP地址、端口号、协议

InetAddress类:为了方便我们对ip地址的获取和操作,Java提供了一个InetAddress类供我们使用

  • static InetAddress getByName(String host) 确定主机名称的ip地址,主机名称可以是机器名称或ip地址
  • String getHostName() 获取此ip地址的主机名
  • String getHostAddress() 返回文本显示中的ip地址字符串
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * @author: 小码农
 * @create: 2021-08-05 16:33
 **/
public class Demo8 {
    public static void main(String[] args) {
        //InetAddress类
        try {
            InetAddress address = InetAddress.getByName("192.168.249.67");
            String hostName = address.getHostName();
            String ipAddress = address.getHostAddress();
            System.out.println("主机名:"+hostName);
            System.out.println("ip地址:"+ipAddress);
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }
}
/*
    主机名:DESKTOP-7CFI8Q7
    ip地址:192.168.249.67
*/

端口:设备上应用程序的唯一标识

端口号:用两个字节表示的整数,取值范围065535。其中01023用于一些知名的网络服务。普通应用程序使用1024~65535之间的端口号。如果端口号被另外一个服务占用,会导致当前程序启动失败

TCP协议:可靠,三次握手

  • 客户端向服务器端发送连接请求,等待服务器确认
  • 服务器端向客户端回复一个响应,通知客户端收到了连接请求
  • 客户端再次向服务器端发送确认信息,确认连接

UDP协议:不可靠传输,它在通信两端各建立一个Socket对象。Java提供了DatagramSocket类作为UDP协议的Socket

UDP

UDP发送数据

步骤:

  • 使用DatagramSocket创建发送端对象,发送端对象不用带端口号参数

    DatagramSocket ds = new DatagramSocket();
    
  • 构建存放发送数据的字节数组

    byte[] data = new byte[1024];
    
  • 使用DatagramPacket创建数据包对象,参数包括:数据,数据长度,InetAddress主机对象,端口号

    DatagramPacket dp = new DatagramPacket(data,data.length,InetAddress.getByName("192.168.249.67"),10086);
    
  • 调用DatagramSocket对象的send方法发送数据,参数为数据包对象

    ds.send(dp);
    
  • 关闭发送端

    ds.close();
    

DatagramSocket类

DatagramPacket(byte[] b,int len,InetAddress add,int port)

构造一个数据包,发送长度为length的数据包到指定主机上的指定端口号

import java.io.IOException;
import java.net.*;

/**UDP发送端
 * @author: 小码农
 * @create: 2021-08-05 17:12
 **/
public class Demo9 {
    //创建Socket对象
    DatagramSocket ds;
    {
        try {
            ds = new DatagramSocket();
            //创建数据
            byte[] bytes = "Hello World".getBytes();
            //调用send方法发送数据
            DatagramPacket dp = new DatagramPacket(bytes,bytes.length,InetAddress.getByName("192.168.249.67"),10086);
            ds.send(dp);
            //关闭发送端
            ds.close();
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
UDP接收数据

步骤:

  • 创建接收端的Socket对象(DatagramSocket),UDP接收端的Socket对象需要传入端口号参数

    DatagramSocket ds = new DatagramSocket(10086);
    
  • 创建一个字节数组,用于接收数据

    byte[] bytes = new byte[1024];
    
  • 调用DatagramSocket对象的方法并接收数据,构建DatagramPacket对象传入参数:容器,容器长度

    DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
    ds.receive(dp);
    
  • 解析数据包,并把数据包在控制台显示

    String str = new String(dp.getData(),0,dp.getLength());
    System.out.println("接收到的数据:"+str);
    
  • 关闭接收端

    ds.close();
    
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
 * @author: 小码农
 * @create: 2021-08-05 17:24
 **/
public class Demo10 {
    public static void main(String[] args) throws IOException {
        //创建接收端对象
        DatagramSocket ds = new DatagramSocket(10086);
        byte[] bytes = new byte[1024];
        //创建数据包
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
        //接收数据
        ds.receive(dp);
        //解析数据包,得到接收到的整体的数据包,有可能包含空字符
//        byte[] bytes1 = dp.getData();
        //使用getLength方法的到接受到的数据长度
        String str = new String(dp.getData(),0,dp.getLength());
        System.out.println("接收到的数据:"+str);
        //关闭接收端
        ds.close();
    }
}

练习:UDP通信程序

UDP发送数据:数据来自于键盘录入,知道输入的数据是886,发送数据结束

UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收

TCP

TCP通信原理

TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象,从而在通信的两端形成网络虚拟链路,一旦建立了虚拟的网络链路,两端的程序就可以铜鼓哦虚拟链路进行通信

TCP发送数据

发送数据步骤:

  • 创建客户端的Socket对象(Socket)
Socket socket = new Socket(InetAddress.getByName("192.168.249.67"),10010);
Socket socket2 = new Socket("192.168.249.67",10010);//参数一可以直接使用接收方IP地址
  • 获取输入流,写数据
//获取输出流,写数据
OutputStream os = socket.getOutputStream();
byte[] bytes = "Hello World".getBytes();
os.write(bytes);
  • 释放资源
//释放资源
socket.close();
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * @author: 小码农
 * @create: 2021-08-05 19:43
 **/
public class Demo_TCP_Send {
    public static void main(String[] args) throws IOException {
        //构造方法1 Socket(主机名,端口)
        Socket socket = new Socket(InetAddress.getByName("192.168.249.67"),10010);
        //构造方法2 Socket(IP,端口)
        Socket socket2 = new Socket("192.168.249.67",10010);

        //获取输出流,写数据
        OutputStream os = socket.getOutputStream();

        byte[] bytes = "Hello World".getBytes();
        os.write(bytes);

        //释放资源
        socket.close();
        socket2.close();
    }
}
TCP接收数据

接收数据步骤:

  • 创建服务器端的Socket对象(ServerSocket)
//创建服务器端ServerSocket 构造方法:ServerSocket(端口)
ServerSocket ss = new ServerSocket(10010);
  • 监听客户端连接,返回一个Socket对象
//Socket accept()监听要连接到此套接字并接受它
Socket s = ss.accept();
  • 获取输入流,读数据,并输出
//获取输入流,读数据,并把数据显示在控制台
InputStream is = s.getInputStream();
byte[] bytes = new byte[1024];
int len = is.read(bytes);
String str = new String(bytes,0,len);
System.out.println("接收数据:"+str);
  • 释放资源
ss.close();
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author: 小码农
 * @create: 2021-08-05 19:51
 **/
public class Demo_TCP_receive {
    public static void main(String[] args) throws IOException {
        //创建服务器端ServerSocket 构造方法:ServerSocket(端口)
        ServerSocket ss = new ServerSocket(10010);

        //Socket accept()监听要连接到此套接字并接受它
        Socket s = ss.accept();
        //获取输入流,读数据,并把数据显示在控制台
        InputStream is = s.getInputStream();
        byte[] bytes = new byte[1024];
        int len = is.read(bytes);
        String str = new String(bytes,0,len);
        System.out.println("接收数据:"+str);
        //释放资源
        s.close();
        ss.close();
    }
}

中断输入输出流的方法:

中断输出流:Socket对象.shutdownOutput();

中断输入流:Socket对象.shutdownInput();

练习1:

  • 客户端:发送数据,接收服务器反馈。先写数据,再读数据
  • 服务器:接收数据,发送反馈。先读数据,再写数据

客户端

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;


/**
 * 客户端程序
 * @author: 小码农
 * @create: 2021-08-05 20:06
 **/
public class Demo12_Send {
    public static void main(String[] args) throws IOException {
        //创建客户端Socket对象
        Socket s = new Socket("192.168.249.67",10101);
        //获取输入流,写数据
        OutputStream os = s.getOutputStream();
        os.write("来自客户端的一条消息".getBytes());
        //接收服务器端反馈
        InputStream is = s.getInputStream();
        byte[] b = new byte[1024];
        int len = is.read(b);
        String str = new String(b,0,len);
        System.out.println("服务端反馈:"+str);
        //释放资源
        s.close();
    }
}

服务器端

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;


/**
 * 服务器端程序
 * @author: 小码农
 * @create: 2021-08-05 20:06
 **/
public class Demo12_Receive {
    public static void main(String[] args) throws IOException {
        //创建服务器端ServerSocket对象
        ServerSocket ss = new ServerSocket(10101);
        //监听客户端连接,返回一个Socket对象
        Socket s = ss.accept();
        //接收数据
        InputStream is = s.getInputStream();
        byte[] b = new byte[1024];
        int len = is.read(b);
        String str = new String(b,0,len);
        System.out.println("接收数据:"+str);

        //向客户端发送反馈
        OutputStream os = s.getOutputStream();
        os.write("接收完成".getBytes());

        //释放资源
        ss.close();

    }
}

练习2:

  • 客户端:将键盘数据数据发送到服务端,输入886时停止
  • 服务器:将接收到的数据打印在控制台

客户端

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

/**
 * @author: 小码农
 * @create: 2021-08-05 20:19
 **/
public class Demo13_send {
    public static void main(String[] args) throws IOException {
        Socket s = new Socket("192.168.249.67",10110);
        //数据来自键盘录入
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        //封装输出流对象,利用转换流OSW将获取的输入字节流,转化为字符输出流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        String str;
        while ((str = br.readLine())!=null){
            if (str.equals("886")){
                break;
            }
            bw.write(str);
            bw.newLine();
            bw.flush();
        }
        s.close();
    }
}

服务器端

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author: 小码农
 * @create: 2021-08-05 20:19
 **/
public class Demo13_receive {
    public static void main(String[] args) throws IOException {
        //创建服务器端ServerSocket对象
        ServerSocket ss = new ServerSocket(10110);
        //监听客户端连接,返回一个对应的Socket对象
        Socket s = ss.accept();
        //使用字符输入流来读取数据,利用转换流ISR将输入的字节转化为字符
        BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        String str;
        while ((str=br.readLine())!=null){
            if (str.equals("886")){
                break;
            }
            System.out.println(str);
        }
        ss.close();
    }
}

练习3:

  • 客户端:将键盘数据数据发送到服务端,输入886时停止
  • 服务器:将接收到的数据写入文本文件

客户端

import java.io.*;
import java.net.Socket;

/**
 * @author: 小码农
 * @create: 2021-08-05 20:42
 **/
public class Demo14_send {
    public static void main(String[] args) throws IOException {
        //第一步,创建Socket对象
        Socket s = new Socket("192.168.249.67",10110);
        //创建字符输出流对象
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));

        String str;
        while ((str=br.readLine())!=null){
            if (str.equals("886")){
                break;
            }
            bw.write(str);
            bw.newLine();
            bw.flush();
        }
        s.close();

    }
}

服务器端

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author: 小码农
 * @create: 2021-08-05 20:46
 **/
public class Demo14_receive {
    public static void main(String[] args) throws IOException {
        //创建ServerSocket对象
        ServerSocket ss = new ServerSocket(10110);
        //创建监听对象Socket
        Socket s = ss.accept();
        //创建File对象
        BufferedWriter bw = new BufferedWriter(new FileWriter("data.txt",true));
        //创建字符输入流对象
        BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        String str;
        while((str=br.readLine())!=null){
            if (str.equals("886")){
                break;
            }
            System.out.println("写入:"+str);
            bw.write(str);
            bw.newLine();
            bw.flush();
        }
        bw.close();
        ss.close();
    }
}

练习4:

  • 客户端:将文本文件数据发送到服务端
  • 服务器:将接收到的数据写入文本文件

在本练习中,服务端与练习3服务端相同,客户端不同

import java.io.*;
import java.net.Socket;

/**
 * @author: 小码农
 * @create: 2021-08-05 21:00
 **/
public class Demo15 {
    public static void main(String[] args) throws IOException {
        //第一步,创建Socket对象
        Socket s = new Socket("192.168.249.67",10110);
        //创建字符输出流对象
        BufferedReader br = new BufferedReader(new FileReader("data2.txt"));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));

        String str;
        while ((str=br.readLine())!=null){
            bw.write(str);
            bw.newLine();
            bw.flush();
        }
        s.close();
    }
}

练习5

  • 客户端:数据来自于文本文件,接收服务器反馈
  • 服务器:接收到的数据写入文本文件,给出反馈

客户端

import java.io.*;
import java.net.Socket;

/**
 * @author: 小码农
 * @create: 2021-08-05 21:08
 **/
public class Demo16_Client {
    public static void main(String[] args) throws IOException {
        //创建Socket对象
        Socket s = new Socket("192.168.249.67",10111);

        //创建字符输入流对象
        BufferedReader br = new BufferedReader(new FileReader("data2.txt"));
        //创建字符输出流对象
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        //创建输入流对象2,来接收服务器端的反馈
        BufferedReader br2 = new BufferedReader(new InputStreamReader(s.getInputStream()));
        String str;
        String str2;
        while ((str=br.readLine())!=null){
            bw.write(str);
            bw.newLine();
            bw.flush();
        }

        //中断输出流
        s.shutdownOutput();
        //接收服务器反馈
        str2 = br2.readLine();
        System.out.println(str2);
        //关闭资源
        bw.close();
        br.close();
        s.close();
    }
}

服务器端

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author: 小码农
 * @create: 2021-08-05 21:08
 **/
public class Demo16_Server {
    public static void main(String[] args) throws IOException {
        //创建ServerSocket对象
        ServerSocket ss = new ServerSocket(10111);
        //创建监听器
        Socket s = ss.accept();

        //创建字符输入流对象
        BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        //创建BufferedWriter2 用于将收到的数据写入文件
        BufferedWriter bw2= new BufferedWriter(new FileWriter("data.txt",true));
        //创建字符输出流对象,用于向客户端发送反馈信息
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        String str;
        while ((str=br.readLine())!=null){
            bw2.write(str);
            bw2.newLine();
            bw2.flush();
        }

        //给客户端反馈
        bw.write("已写入");
        bw.newLine();
        bw.flush();
        //关闭资源
        bw.close();
        bw2.close();
        ss.close();
    }
}

练习6

  • 客户端:数据来自于文本文件,接收服务器反馈
  • 服务器:接收到的数据写入文本文件,给出反馈。代码用线程进行封装,为每个客户端开启一个线程

ServerThread线程类

import java.io.*;
import java.net.Socket;

/**
 * @author: 小码农
 * @create: 2021-08-05 21:41
 **/
public class ServerThread implements Runnable {
    private Socket s;
    public ServerThread(Socket s){
        this.s=s;
    }
    @Override
    public void run() {
        //接收数据写到文本文件
        BufferedReader br = null;
        BufferedWriter bw = null;
        BufferedWriter bw2 = null;
        try {
            br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            //为了避免文件名重复,我们使用动态文件名的方式来构建目标文件名
            int cut = 0;
            File file = new File("data-"+cut+".txt");
            //如果文件存在,则进入循环,找到一个不存在的文件名后离开循环
            while (file.exists()){
                cut++;
                file = new File("data-"+cut+".txt");
            }
            bw = new BufferedWriter(new FileWriter(file));
            String str;
            while ((str=br.readLine())!=null){
                bw.write(str);
                bw.newLine();
                bw.flush();
            }
            //给出反馈
            bw2 = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
            bw2.write("文件上传成功");
            bw2.newLine();
            bw2.flush();
            //释放资源
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (br!=null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bw!=null){
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bw2!=null){
                try {
                    bw2.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

服务端

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author: 小码农
 * @create: 2021-08-05 21:38
 **/
public class Demo17_Server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(10111);
        while(true){
            //监听客户端连接,返回一个对应的Socket对象
            Socket s = ss.accept();
            //为每一个客户端开启一个线程
            new Thread(new ServerThread(s)).start();
        }
    }
}

客户端(可以多个)

import java.io.*;
import java.net.Socket;

/**
 * @author: 小码农
 * @create: 2021-08-05 21:38
 **/
public class Demo17_Client {
    public static void main(String[] args) throws IOException {
        //创建Socket对象
        Socket s = new Socket("192.168.249.67",10111);

        //创建字符输入流对象
        BufferedReader br = new BufferedReader(new FileReader("data2.txt"));
        //创建字符输出流对象
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        //创建输入流对象2,来接收服务器端的反馈
        BufferedReader br2 = new BufferedReader(new InputStreamReader(s.getInputStream()));
        String str;
        String str2;
        while ((str=br.readLine())!=null){
            bw.write(str);
            bw.newLine();
            bw.flush();
        }

        //中断输出流
        s.shutdownOutput();
        //接收服务器反馈
        str2 = br2.readLine();
        System.out.println(str2);
        //关闭资源
        bw.close();
        br.close();
        s.close();
    }
}

Lambda表达式

体验lambda表达式

需求:启动一个线程,在控制台输出:多线程程序启动了

方式1:

  • 定义一个类MyRunnable实现Runnable接口,重写run方法
  • 创建MyRunnable类的对象
  • 创建Thread类对象,把MyRunnable对象作为构造参数传递
  • 启动线程
/**
 * @author: 小码农
 * @create: 2021-08-06 10:38
 **/
public class RunnableDemo1 implements Runnable {
    @Override
    public void run() {
        System.out.println("多线程程序启动了");
    }
}
RunnableDemo1 r = new RunnableDemo1();
new Thread(r).start();

方式2:

  • 匿名内部类的方式改进
//匿名内部类改进
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("启动多线程");
    }
}).start();

方式3:

  • Lambda表达式的方式改进
//Lambda表达式改进
new Thread(() ->{
        System.out.println("启动多线程");
}).start();

可以看出,对比前两种方法,Lambda表达式更为简单快捷

格式

方式3代码分析

  • ():里面没有内容,可以看成是方法形式参数为空
  • ->:用箭头指向后面要做的事情
  • {}:包含一段代码,我们称之为代码块,可以看成是方法体中的内容

组成Lambda表达式的三要素:形式参数、箭头、代码块

  • 格式:(形参)->{代码块}
  • 形参:如果有多个参数,参数之间用逗号分隔;如果没有参数,留空
  • ->:由英文中画线和大于符号组成,固定写法,代表指向动作
  • 代码块:使我们具体要做的事情,也就是以前我们写的方法体内容

使用前提

  • 有一个接口
  • 接口中有且仅有一个抽象方法

练习1:

  • 定义一个接口Eatable,里面定义一个抽象方法:void eat();
  • 定义一个测试类EatableDemo,在测试类中提供两个方法
    • 一个方法时:useEatable(Eatable e)
    • 一个方法是主方法,在主方法中调用useEatable方法
/**
 * @author: 小码农
 * @create: 2021-08-06 10:54
 **/
public class Demo2 {
    public static void main(String[] args) {
        DemoEatable e= new DemoEatableImp();
        useEatable(e);

        //匿名内部类
        useEatable(new DemoEatable() {
            @Override
            public void eat() {
                System.out.println("Hello Java");
            }
        });

        //使用Lambda表达式
        useEatable(()->{
            System.out.println("Hello JavaSE");
        });
    }
    public static void useEatable(DemoEatable e){
        e.eat();
    }
}

练习2:

  • 定义一个接口Flyable,里面定义一个抽象方法void fly(String s)
  • 定义一个测试类FlyableDemo,在测试类中提供两个方法
    • 一个方法是:useFlyable(Flyable f)
    • 一个方法是主方法,在主方法中调用useFlyable方法
/**
 * @author: 小码农
 * @create: 2021-08-06 11:01
 **/
public class Demo3 {
    public static void main(String[] args) {
        //匿名内部类
        useFlyable(new Flyable() {
            @Override
            public void fly(String s) {
                System.out.println(s);
                System.out.println("向着蓝天飞翔");
            }
        });
        //Lambda表达式
        useFlyable((String s)->{
            System.out.println(s);
            System.out.println("向着蓝天飞翔");
        });
    }
    public static void useFlyable(Flyable f){
        f.fly("向着大地奔跑");
    }
}

练习3:

  • 定义一个接口Addable,里面定义一个抽象方法:int add(int x, int y);
  • 定义一个测试类(AddableDemo),在测试类中提供两个方法
    • 一个方法是:useAddable(Addable a)
    • 一个方法是主方法,在主方法中调用useAddable方法
/**
 * @author: 小码农
 * @create: 2021-08-06 11:10
 **/
public interface Addable {
    int add(int x,int y);
}
/**
 * @author: 小码农
 * @create: 2021-08-06 11:10
 **/
public class Demo4 {
    public static void main(String[] args) {
        useAddable((int x,int y)->{
            return x+y;
        });
    }
    public static void useAddable(Addable a){
        int sum = a.add(10,20);
        System.out.println(sum);
    }
}

省略模式

  • 带参的Lambda表达式,参数类型可以省略,但是有多个参数的情况下不能只省略一个
useAddable(( x , y )->{
    return x + y;
});
  • 如果参数有且只有一个,包裹参数的小括号也可以省略
useFlyable(s->{
    System.out.println(s);
});
  • 如果代码块语句只有一句,大括号和分号也可以省略
useFlyable(s -> System.out.println(s));
  • 如果代码块只有一句,且包含return,省略大括号和分号的同时,return也要省略。否则将会报编译错误
useAddable((x,y) -> x + y);

注意事项

  • 使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法

  • 必须有上下文环境,才能推导出Lambda对应的接口

    • 根据局部变量的赋值得知Lambda对应的接口:

      Runnable r = ()->System.out.println("Lambda");
      
    • 根据调用方法的参数得知Lambda对应的接口:

      new Thread(()->System.out.println("Lambda")).start();
      

Lambda与匿名内部类的区别

匿名内部类是使用范围包括:抽象类、具体类、接口。抽象方法数量没有要求,只要重写即可。编译后会产生新的字节码文件。Lambda表达式的使用范围:仅限于接口interface,且接口只能有一个抽象方法。编译后无字节码文件

总结:

所需类型不同

  • 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类

  • Lambda表达式:只能是接口

使用限制不同

  • 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
  • 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式

实现原理不同

  • 匿名内部类:编译之后,产生一个单独的.class字节码文件
  • Lambda表达式:编译之后,没有一个单独的.class字节码文件,对应的字节码会在运行时动态生成

接口组成更新

接口的组成

  • 常量:public static final
  • 抽象方法:public abstract
  • 默认方法default(Java8)
  • 静态方法static(Java8)
  • 私有方法(Java9)
接口默认方法

接口中默认方法的定义格式:

  • 格式:public default返回值类型方法名(参数列表){ }
  • 范例:public default void show3(){ }

接口中默认方法的注意事项:

  • 默认方法不是抽象方法,所以不强制被重写。但是可以被重写,重写的时候去掉default关键字
  • public可以省略,default不能省略
public interface Inter2 {
    void show1();
    void show2();
    public default void show3(){
        System.out.println("show3");
    }
}
/**
 * @author: 小码农
 * @create: 2021-08-06 16:19
 **/
public class Demo8_1 implements Inter2{

    @Override
    public void show1() {
        System.out.println("one show1");
    }

    @Override
    public void show2() {
        System.out.println("one show2");
    }
    //重写默认方法
    @Override
    public void show3() {
        System.out.println("one show3");
    }
}
接口静态方法

接口静态方法的定义格式:

  • 格式:public static 返回值类型 方法名(参数列表){ }
  • 范例:public static void show(){ }

接口中静态方法的注意事项:

  • 静态方法只能通过接口名调用,不能通过实现类名或对象名调用
  • 接口中声明静态方法的public可以省略,static不能省略
/**
 * @author: 小码农
 * @create: 2021-08-06 16:46
 **/
public interface Inter4 {
    //抽象方法
    void show();
    //默认方法
    default void show2(){
        System.out.println("默认方法");
    }
    //静态方法
    static void show3(){
        System.out.println("静态方法");
    }
}
public class Demo9 {
    public static void main(String[] args) {
        Inter4 i = new Demo9_Inter4();
        i.show();
        i.show2();
        Inter4.show3(); //使用接口调用静态方法
    }
}
接口私有方法

接口中私有方法的定义格式(jdk1.9后新增):

  • 格式1:private 返回值类型 方法名(参数列表){ }
  • 范例1:private void show(){ }
  • 格式2:private static 返回值类型 方法名(参数列表){ }
  • 范例2:private static void method(){ }

接口私有方法注意事项:

  • 默认方法可以调用私有的静态方法和非静态方法
  • 静态方法只能调用私有的静态方法
//私有方法使用
public interface Inter5 {
    default void show1(){show();show3();}
    default void show2(){show();show3();}
    static void method1(){show();}
    static void method2(){show();}
    private static void show(){System.out.println("托儿所")}
    private void show3(){System.out.println("小学僧")}
}
//调用接口实现类创建对象实现方法输出
/*
	托儿所
	小学僧
	托儿所
	小学僧
	托儿所
	托儿所
*/

方法引用

在使用Lambda表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿参数做操作

那么考虑一种情况:如果我们在Lambda中所指定的操作方案,已经有其它地方存在相同方案,那是否还有必要再写重复逻辑呢?答案是没有必要

方法引用就是在某些地方直接使用已经存在的方案

/**
 * @author: 小码农
 * @create: 2021-08-06 17:29
 **/
public class Demo11 {
    public static void main(String[] args) {
        usePrint(s -> System.out.println(s));
        //方法引用符 ::,此处方法引用后直接将字符串s传入println方法中
        usePrint(System.out::println);
        //可推导的就是可省略的
    }
    private static void usePrint(Inter6 i){
        i.printString("Hello world");
    }
}
方法引用符
  • :: 该符号为引用运算符,而它所在的表达式被称为方法引用

推导与省略

  • 如果使用Lambda,根据“可推导就是可省略”原则,无需指定参数类型,也无需指定重载形式,它们都将被自动推导
  • 如果使用方法引用,同样可以根据上下文进行推导
  • 方法引用是Lambda的孪生兄弟

常见引用方式:

  • 引用类方法
  • 引用对象的实例方法
引用类方法

引用类方法,其实就是引用类的静态方法

  • 格式:类名::静态方法
  • 范例:Integer::parseInt
    • Integer类的方法:public static int parseInt(String s) 将此String转换为Int类型数据

练习:

  • 定义一个接口(Converter),里面定义一个抽象方法 int convert(String s)

  • 定义一个测试类(ConverterDemo),在测试类中提供两个方法,

    • useConverter(Converter c)
    • 主方法main

引用类方法,Lambda表达式被类方法替代时,它的形参全部传递给静态方法作参数

/**
 * @author: 小码农
 * @create: 2021-08-06 19:04
 **/
public class Demo_Converter {
    public static void main(String[] args) {
        //Lambda表达式
        useConverter(s -> Integer.parseInt(s));
        //引用类方法,Lambda表达式被类方法替代时,它的形参全部传递给静态方法作参数
        useConverter(Integer::parseInt);
    }
    public static void useConverter(Converter c){
        int number = c.convert("666");
        System.out.println(number);
    }
}
引用对象实例方法

引用对象的实例方法,就是引用类中的成员方法

  • 格式:对象::成员方法
  • 范例:“Hello World”::toUpperCase
    • String类中的方法:public String toUpperCase() 将String所有字符转为大写

练习

  • 定义一个类PrintString,里面定义一个方法
    • public void printUpper(String s) 把字符串变大写,然后输出
  • 定义一个接口Printer,内有一个抽象方法
    • void printUpperCase(String s)
  • 定义一个测试类Demo_Printer,在测试类中提供主方法和usePrinter方法

Lambda表达式被对象的实例方法代替的时候,它的形参都要传递给该方法作为参数

/**
 * @author: 小码农
 * @create: 2021-08-06 19:14
 **/
public class Demo_printer {
    public static void main(String[] args) {
        //使用Lambda表达式
        usePrinter(s -> System.out.println(s.toUpperCase()));

        //使用引用对象实例
        PrintString ps = new PrintString();
        usePrinter(ps::printUpper);
        //Lambda表达式被对象的实例方法代替的时候,它的形参都要传递给该方法作为参数
    }
    public static void usePrinter(Printer p){
        p.printUpperCase("Helloworld");
    }
}

接口

/**
 * @author: 小码农
 * @create: 2021-08-06 19:14
 **/
public interface Printer {
    void printUpperCase(String s);
}

PrintString类

/**
 * @author: 小码农
 * @create: 2021-08-06 19:14
 **/
public class PrintString {
    public void printUpper(String s){
        String str = s.toUpperCase();
        System.out.println(str);
    }
}
引用类的实例方法

引用类的实例方法,其实就是引用类中的成员方法

  • 格式:类名::成员方法
  • 范例:String::sbustring
    • String类中的方法:public String substring(int beginIndex,int endIndex)
    • 从beginIndex开始到enIndex结束,截取字符串,返回一个字符串,长度为end-begin

练习:

  • 定义一个接口MyString,里面定义一个抽象方法
    • String mySubString(String s,int x,int y)
  • 定义一个测试类Demo_MyString,在测试类中提供两个方法
    • useMyString(MyString m)
    • main

Lambda表达式被类的实例方法替代的时候,第一个参数作为调用者,后面的参数全部传递该方法作为参数

/**
 * @author: 小码农
 * @create: 2021-08-06 19:30
 **/
public class Demo13_MyString {
    public static void main(String[] args) {
        //在主方法中调用useMyString方法
        useMyString((s,x,y)-> s.substring(x,y));

        //引用类的实例方法
        useMyString(String::substring);
        //Lambda表达式被类的实例方法替代的时候,第一个参数作为调用者,后面的参数全部传递该方法作为参数
    }

    public static void useMyString(MyString m){
        String string = m.mySubString("hello world", 2, 5);
        System.out.println(string);
    }
}
引用构造器

引用构造器,其实就是引用构造方法

  • 格式:类名::new

  • 范例:Student::new

练习

  • 定义一个类Student,里面有两个成员变量name,age
    • 并提供无参构造方法和带参构造方法,以及成员变量对应的get和set方法
  • 定义一个接口StudentBuilder,里面定义一个抽象方法
    • Student build(String name,int age);
  • 定义一个测试类Demo_Student,在测试类中提供两个方法
    • useStudentBuilder
    • main

Lambda表达式被构造器替代的时候,它的形式参数全部传递给构造器作为参数

测试类

/**
 * @author: 小码农
 * @create: 2021-08-06 19:39
 **/
public class Demo14_Student {
    public static void main(String[] args) {
        //使用Lambda表达式
        useStudent((s,i)-> new StudentClass(s,i));

        //使用引用构造器
        useStudent(StudentClass::new);
    }

    public static void useStudent(StudentBuilder sb){
        StudentClass sc = sb.builder("小李",18);
        System.out.println(sc.getName()+" "+sc.getAge());
    }
}

接口

/**
 * @author: 小码农
 * @create: 2021-08-06 19:38
 **/
public interface StudentBuilder {
    StudentClass builder (String name,int age);
}

Student类

/**
 * @author: 小码农
 * @create: 2021-08-06 19:39
 **/
public class StudentClass {
    private String name;
    private int age;

    public StudentClass(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public StudentClass() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

函数式接口

概述

函数式接口:有且仅有一个抽象方法的接口。在代码中用@FunctionalInterface注解

Java中的函数式编程体现就是Lambda表达式,所以函数式接口就是可以适用于Lambda适用的接口,只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利进行推导

函数式接口作为方法的参数

如果方法的参数是一个函数式接口,我们可以使用Lambda表达式作为参数传递

startThread(()-> System.out.println(Thread.currentThread().getName()+"线程启动了"));

函数式接口作为方法的返回值

如果方法的返回值是个函数接口,我们可以使用Lambda表达式作为结果返回

private static Comparator<String> getComparator(){
    //使用Lambda
    return (s1,s2)-> s1.length()-s2.length();
}

常用的函数式接口

Java 8 在java.util.function包下预定了大量的函数式接口供我们使用

  • Supplier接口
  • Consumer接口
  • Predicate接口
  • Function接口
Supplier接口

Supplier:包含一个无参的方法

  • T get():获得结果
  • 该方法不需要参数,它会按照某种实现逻辑(由Lambda表达式实现)返回一个数据
  • Supplier接口也被称为生产型接口,指定接口泛型,那么get方法就会产生什么类型的数据
import java.util.function.Supplier;

/**
 * @author: 小码农
 * @create: 2021-08-08 15:30
 **/
public class Demo3 {
    public static void main(String[] args) {
        String s = getString(()->"刘德华");
        System.out.println(s);
        Integer i = getInteger(()-> 232355);
        System.out.println(i);
    }

    //定义一个方法,返回整数
    private static Integer getInteger(Supplier<Integer> sup){
        return sup.get();
    }
    //定义一个方法,返回字符串数据
    private static String getString(Supplier<String> sup){
        return sup.get();
    }
}

练习:获取数组中的最大值

import java.util.function.Supplier;

/**
 * @author: 小码农
 * @create: 2021-08-08 15:35
 **/
public class Demo4 {
    public static void main(String[] args) {
        //定义一个int数组
        int[] arr = {1,241,52,6266,735};
        int i = getMax(()->{
            int max = arr[0];
            for (int x = 0;x<arr.length;x++){
                if (arr[x]>max){
                    max = arr[x];
                }
            }
            return max;
        });
        System.out.println(i);
    }
    private static int getMax(Supplier<Integer> sup){
        return sup.get();
    }
}
Consumer接口

Consumer:包含两个方法

  • void accept(T t):对给定的参数执行此操作
  • default Consumer andThen(Consumer after):返回一个组合的Consumer,依次执行此操作,然后执行after操作
  • Consumer接口也被称为消费型接口,它消费的数据的数据类型由泛型指定
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * @author: 小码农
 * @create: 2021-08-08 15:35
 **/
public class Demo5 {
    public static void main(String[] args) {
        operatorString("林青霞",s ->System.out.println(s));
        operatorString("林青霞",System.out::println);
        operatorString("林青霞",s -> System.out.println(new StringBuilder(s).reverse().toString()));

        System.out.println("------------");
        operatorString("林青霞",s-> System.out.println(s),s -> System.out.println(new StringBuilder(s).reverse().toString()));

    }

    //用不同的方式消费同一个字符串两次
    private static void operatorString(String name,Consumer<String> co1,Consumer<String> co2){
//        co1.accept(name);
//        co2.accept(name);
        co1.andThen(co2).accept(name);
    }
    //定义一个方法消费一个字符串数据
    private static void operatorString(String string, Consumer<String> con){
        con.accept(string);

    }
}

练习

  • String[] strArray = {“林青霞,30”,“张曼玉,35”,“王祖贤,33”};
  • 字符串中有多条信息,按照格式:“姓名:xx,年龄:xx”打印
  • 要求:把打印姓名动作作为第一个consumer接口的Lambda实例,打印年龄作为第二个实例,将两个Consumer接口按顺序组合使用
import java.util.function.Consumer;

/**
 * @author: 小码农
 * @create: 2021-08-08 15:55
 **/
public class Demo6 {
    public static void main(String[] args) {
        String[] strArray = {"林青霞,30","张曼玉,35","王祖贤,33"};
        printInfo(strArray,(String string)->{
            String name = string.split(",")[0];
            System.out.print("姓名:"+name);
        },(String string)->{
            int age = Integer.parseInt(string.split(",")[1]);
            System.out.println(" 年龄:"+Integer.parseInt(string.split(",")[1]));
        });
        //简化Lambda
        printInfo(strArray,string-> System.out.print("姓名:"+string.split(",")[0]),
                string-> System.out.println(" 年龄:"+Integer.parseInt(string.split(",")[1])));

    }
    private static void printInfo(String[] strArray, Consumer<String> con1,Consumer<String> con2){
        for (String string:strArray){
            con1.andThen(con2).accept(string);
        }
    }
}
Predicate接口

Predicate:常用的四个方法

  • boolean test(T t):对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值
  • default Predicate negate():返回一个逻辑的否定,对应逻辑非
import java.util.function.Predicate;

/**
 * @author: 小码农
 * @create: 2021-08-08 16:13
 **/
public class Demo7 {
    public static void main(String[] args) {
        boolean b = checkString("Hello",s->s.length()>8);
        System.out.println(b);
        boolean b2 = checkString("HelloWorld",s->s.length()>8);
        System.out.println(b2);
    }
    //判断给定的字符串是否满足要求
    private static boolean checkString(String s, Predicate<String> pre) {
//        return pre.test(s);
        //对test结果进行取非
        return pre.negate().test(s);
    }
}
  • defalut Predicate and(Predicate other):返回一个组合判断,对应短路与
  • defalut Predicate or(Predicate other):返回一个组合判断,对应短路或
  • Predicate接口通常用于判断参数是否满足指定的条件
import java.util.function.Predicate;

/**
 * @author: 小码农
 * @create: 2021-08-08 16:13
 **/
public class Demo8 {
    public static void main(String[] args) {
        boolean b = checkString("Hello",s->s.length()>8);
        System.out.println(b);
        boolean b2 = checkString("HelloWorld",s->s.length()>8);
        System.out.println(b2);
        System.out.println("----------------------");
        boolean b3 = checkString("Hello",s -> s.length()>8,s->s.length()<15);
        System.out.println(b3);
        boolean b4 = checkString("HelloWorld",s -> s.length()>8,s->s.length()<15);
        System.out.println(b4);

    }

    //同一个字符串给出两个不同的判断条件,最后把两个结果进行逻辑与并返回
    private static boolean checkString(String s,Predicate<String> pre1,Predicate<String> pre2){
       /* boolean b1 = pre1.test(s);
        boolean b2 = pre2.test(s);
        boolean b = b1&&b2;
        return b;*/
       //短路与
       //return pre1.and(pre2).test(s);
        //短路或
        return pre1.or(pre2).test(s);
    }
    //判断给定的字符串是否满足要求
    private static boolean checkString(String s, Predicate<String> pre) {
        return pre.test(s);
    }
}

练习:将集合中满足条件的内容输出

import java.util.ArrayList;
import java.util.function.Predicate;

/**
 * @author: 小码农
 * @create: 2021-08-08 17:30
 **/
public class Demo9 {
    public static void main(String[] args) {
        String[] strings = {"林青霞,30","柳岩,43","张曼玉,47","貂蝉,999","王祖贤,33"};
        ArrayList<String> arrayList = myFilter(strings,s -> {
            boolean b1 = s.split(",")[0].length()>2;
            return b1;
        },s -> {
            boolean b2 = Integer.parseInt(s.split(",")[1])<35;
            return b2;
        });
        for (String string:arrayList){
            System.out.println(string);
        }
    }
    private static ArrayList<String> myFilter(String[] strArray, Predicate<String> pre1,Predicate<String> pre2){
        //定义一个集合
        ArrayList<String> arrayList = new ArrayList<>();
        //遍历数组
        for (String str:strArray){
            if(pre1.and(pre2).test(str)){
                arrayList.add(str);
            }
        }
        return arrayList;
    }
}
Function接口

Function<T,R>:T输入的类型,R结果的类型

  • R apply(T t):将此函数应用于给定的参数
  • default <V> Function andThen(Function after):返回一个组合函数,首先将该函数应用于输入,然后将after函数应用于结果
import java.util.function.Function;

/**
 * @author: 小码农
 * @create: 2021-08-08 17:46
 **/
public class Demo10 {
    public static void main(String[] args) {
        convert("123",s-> Integer.parseInt(s));
        convert("1234",Integer::parseInt);
        System.out.println("--------------");
        convert(102,integer -> String.valueOf((integer+1)));
        System.out.println("--------------");
        converted("1234",s->Integer.parseInt(s)+1,i->String.valueOf(i));
    }
    //定义一个方法,把字符串转为int加一个整数,转为字符串输出
    private static void converted(String string, Function<String,Integer> fun1,Function<Integer,String> fun2){
        /*int i = fun1.apply(string);
        String s = fun2.apply(i);
        System.out.println(s);*/
        //使用andThen改进
        String s = fun1.andThen(fun2).apply(string);
    }

    //定义一个方法,把字符串转化为int类型,输出
    private static void convert(String string, Function<String,Integer> fun){
        int i = fun.apply(string);
        System.out.println(i);
    }
    //定义一个方法,把一个int类型加上一个整数后,转为String输出
    private static void convert(int i,Function<Integer,String> fun){
        String string = fun.apply(i);
        System.out.println(string);
    }
}

练习:获取年龄,转为整型,加1输出

import java.util.function.Function;

/**
 * @author: 小码农
 * @create: 2021-08-08 18:05
 **/
public class Demo11 {
    public static void main(String[] args) {
        convert("貂蝉,999",s ->s.split(",")[1],s->Integer.parseInt(s)+1);
    }

    private static void convert(String string, Function<String,String> fun1,Function<String,Integer> fun2){
        int s = fun1.andThen(fun2).apply(string);
        System.out.println(s);

    }
}

Stream流

体验Stream流

  • 创建一个集合,存储多个字符串元素
  • 把集合中所有以“张”开头的元素存储到一个新的集合
  • 把“张”开头的集合中的长度为3的元素存储到一个新的集合
  • 遍历上一步得到的集合

使用Stream流的方式完成过滤操作

  • arrayList.stream().filter(s->s.startsWith("张")).filter(s-> s.length()==3).forEach(System.out::println);
    
  • 直接阅读代码的字面意思即可完美展示五官逻辑方式的语义:生成流、过滤张姓、过滤长度3、逐一打印

  • Stream流把真正的函数式编程风格引入到Java中

Stream流的使用

  • 生成流
    • 通过数据源(集合、数组等)生成流
    • list.stream()
  • 中间操作
    • 一个流后面可以跟随零个或多个中间操作,其目的主要是打开流,做某种程度的数据过滤/映射,然后返回一个新的流,交给下一个操作使用
    • filter()
  • 终结操作
    • 一个流只能有一个终结操作,当这个操作执行后,流就被用“光“了,无法再被操作。所以终结操作一定是流的最后一个操作
    • forEach()
常见生成方法
  • Collection体系的集合可以使用默认方法stream()生成流
    • default Stream<E> stream()
  • Map体系的集合间接的生成流
  • 数组可以通过Stream接口的静态方法of(T…values)生成流
import org.omg.CORBA.INTERNAL;

import java.util.*;
import java.util.stream.Stream;

/**
 * @author: 小码农
 * @create: 2021-08-08 20:06
 * - Collection体系的集合可以使用默认方法stream()生成流
 *   - default Stream<E\> stream()
 * - Map体系的集合间接的生成流
 * - 数组可以通过Stream接口的静态方法of(T...values)生成流
 **/
public class Demo13 {
    public static void main(String[] args) {
        //Collection体系集合使用stream()生成流
        List<String> list = new ArrayList<>();
        Stream<String> listStream = list.stream();
        Set<String> set = new HashSet<>();
        Stream<String> setStream = set.stream();

        //Map体系集合间接生成流
        Map<String,Integer> map = new HashMap<>();
        Stream<String> keyStream = map.keySet().stream();
        Stream<Integer> valueStream = map.values().stream();
        Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();

        //数组可以通过Stream接口的静态方法of(T。。。values)生成流
        String[] strings = {"hello","world","java"};
        Stream<String> stringStream = Stream.of(strings);
        Stream<String> stringStream1 = Stream.of("hello", "world", "java");
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
    }
}
中间操作方法
  • Stream<T> filter(Predicate predicate):用于对流中数据进行过滤
    • Predicate接口的方法 boolean test(T t):对给定的参数进行判断,返回bool值
import java.util.ArrayList;

/**
 * @author: 小码农
 * @create: 2021-08-08 20:16
 **/
public class Demo14 {
    public static void main(String[] args) {
        ArrayList<String> List = new ArrayList<>();
        List.add("林青霞");
        List.add("张曼玉");
        List.add("张三");
        List.add("张无忌");
        List.add("赵敏");

        //需求1:把list集合中以张开头的元素在控制台输出
        List.stream().filter((String s)->{
            return s.startsWith("张");
        }).forEach(System.out::println);
        //需求2:输出长度等于3的张开头名字
        List.stream().filter((String s)->{
            return s.length()==3;
        }).forEach(System.out::println);
        //需求3:输出张姓长度为3的姓名
        List.stream().filter(s->s.startsWith("张")).filter(s -> s.length()==3).forEach(System.out::println);
    }
}
  • Stream<T> limit(long maxSize):返回流中的元素组成的流,截取前指定参数个数的数据
List.stream().limit(3).forEach(System.out::println);
  • Stream<T> skip(long n):跳过指定参数个数的数据,返回该流的剩余元素组成的流
List.stream().skip(3).forEach(System.out::println);
  • concat(Stream a,Stream b):合并a和b两个流为一个流
  • distinct():返回由该流的不同元素(根据Object.equals(Object))组成的流
import java.util.ArrayList;
import java.util.stream.Stream;

/**
 * @author: 小码农
 * @create: 2021-08-08 20:36
 **/
public class Demo16 {
    public static void main(String[] args) {
        ArrayList<String> List = new ArrayList<>();
        List.add("林青霞");
        List.add("张曼玉");
        List.add("张三");
        List.add("张无忌");
        List.add("赵敏");

        //取前四个数据返回流
        Stream<String> s1 = List.stream().limit(4);
        //跳过两个数据返回一个流
        Stream<String> s2 = List.stream().skip(2);
        //合并两个流,并输出
//        Stream.concat(s1,s2).forEach(System.out::println);
        //合并两个流,要求元素不重复,输出
        Stream.concat(s1,s2).distinct().forEach(System.out::println);
    }
}
  • sorted():返回由Stream流元素组成的流,根据自然顺序排序
  • sorted(Comparator comparator):返回由该流的元素组成的流,根据提供的Comparator进行排序
import java.util.ArrayList;
import java.util.Comparator;

/**
 * @author: 小码农
 * @create: 2021-08-08 20:44
 **/
public class Demo17 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("linqingxia");
        list.add("linqingxia2");
        list.add("zhangmayu");
        list.add("aerfa");
        list.add("zhangwuji");
        list.add("baoli");
        //按照自然排序
        list.stream().sorted().forEach(System.out::println);
        //自定义排序器按照字符长度排序,长度相同时按照字符顺序排列
//        list.stream().sorted((s1,s2)->s1.length()-s2.length()).forEach(System.out::println);
        list.stream().sorted((s1,s2)->{
            int num1 = s1.length()-s2.length();
            int num2 = num1==0?s1.compareTo(s2):num1;
            return num2;
        }).forEach(System.out::println);
    }
}
  • <R> Stream<R> map(Function mapper):返回由给定函数应用于此流的元素的结果组成的流,Function接口的方法:apply()
  • IntStream mapToInt(ToIntFunction mapper):返回一个IntStream其中包含将给定函数应用于此流的元素的结果
    • IntStream:表示原始的int流
    • ToIntFunction接口中的方法 int applyAsInt(T value)
import java.util.ArrayList;

/**
 * @author: 小码农
 * @create: 2021-08-08 20:54
 **/
public class Demo18 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("10");
        list.add("20");
        list.add("30");
        list.add("40");
        list.add("50");

        //将集合的字符串转为整数后输出map()
        list.stream().map(s -> Integer.parseInt(s)).forEach(System.out::println);

        list.stream().mapToInt(s->Integer.parseInt(s)).forEach(System.out::println);
        int sum = list.stream().mapToInt(s->Integer.parseInt(s)).sum();
        System.out.println(sum);
    }
}
常见终结操作
  • void forEach(Consumer action):对流中的每个元素操作
    • Consumer接口中的方法 void accept(T t):对给定的参数执行该操作
  • long count():返回流中的元素数
import java.util.ArrayList;

/**
 * @author: 小码农
 * @create: 2021-08-08 21:02
 **/
public class Demo19 {
    public static void main(String[] args) {
        ArrayList<String> List = new ArrayList<>();
        List.add("林青霞");
        List.add("张曼玉");
        List.add("张三");
        List.add("张无忌");
        List.add("赵敏");

        //将集合在控制台输出
        List.stream().forEach(System.out::print);
        //统计有几个张开头的元素,输出结果
        long count = List.stream().filter(s->s.startsWith("张")).count();
        System.out.println(count);
    }
}

练习

有两个ArrayList集合,分别存储6个男演员和6个女演员名

Stream流的收集操作

对数据使用Stream流的方式操作完毕后,把流中的数据收集到集合中的操作

  • R collect(Collector collector)
  • 该方法的参数是一个Collector接口

工具类Collectors提供具体的收集方式

  • public static <T> Collector toList():把元素收集到List集合中
  • public static <T> Collector toSet():把元素收集到Set集合中
  • public static Collector toMap(Function keyMapper,Function valueMapper):把元素收集到map集合中
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author: 小码农
 * @create: 2021-08-08 21:17
 **/
public class Demo21 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("林青霞");
        list.add("林心如");
        list.add("范冰冰");
        list.add("李冰冰");

        //得到名字为3字的流
        Stream<String> listStream = list.stream().filter(s -> s.length()==3);
        //stream流放入LIst集合中并遍历
        List<String> li = listStream.collect(Collectors.toList());
        for (String string:li){
            System.out.println(string);
        }

        Set<Integer> set = new HashSet<>();
        set.add(33);
        set.add(22);
        set.add(24);
        set.add(41);

        Stream<Integer> setStream = set.stream().filter(s->s>30);
        Set<Integer> set2 = setStream.collect(Collectors.toSet());
        for (Integer integer:set){
            System.out.println(integer);
        }

        String[] strings = {"张三丰,999","成龙,44","刘德华,55"};
        Stream<String> ss = Stream.of(strings).filter(s->{
            return Integer.parseInt(s.split(",")[1])>50;
        });
//        ss.forEach(System.out::println);
        //将stream流的数据收集到Map集合中,姓名作为键,年龄作为值
        Map<String, Integer> co = ss.collect(Collectors.toMap(s -> s.split(",")[0], s -> Integer.parseInt(s.split(",")[1])));
        Set<String> strings1 = co.keySet();
        for (String s:strings1){
            System.out.println(s);
            System.out.println(co.get(s));
        }

    }
}

类加载&反射&模块化

当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载,类的连接,类的初始化来对类进行初始化。如果不出意外,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或者类初始化

类的加载:

  • 就是将class文件读入内存,并为其创建一个java.lang.Class对象
  • 任何类被使用时,系统都会为之建立一个java.lang.Class对象

类的连接:

  • 验证阶段:用于检验被加载的类是否有正确的内部结构,并和其它类协调一致
  • 准备阶段:负责为类的类变量分配内存,并设置默认初始化值
  • 解析阶段:将类的二进制数据中的符号引用替换为直接引用

类的初始化:

  • 在该阶段,主要是对类变量进行初始化

类加载

类的初始化步骤

  • 假如类还未被加载和连接,则程序先加载并连接该类

  • 假如该类的直接父类还未被初始化,则先初始化其直接父类

  • 假如类中有初始化语句,则系统依次执行这些初始化语句

类的初始化时机:

  • 创建类的实例
  • 调用类的类方法
  • 访问类或者接口的类变量,或者为该类变量赋值
  • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
  • 初始化某个类的子类
  • 直接使用java.exe命令来运行某个主类
类加载器

类加载器的通

  • 负责将.class文件加载到内存中,并为之生成对应的java.lang.Class对象

JVM的类加载机制

  • 全盘负责
  • 父类委托
  • 缓存机制

ClassLoader:是负责加载类的对象

Java运行时具有以下内置类加载器

  • Bootstrap class loader:是虚拟机的内置类加载器,通常表示为null
  • Platform class loader:平台类加载器
  • System class loader:应用程序类加载器
  • 类加载器的继承关系:System的父加载器为Platform,Platform的父加载器为Bootstrap

ClassLoader中的两个方法

  • static ClassLoader getSystemClassLoader():返回用于委派的系统类加载器

  • ClassLoader getParent():返回父类加载器进行委派

反射

Java反射机制:是指在运行时去获取一个类的变量和方法信息。然后通过获取到的信息来创建对象,调用方法的一种机制。由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍可以扩展

获取Class类的对象

我们要想通过反射区使用一个类,首先我们要获取到该类的字节码文件对象,也就是类型为Class的对象,有三种方法获取Class类型的对象

  • 使用类的class属性来获取该类对应的Class对象。Student.class
  • 调用对象的getClass()方法返回该对象所属类对应的Class对象,该方法是Object类中的方法,所有的Java对象都可以调用该方法
  • 使用CLass类中的静态方法forName(String className),该方法需要传入字符串参数,该字符串参数的值是某个类的全路径,也就是完整包名的路径
/**
 * @author: 小码农
 * @create: 2021-08-09 10:50
 **/
public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException {
        //使用类的class属性获取对应的clss对象
        Class<Student> c1 = Student.class;
        Class<Student> c2 = Student.class;
        System.out.println(c1);
        System.out.println(c2==c1);
        System.out.println("---------------");
        //.getClass
        Student s = new Student();
        Class<? extends Student> c3 = s.getClass();
        System.out.println(c1==c3);
        System.out.println("-----------------");
        //forName
        Class<?> c4 = Class.forName("Student");
        System.out.println(c1==c4);
    }
}
反射获取构造方法并使用

Class类中用于获取构造方法的方法

  • getConstructors()方法,返回对象的公共构造函数数组
  • getDeclaredConstructors()方法,返回对象的所有构造函数数组,公共、私有、默认
  • 获取对象单个的构造方法getConstructor,返回单个公共构造方法
  • getDeclaredConstructor()方法,返回单个构造方法对象,包含公共、私有默认

Constructor类中用于创建对象的方法

  • T newInstance(Object … initargs):根据指定的构造方法创建对象

练习

练习1:通过反射实现如下操作

  • Student s = new Student(“林青霞”,30,“西安”);

  • System.out.println(s);

  • 基本数据类型可以通过.class得到对应的类型

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @author: 小码农
 * @create: 2021-08-09 11:47
 **/
public class Demo4 {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<Student> s = Student.class;
        Constructor<Student> con = s.getConstructor(String.class, int.class, String.class);
        Student stu = con.newInstance("林青霞", 33, "西安");
        System.out.println(stu);
    }
}

练习2

  • Student s = new Student(“林青霞”);
  • System.out.println(s);
  • public void setAccessible(boolean b):值为true,取消访问检查
Constructor<Student> de1 = s.getDeclaredConstructor(String.class);
//暴力反射 public void setAccessible(boolean b):值为true,取消访问检查
de1.setAccessible(true);
Student stu2 = de1.newInstance("林青霞");
System.out.println(stu2);
反射获取成员变量并使用

Class类中用于获取成员变量的方法

  • Field[] getFields():返回所有公共成员变量对象的数组
  • Field[] getDeclaredFields():返回所有成员变量对象的数组
  • Field getField(String name):返回单个公共成员变量对象
  • Field getDeclaredField(String name):返回单个成员变量对象

Field类中用于给成员变量赋值的方法

  • void set(Object o,Object value):给obj对象的成员变量赋值为value
  • public void setAccessible(boolean b):值为true,取消访问检查
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Calendar;

/**
 * @author: 小码农
 * @create: 2021-08-09 15:26
 **/
public class Demo5 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //获取Class对象
        Class<?> s1 = Class.forName("Student");
        /*
        Field[] getFields()返回一个包含Field对象的数组
        Field[] getDeclaredFields()返回一个包含Field对象的数组,包含私有默认
         */
//        Field[] fields = s1.getFields();
        Field[] fields = s1.getDeclaredFields();
        for (Field f:fields){
            System.out.println(f);
        }
        System.out.println("----------------");
        //Field getField 返回Field对象
        //Field getDeclaredField 返回Field对象
        Field address = s1.getField("address");
        //获取无参构造方法创建对象
        Constructor<?> con = s1.getConstructor();
        Object o = con.newInstance();
        //对成员变量赋值
        address.set(o,"西安");
        System.out.println(o);
    }
}

反射获取成员变量并使用练习

练习:通过反射做到以下操作

  • Student s = new Student();
  • s.name = “林青霞”;
  • s.age = 30;
  • s.address = “西安”;
  • System.out.println(s);
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

/**
 * @author: 小码农
 * @create: 2021-08-09 15:42
 **/
public class Demo6 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<?> stu = Class.forName("Student");
        Field name = stu.getDeclaredField("name");
        Field age = stu.getDeclaredField("age");
        Field address = stu.getField("address");
        name.setAccessible(true);   //私有变量使用setAccessible取消检查


        Constructor<?> con = stu.getConstructor();
        Object obj = con.newInstance();
        name.set(obj,"李红");
        age.set(obj,33);
        address.set(obj,"陕西");
        System.out.println(obj);
    }
}
反射获取成员方法并使用

Class类中用于获取成员方法的方法

  • Method[] getMethods():返回所有公共成员方法对象的数组,包括继承的
  • Method[] getDeclaredMethods():返回所有成员方法对象的数组,不包括继承的
  • Method getMethod(String name):根据传入的方法名称,返回单个公共成员方法对象
  • Method getDeclaredMethod(String name):返回单个成员方法对象

Method类中用于调用成员方法的方法

  • Object invoke(Object obj,Object…args):调用obj对象的成员方法,参数是args,返回值是Object类型

    成员方法对象.invoke(obj);
    
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author: 小码农
 * @create: 2021-08-09 15:52
 **/
public class Demo7 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //第一步获取Class对象
        Class<?> stu = Class.forName("Student");
        Constructor<?> con = stu.getConstructor();
        Object obj = con.newInstance();

        //返回方法对象数组
        /*Method[] getMethods()返回一个包含方法对象的数组,包含所有公共方法
        Method[] getDeclaredMethods()返回包含所有方法对象的数组,包含私有、默认*/
//        Method[] methods = stu.getMethods();
        Method[] methods = stu.getDeclaredMethods();
        for (Method method:methods){
            System.out.println(method);
        }

        //返回单个方法getMethod
        Method toString = stu.getMethod("toString");

        //返回单个方法,私有方法可以用 getDeclaredMethod()
        Method method2 = stu.getDeclaredMethod("method2", String.class);

        System.out.println("---------------");
        //我们获取方法对象可以使用反射做什么,可以通过对象调方法
        Object invoke = toString.invoke(obj);
        System.out.println(invoke);
        Object ssss = method2.invoke(obj, "ssss");
        System.out.println(ssss);
    }
}

练习:通过反射实现如下操作

  • Student s = new Student();
  • s.method1();
  • s.method2(“林青霞”);
  • String ss = s.method3(“林青霞”,30);
  • System.out.println(ss);
  • s.function();
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author: 小码农
 * @create: 2021-08-09 16:09
 **/
public class Demo8 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //创建Class对象
        Class<?> stu = Class.forName("Student");

        //通过Class对象获取构造方法
        Constructor<?> con = stu.getConstructor();

        //通过构造方法对象创建Student对象
        Object obj = con.newInstance();

        //创建成员方法对象
        Method method1 = stu.getDeclaredMethod("method1");
        method1.setAccessible(true);
        Method method2 = stu.getDeclaredMethod("method2", String.class);
        method2.setAccessible(true);
        Method method3 = stu.getDeclaredMethod("method3", String.class, int.class);
        method3.setAccessible(true);

        Method func = stu.getDeclaredMethod("function");
        func.setAccessible(true);

        //使用成员方法
        method1.invoke(obj);    //没有返回值
        method2.invoke(obj, "李红");  //没有返回值

        Object in3 = method3.invoke(obj, "王伟", 33);
        System.out.println(in3);

        func.invoke(obj);

    }
}
/**
 * @author: 小码农
 * @create: 2021-08-09 11:19
 **/
public class Student {
    private String name;
    int age;
    public String address;
    public Student(){

    }
    private Student(String name){
        this.name=name;
    }
    Student(String name,int age){
        this.name=name;
        this.age=age;
    }

    public Student(String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
    private void function(){
        System.out.println("function");
    }
    public void method1(){
        System.out.println("method1");
    }
    public void method2(String s){
        System.out.println("method"+s);

    }
    public String method3(String s,int i){
        return s+","+i;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                '}';
    }
}

反射练习

练习1:在一个ArrayList<Integer>集合中添加一个字符串数据

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

/**
 * @author: 小码农
 * @create: 2021-08-09 16:30
 * 练习1:在一个ArrayList<Integer>集合中添加一个字符串数据
 **/
public class Demo9 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //创建集合
        ArrayList<Integer> arrayList = new ArrayList<>();

        arrayList.add(10);
        arrayList.add(20);
        //传统的添加方法无法添加字符串元素,我们使用反射越过添加类型检查
        Class<? extends ArrayList> c = arrayList.getClass();
        Method meth = c.getDeclaredMethod("add", Object.class);
        meth.invoke(arrayList,"hello");
        meth.invoke(arrayList,"world");

        System.out.println(arrayList);
    }
}

练习2:通过配置文件运行类中的方法

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

/**
 * @author: 小码农
 * @create: 2021-08-09 16:35
 * 练习2:通过配置文件运行类中的方法
 **/
public class Demo10 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException, ClassNotFoundException, InstantiationException {
        /*Stu stu = new Stu();
        Class<? extends Stu> s = stu.getClass();
        Method study = s.getDeclaredMethod("study");
        study.setAccessible(true);
        study.invoke(stu);*/

        /*  使用配置文件
            class.txt
            className = xxx
            methodName = xxx
         */
        //加载数据
        Properties prop = new Properties();
        FileReader fr = new FileReader("E:\\MYZ\\Code\\JAVA\\20210809\\src\\Class.txt");
        prop.load(fr);
        String className = prop.getProperty("className");
        String methodName = prop.getProperty("methodName");

        //通过反射来使用
        Class<?> c = Class.forName(className);
        Constructor<?> con = c.getConstructor();
        Object obj = con.newInstance();

        Method m = c.getDeclaredMethod(methodName);
        m.invoke(obj);

    }
}

Class.txt

className = Tea
methodName = teach

模块化

概述

如今,java语言过于庞大,无论是运行小程序还是运行一个大型程序,都需要JVM加载整个JRE环境。因此Java 9退出了模块化系统,允许Java程序可以根据需要选择加载必须的Java模块。

模块的基本使用

基本使用步骤

  • 创建模块,按照以前的讲解方式创建模块,创建包,创建类,定义方法
    • 为了体现模块的使用,我们创建两个模块,一个是myOne,一个是myTwo
  • 在模块的src目录下新建一个名为moudule-info.java的描述性文件,该文件专门定义模块名,访问权限,模块依赖等信息。描述性文件中使用模块导出和模块依赖来进行配置并使用(JDK 9以上才有)
  • 模块中所有未导出的包都是模块私有的,它们不能在模块之外被访问
    • 在myOne这个模块下的描述性文件转给你配置模块导出
    • 模块导出格式:exports 包名
  • 一个模块要访问其他的模块,必须明确指定依赖哪些模块,未明确指定依赖的模块不能访问。在myTwo这个模块下的描述性文件中配置模块依赖
    • 模块依赖格式:requires 模块名;
    • 注意:写模块名报错,需要按Alt+Enter提示,然后选择模块依赖
  • 现在,在myTwo模块的类中可以使用依赖模块下的内容

总结:

  • 简单来说就是在被使用类模块的描述性文件中需要导出包含类的包,exports 包名
  • 在使用者模块的描述性文件中要依赖被使用模块,requires 被使用类模块名
模块服务的使用

服务:从Java6开始,Java提供了一种服务机制,允许服务提供者和服务使用者之间完成解耦,简单来说,就是服务使用者只面向接口编程, 但不清楚服务提供者的实现类

Java 9模块化系统进一步简化了Java的服务机制。Java 9允许将服务接口定义在一个模块中,并使用uses语句来声明该服务接口,然后针对该服务接口提供不同的服务实现类,这些服务实现类可以分布在不同的模块中,服务实现模块则使用provides语句为服务接口指定实现类,服务使用者只需要面向接口编程即可

模块服务的使用步骤

  • 在myOne模块下创建一个包com.itheima_03,在该包下提供一个接口,接口中定义一个抽象方法

    • public interface MyServer{
      	void service();
      }
      
  • 在com.itheima_03包下创建一个包impl,在该包下提供接口的两个实现类Itheima和Czxy

  • 在myOne这个模块下的描述性文件中添加配置:

    • 模块导出:exports com.itheima_03
    • 服务提供:provides MyServer with Itheima; 指定MyServer的服务实现类是Itheima
  • 在myTwo这个模块下的描述文件中添加配置

    • 声明服务接口:uses MyService;
  • 在myTwo模块的类中使用Myservice接口提供的服务

    • ServiceLoader:一种加载服务实现的工具
package cn.itcast;
import com.myz03.MyServer;
import java.util.ServiceLoader;

/**
 * @author: 小码农
 * @create: 2021-08-09 18:09
 **/
public class Demo2 {
    public static void main(String[] args) {
        //加载服务
        ServiceLoader<MyServer> load = ServiceLoader.load(MyServer.class);

        //遍历服务
        for (MyServer ser:load){
            ser.service();
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值