Kotlin
程序员文章站
2022-04-26 15:43:25
...
Kotlin
12018.11.07 16:40:09字数 1,032阅读 11,563
- Kotlin为什么可以编写Android程序?
因为使用Java语言编写的代码最终需要编译成.class文件才能执行,而使用Kotlin编写的代码最终也会编译成.class文件。对于Android操作系统而言,它无须关心也无法关心程序是用什么语言开发的,只要最终执行的是一些合法的.class文件即可。 - 我们为什么要使用Kotlin开发Android程序?
1、每行代码行尾毫无意义的分号。
2、switch语句只支持int条件(java1.8版本后也支持String了),且case最后要加break。
3、不是全面向对象语言,而是半面向对象语言。
4、不支持字符串内嵌表达式,拼接字符串繁杂。 - Kotlin学习资料
Kotlin-in-chinese
Kotlin极简教程
Kotlin系列之let、with、run、apply、also函数的使用
kotlin学习笔记:object关键字介绍与java中的静态变量与静态方法的实现以及@JvmField和@JvmStatic的使用
Kotlin学习系列之:可变参数
AndroidStudio配置Gradle
- 父 build.gradle :
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
//创建变量存储当前Kotlin版本,方便几个不同地方用到该版本号
ext.kotlin_version = '1.2.50'
//创建变量存储Anko库版本号,Anko是一个用来简化一些Android任务的很强大的Kotlin库
ext.anko_version='0.10.7'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.4'
//添加Kotlin构建插件
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
- 子build.gradle:
apply plugin: 'com.android.application'
//Kotlin插件
apply plugin: 'kotlin-android'
//Kotlin Android Extensions plugin 插件
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
defaultConfig {
applicationId "cn.lq.com.kotlinstudy"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
//添加Kotlin标准库依赖
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
//添加Anko库依赖,anko-common是我们需要Anko库的一小部分,以至于我们不会把没用到的部分加进来。
implementation"org.jetbrains.anko:anko-common:$anko_version"
}
-
快速配置方法,创建项目时直接勾选Include Kotlin support创建Kotlin项目
创建Kotlin项目.png
Kotlin基础知识
1. 方法和变量
方法
package com.lg.www.kotlinstudy
//Kotlin中极度弱化了静态的概念,尽量少用
// 定义静态方法的方式一,定义顶层方法(不属于任何一个类的方法)
fun staticMethod1(){
}
class KotlinTest {
//定义方法用fun,返回值通过“:”接后面,无返回值不需要写
/*四种限定符:
* public:默认限定符,可以省略不写
* private
* protected
* internal:同模块限定符;同一module可以调用被internal修饰的方法,跨module不行。
*/
private fun helloworld(arg: String): String {
println(arg)
return arg
}
fun helloworld(): String{
return "hellow word"
}
//只有一行代码的方法体可以进行简化,用等号连接返回值
fun helloworld() = "hellow word"
/*companion object:
我们需要一个类里面有一些静态的属性、常量或者函数,
从而方便我们不创建对象就可使用,我们可以使用 companion object。
这个对象被这个类的所有对象所共享,对象里的属性或者方法就像Java中的静态属性或者方法,
但是并不是静态属性或方法,其实只是创建Companion静态对象来调用里面的属性和方法。
*/
companion object {
//伴生方法,类似静态方法
fun companionMethod(){}
//定义静态方法的方式二,加静态注解
@JvmStatic
fun staticMethod2(){}
}
}
变量
package com.lg.www.kotlinstudy
class KotlinTest {
//Any类型 (与java中的 Object 类似)
fun test(arg: Any) {
/*数据类型:
在Kotlin中,一切都是对象。没有像Java中那样的原始基本类型。
这个是非常有帮助的,因为我们可以使用一致的方式来处理所有的
可用的类型。*/
val num: Int = 8 // 声明并立即赋值
/*变量:
变量可以很简单地声明成可变( var修饰 )和不可变( val修饰 )的变量。
可以简单的理解为var是可写的,在它生命周期中可以被多次赋值;
而val是只读的,仅能一次赋值,后面就不能被重新赋值。这个与Java中使
用的 final 很相似。但是不可变在Kotlin(和其它很多现代语言)中是一个很重要
的概念。
一个不可变对象意味着它在实例化之后就不能再去改变它的状态了。如果你需要一
个这个对象修改之后的版本,那就会再创建一个新的对象。这个让编程更加具有健
壮性和预估性。在Java中,大部分的对象是可变的,那就意味着任何可以访问它这
个对象的代码都可以去修改它,从而影响整个程序的其它地方。
不可变对象也可以说是线程安全的,因为它们无法去改变,也不需要去定义访问控
制,因为所有线程访问到的对象都是同一个。
所以在Kotlin中,只要可能,尽量在Kotlin中首选使用val不变值。因为事实上在程序
中大部分地方使用不可变的变量,可带来很多益处,如:可预测的行为和线程安全。
*/
// 变量如果没有初始值类型不能省略
var varTest: String
val valTest: Int
valTest = 10// 声明后赋值
//变量可以不需要声明类型,光标停留在变量上时按Ctrl+Q可以查看变量类型
var a = 1 // 自动推断出 `Int` 类型
a = 2
println(a)
val b = "b"
//b = "c" //编译器会报错: Val cannot be reassigned
println(b)
//低精度类型无法自动转换成高精度类型,如Int无法自动转换成Long
val x: Int = 10
//val y:Long = x // Type mismatch
val y: Long = x.toLong()//需要显式地调用对应的类型转换函数进行转换
//is 运算符检测一个表达式是否某类型的一个实例
if (arg is String) {
// `result` 在该条件分支内自动转换成 `String`
// 检测后的分支中可以直接当作该类型使用,无需显式转换
println(arg::class) //class kotlin.String
val length = arg.length
arg.length
println(length)
}
/*原始字符串(raw string):
由三重引号(""")分隔,原始字符串可以包含换行符和任何其他字符,
都会原样输出,不会进行转义*/
val rawString = """\a/b\\c//d"""
println(rawString)// \a/b\\c//d
//字符串模板表达式:以美元符号($)连接变量,方便字符串拼接
val s = "Hello"
val templateString = "$s has ${s.length} characters"
println(templateString) //Hello has 5 characters
}
}
2. 类和继承
创建类,kind栏选Class,可通过键盘上下键切换类型
AS中创建Kotlin类.png
主构造函数
//普通类
class Invoice {
}
//带主构造函数的类
class Person constructor(firstName: String) {
}
//如果主构造函数没有注解或可见性说明,则 constructor 关键字是可以省略的
class Person(firstName: String) {
}
//如果构造函数有注解或可见性声明,则 constructor 关键字是不可少的,并且可见性应该在前
class Customer public @inject constructor (name: String) {...}
//主构造函数不能包含任意代码。初始化代码可以放在以 init 做前缀的初始化块内
class Customer(name: String) {
init {
println("Customer initialized with value ${name}")
}
}
//主构造函数的参数可以用在初始化块内,也可以用在类的属性初始化声明处
class Customer(name: String) {
val customerName = name
}
//主构造函数中可以同时声明属性并进行初始化
//类如果没有类体可以省略大括号
class Customer(val customerName: String)
//在class前面加data修饰,Kotlin会给该类自动生成copy、toString、hashCode、equals四个方法
data class Customer(val customerName: String)
- Kotlin中被val修饰的属性会自动生成get方法,被var修饰的属性会自动生成get和set方法,如上面的customerName属性是存在自动生成的get属性的
class KtTest {
var name = ""
//替换自动生成的set和get方法
set(value) {
field = value.toLowerCase()
}
get() {
return field.toUpperCase()
}
}
二级构造函数
//类也可以有二级构造函数,需要加前缀 constructor
//如果类有主构造函数,每个二级构造函数都要直接或间接通过另一个二级构造函数代理主构造函数。在同一个类中代理另一个构造函数使用 this 关键字
class Person (firstName: String) {
constructor(firstName: String, secondName: String) : this(firstName){
...
}
}
/*在 JVM 虚拟机中,如果主构造函数的所有参数都有默认值,
编译器会生成一个附加的无参的构造函数,这个构造函数会直接使用默认值。
这使得 Kotlin 可以更简单的使用无参构造函数来创建类实例的库*/
class Customer(val customerName: String = "")
//创建实例, Kotlin 没有 new 关键字
val c = Customer()
val p = Person("小明")
继承
- Kotlin 中所有的类都有共同的父类 Any ,它是一个没有父类声明的类的默认父类:
class Example // 隐式继承于 Any
- Any 不是 java.lang.Object;事实上它除了 equals(),hashCode()以及toString()外没有任何成员了。
- 继承一个明确的父类,需要在类头后加冒号再加父类:
//open注解与java中的final相反:它允许别的类继承这个类。默认情形下,kotlin 中所有的类都是 final
//如果类有主构造函数,则父类必须在主构造函数中使用参数立即初始化。
open class Base(p: Int)
class Derived(p: Int) : Base(p)
/*如果类没有主构造函数,则必须在每一个构造函数中用 super 关键字初始化基类,
或者代理另一个构造函数做这件事。注意在这种情形中不同的二级构造函数
可以调用基类不同的构造方法:*/
class MyView : View {
constructor(ctx: Context) : super(ctx) {
}
constructor(ctx: Context, attrs: AttributeSet) : super(ctx,attrs) {
}
}
复写方法
//在 kotlin 中坚持做明确的事,不像 java ,kotlin 需要把可以复写的成员都明确注解出来,并且重写它们:
open class Base {
open fun v() {}
fun nv() {}
}
//对于有无参数构造方法的父类,继承时可以使用无参构造方法立即初始化
class Derived() : Base() {
override fun v() {}
}
对于 Derived.v() 来说override注解是必须的。如果没有加的话,编译器会提示。如果没有open注解,像 Base.nv() ,在子类中声明一个同样的函数是不合法的,要么加override要么不要复写。在 final 类(没加open注解的类)中,open 类型的成员是不允许的。
- 标记为override的成员是open的,它可以在子类中被复写。如果你不想被重写就要加 final:
open class AnotherDerived() : Base() {
final override fun v() {}
}
复写属性
open class Foo {
open val x: Int = 3
}
class Bar1 : Foo() {
override val x: Int = 9
}
//主构造函数中复写属性,使用override关键字作为属性声明的一部分
class Bar2(override val x: Int) : Foo() {
}
复写时重名方法规则
open class A {
open fun f () { print("A") }
fun a() { print("a") }
}
interface B {
fun f() { print("B") } // 接口的成员变量默认是 open 的
fun b() { print("b") }
}
class C() : A() , B {
// 编译器不知道应该选哪个f方法,会要求复写f()
override fun f() {
super<A>.f() // 调用 A.f()
super<B>.f() // 调用 B.f()
}
}
抽象类
- 一个类或一些成员可能被声明成 abstract 。一个抽象方法在它的类中没有实现方法。记住我们不用给一个抽象类或函数添加 open 注解,它默认是带着的。
- 我们可以用一个抽象成员去复写一个带 open 注解的非抽象方法,如下所示。
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
3. 接口
- Kotlin接口中的方法是可以有方法体默认实现的
interface MyInterface {
fun bar()
fun foo() {
//函数体是可选的
println("foo")
}
}
//接口的实现
//一个类或对象可以实现一个或多个接口,用,隔开
class Child : MyInterface {
//没有默认实现的方法必须重写
override fun bar () {
//函数体
}
}
- 接口中的属性
//与Java中接口中属性必须为常量不一样的是Kotlin接口中的属性可以是抽象的
interface MyInterface {
//抽象的属性
val property: Int // abstract
//提供访问器的实现的属性,和Java中常量类似
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(property)
}
}
//实现接口
class Child : MyInterface {
override val property: Int = 29
}
4. 逻辑控制
4.1 条件判断
if...else...
fun test() {
val a = 100
val b = 87
//if else会将执行最后一行代码的结果作为返回值返回
val bigger = if (a > b) {
a
} else {
b
}
//只有一行代码方法体的括号可以去掉
val bigger1 = if (a>b) a else b
}
when(替换Java中switch,比之强大很多)
//when里面可以传任何对象
fun test(arg: Any) {
when (arg) {
0 -> print("OK")
in 1..8 -> print("1..8")
"OK", "Success" -> print("OK")
"Hello" -> {
println("Hello")
print("World!")
}
is Button -> print("is Button")
else -> {
print("End")
}
}
}
4.2 循环
for循环
//Kotlin中取消了Java中的for i循环
fun loopTest() {
//for in循环
for (i in 0..100) { //0<=i<=100
}
for (i in 0 until 100) { //0<=i<100
}
val list = listOf("a", "b", "c")
for (i in list) {
print(i)//a b c
}
val array = arrayListOf("1", "2", "3")
for (i in array.indices) {
println(array[i])
}
//区间写法
for (i in IntRange(1, array.size - 1)) {
println(array[i])
}
//it为集合对象
list.forEach {
print(it) //a b c
}
//index为集合下标,s为集合对象
list.forEachIndexed { index, s ->
}
}
while循环、do...while循环和Java中完全一样,这里就省略了
5. 空指针防护
- 为什么需要空指针防护
空指针异常时我们平时开发中排在第一的异常。如果一个对象可以是null,则我们需要明确地指定它,然后在使用它之前检查它是否是null。有了空指针防护后就可以节约很多调试空指针异常的时间,解决掉null引发的bug。
//main函数
fun main(args: Array<String>) {
//getContentLength(null) //报错:Null can not be a value of a non-null type String
getContentLength1(null)//不报错
}
fun getContentLength(content: String): Int {
return content.length
}
fun printUpperCase(content: String?) {
//?.表示如果不为空则执对象方法,下面是将字符串转换成大写,如果为空则不执行并返回null
println(content?.toUpperCase())
}
//参数后加?表示允许赋null
fun getContentLength1(content: String?): Int {
//return content.length //报错Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
//return content?.length//这里用?.是不行的,因为返回值是Int非空,当返回null时会类型不匹配
//解决办法1
return if (content == null) 0 else content.length
//解决办法2,?:表示优先前面表达式结果,当前面表达式结果为空是则取后面结果0
return content?.length ?: 0
//解决办法3,!!.强制去除非空检查,当你确信参数为非空时可以这样处理
return content!!.length
}
6. lambda表达式
7. 使用Kotlin开发Android程序
- activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:text="Button2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button" />
</android.support.constraint.ConstraintLayout>
- MainActivity.kt
package com.lg.www.kotlinstudy
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
class MainActivity : AppCompatActivity() {
var button: Button? = null
//lateinit延迟初始化关键字,一定要保证调用该对象方法之前该对象已经初始化完成不为空了
lateinit var button2: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button = findViewById(R.id.button)
//?.解决为null问题
button?.text = "abc"
button?.setOnClickListener {
}
//let方法,在let结构图/代码块里面可以执行和这个对象相关的所有操作语句
//比如下面结构体里面可以把所有和button相关的全部代码引用放里面执行,it就是button对象在结构体的引用
button?.let {
it.text = "def"
it.setOnClickListener {
}
}
button2.text = "123"
}
}
- 使用Kotlin Android Extensions库来优化绑定布局,谷歌推荐方式
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
//引入Activity对应的布局的xml
//此后,我们就可以在 setContentView 被调用后访问这些view,属性的名字就是XML中对应view的id
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.text = "abc"
button2.text = "123"
}
}
8. Java和Kotlin写法对比
名称 | Java | Kotlin |
---|---|---|
对象 | MainActivity.this | aaa@qq.com |
类 | MainActivity.class | MainActivity::class.java |
静态常量 | static final String text = ""; | const val text = ""(需要注意的是要把静态变量定义在类上方) |
比较类型 | if ("" instanceof String) {} | if ("" is String) {} |
比较字符串 | equals | ==(Kotlin 对字符串比较的写法进行优化了,其他类型对象对比还是要用 equals 方法) |