Kotlinの文法記事で関数を改善する方法について話す(3)

https://my.oschina.net/u/3847608/blog/1837611 

簡単な説明:今日はKotlinトークシリーズの第3部です。関数呼び出しと関数のオーバーロードについて話しましょう。タイトルを参照すると、Kotlinは関数呼び出しでJavaよりも優れていることがわかります。まず、次のピットを捨てて(以前に踏んだことがあると推定されます...)、ピットを段階的に埋める方法を確認します。そこから、Kotlinの言語の魅力を体験できます。

  • 1. Javaには関数呼び出しにどのようなピットがありますか?
  • 2. Kotlinは関数呼び出しの落とし穴をどのように解決しますか?
  • 3. Javaには、関数のオーバーロードにどのような落とし穴がありますか?
  • 4. Kotlinは、関数のオーバーロードによって引き起こされる落とし穴をどのように解決しますか?
  • 5. JavaとKotlinが相互に呼び出すときに関数をオーバーロードする場合、どのような問題に注意する必要がありますか?

1.Java関数呼び出しの問題

Javaでプログラムを開発する過程で、いくつかのメソッドを呼び出すことがよくあることを思い出してみましょう。多くのパラメーターを使用してメソッドを設計する人もいれば、パラメーターの命名が標準化されていない(名前と知識を満たせない)場合もあります。同じパラメータタイプのいくつかは、まだ隣り合っています。これは実際に呼び出し元に多くの問題をもたらします。慎重なプログラマーは、この関数が定義されている場所を見つけ、おそらくパラメーター呼び出しの順序と各パラメーターの関数を調べます。特にこのメソッドがlibにパッケージ化されている場合、チェックするのは面倒です。また、同じタイプのパラメータが隣り合っていると、間違ったものに対応しやすくなります。この例を見てみましょう(ここでの要件は、プレフィックス、セパレーター、およびサフィックスに従って各セットの要素を印刷することです)

    //张三设计的函数接口顺序(调用者必须按照这个顺序传)
    static String joinToString(List<Integer> nums, String prex, String sep, String postfix) {
        StringBuilder builder = new StringBuilder(prex);
        for (int i = 0; i < nums.size(); i++) {
            builder.append(i);
            if (i < nums.size() - 1) {
                builder.append(sep);
            }
        }
        builder.append(postfix);
        return builder.toString();
    }

    //李四设计的函数接口顺序(调用者必须按照这个顺序传)
    static String joinToString(List<Integer> nums, String sep, String prex, String postfix) {
        StringBuilder builder = new StringBuilder(prex);
        for (int i = 0; i < nums.size(); i++) {
            builder.append(i);
            if (i < nums.size() - 1) {
                builder.append(sep);
            }
        }
        builder.append(postfix);
        return builder.toString();
    }

    //王二设计的函数接口顺序(调用者必须按照这个顺序传)
    static String joinToString(List<Integer> nums, String prex, String postfix, String sep) {
        StringBuilder builder = new StringBuilder(prex);
        for (int i = 0; i < nums.size(); i++) {
            builder.append(i);
            if (i < nums.size() - 1) {
                builder.append(sep);
            }
        }
        builder.append(postfix);
        return builder.toString();
    }  
    
    //假如现在叫你修改一下,拼接串前缀或分隔符,仅从外部调用是无法知道哪个参数是前缀、分隔符、后缀
    public static void main(String[] args) {
    //后面传入的三个字符串顺序很容易传错,并且外部调用者如果不看具体函数定义根本很难知道每个字符串参数的含义,特别公司中一些远古代码,可能还得打成库的代码,点进去看实现肯定蛋疼。
       //调用张三接口
        System.out.println(joinToString(Arrays.asList(new Integer[]{1, 3, 5, 7, 9}), "<", ",", ">"));
       //调用李四接口
        System.out.println(joinToString(Arrays.asList(new Integer[]{1, 3, 5, 7, 9}), ",", "<", ">"));
      //调用王二接口
        System.out.println(joinToString(Arrays.asList(new Integer[]{1, 3, 5, 7, 9}), "<", ">", ","));    
    }

ただし、上記の問題に対応して、注意深いプログラマーは、AndroidStudio3.0のバージョンが優れた最適化のヒントを提供することを長い間発見してきましたが、3.0より前にはそのようなヒントはありませんでした。示されているように

 

