31

请原谅篇幅,但这里有两个程序,两者完全相同,但一个有一个没有 setter、getter 和构造函数。

我以前上过一个基本的 C++ 课程,但不记得其中的任何一个,目前我没有看到它们的意义,如果有人能用蹩脚的术语解释它们,我将非常感激.. . 目前它们似乎只不过是浪费空间让我的代码看起来更长,但老师说它们很重要(到目前为止就是这样)。

提前致谢!现在这里是代码: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();
    }
}

现在对于没有 setter 和 getter 的那个:

测试里程.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);
    }
}

测试里程测试.java:

package gasMileage;

public class Testmileagetest 
{

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

}

再次感谢!

4

14 回答 14

28

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

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

于 2009-09-22T18:06:40.400 回答
25

封装

访问器方法(“setter 和 getter”)试图隐藏有关对象中数据如何存储的详细信息。在实践中,它们是以非面向对象的方式存储和检索数据的一种美化手段。访问器没有有效地封装任何东西,因为以下两段代码之间几乎没有实际区别:

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

和这个:

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

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

反而:

Person bob = new Person();
bob.colourHair( Colour.RED );

这遵循“告诉,不要问”的前提。换句话说,应该(由其他对象)指示对象执行特定任务。这就是面向对象编程的重点。而且似乎很少有人得到它。

这两种场景的区别在于:

  • 在第一种情况下,鲍勃无法控制他的头发会变成什么颜色。非常适合喜欢红发的发型师,对于鄙视那种颜色的鲍勃来说不是很好。
  • 在第二种情况下,Bob 可以完全控制他的头发将变成什么颜色,因为未经 Bob 许可,不允许系统中的其他对象更改该颜色。

避免此问题的另一种方法是返回 Bob 头发颜色的副本(作为新实例),该副本不再与 Bob 耦合。我发现这是一个不优雅的解决方案,因为这意味着另一个类希望使用 Person 的头发的行为不再与 Person 本身相关联。这会降低重用代码的能力,从而导致重复代码。

隐藏数据类型

在 Java 中,不能有两个仅通过返回类型不同的方法签名,它实际上并没有隐藏对象使用的底层数据类型。如果有的话,您很少会看到以下内容:

public class Person {
  private long hColour = 1024;

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

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

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 );
  }
}

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

告诉,不要问

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

文件示例

考虑以下代码,根据 ColinD 的回答稍作修改:

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()是多余的,并且(在实践中)不可避免地会导致重复的代码,例如:

public void arbitraryMethod( File file ) {
  if( file.getType() == "JPEG" ) {
    // Code.
  }
}

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

问题:

  • 数据类型。type属性不能轻易地从字符串更改为整数(或其他类)。
  • 隐含协议。PNG将类型从特定的 ( , JPEG, TIFF, EPS) 抽象为一般的 ( IMAGE, DOCUMENT, )非常耗时SPREADSHEET
  • 介绍错误。更改隐含协议不会产生编译器错误,这可能会导致错误。

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

public void arbitraryMethod( File file ) {
  if( file.isValidType( "JPEG" ) ) {
    // Code.
  }
}

这意味着将get访问器方法更改为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() );
   }
}

File当类将隐含协议从特定类型(例如,JPEG)转换为通用类型(例如,IMAGE)时,系统中的其他代码不会中断。系统中的所有代码都必须使用该isValidType方法,该方法不给调用对象类型,而是告诉File验证一个类型。

于 2009-09-22T20:33:38.810 回答
18

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

让我们以一个文件为例(忽略FileJava 中类的存在)。此类File有一个用于存储文件类型(.pdf、.exe、.txt 等)的字段……我们将忽略其他所有内容。

最初,您决定将其存储为String没有 getter 和 setter 的:

public class File {
   // ...
   public String type;
   // ...
}

以下是不使用 getter 和 setter 的一些问题。

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

你班的任何客户都可以用它做他们想做的事:

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

您稍后决定您可能不希望他们这样做......但是由于他们可以直接访问您班级中的字段,因此您无法阻止它。

无法轻松更改内部表示:

稍后,您决定要将文件类型存储为名为 的接口的实例FileType,从而允许您将某些行为与不同的文件类型相关联。但是,您班级的许多客户已经在检索文件类型并将其设置为Strings。String所以你会有一个问题......如果你只是将字段从 a 更改为a ,你会破坏很多代码(即使是其他项目中你无法修复的代码,如果它是一个库)FileType

Getter 和 Setter 如何解决这个问题

现在想象一下,您已经创建了 type 字段private并创建了

public String getType() {
   return this.type;
}

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

控制设置属性:

现在,当您想要实现只有某些字符串是有效文件类型并阻止其他字符串的要求时,您可以编写:

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类型的表示相对容易。想象一下,您有一个enum ValidFileTypewhich 实现FileType并包含有效类型的文件。

您可以像这样轻松更改类中文件类型的内部表示:

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()无论如何,从他们的角度来看,没有任何变化。只有类的内部发生了变化,其他类使用的接口没有改变。

于 2009-09-22T21:30:27.947 回答
5

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

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

而不是使用:

