オーバーロードとオーバーライドの違い完全ガイド – Python・Java・C#での実装例

オーバーロードとオーバーライドとは?

オーバーロード(Overload)とオーバーライド(Override)は、オブジェクト指向プログラミングの重要な概念ですが、しばしば混同されがちです。両者の違いを明確に理解することで、より効果的なプログラム設計が可能になります。

基本的な違い

オーバーロード(Overload)

  • 同じクラス内で同じ名前のメソッドを複数定義
  • 引数の型や個数で区別される
  • コンパイル時に決定される(静的ポリモーフィズム)

オーバーライド(Override)

  • 継承関係で親クラスのメソッドを子クラスで再定義
  • メソッド名と引数は同じ
  • 実行時に決定される(動的ポリモーフィズム)

Pythonでの実装例

1. Pythonでのオーバーロード風実装

# Pythonは厳密なオーバーロードをサポートしていない
class Calculator:
    def add(self, *args):
        if len(args) == 2:
            return args[0] + args[1]
        elif len(args) == 3:
            return args[0] + args[1] + args[2]
        else:
            return sum(args)

# 使用例
calc = Calculator()
print(calc.add(1, 2))      # 3
print(calc.add(1, 2, 3))   # 6
print(calc.add(1, 2, 3, 4)) # 10

2. functools.singledispatchを使ったオーバーロード

from functools import singledispatch

@singledispatch
def process(arg):
    print(f"汎用処理: {arg}")

@process.register
def _(arg: int):
    print(f"整数処理: {arg * 2}")

@process.register
def _(arg: str):
    print(f"文字列処理: {arg.upper()}")

# 使用例
process(42)      # 整数処理: 84
process("hello") # 文字列処理: HELLO
process([1,2,3]) # 汎用処理: [1, 2, 3]

3. Pythonでのオーバーライド

class Animal:
    def make_sound(self):
        return "何かの音"
    
    def move(self):
        return "移動する"

class Dog(Animal):
    def make_sound(self):  # オーバーライド
        return "ワンワン"
    
    def move(self):  # オーバーライド
        return "走る"

class Cat(Animal):
    def make_sound(self):  # オーバーライド
        return "ニャーニャー"

# 使用例
animals = [Dog(), Cat(), Animal()]
for animal in animals:
    print(f"{animal.__class__.__name__}: {animal.make_sound()}")

Javaでの実装例

1. Javaでのオーバーロード

public class MathUtils {
    // int型の加算
    public int add(int a, int b) {
        return a + b;
    }
    
    // double型の加算
    public double add(double a, double b) {
        return a + b;
    }
    
    // 3つの引数
    public int add(int a, int b, int c) {
        return a + b + c;
    }
    
    // 配列の加算
    public int add(int[] numbers) {
        int sum = 0;
        for (int num : numbers) {
            sum += num;
        }
        return sum;
    }
}

2. Javaでのオーバーライド

// 親クラス
abstract class Shape {
    protected String name;
    
    public abstract double getArea();
    
    public void display() {
        System.out.println("図形: " + name);
    }
}

// 子クラス
class Circle extends Shape {
    private double radius;
    
    public Circle(double radius) {
        this.name = "円";
        this.radius = radius;
    }
    
    @Override
    public double getArea() {  // オーバーライド
        return Math.PI * radius * radius;
    }
}

class Rectangle extends Shape {
    private double width, height;
    
    public Rectangle(double width, double height) {
        this.name = "長方形";
        this.width = width;
        this.height = height;
    }
    
    @Override
    public double getArea() {  // オーバーライド
        return width * height;
    }
}

C#での実装例

1. C#でのオーバーロード

public class StringProcessor
{
    // 文字列1つを処理
    public string Process(string input)
    {
        return input.ToUpper();
    }
    
    // 文字列と区切り文字を処理
    public string Process(string input, char separator)
    {
        return string.Join(separator.ToString(), input.ToCharArray());
    }
    
    // 文字列配列を処理
    public string Process(string[] inputs)
    {
        return string.Join(" ", inputs);
    }
    
    // 文字列と変換フラグを処理
    public string Process(string input, bool toLower)
    {
        return toLower ? input.ToLower() : input.ToUpper();
    }
}

