哈Ha! 我向您介绍John Fincher撰写的文章“ Python vs Java中的面向对象编程” 。
Java和Python中的面向对象编程(OOP)的实现不同。 使用对象,变量类型和其他语言功能的原理可能会导致从一种语言切换到另一种语言的困难。 本文对想学习Python的Java程序员和想更好地学习Java的Python程序员都非常有用,它给出了这些语言与OOP的主要异同。
更多细节-在削减。
Python和Java中的类示例
首先,让我们在Python和Java中实现最简单的类,以说明这些语言中的一些差异,然后我们将逐步对该类进行更改。
想象一下,我们在Java中对Car类具有以下定义:
1 public class Car { 2 private String color; 3 private String model; 4 private int year; 5 6 public Car(String color, String model, int year) { 7 this.color = color; 8 this.model = model; 9 this.year = year; 10 } 11 12 public String getColor() { 13 return color; 14 } 15 16 public String getModel() { 17 return model; 18 } 19 20 public int getYear() { 21 return year; 22 } 23 }
源Java文件的名称必须与存储在其中的类的名称匹配,因此我们必须将文件命名为Car.java。 每个Java文件只能包含一个公共类。
Python中的同一类如下所示:
1 class Car: 2 def __init__(self, color, model, year): 3 self.color = color 4 self.model = model 5 self.year = year
在Python中,您可以随时随地声明一个类。 将此文件另存为car.py。
以这些类为基础,我们继续研究类和对象的主要组成部分。
对象属性
在所有面向对象的语言中,有关对象的数据都存储在某处。 在Python和Java中,此数据都存储在attribute中 , attribute是与特定对象关联的变量。
Python和Java之间最重要的区别之一是它们如何定义类和对象属性以及这些语言如何控制它们。 其中一些差异是由语言施加的限制引起的,而其他差异则与更有效的实践相关。
声明和初始化
在Java中,我们在类内部但在所有方法外部声明属性(指示其类型)。 在使用类属性之前,我们必须定义它们:
1 public class Car { 2 private String color; 3 private String model; 4 private int year;
在Python中,我们在init ()类的方法内声明和定义属性,该类类似于Java中的构造函数:
1 def __init__(self, color, model, year): 2 self.color = color 3 self.model = model 4 self.year = year
通过在变量名前面指定关键字self,我们告诉Python这些是属性。 该类的每个实例均获得其副本。 Python中的所有变量都不是宽松类型的,属性也不例外。
也可以在init ()方法之外创建变量,但这不是最好的解决方案,并且可能导致难以检测的错误。 例如,可以将新的wheels属性添加到Car对象,如下所示:
1 >>> import car 2 >>> my_car = car.Car("yellow", "beetle", 1967) 3 >>> print(f"My car is {my_car.color}") 4 My car is yellow 5 6 >>> my_car.wheels = 5 7 >>> print(f"Wheels: {my_car.wheels}") 8 Wheels: 5
但是,如果忘记在第六行中指定表达式my_car.wheels = 5,则会收到错误消息:
1 >>> import car 2 >>> my_car = car.Car("yellow", "beetle", 1967) 3 >>> print(f"My car is {my_car.color}") 4 My car is yellow 5 6 >>> print(f"Wheels: {my_car.wheels}") 7 Traceback (most recent call last): 8 File "<stdin>", line 1, in <module> 9 AttributeError: 'Car' object has no attribute 'wheels'
在Python中,如果在方法外部声明变量,则它将被视为类变量。 让我们更改Car类:
1 class Car: 2 3 wheels = 0 4 5 def __init__(self, color, model, year): 6 self.color = color 7 self.model = model 8 self.year = year
现在,可变轮的使用将发生变化。 而不是通过对象访问它,我们使用类名来访问它:
1 >>> import car 2 >>> my_car = car.Car("yellow", "beetle", 1967) 3 >>> print(f"My car is {my_car.color}") 4 My car is yellow 5 6 >>> print(f"It has {car.Car.wheels} wheels") 7 It has 0 wheels 8 9 >>> print(f"It has {my_car.wheels} wheels") 10 It has 0 wheels
注意:在Python中,使用以下语法访问类变量:
- 包含类的文件名(不带.py扩展名)
- 点数
- 类名
- 点数
- 变量名
由于我们将Car类保存在car.py文件中,因此我们以这种方式在第六行中引用了wheel类变量:car.Car.wheels。
使用wheel变量时,需要注意以下事实:更改my_car.wheels类的实例变量的值不会导致更改car.Car.wheels类的变量:
1 >>> from car import * 2 >>> my_car = car.Car("yellow", "Beetle", "1966") 3 >>> my_other_car = car.Car("red", "corvette", "1999") 4 5 >>> print(f"My car is {my_car.color}") 6 My car is yellow 7 >>> print(f"It has {my_car.wheels} wheels") 8 It has 0 wheels 9 10 >>> print(f"My other car is {my_other_car.color}") 11 My other car is red 12 >>> print(f"It has {my_other_car.wheels} wheels") 13 It has 0 wheels 14 15 >>>
在第2行和第3行,我们定义了两个Car对象:my_car和my_other_car。
首先,两个对象的wheels属性均为零。 在第16行,我们设置类变量:car.Car.wheels = 4,两个对象现在都有4个轮子。 但是,当在第24行上更改对象my_car.wheels = 5的属性时,第二个对象的属性保持不变。
这意味着我们现在有了wheel属性的两个不同副本:
- 适用于所有Car对象的类变量
- 仅适用于my_car对象的特定类实例变量。
因此,您可能不小心引用了错误的实例并犯了一个细微的错误。
在Java中,类属性的等效项是静态属性:
public class Car { private String color; private String model; private int year; private static int wheels; public Car(String color, String model, int year) { this.color = color; this.model = model; this.year = year; } public static int getWheels() { return wheels; } public static void setWheels(int count) { wheels = count; } }
通常,我们通过类名称访问Java中的静态变量。 您可以通过类的实例来访问它们,就像在Python中一样,但这不是最好的解决方案。
我们的Java类正在开始加长。 Java对Python“冗长”的原因之一是公共和私有方法与属性的概念。
公共和私人
Java通过区分公共数据和私有数据来控制对方法和属性的访问。
在Java中,期望将属性声明为私有(或保护,如果类的后代需要访问它们)。 因此,我们限制从外部访问它们。 为了提供对私有属性的访问,我们声明安装或接收此数据的公共方法(稍后将对此进行详细介绍)。
回想一下,在我们的Java代码中,color变量被声明为private。 因此,以下代码将无法编译:
Car myCar = new Car("blue", "Ford", 1972);
如果您未指定对属性的访问级别,则默认情况下会将其设置为包保护 ,从而限制对包内类的访问。 如果我们希望上面的代码起作用,那么我们将不得不将该属性设为public。
但是,不鼓励Java将属性声明为public。 建议您将它们声明为私有,然后使用如上面的代码中所述的公共方法,例如getColor()和getModel()。
相反,Python缺少公共和私有数据的概念。 在Python中,一切都是公开的。 此python代码可以使用爆炸:
>>> my_car = car.Car("blue", "Ford", 1972) >>>
非公共( non-public )类实例变量的概念代替了Python中的私有变量。 名称以单个下划线开头的所有变量均视为非公共变量。 这种命名约定不会阻止我们直接访问变量。
将以下行添加到我们的Python类Car中:
class Car: wheels = 0 def __init__(self, color, model, year): self.color = color self.model = model self.year = year self._cupholders = 6
我们可以直接访问_cupholders变量:
>>> import car >>> my_car = car.Car("yellow", "Beetle", "1969") >>> print(f"It was built in {my_car.year}") It was built in 1969 >>> my_car.year = 1966 >>> print(f"It was built in {my_car.year}") It was built in 1966 >>> print(f"It has {my_car._cupholders} cupholders.") It has 6 cupholders.
Python允许您访问这样的变量,但是,某些开发环境(如VS Code)会发出警告:

另外,Python在变量名的开头使用双下划线来隐藏属性。 当Python看到这样的变量时,它将自动更改其名称,从而使直接访问变得困难。 但是,这种机制仍然不能阻止我们转向它。 我们通过以下示例进行演示:
class Car: wheels = 0 def __init__(self, color, model, year): self.color = color self.model = model self.year = year self.__cupholders = 6
现在,如果我们看一下__cupholders变量,就会得到一个错误:
>>> import car >>> my_car = car.Car("yellow", "Beetle", "1969") >>> print(f"It was built in {my_car.year}") It was built in 1969 >>> my_car.year = 1966 >>> print(f"It was built in {my_car.year}") It was built in 1966 >>> print(f"It has {my_car.__cupholders} cupholders.") Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Car' object has no attribute '__cupholders'
那么为什么__cupholders属性不存在?
就是这个 当Python在开始时看到双下划线属性时,会通过在开头添加带下划线的类名来对其进行更改。 为了直接访问属性,您还必须更改名称:
>>> print(f"It has {my_car._Car__cupholders} cupholders") It has 6 cupholders
现在出现了一个问题:如果将Java类的属性声明为私有,并且Python类的属性名称前带有双下划线,那么如何获取此数据?
门禁控制
在Java中,我们使用setter和getters访问私有属性。 为了使用户重新绘制计算机,请将以下代码段添加到Java类中:
public String getColor() { return color; } public void setColor(String color) { this.color = color; }
由于getColor()和setColor()方法是公共的,因此任何用户都可以调用它们并获取/更改计算机的颜色。 使用私有属性(我们可以访问公共获取器和设置器)是Java比Python更大“冗长”的原因之一。
如上所示,在Python中,我们可以直接访问属性。 由于一切都是公开的,因此我们可以随时随地接触任何事物。 我们可以通过按名称直接调用来获取和设置属性值。 在Python中,我们甚至可以删除属性,这在Java中是无法想象的:
>>> my_car = Car("yellow", "beetle", 1969) >>> print(f"My car was built in {my_car.year}") My car was built in 1969 >>> my_car.year = 1966 >>> print(f"It was built in {my_car.year}") It was built in 1966 >>> del my_car.year >>> print(f"It was built in {my_car.year}") Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Car' object has no attribute 'year'
但是,我们也想控制对属性的访问。 在这种情况下,Python属性将为我们提供帮助。
在Python中,属性使用装饰器提供对类属性的受控访问。 使用属性,我们可以在Python类中声明函数,例如Java中的getter和setter(删除属性是一种奖励)。
在下面的Car类示例中可以看到属性的操作:
1 class Car: 2 def __init__(self, color, model, year): 3 self.color = color 4 self.model = model 5 self.year = year 6 self._voltage = 12 7 8 @property 9 def voltage(self): 10 return self._voltage 11 12 @voltage.setter 13 def voltage(self, volts): 14 print("Warning: this can cause problems!") 15 self._voltage = volts 16 17 @voltage.deleter 18 def voltage(self): 19 print("Warning: the radio will stop working!") 20 del self._voltage
在此示例中,我们将Car类的概念扩展到包括电动汽车。 第6行声明_voltage属性以在其中存储电池电压。
在第9行和第10行中,为了进行受控访问,我们创建了voltage()函数并返回私有变量的值。 使用@property装饰器,我们将其变成一个吸气剂,任何用户现在都可以访问它。
在第13-15行中,我们定义了一个函数,也称为电压()。 但是,我们以不同的方式装饰它: 电压设定器。 最后,在第18-20行中,我们用电压 .deleter装饰电压()函数,并可以在必要时删除_voltage属性。
装饰的函数具有相同的名称,表示它们控制对相同属性的访问。 这些函数名称也成为用于获取其值的属性的名称。 运作方式如下:
1 >>> from car import * 2 >>> my_car = Car("yellow", "beetle", 1969) 3 4 >>> print(f"My car uses {my_car.voltage} volts") 5 My car uses 12 volts 6 7 >>> my_car.voltage = 6 8 Warning: this can cause problems! 9 10 >>> print(f"My car now uses {my_car.voltage} volts") 11 My car now uses 6 volts 12 13 >>> del my_car.voltage 14 Warning: the radio will stop working!
请注意,我们使用电压,而不是_电压。 因此,我们告诉Python应用刚刚定义的属性:
- 当我们在第四行中打印值my_car.voltage时,Python调用用@property修饰的voltage()函数。
- 当我们在第7行中分配值my_car.voltage时,Python会调用带有电压 .setter的电压()函数。
- 当我们在第13行中删除my_car.voltage时,Python会调用用电压 .deleter装饰的voltage()函数。
上述装饰器使我们无需使用各种方法即可控制对属性的访问。 您甚至可以通过删除修饰的@ .setter和@ .deleter函数,使该属性成为只读属性。
自我和这个
在Java中,类使用this关键字引用自身:
public void setColor(String color) { this.color = color; }
这在Java代码中暗含。 原则上,甚至没有必要编写它,除非变量名称重合。
Setter可以这样写:
public void setColor(String newColor) { color = newColor; }
由于Car类具有一个名为color的属性,并且作用域中没有其他具有相同名称的变量,因此可以使用指向该名称的链接。 在第一个示例中,我们使用了this关键字来区分具有相同颜色名称的属性和参数。
在Python中,self关键字具有类似的用途:访问属性成员,但与Java不同,它是必需的 :
class Car: def __init__(self, color, model, year): self.color = color self.model = model self.year = year self._voltage = 12 @property def voltage(self): return self._voltage
Python要求self是强制性的。 每个自我创建或引用一个属性。 如果我们跳过它,那么Python将只创建一个局部变量而不是一个属性。
我们使用self以及Python和Java中self的方式的差异是由于这两种语言之间的主要差异以及它们命名变量和属性的方式。
方法与功能
所讨论的语言之间的差异在于,Python中有函数,而在Java中则没有。
在Python中,以下代码可以毫无问题地工作(并且在各处使用):
>>> def say_hi(): ... print("Hi!") ... >>> say_hi() Hi!
我们可以在任何可见的地方调用say_hi()。 此函数不包含对self的引用,这意味着它是全局函数,而不是类函数。 她将无法更改或保存任何类的任何数据,但可以使用局部和全局变量。
相反,我们用Java写的每一行都属于一个类。 类之外不存在函数,并且根据定义,所有Java函数都是方法。 在Java中,最接近纯函数的是静态方法:
public class Utils { static void SayHi() { System.out.println("Hi!"); } }
实用程序。 从任何地方调用SayHi()都无需先创建Utils类的实例。 由于我们不创建对象就调用SayHi(),因此此链接不存在。 但是,从say_hi()在Python的意义上来说,这仍然不是一个函数。
继承与多态
继承和多态是OOP中的两个基本概念。 首先,对象可以接收(换句话说,继承)其他对象的属性和功能,从而创建了从更一般的对象到更具体的对象的层次结构。 例如,Car类和Boat类都是Vehicle类的特定类型。 这两个对象都继承一个父对象或多个父对象的行为。 在这种情况下,它们称为子对象。
反过来,多态是使用相同功能或方法处理不同对象的能力。
这两个基本的OOP概念都以完全不同的方式在Java和Python中实现。
传承
Python支持多种继承,即从多个父类创建一个类。
为了证明这一点,我们将Car类分为两类:一类用于车辆,一类用于使用电力的汽车:
class Vehicle: def __init__(self, color, model): self.color = color self.model = model class Device: def __init__(self): self._voltage = 12 class Car(Vehicle, Device): def __init__(self, color, model, year): Vehicle.__init__(self, color, model) Device.__init__(self) self.year = year @property def voltage(self): return self._voltage @voltage.setter def voltage(self, volts): print("Warning: this can cause problems!") self._voltage = volts @voltage.deleter def voltage(self): print("Warning: the radio will stop working!") del self._voltage
Vehicle类定义颜色和模型属性。 Device类具有_voltage属性。 Car类是从这两个类派生的,并且color,model和_voltage属性现在是新类的一部分。
Car类的init ()方法调用两个父类的init ()方法,以确保正确初始化所有数据。 之后,我们可以向Car类添加任何所需的功能。 在此示例中,我们将添加year属性以及_voltage的getter和setter。
新的Car类的功能保持不变。 我们可以创建和使用类对象,就像前面的几个例子一样:
>>> from car import * >>> my_car = Car("yellow", "beetle", 1969) >>> print(f"My car is {my_car.color}") My car is yellow >>> print(f"My car uses {my_car.voltage} volts") My car uses 12 volts >>> my_car.voltage = 6 Warning: this can cause problems! >>> print(f"My car now uses {my_car.voltage} volts") My car now uses 6 volts
反过来,Java语言仅支持单一继承,这意味着Java中的类只能从一个父类继承数据和行为。 但是在Java中,可以从多个接口继承。 接口提供了一组需要实现的相关方法,从而允许子类以相似的方式运行。
为了看到这一点,我们将Car Java类拆分为其父类和接口:
public class Vehicle { private String color; private String model; public Vehicle(String color, String model) { this.color = color; this.model = model; } public String getColor() { return color; } public String getModel() { return model; } } public interface Device { int getVoltage(); } public class Car extends Vehicle implements Device { private int voltage; private int year; public Car(String color, String model, int year) { super(color, model); this.year = year; this.voltage = 12; } @Override public int getVoltage() { return voltage; } public int getYear() { return year; } }
不要忘记,Java中的每个类和每个接口都必须放在其自己的文件中。
与上面的Python示例一样,我们创建了一个新的Vehicle类来存储车辆固有的通用数据和功能。 但是,要添加设备的功能,我们需要创建一个接口,该接口定义了获取设备电压的方法。
通过使用extends关键字从Vehicle类继承并使用Implements关键字实现Device接口,可以创建Car类。 在类构造函数中,我们用super()调用父构造函数。 由于只有一个父类,因此我们引用Vehicle类的构造函数。 为了实现该接口,我们使用Override注释重新定义getVoltage()。
Java要求与在Python中重用Device中的代码不同,Java要求我们在实现该接口的每个类中实现相同的功能。 接口仅定义方法,而不能定义类实例数据或实现细节。
那么,为什么Java会发生这种情况? .
Java . , Java- , , . , .
Java- charge(), Device. , Device, charge().
Rhino.java:
public class Rhino { }
Main.java charge() , Car Rhino.
public class Main{ public static void charge(Device device) { device.getVoltage(); } public static void main(String[] args) throws Exception { Car car = new Car("yellow", "beetle", 1969); Rhino rhino = new Rhino(); charge(car); charge(rhino); } }
, : Information:2019-02-02 15:20 - Compilation completed with 1 error and 0 warnings in 4 s 395 ms Main.java Error:(43, 11) java: incompatible types: Rhino cannot be converted to Device
Rhino Device, charge().
( — strict variable typing, , Python ) , Java, Python , : « , » ( : " , , , , " – . ). , Python .
Python:
>>> def charge(device): ... if hasattr(device, '_voltage'): ... print(f"Charging a {device._voltage} volt device") ... else: ... print(f"I can't charge a {device.__class__.__name__}") ... >>> class Phone(Device): ... pass ... >>> class Rhino: ... pass ... >>> my_car = Car("yellow", "Beetle", "1966") >>> my_phone = Phone() >>> my_rhino = Rhino() >>> charge(my_car) Charging a 12 volt device >>> charge(my_phone) Charging a 12 volt device >>> charge(my_rhino) I can't charge a Rhino
charge() _voltage. Device , - (Car Phone) , , , . , Device ( Rhino), , , , (rhino) .
Java Object, . , . Object :
class Object { boolean equals(Object obj) { ... } int hashCode() { ... } String toString() { ... } }
equals() , , hashCode() , . Java . , , , .
toString() . . , , , , System.out.println():
Car car = new Car("yellow", "Beetle", 1969); System.out.println(car);
car:
Car@61bbe9ba
, ? , toString(). Car:
public String toString() { return "Car: " + getColor() + " : " + getModel() + " : " + getYear(); }
, , :
Car: yellow : Beetle : 1969
Python (dunder — double underscore). Python- , , , .
Python : repr () str (). repr (), str () . hashcode() toString() Java.
Java, Python :
>>> my_car = Car("yellow", "Beetle", "1966") >>> print(repr(my_car)) <car.Car object at 0x7fe4ca154f98> >>> print(str(my_car)) <car.Car object at 0x7fe4ca154f98>
, str () Python- Car:
def __str__(self): return f'Car {self.color} : {self.model} : {self.year}'
:
>>> my_car = Car("yellow", "Beetle", "1966") >>> print(repr(my_car)) <car.Car object at 0x7f09e9a7b630> >>> print(str(my_car)) Car yellow : Beetle : 1966
. repr (), .
Python , , , .
Python . Python , Java .
Python- Car :
class Car: def __init__(self, color, model, year): self.color = color self.model = model self.year = year def __str__(self): return f'Car {self.color} : {self.model} : {self.year}' def __eq__(self, other): return self.year == other.year def __lt__(self, other): return self.year < other.year def __add__(self, other): return Car(self.color + other.color, self.model + other.model, int(self.year) + int(other.year))
, :
Python , , , .
Car:
>>> my_car = Car("yellow", "Beetle", "1966") >>> your_car = Car("red", "Corvette", "1967") >>> print (my_car < your_car) True >>> print (my_car > your_car) False >>> print (my_car == your_car) False >>> print (my_car + your_car) Car yellowred : BeetleCorvette : 3933
, , , Java.
– . Java, Python .
. Python type() isinstance () , :
>>> my_car = Car("yellow", "Beetle", "1966") >>> print(type(my_car)) <class 'car.Car'> >>> print(isinstance(my_car, Car)) True >>> print(isinstance(my_car, Device)) True
Java getClass() instanceof :
Car car = new Car("yellow", "beetle", 1969); System.out.println(car.getClass()); System.out.println(car instanceof Car);
:
class com.realpython.Car true
Python dir() , ( ). , getattr():
>>> print(dir(my_car)) ['_Car__cupholders', '__add__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_voltage', 'color', 'model', 'voltage', 'wheels', 'year'] >>> print(getattr(my_car, "__format__")) <built-in method __format__ of Car object at 0x7fb4c10f5438>
Java , , , .
getFields() . , Car , :
Field[] fields = car.getClass().getFields();
Java , getDeclaredMethods(). get-, , , :
1) getDeclaredMethods()
2) :
:
1 public static boolean getProperty(String name, Object object) throws Exception { 2 3 Method[] declaredMethods = object.getClass().getDeclaredMethods(); 4 for (Method method : declaredMethods) { 5 if (isGetter(method) && 6 method.getName().toUpperCase().contains(name.toUpperCase())) { 7 return true; 8 } 9 } 10 return false; 11 } 12 13
getProperty() – . . true, , false.
Java, Python .
Java- true , , . , getDeclaredMethods() Method. Method invoke(), Method. 7 true, , method.invoke(object).
Python. , Python , , :
>>> for method_name in dir(my_car): ... if callable(getattr(my_car, method_name)): ... print(method_name) ... __add__ __class__ __delattr__ __dir__ __eq__ __format__ __ge__ __getattribute__ __gt__ __init__ __init_subclass__ __le__ __lt__ __ne__ __new__ __reduce__ __reduce_ex__ __repr__ __setattr__ __sizeof__ __str__ __subclasshook__
Python , Java. str () :
>>> for method_name in dir(my_car): ... attr = getattr(my_car, method_name) ... if callable(attr): ... if method_name == '__str__': ... print(attr()) ... Car yellow : Beetle : 1966
, dir(). , getattr(), callable(), . , , str (), .