关于oop:java中的setter和getter有什么意义?

What is the point of setters and getters in java?

本问题已经有最佳答案,请猛点这里访问。

请原谅这个长度,但是这里有两个程序,两者完全相同,但是一个有设置器,一个没有设置器,getter和构造函数。

我以前学过一个C++基础课,不记得其中的任何一个,现在我看不到它们的要点,如果有人能用拉门的术语来解释的话,我会很感激的……此刻,它们似乎只不过是空间浪费,使我的代码看起来更长,但是老师说它们很重要(到目前为止)。它)。

事先谢谢!下面是代码:Mileage.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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.
"
, 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.
Your total gas consumed on these trips was: %.2f.
"
, totalMiles, totalGallons);
        System.out.printf("Your total mileage for these trips is: %.2f MPG", milesPerGallon);
    }
}

java里程测试:

1
2
3
4
5
6
7
8
9
10
package gasMileage;

public class Mileagetest
{
    public static void main(String[] args)
    {
        Mileage myMileage = new Mileage(1,0,0,0,0,0);
        myMileage.calculateMileage();
    }
}

现在对于没有二传手和得手的人来说:

测试里程.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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("
Continue? 1 = yes, 2 = no:"
);
            restart = input.nextInt();
        }
            mpg = milesTotal / gasTotal;
            System.out.printf("Total Miles: %.2f
Total Gallons: %.2f
Total MPG: %.2f
"
, milesTotal, gasTotal, mpg);
    }
}

测试mileagetest.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package gasMileage;

public class Testmileagetest
{

    /**
     * @param args
     */

    public static void main(String[] args)
    {
        Testmileage test = new Testmileage();
        test.testCalculate();
    }

}

再次感谢!


无论语言如何,getter和setter的要点是隐藏底层变量。这允许您在尝试设置值时添加验证逻辑-例如,如果您有一个出生日期字段,您可能只希望允许将该字段设置为过去的某个时间。如果字段是可公开访问和可修改的,则无法强制执行此操作-您需要getter和setter。

即使您还不需要任何验证,将来也可能需要它。现在编写getter和setter意味着接口保持一致,所以当您更改它时,现有的代码不会中断。


其他的答案通常给出了使用getter和setter的一些原因,但我想给出一个完整的例子,说明它们为什么有用。

让我们以一个文件为例(在Java中忽略EDCOX1的0个类)的存在。这个File类有一个用于存储文件类型的字段(.pdf,.exe,.txt等)。我们会忽略其他一切。

最初,您决定将其存储为不带getter和setter的String

1
2
3
4
5
public class File {
   // ...
   public String type;
   // ...
}

这里有一些不使用getter和setter的问题。

无法控制字段的设置方式:

您类中的任何客户机都可以使用它做他们想要的事情:

1
2
3
4
5
public void doSomething(File file) {
   // ...
   file.type ="this definitely isn't a normal file type";
   // ...
}

你后来决定你可能不希望他们那样做…但是由于他们可以直接访问你班上的这个领域,所以你没有办法阻止它。

无法轻松更改内部表示:

稍后,您仍然决定要将文件类型存储为名为FileType的接口的实例,允许您将某些行为与不同的文件类型相关联。但是,您类中的许多客户机已经在检索和设置文件类型为String。因此,您会遇到一个问题……如果您刚将字段从String更改为FileType,则会破坏许多代码(即使是在其他项目中,如果是库,则无法修复自己的代码)。

getter和setter如何解决这个问题

现在假设您已经创建了类型字段private,并创建了

1
2
3
4
5
6
7
public String getType() {
   return this.type;
}

public void setType(String type) {
   this.type = type;
}

对设置属性的控制:

现在,当您想要实现一个要求,即只有特定的字符串是有效的文件类型并阻止其他字符串时,您可以只写:

1
2
3
4
5
6
7
8
9
10
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
}

能够轻松更改内部表示:

更改类型的String表示形式相对容易。假设您有一个enumValidFileType,它实现FileType,并包含有效类型的文件。

您可以很容易地更改类中文件类型的内部表示,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
   }
}

由于该类客户一直在呼叫getType()setType(),所以从他们的角度看没有任何变化。只更改了类的内部,而不是其他类正在使用的接口。


包封

访问器方法("setters和getter")试图隐藏有关如何存储对象中数据的详细信息。在实践中,它们是以非面向对象的方式存储和检索数据的光荣手段。访问器不能有效地封装任何内容,因为以下两段代码之间的实际差异很小:

1
2
3
Person bob = new Person();
Colour hair = bob.getHairColour();
hair.setRed( 255 );

而这:

1
2
3
Person bob = new Person();
Colour hair = bob.hairColour;
hair.red = 255;