AndroidStudioツール開発者のjetBrainsは、実際にこのヒントを開発したKotlin言語に直接統合します。文法レベルでの間違いや迂回を減らし、コード自体の実装にもっと注意を払うようにします。レベルで直接文法を使用できるようにします。の、それはより明確で混乱が少ないです。

2. Kotlinは関数呼び出しの落とし穴をどのように解決しますか?

Kotlinは、上記で発生した問題を解決できます。Kotlin関数にはnamed parameterと呼ばれるこのようなパラメーターがあり、関数呼び出しで関数名を指定できるため、呼び出し場所のパラメーターと関数を適切に作成できます。定義されたパラメータは1対1の対応であり、パラメータの混乱を渡す問題はありません。

//kotlin一个函数的接口满足以上三种顺序调用的接口,准确来说是参数列表中任意参数顺序组合的调用
fun joinToString(nums: List<Int>, prex: String, sep: String, postfix: String): String {
    val builder = StringBuilder(prex)
    for (i in nums.indices) {
        builder.append(i)
        if (i < nums.size - 1) {
            builder.append(sep)
        }
    }
    builder.append(postfix)
    return builder.toString()
}
fun main(args: Array<String>) {
    //调用kotlin函数接口,满足张三接口设计需求,且调用更加明确
    println(joinToString(nums = listOf(1, 3, 5, 7, 9), prex = "<", sep = ",", postfix = ">"))
    //调用kotlin函数接口,满足李四接口设计需求,且调用更加明确
    println(joinToString(nums = listOf(1, 3, 5, 7, 9), sep = ",", prex = "<", postfix = ">"))
    //调用kotlin函数接口,满足王二接口设计需求,且调用更加明确
    println(joinToString(nums = listOf(1, 3, 5, 7, 9), prex = "<", postfix = ">", sep = ","))
}

名前付きパラメーターを使用したAndroidStudio3.0関数呼び出しのハイライトはより目を引く

 

要約:上記の例から、Kotlinは関数呼び出しの点でJavaよりも確かに明確であり、不要なピットを踏むことも防げると結論付けることができます。比較せずに害はありません。Javaと比較して、あなたは思いますか? Kotlinの方が適していますか?

3つ目は、Javaが関数のオーバーロードにどのようなピットを持っているかです。

JavaまたはC ++のどちらであるかに関係なく、関数のオーバーロードがあります。関数のオーバーロードの目的は、さまざまな機能ビジネス要件を対象とし、パラメーターリストの数、パラメータータイプ、パラメーターの順序など、さまざまなパラメーターインターフェイスを公開することです。つまり、ほぼすべての異なる要件に対応する関数が必要です。将来の拡張では、このクラスの同じ名前の関数が積み重なって、各関数間で階層呼び出しがあり、関数間のパラメーターが異なります。リストは微妙な場合があるため、呼び出し元は非常に混乱します。コードヒントでは、7つまたは8つの同一のメソッドがあることがわかります。たとえば(Android画像読み込みフレームワークは、簡単に呼び出すために再度カプセル化するために使用されます)

//注意:这是我早期写出来kotlin代码(很丑陋),虽然这个看起来是kotlin代码,但是并没有脱离Java语言的思想
//束缚,也没有利用起kotlin的新特性,这个封装完全可以看做是直接从java代码翻译过来的kotlin代码。
fun ImageView.loadUrl(url: String) {//ImageView.loadUrl这个属于扩展函数,后期会介绍,暂时可以先忽略
	loadUrl(Glide.with(context), url)
}

fun ImageView.loadUrl(requestManager: RequestManager, url: String) {
	loadUrl(requestManager, url, false)
}

fun ImageView.loadUrl(requestManager: RequestManager, url: String, isCrossFade: Boolean) {
	ImageLoader.newTask(requestManager).view(this).url(url).crossFade(isCrossFade).start()
}

fun ImageView.loadUrl(urls: List<String>) {
	loadUrl(Glide.with(context), urls)
}

fun ImageView.loadUrl(requestManager: RequestManager, urls: List<String>) {
	loadUrl(requestManager, urls, false)
}

