简体   繁体   中英

Pass static methods in Java

I am trying to create a class that times the average runtime of a method. I understand how to do that by running it 100 times and taking the average of it all. Example:

private long calculateAvg(){
    long totalTime = 0;

    for(int i = 0; i < ITERATIONS; i++){
        long startTime = System.nanoTime();
        testMethod();
        long endTime = System.nanoTime();

        totalTime += (endTime - startTime);  //divide by 1000000 to get milliseconds.
    }
    return (totalTime / ITERATIONS);

}

Now I can set this to work for one static method but is there a way to pass different static methods into this to calculate instead of creating one of these for each method I want to test? If not, is there a design pattern that may work here? As of now, I am creating one of these methods for every other method I want to time and it doesn't seem efficient because I am reusing so much code.

You can accept and run a Runnable argument:

private long calculateAvg(Runnable r) {
    //...
    long startTime = System.nanoTime();
    r.run();
    long endTime = System.nanoTime();
    //...
}

And call it like this:

long avg1 = calculateAvg(() -> testMethod());
long avg2 = calculateAvg(() -> testMethod2());

You might also want to consider some tips from How do I write a correct micro-benchmark in Java?

You cannot "pass methods", static nor non-static. Instead, you need to create an object reference created from a class which contains the method. Then you call the method on the object reference. In order to provide different implementations of a method, you create an interface . Then you can have many classes which implement that interface and the method which it declares. Now your timing code uses that interface to access the correct method.

public interface TheMethod {
    public void foo();
}

public class Method1 implements TheMethod {
    public void foo() {
        // code goes here 
    }
}

public class Method2 implements TheMethod {
    public void foo() {
        // code goes here 
    }
}

Now modify your timing method to accept a TheMethod instance:

private long calculateAvg(TheMethod method){
    // ...
    method.foo();
    // ...
}

Finally, you can call this method with different instances of classes which implement TheMethod :

TheMethod m1 = new Method1();
TheMethod m2 = new Method2();

long x = calculateAvg(m1);
long y = calculateAvg(m2);

I got two solutions may help for this question:
1. Java reflection :

public long calculateAvg(Object obj, String methodName) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException{
    Method method = obj.getClass().getDeclaredMethod(methodName);
    method.setAccessible(true);

    long totalTime = 0;
    for(int i = 0; i < ITERATIONS; i++) {
        long startTime = System.nanoTime();
        method.invoke(obj);
        long endTime = System.nanoTime();

        totalTime += (endTime - startTime);  //divide by 1000000 to get milliseconds.
    }
    return (totalTime / ITERATIONS);
}
  1. Proxy Pattern

You can learn about proxy pattern , you don't need to call the real method yourself, the proxy does. you can get the time before and after calling the real method. proxy pattern tutorials

No you cann't pass method in Java because methods are not first class citizen in Java, they are not values . The pattern to resolve this kind of problem is to use anonymous class . Take your case as example, you can define an interface :

@FunctionalInterface  // this annotation is available since jdk1.8
public interface Measurable {
    void measure();
}

And another MeasureUtil util class, methods inside it accept the type Measurable :

    public class MeasureUtil {
    private static final long ITERATIONS = 1000;

    public static long calculateAvg(Measurable measurable) {
        long totalTime = 0;

        for(int i = 0; i < ITERATIONS; i++){
            long startTime = System.nanoTime();
            measurable.measure();
            long endTime = System.nanoTime();

            totalTime += (endTime - startTime);  //divide by 1000000 to get milliseconds.
        }
        return (totalTime / ITERATIONS);
    }
}

Then you new an instance of Measurable and pass it to MeasureUtil.calculateAvg each time:

    public class Main {
    public static void main(String[] args) {
        long avg = MeasureUtil.calculateAvg(new Measurable() {
            @Override
            public void measure() {
                method_under_mesure();
            }
        });
        System.out.println("Avg: " + avg);
    }

    private static void method_under_mesure() {
        System.out.println("call me");
    }
}

Since jdk1.8 Java starts to support lambda expression , which can make it much simpler and easier with the syntax sugar:

long avg = MeasureUtil.calculateAvg(() -> method_under_mesure());

Based on your input, your method should be something like this

private long calculateAvg(Testable t){
  long totalTime = 0;

  for(int i = 0; i < ITERATIONS; i++){
    long startTime = System.nanoTime();
    t.testMethod();
    long endTime = System.nanoTime();

    totalTime += (endTime - startTime);  //divide by 1000000 to get milliseconds.
  }
  return (totalTime / ITERATIONS);
}

Then, you should have an interface with a single method:

public interface Testable {
    public void testMethod();
}

In your main method, you have 3 ways to pass the method. Let's say you want to test a static sort method in SelectionSort class.

First: anonymous class (the traditional way)

calculateAvg(new Testable(){
    @Override
    public void testMethod(){
       SelectionSort.sort();
    }
})

Second: Lambda (Java 8)

calculateAvg(() -> SelectionSort.sort())

Third: Method Reference Java 8 Method Reference: How to Use it

calculateAvg(SelectionSort::sort)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM