Nulls and varargs
I needed to work out how you can invoke methods that have varargs when you don't actually have anything to send for that parameter. Below are the lessons I have learned and below that is the JUnit test that proves it. I am using JDK 7.
- When a method has a
varargsargument, you can completely ignore it and not put anything at all in that place. - A
varargsparameter always has a type, and you can send a single value of that type - or an array of that type. - The array can be empty - but your code should be able to deal with that.
- The parameter can be null, although Eclipse wants you cast the null - and your code must be prepared to deal with nulls (or you will throw a
NullPointerException). - When using
mockit.Deencapsulationto invoke a method that acceptsvarargs, you cannot ignore the parameter; it has to have something there or JMockit will throw anIllegalArgumentException. mockit.Deencapsulationthrows anIllegalArgumentExceptionif you send null (cast or not) in place of thevarargsbecausenullisn't enough information for JMockit to work out what type of argument it is.- You can tell
mockit.Deencapsulationto send null in place of thevarargsby using aX[].classas the parameter (where X is the type expected byvarargs) - and your code must be prepared to deal with nulls (or you will throw aNullPointerException). See http://bit.ly/ZMYZuR.
Here is the test.
import static org.junit.Assert.assertNotNull;
import java.nio.file.Paths;
import mockit.Deencapsulation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Test var args. What calling a method with varargs, you can totally leave out
* the vararg parameter, but you cannot use null - unless you cast it. But you
* also can't use a null (even a casted one) when you are using JMockit
* {@link Deencapsulation#invoke(Class, String, Object...)}.
*
* @author RobertMarkBram
*/
@RunWith(JUnit4.class)
public final class TestVarArgs {
/**
* A method with var args.
*
* @param first
* first path
* @param more
* the rest - may be null
*/
public void readFiles(final String first, final String... more) {
if (more == null || more.length == 0 || more[0] == null) {
assertNotNull(Paths.get(first).toString());
} else {
assertNotNull(Paths.get(first, more).toString());
}
}
/**
* Calling {@link #readFiles(String, String...)} with just one arg should
* work. Completely ignoring the vararg.
*/
@Test
public void callMethodWithOneArg() {
readFiles("C:/Temp");
}
/**
* Calling {@link #readFiles(String, String...)} with just one arg and an
* empty string should work.
*/
@Test
public void callMethodWithOneArgAndEmptyString() {
readFiles("C:/Temp", "");
}
/**
* Calling {@link #readFiles(String, String...)} with just one arg and an
* empty array should work.
*/
@Test
public void callMethodWithOneArgAndEmptyArray() {
String[] empty = {};
readFiles("C:/Temp", empty);
}
/**
* Calling {@link #readFiles(String, String...)} with just one arg and an
* array with a blank string should work.
*/
@Test
public void callMethodWithOneArgAndArrayWithEmptyString() {
String[] empty = { "" };
readFiles("C:/Temp", empty);
}
/**
* Calling {@link #readFiles(String, String...)} with just one arg and a null
* works, but you have to do a null check on the varargs in case it is null.
* Plus, Eclipse gives you a warning for not casting null as vararg.
*/
@Test
@SuppressWarnings("all")
public void callMethodWithOneArgAndNull() {
readFiles("C:/Temp", null);
}
/**
* Calling {@link #readFiles(String, String...)} with just one arg and a null
* cast to the expected type works, but you have to do a null check on the
* varargs in case it is null.
*/
@Test
public void callMethodWithOneArgAndStringCastNull() {
readFiles("C:/Temp", (String) null);
}
/**
* Calling {@link #readFiles(String, String...)} with just one arg and a null
* cast to the expected type works, but you have to do a null check on the
* varargs in case it is null.
*/
@Test
public void callMethodWithOneArgAndCastNull() {
readFiles("C:/Temp", (String[]) null);
}
/**
* Calling {@link #readFiles(String, String...)} via JMockit with just one
* arg fails with an {@link IllegalArgumentException} because JMockit
* requires a valid varargs argument, i.e., not null.
*
* @see http://bit.ly/ZMYZuR
*/
@Test(expected = IllegalArgumentException.class)
public void callMethodWithOneArgJmockit() {
Deencapsulation.invoke(this, "readFiles", "C:/Temp");
}
/**
* Calling {@link #readFiles(String, String...)} via JMockit with just one
* arg fails with an {@link IllegalArgumentException} because JMockit
* requires a valid varargs argument, i.e., not a single element of the
* array.
*
* @see http://bit.ly/ZMYZuR
*/
@Test(expected = IllegalArgumentException.class)
public void callMethodWithOneArgAndEmptyStringJmockit() {
Deencapsulation.invoke(this, "readFiles", "");
}
/**
* Calling {@link #readFiles(String, String...)} via JMockit with just one
* arg and an empty array should work.
*/
@Test
public void callMethodWithOneArgAndEmptyArrayJmockit() {
String[] empty = {};
Deencapsulation.invoke(this, "readFiles", "C:/Temp", empty);
}
/**
* Calling {@link #readFiles(String, String...)} via JMockit with just one
* arg and an array with a blank string should work.
*/
@Test
public void callMethodWithOneArgAndArrayWithEmptyStringJmockit() {
String[] empty = { "" };
Deencapsulation.invoke(this, "readFiles", "C:/Temp", empty);
}
/**
* Calling {@link #readFiles(String, String...)} via JMockit with just one
* arg and a null fails with an {@link IllegalArgumentException} because
* JMockit requires a valid varargs argument, i.e., not null.
*
* @see http://bit.ly/ZMYZuR
*/
@Test(expected = IllegalArgumentException.class)
public void callMethodWithOneArgAndNullJmockit() {
Deencapsulation.invoke(this, "readFiles", "C:/Temp", null);
}
/**
* Calling {@link #readFiles(String, String...)} via JMockit with just one
* arg and a null cast to the expected type fails with an
* {@link IllegalArgumentException} because JMockit requires a valid varargs
* argument, i.e., not null.
*
* @see http://bit.ly/ZMYZuR
*/
@Test(expected = IllegalArgumentException.class)
public void callMethodWithOneArgAndCastNullJmockit() {
Deencapsulation.invoke(this, "readFiles", "C:/Temp", (String[]) null);
}
/**
* Calling {@link #readFiles(String, String...)} via JMockit with just one
* arg and a null cast to the expected type fails with an
* {@link IllegalArgumentException} because JMockit requires a valid varargs
* argument, i.e., not null.
*
* @see http://bit.ly/ZMYZuR
*/
@Test(expected = IllegalArgumentException.class)
public void callMethodWithOneArgAndStringCastNullJmockit() {
Deencapsulation.invoke(this, "readFiles", "C:/Temp", (String) null);
}
/**
* Calling {@link #readFiles(String, String...)} via JMockit with just one
* arg and a null cast to the expected type fails with an
* {@link IllegalArgumentException} because JMockit requires a valid varargs
* argument, i.e., not null.
*
* @see http://bit.ly/ZMYZuR
*/
public void callMethodWithOneArgAndNullArrayJmockit() {
Deencapsulation.invoke(this, "readFiles", "C:/Temp", String[].class);
}
}
Further reading.
- Notes on the introduction of varargs in JDK 1.5.
- Java trail: Passing Information to a Method or a Constructor.
- I got a few pointers on what nulls to test thanks to einpoklum's answer to my StackOverflow post: @SupressWarnings for the invocation of the varargs with un-cast null.