最近很多小伙伴都在问Kotlin与Java:哪个更合适和kotlin和java哪个好这两个问题,那么本篇文章就来给大家详细解答一下,同时本文还将给你拓展AndroidKotlinRXKotlinRoo
最近很多小伙伴都在问Kotlin 与 Java:哪个更合适和kotlin和java哪个好这两个问题,那么本篇文章就来给大家详细解答一下,同时本文还将给你拓展Android Kotlin RXKotlin Room - 错误 Kotlin.unit、Android Studio 3.0 Canary 1:Kotlin测试或Java测试引用Kotlin类失败、Android Studio 3.0 Canary 1:Kotlin测试或引用Kotlin类的Java测试失败、From Java To Kotlin 2:Kotlin 类型系统与泛型终于懂了等相关知识,下面开始了哦!
本文目录一览:- Kotlin 与 Java:哪个更合适(kotlin和java哪个好)
- Android Kotlin RXKotlin Room - 错误 Kotlin.unit
- Android Studio 3.0 Canary 1:Kotlin测试或Java测试引用Kotlin类失败
- Android Studio 3.0 Canary 1:Kotlin测试或引用Kotlin类的Java测试失败
- From Java To Kotlin 2:Kotlin 类型系统与泛型终于懂了
Kotlin 与 Java:哪个更合适(kotlin和java哪个好)
2019年5月17日,在 Google I/O 的主题演讲上,Google Android 团队宣布 Kotlin 成为 Android 开发官方语言。过去几年中,开发者一直使用 Kotlin 开发 Android 应用程序,但 Google 刚刚宣布 Kotlin 成为一等公民。本文会简单介绍一下 Kotlin 的历史。
Kotlin 基本上是一种运行在 JVM 上的静态类型编程语言。2011年7月首次发布,但一直到2016年2月才向公众发布。它被称为“Kotlin 项目”,由俄罗斯圣彼得堡的 JetBrains 小组创建。事实上,Kotlin 这个名字来源于20世纪90年代圣彼得堡的科特林岛(Kotlin Island)。
Kotlin 好在哪里?
Kotlin 和 Java 都可以用来构建 Android 应用程序,但是自从 Kotlin 发布,你可能一直想知道为什么要切换编程语言?嗯,设计 Kotlin 是为了解决 Java 的一些问题,让学习编程更容易。下面是 Kolin 解决的一些问题:
Kotlin 的类型化系统从代码层面消除了 `NullPointerException`
Kolin 的设计灵活而简单,甚至可以调用 Java 代码
Array 类除了基础函数,还提供了 get 和 set 函数
函数可以接受另一个函数作为参数
Kolin 没有受检异常
与 Java 相比,Kotlin 代码简洁、可移植
可以使用现有的 Java 库在 Kotlin 中进行 JVM、浏览器和 Android 开发
Kotlin 还能构建什么其他应用程序?
JVM: 可以使用 Kotlin 开发服务器端应用程序。和 Java 一样,可以使用 Spring、Vert.x、Ktor 等许多框架。Kotlin 开发的项目也可以部署到任何 Java Web 环境,比如 Amazon web 应用程序、 Heroku 和 Google 云平台
原生应用程序:最近 Kotlin 也可以使用现有的 Java 代码构建原生应用
Javascript:Kotlin 支持转换为 JavaScript
Java 未来会怎样?应该投奔 Kotlin 吗?
即使 Kotlin 成为了 Android 开发中的一等公民,对 Java 不会有任何影响。假如要成为一名 Android 开发者或者你本身是一名新手,还是建议从 Java 开始。等到对 Java 熟悉后,可以转向 Kotlin。这样不但能对语言有更加深入的理解,而且在 Kotlin 中运行 Java 代码时能够更加从容应对。
总而言之,Kotlin 是一门伟大的编程语言,它的目标是为了解决 Java 和其他面向对象程序设计语言中的许多问题。它可靠、灵活、兼容性好,而且非常容易学习。如果你在 Android 开发中使用 Java,那么绝对应该考虑使用 Kotlin。这就是未来。
Android Kotlin RXKotlin Room - 错误 Kotlin.unit
如何解决Android Kotlin RXKotlin Room - 错误 Kotlin.unit?
我想通过循环数组向房间数据库做一个简单的插入数据。 我使用 RXKotlin 来迭代数组 我有一个这样的数组:
fun defaultDataCategory() : ArrayList<CategoryModel>{
var cat: CategoryModel
var catArrayList: ArrayList<CategoryModel> = ArrayList(0)
val date: Int = Calendar.DATE
val formatedDate = SimpleDateFormat("yyyy-MM-dd").format(Date())
val formatedTime = SimpleDateFormat("HH:mm").format(Date())
val DateTime = "$formatedDate $formatedTime"
catArrayList.add(
CategoryModel(
1,"Personal",true,"Red",Converter.toDate(Calendar.getInstance().timeInMillis),"system","system"
)
)
catArrayList.add(
CategoryModel(
2,"Work","Blue","system"
)
)
catArrayList.add(
CategoryModel(
3,"Home","Purple","system"
)
)
catArrayList.add(
CategoryModel(
4,"Learn","Yellow","system"
)
)
return catArrayList
}
我像这样用 RXKotlin 循环了一个数组
var catArrayList: ArrayList<CategoryModel> = DefaultData.defaultDataCategory()
catArrayList.toObservable()
.subscribeBy( // named arguments for lambda Subscribers
onNext = { homeviewmodel.insertCategory(it) },onError = { Log.e("error insert=",it.printstacktrace().toString()) },onComplete = { Log.e("complete insert=","complete insert") }
)
我得到了一个错误“kotlin.unit”。
Error RXKotlin
如何使用 RXKotlin 迭代数组并插入房间数据库?
解决方法
试试
Observable.fromIterable(catArrayList).subscribeBy {...}
Android Studio 3.0 Canary 1:Kotlin测试或Java测试引用Kotlin类失败
UPDATE
此处已针对此问题提交了一个错误:
https://youtrack.jetbrains.com/issue/KT-17951
更新2
该错误已在Android Studio 3.0 Canary 3中修复
原帖
我刚刚开始使用Android Studio 3.0,我从一开始就启用了kotlin支持.我在我的项目中写了一个非常简单的Kotlin类:
data class Wallet(val coins: Int) {
fun add(value: Int): Wallet = Wallet(coins + value)
fun substract(value: Int): Wallet = if (coins > value) Wallet(coins + value) else throw InsufficientFundsException()
}
现在我想测试这个类,首先我在Kotlin写了一个本地运行的unittest(测试目录):
class WalletTestKotlin {
@Throws(Exception::class)
@Test
fun add() {
Assert.assertEquals(22, Wallet(20).add(2).coins.toLong())
Assert.assertNotEquals(5, Wallet(2).add(13).coins.toLong())
}
}
它编译并运行但错误消息:
Class not found:
“com.agentknopf.hachi.repository.model.WalletTestKotlin”Empty test
suite.
因此我用Java重新编写了测试:
public class WalletTest {
@Throws(exceptionClasses = Exception.class)
@Test
public void add() {
Assert.assertEquals(22, new Wallet(20).add(2).getCoins());
Assert.assertNotEquals(5, new Wallet(2).add(13).getCoins());
}
}
然而,该测试也失败了 – 这次Kotlin类“Wallet”无法找到:
java.lang.NoClassDefFoundError: com/example/repository/model/Wallet
我想知道我是否遗漏了某些东西……运行Java测试,不是指Kotlin类,而是java类只能成功完成.
我的项目build.gradle文件是默认文件:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.1.2-4'
repositories {
maven { url 'https://maven.google.com' }
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0-alpha1'
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
}
}
allprojects {
repositories {
jcenter()
maven { url 'https://maven.google.com' }
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
我的特定于模块的build.gradle的依赖项:
dependencies {
compile filetree(dir: 'libs', include: ['*.jar'])
//Kotlin support
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
//Testing libraries
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
testCompile 'junit:junit:4.12'
testCompile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
}
解决方法:
解决方法(暂时):
把它放到你的(app-level)build.gradle中:
task copyTestClasses(type: copy) {
from "build/tmp/kotlin-classes/debugUnitTest"
into "build/intermediates/classes/debug"
}
然后在“启动前”的底部向下修改测试JUnit Run / Debug配置,其中包含“Gradle-aware make”,另一部分,选择gradle任务,选择它所在的项目build.gradle文件,然后键入copyTestClasses. Click here for screenshots的不同测试框架,但管道工作方式相同.
您可能需要根据构建类型更改/添加更多目录管道.您找到这些奇怪位置的方法是通过在项目树中搜索相关的.class文件.
Android Studio 3.0 Canary 1:Kotlin测试或引用Kotlin类的Java测试失败
更新
已针对此问题提交了一个错误:https :
//youtrack.jetbrains.com/issue/KT-17951
更新2
该错误已在Android Studio 3.0 Canary 3中修复
原始帖子
我刚刚开始使用Android Studio 3.0,从一开始就启用了kotlin支持。我在项目中编写了一个非常简单的Kotlin类:
data class Wallet(val coins: Int) { fun add(value: Int): Wallet = Wallet(coins + value) fun substract(value: Int): Wallet = if (coins > value) Wallet(coins + value) else throw InsufficientFundsException()}
现在我想测试该类,首先我在Kotlin中编写了一个本地运行的unittest(测试目录):
class WalletTestKotlin { @Throws(Exception::class) @Test fun add() { Assert.assertEquals(22, Wallet(20).add(2).coins.toLong()) Assert.assertNotEquals(5, Wallet(2).add(13).coins.toLong()) }}
它编译并运行,但显示错误消息:
找不到类:“ com.agentknopf.hachi.repository.model.WalletTestKotlin”空测试套件。
因此,我重新编写了Java中的测试:
public class WalletTest { @Throws(exceptionClasses = Exception.class) @Test public void add() { Assert.assertEquals(22, new Wallet(20).add(2).getCoins()); Assert.assertNotEquals(5, new Wallet(2).add(13).getCoins()); }}
但是该测试也失败了-这次找不到Kotlin类“钱包”:
java.lang.NoClassDefFoundError:com / example / repository / model / Wallet
我想知道我是否缺少某些东西…运行Java测试,该测试不引用Kotlin类,但是仅成功完成Java类。
我的项目build.gradle文件是默认文件:
// Top-level build file where you can add configuration options common to all sub-projects/modules.buildscript { ext.kotlin_version = ''1.1.2-4'' repositories { maven { url ''https://maven.google.com'' } jcenter() } dependencies { classpath ''com.android.tools.build:gradle:3.0.0-alpha1'' 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 }}allprojects { repositories { jcenter() maven { url ''https://maven.google.com'' } mavenCentral() }}task clean(type: Delete) { delete rootProject.buildDir}
我特定于模块的build.gradle的依赖项:
dependencies { compile fileTree(dir: ''libs'', include: [''*.jar'']) //Kotlin support compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" //Testing libraries androidTestCompile(''com.android.support.test.espresso:espresso-core:2.2.2'', { exclude group: ''com.android.support'', module: ''support-annotations'' }) testCompile ''junit:junit:4.12'' testCompile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"}
答案1
小编典典解决方法(目前):
将其放入您的(应用程序级)build.gradle:
task copyTestClasses(type: Copy) { from "build/tmp/kotlin-classes/debugUnitTest" into "build/intermediates/classes/debug"}
然后在“启动之前”自下而下修改测试JUnit Run / Debug配置,在其中有“ Gradle-aware
make”,+
另一部分,选择gradletask
,选择build.gradle
它所在的项目文件,然后键入copyTestClasses
。单击此处获取不同测试框架的屏幕截图,但是管道的工作原理相同。
您可能需要更改/添加更多目录管道,具体取决于您的构建类型。查找那些奇怪位置的方法是通过在项目树中粗略搜索相关的.class文件。
From Java To Kotlin 2:Kotlin 类型系统与泛型终于懂了
上期主要分享了 From Java To Kotlin 1 :空安全、扩展、函数、Lambda。
这是 From Java to Kotlin 第二期。
带来 表达式思维、子类型化、类型系统、泛型。
From Java to Kotlin 关键在于 思维的转变。
表达式思维
Kotlin 中大部分语句是表达式。
表达式思维是一种编程思维。 编程思维是一种非常抽象的概念,很多时候是只可意会不可言传的。
不过,从某种程度上看,学习编程思维,比学习编程语法更重要。因为编程思维决定着我们的代码整体的架构与风格,而具体的某个语法反而没那么大的影响力。当然,如果对 Kotlin 的语法没有一个全面的认识,编程思维也只会是空中楼阁。就像,我们学会了基础的汉字以后开始写作文:学了汉字以后,如果没掌握写作的技巧,是写不出好的文章的。同理,如果学了 Kotlin 语法,却没有掌握它的编程思维,也是写不出优雅的 Kotlin 代码的。
下面我们看一段 Kotlin 代码
//--- 1
var i = 0
if (data != null) {
i = data
}
//--- 2
var j = 0
if (data != null) {
j = data
} else {
j = getDefault()
println(j)
}
//--- 3
var k = 0
if (data != null) {
k = data
} else {
throw NullPointerException()
}
//--- 4
var x = 0
when (data) {
is Int -> x = data
else -> x = 0
}
//--- 5
var y = 0
try {
y = "Kotlin".toInt()
} catch (e: NumberFormatException) {
println(e)
y = 0
}
这些代码,如果我们用平时写 Java 时的思维来分析的话,是挑不出太多毛病的。但是站在 Kotlin 的角度,就完全不一样了。利用 Kotlin 的语法,我们完全可以将代码写得更加简洁,就像下面这样:
//--- 1
val i = data ?: 0
//--- 2
val j = data ?: getDefault().also { println(it) }
//--- 3
val k = data?: throw NullPointerException()
//--- 4
val x = when (data) {
is Int -> data
else -> 0
}
//--- 5
val y = try {
"Kotlin".toInt()
} catch (e: NumberFormatException) {
println(e)
0
}
这段代码看起来就简洁了不少,所以从 Java 转到 Kotlin 要格外注意思维转变,培养表达式思维。
这里有个疑问:Kotlin 为什么就能用这样的方式写代码呢?其实这是因为:if、when、throw、try-catch 这些语法,在 Kotlin 当中都是表达式。
那么,这个“表达式”到底是什么呢?其实,与表达式(Expression)对应的,还有另一个概念,我们叫做语句(Statement)。
- 表达式(Expression),是一段可以产生值的代码;
- 语句(Statement),则是一句不产生值的代码。
我们可以简单来概括一下:
表达式(Expression)有值,而语句(Statement)不总有。
用一个更详细的例子解释:
val a = 1 // statement
println(a) // statement
// statement
var i = 0
if (data != null) {
i = data
}
// 1 + 2 是一个表达式,但是对b的赋值行为是statement
val b = 1 + 2
// if else 整体是一个表达式
// a > b是一个表达式, 子表达式
// a - b是一个表达式, 子表达式
// b - a是一个表达式, 子表达式。
fun minus(a: Int, b: Int) = if (a > b) a - b else b - a
// throw NotImplementedError() 是一个表达式
fun calculate(): Int = throw NotImplementedError()
这段代码是描述了常见的 Kotlin 代码模式,从它的注释当中,我们其实可以总结出这样几个规律:
- 赋值语句,就是典型的 statement;
- if 语法,既可以作为语句,也可以作为表达式;
- 语句与表达式,它们可能会出现在同一行代码中,比如 val b = 1 + 2;
- 表达式还可能包含“子表达式”,就比如这里的 minus 方法;
- throw 语句,也可以作为表达式。
看到这里,可能又有一个疑问,那就是:calculate() 这个函数难道不会引起编译器报错吗?
// 函数返回值类型是Int,实际上却抛出了异常,没有返回Int
// ↓ ↓
fun calculate(): Int = throw NotImplementedError()
要想搞清楚这个疑问, 需要理解Kotlin的类型系统。
小结
- Koltin表达式思维是指时刻记住 Kotlin 大部分的语句都是表达式,它们可以产生返回值。利用这种思维,往往可以大大简化代码逻辑。
Kotlin 的类型系统
类、类型和子类型
- 类(class)是指一种数据类型,类定义定义对象的属性和方法,可以用来创建对象实例,例如
class Person(val name: String)
,用于表示一个人的属性和行为。 - 类型(type)是指一个_变量或表达式 _的 _数据类型_。类型可以用来描述变量或表达式的特征和限制(取值范围和可用的操作)。在Kotlin中,每个变量或表达式都有一个确定的类型,例如Int、String、Boolean等,类型可以是可空的或非空的,例如
String?
或String
。 - 子类型(subtype)是指一个类型的子集,即一个类型的值可以赋值给另一个类型的变量或表达式。例如
class Student(name: String, val grade: Int) : Person(name)
中,Student
是Person
的子类型,String
是String?
的子类型 。
在 Kotlin 中,类和类型之间有一定的对应关系,但并不完全相同。一个类可以用于构造多个类型,
例如泛型类 List<T>
可以构造出 List<String>
、List<Int>
等不同的类型。一个类型也可以由多个类实现,例如接口类型 Runnable
可以由多个实现了 run()
方法的类实现。
子类型化
先看一段代码:
非可空类型的 strNotNull:String ,可以赋值给 可空类型的strNullable:String? ;
可空类型的strNullable:String? 不可以赋值给 非可空类型的 strNotNull:String。
可以看出每一个Kotlin类都可以用于构造至少两种类型。
根据子类型化的定义,String 是 String?的子类型。
看到这里可能有个疑问?没有继承关系,String 并没有 继承 String?,为啥String是 String? 的子类型。
其实我也有, 经常开发 Java 会有一个误区:认为只有继承关系的类型之间才可以有父子类型关系。
因为在Java中,类与类型大部分情况下都是“等价”的(在Java泛型出现前)。事实上,“继承”和“子类型化”是两个完全不同的概念。子类型化的核心是一种类型的替代关系。
子类型化, 以下内容引用自维基百科
在编程语言理论中,子类型(动名词,英语:subtyping(也有翻译为子类型化))是一种类型多态)的形式。这种形式下,子类型(名词,英语:subtype)可以替换另一种相关的数据类型(超类型,英语:supertype)。也就是说,针对超类型元素进行操作的子程序、函数等程序元素,也可以操作相应的子类型。如果 S 是 T 的子类型,这种子类型关系通常写作 S <: T,意思是在任何需要使用 T 类型对象的_环境中,都可以安全地使用_ S 类型的对象。
由于子类型关系的存在,某个对象可能同时属于多种类型,因此,子类型(英语:subtyping)是一种类型多态)的形式,也被称作子类型多态(英语:subtype polymorphism)或者包含多态(英语:inclusion polymorphism)。
子类型与面向对象语言中(类或对象)的继承)是两个概念。子类型反映了类型(即面向对象中的接口)之间的_关系_;而继承反映了一类对象可以从另一类对象创造出来,是_语言特性 _的实现。因此,子类型也称接口继承;继承称作实现继承。
子类型 - 维基百科,自由的百科全书
子类型化可表示为:
S <:T
以上S是T的子类,这意味着在需要T类型 值 的地方,S类型的 值 同样适用,可以用 S 类型的 值 替换。
所以在前面的例子中, 虽然String与String?看起来没有继承关系,然而在我们需要用String?类型值的地方,显然可以传入一个类型为String的值,这在编译上不会产生问题。反之却不然。 所以String?是String的父类型。
继承强调的是一种“实现上的复用”,而子类型化是一种类型语义的关系,与实现没关系。对于 Java 语言,由于一般在声明父子类型关系的同时也声明了继承的关系,所以造成了某种程度上的混淆。
类型系统
Kotlin 的类型还分为可空类型和不可空类型。Any 是所有非空类型的根类型;而 Any? 是所有可空类型的根类型。
我们猜测 Kotlin 的类型体系可能是这样的:
那Any 与 Any? 之间是什么关系呢?
Any 、Any?与 Java 的 Object
Java 当中的 Object 类型,对应 Kotlin 的“Any?”类型。但两者并不完全等价,因为 Kotlin 的 Any 可以没有 wait()、notify() 之类的方法。因此,我们只能说 Kotlin 的“Any?”与 Java 的 Object 是大致对应的。
下面是Java 代码,它有三个方法,分别是可为空的 Object 类型、不可为空的 Object 类型,以及无注解的 Object 类型。
public class TestTypeJava {
@Nullable // 可空注解
public Object test() { return null; }
// 默认
public Object test1() { return null; }
@NotNull // 不可空注解
public Object test2() { return 1; }
}
上面的代码通过 Convert Java File to Kotlin File
转换成 Kotlin:
class TestTypeJava {
// 可空注解
fun test(): Any? {
return null
}
fun test1(): Any? { // 可以看出默认情况下, Java Object 对应 Kotlin Any?
return null
}
// 不可空注解
fun test2(): Any {
return 1
}
}
可以看出默认情况下,没有注解标记可空信息的时候, Java Object 对应 Kotlin Any?。
有些时候Java代码包含了可空性的信息,这些信息使用注解来表达。当代码中出现了这样的信息时,Kotlin就会使用它。因此Java中的@Nullable String被Kotlin当作String?,而@NotNull String就是String
如果没有是否可空注解, Java类型会变成 Kotlin 中的平台类型(后面会解释)。
了解了 Any 和 Any?的关系,可以画出关系图
Unit 与 Void 与 void
先看一段 Java 代码
public class PrintHello {
public void printHelloWorld() {
System.out.println("Hello World!");
}
}
转成 Kotlin
class PrintHello {
fun printHelloWorld():Unit { // Redundant ''Unit'' return type
println("Hello World!")
}
}
Java 的 void
关键字在 Kotlin 里是没有的,取而代之的是一个叫做 Unit
的东西,
Unit 和 Java 的 void
真正的区别在于,void
是真的表示什么都不返回,而 Kotlin 的 Unit
却是一个真实存在的类型:
public object Unit {
override fun toString() = "kotlin.Unit"
}
它是一个 object
,也就是 Kotlin 里的单例类型或者说单例对象。当一个函数的返回值类型是 Unit
的时候,它是需要返回一个 Unit
类型的对象的:
fun printHelloWorld():Unit {
println("Hello World!")
return Unit // return Unit 可以省略
}
只不过因为它是个 object
,所以唯一能返回的值就是 Unit
本身。
这两个 Unit
是不一样的,上面的是 Unit
这个类型,下面的是 Unit
这个单例对象,它俩长得一样但是是不同的东西。注意了,这个并不是 Kotlin 给Unit
的特权,而是 object
本来就有的语法特性。如果有需要,也可以用同样的格式来使用别的单例对象,是不会报错的:
包括也可以这样写:
val unit: Unit = Unit
也是一样的道理,等号左边是类型,等号右边是对象——当然这么写没什么实际作用啊,单例可以直接用。
object Zhangsan
fun getZhangsan(): Zhangsan { // 单例可以直接使用
return Zhangsan
}
因此,在结构上,Unit
并没有任何特别之处,它只是 Kotlin 的 object
。除了对于函数返回值类型和返回值的自动补充之外,它的特殊之处更多地在于语义和用途的角度。它是由官方规定的,用于表示「什么也不返回」的场景的返回值类型。但这只是它被规定的用法而已,本质上它是一个实实在在的类型。在 Kotlin 中,不存在真正没有返回值的函数,所有「没有返回值」的函数实质上的返回值类型都是 Unit,而返回值也都是 Unit 这个单例对象。这是 Unit 和 Java 的 void 在本质上的不同之处。
Unit 相比 void 带来什么不同
Unit 去除了无返回值函数的特殊性和有返回值函数之间的本质区别,从而使得很多事情变得更加简单,这种通用性为我们带来了便利。
例子: 函数类型的函数参数
虽然不能说Java中的所有函数调用都是表达式,但是可以说Kotlin中的所有函数调用都是表达式。
是因为存在特例void,在Java中如果声明的函数没有返回值,那么它就需要用void来修饰。如:
public void printHelloWorld() {
System.out.println("Hello World!");
}
因为 void 不是类型,所以 函数printHelloWorld()无法匹配 () -> Unit 函数类型
class VoidTest {
fun printHelloWorld1():Unit { // 作为参数时,就有函数类型 () -> Unit
println("Hello World!")
}
fun runTask(task: () -> Any) {
when (val result = task()) {
Unit -> println("result is Unit")
String -> println("result is a String: $result")
else -> println("result is an unknown type")
}
}
@Test
fun main1() {
val var1 = ::printHelloWorld1 // () -> Unit
runTask (var1) // () -> Unit
runTask { "This is string" } //:() -> String
runTask { 42 } // () -> Int
}
}
现在有了 Unit , fun printHelloWorld1():Unit 作为参数时,就有函数类型 () -> Unit 。
注意:在 Java 当中,Void 和 void 不是一回事(注意大小写),前者是一个 Java 的类,后者是一个用于修饰方法的关键字。如下所示:
public final class Void {
@SuppressWarnings("unchecked")
public static final Class<Void> TYPE = (Class<Void>) Class.getPrimitiveClass("void");
private Void() {}
}
JAVA中Void类是一个不可实例化的占位符类,用来保存一个引用代表Java关键字void的Class对象。它的作用是在反射或泛型中表示void类型。
例如:Map接口的put方法需要两个类型参数,如果我们只需要存储键而不需要存储值,就可以使用Void类作为类型参数
Map<String, Void> map = new HashMap<>(); map.put("key", null);。
了解了 Unit
和 Unit?
的关系后,可以画出关系图
Nothing
Nothing 是 Kotlin 所有类型的子类型。 Noting 的概念与 Any? 恰好相反。
Nothing 也叫底类型(BottomType)。
Nothing的源码是这样的:
public class Nothing private constructor()
可以看到它本身虽然是 public 的,但它的构造函数是 private 的,这就导致我们没法创建它的实例;而且它不像 Unit 那样是个 object:
public object Unit {
override fun toString() = "kotlin.Unit"
}
而是个普通的 class;并且在源码里 Kotlin 也没有帮我们创建它的实例。
这些条件加起来,结果就是:Nothing 这个类既没有、也不会有任何的实例对象。
基于这样的前提,当我们写出这个函数声明的时候:
fun nothing(): Nothing {
}
我们可能无法找到一个合适的值来返回,但是在编写代码时,我们必须返回一个值。这种情况下,我们遇到了一个悖论,即必须返回一个值,但却永远找不到合适的返回值
Nothing的作用: 作为函数 永远不会返回结果
的提示
fun nothing() : Nothing {
throw RuntimeException("Nothing!")
}
根据Nothing的特性, Nothing 专门用于抛异常。
public class NotImplementedError(message: String = "An operation is not implemented.") : Error(message)
@kotlin.internal.InlineOnly
public inline fun TODO(): Nothing = throw NotImplementedError()
从上面这段代码可以看出,Kotin 源码中 throw 表达式的返回值类型是 Nothing。
throw 这个表达式的返回值是 Nothing 类型。而既然 Nothing 是所有类型的子类型,那么它当然是可以赋值给任意其他类型的。
所以表达式思维中的问题就可以解答了
// 函数返回值类型是Int,实际上却抛出了异常,没有返回Int
// ↓ ↓
fun calculate(): Int = throw NotImplementedError()
作用二
Nothing 类的构造函数是私有的,因此我们无法构造出它的实例。当 Nothing 类型作为函数参数时,一个有趣的现象就出现了:
// 这是一个无法调用的函数,因为找不到合适的参数
fun show(msg: Nothing) {}
show(null) // 报错
show(throw Exception()) // 虽然不报错,但方法仍然不会调用
在这里,我们定义了一个 show 函数,它的参数类型是 Nothing。由于 Nothing 的构造函数是私有的,我们将无法调用 show 函数,除非我们抛出异常,但这没有意义。
这个概念在泛型星投影的时候是有应用的,具体后面会解释。
作用三
而除此之外,Nothing 还有助于编译器进行代码流程的推断。比如说,当一个表达式的返回值是 Nothing 的时候,就往往意味着它后面的语句不再有机会被执行。如下图所示:
了解了 Nothing 和 Nothing?的关系后,可以画出关系图
平台类型
平台类型在Kotlin中表示为type!(如String!,Int!, CustomClass!)。
Kotlin平台类型本质上就是Kotlin不知道可空性信息的类型,即可以当作可空类型,也可以当作非空类型。平台类型只能来自Java,因为Java中所有的引用都可能为null,而Kotlin中对null有严格的检查和限制。
但是在Kotlin中是禁止声明平台类型的变量的。
具体的代码示例如下:
// Java 代码
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// Kotlin 代码
fun main() {
val person = Person() //
val name = person.name // name 是 String! 类型
println(name.length) // 可能抛出空指针异常
person.name = null // 允许赋值为 null
}
在这个例子中, name 是平台类型,
因为它们来自于 Java 代码。Kotlin 编译器不会检查它们是否为 null,所以需要程序员自己负责。如果要避免空指针异常,可以使用安全调用运算符(?.)或非空断言运算符(!!)来处理平台类型。
println(name?.length) // 安全调用,如果 name 为 null 则返回 null
println(name!!.length) // 非空断言,如果 name 为 null 则抛出异常
平台类型是指 Kotlin 和 Java 的互操作性问题, 在混合项目中要多加注意。
小结
- Any 是所有非空类型的根类型,而 Any? 才是所有类型的根类型。
- Unit 与 Java 的 void 类型相似,代表一个函数不需要返回值;而 Unit? 这个类型则没有太多实际的意义。
- 当 Nothing 作为函数返回值时,意味着这个函数永远不会返回结果,而且还会截断程序的后续流程。Kotlin 编译器也会根据这一点进行流程分析。
- 当 Nothing 作为函数参数时,就意味着这个函数永远无法被正常调用。这在泛型星投影的时候是有一定应用的。
- Nothing 可以看作是 Nothing? 的子类型,因此,Nothing 可以看作是 Kotlin 所有类型的底类型。
- 正是因为 Kotlin 在类型系统中加入了 Unit、Nothing 这两个类型,才让大部分无法产生值的语句摇身一变,成为了表达式。这也是“Kotlin 大部分的语句都是表达式”的根本原因。
泛型:让类型更加安全
Kotlin 的泛型与 Java 一样,都是一种语法糖,即只在源代码中有泛型定义,到了class级别就被擦除了。 泛型(Generics)其实就是把类型参数化,真正的名字叫做类型参数,它的引入给强类型编程语言加入了更强的灵活性。
泛型的优点
- 类型安全:泛型可以在编译时检查类型,从而避免了在运行时出现类型不匹配的错误。这可以提高程序的可靠性和稳定性。
- 代码重用:泛型可以使代码更加通用和灵活,从而可以减少代码的重复和冗余。例如,我们可以编写一个通用的排序算法,可以用于任何实现了 Comparable 接口的类型。
在 Java 中,我们常见的泛型有:泛型类、泛型接口、泛型方法和泛型属性,Kotlin 泛型系统继承了 Java 泛型系统,同时添加了一些强化的地方。
泛型接口/类(泛型类型)
定义泛型类型,是在类型名之后、主构造函数之前用尖括号括起的大写字母类型参数指定:
声明泛型接口
Java:
//泛型接口
interface Drinks<T> {
T taste();
void price(T t);
}
Kotlin:
//泛型接口
interface Drinks<T> {
fun taste(): T
fun price(t: T)
}
声明泛型类
Java
abstract class Color<T> {
T t;
abstract void printColor();
}
class Blue {
String color = "blue";
}
class BlueColor extends Color<Blue> {
public BlueColor(Blue1 t) {
this.t = t;
}
@Override
public void printColor() {
System.out.println("color:" + t.color);
}
}
Kotlin
abstract class Color<T>(var t: T/*泛型字段*/) {
abstract fun printColor()
}
class Blue {
val color = "blue"
}
class BlueColor(t: Blue) : Color<Blue>(t) {
override fun printColor() {
println("color:${t.color}")
}
}
泛型字段
定义泛型类型字段,可以完整地写明类型参数,如果编译器可以自动推定类型参数,也可以省略类型参数:
abstract class Color<T>(var t: T/*泛型字段*/) {
abstract fun printColor()
}
声明泛型方法
Kotlin 泛型方法的声明与 Java 相同,类型参数要放在方法名的前面:
Java
public static <T> T fromJson(String json, Class<T> tClass) {
T t = null;
try {
t = tClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return t;
}
Kotlin
fun <T> fromJson(json: String, tClass: Class<T>): T? {
/*获取T的实例*/
val t: T? = tClass.newInstance()
return t
}
泛型约束
Java 中可以通过有界类型参数来限制参数类型的边界,Kotlin中泛型约束也可以限制参数类型的上界:
Java
public static <T extends Comparable<T>> T maxOf(T a, T b) {
if (a.compareTo(b) > 0) return a;
else return b;
}
Kotlin
fun <T : Comparable<T>> maxOf(a: T, b: T): T {
return if (a > b) a else b
}
where关键字: 多个上界用 where
Java 中多约束: &
public static <T extends CharSequence & Comparable<T>> List<T> test(List<T> list, T threshold) {
return list.stream().filter(it -> it.compareTo(threshold) > 0).collect(Collectors.toList());
}
Kotin 中多约束:where
//多个上界的情况
fun <T> test(list: List<T>, threshold: T): List<T>
where T : CharSequence,
T : Comparable<T> {
return list.filter { it > threshold }.map { it }
}
所传递的类型T必须同时满足 where 子句的所有条件,在上述示例中,类型 T 必须既实现了 CharSequence 也实现了 Comparable。
泛型形参&泛型实参
泛型类:
泛型函数:
泛型的型变
不变
先看一段 Java 代码,我们知道在Java中 ,List<Apple>无法赋值给List<Fruit>
public class JavaGeneryc {
public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
apples.add(new Apple());
List<Fruit> fruits = apples; // 编译错误
for (Fruit fruit : fruits) {
System.out.println(fruit);
}
}
}
class Fruit {
// 父类
}
class Apple extends Fruit {
// 子类
}
但是到了Kotlin这里我们发现了一个奇怪的现象
fun main2(args: Array<String>) {
val stringList:List<String> = ArrayList<String>()
val anyList:List<Any> = stringList//编译成功
}
在Kotlin中竟然能将List<String>赋值给List<Any>,不是说好的Kotlin和Java的泛型原理是一样的吗?怎么到了Kotlin中就变了?其实我们前面说的都没错,关键在于这两个List并不是同一种类型。我们分别来看一下两种List的定义:
虽然都叫List,也同样支持泛型,但是Kotlin的List定义的泛型参数前面多了一个 out关键词(加上out 发生协变 ),这个关键词就对这个List的特性起到了很大的作用。
普通方式定义的泛型是不变的,简单来说就是不管类型A和类型B是什么关系,Generic与Generic(其中Generic代表泛型类)都没有任何关系。比如,在Java中String是Oject的子类型,但List<String>并不是List<Object>的子类型,在Kotlin中泛型的原理也是一样的。Kotin 使用 out 才发生了变化。
out 位置与 in 位置
函数参数的类型叫作in位置,而函数返回类型叫作out位置
协变 :保留子类型化关系
如果在定义的泛型类和泛型方法的泛型参数前面加上out关键词,说明这个泛型类及泛型方法是协变,简单来说类型A是类型B的子类型,那么Generic也是Generic的子类型,
协变点 (out 位置)
函数返回值类型为泛型参数。
协变的特征
只能消费,只能取
- 子类型化会被保留(Producer<Cat>是Producer<Animal>的子类型)
- T只能用在out位置
interface Book
interface EduBook : Book
class BookStore<out T : Book> {
fun getBook(): T {
TODO()
}
}
fun covariant(){
// 教材书店
val eduBookStore: BookStore<EduBook> = BookStore<EduBook>()
// 书店
val bookStore: BookStore<Book> = eduBookStore // 协变,教辅书店是书店的子类型
val book: Book = bookStore.getBook()
val eduBook : EduBook = eduBookStore.getBook()
}
协变小结
•子类型 Derived 兼容父类型 Base
•生产者 Producer<Derived>兼容 Producer<Base>
逆变: 反转子类型化关系
如果在定义的泛型类和泛型方法的泛型参数前面加上in关键词,说明这个泛型类及泛型方法是逆变,简单来说类型A是类型B的子类型,那么Generic是Generic的子类型,类型父子关系反转。
逆变点 (in 位置)
函数参数类型为泛型参数。
逆变的特征
只能生产,只能放入
- 子类型化会被反转(Consumer<Animal> 是 Consumer<Cat>的子类型)
- T只能用在in位置
垃圾不能扔到干垃圾桶,但是可以扔到垃圾桶。
干垃圾可以扔到垃圾桶,也可以扔到垃圾桶。
由此可以看出垃圾桶可以替代干垃圾桶, 所以干垃圾桶是父类型。
open class Waste
// 干垃圾
class DryWaste : Waste()
// 垃圾桶
class Dustbin<in T : Waste> {
fun put(t: T) {
TODO()
}
}
fun contravariant(){
val dustbin: Dustbin<Waste> = Dustbin<Waste>()
val dryWasteDustbin: Dustbin<DryWaste> = dustbin
val waste = Waste()
val dryWaste = DryWaste()
dustbin.put(waste)
dustbin.put(dryWaste)
// dryWasteDustbin.put(waste)
dryWasteDustbin.put(dryWaste)
}
声明为 in ,在 out 位置使用,是会报错的。
逆变小结
- 子类型 Derived 兼容父类型 Base
- 消费者 Consumer<Base>兼容 Consumer< Derived>
- 记忆小技巧: in 表示逆变, in 倒序过来是 ni(逆)。
型变小结
协变 | 逆变 | 不变型 |
---|---|---|
Producer <out T> | Consumer<in T> | MutableList:<T> |
类的子类型化保留了:Producers<Cat>是 Producer<Animal>的子类型 | 子类型化反转了:Consumer<Animal> 是 Consumer<Cat>的子类型 | 没有子类型化 |
T只能在out 位置 | T只能在 in 位置 | T可以在任何位置 |
泛型中的out与in与 Java 上下界通配符关系
在Kotlin中out代表协变,in代表逆变,为了加深理解我们可以将Kotlin的协变看成Java的上界通配符,将逆变看成Java的下界通配符:
//Kotlin使用处协变
fun sumOfList(list: List<out Number>)
//Java上界通配符
void sumOfList(List<? extends Number> list)
//Kotlin使用处逆变
fun addNumbers(list: List<in Int>)
//Java下界通配符
void addNumbers(List<? super Integer> list)
小结
Java 泛型 | Java 中代码示例 | Kotlin 中代码示例 | Kotlin 泛型 |
---|---|---|---|
泛型类型 | class Box<T> | class Box<T> | 泛型类型 |
泛型方法 | <T> T fromJson(String json, Class<T> tClass) | fun <T> fromJson(json: String, tClass: Class<T>): T? | 泛型函数 |
有界类型参数 | class Box<T extends Comparable<T> | class Box<T : Comparable<T>> | 泛型约束 |
上界通配符 | void sumOfList(List<? extends Number> list) | fun sumOfList(list: List<out Number>) | 使用处协变 |
下界通配符 | void addNumbers(List<? super Integer> list) | fun addNumbers(list: List<in Int>) | 使用处逆变 |
总的来说,Kotlin 泛型更加简洁安全,但是和 Java 一样都是有类型擦除的,都属于编译时泛型。
下期分享:
星投影
注解 @UnsafeVariance
内联特化(内联强化) reified
系列
From Java To Kotlin:空安全、扩展、函数、Lambda很详细,这次终于懂了
From Java To Kotlin 2:Kotlin 类型系统与泛型
关于Kotlin 与 Java:哪个更合适和kotlin和java哪个好的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于Android Kotlin RXKotlin Room - 错误 Kotlin.unit、Android Studio 3.0 Canary 1:Kotlin测试或Java测试引用Kotlin类失败、Android Studio 3.0 Canary 1:Kotlin测试或引用Kotlin类的Java测试失败、From Java To Kotlin 2:Kotlin 类型系统与泛型终于懂了等相关知识的信息别忘了在本站进行查找喔。
本文标签: