Мені потрібно об'єднати два String
масиви в Java.
void f(String[] first, String[] second) {
String[] both = ???
}
Який найпростіший спосіб зробити це?
array1 + array2
конкатенацію.
Мені потрібно об'єднати два String
масиви в Java.
void f(String[] first, String[] second) {
String[] both = ???
}
Який найпростіший спосіб зробити це?
array1 + array2
конкатенацію.
Відповіді:
Я знайшов однолінійне рішення зі старої доброї бібліотеки Apache Commons Lang.
ArrayUtils.addAll(T[], T...)
Код:
String[] both = ArrayUtils.addAll(first, second);
Ось простий метод, який об'єднає два масиви і поверне результат:
public <T> T[] concatenate(T[] a, T[] b) {
int aLen = a.length;
int bLen = b.length;
@SuppressWarnings("unchecked")
T[] c = (T[]) Array.newInstance(a.getClass().getComponentType(), aLen + bLen);
System.arraycopy(a, 0, c, 0, aLen);
System.arraycopy(b, 0, c, aLen, bLen);
return c;
}
Зауважте, що він не працюватиме з примітивними типами даних, лише з типами об'єктів.
Наступна трохи складніша версія працює як з об’єктними, так і з примітивними масивами. Це робиться, використовуючи T
замість T[]
типу аргументу.
Це також дозволяє об'єднати масиви двох різних типів, вибираючи найбільш загальний тип як тип компонента результату.
public static <T> T concatenate(T a, T b) {
if (!a.getClass().isArray() || !b.getClass().isArray()) {
throw new IllegalArgumentException();
}
Class<?> resCompType;
Class<?> aCompType = a.getClass().getComponentType();
Class<?> bCompType = b.getClass().getComponentType();
if (aCompType.isAssignableFrom(bCompType)) {
resCompType = aCompType;
} else if (bCompType.isAssignableFrom(aCompType)) {
resCompType = bCompType;
} else {
throw new IllegalArgumentException();
}
int aLen = Array.getLength(a);
int bLen = Array.getLength(b);
@SuppressWarnings("unchecked")
T result = (T) Array.newInstance(resCompType, aLen + bLen);
System.arraycopy(a, 0, result, 0, aLen);
System.arraycopy(b, 0, result, aLen, bLen);
return result;
}
Ось приклад:
Assert.assertArrayEquals(new int[] { 1, 2, 3 }, concatenate(new int[] { 1, 2 }, new int[] { 3 }));
Assert.assertArrayEquals(new Number[] { 1, 2, 3f }, concatenate(new Integer[] { 1, 2 }, new Number[] { 3f }));
Array.newInstance(a.getClass().getComponentType(), aLen + bLen);
. Я дивно ніколи раніше цього не бачив. @beaudet Я вважаю, що анотація тут чудова, враховуючи, чому її придушують.
Можна написати повністю загальну версію, яку можна навіть розширити, щоб об'єднати будь-яку кількість масивів. Ці версії вимагають Java 6, якою вони користуютьсяArrays.copyOf()
Обидві версії уникають створення будь-яких посередницьких List
об'єктів і використовують System.arraycopy()
для того, щоб скопіювати великі масиви якнайшвидше.
Для двох масивів це виглядає приблизно так:
public static <T> T[] concat(T[] first, T[] second) {
T[] result = Arrays.copyOf(first, first.length + second.length);
System.arraycopy(second, 0, result, first.length, second.length);
return result;
}
А для довільної кількості масивів (> = 1) це виглядає приблизно так:
public static <T> T[] concatAll(T[] first, T[]... rest) {
int totalLength = first.length;
for (T[] array : rest) {
totalLength += array.length;
}
T[] result = Arrays.copyOf(first, totalLength);
int offset = first.length;
for (T[] array : rest) {
System.arraycopy(array, 0, result, offset, array.length);
offset += array.length;
}
return result;
}
T
на byte
(і втратите <T>
).
ByteBuffer buffer = ByteBuffer.allocate(array1.length + array2.length); buffer.put(array1); buffer.put(array2); return buffer.array();
concat(ai, ad)
, де ai
є Integer[]
і ad
є Double[]
. (У цьому випадку параметр типу <T>
вирішується <? extends Number>
компілятором.) Створений масив Arrays.copyOf
матиме тип компонента першого масиву, тобто Integer
в цьому прикладі. Коли функція збирається скопіювати другий масив, ArrayStoreException
буде викинуто повідомлення. Рішення - мати додатковий Class<T> type
параметр.
Використання Stream
в Java 8:
String[] both = Stream.concat(Arrays.stream(a), Arrays.stream(b))
.toArray(String[]::new);
Або так, використовуючи flatMap
:
String[] both = Stream.of(a, b).flatMap(Stream::of)
.toArray(String[]::new);
Для цього для загального типу потрібно використовувати рефлексію:
@SuppressWarnings("unchecked")
T[] both = Stream.concat(Arrays.stream(a), Arrays.stream(b)).toArray(
size -> (T[]) Array.newInstance(a.getClass().getComponentType(), size));
.boxed()
такими, що вони мають тип, Stream
а не наприклад, IntStream
який не може бути переданий в якості параметра Stream.concat
.
a
і b
є int[]
, використовуйтеint[] both = IntStream.concat(Arrays.stream(a), Arrays.stream(b)).toArray();
System.arrayCopy
. Але і не особливо повільно. Вам, мабуть, доведеться робити це дуже багато разів з величезними масивами в реально чутливих до ефективності контекстах, щоб різниця у часі виконання не мала значення.
Або з коханою Гуавою :
String[] both = ObjectArrays.concat(first, second, String.class);
Також існують версії для примітивних масивів:
Booleans.concat(first, second)
Bytes.concat(first, second)
Chars.concat(first, second)
Doubles.concat(first, second)
Shorts.concat(first, second)
Ints.concat(first, second)
Longs.concat(first, second)
Floats.concat(first, second)
Ви можете додати два масиви у двох рядках коду.
String[] both = Arrays.copyOf(first, first.length + second.length);
System.arraycopy(second, 0, both, first.length, second.length);
Це швидке та ефективне рішення, яке працюватиме для примітивних типів, а також два задіяні методи перевантажені.
Вам слід уникати рішень із залученням ArrayLists, потоків тощо, оскільки для цього потрібно буде виділити тимчасову пам'ять без корисної мети.
Вам слід уникати for
циклів для великих масивів, оскільки вони неефективні. Вбудовані методи використовують функції блок-копіювання, які надзвичайно швидкі.
Використання Java API:
String[] f(String[] first, String[] second) {
List<String> both = new ArrayList<String>(first.length + second.length);
Collections.addAll(both, first);
Collections.addAll(both, second);
return both.toArray(new String[both.size()]);
}
both.toArray(new String[0])
буде швидше both.toArray(new String[both.size()])
, навіть якщо це суперечить нашій наївній інтуїції. Ось чому так важливо вимірювати фактичну ефективність при оптимізації. Або просто використовувати більш просту конструкцію, коли перевагу більш складного варіанту неможливо довести.
Рішення на 100% старого Java і без System.arraycopy
(недоступне для клієнта GWT):
static String[] concat(String[]... arrays) {
int length = 0;
for (String[] array : arrays) {
length += array.length;
}
String[] result = new String[length];
int pos = 0;
for (String[] array : arrays) {
for (String element : array) {
result[pos] = element;
pos++;
}
}
return result;
}
null
чеки. І, можливо, встановіть для деяких своїх змінних final
.
null
перевірки приховатимуть NPE, а не показувати їх, а використання final для локальних vars не має жодної користі (поки).
Нещодавно я боровся з проблемами із надмірним обертанням пам'яті. Якщо а та / або b, як відомо, зазвичай порожні, ось ще одна адаптація коду Silvertab (також узагальнена):
private static <T> T[] concatOrReturnSame(T[] a, T[] b) {
final int alen = a.length;
final int blen = b.length;
if (alen == 0) {
return b;
}
if (blen == 0) {
return a;
}
final T[] result = (T[]) java.lang.reflect.Array.
newInstance(a.getClass().getComponentType(), alen + blen);
System.arraycopy(a, 0, result, 0, alen);
System.arraycopy(b, 0, result, alen, blen);
return result;
}
Редагувати: Попередня версія цієї публікації зазначала, що повторне використання масиву, подібне до цього, має бути чітко зафіксовано. Як зазначає Маартен у коментарях, взагалі краще було б просто видалити заяви if, тим самим позбавивши необхідності мати документацію. Але знову ж таки, коли б висловлювання були цілком суть цієї конкретної оптимізації. Я залишу цю відповідь тут, але будьте обережні!
System.arraycopy
копіює вміст масиву?
if
вилучення двох тверджень було б найпростішим виправленням.
Функціональна Java бібліотека має клас - оболонки масиву, екіпірування масивів з зручними методами , як конкатенація.
import static fj.data.Array.array;
...і потім
Array<String> both = array(first).append(array(second));
Щоб повернути розгорнутий масив назад, зателефонуйте
String[] s = both.array();
ArrayList<String> both = new ArrayList(Arrays.asList(first));
both.addAll(Arrays.asList(second));
both.toArray(new String[0]);
both.toArray(new String[both.size()])
Ось адаптація рішення Silvertab з оновленими дженериками:
static <T> T[] concat(T[] a, T[] b) {
final int alen = a.length;
final int blen = b.length;
final T[] result = (T[]) java.lang.reflect.Array.
newInstance(a.getClass().getComponentType(), alen + blen);
System.arraycopy(a, 0, result, 0, alen);
System.arraycopy(b, 0, result, alen, blen);
return result;
}
ПРИМІТКА. Див . Відповідь Йоахіма щодо рішення Java 6. Він не тільки усуває попередження; це також коротше, ефективніше і легше читати!
Якщо ви користуєтеся цим способом, тому вам не потрібно імпортувати жоден третій клас.
Якщо ви хочете об'єднатись String
Приклад коду для об'єднання двох String Array
public static String[] combineString(String[] first, String[] second){
int length = first.length + second.length;
String[] result = new String[length];
System.arraycopy(first, 0, result, 0, first.length);
System.arraycopy(second, 0, result, first.length, second.length);
return result;
}
Якщо ви хочете об'єднатись Int
Приклад коду для об'єднання двох масивів Integer
public static int[] combineInt(int[] a, int[] b){
int length = a.length + b.length;
int[] result = new int[length];
System.arraycopy(a, 0, result, 0, a.length);
System.arraycopy(b, 0, result, a.length, b.length);
return result;
}
Ось основний метод
public static void main(String[] args) {
String [] first = {"a", "b", "c"};
String [] second = {"d", "e"};
String [] joined = combineString(first, second);
System.out.println("concatenated String array : " + Arrays.toString(joined));
int[] array1 = {101,102,103,104};
int[] array2 = {105,106,107,108};
int[] concatenateInt = combineInt(array1, array2);
System.out.println("concatenated Int array : " + Arrays.toString(concatenateInt));
}
}
Ми можемо використовувати і цей спосіб.
Вибачте, що я додав ще одну версію до цього вже тривалого списку. Я переглянув кожну відповідь і вирішив, що мені дуже хочеться версії із лише одним параметром у підписі. Я також додав перевірку аргументів, щоб скористатися раннім збоєм з розумною інформацією у разі несподіваного введення.
@SuppressWarnings("unchecked")
public static <T> T[] concat(T[]... inputArrays) {
if(inputArrays.length < 2) {
throw new IllegalArgumentException("inputArrays must contain at least 2 arrays");
}
for(int i = 0; i < inputArrays.length; i++) {
if(inputArrays[i] == null) {
throw new IllegalArgumentException("inputArrays[" + i + "] is null");
}
}
int totalLength = 0;
for(T[] array : inputArrays) {
totalLength += array.length;
}
T[] result = (T[]) Array.newInstance(inputArrays[0].getClass().getComponentType(), totalLength);
int offset = 0;
for(T[] array : inputArrays) {
System.arraycopy(array, 0, result, offset, array.length);
offset += array.length;
}
return result;
}
Ви можете спробувати перетворити його в Arraylist і скористатися методом addAll, а потім перетворити назад у масив.
List list = new ArrayList(Arrays.asList(first));
list.addAll(Arrays.asList(second));
String[] both = list.toArray();
Тут можлива реалізація в робочому коді рішення псевдокоду, написаного Silvertab.
Спасибі Silvertab!
public class Array {
public static <T> T[] concat(T[] a, T[] b, ArrayBuilderI<T> builder) {
T[] c = builder.build(a.length + b.length);
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}
}
Далі йде інтерфейс для будівельника.
Примітка: будівельник необхідний, тому що в Java це зробити неможливо
new T[size]
через стирання загального типу:
public interface ArrayBuilderI<T> {
public T[] build(int size);
}
Ось конкретний конструктор, що реалізує інтерфейс, будує Integer
масив:
public class IntegerArrayBuilder implements ArrayBuilderI<Integer> {
@Override
public Integer[] build(int size) {
return new Integer[size];
}
}
І нарешті заявка / тест:
@Test
public class ArrayTest {
public void array_concatenation() {
Integer a[] = new Integer[]{0,1};
Integer b[] = new Integer[]{2,3};
Integer c[] = Array.concat(a, b, new IntegerArrayBuilder());
assertEquals(4, c.length);
assertEquals(0, (int)c[0]);
assertEquals(1, (int)c[1]);
assertEquals(2, (int)c[2]);
assertEquals(3, (int)c[3]);
}
}
Оце Так! тут багато складних відповідей, включаючи деякі прості, які залежать від зовнішніх залежностей. як щодо цього робити:
String [] arg1 = new String{"a","b","c"};
String [] arg2 = new String{"x","y","z"};
ArrayList<String> temp = new ArrayList<String>();
temp.addAll(Arrays.asList(arg1));
temp.addAll(Arrays.asList(arg2));
String [] concatedArgs = temp.toArray(new String[arg1.length+arg2.length]);
Це працює, але вам потрібно вставити власну перевірку помилок.
public class StringConcatenate {
public static void main(String[] args){
// Create two arrays to concatenate and one array to hold both
String[] arr1 = new String[]{"s","t","r","i","n","g"};
String[] arr2 = new String[]{"s","t","r","i","n","g"};
String[] arrBoth = new String[arr1.length+arr2.length];
// Copy elements from first array into first part of new array
for(int i = 0; i < arr1.length; i++){
arrBoth[i] = arr1[i];
}
// Copy elements from second array into last part of new array
for(int j = arr1.length;j < arrBoth.length;j++){
arrBoth[j] = arr2[j-arr1.length];
}
// Print result
for(int k = 0; k < arrBoth.length; k++){
System.out.print(arrBoth[k]);
}
// Additional line to make your terminal look better at completion!
System.out.println();
}
}
Це, мабуть, не найефективніше, але воно не покладається ні на що, крім власного API Java.
for
цикл таким:for(int j = 0; j < arr2.length; j++){arrBoth[arr1.length+j] = arr2[j];}
String[] arrBoth = java.util.Arrays.copyOf(arr1, arr1.length + arr2.length)
для пропуску першої for
петлі. Економить час, пропорційний розміру arr1
.
Це перетворена функція для масиву String:
public String[] mergeArrays(String[] mainArray, String[] addArray) {
String[] finalArray = new String[mainArray.length + addArray.length];
System.arraycopy(mainArray, 0, finalArray, 0, mainArray.length);
System.arraycopy(addArray, 0, finalArray, mainArray.length, addArray.length);
return finalArray;
}
Як щодо просто
public static class Array {
public static <T> T[] concat(T[]... arrays) {
ArrayList<T> al = new ArrayList<T>();
for (T[] one : arrays)
Collections.addAll(al, one);
return (T[]) al.toArray(arrays[0].clone());
}
}
І просто робити Array.concat(arr1, arr2)
. Поки arr1
і arr2
є одного типу, це дасть вам інший масив того ж типу, що містить обидва масиви.
Загальна статична версія, яка використовує високоефективну System.arraycopy, не вимагаючи анотації @SuppressWarnings:
public static <T> T[] arrayConcat(T[] a, T[] b) {
T[] both = Arrays.copyOf(a, a.length + b.length);
System.arraycopy(b, 0, both, a.length, b.length);
return both;
}
public String[] concat(String[]... arrays)
{
int length = 0;
for (String[] array : arrays) {
length += array.length;
}
String[] result = new String[length];
int destPos = 0;
for (String[] array : arrays) {
System.arraycopy(array, 0, result, destPos, array.length);
destPos += array.length;
}
return result;
}
Ось моя дещо вдосконалена версія конгресу Йоахіма Зауера. Вона може працювати на Java 5 або 6, використовуючи System.arraycopy Java 6, якщо вона доступна під час виконання. Цей метод (IMHO) ідеально підходить для Android, оскільки він працює на Android <9 (у якого немає System.arraycopy), але використовуватиме більш швидкий метод, якщо це можливо.
public static <T> T[] concatAll(T[] first, T[]... rest) {
int totalLength = first.length;
for (T[] array : rest) {
totalLength += array.length;
}
T[] result;
try {
Method arraysCopyOf = Arrays.class.getMethod("copyOf", Object[].class, int.class);
result = (T[]) arraysCopyOf.invoke(null, first, totalLength);
} catch (Exception e){
//Java 6 / Android >= 9 way didn't work, so use the "traditional" approach
result = (T[]) java.lang.reflect.Array.newInstance(first.getClass().getComponentType(), totalLength);
System.arraycopy(first, 0, result, 0, first.length);
}
int offset = first.length;
for (T[] array : rest) {
System.arraycopy(array, 0, result, offset, array.length);
offset += array.length;
}
return result;
}
Ще один спосіб подумати над питанням. Щоб об'єднати два або більше масивів, потрібно зробити це перерахувати всі елементи кожного масиву, а потім створити новий масив. Це звучить як створити, List<T>
а потім зателефонувати toArray
на нього. Деякі інші відповіді використовують ArrayList
, і це добре. Але як щодо здійснення нашого власного? Це не важко:
private static <T> T[] addAll(final T[] f, final T...o){
return new AbstractList<T>(){
@Override
public T get(int i) {
return i>=f.length ? o[i - f.length] : f[i];
}
@Override
public int size() {
return f.length + o.length;
}
}.toArray(f);
}
Я вважаю, що вищесказане рівнозначне рішенням, яке використовує System.arraycopy
. Однак я думаю, у цього є своя краса.
Як щодо :
public String[] combineArray (String[] ... strings) {
List<String> tmpList = new ArrayList<String>();
for (int i = 0; i < strings.length; i++)
tmpList.addAll(Arrays.asList(strings[i]));
return tmpList.toArray(new String[tmpList.size()]);
}
Використовуючи лише власний API Javas:
String[] join(String[]... arrays) {
// calculate size of target array
int size = 0;
for (String[] array : arrays) {
size += array.length;
}
// create list of appropriate size
java.util.List list = new java.util.ArrayList(size);
// add arrays
for (String[] array : arrays) {
list.addAll(java.util.Arrays.asList(array));
}
// create and return final array
return list.toArray(new String[size]);
}
Тепер цей код не є найефективнішим, але він спирається лише на стандартні класи java і його легко зрозуміти. Він працює для будь-якої кількості String [] (навіть нульових масивів).