0

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

Note: variable Arguments (Varargs) should be counted as 1 argument of type array instead of infinite number of arguments.

Reason: I'm trying to design better Libraries and I'm thinking of banning methods with more than 4 arguments maybe ... So I'm trying to find methods in the standard library that have a large number of arguments and study the method and think of if it was needed to be defined like that and if there is a valid case to have more than 4 arguments.

Mustafa
  • 931
  • 1
  • 13
  • 26
  • 2
    I'm not sure what you'll learn from this exercise, and I'm certainly not going to scan the entire API, but off the top of my head there is the constructor for [`RSAPrivateCrtKeySpec`](https://docs.oracle.com/javase/8/docs/api/java/security/spec/RSAPrivateCrtKeySpec.html#RSAPrivateCrtKeySpec-java.math.BigInteger-java.math.BigInteger-java.math.BigInteger-java.math.BigInteger-java.math.BigInteger-java.math.BigInteger-java.math.BigInteger-java.math.BigInteger-) which has 8 args if I count correctly. – President James K. Polk Jul 21 '19 at 22:47
  • 1
    @JamesKPolk You don't have to scan it. The computer can do that for you ;-) I think the answer is "15 parameters for [`initMouseEvent`](https://docs.oracle.com/javase/8/docs/api/org/w3c/dom/events/MouseEvent.html#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-)", as elaborated in my answer. – Marco13 Jul 22 '19 at 00:32
  • 1
    Of course I went ahead and scanned all the classes in Java 8 SE. And the winner is ... [`ImageReaderSpi`](https://docs.oracle.com/javase/8/docs/api/javax/imageio/spi/ImageReaderSpi.html#ImageReaderSpi-java.lang.String-java.lang.String-java.lang.String:A-java.lang.String:A-java.lang.String:A-java.lang.String-java.lang.Class:A-java.lang.String:A-boolean-java.lang.String-java.lang.String-java.lang.String:A-java.lang.String:A-boolean-java.lang.String-java.lang.String-java.lang.String:A-java.lang.String:A-) with 18 arguments. – President James K. Polk Jul 22 '19 at 00:53
  • @Marco13: FYI, I only examined classes in [this](https://docs.oracle.com/javase/8/docs/api/allclasses-noframe.html) list, and only public methods including constructors. – President James K. Polk Jul 22 '19 at 01:04

2 Answers2

4

The goal of limiting the number of parameters in own, public APIs is certainly a good one, but one should not blindly obey arbitrary rules and then apply quirky workarounds to follow them. Also, other people's code should sometimes only be an inspiration of how to not solve something...


That said, answering the actual question is a bit difficult. Do you want to...

  • focus only on public or protected methods?
  • only consider public classes?
  • include methods in classes from things like JavaFX?
  • include methods in classes that are public, but proprietary API?
  • ...

However, I was curious. Using a class to scan for all visible classes (+1 there!), loading them (and blatantly ignoring errors), obtaining all methods from the valid classes and having a look at their parameter count, I could find some results:

  • The overall winner seems to be from a class from the JavaFX runtime, called com.sun.scenario.effect.impl.sw.sse.SSEPhongLighting_SPOTPeer. The method is a native method that is simply called filter, and receives a whopping 37 parameters: 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).

    However, the method is private and native, and the class cannot even be found in the OpenJDK JavaFX runtime repo, so I assume that it is auto-generated somehow.

  • Limiting the whole search to public classes and methods that are also public or protected (and not native) still leads to one of the JavaFX classes. This time, it's in the com.sun.prism.impl.VertexBuffer class, which has a method called addMappedPgram, with 24 parameters: 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), and the repo also contains the source code of this method.

    This is an example of a method where most coding guidelines would say that the number of parameters is far too high. But the parameters are so "regular" (following a naming pattern, probably related to 4 corners of a quad) that I think that something like this can still be reasonable. But the class is still not supposed to be used by clients, and has to be considered as "proprietary API".

  • Omitting classes in packages that start with "sun." or "com.sun." brings us to what probably can be considered "the correct answer" to the question: The class org.w3c.dom.events.MouseEvent contains a method called initMouseEvent, which still receives 15 parameters: 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). And here is the JavaDoc API documentation of that method.

(A related side note: The function with the largest number of parameters that was supposed to be used by clients that I have encountered so far is a function from cuDNN with 31 parameters...)

Update

In response to the comments, I now also covered constructors.

The class javafx.scene.input.ScrollEvent has two constructors with 23 parameters, namely 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) and 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). Here is the link to the API documentation for the latter.


The code that I used for my tests - this is ugly and hacky, but I think it should be added here:

(Edited to also cover constructors, in response to the comment:)

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 https://stackoverflow.com/a/19554704/3182664
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 https://stackoverflow.com/a/19554704/3182664
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();
    }
}

(Note: The ClassFinder is from https://stackoverflow.com/a/19554704/3182664 !)

Marco13
  • 53,703
  • 9
  • 80
  • 159
  • 1
    [`ImageReaderSpi`](https://docs.oracle.com/javase/8/docs/api/javax/imageio/spi/ImageReaderSpi.html#ImageReaderSpi-java.lang.String-java.lang.String-java.lang.String:A-java.lang.String:A-java.lang.String:A-java.lang.String-java.lang.Class:A-java.lang.String:A-boolean-java.lang.String-java.lang.String-java.lang.String:A-java.lang.String:A-boolean-java.lang.String-java.lang.String-java.lang.String:A-java.lang.String:A-) has 18 arguments. – President James K. Polk Jul 22 '19 at 00:54
  • 1
    I think you skipped constructors. – President James K. Polk Jul 22 '19 at 00:57
  • 1
    @JamesKPolk The question asked for *methods*, but I have added constructors now. The winner here is one for [a JavaFX `ScrollEvent`](https://docs.oracle.com/javase/8/javafx/api/javafx/scene/input/ScrollEvent.html#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-) with **23 parameters**... – Marco13 Jul 22 '19 at 12:42
  • True, true. Personally I think of a constructor as a static method, but it's not really. However, for the goals of the question I think it qualifies. – President James K. Polk Jul 22 '19 at 12:44
  • 1
    @JamesKPolk The goal of the question is, admittedly, not really related to the reason of why I created this answer. Beyond that, one could argue about the bullet points that I mentioned, and consider adding `"javafx."` to the list of package prefixes to be excluded. In this case, `ImageReaderSpi` and `ImageWriterSpi` would indeed be the winners, with 18 parameters each for their constructors. – Marco13 Jul 22 '19 at 12:48
3

I don't have an answer for the actual question asked but I do think I have some useful insight for the underlying question you're trying to answer.

I would refer to Miller's Law here, which states that the average person can keep about 7 things in their head at once (note how local North American phone numbers are 7 digits).

This means, I would say that once you see around 7 of something, you should consider breaking things up and using composition. For example:

  • 7 classes per package
  • 7 methods per interface
  • 7 parameters for a function

etc.

After that, you can consider:

  • Using multiple interfaces (especially if you can see a separation, or a chance to abide to the Interface Segregation Principle
  • Create a subpackage or new top-level package for a subsection of classes in your package
  • Utilize a Helper class, which could help absorb some arguments to your function
  • In the case where you can't create a Helper, consider a Builder

This is flexible (for example, the law states it's really + or - 2) but I think it could serve as a useful baseline.

Alex Hart
  • 1,663
  • 12
  • 15