Exercice un

On définit la classe A1 par :

package fr.univ_paris1.mass.poo.cc4.exo1;

public class A1 {
    private double x;

    private double y;

    public A1(double x, double y) {
	this.x = x;
	this.y = y;
    }

    public A1 f(A1 that) {
	return new A1(x + that.y, y + that.x);
    }

    public A1 g() {
	return new A1(x + y, x - y);
    }

    public A1 h(A1 u, A1 v) {
	return new A1(x + u.x, y - v.y);
    }

    @Override
    public String toString() {
	return (x + y) + " " + (x - y);
    }
}

Question : quel est alors l'affichage produit par le code suivant ?

package fr.univ_paris1.mass.poo.cc4.exo1;

public class TestA1 {

    public static void main(String[] args) {
	A1 x = new A1(1,2);
	System.out.println(x);
	System.out.println(x.g());
	System.out.println(x.g().g());
	A1 y = new A1(3,-1);
	System.out.println(y);
	System.out.println(x.f(y));
	A1 z = new A1(0,0);
	System.out.println(z.h(new A1(1,2), new A1(3,4)));
	System.out.println(z);
    }

}

L'affichage obtenu est :

3.0 -1.0
2.0 4.0
6.0 -2.0
2.0 4.0
5.0 -5.0
-3.0 5.0
0.0 0.0

Il faut être ici bien attentif au sens d'une expression comme x.g().g(). On doit l'interpréter comme (x.g()).g() : l'ordinateur commence par réaliser l'appel x.g(). D'après la définition de la classe, cet appel renvoie un nouvel objet de type A1. C'est ce nouvel objet qui appelle de son côté de nouveau la méthode g. Ici, concrètement, tout se passe comme si on avait remplacé la ligne System.out.println(x.g().g()); par les lignes suivantes :

A1 u = x.g();
A1 v = u.g();
System.out.println(v);

En général, il est très bénéfique de raisonner comme on le fait au-dessus, en décomposant les appels de méthodes en des étapes intermédiaires plus simples.

Il faut aussi être très attentif à des lignes comme System.out.println(z.h(new A1(1,2), new A1(3,4)));. On pourrait en effet raisonner à l'envers et penser que c'est l'objet z qui est affiché après l'exécution de l'appel de méthode. Or, ce n'est jamais le cas : un appel de méthode est remplacé par son résultat. Ici, la méthode h renvoie un nouvel objet, donc comme dans l'exemple précédent, on peut réécrire la ligne en plusieurs étapes :

A1 w = new A1(1,2);
A1 q = new A1(3,4);
A1 p = z.h(w,q);
System.out.println(p);

Cette décomposition en étapes simplifie grandement l'analyse du programme.

Exercice deux

On définit la classe B1 par :

package fr.univ_paris1.mass.poo.cc4.exo2;

public class B1 {
    private int boo;

    private char X;

    public B1(int boo, char X) {
	this.boo = boo;
	this.X = X;
    }

    @Override
    public String toString() {
	if (boo > 0) {
	    return X + " :-) " + boo;
	} else {
	    return X + " :-( " + (-boo);
	}
    }

    public B1 flip() {
	return new B1(-boo, X);
    }

    public char poke(char Y) {
	char Z = X;
	X = Y;
	return Z;
    }

    public int zap(int gla) {
	boo = boo + gla;
	return boo / 2; // quotient de la division entière
    }
}

Question : quel est alors l'affichage produit par le code suivant ?

package fr.univ_paris1.mass.poo.cc4.exo2;

public class TestB1 {

    public static void main(String[] args) {
	B1 john = new B1(2, 'J');
	System.out.println(john);
	B1 bob = john.flip();
	System.out.println(john + " <-> " + bob);
	System.out.println(john.zap(1) + " <-> " + bob.zap(-2));
	System.out.println(john + " <-> " + bob);
	B1 robert = new B1(5, 'R');
	B1 alfred = robert;
	System.out.println(alfred.poke('A'));
	System.out.println(robert + " <-> " + alfred);
	B1 angie = new B1(-2,'A');
	System.out.println(angie.flip().zap(2));
	System.out.println(angie);
    }

}

L'affichage obtenu est :

