day25
一、网络编程
-
单播:服务器对各个客户端的不同请求作出不同的响应。网络节点之间的通信就好像是人们之间的对话一样。如果一个人对另外一个人说话,那么用网络技术的术语来描述就是“单播”,此时信息的接收和传递只在两个节点之间进行。单播在网络中得到了广泛的应用,网络上绝大部分的数据都是以单播的形式传输的,只是一般网络用户不知道而已。例如,你在收发电子邮件、浏览网页时,必须与邮件服务器、Web服务器建立连接,此时使用的就是单播数据传输方式。
-
多播:一个多播地址需要的客户端的加入,“多播”也可以称为“组播”,在网络技术的应用并不是很多,网上视频会议、网上视频点播特别适合采用多播方式。因为如果采用单播方式,逐个节点传输,有多少个目标节点,就会有多少次传送过程,这种方式显然效率极低,是不可取的;如果采用不区分目标、全部发送的广播方式,虽然一次可以传送完数据,但是显然达不到区分特定数据接收对象的目的。采用多播方式,既可以实现一次传送所有目标节点的数据,也可以达到只对特定对象传送数据的目的。IP网络的多播一般通过多播IP地址来实现。多播IP地址就是D类IP地址,即224.0.0.0至239.255.255.255之间的IP地址。Windows 2000中的DHCP管理器支持多播IP地址的自动分配。
-
广播:一个广播地址只要加入就可以收到,不管需不需要,“广播”在网络中的应用较多,如客户机通过DHCP自动获得IP地址的过程就是通过广播来实现的。但是同单播和多播相比,广播几乎占用了子网内网络的所有带宽。拿开会打一个比方吧,在会场上只能有一个人发言,想象一下如果所有的人同时都用麦克风发言,那会场上就会乱成一锅粥。在IP网络中,广播地址用IP地址“255.255.255.255”来表示,这个IP地址代表同一子网内所有的IP地址。
1.1 UDP协议中的广播与组播
1.1.1 广播
当前主机与网络中的所有主机通信
注意:广播地址必须为255.255.255.255
package com.ujiuye.demo01_广播与组播.广播;
import java.io.IOException;
import java.net.*;
public class Send {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket();
byte[] data = "开始广播了...".getBytes();
InetAddress ip = InetAddress.getByName("255.255.255.255");
DatagramPacket dp = new DatagramPacket(data, data.length, ip, 9999);
socket.send(dp);
socket.close();
}
}
package com.ujiuye.demo01_广播与组播.广播;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class Receive {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(9999);
byte[] b = new byte[1024];
DatagramPacket dp = new DatagramPacket(b, b.length);
socket.receive(dp);
byte[] data = dp.getData();
int length = dp.getLength();
System.out.println(new String(data, 0, length));
socket.close();
}
}
1.1.2 组播
组播是指把信息同时传递给一组目的地址,组播地址的范围为224.0.0.0 ~ 239.255.255.255。如果超出这个范围则会抛出异常
注意:参与组播的所有主机的socket必须是MulticastSocket类的对象,它是DatagramSocket的子类,因此组播socket也是UDP协议的一种。
MulticastSocket类
构造方法:
MulticastSocket() 创建多播套接字
MulticastSocket(int port) 创建多播套接字并将其绑定到特定端口
加入组播的方法:
void joinGroup(InetAddress mcastaddr) 加入多播组
package com.ujiuye.demo01_广播与组播.组播;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class Send {
public static void main(String[] args) throws IOException {
MulticastSocket socket = new MulticastSocket();
byte[] data = "开始多播了...".getBytes();
InetAddress ip = InetAddress.getByName("239.0.0.66");
int port = 10086;
DatagramPacket dp = new DatagramPacket(data, data.length, ip, port);
socket.send(dp);
socket.close();
}
}
package com.ujiuye.demo01_广播与组播.组播;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
public class Receive {
public static void main(String[] args) throws IOException {
MulticastSocket socket = new MulticastSocket(10086);
byte[] b = new byte[1024];
DatagramPacket dp = new DatagramPacket(b, b.length);
InetAddress ip = InetAddress.getByName("239.0.0.66");
socket.joinGroup(ip);
socket.receive(dp);
byte[] data = dp.getData();
int length = dp.getLength();
System.out.println(new String(data, 0, length));
socket.close();
}
}
1.2 TCP协议文件上传
1.2.1 将客户端的图片上传到服务器
package com.ujiuye.demo02_文件上传.单个用户;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
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", 6666);
//获取字节输出流,用来向服务端发送数据
OutputStream out = socket.getOutputStream();
//用文件字节输入流先从计算机上获取要发送的图片
FileInputStream fis = new FileInputStream("D:\\abc.jpeg");
byte[] b = new byte[1024];
int len = 0;
while((len = fis.read(b)) != -1){
//从计算机读取到图片字节数据后,直接通过socket获取的输出流发送到服务器
out.write(b, 0, len);
}
fis.close();
//public void shutdownOutput() 写入结束的标记。禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。
socket.shutdownOutput();
//接收是否上传成功的信息
InputStream in = socket.getInputStream();
byte[] bb = new byte[1024];
int length = in.read(bb);
System.out.println(new String(bb, 0, length));
socket.close();
}
}
package com.ujiuye.demo02_文件上传.单个用户;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;
/*
服务端:用来接收数据
*/
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(6666);
//获取Socket对象
Socket socket = ss.accept();
//随机生成一个文件名
Random r = new Random();
int number = r.nextInt(Integer.MAX_VALUE);
//创建一个文件字节输出流对象,用来将读取到的图片字节数据写入到计算机中
FileOutputStream fos = new FileOutputStream("E:\\" + number + ".jpeg");
InputStream in = socket.getInputStream();
byte[] b = new byte[1024];
int len = 0;
while((len = in.read(b)) != -1){
//用文件字节输出流对象,将读取到的图片字节数据写入到计算机中
fos.write(b, 0, len);
}
fos.close();
//接收数据成功之后,返回给客户端"上传成功"
OutputStream out = socket.getOutputStream();
out.write("上传成功".getBytes());
ss.close();
}
}
1.2.2 使用多个客户端同时将文件上传到服务器(多线程)
使用三个线程同时将这个资源发送到服务器
package com.ujiuye.demo02_文件上传.多个用户;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/*
客户端:也就是上传者
*/
public class Client implements Runnable {
@Override
public void run() {
//获取字节输出流,用来向服务端发送数据
OutputStream out = null;
try {
Socket socket = new Socket("127.0.0.1", 6666);
out = socket.getOutputStream();
//用文件字节输入流先从计算机上获取要发送的图片
FileInputStream fis = new FileInputStream("D:\\abc.jpeg");
byte[] b = new byte[1024];
int len = 0;
while((len = fis.read(b)) != -1){
//从计算机读取到图片字节数据后,直接通过socket获取的输出流发送到服务器
out.write(b, 0, len);
}
fis.close();
//public void shutdownOutput() 写入结束的标记。禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。
socket.shutdownOutput();
//接收是否上传成功的信息
InputStream in = socket.getInputStream();
byte[] bb = new byte[1024];
int length = in.read(bb);
System.out.println(new String(bb, 0, length));
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.ujiuye.demo02_文件上传.多个用户;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;
/*
服务端:用来接收数据
*/
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(6666);
Random r = new Random();
while(true){
//获取Socket对象 (一定要放在while循环中)
Socket socket = ss.accept();
new Thread(){
@Override
public void run() {
//随机生成一个文件名
int number = r.nextInt(Integer.MAX_VALUE);
//创建一个文件字节输出流对象,用来将读取到的图片字节数据写入到计算机中
FileOutputStream fos = null;
try {
fos = new FileOutputStream("E:\\" + number + ".jpeg");
InputStream in = socket.getInputStream();
byte[] b = new byte[1024];
int len = 0;
while((len = in.read(b)) != -1){
//用文件字节输出流对象,将读取到的图片字节数据写入到计算机中
fos.write(b, 0, len);
}
fos.close();
//接收数据成功之后,返回给客户端"上传成功"
OutputStream out = socket.getOutputStream();
out.write("上传成功".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
// ss.close();
}
}
package com.ujiuye.demo02_文件上传.多个用户;
public class Demo {
public static void main(String[] args) {
Client client = new Client();
Thread t = new Thread(client);
Thread t2 = new Thread(client);
Thread t3 = new Thread(client);
t.start();
t2.start();
t3.start();
}
}
二、反射
2.1 虚拟机中类的加载机制
2.1.1 类加载概述
虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制
2.1.2 类的加载过程
当程序要使用某个类时,如果该类还未被加载到内存中,系统会通过加载、连接、初始化三步来实现对这个类的加载
加载
1. 就是指将class文件读入内存,并为之创建一个Class对象
2. 任何类被使用时系统都会建立一个Class对象
连接
1. 验证是否有正确的内部结构,并和其他类协调一致
2. 准备负责为类的静态成员分配内存,并设置默认初始化值
3. 解析将类的二进制数据中的符号引用替换为直接引用
初始化
主要对类的变量进行初始化
1. 类还未被加载,程序先加载并连接该类
2. 如该类还有直接父类,则先初始化其直接父类
3. 有初始化语句,按顺序执行
2.1.3 类的初始化时机 【面试题】
当使用这个类型时JVM虚拟机就会开始对这个类型进行加载和初始化
1. 创建类的实例
2. 类的静态成员使用
3. 类的静态方法调用
4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
5. 初始化某个类的子类
6. 直接使用java.exe命令来运行某个主类
2.2 类加载器【了解】
类加载器是负责加载类的对象,将class文件(硬盘)加载到内存中,并为之生成对应的java.lang.Class对象
2.2.1 分类
1) Bootstrap ClassLoader 引导类加载器,通常表示为null
也被称为根类加载器,负责Java核心类的加载,比如System、String等。
2) Extension ClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载,在JDK中JRE的lib目录下ext目录。
3) Application ClassLoader 系统类加载器
负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径。
4) 自定义类加载器
开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。
类加载器之间的继承关系
-Bootstrap ClassLoader
-Extension ClassLoader
-Application ClassLoader
2.2.2 双亲委派机制 【面试题】
概念
双亲委派机制是指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器。每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载。
工作过程
1) 当Application ClassLoader 收到一个类加载请求时,它首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器Extension ClassLoader去完成。
2) 当Extension ClassLoader收到一个类加载请求时,它也不会首先自己去尝试加载这个类,而是将请求委派给父类加载器Bootstrap ClassLoader去完成。
3) 如果Bootstrap ClassLoader加载失败,就会让Extension ClassLoader尝试加载。
4) 如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载。
5) 如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载。
6) 如果均加载失败,就会抛出ClassNotFoundException异常。
案例
当一个Hello.class这样的文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理会先检查自己是否已经加载过,如果没有再往上。注意这个过程,直到到达Bootstrap classLoader之前,都是没有哪个加载器自己选择加载的。如果父加载器无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。
2.2.3 ClassLoader类
ClassLoader 叫做类加载器。虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流” 也就是.class字节码文件,这个动作放到java虚拟机外部去实现,以便让应用程序自己决定去如何获取所需要的类。实现这个动作的代码块称之为“类加载器”。
static ClassLoader getSystemClassLoader() 返回委托的系统类加载器
ClassLoader getParent() 返回委托的父类加载器
2.3 反射
2.3.1 概述
反射是指在运行时去获取一个类的变量和方法信息,然后通过获取到的信息来创建对象,调用方法的一种机制。由于这种动态性,可以极大的增强程序的灵活性,程序在运行期仍然可以被修改扩展。
比如
1. 例如项目工程中,有两个类:Student、Teacher 需要使用这两个类
2. 因为要使用上述两类,这个两个类就会被类加载器从磁盘加载进入到内存中。进入内存中是类对应的.class字节码文件
3. 一个类型一旦进入到内存,就证明需要使用、证明代码需要运行,这种状态就是动态的效果(正在内存中运行)
4. 类加载器为这个正在运行的.class字节码文件,创建出一个对应的Class对象,对象中包含了class文件中所有代码内容
5. Class对象中,可以包含类中所有成员变量、构造方法、方法...
6. 使用Class对象,获取出类型中所有的需要的成员,这种使用方式称为反射机制
注意:也就是类在的加载进内存的时候,jvm虚拟机就会默认在堆上创建一个该类的Class类型,这个Class类型包含了此类所有的信息(包括成员变量、成员方法、构造方法)。 也就是说有了这个类的Class对象,我们就可以通过这个Class的对象访问类的构造方法、创建对象、访问类的成员等操作。
2.3.2 获取Class类对象三种方式 【掌握】
Class类
Class
类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该Class
对象。基本的 Java 类型(boolean
、byte
、char
、short
、int
、long
、float
和double
)和关键字void
也表示为Class
对象。
Class
没有公共构造方法。Class
对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass
方法自动构造的。
如何理解Class?
比如:
有一个(属性:张三, 23 行为:吃饭 ) (属性:李四,44 行为:吃饭) 这两个不同的对象,虽然他们有不同的属性及行为。但是他们都有人类的特征。所以我们可以把这两个对象归属到一个类中 Person类
有一个(属性:阿猫 行为:卖萌) (属性:阿狗 行为:跳火圈) 这个两个不同的对象,虽然它们有不同的属性及行为。但是它们都有动物的特征。所以我们可以把这两个对象归属到一个类中 Animal类
上方两个类,虽然是不同的类。但是这两个类都有成员变量和成员方法这两项,所以我们也可以将这些类给归属到一个类,这个类就是Class类型。
总结:就是将符合某些特征的类又给抽象出来了一个类,这个类就是Class类
Class类的对象
想获取和操作类中的内容,首先要获取类的字节码对象(Class类对象),每一个正在运行的类,都有对应的字节码对象,获取了类的字节码对象,就可以使用这个对象的所有方法,这些方法都定义在Class类型中。
获取Class类对象的三种方式
1) 类名.class属性
2) 对象名.getClass()方法
因为所有的类都是继承之Object类的,所以所有类都可以使用此getClass方法
3) Class.forName(全类名)方法
注意:
1. 全类名就是 包名.类名
2. 此forName方法中的参数是一个字符串类型。是字符串类型也就意味着无限的可能性。我们可以从任何地方获取到字符串并传入到forName方法中(比如:键盘录入、配置文件中读取、或者从网络中传输)
3. Idea中拷贝类全路径的快捷方式:就是copy reference 快捷键为:CTRL + ALT + SHIFT + C
package com.ujiuye.demo03_反射;
public class Demo2 {
public static void main(String[] args) throws ClassNotFoundException {
/*
全类名:
就是 包名.类名
*/
//1) 类名.class属性
Class<Person> personClass = Person.class;
System.out.println(personClass); //class com.ujiuye.demo03_反射.Person
//2) 对象名.getClass()方法
Person p = new Person();
Class<? extends Person> aClass = p.getClass();
System.out.println(aClass); //class com.ujiuye.demo03_反射.Person
//3) Class.forName(全类名)方法
Class<?> aClass1 = Class.forName("com.ujiuye.demo03_反射.Person");
System.out.println(aClass1); //class com.ujiuye.demo03_反射.Person
System.out.println(personClass == aClass); //true
System.out.println(personClass == aClass1); //true
System.out.println(aClass == aClass1); //true
}
}
2.3.3 反射获取构造方法并使用
Class类获取构造方法对象
Constructor<?>[] getConstructors() 返回所有公共public构造方法对象的数组
Constructor<?>[] getDeclaredConstructors() 返回所有构造方法对象的数组
Constructor getConstructor(Class<?>... parameterTypes) 返回单个公共构造方法对象
Constructor getDeclaredConstructor(Class<?>...parameterTypes) 返回单个构造方法对象
注意:
1. Class<?>... 为可变参数。可变参数即参数的个数可以是任意个(可以是0个也可以是多个)
2. 任意的数据类型都有对应Class对象,连基本数据数据类型也不例外: int.class
3. Constructor类型
1)表示构造方法类型,这个类的每个对象,都是一个确定的、具体的构造方法
2)构造方法对象应该具有的功能:获取构造方法各种信息(构造方法修饰符、构造方法名称、构造方法的参数列表、构造方法的注解)
3)最基本的一个功能就是:创建对象
4.Constructor类用于创建对象的方法:
T newInstance(Object...initargs) 根据指定的构造方法创建对象,参数为所运行构造方法需要的实际参数
package com.ujiuye.demo03_反射;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Demo3 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//获取Person类的Class类型对象
Class<?> clazz = Class.forName("com.ujiuye.demo03_反射.Person");
//Constructor<?>[] getConstructors() 返回所有公共public构造方法对象的数组
Constructor<?>[] constructors = clazz.getConstructors();
// for (Constructor<?> constructor : constructors) {
//
// System.out.println(constructor);
// }
//Constructor<?>[] getDeclaredConstructors() 返回所有构造方法对象的数组
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
// for (Constructor<?> declaredConstructor : declaredConstructors) {
//
// System.out.println(declaredConstructor);
// }
//Constructor getConstructor(Class<?>... parameterTypes) 返回单个公共构造方法对象
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// System.out.println(constructor);
//Constructor getDeclaredConstructor(Class<?>...parameterTypes) 返回单个构造方法对象
Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(int.class);
// System.out.println(declaredConstructor);
//使用构造方法创建对象
//T newInstance(Object...initargs) 根据指定的构造方法创建对象,参数为所运行构造方法需要的实际参数
Person p = (Person)constructor.newInstance("厨师", 10);
System.out.println(p);
System.out.println(p.name + "---" + p.age);
}
}
2.3.4 反射获取成员变量并使用
Class类获取成员变量对象
Field[] getFields() 返回所有公共成员变量对象的数组
Field[] getDeclaredFields() 返回所有成员变量对象的数组
Field getField(String name) 返回单个公共成员变量对象,参数name表示成员变量的名字
Field getDeclaredField(String name) 返回单个成员变量对象,参数name表示成员变量的名字
注意:
1. Field类型: 表示一个成员变量类型,每个对象都是一个具体的成员变量
1) 作用:获取成员变量的各种信息(修饰符、注解、名称)
2) Field类用于给成员变量赋值的方法:
set(Object obj, Object value):用于给obj对象的,该成员变量赋value值
3) Field类获取成员变量值的方法:
get(Object obj): 用于获取obj对象的指定成员变量值
package com.ujiuye.demo03_反射;
import java.lang.reflect.Field;
public class Demo4 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
//获取Person类的Class对象
Class<Person> clazz = Person.class;
//获取成员变量对象
//Field[] getFields() 返回所有公共成员变量对象的数组
Field[] fields = clazz.getFields();
// for (Field field : fields) {
//
// System.out.println(field);
// }
//Field[] getDeclaredFields() 返回所有成员变量对象的数组
// Field[] declaredFields = clazz.getDeclaredFields();
// for (Field declaredField : declaredFields) {
//
// System.out.println(declaredField);
// }
//Field getField(String name) 返回单个公共成员变量对象,参数name表示成员变量的名字
Field name = clazz.getField("name");
Field address = clazz.getField("address");
// System.out.println(address);
//Field getDeclaredField(String name) 返回单个成员变量对象,参数name表示成员变量的名字
Field sex = clazz.getDeclaredField("sex");
System.out.println(sex);
//----------------------------------------------
//Field类用于给成员变量赋值的方法: set(Object obj, Object value):用于给obj对象的,该成员变量赋value值
/*
参数obj:是想要给类的成员变量设置值的对象
Object obj: 就是要给成员变量设置的值
*/
/*
如何获取一个类的对象(实例):
我们可以先获取类的Class对象,然后再通过Class类型的对象获取到类的所有构造方法:
1. 如果此类中有无参构造方法:
clazz.newInstance();
2. 如果此类中只有带参构造方法:
clazz.newInstance(参数...);
*/
Person person = clazz.newInstance();
address.set(person, "中国北京天安门,社会主义接班人");
//Field类获取成员变量值的方法: get(Object obj): 用于获取obj对象的指定成员变量值
Object o = address.get(person);
System.out.println(o); //中国北京天安门,社会主义接班人
Object o1 = name.get(person);
System.out.println(o1); //null
}
}
2.3.5 反射获取成员方法并使用
Class类获取成员方法对象
Method[] getMethods() 返回所有公共成员方法对象的数组,包括继承的
Method[] getDeclaredMethods() 返回所有成员方法对象的数组,不包括继承的
Method getMethod(String methodName, Class<?>...parameterTypes) 返回单个公共成员方法对象
Method getDeclaredMethod(String methodName, Class<?>...parameterTypes) 返回单个成员方法对象
注意:
1. Method类型:表示成员方法的类型,该类型的每个对象,都是一个具体的成员方法
1) 作用:获取成员方法信息,运行方法
2) Method如何执行方法:
invoke(Object obj, Object...values):调用obj对象的成员方法,参数是values,返回值是Object类型
值的方法: get(Object obj): 用于获取obj对象的指定成员变量值
Object o = address.get(person);
System.out.println(o); //中国北京天安门,社会主义接班人
Object o1 = name.get(person);
System.out.println(o1); //null
}
}
##### 2.3.5 反射获取成员方法并使用
###### Class类获取成员方法对象
```java
Method[] getMethods() 返回所有公共成员方法对象的数组,包括继承的
Method[] getDeclaredMethods() 返回所有成员方法对象的数组,不包括继承的
Method getMethod(String methodName, Class<?>...parameterTypes) 返回单个公共成员方法对象
Method getDeclaredMethod(String methodName, Class<?>...parameterTypes) 返回单个成员方法对象
注意:
1. Method类型:表示成员方法的类型,该类型的每个对象,都是一个具体的成员方法
1) 作用:获取成员方法信息,运行方法
2) Method如何执行方法:
invoke(Object obj, Object...values):调用obj对象的成员方法,参数是values,返回值是Object类型