title: Java进阶使用记录
date: 2022-03-09 00:26:17
tags:
用于对值进行判断,为真则运行,否则报错,多用于调试
Assert.isNull(null, "是否为空"); // 为空则运行
Assert.notNull("", "是否不为空"); // 不为空则运行
Assert.notEmpty(new ArrayList<>(), "数组是否不为空"); // 不为空则运行
可以使用 assert 进行更多判断操作,使用原则:
新版 IDEA 默认把 VM Options 去掉了,需要手动加进来
在 VM Options 里输入 -ea 开启断言
public static void main(String[] args) {
int num = 10;
// 判断 num 是否大于 12,不大于则为假,报错;大于则为真,继续运行
assert num > 12;
System.out.println("运行");
}
public static void main(String[] args) {
int i = 10;
// 假如 i < 10,则 i++,否则则报错
assert (i++ < 10) : "num 大于等于 10";
System.out.println(i);
}
[修饰符] class 类名<代表泛型的变量> { }
// 定义泛型类
class User<Fan>{
private Fan type;
public User(Fan type) {
this.type = type;
}
@Override
public String toString() {
return "User{" +
"type=" + type +
'}';
}
public Fan getType() {
return type;
}
public void setType(Fan type) {
this.type = type;
}
}
// 使用
public static void main(String[] args) {
User<String> stringUser = new User<>("String"); // 可以限制泛型为 String
System.out.println(stringUser); // 输出 String
User user = new User<>(1); // 不进行限制
System.out.println(user); // 输出 1
user.setType("类型");
System.out.println(user); // 输出 类型
}
修饰符 <代表泛型的变量> 返回值类型 方法名(参数) { }
// 定义泛型方法
class Person{
public <Fan> void show(Fan fan){
System.out.println(fan);
}
public <Fan> Fan showFan(Fan fan){
return fan;
}
}
// 使用
public static void main(String[] args) {
Person person = new Person();
person.show("String"); // 无返回,直接输出
// 传入一个 List 的 User泛型对象,User 的泛型又为 List
ArrayList<User<List<String>>> users = person.showFan(new ArrayList<User<List<String>>>(){
{
this.add(new User<>(new ArrayList<String>(){
{
this.add("array11");
this.add("array12");
}
}));
this.add(new User<>(new ArrayList<String>(){
{
this.add("array21");
this.add("array22");
}
}));
}
});
System.out.println(users); // 输出返回值
}
修饰符 interface 接口名<代表泛型的变量> { }
interface MyInterface<Fan>{
void add(Fan fan);
Fan get(Fan fan);
}
class MyInterfaceImpl implements MyInterface<String>{
@Override
public void add(String s) {
System.out.println(s + ",fan");
}
@Override
public String get(String s) {
return s;
}
}
// 实现类
class MyInterfaceImpl<Fan> implements MyInterface<Fan>{
@Override
public void add(Fan fan) {
System.out.println(fan + ",fan");
}
@Override
public Fan get(Fan fan) {
return fan;
}
}
// 主启动类
public static void main(String[] args){
MyInterface myInterface = new MyInterfaceImpl(); // 不限制泛型
myInterface.add(1);
MyInterface<String> myInterface1 = new MyInterfaceImpl<>(); // 限制泛型为 String
System.out.println(myInterface1.get("String"));
}
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符 > 表示。但是一旦使用泛型的通配符后,只能使用 Object 类中的共性方法,集合中元素自身方法无法使用
泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用 ?,?
表示未知通配符。此时只能接受数据,不能往该集合中存储数据。
public static void main(String[] args) {
get(new ArrayList<Integer>(){
{
this.add(1);
this.add(2);
}
});
get(new LinkedList<String>(){
{
this.add("String1");
this.add("String1");
}
});
}
public static void get(Collection<?> collection){ // 可以传入任意的 Collection 下的类型
collection.stream().forEach(o -> {
System.out.println(o);
});
}
之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限和下限
泛型的上限:
泛型的下限:
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
// Collection 可以换为 ArrayList 等其他类型
public static void getElement1(Collection<? extends Number> collection){
}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> collection){
}
// 现已知 Object 类,String 类,Number 类,Integer 类,其中 Number 是 Integer 的父类
public static void main(String[] args) {
Collection<Integer> integer = new ArrayList<Integer>();
Collection<String> string = new ArrayList<String>();
Collection<Number> number = new ArrayList<Number>();
Collection<Object> object = new ArrayList<Object>();
getElement1(integer);
getElement1(string);//报错
getElement1(number);
getElement1(object);//报错
getElement2(integer);//报错
getElement2(string);//报错
getElement2(number);
getElement2(object);
}
框架:半成品软件。可以在框架的基础上进行软件开发,简化编码
反射:将类的各个组成部分封装为其他对象,这就是反射机制
好处:
Class.forName("全类名")
:将字节码文件加载进内存,返回Class对象类名.class
:通过类名的属性 class 获取对象.getClass()
:getClass()方法是Object类中的方法,Object类是所有类的父类,所有创建的对象都有该方法.xxxClassLoader.loadClass()
传入类路径获取结论:
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个
获取功能:
// 获取所有 public 的成员变量,包括继承的
Field[] getFields()
// 获取指定名称的 public 成员变量
Field getField(String name)
// 获取所有的成员变量,不考虑修饰符,不包括继承的(这里只能获取到 private 的属性,但并不能访问该 private 字段的值,除非加上 setAccessible(true))
Field[] getDeclaredFields()
// 获取指定名称的所有成员变量,不考虑修饰符
Field getDeclaredField(String name)
// 获取所有 public 的构造方法
Constructor>[] getConstructors()
// 获取传入数据类型的对应构造方法,Constructor constructor = userTestClass.getConstructor(String.class, Integer.class);
Constructor getConstructor(类>... parameterTypes)
// 不考虑修饰符获取
Constructor getDeclaredConstructor(类>... parameterTypes)
// 不考虑修饰符获取
Constructor>[] getDeclaredConstructors()
// 获取所有 public 的成员方法,包括继承的
Method[] getMethods()
// 获取指定名称和传入参数类型的 public 成员方法,Method test = userTestClass.getMethod("test", String.class);
Method getMethod(String name, 类>... parameterTypes)
// 获取所有的成员方法,不包括继承的
Method[] getDeclaredMethods()
// 获取指定名称和传入参数类型的所有成员方法
Method getDeclaredMethod(String name, 类>... parameterTypes)
String getName()
操作:
// 示例类
class UserTest{
private String name;
private Integer age;
public String birthday;
public UserTest() {
}
public UserTest(String name, Integer age) {
this.name = name;
this.age = age;
}
public void test(){
System.out.println("test");
}
@Override
public String toString() {
return "UserTest{" +
"name='" + name + '\'' +
", age=" + age +
", birthday='" + birthday + '\'' +
'}';
}
}
// 使用
public static void main(String[] args) throws Exception {
UserTest userTest = new UserTest(); // 新建一个对象
Class<? extends UserTest> aClass = userTest.getClass(); // 通过对象.getClass() 的方式获取字节码
Field[] fields = aClass.getFields(); // 获取 public 的成员变量
for (Field field : fields) {
// public java.lang.String fan.demo02.UserTest.birthday
System.out.println(field);
System.out.println(field.getName()); // birthday
System.out.println(field.get(userTest)); // 获取值 null
field.set(userTest,"2001"); // 设置值,传入对象和设置的值
System.out.println(field.get(userTest)); // 2001
}
}
创建对象:
T newInstance(Object… initargs)
注:如果使用空参数构造方法创建对象,操作可以简化:Class对象.newInstance方法
// 使用
public static void main(String[] args) throws Exception {
// 无参构造创建对象
Class<UserTest> userTestClass = UserTest.class; // 通过类名.class 的方式获取字节码
UserTest userTest = userTestClass.newInstance(); // 直接通过Class对象.newInstance() 方法创建对象
UserTest userTest = userTestClass.getConstructor().newInstance(); // 获取构造方法再调用 newInstance 方法
// 有参构造创建对象,传入对应的数据类型
Constructor<UserTest> constructor = userTestClass.getConstructor(String.class, Integer.class);
UserTest userTest = constructor.newInstance("张三",1);
}
执行方法:
Object invoke(Object obj, Object… args) :调用 obj 对象的成员方法,参数是args,返回值是 Object 类型
获取方法名称:
String getName:获取方法名
// 使用
public static void main(String[] args) throws Exception {
Class<UserTest> userTestClass = UserTest.class;
Method test = userTestClass.getMethod("test", String.class);
System.out.println(test); // public void fan.demo02.UserTest.test(java.lang.String)
System.out.println(test.getName()); // test
UserTest userTest = userTestClass.newInstance(); // 创建对象
// 调用对象的 test 方法,有传参,传入参数 “李四”,有返回值,输出 “李四”
System.out.println(test.invoke(userTest,"李四"));
// 调用对象的 test1 方法,无传参,无返回值,输出为 null
Method test1 = aClass.getMethod("test1");
System.out.println(test1.invoke(userTest));
}
注解(Annotation),也叫元数据。一种代码级别的说明。它是 JDK1.5 及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
格式:
// 元注解
public @interface 注解名称{
属性列表;
}
本质:
注解本质上就是一个接口,该接口默认继承 Annotation 接口
public interface MyAnnotation extends java.lang.annotation.Annotation { }
属性:接口中的抽象方法
元注解:用于描述注解的注解
// 自定义一个注解
@Target({ElementType.FIELD, ElementType.TYPE}) // 作用在变量和方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
String value() default "fan"; // 设置默认值
boolean isNull(); // 不设置默认值,通过标注注解时传入
}
// 配置注解
@MyAnnotation(isNull = true)
@Data
class Person {
private String name;
private int age;
private boolean isDelete;
}
// 使用
public static void main(String[] args) throws Exception{
Person person = new Person();
Class<? extends Person> personClass = person.getClass();
// 通过反射,获取作用在类上的注解
MyAnnotation annotation = personClass.getAnnotation(MyAnnotation.class);
// @fan.annotation.MyAnnotation(value=fan, isNull=true)
System.out.println(annotation);
person.setName(annotation.value()); // 注入从注解里获取到的值
person.setDelete(annotation.isNull());
System.out.println(person); // Person(name=fan, age=0, isDelete=true)
}
函数式接口在Java中是指:有且仅有一个抽象方法的接口
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导
备注:“语法糖”是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的 for-each 语法,其实底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java 中的 Lambda 可以被当做是匿名内部类的“语法糖”,但是二者在原理上是不同的
只要确保接口中有且仅有一个抽象方法即可
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
与 @Override 注解的作用类似,Java 8 中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注解可用于一个接口的定义上:
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样
// 定义一个函数式接口
@FunctionalInterface
public interface MyFunction {
String myMethod(String name); // public abstract 可以省略
default void hidden() { };
}
// 使用
public static void main(String[] args) {
// 最基础写法
test(new MyFunction() {
@Override
public String myMethod(String name) {
System.out.println("myMethod方法执行!");
return name;
}
});
// 使用 Lambda 表达式
test(name -> {
System.out.println("myMethod方法执行!");
return name;
});
// 假如没有其他语句直接返回值,可以省略大括号和 return
test(name -> name);
}
public static void test(MyFunction myFunction){
String name = myFunction.myMethod("fan");
System.out.println(name);
}
当 flag 为 false 的时候,Lambda将不会执行。从而达到节省性能的效果
public static void main(String[] args) {
test(false, name -> {
System.out.println("myMethod方法执行!");
return name;
});
}
public static void test(Boolean flag, MyFunction myFunction){
if (flag == true){
String s = myFunction.myMethod("fan");
System.out.println(s);
}else {
System.out.println("未执行");
}
}
扩展:实际上使用内部类也可以达到同样的效果,只是将代码操作延迟到了另外一个对象当中通过调用方法来完成。而是否调用其所在方法是在条件判断之后才执行的
如果抛开实现原理不说,Java 中的 Lambda 表达式可以被当作是匿名内部类的替代品。如果方法的参数是一个函数式接口类型,那么就可以使用 Lambda 表达式进行替代。使用 Lambda 表达式作为方法参数,其实就是使用函数式接口作为方法参数
例如 java.lang.Runnable 接口就是一个函数式接口,假设有一个 startThread 方法使用该接口作为参数,那么就可以使用 Lambda 进行传参。这种情况其实和 Thread 类的构造方法参数为 Runnable 没有本质区别
public class Demo04Runnable {
public static void main(String[] args) {
startThread(() ‐> System.out.println("线程任务执行!"));
}
private static void startThread(Runnable runnable) {
new Thread(runnable).start();
}
}
类似地,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个 Lambda 表达式。当需要通过一个方法来获取一个 java.util.Comparator 接口类型的对象作为排序器时,就可以调该方法获取
public class Demo06Comparator {
public static void main(String[] args) {
String[] array = { "abc", "ab", "abcd" };
System.out.println(Arrays.toString(array));
Arrays.sort(array, newComparator());
System.out.println(Arrays.toString(array));
}
private static Comparator<String> newComparator() {
return (a, b) ‐> b.length() ‐ a.length();
}
}
JDK 提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在 java.util.function 包中被提供
java.util.function.Supplier 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的 Lambda 表达式需要“对外提供”一个符合泛型类型的对象数据
public static void main(String[] args) {
System.out.println(test(new Supplier<String>() {
@Override
public String get() { // 参数类型为使用 Supplier 时指定的数据类型
return "fan";
}
}));
System.out.println(test(() -> "fan"));
}
public static String test(Supplier<String> supplier){
return supplier.get();
}
java.util.function.Consumer
抽象方法:accept
Consumer 接口中包含抽象方法 void accept(T t) ,意为消费一个指定泛型的数据
public static void main(String[] args) {
test("fan", new Consumer<String>() {
@Override
public void accept(String name) {
System.out.println(name);
}
});
test("fan", name -> System.out.println(name));
}
public static void test(String name, Consumer<String> consumer){
consumer.accept(name);
}
默认方法:andThen
如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的 default 方法 andThen
public static void main(String[] args) {
test("fan", one -> System.out.println(one.toLowerCase()),
two -> System.out.println(two.toUpperCase()));
}
public static void test(String name, Consumer<String> one, Consumer<String> two){
Consumer<String> stringConsumer = one.andThen(two);
stringConsumer.accept(name);
one.andThen(two).accept(name);
}
有时候我们需要对某种类型的数据进行判断,从而得到一个 boolean 值结果。这时可以使用 java.util.function.Predicate
抽象方法:test
Predicate 接口中包含一个抽象方法: boolean test(T t) 。用于条件判断的场景
public static void main(String[] args) {
test("false", new Predicate<String>() {
@Override
public boolean test(String s) {
return s.length() > 4; // 返回一个判断
}
});
// 判断字符串长度是否小于 4
test("false", s -> s.length() < 4);
}
public static void test(String str, Predicate<String> predicate){
boolean test = predicate.test(str);
System.out.println(test);
}
默认方法:and
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用 default 方法 and
public static void main(String[] args) {
// 判断字符串是否存在 H 并且存在 W
test("fanHW", one -> one.contains("H"), two -> two.contains("W"));
}
public static void test(String str, Predicate<String> one, Predicate<String> two){
boolean test = one.and(two).test(str);
System.out.println(test);
}
默认方法:or
与 and 的“与”类似,默认方法 or 实现逻辑关系中的“或”
public static void main(String[] args) {
// 判断字符串是否存在 H 或存在 W
test("fanH", one -> one.contains("H"), two -> two.contains("W"));
}
public static void test(String str, Predicate<String> one, Predicate<String> two){
boolean test = one.or(two).test(str);
System.out.println(test);
}
默认方法:negate
它是执行了test方法之后,对结果 boolean 值进行 “!” 取反。一定要在 test 方法调用之前调用 negate 方法,正如 and 和 or 方法一样
public static void main(String[] args) {
test("fanH", one -> one.contains("H"), two -> two.contains("W"));
}
public static void test(String str, Predicate<String> one, Predicate<String> two){
boolean test = one.or(two).negate().test(str); // 对结果取反
System.out.println(test);
}
java.util.function.Function
抽象方法:apply
Function 接口中最主要的抽象方法为:R apply(T t) ,根据类型 T 的参数获取类型R的结果
// 将 String 类型转换为 Integer 类型
public static void main(String[] args) {
test("1234", new Function<String, Integer>() {
@Override
public Integer apply(String s) {
System.out.println("apply方法执行!");
return Integer.parseInt(s); // 返回转换后的类型
}
});
test("1234", s -> Integer.parseInt(s));
}
public static void test(String str, Function<String, Integer> function){
Integer apply = function.apply(str);
System.out.println(apply);
}
默认方法:andThen
Function 接口中有一个默认的 andThen 方法,用来进行组合操作,用于“先做什么,再做什么”的场景
public static void main(String[] args) {
// 先进行类型转换,然后再将转换后的类型的值乘以 10
test("1234", one -> Integer.parseInt(one), two -> two *= 10);
}
public static void test(String str, Function<String, Integer> one, Function<Integer, Integer> two){
Integer apply = one.andThen(two).apply(str);
System.out.println(apply);
}
网络编程,就是在一定的协议下,实现两台计算机的通信的程序
通信的协议还是比较复杂的, java.net 包中包含的类和接口,它们提供低层次的通信细节。我们可以直接使用这些类和接口,来专注于网络程序开发,而不用考虑通信的细节。java.net 包中提供了两种常见的网络协议的支持
传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等
用户数据报协议(User Datagram Protocol)。UDP协议是一个面向无连接的协议。传输数据时,不需要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个数据包的大小限制在 64k 以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。日常应用中,例如视频会议、QQ聊天等
计算机网络通信必须遵守的规则
指互联网协议地址(Internet Protocol Address),俗称 IP。IP 地址用来给一个网络中的计算机设备做唯一的编号。假如我们把“个人电脑”比作“一台电话”的话,那么“IP地址”就相当于“电话号码”
IP地址分类
常用命令
ipconfig
ping IP地址
网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?
如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了
利用 协议 + IP地址 + 端口号 三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互
Window 和 Linux 查看端口常用命令
TCP 通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)。两端通信时步骤:
在Java中,提供了两个类用于实现 TCP 通信程序:
Socket 类:该类实现客户端套接字,套接字指的是两台设备之间通讯的端点
构造方法:public Socket(String host, int port)
Socket client = new Socket("127.0.0.1", 6666);
创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的 host 是 null ,则相当于指定地址为回送地址
注:回送地址(127.x.x.x) 是本机回送地址(Loopback Address),主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,立即返回,不进行任何网络传输
成员方法
ServerSocket 类:这个类实现了服务器套接字,该对象等待通过网络的请求
构造方法:public ServerSocket(int port)
ServerSocket server = new ServerSocket(6666);
使用该构造方法在创建 ServerSocket 对象时,就可以将其绑定到一个指定的端口号上,参数 port 就是端口号
成员方法
到此,客户端向服务端发送数据成功。
自此,服务端向客户端回写数据。
// 服务端
public static void main(String[] args) throws IOException {
System.out.println("服务端启动 , 等待连接 .... ");
// 1.创建 ServerSocket对象,绑定端口,开始等待连接
ServerSocket serverSocket = new ServerSocket(6666);
// 2.接收连接 accept 方法, 返回 socket 对象
Socket server = serverSocket.accept();
// 3.通过socket 获取输入流
InputStream inputStream = server.getInputStream();
// 4.一次性读取数据
// 4.1 创建字节数组
byte[] bytes = new byte[1024];
// 4.2 据读取到字节数组中.
int len = inputStream.read(bytes);
// 4.3 解析数组,打印字符串信息
String str = new String(bytes, 0, len);
System.out.println(str);
// =========== 回写数据 ======================
// 5. 通过 socket 获取输出流
OutputStream outputStream = server.getOutputStream();
// 6. 回写数据
outputStream.write("服务端回复给客户端,数据 111 已收到".getBytes());
// 7.关闭资源.
inputStream.close();
outputStream.close();
server.close();
}
// 客户端
public static void main(String[] args) throws Exception {
System.out.println("客户端开始发送数据");
// 1.创建 Socket ( ip , port ) , 确定连接到哪里
Socket client = new Socket("localhost", 6666);
// 2.获取流对象 . 输出流
OutputStream outputStream = client.getOutputStream();
// 3.写出数据.
outputStream.write("客户端发送到服务端的数据 111".getBytes());
// ========== 获取回写数据 ================
// 4. 通过 Scoket 获取输入流对象
InputStream inputStream = client.getInputStream();
// 5. 读取数据数据
byte[] bytes = new byte[1024];
int len = inputStream.read(bytes);
String str = new String(bytes, 0, len);
System.out.println(str);
// 6. 关闭资源
outputStream.close();
inputStream.close();
client.close();
}
// 服务端
public static void main(String[] args) throws IOException {
System.out.println("服务端启动 , 等待连接 .... ");
// 1. 创建ServerSocket 对象,监听端口
ServerSocket server = new ServerSocket(8888);
// 2. 接收连接 accept 方法, 返回 socket 对象
Socket socket = server.accept();
// 3. 转换流读取浏览器的请求消息
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String requst = bufferedReader.readLine(); // 读取一行,GET /web/index.html HTTP/1.1
// 3.1 取出请求资源的路径
String[] strArr = requst.split(" "); //
// 3.2 去掉 web 前面的 /
String path = strArr[1].substring(1); // web/index.html
// 4. 读取客户端请求的资源文件
// 通过从字节码获取文件位置
// InputStream fis = SocketTest.class.getClassLoader().getResourceAsStream(path);
// 通过从项目根路径获取文件位置
FileInputStream fileInputStream = new FileInputStream("src/main/resources/" + path);
byte[] bytes= new byte[1024];
int len = 0 ;
// 5. 字节输出流,将文件写回客户端
OutputStream out = socket.getOutputStream();
// 5.1 写入HTTP协议响应头,固定写法
out.write("HTTP/1.1 200 OK\r\n".getBytes());
out.write("Content‐Type:text/html\r\n".getBytes());
// 5.2 必须要写入空行,否则浏览器不解析
out.write("\r\n".getBytes());
while((len = fileInputStream.read(bytes)) != -1){
out.write(bytes, 0, len);
}
fileInputStream.close();
out.close();
bufferedReader.close();
socket.close();
server.close();
}
浏览器地址输入 localhost:8888/web/index.html 进行访问,浏览器请求消息:
- “Stream流” 其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)
- 这里的 filter 、sorted、map 等都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法 collect 执行的时候,整个模型才会按照指定策略执行操作。而这得益于 Lambda 的延迟执行特性
Stream(流)是一个来自数据源的元素队列并支持聚合操作
特征:
java.util.stream.Stream 是Java 8 新加入的最常用的流接口。(这并不是一个函数式接口)
void forEach(Consumer<? super T> action);
该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理
// 使用
public static void main(String[] args) {
// 以下都用这个 ArrayList 进行操作
ArrayList<User> users = new ArrayList<>();
users.add(User.builder().name("张三").age(19).build());
users.add(User.builder().name("李四").age(20).build());
users.add(User.builder().name("王五").age(21).build());
users.add(User.builder().name("赵六").age(22).build());
users.stream().forEach(new Consumer<User>() {
@Override
public void accept(User user) {
System.out.println(user);
}
});
users.stream().forEach(user -> System.out.println(user));
}
可以通过 filter 方法将一个流转换成另一个子集流
Stream<T> filter(Predicate<? super T> predicate);
该接口接收一个 Predicate 函数式接口参数(可以是一个 Lambda 或方法引用)作为筛选条件
// 使用
public static void main(String[] args) {
// 返回名字里含有 “三” 的 user
users.stream().filter(new Predicate<User>() {
@Override
public boolean test(User user) {
return user.getName().contains("三");
}
}).forEach(user -> System.out.println(user));
users.stream().filter(user -> user.getName().contains("三")).forEach(user -> System.out.println(user));
}
将流中的元素映射到另一个流中
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
该接口需要一个 Function 函数式接口参数,可以将当前流中的 T 类型数据转换为另一种 R 类型的流
public static void main(String[] args) {
// 将 user 转换为获取 user中的 name
users.stream().map(new Function<User, String>() {
@Override
public String apply(User user) {
return user.getName();
}
}).forEach(s -> System.out.println(s));
users.stream().map(user -> user.getName()).forEach(s -> System.out.println(s));
}
limit 方法可以对流进行截取,只取用前 n 个
Stream<T> limit(long maxSize);
参数是一个 long 型,如果集合当前长度大于参数则进行截取,否则不进行操作
// 使用
public static void main(String[] args) {
users.stream().limit(3).forEach(user -> System.out.println(user));
}
long count();
统计个数,该方法返回一个 long 值代表元素个数
// 使用
public static void main(String[] args) {
long count = users.stream().count();
System.out.println(count);
}
跳过前几个元素,使用 skip 方法获取一个截取之后的新流
Stream<T> skip(long n);
如果流的当前长度大于 n,则跳过前 n 个;否则将会得到一个长度为 0 的空流
// 使用
public static void main(String[] args) {
users.stream().skip(2).forEach(user -> System.out.println(user));
}
collect 可以收集流中的数据到【集合】或者【数组】中
<R, A> R collect(Collector<? super T, A, R> collector);
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);
Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素
// 使用
public static void main(String[] args) {
// 转换为 List
List<User> collect = users.stream().collect(Collectors.toList());
System.out.println(collect);
// 转换为 Map,Key 为 name,Value 为 age
Map<String, Integer> collect1 = users.stream().collect(Collectors.toMap(user -> user.getName(), user -> user.getAge()));
System.out.println(collect1);
// 合并字符串,添加分隔符
String collect2 = users.stream().map(user -> user.getName()).collect(Collectors.joining(", "));
System.out.println(collect2);
}
如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
注:这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的
// 使用
public static void main(String[] args) {
Stream<String> stringStream = users.stream().map(user -> user.getName());
Stream<Integer> integerStream = users.stream().map(user -> user.getAge());
// 将包含 name 和 age 的两个流合成一个
Stream.concat(stringStream, integerStream).forEach(serializable -> System.out.println(serializable));
}
双冒号 :: 为引用运算符,而它所在的表达式被称为方法引用。如果 Lambda 要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为 Lambda 的替代者。如:
使用 Lambda,根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定重载形式——它们都将被自动推导。而如果使用方法引用,也是同样可以根据上下文进行推导。函数式接口是 Lambda 的基础,而方法引用是 Lambda 的孪生兄弟。
// 使用
public static void main(String[] args) {
// Lambda 写法
users.stream().map(user -> user).forEach(user -> System.out.println(user));
// 方法引用
users.stream().map(User::toString).forEach(System.out::println);
users.stream().map(User::getName).forEach(System.out::println);
}
一个类的对象要想序列化成功,必须满足两个条件:
ObjectOutputStream 类用来序列化一个对象,如下的 SerializeDemo 例子实例化了一个 Employee 对象,并将该对象序列化到一个文件中。该程序执行后,就创建了一个名为 employee.ser 文件
public class Employee implements java.io.Serializable{
public String name;
public String address;
public transient int SSN; // 短暂的
public int number;
public void mailCheck(){
System.out.println("Mailing a check to " + name + " " + address);
}
}
public class SerializeDemo{
public static void main(String [] args){
Employee e = new Employee();
e.name = "Reyan Ali";
e.address = "Phokka Kuan, Ambehta Peer";
e.SSN = 11122333;
e.number = 101;
try{
FileOutputStream fileOut = new FileOutputStream("/employee.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(e);
out.close();
fileOut.close();
System.out.printf("Serialized data is saved in /employee.ser");
}catch(IOException i){
i.printStackTrace();
}
}
}
下面的 DeserializeDemo 程序实例了反序列化,/employee.ser 存储了 Employee 对象
public class DeserializeDemo{
public static void main(String [] args){
Employee e = null;
try{
FileInputStream fileIn = new FileInputStream("/employee.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
e = (Employee) in.readObject();
in.close();
fileIn.close();
}catch(IOException i){
i.printStackTrace();
return;
}catch(ClassNotFoundException c){
System.out.println("Employee class not found");
c.printStackTrace();
return;
}
System.out.println("Deserialized Employee..."); // Deserialized Employee...
System.out.println("Name: " + e.name); // Name: Reyan Ali
System.out.println("Address: " + e.address); // Address: Phokka Kuan, Ambehta Peer
System.out.println("SSN: " + e.SSN); // SSN: 0
System.out.println("Number: " + e.number); // Number: 101
}
}
/* 当对象被序列化时,属性 SSN 的值为 111222333,但是因为该属性是短暂的,
该值没有被发送到输出流。所以反序列化后 Employee 对象的 SSN 属性为 0。
*/
一个可以更加轻松的避免 NPE(空指针异常,NullPointException)的工具,Optional 是一个包装类,且不可变,不可序列化
为了控制生成实例的方式,也是为了收紧空值 Optional 的定义,Optional 将构造函数定义为 private。想要创建 Optional 实例,可以借助 of 和 ofNullable 两个方法实现。of 方法传入的参数不能是 null 的,否则会抛出 NullPointerException。所以,对于可能是 null 的结果,一定使用 ofNullable
UserDemo userDemo = UserDemo.builder().id(1).name("张三").build();
Optional<UserDemo> userDemoOptional = Optional.of(userDemo);
Optional<Object> nullOptional = Optional.ofNullable(null);
Optional 类中还有一个静态方法:empty,这个方法直接返回了内部定义的一个常量 Optional> EMPTY = new Optional<>(),这个常量的 value 是 null。ofNullable 方法也是借助了 empty 实现 null 的包装:
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
所以说,对于 null 的 Optional 包装类,指向的都是相同的实例对象,Optional.empty() == Optional.ofNullable(null) 返回的是 true。换句话说,空 Optional 是单例的,都是引用 Optional.EMPTY
Optional 值为空时,使用 get 方法将抛出 NoSuchElementException 异常。如果不想抛出异常,或者能够 100% 确定不是空 Optional,或者使用 isPresent 方法判断
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
使用 get 方法前,必须使用 isPresent 检查。但是使用 isPresent 前,可以先看是否可以使用 orElse、orElseGet 等方法代替实现。
System.out.println(userDemoOptional.get().getName());
isPresent 用来判断值是否为空,类似于obj != null,ifPresent 可以传入一个 Consumer 操作,当值不为空的时候,会执行 Consumer 函数。
// if (userDemoOptional!= null) {
if (userDemoOptional.isPresent()) {
System.out.println(1111);
}
上面的方法等价于:
userDemoOptional.ifPresent(userDemo1 -> System.out.println(1111));
map 和 flatMap 是对 Optional 的值进行操作的方法,区别在于,map 会将结果包装到 Optional 中返回, flatMap 不会。但是两个方法返回值都是 Optional 类型,这也就要求,flatMap 的方法函数返回值需要是 Optional 类型
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
如果 Optional 的值为空,map 直接返回 Optional.EMPTY,否则会执行函数结果,并使用Optional.ofNullable 包装并返回
Optional<String> stringOptional = userDemoOptional.map(userDemo1 -> userDemo1.getName()); // abc
Optional<String> stringOptional1 = stringOptional.map(name -> name.toUpperCase()); // ABC
String aDefault = stringOptional1.orElse("default"); // 假如为空,则执行 else,有一个默认值
System.out.println(aDefault); // 有值为 abc,则 aDefault 为 ABC、值为空,则 aDefault 为 default
String aDefault = userDemoOptional.map(UserDemo::getName).map(String::toUpperCase).orElse("default");
当值为空时,orElse和orElseGet返回默认值,orElseThrow抛出指定的异常
orElse 和 orElseGet 的区别是 orElse 方法传入的参数是明确的默认值,orElseGet 方法传入的参数是获取默认值的函数。如果默认值的构造过程比较复杂,需要经过一系列的运算逻辑,那一定要使用 orElseGet,因为 orElseGet 是在值为空的时候,才会执行函数,并返回默认值,如果值不为空,则不会执行函数,相比于 orElse 而言,减少了一次构造默认值的过程。
String aDefault = userDemoOptional.map(UserDemo::getName)
.map(String::toUpperCase)
.orElse(null);
// .orElseGet(() -> null);
// .orElseThrow(() -> new IllegalArgumentException("clazz属性不合法"));
filter 方法提供的是值验证,如果值验证为 true,返回当前值;否则,返回空 Optional
// 遍历 userDemos,找到姓名属性为空的,打印 id
for (UserDemos userDemo : userDemos) {
Optional.of(userDemo )
.filter(x -> x.getName() == null)
.ifPresent(x -> System.out.println(x.getId()));
}
public boolean equals(Object obj) {
// 同一对象判断
if (this == obj) {
return true;
}
// 类型判断
if (!(obj instanceof Optional)) {
return false;
}
Optional<?> other = (Optional<?>) obj;
// 最终还是值的判断
return Objects.equals(value, other.value);
}
public int hashCode() {
// 直接返回值的hashCode
return Objects.hashCode(value);
}
public String toString() {
return value != null
? String.format("Optional[%s]", value) // 用到了值的toString结果
: "Optional.empty";
}