Skip to main content

Section D.9 Exercise Sheet 9

Java Basics

1. Mixed Bag.
  1. Which access modifiers exist in Java? Which properties do they have? What visibility scope does a variable have which is declared in a class without any access modifier? How can you check your hypothesis?

  2. What are the fundamental data types in Java? Which types are automatically converted into others, which are not?

  3. What is the method equals used for in Java? Why is the usage of == insufficient in many cases?

202207281550

202206101400
Solution.
  1. The access modifiers are public, private and protected. The properties are depicted in the table below.

    Class Package Derived Class Rest
    public \(\checkmark\) \(\checkmark\) \(\checkmark\) \(\checkmark\)
    protected \(\checkmark\) \(\checkmark\) \(\checkmark\)
    no modifier \(\checkmark\) \(\checkmark\)
    private \(\checkmark\)
    As can be seen in the table, variables without an access modifier are a special case which allows access to the variable within the package, but prevents access otherwise. One can find this out by creating a minimal Java project consisting of a class with one member, another class inside the same package and two classes in another package, where one of inherits from the original class. By trying to access the member within each class, one can easily see that this is only possible from within the class in the same package.

  2. The fundamental datatypes and possible conversion are depicted below.

  3. The method equals is used to check for semantic equality and to specify when two objects shall be semantically equal.

    Fraction A = new Fraction(1, 2);
    Fraction B = new Fraction(2, 4);
    
    boolean eq = (A == B); \\ evaluates to false
    
    In the above example, both fractions represents the same value, but the == comparison evaluates to false since two different objects are involved. A self-written equals method can declare both objects as equal and return the value true.

2. My First Class.

In the materials section of the CMS, you will find an archive ExerciseSheet.zip 27  which contains a Java project. Install the program unzip by executing the command

        sudo pacman -S unzip
      

in the terminal. The password for VM is prog2. Next, extract the project with the command

        unzip ExerciseSheet.zip
      

which you must also type into the terminal. Now open the extracted folder in Visual Studio Code.

  1. Create a new class MyCounter. To this end, create a new file with name MyCounter.java in Visual Studio Code. Make sure the file is in the same directory as the file MyFirstJava.java. In the first line the statement package firstJavaExercise; should occur.

  2. Add a new method countToN to your class, which counts the numbers from \(0\) to \(n\) to the console. \(n\) should not be passed as a parameter to the function, but to the constructor of the class.

  3. Now use the main method of the given class MyFirstJava to create a new instance of the class MyCounter.

  4. Call the method countToN on the newly created object.

  5. What happens when someone creates an instance of your class using new MyCounter(-1) and then calls countToN on it? Correct the potential problems in your code by defining a reasonable invariant for your class.

202207281550

202206101400
Solution.

File MyFirstJava.java:

package firstJavaExercise;

public class MyFirstJava {
  public static void main(String[] args) {
    MyCounter counter = new MyCounter(10);
    counter.countToN();
  }
}
File MyCounter.java:
package firstJavaExercise;

public class MyCounter {
  private int n;

  public MyCounter(int n){
    assert n>=0 : "MyCounter expects a non-negative value!";
    this.n = n;
  }

  public void countToN() {
    for (int i=0; i <= this.n; i++){
      System.out.println(i);
    }
  }
}
Hint: To enable assertions, Visual Studio Code must be configured appropriately. To this end, create a new directory .vscode in the root directory of the project and add a file launch.json to it with the following content:
{
  "configurations": [
    {
      "type": "java",
      "name": "Launch firstJavaExercise",
      "request": "launch",
      "mainClass": "firstJavaExercise.MyFirstJava",
      "projectName": "ExerciseSheet",
      "vmArgs": "-enableassertions"
    }
  ]
}

3. Modulo in Java.

In this exercise, you will get to know an anomaly of Java regarding the modulo operator. Consider the natural numbers modulo \(n\text{,}\) i.e. the numbers in range \([0, n-1]\text{.}\) For \(n=10\text{,}\) we expect that \(3-5 \equiv 8 \mod 10\text{,}\) since \(-2 - \left\lfloor \frac{-2}{10} \right\rfloor \cdot 10 = 8\text{.}\) What does (3-5) % 10 evaluate to in Java? State an expression which gives the correct, positive modulus with respect to a number \(n\) for a negative number \(a\text{.}\) The expression should evaluate to \(8\) for the numbers \(n=10\) and \(a=-2\text{.}\)

202207281550

202206101400
Solution.

(3-5) % 10 evaluates to \(-2\) in Java.

/**
 * Returns a mod n. 
 * 
 * Calculates the standard modulus if a is positive, 
 * otherwise casts the negative modulus to a positive one.
 * 
 * @param a - Dividend
 * @param n - Divisor
 * @return
 */
public static int modMinus(int a, int n) {
  while (a < 0) {
    a = a + n;
  }

  return a % n;
}

4. Call by What?!

Konrad Klug has discovered Java for himself. He wants to find out whether arguments in Java are passed by value or by reference. In this exercise, you will help Konrad to evaluate his experiments:

  1. What is the output of the program below?

  2. What does the program output tell you about the behaviour when passing fundamental data types versus reference data types? Will these be passed by reference or by value?

public class Main {
    public static void main(String[] args) {
        Color color = new Color(0,0,0);

        color.incRed(color);
        color.incValue(color.green);

        System.out.println(color.red);
        System.out.println(color.green);
    }
}
public class Color {
    public int red, green, blue;

    public void incRed(Color color) {
        color.red++;
    }

    public void incValue(int value) {
        value++;
    }

    public Color(int red, int green, int blue) {
        this.red = red;
        this.green = green;
        this.blue = blue;
    }
}

202207281550

202206101400
Solution.

Attention: While this implementation is a good example, it is otherwise unreasonable. Normally, one would expect that Color::incRed increments the red value of the object it is called on, and that it does not have parameters. The given methods generally do not really make sense for a class representing a color.

  1. The output of the program is

                  1
                  0
                
  2. The reference of the reference data type arguments are passed by value, which simulates a call by reference. This means we can change all fields of the object, but not the object itself. Hence, in incRed a reference to the object color is passed and the member red of this object is incremented. Since fundamental data types are passed by value, value is only locally incremented inside the method incValue.

5. Scopes.

Which output is produced by the following programs?

  1. int y = 5;
    for(y = 10; y > 0; y--) {
        System.out.println(y);
        y--;
    }
    System.out.println(y);
    
  2. for(int y = 10; y > 0; y--) {
        System.out.println(y);
        y--;
    }
    System.out.println(y);
    
  3. Example.java

    public class Example {
        int number = 5;
    
        public void setNumber(int number) {
            this.number = number;
            System.out.println(number);
        }
    }
    

    Main.java

    public class Main {
        public static void main(String[] args) {
            Example ex = new Example();
            ex.setNumber(3);
        }
    }
    
  4. Example.java

    public class Example {
        int number = 5;
    
        public void setNumber(int number) {
            number = number;
            System.out.println(this.number);
        }
    }
    

    Main.java

    public class Main {
        public static void main(String[] args) {
            Example ex = new Example();
            ex.setNumber(3);
        }
    }
    
202207281550

202206101400
Solution.
  1. 10, 8, 6, 4, 2, 0

  2. The last print causes an error since y is only declared in the loop.

  3. 3

  4. 5

6. Automatic Type Conversions.

Decide for each of the following assigments whether they are correct. To find out if your solution is correct, test it using a small Java program.

boolean b = true;
short s = 1;
int i = 0;
long l = 4;
double d = 15.0;

b = (boolean) i;
s = i;
i = (short) d;
l = b;
s = (short) l;
i = s;

202207281550

202206101400
Solution.
boolean b = true;
short s = 1;
int i = 0;
long l = 4;
double d = 15.0;

// error: boolean is incompatible with other types
b = (boolean) i;

// error: short < int, therefore no automatic conversion possible
s = i;

// the explicit cast shortens the double to a short; afterwards the
// short is automatically converted to an int
i = (short) d;

// error: boolean is incompatible with other types
l = b;

// the explicit cast makes it possible to convert a long to a short
s = (short) l;

// short is implicitly convertible to int
i = s;
7. Matrix.

In this exercise, you must implement a class by yourself, including methods for the class. Your class shall represent a matrix, instatianted in the constructor and mutable via the public methods (you may assume that all arguments are vaild in the following). Proceed as follows:

  1. Create a class Matrix with a constructor that receives a width and height, and allocates a corresponding new integer array.

  2. Write a getter and setter with which you can modify and access the matrix values arbitrarily.

  3. Write a method which returns the minimum of all entries of the matrix.

  4. Write a method which returns the average of all entries of the matrix.

202207281550

202206101400
Solution.
public class Matrix {
  int height;
  int width;
  int[][] matrix;
  
  public Matrix(int height, int width){
    this.height = height;
    this.width = width;
    this.matrix = new int[height][width];
  }
  
  public void setValue(int h, int w, int value){
    matrix[h][w] = value;
  }

  public int getValue(int h, int w){
    return matrix[h][w];
  }
  
  public int getMinimum(){
    int value = matrix[0][0];
    for (int i = 0; i < height; i++){
      for (int j = 0; j < width; j++){
        if(matrix[i][j] < value)
          value = matrix[i][j];
      }
    }
    return value;
  }
  
  public double average(){
    int value = 0;
    int counter = 0;
    for (int i = 0; i < height; i++){
      for (int j = 0; j < width; j++){
        counter++;
        value += matrix[i][j];
      }
    }
    double res = ((double) value) / counter;
    return res;
  }
}

Java Testing

8. Java Test Suite.

Take a look at the following Java program:

public class Compute {
  public int rec(int n, int x, int y) {
    if (n == 0) {
      return x + y;
    }
    if (y == 0) {
      return x;
    }
    return rec(n - 1, rec(n, x, y - 1), rec(n, x, y - 1) + y);
  }
}

  1. Shortly describe what the program does.

  2. Write a test suite for the program which covers all statements.

  3. Extend your test suite such that it covers all branches, if possible.

202207281550

202206101400
Solution.
  1. The class can be used to calculate the so-called Sudan function. Similar to the ackermann function, this function is not primitively recursive. The function values grow more rapid than any exponential function, the value of \(\operatorname{rec}(1, 1, 2)\) is \(8\text{,}\) while \(\operatorname{rec}(2, 1, 2)\) already has value \(10228\text{.}\)

  2. class Tests {
      Compute comp = new Compute();
    
      @Test
      public void n_zero_test() {
        int result = comp.rec(0, 1, 2);
        int expected = 3;
        assertEquals(expected, result);
      }
    
      @Test
      public void y_zero_test() {
        int result = comp.rec(100, 5, 0);
        int expected = 5;
        assertEquals(expected, result);
      }
    
      @Test
      public void y_not_zero_test() {
        int result = comp.rec(1, 2, 3);
        int expected = 27;
        assertEquals(expected, result);
      }
    
      @Test
      public void one_step_test() {
        int result = comp.rec(1, 2, 0);
        int expected = 2;
        assertEquals(expected, result);
      }
    }
    

  3. In this case, it is impossible to cover all statements without also covering all branches. The tests which we already have suffice.

9. JUnit Calculator.
  1. Implement a simple calculator in Java. Your program shall receive the following three parameters:

    • int op describes the operator used for the calculation.

    • int l and int r are the operands. The operands must be between \(0\) and \(10000\text{.}\)

    The following operators exist:

    Operator Result
    \(1\) \(l + r\)
    \(2\) \(l - r\)
    \(3\) \(l * r\)
    \(4\) \(l \mathbin{/} r\)
    Your program shall be implemented as a method. To this end, we provide you with the following code skeleton:
    public static int compute(int op, int l, int r) 
      throws IllegalArgumentException {
        res = 0;
        ...
        return res;
    }
    
    If your program receives invalid arguments, it shall throw an IllegalArgumentException. Make sure to consider all reasonable edge cases (e.g. invalid number passed as operand).

  2. Write a JUnit test suite for the program described in a) which achieves maximal code coverage.

202207281550

202206101400
Solution.
  1. package calculator;
    
    public class Calculator {    
        public static int compute(int op, int l, int r) 
            throws IllegalArgumentException {
            int res = 0;
            if (l < 0 || r < 0 || l > 10000 || r > 10000) {
                throw new IllegalArgumentException("Value out of range!");
            }
            if (op == 4 && r == 0) {
                throw new IllegalArgumentException("Division by zero!");
            }
            switch (op) {
                case 1:
                    res = l + r;
                    break;
                case 2:
                    res = l - r;
                    break;
                case 3:
                    res = l * r;
                    break;
                case 4:
                    res = l / r;
                    break;
                default:
                    throw new IllegalArgumentException("Operand does not exist!");
            }
            return res;
        }
    }
    

  2. package calculator;
    
    import static org.junit.Assert.assertEquals;
    
    import org.junit.Rule;
    import org.junit.Test;
    import org.junit.rules.ExpectedException;
    
    public class CalculatorTests {
        @Rule
        public ExpectedException thrown = ExpectedException.none();
    
        @Test
        public void test_basic_op() {
            assertEquals(42, Calculator.compute(1,32,10));
            assertEquals(22, Calculator.compute(2, 32, 10));
            assertEquals(120, Calculator.compute(3, 12, 10));
            assertEquals(10, Calculator.compute(4, 100, 10));
        }
    
        @Test
        public void test_div_invalid() {
            thrown.expect(IllegalArgumentException.class);
            Calculator.compute(4, 20, 0);
        }
    
        @Test
        public void test_invalid_l_range() {
            thrown.expect(IllegalArgumentException.class);
            Calculator.compute(3, -10, 2);
        }
    
        @Test
        public void test_invalid_op_range() {
            thrown.expect(IllegalArgumentException.class);
            Calculator.compute(7, 10, 4);
        }
    }
    

Java Inheritance

10. Phones.

Dieter Schlau found a crate full of phones and would like to sort them in a catalogue to find their owners. To this end, he has created a class Mobilephone and a class Smartphone. Help him by completing the implementations:

  1. Add a reasonable constructor for both classes. The battery life of a mobile phone exhausts after 1500 minutes, the one of a smart phone after 800 minutes.

  2. Create two methods for adding and removing contacts for the class Mobilephone. Both procedures use 2 minutes of the battery life.

  3. Create two methods for adding and removing applications for the class Smartphone. An installation costs 5 minutes of battery life, a deinstallation only 2 minutes.

public class Mobilephone {
  int contacts;
  int number;
  int battery;
  //...
}
public class Smartphone extends Mobilephone {
  int apps;
  //...
}

202207281550

202206101400
Solution.

public class Mobilephone {
  int contacts;
  int number;
  int battery;

  public Mobilephone(int contacts, int number){
    this.contacts = contacts;
    this.number = number;
    this.battery = 1500;
  }

  public void addContact(){ 
    contacts++;
    battery -= 2;
  }

  public void removeContact(){
    contacts--;
    battery -= 2;
  }
}
public class Smartphone extends Mobilephone {
  int apps;

  public Smartphone (int contacts, int number, int apps){
    super(contacts, number);
    this.apps = apps;
    this.battery = 800;
  }

  public void installApp(){
    apps++;
    battery -= 5;
  }

  public void deinstallApp(){
    apps--;
    battery -= 2;
  }
}

11. Inheritance.

Take a look at the following Java classes; they describe several products of a furniture shop:

class WoodenTable {
  private byte   woodspecies;   // 0=unknown, 1=oak, 2=pine, ...
  private float  price;         // euro
  private float  weight;        // kg
  private byte   tshape;        // 0=unknown, 1=rectangular, 2=square, ...
  private byte   tlegs;         // table legs; >=3
  private byte   tfunc;         // 0=unkown, 1=dining, 2=desk, 3=drawing
  // constructor:
  public WoodenTable(byte wspecies, float price, float weight,
                     byte tshape, byte   tlegs, byte   tfunc    ) { ... }
  public String toString() { ... }
}
class WoodenChair {
  private byte   woodspecies;   // 0=unknown, 1=oak, 2=pine, ...
  private float  price;         // euro
  private float  weight;        // kg
  private byte   chshape;       // 0=unknown, 1=chair,  2=stool, 3=armchair
  private byte   chlegs;        // chair legs; >=3
  public WoodenChair(byte wspecies, float price, float weight,
                     byte   chshape, byte   chlegs            ) { ... }
  public String toString() { ... }
}
class WoodenCabinet {
  private byte   woodspecies;   // 0=unknown, 1=oak, 2=pine, 3=beech,
  private float  price;         // euro
  private float  weight;        // kg
  private byte   cashape;       // 0=unknown, 1=chair,  2=stool, 3=armchair
  private byte   cafunc;        // 0=unknown, 1=wardrobe, 2=commode, ...
  public WoodenCabinet(byte wspecies, float price, float weight,
                       byte   cashape, byte cafunc              ) { ... }
  public String toString() { ... }
}

