入坑 Kotlin_kotlin1.9.20-程序员宅基地

技术标签: kotlin  开发语言  

(后续更新见原文:https://blog.iyatt.com/?p=12060

1 前言

准备入 Android,抽空了解一下 Kotlin 语法。

2 环境

JDK 17.0.9
Kotlin 1.9.20

环境配置:https://blog.iyatt.com/?p=12040

3 编码规范

https://book.kotlincn.net/text/coding-conventions.html

这个编码规范可以作为参考,当然我个人而言还是喜欢沿用自己的方案。主要来自 C/C++ 的习惯风格,比如大括号独立成行,条件、循环必须加大括号(即使只有一个语句)。不过还是遇到一个特殊的,Kotlin 不建议使用下划线命名法,在 IDEA 中编辑的时候,只要用了就会出现波浪号。这个和目前的风格有点冲突还不可调和,我最开始学编程的时候是用驼峰法,后面感觉驼峰法太紧密了,不够清晰,之后就改用下划线法了,结果 Kotlin 不让用下划线法(看到波浪线太难受了,还是得妥协)。

4 探索

when 语句

Kotlin 中没有 switch,但是有 when,其作用类似

fun main()
{
    
    val value = 10

    when (value)
    {
    
        1 ->
        {
    
            println("结果为 1")
        }

        5 ->
        {
    
            println("结果为 5")
        }

        10 ->
        {
    
            println("结果为 10")
        }

        else ->
        {
    
            println("其它")
        }
    }
}

file

4.2 条件控制语句的返回值

Kotlin 中条件控制语句可以有返回值,这个在以前接触的语言里没见过。最终的返回值来自于匹配的代码块里最后一个语句,且接收返回值的时候,必须存在 else 以保证在没有匹配到的情况下有默认值。
Kotlin 没有类似 C/C++/Java(后有提到就简称 C 系) 的三元运算符,但是可以通过 if-else 做到三元运算符的效果,还可以提供更多 else if 条件的判断。Python 中的三元运算符语法上是用的 if-else,但是那是专用的,只是相当于把C系的问号冒号换成 if-else 而已,仅是三元运算符的符号,而不是改了 if 条件判断的用途。

if

fun main()
{
    
    val value = 10
    val ret = if (value == 1)
    {
    
        "匹配 1"
        true
    }
    else if (value == 2)
    {
    
        println("匹配 2")
        true
    }
    else
    {
    
        "其它"
        false
    }

    println(ret)
}

file

when

fun main()
{
    
    val value = 11

    val ret = when (value)
    {
    
        1 ->
        {
    
            "结果为 1"
        }

        5 ->
        {
    
            "结果为 5"
        }

        10 ->
        {
    
            "结果为 10"
        }

        else ->
        {
    
            "其它"
        }
    }

    println(ret)
}

file

4.3 循环控制

4.3.1 for

fun main()
{
    
    val items = listOf("小红", "小明", "小张")

    // for 遍历
    for (item in items)
    {
    
        print("$item,")
    }
    println()

    // for 遍历索引
    for (idx in items.indices)
    {
    
        print("$idx ${
      items[idx]},")
    }
    println()

    // forEach 遍历
    items.forEach {
     item ->
        print("$item,")
    }
    println()

    // forEach 遍历索引
    items.forEachIndexed {
     idx, item ->
        print("$idx $item,")
    }
    println()


    val map = mapOf("value1" to 1, "value2" to 2, "value3" to 3)

    for ((key, value) in map)
    {
    
        print("$key:$value, ")
    }
    println()

    map.forEach {
     (key, value) ->
        print("$key:$value, ")
    }
    println()
}

file

4.3.2 while

while 和 do-while 这一点和 C 系是一样的,前者是在每次循环开始前判断是否满足条件,后者是循环结束一次后判断一次条件

fun main()
{
    
    var x = 0
    var y = 10

    while (x > 0)
    {
    
        print(x--)
        print(" ")
    }
    println()

    do
    {
    
        print(y--)
        print(" ")
    }
    while (y > 10)
    println()
}

file

4.3.3 返回和跳转

这点和 C 系差不多,return 结束当前所在函数,break 终止所在的一层循环,continue 所在循环的当此循环结束,进入所在循环的下一轮次循环。
不过 Kotlin 中有了标签的概念,标签名可以自己设置

3.3.3.1 break 标签

break 只能跳出当前所在的一层循环,使用标签后可以跳到标签所在循环的外面,也就是可以一次性可以跳出指定层数的循环

fun main()
{
    
    for (x in 1..3)
    {
    
        myLabel@ for (y in 1..3)
        {
    
            for (z in 1..3)
            {
    
                if (z > 1)
                {
    
                    break@myLabel
                }
                println("$x $y $z")
            }
        }
    }
}

file

3.3.3.2 break 标签

continue 只能略过所在循环的当次循环,使用标签后,可以略过标签所在位置循环的当次循环,即可以略过指定层数的循环

fun main()
{
    
    myLabel@ for (x in 1..3)
    {
    
        for (y in 1..3)
        {
    
            for (z in 1..3)
            {
    
                if (z > 1)
                {
    
                    continue@myLabel
                }
                println("$x $y $z")
            }
        }
    }
}

file

3.3.3.3 return 标签

直接使用 return 会结束当前函数,在一些情况下只是需要局部返回,并不是要整个函数的返回。

fun foo()
{
    
    val items = listOf(10, 11, 12, 13)

    items.forEach {
     item ->
        if (item == 11)
        {
    
            return
        }
        println(item)
    }

    println("foo 结束")
}

fun main()
{
    
    foo()
}

file

使用标签限制后,这里只会在当前 Lambda 表达式返回

fun foo()
{
    
    val items = listOf(10, 11, 12, 13)

    items.forEach myLabel@{
     item ->
        if (item == 11)
        {
    
            return@myLabel
        }
        println(item)
    }

    println("foo 结束")
}

fun main()
{
    
    foo()
}

file

这里可以简化使用隐式标签,只要标签和接受这个 Lambda 表达式的函数同名就行

fun foo()
{
    
    val items = listOf(10, 11, 12, 13)

    items.forEach {
     item ->
        if (item == 11)
        {
    
            return@forEach
        }
        println(item)
    }

    println("foo 结束")
}

fun main()
{
    
    foo()
}

4.4 入口函数

Kotlin 比 Java 各方面都简洁很多,特别是入口函数。因为习惯了 C/C++,刚接触 Java 的时候太不理解把入口函数放在类中的行为,必须写个类来包起来就显得很复杂。我觉得还是像 C++ 形式的最好,支持函数式,同时支持面向对象,Kotlin 这点就类似。完全面向对象太怪异了,入口函数本身也没有作为类成员的意义,像是为了面向对象设计而强制这样搞一样,不是必要的,也可能是现阶段我没理解到它的精神吧。
另外一点 Java 要求入口函数必须有参数,即使不用也要写,这一点上 Kotlin 和 C/C++ 一样不用可以省略。
然而实际上这些工作只是 Kotlin 编译器帮我们做了

fun main()
{
    
    println("你好,世界!")
}

将编译后的字节码文件反编译为 Java,可以看到编译器给加上了类,而用 Kotlin 写的入口函数只是作为成员函数被 Java 的入口函数调用,这样 Kotlin 中的入口函数就可以没有参数。

import kotlin.Metadata;

@Metadata(
   mv = {
    1, 9, 0},
   k = 2,
   d1 = {
    "\u0000\b\n\u0000\n\u0002\u0010\u0002\n\u0000\u001a\u0006\u0010\u0000\u001a\u00020\u0001¨\u0006\u0002"},
   d2 = {
    "main", "", "untitled"}
)
public final class MainKt {
    
   public static final void main() {
    
      String var0 = "你好,世界!";
      System.out.println(var0);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
    
      main();
   }
}

但是如果给入口函数加上参数,那么此时 Kotlin 中入口函数和 Java 的入口函数就等同于一样了

fun main(args: Array<String>)
{
    
    println(args.size)
}
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {
    1, 9, 0},
   k = 2,
   d1 = {
    "\u0000\u0014\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0002\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005¨\u0006\u0006"},
   d2 = {
    "main", "", "args", "", "", "([Ljava/lang/String;)V", "untitled"}
)
public final class MainKt {
    
   public static final void main(@NotNull String[] args) {
    
      Intrinsics.checkNotNullParameter(args, "args");
      int var1 = args.length;
      System.out.println(var1);
   }
}

4.5 函數

一般函数定义格式

// 默认返回值类型为 Unit
fun foo1()
{
    

}

// 显式返回值类型
fun foo2(): Unit
{
    

}

// 参数格式
fun sum1(num1: Int, num2: Int): Int
{
    
    return num1 + num2
}

// 单行语句简化
fun sum2(num1: Int, num2: Int): Int = num1 + num2

// 参数默认值
fun sum3(num1: Int, num2: Int = 10) = num1 + num2

fun main()
{
    
    println(sum1(9, 1))
    println(sum2(11, 9))
    println(sum3(10))
    println(sum3(10, 20))
}

file

4.5.1 infix

infix fun Int.sum(num: Int) = this + num

infix fun Float.sum(num: Int) = this + num

fun main()
{
    
    println(4.sum(6))
    println(13 sum 7)
    println(6.7F sum 4)
}

file

4.5.2 重载

函数名相同,参数个数或类型不同

fun foo()
{
    
    println("空参数")
}

fun foo(str: String)
{
    
    println(str)
}

fun foo(num: Int)
{
    
    println(num)
}

fun main()
{
    
    foo()
    foo("你好,世界!")
    foo(10)
}

