可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
This question already has an answer here:
-
Why use getters and setters/accessors? [closed]
38 answers
Please forgive the length, but here are two programs, both the exact same, but one with and one without setters, getters, and constructors.
I've taken a basic C++ class before and don't remember any of these from it, and at the moment I'm not seeing the point of them, if anyone could explain them in lamen's terms I'd much appreciate it...at the moment they seem to be nothing more than space wasters to make my code look longer, but the teacher says they are important (and so far that's it).
Thanks in advance! And now here's the code:
Mileage.java:
package gasMileage;
import java.util.Scanner; //program uses class Scanner
public class Mileage
{
public int restart;
public double miles, gallons, totalMiles, totalGallons, milesPerGallon;
public Mileage(int newRestart, double newMiles, double newGallons,
double newTotalMiles, double newTotalGallons, double newMilesPerGallon)
{
setRestart(newRestart);
setMiles(newMiles);
setGallons(newGallons);
setTotalMiles(newTotalMiles);
setTotalGallons(newTotalGallons);
setMilesPerGallon(newMilesPerGallon);
}
public void setRestart(int newRestart)
{
restart = newRestart;
}
public int getRestart()
{
return restart;
}
public void setMiles(double newMiles)
{
miles = newMiles;
}
public double getMiles()
{
return miles;
}
public void setGallons(double newGallons)
{
gallons = newGallons;
}
public double getGallons()
{
return gallons;
}
public void setTotalMiles(double newTotalMiles)
{
totalMiles = newTotalMiles;
}
public double getTotalMiles()
{
return totalMiles;
}
public void setTotalGallons(double newTotalGallons)
{
totalGallons = newTotalGallons;
}
public double getTotalGallons()
{
return totalGallons;
}
public void setMilesPerGallon(double newMilesPerGallon)
{
milesPerGallon = newMilesPerGallon;
}
public double getMilesPerGallon()
{
return milesPerGallon;
}
public void calculateMileage()
{
Scanner input = new Scanner(System.in);
while(restart == 1)
{
System.out.print("Please input number of miles you drove: ");
miles = input.nextDouble();
totalMiles = totalMiles + miles;
System.out.print("Please input number of gallons you used: ");
gallons = input.nextDouble();
totalGallons = totalGallons + gallons;
milesPerGallon = miles / gallons;
System.out.printf("Your mileage is %.2f MPG.\n", milesPerGallon);
System.out.print("Would you like to try again? 1 for yes, 2 for no: ");
restart = input.nextInt();
}
milesPerGallon = totalMiles / totalGallons;
System.out.printf("Your total mileage for these trips is: %.2f.\nYour total gas consumed on these trips was: %.2f.\n", totalMiles, totalGallons);
System.out.printf("Your total mileage for these trips is: %.2f MPG", milesPerGallon);
}
}
Mileagetest.java:
package gasMileage;
public class Mileagetest
{
public static void main(String[] args)
{
Mileage myMileage = new Mileage(1,0,0,0,0,0);
myMileage.calculateMileage();
}
}
And now for the one without setters and getters:
Testmileage.java:
package gasMileage;
import java.util.Scanner;
public class Testmileage
{
int restart = 1;
double miles = 0, milesTotal = 0, gas = 0, gasTotal = 0, mpg = 0;
Scanner input = new Scanner(System.in);
public void testCalculate()
{
while(restart == 1)
{
System.out.print("Please input miles: ");
miles = input.nextDouble();
milesTotal = milesTotal + miles;
System.out.print("Please input gas: ");
gas = input.nextDouble();
gasTotal = gasTotal + gas;
mpg = miles/gas;
System.out.printf("MPG: %.2f", mpg);
System.out.print("\nContinue? 1 = yes, 2 = no: ");
restart = input.nextInt();
}
mpg = milesTotal / gasTotal;
System.out.printf("Total Miles: %.2f\nTotal Gallons: %.2f\nTotal MPG: %.2f\n", milesTotal, gasTotal, mpg);
}
}
Testmileagetest.java:
package gasMileage;
public class Testmileagetest
{
/**
* @param args
*/
public static void main(String[] args)
{
Testmileage test = new Testmileage();
test.testCalculate();
}
}
Thanks again!
回答1:
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.
回答2:
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.
回答3:
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.setHairColour( 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.
回答4:
The idea is that if your client classes call get/set functions, you can change what they do later and the callers are insulated. If you have a public variable, and I access it directly, there is no way for you to add behavior later when it is accessed or set.
Even in your simple example, you could take more advantage of it.
Instead of using:
milesPerGallon = miles / gallons;
in calculateMileage()
You could change setMiles() and setGallons() to update milesPerGallon when they were called. Then, remove setMilesPerGallon() to indicate that it's a read-only property.
回答5:
The point is that a class should not allow direct access to its fields, because this is implementation-specific. You may want to change the class later in order to use another data storage, but keep the class the same for its "users", or you may want to create an interface which cannot include fields either.
Have a look at the Wikipedia article on the subject.
回答6:
They provide a public interface for your class, and some measure of encapsulation. Consider how you would access public data without getters and setters.
Mileage m = new Mileage();
m.miles = 5.0;
m.gallons = 10.0;
...
Now if you decide you want to add some validation to your class, you have to change your code everywhere that the fields were directly accessed. If you just use getters and setters from the start (only where they are needed) you can avoid that effort, and only change your code in one place.
回答7:
Using getters and setters gives you the flexibility to change the implementation later. You might not think you need that, but sometimes you do. For example, you might want to use the Proxy pattern to lazy load an object that is expensive to use:
class ExpensiveObject {
private int foo;
public ExpensiveObject() {
// Does something that takes a long time.
}
public int getFoo() { return foo; }
public void setFoo(int i) { foo = i; }
}
class ExpensiveObjectProxy extends ExpensiveObject {
private ExpensiveObject realObject;
public ExpensiveObjectProxy() { ; }
protected void Load() {
if ( realObject == null ) realObject = new ExpensiveObject();
}
public int getFoo() { Load(); return realObject.getFoo(); }
public void setFoo(int i) { Load(); realObject.setFoo(i); }
}
class Main {
public static void main( string[] args ) {
// This takes no time, since ExpensiveOjbect is not constructed yet.
ExpensiveObject myObj = new ExpensiveObjectProxy();
// ExpensiveObject is actually constructed here, when you first use it.
int i = myObj.getFoo();
}
}
Where this often comes to play is when you have objects mapped to databases through an ORM. You only load the stuff you need, then go back to the database to load the rest if/when it is actually used.
回答8:
In general setters and getters were a bad hack by the early GUI builders (borland) to get around the fact that all variables should be private (Really, this is absolutely necessary)
Some people call them an abstraction, but they are not. A boilerplate setter/getter is no better than a public member. They still allow full access to the variable at times the class can't control and still restrict in-class changes (if your variable is an int, you still have to change everything that calls the setter and getter to change the variable to a string)
Getters and Setters encourage accessing the data of a class from outside the class. Any code that accesses a member of a class should probably exist within that class (as your design states) and therefore shouldn't need setters or getters. They SHOULD be unnecessary.
Also forcing a Setter into all your classes is horrid, it means that your classes simply can't be immutable whereas you should actually have a Really Good Reason to make a class mutable.
That said, they are kind of useful for cross-cutting concerns like persistance engines and GUI builders where they can get and set values and the class can monitor what was got or changed and modify or validate it.
A better pattern for those systems that need the cross-cutting variable access would be to access the variable directly through reflection BUT to call a setter or getter if one exists--making the setter and getter private if possible.
This would allow the non-OO cross-cutting code to work correctly, would allow your class to modify sets and gets when it needs to and allow getters (which are sometimes really useful) where necessary.
回答9:
The point of accessor methods ie. getters and setters is to provide encapsulation AKA information hiding. It's one of the basic principles of object oriented programming.
Accessor methods
Information hiding/encapsulation
回答10:
The answer in one word is interfaces.
Interfaces allow for methods, not fields, so the established convention is to have getX and setX methods for this purpose.
(And interfaces is the way to decouple functionality from implementation in Java)
回答11:
Your example is extreme to the point of absurdity. Yes, all those getters and setters bloat the code and add no value in that case. But the underlying idea of encapsulation is meant for larger systems composed of many interacting components, not for small, self-contained programs.
Characteristics of useful, sensible uses of getters and setters:
- A class that is used by many other classes (hiding implementation details makes it easier for the clients)
- Getters and setters only for fields for which they're actually needed - as few as possible, most fields should be private and used only within their class
- Very few setters in general: mutable fields make it much harder to keep track of the program's state than read-only fields
- Getters and setters that actually do something besides accessing a fied, e.g. setters that throw exceptions for invalid values or update a "last modified" timestamp, or a getter that computes a value on the fly rather than relying on an underlying field
回答12:
Fast forward a few months. Maybe your teacher asks you to implement a remote version of the Milage class. Maybe as a web service, maybe something else.
Without the getter/setters, you'd have to change every code everywhere that acccesses a
Milage, with the getter/setters you pretty much(in a perfect world atleast) just have to change the creation of a Milage type.
回答13:
Getters and Setters allow you to build useful shortcuts for accessing and mutating data within an object. Generally, this can be seen as an alternative to having two functions with an object that are used to get and set a value, like so:
{
getValue: function(){
return this._value;
},
setValue: function(val){
this._value = val;
}
}
The obvious advantage to writing JavaScript in this manner is that you can use it obscure values that you don't want the user to directly access. A final result looking something like the following (using a closure to store the value of a newly constructed Field):
function Field(val){
var value = val;
this.getValue = function(){
return value;
};
this.setValue = function(val){
value = val;
};
}
Adding Setter and Getter Methods
To make the state of the managed bean accessible, you need to add setter and getter methods for that state. The createSalutation method calls the bean’sgreet method, and the getSalutation method retrieves the result.
Once the setter and getter methods have been added, the bean is complete. The final code looks like this:
package greetings;
import javax.inject.Inject;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
@Named
@RequestScoped
public class Printer {
@Inject @Informal Greeting greeting;
private String name;
private String salutation;
public void createSalutation() {
this.salutation = greeting.greet(name);
}
public String getSalutation() {
return salutation;
}
public String setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
回答14:
Encapsulation and code reuse-ability is the beauty of object oriented programming.
If We are dealing with some sensitive data in our code then we declare it as private data fields i.e. we encapsulate our data so that no one can access it directly.Now any one who wants to access those data fields must make use of setters and getters i.e. a controlled access mechanism to deal with sensitive data fields. Following example can be helpful in understanding the advantage and importance of setter and getters.
- I have implemented a class in which i am making use of days variable.
- In my class no one can set the value of days more than 365.
- Some one wants to inherit from my class.(code re-usability).
- Now when he enters the value of days more than 365, then all functionality of my class will fail.
- Hence i should have declared the days variable as private data field.
- Now if i had declared days data field as private then no one could set the value of days more then 365 as i would have implemented a setter functions with mentioned limitations about input.