Java Data Containers

Variablen

TypeGrößeVon bis
boolean1 
char2 
byte1-128...127
short2-32768...32767
int4-2147483648 ... 2147483647
long8-9223372036854775808...9223372036854775807
float41,40239846 * 10^-45 ... 3,40282347 * 10^38
double84,94065645841246544 * 10^-324 ... 1,79769131486231570 * 10^308

BigDecimal

Ein BigDecimal erzeugen

BigDecimal x=new BigDecimal("1.123456789");

Ein BigDecimal runden

BigDecimal y=x.setScale(2, BigDecimal.ROUND_HALF_UP);

(x bleibt dabei unverändert)

Mit BigDecimal teilen

BigDecimal a=new BigDecimal("1").setScale(0);
BigDecimal b=new BigDecimal("1.2345");
BigDecimal r=a.divide(b, RoundingMode.HALF_UP);

Achtung, die Genauigkeit beim Teilen zweier BigDecimal wird durch das Objekt festgelegt, dessen divide Methode aufgerufen wird. Das kann zu überraschenden Ergebnissen führen! Daher überlegen, die Genauigkeit beim Teilen anzugeben

BigDecimal r=a.divide(b, 7, RoundingMode.HALF_UP);

Gibt man beim teilen zweier BigDecimal Zahlen keine Roundsart an, und passt das Ergebnis nicht in ein BigDecimal (z.B. 1/3), gibt es eine Exception

java.lang.ArithmeticException: Non-terminating decimal expansion; noexact representable decimal result.

Deshalb beim Teilen immer eine Rundungsart angeben!

Datencontainer

Entweder für beliebige Objekte

Collection c = new LinkedList();
c.add("Hello");
c.add("World");
for ( Object x : c ) System.out.println( x );

Oder für spezielle

Collection<String> c = new LinkedList<String>();
for ( String x : c ) System.out.println( x );

Listen

ArrayList, LinkedList

Mengen

HashSet, TreeSet, LinkedHashSet

Assoziativspeicher

HashMap, TreeMap, LinkedHashMap, WeakHashMap

  • man hat ein Object O
  • und einen Key K
  • Von dem Key K gibt es ein H(K)

Die HashMap fügt im Hash dann unter H(K) alle Objekte O ein. Es kann mehrere Objekte O geben, die den gleichen Wert für H(K) haben, aber unterschiedliche Werte für K. Ist auch K gleich, wird der vorherige Eintrag überschrieben. Für die Klasse Integer gilt z.B. H(K)=K, das muss aber nicht so sein.

WeakHashMap

Der Garbage Collector in Java entsorgt automatisch alle Java Objekte, auf die niemand mehr eine Referenz hält.

Wenn man untersuchen möchte, ob ein Objekt schon entsorgt worden ist, kann man daher natürlich keine Referenz mehr auf das Objekt halten. Hier kommt die WeakHashMap ins Spiel. Objekte die als Value in ihr enthalten sind, zählen nicht als Referenz für die Garbage Collector. Sobald ein Objekt entsorgt wird, wird es auch automatisch aus der WeakHashMap entfernt. Kleines Beispiel:

private WeakHashMap<Integer, BigThing> notYetFreed;

public void doTest()
{
        notYetFreed=new WeakHashMap<Integer, BigThing>();

        for(int i=0; i<100; i++)
        {
                BigThing bt=new BigThing();
                notYetFreed.put(i, bt);
                System.out.println(notYetFreed.size());
        }

        while(notYetFreed.size()>0)
        {
                System.out.println(notYetFreed.size());
        }
}

Da die BigThing Objekte nach jedem Durchlauf der Schleife von niemandem außer der WeakHashMap referenziert werden, werden die Einträge aus der WeakHashMap schnell wieder entfernt.

Achtung: Das funktioniert nicht, wenn man die Objekte auch als Key und nicht nur als Value in der WeakHashMap verwendet. So würde es also nicht funktionieren:

private WeakHashMap< BigThing, BigThing> notYetFreed;
...
notYetFreed.put(bt, bt);

Memory Efficient Containers

If at least your key and maybe even the values are of basic types like int you can save memory by using those memory efficient and fast containers. Here is an overview of the memory efficient and fast containers.

final IntList intList=new IntArrayList();
final Int2FloatMap int2float=new Int2FloatOpenHashMap();
final Object2FloatMap<Foo> int2object = new Object2FloatOpenHashMap<>();

Cache