Obviously, all three classes have common properties, while some properties are unique to individual classes.

  1. Write a new class WoodenFurniture which contains the common properties. To this end, first create a new interface WoodenFurnitureInterface. Then:

    • Adapt the classes WoodenTable, WoodenChair and WoodenCabinet accordingly such that the common properties are inherited from the new class WoodenFurniture.

    • Also adapt the constructors and think about suitable toString methods.

    • In a main method, write down suitable variable declarations and assignments such that an instance of each new class is created and a reference to the instance is stored in a variable.

    • Write a new method sumOfPrices which receives a reference to an array of WoodenFurniture object references and returns the sum of the prices. Write getter methods if needed.

  2. Now wooden designer furnitures are also added to the collection. These furnitures also have a model name (String modelname).

    • Introduce new classes DesignerWoodenTable, DesignerWoodenChair and DesignerWoodenCabinet with the corresponding constructors by reusing code where possible through inheritance. The toString method of these classes shall also contain the model name in the returned string.

202207281550

202206101400
Solution.
  1. public interface WoodenFurnitureInterface {
      public byte getWoodspecies();
      
      public float getPrice();
      
      public float getWeight();
      
      public String toString();
    }
    
    public class WoodenFurniture implements WoodenFurnitureInterface {
      private byte woodspecies;
      private float price;
      private float weight;
    
      public byte getWoodspecies() {
        return woodspecies;
      }
      public float getPrice() {
        return price;
      }
      public float getWeight() {
        return weight;
      }
      public WoodenFurniture(byte woodspecies, float price, float weight) {
        this.woodspecies = woodspecies;
        this.price = price;
        this.weight = weight;
      }
    
      public String toString() {
        return "Furniture of wood " + woodspecies
                + " with weight " + weight
                + " for only " + price;
      }
    }
    
    public class WoodenTable extends WoodenFurniture {
      private byte tshape;
      private byte tlegs;
      private byte tfunc;
    
      public WoodenTable(byte woodspecies, float price, float weight,
                         byte tshape, byte tlegs, byte tfunc) {
        super(woodspecies, price, weight);
        this.tshape = tshape;
        this.tlegs = tlegs;
        this.tfunc = tfunc;
      }
      public String toString () {
        return "Table with shape " + tshape + " and " + tlegs
               + " legs for activity " + tfunc
               + ", " + super.toString();
      }
    }
    
    public class WoodenChair extends WoodenFurniture {
      private byte chshape;
      private byte chlegs;
    
      public WoodenChair(byte woodspecies, float price, float weight,
                        byte chshape, byte chlegs) {
        super(woodspecies, price, weight);
        this.chshape = chshape;
        this.chlegs = chlegs;
      }
      public String toString() {
        return "Chair with shape " + chshape
              + " and " + chlegs
              + " legs, " + super.toString();
      }
    }
    
    public class WoodenCabinet extends WoodenFurniture {
      private byte cashape;
      private byte cafunc;
    
      public WoodenCabinet(byte woodspecies, float price, float weight,
                           byte cashape, byte cafunc) {
        super(woodspecies, price, weight);
        this.cashape = cashape;
        this.cafunc = cafunc;
      }
      public String toString() {
        return "Cabinet with shape " + cashape
               + " and function " + cafunc
               + ", " + super.toString();
      }
    }
    
    public class Main {
      public static void main(String[] args) {
        WoodenFurniture[] myFurniture = new WoodenFurniture[3];
        myFurniture[0] = new WoodenChair((byte)1, 333.0f, 5.0f, (byte)3, 
                                         (byte)1);
        myFurniture[1] = new WoodenTable((byte)2, 4.0f, 4.2f, (byte)1,
                                         (byte)4, (byte)2);
        myFurniture[2] = new DesignerWoodenCabinet((byte)0, 1000.0f,
                                   3.1416f, (byte)0, (byte)2,
                                   new DesignerAnnotation("Billy"));
    
        System.out.println("My furniture:");
        for (WoodenFurniture f : myFurniture) {
          System.out.println(f);
        }
        System.out.println("Price for all: " + sumOfPrices(myFurniture));
      }
    
      public static int sumOfPrices (WoodenFurniture[] furniture) {
        int result = 0;
        for (WoodenFurniture x : furniture) {
          result += x.getPrice();
        }
        return result;
      }
    }
    
  2. We first write down an additional class DesignerAnnotation which handles the model names and allows extension of the designer annotations without adapting the individual furnitures. The task instruction requires that String modelname is used as a property instead of this class. This (trivial) solution is commented out in the following.

    public class DesignerAnnotation {
      private String name;
    
      public DesignerAnnotation(String name) {
        this.name = name;
      }
    
      public String generateString (String oldstr) {
        return "Special Design: \"" + name + "\", " + oldstr;
      }
    }
    
    public class DesignerWoodenTable extends WoodenTable {
      private DesignerAnnotation da;
      // private String modelname;
    
      public DesignerWoodenTable(byte woodspecies, float price,
              float weight, byte tshape, byte tlegs, byte tfunc,
              DesignerAnnotation da) {
              // String modelname) {
        super(woodspecies, price, weight, tshape, tlegs, tfunc);
        this.da = da;
        // this.modelname = modelname;
      }
    
      public String toString() {
        return da.generateString(super.toString());
        // return "Model: "+modelname+", "+super.toString();
      }
    }
    
    public class DesignerWoodenChair extends WoodenChair {
      private DesignerAnnotation da;
      // private String modelname;
    
      public DesignerWoodenChair(byte woodspecies, float price,
              float weight, byte chshape, byte chlegs,
              DesignerAnnotation da) {
              // String modelname) {
        super(woodspecies, price, weight, chshape, chlegs);
        this.da = da;
        // this.modelname = modelname;
      }
      public String toString() {
        return da.generateString(super.toString());
        // return "Model: "+modelname+", "+super.toString();
      }
    }
    
    public class DesignerWoodenCabinet extends WoodenCabinet {
      private DesignerAnnotation da;
      // private String modelname;
    
      public DesignerWoodenCabinet(byte woodspecies, float price,
              float weight, byte cashape, byte cafunc,
              DesignerAnnotation da) {
              // String modelname) {
        super(woodspecies, price, weight, cashape, cafunc);
        this.da = da;
        // this.modelname = modelname;
      }
      public String toString() {
        return da.generateString(super.toString());
        // return "Model: "+modelname+", "+super.toString();
      }
    }
    
