AS JAVA DEVELOPERS, we talk about query methods, command methods, and factory methods. We talk about convenience methods,
helper methods, and assertion methods. We talk about primitive methods, composed methods, and template methods.
Obviously, we have a rich vocabulary for talking about methods of a class or interface. We use this vocabulary
to quickly communicate and document what a method does, who it may be used by, and how it is implemented.
Understanding this vocabulary is key to fast and effective communication among developers.
I present three method-type categories and nine key method types that we use in our daily work.
They are presented using a running example, then cataloged for use as part of a shared vocabulary.
Each of the method types comes with its own naming convention. Mastering this vocabulary helps us
better implement our methods, better document our classes and interfaces, and communicate more effectively.
We use a common class design pattern to illustrate the different types of methods: the combination of an
interface with an abstract superclass and two concrete subclasses that add to the superclass and
implement the interface. The example is a Name interface
with a complementing abstract superclass,
AbstractName, and two concrete implementations,
StringName and VectorName. This example is rich
enough to provide all the core types of methods that make up large parts of our everyday vocabulary
when we talk about methods.
Walking through this design and discussing the methods lets us encounter three main categories of method
types: query methods, mutation methods, and helper methods. We then use specific
method types such as the conversion method or assertion method to better design the interface and
classes as well as to better document them. Using the naming conventions associated with each
method type lets us program more efficiently.
The method types we discuss describe the purpose of a method as it serves a client. A future article
discusses the additional properties a method may have. Method properties include being a primitive,
hook, or convenience method. Every method has exactly one method type, but it may have several method
properties. So, a method may be of type "get method" and have properties "primitive" and "hook."
Consider the design example now: We frequently give names to objects so that we can store the objects
under these names and retrieve them later. Names may be as simple as a single string, and they may
be as complex as a multipart URL. Names we use frequently are class, interface, and package names,
for example, "java.lang.String."
Such a name consists of several parts, called name components.
The name components of "java.lang.String" are
"java", "lang", and "String". Generally speaking,
we can view a name as a sequence of name components. In the example, we represent each name as a
Name object and each name component as a String object.
Name objects do not exist without a purpose: They are always part of a naming scheme. We can manage objects
named by a Name object by using an appropriate naming scheme. For example, viewing a name as a sequence
of name components lets us manage named objects in a hierarchical fashion, which is the most common
(and convenient) management scheme I know of. Examples of Name
objects that are interpreted hierarchically are Internet domain names, file names, and Java class names.
For example, to look up the class file for "com.mystartup.killerapp.Main"
the Java runtime environment first takes the classpath and then searches for a directory called
"com," followed by searching for
a directory called "mystartup," and so on.
This is a recursive descent into a directory hierarchy.
Whether we represent the original name as a sequence of components or not, the lookup algorithm
requires these name components one after another.
We may choose to interpret a name as a sequence of name components. However, this does not say anything about
its implementation. We may implement name objects using a single string or a Vector of strings. In the first
case, the whole name is in the string; in the second case, each element of the
Vector is one component of the name. Let us call the class of the first case "StringName"
and the class of the second case "VectorName"
to indicate how they are implemented.
These two different implementations have different behavior. A StringName
is optimal (minimal) in memory
consumption but slow if you need to retrieve a specific component, for example the class name on index
position 4 of "com.mystartup.killerapp.Main". A
VectorName is (close to) optimal in lookup speed, but
requires more memory, because of the additional Vector object. You might argue to introduce yet another
third class StringArrayName, where we hold the name components in a
String array. That's true, but still
StringName has advantages over StringArrayName,
for example when you have to represent the name object as a single string, as you might have to do when writing out name objects to a stream.
Experience has it that for different use-contexts, you will want different implementations, and so we keep both
StringName and VectorName.
To make programming convenient, we hide these implementation classes behind a
common Name interface that captures all the methods available
both for StringName and VectorName objects.
We also introduce a shared abstract superclass "AbstractName"
from which StringName and VectorName inherit
to reuse code. Figure 1 shows the design visually using UML syntax.
Figure 1. The Name example design
What does the Name interface look like? Listing 1 provides an answer.
Browsing over the methods, we can see immediately two main categories of methods: query methods and mutation methods.
Query methods are methods through which you request some information but that don't change the state of the object.
Examples of query methods are "String component(int)," which requests a
specific name component and "boolean isEmpty(),"
which asks whether the name object is equal to"".
Mutation methods are methods that do something to the object (change its state). Typically, they do not return
anything (for exceptions, see below). Examples of mutation methods are "void component(int i, String c),"
which sets the component on index i to value c
and "void remove(int i)," which removes the component on index i.
Finally, there is a third category of method types: the helper method. You may already have noticed the
method. It creates a NameEnumeration object for iterating over all name
components of a name object. This method is a factory method. A factory method is neither a query nor a mutation method, because
it does not return information about the object being queried nor does it change the object. A factory method is a helper method,
which is a third category of method types.
Purpose of a Method
So we have three main categories of method types: query methods, mutation methods, and helper methods.
Query methods return information about the object, but don't change its state. Mutation methods change
the object's state (cause a state transition), but do not return anything. Helper methods neither
return information about the object nor change its state, but they carry out some helper task.
These three method-type categories answer what a method is good for, what its purpose is. Other valid perspectives
on methods are who may access a method (accessibility, visibility) and how it is implemented. We're discussing
method types, but leaving out method properties, which we'll discuss in a future article.
The purpose point of view comprises the largest number of method types. Table 1 shows all method types that
we're discussing. This list is by no means exhaustive. However, it covers most of the method types I
have worked with, and I believe that it captures the most important ones.
Table 1. Method-type categories and method types.
get method (getter)
set method (setter)
boolean query method
One rule of good Java interface design is to make a method provide exactly one service and not to overload it with too many tasks.
Kent Beck programmatically states: "Divide your program into methods that perform one identifiable task."
The main advantage of keeping methods simple and well-focused is that you make your methods easier to understand. You also
make them easier to use, because a method that is easier to understand is easier to use. In addition, small methods make
a system more flexible. If you break up behavior into many small methods, you can change your system more easily. You
can use small methods and compose them to larger methods more easily. You can also override smaller methods in subclasses
more easily than you can override large methods.
In general, this rule leads to a method being either in the query-method, mutation-method, or helper-method category.
However, no rule is without exceptions, which we will discuss after the discussion of the method-type categories.
Listing 2 shows the set of query methods from the
We can distinguish four different query method types: get methods, boolean
query methods, comparison methods, and conversion methods.
You may know these method types under some other name. For example, conversion methods are also known as converter
or interpretation methods. The sidebar catalog lists the aliases we know (see Catalog of Method Types sidebar).
- Get method (or getter for short). A get method is a query method that returns a (logical) field of the object. The
field may be a technical field or it may be a logical field, derived from other fields. Examples of get methods are
"String component(int)" to retrieve a specific name component and
"char delimiterChar()" to retrieve the default
delimiter char used to separate name components when representing the name as a single string.
- Boolean query method. A boolean query method is a query method
that returns a boolean value. Examples of boolean
query methods are "boolean isEqual(Name)" and
"boolean isEmpty()." Boolean query methods have special naming
conventions and are so frequent that I decided to give them an extra name rather than just call them query methods.
- Comparison method. A comparison method is a query method that compares two objects and returns a value that tells you
which object is "greater" or "lesser" than the other one according to an ordering criterion. The most well-known example
is "boolean equals(Object)," to which Name adds the
"boolean isEqual(Name)" method.* Some comparison methods are boolean
query methods, but not all are necessarily so. It is a frequent idiom to return an integer in the range "-1..1" to indicate
that an object is less, equal, or greater than another object.
- Conversion method. A conversion method is a query method that returns an object that represents the object being
queried using some other (typically simpler) object. The most well-known example is "String toString(),"
to which Name adds the "String asString()"
and "String asStringWith(char d)" methods. The "asStringWith"
method represents the name object with the d character as the delimiting character for name components.
Each method type comes with its own naming convention. Getters typically have
a "get" prefix (or none at all) followed by
the field being asked for. Boolean query methods start with prefixes like
"is," "has," or "may," followed by an indication
of the flag being queried. Conversion methods start with "to" or
"as," followed by the class name of the object being used
as the representation. The method-type catalog lists these conventions.
What types of mutation methods are there? Listing 3 shows the set of mutation methods from the
We can distinguish between set methods and command methods.
The catalog of method types lists aliases for the names of these method types and describes the naming
conventions associated with them. For example, setters either have a set prefix (or none), followed by
the name of the field to be updated. Command methods typically have an active verb form to indicate what
they are doing.
- Set method (or setter for short). A set method is a mutation method that sets a (logical) field of an object
to some value. The field may be a single technical field or it may be mapped on several fields. An example is the
"void component(int i, String c)" method that sets the name component at
index i to the value c.
Setters are typically directed at exactly one field rather than many.
- Command method. A command method is a mutation method that executes a complex change to the object's state, typically
involving several fields and affecting further related objects. Examples of command methods are
"void append(String)" and "void insert(int, String)."
In addition to setter and command methods, we need to take a look at the VectorName class to see a
third mutation method type. Listing 4 shows the constructors of VectorName
and the "void initialize(String, char, char)"
method for initializing a VectorName object from the constructor parameters.
The "void initialize(String, char, char)" method is a protected method used to
initialize a VectorName object from a set
of parameters. The first parameter is a string that contains all name components, properly separated by
delimiter chars. The second parameter is the delimiter char
itself. In the string, regular occurrences of the
delimiter char are masked through an escape char,
which is the third parameter.
For example, a name object for "java.lang.String" requires you to call initialize
with the following parameters:
"java.lang.String," ".," "\" (the escape
char isn't really needed in this specific case).
You could also initialize the name object with the following initialize parameters:
"java#lang#String", "#," "\". The general delimiter + escape
char scheme lets you input any type of string without running into problems with reserved
The "initialize" method parses the name parameter according to the delimiter and escape
chars and initializes the
name object accordingly. The algorithm in Listing 4 breaks up the name parameter into its name components and puts
them into a Vector. The "initialize" method is called directly or indirectly from each of the three constructors
"VectorName()," "VectorName(String)," and
"VectorName(String, char, char)." The first two constructors are convenience
constructors that assume default parameters for the "initialize" method.
Hence, we have a third type of method: initialization method.
A field is initialized lazily (lazy initialization), if its initialization is deferred until it is first accessed.
If this idiom is used a lot, the main initialization methods tend to initialize only a few fields, but not all.
- Initialization method. An initialization method is a mutation method that sets some or all fields of an
object to an initial value. Initialization methods are typically protected methods in support of public
constructors, but they may also be opened up to special clients for reinitializing an existing object.
Initialization methods are typically prefixed with "init" or called
The third main category of method types is helper methods. Helper methods perform some helper task for the calling
client, but typically do not change the state of the object they are part of (at least, this is not their main duty).
Frequently, helper methods are static methods found in helper packages. Helper methods are also called utility methods,
and helper packages are also called utility ("util") packages. For examples, consider Listings 5
and 6 (Listings 5-9 are available online in the code section at
The "VectorName(Vector)" constructor creates a new
Vector and asks the helper method "copy(Vector, Vector)"
of the VectorHelper class to copy the constructor parameter into the "fComponents" Vector. Such a task
is a typical duty of a helper method.
Another example is the static "String nextString(String name, char delimiter, char escape, IntPair ip)" method of
the StringHelper class hinted at in the initialize method of Listing 4.
Its purpose is to parse the name parameter for the next name component using the delimiter and escape chars,
and to advance the parse position in the IntPair ip
to continue with the next name component when it's called again.
Next to such general helper methods, there are two important types of helper methods that deserve special attention.
We already mentioned the factory method "NameEnumeration components()"
from the Name interface.
Factory methods are sometimes prefixed with "create," "make," or
"new," followed by the product name.
- Factory method. A factory method is a helper method that creates an object and returns it to the client.
The second important type of helper method is the assertion method. To understand this type of method, consider Listings 7 and
Listing 7 shows how the "void remove(int i)" method of
VectorName calls the "void assertIsValidIndex(int i)"
method to ensure that the index parameter i is in the range of valid indices.
Only if this precondition is fulfilled, does the remove method work properly. Otherwise, an exception is thrown back
to the client that called the remove method. The "assertIsValidIndex" method is an assertion method.
Assertion methods are part of the larger design-by-contract scheme of designing interfaces. They are used to ensure at
runtime that preconditions and postconditions of methods and invariants of classes are maintained.
- Assertion method. An assertion method is a helper method that checks whether a certain condition holds.
If the condition holds, it returns silently. If it does not hold, an appropriate exception is thrown.
Exceptions to the Rule:
Methods With Several Purposes
You may wonder how well the distinction between method types works in practice. In my experience, it works about
90% of the time, which is good enough to think of methods as being of a specific type. The remaining 10%
of methods mix different purposes.
Consider a get method that logs the access to the field it provides. For example, "String component(int)" can be
implemented to increment a counter each time it is called, so that a user can ask the name object how often
the component method has been called using some other get method. Is the "String component(int)"
method a query or a mutation method?
The primary purpose of this method is to return information about the object, so it is a query method,
with a mutation method aspect thrown in. It is obvious that the clean conceptual scheme discussed above
does not work nicely in this situation. Still the query method aspect is dominating the mutation method
aspect, which justifies calling it a query method. From a practical perspective, you simply need to add
to the method documentation that this method is a query method that increments a counter as a side-effect.
Another well-known example is the "Object nextElement()" method of Enumeration for iterating over a collection.
This method does three things: It first marks the current element, then advances the position pointer, and
finally returns the former current element to the client. Again, this method has both a query and a mutation
aspect to it. In my opinion, the query aspect is stronger than the mutation aspect, so I think it is a query method.
From a design perspective, it is cleaner to have two separate methods: one that returns the current element and
another one that advances the position pointer. From a practical perspective, you can merge these two methods
if you know what you are doing. And in fact, iterating over a collection is such a well-known idiom that
every seasoned Java developer knows what he or she is doing when calling "nextElement()."
Yet another common idiom is lazy initialization, as discussed above under initialization methods. Here,
a get method is also a hidden initialization method. Again, the main purpose of the method is to return a
field's value, which is implemented by initializing the field in the first place before returning its value.
Whenever I face a situation of mixing different method types in one method, I think hard about whether it isn't
better to split up the method into two. Most of the time, it is. If not, I don't worry too much either.
Methods are there for us to make life easier, not to make it more complicated because some abstract scheme tells us so.
What Have We Gained?
Armed with the vocabulary of method types, we can both better implement and document the code. For
example, we can introduce a Javadoc tag @methodtype and then provide the name of the method's type.
This tag helps concisely convey information about the method being documented, much more effectively
than a verbose description could do. Listing 9 presents some sample annotations.
In a future article, we'll add to the annotations of Listing 9 method properties such as "convenience method"
or "template method" that concisely document how a method is to be used and how it is implemented.
We've provided a vocabulary to more effectively talk about methods in Java interfaces and classes. We've provided
the most common method types and given them a precise definition. If you feel that we've left out some key
method types, let us know. If you know further aliases for the names of the method types provided here,
also let me know. You can find a growing catalog of these and other method types as well as the source
code of the examples at www.riehle.org/java-report.
I would like to thank my colleagues Dirk Bäumer, Daniel Megert, Wolf Siberski, and Hans Wegener for
their comments. The method type names are taken from several different sources: Arnold and Gosling's
Java, the Programming Language, Kent Beck's Smalltalk Best Practice Patterns, the Gang-of-Four's Design Patterns.
and, most importantly, from common knowledge and use.
- Arnold, K. and J. Gosling, The Java Programming Language. Addison-Wesley, 1996.
- Beck, K., Smalltalk Best Practice Patterns. Prentice Hall, 1996.
- Gamma, E., et al., Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995.
- Meyer, B., Object-Oriented Software Construction, 2nd Edition, Prentice Hall, 1998.
- Riehle, D. and E. Dubach, "Working With Java Interfaces and Classes: How to Separate Interfaces From Implementations," Java Report, Vol. 4, No. 7, July 1999, pp 35-46.
- Riehle, D. and E. Dubach, "Working with Java Interfaces and Classes: How to maximize design and
code reuse in the face of inheritance," Java Report, Vol. 4, No. 10, Oct. 1999, pp 34-46.