fun ImageView.loadUrl(requestManager: RequestManager, urls: List<String>, isCrossFade: Boolean) {
	ImageLoader.newTask(requestManager).view(this).url(urls).crossFade(isCrossFade).start()
}

fun ImageView.loadRoundUrl(url: String) {
	loadRoundUrl(Glide.with(context), url)
}

fun ImageView.loadRoundUrl(requestManager: RequestManager, url: String) {
	loadRoundUrl(requestManager, url, false)
}

fun ImageView.loadRoundUrl(requestManager: RequestManager, url: String, isCrossFade: Boolean) {
	ImageLoader.newTask(requestManager).view(this).url(url).crossFade(isCrossFade).round().start()
}

fun ImageView.loadRoundUrl(urls: List<String>) {
	loadRoundUrl(Glide.with(context), urls)
}

fun ImageView.loadRoundUrl(requestManager: RequestManager, urls: List<String>) {
	loadRoundUrl(requestManager, urls, false)
}

fun ImageView.loadRoundUrl(requestManager: RequestManager, urls: List<String>, isCrossFade: Boolean) {
	ImageLoader.newTask(requestManager).view(this).url(urls).crossFade(isCrossFade).round().start()
}
//调用的地方
activity.home_iv_top_banner.loadUrl(bannerUrl)
activity.home_iv_top_portrait.loadUrl(portraitUrls)
activity.home_iv_top_avatar.loadRoundUrl(avatarUrl)
activity.home_iv_top_avatar.loadRoundUrl(avatarUrls)

//以上的代码,相信很多人在Java中看到有很多吧,先不说个人写的,就拿官方库中的Thread类的构造器方法就有
//七八个。说明函数重载往往在符合需求接口扩展的时候,也在渐渐埋下了坑。不说别的就拿这个类来说,即使直
//接看函数定义,你也得花时间去理清里面的调用关系,然后才能放心去使用。而且这样函数以后维护起来特别麻烦。

第4に、Kotlinは関数のオーバーロードによって引き起こされる落とし穴をどのように解決しますか

上記の例のオーバーロードされたメソッドの場合、実際には、Kotlinに渡して実装を解決するために必要なメソッドは1つだけであり、呼び出すのは非常に便利です。実際、Kotlinにはデフォルト値パラメーターと呼ばれる関数パラメーターがありますこれは関数のオーバーロードの問題を解決することができ、呼び出しの場所で上記の名前付きパラメーターと組み合わせて使用​​すると非常に便利で簡単になります。

//学完命名参数和默认值参数函数,立即重构后的样子
fun ImageView.loadUrl(requestManager: RequestManager = Glide.with(context)
					  , url: String = ""
					  , urls: List<String> = listOf(url)
					  , isRound: Boolean = false
					  , isCrossFade: Boolean = false) {
	if (isRound) {
		ImageLoader.newTask(requestManager).view(this).url(urls).round().crossFade(isCrossFade).start()
	} else {
		ImageLoader.newTask(requestManager).view(this).url(urls).crossFade(isCrossFade).start()
	}
}
//调用的地方
activity.home_iv_top_banner.loadUrl(url = bannerUrl)
activity.home_iv_top_portrait.loadUrl(urls = portraitUrls)
activity.home_iv_top_avatar.loadUrl(url = avatarUrl, isRound = true)
activity.home_iv_top_avatar.loadUrl(urls = avatarUrls, isRound = true)

概要:Kotlinでは、Kotlinで定義された関数を呼び出すと、特定の対応するパラメーターをパラメーター名で一意に見つけることができるため、一部のパラメーターの名前が表示され、パラメーターの呼び出し順序が乱れる可能性があります。上記のコードにより、Kotlinのデフォルト値関数は関数のオーバーロードの問題を完全に解決し、名前付き関数は関数呼び出しの問題を解決し、任意のパラメーター名を指定することで関数のパラメーターを呼び出すことができることを認識します。注文。

5. JavaとKotlinが相互に呼び出すときに関数をオーバーロードする場合、どのような問題に注意する必要がありますか

5.1 @JvmOverloadsアノテーションを使用して、JavaがKotlinのオーバーロードされた関数を呼び出す問題を解決します

