像Java中的对象结构

完全违背Java创build类似结构对象的方式吗?

class SomeData1 { public int x; public int y; } 

我可以看到一个访问器和mutators更类似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 * dx) / dy; } 

这并不方便。

 // a function in a class public int f(SomeData2 d) { return (3 * d.getX()) / d.getY(); } 

这是一个普遍讨论的话题。 在对象中创build公共字段的缺点是您无法控制为其设置的值。 在许多使用相同代码的程序员的小组项目中,避免副作用是很重要的。 此外,有时最好还是返回一个字段对象的副本或者以某种方式进行转换等等。你可以在你的testing中嘲笑这样的方法。 如果你创build一个新的类,你可能看不到所有可能的操作。 这就像防御性编程 – 有一天,getter和setter可能会有所帮助,创build/使用它们并不花费太多。 所以他们有时是有用的。

实际上,大多数领域都有简单的获取者和设定者。 一个可能的解决scheme将如下所示:

 public property String foo; a->Foo = b->Foo; 

更新:在Java 7中添加属性支持的可能性极小,甚至可能永远不会。 像Groovy,Scala等其他JVM语言现在也支持这个function。 – Alex米勒

看起来很多Java人并不熟悉Sun Java编码指南,他们认为如果Java支持“结构”(当没有行为的时候),那么在类本质上是“结构”的时候使用公共实例variables是非常合适的。

人们倾向于认为getter和setter是Java的方式,就好像它们是Java的核心一样。 事实并非如此。 如果遵循Sun Java编码指南,在适当的情况下使用公共实例variables,实际上编写的代码比用不必要的getter和setter混淆它更好。

Java代码公约从1999年起依然没有改变。

10.1提供对实例和类variables的访问

不要让任何实例或类variables公开没有理由。 通常情况下,实例variables不需要被显式地设置或获取,这通常是方法调用的副作用。

适当的公共实例variables的一个例子就是类本质上是一个数据结构的情况,没有任何行为。 换句话说,如果你使用了一个结构而不是一个类(如果Java支持struct的话),那么使类的实例variables公开是合适的

http://www.oracle.com/technetwork/java/javase/documentation/codeconventions-137265.html#177

http://en.wikipedia.org/wiki/Plain_old_data_structure

http://docs.oracle.com/javase/1.3/docs/guide/collections/designfaq.html#28

真的使用常识。 如果你有这样的东西:

 public class ScreenCoord2D{ public int x; public int y; } 

那么把他们包装在获得者和制定者中就没什么意义了。 你永远不会以任何其他方式存储整个像素的x,y坐标。 吸气剂和吸附剂只会减慢你的速度。

另一方面,与:

 public class BankAccount{ public int balance; } 

您可能想要改变未来某个时刻的平衡计算方式。 这应该真的使用getters和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; } } 

调用试图写入这些字段的代码会得到一个编译时错误“field x is declared final; can not be assigned”。

然后客户端代码可以在您的post中描述的“短手”方便性

 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 * dx) / dy; } public static void main(String[] args) { DataTest dataTest = new DataTest(); } } 

回复:aku,izb,John Topley …

注意可变性问题…

忽略getters / setter似乎是明智的。 实际上在某些情况下可能会好的。 这里提出的模式的真正问题是可变性。

问题是一旦你传递一个包含非final,public字段的对象引用。 任何其他与该参考是自由修改这些领域。 您不再有任何控制该对象的状态。 (想想如果Strings是可变的,会发生什么。)

当这个对象是另一个对象的内部状态的重要组成部分时,它会变得很糟糕,你刚刚暴露了内部的实现。 为了防止这种情况,必须返回对象的副本。 这是有效的,但可能会造成大量的一次性使用副本的GC压力。

如果你有公共领域,考虑让这个类只读。 将这些字段作为参数添加到构造函数中,并标记最终的字段。 否则,请确保您没有暴露内部状态,如果您需要为返回值构造新的实例,请确保它不会被过度调用。