2. C#でのオーバーライド

// 基底クラス
public abstract class Vehicle
{
    protected string brand;
    
    public Vehicle(string brand)
    {
        this.brand = brand;
    }
    
    public virtual void Start()
    {
        Console.WriteLine($"{brand}のエンジンを始動");
    }
    
    public abstract void Move();
}

// 派生クラス
public class Car : Vehicle
{
    public Car(string brand) : base(brand) { }
    
    public override void Start()  // オーバーライド
    {
        Console.WriteLine($"{brand}の車のエンジンを始動");
    }
    
    public override void Move()  // オーバーライド
    {
        Console.WriteLine("道路を走行");
    }
}

public class Airplane : Vehicle
{
    public Airplane(string brand) : base(brand) { }
    
    public override void Move()  // オーバーライド
    {
        Console.WriteLine("空を飛行");
    }
}

実践的な使い分け

1. オーバーロードが適している場面

# ログ出力の例(Python風)
class Logger:
    def log(self, message):
        print(f"INFO: {message}")
    
    def log(self, level, message):
        print(f"{level}: {message}")
    
    def log(self, level, message, timestamp):
        print(f"[{timestamp}] {level}: {message}")

# 実際のPython実装
class Logger:
    def log(self, *args):
        if len(args) == 1:
            print(f"INFO: {args[0]}")
        elif len(args) == 2:
            print(f"{args[0]}: {args[1]}")
        elif len(args) == 3:
            print(f"[{args[2]}] {args[0]}: {args[1]}")

2. オーバーライドが適している場面

# 支払い処理の例
class PaymentProcessor:
    def process_payment(self, amount):
        raise NotImplementedError("子クラスで実装してください")
    
    def validate_amount(self, amount):
        return amount > 0

class CreditCardProcessor(PaymentProcessor):
    def process_payment(self, amount):  # オーバーライド
        if not self.validate_amount(amount):
            return False
        print(f"クレジットカードで{amount}円を決済")
        return True

class PayPalProcessor(PaymentProcessor):
    def process_payment(self, amount):  # オーバーライド
        if not self.validate_amount(amount):
            return False
        print(f"PayPalで{amount}円を決済")
        return True

# 使用例
processors = [CreditCardProcessor(), PayPalProcessor()]
for processor in processors:
    processor.process_payment(1000)

デザインパターンでの活用

1. Strategyパターンでのオーバーライド

from abc import ABC, abstractmethod

class SortStrategy(ABC):
    @abstractmethod
    def sort(self, data):
        pass

class BubbleSort(SortStrategy):
    def sort(self, data):  # オーバーライド
        print("バブルソートで並び替え")
        return sorted(data)  # 簡略化

class QuickSort(SortStrategy):
    def sort(self, data):  # オーバーライド
        print("クイックソートで並び替え")
        return sorted(data)  # 簡略化

class Sorter:
    def __init__(self, strategy: SortStrategy):
        self.strategy = strategy
    
    def sort_data(self, data):
        return self.strategy.sort(data)

# 使用例
data = [3, 1, 4, 1, 5, 9, 2, 6]
sorter = Sorter(QuickSort())
result = sorter.sort_data(data)

2. Factoryパターンでのオーバーロード風実装

class ShapeFactory:
    @staticmethod
    def create_shape(shape_type, *args):
        if shape_type == "circle" and len(args) == 1:
            return Circle(args[0])
        elif shape_type == "rectangle" and len(args) == 2:
            return Rectangle(args[0], args[1])
        elif shape_type == "triangle" and len(args) == 3:
            return Triangle(args[0], args[1], args[2])
        else:
            raise ValueError("不正な図形タイプまたは引数")

class Circle:
    def __init__(self, radius):
        self.radius = radius

# 使用例
circle = ShapeFactory.create_shape("circle", 5)
rectangle = ShapeFactory.create_shape("rectangle", 4, 6)

よくある間違いと対処法

1. Pythonでのオーバーロードの誤解

# 間違った例:最後の定義だけが有効
class BadExample:
    def method(self, a):
        return f"引数1つ: {a}"
    
    def method(self, a, b):  # 前の定義を上書き
        return f"引数2つ: {a}, {b}"

