Java SE 8 Documentation
Exercises

Using Arrays

With the help of arrays, a data structure can be built from values of the same type in a way that the individual elements can be identified or referenced by an index or key. Because the type of the elements is the same, they occupy place of the same size, and because the elements of the array are stored continuously, a formula can be used to calculate the place of each element in the memory.

Let us see how this works for one-dimensional arrays, for example:

a[i] = a + i * sizeof T

where a is an array and [i] is an indexing operator, where i refers to the index (basically the number) of the element to access. The sizeof operator is capable of telling the size of an element of a given type in bytes, and finally T is the type of the elements in the array. The name of the array itself gives the start address of the array in the memory. As we could see from the formula above, the indexing starts from zero.

In the Java language, arrays are considered a special type of objects whose syntax differs from the standard syntax of objects.

  • It is possible to assign a T[] type for every T type, that is basically an array of T elements. For example:

    int[] intArray;
    char[] charArray;
    String[] stringArray;
  • Similary to objects, arrays have to be instantiated, otherwise the corresponding reference is going to be null (and if we want to use that then it will result in a NullPointerException). That is arrays are always references only. For example:

    int[] intArray = new int[5];

    On instantiation, the array elements will be initialized to the default value of the corresponding tpe. For example, objects will be set to null, int values will be set to 0, boolean values will be set to false, and so on.

    At instantiation, besides the new operator, an initializer expression is also allowed in the syntactical form { e1, e2, ... }, where e1, e2, etc. refer to each of the elements. Here are some examples of constant arrays initializers, where barr1, barr2, and barr3 are three equivalent definitsion:

    boolean[] barr1 = { true, false };
    boolean[] barr2 = new boolean[] { true, false };
    boolean[] barr3 = new boolean[2];
    barr3[0] = true; barr3[1] = false;

    Arrays can be built from array-valued expressions on the spot too:

    public static int sum(int[] arr) { /* ... */ }
    
    public static void main(String[] args) {
        int result = sum(new int[] { 1, 2, 3 });
        /* ... */
    }
  • An array always stores its own size, which can be learnt through the length attribute. Note that the size of arrays may not change once they have been instantiated. If we want to increase the size of an array, a new array instance (of a larger size) shall be created, and all the elemnts shall be copied over there.

    int intArray[] = { 1, 2, 3, 4, 5 };
    
    for (int i = 0; i < intArray.length; ++i) {
        System.out.println(intArray[i]);
    }

    With the regard to that, note that it is possible to use the "for each" syntax for traversing arrays, where each of the elements are walked in a sequential fashion, so the loop variable for indexing might be omitted.

    for (int x : intArray) {
        System.out.println(x);
    }
  • On accessing elements with a bad index — that is, an index value is used that would give a memory location that is out of the range of the piece memory occupied by the array — then an exception will be thrown by the run-time system. That is called ArrayIndexOutOfBoundsException.

There can be multi-dimensional arrays created as well. All we have to do for that is just to repeat the [] notation for each of the dimensions in the array type declaration. For instance, a two-dimensional array can be declared like as follows:

int[][] arr;

However, the first dimension has to be always specified on instantiation in that case:

int[][] arr = new int[5][];

Command-line Arguments as an Array

As we have seen before, the Java run-time system will pass the command-line arguments as an array to the entry point of the program on start. This array consists of String values, that has all the words that are delimited by whitespace character and that have been written after the name of the program when the application was launched in the command prompt.

public static void main(String[] args) {
    System.out.println("Number of command-line parameters: " + args.length);
    System.out.println("Their values are as follows:");

    for (int i = 0; i < args.length; ++i) {
        System.out.println(String.format("Parameter #%d: %s", i, args[i]));
    }
}

String Values as Arrays

In the Java language, the String object type that represents character sequences will not act as array directly. Each of the characters in the sequence can be accessed by the charAt() method, but that is not always the most convenient way to do so. That is why it is good to know that String objects might be translated to character arrays with the help of the toCharArray() method. String objects can be also created in a way where a character array has to be specified, so this conversion applies to other direction as well.

For example, replace all a characters to b in a String. Do not modify the original text but create a new one and return that one as the result.

static String replaceAllAsToBs(String input) {
    char[] contents = input.toCharArray();
    char[] result = new char[contents.length];

    for (int i = 0; i < contents.length; ++i) {
        if (contents[i] == 'a') result[i] = 'b';
        else result[i] = contents[i];
    }

    return new String(result);
}

A Utility Class for Common Operations on Arrays

