Kotlinデータクラス、封印されたクラス、内部クラス、ネストされたクラス
Kotlinにはいくつかの特別なクラスがありますが、その中にはデータクラスと封印されたクラスがJavaに存在しないため、それらの使用方法、使用シナリオ、解決する問題、および構文糖衣構文を紹介します。さらに、Kotlinにはネストされたクラスと内部クラスがあります。Kotlinのネストされたクラスは内部クラスではないことに注意してください。これはJavaとは異なるため、内部のネストされたクラスは外部のクラスインスタンスにアクセスできません。
Kotlinでは、内部クラス宣言はinner
キーワード宣言を個別に使用する必要があります
1.内部クラスとネストされたクラス
Javaで内部クラスを表現するということは、実際には、内部クラスを外部クラスにネストすることによって内部クラスが宣言され、内部クラスが外部クラスのプライベートメンバーにアクセスできることを意味することは誰もが知っ ています。
//PageAdapter
public abstract class PageAdapter {
public abstract int getCount();
public abstract String getItem(int position);
}
//PageTest
package com.imooc.test;
import java.util.Arrays;
import java.util.List;
public class PageTest {
private List<String> mData = Arrays.asList("1", "2", "3");
class TestPageAdapter extends PageAdapter {//在Java中只需要把内部类TestPageAdapter声明在外部类PageTest内部即可
@Override
public int getCount() {
return mData.size();//内部类即可以访问外部私有成员
}
@Override
public String getItem(int position) {
return mData.get(position);
}
}
}
ただし、これはKotlinには当てはまりません。Kotlinがクラス内でクラスを宣言するために、ネストされたクラスと呼びます。ネストされたクラスは、外部クラスのプライベートメンバーに直接アクセスできません。
package com.imooc.test
class PageTestKt {
private val mData = listOf<String>("1", "2", "3")
class TestPageAdapter : PageAdapter() {
override fun getItem(position: Int): String {
return mData[position]//由于无法访问mData,所以mData[position]编译报错
}
override fun getCount(): Int {
return mData.size//由于无法访问mData,所以mData.size编译报错
}
}
Kotlinのネストされたクラスが外部クラスのプライベートメンバーに直接アクセスできない理由は、一目でJavaコードに逆コンパイルできます。
public final class PageTestKt {
private final List mData = CollectionsKt.listOf(new String[]{"1", "2", "3"});
public static final class TestPageAdapter extends PageAdapter {//可以看到实际上Kotlin嵌套类就是一个static静态类,所以它肯定不能访问外部类PageTestKt私有成员mData
@NotNull
public String getItem(int position) {
return "";
}
public int getCount() {
return 0;
}
}
}
Kotlinのネストされたクラスは実際には静的静的クラスであるため、外部クラスPageTestKtのプライベートメンバーmDataにアクセスできないようにする必要があります。Kotlinで内部クラスを宣言したい場合はどうすればよいですか?非常に簡単で、ネストされたクラスに基づいてinner
キーワードステートメントを追加するだけ です。
package com.imooc.test
class PageTestKt {
private val mData = listOf<String>("1", "2", "3")
inner class TestPageAdapter : PageAdapter() {//inner关键字声明一个Kotlin中的内部类
override fun getItem(position: Int): String {
return mData[position]//由于TestPageAdapter是PageTestKt的内部类,那么它就可以直接访问外部类私有属性mData
}
override fun getCount(): Int {
return mData.size
}
}
}
inner class
それが実際にJavaの内部クラスに対応していることをさらに検証するために、検証のために 上記のコードをJavaコードに逆コンパイルできます。
public final class PageTestKt {
private final List mData = CollectionsKt.listOf(new String[]{"1", "2", "3"});
public final class TestPageAdapter extends PageAdapter {//可以看到TestPageAdapter确实是PageTestKt内部类,所以能直接访问外部类的私有成员mData
@NotNull
public String getItem(int position) {
return (String)PageTestKt.this.mData.get(position);
}
public int getCount() {
return PageTestKt.this.mData.size();
}
}
}
要約すると、ネストされたクラスを宣言する場合、Javaの外部クラス内で使用されますが static class A
、Kotlinでは外部クラス内でのみ使用する必要があり class A
ます。
internalを宣言する場合は、Javaの外部クラス内でのみ使用 class A
する必要がありますが、Kotlinの外部クラス内で使用する必要があります inner class A
。
クラスAはクラスB内で宣言されています |
Javaでは |
Kotlinで |
ネストされたクラス(外部クラスのプライベートプロパティに直接アクセスすることはできません) |
静的クラスA |
クラスA |
内部クラス(外部クラスのプライベートプロパティに直接アクセスできます) |
クラスA |
インナークラスA |
2.封印されたクラス
2.1なぜ封印されたクラスが必要なのですか
開発では、このようなシナリオに遭遇することがよくあります。when
式を使用する 場合、最終的には式の値を返す必要があります。 Kotlinコンパイラがデフォルトオプションのチェックを強制するため、分岐状況が存在する when
必要がある else
ためです。したがって、通常は無効な値を返しますが、適切な値を返さない場合もあるため、通常は例外をスローして処理します。例を見てみましょう:
interface Expression
class Num(val value: Int): Expression
class Sum(val left: Expression, val right: Expression): Expression
fun eval(e: Expression): Int = when(e) {
is Num -> e.value
is Sum -> eval(e.right) + eval(e.left)
else -> throw IllegalArgumentException("Unknown Expression")//由于Kotlin编译器必须要有else分支,这种不存在分支我们只能抛出异常
}
実際、上記の例はあまり洗練されていません。現時点では、シールされたクラスを使用してExpression
インターフェイスを置き換える ことができるため、このelse
不要なブランチを保存でき 、コンパイラはエラーを報告しません。
sealed class Expression {//使用sealed关键字声明一个密封类
class Num(val value: Int) : Expression()//然后把需要的类声明在密封类的内部
class Sum(val left: Expression, val right: Expression) : Expression()
}
fun eval(e: Expression): Int = when (e) {
is Expression.Num -> e.value
is Expression.Sum -> eval(e.right) + eval(e.left)//在when判断的时候就可以省去不必要else判断
}
2.2封印されたクラスの使用方法
封印されたクラスはsealed
キーワード宣言を使用し 、封印されたクラス内で対応するクラスを宣言するだけでよく、内部クラスは基本クラスを継承します。
sealed class Expression {//使用sealed关键字声明一个密封类
class Num(val value: Int) : Expression()//把需要的类声明在密封类的内部,然后内部类继承基类
class Sum(val left: Expression, val right: Expression) : Expression()
}
2.3封印されたクラスの原則
封印されたクラスの原則は実際には非常に単純です。実際、プライベート抽象クラス内でJavaで複数のネストされたクラスを宣言することです static class
。検証のために上記のコードをJavaコードに逆コンパイルできます。
public abstract class Expression {
private Expression() {//密封类构造器私有化,防止密封类被外部实例化
}
// $FF: synthetic method
public Expression(DefaultConstructorMarker $constructor_marker) {
this();
}
public static final class Num extends Expression {//声明成static class静态类,也就是Expression的嵌套类
private final int value;
public final int getValue() {
return this.value;
}
public Num(int value) {
super((DefaultConstructorMarker)null);
this.value = value;
}
}
public static final class Sum extends Expression {//声明成static class静态类,也就是Expression的嵌套类
@NotNull
private final Expression left;
@NotNull
private final Expression right;
@NotNull
public final Expression getLeft() {
return this.left;
}
@NotNull
public final Expression getRight() {
return this.right;
}
public Sum(@NotNull Expression left, @NotNull Expression right) {
Intrinsics.checkParameterIsNotNull(left, "left");
Intrinsics.checkParameterIsNotNull(right, "right");
super((DefaultConstructorMarker)null);
this.left = left;
this.right = right;
}
}
}
3.データクラス
3.1なぜデータクラスが必要なのですか
Java開発パートナーは、私たちがJavaBeansに精通していると信じています。データモデルを定義する場合は、プロパティごとにgetterメソッドとsetterメソッドを定義する必要があります。オブジェクトの値を比較したい場合は、ハッシュコード、equals、その他のメソッドを書き直す必要があります。Kotlinの場合、このような詳細な構文テンプレートが必要ない場合があります。これにより、実装がより簡単かつ効率的になります。
import java.util.Objects;
public class Student {
private String name;
private int age;
private double weight;
private String nickName;
private String address;
public Student(String name, int age, double weight, String nickName, String address) {
this.name = name;
this.age = age;
this.weight = weight;
this.nickName = nickName;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
return age == student.age &&
Double.compare(student.weight, weight) == 0 &&
name.equals(student.name) &&
nickName.equals(student.nickName) &&
address.equals(student.address);
}
@Override
public int hashCode() {
return Objects.hash(name, age, weight, nickName, address);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", weight=" + weight +
", nickName='" + nickName + '\'' +
", address='" + address + '\'' +
'}';
}
}
Kotlinはデータクラスを使用してStudent
POJOクラスを実装し ます
//你没看错,子啊Java中需要86行代码,在Kotlin中实现仅仅需要一行代码即可,实际上data class数据类编译器背后做了很多处理
data class Student(var name: String, var age: Int, var weight: Double, var nickName: String, var address: String)
3.2データクラスの使用方法
Kotlinでのデータクラスの使用は、通常のクラス宣言の基礎の前にdata
キーワードを追加する限り、非常に簡単 です。Kotlinでは通常、データクラスを使用してデータモデルを記述します。
data class Student(var name: String, var age: Int, var weight: Double, var nickName: String, var address: String)
3.3データクラスの原則
比較すると、KotlinはJavaで86行のコードを実装するのに1行のコードしか必要としないことがわかりました。これを実行できるデータクラスの謎は何ですか?実際、これらはコンパイラによって自動的に生成されます。逆コンパイルできますデータタイプKotlinコードを見てください:
public final class Student {
@NotNull
private String name;
private int age;
private double weight;
@NotNull
private String nickName;
@NotNull
private String address;
//为属性生成了setter和getter方法
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.name = var1;
}
public final int getAge() {
return this.age;
}
public final void setAge(int var1) {
this.age = var1;
}
public final double getWeight() {
return this.weight;
}
public final void setWeight(double var1) {
this.weight = var1;
}
@NotNull
public final String getNickName() {
return this.nickName;
}
public final void setNickName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.nickName = var1;
}
@NotNull
public final String getAddress() {
return this.address;
}
public final void setAddress(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.address = var1;
}
//生成了构造器方法
public Student(@NotNull String name, int age, double weight, @NotNull String nickName, @NotNull String address) {
Intrinsics.checkParameterIsNotNull(name, "name");
Intrinsics.checkParameterIsNotNull(nickName, "nickName");
Intrinsics.checkParameterIsNotNull(address, "address");
super();
this.name = name;
this.age = age;
this.weight = weight;
this.nickName = nickName;
this.address = address;
}
//生成了component 1...N的解构函数,这个下面会详细说明
@NotNull
public final String component1() {
return this.name;
}
public final int component2() {
return this.age;
}
public final double component3() {
return this.weight;
}
@NotNull
public final String component4() {
return this.nickName;
}
@NotNull
public final String component5() {
return this.address;
}
//生成了copy函数,这个下面会详细说明
@NotNull
public final Student copy(@NotNull String name, int age, double weight, @NotNull String nickName, @NotNull String address) {
Intrinsics.checkParameterIsNotNull(name, "name");
Intrinsics.checkParameterIsNotNull(nickName, "nickName");
Intrinsics.checkParameterIsNotNull(address, "address");
return new Student(name, age, weight, nickName, address);
}
// $FF: synthetic method
public static Student copy$default(Student var0, String var1, int var2, double var3, String var5, String var6, int var7, Object var8) {
if ((var7 & 1) != 0) {
var1 = var0.name;
}
if ((var7 & 2) != 0) {
var2 = var0.age;
}
if ((var7 & 4) != 0) {
var3 = var0.weight;
}
if ((var7 & 8) != 0) {
var5 = var0.nickName;
}
if ((var7 & 16) != 0) {
var6 = var0.address;
}
return var0.copy(var1, var2, var3, var5, var6);
}
//自动生成了toString
@NotNull
public String toString() {
return "Student(name=" + this.name + ", age=" + this.age + ", weight=" + this.weight + ", nickName=" + this.nickName + ", address=" + this.address + ")";
}
//自动生成了hashCode方法
public int hashCode() {
String var10000 = this.name;
int var1 = ((var10000 != null ? var10000.hashCode() : 0) * 31 + this.age) * 31;
long var10001 = Double.doubleToLongBits(this.weight);
var1 = (var1 + (int)(var10001 ^ var10001 >>> 32)) * 31;
String var2 = this.nickName;
var1 = (var1 + (var2 != null ? var2.hashCode() : 0)) * 31;
var2 = this.address;
return var1 + (var2 != null ? var2.hashCode() : 0);
}
//自动生成了equals方法
public boolean equals(@Nullable Object var1) {
if (this != var1) {
if (var1 instanceof Student) {
Student var2 = (Student)var1;
if (Intrinsics.areEqual(this.name, var2.name) && this.age == var2.age && Double.compare(this.weight, var2.weight) == 0 && Intrinsics.areEqual(this.nickName, var2.nickName) && Intrinsics.areEqual(this.address, var2.address)) {
return true;
}
}
return false;
} else {
return true;
}
}
}
3.4コピー方法
現在、copy
メソッドの分析に焦点を当ててい ます
@NotNull
public final Student copy(@NotNull String name, int age, double weight, @NotNull String nickName, @NotNull String address) {
Intrinsics.checkParameterIsNotNull(name, "name");
Intrinsics.checkParameterIsNotNull(nickName, "nickName");
Intrinsics.checkParameterIsNotNull(address, "address");
return new Student(name, age, weight, nickName, address);
}
// $FF: synthetic method
public static Student copy$default(Student var0, String var1, int var2, double var3, String var5, String var6, int var7, Object var8) {//var0表示被copy的对象
if ((var7 & 1) != 0) {
var1 = var0.name;//copy时如果未指定具体属性的值,则会使用被copy对象的属性值
}
if ((var7 & 2) != 0) {
var2 = var0.age;
}
if ((var7 & 4) != 0) {
var3 = var0.weight;
}
if ((var7 & 8) != 0) {
var5 = var0.nickName;
}
if ((var7 & 16) != 0) {
var6 = var0.address;
}
return var0.copy(var1, var2, var3, var5, var6);
}
copyメソッドは、主に既存のデータクラスオブジェクトから新しいデータクラスオブジェクトをコピーするのに役立つことに注意してください。もちろん、さまざまなパラメータを渡してさまざまなオブジェクトを生成することもできますが
、コードから、属性で特定の属性値が指定されていない場合、新しく生成されたオブジェクトの値は、の属性値を使用することがわかります。コピーされたオブジェクト。これは、一般に浅いコピーとして知られています。
3.5componentNおよびオブジェクト破壊宣言
オブジェクト分解宣言の定義
破壊宣言は、オブジェクトを個別の変数のセットとして扱うことです。オブジェクトを個別の変数のセットとして扱う方が簡単になる場合があります。注:分解宣言をサポートするオブジェクトのクラスは、データクラス(dataキーワードで変更されたクラス)である必要があります。これは、データクラスのみが対応するcomponent()メソッド(これについては後で説明します)、データクラスを生成するためです。各プロパティには、対応するコンポーネント()メソッドがあります。
オブジェクト分解の使用
//1.定义一个data class
data class Student(var name: String, var age: Int, var weight: Double, var nickName: String, var address: String)
//2. 解构声明调用
fun main() {
val student = Student("imooc", 18, 88.0, "imooc", "北京市")
val (name, age, weight, nickName, address) = student//将一个student对象解构成一组5个单独的变量
println("my name is $name , I'm $age years old, weight is $weight, nickname is $nickName, address is $address")//解构后的5个变量可以脱离对象,直接单独使用
}
オブジェクト分解の原則
破壊宣言は、実際にはオブジェクト内のすべてのプロパティをプロパティ変数のセットに分解することであり、これらの変数は別々に使用できます。各プロパティ値の取得は最終的に対応するコンポーネントにコンパイルされるため、なぜ別々に使用できるのですか( )メソッド、各コンポーネント()メソッドは、クラス内の各属性の値に対応し、スコープ内の各属性のローカル変数を定義します。これらのローカル変数は、対応する各属性の値を格納するため、変数は次のことができるようです。実際には、ローカル変数が使用されます。次の逆コンパイルされたJavaコード
public final class StudentKt {
public static final void main() {
Student student = new Student("imooc", 18, 88.0D, "imooc", "北京市");
String name = student.component1();//对应的component1()方法,返回对应就是Student中name属性,并赋值给创建局部变量name
int age = student.component2();//对应的component2()方法,返回对应就是Student中age属性,并赋值给创建局部变量age
double weight = student.component3();//对应的component3()方法,返回对应就是Student中weight属性,并赋值给创建局部变量weight
String nickName = student.component4();//对应的component4()方法,返回对应就是Student中nickName属性,并赋值给创建局部变量nickName
String address = student.component5();//对应的component5()方法,返回对应就是Student中address属性,并赋值给创建局部变量address
String var7 = "my name is " + name + " , I'm " + age + " years old, weight is " + weight + ", nickname is " + nickName + ", address is " + address;
boolean var8 = false;
System.out.println(var7);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
4.まとめ
この時点で、Kotlinオブジェクト指向のいくつかの特別なクラスを1つずつ説明します。その中で、データクラスと内部クラスがもう少し使用されます。封印されたクラスの使用は、通常の開発ではあまり使用されませんが、この記事を通じて、封印されたクラスをいつ使用するかを知っておく必要があります。また、コードがKotlinの非常に強固な基盤を確実に反映できるように、対応するシナリオでうまく使用できることを期待してください。