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
Related
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.
I want to implement a EAN128 barcode parser. In short, EAN128 barcode is built by one or more fields, each one composed by a string identifier and a value. There is a hundred different identifiers and each value has fix or variable length (numeric or alphanumeric) depends on the identifier. Variable length value ends with special char named FNC1.
I want get all identifiers and its value from a barcode.
My design is based in an enumeration with one field for each code identifier.
public enum IDENT_EAN128 {
// Identifier 8003 that has a value composed by 14 numeric chars and 1 to 20 alphanumeric chars
IDENT_8003 ("8003", FixedParser(14, NUMERIC), VariableParser(20, ALPHANUMERIC)),
IDENT_00 ("00", FixedParser(18, NUMERIC)),
.... // hundred identifiers
private IDENT_EAN128 (String code, Parser... parsers ) {
...
}
public static IDENT_EAN128 search (String code) {
// loop IDENT_EAN128.values() to search code identifier
}
}
public class Main {
public static void test () {
String id1 = "8003";
String field1 = "12345678901234";
String field2 = "12345" + FNC1;
String id2 = "00";
String field3 = "123456789012345678";
String barcode = id1 + field1 + field2 + id2 + field3;
for (int posBarcode; posBarcode < barcode.length(); posBarcode++) { // loop chars of barcode
char[] buffer ...
IDENT_EAN128 idEAN = IDENT_EAN128.search(buffer)
if (idEAN != null) {
// loop Parsers for get identifier value
// move posBarcode to the first barcode position of next identifier
}
....
}
}
}
Parsers return identifier value, validate its length and that the value has the correct char type (numeric or alphanumeric).
The problem with this design is that when is called first time, it creates hundreds of new objects (each identifier and its parsers). Most times barcode only have 3 or 4 identifiers. So, I think it's a wate of time and memory. I have search about "lazy inizialitation" design for parsers, but I haven't found something that correspond with my problem. Is there a better design? or my concern is for nothing.
Thanks
I think you can keep the enum approach. But make use of the enum Singleton feature.
With that I mean you just have and INSTANCE enum value. Inside your enum you keep a hashmap with the code as key. Since your method uses the code as an input. See this:
public enum IDENT_EAN128_CACHE {
INSTANCE;
private static final Map<String, ParserStore> storage = new HashMap<>();
public synchronized IDENT_EAN128 search (String code) {
// If the code is already in the map return the needed values
// Else lazy initialize the hashmap entries if the requested entry is not contained and return it.
return 'the value you should return';
}
}
You are then able to access the functions like this:
IDENT_EAN128.INSTANCE.search("some code");
Edit:
With this design you cannot keep the IDENT_EAN128 as an Enum if you want to lazy initialize it. It should be an object initialized by the "enum cache" I provided the code for.
Edit 2: The modifications of the code suggested by Mike Strobel in the comments.
For the most part, your design is good, although I would use a statically-built map to do your search rather than looping over values(). I would not worry about the extra memory or CPU cycles involved with creating a couple hundred extra objects. They are only created once per JVM, so no big deal unless the objects are mammoth, or if you are running on a 286 from 1990. Clear design is more important. Note that I am also assuming VariableParser and FixedParser are classes, and inserted the new keyword in front of them to call the constructors.
You could populate the codeMap lazily if you want, but that won't buy you much. Since all of the objects are already created, you would just be saving a tiny bit of initialization in creating Map entries.
public enum IDENT_EAN128 {
// Identifier 8003 that has a value composed by 14 numeric chars and 1 to 20 alphanumeric chars
IDENT_8003 ("8003", new FixedParser(14, NUMERIC), new VariableParser(20, ALPHANUMERIC)),
IDENT_00 ("00", new FixedParser(18, NUMERIC));
// ... hundred identifiers
private static final HashMap<String, IDENT_EAN128> codeMap = new HashMap<String, IDENT_EAN128> ();
static {
for(IDENT_EAN128 ident: IDENT_EAN128.values()) {
codeMap.put(ident.getCode(), ident);
}
}
private String code;
private IDENT_EAN128 (String code, Parser... parsers ) {
this.code = code;
// do something with parsers as well...
}
public String getCode() {
return code;
}
public static IDENT_EAN128 search (String code) {
return codeMap.get(code);
}
}
Thanks for your answers. After consider them I have decided to keep Enumeration design but adding HashMap Matt suggestion and a lazy inizialitation for parsers in each identifier. So, code looks like this:
public enum IDENT_EAN128 {
// Identifier 8003 that has a value composed by 14 numeric chars and 1 to 20 alphanumeric chars
IDENT_8003 ("8003") {
#Override public Parser[] getParsers () {
return new Parser[]{new FixedParser(14, NUMERIC), new VariableParser(20, ALPHANUMERIC)};
}
},
.... // hundred identifiers
private static final HashMap<String, IDENT_EAN128> codeMap = new HashMap<String, IDENT_EAN128> ();
static {
for (IDENT_EAN128 ident: IDENT_EAN128.values())
codeMap.put(ident.getCode(), ident);
}
private IDENT_EAN128 (String code) {
...
}
public static IDENT_EAN128 search (String code) {
return codeMap.get(code);
}
public abstract Parser[] getParsers ();
}
This question already has answers here:
How do I compare strings in Java?
(23 answers)
Closed 9 years ago.
I am having a hashMap which stores my values like that:
{10997=Event [Numb=0007449001, date=07.02.2008], 10998=Event [Numb=0007449001, date=08.04.2008], ...
As you can see the key is a integer and the value is an Event Object which has a numeric value and a date, which is saved as a String.
I want to compare the numeric value of the map to another value with an if statment.
I tried:
if(numericVal==eventMap.get(i).toString()) {
However that does not really work. How to get the value easily out of the HashMap and of the Object?
I appreciate your answer!
UPDATE
By easily I mean performance orientated, because I have to process a lot of values.
first off; do not compare Strings with == use equalsIgnoreCase() instead (or just equals() if the casing matters)
second; consider giving your Event a compareTo() and while you are at it an equals() and a hashCode().
Once you got the components in place and why they should be there, you'll realize what you want to do is possible in a much more elegant way.
If I understand correctly you are trying to compare the actual numeric value within your EventObject
Therefore your EventObject has to have an accesor to it.
Lets say your EventObject looks something like:
public class EventObject {
private String numericValue;
private String date;
public EventObject(String numericValue, String date) {
this.numericValue = numericValue;
this.date = date;
}
public String getNumericValue() {
return numericValue;
}
public void setNumericValue(String numericValue) {
this.numericValue = numericValue;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
}
From there now accessing the numericValue from a HashMap should be as easy as:
HashMap<Integer,EventObject> map = new HashMap<>();
map.put(10997,new EventObject("0007449001", "07.02.2008"));
int keyMap = 10997; //for sake of making obvious this is Key and not numericValue
String numValue = hashMap.get(keyMap).getNumericValue();
A comparison then can be done by:
if("0007449001".equals(map.get(10997).getNumericValue())) {
System.out.println("The Date I have is: " + map.get(10997).getDate());
}
Which will get you:
The Date I have is: 07.02.2008
An example is as follows:
SEG1|asdasd|20111212|asdsad
SEG2|asdasd|asdasd
SEG3|sdfsdf|sdfsdf|sdfsdf|sdfsfsdf
SEG4|sdfsfs|
Basically, each SEG* line needs to be parsed into a corresponding object, defining what each of those fields are. Some, such as the third field in SEG1 will be parsed as a Date.
Each object will generally stay the same but there may be instances in which an additional field may be added, like so:
SEG1|asdasd|20111212|asdsad|12334455
At the moment, I'm thinking of using the following type of algorithm:
List<String> segments = Arrays.asList(string.split("\r"); // Will always be a CR.
List<String> fields;
String fieldName;
for (String segment : segments) {
fields = Arrays.asList(segment.split("\\|");
fieldName = fields.get(0);
SEG1 seg1;
if (fieldName.compareTo("SEG1") == 0) {
seg1 = new Seg1();
seg1.setField1(fields.get(1));
seg1.setField2(fields.get(2));
seg1.setField3(fields.get(3));
} else if (fieldName.compareTo("SEG2") == 0) {
...
} else if (fieldName.compareTo("SEG3") == 0) {
...
} else {
// Erroneous/failure case.
}
}
Some fields may be optional as well, depending on the object being populated. My concern is if I add a new field to a class, any checks that use the expect field count number will also need to be updated. How could I go about parsing the rows, while allowing for new or modified field types in the class objects to populate?
If you can define a common interface for all to be parsed classes I would suggest the following:
interface Segment {}
class SEG1 implements Segment
{
void setField1(final String field){};
void setField2(final String field){};
void setField3(final String field){};
}
enum Parser {
SEGMENT1("SEG1") {
#Override
protected Segment parse(final String[] fields)
{
final SEG1 segment = new SEG1();
segment.setField1(fields[0]);
segment.setField1(fields[1]);
segment.setField1(fields[2]);
return segment;
}
},
...
;
private final String name;
private Parser(final String name)
{
this.name = name;
}
protected abstract Segment parse(String[] fields);
public static Segment parse(final String segment)
{
final int firstSeparator = segment.indexOf('|');
final String name = segment.substring(0, firstSeparator);
final String[] fields = segment.substring(firstSeparator + 1).split("\\|");
for (final Parser parser : values())
if (parser.name.equals(name))
return parser.parse(fields);
return null;
}
}
For each type of segment add an element to the enum and handle the different kinds of fields in the parse(String[])method.
You can use collections, e.g. ArrayList
You can use var-args
If you want to make it extensible, you may want to process each segment in a loop, instead of handling each occurance.
I would add a header row to your file format with the names of the fields being stored in the file so it looks something more like this:
(1) field1|field2|field3|field4|field5
(2) SEG1|asdasd|20111212|asdsad|
(3) SEG2|asdasd||asdasd|
(4) SEG3|sdfsdf|sdfsdf|sdfsdf|sdfsfsdf
(5) SEG4|sdfsfs|||
This is common for CSV files. I've also added more delimiters so that each line has five 'values'. This way a null value can be specified by just entering two delimiters in a row (see the third row above for an example where a null value is not the last value).
Now your parsing code knows what fields need to be set and you can call the setters using reflection in a loop. Pseudo code:
get the field names from the first line in the file
for (every line in the file except the first one) {
for (every value in the line) {
if (the value is not empty) {
use reflection to get the setter for the field and invoke it with the
value
}
}
}
This allows you to extend the file with additional fields without having to change the code. It also means you can have meaningful field names. The reflection may get a bit complicated with different types e.g. int, String, boolean etc. so I would have to say that if you can, follow #sethu's advice and use a ready-built proven library that does this for you.
Is there a necessity to use the same string with | as a delimiter? If the same classes are used to create the String, then its an ideal case for Xstream. Xstream will convert your java object into XML and back. Xstream will take care of the scenario where some fields are optional. You will not have write any code that parses your text. Here's a link:
http://x-stream.github.io/
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