Polymorphism: Putting It All Together

Steven J. Zeil

Last modified: Apr 13, 2024
Contents:

1 Combining Techniques within a Class

As we have seen, no one technique for polymorphism covers all the bases. In particular, for primitive types we are generally limited to overloading. Class types give us the option of working with inheritance or generics. That means that to provide a function that work with all data types, we often combine techniques. For example, if we were committed to providing insertInOrder for all types, we would wind up with something like:

import java.util.ArrayList;

public class Utilities {

    public static int insertInOrder(int value, int[] intoArray, int size) {
        int i = size;
        while(i > 0 && value < intoArray[i-1]) {
            intoArray[i] = intoArray[i-1];
            --i;
        }
        intoArray[i] = value;
        return i;
    }

    public static int insertInOrder(double value, double[] intoArray, int size) {
        int i = size;
        while(i > 0 && value < intoArray[i-1]) {
            intoArray[i] = intoArray[i-1];
            --i;
        }
        intoArray[i] = value;
        return i;
    }

    public static int insertInOrder(float value, float[] intoArray, int size) {
        int i = size;
        while(i > 0 && value < intoArray[i-1]) {
            intoArray[i] = intoArray[i-1];
            --i;
        }
        intoArray[i] = value;
        return i;
    }
    
    public static int insertInOrder(char value, char[] intoArray, int size) {
        int i = size;
        while(i > 0 && value < intoArray[i-1]) {
            intoArray[i] = intoArray[i-1];
            --i;
        }
        intoArray[i] = value;
        return i;
    }

    public static int insertInOrder(byte value, byte[] intoArray, int size) {
        int i = size;
        while(i > 0 && value < intoArray[i-1]) {
            intoArray[i] = intoArray[i-1];
            --i;
        }
        intoArray[i] = value;
        return i;
    }

    public static <T extends Comparable<T>> int insertInOrder(T value, T[] intoArray, int size) {
        int i = size;
        while(i > 0 && value.compareTo(intoArray[i-1]) < 0) {
            intoArray[i] = intoArray[i-1];
            --i;
        }
        intoArray[i] = value;
        return i;
    }
}

2 java.utils.Arrays

If you think that this is overkill, try looking at the java.util.Arrays class.

3 System.arrayCopy

Another useful utility is in java.lang.System, (the same package from which we get System.in and System.out):

public class System {
    ⋮
    /**
     *  Copies an array from the specified source array, beginning at the specified position, to the specified
     *  position of the destination array.
     * 
     * @param src the source array.
     * @param srcPos starting position in the source array.
     * @param dest the destination array.
     * @param destPos starting position in the destination data.
     * @param length the number of array elements to be copied.
     * 
     * @throws IndexOutOfBoundsException if copying would cause access of data outside array bounds.
     * @throws ArrayStoreException if an element in the src array could not be stored into the
     *         dest array because of a type mismatch.
     * @throws NullPointerException if either src or dest is null.
     */
    static void <E,T> arraycopy(E[] src, int srcPos, T[] dest, int destPos, int length) {
    ⋮
}

This is only available for class objects, not primitives.

For example, given this code

String[] input = {"abc", "def", "ghi", "jkl"};
String[] output = {"000", "001", "010", "011", "100"};
System.arraycopy(input, 1, output, 2, 2);

the array output would contain {"000", "001", "def", "ghi", "100"}.

As with many of the java.utils functions we have just discussed, this can allow you to remove a common loop pattern from your code, potentially making just that little bit easier to read.

The implementation of this is interesting, because it highlights a common mistake programmers can make when doing their own copies.

Here is a pretty straightforward approach to implementing this function:

    public static <E, T> void arraycopy(E[] src, int srcPos,
        T[] dest, int destPos, int length) {
        for (int k = 0; k < length; ++k) {
            Object toCopy = src[srcPos + k];
            dest[destPos + k] = (T) toCopy;
        }
    }

And that would work for the sample test above. But suppose that we wanted to copy a portion of one array into a different portion of itself.


 

To see why this happens, consider the copies step by step. First we copy the “b” from position 1 into position 2. Then we copy from position 2 to position 3. Position 2 used to hold a “c” but has already been overwritten with a “b”, so we wind up copying the “b” again.

To fix this, we test to detect when this kind of overlapping ranges can occur, and, if so, copy “backwards” from the higher-numbered positions towards the lower ones.

        public static <E, T> void arraycopy(E[] src, int srcPos,
            T[] dest, int destPos, int length) {
        if ((Object) src != (Object) dest || srcPos >= destPos) { ➀
            for (int k = 0; k < length; ++k) {
                Object toCopy = src[srcPos + k];
                dest[destPos + k] = (T) toCopy;
            }
        } else {
            for (int k = length - 1; k >= 0; --k) {  ➁
                Object toCopy = src[srcPos + k];
                dest[destPos + k] = (T) toCopy;
            }

        }
    }

 

As it happens, copying data from one part of an array to another is pretty common, and it’s easy to make the potential problem when the range of positions being copied from and being copied into overlap. So it’s nice to have a utility function like this that is smart enough to cope.

Because we will make frequent use of this function, it’s important to know to know its complexity.

You can run this function as an animation if you want to see it in action.

Look at the code for arraycopy. Can you say what its worst case complexity will be?

Answer