1

I am storing classes like House, Car, Clothes, FacialFeatures; all parts of a Person in a HashMap:

public class Person {
    private HashMap<String, PersonalItem> map = new HashMap<String, PersonalItem>();

    public void init(){
        map.put("house", new House());
        map.put("car", new Car());
        map.put("clothes", new Clothes());
        //etc...

    }

    public PersonalItem getPersonalItem(String name){
        return map.get(name);
    }
}

external usage:

public static void main(String[] args){
Person person = getPerson(args);

//if i want to use car.setColor(blue); i have to do:

    ((Car) person.getPersonalItem("car")).setColor(blue);

//or if i want to use house.setExterior(wooden); i have to do:

    ((House) person.getPersonalItem("house")).setExterior(wooden); 

}

How can I make it so if I were to use the following code:

person.getPersonalItem("house").setExterior(wooden);

It would work and getPersonalItem would return Object instanceof House if the input is "house." I don't want to have to write out a getter every time:

public House getPersonalItemHouse(){
    return (House) map.get("house");
}

Is there an alternative?

5
  • 2
    Why don't you just have regular getters? e.g. House getHouse(). Commented Mar 24, 2018 at 12:01
  • 1
    Because I have a huge amount of items to store, I don't want 500 getters and setters in Person class Commented Mar 24, 2018 at 12:02
  • 2
    Well that is the trade-off. You've thrown away all the compile-time type information, so there isn't a lot that the compiler can do to help you. If verbosity is the concern, then you might consider using Kotlin's data classes - about as succinct as you can get, and Kotlin has very good interop with Java. Commented Mar 24, 2018 at 12:13
  • I'd argue that given the current approach even public members might be a better alternative. Seems like encapsulation is not the big concern here. Commented Mar 24, 2018 at 12:35
  • I’m going to be honest here: This is a poor design. No one knows those String names except you, and expecting others to “read the code” is the opposite of object oriented design. What if House has a front porch? Is the name "frontporch", "frontPorch", or "front porch"? The best solution by far is to bite the bullet and write the properties. Note that every IDE can generate the get/set methods for you, if you write out just the fields. I bet writing those fields would take less than 30 minutes. Commented Mar 24, 2018 at 22:19

5 Answers 5

4

One (dirty?) trick you could use is to use the Class object as the key of the map instead of an arbitrary name and downcast it directly in the getter:

public class Person {
    private Map<Class<? extends PersonalItem>, PersonalItem> map = new HashMap<>();

    public void init() {
        map.put(House.class, new House());
        map.put(Car.class, new Car());
        map.put(Clothes.class, new Clothes());
        //etc...

    }

    public <T extends PersonalItem> T getPersonalItem(Class<T> cls) {
        return (T) map.get(cls);
    }

You still have a cast there, but it's limited to a single place, and you don't have to know about it when using the Person class:

person.getPersonalItem(House.class).setExterior(wooden);

The drawback of this approach is that a person can't have two personal items of the same type.

Sign up to request clarification or add additional context in comments.

3 Comments

Yeah thought of that, but then everybody has the same houses and cars...
@user7148508 why do you think so? the map is an instance variable - each instance of Person can point to a different instance of House.
Yep, sorry my bad.
1

It sounds like you're using the string + map approach to avoid explicit getters (and setters). But you've thrown away all the compile-time type information. So there isn't much help that the compiler can give you here. Indeed, the approaches in the other answers won't protect you against accidentally associating a type of Car with "house".

If verbosity is the concern, there are alternatives that allow you to retain compile-time types:

  1. Code generation (e.g. Immutables or Lombok). These are good, but require extra tooling and build steps.

  2. Use Kotlin, specifically data classes. They're about as succinct as you can get, and Kotlin has very good interop with Java.

I'd strongly advocate for #2 - I've experienced high benefit and low friction with this approach (i.e. defining types in Kotlin, and then consuming them from a Java codebase).

Comments

1

What you try to do is not safe concerning the type safety.
Performing casts that may happen at runtime to avoid multiply methods is not necessary a good thing.
In your case I wonder if accessing the values from the Map makes really sense.
If the Map defines the state of the Person class, use rather fields and getters for each one.
If you have many of them and don't want to write/pollute your class, you can use Lombok that generate getters (and other boiler plate code if needed) for you at compile time.


As alternative approach to Mureinik, you could use a generic in the getPersonalItem() method to infer the type from the client side of the invocation. In this way, you can have multiple entry in the map which the objects have the same type.

@SuppressWarnings("unchecked")
public <T extends PersonalItem> T getPersonalItem(String name, Class<T> clazz){
    return (T) map.get(name);
}

And invoke it :

person.getPersonalItem("myHouse", House.class).setExterior(stone);
person.getPersonalItem("mySecondHouse", House.class).setExterior(wooden);

8 Comments

How is that any better?
@Oliver Charlesworth It just answers to the question : how to avoid duplication.
It seems to me that the code to use this is identical to the OP's current code.
No because then you still have that extra line creating the PersonalItem into the house: House house = person.getPersonalItem("house"); That is what I don't want. I want to be able to just return House when calling getPersonalItem method
@Oliver Charlesworth The current OP code return (House) map.get("house"); returns only House type. Here I return any value of the map and the return type is depending on the type specified by the client in the assignment of the method.
|
1

I would use a file or database table where possible keywords are stored, linked to classes, like

Person: house, car, clothes; House: owner, garage

and so on if it is a file and if it is a database table, then a table of

properties(id, className, propertyName)

could help. Whatever the input is, you could write a code which would generate classes, like PersonDefinition where getters and setters are defined (naturally, you can handle types of properties as well, I have ommitted this for the sake of simplicity)

and then make sure you inherit Person from PersonDefinition and whenever you build your project, a pre-build event should generate the PersonDefinition class, so you will have all the setters and getters you need.

1 Comment

This kind of code generation is what things like Immutables do for you already, but without the need for a database or flat file.
1

The solution using generics, and the class object as key of the map seems best to me. However, if a class e.g. House has not been added to the map:

person.getPersonalItem(House.class).setExterior(wooden);

Can be programmed with code completion in the IDE, but will give a null pointer at run time. Previously somebody gave the solution:

public <T extends PersonalItem> T getPersonalItem(String name){
 return (T) map.get(name);
 }

which indeed only covers the code duplication part of the question, and suffers from the same dangers. However, if you have to code:

House house = (House)person.getPersonalItem("house");

it's way more clear that you have to ascertain that the "house" key is actually present in the map.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.