# 正しい例:可変長引数を使用
class GoodExample:
    def method(self, *args):
        if len(args) == 1:
            return f"引数1つ: {args[0]}"
        elif len(args) == 2:
            return f"引数2つ: {args[0]}, {args[1]}"
        else:
            return f"引数{len(args)}個"

2. オーバーライド時のsuper()の忘れ

class Parent:
    def __init__(self, name):
        self.name = name
        print(f"親クラス初期化: {name}")

class Child(Parent):
    def __init__(self, name, age):
        super().__init__(name)  # 重要:親の初期化を呼ぶ
        self.age = age
        print(f"子クラス初期化: {age}歳")

# 使用例
child = Child("太郎", 10)

3. 型ヒントの活用

from typing import Union, overload

class Calculator:
    @overload
    def add(self, x: int, y: int) -> int: ...
    
    @overload
    def add(self, x: float, y: float) -> float: ...
    
    @overload
    def add(self, x: str, y: str) -> str: ...
    
    def add(self, x: Union[int, float, str], y: Union[int, float, str]):
        return x + y

# 使用例
calc = Calculator()
print(calc.add(1, 2))        # int
print(calc.add(1.5, 2.5))    # float
print(calc.add("Hello", " World"))  # str

パフォーマンスの考慮事項

1. オーバーロードのパフォーマンス

import timeit

# 型チェック版(遅い)
def add_with_typecheck(a, b):
    if isinstance(a, int) and isinstance(b, int):
        return a + b
    elif isinstance(a, float) and isinstance(b, float):
        return a + b
    else:
        return str(a) + str(b)

# シンプル版(速い)
def add_simple(a, b):
    return a + b

# パフォーマンス比較
time1 = timeit.timeit(lambda: add_with_typecheck(1, 2), number=100000)
time2 = timeit.timeit(lambda: add_simple(1, 2), number=100000)

print(f"型チェック版: {time1:.6f}秒")
print(f"シンプル版: {time2:.6f}秒")

2. オーバーライドの最適化

class OptimizedBase:
    __slots__ = ['name']  # メモリ使用量を削減
    
    def __init__(self, name):
        self.name = name
    
    def process(self):
        return f"基本処理: {self.name}"

class OptimizedChild(OptimizedBase):
    __slots__ = ['value']
    
    def __init__(self, name, value):
        super().__init__(name)
        self.value = value
    
    def process(self):  # オーバーライド
        return f"拡張処理: {self.name}, 値: {self.value}"

テストとデバッグ

1. オーバーロードのテスト

import unittest

class TestCalculator(unittest.TestCase):
    def setUp(self):
        self.calc = Calculator()
    
    def test_add_integers(self):
        result = self.calc.add(1, 2)
        self.assertEqual(result, 3)
        self.assertIsInstance(result, int)
    
    def test_add_floats(self):
        result = self.calc.add(1.5, 2.5)
        self.assertEqual(result, 4.0)
        self.assertIsInstance(result, float)
    
    def test_add_strings(self):
        result = self.calc.add("Hello", " World")
        self.assertEqual(result, "Hello World")
        self.assertIsInstance(result, str)

2. オーバーライドのテスト

class TestShapes(unittest.TestCase):
    def test_polymorphism(self):
        shapes = [Circle(5), Rectangle(4, 6)]
        expected_areas = [78.54, 24]  # 概算値
        
        for i, shape in enumerate(shapes):
            area = shape.get_area()
            self.assertAlmostEqual(area, expected_areas[i], places=1)

まとめ

オーバーロードとオーバーライドの適切な使い分け:

オーバーロードを使う場面

  • 同じ処理を異なる引数で実行したい
  • API の使いやすさを向上させたい
  • 型安全性を保ちたい

オーバーライドを使う場面

  • 継承関係で動作を変更したい
  • ポリモーフィズムを実現したい
  • 共通インターフェースで異なる実装を提供したい

両者を適切に活用することで、保守性が高く拡張しやすいオブジェクト指向プログラムが作成できます。

らくらくPython塾 – 読むだけでマスター

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座