查看您自动生成的代码

在这篇文章中,我将根据我的经验回顾一些错误,这些错误在我们使用Java开发代码时,尤其是使用IDE的工具来自动生成代码时,可能会经常发生。

自动生成的代码非常棒,它可以节省很多击键次数,并有助于专注于域问题,而不是样板代码的细微差别。 但是,有时我们不能很方便地反映出IDE为我们生成的代码,并且留下了一些应修复的小缺陷。 在这篇文章中,我将列出其中一些。

Public scope

在默认情况下,IDE将所有类视为公共类。 因此,如果我们告诉IntelliJ创建一个新类,将发生以下情况:

public class MyClass {
}

我认为,类默认应具有包作用域,即:

class MyClass {
}

这样,它们在所属包的外部是不可见的。 如果在某个时候需要更改类的范围,则需要考虑原因,并做出方便的设计决策。 如果默认情况下该类为public,则我们将立即使用它,从而提高代码的耦合级别。

避免此问题的最佳方法是在我们的IDE中编辑模板。 例如,在IntelliJ IDEA中,此选项位于“首选项”>“编辑器”>“文件和代码模板”>“模板”>“类”中。

默认情况下,使用包作用域时的最佳实践之一是将其创建为公共接口,但不公开其实现。 这样,接口的客户端对所使用的实现一无所知,因为他们甚至无法访问它,因此喜欢依赖注入之类的做法。

作为此公共范围首选项的另一个示例,默认情况下,IntelliJ将常量创建为public:

public static final String CONSTANT = "value";

在大多数情况下,常量仅由拥有它们的类使用。 请注意这一点,或者也更改模板。

Immutable classes

这是一个与自动生成的代码没有直接关联的细节,但是我们跳过了99%的时间。 通常,当我们要将一个类设计为不可变的时,结果将是这样的:

public class MyImmutableClass {

    private final int id;
    private final String name;

