Best way to get an enum by numeric id - java

I need to create an enum with about 300 values and have the ability to get its value by id (int). I currently have this:
public enum Country {
DE(1), US(2), UK(3);
private int id;
private static Map<Integer, Country> idToCountry = new HashMap<>();
static {
for (Country c : Country.values()) {
idToCountry.put(c.id, c);
}
}
Country(int id) {
this.id = id;
}
public static Country getById(int id) {
return idToCountry.get(id);
}
}
That enum is going to be used a lot, so I'm wondering if this is the best possible solution performance-wise.
I've read http://docs.oracle.com/javase/1.5.0/docs/guide/language/enums.html over and over again, but couldn't find the part that describes at what time the
static {
}
block is called, and if it is guaranted that this will be called only once. So - is it?

In case if the first country id is 0 and ids are incremented by 1, you may use the next approach:
Cache enum values in array. Enum.values() returns elements in the same order as they declared in enum. But it should be cached, as it creates new array every time it is invoked.
Get values from cached array by id, which will be array index.
Please, see the code below:
enum Country {
A, B, C, D, E;
private static final Country[] values = Country.values();
public static Country getById(int id) {
return values[id];
}
}
UPDATE: To get Country's id, ordinal() method should be used. And to make getting id code clearer, the next method can be added to the enum:
public int getId() {
return ordinal();
}

Static initializer blocks are called once when the class is initialized. It's not guaranteed to be called once, but it will be unless you're doing something exotic with class loaders.
So, your approach is probably fine from a performance perspective. The only changes I'd propose would be to make your fields final.
An alternative way to represent the mapping could be to store elements in an array (or a list):
Country[] countries = new Countries[maxId + 1];
for (Country country : Country.values()) {
countries[country.id] = country;
}
You could then look them up by element index:
System.out.println(countries[1]); // DE.
This avoids the performance penalty of having to box the id in order to call idToCountry.get(Integer).
This of course requires you to have non-negative IDs (and ideally the IDs would be reasonably contiguous, to avoid having to store large runs of null between countries).

First you don't need to have a static block to create the map. You can just add your code to constructor where each component adds itself to your map. Enum is ALWAYS a sigleton so your constructor is guaranteed to be called only once (per a enum value) Also you don't need to even have ID as Enum has method public final int ordinal() that returns its zero-based sequential number in the enum. In your case ordinals would be 0 for DE, 1 forUS and 2 UK.
Here is an example:
public enum Country {
DE, US, UK;
private static Map<Integer, Country> idToCountry = new HashMap<>();
Country() {
idToCountry.put(this.ordinal(), this);
}
public static Country getById(int id) {
return idToCountry.get(id);
}
}

You can try this one too. Simple as it shows.
enum Country {
DE(1), US(2), UK(3);
public int id;
Country(int id) {
this.id = id;
}
public static Country getCountry(int id) {
Country[] c = new Country[Country.values().length];
c = Country.values();
return c[id];
}
}
Thanks a lot.

Related

Boolean value remains false after the value of the static field involved changes

In my assignment I have to use an enum to make an EnumSet of elements that fit the criteria given. So, the code needs to be as flexible as possible and allow any criteria that could be applied to the object declared in the enum.
I've been testing things out, my code involves taking the finals of the enum into the static context, so that a boolean can be applied to them, and then looping through each declared object in the enum to see if they fit the criteria. For some reason though, the state of the boolean doesn't change to true, when the value of the static fields fit the criteria.
Here is my code:
public class test {
// enumeration of persons by age and sex
enum Name {
Adam("Male", 17),
Amy("Female", 24),
Boris("Male", 12),
Bella("Female", 16);
final String _sex;
final int _age;
// static variants to "turn" values to the static context
volatile static String sex = "";
volatile static int age = 0;
Name(String sex, int age) {
_sex = sex;
_age = age;
}
}
public static void main(String[] args) {
// creating a set of people older than 17
EnumSet<Name> set = makeSet(Name.age >= 17);
System.out.print(set.toString());
}
static EnumSet<Name> makeSet(boolean query) {
EnumSet<Name> set = EnumSet.noneOf(Name.class);
for (Name element : Name.values()) {
// this is the "turning" of the values to the static context
Name.sex = element._sex;
Name.age = element._age;
// PROBLEM LIES HERE
// the query remains false, even when age is above 17
if (query) {
set.addAll(EnumSet.of(element));
}
}
return set;
}
}
Changing the static fields to volatile doesn't fix the problem either, so I don't think it's a caching issue.
So, why does the boolean not update? Is there a work-around?
The problem you are facing is that the predicate (Name.age >= 17) is checked at the moment of the function call:
When you are calling makeSet(Name.age >= 17), what actually happens is, the predicate returns a boolean, which at the time of checking returns false, and false is therefore parsed into the function.
Answered in comments by #Turing85
The problem occurred because the boolean is being passed by value.
Using a Predicate instead of just a boolean fixed the problem, because the object reference would be passed on instead, allowing the value to change after the makeSet() method was invoked.
Furthermore, this eliminates the need to take the finals of the enum into the static context.
Here is my code now:
public class test {
// enumeration of persons by age and sex
enum Name {
Adam("Male", 17),
Amy("Female", 24),
Boris("Male", 12),
Bella("Female", 16);
final String sex;
final int age;
Name(String sex, int age) {
this.sex = sex;
this.age = age;
}
}
public static void main(String[] args) {
// creating a set of people older than 17
EnumSet<Name> set = makeSet(query -> query.age >= 17);
System.out.print(set.toString());
}
static EnumSet<Name> makeSet(Predicate<Name> query) {
EnumSet<Name> set = EnumSet.noneOf(Name.class);
for (Name element : Name.values()) {
// PROBLEM FIXED
if (query.test(element)) {
set.addAll(EnumSet.of(element));
}
}
return set;
}
}

What's the best way to change attributes of objects stored in an ArrayList or HashMap?

I have to do a little exercise (homework, like a friendlist) in Java, and i'm a little stuck on one of the tasks that i have to implement in my program.
The exercise is about storing some friend-objects with a variety of attributes in a container-class and implementing some methods in the container-class for various tasks on the friend-objects.
The overall exercise is not a problem at all, but i'm quite unconvinced that my solution is the way to go. I hope you can give me some tips here.
The method that is left over, should be something like a "updateFriend" method, with which you can set the value of a given attribute to a new value, straight from the container-class.
I've already set up my friend-class with a handfull of attributes (e.g. prename, lastname, date of birth, adress, and so on) an getters/setters for all of them. I've also implemented the container-class (as an ArrayList), but i can't seem to find an elegant way to implement this specific method. My updateFriend()-method right now takes three parameters.
1.The specific id of the friend-object
2.The name of the attribute that i want to change
3.The new value of the attribute
It uses an enum to check if the entered attribute is an existing attribute and if yes, the method searches the ArrayList for the object that contains that attribute and should overwrite the existing value. It gets a little bulky, as i have implemented a switch on the enum, that calls the fitting setter-method for each attribute of the friend, if the type in attribute exists at all.
So basically the friend-class looks like this:
public class Friend {
private static int friendCount = 1;
private String firstname;
private String lastname;
private LocalDate dateOfBirth;
private String phonenumber;
private String mobilenumber;
private String eMail;
private Adress home;
private int friendID;
//Getters & Setters
...
}
The method that gives me problems in the container-class looks something like this at the moment:
public void updateFriend(int id, String toChange, String newValue)
{
for(Attribute a : attribute.values())
{
if(String.valueOf(a).equalsIgnoreCase(toChange))
{
for(Friend f : friends)
{
int counter = 1;
if(f.getID() == id)
{
switch(a)
{
case FIRSTNAME:
{
f.setPreName(neuerWert);
break;
}
//a case for each attribute
}
I'm quite certain that my take on the given method is messy, slow, and cumbersome. What would be an elegant way of solving this?
Excuse my wording and thanks in advance, greets.
I would suggest 3 performance improvements.
Use HashMap instead of List with key as id. Since, id will be unique, it will take O(1) time to get the relevant object for modification instead of spending O(n) time on List iteration.
You can change the type of toChange parameter from String to enum. This will avoid enum to String conversion and then comparing it.
Since, you are already doing validation of the attribute to be modified and you must be following standard java convention while naming your getters and setters, you can use reflection to call the method on the Friend object by creating the method name from attribute name like set{Attributename}.
Okay, lets start using the enum Attribute to handle all the changes (Since you already holding the attribute values)
Attribute Enum
public enum Attribute {
FIRSTNAME("fname", (friend, name) -> friend.setFirstname(String.valueOf(name))),
LASTNAME("lname", (friend, lname) -> friend.setLastname(String.valueOf(lname))),
DATEOFBIRTH("dob", (friend, dob) -> friend.setDateOfBirth((LocalDate) dob)),
PHONENUMBER("pno", (friend, pno) -> friend.setFirstname(String.valueOf(pno))),
MOBILENUMBER("mno", (friend, mno) -> friend.setFirstname(String.valueOf(mno)));
private String attributeName;
private BiConsumer<Friend, Object> attributeSetter;
public static Attribute getAttributeSetterByName(String attributeName) {
return Arrays.stream(Attribute.values())
.filter(attribute -> attribute.getAttributeName().equalsIgnoreCase(attributeName))
.findFirst()
.orElseThrow(() -> new RuntimeException(String.format("Invalid Attribute name - %s", attributeName)));
//.orElse(null);
}
//Getter, Setter & Args Constructor (Use Lombok to reduce Boiler Plate code)
}
Update Logic
public void updateFriend(int id, String toChange, String newValue) {
Attribute attribute = Attribute.getAttributeSetterByName(toChange);
for (Friend friend : friends) {
if (friend.getId() == id) {
attribute.getAttributeSetter().accept(friend, newValue);
break;
}
}
}
You can use a java.util.function.Consumer<T> object to change an object inside your container where you have all the type safety you get. Instead of having magic strings and string arguments for values, which might not be even for string fields, you can work directly on the objects type:
public void updateFriend(int id, Consumer<Friend> c) {
// find the friend object
Friend found = null;
for (Friend f: this.friends) {
if (f.getId() == id) {
found = f;
break;
}
}
if (found == null) {
throw new IllegalArgumentException("There is no friend object with the given id");
}
// use the friend object.
c.accept(found);
}
You can use this method like this:
container.updateFriend(42, f -> f.setVorName("abc"));
container.updateFriend(9, f -> f.setAddress(some_address_object));

Mapping Enum value with Hibernate

I want to map Enum's value with hibernate for fetching data from DB.
In DB column **EASE_RATING** is number[1]. I'm able to save data in DB as Numeric.
But When I retrieve data by Criteria.list() I get easeRating=Two instead of easeRating=2.
My question is How can I get Data in form of Ordinal Or Enums's Value.
public enum Rating {
Zero(0), // [No use] Taken Zero B'Coz Ordinal starts with 0
One(1),
...
Five(5);
private final int value;
Rating(int value){
this.value = value;
}
public int getValue(){
return this.value;
}
public static Rating getRating(int x) {
switch(x) {
case 1: return One; ...
case 5: return Five;
}
return One;
}
}
POJO:
#Enumerated(EnumType.ORDINAL)
#Column(name = "EASE_RATING", nullable = false)
private Rating easeRating;
UPDATE
I want this value in int[ordinal()] in Hibernate List().
I'm hitting DB through hibernate.
List<CustomerFeedback> result = criteria.list();
I can achieve value throgh **getValue()**
System.out.println(Rating.Five.getValue()); // 5
System.out.println(Rating.Five); // Five
System.out.println(Rating.Five.name()); // Five
But How would I get this in Hibernate list()
Hibernate is working fine. you question is about Enum.
toString() default implementation returns the name of the enum. The name is the literal of the enum: "One", "Two", etc...
If you want to obtain the ordinal of a enum stating with 1, you have to create a new method, sincel ordinal() is final:
/**
* One-starting index enumeration
*/
public enum Rating {
One, Two, Three, Four;
public int position() {
return ordinal() + 1;
}
public static Rating getRating(int x) {
return Rating.values()[x - 1];
}
public static void main(String args[]) {
Rating one = Rating.One;
System.out.println("ToString() (name): " + one);
System.out.println("Ordinal position stating in 1: " + one.position());
}
}
Answer to update 1:
Why don't just map Rating list to a value list?
List<Rating> ratings = Arrays.asList(Rating.values());
List<Integer> ints = ratings.stream()
.mapToInt(Rating::position)
.boxed()
.collect(Collectors.toList());
I am not sure why you need raw database data; my recommendation is that you should always use Rating enum and take the ordinal/value from its instances in use cases where you need it.
However, one of the solutions to meet your requirements could be to map another entity to the same table with the following mapping:
#Column(name = "EASE_RATING", nullable = false)
private Integer easeRating;
Also include any other desired columns in this entity (columns which you use in this use case). Then use this entity in the query.

How do I add or subtract String values to an object, and set the value to "none" when it's empty?

I have a class Passengers which has member properties String name, int health, and String disease with setter and getter methods. The disease variable will initially hold null. Here's that class
public class Passengers
{
private String name;
private int health;
private String disease;
public Passengers(String _name, int _health, String _disease)
{
name = _name;
health = _health;
disease = _disease;
}
public void setHealth(int _health)
{
health = _health;
}
public void setDisease(String _disease)
{
disease = _disease;
}
public String getName()
{
return name;
}
public int getHealth()
{
return health;
}
public String getDisease()
{
return disease;
}
}
What I want to know is how I could add new strings onto this variable, and then how to take away. For example, a passenger Bill starts at null for his diseases, and then contracts malaria and the cold. Bill's disease variable should now hold malaria, cold. Now say the user chooses to treat Bill's malaria. How would I
1) add malaria and cold
2) subtract just malaria from disease?
Whenever I attempt to change the disease with
passengers[index].setDisease() = null;
it says "error: method setDisease in class Passengers cannot be applied to given types:
required: String
found: no arguments"
I would reccomend making disease a Set of Strings.
Set<String> diseases = new HashSet<String>();
void addDisease(String disease) {
diseases.add(disease);
}
void removeDisease(String deisease) {
diseases.remove(disease);
}
Sets are "better", in this case, than other Collections because they cannot hold duplicates.
You should give the class a List<String> such as an ArrayList<String> and put the diseases in this List.
Better still, create a class or enum of Disease and have your Passenger class use a List<Disease> and avoid over-use of String. You could then give the class public addDisease(Disease disease) and removeDisease(Disease disease) methods.
Incidentally, your class above should be named Passenger, the singular, not Passengers, the plural, since it represents the concept of a single Passenger.
For your requirement if you are using List like ArrayList you can access your elements(disease names) by index, but it will allow duplicate data to be inserted(same disease may be added multiple times, it will unnecessary increase in number of diseases and may arise some problems).
If you use Set like HashSet it will allow unique element only, so no issues related to duplicated entries but at the same time you can't access a particular disease by index (if you need so, as of now I am not aware of your further requirement).
So as best of my knowledge I suggest you to use LinkedHashSet(HashSet with Linked approach) it will provide you FIFO order without duplicate insertion problem.

Passing 2 generics in Priority Queue

I want to keep two things in my priority queue...one is a number and the other is cost. i.e. I want to do the following:
PriorityQueue<Integer, Cost> q=new PriorityQueue<Integer, Cost>();
Cost is another class that i hav:
class Cost implements Comparable<Cost>
{
String name;
double cost;
#Override
public int compareTo(Cost s)
{
return Double.compare(cost, s.cost);
}
}
Also I want to perform comparisons only based on cost...but I also want some integer identifier to be passed along with cost...is there some way to achieve this?
i need to retrieve Cost based on id..therefore I am using a hash map for it. When using an id field in cost...i want to retrieve the entire cost instance based on that id field...is it possible...is yes, then how?
I am a novice at Java programming. Can someone pls suggest some way out?
Change your Cost class
public class Cost implements Comparable<Cost> {
String name;
double cost;
int id;
public Cost(int id, String name, double cost) {
this.id = id;
this.name = name;
this.cost = cost;
}
#Override
public int compareTo(Cost s) {
return Double.compare(cost, s.cost);
}
public int getId() {
return this.id;
}
#Override
public String toString() {
return new StringBuilder().append("id : ").append(id).append(
" name: ").append(name).append(" cost :").append(cost)
.toString();
}
}
Then you can simply declare PriorityQueue of Const
PriorityQueue<Cost> q=new PriorityQueue<Cost>();
Now when you want to find Cost based on id you can do below
PriorityQueue<Cost> queue = new PriorityQueue<Cost>();
queue.add(new Cost(1, "one", 1));
queue.add(new Cost(2, "two", 2));
int id = 2;// Id to be found
for (Cost cost : queue) {
if (cost.getId() == 2) {
System.out.println(cost);
}
}
The Cost object is a good start. Make an object that contains both an integer and a Cost, and put those in the priority queue. Or, add an integer field to the Cost class itself.
You may want to wrap your integer and cost in a Map/HashMap as below:
PriorityQueue<Map<Integer, Cost>> q = new PriorityQueue<Map<Integer, Cost>>();
Now you would be able to create a HashMap object and put you two object in that before putting in the queue.
Also, you want to create a custom wrapper Class e.g. CostNumber which will have Integer and Cost as two member variables. Once done ,you can use that new object in the queue.
Since PriorityQueue stores a single object, you need to do one of the following:
create a class that contains both the integer and the cost object, iff integer and cost are unrelated.
push the integer attribute as another member of Cost class iff they are related.
Also I want to perform comparisons only based on cost...but I also want some integer identifier to be passed along with cost...is there some way to achieve this?
Why would you want to pass something to compareTo that you are not going to use during comparison? In any case, the signature of this method cannot be changed if you want to leverage the Comparator framework. You can add that integer identifier to your Cost class itself as another member and thereby make it available during compareTo method execution.

Categories

Resources