Как изменить тип массива java

If you have an array of Java objects which have a primitive type (for example Byte, Integer, Char, etc). Is there a neat way I can convert it into an array of the primitive type? In particular ca...

We can use Stream API to create primitive type arrays from their boxed object counterparts.

For Character[] array conversion to char[], using an accordingly size-allocated custom Collector, with Supplier<CharBuffer>, BiConsumer<CharBuffer, Character> accumulator, BinaryOperator<CharBuffer> combiner and Function<CharBuffer, char[]> finisher), as following will work:

Collector<Character, CharBuffer, char[]> charArrayCollector = Collector.of(
  () -> CharBuffer.allocate(95), 
  CharBuffer::put, 
  CharBuffer::put, 
  CharBuffer::array
);

It supplies the CharBuffer for the printable ASCII chars, accumulating each streamed Character into a CharBuffer instance, combining parallelized processing results from multiple CharBuffer instances in correct order and finally builds the desired char[] array from the accumulated and combined results, after all threads have finished.

First, we’re creating a Character[] test array from the standard printable ASCII set, by utilizing the int values from an IntStream, by iterating the ASCII range and mapping each value into a Character Stream, after casting them to char primitives and converting those to Character objects:

Character[] asciiCharacters = IntStream.range(32, 127)
  .mapToObj(i -> Character.valueOf((char)i))
  .toArray(Character[]::new);

Now, we simply need to create a Stream of Characters from the Character array, which then can be collected into a char[] array, by the custom Collector.

char[] asciiChars = Stream.of(asciiCharacters ).collect(charArrayCollector);

This works for other Number types accordingly:

byte[] bytes = new byte[] { Byte.MIN_VALUE, -1 , 0, 1, Byte.MAX_VALUE };
Byte[] boxedBytes = IntStream.range(0, bytes.length)
  .mapToObj(i -> bytes[i])
  .toArray(Byte[]::new);
byte[] collectedBytes = Stream.of(boxedBytes).collect(
  Collector.of(
    () -> ByteBuffer.allocate(boxedBytes.length), 
    ByteBuffer::put, 
    ByteBuffer::put, 
    ByteBuffer::array
  )
);

short[] shorts = new short[] { Short.MIN_VALUE, -1, 0, 1, Short.MAX_VALUE };
Short[] boxedShorts = IntStream.range(0, shorts.length)
  .mapToObj(i -> shorts[i])
  .toArray(Short[]::new);
short[] collectedShorts = Stream.of(boxedShorts).collect(
  Collector.of(
    () -> ShortBuffer.allocate(boxedShorts .length), 
    ShortBuffer::put, 
    ShortBuffer::put, 
    ShortBuffer::array
  )
);

float[] floats = new float[] { Float.MIN_VALUE, -1.0f, 0f, 1.0f, Float.MAX_VALUE };
Float[] boxedFLoats = IntStream.range(0, floats.length)
  .mapToObj(i -> floats[i])
  .toArray(Float[]::new);
float[] collectedFloats = Stream.of(boxedFLoats).collect(
  Collector.of(
    () -> FloatBuffer.allocate(boxedFLoats.length), 
    FloatBuffer::put, 
    FloatBuffer::put, 
    FloatBuffer::array
  )
);

The primitive types, for which Stream API supports it, can be converted a bit easier:

int[] ints = new int[] { Integer.MIN_VALUE, -1, 0, 1, Integer.MAX_VALUE };
Integer[] integers = IntStream.of(ints).boxed().toArray(Integer[]::new);
int[] collectedInts = Stream.of(integers).collect(
  Collector.of(
    () -> IntBuffer.allocate(integers.length), 
    IntBuffer::put, 
    IntBuffer::put, 
    IntBuffer::array
  )
);

long[] longs = new long[] { Long.MIN_VALUE, -1l, 0l, 1l, Long.MAX_VALUE };
Long[] boxedLongs = LongStream.of(longs).boxed().toArray(Long[]::new);
long[] collectedLongs = Stream.of(boxedLongs ).collect(
  Collector.of(
    () -> LongBuffer.allocate(boxedLongs .length), 
    LongBuffer::put, 
    LongBuffer::put, 
    LongBuffer::array
  )
);

double[] doubles = new double[] { Double.MIN_VALUE, -1.0, 0, 1.0, Double.MAX_VALUE };
Double[] boxedDoubles = DoubleStream.of(doubles)
  .boxed()
  .toArray(Double[]::new);
double[] collectedDoubles = Stream.of(boxedDoubles).collect(
  Collector.of(
    () -> DoubleBuffer.allocate(boxedDoubles.length), 
    DoubleBuffer::put, 
    DoubleBuffer::put, 
    DoubleBuffer::array
  )
);

I have a code like this :

public class Main{
    public static void main(String args[]) {
    String s="null, arco, baleno";
     Object[] arr=s.split("\,");
     for (int i=0; i<arr.length; i++)
     {
         System.out.println(arr[i]);
     }
    }
}

I need possibility to change type of arr[0] or other elements from String to int or other types,how can I do that?

thanks in advance.
ciao h.

asked Dec 30, 2011 at 13:30

haltman's user avatar

1

try with this

Integer.parseInt(arr[i]);

assign it to new integer array like

newArray[i] = Integer.parseInt(arr[i]);

newArray is an integer array

answered Dec 30, 2011 at 13:34

sethupathi.t's user avatar

2

You can’t. Java arrays store elements of single type.

That’s unlike i.e. python lists where you can do

 l = ['a',2, 3.0]

In java you’ll have to create a different array of the type you need and convert each element of your array accordingly i.e. by parsing it.

answered Dec 30, 2011 at 13:33

soulcheck's user avatar

soulchecksoulcheck

35.9k6 gold badges87 silver badges90 bronze badges

You can change from string to int in different ways as follows.

Integer.parseInt(arr[i].toString());
Integer.valueOf(arr[i].toString()).intValue();
Integer temp=new Integer(arr[i].toString());

You can parse the elements of arr[] to int and store into another array as follows.

public class Main
{
    public static void main(String args[])
    {
        String s="1,2,3";
        Object[] arr=s.split("\,");
        int temp[]=new int[arr.length];

        for(int i=0; i<arr.length; i++)
        {
            temp[i]=Integer.parseInt(arr[i].toString());
            System.out.println(temp[i]);
        }
    }
}

You can convert a data type to another data type as follows (using auto-boxing and unboxing techniques).

    Float f=new Float(arr[0].toString());
    f=Float.parseFloat(arr[0].toString());
    f=Float.valueOf(arr[0].toString()).floatValue();

    Double d=new Double(arr[0].toString());
    d=Double.parseDouble(arr[0].toString());
    d=Double.valueOf(arr[0].toString()).doubleValue();

    Long l=new Long(arr[0].toString());
    l=Long.parseLong(arr[0].toString());
    l=Long.valueOf(arr[0].toString()).longValue();

    Byte b=new Byte(arr[0].toString()).byteValue();
    b=Byte.parseByte(arr[0].toString());
    b=Byte.valueOf(arr[0].toString()).byteValue();

    Short sh=new Short(arr[0].toString());
    sh=Short.parseShort(arr[0].toString());
    sh=Short.valueOf(arr[0].toString()).shortValue();

    String str=String.valueOf(arr[0]).toString();

    Boolean bool=true;
    String boolStr=bool.toString();

    boolean boolPri=false;
    boolStr=String.valueOf(boolPri);

answered Dec 30, 2011 at 13:39

Lion's user avatar

LionLion

18.5k22 gold badges79 silver badges108 bronze badges

0

I have a code like this :

public class Main{
    public static void main(String args[]) {
    String s="null, arco, baleno";
     Object[] arr=s.split("\,");
     for (int i=0; i<arr.length; i++)
     {
         System.out.println(arr[i]);
     }
    }
}

I need possibility to change type of arr[0] or other elements from String to int or other types,how can I do that?

thanks in advance.
ciao h.

asked Dec 30, 2011 at 13:30

haltman's user avatar

1

try with this

Integer.parseInt(arr[i]);

assign it to new integer array like

newArray[i] = Integer.parseInt(arr[i]);

newArray is an integer array

answered Dec 30, 2011 at 13:34

sethupathi.t's user avatar

2

You can’t. Java arrays store elements of single type.

That’s unlike i.e. python lists where you can do

 l = ['a',2, 3.0]

In java you’ll have to create a different array of the type you need and convert each element of your array accordingly i.e. by parsing it.

answered Dec 30, 2011 at 13:33

soulcheck's user avatar

soulchecksoulcheck

35.9k6 gold badges87 silver badges90 bronze badges

You can change from string to int in different ways as follows.

Integer.parseInt(arr[i].toString());
Integer.valueOf(arr[i].toString()).intValue();
Integer temp=new Integer(arr[i].toString());

You can parse the elements of arr[] to int and store into another array as follows.

public class Main
{
    public static void main(String args[])
    {
        String s="1,2,3";
        Object[] arr=s.split("\,");
        int temp[]=new int[arr.length];

        for(int i=0; i<arr.length; i++)
        {
            temp[i]=Integer.parseInt(arr[i].toString());
            System.out.println(temp[i]);
        }
    }
}

You can convert a data type to another data type as follows (using auto-boxing and unboxing techniques).

    Float f=new Float(arr[0].toString());
    f=Float.parseFloat(arr[0].toString());
    f=Float.valueOf(arr[0].toString()).floatValue();

    Double d=new Double(arr[0].toString());
    d=Double.parseDouble(arr[0].toString());
    d=Double.valueOf(arr[0].toString()).doubleValue();

    Long l=new Long(arr[0].toString());
    l=Long.parseLong(arr[0].toString());
    l=Long.valueOf(arr[0].toString()).longValue();

    Byte b=new Byte(arr[0].toString()).byteValue();
    b=Byte.parseByte(arr[0].toString());
    b=Byte.valueOf(arr[0].toString()).byteValue();

    Short sh=new Short(arr[0].toString());
    sh=Short.parseShort(arr[0].toString());
    sh=Short.valueOf(arr[0].toString()).shortValue();

    String str=String.valueOf(arr[0]).toString();

    Boolean bool=true;
    String boolStr=bool.toString();

    boolean boolPri=false;
    boolStr=String.valueOf(boolPri);

answered Dec 30, 2011 at 13:39

Lion's user avatar

LionLion

18.5k22 gold badges79 silver badges108 bronze badges

0

Для хранения данных, используемых при работе программы, применяются переменные того или иного типа. Например, если требуется сохранить имя игрока, то создается переменная String name, если целочисленное значение, то int number и т. д. Когда таких значений немного или их количество заранее известно, то для них приемлемо использовать отдельные переменные.

Но что делать, когда в программе задействовано не одно, а 10 чисел, или их количество становится известно только на этапе запуска приложения, когда пользователь вводит его с клавиатуры? Или в какой-то игре может участвовать разное количество игроков, устанавливаемое при ее старте. В таких ситуациях отдельными переменными уже не обойтись, т. к. их точное количество будет невозможно определить. Да и, если все же попытаться это сделать, код станет громоздким и не универсальным, а работа с ним будет крайне ограниченной из-за невозможности использования цикла для автоматизированной обработки данных.

Вот тут-то и приходит на выручку такая структура данных как массив, которая позволяет хранить в себе значения одного типа, что значительно упрощает их обработку.

Дадим определение массиву.

Массив — это структура данных фиксированного размера, являющаяся объектом и состоящая из ячеек, расположенных последовательно в памяти, которые могут хранить в себе значения только одного, заранее заданного типа. При этом каждая ячейка обладает адресом (порядковым номером, индексом), позволяющим получать к ней доступ.

На картинке ниже схематично изображен массив размерностью 10, заполненный числами, а также индексы ячеек:

В повседневной жизни мы постоянно сталкиваемся с массивами, например, пчелиные соты, контейнер для куриных яиц, коробка с конфетами, формочки для заморозки льда и т. д. Все эти хранилища тоже содержат ячейки, где размещается тот или иной продукт одного типа (яйца не могут храниться в сотах, как и лед вместо конфет — для каждого продукта используется свой контейнер).

Далее мы подробно и обстоятельно рассмотрим массив и его устройство.

1. Объявление переменной массива

Для того, чтобы можно было работать с массивом, необходимо иметь к нему доступ. Для этого требуется объявить (создать, декларировать) переменную, с помощью которой, как через пульт, мы могли бы обращаться к конкретным ячейкам массива, указывая номер нужного «канала» (индекса).

Пример объявления таких переменных:


double[] numbers;
char[] letters;
int[] ids;

Такие переменные в Java объявляются точно так же, как любые другие (у них есть тип данных и имя). Отличие лишь в наличии квадратных скобок [], размещаемых после типа хранимых в массиве данных (при этом перед [] пробел не ставится). Скобки являются специальным маркером, указывающим на то, что переменная предназначена для массива.

Существуют и другие варианты размещения [], но указанный выше способ является наиболее предпочтительным, и именно он активно используется Java-разработчиками. Кроме того, скобки являются частью типа массива и должны стоять рядом с обозначением типа хранимых им значений.

Все возможные варианты оформления:


int[] arr; — общепринятый Java-стиль
int arr[];
int [] arr;
int arr [];

По историческим причинам (наследство от языка С/С++) Java позволяет размещать [] в разных местах, но это не значит, что нужно использовать какой-то иной стиль, кроме общепринятого (или используемого в конкретной компании), даже если вы увидите подобный код у кого-то в интернете или книге. Всегда придерживайтесь одного стиля, не смешивайте разные способы написания в одной программе.

Т. к. массив всегда хранит множество значений, то для именования переменной используется:

  • существительное во множественном числе, например, cats, cars, resumes, playerAttempts
  • местоимение, например, allNumbers, myBooks
  • прилагательное, например, physicalConstants

String[] fullNames;
Player[] players;
short[] nums;

Массив может хранить не только значения примитивных, но и ссылочных типов. И сам он тоже является ссылочным типом данных. В первой строке объявляется переменная массива строк, во второй — массива игроков, а в третьей — переменная для хранения примитивных целых значений.

Забегая вперед, необходимо отметить, что переменные массивов хранят не сам массив, а ссылку на него, которая, в свою очередь, указывает на начало области памяти, содержащую его значения.

Чтобы создать массив, необходимо использовать ключевое слово new с указанием его размерности и типа:


int[] array = new int[10];

Такого рода запись следует читать справа налево следующим образом:

  • Оператор new выделяет в памяти (heap, куче) место под хранение десяти значений типа int
  • Затем он возвращает ссылку (будет храниться в стеке) на начало области памяти (совпадает с первым элементом), выделенной под массив
  • Ссылка помещается в переменную array того же типа, что и массив

На картинке схематично в памяти изображен массив int размерностью 10, заполненный числами:

Следует помнить, что после создания массива его размер (длину) изменить нельзя.

Еще пример, но уже не с примитивами, а со ссылочным типом String:


String[] strings = new String[4];

это то же самое, что и 
String[] strings;
strings = new String[4];

Первый вариант предпочтительней, т. к. не стоит без явной причины объявлять переменную и создавать массив на разных строках — это и больше кода, и подобная запись всегда вызовет у опытного программиста недоумение: «А нафига так было делать, если можно в одну строку?».

Как написано в документации, массив является объектом: «An object is a class instance or an array». Но наблюдательный читатель, возможно, заметил, что хоть он и является таковым, но при этом не имеет класса, который описывал бы его. Мы просто используем массив, как есть. Но это «как есть» хранит в себе множество скрытых процессов и тайн, которые мы по возможности приоткроем.

Хоть тип массива и не является классом, например int[], но все же имеет ассоциируемый с ним класс, который автоматически создает виртуальная машина (Java Virtual Machine, JVM). При этом данный класс неявно наследуется от java.lang.Object, что делает доступным все его методы (кроме clone) и интерфейсы Cloneable и Serializable. Но у программиста нет доступа к его коду, его нельзя посмотреть глазами.

Ассоциируемые классы создаются для каждого типа хранимой в массиве информации, например, для boolean[], или Player[], или String и т. д. И мы можем узнать, как эти классы называются. Они-то и являются реальными типами массивов. Чтобы увидеть их названия, необходимо воспользоваться Java-магией (рефлексией), которая позволит узнать настоящий тип массива, его суперкласс (от которого он наследуется) и список доступных методов.

Создадим массив нулевой длины (пустой) и отобразим нужную нам информацию (не забудьте сделать импорт java.lang.reflect.Method):


int[] arr = {};
System.out.println("Тип массива - " + arr.getClass());
System.out.println("Суперкласс типа массива - " + arr.getClass().getSuperclass());

System.out.println("Доступные методы:");
for (Method m : arr.getClass().getMethods()) {
    System.out.println(m.getName());
}

  • [I — это сигнатура типа (класса, который JVM создает во время выполнения) для объекта «массив с элементами типа int». Это и есть настоящий тип данных массива, как объекта
  • [ говорит, что это одномерный массив
  • I, что он содержит целые числа
  • Имя суперкласса, записанного с помощью его полного имени — java.lang.Object

Все возможные сигнатуры типов имеют следующий формат:


[B - byte
[C - char
[D - double
[F - float
[I - int
[J - long
[S - short
[Z - boolean
[L - любой объект

Можно сделать вывод, что тип массива и тип хранимых им значений — это формально разные типы. В нашем примере типом массива является тип [I, а типом хранимых значений — int.

Разберем еще одно доказательство того, что Object является суперклассом для массивов:


Object obj = new int[]{3, 6, 9};
int[] arr = (int[]) obj;
System.out.println(obj);
System.out.println(arr);

В первой строке объявляется и инициализируется массив, ссылка на который присваивается переменной типа Object. И при этом никакой ошибки не возникнет. Во второй строке происходит приведение Object к int[]. В итоге обе переменные (их ссылки) указывают на один и тот же массив.

Написанный выше код отобразит следующий результат:

Как известно, если попытаться вывести, например, в println значение объекта, просто записав его имя в метод в качестве аргумента, то неявно будет вызван метод toString(). А так как массивы его не переопределяют, т. е. у них нет своей реализации этого метода, то используется реализация по умолчанию, записанная в классе Object.

Как написано в документации, данный метод возвращает строку, получаемую с помощью следующего кода:


getClass().getName() + '@' + Integer.toHexString(hashCode())

В итоге она состоит из имени класса, в нашем случае — [I, разделителя в виде @ и шестнадцатеричного представления хеш-кода объекта. Для человека эта информация не несет никакой полезной нагрузки. Решение этой проблемы обсуждается во второй части статьи.

Нам же сейчас важно, что обе переменные указывают на один и тот же массив, т. е. хранят одну и ту же ссылку на него. Это говорит также о том, что на один массив может указывать любое количество переменных.

И на закуску немного сумасбродства и неочевидных вещей — создадим пустой массив с нулевой длиной:


String[] array1 = {};
char[] array2 = new char[0];

Как вы думаете, как в памяти представляются эти массивы?

Массив с нулевым размером представляет собой объект, у которого есть заголовок объекта (Object header) и размер, но нет места в памяти, выделенного для его элементов. При этом ссылка на такой массив является рабочей и сохраняется в переменную.

Массив нулевой длины не равен null:

null — это указатель в никуда, который является маркером отсутствия указателя на объект.

В спецификации написано: «Поле length является public final (общедоступным и неизменяемым) и содержит в себе количество элементов массива (длина может быть положительной или нулевой)».

Это поле вычисляется один раз при создании массива, т. к. его размер никогда не меняется.

Вызов этого поля часто путают с вызовом метода length() получения длины String. Но length — не метод, а поле. Доступ к нему можно получить напрямую, поскольку оно является общедоступным и неизменяемым (хотя тут, возможно, что это просто плохое архитектурное решение из далекого прошлого).

Т. к. данное поле является частью объекта и не меняется, то это позволяет JVM не пересчитывать количество элементов массива всякий раз, когда в коде используется его длина — она просто берет его из заголовка объекта, когда это требуется.

Чтобы вас окончательно запутать, сошлюсь на документацию, в которой написано: «Длина массива не является частью его типа». Значит length не хранится в ассоциируемом классе!

«Да, это какой-то вынос мозга», — скажете вы: класса нет, но массив — это объект. При этом он наследуется от Object. А length — вроде бы, поле, но не совсем.

А где тогда хранится длина массива?

Она размещается в заголовке объекта. Что такое заголовок? Это часть любого объекта, содержащая метаинформацию о нем. Давайте взглянем на этот заголовок, используя утилиту Java Object Layout (JOL).

Чтобы программа ниже у вас заработала, необходимо в IDEA нажать Ctrl + Alt + Shift + S, выбрать Modules, а затем в Dependencies нажать + → Library… → From Maven… В строке поиска ввести org.openjdk.jol.


import org.openjdk.jol.info.ClassLayout;

public class JOLArray {

    public static void main(String[] args) throws Exception {
        System.out.println(ClassLayout.parseInstance(new int[10]).toPrintable());
    }
}

В итоге на консоль будет выведена следующая метаинформация из заголовка объекта:

Из всего вывода нас интересует строка: 12 4 (array length) 10.

Это и есть длина созданного массива new int[10], которая имеет размер 4 байта. Поле length является особенным в том смысле, что оно находится в заголовке объекта массива, а для получения его длины используется специальный байт-код.

Больше о заголовке объекта можно узнать в видео Алексея Шипилева.

Так же следует заметить, что в наборе инструкций JVM все объекты создаются с использованием одних и тех же байт-кодов, за исключением массивов — они обрабатываются специальными командами.


public class ByteCodeArray {

    public static void main(String[] args) {
        int[] nums = new int[5];
        int len = nums.length;
    }
}

Отобразим байт-код этого класса с помощью javap:


javap -c ByteCodeArray.class

Code:
       0: iconst_5
       1: newarray       int
       3: astore_1
       4: aload_1
       5: arraylength
       6: istore_2
       7: return

Нас в нем интересуют две строки: newarray int, которая создает массив int-ов и arraylength, которая получает длину массива. Таким образом, доступ к нему не осуществляется, как если бы это было обычное поле. Этого поля просто не существует в типе массива. Для работы с ним у JVM есть отдельная инструкция, которая берет его из заголовка объекта.

Создавать и инициализировать массив значениями можно разными способами. Выбор того или иного решения зависит от различных факторов, которые, в том числе, будут упомянуты ниже.

5.1. Сокращенная форма создания и инициализации

Если вам заранее известно, какие значения должен содержать массив, то их можно указать через запятую, используя быструю инициализацию.


String[] names = new String[]{"Max", "Grigoriy", "Java"};

// та же запись, но короче
String[] names = {"Max", "Grigoriy", "Java"};

Длина массива, объявленного таким образом, зависит от количества перечисленных элементов. Во втором варианте тип массива определяется автоматически, исходя из хранимых данных.

Исходя из примеров выше следует отметить, что в Java в массивах хранятся не сами объекты, а ссылки на них, которые, в свою очередь, указывают на место в куче (heap), где находится соответствующий объект.

Схематично это можно изобразить так:


float[] partsExpression = {1.83, 1.67, 1.75, 1.50};

Resume[] resumes = {new Resume("uuid1"), new Resume("uuid2")};

Как видите, это удобный и компактный способ, применяемый, когда значений не так много, и они заранее известны. Его используют при тестировании программ, в алгоритмических задачах, небольших программах и т. д.

5.2. Ручная инициализация по индексу

Элементы массива начинают храниться с нулевого индекса: самая первая ячейка находится под индексом 0, а не 1. Это может показаться непривычным, но на то есть причина.

На самом деле, индекс в Java является ничем иным, как смещением памяти относительно начала массива (расстоянием от его начала до любого элемента). А т. к. начальный адрес совпадает с адресом первого элемента, то смещение будет равно 0, а значит и индекс тоже.

Адрес любой ячейки массива рассчитывается по следующей формуле:


адрес начала массива + индекс * количество памяти, выделенное на один элемент

Например, есть массив int[] nums = {3, 2, 1};

Предположим, что nums хранит адрес 0xFF00. Тогда ячейки массива будут иметь следующие адреса (с учетом того, что на каждое значение int выделяется 4 байта):


nums[0] → 0xFF00;
nums[1] → 0xFF04;
nums[2] → 0xFF08;

Более научное объяснение того, почему индексация начинается с 0, а не с 1, можно прочитать в следующей статье.

С индексацией разобрались. Скажем теперь пару слов про то, чему равно количество индексов в массиве. Оно всегда равно length — 1. Это связано с тем, что размер массива (его длина) всегда больше на единицу самого последнего индекса из-за того, что индексация начинается 0.

Количественно размер массива и число индексов равно, но в числовом представлении оно отличается на 1 в меньшую сторону, если считать индексы по порядку. Таким образом, в приведенном ниже примере с массивом из 2 элементов индексы идут от 0 до 1, а длина массива равна 2.

Благодаря индексу можно получать доступ к любому элементу, указав его в квадратных скобках, размещенных после имени массива.


int[] nums = new int[2];
nums[0] = 3;
nums[1] = 7;
int num = nums[0];
int num1 = nums[1];
System.out.println(num);
System.out.println(num1);

// или можно сразу вывести значение без создания переменной
System.out.println("Под индексом 0 хранится значение " + nums[0]);
System.out.println("Под индексом 1 хранится значение " + nums[1]);

В этом примере в первом случае сначала в ячейки массива под индексами 0 и 1 сохраняются числа 3 и 7. Затем они же считываются из него и размещаются в переменные num и num1. Далее их значения выводятся на консоль.

Второй вариант отличается только тем, что значения берутся из массива и сразу выводятся без создания промежуточных переменных. Этот способ предпочтительнее, когда переменные далее нигде не используются.

В консоли в обоих случаях отобразятся числа 3 и 7, взятые из массива nums по индексам 0 и 1.

Подобный способ инициализации, по сути, является более многословной формой ее сокращенного варианта, если массив целиком инициализируется сразу после создания, но при этом несет в себе большую гибкость в случае, когда инициализация проходит в разных местах программы, исходя из каких-то условий задачи.

Данный способ все же не совсем подходит для автоматической инициализации массива. Но для понимания того, как обращаться к значениям массива по индексу, он очень показателен.

Приведем для примера «странный» код:


System.out.println((new int[]{3, 6, 9})[1]);

В итоге в консоли отобразится число 6, находящееся под индексом 1.

Данный код позволил «подключиться» к массиву без использования переменной. Подобный способ может показаться не совсем очевидным, но, как видим, он работает. Пользы от него немного, но как демонстрация того, что к массиву можно обращаться и без переменной, а сразу через «голую» ссылку, вполне подходит. В реальном проекте вы, скорее всего, не будете использовать подобные выкрутасы. Они нужны лишь для понимания работы тонкостей массива.

5.3. Инициализация и доступ к элементам в цикле

Согласитесь, что инициализировать массив вручную, указывая явно каждый его индекс и значение — громоздко и не всегда оправдано. Как правило, массивы инициализируют и отображают их значения автоматически с помощью цикла — это и универсальнее и короче по строкам кода.

При использовании цикла необходимо знать следующие параметры:

  • начальное значение индекса (откуда стартует итерация)
  • значение, ограничивающее количество итераций (обычно используется длина массива или 0 — если итерация стартует с конца массива)
  • конечное значение индекса (для завершения числа итераций)

Для работы с массивами с учетом вышеописанных требований удобнее всего использовать цикл for. Как мы знаем, он состоит из трех секций: инициализация счетчика (начальное значение индекса), условие выполнения (число итераций) и изменение счетчика (для достижения конечного значения индекса).

В качестве типа данных индекса массива можно использовать int, short, byte или char. Использование типа long приведет к ошибке компиляции, т. к. индексы массива в Java преобразуются в int-значения (4 байта). А т. к. под long выделяется больше памяти (8 байт), то преобразование к int потенциально может привести к потерям данных, о чем и сообщит компилятор.

Приведем пример такого кода:


int[] ints = new int[5];
for (long lng = 0L; lng < ints.length; lng++) {
    System.out.println(ints[lng]);
}

Проблема решается принудительным приведением типов. Тут компилятор ничего не скажет, т. к. всю ответственность за эту операцию вы берете на себя:


System.out.println((int) ints[lng]);

Закончив это небольшое отступление, продолжим разбираться с циклом for.

Начальное значение индекса — это значение, с которого будет начинаться доступ к ячейкам массива. Обычно начинают с самого начала, т. е. с 0 (с первой ячейки). Но не обязательно. Есть ряд задач, в которых удобнее начинать с конца (длина массива — 1).

Значение, ограничивающее количество итераций, позволяет не выйти за правую границу массива, когда индекс >= длине массива. Если цикл реализован так, что проход по массиву осуществляется с конца к началу, то тут все наоборот: длина — 1 будет началом, а условие index >= 0 будет защищать от выхода за его левую границу.

Цикл прерывается, когда конечное значение индекса совпадает с его длиной или оно < 0.

Пример работы циклов по массиву слева направо:


String[] strings = new String[4];
 
for(int i = 0; i < strings.length; i++) {
    strings[i] = "Строка по индексу " + i;
}
 
for(int i = 0; i < strings.length; i++) {
    System.out.println(strings[i]);
}

В этом примере создается массив строк размерностью 4.

В первом цикле в массив сохраняется сконкатенированный (склеенный) текст с номерами индексов.

Второй цикл выводит на консоль значения каждой ячейки. Число в квадратных скобках — это конкретная позиция массива, к которой мы хотим получить доступ.

В обоих циклах переменная i используется в качестве счетчика для обозначения индекса. Она имеет начальное значение 0, которое в процессе работы циклов меняется на единицу (инкрементируется) после каждой итерации (одного прохода цикла). Как только условие i < strings.length перестает выполняться, т. е. проверка вернет false, то цикл прекращается.

Вы можете спросить, почему я использовал идентификатор «i»? Разве это не «плохое» имя для переменной? Да, обычно для имен переменных выбираются имена, глядя на которые сразу бывает понятно, что они хранят. Но в данном случае действует негласное соглашение, о котором можно прочитать тут.

Если в двух словах, то счетчики циклов принято именовать с помощью i, j и k, где i — находится во внешнем цикле, j — во вложенном в него цикле и т. д.

Считается, что этот стиль возник в языке программирования Фортран, где имена переменных, начинающиеся с букв I по Q, по умолчанию считались целыми, остальные были действительными. А еще раньше это появилось в математике, где индексы для сумм и умножений именуются как i, j, k.

Приведем еще один пример работы с массивом в цикле, но уже в обратном порядке:


char[] chars = new char[32];
int i = 0; 
for(char ch = 'Я'; ch >= 'А'; ch--) {
    chars[i++] = ch;
}

for(i = chars.length - 1; i >= 0; i--) {
    System.out.print(chars[i]);
}

В первом цикле массив chars заполняется буквами от А до Я. Он работает до тех пор, пока выполняется условие ch >= ‘А’. Во втором цикле все буквы выводятся на консоль.

Что будет, если вывести массив без инициализации?

Проверим эту идею, создав массивы размерностью 1 всех примитивных типов и String. Отобразим их значения в консоль без инициализации:


System.out.println("Значение по умолчанию для byte " + (new byte[1])[0]);
System.out.println("Значение по умолчанию для short " + (new short[1])[0]);
System.out.println("Значение по умолчанию для int " + (new int[1])[0]);
System.out.println("Значение по умолчанию для long " + (new long[1])[0]);
System.out.println("Значение по умолчанию для float " + (new float[1])[0]);
System.out.println("Значение по умолчанию для double " + (new double[1])[0]);
System.out.println("Значение по умолчанию для char " + (new char[1])[0]);
System.out.println("Значение по умолчанию для boolean " + (new boolean[1])[0]);

System.out.println("Значение по умолчанию для String " + (new String[1])[0]);

Из всего увиденного можно сделать вывод, что после создания массива каждая его ячейка инициализируется значением по умолчанию, исходя из типа хранимых данных.

Значение переменной char не отображается (не имеет никакого представления), т. к. соответствует коду ‘u0000’, являющимся пустым символом.

Известно, что локальные переменные по умолчанию не инициализируются, но с массивом, как мы видим, все иначе. Спецификация (1, 2) языка требует его инициализации значениями по умолчанию, что фактически мы и наблюдали в примере.

Что будет, если выйти за границы массива?

За границу массива мы можем выйти с двух сторон: слева (когда индекс отрицательный) и справа (когда он >= длине массива). В обоих случаях произойдет ошибка (исключительная ситуация) и вылетит исключение java.lang.ArrayIndexOutOfBoundsException.

Этот тип исключения всегда свидетельствует об ошибке в коде, совершенной программистом. С ней можно сделать только одно — исправить код так, чтобы она не возникала, в принципе.


int[] nums = new int[4];
 
for(int i = 0; i <= nums.length; i++) {
    nums[i] = i + 1;
}

Из-за того, что условие i <= nums.length при i = 4 вернуло true, в строке nums[i] = i + 1; возникла ошибка связанная с тем, что в массиве нет ячейки под индексом 4. Ситуации nums[4] = 4 + 1 быть не должно.


String[] str = {"A", "B", "C"};

for(int i = str.length - 1; i >= 0; i--) {
    System.out.println(str[i - 1]);
}

В коде ошибка произошла из-за того, что в какой-то момент в строке str[i — 1] индекс стал равен str[0 — 1]. Информация в консоли сообщает, что индекс не может быть -1, т. к. он вне пределов длины 3.

Для работы с массивами цикл for является более предпочтительным, чем while, но и он не идеален, т. к. имеет массу мест, где можно совершить ошибку: не так инициализировать счетчик, неверно написать условие работы цикла, перепутать название счетчика и т. д.

Чтобы как-то минимизировать количество ошибок, упростить работу с массивами и коллекциями (далее будут упоминаться только массивы) и сделать код более лаконичным, начиная с JDK 5, была внедрена упрощенная версия цикла for под названием for-each.

Код, записанный в традиционном стиле:


int[] nums = new int[10];

for (int i = 0; i < nums.length; i++) {
    System.out.print(nums[i]);
}

Тот же самый код, но с использованием for-each:


int[] nums = new int[10];

for (int num : nums) {
    System.out.print(num);
}

Цикл for-each состоит из двух секций, отделенных друг от друга двоеточием (при этом перед и после него обычно ставят пробел): в правой части размещается имя массива, а в левой создается переменная того же типа, что и элементы массива. Элементы по порядку берутся из массива и помещаются в эту переменную, значение из которой в каждой итерации выводится на консоль.

Данный вид цикла является так называемым «синтаксическим сахаром», который отличается от традиционного цикла for только внешне, но для JVM в них нет никаких различий.

  • Отсутствие счетчика индекса — все ячейки перебираются по порядку, начиная с нулевой
  • Отсутствие условия работы цикла — это действие выполняется автоматически и скрыто от программиста

Цикл for-each позволяет получить доступ к элементам массива, но не дает возможности использовать индексы (к ним банально нет доступа) или обращаться к значениям в отличном от последовательного доступа порядке. Кроме того, он не позволяет менять или удалять значения массива. В основном, его используют для вывода элементов массива на консоль. Если вам нужно больше возможностей, то используйте обычный цикл for.

Цикл for-each используется, когда:

  • необходимо вывести значения массива по порядку
  • не нужно изменять массив
  • не нужны индексы

char[] chars = {'q', 'w'};
 
for(char symbol : chars) {
    System.out.println(symbol);
}

Вывод:
q
w

Цикл for-each для работы с массивами предоставляет массу преимуществ и удобств, нежели for и, тем более, while и do-while. Старайтесь его использовать везде, где это возможно.

7. Удаление элементов из массива

Помимо сохранения данных в массив, очень часто приходится их удалять.

Под удалением не имеется в виду, что массив станет меньше из-за того, что ячейка со значением исчезнет или есть какая-то специальная команда, которая выполняет эту операцию. Удаление в массиве — это всего лишь перезапись ячейки любым значением, отличным от удаляемого.


float[] nums = {1.5f, 0.22f, 1.0f};

System.out.println(nums[1]);
nums[1] = 3.31f;
System.out.println(nums[1]);

Вывод:
0.22
3.31

В этом примере создается массив из float. Затем выводится начальное значение по индексу 1. Далее по этому индексу устанавливается новое значение, которое фактически удаляет (перезаписывает) старое. И новое значение отображается в консоли.

Рассмотрим другой пример. Пусть имеется массив целых чисел размерностью 5. Необходимо сдвинуть все его значения на одну позицию вправо, а затем отобразить результат.

Для решения этой задачи запишем следующий код:


int[] nums = {0, 1, 2, 3, 4};

for(int i = nums.length - 1; i > 0; i--) {
    nums[i] = nums[i - 1];
}

for(int num : nums) {
    System.out.print(num);
}

В консоли отобразятся следующие значения: 123. Число 4 было удалено значением 3, которое в цикле переместилось на его место. В этом примере все значения цикла поменяли свои позиции — сдвинулись вправо на одну ячейку.

8. Скорость работы массива

Чтобы понять, насколько массив является быстрой (но не всегда) структурой данных, необходимо определиться, хотя бы вкратце, с теоретической частью вычисления этой скорости. Для этого необходимо сказать пару слов про сложность алгоритмов (Big O Notation).

Big O Notation можно перевести как О большое. Это способ (из теории алгоритмов) обозначения сложности алгоритма.

Любой алгоритм характеризуется двумя параметрами: временем выполнения (Time Complexity) и расходом памяти (Space Complexity). Например, чем длиннее массив, тем больше нужно памяти и времени на его заполнение.

При этом время считают не в секундах, а в количестве совершаемых алгоритмом операций, что позволяет устранить зависимость скорости работы алгоритма от железа.

Например, Time Complexity для поиска числа в массиве будет обозначаться, как O(n), где n — это число ячеек в массиве. Чем их больше, тем дольше работает алгоритм (выполняется большее количество операций (итераций массива), где n — их максимальное число). Т. к. поиск происходит линейно, то ячейки перебираются одна за другой по порядку. Соответственно, такой алгоритм называется линейным.

Элементы массива в памяти размещаются в едином блоке. Это сделано для более эффективного и быстрого доступа к ним.

Если индекс ячейки известен, то алгоритм имеет константное время (постоянное, не зависящее ни от чего) и обозначается O(1), т. е. доступ к ячейкам массива происходит мгновенно и не зависит от его размера.

Если массив отсортирован по возрастанию и для поиска значений используется алгоритм под названием двоичный поиск (1, 2), то количество операций, необходимых алгоритму для поиска числа будет O(log(n)).

Не беда, если вы ничего не поняли из этой главы — она больше является обзорной. Более подробно об этой теме вы можете узнать в данной статье и в книге Грокаем алгоритмы.

А дорого ли вычислять каждый раз длину массива?

Как мы выяснили, длина массива — это фиксированное значение, которое создается в момент создания массива и хранится в заголовке массива. JVM знает, что это значение никогда не изменится, а, значит, оно никогда не вычисляется, а просто берется из заголовка. Этот тип операции чтения очень быстрый и выполняется за O(1), т. е. за константное время.

Оцените статью, если она вам понравилась!

Вступление

Массивы позволяют хранить и извлекать произвольное количество значений. Они аналогичны векторам в математике. Массивы массивов аналогичны матрицам и действуют как многомерные массивы. Массивы могут хранить любые данные любого типа: примитивы, такие как int или ссылочные типы, такие как Object .

Синтаксис

  • ArrayType[] myArray; // Объявление массивов
  • ArrayType myArray[]; // Другой допустимый синтаксис (менее часто используемый и обескураженный)
  • ArrayType[][][] myArray; // Объявление многомерных зубчатых массивов (repeat [] s)
  • ArrayType myVar = myArray[index]; // Доступ к элементу (считыванию) по индексу
  • myArray[index] = value; // Присвоить значение index позиции массива
  • ArrayType[] myArray = new ArrayType[arrayLength]; // Синтаксис инициализации массива
  • int[] ints = {1, 2, 3}; // Синтаксис инициализации массива с предоставленными значениями, длина выводится из числа предоставленных значений: {[value1 [, value2] *]}
  • new int[]{4, -5, 6} // Can be used as argument, without a local variable
  • int[] ints = new int[3]; // same as {0, 0, 0}
  • int[][] ints = {{1, 2}, {3}, null}; // Инициализация многомерных массивов. int [] extends Object (а также anyType []), поэтому значение null является допустимым.

параметры

параметр подробности
ArrayType Тип массива. Это может быть примитивным ( int , long , byte ) или Object ( String , MyObject и т. Д.).
индекс Индекс относится к позиции определенного объекта в массиве.
длина Каждому массиву при создании требуется заданная длина. Это делается либо при создании пустого массива ( new int[3] ), либо подразумевается при указании значений ( {1, 2, 3} ).

Создание и инициализация массивов

Основные случаи

int[]   numbers1 = new int[3];                 // Array for 3 int values, default value is 0
int[]   numbers2 = { 1, 2, 3 };                // Array literal of 3 int values
int[]   numbers3 = new int[] { 1, 2, 3 };      // Array of 3 int values initialized
int[][] numbers4 = { { 1, 2 }, { 3, 4, 5 } };  // Jagged array literal
int[][] numbers5 = new int[5][];               // Jagged array, one dimension 5 long
int[][] numbers6 = new int[5][4];              // Multidimensional array: 5x4

Массивы могут быть созданы с использованием любого примитивного или ссылочного типа.

float[]  boats = new float[5];          // Array of five 32-bit floating point numbers.
double[] header = new double[] { 4.56, 332.267, 7.0, 0.3367, 10.0 };
                                       // Array of five 64-bit floating point numbers.
String[] theory = new String[] { "a", "b", "c" };
                                       // Array of three strings (reference type).
Object[] dArt = new Object[] { new Object(), "We love Stack Overflow.", new Integer(3) };
                                       // Array of three Objects (reference type).

В последнем примере обратите внимание, что подтипы объявленного типа массива разрешены в массиве.

Массивы для пользовательских типов также могут быть построены подобно примитивным типам

UserDefinedClass[] udType = new UserDefinedClass[5];

Массивы, коллекции и потоки

Java SE 1.2

// Parameters require objects, not primitives

// Auto-boxing happening for int 127 here
Integer[]       initial        = { 127, Integer.valueOf( 42 ) };
List<Integer>   toList         = Arrays.asList( initial );  // Fixed size! 

// Note: Works with all collections
Integer[]       fromCollection = toList.toArray( new Integer[toList.size()] );

//Java doesn't allow you to create an array of a parameterized type
List<String>[]  list = new ArrayList<String>[2];  // Compilation error!

Java SE 8

// Streams - JDK 8+
Stream<Integer> toStream       = Arrays.stream( initial );
Integer[]       fromStream     = toStream.toArray( Integer[]::new );

вступление

Массив — это структура данных, которая содержит фиксированное число примитивных значений или ссылок на экземпляры объектов.

Каждый элемент в массиве называется элементом, и каждый элемент получает доступ к его числовому индексу. Длина массива устанавливается при создании массива:

int size = 42;
int[] array = new int[size];

Размер массива фиксируется во время выполнения при инициализации. Он не может быть изменен после инициализации. Если размер должен быть изменен во время выполнения, вместо него следует использовать класс Collection такой как ArrayList . ArrayList хранит элементы в массиве и поддерживает изменение размера путем выделения нового массива и копирования элементов из старого массива.

Если массив имеет примитивный тип, т. Е.

int[] array1 = { 1,2,3 };
int[] array2 = new int[10];

значения сохраняются в самом массиве. В отсутствие инициализатора (как в array2 выше) значение по умолчанию, присвоенное каждому элементу, равно 0 (ноль).

Если тип массива является ссылкой на объект, как в

SomeClassOrInterface[] array = new SomeClassOrInterface[10];

то массив содержит ссылки на объекты типа SomeClassOrInterface . Эти ссылки могут ссылаться на экземпляр SomeClassOrInterface или любого подкласса (для классов) или на реализацию класса (для интерфейсов) SomeClassOrInterface . Если объявление массива не имеет инициализатора, то каждому элементу присваивается значение по умолчанию null .

Поскольку все массивы являются int -индексированными, размер массива должен быть задан с помощью int . Размер массива не может быть задан как long :

long size = 23L;
int[] array = new int[size]; // Compile-time error:
                             // incompatible types: possible lossy conversion from
                             // long to int

В массивах используется индексная система с нулевым значением , что означает, что индексирование начинается с 0 и заканчивается на length - 1 .

Например, следующее изображение представляет массив с размером 10 . Здесь первый элемент находится в индексе 0 а последний элемент находится в индексе 9 , а первый элемент находится в индексе 1 и последний элемент в индексе 10 (см. Рисунок ниже).

Массив из 10 элементов

Доступ к элементам массивов осуществляется в постоянное время . Это означает, что доступ к первому элементу массива имеет одинаковую стоимость (по времени) доступа к второму элементу, третьему элементу и так далее.

Java предлагает несколько способов определения и инициализации массивов, включая буквенные и конструкторские обозначения. При объявлении массивов с использованием new Type[length] каждый элемент будет инициализирован со следующими значениями по умолчанию:

  • 0 для примитивных числовых типов : byte , short , int , long , float и double .
  • 'u0000' (нулевой символ) для типа char .
  • false для boolean типа.
  • null для ссылочных типов .

Создание и инициализация массивов примитивных типов

int[] array1 = new int[] { 1, 2, 3 }; // Create an array with new operator and 
                                      // array initializer.
int[] array2 = { 1, 2, 3 };           // Shortcut syntax with array initializer.
int[] array3 = new int[3];            // Equivalent to { 0, 0, 0 }
int[] array4 = null;                  // The array itself is an object, so it
                                      // can be set as null.

При объявлении массива [] будет отображаться как часть типа в начале объявления (после имени типа) или как часть декларатора для определенной переменной (после имени переменной) или обоих:

int array5[];       /* equivalent to */  int[] array5;
int a, b[], c[][];  /* equivalent to */  int a; int[] b; int[][] c;
int[] a, b[];       /* equivalent to */  int[] a; int[][] b;
int a, []b, c[][];  /* Compilation Error, because [] is not part of the type at beginning
                       of the declaration, rather it is before 'b'. */    
// The same rules apply when declaring a method that returns an array:
int foo()[] { ... } /* equivalent to */  int[] foo() { ... }

В следующем примере обе декларации правильны и могут компилироваться и запускаться без каких-либо проблем. Однако как Конвенция по кодированию Java, так и Руководство по стилю Google Java препятствуют форме с помощью скобок после имени переменной — скобки идентифицируют тип массива и должны появляться с обозначением типа . То же самое следует использовать для сигнатур возврата метода.

float array[]; /* and */ int foo()[] { ... } /* are discouraged */
float[] array; /* and */ int[] foo() { ... } /* are encouraged */

Воспринимаемый тип предназначен для размещения переходных пользователей C , которые знакомы с синтаксисом C, который имеет скобки после имени переменной.

В Java возможно иметь массивы размером 0 :

int[] array = new int[0]; // Compiles and runs fine.
int[] array2 = {};        // Equivalent syntax.

Однако, поскольку это пустой массив, никакие элементы не могут быть прочитаны или назначены ему:

array[0] = 1;     // Throws java.lang.ArrayIndexOutOfBoundsException.
int i = array2[0]; // Also throws ArrayIndexOutOfBoundsException.

Такие пустые массивы обычно полезны в качестве возвращаемых значений, поэтому вызывающий код должен беспокоиться только о работе с массивом, а не о потенциальном null значении, которое может привести к исключению NullPointerException .

Длина массива должна быть неотрицательным целым числом:

int[] array = new int[-1]; // Throws java.lang.NegativeArraySizeException

Размер массива можно определить, используя публичное конечное поле с названием length :

System.out.println(array.length); // Prints 0 in this case.

Примечание . array.length возвращает фактический размер массива, а не количество элементов массива, которым было присвоено значение, в отличие от ArrayList.size() которое возвращает количество элементов массива, которым было присвоено значение.

Создание и инициализация многомерных массивов

Самый простой способ создания многомерного массива состоит в следующем:

int[][] a = new int[2][3];

Это создаст две три длины int arrays- а a[0] и a[1] . Это очень похоже на классическую инициализацию прямоугольных многомерных массивов в стиле C.

Вы можете создавать и инициализировать одновременно:

int[][] a = { {1, 2}, {3, 4}, {5, 6} };

В отличие от C , где поддерживаются только прямоугольные многомерные массивы, внутренние массивы не должны быть одинаковой длины или даже определены:

int[][] a = { {1}, {2, 3}, null };

Здесь, a[0] является одной длиной int массива, в то время как a[1] представляет собой две длины int массива и a[2] является null . Такие массивы называются зубчатыми массивами или оборванными массивами , то есть массивами массивов. Многомерные массивы в Java реализованы как массивы массивов, т. array[i][j][k] эквивалентен ((array[i])[j])[k] . В отличие от C # , синтаксический array[i,j] не поддерживается в Java.

Визуальное представление многомерного массива Java

Источник — Live on Ideone

Создание и инициализация массивов ссылочных типов

String[] array6 = new String[] { "Laurel", "Hardy" }; // Create an array with new 
                                                      // operator and array initializer.
String[] array7 = { "Laurel", "Hardy" };              // Shortcut syntax with array 
                                                      // initializer.
String[] array8 = new String[3];                      // { null, null, null }
String[] array9 = null;                               // null

Живой на Ideone

В дополнение к String литералам и примитивам, показанным выше, синтаксис ярлыков для инициализации массива также работает с каноническими типами Object :

Object[] array10 = { new Object(), new Object() };

Поскольку массивы являются ковариантными, массив ссылочного типа может быть инициализирован как массив подкласса, хотя ArrayStoreException будет ArrayStoreException , если вы попытаетесь установить элемент в нечто иное, чем String :

Object[] array11 = new String[] { "foo", "bar", "baz" };
array11[1] = "qux"; // fine
array11[1] = new StringBuilder(); // throws ArrayStoreException

Синтаксис ярлыка не может использоваться для этого, потому что синтаксис ярлыка будет иметь неявный тип Object[] .

Массив может быть инициализирован нулевыми элементами, используя String[] emptyArray = new String[0] . Например, массив с нулевой длиной, подобный этому, используется для создания Array из Collection когда методу нужен тип среды выполнения объекта.

В обоих примитивных и ссылочных типах пустая инициализация массива (например, String[] array8 = new String[3] ) инициализирует массив значением по умолчанию для каждого типа данных .

Создание и инициализация массивов типичного типа

В родовых классах массивы генерических типов не могут быть инициализированы, например, из-за стирания типа :

public class MyGenericClass<T> {
    private T[] a;

    public MyGenericClass() {
        a = new T[5]; // Compile time error: generic array creation
    }
}

Вместо этого они могут быть созданы с использованием одного из следующих способов: (обратите внимание, что они будут генерировать непроверенные предупреждения)

  1. Создав массив Object и переведя его в общий тип:

    a = (T[]) new Object[5];
    

    Это самый простой метод, но поскольку базовый массив по-прежнему имеет тип Object[] , этот метод не обеспечивает безопасность типов. Поэтому этот метод создания массива лучше всего использовать только в универсальном классе — не публично публиковаться.

  2. Используя Array.newInstance с параметром класса:

    public MyGenericClass(Class<T> clazz) {
        a = (T[]) Array.newInstance(clazz, 5);
    }
    

    Здесь класс T должен быть явно передан конструктору. Возвращаемый тип Array.newInstance всегда является Object . Однако этот метод более безопасен, потому что вновь созданный массив всегда имеет тип T[] и поэтому может быть безопасно экстернализирован.

Заполнение массива после инициализации

Java SE 1.2

Arrays.fill() может использоваться для заполнения массива с тем же значением после инициализации:

Arrays.fill(array8, "abc");        // { "abc", "abc", "abc" }

Живой на Ideone

fill() также может присваивать значение каждому элементу указанного диапазона массива:

Arrays.fill(array8, 1, 2, "aaa");  // Placing "aaa" from index 1 to 2.

Живой на Ideone

Java SE 8

Поскольку Java-версия 8, метод setAll и ее Concurrent эквивалент parallelSetAll , могут использоваться для установки каждого элемента массива на сгенерированные значения. Этим методам передается функция генератора, которая принимает индекс и возвращает желаемое значение для этой позиции.

Следующий пример создает целочисленный массив и устанавливает все его элементы в соответствующее значение индекса:

int[] array = new int[5];
Arrays.setAll(array, i -> i); // The array becomes { 0, 1, 2, 3, 4 }.

Живой на Ideone

Отдельная декларация и инициализация массивов

Значение индекса для элемента массива должно быть целым числом (0, 1, 2, 3, 4, …) и меньше длины массива (индексы основаны на нуле). В противном случае будет выбрано исключение ArrayIndexOutOfBoundsException :

int[] array9;             // Array declaration - uninitialized
array9 = new int[3];      // Initialize array  - { 0, 0, 0 }
array9[0] = 10;           // Set index 0 value - { 10, 0, 0 }
array9[1] = 20;           // Set index 1 value - { 10, 20, 0 }
array9[2] = 30;           // Set index 2 value - { 10, 20, 30 }

Массивы не могут быть повторно инициализированы синтаксисом ярлыка инициализатора массива

Невозможно повторно инициализировать массив с помощью синтаксиса ярлыков с инициализатором массива, поскольку инициализатор массива может быть указан только в объявлении поля или объявлении локальной переменной или как часть выражения создания массива.

Тем не менее, можно создать новый массив и назначить его переменной, используемой для ссылки на старый массив. Хотя это приводит к тому, что массив, на который ссылается эта переменная, повторно инициализируется, содержимое переменной представляет собой совершенно новый массив. Для этого new оператор может использоваться с инициализатором массива и назначается переменной массива:

// First initialization of array
int[] array = new int[] { 1, 2, 3 };

// Prints "1 2 3 ".
for (int i : array) {
    System.out.print(i + " ");
}

// Re-initializes array to a new int[] array.
array = new int[] { 4, 5, 6 };

// Prints "4 5 6 ".
for (int i : array) {
    System.out.print(i + " ");
}

array = { 1, 2, 3, 4 }; // Compile-time error! Can't re-initialize an array via shortcut 
                        // syntax with array initializer.

Живой на Ideone

Создание массива из коллекции

Два метода в java.util.Collection создают массив из коллекции:

  • Object[] toArray()

  • <T> T[] toArray(T[] a)

Object[] toArray() может использоваться следующим образом:

Java SE 5

Set<String> set = new HashSet<String>();
set.add("red");
set.add("blue");

// although set is a Set<String>, toArray() returns an Object[] not a String[]
Object[] objectArray = set.toArray();

<T> T[] toArray(T[] a) можно использовать следующим образом:

Java SE 5

Set<String> set = new HashSet<String>();
set.add("red");
set.add("blue");

// The array does not need to be created up front with the correct size.
// Only the array type matters. (If the size is wrong, a new array will
// be created with the same type.)
String[] stringArray = set.toArray(new String[0]);  

// If you supply an array of the same size as collection or bigger, it
// will be populated with collection values and returned (new array
// won't be allocated)
String[] stringArray2 = set.toArray(new String[set.size()]);


Разница между ними больше, чем просто отсутствие нетипизированных или типизированных результатов. Их производительность может также отличаться (подробности см. В этом разделе анализа эффективности ):

  • Object[] toArray() использует векторизованную arraycopy , которая намного быстрее, чем проверенная arraycopy используемая в T[] toArray(T[] a) .
  • T[] toArray(new T[non-zero-size]) должен обнулить массив во время выполнения, а T[] toArray(new T[0]) . Такое избегание заставляет последнего звонить быстрее первого. Подробный анализ здесь: Массивы Мудрости Древних .

Java SE 8

Начиная с Java SE 8+, где была введена концепция Stream , можно использовать Stream созданный сборкой, для создания нового массива с использованием метода Stream.toArray .

String[] strings = list.stream().toArray(String[]::new);

Примеры, взятые из двух ответов ( 1 , 2 ) на преобразование ‘ArrayList в’ String [] ‘в Java на переполнение стека.

Массивы для строки

Java SE 5

Начиная с Java 1.5 вы можете получить представление String содержимого указанного массива без итерации по каждому элементу. Просто используйте Arrays.toString(Object[]) или Arrays.deepToString(Object[]) для многомерных массивов:

int[] arr = {1, 2, 3, 4, 5};
System.out.println(Arrays.toString(arr));      // [1, 2, 3, 4, 5]

int[][] arr = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};
System.out.println(Arrays.deepToString(arr));  // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Arrays.toString() использует метод Object.toString() для получения значений String каждого элемента в массиве, помимо массива примитивного типа, его можно использовать для всех типов массивов. Например:

public class Cat { /* implicitly extends Object */
    @Override
    public String toString() {
      return "CAT!";
    }
}

Cat[] arr = { new Cat(), new Cat() };
System.out.println(Arrays.toString(arr));        // [CAT!, CAT!]

Если для класса не существует переопределенного toString() , тогда будет использоваться унаследованная toString() из Object . Обычно выход не очень полезен, например:

public class Dog {
    /* implicitly extends Object */
}

Dog[] arr = { new Dog() };
System.out.println(Arrays.toString(arr));        // [Do[email protected]]

Создание списка из массива

Метод Arrays.asList() может использоваться для возврата List фиксированного размера, содержащего элементы данного массива. Полученный List будет иметь тот же тип параметра, что и базовый тип массива.

String[] stringArray = {"foo", "bar", "baz"};
List<String> stringList = Arrays.asList(stringArray);

Примечание . Этот список поддерживается ( представлением ) исходного массива, что означает, что любые изменения в списке изменят массив и наоборот. Однако изменения в списке, который изменит его размер (и, следовательно, длину массива), вызовут исключение.

Чтобы создать копию списка, используйте конструктор java.util.ArrayList взяв Collection в качестве аргумента:

Java SE 5

String[] stringArray = {"foo", "bar", "baz"};
List<String> stringList = new ArrayList<String>(Arrays.asList(stringArray));

Java SE 7

В Java SE 7 и более поздних версиях может использоваться пара угловых скобок <> (пустой набор аргументов типа), который называется Diamond . Компилятор может определить аргументы типа из контекста. Это означает, что информация о типе может быть опущена при вызове конструктора ArrayList и будет автоматически выведена во время компиляции. Это называется Type Inference, который является частью Java Generics .

// Using Arrays.asList()

String[] stringArray = {"foo", "bar", "baz"};
List<String> stringList = new ArrayList<>(Arrays.asList(stringArray));

// Using ArrayList.addAll()

String[] stringArray = {"foo", "bar", "baz"};
ArrayList<String> list = new ArrayList<>();
list.addAll(Arrays.asList(stringArray));

// Using Collections.addAll()

String[] stringArray = {"foo", "bar", "baz"};
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, stringArray);

Точка стоит отметить о Даймонд , что она не может быть использована с классами Anonymous .

Java SE 8

// Using Streams

int[] ints = {1, 2, 3};
List<Integer> list = Arrays.stream(ints).boxed().collect(Collectors.toList());

String[] stringArray = {"foo", "bar", "baz"};
List<Object> list = Arrays.stream(stringArray).collect(Collectors.toList());


Важные замечания, связанные с использованием метода Arrays.asList ()

  • Этот метод возвращает List , который является экземпляром Arrays$ArrayList (статический внутренний класс Arrays ), а не java.util.ArrayList . Полученный List имеет фиксированный размер. Это означает, что добавление или удаление элементов не поддерживается и будет UnsupportedOperationException исключение UnsupportedOperationException :

    stringList.add("something"); // throws java.lang.UnsupportedOperationException
    
  • Новый List можно создать, передав List с поддержкой массива в конструктор нового List . Это создает новую копию данных, которая имеет изменяемый размер и не поддерживается исходным массивом:

    List<String> modifiableList = new ArrayList<>(Arrays.asList("foo", "bar"));
    
  • Вызов <T> List<T> asList(T... a) в примитивном массиве, такой как int[] , приведет к созданию List<int[]> , единственным элементом которого является исходный примитивный массив вместо фактических элементов исходного массива.

    Причиной такого поведения является то, что примитивные типы не могут использоваться вместо параметров типового типа, поэтому весь примитивный массив заменяет параметр типового типа в этом случае. Чтобы преобразовать примитивный массив в List , прежде всего, преобразовать примитивный массив в массив соответствующего типа-оболочки (т. Arrays.asList в Integer[] вместо int[] ).

    Поэтому это будет напечатать false :

    int[] arr = {1, 2, 3};      // primitive array of int
    System.out.println(Arrays.asList(arr).contains(1));
    

    Посмотреть демо

    С другой стороны, это будет true :

    Integer[] arr = {1, 2, 3};  // object array of Integer (wrapper for int)
    System.out.println(Arrays.asList(arr).contains(1));
    

    Посмотреть демо

    Это также напечатает true , потому что массив будет интерпретироваться как Integer[] ):

    System.out.println(Arrays.asList(1,2,3).contains(1));
    

    Посмотреть демо

Многомерные и зубчатые массивы

Можно определить массив с более чем одним измерением. Вместо того, чтобы получать доступ, предоставляя единый индекс, доступ к многомерному массиву можно получить, указав индекс для каждого измерения.

Объявление многомерного массива можно сделать, добавив [] для каждого измерения к регулярному объявлению массива. Например, чтобы создать двумерный массив int , добавьте еще один набор скобок в объявление, например int[][] . Это продолжается для 3-мерных массивов ( int[][][] ) и т. Д.


Чтобы определить двумерный массив с тремя строками и тремя столбцами:

int rows = 3;
int columns = 3;
int[][] table = new int[rows][columns];

Массив можно индексировать и присваивать ему значения с помощью этой конструкции. Обратите внимание, что неназначенные значения являются значениями по умолчанию для типа массива, в этом случае 0 для int .

table[0][0] = 0;
table[0][1] = 1;
table[0][2] = 2;

Также возможно одновременное создание измерений и даже создание непрямоугольных массивов. Они чаще всего называются зубчатыми массивами .

int[][] nonRect = new int[4][];

Важно отметить, что, хотя можно определить любой размер массива с зубчатым контуром, должен быть определен предшествующий уровень.

// valid
String[][] employeeGraph = new String[30][];

// invalid
int[][] unshapenMatrix = new int[][10];

// also invalid
int[][][] misshapenGrid = new int[100][][10];

Как многомерные массивы представлены в Java

Представление многомерного массива в java

Источник изображения: http://math.hws.edu/eck/cs124/javanotes3/c8/s5.html


Литературная инициализация массива Jagged

Многомерные массивы и зубчатые массивы также могут быть инициализированы литеральным выражением. Следующее объявляет и заполняет массив 2×3 int :

int[][] table = {
    {1, 2, 3},
    {4, 5, 6}
};

Примечание : Subarrays с зубцами также может быть null . Например, следующий код объявляет и заполняет двумерный массив int чей первый подмассив имеет значение null , второй субарейр имеет нулевую длину, третий подмассив имеет одну длину, а последний подмассива представляет собой массив из двух длин:

int[][] table = {
    null,
    {},
    {1},
    {1,2}
};

Для многомерного массива можно выделить массивы измерения нижнего уровня по их индексам:

int[][][] arr = new int[3][3][3];
int[][] arr1 = arr[0]; // get first 3x3-dimensional array from arr
int[] arr2 = arr1[0]; // get first 3-dimensional array from arr1
int[] arr3 = arr[0]; // error: cannot convert from int[][] to int[]

ArrayIndexOutOfBoundsException

ArrayIndexOutOfBoundsException генерируется при доступе к несуществующему индексу массива.

Массивы индексируются с нулевым индексом, поэтому индекс первого элемента равен 0 а индекс последнего элемента — array.length - 1 массива минус 1 (т. array.length - 1 ).

Поэтому любой запрос элемента массива индексом i должен удовлетворять условию 0 <= i < array.length , иначе будет 0 <= i < array.length ArrayIndexOutOfBoundsException .


Следующий код — простой пример, в котором генерируется ArrayIndexOutOfBoundsException .

String[] people = new String[] { "Carol", "Andy" };

// An array will be created:
// people[0]: "Carol"
// people[1]: "Andy"

// Notice: no item on index 2. Trying to access it triggers the exception:
System.out.println(people[2]);  // throws an ArrayIndexOutOfBoundsException.

Выход:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 2
    at your.package.path.method(YourClass.java:15)

Обратите внимание, что незаконный индекс, к которому обращаются, также включен в исключение ( 2 в примере); эта информация может быть полезна, чтобы найти причину исключения.


Чтобы этого избежать, просто проверьте, что индекс находится в пределах массива:

int index = 2;
if (index >= 0 && index < people.length) {
    System.out.println(people[index]);
} 

Получение длины массива

Массивы — это объекты, которые обеспечивают пространство для хранения до размера элементов указанного типа. Размер массива не может быть изменен после создания массива.

int[] arr1 = new int[0];
int[] arr2 = new int[2];
int[] arr3 = new int[]{1, 2, 3, 4};
int[] arr4 = {1, 2, 3, 4, 5, 6, 7};

int len1 = arr1.length; // 0
int len2 = arr2.length; // 2
int len3 = arr3.length; // 4
int len4 = arr4.length; // 7

Поле length в массиве сохраняет размер массива. Это final поле и не может быть изменено.

Этот код показывает разницу между length массива и количеством объектов, хранящихся в массиве.

public static void main(String[] args) {
    Integer arr[] = new Integer[] {1,2,3,null,5,null,7,null,null,null,11,null,13};

    int arrayLength = arr.length;
    int nonEmptyElementsCount = 0;

    for (int i=0; i<arrayLength; i++) {
        Integer arrElt = arr[i];
        if (arrElt != null) {
            nonEmptyElementsCount++;
        }
    }

    System.out.println("Array 'arr' has a length of "+arrayLength+"n"
                            + "and it contains "+nonEmptyElementsCount+" non-empty values");
}

Результат:

Array 'arr' has a length of 13
and it contains 7 non-empty values

Сравнение массивов для равенства

Типы массивов наследуют реализации equals()hashCode() ) из java.lang.Object , поэтому equals() возвращает true только при сравнении с тем же самым объектом массива. Чтобы сравнить массивы для равенства на основе их значений, используйте java.util.Arrays.equals , который перегружен для всех типов массивов.

int[] a = new int[]{1, 2, 3};
int[] b = new int[]{1, 2, 3};
System.out.println(a.equals(b)); //prints "false" because a and b refer to different objects
System.out.println(Arrays.equals(a, b)); //prints "true" because the elements of a and b have the same values

Когда тип элемента является ссылочным типом, Arrays.equals() вызывает equals() для элементов массива для определения равенства. В частности, если тип элемента сам по себе является типом массива, будет использоваться сравнение идентичности. Чтобы сравнить многомерные массивы для равенства, используйте Arrays.deepEquals() как Arrays.deepEquals() ниже:

int a[] = { 1, 2, 3 };
int b[] = { 1, 2, 3 };

Object[] aObject = { a }; // aObject contains one element
Object[] bObject = { b }; // bObject contains one element

System.out.println(Arrays.equals(aObject, bObject)); // false
System.out.println(Arrays.deepEquals(aObject, bObject));// true

Поскольку наборы и карты используют equals() и hashCode() , массивы обычно не полезны в качестве заданных элементов или клавиш карты. Либо оберните их в класс-помощник, который реализует equals() и hashCode() в терминах элементов массива или преобразует их в экземпляры List и сохраняет списки.

Массивы для потока

Java SE 8

Преобразование массива объектов в Stream :

String[] arr = new String[] {"str1", "str2", "str3"};
Stream<String> stream = Arrays.stream(arr);

Преобразование массива примитивов в Stream с использованием Arrays.stream() преобразует массив в примитивную специализацию Stream:

int[] intArr = {1, 2, 3};
IntStream intStream = Arrays.stream(intArr);

Вы также можете ограничить Stream для диапазона элементов в массиве. Начальный индекс является включительным, а конечный индекс — исключительным:

int[] values = {1, 2, 3, 4};
IntStream intStream = Arrays.stream(values, 2, 4);

В классе Stream появляется метод, аналогичный Arrays.stream() : Stream.of() . Разница в том, что Stream.of() использует параметр varargs, поэтому вы можете написать что-то вроде:

Stream<Integer> intStream = Stream.of(1, 2, 3);
Stream<String> stringStream = Stream.of("1", "2", "3");
Stream<Double> doubleStream = Stream.of(new Double[]{1.0, 2.0});

Итерация по массивам

Вы можете перебирать массивы либо с помощью расширенного цикла for (aka foreach), либо с использованием индексов массива:

int[] array = new int[10];

// using indices: read and write
for (int i = 0; i < array.length; i++) {
    array[i] = i;
}

Java SE 5

// extended for: read only
for (int e : array) {
    System.out.println(e);
}

Здесь стоит отметить, что нет прямого способа использовать Iterator в массиве, но через библиотеку Arrays его можно легко преобразовать в список, чтобы получить объект Iterable .

Для массивов в коробке используйте Arrays.asList :

Integer[] boxed = {1, 2, 3};
Iterable<Integer> boxedIt = Arrays.asList(boxed); // list-backed iterable
Iterator<Integer> fromBoxed1 = boxedIt.iterator();

Для примитивных массивов (с использованием java 8) используйте потоки (в частности, в этом примере — Arrays.stream -> IntStream ):

int[] primitives = {1, 2, 3};
IntStream primitiveStream = Arrays.stream(primitives); // list-backed iterable
PrimitiveIterator.OfInt fromPrimitive1 = primitiveStream.iterator();

Если вы не можете использовать потоки (без java 8), вы можете использовать goava- библиотеку guava :

Iterable<Integer> fromPrimitive2 = Ints.asList(primitives);

В двумерных массивах или более, обе методики могут быть использованы несколько более сложным образом.

Пример:

int[][] array = new int[10][10];

for (int indexOuter = 0; indexOuter < array.length; indexOuter++) {
    for (int indexInner = 0; indexInner < array[indexOuter].length; indexInner++) {
        array[indexOuter][indexInner] = indexOuter + indexInner;
    }
}

Java SE 5

for (int[] numbers : array) {
    for (int value : numbers) {
        System.out.println(value);
    }
}


Невозможно установить Array на любое неравномерное значение без использования цикла, основанного на индексе.

Конечно, вы также можете использовать while или do-while циклы при итерации с использованием индексов.

Одно примечание: при использовании индексов массива убедитесь, что индекс находится между 0 и array.length - 1 (оба array.length - 1 ). Не делайте жестко закодированные предположения относительно длины массива, иначе вы можете сломать свой код, если длина массива изменится, но ваши жестко закодированные значения не будут.

Пример:

int[] numbers = {1, 2, 3, 4};

public void incrementNumbers() {
    // DO THIS :
    for (int i = 0; i < numbers.length; i++) {
        numbers[i] += 1; //or this: numbers[i] = numbers[i] + 1; or numbers[i]++;      
    }
 
    // DON'T DO THIS :
    for (int i = 0; i < 4; i++) {
        numbers[i] += 1;
    }
}

Это также лучше, если вы не используете причудливые вычисления для получения индекса, но используете индекс для итерации, и если вам нужны разные значения, подсчитайте их.

Пример:

public void fillArrayWithDoubleIndex(int[] array) {
    // DO THIS :
    for (int i = 0; i < array.length; i++) {
        array[i] = i * 2;
    }
 
    // DON'T DO THIS :
    int doubleLength = array.length * 2;
    for (int i = 0; i < doubleLength; i += 2) {
        array[i / 2] = i;
    }
}

Доступ к массивам в обратном порядке

int[] array = {0, 1, 1, 2, 3, 5, 8, 13};
for (int i = array.length - 1; i >= 0; i--) {
   System.out.println(array[i]);
}

Использование временных массивов для уменьшения повторения кода

Итерация по временному массиву вместо повторения кода может сделать ваш код более чистым. Он может использоваться, когда одна и та же операция выполняется для нескольких переменных.

// we want to print out all of these
String name = "Margaret";
int eyeCount = 16;
double height = 50.2;
int legs = 9;
int arms = 5;


// copy-paste approach:
System.out.println(name);
System.out.println(eyeCount);
System.out.println(height);
System.out.println(legs);
System.out.println(arms);


// temporary array approach:
for(Object attribute : new Object[]{name, eyeCount, height, legs, arms})
    System.out.println(attribute);

// using only numbers
for(double number : new double[]{eyeCount, legs, arms, height})
    System.out.println(Math.sqrt(number));

Имейте в виду, что этот код не должен использоваться в критически важных средах, поскольку массив создается каждый раз, когда вводится цикл, и что примитивные переменные будут скопированы в массив и, следовательно, не могут быть изменены.

Копирование массивов

Java предоставляет несколько способов копирования массива.

для цикла

int[] a = { 4, 1, 3, 2 };
int[] b = new int[a.length]; 
for (int i = 0; i < a.length; i++) {
    b[i] = a[i];
}

Обратите внимание, что использование этой опции с массивом Object вместо примитивного массива будет заполнять копию ссылкой на исходное содержимое вместо его копии.

Object.clone ()

Поскольку массивы Object в Java, вы можете использовать Object.clone() .

int[] a = { 4, 1, 3, 2 };
int[] b = a.clone(); // [4, 1, 3, 2]

Обратите внимание, что метод Object.clone для массива выполняет мелкую копию , то есть возвращает ссылку на новый массив, который ссылается на те же элементы, что и исходный массив.


Arrays.copyOf ()

java.util.Arrays предоставляет простой способ выполнить копию массива в другой. Вот основное использование:

int[] a = {4, 1, 3, 2};
int[] b = Arrays.copyOf(a, a.length); // [4, 1, 3, 2]

Обратите внимание, что Arrays.copyOf также обеспечивает перегрузку, которая позволяет вам изменять тип массива:

Double[] doubles = { 1.0, 2.0, 3.0 };
Number[] numbers = Arrays.copyOf(doubles, doubles.length, Number[].class);

System.arraycopy ()

public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) Копирует массив из указанного исходного массива, начиная с указанной позиции, в указанную позицию целевого массива.

Ниже приведен пример использования

int[] a = { 4, 1, 3, 2 };
int[] b = new int[a.length];
System.arraycopy(a, 0, b, 0, a.length); // [4, 1, 3, 2]

Arrays.copyOfRange ()

В основном используется для копирования части массива, вы также можете использовать его для копирования целого массива в другой, как показано ниже:

int[] a = { 4, 1, 3, 2 };
int[] b = Arrays.copyOfRange(a, 0, a.length); // [4, 1, 3, 2]

Литейные массивы

Массивы — это объекты, но их тип определяется типом содержащихся объектов. Поэтому нельзя просто отбрасывать A[] в T[] , но каждый член A[] конкретного A[] должен быть переведен в объект T Общий пример:

public static <T, A> T[] castArray(T[] target, A[] array) {
    for (int i = 0; i < array.length; i++) {
        target[i] = (T) array[i];
    }
    return target;
}

Таким образом, с учетом массива A[] :

T[] target = new T[array.Length];
target = castArray(target, array);

Java SE предоставляет для этой цели метод Arrays.copyOf(original, newLength, newType) :

Double[] doubles = { 1.0, 2.0, 3.0 };
Number[] numbers = Arrays.copyOf(doubles, doubles.length, Number[].class);

Удалить элемент из массива

Java не предоставляет прямой метод в java.util.Arrays для удаления элемента из массива. Для его выполнения вы можете скопировать исходный массив в новый, не удаляя элемент или преобразовывая его в другую структуру, позволяющую удалить.

Использование ArrayList

Вы можете преобразовать массив в java.util.List , удалить элемент и преобразовать список обратно в массив следующим образом:

String[] array = new String[]{"foo", "bar", "baz"};

List<String> list = new ArrayList<>(Arrays.asList(array));
list.remove("foo");

// Creates a new array with the same size as the list and copies the list
// elements to it.
array = list.toArray(new String[list.size()]);

System.out.println(Arrays.toString(array)); //[bar, baz]

Использование System.arraycopy

System.arraycopy() можно использовать для создания копии исходного массива и удаления System.arraycopy() элемента. Ниже пример:

int[] array = new int[] { 1, 2, 3, 4 }; // Original array.
int[] result = new int[array.length - 1]; // Array which will contain the result.
int index = 1; // Remove the value "2".

// Copy the elements at the left of the index.
System.arraycopy(array, 0, result, 0, index);
// Copy the elements at the right of the index.
System.arraycopy(array, index + 1, result, index, array.length - index - 1);

System.out.println(Arrays.toString(result)); //[1, 3, 4]

Использование Apache Commons Lang

Чтобы легко удалить элемент, вы можете использовать библиотеку Apache Commons Lang и особенно статический метод removeElement() класса ArrayUtils . Ниже пример:

int[] array = new int[]{1,2,3,4};
array = ArrayUtils.removeElement(array, 2); //remove first occurrence of 2
System.out.println(Arrays.toString(array)); //[1, 3, 4]

Ковариантность массива

Объектные массивы являются ковариантными, что означает, что так же, как Integer является подклассом Number , Integer[] является подклассом Number[] . Это может показаться интуитивным, но может привести к неожиданному поведению:

Integer[] integerArray = {1, 2, 3};
Number[] numberArray = integerArray;  // valid
Number firstElement = numberArray[0]; // valid
numberArray[0] = 4L;                  // throws ArrayStoreException at runtime

Хотя Integer[] является подклассом Number[] , он может содержать только Integer s, и попытка назначить элемент Long выдает исключение во время выполнения.

Обратите внимание, что это поведение уникально для массивов, и его можно избежать, используя вместо этого общий List :

List<Integer> integerList = Arrays.asList(1, 2, 3);
//List<Number> numberList = integerList;  // compile error
List<? extends Number> numberList = integerList;
Number firstElement = numberList.get(0);
//numberList.set(0, 4L);                  // compile error

Для всех элементов массива не обязательно использовать один и тот же тип, если они являются подклассом типа массива:

interface I {}

class A implements I {}
class B implements I {}
class C implements I {}

I[] array10 = new I[] { new A(), new B(), new C() }; // Create an array with new
                                                     // operator and array initializer.

I[] array11 = { new A(), new B(), new C() };         // Shortcut syntax with array
                                                     // initializer.

I[] array12 = new I[3];                              // { null, null, null }

I[] array13 = new A[] { new A(), new A() };          // Works because A implements I.

Object[] array14 = new Object[] { "Hello, World!", 3.14159, 42 }; // Create an array with
                                                     // new operator and array initializer.

Object[] array15 = { new A(), 64, "My String" };     // Shortcut syntax 
                                                     // with array initializer.

Как изменить размер массива?

Простой ответ заключается в том, что вы не можете этого сделать. После создания массива его размер не может быть изменен. Вместо этого массив может быть только «изменен», создав новый массив с соответствующим размером и скопировав элементы из существующего массива в новый.

String[] listOfCities = new String[3];   // array created with size 3.
listOfCities[0] = "New York";
listOfCities[1] = "London";
listOfCities[2] = "Berlin";

Предположим (например), что новый элемент необходимо добавить в массив listOfCities как указано выше. Для этого вам необходимо:

  1. создайте новый массив размером 4,
  2. скопируйте существующие 3 элемента старого массива в новый массив при смещениях 0, 1 и 2 и
  3. добавьте новый элемент в новый массив со смещением 3.

Существуют различные способы сделать это. До появления Java 6 наиболее кратким способом было:

String[] newArray = new String[listOfCities.length + 1];
System.arraycopy(listOfCities, 0, newArray, 0, listOfCities.length);
newArray[listOfCities.length] = "Sydney";

Начиная с Java 6 методы Arrays.copyOf и Arrays.copyOfRange могут сделать это проще:

String[] newArray = Arrays.copyOf(listOfCities, listOfCities.length + 1);
newArray[listOfCities.length] = "Sydney";

Для других способов копирования массива см. Следующий пример. Имейте в виду, что при изменении размера вам понадобится копия массива с другой длиной до оригинала.

  • Копирование массивов

Лучшие альтернативы изменению размера массива

Существуют два основных недостатка с изменением размера массива, как описано выше:

  • Это неэффективно. Создание массива больше (или меньше) включает в себя копирование многих или всех существующих элементов массива и выделение нового объекта массива. Чем больше массив, тем он дороже.
  • Вы должны иметь возможность обновлять любые «живые» переменные, содержащие ссылки на старый массив.

Один из вариантов — создать массив с достаточно большим размером для начала. Это возможно только в том случае, если вы можете точно определить этот размер до выделения массива . Если вы не можете этого сделать, возникает проблема изменения размера массива.

Другой альтернативой является использование класса структуры данных, предоставляемого библиотекой классов Java SE или сторонней библиотекой. Например, структура «коллекций» Java SE предоставляет ряд реализаций API-интерфейсов List , Set и Map с различными свойствами среды выполнения. Класс ArrayList ближе всего к характеристикам производительности простого массива (например, O (N) lookup, O (1) get и set, O (N) случайная вставка и удаление), обеспечивая при этом более эффективное изменение размера без проблемы с эталонным обновлением.

(Эффективность изменения размера для ArrayList исходит из стратегии удвоения размера массива поддержки при каждом изменении размера. Для типичного варианта использования это означает, что вы иногда изменяете размер. Когда вы амортизируете за весь срок службы списка, стоимость изменения размера для каждой вставки O(1) . При изменении размера простого массива может быть использована одна и та же стратегия.)

Поиск элемента в массиве

Существует множество способов найти местоположение значения в массиве. В приведенных ниже примерах показано, что массив является одним из следующих:

  String[] strings = new String[] { "A", "B", "C" };
  int[] ints = new int[] { 1, 2, 3, 4 };

Кроме того, каждый устанавливает index или index2 либо индексу требуемого элемента, либо -1 если элемент отсутствует.

Использование Arrays.binarySearch (только для отсортированных массивов)

  int index = Arrays.binarySearch(strings, "A");
  int index2 = Arrays.binarySearch(ints, 1);

Использование массива Arrays.asList (только для не примитивных массивов)

  int index = Arrays.asList(strings).indexOf("A");
  int index2 = Arrays.asList(ints).indexOf(1);  // compilation error

Использование Stream

Java SE 8

  int index = IntStream.range(0, strings.length)
                       .filter(i -> "A".equals(strings[i]))
                       .findFirst()
                       .orElse(-1); // If not present, gives us -1.
  // Similar for an array of primitives

Линейный поиск с использованием цикла

  int index = -1;
  for (int i = 0; i < array.length; i++) {
      if ("A".equals(array[i])) {
          index = i;
          break;
      }            
  }
  // Similar for an array of primitives

Линейный поиск с использованием сторонних библиотек, таких как org.apache.commons

  int index = org.apache.commons.lang3.ArrayUtils.contains(strings, "A");
  int index2 = org.apache.commons.lang3.ArrayUtils.contains(ints, 1);

Примечание. Использование прямого линейного поиска более эффективно, чем перенос в список.

Тестирование, если массив содержит элемент

Вышеприведенные примеры могут быть адаптированы для проверки того, содержит ли массив элемент, просто проверяя, вычисляется ли вычисляемый индекс больше или равен нулю.

Кроме того, есть также несколько более сжатых вариантов:

boolean isPresent = Arrays.asList(strings).contains("A");

Java SE 8

boolean isPresent = Stream<String>.of(strings).anyMatch(x -> "A".equals(x));

boolean isPresent = false;
for (String s : strings) {
    if ("A".equals(s)) {
        isPresent = true;
        break;
    }
}

boolean isPresent = org.apache.commons.lang3.ArrayUtils.contains(ints, 4);

Сортировка массивов

Сортировка массивов можно легко сделать с Массивы апи.

import java.util.Arrays;

// creating an array with integers
int[] array = {7, 4, 2, 1, 19};
// this is the sorting part just one function ready to be used
Arrays.sort(array);
// prints [1, 2, 4, 7, 19]
System.out.println(Arrays.toString(array));

Сортировка массивов строк:

String — это не числовые данные, она определяет ее собственный порядок, который называется лексикографическим порядком, также известным как алфавитный порядок. Когда вы сортируете массив String с помощью метода sort() , он сортирует массив в естественный порядок, определенный интерфейсом Comparable, как показано ниже:

Увеличение порядка

String[] names = {"John", "Steve", "Shane", "Adam", "Ben"};
System.out.println("String array before sorting : " + Arrays.toString(names));
Arrays.sort(names); 
System.out.println("String array after sorting in ascending order : " + Arrays.toString(names));

Выход:

String array before sorting : [John, Steve, Shane, Adam, Ben]    
String array after sorting in ascending order : [Adam, Ben, John, Shane, Steve]

Уменьшение порядка

Arrays.sort(names, 0, names.length, Collections.reverseOrder());
System.out.println("String array after sorting in descending order : " + Arrays.toString(names));

Выход:

String array after sorting in descending order : [Steve, Shane, John, Ben, Adam]

Сортировка массива объектов

Чтобы отсортировать массив объектов, все элементы должны реализовать интерфейс Comparable или Comparator для определения порядка сортировки.

Мы можем использовать любой метод sort(Object[]) для сортировки массива объектов в его естественном порядке, но вы должны убедиться, что все элементы в массиве должны реализовать Comparable .

Кроме того, они должны быть взаимно сопоставимыми, например, e1.compareTo(e2) не должен бросать ClassCastException для любых элементов e1 и e2 в массиве. В качестве альтернативы вы можете отсортировать массив объектов в пользовательском порядке, используя метод sort(T[], Comparator) как показано в следующем примере.

// How to Sort Object Array in Java using Comparator and Comparable
Course[] courses = new Course[4];
courses[0] = new Course(101, "Java", 200);
courses[1] = new Course(201, "Ruby", 300);
courses[2] = new Course(301, "Python", 400);
courses[3] = new Course(401, "Scala", 500);
       
System.out.println("Object array before sorting : " + Arrays.toString(courses));
       
Arrays.sort(courses);
System.out.println("Object array after sorting in natural order : " + Arrays.toString(courses));
       
Arrays.sort(courses, new Course.PriceComparator());
System.out.println("Object array after sorting by price : " + Arrays.toString(courses));
       
Arrays.sort(courses, new Course.NameComparator());
System.out.println("Object array after sorting by name : " + Arrays.toString(courses));

Выход:

Object array before sorting : [#101 [email protected] , #201 [email protected] , #301 [email protected] , #401 [email protected] ]    
Object array after sorting in natural order : [#101 [email protected] , #201 [email protected] , #301 [email protected] , #401 [email protected] ]    
Object array after sorting by price : [#101 [email protected] , #201 [email protected] , #301 [email protected] , #401 [email protected] ]    
Object array after sorting by name : [#101 [email protected] , #301 [email protected] , #201 [email protected] , #401 [email protected] ]

Преобразование массивов между примитивами и коробочными типами

Иногда требуется преобразование примитивных типов в бокс- типы.

Для преобразования массива можно использовать потоки (в Java 8 и выше):

Java SE 8

int[] primitiveArray = {1, 2, 3, 4};
Integer[] boxedArray = 
    Arrays.stream(primitiveArray).boxed().toArray(Integer[]::new);

С более низкими версиями это может быть путем итерации примитивного массива и явного копирования его в массив в штучной упаковке:

Java SE 8

int[] primitiveArray = {1, 2, 3, 4};
Integer[] boxedArray = new Integer[primitiveArray.length];
for (int i = 0; i < primitiveArray.length; ++i) {
    boxedArray[i] = primitiveArray[i]; // Each element is autoboxed here
}

Точно так же массив в коробке может быть преобразован в массив его примитивной копии:

Java SE 8

Integer[] boxedArray = {1, 2, 3, 4};
int[] primitiveArray = 
    Arrays.stream(boxedArray).mapToInt(Integer::intValue).toArray();

Java SE 8

Integer[] boxedArray = {1, 2, 3, 4};
int[] primitiveArray = new int[boxedArray.length];
for (int i = 0; i < boxedArray.length; ++i) {
    primitiveArray[i] = boxedArray[i]; // Each element is outboxed here
}

Преобразование типов для массивов

Теперь, когда массив введен как полноценный тип данных в Java, рассмотрим, какое влияние он окажет на преобразование типов.

Ранее подробно рассматривались переходы между примитивными и обычными (не являющимися массивами) ссылочными типами. Хотя массивы являются объектными типами, их также будет полезно разделить по базовому типу на две группы – основанные на примитивном или ссылочном типе.

Имейте в виду, что переходы между массивами и примитивными типами являются запрещенными. Преобразования между массивами и другими объектными типами возможны только для класса Object и интерфейсов Cloneable и Serializable. Массив всегда можно привести к этим трем типам, обратный же переход является сужением и должен производиться явным образом по усмотрению разработчика. Таким образом, интерес представляют только переходы между разными типами массивов. Очевидно, что массив, основанный на примитивном типе, принципиально нельзя преобразовать к типу массива, основанному на ссылочном типе, и наоборот.

Пока не будем останавливаться на этом подробно, однако заметим, что преобразования между типами массивов, основанных на различных примитивных типах, невозможны ни при каких условиях.

Для ссылочных же типов такого строгого правила нет. Например, если создать экземпляр массива, основанного на типе Child, то ссылку на него можно привести к типу массива, основанного на типе Parent.

Child c[] = new Child[3];
Parent p[] = c;

Вообще, существует универсальное правило: массив, основанный на типе A, можно привести к массиву, основанному на типе B, если сам тип A приводится к типу B.

// если допустимо такое приведение:
B b = (B) new A();
// то допустимо и приведение массивов:
B b[]=(B[]) new A[3];

Применяя это правило рекурсивно, можно преобразовывать многомерные массивы. Например, массив Child[][] можно привести к Parent[][], так как их базовые типы приводимы ( Child[] к Parent[] ) также на основе этого правила (поскольку базовые типы Child и Parent приводимы в силу правил наследования).

Как обычно, расширения можно проводить неявно (как в предыдущем примере), а сужения – только явным приведением.

Вернемся к массивам, основанным на примитивном типе. Невозможность их участия в преобразованиях типов связана, конечно, с различиями между простыми и ссылочными типами данных. Поскольку элементами объектных массивов являются ссылки, они легко могут участвовать в приведении. Напротив, элементы простых типов действительно хранят числовые или булевские значения. Предположим, такое преобразование осуществимо:

// пример вызовет ошибку компиляции
byte b[]={1, 2, 3};
int i[]=b;

В таком случае, элементы b[0] и i[0] хранили бы значения разных типов. Стало быть, преобразование потребовало бы копирования с одновременным преобразованием типа всех элементов исходного массива. В результате был бы создан новый массив, элементы которого равнялись бы по значению элементам исходного массива.

Но преобразование типа не может порождать новые объекты. Такие операции должны выполняться только явным образом с применением ключевого слова new. По этой причине преобразования типов массивов, основанных на примитивных типах, запрещены.

Если же копирование элементов действительно требуется, то нужно сначала создать новый массив, а затем воспользоваться стандартной функцией System.arraycopy(), которая эффективно выполняет копирование элементов одного массива в другой.

Ошибка ArrayStoreException

Преобразование между типами массивов, основанных на ссылочных типах, может стать причиной одной довольно неочевидной ошибки.

Рассмотрим пример:

Child c[] = new Child[5];
Parent p[]=c;
p[0]=new Parent();

С точки зрения компилятора код совершенно корректен. Преобразование во второй строке допустимо. В третьей строке элементу массива типа Parent присваивается значение того же типа.

Однако при выполнении такой программы возникнет ошибка. Нельзя забывать, что преобразование не меняет объект, изменяется лишь способ доступа к нему. В свою очередь, объект всегда «помнит», от какого типа он был порожден. С учетом этих замечаний становится ясно, что в третьей строке делается попытка добавить в массив Child значение типа Parent, что некорректно.

Действительно, ведь переменная с продолжает ссылаться на этот массив, а значит, следующей строкой может быть такое обращение:

где метод onlyChildMethod() определен только в классе Child. Данное обращение совершенно корректно, а значит, недопустима ситуация, когда элемент c[0] ссылается на объект, несовместимый с Child.

Таким образом, несмотря на отсутствие ошибок компиляции, виртуальная машина при выполнении программы всегда осуществляет дополнительную проверку перед присвоением значения элементу массива. Необходимо удостовериться, что реальный массив, существующий на момент исполнения, действительно может хранить присваиваемое значение. Если это условие нарушается, то возникает ошибка, которая называется ArrayStoreException.

Может сложиться впечатление, что разобранная ситуация является надуманной,– зачем преобразовывать массив и тут же задавать для него неверное значение? Однако преобразование при присвоении значений является лишь примером. Рассмотрим объявление метода:

public void process(Parent[] p) {
   if (p!=null && p.length>0) {
      p[0]=new Parent();
   }
}

Метод выглядит абсолютно корректным, все потенциально ошибочные ситуации проверяются if -выражением. Однако следующий вызов этого метода все равно приводит к ошибке:

И это будет как раз ошибка ArrayStoreException.

Переменные типа массив и их значения

Завершим описание взаимосвязи типа переменной и типа значений, которые она может хранить.

Как обычно, массивы, основанные на простых и ссылочных типах, мы описываем раздельно.

Переменная типа массив примитивных величин может хранить значения только точно такого же типа, либо null.

Переменная типа «массив ссылочных величин» может хранить следующие значения:

  1. null ;
  2. значения точно такого же типа, что и тип переменной;
  3. все значения типа массив, основанный на типе, приводимом к базовому типу исходного массива.

Все эти утверждения непосредственно следуют из рассмотренных выше особенностей приведения типов массивов.

Еще раз напомним про исключительный класс Object. Переменные такого типа могут ссылаться на любые объекты, порожденные как от классов, так и от массивов.

Сведем все эти утверждения в таблицу.

Таблица
Табл. 9.1..
Тип переменной и тип ее значения.

Тип переменной Допустимые типы ее значения
Массив простых чисел
  • null
  • в точности совпадающий с типом переменной
Массив ссылочных значений
  • null
  • совпадающий с типом переменной
  • массивы ссылочных значений, удовлетворяющих следующему условию: если тип переменной – массив на основе типа A, то значение типа массив на основе типа B допустимо тогда и только тогда, когда B приводимо к A
Object
  • null
  • любой ссылочный, включая массивы

Понравилась статья? Поделить с друзьями:

Читайте также:

  • Как изменить тип линий у ячейки таблицы
  • Как изменить тип курсора на винде 10
  • Как изменить тип курсора visual studio
  • Как изменить тип колонки sql
  • Как изменить тип колонки postgresql

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии