文章目录
- Java基础学习
- 第十七章 多线程
- 第十八章 File类
- 第十九章 IO流
- 第二十章 网络编程
- 第二十一章 函数式接口
- 第二十二章 Stream流
- 第二十三章 方法引用
Java基础学习
笔记后半部分(续)
第十七章 多线程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qaYubEzy-1573823584716)(C:\Users\Think-Pad\Desktop\java\并发与并行.jpg)]
17.1 继承thread
package cn.code;
import com.sun.corba.se.impl.activation.NameServiceStartThread;
/*
创建线程的方式一:
1. 创建:继承Thread + 重写 run
2. 启动:创建子类对象 + start
*/
public class Demo015 extends Thread {
// 线程入口点
@Override
public void run(){
for (int i = 0; i < 200; i++) {
System.out.println("一边听歌");
}
}
public static void main(String[] args) {
// 创建子类对象
Demo015 dem = new Demo015();
// 启动
dem.start();
dem.run(); // 普通方法调用
for (int i = 0; i < 200; i++) {
System.out.println("一遍coding");
}
}
}
17.2 图片下载
package cn.code.Demo015;
public class TDownloader extends Thread {
private String name; // 存储路径
private String url; // 存储名字
public TDownloader(String name, String url) {
this.name = name;
this.url = url;
}
@Override
public void run() {
WedDownloader wd = new WedDownloader();
wd.download(url,name);
}
public static void main(String[] args) {
TDownloader td1 = new TDownloader("","");
TDownloader td2 = new TDownloader("","");
TDownloader td3 = new TDownloader("","");
// 启动三个线程
td1.start();
td2.start();
td3.start();
}
}
package cn.code.Demo015;
import java.io.FileNotFoundException;
/*
*/
public class WedDownloader {
public void download(String url,String name){
}
}
17.3 实现Runnable
package cn.code.Demo015;
/*
创建线程的方式二:
1. 创建:实现Runnable + 重写 run
2. 启动:创建实现类对象 + Thread对象 + start
*/
public class StartRun implements Runnable {
// 线程入口点
@Override
public void run(){
for (int i = 0; i < 200; i++) {
System.out.println("一边听歌");
}
}
public static void main(String[] args) {
// 创建实现类对象
StartRun sr = new StartRun();
// 创建代理类对象
Thread t = new Thread(sr);
// 启动
t.start(); // 不能保证料立即运行 CPU 调用
for (int i = 0; i < 200; i++) {
System.out.println("一遍coding");
}
}
}
17.4 练习
抢票:
package cn.code.Demo015;
/*
共享资源
*/
public class Web12306 implements Runnable{
// 票数
private int ticketNums = 999;
@Override
public void run() {
while(true){
if (ticketNums < 1){
break;
}try{
Thread.sleep(200);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--);
}
}
public static void main(String[] args){
// 一份资源
Web12306 web =new Web12306();
System.out.println(Thread.currentThread().getName());
// 多个代理
new Thread(web,"码畜").start();
new Thread(web,"码农").start();
new Thread(web,"码蟥").start();
}
}
龟兔赛跑:
package cn.code.Demo015;
/*
模拟龟兔赛跑
*/
public class Racer implements Runnable {
private static String winner;
@Override
public void run() {
for (int steps = 0; steps <= 100; steps++) {
// 模拟休息
if (Thread.currentThread().getName().equals("rabbit") && steps % 10 ==0){
try{
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "-->" + steps);
// 比赛是否结束
boolean flag = gameOver(steps);
if (flag){
break;
}
}
}
private boolean gameOver(int steps){
if (winner != null){ // 存在胜利者
return true;
}else{
if (steps == 100){
winner = Thread.currentThread().getName();
System.out.println("winner = " + winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Racer racer = new Racer();
new Thread(racer,"tortoise").start();
new Thread(racer,"rabbit").start();
}
}
17.5 了解callable
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QrwY42Es-1573823584793)(C:\Users\Think-Pad\Desktop\java\callable.jpg)]
17.6 静态代理设计模式
package cn.code.Demo015;
/*
静态代理:
公共接口:
1. 真实角色
2. 代理角色
*/
public class StaticProxy {
public static void main(String[] args) {
new WeddingCompany(new You()).happyMarry();
// new Throw(线程对象).start();
}
}
interface Marry{
void happyMarry();
}
// 真实角色
class You implements Marry{
@Override
public void happyMarry() {
System.out.println("you and 嫦娥终于奔月了...");
}
}
// 代理角色
class WeddingCompany implements Marry{
// 真实角色
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void happyMarry() {
ready();
this.target.happyMarry();
after();
}
private void ready() {
System.out.println("布置猪窝...");
}
private void after() {
System.out.println("闹玉兔...");
}
}
17.7 推导Lamda——简化线程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p8WbiA10-1573823584794)(C:\Users\Think-Pad\Desktop\java\函数式编程思想.jpg)]
package cn.code.Demo016;
/*
Lambda表达式 简化线程(用一次)的使用
Lambda推导必须存在类型
*/
public class LambdaThread {
// 静态内部类
static class Test implements Runnable {
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("一边听歌");
}
}
}
public static void main(String[] args) {
// new Thread(new Test()).start();
// 匿名内部类
class Test2 implements Runnable {
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("一边听歌");
}
}
}
new Thread(new Test2()).start();
// 匿名内部类 必须借助接口或者父类
new Thread(new Runnable(){
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("一边听歌");
}
}
}).start();
// jdk8 简化 lambda表达式
new Thread(() -> {
for (int i = 0; i < 200; i++) {
System.out.println("一边听歌");
}
}
).start();
}
}
package cn.code.Demo016;
/*
Lambda推导 + 参数
*/
public class LambdaTest01 {
public static void main(String[] args) {
ILove love = (int a) -> {
System.out.println("i like lambda -->" + a);
};
love.lambda(100);
// 简化
love = a -> {
System.out.println("i like lambda -->" + a);
};
love.lambda(10);
love = a -> System.out.println("i like lambda -->" + a);
love.lambda(0);
}
interface ILove{
void lambda(int a);
}
// 外部类
class Love implements ILove{
@Override
public void lambda(int a) {
System.out.println("i like lambda -->" + a);
}
}
}
package cn.code.Demo016;
/*
Lambda推导 + 参数
*/
public class LambdaTest02 {
public static void main(String[] args) {
IInterst inters = (int a,int b) -> {
System.out.println("i like lambda -->" + (a + b));
return a + b;
};
inters.lambda(12,6);
inters = (a,b) -> {
System.out.println("i like lambda -->" + (a + b));
return a + b;
};
inters.lambda(54,8);
inters = (a,b) -> a + b;
}
interface IInterst{
int lambda(int a,int b);
}
// 外部类
class interst implements IInterst{
@Override
public int lambda(int a, int b) {
System.out.println("i like lambda -->" + (a + b));
return a + b;
}
}
}
17.8 线程状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g8MQmUFu-1573823584808)(C:\Users\Think-Pad\Desktop\java\线程状态.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5CEbdwpB-1573823584810)(C:\Users\Think-Pad\Desktop\java\线程状态01.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S0jAbPBi-1573823584811)(C:\Users\Think-Pad\Desktop\java\线程方法.jpg)]
17.9 终止
package cn.code.Demo016;
/*
终止线程:
1. 线程正常执行完毕 --> 次数
2. 外部干涉 --> 加入标识
不要使用 stop destroy
*/
public class TerminateThread implements Runnable{
// 1. 加入标识 标记线程体是否可以运行
private boolean flag = true;
private String name;
public TerminateThread(String name) {
this.name = name;
}
@Override
public void run() {
int i = 0;
// 2. 关联标识,true --> 运行 false --> 停止
while (flag){
System.out.println(name + "-->" + i++);
}
}
// 3.对外提供方法改变标识
public void terminate(){
this.flag = false;
}
public static void main(String[] args) {
TerminateThread tt = new TerminateThread("C罗");
new Thread(tt).start();
for (int i = 0; i < 99; i++) {
if (i == 88){
tt.terminate(); // 线程的终止
System.out.println("tt game over");
}
System.out.println("main -->" +i);
}
}
}
17.10 暂停sleep
package cn.code.Demo016;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
sleep模拟网络延时,放大了发生问题的可能性
sleep模拟倒计时
*/
public class BlockedSleep {
public static void main(String[] args) throws InterruptedException {
// 倒计时
Date endTime = new Date(System.currentTimeMillis() + 1000*10);
long end = endTime.getTime();
while (true){
System.out.println(new SimpleDateFormat("HH:mm:ss").format(endTime));
Thread.sleep(1000);
endTime = new Date(endTime.getTime() - 1000);
if (end - 10000 > endTime.getTime()){
break;
}
}
}
public static void test() throws InterruptedException {
// 倒数10个数,1秒一个
int num = 10;
while(true){
Thread.sleep(1000);
System.out.println(num--);
}
}
}
17.11 礼让 yield
package cn.code.Demo016;
/*
yield 礼让线程,暂停线程,直接进入就绪状态不是阻塞状态
*/
public class YieldDemo02 {
public static void main(String[] args) {
new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("lambda..." + i);
}
}).start();
for (int i = 0; i < 100; i++) {
if (i % 20 == 0){
Thread.yield(); // main礼让
}
System.out.println("main..." + i);
}
}
}
17.12 插队 join
package cn.code.Demo016;
public class BlockedJoin01 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("lambda..." + i);
}
});
t.start();
for (int i = 0; i < 100; i++) {
if (i == 20){
t.join(); // 插队 main函数被阻塞了
}
System.out.println("main..." + i);
}
}
}
17.13 优先级
package cn.code.Demo016;
/*
线程的优先级 1~10
1. NORM_PRIORITY 5
2. MIN_PRIORITY 1
3. MAX_PRIORITY 10
概率,不代表绝对的先后顺序
*/
public class PriorityTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getPriority());
MyPriority mp = new MyPriority();
Thread t1 = new Thread(mp,"adidas");
Thread t2 = new Thread(mp,"NIKE");
Thread t3 = new Thread(mp,"回力");
Thread t4 = new Thread(mp,"李宁");
// 设置优先级在启动前
t1.setPriority(Thread.MAX_PRIORITY); // 可以设置数字
t2.setPriority(Thread.MIN_PRIORITY);
t3.setPriority(Thread.MAX_PRIORITY);
t4.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
t3.start();
t4.start();
}
static class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
Thread.yield();
}
}
}
17.14 守护线程
package cn.code.Demo016;
/*
守护线程:是为用户线程服务的;jvm停止不用等待守护线程执行完毕
默认:用户线程jvm等待用户线程执行完毕才会停止
*/
public class DaemonTest {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread t = new Thread(god);
t.setDaemon(true); // 将用户线程调整为守护 虚拟机不用等待它执行完
t.start();
new Thread(you).start();
}
static class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 365 * 100; i++) {
System.out.println("happy life...");
}
System.out.println("ooooooooo...");
}
}
static class God implements Runnable{
@Override
public void run() {
for (;true;){
System.out.println("bless you");
}
}
}
}
17.15 基本信息
package cn.code.Demo016;
/*
其他方法
isAlive:线程是否还活着
Thread.currentThread():当前线程
setName、getName:代理名称
*/
public class InfoTest {
public static void main(String[] args) throws InterruptedException {
System.out.println(Thread.currentThread().isAlive());
// 设置名称:真实角色 + 代理角色
MyInfo info = new MyInfo("战斗机");
Thread t = new Thread(info);
t.setName("公鸡");
t.start();
t.sleep(1000);
System.out.println(t.isAlive());
}
static class MyInfo implements Runnable{
private String name;
public MyInfo(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->" + name);
}
}
}
17.16 线程安全性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-23WyfwCr-1573823584814)(C:\Users\Think-Pad\Desktop\java\多线程.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WcNKywa0-1573823584815)(C:\Users\Think-Pad\Desktop\java\多线程安全性.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gy3KgSdX-1573823584837)(C:\Users\Think-Pad\Desktop\java\线程安全问题.jpg)]
17.17 解决线程安全问题——同步代码块
package cn.code.Demo017;
/*
模拟卖票案例
创建3个线程,同时开启,对共享的票进行出售
*/
public class Demo01Ticket {
public static void main(String[] args) {
// 创建Runnable接口的实现类对象
RunnableImple run= new RunnableImple();
// 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
// 调用start方法开启多线程
t0.start();
t1.start();
t2.start();
}
}
package cn.code.Demo017;
/*
卖票案例出现了线程安全问题
卖出了不存在的票和重复的票
解决线程安全问题的一种方案:使用同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
1. 通过代码块中的锁对象,可以使用任意的对象
2. 但是必须保证多个线程使用的锁对象是同一个
3. 锁对象作用:
把同步代码块锁住,只让一个线程在同步代码块中执行
*/
public class RunnableImple implements Runnable {
// 定义一个多线程共享的票源
private int ticket = 100;
// 创建一个锁对象
Object obj = new Object();
// 设置线程任务:卖票
@Override
public void run() {
// 使用死循环,让卖票操作重复执行
while(true){
// 同步代码块
synchronized(obj){
// 先判断票是否存在
if(ticket > 0){
// 提高安全问题出现的概率,让程序睡眠
try{
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 票存在,卖票 titck--
System.out.println(Thread.currentThread().getName() + "--> 正在卖第" + ticket + "张票");
ticket--;
}
}
}
}
}
同步技术的原理:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NTgVFhr0-1573823584839)(C:\Users\Think-Pad\Desktop\java\同步技术的原理.jpg)]
解决线程的安全问题——同步方法:
package cn.code.Demo017;
/*
模拟卖票案例
创建3个线程,同时开启,对共享的票进行出售
*/
public class Demo01Ticket {
public static void main(String[] args) {
// 创建Runnable接口的实现类对象
RunnableImple01 run= new RunnableImple01();
// 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
// 调用start方法开启多线程
t0.start();
t1.start();
t2.start();
}
}
package cn.code.Demo017;
/*
解决线程安全问题的第二种方案:使用同步方法
使用步骤:
1. 把访问了共享数据的代码抽取出来,放到一个方法中
2. 在方法上添加synchronized修饰符
格式:定义方法的格式
修饰符 synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
*/
public class RunnableImple01 implements Runnable {
// 定义一个多线程共享的票源
private int ticket = 100;
// 设置线程任务:卖票
@Override
public void run(){
while (true){
payTicket();
}
}
/*
定义一个同步方法
同步方法也会把方法内部的代码锁住
只让一个线程执行
同步方法的锁对象是谁?
就是实现类对象 new RunnableImple01()
也就是 this
*/
public synchronized void payTicket(){
// 先判断票是否存在
if(ticket > 0){
// 提高安全问题出现的概率,让程序睡眠
try{
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 票存在,卖票 titck--
System.out.println(Thread.currentThread().getName() + "--> 正在卖第" + ticket + "张票");
ticket--;
}
}
}
静态的同步方法:
锁对象是谁?
不能是this
this是创建对象之后产生的,静态方法优先于对象
静态方法的锁对象是本类的class属性 --> class文件对象(反射)
17.18 解决线程安全问题——Lock锁
package cn.code.Demo017;
/*
模拟卖票案例
创建3个线程,同时开启,对共享的票进行出售
*/
public class Demo01Ticket {
public static void main(String[] args) {
// 创建Runnable接口的实现类对象
RunnableImpl02 run= new RunnableImpl02();
// 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
// 调用start方法开启多线程
t0.start();
t1.start();
t2.start();
}
}
package cn.code.Demo017;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
解决线程安全问题的三种方案:使用Lock锁
java.util.concurrent.locks.lock接口
lock 实现提供了比使用 synchronized 方法和语句获得的更广泛的锁定操作。
Lock接口中的方法:
void lock() 获取锁
void unlock() 释放锁
java.util.concurrent.locks.lock.ReentrantLock implements lock接口
使用步骤:
1. 在成员位置创建一个ReentrantLock 对象
2. 在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
3. 在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁
*/
public class RunnableImpl02 implements Runnable {
// 定义一个多线程共享的票源
private int ticket = 100;
// 1. 在成员位置创建一个ReentrantLock 对象
Lock l = new ReentrantLock();
@Override
public void run() {
// 使用死循环,让卖票操作重复执行
while (true) {
// 2. 在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
l.lock();
// 先判断票是否存在
if (ticket > 0) {
// 提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
// 票存在,卖票 titck--
System.out.println(Thread.currentThread().getName() + "--> 正在卖第" + ticket + "张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
// 3. 在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁
l.unlock(); // 无论程序是否异常,都会把锁释放(能提高程序的效率)
}
}
}
/* @Override
public void run() {
// 使用死循环,让卖票操作重复执行
while (true) {
// 2. 在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
l.lock();
// 先判断票是否存在
if (ticket > 0) {
// 提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 票存在,卖票 titck--
System.out.println(Thread.currentThread().getName() + "--> 正在卖第" + ticket + "张票");
ticket--;
}
// 3. 在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁
l.unlock();
}*/
}
}
17.19 等待唤醒机制
Object类中wait带参数方法和notifyAll方法:
进入到TimeWaiting(计时器)有两种方式
-
使用sleep(Long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
-
使用wait(Long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Block状态
package cn.code.Demo018;
/*
测试类:
包含main方法,程序执行的入口,启动程序
创建包子类对象;
创建包子铺线程,开启,生产包子;
创建吃货线程,开启,吃包子;
*/
public class Demo {
public static void main(String[] args) {
// 创建包子类对象
BaoZi bz = new BaoZi();
// 创建包子铺线程,开启,生产包子
new Thread(new BaoZiPu(bz)).start();
// 创建吃货线程,开启,吃包子
new Thread(new ChiHuo(bz)).start();
}
}
package cn.code.Demo018;
/*
资源类:包子类
设置包子的属性
皮
馅
包子状态:有 true ,没有 false
*/
public class BaoZi {
// 皮
String pi;
// 馅
String xian;
// 包子的状态: 有 true ,没有 false
boolean flag = false;
}
package cn.code.Demo018;
/*
生产者(包子铺)类:是一个线程类,可以继承Thread 实现接口
设置线程任务(run):生产包子
对包子的状态尽心判断
true:有包子
包子铺调用wait方法进入等待状态
false:没有包子
包子铺生产包子
增加一些趣味性:交替生产两种包子
有两种状态(i % 2 == 0)
包子铺生产好了包子
修改包子的状态为true 有
唤醒吃货线程,让吃货线程吃包子
注意:
包子铺线程和包子线程关系 --> 通信(互斥)
必须同时同步技术保证两个线程只能有一个在执行
锁对象必须保证唯一,可以使用包子对象作为锁对象
包子铺类和吃货的类就需要把包子对象作为参数传递过来
1. 需要在成员位置创建一个包子变量
2. 使用带参数构造方法,为这个包子变量赋值
*/
public class BaoZiPu implements Runnable {
// 1. 需要在成员位置创建一个包子变量
private BaoZi bz;
// 2. 使用带构造方法,为这个包子变量赋值
public BaoZiPu(BaoZi bz) {
this.bz = bz;
}
// 设置线程任务(run):生产包子
@Override
public void run() {
int count = 0;
// 让包子铺一直生产包子
while(true){
// 必须同时同步技术保证两个线程只能有一个在执行
synchronized (bz){
// 对包子的状态进行判断
if (bz.flag == true){
// 包子铺调用wait方法进入等待状态
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 唤醒之后执行,包子铺生产包子
// 增加一些趣味性:交替生产两种包子
if (count % 2 == 0){
// 生产 薄皮三鲜馅包子
bz.pi = "薄皮";
bz.xian = "三鲜馅";
}else {
// 生产 冰皮 牛肉大葱馅
bz.pi = "冰皮";
bz.xian = "牛肉大葱馅";
}
count++;
System.out.println("包子铺正在生产:" + bz.pi + bz.xian + "包子");
// 生产包子需要3秒钟
try{
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 包子铺生产好了包子
// 修改包子的状态为true有
bz.flag = true;
// 唤醒吃货线程,让吃货线程吃包子
bz.notify();
System.out.println("包子铺已经生产好了:" + bz.pi + bz.xian + "包子,吃货可以开始吃了");
}
}
}
}
package cn.code.Demo018;
/*
消费者(吃货):是一个线程类,可以继承Thread
设置线程任务(run):吃包子
对包子的状态进行判断
false:没有包子
吃货调用wait方法进入等待状态
true: 有包子
吃货吃包子
吃货吃完包子
修改包子的状态为false没有
吃货唤醒包子铺线程,生产包子
*/
public class ChiHuo implements Runnable {
// 1. 需要在成员位置创建一个包子变量
private BaoZi bz;
// 2. 使用带参数构造方法,为这个包子变量赋值
public ChiHuo(BaoZi bz) {
this.bz = bz;
}
// 设置线程任务(run):吃包子
@Override
public void run() {
// 使用死循环,让吃货一直吃包子
while (true) {
// 必须同时同步技术保证两个线程只能有一个在执行
synchronized (bz) {
// 对包子的状态进行判断
if (bz.flag == false) {
// 包子铺调用wait方法进入等待状态
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 被唤醒之后执行代码,吃包子
System.out.println("吃货正在吃:" + bz.pi + bz.xian + "的包子");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 吃货吃完包子
// 修改包子的状态为false没有
bz.flag = false;
// 吃货唤醒包子铺线程,生产包子
bz.notify();
System.out.println("吃货已经把:" + bz.pi + bz.xian + "的包子吃完了,包子铺开始生产包子");
System.out.println("=======================================");
}
}
}
}
17.20 线程的状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eC7XUWAE-1573823584840)(C:\Users\Think-Pad\Desktop\java\线程的状态.jpg)]
17.21 线程池的原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Ggpf1Zm-1573823584842)(C:\Users\Think-Pad\Desktop\java\线程池原理.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EStSqjBW-1573823584868)(C:\Users\Think-Pad\Desktop\java\线程池原理01.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BTsXE2GI-1573823584878)(C:\Users\Think-Pad\Desktop\java\线程池的好处.jpg)]
17.22 线程池的代码实现
package cn.code.Demo019;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
线程池:JDK1.5 之后提供的
java.util.concurrent.Executors: 线程池的工厂类,用来生成线程池
Executors类中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重复用固定线程数的线程池
参数:
int nThreads:创建线程池中包含的线程数量
返回值:
ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)
java.util.concurrent.ExecutorService: 线程池接口
用来从线程池中获取线程,调用start方法,执行线程任务
submit(Runnable task) 提交一个 Runnable 任务用于执行
关闭/销毁线程池的方法
void shutdown()
线程池的使用步骤:
1. 使用线程池的工厂类Executors里边提供的静态方法 newFixedThreadPool 生产一个指定数量的线程池
2. 创建一个类,实现 Runnable 接口,重写run方法,设置线程任务
3. 调用ExecutorService中的方法submit,传递线程任务(实现类), 开启线程,执行run方法
4. 调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
*/
public class Demo01ThreadPool {
public static void main(String[] args) {
// 1. 使用线程池的工厂类Executors里边提供的静态方法 newFixedThreadPool 生产一个指定数量的线程池
ExecutorService es = Executors.newFixedThreadPool(2);
// 3. 调用ExecutorService中的方法submit,传递线程任务(实现类), 开启线程,执行run方法
es.submit(new RunnableImpl()); // pool-1-thread-1创建了一个新的线程执行
// 线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用
es.submit(new RunnableImpl()); // pool-1-thread-1创建了一个新的线程执行
es.submit(new RunnableImpl()); // pool-1-thread-2创建了一个新的线程执行
// 4. 调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
es.shutdown();
es.submit(new RunnableImpl()); // 抛异常,线程池都没有了,就不能获取线程了
}
}
package cn.code.Demo019;
/*
2. 创建一个类,实现 Runnable 接口,重写run方法,设置线程任务
*/
public class RunnableImpl implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "创建了一个新的线程执行");
}
}
第十八章 File类
18.1 File类的静态成员变量
package cn.code.Demo019;
import java.io.File;
/*
java.io.File类
文件和目录路径名的抽象表示形式。
java把电脑中的文件和文件夹(目录)封装为了一个File类,我们可以使用File类对文件和文件夹进行操作
我们可以使用File可的方法
创建一个文件/文件夹
删除文件/文件夹
获取文件/文件夹
判断文件/文件夹是否存在
对文件夹进行遍历
获取文件的大小
File类是一个与系统无关的类,任何操作系统都可以使用这个类中的方法
重点:记住这三个单词
file:文件
directory:文件夹/目录
path:路径
*/
public class Demo01File {
public static void main(String[] args) {
/*
static String pathSeparator 与系统有关的路径分隔符,为了方便,它被表示为一个字符串
static char pathSeparatorChar 与系统有关的路径分隔符
static String separator 与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串
static char separatorchar 与系统有关的默认名称分隔符
操作路径:路径不能写死了
c:\develop\a\a.txt windows
c:/develop/a/a.txt linux
"c:"+ File.separator +"develop"+ File.separator +"a"+ File.separator +"a.txt "
*/
String pathSeparator = File.pathSeparator;
System.out.println(pathSeparator); // 路径分隔符 Windows:分号 ; linux:冒号 :
char separatorChar = File.separatorChar;
System.out.println(separatorChar); // 文件名称分隔符 Windows:反斜杠 \ linux:正斜杠 /
}
}
18.2 File类的构造方法
package cn.code.Demo019;
import java.io.File;
/*
路径:
绝对路径:是一个完整的路径
以盘符(c:,d:)开始的路径
E:\\IdeaProjects\\basic-code
相对路径:是一个简化的路径
相对指的是相对于当前项目的根目录(E:\\IdeaProjects\\basic-code)
如果使用当前项目的根目录,路径可以简化书写
E:\\IdeaProjects\\basic-code\\123.txt --> 简化为: 123.txt(可以省略项目的根目录)
注意:
1. 路径是不区分大小写
2. 路径中的文件名分隔符Windows使用反斜杠,反斜杠是转义字符,两个反斜杠代表一个普通的反斜杠
*/
public class Demo02File {
public static void main(String[] args) {
/*
File类的构造方法
*/
// show02("c","123.txt");
// show02("d","123.txt");
show03(); // c:\hello.java
}
/*
File(String parent,String child) 根据 parent 路径名字符串和 child 路径名字符串创建一个新的 File实例
参数:把路径分成了两个部分
String parent: 父路径
String child: 子路径
好处:
父路径和子路径,可以单独书写,使用起来非常灵活;父路径和子路径都可以变化
父路径是File类型,可以使用File的方法对路径进行一些操作,再使用路径创建对象
*/
private static void show03() {
File parent = new File("c:\\");
File file = new File(parent,"hello.java");
System.out.println(file);
}
/*
File(String parent,String child) 根据 parent 路径名字符串和 child 路径名字符串创建一个新的 File实例
参数:把路径分成了两个部分
String parent: 父路径
String child: 子路径
好处:
父路径和子路径,可以单独书写,使用起来非常灵活;父路径和子路径都可以变化
*/
private static void show02(String parent,String child) {
File file = new File(parent,child);
System.out.println(file);
}
/*
File(String pathname) 通过将给定路径字符串转换为抽象路径来创建一个新 File 实例
参数:
String pathanme: 字符串的路径名称
路径可以是以文件结尾,也可以是以文件夹结尾
路径可以相对路径,也可以是绝对路径
路径可以是存在,也可以是不存在
创建File对象,只是把字符串路径封装为File对象,不考虑路径的真假情况
*/
private static void show01() {
File f1 = new File("E:\\IdeaProjects\\basic-code\\123.txt");
System.out.println(f1); // 重写了Object类的toString方法 E:\IdeaProjects\basic-code\123.txt
File f2 = new File("E:\\IdeaProjects\\basic-code");
System.out.println(f2); // E:\IdeaProjects\basic-code
File f3 = new File("123.txt");
System.out.println(f3); // 123.txt
}
}
18.3 File类获取功能的方法
package cn.code.Demo019;
import java.io.File;
/*
File类获取功能的方法
public string getAbsolutePath(): 返回此File的绝对路径名字符串
public string getPath(): 将此File转换为路径名字符串
public string getName(): 返回从File表示的文件或目录的名称
public long length(): 返回由此File表示的长度
*/
public class Demo03File {
public static void main(String[] args) {
show04();
}
/*
public long length(): 返回由此File表示的长度
获取的是构造方法指定的文件大小,以字节为单位
注意:
文件夹没有大小的概念,不能获取问价的大小
如果构造方法中给出的路径不存在,那么length方法返回0
*/
private static void show04() {
File f1 = new File("C:\\Users\\Think-Pad\\Desktop\\java\\if语句.jpg");
long l1 = f1.length();
System.out.println(l1); // 14254
File f2 = new File("C:\\Users\\Think-Pad\\Desktop\\java\\if.jpg");
long l2 = f2.length();
System.out.println(l2); // 0
File f3 = new File("C:\\AMD");
long l3 = f3.length();
System.out.println(l3); // 0
}
/*
public string getName(): 返回从File表示的文件或目录的名称
获取的就是构造方法传递路径的结尾部分(文件/文件夹)
*/
private static void show03() {
File f1 = new File("E:\\IdeaProjects\\basic-code\\123.txt");
String name1 = f1.getName();
System.out.println(name1); // 123.txt
File f2 = new File("E:\\IdeaProjects\\basic-code");
String name2 = f2.getName();
System.out.println(name2); // basic-code
}
/*
public string getPath(): 将此File转换为路径名字符串
获取的构造方法中传递的路径
toString方法调用的就是getPath方法
源码:
public String toString(){
return getPath();
}
*/
private static void show02() {
File f1 = new File("E:\\IdeaProjects\\basic-code\\123.txt");
File f2 = new File("123.txt");
String path1 = f1.getPath();
String path2 = f2.getPath();
System.out.println(path1); // E:\IdeaProjects\basic-code\123.txt
System.out.println(path2); // 123.txt
System.out.println(f1); // E:\IdeaProjects\basic-code\123.txt
System.out.println(f1.toString()); // E:\IdeaProjects\basic-code\123.txt
}
/*
public String getAbsolutePath(): 返回此File的绝对路径名字符串
获取的构造方法中传递的路径
无论路径是绝对的还是相对的,getAbsolutePath方法返回的都是绝对路径
*/
private static void show01() {
File f1 = new File("E:\\IdeaProjects\\basic-code\\123.txt");
String absolutePath1 = f1.getAbsolutePath();
System.out.println(absolutePath1); // E:\IdeaProjects\basic-code\123.txt
File f2 = new File("123.txt");
String absolutePath2 = f2.getAbsolutePath();
System.out.println(absolutePath2); // E:\IdeaProjects\basic-code\123.txt
}
}
18.4 File类判断功能的方法
package cn.code.Demo019;
import java.io.File;
/*
File判断功能的方法
public boolean exists(): 此File表示的文件或目录是否存在
public boolean isDirectory(): 此File表示的是否为目录
public boolean isFile(): 此File表示的是否为文件
*/
public class Demo04File {
public static void main(String[] args) {
show02();
}
/*
public boolean isDirectory(): 此File表示的是否为目录
用于判断构造方法中给定的路径是否以文件夹结尾
是:true
否:false
public boolean isFile(): 此File表示的是否为文件
用于判断构造方法中给定的路径是否以文件结尾
是:true
否:false
注意:
电脑的硬盘中只有文件/文件夹,两个方法是互斥的
这两个方法使用前提,路径必须是存在的,否则都返回false
*/
private static void show02() {
File f1 = new File("C:\\Users\\Think-Pad\\Desktop\\java\\if语句.jpg");
// 不存在,就没有必要获取
if (f1.exists()){
System.out.println(f1.isDirectory());
System.out.println(f1.isFile());
}
File f2 = new File("C:\\Users\\Think-Pad\\Desktop\\java");
// 不存在,就没有必要获取
if (f2.exists()){
System.out.println(f2.isDirectory());
System.out.println(f2.isFile());
}
}
/*
public boolean exists(): 此File表示的文件或目录是否存在
用于判断构造方法中的路径是否存在
存在:true
不存在:false
*/
private static void show01() {
File f1 = new File("C:\\Users\\Think-Pad\\Desktop\\java\\if语句.jpg");
System.out.println(f1.exists()); // true
File f2 = new File("C:\\Users\\Think-Pad\\Desktop\\java\\if.jpg");
System.out.println(f2.exists()); // false
}
}
18.5 File类创建删除功能的方法
package cn.code.Demo019;
import java.io.File;
import java.io.IOException;
/*
File类创建删除功能的方法
public boolean createNewFile(): 当且仅当具有该名称的文件尚不存在在时,创建一个新的空文件
public boolean delete(): 删除由此File表示的文件后或目录
public boolean mkdir(): 创建由此File表示的目录
public boolean mkdirs(): 创建由此File表示的目录,包括任何必须但不存在的父目录
*/
public class Demo05File {
public static void main(String[] args) throws IOException {
show03();
}
/*
public boolean delete(): 删除由此File表示的文件后或目录
此方法,可以删除构造方法路径中给出的文件/文件夹
返回值:布尔值
true:文件/文件夹删除成功,返回true
false:文件夹中有内容,不会删除返回false;构造方法中路径不存在false
注意:
delete方法是直接在硬盘删除文件/文件夹,不走回收站,删除更谨慎
*/
private static void show03() {
File f1 = new File("2.txt");
boolean b1 = f1.delete();
System.out.println("b1 = " + b1);
}
/*
public boolean mkdir(): 创建单级空文件夹
public boolean mkdirs(): 既可以创建单级空文件夹,也可以创建多级文件夹
创建文件夹的路径和名称在构造方法中给出(构造方法的参数)
返回值:
true:文件夹不存在,创建文件夹,返回true
false:文件夹存在,不会创建,返回false;构造方法中给出的路径不存在返回false
注意:
1. 此方法只能创建文件夹,不能创建文件
*/
private static void show02() {
File f1 = new File("aaa");
boolean b1 = f1.mkdir();
System.out.println("b1 = " + b1);
File f2 = new File("E:\\IdeaProjects\\mmb\\bbb\\ccc");
boolean b2 = f1.mkdirs();
System.out.println("b2 = " + b2);
}
/*
public boolean createNewFile(): 当且仅当具有该名称的文件尚不存在在时,创建一个新的空文件
创建文件的路径和名称在构造方法中给出(构造方法的参数)
返回值:布尔值
true:文件不能存在,创建文件,返回true
false:文件存在,不会创建,返回false
注意:
1. 此方法只能创建文件,不能创建文件夹
2. 创建文件的路径必须存在,否则会抛出异常
public boolean createNewFile() throws IOException
createNewFile声明抛出了IOException,我们调用这个方法,就必须的处理这个异常,要么throws,要么tryctch
*/
private static void show01() throws IOException {
File f1 = new File("E:\\IdeaProjects\\1.txt");
boolean b1 = f1.createNewFile();
System.out.println(b1);
/*File f2 = new File("IdeaProjects\\2.txt");
System.out.println(f2.createNewFile());*/ // IOException
File f3 = new File("basic-code\\新建文件夹1");
System.out.println(f3.createNewFile()); // 不要被名称迷惑,要看类型
}
}
18.6 File类遍历(文件夹)目录功能
package cn.code.Demo019;
import java.io.File;
/*
File类遍历(文件夹)目录功能
public String[] list(): 返回一个String数组,表示File目录中的所有子文件或目录
public File[] listFiles(): 返回一个File数组,表示该File目录中的所有子文件或目录
注意:
list方法和listFiles方法遍历的是构造方法中给出的目录
如果构造方法中给出的目录路径不存在,会抛出空指针异常
如果构造方法中给出的目录路径不是一个目录,会抛出空指针异常
*/
public class Demo06File {
public static void main(String[] args) {
show02();
}
/*
public File[] listFiles(): 返回一个File数组,表示该File目录中的所有子文件或目录
遍历构造方法中给出的目录,会获取目录中所有文件/文件夹的名称,把文件/文件夹封装为File对象,多个File对象存储到File数组中
*/
private static void show02() {
File file = new File("E:\\IdeaProjects");
File[] files = file.listFiles();
for (File file1: files){
System.out.println("file1 = " + file1); // file1 = E:\IdeaProjects\1.txt file1 = E:\IdeaProjects\basic-code
}
}
/*
public String[] list(): 返回一个String数组,表示File目录中的所有子文件或目录(可以查看被隐藏的文件和文件夹)
遍历构造方法中给出的目录,会获取目录中所有文件/文件夹的名称,把获取到的多个名称存储到一个String类型的数组中
*/
private static void show01() {
File file = new File("E:\\IdeaProjects");
String[] arr = file.list();
for (String fileName: arr){
System.out.println("fileName = " + fileName); // fileName = 1.txt fileName = basic-code
}
}
}
18.7 递归概念&分类&注意事项
package cn.code.Demo020;
/*
递归:方法自己调用自己
递归的分类:
递归分为两种,直接递归和间接递归
直接递归称为方法自身调用自己
间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法
注意事项:
递归一定要有条件限定,保证递归能够停下来,否则会发生栈内存溢出
在递归中虽然有限定条件,但是递归次数不能太多,否则也会发生栈内存溢出
构造方法,禁止递归
递归的使用前提:
当调用方法的时候,方法的主题不变,每次调用方法的参数不同,可以使用递归
*/
public class Demo01Recurison {
public static void main(String[] args) {
a();
}
/*
递归一定要有条件限定,保证递归能够停下来,否则会发生栈内存溢出
Exception in thread "main" java.lang.StackOverflowError
*/
private static void a() {
System.out.println("a方法!");
// a();
b(1);
}
/*
构造方法,禁止递归
编译报错:构造方法是创建对象使用的,一直递归会导致内存中有无数多个对象
*/
public Demo01Recurison(){
Demo01Recurison();
}
/*
在递归中虽然有限定条件,但是递归次数不能太多,否则也会发生栈内存溢出
11159
Exception in thread "main" java.lang.StackOverflowError
*/
private static void b(int i) {
System.out.println(i);
if (i == 20000){
return; // 结束方法
}
b(++i);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DUic2ofd-1573823584880)(C:\Users\Think-Pad\Desktop\java\递归.jpg)]
18.7.1 使用递归计算1~n之间的和
package cn.code.Demo020;
/*
练习:
使用递归计算1~n之间的和
*/
public class Demo02Recurison {
public static void main(String[] args) {
int s = sum(36);
System.out.println(s);
}
/*
定义一个方法,使用递归计算1~n之间的和
1+2+3+...+n
n+(n-1)+(n-2)+...+1
已知:
最大值:n
最小值:1
使用递归必须明确:
1. 递归的结束条件
获取到1的时候
2. 递归的目的
获取下一个被加的数字(n-1)
*/
public static int sum(int n){
// 获取到1的时候结束
if (n == 1){
return 1;
}
// 获取下一个被加的数字
return (n + sum(n-1));
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YLBUpMo7-1573823584896)(C:\Users\Think-Pad\Desktop\java\递归求和.jpg)]
18.7.2 使用递归计算阶乘
package cn.code.Demo020;
/*
练习:
使用递归计算阶乘
n的阶乘:n! = n * (n-1) *... * 3 * 2 * 1
*/
public class Demo03Recurison {
public static void main(String[] args) {
int jiecheng = jc(5);
System.out.println(jiecheng); // 120
}
/*
定义方法使用递归计算阶乘
5的阶乘:5! = 5 * (5-1) *... * 3 * 2 * 1 = 5*4*3*2*1
递归结束的条件
获取到1的时候结束
递归的目的
获取下一个被乘的数字(n-1)
方法的参数发生变化
5,4,3,2,1
*/
public static int jc(int n){
// 获取到1的时候结束
if (n == 1){
return 1;
}
// 获取下一个被乘的数字(n-1)
return (n * jc(n-1));
}
}
18.7.3 递归打印多级目录
package cn.code.Demo020;
import java.io.File;
/*
练习:递归打印多级目录
*/
public class Demo04Recurison {
public static void main(String[] args) {
getAllFile(new File("E:\\IdeaProjects"));
}
/*
定义一个方法,参数传递File类型的目录
方法中对目录进行遍历
*/
public static void getAllFile(File dir){
System.out.println(dir);
File[] files = dir.listFiles();
for (File f : files){
// 对遍历得到的File对象f进行判断,判断是否是文件夹
if (f.isDirectory()){
// f是一个文件夹,则继续遍历这个文件夹
// 我们发现getAllFile方法就是传递文件夹,遍历文件夹的方法
// 所以直接调用getAllFile方法即可:递归(自己调用自己)
getAllFile(f);
}else {
System.out.println(f);
}
}
}
}
18.7.4 综合案例:文件搜索
package cn.code.Demo020;
import java.io.File;
public class Demo04Recurison {
public static void main(String[] args) {
getAllFile(new File("E:\\IdeaProjects"));
}
/*
定义一个方法,参数传递File类型的目录
方法中对目录进行遍历
*/
public static void getAllFile(File dir){
System.out.println(dir);
File[] files = dir.listFiles();
for (File f : files){
// 对遍历得到的File对象f进行判断,判断是否是文件夹
if (f.isDirectory()){
// f是一个文件夹,则继续遍历这个文件夹
// 我们发现getAllFile方法就是传递文件夹,遍历文件夹的方法
// 所以直接调用getAllFile方法即可:递归(自己调用自己)
getAllFile(f);
}else {
// f是一个文件,直接打印即可
/*
c:\\abc\\abc.java
只要.java结尾的文件
1. 把File对象f,转化为字符串对象
*/
// String name = f.getName(); // abc.java
// String path = f.getPath(); // c:\\abc\\abc.java
// String s = f.toString(); // c:\\abc\\abc.java
// 把字符串转换为小写
// s.toLowerCase();
// 2. 调用String类中的方法endswith判断字符串是否是以.java结尾
// boolean b = s.endsWith(".java");
// 3. 如果是以.java结尾的文件,则输出
// if (b){
// System.out.println(f);
// }
if (f.getName().toLowerCase().endsWith(".java")){
System.out.println(f);
}
}
}
}
}
18.8 FileFilter过滤器的原理和使用
package cn.code.Demo020;
import java.io.File;
/*
只要.java文件
我们可以使用过滤器来实现
在File类中有两个和listFiles重载的方法,方法的参数传递的就是过滤器
File[] listFiles(FileFilter filter)
java.io.FileFilter接口:用于抽象路径名(File对象)的过滤器
作用:用来过滤文件(File对象)
抽象方法:用来过滤文件的方法
boolean accept(File pathname) 测试指定抽象路径是否应该包含在某个路径名列表中
参数:
File pathname:使用ListFiles方法遍历目录,得到的每一个文件对象
File[] listFiles(FilenameFilter filter)
作用:用来过滤文件名称
抽象方法:用来过滤文件的方法
boolean accept(File dir,String name) 测试指定文件是否应该包含在某一文件列表中
参数:
File dir:构造方法中传递的被遍历的目录
String name:使用ListFiles方法遍历目录,获取到每一个文件/文件夹的名称
注意:
两个过滤器接口是没有实现类的,需要我们自己写实现类,重写过滤的方法accept,在方法中自己定义过滤的规则
*/
public class Demo01Fileter {
public static void main(String[] args) {
getAllFile(new File("E:\\IdeaProjects"));
}
public static void getAllFile(File dir) {
File[] files = dir.listFiles(new FileFilterImpl()); // 传递过滤对象
for (File f : files){
if (f.isDirectory()){
getAllFile(f);
}else {
System.out.println(f);
}
}
}
}
package cn.code.Demo020;
import java.io.File;
import java.io.FileFilter;
/*
创建过滤器FileFilter的实现类,重写过滤方法accept,定义过滤规则
*/
public class FileFilterImpl implements FileFilter {
@Override
public boolean accept(File pathname) {
/*
过滤规则:
在accept方法中,判断File对象是否是以.java结尾
是就返回true
不是就返回false
*/
// 如果pathname是一个文件夹,返回true,继续遍历这个文件夹
if (pathname.isDirectory()){
return true;
}
return pathname.getName().toLowerCase().endsWith(".java");
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-luFZ6Yxx-1573823584898)(C:\Users\Think-Pad\Desktop\java\过滤器.jpg)]
18.8.1 FilenameFilter过滤器的使用和lambda简化程序
package cn.code.Demo020;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
public class Demo02Fileter {
public static void main(String[] args) {
getAllFile(new File("E:\\IdeaProjects"));
}
public static void getAllFile(File dir) {
// 传递过滤对象 使用匿名内部类
/*File[] files = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
// 过滤规则,pathname是文件夹或者是.java结尾的文件返回true
return pathname.isDirectory() || pathname.getName().toLowerCase().endsWith(".java");
}
} );*/
// 使用lambda表达式简化匿名内部类(接口中只有一个抽象方法)
// File[] files = dir.listFiles((File pathname) -> pathname.isDirectory() || pathname.getName().toLowerCase().endsWith(".java"));
/* File[] files = dir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
// 过滤规则,pathname是文件夹或者是.java结尾的文件返回true
return new File(dir,name).isDirectory() || name.toLowerCase().endsWith(".java");
}
});*/
// 使用lambda表达式简化匿名内部类(接口中只有一个抽象方法)
File[] files = dir.listFiles((d,name) -> new File(dir,name).isDirectory() || name.toLowerCase().endsWith(".java"));
for (File f : files){
if (f.isDirectory()){
getAllFile(f);
}else {
System.out.println(f);
}
}
}
}
第十九章 IO流
19.1 IO的概念
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dJEZ4nA3-1573823584905)(C:\Users\Think-Pad\Desktop\java\IO概念.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ogQfq6Do-1573823584907)(C:\Users\Think-Pad\Desktop\java\FileOutputStream.jpg)]
19.2 字节输出流
19.2.0 字节输出流写多个字节的方法
package cn.code.Demo021;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
/*
一次写多个字节的方法:
public void write(byte[] b): 将b.length字节从指定的字节数组写入此输出流
public void write(byte[] b,int off ,int len): 从指定的字节数组写入len字节,从偏移量off开始输出到此输出流
*/
public class Demo02OutputStream {
public static void main(String[] args) throws IOException {
// 1. 创建一个FileOutputStream对象,构造方法中传递写入数据的目的地
FileOutputStream fos = new FileOutputStream("E:\\IdeaProjects\\1.txt");
// 2. 调用FileOutputStream对象中的方法write,把数据写入到文件中
// 在文件中显示100,写个字节
fos.write(49);
fos.write(48);
fos.write(48);
/*
public void write(byte[] b): 将b.length字节从指定的字节数组写入此输出流
一次写多个字节:
如果写的第一个字节正数(0~127),那么显示的时候会查询ASCII表
如果写的第一个字节是负数,那第一个字节会和第二个字节,两个字节组成一个中文显示,查询系统默认码表(GBK)
*/
byte[] bytes = {65,66,67,68,69}; // ABCDE
// byte[] bytes = {-65,-66,-67,68,69}; // 烤紻E
fos.write(bytes);
/*
public void write(byte[] b,int off ,int len): 把字节数组的一部分写入到文件中
int off : 数组的开始索引
int len : 写几个字节
*/
fos.write(bytes,1,2); // BC
/*
写入字符的方法:可以使用String类中的方法把字符串,转换为字节数组
byte[] getBytes() 把字符串转换为字节数组
*/
byte[] bytes2 = "你好".getBytes();
System.out.println(Arrays.toString(bytes2)); // [-28, -67, -96, -27, -91, -67]
fos.write(bytes2);
// 释放资源
fos.close();
}
}
19.2.1 字节输出流的续写和换行
package cn.code.Demo021;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/*
追加写/续写:使用两个参数的构造方法
FileOutStream(String name,boolean append)创建一个向具有指定 name 的文件中写入数据的输出文件流
FileOutStream(File file,boolean append)创建一个向具有指定 file 对象表示的文件中写入数据的文件输出流
参数:
String name,File file: 写入数据的目的地
boolean append:追加写开关
true:创建对象不会覆盖源文件,继续在文件的末尾追加写数据
false:创建一个新文件,覆盖源文件
写换行:写换行符号
windows: \r\n
linux: /n
mac: /r
*/
public class Demo03OutputStream {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("E:\\IdeaProjects\\2.txt",true);
for (int i = 0; i < 10; i++) {
fos.write("你好".getBytes());
fos.write("\r\n".getBytes());
}
fos.close();
}
}
19.2.2 字节输出流写入数据到文件
package cn.code.Demo021;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/*
java.io.OutputStream: 字节输出流
此抽象类是表示输出字节列的所有类的超类
定义了一些子类共性的成员方法:
public void close(): 关闭此输出流并释放与此流相关的任何系统资源
public void flush(): 刷新此输出流并强制任何缓冲流的输出字节被写出
public void write(byte[] b): 将b.length 字节从指定的字节数组写入此输出流
public void write(byte[] b,int off,int len): 从指定的字节数组写入len字节,从偏移量 off开始输出到此输出流
public abstract void write(int b): 将指定的字节输出流
java.io.FileOutputStream extends OutputStream
FileOutputStream: 文件字节输出流
作用:把内存中的数据写入到硬盘的文件中
构造方法:
FileOutputStream(String name)创建一个向指定名称的文件中写入数据的输出文件流
FileOutputStream(File file)创建一个向指定 File 对象表示的文件中写入数据的文件输出流
参数:写入数据的目的
String name:目的地是一个文件的路径
File file:目的地是一个文件
构造方法的作用:
1. 创建一个FileOutputStream对象
2. 会根据构造方法中传递的文件/文件路径,创建一个空的文件
3. 会把FileOutputStream对象指向创建好的文件
写入数据的原理(内存 --> 硬盘)
java程序 --> JVM(java虚拟机) --> OS(操作系统) --> OS调用写数据的方法 --> 把数据写入到文件中
字符输出流的使用步骤(重点):
1. 创建一个FileOutputStream对象,构造方法中传递写入数据的目的地
2. 调用FileOutputStream对象中的方法,把数据写入到文件中
3. 释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提供程序的效率)
*/
public class Demo01OutputStream {
public static void main(String[] args) throws IOException {
// 1. 创建一个FileOutputStream对象,构造方法中传递写入数据的目的地
FileOutputStream fos = new FileOutputStream("E:\\IdeaProjects\\1.txt");
// 2. 调用FileOutputStream对象中的方法write,把数据写入到文件中
// public abstract void write(int b): 将指定的字节输出流
fos.write(85);
// 3. 释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提供程序的效率)
fos.close();
}
}
19.2.3 写出单个字符到文件
package cn.code.Demo021;
import java.io.FileWriter;
import java.io.IOException;
/*
java.io.Writer: 字符输出流,是所有字符输出流的最高顶层的父类,是一个抽象类
共性的成员方法:
public write(int c) 写入单个字符
void write(char[] cbuf) 写入字符数组
abstract void write(char[] cbuf,int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
void flush() 刷新该流的缓冲
void close() 关闭此流 ,但要先刷新它
java.io.FileWriter extends OutputStreamWriter extends Writer
FileWriter: 文件字符输出流
作用:把内存中的字符数据写入到文件中
构造方法:
FileWriter(File file) 根据给定的 File 对象构造一个 FileWriter 对象
FileWriter(String fileName) 根据给定的文件名构造一个FileWriter 对象
参数:写入数据的目的地
String fileName: 文件的路径
File file:是一个文件
构造方法的作用:
1. 会创建一个FileWriter对象
2. 会根据构造方法中传递的文件/文件路径,创建文件
3. 会把FileWriter对象指向创建好的文件
字符输出流的使用步骤(重点):
1. 创建FileWriter对象,构造方法中绑定要写入数据的目的地
2. 使用FileWriter中的方法writer,把数据写入到内存的缓冲区中(字符转换为字节的过程)
3. 使用FileWriter中的方法flush,把内存缓冲区中的数据,刷新到文件中
4. 释放资源(会先把内存缓冲区中的数据刷新到文件中)
*/
public class Demo01Writer {
public static void main(String[] args) throws IOException {
// 1. 创建FileWriter对象,构造方法中绑定要写入数据的目的地
FileWriter fw = new FileWriter("E:\\IdeaProjects\\1.txt");
// 2. 使用FileWriter中的方法writer,把数据写入到内存的缓冲区中(字符转换为字节的过程)
// public write(int c) 写入单个字符
fw.write('号');
// 3. 使用FileWriter中的方法flush,把内存缓冲区中的数据,刷新到文件中
fw.flush();
// 4. 释放资源(会先把内存缓冲区中的数据刷新到文件中)
fw.close();
}
}
19.2.4 flush方法与close方法的区别
import java.io.FileWriter;
import java.io.IOException;
public class Demo01Writer {
public static void main(String[] args) throws IOException {
// 1. 创建FileWriter对象,构造方法中绑定要写入数据的目的地
FileWriter fw = new FileWriter("E:\\IdeaProjects\\1.txt");
// 2. 使用FileWriter中的方法writer,把数据写入到内存的缓冲区中(字符转换为字节的过程)
// public write(int c) 写入单个字符
fw.write('号');
// 3. 使用FileWriter中的方法flush,把内存缓冲区中的数据,刷新到文件中
fw.flush();
// 刷新之后流可以继续使用
fw.write(98);
// 4. 释放资源(会先把内存缓冲区中的数据刷新到文件中)
fw.close();
// close方法之后,流已经关闭了,已经从内存中消失了,流就不能再使用了
fw.write(99); // java.io.IOException
}
}
19.2.5 字符输出流写数据的其他方法
package cn.code.Demo021;
import java.io.FileWriter;
import java.io.IOException;
/*
字符输出流写数据的其他方法
void write(char[] cbuf) 写入字符数组
abstract void write(char[] cbuf,int off,int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
void write(String str) 写入字符串
void write(String str,int off , int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
*/
public class Demo03Writer {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("E:\\IdeaProjects\\1.txt");
char[] cs = {'a','b','c','d'};
// void write(char[] cbuf) 写入字符数组
fw.write(cs); // abcd
// void write(char[] cbuf,int off,int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
fw.write(cs,0,2);
// void write(String str) 写入字符串
fw.write("我想当空军!!!");
// void write(String str,int off , int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
fw.write("飞行员",0,3);
fw.close();
}
}
19.2.6 字符输出流的续写和换行
package cn.code.Demo021;
import java.io.FileWriter;
import java.io.IOException;
/*
续写和换行:
续写,追加写:使用两个参数的构造方法
FileWriter(String filenNmae,boolean append)
FileWriter(File file,boolean append)
参数:
String filenNmae,File file :写入数据的目的地
boolean append:续写开关 true:不会创建新的文件覆盖文件,可以续写; false:创建新的文件覆盖源文件
换行:换行符号
windows: \r\n
linux: /n
mac: /r
*/
public class Demo04Writer {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("E:\\IdeaProjects\\1.txt",true);
for (int i = 0; i < 10; i++) {
fw.write("我要当空军!!!" + "\r\n");
}
fw.close();
}
}
19.3 字节输入流
19.3.1 字节输入流读取字节数据
package cn.code.Demo021;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/*
java.io.InputStream: 字节输入流
此抽象类是表示字节输入流的所有类是超类
定义了所有子类共性的方法:
int read() 从输入流中读取数据的下一个字节
int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中
void close() 关闭此输入流并释放与该流关联的所有系统资源
java.io.FileInputStream extends InputStream
FileInputStream:文件字节输入流
作用:把硬盘文件中的数据,读取到内存中使用
构造方法:
FileInputStream(String name)
FileInputStream(File file)
参数:读取文件的数据源
String name: 文件的路径
File file: 文件
构造方法的作用:
1. 会创建一个FileInputStream对象
2. 会把FileInputStream对象指定构造方法中要读取的文件
读取数据的原理(硬盘 --> 内存)
java程序 --> JVM --> OS --> OS读取数据的方法 --> 读取文件
字节输入流的使用步骤(重点):
1. 创建FileInputStream对象,构造方法中绑定要读取的数据源
2. 使用FileInputStream对象中的方法read,读取文件
3. 释放资源
*/
public class Demo01InputStream {
public static void main(String[] args) throws IOException {
// 1. 创建FileInputStream对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("E:\\IdeaProjects\\1.txt");
// 2. 使用FileInputStream对象中的方法read,读取文件
// int read() 读取文件中的一个字节并返回,读取到文件的末尾返回 -1
/*int len1 = fis.read();
System.out.println(len1);
int len2 = fis.read();
System.out.println(len2);
int len3 = fis.read();
System.out.println(len3);
int len4 = fis.read();
System.out.println(len4);
int len5 = fis.read();
System.out.println(len5); // 读取结束,就会打印 -1
int len6 = fis.read();
System.out.println(len6); */ // 读取结束后再读取,打印的还是 -1
/*
发现以上读取的文件是一个重复的过程,所以可以使用循环优化
不知道文件中有多少字节,使用while循环
while循环结束条件,读取到-1的时候结束
布尔表达式(len = fis.read()) != -1
1. fis.read():读取一个字节
2. len = fis.read():把读取到的字节赋值给变量len
3. (len = fis.read()) != -1:判断变量len是否不等于 -1
*/
int len = 0; // 记录读取到的字节
while((len = fis.read()) != -1){
System.out.println(len);
}
// 3. 释放资源
fis.close();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EqbLdnC8-1573823584908)(C:\Users\Think-Pad\Desktop\java\读取文件数据原理.jpg)]
19.3.2 字节输入流一次读取多个字节
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JfQppyKv-1573823584910)(C:\Users\Think-Pad\Desktop\java\一次读取多个字节.jpg)]
package cn.code.Demo021;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
/*
字节输入流一次读取多个字节的方法:
int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中
明确两件事情:
1. 方法的参数byte[]的作用?
起到缓冲作用,存储每次读取到的多个字节
数组的长度一把定义为1024(1kb) 或者1024 的整数倍
2. 方法的返回值int 是什么
每次读取的有效字节个数
String类的构造方法
String(byte[] bytes):把字节数组转换为字符串
String(byte[] bytes,int off, int length):把字节数的一部分转换为字符串 offset:数组的开始索引 length:转换的字节个数
*/
public class Demo02InputStream {
public static void main(String[] args) throws IOException {
// 1. 创建FileInputStream对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("E:\\IdeaProjects\\1.txt");
// 2. 使用FileInputStream对象中的方法read,读取文件
// int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中
/*byte[] bytes = new byte[2];
int len = fis.read(bytes);
System.out.println(len); // 2
// System.out.println(Arrays.toString(bytes)); // [49, 48]
System.out.println(new String(bytes)); // 10
int len1 = fis.read(bytes);
System.out.println(len1); // 2
System.out.println(new String(bytes)); // 0A
int len2 = fis.read(bytes);
System.out.println(len2); // 1
System.out.println(new String(bytes)); // BA
int len3 = fis.read(bytes);
System.out.println(len3); // -1
System.out.println(new String(bytes)); // BA*/
/*
发现以上读取时一个重复的过程,可以使用循环优化
不知道文件中有多少字节,所以使用while循环
while循环结束是条件,读取到-1结束
*/
byte[] bytes = new byte[1024]; // 存储读取到的多个字节
int len = 0; // 记录每次读取的有效字节个数
while ((len = fis.read(bytes)) != -1){
// String(byte[] bytes,int off, int length):把字节数的一部分转换为字符串 offset:数组的开始索引 length:转换的字节个数
System.out.println(new String(bytes,0,len)); // 100AB
}
// 3. 释放资源
fis.close();
}
}
19.3.3 文件复制
package cn.code.Demo021;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/*
文件复制练习:一读一写
明确:
数据源:E:\IdeaProjects\1.txt
数据的目的地:E:\1.txt
文件复制的步骤:
1. 创建一个字节输入流,构造方法中绑定要读取的数据源
2. 创建一个字节输出流,构造方法中绑定要写入的目的地
3. 使用字节输入流中的方法read读取文件
4. 使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中
5. 释放资源
*/
public class Demo01CopyFile {
public static void main(String[] args) throws IOException {
// 1. 创建一个字节输入流,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("E:\\IdeaProjects\\2.txt");
// 2. 创建一个字节输出流,构造方法中绑定要写入的目的地
FileOutputStream fos = new FileOutputStream("E:\\2.txt");
// 一次读取一个字节写入一个字的方式
// 3. 使用字节输入流中的方法read读取文件
/*int len = 0;
while ((len = fis.read()) != -1){
// 4. 使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中
fos.write(len);
}*/
// 使用数组缓冲读取多个字节,写入多个字节
byte[] bytes = new byte[1024];
// 3. 使用字节输入流中的方法read读取文件
int len = 0; // 每次读取的有效字节个数
while ((len = fis.read(bytes)) != -1){
// 4. 使用字节输出流中的方法write,把读取到的字节写入到目的地的文件中
fos.write(bytes,0,len);
}
// 5. 释放资源(先关写的,后关闭读的;如果写完了,肯定读取完毕了)
fos.close();
fis.close();
}
}
19.3.4 使用字节流读取中文的问题
package cn.code.Demo021;
import java.io.FileInputStream;
import java.io.IOException;
/*
使用字节轮流读取中文文件
1个中文
GBK:占用两个字节
UTF-8:占用3个字节
*/
public class Reader {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("E:\\IdeaProjects\\1.txt");
int len = 0;
while ((len = fis.read()) != -1){
System.out.println((char) len); // æ
}
fis.close();
}
}
19.3.5 字符输入流读取字符数据
package cn.code.Demo021;
import java.io.FileReader;
import java.io.IOException;
/*
java.io.Reader: 字符输入流,是字符输入流的最顶层的父类,定义了一些共性的成员方法,是一个抽象类
共性的成员方法:
int read() 读取单个字符并返回
int read(char[] cbuf) 一次读取多个字符,将字符读入数组
void close() 关闭该流并释放与之关联的所有资源
java.io.Reader extends InputStreamReader exends Reader
FileReader: 文件字符输入流
作用:把硬盘文件中的数据以字符的方式读取到内存中
构造方法:
FileReader(String fileName)
FileReader(File file)
参数:读取文件的数据源
String fileName:文件的路径
File file:一个文件
FileReader构造方法的作用:
1. 创建一个FileReader对象
2. 会把FileReader对象指向要读取的文件
字符输入流的使用步骤:
1. 创建FileReader对象,构造方法中绑定要读取的数据源
2. 使用FileReader对象中的方法read读取文件
3. 释放资源
*/
public class Demo02Reader {
public static void main(String[] args) throws IOException {
// 1. 创建FileReader对象,构造方法中绑定要读取的数据源
FileReader fr = new FileReader("E:\\IdeaProjects\\1.txt");
// 2. 使用FileReader对象中的方法read读取文件
// int read() 读取单个字符并返回
/*int len = 0;
while ((len = fr.read()) != -1){
System.out.println((char)len);
}*/
// int read(char[] cbuf) 一次读取多个字符,将字符读入数组
char[] cs = new char[1024]; // 存储读取到的多个字符
int len = 0; // 记录的是每次读取的有效字符个数
while ((len = fr.read(cs)) != -1){
/*
String类的构造方法
String(byte[] bytes):把字节数组转换为字符串
String(byte[] bytes,int off, int length):把字节数的一部分转换为字符串 offset:数组的开始索引 length:转换的字节个数
*/
System.out.println(new String(cs,0,len)); // 100AB我
}
// 3. 释放资源
fr.close();
}
}
19.4 使用try_catch_finally处理流中的异常
package cn.code.Demo021;
import java.io.FileWriter;
import java.io.IOException;
/*
在JDK1.7 之前使用try catch finally 处理流中的异常
格式:
try{
可能会产生异常的代码
}catch(异常类变量 变量名){
异常的处理逻辑
}finally{
一定会执行的代码
资源释放
}
JDK1.7 的新特性
在try的后边可以增加一个(),在括号中可以定义流对象
那么这个流对象的作用域就在try中有效
try中的代码执行完毕,会自动把流对象释放,不用写finally
格式:
try(定义流对象; 定义流对象...){
可能会产生异常的代码
}catch(异常类变量 变量名){
异常的处理逻辑
}
*/
public class Demo01TryCatch {
public static void main(String[] args) {
// 提高变量fw的作用域,让finally可以使用
// 变量咋一定义的时候,可以没有值,但是使用的时候必须有值
FileWriter fw = null;
try{
// 可能会产生异常的代码
fw = new FileWriter("E:\\IdeaProjects",true);
for (int i = 0; i < 10; i++) {
fw.write("我要当空军,报效祖国!!!" + "\r\n");
}
}catch(IOException e){
// 异常的处理逻辑
System.out.println(e);
}finally{
// 一定会执行的代码
if (fw != null){
try {
// fw.close()方法声明抛出了IOException异常对象,所以我们就得处理这个异常对象,要么throws,要么 try catch
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
19.5 Properties集合
19.5.1 使用Properties集合存储数据,遍历出Properties集合中的数据
package cn.code.Demo022;
import java.util.Properties;
import java.util.Set;
/*
java.util.Properties集合 extends Hashtable<k,v> implements Map<k,v>
Properties 类表示了一个持久的属性集。 Properties 可保存在流中或从流中加载。
Properties集合是一个唯一和IO流相结合的集合
可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入硬盘中存储
可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
属性列表中每个键及其对应值都是一个字符串
Properties集合是一个双列集合,key和value默认都是字符串
*/
public class Demo01Properties {
public static void main(String[] args) {
show01();
}
/*
使用Properties集合存储数据,遍历取出Properties集合中的数据
Properties集合是一个双列集合,key和value默认都是字符串
Properties集合有一些操作字符串的特有方法
Object setProperty(String key,String value)调用Hashtable 的方法 put
String getProperty(String key) 通过key找到value值,此方法相当于Map集合中的get(key)方法
Set<String> stringPropertyNames() 返回此属性列表中的链集,其中该键及其对应是字符串,此方法相当于Map集合中的keySet方法
*/
private static void show01() {
// 创建Properties集合对象
Properties prop = new Properties();
// 使用setProperty往集合中添加数据
prop.setProperty("欧阳娜娜","168");
prop.setProperty("迪丽热巴","168");
prop.setProperty("古力娜扎","168");
// 使用stringPropertyName把Properties集合中的键取出来,存储到一个Set集合中
Set<String> strings = prop.stringPropertyNames();
// 遍历Set集合,取出Properties集合的每一个键
for (String key : strings){
// 使用getProperty方法通过key获取value
String value = prop.getProperty(key);
System.out.println(key + "身高" +value);
}
}
}
19.5.2 Properties集合中的方法store
/*
可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
void store(OutputStream out, String comments)
void store(Write write, String comments)
参数:
OutputStream out:字节输出流,不能写中文
Write write:字符输出流,可以写中文
String comments:注释,用来解释说明保存的文件是做什么用的
不能使用中文,会产生乱码,默认是Unicode编码
一般使用"" 空字符串
使用步骤:
1. 创建Properties集合对象,添加数据
2. 创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
3. 使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
4. 释放资源
*/
private static void show02() throws IOException {
// 1. 创建Properties集合对象
Properties prop = new Properties();
// 使用setProperty往集合中添加数据
/*prop.setProperty("欧阳娜娜","168");
prop.setProperty("迪丽热巴","168");
prop.setProperty("古力娜扎","168");*/
// 2. 创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
// FileWriter fw = new FileWriter("E:\\IdeaProjects\\2.txt",true);
// 3. 使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储
// prop.store(fw,"zhixiang");
// 4. 释放资源
// fw.close();
prop.setProperty("99","haha");
prop.store(new FileOutputStream("E:\\\\IdeaProjects\\\\2.txt"),"ui");
}
19.53 Oroperties集合中的方法load
/*
可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用
void load(InputStream inStream)
void load(Reader reader)
参数:
InputStream inStream:字节输出流,不能读取含有中文的键值对
Reader reader:字符输入流,能读取含有中文的键值对
使用步骤:
1. 创建Properties集合对象
2. 使用Properties集合对象中的方法load读取保存键值对的文件
3. 遍历Properties集合
注意:
1. 存储键值对的文件中,键与值默认的连接符号可以使用 =, 空格(其他符号)
2. 存储键值对的文件中,可以使用#进行注释,被注释的键值对不会再被读取
3. 存储键值对的文件中,键值对默认都是字符串,不用再加引号
*/
private static void show03() throws IOException {
// 1. 创建Properties集合对象
Properties prop = new Properties();
// 2. 使用Properties集合对象中的方法load读取保存键值对的文件
prop.load(new FileReader("E:\\IdeaProjects\\1.txt"));
// prop.load(new FileInputStream("E:\\IdeaProjects\\1.txt")); // 出现乱码
// 3. 遍历Properties集合
Set<String> sets = prop.stringPropertyNames();
for (String key : sets){
String value = prop.getProperty(key);
System.out.println(key + "=" + value);
}
}
19.6 缓冲流
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K8qMNG59-1573823584911)(C:\Users\Think-Pad\Desktop\java\缓冲流原理.jpg)]
19.6.1 BufferedOutputStream_字节缓冲输出流
package cn.code.Demo022;
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/*
java.io.BufferedOutputStream extends OutputStream
BufferedOutputStream:字节缓冲输出流
继承自父类的共性成员方法:
public void close(): 关闭此输出流并释放与此流相关的任何系统资源
public void flush(): 刷新此输出流并强制任何缓冲流的输出字节被写出
public void write(byte[] b): 将b.length字节从指定的字节数组写入此输出流
public void write(byte[] b,int off ,int len): 从指定的字节数组写入 len字节,从偏移量 off 开始输出到此输出流
public abstract void write(int b): 将指定的字节输出流写入
构造方法:
BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据写入指定的底层输出流
BufferedOutputStream(OutputStream out,int size) 创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流
参数:
OutputStream out:字节输出流
我们可以传递FileOutputStream,缓冲流会给 FileOutputStream 增加一个缓冲区,提高FileOutputStream的写入效率
int size:指定缓冲流缓冲区的大小,不指定默认
使用步骤(重点)
1. 创建 FileOutputStream对象,构造方法中绑定要输出的目的地
2. 创建 BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream对象写入效率
3. 使用 BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中
4. 使用 BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中
5. 释放资源(会先调用flush方法刷新数据,第4步可以省略)
*/
public class Demo01BufferedOutputStream {
public static void main(String[] args) throws IOException {
// 1. 创建 FileOutputStream对象,构造方法中绑定要输出的目的地
FileOutputStream fos = new FileOutputStream("E:\\IdeaProjects\\2.txt");
// 2. 创建 BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream对象效率
BufferedOutputStream bos = new BufferedOutputStream(fos);
// 3. 使用 BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中
bos.write("我把数据写入到内部缓冲区中".getBytes());
// 4. 使用 BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中
bos.flush();
// 5. 释放资源(会先调用flush方法刷新数据,第4步可以省略)
bos.close();
}
}
19.6.2 BufferedInputStream_字节缓冲输入流
package cn.code.Demo022;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/*
java.io.BufferedInputStream extends InputStream
BufferedInputStream:字节缓冲输入流
继承自父类的成员方法:
int read() 从输入流中读取数据的下一个
int read(byte[] b) 从输入流或者读取一定数量的字节,并将其存储在缓冲区数组 b 中
void close() 关闭此输入流并释放与该流关联的所有系统资源
构造方法:
BufferedInputStream(InputStream in) 创建一个 BufferedInputStream 并保存其参数,即输入流 in ,以便将来使用
BufferedInputStream(InputStream in,int size) 创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用
参数:
InputStream in:字节输入流
我们可以传递FileInputStream,缓冲流会给 FileInputStream 增加一个缓冲区,提高FileInputStream的写入效率
int size:指定缓冲流缓冲区的大小,不指定默认
使用步骤(重点):
1. 创建FileInputStream对象,构造方法中绑定要读取的数据源
2. 创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
3. 使用BufferedInputStream对象中的方法read,读取文件
4. 释放资源
*/
public class Demo02BufferedInputStream {
public static void main(String[] args) throws IOException {
// 1. 创建FileInputStream对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("E:\\IdeaProjects\\2.txt");
// 2. 创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
BufferedInputStream bis = new BufferedInputStream(fis);
// 3. 使用BufferedInputStream对象中的方法read,读取文件
// int read() 从输入流中读取数据的下一个
/*int len = 0; // 记录每次读取到的字节
while ((len = bis.read()) != -1){
System.out.println(len);
}*/
// int read(byte[] b) 从输入流或者读取一定数量的字节,并将其存储在缓冲区数组 b 中
byte[] bytes = new byte[1024]; // 存储每次读取的数据
int len = 0; // 记录每次读取到的有效字节个数
while ((len = bis.read(bytes)) != -1){
System.out.println(new String(bytes,0,len)); // 我把数据写入到内部缓冲区中
}
// 4. 释放资源
bis.close();
}
}
19.6.3 BufferedWrite_字符缓冲输出流
package cn.code.Demo022;
import java.io.*;
/*
java.io.BufferedWriter extends Writer
BufferedWriter:字符缓冲输出流
继承自父类的共性成员方法:
public write(int c) 写入单个字符
void write(char[] cbuf) 写入字符数组
abstract void write(char[] cbuf,int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
void flush() 刷新该流的缓冲
void close() 关闭此流 ,但要先刷新它
构造方法:
BufferedWriter(Writer out) 创建一个使用默认大小输出缓冲区的缓冲字符输出流
BufferedWriter(Writer out , int sz) 创建一个使用给定大小输出缓冲区的新缓冲字符输出流
参数:
Writer out:字符输出流
int sz:指定缓冲区的大小,不写默认大小
特有的成员方法:
void newline() 写入一个行分隔符,会根据不同的操作系统,获取不同的行分隔符
换行:换行符号
windows: \r\n
linux: /n
mac: /r
使用步骤:
1. 创建字符缓冲输出流对象,构造方法中传递字符输出流
2. 调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区中
3. 调用字符缓冲输出流中的方法flush,把内存缓冲区中的数据,刷新到文件中
4. 释放资源
*/
public class Demo03BufferedWriter {
public static void main(String[] args) throws IOException {
// 1. 创建字符缓冲输出流对象,构造方法中传递字符输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\IdeaProjects\\2.txt"));
// 2. 调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区中
for (int i = 0; i < 10; i++) {
bw.write("我想要当空军,报效祖国!!!");
// bw.write("\r\n");
bw.newLine();
}
// 3. 调用字符缓冲输出流中的方法flush,把内存缓冲区中的数据,刷新到文件
bw.flush();
// 4. 释放资源
bw.close();
}
}
19.6.4 BufferedReader_字符缓冲输入流
package cn.code.Demo022;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
/*
java.io.BufferedReader extends Reader
继承自父类的共性成员方法:
int read() 从输入流中读取数据的下一个字节
int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中
void close() 关闭此输入流并释放与该流关联的所有系统资源
构造方法:
BufferedReader(Reader in) 创建一个使用默认大小输入缓冲区的缓冲字符输入流
BufferedReader(Reader in,int sz) 创建一个使用指定大小输入缓冲区的缓冲字符输入流
参数:
Reader in:字符输入流
我们传递FileReader,缓冲区会给FileReader增加一个缓冲区,提高FileReader的读取效率
特有的成员方法:
String readerline() 读取一个文本行。读取一行数据
行的终止符号:通过下列字符之一即可认为某行已终止:换行('\n')、回车('\r')或回车后直接跟着换行(\r\n)
返回值:
包含该行内容的字符串,不包含任何行终止符,如果已到达末尾,则返回 null
使用步骤:
1. 创建字符缓冲输入流对象,构造方法中传递字符输入流
2. 使用字符缓冲输入流对象中的方法read/readline读取文本
3. 释放资源
*/
public class Demo04BufferedReader {
public static void main(String[] args) throws IOException {
// 1. 创建字符缓冲输入流对象,构造方法中传递字符输入流
BufferedReader br = new BufferedReader(new FileReader("E:\\IdeaProjects\\2.txt"));
// 2. 使用字符缓冲输入流对象中的方法read/readline读取文本
/*String s1 = br.readLine();
System.out.println(s1);
String s2 = br.readLine();
System.out.println(s2);
String s3 = br.readLine();
System.out.println(s3);*/
/*
发现以上读取的是一个重复的过程,所以可以使用循环优化
不知道文件中有多少行数据,所以使用while循环
while的结束条件,读到null结束
*/
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
// 3. 释放资源
br.close();
}
}
19.7 转换流
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-femuMn7X-1573823584989)(C:\Users\Think-Pad\Desktop\java\转换流的原理.jpg)]
19.7.1 OutputStreamWriter 介绍&代码实现
package cn.code.Demo022;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
/*
java.io.OutputStreamWriter extends Writer
OutputStreamWriter:是字符流通向字节流的桥梁,可指定的 charset 将要写入流中的字符编码成字节。(编码:把能看懂的变成看不懂的)
继承自父类的共性成员方法:
public write(int c) 写入单个字符
void write(char[] cbuf) 写入字符数组
abstract void write(char[] cbuf,int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
void flush() 刷新该流的缓冲
void close() 关闭此流 ,但要先刷新它
构造方法:
OutputStreamWriter(OutputStream out) 创建使用默认字符编码的 OutputStreamWriter
OutputStreamWriter(OutputStream out,String charsetName) 创建使用指定字符集的OutputStreamWriter
参数:
OutputStream out:字节输出流,可以用来写转换之后的字节到文件中
String charsetName:指定的编码表名称,不区分大小写,可以是 utf-8/UTF-8,gbk/GBK,...不指定默认使用UTF-8
使用步骤:
1. 创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
2. 使用OutputStreamWriter对象中的方法write,把字符转换为字符存储缓冲区中(编码)
3. 使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
4. 释放资源
*/
public class Demo01OutputStreamWriter {
public static void main(String[] args) throws IOException {
write_gbk();
}
/*
使用转换OutputStreamWriter写GBK格式的文件
*/
private static void write_gbk() throws IOException {
// 1. 创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\IdeaProjects\\2.txt"),"GBK"); // 不指定默认使用UTF-8
// 2. 使用OutputStreamWriter对象中的方法write,把字符转换为字符存储缓冲区中(编码)
osw.write("我的理想");
// 3. 使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
osw.flush();
// 4. 释放资源
osw.close();
}
/*
使用转换OutputStreamWriter写UTF-8格式的文件
*/
private static void write_utf_8() throws IOException {
// 1. 创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
// OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\IdeaProjects\\2.txt"),"utf-8");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\IdeaProjects\\2.txt")); // 不指定默认使用UTF-8
// 2. 使用OutputStreamWriter对象中的方法write,把字符转换为字符存储缓冲区中(编码)
osw.write("我的理想");
// 3. 使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
osw.flush();
// 4. 释放资源
osw.close();
}
}
19.7.2 InputStreamWriter 介绍&代码实现
package cn.code.Demo022;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/*
java.io.InputStreamReader extends Reader
InputStreamReader:是字节流通向字符流的桥梁,可指定的 charset 将要写入流中的字节编码成字符。(编码:把能看不懂的变成看懂的)
继承自父类的共性成员方法:
int read() 从输入流中读取数据的下一个
int read(byte[] b) 从输入流或者读取一定数量的字节,并将其存储在缓冲区数组 b 中
void close() 关闭此输入流并释放与该流关联的所有系统资源
构造方法:
InputStreamReader(InptuStream in) 创建一个使用默认字符集的 InputStreamReader
InputStreamReader(InptuStream in ,String charsetName) 创建使用指定字符集的 InputStreamReader
参数:
InptuStream in:字节输入流,用来读取文件中保存的字节
String charsetName:指定的编码表名称,不区分大小写,可以是utf-8/UTF-8,gbk/GBK,...不指定默认使用UTF-8
使用步骤:
1. 创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
2. 使用InputStreamReader对象中的read读取文件
3. 释放资源
注意事项:
构造方法中指定的编码表名称要和文件的编码相同,否则会出现乱码
*/
public class Demo03InputStreamReader {
public static void main(String[] args) throws IOException {
// read_utf_8();
read_GBK();
}
/*
使用InputStreamReader读取GBK格式的文件
*/
private static void read_GBK() throws IOException {
// 1. 创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
InputStreamReader isr = new InputStreamReader(new FileInputStream("1.txt"),"GBK"); // 不指定默认使用UTF-8
// 2. 使用InputStreamReader对象中的read读取文件
int len = 0;
while ((len = isr.read()) != -1){
System.out.println((char)len);
}
// 3. 释放资源
isr.close();
}
/*
使用InputStreamReader读取UTF-8格式的文件
*/
private static void read_utf_8() throws IOException {
// 1. 创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
// InputStreamReader isr = new InputStreamReader(new FileInputStream("1.txt"),"utf-8");
InputStreamReader isr = new InputStreamReader(new FileInputStream("1.txt")); // 不指定默认使用UTF-8
// 2. 使用InputStreamReader对象中的read读取文件
int len = 0;
while ((len = isr.read()) != -1){
System.out.println((char)len);
}
// 3. 释放资源
isr.close();
}
}
19.7.3 转换文件编码
package cn.code.Demo022;
import java.io.*;
/*
练习:转换文件编码
将GBK编码的文件,转换为UTF-8编码的文件
分析:
1. 创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称GBK
2. 创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称UTF-8
3. 使用InputStreamReader对象中的方法read读取文件
4. 使用OutputStreamWriter对象中的方法write,把读取的数据写入到文件中
5. 释放资源
*/
public class Demo04Test {
public static void main(String[] args) throws IOException {
// 1. 创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称GBK
InputStreamReader isr = new InputStreamReader(new FileInputStream("1.txt"),"GBK");
// 2. 创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称UTF-8
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("2.txt"),"UTF-8");
// 3. 使用InputStreamReader对象中的方法read读取文件
int len = 0;
while ((len = isr.read()) != -1){
// 4. 使用OutputStreamWriter对象中的方法write,把读取的数据写入到文件中
osw.write(len);
}
// 5. 释放资源
osw.close();
isr.close();
}
}
19.8 序列化与反序列化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VlfjhaTp-1573823584990)(C:\Users\Think-Pad\Desktop\java\序列化与反序列化.jpg)]
19.8.1 对象的序列化流 ObjectOutputSream
package cn.code.Demo022;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
/*
java.io.ObjectOutputStream extends OutputStream
ObjectOutputStream:对象的序列化流
作用:把对象以流的方式写入到文件中保存
构造:
ObjectOutputStream(OutputStream out) 创建写入指定 OutputStream 的 ObjectOutputStream
参数:
OutputStream ou:字节输出流
特有的成员方法:
void writeObject(Object obj) 将指定的对象写入 ObjectOutputStream
使用步骤:
1. 创建ObjectOutputStream对象,构造方法中传递字节输出流
2. 使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中
3. 释放资源
*/
public class Demo01ObjectOutputStream {
public static void main(String[] args) throws IOException {
// 1. 创建ObjectOutputStream对象,构造方法中传递字节输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("3.txt"));
// 2. 使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中
oos.writeObject(new Person("小美女",18));
// 3. 释放资源
oos.close();
}
}
package cn.code.Demo022;
import java.io.Serializable;
/*
序列化和序列化的时候,会抛出NotSerializableException没有序列化异常
类通过实现 java.io.Serializable 接口以启用其序列化功能。来实现此接口的类将无法使其任何状态序列化或反序列化
Serializable接口也叫标记型接口
要进行序列化和反序列化的类必须实现 Serializable接口,就会给类添加一个标记
当我们进行序列化和反序列化的时候,就会检测类是否有这个标记
有:就可以序列化和反序列化
没有:就会抛出 NotSerializableException 异常
去市场买肉 --> 肉中有一个蓝色章(检测合格) --> 放心购买 --> 买回来怎么吃随意
*/
public class Person implements Serializable {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
19.8.2 对象的反序列化流 ObjectInputStream
package cn.code.Demo022;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
/*
java.io.ObjectInputStream extends InputStream
ObjectInputStream:对象的反序列化流
作用:把文件中保存的对象,以流的方式读取出来使用
构造方法:
ObjectInputStream(InputStream in) 创建从指定 InputStream 读取的 ObjectInputStream
参数:
InputStream in:字节输入流
特有的成员方法:
Object readObject() 从 ObjectInputStream 读取对象
使用步骤:
1. 创建ObjectInputStream对象,构造方法中传递字节输入流
2. 使用ObjectInputStream对象中的方法readObject读取保存对象的文件
3. 释放资源
4. 使用读取出来的对象(打印)
readObject方法声明抛出了ClassNotFoundException(class文件找不到异常)
当存在对象的class文件是抛出异常
反序列化的前提:
1. 类必须实现 Serializable
2. 必须存在类对应的class文件
*/
public class Demo02ObjectInputStream {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 1. 创建ObjectInputStream对象,构造方法中传递字节输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("3.txt"));
// 2. 使用ObjectInputStream对象中的方法readObject读取保存对象的文件
Object o = ois.readObject();
// 3. 释放资源
ois.close();
// 4. 使用读取出来的对象(打印)
System.out.println(o);
Person p = (Person) o;
System.out.println(p.getName() + p.getAge());
}
}
19.8.3 transient关键字——瞬态关键字
package cn.code.Demo022;
import java.io.Serializable;
/*
static 关键字:静态关键字
静态优先于非静态加载到内存中(静态优先于对象进入到内存中)
被static修饰的成员变量不能被序列化的,序列化的都是对象
private static int age;
oos.writeObject(new Person("小美女",18));
Object o = ois.readObject();
Person{name='小美女', age=0}
transient 关键字:瞬间关键字
被transient修饰成员变量,不能被序列化(但是没有static的作用)
private transient int age;
oos.writeObject(new Person("小美女",18));
Object o = ois.readObject();
Person{name='小美女', age=0}
*/
public class Person implements Serializable {
private String name;
// private static int age;
private transient int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
19.8.4 InvalidClassException异常、原理和解决方案
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o8GqkpO7-1573823585028)(C:\Users\Think-Pad\Desktop\java\反序列化异常.jpg)]
19.8.5 序列化集合
package cn.code.Demo022;
import java.io.*;
import java.util.ArrayList;
/*
练习:序列化集合
当我们想咋文件中保存多个对象的时候
可以把多个对象存储到一个集合中
对集合进行序列化和反序列化
分析:
1. 定义一个存储Person对象的ArrarList集合
2. 往ArrayList集合中存储Person对象
3. 创建一个序列化流ObjectOutputStream对象
4. 使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化
5. 创建一个反序列化ObjectInputStream对象
6. 使用ObjectInputStream对象中的方法readObject读取文件中保存的集合
7. 把Object类型的集合转换为ArrayList类型
8. 遍历ArrayList集合
9. 释放资源
*/
public class Demo03Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 1. 定义一个存储Person对象的ArrarList集合
ArrayList<Person> list = new ArrayList<>();
// 2. 往ArrayList集合中存储Person对象
list.add(new Person("张三",18));
list.add(new Person("李四",19));
list.add(new Person("王五",20));
// 3. 创建一个序列化流ObjectOutputStream对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));
// 4. 使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化
oos.writeObject(list);
// 5. 创建一个反序列化ObjectInputStream对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.txt"));
// 6. 使用ObjectInputStream对象中的方法readObject读取文件中保存的集合
Object o = ois.readObject();
// 7. 把Object类型的集合转换为ArrayList类型
ArrayList<Person> list2 = (ArrayList<Person>) o;
// 8. 遍历ArrayList集合
for (Person p : list2) {
System.out.println(p);
}
// 9. 释放资源
oos.close();
ois.close();
}
}
19.9 打印流
package cn.code.Demo022;
import java.io.FileNotFoundException;
import java.io.PrintStream;
/*
java.io.PrintStream:打印流
PrintStream 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式
PrintStream特点:
1. 只负责数据的输出,不负责数据的读取
2. 与其他输出流不同, PrintStream 永远不会抛出 IOException
3. 有特有的方法:print,println
void print(任意类型的值)
void print(任意类型的值并换行)
构造方法:
PrintStream(File file):输出的目的地是一个文件
PrintStream(OutputStream out):输出的目的地是一个字节输出流
PrintStream(String fileName):输出的目的地是一个文件路径
PrintStream extends OutputStream
继承自父类的成员方法:
public void close(): 关闭此输出流并释放与此流相关的任何系统资源
public void flush(): 刷新此输出流并强制任何缓冲流的输出字节被写出
public void write(byte[] b): 将b.length字节从指定的字节数组写入此输出流
public void write(byte[] b,int off ,int len): 从指定的字节数组写入 len字节,从偏移量 off 开始输出到此输出流
public abstract void write(int b): 将指定的字节输出流写入
注意:
如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97-->a
如果使用自己特有的方法print/println写法写数据,写的数据原样输出 97-->97
*/
public class Demo01PrintStream {
public static void main(String[] args) throws FileNotFoundException {
// System.out.println("Hello,World!");
// 创建打印流PrintStream对象,构造方法中绑定要输出的目的地
PrintStream ps = new PrintStream("4.txt");
// 如果使用继承自父类的write写法写数据,那么查看数据的时候会查询编码表 97-->a
ps.write(99);
// 如果使用自己特有的方法print/println写法写数据,写的数据原样输出 97-->97
ps.println(99);
ps.println(9.9);
ps.println('a');
ps.println("Hello,World!");
ps.println(true);
// 释放资源
ps.close();
}
}
package cn.code.Demo022;
import java.io.FileNotFoundException;
import java.io.PrintStream;
/*
可以改变输出语句的目的地(打印流的流向)
输出语句,默认在控制台输出
使用 System.setOut 方法改变输出语句的目的地改为参数中传递的打印流的目的地
static void setOut(PrintStream out)
重新分配“标准”输出流
*/
public class Demo02PrintStream {
public static void main(String[] args) throws FileNotFoundException {
System.out.println("我是在控制台输出");
PrintStream ps = new PrintStream("5.txt");
System.setOut(ps); // 改变输出语句的目的地改为参数中传递的打印流的目的地
System.out.println("我在打印流的目的地中输出");
ps.close();
}
}
第二十章 网络编程
20.1 端口号
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cp4ipMhy-1573823585029)(C:\Users\Think-Pad\Desktop\java\端口号.jpg)]
20.2 TCP通信
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BibrKovf-1573823585035)(C:\Users\Think-Pad\Desktop\java\tcp的通信.jpg)]
20.2.1 TCP通信 —— 客户端&服务器端代码实现
客户端:
package cn.code.Demo023;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/*
TCP通信的客户端:向服务器发送链接请求,给服务器发送数据,读取服务器写的数据
表示客户端的类:
java.net.Socket:此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器键通信的断点
套接字:包含了IP地址和端口号的网络单位
构造方法:
Socket(String host,int port) 创建一个流套接字并将其连接到指定主机上的指定端口号
参数:
String host:服务器主机的名称/服务器的IP地址
int port:服务器的端口号
成员方法:
OutputStream getOutputStream() 返回此套接字的输出流
InputStream getInputStream() 返回此套接字的输出流
void close() 关闭此套接字
实现步骤:
1. 创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
2. 使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
3. 使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
4. 使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
5. 使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
6. 释放资源(Socket)
注意:
1. 客户端和服务器端进行交互,必须使用Socket中提供的网络流,不能使用自己曾经的流对象
2. 当我们曾经客户端对象Ssocket的时候,就会请求服务器和服务器经过3此握手建立连接桐庐
这时如果服务器没有启动,那么就会抛出异常 ConnectException: Connection refused: connect
如果服务器已经启动了,那么就可以进行交互了
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
// 1. 创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
Socket socket = new Socket("127.0.0.1",6666);
// 2. 使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
OutputStream os = socket.getOutputStream();
// 3. 使用网络字节输出流OutputStream对象中的方法write,给服务器发送数据
os.write("你好,服务器!".getBytes());
// 4. 使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
// 5. 使用网络字节输入流InputStream对象中的方法read,读取服务器回写的数据
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new String(bytes,0,len));
// 6. 释放资源(Socket)
socket.close();
}
}
服务器端:
package cn.code.Demo023;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/*
TCP通信的服务器端:接收客户端的请求,读取客户端发送的数据,给客户端回写数据
表示服务器的类:
java.net.ServerSoket:此类实现服务器套接字
构造方法:
ServerSoket(int port) 创建绑定到的特定端口的服务器套接字
服务器必须明确一件事情,必须知道是哪个客户端请求的服务器
所以可以使用accept方法获取到请求到的客户端对象Socket
成员方法:
Socket accept() 监听并接收到此套接字的连接
服务器的实现步骤:
1. 创建服务器ServerSoket对象和系统要指定的端口号
2. 使用ServerSoket对象中的方法accept,获取到请求的客户端对象Socket
3. 使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
4. 使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
5. 使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
6. 使用网络字节输出流OutputStream对象中的方法write,给客户端回写数据
7. 释放资源(Socket, SererSocket)
*/
public class TPCServer {
public static void main(String[] args) throws IOException {
// 1. 创建服务器ServerSoket对象和系统要指定的端口号
ServerSocket server = new ServerSocket(6666);
// 2. 使用ServerSoket对象中的方法accept,获取到请求的客户端对象Socket
Socket socket = server.accept();
// 3. 使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
// 4. 使用网络字节输入流InputStream对象中的方法read,读取客户端发送的数据
byte[] bytes = new byte[1024];
int len = is.read(bytes);
System.out.println(new String(bytes,0,len));
// 5. 使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
OutputStream os = socket.getOutputStream();
// 6. 使用网络字节输出流OutputStream对象中的方法write,给客户端回写数据
os.write("收到,谢谢!".getBytes());
// 7. 释放资源(Socket, SererSocket)
socket.close();
server.close();
}
}
20.3 文件上传案例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BBQSGwq0-1573823585036)(C:\Users\Think-Pad\Desktop\java\文件上传原理.jpg)]
20.3.1 文件上传案例的客户端
package cn.code.Demo023;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/*
文件上传案例的客户端:读取本地文件,上传到服务器,读取服务器回写的数据
明确:
数据源:E:\\IdeaProjects\\1.txt
目的地:服务器
实现步骤:
1. 创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
2. 创建一个客户端Socket对象,构造方法中绑定服务器的IP 地址和端口号
3. 使用Socket中的方法getOutputStream,获取网络字节输出流OutputSream 对象
4. 使用本地字节谁流FileInputStream对象中的方法read,读取本地文件
5. 使用网络字节输出流OutputStream的象中的方法write,把读取到的文件上传到服务器
6. 使用Socket中的方法getInputStream,获取网络字节输出流InputSream 对象
7. 使用网络字节输入流InputStream对象中的方法read读取服务区回写的数据
8. 释放资源(FileInputStream,Socket)
*/
public class FileUpload {
public static void main(String[] args) throws IOException {
// 1. 创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("E:\\IdeaProjects\\1.txt");
// 2. 创建一个客户端Socket对象,构造方法中绑定服务器的IP 地址和端口号
Socket socket = new Socket("127.0.0.1",8888);
// 3. 使用Socket中的方法getOutputStream,获取网络字节输出流OutputSream 对象
OutputStream os = socket.getOutputStream();
// 4. 使用本地字节谁流FileInputStream对象中的方法read,读取本地文件
int len = 0;
byte[] bytes = new byte[1024];
while ((len = fis.read(bytes)) != -1){
// 5. 使用网络字节输出流OutputStream的象中的方法write,把读取到的文件上传到服务器
os.write(bytes,0,len);
}
/*
解决:上传完文件,给服务器写一个结束标记
void shutdownOutput() 禁用此套接字的输出流
对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列
*/
socket.shutdownOutput();
// 6. 使用Socket中的方法getInputStream,获取网络字节输出流InputSream 对象
InputStream is = socket.getInputStream();
// 7. 使用网络字节输入流InputStream对象中的方法read读取服务区回写的数据
while ((len = is.read(bytes)) != -1){
System.out.println(new String(bytes,0,len));
}
// 8. 释放资源(FileInputStream,Socket)
fis.close();
socket.close();
}
}
20.3.2 文件上传案例的服务端
package cn.code.Demo023;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/*
文件上传案例服务器端:读取客户端上传的文件,保存到服务器的硬盘,给客户端回写“上传成功”
明确:
数据源:客户端上传的文件
目的地:服务器的硬盘 D:\\upload\\1.txt
实现步骤:
1. 创建一个服务器ServerSocket对象,和系统要指定的端口号
2. 使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
3. 使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
4. 判断D:\\upload文件是否存在,不存在则创建
5. 创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
6. 使用网络字节输出流InputStream对象中的方法read,读取客户端上传的文件
7. 使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
8. 使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
9. 使用网络字节输出流OutputStream对象中的方法write,给客户端回写“上传成功”
10. 释放资源(FileOutputStream,ServerSocket,Socket)
*/
public class FileUpload02 {
public static void main(String[] args) throws IOException {
// 1. 创建一个服务器ServerSocket对象,和系统要指定的端口号
ServerSocket server = new ServerSocket(8888);
// 2. 使用ServerSocket对象中的方法accept,获取到请求的客户端Socket对象
Socket socket = server.accept();
// 3. 使用Socket对象中的方法getInputStream,获取到网络字节输入流InputStream对象
InputStream is = socket.getInputStream();
// 4. 判断D:\\upload文件是否存在,不存在则创建
File file = new File("D:\\upload");
if (! file.exists()){
file.mkdirs();
}
// 5. 创建一个本地字节输出流FileOutputStream对象,构造方法中绑定要输出的目的地
FileOutputStream fos = new FileOutputStream(file + "\\1.txt");
// 6. 使用网络字节输出流InputStream对象中的方法read,读取客户端上传的文件
int len = 0;
byte[] bytes = new byte[1024];
while ((len = is.read(bytes)) != -1){
// 7. 使用本地字节输出流FileOutputStream对象中的方法write,把读取到的文件保存到服务器的硬盘上
fos.write(bytes,0,len);
}
// 8. 使用Socket对象中的方法getOutputStream,获取到网络字节输出流OutputStream对象
OutputStream os = socket.getOutputStream();
// 9. 使用网络字节输出流OutputStream对象中的方法write,给客户端回写“上传成功”
os.write("上传成功!".getBytes());
// 10. 释放资源(FileOutputStream,ServerSocket,Socket)
fos.close();
server.close();
socket.close();
}
}
20.3.3 文件上传案例的阻塞问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-017CFFTb-1573823585037)(C:\Users\Think-Pad\Desktop\java\文件上传案例阻塞问题.jpg)]
第二十一章 函数式接口
21.1 函数式接口的概念、定义和使用
函数式接口:
package cn.code.Demo023;
/*
函数式接口:有且仅有一个抽象方法的接口,称之为函数式接口
当然接口中也可以包含其他的方法(默认,静态,私有)
@FunctionalInterface注释
作用:
是:编译成功
否:编译失败(接口中没有抽象方法、抽象方法多个数 >1个)
*/
@FunctionalInterface
public interface MyFunctionalInterface {
// 定义一个抽象方法
public abstract void method();
}
函数式接口的实现类:
package cn.code.Demo023;
/*
@Override注解
检查方法是否为重写的方法
是:编译成功
否:编译失败
*/
public class MyFunctionalInterfaceImpl implements MyFunctionalInterface{
@Override
public void method() {
}
}
函数式接口的使用:
package cn.code.Demo023;
/*
函数式接口的使用:一般可以作为方法的参数和返回值类型
*/
public class Test {
// 定义一个方法,参数使用函数式接口MyFunctionalInterface
public static void show(MyFunctionalInterface myInter){
myInter.method();
}
public static void main(String[] args) {
// 调用show方法,方法的参数是一个接口,所以可以传递接口的实现类对象
show(new MyFunctionalInterfaceImpl());
// 调用show方法,方法的参数是一个接口,所以我们可以传递接口的匿名内部类
show(new MyFunctionalInterface() {
@Override
public void method() {
System.out.println("使用匿名内部类重写接口中的抽象方法");
}
});
// 调用show方法,方法的参数是一个函数式接口,所以我们可以lambda表达式
show(() ->{
System.out.println("使用Lambda表达式重写接口中的抽象方法");
});
// 简化Lambda表达式
show(() -> System.out.println("使用Lambda表达式重写接口中的抽象方法"));
}
}
21.1.1 函数式接口作为方法的参数
package cn.code.Demo023;
/*
例如java.lang.Runnable接口就是一个函数式接口
假设有一个startThread方法使用该接口作为参数,俺么就可以使用Lambda进行传参
这种情况其实和Thread类的构造方法参数为Runnable没有本质区别
*/
public class Demo01RUnnable {
// 定义一个方法startThread,方法的参数使用函数式接口Runnable
public static void startThread(Runnable run){
// 开启多线程
new Thread(run).start();
}
public static void main(String[] args) {
// 调用startThread方法,方法的参数是一个接口,那么我们可以传递这个接口的匿名内部类
startThread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->" + "线程启动了"); // Thread-0-->线程启动了
}
});
// 调用startThread方法,方法的参数是一个函数式接口,所以可以传递Lambda表达式
startThread(() -> System.out.println(Thread.currentThread().getName() + "-->" + "线程启动了")); // Thread-1-->线程启动了
}
}
21.1.2 函数式接口作为方法的返回值类型
package cn.code.Demo023;
import java.util.Arrays;
import java.util.Comparator;
/*
如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式
当需要通过一个方法来获取一个java.util.Comparator接口类型的对象作为排序器时,就可以调该方法获取
*/
public class Demo02Comparator {
// 定义一个方法,方法的返回值类型使用函数式接口Comparator
public static Comparator<String > getComparator(){
// 方法的返回值类型是一个接口,那么我们就可以返回这个接口的匿名内部类
/*return new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
// 按照字符串的降序排序
return o2.length() - o1.length();
}
};*/
// 方法的返回值类型是一个函数式接口,所以我们可以返回一个Lambda表达式
/*return (String o1, String o2) ->{
// 按照字符串的降序排序
return o2.length() - o1.length();
};*/
// 继续简化Lambda表达式
return (o1,o2) -> o2.length() - o1.length();
}
public static void main(String[] args) {
// 创建一个字符串
String[] s1 = new String[]{"aaaa", "bbbbbb", "rrrrrrrr", "sasd"};
// 输出排序前的数组
System.out.println(Arrays.toString(s1)); // [aaaa, bbbbbb, rrrrrrrr, sasd]
// 调用Arrays中的sort方法,对字符串数组进行排序
Arrays.sort(s1,getComparator());
// 输出排序后的数组
System.out.println(Arrays.toString(s1)); // [rrrrrrrr, bbbbbb, aaaa, sasd]
}
}
21.2 日志案例
package cn.code.Demo023;
/*
发现以下代码存在的一些性能浪费的问题
调用showLog方法,传递的第二个参数是一个拼接后的字符串
先把字符串拼接好,然后再调用showLog方法
showLog方法中如果传递的日志等级不是1级
那么就不会是输出拼接后的字符串
所以感觉字符串就白拼接了,存在了浪费
*/
public class Demo01Logger {
// 定义一个根据日志的级别,显示日志信息的方法
public static void showLog(int level,String message) {
// 对日志的等级进行判断,如果是1级别,那么输出日志信息
if (level == 1){
System.out.println(message);
}
}
public static void main(String[] args) {
// 定义三个信息
String msg1 = "Hello";
String msg2 = "World";
String msg3 = "Java";
// 调用showLog方法,传递日志级别和日志信息
showLog(1,msg1+msg2+msg3);
}
}
优化:
package cn.code.Demo023;
@FunctionalInterface
public interface MessageBuilder {
// 定义一个拼接信息的抽象方法,返回被拼接的消息
public abstract String builderMessage();
}
package cn.code.Demo023;
/*
使用Lambda优化日志案例
Lambda的特点:延迟加载
Lambda的使用前提,必须存在函数式接口
*/
public class Demo02Lambda {
// 定义一个现实日志的方法,方法的参数传递日志的等级和MessageBuilder接口
public static void showLog(int level,MessageBuilder mb){
// 对日志的等级进行判断,如果是1级,则调用 MessageBuilder 接口中的 builderMessage方法
if (level == 1){
System.out.println(mb.builderMessage());
}
}
public static void main(String[] args) {
// 定义三个日志信息
String msg1 = "Hello";
String msg2 = "World";
String msg3 = "Java";
// 调用showLog方法,参数MessageBuilder是一个函数式接口,所以可以使用Lambda表达式
/*showLog(1,() ->{
// 返回一个拼接好的字符串
return msg1+msg2+msg3;
});*/
/*
使用Lambda表达式作为参数传递,仅仅是把参数传递到showLog方法中
只有满足条件,日志的等级是1级
才会调用接口MessageBuilder中的方法builderMessage
才会进行字符串的拼接
如果条件不满足,日志的等级不是1级
那么MessageBuilder接口中的方法builderMessage也不会执行
所以拼接字符串的代码也不会执行
所以不会存在性能的浪费
*/
showLog(1,() ->{
System.out.println("不满足条件不执行!");
// 返回一个拼接好的字符串
return msg1+msg2+msg3;
});
}
}
21.3 常用的函数式接口 —— Supplier接口
package cn.code.Demo023;
import java.util.function.Supplier;
/*
常用的函数式接口
java.util.function.Supplier<T>接口包含一个无参的方法:T get()。 用来获取一个泛型参数指定类型的对象数据
Supplier<T>接口被称之为成产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据
*/
public class Demo01Supplier {
// 定义一个方法,方法的参数传递Supplier<T>接口,泛型执行String, get方法就会返回一个String
public static String getString(Supplier<String> sup){
return sup.get();
}
public static void main(String[] args) {
// 调用getString方法,方法的参数Supplier是一个函数式接口,所以可以使用Lambda表达式
String s1 = getString(() -> {
// 生产一个字符串,并返回
return "胡歌";
});
System.out.println(s1); // 胡歌
// 优化Lambda表达式
String s2 = getString(() -> "胡歌");
System.out.println(s2); // 胡歌
}
}
21.3.1 求数组元素的最大值
package cn.code.Demo023;
import java.util.function.Supplier;
/*
练习:求数组元素最大值
使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值
提示:接口的泛型请使用java.lang,Integer类
*/
public class Demo02Test {
// 定义一个方法,用于获取int类型数组中元素的最大值,方法的参数传递Supplier接口,泛型使用Integer
public static int getMax(Supplier<Integer> sup){
return sup.get();
}
public static void main(String[] args) {
// 定义一个int类型的数组,并复制
int[] arr = {100,845,55,20,10,516};
// 调用getMax方法,方法的参数Supplier是一个函数式接口,所以可以传递Lambda表达式
int max1 = getMax(() -> {
// 获取数组的最大值,并返回
// 定义一个变量,把数组中的第一个元素赋值给该变量,记录数组中元素的最大值
int max = arr[0];
// 遍历数组,获取数组中的其他元素
for (int i : arr) {
// 使用其他元素和最大值比较
if (i > max) {
// 如果i大于max,则替换max作为最大值
max = i;
}
}
// 返回最大值
return max;
});
System.out.println("数组中元素的最大值是:" + max1); // 数组中元素的最大值是:845
}
}
21.4 常用的函数式接口 —— Consumer接口
package cn.code.Demo024;
import java.util.function.Consumer;
/*
java.util.function.Consumer<T>接口则正好与Supplier接口相反,
它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定
Consumer接口中包含抽象方法 void accept(T t),意为消费一个指定泛型的数据
Consumer接口是一个消费型接口,泛型执行什么类型,就可以使用accept方法消费什么类型的数据
至于具体怎么消费(使用),需要自定义(输出,计算...)
*/
public class Demo01Consumer {
/*
定义一个方法
方法的参数传递一个字符串的姓名
方法的参数传递Consumer接口,泛型使用String
可以使用Consumer接口消费字符串的姓名
*/
public static void method(String name, Consumer<String> con){
con.accept(name);
}
public static void main(String[] args) {
// 调用method方法,传递字符串姓名,方法的另一个参数是Consumer接口,是一个函数式接口,所以可以传递Lambda表达式
method("赵丽颖",(String name) ->{
// 对传递的字符串进行消费
// 消费方法:直接输出字符串
// System.out.println(name);
// 消费方法:把字符串进行反转输出
String rename = new StringBuffer(name).reverse().toString();
System.out.println(rename);
});
}
}
21.4.1 Consumer接口中的默认方法 andThen
package cn.code.Demo024;
import java.util.function.Consumer;
/*
Consumer接口的默认方法andThen
作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,再对数据进行消费
例如:
Consumer<String> con1;
Consumer<String> con2;
String s = "hello";
con1.accept(s);
con2.accept(s);
连接两个Consumer接口,再进行消费
con1.andThen(con2).accept(s); 谁写在前边谁先消费
*/
public class Demo02AndThen {
// 定义一个方法,方法的参数传递一个字符串和两个Consumer接口,Consumer接口的泛型使用字符串
public static void method(String s, Consumer<String> con1, Consumer<String> con2){
// con1.accept(s);
// con2.accept(s);
// 使用andThen方法,把两个Consumer接口连接到一起,再消费数据
con1.andThen(con2).accept(s); // con1连接con2,先执行con1消费数据,再执行con2消费数据
}
public static void main(String[] args) {
// 调用method方法,传递一个字符串,两个Lambda表达式
method("Hello",
(t) -> {
// 消费方式:把字符串转换为大写输出
System.out.println(t.toUpperCase()); // HELLO
},
(t) -> {
// 消费方式:把字符串转换为小写输出
System.out.println(t.toLowerCase()); // hello
});
}
}
21.4.2 Consumer接口练习 —— 字符串拼接输出
package cn.code.Demo024;
import java.util.function.Consumer;
/*
练习:
字符串数组当中存在有多条信息,请按照格式“姓名:xx。性别:xx。” 的格式将信息打印出来
要求将打印姓名的动作作为第一个Consumer接口的Lambda实例
将打印性别的动作作为第二个Consumer接口的Lambda实例
将两个Consumer接口按照顺序“拼接”到一起
*/
public class Demo03Test {
// 定义一个方法,参数传递String类型的数组和两个Consumer接口,泛型使用String
public static void printInfo(String[] arr, Consumer<String> con1, Consumer<String> con2){
// 遍历字符串数组
for (String s : arr) {
// 使用andThen方法连接两个Consumer接口,消费字符串
con1.andThen(con2).accept(s);
}
}
public static void main(String[] args) {
// 定义一个字符串类型的数组
String[] arr ={"迪丽热巴,女","邓伦,男","欧阳娜娜,女"};
// 调用printInfo方法,传递一个字符串数组,和两个Lambda表达式
printInfo(arr,
(s) -> {
// 消费方式:对s进行切割,获取姓名,按照指定的格式输出
String name = s.split(",")[0];
System.out.println("姓名:" + name);
},
(s) -> {
// 消费方式:对s进行切割,获取性别,按照指定的格式输出
String sex = s.split(",")[1];
System.out.println("性别:" + sex);
});
}
}
21.5 常用的函数式接口 —— Predicate接口
package cn.code.Demo024;
import java.util.function.Predicate;
/*
java.util.function.predicate<T>接口
作用:对某种数据类型的数据进行判断,结果返回一个Boolean值
predicate接口中包含一个抽象方法:
boolean test(T t): 用来对指定数据类型进行判断的方法
结果:
符合条件,返回true
不符合条件,返回false
*/
public class Demo01Predicate {
/*
定义一个方法
参数传递一个String类型的字符串
传递一个Predicate接口,泛型使用String
使用Predicate中的方法test对字符串进行判断,并把判断的结果返回
*/
public static boolean checkString(String s, Predicate<String> pre){
return pre.test(s);
}
public static void main(String[] args) {
// 定义一个字符串
String s = "abcffde";
// 调用checkString方法对字符串进行校验,参数传递字符串和Lambda表达式
/*boolean b = checkString(s, (String str) -> {
// 对参数传递的字符串进行判断,判断字符串的长度是否大于5,并把判断的结果返回
return str.length() > 5;
});
System.out.println(b);*/
// 优化Lambda表达式
boolean b = checkString(s,str -> str.length() > 5);
System.out.println(b);
}
}
21.5.1 Predicate接口的默认方法 —— and && or || negate !
package cn.code.Demo024;
import java.util.function.Predicate;
/*
逻辑表达式:可以连接多个判断条件
&&:与运算符,有false则false
||:或运算符,有true则true
!:非(取反)运算符,非真则假,非假则真
需求:判断一个字符串,有两个判断条件
1. 判断字符串的长度是否大于5
2. 判断字符串中是否包含a
两个条件必须同时满足,我们就可以使用&&运算符连接两个条件
Predicate接口中有一个方法and,表示并且关系,也可以用于连接两个判断条件
default Predicate<T> and(Predicate<? super T> other){
Objects.requireNonNull(other);
return (t) -> this.test(t) && other.test(t);
}
方法内部的两个判断条件,也是使用&&运算符连接起来的
*/
public class Demo02Predicate {
/*
定义一个方法,方法的参数,传递一个字符串
传递两个Predicate接口
一个用于判断字符串的长度是否大于5
一个用于判断字符串中是否包含a
两个条件同时满足
*/
public static boolean checkString(String s, Predicate<String> pre1, Predicate<String> pre2){
// return pre1.test(s) && pre2.test(s);
// return pre1.and(pre2).test(s); // 等价于return pre1.test(s) && pre2.test(s);
// return pre1.test(s) || pre2.test(s);
// return pre1.or(pre2).test(s); // 等价于 return pre1.test(s) || pre2.test(s);
// return !pre1.test(s);
return pre1.negate().test(s); // 等价于 return !pre1.test(s);
}
public static void main(String[] args) {
// 定义一个字符串
String s = "aferrd";
// 调用checkString方法,参数传递字符串和两个Lambda表达式
boolean b = checkString(s, (String str) -> {
// 判断字符串的长度是否大于5
return s.length() > 5;
}, (String str) -> {
// 判断字符串中是否包含a
return s.contains("a");
});
System.out.println(b);
}
}
21.5.2 Predicate练习 —— 集合信息筛选
package cn.code.Demo024;
import java.util.ArrayList;
import java.util.function.Predicate;
/*
练习:集合信息筛选
数组当中有多条“姓名+性别” 的信息如下,
String[] arr ={"迪丽热巴,女","邓伦,男","欧阳娜娜,女"};
请通过Predicate接口的拼装将符合要求的字符串筛选到集合ArrayList中,
需要同时满足两个条件:
1. 必须为女生
2. 姓名为4个字
分析:
1. 有两个判断条件,所以需要使用两个Predicate接口,对条件进行判断
2. 必须同时满足两个条件,所以可以使用and方法连接两个判断条件
*/
public class Demo04Text {
/*
定义一个方法
方法的参数传递一个包含人员信息的数组
传递两个Predicate接口,用于对数组中的信息进行传递
把满足条件的信息存到ArrayList集合中并返回
*/
public static ArrayList<String> filter(String[] arr, Predicate<String> pre1,Predicate<String> pre2){
// 定义一个ArrayList集合,存储过滤之后的信息
ArrayList<String> list = new ArrayList<>();
// 遍历数组,获取数组中的每一条信息
for (String s : arr) {
// 使用Predicate接口中的方法test对获取到的字符串进行判断
boolean b = pre1.and(pre2).test(s);
// 对得到的布尔值进行判断
if (b){
// 条件成立,两个条件都满足,把信息存储到ArrayList集合或者
list.add(s);
}
}
// 把集合返回
return list;
}
public static void main(String[] args) {
// 定义一个存储字符串的数组
String[] arr ={"迪丽热巴,女","邓伦,男","欧阳娜娜,女"};
// 调用fileter方法,传递字符串数组和两个Lambda表达式
ArrayList<String> list = filter(arr, (String s) -> {
// 获取字符串中的性别,判断是否为女
return s.split(",")[1].equals("女");
}, (String s) -> {
// 获取字符串中的姓名,判断传递是否为4个字符
return s.split(",")[0].length() == 4;
});
// 遍历数组
for (String s : list) {
System.out.println(s);
}
}
}
21.6 常用的函数式接口 —— Function接口
package cn.code.Demo024;
import java.util.function.Function;
/*
java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据,
前者称为前置条件,后者称为后置条件。
Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取R的结果。
使用的场景例如:将String类型转换为Integer类型。
*/
public class Demo01Function {
/*
定义一个方法
方法的参数传递一个字符串类型的整数
方法的参数传递一个Function接口,泛型使用<String,Integer>
使用Function接口中的方法apply,把字符串类型的整数,转换为Integer类型的整数
*/
public static void change(String s, Function<String,Integer> fun){
Integer in = fun.apply(s);
System.out.println(in);
}
public static void main(String[] args) {
// 定义一个字符串类型的整数
String s = "1235";
// 调用change方法,传递字符串类型的整数,和Lambda表达式
change(s,(String str) -> {
// 把字符串类型的整数,转换为Integer类型的整数返回
return Integer.parseInt(s);
});
}
}
21.6.1 Function接口中的默认方法 —— andThen
package cn.code.Demo024;
import java.util.function.Function;
/*
Function接口中的默认方法andThen:用来进行组合操作
需求:
把String类型的"123",转换为Integer类型,把转换后的结果加10
把增加之后的Integer类型的数据,转换为String类型
分析:
转换了两次
第一次是把String类型转换为了Integer类型
所以我们可以使用Function<String,Integer> fun1
Integer i = fun1.apply("123") + 10;
第二次是把Integer类型转换为String类型
所以我们可以使用Function<String,Integer> fun2
String s = fun2.apply(i);
我们可以使用andThen方法,把两次转换组合在一起使用
String s = fun1.andThen(fun2).apply("123");
fun1先调用apply方法,把字符串转换为Integer
fun2再调用apply方法,把Integer转换为字符串
*/
public class Demo02Function_andThen {
/*
定义一个方法
参数串一个字符串类型的整数
参数再cd两个Function接口
一个泛型使用Function<String,Integer>
一个泛型使用Function<Integer,String>
*/
public static void change(String s, Function<String,Integer> fun1,Function<Integer,String> fun2){
String ss = fun1.andThen(fun2).apply(s);
System.out.println(ss);
}
public static void main(String[] args) {
// 定义一个字符串类型的整数
String s = "123";
/* // 调用change方法,传递字符串和两个Lambda表达式
change(s,(String str) -> {
// 把字符串类型转换为整数 +10
return Integer.parseInt(str) + 10;
}, (Integer i) -> {
// 把整数转换为字符串
return i+"";
});
*/
// 优化Lambda表达式
change(s,str -> Integer.parseInt(str) + 10, i -> i+"");
}
}
21.6.2 Function接口练习 —— 自定义函数模型拼接
package cn.code.Demo024;
import java.util.function.Function;
/*
练习:自定义函数模型拼接
题目
请使用Function进行函数模型的拼接,按照顺序需要执行的多个函数操作为:
String str = "赵丽颖,20";
分析:
1. 将字符串截取数字年龄部分,得到字符串;
Function<String,String> "赵丽颖,20" -->"20"
2. 将上一步的int数字累加100,得到结果int数字
Function<String,Integer> "20" --> 20
3. 将上一步的int数字累加100,得到结果int数字
Function<Integer,Integer> 20 --> 120
*/
public class Demo05Text {
/*
定义一个方法
参数传递包含姓名和年龄的字符串
参数再传递3个Function接口用于类型转换
*/
public static int change(String s, Function<String,String> fun1, Function<String,Integer> fun2, Function<Integer,Integer>fun3){
// 使用andThen方法把三个转换组合到一起
return fun1.andThen(fun2).andThen(fun3).apply(s);
}
public static void main(String[] args) {
// 定义一个字符串
String str = "赵丽颖,20";
// 调用change方法,参数传递字符串和3个Lambda表达式
int change = change(str, (String s) -> {
// "赵丽颖,20" -->"20"
return str.split(",")[1];
}, (String s) -> {
// "20" --> 20
return Integer.parseInt(s);
}, (Integer i) -> {
// 20 --> 120
return i + 100;
});
System.out.println(change); // 120
}
}
第二十二章 Stream流
22.1 使用传统方法&使用Stream流 遍历集合,对集合中的数据进行过滤
传统方式:
package cn.code.Demo025;
import java.util.ArrayList;
import java.util.List;
/*
使用传统的方式:遍历集合,对集合中的数据进行过滤
*/
public class Demo01List {
public static void main(String[] args){
// 创建一个List集合,存储姓名
List<String> list = new ArrayList<>();
list.add("陈粒");
list.add("张杰");
list.add("陈绮贞");
list.add("陈奕迅");
// 对List集合中的元素进行过滤,只要以陈开头的元素,存储到一个新的集合中
List<String> list1 = new ArrayList<>();
for (String s : list) {
if (s.startsWith("陈")){
list1.add(s);
}
}
// 对lsit1集合进行过滤,只要姓名传递为3的人,存储到一个新集合中
List<String> list2 = new ArrayList<>();
for (String s : list1) {
if (s.length() == 3){
list2.add(s);
}
}
// 遍历list2集合
for (String s : list2) {
System.out.println(s);
}
}
}
使用Stream流:
package cn.code.Demo025;
import java.util.ArrayList;
import java.util.List;
/*
使用Stream流的方式,遍历集合,对集合中的数据进行过滤
Stream流是JDK1.8之后出现的
关注的是做什么,而不是怎么做
*/
public class Demo02Stream {
public static void main(String[] args) {
// 创建一个List集合,存储姓名
List<String> list = new ArrayList<>();
list.add("陈粒");
list.add("张杰");
list.add("陈绮贞");
list.add("陈奕迅");
// 对List集合中的元素进行过滤,只要以陈开头的元素,存储到一个新的集合中
// 对lsit1集合进行过滤,只要姓名传递为3的人,存储到一个新集合中
// 遍历list2集合
list.stream()
.filter(name -> name.startsWith("陈"))
.filter(name -> name.length() == 3)
.forEach(name -> System.out.println(name));
}
}
22.2 Stream流中的常用方法 —— forEach
package cn.code.Demo025;
import java.util.stream.Stream;
/*
Stream流中的常用方法_forEach
void _forEach(Consumer<? super T> action);
该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理
Consumer接口是一个消费型的函数式接口,可以传递Lambda表达式,消费数据
简单记:
forEach方法,用来遍历流中的数据
是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法
*/
public class Demo02Stream_forEach {
public static void main(String[] args) {
// 获取一个Stream流
Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六", "田七");
// 使用Stream流中的方法forEach对Stream流中的数据进行遍历
/*stream.forEach((String name) -> {
System.out.println(name);
});*/
stream.forEach(name -> System.out.println(name));
}
}
22.3 Stream流中的常用方法 —— filter
package cn.code.Demo025;
import java.util.stream.Stream;
/*
Stream流中的常用方法_filter:用来对Stream流或者的数据进行过滤
Stream<T> filter(Predicate<? super T> predicate);
filter方法的参数Predicate是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤
Predicate中的抽象方法:
boolean test(T t);
*/
public class Demo03Stream_filter {
public static void main(String[] args) {
// 获取一个Stream流
Stream<String> stream = Stream.of("张三", "李四", "王五", "赵六", "田七");
// 对Stream流中的元素进行过滤,只要姓张的人
Stream<String> stream2 = stream.filter((String name) -> {
return name.startsWith("张");
});
// 遍历stream2流
stream2.forEach(name -> System.out.println(name)); // 张三
/*
Stream流属于管道流,只能被消费(使用)一次
第一个Stream流调用方法完毕,数据就会转到下一个Stream上
而这时第一个Stream流已经使用完毕,就会关闭了
所以第一个Stream流就不能再调用方法了
IllegalStateException: stream has already been operated upon or closed
*/
// 遍历Stream流
stream.forEach(name -> System.out.println(name)); // IllegalStateException: stream has already been operated upon or closed
}
}
22.4 Stream流中的常用方法 —— map
package cn.code.Demo025;
import java.util.stream.Stream;
/*
如果需要将流中的元素映射到另一个流中,可以使用map方法
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流
Function中的抽象方法:
R apply(T t);
*/
public class Demo04Stream_map {
public static void main(String[] args) {
// 获取一个String类型的Stream流
Stream<String> stream = Stream.of("1", "2", "3", "4");
// 使用map方法,把字符串类型的整数,转换(映射)为Integer类型的整数
Stream<Integer> stream2 = stream.map((String s) -> {
return Integer.parseInt(s);
});
// 遍历Stream2流
stream2.forEach(i -> System.out.println(i));
}
}
22.5 Stream流中的常用方法 —— count
package cn.code.Demo025;
import java.util.ArrayList;
/*
Stream流中的常用方法_count:用于统计Stream流中的元素个数
Long count();
count方法是一个终结方法,返回值是一个Long类型的整数
所以不能再调用Stream流中的其他方法了
*/
public class Demo05Stream_count {
public static void main(String[] args) {
// 获取一个Stream流
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
long count = list.stream().count();
System.out.println(count); // 6
}
}
22.6 Stream流中的常用方法 —— limit
package cn.code.Demo025;
import java.util.stream.Stream;
/*
Stream流中的常用方法_limit:用于截取流中的元素
limit方法可以对流进行截取,只取用前n个,方法格式:
Strram<T> limit(long maxSize);
参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作
limit方法是一个延迟方法,只是对流中的元素进行截取,返回的是一个新的流,所以可以继续调用Stream流中的其他方法
*/
public class Demo06Stream_limit {
public static void main(String[] args) {
// 获取一个Stream流
String[] arr = {"美羊羊","喜羊羊","懒羊羊","灰太狼","红太狼"};
Stream<String> stream = Stream.of(arr);
// 使用limit方法对Stream流中的元素进行截取,只要前3个元素
Stream<String> stream2 = stream.limit(3);
// 遍历stream2流
stream2.forEach(name -> System.out.println(name));
}
}
22.7 Stream流中的常用的方法 —— skip
package cn.code.Demo025;
import java.util.stream.Stream;
/*
Stream流中常用方法_skip:用于跳过元素
如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流:
Stream<T> skip(long n);
如果流的当前长度大于n,则跳过前几n个,否则将会得到一个长度为0的空流。
*/
public class Demo07Stream_skip {
public static void main(String[] args) {
// 获取一个Stream流
String[] arr = {"美羊羊","喜羊羊","懒羊羊","灰太狼","红太狼"};
Stream<String> stream = Stream.of(arr);
// 使用skip方法跳过前3个元素
Stream<String> stream2 = stream.skip(3);
// 遍历stream2流
stream2.forEach(name -> System.out.println(name));
}
}
22.8 Stream流中的常用方法 —— concat
package cn.code.Demo025;
import java.util.stream.Stream;
/*
Stream流中的常用方法_concat:用于把流组合到一起
如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
*/
public class Demo08Stream_concat {
public static void main(String[] args) {
// 获取一个Stream流
String[] arr = {"美羊羊","喜羊羊","懒羊羊","灰太狼","红太狼"};
Stream<String> stream = Stream.of(arr);
// 获取一个Stream流
Stream<String> stream2 = Stream.of("张三", "李四", "王五", "赵六", "田七");
// 把以上两个流组合为一个流
Stream<String> concat = Stream.concat(stream2, stream);
// 遍历concat流
concat.forEach(a -> System.out.println(a));
}
}
第二十三章 方法引用
23.1 方法引用基本介绍
接口:
package cn.code.Demo026;
/*
定义一个打印的函数式接口
*/
@FunctionalInterface
public interface Printable {
// 定义字符串的抽象方法
public abstract void print(String s);
}
package cn.code.Demo026;
public class Demo01Printable{
// 定义一个方法,参数传递Printable接口,对字符串进行打印
public static void printable(Printable p){
p.print("HelloWorld");
}
public static void main(String[] args) {
// 调用printString方法,方法的参数Printable是一个函数式接口,所以可以传递Lambda
printable((s) -> System.out.println(s));
/*
分析:
Lambda表达式的目的,打印参数传递的字符串
把参数s,传递给了System.out对象,调用out对象中的println对字符串进行了输出
注意:
1. System.out对象是已经存在的
2. println方法也是已经存在的
所以我们可以使用方法引用来优化Lambda表达式
可以使用System.out方法直接引用(调用)println方法
*/
printable(System.out::println);
}
}
23.2 通过对象名引用成员方法
package cn.code.Demo026;
/*
通过对象名引用成员方法
使用前提是对象名是已经存在的,成员方法也是已经存在
就可以使用对象名来引用成员方法
*/
public class Demo01ObjectMethodReference {
// 定义一个方法,方法的参数传递Printable接口
public static void printString(Printable p){
p.print("Hello");
}
public static void main(String[] args) {
// 调用printString方法,方法的参数Printable是一个函数式接口,所以可以传递Lambda表达式
printString((s) ->{
// 创建MethodRerObject对象
MethodRerObject obj = new MethodRerObject();
// 调用MethodRerObject对象中的成员方法printUpperCaseString,把字符串按照大写输出
obj.printUpperCaseString(s); // HELLO
});
/*
使用方法引用优化Lambda
对象是已经存在的MethodRerObject
成员方法也是已经存在的printUpperCaseString
所以我们可以使用对象名引用成员方法
*/
// 创建MethodRerObject对象
MethodRerObject obj = new MethodRerObject();
printString(obj :: printUpperCaseString); // HELLO
}
}
package cn.code.Demo026;
public class MethodRerObject {
// 定义一个成员方法,传递字符串,把字符串按照大写输出
public void printUpperCaseString(String str){
System.out.println(str.toUpperCase());
}
}
package cn.code.Demo026;
/*
定义一个打印的函数式接口
*/
@FunctionalInterface
public interface Printable {
// 定义字符串的抽象方法
public abstract void print(String s);
}
23.3 通过类名引用静态成员方法
package cn.code.Demo026;
/*
定义一个计算绝对值的函数式接口
*/
@FunctionalInterface
public interface Calcable {
// 定义一个抽象方法,传递一个整数,对整数进行绝对值计算并返回
int calsAbs(int number);
}
package cn.code.Demo026;
/*
静态类名引用静态方法
类已经存在,静态成员方法也已经存在
就可以通过类名直接引用静态成员方法
*/
public class Demo01StaticMethodReference {
// 定义一个方法,方法的参数传递要计算绝对值的整数,和函数式接口Calcable
public static int method(int number, Calcable c){
return c.calsAbs(number);
}
public static void main(String[] args) {
// 调用method方法,传递计算绝对值的整数,和Lambda表达式
int i = method(-10, (n) -> {
// 对参数进行绝对值计算并返回结果
return Math.abs(n);
});
System.out.println(i); // 10
/*
使用方法引用优化Lambda表达式
Math类是存在的
abs计算绝对值的静态方法也是存在的
所以我们可以直接通过类名引用静态方法
*/
int i1 = method(-10, Math::abs);
System.out.println(i1); // 10
}
}
23.4 通过super引用分类的成员方法
package cn.code.Demo026;
/*
定义见面的函数式接口
*/
@FunctionalInterface
public interface Greatable {
// 定义一个见面的方法
void greet();
}
package cn.code.Demo026;
/*
定义父类
*/
public class Fu {
// 定义一个sayHello
public void sayHello(){
System.out.println("Hello,我是Fu类!");
}
}
package cn.code.Demo026;
/*
定义子类
*/
public class Zi extends Fu {
// 子类重写父类sayHello的方法
@Override
public void sayHello(){
System.out.println("Hello,我是Zi类!");
}
// 定义一个方法参数传递Greatable接口
public void method(Greatable g){
g.greet();
}
public void show(){
// 调用method方法,方法的参数Greatable是一个函数式接口,所以可以传递Lambda
/*method(() ->{
// 创建父类Fu对象
Fu f =new Fu();
// 调用父类的sayHello方法
f.sayHello();
});*/
// 因为有子类关系,所以存在一个关键字super,代表父类,所以我们可以直接使用super调用父类的成员方法
/*method(() ->{
super.sayHello();
});*/
/*
使用super引用类的成员方法
super是已经存在的
父类的成员方法sayHello也是已经存在的
所以我们可以直接使用super引用父类的成员方法
*/
method(super :: sayHello);
}
public static void main(String[] args) {
new Zi().show();
}
}
23.5 通过this引用本类的成员方法
package cn.code.Demo026;
/*
定义一个富有的函数式接口
*/
@FunctionalInterface
public interface Richable {
// 定义一个想买什么就买什么的方法
void buy();
}
package cn.code.Demo026;
/*
通过this引用本类的成员方法
*/
public class Husband {
// 定义一个买房子的方法
public void buyHouse(){
System.out.println("北京二环内买一套四合院!");
}
// 定义一个结婚的方法,参数传递Richable接口
public void marry(Richable r){
r.buy();
}
// 定义一个非常高兴的方法
public void soHappy(){
// 调用结婚的方法,方法的参数Richable是一个函数式接口,传递Lambda表达式
marry(() ->{
// 使用this,成员方法,调用本类买房子的方法
this.buyHouse();
});
/*
使用方法引用优化Lambda表达式
this是已经存在的
本类的成员方法buyHouse也是已经存在的
所以我们可以直接使用this引用本类的成员方法buyHouse
*/
marry(this::buyHouse);
}
public static void main(String[] args) {
new Husband().soHappy();
}
}
23.6 类的构造器(构造方法)的引用
package cn.code.Demo026;
public class Person {
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package cn.code.Demo026;
/*
定义一个创建Person对象的函数式接口
*/
@FunctionalInterface
public interface PersonBuilder {
// 定义一个方法,根据传递的姓名,创建Person对象返回
Person builderPerson(String name);
}
package cn.code.Demo026;
/*
类的构造器(构造方法)引用
*/
public class Demo {
// 定义一个方法,参数传递姓名和PersonBuilder接口,方法中通过姓名创建Person对象
public static void printName(String name, PersonBuilder pb){
Person person = pb.builderPerson(name);
System.out.println(person.getName());
}
public static void main(String[] args) {
// 调用printName方法,方法的参数PersonBuilder接口是一个函数式接口,可以传递Lambda
printName("迪丽热巴",(String name) -> {
return new Person(name);
});
/*
使用方法引用优化Lambda表达式
构造方法new Person(String name)已知
创建对象已知 new
就可以使用Person引用new创建对象
*/
printName("迪丽热巴",Person::new); // 使用Person类的带参构造方法,通过传递的姓名创建对象
}
}
23.7 数组的构造器引用
package cn.code.Demo026;
/*
定义一个创建数组的函数式接口
*/
@FunctionalInterface
public interface ArrayBuilder {
// 定义一个创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组
int[] builderArray(int length);
}
package cn.code.Demo026;
import java.util.Arrays;
/*
数组的构造器引用
*/
public class Demo02 {
/*
定义一个方法
方法的参数传递创建数组的长度和ArrayBuilder接口
方法的内部根据传递的长度使用ArrayBuilder中的方法创建数组并返回
*/
public static int[] creatArray(int length, ArrayBuilder ab){
return ab.builderArray(length);
}
public static void main(String[] args) {
// 调用creatArray方法,传递数组的长度和Lambda表达式
int[] arr1 = creatArray(10, (len) -> {
// 根据数组的长度,创建数组并返回
return new int[len];
});
System.out.println(arr1.length); // 10
/*
使用方法引用优化Lambda表达式
已知创建的就是int[]数组
数组的长度也是已知的
就可以使用方法引用
int[]引用new,根据参数传递的长度来创建数组
*/
int[] arr2 = creatArray(10, int[]::new);
System.out.println(Arrays.toString(arr2)); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
System.out.println(arr2.length); // 10
}
}