java中的setter和getter有什么意义?

时间:2009-09-22 17:58:44

标签: java oop constructor getter-setter

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

之前我参加了一个基础的C ++课程,并且不记得任何这些课程,目前我没有看到他们的意思,如果有人能用lamen的话解释他们,我会非常感激它......目前它们似乎只是空间浪费,使我的代码看起来更长,但老师说它们很重要(到目前为止就是这样)。

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

现在对于没有制定者和吸气者的人来说:

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

}

再次感谢!

14 个答案:

答案 0 :(得分:25)

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

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

答案 1 :(得分:17)

封装

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

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.setHairColour( Colour.RED );

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

两种情况的区别在于:

  • 在第一种情况下,鲍勃无法控制他的头发会变成什么颜色。非常适合喜欢红头发的发型师,对那些鄙视这种颜色的鲍勃来说不是那么好。
  • 在第二种情况下,Bob完全控制了他的头发会变成什么颜色,因为没有Bob的许可,系统中没有其他任何物体可以改变这种颜色。

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

隐藏数据类型

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

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

告诉,不要问

有关此方法的详细信息,请阅读Tell, Don't Ask

文件示例

考虑以下代码,稍微修改一下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属性无法轻易地从字符串更改为整数(或其他类)。
  • 隐含协议。将类型从特定(PNGJPEGTIFFEPS)抽象到以下内容非常耗时一般(IMAGEDOCUMENTSPREADSHEET)。
  • 介绍错误。更改隐含协议不会产生编译错误,这可能会导致错误。

通过阻止其他类询问获取数据来完全避免此问题:

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类来验证类型。

答案 2 :(得分:17)

其他答案通常会很好地了解使用getter和setter的一些原因,但我想提供一个有用的原因,为什么它们有用。

例如,让我们看一个文件(忽略Java中File类的存在)。这个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的接口的实例,从而允许您将某些行为与不同的文件类型相关联。但是,您班级的许多客户端已经检索并将文件类型设置为String。所以你在那里遇到了一个问题......如果你刚刚从{{1}更改了字段,你就会破坏很多代码(即使代码在其他项目中也无法自行修复,如果它是一个库) }到String

Getters and Setters如何解决此问题

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

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,它实现ValidFileType并包含有效类型的文件。

您可以轻松更改类中文件类型的内部表示形式,如下所示:

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(),因此他们的观点没有任何改变。只更改了类的内部,而不是其他类正在使用的接口。

答案 3 :(得分:5)

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

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

而不是使用:

milesPerGallon = miles / gallons;

在calculateMileage()

您可以更改setMiles()和setGallons()以在调用它们时更新milesPerGallon。然后,删除setMilesPerGallon()以指示它是只读属性。

答案 4 :(得分:3)

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

查看有关该主题的Wikipedia article

答案 5 :(得分:2)

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

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

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

答案 6 :(得分:2)

使用getter和setter可以让您以后灵活地更改实现。你可能认为不需要那样,但有时你会这样做。例如,您可能希望使用Proxy模式延迟加载使用起来很昂贵的对象:

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将对象映射到数据库时,通常会发生这种情况。你只需要加载你需要的东西,然后回到数据库加载其余的东西,如果/它实际使用的话。

答案 7 :(得分:2)

一般情况下,早期的GUI构建者(borland)设置者和getter是一个糟糕的黑客,以解决所有变量应该是私有的(真的,这是绝对必要的)

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

Getters和Setters鼓励从课堂外访问课程的数据。访问类成员的任何代码都应该存在于该类中(如您的设计所述),因此不需要setter或getter。它们应该是不必要的。

同样强迫Setter进入你的所有类是可怕的,这意味着你的类根本不可能是不可变的,而你实际上应该有一个真正好的理由来使一个类可变。

也就是说,它们对于诸如持久性引擎和GUI构建器之类的交叉问题很有用,它们可以获取和设置值,类可以监视已获得或更改的内容以及修改或验证它。

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

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

答案 8 :(得分:1)

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

Accessor methods

Information hiding/encapsulation

答案 9 :(得分:1)

一个词的答案是 interfaces

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

(接口是将功能与Java中的实现分离的方式)

答案 10 :(得分:1)

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

吸气剂和二传手的有用,合理用途的特征:

  • 许多其他类使用的类(隐藏实现细节使客户端更容易)
  • 仅针对实际需要的字段的getter和setter - 尽可能少,大多数字段应该是私有的并且仅在其类中使用
  • 一般情况下很少有人设定:可变字段使跟踪程序状态比只读字段更难
  • 除了访问fied之外,实际上的getters和setter,例如为无效值抛出异常或更新“上次修改”时间戳的setter,或者动态计算值而不是依赖于基础字段的getter

答案 11 :(得分:0)

快进几个月。也许你的老师会要求你实现Milage课程的远程版本。也许作为一个Web服务,也许是别的东西。

如果没有getter / setter,你必须更改每个代码的所有代码 Milage,你几乎可以获得吸气剂/安装者(至少在一个完美的世界中)只需要改变Milage类型的创造。

答案 12 :(得分:0)

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

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

以这种方式编写JavaScript的一个明显优势是,您可以使用它不希望用户直接访问的模糊值。最终结果类似于以下内容(使用闭包来存储新构造的Field的值):

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

答案 13 :(得分:0)

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

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