What is the point of setters and getters in java? [duplicate]
The point of getters and setters, regardless of language, is to hide the underlying variable. This allows you to add verification logic when attempting to set a value - for example, if you had a field for a birth date, you might only want to allow setting that field to some time in the past. This cannot be enforced if the field is publicly accessible and modifyable - you need the getters and setters.
Even if you don't need any verification yet, you might need it in the future. Writing the getters and setters now means the interface is kept consistent, so existing code won't break when you change it.
Encapsulation
Accessor methods ("setters and getters") attempt to hide the details about how the data in an object is stored. In practice, they are a glorified means to store and retrieve data in a non-object-oriented fashion. Accessors do not effectively encapsulate anything in that there is little practical difference between the following two pieces of code:
Person bob = new Person();
Colour hair = bob.getHairColour();
hair.setRed( 255 );
And this:
Person bob = new Person();
Colour hair = bob.hairColour;
hair.red = 255;
Both code snippets expose the idea that a Person is tightly coupled to Hair. This tight coupling then reveals itself throughout the code base, resulting in brittle software. That is, it becomes difficult to change how a Person's hair is stored.
Instead:
Person bob = new Person();
bob.colourHair( Colour.RED );
This follows the premise of "tell, don't ask." In other words, objects should be instructed (by other objects) to perform a specific task. This is the whole point of object-oriented programming. And very few people seem to get it.
The difference between the two scenarios is this:
- In the first situation, Bob had no control over what colour his hair would become. Great for a hair stylist with a penchant for redheads, not so great for Bob who despises that colour.
- In the second situation, Bob has complete control over what colour his hair will become because no other object in the system is allowed to change that colour without Bob's permission.
Another way to avoid this problem is to return a copy of Bob's hair colour (as a new instance), which is no longer coupled to Bob. I find that to be an inelegant solution because it means there is behaviour that another class desires, using a Person's hair, that is no longer associated with the Person itself. That reduces the ability to reuse code, which leads to duplicated code.
Hiding Data Types
In Java, which cannot have two method signatures that differ only by return type, it really does not hide the underlying data type used by the object. You will seldom, if ever, see the following:
public class Person {
private long hColour = 1024;
public Colour getHairColour() {
return new Colour( hColour & 255, hColour << 8 & 255, hColour << 16 & 255 );
}
}
Typically, the individual variables have their data type exposed verbatim by use of the corresponding accessor, and requires refactoring to change it:
public class Person {
private long hColour = 1024;
public long getHairColour() {
return hColour;
}
/** Cannot exist in Java: compile error. */
public Colour getHairColour() {
return new Colour( hColour & 255, hColour << 8 & 255, hColour<< 16 & 255 );
}
}
While it provides a level of abstraction, it is a thin veil that does nothing for loose coupling.
Tell, Don't Ask
For more information on this approach, read Tell, Don't Ask.
File Example
Consider the following code, slightly modified from ColinD's answer:
public class File {
private String type = "";
public String getType() {
return this.type;
}
public void setType( String type ) {
if( type = null ) {
type = "";
}
this.type = type;
}
public boolean isValidType( String type ) {
return getType().equalsIgnoreCase( type );
}
}
The method getType()
in this instance is redundant and will inevitably (in practice) lead to duplicated code such as:
public void arbitraryMethod( File file ) {
if( file.getType() == "JPEG" ) {
// Code.
}
}
public void anotherArbitraryMethod( File file ) {
if( file.getType() == "WP" ) {
// Code.
}
}
Issues:
-
Data Type. The
type
attribute cannot readily change from a String to an integer (or another class). -
Implied Protocol. It is time consuming to abstract the type from the specific (
PNG
,JPEG
,TIFF
,EPS
) to the general (IMAGE
,DOCUMENT
,SPREADSHEET
). - Introduces Bugs. Changing the implied protocol will not generate a compiler error, which can lead to bugs.
Avoid the problem altogether by preventing other classes from asking for data:
public void arbitraryMethod( File file ) {
if( file.isValidType( "JPEG" ) ) {
// Code.
}
}
This implies changing the get
accessor method to private
:
public class File {
public final static String TYPE_IMAGE = "IMAGE";
private String type = "";
private String getType() {
return this.type;
}
public void setType( String type ) {
if( type == null ) {
type = "";
}
else if(
type.equalsIgnoreCase( "JPEG" ) ||
type.equalsIgnoreCase( "JPG" ) ||
type.equalsIgnoreCase( "PNG" ) ) {
type = File.TYPE_IMAGE;
}
this.type = type;
}
public boolean isValidType( String type ) {
// Coerce the given type to a generic type.
//
File f = new File( this );
f.setType( type );
// Check if the generic type is valid.
//
return isValidGenericType( f.getType() );
}
}
No other code in the system will break when the File
class transitions the implied protocol from specific types (e.g., JPEG) to generic types (e.g., IMAGE). All the code in the system must use the isValidType
method, which does not give the type to the calling object, but tells the File
class to validate a type.
The other answers generally give a good idea of some reasons for using getters and setters, but I want to give a somewhat complete example of why they are useful.
Let's take, for example, a file (ignoring the existence of a File
class in Java). This File
class has a field for storing the type of the file (.pdf, .exe, .txt, etc)... we'll ignore everything else.
Initially you decide to store it as a String
with no getters and setters:
public class File {
// ...
public String type;
// ...
}
Here are some issues with not using getters and setters.
No control over how the field is set:
Any clients of your class can do what they want with it:
public void doSomething(File file) {
// ...
file.type = "this definitely isn't a normal file type";
// ...
}
You decide later that you probably do not want them to do that... but since they have direct access to the field in your class, you have no way of preventing it.
Inability to easily change internal representation:
Later still, you decide that you want to store the file type as an instance of an interface called FileType
, allowing you to associate some behavior with different file types. However, many clients of your class are already retrieving and setting file types as String
s. So you'd have an issue there... you'd break a lot of code (even code in other projects that you can't fix yourself, if it's a library) if you just changed the field from a String
to a FileType
.
How Getters and Setters solve this
Now imagine that you had instead made the type field private
and created
public String getType() {
return this.type;
}
public void setType(String type) {
this.type = type;
}
Control over setting the property:
Now, when you want to implement a requirement that only certain strings are valid file types and prevent other strings, you could just write:
public void setType(String type) {
if(!isValidType(type)) {
throw new IllegalArgumentException("Invalid file type: " + type);
}
this.type = type;
}
private boolean isValidType(String type) {
// logic here
}
Ability to easily change internal representation:
Changing the String
representation of the type is relatively easy. Imagine you have an enum
ValidFileType
which implements FileType
and contains the valid types of files.
You could easily change the internal representation of the file type in the class like this:
public class File {
// ...
private FileType type;
// ...
public String getType() {
return type.toString();
}
public void setType(String type) {
FileType newType = ValidFileType.valueOf(type);
if(newType == null) {
throw new IllegalArgumentException("Invalid file type: " + type);
}
this.type = newType;
}
}
Since clients of the class have been calling getType()
and setType()
anyway, nothing changes from their perspective. Only the internals of the class changed, not the interface that other classes are using.