Java Reflection
Reflection is an amazing feature of many modern programming languages that lets you dynamically create objects and call methods based on String values instead of having to know their exact names when the code is compiled. This flexibility lets you more easily hold their values in data structures or data files instead of hardcoding them directly within your code. It also makes it easier to provide names for Lambdas and refer to them dynamically instead of having to hardcode their relationship to the code that uses them.
Getting Started
The project contains updated copies of two previous examples, Roulette and Browser, so you do not have to worry about what state your version currently is in.
For this lab exercise, work together with someone else in the class:
- One person should fork the original lab_reflection repository into their own account to provide a place for the group to edit, commit, and push changes
- On the resulting project web page, go to
Settings -> Membersto add your partner so both people in the group can access the same repository - Search for your partner's name and give them a
Maintainerrole in the project and chooseAdd to Project - Both students should clone the one forked repository to their individual machines so they can work on their own separate copies
- Import the project into IntelliJ (
New -> Project From Existing Sources)using the folder you just created (and just accept the defaults for all the options by selectingNext)
- Once the project window appears, you will need to do a few more steps to get it to run:
- Select
File -> Project Structureand then selectLibrarieswithin the dialog that appears - Right click on the correct library for your platform (i.e., one of
linux,mac, orwin) and selectAdd to Modules - Click
OKin the dialog that appears andOKagain to close the Project Settings dialog - Click to expand the folders
src -> browserto find the Java classMain - Right click on this class to run it by selecting
Run Main.main()
- Select
- Run the program to verify that your Java installation is working.
If there are compilation errors or it does not run, then the project is not correctly configured.
Work in pairs to implement the changes described below to example code we have discussed this semester. Create a commit for each step below as your programming pair completes it.
Roulette Factory
- Create a
Factoryclass that hides the creation ofBetsubclasses.- For this step just move the
List<Bet>of hard coded subclasses to theFactoryclass (so you are just extracting the existing code from one class to another). - Also move the
promptForBet()method to theFactoryclass because it should also handle printing out the menu of choices rather than theGameclass. - Finally update the
Gameclass to have aFactoryinstance variable instead of theList<Bet>and call itspromptForBet()method instead.
- For this step just move the
- Change the
Factoryclass to use reflection instead of a hardcoded list of concreteBetsubclasses.- For this step change the
List<Bet>to aList<String>, where the strings represent the complete name of eachBetsubclass (so you can focus just on implementing reflection). - Create a new method
makeBet()that gets aClassobject representing theBetsubclass named in the given string using theforName()method, then gets itsConstructorobject, and calls thenewInstance()method (and catches its many exceptions) instead of using the instance directly in the previous list. - Note, you can use either the given default constructor for each
Betsubclass or create some logic to provide the description and odds for each subclass — whichever makes more sense for your project.
- For this step change the
- Change the
Factoryclass to get the class names from a properties file instead of a hardcoded list.- For this step create a properties file and, in the
Factoryclass constructor, read it using a ResourceBundle object to determine which subclasses are possible to create so that nothing is hardcoded in yourFactoryclass. - Note, the reflection code itself should likely not need to change because you are using the properties file to populate the
Listinstance variable(s). - Note, the properties file should map either a general bet name to the complete
Betsubclass name (for the default constructor) or the completeBetsubclass name to its odds and description, on one line separated by a comma for easy parsing by your program (for the two argument constructor) — whichever makes more sense for your project.
- For this step create a properties file and, in the
Browser Flexible GUI
- Change the
makeButton()method so that it that takes aStringparameter instead of a Lambda (so two separateStringparameters)- For this step, in each call to
makeButton(), replace each Lambda that calls a single method with no parameters to a just the name of that method (so it is still essentially hardcoded, but now usingStrings instead of Lambdas). - Change the call to
setOnAction(), within themakeButton()method, to make your own Lambda (instead of the one previously passed in) that uses the given string parameter to get aMethodobject and then call itsinvoke()method (and catches its many exceptions).
- For this step, in each call to
- Change the
makeButton()method so that it that only takes oneStringparameter (i.e., no extra parameter telling it what action to perform)- For this step create a separate properties file that maps the button's key name (the same one used in the original properties file) to the name of the method to invoke when it is pressed.
- Create a second ResourceBundle instance variable and, in the
BrowserViewclass constructor, read the new properties file so that the method name to call is not hardcoded in yourBrowserViewclass. - After loading the both properties files, you should be able to use the one given string to look up both the text for the button to display (in the original resource file) and the action for the button to perform (in the new resource file). Again, this reduces the assumptions and what is hardcoded in your program.
- Create a data structure to represent the buttons in the Browser's panels
- For this step create a
List<List<String>>instance variable that holds hardcoded values for the buttons' key names used in the properties files, with a separateListfor each row of buttons (i.e., the first list will hold the button keys"BackCommand","NextCommand","HomeCommand", and"GoCommand"and the second list will hold the button keys"AddFavoriteCommand"and"SetHomeCommand"). - Change the calls to the methods
makePreferencesPanel()andmakeNavigationPanel()in theBrowserViewclass constructor to take aList<String>. - Change the code in each method to use a loop to create the buttons from the given list rather than hardcoding their order and number.
- Note, in this example, you will need to change the order of the second panel so the non-button components are explicitly created at the end for both panels (which is also why two separate
makeXXXPanel()methods are still needed). But it should not be too big a step to see how to make a more comprehensive data structure that allows you the flexibility to create more than just buttons.
- For this step create a
Note, it is typically likely better overall design to use the Command pattern, which uses an abstraction (substitutable subclasses) rather than methods, because that is more flexible and recognizable than this simple example. But in either case, reflection can be used to make it data driven rather than hardcoded.
Your Projects
If you have completed all of the practice steps, try updating some of your own code with some of these features.
Submission
At the end of class, use Gitlab's Merge Request to submit your pair's refactored code to the lab's organization repository. Make sure the NetIDs of everyone in the group are in the title of your Merge Request.