1. 程式人生 > >Kotlin學習歷程一:Kotlin開發安卓的初體驗

Kotlin學習歷程一:Kotlin開發安卓的初體驗

1.告別findViewById

不同於 JAVA 中,在 Kotlin 中 findViewById 本身就簡化了很多,這得益於 Kotlin 的型別推斷以及轉型語法後置:

val onlyTv = findViewById(R.id.onlyTv) as TextView

在官方庫Anko的支援下,可以更簡化

當你試圖 command/ctrl + 左鍵點選 onlyTv 想要檢視 onlyTv 的來源的時候,你會發現你跳到了 activity_main 的佈局檔案:

是不是很方便呢?

注意:需要在actiivty中引入下佈局

import kotlinx.android.synthetic.main.activity_main.*

在 anko 的幫助下,你只需要根據佈局的 id 寫一句 import 程式碼,然後你就可以把佈局中的 id 作為 view 物件的名稱直接進行使用。不僅 activity 中可以這樣玩,你甚至可以 viewA.viewB.viewC,所以大可不必擔心 adapter 中應當怎麼寫。

沒有 findViewById,也就減少了空指標;沒有 cast,則幾乎不會有型別轉換異常。

2.自動getter、setter及class簡潔宣告

JAVA 中有如下類

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void getName() {
        return name;
    }
}

Person person = new Person("張三");

可以看出,標準寫法下,一個屬性對應了 get 和 set 兩個方法,需要手動寫的程式碼量相當大。當然有快捷鍵幫助我們生成這些程式碼,但是考慮到各種複雜情形總歸不完美。

而 Kotlin 中是這樣的:

class Person(var name: String)
val person = Person("張三");

還可以新增預設值:

class Person(var name: String = "張三")
val person = Person()

再附上我專案中一個比較複雜的資料類:

data class Column(
        var subId: String?,
        var subTitle: String?,
        var subImg: String?,
        var subCreatetime: String?,
        var subUpdatetime: String?,
        var subFocusnum: Int?,
        var lastId: String?,
        var lastMsg: String?,
        var lastType: String?,
        var lastMember: String?,
        var lastTIme: String?,
        var focus: String?,
        var subDesc: String?,
        var subLikenum: Int?,
        var subContentnum: Int?,
        var pushSet: String?
)

一眼望去,沒有多餘程式碼。這是為什麼我認為 Kotlin 程式碼比 JAVA 程式碼要更容易寫得乾淨的原因之一。

3.有了空安全,再也不怕服務端返回空物件了

簡單一點的例子,那就是 String 和 String?是兩種不同的型別。String 已經確定是不會為空,一定有值;而 String?則是未知的,也許有值,也許是空。在使用物件的屬性和方法的時候,String 型別的物件可以毫無顧忌的直接使用,而 String?型別需要你先做非空判斷。

fun demo() {
    val string1: String = "string1"
    val string2: String? = null
    val string3: String? = "string3"
    
    println(string1.length)
    println(string2?.length)
    println(string3?.length)
}

輸出結果為:
7
null
7

儘管 string2 是一個空物件,也並沒有因為我呼叫了它的屬性/方法就報空指標。而你所需要做的,僅僅是加一個"?"。

如果說這樣還體現不出空安全的好處,那麼看下面的例子:

val a: A? = A()
println(a?.b?.c)

試想一下當每一級的屬性皆有可能為空的時候,JAVA 中我們需要怎麼處理?想想是不是就頭(beng)大(kui)了?

4.轉型與智慧轉換,省力又省心

這樣子的 JAVA 程式碼:

if(view instanceof TextView) {
    TextView textView = (TextView) view;
    textView.setText("text");
}

在Kotlin中可以簡化成:

if(view is TextView) {
    view.setText("text")
}

直接少了一半的程式碼量,是不是很省力

智慧轉換還有一個經常出現的場景,那就是 switch case 語句中。在 Kotlin 中,則是 when 語法。

