【Kotlin】【Java】关于Kotlin和Java的比较备忘录

什么样的文章?

如果用Java来编写的话,那么用Kotlin该怎么写呢?一个关于代码比较的备忘录。

感谢您提供的评论,我根据您的意见进行了修改和扩充,解释了@JvmField和const之间的区别。

最近,我在玩Kotlin,對它產生了興趣。
有很多整理Kotlin語法的文章,
但找不到跟Java代碼進行比較的,所以我會記錄下來。
在工作上,大部分還是用Java,為了避免混淆所以記錄下來的目的。

请写一些基础的东西。

声明变量

操作 Kotlin Java 定数 修飾子 val 変数名 : 型 修飾子 final 型 変数名 変数 修飾子 var 変数名 : 型 修飾子 型 変数名 メソッド・関数 fun 関数名(変数名: 型…): 戻り値 戻り値 関数名(型 変数名…)

型推論 –> 类型推论

在 Kotlin 中,由于类型推断的存在,可以省略类型而定义变量。

String str = "文字列";
final int value = 0;
var str = "文字列"
val value = 0

字符序列

字符串的拼接

在Kotlin中,可以在字符串字面值中插入表达式。

String name = "Benjamin Franklin";
int age = 20
String text = "Your name is " + name + ". You'll be " + (age + 1) + " years old next year.";
var name = "Benjamin Franklin"
var age = 20
var text = "Your name is $name. You'll be ${age + 1} years old next year."

如果只插入变量名,则可以省略花括号。

原始字符串字面意思是直接使用字符表示的字符串。

用三个双引号括起来的字符串被称为原始字符串字面量,
它们不会转义特殊字符如反斜杠和换行,并保持原样显示。

String add = "IJKL";
String text = "ABCD\n" +
               "EFGH\n" +
               add + "\n" +
              "MNOP";
var add = "IJKL"
var text = """
ABCD
EFGH
$add
MNOP
"""

如果你想要排除缩进等等的影响,
如果不想要反映缩进,可以在行首放置一个竖线,并调用trimMargin()。

var add = "IJKL"
var text = """
           |ABCD
           |EFGH
           |$add
           |MNOP
           """.trimMargin()

可选的 (Kě de)

在众多Kotlin功能中,这是其中一个最强大的。
Kotlin被设计为null安全,它的默认设置是不允许出现null值。
举例来说,

val a: String = null

这将会导致编译错误。
因为在Kotlin中默认情况下不允许null,所以会这样。
如果想要允许null,可以在类型后面加上?。

val a: String? = null // これはOK

如果要访问带有可选标记的变量,必须始终进行空值检查。与Java进行比较。

String a = null;
a.contains("hoge"); // 当然nullなのでヌルポで落ちる
var a: String? = null
a.contains("hoge") // そもそもコンパイルが通らない

这段 Kotlin 代码会在编译时被拒绝。
因为它没有保证 NotNull。
要访问 Nullable 的类型,需要这样做。

首先是Java中熟悉的空值检查。

String a = null;

if (a != null) {
    a.contains("hoge"); // nullならここは通らない
}

如果使用Java8,可以使用一种被称为Optional的类似功能。

Optional<String> a = Optional.of(null);
a.ifPresent(notNull -> notNull.contains("hoge"))

最重要的Kotlin

val a: String? = null
a?.contains("hoge")

如果是调用可为空类型的函数,只需要像?.那样写即可。
如果a为Null,则不执行contains并且什么也不做。

与Java相比,可以看出Kotlin的编写方式非常简洁。而Java只是在运行时进行检查,即使使用Java8的Optional,也不能解决忘记使用Optional的根本问题。而Kotlin在编译时进行检查,因此不会出现空值检查的遗漏情况。

关于Kotlin的Optional,有足够的内容可以写一篇文章,所以我将它整理到另一篇文章中。

【空安全性】 Kotlin与Java比较备忘录 正确使用空安全的方法。

函数 (方法)

其实我不太明白函数和方法的区别。
在这里,为方便起见,我统一使用函数。

基本句型

アクセス修飾子 戻り値型 関数名(引数型 引数名) {
    処理
} 