Javaにはデフォルト値パラメーターの概念がないため、JavaからKotlinでデフォルト値オーバーロード関数を呼び出す必要がある場合は、すべてのパラメーター値を明示的に指定する必要があります。しかし、これは間違いなく私たちが望んでいることではありません。そうしないと、Kotlinはオーバーロードの意味を失い、Javaと完全に相互運用できなくなります。したがって、Kotlinの別の解決策は、@ JvmOverloadsアノテーションを使用して、Javaが呼び出すために複数のオーバーロードされたメソッドが自動的に生成されるようにすることです。Kotlinコードで次の例を確認できます

@JvmOverloads
fun <T> joinString(
        collection: Collection<T> = listOf(),
        separator: String = ",",
        prefix: String = "",
        postfix: String = ""
): String {
    return collection.joinToString(separator, prefix, postfix)
}
//调用的地方
fun main(args: Array<String>) {
    //函数使用命名参数可以提高代码可读性
    println(joinString(collection = listOf(1, 2, 3, 4), separator = "%", prefix = "<", postfix = ">"))
    println(joinString(collection = listOf(1, 2, 3, 4), separator = "%", prefix = "<", postfix = ">"))
    println(joinString(collection = listOf(1, 2, 3, 4), prefix = "<", postfix = ">"))
    println(joinString(collection = listOf(1, 2, 3, 4), separator = "!", prefix = "<"))
    println(joinString(collection = listOf(1, 2, 3, 4), separator = "!", postfix = ">"))
    println(joinString(collection = listOf(1, 2, 3, 4), separator = "!"))
    println(joinString(collection = listOf(1, 2, 3, 4), prefix = "<"))
    println(joinString(collection = listOf(1, 2, 3, 4), postfix = ">"))
    println(joinString(collection = listOf(1, 2, 3, 4)))
}

Kotlinのパラメーターのデフォルト値は、呼び出し元ではなく呼び出された関数にコンパイルされるため、デフォルト値を変更した後、この関数を再コンパイルする必要があります。次の逆コンパイルされたコードから、Kotlinがデフォルト値を関数にコンパイルしたことがわかります。

  // $FF: synthetic method
  // $FF: bridge method
  @JvmOverloads
  @NotNull
  public static String joinString$default(Collection var0, String var1, String var2, String var3, int var4, Object var5) {
     if((var4 & 1) != 0) {
        var0 = (Collection)CollectionsKt.emptyList();//默认值空集合
     }

     if((var4 & 2) != 0) {
        var1 = ",";//默认值分隔符“,”
     }

     if((var4 & 4) != 0) {
        var2 = "";//默认前缀
     }

     if((var4 & 8) != 0) {
        var3 = "";//默认后缀
     }

     return joinString(var0, var1, var2, var3);
  }

  @JvmOverloads
  @NotNull
  public static final String joinString(@NotNull Collection collection, @NotNull String separator, @NotNull String prefix) {
     return joinString$default(collection, separator, prefix, (String)null, 8, (Object)null);
  }

  @JvmOverloads
  @NotNull
  public static final String joinString(@NotNull Collection collection, @NotNull String separator) {
     return joinString$default(collection, separator, (String)null, (String)null, 12, (Object)null);
  }

  @JvmOverloads
  @NotNull
  public static final String joinString(@NotNull Collection collection) {
     return joinString$default(collection, (String)null, (String)null, (String)null, 14, (Object)null);
  }

  @JvmOverloads
  @NotNull
  public static final String joinString() {
     return joinString$default((Collection)null, (String)null, (String)null, (String)null, 15, (Object)null);
  }

5.2 Kotlinは名前付きパラメーターとデフォルト値パラメーターを使用してJavaを呼び出すことができますか?

注: Kotlinの関数で名前付きパラメーターを使用することはできません。Javaでは多くのコンストラクターメソッドまたは一般的なメソッドがオーバーロードされていますが、JDKまたはAndroidフレームワークの関数であるかどうかに関係なく、KotlinでJavaメソッドを呼び出すときに名前付きパラメーターを使用することはできません。 。の関数は、名前付きパラメーターを使用できません。

この時点で、Kotlinを使用すると、関数呼び出しが本当に簡単で明確になり、すばやく試すことができます。

おすすめ

転載: blog.csdn.net/az44yao/article/details/112917961