0

I've written some code to try to better explain what it is I'm trying to achieve.

public interface Car extends ViewCar {

    static Car getInstance(String make, String model){
        NewCar newCar = new ConcreteCar();
        newCar.setMake(make);
        newCar.setModel(model);
        return newCar;
    }

    void drive();

}

ViewCar has showMake(), showModel() and showCar() methods.

ConcreteCar implements all methods from NewCar. NewCar extends Car.

So basically as it stands

Car car = Car.getInstance("Make", "Model")

Will return a nice shiny new car.

I've got a list within a class called Cars which contains the Car object.

So as it stands, if get the list and do forEach through each car, I can drive it or show the make/model.

What I'd like to be able to do is forEach through the list using ViewCar, which will only be able to show make/model and not drive.

Is there a better way than the code below to achieve this?

public void ViewCarCanOnlyPrintCar(){ //This works but looks a little longwinded to me

        List<ViewCar> viewCars = new ArrayList(cars.getCars());
        viewCars.forEach(car -> {
            car.showMake();
            car.showModel();
        });
    }

Now if I can do this...

cars.getCars().forEach(ViewCar::showCar);

Why can' I do something like this...

cars.getCars().forEach(ViewCar -> {
            showMake();
            showModel();
        });

In my project I have an main Interface for an object which extends multiple other interfaces. I'd like to be able to pass the object around, but only allow access to certain methods.

Am I missing something glaringly obvious?

EDIT: I wan't to be able to store a list of Cars, but then have other classes get that list of cars and only have reduced privileges. So the class that displays the car will only know how to display the make and model, not know how to drive. I understand that List<Car> isn't a List<ViewCar> even though I can do the following:

Car car = new Car();
ViewCar viewCar = car;

So how can I achieve a similar result? Is there a design pattern for this kind of thing? Or do I need to just pass the List<Car> and then do

cars.forEach((ViewCar viewCar) -> {
     viewCar.showMake()
})
Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
Adam.J
  • 2,519
  • 3
  • 14
  • 12
  • `forEach` explicitly allows the use of supertypes, so your last code should work... (but I'm not able to test it right now) – lodo Oct 20 '15 at 18:47
  • I just tried it now and it's printing showMake and showModel, but it also allows me to drive() – Adam.J Oct 20 '15 at 18:54
  • That's because containers are invariant. `List` is not a superclass of `List`. – lodo Oct 20 '15 at 18:57
  • Yeah I understand that, I read about it earlier. I'm asking if theres a way of achieving a similar result? And why does "cars.getCars().forEach(ViewCar::showCar)" work the way I want it to? – Adam.J Oct 20 '15 at 19:01
  • `forEach` is defined to take as parameter a consumer of a supertype of the type of the collection (see http://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html#forEach-java.util.function.Consumer-). In practice, it extracts an object at a time of type `Car` and calls a function on it. Implicit supercasting happens during this call, based on the usual rules of Java. – lodo Oct 20 '15 at 21:25
  • `forEach( (ViewCar vc)->{ vc.showMake(); ... } )` – ZhongYu Oct 20 '15 at 21:26
  • 1
    You cannot give away a collection of `Car`s as a collection of `ViewCar`s. If you could do it, your user could put into the collection an object that is `ViewCar` but not a `Car` (because it sees the collection as a collection of `ViewCar`), and then you would find yourself with a collection of `Car`s containing an object that is not a `Car`. – lodo Oct 20 '15 at 21:28
  • @iodo thanks for the information. Is there another way to achieve a similar result? Maybe using something instead of a List? Or is there a design pattern used to accomplish such a thing? – Adam.J Oct 20 '15 at 21:32
  • I don't understand what is the question/problem. The problematic code you've quoted is using a `ViewCar` name parameter and calling `showMake()` and `showModel()` in the context of the lambda. As `Consumer` doesn't have such default method, it looks in the scope of the caller. I assumed you would like to call the `ViewCar` methods. Java have no such construct like Visual Basic `With`. You can optionnaly use the `andThen` default method. `Consumer showMake = ViewCar::showMake; Consumer showMakeThenshowModel = showMake.andThen(ViewCar::showModel);` – LoganMzz Oct 22 '15 at 11:40
  • @lodo: you *can* give away a collection of `Car`s as a collection of `ViewCar`s if you protect it against the modification problem: `List l=Collections.unmodifiableList(listOfCar);`… – Holger Oct 22 '15 at 12:27
  • @Holger Thanks! I'll give this a go now – Adam.J Oct 22 '15 at 12:29

1 Answers1

0

Thanks to @Holger for letting me know about the Collections.unmodifiableList() method. The solution for my answer is as follows;

List<Car> cars = new ArrayList<>();

public List<ViewCar> getCars(){
    return Collections.unmodifiableList(cars);
}

This allows me to do something like

public void printCars(List<ViewCar> cars){
    cars.forEach(car -> {
        car.showMake();
        car.showModel();
    });
}

Here's the final iteration of getCars();

Stream<Car> getCars(){
    return cars.stream()
}

and

Stream<ViewCar> getViewCars(){
    return cars.stream.map(Function.identity());
}

Thanks to @Holger for the help! Much Appreciated

Adam.J
  • 2,519
  • 3
  • 14
  • 12
  • You have forgotten the type argument at the return type of `getCars()` If you change `List` to `List`, the warning disappears. – Holger Oct 22 '15 at 12:50
  • Yeah it does but I'm not always going to want one return view cars. In my main program I may want to fetch another subtype. The only other way I can think of is by doing List getCars() and then List getViewCars() – Adam.J Oct 22 '15 at 12:57
  • Yes, either you have different methods for these lists, or the caller has to perform the `unmodifiableList(…)` wrapping step. Btw., streams are generally not modifying their source collection and can change their element type to a supertype with a no-op: `Stream cars(){ return cars.stream().map(Function.identity()); }`… – Holger Oct 22 '15 at 13:14