S-JIS[2010-12-06/2013-06-08] 変更履歴
Scalaの文字列のメモ。
Scalaでは、文字列をjava.lang.Stringで扱う。すなわちJavaの文字列そのものであり、不変オブジェクトとなる。
必要に応じて自動的にStringOpsにラップされて、そのメソッドが使われる(Predefで暗黙変換が定義されている)。
StringOpsはStringLikeから派生している(Scala2.8)ので、そのメソッドも使用できる。
(Scala2.7まではRichStringだったらしい)
implicit def augmentString (x: String): StringOps = new StringOps(x) implicit def unaugmentString(x: StringOps): String = x.repr
ダブルクォーテーションで囲むと文字列リテラルになる。
scala> "abcd" res0: java.lang.String = abcd
リテラル内の円マーク「\」はエスケープ文字となる。
scala> "ab\ncd" res1: java.lang.String = ab cd scala> "ab\\ncd" res2: java.lang.String = ab\ncd
ダブルクォーテーション3つで囲むと円マークや改行も直接記述できる。(エスケープ文字扱いされない)
「生文字リテラル(raw string literal)」と呼ばれる機能。(C#の逐語的文字列「@"〜"
」と同様)
「複数行文字列リテラル」と呼ぶこともあるようだ。(バク本p.42)[2011-02-07]
scala> """ab\ncd""" res3: java.lang.String = ab\ncd scala> """ab | cd""" res4: java.lang.String = ab cd scala> """C:\temp\foo.txt""" res5: java.lang.String = C:\temp\foo.txt
※エスケープ文字(\)・Unicodeエスケープ(\u)については、Javaと同様のルールで扱われる。
生文字リテラルでは、ダブルクォーテーションもそのまま記述できる…けど分かりにくいな^^;[2010-12-08]
scala> """abc"def""" res1: java.lang.String = abc"def scala> """"""" res2: java.lang.String = " scala> """""""" res3: java.lang.String = "" scala> """"""""" res4: java.lang.String = """ scala> """"a","b","c"""" res5: java.lang.String = "a","b","c"
呼ばれるクラス(メソッドが定義されているクラス)が「String」になっているものは、Javaのメソッドがそのまま呼ばれる。
内容 | 例 | バージョン | 備考 | |
---|---|---|---|---|
文字列の長さ | val len = str.length |
String | サロゲートが含まれない場合の方法。 | |
val len = str.size |
SeqLike | |||
val len = str.codePointCount(0, str.length) |
String | サロゲートが含まれる場合の方法。 | ||
等値比較 | str == "abc" |
AnyRef | Javaとは異なり、「==」で比較する。 | |
str.equalsIgnoreCase("ABC") |
String | 大文字小文字の区別をせずに比較する。 | ||
大小比較 | str < "abc" |
Ordered | 内部ではcompare()が呼ばれる。 | |
str <= "abc" |
||||
str >= "abc" |
||||
str > "abc" |
||||
str.compareToIgnoreCase("ABC") < 0 |
String | 大文字小文字の区別をせずに比較する。 | ||
str.compare("abc") < 0 |
StringLike | |||
先頭比較 | str.startsWith("a") |
String | ||
末尾比較 | str.endsWith("c") |
|||
空文字列チェック | str.isEmpty |
String | 空文字列の場合、true | |
str.nonEmpty |
TraversableOnce | 空文字列でない場合、true | ||
文字探索 | val n = str.indexOf('c') |
String | 文字の位置を返す。 indexOfは先頭から、lastIndexOfは末尾から探索する。 |
|
val n = str.lastIndexOf('c') |
||||
str.indexOfSlice("bc", 0) |
SeqLike | strの一部分の中から、文字列の位置を返す。[2011-06-11] | ||
str.contains("bc") |
String SeqLike |
文字が含まれているかどうか。[2011-06-11] | ||
str.containsSlice("bc") |
SeqLike | indexOfSliceで文字列が見つかるかどうか。[2011-06-11] | ||
エンコード変換 | val bytes = str.getBytes("MS932") |
String | ||
フォーマット | val s = "%d:%s".format(n, str) |
StringLike | Javaだと「String s = String.format("%d:%s", n, str) 」と書く。→書式文字列(Java) →文字列の整形 |
|
結合 | val s = str + "def" |
SeqLike |
結合された文字列を返す。 | |
val s = str * n |
SeqLike | 文字列をn個結合した文字列を返す。 | ||
str.addString(sb) |
TraversableOnce | StringBuilder(の末尾)に文字列を追加する。 | ||
文字埋め | val s = str.padTo(5, ' ') |
SeqLike | 指定された文字数になるように右側に文字を追加する。 | |
部分文字列 | val s = str.substring(start) |
String | start文字目以降の文字列を返す。 | |
val s = str.substring(start, end) |
String StringOps |
start〜end文字目の文字列を返す。 | ||
val s = str.take(n) |
IndexedSeqOptimized | 先頭n文字の文字列を返す。 | ||
val s = str.takeRight(n) |
末尾n文字の文字列を返す。 | |||
val s = str.drop(n) |
先頭n文字を除去した文字列(n文字目以降の文字列)を返す。 | |||
val s = str.dropRight(n) |
末尾n文字を除去した文字列を返す。 | |||
val c = str.head |
先頭の1文字を返す。 | |||
val c = str.last |
末尾の1文字を返す。 | |||
val s = str.init |
末尾1文字を除いた文字列を返す。 | |||
val s = str.tail |
先頭1文字を除いた文字列(2文字目以降の文字列)を返す。 | |||
val c = str.charAt(n) |
String StringLike |
n文字目の文字を返す。 | ||
トリム | val s = str.trim |
String | 前後の空白を除去した文字列を返す。 | |
末尾改行除去 | val s = str.stripLineEnd |
StringLike | 末尾の改行を1つだけ除去した文字列を返す。 | |
文字削除 | val s = str.diff("c") |
SeqLike | 引数の文字を1個だけ削除した文字列を返す。 複数の文字を指定すると、それぞれ1個ずつ削除する。 |
|
置換 | val s = str.replace(from, to) |
String | 文字列fromを文字列toに置換した文字列を返す。[/2011-06-11] | |
val s = str.replaceAllLiterally(from, to) |
StringLike | 2.9 | ||
val s = str.replaceFirst(regex, to) |
String | 正規表現にマッチする部分を置換した文字列を返す。[2011-06-11] | ||
val s = str.patch(n, to, len) |
SeqLike | n文字目からlen文字分をtoに置き換えた文字列を返す。 lenを0にすると、文字列toを挿入することになる。 |
||
val s = str.updated(n, c) |
SeqLike | n文字目を文字cに置換した文字列を返す。 | ||
逆順 | val s = str.reverse |
IndexedSeqOptimized | 順序を反転させた文字列を返す。 | |
分割 | val ss = str.split(",") |
String | 指定された文字(正規表現)で分割する。 第2引数を付けると、その個数まで分割される。 -1だと無制限、0だと末尾がカンマのみで構成されていた場合にその項目は無視される。 第2引数を省略した場合は、0を指定したのと同じ扱い。 |
|
val ss = str.split(",", 個数) |
||||
val ss = str.split('.') |
StringLike | 指定された文字で分割する。 (正規表現ではなく文字そのものなので、ピリオドとかもそのまま指定可能) |
||
val t = str.splitAt(n) |
IndexedSeqOptimized | n文字目で分割された2つの文字列をタプルで返す。 | ||
for (s <- str.lines) println(s) |
StringLike | 改行コードで区切って文字列を順番に返す。 | ||
正規表現 | "正規表現".r |
StringLike | 正規表現を扱うRegexクラスのインスタンスを返す。 | |
コレクション取得 | str.iterator |
IndexedSeqLike | 文字のIteratorを返す。[2011-06-11] | |
str.seq |
StringOps | 2.9 |
他にも何やら色々あるので、StringLikeのScaladocを見てみるとよい。
文字列の整形方法のメモ。[2011-02-11]
例 | 備考 |
---|---|
String.format("%04d", 123.asInstanceOf[AnyRef]) |
JavaのString#format()と同様にStringオブジェクトのformatメソッドを呼び出すことが出来る。 ただし引数がJavaのプリミティブ型に相当する場合、自動ボクシングは行ってくれないようなので、 自分でキャストする必要がある。 |
"%04d".format(123) |
Scalaでは書式文字列に対してformatメソッドを記述することが出来る。 書式文字列はJavaの書式文字列と同様。 |
printf("%04d", 123) |
コンソール(標準出力)に出力したい場合はprintf関数が使える。 |
val name = "hoge" |
変数の値を文字列の一部に埋め込みたい場合、XMLリテラルを使うと便利かも。 (邪道かも?^^;) |
val name = "hoge" |
Scala2.10では文字列の補間機構が使える。[2013-06-08] |
Scala2.10で、文字列の補間・整形(string interpolation and formatting)の機構が追加された。[2013-06-08]
文字列内に演算式を書く(埋め込む)ことが出来る。
文字列のダブルクォーテーションの前にsやfといった文字(補間子)を付ける。
そして、ダブルクォーテーションで囲まれた文字列内の補間(置換)したい部分に「${〜}
」で値を記述する。
例 | 結果 | 同義 | 備考 |
---|---|---|---|
s"abc ${ 123 } def" |
abc 123 def |
"abc " + 123 + " def" |
s補間子の例。 |
val foo = "bar" |
abc bar def 3 |
val foo = "bar" |
変数や演算も使用可能。 |
s"abc $foo def" |
abc bar def |
"abc " + foo + " def" |
変数名のときは{} で囲まなくても大丈夫。 |
s"""abc $foo def""" |
abc bar def |
"""abc """ + foo + """ def""" |
生文字リテラルでも使える。 |
s"ab${ s"c $foo d" }ef" |
abc bar def |
"ab" + ("c " + foo + " d") + "ef" |
補間子をネストすることも出来る。 |
f"abc ${ 12 }%04d def" |
abc 0012 def |
"abc %04d def".format(12) |
f補間子だと%を付けて書式を指定できる。 |
val zzz = 12 |
abc=0012 |
val zzz = 12 |
|
f"abc=$zzz" |
abc=12 |
"abc=%s".format(zzz) |
%の指定が無いときは「%s」と同じ。 |
raw"abc\ndef" |
abc\ndef |
"""abc\ndef""" |
raw補間子は\n等のエスケープ文字を解釈せずそのまま出力する。 |
val dir = raw"C:\temp" |
C:\temp\new1 |
val dir = """C:\temp""" |
Windowsでファイルのパスを書くのに便利そう。 「\t」や「\n」がタブや改行にならない。 |
raw"C:\user" |
error: error in unicode escape |
ただし、(やはり)「\u」はUnicodeエスケープとして解釈されてしまう。 | |
raw"abc"def" |
error: unclosed string literal |
生文字リテラルではダブルクォーテーション自身や改行を含めることが出来たが、 raw補間子では出来ない。 |
文字列の補間機構はStringContextというクラスで実現されている。
s補間子の場合、実体は以下の様なコードになる。
val str = s"abc ${foo} def ${ 1+2 } ghi" ↓ val str = StringContext("abc ", " def ", " ghi").s(foo, 1+2)
「${〜}
」の部分で文字列が分割されてStringContextに渡され、補間される値部分が補間子のメソッドに渡される。
StringContextには補間子と同名のメソッド(s・f・raw)が定義されている。
(f補間子は実体がマクロになっていて、MacroImplementationsというクラスで実装されているようだが)
したがって、StringContext()の引数の個数は補間子メソッドの引数の個数より常に1だけ多い。
${}
が文字列の先頭や末尾にあった場合は、""
(空文字列)がStringContextの引数に加えられる。
StringContextに対する暗黙クラスを定義してやることにより、自分で補間子を作ることが出来る。
(StringContextは明示的にimportしなくても使える)
補間した結果は必ずしもStringを返す必要は無く、自分で好きなクラスを返せる。
implicit class MyStringContext(val sc: StringContext) extends AnyVal { def list(args: Any*) : Seq[String] = { val str = sc.s(args: _*) //s補間子を利用してString化 str.split(",").map(_.trim) } }
scala> list"abc, ${ 1+2 }, def" res54: Seq[String] = ArraySeq(abc, 3, def)
シンボル(Symbol)も文字列を表す。[2011-02-07]
シンボルのリテラルは、文字の並びの前にシングルクォーテーションを1つ付ける('名前
)。
もしくはSymbol("名前")
でインスタンスを取得する。(Symbolオブジェクトのapply()メソッド)
スペースの入ったシンボルや数字から始まるシンボルを取得するには後者の方法を使うしかない。
Stringとの違いは、Stringは同じ内容でも異なるインスタンスが存在しうる("abc"
とnew
String("abc")
はインスタンスが異なる)のに対し、
Symbolでは同じ内容ならインスタンスも等しい。
scala> "abc" eq new String("abc") res1: Boolean = false scala> 'abc eq Symbol("abc") res2: Boolean = true
なので、SymbolはMapのキーに使うのに向いているのだそうだ。
Symbolオブジェクトのapply()メソッドでシンボルを取得できるが、内部では名前をキーとしてSymbolインスタンスをキャッシュしている。
したがって、同じ名前なら常に同じインスタンスが返る。
(シングルクォーテーションで作るリテラルもSymbolオブジェクトのapply()メソッド呼び出しらしいので、同じ)
Scala2.8の場合、キャッシュにはJavaのWeakHashMapが使われている。
つまり、シンボルがどこからも参照されなくなったら、ちゃんとキャッシュから削除されるという訳だ。
また、このマップにアクセスする際(Symbolインスタンスを取得する際)にはReadWriteLock(読み書きロック)でロックされる。
なので、きちんとMTセーフである(が、多少は遅くなるのかも)。
バク本(p.42)に「シンボルは“インターンされた”文字列」と書かれていたので、てっきりJavaのString#intern()を使って一意の文字列インスタンスを取得しているのかと思ったが、そういう訳ではないようだ。
まぁ、String#intern()は重めの処理らしいので、使わないのが正解なのかも。