04-Java工程师的Python第四课-面向对象编程

1 阅读7分钟

类、继承、多态:Python的OOP与Java有哪些不同

摘要:Python和Java都是面向对象语言,但设计理念不同。Java强调类型安全和单继承,Python更灵活,支持多继承。


写在前面

作为一个Java工程师,你一定对OOP(面向对象编程)了如指掌。但用Java的思维去写Python时,会遇到很多"奇怪"的问题:

  • 为什么Python的类变量会被实例修改?
  • 为什么子类的方法会覆盖父类的方法?
  • Python居然支持多继承?

这篇文章帮你建立Python的OOP思维,避免常见的陷阱。


一、类定义对比

1.1 基本语法

// Java - 一切皆需声明
public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void sayHello() {
        System.out.println("Hello, I'm " + name);
    }
}
# Python - 简洁太多
class Person:
    def __init__(self, name: str, age: int):
        self.name = name  # self相当于Java的this
        self.age = age

    def say_hello(self):
        print(f"Hello, I'm {self.name}")

# 创建实例
p = Person("Alice", 30)
p.say_hello()

1.2 访问控制对比

JavaPython说明
public无修饰符公开访问
private__双下划线名称改写(name mangling)
protected_单下划线约定不可外部访问
final无直接对应常量用全大写约定
# Python 访问控制 - 纯约定,没有强制
class Person:
    def __init__(self, name, age):
        self.name = name          # 公开属性
        self._age = age           # 约定:受保护
        self.__secret = "data"    # 名称改写为 _Person__secret

p = Person("Alice", 30)
print(p.name)      # Alice - 可访问
print(p._age)      # 30 - 不推荐但可访问
# print(p.__secret)  # AttributeError - 实际被改名为 _Person__secret
print(p._Person__secret)  # data - 硬编码可访问,但不推荐
// Java - 真正的访问控制
public class Person {
    private String secret = "data";  // 真正无法外部访问
}

1.3 属性与方法对比

特性JavaPython
实例变量在构造器/类中声明__init__中用self.变量名
类变量static变量类体内直接定义
实例方法普通方法普通方法,第一个参数是self
静态方法@Staticmethod@staticmethod
类方法无原生支持(用static)@classmethod,第一个参数是cls
class Circle:
    pi = 3.14159  # 类变量 - 所有实例共享

    def __init__(self, radius):
        self.radius = radius  # 实例变量

    def area(self):  # 实例方法
        return Circle.pi * self.radius ** 2

    @staticmethod
    def unit_circle():
        return Circle(1)

    @classmethod
    def from_diameter(cls, diameter):
        return cls(diameter / 2)

二、继承对比

2.1 单继承

// Java - extends关键字
public class Animal {
    public void eat() {
        System.out.println("Eating");
    }
}

public class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("Dog is eating");
    }

    public void bark() {
        System.out.println("Woof!");
    }
}
# Python - 括号里写父类
class Animal:
    def eat(self):
        print("Eating")

class Dog(Animal):
    def eat(self):
        print("Dog is eating")

    def bark(self):
        print("Woof!")

d = Dog()
d.eat()    # Dog is eating
d.bark()   # Woof!

2.2 多继承(Python支持!Java不支持!)

这是两者最大的区别之一。

// Java - 不支持多继承
// 只能用接口
public interface Runable {
    void run();
}

public interface Swimmable {
    void swim();
}

// Java 8+ 可以多接口
public class Duck implements Runable, Swimmable {
    @Override
    public void run() { }

    @Override
    public void swim() { }
}
# Python - 支持真正的多继承
class Runable:
    def run(self):
        print("Running")

class Swimmable:
    def swim(self):
        print("Swimming")

class Duck(Runable, Swimmable):
    pass

duck = Duck()
duck.run()   # Running
duck.swim()  # Swimming

2.3 MRO(方法解析顺序)

Python多继承时,如果父类有同名方法,需要了解MRO。

class A:
    def greet(self):
        print("A greet")

class B(A):
    def greet(self):
        print("B greet")

class C(A):
    def greet(self):
        print("C greet")

class D(B, C):  # D继承了B和C
    pass

d = D()
d.greet()  # 打印什么?B greet

# 查看MRO
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>,
#  <class '__main__.A'>, <class 'object'>)

2.4 super()对比

// Java - super调用父类方法
public class Dog extends Animal {
    public Dog(String name) {
        super(name);  // 调用父类构造器
    }

    @Override
    public void eat() {
        super.eat();  // 调用父类方法
        System.out.println("Dog finished eating");
    }
}
# Python - super()更灵活
class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)  # 调用父类构造器
        self.breed = breed

    def eat(self):
        super().eat()  # 调用父类方法
        print("Dog finished eating")

Python 3+ 的super():可以不用传self,Python会自动处理。

class Dog(Animal):
    def eat(self):
        super().eat()  # 不传self也可以!
        print("Dog finished eating")

三、多态对比

3.1 运行时多态

// Java - 编译时确定方法签名,运行时多态
public void makeSound(Animal animal) {
    animal.eat();  // 运行时确定调用哪个子类的方法
}

Dog dog = new Dog();
Cat cat = new Cat();
makeSound(dog);  // Dog eat
makeSound(cat);  // Cat eat
# Python - 天然多态,无需声明
def make_sound(animal):
    animal.eat()  # 运行时确定

dog = Dog()
cat = Cat()
make_sound(dog)  # Dog is eating
make_sound(cat)  # Cat is eating

3.2 抽象类/接口

// Java - 抽象类
public abstract class Shape {
    public abstract double area();

    public void print_area() {
        System.out.println("Area: " + area());
    }
}

// Java - 接口
public interface Drawable {
    void draw();
}
# Python - 抽象基类
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    def print_area(self):
        print(f"Area: {self.area()}")

