Lambda,响应式编程从0到1

248 阅读6分钟

Lambda,响应式编程从0到1

前沿

随着jdk1.8的流行广泛的使用和流行,这里面的lambda语法也变的火了起来,使用lambda大大简化了代码的编写,而且让编码也变的美化了起来。这个格式不是必须的,但是大多数人在用,你不用你就out了。你能多用绝对加分项,这个算是查漏补缺,如果你需要也可以借鉴一下。

初体验

这里主要是做个对比,和之前的不用lambda表达式,接下来的所有的方式实现的东西是一样的

准备

1.接口Factory

public interface Factory {
    public Worker getWorker();
}

2.实体类Worker

package com.duncan.demo;
​
public class Worker {
    private int id;
    private String name;
​
    public Worker() {
​
    }
    public Worker(int id,String name) {
        this.id = id;
        this.name = name;
    }
    public void setId(int id) {
        this.id = id;
    }
​
    public int getId() {
        return id;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    @Override
    public String toString() {
        return " Worker{" +
                "id=" + id +
                ", name='" + name + ''' +
                '}';
    }
}
​

3.接口实现(主要是为了实例化调用用的)

package com.duncan.demo;
​
public class WorkerImpl implements Factory{
​
    @Override
    public Worker getWorker() {
        return new Worker();
    }
}

对比测试

  • 接口实例化调用
  /**
   * 1. 具体的类实现
   */
Factory factory = new WorkerImpl();
System.out.println("具体类的实现: " + factory.getWorker());
  • 内部类实现
/**
 * 2. 内部类的实现
 */
Factory factory1 = new Factory() {
  @Override
  public Worker getWorker() {
    return new Worker(1, "sun");
  }
};
System.out.println("内部类的实现: " + factory1.getWorker());
  • Lambda
/**
 * 3. lambda的实现
 */
Factory factory2 = () -> {
  return new Worker(2, "xu");
};
System.out.println("lambda的实现: " + factory2.getWorker());

结果

image-20220818153904265.png

初步观察总结

  • 从上面的代码可以看出这个lambda必须要有一个接口定义,
  • 实际上可以看出它就是一个实现类,这个可以当作参数传递,和结果传递
  • 这个接口对外开放的函数就只有一个,因为这边没有办法写两个()

语法

  • 基本语法
(param...) -> {statement;}
  • 特殊的情况
// 1.假如只有一个参数的情况可以省略()
msg -> {System.out.println(msg);}
​
// 2. 假如只有一个语句,那可以省略 {}
msg -> System.out.println(msg);
​
// 3. 如果是简单的表达式可以省略return
(int a, int b) -> a + b;
​
// 4. 参数也可以省略类型
(a, b) -> a + b;
​
// 为什么这边有的没有类型也行,不是说java是强类型的语言吗?其实这边并没有违背这个,这边因为lambda必须要去实现interface,所以参数的类型是可以根据interface中定义的来的
  • 注解
@FunctionalInterface
// 这个注解表明这个接口是函数式接口,也可以用来判断自己的函数式接口写的是否正确

常见函数式编程练习

Runnable

这个是常见的多线程定义的方式,实现的接口是 run

  new Thread(() -> {
       System.out.println(Thread.currentThread().getName() + "开始了~");
  }).start();

Callable

这个是另外的多线程实现的方式,实现的接口是 call

Callable<Object> callable = ()->{
    // 这边在做测试
    return null;
};

Supplier

这个接口其实主要是定义了一个规范,可以由名字看出,这里的Supplier实现的是一个提供某一个规则的数据,实现的是get

具体的实例: 找出数组中的最小值 (lambda),作为值传递

int[] numbers = {98,9,8,0,7,6,0,92};
int min = getMin(() -> {
  int temp = numbers[0];
  for(int num : numbers) {
    if (num < temp) {
      temp = num;
    }
  }
  return temp;
});
​
System.out.println(min);
​
​
public static int getMin(Supplier<Integer> supplier) {
   return supplier.get();
}

Consumer

这个接口和上面的Supplier是相对应的,主要用来接受数据,然后做处理,实现的是accept

calConsumer((a) -> {
  System.out.println(a);
}, 2);
​
int b = 1;
calConsumerAndThen((a) -> {
  System.out.println("then1");
},(a) -> {
  System.out.println("then2");
}, b);
​
public static void calConsumer(Consumer<Integer> a, int c) {
        a.accept(c);
}
​
public static void calConsumerAndThen(Consumer<Integer> a, Consumer<Integer> b, int c) {
  a.andThen(b).accept(c);
}

Comparator

这个是比较常见的比较函数,具体用法如下,实现的是compare

Comparator<String> c = (a, b) -> {
  return a.length() - b.length();
};
​
String[] strs = {"abc","asdasdas","ss","s"};
Arrays.sort(strs, c);
System.out.println(Arrays.toString(strs));

Predicate

这个是断言的接口,主要可以用来判断两个对象的关系,与或非, 实现的是test

Predicate<String> predicate = (name) -> {
  return name.contains("sun");
};
System.out.println(predicate.test("sunmaoyun"));  // true
System.out.println(predicate.and((String name) -> {
  return name.equals("s");
}).test("sunmaoyun"));                            // false
System.out.println(predicate.or((String name) -> {
  return name.equals("s");
}).test("sunmaoyun"));                            // true
System.out.println(predicate.negate().test("sunmaoyun")); // false

Function

这个是功能函数,每个实现类可以实现一个功能. 实现的是 apply

Function<Integer,Integer> function = (str) -> {
  return str + 1;
};
​
System.out.println(function.apply(1));

方法引用

从上面的体验我们可以看出来lambda是为了简化内部类的实现,那么方法引用就是为了简化lambda

假如你的lambda的对象要实现的方法在别的类已经实现了,可以直接用别人实现好的

举个简单的例子,使用方法:

  • Object::method
  • 构造方法 Object::new
  • 本类的普通方法: this::method, 父类的普通方法: super::method

这边注意参数要相同,返回结果兼容

// main函数中实现的是同一个东西,打印的结果都是Duncan
public class TestDemo {
    public static void main(String[] args) {
        printInfoByLambda((str) -> {
            System.out.println(str);
        });
        printInfoByLambda(System.out::println);
        printInfoByLambda(Person::new);
        printInfoByLambda(Person::print);
        // 普通方法需要先实例化
        Person person = new Person();
        printInfoByLambda(person::printInfo);
    }
  
    public static void printInfoByLambda(PrintInfo b) {
        b.print("Duncan");
    }
}
​
class Person {
    public Person() {
​
    }
    private String name;
    public Person(String name) {
        this.name = name;
        System.out.println(name);
    }
  
    public static void print(String name) {
        System.out.println(name);
    }
  
   public void printInfo(String name) {
        System.out.println(name);
    }
}
​
interface PrintInfo {
    public void print(String str);
}

Stream流

初体验

这个其实是把数据集合做了一些优化,不仅使代码的可读性变强,其效率也变高了

举个例子

/**
* 这边主要是来使用1.8的stream流,这个比普通的效率更高
* 找出姓孙的人
* 且名字的长度等于3         
*/
List<String> list = new ArrayList<>();
list.add("张三");
list.add("赵四");
list.add("孙悟空");
list.add("齐天大圣");
list.add("孙悟饭");
list.add("孙悟天");
list.add("孙俪");
/**
         * 如果说是常用的方法估计就是 for()
         */
/**
         * jdk8 可以使用流来实现
         */
list.stream().filter(name->name.startsWith("孙"))
  .filter(name -> name.length() == 3)
  .forEach(System.out::println);

结果

image-20220819151227055.png

Stream的生命周期

啥叫做中间操作呢?

先说一下这个流的生命周期

开始->中间->结束 (利用的聚合的思想)

举例:stream().filter(// 断言接口).forEach(// Consumber接口)

在这个里面filter属于中间操作,forEach()属于结束操作,如果没有结束操作的话,这个实际上是没有做操作的

Stream中间操作常用的api

  • map(mapToInt,flatMap)
  • filter
  • limit
  • skip
  • concat

Stream结束(终结操作)

  • forEach
  • count
  • collect
  • min
  • max

简单的使用

如果遇到集合的话,可以建议你使用这种流的方式

  1. 获取流
/**
 * 数组获取 用 Stream.of
 */
Integer[] numbers = {1,2,3};
Stream<Integer> stream1 = Stream.of(numbers);
stream1.forEach(System.out::print);
​
System.out.println();
/**
 * map集合获取
 */
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
// 1. 通过key获取
Stream<String> stream2 = map.keySet().stream();
stream2.forEach(System.out::print);
System.out.println();
​
// 2. 通过value获取
Stream<Integer> stream3 = map.values().stream();
stream3.forEach(System.out::print);
​
System.out.println();
// 3. 通过Entry获取
Stream<Map.Entry<String, Integer>> stream4 = map.entrySet().stream();
stream4.forEach(System.out::print);
  1. map的使用,这个map其实实现的接口是 Function的接口
// 比如这边可以实现一个加密的操作,将数字转化为相应的小写字母,超过26的数字,用取余作映射
 Integer[] numbers = {1,2,26, 3,28,29, 23, 30, 10, 27};
Stream<Integer> stream1 = Stream.of(numbers);
stream1.map((a) -> {
  return (char)('a' - 1 + ((a % 26) == 0 ? 26 : a % 26));
}).forEach((str) -> {
  System.out.print(str + " ");
});
​
// 结果
a b z c b c w d j a

3.collect: 这个可以是操作这个集合然后再返回一个集合

/**
  * 找出这个数组字符串中男的元素
  * 并且按照 name -> age的方式存到一个map中
  */
String[] strs = {"sun,男,26", "xu,女,24", "zhao,男,89", "qian,女,18"};
Stream<String> stream5 = Stream.of(strs);
Map<String, Integer> mapChange = stream5.filter(str -> str.split(",")[1].equals("男"))
  .collect(Collectors.toMap(
    k -> k.split(",")[0], 
    v -> Integer.valueOf(v.split(",")[2])
  )
);
mapChange.forEach((k,v) -> {
  System.out.println(k + "->" + v);
});

4.skip,这个是跳过前面几个元素

Integer[] numbers = {1,2,26, 3,28,29, 23, 30, 10, 27};
Stream<Integer> stream1 = Stream.of(numbers);
stream1.skip(6).forEach(str -> System.out.print(str + " "));
​
// 结果
23 30 10 27 

5.concat 连接2个流

List<String> list = new ArrayList<>();
list.add("张三");
list.add("赵四");
List<String> list1 = new ArrayList<>();
list1.add("孙悟空");
list1.add("齐天大圣");
list1.add("孙悟饭");
​
Stream.concat(list.stream(), list1.stream()).forEach(str -> System.out.print(str + " "));

最后的话

以上就是lambda的常见用法和应用,弄完以后其实也发现了代码的魅力,有的人写的代码就是好看,我们需要往这些人靠齐。

那什么时候用最好呢?

  • 如果你的集合数据量特别大,然后还要做一些操作
  • 如果定义的接口只有一个抽象方法的时候,可以使用,简化代码

最后一句,再好的东西只有自己多用了,熟练了才是好的。