这两个代码片段都揭示了一个人与头发紧密耦合的思想。这种紧密的耦合会在整个代码库中显示出来,从而导致软件脆弱。也就是说,改变一个人头发的存储方式变得很困难。

而是:

1
2
Person bob = new Person();
bob.setHairColour( Colour.RED );

这遵循了"说,不问"的前提,也就是说,对象应该(被其他对象)指示执行特定的任务。这就是面向对象编程的全部要点。似乎很少有人能得到它。

这两种情况的区别在于:

  • 在第一种情况下,鲍勃无法控制他的头发会变成什么颜色。对于一个喜欢红头发的发型师来说是很好的,而对于那些看不起这种颜色的鲍勃来说则不是很好。
  • 在第二种情况下,Bob完全可以控制他的头发将变成什么颜色,因为没有Bob的许可,系统中的任何其他对象都不允许更改该颜色。

另一种避免这个问题的方法是返回鲍勃的头发颜色的副本(作为一个新实例),它不再与鲍勃耦合。我发现这是一个不雅的解决方案,因为这意味着有另一个阶级想要的行为,使用一个人的头发,不再与这个人本身相关。这会降低重用代码的能力,从而导致代码重复。

隐藏数据类型

在Java中,它不能有两种仅由返回类型不同的方法签名,它确实不隐藏对象所使用的基础数据类型。你很少看到以下情况:

1
2
3
4
5
6
7
public class Person {
  private long hColour = 1024;

  public Colour getHairColour() {
    return new Colour( hColour & 255, hColour << 8 & 255, hColour << 16 & 255 );
  }
}

通常,单个变量的数据类型通过使用相应的访问器逐字公开,并且需要重构来更改它:

1
2
3
4
5
6
7
8
9
10
11
12
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 );
  }
}

虽然它提供了一个抽象的层次,但它是一个薄面纱,对松散耦合没有任何作用。

告诉,不要问

有关此方法的更多信息,请阅读"告诉,不要问"。

文件示例

考虑下面的代码,从科林德的答案中稍微修改一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 );
   }
}

在这种情况下,方法getType()是多余的,在实践中不可避免地会导致重复的代码,例如:

1
2
3
4
5
6
7
8
9
10
11
public void arbitraryMethod( File file ) {
  if( file.getType() =="JPEG" ) {
    // Code.
  }
}

public void anotherArbitraryMethod( File file ) {
  if( file.getType() =="WP" ) {
    // Code.
  }
}

问题:

  • 数据类型。type属性不能轻易地从字符串更改为整数(或其他类)。
  • 隐含的协议。从特定(PNGJPEGTIFFEPS到一般(IMAGEDOCUMENTSPREADSHEET的类型提取是很费时的。
  • 引入错误。更改隐含的协议不会生成编译器错误,这可能导致错误。

通过阻止其他类请求数据来完全避免问题:

1
2
3
4
5
public void arbitraryMethod( File file ) {
  if( file.isValidType("JPEG" ) ) {
    // Code.
  }
}

这意味着将get访问方法更改为private

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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() );
   }
}

File类将隐含的协议从特定类型(如jpeg)转换为通用类型(如image)时,系统中的任何其他代码都不会中断。系统中的所有代码都必须使用isValidType方法,该方法不向调用对象提供类型,而是通知File类验证类型。


其思想是,如果您的客户机类调用get/set函数,您可以稍后更改它们的操作,并且调用者是隔离的。如果您有一个公共变量,并且我直接访问它,那么在以后访问或设置它时,您将无法添加行为。

即使在您的简单示例中,您也可以利用它。

而不是使用:

1
milesPerGallon = miles / gallons;

在计算里程()时

您可以更改setmiles()和setgall(),以便在调用milespergallon时更新它们。然后,删除setmilespergallon()以指示它是只读属性。


重点是类不应该允许直接访问其字段,因为这是特定于实现的。您可能希望稍后更改类以使用其他数据存储,但为其"用户"保留相同的类,或者您可能希望创建一个不能包含字段的接口。

看看维基百科关于这个主题的文章。


一般来说,setter和getter是早期GUI构建者(borland)的一个糟糕的黑客,他们绕过了所有变量都应该是私有的这一事实(实际上,这是绝对必要的)。

有些人称之为抽象,但事实并非如此。样板文件设置器/getter并不比公共成员好。它们仍然允许在类无法控制和仍然限制类更改的时候对变量进行完全访问(如果变量是in t,则仍然必须更改调用setter和getter的所有内容,才能将变量更改为字符串)

getter和setter鼓励从类外部访问类的数据。任何访问类成员的代码都可能存在于该类中(按照您的设计状态),因此不需要setter或getter。它们应该是不必要的。

另外,强迫setter进入所有类是可怕的,这意味着类不能是不可变的,而实际上您应该有一个很好的理由使类变为可变的。

也就是说,它们对于跨领域的关注点很有用,比如持久性引擎和GUI构建器,在那里它们可以获取和设置值,类可以监视获得或更改的内容,并修改或验证它。

对于那些需要横切变量访问的系统,一个更好的模式是直接通过反射访问变量,但如果存在,则调用setter或getter——如果可能,使setter和getter私有化。

这将允许非OO横切代码正确工作,允许您的类在需要时修改集合和get,并在必要时允许getter(有时真的很有用)。


使用getter和setter为您提供了稍后更改实现的灵活性。你可能认为你不需要,但有时你需要。例如,您可能希望使用代理模式来延迟加载一个昂贵的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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();
    }
}

当您通过ORM将对象映射到数据库时,这通常会起到作用。您只需加载所需的内容,然后返回数据库,在实际使用时加载其余内容。


它们为类提供了一个公共接口和一些封装度量。考虑在没有getter和setter的情况下如何访问公共数据。

1
2
3
4
Mileage m = new Mileage();
m.miles = 5.0;
m.gallons = 10.0;
...

现在,如果决定要向类中添加一些验证,则必须在直接访问字段的所有地方更改代码。如果您从一开始就使用getter和setter(只在需要它们的地方使用),那么您可以避免这种工作,并且只在一个地方更改代码。


你的例子非常荒谬。是的,所有的getter和setter都会膨胀代码,在这种情况下不添加任何值。但是封装的基本思想是针对由许多交互组件组成的更大的系统,而不是针对小型的、自包含的程序。

吸气剂和setter的有用、合理使用特点:

  • 许多其他类使用的类(隐藏实现细节使客户端更容易)
  • getter和setter只用于实际需要它们的字段-尽可能少,大多数字段应该是私有的,并且只在它们的类中使用。
  • 通常很少有setter:可变字段比只读字段更难跟踪程序的状态。
  • 除了访问一个已定义的字段之外,实际还执行某些操作的getter和setter,例如,为无效值抛出异常或更新"上次修改的"时间戳的setter,或是一个动态计算值而不是依赖于底层字段的getter。

一个字的答案是接口。

接口允许使用方法,而不是字段,因此已建立的约定就是为此目的使用getx和setx方法。

(接口是在爪哇中实现功能与实现分离的方法)


访问器方法(即getter和setter)的要点是提供封装,即信息隐藏。它是面向对象编程的基本原则之一。

访问器方法

信息隐藏/封装


封装和代码重用能力是面向对象编程的优点。如果我们在代码中处理一些敏感数据,那么我们将其声明为私有数据字段,也就是说,我们将数据封装起来,这样就没有人可以直接访问它。现在,任何想要访问这些数据字段的人都必须使用setter和getter,也就是说,控制访问机制来处理敏感数据字段。下面的例子有助于理解setter和getter的优势和重要性。

  • 我已经实现了一个类,在这个类中我使用days变量。
  • 在我的课堂上,没有人能设定超过365天的值。
  • 有人想从我的类继承(代码重用)。
  • 现在,当他输入的天数超过365天时,我类的所有功能都将失败。
  • 因此,我应该将days变量声明为私有数据字段。
  • 现在,如果我声明days数据字段为private,那么没有人可以设置超过365天的值,因为我将实现一个setter函数,其中提到了输入的限制。

getter和setter允许您构建访问和改变对象中数据的有用快捷方式。一般来说,这可以看作是一种替代方法,即使用一个对象来获取和设置一个值,例如:

1
2
3
4
5
6
7
8
{
    getValue: function(){
        return this._value;
    },
    setValue: function(val){
        this._value = val;
    }
}

以这种方式编写JavaScript的明显优势在于,您可以使用它来隐藏您不希望用户直接访问的值。最终结果如下(使用闭包存储新构造字段的值):

1
2
3
4
5
6
7
8
9
10
11
function Field(val){
    var value = val;

    this.getValue = function(){
        return value;
    };

    this.setValue = function(val){
        value = val;
    };
}

添加setter和getter方法要使托管bean的状态可访问,需要为该状态添加setter和getter方法。createSalation方法调用bean的greet方法,getSalation方法检索结果。一旦添加了setter和getter方法,bean就完成了。最终代码如下:包装问候语;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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;
    }
}

快进几个月。也许你的老师要求你实现一个远程版本的里程课程。也许是作为一个网络服务,也许是其他东西。

如果没有getter/setter,则必须更改访问Milage,有了getter/setter,你几乎(至少在一个完美的世界里)必须改变milage类型的创建。