Java技术栈之JDK优雅编程特性探索与实战
链式编程
概述
JDK链式编程优点主要是编程性强 、可读性强 、代码简洁。链式编程的原理就是返回一个this对象,也即是返回本身以达到链式效果。比如JDK的StringBuilder就是实现链式编程效果。
StringBuilder builder = new StringBuilder();
builder.append("aa").append("bb").append("cc").append("dd").append("ee");
代码示例
自定义实现
学生类Student.java
package cn.itxs.chain;
public class Student {
private String name;
private int age;
private String address;
public String getName() {
return name;
}
public Student setName(String name) {
this.name = name;
return this;
}
public int getAge() {
return age;
}
public Student setAge(int age) {
this.age = age;
return this;
}
public String getAddress() {
return address;
}
public Student setAddress(String address) {
this.address = address;
return this;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
测试类
package cn.itxs.chain;
public class ChainMain {
public static void main(String[] args) {
Student student = new Student();
student.setName("李珊珊").setAge(20).setAddress("北京市朝阳区");
System.out.println(student.toString());
}
}
Lombok使用
pom文件加入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
用户类User.java
package cn.itxs.other;
import com.sun.istack.internal.NotNull;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
@Accessors(chain = true)
@Data
@RequiredArgsConstructor(staticName = "of")
public class User {
@NotNull
private String name;
private int age;
}
测试类
package cn.itxs.other;
public class UMain {
public static void main(String[] args) {
User user = User.of("陈水水").setAge(25);
System.out.println(user.toString());
}
}
Lambada表达式
概述
- Lambda表达式是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码;Lambada表达式是JDK8的一个新特性,避免匿名内部类定义过多;去掉一堆没有意义的代码,只留下核心的逻辑,专注于逻辑实现;可以写出更简洁、更灵活、更优雅的Java代码。
- Lambda表达式非常简化因此必须在使用在特殊的上下文环境中,这样编译器才能推断出Lambda表达式到底是哪个方法和计算出Lambda表达式的值,Lambda表达式的值就是方法的入口地址,因此Lambda只能针对只有一个抽象方法的接口实现。
- 函数式接口是指有且仅有一个抽象方法的接口;Java8引入了注解@FunctionalInterface修饰和检查函数式接口。
- 当接口多重继承时,可能会发生默认方法覆盖的问题,这时可以指定使用哪一个接口的默认方法实现。
- 基础语法
- 在Java8中引入新的操作符“->”,该操作符称为箭头操作符或Lambda操作符;箭头操作符将Lambda表达式拆分成左右两部分。
- 左侧:Lambda表达式的参数列表
- 若只有一个参数左侧小括号可以不写,但是基于可读性建议还是带上小括号。
- Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出数据类型即“类型推断”(语法糖);如果要写数据类型就得全写。
- 右侧:Lambda表达式中所需要执行的功能,即Lambda体;右侧只有一条语句大括号{}和return可以省略。有下列三种形式
- 无参数,无返回值
- 有参数,无返回值
- 有参数,有返回值
- 方法的引用
- Lambda主体只有一条语句时可以通过英文“ :: ”来引用方法和构造器。
代码示例
线程调用
在Java还不支持lambda表达式时,我们需要创建一个线程的话需要很多行代码,使用lambda表达式一句代码就能完成线程的创建,Runnable是函数式接口。
new Thread(() -> System.out.println("hello lambada thread demo")).start();
自定义函数式接口
JDK8接口支持默认实现方法,之前没有默认方法的特性修改接口代码带来的影响非常大的;函数接口特性可以提供默认实现
自定义函数式接口:OperateInterface
package cn.itxs.lambada;
@FunctionalInterface
public interface OperateInterface {
int caculate(int num1,int num2,int num3);
default int subtract(int num1, int num2) {
return num1 -num2;
}
}
测试类
package cn.itxs.lambada;
public class LambadaMain {
private static void printDemo(int num1,int num2,int num3, OperateInterface operateInterface) {
System.out.println(operateInterface.caculate(num1, num2, num3));
}
private static void printDescription(String description, UserBuilder userBuilder) {
System.out.println(userBuilder.build(description).getDescription());
}
public static void main(String[] args) {
//直接实现的lambda表达式
printDemo(10,20,30,(int num1,int num2,int num3) -> (num1 + num2) * num3);
//类静态方法lambda表达式
printDemo(10,20,30,(int num1,int num2,int num3) -> Operater.muti(10,20,30));
//类静态方法引用
printDemo(10,20,30,Operater::muti);
Operater operater = new Operater();
//对象方法lambda表达式
printDemo(10,20,30,(int num1,int num2,int num3) -> operater.muti2(10,20,30));
//对象方法引用
printDemo(10,20,30,operater::muti2);
printDescription("构造器引用测试lambda表达式",description -> new User(description));
printDescription("构造器引用测试方法引用",User::new);
}
}
class Operater {
public static int muti(int num1, int num2, int num3){
return num1 * num2 * num3;
}
public int muti2(int num1,int num2,int num3){
return num1 * num2 - num3;
}
}
@FunctionalInterface
interface UserBuilder{
User build(String description);
}
class User{
private String description;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public User(String description) {
this.description = description;
}
}
常用函数式接口
概述
- JDK提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效;Lambda表达式是函数式编程的基础。
- Jdk8的java.util.function包提供了常用的函数式功能接口。
Predicate接口
Predicate是断言函数接口,接收参数对象T,返回一个boolean类型结果,在JDK中函数接口代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y2GRfqz0-1642690079878)(image-20220119164339846.png)]
测试类
package cn.itxs.func;
import java.util.function.Predicate;
public class FunctionalMain {
public static void main(String[] args) {
Predicate<Integer> predicate = i -> {
System.out.println("断言函数式接口测试,输入值:" + i);
return i > 100;
};
System.out.println(predicate.test(100));
System.out.println(predicate.test(101));
Predicate<String> predicateStr = str -> {
System.out.println("断言函数式接口测试,输入值:" + str);
return "true".equals(str);};
System.out.println(predicateStr.test("true"));
System.out.println(predicateStr.test("haha"));
//接口一般有对基本类型的封装,使用特定类型的接口就不需要去指定泛型了,如LongPredicate、DoublePredicate、IntConsumer等
LongPredicate longPredicate = i -> {
System.out.println("长整型断言函数式接口测试,输入值:" + i);
return i > 100000;
};
System.out.println(longPredicate.test(100000));
System.out.println(longPredicate.test(100001));
}
}
Supplier和Consumer接口
- Supplier供应者函数接口:不接收参数,提供T对象的创建工厂
- Consumer消费者函数接口:接收一个T类型的参数,不返回任何结果
@FunctionalInterface
public interface Supplier<T> {
T get();
}
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
测试代码
package cn.itxs.func;
import java.util.Random;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class FunctionalMain {
public static void main(String[] args) {
Supplier<Integer> supplier = () -> {
System.out.println("供应者函数式接口测试,随机生成100以内整形值");
return new Random().nextInt(100);
};
System.out.println(supplier.get());
Consumer<String> consumer = (message) -> {
System.out.println("消费者函数式接口测试,收到消息" + message);
};
consumer.accept("hello java");
}
}
Function和BiFunction接口
- Function接口接收参数对象T,返回结果对象R。
- BiFunction接口只是比Function接口多了一个输入而已。
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
测试代码
package cn.itxs.func;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiFunction;
import java.util.function.Function;
public class FunctionalMain {
public static void main(String[] args) {
Function<Integer, List<Integer>> function = (i) ->{
System.out.println("Function函数式接口测试,输入整数,返回3个复制值的集合");
List<Integer> listInt = new CopyOnWriteArrayList<>();
listInt.add(i);
listInt.add(i);
listInt.add(i);
return listInt;
};
List<Integer> list1 = function.apply(100);
System.out.println(list1);
BiFunction<Integer,Integer,List<String>> biFunction = (i1, i2) -> {
System.out.println("BiFunction函数式接口测试,输入连个整数,返回两数之和的字符串集合");
List<String> listStr = new ArrayList<>();
listStr.add(String.valueOf(i1+i2));
return listStr;
};
List<String> list2 = biFunction.apply(100001,200001);
System.out.println(list2);
}
}
UnaryOperator和BinaryOperator接口
- UnaryOperator接口接收参数对象T,返回结果对象T。
- BinaryOperator接口之比UnaryOperator接口多增加一个接收参数对象。接收两个T对象,返回一个T对象结果。
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
}
public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}
}
测试类
package cn.itxs.func;
import java.util.function.BinaryOperator;
import java.util.function.UnaryOperator;
public class FunctionalMain {
public static void main(String[] args) {
UnaryOperator<Integer> unaryOperator = (i) -> {
System.out.println("unaryOperator函数式接口测试,输入整数,返回整数+1");
return i+1;
};
System.out.println(unaryOperator.apply(100));
BinaryOperator<String> binaryOperator = (str1,str2) -> {
System.out.println("binaryOperator函数式接口测试,输入两个字符串,返回两串拼接");
return str1 + str2;
};
System.out.println(binaryOperator.apply("好事", "成双"));
}
}
Optional
概述
Optional对所有方法的参数使用optional进行包裹,避免空指针。
基本使用
package cn.itxs.optional;
import java.util.Optional;
public class OptionalMain {
public static void main(String[] args) {
// 第三种方式 通过empty方法直接创建一个空的Optional对象
Optional<Object> option = Optional.empty();
//不允许包裹为null的对象,否则程序报错
//Optional op = Optional.of(null);
//允许包裹对象为nul,为null时返回empty Optional
Optional op = Optional.ofNullable(null);
System.out.println(op);
//当使用get()获取容器中的对象时,如果对象为null,会有java.util.NoSuchElementException异常。所以最好先进行isPresent()判断,如果返回true,说明存在,然后再获取
//op.get();
//判断两个Optional是否相等,主要是所包裹的对象是否相等
Optional op1 = Optional.of("java");
Optional op2 = Optional.ofNullable("java");
System.out.println(op1.equals(op2));
op2 = Optional.of("java1");
System.out.println(op1.equals(op2));
//如果值存在并且满足断言,则返回满足条件的Optional,否则返回empty,经常用来做过滤.
Optional<String> op3 = Optional.of("itxiaoshen");
Optional r1 = op3.filter((str)-> str.length() > 15);
System.out.println(r1);
//如果值存在则对其进行mapping函数操作,如果map结果不为空则返回Optional,否则返回empty.
Optional r2 = op3.map((str) -> "hello->" + str);
System.out.println(r2);
//类似上面map,只不过需要在mapp函数中将自己封装成Optional。
Optional res = op3.flatMap((str) -> Optional.ofNullable("hello->"+str));
System.out.println(res);
//值存在则返回,否则返回其它值,相当于给默认值。
Optional<String> op4 = Optional.ofNullable(null);
System.out.println(op4.orElse("haha"));
op4 = Optional.ofNullable("hello");
System.out.println(op4.orElse("haha"));
//功能与上面orElse类似,不同在于它能使用lambda表达式更灵活的处理返回的默认值
Optional<String> op5 = Optional.ofNullable(null);
System.out.println(op5.orElseGet(() -> String.valueOf(true)));
//如果值存在,则执行lambda表达式,否则不做任何处理,它没有返回值。
Optional<String> op6 = Optional.ofNullable("itxiaoshen");
op6.ifPresent((str) -> System.out.println(str));
System.out.println("分隔输出------------");
op6 = Optional.ofNullable(null);
op6.ifPresent((str) -> System.out.println(str));
}
}
应用例子
有班类Squad、排类Platoon、连类Company
package cn.itxs.optional;
public class Squad {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package cn.itxs.optional;
public class Platoon {
private Squad squad;
public Squad getSquad() {
return squad;
}
public void setSquad(Squad squad) {
this.squad = squad;
}
}
package cn.itxs.optional;
public class Company {
private Platoon platoon;
public Platoon getPlatoon() {
return platoon;
}
public void setPlatoon(Platoon platoon) {
this.platoon = platoon;
}
}
测试类
package cn.itxs.optional;
import java.util.Optional;
public class OptionalDemo {
public static void main(String[] args) {
//原来获取Squad的name值需要多层if语句
Company company = new Company();
String name = null;
if (null != company){
if (null != company.getPlatoon()){
if (null != company.getPlatoon().getSquad()){
name = company.getPlatoon().getSquad().getName();
}
}
}
name = Optional.ofNullable(company)
.map(c -> c.getPlatoon())
.map(p -> p.getSquad())
.map(s -> s.getName())
.orElse("我是一个团");
System.out.println(name);
}
}
流式编程
概述
- 流式编程是1.8中的新特性,基于常用的四种函数式接口以及Lambda表达式对集合类数据进行类似流水线一般的操作。使用stream流式编程不仅可以简化代码开发,增强可读性,还可以在不修改原来集合的基础上对数据进行过滤、映射等操作。
- 流式编程分为大概三个步骤:
获取流 → 操作流 → 返回操作结果
。- 获取数据源,将数据源中的数据读取到流中。
- 对流中的数据进行各种各样的操作,并且返回流对象本身,这样的操作,被称为 -- 中间操作。
- 可以对流中的数据进行各种处理,并关闭流,这样的操作,被称为 -- 终止操作。
- 在中间操作和最终操作中,基本上所有的方法参数都是函数式接口,可以使用lambda表达式来实现。使用集合的流式编程,来简化代码量,是需要对 lambda 表达式做到熟练掌握。
- 在java.util.stream包下(java.util.stream.Stream ),但它并不是一个函数式接口,接口里面包含了很多抽象方法,这些方法对于Stream流的操作都是至关重要的,最常见的方法有filter、map、foreach、count、limit、skip、concat。
- Stream是对集合操作的增强,流不是集合的元素,不是一种数据结构,不负责数据的存储的。流更像是一个迭代器,可以单向的遍历一个集合中的每一个元素,并且不可循环。
- 使用场景
- 有些时候,对集合中的元素进行操作的时候,需要使用到其他操作的结果。在这个过程中,集合的流式编程可以大幅度的简化代码的数量。将数据源中的数据,读取到一个流中,可以对这个流中的数据进行操作(删除、过滤、映射...)。每次的操作结果也是一个流对象,可以对这个流再进行其他的操作。
代码示例
流的创建
package cn.itxs.stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.stream.Stream;
public class StreamMain {
public static void main(String[] args) {
//集合中获取流,Java中所有的集合都是Collection下的实现类,在Collection接口中就提供了获取流的方法
List<Integer> list1 = new ArrayList<>();
Stream<Integer> stream1 = list1.stream(); // 获取流
Stream<Integer> stream2 = list1.parallelStream(); // 获取流(多线程,大数据量下效率较高)
//数组中获取流,数组Java中提供了一个Arrays工具类,我们可以将数组转换为集合在获取流
Integer[] arr = {1, 2, 3, 4, 5};
List<Integer> list2 = Arrays.asList(arr);
list2.forEach(str->System.out.print(str+";")); //lambda的写法打印输出list数组元素
Stream<Integer> stream3 = list2.stream();
Stream<Integer> stream4 = list2.parallelStream();
Stream<Integer> stream5 = Arrays.stream(arr); //直接通过Arrays类获取到流
Stream<Integer> stream6 = Stream.of(1, 2, 3, 4, 5); //Stream接口的静态方法of可以获取数组对应的流,of 方法的参数其实是一个可变参数,所以也支持数组.
Stream<Integer> stream7 = Stream.of(arr);
Stream<Integer> stream8 = Stream.generate(() -> new Random().nextInt(10)); //generate方法返回一个无限连续的无序流,其中每个元素由提供的供应商(Supplier)生成,generate方法用于生成常量流和随机元素流.
Stream<Integer> stream9 = Stream.generate(() -> new Random().nextInt(10)).limit(3); //generate返回无限连续流,为了限制流中元素的数量,我们可以使用Stream.limit方法
Stream<Integer> stream10 = Stream.iterate(0, i -> i + 2).limit(5); //指定一个常量seed,生成从seed到常量f(由UnaryOperator返回的值得到)的流,根据起始值seed(0),每次生成一个指定递增值(i+2)的数,limit(5)用于截断流的长度,即只获取前5个元素。
}
}
流的操作
package cn.itxs.stream;
import java.util.ArrayList;
public class StreamMain {
public static void main(String[] args) {
ArrayList<Employee> list = new ArrayList<>();
list.add(new Employee("张三", 32, '男', 8000));
list.add(new Employee("李亭", 36, '女', 7000));
list.add(new Employee("张三", 32, '男', 8000));
list.add(new Employee("冠华", 32, '男', 18000));
list.add(new Employee("李五", 22, '男', 2800));
list.add(new Employee("张海东", 34, '男', 12000));
list.add(new Employee("张安福", 32, '男', 8000));
list.add(new Employee("贾怡", 22, '女', 21000));
list.add(new Employee("方淑贞", 26, '女', 14800));
list.add(new Employee("黄史蒂", 37, '男', 26300));
System.out.println("过滤------");
//filter 过滤方法
list.stream()
.filter(e -> e.getAge() > 32)
.forEach(System.out::println);
System.out.println("截取------");
//只获取结果中的5条数据(从首个开始截取)
list.stream()
.limit(3)
.forEach(System.out::println);
System.out.println("跳过------");
//这里就跳过了2个元素
list.stream()
.skip(7)
.forEach(System.out::println);
System.out.println("去重------");
// 去重操作,两个张三对象保留一个
list.stream()
.distinct()
.forEach(System.out::println);
System.out.println("排序------");
// 通过薪水对集合进行排序
list.stream()
.sorted((e1, e2)->Integer.compare(e1.getSalary(), e2.getSalary()))
.forEach(System.out::println);
System.out.println("转换------");
// 通过map返回每个用户的姓名,将用户集合改变成了用户姓名集合
list.stream()
.map(Employee::getName)
.forEach(System.out::println);
}
}
返回操作结果
上面小节使用的forEach
就属于返回结果的代码,如果只调用了filter
方法而没有调用返回结果,那么filter
方法是不会执行的。
package cn.itxs.stream;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamMain1 {
public static void main(String[] args) {
ArrayList<Employee> list = new ArrayList<>();
list.add(new Employee("张三", 32, '男', 8000));
list.add(new Employee("李亭", 36, '女', 7000));
list.add(new Employee("张三", 32, '男', 8000));
list.add(new Employee("冠华", 32, '男', 18000));
list.add(new Employee("李五", 22, '男', 2800));
list.add(new Employee("张海东", 34, '男', 12000));
list.add(new Employee("张安福", 32, '男', 8000));
list.add(new Employee("贾怡", 22, '女', 21000));
list.add(new Employee("方淑贞", 26, '女', 14800));
list.add(new Employee("方淑贞", 26, '女', 14800));
list.add(new Employee("黄史蒂", 37, '男', 26300));
//取最小值
Employee employee = list.stream()
.filter(item -> item.getSalary() > 8000)
.min(Comparator.comparingInt(Employee::getSalary))
// 在get一下才能获取到实体类
.get();
System.out.println(employee);
//取最大值
employee = list.stream()
.filter(item -> item.getSalary() > 8000)
.max(Comparator.comparingInt(Employee::getSalary))
.get();
System.out.println(employee);
//对结果进行计数
long count = list.stream()
.filter(item -> item.getSalary() > 10000)
.count();
System.out.println(count);
//返回操作结果
List<Employee> collect = list.stream()
.filter(item -> item.getSalary() > 10000)
// 直接调用collect方法,然后调用toList将结果转换为List集合
.collect(Collectors.toList());
System.out.println(collect);
//组合两个流
Stream<Integer> stream1 = Stream.of(11,22);
Stream<Integer> stream2 = Stream.of(33,44);
Stream<Integer> stream3 = Stream.concat(stream1, stream2);
stream3.forEach(s->System.out.println(s));
//综合例子
List<String> result = list.stream()
// 找到所有女性员工
.filter(item->item.getGender()=='女')
// 去除重复数据
.distinct()
// 按照年龄进行排序
.sorted(Comparator.comparingInt(Employee::getAge))
// 提取名字
.map(Employee::getName)
// 转换为List集合
.collect(Collectors.toList());
// 打印查看效果
System.out.println(result);
}
}
**本人博客网站 **IT小神 www.itxiaoshen.com