java中的setter和getter有啥意义? [复制]

Posted

技术标签:

【中文标题】java中的setter和getter有啥意义? [复制]【英文标题】:What is the point of setters and getters in java? [duplicate]java中的setter和getter有什么意义? [复制] 【发布时间】:2010-11-30 12:02:38 【问题描述】:

请原谅篇幅,但这里有两个程序,完全相同,但一个有一个没有 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 的人:

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


再次感谢!

【问题讨论】:

+1,因为您显然正在尝试正确学习和理解这些概念。 呃...我想我有点像个怪胎...给我一个物理作业,看看我用谷歌搜索这个问题的速度有多快...给我一个编码分配并观察我花了多长时间搜索它工作的原因或为什么它更好。到目前为止,我已经大约 3 天了,我一直在思考/寻找这个答案。 我很惊讶您没有学习 C++ 中的封装。它在那里的应用与在 Java 或任何其他 OO 语言中的应用一样多。我会让你的 C++ 老师难过 ;-)。 顺便说一句,+1 表示您的态度。我以前看过这个问题,我尊重你处理这个问题的方式。其他人只是采取了这样的态度“setter/getter 是荒谬的,浪费时间,你为什么要使用它们。我的教授一定是个混蛋,强迫我做一些荒谬的事情。”你清楚地知道这一定是有原因的,否则他们不会那样教它,你是绝对正确的。 【参考方案1】:

封装

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

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

还有这个:

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

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

改为:

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

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

这两种情况的区别是这样的:

在第一种情况下,Bob 无法控制他的头发会变成什么颜色。非常适合喜欢红发的发型师,但对于鄙视这种颜色的 Bob 来说就不太适合了。 在第二种情况下,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 );
  

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

告诉,不要问

有关此方法的更多信息,请阅读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 类来验证一个类型。

【讨论】:

为什么我找不到 +100 按钮?这个答案很到位。通过一个不起眼的脚注,我将补充一点,用于使用 getter 和 setter 的 bean 不幸地使它们看起来比它们应得的更合法,如今 IIRC 注释是首选。【参考方案2】:

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

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

【讨论】:

【参考方案3】:

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 的greet 方法,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;
    

【讨论】:

【参考方案4】:

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

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

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

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

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

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

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

【讨论】:

【参考方案5】:

其他答案通常很好地说明了使用 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";
   // ...

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

无法轻松更改内部表示:

稍后,您决定要将文件类型存储为名为@9​​87654326@ 的接口的实例,从而允许您将某些行为与不同的文件类型相关联。但是,您班级的许多客户已经在检索和设置文件类型为Strings。所以你会有一个问题......如果你只是将字段从String更改为FileType

Getter 和 Setter 如何解决这个问题

现在假设您改为创建类型字段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()setType(),因此从他们的角度来看没有任何变化。只有类的内部发生了变化,其他类使用的接口没有变化。

【讨论】:

【参考方案6】:

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

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

许多其他类使用的类(隐藏实现细节使客户更容易) Getter 和 setter 仅用于实际需要的字段 - 尽可能少,大多数字段应该是私有的,并且只能在其类中使用 通常很少有 setter:可变字段比只读字段更难跟踪程序的状态 除了访问字段之外,实际上一些事情的获取器和设置器,例如为无效值抛出异常或更新“最后修改”时间戳的 setter,或即时计算值而不是依赖基础字段的 getter

【讨论】:

【参考方案7】:

一个词的答案是接口

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

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

【讨论】:

【参考方案8】:

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

【讨论】:

【参考方案9】:

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

如果没有 getter/setter,您将不得不更改所有访问 Milage,使用 getter/setter,您几乎(至少在完美世界中)只需要更改 Milage 类型的创建。

【讨论】:

【参考方案10】:

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

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

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

【讨论】:

【参考方案11】:

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

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

而不是使用:

milesPerGallon = miles / gallons;

在计算里程()中

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

【讨论】:

你能再解释一下吗? “绝缘”是什么意思?我怎么能改变他们以后可以做的事情?如果你直接访问它,为什么我不能稍后添加一个行为? @Jeff:如果您直接访问英里、加仑和milesPerGallon,milesPerGalllon 将不会更新,因为没有编码逻辑可以这样做。 “绝缘”我的意思是您可以对类进行更改,而使用您的类的其他类根本不需要更改。例如,通常 calculateMileage() 不会在 Mileage 类中(它是应用程序的 UI,而不是计算器)——其他类(例如 ConsoleMileageUI)将通过其公共接口(get/set)访问 Mileage——英里/加仑的计算应该保留在 Mileage 中——也许在 setMiles 和 setGallons 中调用——或者在 getMilesPerGallon() 中调用。无论您选择哪个,calculateMileage() 都不必更改。 @R. Bemrose 在我的第二部分代码中更新得很好,运行起来和第一部分一样好,我没有使用单一集合或进入第二个集合@Lou Franco 抱歉,但我担心我不明白你的意思意思还是。我只将其命名为 calculateMileage,因为当时我想不出其他名称,但我意识到它同时包含 UI 和计算。 calculateMileage 是个好名字,但该方法不属于此类。此外,它不应该进行计算——Mileage 类应该进行计算——calculateMileage 应该只是向用户询问值并与他们一起调用 setXXX——然后 getXX 显示答案——关于计算值的一切都应该留在里程等级。关键不在于它是否有效(你可以通过很多不同的方式做到这一点),而是做出易于维护和重用的东西。【参考方案12】:

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

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

【讨论】:

我不知道该怎么说,但我想我明白你说的,但我不明白?如果那有意义的话?您是说集合/获取/构造函数使变量对程序隐藏,我理解,但不是重点?与我没有编写的代码相比,带有 setter/getter 的代码如何更好/重要?两者都完成了完全相同的事情,但一个人用更少的代码来完成它。不是在寻找一种“更少的代码行”的编码方式……只是想看看有什么区别吗? 变量只是一个数据存储,而getter和setter是访问或修改数据的抽象方式。由于 getter 和 setter 是抽象的方式,以后对数据结构的修改(例如,添加验证、计算一些值、使用数据库等)不需要更改 getter 和 setter 的抽象,从而保留所有代码使用相同的类,同时更改代码的内部工作方式。 所以...setter 和 getter 类似于制作变量的副本,允许修改该副本以免与原始值混淆? 没有。它们不是副本。就像在餐厅里一样:服务员端来食物,空盘子还给你,你不必知道(或关心)厨房里的谁在做什么来拿你的食物。因此,如果餐厅制作新鲜的意大利面或用微波炉加热它们,对您来说并不重要;服务员为您抽象出厨房里的食物(和工具)的访问权限。 我 90% 同意你的回答,除了“不分语言”部分。在某些编程语言中(我想到的是 python,但还有其他语言),您可以定义“属性”,即与变量具有相同语法形式的函数。因此,您可以在一段时间内使用真正的内部属性(可公开访问的字段),然后在您需要更精确的控制时将它们更改为属性。它不会改变外部代码。当这种结构内置在语言中时,我相信 gettter 和 setter 变得不那么必要了。【参考方案13】:

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

看看Wikipedia article这个主题。

【讨论】:

【参考方案14】:

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

Accessor methods

Information hiding/encapsulation

【讨论】:

以上是关于java中的setter和getter有啥意义? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

java Java中的Getters和Setter

javafx tableView 不显示数据,getter setter 有啥问题?

JAVA 中get()和get()的用法,和意义?

java第六章 封装

为啥 Swift 不像 Java 或 C# 那样对属性使用 getter 和 setter?

getter和setter方法