// 例
public int add(int a, int b) {
    return a + b;
}

// 呼び出し側
add(2, 3);
アクセス修飾子 fun 関数名(引数名:引数型): 戻り値型 {
    処理
}

// 
fun add(a:Int, b:Int): Int {
    return a + b
}

// 呼び出し側
add(2, 3)

在Kotlin中,如果省略了访问修饰符,默认为public。

可变长度参数

public int sum(int... args) {
    int sum = 0;
    for (int value: args) {
        sum++ value;
    }
    return sum;
} 

Kotlin不使用三个句点,而是使用修饰符”vararg”。

fun sum(vararg args:Int) {
    var sum:Int = 0
    for (value:Int in args) {
        sum++ value;
    }
    return sum
} 

具有命名参数的函数

在Kotlin中,调用函数时可以指定参数名称。
例如,在参数较多时特别有效。

public String chaosArguments(String who, String where, String when, String what, String why) {
    return when + "に" + where + "で" + who + "が" + what + "を" + why + "だからした";
}

chaosArguments("ウメハラリュウ", "ガイルステージ", "制限時間残り10秒", "昇竜拳", "小足見てから余裕");
// 制限時間残り10秒にガイルステージでウメハラリュウが昇竜拳を小足見てから余裕だからした

太难懂了!!参数顺序也是!例题也是!

fun chaosArguments(who:String, where:String, when:String, what:String, why:String): String {
    return "$whenに$whereで$whoが$whatを$whyだからした"
}

chaosArguments(
who = "ウメハラリュウ",
where = "ガイルステージ",
when = "制限時間残り10秒",
what = "昇竜拳",
why = "小足見てから余裕"
)

像这样,
参数名 = 值
可以做到。
可以更清楚地知道把何值传递给哪个参数。

顺便提一下,函数的参数名称不必按照函数参数的定义顺序。

chaosArguments(
where = "ガイルステージ",
when = "制限時間残り10秒",
who = "ウメハラリュウ",
what = "昇竜拳",
why = "小足見てから余裕"
)

可以用其他的方式。

类声明

基本语法

public class ClassName {
    // フィールド
    private int param1;
    private String param2;

    // コンストラクタ
    public ClassName(int param1, String param2) {
        this.param1 = param1;
        this.param2 = param2;
    }
    // メソッド
    private void myMethod() {
        // 何かの処理
        return;
    }
}
class ClassName(private val param1: Int, private val param2: String) {
    // メソッド
    private fun myMethod() {
        // 何かの処理
        return
    }
}

在Kotlin中,与Java不同的是,构造函数是直接在类名之后写的。
据说在Kotlin中被称为主构造函数。
参数部分可以声明修饰符、val和var属性。
顺便提一下,如果主构造函数不带参数,可以省略如下。

class ClassName{
  // hogehoge
}

构造函数的各种重载

如果在构造函数内部需要进行某些处理

public class ClassName {
    private int param1;
    final private String param2;

    // コンストラクタ
    public ClassName(int param1, String param2) {
        this.param1 = param1 + 2;
        this.param2 = param2 + "add String";
    }
}
class ClassName(param1: Int, param2: String) {
    private var param1: Int
    private val param2: String

    init {
        this.param1 = param1 + 2
        this.param2 = param2 + "add String"
    }
}

如果在构造函数中不直接赋值参数,而是进行某种计算操作的话,可以使用初始化代码块来写入。
在这种情况下,需要像Java一样在顶层声明变量。

如果需要编写多个构造函数的话

public class ClassName {
    private int param1;
    private String param2;

    public ClassName(int param1){
        this.param1 = param1;
        this.param2 = "initial";
    }

    public ClassName(int param1, String param2) {
        this.param1 = param1;
        this.param2 = param2;
    }

    public ClassName(int param1, String param2, String param3) {
        this.param1 = param1;
        this.param2 = param2;
        String addParam = param3;
    }
}
class ClassName {
    private var param1: Int
    private var param2: String

    constructor(param1: Int) {
        this.param1 = param1
        this.param2 = "initial"
    }

