Javaアプリケーションで作業している間、私は先にいくつの要素があるのかわからずに別のWebサービスに渡すために値のコンマ区切りのリストを組み立てる必要がありました。私が頭の上から思い付くことができる最高のものはこのようなものでした:
public String appendWithDelimiter( String original, String addition, String delimiter ) {
if ( original.equals( "" ) ) {
return addition;
} else {
return original + delimiter + addition;
}
}
String parameterString = "";
if ( condition ) parameterString = appendWithDelimiter( parameterString, "elementName", "," );
if ( anotherCondition ) parameterString = appendWithDelimiter( parameterString, "anotherElementName", "," );
あちこちで文字列が作成されているので、これは特に効率的ではないことを私は理解していますが、私は最適化よりも明快さのために行きました。
Rubyでは、代わりにこのようなことをすることができます。
parameterArray = [];
parameterArray << "elementName" if condition;
parameterArray << "anotherElementName" if anotherCondition;
parameterString = parameterArray.join(",");
しかし、Javaにはjoinコマンドがないので、これに相当するものは何も見つけられませんでした。
それで、Javaでこれを行うための最良の方法は何ですか?
Apacheのcommons langはここのあなたの友達です - それはあなたがRubyで参照するものと非常によく似たjoinメソッドを提供します:
StringUtils.join(Java.lang.Iterable,char)
Java 8では、StringJoiner
およびString.join()
を使用してすぐに参加できます。以下のスニペットは、それらをどのように使用できるかを示しています。
StringJoiner joiner = new StringJoiner(",");
joiner.add("01").add("02").add("03");
String joinedString = joiner.toString(); // "01,02,03"
String.join(CharSequence delimiter, CharSequence... elements))
String joinedString = String.join(" - ", "04", "05", "06"); // "04 - 05 - 06"
String.join(CharSequence delimiter, Iterable<? extends CharSequence> elements)
List<String> strings = new LinkedList<>();
strings.add("Java");strings.add("is");
strings.add("cool");
String message = String.join(" ", strings);
//message returned is: "Java is cool"
Java.util.Listsで動作する小さな結合スタイルのユーティリティメソッドを書くことができます。
public static String join(List<String> list, String delim) {
StringBuilder sb = new StringBuilder();
String loopDelim = "";
for(String s : list) {
sb.append(loopDelim);
sb.append(s);
loopDelim = delim;
}
return sb.toString();
}
それを次のように使います。
List<String> list = new ArrayList<String>();
if( condition ) list.add("elementName");
if( anotherCondition ) list.add("anotherElementName");
join(list, ",");
Androidの場合、commonsのStringUtilsクラスは利用できないので、このために私は使いました
Android.text.TextUtils.join(CharSequence delimiter, Iterable tokens)
http://developer.Android.com/reference/Android/text/TextUtils.html
GoogleのGuavaライブラリcom.google.common.base.Joinerそのようなタスクを解決するのを助けるクラス。
サンプル:
"My pets are: " + Joiner.on(", ").join(Arrays.asList("rabbit", "parrot", "dog"));
// returns "My pets are: rabbit, parrot, dog"
Joiner.on(" AND ").join(Arrays.asList("field1=1" , "field2=2", "field3=3"));
// returns "field1=1 AND field2=2 AND field3=3"
Joiner.on(",").skipNulls().join(Arrays.asList("London", "Moscow", null, "New York", null, "Paris"));
// returns "London,Moscow,New York,Paris"
Joiner.on(", ").useForNull("Team held a draw").join(Arrays.asList("FC Barcelona", "FC Bayern", null, null, "Chelsea FC", "AC Milan"));
// returns "FC Barcelona, FC Bayern, Team held a draw, Team held a draw, Chelsea FC, AC Milan"
これが Guavaの文字列ユーティリティに関する記事 です。
Java 8では String.join()
を使うことができます。
List<String> list = Arrays.asList("foo", "bar", "baz");
String joined = String.join(" and ", list); // "foo and bar and baz"
Stream APIの例については、 この回答 もご覧ください。
あなたはそれを一般化することができますが、あなたがよく言うように、Javaに参加はありません。
This がうまくいくかもしれません。
public static String join(Iterable<? extends CharSequence> s, String delimiter) {
Iterator<? extends CharSequence> iter = s.iterator();
if (!iter.hasNext()) return "";
StringBuilder buffer = new StringBuilder(iter.next());
while (iter.hasNext()) buffer.append(delimiter).append(iter.next());
return buffer.toString();
}
Java.lang.StringBuilder
に基づくアプローチを使用してください。 (「可変の文字シーケンス」)
あなたが言ったように、それらの文字列連結はすべて文字列を作成しています。 StringBuilder
はそれをしません。
StringBuilder
の代わりにStringBuffer
が使われるのはなぜですか? StringBuilder
javadocから:
可能であれば、このクラスはStringBufferよりも優先して使用することをお勧めします。ほとんどの実装では高速になるからです。
java 8では、次のようにできます。
list.stream().map(Object::toString)
.collect(Collectors.joining(delimiter));
listにnullがある場合は、次のものを使用できます。
list.stream().map(String::valueOf)
.collect(Collectors.joining(delimiter))
私はGoogle Collectionsを使うでしょう。 Nice Join機能があります。
http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?com/google/common/base/Join.html
しかし、私が自分でそれを書きたいのなら、
package util;
import Java.util.ArrayList;
import Java.util.Iterable;
import Java.util.Collections;
import Java.util.Iterator;
public class Utils {
// accept a collection of objects, since all objects have toString()
public static String join(String delimiter, Iterable<? extends Object> objs) {
if (objs.isEmpty()) {
return "";
}
Iterator<? extends Object> iter = objs.iterator();
StringBuilder buffer = new StringBuilder();
buffer.append(iter.next());
while (iter.hasNext()) {
buffer.append(delimiter).append(iter.next());
}
return buffer.toString();
}
// for convenience
public static String join(String delimiter, Object... objs) {
ArrayList<Object> list = new ArrayList<Object>();
Collections.addAll(list, objs);
return join(delimiter, list);
}
}
オブジェクトコレクションの方がうまくいくと思います。オブジェクトを結合する前に、オブジェクトを文字列に変換する必要はないからです。
Apache commons StringUtilsクラスにはjoinメソッドがあります。
stringCollection.stream().collect(Collectors.joining(", "));
そして、最小限のもの(文字列を結合するためだけに、Apache CommonsとGuavaをプロジェクトの依存関係に含めたくない場合)
/**
*
* @param delim : String that should be kept in between the parts
* @param parts : parts that needs to be joined
* @return a String that's formed by joining the parts
*/
private static final String join(String delim, String... parts) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < parts.length - 1; i++) {
builder.append(parts[i]).append(delim);
}
if(parts.length > 0){
builder.append(parts[parts.length - 1]);
}
return builder.toString();
}
StringBuilderとクラスSeparator
を使う
StringBuilder buf = new StringBuilder();
Separator sep = new Separator(", ");
for (String each : list) {
buf.append(sep).append(each);
}
区切り文字は区切り文字を折り返します。最初の呼び出しで空の文字列を返す場合を除き、区切り文字はSeparatorのtoString
メソッドによって返されます。
クラスSeparator
のソースコード
public class Separator {
private boolean skipFirst;
private final String value;
public Separator() {
this(", ");
}
public Separator(String value) {
this.value = value;
this.skipFirst = true;
}
public void reset() {
skipFirst = true;
}
public String toString() {
String sep = skipFirst ? "" : value;
skipFirst = false;
return sep;
}
}
これには、Javaの StringBuilder
タイプを使用できます。 StringBuffer
もありますが、これは不要な余分なスレッドセーフティロジックを含みます。
Eclipse Collections を使用している場合は、makeString()
またはappendString()
を使用できます。
makeString()
は、toString()
と同様にString
表現を返します。
3つの形式があります
makeString(start, separator, end)
makeString(separator)
のデフォルトは空文字列で始まり、終わりますmakeString()
はデフォルトでセパレータを", "
にします(コンマとスペース)コード例:
MutableList<Integer> list = FastList.newListWith(1, 2, 3);
assertEquals("[1/2/3]", list.makeString("[", "/", "]"));
assertEquals("1/2/3", list.makeString("/"));
assertEquals("1, 2, 3", list.makeString());
assertEquals(list.toString(), list.makeString("[", ", ", "]"));
appendString()
はmakeString()
と似ていますが、(Appendable
のように)StringBuilder
に追加され、void
です。追加の最初の引数であるAppendableを使って、同じ3つの形式を取ります。
MutableList<Integer> list = FastList.newListWith(1, 2, 3);
Appendable appendable = new StringBuilder();
list.appendString(appendable, "[", "/", "]");
assertEquals("[1/2/3]", appendable.toString());
自分のコレクションをEclipseのCollections型に変換できない場合は、適切なアダプタでそれを調整してください。
List<Object> list = ...;
ListAdapter.adapt(list).makeString(",");
注:私はEclipseコレクションのコミッターです。
もしあなたがSpring MVCを使っているなら、あなたは次のステップを試すことができます。
import org.springframework.util.StringUtils;
List<String> groupIds = new List<String>;
groupIds.add("a");
groupIds.add("b");
groupIds.add("c");
String csv = StringUtils.arrayToCommaDelimitedString(groupIds.toArray());
それはa,b,c
になるでしょう
独自のjoin()メソッドを書いてみませんか?それはパラメータとしてStringsのコレクションと区切り文字列Stringを取ります。メソッド内でコレクションを反復処理し、StringBufferに結果を構築します。
Java 5の可変引数では、すべての文字列を明示的にコレクションまたは配列に詰める必要はありません。
import junit.framework.Assert;
import org.junit.Test;
public class StringUtil
{
public static String join(String delim, String... strings)
{
StringBuilder builder = new StringBuilder();
if (strings != null)
{
for (String str : strings)
{
if (builder.length() > 0)
{
builder.append(delim).append(" ");
}
builder.append(str);
}
}
return builder.toString();
}
@Test
public void joinTest()
{
Assert.assertEquals("", StringUtil.join(",", null));
Assert.assertEquals("", StringUtil.join(",", ""));
Assert.assertEquals("", StringUtil.join(",", new String[0]));
Assert.assertEquals("test", StringUtil.join(",", "test"));
Assert.assertEquals("foo, bar", StringUtil.join(",", "foo", "bar"));
Assert.assertEquals("foo, bar, x", StringUtil.join(",", "foo", "bar", "x"));
}
}
Java 8ネイティブ型
List<Integer> example;
example.add(1);
example.add(2);
example.add(3);
...
example.stream().collect(Collectors.joining(","));
Java 8カスタムオブジェクト
List<Person> person;
...
person.stream().map(Person::getAge).collect(Collectors.joining(","));
Springのコンテキストにいる人には、 StringUtils クラスも便利です。
以下のような便利なショートカットがたくさんあります。
そして他の多くの。
これは、Java 8をまだ使用しておらず、すでにSpringのコンテキストにいる場合に役立ちます。
私はこれをより簡単にするCollectionサポートのためにApache Commonsに対して(非常に良いことではありますが)それを好みます:
// Encoding Set<String> to String delimited
String asString = org.springframework.util.StringUtils.collectionToDelimitedString(codes, ";");
// Decoding String delimited to Set
Set<String> collection = org.springframework.util.StringUtils.commaDelimitedListToSet(asString);
おそらくStringBuilder
をappend
メソッドと一緒に使用して結果を構築する必要がありますが、それ以外の場合はJavaが提供するのと同じくらい良い解決策です。
Rubyでやっているのと同じこと、つまりすべての要素を配列に追加した後で初めて区切り文字で区切られた文字列を作成することをJavaでやらないでください。
ArrayList<String> parms = new ArrayList<String>();
if (someCondition) parms.add("someString");
if (anotherCondition) parms.add("someOtherString");
// ...
String sep = ""; StringBuffer b = new StringBuffer();
for (String p: parms) {
b.append(sep);
b.append(p);
sep = "yourDelimiter";
}
あなたは別のヘルパーメソッドでそのforループを動かしたいと思うかもしれません、そしてまたStringBufferの代わりにStringBuilderを使います...
編集:追加の順番を修正しました。
だから基本的にこのようなもの:
public static String appendWithDelimiter(String original, String addition, String delimiter) {
if (original.equals("")) {
return addition;
} else {
StringBuilder sb = new StringBuilder(original.length() + addition.length() + delimiter.length());
sb.append(original);
sb.append(delimiter);
sb.append(addition);
return sb.toString();
}
}
これが本当に優れているかどうかはわかりませんが、少なくともStringBuilderを使用しているため、わずかに効率的です。
パラメータの区切りを行う前にパラメータのリストを作成できる場合は、以下のほうがより一般的な方法です。
// Answers real question
public String appendWithDelimiters(String delimiter, String original, String addition) {
StringBuilder sb = new StringBuilder(original);
if(sb.length()!=0) {
sb.append(delimiter).append(addition);
} else {
sb.append(addition);
}
return sb.toString();
}
// A more generic case.
// ... means a list of indeterminate length of Strings.
public String appendWithDelimitersGeneric(String delimiter, String... strings) {
StringBuilder sb = new StringBuilder();
for (String string : strings) {
if(sb.length()!=0) {
sb.append(delimiter).append(string);
} else {
sb.append(string);
}
}
return sb.toString();
}
public void testAppendWithDelimiters() {
String string = appendWithDelimitersGeneric(",", "string1", "string2", "string3");
}
あなたのアプローチはそれほど悪くはありませんが、+記号を使う代わりにStringBufferを使うべきです。 +には、各単一操作に対して新しいStringインスタンスが作成されるという大きな欠点があります。文字列が長くなるほど、オーバーヘッドは大きくなります。そのため、StringBufferを使用するのが最も早い方法です。
public StringBuffer appendWithDelimiter( StringBuffer original, String addition, String delimiter ) {
if ( original == null ) {
StringBuffer buffer = new StringBuffer();
buffer.append(addition);
return buffer;
} else {
buffer.append(delimiter);
buffer.append(addition);
return original;
}
}
文字列の作成が終わったら、返されたStringBufferに対してtoString()を呼び出すだけです。
//Note: if you have access to Java5+,
//use StringBuilder in preference to StringBuffer.
//All that has to be replaced is the class name.
//StringBuffer will work in Java 1.4, though.
appendWithDelimiter( StringBuffer buffer, String addition,
String delimiter ) {
if ( buffer.length() == 0) {
buffer.append(addition);
} else {
buffer.append(delimiter);
buffer.append(addition);
}
}
StringBuffer parameterBuffer = new StringBuffer();
if ( condition ) {
appendWithDelimiter(parameterBuffer, "elementName", "," );
}
if ( anotherCondition ) {
appendWithDelimiter(parameterBuffer, "anotherElementName", "," );
}
//Finally, to return a string representation, call toString() when returning.
return parameterBuffer.toString();
ドル を使うのは簡単です:
String joined = $(aCollection).join(",");
注意:これはArrayや他のデータ型でも動作します
内部的にそれは非常にきちんとしたトリックを使います:
@Override
public String join(String separator) {
Separator sep = new Separator(separator);
StringBuilder sb = new StringBuilder();
for (T item : iterable) {
sb.append(sep).append(item);
}
return sb.toString();
}
クラスSeparator
は、最初に呼び出されたときにだけ空の文字列を返し、次にセパレータを返します。
class Separator {
private final String separator;
private boolean wasCalled;
public Separator(String separator) {
this.separator = separator;
this.wasCalled = false;
}
@Override
public String toString() {
if (!wasCalled) {
wasCalled = true;
return "";
} else {
return separator;
}
}
}
回答を修正するRob Dickerson。
使い方は簡単です。
public static String join(String delimiter, String... values)
{
StringBuilder stringBuilder = new StringBuilder();
for (String value : values)
{
stringBuilder.append(value);
stringBuilder.append(delimiter);
}
String result = stringBuilder.toString();
return result.isEmpty() ? result : result.substring(0, result.length() - 1);
}
文字列の連結を使用する代わりに、コードがスレッド化されていない場合はStringBuilderを、そうでない場合はStringBufferを使用してください。
それで、あなたが探しているように見えるという感覚を得るためにあなたがするかもしれない2、3の事柄:
1)リストクラスを拡張 - そしてそれにjoinメソッドを追加します。 joinメソッドは単にデリミタを連結して追加する作業をするだけです(これはjoinメソッドのパラメータかもしれません)。
2)Java 7はJavaに拡張メソッドを追加しようとしているように見えます - それはあなたがクラスに特定のメソッドをアタッチすることを可能にします:それであなたはそのjoinメソッドを書きそしてListに拡張メソッドとして追加できますコレクション。
Java 7はまだ出ていないので、今のところ、解決策1はおそらく唯一の現実的な解決策です:)しかし、それはうまく機能するはずです。
これらの両方を使用するには、通常どおりすべてのアイテムをListまたはCollectionに追加してから、新しいカスタムメソッドを呼び出してそれらを「結合」します。
あなたはこれを必要以上にもう少し複雑にしています。あなたの例の終わりから始めましょう:
String parameterString = "";
if ( condition ) parameterString = appendWithDelimiter( parameterString, "elementName", "," );
if ( anotherCondition ) parameterString = appendWithDelimiter( parameterString, "anotherElementName", "," );
StringではなくStringBuilderを使用するという小さな変更で、これは次のようになります。
StringBuilder parameterString = new StringBuilder();
if (condition) parameterString.append("elementName").append(",");
if (anotherCondition) parameterString.append("anotherElementName").append(",");
...
完了したら(他のいくつかの条件もチェックする必要があると思います)、次のようなコマンドで末尾のコンマを必ず削除してください。
if (parameterString.length() > 0)
parameterString.deleteCharAt(parameterString.length() - 1);
そして最後に、必要な文字列を取得します。
parameterString.toString();
2番目の呼び出しで追加する "、"を、任意の値に設定できる一般的な区切り文字列に置き換えることもできます。もしあなたがあなたが追加する必要があると知っているもののリストがあれば(条件なしで)、あなたは文字列のリストをとるメソッドの中にこのコードを入れることができます。
Izbからのバージョンのわずかな改善[速度]:
public static String join(String[] strings, char del)
{
StringBuilder sb = new StringBuilder();
int len = strings.length;
if(len > 1)
{
len -= 1;
}else
{
return strings[0];
}
for (int i = 0; i < len; i++)
{
sb.append(strings[i]).append(del);
}
sb.append(strings[i]);
return sb.toString();
}
私は個人的には、ロギングの目的で次のような簡単な解決策を頻繁に使用します。
List lst = Arrays.asList("ab", "bc", "cd");
String str = lst.toString().replaceAll("[\\[\\]]", "");
あなたはこのようなことを試すことができます:
StringBuilder sb = new StringBuilder();
if (condition) { sb.append("elementName").append(","); }
if (anotherCondition) { sb.append("anotherElementName").append(","); }
String parameterString = sb.toString();