Functions in Java
Learn how to define and call methods in Java - parameters, return values, overloading, recursion, varargs, and higher-order functions with Docker-ready examples
In Java, the unit of reusable behavior is the method. Because Java is an object-oriented, class-based language, there are no free-standing functions the way there are in C or Python — every method lives inside a class. The methods you have already used, like main, are just the most visible example. Once you understand how to define your own methods, you can break a program into small, named, reusable pieces.
This tutorial focuses on static methods, which belong to the class itself rather than to any particular object. Static methods are the closest Java equivalent to the plain “functions” you find in other languages, which makes them the natural starting point. You call them directly by name within the same class, without needing to create an instance first — exactly how main is able to call other helpers.
You will learn how to define methods with parameters and return values, how Java uses method overloading instead of default parameters, how methods call themselves through recursion, how to accept a variable number of arguments with varargs, and how Java’s functional interfaces let you pass behavior around as higher-order functions.
Defining and Calling Methods
A method declaration names the method, lists its parameters with their types, and declares the type of value it returns. Java is statically and strongly typed, so every parameter and the return value must have an explicit type, and the compiler checks every call against that signature. A method that returns nothing uses the void keyword.
The example below defines several methods and calls them from main. Notice that each method sits beside main inside the same class, and that the return type comes before the method name.
Create a file named Functions.java:
| |
Parameters and Return Values
The add method declares two int parameters and an int return type. Java passes arguments by value: the method receives a copy of each argument, so reassigning a parameter inside the method never affects the caller’s variable. (For object references the reference is copied, so the method can still mutate the object it points to — but the caller’s variable still points to the same object afterward.)
The return statement both hands a value back to the caller and ends the method immediately. A void method like greet has no return type to satisfy, so it can simply fall off the end, or use a bare return; to exit early.
Overloading Instead of Default Parameters
Java does not have default parameter values the way Python or C# do. Instead, it relies on method overloading: you define several methods with the same name but different parameter lists, and the compiler chooses which one to call based on the arguments you supply. The two multiply methods above show this — one takes two arguments, the other takes three. This is resolved entirely at compile time.
Recursion
A recursive method calls itself with a smaller input until it reaches a base case that stops the recursion. In factorial, the base case is n <= 1, which returns 1 without recursing further. Every other call multiplies n by the factorial of n - 1. Each call adds a frame to the call stack, so very deep recursion can exhaust the stack — but for a problem like factorial it is clear and concise.
Varargs and Higher-Order Methods
The sum method uses varargs (int... numbers), which lets the caller pass any number of arguments; inside the method they arrive as an array you can loop over. The applyTwice method is a higher-order method: it accepts a Function<Integer, Integer> and a value, then applies that function twice. At the call site we supply the function as a lambda expression, x -> x + 10, a feature added in Java 8 that makes treating behavior as data far more natural than the older anonymous-class syntax.
Running with Docker
| |
Expected Output
add(7, 5) = 12
Hello, Ada!
multiply(3, 4) = 12
multiply(3, 4, 5) = 60
factorial(5) = 120
sum(1, 2, 3, 4) = 10
applyTwice(x -> x + 10, 5) = 25
Key Concepts
- Methods live inside classes — Java has no top-level functions; every method belongs to a class, and
staticmethods belong to the class itself rather than to an instance. - Explicit types everywhere — Java’s static, strong typing requires a declared type for every parameter and return value, and the compiler verifies each call against the method’s signature.
- Arguments are passed by value — methods receive copies of their arguments; for object references the reference is copied, so the object can be mutated but the caller’s variable is never reassigned.
- Overloading replaces default parameters — define multiple methods with the same name and different parameter lists, and the compiler selects the right one at compile time.
- Recursion needs a base case — a method that calls itself must have a stopping condition, or it will recurse until the call stack overflows.
- Varargs handle flexible argument counts —
Type... namecollects extra arguments into an array the method can iterate over. - Methods can take methods — functional interfaces like
Function<T, R>plus lambda expressions let you pass behavior as data, enabling higher-order programming since Java 8. - Scope is block-based — a variable declared inside a method (like
totalinsum) is local to that method and invisible to the rest of the program.
Running Today
All examples can be run using Docker:
docker pull eclipse-temurin:21-jdk
Comments
Loading comments...
Leave a Comment