12. Equals.

Adapt the implementation of Foo such that it behaves as desired. Look up what the annotation @Override does and shortly explain how it could have prevented the mistake.

class Foo {
  private int a;
  public Foo(int a) { this.a = a; }
  public boolean equals(Foo b) { return a == b.a; }
  public static void main(String[] args) {
    Object a = new Foo(1);
    Object b = new Foo(1);
    System.out.println(a.equals(b));
    System.out.println(a.equals(a));
  }
}

202207281550

202206101400
Solution.

The equals method of Foo is overloaded instead of overwritten, since the equals method expects an object of type Object. Correction:

public boolean equals(Object b){
    if( !(b instanceof Foo) || b == NULL) {
        return false;
    } else {
        return a == ((Foo) b).a;
    }
}
The annotation @Override tells the compiler that the following method is intended to overwrite another method. If a method which does not overwrite another function follows, a compiler error is provoked.

13. Buggy.

In the materials section of the CMS, you will find the Java project Buggy 28 . In the file BuggyClass.java of the project, numerous errors occur. Your task is to find and correct all errors such that the program can be compiled and executed, leading to the following output:

Magic value: 42
Magic value: 42
Equal: true
The body of the main method, the interface InterfaceForBuggyClass.java and the class ChildOfBuggyClass.java shall not be modified. There are five errors in total in the project.

For some of the errors, VSCode can help you out. Using an IDE is of course allowed and reasonable, but always ask yourself whether the suggestions of the IDE make sense or not.

202207281550

202206101400
Solution.
  • The field magic is private, so it is not accessible from the derived class. One must change the access modifier to public or protected or omit the access specifier completely (implementing getters and setters is also possible, but one would need to adjust the derived class to use them).

  • The main method is missing the parameter String[] args.

  • To correctly implement the interface, another method is needed:

    @Override
    public int subtractNumbers(int num1, int num2) {
      return num1 - num2 + this.magic;
    }
    

  • The equals method must receive an Object to correctly overwrite the default implementation.

    @Override
    public boolean equals(Object o) {
      if (!(o instanceof BuggyClass)) {
        return false;
      }
    
      BuggyClass other = (BuggyClass) o;
      if (other.magic == this.magic) {
        return true;
      }
    
      return false;
    }
    

  • It is now also necessary to implement a custom hashCode, as otherwise the contract between hashCode and equals is violated.

    @Override
    public int hashCode() {
      return this.magic;
    }
    

ExerciseSheet.zip
Java-Buggy.zip