Blog-Archiv

Sonntag, 20. November 2016

Framework as seen from Java, Part 1

The term "Framework" is somehow overburdened with individual and company-specific meanings. When you hear this word, it could designate ....

  • an expensive software product of a big company that solves every problem everywhere when experts of that company configure it to do so
  • a software engine that you can build houses with, but also igloos or space ships
  • a set of template sources for Copy & Paste Programming
  • a class the chief programmer implemented, to be extended by any application class instead of java.lang.Object
  • the API of a set of libraries
  • for example, the Java Development Kit
  • and also the Java Collections library

Little bit of truth in all of them?
Wikipedia states that it is ....

  • an abstraction in which software providing generic functionality can be selectively changed by additional user-written code

  • a universal, reusable software environment that provides particular functionality as part of a larger software platform to facilitate development

  • may include support programs, compilers, code libraries, tool sets, and application programming interfaces

A Framework must be something big and complex. That's the impression you get from literature. I believe that "Framework" expresses the undefined hopes and visions of software thinkers, similar to "Component". There is no precise and generally accepted definition of this term. I stick to the old Catalysis way to understand it. But they talk on UML level, and I want to give the view of a Java developer. For me, framework means Reusability by Abstraction.

The Smallest Framework Ever

"Swapping" abstracts the interchange of two elements in an ordered container. Widely used in books about software to explain generic types. Swapping is needed for example in sort-algorithms. Its responsibility:

  1. In a container (array, list, ...) you have two elements at certain indexes, and you want to exchange them, so that element 1 is where element 2 was before, and vice versa.
  2. It is not possible to return a new container, swapping must be done directly in the given container
        final Integer[] array = ....;
        final int index1 = ....;
        final int index2 = ....;

        final Integer element1  = array[index1];
        array[index1] = array[index2];
        array[index2] = element1;

The problem is that you want to implement the swapping of two elements generically once-and-only-once, and you want it strongly typed. But there is a small problem in the few lines of code to do that.

  • When you replace element 1 by element 2, you need to remember element 1, because you need to put it on the place where element 2 has been.
  • For this you need a local variable that remembers element 1, and that variable needs to have the same type as the element, else it would not be possible to set it into the strongly typed container.

So you either need to hard-code swapping for every thinkable element-type that may need such, or you have a language mechanism that allows generic types. Generic types were introduced in Java 1.5. They make it possible to use an arbitrary (generic) type T instead of a specific type Integer. In older Java-versions you could have done this using the "generic" Java super-class java.lang.Object, but this would not have been type-safe. When using Java generics, the compiler checks the type of the elements that should be in the container, and all related code working on them.

In the following, I use Java generics to implement a type-safe and though generic swap.

public class Swap<T>
{
    public void swap(T [] array, int index1, int index2)  {
        final T element1  = array[index1];
        array[index1] = array[index2];
        array[index2] = element1;
    }
}

Here is some test-code to try this out.

public class Demo
{
    public static void main(String[] args) {
        final Integer[] integerArray = new Integer[] { 0, 1, };
        System.out.println("Before array swap: "+Arrays.asList(integerArray));
        
        new Swap<Integer>().swap(integerArray, 0, 1);
        
        System.out.println("After array swap: "+Arrays.asList(integerArray));
    }
}

Output is:

Before array swap: [0, 1]
After array swap: [1, 0]

Extensions

You might ask now "Where is reusability and abstraction?". Abstraction was done by the Java generics. In case of reusability, you're right. I can not reuse these three lines of code for e.g. List. Nevertheless I can write another Swap that cares about java.util.List. It makes no sense to extend Swap, because there is nothing reusable in it.

List

import java.util.List;

public class SwapInList<T>
{
    public void swap(List<T> list, int index1, int index2)  {
        final T element1  = list.get(index1);
        list.set(index1, list.get(index2));
        list.set(index2, element1);
    }
}

Test-code:

        final List<String> stringList = new ArrayList<>();
        stringList.add("A");
        stringList.add("B");
        stringList.add("C");
        stringList.add("D");
        stringList.add("E");
        System.out.println("Before list swap: "+stringList);
        
        new SwapInList<String>().swap(stringList, 1, 3);
        
        System.out.println("After list swap: "+stringList);

Outputs:

Before list swap: [A, B, C, D, E]
After list swap: [A, D, C, B, E]

Unfortunately there is no Java interface that makes array and list interchangeable. I must access the array by indexing, and the list by calling get() and set().

Linked Set

There is another ordered Java container that may need swapping. Other than List, a Set contains no duplicates, and has no order. LinkedHashSet is different, it keeps the order in which elements were added.

Working with Set means you have no get() and set(). Again we need another implementation for swapping elements.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.util.LinkedHashSet;
import java.util.Set;

public class SwapInLinkedSet<T>
{
    public void swap(Set<T> linkedSet, int index1, int index2)  {
        if (index1 == index2)   // fast and easy: nothing to do
            return;
        
        // uncover mistakes early
        if (index1 < 0 || index2 < 0 || index1 >= linkedSet.size() || index2 >= linkedSet.size())
            throw new IllegalArgumentException("Swap-indexes out of bounds!");
        
        // retrieve elements at given indexes
        int i = 0;
        T element1 = null;
        T element2 = null;
        for (final T element : linkedSet) {
            if (i == index1)
                element1 = element;
            else if (i == index2)
                element2 = element;
            
            i++;
        }
        
        // create a clone and clear the original set
        final LinkedHashSet<T> clone = new LinkedHashSet<>(linkedSet);
        linkedSet.clear();
        
        // establish new order in original set
        i = 0;
        for (final T element : clone) {
            if (i == index1)
                linkedSet.add(element2);
            else if (i == index2)
                linkedSet.add(element1);
            else
                linkedSet.add(element);
            
            i++;
        }
    }
}

Oops, these are definitely more than three lines. Unfortunately the nature of Java Set makes it impossible to access specific indexes. That is the reason why we need two loops: first loop retrieves the elements to be swapped, second loop (over a clone) establishes the new order after having cleared the set completely.

        final Set<Float> floatList = new LinkedHashSet<>();
        floatList.add(1.0f);
        floatList.add(100.0f);
        floatList.add(10000.0f);
        System.out.println("Before linkedSet swap: "+floatList);
        
        new SwapInLinkedSet<Float>().swap(floatList, 1, 2);
        
        System.out.println("After linkedSet swap: "+floatList);

This test code outputs:

Before linkedSet swap: [1.0, 100.0, 10000.0]
After linkedSet swap: [1.0, 10000.0, 100.0]

Summary

These swaps maybe were examples for the abstraction that Java generics provide, but not for implementation abstraction, and definitely not for reusability. We had to use three different implementations for three different Java container types. Can we do better? You guessed right, I will try this in my next Blog article about frameworks as seen by Java.




Keine Kommentare: