Is there any alternative way to implement a switch case in Java other than if else which is not looking good. A set of values will be there in combination, according to the selection corresponding method has to be executed.
If you have plenty of switch/case statements around your code and they are driving you crazy.
You could opt for the Refactoring: Replace conditional with polymorphism.
Let's say you have a piece of software that is used to save information to different devices: 4 persistence operations are defined: fetch, save, delete, update, which could be implemented by N number of persistence mechanism ( flat files, network, RDBMS, XML, etc ) .
Your code have to support them all so in 4 different places you have this:
BEFORE
class YourProblematicClass {
....
public void fetchData( Object criteria ) {
switch ( this.persitanceType ) {
case FilePersistance:
// open file
// read it
// find the criteria
// build the data
// close it.
break;
case NetWorkPersistance:
// Connect to the server
// Authenticate
// retrieve the data
// build the data
// close connection
break;
case DataBasePersistace:
// Get a jdbc connection
// create the query
// execute the query
// fetch and build data
// close connection
break;
}
return data;
}
Same for save/delete/update
public void saveData( Object data) {
switch ( this.persitanceType ) {
case FilePersistance:
// open file, go to EOF, write etc.
break;
case NetWorkPersistance:
// Connect to the server
// Authenticate
// etc
break;
case DataBasePersistace:
// Get a jdbc connection, query, execute...
break;
}
}
And so on....
public void deleteData( Object data) {
switch ( this.persitanceType ) {
case FilePersistance:
break;
case NetWorkPersistance:
break;
case DataBasePersistace:
break;
}
}
public void updateData( Object data) {
switch ( this.persitanceType ) {
case FilePersistance:
break;
case NetWorkPersistance:
break;
case DataBasePersistace:
break;
}
}
Using switch/case statement becomes problematic:
Each time you want to add a new type you have to insert new switch/case in each section.
Many times, some types are similar, and they don't need a different switch/case ( you could cascade them )
Some other they are, and some times they differ slightly
You may even need to load different type at runtime ( like plugins )
So the refactoring here would be to add an interface or abstract type and have the different types implement that interface and delegate the responsibility to that object.
So you would have something like this:
AFTER
public interface PersistenceManager {
public void fetchData( Object criteria );
public void saveData( Object toSave );
public void deleteData( Object toDelete );
public void updateData( Object toUpdate );
}
And different implementations
public class FilePersistence implements PersistanceManager {
public void fetchData( Object criteria ) {
// open file
// read it
// find the criteria
// build the data
// close it.
}
public void saveData( Object toSave ) {
// open file, go to EOF etc.
}
public void deleteData( Object toDelete ){
....
}
public void updateData( Object toUpdate ){
....
}
}
And the other types would implement according to their logic. Network would deal with sockets, and streams, DB would deal with JDBC, ResultSets etc. XML with node etc.etc.
public class NetworkPersistence implements PersistanceManager {
public void fetchData( Object criteria ) {
// Socket stuff
}
public void saveData( Object toSave ) {
// Socket stuff
}
public void deleteData( Object toDelete ){
// Socket stuff
}
public void updateData( Object toUpdate ){
// Socket stuff
}
}
public class DataBasePersistence implements PersistanceManager {
public void fetchData( Object criteria ) {
// JDBC stuff
}
public void saveData( Object toSave ) {
// JDBC stuff
}
public void deleteData( Object toDelete ){
// JDBC stuff
}
public void updateData( Object toUpdate ){
// JDBC stuff
}
}
And finally you just have to delegate the invocations.
Later:
public YouProblematicClass { // not longer that problematic
PersistamceManager persistance = // initialize with the right one.
public void fetchData( Object criteria ) {
// remove the switch and replace it with:
this.persistance.fetchData( criteria );
}
public void saveData( Object toSave ) {
// switch removed
this.persistance.saveData( toSave );
}
public void deleteData( Object toDelete ){
this.persistance.deleteData( toDelete );
}
public void updateData( Object toUpdate ){
this.persistance.updateData( toUpdate );
}
}
So, you just have to create the correct instance for the persistence manager according to the type only once. Then all the invocations are resolved by polymorphism. That's one of the key features of Object Oriented Technology.
If you decide you need another persistence manager, you just create the new implementation and assigned to the class.
public WavePersistance implements PersistanceManager {
public void fetchData( Object criteria ) {
// ....
}
public void saveData( Object toSave ) {
// ....
}
public void deleteData( Object toDelete ){
// ....
}
public void updateData( Object toUpdate ){
// ....
}
}
Presumably you're struggling with the requirement of case's being constant. Typically this is a code-smell, but there are things you can do. You might want to raise and link to another question that details why you're trying to switch.
Map<String,Object> map = new HasMap<String,Object>();
// ... insert stuff into map
// eg: map.add("something", new MyObject());
String key = "something";
if (map.contains(key)) {
Object o = map.get(key);
}
In the example above, you might want to map to 'handlers', something like
interface Handler {
public void doSomething();
}
which then makes this all turn into a lookup.
if (map.contains(key)) { map.get(key).doSomething(); }
Again, it's a bit of a smell, so please post a question which illustrates the reasoning.
Refactoring your code to use polymorphism could get rid of the need for a switch statement. However, there are some legitimate uses for switch so it depends on your situation.
a ugly series of if,else if,else ?
or one could imagine a kind of dynamic switch case:
public interface Task<T>
{
public void doSomething(T context);
}
public Class SwitchCase<T>
{
Map<Integer,Task<T>> tasks;
Task<T> defaultTask;
public void choose(int choice, T context)
{
Task<T> t= this.tasks.get(choice);
if(t!=null) { t.doSomething(context); return;}
if(defaultTask!=null) { defaultTask.doSomething(context);}
}
}
I guess "Clean Code" has a nice chapter according switch/case vs. if/else.
Besides: I think it makes sense to decide whether you can reduce "noise" and make the code cleaner by using switch case, polymorphism or even a good ol' if/else. The number of cases plays a major role here, I guess.
I post a typical case how I replaced switch case with enum.
before refactor I have enum PatternTypes:
public enum PatternTypes {
ALPHA_CHAR, ALPHANUMERIC_CHAR, ADDITIONAL_CHAR, UNICODE_BMP_CHARS
}
and function:
private static final String ALPHA_CHAR = "[a-zA-Z]+";
private static final String ALPHANUMERIC_CHAR = "[a-zA-Z0-9\\_]+";
private static final String ADDITIONAL_CHAR = "[a-zA-Z0-9\\_\\-\\,\\.\\s\\!\\#\\$\\&\\(\\)\\*\\+\\;\\:\\=\\?\\#\\|\\[\\]\\{\\}\\~]+";
private static final String UNICODE_BMP_CHARS = "[a-zA-Z0-9\\_\\-\\,\\.\\s\\!\\#\\$\\&\\(\\)\\*\\+\\;\\:\\=\\?\\#\\|\\[\\]\\{\\}\\~\u00A0-\uD7FF\uF900-\uFFFD]+";
/*
* Match given classAbbr with given RegEx pattern
*/
private void checkInvalidClassAbbr(String classAbbr,
PatternTypes classAbbrPattern) {
switch (classAbbrPattern) {
case ALPHA_CHAR:
checkUnmatched(classAbbr, ALPHA_CHAR, CLASS_ABBR_VAR_NAME);
break;
case ALPHANUMERIC_CHAR:
checkUnmatched(classAbbr, ALPHANUMERIC_CHAR, CLASS_ABBR_VAR_NAME);
break;
case ADDITIONAL_CHAR:
throw new MalFormedDNException("Not support Pattern Type:"
+ classAbbrPattern);
case UNICODE_BMP_CHARS:
throw new MalFormedDNException("Not support Pattern Type:"
+ classAbbrPattern);
}
}
After refactor PatternTypes modified to:
public enum PatternTypes {
/**
* RegEx patterns divided by restriction level
*/
ALPHA_CHAR("[a-zA-Z]+"),
ALPHANUMERIC_CHAR("[a-zA-Z0-9\\_]+"),
ADDITIONAL_CHAR("[a-zA-Z0-9\\_\\-\\,\\.\\s\\!\\#\\$\\&\\(\\)\\*\\+\\;\\:\\=\\?\\#\\|\\[\\]\\{\\}\\~]+"),
UNICODE_BMP_CHARS("[a-zA-Z0-9\\_\\-\\,\\.\\s\\!\\#\\$\\&\\(\\)\\*\\+\\;\\:\\=\\?\\#\\|\\[\\]\\{\\}\\~\u00A0-\uD7FF\uF900-\uFFFD]+");
public String getPatternContent() {
return patternContent;
}
private String patternContent;
PatternTypes(String patternContent) {
this.patternContent = patternContent;
}
}
and function simplify to:
/*
* Match given classAbbr with given RegEx pattern
*/
private void checkInvalidClassAbbr(String classAbbr, PatternTypes classAbbrPattern) {
if (PatternTypes.ADDITIONAL_CHAR.equals(classAbbrPattern) || PatternTypes.UNICODE_BMP_CHARS.equals(classAbbrPattern)){
throw new MalFormedDNException("RegEx pattern:" + classAbbrPattern.name() + "is not allowed for Class Abbr");
}
checkUnmatched(classAbbr, classAbbrPattern.getPatternContent(), CLASS_ABBR_VAR_NAME);
}
Hashmap is considered not to be memory friendly, so you can use Enum for this purpose.
Example:
class EnumExample4{
enum Season{
WINTER(5), SPRING(10), SUMMER(15), FALL(20);
private int value;
private Season(int value){
this.value=value;
}
}
public static void main(String args[]){
System.out.println(Season.WINTER.value); //This gives you 5
}}
This will sve you from writing Switch Case or if statements.
For an alternate to switch statement, I think the best solution will be using an enum. For example: Consider the case below:-
public enum EnumExample {
OPTION1{
public double execute() {
Log.info(CLASS_NAME, "execute", "The is the first option.");
return void;
}
},
OPTION2{
public double execute() {
Log.info(CLASS_NAME, "execute", "The is the second option.");
return void;
}
},
OPTION3{
public double execute() {
Log.info(CLASS_NAME, "execute", "The is the third option.");
return void;
};
public static final String CLASS_NAME = Indicator.class.getName();
public abstract void execute();
}
The above enum can be used in the following fashion:
EnumExample.OPTION1.execute();
Hopefully this helps you guys.
What do you want to do? Why is not Switch-Case good enough?
The fast answer is: use if-else
if () {}
else if () {}
...
else if () {}
?
But I wouldn't say it is better...
How about an if (along with else if and else) statement? While switch will only allow you to switch using equality against integer or Enum types, if lets you use any boolean logic.
You could always replace a switch with if-else if-else if-else if..., though I don't see why you'd want to. Depending on the context switchs can also sometimes be replaced by arrays or hashmaps.
If the strings are static, You can make an ENUM.
and do a switch on it.
Related
I have a list of methods within my class. And then want to have input string array, where the user can choose which methods they want to run. We are running expensive insurance calculations. And have over say eg 20 methods. Is there a way to conduct this without do an if check on each? maybe with reflection or interface?
#Override
public void ProductTest(ProductData productData, String[] methodNames) {
public void methodA(ProductData productData){...};
public void methodB(ProductData productData){...};
public void methodC(ProductData productData){...};
public void methodD(ProductData productData){...};
public void methodE(ProductData productData){...};
}
I am willing to change the Array into a different ObjectType if needed, to execute properly. Using SpringBoot, has it has a library of utility classes.
Use a Map<String, Consumer<ProductData>>, not separate method handles. Main reason - reflection is slow and dangerous when given user "input"
Use map.get(input).accept(product) to call it.
https://docs.oracle.com/javase/8/docs/api/index.html?java/util/function/Consumer.html
Example
Map<String, Consumer<ProductData>> map = new HashMap<>();
map.put("print_it", System.out::println);
map.put("print_id", data -> System.out.println(data.id));
map.put("id_to_hex", data -> {
int id = data.getId();
System.out.printf("0x%x%n", id);
});
ProductData data = new ProductData(16);
map.get("print_it").accept(data);
map.get("print_id").accept(data);
map.get("id_to_hex").accept(data);
Outputs
ProductData(id=16)
16
0x10
If you are planning on chaining consumers using andThen, you'd be better having an Optional<ProductData>, and using a Function<ProductData, ProductData> with Optional.map()
One way to do it is via reflection. You can iterate over methods in the class object and look for ones to run by name. Here's some example code--this would print out a list of names the user could type in:
myObject.getClass().getDeclaredMethods().each((method)->System.out.println(method.getName()))
And this is how you would call it once the user had made a selection:
productTest.getDeclaredMethods().each((method)->
if(method.getName().equals(userSelectedName))
method.invoke(productTest, productData)
)
The ONLY advantage to this approach is that you don't have to maintain a second structure (Switch, Map, etc...) and add to it every time you add a new method. A personality quirk makes me unwilling to do that (If adding something one place forces you to update a second, you're doing it wrong), but this doesn't bother everyone as much as it bothers me.
This isn't dangerous or anything, if you don't have a method in the class it can't call it, but if you are relying on users "Typing", I'd suggest listing out the options and allowing a numeric selection--or using reflection to build a map like OneCricketeer's.
I've used this pattern to write a testing language and fixture to test set-top TV boxes, it was super simple to parse a group of strings, map some to methods and other to parameters and have a very flexible, easily extensible testing language.
The method object also has a "getAnnotation()" which can be used to allow more flexible matching in the future.
You can use method invocation.
For example, you can have two methods, first one will loop through your methodNames array and call the second method:
public void callPassedMethods(ProductData productData, String[] methodNames) {
for (String m : methodNames) {
callMethod(productData, m)
}
}
And the second method will actually find a method in your class that matches the string passed and invoke it:
public void callMethod(ProductData productData, String methodName) {
try {
ClassName yourObj = new ClassName(); // Class where your methods are
Method method = yourObj.getClass().getDeclaredMethod(methodName, ProductData.class);
method.invoke(yourObj, productData);
} catch(NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// handle exceptions
}
}
Or, you can always use the good old switch statement:
for (String m : methodNames) {
switch (m) {
case "methodA":
methodA();
break;
case "methodB":
methodB();
break;
// ... continue with as many cases as you need
}
}
If you go with the reflection route, you don't really want to expose your method names to the end users. They might not be end user-friendly, and if they are, there is no reason for users to know this information and there might be methods, which are not supposed to be invoked by users. I would use custom annotations to build more flexible matching.
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface UserChoice {
String userFriendlyOption();
int optionNumber();
}
optionNumber will be used for matching the method to invoke, userFriendlyOption is some user friendly text.
Annotate only the methods, supposed to be used by users.
#RequiredArgsConstructor
public class ProductData {
private final double data;
#UserChoice(userFriendlyOption = "see result for option a", optionNumber = 1)
public void methodA() {
System.out.println(data + 1);
}
#UserChoice(userFriendlyOption = "see result for option b", optionNumber = 2)
public void methodB() {
System.out.println(data + 2);
}
#UserChoice(userFriendlyOption = "see result for option c", optionNumber = 3)
public void methodC() {
System.out.println(data);
}
public void methodNotForUser() {
System.out.println("Should not be seen by users");
}
}
Like this methodNotForUser() can't be invoked by end users.
Simplified matcher might look like this.
#RequiredArgsConstructor
public class ProductTester {
private final ProductData data;
private Map<Integer, MethodData> map;
public void showOptions() {
if (this.map == null) {
this.map = new HashMap<>();
for (Method method : this.data.getClass().getMethods()) {
UserChoice userChoice = method.getAnnotation(UserChoice.class);
if (userChoice != null) {
String userRepresentation = userChoice.optionNumber() + " - " + userChoice.userFriendlyOption();
this.map.put(userChoice.optionNumber(), new MethodData(userRepresentation, method));
}
}
}
this.map.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(entry -> System.out.println(entry.getValue().getUserRepresentation()));
}
public void showOptionResult(int choice) {
MethodData methodData = this.map.get(choice);
if (methodData == null) {
System.out.println("Invalid choice");
return;
}
System.out.println("Result");
try {
methodData.getMethod().invoke(this.data);
} catch (IllegalAccessException | InvocationTargetException ignore) {
//should not happen
}
}
}
MethodData is simple pojo with the sole purpose to not recalculate user representation.
#RequiredArgsConstructor
#Getter
public class MethodData {
private final String userRepresentation;
private final Method method;
}
Short main to illustrate the idea and play around:
public class Temp {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("Write initial value");
double data = scanner.nextDouble();
ProductData myData = new ProductData(data);
ProductTester tester = new ProductTester(myData);
tester.showOptions();
System.out.println("Write option number");
int userChoice = scanner.nextInt();
tester.showOptionResult(userChoice);
}
}
The following code is an example of my problem.
I would like to simplify the code without having to repeat the call for the same methods, on different switch statements.
public void simulate(String given, Status status) {
switch (status){
case A:
simulateA(given);
break;
case B:
simulateA(given);
simulateB(given);
break;
case C:
simulateA(given);
simulateB(given);
simulateC(given);
break;
}
PS 1: The order of the calling methods matters!
PS 2: I am not looking for another way of doing the switch, I am looking for another way of modelling the problem, maybe using some kind of class composition with the methods.
I do not know the nature of your enum but if you have many simulation calls you could forgo the switch statement and do it like this. But there is nothing wrong with your current approach. This would also change slightly if your methods were static and not instance. The one advantage of this is that it has the potential to scale.
There are many other ways to to this. You could have a list of method references and the enum arguments could be variable arrays of which methods to call by index.
public class Simulations {
static List<BiConsumer<Simulations, String>> sims =
List.of(Simulations::simulateA, Simulations::simulateB,
Simulations::simulateC);
enum Status {
A(1), B(2), C(3);
private int val;
private Status(int v) {
this.val = v;
}
public int getVal() {
return val;
}
}
public static void main(String[] args) {
Simulations simulation = new Simulations();
simulation.simulate("A", Status.A);
System.out.println();
simulation.simulate("B", Status.B);
System.out.println();
simulation.simulate("C", Status.C);
}
public void simulate(String given, Status status) {
for (int i = 0; i < status.getVal(); i++) {
sims.get(i).accept(this, given);
}
}
public void simulateA(String s) {
System.out.println(s);
}
public void simulateB(String s) {
System.out.println(s);
}
public void simulateC(String s) {
System.out.println(s);
}
}
In this case, the order of simulations always cascades "downwards", e.g. Simulating an B is simulating an A plus some extra's. This matches an inheritance pattern, e.g. a Mammal is an Animal with some extras. Thus, letting simulations inherit from each other fixes the pattern:
interface Simulation
{
void simulate( final String given );
}
class ASimulation implements Simulation
{
#Override
public void simulate( String given )
{
// simulate this given!
}
}
class BSimulation extends ASimulation
{
#Override
public void simulate( String given )
{
super.simulate( given );
// simulate this given some more!
}
}
class CSimulation extends BSimulation
{
#Override
public void simulate( String given )
{
super.simulate( given );
// simulate this given even more!
}
}
Note that this is fragile, as all inheritance trees are. Another solution can be achieved with composition and delegation. This is called a chain:
class LeafSimulation
implements Simulation
{
#Override
public void simulate( String given )
{
// simulate this given!
}
}
class ChainedSimulation
implements Simulation
{
private final Simulation delegate;
ChainedSimulation( final Simulation delegate )
{
this.delegate = delegate;
}
#Override
public void simulate( String given )
{
delegate.simulate( given );
// simulate this given some more!
}
}
To instantiate the chain, use the following order:
final var aSimulation = new LeafSimulation();
final var bSimulation = new ChainedSimulation( aSimulation );
final var cSimulation = new ChainedSimulation( bSimulation );
This code approaches the problem statement more naturally and eliminates the repetition, but it is not concise.
Once you have set up a mapping of status values to method calls, you can use a SortedSet or EnumSet.range to get the enum values after a particular value:
Map<Status, Consumer<String>> simulators = new EnumMap<>(Map.of(
Status.A, this::simulateA,
Status.B, this::simulateB,
Status.C, this::simulateC));
if (!simulators.keySet().equals(EnumSet.allOf(Status.class))) {
throw new RuntimeException(
"Not all Status values have simulators defined.");
}
// ...
SortedSet<Status> all = new TreeSet<>(EnumSet.allOf(Status.class));
Collection<Status> remainingValues = all.tailSet(status);
// Or:
//Status[] allStatuses = Status.values();
//Status lastStatus = allStatuses[allStatuses.length - 1];
//Collection<Status> remainingValues = EnumSet.range(status, lastStatus);
for (Status s : remainingValues) {
simulators.get(s).accept(given);
}
Another option to consider, which avoids switch / if. Declare a map of actions per Status value which can be used with a getOrDefault lookup default for unhandled values:
Consumer<String> simA = this::simulateA;
Map<Status, Consumer<String>> actions = new EnumMap<>(Map.of(
Status.A, simA,
Status.B, simA.andThen(this::simulateB),
Status.C, simA.andThen(this::simulateB).andThen(this::simulateC)
));
actions.getOrDefault(status, s -> {}).accept(given);
If you want to guard against missing / unhandled mappings you should validate the map (as in #VGR answer) or swap the no-operation default with an exception handler:
actions.getOrDefault(status,
s -> { throw new RuntimeException("Missing action for status: "+status); }
).accept(given);
Assuming your first status is A you can do something like this:
public void simulate(String given, Status status) {
if (status != Status.A) {
int indexOfStatus = status.ordinal();
simulate(given, Status.values()[indexOfStatus - 1]);
}
switch (status){
case A:
simulateA(given);
break;
case B:
simulateB(given);
break;
case C:
simulateC(given);
break;
// here you still need to put all your "simulateX" calls but without repetitions
}
}
you don't need to write simulateA(given) to all your cases, just moved it to top
public void simulate(String given, Status status) {
simulateA(given);
switch (status){
case C:
simulateC(given);
case B:
simulateB(given);
break;
case A:
break;
}}
You can try the fallthrough mechanism of switch statement. refer to this
In your example, the code can be(not tested):
Edited:
public void simulate(String given, Status status) {
switch (status){
case C:
simulateC(given);
case B:
simulateB(given);
case A:
simulateA(given);
}
}
Original(Wrong):
public void simulate(String given, Status status) {
switch (status){
case A:
simulateA(given);
case B:
simulateB(given);
case C:
simulateC(given);
}
}
When the reader has the fallthrough concept in mind, the code above is cleaner and easier to read than the code in question. But that is not always the case. My recommendation would be to restructure your code so as to eliminate both the repetitive calls and the fallthroughs.
I've been wondering if there is a way for me to replace the current switch statement I have. Below is an example of the code I have, although the statement I have is a lot longer and will only get larger. The switch method gets called through a file reader so it reads a line then calls this function with values assigned.
public static void example(String action, String from, String to){
switch (action) {
case ("run"):
runTo(from,to);
break;
case ("walk"):
walkTo(from,to);
break;
case ("hide"):
hideAt(to);
break;
}
}
edit:
I was curious if there is a better way instead of using a switch statement like the above scenario.
I've updated the example a bit to make a little more sense. Some of the method calls dont need to use all of the parameters.
For Java 7 and below we can declare an interface for function implementation.
for Java 8+ we can use Function interface.
Interface:
public interface FunctionExecutor {
public Object execute(String from,String to);
}
Function Context:
public class FunctionContect {
HashMap<String, FunctionExecutor> context=new HashMap<String, FunctionExecutor>();
public void register(String name,FunctionExecutor function){
context.put(name, function);
}
public Object call(String name,String from,String to){
return context.get(name).execute(from, to);
}
public FunctionExecutor get(String name){
return context.get(name);
}
}
Function Implementations:
public class RunFunctionImpl implements FunctionExecutor{
#Override
public Object execute(String from, String to) {
System.out.println("function run");
return null;
}
}
// OTHER FUCNTIONS
Register Function:
FunctionContect contex = new FunctionContect();
contex.register("run", new RunFunctionImpl());
contex.register("walk", new WalkFunctionImpl());
contex.register("hide", new HideFunctionImpl());
Call Function
context.call(action, from, to);
or
context.get(action).execute(from,to);
I am not completely sure what you want to achieve.
If you don't want to keep adding new
case ("ccc"):
Lmn(b,c,i);
break;
blocks.
You can hash the methods in a HashMap<string, method> and get the method from the map using the key and execute it.
If you have a repetion of switch cases on the same variable, say in method f, g and h. Then you can turn things inside out:
void f(String a) {
switch (a) {
case "aaa": ... ; break;
...
}
}
void g(String a) {
switch (a) {
case "aaa": ... ; break;
case "bbb": ... ; break;
case "ccc": ... ; break;
...
}
}
void h(String a) {
switch (a) {
case "aaa": ... ; break;
...
}
}
Can be handled object orientedly as:
class C {
public f() { }
public g() { }
public h() { }
}
class Aaa extends C {
#Override
public f() { test3(b,c); } // Or even just the body of test3
#Override
public g() { }
#Override
public h() { }
}
class Bbb extends C {}
class Ccc extends C {}
Then once one has to provide a specific C:
C c;
switch (a) {
case "aaa": c = new Aaa(); break;
case "bbb": c = new Bbb(); break;
case "ccc": c = new Ccc(); break;
...
}
c.f(...);
c.g(...);
c.h(...);
This looks circumstantial, but in effect delivers an improvement on development quality.
Adding a new case does not mean searching all switch cases.
The code of one case ("aaa") is all in one class, with its own dedicated fields.
That can simplify things and deliver a better overview.
One possible option to get rid of switch is to use hashmap of functions:
private String stringMethod(final String action, final String source) {
final Function<String, String> toLowerFunction = String::toLowerCase;
final Function<String, String> toUpperFunction = String::toUpperCase;
final HashMap<String, Function<String, String>> stringFunctions = new HashMap<>();
stringFunctions.put("toLower", toLowerFunction);
stringFunctions.put("toUpper", toUpperFunction);
return stringFunctions.get(action).apply(source);
}
Replacing a switch with method calls is definitely not utter nonesense #Stultuske. Usually you use method inheritance, so different child classes with the same parent class override a general method and you don't have to check for the subclass's type.
Your case on the other hand looks like a factory method, but parameters are a bit wildly mixed. I would suggest a Map with String to a wrapper constructor function. For the "ccc" case you have to think about something else (e. g. default arguments) or you always have the unused parameter i.
In my program, the user needs to input what type of players the game will have. The players are "human", "good" (for a good AI), "bad" (for a bad AI) and "random" (for a random AI). Each of these players have their own class that extend one abstract class called PlayerType.
My struggle is mapping a String to the object so I can A) create a new object using the String as sort of a key and B) get the related String from an object of its subclass
Ultimately, I just want the implicit String to only appear once in the code so I can change it later if needed without refactoring.
I've tried using just a plain HashMap, but that seems clunky with searching the keys via the values. Also, I'm guessing that I'll have to use the getInstance() method of Class, which is a little less clunky, which is okay if it's the only way.
What I would do is create an enum which essentially functions as a factory for the given type.
public enum PlayerTypes {
GOOD {
#Override
protected PlayerType newPlayer() {
return new GoodPlayer();
}
},
BAD {
#Override
protected PlayerType newPlayer() {
return new BadPlayer();
}
},
RANDOM {
#Override
protected PlayerType newPlayer() {
return new RandomPlayer();
}
};
protected abstract PlayerType newPlayer();
public static PlayerType create(String input) {
for(PlayerTypes player : PlayerTypes.values()) {
if(player.name().equalsIgnoreCase(input)) {
return player.newPlayer();
}
}
throw new IllegalArgumentException("Invalid player type [" + input + "]");
}
)
Because then you can just call it like so:
String input = getInput();
PlayerTypes.create(input);
Of course, you'll get an IllegalArgumentException which you should probably handle by trying to get the input again.
EDIT: Apparently in this particular case, you can replace that loop with just merely
return PlayerTypes.valueOf(input).newPlayer();
And it'll do the same thing. I tend to match for additional constructor parameters in the enum, so I didn't think of using valueOf(), but it's definitely cleaner.
EDIT2: Only way to get that information back is to define an abstract method in your PlayerType class that returns the PlayerTypes enum for that given type.
public class PlayerType {
public abstract PlayerTypes getType();
}
public class GoodPlayer extends PlayerType {
#Override
public PlayerTypes getType() {
return PlayerTypes.GOOD;
}
}
I like the answer provided by Epic but I don't find maps to be clunky. So it's possible to keep a map and get the constructor call directly.
Map<String, Supplier<PlayerType> map = new HashMap<>();
map.put("human", Human::new);
Human h = map.get("human").get();
The two main options I can think of:
Using Class.newInstance(), as you mentioned (not sure if you had this exact way in mind):
// Set up your map
Map<String, Class> classes = new HashMap<String, Class>();
classes.put("int", Integer.class);
classes.put("string", String.class);
// Get your data
Object s = classes.get("string").newInstance();
You could use Class.getDeclaredConstructor.newInstance if you want to use a constructor with arguments (example).
Another option is using switch:
Object getObject(String identifier) {
switch (identifier) {
case "string": return new String();
case "int": return new Integer(4);
}
return null; // or throw an exception or return a default object
}
One potential solution:
public class ForFunFactory {
private ForFunFactory() {
}
public static AThing getTheAppropriateThing(final String thingIdentifier) {
switch (thingIdentifier) {
case ThingImplApple.id:
return new ThingImplApple();
case ThingImplBanana.id:
return new ThingImplBanana();
default:
throw new RuntimeException("AThing with identifier "
+ thingIdentifier + " not found.");
}
}
}
public interface AThing {
void doStuff();
}
class ThingImplApple implements AThing {
static final String id = "Apple";
#Override
public void doStuff() {
System.out.println("I'm an Apple.");
}
}
class ThingImplBanana implements AThing {
static final String id = "Banana";
#Override
public void doStuff() {
System.out.println("I'm a Banana.");
}
}
I have an enum FileType
public static enum FileType {
CSV, XML, XLS, TXT, FIXED_LENGTH
}
FileType fileType = FileType.CSV;
Is there a better (cleaner) way to check fileType for multiple values than the following (like "myString".matches("a|b|c");)?
if(fileType == FileType.CSV || fileType == FileType.TXT || fileType == FileType.FIXED_LENGTH) {}
Option 1: Add a boolean field to your enum.
public static enum FileType {
CSV(true), XML(false), XLS(false), TXT(true), FIXED_LENGTH(true);
private final boolean interesting;
FileType(boolean interesting) {
this.interesting = interesting;
}
public boolean isInteresting() {
return this.interesting;
}
}
...
if (fileType!=null && fileType.isInteresting()) {
...
}
Option 2: use an EnumSet.
EnumSets use bitfields under the hood, so they are very fast and low memory.
Set<FileType> interestingFileTypes = EnumSet.of(FileType.CSV, FileType.TXT, FileType.FIXED_LENGTH);
...
if (interestingFileTypes.contains(fileType)) {
...
}
Option 3: use a switch, as kocko suggests
Why not use a switch:
switch(fileType) {
case CSV:
case TXT:
case FIXED_LENGTH:
doSomething();
break;
}
This does the same as your if statement check, but it's more readable, imho.
But the problem with this code is not the switch or the if/else statement(s). The problem is that it breaks the Open-closed principle.
In order to fix that, I would completely remove the enum and create an interface:
interface FileType {
boolean isInteresting();
}
Then, for each enum constant we used to have, I would create a separate interface implementation:
public class Txt implements FileType {
#Override
public boolean isInteresting() {
return false;
}
}
How does the switch statement change? We used to pass a fileType parameter, on which we checked the value. Now, we will pass an instance of FileType.
public void method(FileType fileType) {
if (fileType.isInteresting()) {
doSomething();
}
}
The advantage of this is that when you introduce a new FileType (which you would introduce as a new enum constant), you don't have to modify the switch/if/else statement to handle the case when this new file type is interesting or not. The code will simply work here without modification, which is the essence of the Open-closed principle: "Open for extensions, closed for modifications".
I ended up writing a method:
public static enum FileType {
CSV, XML, XLS, TXT, FIXED_LENGTH;
// Java < 8
public boolean in(FileType... fileTypes) {
for(FileType fileType : fileTypes) {
if(this == fileType) {
return true;
}
}
return false;
}
// Java 8
public boolean in(FileType... fileTypes) {
return Arrays.stream(fileTypes).anyMatch(fileType -> fileType == this);
}
}
And then:
if(fileType.in(FileType.CSV, FileType.TXT, FileType.FIXED_LENGTH)) {}
Nice and clean!
Adding a different example:
public class JavaApplication {
public enum CustomerStatus {
ACTIVE("Active"),
DISCONNECTED("Disconnected"),
PENDING("Pending"),
CANCELLED("cancelled"),
NEW("new");
}
public static void main(String[] args) {
EnumSet<CustomerStatus> setA = EnumSet.of(CustomerStatus.ACTIVE, CustomerStatus.NEW);
EnumSet<CustomerStatus> setB = EnumSet.of(CustomerStatus.PENDING, CustomerStatus.CANCELLED);
if (setA.contains(CustomerStatus.ACTIVE)) {
System.out.println("ACTIVE : customer active");
}
if (setB.contains(CustomerStatus.ACTIVE)) {
System.out.println("ACTIVE: Customer is no longer active");
}
if (setB.contains(CustomerStatus.CANCELLED) {
System.out.println("CANCELLED: Customer is no longer active");
}
}
}
**Output**:
ACTIVE : customer active
CANCELLED: Customer is no longer active