この記事では、JavaのStringクラスの基本仕様からsubstringメソッドによる文字列切り出し技術まで包括的に学べます。substring()の基本的な使い方(範囲指定・先頭から・後ろから・インデックス指定)、実務で役立つファイル拡張子抽出やCSV分解などの応用例、さらに例外処理や代替手段としてのStringUtilsクラスも解説。文字列操作で悩むJava初心者から実践的なスキルを求める開発者まで、効率的な文字列処理技術を習得できます。
目次
Java substringメソッドの基礎知識
substringメソッドとは何か
Javaのsubstringメソッドとは、文字列から指定した部分文字列を切り出すためのメソッドです。Stringクラスに標準で組み込まれており、元の文字列を変更することなく、必要な範囲だけを抽出した新しい文字列を生成できます。
substringメソッドは日常的なJava開発において非常に頻繁に使用される基本的なメソッドの一つです。例えば、ファイルパスから拡張子を取得したり、日付文字列から年月日を個別に抽出したり、CSVデータを分解処理する際などに活用されます。このメソッドを適切に理解し使いこなすことで、文字列処理の効率性と可読性を大幅に向上させることができます。
substringメソッドには主に2つの形式があり、開始位置のみを指定するパターンと、開始位置と終了位置を両方指定するパターンが用意されています。どちらの形式も文字列処理において重要な役割を果たし、適切な場面で使い分けることが求められます。
文字列インデックスの仕組みと理解
Java substringメソッドを正しく使用するためには、文字列インデックスの概念を正確に理解することが不可欠です。Javaにおける文字列のインデックスは0から開始され、文字列の長さより1小さい値が最後の文字のインデックスとなります。
具体的な例として「Hello」という文字列を考えてみましょう。この場合のインデックス構造は以下のようになります:
文字 | H | e | l | l | o |
---|---|---|---|---|---|
インデックス | 0 | 1 | 2 | 3 | 4 |
substringメソッドにおいて特に重要な点は、終了位置のインデックスは含まれないという仕様です。つまり、substring(1, 4)を実行した場合、インデックス1から3までの文字が抽出され、インデックス4の文字は含まれません。この仕様は初心者が混乱しやすいポイントですが、一度理解すれば直感的に使用できるようになります。
また、日本語などのマルチバイト文字を扱う場合も、Javaでは1文字を1つのインデックスとして扱うため、英数字と同様の感覚でsubstringメソッドを使用することができます。
substringメソッドの記述方法と構文
Java substringメソッドの構文は、使用する形式によって2つのパターンに分かれています。それぞれの構文を正確に理解し、適切な場面で使い分けることが重要です。
1つ目の構文は開始位置のみを指定する形式です:
public String substring(int beginIndex)
この形式では、指定したbeginIndexから文字列の末尾までを切り出します。実際の使用例は以下のようになります:
String str = "Hello World";
String result = str.substring(6);
// 結果: "World"
2つ目の構文は開始位置と終了位置を両方指定する形式です:
public String substring(int beginIndex, int endIndex)
この形式では、beginIndexから(endIndex – 1)までの範囲を切り出します。使用例は以下の通りです:
String str = "Hello World";
String result = str.substring(0, 5);
// 結果: "Hello"
両方の構文において、インデックスの値が文字列の範囲を超えた場合やbeginIndexがendIndexより大きい場合は、StringIndexOutOfBoundsException例外が発生するため、適切な範囲チェックが必要です。これらの構文を使い分けることで、様々な文字列切り出し処理を効率的に実装することができます。
substringメソッドを使った文字列の切り出し方法
Javaのsubstringメソッドは、既存の文字列から必要な部分だけを抽出する際の基本的なツールです。このメソッドには複数のオーバーロードされたバージョンがあり、用途に応じて使い分けることで効率的な文字列操作が可能になります。以下では、substringメソッドの具体的な使用方法を実例とともに詳しく解説します。
開始位置と終了位置を指定した範囲切り出し
substringメソッドの最も基本的な形式は、開始インデックスと終了インデックスの両方を指定する方法です。この場合、substring(int beginIndex, int endIndex)の形式で使用し、開始位置から終了位置の直前までの文字列を取得します。
String originalString = "Hello World Java";
String result = originalString.substring(6, 11);
// 結果: "World"
重要なポイントは、終了インデックスは含まれないということです。上記の例では、インデックス6から10までの文字が抽出されます。この仕様により、文字列の長さを計算する際に混乱を避けることができます。
- 開始インデックスは含まれる(inclusive)
- 終了インデックスは含まれない(exclusive)
- インデックスは0から開始
- 範囲指定により正確な文字列の一部を取得可能
開始位置から文字列末尾まで切り出し
文字列の途中から末尾まですべてを取得したい場合は、substring(int beginIndex)の形式を使用します。この方法は、特定の位置以降のすべての文字列が必要な場合に便利です。
String originalString = "Hello World Java";
String result = originalString.substring(12);
// 結果: "Java"
この形式のsubstringメソッドは、ファイルパスから拡張子を取得したり、URLから特定部分を抽出したりする際に頻繁に使用されます。また、文字列の前半部分を除去する処理にも適用できます。
使用例 | コード | 結果 |
---|---|---|
ファイル名取得 | path.substring(path.lastIndexOf(“/”) + 1) | ファイル名部分 |
拡張子取得 | filename.substring(filename.lastIndexOf(“.”) + 1) | 拡張子部分 |
文字列の後方から指定文字数を切り出し
文字列の末尾から特定の文字数を取得する場合、文字列の長さを利用して計算を行います。JavaのsubstringメソッドではPythonのような負のインデックスはサポートされていないため、length()メソッドと組み合わせて使用する必要があります。
String originalString = "Hello World Java";
int stringLength = originalString.length();
String result = originalString.substring(stringLength - 4);
// 結果: "Java"
より具体的な例として、後ろから特定の範囲を切り出す場合は以下のようになります:
String originalString = "Hello World Java";
int stringLength = originalString.length();
// 後ろから4文字目から2文字目まで取得
String result = originalString.substring(stringLength - 4, stringLength - 2);
// 結果: "Ja"
この手法は、固定長のデータフォーマットや、末尾に重要な情報が格納されている文字列の処理において特に有用です。
単一文字の抽出方法
文字列から単一の文字を取得する場合、substringメソッドを使用する方法と、より効率的なcharAt()メソッドを使用する方法があります。substringメソッドで単一文字を取得する場合は、開始インデックスと終了インデックスの差を1にすることで実現できます。
String originalString = "Hello World Java";
// substringを使用した単一文字抽出
String singleChar = originalString.substring(6, 7);
// 結果: "W"
// charAt()メソッドを使用した場合(比較用)
char singleCharValue = originalString.charAt(6);
// 結果: 'W'
ただし、単一文字の取得が目的の場合は、charAt()メソッドの使用が推奨されます。これは、charAt()メソッドの方がメモリ効率が良く、処理速度も高速だからです。substringメソッドは新しいStringオブジェクトを生成しますが、charAt()メソッドはプリミティブなchar型を返すため、オーバーヘッドが少なくなります。
- substringで単一文字:新しいStringオブジェクトを生成
- charAt()で単一文字:プリミティブなchar型を返却
- 単一文字取得の場合はcharAt()が効率的
- 文字列として扱いたい場合はsubstringが適切
substringメソッドの実践的な活用例
substringメソッドは、実際の開発現場において文字列処理の様々な場面で活用されています。ここでは、日常的な開発で頻繁に遭遇する具体的なケースを通じて、substringメソッドの実践的な使い方を詳しく解説します。これらの活用例を理解することで、効率的で読みやすいコードが書けるようになるでしょう。
ファイル名と拡張子の分離処理
ファイル処理においてファイル名と拡張子を分離することは頻繁に発生する処理です。substringメソッドを使用することで、効率的にこの分離処理を実装できます。
public class FileProcessor {
public static String getFileName(String filePath) {
// 最後のドット位置を取得
int lastDotIndex = filePath.lastIndexOf(".");
if (lastDotIndex == -1) {
return filePath; // 拡張子がない場合
}
return filePath.substring(0, lastDotIndex);
}
public static String getFileExtension(String filePath) {
int lastDotIndex = filePath.lastIndexOf(".");
if (lastDotIndex == -1 || lastDotIndex == filePath.length() - 1) {
return ""; // 拡張子がない場合
}
return filePath.substring(lastDotIndex + 1);
}
public static void main(String[] args) {
String filePath = "document.pdf";
String fileName = getFileName(filePath); // "document"
String extension = getFileExtension(filePath); // "pdf"
System.out.println("ファイル名: " + fileName);
System.out.println("拡張子: " + extension);
}
}
この実装では、lastIndexOf()メソッドで最後のドットの位置を特定し、substringメソッドでファイル名と拡張子を適切に分離しています。null値や空文字列のチェックも含めることで、より堅牢なコードになります。
日付・時刻文字列の分解と処理
日付や時刻を表す文字列から、年、月、日、時、分、秒などの各要素を抽出する場面では、substringメソッドが効果的に活用できます。特に固定フォーマットの文字列処理において威力を発揮します。
public class DateTimeParser {
// "2024-03-15 14:30:25" 形式の日時文字列を分解
public static void parseDateTime(String dateTimeString) {
if (dateTimeString.length() != 19) {
throw new IllegalArgumentException("日時フォーマットが正しくありません");
}
String year = dateTimeString.substring(0, 4);
String month = dateTimeString.substring(5, 7);
String day = dateTimeString.substring(8, 10);
String hour = dateTimeString.substring(11, 13);
String minute = dateTimeString.substring(14, 16);
String second = dateTimeString.substring(17, 19);
System.out.println("年: " + year);
System.out.println("月: " + month);
System.out.println("日: " + day);
System.out.println("時: " + hour);
System.out.println("分: " + minute);
System.out.println("秒: " + second);
}
// "20240315" 形式の日付文字列を"2024-03-15"に変換
public static String formatDate(String compactDate) {
if (compactDate.length() != 8) {
throw new IllegalArgumentException("日付フォーマットが正しくありません");
}
return compactDate.substring(0, 4) + "-" +
compactDate.substring(4, 6) + "-" +
compactDate.substring(6, 8);
}
}
固定フォーマットの日時文字列処理では、substringメソッドの固定位置での切り出し機能が非常に有効です。ただし、文字列長の事前チェックを必ず行い、IndexOutOfBoundsExceptionを防ぐことが重要です。
CSV形式データの分解処理
CSV形式のデータ処理において、substringメソッドとindexOfメソッドを組み合わせることで、効率的なデータ分解処理を実装できます。特に簡単なCSV処理では、専用ライブラリを使わずに済む場合があります。
public class CsvProcessor {
public static String[] parseSimpleCsv(String csvLine) {
if (csvLine == null || csvLine.isEmpty()) {
return new String[0];
}
List fields = new ArrayList>();
int startIndex = 0;
int commaIndex;
while ((commaIndex = csvLine.indexOf(',', startIndex)) != -1) {
String field = csvLine.substring(startIndex, commaIndex).trim();
fields.add(field);
startIndex = commaIndex + 1;
}
// 最後のフィールドを追加
String lastField = csvLine.substring(startIndex).trim();
fields.add(lastField);
return fields.toArray(new String[0]);
}
// 特定の列を抽出する処理
public static String extractColumn(String csvLine, int columnIndex) {
String[] columns = parseSimpleCsv(csvLine);
if (columnIndex 0 || columnIndex >= columns.length) {
return "";
}
return columns[columnIndex];
}
public static void main(String[] args) {
String csvData = "田中,太郎,30,東京都";
String[] fields = parseSimpleCsv(csvData);
for (int i = 0; i fields.length; i++) {
System.out.println("フィールド" + i + ": " + fields[i]);
}
}
}
この実装では、indexOfメソッドでカンマの位置を特定し、substringメソッドで各フィールドを抽出しています。trimメソッドと組み合わせることで、余分な空白も同時に除去できます。
文字列の前後空白除去処理
substringメソッドを使用して、文字列の前後にある特定の文字や空白を除去するカスタム処理を実装できます。標準のtrimメソッドでは対応できない特殊なケースに対応可能です。
public class StringTrimmer {
// 先頭と末尾の特定文字を除去
public static String trimSpecificChar(String str, char charToTrim) {
if (str == null || str.isEmpty()) {
return str;
}
int startIndex = 0;
int endIndex = str.length();
// 先頭の特定文字をスキップ
while (startIndex endIndex && str.charAt(startIndex) == charToTrim) {
startIndex++;
}
// 末尾の特定文字をスキップ
while (endIndex > startIndex && str.charAt(endIndex - 1) == charToTrim) {
endIndex--;
}
return str.substring(startIndex, endIndex);
}
// 先頭の指定文字数を除去
public static String trimLeft(String str, int count) {
if (str == null || count = 0 || count >= str.length()) {
return str == null ? null : (count >= str.length() ? "" : str);
}
return str.substring(count);
}
// 末尾の指定文字数を除去
public static String trimRight(String str, int count) {
if (str == null || count = 0) {
return str;
}
if (count >= str.length()) {
return "";
}
return str.substring(0, str.length() - count);
}
public static void main(String[] args) {
String text = "***重要なメッセージ***";
String trimmed = trimSpecificChar(text, '*');
System.out.println("結果: " + trimmed); // "重要なメッセージ"
String leftTrimmed = trimLeft(" テスト文字列", 2);
System.out.println("左側除去: " + leftTrimmed); // "テスト文字列"
}
}
このような独自の文字列処理関数を作成することで、プロジェクト固有の要件に合わせた柔軟な文字列処理が可能になります。特に、標準のtrimメソッドでは対応できない特殊文字の除去や、固定文字数の切り詰め処理において威力を発揮します。
substringメソッド使用時の注意点とエラー対策
Javaのsubstringメソッドは文字列操作において非常に有用な機能ですが、使用時にはいくつかの重要な注意点があります。適切な理解なしに使用すると、予期せぬエラーが発生したり、プログラムの動作が不安定になる可能性があります。ここでは、substringメソッドを安全かつ効果的に使用するための重要なポイントについて詳しく解説します。
インデックス指定時の注意事項
substringメソッドでインデックスを指定する際は、0ベースのインデックスであることを常に意識する必要があります。文字列「Hello」の場合、’H’は0、’e’は1、’l’は2というように、最初の文字が0から始まることを理解しておきましょう。
また、終了位置のインデックスは切り出し範囲に含まれないという重要な特徴があります。以下のような指定方法に注意してください:
- substring(1, 4)の場合:インデックス1から3までの文字が取得される
- 終了インデックスで指定した位置の文字は結果に含まれない
- 開始インデックスと終了インデックスが同じ場合は空文字列が返される
String text = "Programming";
String result = text.substring(3, 7); // "gram"が取得される
さらに、負の値をインデックスとして指定することはできません。他のプログラミング言語では負のインデックスで後方からの位置指定が可能な場合もありますが、Javaでは必ず0以上の値を使用する必要があります。
例外発生のパターンと対処法
substringメソッドの使用時に最も頻繁に発生するのがStringIndexOutOfBoundsExceptionです。この例外が発生する主なパターンと、それぞれに対する適切な対処法を理解しておくことが重要です。
例外が発生する代表的なケースは以下の通りです:
- 開始インデックスが負の値:負の数値を指定した場合
- 開始インデックスが文字列長を超過:存在しない位置を指定した場合
- 終了インデックスが文字列長を超過:範囲外の終了位置を指定した場合
- 開始インデックスが終了インデックスより大きい:論理的に矛盾する範囲指定
これらの例外を防ぐための対処法として、以下のような事前チェックを実装することが推奨されます:
public String safeSubstring(String str, int start, int end) {
if (str == null || str.isEmpty()) {
return "";
}
// インデックスの範囲チェック
if (start 0) start = 0;
if (end > str.length()) end = str.length();
if (start >= end) return "";
return str.substring(start, end);
}
また、try-catch文を使用してランタイムでの例外処理を行う方法も有効です。特に外部からの入力データを処理する場合には、予期せぬ値に対する防御的なプログラミングが重要になります。
文字列長との混同を避ける方法
substringメソッドを使用する際によくある間違いが、文字列長とインデックスの混同です。文字列の長さは1から始まりますが、インデックスは0から始まるため、この違いを正確に理解することが重要です。
混同を避けるための具体的な方法として、以下のアプローチが効果的です:
- length()メソッドとの関係性を理解:文字列長nの場合、有効なインデックスは0からn-1まで
- 境界値での動作確認:空文字列や1文字の文字列での動作テスト
- 可視化による確認:文字列とインデックスの対応関係を図示して確認
文字 | H | e | l | l | o |
---|---|---|---|---|---|
インデックス | 0 | 1 | 2 | 3 | 4 |
上記の表のように、文字列「Hello」の長さは5ですが、最後の文字’o’のインデックスは4であることを視覚的に確認できます。このような対応関係を常に意識することで、インデックス指定時のミスを大幅に減らすことができます。
さらに、動的に文字列を処理する場合には、length()メソッドを活用した相対的なインデックス指定を行うことで、より安全で保守性の高いコードを作成できます。例えば、最後の3文字を取得したい場合は「substring(str.length() – 3)」のように記述することで、文字列長に依存しない柔軟な処理が可能になります。
substringの代替手法と最適な選択
Javaの文字列処理において、substringメソッドは基本的な切り出し処理に適していますが、場面によってはより効率的で機能豊富な代替手法が存在します。開発の生産性やコードの保守性を向上させるためには、処理の目的に応じて最適な手法を選択することが重要です。
Apache Commons StringUtilsの活用
Apache Commons Langライブラリに含まれるStringUtilsクラスは、substringメソッドの制限を補完する豊富なメソッドを提供します。特にnull安全性とエラー処理の堅牢性において優れた特徴を持っています。
import org.apache.commons.lang3.StringUtils;
// null安全なsubstring処理
String text = null;
String result1 = StringUtils.substring(text, 0, 5); // nullが返される(例外なし)
// 範囲外インデックスでも安全
String text2 = "Hello";
String result2 = StringUtils.substring(text2, 0, 100); // "Hello"が返される
// 文字列の前後から指定文字数を除去
String text3 = "abcdefgh";
String result3 = StringUtils.mid(text3, 2, 4); // "cdef"
// 左右から指定文字数を取得
String leftPart = StringUtils.left(text3, 3); // "abc"
String rightPart = StringUtils.right(text3, 3); // "fgh"
StringUtilsを使用することで、従来のsubstringメソッドで必要だった事前の長さチェックやnull判定処理を省略でき、コードの可読性と安全性が向上します。
splitメソッドとの使い分け
文字列を特定の区切り文字で分割する処理では、substringよりもsplitメソッドの方が適している場合が多くあります。処理の目的と文字列の構造を考慮した適切な選択が重要です。
処理内容 | substringが適している場合 | splitが適している場合 |
---|---|---|
固定位置での切り出し | 日付文字列(YYYYMMDD)の年月日分離 | – |
区切り文字による分割 | – | CSVデータ、パス文字列の分割 |
複数要素の一括取得 | – | 設定ファイルの解析 |
// substringが適している例:固定フォーマットの処理
String dateString = "20240315";
String year = dateString.substring(0, 4); // "2024"
String month = dateString.substring(4, 6); // "03"
String day = dateString.substring(6, 8); // "15"
// splitが適している例:区切り文字による分割
String csvLine = "田中,太郎,30,東京都";
String[] parts = csvLine.split(",");
String lastName = parts[0]; // "田中"
String firstName = parts[1]; // "太郎"
String age = parts[2]; // "30"
String address = parts[3]; // "東京都"
splitメソッドは区切り文字の位置を動的に検索するため、データの構造が可変的な場合に威力を発揮します。一方、substringは固定位置での切り出しに特化しており、フォーマットが決まっている場合により高速に動作します。
substringを使わない方が良いケース
substringメソッドは汎用性が高い反面、特定の状況では他の手法を選択した方が効率的で安全なコードを作成できます。適切な判断基準を理解することが重要です。
- 大量データの連続処理:substringは新しい文字列オブジェクトを生成するため、メモリ使用量が増加します
- 正規表現パターンマッチング:複雑な条件での文字列抽出には正規表現の方が適しています
- 文字列の検索と置換:特定パターンの置換処理では専用メソッドを使用すべきです
- 国際化対応が必要な処理:マルチバイト文字や絵文字を含む文字列では注意が必要です
// 推奨されないケース:正規表現で処理すべき内容
String email = "user@example.com";
// substringを使った非効率な処理
int atIndex = email.indexOf("@");
String username = email.substring(0, atIndex);
// より適切な処理方法
Pattern pattern = Pattern.compile("^([^@]+)@(.+)$");
Matcher matcher = pattern.matcher(email);
if (matcher.find()) {
String username = matcher.group(1);
String domain = matcher.group(2);
}
大量データを扱う場合のsubstring使用は特に注意が必要です。文字列の生成コストやガベージコレクションの負荷を考慮し、StringBuilderやStreamAPIなどの代替手法を検討することが推奨されます。
適切な手法選択により、処理性能の向上とコードの保守性向上を両立できます。substringメソッドの特性を理解した上で、処理要件に最も適した方法を選択することが、品質の高いJavaアプリケーション開発につながります。
substring使用における性能とベストプラクティス
Javaの文字列処理において、substringメソッドの適切な使用は、アプリケーションの性能に大きな影響を与える重要な要素です。特に大量のデータを扱う場合や、頻繁に文字列操作を行う処理では、メモリ使用量や処理速度の観点から最適化を意識した実装が不可欠となります。ここでは、実際の開発現場で役立つ性能改善のテクニックとベストプラクティスについて詳しく解説します。
メモリ効率を考慮した実装方法
Javaのsubstringメソッドを使用する際は、メモリリークを防ぐための適切な実装パターンを理解することが重要です。特に、大きな文字列から小さな部分文字列を切り出す場合には注意が必要となります。
Java 7以降では、substringメソッドの内部実装が改善され、新しい文字配列を作成するようになりました。これにより、元の文字列への参照が保持されることがなくなり、メモリ効率が向上しています。しかし、依然として以下の点に注意する必要があります。
- 不要な文字列参照を早期に解放するため、使用後はnullを代入する
- 大量の文字列処理では、StringBuilderとの組み合わせを検討する
- キャッシュやstaticフィールドに部分文字列を保存する際は、メモリ使用量を監視する
// メモリ効率を意識した実装例
String largeString = readLargeFile(); // 大きなファイルを読み込み
String extractedPart = new String(largeString.substring(0, 100)); // 明示的に新しいStringを作成
largeString = null; // 元の大きな文字列の参照を解放
文字列比較処理の最適化
substringメソッドで抽出した文字列を比較処理で使用する場合、処理順序や比較方法の工夫により大幅な性能向上が期待できます。文字列比較は頻繁に実行される処理の一つであるため、適切な最適化テクニックを適用することが重要です。
最も効果的な最適化手法として、substring結果を直接比較せずに、元の文字列に対してregionMatchesメソッドを使用する方法があります。これにより、新しい文字列オブジェクトの生成を避けながら、同等の比較処理を実現できます。
// 非効率な実装例
String text = "Hello World Example";
if (text.substring(6, 11).equals("World")) {
// 処理
}
// 最適化された実装例
String text = "Hello World Example";
if (text.regionMatches(6, "World", 0, 5)) {
// 処理(substring不要)
}
また、複数の条件で文字列比較を行う場合は、以下の最適化パターンが有効です。
- 長さチェックを最初に実行し、早期終了を図る
- 比較頻度の高い条件を先頭に配置する
- 正規表現よりもstartsWith、endsWithメソッドを優先使用する
大量データ処理時の注意点
大量のデータを処理する際にsubstringメソッドを使用する場合、メモリ使用量とガベージコレクションの影響を最小限に抑える設計が不可欠です。特に、バッチ処理やストリーミング処理においては、適切な処理パターンを選択することで劇的な性能改善を実現できます。
大量データ処理では、substringの頻繁な使用がガベージコレクションを誘発し、アプリケーション全体の性能低下を招く可能性があります。このような状況を避けるため、以下のアプローチを検討することが重要です。
- バッファリング戦略の採用:一定量のデータをメモリに保持し、まとめて処理する
- ストリーム処理の活用:Java 8以降のStreamAPIを使用し、遅延評価による処理最適化を図る
- プール化の実装:頻繁に使用される文字列パターンを事前にプールしておく
// 大量データ処理の最適化例
List<String> processLargeDataSet(List<String> dataList) {
return dataList.stream()
.filter(s -> s.length() > 10) // 長さチェックを先に実行
.map(s -> s.substring(0, 10)) // 必要な場合のみsubstringを実行
.collect(Collectors.toList());
}
処理規模 | 推奨手法 | 注意点 |
---|---|---|
小規模(1万件未満) | 通常のsubstring使用 | 特別な最適化は不要 |
中規模(1万〜10万件) | StringBuilderとの併用 | メモリ使用量の監視 |
大規模(10万件以上) | ストリーム処理 + バッファリング | GC頻度とヒープサイズの調整 |