精华内容
下载资源
问答
  • 基于SpringMVC的编程式事务源代码示例,一目了然的超简单使用事务的方式,让你一分钟会用事务。
  • [交互式编程].源代码

    2012-05-17 07:36:35
    [交互式编程].源代码 [交互式编程].源代码
  • 在网上看了很多关于编程思想的博客,感觉有的和我的理解不太一样,这里写下我对几种编程思想的理解,同时对链式编程进行代码分析及demo练习。 目录 一、定义 二、关于Masonry 三、链式编程 1、链式单元 2、...

    在网上看了很多关于编程思想的博客,感觉有的和我的理解不太一样,这里写下我对几种编程思想的理解,同时对链式编程进行代码分析及demo练习。

    目录

    一、定义

    二、关于Masonry

    三、链式编程

    1、链式单元

    2、添加category

    3、效果


    一、定义

    链式编程:我的理解为链式编程是一种表现方式为“.”+“()”并能够一直使用“.”连续的链式的进行开发的一种“语法”,其表现方式是由点语法和block来实现的,通过block传对应的参数并返回本身来实现连续调用。

    响应式:响应式编程主要表现为监听改变,即代码的执行不是按代码的书写顺序,而是按响应的顺序,所以数据变化的结果才是最重要的,这点可以参考KVO。

    函数式:我的理解函数式编程是一种封装,而且这种封装是完全独立的不受影响的,也就是函数式的特点是不依赖外部状态,不改变外部状态,完全解耦完全静态的。就像我们进行1+1的操作,这个“+”可以理解为一种封装,“+”方法通过代码实现了“+”逻辑,我们只需要使用就可以,外部数据不会影响“+”方法,“+”方法也不会影响外部数据。

    在实际开发中各种编程思想交叉使用,并不会完全用一种方法开发,所以不需要太过纠结到底是什么编程方式,有自己的理解就可以。

    二、关于Masonry

    关于Masonry

    Masonry是典型的链式编程+函数式编程写法

    [redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.top.equalTo(@(100));
        make.right.bottom.equalTo(@(-100));
    }];

    可以看到 mas_makeConstraints 就是运用的函数式编程,该方法与block中的约束处理是完全独立,完全解耦的,甚至里面的left方法,top方法也可以认为是函数式编程,因为其符合函数式编程的特点。

    而整体的make.left.top.equalTo(@(100)) 是链式编程,因为其符合链式编程“.”+“()”结构组成,并且能够连续点语法。

    这里可以发现单独的left方法即是函数式编程又是链式编程,因为他符合函数式编程的完全解耦的静态特点,也符合链式编程的返回block形式,不过他只是链式编程的其中一个单元。

    三、链式编程

    1、链式单元

    链式编程的组成方式为“.”+“()”,通过用get方法+block传参的方式来实现。

    这里模仿Masonry实现一个加法的功能。

    首先写一个返回值为block的get方法,和一个总和sum

    @property (nonatomic,assign)int sum;
    
    
    -(SumManager * (^)(int))add
    {
        return ^(int a){
          
            self.sum+=a;
            return self;
        };
    }

    然后在调用时是这样的:

    SumManager * manager = [[SumManager alloc]init];
    manager.add(1);

    为了方便理解可以分解成:

    SumManager *(^block)(int) =  [manager add];
    block(1);

    就是add方法返回一个block。

    也可以表达为:

    [manager add](1);
    

    这样一个链式单元就完成了。

    2、添加category

    为了方便引入,并且做成和Masonry的样式,我们需要加入category

    #import "NSObject+Sum.h"
    
    @implementation NSObject (Sum)
    
    -(int)gs_getSumWithManager:(void (^)(SumManager * _Nonnull))block
    {
        SumManager * m = [[SumManager alloc]init];
        block(m);
        return m.sum;
    }

    这样通过category可以随时调用了。

    3、效果

    int sum = [self gs_getSumWithManager:^(SumManager * _Nonnull manager) {
        manager.add(1).add(2);
    }];
    NSLog(@"%d",sum);

    这样gs_getSumWithManager是一个函数式编程,其封装了add方法,并且是完全解耦的可以随时调用。

    manager.add(1).add(2); 这里又是一个链式编程,符合链式编程的所有特点。

    做到了和Masonry一样的效果。

     

    展开全文
  • 响应式编程

    千次阅读 2019-05-07 15:26:39
    我们先来聊一聊响应的由来,对于它的由来,我们可能需要先从一段常见的代码片段看起 int a=1; int b=a+1; System.out.print(“b=”+b) // b=2 a=10; System.out.print(“b=”+b) // b=2 上面是一段很...

    维护关系,却不执行,然后根据触发后再去执行。 相当于原编程运行思路的逆向。

     

    响应式的由来

    我们先来聊一聊响应式的由来,对于它的由来,我们可能需要先从一段常见的代码片段看起

    int a=1;
    int b=a+1;
    System.out.print(“b=”+b)    //  b=2
    a=10;
    System.out.print(“b=”+b)    //  b=2
    
    

    上面是一段很常见的代码,简单的赋值打印语句,但是这种代码有一个缺陷,那就是如果我们想表达的并不是一个赋值动作,而是b和a之间的关系,即无论a如何变化,b永远比a大1。那么可以想见,我们就需要花额外的精力去构建和维护一个b和a的关系。

    而响应式编程的想法正是企图用某种操作符帮助你构建这种关系。
    它的思想完全可以用下面的代码片段来表达:

     
    int a=1;
    int b <= a+1;   // <= 符号只是表示a和b之间关系的操作符
    System.out.print(“b=”+b)    //  b=2
    a=10;
    System.out.print(“b=”+b)    //  b=11
    
    

    这就是是响应式的思想,它希望有某种方式能够构建关系,而不是执行某种赋值命令。

    至此你可能不禁要问,我们为什么需要构建关系的代码而不是命令式的代码呢?如果你翻一翻自己正在开发的APP,你就能看到的每一个交互的页面其实内部都包含了一系列的业务逻辑。而产品的每个需求,其实也对应了一系列的业务逻辑相互作用。总之,我们的开发就是在构建一系列的业务逻辑之间的关系。你说我们是不是需要构建关系的代码?

    说回响应式,前期由于真实的编程环境中并没有构建关系的操作符,主流的编程语言并不支持这种构建关系的方式,所以一开始响应式主要停留在想的层面,直到出现了Rx和一些其他支持这种思想的框架,才真正把响应式编程引入到了实际的代码开发中。

    Rx是响应式拓展,即支持响应式编程的一种拓展,为响应式在不同语言中的实现提供指导思想

    什么是响应式编程

    说完了了响应式的由来,我们就可以谈谈什么是响应式编程了。

    响应式编程是一种通过异步和数据流来构建事务关系的编程模型。这里每个词都很重要,“事务的关系”是响应式编程的核心理念,“数据流”和“异步”是实现这个核心理念的关键。为了帮助大家理解这个概念,我们不妨以APP初始化业务为例来拆解一下这几个词。

     

    这是一个比较理想化的APP初始化逻辑,完成SDK初始化,数据库初始化,登陆,之后跳转主界面

    事务的关系

    • 事务

      • 是一个十分宽泛的概念,它可以是一个变量,一个对象,一段代码,一段业务逻辑.....但实际上我们往往把事务理解成一段业务逻辑(下文你均可以将事务替换为业务逻辑来理解),比如上图中,事务就是指APP初始化中的四个业务逻辑。
    • 事务的关系

      • 这种关系不是类的依赖关系,而是业务之间实际的关系。比如APP初始化中,SDK初始化,数据库初始化,登陆接口,他们共同被跳转页面业务所依赖。但是他们三个本身并没有关联。这也只是业务之间较为简单的关系,实际上,根据我们的需求App端会产生出许多业务之间错综复杂的关系。

    数据流

    关于Rx的数据流有很多说法,比如“Everything is a stream”,“Thinking with stream”等等。虽然我明白这只是想强调流的重要性,可是这些话折射出来的编程思路其实是很虚无缥缈的,只会让开发者对于Rx编程更加迷惑。

    实际上,数据流只是事务之间沟通的桥梁。

    比如在APP初始化中,SDK初始化,数据库初始化,登陆接口这些业务完成之后才会去安排页面跳转的操作,那么这些上游的业务在自己工作完成之后,就需要通知下游,通知下游的方式有很多种,其中最棒的的方式就是通过数据(事件)流。每一个业务完成后,都会有一条数据(一个事件)流向下游,下游的业务收到这条数据(这个事件),才会开始自己的工作。

    但是,只有数据流是不能完全正确的构建出事务之间的关系的。我们依然需要异步编程。

    异步

    异步编程本身是有很多优点的,比如挖掘多核心CPU的能力,提高效率,降低延迟和阻塞等等。但实际上,异步编程也给我们构建事务的关系提供了帮助。

    在APP初始化中,我们能发现SDK初始化,数据库初始化,登陆接口这三个业务本身相互独立,应当在不同的线程环境中执行,以保证他们不会相互阻塞。而假如没有异步编程,我们可能只能在一个线程中顺序调用这三个相对耗时较多的业务,最终再去做页面跳转,这样做不仅没有忠实反映业务本来的关系,而且会让你的程序“反应”更慢

    小结

    总的来说,异步和数据流都是为了正确的构建事务的关系而存在的。只不过,异步是为了区分出无关的事务,而数据流(事件流)是为了联系起有关的事务

    APP初始化应该怎么写

    许多使用Rx编程的同学可能会使用这种方式来完成APP的初始化。

    Observable.just(context)
                .map((context)->{login(getUserId(context))})
                .map((context)->{initSDK(context)})
                .map((context)->{initDatabase(context)})
                .subscribeOn(Schedulers.newThread())
                .subscribe((context)->{startActivity()})
    

    其实,这种写法并不是响应式的,本质上还是创建一个子线程,然后顺序调用代码最后跳转页面。这种代码依然没有忠实反映业务之间的关系。

    在我心目中,响应式的代码应该是这样的

    
    Observable obserInitSDK=Observable.create((context)->{initSDK(context)}).subscribeOn(Schedulers.newThread())
    
    Observable obserInitDB=Observable.create((context)->{initDatabase(context)}).subscribeOn(Schedulers.newThread())
    
    Observable obserLogin=Observable.create((context)->{login(getUserId(context))})
                                  .map((isLogin)->{returnContext()})
                                .subscribeOn(Schedulers.newThread())
                                
    Observable observable = Observable.merge(obserInitSDK,obserInitDB,obserLogin)
    
    observable.subscribe(()->{startActivity()})
    
    

    大家应该能很明显看到两段代码的区别,第二段代码完全遵照了业务之间客观存在的关系,可以说代码和业务关系是完全对应的。

    那么这带来了什么好处呢?当initSDK,initDB,Login都是耗时较长的操作时,遵照业务关系编写响应式代码可以极大的提高程序的执行效率,降低阻塞。

    理论上讲,遵照业务关系运行的代码在执行效率上是最优的。

    为什么引入响应式编程

    对响应式编程有了一些了解之后,我知道马上会由很多人跳出来说,不使用这些响应式编程我们还不是一样开发APP?

    在这里我希望你理解一点,当我们用老办法开发APP的时候,其实做了很多妥协,比如上面的APP初始化业务,三个无关耗时操作为了方便,我们往往就放在一个线程环境中去执行,从而牺牲了程序运行的效率。而且实际开发中,这种类似的业务逻辑还有很多,甚至更加复杂。假如不引入响应式的思路,不使用Rx的编程模型,我们面对这么些复杂的业务关系真的会很糟心。假如你做一些妥协,那就会牺牲程序的效率,假如你千辛万苦构建出业务关系,最终写出来的代码也一定很复杂难以维护。所以,响应式编程其实是一种更友好更高效的开发方式。

    根据个人经验来看,响应式编程至少有如下好处:

    • 在业务层面实现代码逻辑分离,方便后期维护和拓展
    • 极大提高程序响应速度,充分发掘CPU的能力
    • 帮助开发者提高代码的抽象能力和充分理解业务逻辑
    • Rx丰富的操作符会帮助我们极大的简化代码逻辑

    一个复杂一些的例子

    接下来,我就以我们团队目前的一款产品的页面为例,详细点介绍运用响应式编程的正确姿势。

    接下来,我就以我们团队目前的一款产品的页面为例,详细点介绍运用响应式编程的正确姿势。

    首先,UI和产品沟通后,可能会给我们这样的设计图(加上一些尺寸的标注)。但是我们并不需要急忙编码,我们首先要做的是区分其中相对独立的模块。

    上图我做了一点简单的标注。把这个页面的业务逻辑简单的分为四个相互独立的模块,分别是视频模块,在线人数模块,礼物模块,消息模块。他们相互独立,互不影响。接下来,我们再去分析每个模块内部的业务并构建起业务之间的关系。大致如下:

    构建了业务之间的关系图,其实我们的工作已经完成了一半了,接下来就是用代码实现这个关系图。在这里,我就以其中一小段业务关系来编写代码给大家示范。

        Observable obserDownload=Observable.just(url)
                                            .map((url)->{getZipFileFromRemote(url)});
        Observable obserLocal=Observable.just(url)
                                            .map((url)->{getZipFileFromLocal(url)});
        Observable obserGift=Observable.concat(obserLocal,obserDownload)
                                            .takeUnitl((file)->{file!=null});
        obserGift.subscribeOn(Schedulers.io()).flatMap((file)->{readBitmapsFromZipFile(file)})
                                            .subscribe((bitmap)->{showBitmap(bitmap)})
    
    
    

    以上是我手写的伪代码,可能细节上有些问题,但大体思路就是这样。

    有人可能会说,那是因为你运用操作符比较熟练才能这么写。其实操作符都是我查的,我记不住那么多操作符,所以基本上我都是先理清楚业务之间的关系,需要和并逻辑的时候,就去去查合并类的操作符,需要条件判断来分流的逻辑时去找条件判断类的操作符。基本上都能满足需求。你瞧,写代码就是这么简单,后续即使需要增加需求,代码修改起来也很清晰,因为无关的业务已经被你分离好了。



    转载自https://www.jianshu.com/p/c95e29854cb1

     

    展开全文
  • Scala函数式编程

    2017-05-26 10:22:26
    函数式编程(FP)是一种软件开发风格,它注重不依赖于编程状态的函数。函数式代码易于测试和复用,容易实现并发,且不容易受到bug的攻击。Scala是一种能很好支持函数式编程的新兴JVM语言。《Scala函数式编程》是针对...
  • 一文搞懂: 响应式编程是什么?

    千次阅读 2021-01-10 16:09:15
    当一个系统具有即时响应性(Responsive)、回弹性(Resilient)、弹性(Elastic)以及消息驱动(Message Driven)。 我们称这样的系统为反应系统(Reactive System)。

    导言

    目前响应式编程还属于大乱斗的时代,各家有各家的定义和实现。响应式宣言定义了响应式应该满足的特性。Spring5有自己的响应式方案技术栈、广为人知的rxJava所属的reactiveX也有自己的理解。有的说响应式是基于事件的,有的说是基于数据流的。有人说响应式编程其实只是个便于并发编程的api库,有人说响应式编程是一种编程思想、一种编程范型。技术圈子里各有各的说法,正如小马哥所说: 一种技术,各自表述。响应式编程含糊不清、有些空洞无实、有些夸大其词,难免让普通学习者难以入门,十分头痛。
    在这里插入图片描述

    以下主要基于响应式宣言和reactiveX来解释响应式编程,所说观点如有异议欢迎评论区讨论指教。

    响应式编程是什么?

    是一款使用异步数据流编程的响应式编程思想。(from here)

    Reactive Programming是基于观察者模型的这是大家的共识,它提供了非阻塞、异步的特性,便于处理异步情景,从而避免回调地狱和突破Future的局限性: 难以组合。(这个优点我并不认同,java8 的completeFuture已经有很好的改善了)。

    在reactiveX中,对好几个语言提供了api库,该库扩展了观察者模式,支持基于数据事件的驱动。允许声明式组合观察者序列。将同步编程和异步编程抽象成统一。

    异步编程和同步编程在代码中的体现可以如下表体现。不同于传统的pull型的,响应式编程通常采用push型的数据传输,由生产者控制消费速度,但当消费者消费速度不足时,也能反过来提示生产者降低生产速度(backPressure),backPressure将在下文阐述。

    响应式编程通常带有函数式的思想,但这是用来简化面向对象语法臃肿性的,响应式编程的核心在于Reactive,只是带有了部分的Functional的特性,更不是Functional Reactive Programming,这是完全不同的东西。

    响应式宣言

    响应式系统应该具有什么特性

    当一个系统具有即时响应性(Responsive)、回弹性(Resilient)、弹性(Elastic)以及消息驱动(Message Driven)。 我们称这样的系统为反应式系统(Reactive System)。

    img

    如图所示,响应式编程的价值在于1. 易于编写维护(特别是异步编程) 2. 及时响应。

    什么是及时响应? 不管是出了什么问题,或断电、或网络抖动、或代码bug,都能及时做出响应,提供足够的可用性。

    响应式宣言中将异常也看做一种消息来简化对错误的处理,其中错误可以简单看成看做各种Exception。

    什么是弹性? 回弹性?

    弹性是指不同负载下,系统的吞吐量、响应性没有什么区别。响应式通过伸缩算法,使系统没有资源争用点和中央瓶颈。

    回弹性是指当系统出现失败时,例如断电、硬件故障、资源耗尽等情况,通过复制、 遏制、 隔离以及委托来保证依然具有即时响应性。

    消息驱动还是事件驱动?

    消息时数据,事件是信号。消息的包含的信息量更丰富。

    对于消息驱动的系统,如果没有消息来,接受者异步非阻塞等待线程到来(休眠)。

    对于事件驱动的系统,常使用回调的方法实现。

    响应式宣言中推荐使用消息驱动来设计,因为事件驱动的系统很难实现回弹性:

    对于事件驱动系统,当处理过程已经就绪,监听器已经设置好, 以便于响应结果并对结果进行变换时, 这些监听器通常都将直接地处理成功或者失败, 并向原始的客户端报告执行结果。

    (这些监听器)响应组件的失败, 以便于恢复它(指失败的组件)的正常功能,而在另外一方面, 需要处理的是那些并没有与短暂的客户端请求捆绑在一起的, 但是影响了整个组件的健康状况的失败。

    响应式编程相关讨论

    为什么使用观察者模式

    使用该模式将开发者从回调地狱中解放出来。ReactiveX使用流来将异步、同步统一起来,并通过订阅者来将各种操作组合起来,提高了代码易读性。

    什么是回调地狱? 其实就是当回调函数需要组合时,常常会使代码复杂度大大增加,代码易读性大大减少的一种现象。

    ReactiveX的观察者模式是十分灵活的,不仅仅支持push、还支持pull。

    什么是背压(backPressure)

    其实背压翻译得不好,叫回压更合适。知乎上有个背压的解说我觉得说得特别好,背压其实只是一种现象,但响应式编程中的backPressure其实是一种解决消费赶不上生产时的一种处理策略,有让生产者慢点push的, 有直接丢弃的…

    在数据流从上游生产者向下游消费者传输的过程中,上游生产速度大于下游消费速度,导致下游的 Buffer 溢出,这种现象就叫做 Backpressure 。

    个人感想

    在我还是学生的时候就常听人说什么响应式编程、什么webflux,当时被各种新技术迷乱了眼睛的我胡乱的看了几个相关介绍,再听响应式编程被各种各样的博客文章吹嘘的多么多么好、多么多么先进,当时就觉得自己一定要学习。

    工作后才发现实际的生产其实不需要多么牛逼先进的东西,大多时候线上系统稳定性才是最重要的。国内java8还是大行其道,好多人java8的特性还没摸透,但依然在工作岗位上支撑起了一片天。

    对于开发者,不要被互联网上各式各样的“技术营销”骗了,在学习新技术前多想想是不是真的要学。什么代码、什么技术、什么框架,都只是互联网农民工手里的锤子罢了,不是越新越花哨的锤子就越好,能解决实际工作内容的就行。

    有时候与其学着各种各样的新技术新框架,不如陪家人看看电影、逛逛街,我们不是互联网的螺丝钉,我们是人,只是恰逢资本主义时代,大多数人都被异化成了“工具人”,但我们都有自己的生活,自己的家人、朋友,每个人都不一样,都有自己的人生。

    总而言之,目前市场上响应式编程各种各样,定义五花八门,优点论证也不够充足,大家还是不要急着入场。
    在这里插入图片描述

    相关文档

    1. http://reactivex.io/intro.html
    2. GitHub rxjava
    3. Reactive-Programming-一种技术-各自表述
    4. 响应式宣言
    5. 响应式宣言词汇表
    展开全文
  • JAVA函数式编程

    千次阅读 2019-02-17 22:40:55
    JAVA函数式编程背景常见的编程范式函数式编程的优劣JAVA8中为函数式编程引入的变化JAVA函数式编程可以简单概括基本函数Lambda表达式方法引用Stream流API创建操作中间操作终止从操作并行流级联表达式与柯里化收集器...

    背景

    JAVA版本最新的目前已经发布到11了,但目前市面上大多数公司依然在使用Java7之前版本的语法,然而这些编程模式已经渐渐的跟不上新时代的技术了。比如时下潮流前沿spring framework5中的响应式编程就是使用到了函数式编程的风格。

    当时有一段时间了解了一下大数据领域生态发现对于写spark中的程序来说如果你使用java7前的命令式编程会使得整体代码相当臃肿,又臭又长匿名类大几百行的代码其中写到的逻辑其实不过几十行,大部分的代码浪费在语法上。使得代码的可读性变得非常差。spark本身是使用Scala编写的对于本身就支持函数式编程的语言,使得代码简洁而又易于理解。当然spark也支持jdk8相对于jdk7来说8加入了函数式编程的支持使得整体优雅了许多。

    在编程历史的长河中java向来可维护性、可扩展性是一直受业界所推崇的。但由于java语法臃肿,也流失了一部分转向了python、go等。在学习大数据的时候发现大部分的教程更倾向Scala(如果大家有java8的教程麻烦连接评论哦!),而Scala本身依赖与jvm,所以若不是公司技术栈限制的话我相信大家更倾向与使用Scala来编程。OK!那么java也推出了函数式编程也通过本文来了解一下。

    常见的编程范式

    • 命令式编程:命令式编程的主要思想是关注计算机执行的步骤,即一步一步告诉计算机先做什么再做什么。这种风格我相信对于传统程序员来说都不陌生。甚至一些大牛闭着眼睛都可以敲了。
      代表语言有:C, C++, Java, Javascript, BASIC,Ruby等多为老牌语言
    • 声明式编程:声明式编程是以数据结构的形式来表达程序执行的逻辑。它的主要思想是告诉计算机应该做什么,但不指定具体要怎么做。乍一看似乎没有见过但其实日常来说大家也都在用。
      代表语言有:SQL,HTML,CSS
    • 函数式编程:函数式编程将函数作为编程中的“一等公民”,关注于流程而非具体实现。可以将函数作为参数或返回值。所有数据的操作都通过函数来实现。可以理解为数学中的函数。较新的语言基本上追求语法上的简洁基本都有支持。
      代表语言有:JAVA(8以上),js(ES6),C#,Scala,python等

    函数式编程的优劣

    优点:

    1. 代码简洁可读性强,逻辑结构清晰。
    2. 线程安全,内部API屏蔽了coder对多线程的关注。
    3. 更好的复用性,往往一个函数适用于各种计算场景。

    缺点:

    1. 由于函数内数据不变原则,导致的资源占用
    2. 调试上相对于命令式的困难

    JAVA8中为函数式编程引入的变化

    • 函数式接口,函数式接口中只能有一个抽象方法
      @FunctionInterface,这也是为了函数调用时避免带来二义性。@FunctionInterface并不是一定要标注但若是标注可以在编译时就给你提示错误。
    • 静态方法,静态方法目的完全出于编写类库,对某些行为进行抽象,但是接口中的静态方法不能被继承。
    • 默认实现,是不得已而为之,因为Java8引入了函数式接口,许多像Collection这样的基础接口中增加了方法,如果还是一个传统的抽象方法的话,那么可能很多第三方类库就会变得完全无法使用。新增一个方法所有实现类都要实现一次。被default修饰的方法–默认实现

    JAVA函数式编程可以简单概括

    lambda + 方法引用 + stream API = java函数式编程

    基本函数

    在这里插入图片描述
    以上是在函数式编程中的基本函数模型,我们大可以将其与数学函数做关联:y = x +1,我们仅仅需要关注这个函数的输入输出即可。
    以predicte函数举例:该函数输入一个表达式,输出一个布尔类型
    一元函数function:输入一个类型参数输出为另一个类型,当然这两个类型可以是相同的,当相同时也可以使用unaryOperator来代替。具体下面有给出实际场景的代码断:

    public class FunctionDemo {
    
        public static void main(String[] args) {
            //断言型
    //        predicate();
            //消费型
    //        consumer();
            //一元函数 输入输出不同
    //        function();
            //提供型
    //        supplier();
            //一元函数 输入输出类型相同
    //        unaryOperator();
            //二元函数 输入输出不同
    //        biFunction();
            //二元函数 输入输出相同
            binaryOperator();
        }
    
        /**
         *
         */
        public static void predicate(){
            Predicate<Integer> predicate = i -> i > 0;
            IntPredicate intPredicate = i -> i > 0;
            System.out.print(predicate.test(6));
            System.out.print(intPredicate.test(-1));
        }
    
        public static void consumer(){
            Consumer<String> consumer = s -> System.out.println(s);
            consumer.accept("我是一个消费者");
        }
    
        public static void function(){
            Function<Integer,String> function = x -> "数字是:"+ x;
            System.out.println(function.apply(88));
        }
    
        public static void supplier(){
            Supplier<String> supplier = () -> "我是一个提供者";
            System.out.println(supplier.get());
        }
    
        public static void unaryOperator(){
            UnaryOperator<Integer> unaryOperator = x -> ++x;
            System.out.println(unaryOperator.apply(1));
        }
    
        public static void biFunction(){
            BiFunction<Integer,Double,Double> biFunction = (x,y) -> {
                ++x;
                ++y;
                return x+y;
            };
            System.out.println(biFunction.apply(1,2.3));
        }
    
        public static void binaryOperator(){
            IntBinaryOperator intBinaryOperator = (x,y) -> x + y;
            System.out.println(intBinaryOperator.applyAsInt(2,3));
        }
    }
    

    Lambda表达式

    相信大家在写一些匿名内部类时编译器会提示你,该处可以转换为lambda,例如:

        public static void main(String[] args){
            IMethod iMethod = () -> {};
        }
    
        public static void useLambda(){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.print("123");
                }
            });
            thread.start();
    
            //lambda
            new Thread(() -> System.out.print("123")).start();
        }
    

    定义一个线程并启动他,传统写法我们用了7行,但其实只有 System.out.print(“123”);这句话是有用的,而lambda表达式我们仅仅一行就达到了相同目的。‘

    lambda表达式主要用于实例化一些函数式接口,替换匿名类的写法,主要形式有:
    (输入) -> {输出} // 我们仅仅关注输入输出即可,若代码有多行大括号里面可以写多行,多行有返回值要显示声明return。输入可以为空即()。

    在lambda表达式中无需像传统写法那样声明参数和返回值类型,它会根据你的上下文通过类型推导你实现的是哪一个接口,从而跟具这个接口的定义知道你变量的类型。这也是为什么函数式接口只能声明一个方法的原因。

    文末链接有附带工程demo链接可以参考哦!

    方法引用

    我们可以直接使用两个冒号::来调用方法

    • 静态方法引用
    • 非静态 实例方法引用
    • 非静态 类方法引用
    • 构造函数方法引用
    public class MethodReferenceDemo {
        public static void main(String[] args) {
            //消费者 方法引用模式
    //        consumer();
            //静态方法引用
    //        callStaticMethod();
            //非静态 实例方法引用
    //        callMethod();
            //非静态 类方法引用
    //        callMethodByClass();
            //构造函数方法引用
    //        callConstructorMethod();
            //数据不变模式
            callMethod2();
        }
    
        public static void consumer(){
            Consumer<String> consumer = System.out::println;
            consumer.accept("我是一个消费者");
        }
    
        private static void callStaticMethod() {
            Consumer<Dog> consumer = Dog::bark;
            consumer.accept(new Dog());
        }
    
        private static void callMethod() {
            Dog dog = new Dog();
            Function<Integer,Integer> function = dog::eat;
            System.out.println("还剩[" + function.apply(3) + "]斤狗粮");
        }
    
        private static void callMethodByClass() {
            BiFunction<Dog,Integer,Integer> biFunction  = Dog::eat;
            System.out.println("还剩[" + biFunction.apply(new Dog(),4) + "]斤狗粮");
        }
    
        private static void callConstructorMethod() {
            Supplier<Dog> supplier = Dog::new;
            System.out.println("new 了一个对象" + supplier.get());
        }
    
        private static void callMethod2() {
            Dog dog = new Dog();
            Function<Integer,Integer> function = dog::eat; //函数声明
            dog = null;
            System.out.println("还剩[" + function.apply(3) + "]斤狗粮");
        }
    
    }
    

    Stream流API

    Stream API是Java 8中加入的一套新的API,主要用于处理集合操作。Stream流API是函数式编程的核心所在,它以一种流式编程来对数据进行各种加工运算。形象的来说你可以把它看作工业中的流水线,将原料放入流中经过操作1、操作2…操作N输出一个产品。Stream也是如此它分为创建操作、中间操作、终止操作。业务逻辑清晰简单、代码看上去优雅不少。

    流通常是由三个部分组成:

    1. 数据源:流的获取,比如list.stream()方法;
    2. 中间处理:中间处理是对流元素的一系列处理。比如过滤filter,排序sorted,映射map;
    3. 终端处理:终端处理会生成结果,结果可以是任何不是流值。

    创建操作

    在这里插入图片描述
    在jdk8中集合数组加入了不少流的方法其中就有直接通过实例或是工具类创建流。如:list.stream(),而数据没有自身API需要借助工具类Arrays来创建。这里通过parallelStream()并行流的模式来创建就可以透明的使用到多线程了。

    注:通过阅读源码可以知Stream类与IntStream、LongStream并没有继承关系

    public class CreateStream {
    
        public static void main(String[] args) {
    //        collectionCreate();
    //        arrayCreate();
    //        numCreate();
            selfCreate();
        }
    
        /**
         * 集合创建
         */
        public static void collectionCreate(){
            List<String> list = new ArrayList<>();
            Stream<String> stream = list.stream();
            Stream<String> parallelStream = list.parallelStream();
        }
    
        /**
         * 数组创建
         */
        public static void arrayCreate(){
            Integer[] array = new Integer[5];
            Stream<Integer> stream = Arrays.stream(array);
        }
    
        /**
         * 数字创建
         */
        public static void numCreate(){
            IntStream.of(1,2,3);
            IntStream.rangeClosed(1,10);
            new Random().ints().limit(10);
        }
    
        /**
         * 自己创建
         */
        public static void selfCreate(){
            Random random = new Random();
            Stream.generate(random::nextInt).limit(20);
            Stream.iterate(2, (x) -> x*2).limit(10).forEach(System.out::println);
        }
    }
    

    中间操作

    中间操作分为有状态操作、无状态操作。无状态操作即该中间操作不依赖与另外的空间来存放临时结果。有状态即需要。这么说还是比较抽象,我们不妨来举个栗子0.0。

    比如说:你的排序操作传统我们要进行排序是否需要依赖额外空间来进行大小的比较。去重操作需要额外空间来存放未重复的值。而像是filter只是单纯返回过滤后的结果无需额外空间。

    这是一种说法,另一种说法该操作与其他操作,没有依赖关系即为无状态,反正则为有状态。这么说也没错,你看像是order操作不是就要等前面操作都执行完才可以执行吗。后面会提到一点就是Stream的操作模式实际上是每一条数据通过A操作B操作C操作来进行的,而到了中间有有状态操作是,必须停下等所有数据都操作到这一步时一起进行,否则你让他如何进行排序呢?

    public class MiddleStream {
        public static void main(String[] args) {
    //        mapOrMapToXXX();
    //        flatMap();
    //        peek();
    //        distinct();
    //        sort();
            limitSkip();
        }
    
        /**
         * map操作    A -> B
         * filter操作
         */
        public static void mapOrMapToXXX(){
            String s = "my name is 007";
            Stream.of(s.split(" ")).map(String::length).forEach(System.out::println);
            System.out.println("-------------");
            Stream.of(s.split(" ")).filter(x -> x.length() > 2).mapToDouble(x -> x.length()).forEach(System.out::println);
        }
    
        /**
         * flatMap操作 A -> B list
         * IntStream/LongStream 并不是stream的子类需要进行装箱
         */
        public static void flatMap(){
            String s = "my name is 007";
            Stream.of(s.split(" ")).flatMap(x -> x.chars().boxed()).forEach(x -> System.out.println((char)x.intValue()));
        }
    
        /**
         * peek 要类型对应
         */
        public static void peek(){
            IntStream.of(new int[]{1,2,3,4,5}).peek(System.out::println).forEach(x->{});
        }
    
        /**
         * distinct
         */
        public static void distinct(){
            IntStream.of(new int[]{1,3,3,4,5}).distinct().forEach(System.out::println);
        }
    
        /**
         * sort
         */
        public static void sort(){
            IntStream.of(new int[]{5,4,3,1,2}).sorted().forEach(System.out::println);
        }
    
        /**
         * limitSkip
         */
        public static void limitSkip(){
            IntStream.of(new int[]{1,2,3,4,5,6,7,8}).skip(2).limit(2).forEach(System.out::println);
        }
    }
    

    这里提到Stream有个特性叫做:惰性求值。什么意思呢?就是当stream没有调用到终止操作时,实际上是不会执行之前的所有过程的。这一点可以在demo工程中有相应的证明方法。 有接触过spark的同学可以将这一特性类比为Transformation和Action。

    终止从操作

    终止操作即流水线的最后一个操作,往往就是返回你所要的产品。
    这里分为短路操作和非短路操作:

    非短路操作:从流中获取所有数据进行运算返回,有可能返回一个或多个值,但必定运用到了所有数据
    短路操作:从流中截取部分数据返回。
    在这里插入图片描述

    public class FinalStream {
        public static void main(String[] args){
    //        forEachOrdered();
    //        collect();
    //        reduce();
    //        minMixCount();
            findFirst();
        }
    
        /**
         * forEachOrdered
         */
        public static void forEachOrdered(){
            IntStream.of(new int[]{1,2,3,4,5,6,7}).parallel().forEach(System.out::println);
    //        IntStream.of(new int[]{1,2,3,4,5,6,7}).parallel().forEachOrdered(System.out::println);
        }
    
        /**
         * collect、toArray
         */
        public static void collect(){
            String s = "hello world!";
            List<String> collect = Stream.of(s.split(" ")).collect(Collectors.toList());
            System.out.println(collect);
        }
    
        /**
         * reduce
         */
        public static void reduce(){
            Integer[] intArr = new Integer[]{1,2,3,4,5,6,7,8,9,10};
            Optional<Integer> optional = Stream.of(intArr).reduce((x, y) -> x + y);
            System.out.println(optional.get());
        }
    
        /**
         * minMixCount
         */
        public static void minMixCount(){
            Integer[] intArr = new Integer[]{1,2,3,4,5,6,7,8,9,10};
            Optional<Integer> optional = Stream.of(intArr).max(Comparator.comparingInt(x -> x));
            System.out.println(optional.get());
        }
    
        //短路操作--------------------------------
        /**
         * findFirst
         */
        public static void findFirst(){
            Optional<Integer> first = Stream.generate(() -> new Random().nextInt()).findFirst();
            System.out.println(first.get());
        }
    }
    
    

    并行流

    这里注释已经很全了就不做冗余说明。

    public class ParallelStream {
    
        public static void main(String[] args) {
    //        createParallelStream();
    //        feature2();
    //        feature3();
            feature4();
    
        }
    
        /**
         * 特性一 并行流线程数
         * 并行流线程数默认为cpu个数
         * 默认线程池
         */
        public static void createParallelStream(){
            IntStream.range(1, 100).parallel().forEach(ParallelStream::printDebug);
        }
    
        /**
         * 特性二 并行再串行 以最后一个流为准
         */
        private static void feature2(){
            IntStream.range(1, 100).parallel().peek(ParallelStream::printDebug).sequential().peek(ParallelStream::printDebug2).count();
        }
    
        /**
         * 特性三 默认线程池与设置默认线程数
         */
        private static void feature3(){
            System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","3");
            IntStream.range(1, 100).parallel().forEach(ParallelStream::printDebug);
        }
    
        /**
         * 特性四 自定义线程池 防止线程被阻塞
         */
        private static void feature4(){
            ForkJoinPool forkJoinPool = new ForkJoinPool();
            forkJoinPool.submit(() -> IntStream.range(1, 100).parallel().forEach(ParallelStream::printDebug));
            forkJoinPool.shutdown();
    
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    
        private static void printDebug(int i){
    //        System.out.println(i);
            System.out.println(Thread.currentThread().getName() + "debug:" + i);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        private static void printDebug2(int i){
            System.err.println(i);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    级联表达式与柯里化

    简单来说就是将一个复杂表达式拆解为多个简单表达式,比如数学中的:
    y=5! 可以等价为 y = 1 * 2 * 3 * 4 * 5

    注意:这里涉及一个基础概念数据不变性,说白了就是匿名类中运用到外部变量时,外部变量需要是常量。细心的你会发现在级联表达式中外部变量均为常量。

    /**
     * 级联表达式和柯里化
     *
     * @author 旭旭
     * @create 2018-08-12 1:09
     **/
    public class CurryDemo {
    
        public static void main(String[] args) {
            //级联表达式
            Function<Integer,Function<Integer,Integer>> fun = x -> y -> x + y;
            //柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数
            //柯里化的意义:函数标准化
            //高阶函数:返回函数的函数
            System.out.println(fun.apply(2).apply(3));
    
            Function<Integer,Function<Integer,Function<Integer,Integer>>> fun2 = x -> y -> z -> x + y + z;
    
        }
    }
    

    收集器(终止操作因为内容较多提出来说明)

    终止操作中将数据以集合方式回收,可以对数据进行分类统计等。

    /**
     * 收集器
     *
     * @author 旭旭
     * @create 2018-08-18 23:43
     **/
    public class CollectorsStream {
    
        public static void main(String[] args) {
            List<Student> students = new ArrayList<>();
            students.add(new Student("一号",7,true,"一年级"));
            students.add(new Student("二号",8,true,"二年级"));
            students.add(new Student("三号",8,false,"二年级"));
            students.add(new Student("四号",9,true,"三年级"));
            students.add(new Student("五号",7,false,"一年级"));
            students.add(new Student("六号",8,true,"二年级"));
            students.add(new Student("七号",10,true,"四年级"));
    
    //        dataToList(students);
    //        summary(students);
    //        partitioning(students);
            group(students);
        }
    
        /**
         * 获取某一数据的集合
         */
        public static void dataToList(List<Student> students){
            List<Integer> list = students.stream().map(Student::getAge).collect(Collectors.toList());
            System.out.println(list);
        }
    
        /**
         * 获取某一数据的汇总值
         */
        public static void summary(List<Student> students){
            IntSummaryStatistics collect = students.stream().collect(Collectors.summarizingInt(Student::getAge));
            System.out.println(collect);
        }
    
        /**
         * 根据某一数据分类
         */
        public static void partitioning(List<Student> students){
            Map<Boolean, List<Student>> collect = students.stream().collect(Collectors.partitioningBy(x -> x.isGender()));
            System.out.println(collect);
        }
    
        /**
         * 根据某一数据分组
         */
        public static void group(List<Student> students){
            Map<String, Long> collect = students.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.counting()));
            System.out.println(collect);
        }
    }
    

    Stream特性

    /**
     * 流运行机制,基本特性
     *
     * @author 旭旭
     * @create 2018-08-19 22:51
     **/
    public class FeatureStream {
        public static void main(String[] args){
    //        feature123();
            feature46();
    //        feature5();
        }
    
        /**
         * 特性一 所有操作都是链式调用一个操作只迭代一次
         * 特性二 每一个中间流返回一个新的流,里面的sourceStage都指向同一个地方就是Head
         * 特性三 Head -> NextStage -> NextStage -> null
         */
        public static void feature123(){
            Random random = new Random();
            Stream<Integer> integerStream = Stream.generate(random::nextInt)
                    .limit(500)
                    .peek(x -> System.out.println("peek -> " + x))
                    .filter(x -> {System.out.println("filter -> " + x);return x > 100000;});
            integerStream.count();
        }
    
        /**
         * 特性四 有状态操作(多个参数操作),会把无状态操作阶段分隔,单独处理。
         * parallel /  sequetial 这个2个操作也是中间操作,但是他们不创建新的流,而是修改
         * Head的并行状态,所以多次调用时只会生效最后一个。
         */
        public static void feature46(){
            Random random = new Random();
            Stream<Integer> integerStream = Stream.generate(random::nextInt)
                    .limit(500)
                    .peek(x -> System.out.println("peek -> " + x))
                    .filter(x -> {System.out.println("filter -> " + x);return x > 100000;})
                    .sorted((x,y) -> {System.out.println("sorted -> " + x);return x - y;})
                    .filter(x -> {System.out.println("filter -> " + x);return x > 100000;})
    //                .parallel()
                    ;
            integerStream.count();
        }
    
    
        /**
         * 特性五 有状态操作并行环境下不一定能并行操作
         */
        public static void feature5(){
            Random random = new Random();
            Stream<Integer> integerStream = Stream.generate(random::nextInt)
                    .limit(500)
                    .peek(x -> print("peek -> " + x))
                    .filter(x -> {print("filter -> " + x);return x > 100000;})
                    .sorted((x,y) -> {print("sorted -> " + x);return x - y;})
                    .filter(x -> {print("filter -> " + x);return x > 100000;})
                    .parallel();
            integerStream.count();
        }
    
        private static void print(String x){
            System.out.println(Thread.currentThread().getName() + " " + x);
        }
    }
    
    

    工程地址

    俗话说的好:光说不练假把式,这里附上工程连接拉,有需要的童靴可以拉下来玩玩。
    https://gitee.com/softcx/functional_programming

    展开全文
  • Java函数式编程详解

    万次阅读 多人点赞 2019-05-05 21:46:49
    函数式编程的优点在提高编码的效率,增强代码的可读性。本文历时两个多月一点点写出来,即作为心得,亦作为交流。 1.Java函数式编程的语法: 使用Consumer作为示例,它是一个函数式接口,包含一个抽象方法accept,...
  • 代码编程

    万次阅读 多人点赞 2019-04-02 09:05:27
    中台之后,便是无代码编程。 规模化的组织,经常要面临这样的挑战:每个应用的基础设施是相同的,部分的代码也是相同的,甚至于它们可能只是数据模型不同而已。结果却导致了,他/她们要一次又一次地重新编写一个...
  • 命令式编程与函数式编程

    千次阅读 2017-11-09 23:05:26
    命令式编程命令式编程(英语:Imperative programming),是一种描述电脑所需作出的行为的编程典范。几乎所有电脑的硬件工作都是指令式的;几乎所有电脑的硬件都是设计来运行机器码,使用指令式的风格来写的。较高级...
  • python 交互式编程与脚本式编程

    千次阅读 2019-10-07 16:56:57
    交互式编程不需要创建脚本文件,是通过 Python 解释器的交互模式进来编写代码。 linux上你只需要在命令行中输入 Python 命令即可启动交互式编程,提示窗口如下: $ python Python 2.7.6(default,Sep 92014,15:04:36...
  • 也知道React是比较遵循函数式编程设计模式的框架,但是一直对函数是编程的定义和理解不是很清楚,到底函数式编程有上面优势,React如何遵循函数是编程,这些都是需要去仔细推敲的,可以让以后的代码写的更为优雅和...
  • 理解反应式编程 你曾有过订阅报纸或者杂志的经历吗?互联网的确从传统的出版发行商那儿分得了一杯羹,但是过去订阅报纸真的是我们了解时事的最佳方式。那时,我们每天早上都会收到一份新鲜出炉的报纸,并在...
  • 命令式编程vs声明式编程

    千次阅读 2016-07-13 15:08:32
    英文原文:Imperative vs Declarative  先统一一下概念,我们有两种编程方式:命令式和声明式。  我们可以像下面这样定义它们之间的不同: 命令式编程:命令“机器”...声明式编程和命令式编程代码例子举个简单
  • 函数式编程思想概论

    千次阅读 2019-06-26 10:57:16
    函数式编程思想概论前言函数λ 演算λ项绑定变量和自由变量约简α 变换β 约简η 变换纯函数、副作用和引用透明性函数式编程与并发编程总结 原文地址 前言 在讨论函数式编程(Functional Programming)的具体内容...
  • 一、什么是Kotlin?Kotlin是一门新兴的Jvm语言,与Scala等充满野心,想要取代Java的Jvm语言不同,Kotlin更像是Java的扩展,它能很好的和已有的...并且,Kotlin对函数式编程提供了比Java8更好的支持。 就在今年,Googl
  • 函数式编程和响应式编程

    万次阅读 2017-06-12 21:37:57
    函数式编程函数式编程是一系列被不公平对待的编程思想的保护伞,它的核心思想是,它是一种将程序看成是数学方法的求值、不会改变状态、不会产生副作用(后面我们马上会谈到)的编程方式。FP 核心思想强调: 声明式...
  • 编程语言的主要类型Common programming paradigms include imperative which allows side effects, functional which disallows side effects, declarative which does not state the order in which operations ...
  • Visual Studio Code列式编程快捷键

    千次阅读 2021-03-22 14:40:54
    ctrl+ALT+↓可 选择多行然后编辑一行即可出现多行一样的代码
  • 第9章 从面向对象到函数式编程 假如本书的写作时间倒退回十年前,书名可能会变成JavaScript面向对象编程思想。自上世纪90年代兴起的面向对象编程思想随Java的繁荣达于顶点,在JavaScript从一门只被用来编写零星的...
  • 契约式编程与防御式编程

    千次阅读 2017-12-11 17:11:45
    都是非常理想化的编程,但在一般的公司里面不论是防御还是契约,实现起来都是比较困难的,例如前端与后端的接口、不同部门同事的交流,按照契约式编程,没人Care你的契约,按照防御式编程代码惨不忍睹,还容易漏掉...
  • 命令式编程 vs 符号式编程

    千次阅读 2018-07-01 13:14:32
    命令/符号式编程的定义并不明确,CXXNet/Caffe因为依赖于配置文件,配置文件看作计算图的定义,也可以被当作符号式编程 源 源网址 什么是symblic/imperative style编程 使用过python或C++对imperative ...
  • Spring5.0响应式编程入门

    万次阅读 2018-06-14 09:35:29
    引言​ 响应式编程是一种面向数据流和变化传播的编程范式。使用它可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型...响应式编程的优势提高了代码的可读性,因此开发人员只需要关注定义业务逻辑。...
  • JAVA代码实现编程式提交Spark任务

    万次阅读 2020-07-29 16:54:11
    三种方法作为记录: 1)直接调用SparkSubmit的main方法 2)SparkLauncher类的launch方法或者startApplication方法 3)使用RestSubmissionClient的run方法 SparkSubmit提交任务 ... String[] param = { ...
  • socket源代码, 多线程阻塞网络编程socket_源代码, 客户端和服务端为多线程编程, 转载其他源代码
  • 函数式编程及响应式编程之己见

    千次阅读 2018-07-22 19:30:51
    1. what is 函数式编程? 函数,在编程中,通常体现为: 输入 =&gt; 执行 =&gt; 结果。他不是命令式的,而是对一段操作进行逻辑封装,拿到输入,就能产出结果... 函数式编程代码量小,干净。 举个例子: ...
  • 1.函数式编程带来的好处函数式编程近些年异军突起,又重新回到了人们的视线,并得到蓬勃发展。总结起来,无外乎如下好处: 1.减少了可变量(Immutable Variable)的声明,程序更为安全。 2.相比命令式编程,少了非常...
  • 1、链式编程,主要通过点‘.’来连接不同的函数调用 iOS上实现链式编程比较好的框架就是Masonry,通过查看Masonry源码,我们发现,每一个函数返回类型都是一个Block变量,然后Block变量中返回的内容就是当前对象...
  • 目录1、 函数式编程内容及讲课顺序1.1、函数式编程内容1.2、函数式编程授课顺序2、函数式编程内容2.1、几个概念的说明2.2、在学习Scala中将方法、函数、函数式编程和面向对象编程关系分析图2.3、函数式编程小结3、为...
  • 华山论剑之契约式编程与防御式编程

    千次阅读 热门讨论 2016-11-21 10:05:10
    OK,《代码大全》给我们提供了一个定义——『防御式编程』,说白了,就是『人类都是不安全、不值得信任的,所有的人,都会犯错误,而你写的代码,应该考虑到所有可能发生的错误,让你的程序不会因为他人的错误而发生...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 590,451
精华内容 236,180
关键字:

代码式编程