1. Non, on ne peut pas créer d'objets de la classe Expr. En effet le code de la méthode eval n'est pas défini. (La réponse, il n'y a pas de constructeur n'est pas vraiment bonne, cf. la question 4, et penser au constructeur par défaut Expr().)

    En revanche on poura créer des classes par héritage de Expr, et de fait on ne pourra construire des objets de ces classes filles que si la méthode eval est définie. C'est tout le sens de la méthode déclarée abstraite.

    Enfin, la classe Reader que nous connaissons déjà est en fait abstraite. Une de ses classes filles est par exemple StringReader.

  2. Il faut se lancer bêtement.
      private Expr parseP() {
        
    Expr r = parseF() ;
        
    while (tok.nature == Token.MUL || tok.nature == Token.DIV) {
          
    Token op = tok ;
          step() ;
          
    Expr f = parseF() ;
          
    if (op.nature == Token.MUL)
            r = 
    new Mul (r, f) ;
          
    else
            r = 
    new Div (r, f) ;
        }
        
    return r ;
      }
    Pour les curieux, voici le parseur complet.

  3. Une fois que le truc est compris, ça roule tout seul.
    class Int extends Expr {
      
    private int me ;

      
    Int (int i) { me = i ; }

      
    int eval(Env e) { return me ; }

      
    public String toString() { return Integer.toString(me) ; }

    }

    class Id extends Expr {
      
    private String me ;

      
    Id (String s) { me = s ; }
      
    int eval(Env e) {
        
    return e.get(me) ;
      }

      
    public String toString() { return me ; }
    }
    La méthode toString est la redéfintion de la méthode homonyme de la classe Object dont toutes les classes héritent implicitement. Si on ne la redéfinissait pas, l'affichage d'une expression La classe Let est à peine plus complexe.
    class Let extends Expr {
      
    private String x ;
      
    private Expr ex, e ;

      
    Let (String x, Expr ex, Expr e) {
        
    this.x = x ; this.ex = ex ; this.e = e ;
      }

      
    int eval(Env env) {
        
    int vx = ex.eval(env) ;
        
    return e.eval(env.add(x,vx)) ;
      }

      
    public String toString() {
        
    return "LET (" + x + ", " + ex + ", " + e + ")";
      }
    }
    Cette classe nous montre bien la règle d'évaluation de la liaison, il s'agit justement ce qu'il convient d'appeler la liaison statique, ou parfois liaison lexicale. Sa méthode toString montre l'intérêt de redéfinir cette méthode dans toutes les classes filles de Expr.

  4. L'intérêt de la classe intermédiaire est de factoriser un peu de code commun à tous les opérateurs binaires (deux champs, un contructeur, et une méthode toString essentiellement identiques.) On remarque que la méthode (concrète) toString de Binop appelle la méthode abstraite getOp sans complexe aucun.
    class Add extends Binop {

      
    Add (Expr arg1, Expr arg2) {
        
    super(arg1,arg2) ;
      }

      
    String getOp() { return "+" ; }

      
    int eval(Env e) {
        
    return arg1.eval(e) + arg2.eval(e) ;
      }
    }

    class Sub extends Binop {
      
    Sub (Expr arg1, Expr arg2) {
        
    super(arg1,arg2) ;
      }

      
    String getOp() { return "-" ; }

      
    int eval(Env e) {
        
    return arg1.eval(e) - arg2.eval(e) ;
      }
    }
    On note l'appel au constructeur super(arg1,arg2).

    Pour les curieux, voici la classe des expressions au complet.

  5. Les classe abstraites sont plus puissantes que les interface en un sens, car elles ne se limitent pas, comme le montre bien Binop à la spécification de signatures de méthodes dynamiques publiques (cf. supra). Mais l'héritage simple de Java (on hérite d'une seule classe) est parfois limitant, il faut recourir aux interfaces si on souhaite réaliser deux fonctionalités distinctes spécifiées indépendamment.