Java8实战

简介: Java8实战

应对不断变化得需求

初始牛刀:删选绿苹果

public static List<Apple> filetGreenApples (List<Apple> inventory){
    //累积苹果得列表
    ArrayList<Apple> objects = new ArrayList<>();
    for (Apple apple : inventory) {
        if(apple.getColor().equals("green")){//仅仅选出绿苹果
            objects.add(apple);
        }
    }
    return objects;
}

但是农民伯伯改变注意了,他还想要删选红苹果。应该怎么做?

简单得解决办法就是复制这个方法,把名字改为filterRedApples,然后更改if条件来匹配红苹果。然而农民伯伯想要删选更多得颜色,这种方法就应付不了了。一个良好的原则是在编写类似得代码后,尝试将其抽象化。

再展身手:将颜色作为参数

一种做法就是给方法添加一个参数,把颜色作为参数,这样就要可以灵活得变化了。

ArrayList<Apple> objects = new ArrayList<>();
for (Apple apple : inventory) {
    if(apple.getColor().equals(color)){
        objects.add(apple);
    }
}
return objects;

现在只需要向下面一样调用方法,农民伯伯就饿很满意了:

filetApplesByColor(inventory,"green");
filetApplesByColor(inventory,"red");

那这个时候农名伯伯又要能区分苹果得重量,重得苹果大于150g 这时候怎么办呢?

public static List<Apple> filetApplesByWeight (List<Apple> inventory,Long weight){
    //累积苹果得列表
    ArrayList<Apple> objects = new ArrayList<>();
    for (Apple apple : inventory) {
        if(apple.getWeight()>weight){
            objects.add(apple);
        }
    }
    return objects;
}

上面代码解决方案是可以的,但是复制了大部分代码来实现遍历库存,并对每个苹果应用筛选条件。打破了DRY的软件工程原则。如果想改变筛选遍历方式来提升性能,那就要修改所有方法的实现,而不是只改一个。

第三次尝试:对能想到的属性做筛选

一种就是最笨拙的方式:把所有属性结合起来

public static List<Apple> filetApples (List<Apple> inventory,Long weight,String color){
    //累积苹果得列表
    ArrayList<Apple> objects = new ArrayList<>();
    for (Apple apple : inventory) {
        if(apple.getWeight()>weight && apple.getColor().equals(color)){
            objects.add(apple);
        }
    }
    return objects;
}

这个解决方案还是没有能够很好的应对变化的需求。

行为参数化

需要一种比添加很多参数更好的解决办法来应对变化的需求,比如:对选择标准建模,考虑的是苹果,需要根据Apple的某些属性(颜色是绿色吗?,重量大于150g吗?)来返回一个Boolean值。我们把它称为谓词(就是一个返回Boolean的函数)。

首先定义一个接口:

public interface ApplePredicate {
    boolean test (Apple apple);
}

现在就可以根据ApplePredicate的不多个实现来代表不同的选择标准了

public class ApplePredicateColor implements ApplePredicate {
    @Override
    public boolean test(Apple apple) {
        return apple.getColor().equals("green");
    }
}
public class ApplePredicateWeight implements ApplePredicate {
    @Override
    public boolean test(Apple apple) {
        return apple.getWeight()>150;
    }
}

可以把这些标准看作filter方法的不同行为,定义一族算法,把他们封装起来(称为策略),然后再运行时选择一个算法。算法族就是 ApplePredicate,不同的策略就是ApplePredicateColor和ApplePredicateWeight。

但是,该怎么利用Apple Predicate的不同实现呢? 需要filterApples方法接受Apple Predicate对象,对Apple做条件测试,这就是行为参数化:让方法接受多种行为作为参数,并在内部使用,来完成不同的行为。

第四次尝试:根据抽象条件筛选

利用ApplePredicate改过之后,filter方法是这样的:

public static List<Apple> filetApples (List<Apple> inventory,ApplePredicate applePredicate){
    //累积苹果得列表
    ArrayList<Apple> objects = new ArrayList<>();
    for (Apple apple : inventory) {
        if(applePredicate.test(apple)){ //谓词对象封装了测试苹果的条件
            objects.add(apple);
        }
    }
    return objects;
}

庆祝一下,这样的话代码就灵活多了,读起来用起来也很方便!现在只需要创建不同的Apple Predicate对象,并将它们传递给filter Apples方法。比如,如果需要找出所有重量超过150g的红苹果,只需要创建一个类来实现ApplePredicate就行了。代码足够灵活,可以应对任何涉及苹果属性的需求变更了:

public class AppleRedAndHeavyPredicate implements ApplePredicate {
    @Override
    public boolean test(Apple apple) {
        return apple.getWeight()>150 && apple.getColor().equals("red");
    }
}
 public static List<Apple> filterApples (List<Apple> inventory, AppleRedAndHeavyPredicate appleRedAndHeavyPredicate){
     
 }

filterApples方法的行为取决于你通过ApplePredicate对象传递的代码。也就是说把 filterApples方法的行为参数化了。

但是,由于该filterApples方法只能接受对象,所以必须把代码包裹在ApplePredicate对象里。通过一个实现了test方法的对象来传递布尔表达式的。

使用匿名内部类

下面的代码显示了如何通过创建一个匿名内部类实现ApplePredicate的对象,重写筛选的例子;

filterApples(inventory, new ApplePredicate() {
    @Override
    public boolean test(Apple apple) {
        return apple.getWeight()>150;
    }
});

使用匿名内部类的缺点:往往很笨重,占用了很多空间。用起来让人费解

使用Lambda表达式

List<Apple> apples = filterApples(inventory, (Apple apple) -> apple.getColor().equals("red"));

代码看上去干净很多,看起来更像问题陈述本身了,已经解决了啰嗦的问题。

将List类型抽象化

目前,filterApples方法还是只适用于Apple。可以将List类型抽象化,从而超越你眼前要处理的问题。

public interface Predicate<T> {
    boolean test (T t);
}
public  static <T>List<T> filter(List<T>list , Predicate<T> p){
        ArrayList<T> objects = new ArrayList<>();
        for (T t : list) {
            if(p.test(t)){
                objects.add(t);
            }
        }
        return objects;
    }

现在可以把filter方法用在香蕉、橘子、Integer或String的列表上了。下面有一个例子。

小结

Lambda表达式

定义;简洁的表示可传递匿名函数的一种方式,他没有名称,但是有参数列表、返回类型、可能还有一个 可以抛出的异常列表。

组成:参数列表  箭头  Lambda主体

函数式接口

predicate:

@FunctionalInterface
public interface Predicate<T> {
    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);

此接口定义了一个名叫test的抽象方法。接受泛型T对象,并且返回一个boolean。如果需要表示涉及类型T的不然表达式时可以使用。

String p ="周杰伦轮";
Predicate<String> predicate = name-> name.length()>3;
boolean test = predicate.test(p);
System.out.println(test);

组合使用Predicate

Predicate.and()

两个条件都要满足;

List<String> names = Arrays.asList("Adam", "Alexander", "John", "Tom");
Predicate<String> predicate1 = str -> str.startsWith("A");
        Predicate<String> predicate2 = str -> str.length() < 5;
        List<String> result = names.stream()
                                   .filter(predicate1.and(predicate2))
                                   .collect(Collectors.toList());
Predicate.or()

满足一个就可以

List<String> names = Arrays.asList("Adam", "Alexander", "John", "Tom");
Predicate<String> predicate3 = str -> str.startsWith("J");
        Predicate<String> predicate4 = str -> str.length() < 4;
        List<String> result2 = names.stream()
                                    .filter(predicate3.or(predicate4))
                                    .collect(Collectors.toList());
Predicate.negate()

将此条件取反

Predicate predicate2 =  str -> str.length() < 4;

相当于

Predicate predicate2 =  str -> str.length() >= 4;

Predicate<String> predicate1 = str -> str.startsWith("J");
Predicate<String> predicate2 = str -> str.length() < 4;
List<String> result = names.stream()
                           .filter(predicate1.or(predicate2.negate()))
                           .collect(Collectors.toList());
内联的方式组合使用Predicates
List<String> result = names.stream()
.filter(((Predicate<String>)name -> name.startsWith("A"))
.and(name -> name.length()<5))
.collect(Collectors.toList());
组合Predicates集合
List<Predicate<String>> allPredicates = new ArrayList<Predicate<String>>();
allPredicates.add(str -> str.startsWith("A"));
allPredicates.add(str -> str.contains("d"));
allPredicates.add(str -> str.length() > 4);
List<String> result = names.stream()
                           .filter(allPredicates.stream().reduce(x->true, Predicate::and))
                           .collect(Collectors.toList());

Consumer

@FunctionalInterface
public interface Consumer<T> {
    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

此接口定义了一个accept抽象方法,接受泛型T的对象,没有返回值。

比如下面代码;

Arrays.asList(1,2,3,4,5).forEach(( i)->{
            System.out.println(i);
        });
        }

Function

@FunctionalInterface
public interface Function<T, R> {
    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);

此接口定义了一个apply的方法,接受一个泛型T的对象,并且返回一个泛型R的对象。如果你要定义Lambda,将输入对象的信息映射到代码中,就可以使用这个接口。

函数类型特化

Java类型要么是引用类型,要么是原始类型。但是泛型只能绑定到引用类型。

装箱:将原始类型转换为对应的引用类型

拆箱: 将引用类型转换为对应的原始类型

Java有一个自动装箱机制:装箱拆箱操作自动完成。

但是性能方面付出代价。装箱后的值本质上就是把原始类型包裹起来,并保存在堆里。因此,装箱后的值需要更多的内存,并需要额外的内存搜索来获取被包裹的原始值。

总结

方法引用

方法引用可以重复使用现有的方法定义,并像Lambda一样传递他们,方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法。

基本思想:如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它。

构建方法引用

方法引用主要有三类

  1. 指向静态方法的方法引用(比如 Integer的parseInt方法,可以写作 Integer::parseInt)
  2. 指向任意类型实例方法的方法引用(比如 String的length方法,写作String::length)
  3. 指向现有对象的实例方法的方法引用(比如有一个局部变量 expensiveTransaction用于存放Transaction类型的对象,它支持实例方法getValue,那么就可以写成expensiveTransaction::getValue)

构造函数引用

对于一个现有构造函数,可以利用名称和关键字new来创建他的一个引用:  ClassName::new 。 功能与指向静态方法的引用类似。

比如:一个构造器函数没有参数,适合Supplier的签名()->Apple。

什么是流

1、流是JavaAPI的新成员,允许以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现),可以把他们看成便利数据集的高级迭代器。

2、流可以透明的并行处理,不需要写任何多线程代码。


下图所示:使用流来筛选菜单,找出三个高热量菜肴的名字

  • filter----接受Lambda,从流中排除某些元素。
  • map----接受Lambda,将元素转换成其他形式活提取信息。
  • limit-----截断流,使其元素不超过给定数量。
  • collect----将流转换为其他形式。

流和集合

区别:

1、集合与流之间的差异在于什么时候进行计算。集合是一个内存中的数据结构,他包含数据结构中目前所有的值---集合中的每个元素都得先算出来才能添加到集合中。(可以向集合里加或者删除东西,但是不管什么时候,集合中每个元素都是放在内存里的,元素都得先算出来才能成为集合的一部分)

2、流是在概念上固定的数据结构(不能添加或删除元素),其元素使按需计算的。流就像一个延迟创建的集合:只有在消费者要求的时候才会计算值。

3、使用Collection接口需要用户去迭代(foreach),成为外部迭代。而Stream库使用内部迭代---他帮你把迭代做了,把得到的流值存在某个地方,只需要给出一个函数说要干什么就行了。

流和迭代器类似 只能遍历一次


流操作

可以连接起来的流成为中间操作(filter、map、limit等等)

关闭流的操作成为终端操作(collect)

中间操作

中间操作会返回另一个流,让多个操作可以连接起来形成一个查询。除非流水线上触发一个终端操作,否则中间操作不会执行任何处理。


终端操作

终端操作会从流的流水线生成结果


使用流

筛选和切片

用谓词筛选

方法引用检查菜肴是否适合素食者

menu.stream().filter(Dish::isVegetarian).collect(tolist());

筛选各异的元素

筛选列表中所有的偶数,并确保没有重复

List<Integer> numbers = Arrays.asList(1, 2, 3, 1, 33, 2, 3, 3, 4);
        numbers.stream().filter(i->i%2==0).distinct().forEach(System.out::println);

截断流

选出热量超过300卡路里的头三道菜

menu.stream().filter(d->d.getCalories()>300).limit(3).collect(toList());

跳过元素

跳过超过300卡路里的头两道菜,并返回剩下的

menu.stream().filter(d->d.getCalories()>300).skip(2).collect(toList());

映射

对流中每一个元素应用函数

流支持map方法,接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(创建一个新版本,而不是去修改)。

menu.stream().map(Dish:;getName).collect(toList());

流的扁平化

给定单词列表["HELLO",],需要返回["H","E","L","L","O"]

如果用map的话:

words.stream().mao(word->word.split("").distinct.collect(toList));

这个方法的问题在于,传递给map方法的Lambda为每个单词返回一个String[]。因此,map返回的流实际上是Stream类型的。真正需要的是Stream来表示一个字符流。

List<String[]> collect = a.stream().map(b -> b.split("")).distinct().collect(Collectors.toList());
        
List<Stream<String>> streams = a.stream().map(b -> b.split("")).map(Arrays::stream).
                distinct().collect(Collectors.toList());
List<String> strings = a.stream().map(b -> b.split("")).flatMap(Arrays::stream).
                distinct().collect(Collectors.toList());

使用flashMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化成为一个流。

flatMap方法把一个流中的每个值都换成另外一个流,然后把所有的流连接成一个流。



查找和匹配

检查谓词是否至少匹配一个元素

anyMatch

查谓词是否匹配所有元素

allMatch

boolean ishealthy=  menu.stream().allmatch(d->d.getCalories()<1000);

noneMatch


查找元素

findAny

返回当前流中的任意元素。可以与其他操作结合使用。

Optional<Dish> dish=  menu.stream().filter(Dish::isVegetarian).findAny().collect(tolist());

查找第一个元素

findFirst

  • 给定一个数字列表,找出第一个平法能被3整除的数。
Optional<Integer> collect1 = test.stream().map(n -> n * n).filter(n -> n % 3 == 0).findFirst();

规约

元素求和

  • reduce
Integer reduce = numbers.stream().reduce(0, (a, b) -> a + b);


原理:首先,0作为Lambda(a)的第一个参数,从流中获得4作为第二个参数(b)。0+4=4,他成了新的累计值。然后在用累积值和流中下一个元素5调用Lambda,产生新的累积值9.接下来,在用累积值和下一个元素3调用Lambda,得到12。最后用12和流中的最后一个元素9调用Lambda,得到最终结果21。

  • reduce还有一个重载的变体,不接受初始值,但是会返回一个Optional对象
Optional<Integer> reduce1 = numbers.stream().reduce((a, b) -> a + b);

最大值和最小值

Optional<Integer> reduce2 = numbers.stream().reduce(Integer::max);

测试:怎样用map和reduce方法数一数流中共有多少个菜?

思路:可以把流中每个元素都映射成数字 1 ,然后用reduce求和。相当于按顺序 数流中的元素的个数

int count= menu.stream().map(d->1).reduce(0,(a,b)->a+b);
目录
相关文章
|
1月前
|
Java 应用服务中间件 开发工具
苍穹外卖》电商实战项目(java)知识点整理(上)
苍穹外卖》电商实战项目(java)知识点整理(上)
205 3
|
2月前
|
Java 测试技术 Maven
JAVA单元测试概念与实战
单元测试是软件开发中的一个测试方法,用于验证软件代码中最小的、独立的单元是否按照预期工作。在Java中,这通常指的是单个的方法或者一个类的个别功能。单元测试的目的是隔离代码的每个部分,并确保各个部分是正确的。
51 4
|
3月前
|
Java 关系型数据库 MySQL
兴奋!阿里巴巴首推“Java进阶必备宝典”,理论到实战,一键搞定
作为一名Java方向的程序员,打好夯实的基础是非常重要的,现在大厂面试对于程序员基础知识的掌握考察也越来越严格,虽然说现在技术更新比较快,但基础扎实才能够更深入的去理解每一个知识技术点。
|
21天前
|
设计模式 安全 Java
Java并发编程实战:使用synchronized关键字实现线程安全
【4月更文挑战第6天】Java中的`synchronized`关键字用于处理多线程并发,确保共享资源的线程安全。它可以修饰方法或代码块,实现互斥访问。当用于方法时,锁定对象实例或类对象;用于代码块时,锁定指定对象。过度使用可能导致性能问题,应注意避免锁持有时间过长、死锁,并考虑使用`java.util.concurrent`包中的高级工具。正确理解和使用`synchronized`是编写线程安全程序的关键。
|
3月前
|
传感器 自动驾驶 算法
JAVA实战演练之自动驾驶系统
JAVA实战演练之自动驾驶系统
|
4天前
|
安全 Java 调度
Java线程:深入理解与实战应用
Java线程:深入理解与实战应用
24 0
|
2月前
|
监控 IDE Java
Java项目调试实战:如何高效调试Spring Boot项目中的GET请求,并通过equalsIgnoreCase()解决大小写不一致问题
Java项目调试实战:如何高效调试Spring Boot项目中的GET请求,并通过equalsIgnoreCase()解决大小写不一致问题
45 0
|
2天前
|
消息中间件 缓存 NoSQL
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
|
8天前
|
存储 Java 数据库连接
java DDD 领域驱动设计思想的概念与实战
【4月更文挑战第19天】在Java开发中,领域驱动设计(Domain-Driven Design, DDD) 是一种软件设计方法论,强调以领域模型为中心的软件开发。这种方法通过丰富的领域模型来捕捉业务领域的复杂性,并通过软件满足核心业务需求。领域驱动设计不仅是一种技术策略,而且还是一种与业务专家紧密合作的思维方式
30 2
|
18天前
|
Java API 开发者
Java 8新特性之函数式编程实战
【4月更文挑战第9天】本文将深入探讨Java 8的新特性之一——函数式编程,通过实例演示如何运用Lambda表达式、Stream API等技术,提高代码的简洁性和执行效率。