milesPerGallon = miles / gallons;

在计算里程()

您可以更改 setMiles() 和 setGallons() 以在调用 milesPerGallon 时对其进行更新。然后,删除 setMilesPerGallon() 以表明它是只读属性。

于 2009-09-22T18:06:57.793 回答
3

关键是一个类不应该允许直接访问它的字段,因为这是特定于实现的。您可能希望稍后更改该类以使用另一个数据存储,但对其“用户”保持该类相同,或者您可能希望创建一个也不能包含字段的接口。

查看有关该主题的Wikipedia 文章

于 2009-09-22T18:04:59.420 回答
2

一般来说,setter 和 getter 是早期 GUI 构建器(borland)的一个糟糕的 hack,以绕过所有变量都应该是私有的这一事实(真的,这是绝对必要的)

有些人称它们为抽象,但事实并非如此。样板设置器/获取器并不比公共成员好。它们仍然允许在类无法控制的情况下完全访问变量,并且仍然限制类内更改(如果您的变量是 int,您仍然必须更改调用 setter 和 getter 的所有内容才能将变量更改为字符串)

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

强迫一个 Setter 进入你的所有类也是可怕的,这意味着你的类根本不可能是不可变的,而你实际上应该有一个非常好的理由让一个类可变。

也就是说,它们对于诸如持久性引擎和 GUI 构建器之类的横切关注点很有用,它们可以在其中获取和设置值,并且该类可以监视已获取或更改的内容,并对其进行修改或验证。

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

这将允许非 OO 横切代码正常工作,允许您的类在需要时修改集合和获取,并在必要时允许获取器(有时非常有用)。

于 2011-03-18T21:58:16.663 回答
2

使用 getter 和 setter 可以让您灵活地在以后更改实现。你可能认为你不需要那个,但有时你确实需要。例如,您可能希望使用代理模式来延迟加载使用成本高的对象:

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 将对象映射到数据库时,这通常会发挥作用。您只加载您需要的东西,然后在实际使用时返回数据库加载其余部分。

于 2009-09-22T18:13:38.187 回答
2

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

Mileage m = new Mileage();
m.miles = 5.0;
m.gallons = 10.0;
...

现在,如果您决定要为您的类添加一些验证,则必须在直接访问字段的任何地方更改您的代码。如果您只是从一开始就使用 getter 和 setter(仅在需要它们的地方),您可以避免这种努力,并且只在一个地方更改您的代码。

于 2009-09-22T18:08:07.980 回答
1

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)

于 2009-09-22T20:07:25.063 回答
1

你的例子极端荒谬。是的,所有这些 getter 和 setter 都会使代码膨胀,在这种情况下不会增加任何价值。但是封装的基本思想是针对由许多交互组件组成的大型系统,而不是针对小型、独立的程序。

getter 和 setter 的有用、合理使用的特点:

  • 许多其他类使用的类(隐藏实现细节使客户更容易)
  • Getter 和 setter 仅用于实际需要它们的字段 - 尽可能少,大多数字段应该是私有的并且只在他们的类中使用
  • 通常很少有 setter:可变字段比只读字段更难跟踪程序的状态
  • 除了访问字段之外,实际执行某些操作的 getter 和 setter,例如,为无效值抛出异常或更新“最后修改”时间戳的 setter,或者即时计算值而不是依赖于基础字段的 getter
于 2009-09-22T20:39:31.823 回答
1

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

访问器方法

信息隐藏/封装

于 2009-09-22T18:04:12.627 回答
0

Getter 和 Setter 允许您构建有用的快捷方式来访问和更改对象中的数据。通常,这可以看作是对具有用于获取和设置值的对象的两个函数的替代方法,如下所示:

{
    getValue: function(){
        return this._value;
    },
    setValue: function(val){
        this._value = val;
    }
}

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

function Field(val){
    var value = val;

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

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

添加 Setter 和 Getter 方法 要使托管 bean 的状态可访问,您需要为该状态添加 setter 和 getter 方法。createSalutation 方法调用bean 的sgreet 方法,getSalutation 方法检索结果。一旦添加了 setter 和 getter 方法,bean 就完成了。最终代码如下所示:包问候语;

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;
    }
}
于 2010-09-15T02:10:39.267 回答
0

快进几个月。也许您的老师要求您实现 Milage 类的远程版本。也许作为一个网络服务,也许是别的东西。

如果没有 getter/setter,您必须在任何地方更改访问 Milage 的每个代码,而使用 getter/setter,您几乎(至少在完美世界中)只需更改 Milage 类型的创建。

于 2009-09-22T18:08:57.150 回答
0

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

  • 我已经实现了一个使用天数变量的类。
  • 在我的班级中,没有人可以设置超过 365 天的值。
  • 有人想从我的班级继承。(代码可重用性)。
  • 现在当他输入超过 365 天的值时,我的类的所有功能都将失败。
  • 因此,我应该将 days 变量声明为私有数据字段。
  • 现在,如果我将天数数据字段声明为私有,那么没有人可以将天数的值设置为超过 365,因为我将实现一个带有上述输入限制的设置器函数。
于 2016-05-02T10:17:00.317 回答