J :-) 2
J :-) 2 <-> J :-( 2
1 <-> -2
J :-) 3 <-> J :-( 4
R
A :-) 5 <-> A :-) 5
2
A :-( 2

On retrouve ici le même type de problèmes que dans l'exercice précédent. Par exemple, la ligne System.out.println(john.zap(1) + " <-> " + bob.zap(-2)); ne conduit bien entendu pas au même affichage que la ligne qui la précède (System.out.println(john + " <-> " + bob);). En effet, dans cette second ligne, on se contente d'afficher les deux objets, en utilisant leur méthode toString. En revanche, la première ligne contient des appels à la méthode zap. Comme dans les exemples précédents, on obtient le résultat de l'affichage en écrivant un code équivalent :

int u = john.zap(1);
int v = bob.zap(-2);
System.out.println(u + " <-> " + v);

Il est ainsi clair que l'affichage est celui des résultats entiers des deux appels, et non pas un nouvel affichage des objets.

Exercice trois

Soit la classe C1 définie de façon incomplète par :

public class C1 {
    private int x;

    public C1(int x) {
	this.x = x;
    }

    public void f() {
	à compléter (A)
    }

    à compléter (B)
}

Question : Compléter la classe C1 afin que le programme (méthode main ci-dessous) affiche le texte suivant

[1]
[2]
[3]
[4]
public class TestC1 {

    public static void main(String[] args) {
	C1 foo = new C1(1);
	System.out.println(foo);
	for(int i=0;i<3;i++) {
	    foo.f();
	    System.out.println(foo);
	}
    }

}

Une solution possible est la suivante :

public class C1 {
    private int x;

    public C1(int x) {
	this.x = x;
    }

    @Override
    public String toString() {
	return "[" + x + "]";
    }

    public void f() {
	x = x + 1;
    }
}

La solution doit nécessairement passer par la redéfinition de la méthode toString, sinon le programme proposé devrait obligatoirement produire des affichages de la forme C1@.... En outre, comme l'affichage change à chaque tour de la boucle, il semble naturel de s'appuyer sur la variable x de l'objet qu'on modifie sans difficulté dans f.

Exercice quatre

Soit les classes D1 et E1 définies par :

public class D1 {
    public int f() {
	return 2;
    }

    public int g() {
	return 3;
    }
}

public class E1 extends D1 {
    @Override
    public int f() {
	return 4;
    }
}

Question : quel est l'affichage produit par le programme ci-dessous.

public class TestED1 {

    public static void main(String[] args) {
	E1 x = new E1();
	System.out.println(x.f());
	D1 y = new D1();
	System.out.println(y.f());
	y = x;
	System.out.println(y.g());
	Object o = x;
	System.out.println(o);
    }

}

Il s'agit d'un cas classique d'héritage dans lequel la classe E1 hérite de la classe D1. En particulier, la classe E1 hérite de la méthode g mais redéfinit la méthode f. De ce fait, le premier appel x.f() renvoie 4.

L'objet désigné par y étant au départ de type D2, l'appel y.f() donne bien sûr comme résultat 2.

L'appel suivant y.g() donne nécessairement 3 puisque la méthode g utilisée est toujours celle de D1 (elle n'est pas redéfinie dans E1).

Enfin, l'affichage de o conduit à l'appel de la méthode toString. Comme celle-ci n'est pas redéfinie, on obtient l'affichage du type de o (suivi des éléments habituels). Or, ici, o désigne un objet de type E1. L'affichage sera donc de la forme E1@.... On obtient finalement :

4
2
3
E1@3f68336

Exercice cinq

Un objet de la classe Intervalle représente un intervalle fermé. L'objectif de l'exercice est de compléter la classe donnée ci-dessous de la façon suivante :

  • la méthode toString représente l'intervalle sous la forme classique [a,b] ;
  • la méthode contient renvoie true si et seulement si x est contenu dans l'intervalle appelant ;
  • la longueur de l'intervalle [a,b] est b-a ;
  • la méthode fixeMinimum renvoie un nouvel intervalle avec la même borne supérieure que l'intervalle appelant et la borne inférieure passée en paramètre ;
  • la méthode étend construit le plus petit intervalle contenant à la fois l'intervalle appelant et le paramètre x.
public class Intervalle {
    private double inf;

    private double sup;

    public Intervalle(double inf, double sup) {
	this.inf = inf;
	this.sup = sup;
    }

    @Override
    public String toString() {
      // à compléter
    }

    public boolean contient(double x) {
      // à compléter
    }

    public double longueur() {
      // à compléter
    }

    public Intervalle fixeMinimum(double inf) {
      // à compléter
    }

    public Intervalle etend(double x) {
      // à compléter
    }
}

Une solution possible est :

public class Intervalle {
    private double min;

    private double max;

    public Intervalle(double min, double max) {
	this.min = min;
	this.max = max;
    }

    @Override
    public String toString() {
	return "[" + min + ", " + max + "]";
    }

    public boolean contient(double x) {
	return x >= min && x <= max;
    }

    public double longueur() {
	return max - min;
    }

    public Intervalle fixeMaximum(double max) {
	return new Intervalle(min,max);
    }

    public Intervalle fixeMinimum(double min) {
	return new Intervalle(min,max);
    }

    public Intervalle etend(double x) {
	if(x<min) {
	    return new Intervalle(x,max);
	} else if (x>max) {
	    return new Intervalle(min,x);
	} else {
	    return this;
	}
    }
}