Kotlin基础
1.kotlin的简介
Kotlin是由JetBrains开发的针对JVM、Android和浏览器的静态编程语言,目前,在Apache组织的许可下已经开源。使用Kotlin,开发者可以很方便地开发移动Android应用、服务器程序和JavaScript程序。Kotlin可以将代码编译成Java字节码,也可以编译成JavaScript,方便在没有JVM的设备上运行。
Kotlin来源于一个岛屿的名字,全称是Kotlin Island,是英语【科特林岛】之意,这个小岛位于俄罗斯的圣彼得堡附近。之所以要命名为Kotlin,是因为Kotlin的主要开发工作就是由位于圣彼得堡分公司团队完成的。
2.kotlin的基础语法
参考kotlin官方文档
函数定义
函数定义使用关键字fun
,参数格式为: 参数:类型
fun sum2(x: Int, y: Int): Int {
return x + y;
}
表达式作为函数体,返回值类型自动推断
fun sum(x: Int, y: Int) = x + y;
public 方法必须明确返回值类型,否则会报错
public fun sum3(x: Int, y: Int): Int = x + y;
无返回值的函数(Unit类似于java中的void,但是不等同于void)
fun sum4(x: Int, y: Int): Unit {
println(x + y)
}
在定义方法的时候给定参数默认值
fun defaultValue(a: Int = 10) { // 定义参数的时候给定默认值
println(a)
}
可变长参数函数
函数的变长参数可以用 vararg
关键字进行标识, 此时参数是以数组的形式展示
// 打印参数
fun vararg(vararg variable: String) {
for (vars in variable) {
println(vars)
}
}
main函数在1.3版本之后,定义可以不带参数
lamdba表达式
val list: MutableList<String> = ArrayList()
list.add("a")
list.add("b")
list.add("c")
list.add("d")
list.forEach(
Consumer { l: String ->
println(l)
}
)
fun lambda(){
val list=listOf(1,2,3,4,5,6,7)
// 类似于java中的list.stream().filter()
println(list.filter { l ->
// 这里是条件
l > 3
})
}
可以看出这是四大函数式接口中断定型接口类型
定义常量使用 const val
关键字,表示编译时常量,只能写在外部
定义只读变量和可修改变量
可变变量定义: 使用var
关键字
var 标识符:类型=初始化值
不可以变量定义: 使用val
关键字
val 标识符:类型=初始化值
常量和变量都可以没有初始值,但是在引用前必须初始化
编译器支持自动类型判断,即声明时可以不指定类型,由编译器判断
/**
* @定义只读变量和可修改变量
* 可变变量定义: 使用var关键字
* 不可以变量定义: 使用val关键字
* var 标识符:类型=初始化值
* val 标识符:类型=初始化值
* 编译器支持自动类型判断,即声明时可以不指定类型,由编译器判断
*/
var a: Int = 1
var b = 1
println(a)
println(b)//系统会自动推断变量类型为Int
val c: Int// 初始化时候必须提供变量类型
c = 2// 明确赋值
println(c)
var x = 5
x += 2 // 变量是可以被修改
println(x)
字符串模板
$
表示一个变量名或者变量值
$varName
表示变量值
$(varName.fun())
表示变量的方法返回值
/**
* @字符串模板
* $ 表示一个变量名或者变量值
* $varName表示变量值
* $(varName.fun())表示变量的方法返回值
*/
// 1.$varName表示变量值
var s = "wangliang";
var s1 = "i am $s";
println(s1) // 打印出 i am wangliang
// 2.$(varName.fun())表示变量的方法返回值
var s2 = "i am ${s.replace("wangliang", "wl")}";
println(s2)
Null检查机制
Kotlin的空安全设计对于声明可为空的参数,在使用时要进行空判断处理,有两种处理方式:
1.字段后加!!像Java一样抛出空异常
2.另一种字段后加?可不做处理返回值为 null或配合?:做空判断处理
/**
* @Null检查机制
* Kotlin的空安全设计对于声明可为空的参数,在使用时要进行空判断处理,有两种处理方式:
* 1.字段后加!!像Java一样抛出空异常
* 2.另一种字段后加?可不做处理返回值为 null或配合?:做空判断处理
*/
// 类型后面加?表示可以为空
var age: String? = null
// 抛出空指针异常
// var ages=age!!.toInt()
// // 不作处理 返回null
// val ages1=ages?.toInt()
// age为空返回-1
val ages2 = age?.toInt() ?: -1
println(ages2)
类型检测及自动类型转换
注:在kotlin中 if是表达式,java中if是语句
/**
* @类型检测以及自动类型的转换
* 我们可以使用 is 运算符检测一个表达式是否某类型的一个实例(类似于Java中的instanceof关键字)。
*/
fun getStringLength(obj: Any): Int? {
if (obj is String) {
return obj.length;
}
return null;
}
区间
/**
* @区间
* 1.区间表达式由具有操作符形式 .. 的 rangeTo 函数辅以 in 和 !in 形成。
* 2.区间是为任何可比较类型定义的,但对于整型原生类型,它有一个优化的实现。
*/
// 输出 "1234"
for (i in 1..4) print(i)
println()
// 什么都不输出
for (i in 4..1) print(i)
println()
// 输出 "4321"
for (i in 4 downTo 1) print(i)
println()
// if 和区间的配合使用
val i=4;
val l=3;
if (i in l..10){ // 等同于 i>=l && i<=10
println(i)
}
// 使用 step 指定步长
for (i in 1..4 step 2) print(i) // 输出 "13"
println()
for(i in 4 downTo 1 step 2) print(i) // 输出 "42"
println()
for(i in 1 until 10) print(i) // 打印除了10
println()
和java中的StringBuilder 一样功能的 Kotlin的buildString
fun StringBuilder(){
val builder= buildString {
for (i in 5 downTo 1){
append(i)
appendLine()
}
}
println(builder)
}
类
在Kotlin中,类之间的继承是通过:
,而且默认的是被final修饰的,想要使一个类成为可被继承的,要使用open 修饰符
/**
*@author 没有梦想的java菜鸟
* @date 2022/01/05 10:16 上午
*/
open class TestKotlin {
// 有参构造函数
constructor(d: Double, d1: Double, d2: Double) {
}
// 无参构造函数
constructor() {
}
// 表示可被继承的方法
open fun hello(){
println("hello,kotlin")
}
}
// 继承有参构造函数
class Test(d: Double, d1: Double, d2: Double) : TestKotlin(d, d1, d2) {
}
// 继承无参构造函数
class Test2 : TestKotlin() {
override fun hello() {
super.hello()
}
}
创建POJO DTO实体类
data class Student(
val name:String,
val school:String,
val address:String
)
Kotlin 定义实体类已经帮我们封装好了get()和set()方法,还有toString()方法
Sealed 关键字表示这个类是私有的
Object关键字定义类,里面表示这是一个静态类
集合
// 创建一个集合,使用方法等于java中的aList
var items= listOf("A","B","C","D")
for (item in items) {
println(item)
}
// 创建一个集合,使用方法等于java中的aList
var items= listOf("A","B","C","D")
for (index in items.indices) {
// 取出索引的值
print(items[index])
// 取出索引
print(index)
println()
}
检查集合中是否包含此元素
fun containWithEle(){
val list= listOf("String","hello","world")
if ("hello" in list){
println("find it")
}
if ("world" in list){
println("hello,world")
}
}
map集合
fun foreach(){
var map = HashMap<String,Any>()
map.put("1001","胡图图")
map.put("1002","胡英俊")
map.put("1003","翻斗家园")
// 遍历
for ((k,v) in map){
println("$k-$v")
}
// 获取值
println(map["1003"])
}
when关键字(相当于 switch case)
@Test
fun findString() {
val obj: Any = "10"
when (obj) {
1 -> println("i am kotlin")
is String ->println("i am String")
is Long ->println( "i am Long")
"java" ->println("i am java")
else -> println("i can't know you")
}
}
运算符重载(operator overloading)
在java中想要实现对象排序需要使用 Comparable接口,有两种实现方法 ,一种是实现 Comparable接口,另外一种就是匿名内部类的方式,下面举个例子解释一下 kotlin的 运算符重载
data class Point(val x: Int, val y: Int) {
// minus 表示 相减 方法里面的是我们自定义的规则
operator fun minus(o: Point): Point {
return Point(o.x - x, o.y - y)
}
}
fun main(){
val point1=Point(10,20)
val point2=Point(15,26)
// 根据自己的规则 输出 Point(x=5, y=6)
println(point1-point2)
}
第二种写法
data class Point(val x: Int, val y: Int) {}
operator fun Point.minus(o:Point)=Point(o.x-x,o.y-y)
fun main(){
val point1=Point(10,20)
val point2=Point(15,26)
// 根据自己的规则 输出 Point(x=5, y=6)
println(point1-point2)
}
一元运算符
表达式 | 方法 |
---|---|
+a |
a.unaryPlus() |
-a |
a.unaryMinus() |
!a |
a.not() |
自增自减
表达式 | 方法 |
---|---|
a++ |
a.inc() + see below |
a-- |
a.dec() + see below |
二元运算符
表达式 | 方法 |
---|---|
a + b |
a.plus(b) |
a - b |
a.minus(b) |
a * b |
a.times(b) |
a / b |
a.div(b) |
a % b |
a.rem(b) |
a..b |
a.rangeTo(b) |
a > b |
a.compareTo(b) > 0 |
a < b |
a.compareTo(b) < 0 |
a >= b |
a.compareTo(b) >= 0 |
a <= b |
a.compareTo(b) <= 0 |
kotlin与JVM
在idea中查看kotlin生成的字节码文件
查看kotlin的字节码
经过对比发现kotlin的Int,Double类型都是一个类,这样编译起来是不是性能会不好,再看反编译后的java代码,发现会修改为java的基本类型
查看反编译后的java代码
具名函数
具体函数在传参的时候可以指定进行传参,以免出错
fun main() {
specific(name = "胡图图",username = "大耳兔图图",id = 1001,password = "123456",date = Date())
}
private fun specific(name:String,username:String,id:Int,password:String,date:Date){
println("name=${name},username=${username},id=${id},password=${password},date=${date}")
}
Nothing关键字
使用Nothing 类似于java中抛出异常并终止程序
fun main() {
val num = -1
when (num) {
-1 -> TODO("非法参数")
in 0..10 -> println("及格")
in 10..20 -> println("优秀")
}
}
反引号的使用场景
建一个java类
public class Quote {
public static final void is(){
System.out.println("i am is_Function");
}
public static final void in(){
System.out.println("i am in_Function");
}
}
kotlin调用
fun main() {
// 使用场景一 用于测试
`此方法仅用于测试 日期 2022年2月7日`("没有梦想的java菜鸟")
// 使用场景二 在调用java类是,如果java中的方法名是kotlin中的关键字则需要加上 ``
Quote.`in`()
Quote.`is`()
}
private fun `此方法仅用于测试 日期 2022年2月7日`(name: String) {
println("测试人:$name")
}
匿名函数
It 代指这个字符串,谁调用 it就代指谁
fun main() {
// 计算字符串长度
val length="java".count()
println(length)
// 计算指定字符
val length2="java".count {
it=='a'
}
println(length2)
}
隐式返回函数
fun main() {
// 创建隐式函数 必须指定返回值类型
val methodAction:(Int,Int,String)->String={
// num1 num2 name 表示的是参数
num1,num2,name-> "num1=$num1,num2=$num2,name=$name"
}
println(methodAction(1,2,"没有梦想的java菜鸟"))
// it关键字的使用
val methodIt:(String)->String={"$it"}
println(methodIt("AA"))
// fun methodIt(it:String):String{
// return "$it";
// }
}
类型的自动推断
// 匿名函数类型的自动推断 无需指定返回类型
val methodType = {
//t1 和 t2 表示的是 参数 后面的表示类型
t1: Double, t2: String -> "hello"
}
val methodType2 = {
//表示返回值类型是布尔值
true
}
在函数中定义的参数是函数
const val USERNAME="张三"
const val PASSWORD="123456"
fun main() {
// 第一种写法
login(USERNAME, PASSWORD,{ msg,code-> println("msg=$msg,code=$code")})
// 第二种写法
login(USERNAME, PASSWORD,responseResult= {msg,code-> println("msg=$msg,code=$code")})
// 第三种写法
login(USERNAME, PASSWORD){
msg,code->
println("msg=$msg,code=$code")
}
}
// 模拟登陆
inline fun login(username:String,password:String,responseResult:(String,Int)->Unit){
if (username== USERNAME && password == PASSWORD){
responseResult("登陆成功",200)
}else{
responseResult("用户名或密码错误",300)
}
}
注:在不同的类中,kotlin中方法不能一样
经过反编译后的java代码如下
发现这个方法是被static+final修饰的,说明只会存在一份
内联的使用
经过反编译,在上面的login方法中,发现lambda表达式被作为接口替换掉了,这样会造成性能的损耗
在函数中有lambda表达式作为参数的函数中,需要使用inline关键字。使用了inline关键字,发现将login方法里面的内容放到了main函数里
函数的引用
函数作为lambda参数
const val USERNAME="张三"
const val PASSWORD="123456"
fun main() {
// lambda表达式其实就是函数型对象,如果要把 responseResult函数作为参数传进去,需要把responseResult函数转换为函数类型对象
login(USERNAME, PASSWORD, ::responseResult)
}
fun responseResult(msg:String,code:Int){
println("msg=$msg,code=$code")
}
// 模拟登陆
inline fun login(username:String,password:String,responseResult:(String,Int)->Unit){
if (username== USERNAME && password == PASSWORD){
responseResult("登陆成功",200)
}else{
responseResult("用户名或密码错误",300)
}
}
函数类型作为函数的返回值
fun main(){
// 此时返回的method是 一个匿名函数
val method=methodReturn("胡图图")
// 再给调用匿名函数
println(method("翻斗花园", 1001))
}
fun methodReturn(name:String):(String,Int)->String{
return {
address,id-> "address=$address,id=$id,name=$name"
}
}
具名函数和匿名函数
fun main() {
// 匿名函数写法
method1("胡图图", "翻斗花园") {
msg,code->
println("msg=$msg,code=$code")
}
// 具名函数写法
method1("张三","北京市", ::ResultImpl)
}
fun ResultImpl(msg:String,code:Double){
println("msg=$msg,code=$code")
}
inline fun method1(name: String, address: String, Result: (String, Double) -> Unit) {
val str = "(name=$name,address=$address)"
Result(str, 0.1)
}
运行截图
java模拟
public class Specific {
public static void main(String[] args) {
// 第一种 匿名内部类的写法
method1("胡图图", "翻斗花园", new Result() {
@Override
public void result(String msg, double code) {
System.out.println("msg="+msg+",code="+code);
}
});
// 第二种 接口实现类
method1("张三","北京市",new ResultImpl());
}
public static void method1(String name, String address, Result res) {
String str = "(name=%s,address=%s)";
String formatStr = String.format(str, name, address);
res.result(formatStr, 0.1);
}
}
interface Result {
void result(String msg, double code);
}
class ResultImpl implements Result{
@Override
public void result(String msg, double code) {
System.out.println("msg="+msg+",code="+code);
}
}
内置函数及操作符
非空操作符 ?
fun main() {
var str:String?="this is kotlin"
str=null
// 在str后面加上?,表示的是如果str为空,则不会执行后面的代码(str?.length)则不会被执行
println(str?.length)
//在str后面加上!! 表示断言,不管str是否为空都执行
println(str!!.capitalize())
}
let内置函数
fun main() {
val result=listOf(1,2,3,4,5,6).let {
println(it[0])
// 最后一行是let函数的返回值
"$it"
}
println(result)
}
空合并操作符
fun main() {
var str:String?="this is kotlin"
str=null
// 如果str为空,则输出"空值"
println(str ?: "空值")
}
异常处理和自定义异常
自定义异常
fun main() {
// 自定义异常
val str:String?=null
checkIsNull(str)
}
fun checkIsNull(str:String?){
str ?: throw NullException()
}
class NullException:IllegalArgumentException("空指针异常了 ^_^")
运行截图
自带的异常
fun main() {
// 自定义异常
val str:String?=null
requireNotNull(str)
val flag:Boolean=false
require(flag)
}
substring和split
fun main() {
// 传统java用法
// 截取到指定字符
val str = "this is kotlin"
val index = str.indexOf('k')
val newStr = str.substring(0, index)
// kotlin用法
val subStr = str.substring(0 until index)
println(newStr)
println(subStr)
// 分隔字符
val text = "java,kotlin,python,php,go"
// 分隔后 是List<String>类型 val list:List<String> = text.split(",")
val list = text.split(",")
// 解构
val (v1,v2,v3,v4,v5)=list
println("$v1-$v2-$v3-$v4-$v5")
}
运行截图
replace的使用
fun main() {
// 原始数据
val pwd="ABCDGOPKDAOKDAJOIJOAJMNMKJNFKJSNAKA"
// Regex("[ANKOD]") 表示 匹配这里面的字母
val newPwd=pwd.replace(Regex("[ANKOD]")){
// 对字母进行替换 加密
when(it.value){
"A"->"0"
"N"->"6"
"K"->"10"
"O"->"7"
"D"->"1"
else ->it.value
}
}
println("加密后的密码是:$newPwd")
// 进行解密
val sourcePwd=newPwd.replace(Regex("[061071]")){
when (it.value){
"0"->"A"
"6"->"N"
"10"->"K"
"7"->"O"
"1"->"D"
else->it.value
}
}
println("原密码是:$sourcePwd")
}
apply内置函数
fun main() {
// 调用apply返回的是apply本身,类似于建造者模式 关键字 this代表调用者本身
val str="abc??//"
str.apply {
val replaceStr= replace("//","!!")
println(replaceStr)
}.apply {
val subStr=substring(0 until 4)
println(subStr)
}.apply {
val splitStr=split("??")[0]
println(splitStr)
}
}
运行截图
run内置函数
fun main() {
val str = "kotlin"
// run函数的返回值是也是最后一行
str.run(::checkOut)
.run(::printStr)
.run(::println)
}
fun checkOut(str: String) = str == "kotlin"
fun printStr(boolean: Boolean) = if (boolean) "哈哈哈" else ""
运行截图
also内置函数
fun main() {
var line:String?
val buffer=BufferedReader(FileReader("/Users/wangliang/Documents/Kt_study/src/main/resources/2.txt"))
// 在读取文件的时候 我们会让一个字符串变量去接收 读取的内容,kotlin 中不可以 (line=buffer.readLine())!=null 这样写 所以使用also 和apply都可以很好地解决
while (buffer.readLine().also { line=it } !=null){
println(line)
}
}
takeIf内置函数(takeUnless)
fun main() {
val username = "user"
println(checkUsername(username))
}
fun checkUsername(username: String): Int {
// 如果 takeIf的结果为 true 那么就会返回number本身 否则返回 空合并操作符的值
// takeIf 一般都是和空合并操作符 合并用的
val number= 1
return number.takeIf { username == "root" } ?: 10
}
copy函数的使用及其说明
data class CopyTest(var name: String, var age: Int) {
private var info=""
init {
println("主构造被调用")
}
// 次级构造
constructor(name:String):this(name,10){
println("次构造被调用")
info="重要信息"
}
override fun toString(): String {
return "{name=$name,age=$age,info=$info}"
}
}
fun main() {
val cp=CopyTest("小明")
println(cp)
// copy 函数内部代码只处理主构造函数的内容
val cp2=cp.copy()
println(cp2)
}
根据运行记过可以看出,copy 函数内部代码只处理主构造函数的内容
2.kotlin协程
1.协程概念
多线程的不足
多线程看起来是在并行执行,但是cpu在某一个时间片只能执行一个线程,当执行一会儿一个线程,就会立马切换到下一个线程。当线程池中的线程有许多都被阻塞住了,cpu就会将线程挂起,去执行别的线程,这样就会造成线程频繁切换,浪费资源。多线程一旦执行,内部是我们无法控制的。
协程
线程是由操作系统来调度的,而协程依赖于线程,不被操作系统调度。协程是由程序来调度的的,这样就可以大大减少开销。使用协程我们还可以在线程执行时进行控制。总而言之,携程是轻量级的
2.什么是挂起?
协程中的挂起指的不是挂起线程也不是挂起函数,挂起的对象是协程。
当线程执行到挂起函数的时候,这个协程就会被挂起,暂时不再执行剩余的协程代码,跳出协程的代码块,也就是说这个协程正在从执行他的线程上脱离,然后线程会去执行别的任务去了。
当这个协程被挂起以后,继续执行在 Dispatchers
所指定的线程中,当执行完,会再切回原来的线程中。
3.协程的使用
创建协程的方式有三种方式
全局作用域
应用程序还在运行且协程的任务还未结束,协程就可以一直运行
GlobalScope.launch {
launch {
println("全局作用域")
}
}
阻塞式
阻塞调用者线程,直至自己执行完
runBlocking {
launch {
println("阻塞式")
}
}
独立作用域
需要依赖顶层函数,是一个挂起函数,coroutineScope会让所有启动的协程都完成后才结束自身
runBlocking{
coroutineScope {
launch {
println("独立作用域")
}
}
}
使用协程来读取文件
- launch 无返回值开启协程
- async 有返回值开启协程,可以用await方法进行阻塞等待结果
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.BufferedReader
import java.io.FileReader
import java.util.*
/**
*@author 没有梦想的java菜鸟
* @date 2022/04/19 2:08 下午
*/
fun main() {
val startTime=Date().time
runBlocking {
repeat(5){
read()
}
}
println(Date().time-startTime)
}
fun read() = runBlocking {
var line: String?
val buff = BufferedReader(FileReader("src/main/resources/rawlog.txt"))
while (buff.readLine().also { line = it } != null) {
val s = line
// 无返回值开启协程
launch {
delay(1000)
println("hhhh")
}
// 有返回值开启协程
val result=async {
getStr()
}
println(result.await())
}
}
fun getStr():String{
return "hh"
}
协程配合线程使用
import cn.hutool.core.date.DateUtil
import kotlinx.coroutines.*
import java.io.BufferedReader
import java.io.FileReader
import java.util.*
import java.util.concurrent.*
/**
*@author 没有梦想的java菜鸟
* @date 2022/02/22 1:53 下午
*/
private val threadPool = ThreadPoolExecutor(
2,
2,
200,
TimeUnit.SECONDS,
LinkedBlockingDeque(20),
Executors.defaultThreadFactory(),
ThreadPoolExecutor.AbortPolicy()
).asCoroutineDispatcher()
fun main() {
runBlocking {
// 传参数依赖于 线程池 任务顺序按照 id 或者时间 执行时使用
listOf<Student>(
Student(1001, "小明", "****大学", "北京"),
Student(1002, "小李", "----大学", "上海"),
Student(1003, "小张", "@@@@大学", "澳门"),
Student(1004, "小王", "====大学", "西藏")
).map {
launch(threadPool) {
test(it)
}
}
// 不传参 或者固定参数
// repeat(2) {
// launch(Dispatchers.Default) {
// test(Student())
// }
// }
}
}
// 45997
suspend fun test(stu: Student) {
println(stu)
val conn = JdbcUtils.getConnection()
val stmt = conn.createStatement()
println(conn)
withContext(Dispatchers.IO) { // 挂起当前函数 将资源让出 如果要保证顺序执行 不要用
val buffer = BufferedReader(FileReader("src/main/resources/TableauDesktop-2022-2-0.dmg"))
while (buffer.readLine() != null) {
}
println("insert into student values(${stu.id},'${stu.name}','${stu.school}','${stu.address}')")
// stmt.execute("insert into student values(${stu.id},'${stu.name}','${stu.school}','${stu.address}')")
}
}
data class Student(var id: Int? = 0, var name: String? = "", var school: String? = "", var address: String? = "")