Java named method parameters

Named Parameters in Java

Java doesn’t natively support named parameters, but we can easily have something like:

robot.punch(force(1), speed(100));

Named parameters are very handy when we start having methods with a lot of parameters and we want to allow invoking the method with an arbitrary subset of them, using default values for the rest. A typical symptom when this is needed is when we have a large number of overloaded methods, with various combinations of parameters.

In order to provide this we need something like a NamedParameter class and a few factory methods. While we could easily write our own implementation, we can just use the named-parameters library.

 me.jaksa named-parameters 0.2 

We still have to do some manual work. First, for the sake of tidiness, we will create a nested class containing our parameters:

…and inside it an enum with the names of our parameters:

Now we need the factory methods for our parameters inside the PunchParams class:

import static me.jaksa.namedparameters.GiantRobot.PunchParams.Names.*; import static me.jaksa.namedparameters.Params.*; . public static Param force(int f) < return param(FORCE, f); >public static Param speed(int s) < return param(SPEED, s); >public static Param exclamation(String e)

Notice the static imports to make the code slightly more readable. The param() method statically imported from the Params class is just a shrothand for “new Param”. Also we could have used strings instead of the enum and completely avoid defining the enum, but we will later use the same names for looking up the parameters.

Читайте также:  Python add timedelta to datetime

Now we can define our method taking a vararg of Param classes:

public void punch(Param. params)

and we’re done. The getParam() method is statically imported from the Params class and it returns the value of the parameter with the specified name or the specified default value if the parameter is not there.

Now we can use the method from other classes and specify any combination of parameters in any order:

import static me.jaksa.namedparameters.GiantRobot.PunchParams.*; . robot.punch(); robot.punch(force(5)); robot.punch(speed(30)); robot.punch(force(1), speed(100)); robot.punch(speed(100), force(1)); robot.punch(force(12), exclamation("Take this!"));

There are more tricks than what we’ve seen here. We can have methods which have a combination of mandatory, optional or even unnamed parameters, add more type safety, combine parameters for different methods or safely separate parameters with the same name but different type. We will take a look at more advanced uses in the future.

Источник

Passing Information to a Method or a Constructor

The declaration for a method or a constructor declares the number and the type of the arguments for that method or constructor. For example, the following is a method that computes the monthly payments for a home loan, based on the amount of the loan, the interest rate, the length of the loan (the number of periods), and the future value of the loan:

public double computePayment( double loanAmt, double rate, double futureValue, int numPeriods) < double interest = rate / 100.0; double partial1 = Math.pow((1 + interest), - numPeriods); double denominator = (1 - partial1) / interest; double answer = (-loanAmt / denominator) - ((futureValue * partial1) / denominator); return answer; >

This method has four parameters: the loan amount, the interest rate, the future value and the number of periods. The first three are double-precision floating point numbers, and the fourth is an integer. The parameters are used in the method body and at runtime will take on the values of the arguments that are passed in.

Note: Parameters refers to the list of variables in a method declaration. Arguments are the actual values that are passed in when the method is invoked. When you invoke a method, the arguments used must match the declaration’s parameters in type and order.

Parameter Types

You can use any data type for a parameter of a method or a constructor. This includes primitive data types, such as doubles, floats, and integers, as you saw in the computePayment method, and reference data types, such as objects and arrays.

Here’s an example of a method that accepts an array as an argument. In this example, the method creates a new Polygon object and initializes it from an array of Point objects (assume that Point is a class that represents an x, y coordinate):

public Polygon polygonFrom(Point[] corners) < // method body goes here >

Note: If you want to pass a method into a method, then use a lambda expression or a method reference.

Arbitrary Number of Arguments

You can use a construct called varargs to pass an arbitrary number of values to a method. You use varargs when you don’t know how many of a particular type of argument will be passed to the method. It’s a shortcut to creating an array manually (the previous method could have used varargs rather than an array).

To use varargs, you follow the type of the last parameter by an ellipsis (three dots, . ), then a space, and the parameter name. The method can then be called with any number of that parameter, including none.

public Polygon polygonFrom(Point. corners) < int numberOfSides = corners.length; double squareOfSide1, lengthOfSide1; squareOfSide1 = (corners[1].x - corners[0].x) * (corners[1].x - corners[0].x) + (corners[1].y - corners[0].y) * (corners[1].y - corners[0].y); lengthOfSide1 = Math.sqrt(squareOfSide1); // more method body code follows that creates and returns a // polygon connecting the Points >

You can see that, inside the method, corners is treated like an array. The method can be called either with an array or with a sequence of arguments. The code in the method body will treat the parameter as an array in either case.

You will most commonly see varargs with the printing methods; for example, this printf method:

public PrintStream printf(String format, Object. args)

allows you to print an arbitrary number of objects. It can be called like this:

System.out.printf("%s: %d, %s%n", name, idnum, address);
System.out.printf("%s: %d, %s, %s, %s%n", name, idnum, address, phone, email);

or with yet a different number of arguments.

Parameter Names

When you declare a parameter to a method or a constructor, you provide a name for that parameter. This name is used within the method body to refer to the passed-in argument.

The name of a parameter must be unique in its scope. It cannot be the same as the name of another parameter for the same method or constructor, and it cannot be the name of a local variable within the method or constructor.

A parameter can have the same name as one of the class’s fields. If this is the case, the parameter is said to shadow the field. Shadowing fields can make your code difficult to read and is conventionally used only within constructors and methods that set a particular field. For example, consider the following Circle class and its setOrigin method:

The Circle class has three fields: x , y , and radius . The setOrigin method has two parameters, each of which has the same name as one of the fields. Each method parameter shadows the field that shares its name. So using the simple names x or y within the body of the method refers to the parameter, not to the field. To access the field, you must use a qualified name. This will be discussed later in this lesson in the section titled «Using the this Keyword.»

Passing Primitive Data Type Arguments

Primitive arguments, such as an int or a double , are passed into methods by value. This means that any changes to the values of the parameters exist only within the scope of the method. When the method returns, the parameters are gone and any changes to them are lost. Here is an example:

public class PassPrimitiveByValue < public static void main(String[] args) < int x = 3; // invoke passMethod() with // x as argument passMethod(x); // print x to see if its // value has changed System.out.println("After invoking passMethod, x codeblock"> 
After invoking passMethod, x = 3

Passing Reference Data Type Arguments

Reference data type parameters, such as objects, are also passed into methods by value. This means that when the method returns, the passed-in reference still references the same object as before. However, the values of the object's fields can be changed in the method, if they have the proper access level.

For example, consider a method in an arbitrary class that moves Circle objects:

public void moveCircle(Circle circle, int deltaX, int deltaY) < // code to move origin of circle to x+deltaX, y+deltaY circle.setX(circle.getX() + deltaX); circle.setY(circle.getY() + deltaY); // code to assign a new reference to circle circle = new Circle(0, 0); >

Let the method be invoked with these arguments:

Inside the method, circle initially refers to myCircle . The method changes the x and y coordinates of the object that circle references (that is, myCircle ) by 23 and 56, respectively. These changes will persist when the method returns. Then circle is assigned a reference to a new Circle object with x = y = 0 . This reassignment has no permanence, however, because the reference was passed in by value and cannot change. Within the method, the object pointed to by circle has changed, but, when the method returns, myCircle still references the same Circle object as before the method was called.

Источник

Beyond Java

I was very surprised when I read Adam Bien's blog post on named parameters in Java 8.

Named parameters? Well. not quite. At least not the kind of named parameters we know from languages such as Visual Basic, Groovy and many others. But it turned out to be an interesting read nonetheless.

Java 8 API

Java 8 introduces another feature to the reflection API. You can read the names of the parameters of a method. Have you ever seen those annoying synthetic parameter names arg0, arg1, arg2 in the debugger? That's what happens if parameter names get lost. Granted, the average programmer will hardly ever want to read the name of a parameter via reflection. That's just not your frame of mind when you implement business logic. But it's attractive for framework programmers, so application programmers will benefit from the feature on the long run.

Take Spring's @RequestParam as an example. They introduced the annotation basically because they couldn't access the parameter name:

The annotation @RequestParam simply repeats the parameter name. In theory, you could choose another @RequestParam, but why should you? Java 8 makes the annotation superfluous. Read the parameter name, and you're done.

Reading the parameter names is as simple as can be:

Method m = MyFavoriteClass.class.getMethods()[0]; Parameter[] parameters = m.getParameters(); for (Parameter parameter : parameters)

The Java 8 compiler switch

However, parameter names aren't compiled into the byte code by default. That's for a good reason: the extra information requires more memory, and it might confuse older tools dealing with the byte code. Hence you have to add a compiler switch to enable named parameters:

If you omit the compiler switch, you can still call parameter.getName() , but the calls yields synthetic names such as arg0, arg1, and arg2.

What about earlier Java versions?

Now for the interesting stuff. When I researched the article, I learned parameter names can be read for a long time. That shouldn't come as a surprise: every debugger shows the real parameter names. However, it's not possible using the official reflection API. I always thought debuggers somehow access the source code to get additional information, but there's another way. If you compile a Java class with the default settings additional debug info is added to the class files. The parameters of a method are the first few variables of the variable table. Their names can be read using a byte code library such as ASM or BCEL:

com.sun.org.apache.bcel.internal.classfile.Method bcelMethod = Repository.lookupClass(method.getDeclaringClass()).getMethod(method); LocalVariableTable localVariableTable = bcelMethod.getLocalVariableTable(); LocalVariable localVariable = localVariableTable.getLocalVariable(0); if (localVariable.getStartPC() == 0)

Spring

Spring offers a built-in class to accesss parameter names:

@Autowired private ParameterNameDiscoverer parameterNameDiscoverer; Method m = . String[] names = parameterNameDiscoverer.getParameterNames(m);

The DefaultParameterNameDiscoverer uses the Java 8 API, or a down-stripped version ASM in older versions of Java.

There's also an alternative implementation of ParameterNameDiscoverer in Spring dealing with AspectJ: AspectJAdviceParameterNameDiscoverer. This seems to be a sophisticated class that's rarely used (correct me if I'm wrong!). In most cases, the default implementation does the trick, too.

Alternative solutions that don't depend on the debug compile switch

Paranamer adds an additional compilation step that store the parameter names in synthetic static methods of your class. The advantage of this approach is it also works without the debug compiler switch.

Java's annotation processor API (which allows you to write your own preprocessor) gives you access to parameter names. APT is part of the compilation process, so it doesn't have to rely on the information in the class file but can access data stored in the AST during compilation.

Wrapping it up

Java 8 introduces a surprisingly simple mechanism to read method parameter names via reflection. This could be great news to framework developers, but it's spoiled by the decision not to activate the feature by default. As it turns out, you don't need the new Java 8 feature at all. The vast majority or Java byte code includes the debug information (because it's invaluable when it comes to tracing an error in a production stack trace). So it's possible to read parameter names using ASM or BCEL. Maybe - just maybe - framework developers come up with interesting solutions if the word spreads.

Источник

Оцените статью