file

4.5.3 Lambda 表达式

fun main()
{
    
    // 无参数
    val printHello: () -> Unit = {
     println("你好,世界!") }
    printHello()

    // 带参数
    val showInfo: (String, Int) -> Unit = {
     name, age -> println("$name $age") }
    showInfo("小红", 20)

    // 带类型推断
    // 无显式声明参数类型,it 关键字代表参数
    val greet: (String) -> Unit = {
     println("你好,$it") }
    greet("小强")

    // 作为函数参数
    // 为了提高代码可读性,一般使用不同关键词区分用途
    // action:用于执行某种副作用操作或处理
    // operator:用于定义运算符重载或特定的逻辑
    // callback:作为回调函数的参数
    // predicate:作为谓词函数的参数
    // transform:作为转换函数的参数
    // comparator:作为比较器的参数
    // validator:作为验证函数的参数
    fun funGreet(name: String, action: (String) -> Unit)
    {
    
        action(name)
    }
    funGreet("小明", greet)

    fun calculate(x: Int, y: Int, operator: (Int, Int) -> Int): Int
    {
    
        return operator(x, y)
    }

    // 简化形式
    val sum = {
     x: Int, y: Int -> x + y }
    println(sum(4, 6))
    println(calculate(11, 9, sum))
}

file

4.5.4 可变参数

fun show(vararg items: String)
{
    
    items.forEach {
    
        print("$it ")
    }
    println("\n-------------------------")
}

fun show(s: String, vararg items: String)
{
    
    println(s)
    items.forEach {
    
        print("$it ")
    }
    println("\n-------------------------")
}

fun show(s1: String, s2: String = "AAA", vararg items: String)
{
    
    println(s1)
    println(s2)
    items.forEach {
    
        print("$it ")
    }
    println("\n-------------------------")
}

fun main()
{
    
    val items = arrayOf("苹果", "香蕉", "菠萝")

    show(*items)
    show(s = "水蜜桃", *items)
    show("芒果", "柿子", items = items) // 指定参数的时候,可以不加星号(展开数组,将每个元素作为一个独立参数传递),将整个数组作为一个参数传递
    show(s1 = "榴莲", items = items)
}

file

4.6 类

4.6.1 属性

class ClassA
{
    
    var num1 = 0

    // get 方法在取属性值时会调用
    // set 方法在给属性赋值时会调用
    // 下面的 get 和 set 是会默认添加的,可以省略
    // field 关键字代表变量自身
    var num2 = 0
        get() = field
        set(value)
        {
    
            field = value
        }

    // 可以根据需要修改取值赋值规则
    var num3 = 0
        get() = field + 2 // 取值结果加 2
        set(value)
        {
    
            field = value - 3 // 赋值结果减 3
        }
}

fun main()
{
    
    // 创建对象
    val ca = ClassA()

    ca.num1 = 10
    println(ca.num1)

    // num3 定义的取值赋值方法是默认会添加的
    // 所以最终效果和 num1 不重写取值赋值方法的一样
    ca.num2 = 10
    println(ca.num2)

    // 演示自定义的取值赋值方法
    println(ca.num3) // num3 初始值为 0,但是取值方法会 +2,所以会输出 2
    ca.num3 = 10 // 赋值 10,但是赋值方法会 -3,所以实际赋值是 7
    println(ca.num3) // num3 实际值是 7,但是取值方法会 +2,所以会输出 9
}

file

4.6.2 构造函数

class ClassA
{
    
    // 初始化代码段
    init
    {
    
        println("A 执行初始化")
    }
}

class ClassB constructor(name:String, age: Int) // 主构造函数
{
    
    init
    {
    
        print("B 执行初始化:")
        println("$name $age") // 主构造函数的参数可以在初始化代码段里使用
    }

    // 二级构造函数
    // 需要使用主构造函数的参数,可以在此基础上添加额外参数
    // 调用二级构造函数,首先会执行 init 代码块,再执行二级构造函数的代码块
    constructor(name: String, age: Int, height: Float) : this(name, age)
    {
    
        print("B 二级构造函数:")
        println("$name $age $height")
    }
}

// 主构造函数可以省略 constructor 关键字
// 如果需要使用无参构造,则主构造函数不写参数,再写一个无参的二级构造函数即可
//
// 我觉得这种方案更接近于 C++/Python/Java 的构造函数风格
// 直接不管主构造函数,统一使用二级构造函数,显得较为简洁
// 至于 init 的存在,可以方便统一初始化,如果有多个构造函数
// 且都需要执行同样初始化可以放在里面,没需要可以不写 init
class ClassC
{
    
    init
    {
    
        println("C 执行初始化")
    }

    // 用二级构造函数实现无参构造的效果
    constructor()
    {
    
        println("C 无参构造函数")
    }

    // 用二级构造函数实现有参构造函数
    constructor(name: String, age: Int)
    {
    
        print("C 有参构造函数:")
        println("$name $age")
    }
}

fun main()
{
    
    val ca = ClassA()
    val cb1 = ClassB("小明", 20) // 主构造函数
    val cb2 = ClassB("小张", 19, 1.77F) // 二级构造函数
    val cc1 = ClassC() // 无参构造函数
    val cc2 = ClassC("小强", 22)
}

file

4.6.3 修饰符

4.6.3.1 访问权限修饰符
  • public:默认的访问权限修饰符,如果没有显式指定访问权限修饰符,那么默认为public。public修饰的成员在任何地方都可见。
  • private:private修饰的成员只在定义它的类中可见,其他类无法访问。
  • protected:protected修饰的成员在定义它的类中和它的子类中可见,其他类无法访问。
  • internal:internal修饰的成员在同一个模块中可见。模块是指编译在一起的一组Kotlin文件。

C++ 中只有 public、private 和 protected,Java 中没有 internal,但是有一个 default,default 在同一个包里可见,也是不使用修饰符时默认的修饰符,而 Kotlin 默认是 public,只不过感觉 Java 的 default 和 Kotlin 的 internal 实际意义相近。

4.6.3.2 类属性修饰符
abstract    // 抽象类  
final       // 类不可继承,默认属性
enum        // 枚举类
open        // 类可继承
annotation  // 注解类
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_36349997/article/details/134608820

智能推荐

2021年最棒的10款Java框架,你喜欢哪个?_什么框架最好用-程序员宅基地

文章浏览阅读3.7k次,点赞3次,收藏7次。Java是一种强大的语言,当与框架相结合时,Java可以为您提供电子商务,银行,云计算,财务,大数据,股票市场,且更多的任何域的最佳解决方案。如果您刚刚从Java开始,请参阅Java Live Active使用此博客将通过您需要知道的所有重要概念来开始使用框架。什么是Java框架?作为模板或骨架的预先写代码的正文,其中一个开发人员可以根据需要填写其代码来使用和重用以创建应用程序,以便在他们打算引用其作为框架时使用的代码来创建应用程序。重用框架使开发人员能够在没有手动开销的情况下从头开始创建每.._什么框架最好用

安装oracle克隆数据库卡死,oracle数据库之克隆方法-程序员宅基地

文章浏览阅读729次。Oracle 数据库之克隆方法Oracle 8.1.7 for Linux系统,在安装上存在一切困难,尤其在Redhat7.2系统下安装的时候会出现很多意想不到的事情,譬如图形界面无法显示、xwin无法远程连接,在编译的过程中如果没有安装GCC,Glibc等等一些库文件,容易出现无法link的错误,而全部安装又会造成其他困难。因此在Oracle安装过程中总结出来了一些经验。介绍如下::Oracle..._fmw_home/oracle_common/bin/pasteconfig.sh 克隆 timeout

计算机考研408每日一题 day67_用足够容量的一维数组b对nxn阶-程序员宅基地

文章浏览阅读899次,点赞2次,收藏3次。将一个n×n的对称矩阵A的下三角部分按行存放在一个一维数组B中,A[0][0]存放在B[0]中,那么第i行的对角元素A[i][i]在B中的存放位置是___(中国科学院大学 2016)如果分时系统的时间片固定,那么___,则响应时间越长。(兰州大学 2005年)关于路由器说法正确的是___。(中国科学院大学 2015)通常所说的“溢出”,是指___(哈尔滨工程大学 2004年)_用足够容量的一维数组b对nxn阶

AgileEAS.NET SOA 平台5.1开发包介绍-程序员宅基地

文章浏览阅读101次。一、前言 AgileEAS.NET应用开发平台,简称EAS.NET,是基于敏捷并行开发思想和Microsoft .Net构件(组件)开发技术而构建的一个快速开发应用平台。用于帮助中小型软件企业建立一条适合市场快速变化的开发团队,以达到节省开发成本、缩短开发时间,快速适应市场变化的目的。 AgileEAS.NET应用开发平台包含基础类库、资源管理平台、运行容器、开发辅助工具等..._agile eas soa开发教程

友华光猫超级密码获取——PT622&PT632_pt622g超级密码-程序员宅基地

文章浏览阅读4.8w次,点赞5次,收藏14次。前言上上周家里宽带升级成了200M,原有的100M光猫不能用了,电信公司给换上了一个新的1000M光猫,同时也将网络连接方式给从原来的光猫桥接改成光猫路由了,这让我自己的小服务器顿时与外界失去联系了。于是想进入光猫管理界面把连接方式修改回来,哪曾想进去后根本没有发现修改页面,好吧,知道是电信公司搞得鬼,我需要扒掉电信公司给带的套,无套作业才爽嘛。_pt622g超级密码

