Java:有没有一种方法可以强制实现私有方法?

6

我有5或6个类,我希望它们在内部遵循相同的基本结构。实际上,这些类应该遵循的大多数规则仅供函数本身使用,因此我真的希望这些方法是私有的。

有没有办法实现这一点?我知道接口非常适合,但它们不会使用私有成员,并且不允许您重新定义实现方法中的作用域。有没有解决这个问题的方法?

谢谢


7
你试图在这里做的事情没有任何意义。私有方法的作用是它们不是类的公共结构的一部分 - 从定义上来讲,你的类应该无论私有方法和字段的结构如何,都会表现出相同的行为。如果你想组件化你的实现,你可以使用抽象方法或将你的类分解成组件,正如其他人在这里建议的那样。你能澄清一下为什么你想这样做吗?如果你只是想简化代码,让所有的代码都呈现出类似的结构,你必须通过约定来完成,语言本身不能强制实施这种变化。 - Steve B.
是的,我认为你说得很对。我想让它强制执行镜像结构。可惜这不能通过语言结构来实现。 - Matthew Stopa
你做错了。封装的全部意义在于你不关心事物是如何实现的,只关心它们的外部行为。语言特性符合良好的工程实践(大多数情况下)。 - Kevin Peterson
1
我必须尊重地不同意那个观点。我在另一条评论中写道,我的目的是在自己的项目中执行编码标准。因此,让Java为我完成这项工作将会非常有帮助,并且通常是一个好的做法(我认为)。 - Matthew Stopa
13个回答

17

我认为最接近的方法是使用一个带有抽象受保护方法的抽象类:

abstract class A {
    protected abstract void foo();
}

class B extends A {
    protected void foo() {}
}

为了定义共有的逻辑,您可以从父类的私有方法中调用受保护的方法:

abstract class A {
    private void bar() {
        // do common stuff
        foo();
    }
    protected abstract void foo();
}

这样,您可以允许子类使用特定的行为填充私有的公共模板方法


是的,那很有趣。不幸的是,我没有任何通用逻辑,我只想强制所有对象使用相同的内部方法。很遗憾你不能强制将它们设为私有。 - Matthew Stopa
2
一个抽象类也可以有抽象的包私有方法,因此如果你可以强制子类在同一个包中,那么这些方法就几乎和私有方法一样了。 - Yishai
我知道这是一个相当老的答案,但我有一个疑问。私有方法bar将如何被调用?我之所以问这个问题,是因为我有一些常见的事情要做。 - Philippe Gioseffi

6

创建一个抽象基类,概述结构和常见流程。为流程中必须由继承类实现的步骤指定抽象方法。


我在没有意识到它没有回答问题的情况下点了赞。就像我说的,我想要能够强制执行私有方法。抽象类做不到这一点。 - Matthew Stopa
我现在明白你的意思了。抽象类是实现继承类共同内部结构和流程的唯一方式,但代价是你不能使用私有方法。很遗憾,没有办法强制执行私有方法的内部结构跨类。 - Paolo

5

嗯,私有函数不能被任何其他类调用,甚至子类也不行。那么在不同的类中使用相同名称的私有函数有什么意义呢?


如果您有不同类型的汽车对象,并希望它们都实现某些方法,同时保护它们免受其他对象的访问,您将如何实现这一点? - Matthew Stopa
2
但如果它们受到所有其他类的保护,那么它们是否存在并不重要?无论它们被称为什么,或者如何分解,都不会影响程序的操作。例如,如果您有一个“shift(int gear)”函数,在另一个类中可以将其称为“changeGear(int gear)”,效果是相同的。或者您可以拥有只有一个齿轮且没有更改功能的汽车。由于其他类无法调用这些函数,因此无关紧要。抽象受保护将适用于需要在子类中调用函数的情况。 - Chad Okere
是的,这就是整个意思。我知道它们可能被称为不同的名称,但我不希望它们是这样的。想象一下有10个汽车类,它们都在内部使用不同的名称来表示相同的函数。你可以争论说如何实现并不重要,这是正确的。但当为自己编写代码时,让它们都使用相同的方法名称会有所帮助。当有人新加入项目时,尤其如此,他们不知道任何东西,并最终无意中更改了先前使用的命名约定。 - Matthew Stopa
因此,您可以认为您正在尝试实现的是编码标准强制执行而不是代码重用 - 使用javac来强制执行编码标准。[我认为这不一定是一个坏主意:使用编译错误比逻辑错误更容易修复的规则]。然而:如果是这种情况 - 就个人而言,我会选择TofuBeer的想法 - 使用junit来控制:这可能会使事情更清洁,更易于维护。 - monojohnny
嗯,如果你真的想要,你可以使用Java注解和一个带有注解处理器的自定义构建步骤。我写了另一个答案,其中包含更多信息:https://dev59.com/DkvSa4cB1Zd3GeqPbRO6#1987313 - Chad Okere

3

在编译时没有办法强制执行它,但是您可以编写一个单元测试或简单的程序来使用反射测试方法的存在。

我假设您这样做是为了使类在美学/设计方面保持一致。如果您出于其他原因而这样做,您应该真正使用其他人建议的抽象受保护方式。

