真相:Java 开发者钟爱 Kotlin 的五个原因
Posted CSDN
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了真相:Java 开发者钟爱 Kotlin 的五个原因相关的知识,希望对你有一定的参考价值。
【CSDN编者按】现在Kotlin语言越来越流行。它不仅广泛用在移动应用开发上,也能用于服务器端系统上。你也许知道,Kotlin是个运行在JVM上的静态类型编程语言。
Kotlin之所以流行的主要原因之一就是简单。它删掉了许多Java中华而不实的代码。但是,它与Java也很相似,因此任何有经验的Java程序员都能在几个小时之内学会使用Kotlin。
这篇文章中,作者将讨论在服务器端使用Kotlin时的几个有趣的功能,并与Java作比较。下面是作者个人喜欢的Kotlin有而Java却没有的功能。
集合和泛型
我很喜欢Java,但有时候泛型集合非常不好用,特别是在需要使用通配符类型的时候。好消息时,Kotlin没有任何通配符类型,而是提供了另外两种功能,分别称为“声明端型变”和“类型投射”。我们来考虑下面的类层次结构。
abstract class Vehicle {
}
class Truck extends Vehicle {
}
class PassengerCar extends Vehicle {
}
我定义了一个泛型的仓库类Repository来容纳给定类型的所有对象。
public class Repository<T> {
List<T> l = new ArrayList();
public void addAll(List<T> l) {
this.l.addAll(l);
}
public void add(T t) {
l.add(t);
}
}
现在,我想把所有的车辆都保存在这个仓库中,于是我定义Repository r = new Repository()。但是,用List作为参数调用仓库类的方法addAll会产生错误:
即使更改Repository中的addAll的定义,也会收到下面的错误:
当然,这种情况可以得到合理的解释。首先,Java中的泛型是不可变的,也就意味着List<Truck>不是List<Vehicle>的子类型,尽管Truck是Vehicle的子类型。
addAll方法接受通配符类型参数,并扩展T作为类型参数,这意味着该方法接受一个由类型为T或T的子类型的对象的集合作为参数,而不仅仅是类型为T的对象的集合。
List<Truck>是List<? extends Vehicle>的子类型,但目标列表依然是List<Vehicle>。我不想详细展开这个行为,具体你可以阅读Java的标准。这里重要的一点是,Kotlin使用一个名为“声明端型变”(Declaration-site variance)的功能解决了这个问题。
如果给addAll方法声明中的MutableList参数添加out修饰器,编译器就允许它接受一个Truck对象的列表。Kotlin网站上给出了这种行为的解释():“用‘聪明’的词来说,类C在参数T中是协变的(covariant)吗,或者说T是个协变类型参数。你可以认为C是T的生产者,而不是T的消费者。”
class Repository<T> {
var l: MutableList<T> = ArrayList()
fun addAll(objects: MutableList<out T>) {
l.addAll(objects)
}
fun add(o: T) {
l.add(o)
}
}
fun main(args: Array) {
val r = Repository<Vehicle>()
var l1: MutableList<Truck> = ArrayList()
l1.add(Truck())
r.addAll(l1)
println("${r.l.size}")
}
数据类
你也许知道Java的数据类“POJO”(Plain Old Java Object)。根据Java的最佳实践,这种类应当定义getter、setter、hashCode和equals方法,还要定义toString方法供日志功能使用。即使是只有几个字段的简单类,这种实现也会占用很多篇幅。如下所示(代码是Eclipse IDE自动生成的):
public class Person {
private Integer id;
private String firstName;
private String lastName;
private int age;
public Person(Integer id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((lastName == null) ? 0 : lastName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (firstName == null) {
if (other.firstName != null)
return false;
} else if (!firstName.equals(other.firstName))
return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (lastName == null) {
if (other.lastName != null)
return false;
} else if (!lastName.equals(other.lastName))
return false;
return true;
}
@Override
public String toString() {
return "Person [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + "]";
}
}
为了避免在POJO类中添加太多代码,可以使用Lombok项目。这个项目提供了一系列可以用在类上的注解,来提供getter、setter、equals、hashCode等方法的实现。
它还可以用@Data对类进行注解,能一次性提供@ToString、@EqualsAndHashCode、@Getter、@Setter和@REquiredArgsConstructor的功能。因此,使用Lombok和@Data,POJO会变成这样——假设你不需要带参数的构造函数的话:
@Data
public class Person {
private Integer id;
private String firstName;
private String lastName;
private int age;
}
在Java应用程序中加入并使用Lombok很简单,所有主流的IDE都支持,但Kotlin本身就解决了这个问题。
它提供了“数据类”功能,只需在类定义后面加入data关键字即可。编译器会自动根据主构造方法中定义的属性生成以下方法:
toString()方法;
按照属性的定义顺序,依次生成相应的componentN()函数;
copy()函数。
因为Kotlin内部会针对可改变的属性(用var定义的属性)生成默认的getter和setter,为只读属性(用val定义的属性)生成getter,因此Person这个Java POJO用Kotlin来实现就非常简单:
data class Person(val firstName: String, val lastName: String, val id: Int) {
var age: Int = 0
}
值得一提的是,编译器在自动生成函数时只会使用主构造函数中定义的属性。所以,类内部定义的age字段不会被toString、equals、hashCode和copy使用。
测试方法的名称
现在我们来实现一些测试用例,来证明第二步中的功能可以正常工作。下面三个测试会比较两个对象,它们的age属性拥有不同的值,还会试图将同一个对象添加到Java的HashSet中两次,并检查数据类的componentN方法能否按照正确的顺序返回属性。
@Test fun `Test person equality excluding "age" property`() {
val person = Person("John", "Smith", 1)
person.age = 35
val person2 = Person("John", "Smith", 1)
person2.age = 45
Assert.assertEquals(person, person2)
}
@Test fun `Test person componentN method for properties`() {
val person = Person("John", "Smith", 1)
Assert.assertEquals("John", person.component1())
Assert.assertEquals("Smith", person.component2())
Assert.assertEquals(1, person.component3())
}
@Test fun `Test adding and getting person from a Set`() {
val s = HashSet()
val person = Person("John", "Smith", 1)
var added = s.add(person)
Assert.assertTrue(added)
added = s.add(person)
Assert.assertFalse(added)
}
从上面的代码片段可以看到,Kotlin可以在方法名中使用空格,只需用反引号括起来即可。这个功能可以给测试用例起个描述性的名字,在执行过程中可以清晰地看到用例的执行过程,从而能精确地得知测试的状态。
扩展
我们来考虑这种情况:我们有一个函数库,里面包含了不可改变的类定义,但我们需要给它添加一些方法。在Java中有几种方法能实现这个需求。我们可以继承已有的类并实现新方法,可以通过其他方式实现,比如利用修饰器模式。
现在,我们假设有下面的Java类,包含人员的列表,并对外提供了getter和setter:
public class Organization {
private List<Person> persons;
public List<Person> getPersons() {
return persons;
}
public void setPersons(List<Person> persons) {
this.persons = persons;
}
}
如果我想添加一个方法,能给列表添加一个Person对象,我必须继承现有的Organization类,并在继承类中实现新方法。
public class OrganizationExt extends Organization {
public void addPerson(Person person) {
getPersons().add(person);
}
}
Kotlin提供了一种无须从基类继承就能扩展已有类的方式,即一种特殊的定义,称为扩展(extensions)。
下面是在Kotlin中定义与Java类似的Organization类的方式。因为Kotlin将简单的List类视作不可改变,因此我们需要使用MutableList来定义。
class Organization(val persons: MutableList = ArrayList()) {
}
如下可以很容易地扩展出addPerson方法。扩展是静态解析的,而且它们不会修改被扩展的类。
class OrganizationTest {
fun Organization.addPerson(person: Person) {
persons.add(person)
}
@Test
fun testExtension() {
val organization = Organization()
organization.addPerson(Person("John", "Smith", 1))
Assert.assertTrue(organization.persons.size == 1)
}
}
字符串模板
下面这个Java中没有的功能你肯定会喜欢:
println("Organization ${organization.name} with ${organization.persons.size} persons")
结论
当然,Java和Kotlin之间还有其他区别。这只是我个人喜欢的Java中没有的特性。你可以从GitHub上(https://github.com/piomin/sample-kotlin-playground.git)找到文中的源代码。
原文:https://dzone.com/articles/5-things-you-will-like-in-kotlin-as-a-java-develop
译者:弯月,责编:胡巍巍
微信改版了,
想快速看到CSDN的热乎文章,
如果你有优质的文章,或是行业热点事件、技术趋势的真知灼见,或是深度的应用实践、场景方案等的新见解,欢迎联系 CSDN 投稿,联系方式:微信(guorui_1118,请备注投稿+姓名+公司职位),邮箱(guorui@csdn.net)。
推荐阅读:
以上是关于真相:Java 开发者钟爱 Kotlin 的五个原因的主要内容,如果未能解决你的问题,请参考以下文章