解决idea 2020.2版本安装jclasslib无法使用_jclasslib-bytecode-viewer插件没用-程序员宅基地

文章浏览阅读2.1k次,点赞5次,收藏5次。使用jclasslib不光可以直观地查看某个类对应的字节码文件,还可以查看类的基本信息、常量池、接口、属性、函数等信息。使用idea自带插件下载的方式安装在这就不说啦……网络上一大堆啦……说说我遇到的问题:我的idea版本是2020.2的,使用插件市场下载jclasslib后,点击view只有show Bytecode,没有JClasslib我怀疑应该是我的idea版本和插件的版本不太兼容,或者是新版的插件有其他更改导致然后找到了插件官网https://plugins.je..._jclasslib-bytecode-viewer插件没用

随便推点

将oracle sql 创建的表注册为spatial,为Oraclespatial表添加记录和创建空间索引-程序员宅基地

文章浏览阅读92次。在手工录入Oraclespatial表时,需要将空间表和字段记录插入到记录表USER_SDO_GEOM_METADATA中,然后创建空间表的空间字段索引。-- 1. 删除索引(若全新创建可不执行):DROP INDEX 空间表索引名称;-- 2. 删除记录(若全新创建可不执行):DELETE FROM USER_SDO_GEOM_METADATA WHERE TABLE_NAME='空间表名';-..._什么意思select * from user_sdo_geom_metadata

RFC-程序员宅基地

文章浏览阅读94次。1、什么是RFCRFC(Request For Comment):记录协议标准化过程的文档。RFC不仅记录了协议规范内容,还包含了协议的实现和运用的相关信息以及实验方面的信息。编号越大内容越新。2、下载RFC可以从下面这个网址搜索RFC文档并下载:http://www.rfc-editor.org/search/rfc_search.php 3、常见协议的RF..._rfc 2228

图像显著性检测论文及代码汇总-程序员宅基地

文章浏览阅读1.4w次,点赞49次,收藏293次。近年来,图像显著性检测研究引起了大量的学者关注。为了方便学术研究,本博客汇总了近几年比较经典的图像显著性检测论文及代码,同时也收集了一些显著性检测相关的Toolbox、Benchmark和大牛的主页等,希望对研究显著性检测的同学和老师们有所帮助啦~本博客汇总代码有可能需要翻墙请自备梯子。特别声明: 本博客汇总的开源代码仅作学术研究之用,如需商用请联系论文作者。本博客不生产论文和代码,我们只是学术...

JSP SERVLET基础笔记_jsp+servlet笔记-程序员宅基地

文章浏览阅读606次。第一章 jsp开发基础[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MypxrDJs-1646450536079)(E:\QQ\错误代码.png)]什么是动态网页?​ 指的是能够通过不通的操作返回不同的功能及数据,具有交互功能常见的开发模式:​ B/S架构:浏览器和服务器​ C/S架构:客户端和服务器B/S架构的执行原理:​ 基于请求和响应的内容​ 客户端====》应用服务器====》数据库服务器​ 客户端《====应用服务器《==_jsp+servlet笔记

类模板的分离编译问题_seqlist后为什么加<datatype>-程序员宅基地

文章浏览阅读199次。类模板的分离编译类模板的分离编译,即模板类的定义和其模板成员函数(接口)的定义不在同一文件中。如模板类的定义在.h头文件中,同时在.h头文件中声明接口,而在模板类之外的.cpp文件中定义接口。此时在main函数中,如果只引用.h头文件,编译会报错。原因:大多数编译器,不支持类模板的分离编译!(1)实现分离编译其实也可以实现分离编译。解决办法:在main函数中引用定义接口的.cpp文件,这时..._seqlist后为什么加

利用JavaFx开发windows应用程序 —— 界面篇-程序员宅基地

文章浏览阅读3.1k次。为什么80%的码农都做不了架构师?>>> ..._javafx打印界面

推荐文章

热门文章

相关标签