本文假設各位已經有了 Kotlin 基礎,對 Kotlin 還不熟悉的小夥伴能夠去看我以前發的文章-->《Kotlin Jetpack 實戰》。html
本文將帶領各位用 Kotlin 一步步重構咱們的 Demo 工程,順便一窺Kotlin 編程的三重境界
。java
說明:本系列文章都只探討 Kotlin JVM,Kotlin JS/Native 都不在探討範圍內。node
前期準備
第一重境界:用 Java 視角寫 Kotlin
第二重境界:用 Kotlin 視角寫 Kotlin
第三重境界:用 Bytecode 視角寫 Kotlin
結尾
chapter_03_kotlin_refactor_training
上一章咱們已經將 Groovy 改爲了 Kotlin DSL,但工程自己還不支持咱們用 Kotlin 寫 Android App。因此咱們還須要作一些配置:android
Libs.kt
增長如下依賴常量:git
const val kotlinStdLib = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlinVersion}"
const val ktxCore = "androidx.core:core-ktx:${Versions.ktxCore}"
複製代碼
根目錄下的 build.gradle.kt
新增:程序員
dependencies {
...
classpath(kotlin("gradle-plugin", version = Versions.kotlinVersion))
}
複製代碼
app/build.gradle.kt
新增:github
plugins {
...
kotlin("android")
kotlin("android.extensions")
}
dependencies {
...
implementation(Libs.kotlinStdLib)
implementation(Libs.ktxCore)
}
複製代碼
注意事項:
純 Kotlin 開發的話作以上配置就夠,但若是有 Java 混合開發的話,最好加上如下編譯器參數配置,防止出現兼容性問題: app/build.gradle.kt
新增:web
android {
...
// Configure Java compiler compatible with Java 1.8
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
// Configure Kotlin compiler target Java 1.8 when compile Kotlin to bytecode
kotlinOptions {
this as KotlinJvmOptions
jvmTarget = "1.8"
}
}
複製代碼
以上配置的做用,分別是:數據庫
以上修改的具體細節能夠看我這個 GitHub Commit。編程
接下來咱們進入正題,用 Kotlin 重構 Java 代碼。
我一直認爲 Kotlin 是一門易學難精的語言:入門易,精通難。若是要爲 Kotlin 程序員劃分境界,我以爲能夠劃分三重境界。
這幾乎是每一個 Kotlin 程序員都會經歷的境界(包括曾經的我)。我曾覺得學會 Kotlin 的語法就能寫好 Kotlin 代碼,然而我只是把腦子裏的 Java/C 代碼用 Kotlin 語法翻譯一遍寫出來了而已。
接下來我就以第一重境界的"功力",來重構咱們的 Demo 工程。你們看看熱鬧就行,千萬別學進腦子裏啊。[狗頭]
我如今僞裝本身是個新手,剛學會 Kotlin 語法。正所謂,柿子要挑軟的捏,我們重構代碼固然也從最簡單的開始。因而我找到 Demo 工程裏的 User.java
,一咬牙,就你了:
public class User {
// 工程簡單到沒有數據庫,因此將 API 請求寫死緩存到這裏
public static final String CACHE_RESPONSE = "{"login":"JakeWharton","id":66577,"node_id":"MDQ6VXNlcjY2NTc3","avatar_url":"https://avatars0.githubusercontent.com/u/66577?v=4","gravatar_id":"","url":"https://api.github.com/users/JakeWharton","html_url":"https://github.com/JakeWharton","followers_url":"https://api.github.com/users/JakeWharton/followers",小夥伴"following_url":"https://api.github.com/users/JakeWharton/following{/other_user}","gists_url":"https://api.github.com/users/JakeWharton/gists{/gist_id}","starred_url":"https://api.github.com/users/JakeWharton/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JakeWharton/subscriptions","organizations_url":"https://api.github.com/users/JakeWharton/orgs","repos_url":"https://api.github.com/users/JakeWharton/repos","events_url":"https://api.github.com/users/JakeWharton/events{/privacy}","received_events_url":"https://api.github.com/users/JakeWharton/received_events","type":"User","site_admin":false,"name":"Jake Wharton","company":"Square","blog":"https://jakewharton.com","location":"Pittsburgh, PA, USA","email":null,"hireable":null,"bio":null,"twitter_username":null,"public_repos":104,"public_gists":54,"followers":57849,"following":12,"created_at":"2009-03-24T16:09:53Z","updated_at":"2020-05-28T00:07:20Z"}";
private String id;
private String login;
private String avatar_url;
private String name;
private String company;
private String blog;
private Date lastRefresh;
public User() { }
public User(@NonNull String id, String login, String avatar_url, String name, String company, String blog, Date lastRefresh) {
this.id = id;
this.login = login;
this.avatar_url = avatar_url;
this.name = name;
this.company = company;
this.blog = blog;
this.lastRefresh = lastRefresh;
}
public String getId() { return id; }
public String getAvatar_url() { return avatar_url; }
public Date getLastRefresh() { return lastRefresh; }
public String getLogin() { return login; }
public String getName() { return name; }
public String getCompany() { return company; }
public String getBlog() { return blog; }
public void setId(String id) { this.id = id; }
public void setAvatar_url(String avatar_url) { this.avatar_url = avatar_url; }
public void setLastRefresh(Date lastRefresh) { this.lastRefresh = lastRefresh; }
public void setLogin(String login) { this.login = login; }
public void setName(String name) { this.name = name; }
public void setCompany(String company) { this.company = company; }
public void setBlog(String blog) { this.blog = blog; }
複製代碼
一頓操做,我把這個 Java Bean 用 Kotlin 語法翻譯成了這樣:
class User {
companion object {
val CACHE_RESPONSE = "..."
}
private var id: String? = null
private var login: String? = null
private var avatar_url: String? = null
private var name: String? = null
private var company: String? = null
private var blog: String? = null
private var lastRefresh: Date? = null
constructor() {}
constructor(id: String, login: String?, avatar_url: String?, name: String?, company: String?, blog: String?, lastRefresh: Date?) {
this.id = id
this.login = login
this.avatar_url = avatar_url
this.name = name
this.company = company
this.blog = blog
this.lastRefresh = lastRefresh
}
fun getId(): String? { return id }
fun getAvatar_url(): String? { return avatar_url }
fun getLastRefresh(): Date? { return lastRefresh }
fun getLogin(): String? { return login }
fun getName(): String? { return name }
fun getCompany(): String? { return company }
fun getBlog(): String? { return blog }
fun setId(id: String?) { this.id = id }
fun setAvatar_url(avatar_url: String?) { this.avatar_url = avatar_url }
fun setLastRefresh(lastRefresh: Date?) { this.lastRefresh = lastRefresh }
fun setLogin(login: String?) { this.login = login }
fun setName(name: String?) { this.name = name }
fun setCompany(company: String?) { this.company = company }
fun setBlog(blog: String?) { this.blog = blog }
}
複製代碼
我看着本身一行一行寫出來的 Kotlin 代碼,內心成就感滿滿。So easy![狗頭]
爲了讓工程可以模擬 Kotlin/Java 混編,咱們讓 ImagePreviewActivity 繼續維持 Java 狀態,因此接下來就剩下 MainActivity.java 的重構了。咱們先看 MainActivity 的 Java 代碼。
public class MainActivity extends AppCompatActivity {
public static final String TAG = "Main";
public static final String EXTRA_PHOTO = "photo";
StringRequest stringRequest;
RequestQueue requestQueue;
private ImageView image;
private ImageView gif;
private TextView username;
private TextView company;
private TextView website;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
image = findViewById(R.id.image);
gif = findViewById(R.id.gif);
username = findViewById(R.id.username);
company = findViewById(R.id.company);
website = findViewById(R.id.website);
display(User.CACHE_RESPONSE);
requestOnlineInfo();
}
private void requestOnlineInfo() {
requestQueue = Volley.newRequestQueue(this);
String url ="https://api.github.com/users/JakeWharton";
stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
display(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(MainActivity.this, error.getMessage(), Toast.LENGTH_SHORT).show();
}
});
stringRequest.setTag(TAG);
requestQueue.add(stringRequest);
}
private void display(@Nullable String response) {
if (TextUtils.isEmpty(response)) { return; }
Gson gson = new Gson();
final User user = gson.fromJson(response, User.class);
if (user != null){
Glide.with(this).load("file:///android_asset/bless.gif").into(gif);
Glide.with(this).load(user.getAvatar_url()).apply(RequestOptions.circleCropTransform()).into(image);
this.username.setText(user.getName());
this.company.setText(user.getCompany());
this.website.setText(user.getBlog());
image.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
gotoImagePreviewActivity(user);
}
});
}
}
private void gotoImagePreviewActivity(User user) {
Intent intent = new Intent(this, ImagePreviewActivity.class);
intent.putExtra(EXTRA_PHOTO, user.getAvatar_url());
startActivity(intent);
}
@Override
protected void onStop () {
super.onStop();
if (requestQueue != null) {
requestQueue.cancelAll(TAG);
}
}
}
複製代碼
一通操做,我把 MainActivity 重構成了這樣:
class MainActivity : AppCompatActivity() {
companion object {
val TAG = "Main"
val EXTRA_PHOTO = "photo"
}
var stringRequest: StringRequest? = null
var requestQueue: RequestQueue? = null
private var image: ImageView? = null
private var gif: ImageView? = null
private var username: TextView? = null
private var company: TextView? = null
private var website: TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
init()
}
private fun init() {
image = findViewById(R.id.image)
gif = findViewById(R.id.gif)
username = findViewById(R.id.username)
company = findViewById(R.id.company)
website = findViewById(R.id.website)
display(User.CACHE_RESPONSE)
requestOnlineInfo()
}
private fun requestOnlineInfo() {
requestQueue = Volley.newRequestQueue(this)
val url = "https://api.github.com/users/JakeWharton"
stringRequest = StringRequest(Request.Method.GET, url,
object: Response.Listener<String> {
override fun onResponse(response: String?) {
display(response)
}
}, object: Response.ErrorListener {
override fun onErrorResponse(error: VolleyError?) {
Toast.makeText(this@MainActivity, error?.message, Toast.LENGTH_SHORT).show()
}
})
stringRequest!!.tag = TAG
requestQueue!!.add(stringRequest)
}
private fun display(response: String?) {
if (TextUtils.isEmpty(response)) {
return
}
val gson = Gson()
val user = gson.fromJson(response, User::class.java)
if (user != null) {
Glide.with(this).load("file:///android_asset/bless.gif").into(gif!!)
Glide.with(this).load(user.getAvatar_url()).apply(RequestOptions.circleCropTransform()).into(image!!)
username!!.text = user.getName()
company!!.text = user.getCompany()
website!!.text = user.getBlog()
image!!.setOnClickListener(object: View.OnClickListener{
override fun onClick(v: View?) {
gotoImagePreviewActivity(user)
}
})
}
}
private fun gotoImagePreviewActivity(user: User) {
val intent = Intent(this, ImagePreviewActivity::class.java)
intent.putExtra(EXTRA_PHOTO, user.getAvatar_url())
startActivity(intent)
}
override fun onStop() {
super.onStop()
if (requestQueue != null) {
requestQueue!!.cancelAll(TAG)
}
}
}
複製代碼
因爲 MainActivity 重構成了 Kotlin,ImagePreviewActivity.java 須要對應作一些調整。緣由是 Java 還不能很好的識別伴生對象。
修改前:
public class ImagePreviewActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
String url = intent.getStringExtra(MainActivity.EXTRA_PHOTO);
...
}
}
複製代碼
修改後:
public class ImagePreviewActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
String url = intent.getStringExtra(MainActivity.Companion.getEXTRA_PHOTO());
...
}
}
複製代碼
這個境界的特色是:一行 Kotlin 對應一行 Java,還不會運用 Kotlin 獨有的特性。
以上修改的具體細節能夠看我這個 GitHub Commit。
各位小夥伴千萬別看到這裏就走了啊,請看我下一個境界是怎麼寫(演)的。
到第二重境界,我就是個成熟的 Kotlin 程序員了。我會用一些 Kotlin 獨有特性去改善 Java 代碼裏的邏輯。
咱們仍是從最簡單的 User.kt 開始,看過《寫給 Java 開發者的 Kotlin 入坑指南》的小夥伴必定知道 Data Class,咱們來將 User.kt 重構成 Data Class,真的會省很多代碼:
data class User(
var id: String? = null,
var login: String? = null,
var avatar_url: String? = null,
var name: String? = null,
var company: String? = null,
var blog: String? = null,
var lastRefresh: Date? = null
) {
companion object {
val CACHE_RESPONSE = "..."
}
}
複製代碼
Data Class 能夠節省咱們編寫 Java Bean 的時間。
接下來看 MainActivity.kt,咱們從最上面的變量開始。以前咱們定義的變量都是可爲空的
(Nullable),致使這些變量在使用的時候都須要判空,或者使用非空斷言!!
。這很不Kotlin
。解決這個問題的辦法不少,這裏我先用 lateinit
來解決網絡請求的兩個變量。
修改前:
class MainActivity : AppCompatActivity() {
...
var stringRequest: StringRequest? = null
var requestQueue: RequestQueue? = null
private fun requestOnlineInfo() {
...
stringRequest!!.tag = TAG
requestQueue!!.add(stringRequest)
}
}
複製代碼
修改後:
class MainActivity : AppCompatActivity() {
...
private lateinit var stringRequest: StringRequest
private lateinit var requestQueue: RequestQueue
private fun requestOnlineInfo() {
...
stringRequest.tag = TAG
requestQueue.add(stringRequest)
}
}
複製代碼
通常來講,咱們定義不爲空的變量須要在構造函數或者 init 代碼塊裏賦值,這樣編譯器纔不會報錯。但不少時候咱們的變量賦值並不能在以上狀況下完成賦值,好比:findViewById。
lateinit
的做用是告訴編譯器,我定義的這個不爲空的變量,雖然目前沒有對它賦值,但我在使用它以前,必定會對它賦值,確定不爲空,你沒必要報錯。
KTX
是 Android 官方提供的一個 Gradle 插件,可以爲開發者提供便利,它最著名的功能就是可以省掉 findViewById
。以前咱們在工程裏已經添加了這個插件,接下來直接使用就能夠了。
直接將控件的申明和賦值都刪掉,而後在調用的地方咱們按 option + return
選擇 import
:
修改前:
private var image: ImageView? = null
private var gif: ImageView? = null
private var username: TextView? = null
private var company: TextView? = null
private var website: TextView? = null
image = findViewById(R.id.image)
gif = findViewById(R.id.gif)
username = findViewById(R.id.username)
company = findViewById(R.id.company)
website = findViewById(R.id.website)
...
username!!.text = user.name
company!!.text = user.company
website!!.text = user.blog
複製代碼
修改後:
// 注意這裏
import kotlinx.android.synthetic.main.activity_main.*
// private var image: ImageView? = null
// private var gif: ImageView? = null
// private var username: TextView? = null
// private var company: TextView? = null
// private var website: TextView? = null
// image = findViewById(R.id.image)
// gif = findViewById(R.id.gif)
// username = findViewById(R.id.username)
// company = findViewById(R.id.company)
// website = findViewById(R.id.website)
...
username.text = user.name
company.text = user.company
website.text = user.blog
複製代碼
隱患
,咱們後面再講如下代碼 Android Studio 會提示 Convert to lambda
咱們只須要按 option + return
,Android Studio 就會幫咱們重構。
修改前:
...
stringRequest = StringRequest(Request.Method.GET, url,
object : Response.Listener<String> {
override fun onResponse(response: String?) {
display(response)
}
}, object : Response.ErrorListener {
override fun onErrorResponse(error: VolleyError?) {
Toast.makeText(this@MainActivity, error?.message, Toast.LENGTH_SHORT).show()
}
})
...
image.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
gotoImagePreviewActivity(user)
}
})
複製代碼
修改後:
...
stringRequest = StringRequest(Request.Method.GET,
url,
Response.Listener { response ->
display(response)
},
Response.ErrorListener { error ->
Toast.makeText(this@MainActivity, error?.message, Toast.LENGTH_SHORT).show()
})
...
image.setOnClickListener { gotoImagePreviewActivity(user) }
...
複製代碼
可讀性
使用 Kotlin 的擴展函數能消滅一切 xxUtils.java。Kotlin 標準函數就已經爲咱們提供了相關擴展函數,幫助咱們消滅 TextUtils
。
修改前:
...
if (TextUtils.isEmpty(response)) {
return
}
複製代碼
修改後:
...
if (response.isNullOrBlank()) {
return
}
複製代碼
上面修改後的代碼看起來像是 response
有一個成員方法: isNullOrBlank()
,這樣作有不少好處:
Kotlin 提供了一系列標準函數,好比: let, also, with, apply 幫助開發者簡化邏輯。這裏咱們使用 apply,它的做用解釋起來很麻煩,看代碼更明瞭:
修改前:
if (user != null) {
...
username.text = user.name
website.text = user.blog
image.setOnClickListener { gotoImagePreviewActivity(user) }
}
複製代碼
修改後:
user?.apply {
...
username.text = name
website.text = blog
image.setOnClickListener { gotoImagePreviewActivity(this) }
}
複製代碼
這個境界的特色是:
具體細節能夠看這個 Github Commit。
Kotlin 號稱 Java 100% 兼容,就是由於 Kotlin 最終會被編譯成字節碼
(Bytecode)。經過查看 Kotlin 編譯後的字節碼,咱們既能瞭解 Kotlin 的原理,也能探索出一些 Kotlin 編程的 Tips。
受限於本文的篇幅,咱們暫且不談 Kotlin 的實現原理,也不去詳細探討 Kotlin 編程的 Tips。咱們繼續專一於實戰。現階段的項目中,咱們已經嘗試加入了一些 Kotlin 的特性,咱們只研究現階段用到的這些 Kotlin 特性。
Tools -> Kotlin -> Show Kotlin Bytecode 通常咱們狀況下咱們只須要查看 Kotlin 等價的 Java 代碼便可,所以咱們能夠在字節碼彈窗的左上角找到 Decompile
按鈕,這樣就能看到 Kotlin 等價的 Java 代碼了。
Java 中被 final
修飾的變量一旦賦值後就沒法被修改。這在 Java 中也是很好的習慣,咱們在 Kotlin 中也應該沿用。Kotlin 沒有 final
,可是有 val
。
咱們仍是先從 User.kt 開始。
data class User(
var id: String? = null,
var login: String? = null,
var avatar_url: String? = null,
var name: String? = null,
var company: String? = null,
var blog: String? = null,
var lastRefresh: Date? = null
) {
companion object {
val CACHE_RESPONSE = "..."
}
}
複製代碼
User.kt 反編譯成 Java 後:
...
public final class User {
@Nullable
private String id;
...
@NotNull
private static final String CACHE_RESPONSE = "...";
public static final User.Companion Companion = new User.Companion((DefaultConstructorMarker)null);
@Nullable
public final String getId() {
return this.id;
}
public final void setId(@Nullable String var1) {
this.id = var1;
}
...
public static final class Companion {
@NotNull
public final String getCACHE_RESPONSE() {
return User.CACHE_RESPONSE;
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
複製代碼
咱們將 User.kt 裏面的 var
都替換成 val
:
data class User(
val id: String? = null,
val login: String? = null,
val avatar_url: String? = null,
val name: String? = null,
val company: String? = null,
val blog: String? = null,
val lastRefresh: Date? = null
) {
companion object {
val CACHE_RESPONSE = "..."
}
}
複製代碼
它反編譯成 Java 代碼變成了這樣:
public final class User {
@Nullable
private final String id; // 多了 final
...
@NotNull
private static final String CACHE_RESPONSE = "...";
public static final User.Companion Companion = new User.Companion((DefaultConstructorMarker)null);
@Nullable
public final String getId() {
return this.id;
}
// setId() 沒有了
...
public static final class Companion {
@NotNull
public final String getCACHE_RESPONSE() {
return User.CACHE_RESPONSE;
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
複製代碼
這一點在 Java 和 Kotlin 中一樣有用。MainActivity.kt 中有兩個成員變量,其中的 stringRequest
實際上是能夠改成局部變量的。
修改前:
class MainActivity : AppCompatActivity() {
...
private lateinit var stringRequest: StringRequest
private lateinit var requestQueue: RequestQueue
}
複製代碼
修改後:
class MainActivity : AppCompatActivity() {
...
// private lateinit var stringRequest: StringRequest
private lateinit var requestQueue: RequestQueue
private fun requestOnlineInfo() {
...
val stringRequest = StringRequest(Request.Method.GET,
url,
Response.Listener { response ->
display(response)
},
Response.ErrorListener { error ->
Toast.makeText(this@MainActivity, error?.message, Toast.LENGTH_SHORT).show()
})
...
}
}
複製代碼
MainActivity 只剩下一個成員變量 requestQueue
,它仍是用的 var 修飾的,咱們能不能把它改成 val 呢?固然能夠,但咱們須要藉助 by lazy,委託。
修改後:
class MainActivity : AppCompatActivity() {
...
private val requestQueue: RequestQueue by lazy {
Volley.newRequestQueue(this)
}
}
複製代碼
讓咱們看看它等價的 Java 代碼,它的初始化交給了 LazyKt.lazy
:
private final Lazy requestQueue$delegate = LazyKt.lazy((Function0)(new Function0() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke() {
return this.invoke();
}
public final RequestQueue invoke() {
return Volley.newRequestQueue((Context)MainActivity.this);
}
}));
複製代碼
再看看 LazyKt.lazy 的實現,其實是 SynchronizedLazyImpl
:
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
複製代碼
再看看 SynchronizedLazyImpl
:
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
private fun writeReplace(): Any = InitializedLazyImpl(value)
}
複製代碼
果真,和咱們以前文章提到的同樣,by lazy 默認狀況下會使用同步的方式進行初始化。但咱們當前項目並不須要,畢竟多線程同步也是有開銷的。
修改後:
private val requestQueue: RequestQueue by lazy(LazyThreadSafetyMode.NONE) {
Volley.newRequestQueue(this)
}
複製代碼
因爲 Java 沒法識別 Kotlin 裏面的伴生對象,因此咱們在 Java 裏訪問的時候比較彆扭。
class MainActivity : AppCompatActivity() {
companion object {
...
val EXTRA_PHOTO = "photo"
}
}
複製代碼
在 Java 中訪問:
public class ImagePreviewActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
...
String url = intent.getStringExtra(MainActivity.Companion.getEXTRA_PHOTO());
...
}
}
複製代碼
反編譯後:
...
@NotNull
private static final String EXTRA_PHOTO = "photo";
public static final MainActivity.Companion Companion = new MainActivity.Companion((DefaultConstructorMarker)null);
...
public static final class Companion {
@NotNull
public final String getEXTRA_PHOTO() {
return MainActivity.EXTRA_PHOTO;
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
複製代碼
咱們能夠看到,默認狀況下,Kotlin 爲伴生對象裏的變量生成了 get 方法,Java 代碼裏要訪問這個變量必須這樣: MainActivity.Companion.getEXTRA_PHOTO()
,這很不友好。
爲了讓 Java 可以更好的識別伴生對象裏的變量和方法,咱們能夠這麼作:
使用 const:
class MainActivity : AppCompatActivity() {
companion object {
...
const val EXTRA_PHOTO = "photo"
}
}
複製代碼
或者使用 @JvmField 註解:
class MainActivity : AppCompatActivity() {
companion object {
...
@JvmField
val EXTRA_PHOTO = "photo"
}
}
複製代碼
在 Java 中訪問:
public class ImagePreviewActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
...
String url = intent.getStringExtra(MainActivity.EXTRA_PHOTO);
...
}
}
複製代碼
以上兩種狀況反編譯成 Java 的代碼以下:
...
@NotNull
public static final String EXTRA_PHOTO = "photo";
public static final MainActivity.Companion Companion = new MainActivity.Companion((DefaultConstructorMarker)null);
...
public static final class Companion {
@NotNull
public final String getTAG() {
return MainActivity.TAG;
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
複製代碼
很多博客講伴生對象
到這裏就結束了。@JvmField
,const
,@JvmStatic
,這些確實是使用伴生對象須要注意的。
但是,我們的代碼到這裏是否是就完美了?並不。
咱們能夠看到,即便咱們加上了 @JvmField
或者 const
,伴生對象仍然爲常量生成了 get 方法,同時也定義了一個 Companion 的類,還有一個 instance。然而咱們最初的需求只是要定義一個 public static final String
的常量而已。
這個小結的標題是不要用錯伴生對象
。它的前提是什麼?它的前提是:該不應用
。在這裏我不由要問一句:這種狀況下,真的須要伴生對象嗎?答案是:不須要。
MainActivity 中的 TAG
不須要在類之外被訪問,所以能夠直接定義爲成員變量:
class MainActivity : AppCompatActivity() {
private val TAG = "Main"
}
複製代碼
如今只剩下 EXTRA_PHOTO
,咱們應該怎麼處理?在 Java 中,咱們常常會定義一個類來專門存放常量,Kotlin 中咱們一樣能夠借鑑:
讓咱們建立一個 Constant.kt:
//注意這裏,它要放到 package 的前面
@file:JvmName("Constant")
package com.boycoder.kotlinjetpackinaction
const val EXTRA_PHOTO = "photo"
const val CACHE_RESPONSE = "..."
複製代碼
在 Kotlin 中能夠直接這樣使用:
// Kotlin 中甚至能夠省略掉 Constant,由於 CACHE_RESPONSE 是頂層常量。
display(CACHE_RESPONSE)
複製代碼
在 Java 中要這樣使用:
// 因爲 @file:JvmName("Constant") 的存在,Java 中也能很好的訪問 Constant.EXTRA_PHOTO
String url = intent.getStringExtra(Constant.EXTRA_PHOTO);
複製代碼
Constant.kt
反編譯成 Java 後是這樣的:
public final class Constant {
@NotNull
public static final String EXTRA_PHOTO = "photo";
@NotNull
public static final String CACHE_RESPONSE = "...";
}
複製代碼
因此說,若是隻是須要定義靜態常量,哪用得上 Kotlin 的伴生對象?
以上修改的具體細節能夠看我這個 Github Commit。
最佳實踐
不必定對(包括本文),要獨立思考本文只是藉助咱們的 Demo 一窺 Kotlin 編程的三重境界,讓你們對 Kotlin 編程總體有個瞭解。後面我也許會寫專題文章來說《Kotlin 編譯器漫遊指南》,《Kotlin 最佳實踐指北》,也許吧。
文章寫到這已經接近尾聲了,那咱們的 Demo 工程改到這個程度是否是已經完美了呢?固然沒有。但我不想寫了,歡迎各位小夥伴留言一塊兒討論還有哪些地方能改進。
咱們下一篇文章再見。
回目錄-->《Kotlin Jetpack 實戰》