blog.itcode.devblog.itcode.dev

[OOP] Characteristics of Object Orientation - Polymorphism

Object-oriented languages don't allow methods with the same name. For example, suppose there's a method implementing the behavior "eat." There's no need to implement the same "eat" behavior twice. From this perspective, it makes sense that method names would need to be unique, since a method's name could even be considered its own identity. But thinking about it a bit more, this seems strange. Java lives and dies by types. Unlike JavaScript, you can't put just any type into a parameter—if you pass in anything other than the specified type, it gets ruthlessly rejected at compile time.

[OOP] Characteristics of Object Orientation - Polymorphism

Object-oriented languages don't allow methods with the same name. For example, suppose there's a method implementing the behavior "eat." There's no need to implement the same "eat" behavior twice. From this perspective, it makes sense that method names would need to be unique, since a method's name could even be considered its own identity. But thinking about it a bit more, this seems strange. Java lives and dies by types. Unlike JavaScript, you can't put just any type into a parameter—if you pass in anything other than the specified type, it gets ruthlessly rejected at compile time.
RWB0104
@RWBwritten at 2021-08-11 15:32:42
Object-Oriented Programming

시리즈 모아보기

Object-Oriented Programming

4 / 9

Object-oriented languages don't allow methods with the same name. For example, suppose there's a method implementing the behavior "eat." There's no need to implement the same "eat" behavior twice. From this perspective, it makes sense that method names would need to be unique, since a method's name could even be considered its own identity.

But thinking about it a bit more, this seems strange. Java lives and dies by types. Unlike JavaScript, you can't put just any type into a parameter—if you pass in anything other than the specified type, it gets ruthlessly rejected at compile time.

One for One!
A single parameter can only ever have a single type. public void run(String param) can only ever accept a string as its parameter.

That means the same method can't exist twice, so the parameter type accepted by a given method is also fixed to just one. But take a look at the System.out.println() method. It's the method used to print data to the CLI console, and anyone who's worked with Java has used it at some point.

JAVA

public class Main
{
	public static void main(String[] args)
	{
		System.out.println("문자열 데이터");
		System.out.println(123456);
		System.out.println(true);
	}
}

OUT

문자열 데이터
123456
true

Hold on, didn't you just say methods with the same name can't exist?;;


Sure enough, earlier we said methods with the same name can't exist within the same object. And yet, System.out.println() has the same method name while openly accepting and handling several different types. What's going on here? Does some exception apply just because it's a famous method?

The reason System.out.println() can handle multiple types is that polymorphism is applied to this method. Polymorphism means a single object or method can refer to multiple types. Polymorphism is broadly divided into object polymorphism and method polymorphism.

Let's explore polymorphism through code.

First, let's look at polymorphism as it applies to objects. For objects, polymorphism can be applied when creating an instance of an inherited object.

Object polymorphism means that an object can be assigned as an instance of the parent object it inherits from.

JAVA

class TV
{
	// 메소드
}

class SmartTV extends TV
{
	// 메소드
}

Suppose we have two objects like the ones above. SmartTV is an object implemented by inheriting from TV. In this case, polymorphism can be applied to SmartTV.

JAVA

public class Main
{
	public static void main(String[] args)
	{
		// 객체와 인스턴스 타입 일치
		TV tv = new TV();

		// 객체와 인스턴스 타입 일치
		SmartTV smart = new SmartTV();

		// SmartTV는 TV의 자식 객체이므로 다형성이 적용되어 허용
		TV tv2 = new SmartTV();

		// 불가능
		SmartTV smart2 = new TV();
	}
}

The other lines are obvious, so let's not worry about them, and take a close look at line 12. Even though TV and SmartTV are distinctly different objects, the instance is created without any issue.

This is the result of object polymorphism. SmartTV is an object built by inheriting from TV. In other words, since SmartTV fully includes everything TV has, it can be created as an instance of TV. This kind of object polymorphism applies not only when inheriting from an object, but also when inheriting from an interface.

There's one thing to watch out for when working with object polymorphism. Above, we created a SmartTV object as a TV. We understand that this "polymorphism thing" is why the instance gets created successfully—but how does this instance, which is somehow both TV-like and SmartTV-like, actually behave?

The instance tv2 created this way can only use the methods declared in SmartTV that also match methods declared in TV.

