Mustafa
Mustafa

Reputation: 981

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

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.

Upvotes: 0

Views: 411

Answers (2)

Marco13
Marco13

Reputation: 54639

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 !)

Upvotes: 4

Alex Hart
Alex Hart

Reputation: 1673

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.

Upvotes: 3

Related Questions