I have 2 classes. one is named "shipment", the other is called "Inventory"
inside the shipment, there are some variables as below.
public class Shipment
{
private int trackingCode;
private int priority;
private double shippingPrice;
private double weight;
private String originCity;
private String destCity;
private String trackingPage;
and I create the "Inventory" as below
public class Inventory
{
private ArrayList<Shipment> packages;
public Inventory(Shipment[] listOfPackage)
{
if(listOfPackage == null){
throw new IllegalArgumentException("List of Packages cannot be null.");
}
packages = new ArrayList<Shipment>(Arrays.asList(listOfPackage));
}
Now my question is how do I create a method to add a new package to the ArrayList, and also duplicate tracking code is not allowed need to throw an exception.
public ArrayList<Package> addPackage()
I'm very confusing how to do the duplicate tracking code check because it's one of the Shipment[] array element
You can use the this keyword to refer to your private member variables and still keep a similar naming convention for parameters passed into your constructor or functions. It makes your code much more understandable to others and to yourself =). Having two separate naming conventions for everything you pass into a class can get very confusing.
I have added a main function to demonstrate how you would effectively operate on these classes.
public class Application {
public static void main(String[] args) {
// define some unique shipments
Shipment a = new Shipment(1,1, 10.0, 20.3, "Denver", "Seattle", "xyz");
Shipment b = new Shipment(2,9, 45.88, 130.1, "Denver", "Los Angeles", "xyz");
Shipment c = new Shipment(3,3, 14.67, 6.8, "Houston", "Dallas", "xyz");
Shipment d = new Shipment(1,4, 12.99, 2.3, "New York", "London", "xyz");
// populate your inventory with an array of initial shipments "a", "b", and "c"
Shipment[] initialShipments = new Shipment[] { a, b, c };
Inventory inventory = new Inventory(initialShipments);
// print the inventory before adding the new shipment
System.out.println(inventory.toString());
// add shipment "d" to your inventory with the new method
try {
inventory.addShipment(d);
}
catch (Exception e) {
System.out.println(e.getMessage());
}
// print the inventory after adding the new shipment
System.out.println(inventory.toString());
}
}
For your Inventory:
public class Inventory {
private ArrayList<Shipment> shipments;
public Inventory(Shipment[] shipments) {
if(shipments == null) {
throw new IllegalArgumentException("List of shipments cannot be null.");
}
this.shipments = new ArrayList<>(Arrays.asList(shipments));
}
public void addShipment(Shipment shipment) throws Exception {
Optional<Shipment> duplicateShipment = shipments
.stream()
.filter(otherShipment -> otherShipment.getTrackingCode() == shipment.getTrackingCode())
.findAny();
if(duplicateShipment.isPresent()) {
String errorMessage = MessageFormat.format(
"A shipment with tracking code {0} already exists in this inventory.",
shipment.getTrackingCode()
);
throw new Exception(errorMessage);
}
else {
this.shipments.add(shipment);
}
}
#Override
public String toString() {
return "Inventory{" +
"shipments=" + shipments +
'}';
}
}
In order to check for duplicate tracking codes, you must make a stream of your existing shipments so that you can look for any tracking code in those shipments that match the one you are trying to add.
There are many ways to accomplish this in theory, but I did it by making a stream of your shipments so I could make a filter that looked for any shipment tracking code equal to the one you are adding.
The findAny at the end of this stream just returns an Optional which means that it could potentially return something or not.
With the Optional type, you can test if it found a duplicate by using the isPresent() function. If the duplicate is present, you can throw an exception, as needed.
Here I just made the function throw up the Exception, but you could handle it here in the function and just log that you tried to add the same shipment. In real code, you wouldn't want your code to break because you tried to add a duplicate shipment. You would just want to stop it from happening and move on!
For your Shipment:
public class Shipment {
private int trackingCode;
private int priority;
private double shippingPrice;
private double weight;
private String originCity;
private String destCity;
private String trackingPage;
public Shipment(int trackingCode, int priority, double shippingPrice, double weight, String originCity, String destCity, String trackingPage) {
this.trackingCode = trackingCode;
this.priority = priority;
this.shippingPrice = shippingPrice;
this.weight = weight;
this.originCity = originCity;
this.destCity = destCity;
this.trackingPage = trackingPage;
}
public int getTrackingCode() {
return trackingCode;
}
}
You need to add a 'getter' to the Shipment class so that you can access the tracking code outside of this class; otherwise, it will remain private, and you won't be able to make comparisons outside of this class where you need to check for duplicate tracking codes.
Related
This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 11 months ago.
I'm working on a project for a Java class, and I can't seem to get past this NullPointerException. The project is a command-line LinkedIn program. One of the aspects I'm implementing is the ability to add a skillset to a user's profile.
I have a LinkedInUser class in which I define a TreeSet to hold these skillsets in the form of Strings entered by the user. I'm using TreeSet, because the assignment requires them to be sorted.
I define the TreeSet in the LinkedInUser class here:
private Set<String> skillsets = new TreeSet<>();
The action the user takes is defined in the AddSkillsetAction class:
String skillset;
System.out.println("Enter a skillset to add to your list:");
skillset = scanner.nextLine();
loggedInUser.addSkillset(skillset);
System.out.println(skillset + " has been added to your skillsets.");
And the String they enter is passed to the addSkillSet function in the LinkedInUser class:
public void addSkillset(String skillset) {
skillsets.add(skillset);
}
I keep getting a NullPointerException on the line:
skillsets.add(skillset);
What am I doing wrong? I've tested every level up to that line. I even tested the TreeSet inside the addSkillset function with this code:
if(skillsets == null) {
System.out.println("The TreeSet is null.")
}
It's telling me the TreeSet is null. I thought instantiating the Set with:
private Set<String> skillsets = new TreeSet<>();
would actually create an empty TreeSet, instead of it pointing to a null location. Why is my set "skillsets" still pointing to null? What am I doing wrong here?
EDIT:
Here are the full classes:
package edu.institution.asn2;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public class LinkedInUser extends UserAccount implements Comparable<LinkedInUser>, Serializable {
private static final long serialVersionUID = 75648957489235739L;
private String type;
private List<LinkedInUser> connections = new ArrayList<>();
private Set<String> skillsets = new TreeSet<>();
public LinkedInUser(String username, String password) {
super(username, password);
}
#Override
public void setType(String type) {
this.type = type;
}
public String getType() {
return this.type;
}
// Add a connection to user's list
public void addConnection(LinkedInUser user) throws LinkedInException {
int index = connections.indexOf(user);
if (index >= 0) {
throw new LinkedInException("You are already connected with this user.");
}
else {
connections.add(user);
}
}
// Remove a connection from the user's connection list
public void removeConnection(LinkedInUser user) throws LinkedInException {
int index = connections.indexOf(user);
if (index < 0) {
throw new LinkedInException("You are NOT connected to this user.");
}
else {
connections.remove(index);
}
}
// Return a copy of the ArrayList of connections
public List<LinkedInUser> getConnections() {
ArrayList<LinkedInUser> copy = new ArrayList<>(connections);
return copy;
}
// Return the number of connections
public int getNumberOfConnections() {
return connections.size();
}
// Return the skillsets
public Set<String> getSkillsets(){
return skillsets;
}
// Add a skillset
public void addSkillset(String skillset) {
skillsets.add(skillset);
}
// Remove a skillset
public void removeSkillset (String skillset) {
if(skillsets.contains(skillset)){
skillsets.remove(skillset);
} else {
System.out.println(skillset + " is not in your skills list.");
}
}
// Override the compareTo function
#Override
public int compareTo(LinkedInUser user) {
int i = this.getUsername().compareToIgnoreCase(user.getUsername());
return i;
}
}
And the class to add a skillset:
package edu.institution.actions.asn7;
import java.util.Scanner;
import edu.institution.ApplicationHelper;
import edu.institution.UserRepository;
import edu.institution.actions.MenuAction;
import edu.institution.asn2.LinkedInUser;
public class AddSkillsetAction implements MenuAction {
#Override
public boolean process(Scanner scanner, UserRepository userRepository, LinkedInUser loggedInUser) {
String skillset;
System.out.println("Enter a skillset to add to your list:");
skillset = scanner.nextLine();
loggedInUser.addSkillset(skillset);
System.out.println(skillset + " has been added to your skillsets.");
ApplicationHelper.incrementSkillsetCount(skillset);
return true;
}
}
After I run and try to add a skillset, I get this error:
Exception in thread "main" java.lang.NullPointerException
at edu.institution.asn2.LinkedInUser.addSkillset(LinkedInUser.java:69)
at edu.institution.actions.asn7.AddSkillsetAction.process(AddSkillsetAction.java:19)
at edu.institution.ApplicationController.process(ApplicationController.java:61)
at edu.institution.LinkedInCLI.main(LinkedInCLI.java:39)
LinkedInUser.java:69 is:
skillsets.add(skillset);
By the way… Your naming is confusing. String skillset; should be String skill, and .addSkill not .addSkillset, because you are adding individual skills rather than adding a set.
Clarifying your naming may clarify your code. Notice the singular skill and plural skills naming used in code below.
You did not provide enough details to diagnose the problem. But I can show you some example code based on your descriptions.
Your problem may be related to your not properly instantiating the TreeSet. Notice in this code that you have a choice of at least two places in which to instantiate:
On the declaration line of skills.
In the constructor. (Code currently commented-out.)
The LinkedInUser class.
package work.basil.linkedin;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
public class LinkedInUser
{
private String name;
private NavigableSet < String > skills = new TreeSet <>();
// Constructor
public LinkedInUser ( final String name )
{
this.name = name;
// this.skills = new TreeSet <>() ;
}
// Modifiers
public void setName ( String name ) { this.name = name; }
public void addSkill ( String skill ) { this.skills.add( skill ); }
// Getters
public String getName ( ) { return name; }
public Set < String > getSkills ( ) { return Set.copyOf( this.skills ); } // Return a unmodifiable copy of the set. (defensive programming)
}
For defensive programming, we return a copy of the set. This unmodifiable copy returned by Set.copyOf has no order. In some implementations, the order may even change arbitrarily for each iterator. If you want to return an ordered NavigableSet instead, do this:
Change the return type of the method to NavigableSet.
Change the code to pass the instance’s set to the constructor of another set.
public NavigableSet < String > getSkills ( ) { return new TreeSet <>(this.skills ); }
Usage.
LinkedInUser alice = new LinkedInUser( "Alice" );
LinkedInUser bob = new LinkedInUser( "Bob" );
alice.addSkill( "Yodeling" );
alice.addSkill( "Tap Dancing" );
bob.addSkill( "Juggling" );
System.out.println( alice.getName() + " does " + alice.getSkills() );
System.out.println( bob.getName() + " does " + bob.getSkills() );
System.out.println( List.of( alice , bob ) );
When run.
Alice does [Yodeling, Tap Dancing]
Bob does [Juggling]
[LinkedInUser{name='Alice', skills=[Tap Dancing, Yodeling]}, LinkedInUser{name='Bob', skills=[Juggling]}]
You said:
I thought instantiating the Set with:
private Set<String> skillsets = new TreeSet<>();
Yes, that would indeed instantiate a TreeSet object, and store a reference to that set in a variable named skillsets. I expect you are placing that code in the wrong location. Again, look at the two locations I suggested earlier in this Answer: on declaration line, or in constructor.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
I'm trying to create a Java program that manage a booking ticket system.
I have a film Class:
public class Film {
private String title;
private Double price;
private String ageRestriction;
private double rating;
private String genre;
private String location;
private String screenDay;
A FilmList Class that create an ArrayList of Films item and sort, based by two parameters (Location and Week)
public class FilmList {
public FilmList(ArrayList<Film> filmArrayList) {
this.filmArrayList = filmArrayList;
}
public FilmList (){
this.filmArrayList = new ArrayList<>();
}
public ArrayList <Film> filmArrayList;
public void addFilm(Film films){
this.filmArrayList.add(films);
}
private String showLocation;
private String screenWeek;
public void setScreenWeek(String screenDay) {
this.screenWeek = screenDay;
}
public String getScreenWeek() {
return screenWeek;
}
public void setShowLocation(String location) {
this.showLocation = showLocation;
}
public String getShowLocation() {
return showLocation;
}
public Optional<Film> searchFilm(){
Optional<Film> movieFounded = filmArrayList.stream().filter(i -> i.getLocation().contains(getShowLocation()) &&
i.getScreenDay().contains(getScreenWeek())).findAny();
return movieFounded;
}
The setShowLocation parameter it's stetted by the click of a button (there's one of them for each theatre, and the setScreenWeek it's stetted by a Combobox
the Graphic unit interface with the console. Notice that if I press the button without select anything on the combobox I got an error.
So
FilmList filmList = new FilmList();
filmList.addFilm
System.out.println(searchFilm().toString());
Your code is a bit strange, but I suppose you meant passing a Film instance to addFilm, and then using filmList.searchFilm().
Anyway
filter(
i -> i.getLocation().contains(getShowLocation()) &&
i.getScreenDay().contains(getScreenWeek())
)
Here you're filtering the filmArrayList, which contains a single element at that point. And
i.getLocation().contains(getShowLocation())
means basically
i.getLocation().contains(null)
as the showLocation field isn't initialized.
The same applies for the second condition, using screenWeek.
I'm actually surprised it doesn't throw a NullPointerException, as
public boolean contains(CharSequence s) {
return indexOf(s.toString()) > -1; // NullPointerException at s.toString()
}
But anyway, let's say you initialized those fields, then the only element is discarded by the filter operation, that's why you see Optional.empty.
final FilmList filmList = new FilmList();
filmList.setShowLocation("Your location");
filmList.setScreenWeek("Screen week");
filmList.addFilm(filmInstance);
System.out.println(filmList.searchFilm().toString());
You obviosly need a fully constructed Film instance
final Film filmInstance = new Film();
filmInstance.title = "The NullPointerException adventure";
filmInstance.price = 12D;
filmInstance.ageRestriction = "+18";
filmInstance.rating = 18D;
filmInstance.genre = "Horror";
filmInstance.location = "US";
filmInstance.screenDay = "Monday";
filmList.addFilm(filmInstance);
The problem is in the FilmList#setShowLocation method.
You're assigning showLocation to itself, and the location parameter is unused.
public void setShowLocation(String location) {
this.showLocation = showLocation;
}
This should be
public void setShowLocation(String location) {
this.showLocation = location;
}
public class Example {
private static class Courses {
public final String name;
public final Courses[] children;
public Courses(String name, Courses ... children) {
this.name = name;
this.children = children;
}
}
public static void main(String[] args) {
Courses courses =
new Courses("School",
new Courses("Mathematics",
new Courses("Algebra"),
new Courses("Trig"),
new Courses("Calculus"),
new Courses("Calculus 2"),
new Courses("Geometry")),
new Courses("Sciences",
new Courses("Biology"),
new Courses("Chemistry"),
new Courses("Physics"),
new Courses("Business",
new Courses("Finances",
new Courses("Accounting"),
new Courses("Accounting 1"),
new Courses("Accounting 2"),
new Courses("Administration",
new Courses("Economics"),
new Courses("Business Studies"),
new Courses("Administration 1"),
new Courses("Accounting"))),
new Courses("Physical Education"))));
System.out.println(find(courses, "Economics", courses.name));
public static String find(Courses courses, String name, String currentPath) {
if((courses.name).equals(name)){
System.out.println(currentPath);
return currentPath + " / " + name;
}
else{
//System.out.println(currentPath);
for(Courses child:courses.children){
currentPath += " / " + child.name;
find(child, name, currentPath);
}
}
return currentPath + " / " + name;
}
}
So this is the code that I have acquired. I'm trying to determine what the right thinking pattern should be when coding this find courses method. This is an array but I'm thinking of it in like a tree like manner and trying to find the answer. IS that something you guys would do too? I'm trying to find a path like this School / Business / Administration / Economics. But either I'm getting the whole path or it's iterating through the whole thing. Also, what's the approach you guys will take to accomplish this. I wrote a recursive method to achieve this, but its not working out.
Thanks, for your help
CC
Thinking about it as a tree is obviously the way to go, as it is indeed a tree.
It might help you to think what you expect the method to do if the current value of courses was the parent of the node you want. That is in your case, if it's the Administration node. In your current implementation, you will iterate over all the children, never realizing you found the correct child!
As another hint, you would generally in recursion want to do something with the result of the recursive call. In your code, you call find(child, name, currentPath) and then you do nothing with the result!
Hope these hints help you.
Your question contains some opinion-based (sub)questions. These we cannot answer, but we can help with your recursive algorithm.
The if branch of your find method seems alright. It enters that branch when an exact match occurs. The problem is in your else branch, where you need recursion to keep looking in the children courses.
Note that your method declaration returns a String.
public static String find(Courses, String, String)
And note that, when you recursively call this function, you are ignoring its return value, rendering the recursive call useless.
for (Courses child: courses.children) {
currentPath += " / " + child.name;
find(child, name, currentPath); // <-- this return value is being ignored!
}
Start by assigning the return value of the recursive search to some variable, and define a return value for when the search doesn't find the provided name (you will not find Biology under Administration, for instance). I'll assume that an empty String means the search didn't find the course.
String result = find(child, name, currentPath);
if (!result.isEmpty()) {
// it has been found
}
Finally, note that you are changing the value of the currentPath variable on each iteration, by appending to it. That will result in erroneous paths, if the name is found after the first iteration. Assign that temporary path, for that iteration, to another variable.
I would do something like this
Course.java
public class Course {
private Course parentCourse;
private String name;
public Course(String name){
this.setName(name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Course getParentCourse() {
return parentCourse;
}
public void setParentCourse(Course parentCourse) {
this.parentCourse = parentCourse;
}
}
Courses.java
public class Courses {
private List<Course> courses;
public Courses(){
}
public List<Course> getCourses() {
return courses;
}
public void setCourses(List<Course> courses) {
this.courses = courses;
}
}
Main
Courses courses = new Courses();
List listCourses = new ArrayList<Course>();
Course generalMaths = new Course("General Maths");
Course linAlgebra = new Course("Linear Algebra");
linAlgebra.setParentCourse(generalMaths);
listCourses.add(generalMaths);
listCourses.add(linAlgebra);
courses.setCourses(listCourses);
Find path
for(Course course : courses.getCourses()){
StringBuffer coursePath = new StringBuffer();
coursePath.append(course.getName());
while(course.getParentCourse() != null){
course = course.getParentCourse();
coursePath.append(" | "+course.getName());
}
System.out.println(coursePath);
}
I was asked this question in an interview to improve the code that was provided. The provided code used lot of if statements and therefore I decided to use HashMap as retrieval would be faster. Unfortunately, I was not selected for the position. I am wondering if someone knows a better way than what I did to improve the code?
/* The following Java code is responsible for creating an HTML "SELECT" list of
U.S. states, allowing a user to specify his or her state. This might be used,
for instance, on a credit card transaction screen.
Please rewrite this code to be "better". Submit your replacement code, and
please also submit a few brief comments explaining why you think your code
is better than the sample. (For brevity, this sample works for only 5
states. The real version would need to work for all 50 states. But it is
fine if your rewrite shows only the 5 states here.)
*/
/* Generates an HTML select list that can be used to select a specific U.S.
state.
*/
public class StateUtils {
public static String createStateSelectList() {
return
"<select name=\"state\">\n"
+ "<option value=\"Alabama\">Alabama</option>\n"
+ "<option value=\"Alaska\">Alaska</option>\n"
+ "<option value=\"Arizona\">Arizona</option>\n"
+ "<option value=\"Arkansas\">Arkansas</option>\n"
+ "<option value=\"California\">California</option>\n"
// more states here
+ "</select>\n"
;
}
/* Parses the state from an HTML form submission, converting it to the
two-letter abbreviation. We need to store the two-letter abbreviation
in our database.
*/
public static String parseSelectedState(String s) {
if (s.equals("Alabama")) { return "AL"; }
if (s.equals("Alaska")) { return "AK"; }
if (s.equals("Arizona")) { return "AZ"; }
if (s.equals("Arkansas")) { return "AR"; }
if (s.equals("California")) { return "CA"; }
// more states here
}
/* Displays the full name of the state specified by the two-letter code. */
public static String displayStateFullName(String abbr) {
{
if (abbr.equals("AL")) { return "Alabama"; }
if (abbr.equals("AK")) { return "Alaska"; }
if (abbr.equals("AZ")) { return "Arizona"; }
if (abbr.equals("AR")) { return "Arkansas"; }
if (abbr.equals("CA")) { return "California"; }
// more states here
}
}
My solution
/* Replacing the various "if" conditions with Hashmap<key, value> combination
will make the look-up in a constant time while using the if condition
look-up time will depend on the number of if conditions.
*/
import java.util.HashMap;
public class StateUtils {
/* Generates an HTML select list that can be used to select a specific U.S.
state.
*/
public static String createStateSelectList() {
return "<select name=\"state\">\n"
+ "<option value=\"Alabama\">Alabama</option>\n"
+ "<option value=\"Alaska\">Alaska</option>\n"
+ "<option value=\"Arizona\">Arizona</option>\n"
+ "<option value=\"Arkansas\">Arkansas</option>\n"
+ "<option value=\"California\">California</option>\n"
// more states here
+ "</select>\n";
}
/* Parses the state from an HTML form submission, converting it to the
two-letter abbreviation. We need to store the two-letter abbreviation
in our database.
*/
public static String parseSelectedState(String s) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("Alabama", "AL");
map.put("Alaska", "AK");
map.put("Arizona", "AZ");
map.put("Arkansas", "AR");
map.put("California", "CA");
// more states here
String abbr = map.get(s);
return abbr;
}
/* Displays the full name of the state specified by the two-letter code. */
public static String displayStateFullName(String abbr) {
{
HashMap<String, String> map2 = new HashMap<String, String>();
map2.put("AL", "Alabama");
map2.put("AK", "Alaska");
map2.put("AZ", "Arizona");
map2.put("AR", "Arkansas");
map2.put("CA", "California");
// more state abbreviations here here
String full_name = map2.get(abbr);
return full_name;
}
}
}
I think there are many things wrong with your code, not least the recreation of the Map for each method call.
I would start at the very beginning, with interfaces. We need two things; a State and a StateResolver. The interfaces would look like this:
public interface State {
String fullName();
String shortName();
}
public interface StateResolver {
State fromFullName(final String fullName);
State fromShortName(final String shortName);
Set<? extends State> getAllStates();
}
This allows the implementation to be swapped out for something more sensible at a later stage, like a database. But lets stick with the hardcoded states from the example.
I would implement the State as an enum like so:
public enum StateData implements State {
ALABAMA("Alabama", "AL"),
ALASKA("Alaska", "AK"),
ARIZONA("Arizona", "AZ"),
ARKANSAS("Arkansas", "AR"),
CALIFORNIA("Californiaa", "CA");
private final String shortName;
private final String fullName;
private StateData(final String shortName, final String fullName) {
this.shortName = shortName;
this.fullName = fullName;
}
#Override
public String fullName() {
return fullName;
}
#Override
public String shortName() {
return shortName;
}
}
But, as mentioned above, this can be replaced with a bean loaded from a database. The implementation should be self-explanatory.
Next onto the resolver, lets write one against our enum:
public final class EnumStateResolver implements StateResolver {
private final Set<? extends State> states;
private final Map<String, State> shortNameSearch;
private final Map<String, State> longNameSearch;
{
states = Collections.unmodifiableSet(EnumSet.allOf(StateData.class));
shortNameSearch = new HashMap<>();
longNameSearch = new HashMap<>();
for (final State state : StateData.values()) {
shortNameSearch.put(state.shortName(), state);
longNameSearch.put(state.fullName(), state);
}
}
#Override
public State fromFullName(final String fullName) {
final State s = longNameSearch.get(fullName);
if (s == null) {
throw new IllegalArgumentException("Invalid state full name " + fullName);
}
return s;
}
#Override
public State fromShortName(final String shortName) {
final State s = shortNameSearch.get(shortName);
if (s == null) {
throw new IllegalArgumentException("Invalid state short name " + shortName);
}
return s;
}
#Override
public Set<? extends State> getAllStates() {
return states;
}
}
Again this is self explanatory. Variables are at the instance level. The only dependency on the StateData class is in the initialiser block. This would obviously need to be rewritten for another State implementation but that should be not big deal. Notice this class throws an IllegalArgumentException if the state is invalid - this would need to be handled somewhere, somehow. It is unclear where this would happen but something that needs to be considered.
Finally we implement the required methods in the class
public final class StateUtils {
private static final StateResolver STATE_RESOLVER = new EnumStateResolver();
private static final String OPTION_FORMAT = "<option value=\"%1$s\">%1$s</option>\n";
public static String createStateSelectList() {
final StringBuilder sb = new StringBuilder();
sb.append("<select name=\"state\">\n");
for (final State s : STATE_RESOLVER.getAllStates()) {
sb.append(String.format(OPTION_FORMAT, s.fullName()));
}
sb.append("</select>\n");
return sb.toString();
}
public static String parseSelectedState(final String s) {
return STATE_RESOLVER.fromFullName(s).shortName();
}
public static String displayStateFullName(final String abbr) {
return STATE_RESOLVER.fromShortName(abbr).fullName();
}
}
Notice we only reference the implementation at the top of the utility class, this makes swapping out the implementation quick and painless. We use a static final reference to that the StateResolver is created once and only once. I have also replaced the hardcoded creation of the select with a dynamic loop based one. I have also used a formatter to build the select.
It should be noted that it is never a good idea to build HTML in Java and anyone that does so should have unspeakable things done to them.
Needless to say you should have thorough unit tests against each and every line of the above code.
In short your answer doesn't really come close to a proper, extensible, enterprise solution to the problem at hand. My solution might seem overkill, and you may be right. But I think it's the correct approach because abstraction is key to reusable code.
To avoid manually maintaining 2 maps and keeping them in sync I would just create the second one as the first one inverted. See here on how to do it.
Also as pointed out by others you need to create your maps only once outside of method call.
** Just for fun a way to do it in Scala **
val m = Map("AL" -> "Alabama", "AK" -> "Alaska")
m map { case (k, v) => (v, k) }
// gives: Map(Alabama -> AL, Alaska -> AK)
Everyone seems focused on the parse, but the create can be improved, too. Get all of the state names, sort them alphabetically, and iterate over that collection to create each option. That way, the states used for parsing are always in sync with the states used for cresting. If you add a new state, you only need to add it to the "master" Enum (or whatever), and both methods will reflect the change.
The only mistake you made was to rebuild the map every time around. If you had built the Map just once - perhaps in a constructor I suspect you would have done fine.
public class StateUtils {
class State {
final String name;
final String abbreviation;
public State(String name, String abbreviation) {
this.name = name;
this.abbreviation = abbreviation;
}
}
final List<State> states = new ArrayList<State>();
{
states.add(new State("Alabama", "AL"));
states.add(new State("Alaska", "AK"));
states.add(new State("Arizona", "AZ"));
states.add(new State("Arkansas", "AR"));
states.add(new State("California", "CA"));
}
final Map<String, String> nameToAbbreviation = new HashMap<String, String>();
{
for (State s : states) {
nameToAbbreviation.put(s.name, s.abbreviation);
}
}
final Map<String, String> abbreviationToName = new HashMap<String, String>();
{
for (State s : states) {
nameToAbbreviation.put(s.abbreviation, s.name);
}
}
public String getStateAbbreviation(String s) {
return nameToAbbreviation.get(s);
}
public String getStateName(String abbr) {
return abbreviationToName.get(abbr);
}
}
One thing I don't like about your code is that you create a hashmap each time the method is called. The map should be created just once, at class init time, and referenced from the method.
What you did wrong is what guys are saying - you are creating a new HashMap every time the method is invoked - a static field could rather congaing the data, and populating it only once the class is being loaded my the JVM.
I'd rather use simple switch on strings - the search is not worse than that of HashMap (at least asymptotically) but you don't use extra memory. Though you need two long switches - more code.
But than again HashMap solution the the later one would be the same for me.
Immutable classes are great but there is one big problem i cant think of a sensible way to solve - cycles.
class Friend {
Set<Friend> friends();
}
How does one model Me having You as a friend who in turn has me as a Friend back ?
IMMUTABILITY
This class from the outside world should definitely be immutable. The value held internally should be constant for the purposes of equality checks.
[[[ Edit: Added code to demonstrate fully immutable concept ]]]
That's why builders are so nice for immutables - they allow mutability during construction to get everything set before you "freeze" it. In this case, I guess you need a Friend builder that supports creating cycles.
final FriendBuilder john = new FriendBuilder().setName("john");
final FriendBuilder mary = new FriendBuilder().setName("mary");
final FriendBuilder susan = new FriendBuilder().setName("susan");
john
.likes(mary)
.likes(susan);
mary
.likes(susan)
.likes(john);
susan
.likes(john);
// okay lets build the immutable Friends
Map<Friend> friends = FriendsBuilder.createCircleOfFriends(john, mary, susan);
Friend immutableJohn = friends.get("john");
Edit: Added immutable example below to demonstrate approach:
There was some discussion in the comments about whether an immutable version was possible.
Fields are final and immutable. A modifiable set is used in the constructor, but it only the unmodifiable reference is kept after construction.
I have another version that uses Guava ImmutableSet for a truly immutable set rather than JDK's unmodifiable wrapper. It works the same, but uses Guava's nice set builder.
Code:
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
/**
* Note: potentially cycle graph - be careful of deep equals/hashCode/toString/etc.
* Immutable
*/
public class Friend {
public static class Builder {
private final String name;
private final Set<Builder> friends =
new HashSet<Builder>();
Builder(final String name) {
this.name = name;
}
public String getName() {
return name;
}
public Set<Builder> getFriends() {
return friends;
}
void likes(final Builder... newFriends) {
for (final Builder newFriend : newFriends)
friends.add(newFriend);
}
public Map<String, Friend> createCircleOfFriends() {
final IdentityHashMap<Builder, Friend> existing =
new IdentityHashMap<Builder, Friend>();
// Creating one friend creates the graph
new Friend(this, existing);
// after the call existingNodes contains all the nodes in the graph
// Create map of the all nodes
final Map<String, Friend> map =
new HashMap<String, Friend>(existing.size(), 1f);
for (final Friend current : existing.values()) {
map.put(current.getName(), current);
}
return map;
}
}
final String name;
final Set<Friend> friends;
private Friend(
final Builder builder,
final Map<Builder, Friend> existingNodes) {
this.name = builder.getName();
existingNodes.put(builder, this);
final IdentityHashMap<Friend, Friend> friends =
new IdentityHashMap<Friend, Friend>();
for (final Builder current : builder.getFriends()) {
Friend immutableCurrent = existingNodes.get(current);
if (immutableCurrent == null) {
immutableCurrent =
new Friend(current, existingNodes);
}
friends.put(immutableCurrent, immutableCurrent);
}
this.friends = Collections.unmodifiableSet(friends.keySet());
}
public String getName() {
return name;
}
public Set<Friend> getFriends() {
return friends;
}
/** Create string - prints links, but does not traverse them */
#Override
public String toString() {
final StringBuffer sb = new StringBuffer();
sb.append("Friend ").append(System.identityHashCode(this)).append(" {\n");
sb.append(" name = ").append(getName()).append("\n");
sb.append(" links = {").append("\n");
for (final Friend friend : getFriends()) {
sb
.append(" ")
.append(friend.getName())
.append(" (")
.append(System.identityHashCode(friend))
.append(")\n");
}
sb.append(" }\n");
sb.append("}");
return sb.toString();
}
public static void main(final String[] args) {
final Friend.Builder john = new Friend.Builder("john");
final Friend.Builder mary = new Friend.Builder("mary");
final Friend.Builder susan = new Friend.Builder("susan");
john
.likes(mary, susan);
mary
.likes(susan, john);
susan
.likes(john);
// okay lets build the immutable Friends
final Map<String, Friend> friends = john.createCircleOfFriends();
for(final Friend friend : friends.values()) {
System.out.println(friend);
}
final Friend immutableJohn = friends.get("john");
}
}
Output:
Node 11423854 {
value = john
links = {
susan (19537476)
mary (2704014)
}
}
Node 2704014 {
value = mary
links = {
susan (19537476)
john (11423854)
}
}
Node 19537476 {
value = susan
links = {
john (11423854)
}
}
The correct way to model a cycle is with a Graph. And a single source code line comment can be enough to enforce inmutability: "can't touch this".
What kind of inmutable enforcement are you looking for? Do you want a a velociraptor to appear whenever you modify the inmutable Set? The difference between mutable and inmutable is just a convention. However, the bits on the RAM can be easily modified and with the Reflection API you can break any encapsulation and data hiding conventions.
Ignoring the velociraptor for a moment, Java does not support an inmutable type. As a workaround, you need to model a datatype that behaves like one.
And for the inmutable property to make sense you need to make Friend an interface, having one implementing class: InmutableFriend, and the construction of the object should fully happen inside the constructor.
Then, since the graph contains cycles, before creating the final inmutable instances you need to store the graph nodes in some mutable temporary structure. You also need to return an unmodifiableSet on the InmutableFriend.friends() method.
Finally, to clone the graph you need to implement a Deep-copy algorithm like Breadth-first search on the Mutable graph. One question though is what happens when the graph is not fully connected.
interface Friend {
public Set<Friend> friends();
}
class MutableFriend {
private Set<MutableFriend> relations = new HashSet<MutableFriend>();
void connect(MutableFriend otherFiend) {
if (!relations.contains(otherFriend)) {
relations.add(otherFiend);
otherFriend.connect(this);
}
}
Friend freeze() {
Map<MutableFriend, InmutableFriend> table = ...;
/*
* FIXME: Implement a Breadth-first search to clone the graph,
* using this node as the starting point.
*
* TODO: If the graph is not connected this won't work.
*
*/
}
}
class InmutableFriend() implements Friend {
private Set<Friend> connections;
public Set<Friend> friends() {
return connections;
}
public InmutableFriend(Set<Friend> connections) {
// Can't touch this.
this.connections = Collections.unmodifiableSet(connections);
}
}
Immutability doesn't need to be compiler-enforced to be valid architecturaly. You can have a legitimate immutable object that takes post-construction initialization parameters. For instance...
private Object something;
public void init( final Object something )
{
if( this.something != null )
{
throw new IllegalStateException();
}
this.something = something
}
The member field "something" isn't final, but it cannot be set more than once either.
A more complex variant based on discussion in comments...
private boolean initialized;
private Object a;
private Object b;
public void init( final Object a, final Object b )
{
if( this.initialized )
{
throw new IllegalStateException();
}
this.initialized = true;
this.a = a;
this.b = b;
}
public Object getA()
{
assertInitialized();
return this.a;
}
public Object getB()
{
assertInitialized();
return this.b;
}
private void assertInitialized()
{
if( this.initialized )
{
throw new IllegalStateException( "not initialized" );
}
}