JAVA

interface Movable
{
	void move(boolean direction);
}

class Unit implements Movable
{
	@Override
	public void move(boolean direction)
	{
		// 동작
	}

	public void work(String act)
	{
		// 동작
	}
}

This time, let's use an interface as an example. Here's an interface, Movable, describing movement-related behavior, and a Unit object that inherits from it.

JAVA

public class Main
{
	public static void main(String[] args)
	{
		Movable movable = new Unit();

		// Movable에 존재하는 메소드이므로 호출 가능
		movable.move(true);

		// Movable엔 없는 Unit만의 고유 메소드이므로 호출 불가능
		movable.work("run");
	}
}

Thanks to object polymorphism, you already know that a Unit object can be created as a Movable. Let's create an instance called movable and call the move() and work() methods, respectively.

move() is a method implemented by inheriting from the Movable interface, while work() is a method created directly in Unit. In this case, Unit's methods can be called, but only the methods declared in Movable are actually callable. In other words, only the methods that overlap between the Unit and Movable objects can be called. That said, the method's actual behavior still runs as Unit's implementation.

Using object polymorphism makes it very convenient to work with multiple objects that inherit from the same object.

JAVA

class UnitA implements Movable
{
	@Override
	public void move(boolean direction)
	{
		work("run");
	}

	private void work(String act)
	{
		System.out.println("work: " + act);
	}
}

class UnitB implements Movable
{
	@Override
	public void move(boolean direction)
	{
		doing(3);
	}

	private void doing(int num)
	{
		System.out.println("doing: " + num);
	}
}

class UnitC implements Movable
{
	@Override
	public void move(boolean direction)
	{
		active(true);
	}

	private void active(boolean flag)
	{
		System.out.println("active: " + flag);
	}
}

Suppose we have several objects like the ones above, all inheriting from the same interface, Movable. These objects are each individually distinct, but since they all inherit from Movable, all three can be assigned as Movable instances through polymorphism.

JAVA

public class Main
{
	public static void main(String[] args)
	{
		Movable movable = switch (new Random().nextInt(3))
		{
			case 0 -> new UnitA();
			case 1 -> new UnitB();
			case 2 -> new UnitC();
			default -> null;
		};
		
		movable.move(true);
	}
}

OUTPUT

# 실행 시마다 달라짐
work: run

Each time this runs, an instance is randomly chosen from UnitA, UnitB, or UnitC, and assigned to Movable. Even though these are distinct objects, assigning the instance to the common parent object Movable lets you call their shared method. It's perfectly fine for that shared method, move(), to internally include logic unique to each Unit.

This way, whenever a method's input needs to accept multiple types of parameters, as long as those parameters all inherit from the same object, you can apply polymorphism to treat them as a common type.

Methods can also have polymorphism applied to them. While object polymorphism relates to the object's own type, method polymorphism relates to the types of the parameters the method uses.

Method polymorphism means that even if methods share the same name, if they accept different parameters, they're treated as separate, independent methods.

JAVA

/**
 * 컨버터 클래스
 *
 * @author RWB
 * @since 2021.08.06 Fri 23:46:44
 */
public class Converter
{
	/**
	 * 변환 함수
	 *
	 * @param num: [int] 숫자
	 */
	public void convert(int num)
	{
		System.out.println(new StringBuilder().append("int: ").append(num));
	}
	
	/**
	 * 변환 함수
	 *
	 * @param text: [String] 문자열
	 */
	public void convert(String text)
	{
		System.out.println(new StringBuilder().append("String: ").append(text));
	}
	
	/**
	 * 변환 함수
	 *
	 * @param flag: [boolean] T/F
	 */
	public void convert(boolean flag)
	{
		System.out.println(new StringBuilder().append("boolean: ").append(flag));
	}
	
	/**
	 * 변환 함수
	 *
	 * @param c: [char] 문자
	 */
	public void convert(char c)
	{
		System.out.println(new StringBuilder().append("char: ").append(c));
	}
}

The source code above is the Converter class, and as you can see, all of the methods share the same name, convert. But each method takes different parameters. In this case, polymorphism causes each method to be recognized as an independent method.

The existence of polymorphism helps maintain consistency in code. A prime example is the System.out.println() method we use to print to the console.

JAVA

