一文快速了解kotlin与Java的区别

发布于:2024-05-02 ⋅ 阅读:(20) ⋅ 点赞:(0)

image.png

基本语法的区别

变量声明

在 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修饰,会生成 setget 方法
var name: String = ""

//由val修饰,会生成 get 方法,不会生成set方法
val name: String = "xxxx"

//private修饰,无论由val还是var修饰,默认不会生成setget方法
private var name: String = ""

//生成自定义的setget方法
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()
}

集合的区别

Kotlin 中的集合和 java 中的一致。但是 Kotlin 新增了很多接口,大大简化了集合的操作,所有的api可以看。这里介绍一些常用的接口:

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 中,更推荐使用协程而不是线程来执行异步操作。kotlin 协程的教程可以看

注解的区别

kotlin 中的注解和Java类似,不同的是创建注解的方式。在 kotlin 中通过 annotation class 来创建注解。代码如下:

@Target(AnnotationTarget.CLASS)  
public annotation class User(  
    val name: String  
)

反射的区别

要使用 kotlin 的反射,需要先增加如下的依赖:

implementation "org.jetbrains.kotlin:kotlin-reflect:1.9.10"

然后我们就可以通过 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 是否在源代码中标记为可空类型,即这个类型的后面有没有“?”修饰。

参考