Kotlin 新特性你真的了解吗?
开发者技术前线
共 27708字,需浏览 56分钟
·
2021-07-04 11:49
点击“开发者技术前线”,选择“星标”
让一部分开发者先看到未来
前言
一.基本语法
1.1 变量声明
/** * 学生类 */
class Student {
//可变变量声明关键字var
var name: String = "小明"
//不指定变量类型的隐式声明
var age = 10
//只读变量声明关键字val
val sex: String = "男"
fun learn() {
print("$name is learning")
}
}
1.2 语句
when表达式
//when语句可以作为表达式,符合条件的分支就是整个表达式的值
val b = when (num) {
in 0..9 -> {true}
else -> { false }
}
in关键字的使用
//判断是否在区间内
if (num in 1..9) {
print("ok")
}
//不在区间内
if (num !in 1..9) {
print("no")
}
//遍历数组
for (name in names) {
print(name)
}
//判断name是否在数组内
if (name in names) {
print("ok")
}
类型转换
fun foo(o: Any): Int {
if (o is String) {
//判断完类型之后,o会被自动转换为String类型
return o.length
}
//可以使用!is来取反
if (o !is String) {
return 0
}
return 0
}
空值检测
1.3 函数声明
fun plus(x: Int, y: Int) : Int {
return x + y
}
Kotlin中方法声明的关键字是fun,声明的定义格式为:
❝
可见性修饰符 fun 函数名(参数名:类型,...) : 返回值类型{ 函数体 }
1.如果函数体内实现很简单只有一行代码那么函数也可以这样写
fun plus(x: Int, y: Int): Int = x + y
fun plus(x: Int, y: Int) = x + y
1.4 函数的默认参数
fun plus(x: Int, y: Int = 10) : Int {
return x + y
}
可变参数
//java中,可变参数使用...表示
public void selectCourse(String... strArray){
}
//kotlin中,可变参数使用vararg关键字表示
fun selectCourse(vararg strArray: String?) {
}
二.类与对象
2.1 类的构造函数
//声明带一个参数的主构造函数
class Person constructor(name:String){
init {
//初始化的代码可以放到init初始化块中
//初始化块是主构造函数的一部分,因此所有的初始化块中的代码都会在次构造函数体之前执行
}
//次级构造函数委托给主构造函数直接委托
constructor(name:String,parent:Person):this(name){
}
//委托给别的次级构造函数间接委托
constructor(name:String,parent:Person,age:Int):this(name,parent){
}
}
2.2 类的继承
如果派生类有一个主构造函数,其基类型则必须用基类的主构造函数参数初始化
class Derived(p: Int) : Base(p){}
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
//对于可覆盖的成员需要使用显示修饰符open
open class Rectangle {
open var length = 0
open fun draw() { /*.......*/
}
}
interface Polygon {
//接口中的成员默认open
fun draw() { /*........*/
}
}
class Square() : Rectangle(), Polygon {
override fun draw() {
super<Rectangle>.draw() //调用Rectangle.draw()
super<Polygon>.draw() //调用Polygon.draw()
}
}
2.3 类的属性
2.3.1 getter与setter
val修饰的属性只有getter
var age: Int = 11
get() {
return field
}
set(value) {
field = value + 1
}
2.3.2 幕后字段
class Person {
var name = ""
set(value) {
this.name = value
}
}
public final class Person {
private String name = "Paul";
public final String getName() {
return this.name;
}
public final void setName( String value) {
this.setName(value);
}
}
在Kotlin中,如果属性至少一个访问器使用默认实现,那么Kotlin会自动提供幕后字段,用关键字field表示,幕后字段主要用于自定义getter和setter中,并且只能在getter和setter中访问。
上面代码应该修改成
class Person {
var name = ""
set(value) {
filed = value
}
}
使用默认getter/setter的属性,一定有幕后字段。对于var属性来说,只要getter/setter中有一个使用默认实现,就会生成幕后字段; 在自定义getter/setter中使用了field的属性。
2.3.3 常量
//每次获取currentTimeMills都是不同的
val currentTimeMills: Long
get() {
return System.currentTimeMillis()
}
class Person {
companion object{
//使用const修饰符
const val TAG = "Person"
}
//使用@JvmField注解方式
//其内部原理是抑制编译器生成相应的getter方法并且无法重写val的get方法
@JvmField
val TAG = "Person"
}
2.3.4 属性延迟初始化
2.4 内部类
class Outer {
private val b: Int = 1
inner class Inner {
fun foo(): Int = b
}
}
val d = Outer().Inner().foo() // ===1
2.5 数据类
主构造函数至少有一个参数 主构造函数的参数需要显示的标记为val或者var 数据类不能是抽象,开放,密封或者内部的
类中声明的属性在toString(),equals(),hashCode()以及copy()的实现中被排除
data class Person(val name: String) {
var age: Int = 0
}
fun foo() {
val person1 = Person("John")
val person2 = Person("John")
person1.age = 10
person2.age = 20
//person1和person2虽然有不同的年龄但是会视为相等
person1.equals(person2) //true
}
2.6 枚举类
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
要实现枚举常量的匿名类,则必须提供一个抽象方法(必须重写的方法)。且该方法定义在枚举类内部。而且必须在枚举变量的后面。 枚举变量之间使用逗号(,)分割开。但是最后一个枚举变量必须使用分号结束。不然定义不了抽象方法
enum class Color(val rgb: Int) {
RED(0xFF0000) {
override fun print() {
print("red")
}
},
GREEN(0x00FF00) {
override fun print() {
print("green")
}
},
BLUE(0x0000FF) {
override fun print() {
print("blue")
}
};
abstract fun print()
}
fun main() {
Color.BLUE.print()
}
每个枚举常量都包含两个属性:name(枚举常量名)和ordinal(枚举常量位置) 提供了values()和valueOf()方法来检测指定的名称与枚举类中定义的任何枚举常量是否匹配。
2.7 委托
2.7.1 类委托
//创建接口
interface Base {
fun print()
}
//实现此接口的被委托的类
class BaseImpl(val x: Int) : Base {
override fun print() {
print(x)
}
}
//通过关键字by 建立委托类
class Agent(b: Base) : Base by b
fun main(args: Array<String>) {
val b = BaseImpl(1)
Agent(b).print() // 输出 1
}
如果Agent()中覆盖实现了print()方法,那么将使用覆盖的实现,而不是委托对象中的实现
class Agent(b: Base) : Base by b {
override fun print() {
print("123")
}
}
fun main(args: Array<String>) {
val b = BaseImpl(1)
Agent(b).print() // 输出 123
}
2.7.2 属性委托
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "thank you for delegating '${property.name}' to me"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
print("$value has been assigned to '${property.name}'")
}
}
class Example {
var p: String by Delegate()
}
fun foo() {
val e = Example()
print(e.p) // thank you for delegating 'p' to me
e.p = "new" // new has been assigned to 'p'
}
延迟属性Lazy
class LazySample {
val lazyStr: String by lazy {
print("init")
"123"
}
}
fun main(args:Array<String>){
val sample = LazySample()
print("lazy = ${sample.lazyStr}")
print("lazy = ${sample.lazyStr}")
}
// 输出
// init
// lazy = 123
// lazy = 123
属性非空强校验
var name: String by Delegates.notNull()
fun init(name: String) {
this.name = name
}
fun main(args: Array<String>) {
val student = Student()
//初始化要在使用之前不然会报异常->IllegalStateException
student.init("张三")
print(student.name)
}
可观察属性
private var name: String by Delegates.observable("oldValue") { property, oldValue, newValue ->
print("${property.name} 属性变化: $oldValue -> $newValue")
}
private var age: Int by Delegates.observable(0) { property, oldValue, newValue ->
print("${property.name} 属性变化: $oldValue -> $newValue")
}
private fun print() {
print("name = $name")
print("age = $age")
}
fun main(args: Array<String>) {
print()
name = "Bob"
age = -1 //小于0,修改失败
print()
age = 28
print()
}
/*
* name = oldValue
* age = 0
* name 属性变化: oldValue -> Bob
* age 属性变化: 0 -> -1
* name = Bob
* age = 0
* age 属性变化: 0 -> 28
* name = Bob
* ag
三.函数
3.1 局部函数
fun outer(str: String) {
fun inner(index: Int) {
str.substring(0, index)
}
inner(2)
}
3.2 函数类型
所有函数类型都有一个圆括号括起来的参数类型列表以及一个返回类型:(A, B) -> C 表示接受类型分别为 A 与 B 两个参数并返回一个 C 类型值的函数类型 函数类型可以有一个额外的接收者类型,它在表示法中的点之前指定:类型 A.(B) -> C 表示可以在 A 的接收者对象上以一个 B 类型参数来调用并返回一个 C 类型值的函数. 挂起函数属于特殊种类的函数类型,它的表示法中有一个 suspend 修饰符 ,例如 suspend () -> Unit 或者 suspend A.(B) -> C 函数类型可以使用圆括号进行接合:(Int) -> ((Int) -> Unit) 箭头表示法是右结合的,(Int) -> (Int) -> Unit 与(Int) -> ((Int) -> Unit)等价,但不等于 ((Int) -> (Int)) -> Unit。 typealias ClickHandler = (Button, ClickEvent) -> Unit 通过使用类型别名给函数类型起一个别称
//(A,B) -> C
val fun1: (String, String) -> Unit = { s1, s2 ->
print("$s1 and $s2")
}
//A.(B) -> C
val fun2: String.(String) -> Unit = { s ->
print("$this $s")
}
fun foo() {
fun1("123", "456")
fun1.invoke("123", "456")
fun2("123", "456")
fun2.invoke("123", "456")
"123".fun2("456")
}
3.3 Lambda表达式
Lambda表达式总是在花括号中,参数声明放在花括号内,并有可选的类型标注,函数体跟在一个->符号之后,如果推断出的该Lambda的返回类型不是Unit,那么最后一个表达式会视为返回值。
val sum = { x: Int, y: Int -> x + y }
fun sum(m: Int, n: Int, param: (x: Int, y: Int) -> Int): Int {
return param.invoke(m, n)
}
fun main(args: Array<String>) {
sum(1, 2) { x, y -> x + y }
}
fun sum(m: Int, n: Int, param: (x: Int, y: Int) -> Int): Int {
return param.invoke(m, n)
}
fun main(args: Array<String>) {
sum(1, 2) { x, _ -> x + 10 }
}
3.4 匿名函数
fun(x: Int, y: Int): Int = x + y
3.5 高阶函数
fun sum(m: Int, n: Int, param: (x: Int, y: Int) -> Int): Int {
return param.invoke(m, n)
}
fun main(args: Array<String>) {
//函数类型作为参数
val param: (x: Int, y: Int) -> Int = { x, y -> x + y }
sum(1, 2, param)
}
fun sum(): (x: Int, y: Int) -> Int {
return { x, y -> x + y }
}
fun main(args: Array<String>) {
//函数类型作为返回值类型
sum().invoke(1, 2)
}
3.6 中缀表示法
它们必须是成员函数或扩展函数; 它们必须只有一个参数; 其参数不得接受可变数量的参数且不能有默认值。
infix fun Int.sum(x: Int): Int {
return 1
}
fun main(args: Array<String>) {
//用中缀表示法调用该函数
1 sum 2
//等同于调用
1.sum(2)
}
3.7 内联函数
fun main(args: Array<String>) {
print("start")
show("123")
print("end")
}
inline fun show(str: String) {
print(str)
}
public final void main(@NotNull String[] args) {
System.out.print("start");
System.out.print("123");
System.out.print("end");
}
public final void show(@NotNull String str) {
System.out.print(str);
}
public final void main(@NotNull String[] args) {
System.out.print("start");
this.show("123")
System.out.print("end");
}
public final void show(@NotNull String str) {
System.out.print(str);
}
3.8 标准库函数
函数名称 | 定义 | 功能 |
---|---|---|
run | public inline fun | 调用run函数块。返回值为函数块最后一行,或者指定return表达式 |
apply | public inline fun | 调用某对象的apply函数,在函数块内可以通过 this 指代该对象。返回值为该对象自己 |
let | public inline fun <T, R> T.let(block: (T) -> R): R = block(this) | 调用某对象的let函数,则该对象为函数的参数。在函数块内可以通过 it 指代该对象。返回值为函数块的最后一行或指定return表达式 |
also | public inline fun | 调用某对象的also函数,则该对象为函数的参数。在函数块内可以通过 it 指代该对象。返回值为该对象自己 |
with | public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block() | 将某对象作为函数的参数,在函数块内可以通过 this 指代该对象。返回值为函数块的最后一行或指定return表达式 |
总结
— 完 —
点这里👇关注我,记得标星呀~
前线推出学习交流一定要备注:研究/工作方向+地点+学校/公司+昵称(如JAVA+上海
扫码加小编微信,进群和大佬们零距离
后台回复“电子书” “资料” 领取一份干货,数百面试手册等你 开发者技术前线 ,汇集技术前线快讯和关注行业趋势,大厂干货, 是开发者经历和成长的优秀指南。
评论