什么是线程?
线程(thread)是一个程序内部的一条执行路径。
我们之前启动程序执行后,main方法的执行其实就是一条单独的执行路径。
public static void main(String[] args) {
// 代码...
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
// 代码...
}
程序中如果只有一条执行路径,那么这个程序就是单线程的程序。
多线程是什么?
Thread类
多线程的实现方案一:继承Thread类
代码演示
/**
目标:多线程的创建方式一:继承Thread类实现。
*/
public class ThreadDemo1 {
public static void main(String[] args) {
// 3、new一个新线程对象
Thread t = new MyThread();
// 4、调用start方法启动线程(执行的还是run方法)
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程执行输出:" + i);
}
}
}
/**
1、定义一个线程类继承Thread类
*/
class MyThread extends Thread{
/**
2、重写run方法,里面是定义线程以后要干啥
*/
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程执行输出:" + i);
}
}
}
输出结果
主线程执行输出:0
主线程执行输出:1
子线程执行输出:0
主线程执行输出:2
子线程执行输出:1
主线程执行输出:3
子线程执行输出:2
主线程执行输出:4
子线程执行输出:3
子线程执行输出:4
方式一优缺点
注意事项
1、为什么不直接调用了run方法,而是调用start启动线程。
2、把主线程任务放在子线程之前了。
多线程的实现方案二:实现Runnable接口
Thread的构造器
构造器 | 说明 |
---|---|
public Thread(String name) | 可以为当前线程指定名称 |
public Thread(Runnable target) | 封装Runnable对象成为线程对象 |
public Thread(Runnable target ,String name ) | 封装Runnable对象成为线程对象,并指定线程名称 |
代码演示
/**
目标:学会线程的创建方式二,理解它的优缺点。
*/
public class ThreadDemo2 {
public static void main(String[] args) {
// 3、创建一个任务对象
Runnable target = new MyRunnable();
// 4、把任务对象交给Thread处理
Thread t = new Thread(target);
// Thread t = new Thread(target, "1号");
// 5、启动线程
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程执行输出:" + i);
}
}
}
/**
1、定义一个线程任务类 实现Runnable接口
*/
class MyRunnable implements Runnable {
/**
2、重写run方法,定义线程的执行任务的
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程执行输出:" + i);
}
}
}
输出结果
主线程执行输出:0
主线程执行输出:1
主线程执行输出:2
子线程执行输出:0
主线程执行输出:3
子线程执行输出:1
主线程执行输出:4
子线程执行输出:2
主线程执行输出:5
子线程执行输出:3
主线程执行输出:6
子线程执行输出:4
主线程执行输出:7
子线程执行输出:5
主线程执行输出:8
主线程执行输出:9
子线程执行输出:6
子线程执行输出:7
子线程执行输出:8
子线程执行输出:9
方式二优缺点
方式二拓展
实现Runnable接口(匿名内部类形式)
代码演示
/**
目标:学会线程的创建方式二(匿名内部类方式实现,语法形式)
*/
public class ThreadDemo2Other {
public static void main(String[] args) {
Runnable target = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程1执行输出:" + i);
}
}
};
Thread t = new Thread(target);
t.start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程2执行输出:" + i);
}
}
}).start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("子线程3执行输出:" + i);
}
}).start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程执行输出:" + i);
}
}
}
输出结果
主线程执行输出:0
子线程2执行输出:0
子线程3执行输出:0
子线程3执行输出:1
子线程1执行输出:0
子线程3执行输出:2
子线程2执行输出:1
主线程执行输出:1
子线程2执行输出:2
子线程3执行输出:3
子线程1执行输出:1
子线程3执行输出:4
子线程2执行输出:3
主线程执行输出:2
子线程2执行输出:4
子线程3执行输出:5
子线程1执行输出:2
子线程3执行输出:6
子线程2执行输出:5
主线程执行输出:3
子线程2执行输出:6
子线程3执行输出:7
子线程1执行输出:3
子线程3执行输出:8
子线程2执行输出:7
主线程执行输出:4
子线程2执行输出:8
子线程3执行输出:9
子线程1执行输出:4
子线程1执行输出:5
子线程2执行输出:9
主线程执行输出:5
主线程执行输出:6
主线程执行输出:7
子线程1执行输出:6
主线程执行输出:8
子线程1执行输出:7
主线程执行输出:9
子线程1执行输出:8
子线程1执行输出:9
问题引出
1、前2种线程创建方式都存在一个问题:
2、怎么解决这个问题呢?
多线程的实现方案三:利用Callable、FutureTask接口实现。
FutureTask的API
方法名称 | 说明 |
---|---|
public FutureTask<>(Callable call) | 把Callable对象封装成FutureTask对象。 |
public V get() throws Exception | 获取线程执行call方法返回的结果。 |
代码演示
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
目标:学会线程的创建方式三:实现Callable接口,结合FutureTask完成。
*/
public class ThreadDemo3 {
public static void main(String[] args) {
// 3、创建Callable任务对象
Callable<String> call = new MyCallable(100);
// 4、把Callable任务对象 交给 FutureTask 对象
// FutureTask对象的作用1: 是Runnable的对象(实现了Runnable接口),可以交给Thread了
// FutureTask对象的作用2: 可以在线程执行完毕之后通过调用其get方法得到线程执行完成的结果
FutureTask<String> f1 = new FutureTask<>(call);
// 5、交给线程处理
Thread t1 = new Thread(f1);
// 6、启动线程
t1.start();
Callable<String> call2 = new MyCallable(200);
FutureTask<String> f2 = new FutureTask<>(call2);
Thread t2 = new Thread(f2);
t2.start();
try {
// 如果f1任务没有执行完毕,这里的代码会等待,直到线程1跑完才提取结果。
String rs1 = f1.get();
System.out.println("第一个结果:" + rs1);
} catch (Exception e) {
e.printStackTrace();
}
try {
// 如果f2任务没有执行完毕,这里的代码会等待,直到线程2跑完才提取结果。
String rs2 = f2.get();
System.out.println("第二个结果:" + rs2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
1、定义一个任务类 实现Callable接口 应该申明线程任务执行完毕后的结果的数据类型
*/
class MyCallable implements Callable<String>{
private int n;
public MyCallable(int n) {
this.n = n;
}
/**
2、重写call方法(任务方法)
*/
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 1; i <= n ; i++) {
sum += i;
}
return "子线程执行的结果是:" + sum;
}
}
输出结果
第一个结果:子线程执行的结果是:5050
第二个结果:子线程执行的结果是:20100
方式三优缺点
方式 | 优点 | 缺点 |
---|---|---|
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 扩展性较差,不能再继承其他的类,不能返回线程执行的结果 |
实现Runnable接口 | 扩展性强,实现该接口的同时还可以继承其他的类。 | 编程相对复杂,不能返回线程执行的结果 |
实现Callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类。可以得到线程执行的结果 | 编程相对复杂 |
Thread常用API说明
构造器
构造器 | 说明 |
---|---|
public Thread(String name) | 可以为当前线程指定名称 |
public Thread(Runnable target) | 把Runnable对象交给线程对象 |
public Thread(Runnable target ,String name ) | 把Runnable对象交给线程对象,并指定线程名称 |
方法
方法名称 | 说明 |
---|---|
String getName() | 获取当前线程的名称,默认线程名称是Thread-索引 |
void setName(String name) | 设置线程名称 |
public static Thread currentThread(): | 返回对当前正在执行的线程对象的引用 |
public static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒。 |
public void run() | 线程任务方法 |
public void start() | 线程启动方法 |
问题引出
当有很多线程在执行的时候,我们怎么去区分这些线程呢?
Thread获取和设置线程名称
方法名称 | 说明 |
---|---|
String getName() | 获取当前线程的名称,默认线程名称是Thread-索引 |
void setName(String name) | 将此线程的名称更改为指定的名称,通过构造器也可以设置线程名称 |
Thread类获得当前线程的对象
方法名称 | 说明 |
---|---|
public static Thread currentThread(): | 返回对当前正在执行的线程对象的引用 |
注意:
Thread的构造器
方法名称 | 说明 |
---|---|
public Thread(String name) | 可以为当前线程指定名称 |
public Thread(Runnable target) | 封装Runnable对象成为线程对象 |
public Thread(Runnable target ,String name ) | 封装Runnable对象成为线程对象,并指定线程名称 |
MyThread 类
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
// 为当前线程对象设置名称,送给父类的有参数构造器初始化名称
super(name);
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println( Thread.currentThread().getName() + "输出:" + i);
}
}
}
测试类
/**
目标:线程的API
*/
public class ThreadDemo01 {
// main方法是由主线程负责调度的
public static void main(String[] args) {
Thread t1 = new MyThread("1号");
// t1.setName("1号");
t1.start();
System.out.println(t1.getName());
Thread t2 = new MyThread("2号");
// t2.setName("2号");
t2.start();
System.out.println(t2.getName());
// 哪个线程执行它,它就得到哪个线程对象(当前线程对象)
// 主线程的名称就叫main
Thread m = Thread.currentThread();
System.out.println(m.getName());
m.setName("最牛的线程");
for (int i = 0; i < 5; i++) {
System.out.println( m.getName() + "输出:" + i);
}
}
}
输出结果
1号
2号
main
1号输出:0
2号输出:0
2号输出:1
1号输出:1
1号输出:2
2号输出:2
最牛的线程输出:0
2号输出:3
1号输出:3
2号输出:4
最牛的线程输出:1
1号输出:4
最牛的线程输出:2
最牛的线程输出:3
最牛的线程输出:4
方法名称 | 说明 |
---|---|
public static void sleep(long time) | 让当前线程休眠指定的时间后再继续执行,单位为毫秒。 |
代码演示
/**
目标:线程的API
*/
public class ThreadDemo02 {
// main方法是由主线程负责调度的
public static void main(String[] args) throws Exception {
for (int i = 1; i <= 5; i++) {
System.out.println("输出:" + i);
if(i == 3){
// 让当前线程进入休眠状态
// 段子:项目经理让我加上这行代码,如果用户愿意交钱,我就注释掉。
Thread.sleep(3000);
}
}
}
}
线程安全问题
多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。
取钱模型演示
线程安全问题出现的原因
总结:多个线程同时访问同一个共享资源且存在修改该资源。
线程安全问题案例模拟
需求:
分析:
账户类
public class Account {
private String cardId;
private double money; // 账户的余额
public Account(){
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
/**
小明 小红
*/
public void drawMoney(double money) {
// 0、先获取是谁来取钱,线程的名字就是人名
String name = Thread.currentThread().getName();
// 1、判断账户是否够钱
if(this.money >= money){
// 2、取钱
System.out.println(name + "来取钱成功,吐出:" + money);
// 3、更新余额
this.money -= money;
System.out.println(name + "取钱后剩余:" + this.money);
}else {
// 4、余额不足
System.out.println(name +"来取钱,余额不足!");
}
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
线程类
/**
取钱的线程类
*/
public class DrawThread extends Thread {
// 接收处理的账户对象
private Account acc;
public DrawThread(Account acc,String name){
super(name);
this.acc = acc;
}
@Override
public void run() {
// 小明 小红:取钱
acc.drawMoney(100000);
}
}
测试类
/**
需求:模拟取钱案例。
*/
public class ThreadDemo {
public static void main(String[] args) {
// 1、定义线程类,创建一个共享的账户对象
Account acc = new Account("ICBC-111", 100000);
// 2、创建2个线程对象,代表小明和小红同时进来了。
new DrawThread(acc, "小明").start();
new DrawThread(acc, "小红").start();
}
}
输出结果
小明来取钱成功,吐出:100000.0
小红来取钱成功,吐出:100000.0
小红取钱后剩余:-100000.0
小明取钱后剩余:0.0
为了解决线程安全问题
问题分析
1、取钱案例出现问题的原因?
2、如何才能保证线程安全呢?
线程同步的核心思想
加锁,把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。
作用
把出现线程安全问题的核心代码给上锁。
原理
每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。
锁对象要求
理论上锁对象只要对于当前同时执行的线程来说是同一个对象即可。
虽然锁住的可以是任意对象,但是会锁住无关线程
synchronized ("chovy") {
...
}
锁对象用任意唯一的对象好不好呢?
代码演示
public void drawMoney(double money) {
// 1、拿到是谁来取钱
String name = Thread.currentThread().getName();
// 同步代码块
// this == acc 共享账户
synchronized (this) {
// 2、判断余额是否足够
if(this.money >= money){
// 钱够了
System.out.println(name+"来取钱,吐出:" + money);
// 更新余额
this.money -= money;
System.out.println(name+"取钱后,余额剩余:" + this.money);
}else{
// 3、余额不足
System.out.println(name+"来取钱,余额不足!");
}
}
}
this == acc 共享账户原理
锁对象的规范要求
规范上:建议使用共享资源作为锁对象。
对于实例方法建议使用this作为锁对象。
对于静态方法建议使用字节码(类名.class)对象作为锁对象。
public class Account {
public static void run(){
synchronized (Account.class){
...
}
}
}
作用
把出现线程安全问题的核心代码给上锁。
原理
每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。
代码演示
public synchronized void drawMoney(double money) {
// 1、拿到是谁来取钱
String name = Thread.currentThread().getName();
// 2、判断余额是否足够
// 小明 小红
if(this.money >= money){
// 钱够了
System.out.println(name+"来取钱,吐出:" + money);
// 更新余额
this.money -= money;
System.out.println(name+"取钱后,余额剩余:" + this.money);
}else{
// 3、余额不足
System.out.println(name+"来取钱,余额不足!");
}
}
同步方法底层原理
同步方法对比同步代码块
同步代码块锁的范围更小,同步方法锁的范围更大。
方法
方法名称 | 说明 |
---|---|
public ReentrantLock() | 获得Lock锁的实现类对象 |
void lock() | 获得锁 |
void unlock() | 释放锁 |
代码演示
// final修饰后:锁对象是唯一和不可替换的,非常专业
private final Lock lock = new ReentrantLock();
public void drawMoney(double money) {
// 1、拿到是谁来取钱
String name = Thread.currentThread().getName();
// 2、判断余额是否足够
// 小明 小红
lock.lock(); // 上锁
try {
if(this.money >= money){
// 钱够了
System.out.println(name+"来取钱,吐出:" + money);
// 更新余额
this.money -= money;
System.out.println(name+"取钱后,余额剩余:" + this.money);
}else{
// 3、余额不足
System.out.println(name+"来取钱,余额不足!");
}
} finally {
lock.unlock(); // 解锁
}
}
所谓线程通信就是线程间相互发送数据,线程间共享一个资源即可实现线程通信。
线程通信常见形式
线程通信实际应用场景
方法名称 | 说明 |
---|---|
void wait() | 让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或 notifyAll()方法 |
void notify() | 唤醒正在等待的单个线程 |
void notifyAll() | 唤醒正在等待的所有线程 |
**注意:**上述方法应该使用当前同步锁对象进行调用。
CallSystem 类
/**
呼叫系统。
*/
public class CallSystem {
// 定义一个变量记录当前呼入进来的电话。
public static int number = 0; // 最多只接听一个。
/* 接入电话
*/
public synchronized static void call() {
try {
number++;
System.out.println("成功接入一个用户,等待分发~~~~");
// 唤醒别人 : 1个
CallSystem.class.notify();
// 让当前线程对象进入等待状态。
CallSystem.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
分发电话
*/
public synchronized static void receive() {
try {
String name = Thread.currentThread().getName();
if(number == 1){
System.out.println(name + "此电话已经分发给客服并接听完毕了~~~~~");
number--;
// 唤醒别人 : 1个
CallSystem.class.notify();
CallSystem.class.wait(); // 让当前线程等待
}else {
// 唤醒别人 : 1个
CallSystem.class.notify();
CallSystem.class.wait(); // 让当前线程等待
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
CallThread 类
public class CallThread extends Thread{
@Override
public void run() {
// 不断的打入电话
while (true){
CallSystem.call();
}
}
}
ReceiveThread 类
/**
接电话线程类
*/
public class ReceiveThread extends Thread{
@Override
public void run() {
// 1号 2号
while (true){
CallSystem.receive();
}
}
}
测试类
public class TestDemo {
public static void main(String[] args) {
// 1、生产者线程:负责不断接收打进来的电话
CallThread call = new CallThread();
call.start();
// 2、消费者线程:客服,每个客服每次接听一个电话
ReceiveThread r1 = new ReceiveThread();
r1.start();
}
}
线程池就是一个可以复用线程的技术。
不使用线程池的问题
如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。
线程池的工作原理
谁代表线程池?
JDK 5.0起提供了代表线程池的接口:ExecutorService
如何得到线程池对象
ThreadPoolExecutor 构造器的参数说明
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数一:指定线程池的线程数量(核心线程): corePoolSize --->不能小于0
参数二:指定线程池可支持的最大线程数: maximumPoolSize --->最大数量 >= 核心线程数量
参数三:指定临时线程的最大存活时间: keepAliveTime --->不能小于0
参数四:指定存活时间的单位(秒、分、时、天): unit --->时间单位
参数五:指定任务队列: workQueue --->不能为null
参数六:指定用哪个线程工厂创建线程: threadFactory --->不能为null
参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler --->不能为null
线程池常见面试题
1、临时线程什么时候创建?
2、什么时候会开始拒绝任务?
ThreadPoolExecutor创建线程池对象示例
ExecutorService pool = new ThreadPoolExecutor(3, 5 ,
6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5) , Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() );
ExecutorService的常用方法
方法名称 | 说明 |
---|---|
void execute(Runnable command) | 执行任务/命令,没有返回值,一般用来执行 Runnable 任务 |
Future submit(Callable task) | 执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务 |
void shutdown() | 等任务执行完毕后关闭线程池 |
List shutdownNow() | 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务 |
MyRunnable
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "输出了:HelloWorld ==> " + i);
}
try {
System.out.println(Thread.currentThread().getName() + "本任务与线程绑定了,线程进入休眠了~~~");
Thread.sleep(10000000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
ThreadPoolDemo
import java.util.concurrent.*;
/**
目标:自定义一个线程池对象,并测试其特性。
*/
public class ThreadPoolDemo {
public static void main(String[] args) {
// 1、创建线程池对象
/**
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
*/
ExecutorService pool = new ThreadPoolExecutor(3, 5 ,
6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5) , Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() );
// 2、给任务线程池处理。
Runnable target = new MyRunnable();
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
// 创建临时线程
pool.execute(target);
pool.execute(target);
// // 不创建,拒绝策略被触发!!!
// pool.execute(target);
// 关闭线程池(开发中一般不会使用)。
// pool.shutdownNow(); // 立即关闭,即使任务没有完成,会丢失任务的!
pool.shutdown(); // 会等待全部任务执行完毕之后再关闭(建议使用的)
}
}
ExecutorService的常用方法
方法名称 | 说明 |
---|---|
void execute(Runnable command) | 执行任务/命令,没有返回值,一般用来执行 Runnable 任务 |
Future submit(Callable task) | 执行Callable任务,返回未来任务对象获取线程结果 |
void shutdown() | 等任务执行完毕后关闭线程池 |
List shutdownNow() | 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务 |
MyCallable
import java.util.concurrent.Callable;
/**
1、定义一个任务类 实现Callable接口 应该申明线程任务执行完毕后的结果的数据类型
*/
public class MyCallable implements Callable<String>{
private int n;
public MyCallable(int n) {
this.n = n;
}
/**
2、重写call方法(任务方法)
*/
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 1; i <= n ; i++) {
sum += i;
}
return Thread.currentThread().getName()
+ "执行 1-" + n+ "的和,结果是:" + sum;
}
}
ThreadPoolDemo
import java.util.concurrent.*;
/**
目标:自定义一个线程池对象,并测试其特性。
*/
public class ThreadPoolDemo {
public static void main(String[] args) throws Exception {
// 1、创建线程池对象
/**
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
*/
ExecutorService pool = new ThreadPoolExecutor(3, 5 ,
6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5) , Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() );
// 2、给任务线程池处理。
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
Future<String> f3 = pool.submit(new MyCallable(300));
Future<String> f4 = pool.submit(new MyCallable(400));
Future<String> f5 = pool.submit(new MyCallable(500));
// String rs = f1.get();
// System.out.println(rs);
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
System.out.println(f5.get());
}
}
输出结果
pool-1-thread-1执行 1-100的和,结果是:5050
pool-1-thread-2执行 1-200的和,结果是:20100
pool-1-thread-3执行 1-300的和,结果是:45150
pool-1-thread-1执行 1-400的和,结果是:80200
pool-1-thread-1执行 1-500的和,结果是:125250
策略 | 详解 |
---|---|
ThreadPoolExecutor.AbortPolicy | 丢弃任务并抛出RejectedExecutionException异常。是默认的策略 |
ThreadPoolExecutor.DiscardPolicy: | 丢弃任务,但是不抛出异常 这是不推荐的做法 |
ThreadPoolExecutor.DiscardOldestPolicy | 抛弃队列中等待最久的任务 然后把当前任务加入队列中 |
ThreadPoolExecutor.CallerRunsPolicy | 由主线程负责调用任务的run()方法从而绕过线程池直接执行 |
Executors得到线程池对象的常用方法
方法名称 | 说明 |
---|---|
public static ExecutorService newCachedThreadPool() | 线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉。 |
public static ExecutorService newFixedThreadPool(int nThreads) | 创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。 |
public static ExecutorService newSingleThreadExecutor () | 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。 |
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) | 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。 |
注意:Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的。
测试代码
/**
目标:使用Executors的工具方法直接得到一个线程池对象。
*/
public class ThreadPoolDemo3 {
public static void main(String[] args) throws Exception {
// 1、创建固定线程数据的线程池
ExecutorService pool = Executors.newFixedThreadPool(3);
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable()); // 已经没有多余线程了
}
}
代码分析
Executors使用可能存在的陷阱
方法名称 | 存在问题 |
---|---|
public static ExecutorService newFixedThreadPool(int nThreads) | 允许请求的任务队列长度是Integer.MAX_VALUE,可能出现OOM错误( java.lang.OutOfMemoryError ) |
public static ExecutorService newSingleThreadExecutor() | 允许请求的任务队列长度是Integer.MAX_VALUE,可能出现OOM错误( java.lang.OutOfMemoryError ) |
public static ExecutorService newCachedThreadPool() | 创建的线程数量最大上限是Integer.MAX_VALUE,线程数可能会随着任务1:1增长,也可能出现OOM错误( java.lang.OutOfMemoryError ) |
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) | 创建的线程数量最大上限是Integer.MAX_VALUE,线程数可能会随着任务1:1增长,也可能出现OOM错误( java.lang.OutOfMemoryError ) |
总结
Executors是否适合做大型互联网场景的线程池方案?
定时器的实现方式
构造器
构造器 | 说明 |
---|---|
public Timer() | 创建Timer定时器对象 |
方法
方法 | 说明 |
---|---|
public void schedule(TimerTask task, long delay, long period) | 开启一个定时器,按照计划处理TimerTask任务 |
代码演示
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
目标:Timer定时器的使用和了解。
*/
public class TimerDemo1 {
public static void main(String[] args) {
// 1、创建Timer定时器
Timer timer = new Timer(); // 定时器本身就是一个单线程。
// 2、调用方法,处理定时任务
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行AAA~~~" + new Date());
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
}, 0, 2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行BB~~~"+ new Date());
System.out.println(10/0);
}
}, 0, 2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行CCC~~~"+ new Date());
}
}, 0, 3000);
}
}
Timer定时器的特点和存在的问题
1、Timer是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入。
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
2、可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行。
System.out.println(10/0);
ScheduledExecutorService是 jdk1.5中引入了并发包,目的是为了弥补Timer的缺陷, ScheduledExecutorService内部为线程池。
Executors的方法
Executors的方法 | 说明 |
---|---|
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) | 得到线程池对象 |
ScheduledExecutorService的方法
ScheduledExecutorService的方法 | 说明 |
---|---|
public ScheduledFuture> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) | 周期调度方法 |
代码演示
import java.util.Date;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
目标:Timer定时器的使用和了解。
*/
public class TimerDemo2 {
public static void main(String[] args) {
// 1、创建ScheduledExecutorService线程池,做定时器
ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
// 2、开启定时任务
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行输出:AAA ==》 " + new Date());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 0, 2, TimeUnit.SECONDS);
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行输出:BBB ==》 " + new Date());
System.out.println(10 / 0);
}
}, 0, 2, TimeUnit.SECONDS);
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行输出:CCC ==》 " + new Date());
}
}, 0, 2, TimeUnit.SECONDS);
}
}
输出结果
pool-1-thread-1执行输出:AAA ==》 Wed Apr 13 00:31:17 CST 2022
pool-1-thread-3执行输出:CCC ==》 Wed Apr 13 00:31:19 CST 2022
pool-1-thread-3执行输出:CCC ==》 Wed Apr 13 00:31:21 CST 2022
pool-1-thread-3执行输出:CCC ==》 Wed Apr 13 00:31:23 CST 2022
pool-1-thread-3执行输出:CCC ==》 Wed Apr 13 00:31:25 CST 2022
pool-1-thread-3执行输出:CCC ==》 Wed Apr 13 00:31:27 CST 2022
pool-1-thread-1执行输出:AAA ==》 Wed Apr 13 00:31:27 CST 2022
pool-1-thread-3执行输出:CCC ==》 Wed Apr 13 00:31:29 CST 2022
pool-1-thread-3执行输出:CCC ==》 Wed Apr 13 00:31:31 CST 2022
pool-1-thread-3执行输出:CCC ==》 Wed Apr 13 00:31:33 CST 2022
pool-1-thread-3执行输出:CCC ==》 Wed Apr 13 00:31:35 CST 2022
pool-1-thread-3执行输出:CCC ==》 Wed Apr 13 00:31:37 CST 2022
pool-1-thread-1执行输出:AAA ==》 Wed Apr 13 00:31:37 CST 2022
pool-1-thread-3执行输出:CCC ==》 Wed Apr 13 00:31:39 CST 2022
pool-1-thread-3执行输出:CCC ==》 Wed Apr 13 00:31:41 CST 2022
pool-1-thread-3执行输出:CCC ==》 Wed Apr 13 00:31:43 CST 2022
...
ScheduledExecutorService的优点
基于线程池,某个任务的执行情况不会影响其他定时任务的执行。
正在运行的程序(软件)就是一个独立的进程, 线程是属于进程的,多个线程其实是并发与并行同时进行的。
并发的理解
并行的理解
人的状态
线程的状态
Java线程的状态
public class Thread{
...
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
...
}
线程的6种状态
线程状态 | 描述 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。 |
Runnable(可运行) | 线程已经调用了start()等待CPU调度 |
Blocked(锁阻塞) | 线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态;。 |
Waiting(无限等待) | 一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
线程的6种状态互相转换
网络编程可以让程序与网络上的其他设备中的程序进行数据交互。
网络通信基本模式
IPv4
IPv6
IP地址基本寻路
IP地址形式
IP地址常用命令
特殊IP地址
此类表示Internet协议(IP)地址。
InetAddress API
名称 | 说明 |
---|---|
public static InetAddress getLocalHost() | 返回本主机的地址对象 |
public static InetAddress getByName(String host) | 得到指定主机的IP地址对象,参数是域名或者IP地址 |
public String getHostName() | 获取此IP地址的主机名 |
public String getHostAddress() | 返回IP地址字符串 |
public boolean isReachable(int timeout) | 在指定毫秒内连通该IP地址对应的主机,连通返回true |
测试代码
import java.net.InetAddress;
/**
目标:InetAddress类概述(了解)
一个该类的对象就代表一个IP地址对象。
InetAddress类成员方法:
static InetAddress getLocalHost()
* 获得本地主机IP地址对象。
static InetAddress getByName(String host)
* 根据IP地址字符串或主机名获得对应的IP地址对象。
String getHostName()
* 获得主机名。
String getHostAddress()
* 获得IP地址字符串。
*/
public class InetAddressDemo01 {
public static void main(String[] args) throws Exception {
// 1.获取本机地址对象。
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName());
System.out.println(ip1.getHostAddress());
// 2.获取域名ip对象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());
System.out.println(ip2.getHostAddress());
// 3.获取公网IP对象。
InetAddress ip3 = InetAddress.getByName("112.80.248.76");
System.out.println(ip3.getHostName());
System.out.println(ip3.getHostAddress());
// 4.判断是否能通: ping 5s之前测试是否可通
System.out.println(ip3.isReachable(5000));
}
}
输出结果
MS-MJPPDXIVHSHA
192.168.31.151
www.baidu.com
14.215.177.39
112.80.248.76
112.80.248.76
true
标识正在计算机设备上运行的进程(程序),被规定为一个 16 位的二进制,范围是 0~65535。
端口类型
注意:我们自己开发的程序选择注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。
连接和通信数据的规则被称为网络通信协议。
参考模型
传输层的2个常见协议
TCP协议特点
TCP协议通信场景
TCP三次握手确立连接
TCP四次挥手断开连接
UDP协议
UDP协议的特点
DatagramPacket:数据包对象
构造器
构造器 | 说明 |
---|---|
public DatagramPacket(byte[] buf, int length, InetAddress address, int port) | 创建发送端数据包对象buf:要发送的内容,字节数组length:要发送内容的字节长度address:接收端的IP地址对象port:接收端的端口号 |
public DatagramPacket(byte[] buf, int length) | 创建接收端的数据包对象buf:用来存储接收的内容length:能够接收内容的长度 |
方法
方法 | 说明 |
---|---|
public int getLength() | 获得实际接收到的字节个数 |
DatagramSocket:发送端和接收端对象
构造器
构造器 | 说明 |
---|---|
public DatagramSocket() | 创建发送端的Socket对象,系统会随机分配一个端口号。 |
public DatagramSocket(int port) | 创建接收端的Socket对象并指定端口号 |
方法
方法 | 说明 |
---|---|
public void send(DatagramPacket dp) | 发送数据包 |
public void receive(DatagramPacket p) | 接收数据包 |
使用UDP通信实现:发送消息、接收消息
客户端实现步骤:
接收端实现步骤:
发送端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
发送端 一发 一收
*/
public class ClientDemo1 {
public static void main(String[] args) throws Exception {
System.out.println("=====客户端启动======");
// 1、创建发送端对象:发送端自带默认的端口号(人)
DatagramSocket socket = new DatagramSocket(6666);
// 2、创建一个数据包对象封装数据(韭菜盘子)
/**
public DatagramPacket(byte buf[], int length,
InetAddress address, int port)
参数一:封装要发送的数据(韭菜)
参数二:发送数据的大小
参数三:服务端的主机IP地址
参数四:服务端的端口
*/
byte[] buffer = "我是一颗快乐的韭菜,你愿意吃吗?".getBytes();
DatagramPacket packet = new DatagramPacket( buffer, buffer.length,
InetAddress.getLocalHost() , 8888);
// 3、发送数据出去
socket.send(packet);
socket.close();
}
}
接收端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
接收端
*/
public class ServerDemo2 {
public static void main(String[] args) throws Exception {
System.out.println("=====服务端启动======");
// 1、创建接收端对象:注册端口(人)
DatagramSocket socket = new DatagramSocket(8888);
// 2、创建一个数据包对象接收数据(韭菜盘子)
byte[] buffer = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// 3、等待接收数据。
socket.receive(packet);
// 4、取出数据即可
// 读取多少倒出多少
int len = packet.getLength();
String rs = new String(buffer,0, len);
System.out.println("收到了:" + rs);
// 获取发送端的ip和端口
String ip =packet.getSocketAddress().toString();
System.out.println("对方地址:" + ip);
int port = packet.getPort();
System.out.println("对方端口:" + port);
socket.close();
}
}
使用UDP通信实现:多发多收消息
需求:
分析:
客户端实现步骤:
接收端实现步骤:
代码演示
发送端:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
/**
发送端 多发 多收
*/
public class ClientDemo1 {
public static void main(String[] args) throws Exception {
System.out.println("=====客户端启动======");
// 1、创建发送端对象:发送端自带默认的端口号(人)
DatagramSocket socket = new DatagramSocket(7777);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
if("exit".equals(msg)){
System.out.println("离线成功!");
socket.close();
break;
}
// 2、创建一个数据包对象封装数据(韭菜盘子)
byte[] buffer = msg.getBytes();
DatagramPacket packet = new DatagramPacket( buffer, buffer.length,
InetAddress.getLocalHost() , 8888);
// 3、发送数据出去
socket.send(packet);
}
}
}
接收端:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
接收端
*/
public class ServerDemo2 {
public static void main(String[] args) throws Exception {
System.out.println("=====服务端启动======");
// 1、创建接收端对象:注册端口(人)
DatagramSocket socket = new DatagramSocket(8888);
// 2、创建一个数据包对象接收数据(韭菜盘子)
byte[] buffer = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
while (true) {
// 3、等待接收数据。
socket.receive(packet);
// 4、取出数据即可
// 读取多少倒出多少
int len = packet.getLength();
String rs = new String(buffer,0, len);
System.out.println("收到了来自:" + packet.getAddress() +", 对方端口是" + packet.getPort() +"的消息:" + rs);
}
}
}
注意事项
UDP的接收端可以接收很多发送端的消息,因为接收端只负责接收数据包,无所谓是哪个发送端的数据包。
UDP的三种通信方式
广播
代码演示
// 发送端
DatagramPacket packet = new DatagramPacket( buffer, buffer.length, InetAddress.getByName("255.255.255.255") , 8888);
// 接收端
DatagramSocket socket = new DatagramSocket(8888);
UDP实现广播发送端是随机指定端口的
收到了来自:/192.168.31.151, 对方端口是51301的消息:1
组播
代码演示
// 发送端
DatagramPacket packet = new DatagramPacket( buffer, buffer.length, InetAddress.getByName("224.0.1.1") , 9898);
// 接收端
MulticastSocket socket = new MulticastSocket(9898);
// 绑定组播地址
socket.joinGroup(new InetSocketAddress(InetAddress.getByName("224.0.1.1") , 9898), etworkInterface.getByInetAddress(InetAddress.getLocalHost()));
UDP实现组播发送端是随机指定端口的
收到了来自:/192.168.31.151, 对方端口是58786的消息:1
TCP通信模式
Socket
构造器
构造器 | 说明 |
---|---|
public Socket(String host , int port) | 创建发送端的Socket对象与服务端连接,参数为服务端程序的ip和端口。 |
方法
方法 | 说明 |
---|---|
OutputStream getOutputStream() | 获得字节输出流对象 |
InputStream getInputStream() | 获得字节输入流对象 |
客户端实现步骤
代码演示
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
/**
目标:完成Socket网络编程入门案例的客户端开发,实现1发1收。
*/
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 7777);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
// 4、发送消息
ps.println("我是TCP的客户端,我已经与你对接,并发出邀请:约吗?");
ps.flush();
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
ServerSocket(服务端)
构造器
构造器 | 说明 |
---|---|
public ServerSocket(int port) | 注册服务端端口 |
方法
方法 | 说明 |
---|---|
public Socket accept() | 等待接收客户端的Socket通信连接连接成功返回Socket对象与客户端建立端到端通信 |
服务端实现步骤
代码演示
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
目标:开发Socket网络编程入门代码的服务端,实现接收消息
*/
public class ServerDemo2 {
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(7777);
// 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道
Socket socket = serverSocket.accept();
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
if ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
TCP通信的基本原理
使用TCP通信方式实现:多发多收消息
具体要求:
对客户端代码进行改进
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
if (msg.equals("exit")){
return;
}
ps.flush();
}
对服务端代码进行改进
String msg;
while ((msg = br.readLine()) != null) {
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
总结
1、本次多发多收是如何实现的
2、现在服务端为什么不可以同时接收多个客户端的消息。
1、之前我们的通信是否可以同时与多个客户端通信,为什么?
2、如何才可以让服务端可以处理多个客户端的通信需求?
添加线程类
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
}
}
}
对服务端代码进行改进
while (true) {
// 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
// 3、开始创建独立线程处理socket
new ServerReaderThread(socket).start();
}
总结
目前的通信架构存在什么问题?
ServerReaderRunnable
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
public class ServerReaderRunnable implements Runnable{
private Socket socket;
public ServerReaderRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
}
}
}
服务端
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
/**
目标:实现服务端可以同时处理多个客户端的消息。
*/
public class ServerDemo2 {
// 使用静态变量记住一个线程池对象
private static ExecutorService pool = new ThreadPoolExecutor(300,
1500, 6, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2)
, Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(6666);
// a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
while (true) {
// 2、每接收到一个客户端的Socket管道,
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
// 任务对象负责读取消息。
Runnable target = new ServerReaderRunnable(socket);
pool.execute(target);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
总结
本次使用线程池的优势在哪里?
即时通信是什么含义,要实现怎么样的设计?
即时通信-端口转发
ClientReaderThread
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
public class ClientReaderThread extends Thread{
private Socket socket;
public ClientReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "收到了: " + msg);
}
} catch (Exception e) {
System.out.println("服务端把你踢出去了~~");
}
}
}
Client 端测试类
/**
拓展:即时通信
客户端:发消息的同时,随时有人发消息过来。
服务端:接收消息后,推送给其他所有的在线socket
*/
public class ClientDemo {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 6868);
// 马上为客户端分配一个独立的线程负责读取它收到的消息
new ClientReaderThread(socket).start();
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
ps.flush();
}
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
ServerReaderThread
import java.io.*;
import java.net.Socket;
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
// 把这个消息发给当前所有在线socket
sendMsgToAll(msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
// 从在线集合中抹掉本客户端socket
ServerDemo2.onLineSockets.remove(socket);
}
}
private void sendMsgToAll(String msg) {
try {
// 遍历全部的在线 socket给他们发消息
for (Socket onLineSocket : ServerDemo2.onLineSockets) {
// 除了自己的socket,其他socket我都发!!
if(onLineSocket != socket){
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println(msg);
ps.flush();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Server 端测试类
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
目标: 即时通信
*/
public class ServerDemo {
public static List<Socket> onLineSockets = new ArrayList<>();
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(6868);
// a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
while (true) {
// 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
// 把当前客户端管道Socket加入到在线集合中去
onLineSockets.add(socket);
// 3、开始创建独立线程处理socket
new ServerReaderThread(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
1、之前的客户端都是什么样的?
2、BS结构是什么样的,需要开发客户端吗?
实现BS开发
HTTP响应数据的协议格式
ServerReaderRunnable
import java.io.PrintStream;
import java.net.Socket;
public class ServerReaderRunnable implements Runnable{
private Socket socket;
public ServerReaderRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 浏览器 已经与本线程建立了Socket管道
// 响应消息给浏览器显示
PrintStream ps = new PrintStream(socket.getOutputStream());
// 必须响应HTTP协议格式数据,否则浏览器不认识消息
ps.println("HTTP/1.1 200 OK"); // 协议类型和版本 响应成功的消息!
ps.println("Content-Type:text/html;charset=UTF-8"); // 响应的数据类型:文本/网页
ps.println(); // 必须发送一个空行
// 才可以响应数据回去给浏览器
ps.println("《少女的祈祷》 ");
ps.close();
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
}
}
}
测试类
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
/**
了解:BS-浏览器-服务器基本了解。
引入:
之前客户端和服务端都需要自己开发。也就是CS架构。
接下来模拟一下BS架构。
客户端:浏览器。(无需开发)
服务端:自己开发。
需求:在浏览器中请求本程序,响应一个网页文字给浏览器显示
*/
public class BSserverDemo {
// 使用静态变量记住一个线程池对象
private static ExecutorService pool = new ThreadPoolExecutor(3,
5, 6, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2)
, Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
try {
// 1.注册端口
ServerSocket ss = new ServerSocket(8080);
// 2.创建一个循环接收多个客户端的请求。
while(true){
Socket socket = ss.accept();
// 3.交给一个独立的线程来处理!
pool.execute(new ServerReaderRunnable(socket));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试结果
单元测试
目前测试方法是怎么进行的,存在什么问题?
Junit单元测试框架
JUnit优点
使用单元测试进行业务方法预期结果、正确性测试的快速入门
将JUnit的jar包导入到项目中
编写测试方法:该测试方法必须是公共的无参数无返回值的非静态方法。
在测试方法上使用@Test注解:标注该方法是一个测试方法
在测试方法中完成被测试方法的预期正确性测试。
选中测试方法,选择“JUnit运行” ,如果测试良好则是绿色;如果测试失败,则是红色
业务方法
/**
业务方法
*/
public class UserService {
public String loginName(String loginName , String passWord){
if("admin".equals(loginName) && "123456".equals(passWord)){
return "登录成功";
}else {
return "用户名或者密码有问题";
}
}
public void selectNames(){
System.out.println(10/2);
System.out.println("查询全部用户名称成功~~");
}
}
测试方法
/**
测试方法
注意点:
1、必须是公开的,无参数 无返回值的方法
2、测试方法必须使用@Test注解标记。
*/
@Test
public void testLoginName(){
UserService userService = new UserService();
String rs = userService.loginName("admin","123456");
// 进行预期结果的正确性测试:断言。
Assert.assertEquals("您的登录业务可能出现问题", "登录成功", rs );
}
@Test
public void testSelectNames(){
UserService userService = new UserService();
userService.selectNames();
}
Junit常用注解 (Junit 4.xx版本)
注解 | 说明 |
---|---|
@Test | 测试方法 |
@Before | 用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次。 |
@After | 用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次。 |
@BeforeClass | 用来静态修饰方法,该方法会在所有测试方法之前只执行一次。 |
@AfterClass | 用来静态修饰方法,该方法会在所有测试方法之后只执行一次。 |
Junit常用注解 (Junit 5.xx版本)
注解 | 说明 |
---|---|
@Test | 测试方法 |
@BeforeEach | 用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次。 |
@AfterEach | 用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次。 |
@BeforeAll | 用来静态修饰方法,该方法会在所有测试方法之前只执行一次。 |
@AfterAll | 用来静态修饰方法,该方法会在所有测试方法之后只执行一次。 |
测试方法
import org.junit.*;
/**
测试类
*/
public class TestUserService {
// 修饰实例方法的
@Before
public void before(){
System.out.println("===before方法执行一次===");
}
@After
public void after(){
System.out.println("===after方法执行一次===");
}
// 修饰静态方法
@BeforeClass
public static void beforeClass(){
System.out.println("===beforeClass方法执行一次===");
}
@AfterClass
public static void afterClass(){
System.out.println("===afterClass方法执行一次===");
}
/**
测试方法
注意点:
1、必须是公开的,无参数 无返回值的方法
2、测试方法必须使用@Test注解标记。
*/
@Test
public void testLoginName(){
UserService userService = new UserService();
String rs = userService.loginName("admin","123456");
// 进行预期结果的正确性测试:断言。
Assert.assertEquals("您的登录业务可能出现问题", "登录成功", rs );
}
@Test
public void testSelectNames(){
UserService userService = new UserService();
userService.selectNames();
}
}
输出结果
===beforeClass方法执行一次===
===before方法执行一次===
===after方法执行一次===
===before方法执行一次===
5
查询全部用户名称成功~~
===after方法执行一次===
===afterClass方法执行一次===
反射概述
反射的关键
反射的第一步都是先得到编译后的Class类对象,然后就可以得到Class的全部成分。
HelloWorld.java -> javac -> HelloWorld.class
Class c = HelloWorld.class;
获取Class类的对象的三种方式
Student 类
package com.itheima.d2_reflect_class;
public class Student {
}
测试类
/**
目标:反射的第一步:获取Class对象
*/
public class Test {
public static void main(String[] args) throws Exception {
// 1、Class类中的一个静态方法:forName(全限名:包名 + 类名)
Class c = Class.forName("com.itheima.d2_reflect_class.Student");
System.out.println(c); // Student.class
// 2、类名.class
Class c1 = Student.class;
System.out.println(c1);
// 3、对象.getClass() 获取对象对应类的Class对象。
Student s = new Student();
Class c2 = s.getClass();
System.out.println(c2);
}
}
输出结果
class com.itheima.d2_reflect_class.Student
class com.itheima.d2_reflect_class.Student
class com.itheima.d2_reflect_class.Student
使用反射技术获取构造器对象并使用
方法 | 说明 |
---|---|
Constructor>[] getConstructors() | 返回所有构造器对象的数组(只能拿public的) |
Constructor>[] getDeclaredConstructors() | 返回所有构造器对象的数组,存在就能拿到 |
Constructor getConstructor(Class>… parameterTypes) | 返回单个构造器对象(只能拿public的) |
Constructor getDeclaredConstructor(Class>… parameterTypes) | 返回单个构造器对象,存在就能拿到 |
Constructor类中用于创建对象的方法
符号 | 说明 |
---|---|
T newInstance(Object… initargs) | 根据指定的构造器创建对象 |
public void setAccessible(boolean flag) | 设置为true, 表示取消访问检查,进行暴力反射 |
Student 类
public class Student {
private String name;
private int age;
private Student(){
System.out.println("无参数构造器执行!");
}
public Student(String name, int age) {
System.out.println("有参数构造器执行!");
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试代码
import org.junit.Test;
import java.lang.reflect.Constructor;
/**
目标:反射_获取Constructor构造器对象.
反射的第一步是先得到Class类对象。(Class文件)
反射中Class类型获取构造器提供了很多的API:
1. Constructor getConstructor(Class... parameterTypes)
根据参数匹配获取某个构造器,只能拿public修饰的构造器,几乎不用!
2. Constructor getDeclaredConstructor(Class... parameterTypes)
根据参数匹配获取某个构造器,只要申明就可以定位,不关心权限修饰符,建议使用!
3. Constructor[] getConstructors()
获取所有的构造器,只能拿public修饰的构造器。几乎不用!!太弱了!
4. Constructor[] getDeclaredConstructors()
获取所有申明的构造器,只要你写我就能拿到,无所谓权限。建议使用!!
小结:
获取类的全部构造器对象: Constructor[] getDeclaredConstructors()
-- 获取所有申明的构造器,只要你写我就能拿到,无所谓权限。建议使用!!
获取类的某个构造器对象:Constructor getDeclaredConstructor(Class... parameterTypes)
-- 根据参数匹配获取某个构造器,只要申明就可以定位,不关心权限修饰符,建议使用!
*/
public class TestStudent01 {
// 1. getConstructors:
// 获取全部的构造器:只能获取public修饰的构造器。
// Constructor[] getConstructors()
@Test
public void getConstructors(){
// a.第一步:获取类对象
Class c = Student.class;
// b.提取类中的全部的构造器对象(这里只能拿public修饰)
Constructor[] constructors = c.getConstructors();
// c.遍历构造器
for (Constructor constructor : constructors) {
System.out.println(constructor.getName() + "===>" + constructor.getParameterCount());
}
}
// 2.getDeclaredConstructors():
// 获取全部的构造器:只要你敢写,这里就能拿到,无所谓权限是否可及。
@Test
public void getDeclaredConstructors(){
// a.第一步:获取类对象
Class c = Student.class;
// b.提取类中的全部的构造器对象
Constructor[] constructors = c.getDeclaredConstructors();
// c.遍历构造器
for (Constructor constructor : constructors) {
System.out.println(constructor.getName() + "===>" + constructor.getParameterCount());
}
}
// 3.getConstructor(Class... parameterTypes)
// 获取某个构造器:只能拿public修饰的某个构造器
@Test
public void getConstructor() throws Exception {
// a.第一步:获取类对象
Class c = Student.class;
// b.定位单个构造器对象 (按照参数定位无参数构造器 只能拿public修饰的某个构造器)
Constructor cons = c.getConstructor();
System.out.println(cons.getName() + "===>" + cons.getParameterCount());
}
// 4.getConstructor(Class... parameterTypes)
// 获取某个构造器:只要你敢写,这里就能拿到,无所谓权限是否可及。
@Test
public void getDeclaredConstructor() throws Exception {
// a.第一步:获取类对象
Class c = Student.class;
// b.定位单个构造器对象 (按照参数定位无参数构造器)
Constructor cons = c.getDeclaredConstructor();
System.out.println(cons.getName() + "===>" + cons.getParameterCount());
// c.定位某个有参构造器
Constructor cons1 = c.getDeclaredConstructor(String.class, int.class);
System.out.println(cons1.getName() + "===>" + cons1.getParameterCount());
}
}
调用构造器得到一个类的对象
测试代码
import org.junit.Test;
import java.lang.reflect.Constructor;
/**
目标: 反射_获取Constructor构造器然后通过这个构造器初始化对象。
反射获取Class中的构造器对象Constructor作用:
也是初始化并得到类的一个对象返回。
Constructor的API:
1. T newInstance(Object... initargs)
创建对象,注入构造器需要的数据。
2. void setAccessible(true)
修改访问权限,true代表暴力攻破权限,false表示保留不可访问权限(暴力反射)
小结:
可以通过定位类的构造器对象。
如果构造器对象没有访问权限可以通过:void setAccessible(true)打开权限
构造器可以通过T newInstance(Object... initargs)调用自己,传入参数!
*/
public class TestStudent02 {
// 1.调用构造器得到一个类的对象返回。
@Test
public void getDeclaredConstructor() throws Exception {
// a.第一步:获取类对象
Class c = Student.class;
// b.定位单个构造器对象 (按照参数定位无参数构造器)
Constructor cons = c.getDeclaredConstructor();
System.out.println(cons.getName() + "===>" + cons.getParameterCount());
// 如果遇到了私有的构造器,可以暴力反射
cons.setAccessible(true); // 权限被打开
Student s = (Student) cons.newInstance();
System.out.println(s);
System.out.println("-------------------");
// c.定位某个有参构造器
Constructor cons1 = c.getDeclaredConstructor(String.class, int.class);
System.out.println(cons1.getName() + "===>" + cons1.getParameterCount());
Student s1 = (Student) cons1.newInstance("孙悟空", 1000);
System.out.println(s1);
}
}
输出结果
com.itheima.d3_reflect_constructor.Student===>0
无参数构造器执行!
Student{name='null', age=0}
-------------------
com.itheima.d3_reflect_constructor.Student===>2
有参数构造器执行!
Student{name='孙悟空', age=1000}
反射得到的构造器可以做什么?
使用反射技术获取成员变量对象并使用
方法 | 说明 |
---|---|
Field[] getFields() | 返回所有成员变量对象的数组(只能拿public的) |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组,存在就能拿到 |
Field getField(String name) | 返回单个成员变量对象(只能拿public的) |
Field getDeclaredField(String name) | 返回单个成员变量对象,存在就能拿到 |
Field类中用于取值、赋值的方法
符号 | 说明 |
---|---|
void set(Object obj, Object value): | 赋值 |
Object get(Object obj) | 获取值。 |
代码演示
Student类
public class Student {
private String name;
private int age;
public static String schoolName;
public static final String COUNTTRY = "中国";
public Student(){
System.out.println("无参数构造器执行!");
}
public Student(String name, int age) {
System.out.println("有参数构造器执行!");
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试类
import org.junit.Test;
import java.lang.reflect.Field;
/**
目标:反射_获取Field成员变量对象。
反射的第一步是先得到Class类对象。
1、Field getField(String name);
根据成员变量名获得对应Field对象,只能获得public修饰
2.Field getDeclaredField(String name);
根据成员变量名获得对应Field对象,只要申明了就可以得到
3.Field[] getFields();
获得所有的成员变量对应的Field对象,只能获得public的
4.Field[] getDeclaredFields();
获得所有的成员变量对应的Field对象,只要申明了就可以得到
小结:
获取全部成员变量:getDeclaredFields
获取某个成员变量:getDeclaredField
*/
public class FieldDemo01 {
/**
* 1.获取全部的成员变量。
* Field[] getDeclaredFields();
* 获得所有的成员变量对应的Field对象,只要申明了就可以得到
*/
@Test
public void getDeclaredFields(){
// a.定位Class对象
Class c = Student.class;
// b.定位全部成员变量
Field[] fields = c.getDeclaredFields();
// c.遍历一下
for (Field field : fields) {
System.out.println(field.getName() + "==>" + field.getType());
}
}
/**
2.获取某个成员变量对象 Field getDeclaredField(String name);
*/
@Test
public void getDeclaredField() throws Exception {
// a.定位Class对象
Class c = Student.class;
// b.根据名称定位某个成员变量
Field f = c.getDeclaredField("age");
System.out.println(f.getName() +"===>" + f.getType());
}
}
取值、赋值
import org.junit.Test;
import java.lang.reflect.Field;
/**
目标:反射获取成员变量: 取值和赋值。
Field的方法:给成员变量赋值和取值
void set(Object obj, Object value):给对象注入某个成员变量数据
Object get(Object obj):获取对象的成员变量的值。
void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。
Class getType(); 获取属性的类型,返回Class对象。
String getName(); 获取属性的名称。
*/
public class FieldDemo02 {
@Test
public void setField() throws Exception {
// a.反射第一步,获取类对象
Class c = Student.class;
// b.提取某个成员变量
Field ageF = c.getDeclaredField("age");
ageF.setAccessible(true); // 暴力打开权限
// c.赋值
Student s = new Student();
ageF.set(s , 18); // s.setAge(18);
System.out.println(s);
// d、取值
int age = (int) ageF.get(s);
System.out.println(age);
}
}
取值、赋值输出结果
无参数构造器执行!
Student{name='null', age=18}
18
使用反射技术获取方法对象并使用
方法 | 说明 |
---|---|
Method[] getMethods() | 返回所有成员方法对象的数组(只能拿public的) |
Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,存在就能拿到 |
Method getMethod(String name, Class>… parameterTypes) | 返回单个成员方法对象(只能拿public的) |
Method getDeclaredMethod(String name, Class>… parameterTypes) | 返回单个成员方法对象,存在就能拿到 |
Method类中用于触发执行的方法
测试代码
Dog 类
public class Dog {
private String name ;
public Dog(){
}
public Dog(String name) {
this.name = name;
}
public void run(){
System.out.println("狗跑的贼快~~");
}
private void eat(){
System.out.println("狗吃骨头");
}
private String eat(String name){
System.out.println("狗吃" + name);
return "吃的很开心!";
}
public static void inAddr(){
System.out.println("在黑马学习Java!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
获得方法
/**
* 1.获得类中的所有成员方法对象
*/
@Test
public void getDeclaredMethods(){
// a.获取类对象
Class c = Dog.class;
// b.提取全部方法;包括私有的
Method[] methods = c.getDeclaredMethods();
// c.遍历全部方法
for (Method method : methods) {
System.out.println(method.getName() +" 返回值类型:" + method.getReturnType() + " 参数个数:" + method.getParameterCount());
}
}
输出结果
getName 返回值类型:class java.lang.String 参数个数:0
run 返回值类型:void 参数个数:0
setName 返回值类型:void 参数个数:1
eat 返回值类型:void 参数个数:0
eat 返回值类型:class java.lang.String 参数个数:1
inAddr 返回值类型:void 参数个数:0
使用方法
/**
* 2. 获取某个方法对象
*/
@Test
public void getDeclardMethod() throws Exception {
// a.获取类对象
Class c = Dog.class;
// b.提取单个方法对象
Method m = c.getDeclaredMethod("eat");
Method m2 = c.getDeclaredMethod("eat", String.class);
Method m3 = c.getDeclaredMethod("inAddr");
// 暴力打开权限了
m.setAccessible(true);
m2.setAccessible(true);
m3.setAccessible(true);
// c.触发方法的执行
Dog d = new Dog();
// 注意:方法如果是没有结果回来的,那么返回的是null.
Object result = m.invoke(d);
System.out.println(result);
Object result2 = m2.invoke(d, "骨头");
System.out.println(result2);
Object result3 = m3.invoke(d);
System.out.println(result3);
}
输出结果
狗吃骨头
null
狗吃骨头
吃的很开心!
在黑马学习Java!
null
静态方法也是可以通过反射调用的
Method m3 = c.getDeclaredMethod("inAddr");
代码演示
测试代码
import java.lang.reflect.Method;
import java.util.ArrayList;
public class ReflectDemo {
public static void main(String[] args) throws Exception {
// 需求:反射实现泛型擦除后,加入其他类型的元素
ArrayList<String> lists1 = new ArrayList<>();
ArrayList<Integer> lists2 = new ArrayList<>();
System.out.println(lists1.getClass());
System.out.println(lists2.getClass());
System.out.println(lists1.getClass() == lists2.getClass()); // ArrayList.class
System.out.println("---------------------------");
ArrayList<Integer> lists3 = new ArrayList<>();
lists3.add(23);
lists3.add(22);
// lists3.add("黑马");
Class c = lists3.getClass(); // ArrayList.class ===> public boolean add(E e)
// 定位c类中的add方法
Method add = c.getDeclaredMethod("add", Object.class);
boolean rs = (boolean) add.invoke(lists3, "黑马");
System.out.println(rs);
System.out.println(lists3);
// 其实也可以不使用反射
ArrayList list4 = lists3;
list4.add("白马");
list4.add(false);
System.out.println(lists3);
}
}
输出结果
class java.util.ArrayList
class java.util.ArrayList
true
---------------------------
true
[23, 22, 黑马]
[23, 22, 黑马, 白马, false]
其实不适用反射也可以
// 其实也可以不使用反射
ArrayList list4 = lists3;
list4.add("白马");
list4.add(false);
System.out.println(lists3);
反射做通用框架
需求:
分析:
代码演示
学生类
public class Student {
private String name;
private char sex;
private int age;
private String className;
private String hobby;
public Student(){
}
public Student(String name, char sex, int age, String className, String hobby) {
this.name = name;
this.sex = sex;
this.age = age;
this.className = className;
this.hobby = hobby;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getHobby() {
return hobby;
}
public void setHobby(String hobby) {
this.hobby = hobby;
}
}
老师类
public class Teacher {
private String name;
private char sex;
private double salary;
public Teacher(){
}
public Teacher(String name, char sex, double salary) {
this.name = name;
this.sex = sex;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
工具
public class MybatisUtil {
/**
保存任意类型的对象
* @param obj
*/
public static void save(Object obj){
try (
PrintStream ps = new PrintStream(new FileOutputStream("junit-reflect-annotation-proxy-app/src/data.txt", true));
){
// 1、提取这个对象的全部成员变量:只有反射可以解决
Class c = obj.getClass(); // c.getSimpleName()获取当前类名 c.getName获取全限名:包名+类名
ps.println("================" + c.getSimpleName() + "================");
// 2、提取它的全部成员变量
Field[] fields = c.getDeclaredFields();
// 3、获取成员变量的信息
for (Field field : fields) {
String name = field.getName();
// 提取本成员变量在obj对象中的值(取值)
field.setAccessible(true);
String value = field.get(obj) + "";
ps.println(name + "=" + value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试类
/**
目标:提供一个通用框架,支持保存所有对象的具体信息。
*/
public class ReflectDemo {
public static void main(String[] args) throws Exception {
Student s = new Student();
s.setName("猪八戒");
s.setClassName("西天跑路1班");
s.setAge(1000);
s.setHobby("吃,睡");
s.setSex('男');
MybatisUtil.save(s);
Teacher t = new Teacher();
t.setName("波仔");
t.setSex('男');
t.setSalary(6000);
MybatisUtil.save(t);
}
}
data.txt文件信息
================Student================
name=猪八戒
sex=男
age=1000
className=西天跑路1班
hobby=吃,睡
================Teacher================
name=波仔
sex=男
salary=6000.0
注解的作用
格式
public @interface MyBook {
String name();
String[] authors();
double price();
}
特殊属性
public @interface Book {
String value(); // 特殊属性
double price() ;
//double price() default 9.9;
}
//@Book("/delete")
@Book(value = "/delete", price = 23.5)
注解注解的注解。
@Target: 约束自定义注解只能在哪些地方使用
@Target中可使用的值定义在ElementType枚举类中,常用值如下
@Retention:申明注解的生命周期
@Retention中可使用的值定义在RetentionPolicy枚举类中,常用值如下
代码演示
@Target({ElementType.METHOD,ElementType.FIELD}) // 元注解
@Retention(RetentionPolicy.RUNTIME) // 一直活着,在运行阶段这个注解也不消失
public @interface MyTest {
}
注解的操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容。
注解解析相关接口
方法 | 说明 |
---|---|
Annotation[] getDeclaredAnnotations() | 获得当前对象上使用的所有注解,返回注解数组。 |
T getDeclaredAnnotation(Class annotationClass) | 根据注解类型获得对应注解对象 |
boolean isAnnotationPresent(Class annotationClass) | 判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false |
解析注解的技巧
代码演示
Bookk注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Bookk {
String value();
double price() default 100;
String[] author();
}
测试类
import org.junit.Test;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
目标:完成注解的解析
*/
public class AnnotationDemo3 {
@Test
public void parseClass(){
// a.先得到类对象
Class c = BookStore.class;
// b.判断这个类上面是否存在这个注解
if(c.isAnnotationPresent(Bookk.class)){
//c.直接获取该注解对象
Bookk book = (Bookk) c.getDeclaredAnnotation(Bookk.class);
System.out.println(book.value());
System.out.println(book.price());
System.out.println(Arrays.toString(book.author()));
}
}
@Test
public void parseMethod() throws NoSuchMethodException {
// a.先得到类对象
Class c = BookStore.class;
Method m = c.getDeclaredMethod("test");
// b.判断这个类上面是否存在这个注解
if(m.isAnnotationPresent(Bookk.class)){
//c.直接获取该注解对象
Bookk book = (Bookk) m.getDeclaredAnnotation(Bookk.class);
System.out.println(book.value());
System.out.println(book.price());
System.out.println(Arrays.toString(book.author()));
}
}
}
@Bookk(value = "《情深深雨濛濛》", price = 99.9, author = {"琼瑶", "dlei"})
class BookStore{
@Bookk(value = "《三少爷的剑》", price = 399.9, author = {"古龙", "熊耀华"})
public void test(){
}
}
输出结果
《情深深雨濛濛》
99.9
[琼瑶, dlei]
《三少爷的剑》
399.9
[古龙, 熊耀华]
模拟Junit框架
需求:
分析:
代码演示
MyTest 注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD,ElementType.FIELD}) // 元注解
@Retention(RetentionPolicy.RUNTIME) // 一直活着,在运行阶段这个注解也不消失
public @interface MyTest {
}
测试类
import java.lang.reflect.Method;
public class AnnotationDemo4 {
public void test1(){
System.out.println("===test1===");
}
@MyTest
public void test2(){
System.out.println("===test2===");
}
@MyTest
public void test3(){
System.out.println("===test3===");
}
/**
启动菜单:有注解的才被调用。
*/
public static void main(String[] args) throws Exception {
AnnotationDemo4 t = new AnnotationDemo4();
// a.获取类对象
Class c = AnnotationDemo4.class;
// b.提取全部方法
Method[] methods = c.getDeclaredMethods();
// c.遍历方法,看是否有MyTest注解,有就跑它
for (Method method : methods) {
if(method.isAnnotationPresent(MyTest.class)){
// 跑它
method.invoke(t);
}
}
}
}
输出结果
===test2===
===test3===
模拟企业业务功能开发,并完成每个功能的性能统计
需求:
分析:
代码演示
本案例存在哪些问题?
业务对象的的每个方法都要进行性能统计,存在大量重复的代码。
动态代理
代理就是被代理者没有能力或者不愿意去完成某件事情,需要找个人代替自己去完成这件事,动态代理就是用来对业务功能(方法)进行代理的。
关键步骤
代码演示
UserService 接口
/**
模拟用户业务功能
*/
public interface UserService {
String login(String loginName , String passWord) ;
void selectUsers();
boolean deleteUsers();
void updateUsers();
}
UserServiceImpl
public class UserServiceImpl implements UserService{
@Override
public String login(String loginName, String passWord) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
if("admin".equals(loginName) && "1234".equals(passWord)) {
return "success";
}
return "登录名和密码可能有毛病";
}
@Override
public void selectUsers() {
System.out.println("查询了100个用户数据!");
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public boolean deleteUsers() {
try {
System.out.println("删除100个用户数据!");
Thread.sleep(500);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public void updateUsers() {
try {
System.out.println("修改100个用户数据!");
Thread.sleep(2500);
} catch (Exception e) {
e.printStackTrace();
}
}
}
代理对象
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
public static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)
参数一:类加载器,负责加载代理类到内存中使用。
参数二:获取被代理对象实现的全部接口。代理要为全部接口的全部方法进行代理
参数三:代理的核心处理逻辑
*/
public class ProxyUtil {
/**
生成业务对象的代理对象。
* @param obj
* @return
*/
public static <T> T getProxy(T obj) {
// 返回了一个代理对象了
return (T)Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 参数一:代理对象本身。一般不管
// 参数二:正在被代理的方法
// 参数三:被代理方法,应该传入的参数
long startTimer = System .currentTimeMillis();
// 马上触发方法的真正执行。(触发真正的业务功能)
Object result = method.invoke(obj, args);
long endTimer = System.currentTimeMillis();
System.out.println(method.getName() + "方法耗时:" + (endTimer - startTimer) / 1000.0 + "s");
// 把业务功能方法执行的结果返回给调用者
return result;
}
});
}
}
测试类
public class Test {
public static void main(String[] args) {
// 1、把业务对象,直接做成一个代理对象返回,代理对象的类型也是 UserService类型
UserService userService = ProxyUtil.getProxy(new UserServiceImpl());
System.out.println(userService.login("admin", "1234"));
System.out.println(userService.deleteUsers());
userService.selectUsers();
userService.updateUsers(); // 走代理
}
}
测试结果
login方法耗时:1.01s
success
删除100个用户数据!
deleteUsers方法耗时:0.502s
true
查询了100个用户数据!
selectUsers方法耗时:2.001s
修改100个用户数据!
updateUsers方法耗时:2.501s
动态代理的优点
<data>
<sender>张三sender>
<receiver>李四receiver>
<src>
<addr>北京addr>
<date>2022-11-11 11:11:11date>
src>
<current>武汉current>
<dest>广州dest>
data>
XML的几个特点和使用场景
XML的创建
就是创建一个XML类型的文件,要求文件的后缀必须使用xml,如hello_world.xml
XML的语法规则
XML的标签(元素)规则
XML的其他组成
XML文件中可以定义注释信息:
XML文件中可以存在以下特殊字符
< < 小于
> > 大于
& & 和号
' ' 单引号
" " 引号
XML文件中可以存在CDATA区:
文档约束
文档约束的分类
XML文档约束-DTD的使用
需求:
分析:
编写DTD约束文档,后缀必须是.dtd
在需要编写的XML文件中导入该DTD约束文档
DOCTYPE 书架 SYSTEM "data.dtd">
<书架>
<书>
<书名>精通JavaSE加强书名>
<作者>dlei作者>
<售价>很贵售价>
书>
<书>
<书名>书名>
<作者>作者>
<售价>售价>
书>
<书>
<书名>书名>
<作者>作者>
<售价>售价>
书>
书架>
按照约束的规定编写XML文件的内容
MyBatis框架使用到DTD约束
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
DTD约束的问题
不能约束具体的数据类型。
XML文档约束-schema的使用
需求:
分析:
编写schema约束文档,后缀必须是.xsd,具体的形式到代码中观看。
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.itcast.cn"
elementFormDefault="qualified" >
<element name='书架'>
<complexType>
<sequence maxOccurs='unbounded'>
<element name='书'>
<complexType>
<sequence>
<element name='书名' type='string'/>
<element name='作者' type='string'/>
<element name='售价' type='double'/>
sequence>
complexType>
element>
sequence>
complexType>
element>
schema>
在需要编写的XML文件中导入该schema约束文档
<书架 xmlns="http://www.itcast.cn"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.itcast.cn data.xsd">
<书>
<书名>神雕侠侣书名>
<作者>金庸作者>
<售价>399.9售价>
书>
<书>
<书名>神雕侠侣书名>
<作者>金庸作者>
<售价>19.5售价>
书>
书架>
按照约束内容编写XML文件的标签。
Spring框架使用到schema约束
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
beans>
XML的文档约束-schema的优点
XML的数据的作用是什么,最终需要怎么处理?
什么是XML解析?
XML解析方式
Dom常见的解析工具
名称 | 说明 |
---|---|
JAXP | SUN公司提供的一套XML的解析的API |
JDOM | JDOM是一个开源项目,它基于树型结构,利用纯JAVA的技术对XML文档实现解析、生成、序列化以及多种操作。 |
dom4j | 是JDOM的升级品,用来读写XML文件的。具有性能优异、功能强大和极其易使用的特点,它的性能超过sun公司官方的dom 技术,同时它也是一个开放源代码的软件,Hibernate也用它来读写配置文件。 |
jsoup | 功能强大DOM方式的XML解析开发包,尤其对HTML解析更加方便 |
DOM解析解析文档对象模型
Dom4j解析XML-得到Document对象
SAXReader类
构造器/方法 | 说明 |
---|---|
public SAXReader() | 创建Dom4J的解析器对象 |
Document read(String url) | 加载XML文件成为Document对象 |
Document类
方法名 | 说明 |
---|---|
Element getRootElement() | 获得根元素对象 |
代码演示
Contacts.xml
<contactList>
<contact id="1" vip="true">
<name> 潘金莲 name>
<gender>女gender>
<email>[email protected]email>
contact>
<contact id="2" vip="false">
<name>武松name>
<gender>男gender>
<email>[email protected]email>
contact>
<contact id="3" vip="false">
<name>武大狼name>
<gender>男gender>
<email>[email protected]email>
contact>
<user>
user>
contactList>
测试代码
SAXReader saxReader = new SAXReader();
InputStream is = Dom4JHelloWorldDemo1.class.getResourceAsStream("/Contacts.xml");
Document document = saxReader.read(is);
Element root = document.getRootElement();
System.out.println(root.getName());
输出结果
contactList
Dom4j解析XML的元素、属性、文本
方法名 | 说明 |
---|---|
List elements() | 得到当前元素下所有子元素 |
List elements(String name) | 得到当前元素下指定名字的子元素返回集合 |
Element element(String name) | 得到当前元素下指定名字的子元素,如果有很多名字相同的返回第一个 |
String getName() | 得到元素名字 |
String attributeValue(String name) | 通过属性名直接得到属性值 |
String elementText(子元素名) | 得到指定名称的子元素的文本 |
String getText() | 得到文本 |
代码演示
测试代码
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.List;
/**
目标:学会使用dom4j解析XML文件中的数据。
1、导入dom4j框架。
2、准备一个XML文件。
*/
public class Dom4JHelloWorldDemo1 {
@Test
public void parseXMLData() throws Exception {
// 1、创建一个Dom4j的解析器对象,代表了整个dom4j框架
SAXReader saxReader = new SAXReader();
// 2、把XML文件加载到内存中成为一个Document文档对象
// Document document = saxReader.read(new File("xml-app\\src\\Contacts.xml")); // 需要通过模块名去定位
// Document document = saxReader.read(new FileInputStream("xml-app\\src\\Contacts.xml"));
// 注意: getResourceAsStream中的/是直接去src下寻找的文件
InputStream is = Dom4JHelloWorldDemo1.class.getResourceAsStream("/Contacts.xml");
Document document = saxReader.read(is);
// 3、获取根元素对象
Element root = document.getRootElement();
System.out.println(root.getName());
// 4、拿根元素下的全部子元素对象(一级)
// List sonEles = root.elements();
List<Element> sonEles = root.elements("contact");
for (Element sonEle : sonEles) {
System.out.println(sonEle.getName());
}
// 拿某个子元素
Element userEle = root.element("user");
System.out.println(userEle.getName());
// 默认提取第一个子元素对象 (Java语言。)
Element contact = root.element("contact");
// 获取子元素文本
// Element name = contact.element("name");
// System.out.println(name.getText());
System.out.println(contact.elementText("name"));
// 去掉前后空格
System.out.println(contact.elementTextTrim("name"));
// 获取当前元素下的子元素对象
Element email = contact.element("email");
System.out.println(email.getText());
// 去掉前后空格
System.out.println(email.getTextTrim());
// 根据元素获取属性值
Attribute idAttr = contact.attribute("id");
System.out.println(idAttr.getName() + "-->" + idAttr.getValue());
// 直接提取属性值
System.out.println(contact.attributeValue("id"));
System.out.println(contact.attributeValue("vip"));
}
}
输出结果
contactList
contact
contact
contact
user
潘金莲
潘金莲
[email protected]
[email protected]
id-->1
1
true
Dom4J的解析思想
得到文档对象Document,从中获取元素对象和内容。
代码演示
Contacts.xml
<contactList>
<contact id="1" vip="true">
<name> 潘金莲 name>
<gender>女gender>
<email>[email protected]email>
contact>
<contact id="2" vip="false">
<name>武松name>
<gender>男gender>
<email>[email protected]email>
contact>
<contact id="3" vip="false">
<name>武大狼name>
<gender>男gender>
<email>[email protected]email>
contact>
<user>
user>
contactList>
Contact 类
/**
潘金莲
女
[email protected]
*/
public class Contact {
private String name;
private int id;
private boolean vip;
private char gender;
private String email;
public Contact() {
}
public Contact(String name, int id, boolean vip, char gendar, String email) {
this.name = name;
this.id = id;
this.vip = vip;
this.gender = gendar;
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public boolean isVip() {
return vip;
}
public void setVip(boolean vip) {
this.vip = vip;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "Contact{" +
"name='" + name + '\'' +
", id=" + id +
", vip=" + vip +
", gendar=" + gender +
", email='" + email + '\'' +
'}';
}
}
测试代码
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class Dom4JTest2 {
@Test
public void parseToList() throws Exception {
// 需求:解析XML中的数据成为一个List集合对象。
// 1、导入框架(做过)
// 2、创建SaxReader对象
SAXReader saxReader = new SAXReader();
// 3、加载XML文件成为文档对象Document对象。
Document document = saxReader.read(Dom4JTest2.class.getResourceAsStream("/Contacts.xml"));
// 4、先拿根元素
Element root = document.getRootElement();
// 5、提取contact子元素
List<Element> contactEles = root.elements("contact");
// 6、准备一个ArrayList集合封装联系人信息
List<Contact> contacts = new ArrayList<>();
// 7、遍历Contact子元素
for (Element contactEle : contactEles) {
// 8、每个子元素都是一个联系人对象
Contact contact = new Contact();
contact.setId(Integer.valueOf(contactEle.attributeValue("id")));
contact.setVip(Boolean.valueOf(contactEle.attributeValue("vip")));
contact.setName(contactEle.elementTextTrim("name"));
contact.setGender(contactEle.elementTextTrim("gender").charAt(0));
contact.setEmail(contactEle.elementText("email"));
// 9、把联系人对象数据加入到List集合
contacts.add(contact);
}
// 10、遍历List集合
for (Contact contact : contacts) {
System.out.println(contact);
}
}
}
输出结果
Contact{name='潘金莲', id=1, vip=true, gendar=女, email='[email protected]'}
Contact{name='武松', id=2, vip=false, gendar=男, email='[email protected]'}
Contact{name='武大狼', id=3, vip=false, gendar=男, email='[email protected]'}
Contacts.xml
通常数据会封装成Java的对象,如单个对象,或者集合对象形式。
如果需要从XML文件中检索需要的某个信息(如name)怎么解决?
XPath介绍
使用Xpath检索出XML文件
需求:
分析:
方法名 | 说明 |
---|---|
Node selectSingleNode(“表达式”) | 获取符合表达式的唯一元素 |
List selectNodes(“表达式”) | 获取符合表达式的元素集合 |
Xpath的四大检索方案
Contacts2.xml
<contactList>
<contact id="1" vip="true">
<name> 潘金莲 name>
<gender>女gender>
<email>[email protected]email>
contact>
<contact id="2" vip="false">
<name>武松name>
<gender>男gender>
<email>[email protected]email>
contact>
<contact id="3" vip="false">
<name>武大狼name>
<gender>男gender>
<email>[email protected]email>
contact>
<user>
<contact>
<info>
<name id="888">我是西门庆name>
info>
contact>
user>
contactList>
XPath:绝对路径
采用绝对路径获取从根节点开始逐层的查找/contactList/contact/name节点列表并打印信息
方法名 | 说明 |
---|---|
/根元素/子元素/孙元素 | 从根元素开始,一级一级向下查找,不能跨级 |
代码演示
/**
1.绝对路径: /根元素/子元素/子元素。
*/
@Test
public void parse01() throws Exception {
// a、创建解析器对象
SAXReader saxReader = new SAXReader();
// b、把XML加载成Document文档对象
Document document =
saxReader.read(XPathDemo.class.getResourceAsStream("/Contacts2.xml"));
// c、检索全部的名称
List<Node> nameNodes = document.selectNodes("/contactList/contact/name");
for (Node nameNode : nameNodes) {
Element nameEle = (Element) nameNode;
System.out.println(nameEle.getTextTrim());
}
}
输出结果
潘金莲
武松
武大狼
XPath:相对路径
方法名 | 说明 |
---|---|
./子元素/孙元素 | 从当前元素开始,一级一级向下查找,不能跨级 |
代码演示
/**
2.相对路径: ./子元素/子元素。 (.代表了当前元素)
*/
@Test
public void parse02() throws Exception {
// a、创建解析器对象
SAXReader saxReader = new SAXReader();
// b、把XML加载成Document文档对象
Document document =
saxReader.read(XPathDemo.class.getResourceAsStream("/Contacts2.xml"));
Element root = document.getRootElement();
// c、检索全部的名称
List<Node> nameNodes = root.selectNodes("./contact/name");
for (Node nameNode : nameNodes) {
Element nameEle = (Element) nameNode;
System.out.println(nameEle.getTextTrim());
}
}
输出结果
潘金莲
武松
武大狼
XPath:全文搜索
直接全文搜索所有的name元素并打印
方法名 | 说明 |
---|---|
//contact | 找contact元素,无论元素在哪里 |
//contact/name | 找contact,无论在哪一级,但name一定是contact的子节点 |
//contact//name | contact无论在哪一种,name只要是contact的子孙元素都可以找到 |
代码演示
/**
3.全文搜索:
//元素 在全文找这个元素
//元素1/元素2 在全文找元素1下面的一级元素2
//元素1//元素2 在全文找元素1下面的全部元素2
*/
@Test
public void parse03() throws Exception {
// a、创建解析器对象
SAXReader saxReader = new SAXReader();
// b、把XML加载成Document文档对象
Document document =
saxReader.read(XPathDemo.class.getResourceAsStream("/Contacts2.xml"));
// c、检索数据
// List nameNodes = document.selectNodes("//name");
// List nameNodes = document.selectNodes("//contact/name");
List<Node> nameNodes = document.selectNodes("//contact//name");
for (Node nameNode : nameNodes) {
Element nameEle = (Element) nameNode;
System.out.println(nameEle.getTextTrim());
}
}
输出结果
List nameNodes = document.selectNodes("//name");
潘金莲
武松
武大狼
我是西门庆
List nameNodes = document.selectNodes("//contact/name");
潘金莲
武松
武大狼
List nameNodes = document.selectNodes("//contact//name");
潘金莲
武松
武大狼
我是西门庆
XPath:属性查找
在全文中搜索属性,或者带属性的元素
方法名 | 说明 |
---|---|
//@属性名 | 查找属性对象,无论是哪个元素,只要有这个属性即可。 |
//元素[@属性名] | 查找元素对象,全文搜索指定元素名和属性名。 |
//元素//[@属性名=‘值’] | 查找元素对象,全文搜索指定元素名和属性名,并且属性值相等。 |
代码演示
/**
4.属性查找。
//@属性名称 在全文检索属性对象。
//元素[@属性名称] 在全文检索包含该属性的元素对象。
//元素[@属性名称=值] 在全文检索包含该属性的元素且属性值为该值的元素对象。
*/
@Test
public void parse04() throws Exception {
// a、创建解析器对象
SAXReader saxReader = new SAXReader();
// b、把XML加载成Document文档对象
Document document =
saxReader.read(XPathDemo.class.getResourceAsStream("/Contacts2.xml"));
// c、检索数据
List<Node> nodes = document.selectNodes("//@id");
for (Node node : nodes) {
Attribute attr = (Attribute) node;
System.out.println(attr.getName() + "===>" + attr.getValue());
}
// 查询name元素(包含id属性的)
// Node node = document.selectSingleNode("//name[@id]");
Node node = document.selectSingleNode("//name[@id=888]");
Element ele = (Element) node;
System.out.println(ele.getTextTrim());
}
输出结果
id===>1
id===>2
id===>3
id===>888
我是西门庆
工厂模式的作用
代码演示
Computer 类
public abstract class Computer {
private String name;
private double price;
public abstract void start();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
Mac 类
public class Mac extends Computer{
@Override
public void start() {
System.out.println(getName() + "以非常优雅的方法启动了,展示了一个苹果logo");
}
}
Huawei 类
public class Huawei extends Computer{
@Override
public void start() {
System.out.println(getName() + "开机了,展示了华为的菊花图标~~~~");
}
}
FactoryPattern 类
public class FactoryPattern {
/**
定义一个方法,创建对象返回
*/
public static Computer createComputer(String info){
switch (info){
case "huawei":
Computer c = new Huawei();
c.setName("huawei pro 16");
c.setPrice(5999);
return c;
case "mac":
Computer c2 = new Mac();
c2.setName("MacBook pro");
c2.setPrice(11999);
return c2;
default:
return null;
}
}
}
测试类
public class FactoryDemo {
public static void main(String[] args) {
Computer c1 = FactoryPattern.createComputer("huawei");
c1.start();
Computer c2 = FactoryPattern.createComputer("mac");
c2.start();
}
}
输出结果
huawei pro 16开机了,展示了华为的菊花图标~~~~
MacBook pro以非常优雅的方法启动了,展示了一个苹果logo
创建一个新类,包装原始类,从而在新类中提升原来类的功能。
装饰模式的作用
装饰模式指的是在不改变原类的基础上, 动态地扩展一个类的功能。
装饰模式的实现方式
InputStream(抽象父类)
FileInputStream(实现子类,读写性能较差)
BufferedInputStream(实现子类,装饰类,读写性能高)
代码演示
InputStream 类
/**
共同父类
*/
public abstract class InputStream {
public abstract int read();
public abstract int read(byte[] buffer);
}
FileInputStream 类(原始类)
import java.util.Arrays;
/**
原始类
*/
public class FileInputStream extends InputStream{
@Override
public int read() {
System.out.println("低性能的方式读取了一个字节a");
return 97;
}
@Override
public int read(byte[] buffer) {
buffer[0] = 97;
buffer[1] = 98;
buffer[2] = 99;
System.out.println("低性能的方式读取了一个字节数组:" + Arrays.toString(buffer));
return 3;
}
}
BufferedInputStream 类(装饰类)
/**
装饰类:继承InputStream 拓展原始类的功能
*/
public class BufferedInputStream extends InputStream{
private InputStream is;
public BufferedInputStream(InputStream is){
this.is = is;
}
@Override
public int read() {
System.out.println("提供8KB的缓冲区,提高读数据性能~~~~");
return is.read();
}
@Override
public int read(byte[] buffer) {
System.out.println("提供8KB的缓冲区,提高读数据性能~~~~");
return is.read(buffer);
}
}
测试类
/**
装饰模式
定义父类:InputStream
定义实现类:FileInputStream 继续父类 定义功能
定义装饰实现类:BufferedInputStream 继承父类 定义功能 包装原始类,增强功能。
*/
public class DecoratorPattern {
public static void main(String[] args) {
InputStream is = new BufferedInputStream(new FileInputStream());
System.out.println(is.read());
System.out.println(is.read(new byte[3]));
}
}
输出结果
提供8KB的缓冲区,提高读数据性能~~~~
低性能的方式读取了一个字节a
97
提供8KB的缓冲区,提高读数据性能~~~~
低性能的方式读取了一个字节数组:[97, 98, 99]
3
本文章参考B站 Java入门基础视频教程,java零基础自学首选黑马程序员Java入门教程(含Java项目和Java真题),仅供个人学习使用,部分内容为本人自己见解,与黑马程序员无关。