Java, Generik

Generisches Programmieren


Funktionen höherer Ordnung

In der Mathematik Summe_{i=1,n} f(i) oder auch Produkt_{i=1,n} f(i)


Zum Beispiel die Lisp-Funktion summe:

(define (summe f a b)
 (if (> a b)
     0
     (+ (f a) 
        (summe f (+ a 1) b)
     )
 )
)

(define (id x) x)

(define (sqr x) (* x x))

(summe id 1 4)
10

(summe sqr 1 4)
30

Lösung in Java

Mit einem Interface und

public interface SummenFunktion {
    public double f(int i);
}
public class Summation {

    double summe( SummenFunktion sf, int a, int b ) {
	double sum = 0.0;
        for (int i = a; i <= b; i++) sum += sf.f(i);
        return sum;
    }
}
Summation UML Diagramm

Summation UML Diagramm

public class Identitaet implements SummenFunktion {

    public double f(int i) {
	return (double) i;
    }
}

public class Quadrat implements SummenFunktion {

    public double f(int i) {
	double d = (double) i;
	return d*d;
    }
}
public class SummenTest {

    public static void main(String[] args) {
        Screen out = new Screen();

	Summation s = new Summation();
        SummenFunktion sf = null;
        double sum = 0.0;

        sf = new Identitaet();
        sum = s.summe( sf, 1, 4);
        out.println("Summe von 0 bis ? von f(i) = i ist " 
		    + sum);

        sf = new Quadrat();
        sum = s.summe( sf, 1, 4);
        out.println("Summe von 0 bis ? von f(i) = i*i ist " 
		    + sum);
    }
}

Zum Beispiel die Lisp-Funktion operation:

(define (operation op init f a b)
 (if (> a b)
     init
     (op (f a) 
        (operation op init f (+ a 1) b)
     )
 )
)

(operation + 0 id 1 4)
10

(operation + 0 sqr 1 4)
30

(operation * 1 id 1 4)
24

(operation * 1 sqr 1 4)
576

Lösung in Java

public interface Operation {
    public double getInit();
    public double operation( double x, double y);
}
Operation UML Diagramm

Operation UML Diagramm

public class OperSum implements Operation {

    public double getInit() { return 0.0; }

    public double operation( double x, double y) {
	return x + y;
    }
}
public class OperMult implements Operation {

    public double getInit() { return 1.0; }

    public double operation( double x, double y) {
	return x * y;
    }
}
public interface OperationFunktion {
    public double f(int i);
}
public class GeneralOperation {
 
    Operation oper = null;

    public GeneralOperation( Operation op ) {
	oper = op;
    } 

    public double applyoperation( OperationFunktion of, 
                             int a, int b ) {
	double res = oper.getInit();
        for (int i = a; i <= b; i++) 
            res = oper.operation( res, of.f(i));
        return res;
    }
}
public class OperationTest {

    public static void main(String[] args) {
        Screen out = new Screen();

        OperationFunktion of = null;
        double erg = 0.0;

	Operation sum = new OperSum();
	GeneralOperation summe = new GeneralOperation( sum );

        of = new Identitaet();
        erg = summe.applyoperation( of, 1, 4);
        out.println("Summe von 0 bis ? von f(i) = i ist " 
		    + erg);

        of = new Quadrat();
        erg = summe.applyoperation( of, 1, 4);
        out.println("Summe von 0 bis ? von f(i) = i*i ist " 
		    + erg);


	Operation prod = new OperMult();
	GeneralOperation product = new GeneralOperation( prod );

        of = new Identitaet();
        erg = product.applyoperation( of, 1, 4);
        out.println("Produkt von 0 bis ? von f(i) = i ist " 
		    + erg);

        of = new Quadrat();
        erg = product.applyoperation( of, 1, 4);
        out.println("Produkt von 0 bis ? von f(i) = i*i ist " 
		    + erg);
    }
}

Generische Datentypen

Jede Klasse ist Unterklasse von Object.

Datentypen, die ganz allgemein über (Basis-)Elementen vom Typ Object definiert sind, heissen generisch.

Problem: bei der Verarbeitung muss ggf. zur Laufzeit mit instanceof geprüft werden welchen Datentyp die Objekte tatsächlich haben und mit einem entsprechenden Type-Cast angepasst werden.