    constructor(param1: Int, param2: String) {
        this.param1 = param1
        this.param2 = param2
    }

    constructor(param1: Int, param2: String, param3: String) {
        this.param1 = param1
        this.param2 = param2
        val addParam = param3
    }
}

如果使用构造块,可以添加无数个。
将主构造函数以外的构造函数称为次要构造函数。
如上所述,只需要次要构造函数也可以。

在Java中,可以使用super关键字从次级构造函数调用主构造函数。

public class ClassName {
    private int param1;

    public ClassName(int param1){
        this.param1 = param1;
    }

    public ClassName(int param1, String param2) {
        this(param1);
    }
}
class ClassName(private val param1: Int) {

    constructor(param1: Int, param2: String) : this(param1) {
    }
}

调用构造函数(参数):this(参数)。

设定参数的初始值

例如,可以通过提供多个构造函数来实现以下方式:可以在param1和param2两个参数中都传递参数,或者只传递param1的参数,而param2则使用默认值。这种实现方式在Android中经常看到。在Kotlin中,可以通过非常简单的语句来实现这一点。

public class ClassName {
    private int param1;
    private String param2;

    public ClassName(int param1){
        this(param1, "initialize");
    }

    public ClassName(int param1, String param2) {
        this.param1 = param1;
        this.param2 = param2;
    }
}
class ClassName @JvmOverloads constructor(private val param1: Int, private val param2: String = "initialize")

太厉害了!只需要一行代码就可以描述出来!通过将参数设置为 变量:类型 = 初始值 ,可以指定在没有给定参数的情况下的初始值。
由于Java没有这样的功能,可以使用名为 @JvmOverloads 的注解来解决。

如果你想在Android上创建自定义视图的话

public class CustomView extends View {
    public CustomView(Context context) {
        super(context);
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}
class CustomView : View {
    @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, style: Int = 0) : super(context, attrs, style) {
    }
}

用这样超级简单的描述就能成功。太棒了!!

此外,主要构造函数是

类 ClassName(val param1: Int){…} 的汉语本地化表达:
class ClassName(参数1: Int){…}

写代码时通常会用到简写法,例如添加注释或访问修饰符等,则需要正确使用。

类 ClassName public @annotation constructor(val param1: Int){…}

类 ClassName public @annotation constructor(val param1: Int){…} 可以被改写为:

必须写。

类的继承 de

基本的语法结构

// スーパークラス
public class Human {
    private String sex;
    public Human(String sex) {
        this.sex = sex;
    }
}
// サブクラス
public class Hero extends Human {

    public Hero(String sex) {
        super(sex);
    }
} 
// スーパークラス
open class Human(private var sex: String)
// サブクラス
class Hero(sex: String) : Human(sex)

在类名后面加上冒号并指定超类名。
如果超类有主构造函数,则在继承时必须进行初始化。

开放修饰符

就Java而言,默认情况下可以在没有任何特别操作的情况下继承类,但是Kotlin中没有用open修饰的类无法被继承。
换句话说,Kotlin的类默认情况下相当于Java中的final class。
似乎是要阻止过度便利的继承。

内部类

基本语法结构

public class Hero {
    private String name = "まさお";

    private void action() {
        //インナークラスのフィールドやメソッドはインスタンス化すればアクセスできる。
        Equipment eq = new Equipment();
        eq.specialAction();
    }
    // インナークラス
    public class Equipment {
        private String sword = "木の棒";

        private void specialAction() {
            // アウタークラスはインスタンス化しなくてもアクセス可能。
            String owner = name;
            action();
        }
    }
}
class Hero {
    private val name: String? = "まさお"

    private fun action() {
     // ここはJavaと同じ
        val eq = Equipment()
        eq.specialAction()
    }

    inner class Equipment {
        private val sword: String = "木の棒"

        // アウタークラスからアクセスできるようにするにはアクセス修飾子がinternalかpublicじゃないといけない
        internal fun specialAction() {
            val owner = name
            action()
        }
    }
}

内部类可以通过以下方式进行定义:
内部类InnerClassName{}