Note that many array-related operations are available as part of the java.util.Arrays class. Such operations are like the translation to text (toString()), binary search (binarySearch()), or filling an array with some specified value (fill()).

For example, get the command-line arguments and send them to the standard output in ascending order as they were numbers.

public static void main(String[] args) {
    java.util.Arrays.sort(args);
    System.out.println(java.util.Arrays.toString(args));
}

Note that the solution above is not yet perfect as the command-line arguments have to be treated as numbers when the sorting is done. This could be achieved by invoking the sort() method in a different way.

public static void main(String[] args) {
    java.util.Arrays.sort(args, new Comparator<String>() {
        public int compare(String s1, String s2) {
            return (Integer.parseInt(s1) - Integer.parseInt(s2));
        }
    });
    System.out.println(java.util.Arrays.toString(args));
}

Here a so-called anonymous class is used, which is going to be discussed at a later occasion.

Let us sort the command-line arguments by the count of a characters they have.

public static void main(String[] args) {
    static int aCount(String s) {
        int result = 0;

        for (char c : s.toCharArray()) {
            if (c == 'a') result += 1;
        }

        return result;
    }

    java.util.Arrays.sort(args, new Comparator<String>() {
        public int compare(String s1, String s2) {
            return aCount(s2) - aCount(s1);
        }
    });
    System.out.println(java.util.Arrays.toString(args));
}

Comparison of Arrays

Because arrays are like objects, attention has to be paid when the == (equality) operator is used as it will not give the result that one would expect. Because array identifiers are references of objects, their contents can be only checked for equivalence with the corresponding method of the java.util.Arrays class, such as equals(), or deepEquals() in the multi-dimensional case, or we will have to implement it ourselves.

For example, the following method might be written for checking if an array of nine elements contains all the numbers between 1 and 9.

static boolean isValid(int[] input) {
    int[] reference = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    java.util.Arrays.sort(input);
    return java.util.Arrays.equals(input, reference);
}

Strings

Similary to arrays, String values are also like objects, but their syntax is not that special. Mostly the literals and the concatenation operator have been simplified for strings:

String s = "Hello" + " " + "there" + "!";

Yet again, since String values are objects, the s variable that we created above is going to hold a reference. If we did not initialize that properly, its value would have been null, and the deference of such values will lead to throwing a NullPointerException. Similarly, care must be taken on the comparison of two String objects as the == operator will only compare the references, not the values they refer to. So we shall use the equals() method in that case.

The String class has many other quite useful methods that could help in working with texts. Previously, we could have seen the charAt() and toCharArray(), but there are many additional ones.

  • Size of a String, check of emptiness.

    int length = s.length();
    boolean empty = s.isEmpty();
  • Determine if the String starts with or ends with a specific portion of text.

    boolean isExecutable = s.endsWith(".exe");
    boolean isPre = s.startsWith("pre_");
  • Occurence of a character in the text somewhere. The indexOf() method searches for the first occurence and it will return its position if there is any.

    int aIndex = s.indexOf('a');
    boolean hasExclamantion = s.contains('!')
  • Get a substring.

    String snippet = s.substring(1, 3);
  • Make a text uppercase. We can see there that String values do not ever change but a new version is created all the time.

    String upper = s.toUpperCase();
  • Split text by a delimiter.

    String[] words = s.split(" ");
  • Replace a substring, which employs regular expressions. Regular expressions is a tiny language where patterns may be specified with the help of + and * operators. Those operators are always attached to characters to describe if one or more of those characters is expected (+) or the given character may be omitted at the given position (*). In any other cases, one-to-one matching is done.

    So, the a+ pattern refers to an infinite sequence of a characters (until the input matches it) where there shall be at least one a character. That is what is used in the example below:

    public class Replace {
      public static void main(String[] args) {
        for (String s : args) {
    //    convert(s);
    //    s = s.replace("a+", "a");
          s = s.replaceAll("a+", "a");
          System.out.println(s);
        }
      }
    
      // aaaaabaaaaaacaaaaada -> abacada
      static String convert(String s) {
        String result = "";
        // Search for the first 'a' character and store all the others
        // until it is found.
        ...
        // Go until the end of the 'a' characters.
        ...
        // Repeat the previous steps until all the input parameter is
        // fully consumed.
      }
    }

Handling String Values More Efficiently

In the example on replacing sections of String values, it could be noticed that String objects come with a "persistent" implementation. This means that neither of the components will change even on concatenation but a fresh, independent String value of is constructed. Let us study this in the following example:

String string = "AAAxAAA";
string.replace('x', 'A');
System.out.println(string);         // --> "AAAxAAA"
string = string.replace('x', 'A');
System.out.println(string);         // --> "AAAAAAA"

This speciality of String values will not make an efficient implementation in terms of economic use of resources. So, in cases when the goal is to continuously update String values many times, a StringBuffer is recommended. That class has many methods that implement frequent String operations in an efficient, space-friendly manner.

StringBuffer sb = new StringBuffer("Hello");
sb.append(" World");
sb.reverse();
System.out.println(sb.toString());  // --> "dlroW olleH"
sb.reverse();
sb.setCharAt(6, '-');
System.out.println(sb.toString());  // --> "Hello-World"
sb.deleteCharAt(6);
System.out.println(sb.toString());  // --> "HelloWorld"
sb.delete(0, sb.length());
System.out.println( sb.toString()); // --> ""

It can be seen that there are conversions between the String and StringBuffer types in both directions. The append() method appends the value of the parameter to the current content of the buffer inside, the reverse() reverses the contents of the buffer, setChar() sets the character of the given index in the buffer to its second argument. The deleteChar() method deletes the character at the given index, and delete() deletes sections of the buffer within a given range. The length() method can be again used for learning the current size of the buffer.

The java.util.Scanner Class

Note that it is easier to process String values with the help of the java.util.Scanner class. It is recommended to use this class when values of given types has to be parsed from String objects, or a String value has to be split into words by whitespaces. The standard input can be also taken as a String value of infinite size, so reading data from there could be also implemented well with the class.

For example, let us read two words from the standard input. There the next() method will always read the next word until a whitespace character shows up. The hasNext() method may be used to tell if there is a new item available from the source that could be then processed. Therefore it is recommended to call that method before calling next().

public static void main(String[] args) {
    java.util.Scanner sc = new java.util.Scanner(System.in);

    while (sc.hasNext()) {
        String s = sc.next();
        System.out.println("There was a word: " + s);
    }
}

The java.util.Scanner class can even work with String values. For example, let us read numbers that are separated by spaces from a String. Note that a check is made before reading the subsequent numbers.

public static void main(String[] args) {
    java.util.Scanner sc = new java.util.Scanner("42 99 -1 0 32");

    while (sc.hasNextInt()) {
        int x = sc.nextInt();
        System.out.println("There was a number: " + x);
    }
}

Exercises

  • Create a utils.NumericArrays class that shall have the following static methods as procedures (that is, they will always change the arrays they get as parameters):

    • sum() that calculates the sum of the numbers in the array.

    • average() that calculates the average of the numbers in the array.

    • normalize() that normalizes the numbers in the array so the sum of their normalized values shall be equal to one.

    Write a main program for that class that receives the elements of the array and the operation to be made ("sum", "avg", "norm") as arguments from the command line.

  • Create a utils.Cryptor class that shall have the following static methods:

    • encode() that receives a String value (as parameter) and encodes it in a way that the method performs an XOR ("exclusive xor", that is the ^ symbol) on its every character with a constant.

    • decode() which is the inverse of the previous method, and it is capable of decoding the input String value with the previously employed constant.

  • Modify the utils.Cryptor class to implement both the encoding and the decoding with another String value as a secret key.

  • Create the utils.Sudoku class that shall have the following static methods:

    • check() that checks if a two-dimensional array of integer values of size 3 by 3 has numbers only in range from 1 to 9, and each number appears only once.

    • show() that produces a textual representation of a two-dimensional array that is formatted like a matrix.

  • Create a utils.Vector class that shall have the following static methods:

    • scalarProduct() that computes the scalar product of two arrays of floating-point numbers (as vectors).

    • vectorialProduct() that computes the vectorial product of two arrays of floating-point numbers (as vectors) by an angle between them. The result must be produced in three dimensions.

  • Create a utils.Matrix class that shall have the following static methods:

    • scalarProduct() that computes the scalar product of a matrix and a floating-point number. The matrix is assumed to be represented as a two-dimensional array, passed as a parameter.

    • add() that computes sum of two matrices, both passed as two-dimensional arrays.

    • multiply() that computes the product of two matrices, both passed as two-dimensional arrays.

  • Create a program that transforms its command-line arguments in the following ways then sends the result to the standard output:

    • Every numeric character remains unchanged.
    • Every letter is converted to lowercase.
    • Every other character is replaced with a _ (underscore) symbol.
  • Create a method that changes every word starting with a lowercase letter to an uppercase one in its String parameter. Let the result be the modified String.

Related sources
Front page
Back