Make code cleaner by making a method that can accept an enum - java

So currently, I have some code, that is extremely messy and does the opposite of following dry rules. This is killing me internally, and I would love to fix it, if I knew how.
I have a class called Commands, this have two enums in it. Subs, and Options. What I would like to do, is make a method, such as this one here:
public void makeTab(String args, List<String> command, Commands type) {
if (args.equals("")) {
for (Commands.type commd : Commands.type.values()) {
command.add(commd.name().toLowerCase());
}
} else {
for (Commands.type commd : Commands.type.values()) {
if (commd.name().toLowerCase().startsWith(args)) {
command.add(commd.name().toLowerCase());
}
}
}
}
Then, if that method actually worked like I wanted it to, I could then do this.
List<String> command = new ArrayList<>();
switch (args.length) {
case 1:
makeTab(args[0], command, Subs);
break;
case 2:
makeTab(args[1], command, Options);
break;
}
Very sadly though, as you should be able to tell, this doesn't work, specifically because of the "Commands type" bit in the method. The problem is, I don't know what to put there to work, I've tried "Class type", "enum type", "Enum type". So, because of this catastrophe, my code is currently looking like this.
List<String> comd = new ArrayList<>();
switch (args.length) {
case 1:
if (args[0].equals("")) {
for (Commands.Subs commd : Commands.Subs.values()) {
comd.add(commd.name().toLowerCase());
}
} else {
for (Commands.Subs commd : Commands.Subs.values()) {
if (commd.name().toLowerCase().startsWith(args[0])) {
comd.add(commd.name().toLowerCase());
}
}
}
break;
case 2:
if (args[1].equals("")) {
for (Commands.Options commd : Commands.Options.values()) {
comd.add(commd.name().toLowerCase());
}
} else {
for (Commands.Options commd : Commands.Options.values()) {
if (commd.name().toLowerCase().startsWith(args[1])) {
comd.add(commd.name().toLowerCase());
}
}
}
break;
}
tl;dr I'm trying to make the last code block cleaner by making a method for the if, else.

Rather than taking values yourself, have the caller pass values() to you:
public void makeTab(String args, List<String> command, Enum[] values) {
for (Enum commd : values) {
String lowerName = commd.name().toLowerCase()
if (lowerName.startsWith(args)) {
command.add(lowerName);
}
}
}
The caller would invoke your method as follows:
makeTab(args, command, Options.values());
makeTab(args, command, Subs.values());
Note that there is no need to check args to be an empty string, because when args is empty, startsWith(args) returns true for any String value.

Related

How do you create a class of enums without a instance variable?

I have a class with a list of enums, The enums are passed to a constructor and updated to the toString, but I am not allowed to have an instance variable on the class (part of the requirement). How can I make the enums output like the String without adding an instance?
public enum Other {
GAME_BOY("Game Boy"), MACBOOK("Macbook Pro"), IPHONE("iPhone XS"), LAPTOP("Laptop");
private final String product; //can't have instance variable
private Other(String passed) {
this.product = passed;
}
#Override
public String toString() {
return product;
}
}
You can override toString() for each element:
public enum Other {
GAME_BOY {
#Override public String toString() { return "Game Boy"; }
},
MACBOOK { ... },
...
}
See this code run live at IdeOne.com.
Alternatively, you can put a switch statement in the toString method:
#Override
public String toString() {
switch (this) {
case GAME_BOY:
return "Game boy";
case MACBOOK:
return "Macbook Pro";
...
}
}
How about the following:-
public enum Other {
GAME_BOY, MACBOOK, IPHONE, LAPTOP;
#Override
public String toString() {
switch(this) {
case GAME_BOY:
return "Game Boy";
case MACBOOK:
return "Macbook Pro";
case IPHONE:
return "iPhone XS";
case LAPTOP:
return "Laptop";
default:
return null;
}
}
public static void main(String[] args) {
System.out.println("The value of the other is " + Other.GAME_BOY.toString());
}
}
Enhanced switch
The accepted Answer by Williamson provides code that can be improved in Java 14 and later. The enhanced switch statement can prevent the problem of missing any of the enum objects in your list of switch cases. See: JEP 361: Switch Expressions.
The new syntax is also terse, easier to read.
Using the new approach looks like this:
package work.basil.example;
public enum Device
{
GAME_BOY, MACBOOK, IPHONE, LAPTOP;
#Override
public String toString ( )
{
return switch ( this )
{
case GAME_BOY -> "Game boy";
case MACBOOK -> "Macbook Pro";
case IPHONE -> "iPhone XS";
case LAPTOP -> "Laptop";
};
}
}
Now go back and add a new type of device, such as WATCH.
GAME_BOY, MACBOOK, IPHONE, LAPTOP, WATCH;
The compiler will flag your switch statement as incorrect. You will see a message like:
the switch expression does not cover all possible input values
Add the additional case in your switch, thereby satisfying the compiler. Then sleep well knowing a nasty bug was averted.
case WATCH -> "Watch";
Example usage:
// Example usage.
public static void main ( String[] args )
{
for ( Device device : Device.values() )
{
System.out.println( "device = " + device );
}
}

How to compare a string with an Enum in a switch?

i have an enum like below:
public enum TripType {
TRIP1,
TRIP2,
TRIP3,
TRIP4
}
Then i have a method which receives a string parameter setupTravel(String tripType) and i need to check the value of tripType with a TripType. I wish to use a switch-case as below:
setupTravel(String tripType){
switch (tripType){
case TripType.TRIP1.toString():
setup1();
break;
case TripType.TRIP2.toString():
setup2();
break;
}
}
But, in the line TripType.TRIP1.toString() it complains with:
Constant expression required
How can i fix it?
setupTravel(String tripType) {
try {
switch (TripType.valueOf(tripType)) {
case TRIP1:
setup1();
break;
case TRIP2:
setup2();
break;
}
} catch (IllegalArgumentException ex) {
// Handle invalid trip type here
}
}
Since your method seems to switch on the Enum string, why not just use the Enum? You may want to include a default unless you have all Enums in the switch block.
setupTravel(TripType type){
switch (type){
case TRIP1:
setup1();
break;
case TRIP2:
setup2();
break;
}
}
You would call it as
setupTravel(TripType.TRIP1)
If it will be useful for you, You can use an enum constructor instead of using a switch case.
Something like this:
public enum TripType {
TRIP1("setup1"), TRIP2("setup2");
String setup;
private TripType(String setup) {
this.setup = setup;
}
public String getSetup() {
return setup;
}
public static TripType getTripTypeByName(String name) {
return Arrays.stream(TripType.values()).filter(t -> t.name().equalsIgnoreCase(name)).findAny().orElse(null);
}
}
And getting enum based on the name, you can use like this:
TripType tripType = TripType.getTripTypeByName("trip1");
System.out.println(tripType != null ? tripType.getSetup() : null);

Elegant way to parse console commands in java

So im making an application that works via console commands (think of "database new db_name"). Whats the most elegant way to handle this sort of thing?
Heres my little prototype, basicly just tons of switches. I mean it works but it gets kinda messy once I want to add stuff like putting out all the parameters if you pass a wrong one or too few, so im wondering if theres a better option:
public static void main(String[] args) {
while (running) {
Scanner in = new Scanner(System.in);
String cmd = in.nextLine();
parseCmd(cmd);
}
}
private static void parseCmd(String s) {
String[] cmd = s.split(" ");
try {
switch (cmd[0]) {
case "exit":
running = false;
System.out.println("Shutting down...");
break;
case "database":
switch (cmd[1]) {
case "new":
databases.add(new Database(cmd[2]));
break;
default:
System.out.println("Unknown parameter");
break;
}
break;
default:
System.out.println("Unknown command");
break;
}
} catch (Exception ArrayIndexOutOfBoundsException) {
System.out.println("Too few arguments passed");
}
}
I don't know about the most elegant way, but I've done something like this a couple of times and came up with this solution.
Make a class for "database" that represents the command, once you find the string "database", you retrieve an instance of this class and pass it the remaining arguments, so it can do the rest of the parsing.
class DataBaseCommand implements Command {
#Override
public void eval(String[] args) {...}
}
You can then keep a static map of these commands mapped to their names:
private static final Map<String, Command> commands = new HashMap<>();
static {
commands.put("database", new DataBaseCommand());
}
switch (cmd[0]) {
case "exit":
running = false;
System.out.println("Shutting down...");
break;
default:
if(commands.contains(cmd[0])) {
commands.get(cmd[0]).eval(cmd);
} else {
System.out.println("Unknown command");
}
break;
}
Maybe you could use a library that does a lot of those command parsing for you, like joptsimple?
I would suggest using http://jcommander.org/.Its simple and elegant. In it you can use inbuilt annotations
e.g Create a class like this using jcommander annotaions
#Parameters(commandNames = {"test", "Manages dates"})
public class CommandOption {
#Parameter(names = {"-e", "--exit"}, description = "bla bla")
public boolean exit = false;
}
then in main method
public static void main(String... args){
CommandOption opt = new CommandOption();
JCommander jc = new JCommander(opt);
jc.parse(args);
//and now use
System.out.println("value" +opt.exit)
}
and then can pass parmaeters as java class-e true or maybe better if you can create jar and then java -jar 'jar' -e true

How to call a method whose name is the value of a string variable in java?

This is the code of the method that I want to simplify. The method name I call of SerializedExpFamMixture class is exactly the value of "model", my question is how to assign the value of "model" directly as the name of the method instead of using "if" to determine which method I should call. Since by using "if", I need to list all the possible values of "model" and judge which method I should use.
Thank you very much for help. I am new to java.
public static SerializedExpFamMixture RateMtxModel(String model)
{
SerializedExpFamMixture result=new SerializedExpFamMixture();
if(model=="kimura1980()")
result=SerializedExpFamMixture.kimura1980();
if(model=="accordance()")
result=SerializedExpFamMixture.accordance();
if(model=="pair()")
result=SerializedExpFamMixture.pair();
return result;
}
One way you can approach this is to use Reflection:
Method method = myClass.getClass().getMethod("doSomething", null);
method.invoke(myClass, null);
Since you are new to Java, it's time for some general pointers:
In Java, we usually name our methods with camelCase, so the first letter is lower case.
Also, in Java we usually leave the opening curly-bracket on the same line as the code (no newline).
Always use final on your variables. At least your parameters. That way you won't overwrite it, and thus won't have to try to figure out which value it actually has at runtime.
Use curly-brackets! Please!
The result variable is not actually needed.
Use the equals-method to compare Strings.
If you only want one result, use else-if
Fixing these things, your method looks like this:
public static SerializedExpFamMixture rateMtxModel(String model) {
if (model.equals("kimura1980()")) {
return SerializedExpFamMixture.kimura1980();
} else if (model.equals("accordance()")) {
return SerializedExpFamMixture.accordance();
} else if(model.equals("pair()")) {
return SerializedExpFamMixture.pair();
}
return new SerializedExpFamMixture();
}
Next, let's look at what you are actually trying to do here. You want to pass some Strings around, and use them as a basis for creating objects. And now, with the advice given here, you will do this using reflection. This does not sound like a very good idea to me. Say you were to go through with this, and this happened:
rateMtxModel("kinura1980");
Small typo, hard to spot, will give unexpected results. If you were actually calling a method the compiler would let you know that you messed up, now you will get no warning (btw did you see both errors in that method call?). The same if someone were to delete the accordance()-method, the compiler would not alert them that this will break the program.
If it was up to be I would just use the static factory-methods in SerializedExpFamMixture directly, but if you have to do it like this (if the task at hand is using a String input to create an object) I would do something like this:
public enum Something {
KIMURA1980("kimura1980()"),
ACCORDANCE("accordance()"),
PAIR("pair()");
private final String stringValue;
private Something(final String stringValue) {
this.stringValue = stringValue;
}
public static Something fromString(final String string) {
for (final Something something : values()) {
if (something.stringValue.equals(string)) {
return something;
}
}
return null;
}
}
public static SerializedExpFamMixture rateMtxModel(final String model) {
if (model == null) {
throw new IllegalArgumentException("model is null!");
}
final Something something = Something.fromString(model);
if (something == null) {
return new SerializedExpFamMixture();
}
switch(something) {
case KIMURA1980:
return SerializedExpFamMixture.kimura1980();
case ACCORDANCE:
return SerializedExpFamMixture.accordance();
case PAIR:
return SerializedExpFamMixture.pair();
default:
return new SerializedExpFamMixture();
}
}
This way, the one place where you will use the Strings is in the enum, the rest of the code will use the enum constants and thus have the safety of the compiler to rely on.
One could also leave the linking between operation and String to the enum, like this:
interface Operation<T> {
public T run();
}
public enum Something {
KIMURA1980("kimura1980()", new Operation<SerializedExpFamMixture>() {
public SerializedExpFamMixture run() {
return SerializedExpFamMixture.kimura1980();
}
}) ,
ACCORDANCE("accordance()", new Operation<SerializedExpFamMixture>() {
public SerializedExpFamMixture run() {
return SerializedExpFamMixture.accordance();
}
}),
PAIR("pair()", new Operation<SerializedExpFamMixture>() {
public SerializedExpFamMixture run() {
return SerializedExpFamMixture.pair();
}
}),
DEFAULT(null, new Operation<SerializedExpFamMixture>() {
public SerializedExpFamMixture run() {
return new SerializedExpFamMixture();
}
});
private final String stringValue;
private final Operation<SerializedExpFamMixture> operation;
private Something(final String stringValue, final Operation<SerializedExpFamMixture> operation) {
this.stringValue = stringValue;
this.operation = operation;
}
public static Something fromString(final String string) {
if (string != null) {
for (final Something something : values()) {
if (string.equals(something.stringValue)) {
return something;
}
}
}
return DEFAULT;
}
public SerializedExpFamMixture getCorrespondingSerializedExpFamMixture() {
return operation.run();
}
}
With this setup in the enum (I think the Operation-part can be trimmed out with Java8), the method will be as simple as:
public static SerializedExpFamMixture rateMtxModel(String model) {
return Something.fromString(model).getCorrespondingSerializedExpFamMixture();
}
Use reflection, but you need to consider a few things:
Bug alert! Comparing Strings using == doesn't work as expected in java - use .equals() instead. However, the solution below bypasses that problem
For the general case, which includes methods not visible to the invoker, you need to consider accessibility, both in finding the method and invoking it
You don't need the result variable, and even if using your code, don't need to initialize it
Try this:
String methodName = model.replace("(", "").replace(")", "");
try {
// getMethod() returns only public methods, getDeclaredMethod() returns any visibility
Method method = SerializedExpFamMixture.class.getDeclaredMethod(methodName);
// if the method is not guaranteed to be visible (eg public) you need this:
method.setAccessible(true);
return (SerializedExpFamMixture) method.invoke(null); // how to invoke on the class object
} catch (Exception forBrevity) {
return new SerializedExpFamMixture();
}

How to use interface methods randomly?

I have an interface and these methods:
public interface Form {
public void setFirstName (String value);
public void setLastName (String value);
public void setGender (String value);
}
Can I call these methods randomly on an objet? Something like:
form.randomMethodFromFormInterface(String randomString);
Is it actually possible? Just to clarify, I would like to fillout the form randomly: sometimes just the last name, sometimes just the first name, sometimes just the gender.
Random rand = new Random();
switch (rand.nextInt(3)) {
case 0: myForm.setFirstName(myFirstName); break;
case 1: myForm.setLastName(myLastName); break;
case 2: myForm.setGender(myGender); break;
default: throw new IllegalStateException();
}
Couldn't you use Random to pick from 0-2, and then depending on that value call the corresponding method?
Could you make another method in the interface that generates a random number and calls a method based on that number? Although I would bet there's an easier way to do this than creating an interface for it.
Here a general way, using reflection:
private static Random r = new Random();
public static void callRandomMethod(Object target, Class<?> iface, Object ... arguments) {
List<Method> methods = findFittingMethods(iface, arguments);
Method m = methods.get(r.nextInt(methods.size()));
m.invoke(target, arguments);
}
public List<Method> findFittingMethods(Class<?> iface, Object ... arguments
Method[] allMethods = iface.getMethods();
List<Method> fittingMethods = new ArrayList<Method>();
findMethodLoop:
for(Method candidate : allMethods) {
Class<?>[] argumentTypes = candidate.getArguments();
if(argumentTypes.length != arguments.length) {
continue;
}
// check argument types
for(int i = 0; i < argumentTypes.length; i++) {
if(arguments[i] == null) {
if(argumentTypes[i].isPrimitive()) {
// null can't be passed to a primitive argument.
continue findMethodLoop;
}
else {
// ... but to every other argument type.
continue; // check next argument
}
}
if(argumentTypes[i].isInstance(arguments[i])) {
continue; // check next argument
}
if(argumentTypes[i].isPrimitive()) {
// hack to check if we have the right wrapper class
try {
Array.set(Array.newInstance(argumentTypes[i], 1), 0, arguments[i]);
continue; // check next argument
}
catch(ArrayStoreException ex) {
continue findMethodLoop;
}
}
// wrong type
continue findMethodLoop;
}
// now we found a method which would accept the arguments, put it into the list.
fittingMethods.add(candidate);
}
return fittingMethods;
}
Of course, if you do this often, you would not create the list of methods for every call, but only once, and reuse it then. (And if you only have a known interface with a low number of methods, use the switch statement instead, like others have recommended.)
You can place the various method names in an array structure.
Then choose a random index within the scope of the array.
Then use reflection to actually call the method using the randomly chosen name from the previous step
Why not make the following method:
public static void randomMethodFromFormInterface(Form form, String value) {
switch(random.nextInt(3) {
case 0:
form.setFirstName(value);
break;
case 1:
form.setLastName(value);
break;
case 2:
form.setGenderName(value);
break;
}
}
You can put it in a utility class. random here is, of course, an instance of java.util.Random.
Can I call these methods randomly on
an objet?
Yes, this is possible with Reflection. The randomness is not implemented in this example (I assume that you can easily do this with a random int) and all methods are called without knowing how they are named or how many methods are available. For simplicity the example assumes that the parameter is only a String (like in your example). Of course, you must instantiate a class which implements Form:
Class thisClass = Class.forName("FormImpl");
Object o = thisClass.newInstance();
Method[] methods = thisClass.getDeclaredMethods();
for(Method m : methods)
{
m.invoke(o, "test");
}
You could do something like below. However, I am not sure if I really like the idea of calling methods in an interface randomly. It breaks the contract in a way and sounds like a bad design idea in my opinion.
import java.util.Random;
public class RandomInterfaceImpl implements RandomInterface {
private Random rnd;
public RandomInterfaceImpl(){
rnd = new Random();
}
#Override
public void setFirstName(String value) {
System.out.println("called setFirstName");
}
#Override
public void setLastName(String value) {
System.out.println("called setLastName");
}
#Override
public void setGender(String value) {
System.out.println("called setGender");
}
#Override
public void getNextRandomMethod(String value) {
int nextRand = rnd.nextInt(3);
switch(nextRand){
case 0: setFirstName(value); break;
case 1: setLastName(value); break;
case 2: setGender(value); break;
}
}
}
public static void main(String[] args) {
RandomInterface myInterface = new RandomInterfaceImpl();
myInterface.getNextRandomMethod("Foo");
myInterface.getNextRandomMethod("Foo");
myInterface.getNextRandomMethod("Foo");
}
prints:-
called setFirstName
called setLastName
called setLastName

Categories