S-JIS[2010-12-06/2013-06-08] 変更履歴

Scala文字列

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
val len = str.length()
String サロゲートが含まれない場合の方法。
val len = str.size SeqLike
val len = str.codePointCount(0, str.length) String サロゲートが含まれる場合の方法。
等値比較 str == "abc"
str != "abc"
AnyRef Javaとは異なり、「==」で比較する。
str.equalsIgnoreCase("ABC")
str equalsIgnoreCase "ABC"
String 大文字小文字の区別をせずに比較する。
大小比較 str < "abc"
str.compareTo("abc") < 0
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
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)
str.lastIndexOfSlice("bc", 1)
SeqLike strの一部分の中から、文字列の位置を返す。[2011-06-11]
str.contains("bc")
str.contains('b')
String
SeqLike
文字が含まれているかどうか。[2011-06-11]
str.containsSlice("bc") SeqLike indexOfSliceで文字列が見つかるかどうか。[2011-06-11]
エンコード変換 val bytes = str.getBytes("MS932")
val str = new String(bytes, "MS932")
String  
フォーマット val s = "%d:%s".format(n, str) StringLike Javaだと「String s = String.format("%d:%s", n, str)」と書く。
書式文字列(Java)
文字列の整形
結合 val s = str + "def"
val s = str.union("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)
val s = str.slice(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)
val c = str(n)
String
StringLike
n文字目の文字を返す。
トリム val s = str.trim
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)
val s = str.replaceAll(regx, 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"
val value = 123

val s = "%s = [%d]".format(name, value)
val t = <s>{name} = [{value}]</s>.text
変数の値を文字列の一部に埋め込みたい場合、XMLリテラルを使うと便利かも。
(邪道かも?^^;)
val name = "hoge"
val value = 123

val s = s"${name} = [${value}]"
val t = f"${name} = [${value}%4d]"
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"
s"abc ${foo} def ${ 1+2 }"
abc bar def 3 val foo = "bar"
"abc " + foo + " def " + (1+2)
変数や演算も使用可能。
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
f"abc=$zzz%04d"
abc=0012 val zzz = 12
"abc=%04d".format(zzz)
 
f"abc=$zzz" abc=12 "abc=%s".format(zzz) %の指定が無いときは「%s」と同じ。
raw"abc\ndef" abc\ndef """abc\ndef""" raw補間子は\n等のエスケープ文字を解釈せずそのまま出力する。
val dir = raw"C:\temp"
val f = raw"$dir\new1"
C:\temp\new1 val dir = """C:\temp"""
val f = "" + dir + """\new1"""
Windowsでファイルのパスを書くのに便利そう。
「\t」や「\n」がタブや改行にならない。
raw"C:\user" error: error in unicode escape
raw"C:\user"
        ^
  ただし、(やはり)「\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)

SQLのSELECTをselect補間子で実行する例


Symbol

シンボル(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()は重めの処理らしいので、使わないのが正解なのかも。


Scala目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま