kotlin学习总结之一 变量常量

一. 变量

var 与 val.

  • var 用此关键字声明的变量,可以多次重复赋值,可读且可写,相当于Java中普通变量。
  • val 用此关键字声明的变量表示只读变量,即可读但不可写。相当于Java中用final修饰的变量。
var str: String = "" //中,str是变量名,:String,表明该变量是String类型变量,后面就是赋值语句。
var str2= "" //省略了声明变量类型,它可以根据赋的值而自动推断出类型。
str="1234" //var 声明的变量可以重新赋值。

val str3= " "
str3= "123456 " //会报错 val声明的标量无法再次赋值

可空变量和不可空变量.

定义变量时,可在类型后面加一个问号?,表示该变量是可为空的(初始化的时候不需要赋值),不加表示该变量不可为null(初始化时必须要进行赋值)。

        var s:String? = null
        var s2:String = null //如果该变量被赋值为null,则编译不通过
        s = "yyy"//赋值时不需要使用s?形式
        var length= s?.length //如果s为null,则不执行length方法
        var length= s!!.length //如果s为null,则会报错,终止程序执行

对于可以为null的变量,在使用该变量的时候,必须用变量名+?的形式进行调用,表示如果该变量为null,则不执行该变量调用的方法。如上表示当s为null时,不会执行length方法,从而避免了java中频繁的空指针异常。
?和!!的区别
?:表示当前对象是否可以为空,当对象为空时,不会执行后面的代码。
!!: 通知编译器不做非空校验。如果运行时发现变量为空,就抛出异常。

后期初始化与延迟初始化.

在一个Kotlin类中定义一个变量(属性)的时候是必须初始化的,否则就会编辑器就会报错。可是有的时候,并不想声明一个类型可空的对象,而且也没办法在对象一声明的时候就为它初始化,那么这时就需要用到Kotlin提供的延迟初始化。Kotlin中有两种延迟初始化的方式。一种是lateinit var,一种是by lazy

1. lateinit var
  private lateinit var txtView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        txtView = findViewById(R.id.txt_main)
        setText()
    }

    fun setText() {
        txtView.text = "这是一段测试代码"
    }

注意

  • lateinit var只能用来修饰类属性,不能用来修饰局部变量。
  • 必须是可读且可写的变量,即用var声明的变量,不能声明于可空变量。
  • 属性不能拥有自定义的setter与getter。
  • 不能声明于基本数据类型变量(因为基本类型的属性在类加载后的准备阶段都会被初始化为默认值)。例:Int、Float、Double等,注意:String类型是可以的。
  • lateinit的作用就是让编译期在检查时不要因为属性变量未被初始化而报错,所以声明后,在使用该变量前必须赋值,不然会抛出UninitializedPropertyAccessException异常。
2. by lazy

所谓延迟初始化即:指当程序在第一次使用到这个变量(属性)的时候再初始化

//定义变量延迟初始化
 private val aTextView by lazy {
        findViewById(R.id.txt_main)
    }

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setText()
    }
 fun setText() {
        aTextView.text = "这是一段测试代码"
    }
  • 使用lazy{}高阶函数,不能用于类型推断。且该函数在变量的数据类型后面,用by链接。
  • 必须是只读变量,即用val声明的变量。
  • by lazy可以使用于类属性或者局部变量。

那么既然是懒加载,就必然涉及到线程安全的问题,我们看看lazy是怎么解决的:

public actual fun  lazy(initializer: () -> T): Lazy = SynchronizedLazyImpl(initializer)

public actual fun  lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy =
    when (mode) {
        LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
        LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
        LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
    }
    
public actual fun  lazy(lock: Any?, initializer: () -> T): Lazy = SynchronizedLazyImpl(initializer, lock)    

复制代码可以看到这里是根据LazyThreadSafetyMode的字段来分别进行不同的操作。
LazyThreadSafetyMode是一个枚举类

public enum class LazyThreadSafetyMode {

    SYNCHRONIZED,

    PUBLICATION,

    NONE,
}
  • SYNCHRONIZED 通过加锁来确保只有一个线程可以初始化Lazy实例,是线程安全的。

  • PUBLICATION 表示不加锁,可以并发访问多次调用,但只将第一个返回值用作[Lazy]实例的值。这也是线程安全的。

  • NONE 不加锁,是线程不安全的, 如果从多个线程访问实例,可能会有多个实例。除非保证[Lazy]实例永远不会从多个线程初始化,否则不应使用此模式。

在使用by lazy进行初始化的时候的时候,如果不加mode参数的话,它会默认选择执行SynchronizedLazyImpl这个方法,也就是默认线程安全的。也可以通过添加mode参数来指定我们想要的懒加载效果。

    private val aTextView by lazy( LazyThreadSafetyMode.SYNCHRONIZED) {
        findViewById(R.id.txt.more)
    }

延迟加载有几个好处。首先由于加载时机推迟到了变量被访问时,因此它可以提高应用的启动速度。相比于使用 Kotlin 开发服务端应用,这一特性对开发 Android 应用而言特别有用。其次,这样的延迟加载也有更高的内存效率,因为我们只在它被调用时才将资源加载进内存。在 Android 这样的移动平台上,内存的使用是非常重要的,因为手机资源是共享的且有限的。

二.常量。

在Kotlin中,一个var会对应生成两个方法,即getter和setter方法,比如

    var name: String? = null

打开Android studio 点击 Tools->Kotlin->Show Kotlin ByteCode查看生成的字节码会包含如下的两个方法和一个backing field

@Nullable
 private String name;

 @Nullable
 public final String getName() {
    return this.name;
 }

 public final void setName(@Nullable String var1) {
    this.name = var1;
 }

而对于val来说只会生成一个对应的get方法,比如

    val name: Long = "hello"

生成的字节码会包含类似这样的方法

 @NotNull
   private static final String name = "hello";

   @NotNull
   public static final String getName() {
      return name;
   }

val定义常量的坑
在Kotlin中通过val定义的变量,只有get方法,没有set方法,所以只能读不能写。
但是其get方法可以复写,从而造成val定义的变量,有可能会发生值变化,情况下面的例子:

 val A : Int 
    get()  {
        val rand = Random(System.currentTimeMillis())
        return rand.nextInt()
    }

val定义的常量A的get()方法被复写,每次调用常量A都会返回一个随机数,所以不能保证常量A的值不变。

如何才能生成真正的常量?

方法有两种,一种是const,另一个使用@JvmField注解

const的使用

在Kotlin中除了val关键字定义一个常量外,还提供了一个const关键字标识一个常量.
const修饰的val变量相当于java中 static final是真正意义上的java常量,不过仅限于在top-level和object中。

//top-level
const val THOUSAND = 1000 

//object中
object myObject {
    const val constNameObject: String = "constNameObject"
}
class MyClass {
    //object中
    companion object Factory {
        const val constNameCompanionObject: String = "constNameCompanionObject"
    }
}
  • const 必须修饰val
  • const 只允许在top-level级别和object中声明。
  • const val 可见性为public final static,可以直接访问。
@JvmField

在val常量前面增加一个@JvmField就可以将它变成常量。其内部作用是抑制编译器生成相应的getter方法.是用该注解修饰后则无法重写val的get方法。

@JvmField val NAME = "张三"

三 数据类型

Kotlin基本数据类型 Java基本数据类型 Java包装类
Int int java.lang.Integer
Long long java.lang.Long
Float float java.lang.Float
Double double java.lang.Double
Short short java.lang.Short
Char char java.lang.Character
Byte byte java.lang.Byte
Boolean boolean java.lang.Boolean
数字类型Number。
  • Float(32位)
  • Double(64)
  • Int(32)
  • Byte(8)
  • Short(16)
  • Long(64,类型用大写L,如12L)
   var a: Byte = 1
    var b: Short = 1
    var c: Int = 5
    var d: Long = 1L
    var e: Float = 1.555F
    var f: Double = 1.555

kotlin支持二进制、十进制、十六进制; 注意: 不支持八进制(而java以0开头表示八进制 07)

自 Kotlin 1.1 起,可以用下划线使数字更易读:

    val oneMillion = 1_000_000
    val creditCardNumber = 1234_5678_9012_3456L
    val socialSecurityNumber = 999_99_9999L
    val hexBytes = 0xFF_EC_DE_5E
    val bytes = 0b11010010_01101001_10010100_10010010

装箱问题
在kotlin中的基本数据类型的看起来就是java的装箱类型,在这里我们需要注意,kotlin中已经没有来装箱类型,区分装箱还是基本类型,kotlin已经交给了编译器去处理。

类型转换问题
java中可以将声明的int类型赋值给Long类型(java会自动拆箱装箱)即常说的隐式转换,但是kotlin并不可以,必须进行类型转换(显式转换)
如(隐式转换):

val anInt: Int = 5
val aLong: Long = anInt

则无法变异通过,需改成(显示转换):

 val anInt: Int = 5
 val aLong: Long = anInt.toLong()

每个数字类型支持如下类型转换:

    toByte(): Byte
    toShort(): Short
    toInt(): Int
    toLong(): Long
    toFloat(): Float
    toDouble(): Double
    toChar(): Char

== 与 ===
kotlin中并没有单纯的数值,在我们定义每一个数值的时候,kotlin编译器都在默默的为我们将定义的数值封装成一个对象,以最大限度的解决空指针异常的问题。因此kotlin中数值的比较除了我们常见的 == 号之外还多了 ===

java kotlin 说明
equal == 【结构相等】比较两个具体的【数值的大小】是否相同
== === 【引用相等】比较两个对象【在内存的存储地址】是否相同

例如:

    val a : Int = 10//Kotlin并不会自动装箱
    println(a === a) //此处返回的值为true
    val b : Int = a
    println(a === b) //此处返回的值为true,
    println(a == b) //此处返回的值为true.
布尔类型(Boolean)

** Boolean关键字表示布尔类型,并且其值有true和false.**

var isNum: Boolean
isNum = false
println("isNum => $isNum")//输出结果为false

逻辑操作符(与Java相同)

  • ' || ' => 逻辑或(或者)
  • ' && ' => 逻辑与(并且)
  • ' ! ' => 逻辑非(取反)
字符型(Char)

这里需要注意一点的是:

与java不同,kotlin中Char类型仅仅表示字符,不能再被直接当做数字。 因此,Char类型的变量必须在单引号之间表示:’变量’。
java代码如下:

char a = 'c';
System.out.println(a+1);

此时打印的结果为【数字】100。
在kotlin中:

val a : Char = 'c'
println(a+1)

上述代码的打印结果为【字符】d

因此我们要明确:Kotlin中 Char类型由于不是直接当数值用,所以最后返回依旧是Char型。

字符串.

与java类似,kotlin中的String同样是由final修饰的,即不可被继承。
字符串模版
字符串可以包含字符串模板用来表示前面用的到的一段代码。kotlin中规定,所有的字
符串模板必须以美元符号”$”开头。

        val a = "temp"
        val q = "$a.length():"
        println(q+a.length)//输出结果为temp.length():4

用for循环迭代字符串.

for (s in "12345"){println(s)}

你可能感兴趣的:(kotlin学习总结之一 变量常量)