Kutam
Kutam

Reputation: 85

Error Message in Java (Stream and Lambda Comprehension)

I've been stuck for several hours trying to debug my code.

this is the error message that I get

Error:(8, 8) java: trump.Wall is not abstract and does not override abstract method get() in java.util.function.Supplier

these are the classes that corresponds to that error. So when I run the class DonalTrump it gave me the error message above. And apparently it is because of the Wall class. The following is my code

DonaldTrump

package trump;

import java.util.*;
import java.util.stream.*;
import java.util.function.BiConsumer;

public class DonaldTrump{

    public static void main(String[] args) {
        if (args.length < 3) {
            System.out.println("Need three integer arguments: width height #bricks");
            System.exit(1);
        }
        int width = Integer.parseInt(args[0]);
        int height = Integer.parseInt(args[1]);
        int numberOfBricks = Integer.parseInt(args[2]);
        assert numberOfBricks <= width * height: "Too many bricks";
        System.out.printf("Will build a wall %d wide and %d tall%n",
                width, height);
        System.out.println(String.join("", Collections.nCopies(width,"==")));

        Wall trumpWall
        = Stream.generate(() -> new Ball(10.0))
                .filter(b -> b.colour == Ball.Colour.RED)
                .map(Brick::new)
                .limit(numberOfBricks)
                .collect(() -> new Wall(width, height), Wall::accept, Wall::combine); //UPDATE
        System.out.println(trumpWall);
    }
}

//Wall SOURCE OF ERROR HERE

package trump;

import java.util.*;
import java.util.function.Supplier;
import java.util.stream.*;


public class Wall implements Supplier<Wall> { //ERROR HERE//

    public Wall get() { //UPDATE
    return this;
    }

    private Brick[][] bricks;
    private int width;
    private int height;
    private int lastFilledX;
    private int lastFilledY;
    private boolean isComplete;

    final String sideBrick = "\u2b1b";
    final String innerBrick = " \u2b1b"; // black box (brick) in Unicode

    public Wall(int w, int h) {
        assert w > 0 && h > 0 : "the wall must have finite width and height";
        this.width = w;
        this.height = h;
        this.bricks = new Brick[width][height];
        for (int j = 0; j < this.height; j++)
            for (int i = 0; i < this.width; i++)
                this.bricks[i][j] = null;
        this.lastFilledX = 0;
        this.lastFilledY = 0;
        // this.isComplete = false;
    }

    public void lay(Brick brick) {
        if (this.isComplete()) return;
        this.bricks[lastFilledX][lastFilledY] = brick;
        // if (this.isComplete()) return false;
        if (this.lastFilledX == this.width - 1) {
            this.lastFilledX = 0;
            this.lastFilledY += 1;
        } else {
            this.lastFilledX += 1;
        }
    }

    public boolean isComplete() {
        return Stream.of(this.bricks).allMatch(
                level -> Stream.of(level).allMatch(b -> b != null));
        // return (this.lastFilledX == this.width - 1 &&
        //      this.lastFilledY == this.height - 1);
    }

    @Override
    public String toString() {
        StringBuffer buffer = new StringBuffer();
        for (int j = this.height - 1; j >= 0; j--) {
            for (int i = 0; i < this.width; i++)
                // try any from the range u25a2 -- u25a9
                buffer.append(bricks[i][j] == null ? "   " :
                        i == 0 ? sideBrick : innerBrick);
            // buffer.append(bricks[i][j] == 0 ? "0" : "1");
            buffer.append("\n");
        }
        // return "\033[31m" + buffer.toString() + "\033[0m";
        return buffer.toString(); // to hell with color code sequence
    }

    public static Wall linkTwoWalls(Wall w1, Wall w2) {
        assert w1.height == w2.height : "Walls have unequal height";
        if (!w1.isComplete() || !w2.isComplete())
            return null; //Optional.empty();
        int w = w1.width + w2.width;
        int h = w1.height;
        Brick[][] bricks = new Brick[w][h];
        System.arraycopy(w1, 0, bricks, 0, w1.width);
        System.arraycopy(w2, w1.width, bricks, 0, w2.width);
        Wall result = new Wall(w, h);
        result.bricks = bricks;
        return result;//Optional.of(result);
    }

    public static Optional<Wall> joinWalls(Wall... walls) {
        if (walls == null || walls.length == 0)
            return Optional.empty();
        // check all walls are of the same height
        int firstHeight = walls[0].height;
        Stream<Wall> wallStream = Stream.of(walls);
        assert wallStream.allMatch(w -> w.height == firstHeight);
        return wallStream.reduce((w1, w2) -> linkTwoWalls(w1, w2));
    }

    public void accept(Wall wall, Brick brick) { //UPDATE NOT STATIC
        wall.lay(brick);
    }

    public void combine(Wall w1, Wall w2) { //UPDATE NOT STATIC
        Wall.linkTwoWalls(w1, w2);
    }


    public static void main(String[] args) {
        Wall wall = new Wall(40, 10);
        for (int i = 0; i < 411; i++) {
            wall.lay(new Brick(new Ball(10.0)));
            if (wall.isComplete())
                break;
        }
        System.out.print(wall);
    }
}

Upvotes: 1

Views: 700

Answers (2)

fps
fps

Reputation: 34460

At first glance, I see 2 problems with your code:

  1. You are not specifying a Supplier<Wall> in the call to collect in the DonaldTrump class, or you are doing it wrong. It's not correct to make your Wall class implement the Supplier<Wall> interface. Think of it in real life terms: it doesn't make sense that a wall is a supplier of itself. Instead, you should use a lambda expression that matches the Supplier interface, i.e. one that works as if you were implementing the Supplier.get method. This is () -> new Wall(width, height).

  2. In your Wall class, both the accept and combine methods shouldn't be static. Besides, accept should not revceive an instance of Wall, but only accept a Brick, which will be put into this Wall. Also, the combine method should accept only one Wall argument and combine this argument with this Wall. Maybe you could read the lesson about method references in The Java Tutorial, which clearly explains all the different method reference types and when to use each one of them.

Taking these items into account means that you should perform a few changes to your code.

In your DonaldTrump class, place the () -> new Wall(width, height) lambda expression as the Supplier of the collect method:

Wall trumpWall = Stream.generate(() -> new Ball(10.0))
    .filter(b -> b.colour == Ball.Colour.RED)
    .map(Brick::new)
    .limit(numberOfBricks)
    .collect(() -> new Wall(width, height), Wall::accept, Wall::combine);

And in your Wall class, change the accept and combine method as follows:

public void accept(Brick brick) { // Lay a brick into THIS wall
    this.lay(brick);
}

public void combine(Wall wanother) { // Combine another wall with THIS wall
    this.linkToThisWall(another);
}

Where linkToThisWall would be a modified version of your (now useless) linkTwoWalls method:

public void linkToThisWall(Wall another) {
    assert this.height == another.height : "Walls have unequal height";
    if (!this.isComplete() || !another.isComplete()) {
        return; // or maybe throw an exception?
    }        
    int w = this.width + another.width;
    int h = this.height;
    Brick[][] newBricks = new Brick[w][h];
    System.arraycopy(this.bricks, 0, newBricks, 0, this.width);
    System.arraycopy(another.bricks, this.width, bricks, 0, another.width);
    this.bricks = newBricks;
}

Consider also removing the get method, as implementing Supplier<Wall> is no longer needed.

Actually, with this code fix and refactoring, you no longer need the accept and combine methods. In your DonaldTrump class, you could just use references to the refactored lay and linkToThisWall methods instead:

Wall trumpWall = Stream.generate(() -> new Ball(10.0))
    .filter(b -> b.colour == Ball.Colour.RED)
    .map(Brick::new)
    .limit(numberOfBricks)
    .collect(() -> new Wall(width, height), Wall::lay, Wall::linkToThisWall);

EDIT: The main reason for these changes is that you were not using the Stream.collect method correctly.

Stream.collect expects 3 arguments:

  1. A supplier that will be used to create a cumulative, mutable structure where to accumulate the elements of the stream. In your code, this structure is a Wall and the elements of the stream are instances of Brick, so the supplier is () -> new Wall(width, height). This supplier might be seen as an empty wall, i.e. like a place in the ground where to start laying bricks.
  2. An accumulator, which is a BiConsumer that accepts two arguments: the structure returned by the previous item's supplier and an element of the stream. The contract for this accumulator biconsumer is that it has to accumulate one element of the stream into the cumulative, mutable structure. In your case, the cumulative, mutable structure is the Wall created by the supplier above and the elements of the stream are instances of Brick, so the accumulator is Wall::lay, or using a lambda (wall, brick) -> wall.lay(brick). This accumulator might be seen as a worker laying bricks into a wall, one by one.
  3. A combiner, which is a BiConsumer that accepts two arguments, both being instances of a partially filled, mutable structure (these structures have the same type as the structure supplied by the Supplier of item 1). This combiner is to be used when the creation of the final structure can be parallelized, and its contract is to combine (or merge, or mix, or link or join) the second argument structure into the first argument structure. In your case, the partially filled, mutable structures are two Wall instances partially filled with bricks, so the combiner is Wall::linkToThisWall, or using a lambda (leftWall, rightWall) -> leftWall.linkToThisWall(rightWall). All this combining stuff might be seen as two separate workers working in parallel, each one working by laying bricks into his own wall: one worker would start on the left and the other one would start in the right; when they meet in the middle, both half walls are combined into a new full wall.

As to why your solution was incorrect... Your combiner was wrong. You are not supposed to create a new, empty structure and merge the two structures provided as arguments into this new structure. Instead, you should merge the second argument structure into the first argument structure. This is why your static linkTwoWalls method didn't work: you were merging two walls into a new one and you were returning this new wall from that static method. However, that returned wall was being discarded, because the combiner must be a BiConsumer that merges second into first argument. (Yours was actually a BinaryOperator, i.e. you were creating a new wall from two walls and returning it, as when you sum two numbers and get another number as a result). However, you were not using a parallel stream, so your combiner was never used.

Upvotes: 2

Ousmane D.
Ousmane D.

Reputation: 56423

You'll need to override the get method of the Supplier interface.

public Wall get(){
      return this;
}

Upvotes: 0

Related Questions