一文快速了解kotlin与Java的区别
学习一门新的语言,最好的方法就是从自己熟悉的语言中找不同。这篇文章就从基本语法、逻辑判断和控制、属性、函数、接口、抽象类、类、集合、IO、泛型、异常、多线程、反射角度看 Kotlin 与 Java的区别。
基本语法的区别
变量声明
在 Kotlin 中,var
表示变量,val
表示只读的量,而且数据的类型写在变量名的后面,例如
var age:Int = 18
val name:String = "张三"
在kotlin中,属性默认是 private。之所以我们能直接访问属性,是因为 kotlin 默认会生成其对应得set、get方法。如果不想默认生成set/get方法,可以加上 @JvmField
注解。
函数声明
kotlin 通过fun
关键字声明函数,例如
fun methodName(a:Int, b:String): String {
return "a = $a b = $b";
}
//可以设置默认值
fun methodName(
a:Int = 0,
b:String = "test"
): String {
return "a = $a b = $b";
}
//调用时可以加入形参
methodName(
a = 1111,
b = "1234567"
)
在kotlin中,kotlin的函数默认是 public ,并且是 final 的。如果想让函数可以被继承,需要加上 open
关键字
逻辑判断和控制
if
在kotlin中,我们可以直接获取 if 表达式返回的值。除此之外,其他和java一样
val result = if (i > 0) "1" else "0"
对于null的判断,提供了 Elvis 表达式(?:)的语法糖来简化
fun getLength(text: String?): Int {
return text?.length ?: 0
}
//等价于
fun getLength(text: String?): Int {
return if (text != null) text.length else 0
}
switch
在 kotlin 中没有 switch,它通过 when 来代替,而且功能更强大。示例如下:
when(i) {
1 -> print("a")
2 -> print("b")
else -> print("c")
}
//也可以获取 when 表达式返回的值
val result = when(i) {
1 -> "a"
2 -> "b"
else -> "c" // 如果去掉这行,会报错
}
print(result)
for
//顺序遍历,步长为2
for(i in 0 .. 10 step 2) {
println(i)
}
其中 ..
表示区间,即 [0, 10]
。如果需要不包括下届,则使用 util,这时区间为 [0, 10)
。如果需要逆序遍历则需要 downTo
//逆序遍历,步长为1
for(i in 10 downTo 0 step 1) {
println(i)
}
遍历数组时,如果需要知道当前的 index,则需要使用 withIndex
方法
val array = arrayOf(1, 2, 3, 4, 5)
for((index, value) in array.withIndex()) {
}
while
while
和 java 中的使用没有区别
& 和 &&
在 kotlin 中用 and
代替 &
来进行与运算。&&
和 java 中的使用没有区别,and
也可以像 &&
一样连接两个 Boolean 表达式。and
和 &&
不同是,&&
是智能的,and
不是智能的。当第一个条件为 false 时,&&
不会执行下一个表达式;而 and
会执行所有表达式,最后根据这些表达式的结果计算出最后的结果。
| 和 ||
在 kotlin 中用 or
代替 |
来进行或运算。||
和 java 中的使用没有区别,or
也可以像 ||
一样连接两个 Boolean 表达式。or
和 ||
不同是,||
是智能的,or
不是智能的。
^
在 kotlin 中用 xor
代替 ^
来进行异或操作
移位操作
在 kotlin 中用 shl
代替 <<
来进行左移操作;用 shr
代替 >>
来进行右移操作。
属性的区别
访问控制的区别
在 Kotlin 中有这四个可见性修饰符:private
、 protected
、 internal
和 public
。 默认可见性是 public
。其中 internal
意味着模块内可见,其他的修饰符和Java作用类似。
类型的区别
基本类型
我们都知道在 java 中有基本数据类型和引用数据类型。而在 kotlin 中,一切都是对象。因此在 kotlin 中没有基本数据类型,如 Int、Char、Boolean 等等。
还有一点需要注意,在 kotlin 中数字类型是没有类型自动转换的,需要主动转换。
val a: Float = 1.toFloat()
可空类型
为了解决空安全的问题,kotlin 还提出了可空类型的概念。例如,在 kotlin 中,String
不能赋值null
,如果要用可为null
的字符串类型则需要使用String?
,String
类型不能用String?
赋值,要赋值只能通过!!
强制转换。
var m:String = "123"
var n:String? = null
m = n!!
数组类型
在kotlin中通过 arrayof() 来创建数组
val arrayInt = arrayOf(1, 2, 3)
val arrayString = arrayOf("a", "b", "c")
Kotlin 的数组仍然不属于集合,但它的一些操作是跟集合统一的。
函数类型
在 kotlin 中函数是第一公民,函数也可以作为类型,也可以引用。
var function: (String) -> Unit = ::methode
var function1: ((String) -> String)? = null
fun methode(str: String) {
}
如上代码所示,(String) -> Unit
和 ((String) -> String)?
是函数类型,::methode
是函数引用
类型判断和转换
在 kotlin 中,是通过 is 来判断对象的类型,通过 as 来转换类型的
常量的区别
kotlin 常量的声明与java有大的不同。我们使用 val 关键字修饰的变量与 java 中 final 修饰的变量作用是相同的。但是 val 只是表示可读变量,即该变量无法修改。但是这不意味着它不可变,如果我们自定义变量的 get 方法就可以让获取的值不一样。示例如下:
var k = 1
val j: Int
get() {
return if(k > 0) 1 else 0
}
如果要表示不可变的量,需要通过 const val 来修饰的。const
只能修饰没有自定义 getter 的 val
属性,而且它的值必须在编译时确定,而且 const 只能在单例或者伴生对象中使用。
静态变量的区别
kotlin 里没有静态变量和静态方法,在kotlin中通过 @JvmStatic
注解修饰才实现。需要注意,只有在object
或者 companion object
中才能使用 @JvmStatic
注解。示例如下:
object A {
//静态变量
@JvmStatic
var i = 0
//静态方法
@JvmStatic
fun method() {
}
}
在伴生对象的内部,如果存在@JvmStatic
修饰的方法或属性,它会被挪到伴生对象外部的类当中,变成静态成员。示例如下
class A {
companion object {
@JvmStatic
var i = 0
@JvmStatic
fun method() {
}
}
}
函数的区别
构造函数的区别
//主构造函数
class Person(val name: String, val age: Int) {
//次构造函数
constructor(name: String, age: Int, id: String): this(name, age) {
}
}
私有构造函数
//私有化主构造函数
class Person private constructor(val name: String, val age: Int) {
//私有化次构造函数
private constructor(name: String, age: Int, id: String): this(name, age) {
}
}
需要注意当主构造函数的参数没有带上 val 或者 var 时,kotlin 编译器不会生成对应的成员变量,代码如下:
public final class Person {
private Person(String name, int age) {
}
}
set 和 get 函数的区别
//由var修饰,会生成 set 和 get 方法
var name: String = ""
//由val修饰,会生成 get 方法,不会生成set方法
val name: String = "xxxx"
//private修饰,无论由val还是var修饰,默认不会生成set、get方法
private var name: String = ""
//生成自定义的set、get方法
private var name: String = ""
get() = "xxxx"
set(value) {
field = "xxx$value"
}
//设置只读属性
var name: String = ""
peivate set
高阶函数
上面提到函数也可以作为类型,那么我们就可以把函数作为参数和返回值,像这种将函数用作参数或返回值的函数,我们把它叫做高阶函数。代码示例如下:
class Member(var name: String, var id: String) {
//函数作为参数
fun methode(block: (String) -> Unit) {
block(name)
}
//函数作为返回值
fun methode1(): (String) -> Unit {
return ::method2
}
fun method2(str: String) {
}
}
接口的区别
Kotlin 的接口跟 Java 最大的差异就在于,接口的方法可以有默认实现,同时也可以有成员属性。而java的接口内部的变量都是静态变量;而kotlin中是成员变量,它的实现是生成了对应的set、get方法。在java8中,通过 default 关键字也可以给接口加上默认方法。代码示例如下:
interface Behavior {
// 接口内的可以有属性
val canWalk: Boolean
// 接口方法的默认实现
fun walk() {
if (canWalk) {
// do something
}
}
}
class Person(val name: String): Behavior {
// 重写接口的属性
override val canWalk: Boolean
get() = true
}
抽象类的区别
与java一样,都需要用 abstract 修饰类;抽象方法也需要用 abstract 修饰方法。需要注意kotlin的继承可以重写父类的属性,而Java不可以。
abstract class AbstractClass {
open var mode: Int = 0
abstract var type: Int
abstract fun methode()
open fun methode1() {
}
}
class AbstractClassImpl(override var type: Int) : AbstractClass() {
override var mode: Int = 1
override fun methode() {
}
override fun methode1() {
}
}
类的区别
内部类的区别
kotlin 中的内部类默认是静态内部类,如果需要声明普通内部类,需要加上 inner 关键字。代码如下:
class OuterClass {
//相对于java中的静态内部类
class InterClass {
}
//相对于Java中的普通内部类
inner class RealInterClass {
}
}
在kotlin中需要使用 object 声明匿名内部类。实现接口不需要带上(),实现类需要带上(),里面传入构造需要的参数.代码如下:
fun invoke(runnable: Runnable) {
}
abstract class RunnableImpl: Runnable {
}
invoke(object: Runnable {
override fun run() {
}
})
invoke(object: RunnableImpl() {
override fun run() {
}
})
单例的区别
object A {
fun method() {
}
}
A.method()
当我们使用 object 关键字定义单例类时,kotlin 编译器会将其转换成静态代码块的单例模式。这种单例模式的缺点:
- 不支持懒加载
- 不支持传参构造单例
嵌套单例和伴生对象
class A {
//嵌套单例
object B {
fun method() {
}
}
}
//可以这样调用方法
A.B.method()
如果不想要带上 B 的类名,可以加上 companion 关键字,这时 B 就变成了伴生对象
class A {
//伴生对象,B 类名可以不要
companion object B {
fun method() {
}
}
}
//直接调用方法
A.method()
嵌套单例,是 object 单例的一种特殊情况;伴生对象,是嵌套单例的一种特殊情况。
通过伴生对象 Double Check 来创建单例
class A private constructor(val name: String, val id: String) {
companion object {
@Volatile
private var INSTANCE: A? = null
fun getInstance(name: String, id: String): A {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: A(name, id).also { INSTANCE = it }
}
}
}
}
为了减少代码重复,我们可以定义抽象模板,这样就可以方便的实现 double check 创建单例了。代码如下
//定义模板类
abstract class BaseDoubleCheckSingleton<in T, out V> {
@Volatile
private var INSTANCE: V? = null
abstract fun creator(param: T): V
fun getInstance(param: T): V {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: creator(param).also { INSTANCE = it }
}
}
}
class B private constructor(param: String) {
//实现模板类
companion object: BaseDoubleCheckSingleton<String, B>() {
override fun creator(param: String): B = B(param)
}
}
//获取单例
B.getInstance("test")
Java Bean
data class Person(val name: String, val age: Int)
在Java中我们常常使用Java bean来保存数据对象,在 Kotlin 中直接提供了数据类来处理这种情况。数据类的构造函数至少需要有一个参数。数据类不能被继承,不能加 open 关键字。
在 Kotlin 当中,编译器会为数据类自动生成一些有用的方法。它们分别是:equals()、hashCode()、toString() componentN()等等
枚举的区别
Kotlin中的枚举和Java是一样,但是在kotlin中一般使用密封类,而不是枚举类。密封类是更强大的枚举类。它们具体的区别简单来说,就是密封类可以创建对象,而枚举是单例的。密封类示例如下:
sealed class Color {
class Red : Color()
class Blue : Color()
}
集合的区别
emptyList<Int>()//空List
emptySet<Int>()//空Set
emptyMap<Int, Int>()//空Map
listOf(1, 2, 3)//不可变的list
mutableListOf(1, 2, 3, 4)//可变的list
setOf(1, 2, 3)//不可变的set
mutableSetOf(1, 2, 3)//可变的set
mapOf(1 to 1, 2 to 2, 3 to 3)//不可变的map
mutableMapOf(1 to 1, 2 to 2, 3 to 3)//可变的map
IO的区别
以上这些示例中用的都是Java提供的API,kotlin提供了一些扩展方法和扩展属性,可以更加方便的操作文件。
泛型的区别
Kotlin 的泛型和 Java 类似。不同点是 Java 泛型中有通配符类型,而 Kotlin 中没有。相反,Kotlin 新增协变、逆变与星投影,它们与Java的对应关系如下:
协变 | 逆变 | 星投影 | |
---|---|---|---|
kotlin | <out T> | <in T> | <*> |
java | <? extends T> | <? super Object> | <?> |
异常的区别
在 Kotlin 中没有受检查的异常,即 try-catch
不是必需的。代码如下所示:
// java中的代码,try-catch 受检异常
try {
file.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
//kotlin中的代码
// 不加上try-catch
file.createNewFile()
// 加上try-catch
try {
file.createNewFile()
} catch (e: IOException) {
throw RuntimeException(e)
}
在 kotlin 中还提供了扩展函数 runCatching
来方便地处理异常,代码如下所示:
kotlin.runCatching {
doSomething()
}.onSuccess {
//成功时回调
}.onFailure {
//异常时回调
}
//获取执行结果
kotlin.runCatching { doSomething() }.isFailure
kotlin.runCatching { doSomething() }.isSuccess
多线程的区别
注解的区别
kotlin 中的注解和Java类似,不同的是创建注解的方式。在 kotlin 中通过 annotation class
来创建注解。代码如下:
@Target(AnnotationTarget.CLASS)
public annotation class User(
val name: String
)
反射的区别
Kotlin能够使用Java的反射机制。另外,Kotlin也有自己的反射机制,但是需要先增加如下的依赖:
implementation "org.jetbrains.kotlin:kotlin-reflect:1.9.10"
相对于java反射,Kotlin反射提供对属性和可空类型的访问权限,这是由于Java没有属性和可空类型的概念。除此之外,也可以使用Kotlin反射来访问各种基于JVM语言编写的代码。
在kotlin 反射中我们就可以通过 member::class
获取对应的 KClass 对象对其进行操作。下面代码示例是修改 Member 中的 name 属性值。
val member = Member("test", "123")
println("name = ${member.name}")
member::class.memberProperties.forEach {
if(it.name == "name" //属性名是否对上
&& it is KMutableProperty1 //是否是可变的
&& it.setter.parameters.size == 2 //设置参数个数是否为2,第一个参数是 obj 自身,第二个是实际的值
&& it.getter.returnType.classifier == String::class //设置的类型是否为String
) {
it.setter.call(member, "new test")
}
}
println("name = ${member.name}")
要了解 Kotlin 的反射,需要先了解 KClass、KCallable、KParameter、KType。它们的介绍如下:
KClass
KClass代表了一个 Kotlin 的类,下面是它的重要成员:
属性 | 作用 |
---|---|
simpleName | 类的名称,对于匿名内部类,则为 null |
qualifiedName | 完整的类名 |
members | 所有成员属性和方法,类型是Collection<KCallable<*>> |
constructors | 类的所有构造函数,类型是Collection<KFunction>> |
nestedClasses | 类的所有嵌套类,类型是Collection<KClass<*>> |
visibility | 类的可见性,类型是KVisibility?,分别是这几种情况,PUBLIC、PROTECTED、INTERNAL、PRIVATE; |
isFinal | 是不是 final |
isOpen | 是不是 open |
isAbstract | 是不是抽象的 |
isSealed | 是不是密封的 |
isData | 是不是数据类 |
isInner | 是不是内部类 |
isCompanion | 是不是伴生对象 |
isFun | 是不是函数式接口 |
isValue | 是不是 Value Class |
KCallable
KCallable 代表了 Kotlin 当中的所有可调用的元素,比如函数、属性、甚至是构造函数。下面是 KCallable 的重要成员:
属性 | 作用 |
---|---|
name | 属性和函数的名称; |
parameters | 所有的参数,类型是List,指的是调用这个元素所需的所有参数; |
returnType | 返回值类型,类型是 KType; |
typeParameters | 所有的类型参数 (比如泛型),类型是List; |
call() | KCallable 对应的调用方法,在前面的例子中,我们就调用过 setter、getter 的call() 方法。 |
visibility | 可见性; |
isSuspend | 是不是挂起函数。 |
KParameter
KParameter 代表了KCallable当中的参数,它的重要成员如下:
属性 | 作用 |
---|---|
index | 参数的位置,下标从 0 开始; |
name | 参数的名称,源码当中参数的名称; |
type | 参数的类型,类型是 KType; |
kind | 参数的种类,对应三种情况:INSTANCE 是对象实例、EXTENSION_RECEIVER 是扩展接受者、VALUE 是实际的参数值。 |
KType
KType,代表了 Kotlin 当中的类型,它重要的成员如下:
属性 | 作用 |
---|---|
classifier | 类型对应的 Kotlin 类,即 KClass,我们前面的例子中,就是用的 classifier == String::class 来判断它是不是 String 类型的; |
arguments | 类型的类型参数,看起来好像有点绕,其实它就是这个类型的泛型参数; |
isMarkedNullable | 是否在源代码中标记为可空类型,即这个类型的后面有没有“?”修饰。 |
参考
转载自:https://juejin.cn/post/7363635167301287963