fun testWhen(obj: Any) {
    when(obj) {
        is Int -> {
            println("obj is a int")
            println(obj + 1)
        }

        is String -> {
            println("obj is a string")
            println(obj.length)
        }

        else -> {
            println("obj is something i don't care")
        }
    }
}

fun main(args: Array<String>) {
    testWhen(98)
    testWhen("98")
}

輸出如下:
obj is a int
99
obj is a string
2

可以看出在已經判斷出是 String 的條件下,原本是一個 Any 類的 obj 物件,我可以直接使用屬於 String 類的 .length 屬性。

5.緊跟上一條來說下when

通過上面智慧轉化的例子,已經展示了一部分 when 的功能。但相對於 JAVA 的 switch,Kotlin 的 when 帶給我的驚喜遠遠不止這麼一點。

fun testWhen(int: Int) {
    when(int) {
        in 10 .. Int.MAX_VALUE -> println("${int} 太大了我懶得算")
        2, 3, 5, 7 -> println("${int} 是質數")
        else -> println("${int} 不是質數")
    }
}

fun main(args: Array<String>) {
    (0..10).forEach { testWhen(it) }
}

輸出如下:
0 不是質數
1 不是質數
2 是質數
3 是質數
4 不是質數
5 是質數
6 不是質數
7 是質數
8 不是質數
9 不是質數
10 太大了我懶得算

和 JAVA 中死板的 switch-case 語句不同,在 when 中,我既可以用引數去匹配 10 到 Int.MAX_VALUE 的區間,也可以去匹配 2, 3, 5, 7 這一組值,當然我這裡沒有列舉所有特性。when 的靈活、簡潔,使得我在使用它的時候變得相當開心(和 JAVA 的 switch 對比的話)

6.容器的操作符

Kotlin 中,容器自身帶有一系列的操作符,可以非常簡潔的去實現一些邏輯。

(0 until container.childCount)
        .map { container.getChildAt(it) }
        .filter { it.visibility == View.GONE }
        .forEach { it.visibility = View.VISIBLE }

上述程式碼首先建立了一個 0 到 container.childCount - 1 的區間;再用 map 操作符配合取出 child 的程式碼將這個 Int 的集合轉化為了 childView 的集合;然後在用 filter 操作符對集合做篩選,選出 childView 中所有可見性為 GONE 的作為一個新的集合;最終 forEach 遍歷把所有的 childView 都設定為 VISIBLE。

這裡再貼上 JAVA 的程式碼作為對比。

for(int i = 0; i < container.childCount - 1;  i++) {
    View childView = container.getChildAt(i);
    if(childView.getVisibility() == View.GONE) {
        childView.setVisibility(View.VISIBLE);
    }
}

這裡就不詳細的去描述這種鏈式的寫法有什麼優點了。

7.非常方便快捷的執行緒切換方式

 anko中包含了許多可以簡化開發的程式碼,其中就對執行緒進行了簡化。

async {
    val response = URL("https://www.baidu.com").readText()
    uiThread {
        textView.text = response
    }
}

上面的程式碼很簡單,通過 async 方法將程式碼實現在一個非同步的執行緒中,在讀取到 http 請求的響應了之後,再通過 uiThread 方法切換回 ui 執行緒將 response 顯示在 textView 上。

拋開內部的實現,你再也不需要為了一個簡簡單單的非同步任務去寫一大堆的無效程式碼。按照慣例,這裡似乎應該貼上 JAVA 的程式碼做對比,但請原諒我不想刷屏(o(∩_∩)o 哈哈)

8.一個關鍵字實現單例

毫不誇張,就是一個關鍵字,廢話不多說,直接上程式碼

object Log {
    fun i(string: String) {
        println(string)
    }
}

fun main(args: Array<String>) {
    Log.i("test")
}

9.簡單粗暴的 startActivity

原本跳轉activity我們是這樣做的:

Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);

有時候需要傳遞資料:

Intent intent = new Intent(LoginActivity.this, MainActivity.class);
intent.putExtra("name", "張三");
intent.putExtra("age", 27);
startActivity(intent);

在 anko 的幫助下,kotlin的startActivity 是這樣子的:

startActivity<MainActivity>()
startActivity<MainActivity>("name" to "張三", "age" to 27)
startActivityForResult<MainActivity>(101, "name" to "張三", "age" to 27)

無參情況下,只需要在呼叫 startActivity 的時候加一個 Activity 的 Class 泛型來告知要到哪去。有參也好說,這個方法支援你傳入 vararg(變長引數) params: Pair<String, Any>。有沒有覺得程式碼寫起來、讀起來流暢了許多?

10.玲瓏小巧的 toast

Java中寫個Toast是這樣的:

Toast.makeText(context, "this is a toast", Toast.LENGTH_SHORT).show();

做個封裝處理可能會是:

ToastUtil.showShort("this is a toast");

kotlin這邊呢:

context.toast("this is a toast")

如果當前已經在context上下文中(比如activity),context也可以直接省略:

toast("this is a toast")

如果你是想要一個長時間的 toast:

longToast("this is a toast")

11.代理模式,讓SharedPreference 不再麻煩

class EntranceActivity : BaseActivity() {
    
    private var userId: String by Preference(this, "userId", "")

    override fun onCreate(savedInstanceState: Bundle?) {
        testUserId()
    }
    
    fun testUserId() {
        if (userId.isEmpty()) {
            println("userId is empty")
            userId = "default userId"
        } else {
            println("userId is $userId")
        }
    }
}


重複啟動 app 輸出結果:
userId is empty
userId is default userId
userId is default userId
...

第一次啟動 app 的時候從 SharedPreference 中取出來的 userId 是空的,可是後面卻不為空。由此可見,userId = "default userId" 這句程式碼成功的將 SharedPreference 中的值修改成功了。

也就是說,在這個 Preference 代理的幫助下,SharedPreference 存取操作變得和普通的物件呼叫、賦值一樣的簡單。

12.擴充套件,Kotlin相比於Java的一大殺器

fun ImageView.displayUrl(url: String?) {
    if (url == null || url.isEmpty() || url == "url") {
        imageResource = R.mipmap.ic_launcher
    } else {
        Glide.with(context)
                .load(ColumnServer.SERVER_URL + url)
                .into(this)
    }
}
...
val imageView = findViewById(R.id.avatarIv) as ImageView
imageView.displayUrl(url)

上述程式碼可理解為:

1.我給 ImageView 這個類擴充套件了一個名為 displayUrl 的方法,這個方法接收一個名為 url 的 String?類物件。如不出意外,會通過 Glide 載入這個 url 的圖片,顯示在當前的 imageView 上;

2.我在另一個地方通過 findViewById 拿到了一個 ImageView 類的例項,然後呼叫這個 imageView 的displayUrl 方法,試圖載入我傳入的 url

通過擴充套件來為 ImageView 新增方法,相比於通過繼承 ImageView 來寫一個 CustomImageView,再新增方法而言,侵入性更低,不需要在程式碼中全寫 CustomImageView,也不需要在 xml 佈局中將包名寫死,造成移植的麻煩。

這事用工具類當然也可以做,比如做成 ImageUtil.displayUrl(imageView, url),但是工具類閱讀起來並沒有擴展出來的方法讀起來更自然更流暢。

還有比較常用的String的判斷:

fun String.isName(): Boolean {
    if (isEmpty() || length > 10 || contains(" ")) {
        return false
    }

    val reg = Regex("^[a-zA-Z0-9\u4e00-\u9fa5]+$")
    return reg.matches(this)
}

fun String.isPassword(): Boolean {
    return length in 6..12
}

fun String.isNumber(): Boolean {
    val regEx = "^-?[0-9]+$"
    val pat = Pattern.compile(regEx)
    val mat = pat.matcher(this)

    return mat.find()
}
...

println("張三".isName())
println("123abc".isPassword())
println("123456".isNumber())

13.用 apply 方法進行資料組合

假設有如下 A、B、C 三個 class:

class A(val b: B)

