一 网络编程
1. 网络编程的基本概述
前面我们学到的IO技术主要解决的是数据在不同介质之间的传输(内存–硬盘)。但是我们的数据在网络之间进行传输又该怎么解决,这个时候我们需要学习网络编程。网络编程主要解决的是数据在网络之间的传输问题。
Java 提供的网络类库,可以实现无痛的网络连接,联 网的底层细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。并且 Java 实现了一个跨平台的网络库,程序 员面对的是一个统一的网络编程环境。
我们要进行网络编程,我们需要解决以下两个问题:
- 如何准确地定位网络上一台或多台主机;定位 主机上的特定的应用;
- 找到主机后如何可靠高效地进行数据传输。
1.1 IP和端口号
1.1.1 IP地址
IP:就是描述计算机所在的网络地址。在Java中使用一个类InetAddress来描述,我们可以使用其子类来描述不同的ip地址。
IP地址可以分为两种:IPV4 和 IPV6
IPV4:4 个字节组成,4 个 0-255。大概 42 亿, 30 亿都在北美,亚洲 4 亿。2011 年初已经用尽。以点分 十进制表示,如 192.168.0.1。
IPV6:128 位(16 个字节),写成 8 个无符号 整数,每个整数用四个十六进制位表示,数之间用冒号(:) 分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984。
IP有一个缺点就是 不容易被识别。所以我们需要通过域名来和IP地址进行映射。
如何映射ip地址和域名的关系?
第一种: 本机的hosts文件来映射
第二种:DNS域名解析器进行映射
1.1.2 端口
当我们使用ip地址访问到服务器的所在地址之后,此时我们还不能进入到服务器内部。此时我们需要通过端口号才能进入到服务器的内部。这个端口号可以理解成进入服务器内部的一道门。不同的服务器,端口都是不一样的。比如tomcat服务器默认的端口是8080;mysql数据库服务器端口默认是3306;oracle数据库默认的端口号是1521;redis服务器默认的端口号是6379。
端口号被划分为0-65535直接的整数。所以我们在定义端口的时候,一般不要超过这个区间。
-
公认端口:0~1023。被预先定义的服务通信占 用(如:HTTP 占用端口 80,FTP 占用端口 21,Telnet 占用端口 23)。
-
注册端口:1024~49151。分配给用户进程或应 用程序。(如:Tomcat 占用端口 8080,MySQL 占用端 口 3306,Oracle 占用端口 1521 等)。
-
动态 / 私有端口:49152~65535。
端口号与 IP 地址的组合得出一个网络套接字:Socket。
1.2 网络协议
计算机网络中实现通信必须有一些约定,即通信协议, 对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。
问题:网络协议太复杂。
计算机网络通信涉及内容很多,比如指定源地址和目 标地址,加密解密,压缩解压缩,差错控制,流量控制, 路由控制,如何实现如此复杂的网络协议呢?
通信协议分层的思想:
在制定协议时,把复杂成份分解成一些简单的成份, 再将它们复合起来。最常用的复合方式是层次方式,即 同层间可以通信、上一层可以调用下一层,而与再下一 层不发生关系。各层互不影响,利于系统的开发和扩展。
1.2.1 TCP协议(Transmission Control Protocol)
TCP/IP 以其两个主要协议:传输控制协议 (TCP) 和网 络互联协议 (IP) 而得名,实际上是一组协议,包括多个 具有不同功能且互为关联的协议。
-
使用 TCP 协议前,须先建立 TCP 连接,形成传输 数据通道。
-
传输前,采用“三次握手”方式,点对点通信,是 可靠的。
-
TCP 协议进行通信的两个应用进程:客户端、服务端。
-
在连接中可进行大数据量的传输 - 传输完毕,需 释放已建立的连接,效率低。
1.2.2 UDP协议(User Datagram Protocol)
- 将数据、源、目的封装成数据包,不需要建立连接。
- 每个数据报的大小限制在 64K 内。
- 发送不管对方是否准备好,接收方收到也不确认, 故是不可靠的。
- 可以广播发送。
- 发送数据结束时无需释放资源,开销小,速度快。
1.2.3 基于tcp网络协议的案例
需求:客户端发送信息给服务端,服务端收到信息之后,将数据打印输出在控制台上
/**
* 客户端发送信息给服务端,服务端收到信息之后,将数据打印输出在控制台上
*/
public class TcpTest1 {
/**
* 描述客户端
*/
@Test
public void client() throws Exception{
//获取服务器的ip地址
InetAddress inet = InetAddress.getByName("127.0.0.1");
//创建socket套接字
Socket socket = new Socket(inet,8899);
//根据套接字创建字节输出流
OutputStream outputStream = socket.getOutputStream();
//写出数据
outputStream.write(("你好,我是客户端小丽").getBytes());
//关闭资源
outputStream.close();
socket.close();
}
/**
* 描述服务端
*/
@Test
public void server() throws Exception{
//创建服务端的ServerSocket
ServerSocket serverSocket = new ServerSocket(8899);
//调用accept方法,获取客户端发送过来的数据
Socket socket = serverSocket.accept();
//构建字节输入流,来获取客户端发送过来的数据
InputStream is = socket.getInputStream();
//创建二进制输出流对象(不要直接使用InputStream去读取,因为会出现中文乱码的问题)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
int length = 0;
//将客户端发送过来的数据写到ByteArrayOutputStream里面去了。
while((length = is.read(bytes)) != -1){
baos.write(bytes,0,length);
}
//将客户端的数据输出打印到控制台上
System.out.println(baos);
System.out.println("收到了来自于:" + socket.getInetAddress().getHostAddress() + "的数据");
//关闭资源
baos.close();
is.close();
socket.close();
serverSocket.close();
}
}
需求:客户端发送文件给服务端,服务端收到文件之后,将文件保存在本地
/**
* 客户端发送文件给服务端,服务端收到文件之后,将文件保存在本地
*/
public class TcpTest2 {
//编写客户端
@Test
public void client() throws Exception{
//构建socket套接字对象
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9001);
//构建字节输出流对象
OutputStream out = socket.getOutputStream();
File file = new File("F:\\upload\\mm.jpg");
FileInputStream in = new FileInputStream(file);
byte[] buffer = new byte[1024];
int length = 0;
//将图片读取到程序中之后,再发送给服务端
while((length = in.read(buffer)) != -1){
out.write(buffer,0,length);
}
//关闭资源
in.close();
out.close();
socket.close();
}
//编写服务端
@Test
public void server() throws Exception{
ServerSocket serverSocket = new ServerSocket(9001);
Socket socket = serverSocket.accept();
InputStream in = socket.getInputStream();
FileOutputStream out = new FileOutputStream(new File("F:\\upload\\jd\\lily.jpg"));
byte[] buffer = new byte[1024];
int length = 0;
while((length = in.read(buffer)) != -1){
out.write(buffer,0,length);
}
//关闭资源
out.close();
in.close();
socket.close();
serverSocket.close();
}
}
1.2.4 基于UDP网络协议的案例
UDP协议通过数据报套接字DatagramSocket套接字发送和接收数据的。不能保证数据一定能够安全到达服务器,也不确定数据什么时候发送到服务器。
DatagramPacket对象封装了基于UDP协议传递过来的数据。
流程:
- 建立DatagramSocket和DatagramPacket对象
- 创建客户端(数据发送端),创建服务端(数据接收端)
- 建立数据包
- 调用Socket中的发送数据和接收数据的方法
- 关闭socket
/**
* 基于UDP网络协议的编程
*/
public class UDPTest {
@Test
public void client() throws Exception{
//构建Socket对象
DatagramSocket socket = new DatagramSocket();
String str = "客户端发送UDP数据";
byte[] bytes = str.getBytes();
InetAddress inet = InetAddress.getByName("127.0.0.1");
/**
* 构建数据包
* 参数1 发送数据的字节数组
* 参数2 从字节数组的起始位置开始发送
* 参数3 字节数组的实际长度
* 参数4 发送到服务器的IP地址
* 参数5 发送到服务器的端口号
*/
DatagramPacket packet = new DatagramPacket(bytes,0,bytes.length,inet,8099);
//发送数据(发送数据包)
socket.send(packet);
//关闭资源
socket.close();
}
@Test
public void server() throws Exception{
//构建套接字对象
DatagramSocket socket = new DatagramSocket(8099);
byte[] buffer = new byte[1024];
//创建数据包对象,来接收客户端发送过来的数据
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
//接收数据,将数据放在数据包里面
socket.receive(packet);
//解析数据包里面的数据,获取客户端发送过来的数据(字节数组)
byte[] data = packet.getData();
/**
* 根据字节数组构建字符串对象
* 参数1 字节数组
* 参数2 字节数组的起始位置
* 参数3 字节数组中实际存储数据的长度
*/
String message = new String(data,0,packet.getLength());
System.out.println(message);
//关闭资源
socket.close();
}
}
二 jdk1.8新特性
1 lambda表达式
lambda表达式是jdk1.8的新特性。它其实是匿名函数的另外一种写法,目的是简化我们的代码,让我们代码更加简洁。lambda表达式的使用必须需要函数式接口的支持。所谓函数式接口就是接口中必须只有1个抽象方法。我们的函数式接口一般使用注解@FunctionalInterface
来标识。
1.1 lambda表达式的初体验
我们来体验一下lambda表达式的魅力。
定义一个接口:
public interface UserDao {
public void find();
}
我们之前是这样实现这个接口的。
第一种: 创建接口的实现类来实现。
public class UserDaoImpl implements UserDao {
@Override
public void find() {
System.out.println("find方法被实现了.....");
}
}
调用接口中的 方法:
@Test
public void test01(){
UserDao dao = new UserDaoImpl();
dao.find();
}
第二种:我们可以基于匿名函数来实现接口中的方法。
@Test
public void test02(){
//基于匿名函数来对接口进行实现
AnimalDao dao = new AnimalDao() {
@Override
public void find() {
System.out.println("find方法被实现了.....");
}
};
dao.find();
}
现在我们可以基于lambda表达式来实现接口:
@Test
public void test03(){
UserDao userDao = () ->{
System.out.println("userDao中find方法被实现了.....");
};
userDao.find();
}
我们上面实现的接口是不到参数不带返回值的接口。
现在我们再定义一个带1个参数的接口方法:
public interface CatDao {
public int getAge(int age);
}
我们使用lambda表达式来实现接口:
@Test
public void test06(){
CatDao catDao = (age)->{
return age;
};
int age = catDao.getAge(2);
System.out.println(age);
}
我们还可以进一步的简化上面的代码:
@Test
public void test06(){
CatDao catDao = age -> age;
int age = catDao.getAge(2);
System.out.println(age);
}
我们再定义带多个参数的带返回值的接口方法:
public interface EmpDao {
public int getNum(int num1,int num2);
}
我们基于lambda表达式来实现接口:
@Test
public void test05(){
EmpDao empDao = (int num1,int num2) ->{
return num1 + num2;
};
System.out.println(empDao.getNum(2,2));
}
也可以这么写:
@Test
public void test05(){
EmpDao empDao = (num1,num2) ->{ //数据类型可以不用写,数据类型的自动判定
return num1 + num2;
};
System.out.println(empDao.getNum(2,2));
}
还可以进一步简化:
@Test
public void test05(){
EmpDao empDao = (num1,num2) -> num1 + num2;
System.out.println(empDao.getNum(2,2));
}
- Lambda表达式代码小总结
//1.无参无返回值FunctionalInterface
//利用lambda表达式实现接口
@Test
public void teatLambdaImplementInterface(){
//1.无参无返回值FunctionalInterface
UserDao userDao = () -> {
System.out.println("lambdaExpression--实现函数式接口!!!!");
};
userDao.abstractMethod();
UserDao userDao1 = ()->System.out.println("测试无参无返回值的函数是接口FunctionalInterface数据");
userDao1.abstractMethod();
//2.有参FunctionalInterface利用lambda表达式实现
UserGetAge userGetAge = (int age)->{
System.out.println("传参FunctionalInterface测试 age="+age);
return age;
};
userGetAge.getAge(12);
//简化写法
UserGetAge userGetAge1 = age -> {
System.out.println("传参FunctionalInterface测试 age="+age);
return age;
};
//双参数的functionalInterface 函数式接口的实现:
/*
* 参数类型可以省略
* return:只有一条执行语句时,return关键字可以审核
* {}: 只有一条执行语句时可以省略{}
* ():只有一个参数时可以省略()
* */
UserGetAgeAndName userGetAgeAndName = (age,name) -> "姓名:"+name+" 年龄:"+age;
String info = userGetAgeAndName.getNameAndAge(12, "小明");
System.out.println("info = " + info);
}
1.2 深入了解lambda表达式
要理解上面代码的含义,所以我们需要先理解lambda的基本语法。
基本语法:
()->{}
释义:
-> lambda表达式的操作符
->的左侧:()里面定义的参数列表,如果没有参数可以不写。
->的右侧: {} 方法体。接口中抽象方法的实现都是在{}里面实现的。
现在我们就了解lambda表达式的具体的语法格式:
语法格式1: 无参数的无返回值的接口。
() ->{方法体}
- 定义接口:
@FunctionalInterface //函数式接口,里面只能定义一个抽象方法
public interface PersonDao {
public void show();
}
- 实现接口
public class TestLambda {
@Test
public void test01(){
PersonDao dao = () ->{
System.out.println("show方法被实现了.....");
};
//调用接口
dao.show();
}
}
语法格式2:有1个参数,但是没有返回值。
(参数)->{方法体}
public interface StudentDao {
public void getName(String name);
}
@Test
public void test02(){
StudentDao studentDao = (String name) ->{
System.out.println("姓名是:" + name);
};
//调用接口中的方法
studentDao.getName("铁蛋");
}
语法格式3:参数列表中的数据类型可以省略,因为编译器可以自定推断
@Test
public void test02(){
StudentDao studentDao = (name) ->{ //String 数据类型可以不要写了。因为编译器可以自行推断
System.out.println("姓名是:" + name);
};
//调用接口中的方法
studentDao.getName("铁蛋");
}
语法格式4:参数列表如果只有1个参数,我们的圆括号都可以省略
@Test
public void test02(){
StudentDao studentDao = name ->{
System.out.println("姓名是:" + name);
};
//调用接口中的方法
studentDao.getName("铁蛋");
}
语法格式5:参数列表中有多个参数,并且有返回值的时候,花括号里面可以使用return返回值
@FunctionalInterface
public interface FishDao {
public double getPrice(double price1,double price2);
}
@Test
public void test03(){
FishDao fishDao = (price1,price2)->{
double price = price1 + price2;
return price;
};
System.out.println(fishDao.getPrice(1.1,2.3));
}
语法格式6:如果带返回值,但是方法体中有且只写一行代码,花括号可以省略,return关键字也可以省略。
@Test
public void test03(){
FishDao fishDao = (price1,price2)-> price1 + price1;
System.out.println(fishDao.getPrice(1.1,2.3));
}
总结:
- lambada表达式本质是对接口进行实现。接口必须是函数式接口,也就是接口中有且只有1个抽象方法。
- lambada表达式的左边参数的数据类型可以省略,因为编译器可以自动推断。
- lambada表达式的右边的方法体,如果只有一句代码,方法体也可以省略。
- 参数列表的参数如果有且只有1个 圆括号也可以省略。
- 如果接口带返回值,但是方法体只有一句代码,return关键字也可以省略。
2 函数式(Functional)接口
什么是函数式接口:只能定义一个抽象方法的接口,一般使用@FunctionalInterface来修饰。在java中,sun公司给我们提供了很多丰富的函数式接口(java.util.function包下面的)。我们可以查看官方定义的函数式接口:
在Java中内置了4大核心的函数式接口(java.util.function包下面的):
- 消费型接口(Consumer),里面有一个方法accept方法。
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
* 接收定义的参数,但是方法的返回值为void。
*/
void accept(T t);
}
- 供给型接口(Supplier),里面有1个get方法。
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
* 这个方法不传递任何参数,但是方法的返回值是我们指定的类型
* @return a result
*/
T get();
}
- 函数型接口(Function),里面有1个apply方法。
@FunctionalInterface
public interface Function<T, R> { // tagret resource
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
* 传递类型为T的参数,执行apply操作,结果要返回不类型为R的数据。
*/
R apply(T t);
}
- 断言型接口(Predicate),里面有test方法。
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
* 判断传入的数据是否满足一定的约束或规范,如果满足返回true,如果不满足返回false。
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}
2.1 函数式接口的简单使用
2.1.1 Consumer型接口
public class FunctionDemo1 {
public void getResult(double money, Consumer<Double> con){
con.accept(money);
}
//以前的写法
@Test
public void test01(){
getResult(2.5, new Consumer<Double>() {
@Override
public void accept(Double aDouble) {
System.out.println("花费了" + aDouble + "元");
}
});
}
//现在的写法
@Test
public void test02(){
getResult(5.5,(money)-> {
System.out.println("花费了" + money + "元");
});
}
}
2.1.2 Predicate型接口
/**
* 断言型接口
*/
public class PredicateDemo1 {
//将list集合中的元素经过过滤,过滤出来的元素放在新集合里面
public List<String> filterList(List<String> list, Predicate<String> pre){
ArrayList<String> arrayList = new ArrayList<>();
for(String str : list){
if(pre.test(str)){
arrayList.add(str);
}
}
return arrayList;
}
//以前的写法
@Test
public void test01(){
List<String> list = Arrays.asList("北京","南京","东京","天津");
List<String> newList = filterList(list, new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("京");
}
});
//获取过滤后的集合
System.out.println(newList);
}
//现在的写法
@Test
public void test02(){
List<String> list = Arrays.asList("北京","南京","东京","天津");
List<String> newList = filterList(list,(s)->s.contains("京"));
//输出集合
System.out.println(newList);
}
}
3 方法引用(Method References)
当要传递给lambda体的操作,已经有实现方法了,就可以使用方法引用。方法引用可以看做是lambda表达式深层次使用。
要使用方法引用必须要:实现接口的抽象方法的形式参数和返回值类型必须与方法引用的方法的参数列表和返回值类型保持一致。(指的是前两种情况)
格式:使用操作符“::”。 将对象(类)和方法名称分割开来。
常见的写法有以下三种:
-
对象 :: 实例方法名称
-
类名 :: 静态方法名称
-
类名 :: 实例方法名称
3.1 方法引用的基本使用
3.1.1 场景1: 对象 :: 实例方法
例子1:
public class MethodReferenceDemo1 {
@Test
public void test01(){
Consumer<String> con = msg -> System.out.println(msg);
con.accept("上海");
}
/**
* 我们可以进一步简化上面的代码
* void accept(T t)
* void println(String x)
*/
@Test
public void test02(){
//PrintStream ps = System.out;
//Consumer<String> con = ps :: println;
Consumer<String> con = System.out :: println;
con.accept("纽约");
}
}
释义:
test01方法中的lambda体本质上是对accept方法进行实现。(void accept(T t)) 这个方法有对应的替代方法和它长得一样。那么这个方法就是 void println(String str)。 因为这两个方法的返回值和参数都是一样。所以我们可以使用方法引用,就是使用println方法去替代accept方法。那么这样做的好处是我们可以进一步的简化代码(不用写参数列表 不用写lambda操作符,不用写lambda操作体)
例子2:
@Test
public void test03(){
Employee employee = new Employee(10010,"eric",28,30000);
Supplier<String> sup = () -> employee.getName();
System.out.println(sup.get());
}
/**
* lambda操作体的方法是 T get()
* 对应的替代方法是 employee中的 String getName()
*/
@Test
public void test04(){
Employee employee = new Employee(10011,"james",38,30000);
Supplier<String> sup = employee :: getName;
System.out.println(sup.get());
}
3.1.2 类 :: 静态方法名称
public class MethodReferenceDemo2 {
/**
* lambda操作体里面是对 Comparator类里面的compare方法进行实现
* 我们发现 Integer类里面有一个静态方法 compare(x,y),方法的返回值也是int
*/
@Test
public void test01(){
Comparator<Integer> com = (o1,o2) -> Integer.compare(o1,o2);
System.out.println(com.compare(12,56));
}
@Test
public void test02(){
Comparator<Integer> com = Integer::compare;
System.out.println(com.compare(12,23));
}
}
/**
* lambda操作体里面是对apply方法进行实现 R apply(T t) 也就是传入一个类型的数据 返回另一种数据类型的数据
* 我们发现在Math类里面的 round方法也是这样的 long round(double a)
* 所以我们可以使用round方法来对apply方法进行替换
*/
@Test
public void test03(){
Function<Double,Long> fun = t -> Math.round(t);
System.out.println(fun.apply(3.1415926));
}
@Test
public void test04(){
Function<Double,Long> fn = Math :: round;
System.out.println(fn.apply(9.69696969));
}
3.1.3 类名::实例方法名称
例子1:
public class MethodReferenceDemo3 {
/**
* lambda操作体是对Comparator接口中的compare方法进行实现 int compare(s1,s2)
* 在String中compareTo可以对Comparator接口中的compare方法进行替换 int s1.compareTo(s2)
*/
@Test
public void test01(){
Comparator<String> com = (s1,s2) -> s1.compareTo(s2);
System.out.println(com.compare("abc","acd"));
}
@Test
public void test02(){
Comparator<String> com = String :: compareTo;
System.out.println(com.compare("abc","acd"));
}
}
例子:
/**
* lambda操作体是对BiPredicate接口中的 test方法进行实现 boolean test(s1,s2)
* 在String中equals方法可以对test方法进行替换 boolean s1.equals(s2)
*/
@Test
public void test03(){
BiPredicate<String,String> bi = (s1,s2) ->s1.equals(s2);
System.out.println(bi.test("abc","acd"));
}
@Test
public void test04(){
BiPredicate<String,String> bi = String :: equals;
System.out.println(bi.test("abc","acd"));
}
总结:
方法引用的使用要求:
要求接口中的方法和引用的方法的返回值类型和参数列表必须要一致(仅仅针对于第一种和第二种情况)。
4 构造器引用
格式: ClassName :: new
我们如果要使用构造器引用,就要求构造函数参数列表和抽象方法的参数列表要一致。并且方法的返回值就是构造器对应的类的对象。
案例1:
public class ConstructorReferenceDemo1 {
//之前的写法
@Test
public void test01(){
Supplier<Employee> sup = new Supplier<Employee>() {
@Override
public Employee get() {
return new Employee();
}
};
System.out.println(sup.get());
}
/**
* 使用lambda表达式的写法
* lambda表达式的方法体里面其实是对get方法进行实现 T get()
* 我们发现Employee的无参数构造函数 不带任何参数列表 并且这个无参数构造函数构造出来的对象也是Employee类型的对象
* 这个对象的数据类型和get方法的返回值类型是一致的,所以可以使用构造函数引用 (类名 :: new)
*/
@Test
public void test02(){
Supplier<Employee> sup = () ->new Employee();
System.out.println(sup.get());
}
/**
* 使用构造函数引用的写法
*/
@Test
public void test03(){
Supplier<Employee> sup = Employee::new;
System.out.println(sup.get());
}
}
案例2:
/**
* lambda表达式的方法体里面其实是对apply方法进行实现 R get(T t)
* 我们发现Employee类中有一个带一个参数的构造函数 这个构造函数的参数列表和get方法是一样的,
* 并且构造函数创建的对象的类型和get方法的返回值类型也是一样的。所以我们可以使用构造函数引用
*/
@Test
public void test04(){
Function<Integer,Employee> func = id ->new Employee(id);
Employee employee = func.apply(2001);
System.out.println(employee);
}
@Test
public void test05(){
Function<Integer,Employee> func = Employee::new;
Employee employee = func.apply(2010);
System.out.println(employee);
}
案例3:
/**
* lambda表达式的方法体里面其实是对apply方法进行实现 R get(T t)
* 我们发现Employee类中有一个带2个参数的构造函数 这个构造函数的参数列表和get方法是一样的,
* 并且构造函数创建的对象的类型和get方法的返回值类型也是一样的。所以我们可以使用构造函数引用
*/
@Test
public void test06(){
BiFunction<Integer,String,Employee> fun = (id,name) -> new Employee(id,name);
Employee employee = fun.apply(1001,"eric");
System.out.println(employee);
}
@Test
public void test07(){
BiFunction<Integer,String,Employee> fun = Employee::new;
Employee employee = fun.apply(1001,"eric");
System.out.println(employee);
}
- FunctionalInterface函数式接口小总结测试
/**
* java.util.function 包下 Sun公司定义了很多种函数是接口
*
* Runnable -->执行型(无参无返) run抽象方法
* Consumer -->消费型(有参无返) accept抽象方法
* Supplier -->供给型(无参有返) get抽象方法
* Function -->函数型(有参有返) apply抽象方法
* Predicate -->断言型(boolean返回值) test抽象方法
* Comparable --> compareTo
* BiFunction -->
*
* */
@Test
public void TestUtilFunctionalInterface(){
Runnable runnable = ()->{};
}
/* 方法的引用测试~~
格式:使用操作符“::”。 将对象(类)和方法名称分割开来。
常见的写法有以下三种:
对象 :: 实例方法名称
类名 :: 静态方法名称
类名 :: 实例方法名称
*/
@Test
public void test1(){
//正常方式实现lambda表达式
Consumer<String> consumer = srt -> System.out.println(srt);
//利用方法的引用实现
Consumer<String> consumer1 = System.out :: print;
consumer.accept("测试方法的引用");
}
/*
格式: ClassName :: new
我们如果要使用构造器引用,就要求构造函数参数列表和抽象方法的参数列表要一致。并且方法的返回值就是构造器对应的类的对象。
*/
@Test
public void test2(){
//正常情况下lambda的实现
Function<String,String> function = srt -> new String(srt);
//构造器的应用实现
Function<String,String> function1 = String::new;
String test = function1.apply("测试构造器引用的实现");
System.out.println("test = " + test);
}
5 Stream流
jdk1.8新特性中,一个是lambda表达式,另外一个核心的特性就是Stream流。Stream流提供了非常强大的api来帮助我们更加高效、灵活的操作集合。我们可以使用Stream流相关的API可以对集合进行查找、过滤、映射等操作,类似于在数据库中我们使用sql语句对数据进行操作。
如何使用Stream流:
1、创建Stream流。 创建Stream流需要依赖数据。这些数据可以是集合、数组。
2、中间操作。一个中间操作就可以形成一个操作链,这个操作链就可以对数据进行处理。
3、终止操作(终端操作)。 一旦执行终止操作,就会执行中间链的操作,并产生操作之后的结果。
5.1 如何创建Stream流
- 通过集合的方式构建流对象
/**
* 通过集合创建流对象
*/
@Test
public void test01(){
List<Employee> employees = EmployeeData.getEmployees();
//串行流
Stream<Employee> stream = employees.stream();
//并行流
Stream<Employee> employeeStream = employees.parallelStream();
}
- 根据数组构建流对象
/**
* 通过数组创建流对象
*/
@Test
public void test02(){
//通过基本类型数组创建流对象
int[] arr = {11,12,13,14,15};
IntStream stream = Arrays.stream(arr);
//自定义数组创建流对象
Employee[] employees = new Employee[2];
employees[0] = new Employee(1001,"李四");
employees[1] = new Employee(1002,"马六");
Stream<Employee> stream1 = Arrays.stream(employees);
}
- Stream.of方法构建流对象
/**
* 通过Stream.of方法创建
*/
@Test
public void test03(){
Stream<Integer> stream = Stream.of(10, 11, 12, 13);
}
- Stream.iterate方法构建无限流对象
/**
* 构建无限流
*/
@Test
public void test04(){
Stream.iterate(0,e -> e + 2).limit(10).forEach(System.out :: println);
}
5.2 Stream流的中间操作
5.2.1 中间操作-- 筛选与切片
public class StreamAPIDemo2 {
@Test
public void test01(){
List<Employee> list = EmployeeData.getEmployees();
Stream<Employee> stream = list.stream();
//需求: 查找员工列表中薪资大于7000的员工。 filter 对流中的元素进行过滤操作
stream.filter(e -> e.getSalary() > 7000).forEach(System.out :: println);
System.out.println();
//输出所有元素
list.stream().forEach(System.out :: println);
System.out.println();
//输出前3个元素
list.stream().limit(3).forEach(System.out :: println);
System.out.println();
//跳过指定个数的元素
list.stream().skip(5).forEach(System.out :: println);
System.out.println();
//筛选(去重)
list.add(new Employee(10011,"eric",30,4000.0));
list.add(new Employee(10011,"eric",31,4000.0));
list.add(new Employee(10011,"eric",32,4000.0));
list.add(new Employee(10011,"eric",30,4000.0));
list.add(new Employee(10011,"eric",33,4000.0));
list.stream().distinct().forEach(System.out :: println);
}
}
5.2.2 中间操作–映射
@Test
public void test02(){
List<String> list = Arrays.asList("aa", "bb", "cc", "dd", "ee");
//map映射 接收1个函数作为参数。将元素转换成其他形式。该函数会将转换之后的元素应用到集合中的每个元素上,形成一个新的集合
list.stream().map(str -> str.toUpperCase()).forEach(System.out :: println);
System.out.println();
//需求:获取员工集合中 姓名长度大于3的员工的姓名
List<Employee> employees = EmployeeData.getEmployees();
//Stream<String> stringStream = employees.stream().map(str -> str.getName());
Stream<String> stringStream = employees.stream().map(Employee::getName);
stringStream.filter(name -> name.length() > 3).forEach(System.out :: println);
}
5.2.3 中间操作–排序
@Test
public void test03(){
List<Integer> list = Arrays.asList(12, 34, 56, 43, 67, 98, 9);
//sorted 对元素进行升序排序
list.stream().sorted().forEach(System.out :: println);
System.out.println();
//对员工进行排序
List<Employee> employees = EmployeeData.getEmployees();
//这种写法报错,因为我们不知道具体对员工进行排序的规则是什么
//employees.stream().sorted().forEach(System.out :: println);
employees.stream().sorted((s1,s2) ->{
int value = Integer.compare(s1.getAge(),s2.getAge());
if(value != 0){
return value;
}else{
return -Double.compare(s1.getSalary(),s2.getSalary());
}
}).forEach(System.out :: println);
}
5.3 Stream流的终止操作
public class StreamAPIDemo3 {
@Test
public void test01(){
List<Employee> employees = EmployeeData.getEmployees();
//需求1:查询所有员工的年龄是否大于18 allMatch: 检查所有元素是否匹配指定的规则
boolean result = employees.stream().allMatch(e -> e.getAge() > 18);
System.out.println(result);
//需求2:查询所有员工中薪资是否存在大于10000的员工 anyMatch: 至少有1个元素匹配
boolean flag = employees.stream().anyMatch(e -> e.getSalary() > 10000);
System.out.println(flag);
//需求3:检查员工中,员工姓名中以李开头的员工信息 noneMatch 检查元素中是否存在没有匹配的元素
boolean flag1 = employees.stream().noneMatch(e -> e.getName().startsWith("李"));
System.out.println(flag1);
//需求4:找到第一个元素
Optional<Employee> first = employees.stream().findFirst();
System.out.println(first);
//需求5:找到任何一个元素
Optional<Employee> any = employees.parallelStream().findAny();
System.out.println(any);
}
}
@Test
public void test02(){
List<Employee> employees = EmployeeData.getEmployees();
//返回流中的元素的总数 查询薪资大于5000的员工个数
long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
System.out.println(count);
//返回流中的元素 薪资最高的薪水
Stream<Double> doubleStream = employees.stream().map(employee -> employee.getSalary());
Optional<Double> max = doubleStream.max(Double::compare);
System.out.println(max);
System.out.println();
//迭代集合
employees.forEach(employee -> {
System.out.println(employee);
});
System.out.println();
employees.stream().forEach(System.out :: println);
}
/**
* 统计 计算
*/
@Test
public void test03(){
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
//需求1 计算这些数字之和
Integer sum = list.stream().reduce(0, Integer :: sum);
System.out.println(sum);
//需求2 计算所有员工的工资之和
List<Employee> employees = EmployeeData.getEmployees();
Stream<Double> doubleStream = employees.stream().map(e -> e.getSalary());
Double totalSalary = doubleStream.reduce(0.0, Double::sum);
System.out.println(totalSalary);
}
@Test
public void test04(){
//collect 将流转换成其他的形式
List<Employee> employees = EmployeeData.getEmployees();
//将流转换成List集合
List<Employee> list = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
System.out.println(list);
System.out.println();
Set<Employee> set = employees.stream().filter(e -> e.getAge() > 30).collect(Collectors.toSet());
System.out.println(set);
}
6 Optional类
Optional类的主要作用就是帮助我们在开发的过程中,避免出现空指针异常。
6.1 Optional对象的创建
public class OptionalTest1 {
@Test
public void test01(){
//非空对象
Girl girl = new Girl();
//Girl girl = null;
//of方法中传递的参数 必须是一个非空的对象,否则报空指针异常。
Optional<Girl> optional = Optional.of(girl);
System.out.println(optional);
}
@Test
public void test02(){
//Girl girl = new Girl();
Girl girl = null;
//ofNullable 里面传递的参数可以为null 也可以不为null
Optional<Girl> optionalGirl = Optional.ofNullable(girl);
System.out.println(optionalGirl);
//如果optionalGirl是空的,就返回orElse方法中的参数
Girl newGirl = optionalGirl.orElse(new Girl("lily"));
System.out.println(newGirl);
}
}
6.2 简单使用Optional类
6.2.1 不使用Optional的后果
下面的案例,就出现了空指针异常。
public class OptionalTest2 {
//获取girl的名称
public String getName(Boy boy){
return boy.getGirl().getName();
}
//优化后的方案
public String getName1(Boy boy){
if(boy != null){
Girl girl = boy.getGirl();
if(girl != null){
return girl.getName();
}
}
return null;
}
@Test
public void test01(){
Boy boy = new Boy();
boy.setGirl(new Girl("lily"));
//boy = null; //此时会出现空指针异常
String name = getName(boy);
System.out.println(name);
}
@Test
public void test02(){
Boy boy = new Boy();
boy.setGirl(new Girl("lily"));
boy = null; //此时不会出现空指针异常,返回值为null.
String name = getName1(boy);
System.out.println(name);
}
}
6.2.2 使用Optional类来避免出现空指针异常
//使用Optional类来优化代码
public String getGirlName(Boy boy){
Optional<Boy> boyOptional = Optional.ofNullable(boy);
//如果boyOptional为null。就使用orElse里面的默认值
Boy boy1 = boyOptional.orElse(new Boy(new Girl("张柏芝")));
Girl girl = boy1.getGirl();
//如果girlOptional的内容为null。就使用orElse里面的默认值
Optional<Girl> girlOptional = Optional.ofNullable(girl);
Girl girl1 = girlOptional.orElse(new Girl("马蓉"));
return girl1.getName();
}
@Test
public void test03(){
//Boy boy = new Boy(new Girl("Lily"));
//Boy boy = null;
Boy boy = new Boy();
boy.setGirl(null);
String name = getGirlName(boy);
System.out.println(name);
}
三 Java基础面试题
1 值传递与引用传递
值的传递:指将实际参数传递给形式参数的过程。
1.1 值传递
将基本数据类型的值(包括字符串)传递给形参的过程
public class Test01 {
/**
* 值的传递(基本数据的传递 包括字符串)。它传递的并不是值的本身。传递的只是值的副本(拷贝)。
* 将这个副本传递给形式参数之后,修改的只是值的副本内容,并不是值的本身。
* @param args
*/
public static void main(String[] args) {
int number = 10;
change(number);
System.out.println(number);
}
public static void change(int number){
number += 1;
}
}
释义:
值传递,传递的不是值本身,而是值的拷贝(值的副本)。在change方法内部对值进行的修改,都不会修改值的本身,只会修改值的副本的
内容。
<
1.2 引用传递
引用传递,传递的值是引用类型的数据。
public class Car {
private String name;
private String brand;
private Double price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", brand='" + brand + '\'' +
", price=" + price +
'}';
}
}
public class Test02 {
public static void main(String[] args) {
Car car = new Car();
car.setName("BMW530i");
car.setBrand("BMW");
car.setPrice(480000.0);
changeCar(car);
System.out.println(car); // Car{name='E300', brand='Benz', price=500000.0}
}
public static void changeCar(Car car){
car.setBrand("Benz");
car.setName("E300");
car.setPrice(500000.0);
}
}
释义:
引用传递,传递的是引用类型数据的内存地址。将内存地址传递给changeCar方法的形式参数里面之后,changeCar方法内部修改的数据,其实就是对当前内存地址指向的对象进行修改。所以修改之后,数据会发生变化。
2 对象的拷贝(克隆)
2.1浅拷贝
如果我们要使用浅拷贝,拷贝涉及到的对象,必须要实现Cloneable接口。
浅拷贝的实现方式非常简单:
1、在所涉及的类上面实现Cloneable接口。
2、 在需要被克隆的对象所属的类里面重写clone方法(访问修饰符必须使用public)。
3、调用clone方法,实现对象的克隆。
public class Car implements Cloneable {
private String name;
private String color;
public Car(){}
public Car(String name, String color) {
this.name = name;
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}
public class Person implements Cloneable{
private Integer id;
private String name;
private Car car;
public Person(){}
public Person(Integer id, String name, Car car) {
this.id = id;
this.name = name;
this.car = car;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
@Override
public Object clone() throws CloneNotSupportedException {
return (Person)super.clone();
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", car=" + car +
'}';
}
}
//测试浅拷贝
@Test
public void test01() throws Exception{
Car car = new Car("BMW530i","黑色");
Person person = new Person(10010, "eric", car);
//浅拷贝(克隆)对象
Person clonePerson = (Person) person.clone();
System.out.println(person);
System.out.println(clonePerson);
System.out.println(person == clonePerson); //原对象和克隆对象不是同一个对象
System.out.println("-----修改克隆对象----- ");
car.setName("BenZ");
car.setColor("白色");
clonePerson.setCar(car);
clonePerson.setId(10889);
clonePerson.setName("奔驰汽车");
System.out.println(person);
System.out.println(clonePerson);
}
测试结果:
总结:
1、克隆出来的对象和原来的对象并不是同一个对象。
2、如果我们对克隆对象的属性进行修改,如果修改的基本类型的属性,对原对象的基本类型的属性是没有影响的。如果我们修改克隆对象的引用数据类型的属性,会影响原对象的引用类型数据的属性值。
2.2深拷贝
2.2.1 构造函数拷贝
要求深拷贝所涉及到的对象必须定义构造函数。
public class Cat {
private String name;
private String color;
public Cat(){}
public Cat(String name, String color) {
this.name = name;
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}
public class Animal {
private String name;
private String type;
private Cat cat;
public Animal(){}
public Animal(String name, String type, Cat cat) {
this.name = name;
this.type = type;
this.cat = cat;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
", type='" + type + '\'' +
", cat=" + cat +
'}';
}
}
//测试深拷贝
@Test
public void test02() throws Exception{
Cat cat = new Cat("橘猫","橘色");
//创建一个animal对象
Animal animal = new Animal("小橘","猫科",cat);
//深拷贝对象
Animal copyAnimal = new Animal(animal.getName(),animal.getType(),new Cat(cat.getName(),cat.getColor()));
System.out.println(animal == copyAnimal);
System.out.println(animal);
System.out.println(copyAnimal);
System.out.println("-------修改深拷贝的值--------");
copyAnimal.setType("犬类");
copyAnimal.setName("大黄");
copyAnimal.getCat().setColor("黄色");
copyAnimal.getCat().setName("狗");
System.out.println(animal);
System.out.println(copyAnimal);
}
总结:
深拷贝克隆出来的对象,如果对象的属性发生变化(不管是基本类型数据的值还是引用类型数据的值),都不会影响源对象的属性值。
2.2.2 重写clone方法
深拷贝所涉及到的类,必须要实现Cloneable接口,并且需要重写clone方法。
public class Emp implements Cloneable {
private String name;
private Department department;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
//重写clone方法
@Override
public Object clone() throws CloneNotSupportedException {
Emp cloneEmp = (Emp) super.clone();
cloneEmp.setDepartment((Department) this.department.clone());
return cloneEmp;
}
@Override
public String toString() {
return "Emp{" +
"name='" + name + '\'' +
", department=" + department +
'}';
}
}
public class Department implements Cloneable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Department{" +
"name='" + name + '\'' +
'}';
}
}
@Test
public void test03() throws Exception{
Department department = new Department();
department.setName("技术部");
Emp emp = new Emp();
emp.setName("eric");
emp.setDepartment(department);
//通过深拷贝,创建克隆对象
Emp cloneEmp = (Emp) emp.clone();
System.out.println(emp == cloneEmp);
System.out.println(emp);
System.out.println(cloneEmp);
System.out.println("-------修改克隆对象-------");
cloneEmp.setName("铁蛋");
cloneEmp.getDepartment().setName("市场部");
System.out.println(emp);
System.out.println(cloneEmp);
}
测试:
2.2.3 使用序列化的方式实现深拷贝
要求涉及深拷贝所属的类必须要实现序列化接口
public class School implements Serializable {
private String name;
public School(){}
public School(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
'}';
}
}
public class Student implements Serializable {
private String name;
private School school;
public Student(){}
public Student(String name, School school) {
this.name = name;
this.school = school;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", school=" + school +
'}';
}
}
//使用序列化实现深拷贝
@Test
public void test04() throws Exception{
School school = new School("北京大学");
Student student = new Student("eric",school);
//深拷贝对象
Student cloneStudent = clone(student);
System.out.println(student == cloneStudent);
System.out.println(student);
System.out.println(cloneStudent);
System.out.println("-------修改克隆对象-------");
cloneStudent.getSchool().setName("南京大学");
cloneStudent.setName("Lily");
System.out.println(student);
System.out.println(cloneStudent);
}
//拷贝的方法
private static <T extends Serializable> T clone(T obj) throws Exception{
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(outputStream);
oos.writeObject(obj);
ByteArrayInputStream bins = new ByteArrayInputStream(outputStream.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bins);
return (T) ois.readObject();
}
测试:
四 反射技术
1 反射的基本概述
反射:当一个字节码文件加载到JVM的时候,JVM会剖析该字节码文件,然后创建一个Class对象。如果我们需要使用、获取这些资源,我们就可以通过Class字节码对象中的一些方法去获取这些数据。
1.1 获取Class字节码对象
对于字节码文件,java使用了一个类来描述,这个类叫Class类。如何获取字节码对象?有三种方式:
- Class.forName(类的全限定名);
- 类名.class
- 对象.getClass();
不管哪种方式创建的字节码对象,字节码对象永远唯一。
@Test
public void test01() throws Exception{
//第一种创建字节码对象的方式
Class<?> clazz = Class.forName("com.qf.pojo.Person");
System.out.println(clazz);
//第二种创建字节码对象的方式
Class clazz1 = Person.class;
System.out.println(clazz1);
//第三种创建字节码对象的方式
Person person = new Person();
Class<? extends Person> clazz2 = person.getClass();
System.out.println(clazz2);
System.out.println(clazz == clazz1);
System.out.println(clazz1 == clazz2);
}
1.2 Constructor构造函数对象
在Java中,对于构造函数的描述,我们使用Constructor类来描述,通过Constructor我们可以去实例化对象。
@Test
public void test02() throws Exception{
Class<?> clazz = Class.forName("com.qf.pojo.Person");
/**
* getConstructor 获取构造函数
* 参数可以有多个,传递的参数的class对象
*/
Constructor<?> c1 = clazz.getConstructor(String.class);//获取带String类型参数的构造函数
Constructor<?> c2 = clazz.getConstructor(String.class, int.class);//获取带int类型参数的构造函数
System.out.println(c1);
System.out.println(c2);
System.out.println();
//获取所有公有的构造函数
Constructor<?>[] constructors = clazz.getConstructors();
for(Constructor c : constructors){
System.out.println(c);
}
System.out.println();
//获取私有的构造函数
//Constructor<?> c3 = clazz.getConstructor(int.class);
//System.out.println(c3); //报错 NoSuchMethodException
//getConstructor不能获取私有的构造函数
//获取所有的构造函数(公有 私有的)
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
for(Constructor cs : declaredConstructors){
System.out.println(cs);
}
System.out.println();
Constructor<?> c3 = clazz.getDeclaredConstructor(int.class);
System.out.println(c3);
c3.setAccessible(true);//开启对私有资源的访问权限(暴力获取)
Person p = (Person) c3.newInstance(3);
System.out.println(p);
}
注意:
getConstructor方法只能获取公有的构造函数资源。如果我们想获取私有的构造函数资源,我们需要使用getDeclaredConstructor方法。
对于私有化的资源,我们必须要开启对私有化资源操作的权限。setAccessible(true)。
1.3 Method方法对象
在Java中,对于类里面方法的描述,我们使用Method来描述。Method类提供了很多api帮助我们进行方法的获取和调用。
@Test
public void test03() throws Exception{
Class clazz = Class.forName("com.qf.pojo.Person");
Person p = (Person) clazz.getConstructor(null).newInstance(null);
System.out.println(p);
/**
* 根据字节码对象获取对应公有的Method对象
* 这些方法不仅仅只是我们自己定义的方法,还有很多方法是继承了Object类里面的方法
*/
Method[] methods = clazz.getMethods();
for(Method m : methods){
System.out.println(m);
}
System.out.println();
/**
* getMethod 获取指定的方法对象
* 参数1: 方法名称
* 参数2: 参数列表 如果参数不为null,就定义参数的class对象,如果为null,直接写null即可。
*/
Method method = clazz.getMethod("showInfo",null);
/**
* invoke调用方法
* 参数1: 调用方法的对象
* 参数2: 调用方法需要用到的实际参数
* invoke的返回值就是被调用方法的返回值,如果被调用方法的返回值为void。那么就不返回。
*/
method.invoke(p,null);
Method m1 = clazz.getMethod("getResult",int.class);
int num = (int)m1.invoke(p,1);
System.out.println(num);
System.out.println();
/**
* getDeclaredMethods 获取所有公有的和私有的方法
* 注意: Object父类里面的方法就不再获取了。
*/
Method[] methods1 = clazz.getDeclaredMethods();
for(Method method1 : methods1){
System.out.println(method1);
}
System.out.println();
/**
* 获取指定的私有方法
*/
Method m2 = clazz.getDeclaredMethod("getName",String.class);
m2.setAccessible(true);
String name = (String) m2.invoke(p,"eric");
System.out.println(name);
}
1.4 Field变量
在Java中,对于类里面成员变量的描述,我们使用Field来描述。Field类提供了很多api帮助我们进行成员变量的获取和调用。
@Test
public void test04() throws Exception{
Class clazz = Class.forName("com.qf.pojo.Person");
Person p = (Person) clazz.getConstructor(null).newInstance(null);
/**
* getFields 获取所有的公有的Field对象。
*/
Field[] fields = clazz.getFields();
for(Field f : fields){
System.out.println(f);
}
System.out.println();
//获取指定的变量
Field emailField = clazz.getField("email");
emailField.set(p,"abc@qq.com");
System.out.println(p.email);
System.out.println();
//所有的变量的获取(公有 私有都能够获取)
Field[] declaredFields = clazz.getDeclaredFields();
for(Field df : declaredFields){
System.out.println(df);
}
System.out.println();
//获取指定的私有变量
Field addressField = clazz.getDeclaredField("address");
addressField.setAccessible(true);
addressField.set(p,"CHINA");
System.out.println(p.getAddress());
}
2 类加载器
2.1 类加载器的基本概述
类加载器用来帮助我们进行资源加载的。JVM支持的类加载器如下:
BootStrap ClassLoader 引导类加载器
Extension ClassLoader 扩展类架子器
System ClassLoader 系统类加载器
用户自定义类加载器
如何获取类加载器:
public class TestClassLoader {
//获取不同的类加载器
@Test
public void test01(){
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//根据系统类加载器获取其父加载器(扩展类加载器)
ClassLoader extensionClassLoader = systemClassLoader.getParent();
System.out.println(extensionClassLoader);
//通过系统类加载器获取引导类加载器(null) 引导类加载器是由c/c++编写的,我们无法直接获取
ClassLoader bootstrapClassLoader = extensionClassLoader.getParent();
System.out.println(bootstrapClassLoader);
//自定义类加载器
ClassLoader personClassLoader = Person.class.getClassLoader();
System.out.println(personClassLoader);
}
}
JVM自带3个类加载器(引导类加载器,扩展类加载器,系统类加载器)。
- 引导类加载器
这个类加载使用C/C++语言实现的,嵌套在JVM内部。它用来加载Java的核心库(JAVA HOME/jre/lib/rt.jar.resources.jar或sun.boot.class. path路径下的内容) ,用于提供JVM自身需要的类,保证JVM正常的运行。并不继承自java.lang.ClassLoader,没有父加载器。
- 扩展类加载器
Java语言编写,由sun.misc. Launcher$ExtclassLoader实现
派生于ClassLoader类
父类加载器为启动类加载器
从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。
- 系统类加载器
派生于classLoader类,父类加载器为扩展类加载器,它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库。该类加载是程序中默认的类加载器,一般来说, Java应用的类都是由它来完成加载。
2.2 使用类加载器来进行资源的加载
- 定义properties配置文件保存配置信息
username=eric
password=admin
age=18
注意:这个配置文件目前放在src目录下面。
- 定义实体,封装加载的资源信息
public class User {
private String username;
private String password;
private Integer age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
'}';
}
}
- 执行加载资源的操作
通过类加载器来进行资源的加载。
@Test
public void test01(){
InputStream in = null;
try {
Properties properties = new Properties();
//获取类加载器
ClassLoader classLoader = TestUser.class.getClassLoader();
//通过类加载器将资源以输入流的方式进行读取
in = classLoader.getResourceAsStream("user.properties");
//将资源(流)装载到properties对象里面去。
properties.load(in);
User user = new User();
String username = properties.getProperty("username");
String password = properties.getProperty("password");
Integer age = Integer.parseInt(properties.getProperty("age"));
user.setUsername(username);
user.setPassword(password);
user.setAge(age);
System.out.println(user);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}