Java中getter的访问安全

Java中getter的访问安全,java,getter-setter,access-modifiers,Java,Getter Setter,Access Modifiers,因此,我们创建了一个带有一些私有类成员的简单类,并自动为其生成getter。但getter实际上返回了对该成员的引用,从而获得了对私有成员的完全访问权。可以吗? 下面是一个类的代码: public class User { private ArrayList<String> strings = new ArrayList(){ { add("String1"); add("String2"); } };

因此,我们创建了一个带有一些私有类成员的简单类,并自动为其生成getter。但getter实际上返回了对该成员的引用,从而获得了对私有成员的完全访问权。可以吗? 下面是一个类的代码:

public class User {

    private ArrayList<String> strings = new ArrayList(){ {
            add("String1");
            add("String2");
        } };

    public User() {
    }

    public ArrayList<String> getStrings() {
        return strings;
    }

    public void setStrings(ArrayList<String> strings) {
        this.strings = strings;
    }
}
和输出:

[String1,String2]

[String1、String2、String3]

我已将getter更改为此:

public ArrayList<String> getStrings() {
    return (ArrayList<String>)strings.clone();
}
public ArrayList getStrings(){
返回(ArrayList)字符串。克隆();
}

但问题仍然是,如果不是为了安全,那么吸气剂的作用是什么?编写它们的正确方法是什么?

不,这不好,因为它破坏了封装,因此类无法维护自己的不变量。构造函数也是如此

但问题不在于getter/setter,而在于自动生成它们的代码

长话短说:不要盲目使用自动生成的访问器,如果它们处理的是可变结构,那么就制作防御性拷贝(或不可变的等价物)


顺便说一句,我不会有一个带有
ArrayList
返回类型的getter,即使它只是一个副本。您返回的列表类型通常与客户无关,因此我的getter如下所示:

public List<String> getStrings() {
    return new ArrayList<>(strings);
}
public class Foo {
   private List<String> strings = new ArrayList() {{ add("bar");}};
   private Object veryLargeField; //the object stored here consumes a lot of memory

   public List<String> getStrings() {
     return strings;
   }
}
这三种解决方案之间存在细微差异,因此哪一种最好可能会有所不同。作为一般规则,我更喜欢返回不可变的结构,因为这清楚地表明对结构所做的更改不会被反映出来,即
user.getStrings().add(“X”)将失败并出现异常


您向我们展示的代码的另一个微妙问题是双大括号的初始化。想象这样一个类:

public List<String> getStrings() {
    return new ArrayList<>(strings);
}
public class Foo {
   private List<String> strings = new ArrayList() {{ add("bar");}};
   private Object veryLargeField; //the object stored here consumes a lot of memory

   public List<String> getStrings() {
     return strings;
   }
}
公共类Foo{
private List strings=new ArrayList(){{add(“bar”);};
private Object veryLargeField;//此处存储的对象占用大量内存
公共列表getStrings(){
返回字符串;
}
}
现在想象一下我们正在这样做:

private class Bar {
   private List<String> fooStrings;

   public Bar() {
     this.fooStrings = new Foo().getStrings();
   }
}
专用类栏{
私有列表字符串;
公共酒吧(){
this.fooStrings=new Foo().getStrings();
}
}

Bar
会消耗多少内存(或者使用确切的术语:retain)?事实证明,这相当多,因为使用双括号初始化所做的是创建一个匿名内部类,其中将包含对其外部类(
Foo
)的引用,因此当返回的列表可访问时,
Foo
的所有其他字段都不符合垃圾收集的条件。

在我看来,getter通常有两个用途:

  • 首先,他们应该注意实施细节
  • 其次,它们应提供一种易于扩展的方法(例如验证或仪器)
如果您的示例违反了这些原则,则取决于上下文:

  • 如果您的类应该拥有字符串,那么可能每个人都应该与容器对象交互以修改列表,而不是列表本身。要公开集合(例如,在需要集合的方法中进行处理),可以使用例如Collections.unmodifiableList()。另一方面,如果类只拥有字符串列表,那么拥有列表不是实现细节
  • 使用getter而不是直接访问字段,可以轻松添加数据对话、跟踪工具和其他内容,而无需更改字段的所有使用位置
    没有错不,在大多数情况下这是不好的。您可能希望公开作用于
    列表
    的方法,而不是
    列表
    本身。您也不希望直接使用setter中的
    列表
    。如果您计划在主函数中向列表中添加元素,则可以这样做。如果你克隆,在你调用setter之前,这些更改不会被记住。正如我看到的,你在使用不变性。我建议你读一读关于创造软或强不可变对象的主题。复印很贵。几乎总是最好返回一个不可变的视图,然后消费者可以在需要时复制它。@BoristSpider它的价格取决于很多因素。返回不可变视图对getter有效,但setter和构造函数必须创建副本。在getters中,是否创建副本或视图取决于您。重要的一课是,你不应该仅仅因为你认为它会很昂贵就停止做正确的事情。好吧,一个副本必须至少是
    O(n)
    ,而创建一个视图(至少使用JDK类)是
    O(1)
    。同意构造函数,这就是为什么我说返回而不是消耗。我的论点是,您应该始终返回一个不可变的视图,由使用者决定是否复制它。@BoristheSpider如果您的列表很小怎么办?如果您的getter只被调用了几次呢?如果JIT编译器完全优化了复制,会怎么样?如果你想支持不可变视图,一个更好的论点是即使是愚蠢的客户端也会知道这只是一个视图,因为修改尝试会因异常而失败,而不仅仅是默默地进行。@user2979453看一看。
    private class Bar {
       private List<String> fooStrings;
    
       public Bar() {
         this.fooStrings = new Foo().getStrings();
       }
    }