This cache implementation returns the cached value if it is already there or call your method myHelperMethodToGetTheValueOfNotThereYet to get it, store it and return it to you. Only problem here is that your method only gets the key as a parameter.

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

...

LoadingCache<String, List<String>> memoryCache = CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(memoryCacheMaxEntries)
 .expireAfterWrite(memoryCacheMaxAgeMinutes, TimeUnit.MINUTES)
 .build(new CacheLoader<String, List<String>>() {
  @Override
  public List<String> load(final String key) throws Exception {
        return myHelperMethodToGetTheValueOfNotThereYet(key);
  }
});

...

final List<String> res = memoryCache.get("foo");

In case you method for loading new values needs more parameters this works like this

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

...

memoryCache = CacheBuilder.newBuilder().concurrencyLevel(1).maximumSize(memoryCacheMaxEntries)
 .expireAfterWrite(memoryCacheMaxAgeMinutes, TimeUnit.MINUTES).build();

...

final List<String> res = memoryCache.get("foo", () -> myHelperMethodToGetTheValueOfNotThereYet("foo", a, b, c, ...));

You can also add values by yourself (less cool)

final Cache<String, String> myCache = CacheBuilder.newBuilder().concurrencyLevel(1).expireAfterWrite(3, TimeUnit.MINUTES)
                                        .build();
myCache.put("Key", "Value");

Queue

LinkedList, ArrayBlockingQueue, PriorityQueue

Sortieren von Datencontainern

So kann man eine Collection sortieren lassen:

public class MyFooComparator implements Comparator<Foo>
{
 ...

 @Override
 public int compare(Foo o1, Foo o2)
 {
 ...
 }

 ...
}

List<Foo> myList;
Collections.sort(myList);

Comparator myComparator=new MyFooComparator();
Collections.sort(myList, myComparator);

Im ersten Fall muss Foo Comparable implementieren, im zweiten Fall kann man seine(n) eigenen Comparator(en) schreiben. Die Sortierung arbeitet direkt auf dem Container selbst, gegenfalls vorher klonen.

Bei Sortieren kann man leicht das Problem bekommen, dass man zwei Elemente a, b hat, die nicht gleich sind (!a.equals(b)). Allerdings könnten beide Objekte bezüglich einer Sortierung weder kleiner noch größer sein. Beispielsweise bei der Sortierung von Personen nach Schuhgröße. a und b können ganz verschiedene Menschen sein und dennoch genau die selbe Schuhegröße haben. In diesem Fall muss man besonders mit einem TreeSet Container aufpassen. Dieser speichert jedes Objekt höchstens ein mal und aus a.compareTo(b)==0 schließt er (fälschlicherweise?) nur eines der beiden Objekte muss im Container gespeichert werden. Lösung: compareTo() auf mehr Attribute ansetzten oder statt TreeSet eine andere Datenstruktur benutzen (TreeBag?)

Deal with value is there yet or not

Adding a value is complicated as you never know if the value is already there

List<Integer> lst;
if (!myMap.containsKey("john")) {
        lst = new ArrayList<>();
        myMap.put("john", lst);
        lst.add(42);
} else {
        lst = myMap.get("john");
        lst.add(42);
}

Better way, put value if not yet there. But watch out, putIfAbsent returns the value for the key before you called the method, so you can not operate directly on the return value. Also the value is always computed even if the key is there. Use computeIfAbsent instead!

myMap.putIfAbsent("john", new ArrayList<>());
myMap.get("john").add(42);

With this you can provide a lamda to do even more in the case there is no value yet

lst = myMap.computeIfAbsent("john", key -> {
        if (key.startsWith("j")) {
                return new ArrayList<>();
        } else {
                return new LinkedList<>();
        }
});
lst.add(42);

If you only want to do something if value is already there

myMap.computeIfPresent("john", (key, value) -> {
        value.add(42);
        return value;
});

Provide code to handle both cases (value there, value not there)

myMap.compute("john", (key, value) -> {
        if (value == null) {
                value = new ArrayList<>();
        }
        value.add(42);
        return value;
});

If you already have the to be placed data in the right format you can also you merge as it puts your value directly to the key if there is no value yet and exectues your code otherwise

final List<Integer> extra=Arrays.asList(42);
myMap.merge("john", extra, (valueCurrent, valueNew) -> {
        valueCurrent.addAll(valueNew);
        return valueCurrent;
});

Strings

Strings Vorsicht beim Addieren von chars, das Ergebnis ist eine Zahl und kein String

char c1='a';

char c2='b';

Object c1_2=c1+c2;

System.out.println(c1_2.getClass()+" "+c1_2); //class java.lang.Integer 195

