【Kotlin】引入与基础语法

news2024/11/29 6:38:02

文章目录

    • Kotlin的特性
    • Kotlin优势
    • Kotlin的安卓项目
    • 变量
      • 变量保存了指向对象的引用
      • 优先使用val来避免副作用
    • 后端变量Backing Fields
    • 延迟初始化

android_kotlin

Kotlin的特性

  • 它更加易表现:这是它最重要的优点之一。你可以编写少得多的代码。
  • Kotlin是一种兼容Java的语言
  • Kotlin比Java更安全,能够静态检测常见的陷阱。如:引用空指针
  • Kotlin比Java更简洁,通过支持variable type inference,higher-order functions (closures),extension functions,mixins
    and first-class delegation等实现
  • Kotlin可与Java语言无缝通信。这意味着我们可以在Kotlin代码中使用任何已有的Java库;同样的Kotlin代码还可以为Java代码所用
  • Kotlin在代码中很少需要在代码中指定类型,因为编译器可以在绝大多数情况下推断出变量或是函数返回值的类型。这样就能获得两个好处:简洁与安全
  • Kotlin是一种静态类型的语言。这意味着,类型将在编译时解析且从不改变

Kotlin优势

  • 全面支持Lambda表达式
  • 数据类Data classes
  • 函数字面量和内联函数Function literals & inline functions
  • 函数扩展Extension functions
  • 空安全Null safety
  • 智能转换Smart casts
  • 字符串模板String templates
  • 主构造函数Primary constructors
  • 类委托Class delegation
  • 类型推判Type inference
  • 单例Singletons
  • 声明点变量Declaration-site variance
  • 区间表达式Range expressions

上面说简洁简洁,到底简洁在哪里?这里先用一个例子开始,在Java开发过程中经常会写一些Bean类:

public class Person {
    private int age;
    private String name;
    private float height;
    private float weight;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public float getHeight() {
        return height;
    }

    public void setHeight(float height) {
        this.height = height;
    }

    public float getWeight() {
        return weight;
    }

    public void setWeight(float weight) {
        this.weight = weight;
    }

    @Override
    public String toString() {
        ...
    }
}

使用Kotlin:

data class Person(
        var name: String,
        var age: Int,
        var height: Float,
        var weight: Float)

这个数据类,它会自动生成所有属性和它们的访问器,以及一些有用的方法,比如toString()方法。

Kotlin的安卓项目

我们看一下MainActivity的代码:

// 定义类,继承AppCompatActivity
class MainActivity : AppCompatActivity() {
    // 重写方法用overide,函数名用fun声明  参数是a: 类型的形式 ?是啥?它是指明该对象可能为null,
    // 如果有了?那在调用该方法的时候参数可以传递null进入,如果没有?传递null就会报错
    override fun onCreate(savedInstanceState: Bundle?) {
        // super 
        super.onCreate(savedInstanceState)
        // 调用方法
        setContentView(R.layout.activity_main)
    }
}

变量

变量可以很简单地定义成可变var(可读可写)和不可变val(只读)的变量。如果var代表了variable(变量),那么val可看成value(值)的缩写
也有人把val解释成variable+final,即通过val声明的变量具有Java中的final关键字的
效果(我们通过查看对val语法反编译后转化的java代码,从中可以很清楚的发现它是用final实现的),也就是引用不可变。
因此,val声明的变量是只读变量,它的引用不可更改,但并不代表其引用对象也不可变。事实上,我们依然可以修改引用对象的可变成员。

声明:

var age: Int = 18

val book = Book("Thinking in Java") // 用val声明的book对象的引用不可变
book.name = "Diving into Kotlin"

字面上可以写明具体的类型。这个不是必须的,但是一个通用的Kotlin实践是省略变量的类型我们可以让编译器自己去推断出具体的类型,
Kotlin拥有比Java更加强大的类型推导功能,这避免了静态类型语言在编码时需要书写大量类型的弊端:

var age = 18 // int
val name = "charon" // string
var height = 180.5f // flat
var weight = 70.5 // double