public void println(float x) {
	if (getClass() == PrintStream.class) {
		writeln(String.valueOf(x));
	} else {
		synchronized (this) {
			print(x);
			newLine();
		}
	}
}

public void println(double x) {
	if (getClass() == PrintStream.class) {
		writeln(String.valueOf(x));
	} else {
		synchronized (this) {
			print(x);
			newLine();
		}
	}
}

public void println(char[] x) {
	if (getClass() == PrintStream.class) {
		writeln(x);
	} else {
		synchronized (this) {
			print(x);
			newLine();
		}
	}
}

public void println(String x) {
	if (getClass() == PrintStream.class) {
		writeln(String.valueOf(x));
	} else {
		synchronized (this) {
			print(x);
			newLine();
		}
	}
}

The source code above is System.out.println()'s internal implementation. As you can see, the name is identical, and even the behavior—printing to the console—is identical, but thanks to polymorphism, each method is recognized as its own complete, independent method.

What would happen if the concept of polymorphism didn't exist? Even though the behavior is the same, just because the parameters differ, you'd have to create methods with similarly awkward names, and developers would have to remember to use the right one for each parameter type.

JAVA

public void printlnFloat(float x) {
	if (getClass() == PrintStream.class) {
		writeln(String.valueOf(x));
	} else {
		synchronized (this) {
			print(x);
			newLine();
		}
	}
}

public void printlnDouble(double x) {
	if (getClass() == PrintStream.class) {
		writeln(String.valueOf(x));
	} else {
		synchronized (this) {
			print(x);
			newLine();
		}
	}
}

public void printlnChar(char[] x) {
	if (getClass() == PrintStream.class) {
		writeln(x);
	} else {
		synchronized (this) {
			print(x);
			newLine();
		}
	}
}

public void printlnString(String x) {
	if (getClass() == PrintStream.class) {
		writeln(String.valueOf(x));
	} else {
		synchronized (this) {
			print(x);
			newLine();
		}
	}
}

In other words, you'd be forced into a design like the one above. When designing code, the same behavior sometimes needs to accept various kinds of objects. Since Java strictly follows the principle of one parameter = one type, unlike JavaScript, a parameter can't accept a variety of different types.

Making good use of polymorphism effectively resolves this problem. By writing methods that share the same name but accept different parameters, you give developers a development experience where they can use it as if it were the same method, without worrying about the type distinction at all.

JAVA

// println(String x)
System.out.println("text");

// println(double x)
System.out.println(1.5D);

As shown above, even when developers use it without distinguishing the types themselves, the appropriate method matching that parameter is automatically called at compile time.

If you're the curious type, you might wonder about a case like this: if there's polymorphism for parameters, wouldn't there also be polymorphism for a method's return type? It's a good thought, but unfortunately, polymorphism is always distinguished solely by parameters. Polymorphism does not apply to return types.

JAVA

public void println(char[] x) {
	if (getClass() == PrintStream.class) {
		writeln(x);
	} else {
		synchronized (this) {
			print(x);
			newLine();
		}
	}
}

public void println(String x) {
	if (getClass() == PrintStream.class) {
		writeln(String.valueOf(x));
	} else {
		synchronized (this) {
			print(x);
			newLine();
		}
	}
}

In the case above, since the parameters are char[] and String respectively, polymorphism applies.

JAVA

public void println(char[] x) {
	if (getClass() == PrintStream.class) {
		writeln(x);
	} else {
		synchronized (this) {
			print(x);
			newLine();
		}
	}
}

public boolean println(char[] x) {
	if (getClass() == PrintStream.class) {
		writeln(x);
	} else {
		synchronized (this) {
			print(x);
			newLine();
		}
	}

	return true;
}

Conversely, in the case above, the method name and parameters are identical, but the return type differs. Unlike parameters, polymorphism doesn't apply to return types, so this is treated as a duplicate method. As a result, the source code above triggers a compile error.

Object polymorphism is focused on productivity. By letting the same method handle multiple types of data, or by handling objects that share a common ancestor, it eliminates redundant code and improves development convenience. Make active use of polymorphism to cut down on duplicate code and broaden the range of data your code can handle.

# CS# Object-Oriented Programming# Polymorphism
ship
blog.itcode.dev

Notes from the π-th Alpaca

7.0.1
Developed by RWB since 2021.057th upgraded at 2026.08