    public MyImmutableClass(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

专用字段和最终字段,没有设置方法和构造方法的初始化。 好的,这很好,但是我们缺少了一些东西。 让我们看看如果执行此操作会发生什么:

public class MyImmutableDerivedClass extends MyImmutableClass{

    public String address;

    public MyImmutableDerivedClass(int id, 
                                   String name, 
                                   String address) {
        super(id, name);
        this.address = address;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

第二个类的实例,该实例继承自MyImmutableClass可以在没有客户注意的情况下充当其替代品,即:

MyImmutableClass myImmutableClass
                = new MyImmutableDerivedClass(1, "name", "address");

这不是理想的,因为此派生类不是不可变的!

解决方案很简单,让我们将不可变的类定型为final:

public final class MyImmutableClass {
    //...
}

现在将无法创建派生类。

Getters and setters

创建一个包含所有字段的类,并在自动生成的代码狂潮中添加所有字段的构造函数,getter和setter的情况很常见:

我们真的需要这样做吗?

public class MyClass {

    private Collaborator1 collaborator1;
    private Collaborator2 collaborator2;

    public MyClass(Collaborator1 collaborator1,
                   Collaborator2 collaborator2) {
        this.collaborator1 = collaborator1;
        this.collaborator2 = collaborator2;
    }

    public Collaborator1 getCollaborator1() {
        return collaborator1;
    }

    public void setCollaborator1(Collaborator1 collaborator1) {
        this.collaborator1 = collaborator1;
    }

    public Collaborator2 getCollaborator2() {
        return collaborator2;
    }

    public void setCollaborator2(Collaborator2 collaborator2) {
        this.collaborator2 = collaborator2;
    }
}

一个类应公开最少数量的内部细节。 因此,在第一部分中说过,一个类在需要公开之前应该具有包范围,现在我要说一个类在需要之前不应具有getter或setter。 当发生这种情况时,请考虑这是否是最佳决定,或者您的设计中是否存在不合适的内容。

Equals method

In general, IDEs expose an option to generate the methods equals and hashCode together. This makes sense, because we usually need a right and consistent implementation for both methods (I won't get into many details here, assuming we all know the equals and hashCode contract).

我对IDE生成的实现没有很多异议hashCode,它们通常是最佳的,即使我们可以进一步优化它们。 有一个小问题等于虽然实现:

public class MyClass {

    private final int id;
    private final String name;

    //Constructor, equals, etc

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;

        if (o == null ||
            getClass() != o.getClass())
                return false;

        MyClass myClass = (MyClass) o;

        if (id != myClass.id)
            return false;

        if (name != null ?
            !name.equals(myClass.name) :
            myClass.name != null)
                return false;

        return true;
    }
}    

Everything seems correct here, but something serious is happening, this class is violating the Liskov Substitution Principle. This principle is part of the SOLID principles, and in few words it says that "a derived class can act as its base class without clients noticing it". This is a bit rough definition (this principle would require an entire post), so I think I'll illustrate the problem better with an example, so that we can see why our equals method is broken.

让我们创建一个派生类:

public class MyDerivedClass extends MyClass {
    private final String address;

    public MyDerivedClass(int id, String name, String address) {
        super(id, name);
        this.address = address;
    }

    public String getAddress() {
        return address;
    }

    // equals and hashCode
}

我省略等于和hashCode实现,因为它们对于示例而言不是必需的。

public static void main(String[] args) {
    MyClass object1 = new MyClass(1, "name");
    MyDerivedClass object2 = new MyDerivedClass(1, "name", "address");

    System.out.println(areEquals(object1, object2));
}

public static boolean areEquals(MyClass object1, MyClass object2) {
    return object1.equals(object2);
}

方法等于收到两个实例我的课。 根据它们所包含的信息,这两个实例是相同的,因此我们应该期望等于返回真正。 但这不是事实,因为自动生成等于方法是这样做的:

if (o == null ||
    getClass() != o.getClass())
        return false;

getClass退货我的课对于第一个对象,MyDerivedClass第二,所以我们的方法等于退货假。

我们可以讨论这样做的便利性,因为两者都是不同类的实例。 但是,当两个实例中所有字段的值完全相同时,这真的有意义吗? 实际上,等于可能:

public static boolean areEquals(MyClass object1, MyClass object2) {
    return object1.getId() == object2.getId() &&
            object1.getName().equals(object2.getName());
}

在这种情况下,结果是真正。

这就是Liskov替代原理。 该原理的含义之一是,如果我们覆盖一个方法,它将是添加一些行为而不删除基类正在做的任何事情。 彻底打破该原理的是重写一种方法以使其为空的做法(我想我们很多人在职业生涯中的某个时候都这样做了)。

因此,我们需要一种更好的方法实现等于。 这将是这样的:

@Override
public boolean equals(Object o) {
    if (this == o)
        return true;

    if (!(o instanceof MyClass))
        return false;

    MyClass myClass = (MyClass) o;

    if (id != myClass.id)
        return false;

    if (name != null ?
            !name.equals(myClass.name) :
            myClass.name != null)
                return false;

    return true;
}

Instead of interrogating the objects to know their class using getClass, what we do is asking the object passed by parameter to equals if it's an instance of the class where equals lives through the operator instanceof. This operator returns true for instances of that class and its derived classes too. And now, finally, our areEquals method works the way we want. All this (and more) is explained perfectly in the seminal book Effective Java, book that every Java developer should have on his library.

事实是IntelliJ提供了一种自动生成此版本的等于, but we must be careful with the options of the wizard, because it's unchecked by default, and we must check it ourselves. It is "Accept subclasses as parameters to 等于() method":

IntelliJ

对话框本身提到默认情况下,实现会中断等于合同。 看来实例实现可能与某些框架不兼容。 在这种情况下,我们应该切换到第一个版本,但要始终意识到后果。 因此,首先让我们使用正确的版本,并在必要时切换到计划B :)

from: https://dev.to//raulavila/review-your-auto-generated-code

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值