Kotlin中,一切都是对象。没有像Java中那样的原始基本类型。
当然,像IntegerFloat或者Boolean等类型仍然存在,但是它们全部都会作为对象存在的。基本类型的名字和它们工作方式都是与Java非常相似的,
但是有一些不同之处你可能需要考虑到:

  • 数字类型中不会自动转型。举个例子,你不能给Double变量分配一个Int。必须要做一个明确的类型转换,可以使用众多的函数之一:

    private var age = 18
    private var weight = age.toFloat()
    
  • 字符(Char)不能直接作为一个数字来处理。在需要时我们需要把他们转换为一个数字:

    val c: Char = 'c'
    val i: Int = c.toInt()
    
  • 位运算也有一点不同。

    // Java
    int bitwiseOr = FLAG1 | FLAG2;
    int bitwiseAnd = FLAG1 & FLAG2;
    
    // Kotlin
    val bitwiseOr = FLAG1 or FLAG2
    val bitwiseAnd = FLAG1 and FLAG2
    

变量保存了指向对象的引用

variable

当该对象被赋值给变量时,这个对象本身并不会被直接赋值给当前的变量。相反,该对象的引用会被赋值给该变量。
因为当前的变量存储的是对象的引用,因此它可以访问该对象。

如果你使用val来声明一个变量,那么该变量所存储的对象的引用将不可修改。然而如果你使用var声明了一个变量,你可以对该变量重新赋值。
例如,如果我们使用代码: x = 6,将x的值赋为6,此时会创建一个值为6的新Int对象,并且x会存放该对象的引用。下面新的引用会替代原有的引用值被存放在x中:

Image

注意: 在Java中,数字类型是原生类型,所以变量存储的是实际数值。但是在Kotlin中的数字也是对象,而变量仅仅存储该数字对象的引用,并非对象本身。

优先使用val来避免副作用

在很多Kotlin的学习资料中,都会传递一个原则:优先使用val来声明变量。这相当正确,但更好的理解可以是:尽可能采用val、不可变对象及纯函数来设计程序。
关于纯函数的概念,其实就是没有副作用的函数,具备引用透明性。

简单来说,副作用就是修改了某处的某些东西,比如说:

  • 修改了外部变量的值
  • IO操作,如写数据到磁盘
  • UI操作,如修改了一个按钮的可操作状态

后端变量Backing Fields

Kotlin会默认创建set get方法,我们也可以自定义get set方法:

kotlingettersetter是不允许调用本身的局部变量的,因为属性的调用也是对get的调用,因此会产生递归,造成内存溢出。

例如:

var count = 1
var size: Int = 2
    set(value) {
        Log.e("text", "count : ${count++}")
        size = if (value > 10) 15 else 0
    }

这个例子中就会内存溢出

kotlin为此提供了一种我们要说的后端变量,也就是field。编译器会检查函数体,如果使用到了它,就会生成一个后端变量,否则就不会生成。
我们在使用的时候,用field代替属性本身进行操作。按照惯例set参数的名称是value,但是如果你喜欢你可以选择一个不同的名称。
setter通过field标识更新变量属性值。field指的是属性的支持字段,你可以将其视为对属性的底层值的引用。在getter和setter中使用field代替属性名称

class A {
    var count = 1
    var size: Int = 2
        set(value) {
            field = if (value > 10) 15 else 0
        }
        get() {
            return if (field == 15) 1 else 0
        }
}

如果我们不手动写getter和setter方法,编译器会在编译代码时添加以下代码段:

var myProperty: String
    get() = field
    set(value) {
        field = value
    }

这意味着无论何时当你使用点操作符来获取或设置属性值时,实际上你总是调用了属性的getter或是setter。那么,为什么编译器要这么做呢?
为属性添加getter和setter意味着有访问该属性的标准方法。getter处理获取值的所有请求,而setter处理所有属性值设置的请求。
因此,如果你想要改变处理这些请求的方式,你可以在不破坏任何人代码的前提下进行。通过将其包装在getter和setter中来输出对属性的直接访问称为数据隐藏。

延迟初始化

在类内声明的属性必须初始化,如果设置非null的属性,应该将此属性在构造器内进行初始化。
假如想在类内声明一个null属性,在需要时再进行初始化(最典型的就是懒汉式单例模式),这就与Kotlin的规则是相背的,此时我们可以声明一个属性并
延迟其初始化,此属性用lateinit修饰符修饰。

class MainActivity : AppCompatActivity() {
    lateinit var name : String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        var test = MainActivity()
        // 要先调用方法让其初始化
        test.init()
        // 初始化之后才能进行调用test的属性
    }

    fun init() {
        name = "sth"
    }
}

需要注意的是,我们在使用的时候,一定要确保属性是被初始化过的,通常先调用初始化方法,否则会有异常。
如果只是用lateinit声明了,但是还没有调用初始化方法就使用,哪怕你判断了该变量是否为null也是会crash的:

We’ve added a new reflection API allowing you to check whether a lateinit variable has been initialized:
这里想要判断是否初始化了,需要用isInitialized来判断:

class MyService{
    fun performAction(): String = "foo"
}

class Test{
    private lateinit var myService: MyService

    fun main(){
        // 如果 myService 对象还未初始化,则进行初始化
        if(!::myService.isInitialized){
            println("hha")
            myService = MyService()
        }
    }
}

注意: ::myService.isInitialized可用于判断adapter变量是否已经初始化。虽然语法看上去有点奇怪,但这是固定的写法。::前缀不能省。::是一个引用运算符,一般用于反射相关的操作中,可以引用属性或者函数。
这里可以写成::myService.isInitializedthis::myService.isInitialized
如果在listener或者内部类中,可以这样写this@OuterClassName::myService.isInitialized

那lateinit有什么用呢? 每次使用还要判断isInitialized。

lateinit的主要用例是当您不能初始化构造函数中的属性,但可以保证它在某种意义上“足够早”初始化时,大多数使用不需要isInitialized检查。例如,因为某个框架调用了一个方法,在构造后立即对其进行初始化。

除了使用lateinit外还可以使用by lazy {}效果是一样的:

private val test by lazy { "test" }

private fun switchFragment(position: Int) {
    if (test == null) {
        Log.e("xoliu", "test is null")
    } else {
        Log.e("xoliu", "test is not null ${test}")
        check(test)
    }
}    

执行结果:

test is not null test

lateinitby lazy有什么区别呢?

  • by lazy{}只能用在val类型而lateinit只能用在var类型
  • lateinit不能用在可空的属性上和java的基本类型上(有默认值),否则会报lateinit错误
  • lateinit在分配之前不会初始化变量,而by lazy在第一次访问时初始化它。
  • 如果在初始化之前访问,lateinit会抛出异常,而lazy则可以确保已初始化。
    lazy的背后是接收一个lambda并返回一个Lazy实例的函数,第一次访问该属性时,会执行lazy对应的Lambda表达式并记录结果,后续访问该属性时只是返回记录的结果。

另外系统会给lazy属性默认加上同步锁,也就是LazyThreadSafetyMode.SYNCHRONIZED,它在同一时刻只允许一个线程对lazy属性进行初始化,所以它是线程安全的。
但若你能确认该属性可以并行执行,没有线程安全问题,那么可以给lazy传递LazyThreadSafetyMode.PUBLICATION参数。
你还可以给lazy传递LazyThreadSafetyMode.NONE参数,这将不会有任何线程方面的开销,当然也不会有任何线程安全的保证。例如:

val sex: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
    // 可以并行执行
    if (color == "yellow") "male" else "female"
}

val sex: String by lazy(LazyThreadSafetyMode.NONE) {
    // 不做任何线程保证也不会有任何线程开销
    if (color == "yellow") "male" else "female"
}
  • 尽量不要使用lateinit来定义不可空类型的变量,可能会在使用时出现null的情况
  • 只读变量(val修饰)可以使用by lazy { }实现懒加载,可变变量(var修饰)使用改写get方法的形式实现懒加载
