学习java中的笔记
学习java中整理的笔记
- java基础知识
- 1 Java概述
-
- 1.1 Java语言发展史
- 1.2 Java开发环境
- 1.3 JDK的下载和安装
-
-
- 1.3.1 下载
- 1.3.2 安装
-
- 2 入门程序HelloWorld
-
- 2.1 常用DOS命令
- 2.2 HelloWorld案例
-
- 2.2.1 Java程序开发运行流程
- 2.2.2 HelloWorld案例的编写
- 2.2.3 HelloWorld案例的编译和运行
- 3 IDEA安装使用
-
- 3.1 下载
- 3.2 安装
- 3.3 idea常用快捷键
- 4 Java基础语法
-
- 4.1 注释
-
- 单行注释
- 多行注释
- 4.2 数据类型
-
- 4.2.1 计算机存储单元
- 4.2.2 变量
- 4.3 Java中的基本数据类型
- 4.4 关键字、标志符
-
- 4.4.1 关键字
- 4.4.1 标志符
- 4.5 基本命名规定
-
- 4.5.1 变量名称
- 4.5.2 类名称
- 4.6 类型间的转换
-
- 4.6.1 自动/隐式类型转换:小类型到大类型
- 4.6.2 强制类型转换:大类型到小类型
- 4.7 运算符
-
- 4.7.1 算术运算符
- 4.7.2 关系运算符
- 4.7.3 逻辑运算符
- 4.7.4 赋值运算符
- 4.7.5 短路逻辑运算符
- 4.7.6 字符的“+”操作
- 4.7.7 字符串的“+”操作
- 4.7.8 三元运算符
- 4.8 流程控制语句
-
- 4.8.1 顺序结构
- 4.8.2 分支结构(if, switch)
-
- 1. if语句:
- 2. switch语句:
- 4.8.3 循环结构(for, while, do…while)
-
- 1. for循环语句
- 2. while循环语句
- 3. do…while循环语句
- 4. 三种循环的区别
- 5. 跳转控制语句
- 6. 循环嵌套
- 5. 数组
-
- 5.1 数组定义格式
-
- 5.1.1 第一种方式
- 5.1.2 第二种方式
- 5.2 数组动态初始化
- 5.3静态初始化格式
- 5.4 访问数组元素格式
- 5.5 索引越界异常
- 5.6 空指针异常
- 5.7 数组遍历
- 5.8 数组最值
- 5.9 循环控制语句
- 6 方法
-
- 6.1 定义方法
- 6.2 调用方法
- 6.3 return:
- 6.4 形参和实参
- 6.5 方法重载
- 7 类和对象
-
- 7.1 对象的使用
- 7.2 成员变量和局部变量的区别
- 7.3 封装思想
- 7.4 构造方法
- 7.5 构造方法的注意事项
- 7.6 private关键字
- 7.7 this关键字
- 8 继承
-
- 8.1 继承概念
- 8.2 继承特点
- 8.3 继承的作用
- 8.4 继承带来的好处与弊端
- 8.5 super关键字
- 8.6 this&super关键字
- 8.7 向上造型
- 8.8 方法的重写
- 9 多态
-
- 9.1 常见形式
- 9.2 多态中成员访问特点
- 9.3 多态的前提
- 9.4 多态的优势
- 9.5 向上造型/自动类型转换:
- 9.6 强制类型转换
- 9.7 可能出现的异常
- 10 访问控制权限修饰符
- 11 static关键字
-
- 11.1 static作用
- 11.2 静态成员变量
- 11.3 静态成员方法
- 11.4 静态代码块
- 12 final关键字
-
- 12.1 static final常量
- 13 抽象类
- 14 接口
-
- 14.1 定义接口
- 14.2 实现接口
- 14.3 接口的特点
- 14.4 接口的成员特点
- 14.5 类和接口的关系
- 14.6抽象类和接口的区别
- 15 内部类
-
- 15.1 成员内部类
- 15.2 匿名内部类
- 16 内存分配
-
- 16.1 内存概述
- 16.2 Java中的内存分配
- 16.3 内存管理
- 17 面向对象三大特征
- 18 String类
-
- 18.1 概述
- 18.2 String常用构造方法:
- 18.3 String常用方法
- 18.4 StringBuilder类
- 18.4 常量池
- 19 正则表达式
-
- String支持与正则表达式相关的方法
- 20 Object类
-
- 20.1 Object类的作用
- 20.2 Object类的常用方法
- 20.3 ==操作符与equals方法
- 21 包装类
-
- 21.1 包装类的作用
- 21.2 包装类的特有功能
- 21.3 转义序列
- 22 异常
-
- 22.1 异常分类
- 22.2 异常体系
- 22.3 运行时异常
- 22.4 编译时异常
- 22.5JVM默认处理异常流程
- 22.6 throws异常处理方式
- 22.7 try-catch异常处理方式
- 22.8 throws与try-catch的区别
- 22.9 自定义异常
- 23 File类
-
- 23.1 判断功能
- 23.2 File类创建和删除
- 23.3 File类的遍历
- 24 Lambda表达式
- 25 IO流
-
- 25.1 字节流
-
- 25.1.1 文件字节输入流FileInputStream
- 25.1.2 文件字节输出流FileOutputStream
- 25.1.3 流的关闭与刷新
- 25.2 字符流
-
- 25.2.1 文件字符输入流:FileReader
- 25.2.2 文件字符输出流:FileWriter
- 25.3 字节缓冲流
-
- 25.3.1 字节缓冲输入流: BufferedInputStream
- 25.3.2 字节缓冲输出流:BufferedOutputStream
- 25.4 字符缓冲流
-
- 25.4.1 字符缓冲输入流:BufferedReader
- 25.4.2 字符缓冲输出流:BufferedWriter
- 25.5 转换流
-
- 25.5.1 字符输入转换流:InputStreamReader
- 25.5.1 字符输出转换流:OutputStreamWriter
- 25.6 对象流
-
- 25.6.1 对象字节输出流:ObjectOutputStream
- 25.6.2 对象字节输出流:ObjectOutputStream
- 25.7 打印流
-
- 1. PrintStream
- 2. PrintWriter
- 25 多线程
-
- 25.1 多线程的创建
-
- 1. 继承Thread类
- 2. 实现Runnable接口
- 3. JDK 5.0新增:实现Callable接口
- 4. 3三种方式的区别
- 5. 匿名内部类形式的线程创建
- 25.2 线程同步
-
- 25.3.1 同步代码块
- 25.3.2 同步方法
- 25.3.3 Lock锁
- 25.3 有关方法
- 25.4 多线程并发安全问题
- 25.5 守护线程
- 25.6 互斥锁
- 25.7 死锁
- 25.8 解决死锁
- 26 网络编程
-
- 26.1 Socket
- 26.2 实现客户端
- 26.3 实现服务端
- 26.4 实现多发多收消息
- 26.5 同时接受多个客户端消息
- 26.6 使用线程池优化
- 26.7 即时通信
- 27 集合
-
- 27.1 集合概述
- 27.2 集合体系特点
- 27.3 Collection的常用方法
- 27.4 集合的遍历方式
-
- 27.4.1 方式一:迭代器
- 27.4.2 方式二:foreach/增强for循环
- 27.4.3 方式三:lambda表达式
- 27.5 集合存储自定义类型的对象
- 27.6 List集合
-
- 27.6.1 List集合特点、特有API
- 27.6.2 List集合的遍历方式小结
- 27.6.4 LinkedList集合
- 27.6.5 集合的并发修改异常问题
- 27.7 泛型
-
-
- 1. 泛型概述
- 2. 自定义泛型类
- 3. 自定义泛型方法
- 4. 自定义泛型接口
- 5. 泛型通配符、上下限
-
- 27.8 Set系列
-
- 1. Set系列集系概述
- 2. 哈希表HashSet
- 3. 实现类:LinkedHashSet
- 3. 实现类:TreeSet
- 27.6.8 Collection体系的特点、使用场景总结
- 27.6.10 集合工具类Collections
- 27.6.11 Map集合体系
- 27.6.12 集合的嵌套
java基础知识
1 Java概述
1.1 Java语言发展史
- 语言:人与人交流沟通的表达方式
- 计算机语言:人与计算机之间进行信息交流沟通的一种特殊语言
- Java语言是美国Sun公司(Stanford University Network)在1995年推出的计算机语言
- Java之父:詹姆斯·高斯林(James Gosling)
- 2009年,Sun公司被甲骨文公司收购,所以我们现在访问oracle官网即可:https://www.oracle.com
1.2 Java开发环境
- JVM(Java Virtual Machine),Java虚拟机 加载.class并运行.class
- JRE(Java Runtime Environment),运行环境,包含了JVM和Java的核心类库(Java
API)JRE=JVM+Java系统类库 - JDK(Java Development Kit)称为Java开发工具,包含了JRE和开发工具 JDK = JRE+编译、运行等命令工具
- 编译运行过程:
编译期:.java源文件,经过编译,生成.class字节码文件
运行期:JVM加载.class并运行.class(0和1)
总结:
- 其特点为跨平台、一次编程到处使用,我们只需安装JDK即可,它包含了java的运行环境JRE和虚拟机JVM。
- 说明:
运行java程序的最小环境为JRE
开发java程序的最小环境为JDK
1.3 JDK的下载和安装
1.3.1 下载
通过官方网站下载JDK,官方网站: http://www.oracle.com
找到下面页面进行选择下载:
注意:有Windows、Linux、macOS系统版本不同的JDK,根据自己使用的操作系统,下载对应版本的JDK。
1.3.2 安装
下载后安装JDK,安装过程中点击下一步即可。但默认的安装路径是在C:\Program Files\Java\jdkxxx下,为方便统一管理建议修改安装路径,将与开发相关的软件都安装到一个目录下。例如我们选择安装目录为 C:\Program Files (x86)\Java\jdk1.8.0_301。
注意:建议安装路径不要包含中文或者空格等特殊字符(使用纯英文目录)。
下面变量设置参数如下:
- 变量名:JAVA_HOME
- 变量值:D:\Java\jdk1.8.0_301 // 要根据自己的实际路径配置
- 变量名:Path
- 变量值:%JAVA_HOME%\bin;
注意:如果发现环境变量中某一项已经存在,则说明你安装的JDK版本已经给你配置了,需要配置没有出现的环境变量
安装完成后,需要我们手动配置Java的环境变量,此电脑右键->点击属性->往下滑找到高级系统设置->点击高级->点击环境变量
在系统环境变量下点击**新建(W)…**输入变量名和变量值确定。
在系统环境变量下找到Path选中后点击编辑,在右侧点击新建,输入%JAVA_HOME%\bin确定即可。
验证环境变量是否配置成功,win+r输入cmd输入javac和java -version
配置成功。
注意:路径不能出错,建议到对应的安装路径复制。
2 入门程序HelloWorld
2.1 常用DOS命令
在接触集成开发环境之前,我们需要使用命令行窗口对java程序进行编译和运行,所以需要知道一些常用DOS命令。
1、打开命令行窗口的方式:win + r打开运行窗口,输入cmd,回车。
2、常用命令及其作用
操作 | 说明 |
---|---|
盘符名称: | 盘符切换。E:回车,表示切换到E盘。 |
dir | 查看当前路径下的内容。 |
cd 目录 | 进入单级目录。cd java |
cd … | 回退到上一级目录。 |
cd 目录1\目录2… | 进入多级目录。cd java\JavaSE |
cd \ | 回退到盘符目录。 |
cls | 清屏。 |
exit | 退出命令提示符窗口。 |
2.2 HelloWorld案例
HelloWorld案例是指在计算机屏幕上输出“HelloWorld”。
2.2.1 Java程序开发运行流程
开发Java程序,需要三个步骤:编写程序,编译程序,运行程序。
2.2.2 HelloWorld案例的编写
1、新建文本文档文件,修改名称为HelloWorld.java。
2、用记事本打开HelloWorld.java文件,输写程序内容。
public class HelloWorld {public static void main(String[] args) {System.out.println("HelloWorld");}}
2.2.3 HelloWorld案例的编译和运行
保存文件,打开命令行窗口,将目录切换至java文件所在目录,在这里直接建议打开HelloWorld.java的文件夹,在地址栏输入cmd,编译java文件生成class文件,运行class文件,感受Java的编写程序->编译->运行。
编译:javac 文件名.java
范例:javac HelloWorld.java
执行:java 类名
范例:java HelloWorld
3 IDEA安装使用
3.1 下载
进入 IDEA 官方下载页面,(官网地址为 https://www.jetbrains.com/idea/),点击 DOWNLOAD。
3.2 安装
打开后选择安装路径,首先会显示欢迎界面,next下一步,选择安装路径建议选择其它盘,到配置安装选项在Create Desktop Shortcut中选择64-bit launcher,意思是将64位启动程序添加到桌面,然后一直下一步即可。
安装完成后启动,第一次打开会弹出一个激活框,选中Evaluate for free-免费评估,再点击Evaluate评估,可以免费试用30天,安装完成。
3.3 idea常用快捷键
记得这些快捷键可以提高敲代码的速度。
快捷键 | 功能 |
---|---|
Alt + Enter | 神键,可以根据光标所在问题,提供快速修复大部分问题选择,光标放在的位置不同提示的结果也不同 |
Ctrl + Z | 撤销 |
Ctrl + Shift + Z | 取消撤销 |
Alt+Shift+上下箭头 | 移动当前代码行 |
Ctrl+D | 复制光标所在行的内容,插入光标位置下面 |
Ctrl+Y | 删除光标所在行 |
Ctrl + Alt + L | 格式化代码,可以对当前类或者文件和整个包目录使用 |
Ctrl + Alt + V | 自动补全前面的代码或者加.var |
Ctrl + F | 在当前文件进行文本查找 |
Ctrl + R | 在当前文件进行文本替换 |
Ctrl + O | 选择可重写的方法 |
Ctrl + / | 注释光标所在行代码,会根据当前不同文件类型使用不同的注释符号// |
Ctrl + Shift + / | 代码块注释 /**/ |
Shift+Enter | 光标跳到下一行并回车,在敲代码中不用按end来Enter |
Ctrl+Enter | 向下回车,光标不动 |
Ctrl+Shift+Enter | 回车并格式化这行代码,并自动补分号; |
Alt + Insert | 代码自动生成,如生成构造方法对象的 set / get 方法,toString() 等 |
Ctrl + Alt + O | 优化导入的类,可以对当前文件和整个包目录使用 |
Ctrl + Shift + F | 根据输入内容查找整个项目 或 指定目录内文件 |
Ctrl + Shift + R | 根据输入内容替换对应内容,范围为整个项目 或 指定目录内文件 |
Ctrl + Shift + Enter | 自动结束代码,行末自动添加分号 |
Ctrl + Alt+ T | 快速写出try-catch、if等代码 |
4 Java基础语法
一个 Java 程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作。下面简要介绍下类、对象、方法和实例变量的概念。
1. 对象:对象是类的一个实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。 2. 类:类是一个模板,它描述一类对象的行为和状态。 3. 方法:方法就是行为,一个类可以有很多方法。逻辑运算、数据修改以及所有动作都是在方法中完成的。 4. 实例变量:每个对象都有独特的实例变量,对象的状态由这些实例变量的值决定。
4.1 注释
提高代码阅读性,解释代码的作用
单行注释
//单行注释
多行注释
/*多行注释*/
4.2 数据类型
4.2.1 计算机存储单元
计算机底层都是一些数字电路(理解成开关),用开表示0、关表示1,这些01的形式就是二进制。
数据在计算机底层都是采用二进制存储的,l在计算机中认为一个开关表示的0|1称为1位(b),每8位称为一个字节(B), 所以1B=8b
字节是计算机中数据的最小单位。
我们知道计算机是可以用来存储数据的,但是无论是内存还是硬盘,计算机存储设备的最小信息单元叫“位(bit)”,我们又称之为“比特位”,通常用小写的字母”b”表示。而计算机中最基本的存储单元叫“字节(byte)”,
通常用大写字母”B”表示,字节是由连续的8个位组成。
除了字节外还有一些常用的存储单位,其换算单位如下:
1B(字节) = 8bit
1KB = 1024B
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
4.2.2 变量
变量:在程序运行过程中,其值可以发生改变的量。
从本质上讲,变量是内存中的一小块区域,其值可以在一定范围内变化。
声明格式:
//数据类型 变量名;int a; //声明了一个整型的变量,名为aint b,c,d; //声明了三个整型的变量,名为b,c,d//int a; //编译错误,变量不能同名
初始化/第一次赋值:
int a = 250; //声明整型变量a并赋值为250int b; //声明整型变量bb = 250; //给变量b赋值为250b = 360; //修改变量b的值为360
使用:
int a = 5;int b = a + 10; //取出a的值5,加10后,再赋值给变量bSystem.out.println(b); //输出变量b的值15System.out.println("b"); //输出b,双引号中的原样输出a = a+10; //在a本身基础之上增10System.out.println(a); //输出变量a的值15
使用之前必须声明并初始化:
int a = 5;int b = a + 10; //取出a的值5,加10后,再赋值给变量bSystem.out.println(b); //输出变量b的值15System.out.println("b"); //输出b,双引号中的原样输出a = a+10; //在a本身基础之上增10System.out.println(a); //输出变量a的值15
4.3 Java中的基本数据类型
八种基本数据类型
数据类型 | 关键字 | 内存占用(字节) | 取值范围 |
---|---|---|---|
整数 | byte | 1 | 负的2的7次方 ~ 2的7次方-1(-128~127) |
short | 2 | 负的2的15次方 ~ 2的15次方-1(-32768~32767) | |
int | 4 | 负的2的31次方 ~ 2的31次方-1 | |
long | 8 | 负的2的63次方 ~ 2的63次方-1 | |
浮点数 | float | 4 | 1.401298e-45 ~ 3.402823e+38 |
double | 8 | 4.9000000e-324 ~ 1.797693e+308 | |
字符 | char | 2 | 0-65535 |
布尔 | boolean | 1 | true,false |
注意: float类型:数据后必须加F或者f表示。
- int:整型,4个字节,-21个多亿到21个多亿
- 整数直接量默认为int类型,但不能超出范围,若超范围则发生编译错误
- 两个整数相除,结果还是整数,小数位无条件舍弃(不会四舍五入)
- 运算时若超范围,则发生溢出(溢出不是错误,但是需要避免)
//1)int:整型,4个字节,-21个多亿到21个多亿int a = 25; //25为整数直接量,默认为int型//int b = 10000000000; //编译错误,100亿默认为int类型,但超出范围了//int c = 25.678; //编译错误,整型变量中不能装小数System.out.println(5/2); //2System.out.println(2/5); //0System.out.println(5/2.0); //2.5int d = 2147483647; //int的最大值d = d+1;System.out.println(d); //-2147483648(int的最小值),发生溢出了
- long:长整型,8个字节,很大很大很大
- 长整型直接量需在数字后加L或l
- 运算时若有可能溢出,建议在第1个数字后加L
//2)long:长整型,8个字节,很大很大很大long a = 25L; //25L为长整型直接量//long b = 10000000000; //编译错误,100亿默认为int类型,但超出int范围了long c = 10000000000L;//运算时若有可能溢出,建议在第1个数字后加Llong d = 1000000000*2*10L;System.out.println(d); //200亿long e = 1000000000*3*10L;System.out.println(e); //不是300亿long f = 1000000000L*3*10;System.out.println(f); //300亿
- double:浮点型,8个字节,很大很大很大
- 浮点数直接量默认为double型,若表示float需在数字后加F或f
- double和float型数据参与运算时,有可能会发生舍入误差,精确场合不能使用
//3)double:浮点型,8个字节,很大很大很大double a = 3.14; //3.14为浮点型直接量,默认double型float b = 3.14F; //3.14F为float型直接量double c = 12340000000000000000000000.0;System.out.println(c); //1.234E25,科学计数法表示,相当于1.234*(10的25次幂)double d=3.0,e=2.9;System.out.println(d-e); //0.10000000000000009,有可能发生舍入误差
- boolean:布尔型,1个字节
只能存储true或false
//4)boolean:布尔型,1个字节boolean b1 = true; //true为布尔型直接量boolean b2 = false; //false为布尔型直接量//boolean b3 = 25; //编译错误,布尔型只能取值为true或false
- char:字符型,2个字节
- 采用Unicode字符集编码格式,一个字符对应一个码,
表现的形式是字符char,但本质上是码int(0到65535之间)
ASCII码:‘a’—97 ‘A’—65 ‘0’—48- 字符型直接量必须放在单引号中,有且仅有一个字符
- 特殊符号需通过\来转义
//5)char:字符型,2个字节char c1 = '女'; //字符女char c2 = 'f'; //字符fchar c3 = '6'; //字符6char c4 = '*'; //字符*//char c5 = 女; //编译错误,字符型直接量必须放在单引号中//char c6 = ''; //编译错误,必须有字符//char c7 = '女性'; //编译错误,只能有1个字符char c8 = 65; //0到65535之间----一般不这么用System.out.println(c8); //println()输出时会依据c8的数据类型来显示//若c8为char型,则显示为字符//若c8为int型,则显示为数字char c9 = '\\';System.out.println(c9); //\
4.4 关键字、标志符
4.4.1 关键字
- Java自己保留的一些单词,作为特殊功能的,例如:public、class、byte、short、int、long、double…
- 我们不能用来作为类名或者是变量名称,否则报错。
abstract | assert | boolean | break | byte |
---|---|---|---|---|
case | catch | char | class | const |
continue | default | do | double | else |
enum | extends | final | finally | float |
for | goto | if | implements | import |
instanceof | int | interface | long | native |
new | package | private | protected | public |
return | strictfp | short | static | super |
switch | synchronized | this | throw | throws |
transient | try | void | volatile | while |
4.4.1 标志符
- 标志符就是由一些字符、符号组合起来的名称,用于给类,方法,变量等起名字的规矩。
- 基本要求:由数字、字母、下划线(_)和美元符($)等组成
- 强制要求:不能以数字开头、不能是关键字、区分大小写
4.5 基本命名规定
4.5.1 变量名称
- 只能包含字母、数字、_和$符,不能以数字开头
- 严格区分大小写
- 不能使用关键字
- 满足标识符规则,建议全英文、有意义、首字母小写,满足“小驼峰命名法”。
4.5.2 类名称
满足标识符规则,建议全英文、有意义、首字母大写,满足“大驼峰命名法”,例如:HelloWorld.java。
class TestOne{public static void main(String[] args) throws IOException {int a1,a_5$,_3c,$t;//int a*b; //编译错误,不能包含*等特殊符号//int 1a; //编译错误,不能以数字开头int aa = 5;//System.out.println(aA); //编译错误,严格区分大小写//int class; //编译错误,不能使用关键字int 年龄; //允许,但不建议int nianLing; //必须杜绝,既不直观也不专业int age; //建议---英文的见名知意int score,myScore,myJavaScore; //建议"小驼峰命名法"}}
4.6 类型间的转换
基本类型由小到大依次为:
4.6.1 自动/隐式类型转换:小类型到大类型
把一个表示数据范围小的数值或者变量赋值给另一个表示数据范围大的变量。这种转换方式是自动的,直接书写即可。
int a = 5;long b = a; //自动/隐式类型转换int c = (int)b; //强制类型转换long d = 5; //自动类型转换double e = 5; //自动类型转换
注意事项:
整数直接量可以直接赋值给byte,short,char,但不能超出范围。
byte,short,char型数据参与运算时,系统一律自动将其转换为int再运算 。
4.6.2 强制类型转换:大类型到小类型
类型范围大的数据或者变量,不能直接赋值给类型范围小的变量,会报错,把一个表示数据范围大的数值或者变量赋值给另一个表示数据范围小的变量必须进行强制类型转换。
强制类型转换格式:目标数据类型 变量名 = (目标数据类型)值或者变量;
long f = 10000000000L;int g = (int)f; //强制类型转换System.out.println(g); //1410065408,强转有可能发生溢出double h = 25.987;int i = (int)h; //强转类型转换System.out.println(i); //25,强转有可能丢失精度
注意:boolean类型不能与其他基本数据类型相互转换。
4.7 运算符
4.7.1 算术运算符
符号 | 作用 | 说明 |
---|---|---|
+ | 加 | 加法 - 相加运算符两侧的值 |
- | 减 | 减法 - 左操作数减去右操作数 |
* | 乘 | 乘法 - 相乘操作符两侧的值 |
/ | 除 | 除法 - 左操作数除以右操作数 |
% | 取余 | 获取的是两个数据做除法的余数 |
++ | 自增: 操作数的值增加1 | B++ 或 ++B 等于 21 |
– | 自减: 操作数的值减少1 | B-- 或 --B 等于 19 |
//%的演示System.out.println(8%2); //0,商4余0----整除System.out.println(5%2); //1,商2余1System.out.println(2%8); //2,商0余2//++单独使用:int a=5,b=5;a++; //相当于a=a+1++b; //相当于b=b+1System.out.println(a); //6System.out.println(b); //6//++被使用:int a=5,b=5;int c = a++; //1)保存a++的值5 2)a自增1变为6 3)将第1步保存的值5赋值给c--底层运算过程//---粗暴记法:a++的值为5,c就是5int d = ++b; //1)保存++b的值6 2)b自增1变为6 3)将第1步保存的值6赋值给d--底层运算过程//---粗暴记法:++b的值为6,d就是6System.out.println(a); //6System.out.println(b); //6System.out.println(c); //5System.out.println(d); //6//--单独使用:int a=5,b=5;a--; //相当于a=a-1--b; //相当于b=b-1System.out.println(a); //4System.out.println(b); //4//--被使用:int a=5,b=5;int c = a--; //a--的值为5,所以c的值为5int d = --b; //--b的值为4,所以d的值为4System.out.println(a); //4System.out.println(b); //4System.out.println(c); //5System.out.println(d); //4
4.7.2 关系运算符
符号 | 说明 |
---|---|
== | a==b,判断a和b的值是否相等,成立为true,不成立为false |
!= | a!=b,判断a和b的值是否不相等,成立为true,不成立为false |
> | a>b,判断a是否大于b,成立为true,不成立为false |
>= | a>=b,判断a是否大于等于b,成立为true,不成立为false |
< | a<b,判断a是否小于b,成立为true,不成立为false |
<= | a<=b,判断a是否小于等于b,成立为true,不成立为false |
int a=5,b=10,c=5;boolean b1 = a>b;System.out.println(b1); //falseSystem.out.println(c<b); //trueSystem.out.println(a>=c); //trueSystem.out.println(a<=b); //trueSystem.out.println(a==c); //trueSystem.out.println(a!=c); //falseSystem.out.println(a+c>b); //falseSystem.out.println(a%2==0); //falseSystem.out.println(c++>5); //false-------c自增1变为6System.out.println(c++>5); //true--------c自增1变为7
注意事项:
- 关系运算符的结果都是boolean类型,要么是true,要么是false。
4.7.3 逻辑运算符
符号 | 作用 | 说明 |
---|---|---|
& | 逻辑与 | a&b,a和b都是true,结果为true,否则为false |
| | 逻辑或 | a|b,a和b都是false,结果为false,否则为true |
^ | 逻辑异或 | a^b,a和b结果不同为true,相同为false |
! | 逻辑非 | !a,结果和a的结果正好相反 |
//定义变量int i = 10;int j = 20;int k = 30;//& “与”,并且的关系,只要表达式中有一个值为false,结果即为falseSystem.out.println((i > j) & (i > k)); //false & false,输出falseSystem.out.println((i < j) & (i > k)); //true & false,输出falseSystem.out.println((i > j) & (i < k)); //false & true,输出falseSystem.out.println((i < j) & (i < k)); //true & true,输出trueSystem.out.println("--------");//| “或”,或者的关系,只要表达式中有一个值为true,结果即为trueSystem.out.println((i > j) | (i > k)); //false | false,输出falseSystem.out.println((i < j) | (i > k)); //true | false,输出trueSystem.out.println((i > j) | (i < k)); //false | true,输出trueSystem.out.println((i < j) | (i < k)); //true | true,输出trueSystem.out.println("--------");//^ “异或”,相同为false,不同为trueSystem.out.println((i > j) ^ (i > k)); //false ^ false,输出falseSystem.out.println((i < j) ^ (i > k)); //true ^ false,输出trueSystem.out.println((i > j) ^ (i < k)); //false ^ true,输出trueSystem.out.println((i < j) ^ (i < k)); //true ^ true,输出falseSystem.out.println("--------");//! “非”,取反System.out.println((i > j)); //falseSystem.out.println(!(i > j)); //!false,,输出true
4.7.4 赋值运算符
符号 | 作用 | 说明 |
---|---|---|
= | 赋值 | a=10,将10赋值给变量a |
+= | 加后赋值 | a+=b,将a+b的值给a |
-= | 减后赋值 | a-=b,将a-b的值给a |
*= | 乘后赋值 | a*=b,将a×b的值给a |
/= | 除后赋值 | a/=b,将a÷b的商给a |
%= | 取余后赋值 | a%=b,将a÷b的余数给a |
注意:扩展的赋值运算符隐含了强制类型转换。
short s = 10;s = s + 10; // 此行代码报出,因为运算中s提升为int类型,运算结果int赋值给short可能损失精度s += 10; // 此行代码没有问题,隐含了强制类型转换,相当于 s = (short) (s + 10);
4.7.5 短路逻辑运算符
符号 | 作用 | 说明 |
---|---|---|
&& | 短路与 | 作用和&相同,但是有短路效果 |
|| | 短路或 | 作用和|相同,但是有短路效果 |
在逻辑与运算中,只要有一个表达式的值为false,那么结果就可以判定为false了,没有必要将所有表达式的值都计算出来,短路与操作就有这样的效果,可以提高效率。同理在逻辑或运算中,一旦发现值为true,右边的表达式将不再参与运算。 |
-
逻辑与&,无论左边真假,右边都要执行。
-
短路与&&,如果左边为真,右边执行;如果左边为假,右边不执行。
-
逻辑或|,无论左边真假,右边都要执行。
-
短路或||,如果左边为假,右边执行;如果左边为真,右边不执行。
int x = 3;int y = 4;System.out.println((x++ > 4) & (y++ > 5)); // 两个表达都会运算System.out.println(x); // 4System.out.println(y); // 5System.out.println((x++ > 4) && (y++ > 5)); // 左边已经可以确定结果为false,右边不参与运算System.out.println(x); // 4System.out.println(y); // 4
4.7.6 字符的“+”操作
char类型参与算术运算,使用的是计算机底层对应的十进制数值。需要我们记住三个字符对应的数值:
‘a’ – 97 a-z是连续的,所以’b’对应的数值是98,'c’是99,依次递加
‘A’ – 65 A-Z是连续的,所以’B’对应的数值是66,'C’是67,依次递加
‘0’ – 48 0-9是连续的,所以’1’对应的数值是49,'2’是50,依次递加
// 可以通过使用字符与整数做算术运算,得出字符对应的数值是多少char ch1 = 'a';System.out.println(ch1 + 1); // 输出98,97 + 1 = 98char ch2 = 'A';System.out.println(ch2 + 1); // 输出66,65 + 1 = 66char ch3 = '0';System.out.println(ch3 + 1); // 输出49,48 + 1 = 49
4.7.7 字符串的“+”操作
当“+”操作中出现字符串时,这个”+”是字符串连接符,而不是算术运算。
System.out.println("a"+ 666); // 输出:a666
在”+”操作中,如果出现了字符串,就是连接运算符,否则就是算术运算。当连续进行“+”操作时,从左到右逐个执行。
System.out.println(1 + 99 + "年s"); // 输出:100年sSystem.out.println(1 + 2 + "ssss" + 3 + 4); // 输出:3ssss34// 可以使用小括号改变运算的优先级 System.out.println(1 + 2 + "ssss" + (3 + 4)); // 输出:3ssss7
4.7.8 三元运算符
三元运算符语法格式:
关系表达式 ? 表达式1 : 表达式2;
解释:问号前面的位置是判断的条件,判断结果为boolean型,为true时调用表达式1,为false时调用表达式2。其逻辑为:如果条件表达式成立或者满足则执行表达式1,否则执行第二个。
int a = 10;int b = 20;int c = a > b ? a : b; // 判断 a>b 是否为真,如果为真取a的值,如果为假,取b的值
4.8 流程控制语句
- 顺序结构
- 分支结构(if, switch)
- 循环结构(for, while, do…while)
4.8.1 顺序结构
顺序结构是程序中最简单最基本的流程控制,没有特定的语法结构,按照代码的先后顺序,依次执行,程序中大多数的代码都是这样执行的。
4.8.2 分支结构(if, switch)
1. if语句:
格式:
if (条件表达式1) {
语句体1;
} else if (条件表达式2) {
语句体2;
} else if (条件表达式3) {
语句体3;
}
. . .
else {
语句体n+1;
}
public class IfDemo {public static void main(String[] args) {System.out.println("开始");//定义两个变量int a = 10;int b = 20;//需求:判断a和b的值是否相等,如果相等,就在控制台输出:a等于bif(a == b) {System.out.println("a等于b");}//需求:判断a和c的值是否相等,如果相等,就在控制台输出:a等于cint c = 10;if(a == c) {System.out.println("a等于c");}System.out.println("结束");}}
2. switch语句:
switch…case结构:多条路
优点:效率高、结构清晰
缺点:只能对整数判断相等
break:跳出switch
注意:如果switch中得case,没有对应break的话,则会出现case穿透的现象。
格式:
switch (表达式) {
case 1:
语句体1;
break;
case 2:
语句体2;
break;
…
default:
语句体n+1;
break;
}
public class SwitchDemo { public static void main(String[] args) { //键盘录入月份数据,使用变量接收 Scanner sc = new Scanner(System.in); System.out.println("请输入一个月份:"); int month = sc.nextInt(); //case穿透 switch(month) { case 1: case 2: case 12: System.out.println("冬季"); break; case 3: case 4: case 5: System.out.println("春季"); break; case 6: case 7: case 8: System.out.println("夏季"); break; case 9: case 10: case 11: System.out.println("秋季"); break; default: System.out.println("你输入的月份有误"); } }}
4.8.3 循环结构(for, while, do…while)
1. for循环语句
for循环格式:
for (初始化语句;条件判断语句;条件控制语句) {
循环体语句;
}
示例1:
//输出3次Hello Worldfor (int i = 0; i < 3; i++) {System.out.println("Hello World");}
示例2:
//需求:求1-5之间的数据和,并把求和结果在控制台输出 public class ForTest02 { public static void main(String[] args) {//求和的最终结果必须保存起来,需要定义一个变量,用于保存求和的结果,初始值为0int sum = 0;//从1开始到5结束的数据,使用循环结构完成for(int i=1; i<=5; i++) {//将反复进行的事情写入循环结构内部 // 此处反复进行的事情是将数据 i 加到用于保存最终求和的变量 sum 中sum += i;/*sum += i;sum = sum + i;第一次:sum = sum + i = 0 + 1 = 1;第二次:sum = sum + i = 1 + 2 = 3;第三次:sum = sum + i = 3 + 3 = 6;第四次:sum = sum + i = 6 + 4 = 10;第五次:sum = sum + i = 10 + 5 = 15;*/}//当循环执行完毕时,将最终数据打印出来System.out.println("1-5之间的数据和是:" + sum); }}
示例3:
//需求:求1-100之间的偶数和,并把求和结果在控制台输出public class ForTest03 { public static void main(String[] args) {//求和的最终结果必须保存起来,需要定义一个变量,用于保存求和的结果,初始值为0int sum = 0;//对1-100的数据求和与1-5的数据求和几乎完全一样,仅仅是结束条件不同for(int i=1; i<=100; i++) {//对1-100的偶数求和,需要对求和操作添加限制条件,判断是否是偶数if(i%2 == 0) {sum += i;}}//当循环执行完毕时,将最终数据打印出来System.out.println("1-100之间的偶数和是:" + sum); }}
示例4:
/** 需求:在控制台输出所有的“水仙花数” * 解释:什么是水仙花数?* 水仙花数,指的是一个三位数,个位、十位、百位的数字立方和等于原数* 例如`153 3*3*3 + 5*5*5 + 1*1*1 = 153`* 思路:* 1. 获取所有的三位数,准备进行筛选,最小的三位数为100,最大的三位数为999,使用for循环获取* 2. 获取每一个三位数的个位,十位,百位,做if语句判断是否是水仙花数*/public class ForTest04 { public static void main(String[] args) {//输出所有的水仙花数必然要使用到循环,遍历所有的三位数,三位数从100开始,到999结束for(int i=100; i<1000; i++) {//在计算之前获取三位数中每个位上的值int ge = i%10;int shi = i/10%10;int bai = i/10/10%10;//判定条件是将三位数中的每个数值取出来,计算立方和后与原始数字比较是否相等if(ge*ge*ge + shi*shi*shi + bai*bai*bai == i) {//输出满足条件的数字就是水仙花数System.out.println(i);}} }}
2. while循环语句
while循环完整格式:
初始化语句;
while (条件判断语句) {
循环体语句;
条件控制语句;
}
示例1:
public class WhileDemo { public static void main(String[] args) { //需求:在控制台输出5次"HelloWorld"//for循环实现for(int i=1; i<=5; i++) {System.out.println("HelloWorld");}System.out.println("--------");//while循环实现int j = 1;while(j<=5) {System.out.println("HelloWorld");j++;} }}
示例2:
//猜字小游戏public class Guessing { public static void main(String[] args) { Scanner scan = new Scanner(System.in); int num = (int)(Math.random()*1000+1); //1到1000之内的随机数 System.out.println(num); //作弊 //300(大),200(小),250(对) System.out.println("猜吧!"); int guess = scan.nextInt(); //1. while(guess!=num){ //2. if(guess>num){ System.out.println("太大了"); }else{ System.out.println("太小了"); } System.out.println("猜吧!"); guess = scan.nextInt(); //3. } System.out.println("恭喜你猜对了!"); }}
3. do…while循环语句
格式:
初始化语句;
do {
循环体语句;
条件控制语句;
}while(条件判断语句);
public class DoWhileDemo { public static void main(String[] args) { //需求:在控制台输出5次"HelloWorld"//for循环实现for(int i=1; i<=5; i++) {System.out.println("HelloWorld");}System.out.println("--------");//do...while循环实现int j = 1;do {System.out.println("HelloWorld");j++;}while(j<=5); }}
4. 三种循环的区别
- 三种循环的区别
- for循环和while循环先判断条件是否成立,然后决定是否执行循环体(先判断后执行)
- do…while循环先执行一次循环体,然后判断条件是否成立,是否继续执行循环体(先执行后判断)
- for循环和while的区别
- 条件控制语句所控制的自增变量,因为归属for循环的语法结构中,在for循环结束后,就不能再次被访问到了
- 条件控制语句所控制的自增变量,对于while循环来说不归属其语法结构中,在while循环结束后,该变量还可以继续使用
- 死循环(无限循环)的三种格式
- for( ; ; ){}
- while(true){}
- do {} while(true);
5. 跳转控制语句
- 跳转控制语句(break)
- 跳出循环,结束循环
- 跳转控制语句(continue)
- 跳过本次循环,继续下次循环
for(int i=1;i<=9;i++){ if(i==4){ //在某种特定条件下,提前结束循环 break; } System.out.println(i+"*9="+i*9);}
for(int i=1;i<=9;i++){ if(i%3==0){ continue; //跳过循环体中剩余语句而进入下一次循环 } System.out.println(i+"*9="+i*9); }
注意: continue只能在循环中进行使用!
6. 循环嵌套
- 循环中套循环,常常多行多列时使用,一般外层控制行,内层控制列
- 执行过程:外层循环走一次,内层循环走所有次
- 建议:嵌套层数越少越好,能用一层就不用两层,能用两层就不用三层
- break只能跳出当前一层循环
public static void main(String[] args) { //外循环控制小时的范围,内循环控制分钟的范围 for (int hour = 0; hour < 24; hour++) { for (int minute = 0; minute < 60; minute++) { System.out.println(hour + "时" + minute + "分"); } System.out.println("--------"); } }
结论:
外循环执行一次,内循环执行一圈
5. 数组
- 数组就是存储数据长度固定的容器,存储多个数据的数据类型要一致
- 是一种数据类型(引用类型)
- 相同数据类型元素的集合
5.1 数组定义格式
5.1.1 第一种方式
数据类型[] 数组名
int[] arr; double[] arr; char[] arr;
5.1.2 第二种方式
数据类型 数组名[]
int arr[];double arr[];char arr[];
5.2 数组动态初始化
数组动态初始化就是只给定数组的长度,由系统给出默认初始化值。
动态初始化格式:
数据类型[] 数组名 = new 数据类型[数组长度];
int[] arr = new int[3];
5.3静态初始化格式
完整格式:
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,...};
简化版格式:
数据类型[] 数组名 = {元素1,元素2,...};
5.4 访问数组元素格式
数组名[索引];
public class ArrayDemo { public static void main(String[] args) { int[] arr = new int[3]; //输出数组名 System.out.println(arr); //[I@880ec60 //输出数组中的元素 System.out.println(arr[0]); System.out.println(arr[1]); System.out.println(arr[2]); }}
5.5 索引越界异常
出现原因
public class ArrayDemo { public static void main(String[] args) { int[] arr = new int[3]; System.out.println(arr[3]); }}
数组长度为3,索引范围是0~2,但是我们却访问了一个3的索引。
程序运行后,将会抛出ArrayIndexOutOfBoundsException 数组越界异常。在开发中,数组的越界异常是不能出现的,一旦出现了,就必须要修改我们编写的代码。
解决方案:将错误的索引修改为正确的索引范围即可!
5.6 空指针异常
public class ArrayDemo { public static void main(String[] args) { int[] arr = new int[3]; //把null赋值给数组 arr = null; System.out.println(arr[0]); }}
arr = null 这行代码,意味着变量arr将不会再保存数组的内存地址,也就不允许再操作数组了,因此运行的时候会抛出 NullPointerException 空指针异常。在开发中,数组的越界异常是不能出现的,一旦出现了,就必须要修改我们编写的代码。
解决方案:给数组一个真正的堆内存空间引用即可!
5.7 数组遍历
数组遍历:就是将数组中的每个元素分别获取出来,就是遍历。遍历也是数组操作中的基石。
public class ArrayTest01 { public static void main(String[] args) { //定义数组 int[] arr = {11, 22, 33, 44, 55}; //使用通用的遍历格式 for(int x = 0; x < arr.length; x++) { System.out.println(arr[x]); } }}
5.8 数组最值
最大值获取:从数组的所有元素中找出最大值,最小值同理。
public class ArrayTest02 { public static void main(String[] args) { //定义数组 int[] arr = {12, 45, 98, 73, 60}; //定义一个变量,用于保存最大值 //取数组中第一个数据作为变量的初始值 int max = arr[0]; //与数组中剩余的数据逐个比对,每次比对将最大值保存到变量中 for(int x=1; x<arr.length; x++) { if(arr[x] > max) { max = arr[x]; } } //循环结束后打印变量的值 System.out.println("max:" + max); }}
5.9 循环控制语句
循环控制语句主要有两个:break,continue
break
在循环过程中,碰到break整个循环就直接结束了
注意:break只能出现在循环中或者switch中
continue
如果在循环过程中碰到了continue,则跳过本次循环 , 继续下次循环
6 方法
方法:函数、过程
- 封装一段特定的业务逻辑功能
- 尽可能的独立,一个方法只干一件事
- 方法可以被反复多次调用
- 减少代码重复,有利于代码复用,有利于代码维护
6.1 定义方法
格式:
修饰词 返回值类型 方法名(参数列表) {
方法体--------------具体的业务逻辑功能实现
}
//无参无返回值public static void say(){ System.out.println("大家好,我叫WKJ,今年38岁了");}//有参无返回值public static void sayHi(String name){ //---------形参 System.out.println("大家好,我叫"+name+",今年38岁了");}//有参无返回值public static void sayHello(String name,int age){ if(age>=35){ //在某种特定条件下,提前结束方法 return; //结束方法 } System.out.println("大家好,我叫"+name+",今年"+age+"岁了");}//无参有返回值public static double getNum(){ //在有返回值的方法中: //--必须得通过return来返回一个值,并且这个值的类型必须与返回值类型匹配 //return "abc"; //编译错误,返回的值必须与返回值类型匹配 return 8.88; //1)结束方法的执行 2)返回一个结果给调用方}//有参有返回值public static int plus(int num1,int num2){ int num = num1+num2; return num; //返回的是num里面的那个数 //return num1+num2; //返回的是num1与num2的和}//无参有返回值 public static int[] testArray(){ int[] arr = new int[10]; for(int i=0;i<arr.length;i++){ arr[i] = (int)(Math.random()*100); } return arr; }
6.2 调用方法
- 无返回值:方法名(有参传参);
- 有返回值:数据类型 变量 = 方法名(有参传参);
//say();//sayHi(); //编译错误,有参则必须传参//sayHi(250); //编译错误,参数类型必须匹配sayHi("zhangsan"); //String name="zhangsan" //-------实参sayHi("lisi"); //String name="lisi" //---------------实参sayHi("wangwu"); //String name="wangwu" //-----------实参sayHello("zhangsan",25); //实参sayHello("WKJ",38); //实参double a = getNum(); //getNum()的值就是8.88System.out.println(a); //8.88----模拟对返回值的后续操作int b = plus(5,6);System.out.println(b); //11----模拟对返回值的后续操作int m=5,n=6;int c = plus(m,n); //传递的是m和n里面的那个数System.out.println(c); //11----模拟对返回值的后续操作int[] d = testArray();System.out.println(d.length); //10---模拟对返回值的后续操作for(int i=0;i<d.length;i++){ //输出每个元素的值---模拟对返回值的后续操作 System.out.println(d[i]);}
6.3 return:
-
return 值;
1)结束方法的执行
2)返回结果给调用方
----------用在有返回值方法中 -
return; 结束方法的执行
-----------------用在无返回值的方法中
6.4 形参和实参
- 形参:方法定义中的参数
等同于变量定义格式,例如:int number
- 实参:方法调用中的参数
等同于使用变量或常量,例如: 10 number
6.5 方法重载
方法重载指同一个类中定义的多个方法之间的关系,满足下列条件的多个方法相互构成重载
- 多个方法在同一个类中
- 多个方法具有相同的方法名
- 多个方法的参数不相同,类型不同或者数量不同
public class MethodTest { public static void main(String[] args) { //调用方法 System.out.println(compare(10, 20)); System.out.println(compare((byte) 10, (byte) 20)); System.out.println(compare((short) 10, (short) 20)); System.out.println(compare(10L, 20L)); } //int public static boolean compare(int a, int b) { System.out.println("int"); return a == b; } //byte public static boolean compare(byte a, byte b) { System.out.println("byte"); return a == b; } //short public static boolean compare(short a, short b) { System.out.println("short"); return a == b; } //long public static boolean compare(long a, long b) { System.out.println("long"); return a == b; }}
7 类和对象
类:对现实事物的一种描述
对象:对象是某类事物的一个个体
类中可以包含:
对象的属性/特征-----------------------在类中通过成员变量来体现
对象的行为/动作-----------------------在类中通过成员方法来体现
一个类可以创建多个对象
类和对象的关系
- 类:类是对现实生活中一类具有共同属性和行为的事物的抽象
- 对象:是能够看得到摸的着的真实存在的实体
- 简单理解:类是对事物的一种描述,对象则为具体存在的事物
public class 类名 {// 成员变量变量1的数据类型 变量1;变量2的数据类型 变量2;…// 成员方法方法1;方法2;}
7.1 对象的使用
- 创建对象的格式:
- 类名 对象名 = new 类名();
- 调用成员的格式:
- 对象名.成员变量
- 对象名.成员方法();
/* 创建对象 格式:类名 对象名 = new 类名(); 范例:Phone p = new Phone(); 使用对象 1:使用成员变量 格式:对象名.变量名 范例:p.brand 2:使用成员方法 格式:对象名.方法名() 范例:p.call() */public class PhoneDemo { public static void main(String[] args) { //创建对象 Phone p = new Phone(); //使用成员变量 System.out.println(p.brand); System.out.println(p.price); p.brand = "小米"; p.price = 2999; System.out.println(p.brand); System.out.println(p.price); //使用成员方法 p.call(); p.sendMessage(); }}
学生对象练习:
//成员变量 String name; int age; //成员方法 public void study() { System.out.println("好好学习,天天向上"); } public void doHomework() { System.out.println("键盘敲烂,月薪过万"); }}/* 学生测试类 */public class StudentDemo { public static void main(String[] args) { //创建对象 Student s = new Student(); //使用对象 System.out.println(s.name + "," + s.age); s.name = "林青霞"; s.age = 30; System.out.println(s.name + "," + s.age); s.study(); s.doHomework(); }}
7.2 成员变量和局部变量的区别
- 类中位置不同:成员变量(类中方法外)局部变量(方法内部或方法声明上)
- 内存中位置不同:成员变量(堆内存)局部变量(栈内存)
- 生命周期不同:成员变量(随着对象的存在而存在,随着对象的消失而消失)局部变量(随着方法的调用而存在,醉着方法的调用完毕而消失)
- 初始化值不同:成员变量(有默认初始化值)局部变量(没有默认初始化值,必须先定义,赋值才能使用)
7.3 封装思想
-
封装概述
是面向对象三大特征之一(封装,继承,多态)
是面向对象编程语言对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界是无法直接操作的
-
封装原则
将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问
成员变量private,提供对应的getXxx()/setXxx()方法
-
封装好处
通过方法来控制成员变量的操作,提高了代码的安全性
把代码用方法进行封装,提高了代码的复用性
7.4 构造方法
构造方法是一种特殊的方法
-
作用:创建对象 Student stu = new Student();
-
格式:
public class 类名{
修饰符 类名( 参数 ) {
}
}
-
功能:主要是完成对象数据的初始化
class Student { private String name; private int age; //构造方法 public Student() { System.out.println("无参构造方法"); } public void show() { System.out.println(name + "," + age); }}/* 测试类 */public class StudentDemo { public static void main(String[] args) { //创建对象 Student s = new Student(); s.show(); }}
7.5 构造方法的注意事项
- 构造方法的创建
如果没有定义构造方法,系统将给出一个默认的无参数构造方法
如果定义了构造方法,系统将不再提供默认的构造方法
- 构造方法的重载
如果自定义了带参构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法
- 推荐的使用方式
无论是否使用,都手工书写无参数构造方法
- 重要功能!
可以使用带参构造,为成员变量进行初始化
/* 学生类 */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(); }}
7.6 private关键字
private是一个修饰符,可以用来修饰成员(成员变量,成员方法)
被private修饰的成员,只能在本类进行访问,针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作
- 提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰
- 提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰
/* 学生类 */class Student { //成员变量 String name; private int age; //提供get/set方法 public void setAge(int a) { if(a<0 || a>120) { System.out.println("你给的年龄有误"); } else { age = a; } } public int getAge() { return age; } //成员方法 public void show() { System.out.println(name + "," + age); }}/* 学生测试类 */public class StudentDemo { public static void main(String[] args) { //创建对象 Student s = new Student(); //给成员变量赋值 s.name = "林青霞"; s.setAge(30); //调用show方法 s.show(); }}
7.7 this关键字
this代表当前调用方法的引用,哪个对象调用的方法,this就代表哪一个对象。
this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)
- 方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
- 方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
public class Student { private String name; private int age; public void setName(String name) { this.name = name; } public String getName() { return name; } public void setAge(int age) { this.age = age; } public int getAge() { return age; } public void show() { System.out.println(name + "," + age); }}
8 继承
8.1 继承概念
继承可以理解为就是让两个类(事物)产生从属关系,有了从属关系子类就肯定会具有父类的特征(父类中的非私有成员),这样我们用类去描述一些事物的时候就可以更方便
超类/父类:共有的属性和行为
派生类/子类:特有的属性和行为
实现继承的格式 继承通过extends实现
格式:
class 子类 extends 父类 { }
public class Fu { public void show() { System.out.println("show方法被调用"); } } public class Zi extends Fu { public void method() { System.out.println("method方法被调用"); } } public class Demo { public static void main(String[] args) {//创建对象,调用方法 Fu f = new Fu(); f.show(); Zi z = new Zi(); z.method(); z.show(); } }
8.2 继承特点
继承鼓励类的重用继承可以多层继承Java中类只支持单继承,不支持多继承(接口支持多继承)父类中private,default修饰的不能被继承构造方法不能被继承,只能调用具有传递性
8.3 继承的作用
- 继承的出现减少了代码冗余,提高了代码的复用性。
- 继承的出现,更有利于功能的扩展。
- 继承的出现让类与类之间产生了关系,提供了多态的前提。
8.4 继承带来的好处与弊端
继承的好处继承可以让类与类之间产生关系,子父类关系,产生子父类后,子类则可以使用父类中非私有的成员。继承好处 提高了代码的复用性(多个类相同的成员可以放到同一个类中) 提高了代码的维护性(如果方法的代码需要修改,修改一处即可)继承弊端继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削 弱了子类的独立性,违背了`高内聚,低耦合`原则继承的应用场景: 使用继承,需要考虑类与类之间是否存在is..a的关系,不能盲目使用继承 is..a的关系:谁是谁的一种,例如:老师和学生是人的一种,那人就是父类,学生和老师就是子类
8.5 super关键字
super:指代当前对象的超类对象
super的用法:
super.成员变量名---------------------访问超类的成员变量
super.方法名()-------------------------调用超类的方法
super()-----------------------------------调用超类的构造方法
//super的演示 public class SuperDemo { public static void main(String[] args) { Boo o = new Boo(); } } class Coo{ Coo(int a){ } } class Doo extends Coo{ Doo(){ super(5); //调用超类的有参构造 } /* //如下代码为默认的: Doo(){ super(); } */ } class Aoo{ Aoo(){ System.out.println("超类构造"); } } class Boo extends Aoo{ Boo(){ //super(); //默认的,调用超类的无参构造 System.out.println("派生类构造"); } }
8.6 this&super关键字
this:代表本类对象的引用 super:代表父类存储空间的标识(可以理解为父类对象引用) this和super的使用分别成员变量: this.成员变量 - 访问本类成员变量super.成员变量 - 访问父类成员变量成员方法: this.成员方法 - 访问本类成员方法super.成员方法 - 访问父类成员方法构造方法: this(…) - 访问本类构造方法 super(…) - 访问父类构造方法
8.7 向上造型
- 超类型的引用指向派生类的对象
- 规定:能点出来什么,看引用的类型
public class UploadDemo { public static void main(String[] args) { Aoo o1 = new Aoo(); o1.a = 1; o1.show(); //o1.b = 2; //编译错误 //o1.test(); //编译错误,超类不能访问派生类的 Boo o2 = new Boo(); o2.b = 1; o2.test(); o2.a = 2; //正确 o2.show(); //正确,派生类可以访问超类的 Aoo o3 = new Boo(); //向上造型 o3.a = 1; o3.show(); //o3.b = 2; //编译错误 //o3.test(); //编译错误,能点出来什么,看引用的类型 }}class Aoo{ int a; void show(){ }}class Boo extends Aoo{ int b; void test(){ }}
8.8 方法的重写
1. 发生在父子类中,方法名相同,参数列表相同2. 重写方法被调用时,看对象的类型-------这是规定,记住就行了3. 重写遵循"两同两小一大"原则:-----------了解,一般都是一模一样的1. 两同:方法名相同参数列表相同2. 两小:1. 派生类方法的返回值类型小于或等于超类方法的1) void和基本类型时,必须相等2) 引用类型时,小于或等于2. 派生类方法抛出的异常小于或等于超类方法的3. 一大:派生类方法的访问权限大于或等于超类方法的
重写与重载的区别:-----------常见面试题
- 重写(override):发生在父子类中,方法名相同,参数列表相同
- 重载(overload):发生在同一类中,方法名相同,参数列表不同
9 多态
同类型的对象,执行同一个行为,会表现出不同的行为特征。
9.1 常见形式
父类类型 对象名称 = new 子类构造器;接口 对象名称 = new 实现类构造器;
9.2 多态中成员访问特点
方法调用:编译看左边,运行看右边。变量调用:编译看左边,运行也看左边。(**多态侧重行为多态**)
9.3 多态的前提
要有继承或实现关系 要有方法的重写 要有父类引用指向子类对象
9.4 多态的优势
- 在多态形式下,右边对象可以实现解耦合,便于扩展和维护。
- 定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利。
9.5 向上造型/自动类型转换:
- 超类型的引用指向派生类的对象
- 能点出来什么,看引用的类型
- 能造型成为的数据类型有:超类+所实现的接口
9.6 强制类型转换
成功的条件只有如下两种:
- 引用所指向的对象,就是该类型
- 引用所指向的对象,实现了该接口或继承了该类
9.7 可能出现的异常
强转时若不符合如上条件,则发生ClassCastException类型转换异常
建议在强转之前先通过instanceof判断引用的对象是否是该类型
public class MultiTypeDemo { public static void main(String[] args) { Aoo o = new Boo(); //向上造型 Boo o1 = (Boo)o; //引用o指向的对象就是Boo Inter o2 = (Inter)o; //引用o指向的对象实现了Inter接口 //Coo o3 = (Coo)o; //运行时发生ClassCastException类型转换异常 if(o instanceof Coo){ //false Coo o4 = (Coo)o; }else{ System.out.println("o不是Coo类型"); } }}interface Inter{}class Aoo{}class Boo extends Aoo implements Inter{}class Coo extends Aoo{}
10 访问控制权限修饰符
修饰符 | 同一个类中 | 同一个包中子类无关类 | 不同包的子类 | 不同包的无关类 |
---|---|---|---|---|
private | √ | |||
默认 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
说明:
- 类的访问权限只能是public或默认的
- 类中成员的访问权限如上4种都可以
package ooday05;//访问控制修饰符的演示public class Aoo { public int a; //任何类 protected int b; //本类、派生类、同包类 int c; //本类、同包类 private int d; //本类 void show(){ a = 1; b = 2; c = 3; d = 4; }}class Boo{ //--------------------演示private void show(){ Aoo o = new Aoo(); o.a = 1; o.b = 2; o.c = 3; //o.d = 4; //编译错误 }}package ooday05_vis;import ooday05.Aoo;public class Coo { //----------------演示同包的 void show(){ Aoo o = new Aoo(); o.a = 1; //o.b = 2; //编译错误 //o.c = 3; //编译错误 //o.d = 4; //编译错误 }}class Doo extends Aoo{ //跨包继承----------演示protected void show(){ a = 1; b = 2; //c = 3; //编译错误 //d = 4; //编译错误 }}
11 static关键字
11.1 static作用
- static是静态的意思,可以修饰成员变量和成员方法。
- static修饰成员变量表示该成员变量只在内存中只存储一份,可以被共享访问、修改。
11.2 静态成员变量
静态成员变量(有static修饰,属于类、加载一次,可以被共享访问)
访问格式:
- 类名.静态成员变量(推荐)
- 对象.静态成员变量(不推荐)。
public class StaticDemo { public static void main(String[] args) { Loo o1 = new Loo(); o1.show(); Loo o2 = new Loo(); o2.show(); Loo o3 = new Loo(); o3.show(); System.out.println(Loo.b); //常常通过类名点来访问 }}//演示静态变量class Loo{ int a; static int b; Loo(){ a++; b++; } void show(){ System.out.println("a="+a+",b="+b); }}
11.3 静态成员方法
静态成员方法(有static修饰,属于类),建议用类名访问,也可以用对象访问
访问格式:
类名.静态成员方法
访问特点:
静态成员方法只能访问静态成员
注意事项
- 静态方法中不能直接使用非静态的成员
- 静态方法中不能使用this
使用场景:
- 表示对象自己的行为的,且方法中需要访问实例成员的,则该方法必须申明成实例方法。
- 如果该方法是以执行一个共用功能为目的,则可以申明成静态方法。
public class StaticDemo { public static void main(String[] args) { Moo.test(); }}//演示静态方法class Moo{ int a; //实例变量(对象点来访问) static int b; //静态变量(类名点来访问) void show(){ //有隐式this System.out.println(this.a); System.out.println(Moo.b); } static void test(){ //没有隐式this //静态方法中没有隐式this传递 //没有this就意味着没有对象 //而实例变量a必须通过对象点来访问 //所以如下代码发生编译错误 //System.out.println(a); //编译错误 System.out.println(Moo.b); }}//演示静态方法在何时用class Noo{ int a; //对象的属性a //在show()中用到了对象的属性a,意味着show()方法与对象有关,所以不能设计为静态方法 void show(){ System.out.println(a); } //在plus()中没有用到对象的属性,意味着plus()方法与对象无关,所以可以设计为静态方法 static int plus(int num1,int num2){ int num = num1+num2; return num; }}
11.4 静态代码块
格式:static{}
特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次
使用场景:在类加载的时候做一些静态数据初始化的操作,以便后续使用。
作用:
- 如果要在启动系统时对数据进行初始化。
- 建议使用静态代码块完成数据的初始化操作,代码优雅。
public class StaticDemo { public static void main(String[] args) { Poo o4 = new Poo(); Poo o5 = new Poo(); }}//演示静态块class Poo{ static{ System.out.println("静态块"); } Poo(){ System.out.println("构造方法"); }}
12 final关键字
fianl关键字的作用 final代表最终的意思,可以修饰成员方法,成员变量,类 final修饰类、方法、变量的效果
- final修饰变量:表明该变量是一个常量,不能再次赋值
- final修饰方法:该方法不能被重写
- fianl修饰类:该类不能被继承(不能有子类,但是可以有父类)
注意:
- final修饰的变量是基本类型:那么变量存储的数据值不能发生改变。
- final修饰的变量是引用类型:那么变量存储的地址值不能发生改变,但是地址指向的对象内容是可以发生变化的。
package ooday05_vis;//演示final修饰变量class Eoo{ final int a = 5; void test(){ //a = 55; //编译错误,final的变量不能被改变 }}//演示final修饰方法class Foo{ final void show(){}}class Goo extends Foo{ //void show(){} //编译错误,final的方法不能被重写}//演示final修饰类final class Hoo{}//class Ioo extends Hoo{} //编译错误,final的类不能被继承class Joo{}final class Koo extends Joo{} //正确,不能当老爸,但能当儿子
12.1 static final常量
- 必须声明同时初始化
- 类名点来访问,不能被改变
- 建议:常量名所有字母都大写,多个单词用_分隔
- 编译器在编译时会将常量直接替换为具体的数,效率高
- 何时用:数据永远不变,并且经常使用
public class StaticFinalDemo { public static void main(String[] args) { System.out.println(Aoo.PI); //常常通过类名点来访问 //Aoo.PI = 3.1415926; //编译错误,常量不能被改变 //1)加载Boo.class到方法区中 //2)静态变量num一并存储到方法区中 //3)到方法区中获取num的值并输出 System.out.println(Boo.num); //编译器在编译时会将常量直接替换为具体的值,效率高 //相当于System.out.println(5); System.out.println(Boo.COUNT); }}class Boo{ public static int num = 5; //静态变量 public static final int COUNT = 5; //常量}class Aoo{ public static final double PI = 3.14159; //public static final int NUM; //编译错误,常量必须声明同时初始化}
13 抽象类
在Java中abstract是抽象的意思,如果一个类中的某个方法的具体实现不能确定,就可以申明成abstract修饰的抽象方法(不能写方法体了),这个类必须用abstract修饰,被称为抽象类。
一个类如果继承了抽象类,那么这个类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
抽象类的意义:
1. 封装共有的属性和行为---------------代码复用 2. 为所有派生类提供统一的类型------向上造型 3. 可以包含抽象方法,为所有派生类提供统一的入口(能点出来), 4. 派生类的行为不同,但入口是一致的,同时相当于定义了一个标准(强制重写)
抽象方法:
- 由abstract修饰
- 只有方法的定义,没有具体的实现(连{}都没有)
14 接口
14.1 定义接口
格式:
public interface 接口名{ //定义抽象方法 void method();}
14.2 实现接口
在要实现接口的类名后面加上implements 接口名。
public class 类名 implements 接口名{}
14.3 接口的特点
- 是一种引用数据类型
- 由interface定义
- 只能包含常量和抽象方法
- 接口不能被实例化(new对象)
- 接口是需要被实现/继承的,实现类/派生类:----必须重写所有抽象方法
- 一个类可以实现多个接口,用逗号分隔,若又继承又实现时,应先继承后实现
- 接口可以继承接口
//接口的演示public class InterfaceDemo { public static void main(String[] args) { //Inter5 o1 = new Inter5(); //编译错误,接口不能被实例化 Inter5 o2 = new Doo(); //向上造型(可以造型为它所实现的接口) Inter4 o3 = new Doo(); //向上造型 }}//演示接口继承接口interface Inter4{ void show();}interface Inter5 extends Inter4{ void test();}class Doo implements Inter5{ public void test(){} public void show(){}}//演示接口多实现interface Inter2{ void show();}interface Inter3{ void test();}abstract class Boo{ abstract void say();}class Coo extends Boo implements Inter2,Inter3{ public void show(){} public void test(){} public void say(){}}//演示接口的实现interface Inter1{ void show(); //访问权限默认是public void test();}class Aoo implements Inter1{ public void show(){} //重写接口中的抽象方法,访问权限必须是public public void test(){}}//演示接口的语法interface Inter{ public static final int NUM = 5; //接口中成员的访问权限只能是public的 public abstract void show(); int COUNT = 6; //默认public static final void say(); //默认public abstract //int number; //编译错误,常量必须声明同时初始化 //void test(){} //编译错误,抽象方法不能有方法体}
14.4 接口的成员特点
成员变量 只能是常量 默认修饰符:public static final 构造方法 没有,因为接口主要是扩展功能的,而没有具体存在 成员方法 只能是抽象方法 默认修饰符:public abstract
14.5 类和接口的关系
类与类的关系 继承关系,只能单继承,但是可以多层继承 类与接口的关系 实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口 接口与接口的关系 继承关系,可以单继承,也可以多继承
14.6抽象类和接口的区别
成员区别抽象类变量,常量;有构造方法;有抽象方法,也有非抽象方法接口常量;无构造方法,抽象方法关系区别类与类继承,单继承类与接口实现,可以单实现,也可以多实现接口与接口继承,单继承,多继承设计理念区别抽象类对类抽象,包括属性、行为接口对行为抽象,主要是行为
15 内部类
15.1 成员内部类
- 成员内部类:应用率低
- 类中套类,外面的称为外部类,里面的称为内部类
- 内部类通常只服务于外部类,对外不具备可见性
- 内部类对象通常在外部类中创建
- 内部类中可以直接访问外部类的成员(包括私有的)
- 内部类中有个隐式的引用指向了创建它的外部类对象:外部类名.this----API时会用
public class InnerClassDemo { public static void main(String[] args) { Mama m = new Mama(); //Baby b = new Baby(); //编译错误,内部类对外不具备可见性 }}class Mama{ //外部类 private String name; void create(){ Baby b = new Baby(); //正确,内部类对象通常在外部类中创建 } class Baby{ //内部类 void showName(){ System.out.println(name); //省略写法 System.out.println(Mama.this.name); //完整写法 //System.out.println(this.name); //编译错误,this指代当前Baby对象 } }}
15.2 匿名内部类
匿名内部类本质是一个对象,它是某个(接口)的子类(实现类对象)
本质:是一个继承了该类或者实现了该接口的子类匿名对象
匿名内部类的特点
- 匿名内部类必须继承父类或实现接口
- 匿名内部类只能有一个对象
- 匿名内部类对象只能使用多态形式引用
面试题:问:内部类有独立的.class吗? 答:有
interface Inter{ void method(); } class Test{ public static void main(String[] args){ new Inter(){ @Override public void method(){ System.out.println("我是匿名内部类"); } }.method(); // 直接调用方法} }
16 内存分配
16.1 内存概述
内存是计算机中的重要原件,临时存储区域,作用是运行程序。
我们编写的程序是存放在硬盘中的,在硬盘中的程序是不会运行的。
必须放进内存中才能运行,运行完毕后会清空内存。
Java虚拟机要运行程序,必须要对内存进行空间的分配和管理。
16.2 Java中的内存分配
区域名称 | 作用 |
---|---|
寄存器 | 给CPU使用,和我们开发无关。 |
本地方法栈 | JVM在使用操作系统功能的时候使用,和我们开发无关。 |
方法区 | 存储可以运行的class文件。 |
堆内存 | 存储对象或者数组,new来创建的,都存储在堆内存。 |
方法栈 | 方法运行时使用的内存,比如main方法运行,进入方法栈中执行。 |
16.3 内存管理
由JVM来管理的
堆:
- 存储new出来的对象(包括实例变量)
- 垃圾:没有任何引用所指向的对象
垃圾回收器(GC)不定时到内存中清扫垃圾,回收过程是透明的(看不到的),不一定一发现垃圾就立刻回收,通过调用System.gc()可以建议JVM尽快调度GC回收 - 实例变量的生命周期: 创建(new)对象时存储在堆中,对象被回收时一并被回收
- 内存泄漏:不再使用的对象没有被及时的回收,严重的泄漏会导致系统的崩溃
建议:不再使用的对象应及时将引用设置为null
栈:
- 存储正在调用的方法中的局部变量(包括方法的参数)
- 调用方法时,会在栈中为该方法分配一块对应的栈帧,栈帧中存储局部变量(包括方法的参数),方法调用结束时,栈帧被自动清除,局部变量一并被清除
- 局部变量的生命周期: 调用方法时存储在栈中,方法调用结束时与栈帧一并被清除
方法区:
- 存储.class字节码文件(包括静态变量、所有方法)
- 方法只有一份,通过this来区分具体的访问对象
17 面向对象三大特征
封装:
- 类:封装的是对象的属性和行为
- 方法:封装的是具体的业务逻辑功能实现
- 访问控制修饰符:封装的是具体的访问权限,以保护数据的安全
继承:
- 作用:代码复用
- 超类:所有派生类所共有的属性和行为
接口:部分派生类所共有的属性和行为
派生类:派生类所特有的属性和行为 - 单一继承、多接口实现,具有传递性
多态:
- 行为多态:所有抽象方法都是多态的(通过方法的重写实现的)
对象多态:所有对象都是多态的(通过向上造型来实现) - 向上造型、强制类型转换、instanceof判断
18 String类
18.1 概述
- java.lang.String使用final修饰,不能被继承
- java中的String在内存中采用Unicode编码方式,任何一个字符占用两个字节的编码
- 字符串底层封装的是一个字符数组
- 字符串一旦创建,对象内容永远无法改变,但字符串引用可以重新赋值------不变对象
18.2 String常用构造方法:
方法名 | 说明 |
---|---|
public String() | 创建一个空白字符串对象,不含有任何内容 |
public String(char[] chs) | 根据字符数组内容,来创建字符串对象 |
public String(byte[] bys) | 根据字符数组内容,来创建字符串对象 |
String s=“abc” | 赋值的方式来创建字符串对象,内容为abc |
方法名 | 说明 |
---|---|
public boolean equals(Object anObject) | 比较字符串的内容,严格区分大小写(用户名和密码) |
public char charAt(int index) | 返回指定索引处的char值 |
public int length() | 返回此字符串的长度 |
/* 使用字面量来创建字符串对象时,JVM会检查常量池中是否有该对象: 1)若没有,则会创建该字符串对象并存入常量池中 2)若有,则直接将常量池中的对象返回(不会再创建新的字符串对象) *//*String s1 = "123abc"; //常量池还没有,因此创建该字符串对象,并存入常量池String s2 = "123abc"; //常量池中已经有了,直接重用对象String s3 = "123abc"; //常量池中已经有了,直接重用对象//引用类型==,比较地址是否相同System.out.println(s1==s2); //trueSystem.out.println(s1==s3); //trueSystem.out.println(s2==s3); //trues1 = s1+"!"; //创建新的字符串对象并将地址赋值给s1System.out.println(s1==s2); //false,因为s1为新对象的地址,与s2不同了*/String s1 = "123abc"; //堆中创建一个123abc对象,常量池中存储这个对象的引用//编译器在编译时,若发现是两个字面量连接,//则直接运算好并将结果保存起来,如下代码相当于String s2="123abc";String s2 = "123"+"abc"; //复用常量池中的123abc对象System.out.println(s1==s2); //trueString s3 = "123";//因为s3不是字面量,所以并不会直接运算结果String s4 = s3+"abc"; //会在堆中创建新的123abc对象,而不会重用常量池中的对象System.out.println(s1==s4); //false
String面试题
/* String s = new String("hello"); 问:如上语句创建了几个对象? 答:2个 第一个:字面量"hello"---java会创建一个String对象表示字面量"hello",并将其存入常量池 第二个:new String()---new String()时会再创建一个字符串对象,并引用hello字符串的内容 */String s = new String("hello");String s1 = "hello";System.out.println("s:"+s); //helloSystem.out.println("s1:"+s1); //helloSystem.out.println(s==s1); //false,==比较的是地址是否相同//字符串实际开发中比较相等一般都是比较字符串的内容//因此我们需要使用equals()方法来比较两个字符串的内容System.out.println(s.equals(s1)); //equals()比较的是内容是否相同
18.3 String常用方法
- length():获取字符串的长度(字符个数)
String str = "我爱Java!"; int len = str.length(); //获取str的长度 System.out.println(len); //7
- trim():去除当前字符串两边的空白字符
String str = " hello world "; System.out.println(str); // hello world str = str.trim(); //去除当前字符串两边的空白字符 System.out.println(str); //hello world
- toUpperCase()和toLowerCase():将当前字符串中的英文部分转为全大写/全小写
String str = "我爱Java!"; String upper = str.toUpperCase(); //将str中英文部分转为全大写 System.out.println(upper); //我爱JAVA! String lower = str.toLowerCase(); //将str中英文部分转为全小写 System.out.println(lower); //我爱java!
- startsWith(String str)和endsWith(String str):判断当前字符串是否是以给定的字符串开始/结尾的
String str = "thinking in java"; boolean starts = str.startsWith("think"); //判断str是否是以think开头的 System.out.println("starts:"+starts); //true boolean ends = str.endsWith(".png"); //判断str是否是以.png结尾的 System.out.println("ends:"+ends); //false
- charAt():返回当前字符串指定位置上的字符
// 0123456789012345 String str = "thinking in java"; char c = str.charAt(9); //获取位置9所对应的字符 System.out.println(c); //i
- indexOf()和lastIndexOf():检索给定字符串在当前字符串中的开始位置
// 111111// 0123456789012345String str = "thinking in java";int index = str.indexOf("in"); //检索in在字符串str中的开始位置System.out.println(index); //2index = str.indexOf("in",3); //从下标为3的位置开始找in第一次出现的位置System.out.println(index); //5index = str.indexOf("IN"); //当前字符串不包含IN,所以返回-1System.out.println(index); //-1index = str.lastIndexOf("in"); //找in最后一次出现的位置System.out.println(index); //9
- substring():截取当前字符串中指定范围内的字符串
// 1// 01234567890String str = "www.tedu.cn";String name = str.substring(4,8); //截到下标4到7范围的字符串System.out.println(name); //teduname = str.substring(4); //从下标4开始一直截到末尾System.out.println(name); //tedu.cn
- String的静态方法valueOf():将其它数据类型转换为String
int a = 123;String s1 = String.valueOf(a); //将int型变量a转换为String类型并赋值给s1System.out.println(s1); //123---字符串类型double d = 123.456;String s2 = String.valueOf(d); //将double型变量d转换为String类型并赋值给s2System.out.println(s2); //123.456----字符串类型String s3 = a+""; //任何内容和字符串连接的结果都是字符串,效率低(下节课讲)System.out.println(s3); //123---字符串类型
18.4 StringBuilder类
StringBuilder构造器
名称 | 说明 |
---|---|
public StringBuilder() | 创建一个空白的可变的字符串对象,不包含任何内容 |
public StringBuilder(String str) | 创建一个指定字符串内容的可变字符串对象 |
常用方法:
名称 | 说明 |
---|---|
public StringBuilder append(任意类型) | 添加数据并返回StringBuilder对象本身 |
public StringBuilder reverse() | 将对象的内容反转 |
public int length() | 返回对象内容长度 |
public String toString() | 通过toString()就可以实现把StringBuilder转换为String |
replace() | 替换部分内容 |
delete() | 删除部分内容 |
insert() | 插入内容 |
String str = "好好学习java";//复制str中的内容到builder中-----好好学习javaStringBuilder builder = new StringBuilder(str);//append():追加内容builder.append(",为了找个好工作!");System.out.println(builder); //好好学习java,为了找个好工作!//replace():替换部分内容builder.replace(9,16,"就是为了改变世界"); //替换下标9到15的System.out.println(builder); //好好学习java,就是为了改变世界!//delete():删除部分内容builder.delete(0,8); //删除下标0到7的System.out.println(builder); //,就是为了改变世界!//insert():插入操作builder.insert(0,"活着"); //从下标0的位置插入System.out.println(builder); //活着,就是为了改变世界!//reverse():翻转builder.reverse(); //翻转内容System.out.println(builder); //!界世变改了为是就,着活
import java.util.ArrayList;import java.util.List;public class ArrayList01 { public static void main(String[] args) { StringBuilder sb=new StringBuilder("hello"); System.out.println(sb);//hellow StringBuilder sb1= sb.append("world"); System.out.println(sb1==sb);//true append返回对象本身 sb.reverse();//反转 System.out.println(sb);//dlrowwolleh }}
面试题:
StringBuilder s1=new StringBuilder("java");StringBuilder s2=new StringBuilder("java");s1==s2;//felses1.equles(s2);//felse
补充:
StringBuilder和StringBuffer:
- StringBuffer:是线程安全的,同步处理的,性能稍慢
- StringBuilder:非线程安全的,并发处理的,性能稍快
18.4 常量池
- java对字符串有一个优化措施:字符串常量池(堆中)
- java推荐我们使用字面量/直接量的方式来创建字符串,并且会缓存所有以字面量形式创建的字符串对象到常量池中,当使用相同字面量再创
- 建对象时将复用常量池中的对象以减少内存开销,从而避免内存中堆积大量内容相同的字符串对象
19 正则表达式
正则表达式是用来描述字符串内容格式,使用它通常用来匹配一个字符串的内容是否符合要求
正则表达式的语法:
[]:表示一个字符,该字符可以是[]中指定的内容例如:[abc]:这个字符可以是a或b或c[a-z]:表示任意一个小写字母[a-zA-Z]:表示任意一个字母[a-zA-Z0-9_]:表示任意一个数字字母下划线[^abc]:该字符只要不是a或b或c预定义字符:.:表示任意一个字符,没有范围限制\d:表示任意一个数字,等同于[0-9]\w:表示任意一个单词字符,等同于[a-zA-Z0-9_]\s:表示任意一个空白字符\D:表示不是数字\W:不是单词字符\S:不是空白字符量词:?:表示前面的内容出现0-1次 例如: [abc]? 可以匹配:a 或 b 或 c 或什么也不写+:表示前面的内容最少出现1次 例如: [abc]+ 可以匹配:b或aaaaaaaaaa...或abcabcbabcbabcbabcbabbabab.... 但是不能匹配:什么都不写 或 abcfdfsbbaqbb34bbwer...*:表示前面的内容出现任意次(0-多次)---匹配内容与+一致,只是可以一次都不写 例如: [abc]* 可以匹配:b或aaaaaaaaaa...或abcabcbabcbabcbabcbabbabab....或什么也不写 但是不能匹配:abcfdfsbbaqbb34bbwer...{n}:表示前面的内容出现n次 例如: [abc]{3} 可以匹配:aaa 或 bbb 或 aab 或abc 或bbc 但是不能匹配: aaaa 或 aad{n,m}:表示前面的内容出现最少n次最多m次 例如: [abc]{3,5} 可以匹配:aaa 或 abcab 或者 abcc 但是不能匹配:aaaaaa 或 aabbd{n,}:表示前面的内容出现n次以上(含n次) 例如: [abc]{3,} 可以匹配:aaa 或 aaaaa.... 或 abcbabbcbabcbabcba.... 但是不能匹配:aa 或 abbdaw...()用于分组,是将括号内的内容看做是一个整体 例如: (abc){3} 表示abc整体出现3次. 可以匹配abcabcabc 但是不能匹配aaa 或abcabc (abc|def){3}表示abc或def整体出现3次. 可以匹配: abcabcabc 或 defdefdef 或 abcdefabc 但是不能匹配abcdef 或abcdfbdef
String支持与正则表达式相关的方法
方法1: matches():使用给定的正则表达式验证当前字符串的格式是否符合要求
/*邮箱的正则表达式: [a-zA-Z0-9_]+@[a-zA-Z0-9]+(\.[a-zA-Z]+)+*/String email = "wangkj@tedu.cn";String regex = "[a-zA-Z0-9_]+@[a-zA-Z0-9]+(\\.[a-zA-Z]+)+";boolean match = email.matches(regex);if(match){ System.out.println("是正确的邮箱");}else{ System.out.println("不是正确的邮箱");}
方法2: split():将当前字符串按照满足正则表达式的部分进行拆分
String line = "abc123def456ghi";String[] data = line.split("[0-9]+"); //按数字拆分System.out.println(Arrays.toString(data)); //将data数组按照字符串的格式输出line = "123,456,789,482";data = line.split(","); //按逗号拆分System.out.println(Arrays.toString(data));line = "123.456.789.482"; //练习+下课--------11:35继续data = line.split("\\."); //按点拆分System.out.println(Arrays.toString(data));//最开始就是可拆分项(.),那么数组中的第1个元素为一个空字符串------""//如果连续两个(两个以上)可拆分项,它们中间也会拆出一个空字符串-----""//如果末尾连续多个可拆分项,那么拆出的空字符串被忽略line = ".123.456..789.482.......";data = line.split("\\.");System.out.println(Arrays.toString(data));
方法3: replaceAll():将当前字符串中满足正则表达式的部分替换为给定的字符串
String line = "abc123def456ghi";line = line.replaceAll("[0-9]+","#NUMBER#"); //将数字部分替换为#NUMBER#System.out.println(line);
20 Object类
20.1 Object类的作用
- 是所有类的鼻祖,所有类都直接或间接继承了Object,万物皆对象,为了多态
20.2 Object类的常用方法
方法名称 | 类型 | 描述 |
---|---|---|
public Object() | 构造 | 构造器 |
public boolean equals(Object obj) | 普通 | 对象比较 |
public String toString() | 普通 | 对象打印时调用 |
- Object的oString方法存在的意义:
父类toString()方法存在的意义就是为了被子类重写,以便返回对象的内容信息,而不是地址信息
Object的oString方法作用:
让子类重写,以便返回子类对象的内容。
- Object的equals方法存在的意义:
父类equals方法存在的意义就是为了被子类重写,以便子类自己来定制比较规则。
Object的equals方法作用:
- 默认是与另一个对象比较地址是否一样
- 让子类重写,以便比较2个子类对象的内容是否相同
import java.util.Objects;/** 测试常常被派生类重写的Object中的相关方法 */public class Point { private int x; private int y; @Override public String toString() { return "Point{" + "x=" + x + ", y=" + y + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Point point = (Point) o; return x == point.x && y == point.y; } @Override public int hashCode() { return Objects.hash(x, y); } public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; }}public class ObjectDemo { public static void main(String[] args) { //演示重写toString() Point p = new Point(1,2); //输出引用对象时默认调用toString() //相当于System.out.println(p.toString()); System.out.println(p); //字符串连接时将默认调用对象的toString() //相当于String str = "这是个点:"+p.toString(); String str = "这是个点:"+p; System.out.println(str); //演示重写equals() //Object类另一个常常被派生类重写的方法:equals() Point p1 = new Point(1,2); Point p2 = new Point(1,2); System.out.println(p1==p2); //false,==比较的地址 System.out.println(p1.equals(p2)); //true,因为重写equals()中比较的是x和y }}
20.3 ==操作符与equals方法
面试题1. ==基本类型比较值:只要两个变量的值相等,即为true。引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,==才返回true用“==”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错2. equals所有类都继承了Object,也就获得了equals()方法。还可以重写只能比较引用类型,其作用与“==”相同,比较是否指向同一个对象特例:当用equals()方法进行比较时,对类File、String、Date及包装类(Wrapper Class)来说,是比较类型及内容而不考虑引用的是否是同一个对象;(原因:在这些类中重写了Object类的equals()方法。)
21 包装类
21.1 包装类的作用
- Java为了实现一切皆对象,为8种基本类型提供了对应的引用类型。
- 后面的集合和泛型其实也只能支持包装类型,不支持基本数据类型。
- java定义了8个包装类,目的是为了解决基本类型不能直接参与面向对象开发的问题,使得基本类型可以通过包装类的实例以对象的方式存在
自动装箱:基本类型的数据和变量可以直接赋值给包装类型的变量。
自动装箱:基本类型的数据和变量可以直接赋值给包装类型的变量。
基本数据类型 | 引用数据类型 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
float | Float |
double | Double |
boolean | Boolean |
//演示自动拆装箱//触发了自动装箱特性Integer i1 = 5; //会被编译为: Integer i1 = Integer.valueOf(5);//触发了自动拆箱特性int ii = i1; //会被编译为: int ii = i1.intValue();//演示包装类的实际操作://1)可以通过包装类来得到基本类型的取值范围:int max = Integer.MAX_VALUE; //获取int的最大值int min = Integer.MIN_VALUE; //获取int的最小值System.out.println("int的最大值为:"+max);System.out.println("int的最小值为:"+min);long lMax = Long.MAX_VALUE; //获取long的最大值long lMin = Long.MIN_VALUE; //获取long的最小值System.out.println("long的最大值为:"+lMax);System.out.println("long的最小值为:"+lMin);//2)包装类可以将字符串转换为对应的基本类型// 前提是该字符串正确表达了基本类型的值// 若不能正确表达,则发生NumberFormatException数字转换异常String str = "123";int num = Integer.parseInt(str); //将字符串str转换为int类型System.out.println(num); //123str = "123.456";double dou = Double.parseDouble(str); //将字符串str转换为double类型System.out.println(dou); //123.456
21.2 包装类的特有功能
- 可以把基本类型的数据转换成字符串类型(用处不大)
//1)可以把基本类型的数据转换成字符串类型public class Test1 { public static void main(String[] args) { int a=1; double b=2.0; System.out.println(Integer.toString(a)); System.out.println(Double.toString(b)); }}
- 可以把字符串类型的数值转换成真实的数据类型(真的很有用)
//2)包装类可以将字符串转换为对应的基本类型// 前提是该字符串正确表达了基本类型的值// 若不能正确表达,则发生NumberFormatException数字转换异常String str = "123";int num = Integer.parseInt(str); //将字符串str转换为int类型System.out.println(num); //123str = "123.456";double dou = Double.parseDouble(str); //将字符串str转换为double类型System.out.println(dou); //123.456
21.3 转义序列
Java的转义序列:
转义序列 | 描述 |
---|---|
\t | 在文中该处插入一个tab键 |
\b | 在文中该处插入一个后退键 |
\n | 在文中该处换行 |
\r | 在文中该处插入回车 |
\f | 在文中该处插入换页符 |
’ | 在文中该处插入单引号 |
" | 在文中该处插入双引号 |
\ | 在文中该处插入反斜杠 |
public class Test { public static void main(String[] args) { System.out.println("访问\"CSDN!\""); }}
22 异常
- 异常是代码在编译或者执行的过程中可能出现的错误。
- 应该避免异常的出现,同时处理可能出现的异常,让代码更稳健。
22.1 异常分类
分为编译时异常、运行时异常。
- 编译时异常:没有继承RuntimeExcpetion的异常,编译阶段就会出错,编译期必须处理的,否则程序不能通过编译。
- 运行时异常:继承自RuntimeException的异常或其子类,编译阶段不报错,运行可能报错。
22.2 异常体系
java的异常处理机制
java中所有的异常的超类为Throwable,其下派生了两个子类:Error和Exception
Error:表示系统错误,通常是不能在程序运行期间被解决的错误。
Exception:异常类,表示程序级别的错误,通常是由于逻辑等导致的问题,可以在程序运行期间被解决。
- RuntimeException及其子类:运行时异常,编译阶段不会报错。 (空指针异常,数组索引越界异常)
- 除RuntimeException之外所有的异常:编译时异常,编译期必须处理的,否则程序不能通过编译。 (日期格式化异常)。
22.3 运行时异常
继承自RuntimeException的异常或其子类,编译阶段不报错,运行可能报错。
常见运行时异常:
- 空指针异常 : NullPointerException,直接输出没有问题,但是调用空指针的变量的功能就会报错。
- 数学操作异常:ArithmeticException
- 数字转换异常: NumberFormatException
- 数组索引越界异常: ArrayIndexOutOfBoundsException
- 类型转换异常:ClassCastException
- 字符串下标越界异常:StringIndexOutOfBoundsException
22.4 编译时异常
没有继承RuntimeExcpetion的异常,编译阶段就会出错,编译期必须处理的,否则程序不能通过编译。
常见编译时异常:
IO流异常:java.io.IOExeption
没有找到指定的类异常:java.lang.ClassNotFoundException
系统找不到指定的文件异常:java.io.FileNotFoundException
访问关系数据库类产生的异常:java.sql.SQLException
22.5JVM默认处理异常流程
- 默认会在出现异常的代码那里自动的创建一个异常对象
- 异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机
- 虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据
- 直接从当前执行的异常点干掉当前程序
- 后续代码没有机会执行了,因为程序已经死亡
22.6 throws异常处理方式
throws 格式:
方法 throws 异常1 ,异常2 ,异常3 ..{}方法 throws Exception{//抛出一切异常}
22.7 try-catch异常处理方式
try-catch格式:
try{ // 监视可能出现异常的代码!}catch(异常类型1 变量){ // 处理异常 }catch(异常类型2 变量){ // 处理异常}...try{// 可能出现异常的代码!}catch (Exception e){//可以捕获处理一切异常类型 e.printStackTrace(); // 直接打印异常栈信息}}
/** * java的异常处理机制 * java中所有的异常的超类为Throwable,其下派生了两个子类型:Error和Exception * Error表示系统错误,通常是不能在程序运行期间被解决的错误。 * Exception表示程序级别的错误,通常是由于逻辑等导致的问题,可以在程序运行期间被解决。 * * 异常处理机制中的try-catch * 语法: * try{ * 可能会出现的异常的代码片段 * }catch(xxxxException e){ * try语句块中出现xxxxException后的解决办法 * } * */public class TryCatchDemo { public static void main(String[] args) { System.out.println("程序开始了..."); try { String line = ""; String s = null; String a = "a"; /* JVM执行到这里时,如果发生了异常就会实例化一个对应的异常实例,并将程序执行 过程设置进去,然后将异常抛出。 */ System.out.println(s.length()); System.out.println(line.charAt(5)); System.out.println(Integer.parseInt(a)); } /*catch (NullPointerException e) { System.out.println("空指针异常出现"); }catch (StringIndexOutOfBoundsException e) { System.out.println("出现下标越界异常"); }*/ //当多个异常的解决办法相同时,可以合并到一个catch来捕获并处理 catch (StringIndexOutOfBoundsException | NullPointerException e) { System.out.println("两种异常统一解决的办法"); //可以在最后一个catch处捕获Eeception,避免因未能捕获的异常导致程序中断 } catch (Exception e) { System.out.println(""); } finally { System.out.println("---"); } System.out.println("程序结束了..."); }}
finally:
/** * 异常处理机制中的finally块 * finally块就是异常处理机制的最后一块,它可以跟在try之后或者最后一个catch之后。 * finally可以保证只要程序执行到try语句块中,无论try中是否出现异常,finally最终都 * 会必定执行。 * 通常我们将释放资源这类操作方法finally中去报运行,例如IO操作后最终的close()调用 * */public class FinallyDemo { public static void main(String[] args) { System.out.println("程序开始了"); try { String line = null;// String line = "abc"; System.out.println(line.length());//如果出错try中的以下的语句将不会执行 System.out.println("!!!!!!!!!!!!"); } catch (Exception e) { System.out.println("出错了"); }finally { System.out.println("finally中的代码执行了"); } System.out.println("程序结束了");//出错正常执行 }}
22.8 throws与try-catch的区别
22.9 自定义异常
1、自定义编译时异常
定义一个异常类继承Exception.
重写构造器。
在出现异常的地方用throw new 自定义对象抛出,
作用:编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理!!
2、自定义运行时异常
定义一个异常类继承RuntimeException.
重写构造器。
在出现异常的地方用throw new 自定义对象抛出!
作用:提醒不强烈,编译阶段不报错!!运行时才可能出现!!
23 File类
File类的每一个实例可以表示硬盘(文件系统)中的一个文件或目录(实际上表示的是一个抽象路径)
使用File可以做到:
- 访问其表示的文件或目录的属性信息,例如:名字,大小,修改时间等等
- 创建和删除文件或目录
- 访问一个目录中的子项
但是File不能访问文件数据。
方法名称 | 说明 |
---|---|
public File(String pathname) | 根据文件路径创建文件对象 |
public File(String parent, String child) | 从父路径名字字符串和子路径名字符串创建对象 |
public File(File parent, , String child) | 根据父路径和子路径名字符串创建文件对象 |
public class FileDemo { public static void main(String[] args) { //使用File访问当前项目目录下的demo.txt文件 /* 创建File时要指定路径,而路径通常使用相对路径。 相对路径的好处在于有良好的跨平台性。 "./"是相对路径中使用最多的,表示"当前目录",而当前目录是哪里 取决于程序运行环境而定,在idea中运行java程序时,这里指定的 当前目录就是当前程序所在的项目目录。 */// File file = new File("c:/xxx/xxx/xx/xxx.txt"); File file = new File("./demo.txt"); //获取名字 String name = file.getName(); System.out.println(name); //获取文件大小(单位是字节) long len = file.length(); System.out.println(len+"字节"); //是否可读可写 boolean cr = file.canRead(); boolean cw = file.canWrite(); System.out.println("是否可读:"+cr); System.out.println("是否可写:"+cw); //是否隐藏 boolean ih = file.isHidden(); System.out.println("是否隐藏:"+ih); }}
23.1 判断功能
方法名称 | 说明 |
---|---|
public boolean isDirectory() | 测试此抽象路径名表示的File是否为文件夹 |
public boolean isFile() | 测试此抽象路径名表示的File是否为文件 |
public boolean exists() | 测试此抽象路径名表示的File是否存在 |
public String getAbsolutePath() | 返回此抽象路径名的绝对路径名字符串 |
public String getPath() | 将此抽象路径名转换为路径名字符串 |
public String getName() | 返回由此抽象路径名表示的文件或文件夹的名称 |
public long lastModified() | 返回文件最后修改的时间毫秒值 |
23.2 File类创建和删除
方法名称 | 说明 |
---|---|
public boolean createNewFile() | 创建一个新的空的文件 |
public boolean delete() | 删除由此抽象路径名表示的文件或空文件夹 |
public boolean mkdir() | 只能创建一级文件夹 |
public boolean mkdirs() | 可以创建多级文件夹 |
createNewFile():创建一个新的空的文件
/** * 使用File创建一个新文件 */public class CreateNewFileDemo { public static void main(String[] args) throws IOException { //在当前目录下新建一个文件:test.txt File file = new File("./test.txt"); //boolean exists()判断当前File表示的位置是否已经实际存在该文件或目录 if(file.exists()){ System.out.println("该文件已存在!"); }else{ file.createNewFile();//将File表示的文件创建出来 System.out.println("文件已创建!"); } }}
delete:删除由此抽象路径名表示的文件或空文件夹
- delete方法默认只能删除文件和空文件夹。
- delete方法直接删除不走回收站。
/** * 使用File删除一个文件 */public class DeleteFileDemo { public static void main(String[] args) { //将当前目录下的test.txt文件删除 /* 相对路径中"./"可以忽略不写,默认就是从当前目录开始的。 */ File file = new File("test.txt"); if(file.exists()){ file.delete(); System.out.println("文件已删除!"); }else{ System.out.println("文件不存在!"); } }}/** * 删除一个目录 */public class DeleteDirDemo { public static void main(String[] args) { //将当前目录下的demo目录删除 File dir = new File("demo");// File dir = new File("a"); if(dir.exists()){ dir.delete();//delete方法删除目录时只能删除空目录 System.out.println("目录已删除!"); }else{ System.out.println("目录不存在!"); } }}
mkDir():创建当前File表示的目录
mkDirs():创建当前File表示的目录,同时将所有不存在的父目录一同创建
/** * 使用File创建目录 */public class MkDirDemo { public static void main(String[] args) { //在当前目录下新建一个目录:demo// File dir = new File("demo"); File dir = new File("./a/b/c/d/e/f"); if(dir.exists()){ System.out.println("该目录已存在!"); }else{// dir.mkdir();//创建目录时要求所在的目录必须存在 dir.mkdirs();//创建目录时会将路径上所有不存在的目录一同创建 System.out.println("目录已创建!"); } }}
23.3 File类的遍历
方法名称 | 说明 |
---|---|
public String[] list() | 获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回。 |
public File[] listFiles()(常用) | 获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点) |
/** * 访问一个目录中的所有子项 */public class ListFilesDemo1 { public static void main(String[] args) { //获取当前目录中的所有子项 File dir = new File("."); /* boolean isFile() 判断当前File表示的是否为一个文件 boolean isDirectory() 判断当前File表示的是否为一个目录 */ if(dir.isDirectory()){ /* File[] listFiles() 将当前目录中的所有子项返回。返回的数组中每个File实例表示其中的一个子项 */ File[] subs = dir.listFiles(); System.out.println("当前目录包含"+subs.length+"个子项"); for(int i=0;i<subs.length;i++){ File sub = subs[i]; System.out.println(sub.getName()); } } }}
获取目录中符合特定条件的子项
/** * 重载的listFiles方法,允许我们传入一个文件过滤器从而可以有条件的获取一个目录 * 中的子项。 */public class ListFilesDemo2 { public static void main(String[] args) { /* 需求:获取当前目录中所有名字以"."开始的子项 */ File dir = new File("."); if(dir.isDirectory()){// FileFilter filter = new FileFilter(){//匿名内部类创建过滤器// public boolean accept(File file) {// String name = file.getName();// boolean starts = name.startsWith(".");//名字是否以"."开始// System.out.println("过滤器过滤:"+name+",是否符合要求:"+starts);// return starts;// }// };// File[] subs = dir.listFiles(filter);//方法内部会调用accept方法 File[] subs = dir.listFiles(new FileFilter(){ public boolean accept(File file) { return file.getName().startsWith("."); } }); System.out.println(subs.length); } }}
listFiles方法注意事项:
- 当调用者不存在时,返回null
- 当调用者是一个文件时,返回null
- 当调用者是一个空文件夹时,返回一个长度为0的数组
- 当调用者是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回
- 当调用者是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏内容
- 当调用者是一个需要权限才能进入的文件夹时,返回null
24 Lambda表达式
DK8之后,java支持了lambda表达式这个特性.
- lambda可以用更精简的代码创建匿名内部类.但是该匿名内部类实现的接口只能有一个抽象方法,否则无法使用!
- lambda表达式是编译器认可的,最终会将其改为内部类编译到class文件中
/** * JDK8之后java支持了lambda表达式这个特性 * lambda表达式可以用更精简的语法创建匿名内部类,但是实现的接口只能有一个抽象 * 方法,否则无法使用。 * lambda表达式是编译器认可的,最终会被改为内部类形式编译到class文件中。 * * 语法: * (参数列表)->{ * 方法体 * } */public class LambdaDemo { public static void main(String[] args) { //匿名内部类形式创建FileFilter FileFilter filter = new FileFilter() { public boolean accept(File file) { return file.getName().startsWith("."); } }; FileFilter filter2 = (File file)->{ return file.getName().startsWith("."); }; //lambda表达式中参数的类型可以忽略不写 FileFilter filter3 = (file)->{ return file.getName().startsWith("."); }; /* lambda表达式方法体中若只有一句代码,则{}可以省略 如果这句话有return关键字,那么return也要一并省略! */ FileFilter filter4 = (file)->file.getName().startsWith("."); }}
25 IO流
字节流:操作所有类型的文件,比如音频视频图片等。
字符流:只能操作纯文本文件,比如包括java文件,txt文件等。
25.1 字节流
25.1.1 文件字节输入流FileInputStream
作用:以内存为基准,把磁盘文件中的数据以字节的形式读取到内存中去。
构造器 | 说明 |
---|---|
public FileInputStream(File file) | 创建字节输入流管道与源文件对象接通 |
public FileInputStream(String pathname) | 创建字节输入流管道与源文件路径接通 |
方法名称 | 说明 |
---|---|
public int read0 | 每次读取一个字节返回,如果字节已经没有可读的返回-1 |
public int read(byte] buffer) | 每次读取一个字节数组返回,如果字节已经没有可读的返回-1 |
/*目标:字节输入流的使用。*/public class FileInputStreamDemo01 { public static void main(String[] args) throws Exception { // 1、创建一个文件字节输入流管道与源文件接通。 // InputStream is = new FileInputStream(new File("file-io-app\\src\\data.txt")); // 简化写法 InputStream is = new FileInputStream("data.txt"); // 2、读取一个字节返回 (每次读取一滴水)// int b1 = is.read();// System.out.println((char)b1);//// int b2 = is.read();// System.out.println((char)b2);//// int b3 = is.read();// System.out.println((char)b3);//// int b4 = is.read(); // 读取完毕返回-1// System.out.println(b4); // 3、使用循环改进 // 定义一个变量记录每次读取的字节 a b 3 爱 // o o o [ooo] int b; while (( b = is.read() ) != -1){ System.out.print((char) b); } }}
/** 目标:使用文件字节输入流每次读取一个字节数组的数据。 */public class FileInputStreamDemo02 { public static void main(String[] args) throws Exception { // 1、创建一个文件字节输入流管道与源文件接通 InputStream is = new FileInputStream("data02.txt"); // 2、定义一个字节数组,用于读取字节数组// byte[] buffer = new byte[3]; // 3B// int len = is.read(buffer);// System.out.println("读取了几个字节:" + len);// String rs = new String(buffer);// System.out.println(rs);//// int len1 = is.read(buffer);// System.out.println("读取了几个字节:" + len1);// String rs1 = new String(buffer);// System.out.println(rs1);// // buffer = [a b c]//// // buffer = [a b c] ==> [c d c]// int len2 = is.read(buffer);// System.out.println("读取了几个字节:" + len2);// // 读取多少倒出多少// String rs2 = new String(buffer,0 ,len2);// System.out.println(rs2);//// int len3 = is.read(buffer);// System.out.println(len3); // 读取完毕返回-1 // 3、改进使用循环,每次读取一个字节数组 byte[] buffer = new byte[3]; int len; // 记录每次读取的字节数。 while ((len = is.read(buffer)) != -1) { // 读取多少倒出多少 System.out.print(new String(buffer, 0 , len)); } }}
/** 目标:使用文件字节输入流一次读完文件的全部字节。可以解决乱码问题。 */public class FileInputStreamDemo03 { public static void main(String[] args) throws Exception { // 1、创建一个文件字节输入流管道与源文件接通 File f = new File("data03.txt"); InputStream is = new FileInputStream(f); // 2、定义一个字节数组与文件的大小刚刚一样大。// byte[] buffer = new byte[(int) f.length()];// int len = is.read(buffer);// System.out.println("读取了多少个字节:" + len);// System.out.println("文件大小:" + f.length());// System.out.println(new String(buffer)); // 读取全部字节数组 byte[] buffer = is.readAllBytes();//直接将当前字节输入流对应的文件对象的字节数据装到一个字节数组返回 System.out.println(new String(buffer)); }}
25.1.2 文件字节输出流FileOutputStream
作用:以内存为基准,把内存中的数据以字节的形式写出到磁盘文件中去的流。
构造器 | 说明 |
---|---|
public FileOutputStream(File file) | 创建字节输出流管道与源文件对象接通 |
public FileOutputStream(File file, boolean append) | 创建字节输出流管道与源文件对象接通,可追加数据 |
public FileOutputStream(String filepath) | 创建字节输出流管道与源文件路径接通 |
public FileOutputStream(String filepath, boolean append) | 创建字节输出流管道与源文件路径接通,可追加数据 |
方法名称 | 说明 |
---|---|
public void write(int a) | 写一个字节出去 |
public void write(byte[] buffer) | 写一个字节数组出去 |
public void write(byte[] buffer , int pos , int len) | 写一个字节数组的一部分出去。 |
/*目标:字节输出流的使用。*/public class OutputStreamDemo04 { public static void main(String[] args) throws Exception { // 1、创建一个文件字节输出流管道与目标文件接通 OutputStream os = new FileOutputStream("file-io-app/src/out04.txt" , true); // 追加数据管道// OutputStream os = new FileOutputStream("file-io-app/src/out04.txt"); // 先清空之前的数据,写新数据进入 // 2、写数据出去 // a.public void write(int a):写一个字节出去 os.write('a'); os.write(98); os.write("\r\n".getBytes()); // 换行 // os.write('徐'); // [ooo] // b.public void write(byte[] buffer):写一个字节数组出去。 byte[] buffer = {'a' , 97, 98, 99}; os.write(buffer); os.write("\r\n".getBytes()); // 换行 byte[] buffer2 = "我是中国人".getBytes();// byte[] buffer2 = "我是中国人".getBytes("GBK"); os.write(buffer2); os.write("\r\n".getBytes()); // 换行 // c. public void write(byte[] buffer , int pos , int len):写一个字节数组的一部分出去。 byte[] buffer3 = {'a',97, 98, 99}; os.write(buffer3, 0 , 3); os.write("\r\n".getBytes()); // 换行 // os.flush(); // 写数据必须,刷新数据 可以继续使用流 os.close(); // 释放资源,包含了刷新的!关闭后流不可以使用了 }}
文件的复制:
/** * 文件的复制 */public class CopyDemo { public static void main(String[] args) throws IOException { //创建文件输入流读取原文件 FileInputStream fis = new FileInputStream("image.jpg"); //创建文件输出流写入复制文件 FileOutputStream fos = new FileOutputStream("image_cp.jpg"); int d;//保存每次读取到的字节 /* 原文件数据: 11000011 10101010 00001111 11001100 00110011 ...^^^^^^^^ d = fis.read(); d:00000000 00000000 00000000 10101010 fos.write(d); 复制文件的数据: 11000011 10101010 */ long start = System.currentTimeMillis();//获取当前系统时间的毫秒值(UTC时间) while((d = fis.read()) != -1) { fos.write(d); } long end = System.currentTimeMillis();//获取当前系统时间的毫秒值(UTC时间) System.out.println("复制完毕!耗时:"+(end-start)+"ms"); fis.close(); fos.close(); }}
25.1.3 流的关闭与刷新
方法 | 说明 |
---|---|
flush0 | 刷新流,还可以继续写数据 |
close() | 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
25.2 字符流
25.2.1 文件字符输入流:FileReader
构造器 | 说明 |
---|---|
public EileReader(File file) | 创建字符输入流管道与源文件对象接通 |
public FileReader(String pathname) | 创建字符输入流管道与源文件路径接通 |
方法名称 | 说明 |
---|---|
public int read0 | 每次读取一个字符返回,如果字符已经没有可读的返回-1 |
public int read(char[] buffer) | 每次读取一个字符数组,返回读取的字符个数,如果字符已经没有可读的返回-1 |
/* 目标:字符输入流的使用。 */ public class FileReaderDemo01 { public static void main(String[] args) throws Exception { // 目标:每次读取一个字符。 // 1、创建一个字符输入流管道与源文件接通 Reader fr = new FileReader("data06.txt"); // 2、读取一个字符返回,没有可读的字符了返回-1// int code = fr.read();// System.out.print((char)code);//// int code1 = fr.read();// System.out.print((char)code1); // 3、使用循环读取字符 int code; while ((code = fr.read()) != -1){ System.out.print((char) code); } }}
/* 目标:字符输入流的使用-按照字符数组读取。*/public class FileReaderDemo02 { public static void main(String[] args) throws Exception { // 1、创建一个文件字符输入流与源文件接通 Reader fr = new FileReader("data07.txt"); // 2、用循环,每次读取一个字符数组的数据。 1024 + 1024 + 8 char[] buffer = new char[1024]; // 1K字符 int len; while ((len = fr.read(buffer)) != -1) { String rs = new String(buffer, 0, len); System.out.print(rs); } }}
25.2.2 文件字符输出流:FileWriter
作用:以内存为基准,把内存中的数据以字符的形式写出到磁盘文件中去的流。
构造器 | 说明 |
---|---|
public Eilelritec(File file) | 创建字符输出流管道与源文件对象接通 |
public Eileiitec(File file,boolean append) | 创建字符输出流管道与源文件对象接通,可追加数据 |
public Eilewritec(String filepath) | 创建字符输出流管道与源文件路径接通 |
public EileMrite(String filepath,boolsan append) | 创建字符输出流管道与源文件路径接通,可追加数据 |
方法名称 | 说明 |
---|---|
public void write(int c) | 写一个字符出去 |
public void write(String c) | 写一个字符串出去 |
public void write(char[] buffer) | 写一个字符数组出去 |
public void write(String c ,int pos ,int len) | 写字符串的一部分出去 |
public void write(char[] buffer ,int pos ,int len) | 写字符数组的一部分出去 |
/* 目标:字符输出流的使用。*/public class FileWriterDemo03 { public static void main(String[] args) throws Exception { // 1、创建一个字符输出流管道与目标文件接通 // Writer fw = new FileWriter("file-io-app/src/out08.txt"); // 覆盖管道,每次启动都会清空文件之前的数据 Writer fw = new FileWriter("out08.txt", true); // 覆盖管道,每次启动都会清空文件之前的数据// a.public void write(int c):写一个字符出去 fw.write(98); fw.write('a'); fw.write('徐'); // 不会出问题了 fw.write("\r\n"); // 换行//b.public void write(String c)写一个字符串出去 fw.write("abc我是中国人"); fw.write("\r\n"); // 换行//c.public void write(char[] buffer):写一个字符数组出去 char[] chars = "abc我是中国人".toCharArray(); fw.write(chars); fw.write("\r\n"); // 换行//d.public void write(String c ,int pos ,int len):写字符串的一部分出去 fw.write("abc我是中国人", 0, 5); fw.write("\r\n"); // 换行//e.public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去 fw.write(chars, 3, 5); fw.write("\r\n"); // 换行 // fw.flush();// 刷新后流可以继续使用 fw.close(); // 关闭包含刷线,关闭后流不能使用 }}
25.3 字节缓冲流
- 缓冲流也称为高效流、或者高级流。之前学习的字节流可以称为原始流。
- 作用:缓冲流自带缓冲区、可以提高原始字节流、字符流读写数据的性能
25.3.1 字节缓冲输入流: BufferedInputStream
构造器 | 说明 |
---|---|
public BufferedInputStream(InputStream is) | 可以把低级的字节输入流包装成一个高级的缓冲字节输入流管道,从而提高字节输入流读数据的性能 |
public BufferedOutputStream(OutputStream os) | 可以把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能 |
/** * java将流分为节点流与处理流两类 * 节点流:也称为低级流,是真实连接程序与另一端的"管道",负责实际读写数据的流。 *读写一定是建立在节点流的基础上进行的。 *节点流好比家里的"自来水管"。连接我们的家庭与自来水厂,负责搬运水。 * 处理流:也称为高级流,不能独立存在,必须连接在其他流上,目的是当数据经过当前流时 *对其进行某种加工处理,简化我们对数据的同等操作。 *高级流好比家里常见的对水做加工的设备,比如"净水器","热水器"。 *有了它们我们就不必再自己对水进行加工了。 * 实际开发中我们经常会串联一组高级流最终连接到低级流上,在读写操作时以流水线式的加工 * 完成复杂IO操作。这个过程也称为"流的连接"。 * * 缓冲流,是一对高级流,作用是加快读写效率。 * java.io.BufferedInputStream和java.io.BufferedOutputStream * */public class CopyDemo3 { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("ppt.pptx"); BufferedInputStream bis = new BufferedInputStream(fis); FileOutputStream fos = new FileOutputStream("ppt_cp.pptx"); BufferedOutputStream bos = new BufferedOutputStream(fos); int d; long start = System.currentTimeMillis(); while((d = bis.read())!=-1){//使用缓冲流读取字节 bos.write(d);//使用缓冲流写出字节 } long end = System.currentTimeMillis(); System.out.println("耗时:"+(end-start)+"ms"); bis.close();//关闭流时只需要关闭高级流即可,它会自动关闭它连接的流 bos.close(); }}
25.3.2 字节缓冲输出流:BufferedOutputStream
构造器 | 说明 |
---|---|
public BufferedOutputStream(OutputStream os) | 可以把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能 |
/** * 缓冲输出流写出数据的缓冲区问题 */public class BOS_FlushDemo { public static void main(String[] args) throws IOException { FileOutputStream fos = new FileOutputStream("bos.txt"); BufferedOutputStream bos = new BufferedOutputStream(fos); String line = "奥里给!"; byte[] data = line.getBytes(StandardCharsets.UTF_8); bos.write(data); System.out.println("写出完毕!"); /* 缓冲流的flush方法用于强制将缓冲区中已经缓存的数据一次性写出。 注:该方法实际上实在字节输出流的超类OutputStream上定义的,并非只有缓冲 输出流有这个方法。但是实际上只有缓冲输出流的该方法有实际意义,其他的流实现 该方法的目的仅仅是为了在流连接过程中传递flush动作给缓冲输出流。 */ bos.flush();//冲 bos.close(); }}
25.4 字符缓冲流
- 缓冲流也称为高效流、或者高级流。之前学习的字节流可以称为原始流。
- 作用:缓冲流自带缓冲区、可以提高原始字节流、字符流读写数据的性能
25.4.1 字符缓冲输入流:BufferedReader
作用:提高字符输入流读取数据的性能,除此之外多了按照行读取数据的功能。
构造器 | 说明 |
---|---|
public BufferedReader(Reader r) | 可以把低级的字符输入流包装成一个高级的缓冲字符输入流管道,从而提高字符输入流读数据的性能 |
方法 | 说明 |
---|---|
public String readLine() | 读取一行数据返回,如果读取没有完毕,无行可读返回null |
/** 目标:学会使用缓冲字符输入流提高字符输入流的性能,新增了按照行读取的方法(经典代码) */public class BufferedReaderDemo1 { public static void main(String[] args) { try ( // 1、创建一个文件字符输入流与源文件接通。 Reader fr = new FileReader("data01.txt"); // a、把低级的字符输入流包装成高级的缓冲字符输入流。 BufferedReader br = new BufferedReader(fr); ){ // 2、用循环,每次读取一个字符数组的数据。 1024 + 1024 + 8// char[] buffer = new char[1024]; // 1K字符// int len;// while ((len = br.read(buffer)) != -1) {// String rs = new String(buffer, 0, len);// System.out.print(rs);// }String line;while ((line = br.readLine()) != null){ System.out.println(line);} } catch (IOException e) { e.printStackTrace(); } }}
25.4.2 字符缓冲输出流:BufferedWriter
作用:提高字符输出流写取数据的性能,除此之外多了换行功能
构造器 | 说明 |
---|---|
public BufferedWriter(Writer w) | 可以把低级的字符输出流包装成一个高级的缓冲字符输出流管道,从而提高字符输出流写数据的性能 |
方法 | 说明 |
---|---|
public void newLine() | 换行操作 |
/** 目标:缓冲字符输出流的使用,学会它多出来的一个功能:newLine(); */public class BufferedWriterDemo2 { public static void main(String[] args) throws Exception { // 1、创建一个字符输出流管道与目标文件接通 Writer fw = new FileWriter("out02.txt"); // 覆盖管道,每次启动都会清空文件之前的数据//Writer fw = new FileWriter("io-app2/src/out02.txt", true); // 追加数据 BufferedWriter bw = new BufferedWriter(fw);// a.public void write(int c):写一个字符出去 bw.write(98); bw.write('a'); bw.write('徐'); // 不会出问题了 bw.newLine(); // bw.write("\r\n"); // 换行//b.public void write(String c)写一个字符串出去 bw.write("abc我是中国人"); bw.newLine(); // bw.write("\r\n"); // 换行//c.public void write(char[] buffer):写一个字符数组出去 char[] chars = "abc我是中国人".toCharArray(); bw.write(chars); bw.newLine(); // bw.write("\r\n"); // 换行//d.public void write(String c ,int pos ,int len):写字符串的一部分出去 bw.write("abc我是中国人", 0, 5); bw.newLine(); // bw.write("\r\n"); // 换行//e.public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去 bw.write(chars, 3, 5); bw.newLine(); // bw.write("\r\n"); // 换行 // fw.flush();// 刷新后流可以继续使用 bw.close(); // 关闭包含刷线,关闭后流不能使用 }}
25.5 转换流
字符输入转换流InputStreamReader作用:
- 可以解决字符流读取不同编码乱码的问题
- public InputStreamReader(InputStream is,String
charset):可以指定编码把原始字节流转换成字符流,如此字符流中的字符不乱码。
25.5.1 字符输入转换流:InputStreamReader
作用:可以把原始的字节流按照指定编码转换成字符输入流。
构造器 | 说明 |
---|---|
public InputStreamReader(InputStream is) | 可以把原始的字节流按照代码默认编码转换成字符输入流。几乎不用,与默认的FileReader一样。 |
public InputStreamReader(InputStream is ,String charset) | 可以把原始的字节流按照指定编码转换成字符输入流,这样字符流中的字符就不乱码了(重点) |
/** 目标:字符输入转换流InputStreamReader的使用。 */public class InputStreamReaderDemo01 { public static void main(String[] args) throws Exception { // 代码UTF-8 文件 GBK "D:\\resources\\data.txt" // 1、提取GBK文件的原始字节流。 abc 我 //ooo oo InputStream is = new FileInputStream("D:\\resources\\data.txt"); // 2、把原始字节流转换成字符输入流 // Reader isr = new InputStreamReader(is); // 默认以UTF-8的方式转换成字符流。 还是会乱码的 跟直接使用FileReader是一样的 Reader isr = new InputStreamReader(is , "GBK"); // 以指定的GBK编码转换成字符输入流 完美的解决了乱码问题 BufferedReader br = new BufferedReader(isr); String line; while ((line = br.readLine()) != null){ System.out.println(line); } }}
25.5.1 字符输出转换流:OutputStreamWriter
作用:可以把字节输出流按照指定编码转换成字符输出流。
构造器 | 说明 |
---|---|
public OutputStreamWriter(OutputStream os) | 可以把原始的字节输出流按照代码默认编码转换成字符输出流。几乎不用。 |
public OutputStreamWriter(OutputStream os,String charset) | 可以把原始的字节输出流按照指定编码转换成字符输出流(重点) |
/** 目标:字符输出转换OutputStreamWriter流的使用。 */public class OutputStreamWriterDemo02 { public static void main(String[] args) throws Exception { // 1、定义一个字节输出流 OutputStream os = new FileOutputStream("io-app2/src/out03.txt"); // 2、把原始的字节输出流转换成字符输出流 // Writer osw = new OutputStreamWriter(os); // 以默认的UTF-8写字符出去 跟直接写FileWriter一样 Writer osw = new OutputStreamWriter(os , "GBK"); // 指定GBK的方式写字符出去 // 3、把低级的字符输出流包装成高级的缓冲字符输出流。 BufferedWriter bw = new BufferedWriter(osw); bw.write("我爱中国1~~"); bw.write("我爱中国2~~"); bw.write("我爱中国3~~"); bw.close(); }}
25.6 对象流
25.6.1 对象字节输出流:ObjectOutputStream
作用:以内存为基准,把内存中的对象存储到磁盘文件中去,称为对象序列化。
使用到的流是对象字节输出流:ObjectOutputStream
构造器 | 说明 |
---|---|
public ObjectOutputStream(OutputStream out) | 把低级字节输出流包装成高级的对象字节输出流 |
ObjectOutputStream序列化方法
方法名称 | 说明 |
---|---|
public final void writeObject(Object obj) | 把对象写出去到对象序列化流的文件中去 |
/*目标:学会对象序列化,使用 ObjectOutputStream 把内存中的对象存入到磁盘文件中。*/public class ObjectOutputStreamDemo1 { public static void main(String[] args) throws Exception { // 1、创建学生对象 Student s = new Student("陈磊", "chenlei","1314520", 21); // 2、对象序列化:使用对象字节输出流包装字节输出流管道 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("io-app2/src/obj.txt")); // 3、直接调用序列化方法 oos.writeObject(s); // 4、释放资源 oos.close(); System.out.println("序列化完成了~~"); }}
25.6.2 对象字节输出流:ObjectOutputStream
作用:以内存为基准,把存储到磁盘文件中去的对象数据恢复成内存中的对象,称为对象反序列化。
使用到的流是对象字节输入流:ObjectOutputStream
构造器 | 说明 |
---|---|
public ObjectInputStream(InputStream out) | 把低级字节输如流包装成高级的对象字节输入流 |
ObjectOutputStream序列化方法
方法名称 | 说明 |
---|---|
public Object readObject() | 把存储到磁盘文件中去的对象数据恢复成内存中的对象返回 |
/** 目标:学会进行对象反序列化:使用对象字节输入流把文件中的对象数据恢复成内存中的Java对象。 */public class ObjectInputStreamDemo2 { public static void main(String[] args) throws Exception { // 1、创建对象字节输入流管道包装低级的字节输入流管道 ObjectInputStream is = new ObjectInputStream(new FileInputStream("io-app2/src/obj.txt")); // 2、调用对象字节输入流的反序列化方法 Student s = (Student) is.readObject(); System.out.println(s); }}
25.7 打印流
作用:打印流可以实现方便、高效的打印数据到文件中去。打印流一般是指:PrintStream,PrintWriter两个类。
可以实现打印什么数据就是什么数据,例如打印整数97写出去就是97,打印boolean的true,写出去就是true。
1. PrintStream
构造器 | 说明 |
---|---|
public PrintWriter(OutputStream os) | 打印流直接通向字节输出流管道 |
public PrintStream(File f) | 打印流直接通向文件对象 |
public PrintStream(String filepath) | 打印流直接通向文件路径 |
方法 | 说明 |
---|---|
public void print(Xxx xx) | 打印任意类型的数据出去 |
2. PrintWriter
构造器 | 说明 |
---|---|
public PrintStream(OutputStream os) | 打印流直接通向字节输出流管道 |
public PrintWriter (Writer w) | 打印流直接通向字符输出流管道 |
public PrintWriter (File f) | 打印流直接通向文件对象 |
public PrintWriter (String filepath) | 打印流直接通向文件路径 |
方法 | 说明 |
---|---|
public void print(Xxx xx) | 打印任意类型的数据出去 |
PrintStream和PrintWriter的区别:
- 打印数据功能上是一模一样的,都是使用方便,性能高效(核心优势)
- PrintStream继承自字节输出流OutputStream,支持写字节数据的方法。
- PrintWriter继承自字符输出流Writer,支持写字符数据出去。
/** 目标:学会使用打印流 高效 方便写数据到文件。 */public class PrintDemo1 { public static void main(String[] args) throws Exception { // 1、创建一个打印流对象// PrintStream ps = new PrintStream(new FileOutputStream("io-app2/src/ps.txt"));// PrintStream ps = new PrintStream(new FileOutputStream("io-app2/src/ps.txt" , true)); // 追加数据,在低级管道后面加True// PrintStream ps = new PrintStream("io-app2/src/ps.txt" ); PrintWriter ps = new PrintWriter("io-app2/src/ps.txt"); // 打印功能上与PrintStream的使用没有区别 ps.println(97); ps.println('a'); ps.println(23.3); ps.println(true); ps.println("我是打印流输出的,我是啥就打印啥"); ps.close(); }}
25 多线程
- 线程(thread)是一个程序内部的一条执行路径。
- 我们之前启动程序执行后,main方法的执行其实就是一条单独的执行路径。
- 程序中如果只有一条执行路径,那么这个程序就是单线程的程序。
- 多线程是指从软硬件上实现多条执行流程的技术。
25.1 多线程的创建
Thread的构造器
构造器 | 说明 |
---|---|
public Thread(String name) | 可以为当前线程指定名称 |
public Thread(Runnable target) | 封装Runnable对象成为线程对象 |
public Thread(Runnable target ,String name ) | 封装Runnable对象成为线程对象,并指定线程名称 |
1. 继承Thread类
- 定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
- 创建MyThread类的对象
- 调用线程对象的start()方法启动线程(启动后还是执行run方法的)
优点:编码简单
缺点:线程类已经继承Thread,无法继承其他类,不利于扩展。
注意:
- 直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。
- 只有调用start方法才是启动一个新的线程执行。
/** 目标:多线程的创建方式一:继承Thread类实现。 */public class ThreadDemo1 { public static void main(String[] args) { // 3、new一个新线程对象 Thread t = new MyThread(); // 4、调用start方法启动线程(执行的还是run方法) t.start(); for (int i = 0; i < 5; i++) { System.out.println("主线程执行输出:" + i); } }}/** 1、定义一个线程类继承Thread类 */class MyThread extends Thread{ /**2、重写run方法,里面是定义线程以后要干啥 */ @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("子线程执行输出:" + i); } }}
2. 实现Runnable接口
- 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
- 创建MyRunnable任务对象
- 把MyRunnable任务对象交给Thread处理。
- 调用线程对象的start()方法启动线程
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。
/** 目标:学会线程的创建方式二,理解它的优缺点。 */public class ThreadDemo2 { public static void main(String[] args) { // 3、创建一个任务对象 Runnable target = new MyRunnable(); // 4、把任务对象交给Thread处理 Thread t = new Thread(target); // Thread t = new Thread(target, "1号"); // 5、启动线程 t.start(); for (int i = 0; i < 10; i++) { System.out.println("主线程执行输出:" + i); } }}/** 1、定义一个线程任务类 实现Runnable接口 */class MyRunnable implements Runnable { /**2、重写run方法,定义线程的执行任务的 */ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("子线程执行输出:" + i); } }}
3. JDK 5.0新增:实现Callable接口
前2种线程创建方式都存在一个问题:
- 他们重写的run方法均不能直接返回结果。
- 不适合需要返回线程执行结果的业务场景。
怎么解决这个问题呢?
- JDK 5.0提供了Callable和FutureTask来实现。
- 这种方式的优点是:可以得到线程执行的结果。
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
可以在线程执行完毕后去获取线程执行的结果。
缺点:编码复杂一点。
利用Callable、FutureTask接口实现。
/** 目标:学会线程的创建方式三:实现Callable接口,结合FutureTask完成。 */public class ThreadDemo3 { public static void main(String[] args) { // 3、创建Callable任务对象 Callable<String> call = new MyCallable(100); // 4、把Callable任务对象 交给 FutureTask 对象 // FutureTask对象的作用1: 是Runnable的对象(实现了Runnable接口),可以交给Thread了 // FutureTask对象的作用2: 可以在线程执行完毕之后通过调用其get方法得到线程执行完成的结果 FutureTask<String> f1 = new FutureTask<>(call); // 5、交给线程处理 Thread t1 = new Thread(f1); // 6、启动线程 t1.start(); Callable<String> call2 = new MyCallable(200); FutureTask<String> f2 = new FutureTask<>(call2); Thread t2 = new Thread(f2); t2.start(); try { // 如果f1任务没有执行完毕,这里的代码会等待,直到线程1跑完才提取结果。 String rs1 = f1.get(); System.out.println("第一个结果:" + rs1); } catch (Exception e) { e.printStackTrace(); } try { // 如果f2任务没有执行完毕,这里的代码会等待,直到线程2跑完才提取结果。 String rs2 = f2.get(); System.out.println("第二个结果:" + rs2); } catch (Exception e) { e.printStackTrace(); } }}/** 1、定义一个任务类 实现Callable接口 应该申明线程任务执行完毕后的结果的数据类型 */class MyCallable implements Callable<String>{ private int n; public MyCallable(int n) { this.n = n; } /**2、重写call方法(任务方法) */ @Override public String call() throws Exception { int sum = 0; for (int i = 1; i <= n ; i++) { sum += i; } return "子线程执行的结果是:" + sum; }}
4. 3三种方式的区别
方法 | 优点 | 缺点 |
---|---|---|
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 扩展性较差,不能再继承其他的类,不能返回线程执行的结果 |
实现Runnable接口 | 扩展性强,实现该接口的同时还可以继承其他的类。 | 编程相对复杂,不能返回线程执行的结果 |
实现Callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类。可以得到线程执行的结果 | 编程相对复杂 |
5. 匿名内部类形式的线程创建
/** * 使用匿名内部类完成线程的两种创建 */public class ThreadDemo3 { public static void main(String[] args) { Thread t1 = new Thread(){ public void run(){ for(int i=0;i<1000;i++){ System.out.println("你是谁啊?"); } } };// Runnable r2 = new Runnable() {// public void run() {// for(int i=0;i<1000;i++){// System.out.println("我是查水表的!");// }// }// }; //Runnable可以使用lambda表达式创建 Runnable r2 = ()->{ for(int i=0;i<1000;i++){ System.out.println("我是查水表的!"); } }; Thread t2 = new Thread(r2); t1.start(); t2.start(); }}
25.2 线程同步
25.3.1 同步代码块
原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。
作用:把出现线程安全问题的核心代码给上锁。
锁对象的规范要求
- 规范上:建议使用共享资源作为锁对象。
- 对于实例方法建议使用this作为锁对象。
- 对于静态方法建议使用字节码(类名.class)对象作为锁对象。
格式:
synchronized(同步锁对象) {操作共享资源的代码(核心代码)}
/** * 同步块 * 同步块可以更准确的锁定需要同步执行的代码片段,有效的缩小排队范围可以在保证安全的前提下 * 尽可能的提高并发效率。 * 语法: * synchronized(同步监视器对象){ * 需要多个线程同步执行的代码片段 * } * * 同步执行:多个线程执行时有先后顺序 */public class SyncDemo2 { public static void main(String[] args) { Shop shop = new Shop(); Thread t1 = new Thread(){ public void run(){ shop.buy(); } }; Thread t2 = new Thread(){ public void run(){ shop.buy(); } }; t1.start(); t2.start(); }}class Shop { /* 在方法上使用synchronized时,同步监视器对象就是当前方法的所属对象,即:this */// public synchronized void buy(){ public void buy() { try { Thread t = Thread.currentThread();//获取运行buy方法的线程 System.out.println(t.getName()+":"+"正在挑衣服..."); Thread.sleep(5000); /* 同步块使用时需要指定一个同步监视器对象,即:上锁的对象 该对象从语法的角度来讲可以是任意引用类型的实例,但是必须同时满足多个需要同步(排队) 执行该代码片段的线程看到的是同一个对象才行! */// synchronized (new Object()) {//无效的 synchronized (this) { System.out.println(t.getName() + ":正在试衣服..."); Thread.sleep(5000); } System.out.println(t.getName()+":结账离开"); } catch (InterruptedException e) { e.printStackTrace(); } finally { } }}
25.3.2 同步方法
作用:把出现线程安全问题的核心方法给上锁。
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。
格式:
修饰符 synchronized 返回值类型 方法名称(形参列表) {操作共享资源的代码}
package thread;/** * 静态方法上如果使用synchronized,那么该方法一定是同步的。 * * 静态方法上指定的锁对象为当前类的类对象,即Class类的实例。 * 在JVM中每个被加载的类都有且只有一个Class的实例与之对应,它称为一个类的类对象。 * */public class SyncDemo3 { public static void main(String[] args) { Thread t1 = new Thread(){ public void run(){ Boo.dosome(); } }; Thread t2 = new Thread(){ public void run(){ Boo.dosome(); } }; t1.start(); t2.start(); }}class Boo{ public synchronized static void dosome(){// public static void dosome(){ //静态方法中使用同步块时,也可以指定类对象,方式为:类名.class try { Thread t = Thread.currentThread(); System.out.println(t.getName() + ":正在执行dosome方法..."); Thread.sleep(5000); System.out.println(t.getName() + ":执行dosome方法完毕!"); } catch (InterruptedException e) { } }}
25.3.3 Lock锁
- 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便。
- Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。
- Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象。
|方法名称|说明 |
|–|–|
| public ReentrantLock() | 获得Lock锁的实现类对象 |
Lock的API:
方法名称 | 说明 |
---|---|
void lock() | 获得锁 |
void unlock() | 释放锁 |
public class sellticks implements Runnable{ private static int tickets=100; private Lock lock=new ReentrantLock(); @Override public void run() { while (true){ try { lock.lock(); if(tickets>0) { Thread.sleep(200); System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票"); tickets--; } } catch (InterruptedException e) {e.printStackTrace(); } finally { lock.unlock(); } } }}public class sellsticks_test { public static void main(String[] args) { sellticks st = new sellticks(); Thread th1 = new Thread(st,"窗口一"); Thread th2 = new Thread(st,"窗口二"); Thread th3 = new Thread(st,"窗口三"); th1.start(); th2.start(); th3.start(); }}
25.3 有关方法
方法名称 | 说明 |
---|---|
string getName() | 获取当前线程的名称,默认线程名称是Thread-索引 |
void setName(String name) | 设量线程名称 |
public static Thread curretThread(): | 返回对当前正在执行的线程对象的引用 |
public static void sleep( long time) | 让线程休眠指定的时间,单位为毫秒。 |
public void run ( ) | 线程任务方法 |
public void start() | 线程启动方法 |
public static void sleep(long time) | 让当前线程休眠指定的时间后再继续执行,单位为毫秒 |
/** * 获取线程相关信息的一组方法 */public class ThreadInfoDemo { public static void main(String[] args) { Thread main = Thread.currentThread();//获取主线程 String name = main.getName();//获取线程的名字 System.out.println("名字:"+name); long id = main.getId();//获取该线程的唯一标识 System.out.println("id:"+id); int priority = main.getPriority();//获取该线程的优先级 System.out.println("优先级:"+priority); boolean isAlive = main.isAlive();//该线程是否活着 System.out.println("是否活着:"+isAlive); boolean isDaemon = main.isDaemon();//是否为守护线程 System.out.println("是否为守护线程:"+isDaemon); boolean isInterrupted = main.isInterrupted();//是否被中断了 System.out.println("是否被中断了:"+isInterrupted); }}
sleep阻塞
public class SleepDemo { public static void main(String[] args) { System.out.println("程序开始了!"); try { Thread.sleep(5000);//主线程阻塞5秒钟 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("程序结束了!"); }}
25.4 多线程并发安全问题
当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作临界资源的顺序出现混乱严重时可能导致系统瘫痪.
临界资源:操作该资源的全过程同时只能被单个线程完成。
/** * 多线程并发安全问题 * 当多个线程并发操作同一临界资源,由于线程切换的时机不确定,导致操作顺序出现 * 混乱,严重时可能导致系统瘫痪。 * 临界资源:同时只能被单一线程访问操作过程的资源。 */public class SyncDemo { public static void main(String[] args) { Table table = new Table(); Thread t1 = new Thread(){ public void run(){ while(true){ int bean = table.getBean(); Thread.yield(); System.out.println(getName()+":"+bean); } } }; Thread t2 = new Thread(){ public void run(){ while(true){ int bean = table.getBean(); /* static void yield() 线程提供的这个静态方法作用是让执行该方法的线程 主动放弃本次时间片。 这里使用它的目的是模拟执行到这里CPU没有时间了,发生 线程切换,来看并发安全问题的产生。*/ Thread.yield(); System.out.println(getName()+":"+bean); } } }; t1.start(); t2.start(); }}class Table{ private int beans = 20;//桌子上有20个豆子 public int getBean(){ if(beans==0){ throw new RuntimeException("没有豆子了!"); } Thread.yield(); return beans--; }}
25.5 守护线程
守护线程也称为:后台线程
- 守护线程是通过普通线程调用setDaemon(boolean on)方法设置而来的,因此创建上与普通线程无异.
- 守护线程的结束时机上有一点与普通线程不同,即:进程的结束.
- 进程结束:当一个进程中的所有普通线程都结束时,进程就会结束,此时会杀掉所有正在运行的守护线程.
/** * 守护线程 * 守护线程是通过普通线程调用setDaemon(true)设置而转变的。因此守护线程创建上 * 与普通线程无异。 * 但是结束时机上有一点不同:进程结束。 * 当一个java进程中的所有普通线程都结束时,该进程就会结束,此时会强制杀死所有正在 * 运行的守护线程。 */public class DaemonThreadDemo { public static void main(String[] args) { Thread rose = new Thread(){ public void run(){ for(int i=0;i<5;i++){ System.out.println("rose:let me go!"); try { Thread.sleep(1000); } catch (InterruptedException e) { } } System.out.println("rose:啊啊啊啊啊啊AAAAAAAaaaaa...."); System.out.println("噗通"); } }; Thread jack = new Thread(){ public void run(){ while(true){ System.out.println("jack:you jump!i jump!"); try { Thread.sleep(1000); } catch (InterruptedException e) { } } } }; rose.start(); jack.setDaemon(true);//设置守护线程必须在线程启动前进行 jack.start(); }}
注意:
通常当我们不关心某个线程的任务什么时候停下来,它可以一直运行,但是程序主要的工作都结束时它应当跟着结束时,这样的任务就适合放在守护线程上执行.比如GC就是在守护线程上运行的.
25.6 互斥锁
当多个线程执行不同的代码片段,但是这些代码片段之间不能同时运行时就要设置为互斥的.
使用synchronized锁定多个代码片段,并且指定的同步监视器是同一个时,这些代码片段之间就是互斥的。
/** * 互斥锁 * 当使用synchronized锁定多个不同的代码片段,并且指定的同步监视器对象相同时, * 这些代码片段之间就是互斥的,即:多个线程不能同时访问这些方法。 */public class SyncDemo4 { public static void main(String[] args) { Foo foo = new Foo(); Thread t1 = new Thread(){ public void run(){ foo.methodA(); } }; Thread t2 = new Thread(){ public void run(){ foo.methodB(); } }; t1.start(); t2.start(); }}class Foo{ public synchronized void methodA(){ Thread t = Thread.currentThread(); try { System.out.println(t.getName()+":正在执行A方法..."); Thread.sleep(5000); System.out.println(t.getName()+":执行A方法完毕!"); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void methodB(){ Thread t = Thread.currentThread(); try { System.out.println(t.getName()+":正在执行B方法..."); Thread.sleep(5000); System.out.println(t.getName()+":执行B方法完毕!"); } catch (InterruptedException e) { e.printStackTrace(); } }}
25.7 死锁
死锁的产生:
两个线程各自持有一个锁对象的同时等待对方先释放锁对象,此时会出现僵持状态。这个现象就是死锁。
/** * 死锁 * 死锁的产生: * 两个线程各自持有一个锁对象的同时等待对方先释放锁对象,此时会出现僵持状态。 * 这个现象就是死锁。 */public class DeadLockDemo { //定义两个锁对象,"筷子"和"勺" public static Object chopsticks = new Object(); public static Object spoon = new Object(); public static void main(String[] args) { Thread np = new Thread(){ public void run(){ System.out.println("北方人开始吃饭."); System.out.println("北方人去拿筷子..."); synchronized (chopsticks){ System.out.println("北方人拿起了筷子开始吃饭..."); try { Thread.sleep(5000); } catch (InterruptedException e) { } System.out.println("北方人吃完了饭,去拿勺..."); synchronized (spoon){ System.out.println("北方人拿起了勺子开始喝汤..."); try {Thread.sleep(5000); } catch (InterruptedException e) { } System.out.println("北方人喝完了汤"); } System.out.println("北方人放下了勺"); } System.out.println("北方人放下了筷子,吃饭完毕!"); } }; Thread sp = new Thread(){ public void run(){ System.out.println("南方人开始吃饭."); System.out.println("南方人去拿勺..."); synchronized (spoon){ System.out.println("南方人拿起了勺开始喝汤..."); try { Thread.sleep(5000); } catch (InterruptedException e) { } System.out.println("南方人喝完了汤,去拿筷子..."); synchronized (chopsticks){ System.out.println("南方人拿起了筷子开始吃饭..."); try {Thread.sleep(5000); } catch (InterruptedException e) { } System.out.println("南方人吃完了饭"); } System.out.println("南方人放下了筷子"); } System.out.println("南方人放下了勺,吃饭完毕!"); } }; np.start(); sp.start(); }}
25.8 解决死锁
/** * 解决死锁: * 1:尽量避免在持有一个锁的同时去等待持有另一个锁(避免synchronized嵌套) * 2:当无法避免synchronized嵌套时,就必须保证多个线程锁对象的持有顺序必须一致。 * 即:A线程在持有锁1的过程中去持有锁2时,B线程也要以这样的持有顺序进行。 */public class DeadLockDemo2 { //筷子 private static Object chopsticks = new Object(); //勺 private static Object spoon = new Object(); public static void main(String[] args) { //北方人 Thread np = new Thread(){ public void run(){ try { System.out.println("北方人:开始吃饭"); System.out.println("北方人去拿筷子..."); synchronized (chopsticks) { System.out.println("北方人拿起了筷子,开始吃饭..."); Thread.sleep(5000); } System.out.println("北方人吃完了饭,放下了筷子"); System.out.println("北方人去拿勺子..."); synchronized (spoon){System.out.println("北方人拿起了勺子,开始喝汤...");Thread.sleep(5000); } System.out.println("北方人喝完了汤,北方人放下了勺子"); System.out.println("吃饭完毕。"); } catch (InterruptedException e) { e.printStackTrace(); } } }; //南方人 Thread sp = new Thread(){ public void run(){ try { System.out.println("南方人:开始吃饭"); System.out.println("南方人去拿勺..."); synchronized (spoon) { System.out.println("南方人拿起了勺,开始喝汤..."); Thread.sleep(5000); } System.out.println("南方人喝完了汤,放下勺子..."); System.out.println("南方人去拿筷子..."); synchronized (chopsticks){ System.out.println("南方人拿起了筷子,开始吃饭..."); Thread.sleep(5000); } System.out.println("南方人吃完了饭,南方人放下了筷子"); System.out.println("吃饭完毕。"); } catch (InterruptedException e) { e.printStackTrace(); } } }; np.start(); sp.start(); }}
26 网络编程
26.1 Socket
构造器 | 说明 |
---|---|
public Socket(String host , int port) | 创建发送端的Socket对象与服务端连接,参数为服务端程序的ip和端口。 |
Socket构造方法
方法 | 说明 |
---|---|
OutputStream getOutputStream() | 获得字节输出流对象 |
InputStream getInputStream() | 获得字节输入流对象 |
26.2 实现客户端
客户端实现步骤
- 创建客户端的Socket对象,请求与服务端的连接。
- 使用socket对象调用getOutputStream()方法得到字节输出流。
- 使用字节输出流完成数据的发送。
- 释放资源:关闭socket管道。
/** 目标:完成Socket网络编程入门案例的客户端开发,实现1发1收。 */public class ClientDemo1 { public static void main(String[] args) { try { System.out.println("====客户端启动==="); // 1、创建Socket通信管道请求有服务端的连接 // public Socket(String host, int port) // 参数一:服务端的IP地址 // 参数二:服务端的端口 Socket socket = new Socket("127.0.0.1", 7777); // 2、从socket通信管道中得到一个字节输出流 负责发送数据 OutputStream os = socket.getOutputStream(); // 3、把低级的字节流包装成打印流 PrintStream ps = new PrintStream(os); // 4、发送消息 ps.println("我是TCP的客户端,我已经与你对接,并发出邀请:约吗?"); ps.flush(); // 关闭资源。 // socket.close(); } catch (Exception e) { e.printStackTrace(); } }}
26.3 实现服务端
ServerSocket(服务端)
构造器 | 说明 |
---|---|
public ServerSocket(int port) | 注册服务端端口 |
方法 | 说明 |
---|---|
public Socket accept() | 等待接收客户端的Socket通信连接,连接成功返回Socket对象与客户端建立端到端通信 |
/** 目标:开发Socket网络编程入门代码的服务端,实现接收消息 */public class ServerDemo2 { public static void main(String[] args) { try { System.out.println("===服务端启动成功==="); // 1、注册端口 ServerSocket serverSocket = new ServerSocket(7777); // 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道 Socket socket = serverSocket.accept(); // 3、从socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); // 4、把字节输入流包装成缓冲字符输入流进行消息的接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 5、按照行读取消息 String msg; if ((msg = br.readLine()) != null){ System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg); } } catch (Exception e) { e.printStackTrace(); } }}
26.4 实现多发多收消息
客户端:
/** 目标:实现多发和多收 */public class ClientDemo1 { public static void main(String[] args) { try { System.out.println("====客户端启动==="); // 1、创建Socket通信管道请求有服务端的连接 // public Socket(String host, int port) // 参数一:服务端的IP地址 // 参数二:服务端的端口 Socket socket = new Socket("127.0.0.1", 7777); // 2、从socket通信管道中得到一个字节输出流 负责发送数据 OutputStream os = socket.getOutputStream(); // 3、把低级的字节流包装成打印流 PrintStream ps = new PrintStream(os); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请说:"); String msg = sc.nextLine(); // 4、发送消息 ps.println(msg); ps.flush(); } // 关闭资源。 // socket.close(); } catch (Exception e) { e.printStackTrace(); } }}
服务端:
/** 目标:开发Socket网络编程入门代码的服务端,实现接收消息 */public class ServerDemo2 { public static void main(String[] args) { try { System.out.println("===服务端启动成功==="); // 1、注册端口 ServerSocket serverSocket = new ServerSocket(7777); while (true) { // 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道 Socket socket = serverSocket.accept(); // 3、从socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); // 4、把字节输入流包装成缓冲字符输入流进行消息的接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 5、按照行读取消息 String msg; while ((msg = br.readLine()) != null){ System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg); } } } catch (Exception e) { e.printStackTrace(); } }}
26.5 同时接受多个客户端消息
客户端:
/** 目标:实现服务端可以同时处理多个客户端的消息。 */public class ClientDemo1 { public static void main(String[] args) { try { System.out.println("====客户端启动==="); // 1、创建Socket通信管道请求有服务端的连接 // public Socket(String host, int port) // 参数一:服务端的IP地址 // 参数二:服务端的端口 Socket socket = new Socket("127.0.0.1", 7777); // 2、从socket通信管道中得到一个字节输出流 负责发送数据 OutputStream os = socket.getOutputStream(); // 3、把低级的字节流包装成打印流 PrintStream ps = new PrintStream(os); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请说:"); String msg = sc.nextLine(); // 4、发送消息 ps.println(msg); ps.flush(); } // 关闭资源。 // socket.close(); } catch (Exception e) { e.printStackTrace(); } }}
服务端:
/** 目标:实现服务端可以同时处理多个客户端的消息。 */public class ServerDemo2 { public static void main(String[] args) { try { System.out.println("===服务端启动成功==="); // 1、注册端口 ServerSocket serverSocket = new ServerSocket(7777); // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。 while (true) { // 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息 Socket socket = serverSocket.accept(); System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!"); // 3、开始创建独立线程处理socket new ServerReaderThread(socket).start(); } } catch (Exception e) { e.printStackTrace(); } }}
ServerReaderThread类:
public class ServerReaderThread extends Thread{ private Socket socket; public ServerReaderThread(Socket socket){ this.socket = socket; } @Override public void run() { try { // 3、从socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); // 4、把字节输入流包装成缓冲字符输入流进行消息的接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 5、按照行读取消息 String msg; while ((msg = br.readLine()) != null){ System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg); } } catch (Exception e) { System.out.println(socket.getRemoteSocketAddress() + "下线了!!!"); } }}
26.6 使用线程池优化
线程池的优势在哪里?
- 服务端可以复用线程处理多个客户端,可以避免系统瘫痪。
- 适合客户端通信时长较短的场景。
客户端:
/** 拓展:使用线程池优化:实现通信。 */public class ClientDemo1 { public static void main(String[] args) { try { System.out.println("====客户端启动==="); // 1、创建Socket通信管道请求有服务端的连接 // public Socket(String host, int port) // 参数一:服务端的IP地址 // 参数二:服务端的端口 Socket socket = new Socket("127.0.0.1", 6666); // 2、从socket通信管道中得到一个字节输出流 负责发送数据 OutputStream os = socket.getOutputStream(); // 3、把低级的字节流包装成打印流 PrintStream ps = new PrintStream(os); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请说:"); String msg = sc.nextLine(); // 4、发送消息 ps.println(msg); ps.flush(); } // 关闭资源。 // socket.close(); } catch (Exception e) { e.printStackTrace(); } }}
服务端:
/** 目标:使用线程池优化:实现通信。 */public class ServerDemo2 { // 使用静态变量记住一个线程池对象 private static ExecutorService pool = new ThreadPoolExecutor(300, 1500, 6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2) , Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); public static void main(String[] args) { try { System.out.println("===服务端启动成功==="); // 1、注册端口 ServerSocket serverSocket = new ServerSocket(6666); // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。 while (true) { // 2、每接收到一个客户端的Socket管道, Socket socket = serverSocket.accept(); System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!"); // 任务对象负责读取消息。 Runnable target = new ServerReaderRunnable(socket); pool.execute(target); } } catch (Exception e) { e.printStackTrace(); } }}
ServerReaderRunnable类:
public class ServerReaderRunnable implements Runnable{ private Socket socket; public ServerReaderRunnable(Socket socket){ this.socket = socket; } @Override public void run() { try { // 3、从socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); // 4、把字节输入流包装成缓冲字符输入流进行消息的接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 5、按照行读取消息 String msg; while ((msg = br.readLine()) != null){ System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg); } } catch (Exception e) { System.out.println(socket.getRemoteSocketAddress() + "下线了!!!"); } }}
26.7 即时通信
- 即时通信,是指一个客户端的消息发出去,其他客户端可以接收到
- 即时通信需要进行端口转发的设计思想。
- 服务端需要把在线的Socket管道存储起来
- 一旦收到一个消息要推送给其他管道
客户端:
/** 拓展:即时通信 客户端:发消息的同时,随时有人发消息过来。 服务端:接收消息后,推送给其他所有的在线socket */public class ClientDemo1 { public static void main(String[] args) { try { System.out.println("====客户端启动==="); // 1、创建Socket通信管道请求有服务端的连接 // public Socket(String host, int port) // 参数一:服务端的IP地址 // 参数二:服务端的端口 Socket socket = new Socket("127.0.0.1", 6868); // 马上为客户端分配一个独立的线程负责读取它收到的消息 new ClientReaderThread(socket).start(); // 2、从socket通信管道中得到一个字节输出流 负责发送数据 OutputStream os = socket.getOutputStream(); // 3、把低级的字节流包装成打印流 PrintStream ps = new PrintStream(os); Scanner sc = new Scanner(System.in); while (true) { System.out.println("请说:"); String msg = sc.nextLine(); // 4、发送消息 ps.println(msg); ps.flush(); } // 关闭资源。 // socket.close(); } catch (Exception e) { e.printStackTrace(); } }}
服务端:
/** 目标: 即时通信 */public class ServerDemo2 { public static List<Socket> onLineSockets = new ArrayList<>(); public static void main(String[] args) { try { System.out.println("===服务端启动成功==="); // 1、注册端口 ServerSocket serverSocket = new ServerSocket(6868); // a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。 while (true) { // 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息 Socket socket = serverSocket.accept(); System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!"); // 把当前客户端管道Socket加入到在线集合中去 onLineSockets.add(socket); // 3、开始创建独立线程处理socket new ServerReaderThread(socket).start(); } } catch (Exception e) { e.printStackTrace(); } }}
ClientReaderThread类:
public class ClientReaderThread extends Thread{ private Socket socket; public ClientReaderThread(Socket socket){ this.socket = socket; } @Override public void run() { try { // 3、从socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); // 4、把字节输入流包装成缓冲字符输入流进行消息的接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 5、按照行读取消息 String msg; while ((msg = br.readLine()) != null){ System.out.println(socket.getRemoteSocketAddress() + "收到了: " + msg); } } catch (Exception e) { System.out.println("服务端把你踢出去了~~"); } }}
ServerReaderThread类:
public class ServerReaderThread extends Thread{ private Socket socket; public ServerReaderThread(Socket socket){ this.socket = socket; } @Override public void run() { try { // 3、从socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); // 4、把字节输入流包装成缓冲字符输入流进行消息的接收 BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 5、按照行读取消息 String msg; while ((msg = br.readLine()) != null){ System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg); // 把这个消息发给当前所有在线socket sendMsgToAll(msg); } } catch (Exception e) { System.out.println(socket.getRemoteSocketAddress() + "下线了!!!"); // 从在线集合中抹掉本客户端socket ServerDemo2.onLineSockets.remove(socket); } } private void sendMsgToAll(String msg) { try { // 遍历全部的在线 socket给他们发消息 for (Socket onLineSocket : ServerDemo2.onLineSockets) { // 除了自己的socket,其他socket我都发!! if(onLineSocket != socket){ PrintStream ps = new PrintStream(socket.getOutputStream()); ps.println(msg); ps.flush(); } } } catch (Exception e) { e.printStackTrace(); } }}
27 集合
27.1 集合概述
与数组的区别:
大小:
- 数组定义后类型确定,长度固定
- 集合类型可以不固定,大小是可变的。
类型:
- 数组可以存储基本类型和引用类型的数据。
- 集合只能存储引用数据类型的数据。
场景:
- 数组适合做数据个数和类型确定的场景。
- 集合适合做数据个数不确定,且要做增删元素的场景。
27.2 集合体系特点
Collection集合特点:
List系列集合:添加的元素是有序、可重复、有索引。
- ArrayList、LinekdList :有序、可重复、有索引。
Set系列集合:添加的元素是无序、不重复、无索引。
- HashSet: 无序、不重复、无索引;LinkedHashSet: 有序、不重复、无索引。
- TreeSet:按照大小默认升序排序、不重复、无索引。
/** 目标:明确Collection集合体系的特点 */public class CollectionDemo1 { public static void main(String[] args) { // 有序 可重复 有索引 Collection list = new ArrayList(); list.add("Java"); list.add("Java"); list.add("Mybatis"); list.add(23); list.add(23); list.add(false); list.add(false); System.out.println(list); // 无序 不重复 无索引 Collection list1 = new HashSet(); list1.add("Java"); list1.add("Java"); list1.add("Mybatis"); list1.add(23); list1.add(23); list1.add(false); list1.add(false); System.out.println(list1); System.out.println("-----------------------------"); // Collection list2 = new ArrayList(); Collection<String> list2 = new ArrayList<>(); // JDK 7开始之后后面类型申明可以不写 list2.add("Java"); // list2.add(23); list2.add("唐三"); // 集合和泛型不支持基本数据类型,只能支持引用数据类型 // Collection list3 = new ArrayList(); Collection<Integer> list3 = new ArrayList<>(); list3.add(23); list3.add(233); list3.add(2333); Collection<Double> list4 = new ArrayList<>(); list4.add(23.4); list4.add(233.0); list4.add(233.3); }}
27.3 Collection的常用方法
方法名称 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数。 |
public Object[] toArray() | 把集合中的元素,存储到数组中 |
/** 目标:Collection集合的常用API. Collection是集合的祖宗类,它的功能是全部集合都可以继承使用的,所以要学习它。 Collection API如下: - public boolean add(E e): 把给定的对象添加到当前集合中 。 - public void clear() :清空集合中所有的元素。 - public boolean remove(E e): 把给定的对象在当前集合中删除。 - public boolean contains(Object obj): 判断当前集合中是否包含给定的对象。 - public boolean isEmpty(): 判断当前集合是否为空。 - public int size(): 返回集合中元素的个数。 - public Object[] toArray(): 把集合中的元素,存储到数组中。 小结: 记住以上API。 */public class CollectionDemo { public static void main(String[] args) { // HashSet:添加的元素是无序,不重复,无索引。 Collection<String> c = new ArrayList<>(); // 1.添加元素, 添加成功返回true。 c.add("Java"); c.add("HTML"); System.out.println(c.add("HTML")); c.add("MySQL"); c.add("Java"); System.out.println(c.add("唐三")); System.out.println(c); // [Java, HTML, HTML, MySQL, Java, 唐三] // 2.清空集合的元素。 // c.clear(); // System.out.println(c); // 3.判断集合是否为空 是空返回true,反之。 // System.out.println(c.isEmpty()); // 4.获取集合的大小。 System.out.println(c.size()); // 5.判断集合中是否包含某个元素。 System.out.println(c.contains("Java")); // true System.out.println(c.contains("java")); // false System.out.println(c.contains("唐三")); // true // 6.删除某个元素:如果有多个重复元素默认删除前面的第一个! System.out.println(c.remove("java")); // false System.out.println(c); System.out.println(c.remove("Java")); // true System.out.println(c); // 7.把集合转换成数组 [HTML, HTML, MySQL, Java, 唐三] Object[] arrs = c.toArray(); System.out.println("数组:" + Arrays.toString(arrs)); System.out.println("----------------------拓展----------------------"); Collection<String> c1 = new ArrayList<>(); c1.add("java1"); c1.add("java2"); Collection<String> c2 = new ArrayList<>(); c2.add("赵敏"); c2.add("殷素素"); // addAll把c2集合的元素全部倒入到c1中去。 c1.addAll(c2); System.out.println(c1); System.out.println(c2); }}
27.4 集合的遍历方式
27.4.1 方式一:迭代器
Collection集合获取迭代器:
方法名称 | 说明 |
---|---|
Iterator iterator() | 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引 |
Iterator中的常用方法:
方法名称 | 说明 |
---|---|
boolean hasNext() | 询问当前位置是否有元素存在,存在返回true ,不存在返回false |
E next() | 获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界 |
/** 目标:Collection集合的遍历方式。 什么是遍历? 为什么开发中要遍历? 遍历就是一个一个的把容器中的元素访问一遍。 开发中经常要统计元素的总和,找最值,找出某个数据然后干掉等等业务都需要遍历。 Collection集合的遍历方式是全部集合都可以直接使用的,所以我们学习它。 Collection集合的遍历方式有三种: (1)迭代器。 (2)foreach(增强for循环)。 (3)JDK 1.8开始之后的新技术Lambda表达式(了解) a.迭代器遍历集合。 -- 方法: public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的 boolean hasNext():判断是否有下一个元素,有返回true ,反之。 E next():获取下一个元素值! --流程: 1.先获取当前集合的迭代器 Iterator it = lists.iterator(); 2.定义一个while循环,问一次取一次。通过it.hasNext()询问是否有下一个元素,有就通过it.next()取出下一个元素。 小结: 记住代码。 */public class CollectionDemo01 { public static void main(String[] args) { ArrayList<String> lists = new ArrayList<>(); lists.add("赵敏"); lists.add("小昭"); lists.add("素素"); lists.add("灭绝"); System.out.println(lists); // [赵敏, 小昭, 素素, 灭绝] // it // 1、得到当前集合的迭代器对象。 Iterator<String> it = lists.iterator();// String ele = it.next();// System.out.println(ele);// System.out.println(it.next());// System.out.println(it.next());// System.out.println(it.next()); // System.out.println(it.next()); // NoSuchElementException 出现无此元素异常的错误 // 2、定义while循环 while (it.hasNext()){ String ele = it.next(); System.out.println(ele); } System.out.println("-----------------------------"); }}
27.4.2 方式二:foreach/增强for循环
/** 目标:Collection集合的遍历方式。 什么是遍历? 为什么开发中要遍历? 遍历就是一个一个的把容器中的元素访问一遍。 开发中经常要统计元素的总和,找最值,找出某个数据然后干掉等等业务都需要遍历。 Collection集合的遍历方式是全部集合都可以直接使用的,所以我们学习它。 Collection集合的遍历方式有三种: (1)迭代器。 (2)foreach(增强for循环)。 (3)JDK 1.8开始之后的新技术Lambda表达式。 b.foreach(增强for循环)遍历集合。 foreach是一种遍历形式,可以遍历集合或者数组。 foreach遍历集合实际上是迭代器遍历集合的简化写法。 foreach遍历的关键是记住格式: for(被遍历集合或者数组中元素的类型 变量名称 : 被遍历集合或者数组){ } */public class CollectionDemo02 { public static void main(String[] args) { Collection<String> lists = new ArrayList<>(); lists.add("赵敏"); lists.add("小昭"); lists.add("殷素素"); lists.add("周芷若"); System.out.println(lists); // [赵敏, 小昭, 殷素素, 周芷若] // ele for (String ele : lists) { System.out.println(ele); } System.out.println("------------------"); double[] scores = {100, 99.5 , 59.5}; for (double score : scores) { System.out.println(score);// if(score == 59.5){// score = 100.0; // 修改无意义,不会影响数组的元素值。// } } System.out.println(Arrays.toString(scores)); }}
27.4.3 方式三:lambda表达式
/** 目标:Collection集合的遍历方式。 什么是遍历? 为什么开发中要遍历? 遍历就是一个一个的把容器中的元素访问一遍。 开发中经常要统计元素的总和,找最值,找出某个数据然后干掉等等业务都需要遍历。 Collection集合的遍历方式是全部集合都可以直接使用的,所以我们学习它。 Collection集合的遍历方式有三种: (1)迭代器。 (2)foreach(增强for循环)。 (3)JDK 1.8开始之后的新技术Lambda表达式。 c.JDK 1.8开始之后的新技术Lambda表达式。 */public class CollectionDemo03 { public static void main(String[] args) { Collection<String> lists = new ArrayList<>(); lists.add("赵敏"); lists.add("小昭"); lists.add("殷素素"); lists.add("周芷若"); System.out.println(lists); // [赵敏, 小昭, 殷素素, 周芷若] // s lists.forEach(new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } });// lists.forEach(s -> {// System.out.println(s);// }); // lists.forEach(s -> System.out.println(s) ); lists.forEach(System.out::println ); }}
27.5 集合存储自定义类型的对象
注意 :集合中存储的是元素对象的地址。
public class TestDemo { public static void main(String[] args) { // 1、定义一个电影类 // 2、定义一个集合对象存储3部电影对象 Collection<Movie> movies = new ArrayList<>(); movies.add(new Movie("《你好,李焕英》", 9.5, "张小斐,贾玲,沈腾,陈赫")); movies.add(new Movie("《唐人街探案》", 8.5, "王宝强,刘昊然,美女")); movies.add(new Movie("《刺杀小说家》",8.6, "雷佳音,杨幂")); System.out.println(movies); // 3、遍历集合容器中的每个电影对象 for (Movie movie : movies) { System.out.println("片名:" + movie.getName()); System.out.println("得分:" + movie.getScore()); System.out.println("主演:" + movie.getActor()); } }}
电影类:
/* 电影类 */public class Movie { private String name; private double score; private String actor; public Movie() { } public Movie(String name, double score, String actor) { this.name = name; this.score = score; this.actor = actor; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getScore() { return score; } public void setScore(double score) { this.score = score; } public String getActor() { return actor; } public void setActor(String actor) { this.actor = actor; } @Override public String toString() { return "Movie{" + "name='" + name + '\'' + ", score=" + score + ", actor='" + actor + '\'' + '}'; }}
27.6 List集合
27.6.1 List集合特点、特有API
List系列集合特点
- ArrayList、LinekdList :有序,可重复,有索引。
- 有序:存储和取出的元素顺序一致
- 有索引:可以通过索引操作元素
- 可重复:存储的元素可以重复
List集合特有方法
方法名称 | 说明 |
---|---|
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
/** 目标:ArrayList集合。 Collection集合体系的特点: Set系列集合: 添加的元素,是无序,不重复,无索引的。 -- HashSet:添加的元素,是无序,不重复,无索引的。 -- LinkedHashSet:添加的元素,是有序,不重复,无索引的。 List系列集合:添加的元素,是有序,可重复,有索引的。 -- LinkedList: 添加的元素,是有序,可重复,有索引的。 -- ArrayList: 添加的元素,是有序,可重复,有索引的。 -- Vector 是线程安全的,速度慢,工作中很少使用。 1、List集合继承了Collection集合的全部功能,"同时因为List系列集合有索引", 2、因为List集合多了索引,所以多了很多按照索引操作元素的功能: 3、ArrayList实现类集合底层基于数组存储数据的,查询快,增删慢! - public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。 - public E get(int index):返回集合中指定位置的元素。 - public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。 - public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回更新前的元素值。 小结: ArrayList集合的底层是基于数组存储数据。查询快,增删慢!(相对的) */public class ListDemo01 { public static void main(String[] args) { // 1.创建一个ArrayList集合对象: // List:有序,可重复,有索引的。 ArrayList<String> list = new ArrayList<>(); // 一行经典代码! list.add("Java"); list.add("Java"); list.add("HTML"); list.add("HTML"); list.add("MySQL"); list.add("MySQL"); // 2.在某个索引位置插入元素。 list.add(2, "唐三"); System.out.println(list); // 3.根据索引删除元素,返回被删除元素 System.out.println(list.remove(1)); System.out.println(list); // 4.根据索引获取元素:public E get(int index):返回集合中指定位置的元素。 System.out.println(list.get(1)); // 5.修改索引位置处的元素: public E set(int index, E element) System.out.println(list.set(2, "小舞")); System.out.println(list); }}
27.6.2 List集合的遍历方式小结
List遍历方式:
- for循环。(独有的,因为List有索引)。
- 迭代器。
- foreach。
- JDK 1.8新技术。
/** 拓展:List系列集合的遍历方式有:4种。 List系列集合多了索引,所以多了一种按照索引遍历集合的for循环。 List遍历方式: (1)for循环。(独有的,因为List有索引)。 (2)迭代器。 (3)foreach。 (4)JDK 1.8新技术。 */public class ListDemo02 { public static void main(String[] args) { List<String> lists = new ArrayList<>(); lists.add("唐三"); lists.add("小舞"); lists.add("小白"); /** (1)for循环。 */ System.out.println("-----------------------"); for (int i = 0; i < lists.size(); i++) { String ele = lists.get(i); System.out.println(ele); } /** (2)迭代器。 */ System.out.println("-----------------------"); Iterator<String> it = lists.iterator(); while (it.hasNext()){ String ele = it.next(); System.out.println(ele); } /** (3)foreach */ System.out.println("-----------------------"); for (String ele : lists) { System.out.println(ele); } /** (4)JDK 1.8开始之后的Lambda表达式 */ System.out.println("-----------------------"); lists.forEach(s -> { System.out.println(s); }); }}
27.6.4 LinkedList集合
LinkedList也是List的实现类:底层是基于双链表的,增删比较快,查询慢!!
LinkedList是支持双链表,定位前后的元素是非常快的,增删首尾的元素也是最快的
所以LinkedList除了拥有List集合的全部功能还多了很多操作首尾元素的特殊功能:
方法名称 | 说明 |
---|---|
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
/** 目标:LinkedList集合。 LinkedList也是List的实现类:底层是基于双链表的,增删比较快,查询慢!! LinkedList是支持双链表,定位前后的元素是非常快的,增删首尾的元素也是最快的 所以LinkedList除了拥有List集合的全部功能还多了很多操作首尾元素的特殊功能: - public void addFirst(E e):将指定元素插入此列表的开头。 - public void addLast(E e):将指定元素添加到此列表的结尾。 - public E getFirst():返回此列表的第一个元素。 - public E getLast():返回此列表的最后一个元素。 - public E removeFirst():移除并返回此列表的第一个元素。 - public E removeLast():移除并返回此列表的最后一个元素。 - public E pop():从此列表所表示的堆栈处弹出一个元素。 - public void push(E e):将元素推入此列表所表示的堆栈。 小结: LinkedList是支持双链表,定位前后的元素是非常快的,增删首尾的元素也是最快的。 所以提供了很多操作首尾元素的特殊API可的实以做栈和队列现。 如果查询多而增删少用ArrayList集合。(用的最多的) 如果查询少而增删首尾较多用LinkedList集合。 */public class ListDemo03 { public static void main(String[] args) { // LinkedList可以完成队列结构,和栈结构 (双链表) // 1、做一个队列: LinkedList<String> queue = new LinkedList<>(); // 入队 queue.addLast("1号"); queue.addLast("2号"); queue.addLast("3号"); System.out.println(queue); // 出队 // System.out.println(queue.getFirst()); System.out.println(queue.removeFirst()); System.out.println(queue.removeFirst()); System.out.println(queue); // 2、做一个栈 LinkedList<String> stack = new LinkedList<>(); // 入栈 压栈 (push) stack.push("第1颗子弹"); stack.push("第2颗子弹"); stack.push("第3颗子弹"); stack.push("第4颗子弹"); System.out.println(stack); // 出栈 弹栈 pop System.out.println(stack.pop()); System.out.println(stack.pop()); System.out.println(stack.pop()); System.out.println(stack); }}
27.6.5 集合的并发修改异常问题
/** 目标:研究集合遍历并删除元素可能出现的:并发修改异常问题。 */public class Test { public static void main(String[] args) { // 1、准备数据 ArrayList<String> list = new ArrayList<>(); list.add("唐三"); list.add("小舞"); list.add("戴沐白"); list.add("马红俊"); list.add("宁荣荣"); list.add("朱竹青"); list.add("奥斯卡"); System.out.println(list); // it // 需求:删除全部的Java信息。 // a、迭代器遍历删除 Iterator<String> it = list.iterator();// while (it.hasNext()){// String ele = it.next();// if("Java".equals(ele)){// // 删除Java// // list.remove(ele); // 集合删除会出毛病// it.remove(); // 删除迭代器所在位置的元素值(没毛病)// }// }// System.out.println(list); // b、foreach遍历删除 (会出现问题,这种无法解决的,foreach不能边遍历边删除,会出bug)// for (String s : list) {// if("Java".equals(s)){// list.remove(s);// }// } // c、lambda表达式(会出现问题,这种无法解决的,Lambda遍历不能边遍历边删除,会出bug)// list.forEach(s -> {// if("Java".equals(s)){// list.remove(s);// }// }); // d、for循环(边遍历边删除集合没毛病,但是必须从后面开始遍历删除才不会出现漏掉应该删除的元素) for (int i = list.size() - 1; i >= 0 ; i--) { String ele = list.get(i); if("Java".equals(ele)){ list.remove(ele); } } System.out.println(list); }}
27.7 泛型
1. 泛型概述
- 泛型就是一个标签:
- 泛型可以在编译阶段约束只能操作某种数据类型。
注意:JDK 1.7开始之后后面的泛型申明可以省略不写
小结:
泛型就是一个标签。
泛型可以在编译阶段约束只能操作某种数据类型。
泛型只能支持引用数据类型。
/** 目标:泛型的概述。 什么是泛型? 泛型就是一个标签: 泛型可以在编译阶段约束只能操作某种数据类型。 注意: JDK 1.7开始之后后面的泛型申明可以省略不写 小结: 泛型就是一个标签。 泛型可以在编译阶段约束只能操作某种数据类型。 泛型只能支持引用数据类型。 */public class GenericityDemo { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("Java"); list.add("Java2"); // list.add(23); List<String> list1 = new ArrayList(); list1.add("Java");// list1.add(23.3);// list1.add(false); list1.add("Spring");// for (Object o : list1) {// String ele = (String) o;// System.out.println(ele);// } for (String s : list1) { System.out.println(s); } System.out.println("---------------------"); // 存储任意类型的元素 List<Object> list2 = new ArrayList<>(); list2.add(23); list2.add(23.3); list2.add("Java"); // List list3 = new ArrayList(); List<Integer> list3 = new ArrayList<>(); }}
2. 自定义泛型类
泛型类的概述
- 定义类时同时定义了泛型的类就是泛型类。
- 泛型类的格式:
修饰符 class 类名<泛型变量>{ }
此处泛型变量T可以随便写为任意标识,常见的如E、T、K、V等。
作用:编译阶段可以指定数据类型,类似于集合的作用。
public class Test { public static void main(String[] args) { // 需求:模拟ArrayList定义一个MyArrayList ,关注泛型设计 MyArrayList<String> list = new MyArrayList<>(); list.add("Java"); list.add("Java"); list.add("MySQL"); list.remove("MySQL"); System.out.println(list); MyArrayList<Integer> list2 = new MyArrayList<>(); list2.add(23); list2.add(24); list2.add(25); list2.remove(25); System.out.println(list2); }}
public class MyArrayList<E> { private ArrayList lists = new ArrayList(); public void add(E e){ lists.add(e); } public void remove(E e){ lists.remove(e); } @Override public String toString() { return lists.toString(); }}
3. 自定义泛型方法
泛型方法的概述:
- 定义方法时同时定义了泛型的方法就是泛型方法。
- 泛型方法的格式:
修饰符 <泛型变量> 方法返回值 方法名称(形参列表){}
作用:方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。
/** 目标:自定义泛型方法。 什么是泛型方法? 定义了泛型的方法就是泛型方法。 泛型方法的定义格式: 修饰符 返回值类型 方法名称(形参列表){ } 注意:方法定义了是什么泛型变量,后面就只能用什么泛型变量。 泛型类的核心思想:是把出现泛型变量的地方全部替换成传输的真实数据类型。 需求:给你任何一个类型的数组,都能返回它的内容。Arrays.toString(数组)的功能! 小结: 泛型方法可以让方法更灵活的接收数据,可以做通用技术! */public class GenericDemo { public static void main(String[] args) { String[] names = {"唐三", "小舞", "小白"}; printArray(names); Integer[] ages = {10, 20, 30}; printArray(ages); Integer[] ages2 = getArr(ages); String[] names2 = getArr(names); } public static <T> T[] getArr(T[] arr){ return arr; } public static <T> void printArray(T[] arr){ if(arr != null){ StringBuilder sb = new StringBuilder("["); for (int i = 0; i < arr.length; i++) { sb.append(arr[i]).append(i == arr.length - 1 ? "" : ", "); } sb.append("]"); System.out.println(sb); }else { System.out.println(arr); } }}
4. 自定义泛型接口
泛型接口的概述
- 使用了泛型定义的接口就是泛型接口。
- 泛型接口的格式:
修饰符 interface 接口名称<泛型变量>{}
作用:泛型接口可以约束实现类,实现类可以在实现接口的时候传入自己操作的数据类型这样重写的方法都将是针对于该类型的操作。
public class StudentData implements Data<Student>{ @Override public void add(Student student) { } @Override public void delete(int id) { } @Override public void update(Student student) { } @Override public Student queryById(int id) { return null; }}
5. 泛型通配符、上下限
27.8 Set系列
1. Set系列集系概述
Set系列集合特点:
- 无序:存取顺序不一致
- 不重复:可以去除重复
- 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。
Set集合实现类特点:
- HashSet : 无序、不重复、无索引。
- LinkedHashSet:有序、不重复、无索引。
- TreeSet:排序、不重复、无索引。
public class SetDemo1 { public static void main(String[] args) { // 看看Set系列集合的特点: HashSet LinkedHashSet TreeSet // Set<String> sets = new HashSet<>(); // 一行经典代码 无序不重复,无索引 // Set sets = new LinkedHashSet(); // 有序 不重复 无索引 sets.add("MySQL"); sets.add("MySQL"); sets.add("Java"); sets.add("Java"); sets.add("HTML"); sets.add("HTML"); sets.add("SpringBoot"); sets.add("SpringBoot"); System.out.println(sets); }}
2. 哈希表HashSet
哈希值
是JDK根据对象的地址,按照某种规则算出来的int类型的数值。
Object类的API
public int hashCode():返回对象的哈希值
public class SetDemo2 { public static void main(String[] args) { // 目标:学会获取对象的哈希值,并确认一下 String name = "douluodalu"; System.out.println(name.hashCode()); System.out.println(name.hashCode()); String name1 = "douluodalu"; System.out.println(name1.hashCode()); System.out.println(name1.hashCode()); }}
3. 实现类:LinkedHashSet
public class SetDemo4 { public static void main(String[] args) { // 看看Set系列集合的特点: HashSet LinkedHashSet TreeSet Set<String> sets = new LinkedHashSet<>(); // 有序 不重复 无索引 sets.add("MySQL"); sets.add("MySQL"); sets.add("Java"); sets.add("Java"); sets.add("HTML"); sets.add("HTML"); sets.add("SpringBoot"); sets.add("SpringBoot"); System.out.println(sets); }}
3. 实现类:TreeSet
TreeSet集合概述和特点
- 不重复、无索引、可排序
- 可排序:按照元素的大小默认升序(有小到大)排序。
- TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
注意:TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。
TreeSet集合自定义排序规则方式
- 2种方式。
- 类实现Comparable接口,重写比较规则。
- 集合自定义Comparator比较器对象,重写比较规则。
/** 目标:观察TreeSet对于有值特性的数据如何排序。 学会对自定义类型的对象进行指定规则排序 */public class SetDemo5 { public static void main(String[] args) { Set<Integer> sets = new TreeSet<>(); // 不重复 无索引 可排序 sets.add(23); sets.add(24); sets.add(12); sets.add(8); System.out.println(sets); Set<String> sets1 = new TreeSet<>(); // 不重复 无索引 可排序 sets1.add("Java"); sets1.add("Java"); sets1.add("angela"); sets1.add("唐三"); sets1.add("Java"); sets1.add("About"); sets1.add("Python"); sets1.add("UI"); sets1.add("UI"); System.out.println(sets1); System.out.println("------------------------------"); // 方式二:集合自带比较器对象进行规则定制 //// Set apples = new TreeSet(new Comparator() {// @Override// public int compare(Apple o1, Apple o2) {// // return o1.getWeight() - o2.getWeight(); // 升序// // return o2.getWeight() - o1.getWeight(); // 降序// // 注意:浮点型建议直接使用Double.compare进行比较// // return Double.compare(o1.getPrice() , o2.getPrice()); // 升序// return Double.compare(o2.getPrice() , o1.getPrice()); // 降序// }// }); Set<Apple> apples = new TreeSet<>(( o1, o2) -> Double.compare(o2.getPrice() , o1.getPrice()) ); apples.add(new Apple("红富士", "红色", 9.9, 500)); apples.add(new Apple("青苹果", "绿色", 15.9, 300)); apples.add(new Apple("绿苹果", "青色", 29.9, 400)); apples.add(new Apple("黄苹果", "黄色", 9.8, 500)); System.out.println(apples); }}
Apple类:
public class Apple implements Comparable<Apple>{ private String name; private String color; private double price; private int weight; public Apple() { } public Apple(String name, String color, double price, int weight) { this.name = name; this.color = color; this.price = price; this.weight = weight; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } @Override public String toString() { return "Apple{" + "name='" + name + '\'' + ", color='" + color + '\'' + ", price=" + price + ", weight=" + weight + '}'; } /** 方式一:类自定义比较规则 o1.compareTo(o2) * @param o * @return */ @Override public int compareTo(Apple o) { // 按照重量进行比较的 return this.weight - o.weight ; // 去重重量重复的元素 // return this.weight - o.weight >= 0 ? 1 : -1; // 保留重量重复的元素 }}
27.6.8 Collection体系的特点、使用场景总结
- 如果希望元素可以重复,又有索引,索引查询要快?
用ArrayList集合,基于数组的。(用的最多) - 如果希望元素可以重复,又有索引,增删首尾操作快?
用LinkedList集合,基于链表的。 - 如果希望增删改查都快,但是元素不重复、无序、无索引。
用HashSet集合,基于哈希表的。 - 如果希望增删改查都快,但是元素不重复、有序、无索引。
用LinkedHashSet集合,基于哈希表和双链表。 - 如果要对对象进行排序。
用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。
27.6.10 集合工具类Collections
Collections集合工具类
- java.utils.Collections:是集合工具类
- 作用:Collections并不属于集合,是用来操作集合的工具类。
Collections常用的API
方法名称 | 说明 |
---|---|
public static boolean addAll(Collection c, T… elements) | 给集合对象批量添加元素 |
public static void shuffle(List list) | 打乱List集合元素的顺序 |
Collections排序相关API
使用范围:只能对于List集合的排序。
排序方式一:
方法名称 | 说明 |
---|---|
public static void sort(List list) | 将集合中元素按照默认规则排序 |
/** 目标:Collections工具类的使用。 java.utils.Collections:是集合工具类 Collections并不属于集合,是用来操作集合的工具类。 Collections有几个常用的API: - public static boolean addAll(Collection c, T... elements) 给集合对象批量添加元素! - public static void shuffle(List list) :打乱集合顺序。 - public static void sort(List list):将集合中元素按照默认规则排序。 - public static void sort(List list,Comparator c):将集合中元素按照指定规则排序。 */public class CollectionsDemo01 { public static void main(String[] args) { List<String> names = new ArrayList<>(); //names.add("楚留香"); //names.add("胡铁花"); //names.add("张无忌"); //names.add("陆小凤"); Collections.addAll(names, "楚留香","胡铁花", "张无忌","陆小凤"); System.out.println(names); // 2、public static void shuffle(List list) :打乱集合顺序。 Collections.shuffle(names); System.out.println(names); // 3、 public static void sort(List list):将集合中元素按照默认规则排序。 (排值特性的元素) List<Integer> list = new ArrayList<>(); Collections.addAll(list, 12, 23, 2, 4); System.out.println(list); Collections.sort(list); System.out.println(list); }}
排序方式二:
方法名称 | 说明 |
---|---|
public static void sort(List list,Comparator c) | 将集合中元素按照指定规则排序 |
/** 目标:引用数据类型的排序。 字符串按照首字符的编号升序排序! 自定义类型的比较方法API:Collections - public static void sort(List list): 将集合中元素按照默认规则排序。 对于自定义的引用类型的排序人家根本不知道怎么排,直接报错! - public static void sort(List list,Comparator c): 将集合中元素按照指定规则排序,自带比较器 */public class CollectionsDemo02 { public static void main(String[] args) { List<Apple> apples = new ArrayList<>(); // 可以重复! apples.add(new Apple("红富士", "红色", 9.9, 500)); apples.add(new Apple("青苹果", "绿色", 15.9, 300)); apples.add(new Apple("绿苹果", "青色", 29.9, 400)); apples.add(new Apple("黄苹果", "黄色", 9.8, 500));// Collections.sort(apples); // 方法一:可以的,Apple类已经重写了比较规则// System.out.println(apples); // 方式二:sort方法自带比较器对象// Collections.sort(apples, new Comparator() {// @Override// public int compare(Apple o1, Apple o2) {// return Double.compare(o1.getPrice() , o2.getPrice()); // 按照价格排序!!// }// }); Collections.sort(apples, ( o1, o2) -> Double.compare(o1.getPrice() , o2.getPrice()) ); System.out.println(apples); }}
Apple 类
public class Apple implements Comparable<Apple>{ private String name; private String color; private double price; private int weight; public Apple() { } public Apple(String name, String color, double price, int weight) { this.name = name; this.color = color; this.price = price; this.weight = weight; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } @Override public String toString() { return "Apple{" + "name='" + name + '\'' + ", color='" + color + '\'' + ", price=" + price + ", weight=" + weight + '}'; } /** 方式一:类自定义比较规则 o1.compareTo(o2) * @param o * @return */ @Override public int compareTo(Apple o) { // 按照重量进行比较的 return this.weight - o.weight ; // 去重重量重复的元素 // return this.weight - o.weight >= 0 ? 1 : -1; // 保留重量重复的元素 }}