Kotlin+Spring Boot开发REST API实战

Kotlin简介

Kotlin是面向JVM、Android、JavaScript 及原生平台的开源静态类型编程语言,支持面向对象和函数式编程。Kotlin项目开始于2010年,由 JetBrains开发,2016年2月发布第一个官方版本1.0,当前版本是 1.3.72,发布于2020年4月。

Spring Framework 5.0开始支持Kotlin,采用Kotlin的语言特性提供更简洁的API,当前官方文档提供Java和Kotlin两种语言的示例代码。Gradle 3.0开始支持Kotlin,Kotlin DSL为传统的Groovy DSL提供了替代语法,在支持的IDE中增强了编辑体验。

相对于Java,Kotlin更简洁,代码行数减少约40%。Kotlin更安全,例如对不可空类型的支持使应用程序不易发生NPE。Kotlin语法更强大,如智能类型转换、高阶函数、扩展函数等。Kotlin与Java可以100%互操作,可以与Java库交互。Kotlin/JVM 编译器会生成兼容 Java的字节码。Kotlin支持大型代码库从Java到Kotlin逐步迁移,可以用Kotlin编写新代码,同时系统中较旧部分继续用Java。

对于Java开发人员,Kotlin入门很容易。主要的Java IDE都支持 Kotlin,包括 IntelliJ IDEA、Android Studio、Eclipse和NetBeans,Kotlin 插件支持自动Java到Kotlin的转换。Kotlin官方文档全面、详尽,您还可以通过Kotlin 心印在线学习Kotlin语言的主要功能。

简洁之处

Class

在Java中定义一个POJO类需要声明属性、getters和setters等方法。在Kotlin中声明一个仅包含getters和setters方法的POJO仅需一行代码:

class Person(var firstName: String, var lastName: String, var age: Int)

编译器会自动从主构造函数中声明的所有属性导出getters和setters方法。var 声明的为可变属性,val 声明的为只读属性,只读属性仅会生成getters方法。若没有使用var或val声明,则只是普通参数,仅可用于初始化块、类体内声明的属性初始化。

class Customer(name: String) {
   
    val customerKey = name.toUpperCase()

    init {
   
        println(name)
    }
}

Kotlin不需分号,空类可以省略花括号。

下面代码在类体内声明属性,未定义主构造函数:

class Address {
   
    var name: String = ""
    var street: String = ""
    var city: String = ""
    var state: String? = null
    var zip: String = ""
}

编译生成的字节码仅包含getters和setters方法,没有构造方法。

声明属性的完整语法:

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]

初始器(initializer)、getter和setter都是可选的。属性类型如果可以从初始器(或从getter返回值)中推断出来,也可以省略。仅在我们要自定义取值、赋值方法时,才需定义getter和setter,如:

var stringRepresentation: String
    get() = this.toString()
    set(value) {
   
        setDataFromString(value) // 解析字符串并赋值给其他属性
    }

读取、设置属性值时直接使用属性名称, 而不是使用getter和setter方法,如:

fun main() {
   
    var address = Address() // Kotlin 中没有“new”关键字
    address.name = "Holmes, Sherlock"
    address.street = "Baker"
    address.city = "London"
    address.zip = "123456"

    println(address.name)
}

在Java中,为了简化代码可以引入lombok,给类添加注解自动生成getter、setter等方法,而在Kotlin中这一切都不需要了。

Data Class

/*
使用一行代码创建一个包含getters、setters、`equals()`、`hashCode()`、`toString()`和`copy()` 的 POJO
*/
data class User(val name: String, val age: Int)

编译器自动从主构造函数中声明的所有属性导出getters、setters、equals()、hashCode()、toString()、componentN()(按声明顺序对应于所有属性)和copy() 函数。

生成的Java代码
下面是编译生成的Java class文件反编译后的代码:

import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@Metadata(mv = {
   1, 1, 16}, bv = {
   1, 0, 3}, k = 1, d1 = {
   "\000 \n\002\030\002\n\002\020\000\n\000\n\002\020\016\n\000\n\002\020\b\n\002\b\t\n\002\020\013\n\002\b\004\b\b\030\0002\0020\001B\025\022\006\020\002\032\0020\003\022\006\020\004\032\0020\005\006\002\020\006J\t\020\013\032\0020\003H\003J\t\020\f\032\0020\005H\003J\035\020\r\032\0020\0002\b\b\002\020\002\032\0020\0032\b\b\002\020\004\032\0020\005H\001J\023\020\016\032\0020\0172\b\020\020\032\004\030\0010\001H\003J\t\020\021\032\0020\005H\001J\t\020\022\032\0020\003H\001R\021\020\004\032\0020\005\006\b\n\000\032\004\b\007\020\bR\021\020\002\032\0020\003\006\b\n\000\032\004\b\t\020\n\006\023"}, d2 = {
   "Lio/itrunner/heroes/dto/User;", "", "name", "", "age", "", "(Ljava/lang/String;I)V", "getAge", "()I", "getName", "()Ljava/lang/String;", "component1", "component2", "copy", "equals", "", "other", "hashCode", "toString", "heroes-kotlin"})
public final class User {
   
  @NotNull
  private final String name;
  
  private final int age;
  
  @NotNull
  public final String getName() {
   
    return this.name;
  }
  
  public final int getAge() {
   
    return this.age;
  }
  
  public User(@NotNull String name, int age) {
   
    this.name = name;
    this.age = age;
  }
  
  @NotNull
  public final String component1() {
   
    return this.name;
  }
  
  public final int component2() {
   
    return this.age;
  }
  
  @NotNull
  public final User copy(@NotNull String name, int age) {
   
    Intrinsics.checkParameterIsNotNull(name, "name");
    return new User(name, age);
  }
  
  @NotNull
  public String toString() {
   
    return "User(name=" + this.name + ", age=" + this.age + ")";
  }
  
  public int hashCode() {
   
    return ((this.name != null) ? this.name.hashCode() : 0) * 31 + Integer.hashCode(this.age);
  }
  
  public boolean equals(@Nullable Object paramObject) {
   
    if (this != paramObject) {
   
      if (paramObject instanceof User) {
   
        User user = (User)paramObject;
        if (Intrinsics.areEqual(this.name, user.name) && this.age == user.age)
          return true; 
      } 
    } else {
   
      return true;
    } 
    return false;
  }
}

说明:

  1. 编译后生成的每个类文件都含有@Metadata注解,具体参数的意义请查看Kotlin文档。
  2. 生成的类、属性、getters/setters方法都是final的。

解构声明
为数据类生成的Component函数可在解构声明中使用:

val user = User("Jane", 35)
val (name, age) = user
println("$name, $age years of age") // 输出 "Jane, 35 years of age"

一个解构声明同时创建多个变量。 解构声明会被编译成以下代码:

val name = user.component1()
val age = user.component2()

Object

单例模式是常用的一种设计模式,Kotlin的实现方式非常简单,仅需使用Object声明。

object DataProviderManager {
   
    fun registerDataProvider(provider: String) {
   
        // ……
    }
}

编译后生成的Java代码如下:

public final class DataProviderManager {
   
  public static final DataProviderManager INSTANCE;
  
  static {
   
    DataProviderManager dataProviderManager = new DataProviderManager();
  }
  
  public final void registerDataProvider(@NotNull String provider) {
   
    Intrinsics.checkParameterIsNotNull(provider, "provider");
    // ……
  }
}

默认参数和具名参数

普通函数和构造函数的参数都可以有默认值,当省略相应的参数时使用默认值,这可以减少重载数量。当一个函数有大量的参数或默认参数时,可以使用具名参数来调用,这会非常方便,代码也更具可读性。
函数

fun reformat(str: String,
             normalizeCase: Boolean = true,
             upperCaseFirstLetter: Boolean = true,
             divideByCamelHumps: Boolean = false,
             wordSeparator: Char = ' ') {
   
/*……*/
}

调用:

reformat(str) // 使用默认参数
reformat(str, normalizeCase = true, upperCaseFirstLetter = true, divideByCamelHumps = false, wordSeparator = '_')  // 使用具名参数
reformat(str, wordSeparator = '_') // 使用具名参数和默认参数

构造函数

class User(var name: String = "", var email: String = "", var address: String = "")

调用:

val user1 = User()
val user2 = User(name = "Jason")

如果主构造函数的所有参数都有默认值,编译器会生成一个使用默认值的无参构造函数。

类型别名

类型别名为现有类型或函数提供替代名称。 如果名称太长,可以引入较短的名称,则可使用新的名称替代原类型名。

typealias NodeSet = Set<Network.Node>
typealias FileTable<K> = MutableMap<K, MutableList<File>>

typealias MyHandler = (Int, String, Any) -> Unit
typealias Predicate<T> = (T) -> Boolean

字符串模板

字符串字面值可以包含模板表达式 ,会求值并把结果合并到字符串中。 模板表达式可以为$开头的变量,或为${}括起来的任意表达式。

val s = "abc"
println("$s.length is ${
     s.length}") // 输出“abc.length is 3”

智能类型转换

if (obj is String) {
    print(obj.length)
}

if (obj !is String) { // 与 !(obj is String) 相同
    print("Not a String")
}
else {
    print(obj.length)
}

在许多情况下,不需要使用显式转换操作符(as),编译器可以跟踪is检测自动智能转换。

as转换

val x: String = y as String // 不安全,y为空时会抛出异常
val x: String? = y as? String // 安全,y为空时值为空

控制语句

if、when既可用作语句,也可用作表达式。作为表达式时,每个分支可以是一个代码块,它的值是块中最后表达式的值。Kotlin没有三目操作符,可以使用if表达式替代。

// 作为语句
var max: Int
if (a > b) {
   
    max = a
} else {
   
    max = b
}
 
// 作为表达式
val max = if (a > b) a else b

Java switch语句仅支持整型、字符串或枚举,Kotlin when语句支持任何类型,分支条件可以使用任意表达式(而不只是常量),每个分支也不需要break语句。

// 多分支处理方式相同时,可以把多个分支条件放在一起,用逗号分隔
when (x) {
   
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

// 分支条件可以使用任意表达式
when (x) {
   
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
}

// 单表达式函数
fun hasPrefix(x: Any) = when(x) {
   
    is String -> x.startsWith("prefix")
    else -> false
}

强大、简洁的for语法:

for (i in 1..100) {
    …… }  // 闭区间:包含 100
for (i in 1 until 100) {
    …… } // 半开区间:不包含 100
for (x in 2..10 step 2) {
    …… }
for (x in 10 downTo 1) {
    …… }
for ((k, v) in map) {
   
    println("$k -> $v")
}

集合操作

Kotlin标准库提供了集合操作的多种函数,包括简单的操作,如获取或添加元素,以及更复杂的操作,如搜索、排序、过滤、转换等。
创建集合

// 只读集合
val numbers = listOf(1, 2, 3, 4)
val numbersSet = setOf("one", "two", "three", "four")
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)

// 可变集合
val numbers = mutableListOf("one", "five", "six")
val numbersSet = mutableSetOf("one", "two", "three", "four")
val numbersMap = mutableMapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)

// 空集合
val empty = emptyList<String>()
val emptySet = emptySet<String>()
val emptyMap = emptyMap<String, String>()

map转换

val numbers = setOf(1, 2, 3)
println(numbers.map {
    it * 3 })

双路合并

val colors = listOf("red", "brown", "grey")
val animals = listOf("fox", "bear", "wolf")
println(colors zip animals) // 输出结果为[(red, fox), (brown, bear), (grey, wolf)]

val twoAnimals = listOf("fox", "bear")
println(colors.zip(twoAnimals)) // 输出结果为[(red, fox), (brown, bear)]

关联

val numbers = listOf("one", "two", "three", "four")
println(numbers.associateWith {
    it.length }) // 转换为map,输出结果为{one=3, two=3, three=5, four=4}

过滤

val numbers = listOf("one", "two", "three", "four")  
val longerThan3 = numbers.filter {
    it.length > 3 }
println(longerThan3) // 输出结果为[three, four]

划分

val numbers = listOf("one", "two", "three", "four")
val (match, rest) = numbers.partition {
    it.length > 3 }

println(match) // 输出结果为[three, four]
println(rest) // 输出结果为[one, two]

plus 与 minus

val numbers = listOf("one", "two", "three", "four")

val plusList = numbers + "five"
val minusList = numbers - listOf("three", "four")
println(plusList) // 输出结果为 [one, two, three, four, five]
println(minusList) // 输出结果为 [one, two]

slice

val numbers = listOf("one", "two", "three", "four", "five", "six")    
println(numbers.slice(1..3)) 
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值