Java和Kotlin有所不同的地方在于,Java中从外部类可以访问内部类的私有方法,但在Kotlin中,如果方法被标记为私有(private),则无法访问。必须使用internal这个访问修饰符。

指定this的实例

如果想要从内部类指定外部类的实例,用Java的写法会有所不同。

public class Hero {

    public class Equipment {
            Hero hero = Hero.this;
            Equipment eq = this;
    }
}
class Hero {

    inner class Equipment {
            val hero = this@Hero

            val eq1 = this@Equipment
            val eq2= this
    }
}

在Java中,可以使用”外部类名.this”来获取外部类的实例。
在Kotlin中,可以使用”this@类名”来指定获取指定类的实例。

在上面的例子中,”this@Equipment”和”this”是相同的。

属性

说到Java中最无聊的代码,那就是getter和setter。
但如果是用Kotlin的话,这个问题就会有炫酷的解决方案。

基本語法结构

public class ClassName {
    // これはフィールド
    private int param = 0;

    // これはアクセサ
    public int getParam() {
        return param;
    }
    // これもアクセサ
    public void setParam(int mParam) {
        param = mParam;
    }
}
class Hero {
    // これはフィールドではなくプロパティ 内部的にアクセサが生成される
    var param = 0
}

只需要一个选项-用中文对以下内容进行释义:
其实,在Kotlin中并不存在字段(field)这个概念,表面上看起来像是字段的东西实际上被称为属性(property)。
在Kotlin中,声明属性时会在内部自动生成访问器(accessor)。
即使不自己创建getter/setter,也可以通过访问器进行引用。

如果使用val进行变量定义,那么显然无法使用setter。

自定义的getter/setter

也可以自己定义getter/setter方法。

public class ClassName {
    private int param = 0;
    // 絶対値で返す
    public int getParam() {
        return Math.abs(param);
    }
    // 絶対値を設定する
    public void setParam(int mParam) {
        param = Math.abs(mParam);
    }
}
class ClassName {
    var param = 0
        get(){
            return Math.abs(param)
        }
        //set()の引数名は慣例的にvalueにするようだ
        set(value) {
            field = Math.abs(value)
        }
}

在这种情况下,不会内部创建访问器。
在自定义setter中出现的field被称为backing field,它会自动定义用于引用属性值的时候。

For example

var param = 0
        //バッキングフィールドを使わない例。循環参照になって落ちる。
        set(value) {
            param = Math.abs(value)
        }

如果创建这样的定制setter,将导致在定制setter中再次调用param的setter,从而导致循环引用。
param = Math.abs(value)
注意,这不是将Math.abs(value)的值赋给param,而是将Math.abs(value)作为param的setter参数传入并调用它的意思。

重写

属性也可以被覆盖。
被覆盖的属性需要像类一样使用open修饰符。

Java的字段将隐藏而不是重写。
参考:http://log.nissuk.info/2012/03/java.html

class SubClass : SuperClass() {
    override var param = "sub"
    get() = "override"
}

open class SuperClass {
    open var param = "super"
}

不仅可以设置值,还可以重写getter/setter。当然,如果从子类中访问param,将返回字符串”override”。

静态字段

在Kotlin中,不存在Static的概念。如果想要处理类似于Java中的Static字段的情况,可以使用”companion object”这个单例对象。

public class ClassName {
    public static final int STATIC_VALUE = 1;
    public static final 

}
class ClassName {
    companion object {
      @JvmField val STATIC_VALUE = 1
    }
}

@JvmField这个注解在从Java端调用时,不需要通过companion object来调用。

如果没有@JvmField,访问Kotlin的STATIC_VALUE的方法是什么?

int value = ClassName.Companion.getSTATIC_VALUE();

然而,对于标有@JvmField注解的属性来说,情况会有所不同。

int value = ClassName.STATIC_VALUE;

可以以与Java的static字段相同的方式访问。

此外,Java的基本类型和字符串类型还有一个叫做const的修饰符。

static final int HOGE = 0;
static final List<String> FUGA = new ArrayList<>();
companion object {
  const val HOGE = 0
  const val FUGA = ArrayList<String>() // これはダメ。
  @JvmField val FUGA = ArrayList<String>() // これはOK。
}

虽然我不太清楚const和@JvmField之间的区别,但根据阅读这篇文章的感觉,它们在原始类型上似乎没有差异。
const和@JvmField之间的差异似乎与编译后的行为有关。

const用于在字段上定义初始值,而@JvmField用于在静态构造函数中定义初始值。

试试看把以下代码转化为字节码来查看。

class A {
    companion object {
        const val a = 12
        @JvmField val b = 12
        @JvmField val c = Date() // 非プリミティブ
    }
}
// access flags 0x19
  public final static I a = 12

  // access flags 0x19
  public final static I b = 12
  @Lkotlin/jvm/JvmField;() // invisible

  // access flags 0x19
  public final static Ljava/util/Date; c
  @Lkotlin/jvm/JvmField;() // invisible
  @Lorg/jetbrains/annotations/NotNull;() // invisible

  // access flags 0x8
  static <clinit>()V
    NEW com/hoge/A$Companion
    DUP
    ACONST_NULL
    INVOKESPECIAL com/hoge/A$Companion.<init> (Lkotlin/jvm/internal/DefaultConstructorMarker;)V
    PUTSTATIC com/hoge/A.Companion : Lcom/hoge/A$Companion;
   L0
    LINENUMBER 12 L0
    BIPUSH 12
    PUTSTATIC com/hoge/A.b : I
   L1
    LINENUMBER 13 L1
    NEW java/util/Date
    DUP
    INVOKESPECIAL java/util/Date.<init> ()V
    PUTSTATIC com/hoge/A.c : Ljava/util/Date;
    RETURN
    MAXSTACK = 3
    MAXLOCALS = 0

总结起来就是这个样子。

初期化のタイミング const @JvmField(プリミティブ)  @JvmField(非プリミティブ) フィールド定義 ○ ○ × staticコンストラクタ × ○ ○

如果使用@JvmField来定义原始类型,会导致两次赋予初始值,因此最好将原始类型定义为const,非原始类型定义为@JvmField。

排列

事先仅声明变量,并稍后将实体赋值给它。

String[] a;
a = new String[5];
var a: Array<String?>
a = arrayOfNulls<String>(5)

在Kotlin中,如果在赋值时不明确在类型后面加上?,那么不允许为空值,因此需要使用Array
可以使用arrayOfNulls<类型>()来创建一个内部为空值的数组。

同时定义变量和实体

String[] a = new String[5];
var a = arrayOfNulls<String>(5)

如果在声明同时将Null赋值,那么?是不需要的。

给予初始值

String[] a = {"a","b","c"};
var a = arrayOf("a","b","c")

使用工厂函数进行初始化。

String[] a = {"0","2","4","6","8"};
var a = Array(5,{ i -> (i * 2).toString() })

在中国,将数组(Array())的第一个参数作为元素数量,第二个参数作为索引值和计算表达式。

操作数组元素

将其应用

a[0]= "test";
a[0] = "test"
a.set(0, "test")

获得

final String element = a[0];
val element = a[0]
val element = a.get(0)

获取要素数量

final int size = a.length;
val size = a.size

获取所有要素

// 添え字付きのループ
for(int i = 0; i < c.length; i++){
    final String element = c[i];
}
// 要素を直接取得するループ
for(String elm : c){
    final String element = elm;
} 
// 添え字付きのループ
for (i in c.indices) {
    val element = c[i]
}
// 要素を直接取得するループ
for (elm in c) {
    val element = elm
}
// forEachを使った要素を直接取得するループ
c.forEach{elm -> val element = elm}

Kotlin独有的循环方式

// 添え字と要素の中身を同時に取り出す
for ((index, value) in c.withIndex()) {
    println("the element at $index is $value.")
}

收藏品

在Kotlin中,除了Java的数组对应的Array之外,当然还有其他与集合相对应的东西。

列出

生成List

List<Integer> list = Arrays.asList(0,1,2);
val list = listOf(0,1,2)

获取要素

final int elm = list.get(0);
val elm = list[0]
val elm = list.get(0)

不变(immutable)和可变(mutable)

