How to properly use Enums with multiple values - java

New to Java and trying to get my head around how I could properly use Enums in this scenario;
User selects his model from a Combobox dropdown, e.g.
comboBox1.setModel(new DefaultComboBoxModel<>(new String[] {
...
Focus,
Mondeo,
Fiesta,
...
}));
I then need to find a way to get the carId for the string the user selected in my Enum:
public enum Ford{
...
FOCUS("Focus", 26),
MONDEO("Mondeo", 6),
FIESTA("Fiesta", 13),
...
;
private final String name;
private final int carId;
}
I was thinking of using some kind of comparitor loop, which tries to match the string collected to the Ford.name, and if it matches, return the carId:
public String getCarId() {
String selectedItem = comboBox1.getSelectedItem().toString();
for (Ford c : Ford.values()) {
if (c.name().equals(selectedItem)) {
return c.carId
}
}
return false;
}
However I'm not sure how to proceed / fix my problem.
Is my logic all off or am I on the right track at all?

Keep track of the enum object, not its label
Keep a reference to the enum object selected by the user, rather than a reference to the string of the enum object’s display name.
// Display combobox
// User picks an item.
// Your event-handling code reacts by remembering which *object* not *string* was select.
Ford fordSelectedByUser = … ; // You pull reference from the data model backing your widget.
I do not know what combobox widget you are using. In the Vaadin framework, for example, a combobox is backed by a data model of objects. Perhaps you are using Swing? I no longer recall the details of how Swing works, but glancing at this documentation, it looks like you can back the combobox with objects and use a custom renderer.
JComboBox< Ford >, not JComboBox< String >
Make a JComboBox that holds Ford objects, not String objects. You can get an array of all your enum values by calling values(). That method is a strange one, not listed on the JavaDoc of Enum though mentioned in Enum.valueOf method doc – it is an “implicit” method, but I don’t think we care about the gory technical details there.
Ford[] fords = Ford.values() ; // Get array of all the objects defined by this enum.
JComboBox< Ford > fordsComboBox = new JComboBox( fords );
Track the selected Ford object selected, not its display name.
public void actionPerformed( ActionEvent e ) {
JComboBox cb = ( JComboBox )e.getSource() ;
Ford selectedFord = ( Ford )cb.getSelectedItem() ; // Is casting still needed, or is Swing Generics-aware? Maybe: Ford selectedFord = ( JComboBox< Ford > )e.getSource().getSelectedItem() ;
updateLabel( selectedFord.getDisplayName() ) ;
}
Your custom renderer calls the enum Ford object’s getDisplayName method that you will write.
package com.basilbourque.example;
public enum Ford {
FOCUS( "Ford" , 26 ),
MONDEO( "Mondeo" , 6 ),
FIESTA( "Fiesta" , 13 );
private final String displayName;
private final int id;
// Constructor
Ford ( String name , int carId ) {
this.displayName = name;
this.id = carId;
}
// Getters
public String getDisplayName ( ) {
return this.displayName;
}
public int getId ( ) {
return this.id;
}
// `Object` methods
#Override
public String toString ( ) {
return "Ford{ " +
"id=" + id +
", displayName='" + displayName + '\'' +
" }";
}
}
Tips:
Throughout your code base, use Ford objects, not a mere integer of its ID number nor a mere string of its display name. This makes your code more self-documenting, provides type-safety, and ensures valid values.
To collect a subset of Enum objects, use EnumSet or EnumMap classes. These are high-performance low-memory implementations of the Set and Map interfaces.
Remember an Enum is only appropriate if the domain, the set of all possible values, is known at compile-time. Adding or eliminating any of the cars means editing your Ford enum class and re-compiling.
In contrast, if your app can add more Ford cars during runtime, or eliminate any, then you cannot use an Enum. You would make Ford a regular class rather than a subclass of Enum, and would instantiate them as we do any POJO and collect them.

You are using it correctly enough already, just some minor changes
public enum Ford {
FOCUS("Ford", 26),
MONDEO("Mondeo", 6),
FIESTA("Fiesta", 13);
private final String name; //Good
private final int carId; //Good
/** This is a constructor for the enum variable */
Ford(String name, int carId) {
this.name = name; //stores the name variable for the enum
this.carId = carId;
}
public int getCarId() {
return this.carId; //gets the carID from the enum variable
}
public String getCarName() {
return this.name; //gets the car name from the enum variable
}
}
To use this enum class, and its values here is an example
public static void main(String[] args) {
/** This is how you reference an enum value directly */
System.out.println("Type: " + Ford.FOCUS
+ ", Name: " + Ford.FOCUS.getCarName()
+ ", ID: " + Ford.FOCUS.getCarId());
/** This is how you can cycle through all of the values in this enum class */
for (final Ford f : Ford.values())
System.out.println("Type: " + f
+ ", Name: " + f.getCarName()
+ ", ID: " + f.getCarId());
}
Also, note that in java, the keyword "this" is used to reference a global variable that is a member of the class/enum that "this" is being used in. It means "this class's member"

You don't need to iterate on all the values because you can easily get the enum value in O(1) with Enum.valueOf(String name).
In your case, Ford.valueOf(comboBox1.getSelectedItem().toString()).getCarId()
should work.

Related

contains with ObservableList () returns always false

im using ObservableList in my JavaFX code , i'm trying to test if an object exists in this list but it always returns false even for those already existing in it .
here is my code :
private ObservableList<OMission> UserMission = FXCollections.observableArrayList();
OMission OM1 = new OMission(1,"user_firstname","user_lastname");
UserMission.add(OM1);
if(UserMission.contains(new OMission(1,"user_firstname","user_lastname")){
System.out.println("true");}
else {
System.out.println("false");
}
I was expecting to get "true" but im always getting false
what's happening ?
You likely neglected to implement the crucial Object overrides for equals and hashCode.
This issue is not specific to the OpenJFX class ObservableList. Any collection (list, set, map, etc.) that performs comparisons will depend on you writing an appropriate override for at least equals & possibly hashCode too. (Tip: Always override both or neither, never one alone.)
Below is example code using a modified version of your code. By the way, you were missing a right-paren. Also, life is easier if you follow the Java naming conventions.
For brevity, we use the new records feature in Java 16+. A record is a briefer way to write a class whose main purpose is to communicate data transparently and immutably. You merely need to declare the type and name of each member field. The compiler implicitly creates the constructor, getters, equals & hashCode, and toString. Those last three methods by default examine each and every member field‘s value.
For simplicity we declared the record locally. You can just as well declare it nested or separate.
package work.basil.example;
import javafx.collections.*;
public class App {
public static void main ( String[] args ) {
System.out.println ( "Hello World!" );
App app = new App ();
app.demo ();
}
private void demo () {
record Person( int id , String firstName , String lastName ) { }
ObservableList < Person > UserMission = FXCollections.observableArrayList ();
Person p1 = new Person ( 1 , "Alice" , "Anderson" );
UserMission.add ( p1 );
if ( UserMission.contains ( new Person ( 1, "Alice" , "Anderson" ) ) ) {
System.out.println ( "true" );
} else {
System.out.println ( "false" );
}
}
}
When run.
Hello World!
true
If working with earlier versions for Java, or if a record is not appropriate to your situation, write a class similar to the following. Notice the methods equals & hashCode.
package work.basil.example;
import java.util.Objects;
public final class Person {
private final int id;
private final String firstName;
private final String lastName;
public Person ( int id , String firstName , String lastName ) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
public int id () { return id; }
public String firstName () { return firstName; }
public String lastName () { return lastName; }
#Override
public boolean equals ( Object obj ) {
if ( obj == this ) return true;
if ( obj == null || obj.getClass () != this.getClass () ) return false;
var that = ( Person ) obj;
return this.id == that.id &&
Objects.equals ( this.firstName , that.firstName ) &&
Objects.equals ( this.lastName , that.lastName );
}
#Override
public int hashCode () {
return Objects.hash ( id , firstName , lastName );
}
#Override
public String toString () {
return "Person[" +
"id=" + id + ", " +
"firstName=" + firstName + ", " +
"lastName=" + lastName + ']';
}
}
The issue of overriding equals/hashCode has been covered many many times already. Search to learn more.
Basil concurrently updated his answer to include some additional info while I was writing this. So, some info is duplicated in this answer. Please forgive any duplication here. I'll just leave this answer as-is for now.
To supplement Basil's answer, some different solutions are:
Write custom implementations of equals and hashcode.
For OMission, implement equals(Object obj) (and probably best to have hashCode() implemented too).
You will end up with code like in Basil's example answer.
You can code these methods yourself from scratch, however, I don't recommend that. It is too easy to make needless, silly mistakes when coding these functions by hand.
Use an IDE to auto-generate implementations of equals and hashcode.
Most IDEs have a menu item or shortcut to autogenerate these methods (see how to do this in Idea). This is how I normally generate these functions. If I add or remove a field to the class, then I delete the previous auto-generated functions and auto-generate new ones.
Use a 3rd party library to generate equals and hashcode.
A third-party library, such as Lombok, can be used to autogenerate the methods. Lombok will do this via annotations, e.g. just add the annotation #EqualsAndHashcode to your class definition.
Make OMission a record instead of a class.
If it is appropriate that the data can be represented as an immutable record rather than a class, then this is a recommended approach. It will not be appropriate for all data types.
By default, records implement appropriate equals and hashcode methods.
record OMission(
int missionNum,
String firstname,
String lastname
) {}

is it possible to create an array of more than one datatype in java If possible, how?

I want to create an array like the list ones like the ones in python3 in java but I don't know how to do it
Ex: {"John", 1, True, 1.7}
Is it possible to create an array of more than one datatype in java
If it is possible, how is it done?
Is it possible to create an array of more than one datatype in java If it is possible
No, not possible. But you're mistaken - in python, this is also impossible.
The trick is, in Python all objects are just 'object', and you have a list of objects. In java, expressions do have a type. Nevertheless, all objects are, well, objects. So:
Object[] o = {"John", 1, true, 1.7};
works. You really don't want this - arrays are low level constructs. You'd want List<Object> o = List.of("John", 1, true, 1.7}; no doubt.
Also, why do you want to store this in a list? It SOUNDS like you want this:
class Person {
String name;
int id;
boolean enrolled;
double gpa;
}
and then a List<Person>. That is 'the java way'. Junking that stuff in an Object[] is not the java way. When in rome, act like romans. When coding java, write it like java people would. If you do not, anybody else can't read your code, and libraries do not work for you or feel weird and unwieldy. If you insist on programming 'python style', then just use python.
tl;dr
Use records in Java 16 and later.
record Person ( String name , int talentCode , boolean isAvailable , double grading ) {}
Tuple
Given your example of {"John", 1, True, 1.7}, I suspect you have a set of properties related to a single entity rather than a collection of siblings. In other words, you have tuple.
Class
If this is case, such as all four of those values describing a single person, then in Java we would create a class named Person to hold four member fields. Each field would be of the appropriate type.
package work.basil.example.recs;
import java.util.Objects;
public final class Person
{
private final String name;
private final int talentCode;
private final boolean isAvailable;
private final double grading;
public Person ( String name , int talentCode , boolean isAvailable , double grading )
{
this.name = name;
this.talentCode = talentCode;
this.isAvailable = isAvailable;
this.grading = grading;
}
public String name ( ) { return name; }
public int talentCode ( ) { return talentCode; }
public boolean isAvailable ( ) { return isAvailable; }
public double grading ( ) { return grading; }
#Override
public boolean equals ( Object obj )
{
if ( obj == this ) return true;
if ( obj == null || obj.getClass() != this.getClass() ) return false;
var that = ( Person ) obj;
return Objects.equals( this.name , that.name ) &&
this.talentCode == that.talentCode &&
this.isAvailable == that.isAvailable &&
Double.doubleToLongBits( this.grading ) == Double.doubleToLongBits( that.grading );
}
#Override
public int hashCode ( )
{
return Objects.hash( name , talentCode , isAvailable , grading );
}
#Override
public String toString ( )
{
return "Person[" +
"name=" + name + ", " +
"talentCode=" + talentCode + ", " +
"isAvailable=" + isAvailable + ", " +
"grading=" + grading + ']';
}
}
Using this class.
Person alice = new Person( "Alice" , 1 , true , 1.7 ) ;
System.out.println( alice.getName() ) ;
Alice
record
Writing such a class is much easier in Java 16, if the main purpose of such a class is to transparently and immutably hold this data.
The new records feature lets us define a class briefly, declaring the member fields only. The compiler implicitly creates the constructor, getters, equals & hashCode, and toString.
Records can be thought of as nominal tuples, to quote JEP 395. The “nominal” means each value is named, and “tuple” means a finite ordered sequence of values.
public record Person ( String name , int talentCode , boolean isAvailable , double grading )
{
}
Using this record.
Person alice = new Person( "Alice" , 1 , true , 1.7 ) ;
System.out.println( alice.name() ) ;
Alice
A record can be written as a separate class, nested within another class, and can even be defined locally within a method.

Java several enums as getter/setter

I wanted to use enum class to assign book formats to book object. Issue that I have encountered is that some books have 1 format and other have several.
public enum Format {
HARD_COVER, PAPERBACK, E_BOOK, AUDIOBOOK
}
Let's say that first book have only 1 format while second book have all formats. How can I solve it with getters/setters?
How can I solve it with getters/setters?
I wouldn't do this since in my experience, getters and setters are for setting single properties. If I have a class that requires a collection of property, then I'd have the class contain a List for this:
List<Format> formats = new ArrayList<>();
and then:
public void addFormat(Format format) {
formats.add(format); // add to the ArrayList of Format
}
and similarly a removeFormat(Format format) method for removing from the List.
Edit: as correctly stated by Dorian Gray in comments, it would be better that the collection of Formats be a Set<Format> initialized as an EnumSet` since this would prevent duplicate Formats being added to the collection.
Set<Format> formatSet = EnumSet.noneOf(Format.class);
You could work with Bitwise Operators to have one property with the possibility of having multiple values.
Have Enums with int values like this:
public enum Format {
HARD_COVER(1),
PAPERBACK(2),
E_BOOK(4),
AUDIOBOOK(8);
public final int value;
Format(int value) {
this.value = value;
}
}
In your Book class add an int property called format instead of a Format property. Also create a method called isFormat that returns a boolean value according to its Format parameter:
class Book {
private int format;
public Book(int format) {
this.format = format;
}
public boolean isFormat(Format format) {
return format.value == (this.format & format.value); // &: bitwise and
}
}
Finally create a book adding formats separated by | (bitwise or):
Book book = new Book(Format.E_BOOK.value | Format.HARD_COVER.value | Format.AUDIOBOOK.value);
And check its formats like this:
System.out.println("Is paperback?: " + book.isFormat(Format.PAPERBACK));
System.out.println("Is hard cover?: " + book.isFormat(Format.HARD_COVER));
System.out.println("Is audio book?: " + book.isFormat(Format.AUDIOBOOK));
System.out.println("Is E-book?: " + book.isFormat(Format.E_BOOK));
The result of this execution is this:
Is paperback?: false
Is hard cover?: true
Is audio book?: true
Is E-book?: true

Adding strings to an Array in Abstract class A, from an extended class B in Java

I'm writing a small program to demonstrate the factory design pattern. It's a food court application which serves different types of food like Chinese, Italian, Sushi etc. I've created an array in the abstract class below, and I'm trying to populate it by adding a string via an extended class.
abstract public class FoodCourt
{
String name; //e.g. italian, chinese etc...
static String [] dailySpecial = new String[10];
int idx = new Random().nextInt(dailySpecial.length);
String random = (dailySpecial[idx]);
public String getName()
{
return name;
}
public void takeOrder()
{
System.out.println("You ordered " + random + " from our list of Daily Specials");
}
public void serve()
{
System.out.println(" -> Serving " + random + " from our " + name + " menu"); //Serving Chow Mein from our Chinese menu
}
public void printList()
{
for (String name : dailySpecial)
{
System.out.println(name);
}
}
}
Extended class
public class Chinese extends FoodCourt
{
public Chinese()
{
name = "Chinese";
String s = "Chicken Chow Mein";
dailySpecial[0] = s;
}
}
Each extended class (there are 4) will add a special dish to the array but it is being output to the screen as follows:
You ordered null from our list of Daily Specials
-> Serving null from our Chinese menu
Chinese food served
When it should be something like
You ordered Chicken Chow Mein from our list of Daily Specials
-> Serving Chicken Chow Mein from our Chinese menu
Chinese food served
If anyone could help out and see why nothing is being added in that would be great. (If necessary I can post the rest of the classes).
Your OOP structure is way off and you look to be misusing inheritance as this is not how inheritance works or should work. Your Chinese class should not extend FoodCourt since it does not satisfy the "is-a" rule or the Liskov substitution principle. I recommend a re-design with a view towards composition:
FoodCourt should hold a List of objects that extend Restaurant, say called restaurantList. Do it right from the beginning.
You could give Restaurant a getDailySpecial() method that each subclass extends
FoodCourt will call this method when iterating through the list.
You would make Restaurant abstract and your factory would create concrete Restaurant instances.
The parent class shouldn't have a fixed array as you're implementing things.
To get a random daily special, simply get a random Restaurant from the list and call its get daily special method: restaurantList.get(random.nextInt(restaurantList.size())).getDailySpecial();
The problem are those lines:
static String [] dailySpecial = new String[10];
int idx = new Random().nextInt(dailySpecial.length);
String random = (dailySpecial[idx]);
The dailySpecial.length is 10. And in your description, you said that you have 4 extended classes, you are using just 4 position of this array. But you are generating a random integer from 0 to 9.
So, your string random can be null if the idx is greater than 3.

Can I give an enum an attribute in VB.NET (like I can do in Java)?

In Java I can do something like this:
enum Country {
IRELAND("Europe"),
FRANCE("Europe"),
NIGERIA("Africa"),
THAILAND("Asia");
private String continent;
Country(String continent) {
this.continent = continent;
}
public String getContinent() {
return continent;
}
}
which allows me to do something like:
Country country1 = getCountryFromSomewhere();
Country country2 = Country.FRANCE;
System.out.print("country1 is in " + country1.getContinent());
System.out.print("country2 is in " + country2.getContinent());
Is it possible to do the same thing in VB.NET i.e. add the continent attribute to the country enum?
(Apologies for using C# throughout - I believe the concepts are more about .NET than the language you happen to use; hopefully you're better at reading C# than I am at writing VB.)
Not directly - enums in .NET are just integer types with names for some of the values.
The closest you can come in .NET is to create a type with a fixed set of values. For example, in your case:
public sealed class Country
{
public static readonly Country Ireland = new Country("Europe");
public static readonly Country France = new Country("Europe");
public static readonly Country Nigeria = new Country("Africa");
public static readonly Country Thailand = new Country("Asia");
private readonly string continent;
public string Continent { get { return continent; } }
private Country(string continent)
{
this.continent = continent;
}
}
(I assume the VB.NET would be very similar.)
Note that this doesn't let you switch on the enum values.
If you want polymorphism, you can create nested subclasses which can still call the private constructor, which prevents any other subclasses being created.
One alternative to this is to use attributes on normal enums:
[AttributeUsageAttribute(AttributeTargets.Field)]
public class ContinentAttribute : Attribute
{
// etc
}
public enum Country
{
[Continent("Europe")] Ireland = 1,
[Continent("Europe")] France = 2,
...
}
You'd then need to use reflection to get at the ContinentAttribute and retrieve the string.
Note that here there isn't really a fixed set of values - you could write:
Country country = (Country) 15;
At that point you can't get the continent for it, and if you pass it to any methods which expect it to be a real country, you've got problems. That isn't the case with the earlier solution, where you really are restricted to those few values (and null).
Here is the code:
Imports System.ComponentModel
Imports System.Reflection
Public Enum enumOrderStatus
<Description("None")>
None
<Description("Sent")>
Sent
<Description("Accepted")>
Accepted
<Description("Cancelled")>
Cancelled
<Description("Declined")>
Declined
End Enum
Public Function GetEnumDescription(ByVal EnumConstant As [Enum]) As String
Dim fi As FieldInfo = EnumConstant.GetType().GetField(EnumConstant.ToString())
Dim aattr() As DescriptionAttribute = DirectCast(fi.GetCustomAttributes(GetType(DescriptionAttribute), False), DescriptionAttribute())
If aattr.Length > 0 Then
Return aattr(0).Description
Else
Return EnumConstant.ToString()
End If
End Function
I used this solution instead:
Declare enum:
Private Enum Country
IRELAND
FRANCE
THAILAND
End Enum
Declare and initialise Dictionary (aka a map):
Dim countryContinentMap As IDictionary(Of Country, String) = New Dictionary(Of Country, String)
countryContinentMap.add(Country.IRELAND, "Europe")
countryContinentMap.add(Country.FRANCE, "Europe")
countryContinentMap.add(Country.THAILAND, "Asia")
which allows me to get the continent like this:
Dim franceContinent As String = countryContinentMap(Country.FRANCE)
Here is how I solved this in my application. Still looking for something even easier.
What do you think about it?
Public Sub Init()
Dim values() As Integer = CType([Enum].GetValues(GetType(MyEnum)), Integer())
For i As Integer = 0 To values.Count - 1
Me.contextMenuInGUI.Items.Add(Me.GetEnumDescription(i))
Next
End Sub
Private Function GetEnumDescription(ByVal i As Integer) As String
Select Case i
Case MyEnum.Comment
Return "Description for Comment"
Case MyEnum.SomeEnumValueInCamelCase
Return "Value without camel case (€)(%)(#)"
End Select
Return "Add a case in Class:GetEnumDescription"
End Function
Create an extension method for your Enum
Usage example:
dim description = TableTag.Important.GetDescription()
Definition example:
Imports System.ComponentModel
Imports System.Reflection
Imports System.Runtime.CompilerServices
Namespace Foo
Public Enum TableTag
<Description("Identifies tables that should be availible for writing as table or view to the model database")>
Important
<Description("Example for a table group that helps to select disctinct tables")>
CustomGroup
End Enum
Public Module TableTagExtensions
<Extension>
Public Function GetDescription(enumValue As TableTag) As String
Dim fieldInfo As FieldInfo = enumValue.GetType().GetField(enumValue.ToString())
Dim attributes = DirectCast(fieldInfo.GetCustomAttributes(GetType(DescriptionAttribute), False), DescriptionAttribute())
If attributes.Length > 0 Then
Return attributes(0).Description
Else
Return enumValue.ToString()
End If
End Function
End Module
End Namespace

Categories

Resources