目录
一、Kotlin 基础语法
1、方法函数
2、常量 val 和变量 var
3、${} 字符串模板
4、null 处理 !!. 不能为空 ?.为空不处理 ?:为空处理成
5、is 类型转换 相当于 instanceof
6、Any 相当于 Java的 Object
二、Kotlin 基本数据类型
1、基本数据类型(6种,比java少了boolean/char)
2、数据类型转换
3、字符 `c`
4、数组arrayOf、arrayListOf
5、字符串换行 """xxx"""
三、Kotlin 条件控制
1、if语句
2、判断是否在区间内 in
3、When 表达式(相当于 Java switch语句)
四、Kotlin 循环控制
五、Kotlin 类和对象
1、类
2、属性
3、自定义set/get方法 field 表示当前属性
4、 lateinit 延迟初始化关键字
5、抽象类
6、嵌套类
7、内部类 inner (可使用外部类属性和方法)
8、类的修饰符
六、Kotlin 继承
1、可以被继承的类都要用 open修饰符
2、构造函数
3、重写 override
七、Kotlin 接口
八、Kotlin 扩展
1、扩展函数
2、扩展一个空对象
3、扩展函数是静态解析的(注意)
4、拓展属性
5、伴生对象 companion object 相当于Java 静态(修饰符单例)
九、Kotlin 数据类与密封类
1、数据类 data
十、Kotlin 泛型
1、泛型类
2、泛型函数
十一、Kotlin 对象表达式和对象声明 object
1、对象表达式
2、对象声明(获取单例)
十二、kotlin 委托 by
1、委托类
2、属性委托
3、懒加载 by lazy{ }
4、可观察属性 Observable
5、map/mutableMap
十三、kotlin 回调 block:
1、block: () -> T block: (T) -> T block: T.() -> T
2、Block语法格式
十四、Kotlin中let、run、with、apply及also的差别
1、let public inline fun T.let(block: (T) -> R): R ,>
2、run “this”作为上下文对象,且它的调用方式与let一致。
3、with public inline fun with(receiver: T, block: T.() -> R): R ,>
4、apply public inline fun T.apply(block: T.() -> Unit): T
5、also public inline fun T.also(block: (T) -> Unit): T
一、Kotlin 基础语法
1、方法函数
/**
* fun : 方法关键字
* sum :方法名
* a:Int : 传参及参数类型
* b:Int : 传参及参数类型
* :Int : 返回值
*/
fun sum(a:Int,b:Int) :Int{
return a+b
}
2、常量 val 和变量 var
3、${} 字符串模板
示例
object Main {
private val name :String = "小明"
@JvmStatic
fun main(args: Array<String>) {
val newStr = "这是小明"
val newStr2 = "这是${name}"
println(newStr)
print(newStr2)
}
}
打印日志如下:
4、null 处理 !!. 不能为空 ?.为空不处理 ?:为空处理成
示例
object Main {
@JvmStatic
fun main(args: Array<String>) {
val age1: String? = null// ? 表示可为空
val age2: String? = "23"
// !!. 不能为空
val age3 = age1!!.toInt()//做了非空判断,传入null则抛出null异常
// ?.为空不处理
val age4 = age1?.toInt() // 相当于 age1?.toInt()== null -> age4 = age1?.toInt() -> val age4 : String? = null
// ?:为空处理成
val age5 = age1?.toInt() ?: "666" // 相当 age1?.toInt()== null -> if(null ==age1?.toInt() ) return "666"
}
}
5、is 类型转换 相当于 instanceof
示例
object Main {
@JvmStatic
fun main(args: Array<String>) {
changeType("100")
changeType(99)
}
private fun changeType(obj: Any) {
if (obj is String) {
println("${obj}长度是${obj.length}")
} else {
println("非 String 类型")
}
}
}
日志如下:
6、Any 相当于 Java的 Object
二、Kotlin 基本数据类型
1、基本数据类型(6种,比java少了boolean/char)
对比Java
Java基本类型共有八种,基本类型可以分为三类,字符类型char,布尔类型boolean以及数值类型byte、short、int、long、float、double。数值类型又可以分为整数类型byte、short、int、long和浮点数类型float、double
2、数据类型转换
3、字符 `c`
示例
4、数组arrayOf、arrayListOf
示例
object Main {
@JvmStatic
fun main(args: Array<String>) {
val array = arrayOf(1, 2, 3)
val arrayList = arrayListOf<Person>()
arrayList.add(Person("xiaoming"))
arrayList.add(Person("xiaohong"))
for (a in array) {
println(a.toString())
}
for (p: Person in arrayList) {
println(p.name)
}
}
class Person(val name: String) {
}
}
日志如下:
5、字符串换行 """xxx"""
以为kotlin不支持加号字符串换行
示例
object Main {
@JvmStatic
fun main(args: Array<String>) {
val str = "hello " +
"world " +
"!"
println(str)
}
}
日志如下:
所以需要"""xxx"""来实现字符串换行显示
示例: trimIndent() 去除前置空格
object Main {
@JvmStatic
fun main(args: Array<String>) {
val str = """
Hellow
World
!
""".trimIndent()
println(str)
}
}
日志如下:
三、Kotlin 条件控制
1、if语句
kotlin没有Java的三元运算
示例
object Main {
@JvmStatic
fun main(args: Array<String>) {
val a: Int = 100
val b: Int = 99
val max: Int = if (a > b) a else b
val max2: Int = if (a < b) {
println("a<b")//先执行
a
} else {
println("a>b")//先执行
b
}
println(max)//后执行
println(max2)//后执行
}
}
日志如下
2、判断是否在区间内 in
示例
object Main {
@JvmStatic
fun main(args: Array<String>) {
val a = 2
val b = 10
if (a in 5..10) {
println("a 在 5到10 区间")
} else {
println("a 不在 5到10 区间")
}
if (b in 5..10) {
println("b 在 5到10 区间")
} else {
println("b 不在 5到10 区间")
}
}
}
日志如下
3、When 表达式(相当于 Java switch语句)
示例
object Main {
@JvmStatic
fun main(args: Array<String>) {
val a = 3
when(a){
1 -> print("1")
2 -> print("2")
else -> print("0")
}
}
}
如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:
示例
object Main {
@JvmStatic
fun main(args: Array<String>) {
val a = 3
when(a){
1,2,3,4 -> print("1")
5 -> print("2")
else -> print("0")
}
}
}
四、Kotlin 循环控制
1、for循环 ,withIndex() indices
示例
object Main {
@JvmStatic
fun main(args: Array<String>) {
val arrayList = arrayListOf<String>("1", "2", "3")
for (a in arrayList) {
println(a)
}
//withIndex() 带位置标示
for ((index, value) in arrayList.withIndex()) {
println("index $index value is $value")
}
//indices
for (index in arrayList.indices) {
println("index $index value is ${arrayList[index]}")
}
}
}
日志如下
五、Kotlin 类和对象
1、类
2、属性
3、自定义set/get方法 field 表示当前属性
有时候会对后台返回的对象属性做键值转换,例如 性别sex返回 1:男 2:女,希望getSex时直接返回其对应值
示例
object Main {
@JvmStatic
fun main(args: Array<String>) {
val person = Person()
person.name = "xiao ming"
person.age = 18
person.sex = "1"
println(person.name)
println(person.age)
println(person.sex)
}
}
class Person() {
//field 表示当前属性
var name: String? = null
get() = field?.toUpperCase()
var age: Int = 0
set(value) {
if (value >= 18) {
field = value - 10
} else {
field = value + 10
}
}
var sex: String? = null
get() = if ("1" == field) {
"男"
} else {
"女"
}
}
日志如下
4、 lateinit 延迟初始化关键字
非空属性必须在定义的时候初始化
object Main {
@JvmStatic
fun main(args: Array<String>) {
}
class Person(val name:String){
lateinit var bean:String //定义变量不初始化会报错时 则可以使用 lateinit 确定后面一定会初始化
}
}
5、抽象类
示例
object Main {
@JvmStatic
fun main(args: Array<String>) {
val animalA = Cat()
val animalB = Dog()
animalA.say()
animalB.say()
}
}
class Cat : Animal() {
override fun say() {
println("喵~")
}
}
class Dog : Animal() {
override fun say() {
println("汪~")
}
}
//抽象类
abstract class Animal() {
abstract fun say();//抽象方法
}
日志如下
6、嵌套类
示例
object Main {
@JvmStatic
fun main(args: Array<String>) {
val man = Person.Man()
man.name = "xiao ming"
println(man.name)
}
}
class Person(){
//嵌套类
class Man(){
var name: String? = null
}
}
日志如下
7、内部类 inner (可使用外部类属性和方法)
示例
object Main {
@JvmStatic
fun main(args: Array<String>) {
val outer = Outer()
val inner = outer.Inner()
val demo = inner.foo()
println(demo) // 1
val demo2 = inner.innerTest()
println("被修改的outer.v = ${outer.v}") // 内部类可以引用外部类的成员,例如:成员属性
}
}
class Outer {
private val bar: Int = 1
var v = "成员属性"
/**嵌套内部类**/
inner class Inner {
fun foo() = bar // 访问外部类成员
fun innerTest() {
val o = this@Outer //获取外部类的成员变量
println("内部类可以引用外部类的成员,例如:" + o.v)
o.v = "111"
println("内部类可以修改引用外部类的成员,例如:" + o.v)
}
}
}
日志如下
8、类的修饰符
六、Kotlin 继承
1、可以被继承的类都要用 open修饰符
示例
object Main {
@JvmStatic
fun main(args: Array<String>) {
val p = Person("xiao ming")
println(p.name)
println(p.str)
}
}
class Person(val name: String) : Base("child is Person, child name is $name") {
}
//open 关键词 允许当前类被继承
open class Base(val str: String) {
}
日志如下
2、构造函数
1)子类有构造函数,则要在子类构造初始化父类构造
示例
class Person(name: String, age: Int, sex: String) : Base(name) {
}
//open 关键词 允许当前类被继承
open class Base(name: String) {
}
2)子类无构造函数,则需要使用 constructor():super()
示例
class Person : Base {
constructor():super("person")
}
//open 关键词 允许当前类被继承
open class Base(val name: String) {
}
3、重写 override
1)在基类中,使用fun声明函数时,此函数默认为final修饰,不能被子类重写。如果允许子类重写该函数,那么就要手动添加 open 修饰它, 子类重写方法使用 override 关键词:
示例
object Main {
@JvmStatic
fun main(args: Array<String>) {
val p = Person()
p.say("xiao ming")
}
}
class Person : Base("Person") {
override fun say(name: String) {// 重写方法
println("person $name")
}
}
//open 关键词 允许当前类被继承/重写
open class Base(val name: String) {
open fun say(name: String) {// 允许重写方法
println("base name")
}
}
日志如下
2)super<A>.f() 如果有多个相同的方法(继承或者实现自其他类,如A、B类),则必须要重写该方法,使用super范型去选择性地调用父类的实现。
示例
object Main {
@JvmStatic
fun main(args: Array<String>) {
val c = C()
c.f()
}
}
open class A {
open fun f() { println("A") }
fun a() { println("a") }
}
interface B{
fun f(){ println("B")}
fun b(){ println("b") }
}
class C() :A(),B{
override fun f() {
super<A>.f()//调用 A.f()
super<B>.f()//调用 B.f()
}
}
日志如下
七、Kotlin 接口
1) 接口方法可以提前实现
2) 接口属性被继承时必须赋值
示例
object Main {
@JvmStatic
fun main(args: Array<String>) {
val p = Person()
println(p.name)
p.bar()
p.foo()
}
}
class Person : PersonInterface {
override var name: String = "xiao ming"
override fun bar() {
println("bar")
}
}
interface PersonInterface {
var name: String//接口属性,接口属性不赋值,继承接口必须赋值
fun bar()
fun foo() {
println("foo")//接口可以提前实现方法体
}
}
日志如下
八、Kotlin 扩展
Kotlin 可以对一个类的属性和方法进行扩展,且不需要继承或使用 Decorator 模式。
扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。
1、扩展函数
示例
object Main {
@JvmStatic
fun main(args: Array<String>) {
Person("xiao ming").say()//拓展函数调用
}
}
//拓展Person类,提供 say()函数
fun Person.say() {
println("我是拓展函数:$name")
}
class Person(val name: String) {}
日志如下
2、扩展一个空对象
在扩展函数内, 可以通过 this 来判断接收者是否为 NULL,这样,即使接收者为 NULL,也可以调用扩展函数。例如:
object Main {
@JvmStatic
fun main(args: Array<String>) {
val t :Any? = null
println(t.toString())
}
}
fun Any?.toString(): String {
if (null == this) {
return "null"
}
// 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
return toString()
}
日志如下
3、扩展函数是静态解析的(注意)
object Main {
@JvmStatic
fun main(args: Array<String>) {
printFoo(D())
}
}
open class C
class D : C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
println(c.foo())//拓展函数是静态,所以不管传值什么,都会调用 fun C.foo() = "c"
}
日志如下:
若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数。
object Main {
@JvmStatic
fun main(args: Array<String>) {
D().foo()
}
}
class D{
fun foo(){ println("成员函数") }
}
fun D.foo(){ println("拓展函数") }
日志如下:
4、拓展属性
扩展属性允许定义在类或者kotlin文件中,不允许定义在函数中。初始化属性因为属性没有后端字段(backing field),所以不允许被初始化,只能由显式提供的 getter/setter 定义。
object Main {
@JvmStatic
fun main(args: Array<String>) {
val d = D()
d.name = "name"
println(d.name)
println(d.name2)
}
}
class D {
var name2: String? = null
}
//拓展属性
var D.name: String
get() {
return "xiao ming"
}
set(value) {
println("拓展属性")
this.name2 = "name2 :$value"
}
日志如下:
5、伴生对象 companion object 相当于Java 静态(修饰符单例)
object Main {
@JvmStatic
fun main(args: Array<String>) {
D.foo()//拓展函数
println(D.name)//拓展属性
//companion object 演示
D.A.age
D.A.say()
D.age//静态属性,可以直接调用
D.say()//静态方法,可以直接调用
}
}
class D {
object A {
val age = "10"
fun say() {
println("age:10")
}
}
companion object {
val age = "20"
fun say() {
println("age:20")
}
}
}
fun D.Companion.foo() {
println("伴生对象的扩展函数")
}
val D.Companion.name: String
get() = "伴生对象的扩展属性"
日志如下:
九、Kotlin 数据类与密封类
1、数据类 data
copy()函数使用示例(复制数据的同时,可以修改数据内容)
object Main {
@JvmStatic
fun main(args: Array<String>) {
val user1 = User("xiaoming", 18)
val user2 = user1.copy(age = 20)
println("user1: name=${user1.name},age=${user1.age}")
println("user2: name=${user2.name},age=${user2.age}")
}
}
data class User(val name: String, val age: Int)
日志如下:
十、Kotlin 泛型
泛型,即 "参数化类型",将类型参数化,可以用在类,接口,方法上。
与 Java 一样,Kotlin 也提供泛型,为类型安全提供保证,消除类型强转的烦恼。
1、泛型类
object Main {
@JvmStatic
fun main(args: Array<String>) {
val box01 = Box<String>("xiao ming")
val box02 = Box<Int>(20)
println(box01.value)
println(box02.value)
}
}
//泛型类
class Box<T>(t: T) {
val value = t
}
日志如下:
2、泛型函数
object Main {
@JvmStatic
fun main(args: Array<String>) {
doSomething(100)
doSomething("xiao ming")
doSomething(true)
doSomething(15.55)
}
//泛型函数
fun <T> doSomething(t: T) {
when (t) {
is Int -> println("泛型是Int型")
is String -> println("泛型是String型")
is Boolean -> println("泛型是Boolean型")
else -> println("未判断的类型")
}
}
}
日志如下:
十一、Kotlin 对象表达式和对象声明 object
1、对象表达式
object Main {
@JvmStatic
fun main(args: Array<String>) {
//object: 对象表达式
val listener = object : Listener{
override fun onClick() {
println("onClick")
}
}
listener.onClick()
}
}
interface Listener{
fun onClick()
}
日志如下:
2、对象声明(获取单例)
Kotlin 使用 object 关键字来声明一个对象。
Kotlin 中我们可以方便的通过对象声明来获得一个单例。
object Main {
@JvmStatic
fun main(args: Array<String>) {
val user01 = User
val user02 = User
user01.name = "xiao ming"// object对象是单例,所以 user02 值也会改变
println(user01.name)
println(user02.name)
}
}
object User{
var name = ""
var age = 20
}
日志如下:
十二、kotlin 委托 by
1、委托类
类委托的核心思想在于将一个类的具体实现委托给另一个类去完成。
意义在于让大部分的方法实现调用辅助对象中的方法,少部分的方法实现由自己来重写,甚至加入一些自己独有的方法。
object Main {
@JvmStatic
fun main(args: Array<String>) {
val a = Dog("wangwang")
BlackDog(a).print()
BlackDog(a).dogPrint()
}
}
interface Animal {
fun print()
}
class Dog(val name: String) : Animal {
override fun print() {
println("name:${name}")
}
}
//委托类 BlackDog 委托给 Animal
class BlackDog(a: Animal) : Animal by a {
//实现自己的方法
fun dogPrint(){
println("dogPrint")
}
}
日志如下
2、属性委托
委托属性的核心思想是将一个属性(字段)的具体实现委托给另一个类去完成。
import kotlin.reflect.KProperty
object Main {
@JvmStatic
fun main(args: Array<String>) {
val dog = Dog()
dog.name = "dog"
println(dog.name)
}
}
class Dog{
//委托属性
var name: String by Animal()
}
class Animal{
operator fun getValue(dog: Dog, property: KProperty<*>): String {
return "委托属性${property}为wangwang"
}
operator fun setValue(dog: Dog, property: KProperty<*>, s: String) {
println("property:${property}, value:${s}")
}
}
日志如下:
3、懒加载 by lazy{ }
by lazy
并不是连在一起的关键字,只有by
才是Kotlin
中的关键字,lazy
在这里只是一个高阶函数而已。
object Main {
@JvmStatic
fun main(args: Array<String>) {
println(lazyValue) // 第一次执行,执行两次输出表达式
println(lazyValue) // 第二次执行,只输出返回值
}
val lazyValue: String by lazy {
println("computed!") // 第一次调用输出,第二次调用不执行
"Hello"
}
}
日志如下:
4、可观察属性 Observable
observable 可以用于实现观察者模式。
Delegates.observable() 函数接受两个参数: 第一个是初始化值, 第二个是属性值变化事件的响应器(handler)。
在属性赋值后会执行事件的响应器(handler),它有三个参数:被赋值的属性、旧值和新值:
import kotlin.properties.Delegates
object Main {
@JvmStatic
fun main(args: Array<String>) {
val user = User()
user.name="第一次赋值"
user.name="第二次赋值"
}
}
class User{
var name:String by Delegates.observable("初始值"){
property, oldValue, newValue ->
println("旧值:$oldValue -> 新值:$newValue")
}
}
日志如下:
5、map/mutableMap
object Main {
@JvmStatic
fun main(args: Array<String>) {
val map = mapOf<String, String>("name" to "xiaoming", "name2" to "xiaoming2")
val map2 = mutableMapOf<String, String>("name3" to "xiaoming3", "name4" to "xiaoming4")
map2.put("name5", "xiaoming5")
println(map["name"])
println(map["name2"])
println(map2["name3"])
println(map2["name4"])
println(map2["name5"])
}
}
日志如下:
十三、kotlin 回调 block:
1、block: () -> T block: (T) -> T block: T.() -> T
object Main {
@JvmStatic
fun main(args: Array<String>) {
"==1==".method1 {
println("==1==")
}
"==2==".method2 {
println("==2==this:$it")
it
}
"==3==".method3 {
println("==3==this:$this")
this
}
}
/**
* 1. block: () -> T
* 01.传入的函数无参
* 02.该函数最后一行需要是调用者对象类型,而且无return
*/
fun <T> T.method1(block: () -> T): T {
return block()
}
/**
* 2. block: (T) -> T
* 01.传入的函数带有自身作为参数
* 02.该函数最后一行需要是调用者对象类型,而且无return
* 03.把调用者作为it,传入定义的lambda表达式函数域中
*/
fun <T> T.method2(block: (T) -> T): T {
return block(this)
}
/**
* 3. block: T.() -> T
* 01.传入的函数无参
* 02.该函数最后一行需要是调用者对象类型,而且无return
* 03.把调用者作为this,传入定义的lambda表达式函数域中
*/
fun <T> T.method3(block: T.() -> T): T {
println("=======3==this:$this")
return block()
}
}
日志如下:
2、Block语法格式
object Main {
@JvmStatic
fun main(args: Array<String>) {
test {
println("test:无返回值")
}
test2 {
"test2:这是返回值"//不需要写 return
}
test3 {
println("test3:${it}")
}
test4 {
println("test4:${it}")
"test4:这是返回值"//不需要写 return
}
test5 { x, y ->
x + y//不需要写 return
}
}
//1、无参 无返回值
fun test(block: () -> Unit) {
return block()
}
//2、无参 有返回值
fun test2(block: () -> String) {
println(block())
}
//3、有参 无返回值
fun test3(block: (s: String) -> Unit) {
block("这是入参")
}
//4、有参 有返回值
fun test4(block: (s: String) -> String) {
println(block("这是入参"))
}
//5、有多个参数,有返回值
fun test5(block: (x: Int, y: Int) -> Int) {
println(block(5, 10))
}
}
日志如下:
十四、Kotlin中let、run、with、apply及also的差别
作用域函数是Kotlin比较重要的一个特性,共分为以下5种:let、run、with、apply 以及 also。它们的唯一目的是在对象的上下文中执行代码块。当对一个对象调用这样的函数并提供一个 lambda 表达式时,它会形成一个临时作用域。在此作用域中,可以访问该对象而无需其名称。这些函数称为作用域函数。
1、let public inline fun <T, R> T.let(block: (T) -> R): R
1)作用域 let 有入参,有返回值it
object Main {
@JvmStatic
fun main(args: Array<String>) {
//let 作用于 ,直接用 it ,有返回值
val user = User().let {
it.name = "xiao ming"
it.age = 20
"这是返回值R"
}
val user2 = User().let {
it.name="zhang san"
}
println(user)
println(user2)
}
}
class User() {
var name: String? = null
var age: Int? = null
fun say() {
println("name :${name}, age :${age}")
}
}
日志如下:
2) 空安全检测 xxx?.let{ }
object Main {
val name: String? = null
@JvmStatic
fun main(args: Array<String>) {
val result = name?.let {
it.length
} ?: "name 空处理"
println(result)
}
}
日志如下:
2、run “this”作为上下文对象,且它的调用方式与let一致。
表达式同时包含对象初始化和返回值的计算时,run更适合
public inline fun <T, R> T.run(block: T.() -> R): R
public inline fun <R> run(block: () -> R): R
无返回值
object Main {
@JvmStatic
fun main(args: Array<String>) {
val user = User()
user.run{
this.name = "xiaoming"
this.age = 20
say()
}
println(user)
}
}
class User() {
var name: String? = null
var age: Int? = null
fun say() {
println("name :${name}, age :${age}")
}
}
日志如下:
3、with public inline fun <T, R> with(receiver: T, block: T.() -> R): R
with属于非扩展函数,直接输入一个对象receiver,当输入receiver后,便可以更改receiver的属性,同时,它也与run做着同样的事情。
with使用的是非null的对象,当函数块中不需要返回值时,可以使用with。
object Main {
@JvmStatic
fun main(args: Array<String>) {
val user = User()
with(user) {
this.name = "xiaoming"
this.age = 20
say()
}
println(user)
}
}
class User() {
var name: String? = null
var age: Int? = null
fun say() {
println("name :${name}, age :${age}")
}
}
日志如下:
4、apply public inline fun <T> T.apply(block: T.() -> Unit): T
apply函数主要用于初始化或更改对象,因为它用于在不使用对象的函数的情况下返回自身。
object Main {
@JvmStatic
fun main(args: Array<String>) {
val user = User()
user?.apply{
this.name = "xiaoming"
this.age = 20
}
println(user)
}
}
class User() {
var name: String? = null
var age: Int? = null
fun say() {
println("name :${name}, age :${age}")
}
}
日志如下:
5、also public inline fun <T> T.also(block: (T) -> Unit): T
also是 T 的扩展函数,返回值与apply一致,直接返回T。also函数的用法类似于let函数,将对象的上下文引用为“it”而不是“this”以及提供空安全检查方面。
object Main {
@JvmStatic
fun main(args: Array<String>) {
val user = User()
user?.also {
it.name = "xiaoming"
it.age = 20
}
println(user)
}
}
class User() {
var name: String? = null
var age: Int? = null
fun say() {
println("name :${name}, age :${age}")
}
}
日志如下: