3322网建站,网站建设的单可以刷吗,网店交易平台网站建设,ngo网页模板下载文章目录 Kotlin的特性Kotlin优势Kotlin的安卓项目变量变量保存了指向对象的引用优先使用val来避免副作用 后端变量Backing Fields延迟初始化 Kotlin的特性
它更加易表现#xff1a;这是它最重要的优点之一。你可以编写少得多的代码。Kotlin是一种兼容Java的语言Kotlin比Java… 文章目录 Kotlin的特性Kotlin优势Kotlin的安卓项目变量变量保存了指向对象的引用优先使用val来避免副作用 后端变量Backing Fields延迟初始化 Kotlin的特性
它更加易表现这是它最重要的优点之一。你可以编写少得多的代码。Kotlin是一种兼容Java的语言Kotlin比Java更安全能够静态检测常见的陷阱。如:引用空指针Kotlin比Java更简洁通过支持variable type inferencehigher-order functions (closures)extension functionsmixins 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;}Overridepublic 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解释成variablefinal即通过val声明的变量具有Java中的final关键字的 效果(我们通过查看对val语法反编译后转化的java代码从中可以很清楚的发现它是用final实现的)也就是引用不可变。 因此val声明的变量是只读变量它的引用不可更改但并不代表其引用对象也不可变。事实上我们依然可以修改引用对象的可变成员。
声明:
var age: Int 18val 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变量保存了指向对象的引用
当该对象被赋值给变量时这个对象本身并不会被直接赋值给当前的变量。相反该对象的引用会被赋值给该变量。 因为当前的变量存储的是对象的引用因此它可以访问该对象。
如果你使用val来声明一个变量那么该变量所存储的对象的引用将不可修改。然而如果你使用var声明了一个变量你可以对该变量重新赋值。 例如如果我们使用代码 x 6将x的值赋为6此时会创建一个值为6的新Int对象并且x会存放该对象的引用。下面新的引用会替代原有的引用值被存放在x中 注意: 在Java中数字类型是原生类型所以变量存储的是实际数值。但是在Kotlin中的数字也是对象而变量仅仅存储该数字对象的引用并非对象本身。
优先使用val来避免副作用
在很多Kotlin的学习资料中都会传递一个原则优先使用val来声明变量。这相当正确但更好的理解可以是尽可能采用val、不可变对象及纯函数来设计程序。 关于纯函数的概念其实就是没有副作用的函数具备引用透明性。
简单来说副作用就是修改了某处的某些东西比如说
修改了外部变量的值IO操作如写数据到磁盘UI操作如修改了一个按钮的可操作状态
后端变量Backing Fields
Kotlin会默认创建set get方法我们也可以自定义get set方法: 在kotlin的getter和setter是不允许调用本身的局部变量的因为属性的调用也是对get的调用因此会产生递归造成内存溢出。 例如:
var count 1
var size: Int 2set(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 1var size: Int 2set(value) {field if (value 10) 15 else 0}get() {return if (field 15) 1 else 0}
}如果我们不手动写getter和setter方法编译器会在编译代码时添加以下代码段
var myProperty: Stringget() fieldset(value) {field value}这意味着无论何时当你使用点操作符来获取或设置属性值时实际上你总是调用了属性的getter或是setter。那么为什么编译器要这么做呢 为属性添加getter和setter意味着有访问该属性的标准方法。getter处理获取值的所有请求而setter处理所有属性值设置的请求。 因此如果你想要改变处理这些请求的方式你可以在不破坏任何人代码的前提下进行。通过将其包装在getter和setter中来输出对属性的直接访问称为数据隐藏。
延迟初始化
在类内声明的属性必须初始化如果设置非null的属性应该将此属性在构造器内进行初始化。 假如想在类内声明一个null属性在需要时再进行初始化最典型的就是懒汉式单例模式这就与Kotlin的规则是相背的此时我们可以声明一个属性并 延迟其初始化此属性用lateinit修饰符修饰。
class MainActivity : AppCompatActivity() {lateinit var name : Stringoverride 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: MyServicefun main(){// 如果 myService 对象还未初始化则进行初始化if(!::myService.isInitialized){println(hha)myService MyService()}}
}注意: ::myService.isInitialized可用于判断adapter变量是否已经初始化。虽然语法看上去有点奇怪但这是固定的写法。::前缀不能省。::是一个引用运算符一般用于反射相关的操作中可以引用属性或者函数。 这里可以写成::myService.isInitialized或this::myService.isInitialized。 如果在listener或者内部类中可以这样写thisOuterClassName::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那lateinit和by 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? nullget() {if (field null) {field Fragment()}return field}当您稍后需要在代码中初始化var时请选择lateinit它将被重新分配。当您想要初始化一个val值一次时特别是当初始化的计算量很大时请选择by lazy。