一覧から詳細の起動_一覧側(MainActivity)
一覧から詳細を起動する一覧側(MainActivity.kt)の実装。 ListViewのsetOnItemClickListener()の書き方。 setOnItemClickListener()の引数はAdapterView.OnItemClickListenerオブジェクトを取る。 OnItemClickListenerオブジェクトは1つの仮想関数(Single Abstract Method)を持っていて、 それを実装したものを渡すことになる。 本来、OnItemClicListenerを派生したオブジェクトでSAMを実装したものを渡す、という とても長い記述になるのだけれども、仮想関数が1個だけならば代わりにラムダ式を1個渡せば良い。 (SAM変換)。 ActivityからIntentを取得してIntentからActivityを起動する、という書き方。 Intentを取得するときにパラメタ(今回の場合はArticleインスタンス)が渡る。 package com.example.ikuty.myapplication import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.widget.ListView import com.example.ikuty.myapplication.model.Article class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val listAdapter = ArticleListAdapter(applicationContext) val dummyArticle1 = dummyArticle(\"ikuty.com記事1\",\"ikuty1\") val dummyArticle2 = dummyArticle(\"ikuty.com記事2\",\"ikuty2\") listAdapter.articles = listOf(dummyArticle1,dummyArticle2) val listView: ListView = findViewById(R.id.list_view) as ListView listView.adapter = listAdapter listView.setOnItemClickListener { adapterView,view, position, id -> val article = listAdapter.articles[position] ArticleActivity.intent(this, article).let { startActivity(it) } } } }
一覧から詳細の起動 – ParcelableなインスタンスをIntentに付与してActivityを起動する
記事一覧画面でアイテムを触るとそのアイテムの詳細画面が開くというのをやりたい。 記事詳細画面のController(Activity)を書く。 Intent まずIntent。Activityを起動する際にパラメタの役割を果たす。 Intentには「意図」「目的」の意味がある。Activityを起動する意図とか目的とか。 ベースとなるIntentに付加的な情報を付与(putExtra)することで「意図」「目的」となる。 Intentには基本型の他にカスタムオブジェクトを付与することができる。 ただし、カスタムオブジェクトはParcelableインターフェースを実装している必要がある。 あるActivityから別のActivityを起動する際のテクニックとして、 起動される側のActivityが持つintent()メソッドを使ってIntentを作成し(つまりパラメタを渡し)、 作成したIntent経由でActivityを起動する。 companionオブジェクトにfun intent(context: Context, article: Article): Intentを作る。 このintent()を経由すれば起動に必要なパラメタ(今回はarticle: Article)を付与したIntentを取得できる。 intent()内ではputExtra()を使ってarticleとIntentを紐づけている。 onCreate(savedInstanceState: Bundle?) onCreate()が発火したときにはIntentにParcalableなArticleインスタンスが付与済み。 Activityが持つintentからgetParcelableExtra()によりArticleインスタンスを取得して、 ビューにそのArticleインスタンスを表示する。 package com.example.ikuty.myapplication import android.content.Context import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.webkit.WebView import com.example.ikuty.myapplication.model.Article import com.example.ikuty.myapplication.view.ArticleView class ArticleActivity: AppCompatActivity() { companion object { private const val ARTICLE_EXTRA: String = \"article\" fun intent(context: Context,article: Article): Intent = Intent(context, ArticleActivity::class.java) .putExtra(ARTICLE_EXTRA, article) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_article) val articleView = findViewById(R.id.article_view) as ArticleView val webView = findViewById(R.id.web_view) as WebView val article: Article = intent.getParcelableExtra(ARTICLE_EXTRA) articleView.setArticle(article) webView.loadUrl(article.url) } }
リストビューを表示するためにListViewAdapterを実装する
ListViewAdapter リストビューを表示するためにListViewAdapterを実装する。 BaseAdapterを継承して必要なメソッドを実装する。 実装が必要なのは以下。 fun getCount(): Int fun getItem(position: Int): Any? fun getItemId(position: Int): Long fun getView(position:Int,convertView: View?,parent: ViewGroup?): View fun getCount(): Int 名前の通り、リストビューが保持するアイテムの個数を返す。 ListViewAdapterが保持するList<Article>のサイズを返すようにする。 fun getItem(position: Int): Any? 名前の通り、指定した位置のアイテムを返す。 保持するList<Article>の要素を返すようにする。 fun getItemId(position: Int): Long 指定した位置のアイテムに関するアプリケーション固有のIDを返す。 ListViewの外から「位置->固有ID」を取得できる。 アイテムを取得してアイテムからIDを取るんじゃダメなのか...。 Get the row id associated with the specified position in the list. fun getView(position:Int,convertView: View?,parent: ViewGroup?): View 指定した位置のアイテムを表示するためのビューを取得する。 convertViewには、画面から表示しきれなくなったViewが来る。 既に画面に表示されているならばnullが来る。 画面から表示しきれなくなったView(convertView)を使い回すことで負荷削減する。 Get a View that displays the data at the specified position in the data set. convertViewをArticleViewにダウンキャストした結果をViewとする。 ダウンキャストがnullならばエルビス演算子(?:)の右辺が評価され、新しいArticleViewが作られる。 View = ((convertView as? ArticleView) ?: ArticleView(context)).apply { setArticle(articles[position]) package com.example.ikuty.myapplication import android.content.Context import android.view.View import android.view.ViewGroup import android.widget.BaseAdapter import com.example.ikuty.myapplication.model.Article import com.example.ikuty.myapplication.view.ArticleView class ArticleListAdapter(private val context: Context): BaseAdapter() { var articles: List = emptyList() override fun getCount(): Int = articles.size override fun getItem(position: Int): Any? = articles[position] override fun getItemId(position: Int): Long = 0 override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View = ((convertView as? ArticleView) ?: ArticleView(context)).apply { setArticle(articles[position]) } }
ListViewの1行を表示するViewControllerを書いてみる
ボキャブラリーが貧弱でアレなんだけども、やりたいことはそんな感じ。 リスとビューの1行に表示するアイテムのViewCOntrollerを書いてみる。 1行に前回定義したArticleを表示する予定なので、そういう文脈になっている。 findViewById()ではないKotlin風のViewの取り方 findViewById()を使うことで、IDから該当するViewを取ることができるけれど、 その応答値はnullableなので、findViewById()を使って取ったViewを使いまわそうとすると、 nullableに対するケアがつきまとうようになる。 Kotlinのlazy()を使うことでnullableでないViewの取り方を実現できる! lazy()は移譲プロパティの移譲先を書く書き方。公式の仕様はこちら。 遅延プロパティ、つまりアクセスされたときに初めて評価されるプロパティを実現する。 以下、Viewに<T: View> bindView(@IdRes id:Int)という拡張関数を追加している。 追加した拡張関数の戻りがnullを許容しないから、 これを経由すればnullのケアをしなくて良くなる(という話) lazy()の中で評価したfindViewById()がnullを返した場合、ダウンキャストの失敗を どう扱うんだろうか。 package com.example.ikuty.myapplication import android.support.annotation.IdRes import android.view.View fun View.bindView(@IdRes id: Int): Lazy = lazy { findViewById(id) as T } 実装 この拡張関数を使ったArticleViewは以下。 プロパティとして保持するViewがnull非許容になっている。 それにより、setArticle(artilce:Article)において、 プロパティにアクセスする際にnullのケアをしないで済んでいる。 これは便利なんじゃなかろうか。 package com.example.ikuty.myapplication.view import android.content.Context import android.graphics.Color import android.util.AttributeSet import android.view.LayoutInflater import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView import com.example.ikuty.myapplication.R import com.example.ikuty.myapplication.model.Article import com.example.ikuty.myapplication.bindView class ArticleView : FrameLayout { constructor(context: Context?) : super(context) constructor(context: Context?, attrs: AttributeSet?) : super(context,attrs) constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context,attrs,defStyleAttr) constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context,attrs,defStyleAttr,defStyleRes) val profileImageView: ImageView by bindView(R.id.profile_image_view) val titleTextView: TextView by bindView(R.id.title_text_view) val userNameTextView: TextView by bindView(R.id.user_name_text_view) init { LayoutInflater.from(context).inflate(R.layout.view_article,this) } fun setArticle(article: Article) { titleTextView.text = article.title userNameTextView.text = article.user.name profileImageView.setBackgroundColor(Color.RED) } }
Percelableを実装してIntentに乗せてBundleに渡せるモデルを書く
Percelable interface モデルをActivity間でやりとりしたり、再生成時の復元処理を書いたり、など、 Percelableというインターフェースを実装すれば、面倒みてくる。 Bundleには基本的な型以外に、Percelableを実装したインスタンスを渡すことができる。 (つまり、ActivityとFragmentに渡すことができる。) Percelableを実装するとIntentに載せることができる。 Parcelable interfaceは2つの仮想関数を持っていてそれぞれ実装する。 CREATORというstaticメンバを実装する。 abstract describeContents() abstract writeToPercel(Percel dest, int flags) CREATOR abstract describeContents() describeContents()はひとまず0を返すようにする。 0以外の応答値が必要な場合についてひとまず省略。 abstract writeToPercel(Percel dest, int flags) writeToPercel(Percel dest, int flags)はPercelに保存するデータを列挙する。 通常クラスが持つプロパティを列挙する。 CREATOR CREATORという名前のstaticフィールドを実装する必要がある。 Interface for classes whose instances can be written to and restored from a Parcel. Classes implementing the Parcelable interface must also have a non-null static field called CREATOR of a type that implements the Parcelable.Creator interface. Kotlinはstaticフィールドに対応していない(companionオブジェクトが対応する)ため、 companionオブジェクトとして実装する必要がある。 Percelable.Create<T>というobject式(シングルトンオブジェクト)を継承して作る。 Kotlinから呼ぶだけなら、companionオブジェクトを用意するだけで外からstaticメンバのように扱えるけれども、 Javaから呼ぶなら、さらに@JvmFieldを付ける必要がある。 Kotlnでプロパティを書くと、Javaに変換する際にgetXXX()やsetXXX()のように 自動的にgetter/setterが付く(らしい)。 プロパティに@JvmFieldアノテーションをつけると、getXXX()/setXXX()ではなく、 直接フィールドを触るように変換される。 @JvmFieldを付けずにstaticフィールドを書こうとすると、 勝手にgetXXX()/setXXX()が付いてしまい要求を満たせなくなる。 (JVM言語っぽいところを初めて理解した図...) 実装例は以下参照。 スコープ関数,run ここ見ながら理解。 任意の型Tの拡張関数で、そのTをレシーバとする関数Rを引数にとる。 public inline fun T.run(f: T.() -> R): R = f() 例えば、文字列\"AIUEO\"を小文字にする例。 val str = \"AIUEO\".run { toLowerCase() } println(s) // aiueo 実装例 Kotlinの文法の基本を詰めていれば問題なし。 package com.example.ikuty.myapplication.model import android.os.Parcel import android.os.Parcelable data class Article(val id: String, val title: String, val url: String, val user: User) : Parcelable { companion object { @JvmField val CREATOR: Parcelable.Creator = object : Parcelable.Creator { override fun createFromParcel(source: Parcel): Article = source.run { Article(readString(),readString(),readString(), readParcelable(Article::class.java.classLoader)) } override fun newArray(size: Int): Array = arrayOfNulls(size) } } override fun describeContents(): Int = 0 override fun writeToParcel(dest: Parcel, flags: Int) { dest.run { writeString(id) writeString(title) writeString(url) writeParcelable(user, flags) } } }
Kotlinスタートブックを読み始める
断片的な情報を集めるよりも、評判が良い良書を読む方が確実だし結果的に理解が早いと思う。 なので、超入門に続いて、Kotlin中級書として最も評判が良いだろう本を読み始めてみる。 何事もアウトプットしないと理解が定着しないので、引き続きWPへ転記。 超入門に使った書籍が薄すぎて...と思っていたのだけど、 もしかすると、さっそくKotlinの簡潔さに触れたのかもしれない。 言語仕様からくだらない拘りを捨てて、いかに現実的な書き方を実現するか、というところ。 これは良いかもしれないぞ、と。 Kotlinスタートブックposted with amazlet at 18.12.10リックテレコム (2017-03-21)売り上げランキング: 68,877Amazon.co.jpで詳細を見る 実際読んでみると、超入門で使ったKindle本で良かったかなと思う。 確かに上の方が書いてある幅が広いんだけど、下の方が要点がまとまっている気がする。 こちらの方が薄いし500円で買えるし。 速習 Kotlin: Javaより簡単!新Android開発言語を今すぐマスター 速習シリーズposted with amazlet at 18.12.10WINGSプロジェクト (2018-09-26)売り上げランキング: 1,981Amazon.co.jpで詳細を見る Amazon Cyber MondayでKindle Paper whiteを手に入れたので、 この手の本は、今後はKindleで入手する見込み。
Swift超入門_関数とクロージャ的な何か
仮引数と省略 swift初心者(おっさん)が一見して理解できなかったのが関数の仮引数の前にあるアンダースコア(_)。 swift全般的にアンダースコアが言語仕様に含まれているので、いろいろ調べてみた。 単に、関数を呼び出す際にキーワード引数の名称を省略するための記法だった。 仮引数の前にアンダースコア(_)を置くと、呼び出し時にキーワード無しで引数を呼べる。 func hoge(_ int: Int) { print(int) } hoge(100) func hoge2(int: Int) { print(int) } hoge2(int: 200) Swiftは、仮引数と実引数を違う名前で定義できる。 以下、param2というキーワードを使ってhoge3を呼び出しているが、 hoge3の中では仮引数param2をparam3として使っている。 一般に、param3をparam1と関係がある名称にすることで冗長にさせない工夫。 func hoge3(param1: Int, param2 param3: Int) { print(\"(param1),(param3)\") } hoge3(param1: 100, param2: 200) デフォルト引数は一般的。 func hoge4(param1: Int, param2 param3: Int = 200) { print(\"(param1),(param3)\") } hoge4(param1: 100) 関数の定義名に特徴がある。hoge4の定義名は以下のようになる。 ObjectiveCっぽい。 hoge4(param1:param2) インアウト引数 いわゆる参照渡し。 仮引数にinout予約語をつけておくと、仮引数の操作が実引数に反映される。 呼び出す際に、引数に&をつける。 func hoge5(param1: Int, param2: inout Int) { param2 = 300 print(param1, param2) } var val1 = 100 var val2 = 200 hoge5(param1: val1, param2: &val2) // 100 200 print(val2) // 300 可変長引数 可変長引数はスリードット(...)を指定し、配列(Array)で受ける。 func hoge6(params: String...) { for param in params { print (\"element is (param)\") } } hoge6(params: \"hoge1\",\"hoge2\",\"hoge3\") クロージャ さて、Swiftのクロージャ。まぁ何回か使ったら慣れるだろうぐらい分かりづらくはない。 前置詞のinがクロージャブロックの開始になってるのは意図があるんだろうか。 let double = { (x:Int) -> Int in return x * 2 } let v = double(2) クロージャの引数の型と戻り値の型は型推論が効く。 var closure: (Int) -> Int closure = { (x:Int)-> Int in return x*x } closuer(2) // 4 var closure2 = { x in return x*2 } closure2(3) // 6 クロージャの簡易引数名 クロージャの引数名すら書きなくない場合もある。 前置詞inではなく、一般的なブラケットでクロージャブロックを書く。 クロージャブロックからは、n番目のパラメタを$nとして受け取る。 let add: (Int,Int)->Int = { return $0+$1 } add(2,3) // 5 クロージャによる変数束縛 クロージャの実行時にクロージャ呼び出し元の環境を利用できる。 文脈によって呼び出し元の環境は異なるので、実行時に環境のスナップショットを取るイメージ。 Swiftでは\"キャプチャ\"という名前で呼んでいるらしい。 例えば以下。クロージャ add:(Int,Int)-> Int 内で呼び出し元の変数pを使っている。 クロージャ呼び出し時に呼び出し元の環境に依存してpの値が決まる。 なお、pが初期化済みでない状態でクロージャを定義するとエラーになる。 クロージャの実行前にキャプチャする変数は初期化されている必要があるらしい。 var p : Int = 200 let add: (Int,Int)->Int = { return $0+$1 + p } p = 200 add(2,3) // 205 p = 300 add(2,3) // 305 クロージャ内でキャプチャした変数を書き換えた場合、キャプチャ元になった変数自体が変わる。 autoclosure属性 引数の遅延評価を行う指令。 論理和を計算する関数を定義しようとしたとき、Bool型の引数を2つ取ってBool型の戻り値を返す 関数を定義するとする。 論理和なので、どちらかが真であれば一方の引数を見なくても結果がわかるのだが、 下記のようにすると、どちらかが真であっても、もう一方の引数を評価して返すことになる。 func or(_ lhs:Bool, _ rhs:Bool) -> Bool { if lhs { return true } else { return rhs } } or(true,false) 引数をクロージャとし、クロージャ内で引数の評価の必要性を判断するようにして、 クロージャを遅延評価(関数内での評価)することで不要な引数の評価を回避するというアイデア。 func or2(_ lhs:Bool, _ rhs:()->Bool) -> Bool { if lhs { return true } else { let rhs = rhs() return rhs } } func lhs() -> Bool { return true } func rhs() -> Bool { return false } or2(lhs(),{return rhs()}) or2の第2引数が複雑になってしまうところを、文法解決で簡単に書けるようにするのが@autoclosure。 引数をクロージャで包む書き方を省略できる。 下記のように書ける。 func or3(_ lhs:Bool, _ rhs: @autoclosure ()->Bool) -> Bool { if lhs { return true } else { let rhs = rhs() return rhs } } func lhs() -> Bool { return true } func rhs() -> Bool { return false } or3(lhs(),rhs()) trailing closure 引数の最後がクロージャである場合、その引数の前で引数のブラケットを閉じて、 その後にクロージャを書くことができる。 例えば以下のような感じ。 1つ目は真面目に引数の最後にクロージャを与えている。 2つ目は関数呼び出しの後にクロージャを書いている。 func execute(parameter:Int, closure: (String)->Void) { closure(\"parameter is (parameter)\") } execute(parameter:1, closure:{ string in print(string) }) execute(parameter:2) { string in print(string) } 関数をクロージャとして扱う 関数をクロージャとして扱うことができる。関数double(_:)は(Int)->Int型。 それをfunctionという定数の初期値に設定している。 functionには型アノテーションがないけれども、 double(_:)が(Int)->Intなので型推論されて(Int)->Intになる。 func double(_ x:Int)->Int { return x*2 } let function = double function(2) 関数の引数としてクロージャを与えるシーンで、 クロージャを関数として用意しておくことで、それぞれの呼び出しでクロージャを定義する必要がなくなる。 下記について、上はmapの引数としてそれぞれクロージャを定義して呼び出している。 同じ処理をするのに2回定義しないといけない。 下は、関数を定義し、mapの引数として関数をクロージャとして与えている。 関数(クロージャ)の定義は1回で良い。 let array1 = [1,2,3] let toArray1b = array1.map{$0*2} let array2 = [4,5,6] let toArray2b = array2.map{$0*2} func double(_ x:Int)->Int { return x*2 } let toArray1a = array1.map(double) let toArray2a = array2.map(double)
Swift4基本構文_制御文的な何か
オプショナルバインディング</h3 Optional型の存在の有無を冗長な書き方にしないためのif-let構文。 if letで指定したOptional型の変数がnilでなければ、構文の中でアンラップ済みの変数を使えるようにする。 ダウンキャストもif letで対応できる。 let oa = Optional(\"a\") if let a = oa { print(\"(a)\") } else { print(\"nil\") } let ob = Optional(\"b\") if let a = oa, let b = ob { print(\"(a),(b)\") } else { print(\"nil\") } let any : Any = 1 if let iany = any as? Int { print(\"(iany)\") } guard,guard-let ある関数のスコープ内で、guardで指定した条件が満たされることを保証する。 関数の先頭で引数に対してguardを定義することで引数の前提条件を書いたりする。 if-letのguard版(guard-let)もある。 func someFunc() { let value = 1 guard value >= 0 else { return } // ここまで来たならvalueが0以上であることを保証する print(\"0以上です。\") } func someFunc2() { let any : Any = 1 guard let iany = any as? Int else { return } print(\"(iany)\") } nil可能な引数を取る加算関数をif,guard両方で書くと以下。 guardで書いた方が見通しがよくなる。 func add(_optionalA:Int?, _optionalB:Int?) -> Int? { let a: Int let b: Int if let unwrappedA = _optionalA { a = unwrappedA } else { return nil } if let unwrappedB = _optionalB { b = unwrappedB } else { return nil } return a + b } func add2(_optionalA:Int?, _optionalB:Int?) -> Int? { guard let a = _optionalA else { return nil } guard let b = _optionalB else { return nil } return a + b } for-in,for-case for-inで要素の全てにアクセスする。for-caseで条件を満たす要素にアクセスする。 foreachで回す先頭で条件でスキップする奴はfor-caseで書ける。 let dict:Dictionary = [\"a\":1,\"b\":2] for (key,value) in dict { print(\"(key):(value)\") } let ary:Array = [1,2,3,4] for case 2...3 in ary { print(\"2以上3以下\") } fallthrough switch文の各caseの終わりでfallthroughを書いたときに初めて後ろのcaseが実行される。 fallthroughを書かないと条件にマッチしたcaseした実行されない。 なので、switch-caseの各caseの末尾にbreakを書く必要はない。 let a = 1 switch a { case 1: print(\"case1\") fallthrough case 2: print(\"case2\") default: print(\"other\") } 遅延実行 関数の評価時ではなく、関数のスコープから抜けたときに実行する文を記述する。 以下、関数評価時にはgは0だが、関数がスコープから抜けたときにdeferが評価されgが1になる。 var g = 0 func someFunc2() -> Int { defer { g += 1 } print(\"(g)\") return g } someFunc2() print(\"(g)\")
Swift4基本構文_型的な何か
Kotlinに超入門してみたので続いてSwiftに超入門してみる。 超入門に使用する書籍は以下。 [改定新版]Swift実践入門-直感的な文法と安全性を兼ね備えた言語 WEB+DB PRESS plus Playgroundsを使って確認しながら各単元の何かを書いてみる。 変数と定数 変数はvar、定数はletで宣言する。Kotlinと同様に宣言時に型情報が決まる必要がある。 つまり、変数(定数)宣言時に型アノテーションを書くか、初期値の型から型推論させるか。 型推論が出来るので、右辺から型が決まる場合には左辺の型定義(型アノテーション)は省略可。 let i: Int = 123 let j = 123 print(type(of:i)) // Int print(type(of:j)) // Int nilは代入不可。使用時までに値を入れている必要あり。 let i: Int = nil // Nil cannot initialize specified type \'Int\' let j: Int j = nil // Nil cannot be assigned to type \'Int\' let v: Int print(v) // Constant \'v\' used before being initialized スコープ 外側の宣言は内側の宣言で使える。逆はできない。特別な予約語は不要。普通。 let globalint: Int = 100 func hoge() { lef localint: Int = 200 print(globalint) // 100 print(localint) // 200 } Bool型,リテラル Bool型のリテラルはtrue,false。 演算子は論理和、論理積、否定のみ。普通。 let a: Bool = true let b: Bool = false print (a && b )// false print (a || b )// true print (!a) // false Int型,リテラル IntはInt8,Int16,Int32,Int64。 32bitプラットフォームでIntはInt32、64bitだとInt64。普通。 Float型,Double型,リテラル 32bitがFloat、64bitがDouble。 Float,Doubleは無限(isInfinite)と非数値(isNaN)という状態をStaticプロパティとして持つ。 let a: Double = 10.0 / 0.0 a.isInfinite // true let b: Double = 0.0 / 0.0 b.isNaN // true 異型間の代入 Swiftでは型が異なる変数同士の代入を暗黙的にできない。イニシャライザをかます。 縮小方向の代入は端数処理が行われる。 let a: Int32 = 100 let b: Int64 = a // Cannot convert value of type \'Int32\' to specified type \'Int64\' let c: Int64 = Int64(a) let x: Float = 1.0 let y: Int = Int(x) // 1 異型間の比較 Swiftでは型が異なる変数同士の比較ができない。イニシャライザをかます。 結構、型の違いにシビアなのか...。 Int32とInt64は比較できるみたい。FloatとDoubleはできない。 let a: Float = 100.0 let b: Double = 100.0 let c = (a == b) // Binary operator \'==\' cannot be applied to operands of type \'Float\' and \'Double\' let d = (a == Float(b)) // true String型,リテラル 文字列リテラルはダブルクォーテーション。エスケープは一般的なやつが使える。 リテラル内での変数展開は()演算子。${}の方がいいのに...。 let x: Int = 100 let v: String = \"hoge(x)fuga\" // hoge100fuga kotlinでも見かけたスリーダブルクォート。これの発祥はPythonらしい。 面白いのがスリーダブルクォート内において変数自体のインデント分が無視されること。 kotlinのTrimよりも強烈。以下、sとrの出力は同じになる。 let s: String = \"\"\" Swift入門 Swift入門 Swift入門 \"\"\" print(s) let r: String = \"\"\" Swift入門 Swift入門 Swift入門 \"\"\" print(r) String.Index StringとCharacterの関係も一般的。String内のCharacterを指す方法が用意されている。 String.startIndex(=0),String.endIndex(=文字数)を使ってCharacterを指す例。 let a: String = \"abcde\" let b: Character = a[a.startIndex] // a let lastindex = a.index(a.endIndex,offsetBy: -1) let c: Character = a[lastindex] // e Int型とString型の変換 イニシャライザを通す。変換できない場合はnilが入る。 let i: Int = 100 let s: String = String(i) let s2: String = \"hoge\" let i2 = Int(s2) // nil 文字列の結合 文字列の結合は+演算子。append(_:)も使える。 let s1: String = \"hoge\" let s2: String = \"fuga\" let s3: String = s1 + s2 let s4: String = s1.append(s2) 配列,配列リテラル 配列はArray。SyntaxSugarとして[Element]という書き方もできる。 配列リテラルは[]。同一型リテラルの配列から型推論可能。 順序数で要素にアクセス可能。 append(_:)で追加、insert(_at:)で挿入、remote(at:)で削除。 let ary1: Array = [1,2,3,4,5] let ary2: [Int] = [1,2,3,4,5] let ary3 = [1,2,3,4,5] // Intの配列 let ary4 = [\"hoge\",\"fuga\"] // Stringの配列 let a = ary1[0] // 1 ary1.append(6) // [1,2,3,4,5,6] ary1.insert(10,at: 1) // [ 1,10,2,3,4,5,6] ary1.remove(at: 0) // [10,2,3,4,5,6] ary1.removeLast() // [10,2,3,4,5] ary1.removeAll() // [] Dictionary型,リテラル Key->Value辞書はDictionary、SyntaxSugerとして[Key:Value]という書き方もできる。 辞書リテラルは[\"key1\":\"value1\",\"key2\":\"value2\"]のように書く。 同一型の辞書から型推論可能。 let dic1 : Dictionary = [\"a\":1,\"b\":2] let dic2 : [String:Int] = [\"a\":1,\"b\":2] let dic3 = [\"a\":1, \"b\":2] Keyは一意でないといけないので、Keyとして使える型には制限がつく。 DictionaryはDictionary。Key型はHashableプロトコル(インターフェース?) に準拠したもの(Keyからハッシュ値を計算できる)のみ使える。 DictionaryへのアクセスはOptional(Value)を返す。つまり無いキーの値はnil。 let dic3 = [\"a\":1, \"b\":2] let a = dic3[\"a\"] // 1 let b = dic3[\"c\"] // nil 更新,追加は同じ。存在するkeyを指定して値を代入すれば更新、存在しないkeyなら追加。 削除はnilを設定する。 var dic4 = [\"a\":1,\"b\":2] dic4[\"b\"] = 3 // [\"a:1\",\"b\":3] dic4[\"c\"] = 4 // [\"a:1\",\"b\":3,\"c\":4] dic4[\"a\"] = nil // [\"b\":3,\"c\":4] 範囲型 Swiftの範囲型は数学的なモデリングになってる。 1つ目は開区間、半開区間、閉区間。2つ目は順序(計数可能)。 数値以外の範囲についてもforループを回して順番に要素にアクセスできたりする。 範囲型が範囲を定義すると、範囲に収まる値が得られるのではなく、 区間と上限下限をもったオブジェクトのまま保持される。 let range1 = 1..<4 // CountableRange(1..<4) : lower bound=1,uppper bound=4 for value in range1 { print(value) // 1 2 3 } let range2 = 1...4 // CountableCloseRange(1..4) : lower bound=1,uppper bound=4 for value in range2 { print(value) // 1 2 3 4 } let range3 : Range = 1..<4 // Range<1..<4) :lower bound=1,upper bound=4 let range4 : Rnage = 1...4 // Range<1..4) : lower bound=1,upper bound=4 Optional型 型がnilを許容するか否か。Wrapped型がnilを許容する場合はOptional、許容しない場合はWrapped。 Suger Syntaxとして、OptionalをWrapped?と書くこともできる。 let ival1: Int = 32 let ival2: Int = nil // Nil cannot initialize specified type \'Int\' let ival3: Optional = 32 let ival4: Optional = nil let ival5: Int? = nil ぬるぽ防止(Unwrap) デフォルトではOptional型同士の四則演算はできない。 明示的なアンラップ(Optional->Wrapped)が必要。 ??演算子を使うと、左辺のOptional(Wrapped)がnilの場合に右辺、 nilでない場合にWrappedをアンラップして返す。 !演算子を使うと強制アンラップ。当然nilならぬるぽ。ぬるぽ時のエラーがドキッとする。 当然、強制アンラップは非推奨。 let ival6: Int? = 100 let ival7: Int? = 200 let ival8 = ival6 + ival7 let ival6: Int? = 100 let value1 = ival6 ?? 3 // 100 let ival7: Int? = nil let value2 = ival7 ?? 3 // 3 let ival8: Int? = 100 let ival9: Int? = 200 let ival10 = ival8! + ival9! // error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0). ぬるぽ防止(Optional chain) Optionalの値を使うために必ずアンラップしないといけないとなると面倒。 ?.演算子を使うと、nil、非nilの場合を1行に含めることができる。 左辺がnilである場合はnil、nilでない場合はOptional(Wrapped)を返す。 ?.を数珠つなぎに書いていくと、最初のnilで評価が止まってそれ以降のnil参照を行わない。(なのでchain) let optionalDouble1 = Optional(1.0) // Optional(1.0) let optionalInfinite1 = optionalDouble1?.isInfinite // Optional(false) let optionalDouble2 :Double? = nil let optionalInfinite2 = optionalDouble2?.isInfinite // nil 強制アンラップ構文 ぬるぽを恐れるあまり、nil可能なケースが面倒になっているのを和らげるためか、 何か迷いのようなものを感じるけれども、OptionalをWrapped?ではなくWrapped!と書くと、 型としてOptionalを維持したまま、暗黙的に強制アンラップしてから計算が行われる。 当然、nilが入っていればぬるぽで止まる。 let intval: Int? = 100 print(intval + 200) // Value of optional type \'Int?\' must be unwrapped to a value of type \'Int\' let intval: Int! = 100 print(intval + 200) // 300 let intval2: Int! = nil print(intval2 + 200) // error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0). アップキャスト 継承関係やプロトコル(インターフェース?)準拠により上位型の存在が確実な場合、 アップキャスト可能。アップキャストはas演算子。 確実な上位関係でない型へのアップキャストは実行時エラー。 アップキャストは暗黙的に実行可能。 let str : String = \"hogehoge\" let any : Any = str as Any let int : Int = str as Int // Cannot convert value of type \'String\' to type \'Int\' in coercion let any2 : Any = str ダウンキャスト 上位型から下位型へのキャスト。失敗のリスクがある。 失敗のリスクをOptionalで解決するのがas?演算子、リスクをケアしないのがas!演算子。 as?によるダウンキャストはOptionalを返す。as!はWrappedを返す。 as!が失敗した場合、実行時エラーが発生する。 let any = 1 as Any let int = any as? Int // Optional(1) let any2 = 1 as Any let int2 = any2 as! Int // 1
Kotlin@IntelliJ IDEAでAndroid hello world.
古めのIntelliJ IDEAがAndroid Studioで使われている様子で、 IntelliJ IDEAを使おうがAndroid Studioを使おうが大差ないのかもだけども、 Androidの最新の動向をトレースできるのはきっとAndroid Studio。 以下の記事を順当に流してとりあえずKotlin Android Hello World環境ができた。 IntelliJ IDEAで、JavaSDK,AndroidSDK,Android support plugin,Gradle pluginの設定をするのは順当。 参考にしたやつ。 https://qiita.com/shitake/items/150d7a94b66ba73a93ba https://qiita.com/g0z4ru/items/24bca8e6fb691f29260f 今日現在、ext.kotlin_versionがデフォの1.3.0だといろいろ見つからないと言われるので1.2.71に設定。 // build.grade // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.kotlin_version = \'1.2.71\' // change from 1.3.0 to 1.2.7 repositories { google() jcenter() } dependencies { classpath \'com.android.tools.build:gradle:3.1.0\' classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version\" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } MainActivity.kt。ちゃんとKotlinになってる。 5年前くらいにAndroidアプリを開発していたころと同じMacで作業しているのだけども、 エミュレータが遅いのは相変わらずな以外は、意外にもモッサリしていない。 当時はEclipse、今はIntelliJ IDEA(Android Studio)。 割と2013LateのMBPで何かやろうとする気になる。 サンプルをビルドするだけでなくて、 何か簡単なアプリを1つ作ってみよう..。