零基础入门Java(十万字呕心沥血,倾情推荐)
提示:本文知识点均为学习总结,部分参考自黑马教程和大佬总结以及自己的一些体会。若有错漏,实属作者知识有限,请提出,及时更改。
文章目录
- 零基础入门Java(十万字呕心沥血,倾情推荐)
- 前言
- Java 核心基础(入门必备,十万字精简)
- 一、Java编程基本规则
- 1.1 Java语言编码规范
- 1.2 注释规则
- 1.3 Java常见关键字
- 1.3.1 用于定义数据类型的关键字
- 1.3.2 用于定义数据类型值的关键字
- 1.3.3 用于定义流程控制的关键字
- 1.3.4 用于定义访问权限的关键字
- 1.3.5 用于定义类、函数、变量修饰符的关键字
- 1.3.6 用于定义类与类之间关系的关键字
- 1.3.7 用于定义建立实例、应用实例、判断实例的关键字
- 1.3.8 用于处理异常的关键字
- 1.3.9 用于包的关键字
- 1.3.10 其它关键字
- 1.4 常量
- 1.4.1 常量的分类
- 1.5 变量的定义和使用
- 二、Java 基本数据类型和标识符
- 2.1 8种基本数据类型
- 2.2 数据类型装换
- 2.3 标识符
- 2.3.1 定义规则
- 2.3.2 命名规范
- 2.3.4 总结
- 三、Java 运算符
- 3.1 运算符概述
- 3.2 算术运算符概述
- 3.3 加法运算、自增和自减
- 3.3.1 加法运算的特点
- 3.3.2 自增和自减运算
- 3.4 赋值运算符
- 3.5 关系运算符
- 3.6 逻辑运算符
- 3.7 三元运算符
- 四、Java 流程控制结构、顺序结构与循环结构
- 4.1 选择结构if的三种格式
- 4.2 switch语句
- 4.3 三种循环结构
- 4.4 三种循环结构的区别
- 4.5 关键字break和continue
- 4.6 循环标号
- 五、方法和数组
- 5.1 方法重载(**Overload**)
- 5.2 方法重写(**Override**)
- 5.3 方法重载和方法重写的区别
- 5.4 方法引用
- 5.5 数组
- 5.5.1 数组的三种定义格式
- 5.5.2 数组的访问
- 5.5.3 数组的遍历
- 六、类与对象
- 6.1 类的概念
- 6.2 对象的概念
- 6.3 类与对象的关系
- 6.4 Java中的封装体
- 6.4.1 封装的好处
- 6.5 private关键字
- 6.5.1 案例:private修饰成员变量
- 6.6 this关键字
- 6.6.1 案例:使用this完善set方法
- 6.6.2 this和super的区别
- 6.7 构造方法的基本概念
- 6.7.1 谁来创建对象 ?
- 6.7.2 构造方法的定义
- 6.8 继承
- 6.8.1 继承的优缺点
- 6.8.2 继承关系中子父类构造方法的使用
- 6.8.3 Java中继承的特点
- 6.9 多态
- 6.9.1 Java中实现多态的步骤
- 6.9.2 多态关系中成员变量的使用
- 6.9.3 多态的优点和缺点
- 6.10 类型转换
- 6.11 抽象类
- 6.11.1 抽象类的特点
- 6.11.2 抽象类成员的特点
- 6.11.3 抽象类名作为形参和返回值
- 6.12 final关键字和static关键字
- 6.12.1 final的作用
- 6.12.2 static关键字
- 6.12.3 static的作用
- 6.12.4 static修饰成员变量
- 6.12.5 static修饰成员方法
- 6.13 接口
- 6.13.1 接口创建对象的特点
- 6.13.2 接口继承关系的特点
- 6.13.3 接口成员方法的特点
- 6.13.4 接口名作为形参和返回值
- 6.13.5 接口组成更新
- 1. **接口组成更新概述**
- 2. **接口中默认方法**
- 3. **接口中静态方法**
- 4. 接口中私有方法
- 6.14 匿名类
- 6.15 内部类
- 6.15.1 成员内部类
- 6.15.2 局部内部类
- 6.15.3 静态内部类
- 6.15.4 匿名内部类
- 七、Java API
- 7.1 Object类
- 7.2 Scanner类
- 7.3 String类
- 7.3.1 **字符串的比较:**
- 7.4 StringBuilder和StringBuffer类
- 7.5 Date和Calendar类
- 7.6 基本类型的包装类
- 八、集合
- 8.1 集合的概念
- 8.1.1 集合和数组的区别
- 8.2 Java的集合体系
- 8.3 List集合、Set集合、Map集合的特点
- 8.3.1 List集合
- 8.3.1.1 LIst集合一般概念
- 8.3.1.2 **List集合特有方法**
- 8.3.1.3 **并发修改异常**
- 8.3.1.4 列表迭代器
- 8.3.1.5 List集合子类特点
- 8.3.2 Set集合
- 8.3.2.1 Set集合的一般概念
- 8.3.2.2 **哈希值**
- 8.3.2.3 HashSet集合
- 8.3.2.3.1 **HashSet集合特点**
- 8.3.2.3.2 **LinkedHashSet集合特点**
- 8.3.2.4 TreeSet集合
- 8.3.3 Map集合的特点
- 8.3.3.1 Map集合的一般概念
- 8.3.3.2 Map集合的基本功能
- 8.3.3.3 Map集合的获取功能
- 8.3.3.4 Map集合的遍历
- 8.3.3.5 **案例:**
- 1.HasMap存储学生对象并遍历
- 2.ArrayList集合存储HasMap元素并遍历
- 3.HasMap集合存储ArrayList元素并遍历
- 8.4 Collection集合和Collections类
- 8.4.1 Collection集合
- 8.4.1.1 **Collection集合概述**
- 8.4.1.2 **创建Collection集合的对象**
- 8.4.1.3 **Collection 集合常用方法**
- 8.4.2 Collection 类
- 8.5 增强for循环和迭代器
- 8.5.1 增强for循环
- 8.5.1 迭代器
- 8.6 泛型
- 8.6.1 **泛型定义格式**
- 8.6.2 **泛型类的定义格式**
- 8.6.3 **泛型方法的定义格式**
- 8.6.4 **泛型接口的定义格式**
- 8.6.5 **类型通配符**
- 8.6.6 **可变参数**
- 九、IO流及异常
- 9.1 异常及IO流体系
- 9.1.1 异常
- 9.2 File类
- 9.2.1 File的概念
- 9.2.2 File的创建
- 9.2.3 File的功能获取
- 9.3 IO流:字节流和字符流
- 9.3.1 字节流
- 9.3.1.1 **字节流写数据**
- 9.3.1.2 字节流写数据
- 9.3.1.3 字节缓冲流
- 9.3.2 字符流
- 9.2.2.1 字符流读写文件
- 1. 字符流读文件
- 2. 字符流写文件
- 3. 字符缓冲流
- 9.3.3 IO流总结
- 9.3.4 案例
- 9.3.4.1 **集合到文件**
- 9.3.4.2 文件到集合
- 9.4特殊操作流
- 9.4.1 **对象序列化流**
- 9.4.2 对象反序列化流
- 9.4.3 Properties
- 9.4.4 案例:游戏次数
- 十、多线程
- 10.1 实现多线程
- 10.1.1 进程和线程
- 10.1.2 多线程的实现方式
- 10.1.2.1 实现多线程的两种方式
- 1. 方式一:继承Thread类
- 2. 方式2∶ 实现Runnable接口
- 10.1.2.2 设置和获取线程名称
- 10.1.2.3 线程调度
- 10.1.2.4 线程控制
- 10.1.2.5 线程的生命周期
- 10.2 线程同步
- 10.2.1 `案例`:卖票
- 10.2.2 **同步代码块**
- 10.2.3 **同步方法**
- 10.2.4 线程安全的类
- 10.2.5 Lock锁
- 10.3 生产者 消费者
- 10.3.1 生产者消费者模式概述
- 10.3.2 Object类的等待和唤醒方法
- 10.3.2.1. 案例
- 十一、网络编程
- 11.1 网络编程入门
- 11.1.1 网络编程概述
- 11.1.2 网络编程三要素
- 11.1.3 IP地址
- 11.1.4 端口
- 11.1.5 协议
- 11.2 UDP通信编程
- 11.2.1 **UDP通信原理**
- 11.2.2 **UDP发送数据**
- 11.2.3 **UDP接收数据**
- 11.2.4 **案例**
- 11.3 TCP通信编程
- 11.3.1 TCP 通信原理
- 11.3.2 TCP 发送数据
- 11.3.3 TCP 接收数据
- 11.3.4 案例
- 十二、反射
- 12.1 类加载器
- 12.2 获取class对象
- 12.3 反射方式获取构造方法并使用
- 12.4 反射方式获取成员方法并使用
- 12.5 反射方式获取成员变量并使用
- 十三、Lambda表达式
- 13.1 Lambda表达式的格式
- 13.2 案例
- 13.2.1 案例一
- 13.2.2 案例二
- 13.3 Lambda表达式的省略模式
- 13.4 Lambda表达式的注意事项
- 13.5 Lambda表达式和匿名内部类的区别
前言
提示:部分案例可以直接运行,以下文正文:
Java 核心基础(入门必备,十万字精简)
一、Java编程基本规则
1.1 Java语言编码规范
大括号成对、对齐写
左大括号前有空格
代码缩进
方法和程序块之间空行
并排语句加空格
运算符两侧加空格
1.2 注释规则
- 注释的概念∶对程序作介绍、解释说明的文字
- 注释的作用∶用于介绍、解释说明程序;调试错误
- 注释的分类∶
//单行注释
/*多行注释 */
/** 文档注释 */
1.3 Java常见关键字
- 关键字的概念∶被Java语言赋予特定含义的单词
- 关键字的特点∶全部小写;有特殊的颜色标记
- 学一个记一个,不需要特意去记所有的关键字
1.3.1 用于定义数据类型的关键字
class | interface | enum | @interface | |
---|---|---|---|---|
byte | short | int | long | char |
float | double | boolean | void |
1.3.2 用于定义数据类型值的关键字
true | false | null |
---|---|---|
1.3.3 用于定义流程控制的关键字
if | else | switch | case | default |
---|---|---|---|---|
for | while | do | break | continue |
return |
1.3.4 用于定义访问权限的关键字
public | protected | private |
---|---|---|
tips:
Java中的访问权限修饰符
本类 | 本包 | 子类 | 其它类 | |
---|---|---|---|---|
private | √ | |||
default(默认) | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
1.3.5 用于定义类、函数、变量修饰符的关键字
abstract | final | static | synchronized |
---|---|---|---|
1.3.6 用于定义类与类之间关系的关键字
extends | implements |
---|---|
1.3.7 用于定义建立实例、应用实例、判断实例的关键字
new | this | super | instanceof |
---|---|---|---|
1.3.8 用于处理异常的关键字
try | catch | finally | throw | throws |
---|---|---|---|---|
1.3.9 用于包的关键字
package | import |
---|---|
1.3.10 其它关键字
native | strictfp | transient | volatile | assert |
---|---|---|---|---|
1.4 常量
-
常量的概念;不变化的量,即不会被人为修改的量
-
常量的分类∶
① 字面值常量(掌握)
② 自定义常量(了解)
-
注意事项;区分字符串常量和字符常量
- 字符串常量:“HelloWorld”
- 字符常量: ‘a’
1.4.1 常量的分类
-
字面值常量(掌握)
A∶字符串常量 “HelloWorld”
B∶整数常量 12,-23
C∶小数常量 12.34
D:字符常量 'al,‘0’
E∶布尔常量 true, false
F∶空常量 (了解) nu11
-
自定义常量(了解)
1.5 变量的定义和使用
-
变量的概念∶
在程序执行的过程中,其值可以在某个范围内发生改变的量
变量的本质,是内存中的一小块区域
-
变量定义的格式∶ 数据类型 变量=初始化值
-
数据类型的概念∶变量变化的范围就是数据类型
-
变量的使用∶直接通过变量名来使用变量。
二、Java 基本数据类型和标识符
2.1 8种基本数据类型
总结:
-
数据类型的分类
基本数据类型∶byte、short、int、long、char,float、double,boolean
引用数据类型(了解):类、接口、数组
-
变量的作用域∶ 只在它(定义的位置)所属的代码块内有效
代码块∶一对大括号范围内的代码,称为一个代码块
-
变量定义和使用中的注意事项∶
A 变量未赋值,不能使用
B 变呈只在它所属的范围内有效
C∶一行上可以定义多个变量,但是不建议
2.2 数据类型装换
总结
-
类型转换的分类∶
自动(隐式)类型转换∶
小类型转大类型,自动提升为大类型,运算结果是大类型
强制(显式)类型转换∶
手动将大类型转换成小类型,运算结果是小类型
小类型 变量名 = (小类型)大类型数据;
-
注意:
当且仅当大类型数据可以转换为小类型时,才进行转换,否则会造成精度损失。
2.3 标识符
组成部分:英文大小写字母、数字、下划线( _ )和美元符号($)
2.3.1 定义规则
- 不能以数字开头
- 不能是java关键字
- 严格区分大小写
2.3.2 命名规范
- 类和接口∶首字母大写,如果有多个单词,每个单词首字母大写∶ HelloWorld,Student
- 变量和方法∶首字母小写,如果有多个单词,从第二个单词开始首字母大写∶getName,studyJava
- 常量名(自定义常量)∶所有字母都大写,多个单词用下划线隔开(_)∶ MAX_VALUE
- 包名∶全部小写,如果有多级,用点号(.)隔开,遵循域名反写的格式∶cn.itcast.demo
- 总结 ∶ 驼峰命名,见名知意
2.3.4 总结
-
标识符∶ 给类、方法、变量、常量等起名字的字符序列
-
标识符的组成∶英文大小写字母、数字、下划线(_)和美元符号($)
-
标识符定义规则
不能以数字开头
不能是关键字
严格区分大小写
-
标识符命名规范∶驼峰命名,见名知意
类和接口;变量和方法;常量名(自定义常量);包名;
三、Java 运算符
3.1 运算符概述
-
运算符定义∶对常量和变量进行运算操作的符号
-
常见的运算符∶(1)算术运算符(2)赋值运算符(3)关系运算符
(4)逻辑运算符(5 )三元运算符
-
表达式∶用运算符把常量或变量连接起来的式子
3.2 算术运算符概述
- 常见的算术运算符∶+、-、*、/、%
- Java中整数除以整数,结果还是整数
- /表示两数相除的商,%表示两数相除的余数
- 浮点数参与运算,结果是浮点数类型
3.3 加法运算、自增和自减
3.3.1 加法运算的特点
- 加号两边是数值型数据时,进行加法运算
- 加号两边有任意一边是字符串时,进行字符串的拼
- 字符型数据参与算术运算,是用字符在计算机中存储的数据来运算
3.3.2 自增和自减运算
-
++∶自增1
-
– ∶自减1
-
单独使用∶ 放在变量前或后结果一样
-
参与运算∶
在变量前,先自增(自减),再进行其它运算
在变量后,先以原值进行其它运算,再自增(自减)
特点:
-
++自增1,–自减1
-
单独使用时,放在变量前、后,效果一致
-
与其它操作一起使用时∶
放在变量前,先自增或自减,再进行其它运算
放在变量后,先以原值进行其它运算,再自增或自减
3.4 赋值运算符
总结:
-
常见的赋值运算符∶=、+=、*=、-=、/=、%=
-
扩展赋值运算符的好处∶自动强转
-
注意∶
= 表示赋值操作,不是相等
== 用来表示相等
3.5 关系运算符
总结:
- 关系运算符是用来描述两个变量的大小是否为相等关系的
- 常见的关系运算符∶>、<、==、!=、>=、<=
- 注意∶关系运算符 == 和赋值运算符 = 的区别
3.6 逻辑运算符
总结:
- 用于判断"并且"、“或者”、“除非” 等逻辑关系的运算符
- 逻辑运算符两端连接关系表达式,或逻辑表达式
- 逻辑运算符的运算结果为布尔值∶true或false
- 偶数个逻辑非 ! 结果不变
3.7 三元运算符
又叫 ”三目运算符“ ,即由三部分组成。
-
格式∶ (关系表达式)?表达式1∶表达式2;
-
执行流程∶
关系表达式结果为true,三元运算符结果为表达式1;
关系表达式结果为false,三元运算符结果为表达式2;
四、Java 流程控制结构、顺序结构与循环结构
4.1 选择结构if的三种格式
4.2 switch语句
4.3 三种循环结构
4.4 三种循环结构的区别
4.5 关键字break和continue
break:中断,用于switch语句和循环语句∶
- 在switch语句中,表示结束switch代码块;
- 在循环语句中,表示结束循环。
continue:继续,用于循环语句;表示结束本次循环,继续下次循环。
4.6 循环标号
-
概念∶即循环的名称。
给循环定义一个标号,就可以根据需要结束或跳转到指定循环,常用于多层嵌套循环中
-
语法
标号∶for(){ } // while和do…while举例
break 标号; // 结束指定标号的循环
continue 标号; //跳转到指定标号的循环继续执行
//break举例
public class Label {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("i j");
search: for (int i = 0; i < 3; i++) {
for (int j = 0; j < 50; j++) {
if (j == 3)
break search;
System.out.println(i+" "+j);
}
}
}
}
/* output:
i j
0 0
0 1
0 2
**/
//continue举例
public class Label {
public static void main(String[] args) {
System.out.println("i j");
search:for (int i = 0; i < 3; i++) {
for (int j = 0; j < 50; j++) {
if (j == 3)
continue search;
System.out.println(i+" "+j);
}
}
}
}
/*output:
i j
0 0
0 1
0 2
1 0
1 1
1 2
2 0
2 1
2 2
**/
五、方法和数组
5.1 方法重载(Overload)
存在于同⼀个类中,指⼀个⽅法与已经存在的⽅法名称上相同,但是参数类型、个数、顺序⾄少有⼀个不同。
应该注意的是,返回值不同,其它都相同不算是重载。(方法重载和返回值类型无关)
class OverloadingExample {
public void show(int x) {
System.out.println(x);
}
public void show(int x, String y) {
System.out.println(x + " " + y);
}
public static void main(String[] args) {
OverloadingExample example = new OverloadingExample();
example.show(1);
example.show(1, "2");
}
}
总结:
什么是方法重载 ?
在同一个类中的多个方法,它们的方法名相同,参数列表不同,这样的情况,称为方法重载。
方法重载与返回值类型无关。
参数列表不同 ∶
- 参数的个数不同
- 对应位置的参数类型不同
方法签名∶方法名 + 参数列表
为什么需要方法重载 ?
当实现的功能相同,但具体的实现方式不同时,我们可以通过定义名称相同,参数(条件)不同的方法,来更好的识别和管理类中的方法。
5.2 方法重写(Override)
存在于继承体系中,指⼦类实现了⼀个与⽗类在⽅法声明上完全相同的⼀个⽅法。
为了满⾜⾥式替换原则,重写有以下三个限制:
- ⼦类⽅法的访问权限必须⼤于等于⽗类⽅法;
- ⼦类⽅法的返回类型必须是⽗类⽅法返回类型或为其⼦类型。
- ⼦类⽅法抛出的异常类型必须是⽗类抛出异常类型或为其⼦类型。
使⽤ @Override 注解,可以让编译器帮忙检查是否满⾜上⾯的三个限制条件。
下⾯的示例中,SubClass 为 SuperClass 的⼦类,SubClass 重写了 SuperClass 的 func() ⽅法。其
中:
- ⼦类⽅法访问权限为 public,⼤于⽗类的 protected。
- ⼦类的返回类型为 ArrayList,是⽗类返回类型 List 的⼦类。
- ⼦类抛出的异常类型为 Exception,是⽗类抛出异常 Throwable 的⼦类。
- ⼦类重写⽅法使⽤ @Override 注解,从⽽让编译器⾃动检查是否满⾜限制条件。
总结:
方法重写(Override)
◆定义:
子类中出现和父类方法定义相同的方法的现象
◆解释 :
方法重写也叫方法的复写、覆盖方法名、参数列表、返回值类型都相同
◆ 注意事项 :
- 父类私有方法无法重写
- 子类方法访问权限不能小于父类方法
- 子类不能比父类方法抛出更大的异常(了解)
◆使用场景∶
- 扩展父类功能
- 父类功能过时,重新实现父类功能
class SuperClass {
protected List<Integer> func() throws Throwable {
return new ArrayList<>();
}
}
class SubClass extends SuperClass {
@Override
public ArrayList<Integer> func() throws Exception {
return new ArrayList<>();
}
}
在调⽤⼀个⽅法时,先从本类中查找看是否有对应的⽅法,如果没有再到⽗类中查看,看是否从⽗类继承来。否则就要对参数进⾏转型,转成⽗类之后看是否有对应的⽅法。总的来说,⽅法调⽤的优先级为:
- this.func(this)
- super.func(this)
- this.func(super)
- super.func(super)
/*
A
|
B
|
C
|
D
*/
class A {
public void show(A obj) {
System.out.println("A.show(A)");
}
public void show(C obj) {
System.out.println("A.show(C)");
}
}
class B extends A {
@Override
public void show(A obj) {
System.out.println("B.show(A)");
}
}
class C extends B {
}
class D extends C {
}
public static void main(String[] args) {
A a = new A();
B b = new B();
C c = new C();
D d = new D();
// 在 A 中存在 show(A obj),直接调⽤
a.show(a); // A.show(A)
// 在 A 中不存在 show(B obj),将 B 转型成其⽗类 A
a.show(b); // A.show(A)
// 在 B 中存在从 A 继承来的 show(C obj),直接调⽤
b.show(c); // A.show(C)
// 在 B 中不存在 show(D obj),但是存在从 A 继承来的 show(C obj),将 D 转型成其
⽗类 C
b.show(d); // A.show(C)
// 引⽤的还是 B 对象,所以 ba 和 b 的调⽤结果⼀样
A ba = new B();
ba.show(c); // A.show(C)
ba.show(d); // A.show(C)
}
5.3 方法重载和方法重写的区别
5.4 方法引用
方法引用符
- ∶∶该符号为引用运算符,而它所在的表达式被称为方法引用
回顾一下我们在体验方法引用中的代码
-
Lambda表达式∶ usePrintable(s-> System.out.printIn(s);
分析∶ 拿到参数s 之后通过Lambda表达式,传递给System.out.printIn方法去处理
-
方法引用∶ usePrintable(System.out∶printIn);
分析∶ 直接使用System.out 中的 printIn方法来取代Lambda,代码更加的简洁
推导与省略
- 如果使用Lambda,那么根据"可推导就是可省略"的原则,无需指定参数类型,也无需指定的重载形式,它们都将被自动推导● 如果使用方法引用,也是同样可以根据上下文进行推导
- 方法引用是Lambda的孪生兄弟
5.5 数组
5.5.1 数组的三种定义格式
数组的定义格式一 ∶数据类型 [ ] 数组名= new 数据类型 [长度] ;
定义格式详解∶
**数据类型
**∶即数组中存储元素的数据类型,可以是基
本数据类型,也可以是引用数据类型
[ ]
∶表示数组
**数组名
**∶数组的变量名,遵循标识符命名规范
**new
**∶创建数组的关键字,通过new开辟内存空间
**长度
**∶即数组长度,数组最多能够存放元素的个数。
数组长度在定义时指定,不可更改
//定义一个长度为3的整型数组
int[] arr = new int[3];
数组的定义格式二 ∶
数据类型 [ ] 数组名= new数据类型】伉素1,元素2,元素3…};
格式二的好处∶定义时元素是确定的,避免内存空间的浪费
//定义一个长度为3的整型数组
int[] arr = new int[]{1,2,3};
数组的定义格式三 ∶
数据类型 [ ] 数组名 = (元素1, 元素2,元素3.;
格式三是格式二的变形,简化了代码编写
//定义一个长度为3的整型数组
int[] arr = {1,2,3};
5.5.2 数组的访问
通过数组的索引访问数组的元素
**索引
**∶也叫下标、脚标,是数组元素距离数组起始位置的偏移量
第一个元素的偏移量为0,所以数组的索引从0开始
**格式
**∶数组名 [ 索引 ]
取值∶数组名 [ 索引 ]
赋值∶数组名 [ 索引 ] = 值;
5.5.3 数组的遍历
需求∶给定一个int型数组,输出数组中的每一个元素
分析∶
-
A∶使用格式一定义一个长度为5的int型数组
-
B∶为数组的前三个元素分别赋值为1,2,3
-
C∶使用循环遍历数组
数组的长度∶数组名.length
结论∶
- 数组的最大索引为数组长度-1
- 数组中未手动赋值的元素,有默认值0
- 直接输出数组变量名,得到的是数组的内存地址值
六、类与对象
6.1 类的概念
即归类,分类,是一系列具有相同属性和行为的事物的统称
**属性
**∶品牌、型号、名称…
**行为
**∶打电话、发短信、玩游戏….
抽象:把一系列相关事物共同的属性和行为提取出来的过程
类的特点:1.类是对象的数据类型
2.类是具有相同属性和行为的一组对象的集合
类的定义:
类的重要性∶是Java程序的基本组成单位
类是什么∶是对现实生活中一类具有共同属性和行为的事物的抽象,确定对象将会拥有的属性和行为
类的组成∶ 属性和行为
- 属性∶ 在类中通过成员变量来体现(类中方法外的变量)
- 行为∶在类中通过成员方法来体现(和前面的方法相比去掉satc关键字即可
6.2 对象的概念
某一类事物的某个具体的存在
6.3 类与对象的关系
类∶属性和行为的集合,是一个抽象概念
对象∶是该类事物的具体体现,是一种具体存在
举例∶
手机 → 类
手中的这部魅族手机 → 对象
6.4 Java中的封装体
-
方法
安全性∶调用者不知道方法的具体实现
复用性∶ 方法可以被重复使用
简单化∶将繁多的代码以一个方法的方式呈现,
仅通过调用方法就可以实现功能;代码维护也变得简单
-
类
安全性∶调用者不知道类的具体实现
复用性∶类的对象可以被重复使用
简单化∶类的对象包含了更多的功能,使用起来也更方便
6.4.1 封装的好处
- 提高安全性
- 提高复用性
- 将复杂的事情简单化
6.5 private关键字
私有的,一种访问权限修饰符,用来修饰类的成员
-
特点
被修饰的成员只能在本类中访问
-
用法
private 数据类型 变量名;
private 返回值类型 方法名(参数列表){ }
-
扩展
public,公共的,访问权限修饰符,用来修饰类、成员变量、成员方法等,被修饰的内容可以在任意类中访问
6.5.1 案例:private修饰成员变量
需求∶给Student类的成员变量用private修饰,然后在测试类中正确使用该成员变量
-
分析∶
A∶给成员变量添加private修饰后,测试类中将不能直接访问
B∶由于private的特性,需要在Student类中添加访问该属性的方法,供其它类调用
C∶属性的操作一般都是取值和赋值,所以添加对应的公共方法∶ getXxx() setXxx(参数)
6.6 this关键字
表示本类对象的引用,本质是一个对象
-
特点
每一个普通方法都有一个this,谁调用该方法,this就指向谁
-
用法
this.属性名;
this.方法名(参数);
6.6.1 案例:使用this完善set方法
需求∶ Student类中的set方法的参数名应按规范命名
-
分析∶
A∶将set方法参数名按规范改写后,会与成员变量重
名,使用this关键字进行区分∶
this.name = name;
this.age = age;
B∶在测试类中通过setXxx(参数)方法来赋值
6.6.2 this和super的区别
6.7 构造方法的基本概念
构建、创造,也叫构造器,用来帮助创建对象的方法,准确的说,构造方法的作用是初始化对象。
6.7.1 谁来创建对象 ?
new关键字。Java中通过new关键字创建对象,并在内存中开辟空间,然后使用构造方法(构造器)完成对象的初始化工作。
6.7.2 构造方法的定义
-
格式
修饰符 构造方法名(参数列表){ // 方法体 }
-
要求
方法名必须与类名相同
没有返回值
没有返回值类型
-
功能:
完成对象数据的初始化
-
注意事项
1.若未提供任何构造方法,系统会给出默认无参构造
2.若已提供任何构造方法,系统不再提供无参构造
3.若构造方法重载时自定义了一个带参构造方法,还需要手写一个无参构造方法
4.推荐使用方法,无论是否使用,都写一个无参构造方法
例如:
public class Student { public Student (){ //构造方法的书写内容 } }
6.8 继承
继承的概念:泛指把前人的作风、文化、知识、财产等接受过来
Java中的继承让类与类之间产生父子关系
被继承的类叫做父类(基类、超类)
继承的类叫做子类(派生类)
格式 (extends )
class 父类{
//..
}
class 子类 extends 父类{
//..
}
6.8.1 继承的优缺点
- 优点:
- 功能复用:直接将已有的属性和行为继承过来,实现了功能的复用,节省了大量的工作。
- 便于扩展性功能:在已有的基础上,更容易建立,扩充新功能。
- 结构清晰,简化认识:同属于一个继承体系的相关类,他们之间结构层次清晰,简化了人们对代码结构的认识。
- 易维护性:不同类之间的继承关系,让这些事物之间保持一定程度的一致性,大大降低了维护成本。
- 缺点:
- 破坏了封装性:父类向子类暴露了实现细节,打破了父类对象的封装性。
- 高耦合性:类与类之间紧密的结合在一起,相互依赖性高。
tips:
程序设计的追求:低耦合,高内聚
- 耦合:两个(或更多)模块相互依赖于对方
- 内聚:模块内部结构紧密,独立性强
6.8.2 继承关系中子父类构造方法的使用
需求∶创建对象时,构造方法是如何被调用的?
-
分析∶
A∶定义父类Person,在默认无参构造中输出语句
B∶定义子类Worker,继承Person,在默认无参构造中输出语句
C∶定义测试类,创建子类Worker对象
-
结论∶
创建子类对象时,优先调用父类构造方法
子类构造方法的第一行,隐含语句super(),用于调用父的类默认无参构造
6.8.3 Java中继承的特点
-
单继承
Java只支持类的单继承,但是支持多层(重)继承
Java支持接口的多继承,语法为∶
接口A extends 接口B,接口C,接口D…
-
私有成员不能继承
只能继承父类的非私有成员(成员变量、成员方法)
-
构造方法不能继承
构造方法用于初始化本类对象。
创建子类对象时,需要调用父类构造初始化该对象的父类内容,若父类构造可以被继承,该操作会造成调用的混乱。
-
继承体现了"is a"的关系
子类符合"is a(是一个)"父类的情况下,才使用继承,其它情况不建议使用
6.9 多态
多种状态,同一对象在不同情况小表现出不同的状态或行为
6.9.1 Java中实现多态的步骤
- 要有继承(或实现)关系
- 要有方法重写
- 父类引用指向子类对象(is a 关系)
public class Test{
public static void main(String[] args){
//父类引用指向子类对象
Aminal a = new Dog();
}
}
6.9.2 多态关系中成员变量的使用
-
需求∶子父类中定义了同名的成员变量,如何调用?
-
分析∶
A∶子父类中定义同名属性name井分别初始化值∶
String name;
B∶在测试类中以多态的方式创建对象并打印name属性值∶Animal animal = new Dog();
C∶在测试类中以普通方式创建对象并打印name属性值∶ Dog dog = new Dog();
结论∶成员变量不能重写
public class Test {
public static void main(String[]] args){
Aninal animal = new Dog();
System.out.println(animal.name);
Dog dog =new Dog();
System.out.println(dog.name);
}
}
public class Animal (
String name ="Animal";
}
public class Dog extends Animal {
String name = "Dog";
}
6.9.3 多态的优点和缺点
-
多态的好处
可维护性∶基于继承关系,只需要维护父类代码,提高了代码的复用性,大大降低了维护程序的工作量。
可扩展性∶把不同的子类对象都当作父类看待,屏蔽了不同子类对象间的差异,做出通用的代码,以适应不同的需求,实现了向后兼容。
多态的弊端:不能使用子类特有成员。
6.10 类型转换
当需要使用子类特有功能时,需要进行类型转换
-
向上转型(自动类型转换)
子类型转换成父类型
Animal animal = new Dog();
-
向下转型(强制类型转换)
父类型转换成子类型
Dog dog = (Dog)animal;
public static void main(String[] args){ Dog dog = new Dog(); dog.setName("布鲁斯"); showAnimal(dog);//输出∶市告斯吃骨头 } public static void showAnimal(Animal animal){ if(animal instanceof Dog){ Dog dog =(Dog)animal; dog. watch(); } animal. eat (); }
-
注意事项
1.只能在继承层次内进行转换(ClasCastException)
2.将父类对象转换成子类之前,使用instanceof进行检查
3.有了向上转型才能有向下转型
6.11 抽象类
抽象类的概念
包含抽象方法的类,用abstract修饰
抽象方法的概念
只有方法声明,没有方法体的方法。用abstract修饰
抽象方法的由来
当需要定义一个方法,却不明确方法的具体实现时,可以将方法定义为abstract,具体实现延迟到子类
public abstract class Animal{
private String name;
public abstract void eat();
//getter,setter
}
public class Mouse extends Animal{
public void eat() {
System. out.println(getName()+"吃奶酪");
}
}
public class Dog extends Animal {
public void eat()(
System.out.printIn(getName()+"吃骨头");
}
}
6.11.1 抽象类的特点
-
修饰符∶必须用 abstract 关键字修饰
修饰符 abstract class 类名 { }
修饰符 abstract 返回类型 方法名 {}
-
抽象类不能被实例化,只能创建子类对象
-
抽象类子类的两个选择
重写父类所有抽象方法
定义成抽象类
6.11.2 抽象类成员的特点
-
成员变量∶
可以有普通的成员变量;也可以有成员常量(final)
-
成员方法 ∶
可以有普通方法,也可以有抽象方法;抽象类不一定有抽象方法;有抽象方法的类一定是抽象类(或接口)
-
构造方法∶
像普通类一样有构造方法,且可以重载
6.11.3 抽象类名作为形参和返回值
- 方法的形参是抽象类名,其实需要的是该抽象类的子类对象
- 方法的返回值是抽象类名,其实返回的是该抽象类的子类对象
6.12 final关键字和static关键字
final的概念:最终的,最后的
6.12.1 final的作用
用于修饰类、方法和变量
-
修饰变量∶最终变量,即常量,只能赋值一次
不建议修饰引用类型数据,因为仍然可以通过引用修改对象的内部数据,意义不大
6.12.2 static关键字
static概念:静态的
6.12.3 static的作用
-
用于修饰类的成员 ∶
成员变量∶类变量
成员方法∶类方法
-
调用方式
类名. 成员变量名;
类名.成员方法名(参数);
6.12.4 static修饰成员变量
-
特点∶
被本类所有对象共享
-
需求∶定义研发部成员类,让每位成员进行自我介绍
-
分析∶
A∶研发部成员统称为开发者,定义类Developer。
B∶每位开发者所属部门相同,所以属性
departmentName用static修饰∶
public static String departmentName=“研发部”;
C∶ Developer类的普通属性和行为 ∶
name, work; selfIntroduction();
D∶在测试类中创建对象并使用
E∶ 修改部门名称为"开发部" ,测试效果
-
注意事项
随意修改静态变量的值是有风险的,为了降低风险,可以同时用 final关键字修饰,即公有静态常量(注意命名的变化)∶
6.12.5 static修饰成员方法
-
静态方法∶
静态方法中没有对象this,所以不能访问非静态成员
-
静态方法的使用场景
只需要访问静态成员
不需要访问对象状态,所需参数都由参数列表显示提供
-
需求∶定义静态方法,反转数组中的元素
-
分析∶
A∶先明确定义方法的三要素∶
方法名∶ reverse(反转)
参数列表∶int [ ] arr
返回值类型∶ void
B∶遍历数组,交换数组索引为和length-1-i的元素∶
arr[i] <=> arr[arr.length-1-ij]
C∶当索引**i>= (length-1-i)**时,停止交换元素
D∶在测试类中创建对象并使用
6.13 接口
定义:
-
定义接口使用关键字interface
interface 接口名 { }
-
类和接口是实现关系,用implements表示
class类名 implements 接口名
6.13.1 接口创建对象的特点
-
接口不能实例化
通过多态的方式实例化子类对象
-
接口的子类(实现类)
可以是抽象类,也可以是普通类
6.13.2 接口继承关系的特点
-
接口与接口之间的关系
继承关系,可以多继承,格式∶
接口 extends 接口1,接口2,接口3.…
-
继承和实现的区别
继承体现的是"is a"的关系,父类中定义共性内容
实现体现的是""like a"的关系,接口中定义扩展功能
6.13.3 接口成员方法的特点
-
成员方法
JDK7及以前,公有的、抽象方法∶ public abstract 返回值类型方法名();
JDK8以后,可以有默认方法和静态方法∶ public default 返回值类型方法名(){ } static 返回值类型 方法名() { }
JDK9以后,可以有私有方法∶ private 返回值类型 方法名() { }
6.13.4 接口名作为形参和返回值
- 方法的形参是接口名,其实需要的是该接口的实现类对象
- 方法的返回值是接口名,其实返回的是该接口的实现类对象
6.13.5 接口组成更新
1. 接口组成更新概述
-
常量
public static final
-
抽象方法
public abstract
-
默认方法Java8)
-
静态方法Java8)
-
私有方法(Java9)
2. 接口中默认方法
定义格式∶
- 格式∶ public default 返回值类型 方法名(参数列表){ }
- 范例∶ public default void show3(){}
接口中默认方法的注意事项∶
- 默认方法不是抽象方法,所以不强制被重写。但是可以被重写,重写的时候去掉default关键字
- public可以省略,default不能省略
3. 接口中静态方法
定义格式∶
- 格式∶ public static 返回值类型 方法名(参数列表){}
- 范例∶ publicstatic void show(){ }
接口中静态方法的注意事项∶
- 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
- public可以省略,static不能省略
4. 接口中私有方法
Java9中新增了带方法体的私有方法,这其实在Java8中就埋下了伏笔∶Java8允许在接口中定义带方法体的默认方法和静态方法。这样可能就会引发一个问题∶当两个默认方法或者静态方法中包含一段相同的代码实现时,程序必然考虑将这段实现代码抽取成一个共性方法,而这个共性方法是不需要让别人使用的,因此用私有给隐藏起来,这就是Java9增加私有方法的必然性
接口中私有方法的定义格式∶
-
格式1∶ private 回值类型 方法名(参数列表){ }
范例1∶ private void show){}
-
格式2∶ private static 返回值类型 方法名(参数列表){ }
范例2∶ private static void method(){ }
接口中私有方法的注意事项∶
- 默认方法可以调用私有的静态方法和静态方法
- 静态方法只能调用私有的静态方法
6.14 匿名类
匿名类有两种:
-
与子类有关的匿名类
-
与接口有关的匿名类
1、与子类有关的匿名类
class Demo {
public static void main(String args[]) {
Test test = new Test();
Person person = new Boy();
test.test(person);
person = new Gril();
test.test(person);
test.test(new Boy());
/**
* 这里是与子类有关的匿名类的关键知识点
* 这个匿名类相当与Boy,Gril类,这个是我不需要事先定义这个类,而是我什么时候要用,什么时候写一个匿名类,这种写法代表着其子类
*/
test.test(new Person() {
@Override
void speak() {
System.out.println("与子类有关的匿名类!");
}
});
}
}
abstract class Person {
abstract void speak();
}
class Boy extends Person {
@Override
void speak() {
System.out.println("Boy");
}
}
class Gril extends Person {
@Override
void speak() {
System.out.println("gril");
}
}
class Test {
/**
* 这个类就是纯粹的为了使用多态
*/
void test(Person person) {
person.speak();
}
}
2、与接口有关的匿名类
只要把接口换成子类即可
6.15 内部类
内部类(inner class)是定义在另一个类中的类。
- 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
- 内部类可以对同一个包中的其他类隐藏起来
- 当想定义一个回调函数且不想编写大量代码时,使用匿名(anonymous)内部类比较便捷。
6.15.1 成员内部类
成员内部类就是作为外部类的成员,可以直接使用外部类的所有成员和方法,即使是private。
外部类要访问内部类的所有成员变量或方法,则需要通过内部类的对象来获取
注意:成员内部类不能含有 static 的变量和方法。
成员内部类的定义如下:
public class 外部类{
public class 内部类{}
}
内部类的实例化:
外部类 对象 = new 外部类();
外部类.内部类 对象2=对象.new 内部类();
示例:
class Circle {
double radius = 0;
public Circle(double radius) {
this.radius = radius;
}
class Draw { //内部类
public void drawSahpe() {
System.out.println("drawshape");
}
}
}
这样看起来,类Draw像是类Circle的一个成员,Circle称为外部类。成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
class Circle {
private double radius = 0;
public static int count =1;
public Circle(double radius) {
this.radius = radius;
}
class Draw { //内部类
public void drawSahpe() {
System.out.println(radius); //外部类的private成员
System.out.println(count); //外部类的静态成员
}
}
}
不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类. this .成员变量
外部类. this .成员方法
虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:
class Circle {
private double radius = 0;
public Circle(double radius) {
this.radius = radius;
getDrawInstance().drawSahpe(); //必须先创建成员内部类的对象,再进行访问
}
private Draw getDrawInstance() {
return new Draw();
}
class Draw { //内部类
public void drawSahpe() {
System.out.println(radius); //外部类的private成员
}
}
}
成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:
public class Test {
public static void main(String[] args) {
//第一种方式:
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner(); //必须通过Outter对象来创建
//第二种方式:
Outter.Inner inner1 = outter.getInnerInstance();
}
}
class Outter {
private Inner inner = null;
public Outter() {
}
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
class Inner {
public Inner() {
}
}
}
内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。
比如上面的例子,
- 如果成员内部类Inner用private修饰,则只能在外部类的内部访问,如果用public修饰,则任何地方都能访问;
- 如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;
- 如果是默认访问权限,则只能在同一个包下访问。这一点和外部类有一点不一样,外部类只能被public和包访问两种权限修饰。
- 个人理解:由于成员内部类看起来像是外部类的一个成员,所以可以像类的成员一样拥有多种权限修饰。
6.15.2 局部内部类
局部内部类是指内部类定义在方法和作用域内。通俗来说,就是在外部内的方法中定义的内部类就是局部内部类。
局部内部类由于是在方法中定义的,因此,其作用域也是在方法内部中,方法外执行到,则被JVM回收。局部内部类的实例化也只能在方法中进行。
示例:
public class Method {
public static void main(String[] args) {
Method m = new Method();
m.test();
}
public void test() {
final double pi = 3.14;
int r = 6;
class Circle implements Type {
public double area() {
return pi * 6 * 6;
}
}
Circle c = new Circle();
System.out.println("area=" + c.area());
}
}
interface Type {
public double area();
}
注意:局部内部类方法中想要使用局部变量,该变量必须声明为 final 类型;所以例子中未用 r 成员变量
6.15.3 静态内部类
静态内部类就是修饰为 static 的内部类。声明为 static 的内部类,不需要内部类对象和外部类对象之间的联系,就是说,用户可以直接引用“外部类.内部类”。
静态内部类实例化如下:
外部类.内部类 对象 = new 外部类.内部类()
6.15.4 匿名内部类
匿名内部类是不能有名称的内,所以没办法引用。必须在创建时,作为 new 语句的一部分来声明,如下:
new <类或接口> <类的主体>
匿名内部类形式如下:
new 类或接口{
//方法主体
}
示例:
public class NiMing {
public static void main(String[] args) {
T t = new T() {
@Override
public void t() {
System.out.println("t...");
}
};
t.t();
}
}
interface T {
public void t();
}
匿名内内部类可以当作方法的返回值。
特别注意:
在使用匿名内部类时,要记住以下几个原则。
- 匿名内部类不能有构造方法。
- 匿名内部类不能定义任何静态成员,方法和类。
- 匿名内部类不能使用public,protected,private,static。
- 只能创建匿名内部类的一个实例。
- 一个匿名内部类一定时在 new 后面,用其隐含实现一个接口或实现一个类。
- 因匿名内部类为局部内部类,所以,局部内部类的所有限制都对其有效。
- 内部类只能访问外部类的静态变量或静态方法。
- 内部类当中的 this 指的是匿名内部类本身,如果使用外部类中的 this,则“外部类.this”。
七、Java API
API:(Application Programming Interface)–应用程序编程接口
7.1 Object类
7.2 Scanner类
7.3 String类
7.3.1 字符串的比较:
使用 == 做比较
- 基本类型:比较的是数据值是否相同
- 引用类型:比较的是地址值是否相同
7.4 StringBuilder和StringBuffer类
StringBuilder和String的区别:
String:字符串内容不可变
StringBuilder:字符串内容可变
7.5 Date和Calendar类
7.6 基本类型的包装类
八、集合
8.1 集合的概念
简称集,是用来存储多个元素的容器
8.1.1 集合和数组的区别
-
元素类型
集合∶引用类型(存储基本类型时自动装箱)
数组∶基本类型、引用类型
-
元素个数
集合∶不固定,可任意扩容
数组∶ 固定,不能改变容量
-
集合的好处:
不受容器大小限制,可以随时添加、删除元素提供了大量操作元素的方法(判断、获取等)
8.2 Java的集合体系
8.3 List集合、Set集合、Map集合的特点
8.3.1 List集合
8.3.1.1 LIst集合一般概念
- 特点:
可重复、有序(存储顺序和取出的顺序一致),可以通过索引获取元素
-
应用:
List list = new ArryayList();
-
List集合常用方法:
添加元素:add();
获取集合中元素个数:size()
public class test_01 {
public static void main(String[] args) {
// 创建Collection集合对象
List<String> list = new ArrayList<String>();
// 添加元素
list.add("hello");
list.add("world");
list.add("hello");
list.add("world");
// 输出集合对象
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
}
}
//output:
hello
world
hello
world
8.3.1.2 List集合特有方法
方法名 | 说明 |
---|---|
void add (intindex,E element) | 在此集合中的指定位置插入指定的元素 |
E remove (int index) | 删除指定索引处的元素,返回被删除的元素 |
E set (int index Eelement) | 修改指定索引处的元素,返回被修改的元素 |
E get (int index) | 返回指定索引处的元素 |
8.3.1.3 并发修改异常
-
并发修改异常
ConcurrentModificationException
-
产生原因
迭代器遍历的过程中,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改值和实际修改值不一致
-
解决方案
用for循环遍历,然后用集合对象做对应的操作即可
8.3.1.4 列表迭代器
Listlterator∶ 列表迭代器
- 通过List集合的listIterator()方法得到,所以说它是List集合特有的迭代器
- 用于允许程序员沿任一方向遍历列表的列表迭代器,在迭代期间修改列表,并获取列表中迭代器的当前位置
Listlterator中的常用方法
- E next()∶ 返回迭代中的下一个元素
- boolean hasNext()∶ 如果迭代具有更多元素,则返回 true
- E previous()∶ 返回列表中的上一个元素
- boolean hasPrevious)∶如果此列表迭代器在相反方向遍历列表时具有更多元素,则返回true
- void add(E e)∶ 将指定的元素插入列表
public class test_01 {
public static void main(String[] args) {
// 创建Collection集合对象
List<String> list = new ArrayList<String>();
// 添加元素
list.add("hello");
list.add("world");
// 通过List集合的LisIterator () 得到
ListIterator<String> lit = list.listIterator();
// 正向遍历
while (lit.hasNext()) {
String s = lit.next();
System.out.println(s);
}
System.out.println("-----------");
// 逆向遍历
while (lit.hasPrevious()) {
String s = lit.previous();
System.out.println(s);
}
System.out.println("-----------");
ListIterator<String> lit1 = list.listIterator();
// 迭代修改 -- 不会发生并发异常
while (lit1.hasNext()) {
String L = lit1.next();
if (L.equals("world")) {
lit1.add("java");
}
}
System.out.println(list);
}
}
//output
hello
world
-----------
world
hello
-----------
[hello, world, java]
8.3.1.5 List集合子类特点
List集合常用子类∶ ArrayList,LinkedList
- ArrayList∶ 底层数据结构是数组,查询快,增删慢
- LinkedList∶ 底层数据结构是链表,查询慢,增删快
LinkedList集合的特有功能
方法名 | 说明 |
---|---|
public void addFirst (E e) | 在该列表开头插入指定的元素 |
publicvoid addLast (E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst () | 返回此列表中的第一个元素 |
public E getLast () | 返回此列表中的最后一个元素 |
public E removeFirst () | 从此列表中删除并返回第一个元素 |
public E removeLast () | 从此列表中删除并返回最后一个元素 |
8.3.2 Set集合
8.3.2.1 Set集合的一般概念
-
特点:
不包含重复元素的集合
没有带索引的方法,所以不能使用普通for循环遍历
-
应用:
Set<T> set = new HashSet<>();
示例:
public class test_01 {
public static void main(String[] args) {
// 创建集合对象
Set<String> set = new HashSet<String>(); //HashSet不对集合遍历顺序作任何保证
// 添加元素
set.add("hello");
set.add("world");
// 遍历
for (String s : set) {
System.out.println(s);
}
}
}
//output:
world
hello
//若有重复元素,则不会重复输出
8.3.2.2 哈希值
哈希值∶是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
Object类中有一个方法可以获取对象的哈希值
- publicint hashCode()∶返回对象的哈希码值
对象的哈希值特点
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同
- String类重写了hashCode ()方法,因此中文字符串的哈希值是相同的,英文的字符串的哈希值是不同的
8.3.2.3 HashSet集合
8.3.2.3.1 HashSet集合特点
- 底层数据结构是哈希表
- 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
- 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以是不包含重复元素的集合
public class test_01 {
public static void main(String[] args) {
// 创建集合对象
HashSet<String> hs = new HashSet<String>();
// 添加元素
hs.add("hello");
hs.add("world");
// 遍历
for (String s : hs) {
System.out.println(s);
}
}
}
//output
world
hello
HashSet集合保证元素唯一性源码分析:
8.3.2.3.2 LinkedHashSet集合特点
LinkedHashSet集合特点
- 哈希表和链表实现的Set接口,具有可预测的迭代次序
- 由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
- 由哈希表保证元素唯一,也就是说没有重复的元素
public class study_Test01 {
public static void main(String[] args) {
// 创建集合对象
LinkedHashSet<String> lkhs = new LinkedHashSet<>();
// 添加元素
lkhs.add("hello");
lkhs.add("world");
//测试重复元素 唯一
lkhs.add("world");
// 遍历
for (String s : lkhs) {
System.out.println(s);
}
}
}
//output:
hello
world
8.3.2.4 TreeSet集合
TreeSet集合特点
-
元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法
TreeSet()∶ 根据其元素的自然排序进行排序
TreeSet(Comparator comparator)∶ 根据指定的比较器进行排序
-
没有带索引的方法,所以不能使用普通for循环遍历
-
由于是Set集合,所以不包含重复元素的集合
public class study_Test01 {
public static void main(String[] args) {
// 创建集合对象
TreeSet<Integer> ts = new TreeSet<Integer>();
// 添加元素
ts.add(10);
ts.add(20);
ts.add(5);
ts.add(1);
ts.add(65);
ts.add(100);
//测试重复元素
ts.add(10);
// 遍历
for (Integer s : ts) {
System.out.println(s);
}
}
}
//output
1
5
10
20
65
100
自然排序Comparable的使用
- 存储学生对象并遍历,创建TreeSet集合使用无参构造方法
- 要求∶按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
package YW.demo3;
/*
学生类
*/
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = 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 int compareTo(Student s) {
/*
* 1 正序排列
* 0 重复,只有一个元素
* -1 倒序排列
* */
//按年龄从小到大排列
int num1 = this.age - s.age;
//年龄相同时,按名字排列
int num2 = num1 == 0 ? this.name.compareTo(s.name) : num1;
return num2;
}
}
package YW.demo3;
/*
测试类
*/
import java.util.TreeSet;
public class study_Test01 {
public static void main(String[] args) {
// 创建集合对象
TreeSet<Student> ts = new TreeSet<Student>();
//创建对象
Student s1 = new Student("西施", 25);
Student s2 = new Student("王昭君", 24);
Student s3 = new Student("杨玉环", 23);
Student s4 = new Student("貂蝉", 22);
Student s5 = new Student("杨玉环", 23);
// 添加元素
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
// 遍历
for (Student s : ts) {
System.out.println("姓名:"+s.getName()+" , "+s.getAge()+"岁");
}
}
}
//output
姓名:貂蝉 , 22岁
姓名:杨玉环 , 23岁
姓名:王昭君 , 24岁
姓名:西施 , 25岁
结论
- 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
- 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(To)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
比较器排序Comparator的使用
- 存储学生对象并遍历,创建TreeSet集合使用带参构造方法
- 要求∶按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
package YW.demo4;
/*
学生类
*/
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = 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;
}
}
package YW.demo4;
import java.util.Comparator;
import java.util.TreeSet;
public class test {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
//按年龄从小到大排列
int num1 = s1.getAge() - s2.getAge();
//年龄相同时,按名字排列
int num2 = num1 == 0 ? s1.getName().compareTo(s2.getName()) : num1;
return num2;
}
});
//创建对象
Student s1 = new Student("西施", 25);
Student s2 = new Student("王昭君", 24);
Student s3 = new Student("杨玉环", 23);
Student s4 = new Student("貂蝉", 22);
Student s5 = new Student("杨玉环", 23);
// 添加元素
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
// 遍历
for (Student s : ts) {
System.out.println("姓名:" + s.getName() + " , " + s.getAge() + "岁");
}
}
}
//output
姓名:貂蝉 , 22岁
姓名:杨玉环 , 23岁
姓名:王昭君 , 24岁
姓名:西施 , 25岁
结论
- 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
- 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(To1,To2)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
8.3.3 Map集合的特点
8.3.3.1 Map集合的一般概念
-
特点:
双列集合,元素有键值对(Entry)构成:
Interface Map<K,V> K∶ 键的类型;V∶值的类型
将键映射到值的对象;不能包含重复的键;每个键可以映射到最多一个值
举例∶学生的学号和姓名
YW001 林青霞
YW002 张曼玉
YW003 王祖贤
-
应用:
Map<T1,T2> map = new HashMap<>()
package YW.demo5;
import java.util.HashMap;
import java.util.Map;
public class MapTest {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("YW001", "林青霞");
map.put("YW002", "张曼玉");
map.put("YW003", "王祖贤");
//map.put("YW002", "张曼玉"); //put()出现第二次是修改该key对应的的value
System.out.println(map);
}
}
//output
{YW002=张曼玉, YW003=王祖贤, YW001=林青霞}
8.3.3.2 Map集合的基本功能
方法名 | 说明 |
---|---|
V put (K key,V value) | 添加元素 |
V remove (Object key) | 根据键删除键值对元素 |
void clear () | 移除所有的键值对元素 |
boolean containsKey (Object key) | 判断集合是否包含指定的键 |
boolean containsValue (Object value) | 判断集合是否包含指定的值 |
boolean isEmpty () | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
8.3.3.3 Map集合的获取功能
方法名 | 说明 |
---|---|
V get (Object key) | 根据键获取值 |
Set keySet () | 获取所有键的集合 |
Collection values () | 获取所有值的集合 |
Set<Map.Entry>entrySet () | 获取所有键值对对象的集合 |
8.3.3.4 Map集合的遍历
方式一:
- 获取所有键的集合。用 **keySet()**方法实现
- 遍历键的集合,获取到每一个键。用增强for实现
- 根据键去找值。用 **get(Object key)**方法实现
package YW.demo5;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapTraverse_01 {
public static void main(String[] args) {
//创建集合对象
Map<String, String> map = new HashMap<>();
//添加元素
map.put("YW001", "林青霞");
map.put("YW002", "张曼玉");
map.put("YW003", "王祖贤");
//获取所有键的集合,用 keySet() 方法实现
Set<String> keySet = map.keySet();
//遍历所有键的集合,获取到每一个键,用增强for实现
for (String key : keySet) {
//根据键找值,用get(Object key)方法实现
String value = map.get(key);
System.out.println(key+","+value);
}
}
}
//output
YW002,张曼玉
YW003,王祖贤
YW001,林青霞
方式二:
-
获取所有键值对对象的集合
**Set<Map.Entry<K,V>>entrySet()**∶获取所有键值对对象的集合
-
遍历键值对对象的集合,得到每一个键值对对象
用增强for实现,得到每一个Map.Entry
-
根据键值对对象获取键和值
用**getKey()得到键用getValue()**得到值
package YW.demo5;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapTraverse_02 {
public static void main(String[] args) {
//创建集合对象
Map<String, String> map = new HashMap<>();
//添加元素
map.put("YW001", "林青霞");
map.put("YW002", "张曼玉");
map.put("YW003", "王祖贤");
//获取所有键值对对象的集合
Set<Map.Entry<String, String>> entries = map.entrySet();
//遍历键值对对象的集合,得到每一个键值对对象
for (Map.Entry<String, String> en : entries) {
//根据键值对对象获取键和值
String key = en.getKey();
String value = en.getValue();
System.out.println(key+" , "+value);
}
}
}
//output
YW002,张曼玉
YW003,王祖贤
YW001,林青霞
8.3.3.5 案例:
1.HasMap存储学生对象并遍历
package YW.demo5;
/*
学生类
*/
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = 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;
}
}
package YW.demo5;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapTest {
public static void main(String[] args) {
//创建集合对象
HashMap<String, Student> hs = new HashMap<>();
//创建学生对象
Student s1 = new Student("张曼玉", 20);
Student s2 = new Student("王祖贤", 21);
Student s3 = new Student("周杰伦", 24);
Student s4 = new Student("林青霞", 22);
//添加到集合
hs.put("YW001", s1);
hs.put("YW002", s2);
hs.put("YW003", s3);
hs.put("YW004", s4);
//遍历:1.键找值
Set<String> keySet = hs.keySet();
for (String key:keySet) {
Student value = hs.get(key);
//value是一个Student对象
System.out.println(key+","+value.getName()+","+value.getAge());
}
System.out.println("---------------");
//遍历:2.对象找键和值
//获取所有键值对对象的集合
Set<Map.Entry<String, Student>> entries = hs.entrySet();
//遍历键值对对象的集合,得到每一个键值对对象
for (Map.Entry<String, Student> en : entries) {
//根据键值对对象获取键和值
String key = en.getKey();
Student value = en.getValue();
System.out.println(key+","+value.getName()+","+value.getAge());
}
}
}
//output:
YW004,林青霞,22
YW002,王祖贤,21
YW003,周杰伦,24
YW001,张曼玉,20
---------------
YW004,林青霞,22
YW002,王祖贤,21
YW003,周杰伦,24
YW001,张曼玉,20
HasMap,保证唯一性,要重写Student类中的hasCode() 和 equals() 方法,在Test()中 将对象Student作为key值存储。
2.ArrayList集合存储HasMap元素并遍历
package YW.demo5;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
public class MapTraverse_03 {
public static void main(String[] args) {
//创建ArrayList集合
ArrayList<HashMap<String, String>> array = new ArrayList<>();
//创建HasMap集合
HashMap<String, String> hm1 = new HashMap<>();
hm1.put("孙策", "大乔");
hm1.put("周瑜", "小乔");
array.add(hm1);
//遍历
for(HashMap<String, String> hs : array){
Set<String> keySet = hs.keySet();
for (String key :keySet){
String value = hs.get(key);
System.out.println(key+","+value);
}
}
}
}
//output:
孙策,大乔
周瑜,小乔
3.HasMap集合存储ArrayList元素并遍历
package YW.demo5;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
public class MapTraverse_04 {
public static void main(String[] args) {
//创建HasMap集合对象
HashMap<String, ArrayList<String>> hs = new HashMap<>();
//创建ArrayList集合,并添加元素
ArrayList<String> array = new ArrayList<>();
array.add("林曼玉");
array.add("王祖贤");
//将ArrayList集合添加到HasMap中
hs.put("学生", array);
//遍历集合对象
Set<String> keySet = hs.keySet();
for (String key : keySet) {
System.out.println(key);
ArrayList<String> value = hs.get(key);
for(String s : value){
System.out.println("\t"+s);
}
}
}
}
//output:
学生
林曼玉
王祖贤
8.4 Collection集合和Collections类
8.4.1 Collection集合
8.4.1.1 Collection集合概述
- 是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素
- JDK不提供此接口的任何直接实现,它提供更具体的子接口(如Set和List)实现
8.4.1.2 创建Collection集合的对象
- 多态的方式
- 具体的实现类Arraylist
public class test_01 {
public static void main(String[] args) {
// 创建Collection集合对象
Collection<String> c = new ArrayList<String>();
// 添加元素
c.add("hello");
c.add("world");
// 输出集合对象
System.out.println(c);
}
}
//output:[hello, world]
8.4.1.3 Collection 集合常用方法
方法名 | 说明 |
---|---|
boolean add (E e) | 添加元素 (返回值永远为 true) |
boolean remove (Object o) | 从集合中移除指定的元素 |
void clear (0) | 清空集合中的元素 |
booleancontains (Object o) | 判断集合中是否存在指定的元素 |
boolean isEmpty () | 判断集合是否为空 |
int size () | 集合的长度,也就是集合中元素的个数 |
遍历方式:迭代器 Iterator
8.4.2 Collection 类
**Collections类的概述:**是针对集合操作的工具类
Collections类的常用方法
- publicstatic<Textends Comparable<?superT>>void sort(Listlist)∶将指定的列表按升序排序
- publi staticvoid reverse(List<?> list)∶ 反转指定列表中元素的顺序
- publicstaticvoid shufle(List<?>list)∶ 使用默认的随机源随机排列指定的列表
8.5 增强for循环和迭代器
8.5.1 增强for循环
作用:简化数组和Collection集合的遍历
- 实现Iterable接口的类允许其对象成为增强型for语句的目标
- 它是JDK5之后出现的,其内部原理是一个lterator迭代器
格式:
for(数据类型 变量名 : 数组或者集合对象){
//循环体,变量即元素
}
示例:
int[] arr= {1,2, 3, 4, 5);
for(int i :arr){
System.ut.printIn(i);
}
8.5.1 迭代器
lterator∶ 迭代器,集合的专用遍历方式
作用:对过程的重复,称为迭代。
- lteratoriterator()∶返回此集合中元素的迭代器,通过集合的iterator()方法得到
- 迭代器是通过集合的iterator()方法得到的,所以我们说它是依赖于集合而存在的
迭代器是遍历Collection集合的通用方式,可以在对集合遍历的同时进行添加、删除等操作。
-
迭代器lterator的常用方法
E next()∶返回迭代的下一个元素对象
boolean hasNext()∶如果仍有元素可以迭代,则返回true
public class test_01 {
public static void main(String[] args) {
// 创建Collection集合对象
Collection<String> c = new ArrayList<String>();
// 添加元素
c.add("hello");
c.add("world");
// 输出集合对象
Iterator<String> it = c.iterator();
//迭代下一个元素
System.out.println(it.next());
//是否有元素可以迭代
System.out.println(it.hasNext());
}
}
//output:hello true
8.6 泛型
即泛指任意类型,又叫参数化类型(ParameterizedType),对具体类型的使用起到辅助作用,类似于方法的参数。
一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,然后在使用/调用时传入具体的类型这种参数类型可以用在类、方法和接口中,分别被称为泛型类、泛型方法、泛型接口
8.6.1 泛型定义格式
-
<类型>∶指定一种类型的格式。这里的类型可以看成是形参
-
<类型1,类型2…∶指定多种类型的格式,多种类型之间用逗号隔开。这里的类型可以看成是形参● 将来具体调用时候给定的类型可以看成是实参,并且实参的类型只能是引用数据类型
-
集合类泛型的解释
表示该集合中存放指定类型的元素
-
案例演示(给List集合加上泛型String)
List<String>list = new ArrayList<>0;
-
泛型的好处
类型安全,将运行期间问题提到了编译期间
避免了强制类型转换
8.6.2 泛型类的定义格式
-
格式∶ 修饰符class类名<类型>{ }
public class Generic<T>{ } // 此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
8.6.3 泛型方法的定义格式
-
格式∶修饰符<类型>返回值类型方法名(类型变量名){)
public<T>void show(T t){}
8.6.4 泛型接口的定义格式
-
格式∶修饰符interface接口名<类型>{ }
public interface Generic<T>{}
8.6.5 类型通配符
为了表示各种泛型List的父类,可以使用类型通配符
- 类型通配符∶<?>
- List<?>∶表示元素类型未知的List,它的元素可以匹配任何的类型
- 这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中
如果说我们不希望List<?>是任何泛型List的父类,只希望它代表某一类泛型List的父类,可以使用类型通配符
- 类型通配符上限∶ <?extends 类型>
- List<?extends Number>∶ 它表示的类型是Number或者其子类型
除了可以指定类型通配符的上限,我们也可以指定类型通配符的下限
- 类型通配符下限∶**<?super 类型>**
- List<? super Number>∶它表示的类型是Number或者其父类型
8.6.6 可变参数
可变参数又称参数个数可变,用作方法的形参出现,那么方法参数个数就是可变的了
-
格式∶ 修饰符返回值类型方法名(数据类型… 变量名){}
public static int sum(int…a){ }
九、IO流及异常
9.1 异常及IO流体系
9.1.1 异常
Error∶ 严重问题,不需要处理
Exception∶称为异常类,它表示程序本身可以处理的问题
- RuntimeException∶ 在编译期是不检查的,出现问题后,需要我们回来修改代码
- 非 RuntimeException∶编译期就必须处理的,否则程序不能通过编译,就更不能正常运行了
自定义异常:
格式:
public class 异常类名 extends Exception {
无参构造
带参构造
}
示例:
public class ScoreException extends Exception {
public ScoreException(){}
public ScoreException(String message){
super(message);
}
}
public class Teacher {
public void checkScore(int score) throws ScoreException {
if (score < 0 || score > 100) {
throw new ScoreException("分数有误,应在0--100之间"); //在方法内部抛出异常
} else {
System.out.println("分数正常");
}
}
}
throws 和 throw 的区别
throws | throw |
---|---|
用在方法声明后面,跟的是异常类名 | 用在方法体内,跟的是异常对象名 |
表示抛出异常,由该方法的调用者来处理 | 表示抛出异常,由方法体内的语句处理 |
表示出现异常的一种可能性,并不一定会发生这些异常 | 执行 throw 一定抛出了某种异常 |
9.2 File类
9.2.1 File的概念
概念
文件,文件夹,一个File对象代表磁盘上的某个文件或文件夹
File∶ 它是文件和目录路径名的抽象表示
File类概述和构造方法
- 文件和目录是可以通过File封装成对象的
- 对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已。它可以是存在的,也可以是不存在的。将来是要通过具体的操作把这个路径的内容转换为具体存在的
方法名 | 说明 |
---|---|
File(String pathname) | 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例 |
File(String parent, String child) | 从父路径名字符串和子路径名字符串创建新的 File实例 |
File(File parent,String child) | 从父抽象路径名和子路径名字符串创建新的File实例 |
package YW.demo6;
import java.io.File;
public class FileDemo1 {
public static void main(String[] args) {
File f1 = new File("D:\\IO\\File\\java.txt");
System.out.println(f1);
File f2 = new File("D:\\IO\\File","java.txt");
System.out.println(f2);
File f3 = new File("D:\\IO\\File");
File f4 = new File(f3,"java.txt");
System.out.println(f4);
}
}
//output:
D:\IO\File\java.txt
D:\IO\File\java.txt
D:\IO\File\java.txt
9.2.2 File的创建
方法名 | 说明 |
---|---|
publi boolean createNewFile() | 当具有该名称的文件不存在时,创建一个由该抽象路径名命名的新空文件 。 |
public boolean mkdir() | 创建由此抽象路径名命名的目录 |
public boolean mkdirs() | 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录 |
package YW.demo6;
import java.io.File;
import java.io.IOException;
public class FileDemo2 {
public static void main(String[] args) throws IOException {
//创建一个java.txt的文件
File f1= new File("D:\\IO\\File\\java.txt");
//1文件不存在:创建成功,返回true。2文件存在,创建失败,返回false
System.out.println(f1.createNewFile());
File f2= new File("D:\\IO\\File\\JavaSE");
//1目录不存在:创建成功,返回true。2目录存在,创建失败,返回false
System.out.println(f2.mkdir());
File f3= new File("D:\\IO\\File\\JavaSE\\Html");
//1目录不存在:创建成功,返回true。2目录存在,创建失败,返回false
System.out.println(f3.mkdirs());
}
}
9.2.3 File的功能获取
1.4 File类判断和获取功能
方法名 | 说明 |
---|---|
public boolean isDirectory() | 测试此抽象路径名表示的File是否为目录 |
publicboolean isFile() | 测试此抽象路径名表示的File是否为文件 |
publicboolean exists() | 测试此抽象路径名表示的File是否存在 |
publicString getAbsolutePath() | 返回此抽象路径名的绝对路径名字符串 |
public String getPath() | 将此抽象路径名转换为路径名字符串 |
public String getName() | 返回由此抽象路径名表示的文件或目录的名称 |
public String[] list() | 返回此抽象路径名表示的目录中的文件和目录的名称字符串数组 |
publicFile[] listFiles() | 返回此抽象路径名表示的目录中的文件和目录的Fie对象数组 |
public boolean delete() | 删除此抽象路径名表示的文件或目录 |
9.3 IO流:字节流和字符流
注意:不知道使用哪种流,就是使用字节流(它是万能的)
9.3.1 字节流
9.3.1.1 字节流写数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TqBCYkn1-1646387778303)(Java 核心基础.assets/
)]
字节流抽象基类
- InputStream∶ 这个抽象类是表示字节输入流的所有类的超
- OutputStream∶这个抽象类是表示字节输出流的所有类的超类
- 子类名特点∶子类名称都是以其父类名作为子类名的后缀
FileOutputStream∶ 文件输出流用于将数据写入File
- FileOutputStream(Stringname)∶创建文件输出流以指定的名称写入文件
package YW.demo6;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileDemo3 {
public static void main(String[] args) throws IOException {
//创建字节输出流对象
FileOutputStream fos = new FileOutputStream("Study\\java.txt");
fos.write(97);
//释放资源
fos.close();
}
}
字节流写数据的方式:
方法名 | 说明 |
---|---|
void write(int b) | 将指定的字节写入此文件输出流;一次写一个字节数据 |
void write(byte[] b) | 将 b.length字节从指定的字节数组写入此文件输出流;一次写一个字节数组数据 |
void write(byte[] b,int off,int len) | 将len字节从指定的字节数组开始,从偏移量of开始写入此文件输出流;一次写一个字节数组的部分数据 |
package YW.demo6;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileDemo4 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("src\\java.txt");
fos.write(97);
fos.write(98);
fos.write(99);
fos.write(100);
//字节数组
byte[] bytes1 = {97, 98, 99, 100, 101};
byte[] bytes2 = "abcdef".getBytes();
fos.write(bytes1);
fos.write(bytes2);
fos.write(bytes2,3,4);
fos.close();
}
}
字节流写数据的两个小问题
字节流写数据如何实现换行呢?
写完数据后,加换行符
- windows:\r\n
- linux:\n
- mac:r
字节流写数据如何实现追加写入呢?
-
publicFileOutputStream(Stringname,boolean append)
-
创建文件输出流以指定的名称写入文件。如果第二个参数为true,则字节将写入文件的末尾而不是开头
package YW.demo6;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileDemo5 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("src\\java.txt",true);
for (int i = 0; i < 10; i++) {
fos.write("hello".getBytes());
//换行
fos.write("\n".getBytes());
}
fos.close();
}
}
加入异常处理:
package YW.demo6;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileDemo6 {
public static void main(String[] args) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream("src\\java.txt", true);
//fos = new FileOutputStream("X:\\Project documents\\Java\\idea\\Study\\src\\java.txt", true);
fos.write("hello".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) { //X盘符不存在,避免空指针异常
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
9.3.1.2 字节流写数据
字节流读数据(一次读一个字节数据)
需求∶把文件java.txt中的内容读取出来在控制台输出
FilelnputStream∶ 从文件系统中的文件获取输入字节
- FilelnputStream(String name)∶通过打开与实际文件的连接来创建一个FilelnputStream,该文件由文件系统中的路径名 name命名
使用字节输入流读数据的步骤∶
- 创建字节输入流对象
- 调用字节输入流对象的读数据方法
- 释放资源
package YW.demo6;
import java.io.FileInputStream;
import java.io.IOException;
public class FileDemo7 {
public static void main(String[] args) throws IOException {
//创建字节输入流对象
FileInputStream fis = new FileInputStream("src\\java.txt");
//读取数据
/*
int read = fis.read();//一次只能读取一个字符
System.out.println((char)read);
*/
//循环读取 读取到末尾为 -1
int read;
while((read = fis.read()) != -1){
System.out.print((char) read);
}
//释放资源
fis.close();
}
}
//output:
abcdef123
案例:复制文本文件
需求∶把"java.txt"复制到模块目录下的test.txt"
思路∶
- 根据数据源创建字节输入流对象
- 根据目的地创建字节输出流对象
- 读写数据,复制文本文件(一次读取一个字节,一次写入一个字节)
- 释放资源
package YW.demo6;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileDemo8 {
public static void main(String[] args) throws IOException {
//1. 根据数据源创建字节输入流对象
FileInputStream fin = new FileInputStream("src\\java.txt");
//2. 根据目的地创建字节输出流对象
FileOutputStream fos = new FileOutputStream("src\\java.txt");
//3. 读写数据,复制文本文件(一次读取一个字节,一次写入一个字节)
int read;
while((read = fin.read())!=-1){
fos.write(read);
}
//4. 释放资源
fos.close();
fin.close();
}
}
一次读取一个字节数组的数据
package YW.demo6;
import java.io.FileInputStream;
import java.io.IOException;
public class FileDemo9 {
public static void main(String[] args) throws IOException {
FileInputStream fin = new FileInputStream("src\\java.txt");
byte[] bs = new byte[1024];//一般给的是1024及其整数倍
int read ;
while((read = fin.read(bs))!=-1){
System.out.print(new String(bs,0,read));
}
fin.close();
}
}
9.3.1.3 字节缓冲流
字节缓冲流∶
-
BufferOutputStream∶该类实现缓冲输出流。通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用
-
BufferedInputStream∶创建BufferedInputStream将创建一个内部缓冲区数组。当从流中读取或游过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节
构造方法:
-
字节缓冲输出流∶ BufferedOutputStream(OutputStream out)
-
字节缓冲输入流∶ BufferedInputStream(InputStreamin)
为什么构造方法需要的是字节流,而不是具体的文件或者路径呢?
- 字节缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本的字节流对象进行操作
package YW.demo6;
import java.io.*;
public class FileDemo10 {
public static void main(String[] args) throws IOException {
//第一种
/*
FileOutputStream fos = new FileOutputStream("D:\\Project documents\\Java\\idea\\Study\\src\\java.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
*/
//第二种
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\Project documents\\Java\\idea\\Study\\src\\java.txt"));
bos.write("Hello\r\n".getBytes());
bos.write("world\r\n".getBytes());
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\Project documents\\Java\\idea\\Study\\src\\java.txt"));
//1.1次读取一个字节的数据
int read;
while ((read = bis.read()) != -1) {
System.out.print((char) read);
}
//2.1次读取一个字节数组的数据
byte[] bys = new byte[1024];
int len;
while ((len = bis.read(bys)) != -1) {
System.out.print(new String(bys, 0, len));
}
//释放资源
bos.close();
bis.close();
}
}
案例:四种方式实现复制文件,并记录时间
package YW.demo6;
import java.io.*;
/**
* 四种方式实现,并记录时间
* 1.基本字节流一次读取一个字节 共耗时: 22004毫秒
* 2.基本字节流一次读取一个字节数组 共耗时: 30毫秒
* 3.字节缓冲流一次读取一个字节 共耗时: 55毫秒
* 4.字节缓冲流一次读取一个字节数组 共耗时: 9毫秒
*/
public class FileDemo11 {
public static void main(String[] args) throws IOException {
//记录开始时间
long startTime = System.currentTimeMillis();
//复制文件
//method1();
//method2();
//method3();
method4();
//记录结束时间
long endTime = System.currentTimeMillis();
System.out.println("共耗时: " + (endTime - startTime) + "毫秒");
}
//1.基本字节流一次读取一个字节
public static void method1() throws IOException {
FileInputStream fin = new FileInputStream("src\\java.txt");
FileOutputStream fos = new FileOutputStream("src\\test.txt");
int by;
while ((by = fin.read()) != -1) {
fos.write(by);
}
fos.close();
fin.close();
}
//2.基本字节流一次读取一个字节数组
public static void method2() throws IOException {
FileInputStream fin = new FileInputStream("src\\java.txt");
FileOutputStream fos = new FileOutputStream("src\\test.txt");
byte[] byt = new byte[1024];
int by;
while ((by = fin.read(byt)) != -1) {
fos.write(byt,0,by);
}
fos.close();
fin.close();
}
//3.字节缓冲流一次读取一个字节
public static void method3() throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src\\java.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src\\test.txt"));
int by;
while ((by = bis.read()) != -1) {
bos.write(by);
}
bis.close();
bos.close();
}
//4.字节缓冲流一次读取一个字节数组
public static void method4() throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src\\java.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src\\test.txt"));
byte[] byt = new byte[1024];
int by;
while ((by = bis.read(byt)) != -1) {
bos.write(byt,0,by);
}
bos.close();
bis.close();
}
}
9.3.2 字符流
为什么会出现字符流
由于字节流操作中文不是特别的方便,所以ava就提供字符流
- 字符流 = 字节流 + 编码表
用字节流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文,如何识别是中文的呢?
- 汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数
tips:GBK:中文字符占2个字节
UTF-8:中文字符占3个字节
字符流中的编码解码
package YW.demo6;
import java.io.*;
public class FileDemo12 {
public static void main(String[] args) throws IOException {
// IDEA默认UTF-8编码
OutputStreamWriter osw1 = new OutputStreamWriter(new FileOutputStream("src\\java.txt"));
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream("src\\java.txt"), "GBK");
// IDEA默认UTF-8编码
InputStreamReader isr = new InputStreamReader(new FileInputStream("src\\java.txt"), "GBK");
osw1.write("中国");
osw2.write("世界");
int read;
while ((read = isr.read()) != -1) {
System.out.print((char) read);
}
isr.close();
osw2.close();
osw1.close();
}
}
9.2.2.1 字符流读写文件
1. 字符流读文件
总结:字符流读数据的2种方式
方法名 | 说明 |
---|---|
int read() | 一次读一个字符数据 |
int read(char[] cbuf) | 一次读一个字符数组数据 |
示例:
package YW.demo6;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class FileDemo14 {
public static void main(String[] args) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("src\\java.txt"));
//1.一次读一个字符数据
int read;
while((read = isr.read())!=-1){
System.out.print((char) read);
}
//2.一次读一个字符数组数据
char[] ch = new char[1024];
int read1 ;
while((read1 = isr.read(ch))!=-1){
System.out.print(new String(ch,0,read1));
}
//释放资源
isr.close();
}
}
2. 字符流写文件
总结:字符流写数据的5种方式
方法名 | 说明 |
---|---|
void write(int c) | 写一个字符 |
void write(char[] cbuf) | 写入一个字符数组 |
void write(char[] cbuf,int off,int len) | 写入字符数组的一部分 |
void write(String str) | 写一个字符串 |
void write(String str, int off, int len) | 写一个字符串的一部分 |
示例:
package YW.demo6;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class FileDemo13 {
public static void main(String[] args) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("src\\java.txt"));
//写一个字符
osw.write(97);
//写入一个字符数组
char[] ch = {'a','b','c','d','e'};
osw.write(ch);
//写入字符数组的一部分
osw.write(ch,0,3);
//写一个字符串
osw.write("java学习");
//写一个字符串的一部分
osw.write("java学习",0,4);
//刷新流
osw.flush();
//释放资源
osw.close();//自带刷新
}
}
//java.txt:
aabcdeabcjava学习java
案例:复制java文件
package YW.demo6;
import java.io.*;
public class FileDemo15 {
public static void main(String[] args) throws IOException {
//根据数据源创建字符输入流对象
InputStreamReader isr = new InputStreamReader(new FileInputStream("src\\java.txt"));
//根据目的地创建字符输出流对象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("src\\copy.txt"));
//读写数据,copy文件
//1.一次读取一个字符数据
int read;
while((read = isr.read())!=-1){
osw.write(read);
}
//2.一次读取一个字符数组
char[] ch = new char[1024];
int read1;
while((read1 = isr.read(ch))!=-1){
osw.write(ch,0,read1);
}
//释放资源
osw.close();
isr.close();
}
}
改进版:
分析∶
- 转换流的名字比较长,而我们常见的操作都是按照本地默认编码实现的,所以,为了简化书写,转换流提供了对应的子类
- FileReader∶ 用于读取字符文件的便捷类 FileReader(String fileName)
- FileWriter∶ 用于写入字符文件的便捷类 FileWriter(String fileName)
- 数据源和目的地的分析
- 数据源∶myCharStream\ConversionStreamDemojava–数据–Reader–InputStreamReader-FileReader
- 目的地∶ myCharStream\Copyjava-写数据–Writer–OutputStreamWriter-FileWriter
代码:
package YW.demo6;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileDEmo16 {
public static void main(String[] args) throws IOException {
//根据数据源创建字符输入流对象
FileReader fr = new FileReader("src\\java.txt");
//根据目的地创建字符输出流对象
FileWriter fw = new FileWriter("src\\java.txt");
//读写数据,copy文件
//1.一次读取一个字符数据
int read;
while ((read = fr.read()) != -1) {
fw.write(read);
}
//2.一次读取一个字符数组
char[] ch = new char[1024];
int read1;
while ((read1 = fr.read(ch)) != -1) {
fw.write(ch, 0, read1);
}
//释放资源
fw.close();
fr.close();
}
}
3. 字符缓冲流
总结:
流字符缓冲流∶
-
BufferedWriter∶将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途
-
BufferedReader∶ 从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途
构造方法∶
-
BufferedWriter(Writer out)
-
BufferedReader(Reader in)
package YW.demo6;
import java.io.*;
public class FileDemo17 {
public static void main(String[] args) throws IOException {
/*
FileWriter fw = new FileWriter("src/java.txt");
BufferedWriter bw = new BufferedWriter(fw);
*/
BufferedWriter bw = new BufferedWriter(new FileWriter("src\\java.txt"));
BufferedReader br = new BufferedReader(new FileReader("src\\java.txt"));
//写入数据
bw.write("hello\r\n");
bw.write("world\r\n");
//读取数据
//1.一次读一个字符数据
int read;
while ((read = br.read()) != -1) {
System.out.print((char) read);
}
//2.一次读一个字符数组
char[] ch = new char[1024];
int read1;
while ((read1 = br.read(ch)) != -1) {
System.out.print(new String(ch, 0, read1));
}
//释放资源
br.close();
bw.close();
}
}
字符缓冲流特有功能
BufferedWriter:
- void newLine()∶ 写一行行分隔符,行分隔符字符串由系统属性定义
BufferedReader:
- publicStringreadLine()∶读一行文字。结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null
package YW.demo6;
//void newLine()∶ 写一行行分隔符,行分隔符字符串由系统属性定义
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class FileDemo18 {
public static void main(String[] args) throws IOException {
//创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("src\\java.txt"));
//写入数据
for (int i = 0; i < 10; i++) {
bw.write("hello world");
bw.newLine();
bw.flush();
}
//释放资源
bw.close();
}
}
package YW.demo6;
//publicStringreadLine()∶读一行文字。结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class FileDemo19 {
public static void main(String[] args) throws IOException {
//创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("src\\java.txt"));
//读取数据
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
//释放资源
br.close();
}
}
案例:改进版–复制java.txt文件
使用字符缓冲流的特有功能
package YW.demo6;
/*
* 1∶根据数据源创建字符缓冲输入流对象
* 2∶根据目的地创建字符缓冲输出流对象
* 3∶读写数据,复制文件
使用字符缓冲流特有功能实现
4∶释放资源
* */
import java.io.*;
public class FileDemo20 {
public static void main(String[] args) throws IOException {
// 1∶根据数据源创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("src\\java.txt"));
//2∶根据目的地创建字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("src\\copy.txt"));
//3∶读写数据,复制文件
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
//4∶释放资源
bw.close();
br.close();
}
}
9.3.3 IO流总结
9.3.4 案例
9.3.4.1 集合到文件
需求∶把Arylist集合中的字符串数据写入到文本文件。要求∶每一个字符串元素作为文件中的一行数据
思路∶
- 创建ArrayList集合
- 往集合中存储字符串元素
- 创建字符缓冲输出流对象
- 遍历集合,得到每一个字符串数据
- 调用字符缓冲输出流对象的方法写数据
- 释放资源
package YW.demo6;
/*
1. 创建ArrayList集合
2. 往集合中存储字符串元素
3. 创建字符缓冲输出流对象
4. 遍历集合,得到每一个字符串数据
5. 调用字符缓冲输出流对象的方法写数据
6. 释放资源
*/
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
public class FileDemo21 {
public static void main(String[] args) throws IOException {
//1.创建ArrayList集合
ArrayList<String> array = new ArrayList<>();
//2.往集合中存储字符串元素
array.add("hello");
array.add("world");
array.add("java");
//3.创建字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("src\\java.txt"));
//4.遍历集合,得到每一个字符串数据
for (String s : array) {
//5.调用字符缓冲输出流对象的方法写数据
bw.write(s);
bw.newLine();
bw.flush();
}
//6.释放资源
bw.close();
}
}
9.3.4.2 文件到集合
点名器:
需求∶我有一个文件里面存储了班级同学的姓名,每一个姓名占一行,要求通过程序实现随机点名器思路∶
- 创建字符缓冲输入流对象
- 创建ArrayList集合对象
- 调用字符缓冲输入流对象的方法读数据
- 把读取到的字符串数据存储到集合中
- 释放资源
- 使用Random产生一个随机数,随机数的范围在∶[0,集合的长度)
- 把第6步产生的随机数作为索引到ArrayList集合中获取值
- 把第7步得到的数据输出在控制台
package YW.demo6;
/*
1. 创建字符缓冲输入流对象
2. 创建ArrayList集合对象
3. 调用字符缓冲输入流对象的方法读数据
4. 把读取到的字符串数据存储到集合中
5. 释放资源
6. 使用Random产生一个随机数,随机数的范围在∶[0,集合的长度)
7. 把第6步产生的随机数作为索引到ArrayList集合中获取值
8. 把第7步得到的数据输出在控制台
*/
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;
public class FileDemo22 {
public static void main(String[] args) throws IOException {
//1. 创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("src\\java.txt"));
//2.创建ArrayList集合对象
ArrayList<String> array = new ArrayList<>();
//3. 调用字符缓冲输入流对象的方法读数据
String line;
while ((line = br.readLine()) != null) {
//4. 把读取到的字符串数据存储到集合中
array.add(line);
}
//5.释放资源
br.close();
// 6. 使用Random产生一个随机数,随机数的范围在∶[0,集合的长度)
Random r = new Random();
int index = r.nextInt(array.size());
//7. 把第6步产生的随机数作为索引到ArrayList集合中获取值
String name = array.get(index);
//8. 把第7步得到的数据输出在控制台
System.out.println("幸运者是:" + name);
}
}
9.4特殊操作流
9.4.1 对象序列化流
对象序列化流∶ ObjectOutputStream
- 将Java对象的原始数据类型和图形写入OutputStream。可以使用ObjectInputStream读取(重构)对象。可以通过使用流的文件来实现对象的持久存储。如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象
构造方法∶
- ObjectOutputStream(OutputStreamout)∶创建一个写入指定的OutputStream的ObjectOutputStream
序列化对象的方法∶
- void writeObject(Objectobj)∶ 将指定的对象写入ObjectOutputStream
注意∶
-
一个对象要想被序列化,该对象所属的类必须必须实现Serializable接口
-
Serializable是一个标记接口,实现该接口,不需要重写任何方法
package YW.demo7;
import java.io.Serializable;
public class Student implements Serializable {
String name;
int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = 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;
}
}
package YW.demo7;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Demo1 {
public static void main(String[] args) throws IOException {
//对象序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src\\java.txt"));
//创建对象
Student s =new Student("林青霞",25);
oos.writeObject(s);
oos.close();
}
}
9.4.2 对象反序列化流
** ObjectInputStream**
- ObjectInputStream反序列化先前使用ObjectOutputStream编写的原始数据和对象
构造方法∶
- ObjectInputStream(InputStreamin)∶创建从指定的InputStream读取的ObjectInputStream
反序列化对象的方法∶
- Object readObject()∶ 从ObjectInputStream读取一个对象
package YW.demo7;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Demo2 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src\\oos.txt"));
Object object = ois.readObject();
Student s = (Student) object;
System.out.println(s.getName() + "," + s.getAge());
ois.close();
}
}
//output
林青霞,25
9.4.3 Properties
Properties概述∶
-
是一个Map体系的集合类
-
Properties可以保存到流中或从流中加载
示例∶ Properties作为Map集合的使用
package YW.demo7;
import java.util.Properties;
import java.util.Set;
public class Demo3 {
public static void main(String[] args) {
//创建集合对象
Properties prop = new Properties();
//添加元素
prop.put("YW001", "林青霞");
prop.put("YW002", "王祖贤");
prop.put("YW003", "周杰伦");
//遍历集合
Set<Object> keySet = prop.keySet();
for (Object key: keySet){
Object value = prop.get(key);
System.out.println(key+","+value);
}
}
}
//output:
YW002,王祖贤
YW003,周杰伦
YW001,林青霞
Properties作为集合的特有方法:
方法名 | 说明 |
---|---|
Object setProperty(String key,String value) | 设置集合的键和值,都是String类型,底层调用Hashtable方法put |
String getProperty(String key) | 使用此属性列表中指定的键搜索属性 |
SetstringPropertyNames() | 从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串 |
package YW.demo7;
import java.util.Properties;
import java.util.Set;
public class Demo4 {
public static void main(String[] args) {
Properties prop = new Properties();
//1.设置集合的键和值,都是String类型,底层调用Hashtable方法put
prop.setProperty("YW001", "林青霞");
prop.setProperty("YW002", "王祖贤");
prop.setProperty("YW003", "周杰伦");
System.out.println(prop);
//2.使用此属性列表中指定的键搜索属性
System.out.println(prop.getProperty("YW001"));
//3.从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串
Set<String> names = prop.stringPropertyNames();
for (String key : names) {
String value = prop.getProperty(key);
System.out.println(key + "," + value);
}
}
}
//output:
{YW002=王祖贤, YW003=周杰伦, YW001=林青霞}
林青霞
YW002,王祖贤
YW003,周杰伦
YW001,林青霞
Properties和IO流结合的方法:
方法名 | 说明 |
---|---|
void load(InputStream inStream) | 从输入字节流读取属性列表(键和元素对) |
void load(Reader reader) | 从输入字符流读取属性列表(键和元素对) |
void store(OutputStream out, String comments) | 将此属性列表(键和元素对)写入此Properties表中,以适合于使用load(InputStream)方法的格式写入输出字节流 |
void store(Writer writer, String comments) | 将此属性列表(键和元素对)写入此Properties表中,以适合使用 load(Reader)方法的格式写入输出字符流 |
package YW.demo7;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
public class DEmo5 {
public static void main(String[] args) throws IOException {
//把集合数据保存到文件
myStore();
//把文件中数据加载到集合
myLoad();
}
private static void myLoad() throws IOException {
Properties prop = new Properties();
FileReader fr = new FileReader("src\\fw.txt");
prop.load(fr);
fr.close();
System.out.println(prop);
}
private static void myStore() throws IOException {
Properties prop = new Properties();
prop.setProperty("YW001", "林青霞");
prop.setProperty("YW002", "王祖贤");
prop.setProperty("YW003", "周杰伦");
FileWriter fw = new FileWriter("src\\fw.txt");
prop.store(fw, null);
fw.close();
}
}
//output:
{YW002=王祖贤, YW003=周杰伦, YW001=林青霞}
9.4.4 案例:游戏次数
需求∶请写程序实现猜数字小游戏只能试玩3次,如果还想玩,提示∶游戏试玩已结束,想玩请充值(www.xxxx)
思路∶
-
写一个游戏类,里面有一个猜数字的小游戏
-
写一个测试类,测试类中有main()方法,main()方法中按照下面步骤完成∶
A∶从文件中读取数据到Properties集合,用load()方法实现
文件已经存在∶ game.txt
里面有一个数据值∶count=0
B∶通过Properties集合获取到玩游戏的次数
C∶判断次数是否到到3次了
如果到了,给出提示∶游戏试玩已结束,想玩请充值(www.xxxx)
如果不到3次∶玩游戏
次数+1,重新写回文件,用Properties的store()方法实现
package YW.demo7;
//游戏类
import java.util.Random;
import java.util.Scanner;
public class GuessNumber {
private GuessNumber(){
}
public static void start(){
Random r =new Random();
int number = r.nextInt(100) + 1;
while(true){
Scanner sc = new Scanner(System.in);
System.out.println("请输入你要猜的数字:");
int guessNumber = sc.nextInt();
if(guessNumber>number){
System.out.println("你猜的数字"+guessNumber+"大了");
}else if(guessNumber<number){
System.out.println("你猜的数字"+guessNumber+"小了");
}else{
System.out.println("恭喜你猜中了");
break;
}
}
}
}
package YW;
import YW.demo7.GuessNumber;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
public class Demo8 {
public static void main(String[] args) throws IOException {
Properties prop = new Properties();
FileReader fr = new FileReader("src\\game.txt");
prop.load(fr);
fr.close();
String count = prop.getProperty("count");
int number = Integer.parseInt(count);
if (number >= 3) {
System.out.println("已经试玩3次,继续玩,请充值,www.xxxx.top");
}else{
GuessNumber.start();
number++;
prop.setProperty("count",String.valueOf(number));
FileWriter fw =new FileWriter("src\\game.txt");
prop.store(fw,null);
fw.close();
}
}
}
十、多线程
10.1 实现多线程
10.1.1 进程和线程
进程∶是正在运行的程序
- 是系统进行资源分配粕调用的独立单位
- 每一个进程都有它自己的内存空间和系统资源
线程∶是进程中的单个顺序控制流,是一条执行路径
- 单线程∶一个进程如果只有一条执行路径,则称为单线程程序
- 多线程∶一个进程如果有多条执行路径,则称为多线程程序
举例:
- 记事本程序
- 扫雷程序
10.1.2 多线程的实现方式
10.1.2.1 实现多线程的两种方式
1. 方式一:继承Thread类
- 定义一个类MyThread继承Thread类
- 在MyThread类中重写run()方法
- 创建MyThread类的对象
- 启动线程
两个小问题∶
- 为什么要重写run()方法?
因为run()是用来封装被线程执行的代码
- run()方法和start()方法的区别?
run()∶封装线程执行的代码,直接调用,相当于普通方法的调用
start()∶ 启动线程;然后由JVM调用此线程的run()方法
package YW.Demo9;
//1. 定义一个类MyThread继承Thread类
public class MyThread extends Thread {
2. 在MyThread类中重写run()方法
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println(i);
}
}
}
package YW.Demo9;
/*
1. 定义一个类MyThread继承Thread类
2. 在MyThread类中重写run()方法
3. 创建MyThread类的对象
4. 启动线程
*/
public class ThreadDemo1 {
public static void main(String[] args) {
//创建MyThread类的对象
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
// 4. 启动线程
thread1.start();
thread2.start();
}
}
2. 方式2∶ 实现Runnable接口
- 定义一个类MyRunnable实现Runnable接口
- 在MyRunnable类中重写run()方法
- 创建MyRunnable类的对
- 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
- 启动线程
package YW.Demo9;
//1. 定义一个类MyRunnable实现Runnable接口
public class MyRunnable implements Runnable{
@Override
public void run() {
//2. 在MyRunnable类中重写run()方法
for (int i = 0;i<30;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
package YW.Demo9;
/*
1. 定义一个类MyRunnable实现Runnable接口
2. 在MyRunnable类中重写run()方法
3. 创建MyRunnable类的对
4. 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
5. 启动线程
*/
public class MyRunnableDemo {
public static void main(String[] args) {
//3. 创建MyRunnable类的对
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr,"lota");//设置线程名称
// 5. 启动线程
t1.start();
t2.start();
}
}
总结:
多线程的实现方案有两种
- 继承Thread类
- 实现Runnable接口
相比继承Thread类,实现Runnable接口的好处
- 避免了Java单继承的局限性
- 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
10.1.2.2 设置和获取线程名称
Thread类中设置和获取线程名称的方法
- void setName(String name)∶将此线程的名称更改为等于参数name
- String getName()∶返回此线程的名称
- 通过构造方法也可以设置线程名称
如何获取main()方法所在的线程名称?
- publicstaticThread currentThread)∶返回对当前正在执行的线程对象的引用
package YW.Demo9;
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
package YW.Demo9;
public class ThreadDemo1 {
public static void main(String[] args) {
//第一种
MyThread thread1 = new MyThread("lota");
MyThread thread2 = new MyThread("suha");
//第2种
//thread1.setName("lota");
//thread2.setName("suha");
thread1.start();
thread2.start();
}
}
10.1.2.3 线程调度
线程有两种调度模型
- 分时调度模型∶ 所有线程轮流使用CPU 的使用权,平均分配每个线程占用CPU 的时间片
- 抢占式调度模型∶优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程
获取的 CPU 时间片相对多一些
Java使用的是抢占式调度模型
假如计算机只有一个CPU,那么CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的
Thread类中设置和获取线程优先级的方法
-
public finalint getPriority()∶ 返回此线程的优先级
-
publicfinalvoid setPriority(intnewPriority∶更改此线程的优先级
-
线程默认优先级是5; 线程优先级的范围是∶ 1-10
线程优先级高仅仅表示线程获取的CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果
package YW.Demo9;
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
package YW.Demo9;
public class ThreadDemo1 {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
MyThread thread3 = new MyThread();
thread1.setName("lota");
thread2.setName("suha");
thread3.setName("mark");
/* //获取线程的优先级
System.out.println(thread1.getPriority()); //5
System.out.println(thread2.getPriority()); //5
System.out.println(thread3.getPriority()); //5
*/
//设置线程优先级 1--10
thread1.setPriority(5);
thread2.setPriority(10);
thread3.setPriority(1);
//启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
10.1.2.4 线程控制
方法名 | 说明 |
---|---|
static void sleep(long millis) | 使当前正在执行的线程停留(暂停执行)指定的毫秒数 |
void join() | 等待这个线程死亡 |
void setDaemon(boolean on) | 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 |
1.static void sleep(long millis)–使当前正在执行的线程停留(暂停执行)指定的毫秒数
package YW.Demo9;
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println(getName() + ":" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package YW.Demo9;
public class ThreadDemo1 {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
MyThread thread3 = new MyThread();
thread1.setName("lota");
thread2.setName("suha");
thread3.setName("mark");
//启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
//output:
suha:0
mark:0
lota:0
mark:1
lota:1
suha:1
lota:2
suha:2
mark:2
2.void join() –等待这个线程死亡
package YW.Demo9;
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + ":" + i);
}
}
}
package YW.Demo9;
public class ThreadDemo1 {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
MyThread thread3 = new MyThread();
thread1.setName("lota");
thread2.setName("suha");
thread3.setName("mark");
//启动线程
thread1.start();
try {
//等待线程死亡
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
thread3.start();
}
}
3.void setDaemon(boolean on) –将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
package YW.Demo9;
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + ":" + i);
}
}
}
package YW.Demo9;
public class ThreadDemo1 {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
MyThread thread3 = new MyThread();
thread1.setName("lota");
thread2.setName("suha");
thread3.setName("mark");
//设置主线程
Thread.currentThread().setName("Main");
//设置守护进程
thread1.setDaemon(true);
thread2.setDaemon(true);
thread3.setDaemon(true);
//启动线程
thread1.start();
thread2.start();
thread3.start();
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
//output:
mark:0
lota:0
Main-->0
suha:0
Main-->1
lota:1
mark:1
lota:2
Main-->2
suha:1
Main-->3
lota:3
mark:2
mark:3
lota:4
Main-->4
suha:2
Main-->5
lota:5
mark:4
lota:6
Main-->6
suha:3
Main-->7
lota:7
mark:5
lota:8
Main-->8
suha:4
Main-->9 //主线程死亡
lota:9
mark:6
suha:5
mark:7
suha:6
mark:8
suha:7
mark:9
suha:8
suha:9
10.1.2.5 线程的生命周期
10.2 线程同步
10.2.1 案例
:卖票
需求∶某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
思路
定义一个类SelTicket实现Runnable接口,里面定义一个成员变量∶ privateint tickets=100;
在SelTicket类中重写run()方法实现卖票,代码步骤如下
A∶ 判断票数大于0,就卖票,并告知是哪个窗口卖的
B∶卖了票之后,总票数要减1
C∶ 票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
3.定义一个测试类SellTiketDemo,里面有main方法,代码步骤如下
A∶ 创建SelITicket类的对象
B∶ 创建三个Thread类的对象,把SelITicket对象作为构造方法的参数,并给出对应的窗口名称
C∶启动线程
思考:
A.卖票需要时间,以100ms为例,用**sleep()**方法实现
问题:
A.卖重复的票
B.卖负数的票
原因:线程的随机性造成的
为什么出现问题?
判断多线程程序是否会有数据安全问题的标准
- 是否是多线程环境
- 是否有共享数据
- 是否有多条语句操作共享数据
如何解决多线程安全问题呢?
- 基本思想∶让程序没有安全问题的环境
怎么实现呢?
- 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
- Java提供了同步代码块的方式来解决
10.2.2 同步代码块
锁多条语句操作共享数据,可以使用同步代码块实现
格式∶
synchronized(任意对象){
//多条语句操作共享数据的代码
}
- synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
package YW.Demo10;
//定义一个类SelTicket实现Runnable接口,里面定义一个成员变量∶ privateint tickets=100;
public class SelITicket implements Runnable {
private int tickets = 100;
private Object obj =new Object();//声明只有一把锁
//在SelTicket类中重写run()方法实现卖票,代码步骤如下
@Override
public void run() {
/*
A∶ 判断票数大于0,就卖票,并告知是哪个窗口卖的
B∶卖了票之后,总票数要减1
C∶ 票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
*/
while(true) {
//synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
synchronized (obj) {
if (tickets > 0) {
//sleep 100ms
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}
}
}
package YW.Demo10;
public class SelITicketDemo {
public static void main(String[] args) {
/*
A∶ 创建SelITicket类的对象
B∶ 创建三个Thread类的对象,把SelITicket对象作为构造方法的参数,并给出对应的窗口名称
C∶启动线程
*/
SelITicket sl = new SelITicket();
Thread t1 = new Thread(sl, "窗口1");
Thread t2 = new Thread(sl, "窗口2");
Thread t3 = new Thread(sl, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
10.2.3 同步方法
同步方法∶ 就是把synchronized关键字加方法上
- 格式∶
修饰符 synchronized 返回值类型 方法名 (方法参数){}
同步方法的锁对象是什么呢?
this
private synchronized void SelITicket() {
if (tickets > 0) {
//sleep 100ms
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
//synchronized(obj)-->synchronized(this)
同步静态方法∶就是把synchronized关键字加到静态方法上
- 格式∶
修饰符 static synchronized 返回值类型 方法名(方法参数){ }
同步静态方法的锁对象是什么呢?
类名.cass
private static synchronized void SelITicket() {
if (tickets > 0) {
//sleep 100ms
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
//synchronized(obj)-->synchronized(SelITicket.class)
10.2.4 线程安全的类
StringBuffer
- 线程安全,可变的字符序列
- 从版本JDK5开始,被StringBuilder替代。通常应该使用StringBuider类,因为它支持所有相同的操作,但它更快,因为它不执行同步
Vector
- 从Java2平台v1.2开始,该类改进了List接口,使其成为JavaColectionsFramework的成员。与新的集合实现不同,Vector被同步。如果不需要线程安全的实现,建议使用Arrayist代替Vector
Hashtable
- 该类实现了一个哈希表,它将键映射到值。 任何非null对象都可以用作键或者值
- 从ava2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为JavaCollectionsFramework的成员。与新的集合实现不同,Hashtable被同步。如果不需要线程安全的实现,建议使用HashMap代替Hashtable
10.2.5 Lock锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock实现提供比使用ynchronized方法和语句可以获得更广泛的锁定操作Lock中提供了获得锁和释放锁的方法
- void lock()∶获得锁
- void unlock()∶释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock的构造方法
ReentrantLock()∶创建一个Reentrantlock的实例
package YW.Demo10;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SelITicket implements Runnable {
private int tickets = 100;
//创建一个Reentrantlock的实例**
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try{
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
} finally { //始终执行
lock.unlock();
}
}
}
}
package YW.Demo10;
public class SelITicketDemo {
public static void main(String[] args) {
SelITicket sl = new SelITicket();
Thread t1 = new Thread(sl, "窗口1");
Thread t2 = new Thread(sl, "窗口2");
Thread t3 = new Thread(sl, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
10.3 生产者 消费者
10.3.1 生产者消费者模式概述
生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻所谓生产者消费者问题,实际上主要是包含了两类线程∶
- 一类是生产者线程用于生产数据
- 一类是消费者线程用于消费数据
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
- 生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
- 消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iPcs0wMV-1646387778308)(Java 核心基础.assets/image-20210322203733852.png)]
10.3.2 Object类的等待和唤醒方法
方法名 | 说明 |
---|---|
void wait() | 导致当前线程等待,直到另一个线程调用该对象的notifyO方法或notifyAll()方法 |
void notify() | 唤醒正在等待对象监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
10.3.2.1. 案例
生产者消费者案例生产者消费者案例中包含的类∶
-
奶箱类(Box)∶ 定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
-
生产者类(Producer)∶ 实现Runnable接口,重写run0方法,调用存储牛奶的操作
-
消费者类(Customer)∶ 实现Runnable接口,重写run)方法,调用获取牛奶的操作
-
测试类(BoxDemo)∶ 里面有main方法,main方法中的代码步骤如下
① 创建奶箱对象,这是共享数据区域
② 创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
③ 创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
④ 创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
⑤ 启动线程
package YW.Demo11;
public class Box {
//定义一个成员变量,表示X瓶牛奶
private int milk;
//定义一个成员变量,表示牛奶箱的状态
private boolean state = false;
//提供存储牛奶和获取牛奶的操作
public synchronized void put(int milk) {
//如果有牛奶,等待消费
if (state) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有牛奶,就生产牛奶
this.milk = milk;
System.out.println("送奶工将第" + this.milk + "瓶牛奶放入奶箱");
//生产完毕后,改变奶箱状态
state = true;
//唤醒其他等待线程
notifyAll();
}
public synchronized void get() {
//如果没有牛奶,等待生产
if (!state) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果有牛奶,消费
System.out.println("用户拿到第" + this.milk + "瓶奶");
//消费完毕后,改变奶箱状态
state = false;
//唤醒其他等待线程
notifyAll();
}
}
package YW.Demo11;
public class Producer implements Runnable {
private Box box;
public Producer(Box box) {
this.box = box;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
box.put(i);
}
}
}
package YW.Demo11;
public class Customer implements Runnable {
private Box box;
public Customer(Box box) {
this.box = box;
}
@Override
public void run() {
while (true) {
box.get();
}
}
}
package YW.Demo11;
public class BoxTest {
public static void main(String[] args) {
//创建奶箱对象,这是共享数据区域
Box box = new Box();
//创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
Producer pd = new Producer(box);
//创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
Customer ct = new Customer(box);
//创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
Thread t1 = new Thread(pd);
Thread t2 = new Thread(ct);
//启动线程
t1.start();
t2.start();
}
}
//output:
送奶工将第1瓶牛奶放入奶箱
用户拿到第1瓶奶
送奶工将第2瓶牛奶放入奶箱
用户拿到第2瓶奶
送奶工将第3瓶牛奶放入奶箱
用户拿到第3瓶奶
送奶工将第4瓶牛奶放入奶箱
用户拿到第4瓶奶
送奶工将第5瓶牛奶放入奶箱
用户拿到第5瓶奶
十一、网络编程
11.1 网络编程入门
11.1.1 网络编程概述
- 在网络通信协议下,实现网络互连的不同计算机上运行的程序间可以进行数据交换
11.1.2 网络编程三要素
IP地址
- 要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而IP地址就是这个标识号。也就是设备的标识
端口
- 网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序了。也就是应用程序的标识
协议
- 通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。常见的协议有UDP协议和TCP协议
11.1.3 IP地址
IP地址∶是网络中设备的唯一标识
IP地址分为两大类
-
IPv4∶是给每个连接在网络上的主机分配一个32bi地址。按照TCPIP规定,IP地址用二进制来表示,每个P地址长32bit,也就是4个字节。例如一个采用二进制形式的P地址是"110000010101000000010100010°,这么长的地址,处理起来也太费劲了。为了方便使用,IP地址经常被写成十进制的形式,中间使用符号"“分隔不同的字节。于是,上面的IP地址可以表示为"192.168.1.66”。IP地址的这种表示法叫做"点分十进制表示法",这显然比1和0容易记忆得多
-
IPv6∶由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,这样就解决了网络地址资源数量不够的问题
常用命令:
- ipconfig∶ 查看本机IP地址
- ping IP地址∶检查网络是否连通
特殊IP地址∶
- 127.0.0.1∶ 是回送地址,可以代表本机地址,一般用来测试使用
InetAddress 的使用
为了方便我们对IP地址的获取和操作,Java提供了一个类InetAddress供我们使用
InetAddress∶ 此类表示nternet协议(IP)地址
方法名 | 说明 |
---|---|
static netAddress getByName(String host) | 确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址 |
String getHostName() | 获取此IP地址的主机名 |
String getHostAddress() | 返回文本显示中的IP地址字符串 |
package YW.demo12;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddressDemo {
public static void main(String[] args) throws UnknownHostException {
//InetAddress address = InetAddress.getByName("YWXK");
InetAddress address = InetAddress.getByName("172.16.1.13");
String name = address.getHostName();
String ip = address.getHostAddress();
System.out.println("主机名:"+name);
System.out.println("IP地址:"+ip);
}
}
//output:
主机名:YWXK
IP地址:172.16.1.13
11.1.4 端口
端口∶ 设备上应用程序的唯一标识
端口号∶用两个字节表示的整数,它的取值范围是0 ~ 65535。其中,0 ~ 1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
11.1.5 协议
协议∶计算机网络中,连接和通信的规则被称为网络通信协议
UDP协议
-
用户数据报协议(User Datagram Protocol)
-
UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输
-
例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议
TCP协议
● 传输控制协议(Transmission Control Protocol)
-
TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过"三次握手"
-
三次握手∶TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
第1次握手,客户端向服务器端发出连接请求,等待服务器确认
第2次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
第3次握手,客户端再次向服务器端发送确认信息,确认连接
- 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性, TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页等
11.2 UDP通信编程
11.2.1 UDP通信原理
UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket象,但是这两个Socket只是发送,接收数据的对象
因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念
Java提供了DatagramSocket类作为基于UDP协议的Socket
11.2.2 UDP发送数据
步骤:
-
创建发送端的Socket对象(DatagramSocket)
DatagramSocket()
-
创建数据,并把数据打包
DatagramPacket(bytelbuf, intlength, InetAddress address,int port)
-
调用DatagramSocket对象的方法发送数据
void send(DatagramPacket p)
-
关闭发送端
void close()
package YW.demo12;
import java.io.IOException;
import java.net.*;
public class SedDemo {
public static void main(String[] args) throws IOException {
//创建发送端的Socket对象
DatagramSocket socket = new DatagramSocket();
//创建数据,并把数据打包
byte[] bys = "hello ,YWXK".getBytes();
int length = bys.length;
InetAddress address = InetAddress.getByName("172.16.1.13");
int port = 10086;
DatagramPacket dp = new DatagramPacket(bys, length, address, port);
//发送数据
socket.send(dp);
//关闭端口
socket.close();
}
}
11.2.3 UDP接收数据
步骤
-
创建接收端的Socket对象(DatagramSocket)
DatagramSocket(int port)
-
创建一个数据包,用于接收数据
DatagramPacket(byte[] buf, int length)
-
调用DatagramSocket对象的方法接收数据
void receive(DatagramPacket p)
-
解析数据包,并把数据在控制台显示
byte[] getData()
intgetLength()
-
关闭接收端
void close()
package YW.demo12;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(10086);
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
ds.receive(dp);
//解析数据包,并显示到控制台
byte[] data = dp.getData();;
System.out.println("数据是:"+new String(data));
ds.close();
}
}
11.2.4 案例
按照下面的要求实现程序
- UDP发送数据∶数据来自于键盘录入,直到输入的数据是886,发送数据结束
- UDP接收数据∶因为接收端不知道发送端什么时候停止发送,故采用死循环接收
package YW.demo13;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class SendDemo {
public static void main(String[] args) throws IOException {
//创建发送端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket();
//封装键盘录入数据
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;
while((line = br.readLine())!=null){
//输入的数据是886,发送数据结束
if ("886".equals(line)){
break;
}
//创建数据,并打包
byte[] bys = line.getBytes();
DatagramPacket dp = new DatagramPacket(bys,bys.length, InetAddress.getByName("YWXK"),12345);
//发送数据
ds.send(dp);
}
//关闭发送端
ds.close();
}
}
package YW.demo13;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建发送端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket(12345);
while(true){
//接收数据
byte[] by = new byte[1024];
DatagramPacket dp = new DatagramPacket(by,by.length);
ds.receive(dp);
System.out.println("数据:"+new String(dp.getData(),0,dp.getLength()));
}
}
}
11.3 TCP通信编程
11.3.1 TCP 通信原理
TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象,从而在通信的两端形成网络虚拟链路,一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信。
Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信 。
Java为客户端提供了Socket类,为服务器端提供了ServerSocket类
11.3.2 TCP 发送数据
发送数据的步骤
-
创建客户端的Socket对象(Socket)
Socket(Stringhost, int port)
-
获取输出流,写数据
OutputStream getOutputStream()
-
释放资源
void close()
package YW.demo14;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建客户端的Socket对象(Socket)
Socket s = new Socket(InetAddress.getByName("YWXK"), 10000);
//获取输出流,写对象
OutputStream os = s.getOutputStream();
os.write("hello YWXK".getBytes());
//释放资源
os.close();
}
}
11.3.3 TCP 接收数据
接收数据的步骤
-
创建服务器端的Socket对象(ServerSocket)
ServerSocket(int port)
-
监听客户端连接,返回一个Socket对象
Socket accept()
-
获取输入流,读数据,并把数据显示在控制台
InputStreamgetInputStream()
-
释放资源
void close()
package YW.demo14;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建服务器端的Scoket对象
ServerSocket ss =new ServerSocket(1000);
//侦听
Socket s = ss.accept();
//获取输入流,读数据,并显示在控制台
InputStream is = s.getInputStream();
byte[] bys =new byte[1024];
int len = is.read(bys);
System.out.println("数据:"+new String(bys,0,len));
//释放资源
s.close();
ss.close();
}
}
11.3.4 案例
package YW.demo14;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建服务器端的Scoket对象
ServerSocket ss =new ServerSocket(10000);
//侦听
Socket s = ss.accept();
//获取输入流,读数据,并显示在控制台
InputStream is = s.getInputStream();
byte[] bys =new byte[1024];
int len = is.read(bys);
System.out.println("服务器:"+new String(bys,0,len));
//给出反馈
OutputStream os = s.getOutputStream();
os.write("数据已经收到".getBytes());
//释放资源
ss.close();
}
}
//output
服务器:hello YWXK
package YW.demo14;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建客户端的Socket对象(Socket)
Socket s = new Socket(InetAddress.getByName("YWXK"), 10000);
//获取输出流,写对象
OutputStream os = s.getOutputStream();
os.write("hello YWXK".getBytes());
//接收反馈
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys);
System.out.println("客户端:" + new String(bys, 0, len));
//释放资源
s.close();
}
}
//output
客户端:数据已经收到
十二、反射
12.1 类加载器
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载,类的连接,类的初始化这三个步骤来对类进行初始化。如果不出现意外情况,JM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或者类初始化
类的加载
- 就是指将class文件读入内存,并为之创建一个java.lang.Clss对象
- 任何类被使用时,系统都会为之建立一个java.lang.Class 对象
类的连接
- 验证阶段∶用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
- 准备阶段∶负责为类的类变量分配内存,并设置默认初始化值
- 解析阶段∶将类的二进制数据中的符号引用替换为直接引用
类的初始化
- 在该阶段,主要就是对类变量进行初始化
类的初始化步骤
假如类还未被加载和连接,则程序先加载并连接该类● 假如该类的直接父类还未被初始化,则先初始化其直接父类● 假如类中有初始化语句,则系统依次执行这些初始化语句
注意∶在执行第2个步骤的时候,系统对直接父类的初始化步骤也遵循初始化步骤1-3
类的初始化时机∶
- 创建类的实例
- 调用类的类方法
- 访问类或者接口的类变量,或者为该类变量赋值
- 使用反射方式来强制创建某个类或接口对应的javalang.Clss对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
12.2 获取class对象
获取Class类的对象
我们要想通过反射去使用一个类,首先我们要获取到该类的字节码文件对象,也就是类型为Class类型的对象这里我们提供三种方式获取Class类型的对象
-
使用类的class属性来获取该类对应的Class对象。举例∶Studentclass将会返回Student类对应的Class对象
-
调用对象的getClass()方法,返回该对象所属类对应的Class对象
该方法是Object类中的方法,所有的Java对象都可以调用该方法
-
使用Clas类中的静态方法forName(StringcasName),该方法需要传入字符串参数,该字符串参数的值是某个类的全路径,也就是完整包名的路径
12.3 反射方式获取构造方法并使用
12.4 反射方式获取成员方法并使用
12.5 反射方式获取成员变量并使用
十三、Lambda表达式
13.1 Lambda表达式的格式
Lambda表达式的格式
- 格式∶**(形式参数)->{代码块}**
- 形式参数∶如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
- ->∶由英文中画线和大于符号组成,固定写法。代表指向动作
- 代码块∶是我们具体要做的事情,也就是以前我们写的方法体内容
13.2 案例
Lambda表达式的使用前提
- 有一个接口
- 接口中有且仅有一个抽象方法
13.2.1 案例一
-
定义一个接口(Eatable),里面定义一个抽象方法∶ void eat0;
-
定义一个测试类(EatableDemo),在测试类中提供两个方法
一个方法是∶ useEatable(Eatable e)
一个方法是主方法,在主方法中调用useEatable方法
package YW.demo15;
public interface Eatable {
void eat();
}
package YW.demo15;
public class EatableImpl implements Eatable{
@Override
public void eat() {
System.out.println("方法重写");
}
}
package YW.demo15;
public class EatableDemo {
public static void main(String[] args) {
Eatable e = new EatableImpl();
useEatable(e);
//匿名内部类
useEatable(new Eatable() {
@Override
public void eat() {
System.out.println("匿名内部类");
}
});
//Lambda表达式
useEatable(() -> {
System.out.println("Lambda表达式");
});
}
private static void useEatable(Eatable e) {
e.eat();
}
}
//output
方法重写
匿名内部类
Lambda表达式
13.2.2 案例二
-
定义一个接口(Flyable),里面定义一个抽象方法∶ voidfly(Strings);
-
定义一个测试类(FlyableDemo),在测试类中提供两个方法
一个方法是∶ useFlyable(Flyable f)
一个方法是主方法,在主方法中调用useFlyable方法
package YW.demo16;
public interface Flyable {
void fly(String s);
}
package YW.demo16;
public class FlyableDemo {
public static void main(String[] args) {
//匿名内部类
useFlyable(new Flyable() {
@Override
public void fly(String s) {
System.out.println("匿名内部类: " + s);
}
});
//Lambda表达式
useFlyable((String s) -> {
System.out.println("Lambda表达式: " + s);
});
}
private static void useFlyable(Flyable f) {
f.fly("success");
}
}
//output
匿名内部类: success
Lambda表达式: success
13.3 Lambda表达式的省略模式
省略规则∶
- 参数类型可以省略。但是有多个参数的情况下,不能只省略一个
- 如果参数有且仅有一个,那么小括号可以省略
- 如果代码块的语句只有一条,可以省略大括号和分号,甚至是retum
13.4 Lambda表达式的注意事项
注意事项∶
-
使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
-
必须有上下文环境,才能推导出Lambda对应的接口
根据局部变量的赋值得知Lambda对应的接口∶Runnabler=0→>System.outprintn("Lambda表达式);
根据调用方法的参数得知Lambda对应的接口∶new Thread()→>Systemoutprintin('Lambda表达式).start();
13.5 Lambda表达式和匿名内部类的区别
所需类型不同
- 匿名内部类∶可以是接口,也可以是抽象类,还可以是具体类
- Lambda表达式∶ 只能是接口
使用限制不同
- 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
- 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
实现原理不同
- 匿名内部类∶ 编译之后,产生一个单独的.class字节码文件
- Lambda表达式∶ 编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成
更多推荐
零基础入门Java(十万字呕心沥血,倾情推荐)
发布评论