// 只读变量
private val lazyImmutableValue: String by lazy {
    "Hello"
}

// 可变变量
private var lazyValue: Fragment? = null
    get() {
        if (field == null) {
            field = Fragment()
        }
        return field
    }

当您稍后需要在代码中初始化var时,请选择lateinit,它将被重新分配。当您想要初始化一个val值一次时,特别是当初始化的计算量很大时,请选择by lazy。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1253997.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

企业计算机服务器中了mkp勒索病毒怎么办?Mkp勒索病毒解密数据恢复

网络技术的不断发展,为企业的生产运营提供了坚实的基础,但随之而来的网络安全威胁也不断增加,影响了企业的正常生产生活。近期,云天数据恢复中心陆续接到很多企业的求助,企业计算机服务器遭到了mkp勒索病毒攻击&#x…

不同品牌的手机可以则哪一个你投屏到电视?

如果你使用AirDroid Cast的TV版,苹果手机可以通过airPlay或无线投屏方式,将屏幕同步到电视屏幕;多个品牌的安卓手机可以通过无线投屏投射到电视。而且无线投屏不限制距离,即使是远程投屏也可以实现。 打开AirDroid Cast的TV版&…

External model DLL ”ADC083XDLL“ not found_proteus仿真报错解决方法

仿真运行报错 External model DLL ”ADC083XDLL“ not found 原因 是proteus仿真软件缺少ADC083X.DLL文件或者ADC083X.DLL文件损坏。 解决方法 1.下载没问题的ADC083x.DLL ADC083X.DLL下载链接: 2.找到库文件夹,替换库文件ADC083X.DLL 库文件夹位置…

快速、精确仿真高频电磁场的工具CST Studio Suite 2024版本下载与安装配置

目录 前言一、CST 2024 安装二、使用配置总结 前言 CST Studio Suite是一个集成的仿真工具套件,用于模拟和优化电子系统的性能。它包括多个工具和模块,如电磁仿真、结构仿真、热仿真、电路分析等,以支持从概念设计到生产部署的整个开发周期。…

《微信小程序开发从入门到实战》学习三十一

3.4 开发参与投票页面 3.4.9 显示投票结果 在实际使用中,一个用户不能对同一个投票进行重复提交,因此需要向服务器端提交投票结果和提交用户ID。另外页面,需要完善。用户提交完投票后 ,还需要显示投票目前的结果,提交…

3-全功能pytest测试框架-allure2

3-全功能pytest测试框架-allure2 一:Allure介绍1> Allure 优势2> Allure 安装二:allure demo1> 脚本demo2> 查看result三:allure装饰器1> 装饰器一览表2> 装饰器概述1. epic、feature、story3> @allure.title()4> @allure.testcase()5> @allure.…

【JMeter】不同场景下的接口请求

场景1: 上传文件接口即Content-Type=multipart/form-data 步骤: 1. 接口url,method以及path正常填写 2.文件上传content-type是multipart/form-data,所以可以勾选【use multipart/form-data】,如果还有其他请求头信息可以添加一个请求头元件 3.请求参…

来自Microsoft Teams的摄像头背景图片

原文件在👆,下面是预览图 如果你安装了Microsoft Teams也可以搜索MSTeams,就在MSTeams/Backgrounds

处理分类问题的不平衡数据的 5 种技术

一、介绍 分类问题在机器学习领域很常见。正如我们所知,在分类问题中,我们试图通过研究输入数据或预测变量来预测类标签,其中目标或输出变量本质上是分类变量。 如果您已经处理过分类问题,那么您一定遇到过以下情况:其…

贝叶斯个性化排序损失函数

