English 中文(简体)
Java中类似结构的对象
原标题:
  • 时间:2008-08-31 09:17:01
  •  标签:

创建类似结构的对象是否完全违反Java方法?

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等,现在都支持此功能亚历克斯·米勒

问题回答

It appears that many Java people are not familiar with the Sun Java Coding Guidelines which say it is quite appropriate to use public instance variable when the class is essentially a "Struct", if Java supported "struct" (when there is no behavior).

People tend to think getters and setters are the Java way, as if they are at the heart of Java. This is not so. If you follow the Sun Java Coding Guidelines, using public instance variables in appropriate situations, you are actually writing better code than cluttering it with needless getters and 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_structure

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坐标存储在整个像素中。接球手和二传手只会让你慢下来。

另一方面,具有:

public class BankAccount{
    public int balance;
}

您可能希望在将来的某个时候更改余额的计算方式。这应该真正使用getter和setter。

最好知道为什么你正在应用良好的实践,这样你就知道什么时候可以改变规则。

为了解决可变性问题,您可以将x和y声明为final。例如:

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

Do not use public fields

当您真正想要包装类的内部行为时,不要使用公共字段。采取java.io.BufferedReader。它有以下字段:

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

<code>skipLF</code>在所有读取方法中进行读取和写入。如果在单独线程中运行的外部类在读取过程中恶意修改了skipLF的状态,该怎么办BufferedReader肯定会失控。

Do use public fields

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之外,该类没有任何其他行为。当类只表示一个数据结构,并且没有,<em>和</em>永远不会有行为时,使用公共字段是可以接受的(在这里,瘦getter和setter是<em>而不是<em>考虑的行为)。这样可以写得更好:

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。编程实践“

适当的公共实例变量的一个例子是,类本质上是一个没有行为的数据结构。换言之,如果使用结构而不是类(如果Java支持struct),那么将类的实例变量公开是合适的。

因此,官方文件也接受这种做法。)


此外,如果您特别确定上面Point类的成员应该是不可变的,那么您可以添加final关键字来强制执行它:

public final double x;
public final double y;

顺便说一句,您作为示例给出的结构已经以Java.awt.Point的形式存在于Java基类库中。它有x和y作为公共字段,自己看看

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

回复:aku、izb、John Topley。。。

注意可变性问题。。。

省略getter/setter似乎是明智的。事实上,在某些情况下可能还可以。这里显示的所提出的模式的真正问题是可变性。

问题是,一旦您将包含非最终公共字段的对象引用传递出去。任何具有该引用的其他内容都可以自由修改这些字段。您不再能够控制该对象的状态。(想想如果字符串是可变的会发生什么。)

当该对象是另一个对象的内部状态的重要部分时,情况就会变得糟糕,因为您刚刚公开了内部实现。为了防止这种情况发生,必须返回对象的副本。这是可行的,但可能会导致大量的一次性拷贝产生巨大的GC压力。

如果您有公共字段,请考虑将类设为只读。将字段作为参数添加到构造函数中,并将字段标记为final。否则,请确保没有公开内部状态,如果需要为返回值构造新实例,请确保不会过度调用它。

请参阅:“有效的Java”,作者Joshua Bloch——第13项:有利的不可变性。

PS:还要记住,如果可能的话,现在所有的JVM都会优化getMethod,只生成一条字段读取指令。

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

正如上面其他人所指出的,你会遇到两个问题,但它们并不能真正解决:

  • Just about any automated tool in the java world relies on the getter/setter convention. Ditto for, as noted by others, jsp tags, spring configuration, eclipse tools, etc. etc... Fighting against what your tools expect to see is a recipe for long sessions trolling through google trying to find that non-standard way of initiating spring beans. Really not worth the trouble.
  • Once you have your elegantly coded application with hundreds of public variables you will likely find at least one situation where they re insufficient- where you absolutely need immutability, or you need to trigger some event when the variable gets set, or you want to throw an exception on a variable change because it sets an object state to something unpleasant. You re then stuck with the unenviable choices between cluttering up your code with some special method everywhere the variable is directly referenced, having some special access form for 3 out of the 1000 variables in your application.

这是完全在一个独立的私人项目中工作的最佳情况。一旦您将整个内容导出到一个可公开访问的库中,这些问题将变得更加严重。

Java非常冗长,这是一件很有诱惑力的事情。不要这么做。

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

您也不能利用对JavaBean命名兼容类的支持,如果您决定在使用表达式语言编写的JavaServer页面中使用该类,这将造成伤害。

JavaWorld文章为什么Getter和Setter方法是邪恶的文章也可能会让您感兴趣,思考何时不实现访问器和赋值器方法。

如果您正在编写一个小的解决方案,并且希望最大限度地减少所涉及的代码量,那么Java方式可能不是正确的方式——我想这总是取决于您和您试图解决的问题。

这种类型的代码没有错,前提是作者知道它们是结构(或数据穿梭器)而不是对象。许多Java开发人员无法区分格式良好的对象(不仅是Java.lang.object的子类,而且是特定域中的true对象)和菠萝。呃,当他们需要对象时,他们最终会编写结构,反之亦然。

这是一个非常古老的问题,但让我再做一个简短的贡献。Java 8引入了lambda表达式和方法引用。Lambda表达式可以是简单的方法引用,而不声明“true”主体。但是您不能将字段“转换”为方法引用。因此

stream.mapToInt(SomeData1::x)

不合法,但是

stream.mapToInt(SomeData2::getX)

使用公共字段访问的问题与使用new方法而不是factory方法的问题相同——如果您稍后改变主意,所有现有的调用方都会崩溃。因此,从API进化的角度来看,咬紧牙关使用getter/setter通常是个好主意。

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

顺便说一句,根据e-bartek的说法,IMO极不可能在Java7中添加属性支持。

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

顺便说一句,即使我把这个对象写为一个私有的内部类,我仍然会提供一个构造函数来简化初始化对象的代码。必须有3行代码才能获得一个可用的对象,这简直是一团糟。

如果你知道它总是一个简单的结构,并且你永远不想把行为附加到它上,我看不出有什么害处。

这是一个关于面向对象设计的问题,而不是Java语言。通常,在类中隐藏数据类型并只公开类API中的方法是很好的做法。如果您公开内部数据类型,那么将来永远无法更改它们。如果您隐藏它们,那么您对用户的唯一义务就是方法的返回和参数类型。

有时,当我需要从一个方法返回多个值时,我会使用这样的类。当然,这样的物体寿命很短,能见度也很有限,所以应该没问题。

您可以在Java中创建一个带有公共字段而没有方法的简单类,但它仍然是一个类,并且仍然像类一样在语法和内存分配方面进行处理。在Java中没有办法真正再现结构。

As with most things, there s the general rule and then there are specific circumstances. If you are doing a closed, captured application so that you know how a given object is going to be used, then you can exercise more freedom to favor visibility and/or efficiency. If you re developing a class which is going to be used publicly by others beyond your control, then lean towards the getter/setter model. As with all things, just use common sense. It s often ok to do an initial round with publics and then change them to getter/setters later.

面向方面编程可以捕获赋值或获取,并将拦截逻辑附加到它们上,我认为这是解决问题的正确方法。(它们应该是公共的、受保护的还是受包保护的问题是正交的。)

因此,您可以从具有正确访问限定符的未被感知的字段开始。随着程序需求的增长,您可能会附加逻辑进行验证,制作返回对象的副本,等等。

getter/setter哲学将成本强加给大量不需要它们的简单案例。

方面风格是否更干净在一定程度上是定性的。我会发现只查看类中的变量并单独查看逻辑很容易。事实上,面向Apect的编程存在的理由是,许多问题都是交叉的,在类主体中划分它们本身并不理想(日志就是一个例子——如果你想记录所有的getter,Java希望你写一大堆getter并保持它们的同步,但AspectJ允许你使用一行代码)。

IDE的问题只是转移注意力。与其说是打字,不如说是由get/set引起的阅读和视觉污染。

乍一看,注释似乎类似于面向方面的编程,但它们要求您通过附加注释来详尽地枚举切入点,而不是在AspectJ中使用简洁的通配符式切入点规范。

我希望对AspectJ的认识能防止人们过早地适应动态语言。

在这里,我创建了一个程序来输入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中。。。你知道吗???:)





相关问题
热门标签