首页 文章 精选 留言 我的

精选列表

搜索[hello],共6606篇文章
优秀的个人博客,低调大师

hello Kotlin

1.1 Kotlin的身世 写了许久 Java,有没有发现其实你写了太多冗余的代码? 后来你体验了一下 Python,有没有觉得不写分号的感觉真是超级爽? 你虽然勤勤恳恳,可到头来却被 NullPointerException 折磨的死去活来,难道就没有受够这种日子么? 直到有一天你发现自己已经写了好几十万行代码,发现居然全是 getter 和 setter! 哈哈,实际上你完全可以不用这么痛苦,用 Kotlin 替代 Java 开发你的程序,无论是 Android 还是 Server,你都能像之前写 Java 一样思考,同时又能享受到新一代编程语言的特性,说到这里你是不是开始心动了呢?下面我就通过这篇文章来给大家介绍一下 Kotlin 究竟是何方神圣。 话说,Kotlin 是 JetBrain 公司搞出来的,运行在 JVM 上的一门静态类型语言,它是用波罗的海的一个小岛的名字命名的。从外观上,乍一看还以为是 Scala,我曾经琢磨着把 Scala 作为我的下一门语言,不过想想用 Scala 来干嘛呢,我又不做大数据,而它又太复杂了o(╯□╰)o 用Kotlin创建一个数据类 最初是在 intelliJ 的源码中看到 Kotlin 的,那时候 Kotlin 的版本还不太稳定,所以源码总是编译不过,真是要抓狂啊,还骂『什么破玩意儿!为什么又出来新语言了?Groovy 还没怎么学会,又来个 Kotlin!』话说,Kotlin,难道是『靠它灵』的意思?? 其实经过一年多的发展,Kotlin 1.0已经 release,feature 基本完善,api 也趋于稳定,这时候尝试也不会有那种被坑的感觉了。过年期间也算清闲,于是用 Kotlin 做了个 app,简单来说,就是几个感觉: 思路与写 Java 时一样,不过更简洁清爽 少了冗余代码的烦恼,更容易专注于功能的开发,整个过程轻松愉快 扩展功能使得代码写起来更有趣 空安全和不可变类型使得开发中对变量的定义和初始化倾注了更多关注 啊啊,我再也不用写那个 findViewById 了,真的爽爆有木有! 1.2 第一个Kotlin程序 Kotlin 开发当然使用 JetBrain 系列的 IDE,实际上 intelliJ idea 15 发布时就已经内置了 Kotlin 插件,更早的版本则需要到插件仓库中下载安装 Kotlin 插件——在安装时你还会看到有个Kotlin Extensions for Android,不要管他,已经过时了。安装好以后,我们就可以使用 Kotlin 进行开发了。 接下来我们用 Android Studio 创建一个 Android 工程,比如叫做 HelloKotlin,在 app 目录下面的 build.gradle 文件中添加下面的配置: 这里添加了 Kotlin 对 Android 的扩展,同时也添加了 Kotlin 的 Gradle 插件。 接下来就可以编写 Kotlin 代码了——等等,Android Studio 会帮我们生成一个MainActivity,你可以直接在菜单 Code -> Convert Java file to Kotlin file 将这个 Java 代码转换为 Kotlin 代码。截止到现在,你什么都不用做,程序就已经可以跑起来了。 2、完美为Java开发者打造 2.1 通用的集合框架 我们都知道 Jvm 上面的语言,像什么 Java、Groovy、Jython 啥的,都是要编成虚拟机的字节码的,一旦编成字节码,在一定程度上大家就都平等了。 英雄不问出身啊 有人做过一个非常形象的比喻:Java 虚拟机语言就是打群架。Kotlin 正是充分利用了这一点,它自己的标准库只是基于 Java 的语言框架做了许多扩展,你在Kotlin 当中使用的集合框架仍然跟你在Java当中一样。 举个例子,如果你想要在 Kotlin 中使用 ArrayList,很简单,Java 的 ArrayList 你可以随意使用,这个感觉跟使用 Java 没有任何区别,请看: 当然,Kotlin 标准库也对这些做了扩展,我们在享用 Java 世界的一切资源的同时,还能比原生 Java 代码更滋润,真是爽爆有木有: 2.2 与Java交互 Kotlin 的标准库更多的是对 Java 库的扩展,基于这个设计思路,你丝毫不需要担心 Kotlin 对 Java 代码的引用,你甚至可以在 Kotlin 当中使用 Java 反射,反正只要是 Java 有的,Kotlin 都有,于是有人做出这样的评价: Kotlin 就是 Java 的一个扩展 这样说 Kotlin 显然是不公平的,但就像微信刚面世那会儿要为 QQ 接收离线消息一样,总得抱几天大腿嘛。 有关从 Kotlin 中调用Java的官方文档在此Calling Java code from Kotlin(https://kotlinlang.org/docs/reference/java-interop.html#static-methods-and-fields),其中最常见的就是 Getter / Setter 方法对应到 Kotlin 属性的调用,举个例子: 准备一个Java类 下面是Kotlin代码 所以我们在 Android 开发时,就可以这样: view.background=... textView.text=... 反过来在 Java 中调用 Kotlin 也毫无压力,官方文档C alling Kotlin from Java对于常见的情况作了比较详细的阐述,这里就不再赘述。 3、简洁,可靠,有趣 3.1 数据类 最初学 Java 的时候,学到一个概念叫 JavaBean,当时就要被这个概念给折磨死了。明明很简单的一个东西,结果搞得很复杂的样子,而且由于当时对于这些数据类的设计概念不是很清晰,因而也并不懂得去覆写诸如 equals 和 hashcode 这样重要的方法,一旦用到 HashMap 这样的集合框架,总是出了问题都不知道找谁。 Kotlin 提供了一种非常简单的方式来创建这样的数据类,例如: dataclassCoordinate(valx:Double,valy:Double) 仅仅一行代码,Kotlin 就会创建出一个完整的数据类,并自动生成相应的 equals、hashcode、toString 方法。是不是早就受够了 getter和setter?反正我是受够了。 3.2 空安全与属性代理 第一次见到空类型安全的设计是在 Swift 当中,那时候还觉得这个东西有点儿意思哈,一旦要求变量不能为空以后,因它而导致的空指针异常的可能性就直接没有了。想想每次 QA 提的 bug 吧,说少了都得有三分之一是空指针吧。 Kotlin 的空安全设计,主要是在类型后面加?表示可空,否则就不能为 null。 valanInt:Int=null//错误 valanotherInt:Int?=null//正确 使用时,则: 而对于 Java 代码,比如我们在覆写 Activity 的 onCreate 方法时,有个参数 savedInstanceState: overridefunonCreate(savedInstanceState:Bundle!) 这表示编译器不再强制 savedInstanceState 是否可 null,开发者在覆写时可以自己决定是否可 null。当然,对于本例,onCreate 的参数是可能为 null 的,因此覆写以后的方法应为: overridefunonCreate(savedInstanceState:Bundle?) 通常来讲,教科书式的讲法,到这里就该结束了。然而直到我真正用 Kotlin 开始写代码时,发现,有些需求实现起来真的有些奇怪。 还是举个例子,我需要在 Activity 当中创建一个 View 的引用,通常我们在 Java 代码中这么写: 在 Kotlin 当中呢? 每次用 aTextView 都要加俩!,不然编译器不能确定它究竟是不是 null,于是不让你使用。。这尼玛。。。到底是为了方便还是为了麻烦?? 所以后来我又决定这么写: 这可如何是好?? 其实 Kotlin 肯定是有办法解决这个问题哒!比如上面的场景,我们这么写就可以咯: lazy 是 Kotlin 的属性代理的一个实例,它提供了延迟加载的机制。换句话说,这里的 lazy 提供了初始化 aTextView 的方法,不过真正初始化这个动作发生的时机却是在 aTextView 第一次被使用时了。lazy 默认是线程安全的,你当然也可以关掉这个配置,只需要加个参数即可: 好,这时候肯定有人要扔西红柿过来了(再扔点儿鸡蛋呗),你这 lazy 只能初始化 val 啊,万一我要定义一个 var 成员,又需要延迟初始化,关键还不为 null,怎么办?? lateinit 的使用还是有很多限制的,比如只能在不可 null 的对象上使用,比须为var,不能为 primitives(Int、Float之类)等等,不过这样逼迫你一定要初始化这个变量的做法,确实能减少我们在开发中的遗漏,从而提高开发效率。 至于 lazy 技术,实际上是 Delegate Properties 的一个应用,也就是属性代理了。在 Kotlin 当中,声明成员属性,除了直接赋值,还可以用 Delegate 的方式来声明,这个 Delegate 需要根据成员的类型(val 或者 var)来提供相应的 getValue 和 setValue 方法,比如一个可读写的 Delegate,需要提供下面的方法: 好嘴皮不如来个栗子,下面我们就看一个自定义 Delegate,用来访问 SharedPreference: 需要说明的是,这段代码是我从《Kotlin for Android Developer》的示例中摘出来的。有了这个 Delegate 类,我们就可以完全不需要关心 SharedPreference了,下面给出使用的示例代码: 于是我们再也不需要重复写那些 getSharedPreference,也不用 edit、commit,再见那些 edit 之后忘了 commit 的日子。有没有觉得非常赞! 3.3 扩展类 扩展类,就是在现有类的基础上,添加一些属性或者方法,当然扩展的这些成员需要导入当前扩展成员所在的包才可以访问到。下面给出一个例子: 我们已经介绍过 data class,Coordinate 有两个成员分别是 x 和 y,我们知道通常表示一个二维平面,有这俩够了;然而我们在图形学当中经常会需要求得其极坐标,所以我们扩展了 Coordinate,增加了一个属性 theta 表示角度(反正切的值域为 -π/2 ~ π/2,所以这个式子不适用于二三象限,不过这不是重点了),增加了一个 R 方法来获得点的半径,于是我们在 main 方法中就可以这么用: 那么这个扩展有什么限制呢? 在扩展成员当中,只能访问被扩展类在当前作用域内可见的成员,本例中的x 和 y 都是 public 的(Kotlin 默认 public,这个我们后面会提到),所以可以在扩展方法和属性中直接访问。 扩展成员与被扩展类的内部成员名称相同时,扩展成员将无法被访问到 好的,基本知识就是这些了,下面我们再给出一个实际的例子。 通常我们在 Java 中会自定义一些 LogUtils 类来打日志,或者直接用 android.util.log 来输出日志,不知道大家是什么感受,我反正每次因为要输入 Log.d 还要输入个 tag 简直烦的要死,而且有时候恰好这个类还没有 tag 这个成员,实践中我们通常会把当前类名作为 TAG,但每个类都要做这么个工作,是在是没有什么趣味可言(之前我是用 LiveTemplates 帮我的,即便如此也没有那种流畅的感觉)。 有了 Kotlin 的这个扩展功能,日子就会好过得多了,下面我创建的一个打日志的方法: 有了这个方法,你可以在任何类的方法体中直接写: debug(whatever) 然后就会输出以这个类名为 TAG 的日志。 嗯,这里需要简单介绍 Kotlin 在泛型中的一个比较重要的增强,这个在 Java 中无论如何也是做不到的:inline、reified。我们再来回头看一下 debug 这个方法,我们发现它可以通过泛型参数 T 来获取到T的具体类型,并且拿到它的类名——当然,如果你愿意,你甚至可以调用它的构造方法来构造一个对象出来——为什么 Kotlin 可以做到呢?因为这段代码是 inline 的,最终编译时是要编译到调用它的代码块中,这时候T的类型实际上是确定的,因而 Kotlin 通过 reified 这个关键字告诉编译器,T 这个参数可不只是个摆设,我要把它当实际类型来用呢。 为了让大家印象深刻,我下面给出类似功能的 Java 的代码实现: 而你如果说希望在 Java 中也希望像下面这样拿到这个泛型参数的类型,是不可以的: 就算我们在调用处会写道 debug < Date >(“blabla”),但这个 Date 在编译之后还是会被擦除。 3.4 函数式支持(Lambdas) Java 8 已经开始可以支持 Lambda 表达式了,这种东西对于 Java 这样一个『根红苗正』的面向对象编程语言来说还真是显得不自然,不过对于 Kotlin 来说,就没那么多顾忌了。 通常我们需要执行一段异步的代码,我们会构造一个 Runnable 对象,然后交给 executor,比如这段 java 代码: 用 Kotlin 怎么写呢? executor.submit({//todo}) 一下子省了很多代码。 那么实际当中我们可能更常见到下面的例子,这是一段很常见的 Java 代码,在 Android 的 UI 初始化会见到: 那么我们用 Kotlin 怎么写呢? textView.setOnClickListener{/*todo*/} handler.post{/*todo*/} 在 Anko 这个 Android 库的帮助下,我们甚至可以继续简化 OnClickListener 的设置方式: textView.onClick{/*todo*/} 当然,好玩的不止这些,如果结合上一节我们提到的扩展方法,我们就很容易看到 Kotlin 的标准库提供的类似 with 和 apply 这样的方法是怎么工作的了: 我们通常会在某个方法体内创建一个对象并返回它,可我们除了调用它的构造方法之外还需要做一些其他的操作,于是就要创建一个局部变量。。。有了 apply 这个扩展方法,我们就可以这么写: 这样返回的 StringBuilder 对象实际上是包 "whatever" 这个字符串的。 至于说 Kotlin 对于 RxJava 的友好性,使得我突然有点儿相信缘分这种东西了: 3.5 Pattern Matching 记得之前在浏览 Scala 的特性时,看到: objectHelloScala{ //dosomething } 觉得很新鲜,这时候有个朋友不屑的说了句,Scala 的模式匹配才真正犀利——Kotlin 当中也有这样的特性,我们下面就来看个例子: 咋一看感觉 when 表达式就是一个增强版的 switch——Java 7 以前的 switch 实际上支持的类型非常有限,Java 7 当中增加的对 String 的支持也是基于 int 类型的——我们可以看到 when 不再像 switch 那样只匹配一个数值,它的子式可以是各种返回 Boolean 的表达式。 when 表达式还有一种写法更革命: 只要是返回 Boolean 的表达式就可以作为 when 的子式,这样 when 表达式的灵活性可见一斑。当然,与 Scala 相比,Kotlin 还是要保守一些的,下面给出一个 Scala 类似的例子,大家感受一下,这实际上也可以体现出 Kotlin 在增加 Java 的同时也尽量保持简单的设计哲学(大家都知道,毕竟 Scala 需要智商o(╯□╰)o)。 运行结果如下: atuplewith:1,3 [I@2d554825 3.0,4.0 3.6 如果你是一个SDK开发者 我曾经做过一段时间的 SDK 开发,SDK 的内部有很多类其实是需要互相有访问权限的,但一旦类及其成员是 public 的,那么调用方也就可以看到它们了;而 protected 或者 default 这样的可见性对于子包却是不可见的。 用了这么久 Java,这简直是我唯一强烈感到不满的地方了,甚至于我突然明白了 C++ 的 friend 是多么的有用。 Kotlin 虽然没有提供对于子包可见的修饰符,不过它提供了i nternal:即模块内可见。换句话说,internal 在模块内相当于 public,而对于模块外就是 private 了——于是乎我们如果开发 SDK,那么可以减少 api 层的编写,那些用户不可见的部分直接用 internal 岂不更好。当然有人会说我们应当有 proguard 做混淆,我想说的是,proguard 自然是要用到的,不过那是 SDK 这个产品加工的下一个环节了,我们为什么不能在代码级别把这个事情做好呢? 关于Kotlin的默认可见性究竟是哪个还有人做出过讨论,有兴趣的可以参考这里:Kotlin’s default visibility should be internal(https://discuss.kotlinlang.org/t/kotlins-default-visibility-should-be-internal/1400)。 3.7 DSL 其实我们对 DSL 肯定不会陌生,gradle 的脚本就是基于 groovy 的 DSL,而 Kotlin 的函数特性显然也是可以支持 DSL 的。比如,我们最终要生成下面的 xml 数据: 我们可以构建下面的类: 我们看到在 main 方法当中,我们用 Kotlin 定义的 dsl 写出了一个 Project 对象,它有这与 xml 描述的一致的结构和含义,如果你愿意,可以构造相应的方法来输出这样的 xml,运行之后的结果: 当然,这个例子做的足够的简陋,如果你有兴趣也可以抽象出 "Element",并为之添加 "Attributes",实际上这也不是很难。 3.7 Kotlin与Android的另一些有趣的东西 写了很多代码,却发现它们干不了多少事情,终究还是会苦恼的。比如我一直比较痛苦的一件事儿就是: Buttonbutton=(Button)findViewById(R.id.btn); 如果我需要很多个按钮和图片,那么我们要写一大片这样的 findViewById。。妈呀。。。这活我干不了啦。。 不过用 Kotlin 的 Android 扩展插件,我们就可以这样: 先上布局文件: main.xml 在 Activity 中: 注意到: importkotlinx.android.synthetic.main.load_activity.* 导入这一句之后,我们就可以直接在代码中使用 start、textView,他们分别对应于 main.xml 中的 id 为 start 的按钮和 id 为 textView 的 TextView。 于是你就发现你再也不用 findViewById 了,多么愉快的一件事!!!当然,你还会发现 Toast 的调用也变得简单了,那其实就是一个扩展方法 toast();而 startActivity 呢,其实就是一个 inline加reified 的应用——这我们前面都提到过了。 还有一个恶心的东西就是 UI 线程和非 UI 线程的切换问题。也许你会用 handler 不断的 post,不过说真的,用 handler 的时候难道你不颤抖么,那可是一个很容易内存泄露的魔鬼呀~哈哈,好吧其实我不是说这个,主要是用 handler 写出来的代码 实在 太 丑 了 !! 原来在 Java 当中,我们这么写: 而在 Kotlin 当中呢,我们只需要这么写: 自己感受一下吧。 下面我们再来提一个有意思的东西,我们从做 Android 开发一开始就要编写 xml,印象中这个对于我来说真的是一件痛苦的事情,因为它的工作机制并不如代码那样直接(以至于我现在很多时候居然喜欢用 Java 代码直接写布局)——当然,最主要的问题并不是这个,而是解析 xml 需要耗费 CPU。Kotlin 有办法可以解决这个问题,那就是 DSL 了。下面给出一个例子: 一个 LinearLayou t包含了一个 Button,这段代码你可以直接写到你的代码中灵活复用,就像这样: 这样做的好处真是不少: 比起 xml 的繁琐来,这真是要清爽很多 布局本身也是代码,可以灵活复用 再也不用 findViewById 了,难道你不觉得在这个上面浪费的生命已经足够多吗 事件监听很方便的嵌到布局当中 DSL 方式的布局没有运行时的解析的负担,你的逻辑代码怎么运行它就怎么运行 Anko还增加了更多好玩的特性,有兴趣的可以参考:Anko@Github(https://github.com/Kotlin/anko) 3.8 方法数之痛 我曾经尝试用 Scala 写了个 Android 的 HelloWorld,一切都配置好以后,仅仅引入了 Scala 常见的几个库,加上 support-v4 以及 appcompat 这样常见的库,结果还是报错了。是的,65K。。。而且用 Scala 开发 Android 的话,基于 gradle 的构建会让整个 app 的 build 过程异常漫长,有时候你会觉得自己悟出了广义相对论的奥义,哦不,你一定是晕了,时间并没有变慢。 相比之下,Kotlin 的标准库只有 7000 个方法,比 support-v4 还要小,这正反映了 Kotlin 的设计理念:100% interoperable with Java。其实我们之前就提到,Java 有的 Kotlin 就直接拿来用,而 Scala 的标准库要有 5W 多个方法,想想就还是想想算了。 4、小结 目前 Kotlin 1.0 已经 release,尽管像 0xffffffff 识别成 Long 类型这样的 bug 仍然没有解详情(https://youtrack.jetbrains.com/oauth?state=%2Fissue%2FKT-4749): valint:Int=0xffffffff//error valanotherInt:Int=0xffffffff.toInt()//correct 不过,Kotlin 的教学资源和社区建设也已经相对成熟,按照官方的说法,Kotlin可以作为生产工具投入开发,详情可以参考:Kotlin 1.0 Released: Pragmatic Language for JVM and Android(http://blog.jetbrains.com/kotlin/2016/02/kotlin-1-0-released-pragmatic-language-for-jvm-and-android/)。 敢于吃螃蟹,多少有些浪漫主义色彩,我们这些程序员多少可以有些浪漫主义特质,不过在生成环境中,稳定高于一切仍然是不二法则。追求新技术,一方面会给团队带来开发和维护上的学习成本,另一方面也要承担未来某些情况下因为对新技术不熟悉而产生未知问题的风险——老板们最怕风险了~~ 基于这一点,毫无疑问,Kotlin 可以作为小工具、测试用例等的开发工具,这是考虑到这些代码通常体量较小,维护人数较少较集中,对项目整体的影响也较小;而对于核心代码,则视情况而定吧。 就我个人而言,长期下去,Kotlin 很大可能会成为我的主要语言,短期内则仍然采用温和的改革方式慢慢将Kotlin 渗透进来。

优秀的个人博客,低调大师

Hello AgentScope Java

作者:远云 随着 LLM 应用的飞速发展,越来越多的 Agent 应用开始走近每个人。围绕着 Agent 应用的核心,目前业界有零代码、低代码和高代码三条主流的技术路线。AgentScope 作为 Python 社区中受到广泛应用的高代码框架,在 Java 生态下的需求也越来越大。 今天,我们很高兴地宣布AgentScope Java v0.2 版本正式发布了,具备了所有核心的 ReActAgent 的能力。 第一性原则:透明度 AgentScope 的首要设计目标是对开发者透明。 当下,许多 Agent 框架将底层的调度进行了深度的封装,这固然会给用户带来一些概念上的简化,但是也带来了遇到问题时排查的复杂度。AgentScope 不同: Prompt Engineering: 用户可以自己修改所有提示词相关的内容。 API 调用: 每一次 API 调用都能够被定位。 Agent 构建: 所有 Agent 的配置都来自用户确定性的配置。 决策过程: Agent 的推理、执行过程都可以通过 Hook 对外暴露。 三分钟构建一个智能体 以下是一个简单的智能体示例: Maven 依赖 <dependency> <groupId>io.agentscope</groupId> <artifactId>agentscope-core</artifactId> <version>0.2.1</version> </dependency> ReActAgent publicclassHelloAgentScope{ publicstaticvoidmain(String[]args){ // 创建 ReActAgent ReActAgent agent = ReActAgent.builder() .name("Assistant") .model(DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .build()) .build(); // 调用智能体 Msg response = agent.call( Msg.builder() .role(MsgRole.USER) .content(TextBlock.builder() .text("你好,请介绍一下自己") .build()) .build() ).block(); System.out.println(response.getTextContent()); } } 至此,一个 Agent 就构建完成了。在这个示例中,ReActAgent是 AgentScope 的核心,我们后面几乎所有的功能都是基于它的。 架构概览 和 Python 版本类似,AgentScope Java 采用分层架构: 基础组件层(Foundational Components) Message:统一的消息抽象对象,通过一套数据结构支持文本、图像、音频、视频。 Model API:支持 DashScope、OpenAI 等主流模型提供商。通过 Formatter 机制屏蔽不同模型提供商的格式差异。 Tool:允许用户定义工具给 LLM 使用,支持同步/异步、流式/非流式等 API 风格。 智能体基础设施层(Agent-level Infrastructure) ReAct 范式:核心 Agentic 实现,通过推理(Reasoning)再行动(Acting)的迭代循环。 Agent Hooks:运行于 ReActAgent 内部,允许用户对 Agent 执行的过程进行监测、修改。 状态管理:会话持久化组件,支持用户对话状态的保存和恢复。 多智能体协作层(Multi-Agent Cooperation) MsgHub:支持多个 Agent 之间共享消息,实现多 Agent 沟通协作的工具。 Pipeline:组合多个 Agent 按照特定(顺序、并行等)策略执行的工具。 部署层(Deployment) AgentScope Runtime:解决分布式部署与安全隔离问题的企业级运行时基础设施,提供工具运行沙箱、A2A 协议、远程部署等能力。 AgentScope Studio:提供开发阶段到运行阶段的可视化调试、观测能力,为开发者从 0 到 1 的开发提速。 Reasoning and Acting ReAct(Reasoning and Acting)是 AgentScope 最核心的实现范式。其设计思路很简单:将思考和执行分离,通过迭代循环解决问题。 工作原理 Reasoning(推理)阶段:Agent 会基于当前的上下文分析,决定下一步行动: 理解用户意图 评估已有信息(上下文) 确定需要调用的工具及参数 Acting(行动)阶段:执行 Reasoning 阶段所需的获取数据行为。 并行执行工具调用 收集执行结果 将结果计入记忆 迭代控制:ReActAgent 会不断执行 Reasoning 和 Acting 的迭代,如果模型在最大迭代轮内完成迭代则会正常结束,如果未完成则会触发 summary 的能力,进行会话总结。 为 ReActAgent 添加工具 为了让 ReActAgent 真正可以实现 Acting,需要为 ReActAgent 添加对应的工具。 这里以一个 Weather Assistant 为例子: // 定义工具类 publicclassWeatherTools{ @Tool(description ="获取指定城市的天气信息") publicStringgetWeather( @ToolParam(name ="city", description ="城市名称")Stringcity) { // 实际应用中调用天气 API returnString.format("%s:晴天,气温 25 ℃", city); } } // 注册工具 Toolkittoolkit =newToolkit(); toolkit.registerTool(newWeatherTools()); // 构建带工具的 ReActAgent ReActAgentagent =ReActAgent.builder() .name("WeatherAssistant") .sysPrompt("你是一个天气助手,可以查询城市天气信息。") .model(DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .build()) .toolkit(toolkit) .build(); // 调用智能体 Msgresponse = agent.call( Msg.builder() .role(MsgRole.USER) .content(TextBlock.builder() .text("北京今天天气如何?") .build()) .build() ).block(); 执行流程: 用户问题:北京今天天气如何? ↓ [推理]需要查天气,决定调用 getWeather("北京") ↓ [行动]执行工具 → "北京:晴天,气温25℃" ↓ [推理]已获取信息,生成回答 ↓ 回答:根据查询结果,北京今天晴天,气温25℃ ReActAgent 核心特性 除了基础的 Reasoning 和 Acting 能力,AgentScope 的 ReActAgent 还具备多个特性。 1. 多模态消息支持 ReActAgent 可以处理多模态输入,不限于纯文本: // 创建支持视觉的 ReActAgent(使用视觉模型) ReActAgentvisionAgent=ReActAgent.builder() .name("VisionAssistant") .model(DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-vl-plus") // 视觉模型 .build()) .build(); // 发送包含图片的消息 Msgresponse=visionAgent.call( Msg.builder() .role(MsgRole.USER) .content(List.of( TextBlock.builder().text("请分析这张图片的内容").build(), ImageBlock.builder().source(URLSource.builder().url("https://example.com/image.jpg").build()).build() )) .build() ).block(); 支持的多模态内容类型:TextBlock、ImageBlock、AudioBlock、VideoBlock。 2. 钩子机制 为 ReActAgent 添加钩子,监控和扩展其行为。这里以前文中用到的 WeatherAssistant 为例子添加钩子,实时看到智能体的思考和执行过程: // 定义调试钩子,显示完整的 ReAct 执行过程 Hook debugHook = new Hook() { @Override public <T extends HookEvent> Mono<T> onEvent(T event) { try{ switch (event) { casePreReasoningEvent e -> { System.out.println("\n[推理] 智能体开始思考..."); } casePostReasoningEvent e -> { System.out.println("[推理] 推理结果:"+ new ObjectMapper().writeValueAsString(e.getReasoningMessage())); } casePostActingEvent e -> { System.out.println("[行动] 执行工具 → "+ new ObjectMapper().writeValueAsString(e.getToolResult())); } casePostCallEvent e -> { System.out.println("[推理] 已获取信息,生成回答"); System.out.println("回答:"+ e.getFinalMessage().getTextContent()); } default -> {} } ; } catch (JsonProcessingException e) { ... } returnMono.just(event); } }; // 将钩子添加到 WeatherAssistant ReActAgent weatherAgent = ReActAgent.builder() .name("WeatherAssistant") .sysPrompt("你是一个天气助手,可以查询城市天气信息。") .model(DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .build()) .toolkit(toolkit) // 前文中定义的 Toolkit .hook(debugHook) // 添加调试钩子 .build(); // 查询天气 Msg response = weatherAgent.call( Msg.builder() .role(MsgRole.USER) .content(TextBlock.builder() .text("北京今天天气如何?") .build()) .build() ).block(); // 输出示例: // [推理] 智能体开始思考... // [推理] 推理结果:{"id":"xxx","name":"WeatherAssistant","role":"ASSISTANT","content":[{"type":"tool_use","id":"call_xxx","name":"getWeather","input":{"city":"北京"},"content":null}],"metadata":null,"timestamp":"xxx"} // [行动] 执行工具 → {"type":"tool_result","id":"call_xxx","name":"getWeather","output":[{"type":"text","text":"\"北京:晴天,气温 25 ℃\""}],"metadata":{}} // [推理] 智能体开始思考... // [推理] 推理结果:{"id":"xxx","name":"WeatherAssistant","role":"ASSISTANT","content":[{"type":"text","text":"北京今天天气晴朗,气温为25℃。建议外出时注意防晒,祝您拥有愉快的一天!"}],"metadata":null,"timestamp":"xxx"} // [推理] 已获取信息,生成回答 // 回答:北京今天天气晴朗,气温为25℃。建议外出时注意防晒,祝您拥有愉快的一天! 3. 会话持久化 保存和恢复 ReActAgent 的状态: // 创建 ReActAgent ReActAgentagent=ReActAgent.builder() .name("PersistentAgent") .model(DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .build()) .memory(newInMemoryMemory()) .build(); // 保存会话 SessionManager.forSessionId("session-001") .withJsonSession(Path.of("./sessions")) .addComponent(agent) .saveSession(); // 下次启动时恢复 SessionManager.forSessionId("session-001") .withJsonSession(Path.of("./sessions")) .addComponent(agent) .loadIfExists(); // agent 现在恢复到了之前的状态,可以继续对话 4. 结构化输出 让 ReActAgent 返回类型安全的结构化数据: // 定义输出结构 publicclassWeatherReport{ publicString city; publicinttemperature; publicString condition; publicList<String> suggestions; } // ReActAgent 调用时指定输出类型 Msg response = agent.call( Msg.builder() .role(MsgRole.USER) .content(TextBlock.builder() .text("分析北京的天气并给出建议") .build()) .build(), WeatherReport.class// 指定结构化输出类型 ).block(); // 提取结构化数据 WeatherReport report = response.getStructuredData(WeatherReport.class); System.out.println("城市: "+ report.city); System.out.println("温度: "+ report.temperature); 避免了文本解析的不确定性,编译期就能发现类型错误。 5. 多智能体协作 多个 ReActAgent 可以通过 Pipeline 协作: // 创建模型配置 DashScopeChatModelmodel=DashScopeChatModel.builder() .apiKey(System.getenv("DASHSCOPE_API_KEY")) .modelName("qwen3-max") .build(); // 创建多个 ReActAgent ReActAgentdataCollector=ReActAgent.builder() .name("DataCollector") .model(model) .build(); ReActAgentdataAnalyzer=ReActAgent.builder() .name("DataAnalyzer") .model(model) .build(); ReActAgentreportGenerator=ReActAgent.builder() .name("ReportGenerator") .model(model) .build(); // 顺序执行:智能体依次处理 Msgresult=Pipelines.sequential( List.of(dataCollector, dataAnalyzer, reportGenerator), inputMsg ).block(); // 并行执行:多个智能体同时处理 List<Msg> results = Pipelines.fanout( List.of(dataCollector, dataAnalyzer, reportGenerator), inputMsg ).block(); Roadmap AgentScope Java 自 2025 年 9 月开源以来,当前 v0.2 版本已具备 ReActAgent 核心能力。 我们计划于 11 月底发布 v1.0 版本,届时将新增 RAG、Plan、Tracing、Evaluation 及 Studio 等全套功能,标志着框架正式生产可用;Runtime v1.0 也将同步上线,提供涵盖安全沙箱、A2A Agent 在内的企业级落地方案。随后在 12 月,我们将进一步推出基于 ReMe 的上下文管理与基于 Trinity-RFT 的强化学习最佳实践。 在技术演进层面,我们正持续探索更高效、智能的上下文工程与多 Agent 协同范式,致力于支撑更强大的 AI 应用构建。此外,针对 Agent 流量呈现的“二八定律”特征(头部 20% 的 Agent 承载了 80% 的流量),我们在架构上全力推进 Serverless 化,通过实现毫秒级冷启动与混合部署,帮助开发者在应对高并发的同时,显著降低部署成本并提升效率。 未完待续 本文作为 AgentScope Java 系列推文的首篇,受篇幅限制只能抛砖引玉,在接下来还会有更多的干货: AgentScope Runtime:帮助开发者实现 Agent 应用从 1 到 100,提供工具运行沙箱、A2A 协议、远程部署等强大能力。 Agent 开发范式讨论:Workflow or Agentic?AgentScope 基于狼人杀游戏的 Agent 实践分享。 Meta Tool:面对日益膨胀的 Tool Definition,AgentScope 的解决方案。 Plan:使 Agent 能够自主拆解复杂任务并系统性地执行。 如果你觉得 AgentScope Java 不错,欢迎给我们的项目 Star~ https://github.com/agentscope-ai/agentscope-java

优秀的个人博客,低调大师

Modern.js:Hello world!

概要 ​7月,字节跳动 Web Infra 做过一次主题为《迈入现代 Web 开发(字节跳动的现代 Web 开发实践)》[1]的分享,在分享中我们梳理了「传统前端技术栈」的典型组成部分,展示了其中每个部分都存在的瓶颈问题。并介绍了在这些问题的驱动下,业x x x x界正在发生从「传统 Web 开发范式」到「现代 Web 开发范式」的「范式转移」。在这个分享的最后预告了 Modern.js 的开源项目。 10 月 27-28 日的稀土开发者大会[2]上,字节跳动 Web Infra 正式发起Modern.js[3]开源项目。在专场分享《介绍 Modern.js —— 现代 Web 工程体系》中,第一部分先介绍了业界和字节内部的前端开发、Web 开发在发生哪些影响深远的变革,从这些变革的角度,展示了基于 Modern.js 的现代 Web 开发。 这些变革包括: 「更多「前端开发者」成为「应用开发者 / 产品开发者」。」 先讨论了什么根本因素在驱动这种转变,"Frontend Focused" 的意义,指出服务器端开发门槛不断降低的长期趋势、原有基建的缺陷,用 Modern.js 演示了「一体化、无服务器化的全栈开发」、「以客户端为中心的 Web 开发」。 「从「前后端分离」到「前后端一体化」。」 分析了「前后端分离」产生的两种前端项目,为什么其中一种是「假分离」,另一种「不完整」,用 Modern.js 演示了「前后端一体化」在哪些地方带来改变。 「Meta」「Framework 取代传统「前端三剑客」。」 分析了四代「前端三剑客」,以及每一代都被下一代的成员「吞并」的规律,结合字节内部的真实案例,讲解了 Meta Framework 的角色。 「形成基于「前端技术」的成熟 GUI 软件研发体系。」 先明确了「前端技术」的定义, 结合 Modern.js 的功能和设计,讨论了如何实现「充分抽象」,才能解决 DX 和 UX 的矛盾。 「智能化、平台化、低码化。」 接下来第二部分系统介绍 Modern.js 的六大要素,包括: 「普及:现代 Web 开发范式。」 回顾了这种范式的 9 大主要特征。 「核心:现代 Web 应用(「「MWA」」)。」 从 Universal App、一体化、应用架构、Runtime API 这四个角度来了解 MWA。 在应用架构部分介绍了 Modern.js 中 Model 的设计和背景。 「内置:前端工程最佳实践。」 列举了几个典型的最佳实践,包括 Post-Webpack Era 的新工具趋势、Modern.js 的 Unbundled 开发,Modern.js 推荐的「CSS 三剑客」,Modern.js 微前端项目跟直接使用 Garfish 的微前端项目对比、模块工程方案和 Monorepo 工程方案中的最佳实践。 「包含:Web 开发全流程。」 演示了 Modern.js 在「编码」环节的微生成器功能、在「调试环节」的微前端调试。 「提供:工程标准体系。」 「鼓励:定制工程方案。」 末尾介绍了除已经发布的开源项目,还有哪些对现代 Web 开发者有帮助的事情在发起和推进中。也介绍了 Modern.js 当前高优的社区计划。 欢迎大家扫码入群,一起讨论交流 分享实录 大家好,我是来自字节跳动 Web Infra 的宋振伟,在字节跳动,我们部门负责打造和发展「Web 技术中台」和「前端研发体系」。 今年7月,我们做过一次主题是《字节跳动的现代 Web 开发实践》[1]的分享,在分享中我们梳理了「传统前端技术栈」的典型组成部分,展示了其中每个部分都存在的瓶颈问题。 也介绍了在这些问题的驱动下,业界正在发生从「传统 Web 开发范式」到「现代 Web 开发范式」的「范式转移」。 在这个分享的最后也预告了 Modern.js 开源项目。 昨天上午的主题演讲[6]中,字节跳动正式发布了Modern.js[7]。今天的专场分享,我想结合字节内部的变革和实践,介绍基于 Modern.js 的现代 Web 开发,和所带来的实际效果。 议程 今天的分享可以分成三个部分。 昨天的主题演讲有说到,整个业界和字节内部的前端开发、Web 开发,都在发生着影响深远的变革,我们首先从这些变革的角度,看下基于 Modern.js 的现代 Web 开发是什么样子,有什么区别。 然后,我们整体看下 Modern.js 有哪些要素和收益。 最后再看下除了已经发布的开源项目,还有哪些对现代 Web 开发者有帮助的事情在发起和推进中。 我们先来看第一部分「现代 Web 开发」 一、基于 Modern.js 的现代 Web 开发 1.1 更多「前端开发者」成为「应用开发者 / 产品开发者」 可以从这五个方面的变革,来展示「现代 Web 开发」是什么样子。 这五个变革之间是承前启后的关系。首先最根本的推动力,不是来自技术侧,不是前端开发者一厢情愿的发展自己主观的技术偏好,而是在互联网和 IT 行业、市场需求、用户产品这一侧的大趋势,需要更多「前端开发者」成为「应用开发者」或「产品开发者」,鼓励和倒逼着技术领域,不断产生更有利于这种需求的技术形态和基础设施。 当传统技术范式遇到瓶颈,不再能进一步适应需求,就会发生「范式转移」,出现从一开始就针对这种需求重新设计的、新一代的技术范式。 这种转变推动前端技术领域出现了「从分离到一体化」、新一代「前端三剑客」的变革。 这种变革带来的新一代技术标准和基础设施,开始形成完全基于「前端技术」的成熟 GUI 软件研发体系,并且进一步朝着平台化、低码化的方向发展。 1.1.1 "Frontend Focused" 的意义 我们刚才一直在说「前端」,「前端」这个概念似乎一直是只有开发者才关心的技术细节,但最近几年,却变成了商业领域、投资机构也都很关心的事情,全球市场上涌现出越来越多的新一代云平台和研发工具产品,多数都涉及前端研发特有的需求和模式,其中还有很多像 Vercel 这样明确「专注于前端」的产品。 就像幻灯片上这张图,云计算和研发产品最初是从最接近机器的底层开始发展,从虚拟化,到容器编排,到基于容器技术的各种平台化、服务化的研发工具形态,这个阶段是后端技术主导的,整个趋势是越来越向上层发展,越来越接近市场和商业价值最终所在的地方——也就是面向用户的产品,因此必然会发展到前端技术主导的抽象层,让应用开发和产品开发能更专注于用户需求,而越来越不需要关心服务器端的复杂性和专业技术细节。 所以市场需求会趋向于推动应用开发方式往「专注于前端」的方向的发展,专注于前端就是专注于用户,而专注于用户是多数企业、产品的根本利益所在 1.1.2 最大的开发者群体 另一方面,从进入移动互联网时代开始就不断大幅增加的应用开发需求,现在不但没减弱,反而还在加强,比如幻灯片上 IDC 的预测,要满足这么庞大的应用开发需求,传统开发方式和人才储备是很不够的,需要让尽可能多的开发者能独立、完整的开发这些应用,而前端技术栈的开发者,正是最大的开发者群体和技术社区。 所以在用户、产品、市场这一侧,一直有趋势和压力,需要更多「前端开发者」成为「应用开发者」或「产品开发者」,鼓励和倒逼着技术领域,不断产生更有利于这种需求的技术形态和基础设施。 1.1.3 服务器端开发门槛不断降低 在这种客观趋势的推动下,基于 Web 技术的应用开发中,服务器端的占比和门槛一直在不断下降,国内大厂的中台建设,提供了大量不跟特定客户端捆绑、专注于数据需求和底层业务逻辑的 API,让产品开发更聚焦在上层的客户端业务逻辑。还有 BaaS 和基于云函数的后端云 Serverless,也进一步降低服务器端的门槛,让前端开发者能更独立的、端到端的完成产品开发。 但要进一步降低门槛,提高效率,这些基础设施的一个缺陷就暴露出来,就是他们都把应用依赖的 API,放在应用项目之外维护,跟前端研发是割裂的。 还有一个缺陷是它们不解决 API 之外的服务器端需求,比如路由、SSR 等。 有一个字节内部的典型例子,前端开发者在自己实现的 SSR 项目中,始终用 HTTP 方式请求外网域名的 API 来获取数据,导致 SSR 频繁超时,HTML 响应慢严重影响用户体验。可以给前端开发者做培训,让他们具备足够的服务器端开发思维和知识,知道要在 SSR 环节切换成内网的请求方式,还要考虑缓存机制等,但更根本的解决方法是屏蔽这种服务器端问题和实现细节,自动处理这些问题。 1.1.4 一体化、无服务器化的全栈开发 因此服务器端门槛不断降低的趋势,自然会发展到「一体化、无服务器化的做全栈开发」的阶段,让前端开发者直接开发 「接近纯前端的项目」而不是 「Node.js 框架项目」,感觉就像没有服务器一样。 幻灯片上是这次分享中第一个 Modern.js 的 demo, 左侧是一个 Modern.js 应用项目的完整目录结构,src/ 目录下的应用主体,可以像调用普通函数文件一样访问 api/ 目录下的 BFF 函数,不需要了解网络细节,Modern.js 会自动基于 BFF 函数的路径、参数等自动生成 REST API,在 CSR 过程中自动请求。 接下来我们在 package.json 的 modernConfig 配置里启用 SSR、「差异化分发」和「自动 Polyfill」,可以看到这些功能不用增加代码逻辑,只需要静态开关配置。 构建后,幻灯片左侧可以看到产物里的 HTML 和 JS 都有 es6 和 es5 两个版本,用户访问时应用的时候,Modern.js 的 Web Server 会根据浏览器 UA 选择分发 es6 版本还是 es5 版本,也就是「差异化分发」。 右边上面的图是现代浏览器的访问结果,不会返回任何 polyfill 代码, 下面的图是低版本浏览器的访问结果, Web Server 会自动提供这个 UA 需要的 polyfill 代码。 可以看到 Modern.js 不但支持一体化的开发 BFF,也满足 BFF 之外的服务器端需求,尽可能自动利用自带的 web server 去做性能优化和提供产品级的兼容性,同时开发体验仍然是无服务器化的 之前我们启用了 SSR, 左侧图上高亮的 HTML 片段,已经包含了通过 BFF 请求到的数据,会根据应用运行的方式自动选择最高效的请求方式。 这种自动优化不会阻碍开发者对技术细节的掌控,右边这张图展示了 BFF 函数也会生成标准的 REST API,可以手动调用。 1.1.5 以客户端为中心的 Web 开发 这种一体化、无服务器化的全栈开发进一步发展,自然会得到一种客户端为中心的 Web 开发方式。 比如在传统 Web 开发中,要实现常见的权限识别和重定向,除了前端页面的逻辑,还需要在服务器端的路由中,添加实现跳转的业务逻辑。比如图上,在访问 home 页面的时候根据 cookie 的值决定要不要重定向到登陆页面。 同样的需求,在以客户端为中心的 Web 开发方式中,可以一体化的在客户端代码里实现,比如前面已经启用 SSR 的 Modern.js 项目,只需要添加 Redirect 组件,就可以实现和刚才完全相同的权限识别和重定向效果,访问页面时会根据 cookie 决定要不要返回 302 状态码。整个实现过程是客户端思维的。 以客户端为中心,不代表不能掌控服务器端,不能直接写服务器端业务逻辑。 如果已经习惯 Node.js 框架的开发方式,可以 server 目录的钩子文件里,对框架自带的 Web Server 添加自定义逻辑,比如自由添加中间件,可以在这个局部,用自己熟悉的传统 Web 开发方式实现权限识别和重定向。 1.2 从「前后端分离」到「前后端一体化」 Web 项目的技术栈也在转变,相当于是先发展出「前后端分离」,然后又用新方式回归了 「前后端一体化」 1.2.1 「前后端分离」 以前的 Web 开发就像图上这个 Ruby on Rails 项目,图中粉色的前端代码,「寄居」在图中绿色的后端 Web 框架项目中的,前端和后端会互相干扰互相拖累,做工程建设也比较麻烦。 之后 Web 开发普遍转变到「前后端分离」的模式,分离后的前端项目和后端项目,都倾向特定的类型。 后端项目不倾向包含 Web 的功能,而这时的前端项目可以归纳为两种类型。 MERN 这种项目类型相当于又回到了分离前的状态,整个项目是基于 Node.js 框架的,前端被嵌在里面。这种结果其实反映出「前后端分离」实现的更多是分工上的分离,而不是技术架构上的分离,在技术架构上仍然没有摆脱以服务器端框架为中心的 Web 开发。 从 MERN 项目的结构可以看到,它不但是假分离,而且也不算一体化,React 代表的前端部分和 Node.js 框架代表的后端部分,在项目里是泾渭分明的,没有真正融合到一起去。 使用 Node.js 框架的项目,多数属于这个类型。 「前后端分离」模式中另一种前端项目类型,我们称作「老一代 JAMstack」,这种类型没有假分离问题,就是纯粹的前端项目。可以实现 SPA 和 MPA,也能基于编译工具实现 SSG(静态网站生成)。靠静态托管来运行,鼓励在 CSR 中调用 API 满足动态的应用需求。 「老一代 JAMstack」最大的问题是,虽然分离成了独立的项目,却不足以承担完整的应用开发,只能产出静态文件,依靠外部的 Web 服务器去运行,无法实现 SSR,三大组成部分里的 API,也需要在项目之外,用云函数、独立后端项目等方式来实现,不能跟着项目一起迭代。 用 CRA 或直接用 webpack 搭建的项目,多数属于这个类型。 1.2.2「前后端一体化」 在前面说的需要更多「前端开发者」成为「应用开发者」的背景下,新一代的 JAMstack 项目用「客户端为中心」的「前后端一体化」方式,解决了上面说的问题 新一代 JAMstack 的三大组成部分虽然没变,对应的内容却有很大变化,JS 部分更加函数化和组件化、以 JS 为中心,HTML 可以完全不在项目中出现,自动生成。BFF API 变成项目自包含。和之前简单的静态托管相比,基于前端 Serverless 平台可以实现 SSR、SPR 等动态能力,即使是静态页面,也可以获得很多好处,比如前面展示的 「差异化分发」。 图上是用 Modern.js 的 demo 来展示新一代 JAMstack 项目。在开发中只需要聚焦在 JS 代码上,不论是 SPA 还是 MPA,HTML 都是自动生成的。不论是 SSR 渲染的代码,还是 API 逻辑,构建之后按照规范输出到 dist 下的不同目录,构建产物规范是 Serverless 友好的,支持把 Web、SSR、BFF 等拆分成不同服务器。 前面提到 Modern.js 倾向于 JS 为中心、自动生成 HTML。但不阻碍开发者自己掌控 HTML。图上是 Modern.js 渲染 HTML 的默认模板。 一体化 BFF 的调用,在前面的例子演示过,这里可以看到 BFF 函数的文件路径是有约定的,可以实现任意设计的 REST API。 构建产物会针对 BFF、Web、SSR 分别生成独立可运行的 Server,这是对前端 Serverless 平台更友好的,Serverless 平台可以自主选择让 BFF、Web、SSR 用不同方式在独立进程中运行,不会互相干扰。比如在 SSR 环节遇到 app 代码的的内存泄露导致 SSR 超时,Web Server 不受影响,可以自动降级到 CSR 模式,返回静态的 HTML 给用户作为兜底,用户的 HTML 请求始终不会超时或挂掉。 对于 SSR,同样可以前后端一体化的开发,图里高亮的 useLoader 函数中的代码,同时适用于 SSR 和 CSR,如果这个 Loader 在 SSR 中已经预加载,CSR 就会自动跳过,否则会执行。 SPR 相当于有缓存机制的 SSR,在 Modern.js 里也可以一体化的开发,只要使用这个预渲染组件。 SSG 实际上就是在编译时运行的 SSR,在 Modern.js 里只要配置 SSG 路由,就会自动启用这种编译逻辑,给路由生成静态 HTML。CSR、SSR、SSG 都是用同一份代码。 1.3 新一代「前端三剑客」和 Meta Framework 除了在技术栈层面向「前后端一体化」转变,在工程层面,传统的「前端三剑客」也在转变成元框架这种新的工程基建。 1.3.1 传统「前端三剑客」 先来回顾下传统的「前端三剑客」,第 1 代和第 2 代如图上所示,也被大家熟知。而第 3 代「前端三剑客」由视图框架、Node.js 命令行、Node.js 框架三个方向组成。 其中 Node.js 命令行代表了工程化,其中最典型的是像 Webpack 这样的打包工具,以及 Babel、PostCSS 这样的编译工具。 视图框架和 Node.js 框架很好理解,就是之前讨论的 MERN 项目中的前端和后端部分 1.3.2 第 4 代「前端三剑客」 随着现代 Web 开发范式的发展,第4代「前端三剑客」的轮廓已经越来越明显,由元框架、前端 PaaS、低代码三个方向组成。其中低代码方向在昨天晚上稀土大会的低码专场已经介绍过,而 Modern.js 就属于元框架这个方向。 从这张图可以清楚的看到,每一代前端三剑客中,都有一个方向,把上一代前端三剑客完整包含在自己里面,变成不需要太关心的底层,让自己取代他们成为前端开发的新地基。 第 3 代中的视图框架,就扮演这样的角色,把第二代的 HTML、CSS、JS 封装在自己里面,而第 4 代中的元框架,又对视图框架、Node.js 框架、Node.js 命令行做了整合和抽象,成为前端开发和工程建设性起点,元框架扮演了过去 Webpack、React 扮演的角色 这张 JS 框架的 S 曲线图,也能体现这种转变。在左边这个时期,发展的前沿、开发的起点,都是 React、Vue、Svelte 这样的视图框架,新的视图框架项目也层出不穷。现在已进入右边这个时期,前沿收敛到基于 React 发展更上层的元框架。 Modern.js 作为现代 Web 工程体系,是由元框架组成的,提供三大工程类型,鼓励开发者基于工程类型建设自己的业务工程方案。 以字节内部的「火山引擎子应用工程方案」为例,初始化的目录结构没有什么变化,只在配置中默认加载了自己的框架插件,插件中通过 server 提供的 hook 修改渲染后的 HTML ,在原来的 HTML 上套了层壳,也就是右下角截图中火山引擎统一的顶栏和左侧导航栏。 这样建设出来的工程方案,既能满足垂直场景的需求或自己的偏好,又能保持跟三大工程类型的兼容,自动获得 Modern.js 的能力和收益 1.4 基于「前端技术」的成熟 GUI 软件研发体系 在「前端开发者」成为「应用开发者」的大背景下,技术栈、工程基建的发展,开始形成基于「前端技术」的成熟 GUI 软件研发体系。 1.4.1 什么是「前端技术」 先明确一下我们一直说的前端技术,不是指做 UI 的技术,而是由 Web 原生语言、Web Runtime、Web 技术生态组成的技术栈,不是只在浏览器里才有前端技术,而是有 Web Runtime、有 Web 语言的地方,就有前端技术 1.4.2 DX 和 UX 同样重要 传统前端开发不是成熟的软件研发体系,缺乏足够的抽象和基建,导致 DX 和 UX 始终存在矛盾,此消彼涨。以往的产品开发中,习惯更重视 UX,这有两方面的原因,一来是因为产品是由产品主导,因此更重视 UX, 不会过多关注开发者体验,再就是缺乏足够的抽象和基建,导致 DX 和 UX 之间必须牺牲一个。 在新一代更成熟的研发体系的支持下,已经可以实现 DX 和 UX 的同时最大化了,也从「更重视 UX」转变为「DX 优先」的方式 要实现 DX 和 UX 的同时最大化,需要充分的抽象。比如前面提到的 Modern.js 的这个例子,项目里只有三个文件,就具备全面的能力,包括自动 Polyfill、差异化分发、SSR 等,既具备产品级的 UX,有保持了 DX 的简单、开箱即用等 1.4.3 充分抽象 \ 要实现充分抽象,需要让项目从基于「库、工具」发展成基于「框架」,这两者的区别在图上表现的比较好。蓝底白边部分是项目开发者自己写的代码。左边是传统前端项目,由开发者手写整个应用,把库和工具当做积木来组装,填补项目里的空白。而右边是 Modern.js 项目,整个应用是框架本身,开发者手写的代码,是按照框架的要求填充到框架预留的位置上 要实现充分抽象,也需要在尽可能多的环节实现最大化的抽象,图上体现了 Modern.js 除了像常规的框架一样,在运行时和编译时做抽象,也会在 IDE 编写代码的环节,和部署产物的环节,引入最大化的抽象 \ 要实现充分抽象,也需要解决前端模板的问题,Modern.js 把各种研发场景、项目类型,收敛和标准化成了始终固定的三个工程类型,其中「应用」工程方案,也就是 MWA,支持所有需要部署和运行的项目,「模块」工程方案支持所有需要实现代码复用的项目 1.5 智能化、平台化、低码化 Modern.js 代表的现代 Web 开发,也在继续朝着智能化、平台化、低码化的方向发展 智能化方面,当前可用的功能,是用 Modern.js 的初始化工具创建的项目,会开箱即用的在 VSCode 里做好配置,启用几千条规则组成的 ESLint 全量规则集,加上按最佳实践内置在 ESLint 里的 Prettier,期望尽可能多自动修正问题,而不是仅仅提示问题。也追求尽可能多的让 IDE 负责生成真正的源码,让开发者手写的代码变成跟 IDE 沟通的语言 在平台化方面,Modern.js 的目标之一就是形成「工程标准」,让各种前端 PaaS 平台可以围绕标准实现高级能力,比如图上粉色部分列出的产品级 Web Server、差异化分发、SSR 兜底、ESR、微前端等,都需要结合代码层面的工程标准。 除了部署运行环节方面的平台,有了工程标准之后,研发环节也可以引入更多低码提效。 目前我们内部使用的研发平台,可以直接在图形界面上简单操作完成 项目的创建、开发、部署。图上右侧可以看到图形界面上展示了当前项目的的状态: 入口数量、项目配置等,蓝色框内添加应用的入口,一键从 「单入口」 转变为 「多入口」 。右侧在 Web IDE 中也能看到 src 目录结构下的变化。 低码化有两个方向,一个是刚才说的跟研发工具结合,另一个就是研发从某些工作中解脱出来的低码搭建,昨天的低代码专题中有介绍 总结 到目前为止,我们从这些变革的角度,展示了很多 Modern.js 的 demo 和效果 二、Modern.js 的六大要素 接下来我们系统的看一下 Modern.js 是什么,Modern.js 提供了什么。 2.1 普及:现代 Web 开发范式 可以用这六大要素来说明 Modern.js 。 首先这个项目是希望能推动现代 Web 开发范式的普及,发展完整的现代 Web 工程体系,突破应用开发效率的瓶颈。 前面讨论「现代 Web 开发」的时候,已经展示过这种范式的 9 大主要特征。 其中 Serverless 范式、平台化、低码化这三个特征,在当前版本的 Modern.js 里还没什么体现,需要后续会跟一些平台配合提供。 2.2 核心:现代 Web 应用(MWA) 继续看第二个要素。Modern.js 三大工程类型中最核心的就是 「现代 Web 应用」,简称 MWA,或直接叫「应用」。 2.2.1 从 Universal JS 到 Universal App 前面提到过,「应用」工程方案支持所有需要部署和运行的项目,把这些项目收敛成用同一套框架、同一套约定、同一套模板、同一套架构、一套 API 来开发。 反过来,我们也可以从 Universal App、一体化、应用架构、Runtime API 这四个角度来了解 MWA。 Universal JS 指同一份 JS 代码,既能在浏览器端运行,也能在服务器端运行。Universal App 是它的进一步发展,同一份 App 代码可以在不同环节运行,也可以用不同的模式来运行。 首先是常见的 MPA 和 SPA 的需求,本质上是「服务器端路由」和「客户端路由」的需求。 在 Modern.js 里它们可以随意组合。 我们之前的例子都是单入口应用,只需要把 App.tsx 组件、pages 目录这样的入口标识放到 src 的子目录里,就能将单入口应用变成多入口的 MPA。会基于入口名称,自动生成服务器端路由,比如图上的 admin-app 和 landing-page 两个入口的 URL。 admin-app 和 landing-page 也分别都是 SPA,根据入口标识不同,一个使用基于组件的客户端路由,一个使用基于文件系统的客户端路由。 然后是 MWA 中的「动静一体」 之前演示过,一个静态的 CSR 项目如何直接开启 SSR、SPR、SSG 功能。 Modern.js 也支持 CSR 和 SSR/SSG 混用,比如图上右侧红色高亮部分会在服务端被渲染到 HTML 中,蓝色区域的日期时间,在 CSR 阶段动态展示在页面上。也就是整体 SSR + 局部 CSR。 整体 CSR + 局部 SSR的能力后续会加入。 在 BFF 支持方面,Modern.js 中还提供了类型友好的方式,可以通过 Type Schema 实现运行时自动校验接口的参数和返回值。比如右下角请求时,参数 text 类型为 number 时,response 中会自动提示相应的错误。 Modern.js 还支持不同类型应用的开发和运行。 Modern.js 原生支持微前端,底层解决方案是 Web Infra 之前开源的 Garfish 微前端解决方案。一个 MWA 可以随时变成微前端主应用,在配置中指定子应用列表的加载地址,Modern.js 就会自动在 Web Server 中预加载子应用数据,注入到运行时。在 Runtime API 的帮助下,可以像普通 React 组件一样使用子应用。 MWA 也可以分别作为独立的 Web 和微前端子应用的运行和部署。 MWA 在启用 Electron 支持之后,能作为桌面应用来运行,项目里会新增 electron 目录用于写主进程相关代码。除了开箱即用的 Electron 构建等能力,也提供运行时 API 支持 Electron 的常见需求和最佳实践,进一步提升开发效率。 2.2.2 前后端一体化 第二个看待 MWA 的角度是 「前后端一体化」 之前已经演示过 BFF 函数,api/ 目录下每个文件就是 BFF 路由,当服务器端逻辑更重的时候,可以加入 Node.js 框架元素,目前支持了 4 种不同的框架,还可以自己开发 Modern.js 插件支持更多框架。 前面提到过:以客户端为中心,不代表不能掌控服务器端,不能直接写服务器端业务逻辑。 比如之前演示过的火山引擎子应用,除了通过框架插件来实现,我们也可以在项目里创建 server 目录和钩子文件,添加修改 HTML 渲染结果的逻辑。 \ 在只有 src 目录,或有 api 目录的情况下,MWA 类似 JAMstack 项目。 如果增加了 server 等钩子文件,MWA 就能像传统 Node.js App 一样直接写服务器端业务逻辑,使用 Node.js 框架插件、中间件等。 如果删掉 src 目录,MWA 就是一个纯 REST API 的项目。 我们把这三种模式之间随意迁移的能力,称作「三位一体」 2.2.3 应用架构 接下来,我们从「应用架构」的角度看看 传统 Web 开发中的应用架构,等同于服务器端应用架构,前端部分的架构要么缺失,要么需要项目开发者自己摸索、搭建,缺乏 API 支持和一致的抽象,难以跨项目复用业务逻辑。 如上图,MWA 提供的开箱即用的、客户端为中心的应用架构,可以通过标准化的 Runtime API,轻易实现 React 开发中缺少的 Model 层和 Controller 层。Model 作为封装 UI 无关业务逻辑的积木,跟 UI 组件一样可以复用和组装。 之前 Web Infra 举办的 React 核心开发者在线访谈里,Redux 作者 Dan 提到,状态管理最重要的是理解状态的类型,根据需要处理的状态是什么种类,来选择对应的方案, 常见的状态管理方案,都有适合的状态类型和场景,很多时候需要混合使用,而不是一把锤子锤所有钉子,要么所有状态都放到全局应用状态里,要么所有状态都在局部状态里。 \ 很多开发者不用 Redux,是因为 Redux 本身只能算底层 API,需要手动创建和维护 store,业务逻辑被 reducer,action 等分散在不同的地方,提高了维护成本。其实 Redux 社区一直有解决方案,比如 Ducks Modular 设计模式会把业务逻辑聚集在一起,Redux 官方支持的主流库 RTK 也为解决这样的问题而生。 Modern.js 的 Model 基于 Redux 进一步提高抽象程度,保留了 Redux 在不可变数据、数据流等方面的收益,对整个 Redux 生态兼容,让使用和不使用 Redux 的开发者都能受益。支持多种状态类型,也支持不同的 Model 写法 2.2.4 Runtime API 标准库 最后看下 MWA 的 Runtime API 标准库。 相当于「应用」层级的基础 API,不止能在 MWA 里使用,在 Modern.js 的模块工程方案里,同样可以使用这些 API,开发可复用的业务组件,支持独立调试和测试。 图中最上面蓝色方块是业务开发中常用的 API,比如 useLoader,useModel 等 API。中间绿色部分就是前面提到的定制 web server、BFF 函数需要用到的 API,最底层的插件 API,是整个框架的基础,框架里所有的包都是用插件 API 来实现的,也可以用插件 API 来扩展框架、定制工程方案。粉色部分包括很多重要的工具 API,比如 useLocalModel。 \ 当应用中的组件需要拆分成独立的模块复用时,实现中用到的 Runtime API 还能正常调试、测试吗,答案是肯定的,这套 API 相当于「应用」领域的 API 标准库,不止能在 MWA 里使用,在 Modern.js 的「模块」工程方案里,同样可以使用这些 API,开发可复用的业务组件,支持独立调试和测试。上图右侧是模块工程的目录结构。左侧 TableList 组件中使用了 useLoader API,调试时只需要提供对应的 story 文件,模块工程方案支持我们在 Storybook 可视化测试以及单元测试中测试使用了 Runtime API 的组件。 2.3 内置:前端工程最佳实践 对于第三个要素,简单列举几个 Modern.js 内置的前端工程最佳实践 2.3.1 Post-Webpack Era 传统前端工程建设都是基于 Webpack 的配置封装, Webpack 配置复杂和编译缓慢的问题,大家应该都有比较深的感受, 但是从去年开始业界涌现很多新的工具,完全不涉及 Webpack,比如 Snowpack、 Vite、wmr 等,有人把它称为 JS 第三纪元。 从第三纪元开始 esbuild、swc 这种编译打包工具使用非 JS 的系统编程语言开发,显著提高编译速度。编译时间的缩减也意味着不打包,按需编译的 ESM 场景可以实现, Snowpack、Vite 这样的工具,就是在 esbuild 的基础上实现的、开发者体验优先的、不打包的开发调试模式。在 Modern.js 中不打包的模式目前已经被用于公共库的构建、业务项目的开发调试等真实场景。 Modern.js 中也内置类似 Snowpack、Vite 的不打包开发调试模式,图中左侧启用该功能之后,运行效果就像图中右侧那样,开发服务器在秒级启动。 为什么可以做到速度这么快?主要是因为业务代码只有在请求时使用 esbuild 按需编译,第三方依赖自动从 Goofy PDN 加载已经预编译好的产物。 2.3.2 CSS 最佳实践 在 CSS 开发方面,Modern.js 默认推荐图上「CSS 三剑客」搭配使用,有需要也可以开启 LESS/SASS 等预处理器和 CSS Modules 支持。 2.3.3 默认零配置、样板文件最小化 和以前把功能作为样板文件塞到项目里相比,现代 Web 开发范式下的最佳实践是默认零配置的,同时样板文件尽可能简洁最小化。之前我们也通过例子看到 Modern.js 项目手动创建非常简单,只需要应用根组件和 package.json。 跟直接使用 Garfish 开发微前端主应用的项目做对比, 上图可以看到直接使用 Garifish 的项目,需要手动运行 Garfish 框架、处理公用模块、路由等逻辑。除了运行时,还需要编译环节自定义一大堆配置。直接使用还是有一定的成本。 \ 之前的例子已经说过,在 Modern.js 中使用微前端,只需要在 web 应用的基础上启用微前端功能,提供子应用列表即可,每个子应用加载后就是组件,路由可以自己灵活组织。 2.3.4 构建产物规范 Modern.js 的模块工程方案,会并行编译出多种符合社区主流规范的构建产物。 模块的编译也是不打包的,更容易引入速度更快的工具比如 esbuild、swc 等。 2.3.5 Monorepo 工程方案 应用和库如果分散在不同项目中开发的话,通过 npm link 调试也比较麻烦,业界的主流方案通过 Monorepo 管理多个子项目, Modern.js 本身就是基于 pnpm monorepo 开发的,同时也将这部分最佳实践收敛到 monorepo 工程方案,默认使用 pnpm 进行包管理, 左侧是它的目录结构, apps 对应的是 MWA 应用 、features/packages 对应是可复用的模块。 右侧内部模块指的是不会发布到 npm、仅在当前仓库下复用的库。它本身不需要构建,同仓库下的应用直接使用它的源码即可。monorepo 我们也提供了 new 命令,可以选择创建应用或者模块。 2.3.6 更多 除了上面提到的一些最佳实践,Modern.js 还提供了单元测试、集成测试、Visual Testing 等、ESlint 全量规则集等最佳实践。这里不一一展开介绍。可以查阅 Modern.js 文档进一步了解。 2.4 包含:Web 开发全流程 Modern.js 不只是在上述运行时、构建、调试等方面提供了支持,它本身就覆盖了 Web 开发的全流程。 在编码环节,可以通过微生成器启用某个功能或者添加入口,从 SPA 迁移到 MPA。和前面提到的通过研发平台 「低码提效」类似,还可以像图上那样在项目目录下执行 new 命令选择要启用的功能。这个命令会自动重构我们的代码。 通过微生成器按需自动启用的方式,可以放心的将一些功能作为插件提供,也可以控制 Modern.js 初始化项目的体积。 在微前端子应用开发时,通常情况下主应用已经部署上线了,这时候开发子应用就需要结合线上的主应用一起调试,解决方式之一是通过全局代理子应用 JS 到本地,比较麻烦。 在 Modern.js 中,只需要主应用像图中右下角那样启用 DEBUG 模式,之后打开主应用线上链接,在 header 中设置需要开发的子应用信息,server 会自动替换注入到 html 中的子应用列表数据。这样也就可以让线上主应用加载本地子应用。 在运行环节,传统的 Web 开发模式,通常没有提供生产环境运行项目的方式,MWA 项目本身自带产品级的 Server,自己就能产品级的运行自己,比如图上的自动 Polyfill 服务。之前也提过,结合 serverless 平台,可以自动做一些优化,也可以在本地运行模拟生产环境的效果。 2.5 提供:工程标准体系 Modern.js 不只是一个现代 Web 应用开发框架,而是提供了整套的现代 Web 工程体系。 前面已经介绍过,我们将前端开发中涉及的场景收敛到 3 种:应用、模块和 monorepo。 不仅解决了业务模板数量爆炸的问题。融合后的工程类型,比如 MWA 不是多个场景简单叠加,导致工程变的大而全,通过抽象可以做到很轻量,也能更容易交付一些之前不好实现的功能。 2.6 鼓励:定制工程方案 Modern.js 鼓励业务结合自身场景定制垂直的工程方案。 就像前面提到的火山引擎例子一样,封装插件、微生成器、定制出自己的业务工程方案。 关于 Modern.js 六大要素的更多解释和例子,可以到 Modern.js 官网进一步查阅。 三、Modern.js 社区和现代 Web 研发体系 最后我们一起看下除了已经发布的开源项目,还有哪些对现代 Web 开发者有帮助的事情在发起和推进中。 Modern.js 开源项目现在是刚起步的状态,昨天上线的官网,以及最新发的 1.0 版,都是公测状态,还需要更多意见、测试和实践,希望大家多参与社区建设。 Modern.js 的起点是字节内部的现代 Web 工程体系项目,现在大部分代码已经完全转到 Github 上开发,工作流还在建设中。 双月计划、每周计划、缺陷管理等,也都会全面转到 Github 上公开推进。 当前版本还没有包含 Roadmap 上一些重要功能,计划以每周发版的节奏,把这些功能补上。 昨天的分享介绍了 「现代 Web 研发体系」中的其他部分,这些部分也都算是 Modern.js 的重要功能,后续会陆续对外开放,欢迎大家关注。 最后,欢迎大家扫码入群交流,也可以在官网上通过快速上手和实战教程了解更多 Modernjs 的细节使用部分。\ 谢谢大家。 官网:https://modernjs.dev/ Github:https://github.com/modern-js-dev/modern.js Reference [1]《迈入现代 Web 开发(字节跳动的现代 Web 开发实践)》:https://zhuanlan.zhihu.com/p/386607009 [2]稀土开发者大会:http://conf.juejin.cn/xdc2021 [3]Modern.js:https://modernjs.dev/ [6]昨天上午的主题演讲:https://conf.juejin.cn/xdc2021 [7]Modern.js:https://modernjs.dev/ 点击直达官网,了解更多信息。

优秀的个人博客,低调大师

JFinal 4.9.06 发布,Hello 2021

过去一年极不平凡,黑天鹅频频出现。 很多人在 2020 年并不容易,作为开源人时间上也更不够用了。即便如此,jfinal 仍发布了 6 个版本。 这 6 个版本共有 86 项增强与改进,有些是针对你的反馈与需求,有些是源于作者主动的极致打磨。 经过多年的迭代与打磨,jfinal 已经成熟、稳定、可靠应用于千千万万、各行各业的项目之中,我也收获了足够多的成就感,但责任感也更重了。 过去一年的更新绝大部分是细微打磨与改进,这些努力可以让你的开发工作更顺畅,很多你可能会碰到的问题 jfinal 已经处理掉了,进一步为你省时间。 新增的最重要的功能就是路由自动扫描了,在 configRoute 中添加一行配置即可开启: publicvoidconfigRoute(Routesme){ me.scan("com.your.pkg."); } 这个功能相对手动路由添加不会损失性能,比以往非官方扩展出来的扫描适应性要好,功能要全面,例如支持 Routes 级别的 Interceptor 与 baseViewPath 配置。 更详细的介绍请移步:https://jfinal.com/doc/2-3 还有一些次要的功能,可能对你有用,例如: 1:enjoy 模板引擎有关数值计算的舍入规则由银行家舍入法改成了四舍五入法,而且提供了配置方法 2:enjoy 模板引擎新增压缩功能,方便压缩 html 与 sql 模板 3:JFinalJson 被完全重写,性能提升至 jackson 的 3 倍以上,并且提供了丰富的扩展、配置方式 4:enjoy 支持中文变量名、中文方法名、中文模板函数名 本次要发布的 jfinal 4.9.06 版本仍然是细微打磨: 1:Model、Db 新增 each 方法 2:DbTemplate、DaoTemplate 新增 each 与 getSqlPara 方法 3:MetaBuilder 中的 filter 方法更名为 skip 4:微调 PathScanner,在跳过 provided 依赖时输出一条 debug 信息 5:DaoTemplate 添加 queryDouble、queryFloat、queryDate、queryBoolean 6:修正 redis plugin 中 hgetCounter 方法的 bug,添加 hgetFloatCounter jfinal 官方出品的众多开源项目也都升级了,jfinal weixin 2.9 已经发布,该版本主要改进了与 SSL 通信有关的 API,对 SSL 证书支持更好。 jfinal undertow 2.4 也已经发布,jfinal 中独立出来的 activerecord 、enjoy 两个项目的新版本也已经发布,记得升级一下,超好用的。 2021 已来,JFinal 会一如既往的帮助你提升开发效率、降低学习成本、提升开发体验! One More Thing:JFinal 俱乐部的同学以及参与过 jfinal 官网 "App & Coffee" 的同学记得下载更新后的资源。

优秀的个人博客,低调大师

iOS学习——Hello iOS(二)

前面写了iOS开发环境搭建,只简单提了一下安装Xcode,这里再补充一下,点击下载Xcode的dmp文件,稍等片刻会有图一(拖拽Xcode至Applications)的提示,拖拽至Application即可,在Finder-应用程序-Xcode找到Xcode图标。点击Xcode图表,下一步操作即可进入开发界面。 图一(拖拽Xcode至Applications) 安装完Xcode,首先熟悉一下Xcode界面(这张是网上看到的一张图): 从上图看,Xcode和开发windows phone的Visual Studio、开发Android的Eclipse很像,如果是从那两个方向转过来做iOS开发的应该很快就能熟悉。 创建iOS项目: 1. 打开XCode,选择File->New Project,然后选择iOS->Application->Single View Application,然后点Next,如下图所示: 从上图看出,可以创建7种项目,我也是初学者,不能一一说出他们的不同,等熟悉了再补充。 2. 填写应用信息 Product Name:项目名称; Organization Name:组织名,一般显示计算机名; Company Identifier: 公司标示,此处输入一个唯一标示即可,也可以在项目中统一修改; Class Prefix: 默认类前缀,为了避免与其他类混淆,此处最好输入一个前缀名称,默认使用全大写字母,保留为空也可以; Device : 设备类型,可以选择“iPhone”“iPad”或者“Universal”(通用,同时支持iPhone和iPad) ; Storyboard: 是Xcode 4.x新增特性,是以前版本的xib的一个扩展升级,让程序的UI设计更加的简化;注意:基于Storyboard的应用只能在iOS5以上版本的设备上运行。 ARC: 是Xcode 4.x新增特性,对于以前版本的iOS程序员每alloc一个对象,都会下意识地先去release它,这是一个非常讨厌的事情。现在有了 ARC,一切都变得更加简单,程序员可以更加专注地处理自己应用的表现。 Include Unit Tests:生成项目时会包含测试包。 生成项目: 项目结构如下图: 选择MainStoryboard_iPhone.storyboard,初始时是一个空白,选择右下角的Label标签,双击,修改标签,点上面的运行按钮,模拟器就出现了HelloiOS,自己没写一行代码。Xcode开发工具还是很强大的。 /** * @author 张兴业 * http://blog.csdn.net/xyz_lmn * iOS入门群: 83702688 * android开发进阶群: 241395671 * 我的新浪微博: @张兴业TBOW */ 本文转自xyz_lmn51CTO博客,原文链接:http://blog.51cto.com/xyzlmn/1230739 ,如需转载请自行联系原作者

优秀的个人博客,低调大师

hello Kotlin (android 的swift)

1.1 Kotlin的身世 写了许久Java,有没有发现其实你写了太多冗余的代码? 后来你体验了一下Python,有没有觉得不写分号的感觉真是超级爽? 你虽然勤勤恳恳,可到头来却被 NullPointerException 折磨的死去活来,难道就没有受够这种日子么? 直到有一天你发现自己已经写了好几十万行代码,发现居然全是 getter 和 setter! 哈哈,实际上你完全可以不用这么痛苦,用 Kotlin 替代 Java 开发你的程序,无论是Android还是 Server,你都能像之前写 Java 一样思考,同时又能享受到新一代编程语言的特性,说到这里你是不是开始心动了呢?下面我就通过这篇文章来给大家介绍一下 Kotlin 究竟是何方神圣。 话说,Kotlin 是 JetBrain 公司搞出来的,运行在 JVM 上的一门静态类型语言,它是用波罗的海的一个小岛的名字命名的。从外观上,乍一看还以为是Scala,我曾经琢磨着把 Scala 作为我的下一门语言,不过想想用 Scala 来干嘛呢,我又不做大数据,而它又太复杂了o(╯□╰)o 用Kotlin创建一个数据类 最初是在 intelliJ 的源码中看到 Kotlin 的,那时候 Kotlin 的版本还不太稳定,所以源码总是编译不过,真是要抓狂啊,还骂『什么破玩意儿!为什么又出来新语言了?Groovy 还没怎么学会,又来个 Kotlin!』话说,Kotlin,难道是『靠它灵』的意思?? 其实经过一年多的发展,Kotlin 1.0已经 release,feature 基本完善,api 也趋于稳定,这时候尝试也不会有那种被坑的感觉了。过年期间也算清闲,于是用 Kotlin 做了个 app,简单来说,就是几个感觉: 思路与写 Java 时一样,不过更简洁清爽 少了冗余代码的烦恼,更容易专注于功能的开发,整个过程轻松愉快 扩展功能使得代码写起来更有趣 空安全和不可变类型使得开发中对变量的定义和初始化倾注了更多关注 啊啊,我再也不用写那个 findViewById 了,真的爽爆有木有! 1.2 第一个Kotlin程序 Kotlin 开发当然使用 JetBrain 系列的 IDE,实际上 intelliJ idea 15 发布时就已经内置了 Kotlin 插件,更早的版本则需要到插件仓库中下载安装 Kotlin 插件——在安装时你还会看到有个Kotlin Extensions for Android,不要管他,已经过时了。安装好以后,我们就可以使用 Kotlin 进行开发了。 接下来我们用 Android Studio 创建一个 Android 工程,比如叫做 HelloKotlin,在 app 目录下面的 build.gradle 文件中添加下面的配置: 这里添加了 Kotlin 对 Android 的扩展,同时也添加了 Kotlin 的 Gradle 插件。 接下来就可以编写 Kotlin 代码了——等等,Android Studio 会帮我们生成一个MainActivity,你可以直接在菜单 Code -> ConvertJavafile to Kotlin file 将这个 Java 代码转换为 Kotlin 代码。截止到现在,你什么都不用做,程序就已经可以跑起来了。 2、完美为Java开发者打造 2.1 通用的集合框架 我们都知道 Jvm 上面的语言,像什么 Java、Groovy、Jython 啥的,都是要编成虚拟机的字节码的,一旦编成字节码,在一定程度上大家就都平等了。 英雄不问出身啊 有人做过一个非常形象的比喻:Java 虚拟机语言就是打群架。Kotlin 正是充分利用了这一点,它自己的标准库只是基于 Java 的语言框架做了许多扩展,你在Kotlin 当中使用的集合框架仍然跟你在Java当中一样。 举个例子,如果你想要在 Kotlin 中使用 ArrayList,很简单,Java 的 ArrayList 你可以随意使用,这个感觉跟使用 Java 没有任何区别,请看: 当然,Kotlin 标准库也对这些做了扩展,我们在享用 Java 世界的一切资源的同时,还能比原生 Java 代码更滋润,真是爽爆有木有: 2.2 与Java交互 Kotlin 的标准库更多的是对 Java 库的扩展,基于这个设计思路,你丝毫不需要担心 Kotlin 对 Java 代码的引用,你甚至可以在 Kotlin 当中使用 Java 反射,反正只要是 Java 有的,Kotlin 都有,于是有人做出这样的评价: Kotlin 就是 Java 的一个扩展 这样说 Kotlin 显然是不公平的,但就像微信刚面世那会儿要为 QQ 接收离线消息一样,总得抱几天大腿嘛。 有关从 Kotlin 中调用Java的官方文档在此Calling Java code from Kotlin(https://kotlinlang.org/docs/reference/java-interop.html#static-methods-and-fields),其中最常见的就是 Getter / Setter 方法对应到 Kotlin 属性的调用,举个例子: 准备一个Java类 下面是Kotlin代码 所以我们在 Android 开发时,就可以这样: [js] view plain copy print ? <olclass="linenums"style="box-sizing:border-box;border:0px;margin:0px0px10px25px;padding:0px;color:rgb(204,204,204);"><liclass="L0"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">view</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">.</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">background</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">=</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">...</span></li><liclass="L1"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">textView</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">.</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">text</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">=</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">...</span></li></ol> 反过来在 Java 中调用 Kotlin 也毫无压力,官方文档C alling Kotlin from Java对于常见的情况作了比较详细的阐述,这里就不再赘述。 3、简洁,可靠,有趣 3.1 数据类 最初学 Java 的时候,学到一个概念叫 JavaBean,当时就要被这个概念给折磨死了。明明很简单的一个东西,结果搞得很复杂的样子,而且由于当时对于这些数据类的设计概念不是很清晰,因而也并不懂得去覆写诸如 equals 和 hashcode 这样重要的方法,一旦用到 HashMap 这样的集合框架,总是出了问题都不知道找谁。 Kotlin 提供了一种非常简单的方式来创建这样的数据类,例如: [js] view plain copy print ? <olclass="linenums"style="box-sizing:border-box;border:0px;margin:0px0px10px25px;padding:0px;color:rgb(204,204,204);"><liclass="L0"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">data</span><spanclass="kwd"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(167,29,93);">class</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="typ"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(0,134,179);">Coordinate</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">(</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">valx</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">:</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="typ"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(0,134,179);">Double</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">,</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">valy</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">:</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="typ"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(0,134,179);">Double</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">)</span></li></ol> 仅仅一行代码,Kotlin 就会创建出一个完整的数据类,并自动生成相应的 equals、hashcode、toString 方法。是不是早就受够了 getter和setter?反正我是受够了。 3.2 空安全与属性代理 第一次见到空类型安全的设计是在Swift当中,那时候还觉得这个东西有点儿意思哈,一旦要求变量不能为空以后,因它而导致的空指针异常的可能性就直接没有了。想想每次 QA 提的 bug 吧,说少了都得有三分之一是空指针吧。 Kotlin 的空安全设计,主要是在类型后面加?表示可空,否则就不能为 null。 [js] view plain copy print ? <olclass="linenums"style="box-sizing:border-box;border:0px;margin:0px0px10px25px;padding:0px;color:rgb(204,204,204);"><liclass="L0"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">valanInt</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">:</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="typ"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(0,134,179);">Int</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">=</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="kwd"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(167,29,93);">null</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="com"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(150,152,150);">//错误</span></li><liclass="L1"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">valanotherInt</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">:</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="typ"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(0,134,179);">Int</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">?</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">=</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="kwd"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(167,29,93);">null</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="com"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(150,152,150);">//正确</span></li></ol> 使用时,则: 而对于 Java 代码,比如我们在覆写 Activity 的 onCreate 方法时,有个参数 savedInstanceState: [js] view plain copy print ? <olclass="linenums"style="box-sizing:border-box;border:0px;margin:0px0px10px25px;padding:0px;color:rgb(204,204,204);"><liclass="L0"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="kwd"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(167,29,93);">override</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">funonCreate</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">(</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">savedInstanceState</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">:</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="typ"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(0,134,179);">Bundle</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">!)</span></li></ol> 这表示编译器不再强制 savedInstanceState 是否可 null,开发者在覆写时可以自己决定是否可 null。当然,对于本例,onCreate 的参数是可能为 null 的,因此覆写以后的方法应为: [js] view plain copy print ? <olclass="linenums"style="box-sizing:border-box;border:0px;margin:0px0px10px25px;padding:0px;color:rgb(204,204,204);"><liclass="L0"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="kwd"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(167,29,93);">override</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">funonCreate</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">(</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">savedInstanceState</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">:</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="typ"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(0,134,179);">Bundle</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">?)</span></li></ol> 通常来讲,教科书式的讲法,到这里就该结束了。然而直到我真正用 Kotlin 开始写代码时,发现,有些需求实现起来真的有些奇怪。 还是举个例子,我需要在 Activity 当中创建一个 View 的引用,通常我们在 Java 代码中这么写: 在 Kotlin 当中呢? 每次用 aTextView 都要加俩!,不然编译器不能确定它究竟是不是 null,于是不让你使用。。这尼玛。。。到底是为了方便还是为了麻烦?? 所以后来我又决定这么写: 这可如何是好?? 其实 Kotlin 肯定是有办法解决这个问题哒!比如上面的场景,我们这么写就可以咯: lazy 是 Kotlin 的属性代理的一个实例,它提供了延迟加载的机制。换句话说,这里的 lazy 提供了初始化 aTextView 的方法,不过真正初始化这个动作发生的时机却是在 aTextView 第一次被使用时了。lazy 默认是线程安全的,你当然也可以关掉这个配置,只需要加个参数即可: 好,这时候肯定有人要扔西红柿过来了(再扔点儿鸡蛋呗),你这 lazy 只能初始化 val 啊,万一我要定义一个 var 成员,又需要延迟初始化,关键还不为 null,怎么办?? lateinit 的使用还是有很多限制的,比如只能在不可 null 的对象上使用,比须为var,不能为 primitives(Int、Float之类)等等,不过这样逼迫你一定要初始化这个变量的做法,确实能减少我们在开发中的遗漏,从而提高开发效率。 至于 lazy 技术,实际上是 Delegate Properties 的一个应用,也就是属性代理了。在 Kotlin 当中,声明成员属性,除了直接赋值,还可以用 Delegate 的方式来声明,这个 Delegate 需要根据成员的类型(val 或者 var)来提供相应的 getValue 和 setValue 方法,比如一个可读写的 Delegate,需要提供下面的方法: 好嘴皮不如来个栗子,下面我们就看一个自定义 Delegate,用来访问 SharedPreference: 需要说明的是,这段代码是我从《Kotlin for Android Developer》的示例中摘出来的。有了这个 Delegate 类,我们就可以完全不需要关心 SharedPreference了,下面给出使用的示例代码: 于是我们再也不需要重复写那些 getSharedPreference,也不用 edit、commit,再见那些 edit 之后忘了 commit 的日子。有没有觉得非常赞! 3.3 扩展类 扩展类,就是在现有类的基础上,添加一些属性或者方法,当然扩展的这些成员需要导入当前扩展成员所在的包才可以访问到。下面给出一个例子: 我们已经介绍过 data class,Coordinate 有两个成员分别是 x 和 y,我们知道通常表示一个二维平面,有这俩够了;然而我们在图形学当中经常会需要求得其极坐标,所以我们扩展了 Coordinate,增加了一个属性 theta 表示角度(反正切的值域为 -π/2 ~ π/2,所以这个式子不适用于二三象限,不过这不是重点了),增加了一个 R 方法来获得点的半径,于是我们在 main 方法中就可以这么用: 那么这个扩展有什么限制呢? 在扩展成员当中,只能访问被扩展类在当前作用域内可见的成员,本例中的x 和 y 都是 public 的(Kotlin 默认 public,这个我们后面会提到),所以可以在扩展方法和属性中直接访问。 扩展成员与被扩展类的内部成员名称相同时,扩展成员将无法被访问到 好的,基本知识就是这些了,下面我们再给出一个实际的例子。 通常我们在 Java 中会自定义一些 LogUtils 类来打日志,或者直接用 android.util.log 来输出日志,不知道大家是什么感受,我反正每次因为要输入 Log.d 还要输入个 tag 简直烦的要死,而且有时候恰好这个类还没有 tag 这个成员,实践中我们通常会把当前类名作为 TAG,但每个类都要做这么个工作,是在是没有什么趣味可言(之前我是用 LiveTemplates 帮我的,即便如此也没有那种流畅的感觉)。 有了 Kotlin 的这个扩展功能,日子就会好过得多了,下面我创建的一个打日志的方法: 有了这个方法,你可以在任何类的方法体中直接写: [js] view plain copy print ? <olclass="linenums"style="box-sizing:border-box;border:0px;margin:0px0px10px25px;padding:0px;color:rgb(204,204,204);"><liclass="L0"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">debug</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">(</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">whatever</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">)</span></li></ol> 然后就会输出以这个类名为 TAG 的日志。 嗯,这里需要简单介绍 Kotlin 在泛型中的一个比较重要的增强,这个在 Java 中无论如何也是做不到的:inline、reified。我们再来回头看一下 debug 这个方法,我们发现它可以通过泛型参数 T 来获取到T的具体类型,并且拿到它的类名——当然,如果你愿意,你甚至可以调用它的构造方法来构造一个对象出来——为什么 Kotlin 可以做到呢?因为这段代码是 inline 的,最终编译时是要编译到调用它的代码块中,这时候T的类型实际上是确定的,因而 Kotlin 通过 reified 这个关键字告诉编译器,T 这个参数可不只是个摆设,我要把它当实际类型来用呢。 为了让大家印象深刻,我下面给出类似功能的 Java 的代码实现: 而你如果说希望在 Java 中也希望像下面这样拿到这个泛型参数的类型,是不可以的: 就算我们在调用处会写道 debug < Date >(“blabla”),但这个 Date 在编译之后还是会被擦除。 3.4 函数式支持(Lambdas) Java 8 已经开始可以支持 Lambda 表达式了,这种东西对于 Java 这样一个『根红苗正』的面向对象编程语言来说还真是显得不自然,不过对于 Kotlin 来说,就没那么多顾忌了。 通常我们需要执行一段异步的代码,我们会构造一个 Runnable 对象,然后交给 executor,比如这段 java 代码: 用 Kotlin 怎么写呢? [js] view plain copy print ? <olclass="linenums"style="box-sizing:border-box;border:0px;margin:0px0px10px25px;padding:0px;color:rgb(204,204,204);"><liclass="L0"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">executor</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">.</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">submit</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">({</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="com"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(150,152,150);">//todo})</span></li></ol> 一下子省了很多代码。 那么实际当中我们可能更常见到下面的例子,这是一段很常见的 Java 代码,在 Android 的 UI 初始化会见到: 那么我们用 Kotlin 怎么写呢? [js] view plain copy print ? <olclass="linenums"style="box-sizing:border-box;border:0px;margin:0px0px10px25px;padding:0px;color:rgb(204,204,204);"><liclass="L0"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">textView</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">.</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">setOnClickListener</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">{</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="com"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(150,152,150);">/*todo*/</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">}</span></li><liclass="L1"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">handler</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">.</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">post</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">{</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="com"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(150,152,150);">/*todo*/</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">}</span></li></ol> 在 Anko 这个 Android 库的帮助下,我们甚至可以继续简化 OnClickListener 的设置方式: [js] view plain copy print ? <olclass="linenums"style="box-sizing:border-box;border:0px;margin:0px0px10px25px;padding:0px;color:rgb(204,204,204);"><liclass="L0"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">textView</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">.</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">onClick</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">{</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="com"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(150,152,150);">/*todo*/</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">}</span></li></ol> 当然,好玩的不止这些,如果结合上一节我们提到的扩展方法,我们就很容易看到 Kotlin 的标准库提供的类似 with 和 apply 这样的方法是怎么工作的了: 我们通常会在某个方法体内创建一个对象并返回它,可我们除了调用它的构造方法之外还需要做一些其他的操作,于是就要创建一个局部变量。。。有了 apply 这个扩展方法,我们就可以这么写: 这样返回的 StringBuilder 对象实际上是包 "whatever" 这个字符串的。 至于说 Kotlin 对于 RxJava 的友好性,使得我突然有点儿相信缘分这种东西了: 3.5 Pattern Matching 记得之前在浏览 Scala 的特性时,看到: [js] view plain copy print ? <olclass="linenums"style="box-sizing:border-box;border:0px;margin:0px0px10px25px;padding:0px;color:rgb(204,204,204);"><liclass="L0"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="kwd"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(167,29,93);">object</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="typ"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(0,134,179);">HelloScala</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">{</span></li><liclass="L1"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="com"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(150,152,150);">//dosomething</span></li><liclass="L2"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">}</span></li></ol> 觉得很新鲜,这时候有个朋友不屑的说了句,Scala 的模式匹配才真正犀利——Kotlin 当中也有这样的特性,我们下面就来看个例子: 咋一看感觉 when 表达式就是一个增强版的 switch——Java 7 以前的 switch 实际上支持的类型非常有限,Java 7 当中增加的对 String 的支持也是基于 int 类型的——我们可以看到 when 不再像 switch 那样只匹配一个数值,它的子式可以是各种返回 Boolean 的表达式。 when 表达式还有一种写法更革命: 只要是返回 Boolean 的表达式就可以作为 when 的子式,这样 when 表达式的灵活性可见一斑。当然,与 Scala 相比,Kotlin 还是要保守一些的,下面给出一个 Scala 类似的例子,大家感受一下,这实际上也可以体现出 Kotlin 在增加 Java 的同时也尽量保持简单的设计哲学(大家都知道,毕竟 Scala 需要智商o(╯□╰)o)。 运行结果如下: [js] view plain copy print ? <olclass="linenums"style="box-sizing:border-box;border:0px;margin:0px0px10px25px;padding:0px;color:rgb(204,204,204);"><liclass="L0"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">atuple</span><spanclass="kwd"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(167,29,93);">with</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">:</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="lit"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(0,134,179);">1</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">,</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="lit"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(0,134,179);">3</span></li><liclass="L1"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">[</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">I@2d554825</span></li><liclass="L2"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="lit"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(0,134,179);">3.0</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">,</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="lit"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(0,134,179);">4.0</span></li></ol> 3.6 如果你是一个SDK开发者 我曾经做过一段时间的 SDK 开发,SDK 的内部有很多类其实是需要互相有访问权限的,但一旦类及其成员是 public 的,那么调用方也就可以看到它们了;而 protected 或者 default 这样的可见性对于子包却是不可见的。 用了这么久 Java,这简直是我唯一强烈感到不满的地方了,甚至于我突然明白了 C++ 的 friend 是多么的有用。 Kotlin 虽然没有提供对于子包可见的修饰符,不过它提供了i nternal:即模块内可见。换句话说,internal 在模块内相当于 public,而对于模块外就是 private 了——于是乎我们如果开发 SDK,那么可以减少 api 层的编写,那些用户不可见的部分直接用 internal 岂不更好。当然有人会说我们应当有 proguard 做混淆,我想说的是,proguard 自然是要用到的,不过那是 SDK 这个产品加工的下一个环节了,我们为什么不能在代码级别把这个事情做好呢? 关于Kotlin的默认可见性究竟是哪个还有人做出过讨论,有兴趣的可以参考这里:Kotlin’s default visibility should be internal(https://discuss.kotlinlang.org/t/kotlins-default-visibility-should-be-internal/1400)。 3.7 DSL 其实我们对 DSL 肯定不会陌生,gradle 的脚本就是基于 groovy 的 DSL,而 Kotlin 的函数特性显然也是可以支持 DSL 的。比如,我们最终要生成下面的 xml 数据: 我们可以构建下面的类: 我们看到在 main 方法当中,我们用 Kotlin 定义的 dsl 写出了一个 Project 对象,它有这与 xml 描述的一致的结构和含义,如果你愿意,可以构造相应的方法来输出这样的 xml,运行之后的结果: 当然,这个例子做的足够的简陋,如果你有兴趣也可以抽象出 "Element",并为之添加 "Attributes",实际上这也不是很难。 3.7 Kotlin与Android的另一些有趣的东西 写了很多代码,却发现它们干不了多少事情,终究还是会苦恼的。比如我一直比较痛苦的一件事儿就是: [js] view plain copy print ? <olclass="linenums"style="box-sizing:border-box;border:0px;margin:0px0px10px25px;padding:0px;color:rgb(204,204,204);"><liclass="L0"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="typ"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(0,134,179);">Button</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">button</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">=</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">(</span><spanclass="typ"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(0,134,179);">Button</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">)</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">findViewById</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">(</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">R</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">.</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">id</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">.</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">btn</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">);</span></li></ol> 如果我需要很多个按钮和图片,那么我们要写一大片这样的 findViewById。。妈呀。。。这活我干不了啦。。 不过用 Kotlin 的 Android 扩展插件,我们就可以这样: 先上布局文件: main.xml 在 Activity 中: 注意到: [js] view plain copy print ? <olclass="linenums"style="box-sizing:border-box;border:0px;margin:0px0px10px25px;padding:0px;color:rgb(204,204,204);"><liclass="L0"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="kwd"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(167,29,93);">import</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">kotlinx</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">.</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">android</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">.</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">synthetic</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">.</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">main</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">.</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">load_activity</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">.*</span></li></ol> 导入这一句之后,我们就可以直接在代码中使用 start、textView,他们分别对应于 main.xml 中的 id 为 start 的按钮和 id 为 textView 的 TextView。 于是你就发现你再也不用 findViewById 了,多么愉快的一件事!!!当然,你还会发现 Toast 的调用也变得简单了,那其实就是一个扩展方法 toast();而 startActivity 呢,其实就是一个 inline加reified 的应用——这我们前面都提到过了。 还有一个恶心的东西就是 UI 线程和非 UI 线程的切换问题。也许你会用 handler 不断的 post,不过说真的,用 handler 的时候难道你不颤抖么,那可是一个很容易内存泄露的魔鬼呀~哈哈,好吧其实我不是说这个,主要是用 handler 写出来的代码 实在 太 丑 了 !! 原来在 Java 当中,我们这么写: 而在 Kotlin 当中呢,我们只需要这么写: 自己感受一下吧。 下面我们再来提一个有意思的东西,我们从做 Android 开发一开始就要编写 xml,印象中这个对于我来说真的是一件痛苦的事情,因为它的工作机制并不如代码那样直接(以至于我现在很多时候居然喜欢用 Java 代码直接写布局)——当然,最主要的问题并不是这个,而是解析 xml 需要耗费 CPU。Kotlin 有办法可以解决这个问题,那就是 DSL 了。下面给出一个例子: 一个 LinearLayou t包含了一个 Button,这段代码你可以直接写到你的代码中灵活复用,就像这样: 这样做的好处真是不少: 比起 xml 的繁琐来,这真是要清爽很多 布局本身也是代码,可以灵活复用 再也不用 findViewById 了,难道你不觉得在这个上面浪费的生命已经足够多吗 事件监听很方便的嵌到布局当中 DSL 方式的布局没有运行时的解析的负担,你的逻辑代码怎么运行它就怎么运行 Anko还增加了更多好玩的特性,有兴趣的可以参考:Anko@Github(https://github.com/Kotlin/anko) 3.8 方法数之痛 我曾经尝试用 Scala 写了个 Android 的 HelloWorld,一切都配置好以后,仅仅引入了 Scala 常见的几个库,加上 support-v4 以及 appcompat 这样常见的库,结果还是报错了。是的,65K。。。而且用 Scala 开发 Android 的话,基于 gradle 的构建会让整个 app 的 build 过程异常漫长,有时候你会觉得自己悟出了广义相对论的奥义,哦不,你一定是晕了,时间并没有变慢。 相比之下,Kotlin 的标准库只有 7000 个方法,比 support-v4 还要小,这正反映了 Kotlin 的设计理念:100% interoperable with Java。其实我们之前就提到,Java 有的 Kotlin 就直接拿来用,而 Scala 的标准库要有 5W 多个方法,想想就还是想想算了。 4、小结 目前 Kotlin 1.0 已经 release,尽管像 0xffffffff 识别成 Long 类型这样的 bug 仍然没有解详情(https://youtrack.jetbrains.com/oauth?state=%2Fissue%2FKT-4749): [js] view plain copy print ? <olclass="linenums"style="box-sizing:border-box;border:0px;margin:0px0px10px25px;padding:0px;color:rgb(204,204,204);"><liclass="L0"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">val</span><spanclass="kwd"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(167,29,93);">int</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">:</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="typ"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(0,134,179);">Int</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">=</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="lit"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(0,134,179);">0xffffffff</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="com"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(150,152,150);">//error</span></li><liclass="L1"style="box-sizing:border-box;border:0px;margin:0px;padding:0px0px0px1em;list-style:decimal;"><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">valanotherInt</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">:</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="typ"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(0,134,179);">Int</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">=</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="lit"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(0,134,179);">0xffffffff</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">.</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">toInt</span><spanclass="pun"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);">()</span><spanclass="pln"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(51,51,51);"></span><spanclass="com"style="box-sizing:border-box;border:0px;margin:0px;padding:0px;color:rgb(150,152,150);">//correct</span></li></ol> 不过,Kotlin 的教学资源和社区建设也已经相对成熟,按照官方的说法,Kotlin可以作为生产工具投入开发,详情可以参考:Kotlin 1.0 Released: Pragmatic Language for JVM and Android(http://blog.jetbrains.com/kotlin/2016/02/kotlin-1-0-released-pragmatic-language-for-jvm-and-android/)。 敢于吃螃蟹,多少有些浪漫主义色彩,我们这些程序员多少可以有些浪漫主义特质,不过在生成环境中,稳定高于一切仍然是不二法则。追求新技术,一方面会给团队带来开发和维护上的学习成本,另一方面也要承担未来某些情况下因为对新技术不熟悉而产生未知问题的风险——老板们最怕风险了~~ 基于这一点,毫无疑问,Kotlin 可以作为小工具、测试用例等的开发工具,这是考虑到这些代码通常体量较小,维护人数较少较集中,对项目整体的影响也较小;而对于核心代码,则视情况而定吧。 就我个人而言,长期下去,Kotlin 很大可能会成为我的主要语言,短期内则仍然采用温和的改革方式慢慢将Kotlin 渗透进来

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册