贝叶斯个性化排名(Bayesian Personalized Ranking, BPR)是一种用于推荐系统的机器学习方法,旨在为用户提供个性化的排名列表。BPR的核心思想是通过对用户历史行为数据的分析,对用户可能喜欢和不喜欢的物品对(item pair…

【matlab程序】matlab画台风符号和实例应用

【matlab程序】matlab画台风符号和实例应用 没有看文献,不知道文献中的符号什么样子,据我理解为这样子的: 因此,按照自己的理解做了这期。 结果浏览: 台风符号一切可改,可细细改。可是我不发论文&#xf…

3、MSF使用

文章目录 一、利用ms17-010漏洞对靶机执行溢出攻击二、后渗透模块meterpreter的使用 一、利用ms17-010漏洞对靶机执行溢出攻击 分别输入以下命令,使用ms17_010_eternalblue模块对目标机的ms17-010漏洞进行利用: use exploit/windows/smb/ms17_010_eter…

Percepio Tracealyzer 4.8.1 视觉跟踪诊断解决方案

Percepio Tracealyzer 4.8.1 视觉跟踪诊断解决方案, 是使嵌入式软件开发人员能够深入了解其运行时系统。这样可以更轻松地调试系统级问题、查找软件设计缺陷以及测量软件时序和资源使用情况。确保您的代码可靠、高效且响应迅速。 视觉运行时洞察 在运行时将 X 射线视…

Git 仓库越来越大?不要慌!试试这个简单方法,轻松秒瘦身

开局两张图 瘦身前瘦身后 目录 开局两张图前言下载 BFG克隆代码Git 仓库瘦身清理存储库储存库 GC推送仓库 Git 瘦身验证结语开源项目 前言 在进行项目开发的过程中,代码仓库的体积可能会逐渐增大,特别是在版本控制系统中保留了大量的历史提交记录和不必…

单片机学习1——点亮一个LED灯

Keil软件编写程序&#xff1a; 特殊功能寄存器声明&#xff1a; #include<reg52.h>sbit LED P1^0;void main() {LED 0;while(1); } 代码说明&#xff1a; sbit 语句是特殊功能位声明。 生成HEX文件&#xff0c;这个文件是下载到单片机里的文件。Options for Target…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《基于多尺度分量特征学习的用户级超短期负荷预测》

这篇文章的标题表明研究的主题是用户级超短期负荷预测&#xff0c;并且该预测方法基于多尺度分量特征学习。让我们逐步解读这个标题&#xff1a; 用户级&#xff1a; 这表示研究的焦点是在个体用户层面上进行的。负荷预测可能是指电力系统中的负荷&#xff0c;即电力需求。用户…

影响语音芯片识别率的因素概述

语音芯片识别率是指芯片对人类语音信号的识别能力。在实际应用中&#xff0c;语音芯片识别率的高低直接影响了用户对芯片的体验和满意度。因此&#xff0c;提高语音芯片识别率是当前语音技术领域的重要任务之一。 1.、语音芯片的硬件设计&#xff1a;设计良好的芯片可以更好地…

浅析智能电能表远程费控的推广及应用

安科瑞 华楠 摘 要: 电力资源是我国社会发展中一种必不可少的资源,随着我国经济的不断发展和人们生活水平的不断提升,对电力行业的要求也不断提升。因此,电力企业应该不断提升自身的服务水平和服务质量,强智能电能表远程费控的推广与应用,提升电力计量和收费工作的效率,提高电…

五种多目标优化算法(MSSA、MOAHA、MOPSO、NSGA3、NSGA2)求解微电网多目标优化调度(MATLAB)

一、多目标优化算法简介 &#xff08;1&#xff09;多目标鳟海鞘算法MSSA 多目标优化算法&#xff1a;多目标鳟海鞘算法&#xff08;Multi-objective Salp Swarm Algorithm &#xff0c;MSSA&#xff09;-CSDN博客 参考文献&#xff1a; S. Mirjalili, A.H. Gandomi, S.Z. M…

多线程04 线程安全问题以及一些简单的解决策略

前言 首先我们引入多线程是为了解决多次创建进程和销毁进程带来的巨大开销,线程可以共享内存和硬盘资源等等,这里我们就会想,他们共享这些东西会不会涉及到一些安全问题呢?他们没有独立分配自己的资源是一定会有安全问题的,但是就目前在这个快节奏的社会来说,效率的提升是必然…