Zum Beispiel die Klassen in java.util oder die folgende Klasse List. Diese Klasse implementiert Listen ähnlich wie in Scheme/Lisp.

public class List {

    private Object a; // allgemeine Daten
    private List d;   // Referenz auf das nächste ELement

    /**
     * Der Konstruktor und alle Objekt-Methoden sind nicht 
     * öffentlich.
     */
    protected List(Object ap, List dp) {
	a = ap; d = dp; 
    }

    protected Object car() { return a; }
    protected List cdr() { return d; }


    /**
     * Nur die Klassen-Methoden sind öffentlich.
     */
    public static List cons(Object a, List d) {
	return new List(a,d);
    }

    public static Object car(List n) {
	//if ( n == null ) return null;
	return n.car();
    }

    public static List cdr(List n) {
	//if ( n == null ) return null;
	return n.cdr();
    }

    /**
     * toString ist auch öffentlich.
     */
    public String toString() {
	String s = "(";
	List t = this;
	Object a;
	for (;;) { 
            a = t.car();
	    if ( a == null ) s += "null";
	    else s += a.toString();
	    if ( t.cdr() != null ) {
               s += " ";
               t = t.cdr();
            } else break;
	}
	s += ")";
	return s;
    }
...
}
        List l = List.cons(null,null);
	l = List.cons(l,l);
	l = List.cons(l,l);
	System.out.println("l = " + l);

	List x = new List(new Integer(2),null);
	System.out.println("x = " + x);

	List y = new List(new Double(3.0),null);
	System.out.println("y = " + y);

	y = List.cons( new Integer(4), y);
	System.out.println("y = " + y);

	List z = null;
	for (int i = 0; i < 10; i++) {
	    z = List.cons( new Integer(i), z);
	}
	System.out.println("z = " + z);
l = (((null) null) (null) null)
x = (2)
y = (3.0)
y = (4 3.0)
z = (9 8 7 6 5 4 3 2 1 0)

Datentypen als Parameter

Die Verwendung des allgemeinsten Datentypes Object hat den Nachteil, dass immer Typecasts zu den tatsächlich verwendeten Datentypen notwendig sind.

Wird die Sprache so erweitert, dass Datentypen als Parameter möglich sind, können die Programme unter Verwendung Datentyp-Parameter formuliert werden. Bei der Instanziierung von Objekten wird für den Typparameter ein konkreter Datentyp eingesetzt. Die nachträglichen Typecasts entfallen damit.

Für Datentypen als Parameter wird meist eine spezielle Syntax verwendet. Der Name des formalen Typparameters wird bei der Definition in spitze Klammern eingeschlossen:

public class A<Typname> { ... }
private Typname variable;
public Typname methodname( Typname a, ...) { ... }

In C++ sind Datentyp Parameter durch Klassen-Templates realisiert. Templates wie in C++ sind in Java bis JDK 1.4 nicht vorgesehen, aber voraussichtlich ab JDK 1.5 wird es das geben.

Neue Frage: Wie können Einschränkungen für die formalen Typparameter formuliert werden?

public class A<Typname implementing Interfaces> { }

Ein Beispiel in GJ:

public class List<EinTyp> {

    private EinTyp a; // Daten vom Typ 'EinTyp'
    private List<EinTyp> d;   // Referenz auf das nächste ELement

    /**
     * Der Konstruktor und alle Objekt-Methoden sind nicht 
     * öffentlich.
     */
    protected List(EinTyp ap, List dp) {
	a = ap; d = dp; 
    }

    protected EinTyp car() { return a; }
    protected List<EinTyp> cdr() { return d; }
...
	List<Integer> x = new List<Integer>(new Integer(2),null);
	System.out.println("x = " + x);

	List<Double> y = new List<Double>(new Double(3.0),null);
	System.out.println("y = " + y);

        // Fehler: kein Integer in Double Liste
	y = List<Double>.cons( new Integer(4), y);
	System.out.println("y = " + y);

	List<Integer> z = null;
	for (int i = 0; i < 10; i++) {
	    z = List<Integer>.cons( new Integer(i), z);
	}
	System.out.println("z = " + z);

© Universität Mannheim, Rechenzentrum, 2002-2004.

Heinz Kredel

Last modified: Sun Oct 24 13:38:58 CEST 2004