1

I have a current state where an enum MyType represent Type table with columns as:

ID
Name

And it's used to identify type using ID parameter with byId method:

public enum MyType {

FIRST_TYPE("First Type", 10),
SECOND_TYPE("Second Type", 20);
public static class Holder {
    static Map<Integer, MyType > idMap = new HashMap<>();
    private Holder() { }
}

private MyType(String name, Integer id) {
    this.name = name;
    this.id = id;
    Holder.idMap.put(id, this);
}

public String getName() {
    return name;
}

public static MyType byId(Integer id) {
    return Holder.idMap.get(id);
}

My new requirement is to support also values exists in Type table, I found answers for dynamic enum, but accept answer is not to do it

No. Enums are always fixed at compile-time. The only way you could do this would be to dyamically generate the relevant bytecode.

What will be a better solution for finding also values (mainly IDs) from database (for example ID 30)

 select ID from TYPE

Can I extends existing state instead of change it? can I add extra IDS from database using method?

EDIT

Even if I update as @StefanFischer suggested an interface which populate map with enum class and new database class, I still expect in code an enum return by byId method,

public interface MyType {
    public static class Holder {
        static Map<Integer, MyType> idMap = new HashMap<>();
        private Holder() { }
    }

    public default void add(MyType myType, Integer id) {
        Holder.idMap.put(id, myType);
    }


    public static MyType byId(Integer id) {
        return Holder.idMap.get(id);
    }
}
Ori Marko
  • 56,308
  • 23
  • 131
  • 233
  • 3
    Enums are not suited for dyanmic values. You should use a class and objects of this to represent the database entries. – markusw Dec 05 '18 at 09:06
  • @markusw can you suggest adding class that **extends** existing state instead of change it? – Ori Marko Dec 05 '18 at 09:09
  • 2
    You cannot extend an enum. Actually it is just not suitable for this task. You should throw away this solution and create a class providing the exact same interface and name. Therefore the client code does not need to be touched. – markusw Dec 05 '18 at 09:14
  • I guess the "question" is about this : "My new requirement is to support also values exists in Type table" but I don' t understand it. Does it mean u want to create an "exists(...)" method somewhere which checks if a type exists in Type table ? will this method have some constraints which make it hard to do ? – Tristan Dec 05 '18 at 09:15
  • If I understand well the question, what you need is a MyType class with 2 map attributes (map by id and map by type name) populated with the type table which will allow u to achieve your goals, except if u have one more untold constraint. – Tristan Dec 05 '18 at 09:21
  • @Tristan I want to be able to use `byId` and if not found in enum also support ID values exists in table type, if it can't be using existing enum by an extending class – Ori Marko Dec 05 '18 at 09:31
  • Extract the interface from `MyType`. Rename the enum to `MyStandardType implements MyType`. Move the static `byId` method and Holder class to the interface. Everything should work as before. – Stefan Fischer Dec 05 '18 at 09:44
  • @StefanFischer can you extend your comment to an answer? – Ori Marko Dec 05 '18 at 09:46
  • Extracting an interface means, you take all the instance methods make a new `interface` to hold them. That is `getName` and `getId` in your case. Call that interface `MyType`. To do so, you have to rename your old enum. Make it implement that interface. You can now create more objects adhering to that same interface. – Stefan Fischer Dec 05 '18 at 09:50
  • @StefanFischer what about the database values? – Ori Marko Dec 05 '18 at 09:51
  • You make a class `MyDatabaseType implements MyType`, read those values in from the database and put it into the Map. – Stefan Fischer Dec 05 '18 at 09:54
  • @StefanFischer Map holds currently an `enum`, in your case `MyType`, but how will I add it in `MyDatabaseType`? Can you give an answer ? – Ori Marko Dec 05 '18 at 10:12
  • Change the map so it holds implementors of the interface to which both the enum members and the objects from the database adhere. – Stefan Fischer Dec 05 '18 at 10:15
  • 2
    This question clearly suffers from the [XY problem](https://meta.stackexchange.com/a/66378/309898). Please first look at your problem, then find a suitable technical solution for it. You do it the other way around: You insist in a solution, no matter how unfit for the problem. Given the fact that the author has 26k reputation, this is rather hard to believe. Enums are not dynamic by design and it is one of their main features (not an unwanted limitation). – kriegaex Jun 21 '19 at 01:21

3 Answers3

8

A distinct non-answer: you are trying to force yourself down the wrong rabbit hole.

The whole point of Enums are to give you certain advantages at compile time. At runtime, it really wouldn't matter to the JVM if you have a class with some final static Whatever fields, or an Enum with different constants. Or if you use an EnumSet versus an ordinary Set.

You use enums because they allow you to write down your source code in more elegant ways.

Therefore the approach of generating enums at runtime doesn't make sense.

The idea of enums is that you write source code using them. But when your enums are generated for you, how exactly would you write source code exploiting them?! As mentioned already, enum classes are final by default. You can't extend or enhance them separately. Whatever you would want to have, it needs to be generated for you. Which again raises the question: how would you exploit something at compile time, that gets generated at runtime?

Therefore, from a conceptual point of view, the approach outlined in the other answer (to use a Map) is a much better design point than trying to turn enums into something that they aren't meant to be.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
7

If I understand it correctly the requirements are:

  • having a MyType.byId(Integer id) method that delivers some predefined values
  • it should be also extended dynamically from a Table Type from the database

So a enum can not be extended dynamically, but we could switch to a class.

So staying close to your code one could write something like:

import java.util.HashMap;
import java.util.Map;

public class MyType {

    static Map<Integer, MyType> idMap = new HashMap<>();
    static {
        idMap.put(10, new MyType("First Type"));
        idMap.put(20, new MyType("Second Type"));
    }

    private final String name;

    private MyType(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public static MyType byId(Integer id) {
        return idMap.get(id);
    }

    public static void addType(String name, Integer id) {
        MyType lookup = byId(id);
        if(lookup != null) {
            if(!lookup.getName().equals(name)) {
                System.out.println("conflicting redefinition for id " + id + ": '" + name + "' vs '" + lookup.name + "'");
                //handle...
            }
        }
        idMap.put(id, new MyType(name));
    }
}

Test Data

Let's assume we have the following in the database:

stephan=# select * from Type;
 id |    name     
----+-------------
 30 | Third Type
 10 | First Type
 20 | Second Type
(3 rows)

So in the database we have the predefined types with id=10 and id=20 but also a type with id=30 that is not known per default to the application. But we can populate the types from the database.

Test Case

public static void main(String[] args) {
    try {
        Connection connection = createConnection();
        try (connection) {
            populateTypes(connection);
        }

        MyType type;

        type = MyType.byId(10);
        System.out.println(type.getName());

        type = MyType.byId(20);
        System.out.println(type.getName());

        type = MyType.byId(30);
        System.out.println(type.getName());

    } catch (Exception e) {
        e.printStackTrace();
    }
}

JDBC Example

It doesn't matter what actual database technology is used to retrieve the values. Here an example for JDBC:

private static void populateTypes(Connection connection)
        throws SQLException {

    String sql = "SELECT * FROM type";
    try (Statement st = connection.createStatement()) {
        try (ResultSet rs = st.executeQuery(sql)) {
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                MyType.addType(name, id);
            }
        }
    }
}

Demo Output

First Type
Second Type
Third Type

Is that what you are looking for?

Stephan Schlecht
  • 26,556
  • 1
  • 33
  • 47
  • Can't `MyType` remains Enum? – Ori Marko Jun 17 '19 at 16:28
  • 4
    No, enums must be known at compile time. If you want to dynamically extend the types with DB entries, you cannot use an enum. Is there a particular feature of enums that interests you? – Stephan Schlecht Jun 17 '19 at 16:32
  • I want to extend existing enum with dynamic values – Ori Marko Jun 17 '19 at 16:34
  • 6
    Not possible. Enums are public, static, and most importantly, *final* by default, therefore no extension, no changing the enum's value itself, and it is extremely strongly recommended against using field values as dynamic store holds. Wanting dynamic values in an enum is a code smell and strong sign you need a full-fledged class. – Austin Schaefer Jun 18 '19 at 09:12
  • A good answer, and thanks for leaving some room for a more "conceptual" answer ... going in the same direction! – GhostCat Jun 21 '19 at 10:54
1

enum represents a group of constants (unchangeable variables, like final variables). you can not define it on runtime.

Erez Levi
  • 81
  • 4