6.2 Variables and Methods
In the object-oriented paradigm, variables and methods are the core building blocks of a class. The type of variable changes depending on where it is declared, and methods are code blocks responsible for specific functionality.
1. Types of Variables by Declaration Location
Variables are divided into three types based on where they are declared: class variables, instance variables, and local variables.
1.1 Class Variable (Static Variable)
- Declaration: Declared in the class body with the
statickeyword - Characteristic: All instances share a single storage space
- Access: Can be accessed without an instance using
ClassName.variableName - Lifetime: Created when the program starts → destroyed when the program ends
- Use case: Values that all objects must share (e.g., interest rate, card size)
1.2 Instance Variable
- Declaration: Declared in the class body without
static - Characteristic: Created independently for each instance
- Access: Accessed via
referenceVariable.variableName - Lifetime: Created when instance is created → destroyed when instance is garbage-collected
- Use case: Values unique to each object (e.g., card suit, card number)
1.3 Local Variable
- Declaration: Declared inside a method, constructor, or block (
{}) - Characteristic: Only valid within that block, automatically destroyed when the block ends
- Default value: No automatic initialization — must be initialized manually before use
- Storage: Stack memory
public class Card {
// === Instance variables (different for each card) ===
String kind; // suit (Spades, Hearts, Diamonds, Clubs)
int number; // number (1~13)
// === Class variables (shared by all cards) ===
static int width = 100; // width (mm)
static int height = 250; // height (mm)
void printCard() {
// === Local variable (valid only inside this method) ===
String display = kind + number;
System.out.println("Card: " + display);
// display disappears when the method ends
}
}
public class CardTest {
public static void main(String[] args) {
// Class variable: accessible via class name without an instance
System.out.println("Card width: " + Card.width); // 100
System.out.println("Card height: " + Card.height); // 250
Card c1 = new Card();
Card c2 = new Card();
c1.kind = "Spades"; c1.number = 1;
c2.kind = "Hearts"; c2.number = 13;
// Instance variable: independent for each object
System.out.println(c1.kind + c1.number); // Spades1
System.out.println(c2.kind + c2.number); // Hearts13
// Changing a class variable is reflected across all instances
Card.width = 120;
System.out.println(c1.width); // 120 (shared!)
System.out.println(c2.width); // 120 (shared!)
}
}
2. What Is a Method?
A method is a block of code that performs a specific task, given a name so it can be reused at any time.
Why Use Methods?
- Reusability: Write code once and reuse it in multiple places
- Eliminate duplication: No need to write the same logic multiple times
- Organization: Keeps
mainclean and separates logic - Maintenance: Updating one method updates the entire feature
3. Method Declaration Syntax
accessModifier returnType methodName(parameter1, parameter2, ...) {
// method body (code to execute)
return returnValue; // can be omitted if returnType is void
}
// void method: no return value
public void printGreeting(String name) {
System.out.println("Hello, " + name + "!");
// return; // void can omit return (or use empty return for early exit)
}
// Method with a return value
public int add(int a, int b) {
return a + b; // must return an int value
}
// Conditional return (every code path must have a return)
public String grade(int score) {
if (score >= 90) return "A";
else if (score >= 80) return "B";
else if (score >= 70) return "C";
else return "F";
}
4. Parameter vs Argument
The two terms are often used interchangeably, but they are technically different.
| Term | Description | Example |
|---|---|---|
| Parameter | Defined in method declaration parentheses | int add(int a, int b) — a, b are parameters |
| Argument | Actual values passed when calling the method | add(3, 5) — 3, 5 are arguments |
// Parameters: x, y
public double calculateDistance(double x, double y) {
return Math.sqrt(x * x + y * y);
}
// Arguments: 3.0, 4.0
double distance = calculateDistance(3.0, 4.0); // 5.0
5. Call by Value
Java always uses Call by Value only. However, the behavior differs between primitive and reference types.
Primitive Types: Copy of Value Is Passed
public class CallByValueExample {
static void doubleValue(int x) {
x = x * 2; // modifies the copy -> no effect on original
System.out.println("x inside method: " + x); // 20
}
public static void main(String[] args) {
int num = 10;
doubleValue(num);
System.out.println("num after method call: " + num); // 10 (unchanged!)
}
}
Reference Types: Copy of Address Is Passed
public class CallByReferenceExample {
static void changeBrand(Car car) {
car.brand = "Kia"; // modifies the field of the same object via address -> affects original!
}
static void replaceCar(Car car) {
car = new Car(); // local variable car now points to a new object -> no effect on original variable
car.brand = "BMW";
}
public static void main(String[] args) {
Car myCar = new Car();
myCar.brand = "Hyundai";
changeBrand(myCar);
System.out.println(myCar.brand); // Kia (field value was changed)
replaceCar(myCar);
System.out.println(myCar.brand); // Kia (the reference itself was not changed)
}
}
When a reference type argument is passed to a method, modifying the object's fields inside the method does affect the original. However, reassigning the reference variable to a different object does not change the original reference.
6. Recursive Methods
When a method calls itself, it is called recursion.
Factorial
public class RecursionExample {
// n! = n x (n-1) x ... x 2 x 1
static long factorial(int n) {
if (n <= 1) return 1; // Base Case — absolutely required!
return n * factorial(n - 1); // recursive call
}
// Fibonacci sequence (naive recursion — slow performance, for concept learning)
static int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
public static void main(String[] args) {
System.out.println("5! = " + factorial(5)); // 120
System.out.println("10! = " + factorial(10)); // 3628800
System.out.print("Fibonacci sequence: ");
for (int i = 0; i < 10; i++) {
System.out.print(fibonacci(i) + " ");
}
// 0 1 1 2 3 5 8 13 21 34
}
}
Every recursive call adds a stack frame. Without a Base Case, or with a wrong one, infinite recursion occurs and throws a StackOverflowError. Always set a clear termination condition.
Recursion vs Iteration
// Recursive style (intuitive but uses more memory)
static long factorialRecursive(int n) {
if (n <= 1) return 1;
return n * factorialRecursive(n - 1);
}
// Iterative style (better performance, suitable for large data)
static long factorialIterative(int n) {
long result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
7. static Methods vs Instance Methods
| Aspect | static Method | Instance Method |
|---|---|---|
| How to call | ClassName.methodName() | referenceVariable.methodName() |
| Instance required | No | Yes |
| Member variable access | Class variables only | All member variables |
this usage | Not allowed | Allowed |
| Use case | Utility, conversion, calculation | Methods that use/modify object state |
public class MathUtils {
// static method: can be called without an instance
public static int max(int a, int b) {
return a > b ? a : b;
}
public static double circleArea(double radius) {
return Math.PI * radius * radius;
}
}
public class Counter {
private int count = 0; // instance variable
// Instance method: uses the instance variable
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class MethodTypeExample {
public static void main(String[] args) {
// static method: call directly without creating an instance
System.out.println(MathUtils.max(10, 20)); // 20
System.out.println(MathUtils.circleArea(5.0)); // 78.53...
// Instance method: must create an object first
Counter counter = new Counter();
counter.increment();
counter.increment();
counter.increment();
System.out.println(counter.getCount()); // 3
}
}
8. Practical Example: Calculator Class
Implementing a complete calculator class.
public class Calculator {
// Remembers the last calculation result (instance variable)
private double lastResult;
// Operation count (class variable — shared by all Calculator instances)
private static int operationCount = 0;
// Addition
public double add(double a, double b) {
lastResult = a + b;
operationCount++;
return lastResult;
}
// Subtraction
public double subtract(double a, double b) {
lastResult = a - b;
operationCount++;
return lastResult;
}
// Multiplication
public double multiply(double a, double b) {
lastResult = a * b;
operationCount++;
return lastResult;
}
// Division
public double divide(double a, double b) {
if (b == 0) {
System.out.println("Error: Cannot divide by zero!");
return Double.NaN;
}
lastResult = a / b;
operationCount++;
return lastResult;
}
// Modulo
public double modulo(double a, double b) {
if (b == 0) {
System.out.println("Error: Cannot divide by zero!");
return Double.NaN;
}
lastResult = a % b;
operationCount++;
return lastResult;
}
// Power
public double power(double base, double exponent) {
lastResult = Math.pow(base, exponent);
operationCount++;
return lastResult;
}
// Square root
public double sqrt(double a) {
if (a < 0) {
System.out.println("Error: Cannot compute square root of a negative number!");
return Double.NaN;
}
lastResult = Math.sqrt(a);
operationCount++;
return lastResult;
}
// Return last result
public double getLastResult() {
return lastResult;
}
// Return total operation count (static method)
public static int getOperationCount() {
return operationCount;
}
// Reset calculator
public void reset() {
lastResult = 0;
System.out.println("Calculator has been reset.");
}
}
public class CalculatorTest {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println("=== Calculator Test ===");
System.out.printf("10 + 5 = %.1f%n", calc.add(10, 5)); // 15.0
System.out.printf("10 - 3 = %.1f%n", calc.subtract(10, 3)); // 7.0
System.out.printf("4 x 6 = %.1f%n", calc.multiply(4, 6)); // 24.0
System.out.printf("15 / 4 = %.2f%n", calc.divide(15, 4)); // 3.75
System.out.printf("17 %% 5 = %.1f%n", calc.modulo(17, 5)); // 2.0
System.out.printf("2^10 = %.0f%n", calc.power(2, 10)); // 1024
System.out.printf("sqrt(144) = %.1f%n", calc.sqrt(144)); // 12.0
System.out.println("Last result: " + calc.getLastResult()); // 12.0
System.out.println("Total operations: " + Calculator.getOperationCount()); // 7
// Error case test
calc.divide(10, 0); // prints error message
}
}
Output:
=== Calculator Test ===
10 + 5 = 15.0
10 - 3 = 7.0
4 x 6 = 24.0
15 / 4 = 3.75
17 % 5 = 2.0
2^10 = 1024
sqrt(144) = 12.0
Last result: 12.0
Total operations: 7
Error: Cannot divide by zero!
9. Method Writing Guidelines
- Single responsibility: One method does one thing only.
- Keep it short: Aim for 20 lines or fewer.
- Clear name: Start with a verb and make the function obvious (
calculateTax(),getUserById()). - Minimize parameters: If there are too many parameters, consider bundling them into an object.
- Handle exceptions: Validate input values for safe handling.
Summary
| Concept | Key Content |
|---|---|
| Class variable | Declared with static, shared by all instances |
| Instance variable | Independent for each object, stored on heap |
| Local variable | Declared inside method/block, no automatic initialization |
| void method | No return value |
| Call by Value | Primitives pass a copy of value; reference types pass a copy of address |
| Recursion | Base case is required; watch for StackOverflowError |
| static method | Can be called without an instance; can only access class variables |
| Instance method | Called after creating an object; can access all member variables |