Creating a HashMap for custom object [duplicate] - java

This question already has answers here:
Java: Object as Value in HashMap being overwritten?
(3 answers)
Closed last month.
Not to sure if this has been asked or answered but I seem to be having trouble with it.
I have a class (Location) with a constructor that reads a file, based on the name parameter, that has all the information for a location. My thought was that each time a location is travelled too the constructor will read the file and create it on the fly. My problem is that I have a class wide HashMap that seems to be getting overridden once I create a second location.
Start of the Location class
public class Location extends Assets{
private String name;
private String desc;
public static HashMap<String, Location> map;
private static HashMap<Direction, String> exits = new HashMap<>();
Start of the constructor
public Location(String name) {
super(name);
//loads each location file as they are travelled too
String filename = "res/" + name + ".txt";
ArrayList<String> arr = new ArrayList<>();
try
{
BufferedReader br = new BufferedReader(new FileReader(filename));
String line;
while((line = br.readLine())!= null)
{
String[] entry = line.replace(",", "").split("-",2);
arr.add(entry[1]);
}
setName(arr.get(0));
setDesc(arr.get(1));
exits.put(Direction.NORTH, arr.get(2));
exits.put(Direction.EAST, arr.get(3));
exits.put(Direction.SOUTH, arr.get(4));
exits.put(Direction.WEST, arr.get(5));
But as I mentioned above the second location I create completely overwrites the previous locations exit data when called from my very simple map class. The name and description methods still work fine but for whatever reason both locations show the same exit data which I know to be incorrect.
Map class
public class Map {
public static HashMap<String, Location> map = new HashMap<>();
public static void readMap(String name)
{
System.out.println(map.get(name).getName());
map.get(name).getDesc();
map.get(name).getExits();
System.out.println("");
}
public static String lowerString(String name)
{
String str = name.toLowerCase().replace(" ", "");
return str;
}
public static void main(String[] args) {
Location riverbank = new Location("riverbank");
Location rabbithole = new Location("rabbithole");
map.put("riverbank", riverbank);
map.put("rabbithole", rabbithole);
readMap(lowerString("River Bank"));
readMap(lowerString("Rabbit Hole"));
}
}

You have declared your exits MAP as static so it will class level and will be common for all objects.
You might know that in case of MAP if tries to put different data in same map with already existing key then it will update the value.
So in your case values related to latest object only will be present in MAP and for every location object it will same.
So either declare exits MAP object as instance variable or change your approach in case you need object level values.

Related

Why does Map.put() overwrite existing values in already existing key/value pairs?

A Map.put() overwrites existing values in itself, despite putting in a unique value.
I am initializing a singleton of an object "Game" in which I have a map of Rooms (and Players).
When I input new key/value pair into the roomsList like this:
roomsList.put(uniqueKey, new Room(uniqueKey, name)), the new key/value pair is added into the Map, but the rest of the pairs (each with a unique identifier) have their values overwritten as well.
I have tried putting in a new room already in my Spring Controller with Game.getRoomsList().put()
creating a separate object for client Messages so that instead of a Player message,
- so the Controller's parameter is of object type NewRoomMessage
And even putting the key/value pairs directly in the private Game constructor...
The weird thing is, that with my Map playersList, everything works fine..
Files in order of communication
lobby.js
This script sends name and generated roomId to the server endpoint /game/sessionId, where sessionId is the websocket identifier of a player, roomId is a newly generated ID and name is the user's input for new room name
function createRoom() {
name = $('#inputRoomName').val();
roomId = '_' + Math.random().toString(36).substr(2, 9);
console.log(roomId);
stompClient.send('/game/'+sessionId, {}, JSON.stringify({'message': name, 'roomId': roomId}));
}
This is my Spring controller which handles messages from Player for and is made for creating Rooms in game
RoomsController
#Controller
public class RoomsController {
#MessageMapping("/game/{sessionId}")
#SendTo("/topic/game/{sessionId}")
public RoomMessage createRoom(#DestinationVariable String sessionId, Player player) throws Exception {
Game game = Game.getInstance();
game.addRoom(player);
return new RoomMessage("Room with the ID " + player.getRoomId() + " created");
}
}
This is the Game Object which is a Singleton and with which my Spring server communicates
Game.java
package cz.vse.pavm07.bp.objects;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Controller;
#Controller
public class Game{
private static Game game;
/*
private static List<Player> players = new ArrayList<Player>();
private static List<Player> playersToRemove = new ArrayList<Player>();
*/
private static Map<String, Player> playersList = new HashMap<String, Player>();
private static Map<String, Player> playersToRemove = new HashMap<String, Player>();
private static Map<String, Room> roomsList = new HashMap<String, Room>();
private static Map<String, Room> roomsToRemove = new HashMap<String, Room>();
// LAZY SINGLETON GAME
private Game() {}
public static Game getInstance() throws Exception{
if(game == null) {
game = new Game();
}
return game;
}
/* This is the method I have issues with, it somehow overwrites already existing values in my Map of Rooms */
/* ROOMS LIST */
public static boolean addRoom(Player player) {
if (!roomsList.containsKey(player.getRoomId())) {
roomsList.put(player.getRoomId(), new Room(player.getRoomId(), player.getMessage()));
return true;
}
return false;
}
/* PLAYERS LISTS */
/* Method adds new player to the playersList and it works right */
public static boolean addPlayer(String sessionId, String name) {
if (playersList.containsKey(sessionId)) {
return false;
} else {
playersList.put(sessionId, new Player(sessionId, name));
return true;
}
}
}
This is my Room constructor
Room.java
public Room (String ID, String name) {
this.ID = ID;
this.name = name;
}
Example output:
NEW ROOM IS ADDED
I am printing out the keyset of my roomsList, and then for each room its key, its ID and its Name
[_y46r22hdu]
The Key of the Room is_y46r22hdu, The ID is _y46r22hdu and the Name of the room isRoom1
NEW ROOM IS ADDED
I am printing out the keyset of my roomsList, and then for each room its key, its ID and its Name
[_jxltglk5z, _y46r22hdu]
The Key of the Room is_jxltglk5z, The ID is _jxltglk5z and the Name of the room isRoom2
The Key of the Room is_y46r22hdu, The ID is _jxltglk5z and the Name of the room isRoom2
I expect the Map to add a new Object without overwriting the already existing key/value pairs...
The link to this project's repository is here: https://github.com/MartinPavelka/chaser-server
The error is in your Room class. Both name and ID are static fields, whereas you're expecting each instance to have a separate pair of fields.
So this:
private static String name;
private static String ID;
should be
private String name;
private String id; // Name changed to follow conventions
I'd personally make them final as well, to make it clear they don't change over the course of the lifetime of the object:
private final String name;
private final String id;
You'll want to change some of your static methods to be instance methods, too... and look at the rest of your fields. Basically you need take care about what aspects of state are intended to be type-wide (static), and what aspects are meant to be per-instance. (You should look at the same issues for Game, as well. Fundamentally static fields are usually pretty rare in an application. When you end up with almost all your fields static - as per your project at the moment - that suggests you need to look at the design again.)

Change a Singleton with static properties into a Multiton

I have the following Singleton class
public class TestFileEngine {
private static int counter = 0;
private static List<GeneratedFile> generatedFileList;
private static Optional<TestFileEngine> engine = Optional.empty();
private TestFileEngine() {
generatedFileList = Collections.synchronizedList(new ArrayList<>());
}
public static synchronized TestFileEngine getInstance() {
if (!engine.isPresent()) {
engine = Optional.of(new TestFileEngine());
}
return ruEngine.get();
}
public synchronized void generateFile() {
counter++;
String timestamp = LocalDateTime.now().toString();
String content = "";//insert random content generation
GeneratedFile gf = new GeneratedFile(counter + ".txt", timestamp, content);
generatedFileList.add(gf);
System.out.println("Generated file " + counter + ".txt");
}
public GeneratedFile findFileByName(String filename) {
for (GeneratedFile file : generatedFileList){
if(file.getFileName().equals(filename)){
return file;
}
}
return null;
}
}
Now I want to have two separate engines (and possibly more in the future), for tracking purpose and I stumbled upon the multiton pattern, still using the lazy implementation. So I will change the following:
//Getting rid of Optional, as it can get funky with Maps
private static final Map<String, TestFileEngine> _engines = new HashMap<String, TestFileEngine>();
public static synchronized TestFileEngine getInstance(String key) {
if (_engines.get(key) == null) {
_engines.put(key, new TestFileEngine());
System.out.println("Create engine " + key);
}else {
System.out.println("Using engine " + key);
}
return _engines.get(key);
}
I want to be sure that each of the engines has its separate counter and file list. However after I ran the following code, it seems they share counter and list:
TestFileEngine.getInstance("http").generateFile();
TestFileEngine.getInstance("http").generateFile();
TestFileEngine.getInstance("http").generateFile();
TestFileEngine.getInstance("ftp").generateFile();
TestFileEngine.getInstance("ftp").generateFile();
System.out.println(TestFileEngine.getInstance("http").findFileByName("4.txt").getFileName());
System.out.println(TestFileEngine.getInstance("ftp").findFileByName("2.txt"));
Console:
Create engine http
Generated file 1.txt
Using engine http
Generated file 2.txt
Using engine http
Generated file 3.txt
Create engine ftp
Generated file 4.txt
Using engine ftp
Generated file 5.txt
Using engine http
4.txt
Using engine ftp
null
What should I do to the counter and generatedFileList fields so that each TestFileEngine created from the Multiton is completely separated?
Actually you declare the 3 fields of the class with the static modifier :
private static int counter = 0;
private static List<GeneratedFile> generatedFileList;
private static final Map<String, TestFileEngine> _engines = new HashMap<String, TestFileEngine>();
static fields are shared among all instances of the current class.
You want that for engines field.
But you don't want that for counter and generatedFileList fields that have to be attached to a specific instance of TestFileEngine.
So make them instance fields instead of static.
As a side note, int fields are by default valued to 0 and you should avoid _ to prefix your variables that doesn't make part of the naming conventions.
So you could write :
private int counter;
private List<GeneratedFile> generatedFileList;
private static final Map<String, TestFileEngine> engines = new HashMap<String, TestFileEngine>();

How can I assign instance of a class from an array to a class initializer in java?

In a Android application I am making I have an array of instances of a certain class I made, and later in the program I need to use the getter and setter methods from that class on an instance of the class from the array. Do I need to assign the instance of the class from the array to a new class initializer? Here is some code to clear this up:
Class
public class ProfileInformation {
private String console;
private String gamertag;
public String getConsole() {
return console;
}
public void setConsole(String console) {
this.console = console;
}
public String getGamertag() {
return gamertag;
}
public void setGamertag(String gamertag) {
this.gamertag = gamertag;
}
}
Array
ArrayList<ProfileInformation> ProfTags = new ArrayList<>();
Some instances of ProfileInformation are then added to arraylist, and then I get one of the instances from the arraylist and try to use getGamertag() to set it to a string:
ProfileInformation profNew = ProfTags.get(ProfTags.size()-1);
String example = profNew.getGamertag();
The problem is example will equal null. Why is this?
First, an Arraylist is a List, try not to confuse that with actual arrays.
Do I need to assign the instance of the class from the array to a new class initializer?
You don't need to get an element out of the Arraylist, no. You can chain many methods together
String example = ProfTags.get(ProfTags.size()-1).getGamertag();
example will equal null. Why is this?
For the same reason any object is null... You never set it equal to anything else
This code runs on my laptop:
public static void main(String[] args) {
ArrayList<ProfileInformation> ProfTags = new ArrayList<>();
element = new ProfileInformation();
element.setGamertag("Actual Gamer tag value");
ProfTags.add(element);
ProfileInformation profNew = ProfTags.get(ProfTags.size()-1);
String example = profNew.getGamertag();
}
Output is:
Actual Gamer tag value
I guess you didn't call setGamertag(String).

ArrayList<HashMap> returning empty in tests and UI?

Here's my code. I apologize for the sloppiness but essentially what it's supposed to do is simulate the backwards learning algorithm used by switches. The handleInput method takes in the src and dest MAC addresses and a port number and adds the src MAC and port# as a HashMaps into an ArrayList. The whole method is useless right now because none of the HashMaps stay in the ArrayList for some reason. Any help is much appreciated!
public class Switching {
ArrayList<HashMap> switchTable = new ArrayList<HashMap>();
public String handleInput(String srcMacAddress, int portNumber, String destMacAddress){
String output = "";
HashMap tableEntry = new HashMap();
tableEntry.put(srcMacAddress, portNumber);
for (HashMap hm : switchTable) {
if (hm.containsKey(destMacAddress)) {
output += hm.get(destMacAddress).toString();
} else {
output += "Ports flooded";
}
}
switchTable.add(tableEntry);
return output;
}
public ArrayList<HashMap> getTable(){
return switchTable;
}
public class SwitchingTests {
#Test
public void testSwitching(){
new Switching().handleInput("123456", 12, "abcdef");
ArrayList<HashMap> switchingTable = new Switching().getTable();
Assert.assertEquals(switchingTable.toString(), "[{123456=12}]");
}
}
You are creating a Switching object and call handleInput(...) on it and then proceed to create a new Switching object and get its table.
You need to get the table from the one you already created.
public class SwitchingTests {
#Test
public void testSwitching(){
Switching switching = new Switching();
switching.handleInput("123456", 12, "abcdef");
ArrayList<HashMap> switchingTable = switching.getTable();
Assert.assertEquals(switchingTable.toString(), "[{123456=12}]");
}
}
inside your handleInput method you are creating new switchTable.
what you have to do is to change your line
switchTable = new ArrayList() to switchTable = getTable();

Associating a pair of Strings in Java 7 and looping over them

Given the following code:-
//setup code and import statements, including:
private static String baseURL = Environment.getTestWebsiteURL();
public static String articleOneName = ArticleCreationTest.getArticleOneName();
public static String articleTwoName = ArticleCreationTest.getArticleTwoName();
public static String articleThreeName = ArticleCreationTest.getArticleThreeName();
public static String articleOnePath = ArticleCreationTest.getArticleOnePath();
public static String articleTwoPath = ArticleCreationTest.getArticleTwoPath();
public static String articleThreePath = ArticleCreationTest.getArticleThreePath();
public static String[] articlesPathArray = {articleOnePath, articleTwoPath, articleThreePath}
#BeforeClass
public static void setup() {
driver = Driver.getURL();
for (String s : articlesArray) {
if (s == null) {
//tell me which articles could not be found
System.out.println("Could not find an article for: " + s + " , perhaps it wasn't created in the prior test");
} else {
//assuming array holds some path values, append to the baseURL
driver.get(baseURL + s);
}
}
#Test...
//run some test assertions against the baseURL + path website page that is returned
I need the code to loop through wherever the path variable holds a value and run tests. The current solution is not helpful wherever the prior ArticleCreationTest fails to generate the article, because the variable simply contains null. So the text is: "Could not find an article for: null, perhaps it wasn't created in the prior test".
What I really need is to associate the articleName with the articlePath so the message is something like: "Could not find ArticleOne: perhaps is wasn't created", and then run the tests against all that were created. Perhaps some kind of hashmap or 2D array?
Based on the code given,
public static String articleOneName = ArticleCreationTest.getArticleOneName();
public static String articleTwoName = ArticleCreationTest.getArticleTwoName();
public static String articleThreeName = ArticleCreationTest.getArticleThreeName();
public static String articleOnePath = ArticleCreationTest.getArticleOnePath();
public static String articleTwoPath = ArticleCreationTest.getArticleTwoPath();
public static String articleThreePath = ArticleCreationTest.getArticleThreePath();
public static String[] articlesPathArray = {articleOnePath, articleTwoPath, articleThreePath}
It seems like, it is a list of articleNames and articlePaths
List<String> acticleNames = new ArrayList<String>();
List<String> acticlePaths = new ArrayList<String>();
The List will contain the Strings to be checked, which can be used for the tests.
I need the code to loop through wherever the path variable holds a
value and run tests
You can check this condition by checking if (s != null), currently you are checking for
if (s == null)

Categories