0

In short, I have a class named NoiseMaker which stores objects who implements the interface Beep. NoiseMaker has a method where it prints out the beep sound of the specific object. In this case, I only have objects of type Car and Phone which have the beep method. However when I try to run my code I get this error

Exception in thread "main" java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [LBeepable; ([Ljava.lang.Object; is in module java.base of loader 'bootstrap'; [LBeepable; is in unnamed module of loader 'app')
    at NoiseMaker.<init>(NoiseMaker.java:7)
    at Main.main(Main.java:5)  

I don't know how to fix this because I need the arary to be able to store any object which has the method beep().How can I use generic paramaters to be able to do this task. This is the main method that calls the classes and produces the error :

public class Main {
    public static void main(String[] args) {
        NoiseMaker m;
        m = new NoiseMaker<>(5);
        m.addItem(new Phone());
        m.addItem(new Car());
        m.addItem(new Car());
        m.addItem(new Phone());
        m.addItem(new Phone());
        m.addItem(new Car());
        m.makeNoise();
    }
}

Here are the other relevenat classes :

public class NoiseMaker<T extends Beepable> {
    private int numberOfItems;
    private T [] items;

    public NoiseMaker(int capacity){
        this.numberOfItems = 0;
        items = (T[]) new Object[capacity];

    }

    public void addItem(T item){
        if (this.numberOfItems== capacity) {
            System.out.println("The NoiseMakes is full");
        } else if (item == null) {
            System.out.println("null is not a valid value");
        } else {
            items[this.numberOfItems++] = item;
        }
    }

    public void makeNoise() {
        for(T item : items) {
            item.beep();
        }
    }
}

public interface Beepable {
     void beep();
}

PS : instead of using T , can I just Beepable instead. since essentialy, I want objects who implement the interface beepable? and if so why can I do that since Beepable is an interface and cant be instatiated.

shanfeng
  • 503
  • 2
  • 14
momo123321
  • 147
  • 8
  • 1
    The `ClassCastException` is due to the fact that `T[]` is, in this case, the same as `Beepable[]`, because `Beepable` is the left-most upper bound of `T`. You cannot assign an `Object[]` to a `Beepable[]` (note that arrays know their component type at run-time). It's not much different than trying to do `Beepable b = (Beepable) new Object();`. The fix is to change the instantiation to `(T[]) new Beepable[capacity]`. It would probably make more sense, however, to simply make `items` a `Beepable[]` and avoid `T[]` entirely. You'd then also have to switch to use `for (Beepable item : items)`. – Slaw Feb 25 '23 at 20:43
  • 1
    Also, note you're using a [raw type](https://stackoverflow.com/q/2770321/6395627). You've declared `m` to be a `NoiseMaker`, but you should parameterize the type. In your case, it seems the only option is `NoiseMaker m = new NoiseMaker<>(5)` (because you add multiple implementations of `Beepable`). If you want to only allow e.g., `Phone` instances, then you should do `NoiseMaker m = new NoiseMaker<>(5);`. – Slaw Feb 25 '23 at 20:49
  • 1
    And in your case, limiting the specific type of `Beepable` to allow in your `NoiseMaker` instance is the only reason to keep it generic. If you don't care about making such limits, then I would recommend you make `NoiseMaker` non-generic and just replace all uses of `T` with `Beepable`. And note that's possible, despite `Beepable` being an interface, because nowhere in `NoiseMaker` have you actually tried to _instantiate_ a `Beepable`. You've only declared it as the _type_ of a field/array/parameter/variable/etc. – Slaw Feb 25 '23 at 20:53

2 Answers2

1

As indicated by compilation error, we can not case Object which is loaded by bootstrap classloader to Beepable which is loaded by app class loader.
You can get more information about class loader here.
Thanks for @Slaw correcting. Class loader is not the real issue here. We cannot cast an object to another object which has no inheritance relationship.
We can try to solve this by constructing a Beepable array, or using List.

public class NoiseMaker<T extends Beepable> {
    ...
    private int capacity;

    public NoiseMaker(int capacity){
        this.numberOfItems = 0;
        this.capacity = capacity;
        items = (T[]) new Beepable[capacity];
    }
    ...
public class NoiseMaker<T extends Beepable> {
    ...
    private List<T> items;

    public NoiseMaker(int capacity){
        this.numberOfItems = 0;
        this.capacity = capacity;
        items = new ArrayList<>(capacity);
    }
    ...
shanfeng
  • 503
  • 2
  • 14
  • 2
    In this case, which class loader is used to load a class is irrelevant. The problem is `T` is erased to `Beepable` because that's the left-most upper bound, and so `T[]` is actually `Beepable[]`, and trying to assign an `Object[]` to a `Beepable[]` field is obviously not going to work. Arrays know their component type at run-time. It's really not any different than trying to do `Beepable b = (Beepable) new Object();`. – Slaw Feb 25 '23 at 20:28
  • Yeah, class loader is irrelevant in this case. – shanfeng Feb 27 '23 at 01:09
1

You did a great job defining the NoiseMaker as a container of Beepables but you instantiate the objects that are added as type of Object. Those two belong to a different abstraction level. You could fix the ClassCastException by replacing this line items = (T[]) new Object[capacity]; with items = (T[]) new Beepable[capacity];

Also, you could get rid of some warnings if you define the noise maker as NoiseMaker<Beepable> m = new NoiseMaker<>(5);

Some observations: you could work with the interface in the NoiserMaker instead of the concrete class, so you could avoid cast warnings.

class NoiseMaker<T extends Beepable> {

private int numberOfItems;
private Beepable [] items;

NoiseMaker(int capacity){
    this.numberOfItems = 0;
    items = new Beepable[capacity];

}

void addItem(T item){
    if (this.numberOfItems== items.length) {
        System.out.println("The NoiseMakes is full");
    } else if (item == null) {
        System.out.println("null is not a valid value");
    } else {
        items[this.numberOfItems++] = item;
    }
}

void makeNoise() {
    for(Beepable item : items) {
        item.beep();
    }
  }
}

At last, maybe it is a personal preference thing, but working with Collections can make your life really easier. The beepable array could be easily replaced with a list as mentioned in the above answer.

Eirini Graonidou
  • 1,506
  • 16
  • 24