以下是一些代码,可帮助您开始编写此类工具/单元测试(您应该至少改进错误消息,并且我真的建议使用单元测试而不是我在此处所做的内容):

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class Main
{
    public static void main(String[] args) 
    {
        check(B.class, Modifier.PRIVATE, void.class, "doit", new Class<?>[] { int.class });
        check(C.class, Modifier.PRIVATE, void.class, "doit", new Class<?>[] { int.class });
    }

    private static void check(final Class<?>   clazz,
                              final int        modifiers,
                              final Class<?>   returnType,
                              final String     name,
                              final Class<?>[] params)
    {
        try
        {
            final Method method;

            method = clazz.getDeclaredMethod(name, params);

            if(method.getModifiers() != modifiers)
            {
                System.out.println("modifiers do not match");
            }

            if(method.getReturnType() != returnType)
            {
                System.out.println("return type does not match");
            }
        }
        catch(final NoSuchMethodException ex)
        {
            System.out.println("could not find method");
        }
    }
}

interface A
{
    void foo();
}


class B
    implements A
{
    public void foo()
    {
        doit(0);
    }

    private void doit(final int x)
    {
    }
}

class C
    implements A
{
    public void foo()
    {
        doit(0);
    }

    private int doit(final int x)
    {
        return (5);
    }
}

2

创建一个名为“common”的概要类,并将所有私有方法放在其中。

然后创建5或6个类,每个类都有一个类型为“common”的字段。 当然,您无法调用私有方法(但是您说这些方法实际上是类内部的),您必须公开一些公共方法来改变状态。

public class common { 
    private method1() { ; }
    private method2() { ; }
    public other() { ; }
...
}

public class myclass1 { 
    common commonMethods;
}

public class myclass2 { 
    common commonMethods;
}

甚至可以(假设“common”已如上所定义):
public class template {
    common commonMethods;
}

public class myclass1 extends template {
...
}

因此,在每个5或6个子类中,您都可以免费获得一个(包保护的)“commonMethods”字段。

在对此线程进行后续讨论后,似乎作者实际上并不想共享逻辑:只是方法签名基本上,因此,这个答案不符合该要求。


2

虽然接口方法本身必须始终是公共的,但您可以将接口设为包私有并将所有Car(例如)的实现保留在同一个包中。

package com.some.car.pkg;

interface Car
{
    public void gas();
    public void brake();
}

尽管这些方法是公开的,但由于在 com.some.car.pkg 包之外,Car 是不可见的,所以这并不重要。这样,所有实现者都不必强制扩展抽象类。你想要共同的方法意味着真正的私有不是解决方案,而且在我看来,你需要一个接口,因为在你的情况下,抽象类不太合适,因为没有共享逻辑。

以上仅供参考。


这对我来说听起来是一种干净的做事方式:它似乎满足了两个要求:实现者必须在设计时编写具有所需接口签名的方法,而包结构将整个类及其所有方法与其他类隔离开来。同时,这里的类之间似乎也没有真正的共享逻辑:因此我同意抽象类并不完全适合。 - monojohnny

1
创建一个抽象基类,并使用标记为final的方法描述包含您私有方法的常见流程。将其标记为final意味着它不能被子类扩展,因此只要您的调用代码使用它,业务逻辑就得到了执行。通过将方法标记为protected可以创建扩展点。例如,假设您有一个表示零售商店的类。
private final void doTransaction() {
    float amountDue;

    // a protected or abstract method that extenders can override
    Collection items = this.unloadShoppingCart();

    for (Object item : items) {
        // another protected or abstract method
        amountDue +=  this.getPrice(item);
    }

    // your private method 
    amountDue += this.getSalesTax(amountDue);

}

1
如果抽象保护确实不够安全,我想知道担忧在哪里。无论如何,与monojohnny类似的替代方法是使用策略模式。这可以确保:
  • 派生类必须定义行为
  • 派生类在定义行为后不能访问它
  • 实例不能访问彼此的行为
例如,尽管没有汽车技术,但请原谅我借用汽车隐喻:
public interface GearBoxStrategy {
    public void changeGear(int newGear);
}

abstract public class Car {
    private GearBoxStrategy gearBox;
    public Car(GearBoxStrategy g) {
       this.gearBox = g;
    }

    public void accelerate(double targetSpeed) {
        int gear = getTargetGear(targetSpeed):
        gearBox.shift(gear);
    }
}

public class AutomaticTransmissionCar {
    public AutomaticTransmissionCar() {
        super(new AutomaticTransmissionGearBoxStrategy());
    }
}

public class ManualTransmissionCar {
    public ManualTransmissionCar() {
        super(new ManualTransmissionGearBoxStrategy());
    }
}

1

使用 "throw MethodNotImplementedException();" 可能是一个有用的结构。


1

看看XDepend,它使用反射根据您编译的代码创建数据库。

http://www.xdepend.com

该工具旨在为软件架构师提供快速检查潜在问题的编译代码库。它具有内置报告和可视化功能,例如类之间的关系、圆形复杂度、耦合等等。

此外,它还包括一个内置的类似于SQL的查询语言"CQL"(用于"代码查询语言")。使用CQL,您可以定义自己的报告。您可能应该能够使用它来定义违反您描述的规则的报告。此外,您还可以使用注释将CQL查询直接嵌入到您的代码中。

我没有研究过它,但已经使用了它的.NET等效工具'NDepend',这是一个非常酷的工具。

当然,您也可以编写自己的定制工具,使用反射来检查您特定的规则。不过,XDepend仍然值得一看——它应该更加灵活。


看起来很有趣。但不幸的是,价格有点贵。 - Matthew Stopa
是的,如果你需要为所有开发人员配备一份副本,这可能会有些困难。不过它主要面向软件架构师,所以预期通常只需要少量副本。 - Phil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接