This question already has answers here:
How to create immutable objects in Java?
(14 answers)
Closed 2 years ago.
How to create immutable Planet so the name doesn't change? I am struggling as I think it is immutable project with mutable object. Correct me if I am wrong.
Every time I change name in the output also changes. Am I missing something?
I tried to do all fields private and final (not in this example) but I think I am missing some code to work.
I know java.util.Date is deprecated but this is just for example.
import java.util.Date;
public final class Planet {
String name;
private final Date discoveryDate;
public Planet (String name, Date discoveryDate) {
this.name = name;
this.discoveryDate = new Date(discoveryDate.getTime());
}
public String getName()
return name;
}
public Date getDiscoveryDate() {
return new Date(discoveryDate.getTime());
}
public static void main(String [] args) {
Planet Earth = new Planet("Earth Planet", new Date(2020,01,16,17,28));
System.out.println("Earth");
System.out.println("------------------------------------");
System.out.println("Earth.getName: " + Earth.getName());
System.out.println("Earth.getDiscoveryDate: " + Earth.getDiscoveryDate());
}
}
tl;dr
Either:
Make a record like this, in Java 16 and later:public record Planet( String name , LocalDate discovered ) {}
Or, before Java 16, make a class where you:
Mark all member fields final and private.
Make getter methods as needed, but no setter methods.
Record
Just use the new records feature in Java 16 (previewed in Java 15).
Define your class as a record when its main job is to transparently and immutably carry data. The compiler implicitly creates a constructor, the getters, hashCode & equals, and toString.
Notice that the getter methods implicitly defined in a record do not begin with the JavaBeans-style get… wording. The getter method is simply the name of member field as defined in the parentheses following the class name.
Of course, if your getter methods provide access to an object that is itself mutable, being contained in a record does nothing to stop the calling programmer from mutating the contained object. Notice in the example class next that both String and LocalDate classes are themselves immutable by design. So the mutability of a contained object is a non-issue here.
package org.example;
import java.time.LocalDate;
public record Planet( String name , LocalDate discovered )
{
}
Using that record.
Planet Earth = new Planet( "Earth" , LocalDate.of( 2020 , 1 , 16 ) );
System.out.println( "Earth" );
System.out.println( "------------------------------------" );
System.out.println( "Earth.name: " + Earth.name() );
System.out.println( "Earth.discovered: " + Earth.discovered() );
When run.
Earth
------------------------------------
Earth.name: Earth
Earth.discovered: 2020-01-16
Class
Without the records feature, to make sure a class is immutable you should:
Mark the member fields final. This means the field cannot be assigned a different object after the constructor has finished.
Mark the member fields private. This means objects of other classes will not have direct access to read or change those fields.
Provide getter methods, if needed, but no setter methods. By convention, the JavaBeans-style get… or is… naming is used.
You should also provide appropriate override implementations of hashCode, equals, and toString. Your IDE will help generate the source code for those.
package org.example;
import java.time.LocalDate;
import java.util.Objects;
public class Planète
{
// Member fields
final String name;
final LocalDate discovered;
// Constructors
public Planète ( String name , LocalDate discovered )
{
Objects.requireNonNull( name );
Objects.requireNonNull( discovered );
this.name = name;
this.discovered = discovered;
}
// Getters (read-only immutable class, no setters)
public String getName ( ) { return this.name; }
public LocalDate getDiscovered ( ) { return this.discovered; }
// Object class overrides
#Override
public boolean equals ( Object o )
{
if ( this == o ) return true;
if ( o == null || getClass() != o.getClass() ) return false;
Planète planète = ( Planète ) o;
return getName().equals( planète.getName() ) && getDiscovered().equals( planète.getDiscovered() );
}
#Override
public int hashCode ( )
{
return Objects.hash( getName() , getDiscovered() );
}
#Override
public String toString ( )
{
return "Planète{ " +
"name='" + name + '\'' +
" | discovered=" + discovered +
" }";
}
}
Using that class.
Planète Earth = new Planète( "Earth" , LocalDate.of( 2020 , 1 , 16 ) );
System.out.println( "Earth" );
System.out.println( "------------------------------------" );
System.out.println( "Earth.getName: " + Earth.getName() );
System.out.println( "Earth.getDiscoveryDate: " + Earth.getDiscovered() );
Side issues
Do not start a decimal integer literal with 0. The leading zero makes the number octal rather decimal. So your code passing 2020,01,16 should be 2020,1,16.
Never use the Date class, nor Calendar or SimpleDateFormat. These terrible classes are now legacy, supplanted years ago by the modern java.time classes defined in JSR 310. In code above, we used java.time.LocalDate to represent a date-only value, without a time-of-day and without a time zone.
Planet is immutable but field name should be private.
Related
Im new to java and I am super confused on how you format arrays to hold objects, specifically with multiple data types. I am trying to make an array called myMonsters that essentially holds a bunch of other monsters, which is a string and an integer. 1, I really don't understand how to put objects into an array. 2, I don't get why the array are under one class name instead of another.
What I mean -->
private Monster[] myMonsters;
private int s;
public Monsters(int size)
{
s = size;
myMonsters = new Monster[s];
}
public void add(int spot, Monsters m)
{
myMonsters[spot] = m;
}
This creates the array that holds the monster objects. I don't understand why I would create the array under Monster. Doesn't it make more sense to create it under the Monsters class, where the monster objects are being held?
private int weight;
private int height;
private int age;
public Monster( int a, int h, int w )
{
age = a;
height = h;
weight = w;
}
And of course this creates the objects.
I can tell how poorly formatted this questions is but can you still help me with this?
A class is a template, like a cookie-cutter for making cookies. A class has no content, just as a cookie-cutter has no cookie dough.
A cookie-cutter exists only to make delicious cookies. A class exists only to make useful objects (a.k.a. instances). Objects have content inside, just as cookies have dough inside.
Monster is the class, the cookie-cutter, for making various monster objects.
In Java 16 and later, we can more briefly define a class as a record. The compiler implicitly creates the constructor, getters, equals & hashCode, and toString.
package work.basil.example.animalkingdom.cookies;
public record Monster(String name , int weight)
{
}
As a conventional class, that would be:
package work.basil.example.animalkingdom.cookies;
import java.util.Objects;
public final class Monster
{
private final String name;
private final int weight;
public Monster ( String name , int weight )
{
this.name = name;
this.weight = weight;
}
public String name ( ) { return name; }
public int weight ( ) { return weight; }
#Override
public boolean equals ( Object obj )
{
if ( obj == this ) return true;
if ( obj == null || obj.getClass() != this.getClass() ) return false;
var that = ( Monster ) obj;
return Objects.equals( this.name , that.name ) &&
this.weight == that.weight;
}
#Override
public int hashCode ( )
{
return Objects.hash( name , weight );
}
#Override
public String toString ( )
{
return "Monster[" +
"name=" + name + ", " +
"weight=" + weight + ']';
}
}
Let's cut some cookies using that cookie-cutter. We want to represent 3 monsters, each with a different name and different weight.
Monster x = new Monster( "Alice" , 42 );
Monster y = new Monster( "Bob" , 77 );
Monster z = new Monster( "Carol" , 58 );
There we have three separate objects, each cut from the same Monster class. We are currently handling the 3 monsters separately, each assigned to separate variables.
If we want to track those monsters together, in a particular order, we do not need your Monsters class. Just use an array.
Say we want them organized together by weight.
Monster[] monstersByWeight = new Monster[ 3 ];
monstersByWeight[ 0 ] = x;
monstersByWeight[ 1 ] = z;
monstersByWeight[ 2 ] = y;
The Monster[] says we want this variable named monstersByWeight to hold an array that holds Monster objects. The new Monster[ 3 ] creates such an array, an array with three empty slots. The = puts that array of three empty slots into the variable named monstersByWeight so we can use it later.
Dump that array to the console, to inspect.
String output = Arrays.toString( monstersByWeight );
System.out.println( "output = " + output );
output = [Monster[name=Alice, weight=42], Monster[name=Carol, weight=58], Monster[name=Bob, weight=77]]
You said:
… to hold objects, specifically with multiple data types
There is only one data type involved here: Monster. We have only Monster objects to track. So the data type of the array named monstersByWeight is Monster.
If we want to add another Monster object:
Monster david = new Monster( "David" , 63 ) ;
…to our array, we need to open up a slot. Using arrays, this is tedious programming. Likely we would create another array, move elements from old array to new array while skipping the slot we want to keep open for our new Monster. Not fun writing that code.
Fortunately, that code has already been written. We can use the class ArrayList. This class implementing the List interface. ArrayList can do all the element-insertion work for us.
List< Monster > monstersByWeight = new ArrayList<>() ;
The List< Monster > monstersByWeight says we want a list that holds Monster objects. The new ArrayList<>() instantiates such a list, of a default size.
Next we add our Monster objects.
monstersByWeight.add( x ) ;
monstersByWeight.add( z ) ;
monstersByWeight.add( y ) ;
Dump to console.
System.out.println( monstersByWeight ) ;
[Monster[name=Alice, weight=42], Monster[name=Carol, weight=58], Monster[name=Bob, weight=77]]
Later we can insert our new Monster object named David, assigned to variable named david. At weight of 63, this new monster belongs before the one named Bob but after the one named Carol. Like arrays, the List interface uses annoying zero-based counting rather than ordinal counting. To specify the 3rd element, we say 2 (annoying, as I said).
monstersByWeight.add( 2 , david ) ;
Dump to console.
System.out.println( monstersByWeight ) ;
[Monster[name=Alice, weight=42], Monster[name=Carol, weight=58], Monster[name=David, weight=63], Monster[name=Bob, weight=77]]
You asked:
This creates the array that holds the monster objects. I don't understand why I would create the array under Monster. Doesn't it make more sense to create it under the Monsters class, where the monster objects are being held?
No, you would not want a collection of Monster objects stored within the class of Monster. The job of the Monster class is to define the state (data) and behavior (methods) for any one Monster object (instance) that represents any one real-world monster.
You want a separate class to track a bunch of Monster objects together. Here in this Answer we used an instance of the ArrayList class which we named monstersByWeight to track our group of monsters. You could just as well have written your own MonsterList class if you wanted to do all that array-rearranging yourself, as practice. But for real work, use ArrayList.
The principal at work here is called separation of concerns. The ArrayList class knows nothing of what it means to be a Monster. And the Monster class knows nothing of how to group together Monster objects. Following this principal leads to clean, well-organized code that is easier to read, comprehend, and maintain.
I wanted to create a class with a custom data type that returns the class object. Consider a class Custom:
public class Custom {
// Some fields.
public Custom(String custom) {
// Some Text.
}
// Some Methods.
public void customMethod() {
// Some Code.
}
}
Now, consider a second class TestCustom:
public class TestCustom {
public static void main(String[] args) {
Custom custom = new Custom("Custom");
System.out.println(custom); // This should print "Custom"
custom.customMethod(); // This should perform the action
}
}
So, the question how to get the value custom on instantiating an object instead of memory location. Like what I get is:
Custom#279f2327
The java.util.Date class returns the current date. This can be seen as the constructor for the class is
public Date() {
this(System.currentTimeMillis());
}
For example, the following code would print out the current date:
DateFormat format = new SimpleDateFormat("dd/MM/yyyy");
Date date = new Date();
System.out.println(format.format(date));
The Answer by ML72 is correct and should be accepted. The java.util.Date constructor captures the current moment in UTC.
java.time
The java.util.Date class is terrible, for many reasons. That class is now legacy, supplanted years ago but the java.time classes as of the adoption of JSR 310.
The java.time classes avoid constructors, instead using factory methods.
The replacement for java.util.Date is java.time.Instant. To capture the current moment in UTC, call the class method .now().
Instant instant = Instant.now() ;
If you want the current moment as seen through the wall-clock time used by the people of a particular region (a time zone), use ZoneId to get a ZonedDateTime object. Notice again the factory method rather than a constructor.
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;
Adjust to UTC by extracting an Instant.
Instant instant = zdt.toInstant() ;
Override the toString() method, as it is automatically invoked when you try to display an object:
Add a field. For example;
private String value;
In the constructor, add the following code:
value = custom;
this will assign a value passed to the constructor as a parameter, to the value field.
And finally override the toString() method as follows:
#Override
public String toString() {
return value;
}
Now, when you display the value of the custom object, the overridden toString() method will be invoked and the argument will be displayed instead of the memory address. Whereas methods of the object will work as they are programmed to work. There is nothing to be changed with them.
I want to validate an argument passed into the setter.
I have an Enum and in this Enum I have a car colors.
In the setter I want to check if the passed in argument is a part of my Color enum.
I am looking for any enum method but I haven't found any. I tried to use regex but this was not good idea.
public enum Color {
BLUE, RED, BLACK, WHITE
}
public void setColor(String color) {
//hear should be a method checking if a color contain enum
this.color = color;
}
I expect that this element will be contain an enum.
tl;dr
In the setter I want to check if the passed in argument is a part of my Color enum.
Wrong approach. You should be passing an object, one of your enum objects, rather than the text of the name of the color.
See the poster.setBackgroundColor( Color.GREEN ) example below.
That is the main point of using enums in Java: To let the compiler check at compile-time if a valid value was passed, rather than you worrying about validation in your code at runtime.
Details
Looks like you are mixing up some things. You may be mixing up the enum instance (a constant), its hard-coded name, and a name you may want to display to the user at runtime.
An Enum is a way to conveniently instantiate a predetermined number of instances of a class, each instance being assigned a predetermined name. For example, Month, with Month.JANUARY through Month.DECEMBER.
Constant name
If you want that hard-coded name of the instance, in all uppercase by convention (as a constant), then call toString.
String constantName = Month.JANUARY.toString() ;
JANUARY
Be very clear here: The String object with the text “JANUARY” was generated by the Month object named JANUARY. The Month object and the String object are entirely separate and distinct. The text of the String is merely a representation of the value of the Month object.
If you want to obtain the constant by that hard-coded instance name, call valueOf.
Month m = Month.valueOf( "JANUARY" ) ; // Returns a `Month` object, not text.
Try to not make a habit of valueOf. Calling valueOf defeats the purpose and power of enums in Java. You should be passing around the enum objects, not a string of their names.
Display name
Those all-uppercase English values may work well enough for logging and debugging. But we do not likely want all-uppercase English text to appear in our user-interface and reports. So when writing your own Enum, add a method named something like getDisplayName, as seen on Month & DayOfWeek. Specify TextStyle for the length of abbreviation, and Locale for the human language and cultural norms used in translation.
String output = m.getDisplayName(
TextStyle.FULL ,
Locale.CANADA_FRENCH
) ;
janvier
How to implement that on your own enum? Well, your enum is actually a subclass of Enum. So your enum is actually a Java class like any other, in that it can have constructors and methods. Add arguments to each declared enum instance name.
Example class
Here is a complete example enum class in a single .java file, with a main method to demo.
package work.basil.example;
import java.util.EnumSet;
import java.util.Set;
public enum Color {
BLACK( "Black" ), // Pass arguments to each declared constant instant name, as you are really calling the constructor on each `static` `final` class constant object being automatically instantiated.
GREY( "Grey" ),
RED( "Red" ),
GREEN( "Green" ),
BLUE( "Blue" );
private String displayName; // Store the display name as a member variable on each enum instance.
// Constructor
Color ( String displayName ) { // Define a constructor taking the display name argument you passed above.
this.displayName = displayName;
}
String getDisplayName ( ) {
return this.displayName;
}
public static void main ( String[] args ) {
System.out.println( "Color.BLACK.toString(): " + Color.BLACK.toString() );
System.out.println( "Color.BLACK.getDisplayName(): " + Color.BLACK.getDisplayName() );
Set < Color > monochromeColors = EnumSet.of( Color.BLACK , Color.GREY );
boolean isRedMonochrome = monochromeColors.contains( Color.RED ); // ➙ false.
System.out.println( "isRedMonochrome: " + isRedMonochrome );
}
}
When run.
Color.BLACK.toString(): BLACK
Color.BLACK.getDisplayName(): Black
isRedMonochrome: false
Notice the use of EnumSet (or EnumMap) to collect enum instances. Here we defined a collection of Color objects in a Set named monochromeColors for the black and grey colors, but ignoring the red, green, and blue colors.
To use your enum, imagine a Poster class with a setter method for the background color of the poster.
Poster poster = new Poster() ;
poster.setBackgroundColor( Color.GREEN ) ;
No need for text – just use objects
Notice that there is no text involved. There is no need for string of the name of the instance, nor any need for the display name. Using an enum instance is about using an object, not text, not a String. Using the enum object ensures valid values, provides type-safety, and makes your code more self-documenting.
Using names
Let's add code needing those names. First for logging, add a toString method on Poster.
#Override public String toString() {
return "Poster{ name=" + this.name + " | backgroundColor: " + this.backgroundColor.toString() + " }" ; // Shows "GREEN"
}
When presenting the poster in the user-interface, show the name of the color.
Label label = new Label(
"Background color: " +
poster.getBackgroundColor().getDisplayName() // Shows "Green".
) ;
I have this pared down set of class files as an example of OO. They work perfectly but I do not understand how the println call from WorkerTest.java makes it all the way through Worker.java and to Date.java? Both Worker.java and Date.java have toString methods but neither are explicitly called, but I can tell from the output that both are used.
How is this working or what concept should I be studying?
public class WorkerTest {
public static void main( String[] args ) {
Date birth = new Date( 7, 15, 1922 );
Worker worker = new Worker( birth );
System.out.println( worker );
}
}
public class Worker {
private Date birthday;
public Worker( Date inboundBirthday ) {
birthday = inboundBirthday;
}
public String toString() {
return String.format( "Birthday: %s", birthday );
}
}
public class Date {
private int month;
private int day;
private int year;
public Date( int inboundMonth, int inboundDay, int inboundYear ) {
month = inboundMonth;
day = inboundDay;
year = inboundYear;
}
public String toString() {
return String.format( "%d/%d/%d", month, day, year );
}
}
Output:
Birthday: 7/15/1922
PrintStream.println(obj) calls obj.toString(). (Or more precisely: it calls String.valueOf(obj), which in turn calls obj.toString() unless obj is a null reference.)
See the Javadoc for java.io.PrintStream.
(Maybe what you're missing is that toString is actually a method on java.lang.Object? Worker and Date are merely overriding it. So all objects have that method, and JDK methods can rely on its existence. See the Javadoc for `java.lang.Object for a list of all the methods that all objects have.)
When println encounters a variable it tries to determine how it should be printed. It checks to see if the toString() method for the class in question has been overridden. So here's what's happening: println needs to print an instance of class worker, so it checks for the toString() method inside the Worker class. Inside the worker class it finds this line:
return String.format( "Birthday: %s", birthday );
Now it must figure out how to print birthday. Since birthday is an instance of Date, it checks for Date's toString() method. The key thing in understanding all this is that Java's built in classes have toString() methods too, you just don't see them. This is a good example because it shows you what's happening behind the scenes.
PrintStream.println() explicitly makes a call to String.valueOf(), which in turn calls Object.toString(), which is overridden in your two objects.
I have created a number objects using an array statement, and I can println the values passed within the class as it is created, but when I try and retrieve element values from outside of the class (monopolygame class) it doesn't recognise the refrence - how can I refrence this correctly?
public class monopolygame {
public static void main(String[] args) {
//set up array of 18 objects
property properties[] = new property[18];
//create 18 property objects and populate array
properties[0] = new property("a","available",400,500);//create property
properties[1] = new property("b","available",400,500);//create property
properties[2] = new property("c","available",200,300);//create property
properties[3] = new property("d","available",100,180);//create property
properties[4] = new property("e","available",400,700);//create property
}
}
property class...
public class property
{
public static void main(String[] args)
{
}
//constructor
public property(String propertyname, String owner, double price, double rent)
{
System.out.println("Property info for " + propertyname
+ " - Rent : £" + rent
+ "Price : £" + price
+ "owned by :" + owner);
}
}
I am using this kind of reference in the monopoly class to try and access the data
if (properties[2].propertyname == "available")
{
System.out.println("avaialble");
}
else
{
System.out.println("sold");
}
Thanks
You have to declare those attributes in the "property" class first:
class property {
String propertyname;
String owner;
int price;
int rent;
public Property( String somename, String owner, int price, int rent ) {
this.propertyname = somename;
this.owner = owner;
this.price = price;
this.rent = rent;
// and so on
}
}
The array you're using is local to the main method.
To access it outside of the scope of the main method you should declared either as a class attribute or as an instance attribute like this:
public class monopolygame {
public static property properties[];
public static void main(String[] args) {
//set up array of 18 objects
properties = new property[18];
.....
That way you can access the array in other method like this:
public void setUp() {
for( property p : properties ) {
System.out.println( p.propertyname ); // etc.
Then your code:
if (properties[2].propertyname == "available")
Will work.
BTW in Java all the class name start with uppercase by convention , so it should be:
Property instead of property and MonopolyGame instead of monopolygame
Given the code you've supplied us with, it doesn't look like you're actually storing the values passed in to your property constructor. Here's something a bit closer to what your property class should look like:
public class property
{
private String propertyname;
private String owner;
private double price;
private double rent;
public String getPropertyName()
{
return propertyname;
}
public void setPropertyName(string newName)
{
propertyname = newName;
}
// more getter/setter methods here
public property(String propertyname, String owner, double price, double rent)//constructor
{
this.propertyname = propertyname;
this.owner = owner;
this.price = price;
this.rent = rent;
System.out.println("Property info for " + propertyname + " - Rent : £" + rent + "Price : £" + price + "owned by :" + owner);
}
}
A few remarks:
In Java, string comparisons need to
be done with the equals() method, not
==. See this link for an explanation of why using == might work in some cases, but that shouldn't be expected.
It is a convention to capitalize class names -> Property rather than
property.
Avoid mixing and matching bracket positioning. Use at the end of the same line or at the beginning of the next line, but not both. The most frequent use is at the end of the same line.
You need to add a public method to access the internal of monopolygame class. That is the main aspect of your question.
But in general your code is not the correct way of doing things in Java. Class names must be capitalized, for example. An empty main in the second class is pointless. You need to learn more about the basic Java stuff, I answer your question because I think you could learn a lot here, but I suggest you to check the trails covering the basics on The Java Tutorial.
Two problems are immediately obvious:
You're not storing the arguments passed to the property constructor in fields within that class.
Once you do that, you're trying to compare strings by reference (or by identity, via ==) rather than by value (via String#equals(String)). Unless you've interned the strings via String#intern(), two different String instances with the same character content will not compare as equal via ==. That comparison only looks at the memory addresses of the object references, which will most likely point to two different String instances, each with a different address.
As this question looks like a homework assignment, please tag it as such.