简体   繁体   中英

How can I avoid Java downcasts?

I currently am working on a project where I have 3 user classes, lets say UserA, UserB, UserC, that inherit from a abstract User class.

The program is supposed to emulate a system wish requires users to login and logout.

The instances of those users are stored in 3 separate lists.

Only one user is "logged" at any time, so I have a User currentUser variable.

When I want to call a method specific to UserA, B or C, I do a cast. How can I avoid doing that cast while keeping the code elegant?

Code ex:

public abstract class User {
}

public class UserA extends User {
    public void clearAgenda();
}

public class UserB extends User {
}

public class UserC extends User {
}

public class System {
    private User loggedUser;

    public void clearUserAgenda() {
        ((UserA) loggedUser).clearAgenda();
    }
}

Note: I know before hand that if a operation is being used, the current user supports that operation. (ex: if System.clearUserAgenda() is called then the user is a instance of UserA)

If you want to avoid the cast, you have to declaire the method clearAgenda() in the Abstract Class User. If you dont do that only UserA can use this method so it must be casted.

You pretty much have two options:

  1. Use reflection but that pretty much takes out the "keeping code clean" requirement.
  2. Make all User comply with a common interface (ie put clearAgenda() in User , provide a default blank implementation and override it in UserA). Do the same for all the methods.

I recommend option 2. You're running into this conundrum because you're giving the caller of System class the responsibility to know what sort of user you have. System then assumes the caller is right (which is a really bad assumption in the real world) and calls the method blindly. If System can't know, it's ok, but it needs to be able to deal with the user calling the wrong method on the wrong user. For that, make it not be wrong to call clearAgenda() on a UserB . In the superclass method, you can just log that the method was called and not overridden, this way you'll be able to detect problems without crashing the whole thing with a cast exception.

It's a clear cut case for polymorphism. You can declare the method in User (as Baalthasarr says), then override it in the subtypes (UserA, UserB, etc) as necessary. Then you just use loggedUser.clearAgenda() and the JRE will figure out which method to use automatically at runtime.

public abstract class User {
    public void clearAgenda() {} // do nothing (or or something if you want)
}

public class UserA extends User {
    @Override public void clearAgenda() { ... } // do stuff specific to A
}

public class UserB extends User {
    @Override public void clearAgenda() { ... } // do stuff specific to B
}

public class UserC extends User {
}

public class System {
    private User loggedUser;

    public void clearUserAgenda() {
        // This will use the most appropriate method. If the type is
        // UserA, UserA's `clearAgenda` method will be used. Similar
        // for UserB. For UserC, The abstract User class's `clearAgenda`
        // method is used, because it was not overridden in UserC.
        loggedUser.clearAgenda();
    }
}

If there's stuff you want to do that's common to all User subclasses, you can put it in User and still run it even if you override the method. For example:

public class UserA extends User {
    @Override public void clearAgenda() {
        super.clearAgenda(); // do the stuff common to all User subclasses
        ... // do stuff specific to A
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM