现在Kotlin语言越来越流行。它不仅广泛用在移动应用开发上,也能用于服务器端系统上。你也许知道,Kotlin是个运行在JVM上的静态类型编程语言。
Kotlin之所以流行的主要原因之一就是简单。它删掉了许多Java中华而不实的代码。但是,它与Java也很相似,因此任何有经验的Java程序员都能在几个小时之内学会使用Kotlin。
这篇文章中,作者将讨论在服务器端使用Kotlin时的几个有趣的功能,并与Java作比较。下面是作者个人喜欢的Kotlin有而Java却没有的功能。
集合和泛型
我很喜欢Java,但有时候泛型集合非常不好用,特别是在需要使用通配符类型的时候。好消息时,Kotlin没有任何通配符类型,而是提供了另外两种功能,分别称为“声明端型变”和“类型投射”。我们来考虑下面的类层次结构。
abstractclassVhicl{}classTruckxtndsVhicl{}classPassngrCarxtndsVhicl{}
我定义了一个泛型的仓库类Rpository来容纳给定类型的所有对象。
publicclassRpositoryT{ListTl=nwArrayList();publicvoidaddAll(ListTl){this.l.addAll(l);}publicvoidadd(Tt){l.add(t);}}
现在,我想把所有的车辆都保存在这个仓库中,于是我定义Rpositoryr=nwRpository()。但是,用List作为参数调用仓库类的方法addAll会产生错误:
即使更改Rpository中的addAll的定义,也会收到下面的错误:
当然,这种情况可以得到合理的解释。首先,Java中的泛型是不可变的,也就意味着ListTruck不是ListVhicl的子类型,尽管Truck是Vhicl的子类型。
addAll方法接受通配符类型参数,并扩展T作为类型参数,这意味着该方法接受一个由类型为T或T的子类型的对象的集合作为参数,而不仅仅是类型为T的对象的集合。
ListTruck是List?xtndsVhicl的子类型,但目标列表依然是ListVhicl。我不想详细展开这个行为,具体你可以阅读Java的标准。这里重要的一点是,Kotlin使用一个名为“声明端型变”(Dclaration-sitvarianc)的功能解决了这个问题。
如果给addAll方法声明中的MutablList参数添加out修饰器,编译器就允许它接受一个Truck对象的列表。Kotlin网站上给出了这种行为的解释():“用‘聪明’的词来说,类C在参数T中是协变的(covariant)吗,或者说T是个协变类型参数。你可以认为C是T的生产者,而不是T的消费者。”
classRpositoryT{varl:MutablListT=ArrayList()funaddAll(objcts:MutablListoutT){l.addAll(objcts)}funadd(o:T){l.add(o)}}funmain(args:Array){valr=RpositoryVhicl()varl1:MutablListTruck=ArrayList()l1.add(Truck())r.addAll(l1)println("${r.l.siz}")}
数据类
你也许知道Java的数据类“POJO”(PlainOldJavaObjct)。根据Java的最佳实践,这种类应当定义gttr、sttr、hashCod和quals方法,还要定义toString方法供日志功能使用。即使是只有几个字段的简单类,这种实现也会占用很多篇幅。如下所示(代码是EclipsIDE自动生成的):
publicclassPrson{privatIntgrid;privatStringfirstNam;privatStringlastNam;privatintag;publicPrson(Intgrid,StringfirstNam,StringlastNam){this.id=id;this.firstNam=firstNam;this.lastNam=lastNam;}publicIntgrgtId(){turnid;}publicvoidstId(Intgrid){this.id=id;}publicStringgtFirstNam(){turnfirstNam;}publicvoidstFirstNam(StringfirstNam){this.firstNam=firstNam;}publicStringgtLastNam(){turnlastNam;}publicvoidstLastNam(StringlastNam){this.lastNam=lastNam;}publicintgtAg(){turnag;}publicvoidstAg(intag){this.ag=ag;}
OvrridpublicinthashCod(){finalintprim=31;intsult=1;sult=prim*sult+((firstNam==null)?0:firstNam.hashCod());sult=prim*sult+((id==null)?0:id.hashCod());sult=prim*sult+((lastNam==null)?0:lastNam.hashCod());turnsult;}Ovrridpublicboolanquals(Objctobj){if(this==obj)turntru;if(obj==null)turnfals;if(gtClass()!=obj.gtClass())turnfals;Prsonothr=(Prson)obj;if(firstNam==null){if(othr.firstNam!=null)turnfals;}lsif(!firstNam.quals(othr.firstNam))turnfals;if(id==null){if(othr.id!=null)turnfals;}lsif(!id.quals(othr.id))turnfals;if(lastNam==null){if(othr.lastNam!=null)turnfals;}lsif(!lastNam.quals(othr.lastNam))turnfals;turntru;}OvrridpublicStringtoString(){turn"Prson[id="+id+",firstNam="+firstNam+",lastNam="+lastNam+"]";}}为了避免在POJO类中添加太多代码,可以使用Lombok项目。这个项目提供了一系列可以用在类上的注解,来提供gttr、sttr、quals、hashCod等方法的实现。
它还可以用
Data对类进行注解,能一次性提供ToString、EqualsAndHashCod、Gttr、Sttr和REquidArgsConstructor的功能。因此,使用Lombok和Data,POJO会变成这样——假设你不需要带参数的构造函数的话:DatapublicclassPrson{privatIntgrid;privatStringfirstNam;privatStringlastNam;privatintag;}在Java应用程序中加入并使用Lombok很简单,所有主流的IDE都支持,但Kotlin本身就解决了这个问题。
它提供了“数据类”功能,只需在类定义后面加入data关键字即可。编译器会自动根据主构造方法中定义的属性生成以下方法:
toString()方法;
按照属性的定义顺序,依次生成相应的