目录:
一.环境搭建
二.基础语法
1.函数定义
2.可变参数
3.定义常量与变量
4.NULL检查机制
5.类型转换
三.基本类型
1.数字型,Number(包含整数和浮点等)
2.布尔型(Boolean)
3.数组
4.字符串
5.判断两个对象相等
四.条件语句
1.If
2.When
五.循环语句
1.for
2.While和do while
3.返回和跳转 (continue return break)
4.标签
六.类和对象
1.类声明
2.创建类
3.类构造器
4.抽象类
5.嵌套类
6.内部类
7.类的修饰符
8.类中属性的set,get方法
七.继承
1.kotlin的超类
2.继承的基本实现
3.对构造函数的处理
4.属性重写
八. 接口
九. 泛型
十. 枚举
十一.数据类
十二.密封类
一. 环境搭建(基于android studio 3.0):
- 安装kotlin插件:
路径:file---setting---plugins—Browers Repositories--输入框输入kotlin--点击install(as3.0默认安装) - 项目gradle中加入
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version
buildscript {
ext.kotlin_version = '1.1.51'
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
- module的gradle中加入
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
dependencies之中加入:compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
整体如下
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 26
defaultConfig {
applicationId "kotlin.com.kotlindemo"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
二. 基础语法
1. 函数定义:
- <作用域> fun <函数名>(<参数>):<返回参数>{ 函数主体 }
- 例子:
private fun sum(a : Int , b : Int) : Int{
return a+b
}
- 表达是作为函数主体,返回类型自动推断:
例子:
public fun sum(a : Int , b : Int) = a + b
2.可变参数:
函数的变长参数可以用 vararg 关键字进行标识:
例子:
fun vars(vararg v : Int){
for ( vt in v){
print(vt)
}
}
3. 定义常量与变量:
var <标识符> :<类型> = <初始化值>
val <标识符> :<类型> = <初始化值>
var : 代表当前参数为可变参数
val : 代表当前参数为不可变参数,只可以进行一次赋值。类似于java中的final
例子:
val a : Int = 0
val b = 0
var c : Int = 0
c += 1
4. NULL检查机制:
相比java,kotlin在创建变量和传入变量的时候,就提供了可空相关的标识符
- ? :?在标识符后使用,代表对象可为空,如果当前对象为空,不会抛出空指针异常。而是会返回null
- !! :代表当前变量或常量一定不能为空。如果为空,就抛出空指正异常
- 例子:
// 可为空
var age : String? = null
// 可为空,如果age为空,返回null
var age1 = age?.toInt()
// 不能为空
var age2 = age!!.toInt()
5. 类型转换
- Java中,判断类型使用instanceof
- Kotlin中,使用is
fun getStringLength(obj : Any): Int? {
if(obj is String){
// 进行is判断以后,会自动转化成obj
return obj.length
}
if(obj !is String){
return null
}
return null
}
三. 基本类型
1. 数字型,Number(包含整数和浮点等)
- Byte(8位) 、Short(16位)、Int(32位)、Long(64位)、Float(32位)、Double(64位)
- 表现形式:
十进制:123 十六进制:0x123 二进制:0b123 长整型:123L 备注:不支持八进制 - 类型转换:
所有的数字类型都支持以下的方法进行类型转换:
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char - 例子:
//正确的例子:
val b: Byte = 1
val i: Int = b.toInt()
//错误的例子:
val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b // 错误
2. 布尔型(Boolean)
和java一致,不做赘述
3. 数组
创建数组的两种方式:
var intArr : IntArray = intArrayof(0,1,2)
var intArr2 : Array<Int> = arrayof(0,1,2)
4. 字符串
- 定义
var str : String = “str” - striing转int
Java : var strToInt : Int = Integer.parseInt(str)
Kotlin : var strToInt : Int = str.toInt()
- int 转 String
var int : Int = 0
str = int.toString();
- 对于字符串的substring, indexOf, replace等方法,和原方法完全一致。
下面专门介绍几个Kotlin新增和修改的字符串使用方法- Split方法,相比java,kotlin的split方法更灵活,直接返回List集合
例子:Java : char[] charStr = str.split()
Kotlin: val listString : List<String> = str.split(".") - 因为Kotlin允许直接通过下标访问指定位置的字符,所以如果想要获取当前字符。串一个位置的值,只用以下两种方法即可
例子:
var str : String = “str” var s : String = str[i].toString() s = str.get(0).toString()
- Split方法,相比java,kotlin的split方法更灵活,直接返回List集合
- kotlin字符串拼接
Java在字符串拼接这一块,几乎每次都要使用+去进行处理。不易于阅读,同时编写复杂。
Kotlin在这一块进行了改进,很大程度上为开发者节省了大量时间。var moneySum : Int = 5 var str : String = “今天捡到${moneySum}块钱” str = “字符串长度为${str.length}”
5. 判断两个对象相等
- Java中,判断两个对象的地址,或者两个对象是否相等,分别使用equals和==。
- kotlin中,稍微做了一点改动,kotlin中的==等同于java中的equals,kotlin中的===等同于java中的==。
四. 条件语句
1. If:
- if的使用和java上基本相同。这里强调一个if在kotlin之中的扩展功能。
扩展功能:if条件语句可以支持直接返回对象。具体例子如下: - 例子:
val x = 5
val y = 6
val a = if(x > y) 5 else 6
2. When:
着重强调一下when。在kotlin之中,大家会发现找不到switch了。因为switch被when给替代了。可想而知,when的地位有多高了吧。下面介绍一下when的用法。
- 基本用法:when 将它的参数和所有的分支条件顺序比较,直到某个分支满足条件。when 既可以被当做表达式使用也可以被当做语句使用。如果它被当做表达式,符合条件的分支的值就是整个表达式的值,如果当做语句使用, 则忽略个别分支的值。
- 类似于switch的用法:
我们知道switch只能制定一种特定类型,并且相对于每一个case的条件判断只能是相等。但是对于when,同样拥有可配置的case。但是case的判断条件完全由自己去控制。相比switch,极大得增加了扩展性。 - 例子:
fun whenFun(x : Int){ when(x){ 1-> print("1") 2-> print("2") in 1..4 -> print("在1和4之间") 4,5->print("4或者5") else -> print("none") // else类似switch中的default } }
五. 循环语句
1. for:
for循环可以便利所有实现迭代器(iterator)的对象,介绍几种实现方式
- 最基本,类似java中的foreach :
for(item in collection){
print(item)
}
}
- 便利的时候,仅获取索引,实现如下:
for(i in array.indices){
array[i] // i是索引值
}
- 便利的时候,同时获取索引和key
for((index,value) in array.withIndex()){
value = array[index]
}
2. While和do while
实现和java完全一致,不做赘述。
例子:
while( 布尔表达式 ) {
//循环内容
}
do {
//代码语句
}while(布尔表达式);
3. 返回和跳转 (continue return break)
使用方式和java完全一致,简单介绍
- return。默认从最直接包围它的函数或者匿名函数返回。
- break。终止最直接包围它的循环。
- continue。继续下一次最直接包围它的循环。
4. 标签
好了,本节重头戏来了。标签是一个非常好用的东西。他可以让我们在多循环嵌套的情况下,进行穿插处理。不会像java一样,continue,break只能针对当前循环。那么让我们来体验下标签吧。
- 标签介绍:
在 Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 @ 符号,例如:abc@、fooBar@都是有效的标签。 要为一个表达式加标签,我们只要在其前加标签即可。 - 例子
fun labelDemo(){
val intArr = intArrayOf(0,1,2)
val intArr2 = intArrayOf(0,1,2)
Log.i(TAG,"start for")
intArrFor@for (int in intArr){
Log.i(TAG,"for intArr int : $int")
intArr2For@for (int2 in intArr2){
// 每一次退出,回到intArrFor
if(int2 == 1) break@intArrFor
Log.i(TAG,"for intArr2 int2 : $int2")
}
}
Log.i(TAG,"end for")
}
备注:暂时缺失对标签在return应用的介绍,讲真,小编自己还没搞明白。搞明白以后再加上来。
六. 类和对象
1. 类声明
class <类名称> {
// 函数
fun foo(){}
}
}
2. 创建类
class Student{
val name = “小明”
val age = 5
}
// 创建类
val stu = Student()
stu.name
stu.age
3. 类构造器
kotlin中类分成主构造器和次构造器。一个类可以有主构造器和多个次构造器。
- 主构造器:
主构造器不包含任何初始化代码,只有请求参数,如下
class Preson constructor(name : String , age : Int){
}
如果主构造器没有任何注解,也没有任何可修饰符,那么constructor可以隐藏
class Person(name : String , age : Int){
}
- 次构造器:
- 没有主构造器的情况
class ClassSample{ var age = 0 constructor(name: String , age : Int) { this.age = age } }
- 有主构造器的情况
class ClassSample(name : String) { var age = 0 constructor(name: String , age : Int) : this(name){ this.age = age } }
- 对于需要初始化的参数,我们使用关键词init进行处理,如下
class Person (name : String){
val personName : String = name
init{
personName = name
}
}
4. 抽象类
- open关键字,所有类和方法默认都是final。所以如果需要当前类可继承,方法可覆盖,需要将当前方法和类加上open。抽象类默认加上open,不需要再加一遍
- 例子:
open class Base(){
open fun baseFun(){}
}
abstract class Child() : Base(){
abstract fun baseFun(){}
}
5. 嵌套类
嵌套类引用不了外部类的成员,这和Java类似,相当于隐式的外部类。但是在实现上和java略有差异。
java:通过在内部类中加上一个static,说明这是嵌套类。
kotlin:在类的内部再创建一个类的时候,如果加任何修饰符,那么就是嵌套类。
例子
class Outer{
val a = “”
// 嵌套类
class Inner{
fun foo(){
}
}
}
6. 内部类
kotlin内部类需要使用关键字inner来表示。相比嵌套类,内部类可以使用外部类的引用。从而访问外部类的属性和方法等。
例子
class Outer{
val a = ""
/**
* inner标记一个类是内部类
* 可以引用外部类的成员
* 采用this@类名方式,获取到外部类的this对象
*/
inner class inner{
fun foo(){
val innerA = this@Outer.a
}
}
}
7. 类的修饰符
- 权限修饰符
- public 所有情况都可以访问
- internal 针对同一模块
- protected 包权限,针对同一文件和子类
- private仅针对当前文件
特别说明:
1.同一模块比较抽象,以前在java中没有遇到过,这里说的是同一个module。
2.如果不对方法或者类增加权限修饰符,那么默认是public。
- 类和方法属性标识符
- abstract 抽象类
- enum枚举类(java中只需要enum,kotlin中需要enum class)
- final 不可继承(默认属性)
- open 修饰后,类可被继承,方法可被重写(abstract 默认带open)
8.类中属性的set,get方法
set和get在很多教程里都会被放在类的属性里作为一个小点来讲。为什么小编要单独拿出来。我相信很多人如果没有接触过property。都会跟小编有一样一头雾水,所以小编认为有必要再专门说明一下。
- 先看一下java对set和get的实现
class Student{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 所以,一般设置一个参数,都用以下方式实现。
Student stu = new Student();
stu.setName("小明");
- 再看一下kotlin的实现方式
class Student{
val name = ""
}
// 设置参数
val stu = Student()
stu.name = "小明"
看到这里,肯定会有很多小伙伴会有疑问。java如果把参数的权限变成public也可以用和kotlin一样的方式。但是小编在这里告诉你,不要太天真。来让我们看一看,在我们使用kotlin设置参数的时候他,kotlin偷偷背着我们做了什么。
- 属性声明的完整语法:
var <propertyName> [ : propertyType ] [ = <property_initializer> ]
[<getter>]
[<setter>]
set和get都是可选的。当我们默认使用类似kotlin的实现方式stu.name = 小明的时候,其实是调用了默认的setter方法。当然val声明不允许实现setter方法。
class Student {
var name : String = "小豆"
get() = field + "啦啦啦"
set(value) {
field = value + "拉拉"
}
val age = 10
set(value) {} // 会报错
}
七. 继承
1. kotlin的超类
所有的kotlin都继承Any这个类,算是kotlin中所有类的超类。但他不是object,可以理解成object在java中的地位。
- Any默认提供了以下三个方法
equals()
hashCode()
toString()
- 相比较而言,java中的object则提供了以下这些方法
toString()
equals()
hashCode()
getClass()
clone()
finalize()
notify()
notifyAll()
wait()
wait(long)
wait(long,int)
2. 继承的基本实现
类需要加上关键字open,因为类是默认final的。同时抽象类默认加上open
例子:
open class Base(age : Int)
class Child(age : Int) : Base(age){}
3. 对构造函数的处理
- 如果子类有构造函数
如果子类有主构造函数, 则基类必须在主构造函数中立即初始化。
// 父类
open class Base(name : String , age : Int){
}
// 子类
class Child(name : String , age : Int, no : Int) : Base(name , age){
}
// 测试
fun main(args: Array<String>) {
val s = Child("小豆", 18,10)
println("学生名: ${s.name}")
println("年龄: ${s.age}")
println("学生号: ${s.no}")
}
- 子类没有构造函数
如果子类没有主构造函数,则必须在每一个二级构造函数中用 super 关键字初始化基类,或者在代理另一个构造函数。初始化基类时,可以调用基类的不同构造方法。
open class Base(name : String) {
constructor(name : String , age : Int) : this(name){
}
}
class Student : Base{
constructor(name : String) : super(name){
}
}
4. 属性重写
属性重写和函数继承一样。需要在属性前加上关键字open。属性重写使用 override 关键字,属性必须具有兼容类型,每一个声明的属性都可以通过初始化程序或者getter方法被重写:
例子
open class Base {
open val name = ""
open fun base(){
print("父类Base")
}
}
open class Student : Base() {
override val name: String
get() = super.name
override fun base() {
print("子类base")
}
}
八. 接口
Kotlin 接口与 Java 8 类似,使用 interface 关键字定义接口,允许方法有默认实现
- 接口定义
interface InterDemo {
fun foo(){ }
}
- 接口实现
class Child : InterDemo{
override fun foo(){
}
}
- 接口属性
kotlin的接口属性和java有一定差异。- kotlin:接口中的属性只能是抽象的,不允许初始化值,接口不会保存属性值,实现接口时,必须重写属性。
- java:接口中的成员变量只能是public final static 。
interface InterDemo { var name : String fun foo() } class Child : InterDemo { override var name: String = "" //子类必须重写接口的属性 override fun foo() { } }
- 函数重写
和类继承一样,需要在函数前加上open(函数默认被final修饰)
open class Base {
open fun base(){
print("父类Base")
}
}
open class Student : Base() {
override fun base() {
print("子类base")
}
}
fun main(args: Array<String>) {
val s = Student()
s.base();
}
// 输出结果
子类base
由于kolin中接口中的函数是可以有初始化代码的,所以就遇到了继承和实现的接口和类中,包含同一个函数名的情况。我们需要使用super<类名>.<方法名>来选择调用
例子:
open class A {
open fun base(){ }
}
interface B {
fun base(){ }
}
class C() : A() , B {
override fun base() {
super<A>.base()
super<B>.base()
}
}
九. 泛型
泛型,即 "参数化类型",将类型参数化,可以用在类,接口,方法上。与 Java 一样,Kotlin 也提供泛型,为类型安全提供保证,消除类型强转的烦恼。
- 声明一个泛型
class Person<T>(t : T) {
val value = t
}
- 创建一个泛型
val person = Person<String>("小豆")
- 定义泛型类型变量,可以完整地写明类型参数,如果编译器可以自动推定类型参数,也可以省略类型参数。
val person = Person("小豆")
- 函数中声明泛型,类型参数要放在函数名的前面
fun <T> box(value : T){
print(value)
}
//以下全都合法
val box1 = box("小豆1")
val box2 = box<String>("小豆2")
- 型变
高能预警!!这是小编看到暂时比较隐涩的一个知识点。
生产者与消费者
在 Java 泛型里,有通配符这种东西,我们要用 ? extends T 指定类型参数的上限,用 ? super T 指定类型参数的下限。Kotlin 抛弃了这个系统,引用了生产者和消费者的概念。
什么是生产者?生产者是那些只能 读取 数据的对象;
什么是消费者?消费者是那些只能 写入 数据的对象;
要理解这个概念,我们可以看一个例子:
public interface Collection<E> extends Iterable<E> {
boolean add(E e);
boolean addAll(Collection<? extends E> c);
}
这是 Collection 接口的add() 和 addAll() 方法,传入它们的类型参数一个是 E ,一个是 ? extends E,为什么呢?这两个方法之间不就是批量操作的区别吗?为什么一个只接受 E 类型的参数,另一个却接受 ? extend E 的类型?
这是因为,Java 泛型是不型变的,也就是说,虽然 String 类型是 Object 类型的子类型,但 Collection<String> 并不是 Collection<Object> 的子类型。
对于一个 Collection<Object> 来说,因为 String 是 Object 的子类型,一个 String 对象就是 Object 类型,所以可以直接把它添加入 Collection<Object> 里,add() 方法的类型参数因为可以设为 E;而想把 Collection<String> 添加入 Collection<Object> 时,因为 Java 泛型不型变的原因,就会出现编译错误,必须用 ? extends E 将 Collection<String> 囊括到 Collection<Object> 里。
从另一个角度看,对于一个 Collection<A>,我们从其中读取出一个对象,这个对象可以是 A 的子类型,也可以是 A 类型,这种特性叫 协变(Convariant);而要向 Collection<A> 写入一个对象时,我们既可以写入一个 A 类型对象,也可以写入 A 的父类型,这种特性叫 逆协变(contravariance),协变和逆协变都是类型安全的。
从这个角度来说,我们可以把那些只能保证读取数据时类型安全的对象叫做生产者,用 out T 标记;把那些只能保证写入数据安全时类型安全的对象叫做消费者,用 in T 标记。
如果你觉得太晦涩难懂,就这么记吧:out T 等价于 ? extends T,in T 等价于 ? super T,此外还有 * 等价于 ?。
声明处型变
Kotlin 对 Java 泛型最大的改动就是添加了声明处型变。看下面的例子:
interface Source<T> {
T nextT();
}
void demo(Source<String> str) {
// Java 中这种写法是不允许的
Source<Object> obj = str;
/*...*/
}
因为 Java 泛型是不型变的,Source<String> 不是 Source<Object> 的子类型,所以不能把 Source<String> 类型变量赋给 Source<Object> 类型变量。
现在用 Kotlin 改写上面的接口声明:
interface Source<out T> {
T nextT();
}
我们在接口的声明处用 out T 做了生产者声明,因为这个接口只有一个读取数据的 nextT() 方法,可以视为生产者。把这个接口的类型参数声明为生产者后,就可以实现安全的类型协变了:
fun demo(Source<String> str) {
val obj: Source<Any> = str // 合法的类型协变
/*...*/
}
十. 枚举
- 枚举基本用法
枚举的用法和java基本一致。枚举类最基本的用法是实现一个类型安全的枚举。枚举常量用逗号分隔,每个枚举常量都是一个对象。 - 声明枚举类
java中声明枚举类只需要用关键字enum修饰。kotlin之中需要使用enum class来修饰。
enum class Color {
RED,BLUE,GREEN
}
- 枚举初始化
enum class Color(value : Int) {
RED(100),BLUE(200),GREEN(200)
}
- 使用枚举常量
Kotlin 中的枚举类具有合成方法,允许遍历定义的枚举常量,并通过其名称获取枚举常数。
Color red = Color.valueOf("RED");
Color[] values = Color.values();
十一.数据类
- 数据类介绍
数据类是kotlin独有的,帮我们封装了许多方法来存储和展示数据。对于我们平时需要展示的model来说,用数据类最合适不过。 - 数据类创建
使用data关键词来修饰class
例子:
data class User(val name : String , var age : Int)
- data类会自动提取以下函数(如果这些函数在类中已经被明确定义了,或者从超类中继承而来,就不再会生成。)
1.equals() / hashCode()
2.toString() 格式如 "User(name=John, age=42)"
3.componentN() functions 对应于属性,按声明顺序排列
4.copy() 函数 - 所需满足的条件
1.主构造函数至少包含一个参数。
2.所有的主构造函数的参数必须标识为val 或者 var ;
3.数据类不可以声明为 abstract, open, sealed 或者 inner;
4.数据类不能继承其他类 (但是可以实现接口)。 - Copy函数
类似于java中的clone(),但是比clone()更灵活
例子
//创建data类
data class User(name : String , age : Int)
val user = User("aaa",12)
// 拷贝过程中重新赋值
val userChangeCopy = user.copy(name = "小豆")
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
十二.密封类
- 介绍:密封类类似于枚举类,可以当成是枚举类的扩展。相比枚举类只能定义一个实例。密封类可以定义多个实例。
- 声明密封类
声明一个密封类,使用 sealed 修饰类,密封类可以有子类,但是所有的子类都必须要内嵌在密封类中。
sealed 不能修饰 interface ,abstract class(会报 warning,但是不会出现编译错误)
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
fun eval(expr: Expr): Double = when (expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
}
- 优点
使用密封类的关键好处在于使用 when 表达式 的时候,如果能够 验证语句覆盖了所有情况,就不需要为该语句再添加一个 else 子句了。
fun eval(expr: Expr): Double = when(expr) {
is Expr.Const -> expr.number
is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
Expr.NotANumber -> Double.NaN
// 不再需要 `else` 子句,因为我们已经覆盖了所有的情况
}
暂未了解清楚:
1.属性重写
2.标签return用法
3.约束泛型
4.型变
5.星号投射
6.密封类
7.数据类
待补充:
1.委托
2.扩展
网友评论