Kotlin学习笔记
Kotlin的注释
Kotlin 的代码注释和Java一模一样
Kotlin的运行方式也是先kotlinc
生成字节码,再kotlin
字节码
如果一行里面只有一条语句,那么可以不写分号.但如果打算在同一行写多条语句,那么前面的语句就要加上分号.允许一条语句跨越多行.但是一个字符串或者一个变量民不可以.
Kotlin的变量
Kotlin声明变量
var|val 变量名 [:类型] [= 初始值]
使用var声明的变量是可变的.使用val声明的变量是不可变的(一旦被初始化就不能改变,但是要是没初始化,就可以进行第一次赋值).
类型与初始值两个必选一个.不能既没有类型有没有初始值.这样Kotlin推断不出来类型.
为什么Kotlin允许直接写变量或者函数:
由于Kotlin 程序编译的字节码必须遵守NM 规范,因此,如果直接在
Kotlin 程序中定义变量、函数, kotlinc 将会自动生成一个名为“文件名首字母大写+Kt "的类,
并将变量转换为该类的静态的ge忧er 、se忧er 方法(其中va l 声明的只有ge 忧er 方法) ,函数则转换为该类的静态方法。
Kotlin整型
Kotlin提供与Java一样的四种整型:
- Byte Byte 型整数在内存中通常占8 位,表数范围是- 128~ 127 0 兼容Java 的byte 和 Byte 类型。
- Short Short 型整数在内存中占16 位,表数范围是-32768(-215)~32767(215-1 ) 。兼容Java的short 和Short 类型。
- Int Int 型整数在内存中占32 位,表数范围是-2147483648(-2^31 )~2147483647(2^31-1 )。兼容Java 的int 和Integer 类型。
- Long Long 型整数在内存中占64 位,表数范围是-263~263-1 。兼容Java 的long 和Long类型。
建议能用Int 用 Int.
这四种整型都是引用类型,相当于Java的包装类.
程序可通过访问不同整数类型的MIN VALUE 和MAX VALUE 属性来获取对应类型的最大值和最小值.
Kotlin 是null 安全的语言,因此Byte 、Short、Int 、Long 型变量都不能接受null 值,如果要存储null 值,则应该使用Byte?、Short?、Int?、Long?类型。
// Int 型变量不支持null 值,所以下面代码是错误的
var notNull: Int = null
// Int ?相当于支持null 值的Int 型,所以下面代码是正确的
var nullable : Int? = null
由此可见, Kotlin 语言允许在己有数据类型后添加“?”,添加“?”后的数据类型相当于对原有类型进行了扩展,带“?”的数据类型可支持被赋予null 值。
普通类型的整型变量将会映射成Java 的基本类型;带“?”后缀的整型变量将会映射成基本类型的包装类。举例来说, Kotlin程序中Int 类型的变量将会映射成Java 的int 基本类型,但Int?类型的变量则会自动映射成Java的Integer 类型
比如:
var pml :Int = 200 ; // pml 的类型是Java 的int 类型
var pm2: Int = 200 ; // pm2 的类型是Java 的int 类型
println(pml == pm2); // 基本类型比较,输出true
var obl : Int? = 200 ; // obl 的类型是Java 的Integer 类型
var ob2 : Int? = 200 ; // ob2 的类型是Java 的Integer 类型
println(obl == ob2); // 引用类型比较,输出false
Kotlin 的整数有三种表示方式
十进制,二进制(0b),十六进制(0x)。不支持八进制。
Kotlin 允许为数值(包括浮点型)增加下画线作为分隔符,也可以在数值前添加额外的零。这些下画线和零并不会影响数值本身。例如如下代码:
val oneMillion = 1_000_000
val price = 234_234 234 // price 实际的值为234234234
val android = 1234_1234 // android 实际的值为12341234
Kotlin 的浮点型有两种。
Float
:表示32 位的浮点型,当精度要求不高时可以使用此种类型。
Double
:表示64 位的双精度浮点型,当程序要求存储很大或者高精度的浮点数时使用这种类型。
Kotlin 的浮点数有两种表示形式。
十进制数形式:这种形式就是简单的浮点数, 例如5 . 12 、512 . 0 、0 . 5 12 等。浮点数必须包含一个小数点,否则会被当成整数类型处理。
科学计数形式:例如5.12e2 (即5.12 x 10^2 )
、5.12E2 (也是5.12×10^2 )
等。
默认判定为Double
Kotlin 字符型
和Java一样.直接用单引号来表示.可以是普通字符,转义字符,或者是'\uxxxx'
Java里,字符可以赋值给整型.但Kotlin不可以. Kotlin 与Java 不同, Katlin 不支持取值范围小的数据类型隐式转换为取值范围大的类型。必须要使用下面方法显式转换:
Kotlin 为所有数值类型都提供了如下方法进行转换。
toByte ()
: 转换为Byte 类型。
toShort()
:转换为Short 类型。
tolnt()
: 转换为Int 类型。
tolong ()
:转换为Long 类型。
toFloat ()
: 转换为Float 类型。
toDouble ()
: 转换为Double 类型。
toChar ()
: 转换为Char 类型。
toFloat( )
: 转换为Float类型.
toDouble
: 转换为Double类型.
Kotlin 语言的各种数值型的表数范围由小到大的顺序为: Byte → Short→ Int
→ Long→ Float → Double 。
表达式类型的自动提升
当一个算术表达式中包含多个数值型的值时,整个算术表达式的数据类型将发生自动提升。Kotlin 定义了与Java 基本相似的自动提升规则。
〉所有的Byte 、Short 类型将被提升到Int 类型。
〉整个算术表达式的数据类型自动提升到与表达式中最高等级操作数同样的类型。操作数的等级排列如图2.4 所示,位于箭头右边的类型等级高于位于箭头左边的类型等级。
Boolean类型
可空类型的变量不允许直接调用方法或属性,但可以先判断该变量不为 null ,然后再调用该变量的方法或属性。
Kotlin的安全调用
user?.dog?.name//如果usr不为空的话获取dog,同时dog不为空的话获取name,也就是安全调用支持链式调用
kotlin的运算
Elvis是if else的简化写法,例如如下代码:
fun main(args: Array<String>) {
var b: String? = "gaolei"
var len1 = if (b != null) b.length else -1
println(len1)
b = null
var len2 = b?.length ?:-1 //Elvis表达式
//使用`?:`运算符,该运算符就是 lvis -一它的含义是,如果'?:'左边的表达式不为 null ,则返回左边表达式的值,否则返回`?:`右边表达式的值。
//`?:`其实就是if分支的简化写法
println(len2)
}
强制调用
就算是可空类型,如果不想进行非空判断的话,那么就是用强制调用!!.
例如:var b: String? = "gaolei" println(b!!.length)
字符串
Kotlin的String允许直接使用[]来获取某个位置上的字符,同时也可以直接使用for循环来便利,比Java方便了很多。
Kotlin使用“”“字符串”“”来表示原始字符串,原始字符串可以包含很多东西。
Kotlin 允许在字符串中嵌入变量或表达式,只要将变量或表达式放入${}中即可,这样Kotlin 将会把该变量或表达式的值嵌入该字符串中.
类型别名
Kotlin 提供了类似于 语言中的 typ edef 功能 可以为己有的类型指定另一个可读性更强的名字。 Kotlin 提供了 typealias 来定义类型别名。typealias 语句的语法格式为
typealias 类型别名=已有类型
//为 Set<Network Node >指定更短的别名: NodeSet
typealias NodeSet = Set<Network.Node>
//为 MutableMap<K MutableList<File>指定更短的别名:leTable<K>
typealias FileTable<K> = MutableMap<K, MutableList<File>>
运算符
Kotlin不支持Java的三目运算符。但是他有 ?:
这个精简if运算符。够用了。
Kotlin的运算符和C++的运算符有点像,就是不只是普通的数值类型可以使用.只要实现了特定方法的类都可以使用这些运算符。也就是运算符可以重载。
单目前缀运算符
有+
-
!
这三个。分别代表正负非。
自加和自减运算符
++
与--
和Java是一样的,这里不做赘述了。
双目运算符
除了+ - * / %
之外,还有..
,叫做区间运算符。
in
和!in
运算符
相对于Java新增的运算符.in
就是在 !in
就是不在.在数组和集合类以及字符串中有大量使用.
索引访问运算符
就是中括号[],不过Kotlin的用法比Java多多了.可以有多种使用方法.
比较运算符
kotlin中的==
和!=
更类似于Java中的equals,只是比较值,不是比较两个对象的引用地址是否相同.kotlin中的===
和!==
才是Java中的==
和!=
而>
<
>=
'<='都是由cpmpareTo()方法来实现的,所以也可以进行重载.
位运算符
Kotlin中的位运算符不如Java方便,不能直接使用~
,^
,|
,&
,>>
,'<<','>>>'来进行取否,亦或,或,与,右移,左移,无符号右移的位运算.而是要调用方法来进行.
分别对应如下方法:
and(bits): 按位与。当两位同时为1 时才返回l 。
or(bits): 按位或。只要有一位为l ,即可返回1 。
inv(bits):按位非。单目运算符,将操作数的每个位(包括符号位〉全部取反。
xor(bits): 按位异或。当两位相同时返回0 ,不同时返回1 。
shl(bits): 左移运算符。
shr(bits): 右移运算符。
ushr(bits): 无符号右移运算符。
Kotlin位运算符只对Int和Long起作用.
区间运算符
这Java没有的新东西,有两种,一种是闭区间运算符,另一种是半开区间运算符.
闭区间运算符
a..b
a必须要小于等于b,否则会报错.使用举例:
fun main(args: Array<String>) {
var range = 2 .. 6
for (num in range) {
println(num)
}
}
半开区间运算符
a until b
类似于a..b
但是包含a的边界,不包含b的边界,同样a要小于等于b
反向运算符
如果程序希望区间可以从大到小,则可使用downTo 运算符(其实是一个infix 函数〉,该
运算符同样构建一个闭区间。对于a downTo b 而言,此时要求b 不能大于a。
示例代码如下:
fun main(args: Array<String>) {
var range = 6 downTo 2
for (num in range) {
println(num)
}
}
区间步长
所有的区间默认步长都是1,当然可以使用step运算符显示指定区间步长.
//下面这段代码打印7 5 3 1
for (num in 7 downTo 1 step 2) {
println(num)
}
分支结构
情况较少时使用if,情况较多时使用when.
if表达式
if表达式可以直接作为一个结果给变量赋值.但是必须要有else.if表达式的分支依然可以是用花括号括起来的代码块,此时代码块的最后一个表达式的值将作为整个代码块的值
fun main(args: Array<String>) {
var age = 20
var str = if (age > 20) "成年人" else if (age < 20) "未成年人" else "人生的岔路口"
println(str)
}
fun main(args: Array<String>) {
var age = 20
age++;
val str = if (age > 20) {
println("你已经老了")
"成年人"
} else if (age < 20) {
println("你肯定盼望着长大")
"未成年人"
} else {
println("你在一生中的黄金时代")
"人生的岔路口"
}
println(str)
}
when分支就是switch分支的表达式,只不过精简了很多
省略了case,也不用写break;default改为了else,冒号改为了->,而且更加高级,不只是数字,字符,和字符串,任何类型都可以,它使用的是来进行判断的,只要返回true,程序就可以进入.
fun main(args: Array<String>) {
'A' -> println("优秀")//只有一句话省略了花括号,多句代码的话要加上花括号
'B' -> println("良好")
'C' -> println("中")
'D' -> println("及格")
'E','F' -> println("不及格")//多种情况的话用逗号分开就行了
}
when表达式
与if 分支相同, when 分支也可作为表达式。
如果when 分支被当作表达式,那么符合条件的分支的代码块的值就是整个表达式的值。
与if 分支相同的是,如果分支的执行体是一个代码块,那么该代码块的值就是块中最后的表达式的值。当when 语句作为表达式使用时, w hen 表达式也需要有一个返回值,因此when 表达式通常必须有else 分支,除非编译器能够检测出所有的可能情况都己经被覆盖了。示例代码如下:
fun main(args: Array<String>) {
var score = 'B'
var str = when(score) {
'A' -> {
println("你好秀啊")
"优秀"
}
'B' -> {
println("你也可以呀")
"良好"
}
'C' -> {
"中"
}
'D' -> {
"差"
}
'E' -> {
"不及格"
}
}
println(str)
}
when 分支处理范围
用in
关键字可以用来表达范围
fun main(args: Array<String>) {
val age = java.util.Random().nextInt(100)
println(age)
var str = when (age) {
in 10..25 -> "当时年少青衫薄"
in 26..50 -> "风景依稀似去年"
in 51..80 -> "醉听清吟胜管弦"
else -> "其他"
}
println("str")
}
when 分支处理类型,通过使用is 、! is 运算符,我们还可以使用when 分支检查表达式是否为指定类型。例如如下代码。
fun main() {
var inputPrice = 26;
inputPrice++;
println(realPrice(inputPrice))
}
fun realPrice(inputPrice: Any) = when (inputPrice) {
is String -> inputPrice.toDouble()
is Int -> inputPrice.toDouble()
is Double -> inputPrice
else -> 0.0
}
when条件分支
when分支还可以用来取代if... else if 链,此时不需要为when 分支提供任何条件表达式,每个分支条件都是一个布尔表达式,当指定分支的布尔表达式为true 时执行该分支。例如如下代码。
fun main(args: Arrray<String>) {
val ln = readLine()
if (ln != null) {
when {
ln.matches(Regex("\\d+")) -> println("您输入的全是数字")
ln.matches(Regex("[a-zA-Z]+")) -> println("您输入的全是字母")
ln.matches(Regex("[a-zA-Z0-9]+")) -> println("您输入的是字母和数字")
else -> println("您输入的包含特殊字符")
}
}
}
循环结构
while
do while
和Java完全一样,没啥可说的
for
循环有了很大的改变,主要格式是这样的
for (常量名 in 字符串|范围|集合) {
statements
}
for-in 循环中的常量无须声明。for - in 循环中的常量将会在每次循环开始时自动被赋值,
因此该常量无须提前声明。只要将它包含在for-in 循环的声明中,即可隐式声明该常
量,无须使用let关键宇声明。
for-in 循环可用于遍历任何可迭代对象。所谓可选代对象就是该对象包含一个iteratorO
方法,且该方法的返回值对象具有next ()、hasNext()方法,这三个方法都使用operator
修饰。
不要对for in 循环的循环计数器赋值
kotlin想要结束外层循环的话,可以加个标签跟Java的不太一样(Java是直接在循环前面加一个名字:
就行了),kotlin要这么用:
fun main (args: Array<String>) {
outer@ for (i in 0 until 5) {
for (j in 0 until 3) {
println("i的值为${i}, j的值为${j}")
if (j == 1) {
break@outer//直接结束外圈循环
}
}
}
}
return 是直接结束整个函数或方法.没有外圈内圈之说
Kotlin的数组
Kotlin的数组是Array类
Kotlin的集合有三种类型List,Set,Map.具体的定义都和Java一样.
Kotlin 创建数组其实就是创建Array
Kotlin 创建数组大致有如下两种方式。
使用arrayOf()、arrayOfNulls()、emptyArray()工具函数。
使用Array(size: Int, init: (Int) -> T)构造器。
fun main(args: Array<String>) {
//创建包含指定元素的数组(相当于Java 数组的静态初始化〉
var arr1 = arrayOf("Java","Kotlin","Scala","Go","Dart")
var arr2 = arrayOf(1,2,3,4,5)
//创建指定长度、元素为null 的数组(相当于Java数组的动态初始化)
var arr3 = arrayOfNulls<Double>(5)//创建一个长度为5的都为空的,类型为Double的数组
var arr4 = arrayOfNulls<Int>(6)
//创建长度为0的数组
var arr5 = emptyArray()
//创建指定长度、使用Lambda 表达式初始化数组元素的数组
var array6 = Array(5, {(it * 2 + 97).toChar()})//Array()后面的第二个参数是个lambda表达式,里面的it是代表的意思是数组里的每一个元素.
var array7 = Array(6, {"gaolei"})//创建一个6个长度的数组,数组里的每一个值都是"gaolei"
}
使用arrayOf()函数:这种方式无须显式指定数组的长度,但需要依次列出每个数组元
素。因此,这种方式其实就相当于Java 数组的静态初始化。使用这种方式创建数组时,
由于程序己经给出了每个数组元素,因此Kotlin 可以推断出数组元素的类型。所以,
不需要在arrayOf() 函数上使用泛型来指定数组元素的类型。
使用arrayOfNulls ()函数:这种方式需要显式指定数组的长度,数组元素全部被初始化
为null ,可见这种方式就是Java 数组的动态初始化。使用这种方式创建数组时,由于
Kotlin 无法推断出数组元素的类型,所以需要在arrayOfNull s ()函数上使用泛型来指定
数组元素的类型。
使用emptyArray ()函数: 这种方式会创建一个长度为0 的空数组。由于没有指定数组
元素,因此需要使用泛型来指定数组元素的类型。
使用Array(size: Int, init: (Int) -> T )构造器: 这种方式需要显式指定数组的长度,并可
通过Lambda 表达式来动态计算各数组元素的值。这种方式是原来Java 所不具备的。关于lambda表达式的更多介绍,请参照后面的内容.
Kotlin的数组直接使用[]来访问就好,也可以直接赋值.元素索引值也是从零开始的.
Java的数组长度叫做length,但kotlin里数组的长度叫做size
可以使用for in来遍历数组.此外还可以根据数组索引来访问数组.代码如下:
var books = arrayOf("海边的卡夫卡","且听风吟","挪威的森林")
for (book in books) {
println(book)//直接遍历数组里的元素
}
for (i in book.indices) {
println(book[i])//根据数组索引来遍历数组
}
kotlin还有一个lastindex属性,可以直接获得数组的最后一个索引值.非常方便.
如果程序需要同时访问数组的索引和元素,则也可使用数组的withlndexO 方法,该方法返回一个Iterable 对象,该对象的所有元素都是IndexedValue 。例如,如下代码可同时访问数组的索引和元素。
fun main(args: Array<String>) {
var books = arrayOf("鸟哥的Linux私房菜","算法第四版","Thinking in Java")
for ((index, value) in books.withIndex()) {
println("索引为${index}的元素是:${value}")
}
}
Kotlin中的集合
Kotlin中的集合由两个接口派生,Collection和Map,Kotlin中集合分为两大类,可变集合和不可变集合.不可变集合只能读取元素.
collection中的继承关系.
Kotlin 的集合实现类比Java 更少,它只提供了HashSet、Link:edHashSet 、
Array List 这三个最常见的实现类,如果开发者需要使用TreeSet 、LinkedList 集合实现类,则依然可以使用Java 集合框架提供的类.
Map集合的继承树
不难发现Kotlin 只提供了HashSet 、HashMap 、LinkedHashSet 、LinkedHashMap 、ArrayList 这5个集合实现类,而且它们都是可变集合。
set有一系列的方法可以使用.
两个set求交集:set1 intersect set2
两个set求并集:set1 + set2
两个set求差集:set1 - set2
关于Map还有一点需要说明,创建map集合的时候,需要传入键值对.而一对键值对是这么写的,"键"to 值
比如:
var map = mapOf("Java" to 86, "Kotlin" to 92, "Go" to 78)
//以后看到to的时候就不要再奇怪了,他就是起到连接键值对的作用.
Kotlin 语言的特征所决定的一-Kotlin 规定以infix 修饰的方法,能以运算符的方式进行调用。
Kotlin的函数
定义和调用函数
定义函数的语法格式如下:
fun 函数名(形参列表) [: 返回值类型] {
零到多条可执行语句
}
如果没有返回值,可以省略 : 返回值类型
这段代码,或者写成: Unit
,这个Unit就相当于Java的void
有些时候函数只是返回单个表达式,此时可以省略,例如fun area(x: Double, y: Double): Double = x * y
,而且对于单表达式函数来说,编译器可以推断出函数的返回值类型,因此Kotlin允许省略声明函数的返回值类型,刚才的函数可以去掉最后的 : Double.
函数的形参
Kotlin允许调用函数时通过名字来传入参数.kotlin函数除第一个参数之外,其他所有的形参都分配隐式的外部形参名.例如如下的程序:
fun girth(width: Double, height: Double): Double {
println("width: ${width}")
println("height: ${height}")
return 2 * (width + height)
}
fun main(args: Array<String>) {
//传统的函数调用方式,根据位置传入参数
println(girth(3.5, 4.8))
//根据参数名来传入参数
println(girth(width = 3.5, height = 4.8))
//使用命名参数时可以交换位置
println(girth(height = 3.5, width = 4.8))
//部分使用命名参数,部分使用位置参数(这种使用方法的话,命名参数必须在位置参数之后,不然编译器会分不清的)
println(girth(3.5, height = 4.8))
}
形参默认值
kotlin可以为形参指定默认值(欧,这让我想到了该死的C++).这样调用函数时就可以省略该形参.而且会直接使用形参的默认值.格式如下形参名: 形参类型 = 默认值
.示例代码如下:
fun sayHi(name: String = "孙悟空", message: String = "问世间情为何物") {
println("${name},Hello~")
println("i want to say ${message}")
}
fun main(args: Array<String>) {
//全部使用默认参数
sayHi()
//给name赋值,只有message参数使用默认值,不指定形参名字的话,默认按照顺序赋值.
sayHi("唐长老")
//两个参数都不使用默认值
sayHi("女儿国国王","世间安得两全法,不负如来不负卿")
//给message赋值,只有name属性使用默认值
sayHi(message = "月光宝盒还有吗")
}
kotlin建议将带默认值的参数放在函数形参列表的后面.
尾递归函数
将函数调用自身作为他执行的最后一行代码,且递归调用后没有更多的代码时.可使用尾递归调用.尾递归不能在异常处理中使用.尾递归函数需要使用tailrec函数修饰.
比如我们普通的递归函数--阶乘,普通递归这么写
fun fact(n: Int) {
if (n == 1) {
return 1
} else {
return n * fact(n - 1)
}
}
此函数将调用自身作为其执行体的最后一行代码,且递归调用后没有更多代码.因此可以将该函数改为尾递归调用语法.格式如下:
tailrec fun factRec(n: Int, total: Int = 1): Int = if (n == 1) total else factRec(n - 1, total * n)
在这个尾递归函数里面,第一个参数还是原来的n,增加了一个total形参.if后面是递归的出口,else后面则是递归的调用自身,其实就是上面递归函数的精简写法,不难理解的.
个数可变的形参
在定义函数时,在形参的名字前面加上vararg修饰符,则表明该形参可以接受多个参数值.多个参数值被当成数组传入.如下
fun test(a: Int, vararg books: String) {
for (book in books) {
println(b)
}
println(a)
}
fun main(args: Array<String>) {
test(5, "挪威的森林", "刺杀骑士团长")
}
kotlin允许将个数可变的形参处于形参列表的任意位置(Java要求只能是最后,且是最后一个),但kotlin要求一个函数最多只能带一个个数可变的形参.所以如果可变参数在前面的话,要想给后面的参数赋值,那么就只能使用命名参数.所以遵守Java的准则,只放在最后一个,挺好的,应对大多数情况足够啦.
如果你的参数是个可变参数,你可以把多个值传给这个参数,编译器会把他们变成数组传进去,可如果你的实参就是个数组呢?不能直接传进去(虽然进去之后还要变成数组),你要在数组前面加一个*(欧,又让我想到了该死的C++).(而我们优雅的Java就不一样,我们Java直接就可以传数组进去,Java用笨拙的语法,把编程的可能性变得少了一些,可是也变得死板了很多,代码量也多了很多,但说到底,kotlin就是个语法糖版的Java.)
函数重载(不要叫方法重载,方法是类里面的叫法,现在kotlin允许函数是一等公民,明显函数这个名字高大上很多了)
kotlin允许定义多个同名函数,只要形参列表就行.(Java的方法重载规定,必须要形参列表不同)
形参个数不同,形参类型不同,都可以算函数重载.但仅有形参名不同,返回值类型不同或修饰符不同,则不能算是函数重载.
不推荐重载形参个数可变的函数.没有太大意义,且容易导致错误.
局部函数
kotlin支持在函数体内部定义函数,放在函数体内部定义的函数称为局部函数.
局部函数对外是隐藏的,只在其封闭函数内部有效.当然函数也可以把局部函数作为返回值返回.
高阶函数
kotlin函数也是一等公民,拥有自己的合法权益,不用依附于类而存在.(这是相对于Java的最大改变)
函数是一种数据类型,也可以用来定义变量,也可以作为函数的形参类型,还可作为函数的返回值类型,这可比Java里面写那种只有一个方法的函数式接口以及lambda表达式方便多了(感觉这个东西像是Java对时代的妥协,可还是不够好).
函数的类型很有意思,她不是一个名字,而是由函数的形参列表,->和返回值类型组成.例如:
func foo(a: Int, name: String) -> String{
...
}
//对于这个函数来说,(Int, String) -> String 就是该函数的类型.
func bar(width: Double, height: Double) {
...
}
//此函数,(Double, Double) -> Unit或者(Double, Double)就是该函数的类型
func test() {
...
}
//此函数,() -> Unit或者()就是该函数的类型
掌握了函数类型之后,就可以像普通类型定义变量一样,例如下面的代码
//定义一个变量,其类型为(Int, Int) -> Int
var myFun: (Int, Int) -> Int
//定义一个函数变量,其类型为(String)
var test: (String)
定义了函数类型的变量之后,接下来程序即可将函数赋值给该变量.如:
//定义一个计算乘方的函数
fun pow(base: Int, exponent: Int): Int {
var result = 1
for (i in 1..exponent) {
result *= base
}
return result
}
//将pow函数赋值给myfun,则myfun可当成pow来使用
//直接访问一个函数的函数引用,而不是调用函数时,需要在函数名前面加上两个::,而且不能在函数后面添加圆括号,一旦添加了圆括号,就变成了调用函数,而不是访问函数引用了.
myfun = ::pow//只要函数的类型一致,就可以互相赋值.
println(myfun(3, 4))//直接可以使用函数myfun啦,跟pow一个效果
//定义一个计算面积的函数
fun area(width: Int, height: Int) {
return width * height
}
//将area函数赋值给myfun来使用,则myfun现在就可以求面积了
println(myfun(3, 4))
使用函数类型作为形参类型
//定义函数类型的形参,其中fn是`(Int) -> Int` 类型的形参
fun map(data: Array<Int>, fn: (Int) -> Int): Array<Int> {
var result = Array<Int>(data.size, {0})//创建一个数组,数组的大小和data数组一样,同时,都用0作为初始值.
//遍历data数组的每个元素,并用fn函数对data[i]进行计算,并将计算结果作为新数组的元素
for (i in data.indices) {
result[i] = fn(data[i])
}
return result
}
//定义一个计算平方的函数
fun square(n: Int): Int {
return n * n
}
//定义一个计算立方的函数
fun cube(n: Int): Int {
return n * n * n
}
//定义一个计算阶乘的函数
fun factorial(n: Int): Int {
var result = 1
for (index in 2..n) {
result *= index
}
return result
}
fun main(args: Array<String>) {
var data = arrayOf(3, 4, 9, 5, 8)
println("原数组为:${data.contentToString()}")
//分三次调用map函数,每次都传入不同的参数
println("计算数组元素的平方")
println(map(data, ::square).contentToString)
println("计算数组元素的立方")
println(map(data, ::cube).contentToString)
println("计算数组元素的阶乘")
println(map(data, ::factorial).contentToString)
}
//函数能作为参数真的比Java方便太多了.Java要用lambda表达式也可以做到,但是还是感觉到不如直接传一个函数进去方便
使用函数类型作为返回值类型
//此函数的返回值是一个(Int) -> Int类型的函数
fun getMathFunc(type: String): (Int) -> Int {
fun square(n: Int): Int {
return n * n
}
fun cube(n: Int): Int {
return n * n * n
}
fun factorial(n: Int): Int {
var result = 1
for (index in 2..n) {
result *= index
}
return result
}
when(type) {
//返回局部函数
"square" -> return ::square
"cube" -> return ::cube
else -> return ::factorial
}
}
fun main(args: Array<String>) {
//调用getMathFunc函数,返回一个(Int) -> Int类型的函数
var mathFunc = getMathFunc("cube")//这里返回的是求立方的函数
println(mathFunc(5))//会输出125
//另外两个调用省略
}
局部函数与lambda表达式
例如上面的getMathFunc
函数可以进行省略简化成如下格式:
fun getMathFunc(type: String): (Int) -> Int {
//该函数返回的是lambda表达式
when(type) {
"square" -> return {n: Int -> n * n}
"cube" -> return {n: Int -> n * n * n}
else -> return {n: Int ->
var result = 1
for(index in 2..n) {
result *= index
}
result
}
}
}
//调用函数和上面的一样,这里不写了
在kotlin里,lambda表达式只是与局部函数存在如下差别
- lambda表达式总是被大括号括着
- 定义lambda表达式不需要fun关键字,无须指定函数名.
- 形参列表(如果有的话)在 -> 之前声明,参数类型可以省略
- 函数体(Lambda表达式执行体)放在 -> 之后
- 函数的最后一个表达式自动被作为lambda表达式的返回值,无须使用return关键字.
lambda表达式的脱离
作为函数参数传入的lambda表达式可以脱离函数独立使用.如下:
//定义一个list类型的变量,并将其初始化为空list
var lambdaLiast = java.util.ArrayList<(Int) -> Int>()
//定义一个函数,该函数的形参类型为函数
fun collectFn(fn: (Int) -> Int) {
//将传入的fn参数(函数或者lambda表达式)添加到lambdaList集合中
//这意味着fn将可以在collectFn范围之外的地方使用
lambdaList.add(fn)
}
fun main(args: Array<String>) {
//调用collectFn()函数两次,将会向lambdaList中添加元素(每个元素都是lambda表达式)
collectFn({it * it})
collectFn({it * it * it})
println(lambdaList.size)
for (i in lambdaList.indices) {
println(lambda[i](i + 10))
}
}
总结一下kotlin的语法格式就是这样子的.
{
形参1,形参2,形参3... -> 零条到多条可执行语句
}
调用lambda表达式
可以将lambda表达式赋值给变量或者直接调用lambda表达式
fun main(args: Array<String>) {
var square = {n: Int -> n * n}
//使用square调用lambda表达式(你就把lambda表达式看成是精简版的函数就好了)
println(square(5))
//定义一个lambda表达式,并在他后面添加圆括号来调用该lambda表达式]
var result = {base: Int, exponent: Int ->
var result = 1
for (i in 1..exponent) {
result *= base
}
result
}(4, 3)//写完lambda表达式后面加个括号传入参数直接调用
println(result)//那么在这里就不用再写括号传入参数了.(不过我觉得这个功能好鸡肋啊,真的会有人用吗....)
}
lambda表达式如果能推断出形参类型,那么就可以省略形参类型.
如果只有一个形参,甚至允许你省略形参名(kotlin就是要代码简单,简单到看代码会一头蒙),当然如果省略了形参名,那么->也就没必要写了.省略了形参名之后,他们有一个统一的国家分配的形参名:it,示例代码如下:
fun main(args: Array<String>) {
//省略形参名,用it代表形参
var square: (Int) -> Int = {it * it}
//使用square来调用lambda表达式
println(square(5)) //输出25
println(square(3)) //输出9
}
kotlin约定,如果函数的最后一个参数是函数类型,而且你打算传入一个lambda表达式作为相应的参数,那么就允许在圆括号之外指定lambda表达式.不懂没关系,看一下下面的代码你就明白了.
fun main(args: Array<String>) {
var list = listOf("Java", "Kotlin", "Go")
var rt = list.dropWhile(){it.length > 3}//看到没,把lambda表达式写在了圆括号外面.
println(rt)
}
如果lambda表达式是函数调用的唯一参数,那么调用方法时的圆括号完全可以省略.比如上面那句你就可以写成:
var rt = list.dropWhile{it.length > 3}
个数可变的参数以及lambda参数
如果一个函数既包含个数可变的形参,也包含函数类型的形参,那么就应该将函数类型的形参放在最后.而且把函数类型的实参放在圆括号外面,你也不用使用命名参数来给他命名了,方便一些.
匿名函数
匿名函数和普通函数唯一的区别就是没有函数名字.
内联函数
使用inline修饰带函数形参的函数即可,他会将调用的函数copy进到本函数里,而不是调用关系.函数代码少时非常合适
定义类
[修饰符] class 类名 [constructor 主构造器] {
次构造器定义...
属性...
方法...
}
主构造器
写在类头上
class User constructor(firstName: String) {
}
//如果没有任何注解或者修饰符,那么可以省略constructor关键字,上面代码写成
class User (firstName: String) {
}
定义属性
[修饰符] var|val 属性名: 类型 [= 默认值]
[<getter>]
[<setter>]
对象的产生
新建一个对象不需要使用new关键字
var p: Person = Person()
var p: person p = Person()
调用类里的方法
我们依旧可以将类里的方法赋值给一个变量,只不过跟函数稍微有点不一样.看下面示例代码:
class Dog{
fun run(){
println("run方法")
}
fun eat(food: String) {
println("like " + food)
}
}
fun main(args: Array<String>) {
var rn: (Dog) -> Unit = Dog::run
val d = Dog()
rn(d)
var et: (Dog, String) -> Unit = Dog::eat
et(d, "宫保鸡丁")
}
//可以看到,有两点不同,1.赋值的时候,在`::`前要加类名. 2.参数列表自动增加一个类.调用的时候也要把类的对象传进去.
中缀表示法
将kotlin中的方法使用infix修饰,那么这个方法就可以像+-*/一样使用了.但是这种中缀方法只能有一个参数,因为加减乘除也只有一个参数.示例代码:
class Apple(weight: Double){
var weight = weight
override fun toString(): String{
return "这是一个${weight}斤重的苹果"
}
infix fun drop(other: Apple) {
this.weight -= other.weight
return this
}
}
fun main(args: Array<String>) {
var apple = Apple(3.5)
println(apple)
apple drop Apple(1.5)
println(apple)
}
componentN方法与结构
kotlin允许将一个属性的多个属性解构给多个变量,例如: var (name, pass) = user
,会将user的name属性和pass属性分别赋值给name和pass变量.
那么问题来了,kotlin怎么知道哪个属性赋值给哪个变量呢?上面的代码其实就是下面的代码:
var name = user.component1()
var pass = user.component2()
所以我们要给User这个类添加componentN方法.且该方法需要operator修饰
class User(name: String, pass: String, age: Int) {
var name = name
var pass = pass
var age = age
operator fun component1(): String {
return this.name
}
operator fun component2(): String {
return this.pass
}
operator fun component3(): Int{
return this.age
}
}
//这样你就可以解构赋值了
var user: User = User("gaolei", "loveyou", 25)
var (name, pass, age) = user
//还可以用下划线`_`来占位,例如
var (_, pass, age) = user
遍历map的方式
for ((key, value) in map){执行体}
或者是 for (entry in map) {执行体}
kotlin的数据类(有点类似于Java里的pojo,单纯为了封装数据,不做别的用)
数据类要使用data来修饰,还需要满足以下要求.
- 主构造器至少需要一个参数
- 主构造器所有参数需要用val 和 var 来修饰.
- 数据类不能用abstract, open, sealed修饰.也不能定义成内部类.
系统会自动为数据类生成下面的内容, - 生成equals, hashCode方法
- 自动重写toString方法
- 为每个属性生成operator修饰的componentN方法.
- 生成copy()方法,用于完成对象复制.
示例代码:
data class Result(val result: Int, val status:String)
fun factroial(n: Int): Result {
if (n == 1) return Result(1, "成功")
else if (n > 1) return Result(factroial(n - 1).result * n, "成功")
else return Result(-1, "参数必须大于0")
}
fun main(args: Array<String>) {
var (rt, status) = factroial(6)
println(rt)
println(status)
}
属性和字段
kotlin不需要setter和getter.自带.高级,讲究.
在定义Kotlin 的普通属性时,需要程序员显式指定初始值:要么在定义时指定初始值,要么在构造器中指定初始值。
当然你也可以为类的属性自定义getter和setter方法.不过跟Java不一样.看示例代码:
class User(first: String, last: String) {
var first: String = first
var last: String = last
//setter和getter方法直接跟在属性的后面就行,
var fullName: String
get() = "${first}.${last}"
set(value: String) = {
println("执行fullName的setter方法")
var tokens = value.split("\\.")
first = token[0]
second = token[1]
}
}
kotlin的幕后字段
当满足以下条件时,系统会为属性生成幕后字段.
- 该属性使用Kotlin 自动生成的ge 忧er 和se忧er 方法或其中之一。换句话说,对于只读属性,必须重写ge tter 方法:对于读写属性,必须重写getter 、setter 方法: 否则总会为该属性生成幕后宇段。
- 重写getter 、setter 方法时,使用field 关键字显式引用了幕后字段。
当程序重写getter 或setter 方法时,不能通过点语法来对name 、age 赋值! 假如在name的setter 方法中使用点语法对name赋值, 由于点语法赋值的本质是调用setter方法,这样就会造成在setter方法中再次调用setter方法,从而形成无限递归。
幕后属性
幕后属性就是用private修饰的属性,kotlin不会为幕后属性生成任何getter, setter方法,必须由程序员显式提供getter, setter.
Kotlin 提供了lateinit 修饰符来解决属性的延迟初始化。使用lateinit 修饰的属性,可以在定义该属性时和在构造器中都不指定初始值。
对lateinit 修饰符有以下限制。
- lateinit 只能修饰在类体中声明的可变属性(使用val 声明的属性不行,在主构造器中声明的属性也不行)。
- lateinit 修饰的属性不能有自定义的getter 或setter方法。
- lateinit 修饰的属性必须是非空类型。
- lateinit 修饰的属性不能是原生类型(即Java 的8种基本类型对应的类型)。
与Java不同的是,kotlin不会为属性执行默认初始化
访问控制符
- private: 与Java一样,只能类内部的文件访问.
- internal: internal 成员可以在该类的内部或文件的内部或者同一个模块内被访问。
- protected: protected 成员可以在该类的内部或文件的内部或者其子类中被访问。
- public: public 成员可以在任意地方被访问。
- kotlin的默认修饰符是public
构造器进阶
主构造器作为类头的一部分,可以声明形参,但它自己并没有执行体。那么主构造器的形
参有什么用呢?其作用主要有两点。
- 初始化块可以使用主构造器定义的形参。
- 在声明属性时可以使用主构造器定义的形参。
初始化块的语法格式如下:
//初始化块要用init来修饰.而Java不需要,Java就是一个代码块.
init {
//初始化块中的可执行代码, 可以使用主构造器定义的参数
}
kotlin要求所有的次构造器执行前都要先调用主构造器,而主构造器又会调用初始代码块.
次构造器,这么写:
constructor {
//多条语句
}
Kotlin 使用“:this (参数)”的语法委托另一个构造器,到底委托哪个构造器则取决于传入的参数。系统会根据传入的参数来推断委托了哪个构造器。
在主构造器声明属性
Kotlin 允许在主构造器上声明属性,直接在参数之前使用var 或val 即可声明属性一一使用var 声明的是读写属性,使用val 声明的是只读属性。当程序调用这种方式声明的主构造器创建对象时,传给该构造器的参数将会赋值给对象的属性。示例代码:
class Item(val code: String, val price: Double){}
fun main(args: Array<String>) {
var im = Item("1234567", 3.7)
println(im.code)
println(im.price)
}
如果主构造器的所有参数都有默认值,程序能以构造参数的默认值来调用该构造器(即不需要为构造参数传入值),此时看上去就像调用无参数的构造器。例如如下程序。
//使用主构造器声明属性
class Customer(val name: String = "匿名", val addr: String = "庞口"){}
fun main(args: Array<String>) {
var ct = Customer("周杰伦", "台湾")
var ctm = Customer()
}
类的继承
kotlin同样是单继承,只能有一个父类.
子类继承父类的语法格式如下:
//这写法又让我想到了该死的C++,欧,我那苦不堪言的回忆
//我翻译一下,就是你想继承谁,就在类名后面加个`:父类`就完事儿了.
修饰符 class SubClass : SuperClass {
类定义部分
}
子类的主构造器
open class BaseClass {
var name: String
constructor(name: String) {
this.name = name
}
}
//子类没有显示声明主构造器,默认有一个主构造器,因此要在声明时委托调用父类构造器.
class SubClass1: BaseClass("foo") {
}
//子类显示声明主构造器,主构造器必须在声明继承时委托调用父类构造器.
class SubClass2(name: String): BaseClass(name) {
}
子类的次构造器
次构造器同样需要委托父类构造器
如果子类定义了主构造器,由于子类的次构造器总会委托调用子类的主构造器(直接或间接),而主构造器一定会委托调用父类构造器,因此子类的所有次构器最终也调用了父类构造器。
如果子类没有定义主构造器,则此时次构造器委托调用父类构造器可分为3 种方式。
- 子类构造器显式使用: this (参数)显式调用本类中重载的构造器,系统将根据this (参数)调用中传入的实参列表调用本类中的另一个构造器。调用本类中的另一个构造器最终还是要调用父类构造器。
- 子类构造器显式使用: super(参数)委托调用父类构造器,系统将根据super(参数)调用中传入的实参列表调用父类对应的构造器。
- 子类构造器既没有: s uper(参数)调用,也没有: this (参数)调用,系统将会在执行子类构造器之前,隐式调用父类无参数的构造器。
open class Base {
constructor(){
println("父类的无参构造器")
}
constructor(name: String){
println("父类的有参数的构造器,参数是${name}")
}
}
class Sub: Base {
//构造器没有显示委托,会隐式调用父类无参数构造器
constructor() {
println("子类的无参数构造器")
}
//构造器用super(name)显式调用父类的带String参数的构造器
constructor(name: String): super(name) {
...
}
//调用另一个构造方法
constructor(name: String, age: Int): this(name) {
...
}
}
插一句嘴,kotlin所有类的总的父类是Any类,不是Java的Object类
子类进程父类,将可以获得父类的全部属性和方法
重写父类方法
Kotlin 类重写父类的方法必须添加override 修饰符,而且父类的方法一定要是open修饰
方法的重写要遵循“两同两小一大”规则(跟Java是一样的),“两同”即方法名相同、形参列表相同:“两小”指的是子类方法的返回值类型应比父类方法的返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等:“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
重写父类的属性与重写父类的方法大致相似:父类被重写的属性必须使用open 修饰,子类重写的属性必须使用override 修饰。此外,属性重写还有如下两个限制。
- 重写的子类属性的类型与父类属性的类型要兼容。
- 重写的子类属性要提供更大的访问权限。此处包含两方面的含义: ①在访问权限方面,子类属性的访问权限应比父类属性的访问权限更大或相等。②只读属性可被读写属性重写,但读写属性不能被只读属性重写。
如果需要在子类方法中调用父类中被覆盖的方法或属性,则可使用super 限定(这点跟Java是一样的)。
注意!!!如果子类从多个直接超类型(接口或类〉继承了同名的成员,那么Kotlin 要求子类必须重写该成员。如果需要在子类中使用super来引用超类型中的成员,则可使用尖括号加超类型名限定的super 进行引用, 如super<Bar>。
多态
Kotlin的多态同样编译向左,使用向右.
使用is检查类型
Kotlin 的类型转换运算符包含as和as?两个。由于向上转型可由Kotlin 自动转换,因此强制转型通常总是向下转型。为了保证类型转换不会出错, Kotlin 提供了类型检查运算符: is 和!is。
is 运算符的前一个操作数通常是一个变量,后一个操作数通常是一个类(也可以是接口,可以把接口理解成一种特殊的类),它用于判断前面的变量是否引用后面的类,或者其子类、实现类的实例。如果是,则返回true ,否则返回false 。
import java.util.Date
//定义一个接口
interface Testable{}
fun main(args: Array<String>) {
//声明hello时使用Any类,则hello的编译时类型是Any
//hello变量的实际类型是String
val hello: Any = "Hello"
println("字符串是否是String类的实例: ${hello is String}")//返回true
//Date与Any类存在继承关系,因此可以进行is运算
println("字符串是否是Date类的实例: ${hello is Date}")//返回false
//String没有实现Testable接口,所以返回false.
println("字符串是否实现了Testable接口: ${hello is Testable}")//返回false
var a: String = "hello"
//String类与Date类没有继承关系,因此下面代码会报错
println("字符串是否是Date类的实例: ${a is Date}")//抛出异常
}
使用as运算符进行转型
- as : 不安全的强制转型运算符,如果转型失败,程序将会引发ClassCastException 异常。
- as ?: 安全的强制转型运算符,如果转型失败,程序不会引发异常,而是返回null 。
如果试图把一个父类变量转换成子类类型,则该变量实际引用的实例必须是子类实例才行(即编译时类型为父类类型,而运行时类型是子类类型),否则就会转换失败。
Kotlin 默认为所有类、方法、属性都提供了final 关键字来修饰,这意味着在默认情况下,Kotlin 的类不可派生子类, Kotlin 的方法、属性都不可被重写。为了取消默认的final 修饰符,Kotlin 提供了final 的反义词: open
kotlin扩展方法
扩展方法的语法很简单,其实就是定义一个函数,只是函数名不要写成简单的函数,而是要在函数名前增加被扩展的类(或接口)名和点号(.) 。例如如下程序。
open class Ram{
fun test() {
println("test方法")
}
}
上面程序定义了一个Raw 类,该Raw 类使用了open 修饰符,这是为了示范给Raw 类派生子类。如果只是对Raw 类进行扩展,那么Kotlin 并不需要使用open 修饰该类。接下来为Raw类派生如下类:
class RawSub: Raw(){
fun sub() {
println("sub方法")
}
}
上面RawSub 类继承了Raw 类,并增加了一个新方法: sub() 。接下来,程序为Raw 类扩展一个方法, 代码如下。
fun Raw.info() {
println("扩展的info方法")
}
程序为Raw 类扩展了info()方法之后,就像为Raw 类增加了info()方法一样,所有的Raw对象都可调用info()方法。不仅如此, Raw 类的子类的实例也可调用info 方法。例如如下程序。
fun main(args: Array<String>) {
var t = Raw()
t.test()
t.info()
var rs = RawSub()
rs.test()
rs.sub()
rs.info()
}
扩展方法中的this 与成员方法中的this 一样,也代表调用该方法的对象。
当然,扩展也可以为Kotlin 系统提供的类增加方法。这就牛逼了,给String类增加个方法,岂不美滋滋,哈哈哈蛤.
对于多态的扩展方法来说,如果父类和子类都扩展了同一种方法,那么多态调用时,调用的是父类的扩展方法.
而且如果有个成员方法和扩展方法同名的话,也不会调用扩展方法.而是调用成员方法.但是如果只是名字相同,形参列表不同,编译器还是能分清的.
扩展属性
由于Kotlin 的扩展属性只能是计算属性,因此对扩展属性有如下两个限制。
- 扩展属性不能有初始值(没有存储属性值的幕后宇段) 。
- 不能用field 关键字显式访问幕后宇段。
- 扩展只读属性必须提供getter方法: 扩展读写属性必须提供getter 、setter 方法。
示例代码:
var User.fullName: String
get() = "${first}.${last}"
set(value) {
println("执行扩展属性fullName的setter方法")
var tokens = valule.split("\\.")
first = tokens[0]
second = tokens[1]
}
fun main(args: Array<String>) {
var user = User("杰伦", "周")
println(user.fullName)
user.fullName = "周口.杰伦"
println(user.first)
println(user.second)
}
带接受者的匿名函数
Kotlin 还支持为类扩展匿名函数,在这种情况下,该扩展函数所属的类也是该函数的接收者.
与普通扩展方法不同的是:去掉点(.)之后的函数名即可,其他部分井没有太大的区别。
示例代码:
//定义一个带接受者的匿名函数
val factorial = fun Int.(): Int{
//该匿名函数的接收者是Int对象
//因此在该匿名函数中,this代表调用该匿名函数的Int对象
if (this < 0) {
return -1
} else if (this == 1) {
return 1
} else {
var result = 1
for (i in 1..this) {
result *= i
}
return result
}
}
fun main(args: Array<String>) {
println(6.factorial())
}
kotlin就是比Java牛逼,不接触新技术怎么会有发展呢,死守旧技术,死路一条.
final 和 open 修饰符.
kotlin自动为非抽象类添加final修饰符,默认不允许继承.但要注意,kotlin中final不能修饰局部变量.
kotlin的宏变量,要满足以下条件
- 用const修饰
- 位于顶层或是对象表达式的成员
- 初始值为基本类型值(Java八种基本类型值)或字符串,
- 没有自定义的getter方法
//编译时所有的MAX_AGE都会被替换为100
const val MAX_AGE = 100
不可变类
如果需要创建自定义的不可变类,可遵守如下规则。
- 提供带参数的构造器,用于根据传入的参数来初始化类中的属性。
- 定义使用final 修饰的只读属性,避免程序通过setter 方法改变该属性值。
抽象类(定义啥的和Java是一样的)
使用abstract 修饰的类,无法用open修饰,因为自带open
密封类
密封类是一种特殊的抽象类,专门用来派生子类.密封类与普通抽象类的区别在于: 密封类的子类是固定的。密封类的子类必须与密封类本身在同一个文件中,在其他文件中则不能为密封类派生子类,这样就限制了在其他文件中派生子类。
例如下面代码定义了一个密封类和两个子类.
//定义一个密封类,其实就是抽象类
sealed class Apple {
abstract fun taste()
}
open class RedFuji: Apple() {
override fun taste() {
println("红富士苹果香甜可口")
}
}
data class Gala(var weight: Double): Apple() {
override fun taste() {
println("嘎啦果更清脆,重量为: ${weight}")
}
}
fun main(args: Array<String>) {
//使用Apple声明变量,用子类实例赋值
var ap1: Apple = RedFuji()
var ap2: Apple = Gala(2.3)
ap1.taste()
ap2.taste()
}
密封类的直接子类必须与密封类位于同一个文件中,但密封类的间接子类(子类的子类)则无须在同一个文件中
kotlin的接口
kotlin的接口与Java8的接口非常相似
接口定义的基本语法如下:
[修饰符] interface 接口名: 父接口1,父接口2... {
零个到多个属性定义...
零个到多个方法定义...
零个到多个嵌套类、嵌套接口、嵌套枚举定义...
}
修饰符可以是public|internal|private中的任意一个,或完全省略修饰符,省略的话默认采用public
一个接口可以有多个父接口(这个跟Java是一样的),但接口只能继承接口,不能继承类.
接口中的属性没有幕后字段, 因此无法保存状态,所以接口中的属性要么声明为抽象属性,要么为之提供setter、getter 方法。
接口中定义的方法既可是抽象方法,也可是非抽象方法。如果一个方法没有方法体, Kotlin会自动为该方法添加abstract 修饰符;同理,如果一个只读属性没有定义getter 方法, Kotlin会自动为该属性添加abstract 修饰符;如果一个读写属性没有定义getter 、setter 方法, Kotlin会自动为该属性添加abstract 修饰符。
Java 接口中的所有成员都会自动使用public 修饰,如果为这些成员指定访问权限,也只能指定public 访问权限;但Kotlin 接口中的成员可支持private 和public 两种访问权限。
一个类可以实现多个接口,使用方法如下:
[修饰符] class 类名: 父类, 接口1, 接口2... {
类体部分...
}
嵌套类和内部类
在某些情况下,要把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为嵌套类.kotlin中的嵌套类一共分为两种:
- 嵌套类(相当于静态内部类):只要将一个类放在另一个类中定义,这个类就变成了嵌套类,相当于Java 中有static 修饰的静态内部类。
- 内部类(非静态内部类):使用inner 修饰的嵌套类叫内部类,相当于Java 中无static修饰的非静态内部类
kotlin没有static修饰符,也就没有静态一说了,要注意,要注意
因此Kotlin 类中的成员除嵌套类之外,全部都是非静态成员,因此嵌套类不可访问外部类的其他任何成员(只能访问其他嵌套类)。
在外部类之外使用内部类
class Out {
//定义一个内部类,不使用访问控制符,默认是public
inner class In(msg: String) {
//init开头的是初始化块
init {
println(msg)
}
}
}
fun main(args: Array<String>) {
//使用Out的内部类In
var oi: Out.In = Out().In("测试信息")
}
而在外部类之外使用嵌套类,相对来说简单一些.比如上面的代码,如果使用内部类的话.这么写:var oi: Out.In = Out.In("测试信息")
.
所以我们得出结论:能用嵌套类就不要用内部类.
匿名内部类
kotlin抛弃了匿名内部类.但是创造了更加高级的表达式语法.
对象表达式
Kotlin 提供了比匿名内部类更加强大的语法:对象表达式。它们的主要区别在于:匿名内部类只能指定一个父类型(接口或父类),但对象表达式可指定0~N 个父类型(接口或父类)。
对象表达式的语法如下:
object[: 0~N个父类型] {
//对象表达式的类体部分
}
对象表达式就是升级版的匿名内部类.
对象表达式还有如下规则:
- 对象表达式不能是抽象类,因为系统在创建对象表达式时会立即创建对象。因此不允许将对象表达式定义成抽象类。
- 对象表达式不能定义构造器。但对象表达式可以定义初始化块,可以通过初始化块来完成构造器需要完成的事情。
- 对象表达式可以包含内部类(有inner 修饰的内部类),不能包含嵌套类。
几种对象表达式的示范:
interface Outputable {
fun output(msg: String)
}
abstract class Product(var price: Double) {
abstract val name: String
abstract fun printInfo()
}
fun main(args: Array<String>) {
//指定一个父类型(接口)的对象表达式
var ob1 = object: Outputable{
//重写父接口中的抽象方法
override fun output(msg: String) {
for (i in 1..6) {
println("${i} + msg")
}
}
}
ob1.output("时光轻轻吹")
//指定零个父类型的对象表达式
var ob2 = object {
//初始化块
init {
println("初始化块")
}
//属性
var name = "Kotlin"
//方法
fun test() {
println("test方法")
}
//只能包含内部类,不能包含嵌套类
inner class Foo
}
println(ob2.name)
ob2.test()
//指定两个父类型的对象表达式
//由于Product只有一个带参数的构造器,因此需要传入构造器参数
var ob3 = object: Outputable, Product(28.8) {
override fun output(msg: String) {
println("msg")
}
override var name: String
get() = "激光打印机"
override fun printInfo() {
println("高速激光打印机,支持双面打印")
}
}
println(ob3.name)
ob3.output("Kotlin真不错")
ob3.printInfo()
}
对象声明和单例模式
对象声明的语法格式如下:
object ObjectName[: 0~N个父类型] {
//对象表达式的类体部分
}
对象声明与对象表达式的语法非常相似.唯一区别就是 在object关键字后指定名字.同时还存在以下区别:
- 对象表达式是一个表达式,因 它可以被赋值给变量 而对象声明不是表达式,因此它不能用于赋值。但是他本身就有名字
- 对象声明可包含嵌套类,不能包含内部类 而对象表达式可包含内部类,不能包含嵌套类。
- 对象声明不能定义在函数和方法内 但对象表达式可嵌套在其他对象声明或非内部类中。
伴生对象和静态成员
在类中定义的对象声明,可使用companion修饰,这样该对象就变成了伴生对象.
每个类最多只能定义一个伴生对象,伴生对象相当于外部类的对象,程序可通过外部类直接调用伴生对象的成员.示例代码如下:
interface Outputable {
fun output(msg: String)
}
class MyClass {
companion object MyObject1: Outputable {
var name = "name属性值"
//重写父接口中的抽象方法
override fun output(msg: String) {
for (i in 1..6) {
println("${i} : ${msg}")
}
}
}
}
fun main(args: Array<String>) {
//使用伴生对象所在类调用伴生对象的方法.
MyClass.output("gaolei")
println(MyClass.name)
}
我们可以看到伴生对象很像Java里面的静态成员,直接使用类名来调用.因为kotlin没有静态成员.所以用这种方式来模拟静态成员.
伴生对象的扩展
伴生对象做了扩展,就相当于为半生对象所在类扩展了静态成员.可以通过外部类的类名访问这些扩展成员.
kotlin的枚举类与Java的枚举类相似
下面定义一个season枚举类
//枚举类要用enum class 来修饰
enum class Season {
//在第一行列出4个枚举实例,枚举实例之间用逗号分隔
SPRING, SUMMER, FALL, WINTER
}
带参数的枚举类这么写(保持和他老爹Java一致):
enum class Gender(val cnName: String) {
MALE("男"),FEMALE("女");//因为下面有方法,所以这里是为数不多的要用分号的地方
//定义方法
fun info() {
when(this) {
MALE -> println("男的")
FEMALE -> println("女的")
}
}
}
//调用的话,就这么用
var g = Gender.valueof("FEMALE")
类委托和属性委托
类委托
示例代码如下:
interface Outputable {
fun output(msg: String)
var type: String
}
//定义一个DefaultOutput类实现Outputable接口
class DefaultOutput: Outputable {
override fun output(msg: String) {
for (i in 1..6) {
println("${i} : ${msg}")
}
}
override var type: String = "输出设备"
}
//定义Printer类,指定构造参数b作为委托对象
class Printer(b: DefaultOutput): Outputable by b
//定义Projecter类,指定新建的对象作为委托对象
class Projecter(): Outputable by DefaultOutput() {
//该类重写output方法
override fun output(msg: String) {
println("恭喜恭喜恭喜你呀")
}
}
fun main(args: Array<String>) {
val output = DefaultOutput()
//Printer对象委托的对象是output
var printer = Printer(output)
//其实就是调用委托对象的output()方法
printer.output("周杰伦")
//Projecter对象委托的对象也是output
var projecter = Projecter()
//Projecter类重写了output方法,所以此处调用的是其本身的output方法
projecter.output("李荣浩")
//这里调用的是其委托对象的属性
println(projecter.type)
}
我们通过by关键字为两个类指定委托对象.分别采用了两种方式,不过第一种比较常见,也比较好用.传一个委托对象进去.
属性委托
Kotlin 也支持属性委托,属性委托可以将多个类的类似属性统一交给委托对象集中实现,这样就可避免每个类都需要单独实现这些属性。说实话我没看明白,太多了,先空着,有机会再细看一遍吧.
异常处理
kotlin没有checked异常了,所有异常都是runtime异常,也就是不捕获也能运行.
kotlin的异常处理结构如下(跟Java基本一样):
try {
//业务实现代码
...
} catch (e: Exception) {
//异常处理代码
...
}
...
finally {
//可选的finally块
}
try语句是表达式
与if 语句类似, Kotlin 的try语句也是表达式,因此位try语句也可用于对变量赋值,try表达式的返回值是try块中的最后一个表达式的值,或者是被执行的 catch 块中的最后一个表达式的值, finally 块中的内容不会影响表达式的结果。示例代码如下:
fun main(args: Array<String>) {
val input = readLine()
//用try表达式对变量a赋值
val a: Int? = try {Integer.parseInt(input)} catch(e: NumberFormatException){null}
println(a)
}
使用throw抛出异常.
跟Java一样,没啥可说的,可以完全不管,直接抛出就行.
自定义异常类
也跟Java的基本一样,看下面的示例代码:
class AuctionException: Exception {
//无参数的构造器
constructor() {}
//带一个字符串参数的构造器
constructor(msg: String): super(msg) {}
}
多提一句,throw语句也是表达式
泛型
与Java 类似, Kotlin 的泛型也允许在定义类、接口、函数时使用泛型形参,这个泛型形参将在声明变量、创建对象、调用方法时动态地指定(即传入实际的类型,也可称为类型实参〉。
例如下面的范型使用:
open class Apple<T> {
//使用范型T定义属性
open var info: T?
constructor() {
info = null
}
//下面方法中使用范型T来定义构造器
constructor (info: T) {
this.info = info
}
}
fun main(args: Array<String>) {
//由于传给泛型T的是String,所以构造器的参数只能是String
var a1: Apple<String> = Apple<String>("红富士")
println(a1.info)
//由于穿个泛型T的是Int,所以构造器的参数只能是Int
var a2: Apple<Int> = Apple(3)
System.out.println(a2.info)
//由于构造器的参数是Double,因此系统可推断出泛型的形参为Double类.
var a3 = Apple(5.67)
println(a3.info)
}
kotlin的泛型甚至不用写Java的菱形语法.比如声明一个List,在Java里要这样写,List<Integer> list = new ArrayList<>();
在Java里,后面的这个菱形不能省略,而在kotlin里,直接可以写成 var list: List<Int> = ArrayList()
就完事儿了.
声明处型变
- 如果泛型只需要出现在方法的返回值声明中(不出现在形参声明中〉,那么该方法就只是取出泛型对象,因此该方法就支持泛型协变(相当于通配符上限〉:如果一个类的所有方法都支持泛型协变,那么该类的泛型参数可使用out 修饰。
- 如果泛型只需要出现在方法的形参声明中(不出现在返回值声明中〉,那么该方法就只是传入泛型对象,因此该方法就支持泛型逆变(相当于通配符下限〉:如果一个类的所有方法都支持泛型逆变,那么该类的泛型参数可使用in 修饰。
- 前两条简单来说就是,如果只是返回值有泛型,那泛型就用out修饰,如果只是参数有泛型,那泛型就用in来修饰.
使用out 修饰泛型的类支持协变,也就是可以将User< String>、User
星号投影
在Java里,如果使用原始类型,不带泛型,可以直接这么写
ArrayList list = new ArrayList();
在kotlin里可不行.在kotlin里你要这么写:
var list: ArrayList<*> = arrayListOf(1, "Kotlin");
上面的写法就是星号投影.
泛型函数
所谓泛型函数,就是在声明函数时允许定义一个或多个泛型形参,泛型形参要用尖括号括起来,整体放在fun 与函数名之间。例如:
fun <T, S> 函数名(形参列表): 返回值类型 {
//函数体...
}
我们来举一个例子:
fun <T> copy(from: List<T>, to: MutableList<in T>) {
for(ele in from) {
to.add(ele)
}
}
fun main(args: Array<String>) {
var strList = listOf("Java", "Kotlin")
var objList: MutableList<Any> = mutableListOf)(2, 1.2, "Android")
//指定泛型函数的T为String类型
copy<String>(strList, objList)
println(objList)
var intList = listOf(7, 13, 17, 19)
//不显示指定泛型函数的T的类型,系统自行推断T为Int类型
copy(intList, objList)
println(objList)
}
声明了泛型函数之后,调用泛型函数时可以在函数名后用尖括号传入实际的类型,如上面程序中①号代码所示:也可以在调用泛型函数时不为泛型参数指定实际的类型,而是让系统自动推断出泛型参数的类型,如上面程序中②号代码所示。
设定类型形参的上限
在Java里我们用<T extends Number>
来表示.而在kotlin里我们这么写<T: Number>
注解
定义注解
Kotlin 使用annotation class 关键字(就像使用enum class 定义枚举类一样),定义注解非常简单, Kotlin 甚至不允许为注解定义注解体,也就是说,注解后面不能有花括号.例如:
annotation class Test
使用的话这么用:
@Test class MyClass {
...
}
如果要用注解来修饰主构造器,就像前面所介绍的,程序必须为主构造器添加constructor关键字。例如:
class User @Test constructor (var: name: String, var pass: String) {
...
}
kotlin调用Java
kotlin想调用Java类也很简单,只要Java类的属性有getter和setter即可.只有getter就是val,有getter和setter就是var
kotlin的关键字比Java多,因此如果有一些不是Java的关键字,但是是kotlin的关键字,使用反引号,就是1左边这个,对关键字进行转义.例如,我们定义了一个Java类,里面有一个in方法.在kotlin中调用in方法时,要转义
fun main(args: Array<String>) {
val im = InMethod()
im.`in`()//要用反引号包起来.
}
kotlin的已映射类型
虽然Kotlin 并未完整地提供整套类库,但Kotlin 还是为部分Java 类提供了特殊处理,这部分Java 类被映射到Kotlin 类,这种映射只在编译阶段发生,在运行阶段依然使用Java 类型。
获取obj的Java类
obj.javaClass
访问静态对象
虽然Kotlin 本身没有提供static 关键字,但Kotlin 提供了伴生对象来实现静态成员,因此Java 类中的静态成员都可通过伴生对象的语法来调用。
Kotlin反射
类引用(获取Kclass)
val c = MyClass::class
从Kclass获取信息
(后面内容没细看,有机会深入研究疯狂kotlin讲义即可.)