美文网首页
响应式编程开源库 RxJava2——关键名词

响应式编程开源库 RxJava2——关键名词

作者: 阿扎泼柴 | 来源:发表于2019-05-11 11:09 被阅读0次

    上一篇响应式编程开源库 RxJava2——起源
    )主要是对RxJava的来源,以及Observble和Observer和一些相关联的基本概念简介。上篇文章应该能基本了解观察者模式,接下来就需要更深入了解其他重要名词——函数式接口、默认方法、纯函数、函数的副作用、高阶函数、可变的和不可变的、函数式编程和 Lambda 表达式。

    1.函数式接口

    一言以蔽之,函数式接口是有且只有一个抽象方法的接口。换言之,任何拥有唯一抽象方法的接口都可以被称为函数式接口。可以用注解 @FunctionalInterface标明,也可忽略注解。在 Java 8 里,我们可以在接口中定义一个包含方法体的方法,这个方法叫默认方法,如下面所示。

    @FunctionalInterface
    public interface Test {
        void test();
        default void test2(){
        }
    }
    

    如果接口定义默认方法或者继承并重写 java.lang.Object 类里的任何方法。那个接口还是函数式接口,这是因为在Java中所有类都是 Object的子类的。

    @FunctionalInterface
    public interface Test {
        void test();
        default void test2(){
        }
        @Override
        String toString();
    }
    

    所以函数式接口满足下列三个条件:
    只有一个抽象方法 - 可以有默认方法 - 可以使用 java.lang.Object的方法。

    在 Java 8 里有一个新的工具包 java.util.function。在这个包里,所有的接口都是函数式接口。当我们需要用到流(Stream) API 时,这个工具包很有用。
    关于Java8中的Stream API额外提一下,它必须是在SDK版本大于24才能使用。这是一个强大的API,能将Java中所有实现Iterable接口的类转变为数据流,在Java中也就是所有集合,都能转变为数据流。并且能方便对数据流做出相应操作,例如将集合中的值转换并把流中的每个数据执行打印操作。

    List<Integer> list=new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                list.add(i);
            }
    list.stream().map(new Function<Integer, String>() {
                @Override
                public String apply(Integer integer) {
                    return integer+"转换int->String";
                }
            }).forEach(new Consumer<String>() {
                @Override
                public void accept(String s) {
                    Log(s);
                }
            });
    2019-05-09 18:03:39.494 30878-30878/com.example.studyrx I/Study RxJava: 0转换int->String
    2019-05-09 18:03:39.494 30878-30878/com.example.studyrx I/Study RxJava: 1转换int->String
    2019-05-09 18:03:39.494 30878-30878/com.example.studyrx I/Study RxJava: 2转换int->String
    2019-05-09 18:03:39.494 30878-30878/com.example.studyrx I/Study RxJava: 3转换int->String
    2019-05-09 18:03:39.494 30878-30878/com.example.studyrx I/Study RxJava: 4转换int->String
    2019-05-09 18:03:39.494 30878-30878/com.example.studyrx I/Study RxJava: 5转换int->String
    2019-05-09 18:03:39.494 30878-30878/com.example.studyrx I/Study RxJava: 6转换int->String
    2019-05-09 18:03:39.494 30878-30878/com.example.studyrx I/Study RxJava: 7转换int->String
    2019-05-09 18:03:39.494 30878-30878/com.example.studyrx I/Study RxJava: 8转换int->String
    2019-05-09 18:03:39.494 30878-30878/com.example.studyrx I/Study RxJava: 9转换int->String
    

    2.纯函数

    首先我们要理解什么是函数。在Java中函数也叫方法,是具备特定功能的一段代码块,解决了重复性代码的问题,目的是为了提高程序的复用性和可读性。
    现在忘掉这个概念,我们从纯数学的方面理解函数。
    函数的定义:给定一个数集A,假设其中的元素为x。现对A中的元素x施加对应法则f,记作f(x),得到另一数集B。假设B中的元素为y。则y与x之间的等量关系可以用y=f(x)表示。函数概念含有三个要素:定义域A、值域C和对应法则f。其中核心是对应法则f,它是函数关系的本质特征。
    一个根据输入决定输出的方程式,就是函数。如 f(x) = x+3。
    数学中对于给定的输入值,会得到唯一的结果,这就叫纯函数。但是编程中的函数给同样的输入可以有不同的输出,因为它依赖于其它数值。比如我们计算汇率:

    public float convertRMB2Dollar(int RMB){
            return RMB*getTodayRateFromAPI();
        }
    

    所以编程语言中的纯函数是返回值由它的输入值决定,而且没有明显可见的副作用。
    输入输出数据流全是显式(Explicit)的。 显式(Explicit)的意思是,函数与外界交换数据只有一个唯一渠道——参数和返回值。函数从函数外部接受的所有输入信息都通过参数传递到该函数内部。函数输出到函数外部的所有信息都通过返回值传递到该函数外部。

    3.函数的副作用

    任何不纯的函数叫非纯函数,它可能产生副作用。或者一些函数本身是纯函数(指对于给定的输入值可以得出相同的输出值),但是如果它在产生结果的时候与外界发生了数据交换,那么我们就不能说这是一个纯函数。
    第一类非纯函数的典型就是 Random 函数。对于给定的一个输入值,它总是返回不同的结果。
    第二类副作用的典型是 println() ,它是一个非纯的函数。因为它将输出值转去了输入输出设备(而不是作为函数返回值输出),所以产生了副作用。任何纯函数一旦用 println() 来注释打印,那它就不再是纯函数了。
    纯函数:

    public int squre(int x){
        return x*x;
    }
    

    因为副作用而非纯的的函数:

    public int squre(int x){
        System.out.println(x*x);
        return x*x;
    }
    

    非纯函数(与外界数据交换):

    public void login(String username, String password, Callback c){
        API.login(username, password, callback);
    }
    

    函数副作用指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。例如修改全局变量(函数外的变量)或修改参数。

    4.Java 可变对象和不可变对象

    不可变对象(Immutable Objects)即对象一旦被创建它的状态(对象的数据,也即对象属性值)就不能改变,反之即为可变对象(Mutable Objects)。
    不可变对象的类即为不可变类(Immutable Class)。Java平台类库中包含许多不可变类,如String、基本类型的包装类、BigInteger和BigDecimal等。

    不可变对象有很多优缺点:
      构造、测试和使用都很简单
      线程安全且没有同步问题,不需要担心数据会被其它线程修改
      当用作类的属性时不需要保护性拷贝
      可以很好的用作Map键值和Set元素
      不可变对象最大的缺点就是创建对象的开销,因为每一步操作都会产生一个新的对象。
    可以遵照以下几点来编写一个不可变类:
      确保类不能被继承 - 将类声明为final, 或者使用静态工厂并声明构造器为private
      声明属性为private 和 final
      不要提供任何可以修改对象状态的方法 - 不仅仅是set方法, 还有任何其它可以改变状态的方法
      如果类有任何可变对象属性, 那么当它们在类和类的调用者间传递的时候必须被保护性拷贝
    下面举例感受可变和不可变:

    int array []= {1,2,3,4,5};
    for (int i = 0; i < array.length; i++) {
        array[i] = array[i] * 2;
    }
    

    在 Java 或者命令式编程中,上面的代码是可变的。它改变了原本的数组值。但是在函数式编程里,如果我做了同样的事情,我总是获得与 2 相乘后的数值组成的新数组,而我原来的数据仍然保持不变。

    Integer array []= {1,2,3,4,5};
    Arrays.stream(array).map(v->v*2).forEach(i-> System.out.print(i+" "));
    for (int i = 0; i < array.length; i++) {
        System.out.print(array[i]+ " ");
    }
    

    那么可变和不可变有什么用,这里简单举例:

    public static void main(String[] args) {
    
            Integer array []= {1,2,3,4,5};
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < array.length; i++) {
                        array[i] = array[i]+1;
                    }
                }
            }).start();
            for (int i = 0; i < array.length; i++) {
                System.out.println(square(array[i]));
            }
        }
        public static int square(int a){
            return a*a;
        }
    

    在这段代码里我们使用了子线程对数组做+1操作,在主线程对数组做平方操作。作为开发者,我们知道这里的打印结果根本不可控制。例如我运行后是如下结果:



    这时候数据是可变的。那么现在对数据的不可变性进行严格控制。会是什么结果?

    public static void main(String[] args) {
    
            Integer array []= {1,2,3,4,5};
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Observable.fromArray(array)
                            .map(integer -> integer+1)
                            .subscribe(integer -> {});
                }
            }).start();
    
            Observable.fromArray(array)
                    .map(integer -> square(integer))
                    .subscribe(integer -> System.out.println(integer));
    
        }
    
        public static int square(int a){
            return a*a;
        }
    

    这时候因为我的程序没有对数组做直接改变,而是拷贝了我的数据。这时候数组是不可变的。这就能避免很多问题,比如这里的线程安全问题,我们根本不用考虑。

    5.高阶函数

    在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:

    • 接受一个或多个函数作为输入
    • 输出一个函数

    数学

    在数学中它们叫算子(运算符)或泛函。微积分中的导数就是常见的例子,因为它映射一个函数到另一个函数。

    计算机科学

    在无类型 lambda演算,所有函数都是高阶的;在有类型 lambda演算(大多数函数式编程语言都从中演化而来)中,高阶函数一般是那些函数型别包含多于一个箭头的函数。在函数式编程中,返回另一个函数的高阶函数被称为柯里化的函数。
    在很多函数式编程语言中能找到的 map 函数是高阶函数的一个例子。它接受一个函数 f 作为参数,并返回接受一个列表并应用 f 到它的每个元素的一个函数。
    在编程中,拥有至少一个函数类型为参数的函数,或着返回一个函数的函数叫做高阶函数。函数式编程就是指这种高度抽象的编程范式。
    上面所介绍的的纯函数,高阶函数,不可变的都是函数式范式。在面向对象编程时我们经常要管理对象的状态,但是在函数式程序里,我们有数据,管理好了不可变性,我们可以大胆地做运算。
    进入Lambda表达式之前先复习下之前的内容:
    函数式接口 - 有且仅有一个抽象方法的接口,可以有默认方法,可以包含Object的方法。
    默认方法 - 在 Java 8 里,我们可以在接口中定义有方法体的方法,这些叫默认方法。
    纯函数 - 一个函数的返回值仅由输入值决定,没有明显可见的副作用,函数与外界交换数据只有一个唯一渠道——参数和返回值。
    高阶函数 - 拥有至少一个函数类型为参数的函数,或着返回一个函数的函数叫做高阶函数。

    6.Lambda 表达式

    在计算机编程中,lambda 表达式,也叫匿名函数,是指一类无需定义标识符(函数名)的函数或子程序。
    我们对一个控件设置监听是这样写的:

    text.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                }
            });
    

    而通过Lambda表达式书写就是:

    text.setOnClickListener(v -> {
            });
    

    初学的话会很难理解,很难看懂。初学的时候真的很困惑,编译器是如何知道我这里在做什么。这里就是函数式接口的运用了,编译时接口中只有一个方法,Java 自动知道‘v'是一个 View。这个接口只有一个抽象方法,它的参数是一个 view,如下所示

    public interface OnClickListener {
            /**
             * Called when a view has been clicked.
             *
             * @param v The view that was clicked.
             */
            void onClick(View v);
        }
    

    所以可以简单理解为Lambda表达式等价于函数式接口。

    相关文章

      网友评论

          本文标题:响应式编程开源库 RxJava2——关键名词

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