一、多线程
1.1 多线程概述
进程:进程是程序的基本执行实体
线程:线程是操作系统能够进行运算调度的最小单位,它被包含在进程中,是进程中的实际运作单元
可以简单理解为应用软件中相互独立,可以同时运行的功能,提高了程序的运行效率
只要想让多个事情同时运行就需要用到多线程,比如:
-
软件中的耗时操作
-
所有的聊天软件
-
所有的服务器
并发:在同一时刻,有多个指令在单个CPU上交替执行
并行:在同一时刻,有多个指令在多个CPU上同时执行
1.2 多线程的实现方式
1.2.1 继承Thread类的方式实现
public class MyThread1 extends Thread {
@Override
public void run() {
//书写程序要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"Hello World");
}
}
}
public class demo1 {
public static void main(String[] args) {
//多线程的第一种启动方式:
//1.自己定义一个类继承Thread
//2.重写方法
//3.创建子类的对象,并启动程序
MyThread1 t1 = new MyThread1();
MyThread1 t2 = new MyThread1();
t1.setName("线程1:");
t2.setName("线程2:");
//start表示开启线程
t1.start();
t2.start();
}
}
1.2.2 实现Runnable接口的方式进行实现
public class MyRun implements Runnable {
@Override
public void run() {
//书写线程执行的代码
for (int i = 0; i < 100; i++) {
//先获取当前线程的对象
Thread t = Thread.currentThread();
System.out.println(t.getName()+"Hello World");
}
}
}
public class demo2 {
public static void main(String[] args) {
//多线程的第二种启动方式:
//1.自己定义一个类实现Runnable接口
//2.重写里面的run方法
//3.创建自己类的对象,
//4.创建一个Thread类的对象,并启动线程
//创建MyRun对象
//表示多线程要执行的任务
MyRun mr=new MyRun();
//创建线程对象
Thread t1=new Thread(mr);
Thread t2=new Thread(mr);
//线程设置名字
t1.setName("线程1");
t2.setName("线程2");
//开启线程
t1.start();
t2.start();
}
}
1.2.3 利用Callable接口和Future接口方式实现
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
//求1-100之间的和
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class demo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//多线程的第三种启动方式:
//特点:可以获取到多线程执行的结果
// 1.创建一个类MyCallable实现Callable接口
// 2.重写call(有返回值,表示多线程运行的结果)
//
// 3.创建MyCallable的对象(表示多线程要执行的任务)
// 4.创建FutureTask的对象(作用管理多线程运行的结果)
// 5.创建Thread类的对象,并启动表示线程
// /
// 创建MyCallable的对象(表示多线程要执行的任务)
MyCallable mc = new MyCallable();
// 创建FutureTask的对象(作用管理多线程运行的结果)
FutureTask<Integer> ft = new FutureTask<>(mc);
// 创建Thread类的对象,并启动表示线程
Thread thread = new Thread(ft);
//启动线程
thread.start();
//获取多线程运行的结果
Integer result = ft.get();
System.out.println(result);
}
}
1.2.4 三种实现方法对比
优点 | 缺点 | |
---|---|---|
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 可扩展性较差,不能在继承其他的类 |
实现Runnable接口<br>实现Callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类 | 编程相对复杂,不能直接使用Thread类中的方法 |
1.3 常见的成员方法
方法名称 | 说明 |
---|---|
String getName() | 返回此线程的名称<br>1. 如果没有给线程设置名字,线程也是有默认名字的<br> 格式:Thread-x(x序号,从0开始的) |
void setName(String name) | 设置姓名的名字(构造方法页可以设置名字)<br>2. 如果我们给线程设置名字,既可以用set方法进行设置,也可以通过构造方法设置 |
static Thread currentThread() | 获取当前线程的对象<br>3. 当JVM虚拟机启动后,会自动启动多条线程,其中有一条线程就是main线程,它的作用就是调用main方法,并执行里面的代码,以前我们写的所有代码,其实就是运行在main线程当中 |
static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒<br>4. 哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间 |
setPriority(int newPriority) | 设置线程的优先级<br>5. 线程优先级默认为5,最高为10,最低为1 |
final int getPriority() | 获取线程的优先级 |
final void setDaemon(boolean on) | 设置为守护线程<br>6. 当其他非守护线程执行完毕后,守护线程会陆续结束(不会立马结束) |
public static void yield() | 出让线程/礼让线程 |
public final void join() | 插入线程/插队线程 |
1.4 线程的安全问题解决办法
1.4.1 同步代码块
把操作共享数据的代码锁起来
synchronized(锁对象){ //锁对象一定要是唯一的
操作共享数据的代码
}
特点:
-
锁默认打开,有一个线程进去,锁自动关闭
-
里面的代码全部执行完毕,线程出来,锁自动打开
1.4.2 同步方法
把synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(方法参数){
方法体
}
特点:
-
同步方法是锁住方法里面的所有代码
-
锁对象不能自己指定
非静态方法:this
静态方法:当前类的字节码文件对象
1.4.3 Lock锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,所以java提供了新的锁对象Lock
Lock实现提供比使用synchronized方法和语句可以获得更广泛的碎丁操作,Lock中提供了获得锁和释放锁的方法:
void lock();//获得锁
void unlock();//释放锁
Lock是接口不能实例化,一般采用它的实现类ReentrantLock来实例化
ReentrantLock的构造方法:
ReentrantLock();//创建一个ReentrantLock的实例
1.5 生产者和消费者(等待唤醒机制)
生产者消费者是一个十分经典的多线程协作的模式
常见方法:
方法名称 | 说明 |
---|---|
void wait() | 当前线程等待,直到被其他线程唤醒 |
void notify() | 随机唤醒单个线程 |
void notifyAll() | 唤醒所有线程 |
1.5.1 代码示例
Cook类:
package thread;
public class Cook extends Thread {
@Override
public void run() {
/*
* 1.循环
* 2.同步代码块
* 3.判断共享数据是否到末尾(到了末尾)
* 4.判断共享数据是否到末尾(没到末尾,执行题目的核心逻辑)
*
*/
while (true) {
synchronized (Desk.lock) {
if (Desk.count==0){
break;
}else{
//判断桌子上是否有食物
if(Desk.foodFlag==1) {
//如果有就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
//如果没有就制作食物
System.out.println("厨师做了一碗面条");
//修改桌子上的食物的状态
Desk.foodFlag=1;
//唤醒等待的消费者开吃
Desk.lock.notifyAll();
}
}
}
}
}
}
Desk类:
package thread;
public class Desk {
/*
* 作用:控制生产者和消费者的执行
*/
//表示桌子上是否有面条,0:表述没有面条,1:表示没有面条
public static int foodFlag = 0;
//总个数
public static int count = 10;
//锁对象
public static final Object lock = new Object();
}
Foodie类:
package thread;
public class Foodie extends Thread {
@Override
public void run() {
/*
* 1.循环
* 2.同步代码块
* 3.判断共享数据是否到末尾(到了末尾)
* 4.判断共享数据是否到末尾(没到末尾,执行题目的核心逻辑)
*
*/
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
} else {
//先去判断桌子上是否有面条
if (Desk.foodFlag == 0) {
//没有,等待
try {
Desk.lock.wait();//让当前线程跟锁绑定
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//把吃的总数-1
Desk.count--;
//有,开吃
System.out.println("吃货在吃面条,还能再吃" + Desk.count + "碗!!!");
//吃完之后,唤醒厨师继续做
Desk.lock.notifyAll();//唤醒这把锁绑定的所有线程
//修改桌子的状态
Desk.foodFlag = 0;
}
}
}
}
}
}
测试类:
package thread;
public class demo {
public static void main(String[] args) {
/*
* 需求:完成生产者和消费者(等待唤醒机制)的代码
* 实现线程轮流交替执行的效果
*/
//创建线程对象
Cook c=new Cook();
Foodie f=new Foodie();
//给线程设置名字
c.setName("厨师");
c.setName("吃货");
//开启线程
c.start();
f.start();
}
}
1.5.2 等待唤醒机制(阻塞队列实现)
demoFoodie类:
package thread;
import java.util.concurrent.ArrayBlockingQueue;
public class demoFoodie extends Thread {
ArrayBlockingQueue<String> queue;
public demoFoodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while(true){
//不断地从阻塞队伍当中获取面条
try {
String food = queue.take();
System.out.println(food);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
demoCook类:
package thread;
import java.lang.reflect.Array;
import java.util.concurrent.ArrayBlockingQueue;
public class demoCook extends Thread{
ArrayBlockingQueue<String> queue;
public demoCook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while(true){
//不断地把面条放到阻塞队伍当中
try {
queue.put("面条");
System.out.println("厨师放了一碗面条");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
测试类:
package thread;
import java.util.concurrent.ArrayBlockingQueue;
public class demo11 {
public static void main(String[] args) {
/*
* 需求:利用阻塞队伍完成生产者和消费者(等待唤醒机制)的代码
* 细节:
* 生产者和消费者必须使用同一个阻塞队伍
*/
//1.创建阻塞队列的对象
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(10);
//2.创建线程的对象,并把阻塞队列传递过去
demoCook c=new demoCook(queue);
demoFoodie f=new demoFoodie(queue);
//3.开启线程
c.start();
f.start();
}
}
1.6 线程的六种状态
-
NEW
新建至今尚未启动的线程处于这种状态。 -
RUNNABLE
就绪正在 Java 虚拟机中执行的线程处于这种状态。 -
BLOCKED
阻塞受阻塞并等待某个监视器锁的线程处于这种状态。 -
WAITING
等待无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。 -
TIMED_WAITING
计时等待等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。 -
TERMINATED
死亡已退出的线程处于这种状态。
1.7 线程池
1.7.1 基本概念
线程池主要核心原理:
①创建一个池子,池子中是空的
②提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还池子下回提交任务时,不需要创建新的线程,直接复用已有的线程即可
③但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队
线程代码实现:
-
创建线程池
-
提交任务
-
所有的任务全部执行完毕,关闭线程池
工具类Executors:线程池的工具类通过调用方法返回不同类型的线程池对象
方法名称 | 说明 |
---|---|
public static ExecutorService newCachedThreadPool() | 创建一个没有上限的线程池 |
public static ExecutorService newCachedThreadPool(int nThreads) | 创建有上限的线程池 |
1.7.2 自定义线程池
ThreadPoolExecutor pool=new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,事件单位,任务队列,创建线程工厂,任务的拒绝策略);
代码实例:
ThreadPoolExecutor pool=new ThreadPoolExecutor(3,//核心线程数量
6,//最大线程数量
60,//空闲线程最大存活时间
TimeUnit.SECONDS,//事件单位
new LinkedBlockingDeque<>(3),//任务队列
Executors.defaultThreadFactory(),//创建线程工厂
new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
);
七个重要参数:
-
核心线程数量(不能小于0)
-
线程池中的最大线程数量(最大数量>=核心线程数量)
-
空闲时间(值)(不能小于0)
-
空闲时间(单位)(用TimeUnit指定)
-
阻塞队伍(不能为null)
-
创建线程的方式(不能为null)
-
要执行的任务过多时的解决方案(不能为null)
不断地提交任务,会有三个临界点:
-
当核心线程满时,在提交任务就会排队
-
当核心线程满,队伍满时,会创建临时线程
-
当核心线程满时,队伍满,临时线程满时,会触发任务拒绝策略
任务拒绝策略:
任务拒绝策略 | 说明 |
---|---|
ThreadPoolExecuor.AbortPolicy | 默认策略:丢弃任务并抛出RejectedExecutionException异常 |
ThreadPoolExecuor.DiscardPolicy | 丢弃任务,但是不抛出异常,这是不推荐的做法 |
ThreadPoolExecuor.DiscardOldestPolicy | 抛弃队列中等待最久的任务,然后把当前任务加入队列中 |
ThreadPoolExecuor.CallerRunPolicy | 调用任务的run()方法绕过线程池直接执行 |
1.7.3 线程池多大合适
最大并行数:操作系统能支持地最大线程数
获取CPU的最大线程数:
public class demo {
public static void main(String[] args) {
int count=Runtime.getRuntime().availableProcessors();
System.out.println(count);
}
}
CPU密集型运算:最大并行数+1
I/O密集型计算:最大并行数 * 期望CPU利用率 * (CPU计算时间+等待时间) / CPU计算时间
二、网络编程
2.1 初始网络编程
在网络通信协议上,不同计算机上运行的程序,进行的数据传输
java中可以使用java.net包下的技术轻松开发出常见的网络应用程序
常见的软件架构:
C/S:Client/Server(客户端/服务端)
在用户本地需要下载并安装用户端程序,在远程有一个服务器端程序
B/S:Browser/Server(浏览器/服务端)
只需要一个浏览器,用户通过不同的地址。客户访问不同的服务器
2.2 网络编程三要素
2.2.1 IP
设备在网络中的地址,是唯一的标识
全称:Internet Protocol,是互联网协议地址,也称IP地址,是分配给上网设备的数字标签
分类:
-
IPv4:互联网通信协议第四版
采用32位地址长度,分成4组
IPv4的地址分类形式:
-
公网地址(万维网使用)和私有地址(局域网使用)
-
192.168.开头的就是私有地址,范围即位192.168.0.0-192.168.255.255,专门为组织机构内部使用,以此节省IP
特殊IP地址:127.0.0.1,也可以是localhost:是回送地址也称本地回环地址,也称本地IP,永远只会寻找当前所在主机
-
-
IPv6:互联网通信协议第六版
采用128为地址长度,分成8组
InetAddress类的使用
常用方法 | 描述 |
---|---|
static InetAddress getByName(String host) | 确定本地名称的IP地址,主机名称可以是机器名称,也可以是IP地址 |
String getHostName() | 获取此IP地址的主机名 |
String getHostAddress | 返回文本显示中的IP地址字符串 |
2.2.2 端口号
应用程序在设备中唯一的标识
端口号:由两个字节表示的整数,取值名称:0-65535,其中0-1023之间的端口号用于一些知名的网络服务或者应用
一个端口号只能被一个应用程序使用
2.2.3 协议
数据在网络中传输的规则,常见的协议有UDP、TCP、http、https、ftp
计算机网络中,连接和通信的规则被称为网络通信协议
-
OSI参考模型:世界互联协议标准,全球通信规范,单模型过于理想化,未能在因特网上进行广泛推广
-
TCP/IP参考模型:事实上的国际标准
UDP协议:
-
用户数据报协议(User Datagram Protocol)
-
UDP是面向无连接通信协议
速度快,有大小限制一次最多发送64K,数据不安全,易丢失数据
TCP协议:
-
传输控制协议(Transmission Control Protocol)
-
UDP是面向连接通信协议
速度慢,没有大小限制,数据安全
2.3 UDP协议
2.3.1 发送数据
-
创建发送端的DatagramSocket对象
-
数据打包(DatagramPacket)
-
发送数据
-
释放数据
import java.io.IOException;
import java.net.*;
public class demo {
public static void main(String[] args) throws IOException {
//发送数据:
//1. 创建发送端的DatagramSocket对象
//细节:
//绑定端口:以后我们就是通过这个端口往外发送
//空参:所有可用的端口中随机一个进行使用
//有参:指定端口进行绑定
DatagramSocket ds=new DatagramSocket();
//2. 数据打包(DatagramPacket)
String str="Hello World";
byte[] bytes = str.getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
int port=10086;
DatagramPacket dp=new DatagramPacket(bytes,bytes.length,address,port);
//3. 发送数据
ds.send(dp);
//4. 释放数据
ds.close();
}
}
2.3.2 接收数据
-
创建接收端的DatagramSocket对象
-
接收打包好的数据(DatagramPacket)
-
解析数据包
-
释放数据
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class demo3 {
public static void main(String[] args) throws IOException {
// 接收数据
// 1. 创建接收端的DatagramSocket对象
//细节:
// 在接受数据的时候,一定要绑定端口
//接收绑定的端口,一定要跟发送的端口保持一致
DatagramSocket ds = new DatagramSocket(10086);
// 2. 接收打包好的数据(DatagramPacket)
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
//该方法是阻塞的,
//程序执行到这一步的时候,会在这里死等
//等发送端发送消息
ds.receive(dp);
// 3. 解析数据包
byte[] data = dp.getData();
int length = dp.getLength();
InetAddress address = dp.getAddress();
int port = dp.getPort();
System.out.println("接收到的数据:" + new String(data, 0, length));
System.out.println("该数据是从:" + address + "这台电脑中的" + port + "这个端口发出的");
// 4. 释放数据
ds.close();
}
}
接收到的数据:Hello World
该数据是从:/127.0.0.1这台电脑中的56794这个端口发出的
2.3.3 UDP练习(聊天室)
要求:
UDP发送数据:数据来自于键盘录入,直到输入的数据为886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
package Net;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class SendMessage {
public static void main(String[] args) throws IOException {
//发送端
DatagramSocket ds = new DatagramSocket();
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入您要说的话:");
String str = sc.nextLine();
if("886".equals(str)) {
break;
}
byte[] bytes = str.getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 10086;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
ds.send(dp);
}
ds.close();
}
}
package Net;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class ReceiveMessage {
public static void main(String[] args) throws IOException {
//接收端
DatagramSocket ds = new DatagramSocket(10086);
DatagramPacket dp = new DatagramPacket(new byte[1024], 1024);
while (true) {
ds.receive(dp);
//解析数据包
byte[] data = dp.getData();
int len = dp.getLength();
String ip = dp.getAddress().getHostAddress();
String name = dp.getAddress().getHostName();
int port = dp.getPort();
System.out.println("ip为:" + ip + ",主机名为:" + name + ",端口名为:" + port + "的人" +
",发送了的数据:" + new String(data, 0, len));
}
}
}
2.3.4 UDP的三种通信方式
-
单播:以前的代码就是单播
-
组播:组播地址:224.0.0.0-239.255.255.255
其中224.0.0.0-224.0.0.255为预留的组播地址
-
广播:广播地址:255.255.255.255
2.4 TCP通信协议
TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象
通信之前要保证连接已建立
通过Socket产生IO流来进行网络通信
2.4.1 客户端发送数据
-
创建客户端的Socket对象(Socket)与指定服务端连接
Socket(String host,int port)
-
获取输出流,写数据
OutputStream getOutputStream()
-
释放资源
void close()
package Net;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
//在创建对象的同时会连接服务器,如果连接不上,代码会报错
Socket socket = new Socket("127.0.0.1",10086);
OutputStream os = socket.getOutputStream();
os.write("高鑫".getBytes());
os.close();
socket.close();
}
}
2.4.2 服务器接收数据
-
创建服务器端的Socket对象(ServerSocket)
ServerSocket(int port)
-
监听客户端连接,返回一个Socket对象
Socket accept()
-
获取输入流,读数据,并把数据显示在控制台上
InputStream getInputStream()
-
释放资源
void close()
package Net;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10086);
Socket socket = ss.accept();
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
int b;
while ((b = br.read()) != -1) {
System.out.print((char) b);
}
socket.close();
ss.close();
}
}
2.4.3 三次握手与四次挥手
三次握手:确保连接建立
四次握手:确保连接断开,且数据处理完毕
三、反射
反射允许对成员变量,成员方法和构造方法的信息进行编程访问
3.1 获取class对象的三种方式
-
Class.forName("全类名");
-
类名.class
-
对象.getClass();
package reflect;
public class demo1 {
public static void main(String[] args) throws ClassNotFoundException {
// 1.第一种方式---最为常用的
// 要写全类名:包名+类名
Class<?> clazz1 = Class.forName("reflect.Student");
// 打印
System.out.println(clazz1);
//结果:class reflect.Student
// 2.第二种方式---一般更多的是当作参数进行传递
Class<Student> clazz2 = Student.class;
System.out.println(clazz1 == clazz2);
//结果:true
// 3.第三种方式---当我们已经有了这个类的对象时,才可以使用
Student s=new Student();
Class<? extends Student> clazz3 = s.getClass();
System.out.println(clazz1 == clazz3);
//结果:true
}
}
3.2 反射获取构造方法
Class类中用于获取构造方法的方法:
方法 | 描述 |
---|---|
Constuctor<?>[] getConstructors() | 返回所有公共构造方法对象的数组 |
Constuctor<?>[] getDeclaredConstructors() | 返回所有构造方法对象的数组 |
Constuctor<?>[] getConstructors(Class<?>...parameterTypes) | 返回单个公共构造方法对象 |
Constuctor<?>[] getDeclaredConstructor(Class<?>...parameterTypes) | 返回单个构造方法对象 |
Constructor类中用于创建对象的方法:
方法 | 描述 |
---|---|
T newInstance(Object...initargs) | 根据指定的构造方法创建对象 |
setAccessible(boolean flag) | 暴力反射,flag=true时,临时取消权限检验 |
3.3 反射获取构造成员变量
Class类中用于获取成员变量的方法:
方法 | 描述 |
---|---|
Field[] getFields() | 返回所有公共成员变量对象的数组 |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组 |
Field[] getField(String name) | 返回单个公共成员变量对象 |
Field[] getDeclaredField(String name) | 返回单个成员变量对象 |
Field类中用于创建对象的方法:
方法 | 描述 |
---|---|
void set(Object obj,Object value) | 赋值 |
Object get(Object obj) | 获取值 |
3.4 反射获取成员方法
Class类中用于获取成员方法的方法:
方法 | 描述 |
---|---|
Method[] getMethod() | 返回所有的公共成员方法对象的数组,包含继承的 |
Method[] getDeclaredMethod() | 返回所有的成员方法对象的数组,不包含继承的 |
Method[] getMethod(String name,Class<?>... parameterTypes) | 返回单个公共成员方法对象 |
Method[] getMethod(String name,Class<?>... parameterTypes) | 返回单个成员方法对象 |
Method类中用于创建对象的方法:
Object invoke(Object obj,Object...args)
:运行方法
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)
3.5 反射的作用
-
获取一个类里面的所有信息,获取到了之后,在执行其他业务逻辑
-
结合配置文件,动态的创建对象并调用方法
常见单词
单词 | 意思 | 单词 | 意思 |
---|---|---|---|
get | 获取 | set | 设置 |
Constructor | 构造方法 | Parameter | 参数 |
Field | 成员变量 | Modifiers | 修饰符 |
Method | 方法 | Declared | 私有的 |
3.5.1 综合练习(保存任意对象数据)
对于任意一个对象,都可以把对象所有的字段名和值,保存到文件中
package reflect;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
public class demo3 {
public static void main(String[] args) throws IllegalAccessException, IOException {
Student s = new Student("小A", 23, '女', 167.5, "睡觉");
Teacher t = new Teacher("波妞", 10000);
saveObject(s);
}
// 对于任意一个对象,都可以把对象所有的字段名和值,保存到文件中
public static void saveObject(Object obj) throws IllegalAccessException, IOException {
//1.获取字节码文件的对象
Class<?> clazz = obj.getClass();
//创建IO流
BufferedWriter bw=new BufferedWriter(new FileWriter("a.txt"));
//2.获取所有的成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
f.setAccessible(true);
//获取成员变量的名字
String name = f.getName();
//获取成员变量的值
Object value = f.get(obj);
bw.write(name+"="+value);
bw.newLine();
}
bw.close();
}
}
a.txt中如下:
name=小A
age=23
gender=女
height=167.5
hobby=睡觉
3.5.2 综合练习(跟配置文件结合动态创建)
反射可以跟配置文件结合的方式,动态的创建对象,并调用方法
classname=reflect.Student1
method=study
Student1对象:
package reflect;
public class Student1 {
private String name;
private int age;
public Student1(){}
public Student1(String name, int age) {
this.name = name;
this.age = age;
}
public void study(){
System.out.println("学生在学习!");
}
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 "Student1{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
package reflect;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
public class demo4 {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//1.读取配置文件中的信息
Properties prop = new Properties();
FileInputStream fis = new FileInputStream("src\\prop.properties");
prop.load(fis);
System.out.println(prop);
//结果:{classname=reflect.Student1, method=study}
//2.获取全类名和方法名
String className = (String) prop.get("classname");
String methodName = (String) prop.get("method");
System.out.println(className);
System.out.println(methodName);
//结果:reflect.Student1
//结果:study
//3.利用反射创建对象并运行方法
Class<?> clazz = Class.forName(className);
//获取构造方法
Constructor<?> con = clazz.getDeclaredConstructor();
Object o = con.newInstance();
System.out.println(o);
//结果:Student1{name='null', age=0}
//获取成员方法并运行
Method method=clazz.getDeclaredMethod(methodName);
method.setAccessible(true);
method.invoke(o);
//结果:学生在学习!
}
}
四、动态代理
4.1 动态代理的思想分析
特点:无侵入式的给代码增加额外的功能
程序需要代理的原因:对象如果身上的事太多,可以通过代理来转移部分职责
代理的样子:对象有什么方法想被代理,代理就一定要有对应的方法
Java通过接口保证代理的样子,后面的对象和代理需要实现同一个接口,接口中就是被代理的所有方法
4.2 动态代理的代码实现
java.lang.reflect.Proxy类:提供了为对象产生代理的方法:
public static Object newProxyInstance(classLoader loader,Class<?>[] interfaces,InvocationHandler h)
//参数一:用于指定用于哪个类加载器,去加载生成的代理类
//参数二:指定接口,这些接口用于指定生成的代理长什么样,也就是有哪些方法
//参数三:用来指定生成的代理对象要干什么事情
明星经纪人案例实现:
Star接口:
package dynamicproxy;
public interface Star {
//唱歌
public abstract String sing(String name);
//跳舞
public abstract void dance();
}
BigStar:
package dynamicproxy;
public class BigStar implements Star{
private String name;
public BigStar(){
}
public BigStar(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "BigStar{" +
"name='" + name + '\'' +
'}';
}
@Override
public String sing(String name) {
System.out.println(this.name+"正在唱"+name);
return "谢谢";
}
@Override
public void dance() {
System.out.println(this.name+"正在跳舞");
}
}
代理人对象:
package dynamicproxy;
/*
* 类的作用:
* 就是创建一个代理
*/
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyUtil {
/*
* 方法的作用:
* 就是给明星对象,创建一个代理
* 形参:
* 就是被代理的明星对象
* 返回值:
* 给明星创建的代理
* 需求:
* 外面的人想要大明星唱歌
* 1.获取代理的对象
* 代理对象=ProxyUtil.createProxy(大明星的对象)
* 2.在调用代理的唱歌方法
* 代理对象.唱歌方法()
*/
public static Star createProxy(BigStar bigStar) {
//public static Object newProxyInstance(classLoader loader,Class<?>[] interfaces,InvocationHandler h)
Star star = (Star) Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(),//参数一:用于指定用于哪个类加载器,去加载生成的代理类
new Class[]{Star.class},//参数二:指定接口,这些接口用于指定生成的代理长什么样,也就是有哪些方法
new InvocationHandler() {//参数三:用来指定生成的代理对象要干什么事情
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*
* 参数一:代理的对象
* 参数二:要运行的方法 sing
* 参数三:调用sing方法时,传递的实参
*/
if("sing".equals(method.getName())){
System.out.println("准备话筒,收钱");
}else if("dance".equals(method.getName())){
System.out.println("准备场地,收钱");
}
//代码的表现形式:调用大明星里面的唱歌或者跳舞的方法
return method.invoke(bigStar, args);
}
});
return star;
}
}
测试类: