zl程序教程

您现在的位置是:首页 >  Java

当前栏目

Java技术栈之JDK优雅编程特性探索与实战

2023-02-18 16:40:36 时间

链式编程

概述

JDK链式编程优点主要是编程性强 、可读性强 、代码简洁。链式编程的原理就是返回一个this对象,也即是返回本身以达到链式效果。比如JDK的StringBuilder就是实现链式编程效果。

StringBuilder builder = new StringBuilder();
builder.append("aa").append("bb").append("cc").append("dd").append("ee");

image-20220118163204043

代码示例

自定义实现

学生类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主体只有一条语句时可以通过英文“ :: ”来引用方法和构造器。

image-20220118172128148

代码示例

线程调用

在Java还不支持lambda表达式时,我们需要创建一个线程的话需要很多行代码,使用lambda表达式一句代码就能完成线程的创建,Runnable是函数式接口。

new Thread(() -> System.out.println("hello lambada thread demo")).start();

image-20220118175642245

自定义函数式接口

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;
    }
}

image-20220118185832982

常用函数式接口

概述

  • 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));
    }
}

image-20220119165604560

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);
    }
}

image-20220119185840132

流式编程

概述

  • 流式编程是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