请参阅:Joshua Bloch的“ Effective Java ” – Item#13:赞成不变性。

PS:另外请记住,如果可能的话,所有的JVM都会优化掉getMethod,只产生一个字段读取指令。

顺便说一下,您作为示例给出的结构已经作为java.awt.Point存在于Java基类库中。 它有x和y作为公共领域, 自己检查一下 。

如果你知道自己在做什么,并且团队中的其他人都知道,那么拥有公共领域是可以的。 但是你不应该依赖它,因为它们可能会引起头疼,就像使用对象的开发人员的错误一样,就好像它们是堆栈分配结构一样(java对象总是作为引用而不是副本发送给方法)。

我已经在一些项目中尝试了这一点,理论上讲,getter和setter将语义上无意义的代码搞乱了代码,而其他语言似乎在基于约定的数据隐藏或责任划分方面(例如python)做得很好。

正如其他人已经指出,有两个问题,你遇到,他们不是真正可以解决的:

  • 几乎所有的Java世界中的自动化工具都依赖于getter / setter约定。 同样,正如别人所指出的,jsp标签,弹簧configuration,eclipse工具等等等等…对抗你的工具期望看到的是一个长时间的会话通过google试图find非标准的启动方式春豆。 真的不值得麻烦。
  • 一旦你有了数百个公共variables,你的优雅编码应用程序,你可能会发现至less有一种情况,他们是不够的,你绝对需要不变性,或者你需要触发一些事件,当variables设置,或者你想扔一个variables的exception,因为它将对象状态设置为令人不快的事情。 然后,在variables被直接引用的地方,用一些特殊的方法把你的代码混淆起来,在你的应用程序的1000个variables中有3个具有特殊的访问forms,那么你就会陷入困境。

这完全是在一个独立的私人项目中进行的。 一旦你把整个东西输出到一个可公开访问的库中,这些问题就会变得更大。

Java非常冗长,这是一个很有吸引力的事情。 不要这样做。

不要使用public领域

当你想要包装一个类的内部行为时,不要使用public字段。 以java.io.BufferedReader为例。 它有以下领域:

 private boolean skipLF = false; // If the next character is a line feed, skip it 

skipLF以所有读取方式读写。 如果在一个单独的线程中运行的外部类在读取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,这个类没有任何行为。 当类仅仅代表一个数据结构,并且没有, 并且永远不会有行为的时候,使用公有字段是可以接受的。 这可以写得更好:

 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(bx - ax, 2) + Math.pow(by - ay, 2)); 

清洁!

但要记住:不仅你的class级不存在行为,而且在将来也没有理由有行为。

如果Java的方式是面向对象的方式,那么是的,创build一个带有公共字段的类打破了信息隐藏的原则,即信息隐藏应该pipe理自己的内部状态。 (所以,我不只是向你吐口水,隐藏信息的一个好处是,一个类的内部工作隐藏在一个接口后面 – 比如你想改变你的结构类保存其中一个字段的机制,您可能需要返回并更改使用该类的任何类…)

您也无法利用对JavaBean命名兼容类的支持,如果您决定在使用expression式语言编写的JavaServer Page中使用该类,将会受到伤害。

JavaWorld文章为什么Getter和Setter方法是邪恶的文章也可能是你在思考什么时候不去实现访问器和增变器方法的兴趣。

如果你正在编写一个小的解决scheme,并想尽量减less涉及的代码量,Java方式可能不是正确的方式 – 我想这总是取决于你和你正试图解决的问题。

只要作者知道它们是结构(或数据穿梭)而不是对象,这种types的代码没有任何问题。 许多Java开发人员无法区分格式良好的对象(不只是java.lang.Object的子类,而是特定域中的真实对象)和菠萝。 人类,他们最终会写结构,当他们需要的东西,反之亦然。