Abhilfe, entweder sicherstellen, dass der erste Summand ein String ist

Object c1_2=""+c1+c2;

Oder einen StringBuffer verwenden

StringBuffer sb=new StringBuffer();
sb.append(c1).append(c2);

Aber auch eine Summe aus Zahlen kann leicht (ungewollt) zu einem String werden

int i1=4;
int i2=2;
Object c1_2=""+i1+i2; // 42

String Vergleiche

Man sollte String immer über deren equals() Methode auf Gleichheit vergleichen. Aus Performance Gründen versucht Java allerdings konstante und gleiche Strings in identische Objekte abzubilden

String s1="Foo2";
String s2="Foo"+2;
boolean x=(s1==s2); // true

Das sollte man aber nicht für Vergleiche missbrauchen. Nach dieser kleinen Änderungen sind die Strings schon nicht mehr identisch

String s2="Foo"+1+1;

byte Array nach String

Immer den Zeichensatz angeben damit die Abbildung überall gleich ist

byte[] b={ 70, 111, 111, (byte) 164 };
String s=new String(b, "ISO-8859-15"); // Foo€

Arrays

int i[];   // == int [] i;
Foo[] x;

int i[][]; // == int [][] i == int[] i[]  
int[] i, x[], y; // == int [] i; int [][]x; int[] y;

int[] prim = { 5, 7, 7 + 4, };

Array mit vorgegebener Größe:

// int hundredElements[ 100 ];  <-- ERROR
int[] hundredElements;
hunderdElements = new int[ 100 ];

int[] prim;
prim = new int[]{ 5, 7, 11, };

Arrays überschreiben toString() nicht. Wenn z.B.

System.out.println()
String.valueOf()

einen Array lesbar ausgeben, dann liegt das daran, dass sie selbst entsprechende Logik eingebaut haben.

Arraygröße

System.out.println( hunderedElements.length );

Mehrdimensionale Arrays

Rechteckiges Array

int i[][]=new int[3][4];

Beliebiges mehrdimensionales Array

int i[][]=new int[3][];
i[0]=new int[7];
i[1]=new int[19];
i[2]=new int[2];
i[3]=new int[100];
 

Anonyme Arrays

foo( new double[]{ 1.1, 2,5, 3.2 } );

Arrays durchlaufen


double sum = 0;
double[] myarray;
List<Double> mylist=new LinkedList<Double>();

// (1)
for ( int i = 0; i < myarray.length; i++ )
{
 sum += myarray[i];
}

// (1b)
for ( double n : mylist )
{
 sum += n;
}

// (2)
for (ListIterator<Double> i = mylist.listIterator(); i.hasNext(); )
{
 sum+=i.next();
}

// (2b)
for ( double n : myarray )
{
 sum += n;
}

(1) Normaler Array Durchlauf (1b) Kurzschreibweise für (1) (2) Durchlauf eines Containers mit einem Iterator (2b) Kurzschreibweise für (2). Man verliert aber den Vorteil gegebenfalls einen Iterator zur Hand zu haben (z.B. zum Löschen von Elementen).

Unnötige Methodenaufrufe in for Schleifen sparen Häufig sehen for Schleifen so aus

for(int a=0; a<getMaxA(); a++)

Wenn sich der Rückgabewerte der Methode aber für die Dauer des Schleifendurchlaufes nicht ändert und die Methode eine nennenswerte Laufzeit hat, kann man die Schleife über eine weitere Variable deutlich beschleunigen

for(int a, int b=getMaxFoo();
    a<b;
    a++);

Teile eines Arrays in einen anderen Array oder in sich selbst kopieren

static void arraycopy( Object src, int srcPos, Object dest, int destPos, int length );

int[] y = Arrays.copyOfRange( x, 6, 9 );

Einen Array in einen Vector wandeln

So kann man einen String Array in einen String Vector wandeln

String [] myArray;<br/>...<br/>Vector<String> myVector = new Vector<String>(Arrays.asList(myArray));

Array Vergleichen

Arrays.equals( a1, a2 ) );   // (1)
Arrays.deepEquals( a1, a2 ); // (2)

// (1) Referenzen der Elemente im Array müssen gleich sein // (2) Inhalt muss gleich sein // (2)

enum

public enum Weekday
{
  MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

Der Zugriff erfolgt so:

Weekday.MONDAY

In den case Statements einer switch Anweisung dagegen so:

MONDAY

Über Enums kann man iterieren

for (Tag tag:Weekday.values()) { }