Java 标准库:库中哪些方法的参数数量最多?

Java standard library: Which methods are with biggest number of arguments in the library?

Java 标准库中哪些方法的参数数量最多?

注意:可变参数 (Varargs) 应计为 1 个数组类型的参数,而不是无限数量的参数。

原因:我正在尝试设计更好的库,我正在考虑禁止具有超过 4 个参数的方法...... 所以我试图在标准库中找到具有大量参数的方法并研究该方法并考虑是否需要像这样定义它以及是否存在超过 4 个参数的有效案例。

对于所问的实际问题,我没有答案,但我确实认为我对您要回答的基本问题有一些有用的见解。

我会在这里提到 Miller's Law,它表明普通人可以同时在脑海中保留大约 7 个东西(请注意北美当地的 phone 数字是 7 位数)。

这意味着,我会说一旦你看到大约 7 个东西,你应该考虑分解并使用组合。例如:

  • 7 class每包
  • 每个接口 7 个方法
  • 一个函数的 7 个参数

等等

之后可以考虑:

  • 使用多个接口(特别是如果您可以看到分离,或者有机会遵守接口隔离原则
  • 为包中 class 的子部分创建子包或新的 top-level 包
  • 利用助手 class,它可以帮助吸收函数的一些参数
  • 在无法创建Helper的情况下,考虑Builder

这很灵活(例如,法律规定它实际上是 + 或 - 2),但我认为它可以作为一个有用的基准。

限制自己的参数数量的目标,public APIs 当然是一个很好的目标,但不应盲目地遵守任意规则,然后应用古怪的变通方法来遵循它们。此外,other people's code 有时应该只是启发如何 而不是 解决问题...


也就是说,回答实际问题有点困难。你想...

  • 只关注 publicprotected 方法?
  • 只考虑public classes?
  • 在 classes 中包含来自 JavaFX 之类的方法?
  • 在 classes 中包含 public 但专有的方法 API?
  • ...

不过,我很好奇。使用 class to scan for all visible classes(+1 那里!),加载它们(并公然忽略错误),从有效的 classes 获取所有方法并查看它们的参数计数,我可以找到一些结果:

  • 总冠军似乎来自 JavaFX 运行时的 class,名为 com.sun.scenario.effect.impl.sw.sse.SSEPhongLighting_SPOTPeer。该方法是一个 native 方法,简单地称为 filter,并接收高达 37 个参数private static native void com.sun.scenario.effect.impl.sw.sse.SSEPhongLighting_SPOTPeer.filter(int[],int,int,int,int,int,int[],float,float,float,float,int,int,int,float,float[],float,float,float,float,float,float,float,float,float,float,int[],float,float,float,float,int,int,int,float,float,float)

    然而,方法是privatenative,并且在OpenJDK JavaFX runtime repo中甚至找不到class,所以我假设它是auto-generated不知何故。

  • 将整个搜索限制为 public class 也是 publicprotected 的类型和方法(而不是 native ) 仍然导致 JavaFX classes 之一。这一次,它在 com.sun.prism.impl.VertexBuffer class 中,它有一个名为 addMappedPgram 的方法,带有 24 个参数 : public final void com.sun.prism.impl.VertexBuffer.addMappedPgram(float,float,float,float,float,float,float,float,float,float,float,float,float,float,float,float,float,float,float,float,float,float,float,float),并且repo 还包含 the source code of this method

    这是大多数编码指南都认为参数数量太多的方法示例。但是参数是如此 "regular"(遵循命名模式,可能与四边形的 4 个角有关),我认为像这样的东西 可以 仍然是合理的。但是 class 仍然不应该被客户端使用,并且必须被视为 "proprietary API"。

  • 在以 "sun.""com.sun." 开头的包中省略 classes 将我们带到可能被认为是 "the correct answer" 的问题: class org.w3c.dom.events.MouseEvent 包含一个名为 initMouseEvent 的方法,它仍然接收 15 个参数 public abstract void org.w3c.dom.events.MouseEvent.initMouseEvent(java.lang.String,boolean,boolean,org.w3c.dom.views.AbstractView,int,int,int,int,int,boolean,boolean,boolean,boolean,short,org.w3c.dom.events.EventTarget)。这里是 the JavaDoc API documentation of that method.

(相关附注:到目前为止我遇到的客户应该使用的参数数量最多的函数是function from cuDNN 具有 31 个参数...)

更新

为了回应评论,我现在还介绍了 构造函数

classjavafx.scene.input.ScrollEvent有两个23个参数的构造函数,即public javafx.scene.input.ScrollEvent(javafx.event.EventType,double,double,double,double,boolean,boolean,boolean,boolean,boolean,boolean,double,double,double,double,double,double,javafx.scene.input.ScrollEvent$HorizontalTextScrollUnits,double,javafx.scene.input.ScrollEvent$VerticalTextScrollUnits,double,int,javafx.scene.input.PickResult)public javafx.scene.input.ScrollEvent(java.lang.Object,javafx.event.EventTarget,javafx.event.EventType,double,double,double,double,boolean,boolean,boolean,boolean,boolean,boolean,double,double,double,double,javafx.scene.input.ScrollEvent$HorizontalTextScrollUnits,double,javafx.scene.input.ScrollEvent$VerticalTextScrollUnits,double,int,javafx.scene.input.PickResult)。这是后者的link to the API documentation


我用于我的测试的代码 - 这是丑陋和 hacky,但我认为它应该添加在这里:

(编辑为也涵盖构造函数,以回应评论:)

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ArgCounting
{
    static class Entry
    {
        Class<?> clazz;
        Executable executable;
        int numParams;
    }

    public static void main(String[] args) throws Exception
    {
        List<Entry> entries = new ArrayList<Entry>();
        ClassFinder.findClasses(new Visitor<String>()
        {
            @Override
            public boolean visit(String clazz)
            {
                try
                {
                    System.out.println(clazz);
                    Class<?> c = Class.forName(clazz);
                    Method[] methods = c.getDeclaredMethods();
                    for (Method method : methods)
                    {
                        Entry entry = new Entry();
                        entry.clazz = c;
                        entry.executable = method;
                        entry.numParams = method.getParameterCount();
                        entries.add(entry);
                    }
                    Constructor<?>[] constructors = c.getDeclaredConstructors();
                    for (Constructor<?> constructor : constructors)
                    {
                        Entry entry = new Entry();
                        entry.clazz = c;
                        entry.executable = constructor;
                        entry.numParams = constructor.getParameterCount();
                        entries.add(entry);
                    }
                }
                catch (Throwable e)
                {
                    System.out.println("Ignoring: " + e);
                }
                return true;
            }
        });

        System.out.println("There are " + entries.size() + " executables");

        Predicate<Entry> executableIsNotNative = 
            e -> !Modifier.isNative(e.executable.getModifiers());
        Predicate<Entry> executableIsPublic = 
            e -> Modifier.isPublic(e.executable.getModifiers());
        Predicate<Entry> executableIsProtected = 
            e -> Modifier.isProtected(e.executable.getModifiers());
        Predicate<Entry> classIsPublic = 
            e -> Modifier.isPublic(e.clazz.getModifiers());

        List<String> skippedPackagePrefixes = Arrays.asList(
            "sun.", "com.sun.");
        Predicate<Entry> isSkipped = e -> 
        {
            for (String prefix : skippedPackagePrefixes) 
            {
                Package p = e.clazz.getPackage();
                if (p != null)
                {
                    if (p.getName().startsWith(prefix))
                    {
                        return true;
                    }
                }
            }
            return false;
        };
        Predicate<Entry> isNotSkipped = isSkipped.negate();

        Predicate<Entry> executableIsRelevant = 
                executableIsNotNative.and(executableIsPublic.or(executableIsProtected));

        System.out.println("Methods:");
        printAllMax(entries, 
            classIsPublic.and(executableIsRelevant).and(isNotSkipped).and(e -> e.executable instanceof Method));

        System.out.println("Constructors:");
        printAllMax(entries, 
            classIsPublic.and(executableIsRelevant).and(isNotSkipped).and(e -> e.executable instanceof Constructor));
    }

    private static void printAllMax(Collection<Entry> entries, Predicate<Entry> filter)
    {
        int max = entries.stream()
                .filter(filter)
                .mapToInt(e -> e.numParams)
                .max()
                .getAsInt();

        System.out.println("Having " + max + " parameters:");
        entries.stream().filter(filter.and(e -> e.numParams == max)).forEach(e -> 
        {
            System.out.println(e.executable);
        });
    }

}

// From 
interface Visitor<T>
{
    /**
     * @return {@code true} if the algorithm should visit more results,
     *         {@code false} if it should terminate now.
     */
    public boolean visit(T t);
}

// From 
class ClassFinder
{
    public static void findClasses(Visitor<String> visitor)
    {
        String classpath = System.getProperty("java.class.path");
        String[] paths = classpath.split(System.getProperty("path.separator"));

        String javaHome = System.getProperty("java.home");
        File file = new File(javaHome + File.separator + "lib");
        if (file.exists())
        {
            findClasses(file, file, true, visitor);
        }

        for (String path : paths)
        {
            file = new File(path);
            if (file.exists())
            {
                findClasses(file, file, false, visitor);
            }
        }
    }

    private static boolean findClasses(File root, File file,
        boolean includeJars, Visitor<String> visitor)
    {
        if (file.isDirectory())
        {
            for (File child : file.listFiles())
            {
                if (!findClasses(root, child, includeJars, visitor))
                {
                    return false;
                }
            }
        }
        else
        {
            if (file.getName().toLowerCase().endsWith(".jar") && includeJars)
            {
                JarFile jar = null;
                try
                {
                    jar = new JarFile(file);
                }
                catch (Exception ex)
                {

                }
                if (jar != null)
                {
                    Enumeration<JarEntry> entries = jar.entries();
                    while (entries.hasMoreElements())
                    {
                        JarEntry entry = entries.nextElement();
                        String name = entry.getName();
                        int extIndex = name.lastIndexOf(".class");
                        if (extIndex > 0)
                        {
                            if (!visitor.visit(
                                name.substring(0, extIndex).replace("/", ".")))
                            {
                                return false;
                            }
                        }
                    }
                }
            }
            else if (file.getName().toLowerCase().endsWith(".class"))
            {
                if (!visitor.visit(createClassName(root, file)))
                {
                    return false;
                }
            }
        }

        return true;
    }

    private static String createClassName(File root, File file)
    {
        StringBuffer sb = new StringBuffer();
        String fileName = file.getName();
        sb.append(fileName.substring(0, fileName.lastIndexOf(".class")));
        file = file.getParentFile();
        while (file != null && !file.equals(root))
        {
            sb.insert(0, '.').insert(0, file.getName());
            file = file.getParentFile();
        }
        return sb.toString();
    }
}

(注:ClassFinder来自!)