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);
目录
相关文章
|
2月前
|
存储 Java 开发者
Java Map实战:用HashMap和TreeMap轻松解决复杂数据结构问题!
【10月更文挑战第17天】本文深入探讨了Java中HashMap和TreeMap两种Map类型的特性和应用场景。HashMap基于哈希表实现,支持高效的数据操作且允许键值为null;TreeMap基于红黑树实现,支持自然排序或自定义排序,确保元素有序。文章通过具体示例展示了两者的实战应用,帮助开发者根据实际需求选择合适的数据结构,提高开发效率。
74 2
|
3天前
|
Java
Java基础却常被忽略:全面讲解this的实战技巧!
本次分享来自于一道Java基础的面试试题,对this的各种妙用进行了深度讲解,并分析了一些关于this的常见面试陷阱,主要包括以下几方面内容: 1.什么是this 2.this的场景化使用案例 3.关于this的误区 4.总结与练习
|
20天前
|
Java 程序员
Java基础却常被忽略:全面讲解this的实战技巧!
小米,29岁程序员,分享Java中`this`关键字的用法。`this`代表当前对象引用,用于区分成员变量与局部变量、构造方法间调用、支持链式调用及作为参数传递。文章还探讨了`this`在静态方法和匿名内部类中的使用误区,并提供了练习题。
21 1
|
1月前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
52 6
|
1月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
2月前
|
存储 消息中间件 安全
JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
【10月更文挑战第9天】本文介绍了如何利用JUC组件实现Java服务与硬件通过MQTT的同步通信(RRPC)。通过模拟MQTT通信流程,使用`LinkedBlockingQueue`作为消息队列,详细讲解了消息发送、接收及响应的同步处理机制,包括任务超时处理和内存泄漏的预防措施。文中还提供了具体的类设计和方法实现,帮助理解同步通信的内部工作原理。
JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
|
2月前
|
开发框架 Java 程序员
揭开Java反射的神秘面纱:从原理到实战应用!
本文介绍了Java反射的基本概念、原理及应用场景。反射允许程序在运行时动态获取类的信息并操作其属性和方法,广泛应用于开发框架、动态代理和自定义注解等领域。通过反射,可以实现更灵活的代码设计,但也需注意其性能开销。
55 1
|
3月前
|
缓存 负载均衡 Dubbo
Dubbo技术深度解析及其在Java中的实战应用
Dubbo是一款由阿里巴巴开源的高性能、轻量级的Java分布式服务框架,它致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
93 6
|
3月前
|
Java
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
40 7
|
3月前
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
在Android应用开发中,追求卓越性能是不变的主题。本文介绍如何利用Android NDK(Native Development Kit)结合Java与C++进行混合编程,提升应用性能。从环境搭建到JNI接口设计,再到实战示例,全面展示NDK的优势与应用技巧,助你打造高性能应用。通过具体案例,如计算斐波那契数列,详细讲解Java与C++的协作流程,帮助开发者掌握NDK开发精髓,实现高效计算与硬件交互。
166 1