【ITニュース解説】Q&D: String and Iterable in Dart
2026年08月25日に「Dev.to」が公開したITニュース「Q&D: String and Iterable in Dart」について初心者にもわかりやすく解説しています。
ITニュース概要
DartのStringは直接1文字ずつ処理できない。ASCII文字列は`ascii.encode`で数値の並びに変換可能。UTF-8文字を含む文字列は、`utf8.encode`では1文字が複数数値に分かれるため、`String.split("")`で文字のリストに変換すると扱いやすい。
ITニュース解説
システムエンジニアを目指す上で、プログラミングにおける文字の扱いは非常に重要な基礎知識だ。特に、世界中のさまざまな言語や記号を扱う現代のソフトウェア開発では、文字エンコーディングの理解は避けて通れないテーマである。ここでは、プログラミング言語Dartを例に、文字列(String)と、その中の個々の文字をどのように扱っていくか、そしてASCIIやUTF-8といったエンコーディングがどのように関わってくるのかを詳しく見ていこう。
Dart言語では、文字列は文字の並びとして扱われるが、標準の状態では、他の多くの言語のように直接「繰り返し可能なもの」(Iterable)として個々の文字を取り出すことができない。Iterableとは、簡単に言えば、要素を一つずつ順に取り出せるデータ構造のことだ。例えば、リストや配列のようなものはIterableの代表例だ。Dartの文字列をIterableとして扱いたい場合、つまり文字列の中の文字を一つずつ処理したい場合は、一度その文字列を別の形式、具体的には整数のリスト(List<int>)のようなIterableなクラスに変換する必要がある。この変換を行うために、Dartの標準ライブラリであるdart:convertモジュールをインポートして利用することになる。
まず、比較的シンプルで基本的な文字セットであるASCII文字から見てみよう。ASCIIは、英語のアルファベット、数字、一部の記号など、ごく限られた範囲の文字を表現するために使われるエンコーディングだ。各ASCII文字は、0から127までの単一の数値(バイト)で表現される。dart:convertモジュールの中にあるascii.encodeメソッドは、与えられたASCII文字列を、その各文字に対応するASCIIコードの整数値のリスト(List<int>)に変換する役割を果たす。
具体的なコード例を見てみよう。
1import 'dart:convert'; 2 3void main() { 4 final str = "hello world"; 5 for (var c in ascii.encode(str)) { 6 print("${c} (${String.fromCharCode(c)})"); 7 } 8}
このコードでは、「hello world」という文字列をascii.encodeを使って整数のリストに変換し、そのリストの各要素をループで取り出している。String.fromCharCode(c)は、与えられた数値(ASCIIコード)に対応する文字を再構築する関数だ。このコードを実行すると、次のような結果が得られる。
104 (h)
101 (e)
108 (l)
108 (l)
111 (o)
32 ( )
119 (w)
111 (o)
114 (r)
108 (l)
100 (d)
結果を見ると、「h」がASCIIコード104、「e」が101といった具合に、各文字が対応する数値とペアになって出力されているのがわかる。スペース「 」も32という数値で表現されている。このように、ASCII文字列の場合、1つの文字が1つの数値(バイト)に対応するため、ascii.encodeで変換された整数のリストは、元の文字列の文字数と同じ要素数となり、比較的直感的に理解しやすい。
しかし、現代のソフトウェア開発では、英語圏の文字だけでなく、日本語、中国語、アラビア語、絵文字など、世界中のあらゆる文字を扱う必要がある。そこで登場するのがUTF-8というエンコーディング方式だ。UTF-8は、これらの多種多様な文字を効率的に表現できるように設計されており、Unicodeという巨大な文字セットの一部を構成している。ASCIIとは異なり、UTF-8では1つの文字が1バイトで表現されることもあれば、2バイト、3バイト、あるいはそれ以上のバイト数で表現されることもある。特に日本語のような漢字を含む文字は、ほとんどの場合2バイト以上で表現される。
DartでUTF-8文字列を扱う際も、dart:convertモジュールに含まれるutf8.encodeメソッドが利用できる。このメソッドもascii.encodeと同様に、与えられた文字列を整数のリスト(List<int>)に変換する。
以下のコード例を見てみよう。
1import 'dart:convert'; 2 3void main() { 4 final str = "波動拳: ↓↙←Ⓑ"; 5 for (var c in utf8.encode(str)) { 6 print("${c} (${String.fromCharCode(c)})"); 7 } 8}
このコードは、日本語の漢字や特殊な記号を含む文字列「波動拳: ↓↙←Ⓑ」をutf8.encodeで変換し、その結果をループで出力している。実行結果は次のようになる。
230 (æ)
179 (³)
162 (¢)
229 (å)
139 ()
149 ()
230 (æ)
139 ()
179 (³)
58 (:)
32 ( )
226 (â)
134 ()
147 ()
226 (â)
134 ()
153 ()
226 (â)
134 ()
144 (
226 (â)
146 ()
183 (·)
この出力を見ると、ASCIIの例とは異なり、非常に奇妙な結果になっていることに気づくだろう。元の文字列の「波動拳」という文字が、それぞれ複数の数値(バイト)に分解され、それらの数値がString.fromCharCode(c)によって再構築された際に、全く意味をなさない文字(「æ」「³」「¢」など)として表示されている。これはなぜだろうか。
その理由は、utf8.encodeが文字列をバイト(8ビットの整数)のシーケンスにエンコードするからだ。UTF-8文字は、前述の通り1バイトだけでなく、2バイト、3バイト、あるいはそれ以上で1つの文字を表現することがある。例えば、日本語の「波」という1文字は、UTF-8では通常3バイトで表現される。utf8.encodeは、この3バイトを個別の整数(List<int>の要素)としてリストに格納する。しかし、String.fromCharCode(c)は、与えられた1つの整数を1つの文字コードとして解釈しようとする。そのため、本来3バイトで1文字を構成する「波」のうち、最初の1バイト(例えば230)だけを渡されても、それは「波」という文字にはならず、別の単一バイト文字(例えば「æ」)として解釈されてしまうのだ。つまり、utf8.encodeは正しくUTF-8のバイト列を生成しているのだが、それを1バイトずつString.fromCharCodeで文字に戻そうとすると、文字の境界が失われてしまうために、期待通りの結果が得られないというわけだ。
では、この問題を回避し、UTF-8文字列を文字単位で正しく処理するにはどうすればよいのだろうか。最もシンプルで手軽な解決策の一つとして、DartのString.split("")メソッドを利用する方法がある。このメソッドは、空文字列""を区切り文字として指定することで、元の文字列を1文字ずつに分割し、それぞれの文字を要素とする文字列のリスト(List<String>)を生成する。このリストはIterableであるため、その要素をループで簡単に取り出すことができる。
以下のコード例でこの方法を見てみよう。
1void main() { 2 final str = "波動拳: ↓↙←Ⓑ"; 3 for (var c in str.split("")) { 4 print("${c} : ${utf8.encode(c)}"); 5 } 6}
このコードでは、「波動拳: ↓↙←Ⓑ」という文字列をstr.split("")で分割し、得られた各文字cに対して、その文字自体と、その文字をutf8.encodeでバイト列に変換した結果を出力している。この場合の実行結果は次のようになる。
波 : [230, 179, 162]
動 : [229, 139, 149]
拳 : [230, 139, 179]
: : [58]
: [32]
↓ : [226, 134, 147]
↙ : [226, 134, 153]
← : [226, 134, 144]
Ⓑ : [226, 146, 183]
この結果は非常に明確で理解しやすい。例えば、「波」という文字は、それ自体が正しく認識され、そのUTF-8エンコードされたバイト列が[230, 179, 162]という3つの整数(バイト)のリストとして表示されている。同様に、「動」や「拳」といった文字も3バイトで表現されていることがわかる。一方、「:」や「 」といったASCII範囲の文字は、依然として単一の数値(例えば「:」は58、「 」は32)で表現されている。特殊な矢印や丸付き文字も、それぞれ複数バイトの組み合わせで表現されていることが確認できる。
このように、String.split("")メソッドを使うことで、DartにおいてUTF-8のような可変長バイトエンコーディングの文字を、プログラマーが期待する「文字」単位で正確に処理し、その内部的なバイト表現を安全に確認することができる。この知識は、ファイルI/O、ネットワーク通信、データベースとの連携など、文字エンコーディングが関わるあらゆる場面で非常に重要となる。
システムエンジニアとして、単に文字を扱うだけでなく、その裏側にあるエンコーディングの仕組み、特に多言語対応に必須のUTF-8の特性を理解しておくことは、予期せぬ文字化けやデータ破損を防ぎ、堅牢なシステムを構築するために不可欠なスキルだ。DartにおけるStringとIterable、そしてASCIIやUTF-8といったエンコーディングの扱いは、この基礎を学ぶ良い出発点となるだろう。