Is is possible in Java to call a method based on a field value? For example, I ask the user what soda they want. They enter their selection and the value of the field that holds their selection is used to call the method.
Obviously the below code doesn't work. But I'm hoping it will illustrate my intent. Is this sort of thing even possible in Java?
The goal is to effectively switch without using IF or SWITCH statements.
import java.util.Scanner;
public class FieldResolutionTest
{
public static void main (String[] args)
{
Scanner inputScanner = new Scanner(System.in);
MethodTester test = new MethodTester();
System.out.println("Please enter ONE or TWO");
String selection = inputScanner.nextLine();
test.(selection)();
}
}
public class MethodTester
{
public void ONE()
{
System.out.println("You ran method ONE");
}
public void TWO()
{
System.out.println("You ran method TWO");
}
}
It is technically possible with reflection, but if you have a finite number of known possible methods, it would be much simpler to use a switch statement (or if/else if you don't have Java 7):
switch (selection) {
case "ONE":
test.ONE();
break;
case "TWO":
test.TWO();
break;
// etc.
default:
System.out.println("Invalid method!");
break;
}
If the methods are dynamic or you are testing lots of classes, and you still want to go the reflection route, it would look something like this:
try {
Method m = MethodTester.class.getMethod(selection);
m.invoke(test);
} catch (NoSuchMethodException nsme) {
System.out.println("Invalid method!");
}
A nice approach is with enum:
public class FieldResolutionTest
{
public static void main (String[] args)
{
Scanner inputScanner = new Scanner(System.in);
System.out.println("Please enter ONE or TWO");
String selection = inputScanner.nextLine();
FieldResolutionTest.valueOf(selection).run();
}
}
public enum MethodTester
{
ONE { public void run()
{
System.out.println("You ran method ONE");
}},
TWO { public void run()
{
System.out.println("You ran method TWO");
}};
public abstract void run();
}
You can use reflection. This is your example with minimal modifications:
import java.lang.reflect.Method;
import java.util.Scanner;
public class FieldResolutionTest {
public static void main (String[] args) throws Exception {
Scanner inputScanner = new Scanner(System.in);
MethodTester test = new MethodTester();
System.out.println("Please enter ONE or TWO");
String selection = inputScanner.nextLine();
Method method = MethodTester.class.getMethod(selection);
method.invoke(test);
}
}
class MethodTester {
public void ONE() {
System.out.println("You ran method ONE");
}
public void TWO() {
System.out.println("You ran method TWO");
}
}
Of course, you need to add some error handling. For example, you should check that the user entered the name of one of the allowed methods, like this:
Map<String, Method> methods = new HashMap<String, Method>();
for (Method m: MethodTester.class.getMethods()) {
methods.put(m.getName(), m);
}
if (methods.containsKey(selection)) {
methods.get(selection).invoke(test);
} else {
System.err.println("No such method: " + selection);
}
You need to test the input value and call the corresponding method:
if(selection.equals("ONE")) test.ONE();
else if (selection.equals("ONE")) test.TWO();
else System.out.println("Unknown input!");
Since Java 7 was released, it is possible to perform the switch-case method on a string type variable
switch(selection) {
case "ONE":
test.ONE();
break;
case "TWO":
test.TWO();
break;
}
Again, only available since Java 7.
As told by mellamokb you can use reflection to invoke the method. Typically the code will look like test.getClass().getMethod("ONE", null).invoke(..)
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);
}
}
I have methods with which I get data from a database.
Depending on the variable that the user entered through the console, I must execute the desired method
while (flag) {
try {
sw = new ScannerWrapper();
menuHeader();
int choice = sw.readInt();
switch (choice) {
case 1:
System.out.println("Input first name: ");
String name = sw.readLine().toUpperCase();
printResults(DataParser.getFilmByName(name));
break;
case 0:
System.out.println("Bye-bye. Come again");
flag = false;
break;
default:
System.out.println("Please enter correct number");
}
} catch (Exception e) {
System.out.println("Enter correct data");
} finally {
DBConnector.getInstance().closeConnection();
}
}
This code is very bad.There are more than 5 cases with methods and the code becomes redundant
You should have a look at the Strategy design pattern. That will allow you to abstract the logic related to an action.
On top of that, you want to replace the switch to find the right strategy according to the input variable. That is the job of the Factory design pattern, which in your case would return one of the different strategies according to the database value.
Basically:
interface UserAction {
public void execute();
}
class ListMovies implements UserAction {
public void execute() {
// List the movies
}
}
class ExitProgram implements UserAction {
public void execute() {
// Kill kenny
}
}
class Noop implements UserAction {
public void execute() {
// Do nothing
}
}
And a factory:
class UserActionFactory {
public UserAction make(int actionId) {
switch (actionId) {
0: return new ListMovies();
1: return new ExitProgram();
default: return new Noop();
}
}
}
Which then allows:
UserActionFactory factory = new UserActionFactory();
ScannerWrapper sw = new ScannerWrapper();
while (true) {
menuHeader();
int choice = sw.readInt();
UserAction action = factory.make(choice);
action.execute();
}
This could also be the Command design pattern, depends on how you name things and instantiate objects for the rest of the classes.
When I try to execute this code, after I choose AMD, I got null in value. how it can be happen ?
below is the source code :
[for main]
public class processor{
public int hargapro;
public String nmbarangpro;
public static final Scanner input = new Scanner(System.in);
public String getpro()
{
return nmbarangpro;
}
public int getproharga()
{
return hargapro;
}
public void daftarpro() {
List<String> daftarpro = new ArrayList<>();
daftarpro.add("AMD");
daftarpro.add("Intel");
List<String> nomer = new ArrayList<>();
nomer.add("1. ");
nomer.add("2. ");
System.out.println("Processor yang tersedia :");
for (int i = 0; i < daftarpro.size(); i++) {
System.out.println(nomer.get(i)+daftarpro.get(i));
}
System.out.println("Pilihan anda : ");
int pilih = input.nextInt();
switch(pilih)
{
case 1:
{
System.out.println("Anda membeli Processor AMD");
System.out.println("Seharga Rp 1.200.000");
harga(1200000); //call harga method
namabarang("AMD"); //call namabarang method
System.out.println(getpro()); //[for testing]filled with AMD[ni problem here]
System.out.println(getproharga()); //[for testing][filled with 1200000[no problem here]
break;
}
case 2:
{
System.out.println("Anda membeli Processor AMD");
System.out.println("Seharga Rp 1.200.000");
harga(1500000);
namabarang("Intel");
break;
}
default:
System.out.println("Pilihan tidak tersedia");
daftarpro();
}
}
#Override
public int harga(int hargamasuk) {
return hargapro = hargamasuk;
}
#Override
public String namabarang(String barang) {
return nmbarangpro = barang;
}
public static void main(String[] args) {
processor a = new processor();
a.daftarpro();//get menu from daftarpro()
kasir x = new kasir();
x.semua();//get null in value
}
}
my second files :
public class kasir {
public void semua()
{
processor a = new processor();
System.out.println(a.getpro());
}}
When I try to read value through class kasir, i get x.semua filled with null value. how it can be happen ?
Your semua method creates a new instance of processor which it then reads from:
public void semua()
{
processor a = new processor();
System.out.println(a.getpro());
}
That's entirely unrelated to the processor instance you've created in your main method. If your kasir class should logically "know about" the other processor instance, you probably want a processor field in the class, which you might populate via the constructor - so your main method might become:
public static void main(String[] args) {
processor a = new processor();
a.daftarpro();
kasir x = new kasir(a);
x.semua();
}
As an aside, you should really try to follow the Java naming conventions, so classes of Processor and Kasir, and methods of getPro etc. (And if your code actually looks like that in your editor, I suggest you reformat it, too...)
i'm new to this forum, first of all i would like to say thanks for reviewing my question.
I'm receiving this message: The method CreandoFunciones() is undefined for the type CreandoFunciones. I'm new # Java, i try a few things, but nothing.
Any help would be appreciated. Thanks
package funciones;
import java.util.Scanner;
public class Funciones1 {
private static Scanner scan;
public static void main(String[] args)
{
CreandoFunciones link = new CreandoFunciones();
{
int menu;
scan = new Scanner(System.in);
System.out.println("OpciĆ³n: ");
menu = scan.nextInt();
switch(menu)
{
case 1:
link.CreandoFunciones();
break;
case 2:
System.out.println("C'est la vie!");
break;
case 3:
System.out.println("Alors on danse!");
break;
default: System.out.println("Owned.");
}
}
}
}
package funciones;
public class CreandoFunciones {
public CreandoFunciones()
{
System.out.println("Testing");
}
}
CreandoFunciones() is a constructor of the class not a usual method. So you cannot call like link.CreandoFunciones();. to perform operation in link 1 create a separate method in the class.
This is for coursework. I've built the whole program, and it does everything right, apart from this one thing.
I have a class called 'Schedule' this method is at the very end of schedule:
public void bookSeatMenu()
{ boolean leaveBookSeatMenu = false;
String seatBookingMenuStr;
int seatBookingMenuInt = 14;
boolean isInteger = false;
Scanner input = new Scanner(System.in);
System.out.println("Press 1 to add an individual booking, 2 to cancel a booked seat or 3 to go back");
seatBookingMenuStr = input.nextLine();
try {
seatBookingMenuInt = Integer.parseInt(seatBookingMenuStr);
isInteger = true;
}
catch (NumberFormatException e) {
}
switch (seatBookingMenuInt) {
case 1:
bookSeat();
break;
case 2:
cancelSeat();
break;
case 3:
leaveBookSeatMenu = true;
break;
default:
System.out.println("Invalid Choice");
} while (leaveBookSeatMenu == false);
}
I know you all know what a switch menu looks like, but I thought I'd throw it in there anyway, in case (pardon the pun) I'm going wrong here.
Moving on, I have the bookSeat method, this is where the user books a seat (which works fine). Then afterwards it displays the bookSeatMenu() just it displays the menu. But then it won't go back to the previous one.
public void bookSeat()
{
Scanner input = new Scanner(System.in);
boolean isSeatBooked = true;
showSeatPlan();
int seatNum = 0;
int rowNum = 90;
int columnNum = 16;
boolean isInt = false;
while (isSeatBooked == true)
{
System.out.println("Please pick column of a seat to book");
columnNum = input.nextInt();
System.out.println("Please pick row of a seat to book");
rowNum = input.nextInt();
seatNum = (columnNum + ((rowNum) * 15));
if (seats[seatNum] == false)
{
isSeatBooked = false;
}
else
{
System.out.println("This seat is already booked");
}
}
seats[seatNum] = true;
System.out.println("");
bookSeatMenu();
}
Now not for love nor money am I able to get it to go back to the previous menu after it's booked a seat.
Basically the process is:
Book a seat --> go to bookSeatMenu --> press 4 to go back --> Arrive at previous menu.
If I don't book a seat, the program will happily go back to the menu before hand, but after, it just keeps on going on to a new line in the command prompt, not doing anything else, no error etc.
I'm pretty tempted to say this might be a problem with BlueJ, although a bad workman blames his tools, and I don't wanna be 'that guy'
I also need to make a 'testing class' - having never used a 'testing class' before, and the assignment asking us to look in the 'textbook' which noone bothered to buy, I actually have no idea!
There's no switch...while so I assume your problem is as soon you do choose 3, you end up in while(true); which is an infinite loop.
correct pseudo-code:
do {
// read System.in
// handle menu options with your switch
} while(...)
By the way, design is bad IMHO, you should try to think about your model (in your case I would see something like Room, Seat, Scheduler, Menu) and make those object interact with each others :
public class Room {
private Seat[][] seats;
public String toString() {
// like showSeatPlan() using toString() of Seat
}
}
public class Seat {
private int row, column;
private boolean isBooked;
public void book() { /* ... */ }
public void cancel() { /* ... */ }
public String toString() { /* "X" or " " */ }
}
public final class Scheduler {
// "main class" with a "main" method
}
public class Menu {
private Room room;
public String toString() {
// print out menu
}
public void bookSeat() { /* ... */ }
public void cancelSeat() { /* ... */ }
}
(something like that)
For the test part, each class have a test class and each method have a test method, as an example, for Seat:
public class Seat {
public void book() {
if (this.isBooled) {
throw new CannotBookException("seats is taken!");
}
this.isBooled = true;
}
}
public class SeatTest {
#Test // when I book a seat, it's markedas booked.
public void testBook() {
final Seat seat = new Seat();
seat.book();
assertTrue(seat.isBooked)
}
#Test(expected = CannotBookException.class) // when I book an already booked seat, I get an exception.
public void testBookAlreadBooked() {
final Seat seat = new Seat();
// book the seat
seat.book();
assertTrue(seat.isBooked)
// try to book again
seat.book();
}
}