当前位置: 首页 > 科技观察

不要用Java的语法思维写Kotlin

时间:2023-03-14 12:28:29 科技观察

我写了很多年Java,直到看到Kotlin,原来代码可以这么优雅!如果你和我一样是优秀的Java开发者^_^,并且已经想使用kotlin来实现你的程序,那么,抱歉!不要用Java的语法思维去写Kotlin,也不要让kotlin的优雅被埋没。如果你没有Java开发经验,下面的内容也会对你有所帮助。..1.尽量少用!!个人感觉Null检查是Koltin中最语法糖的东西。在编码过程中被迫考虑空指针,所以《十亿美元的错误》,也许你不会再有这个机会犯错了(也许可以说你赚了十亿美元^_^)。首先要介绍的是!!操作员。!!运算符:这是为空指针爱好者准备的,非空断言运算符(!!)将任何值转换为非空类型,如果值为空则抛出异常。我们可以编写a!!,它将返回a的非空值(例如:我们示例中的String),或者如果a为空则抛出NullPointerException:valb=a!!.length所以,我们可以不使用!!操作员。..下面介绍几种避免使用!!的方法。操作员1).使用val而不是var。在Kotlin中,val表示只读,var表示变量。建议尽量使用val。val是线程安全的,必须在定义时初始化,所以不用担心null。请注意val在某些情况下也是可变的。val和var用来表示一个属性是否有getter/setter:var:既有getter又有setterval:只有getter因此,强烈建议在可以使用val的地方使用。2).有时在使用lateinit时不能使用val。比如springboot接口测试中不能使用val。在这种情况下,可以使用lateinit关键字。.依赖倒置,通过spring创建对象,val在定义时需要初始化/***Createdbyquankeon2018/1/9.*站点:http://woquanke.com.*/@RunWith(SpringRunner::class)@SpringBootTest(webEnvironment=SpringBootTest.WebEnvironment.RANDOM_PORT)classApplicationTests{vallog=LogFactory.getLog(ApplicationTests::class.java)!!@AutowiredlateinitvarrestTemplate:TestRestTemplate@Testfun`GETwhengivenquankethenreturns"Hello,quanke"`(){//Givalvenname="quanke"//Whenvalbody=restTemplate.getForObject("/users/hello/{name}",String::class.java,name)//ThenassertThat(body).isEqualTo("Hello,$name")}}注意:lateinit很好用,但是也有坑访问未初始化的lateinit属性会引发UninitializedPropertyAccessException。lateinit不支持Int等原始数据类型。对于基本数据类型,我们可以这样做:>privatevarmNumber:IntbyDelegates.notNull()>3).Elvisoperator当b为可空引用时,我们可以使用if表达式来处理vall:Int=if(b!=null)b.lengthelse-1但更优雅的方法是使用Elvis运算符?:vall=b?.length?:-1如果?:的左表达式不为空,则elvis运算符返回其左表达式,否则返回右边的表达式。注意:当且仅当左侧为空时,右侧的表达式才会被计算。4).或许你可以试试let函数。let函数通常与安全调用运算符一起使用。我们先介绍安全调用操作?.b?.length如果b不为空,返回b.length,否则返回null,这个表达式是Int?类型。安全调用在链接调用中很有用。例如,如果一名员工全科可能(或可能不)分配到一个部门,并且可能有另一名员工是该部门的负责人,那么获取该部门全科负责人(如果有)的姓名是在里面,我们写:quanke?.department?.head?.name如果任何属性(链接)为空,链式调用将返回null。如果只想对非空值执行操作,调用安全运算符可以与let一起使用:vallistWithNulls:List=listOf("A",null)for(iteminlistWithNulls){item?.let{println(it)}//输出A并忽略null}还有一个常见的错误(在ide里试一下就知道哪里错了):privatevara:String?=nullfunaLetDemo(){if(a!=null){test(a)}}我们可以这样做:privatevara:String?=nullfunaLetDemo(){if(a!=null){test(a!!)}}但这样做的结果是你仍然需要处理空指针在测试功能中。我们充分利用了?的特性。加上let更优雅的解决这个编译错误,如下:privatevara:String?=nullfunaLetDemo(){if(a!=null){a?.let{test(it)}}}2。少写Util类和继承。很多时候,框架提供的方法都是比较原子性的,或者一些常用的方法,框架并没有提供。java一般会写一个工具类:publicfinalclassStringUtil{/***删除所有标点符号**@paramstr处理过的字符串*/publicstaticStringtrimPunct(Stringstr){if(isEmpty(str)){return"";}returnstr.replaceAll("[\\pP\\p{Punct}]","");}}Kotlin可以通过扩展函数的形式实现:/***去除所有标点符号**@paramstr处理过的字符串*/funString.trimPunct():String{returnif(this.isEmpty()){""}elsethis.replace("[\\pP\\p{Punct}]".toRegex(),"")}调用:funmain(args:Array){vala="Getridofmypunctuationmarks,generalsubject"print(a.trimPunct())}print:removemypunctuationmarksGeneralScienceProcessfinishedwithexitcode03.不要用+号拼接字符串无论是Java还是Android开发,我们都会用到字符串拼接,比如日志输出等等。在Kotlin中,支持字符串模板,我们可以轻松完成一个字符串数字的拼接。当然,你可能会说使用StringBuilder性能更好,比如:valsite="http://woquanke.com"valsb:StringBuilder=StringBuilder()sb.append("我的博客名字是《我全科》,我的博客地址是:")sb.append(site)println(sb.toString())但是kotlin的字符串模板可以优雅的做到这一点Things:valsite="http://woquanke.com"println("Myblognameis《我全科》,我的博客地址是:$site")4.也许你可以忘掉getters/setters我们经常创建一些数据的类。在这些类中,往往有一些标准函数是通过ide操作生成的。在Kotlin中,这个叫做数据类,标记为data:dataclassUser(valname:String,valage:Int)数据类自动生成getter,setting,hashcode,equals等方法5.请忘记Kotlin中的三元运算符,如果是一个表达式,也就是它返回一个值。所以不需要三元运算符(condition?then:else),因为一个简单的if将填补这个角色。//作为表达式valmax=if(a>b)aelseb6.whereisswitchwhen替换了类java语言的switch操作符。它最简单的形式如下:when(x){1->print("x==1")2->print("x==2")else->{//注意这块print("xisneither1nor2")}}如果有很多分支需要用同样的方式处理,可以将多个分支条件放在一起,用逗号分隔:when(x){0,1->print("x==0orx==1")else->print("otherwise")}可以使用任何表达式(不仅仅是常量)作为分支条件when(x){parseInt(s)->print("sencodesx")else->print("sdoesnotencodex")}也可以检测一个值是否在一个范围或集合中(in)或不(!in):when(x){in1..10->print("xisintherange")invalidNumbers->print("xisvalid")!in10..20->print("xisoutsidetherange")else->print("noneoftheabove")}另一种可能性是检测一个值是否(是)或不是(!是)特定类型的值。注意:由于智能转换,您可以访问类型的方法和属性而无需任何额外检查。funhasPrefix(x:Any)=when(x){isString->x.startsWith("prefix")else->false}when也可以用来替换if-elseif链。如果不提供参数,所有分支条件都是简单的布尔表达式,当一个分支条件为真时,执行分支:when{x.isOdd()->print("xisodd")x.isEven()->print("xiseven")else->print("xisfunny")}7.去你的ClassCastExceptionKotlin智能转换(SmartCasts)对于不可变的值,Kotlin一般不需要显式转换对象类型,编译器可以跟踪是检查类型,需要时自动插入类型转换代码(安全):funclassCast(a:Any){if(aisString){print(a.length)//编译器自动将a转换为String类型}}Kotlin编译器很聪明并且可以识别反向支票类型!is运算符,会自动插入类型转换代码:if(a!isString)returnprint(a.length)//编译器自动将x转换为String类型://|右边a自动转String类型if(a!isString||a.length==0)return//&&右边a自动转String类型if(aisString&&a.length>0){print(a.length)//a自动转为字符串}//Smartcasts(智能广播)也用于when表达式和while循环when(a){isInt->print(a+1)isString->print(a.length+1)isIntArray->print(a.sum())}如果在类型检查is/!is运算符和变量使用之间不能保证变量不可变,则不能使用智能转换。智能转换的适用条件或规则:val局部变量——始终适用!val属性-适用于私有或内部,或者类型检查是/!在声明属性的同一模块中执行;不适用于开放属性,或具有自定义getters的属性!var局部变量-适用于在类型检查和使用之间未修改的变量,并且未在修改它的lambda中捕获!var属性-不适用(因为变量可以随时修改)安全(可为空)转换运算符as?为避免抛出异常,安全转换运算符为?可用于在失败时返回nullvala:String?=bas?String尽管as?的右侧是一个非空类型的字符串,但是什么时候呢?转换失败返回可以是null(空),换句话说,as?函数参数String不能为null,但是as的返回值呢?函数可以为null8。确实需要习惯Koltin的for循环,太强大了Kotlin没有Java中的for(初值;条件;增减步长)这个规则。但是Kotlin为for循环语句添加了其他规则以满足刚才提到的规则。for循环提供了一个迭代器来迭代任何东西for循环数组被编译成一个基于索引的循环,它不会创建一个迭代器对象添加规则以满足for(初始值;条件;增加或减少步骤)这个规则增量关键字:untilrange:until[n,m)=>即大于等于n,小于m示例://循环5次,步长为1for(iin0until5){print("i=>$i\t")}输出结果为i=>0i=>1i=>2i=>3i=>4Decrementkeyword:downTorange:downTo[n,m]=>即小于等于n,大于等于m,n>例子m://循环5次,递减步长1for(iin15downTo11){print("i=>$i\t")}输出结果为:i=>15i=>14i=>13i=>12i=>11符号('..')表示使用符号('..')的增量循环的另一种操作。Range:..[n,m]=>即大于等于n,小于等于m和直到的区别,一个是简单。二是范围的不同。例子:print("Printresultusingsymbol`..`\n")for(iin20..25){print("i=>$i\t")}println()print("Printresultusinguntil\n")for(iin20until25){print("i=>$i\t")}输出结果为:使用符号..i=>20i=>21i=>22i=>23i=>24i的打印结果=>25使用untili=>20i=>21i=>22i=>23i=>24的打印结果设置step关键字:step例子:for(iin10until16step2){print("i=>$i\t")}输出是:i=>10i=>12i=>14iterationsfor循环提供了一个迭代器来遍历任何东西。for循环数组被编译为基于索引的循环,它不会创建迭代器对象来遍历字符串。这种用法在数据类型章节中的字符串类型中使用。如果还有不明白的,可以查看Kotlin——最详细的数据类型介绍。例子:for(iin"abcdefg"){print("i=>$i\t")}输出结果为:i=>ai=>bi=>ci=>di=>ei=>fi=>g遍历数组这种用法在数据类型章节的数组类型中已经用过。如果还有不明白的,可以查看Kotlin——最详细的数据类型介绍。例子:vararrayListOne=arrayOf(10,20,30,40,50)for(iinarrayListOne){print("i=>$i\t")}输出结果为:i=>10i=>20i=>30i=>40i=>50使用数组的indices属性遍历例子:vararrayListTwo=arrayOf(1,3,5,7,9)for(iinarrayListTwo.indices){println("arrayListTwo[$i]=>"+arrayListTwo[i])}输出结果为:arrayListTwo[0]=>1arrayListTwo[1]=>3arrayListTwo[2]=>5arrayListTwo[3]=>7arrayListTwo[4]=>9使用数组的withIndex()方法遍历示例:vararrayListTwo=arrayOf(1,3,5,7,9)for((index,value)inarrayListTwo.withIndex()){println("index=>$index\tvalue=>$value")}输出结果是:index=>0value=>1index=>1value=>3index=>2value=>5index=>3value=>7index=>4value=>9使用list或arrayextension函数遍历array或list有成员or扩展函数iterator()实现Iterator接口,该接口提供next()和hasNext()两个成员或扩展函数,一般与while循环配合使用。您可以查看Array.kt类。可以看到里面有iterator()函数,这个函数实现了Iterator接口。/***Createsaniteratorforiteratingovertheelementsofthearray.*/publicoperatorfuniterator():Iterator查看接口类Iterator.kt,它提供了hasNext()和next()函数。publicinterfaceIterator{/***Returnsthenextelementintheiteration.*/publicoperatorfunnext():T/***如果迭代有更多元素则返回`true`。*/publicoperatorfunhasNext():Boolean}示例:vararrayListThree=arrayOf(2,'a',3,false,9)variterator:Iterator=arrayListThree.iterator()while(iterator.hasNext()){println(iterator.next())}输出结果为:2a3false99.kotlinstreamcanreallystreamtousCollectionoperations带来极大的方便。事实上,Java8也支持流处理。我只是想在这里推广流媒体。下面是一个例子:valnames=arrayOf("Amy","Alex","Bob","Cindy","Jeff","Jack","Sunny","Sara","Steven")//过滤名字以S开头valsName=names.filter{it.startsWith("S")}.toList()//按首字母分组排序valnameGroup=names.groupBy{it[0]}.toSortedMap(Comparator{key1,key2->key1.compareTo(key2)})更多流处理请自行搜索Javastream10。少写方法重载,因为kotlin是支持默认参数的,所以在封装方法的时候会少写很多方法重载。如果没有默认参数,需要实现如下日志打印,需要写多个方法:funlog(tag:String,content:String){println("tag:$tag-->$content")}funlog(content:String){log("quanke","")}只需要一个默认参数的方法:funlog(tag:String="quanke",content:String){println("tag:$tag-->$content")}***我还是想说:对不起!不要用Java的语法思维写Kotlin!