Java 學習記錄67 — Maps 3/3

張小雄
8 min readJun 2, 2021

--

MainLocation.java 改

import java.util.HashMap;
import java.util.Map;
public class MainLocation { private static Map<Integer, Location> locations = new HashMap<Integer, Location>(); public static void main(String[] args) {// Scanner scanner = new Scanner(System.in);
//
// locations.put(0, new Location(0, "You are sitting in front of a computer learning Java."));
// locations.put(1, new Location(1, "You are standing at the end of road before a small brick building."));
// locations.put(2, new Location(2, "You are at the top of a hill."));
// locations.put(3, new Location(3, "You are inside a building, a well house for a small spring."));
// locations.put(4, new Location(4, "You are in a valley beside a stream."));
// locations.put(5, new Location(5, "You are in the forest."));
//
// locations.get(1).addExit("W", 2);
// locations.get(1).addExit("E", 3);
// locations.get(1).addExit("S", 4);
// locations.get(1).addExit("N", 5);
//
// locations.get(2).addExit("N", 5);
//
// locations.get(3).addExit("W", 1);
//
// locations.get(4).addExit("N", 1);
// locations.get(4).addExit("W", 2);
//
// locations.get(5).addExit("W", 2);
// locations.get(5).addExit("S", 1);
// int loc = 1;
// while (true) {
// System.out.println(locations.get(loc).getDescription());
// if (loc == 0) {
// break;
// }
// Map<String, Integer> exits = locations.get(loc).getExits();
// System.out.println("Available exits are ");
// for (String exit : exits.keySet()) {
// System.out.println(exit + ", ");
// }
// System.out.println();
//
// String direction = scanner.nextLine().toUpperCase();
// if (exits.containsKey(direction)) {
// loc = exits.get(direction);
// } else {
// System.out.println("You cannot go in that direction");
// }
//
//
// }
String[] road = "You are standing at the end of a road before a small brick building".split(" ");
for (String i : road) {
System.out.println(i);
}
System.out.println("==============");
String[] building = "You are inside a building, a well house for a small spring".split((", "));
for (String i : building) {
System.out.println(i);
}
}
}

輸出結果:

You

are

standing

at

the

end

of

a

road

before

a

small

brick

building

==============

You are inside a building

a well house for a small spring

介紹 spilt 用法

小挑戰:

Change the program to allow players to type full words, or phrases, then move to the

correct location based upon their input.

The player should be able to type commands such as “Go West”, “run South”, or just “East”

and the program will move to the appropriate location if there is one. As at present, an

attempt to move in an invalid direction should print a message and remain in the same place.

Single letter commands (N, W, S, E, Q) should still be available.

參考答案:

Location.java

import java.util.HashMap;
import java.util.Map;
public class Location {
private final int locationId;
private final String description;
private final Map<String, Integer> exits;
public Location(int locationId, String description) {
this.locationId = locationId;
this.description = description;
this.exits = new HashMap<String, Integer>();
this.exits.put("Q", 0);
}
public void addExit(String direction, int location) {
exits.put(direction, location);
}
public int getLocationId() {
return locationId;
}
public String getDescription() {
return description;
}
public Map<String, Integer> getExits() {
return new HashMap<String, Integer>(exits);
}
}