使用公共字段访问的问题与使用new而不是工厂方法相同的问题 – 如果稍后改变主意,所有现有的调用者都将被打破。 因此,从API演进的angular度来看,咬住子弹并使用getter / setter通常是一个好主意。

另一种方式是我强烈地控制对类的访问,例如在用作内部数据结构的内部静态类中。 在这种情况下,使用字段访问可能会更清晰。

顺便说一下,在e-bartek的说法中,IMO很less不支持在Java 7中添加属性支持。

我经常在构build私有内部类来简化我的代码时使用这种模式,但我不build议在公共API中公开这样的对象。 一般来说,你可以越频繁地使你的公共API中的对象不变更好,也不可能以一种不可变的方式来构造你的类似于结构的对象。

另外,即使我将这个对象作为私有内部类来编写,我仍然会提供一个构造函数来简化代码来初始化对象。 必须有3行代码才能得到一个可用的对象,只是一团糟。

一个非常非常古老的问题,但让我再作一个短暂的贡献。 Java 8引入了lambdaexpression式和方法引用。 Lambdaexpression式可以是简单的方法引用,而不是声明“真实”的主体。 但是你不能把一个字段“转换”成一个方法引用。 从而

 stream.mapToInt(SomeData1::x) 

是不合法的,但是

 stream.mapToInt(SomeData2::getX) 

是。

如果你知道它总是一个简单的结构,而且你永远不想把行为附加到它,我不会看到这种危害。

这是面向对象devise的问题,而不是Java语言。 在类中隐藏数据types并仅公开属于类API一部分的方法通常是很好的做法。 如果公开内部数据types,将来永远不能更改它们。 如果你隐藏它们,你对用户的唯一义务就是方法的返回和参数types。

在这里,我创build一个程序,input5个不同的人的姓名和年龄,并进行selectsorting(年龄明智)。 我使用了一个类作为一个结构(如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+")"); } } 

上面的代码是无错误和testing…只需复制并粘贴到您的IDE和…你知道什么? 🙂

您可以使用公共字段和Java中的任何方法创build一个简单的类,但是它仍然是一个类,仍然按语法和内存分配方式处理,就像一个类一样。 Java没有办法真正地复制结构。

有时我使用这样的类,当我需要从一个方法返回多个值。 当然,这样的对象是短命的,能见度非常有限,所以应该没问题。

和大多数事情一样,有一般的规则,然后有特定的情况。 如果您正在执行一个封闭的捕获应用程序,以便您知道将如何使用给定的对象,那么您可以更自由地使用可见性和/或效率。 如果你正在开发一个将被他人无法控制的公开使用的类,那么就倾向于getter / setter模型。 和所有的事情一样,只要使用常识。 与公众进行首轮比赛往往是可以的,然后再把它们改为吸气/安装者。

面向方面的编程可以让你捕捉任务或提取并附加拦截逻辑,我build议这是解决问题的正确方法。 (它们应该是公共的,保护的还是包装保护的问题是正交的。)

因此,您从具有正确访问限定符的未拦截字段开始。 随着程序需求的增长,你可能会附加逻辑来validation,复制被返回的对象等。

getter / setter哲学在大量不需要的简单情况下会增加成本。

方面风格是否更清晰也有一定的定性。 我会发现很容易看到一个类中的variables并单独查看逻辑。 实际上,面向方面编程的存在理由是,许多担心是交叉的,并且将它们划分到类本身并不理想(日志logging就是一个例子 – 如果你想logging所有的Java需要你写一大堆getters并保持同步,但AspectJ允许你一行)。

IDE的问题是一个红鲱鱼。 打字不如打字,因为get / set引起的阅读和视觉污染。

注释看上去与面向方面的编程类似,但是它们要求您通过附加注释详尽地枚举切入点,而不像AspectJ中的简明通配符类切入点规范。

我希望AspectJ的意识可以防止人们过早地使用dynamic语言。