美文网首页
第1部分-基础知识

第1部分-基础知识

作者: 飞的慢的笨鸟 | 来源:发表于2022-05-17 17:54 被阅读0次

    第1章:Java8、9、10、11的变化

    简洁:利用Java8,可以书写更加简洁的代码。

    多核:当前计算机硬件大多都是多核CPU,而Java并没有充分利用多核的优势,即使是多线程也是在单个CPU上进行。利用synchronized成本会比想象中的更高。

    行为参数化:方法引用(::语法)和Lambda表达式。将方法作为参数来传递,实现函数式编程。Lambda表达式其实就是一个匿名函数,但没有传统的匿名函数有那么多繁琐的模板代码,形式上更加简洁。

    接口的默认方法:为了解决接口平滑演进,比如如何改变已发布的接口而不破坏已有的实现,可使用这一技术实现。

    为什么Java还在变:即使现在很活跃,并不代表以后还会一直活跃。只有一直变化,才能不被时代抛弃。要么改变,要么衰亡。COBOL的前车之鉴。

    流处理:Unix或Linux中,可以通过管道(|)将多个命令连接在一起,其实就是一个流处理,这些命令是同时处理的。Java8借鉴的这一思想,推出了Stream API,这样就可以把输入的不相关部分拿到几个CPU上分别执行,这是几乎免费的并行,用不着费劲搞Thread了。Collection主要是为了存储和访问数据,Stream主要用于描述对数据的计算,前者迭代是外部迭代,开发者自己控制,而后者迭代是内部迭代,开发者无需关心如何迭代,内部迭代可并行迭代,这是外部迭代不好处理的地方。

    谓词:predicate,一个返回boolean值的函数。

    Optional类:避免出现NullPointerException。

    (结构化的)模式匹配:Switch的扩展形式,能够同时将一个数据类型分解成元素。

    第2章:通过行为参数化传递代码

    应对不断变化的需求
    第1次尝试:直接定义一个判断绿苹果的方法,无扩展性。

    public static List<Apple> filterGreenApples(List<Apple> inventory) {
            List<Apple> result = new ArrayList<>();
            for (Apple apple : inventory) {
                if (apple.getColor() == Color.GREEN) {
                    result.add(apple);
                }
            }
            return result;
        }
    

    第2次尝试:在参数中增加颜色或重量,可以判断颜色或重量,有一定扩展性。

    public static List<Apple> filterApplesByColor(List<Apple> inventory, Color color) {
            List<Apple> result = new ArrayList<>();
            for (Apple apple : inventory) {
                if (apple.getColor() == color) {
                    result.add(apple);
                }
            }
            return result;
        }
    
    public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight) {
            List<Apple> result = new ArrayList<>();
            for (Apple apple : inventory) {
                if (apple.getWeight() > weight) {
                    result.add(apple);
                }
            }
            return result;
        }
    

    第3次尝试:在参数中增加能想到的所有属性,这样的代码可读性和可维护性会非常差,非常不推荐。代码省略。

    第4次尝试:抽象行为,利用谓词定义一个接口ApplePredicate来对选择标准建模,不同的选择实现内容不同。然后将接口作为参数传入filter方法。但可惜的是filter方法只能接受对象,所以必须把代码包裹在ApplePredicate对象里面,有点像策略模式,我们还想更加简洁。

    interface ApplePredicate {
            boolean test(Apple a);
        }
    
    static class AppleWeightPredicate implements ApplePredicate {
            @Override
            public boolean test(Apple apple) {
                return apple.getWeight() > 150;
            }
        }
    
    static class AppleColorPredicate implements ApplePredicate {
            @Override
            public boolean test(Apple apple) {
                return apple.getColor() == Color.GREEN;
            }
        }
    
    static class AppleRedAndHeavyPredicate implements ApplePredicate {
            @Override
            public boolean test(Apple apple) {
                return apple.getColor() == Color.RED && apple.getWeight() > 150;
            }
        }
    
    public static List<Apple> filter(List<Apple> inventory, ApplePredicate p) {
            List<Apple> result = new ArrayList<>();
            for (Apple apple : inventory) {
                if (p.test(apple)) {
                    result.add(apple);
                }
            }
            return result;
        }
    

    第5次尝试:使用匿名类,避免声明实现ApplePredicate接口的类,还要实例化。虽然比之前少了很多代码,但还是有很多模板代码。

    List<Apple> redApples2 = filter(inventory, new ApplePredicate() {
                @Override
                public boolean test(Apple a) {
                    return a.getColor() == Color.RED;
                }
            });
    

    第6次尝试:使用Lambda表达式,相比匿名类更加简洁,将所有的模板代码都省去。

    List<Apple> redApples3 = filter(inventory, (Apple apple) -> Color.RED.equals(apple.getColor()));
    

    第7次尝试:将List类型抽象化,之前的代码只适用于Apple,List类型抽象化,从而超越眼前要处理的问题。在灵活性和简洁性之前找到了最佳平衡点。

    public interface Predicate<T>{
            boolean test(T t);
        }
    
    public static <T> List<T> filter(List<T> list, Predicate<T> p){
            List<T> result = new ArrayList<>();
            for (T e : list){
                if (p.test(e)){
                    result.add(e);
                }
            }
            return result;
        }
    
    List<Integer> numbers = Arrays.asList(1, 2, 4);
    List<Integer> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0);
    

    第3章:Lambda表达式

    Lambda表达式可以看成是匿名函数,也是没有声明名称的方法,也可以像匿名函数一样,作为参数传递给一个方法。Lambda表达式没有名称,但有参数列表、函数主体、返回类型和可能的异常列表。
    Lambda表达式的有个两种风格:

    • 表达式风格:(parameters) -> expression
    • 块风格:(parameters) -> { statements;},块中是一个个语句。

    函数式接口:只定义一个抽象方法的接口(多个默认实现方法不影响)

    可以使用注解@FunctionalInterface来表示这个接口是函数式接口,但不是强制要求一定需要此注解。

    函数描述符:函数式接口中的方法签名。

    Java8库中常见的函数式接口

    • Predicate接口:T -> boolean,布尔表达式
    • Consumer接口:T -> void,消费一个对象
    • Function接口:T -> R,从一个对象中选择/提取
    • Supplier接口:() -> T,创建对象
    • UnaryOperator接口:T -> T
    • BinaryOperator接口:(T, T) -> T,合并两个值
    • BiPredicate接口:(T, U) -> boolean
    • BiConsumer接口:(T, U) -> void
    • BiFunction接口:(T, U) -> R,比较两个对象

    异常处理

    • 自定义函数式接口,并声明受检异常
    • 把Lambda包在一个try/catch块中

    类型检查

    Lambda的类型是从使用Lambda的上下文推断出来的。因为Lambda表达式将名称都省略掉了,所以只要Lambda表达式的参数列表和出参跟某个函数式接口的方法签名兼容,那么就可以对应传递。

    显式二义性问题

    两个不同的函数式接口有相同的函数描述符,那么就可能出现一个Lambda表达式对应两个函数式接口。为解决这种问题,可使用显式将Lambda表达式强制转换成对应的接口。

    类型推断

    Lambda表达式的入参列表可以显式表明类型,也可以不显式表明,后者就是通过类型推断而来。

    使用局部变量

    Lambda表达式可以没有限制的捕获实例变量和静态变量,但是局部变量必须显式声明为final,或事实上是final。也就是说,Lambda表达式只能捕获指派给它们的局部变量一次。

    方法引用

    语法::,符号前面是类型,符号后面是方法,针对仅仅涉及单一方法的Lambda的语法糖。
    三类方法引用:

    • 指向静态方法的方法引用,如Integer::parseInt
    • 指向任意类型实例方法的方法引用,引用一个对象的方法,这个对象是Lambda表达式的一个参数,如String::length
    • 指向现存对象或表达式实例方法的方法引用,需要传递一个私有辅助方法时特别有用。

    构造函数引用,ClassName::new

    • 无参构造函数,适合Supplier接口
    • 一个参数的构造函数,适合Function接口

    复合Lambda表达式的有用方法

    • 比较器复合,Comparator接口支持逆序reversed和比较器链thenComparing两个方法
    • 谓词复合,Predicate接口支持negate、and和or三个方法
    • 函数复合,Function接口有andThen和compose两个默认方法,方法返回Function的一个实例

    相关文章

      网友评论

          本文标题:第1部分-基础知识

          本文链接:https://www.haomeiwen.com/subject/gtjnurtx.html