MainLocation.java 改

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class MainLocation { private static Map<Integer, Location> locations = new HashMap<Integer, Location>(); public static void main(String[] args) { Scanner scanner = new Scanner(System.in);
String[] west = "west,go west,w".split((", "));
locations.put(0, new Location(0, "You are sitting in front of a computer learning Java."));
locations.put(1, new Location(1, "You are standing at the end of road before a small brick building."));
locations.put(2, new Location(2, "You are at the top of a hill."));
locations.put(3, new Location(3, "You are inside a building, a well house for a small spring."));
locations.put(4, new Location(4, "You are in a valley beside a stream."));
locations.put(5, new Location(5, "You are in the forest."));
locations.get(1).addExit("W", 2);
locations.get(1).addExit("E", 3);
locations.get(1).addExit("S", 4);
locations.get(1).addExit("N", 5);
locations.get(2).addExit("N", 5); locations.get(3).addExit("W", 1); locations.get(4).addExit("N", 1);
locations.get(4).addExit("W", 2);
locations.get(5).addExit("W", 2);
locations.get(5).addExit("S", 1);
Map<String, String> vocabulary = new HashMap<>();
vocabulary.put("QUIT", "Q");
vocabulary.put("NORTH", "N");
vocabulary.put("SOUTH", "S");
vocabulary.put("WEST", "W");
vocabulary.put("EAST", "E");
int loc = 1;
while (true) {
System.out.println(locations.get(loc).getDescription());
if (loc == 0) {
break;
}
Map<String, Integer> exits = locations.get(loc).getExits();
System.out.println("Available exits are ");
for (String exit : exits.keySet()) {
System.out.print(exit + ", ");
}
System.out.println();
String direction = scanner.nextLine().toUpperCase();
if (direction.length() > 1) {
String[] words = direction.split(" ");
for (String word : words) {
if (vocabulary.containsKey(word)) {
direction = vocabulary.get(word);
break;
}
}
}
if (exits.containsKey(direction)) {
loc = exits.get(direction);
} else {
System.out.println("You cannot go in that direction");
}
}
}
}

輸出結果:

You are standing at the end of road before a small brick building.

Available exits are

Q, S, E, N, W,

wanna go to south \\ user enter

You are in a valley beside a stream.

Available exits are

Q, N, W,

sadfjklsdafjlkasdjf i need to quit her \\ user enter

You are sitting in front of a computer learning Java.

主要就是檢測使用者有無輸入關鍵字

把超過一個單詞的字句,拆成一個一個單詞,然後循環檢測

包含方向關鍵字的話就可以移動,不包含的話就提示不能往那移動

但我自己試驗輸入多個方向的話,就會往第一個檢測到的方向移動

畢竟是用循環去跑,第一個出現符合的,邏輯就往下走了

想一想還是滿有趣的,這款上古老遊戲,用這小小的技巧

就能讓不懂原理的使用者以為這款遊戲,真的智能到能理解玩家輸入的訊息

Joao

Note that in the Location class, Tim does not return a reference to the map.

A new HashMap is created from the existing map.

So if you change elements in this returned map, it does not change the elements in the original map, as you have no reference to the original map in the Location class.

public class Location {
private final Map<String, Integer> exits;

public Map<String, Integer> getExits() {
return new HashMap<String, Integer>(exits);// exits added as a parameter to constructor
}

Here “below” I add a method to the above class violating the immutability rule.

import java.util.*;
import java.util.HashMap;

public class Test {

public static void main(String[] args){
// adding two locations N and S
Location room = new Location(1,"Room");
Location kitchen = new Location(2,"Kitchen");
room.addExit("N",2);
kitchen.addExit("S",1);
System.out.println("Using getExits() which is immutable") ;
Map<String,Integer> locs = room.getExits();// here I get te map
System.out.println(locs);// {Q=0, N=2}
locs.put("S",3); // I add an element to my map with reference locs
System.out.println(locs);//{Q=0, N=2, S=3}
System.out.println(room.getExits());// {Q=0, N=2} did not change as its immutable
// now I added a method to my class that returns a reference to the map. Not mutable
// and try the code above again
System.out.println("Using getMutableExits() which is mutable") ;
locs = room.getMutableExits() ; // returns a mutable reference to map
System.out.println(locs);//{Q=0, N=2}
locs.put("S",3); //
System.out.println(locs);//{ {Q=0, S=3, N=2}
System.out.println(room.getExits()); //{Q=0, S=3, N=2} it changed, does not matter as I had access with getMutableExits
}
}


class Location {
private final int locationID;
private final String description;
private final Map<String, Integer> exits;

public Location(int locationID, String description) {
this.locationID = locationID;
this.description = description;
this.exits = new HashMap<String, Integer>();
this.exits.put("Q", 0);
}

public void addExit(String direction, int location) {
exits.put(direction, location);
}
public int getLocationID() {
return locationID;
}

public String getDescription() {
return description;
}

public Map<String, Integer> getExits() {
return new HashMap<String, Integer>(exits);
}

public Map<String, Integer> getMutableExits() {
return exits; // see I return a reference to my map
}
}

output:

  1. outputs
  2. Using getExits() which is immutable
  3. {Q=0, N=2}
  4. {Q=0, N=2, S=3}
  5. {Q=0, N=2}
  6. Using getMutableExits() which is mutable
  7. {Q=0, N=2}
  8. {Q=0, S=3, N=2}
  9. {Q=0, S=3, N=2}

Here are the Rules

To define an immutable class, make it final.

Make all its fields private and final.

Provide only accessor methods (i.e., getters but don’t provide mutator methods (setters).

For fields that are mutable reference types, (Sush as HashMaps )or methods that need to mutate the state, create a deep copy of the object if needed.

Goran

Hi,

You cant compare the situations with Integer since it is already immutable.

The shallow copy means you are creating another list but with same elements so at the end you have 2 lists that work independently, if you add/remove from 1st the 2nd is not affected and that is because there are 2 different lists in memory. Try to check video reference types vs value types. When you have 2 references to the same object then situation is different but here you have 2 references where each of them points to a different object in memory e.g. a different lists since there are 2 lists.

Immutable class means that once an object is created, we cannot change its content. In Java, all the wrapper classes (like Integer, Boolean, Byte, Short) and String class is immutable.

Lets create our own immutable class

public class Student {
private final String name;
private final int registrationNumber;

public Student(String name, int registrationNumber) {
this.name = name;
this.regNo = registrationNumber;
}

public String getName() {
return name;
}

public int getRegistrationNumber() {
return registrationNumber;
}
}

What you can notice there are no setters e.g. you cannot change the internal data of class (immutable) that is also the reason why name and registrationNumber are final (cannot be dereferenced/changed).

So if we create few instances of Student class we have to do something like the following

Student miniMe = new Student("miniMe", 123); 
Student bill = new Student("Bill", 456);
List<Student> students = new ArrayList<>();
students.add(miniMe);
students.add(bill);

Now we have list of 2 students. Since there are no setters we cannot change the student internal data like name and registration number.

Now to create a shallow copy of the list you can do something like this

List<Student> copyStudents = new ArrayList<>(students);

This creates another list that contains same elements like the students list but now we have 2 lists. So if you modify the first list lets say add new student

Student max = new Student("max", 789);
copyStudents.add(max);

Now student max is only in copyStudents list because we have 2 lists, if you want it in both lists you have to add it to both lists.

Lets now create class Person

public class Person {
private String firstName;
private String lastName;

public Person (String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getFirstName() {
return firstName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getLastName() {
return lastName;
}

@Override
public String toString() {
return firstName;
}
}

Now this class has setters and after we create instance of person we can call setter to set the internal data in this case firstName and lastName. This class is mutable/changeable the opposite of immutable/non-changeable.

So you can do something like the following to create shallow copy (2 lists, same references to elements)

Person miniMe = new Person("mini", "Me");
Person bill = new Person("Billie", "Jean");

List<Person> people = new ArrayList<>();
people.add(miniMe);
people.add(bill);

List<Person> shallowCopy = new ArrayList<>(people); // if we add/remove from this list the other list is not affected (2 lists)
shallowCopy.remove(miniMe); // removes miniMe from copy

System.out.println("people= " + people); // prints both
System.out.println("shallowCopy= " + shallowCopy); // prints Billie since miniMe was removed

// BOTH lists contain same references of elements so if we change the element both are affected
bill.setFirstName("Bill");

System.out.println("people= " + people); // prints both but this time Bill
System.out.println("shallowCopy= " + shallowCopy); // prints Bill

A deep copy would be to copy each person in other words to create the new instance of Person with same name and number, that is why it is called deep.

So you could do something like this

List<Person> deepCopy = new ArrayList<>();

for(Person person : people) {
Person copyPerson = new Person(person.getFirstName(), person.getLastName()); // copy whole person (new object)
deepCopy.add(copyPerson);
}

That is a deep copy, when you copy each element and create a new instance with same internal data.

Sorry for the long answer.

Hope that makes sense.

--

--

張小雄
張小雄

Written by 張小雄

記錄成為軟體工程師的過程

No responses yet