class B(val c: C)

class C(val content: String)

可以看到,A 中有 B,B 中有 C。在實際開發的時候,我們有的時候難免會遇到比這個更復雜的資料,巢狀層級很深。這種時候,用 JAVA 初始化一個 A 類資料會變成一件非常痛苦的事情。例如:

C c = new C("content");
B b = new B(c);
A a = new A(b);

這還是 A、B、C 的關係很單純的情況下,如果有大量資料進行組合,那麼我們會需要初始化大量的物件進行賦值、修改等操作。如果我描述的不夠清楚的話,大家不妨想一想用 JAVA 程式碼佈局是一種什麼樣的感覺?

當然,在 JAVA 中也是有解決方案的,比如 Android 中常用的 Dialog,就用了 Builder 模式來進行相應配置。(說到這裡,其實用 Builder 模式基本上也可以說是 JAVA 語言的 DSL)

但是在更為複雜的情況下,即便是有設計模式的幫助,也很難保證程式碼的可讀性。那麼 Kotlin 有什麼好方法,或者說小技巧來解決這個問題嗎?

Kotlin 中有一個名為 apply 的方法,它的原始碼是這樣子的:

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

沒有 Kotlin 基礎的小夥伴看到這裡一定會有點暈。我們先忽略一部分細節,把關鍵的資訊提取出來,再改改格式看看:

public fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}

1.首先,我們可以看出 T 是一個泛型,而且後面沒有給 T 增加約束條件,那麼這裡的 T 可以理解為:我這是在給所有類擴充套件一個名為『apply』的方法;

2.第一行最後的: T 表明,我最終是要返回一個 T 類。我們也可以看到方法內部最後的 return this 也能說明,其實最後我就是要返回呼叫方法的這個物件自身;

3.在 return this 之前,我執行了一句 block(),這意味著 block 本身一定是一個方法。我們可以看到,apply 方法接收的 block 引數的型別有點特殊,不是 String 也不是其他什麼明確的型別,而是T.() -> Unit

4.T.() -> Unit 表示的意思是:這是一個 ①上下文在 T 物件中②返回一個 Unit 類物件的方法。由於 Unit 和 JAVA 中的 Void 一致,所以可以理解為不需要返回值。那麼這裡的 block 的意義就清晰起來了:一個執行在 T,即呼叫 apply 方法的物件自身當中,又不需要返回值的方法。

有了上面的解析,我們再來看一下這句程式碼:

val textView = TextView(context).apply {
    text = "這是文字內容"
    textSize = 16f
}

這句程式碼就是初始化了一個 TextView,並且在將它賦值給 textView 之前,將自己的文字、字型大小修改了。

或許你會覺得這和 JAVA 比起來並沒有什麼優勢。彆著急,我們慢慢來:

layout.addView(TextView(context).apply {
    text = "這是文字內容"
    textSize = 16f
})

這樣又如何呢?我並不需要宣告一個變數或者常量來持有這個物件才能去做修改操作。

上面的A、B、C 問題用 Kotlin 來實現是可以這麼寫的:

val a = A().apply {
    b = B().apply {
        c = C("content")
    }
}

我只聲明瞭一個 a 物件,然後初始化了一個 A,在這個初始化的物件中先給 B 賦值,然後再提交給了 a。B 中的 C 也是如此。當組合變得複雜的時候,我也能保持我的可讀性:

val a = A().apply {
    b = B().apply {
        c = C("content")
    }

    d = D().apply {
        b = B().apply {
            c = C("test")
        }

        e = E("test")
    }
}

上面的程式碼用 JAVA 實現會是如何一番場景?反正我是想一想就已經暈了。說到底,這個小技巧也就是①擴充套件方法 + ②高階函式兩個特性組合在一起實現的效果。

14.利用高階函式搞事情

inline fun debug(code: () -> Unit) {
    if (BuildConfig.DEBUG) {
        code()
    }
}
...
// Application 中
debug {
    Timber.plant(Timber.DebugTree())
}

上述程式碼是先定義了一個全域性的名為 debug 的方法,這個方法接收一個方法作為引數,命名為 code。然後在方法體內部,我先判斷當前是不是 DEBUG 版本,如果是,再呼叫傳入的 code 方法。

而後我們在 Application 中,debug 方法就成為了依據條件執行程式碼的關鍵字。僅當 DEBUG 版本的時候,我才初始化 Timber 這個日誌庫。

如果這還不夠體現優點的話,那麼可以再看看下面一段:

supportsLollipop {
    window.statusBarColor = Color.TRANSPARENT
    window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
}

當系統版本在 Lollipop 之上時才去做沉浸式狀態列。系統 api 經常會有版本的限制,相對於一個 supportsLollipop 關鍵字, 我想一定不是所有人都希望每次都去寫:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    // do something
}

諸如此類的場景和可以自創的 關鍵字/程式碼塊 還有很多。

inline fun handleException(code : () -> Unit) {
    try {
        code()
    } catch (e : Exception) {
        e.printStackTrace()
    }
}
...
handleException {
     println(Integer.parseInt("這明顯不是數字"))
}

雖然大都可以用 if(xxxxUtil.isxxxx()) 來湊合,但是既然有了更好的方案,那還何必湊合呢?

15.DSL式程式設計

說起 dsl ,咱們Android 開發者接觸的最多的或許就是 gradle 了

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.zll.demo"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

這就是一段 Groovy 的 DSL,用來宣告編譯配置

那麼在 Android 專案的程式碼中使用 DSL 是一種什麼樣的感覺呢?

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val homeFragment = HomeFragment()
    val columnFragment = ColumnFragment()
    val mineFragment = MineFragment()

    setContentView(
            tabPages {
                backgroundColor = R.color.white
                dividerColor = R.color.colorPrimary
                behavior = ByeBurgerBottomBehavior(context, null)

                tabFragment {
                    icon = R.drawable.selector_tab_home
                    body = homeFragment
                    onSelect { toast("home selected") }
                }

                tabFragment {
                    icon = R.drawable.selector_tab_search
                    body = columnFragment
                }

                tabImage {
                    imageResource = R.drawable.selector_tab_photo
                    onClick { showSheet() }
                }

                tabFragment {
                    icon = R.drawable.selector_tab_mine
                    body = mineFragment
                }
            }
    )
}

沒錯,上面的程式碼就是用來構建這個主介面的 viewPager + fragments + tabBar 的。以 tabPages 作為開始,設定背景色,分割線等屬性;再用 tabFrament 新增 fragment + tabButton,tabImage 方法則只新增 tabButton。所見的程式碼都是在做配置,而具體的實現則被封裝了起來。

前面提到過 anko 這個庫,其實也可以用來替代 xml 做佈局用:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    verticalLayout {
        textView {
            text = "這是標題"
        }.lparams {
            width = matchParent
            height = dip(44)
        }

        textView {
            text = "這是內容"
            gravity = Gravity.CENTER
        }.lparams {
            width = matchParent
            height = matchParent
        }
    }
}

相比於用 JAVA 程式碼做佈局,這種 DSL 的方式也是在做配置,把佈局的實現程式碼封裝在了背後,和 xml 佈局很接近。

構建並顯示 BottomSheet的對比:

Builder 版

BottomSheet.Builder([email protected], R.style.ShareSheetStyle)
        .sheet(999, R.drawable.share_circle,  R.string.wXSceneTimeline)
        .sheet(998, R.drawable.share_freind,  R.string.wXSceneSession)
        .listener { _, id ->
            shareTo(bitmap, target = when(id) {
                999 -> SendMessageToWX.Req.WXSceneTimeline
                998 -> SendMessageToWX.Req.WXSceneSession
                else -> throw Exception("it can not happen")
            })
        }
        .build()
        .show()
DSL 版

showBottomSheet {
    style = R.style.ShareSheetStyle

    sheet {
        icon = R.drawable.share_circle
        text = R.string.wXSceneTimeline

        selected {
            shareTo(bitmap, SendMessageToWX.Req.WXSceneTimeline)
        }
    }

    sheet {
        icon = R.drawable.share_freind
        text = R.string.wXSceneSession

        selected {
            shareTo(bitmap, SendMessageToWX.Req.WXSceneTimeline)
        }
    }
}

apply 構建資料例項(微信分享)

普通版

val obj = WXImageObject(bitmap)
val thumb = ......
bitmap.recycle()

val msg = WXMediaMessage()
msg.mediaObject = obj
msg.thumbData = thumb

val req = SendMessageToWX.Req()
req.transaction = "share"
req.scene = target
req.message = msg

WxObject.api.sendReq(req)
DSL 版

WxObject.api.sendReq(
        SendMessageToWX.Req().apply {
            transaction = "share"
            scene = target
            message = WXMediaMessage().apply {
                mediaObject = WXImageObject(bitmap)
                thumbData = ......
                bitmap.recycle()
            }
        }
)

相關推薦

Kotlin學習歷程Kotlin開發體驗

1.告別findViewById 不同於 JAVA 中,在 Kotlin 中 findViewById 本身就簡化了很多,這得益於 Kotlin 的型別推斷以及轉型語法後置: val onlyTv = findViewById(R.id.onlyTv) as TextVi

Kotlin學習歷程kotlin中的變數、常量和註釋

一、變數 kotlin中變數的宣告和java有很大區別,必須使用var或var關鍵字。 var表示:可變變數,可讀也可寫,相當於java中的普通的變數。 val表示:不可變變數,可讀但是不可寫,相當於java中用final修飾的變數。 1.1 基礎用法: 格式:關鍵字(

Android知識體系梳理筆記五Kotlin學習筆記類和繼承以及Anko(全)的基本使用

前言 對於kotlin,我是邊寫專案邊學的方式來學習的,這些都是在做專案的時候遇到的問題及擴充套件學習的時候記錄的,雖然有些內容不會涉及,但是我認為這種邊寫程式碼邊學習的方式特別有助於記憶,畢竟紙上得來終覺淺! 類和繼承 Kotlin較Java在繼承和實現

基於.NET的CAD二次開發學習筆記CAD開發入門

1、AutoCAD .NET API由不同的DLL檔案組成,它們提供用於訪問圖形檔案或AutoCAD應用程式的包含豐富的類、結構、方法和事件。每一個DLL檔案都定義不同的使用基於功能的庫組織元件的名稱空間。 下面是你將頻繁地要使用的AutoCAD .NET API 的三個主要的DLL檔案:

我的職業歷程springboot開發周邊

打算對這兩個月所做的專案做一個小結, 是應該做個總結。公司技術人才豐富,各司其職,在某些方面並不需要自己硬著頭皮去啃,可以在同事優化的方案上進行修改,也可以參照同事的成果物完成相應的任務。所以,專案結束了,所涉及到的技術並非真的理解掌握。這便是以下總結的起因。比較懶,不願意多寫。 今年以前的人生規劃中未曾想

Kotlin學習系列之Kotlin的建構函式

    Kotlin的建構函式分為主構造器(primary constructor)和次級構造器(secondary constructor)。下面我們來看看他們的寫法。一、Primary Constructor1. 寫法一:class 類名 constructor(形參1,

Kotlin學習筆記之一Kotlin基礎

1.函式: /** * 關鍵詞fun 申明是函式 * 引數形式:引數名:型別 * Kotlin中陣列是一個類 * 這是一個不帶返回值的函式 */ fun main(args: Array<String>){ println("

ArcGIS Engine 開發學習-------基礎()自定義命令

 要求:在ToolbarControl中新增一個自定義命令,點選可清除當前活動工具。     步驟: 1、建立GIS類,選擇Base Command模版,Extending ArcObjects,ArcMap MapControl or PageLayo

Android Kotlin 學習總結() 《KAE 優缺點並且深入位元組碼分析工作原理》

本章會分為以下內容: 1.Kotlin KAE介紹,使用和原始Android findViewById對比優缺點 2.Kotlin KAE所存在的問題 3.通過位元組碼分析他的實現原理 閱讀本章內容大概需要您5分鐘的時間 一、Kotlin KAE介紹,使用和原始

Kotlin 開發PopWindow 內部按鈕出現空指標錯誤!解決方法

錯誤:按鈕事件監聽發生異常 Attempt to invoke virtual method 'void android.widget.Button.setOnClickListener 原因是po

Kotlin 學習) 基本語法

基本語法 定義包 包的宣告應處於原始檔頂部: package kotlin.demo import java.util.* 目錄與包的結構無需匹配:原始碼可以在檔案系統的任意位置。 定義函式 帶有兩個 Int 引數、返回 Int 的函式: fun sum

Kotlin 學習筆記() 基本型別和基本語法

Kotlin 被提升為 Android 開發一級語言, 雖然短時間內 Java 並不會被取代, 不過學習 Kotlin 還是宜早不宜遲, 做好迎接變化的準備。 Android Studio 3.0 開始官方支援了 Kotlin, 因此, 先下一個 3.0 Pr

iOS開發之opencv學習筆記下載和安裝

1. opencv是什麼? opencv是一個基於BSD開源協議的影象處理開源庫,截止本人編輯時間:2017年6月1日,最新版本為3.2.0。 2.哪裡可以得到opencv的原始碼以及不同平臺的動態庫? opencv的官方地址為http://opencv.org/,可以在這

不同步節點線上使用Remix開發以太坊Dapp及solidity學習入門 ( )智慧合約HelloWorld

有問題可以點選–>加群互相學習 本人本來想自己寫公鏈,結果發現任重道遠; 遂,開始寫Dapp,順便寫的時候搞個教程吧。。。 通過系列教程學習將會: 1.基本使用solidity 語言開發智慧合約 2.知道怎麼發自己的以太坊的token 3.看見前方區

Kotlin學習系列之對比Any和Object

Any中定義的方法有:toString()、equals()、hashCode()  3個Object類中定義的方法有:toString()、equals()、hashCode()、getClass()、clone()、finalize()、notify()、notifyAl

Flask-RESTful介面開發學習筆記實現簡單的GET請求

1、首先安裝需要依賴的第三方包:   (1):Flask   (2):Flask-RESTful 如何安裝就不在囉嗦,pip也好,下載到本地再安裝也好,看你隨意。 2、我們先實現GET請求。 Q:什麼是GET請求。 A:簡單來說,GET請求,就是根據傳過來的引數條件,來獲

TP5學習基礎增刪改查小demo

表單 arr 處理 php req 學習 model類 浪費 新手 ①TP5--增刪改查簡單的demo 我先吐槽一下:因為工作需要研究tp5,去官網看了一下哎呦,資源挺多挺全啊!然後下載唯一免費的官方教程,我曹pdf打開533頁。講的很細但是開發能等看完才做嗎?看到精簡版快

Kotlin基礎(Kotlin快速入門

語法 note 初始 字母 文件中 create 列表 orange 快的 Kotlin快速入門 一、函數 1 /* 2 * 1.函數可以定義在文件最外層,不需要把它放在類中 3 * 2.可以省略結尾分號 4 * */ 5 fun main(args: Ar

Spring4學習筆記環境搭建與插件安裝

str nag j2e 容器 獲取 相關 market 至少 ips 一:環境搭建 1:開發環境:JDK安裝、Eclipse安裝 2:數據庫:Mysql、Sequel Pro(數據庫可視化操作工具) 3:web服務器:Tomcat下載,並且把tomcat配置到Eclip

linux學習筆記遠程連接linux服務器

user 亂碼 roo 開機啟動 sta 文件 ftpd 連不上 服務 環境介紹:win7電腦,通過VM虛擬出linux系統,安裝centOS7 通過Xshell連接linux,ftp訪問服務器資源。 遇到的問題,ftp連不上linux 解決:linux上安裝ftp服務 步