文章目录
- 一. JAVA 概述
- 二. 基础语法
- 三. 运算符
- 四. 分支语句
- 五. 循环语句
- 六. 数组
- 七. 方法
- 八. 面向对象基础
- 九. API
- 十. 集合基础
- 十一. 继承
- 十二. 修饰符
- 十三. 多态
- 十四. 抽象 (abstract )
- 十五. 接口 (interface )
- 十六. 参数传递
- 十七. 内部类
- 十八. 异常
- 十九. 递归
- 二十. 多线程
- 二十一. 网络编程入门
- 二十二. Lambda表达式
- 二十三. 接口组成更新
一. JAVA 概述
(一) java语言跨平台原理
1. 平台
🔰平台指的是操作系统;
◓Windows
◓Mac
◓Linux
2. 跨平台
🔰java 程序可以在任意操作系统上运行;
3. 跨平台原理
🔰在需要运行Java应用程序的操作系统上,安装一个与操作系统对应的Java虚拟机(JVM)即可;
(二) JRE 和 JDK
1. JRE
🔰是Java虚拟机运行时环境,包含JVM和运行时所需要的核心类库;
2. JDK
🔰是Java程序开发工具包,包含JRE和开发人员使用的工具;
🔰我们想要开发一个全新的Java程序,那么就必须安装JDK
;
3. JDK, JRE 和 JVM 的关系
(三) 常用 DOS 命令
1. 打开命令提示符窗口
- 按下win+R ⇒ 2. 输入cmd ⇒ 3. 按下回车键
2. 常用命令
操作 | 说明 |
---|---|
盘符名称: | 盘符切换 。E: 回车,表示切换到E盘 |
dir | 查看当前路径下的内容 |
cd 目录 | 进入单级目录。cd itheima |
cd… | 回退到上一级目录 |
cd 目录1\目录2… | 进入多级目录。cd itheima\javase |
cd\ | 回退到盘符目录 |
cls | 清屏 |
exit | 退出命令提示符窗口 |
(四) Path 环境变量的配置
1. 为什么要配置Path环境变量
🔰为了在开发Java程序的时候,能够方便的使用javac和java这些命令,我们需要配置Path环境变量;
2. 如何配置Path环境变量
1.
2.
3.
3. 如何检测配置Path环境变量是否成功
🔰命令提示符窗口上输入javac,出现如下图一样的内容即可;
Ⅰ main方法格式
public static void main(String[] args) {}
Ⅱ 输出语句范例
System.out.println("内容"); 输出内容并换行
System.out.print("内容"); 输出内容不换行
System.out.println(); 起到换行作用
Ⅲ JVM退出
System.exit(0); JVM退出(java虚拟机退出)
Ⅳ IDEA中项目结构
🔰项目->模块->包->类
二. 基础语法
(一) 注释
🔰单行注释:
//注释信息
;快捷键:Ctrl+/
🔰多行注释:/*注释信息*/
;快捷键:Ctrl+Shift+/
(二) 标识符
1. 标识符定义规则
🔰由数字,字母,下划线(_)和($)组成;
◓ 不能以数字开头;
◓ 不能是关键字;
◓ 区分大小写;
2. 常见命名约定
1) 小驼峰命名法:(常用于命名方法
和变量
)
1) 约定1:标识符是一个单词 ⇒首字母小写(name)
2) 约定2:标识符是多个单词 ⇒第一个单词首字母小写,其他的首字母大写(firstName)
2) 大驼峰命名法:(常用于命名类
)
1) 约定1:标识符是一个单词 ⇒首字母大写(Student)
1) 约定2:标识符是多个单词 ⇒每个单词的首字母大写(GoodStudent)
(三) 数据类型
🔰定义
long
类型数据时应定义成如下:long a = 10L;
🔰定义
float
类型数据时应定义成如下:float a = 10F;
数据类型内存占用和取值范围
(四) 数据类型转换
1. 自动类型转换(隐式)
🔰把一个表示数据范围小的数值或者变量赋值给另一个表示数据范围大的变量;
🔰范例:double d = 10;
1.特点:代码不需要进行特殊处理,自动完成。
2.规则:数据范围从小到大。
2. 强制类型转换(显式)
🔰把一个表示数据范围大的数值或者变量,赋值给另一个表示数据范围小的变量;
◓ 格式:目标数据类型 变量名 = (目标数据类型)值或者变量;
◓ 范例:int k = (int)88.88;
1) int转换为String
🔰转换方式
◓ 方式一:直接在数字后加一个空字符串
String s1 = number + "";
◓ 方式二:通过String类静态方法valueOf()
String s2 = String.valueOf(number);
2) String转换为int
🔰转换方式
◓ 方式一:先将字符串数字转成Integer,再调用valueOf()方法
Integer i = Integer.valueOf("s");
int x = i.intValue();
◓ 方式二:通过Integer静态方法parseInt()进行转换
int y = Integer.parseInt(s);
(五) ASCII码表
记住三个:48代表0,65代表A,97代表a。其余的查表即可。
(六) 变量的作用域
🔰规定了变量所能使用的范围,只有在作用域范围内变量才能被使用。
🔰根据变量声明地点的不同,变量的作用域也不同。
🔰根据作用域的不同,一般将变量分为不同的类型:类变量、局部变量、方法参数变量及异常处理参数变量
。
1. 类变量
🔰类变量也称为成员变量,声明在类中,不属于任何一个方法,作用域是整个类。
2. 局部变量
🔰局部变量是指在方法或者方法代码块中定义的变量,其作用域是其所在的代码块。
3. 方法参数变量
🔰作为方法参数声明的变量的作用域是整个方法。
4. 异常处理参数变量
🔰异常处理参数变量的作用域是在异常处理块中,该变量是将异常处理参数传递给异常处理块,与方法参数变量类似。
三. 运算符
(一) 算术运算符
(二) 赋值运算符
(三) 自增自减运算符
🔰++ 和 - - 即可以放在变量的后面,也可以放在变量的前面;
🔰单独使用的时候,++ 和 - - 无论放在变量前边还是后边,结果都是一样的;
🔰参与操作的时候,如果放在变量的后边,先拿变量参与操作,后拿变量做++ 或者 - - ;如果放在变量的前面,先拿变量做++ 或者 - - ,后拿变量参与操作;
(四) 比较运算符
🔰关系运算符的结果都是Boolean类型,要么true,要么false;
🔰如果进行多次判断,不能连着写,例如1<x<3,此时需要用到逻辑运算符
🔰千万不要把“==”误写成“=”;
(五) 逻辑运算符
🔰与(并且)
&&
全都是true才是true,否则是false
🔰或(或者)||
有一个是true就是true;全都是false就是false
🔰非(取反)!
本来是true,变成false;本来是false,变成true
(六) 三元运算符
🔰格式:
关系表达式 ? 表达式1 : 表达式2;
🔰范例:a > b ? a : b;
四. 分支语句
(一) if 语句
if(关系表达式) {
语句体1;
} else {
语句体2;
}
(二) switch 语句
switch(表达式) {
case 值1:
语句体1;
break;
case 值2:
语句体1;
break;
……
default:
语句体n+1;
break;
}
五. 循环语句
(一) for 循环语句
for(初始化语句;条件判断语句;条件控制语句) {
循环语句;
}
增强for循环
// 元素数据类型 必须和 数组/集合对象名 是同类型
for(元素数据类型 变量名 : 数组/集合对象名) {
循环体;
}
for(int i : arr) { System.out.println(i); }
for(String s : list) { System.out.println(s); }
(二) while 循环语句
初始化语句;
while(条件判断语句) {
循环体语句;
条件控制语句;
}
(三) do…while 循环语句
初始化语句;
do {
循环体语句;
条件控制语句;
} while(条件判断语句);
(四) 三种循环的区别
🔰do…while循环至少会执行一次循环体。
🔰for循环和while循环只有在条件成立的时候才会去执行循环体。
(五) 死循环
for(;;) {}
while(true) {}
do {} while(true);
(六) 跳转控制语句
🔰
continue
跳过本次循环,继续下一次循环
🔰break
停止整个循环的执行
六. 数组
🔰定义格式:
int[] arr;
🔰动态初始化:int[] arr = new int[3];
🔰静态初始化:int[] arr = {1, 2, 3, 4, 5};
🔰静态初始化:int[][] arr = {{1, 2}, {3, 4, 5}};
🔰 获取数组长度的函数:arr.length
七. 方法
(一) 方法的定义与调用
🔰方法的定义:
public static 返回值类型 方法名(参数) {
方法体
return 返回值;
}
🔰调用:
1)方法名(参数); ⇒qq(a);
2)数据类型 变量名 = 方法名(参数) ; ⇒int c = qq(a);
(二) 方法注意事项
🔰方法不能嵌套定义;
🔰void
表示无返回值,可以省略return
,也可以单独的书写return
,后面不能加数据;
(三) 方法重载
🔰多个方法在同一个类中
🔰多个方法具有相同的方法名
🔰多个方法的参数不相同,类型不相同或数量不同
八. 面向对象基础
(一) 类的定义
🔰类的定义步骤:
①定义类;
②编写类的成员变量;
【位置发生了变化】在类中方法外
③缩写类的成员方法;
跟之前定义方法的格式相同【static关键字暂时不加】public class 类名{ //成员变量 变量1的数据类型 变量1; 变量2的数据类型 变量2; …… //成员方法 方法1; 方法2; …… }
(二) 对象的使用
🔰创建对象
格式:类名 对象名 = new 类名();
范例:phone p = new phone();
🔰使用对象的成员变量
格式:对象名.变量名
范例:p.brand
🔰使用对象的成员方法
格式:对象名.方法名()
范例:p.call()
(三) 封装
🔰面向对象三大特征
- 封装 2. 继承 3. 多态
🔰封装的原则
将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问成员变量private
,提供对应的getXxx()/setXxx()
方法
🔰封装的好处
通过方法来控制成员变量的操作,提高了代码的安全性,把代码用方法进行封装,提高了代码的复用性
1. private 关键字
🔰是一个权限修饰符,可以修饰成员变量和成员方法
🔰作用是保护成员不被别的类使用,被private
修饰的成员只在本类中才能访问
🔰针对private
修饰的成员变量,如果需要被其他类使用,提供相应的操作
⚪提供“gei变量名()
”方法,用于获取成员变量的值,方法用public
修饰
⚪提供“set变量名(参数)
”方法,用于设置成员变量的值,方法用public
修饰
private 关键字的作用
🔰把成员变量用private
修饰
🔰提供对应的getXxx()/setXxx()
方法
2. this 关键字
🔰
this
修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)
⚪方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
⚪方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
(四) 构造方法
🔰作用:创建对象;
🔰格式:public class 类名 { 修饰符 类名(参数) { } }
🔰构造方法的调用:
构造方法在创建对象的时候调用执行
举例:Student s = new Student();
1. 构造方法的注意事项
🔰构造方法的创建
⚪如果没有定义构造方法,系统会给出一个默认的无参构造方法;
⚪如果定义了构造方法,系统不再提供默认的构造方法;
🔰构造方法也是方法,可以进行重载
🔰可以使用有参构造方法,对成员变量进行赋值,达到初始化数据的效果
🔰构造方法的重载
⚪如果自定义了带参构造方法,还要使用无参构造方法,就必须再写一个无参构造方法;
2. 标准类的制作
成员类
① 成员变量
⚪ 使用private
修饰
② 构造方法
⚪ 提供一个无参构造方法
⚪ 提供一个带多个参数的构造方法
③ 成员方法
⚪ 提供每一个成员变量对应的setXxx()/getXxx()
⚪ 提供一个显示对象信息的show()
测试类
④ 创建对象并为其成员变量赋值的两种方式
⚪ 无参构造方法创建对象后使用setXxx()
赋值
⚪ 使用带参构造方法直接创建带有属性值的对象
//学生类
class Student {
private String name;
private int age;
public Student() {}
public Student(String name) {
this.name = name;
}
public Student(int age) {
this.age = age;
}
public Student(String name,int age) {
this.name = name;
this.age = age;
}
public void show() {
System.out.println(name + "," + age);
}
}
//测试类
public class StudentDemo {
public static void main(String[] args) {
//创建对象
Student s1 = new Student();
s1.show();
//public Student(String name)
Student s2 = new Student("林青霞");
s2.show();
//public Student(int age)
Student s3 = new Student(30);
s3.show();
//public Student(String name,int age)
Student s4 = new Student("林青霞",30);
s4.show();
}
}
九. API
🔰 什么是API?
应用程序编程接口
说白了就是java提供好的一些核心类库
🔰 如何查看API帮助文档?
1.打开API帮助文档
2.点击索引选项卡
3.在搜索框输入要搜索的类
4.查看该类中的组成部分
(一) Scanner(数据输入)
Scanner 使用的基本步骤
1. 导包:
//写在类定义的上边 import java.util.Scanner;
2. 创建对象:
//写在类定义的里面 Scanner sc = new Scanner(System.in);
3. 接收数据:
//接收[int]类型数据(类内) int k = sc.nextInt(); //接收[float]类型数据(类内) float k = sc.nextFloat(); //接收[String]类型数据(类内) String name = sc.nextLine();
🔰 快捷键:sc.nextLine();+ctrl+alt+v
(二) Random(随机数)
Random使用的基本步骤
1. 导包:
import java.util Random;
2. 创建对象:
Random r = new Random();
3. 接收数据:
//获取随机数([int]范围:[0, 100) ) int k = ss.nextInt(100); //获取随机数([boolean]) boolean k = ss.nextBoolean(); //获取随机数([float]) float k = ss.nextFloat();
🔰 快捷键:r.nextint(10);+ctrl+alt+v
(三) String(内容不可变)
字符串比较
🔰 比较地址是否相同
s1 == s2
🔰 比较内容是否相同
s1.equals(s2)
常用字符串函数
🔰获取字符串长度的函数:
字符串对象.length()
🔰返回指定索引处的字符值:字符串对象.charAt(i)
一般用于字符串遍历
字符串改成int类型数组
String s = "91 27 46 38 50";
// 以空格为分割点,字符串转换成String数组
String[] strArray = s.split(" ");
int[] arr = new int[strArray.length];
for(int i=0; i<arr.length; i++) {
arr[i] = Integer.parseInt(strArray[i]);
}
char[]转换成String: (通过构造方法)
String s2 = new String(arr);
byte[]转换成String: (通过构造方法)
String num = new String(arr, 0, 5); //第一个开始,读5个
String转换为StringBuilder: (通过构造方法)
StringBuilder sb = new StringBuilder(s);
(四) StringBuilder(内容可变)
🔰 StringBuilder是一个字符串缓冲区。
🔰 它可以在原有的空间上继续拼接字符串内容!
常用函数
🔰 sb.append(“hello”); => 添加数据,并返回对象本身
🔰 sb.reverse(); => 返回相反的字符序列
StringBuilder转换为String: (通过toString()方法)
String s = sb.toString();
(五) Math
🔰Math类概述:Math 包含执行基本数字运算的方法;
🔰Math中方法的调用方式:
● Math类中无构造方法,但内部的方法都是静态的,则可以通过 类名.进行调用Math.abs(23)
🔰Math类的常用方法:Math.abs(int a); // 返回参数的绝对值 Math.ceil(double a); // 返回大于等于参数的最小double值,等于一个整数 c = Math.ceil(12.4)==>c = 13.0 c = Math.ceil(12.8)==>c = 13.0 Math.floor(double a); // 返回小于等于参数的最小double值,等于一个整数 c = Math.floor(12.4)==>c = 12.0 c = Math.floor(12.8)==>c = 12.0 Math.round(float a); // 按照四舍五入返回最接近参数的int c = Math.round(12.4)==>c = 12 c = Math.round(12.8)==>c = 13 Math.max(int a, int b); // 返回最大值 Math.min(int a, int b); // 返回最小值 Math.pow(double a, double b); 返回a的b次幂的值 Math.random(); // 返回值为double的正值,[0.0, 1.0)
(六) System
🔰System类的常用方法:
// 终止当前运行的Java虚拟机,非零表示异常终止 ystem.exit(0); // 返回当前时间(以毫秒为单位(可以用于计算程序运行耗时)) long a = System.currentTimeMillis();
(七) Object
🔰Object类概述:
● Object 是类层次结构的根,每个类都可以将 Object 作为超类。该类所具备的方法,所有类都会有一份
1. toString方法
🔰重写toString方法的方式:
- Alt + Insert 选择toString
- 在类的空白区域,右键 -> Generate -> 选择toString
🔰toString方法的作用:
● 以良好的格式,更方便的展示对象中的属性值
🔰示例代码:class Student extends Object { private String name; public Student() {} public Student(String name) {this.name = name; } public String getName() { return name; } public void setName(String name) {this.name = name; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + '}'; } } public class ObjectDemo { public static void main(String[] args) { Student s = new Student(); s.setName("林青霞"); System.out.println(s); System.out.println(s.toString()); } } // Student{name='林青霞'} // Student{name='林青霞'}
2. equals方法
🔰equals方法的作用:
● 用于对象之间的比较,返回true和false的结果
● 举例:s1.equals(s2); s1和s2是两个对象
🔰重写equals方法的场景:
● 不希望比较对象的地址值,想要结合对象属性进行比较的时候
🔰重写equals方法的方式:
- alt + insert 选择equals() and hashCode(),IntelliJ Default,一路next,finish即可
- 在类的空白区域,右键 -> Generate -> 选择equals() and hashCode(),后面的同上。
🔰示例代码:class Student extends Object { private String name; public Student() {} public Student(String name) {this.name = name; } 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; //student s return name != null ? name.equals(student.name) : >student.name == null; } } public class ObjectDemo { public static void main(String[] args) { tudent s1 = new Student(); s1.setName("林青霞"); Student s2 = new Student(); s2.setName("林青霞"); //需求:比较两个对象的内容是否相同 System.out.println(s1.equals(s2)); } } // true
(八) Arrays
🔰该类包含用于操作数组的各种方法,如排序和搜索
import java.util.Arrays; // 函数导包(类外) Arrays.toString(arr); // 返回指定数组的内容的字符串表示方式 // 输出内容:[11, 12, 23, 34, 43, 76] Arrays.sort(arr); // 对数组进行排序
(九) 时间日期类
1. Date,SimpleDateFormat
🔰Date 代表了一个特定的时间,精确到毫秒;
🔰SimpleDateFormat是一个具体的类,用于以区域设置敏感的方式格式化和解析日期。
// 创建日期对象(获取当前时间部分时间值)
Date date = new Date();
// 创建对象(对获取的时间进行加工,便于理解)
SimpleDateFormat formats = new SimpleDateFormat(); // 默认:2022/10/14 下午3:07
SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); // 2022年10月14日 15:07:45
// 格式化:将日期格式化成日期/时间的字符串(从Date到String)
String s = formats.format(date);
String ss = format.format(date);
//解析:从给定字符串的开始解析文本以生成日期(从String到Date)
String sss = "2048-08-09 11:11:11";
Date d = format.parse(sss); // 输出结果:Fri Oct 14 15:07:45 CST 2022
System.out.println(s);
System.out.println(ss);
2. Calendar
🔰为操作日历字段提供了一些方法
// 创建日历对象(多态的形式)(获取当前时间段的所有时间值) Calendar c = Calendar.getInstance(); // 返回给定日历字段的值 int year = c.get(Calendar.YEAR); // 将指定的时间量添加或减去给定的日历字段 c.add(Calendar.YEAR, -3); // 自定义当前日历的年月日 c.set(2020, 12, 23);
🔰
c.get(参数值)
参数值 描述 Calendar.YEAR 指示当前年份 Calendar.MONTH 指示当前月份(+1),给获取的值 +1 Calendar.DATE 指示当前日期 Calendar.WEEK_OF_YEAR 指示这一个年中的第n个星期 Calendar.WEEK_OF_MONTH 指示这一个月中的第n个星期 Calendar.DAY_OF_MONTH 指示这一个月中的第n天 Calendar.DAY_OF_YEAR 指示这一个年中的第n天 Calendar.DAY_OF_WEEK 指示这一个星期中的第n天(-1) Calendar.AM_PM 指示HOUR是在中午之前还是中午之后
(上午:0, 下午:1)Calendar.HOUR 指示上午或下午的小时 Calendar.HOUR_OF_DAY 指示当前一天中的第n小时 Calendar.MINUTE 指示当前一小时中的第n分钟 Calendar.SECOND 指示当前一分钟的第n秒 Calendar.MILLISECOND 指示当前一秒中的第n毫秒
(十) File类
🔰File类介绍:
● 它是文件和目录路径名的抽象表示
● 文件和目录是可以通过File封装成对象的
● 对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已。它可以是存在的,也可以
是不存在的。将来是要通过具体的操作把这个路径的内容转换为具体存在的
1. File类的构造方法
// 通过将给定的路径名字符串转换为抽象路径名来创建新 的 File实例。 File f1 = new File("E:\\itcast\\java.txt"); // 从父路径名字符串和子路径名字符串创建新的File实例。 File f2 = new File("E:\\itcast","java.txt"); // 从父抽象路径名和子路径名字符串创建新的 File实例。 File f3 = new File("E:\\itcast"); File f4 = new File(f3,"java.txt");
2. File类创建功能
//需求1:我要在E:\\itcast目录下创建一个文件java.txt File f1 = new File("E:\\ss\\java.txt"); //当具有该名称的文件不存在时,创建一个由该抽象路径名命名的新空文件,并返回true //当具有该名称的文件存在时,返回false boolean b = f1.createNewFile(); // 创建新空文件 //需求2:我要在E:\\itcast目录下创建一个目录JavaSE File f2 = new File("E:\\ss\\JavaSE"); //当具有该名称的目录不存在时,创建一个由该抽象路径名命名的新空目录,并返回true //当具有该名称的目录存在时,返回false boolean bb = f2.mkdir(); // 创建新空目录 //需求3:我要在E:\\itcast目录下创建一个多级目录JavaWEB\\HTML File f3 = new File("E:\\itcast\\JavaWEB\\HTML"); //当具有该名称的多级目录不存在时,创建一个由该抽象路径名命名的新空多级目录,并返回true //当具有该名称的多级目录存在时,返回false boolean bbb = f3.mkdirs(); //创建新空多级目录
3. File类判断和获取功能
🔰判断功能
//创建一个File对象 File f = new File("E:\\ss\\java.txt"); // 测试此抽象路径名表示的File是否为目录,是:返回true boolean b = f.isDirectory(); // 测试此抽象路径名表示的File是否为文件,是:返回true boolean bb = f.isFile(); // 测试此抽象路径名表示的File是否存在,是:返回true boolean bbb = f.exists();
🔰获取功能
//创建一个File对象 File f = new File("ss\\java.txt"); // 返回此抽象路径名的绝对路径名字符串 String s = f.getAbsolutePath(); // 将此抽象路径名转换为路径名字符串 String ss = f.getPath(); // 返回由此抽象路径名表示的文件或目录的名称 String sss = f.getName(); File f2 = new File("E:\\ss"); // 返回此抽象路径名表示的目录中的文件和目录的名称字符串数组(包含目录和文件) String[] strArray = f2.list(); for(String str : strArray) { System.out.println(str); } // 返回此抽象路径名表示的目录中的文件和目录的File对象数组(只有文件) File[] fileArray = f2.listFiles(); for(File file : fileArray) { if (file.isFile()) { System.out.println(file.getName()); } }
4. File类删除功能
// 在当前模块目录下创建java.txt文件 File f1 = new File("E:\\ss\\java - 副本 (6).txt"); // 删除由此抽象路径名表示的文件或目录,成功,返回true boolean b = f1.delete();
🔰绝对路径和相对路径的区别:
● 绝对路径:完整的路径名,不需要任何其他信息就可以定位它所表示的文件。例如:E:\itcast\java.txt
● 相对路径:必须使用取自其他路径名的信息进行解释。例如:myFile\java.txt
(十一) IO流
🔰IO流介绍:
◓ IO:输入/输出(Input/Output)
◓ 流:是一种抽象概念,是对数据传输的总称。也就是说数据在设备间的传输称为流,流的本质是数据传输
◓ IO流就是用来处理设备间数据传输问题的。常见的应用:文件复制;文件上传;文件下载
🔰IO流的分类:
◓ 按照数据的流向
◉ 输入流:读数据
◉ 输出流:写数据
◓ 按照数据类型来分
◉ 字节流
● 字节输入流
● 字节输出流
◉ 字符流
● 字符输入流
● 字符输出流
🔰IO流的使用场景:
◓ 如果操作的是纯文本文件,优先使用字符流
◓ 如果操作的是图片、视频、音频等二进制文件。优先使用字节流
◓ 如果不确定文件类型,优先使用字节流。字节流是万能的流
1. 字节流写数据
🔰FileOutputStream(String name):创建文件输出流以指定的名称写入文件;
🔰使用字节输出流写数据的步骤:
◓ 创建字节输出流对象(调用系统功能创建了文件,创建字节输出流对象,让字节输出流对象指向文件)
◓ 调用字节输出流对象的写数据方法
◓ 释放资源(关闭此文件输出流并释放与此流相关联的任何系统资源)//创建字节输出流对象 FileOutputStream fos = new FileOutputStream("E:\\ss\\fos.txt"); // 做了三件事情: // A:调用系统功能创建了文件 // B:创建了字节输出流对象 // C:让字节输出流对象指向创建好的文件 fos.write(97); // 将指定的字节写入此文件输出流 // 关闭此文件输出流并释放与此流相关联的任何系统资源。 fos.close();
1)写数据的方法
FileOutputStream fos = new FileOutputStream("E:\\ss\\fos.txt"); // 将指定的字节写入此文件输出流 一次写一个字节数据 fos.write('d'); fos.write(23); byte[] bys = "abcde".getBytes(); // 将 b.length字节从指定的字节数组写入此文件输出流 一次写一个字节数组 数据 fos.write(bys); // 将 len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流 一次写一个字节数组的部分数据 fos.write(bys, 1, 4); //释放资源 fos.close();
🔰字节流写数据实现换行:
windows:\r\n
linux:\n
mac:\r
2)字节流写数据实现追加写入
🔰
public FileOutputStream(String name,boolean append)
🔰创建文件输出流以指定的名称写入文件。如果第二个参数append为true ,则字节将写入文件的末尾而不是开头//创建字节输出流对象 FileOutputStream fos = new FileOutputStream("myByteStream\\fos.txt",true); //写数据 for (int i = 0; i < 10; i++) { fos.write("hello".getBytes()); fos.write("\r\n".getBytes()); } //释放资源 fos.close();
3)字节流写数据加异常处理
try{
可能出现异常的代码;
}catch(异常类名 变量名){
异常的处理代码;
}finally{
执行所有清除操作; }
🔰被finally控制的语句一定会执行,除非JVM退出//加入finally来实现释放资源 FileOutputStream fos = null; try { fos = new FileOutputStream("myByteStream\\fos.txt"); fos.write("hello".getBytes()); } catch (IOException e) { e.printStackTrace(); } finally { if(fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace();} } }
2. 字节流读数据
🔰FileInputStream(String name):通过打开与实际文件的连接来创建一个FileInputStream ,该文件由文件系统中的路径名name命名
🔰字节输入流读取数据的步骤:
◓创建字节输入流对象
◓ 调用字节输入流对象的读数据方法
◓ 释放资源//创建字节输入流对象 FileInputStream fis = new FileInputStream("E:\\ss\\fos.txt"); int by; // fis.read():一次读一个数据 // by=fis.read():把读取到的数据赋值给by // by != -1:判断读取到的数据是否是-1 while ((by=fis.read())!=-1) { System.out.print((char)by); } // fis.read(byte b[]); 一次读一个字节数组 byte[] bys = new byte[1024]; //1024及其整数倍 int len; while ((len=fis.read(bys))!=-1) { System.out.print(new String(bys,0,len)); } //释放资源 fis.close();
🔰
1) 字节流复制文本文件
🔰实现步骤
◓ 复制文本文件,其实就把文本文件的内容从一个文件中读取出来(数据源),然后写入到另一个文件中(目的地)// 把“E:\ss\fos.txt”复制到模块目录下的“E:\ss\java.txt” FileInputStream fis = new FileInputStream("E:\\ss\\fos.txt"); //根据目的地创建字节输出流对象 FileOutputStream fos = new FileOutputStream("E:\\ss\\java.txt"); //读写数据,复制文本文件(一次读取一个字节,一次写入一个字节) int by; while ((by=fis.read())!=-1) { fos.write(by); } //释放资源 fos.close(); fis.close();
2) 字节流复制图片
🔰实现步骤
◓ 根据数据源创建字节输入流对象
◓ 根据目的地创建字节输出流对象
◓ 读写数据,复制图片(一次读取一个字节数组,一次写入一个字节数组)
◓ 释放资源// 把“E:\ss\mn.jpg”复制到模块目录下的“E:\ss\mn.jpg” //根据数据源创建字节输入流对象 FileInputStream fis = new FileInputStream("E:\\ss\\mn.jpg"); //根据目的地创建字节输出流对象 FileOutputStream fos = new >FileOutputStream("E:\\ss\\JavaSE\\mn.jpg"); //读写数据,复制图片(一次读取一个字节数组,一次写入一个字节数组) byte[] bys = new byte[1024]; int len; while ((len=fis.read(bys))!=-1) { fos.write(bys,0,len); } >//释放资源 fos.close(); fis.close();
3. 字节缓冲流
🔰lBufferOutputStream:该类实现缓冲输出流。 通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用 。
🔰lBufferedInputStream:创建BufferedInputStream将创建一个内部缓冲区数组。 当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节
//字节缓冲输出流:BufferedOutputStream(OutputStream out) BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\ss\\bos.txt")); //写数据 bos.write("hello\r\n".getBytes()); bos.write("world\r\n".getBytes()); //释放资源 bos.close(); //字节缓冲输入流:BufferedInputStream(InputStream in) BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\ss\\bos.txt")); //一次读取一个字节数据 // int by; // while ((by=bis.read())!=-1) { // System.out.print((char)by); // } //一次读取一个字节数组数据 byte[] bys = new byte[1024]; int len; while ((len=bis.read(bys))!=-1) { System.out.print(new String(bys,0,len)); } //释放资源 bis.close();
1) 字节流复制视频
🔰实现步骤
◓ 根据数据源创建字节输入流对象
◓ 根据目的地创建字节输出流对象
◓ 读写数据,复制视频
◓ 释放资源//把“E:\ss\ss\ds.mp4”复制到模块目录下的“E:\ss\JavaSE\ds.mp4” //根据数据源创建字节输入流对象 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\ss\\ds.mp4")); //根据目的地创建字节输出流对象 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\ss\\JavaSE\\ds.mp4")); //读写数据,复制图片(一次读取一个字节数组,一次写入一个字节数组) byte[] bys = new byte[1024]; int len; while ((len=bis.read(bys))!=-1) { bos.write(bys,0,len); } >//释放资源 bos.close(); bis.close();
4. 字符流
🔰字符流的介绍:
● 由于字节流操作中文不是特别的方便,所以Java就提供字符流
● 字符流 = 字节流 + 编码表
1) 字符串中的编码解码问题
//定义一个字符串 String s = "中国"; byte[] bys = s.getBytes(); //[-28, -72, -83, -27, -101, -67] //byte[] bys = s.getBytes("UTF-8"); //[-28, -72, -83, -27, -101, -67] String ss = new String(bys); //String ss = new String(bys,"UTF-8"); System.out.println(ss);
2) 字符串中的编码解码问题
🔰字符流中和编码解码问题相关的两个类:
● InputStreamReader:是从字节流到字符流的桥梁
它读取字节,并使用指定的编码将其解码为字符
它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
● OutputStreamWriter:是从字符流到字节流的桥梁
是从字符流到字节流的桥梁,使用指定的编码将写入的字符编码为字节
它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\ss\\java.txt")); //存字符串 osw.write("中国"); osw.close(); InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\ss\\bos.txt")); //一次读取一个字符数据 int ch; while ((ch=isr.read())!=-1) { System.out.print((char)ch); } isr.close();
3) 字符流写数据的5种方式
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\ss\\java.txt")); //void write(int c):写一个字符 osw.write(97); //void writ(char[] cbuf):写入一个字符数组 char[] chs = {'a', 'b', 'c', 'd', 'e'}; osw.write(chs); //void write(char[] cbuf, int off, int len):写入字符数组的一部分 //osw.write(chs, 0, chs.length); osw.write(chs, 1, 3); //void write(String str):写一个字符串 osw.write("hello"); //void write(String str, int off, int len):写一个字符串的一部分 //osw.write("abcde", 0, "abcde".length()); osw.write("abcde", 1, 3); //释放资源 osw.close();
刷新和关闭的方法
4) 字符流读数据的2种方式
InputStreamReader isr = new InputStreamReader(new >FileInputStream("E:\\ss\\java.txt")); //int read():一次读一个字符数据 int ch; while ((ch=isr.read())!=-1) { System.out.print((char)ch ); } //int read(char[] cbuf):一次读一个字符数组数据 char[] chs = new char[1024]; int len; while ((len = isr.read(chs)) != -1) { System.out.print(new String(chs, 0, len)); } //释放资源 isr.close()
5) 字符流复制Java文件
//根据数据源创建字符输入流对象 InputStreamReader isr = new InputStreamReader(new FileInputStream("myCharStream\\ConversionStreamDemo.java")); //根据目的地创建字符输出流对象 OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("myCharStream\\Copy.java")); //读写数据,复制文件 //一次读写一个字符数据 int ch; while ((ch=isr.read())!=-1) { osw.write(ch); } //一次读写一个字符数组数据 char[] chs = new char[1024]; int len; while ((len=isr.read(chs))!=-1) { osw.write(chs,0,len); } //释放资源 osw.close(); isr.close();
5. 字符缓冲流
🔰字符缓冲流介绍:
● BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途
● BufferedReader:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途
//BufferedWriter(Writer out) BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\ss\\bw.txt")); bw.write("hello\r\n"); bw.close(); //BufferedReader(Reader in) BufferedReader br = new BufferedReader(new FileReader("E:\\ss\\bw.txt")); char[] chs = new char[1024]; int len; while ((len=br.read(chs))!=-1) { System.out.print(new String(chs,0,len)); } br.close();
1) 字符缓冲流特有功能
//创建字符缓冲输出流 BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\ss\\bw.txt")); //写数据 for (int i = 0; i < 10; i++) { bw.write("hello" + i); //bw.write("\r\n"); bw.newLine(); bw.flush(); } //释放资源 bw.close(); //创建字符缓冲输入流 BufferedReader br = new BufferedReader(new FileReader("E:\\ss\\bw.txt")); String line; while ((line=br.readLine())!=null) { System.out.println(line); } br.close();
2) 集合到文件
// 把文本文件中的数据读取到集合中,并遍历集合。要求:文件中每一行数据是一个集合元素
public class ObjectDemo {
public static void main(String[] args) throws IOException {
//创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new
FileReader("E:\\ss\\java.txt"));
//创建ArrayList集合对象
ArrayList<String> array = new ArrayList<String>();
//调用字符缓冲输入流对象的方法读数据
String line;
while ((line=br.readLine())!=null) {
//把读取到的字符串数据存储到集合中
array.add(line);
}
//释放资源
br.close();
//遍历集合
for(String s : array) {
System.out.println(s);
}
}
}
3) 集合到文件改进版
// 把ArrayList集合中的学生数据写入到文本文件。要求:每一个学生对象的数据作为文件中的一行数据
// 格式:学号,姓名,年龄,居住地 举例:itheima001,林青霞,30,西安
public class Student {
private String sid;
private String name;
private int age;
private String address;
public Student() {}
public Student(String sid, String name, int age, String address) {
this.sid = sid;
this.name = name;
this.age = age;
this.address = address;
}
public String getSid() { return sid; }
public void setSid(String sid) { this.sid = sid; }
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; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
}
public class ObjectDemo {
public static void main(String[] args) throws IOException {
//创建ArrayList集合
ArrayList<Student> array = new ArrayList<Student>();
//创建学生对象
Student s1 = new Student("itheima001", "林青霞", 30, "西安");
Student s2 = new Student("itheima002", "张曼玉", 35, "武汉");
Student s3 = new Student("itheima003", "王祖贤", 33, "郑州");
//把学生对象添加到集合中
array.add(s1);
array.add(s2);
array.add(s3);
//创建字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new
FileWriter("E:\\ss\\students.txt"));
//遍历集合,得到每一个学生对象
for (Student s : array) {
//把学生对象的数据拼接成指定格式的字符串
StringBuilder sb = new StringBuilder();
sb.append(s.getSid()).append(",").append(s.getName()).append(",").append(s.getAge()).append(",").append(s.getAddress());
//调用字符缓冲输出流对象的方法写数据
bw.write(sb.toString());
bw.newLine();
bw.flush();
}
//释放资源
bw.close();
}
}
4) 文件到集合
// 把ArrayList集合中的字符串数据写入到文本文件。要求:每一个字符串元素作为文件中的一行数据
public class ObjectDemo {
public static void main(String[] args) throws IOException {
//创建ArrayList集合
ArrayList<String> array = new ArrayList<String>();
//往集合中存储字符串元素
array.add("hello");
array.add("world");
array.add("java");
//创建字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new
FileWriter("E:\\ss\\array.txt"));
//遍历集合,得到每一个字符串数据
for(String s : array) {
//调用字符缓冲输出流对象的方法写数据
bw.write(s);
bw.newLine();
bw.flush();
}
//释放资源
bw.close();
}
}
5) 文件到集合改进版
// 把文本文件中的数据读取到集合中,并遍历集合。要求:文件中每一行数据是一个学生对象的成员变量值
//举例:itheima001,林青霞,30,西安
public class Student {
private String sid;
private String name;
private int age;
private String address;
public Student() {}
public Student(String sid, String name, int age, String address) {
this.sid = sid;
this.name = name;
this.age = age;
this.address = address;
}
public String getSid() { return sid; }
public void setSid(String sid) { this.sid = sid; }
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; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
}
public class ObjectDemo {
public static void main(String[] args) throws IOException {
//创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new
FileReader("E:\\ss\\students.txt"));
//创建ArrayList集合对象
ArrayList<Student> array = new ArrayList<Student>();
//调用字符缓冲输入流对象的方法读数据
String line;
while ((line = br.readLine()) != null) {
//把读取到的字符串数据用split()进行分割,得到一个字符串数组
String[] strArray = line.split(",");
//创建学生对象
Student s = new Student();
//把字符串数组中的每一个元素取出来对应的赋值给学生对象的成员变量值
//itheima001,林青霞,30,西安
s.setSid(strArray[0]);
s.setName(strArray[1]);
s.setAge(Integer.parseInt(strArray[2]));
s.setAddress(strArray[3]);
//把学生对象添加到集合
array.add(s);
}
//释放资源
br.close();
//遍历集合
for (Student s : array) {
System.out.println(s.getSid() + "," + s.getName() + "," +
s.getAge() + "," + s.getAddress());
}
}
}
6. IO特殊操作流
1) 标准输入流
🔰System类中有两个静态的成员变量:
●public static final InputStream in
:标准输入流。通常该流对应于键盘输入或由主机环境或用户指定的另一个输入源
●public static final PrintStream out
:标准输出流。通常该流对应于显示输出或由主机环境或用户指定的另一个输出目标BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.println("请输入一个字符串:"); String line = br.readLine(); System.out.println("你输入的字符串是:" + line); System.out.println("请输入一个整数:"); int i = Integer.parseInt(br.readLine()); System.out.println("你输入的整数是:" + i); //自己实现键盘录入数据太麻烦了,所以Java就提供了一个类供我们使用 Scanner sc = new Scanner(System.in);
2) 标准输入流
🔰System类中有两个静态的成员变量:
●public static final InputStream in
:标准输入流。通常该流对应于键盘输入或由主机环境或用户指定的
另一个输入源
●public static final PrintStream out
:标准输出流。通常该流对应于显示输出或由主机环境或用户指定的另一个输出目标/public static final PrintStream out:标准输出流 PrintStream ps = System.out; //能够方便地打印各种数据值 // ps.print("hello"); // ps.print(100); // ps.println("hello"); // ps.println(100); //System.out的本质是一个字节输出流 System.out.println("hello"); System.out.println(100); System.out.println();
十. 集合基础
🔰集合类的特点:
● 提供一种存储空间可变的存储模型,存储的数据容量可以随时发生改变
(一) Collection (单列)
🔰Collection集合概述:
● 是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素
● JDK 不提供此接口的任何直接实现,它提供更具体的子接口(如Set和List)实现
🔰 常用方法:
//创建集合对象 Collection<String> c = new ArrayList<>(); //指定元素追加到此集合末尾;若添加成功,返回true boolean k = a.add("hello"); //删除指定元素;若删除成功,返回true boolean k = a.remove("hello"); //移除此列表中的所有元素 a.clear(); //查找指定索引处的元素;若存在,返回true boolean k = a.contains("hello"); //如果此集合中没有元素,则返回true boolean k = a.isEmpty(); //返回集合中的元素个数 int k = a.size();
// 将指定的列表按升序排序 Collections.sort(map); // 反转指定列表中元素的顺序 Collections.reverse(map); // 使用默认的随机源随机排列指定的列表 Collections.shuffle(map);
🔰迭代器的介绍
● 迭代器,集合的专用遍历方式
● Iterator iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
● 迭代器是通过集合的iterator()方法得到的,所以我们说它是依赖于集合而存在的//创建集合对象 Collection<String> c = new ArrayList<>(); //添加元素 c.add("hello"); c.add("world"); //Iterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到 Iterator<String> it = c.iterator(); //用while循环改进元素的判断和获取 while (it.hasNext()) { String s = it.next(); System.out.println(s); }
1. List (可重复)
🔰List集合概述:
● 有序集合(也称为序列),用户可以精确控制列表中每个元素的插入位置。用户可以通过整数索引访问元素,并搜索列表中的元素
● 与Set集合不同,列表通常允许重复的元素
🔰List集合特点:
● 有索引;
● 可以存储重复元素;
● 元素存取有序;
🔰 特有方法:// 创建集合对象 List<String> a = new ArrayList<>(); // 在此集合中的指定位置插入指定的元素 a.add(1,"hello"); // 删除指定索引出的元素,返回被删除元素 String k = a.remove(1); // 修改指定索引处的元素,返回被修改的元素 String k1 = a.set(1,"java"); //返回指定索引处的元素 String k2 = a.get(1);
列表迭代器
🔰ListIterator介绍:
● 通过List集合的listIterator()方法得到,所以说它是List集合特有的迭代器
● 用于允许程序员沿任一方向遍历的列表迭代器,在迭代期间修改列表,并获取列表中迭代器的当前位置
🔰示例代码://创建集合对象 List<String> list = new ArrayList<String>(); //添加元素 list.add("hello"); list.add("world"); //获取列表迭代器 ListIterator<String> lit = list.listIterator(); while (lit.hasNext()) { String s = lit.next(); if(s.equals("world")) { lit.add("javaee"); }} System.out.println(list);
类型通配符
🔰类型通配符的作用:
● 为了表示各种泛型List的父类,可以使用类型通配符
🔰类型通配符的分类:
◓ 类型通配符:<?>
● List<?>:表示元素类型未知的List,它的元素可以匹配任何的类型
● 这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中
◓ 类型通配符上限:<? extends 类型>
● List<? extends Number>:它表示的类型是Number或者其子类型
◓ 类型通配符下限:<? super 类型>
● List<? super Number>:它表示的类型是Number或者其父类型
1)ArrayList集合
🔰ArrayList集合的介绍
● ArrayList集合是一个长度可变的容器
🔰ArrayList集合的特点
● ArrayList集合的长度是可变的
● ArrayList集合底层是数组实现,,查询快、增删慢
🔰什么是泛型
● 泛型是一种广泛的数据类型,用于约束集合存储的元素数据类型
● 例如:
● 我们想存储字符串类型的数据,就可以这样写:ArrayList<String>
● 我们想存储学生类型的数据,就可以这样写:ArrayList<Student>
ArrayList集合使用的基本步骤
1. 导包:
import java.util.ArrayList; //集合函数导包
2. 创建对象:
ArrayList<String> a = new ArrayList<>(); //创建对象
3. 常用方法:
//指定元素追加到此集合末尾;若添加成功,返回true boolean k = a.add("hello"); //指定位置插入指定的元素 a.add(1, "hello"): //查找指定索引处的元素;若存在,返回true boolean k = a.contains("hello"); //修改指定索引处的元素,返回被修改的元素 String k = a.set(1, "java"); //删除指定元素;若删除成功,返回true boolean k = a.remove("hello"); //删除指定索引处的元素,返回被删除的元素 String k = a.remove(1); //返回指定索引处的元素 String k = a.get(1); //如果此集合中没有元素,则返回true boolean k = a.isEmpty(); //返回集合中的元素个数 int k = a.size(); //移除此列表中的所有元素 a.clear();
2)LinkedList集合
ArrayList集合使用的基本步骤
1. 导包:
import java.util.LinkedList; //集合函数导包
2. 创建对象:
LinkedList<String> a = new LinkedList<>(); //创建对象
3. 特有方法:
list.addFirst("A"); // 在该列表开头插入指定的元素 list.addLast("D"); // 将指定的元素追加到此列表的末尾 String s = list.getFirst(); // 返回此列表中的第一个元素 String ss = list.getLast(); // 返回此列表中的最后一个元素 String sss = list.removeFirst(); // 从此列表中删除并返回第一个元素 String ssss = list.removeLast(); // 从此列表中删除并返回最后一个元素
2. Set (不可重复)
🔰Set集合的特点:
● 元素存取无序
● 没有索引、只能通过迭代器或增强for循环遍历
● 不能存储重复元素Set<String> set = new HashSet<String>(); //添加元素 set.add("hello"); set.add("world"); //遍历 for(String s : set) { System.out.println(s); }
1)HashSet集合
🔰HashSet集合的特点:
● 底层数据结构是哈希表
● 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
● 没有带索引的方法,所以不能使用普通for循环遍历
● 由于是Set集合,所以是不包含重复元素的集合
2)TreeSet集合
🔰TreeSet集合的特点:
● 元素有序,可以按照一定的规则进行排序,具体排序方式取决于构造方法
● TreeSet():根据其元素的自然排序进行排序
●TreeSet(Comparator comparator) :根据指定的比较器进行排序
● 没有带索引的方法,所以不能使用普通for循环遍历
● 由于是Set集合,所以不包含重复元素的集合
(二) Map(双列)
🔰Map集合概述:
●interface Map<K,V>
K:键的类型;V:值的类型
🔰Map集合的特点:
● 键值对映射关系
● 一个键对应一个值
● 键不能重复,值可以重复
● 元素存取无序
🔰Map集合的基本使用://创建集合对象 Map<String,String> map = new HashMap<String,String>(); //V put(K key, V value) 将指定的值与该映射中的指定键相关联 map.put("itheima001","林青霞"); map.put("itheima002","张曼玉"); //输出集合对象 System.out.println(map); >>{itheima1=林青霞, itheima2=张曼玉}
🔰Map集合的的基本功能:
//创建集合对象 Map<String,String> map = new HashMap<String,String>(); map.put("itheima2","张曼玉"); // 添加元素 // 根据键删除键值对元素,并返回删除的键值 String s = map.remove("itheima2"); map.clear(); // 移除所有的键值对元素 // 判断集合是否包含指定的键,包含返回true boolean ss = map.containsKey("itheima1"); // 判断集合是否包含指定的值,包含返回true boolean sss = map.containsValue("林青霞"); // 判断集合是否为空,空返回true boolean ssss = map.isEmpty(); // 集合长度,也就是集合中的键值对个数 int sssss = map.size();
🔰Map集合的的基本功能:
String s = map.get("ss"); // 根据键获取值 // 获取所有键的集合 Set<String> ss = map.keySet(); // [ss, sss, s] // 获取所有值的集合 Collection<String> sss = map.values(); // [古力, 张曼玉, 艾莉] // 获取所有键值对对象的集合 Set<Map.Entry<String, String>> ssss = map.entrySet(); // [ss=古力, sss=张曼玉, s=艾莉]
🔰Map集合的遍历
//创建集合对象 Map<String,String> map = new HashMap<String,String>(); // 添加元素 map.put("s","艾莉"); map.put("ss","古力"); map.put("sss","张曼玉"); //——————————————第一种—————————————————————— //获取所有键的集合。用keySet()方法实现 Set<String> keySet = map.keySet(); //遍历键的集合,获取到每一个键。用增强for实现 for (String key : keySet) { //根据键去找值。用get(Object key)方法实现 String value = map.get(key); System.out.println(key + "," + value);} // ——————————————第二种—————————————————————— //获取所有键值对对象的集合 Set<Map.Entry<String, String>> entrySet = map.entrySet(); //遍历键值对对象的集合,得到每一个键值对对象 for (Map.Entry<String, String> me : entrySet) { //根据键值对对象获取键和值 String key = me.getKey(); String value = me.getValue(); System.out.println(key + "," + value);}
1. Properties集合
🔰Properties介绍:
● 是一个Map体系的集合类
● Properties可以保存到流中或从流中加载
● 属性列表中的每个键及其对应的值都是一个字符串//创建集合对象 Properties prop = new Properties(); //存储元素 prop.put("itheima001", "林青霞"); prop.put("itheima002", "张曼玉"); //遍历集合 Set<Object> keySet = prop.keySet(); for (Object key : keySet) { Object value = prop.get(key); System.out.println(key + "," + value); }
//创建集合对象 Properties prop = new Properties(); prop.setProperty("itheima001", "林青霞"); prop.setProperty("itheima003", "王祖贤"); System.out.println(prop.getProperty("itheima001")); Set<String> names = prop.stringPropertyNames(); for (String key : names) { String value = prop.getProperty(key); System.out.println(key + "," + value); }
2. Properties和IO流相结合的方法
public class ObjectDemo {
public static void main(String[] args) throws IOException {
//把集合中的数据保存到文件
myStore();
//把文件中的数据加载到集合
myLoad();
}
private static void myLoad() throws IOException {
Properties prop = new Properties();
//void load(Reader reader):
FileReader fr = new FileReader("E:\\ss\\array.txt");
prop.load(fr);
fr.close();
System.out.println(prop);
}
private static void myStore() throws IOException {
Properties prop = new Properties();
prop.setProperty("itheima001","林青霞");
prop.setProperty("itheima003","王祖贤");
//void store(Writer, String comments):
FileWriter fw = new FileWriter("E:\\ss\\array.txt");
prop.store(fw,null);
fw.close();
}
}
十一. 继承
(一) 继承格式
● 格式:
public class 子类名 extends 父类名() {}
● 范例:public class Fu extends Zi() {}
● Fu:父类,也称基类; Zi:子类,也称派生类;
(二) 继承中子类的特点
● 子类可以有父亲的内容
● 子类可以有自己特有的内容
(三) 继承中变量的访问特点
🔰在子类中访问一个变量;
● 子类局部变量中找
● 子类成员变量中找
● 父类成员变量中找
● 都没有就报错
(四) 成员方法的访问特点
🔰通过子类对象访问一个方法;
● 子类成员方法中找
● 父类成员方法中找
● 都没有就报错
(五) super
🔰子类中所有的构造方法默认都会访问父类中无参的构造方法;
⚪ 因为子类会继承父类中的数据,可能还会使用父类中的数据。所以,子类初始化之前,一定要先完成父类数据的初始化;
⚪ 每个子类构造方法的第一条语句默认都是:super()
关键字 访问成员变量 访问构造方法 访问成员方法 this this.成员变量
访问本类成员变量this(…)
访问本类构造方法this.成员方法(…)
访问本类成员方法super super.成员变量
访问父类成员变量super(…)
访问父类构造方法super.成员方法(…)
访问父类成员方法
(六) 方法重写
🔰概述:子类中出现和父类中一模一样的方法声明
🔰应用:当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法
🔰 @Override
● 是一个注解,可以帮助我们检查重写方法的方法声明的正确性
● 注意事项:
》私有方法不能被重写(父类私有成员子类是不能访问的)
》子类方法访问权限不能更低(public > 默认 > 私有)
(七) java中继承的注意事项
● java中只支持单继承,不支持多继承
● java中支持多层继承
十二. 修饰符
(一) 包 (package)
🔰包定义格式:
● 格式:package 包名;
(多级包用.
分开)
● 范例:package com.itheima;
(二) 导包 (import)
🔰导包的格式:
● 格式:import 包名;
● 范例:import itcat.teacher;
(三) 权限修饰符(public, protected, private)
🔰 可以修饰成员方法,成员变量,类
修饰符 同一类内 同一包内子类或无关类 不同包内子类 不同包内无关类 public(公共)
√ √ √ √ protected(保护)
√ √ √ 默认
√ √ private(私有)
√
(四) 状态修饰符(final, static)
1. final(最终态)
🔰可以修饰成员方法,成员变量,类
特点:
🔰 修饰方法:表明该方法是最终方法,不能被重写;
🔰 修饰变量:表明该变量是常量,不能再次被赋值;
🔰 修饰类:表明该类是最终类,不能被继承;
2. static(静态)
🔰 可以修饰成员方法,成员变量
特点:
🔰 被类的所有对象共享(判断是否使用静态关键词的条件);
🔰 可以通过类名调用;
🔰 静态成员方法只能访问静态的成员(变量,方法);
🔰 非静态的成员方法都(静态成员,非静态成员)能访问;
十三. 多态
🔰多态的形式:具体类多态,抽象类多态,接口多态
🔰多态的前提与实现:
● 有继承/实现关系
● 有方法重写
● 有父(类/接口)引用指向(子/实现)类对象
(一) 多态成员方法特点
🔰 成员变量;编译看左边,执行看左边
🔰 成员方法;编译看左边,执行看右边
(执行时,如果右边没有成员方法,就执行左边的成员方法)
如果编译时找不到成员变量\方法,就会出错,无法运行
(二) 多态的好处与弊端
🔰 好处:定义方法的时候,使用父类作为参数,将来在使用的时候,使用具体的子类型参与操作
🔰 弊端:不能使用子类的特有功能
(三) 多态的转型
🔰向上转型 Animal a = new Cat();
从子到父(创建子类对象,赋值给父类参数)
🔰向下转型 Cat c = (Cat)a;
从父到子(父类参数强制转换成子类参数)
通过向下转型可以访问Zi类的特有方法
十四. 抽象 (abstract )
🔰 没有方法体的方法应该定义为抽象方法; 如果有抽象方法,该类必须定义为抽象类
🔰 抽象类和抽象方法必须使用 abstract 关键字修饰
public abstract class 类名{}
public abstract void 方法名();
🔰 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
🔰 抽象类不能实例化
🔰 参照多态的方式,通过子类对象实例化,这叫抽象多态
(一) 抽象多态的前提与实现:
🔰 有继承/实现关系
🔰 有方法重写
🔰 有父(类/接口)引用指向(子/实现)类对象
(二) 抽象类的成员特点
🔰有成员变量(变量,常量都可以)
🔰有构造方法(不能实例化,用于子类访问父类数据的初始化)
🔰可以有抽象方法,也可以有普通方法
十五. 接口 (interface )
🔰接口就是一种公共的规范标准,只要符合规范标准,大家都可以用
🔰特点:
Ⓜ️接口用关键字 interface 修饰
public interface 接口名{}
Ⓜ️类实现接口用 implements 表示
public class 类名 implements 接口名{}
Ⓜ️接口不能实例化
● 参照多态的方式,通过实现类对象实例化,这叫接口多态
Ⓜ️多态的前提与实现:
● 有继承/实现关系
● 有方法重写
● 有父(类/接口)引用指向(子/实现)类对象
Ⓜ️接口的实现类
● 要么在实现类中重写接口中的所有抽象方法
● 要么把实现类定义为抽象类
● 如果实现类是父类,可以不重写接口中的抽象方法,但是子类要重写
(一) 接口的成员特点
🔰成员变量
只能是常量,没有变量
常量前有默认修饰符:public static final
🔰没有构造方法
🔰 成员方法
只能是抽象方法
抽象方法前有默认修饰符:public abstract
(二) 类和接口的关系
● 实现关系,可以单实现,也可以多实现
● 可以在继承一个类的同时实现多个接口
(三) 接口和接口的关系
● 继承关系,可以单继承,也可以多继承
十六. 参数传递
(一) 类名作为形参和返回值
🔰方法的形参是类名,其实需要的是该类的对象
🔰方法的返回值是类名,其实返回的是该类的对象
( 二) 抽象类名作为形参和返回值
🔰方法的形参是抽象类名,其实需要的是该抽象类的子类对象
🔰方法的返回值是抽象类名,其实返回的是该抽象类的子类对象
(三) 接口名作为形参和返回值
🔰方法的形参是接口名,其实需要的是该接口的实现类对象
🔰方法的返回值是接口名,其实返回的是该接口的实现类对象
十七. 内部类
🔰描述:就是在一个类中定义一个类;
🔰格式:
public class 类名 {
修饰符 class 类名 {
}
]
🔰内部类的访问特点:
● 内部类可以直接访问外部类的成员,包括私有;
● 外部类要访问内部类的成员,必须创建对象;
(一) 成员内部类(在类的成员位置)
🔰成员内部类的定义位置:
在类中方法,跟成员变量是一个位置
🔰成员内部类,外界如何创建对象使用呢?
● 格式:外部类名.内部类名 对象 = 外部类对象.内部类对象;
● 范例:outer.lnner oi = new outer().new lnner();
(二) 局部内部类
🔰局部内部类是
在方法中定义的类
,所以外界是无法直接使用,需要在方法内部创建对象使用;
🔰该类可以直接访问外部类的成员,也可以访问方法内的局部变量;public class outer { int num = 20; public void method() { class inter { public void show() { System.out.println(num); } } inter i = new inter(); i.show(); } }
(三) 匿名内部类
🔰前提:存在一个类或者接口,这里的类可以是具体类也可以是抽象类;
🔰格式:new 类名或接口名() { 重写方法; }; new Inter(){ @Override public void method(){} }
十八. 异常
🔰异常:就是程序出现了不正常的情况
🔰Error
:严重问题,不需要处理;
🔰Exception
:称为异常类,它表示程序本身可以处理的问题;
●RuntimeException
:在编译期是不检查的,出现问题后,需要我们会来修改代码;
●非RuntimeException
:编译期就必须处理的,否则程序不能通过编译,就更不能运行了;
(一) IVM的默认处理方案
🔰如果程序出现了问题,我们没有做任何处理,最终 JVM 会默认的处理
● 把异常的名称,异常原因及异常出现的位置等信息输出在了控制台;
● 程序停止执行;
(二) try-catch方式处理异常
🔰定义格式:
try { 可能出现异常的代码; } catch(异常类名 变量名) { 异常的处理代码;}
🔰执行流程:
● 程序从 try 里面的代码开始执行
● 出现异常,就会跳转到对应的 catch 里面去执行
● 执行完毕之后,程序还可以继续往下执行
🔰示例代码:public class ExceptionDemo01 { public static void main(String[] args) { System.out.println("开始"); method(); System.out.println("结束"); } public static void method() { try { int[] arr = {1, 2, 3}; System.out.println(arr[3]); System.out.println("这里能够访问到吗"); } catch (ArrayIndexOutOfBoundsException e) { // System.out.println("你访问的数组索引不存在,请回去修改为正确的索引"); e.printStackTrace(); } } }
(三) throws方式处理异常
🔰定义格式:
public void 方法() throws 异常类名 { } //编译时异常 public static void method2() throws ParseException {} //运行时异常 public static void method() throws ArrayIndexOutOfBoundsException {}
🔰注意事项:
● 这个throws格式是跟在方法的括号后面的
● 编译时异常必须要进行处理,两种处理方案:try…catch …或者 throws,如果采用 throws 这种方案,将来谁调用谁处理
● 运行时异常可以不处理,出现问题后,需要我们回来修改代码
十九. 递归
🔰递归的介绍:
● 以编程的角度来看,递归指的是方法定义中调用方法本身的现象
● 把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
● 递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算
🔰递归的注意事项:
● 递归一定要有出口。否则内存溢出
● 递归虽然有出口,但是递归的次数也不宜过多。否则内存溢出
(一) 递归的基本使用
1. 不死神兔问题
🔰每个月的兔子对数:1,1,2,3,5,8,…,求第20个月兔子的对数
public class ObjectDemo {
public static void main(String[] args){
// 第一种:for循环求解
int[] arr = new int[20];
arr[0] = 1;
arr[1] = 1;
for (int i = 2; i < arr.length; i++) {
arr[i] = arr[i - 1] + arr[i - 2];
}
System.out.println(arr[19]);
// 第二种:递归函数求解
//调用方法
int result = f(20);
//输出结果
System.out.println("第20个月兔子的对数:" + result);
}
/*
递归解决问题,首先就是要定义一个方法:
定义一个方法f(n):表示第n个月的兔子对数
那么,第n-1个月的兔子对数该如何表示呢?f(n-1)
同理,第n-2个月的兔子对数该如何表示呢?f(n-2)
StackOverflowError:当堆栈溢出发生时抛出一个应用程序递归太深
*/
public static int f(int n) {
if(n==1 || n==2) {
return 1;
} else {
return f(n - 1) + f(n - 2);
}
}
}
2. 递归求阶乘
🔰用递归求5的阶乘,并把结果在控制台输出
public class ObjectDemo {
public static void main(String[] args){
//调用方法
int result = jc(3);
//输出结果
System.out.println("5的阶乘是:" + result);
}
//定义一个方法,用于递归求阶乘,参数为一个int类型的变量
public static int jc(int n) {
//在方法内部判断该变量的值是否是1
if(n == 1) {
//是:返回1
return 1;
} else {
//不是:返回n*(n-1)!
return n*jc(n-1);
}
}
}
3. 递归遍历目录
🔰给定一个路径(E:\ss),通过递归完成遍历该目录下所有内容,并把所有文件的绝对路径输出在控制台
import java.io.File;
public class ObjectDemo {
public static void main(String[] args){
//根据给定的路径创建一个File对象
File srcFile = new File("E:\\ss");
//调用方法
getAllFilePath(srcFile);
}
//定义一个方法,用于获取给定目录下的所有内容,参数为第1步创建的File对象
public static void getAllFilePath(File srcFile) {
//获取给定的File目录下所有的文件或者目录的File数组
File[] fileArray = srcFile.listFiles();
//遍历该File数组,得到每一个File对象
if (fileArray != null) {
for (File file : fileArray) {
//判断该File对象是否是目录
if (file.isDirectory()) {
//是:递归调用
getAllFilePath(file);
} else {
//不是:获取绝对路径输出在控制台
System.out.println(file.getAbsolutePath());
}
}
}
}
}
二十. 多线程
(一) 进程和线程
🔰进程:是正在运行的程序;
● 是系统进行资源分配和调用的独立单位;
● 每一个进程都有它自己的内存空间和系统资源;
🔰线程:是进行中的单个顺序控制流,是一条执行路径;
● 单线程:一个进程如果只有一个执行路径,则称之为单线程程序;
● 多线程:一个进程如果有多条执行路径,则称之为多线程程序;
(二) 实现多线程方式一:继承Thread类
🔰实现步骤:
● 定义一个类MyThread继承Thread类
● 在MyThread类中重写run()方法
◉ 因为run()是用来封装被线程执行的代码, 所以要重写
● 创建MyThread类的对象
● 启动线程public class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.print(i + " "); } } } public class MyThreadDemo{ public static void main(String[] args) { MyThread my1 = new MyThread(); MyThread my2 = new MyThread(); my1.start(); } }
🔰run()方法和start()方法的区别:
●run()
:封装线程执行的代码,直接调用,相当于普通方法的调用
●start()
:启动线程;然后由JVM调用此线程的run()方法
1. 设置和获取线程名称
public class MyThread extends Thread{ public MyThread() {} public MyThread(String name) { super(name); } @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(getName()+":"+i + " "); } } } public class MyThreadDemo { public static void main(String[] args) { MyThread my1 = new MyThread(); MyThread my2 = new MyThread(); // setName 方法设置线程名称 my1.setName("高铁"); my2.setName("飞机"); // 用带参数的构造方法设置线程名称 MyThread my1 = new MyThread("高铁"); MyThread my2 = new MyThread("飞机"); my1.start(); my2.start(); // 返回对当前正在执行的线程对象的引用 System.out.println(Thread.currentThread().getName()); } }
2. 线程优先级
🔰线程调度:
◉ 两种调度方式
● 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
● 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
◉ Java使用的是抢占式调度模型
◉ 随机性
● 假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的;
public class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + ":" + i); } } } public class MyThreadDemo { public static void main(String[] args) { MyThread tp1 = new MyThread(); MyThread tp2 = new MyThread(); MyThread tp3 = new MyThread(); tp1.setName("高铁"); tp2.setName("飞机"); tp3.setName("汽车"); // getPriority():返回此线程的优先级 System.out.println(tp1.getPriority()); //5 System.out.println(tp2.getPriority()); //5 System.out.println(tp3.getPriority()); //5 // setPriority(int newPriority):更改此线程的优先级 System.out.println(Thread.MAX_PRIORITY); //10 System.out.println(Thread.MIN_PRIORITY); //1 System.out.println(Thread.NORM_PRIORITY); //5 //设置正确的优先级 tp1.setPriority(5); tp2.setPriority(10); tp3.setPriority(1); tp1.start(); tp2.start(); tp3.start(); } }
3. 线程控制
public class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(getName() + ":" + i); try { // 使当前正在执行的线程停留(暂停执行)指定的毫秒数 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class MyThreadDemo { public static void main(String[] args) { MyThread ts1 = new MyThread(); MyThread ts2 = new MyThread(); MyThread ts3 = new MyThread(); ts1.setName("曹操"); ts2.setName("刘备"); ts3.setName("孙权"); // 设置主线程为刘备 Thread.currentThread().setName("主线程刘备"); ts1.start(); try { ts1.join(); } catch (InterruptedException e) { e.printStackTrace(); } // 设置守护线程 ts2.setDaemon(true); ts3.setDaemon(true); ts2.start(); ts3.start(); for(int i=0; i<10; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } } }
4. 线程的生命周期
🔰线程一共有五种状态,线程在各种状态之间转换。
(三) 实现多线程方式二:实现Runnable接口
🔰实现步骤:
● 定义一个类MyRunnable实现Runnable接口
● 在MyRunnable类中重写run()方法
● 创建MyRunnable类的对象
● 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
● 启动线程public class MyRunnable implements Runnable { @Override public void run() { for(int i=0; i<20; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } } } public class MyRunnableDemo { public static void main(String[] args) { //创建MyRunnable类的对象 MyRunnable my = new MyRunnable(); //创建Thread类的对象,把MyRunnable对象作为构造方法的参数 //Thread(Runnable target) // Thread t1 = new Thread(my); // Thread t2 = new Thread(my); Thread t1 = new Thread(my,"高铁"); Thread t2 = new Thread(my,"飞机"); //启动线程 t1.start(); t2.start(); } }
🔰多线程的实现方案有两种:
● 继承Thread类
● 实现Runnable接口
🔰相比继承Thread类,实现Runnable接口的好处:
● 避免了Java单继承的局限性
● 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
(四) 线程同步
1. 同步代码块解决数据安全问题
🔰安全问题出现的条件:
● 是多线程环境
● 有共享数据
● 有多条语句操作共享数据
🔰如何解决多线程安全问题呢?
● 基本思想:让程序没有安全问题的环境
🔰怎么实现呢?
● 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
● Java提供了同步代码块的方式来解决
🔰同步代码块格式:synchronized(任意对象) { 多条语句操作共享数据的代码 }
● synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
//某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
public class SellTicket implements Runnable {
private int tickets = 100;
private Object obj = new Object();
@Override
public void run() {
while (true) {
//tickets = 100;
//t1,t2,t3
//假设t1抢到了CPU的执行权
//假设t2抢到了CPU的执行权
synchronized (obj) {
//t1进来后,就会把这段代码给锁起来
if (tickets > 0) {
try {
Thread.sleep(100);
//t1休息100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//窗口1正在出售第100张票
System.out.println(Thread.currentThread().getName() + "正在出售 第" + tickets + "张票");
tickets--; //tickets = 99;
}
}
//t1出来了,这段代码的锁就被释放了
}}}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}}
2. 同步方法解决数据安全问题
🔰同步方法的格式
● 同步方法:就是把synchronized关键字加到方法上修饰符 synchronized 返回值类型 方法名(方法参数) { 方法体; }
● 同步方法的锁对象是什么呢?
this
🔰静态同步方法
● 同步静态方法:就是把synchronized关键字加到静态方法上修饰符 static synchronized 返回值类型 方法名(方法参数) { 方法体; }
● 同步静态方法的锁对象是什么呢?
类名.class
public class SellTicket implements Runnable {
private static int tickets = 100;
private int x = 0;
@Override
public void run() {
while (true) {
sellTicket();
}
}
// 同步方法
// private synchronized void sellTicket() {
// if (tickets > 0) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName() + "正在出售第" +tickets + "张票");
// tickets--;
// }
// }
// 静态同步方法
private static synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" +
tickets + "张票");
tickets--;
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
3. Lock锁
🔰Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
● ReentrantLock构造方法
方法名 说明 ReentrantLock() 创建一个ReentrantLock的实例 ● 加锁解锁方法
方法名 说明 void lock() 获得锁 void unlock() 释放锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicket implements Runnable {
private int tickets = 100;
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();
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
(五) 生产者消费者
1. 生产者和消费者模式概述
🔰所谓生产者消费者问题,实际上主要是包含了两类线程:
● 一类是生产者线程用于生产数据
● 一类是消费者线程用于消费数据
● 为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
● 生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为● 消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
🔰Object类的等待和唤醒方法
方法名 说明 void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 void notify() 唤醒正在等待对象监视器的单个线程 void notifyAll() 唤醒正在等待对象监视器的所有线程
2. 生产者和消费者案例
🔰生产者消费者案例中包含的类:
● 奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
● 生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
● 消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作
● 测试类(BoxDemo):里面有main方法,main方法中的代码步骤如下:
①创建奶箱对象,这是共享数据区域
②创建消费者创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
③对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
④创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
⑤启动线程
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();
}
}
public class Producer implements Runnable { // 生产者类
private Box b;
public Producer(Box b) {
this.b = b;
}
@Override
public void run() {
for(int i=1; i<=30; i++) {
b.put(i);
}
}
}
public class Customer implements Runnable { // 消费者类
private Box b;
public Customer(Box b) {
this.b = b;
}
@Override
public void run() {
while (true) {
b.get();
}
}
}
public class BoxDemo { // 测试类
public static void main(String[] args) {
//创建奶箱对象,这是共享数据区域
Box b = new Box();
//创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
Producer p = new Producer(b);
//创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
Customer c = new Customer(b);
//创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
//启动线程
t1.start();
t2.start();
}
}
二十一. 网络编程入门
🔰计算机网络:是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统;
🔰网络编程: 在网络通信协议下,实现网络互连的不同计算机上运行的程序间可以进行数据交换;
(一) 网络编程三要素
🔰IP地址:
● 要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而IP地址就是这个标识号。也就是设备的标识。
🔰端口:
● 网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序了。也就是应用程序的标识。
🔰协议:
● 通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。常见的协议有UDP协议和TCP协议。
(二) IP地址
🔰IP地址:是网络中设备的唯一标识;
🔰IP地址分为两大类
◉ IPv4:是给每个连接在网络上的主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,也就是4个字节。例如一个采用二进制形式的IP地址是“11000000 1010100000000001 01000010”,这么长的地址,处理起来也太费劲了。为了方便使用,IP地址经常被写成十进制的形式,中间使用符号“.”分隔不同的字节。于是,上面的IP地址可以表示为“192.168.1.66”。IP地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多;
◉ IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,这样就解决了网络地址资源数量不够的问题;
🔰DOS常用命令:
◉ ipconfig:查看本机IP地址
◉ ping IP地址:检查网络是否连通
🔰特殊IP地址:
◉ 127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用
(三) InetAddress
🔰InetAddress:此类表示Internet协议(IP)地址
import java.net.InetAddress; import java.net.UnknownHostException; public class InetAddressDemo { public static void main(String[] args) throws UnknownHostException { //确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址 InetAddress address = InetAddress.getByName("192.168.1.66"); // 获取此IP地址的主机名 String name = address.getHostName(); // 返回文本显示中的IP地址字符串 String ip = address.getHostAddress(); System.out.println("主机名:" + name); System.out.println("IP地址:" + ip); } }
(四) 端口和协议
🔰端口:
◉ 设备上应用程序的唯一标识
🔰端口号
◉ 用两个字节表示的整数,它的取值范围是0 ~ 65535。其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。
🔰协议
◉ 计算机网络中,连接和通信的规则被称为网络通信协议
🔰UDP协议
◉ 用户数据报协议(User Datagram Protocol)
◉ UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
◉ 由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输。
◉ 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。
🔰TCP协议
◉ 传输控制协议 (Transmission Control Protocol)
◉ TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。
◉ 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
第一次握手,客户端向服务器端发出连接请求,等待服务器确认
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
第三次握手,客户端再次向服务器端发送确认信息,确认连接
◉ 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页等。
(五) UDP通信程序
1. UDP发送数据
🔰Java中的UDP通信:
◉ UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象,因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念;
◉ Java提供了DatagramSocket类作为基于UDP协议的Socket;
🔰构造方法:
🔰相关方法
🔰发送数据的步骤
◉ 创建发送端的Socket对象(DatagramSocket)
◉ 创建数据,并把数据打包
◉ 调用DatagramSocket对象的方法发送数据
◉ 关闭发送端
// UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
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("layman"), 12345);
//调用DatagramSocket对象的方法发送数据
ds.send(dp);
}
//关闭发送端
ds.close();
}
}
2.UDP接收数据
🔰接受数据的步骤
◉ 创建接受端的Socket对象(DatagramSocket)
◉ 创建数据包,用于接收数据
◉ 调用DatagramSocket对象的方法接受数据
◉ 解析数据包,并把数据在控制台显示
◉ 关闭发送端
🔰构造方法
🔰相关方法
// UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建接收端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket(12345);
while (true) {
//创建一个数据包,用于接收数据
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
//调用DatagramSocket对象的方法接收数据
ds.receive(dp);
//解析数据包,并把数据在控制台显示
System.out.println("数据是:" + new String(dp.getData(), 0,
dp.getLength()));
}
//关闭接收端
// ds.close();
}
}
(六) TCP通信程序
1. TCP发送数据
🔰Java中的TCP通信
◉ Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。
◉ Java为客户端提供了Socket类,为服务器端提供了ServerSocket类
🔰构造方法
🔰相关方法
public class Box {
// 客户端:数据来自于键盘录入, 直到输入的数据是886,发送数据结束
// 客户端创建对象,使用键盘录入循环接受数据,接受一行发送一行,直到键盘录入886为止
public synchronized void FaSong() throws IOException {
//创建客户端Socket对象
Socket s = new Socket("www.baidu.com",12345);
System.out.print(">>>");
//数据来自于键盘录入,直到输入的数据是886,发送数据结束
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//封装输出流对象
BufferedWriter bw = new BufferedWriter(new
OutputStreamWriter(s.getOutputStream()));
String line;
while ((line=br.readLine())!=null) {
if("886".equals(line)) {
break;
}
//获取输出流对象
bw.write(line);
bw.newLine();
bw.flush();
}
//释放资源
s.close();
}}
2. TCP接收数据
🔰构造方法
🔰相关方法
public class ClientDemo {
// 服务端:接收到数据在控制台输出
// 服务端创建对象,使用输入流按行循环接受数据,直到接受到null为止
public synchronized String JieShou() throws IOException {
//创建服务器Socket对象
ServerSocket ss = new ServerSocket(12345);
//监听客户端的连接,返回一个对应的Socket对象
Socket s = ss.accept();
//获取输入流
BufferedReader br = new BufferedReader(new
InputStreamReader(s.getInputStream()));
//把数据写入文本文件
//BufferedWriter bw = new BufferedWriter(new FileWriter("myNet\\s.txt"));
//String line;
//while ((line=br.readLine())!=null) {
// bw.write(line);
// bw.newLine();
// bw.flush();
//}
//释放资源
//bw.close();
String line;
while ((line = br.readLine()) != null) {
return line;
}
//释放资源
ss.close();
return null;
}
}
二十二. Lambda表达式
(一) 体验Lambda表达式
🔰案例需求
◉ 启动一个线程,在控制台输出一句话:多线程程序启动了
🔰实现方式一:
◉ 定义一个类MyRunnable实现Runnable接口,重写run()方法
◉ 创建MyRunnable类的对象
◉ 创建Thread类的对象,把MyRunnable的对象作为构造参数传递
◉ 启动线程
🔰实现方式二
◉ 匿名内部类的方式改进
🔰实现方式三
◉ Lambda表达式的方式改进
//方式一的线程类
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("多线程程序启动了");
}}
public class LambdaDemo {
public static void main(String[] args) {
//方式一
// MyRunnable my = new MyRunnable();
// Thread t = new Thread(my);
// t.start();
//方式二
//new Thread(new Runnable() {
// @Override
// public void run() {
// System.out.println("多线程程序启动了");
// }
//}).start();
//方式三
new Thread(() -> {
System.out.println("多线程程序启动了");
}).start();
}
}
🔰函数式编程思想概述
◉ 函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”
◉ 而我们要学习的Lambda表达式就是函数式思想的体现
(二) Lambda表达式的标准格式
🔰格式:
(形式参数) -> {代码块}
◉ 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
◉ ->:由英文中画线和大于符号组成,固定写法。代表指向动作
◉ 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容
🔰组成Lambda表达式的三要素:
◉ 形式参数,箭头,代码块
(三) Lambda表达式练习 1
🔰Lambda表达式的使用前提:
◉ 有一个接口
◉ 接口中有且仅有一个抽象方法
🔰练习描述:
◉ 无参无返回值抽象方法的练习
//接口
public interface Eatable {
void eat();
}
//实现类
public class EatableImpl implements Eatable {
@Override
public void eat() {
System.out.println("一天一苹果,医生远离我");
}
}
//测试类
public class EatableDemo {
public static void main(String[] args) {
//在主方法中调用useEatable方法
Eatable e = new EatableImpl();
useEatable(e);
//匿名内部类
useEatable(new Eatable() {
@Override
public void eat() {
System.out.println("一天一苹果,医生远离我");
}
});
//Lambda表达式
useEatable(() -> {
System.out.println("一天一苹果,医生远离我");
});
}
private static void useEatable(Eatable e) {
e.eat();
}
}
(四) Lambda表达式练习 2
🔰练习描述:
◉有参无返回值抽象方法的练习
//接口
public interface Flyable {
void fly(String s);
}
//测试类
public class FlyableDemo {
public static void main(String[] args) {
//在主方法中调用useFlyable方法
//匿名内部类
useFlyable(new Flyable() {
@Override
public void fly(String s) {
System.out.println(s);
System.out.println("飞机自驾游");
}
});
System.out.println("--------");
//Lambda
useFlyable((String s) -> {
System.out.println(s);
System.out.println("飞机自驾游");
});
}
private static void useFlyable(Flyable f) {
f.fly("风和日丽,晴空万里");
}
}
(五) Lambda表达式练习 3
🔰练习描述:
◉有参有返回值抽象方法的练习
//接口
public interface Addable {
int add(int x,int y);
}
//测试类
public class AddableDemo {
public static void main(String[] args) {
//在主方法中调用useAddable方法
useAddable((int x,int y) -> {
return x + y;
});
}
private static void useAddable(Addable a) {
int sum = a.add(10, 20);
System.out.println(sum);
}
}
(六) Lambda表达式的省略模式
🔰省略的规则:
◉ 参数类型可以省略。但是有多个参数的情况下,不能只省略一个
◉ 如果参数有且仅有一个,那么小括号可以省略
◉ 如果代码块的语句只有一条,可以省略大括号和分号,和return关键字
public interface Addable {
int add(int x, int y);
}
public interface Flyable {
void fly(String s);
}
public class LambdaDemo {
public static void main(String[] args) {
// useAddable((int x,int y) -> {
// return x + y;
// });
//参数的类型可以省略
useAddable((x, y) -> {
return x + y;
});
// useFlyable((String s) -> {
// System.out.println(s);
// });
//如果参数有且仅有一个,那么小括号可以省略
useFlyable(s -> {
System.out.println(s);
});
//如果代码块的语句只有一条,可以省略大括号和分号
useFlyable(s -> System.out.println(s));
//如果代码块的语句只有一条,可以省略大括号和分号,如果有return,return也要省略掉
useAddable((x, y) -> x + y);
}
private static void useFlyable(Flyable f) {
f.fly("风和日丽,晴空万里");
}
private static void useAddable(Addable a) {
int sum = a.add(10, 20);
System.out.println(sum);
}
}
(七) Lambda表达式的注意事项
🔰使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
🔰必须有上下文环境,才能推导出Lambda对应的接口
◉ 根据局部变量的赋值得知Lambda对应的接口
Runnable r = () -> System.out.println("Lambda表达式");
◉ 根据调用方法的参数得知Lambda对应的接口
new Thread(() -> System.out.println("Lambda表达式")).start();
(八) Lambda表达式和匿名内部类的区别
🔰所需类型不同
◉ 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
◉ Lambda表达式:只能是接口
🔰使用限制不同
◉ 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
◉ 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
🔰实现原理不同
◉ 匿名内部类:编译之后,产生一个单独的.class字节码文件
◉ Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成
二十三. 接口组成更新
(一) 接口组成更新概述
🔰常量:
public static final
🔰抽象方法:public abstract
(二) 接口中默认方法
🔰格式:
public default 返回值类型 方法名(参数列表) { }
public default void show3() { }
🔰注意事项
◉ 默认方法不是抽象方法,所以不强制被重写。但是可以被重写,重写的时候去掉default关键字
◉ public可以省略,default不能省略
(三) 接口中静态方法
🔰格式:
public static 返回值类型 方法名(参数列表) { }
public static void show3() { }
🔰注意事项
◉ 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
◉ public可以省略,static不能省略
(四) 接口中私有方法
🔰格式1:
private 返回值类型 方法名(参数列表) { }
private void show3() { }
🔰格式2:
private static 返回值类型 方法名(参数列表) { }
private static void show3() { }
🔰注意事项
◉ 默认方法可以调用私有的静态方法和非静态方法
◉ 静态方法只能调用私有的静态方法