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

Sou*_*lly 30 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Testmileagetest.java:

package gasMileage;

public class Testmileagetest 
{

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

}
Run Code Online (Sandbox Code Playgroud)

再次感谢!

Mic*_*sen 27

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

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

  • 我90%同意你的答案,除了"不论语言"部分.在某些编程语言中(我考虑到了python,但还有其他编程语言),您可以定义"属性",即具有与变量相同的语法形式的函数.因此,您可以使用实际内部属性(可公开访问的字段)一段时间,然后在需要更精确控制时更改属性.它不会改变外部代码的东西.当这样的结构用一种语言构建时,我相信gettter和setter变得不那么必要了. (5认同)
  • 不,他们不是副本.就像在餐馆里一样:服务员带来食物并返回空盘子,你不必知道(或关心)厨房里的人在做什么来获取你的食物.因此,如果餐厅制作新鲜的意大利面或在微波炉中加热它们并不重要; 服务员为您抽取厨房食物(和工具)的访问权限. (3认同)

Dav*_*vis 17

封装

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

Person bob = new Person();
Colour hair = bob.getHairColour();
hair.setRed( 255 );
Run Code Online (Sandbox Code Playgroud)

还有这个:

Person bob = new Person();
Colour hair = bob.hairColour;
hair.red = 255;
Run Code Online (Sandbox Code Playgroud)

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

代替:

Person bob = new Person();
bob.setHairColour( Colour.RED );
Run Code Online (Sandbox Code Playgroud)

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

这两种情况的区别在于:

  • 在第一种情况下,鲍勃无法控制他的头发会变成什么颜色.非常适合喜欢红头发的发型师,对于那些鄙视这种颜色的鲍勃来说并不是那么好.
  • 在第二种情况下,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 );
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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 );
  }
}
Run Code Online (Sandbox Code Playgroud)

虽然它提供了一定程度的抽象,但它是一层薄薄的面纱,对松耦合无效.

告诉,不要问

有关此方法的更多信息,请阅读Tell,Do not 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 );
   }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

问题:

  • 数据类型.type属性不能轻易地从String更改为整数(或其他类).
  • 隐含协议.这是耗时的抽象从特定类型(PNG,JPEG,TIFF,EPS一般的() ,IMAGE,).DOCUMENTSPREADSHEET
  • 介绍错误.更改隐含协议不会生成编译器错误,这可能导致错误.

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

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

这意味着将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() );
   }
}
Run Code Online (Sandbox Code Playgroud)

File类将隐含协议从特定类型(例如,JPEG)转换为泛型类型(例如,IMAGE)时,系统中的其他代码都不会中断.系统中的所有代码必须使用isValidType方法,它不给类型调用对象,但讲述File类来验证类型.

  • 为什么我找不到+100按钮?这个答案是现实的.通过一个不起眼的脚注,我将补充说,用于使用吸气剂和固定剂的豆子不幸地使它们看起来比它们应得的更合理,IIRC注释最近是首选. (2认同)

Col*_*inD 17

其他答案通常可以很好地了解使用getter和setter的一些原因,但我想给出一个有用的原因.

例如,让我们看一个文件(忽略FileJava中存在的类).这个File类有一个用于存储文件类型的字段(.pdf,.exe,.txt等)......我们将忽略其他所有内容.

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

public class File {
   // ...
   public String type;
   // ...
}
Run Code Online (Sandbox Code Playgroud)

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

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

你班上的任何客户都可以随心所欲地做到:

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

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

无法轻易更改内部表示:

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

Getters和Setters如何解决这个问题

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

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

public void setType(String type) {
   this.type = type;
}
Run Code Online (Sandbox Code Playgroud)

控制设置属性:

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

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
}
Run Code Online (Sandbox Code Playgroud)

能够轻松更改内部表示:

更改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;
   }
}
Run Code Online (Sandbox Code Playgroud)

由于班上的客户一直在打电话getType(),所以setType()从他们的角度来看没有任何改变.只更改了类的内部,而不是其他类正在使用的接口.


Lou*_*nco 5

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

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

而不是使用:

milesPerGallon = miles / gallons;
Run Code Online (Sandbox Code Playgroud)

在calculateMileage()中

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