题
它完全违背了Java创建struct like对象的方式吗?
class SomeData1 {
public int x;
public int y;
}
我可以看到一个带有访问器和修改器的类更像 Java。
class SomeData2 {
int getX();
void setX(int x);
int getY();
void setY(int y);
private int x;
private int y;
}
第一个示例中的类在符号上很方便。
// a function in a class
public int f(SomeData1 d) {
return (3 * d.x) / d.y;
}
这不太方便。
// a function in a class
public int f(SomeData2 d) {
return (3 * d.getX()) / d.getY();
}
解决方案
这是一个经常讨论的话题。在对象中创建公共字段的缺点是您无法控制为其设置的值。在有许多程序员使用相同代码的小组项目中,避免副作用非常重要。此外,有时最好返回字段对象的副本或以某种方式对其进行转换等。您可以在测试中模拟此类方法。如果您创建一个新类,您可能看不到所有可能的操作。这就像防御性编程 - 有一天 getter 和 setter 可能会有所帮助,并且创建/使用它们并不需要花费太多。所以它们有时很有用。
实际上,大多数字段都有简单的 getter 和 setter。一个可能的解决方案如下所示:
public property String foo;
a->Foo = b->Foo;
更新:Java 7 中添加属性支持的可能性极小,甚至永远不可能。其他 JVM 语言(例如 Groovy、Scala 等)现在都支持此功能。——亚历克斯·米勒
其他提示
看来,许多Java人不熟悉Sun Java编码指南,这些指南说,如果类实质上是“ strupt”,则使用公共实例变量非常适合,如果Java支持的“ struct”(当没有行为时)。
人们倾向于认为Getters和Setter是Java的方式,就好像它们是Java的核心一样。事实并非如此。如果您遵循Sun Java编码指南,请在适当情况下使用公共实例变量,实际上,您编写的代码比用不必要的Getters和setters杂乱无章。
1999 年的 Java 代码约定 并且仍然没有改变。
10.1 提供对实例和类变量的访问
没有充分的理由,不要将任何实例或类变量公开。通常,实例变量不需要显式设置或获取——这通常是方法调用的副作用。
适当的公共实例变量的一个例子是类本质上是一种数据结构,没有行为。 换句话说,如果您使用结构体而不是类(如果 Java 支持结构体),那么将类的实例变量设为公共是合适的.
http://www.oracle.com/technetwork/java/javase/documentation/codeconventions-137265.html#177
http://en.wikipedia.org/wiki/Plain_old_data_struct
http://docs.oracle.com/javase/1.3/docs/guide/collections/designfaq.html#28
真正运用常识。如果你有类似的东西:
public class ScreenCoord2D{
public int x;
public int y;
}
那么将它们包裹在 getter 和 setter 中就没有什么意义了。您永远不会以任何其他方式存储整个像素中的 x, y 坐标。Getters 和 Setters 只会减慢你的速度。
另一方面,与:
public class BankAccount{
public int balance;
}
您可能希望在将来的某个时候更改余额的计算方式。这确实应该使用 getter 和 setter。
知道总是更好 为什么 你正在应用良好的实践,这样你就知道什么时候可以违反规则。
为了解决可变性问题,您可以将 x 和 y 声明为最终的。例如:
class Data {
public final int x;
public final int y;
public Data( int x, int y){
this.x = x;
this.y = y;
}
}
调用尝试写入这些字段的代码将收到编译时错误“字段 x 被声明为最终的;无法分配”。
然后,客户端代码可以拥有您在帖子中描述的“速记”便利
public class DataTest {
public DataTest() {
Data data1 = new Data(1, 5);
Data data2 = new Data(2, 4);
System.out.println(f(data1));
System.out.println(f(data2));
}
public int f(Data d) {
return (3 * d.x) / d.y;
}
public static void main(String[] args) {
DataTest dataTest = new DataTest();
}
}
不使用 public
领域
不要使用 public
当您确实想要包装类的内部行为时,可以使用字段。拿 java.io.BufferedReader
例如。它有以下字段:
private boolean skipLF = false; // If the next character is a line feed, skip it
skipLF
在所有读取方法中进行读取和写入。如果在单独线程中运行的外部类恶意修改了状态怎么办? skipLF
在阅读过程中? BufferedReader
肯定会失控。
使用 public
领域
拿着它 Point
类例如:
class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return this.x;
}
public double getY() {
return this.y;
}
public void setX(double x) {
this.x = x;
}
public void setY(double y) {
this.y = y;
}
}
这会使计算两点之间的距离写起来非常痛苦。
Point a = new Point(5.0, 4.0);
Point b = new Point(4.0, 9.0);
double distance = Math.sqrt(Math.pow(b.getX() - a.getX(), 2) + Math.pow(b.getY() - a.getY(), 2));
除了简单的 getter 和 setter 之外,该类没有任何行为。当类仅表示数据结构且不具有以下属性时,使用公共字段是可以接受的: 和 永远不会有行为(薄 getters 和 setters 是 不是 此处考虑的行为)。可以这样写更好:
class Point {
public double x;
public double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
}
Point a = new Point(5.0, 4.0);
Point b = new Point(4.0, 9.0);
double distance = Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2));
干净的!
但要记住:你的班级不仅必须没有行为,而且还应该有 不 未来也有行为的理由。
(这正是 这个答案 描述。去引用 “Java 编程语言的代码约定:10.编程实践》:
适当的公共实例变量的一个例子是类本质上是一种数据结构,没有行为。换句话说,如果您使用
struct
而不是类(如果 Java 支持struct
),那么将类的实例变量公开是合适的。
所以官方文档也接受这种做法。)
另外,如果您特别确定上述成员 Point
类应该是不可变的,那么你可以添加 final
关键字来强制执行:
public final double x;
public final double y;
顺便说一句,您作为示例给出的结构已经存在于 Java 基类库中,如下所示 java.awt.Point
. 。它有 x 和 y 作为公共字段, 自己检查一下.
如果您知道自己在做什么,并且团队中的其他人也知道,那么拥有公共字段就可以了。但您不应该依赖它,因为它们可能会导致令人头疼的问题,例如与开发人员使用对象(就好像它们是堆栈分配结构一样)相关的错误(Java 对象始终作为引用而不是作为副本发送到方法)。
关于:阿库、izb、约翰·托普利……
注意可变性问题...
省略 getter/setter 似乎是明智的。实际上在某些情况下可能没问题。这里显示的建议模式的真正问题是可变性。
问题是一旦您传递出包含非最终公共字段的对象引用。具有该引用的任何其他内容都可以自由修改这些字段。您不再能够控制该对象的状态。(想想如果字符串是可变的会发生什么。)
当该对象是另一个对象内部状态的重要组成部分时,情况会变得很糟糕,而您刚刚暴露了内部实现。为了防止这种情况,必须返回该对象的副本。这可行,但可能会因创建大量一次性副本而导致巨大的 GC 压力。
如果您有公共字段,请考虑将类设置为只读。将字段作为参数添加到构造函数中,并将字段标记为 Final。否则,请确保您没有公开内部状态,并且如果您需要为返回值构造新实例,请确保不会过度调用它。
看:”有效的Java” 作者:Joshua Bloch -- 第 13 条:支持不变性。
附:另请记住,如果可能的话,现在所有 JVM 都会优化 getMethod,从而只产生单个字段读取指令。
我在几个项目中尝试过这一点,其理论是 getter 和 setter 会用语义上毫无意义的废话弄乱代码,而其他语言似乎在基于约定的数据隐藏或职责划分方面做得很好(例如Python)。
正如其他人上面指出的,您遇到了两个问题,并且它们实际上无法修复:
- Java 世界中几乎所有自动化工具都依赖于 getter/setter 约定。正如其他人指出的那样,jsp 标签、spring 配置、eclipse 工具等也是如此。ETC...与你的工具期望看到的内容作斗争是通过谷歌进行长时间会话试图找到启动 spring beans 的非标准方法的秘诀。实在不值得这么麻烦。
- 一旦您拥有包含数百个公共变量的优雅编码的应用程序,您可能会发现至少一种它们不足的情况 - 您绝对需要不变性,或者您需要在变量设置时触发某些事件,或者您想抛出变量更改的异常,因为它将对象状态设置为令人不快的状态。然后,您就陷入了两种令人羡慕的选择:在直接引用变量的地方使用某种特殊方法使代码变得混乱,为应用程序中 1000 个变量中的 3 个采用某种特殊访问形式。
这是完全在独立的私人项目中工作的最佳情况。一旦将整个内容导出到可公开访问的库,这些问题就会变得更大。
Java 非常冗长,这是一件很诱人的事情。不要这样做。
如果 Java 方式是 OO 方式,那么是的,创建具有公共字段的类违反了信息隐藏的原则,即对象应该管理其自己的内部状态。(因此,我不只是向您滔滔不绝地讲行话,信息隐藏的一个好处是类的内部工作隐藏在接口后面 - 假设您想更改结构类保存其字段之一的机制,您可能需要返回并更改使用该类的任何类...)
您也无法利用对 JavaBean 命名兼容类的支持,如果您决定在使用表达式语言编写的 JavaServer Page 中使用该类,这将会受到损害。
JavaWorld 文章 为什么 Getter 和 Setter 方法是邪恶的 您可能还会对本文感兴趣,思考何时不实现访问器和修改器方法。
如果您正在编写一个小型解决方案并希望最大限度地减少所涉及的代码量,那么 Java 方式可能不是正确的方式 - 我想这始终取决于您和您要解决的问题。
这种类型的代码没有什么问题,只要作者 知道 它们是结构(或数据穿梭)而不是对象。许多 Java 开发人员无法区分格式良好的对象(不仅仅是 java.lang.Object 的子类,而是 java.lang.Object 的子类)之间的区别。 真的 特定域中的对象)和菠萝。因此,当他们需要对象时,他们最终会编写结构,反之亦然。
使用公共字段访问的问题与使用 new 而不是工厂方法的问题相同 - 如果您稍后改变主意,所有现有的调用者都会被破坏。因此,从 API 演变的角度来看,硬着头皮使用 getter/setter 通常是个好主意。
我采用另一种方式的地方是当您强烈控制对类的访问时,例如在用作内部数据结构的内部静态类中。在这种情况下,使用现场访问可能会更清晰。
顺便说一句,根据 e-bartek 的说法,在我看来,Java 7 中添加属性支持的可能性极小。
在构建私有内部类来简化代码时,我经常使用这种模式,但我不建议在公共 API 中公开此类对象。一般来说,使公共 API 中的对象不可变的频率越高越好,并且不可能以不可变的方式构造“类似结构”的对象。
顺便说一句,即使我将此对象编写为私有内部类,我仍然会提供一个构造函数来简化初始化该对象的代码。当一个人能做的时候,必须有 3 行代码才能获得一个可用的对象,这太混乱了。
这是一个非常非常老的问题,但让我再做一个简短的贡献。Java 8 引入了 lambda 表达式和方法引用。Lambda 表达式可以是简单的方法引用,并且不声明“真实”主体。但是您不能将字段“转换”为方法引用。因此
stream.mapToInt(SomeData1::x)
不合法,但是
stream.mapToInt(SomeData2::getX)
是。
如果您知道它始终是一个简单的结构并且您永远不会想要向其附加行为,那么我看不出有什么坏处。
这是一个关于面向对象设计的问题,而不是Java语言的问题。通常最好的做法是隐藏类中的数据类型并仅公开属于类 API 的方法。如果公开内部数据类型,则将来永远无法更改它们。如果隐藏它们,您对用户的唯一义务是方法的返回和参数类型。
在这里,我创建一个程序来输入 5 个不同人的姓名和年龄,并执行选择排序(年龄方面)。我使用一个充当结构的类(如 C 编程语言)和一个主类来执行完整的操作。下面我提供代码......
import java.io.*;
class NameList {
String name;
int age;
}
class StructNameAge {
public static void main(String [] args) throws IOException {
NameList nl[]=new NameList[5]; // Create new radix of the structure NameList into 'nl' object
NameList temp=new NameList(); // Create a temporary object of the structure
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
/* Enter data into each radix of 'nl' object */
for(int i=0; i<5; i++) {
nl[i]=new NameList(); // Assign the structure into each radix
System.out.print("Name: ");
nl[i].name=br.readLine();
System.out.print("Age: ");
nl[i].age=Integer.parseInt(br.readLine());
System.out.println();
}
/* Perform the sort (Selection Sort Method) */
for(int i=0; i<4; i++) {
for(int j=i+1; j<5; j++) {
if(nl[i].age>nl[j].age) {
temp=nl[i];
nl[i]=nl[j];
nl[j]=temp;
}
}
}
/* Print each radix stored in 'nl' object */
for(int i=0; i<5; i++)
System.out.println(nl[i].name+" ("+nl[i].age+")");
}
}
上面的代码没有错误并且经过测试...只需将其复制并粘贴到您的 IDE 中即可...你知道还有什么???:)
您可以在 Java 中创建一个具有公共字段且没有方法的简单类,但它仍然是一个类,并且仍然像类一样在语法和内存分配方面进行处理。在 Java 中没有办法真正重现结构。
有时,当我需要从方法返回多个值时,我会使用此类。当然,这样的物体寿命很短,可见性也非常有限,所以应该没问题。
与大多数事情一样,有一般规则,也有具体情况。如果您正在开发一个封闭的、捕获的应用程序,以便您知道如何使用给定的对象,那么您可以行使更多的自由来支持可见性和/或效率。如果您正在开发一个将由您无法控制的其他人公开使用的类,那么请倾向于 getter/setter 模型。与所有事情一样,只需运用常识即可。通常可以先与 public 进行第一轮,然后将其更改为 getter/setter。
面向方面的编程允许您捕获分配或获取并向它们附加拦截逻辑,我认为这是解决问题的正确方法。(它们是否应该公开、受保护或受包保护的问题是正交的。)
因此,您可以从具有正确访问限定符的未拦截字段开始。随着程序需求的增长,您可以附加逻辑来验证、复制返回的对象等。
getter/setter 哲学给大量不需要它们的简单情况带来了成本。
方面风格是否干净在某种程度上是定性的。我发现只查看类中的变量并单独查看逻辑很容易。事实上,面向 Apect 的编程存在的理由是,许多关注点是交叉的,并且将它们划分在类主体本身中并不理想(日志记录就是一个例子——如果你想记录所有 Java 想要你做的事情)编写一大堆 getter 并保持它们同步,但 AspectJ 允许你简单说一下)。
IDE 的问题是一个转移注意力的问题。与其说是打字,不如说是由 get/sets 引起的阅读和视觉污染。
乍一看,注释与面向方面的编程类似,但它们要求您通过附加注释来详尽地枚举切入点,这与 AspectJ 中简洁的类似通配符的切入点规范相反。
我希望对 AspectJ 的认识能够防止人们过早地采用动态语言。