# Python 没有interface关键字,抽象基类既当接口又当抽象类
class Drawable(ABC):
    @abstractmethod
    def draw(self):
        pass

四、数据类对比

4.1 Java的Record(Java 16+)

// Java - Record 不可变数据类
public record Person(String name, int age) {
    // 自动生成:构造器、getter、equals、hashCode、toString
}

// 使用
Person p = new Person("Alice", 30);
System.out.println(p.name());  // 注意:不是getName()
System.out.println(p);  // Person[name=Alice, age=30]

4.2 Python的dataclass

# Python 3.7+ dataclass
from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int

# 自动生成:__init__、__repr__、__eq__、__hashCode__
p = Person("Alice", 30)
print(p)  # Person(name='Alice', age=30)
print(p.name)  # Alice
print(p == Person("Alice", 30))  # True

4.3 dataclass高级特性

from dataclasses import dataclass, field
from typing import List

@dataclass
class Student:
    name: str
    grades: List[int] = field(default_factory=list)  # 可变默认值
    avg_grade: float = field(init=False)  # 不在构造器中

    def __post_init__(self):
        self.avg_grade = sum(self.grades) / len(self.grades) if self.grades else 0

s = Student("Alice", [90, 85, 88])
print(s)  # Student(name='Alice', grades=[90, 85, 88], avg_grade=87.666...)

五、特殊方法(魔术方法)

5.1 Java的Object方法

public class Person {
    private String name;

    @Override
    public String toString() {
        return "Person{name='" + name + "'}";
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

5.2 Python的特殊方法

class Person:
    def __init__(self, name):
        self.name = name

    def __str__(self):  # str()调用
        return f"Person(name='{self.name}')"

    def __repr__(self):  # 开发调试用
        return f"Person(name='{self.name}')"

    def __eq__(self, other):  # ==
        if isinstance(other, Person):
            return self.name == other.name
        return False

    def __hash__(self):  # hash()
        return hash(self.name)

    def __len__(self):  # len()
        return len(self.name)

    def __call__(self):  # 像函数一样调用
        return f"Hello, I'm {self.name}"

5.3 常用特殊方法一览

Java方法Python方法调用场景
toString()__str__ / __repr__str(obj)
equals()__eq__==
hashCode()__hash__hash(obj)
compareTo()__lt__ / __le__ / ...<, <=
clone()__copy__ / __deepcopy__copy.copy()
Comparable__lt__sorted()

六、类变量 vs 实例变量

6.1 常见坑:类变量被意外修改

# ❌ 错误示例
class Gadget:
    brand = "Generic"  # 类变量

g1 = Gadget()
g2 = Gadget()
print(g1.brand, g2.brand)  # Generic Generic

g1.brand = "Apple"  # 这里创建了一个新的实例变量!
print(g1.brand, g2.brand)  # Apple Generic - 不会影响g2

# ✅ 正确做法 - 如果想修改类变量
Gadget.brand = "Apple"  # 修改类变量会影响所有实例
// Java - 没有这个问题
class Gadget {
    static String brand = "Generic";
}

Gadget g1 = new Gadget();
Gadget g2 = new Gadget();
g1.brand = "Apple";  // 编译错误!static变量要用类名访问
Gadget.brand = "Apple";  // 正确

七、接口与抽象类对比

7.1 Java的接口

public interface Drawable {
    void draw();  # 隐式public abstract

    default void print() {  # Java 8+ default方法
        System.out.println("Printing");
    }

    static void staticMethod() {  # Java 8+ 静态方法
        System.out.println("Static");
    }
}

7.2 Python的协议(Protocol)

# Python 3.8+ 协议 - 类似Go的接口
from typing import Protocol

class Drawable(Protocol):
    def draw(self) -> None:
        ...

# 隐式实现 - 不需要显式继承
class Circle:
    def draw(self):
        print("Drawing circle")

def render(d: Drawable):
    d.draw()

render(Circle())  # 可以!自动检查是否有draw方法

八、实战:策略模式

8.1 Java实现

public interface PaymentStrategy {
    void pay(double amount);
}

public class CreditCardPayment implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("Paid " + amount + " via Credit Card");
    }
}

public class PayPalPayment implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("Paid " + amount + " via PayPal");
    }
}

public class ShoppingCart {
    private PaymentStrategy strategy;

    public void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void checkout(double amount) {
        strategy.pay(amount);
    }
}

8.2 Python实现

# Python实现策略模式更简洁
class PaymentStrategy:
    def pay(self, amount):
        raise NotImplementedError

class CreditCardPayment(PaymentStrategy):
    def pay(self, amount):
        print(f"Paid {amount} via Credit Card")

class PayPalPayment(PaymentStrategy):
    def pay(self, amount):
        print(f"Paid {amount} via PayPal")

class ShoppingCart:
    def __init__(self, strategy=None):
        self.strategy = strategy

    def set_strategy(self, strategy):
        self.strategy = strategy

    def checkout(self, amount):
        if self.strategy:
            self.strategy.pay(amount)

# 使用
cart = ShoppingCart(CreditCardPayment())
cart.checkout(100)

cart.set_strategy(PayPalPayment())
cart.checkout(200)

九、总结

特性JavaPython
多继承不支持(只有接口)支持
访问控制强制private/public约定___
抽象类abstract class@abstractmethod
接口interface抽象基类或Protocol
数据类record(Java 16+)@dataclass
构造器与类名同名的方法__init__
调用父类super()super()
特殊方法Object方法__xxx__

Python的OOP设计理念是"约定优于强制"。没有强制的访问控制,没有编译时类型检查,但有强大的元编程能力。多继承和dataclass是Python最实用的特性,值得熟练掌握。