原本笔记都是手写的,为了之后方便保存和查阅,还是下定决心,把他敲出来正好作为一篇博客的内容。也借这个机会,好好的复习一遍Java基础知识。这是下半部分。
自学材料:黑马程序员Java全套视频
常用的端口号:
面向连接的通信,客户端和服务端必须经过3次握手,建立逻辑链接,才能安全通信
该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。
套接字:包含了IP地址和端口号的网络单位
构造方法:
参数:
成员方法:
如果此Scoket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道;
关闭生成的OutputStream也将关闭相关的Socket
如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道;
关闭生成的InputStream也将关闭相关的Socket
一旦一个socket被关闭,它不可再使用;
关闭此socket也将关闭相关的InputStream和OutputStream
任何先前写出的数据将被发送,随后终止输出流
使用步骤:
1)创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
2)使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
3)使用网络字节输出流OutputStream对象中的write()方法,给服务器发送数据
4)使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
5)使用网络字节输入流InputStream对象中的read()方法,读取服务器回写的数据
6)释放Socket资源
注意事项:
1)客户端和服务端进行交互,必须使用Socket中提供的网络流,不能使用自己创建的流对象
2)当我们创建客户端对象Socket的时候,就会去请求服务器,和服务器经过3次握手建立连接通路
这时若服务器没启动,就会抛出异常;若服务器已启动,就可进行交互
这个类实现了服务器套接字,接收服务端的请求,读取客户端发送的数据,给客户端回写数据
构造方法:
注意:
服务器需要明确一件事情,必须得知道是哪个客户端请求的服务器:使用accept()方法来获取
成员方法:
实现步骤:
1)创建服务器ServerSocket对象和系统要指定的端口号
2)使用ServerSocket对象中的方法accept(),获取请求的客户端对象Socket
3)使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
4)使用InputStream对象中的方法read(),读取客户端发送的数据
5)使用Socket对象中的方法getOutputStream()获取OutputStream对象
6)使用OutputStream对象中的方法write(),给客户端回写数据
7)释放资源(Socket和ServerSocket)
流程:
1)客户端使用本地的字节输入流,读取要上传的文件
2)客户端使用网络字节输出流,把读取的文件上传到服务端
3)服务端使用网络字节输入流,读取客户端上传的文件
4)服务端使用本地字节输出流,把读取的文件,保存到服务端硬盘上
5)服务端使用网络字节输出流,给客户端回写一个上传成功
6)客户端使用网络字节输入流,读取服务端回写的数据
优化方法:
1)接收并保存文件命名规则(随机数或毫秒值或。。。)
2)死循环,让服务端一直处于监听状态
3)使用多线程技术,提高程序的效率:有一个的客户端上传文件,就开启一个线程,完成文件的上传
客户端代码示例:
public class FileUpload_Client {
public static void main(String[] args) throws IOException {
// 1.创建流对象
// 1.1 创建输入流,读取本地文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));
// 1.2 创建输出流,写到服务端
Socket socket = new Socket("localhost", 6666);
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//2.写出数据.
byte[] b = new byte[1024 * 8 ];
int len ;
while (( len = bis.read(b))!=-1) {
bos.write(b, 0, len);
}
// 关闭输出流,通知服务端,写出数据完毕
socket.shutdownOutput();
System.out.println("文件发送完毕");
// 3. =====解析回写============
InputStream in = socket.getInputStream();
byte[] back = new byte[20];
in.read(back);
System.out.println(new String(back));
in.close();
// ============================
// 4.释放资源
socket.close();
bis.close();
}
}
服务端代码示例:
public class FileUpload_Server {
public static void main(String[] args) throws IOException {
System.out.println("服务器 启动..... ");
// 1. 创建服务端ServerSocket
ServerSocket serverSocket = new ServerSocket(6666);
// 2. 循环接收,建立连接
while (true) {
Socket accept = serverSocket.accept();
/*
3. socket对象交给子线程处理,进行读写操作
Runnable接口中,只有一个run方法,使用lambda表达式简化格式
*/
new Thread(() -> {
try (
//3.1 获取输入流对象
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
//3.2 创建输出流对象, 保存到本地 .
FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
BufferedOutputStream bos = new BufferedOutputStream(fis);
) {
// 3.3 读写数据
byte[] b = new byte[1024 * 8];
int len;
while ((len = bis.read(b)) != -1) {
bos.write(b, 0, len);
}
// 4.=======信息回写===========================
System.out.println("back ........");
OutputStream out = accept.getOutputStream();
out.write("上传成功".getBytes());
out.close();
//================================
//5. 关闭 资源
bos.close();
bis.close();
accept.close();
System.out.println("文件上传已保存");
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
}
有且只有一个抽象方法的接口,称之为函数式接口。当然接口中可以包含其他方法(默认、静态、私有)
只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导
注解:@FunctionalInterface
JDK1.8引入的新注解;编译器会检查该接口是否确实有且仅有一个抽象方法,即检测接口是不是一个函数式接口
函数式接口的使用场景:作为方法的参数和返回值类型(两种)
例如:
public class Demo09FunctionalInterface {
// 使用自定义的函数式接口作为方法参数
private static void doSomething(MyFunctionalInterface inter) {
inter.myMethod(); // 调用自定义的函数式接口方法
}
public static void main(String[] args) {
// 调用使用函数式接口的方法
doSomething(() ‐> System.out.println("Lambda执行啦!"));
}
}
当函数式接口作为方法的参数时:
代码示例:性能浪费的日志案例
这样一来,只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接
//优化前
public class Demo01Logger {
private static void log(int level, String msg) {
if (level == 1) {
System.out.println(msg);
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, msgA + msgB + msgC);
}
}
//优化后
@FunctionalInterface
public interface MessageBuilder {
String buildMessage();
}
public class Demo02LoggerLambda {
private static void log(int level, MessageBuilder builder) {
if (level == 1) {
System.out.println(builder.buildMessage());
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(1, () ‐> msgA + msgB + msgC );
}
}
接口仅包含一个无参的方法:
Supplier< T >接口称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get()方法就会生产什么类型的数据
例:使用Supplier 接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值
public class Demo02Test {
//定一个方法,方法的参数传递Supplier,泛型使用Integer
public static int getMax(Supplier<Integer> sup){
return sup.get();
}
public static void main(String[] args) {
int arr[] = {2,3,4,52,333,23};
//调用getMax方法,参数传递Lambda
int maxNum = getMax(()‐>{
//计算数组的最大值
int max = arr[0];
for(int i : arr){
if(i>max){
max = i;
}
}
return max;
});
System.out.println(maxNum);
}
}
接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定
接口中包含抽象方法
代码示例:
import java.util.function.Consumer;
public class Demo09Consumer {
private static void consumeString(Consumer<String> function) {
function.accept("Hello");
}
public static void main(String[] args) {
consumeString(s ‐> System.out.println(s));
}
}
Comsumer接口的默认方法:
作用:需要2个Consumer接口,可以把两个Consumer接口组合在一起,对数据进行消费;谁写在前面,谁先消费
代码示例:
先输出大写HELLO,然后输出小写hello
import java.util.function.Consumer;
public class Demo10ConsumerAndThen {
private static void consumeString(Consumer<String> one, Consumer<String> two) {
one.andThen(two).accept("Hello");
}
public static void main(String[] args) {
consumeString(
s ‐> System.out.println(s.toUpperCase()),
s ‐> System.out.println(s.toLowerCase()));
}
}
例:按照格式“ 姓名:XX。性别:XX。”的格式将信息打印出来。打印姓名的动作作为第一个Consumer接口的Lambda实例,打印性别的动作作为第二个Consumer接口的Lambda实例
import java.util.function.Consumer;
public class DemoConsumer {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };
printInfo(s ‐> System.out.print("姓名:" + s.split(",")[0]),
s ‐> System.out.println("。性别:" + s.split(",")[1] + "。"),
array);
}
private static void printInfo(Consumer<String> one, Consumer<String> two, String[]array) {
for (String info : array) {
one.andThen(two).accept(info); // 姓名:迪丽热巴。性别:女。
}
}
}
对于某种数据类型进行判断,结果返回一个boolean值
Predicate接口中包含一个抽象方法:
代码示例:
import java.util.function.Predicate;
public class Demo15PredicateTest {
private static void method(Predicate<String> predicate) {
boolean veryLong = predicate.test("HelloWorld");
System.out.println("字符串很长吗:" + veryLong);
}
public static void main(String[] args) {
method(s ‐> s.length() > 5);
}
}
Predicate接口中的默认方法(与或非):
例:如果要判断一个字符串既要包含大写“H”,又要包含大写“W”,那么:
//and代码示例
import java.util.function.Predicate;
public class Demo16PredicateAnd {
private static void method(Predicate<String> one, Predicate<String> two) {
boolean isValid = one.and(two).test("Helloworld");
System.out.println("字符串符合要求吗:" + isValid);
}
public static void main(String[] args) {
method(s ‐> s.contains("H"), s ‐> s.contains("W"));
}
}
//negate代码示例
import java.util.function.Predicate;
public class Demo17PredicateNegate {
private static void method(Predicate<String> predicate) {
boolean veryLong = predicate.negate().test("HelloWorld");
System.out.println("字符串很长吗:" + veryLong);
}
public static void main(String[] args) {
method(s ‐> s.length() < 5);
}
}
例:数组当中有多条“姓名+性别”的信息,通过Predicate 接口的拼装将符合要求的字符串筛选到集合ArrayList 中,需要同时满足两个条件:1)必须为女生;2)姓名为4个字。
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class DemoPredicate {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
List<String> list = filter(array,
s ‐> "女".equals(s.split(",")[1]),
s ‐> s.split(",")[0].length() == 4);
System.out.println(list);
}
private static List<String> filter(String[] array, Predicate<String> one,
Predicate<String> two) {
List<String> list = new ArrayList<>();
for (String info : array) {
if (one.and(two).test(info)) {
list.add(info);
}
}
return list;
}
}
接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件
Function接口中最主要的抽象方法为:
使用的场景例如:将String类型转换为Integer类型
Function接口中有一个默认方法
注意:
代码示例:第一个操作是将字符串解析成为int数字,第二个操作是乘以10。两个操作通过andThen 按照前后顺序组合到了一起
import java.util.function.Function;
public class Demo12FunctionAndThen {
private static void method(Function<String, Integer> one, Function<Integer, Integer> two) {
int num = one.andThen(two).apply("10");
System.out.println(num + 20);
}
public static void main(String[] args) {
method(str‐>Integer.parseInt(str)+10, i ‐> i *= 10);
}
}
例:String str = “赵丽颖,20”:1)将字符串截取数字年龄部分,得到字符串;2)将上一步的字符串转换成为int类型的数字;3)将上一步的int数字累加100,得到结果int数字
import java.util.function.Function;
public class DemoFunction {
public static void main(String[] args) {
String str = "赵丽颖,20";
int age = getAgeNum(str, s ‐> s.split(",")[1],
s ‐>Integer.parseInt(s),
n ‐> n += 100);
System.out.println(age);
}
private static int getAgeNum(String str, Function<String, String> one,
Function<String, Integer> two,
Function<Integer, Integer> three) {
return one.andThen(two).andThen(three).apply(str);
}
}
Stream流主要是对集合的操作
关注的是做什么,而不是怎么做
例:直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。代码中并没有体现使用线性循环或是其他任何算法进行遍历,我们真正要做的事情内容被更好地体现在代码中。
import java.util.ArrayList;
import java.util.List;
public class Demo03StreamFilter {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
list.stream()
.filter(s ‐> s.startsWith("张"))
.filter(s ‐> s.length() == 3)
.forEach(System.out::println);
}
}
Stream(流)是一个来自数据源的元素队列
Stream操作还有2个基础的特征:
使用一个流通常包括三个步骤:
是JDK1.8新加入的最常用的流接口。(这并不是一个函数式接口)
1)所有的Collection集合都可以通过stream默认方法获取流:default Stream < E > stream()
2)Stream接口的静态方法of可以获取 数组 对应的流:static < T > Stream< T > of(T…values)
of方法的参数是一个可变参数,所以支持数组。
方法一:
1)Collection获取流
java.util.Collection接口中加入了default方法stream用来获取流,所以其所有实现类均可获取流
代码示例:
import java.util.*;
import java.util.stream.Stream;
public class Demo04GetStream {
public static void main(String[] args) {
//list集合
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
//Set集合
Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();
//Vector集合
Vector<String> vector = new Vector<>();
Stream<String> stream3 = vector.stream();
}
}
2)Map获取流
java.util.Map接口不是Collection的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况
代码示例:
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public class Demo05GetStream {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
//键获取流
Stream<String> keyStream = map.keySet().stream();
//值获取流
Stream<String> valueStream = map.values().stream();
//键值对获取流
//Set> entries = map.entrySet();
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
}
}
方法二:
如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以Stream 接口中提供了静态方法of
代码示例:
import java.util.stream.Stream;
public class Demo06GetStream {
public static void main(String[] args) {
String[] array = { "张无忌", "张翠山", "张三丰", "张一元" };
Stream<String> stream = Stream.of(array);
}
}
这些方法可以被分成两种:
1)void forEach(Consumer< ? super T> action)
该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理
注意:
代码示例:
import java.util.stream.Stream;
public class Demo12StreamForEach {
public static void main(String[] args) {
Stream<String> stream = Stream.of("张无忌", "张三丰", "周芷若");
stream.forEach(name‐> System.out.println(name));
}
}
2)Stream< T > filter(Predicate super T> predicate)
该接口接收一个Predicate函数式接口参数作为筛选条件,用于对Stream流中的数据进行过滤
如果函数式接口返回结果为true,那么Stream流的filter方法将会留用元素;如果结果为false,那么filter方法将会舍弃元素
代码示例:
import java.util.stream.Stream;
public class Demo07StreamFilter {
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.filter(s ‐> s.startsWith("张"));
}
}
3)< R > Stream< R > map(Function super T, ? extends R> mapper);
将流中的元素映射到另一个流中
该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流
代码示例:
//map方法的参数通过方法引用,将字符串类型转换成为了int类型(并自动装箱为Integer类对象)
import java.util.stream.Stream;
public class Demo08StreamMap {
public static void main(String[] args) {
Stream<String> original = Stream.of("10", "12", "18");
Stream<Integer> result = original.map(str‐>Integer.parseInt(str));
}
}
4)long count()
用于统计Stream流中元素的个数
注意:
代码示例:
import java.util.stream.Stream;
public class Demo09StreamCount {
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.filter(s ‐> s.startsWith("张"));
System.out.println(result.count()); // 2
}
}
5)Stream< T > limit(long maxSize)
对流进行截取,只取用前n个
参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作
注意:limit方法是一个延迟方法,只是对流中的元素进行截取,返回一个新的流,所以可以继续调用Stream流中的其他方法
代码示例:
import java.util.stream.Stream;
public class Demo10StreamLimit {
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.limit(2);
System.out.println(result.count()); // 2
}
}
6)Stream< T > skip(long n)
如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流
代码示例:
import java.util.stream.Stream;
public class Demo11StreamSkip {
public static void main(String[] args) {
Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.skip(2);
System.out.println(result.count()); // 1
}
}
7)static < T > Stream< T > concat(Stream extends T> a, Stream extends T> b)
如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat
注意:这是一个静态方法,与java.lang.String当中的concat方法是不同的
代码示例:
import java.util.stream.Stream;
public class Demo12StreamConcat {
public static void main(String[] args) {
Stream<String> streamA = Stream.of("张无忌");
Stream<String> streamB = Stream.of("张翠山");
Stream<String> result = Stream.concat(streamA, streamB);
}
}
小练习代码示例:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class DemoStreamNames {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
// ...
List<String> two = new ArrayList<>();
// ...
// 第一个队伍只要名字为3个字的成员姓名;
// 第一个队伍筛选之后只要前3个人;
Stream<String> streamOne = one.stream().filter(s ‐> s.length() == 3).limit(3);
// 第二个队伍只要姓张的成员姓名;
// 第二个队伍筛选之后不要前2个人;
Stream<String> streamTwo = two.stream().filter(s ‐> s.startsWith("张")).skip(2);
// 将两个队伍合并为一个队伍;
// 根据姓名创建Person对象;
// 打印整个队伍的Person对象信息。
Stream.concat(streamOne,streamTwo).map(Person::new).forEach(System.out::println);
}
}
双冒号::为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者
语义分析:
第一种语义是指:拿到参数之后经Lambda之手,继而传递给System.out.println方法去处理
第二种等效写法的语义是指:直接让System.out中的println方法来取代Lambda
两种写法的执行效果完全一样,而第二种方法引用的写法复用了已有方案,更加简洁
注意:Lambda中传递的参数一定是方法引用中的那个方法可以接收的类型,否则会抛出异常
1)通过对象名引用成员方法
使用前提是对象名已经存在,成员方法也已经存在
//定义成员方法的一个类
public class MethodRefObject {
public void printUpperCase(String str) {
System.out.println(str.toUpperCase());
}
}
//函数接口的定义
@FunctionalInterface
public interface Printable {
void print(String str);
}
//那么当需要使用这个printUpperCase成员方法来替代Printable接口的Lambda的时候,已经具有了MethodRefObject类的对象实例,则可以通过对象名引用成员方法
public class Demo04MethodRef {
private static void printString(Printable lambda) {
lambda.print("Hello");
}
public static void main(String[] args) {
MethodRefObject obj = new MethodRefObject();
printString(obj::printUpperCase);
}
}
2)通过类名称引用静态成员方法
前提是类已经存在,静态方法也已经存在
由于在java.lang.Math类中已经存在了静态方法abs,所以当我们需要通过Lambda来调用该方法时,有两种写法。
//函数式接口的定义
@FunctionalInterface
public interface Calcable {
int calc(int num);
}
//法一:Lambda表达式的写法
public class Demo05Lambda {
private static void method(int num, Calcable lambda) {
System.out.println(lambda.calc(num));
}
public static void main(String[] args) {
method(‐10, n ‐> Math.abs(n));
}
}
//法二:方法引用的写法
//两个方法等效
public class Demo06MethodRef {
private static void method(int num, Calcable lambda) {
System.out.println(lambda.calc(num));
}
public static void main(String[] args) {
method(‐10, Math::abs);
}
}
3)使用super引用父类的成员方法
前提是super是已经存在的,父类的成员方法也已经存在
如果存在继承关系,当Lambda中需要出现super调用时,也可以使用方法引用进行替代
//函数式接口的定义
@FunctionalInterface
public interface Greetable {
void greet();
}
//父类的定义
public class Human {
public void sayHello() {
System.out.println("Hello!");
}
}
//子类的用法
public class Man extends Human {
@Override
public void sayHello() {
System.out.println("大家好,我是Man!");
}
//定义方法method,参数传递Greetable接口
public void method(Greetable g){
g.greet();
}
public void show(){
//调用method方法,使用Lambda表达式
method(()‐>{
//创建Human对象,调用sayHello方法
new Human().sayHello();
});
//简化Lambda
method(()‐>new Human().sayHello());
//使用super关键字代替父类对象
method(()‐>super.sayHello());
}
}
//如果使用方法引用来调用父类中的sayHello方法会更好
public class Man extends Human {
@Override
public void sayHello() {
System.out.println("大家好,我是Man!");
}
//定义方法method,参数传递Greetable接口
public void method(Greetable g){
g.greet();
}
public void show(){
method(super::sayHello);
}
}
4)使用this引用本类的成员方法
前提是this已经存在,本类的成员方法也已经存在
//函数式接口的定义
@FunctionalInterface
public interface Richable {
void buy();
}
//原始写法
public class Husband {
private void marry(Richable lambda) {
lambda.buy();
}
public void beHappy() {
marry(() ‐> System.out.println("买套房子"));
}
}
//如果这个Lambda表达式的内容已经在本类当中存在了,则可以对Husband丈夫类进行修改
public class Husband {
private void buyHouse() {
System.out.println("买套房子");
}
private void marry(Richable lambda) {
lambda.buy();
}
public void beHappy() {
marry(() ‐> this.buyHouse());
}
}
//如果希望取消掉Lambda表达式,用方法引用进行替换,则更好的写法为:
public class Husband {
private void buyHouse() {
System.out.println("买套房子");
}
private void marry(Richable lambda) {
lambda.buy();
}
public void beHappy() {
marry(this::buyHouse);
}
}
5)类的构造器(构造方法)引用
由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用 类名称::new 的格式表示
//创建一个类
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//用来创建Person对象的函数式接口
public interface PersonBuilder {
Person buildPerson(String name);
}
//要使用这个函数式接口,可以通过Lambda表达式:
public class Demo09Lambda {
public static void printName(String name, PersonBuilder builder) {
System.out.println(builder.buildPerson(name).getName());
}
public static void main(String[] args) {
printName("赵丽颖", name ‐> new Person(name));
}
}
//通过构造器引用,有更好的写法:
public class Demo10ConstructorRef {
public static void printName(String name, PersonBuilder builder) {
System.out.println(builder.buildPerson(name).getName());
}
public static void main(String[] args) {
printName("赵丽颖", Person::new);
}
}
6)数组的构造器的引用
前提是要创建的数组的长度已知
数组也是Object的子类对象,所以同样具有构造器,只是语法稍有不同
//函数式接口的定义
@FunctionalInterface
public interface ArrayBuilder {
int[] buildArray(int length);
}
//在应用该接口的时候,可以通过Lambda表达式:
public class Demo11ArrayInitRef {
private static int[] initArray(int length, ArrayBuilder builder) {
return builder.buildArray(length);
}
public static void main(String[] args) {
int[] array = initArray(10, length ‐> new int[length]);
}
}
//更好的写法是使用数组的构造器引用:
public class Demo12ArrayInitRef {
private static int[] initArray(int length, ArrayBuilder builder) {
return builder.buildArray(length);
}
public static void main(String[] args) {
int[] array = initArray(10, int[]::new);
}
}
测试分类
1)黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值。
2)白盒测试:需要写代码的。关注程序具体的执行流程。
Junit使用:白盒测试
步骤:
1)定义一个测试类(测试用例)
2)定义测试方法:可以独立运行
3)给方法加@Test
4)导入junit依赖环境
判定结果:
补充:
框架:半成品软件。可以在框架的基础上进行软件开发,简化编码
反射:将类的各个组成部分封装为其他对象,这就是反射机制
好处:
1)可以在程序运行过程中,操作这些对象。
2)可以解耦,提高程序的可扩展性。
获取Class对象的方式:
1)Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象
2)类名.class:通过类名的属性class获取
3)对象.getClass():getClass()方法在Object类中定义着。
注意:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
应用举例:
//法一:
Class cls1 = Class.forName("cn.itheima.domain.Person");
//法二:
Class cls2 = Person.class;
//法三:
Person p = new Person();
Class cls3 = p.getClass();
Class对象功能:
获取功能:
1)获取成员变量们
对于Filed成员变量的操作:
(1)对于访问private修饰符时,需要忽略访问权限修饰符的安全检查,需要加一行代码。
忽略访问权限修饰符的安全检查:。。.setAccessible(true); //暴力反射
(2)设置值:void set(Object obj, Object value)
(3)获取值:get(Object obj)
2)获取构造方法们
用来 创建对象:
(1)T newInstance(Object… initargs)
(2)如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
3)获取成员方法对象们:
对于成员方法对象的操作:
(1)执行方法:Object invoke(Object obj, Object… args参数列表)
(2)获取方法名称:String getName //获取方法名
4)获取全类名
案例:
1)将需要创建的对象的全类名和需要执行的方法定义在配置文件中
2)在程序中加载读取配置文件
3)使用反射技术来加载类文件进内存
4)创建对象
5)执行方法
简单实现:
//一、获取class目录下的配置文件
//1.先获得字节码文件对于的类加载器.getClassLoader()
ClassLoader classloader = ReflectTest.class.getClassLoader();
//2.这一步有两个方法
//法一:getResource(String name):获取资源路径
//法二:getResourceAsStream(String name):获取资源对应的字节流
InputStream is = classloader.getResourceAsStream("pro.properties");
pro.load(is);
//二、获取配置文件中定义的数据
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
//三、加载该类进内存
Class cls = Class.forName(className);
//四、创建对象
Object obj = cls.newInstance();
//五、获取方法对象
Method method = cls.getMethod(methodName);
//六、执行方法
method.invoke(obj);
概念:说明程序的。给计算机看的
定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释
概念描述:
作用分类:
1)编写文档:通过代码里标识的注解生成文档【生成文档doc文档,指令:java doc】
2)代码分析:通过代码里标识的注解对代码进行分析【使用反射】
3)编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
1)JDK中预定的一些注解
2)自定义注解
3)在程序中使用(解析)注解
自定义注解
格式:
元注解
public @interface 注解名称{
属性列表;
}
本质:注解本质上就是一个接口,该接口默认继承Annotation接口
属性:接口中的抽象方法
要求:
1)属性的返回值类型有下列取值
2)定义了属性,在使用时需要给属性赋值
如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值
如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
元注解:用于描述注解的注解
在程序使用(解析)注解:获取注解中定义的属性值
1)获取注解定义的位置的对象 (Class,Method,Field)
2)获取指定的注解
3)调用注解中的抽象方法获取配置的属性值
代码示例:
//********************Pro注解的定义****************************
package cn.itcast.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 描述需要执行的类名,和方法名
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
String className();
String methodName();
}
//***********************************************************
//********************Pro注解在程序中的使用(解析)***************
package cn.itcast.annotation;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* 框架类
*/
@Pro(className = "cn.itcast.annotation.Demo1",methodName = "show")
public class ReflectTest {
public static void main(String[] args) throws Exception {
/*
前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法
*/
//1.解析注解
//1.1获取该类的字节码文件对象
Class<ReflectTest> reflectTestClass = ReflectTest.class;
//2.获取上边的注解对象
//其实就是在内存中生成了一个该注解接口的子类实现对象
/*
public class ProImpl implements Pro{
public String className(){
return "cn.itcast.annotation.Demo1";
}
public String methodName(){
return "show";
}
}
*/
Pro an = reflectTestClass.getAnnotation(Pro.class);
//3.调用注解对象中定义的抽象方法,获取返回值
String className = an.className();
String methodName = an.methodName();
System.out.println(className);
System.out.println(methodName);
//4.加载该类进内存
Class cls = Class.forName(className);
//5.创建对象
Object obj = cls.newInstance();
//6.获取方法对象
Method method = cls.getMethod(methodName);
//7.执行方法
method.invoke(obj);
}
}
数组:.length
String:.length()
集合:.size()