文章目录
- 1、关键字:
- ?的含义
- ?:的含义
- :(冒号)
- ::(双冒号)
- ::class
- !!(双叹号)
- abstract :
- any:
- by
- companion object 伴生对象:
- const:
- constructor 用于标识构造函数
- data class:
- indices:
- inline:
- inner:
- internal:
- is:
- lateinit var 延迟初始化属性:
- by lazy 懒属性(延迟加载)
- object(用于创建单例模式):
- open 说明可以被继承 :
- override :
- reified:
- sealed 密封类:
- tailrec(尾递归):
- Unit 类型:
- var和val:
- typealias:
- vararg :
- when 用于判断 相当于java中的switch()语句
- 2、注解:
- 3、作用域函数
- 4、定义类
1、关键字:
关键字与操作符 (官)
https://blog.csdn.net/dota_wy/article/details/76060078 (荐)
?的含义
在kotlin中单独使用?表示可为空;
kotlin代码书写格式:
var result = str?.length
java代码书写格式:
if(str == null)
result = null; // 这里result为一个引用类型
else
result = str.length; // 这里result为Int
有人疑问加“?”和不加的根本区别在哪?就在于程序运行过程中对变量的赋值,如果给没有加“?”的变量赋值了null,程序就会异常。
?:的含义
在kotlin中表示三元操作符(即三目运算符)
var str : String? = null
var result = str?.length ?: -1
//等价于
var result : Int = if(str != null) str.length else -1
:(冒号)
类型和超类型之间的冒号前要有一个空格,而实例和类型之间的冒号前不要有空格:
interface Foo<out T : Any> : Bar {
fun foo(a: Int): T
}
Kotlin 可以有包级函数,因此我们并不需要声明一个类来包装 main 函数:
fun main(args: Array<String>){
...
}
::(双冒号)
双冒号操作符 表示把一个方法当做一个参数,传递到另一个方法中进行使用,通俗的来讲就是引用一个方法。
https://blog.csdn.net/lv_fq/article/details/72869124
/**
* @param p1 参数1
* @param p2 参数2
* @param method 方法名称
*/
fun lock(p1: String, p2: String, method: (str1: String, str2: String) -> String): String {
return method(p1, p2)
}
::class
如何获得 class 的实例
Java 当中:
public class Hello{
...
}
...
Class<?> clazz = Hello.class;
Hello hello = new Hello();
Class<?> clazz2 = hello.getClass();
Kotlin当中:
class Hello
val clazz = Hello::class.java
val hello = Hello()
val clazz2 = hello.javaClass
同样效果的 Kotlin 代码看上去确实很奇怪,实际上 Hello::class 拿到的是 Kotlin 的 KClass,这个是 Kotlin 的类型;
如果想要拿到 Java 的 Class 实例,那么就需要Hello::class.java。
!!(双叹号)
意味着「有一个潜在未处理的 KotlinNullPointerException 在这里」
在kotlin中表示一定不能为空;
var result : Int = str!!.length
https://zhuanlan.zhihu.com/p/27285806
abstract :
抽象类 一个类或一些成员可能被声明成 abstract 。一个抽象方法在它的类中没有实现方法。
记住我们不用给一个抽象类或函数添加 open 注解,它默认是带着的。
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
any:
相当于Java中的Object。
by
类委托、属性委托
companion object 伴生对象:
类似static。
在 kotlin 中,不像 java 或者 C# ,它没有静态方法。在大多数情形下,我们建议只用包级别的函数。
如果你要写一个没有实例类就可以调用的方法,但需要访问到类内部(比如说一个工厂方法),你可以把它写成它所在类的一个成员(you can write it as a member of an object declaration inside that class)更高效的方法是,你可以在你的类中声明一个伴随对象,这样你就可以像 java/c# 那样把它当做静态方法调用,只需要它的类名做一个识别就好了。
实例:获取全局的Application实例
companion object {
lateinit var instance: App
}
const:
使用 const 修饰符标记为 编译期常量
constructor 用于标识构造函数
class Customer(name: String){
init {
logger,info("Customer initialized with value ${name}")
}
}
二级构造函数
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}
https://blog.csdn.net/dota_wy/article/details/76060078
3.1、注:kotlin中的类定义同时也是构造函数,这个时候是不能进行操作的,所以kotlin增加了一个新的关键字init用来处理类的初始化问题,init模块中的内容可以直接使用构造函数的参数。
class Person(name:String,age:int){
init{
//do some thiing
}
}
3.2、java中加入final为不可继承,而kotlin中定义类默认前面带有修饰符final,所以,如果想继承该类,在最前面加上open或者abstract即可。即:
open class Person(name : String, age : int) {
init{
// do some thing
}
}
3.3、如果init中没有操作,则可以省略
class Person(name : String, age : int)
3.4、如果连参数也没有,甚至可以这么写
class Person
3.5、但是当构造参数中的参数、类型变化时可能需要不只是一个构造函数,需要多组构造函数来处理不同view上的数据时,使用constructor加上参数,后面用this加上主构造函数的参数。
次级构造函数
class Person(name:String){
var a = 1
init{
log("This is --> primary constructor,a = $a,name = $name")
}
constructor(name:String,age:Int): this(name){
log("This is --> secondry constructor,a = ${++a}, age = $age")
}
}
data class:
data class就是一个类中只包含一些数据字段。
编译器在背后默默给我们生成了如下的东西
- equals()/hashCode()
- toString()方法
- componentN()方法《实现解构申明》
- copy()方法
如何申明一个简单的数据类? 有一下几点要求:
-
主构造函数必须要至少有一个参数(就代表了必须要有一个数据字段,hashCode,equals,toString都是要依据主构造函数来生成的)
-
主构造函数中的所有参数必须被标记为val或者var(相当于表明了数据字段的访问权限,这就达到了Java中我们手动生成set get的目的)
-
数据类不能有以下修饰符:abstract,inner,open,sealed
-
data class只能实现接口(Kotlin1.1以前的规则),现在也可以继承其它类
data class User(var id: Int, var name: String)
indices:
inline:
作用:修饰内联函数。
如果被调用的Lambda表达式或函数包含大量的执行代码,那么就不应该使用内联函数;
如果被调用的Lambda表达式或函数只包含非常简单的执行代码,就使用内联函数;
inner:
嵌套类:
如果想让类 B 能访问类 A 的成员,可以加 inner 标记:
class A {
val a: String = "a"
inner class B {
val b: String = a
}
}
访问:
val a = A().B().b//a输出为“a”
internal:
在Kotlin编程中有四种修饰词:private,protected,internal,public,默认的修饰词是public。
- internal 声明,在同一模块中的任何地方可见。
fun main(args: Array<String>) {
Test().test()
}
open class BaseTest{
private val a="该属性不能被子类访问"
protected var b="该属性能被子类访问"
internal var c="同一模块下被访问"
var d="到处都可以被访问"
}
class Test:BaseTest(){
fun test(){
println(b)
println(c)
println(d)
}
}
is:
相当于Java中的 instanceof
lateinit var 延迟初始化属性:
通常,那些被定义为拥有非空类型的属性,都需要在构造器中初始化.但有时候这并没有那么方便.
例如在单元测试中,属性应该通过依赖注入进行初始化, 或者通过一个 setup 方法进行初始化.在这种条件下,你不能在构造器中提供一个非空的初始化语句,但是你仍然希望在访问这个属性的时候,避免非空检查.
为了处理这种情况,你可以为这个属性加上 lateinit 修饰符
注意:lateinit修饰的变量/属性 不能 是原始数据类型。
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method()
}
}
这个修饰符只能够被用在类的 var 类型的可变属性定义中,不能用在构造方法中.并且属性不能有自定义的 getter 和 setter访问器.这个属性的类型必须是非空的,同样也不能为一个基本类型.
在一个延迟初始化的属性初始化前访问他,会导致一个特定异常,告诉你访问的时候值还没有初始化.
初始化前访问一个 lateinit 属性会抛出一个特定异常,该异常明确标识该属性被访问及它没有初始化的事实。
by lazy 懒属性(延迟加载)
val p: String by lazy {
// 生成string的值
}
Kotlin的延迟初始化: lateinit var 和 by lazy
- lateinit 只用于 var,而 lazy 只用于 val
- lateinit var只是让编译期忽略对属性未初始化的检查,后续在哪里以及何时初始化还需要开发者自己决定。
- by lazy真正做到了声明的同时也指定了延迟初始化时的行为,在属性被第一次被使用的时候能自动初始化。
object(用于创建单例模式):
object Resource {
val name = "Name"
}
open 说明可以被继承 :
open 注解与java 中的 final相反:它允许别的类继承这个类。
默认情形下,kotlin 中所有的类都是 final ,用来表示他可以被继承。
所以,父类使用open声明类,不然默认final,无法继承。
修饰类: 说明可以被继承
open class Base(p: Int)
class Derived(p: Int) : Base(p)
修饰成员 : 在 kotlin 中坚持做明确的事。不像 java ,kotlin 需要把可以复写的成员都明确注解出来,并且重写它们
open class Base {
open fun v() {}
fun nv() {}
}
class Derived() : Base() {
override fun v() {}
}
override :
reified:
sealed 密封类:
密封类用于代表严格的类结构,值只能是有限集合中的某中类型,不可以是任何其它类型。这就相当于一个枚举类的扩展:枚举值集合的类型是严格限制的,但每个枚举常量只有一个实例,而密封类的子类可以有包含不同状态的多个实例。
声明密封类需要在 class 前加一个 sealed 修饰符。密封类可以有子类但必须全部嵌套在密封类声明内部.
sealed class Expr {
class Const(val number: Double) : Expr()
class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
}
注意密封类子类的扩展可以在任何地方,不必在密封类声明内部进行。
使用密封类的最主要的的好处体现在你使用 when 表达式。可以确保声明可以覆盖到所有的情形,不需要再使用 else 情形。
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// the `else` clause is not required because we've covered all the cases
}
tailrec(尾递归):
作用:修身尾递归函数。与普通递归相比,编译器会对尾递归进行修改,将其优化成一个快速而高效的基于循环的版本,这样减少内存消耗。
tailrec fun test3(n: Int, total: Int = 1): Int =
if (n == 1)
total
else
test3(n - 1, total * n)
Unit 类型:
如果函数返回 Unit 类型,该返回类型应该省略:
var和val:
var是一个可变变量,这是一个可以通过重新分配来更改为另一个值的变量。这种声明变量的方式和java中声明变量的方式一样。
val是一个只读变量,这种声明变量的方式相当于java中的final变量。一个val创建的时候必须初始化,因为以后不能被改变。
typealias:
定义类型别名。
vararg :
功能:可变参数。一个函数最多只能有一个可变参数。
fun asList<T>(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts)
result.add(t)
return result
}
标记后,允许给函数传递可变长度的参数:
val list = asList(1, 2, 3)
when 用于判断 相当于java中的switch()语句
when分支:
- 不再需要case关键字
- case值后的冒号改为使用箭头(->)
- default改为更有意义、更明确的else
- when分支可以匹配多个值
- when分支可以是任意表达式
- when分支对条件表达式的类型没有任何要求
when(color) {
"Red" -> 0
"Green" -> 1
"Blue" -> {
println("2")
println("3")
}}
else -> throw IllegalArgumentException("Invalid color param value")
}
when分支作为表达式:
fun test(){
var score='B'
val str= when(score){
"A"->"优秀"
"B"->"良好"
"C"->"及格"
else->{
println("继续努力啊")
"不及格"
}
}
}
处理范围(in…),处理类型(is Int,is Double…):
fun test1(){
var score='B'
val str= when(score){
in 80..100->"优秀"
"B"->"良好"
"C"->"及格"
is String->"属于字符串类型"
else->{
println("继续努力啊")
"不及格"
}
}
}
2、注解:
@JvmField
https://www.kotlincn.net/docs/reference/java-to-kotlin-interop.html#%E9%9D%99%E6%80%81%E5%AD%97%E6%AE%B5
静态字段:
在命名对象或伴生对象中声明的 Kotlin 属性会在该命名对象或包含伴生对象的类中具有静态幕后字段。
通常这些字段是私有的,但可以通过以下方式之一暴露出来:
- @JvmField 注解;( public static final 字段)
- lateinit 修饰符;( public static 非-final 字段)
- const 修饰符。(static 字段)
@JVMStatic
https://www.kotlincn.net/docs/reference/java-to-kotlin-interop.html#%E9%9D%99%E6%80%81%E6%96%B9%E6%B3%95
静态方法:
在 “companion object” 中的公共函数必须用使用 @JvmStatic 注解才能暴露为静态方法。
如果使用 @JvmStatic 注解,编译器既会在相应对象的类中生成静态方法,也会在对象自身中生成实例方法。
如果没有这个注解,这些函数仅可用作静态 Companion 字段上的实例方法。
3、作用域函数
Kotlin之let,apply,run,with等函数区别2
Kotlin 标准库包含几个函数,它们的唯一目的是在对象的上下文中执行代码块。当对一个对象调用这样的函数并提供一个 lambda 表达式时,它会形成一个临时作用域。在此作用域中,可以访问该对象而无需其名称。这些函数称为作用域函数。共有以下五种:let、run、with、apply 以及 also。
每个作用域函数之间有两个主要区别:
- 引用上下文对象的方式
- 返回值
函数名 | 定义 | 参数 | 返回值 | extension |
---|---|---|---|---|
let | fun T.let(f: (T) -> R): R = f(this) | it | 闭包返回 | 是 |
apply | fun T.apply(f: T.() -> Unit): T { f(); return this } | 无,可以使用this | this | 是 |
with | fun with(receiver: T, f: T.() -> R): R = receiver.f() | 无,可以使用this | 闭包返回 | 否,调用方式有区别 |
run | fun T.run(f: T.() -> R): R = f() | 无,可以使用this | 闭包返回 | 是 |
let
默认当前这个对象作为闭包的it参数,返回值是函数里面最后一行,或者指定return
apply:
apply函数是这样的,调用某对象的apply函数,在函数范围内,可以任意调用该对象的任意方法,并返回该对象.
apply函数和run函数很像,唯一不同点就是它们各自返回的值不一样,run函数是以闭包形式返回最后一行代码的值,而apply函数的返回的是传入对象的本身。
run:
run函数和apply函数很像,只不过run函数是使用最后一行的返回,apply返回当前自己的对象。
run函数是let,with两个函数结合体,准确来说它弥补了let函数在函数体内必须使用it参数替代对象,在run函数中可以像with函数一样可以省略,直接访问实例的公有属性和方法,另一方面它弥补了with函数传入对象判空问题,在run函数中可以像let函数一样做判空处理。
with:
with函数是一个单独的函数,并不是Kotlin中的extension,所以调用方式有点不一样,返回是最后一行,然后可以直接调用对象的方法,感觉像是let和apply的结合。
also
上下文对象作为 lambda 表达式的参数(it)来访问。 返回值是上下文对面本身。
4、定义类
4.1、属性修饰符
- annotation //注解类
- abstract //抽象类
- final //类不可继承,默认属性
- enum //枚举类
- open //类可继承,类默认是final的
4.2、访问权限修饰符
- private //仅在同一个文件中可见
- protected //同一个文件中或子类可见
- public //所有调用的地方都可见
- internal //同一个模块中可见