5.5 实习Day5记录

244 阅读5分钟

安恒实习Day5

公司项目学习

网络安全态势感知白皮书学习

项目源码学习

lamda表达式

语法

Lambda表达式语法由参数列表、->和函数体组成。函数体既可以是一个表达式也可以是一个代码块。

  • 表达式:表达式会被执行然后返回结果。它简化掉了return关键字。
  • 代码块:顾名思义就是一坨代码,和普通方法中的语句一样。

目标类型

Lambda表达式没有名字,通过上下文推导得到它的类型,比如下面的表达式类型是listener: Listener listener = View -> {v.setText("hello lamda")}

相同的lambda表达式在不同的上下文里有不同的类型

Runnable runnable = () -> doSomething(); //这个表达式是Runnable类型的
Callback callback = () -> doSomething(); //这个表达式是Callback类型的

编译器利用lambda表达式所在的上下文所期待的类型来推导表达式的类型,这个被期待的类型被称为目标类型。

Lambda表达式的类型和目标类型的方法签名必须一致,编译器会对此做检查,一个lambda表达式要想赋值给目标类型T则必须满足下面所有的条件:

  • T是一个函数式接口

  • lambda表达式的参数必须和T的方法参数在数量、类型和顺序上一致(一一对应)

  • lambda表达式的返回值必须和T的方法的返回值一致或者是它的子类

  • lambda表达式抛出的异常和T的方法的异常一致或者是它的子类

作用域

Lambda表达式

public class HelloLambda {

    Runnable r1 = () -> System.out.println(this);
    Runnable r2 = () -> System.out.println(toString());

    @Override
    public String toString() {
        return "Hello, lambda!";
    }

    public static void main(String[] args) {
        new HelloLambda().r1.run();  
        new HelloLambda().r2.run();
    }
}

变量捕获

在Java7中,编译器对内部类中引用的外部变量(即捕获的变量)要求非常严格:如果捕获的变量没有被声明为final就会产生一个编译错误。但是在Java8中放宽了这一限制--对于lambda表达式和内部类,允许在其中捕获那些符合有效只读的局部变量(如果一个局部变量在初始化后从未被修改过,那么它就是有效只读)。

Runnable getRunnable(String name){
    String hello = "hello";
    return () -> System.out.println(hello+","+name);
}

对于this的引用以及通过this对未限定字段的引用和未限定方法的调用本质上都属于使用final局部变量。包含此类引用的lambda表达式相当于捕获了this实例。在其他情况下,lambda对象不会保留任何对this的应用。

这个特性对内存管理是极好的:要知道在java中一个非静态内部类会默认持有外部类实例的强引用,这往往会造成内存泄露。而在lambda表达式中如果没有捕获外部类成员则不会保留对外部类实例的引用。

不过尽管Java8放宽了对捕获变量的语法限制,但试图修改捕获变量的行为是被禁止的,比如下面这个例子就是非法的:

int sum  = 0;
list.forEach(i -> {sum += i;});

为什么要禁止这种行为呢?因为这样的lambda表达式很容易引起race condition lambda表达式不支持修改捕获变量的另外一个原因是我们可以使用更好的方式来实现同样的效果:使用规约(condition)。java.util.stream包提供了各种规约操作,关于Java8中的Stream API我们放到下一章介绍。

方法引用

lambda表达式允许我们定义一个匿名方法,并以函数式接口的方式使用它。Java8能够在已有的方法上实现同样的特性。

方法引用和lambda表达式拥有相同的特性(他们都需要一个目标类型,并且需要被转化为函数式接口的实例),不过我们不需要为方法引用提供方法体,我们可以直接通过方法名引用已有方法。 以下面的代码为例,假设我们要按照userName排序

class User{

    private String userName;

    public String getUserName() {
        return userName;
    }
}

List users = new ArrayList<>(); Comparator comparator = Comparator.comparing(u -> u.getUserName()); Collections.sort(users, comparator);复制代码我们可以用方法引用替换上面的lambda表达式 Comparator comparator = Comparator.comparing(User::getUserName);

    这里的User::getUserName被看做是lambda表达式的简写形式。尽管方法引用不一定会把代码变得更紧凑,但它拥有更明确的语义--如果我们想要调用的方法拥有一个名字,那么我们就可以通过方法名调用它。

方法引用有很多种,它们的语法如下:

静态方法引用:ClassName::methodName
实例上的实例方法引用:instanceReference::methodName
超类上的实例方法引用:super::methodName
类型上的实例方法引用:ClassName::methodName
构造方法引用:Class::new
数组构造方法引用:TypeName[]::new


作者:张磊BARON
链接:https://juejin.cn/post/6844903444336082957
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

## 拓展知识:自己实现一个JVM

## 每日一题 
leetcode 6 Z字形变换
思路:
1. 暴力法 
将原字符串按题目规则存入一个n * (字符串长度/n*2)的矩阵中,然后按行打印。
算法: 用两个flag表示竖向和横向的移动方向,然后Z字形将字符存储,最后遍历数组。
2. 按行排列,从左向右迭代字符串,可以知道字符的位置,按下标存入字符串中。