目录
1.如何在Android Studio中运行Kotlin代码
11.Kotlin代码中调用Java方法,当被调用的方法接收了一个Java单抽象接口参数时,Kotlin是怎么简化的
12.Kotlin中在避免空指针异常上,与Java有什么不一样。
第一章
这一章作者带着我创建了我的第一个安卓程序——HelloWorld。简要地描述了Android系统的四层架构,Android Studio中安卓项目的项目目录结构以及日志工具的使用。
在阅读的过程中,通过自己翻阅xml语言的相关资料,加深了对这一章中出现的各种xml文件的理解。
这一章中我认为需要记忆的点有以下几个:
1.注册Activity
<activity android:name=".MainActivity">
<intent-filter>
<action android:name = "android.intent.action.MAIN"/>
<category android:name = "android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
2.Android程序的设计讲究逻辑和视图分离
具体体现在:布局文件中编写界面,而后通过Activity引入
setContentView(R.layout.activity_main)
3.res文件夹中的资源引用
代码中:R.xx.xxxx
XML中:@xx/xxxx
xx为res下属目录文件夹名,xxxx为具体资源名
4.日志工具类andriod.util.Log
Log.v() verbose级别 最低级、最琐碎的日志信息
Log.d() debug级别 调试程序用
Log.i() info级别 分析用户行为用
Log.w() warn级别
Log.e() error级别
第二章
本章主要通过Java和Kotlin两种语言之间的联系,对比地学习了Kotlin语言的基本语法。
Java语言是一种解释型语言。解释型语言是一行行代码一边编译成机器语言,一边运行代码的语言。因此,解释型语言的效率通常会差一些,不过因为解释器的存在,它的跨平台性会好一些。但是为什么编译型语言也有编译器存在,而其跨平台性却要差一些呢?编译器跟解释器的工作原理有什么不同吗,是哪些关键步骤导致了二者在跨平台性上的差别?此处留下这些问题,待以后解答,目前的主要任务是尽快学会这本书并作出一个我想做的APP。
前面说到JAVA是一种解释型语言,它首先通过javac命令预编译成.class文件,而后通过JVM处理.class文件。JVM在此处充当了解释器的作用。而Kotlin语言是在Java的基础上修改的,它可以很好的简化代码,减少开发代码量。为什么说Kotlin是在JAVA语言上修改的呢?因为Kotlin语言源代码.kt文件同样需要预编译成.class文件,而后利用JVM进行解释。因此其与JAVA的联系是十分紧密的。
那么接下来就总结一下第二章讲得一些知识点吧。
1.如何在Android Studio中运行Kotlin代码
在项目源文件目录中创建一个file文件(生成后可以注意到这是一个.kt文件),而后编写一个main函数,会发现main函数头左边会出现一个运行标志的小箭头,单击该标志选择第一个选项即可运行。
2.Kotlin中如何定义变量,与JAVA语言有何区别
Kotlin中声明变量的关键字仅有两个,分别是val、var,举例使用如下:
fun main(){
val value = 10 //val声明的是常量,其值不可变化,对应JAVA的final变量
var variable = ‘a’ //var声明的是变量,其值可变
}//注意,Kotlin是不采用分号分隔语句的
学过Java语言的同学都知道,Java中声明不同的数据类型需要使用不同的关键字,其数量远远不止2个。而Kotlin中仅有两个关键字,那编译器如何知道你声明的是什么数据类型呢?
编译器可以通过两种方式知道你所声明的值是什么类型。第一种是通过Kotlin的类型自动推导机制,这适用于变量在声明时就被初始化的情况,如上面的代码段所示。还有一个问题就是,当将变量初始化为一个数字的时候,这个数字类型会是Int、Long、还是Double呢?具体各种数据类型表示,留到之后的when关键字进行学习实验。第二种则需要显式地声明变量类型,适用于变量声明时未被初始化的情况,如下面的代码所示
fun main(){
val value:Int = 10
var variable:Char
}
/******************************************************************
·可以做几个实验加深对变量声明的语法理解:
1.是否由val声明的变量一定要初始化
答:不一定要初始化,但是不初始化一定要显式声明类型。
2.如果变量声明后一直没被赋值,代码能否编译成功,程序能否正常运行结束?
答:可以正常运行,但是会提示变量未被使用。
3.在声明时不初始化变量,同时不显式声明类型,之后下一行代码处赋值,是否能编译通过且正常运行。
答:无法编译通过,这种情况是延时赋值的情况。
******************************************************************/
可以看到显式声明数据类型的语法结构,与之后讲的接口实现的结构十分的相似。因此是否可以大胆的猜测一下,Kotlin中的数据类型都被写出了接口的形式?不过作者说是对象数据类型,具体的验证还需要以后去查阅Kotlin的源代码才能知道。
为什么Kotlin要把具体数据类型的声明分为两种方式呢?我认为首先是平时的开发工作中变量延迟赋值的情况比较少,利用类型自动推导机制可以大大简化编码量,提高开发效率。同时还能引导程序员养成在声明变量的同时初始化的习惯,我记得在学习C语言时有提到,如果一个变量在声明时未进行初始化,那么其值并非不存在,它会是一个无法预知的值,取决于上一个使用过该变量分配的内存地址上的内存空间的程序,因此可能会导致一些莫名其妙的bug出来。
另外此处提到了一个编码习惯,如果不是变量明确应该被修改,则使用val来声明变量。
3.Kotlin中的函数与JAVA相比有什么特点?
1.Kotlin中的函数,将函数返回值类型声明放到了函数名后面,中间用冒号连接。同时需要使用关键字fun声明,而JAVA语言则不需要。
Kotlin函数返回值的声明,有点类似于变量声明的形式,变量有自动类型推导机制,函数返回值也有自动类型推导机制,同样这种自动类型推导也不是所有情况都可以起作用的。当且仅当函数只有一行代码,并且采用了简化形式(将改行代码写在函数名后面,中间用 等号连接)才可以起作用。
2.Kotlin函数参数不用val、var关键字声明,如果使用了编译无法通过。
此处可以跟类的定义中的主构造函数做对比,类的主构造函数参数,如果使用了val、var关键字进行声明,则声明的变量会成为类的字段。
对此我的理解是,函数参数中不使用var、val声明,则表明这个变量仅作为局部变量之用。
笔记整理到此处,我突然想到了JAVA中的静态变量,Kotlin中又是如何解决的呢?看来在看完这本书之后,有必要再系统地学习一下Kotlin语言。
3.Kotlin中的函数当只有一行代码的时候,有简化的写法。写到这里,突然想到了JAVA中的inline函数 - - 查询资料发现是inline内联函数是C++中的关键字,跟这个简化没啥关系哈哈哈哈
言归正传,当函数只有一行代码时,Kotlin可以进行如下简化:
fun largeNum(num1:Int,num2:Int):Int{
return max(num1,num2)
}
fun largeNum(num1:Int, num2:Int):Int = max(num1,num2)//函数只有一行代码,可以使用Kotlin中的代码糖
fun largeNum(num1:Int, num2:Int) = max (num1, num2)//这种形式可以触发自动类型推导机制,因此返回值声明可以省略
4.Kotlin的逻辑控制语句与JAVA相比有什么不同?
1.Kotlin中的逻辑运算是有返回值的,其返回值是所执行条件的最后一行代码。
2.Kotlin中多了一个关键词when
/*带参when*/
fun getScore(name:String) = when(name){
"Tom" -> {86}//基本格式为“匹配值 -> {执行逻辑}”
"Jim" -> 77 //只有一行代码,括号可省略
"Jack"-> 95
else -> 0
}
/*when结构语句允许进行类型匹配*/
fun checkNum(num:Number){
when(num){
is Int -> println("num is Int")//is关键字相当于JAVA中的instanceof
is Double -> println("num is Double")
else -> println("other")
}
}
/*不带参的when结构语句,方便进行更多样的匹配*/
fun getScore(name:String) = when{
name == "Tom" -> 86 //Kotlin中判断对象是否相等可以直接用等号
name.startWith("Tom") -> 77
name == "Jack" -> 94
else -> 0
}
3.循环语句中有两种类型 for 、while,其中while循环与JAVA相差不多。for循环则进行了大改,采用了区间的形式。区间的表示用到了1个符号‘..’,2个关键字until、downTo
for(i in 0..10){
println(i)
}//[0,10]递增1
for(i in 0 until 10 step 2){
println(i)
}//[0,10)递增2
for(i in 10 downTo 0){
println(i)
}//[0,10]区间从10开始递减1
5.Kotlin中类、接口与Java有何异同
在类的定义上,主要的不同在于构造函数。Kotlin的构造函数分为主构造函数和次构造函数。主构造函数不显式地写出函数体,其默认逻辑为将函数参数中的值赋给类中字段,要为构造函数编写其他逻辑则需要用到关键字init。次构造函数用constructor作为函数名,同时要求其必须调用主构造函数,形如constructor():this(){}。另外,Kotlin允许只定义次构造函数,不定义主构造函数,这种情况更类似于Java的构造函数写法,形如constructor():super(){},同时在类的头部继承父类时,父类没有括号(这是十分合理的,括号表示冒号前的主体调用了父类的构造函数,而只有函数才能调用函数)。
Kotlin中的一般函数是不可以使用val或者var声明变量的,而主构造函数却可以,在主构造函数中声明var或者val,会将其作为类的字段。
在类的继承以及接口的上,Kotlin使用符号‘:’表示继承与实现关系,然后通过是否带括号来区分是接口的实现还是类的继承(类的继承中,子类需要调用父类的构造函数。带括号的实质就是调用父类的构造函数,因此括号中是可以带参数的。这是一种杂糅的方法,虽然看起来有点奇怪,但是又不影响其表达其主要意思,而代码量却大大减少)。
还有就是Kotlin中的类都是默认不可继承的,要用关键字open声明,才能使类可以被继承。
另外Kotlin类的实例化去掉了new关键字。
以下这段代码有用到类、接口、主次构造函数、字符串内嵌表达式以及为函数参数提供默认值。
open class Person(val name:String, val age:Int, val sex:String){
init{
println("此人名$name,年方$age,是一个${sex}人")
}
}
interface Study{
fun readBooks()
fun doHomework(){
println("The person is doing homework")
}//这是接口中的默认实现,可以不被强制要求实现
}
class Student(val sno:String, name:String, age:Int, sex:String):Person(name, age, sex), Study{
init{
println("Student类的主构造函数被调用")
}
constructor(name:String, age:Int, sex:String):this("123456",name, age, sex){
println("构造参数被调用")
}//主次构造函数一起使用的目的主要是为了给主构造函数提供默认值,但其实有更好地方式去提供
override fun readBooks(){
println("$name is reading books.")
}
}
/*只有次构造函数的情形*/
class Teacher:Person{
constructor(name:String = "佚名", age:Int, sex:String):super(name, age, sex){
println("${name}是一个教师")
}
}
fun main(){
val defaultSno = Student("张三",18,"男")
val diySno = Student("789542","李四",17,"女")
val defaultName = Teacher(age = 45, sex = "男")
val diyName = Teacher("王五",55, sex = "女")
doStudy(diySno)
}
fun doStudy(study:Study){
study.readBooks()
study.doHomework()
}
6.Kotlin与Java在可见性修饰符上的异同
修饰符 | Java | Kotlin |
---|---|---|
public | 所有类可见 | 所有类可见(默认) |
private | 当前类可见 | 当前类可见 |
protected | 同一包路径下的类可见、当前类、子类可见 | 当前类、子类可见 |
default | 同一包路径下的类可见 | / |
internal | / | 同一模块中可见 |
7.数据类
当用data声明一个类时,Kotlin会自动根据主构造函数的参数生成equals()、hashCode()、toString()等固定且无实际逻辑意义的函数,从而减少开发工作量。
8.单例类
单例类只能有一个实例,声明单例类,用object代替class声明。
其调用类似于Java 中静态方法的调用。
9.Kotlin中集合的使用
listOf()用来创建不可变的List集合;mutableListOf()用来创建一个可变的List集合。
setOf()用来创建不可变的Set集合;mutableSetOf()用来创建一个可变的Set集合。
Set集合底层用hash映射机制,因此其中元素无法保证有序。
Set集合与List集合的遍历都采用for-in机制:
for(element in list){
}
Map集合的使用:
fun main(){
val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4)//Kotlin同样提供了mutableMapOf()用于创建可修改的Map集合
for ((fruit,number) in map){
println("fruit is $fruit, number is $number")
}
}
Kotlin的集合有一些使用Lambda表达式作为参数的API,如maxBy()、toUpperCase()等。Kotlin的Lambda表达式结构为:{ 参数列表 -> 函数体}
Lambda表达式作为函数参数的时候,代码有很多可以简化的地方:
//未经简化
val maxLengthFruit = list.maxBy({fruit:String -> fruit.length}
//Lambda表达式是函数的最后一个参数,大括号可以写在括号外面
val maxLengthFruit = list.maxBy(){fruit:String -> fruit.length}
//Lambda表达式是函数的唯一参数,括号可以省略
val maxLengthFruit = list.maxBy{fruit:String -> fruit.length}
//Lambda表达式只有一个参数,不必声明参数名,用it关键字来代替
val maxLengthFruit = list.maxBy{ it.length }
文中提到的函数式API这个概念,我的理解是,API提供的参数中有一个或以上的参数是函数。而Lambda表达式本质上是一个函数。查看maxBy的源代码没看懂,插个眼,以后再看。
11.Kotlin代码中调用Java方法,当被调用的方法接收了一个Java单抽象接口参数时,Kotlin是怎么简化的
Java单抽象接口,是指接口中有且仅有一个待实现方法。Java中最常见的单抽象接口莫过于Runnable接口,只有一个待实现的run()方法。简化前后对比如下
//简化前
Thread(object:Runnable{
override fun run(){
//run方法实现逻辑
}
}).start()
//简化后
Thread{
//run方法实现逻辑
}.start()
简化前的代码的一个猜测理解:前面提到单例类的时候,使用的关键字也是object,会自动实现一个实例。因此在此处使用object,跟单例类也是有异曲同工之妙,同样是声明类而后自动实现一个类实例。因为这里是匿名类,也就不需要类名,所以直接在object关键字后实现接口。
12.Kotlin中在避免空指针异常上,与Java有什么不一样。
首先,Kotlin默认所有的参数和变量都不可以为空。也就是将空指针异常的检查提前到了编译时期。
当我们需要变量为空时,则需要使用到Kotlin提供的可空类型系统,在使用时需要将所有潜在的空指针异常都处理掉。
Int? ,类似这样的结构,在数据类型后面加一个问号,就表示该数据类型是可以为空的。
至于如何处理潜在异常,Kotlin提供了一些方便的判空辅助工具。
?.操作符:当且仅当调用主体不为空时,才执行方法调用
a?.doSomething()
相当于
if( a != null){
a.doSomething()
}
?:操作符:a?:b 如果左边为空,返回右边;否则返回左边。
val c = a?:b
相当于
val c = if( a != null) a else b
!!变量后缀,表示该变量一定非空,一般用于强行通过编译,用于解决函数之间判空逻辑一般不沟通导致的编译无法通过。
let(Lambda)函数,用于解决同一个对象多次调用其方法,每次调用都要判空的情况。let()的作用是将对象本身作为参数传递给Lambda表达式,其参数有且仅有一个Lambda表达式,因此可以进行简化。使用示例如下:
fun doStudy(study: Study?){
study?.doHomework()
study?.readBooks()
}
//使用let函数,避免多次判空
fun doStudy(study: Study?){
study?.let{
it.doHomework()
it.readBooks()
}
}
需要注意的是,处理全局变量时,if语句无法代替let函数。因为采用let函数,需要将一整个代码块执行完毕才能执行其他代码。也就是说在let函数的代码块执行完毕前,全局变量不会被其他函数修改。
第三章
本章学习Android四大组件之一的Activity。
最好的学习过程莫过于自己动手做一遍。本章作者带着创建了一个不带Activity的项目,用于与Activity相关的知识点的实验。
本章主要的知识点如下:
1.什么是Activity
一种可以包含用户界面的组件,主要用于和用户进行交互。Activity中可以引用页面布局、菜单等资源文件,并编写相关的交互代码。
2.以Button控件为例,大概展示了layout中控件的用法
layout控件有两种用法,一种是可视化界面中进行拖动,一种是编写xml代码。而后在Activity中使用setContentView()引用布局,布局中的控件id因为Kotlin中的JavaBean语法糖的存在可以直接使用。
3.创建了菜单文件,并在Activity 中引用。P93-P95
在res文件夹下创建menu文件,之后创建Menu Resource File文件。在xml中使用item标签添加菜单项。在Activity中重写方法onCreateOptionsMenu(),并返回true以显示出来。采用when结构给菜单项添加响应事件。
4.如何销毁一个Activity
在控件响应代码中添加语句finish()
5.Toast是什么
按钮响应消息,在屏幕上显示一小段时间即消失。
6.什么是Intent
Intent用于不同的组件之间进行交互。其使用方式分为显式调用和隐式调用两种。
显式调用:通过构造方法,制定需要打开的具体组件,而后通过组件自带的函数,如startActivtiy(intent)执行交互。
隐式调用:在AndroidManifest.xml中注册组件时,制定相应的<intent-filter>过滤规则。intent-filter可以制定三种不同的响应规则:action、category、data。隐式调用可以进行不同程序之间的交互。