如何在List的元素中进行赋值或添加。
包括后面提到的Map和Set在内,Kotlin的集合是不可变的,一旦生成后就无法更改。
而Java的集合是可变的,可以进行赋值和添加操作而无需特意注意。

当然,Kotlin也提供了可变集合,如果想要进行赋值或添加操作,可以使用这个。

增加要素

List<Integer> list = new ArrayList<>();
list.add(0);
val list: MutableList<Int> = mutableListOf()
list.add(0)

替换要素

List<String> list = Arrays.asList("one","two","three");
list.set(0,"new-one");
val list = mutableListOf("one","two","three")
list[0] = "new-one"

删除的要素 de

List<String> list = Arrays.asList("one","two","three");
list.remove(0);
val list = mutableListOf("one","two","three")
list.removeAt(0)

地图

地图基本上与列表相同。

创建地图。

Map<String, Integer> map = new HashMap<String, Integer>() {
    {
        put("one", 1);
        put("two", 2);
        put("three", 3);
    }
};
val map = mapOf("one" to 1, "two" to 2, "three" to 3)

获取值

final int value = map.get("one");
val value = map["one"]
val value = map.get("one")

添加元素 (Tianji yuansu)

Map<String, Integer> map = new HashMap<String, Integer>();
map.put("one", 1);
val map: MutableMap<String, Int> = mutableMapOf()
map.put("one", 1)

元素的替代

Map<String, Integer> map = new HashMap<String, Integer>() {
    {
        put("one", 1);
        put("two", 2);
        put("three", 3);
    }
};
map.put("one", 100);
val map = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
map.put("one", 100)

删除要素

Map<String, Integer> map = new HashMap<String, Integer>() {
    {
        put("one", 1);
        put("two", 2);
        put("three", 3);
    }
};
map.remove("one");
val map = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
map.remove("one")

设定

由于与List、Map几乎相同,故略去。

界面

基礎句構

public interface MyInterface {
    // 初期値ありのフィールド
    String str = "interface";

    // 実装なしのメソッド
    // Java8前でも利用可能
    void emptyMethod();

    // デフォルト実装ありのメソッド
    // Java8以降のみ利用可能
    void defaultMethod(){
        // do something
    }
}
interface MyInterface {
    // 実装なしのメソッド
    fun emptyMethod()

    // デフォルト実装ありのメソッド
    fun defaultMethod() {
        // do something
    }

    companion object {
      val str= "interface"
    }
}

抽象属性(字段)

在Java中,无法将字段声明为抽象的,而在Kotlin中可以定义抽象属性。

interface MyInterface {
    // 暗黙的にabstractになってる
    val abstractStr: String

    // getter/setterは定義できる
    var strHasAccessor: String
    get() = strHasAccessor
    set(value) {
        strHasAccessor = value
    }

    // 以下のような実装は不可。初期値を与えることはできない
    val str = "init"
}

接口中的属性无法具有初始值。

接口的实现

尝试写一下实现以下接口的类,该接口是用Kotlin编写的。
在Java中,通过extends来表示继承,通过implements来表示实现接口,但在Kotlin中,实现接口的形式为:
class类名 :(冒号)接口名
当然,也可以使用逗号分隔来同时实现多个接口。

interface MyInterface {
    // abstractプロパティ
    val abstractStr: String

    // 実装なしメソッド
    fun abstractMethod()

    // 実装ありメソッド
    fun defaultMethod(){
        // do something
    }
}
public class MyClass implements MyInterface {

    @NonNull
    @Override
    public String getAbstractStr() {
        // return値に設定値を入れる
        return "init";
    }

    @Override
    public void abstractMethod() {
    // do something
    }

    // Java8以前ではデフォルト実装ありのメソッドも必ず@Overrideしなくてはいけないようだ
    @Override
    public void defaultMethod() {
       // do something
    }
}
class MyClass : MyInterface{
    override val abstractStr: String = "init"

    override fun abstractMethod() {
    }
}

值得注意的是,有以下两个方面:
1. 可以在没有访问器定义的情况下直接为抽象属性设置初始值。
2. 默认实现的defaultMethod()可以由用户决定是否重写。

bannerAds