面向对象的编程提供了一种可持续的方式来编写意大利面条式代码。它使您可以将程序作为一系列补丁来添加。
保罗·格雷厄姆
面向对象的编程是一种编程范例,其中所有内容都表示为一个对象。
对象之间传递消息。每个对象决定如何处理收到的消息。OOP专注于每个对象的状态和行为。
对象是具有状态和行为的实体。
例如,狗,猫和车辆。为了说明这一点,狗的年龄,颜色,名字等状态以及进食,睡觉和奔跑等行为都有。
状态告诉我们对象的外观或具有的属性。
行为告诉我们对象的行为。
通过定义程序的状态和行为,我们实际上可以将程序中的现实世界的狗表示为软件对象。
软件对象是真实世界对象的实际表示。每当创建逻辑对象时,都会在RAM中分配内存。
对象也称为类的实例。实例化一个类与创建一个对象的意思相同。
创建对象时要记住的重要事项是:引用类型应与对象类型相同或为父类型。我们将在本文后面看到什么是引用类型。
类是从中创建对象的模板或蓝图。
想象一个类作为一个cookie切割器,而一个对象作为cookie。
类将状态定义为实例变量,将行为定义为实例方法。
实例变量也称为成员变量。
类不占用任何空间。
为了让您对类和对象有一个了解,让我们创建一个Cat类,该类代表现实世界中Cat的状态和行为。
public class Cat { /* Instance variables: states of Cat */ String name; int age; String color; String breed; /* Instance methods: behaviors of Cat */ void sleep(){ System.out.println("Sleeping"); } void play(){ System.out.println("Playing"); } void feed(){ System.out.println("Eating"); }}
现在,我们已经成功地为Cat定义了模板。假设我们有两只猫叫雷神(Thor)和兰博(Rambo)。
我们如何在程序中定义它们?
首先,我们需要创建Cat类的两个对象。
public class Main { public static void main(String[] args) { Cat thor = new Cat(); Cat rambo = new Cat(); }}
接下来,我们将定义它们的状态和行为。
public class Main { public static void main(String[] args) { /* Creating objects */ Cat thor = new Cat(); Cat rambo = new Cat(); /* Defining Thor cat */ thor.name = "Thor"; thor.age = 3; thor.breed = "Russian Blue"; thor.color = "Brown"; thor.sleep(); /* Defining Rambo cat */ rambo.name = "Rambo"; rambo.age = 4; rambo.breed = "Maine Coon"; rambo.color = "Brown"; rambo.play(); }}
像上面的代码示例一样,我们可以定义我们的类,实例化它(创建对象)并指定这些对象的状态和行为。
现在,我们已经介绍了面向对象编程的基础知识。让我们继续进行面向对象编程的原理。
这是面向对象编程范例的四个主要原理。了解它们对于成为一名成功的程序员至关重要。
封装形式
遗产
抽象化
多态性
现在,让我们更详细地看看每个。
封装是将代码和数据包装到一个单元中的过程。
这就像一个包含多种药物的胶囊,是一种有助于保护实例变量的技术。
这可以通过使用private
类以外的任何东西都不能访问的访问修饰符来实现。为了安全地访问私有国家,我们必须提供公共获取和设置方法。(在Java中,这些方法应遵循JavaBeans命名标准。)
假设有一家唱片店,出售不同艺术家的音乐专辑,并管理着唱片商。
如果您查看图4,则由于类的状态设置为,因此StockKeeper
类可以Album
直接访问类的状态。Album
public
如果库存管理员创建相册并将状态设置为负值怎么办?这可以由库存管理员有意或无意地完成。
为了说明,让我们看一个解释上面的图和语句的示例Java程序。
专辑类别:
public class Album { public String name; public String artist; public double price; public int numberOfCopies; public void sellCopies(){ if(numberOfCopies > 0){ numberOfCopies--; System.out.println("One album has sold!"); } else{ System.out.println("No more albums available!"); } } public void orderCopies(int num){ numberOfCopies += num; }}
StockKeeper类:
public class StockKeeper { public String name; public StockKeeper(String name){ this.name = name; } public void manageAlbum(Album album, String name, String artist, double price, int numberOfCopies){ /* Defining states and behaviors for album */ album.name = name; album.artist = artist; album.price = price; album.numberOfCopies = numberOfCopies; /* Printing album details */ System.out.println("Album managed by :"+ this.name); System.out.println("Album details::::::::::"); System.out.println("Album name : " + album.name); System.out.println("Album artist : " + album.artist); System.out.println("Album price : " + album.price); System.out.println("Album number of copies : " + album.numberOfCopies); }}
主班:
public class Main { public static void main(String[] args) { StockKeeper johnDoe = new StockKeeper("John Doe"); /* Stock keeper creates album and assigns negative values for price and number of copies available */ johnDoe.manageAlbum(new Album(), "Slippery When Wet", "Bon Jovi", -1000.00, -50); }}
输出:
Album managed by :John DoeAlbum details::::::::::Album name : Slippery When WetAlbum artist : Bon JoviAlbum price : -1000.0Album number of copies : -50
专辑的价格和份数不能为负值。我们如何避免这种情况?这是我们使用封装的地方。
在这种情况下,我们可以阻止库存管理者分配负值。如果他们尝试为相册的价格和份数分配负值,我们会将它们分配为0.0和0。
专辑类别:
public class Album { private String name; private String artist; private double price; private int numberOfCopies; public void sellCopies(){ if(numberOfCopies > 0){ numberOfCopies--; System.out.println("One album has sold!"); } else{ System.out.println("No more albums available!"); } } public void orderCopies(int num){ numberOfCopies += num; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getArtist() { return artist; } public void setArtist(String artist) { this.artist = artist; } public double getPrice() { return price; } public void setPrice(double price) { if(price > 0) { this.price = price; } else { this.price = 0.0; } } public int getNumberOfCopies() { return numberOfCopies; } public void setNumberOfCopies(int numberOfCopies) { if(numberOfCopies > 0) { this.numberOfCopies = numberOfCopies; } else { this.numberOfCopies = 0; } }}
StockKeeper类:
public class StockKeeper { private String name; StockKeeper(String name){ setName(name); } public void manageAlbum(Album album, String name, String artist, double price, int numberOfCopies){ /* Defining states and behaviors for album */ album.setName(name); album.setArtist(artist); album.setPrice(price); album.setNumberOfCopies(numberOfCopies); /* Printing album details */ System.out.println("Album managed by :"+ getName()); System.out.println("Album details::::::::::"); System.out.println("Album name : " + album.getName()); System.out.println("Album artist : " + album.getArtist()); System.out.println("Album price : " + album.getPrice()); System.out.println("Album number of copies : " + album.getNumberOfCopies()); } public String getName() { return name; } public void setName(String name) { this.name = name; }}
主班:
public class Main { public static void main(String[] args) { StockKeeper johnDoe = new StockKeeper("John Doe"); /* Stock keeper creates album and assigns negative values for price and number of copies available */ johnDoe.manageAlbum(new Album(), "Slippery When Wet", "Bon Jovi", -1000.00, -50); }}
输出:
Album managed by :John DoeAlbum details::::::::::Album name : Slippery When WetAlbum artist : Bon JoviAlbum price : 0.0Album number of copies : 0
通过封装,我们阻止了库存管理者分配负值,这意味着我们可以控制数据。
我们可以将类设为只读或只写:对于只读类,我们应该仅提供getter方法。对于只写类,我们应该只提供setter方法。
控制数据:我们可以通过向setter方法提供逻辑来控制数据,就像在上面的示例中限制了库存管理者分配负值一样。
数据隐藏:其他类无法直接访问类的私有成员。
假设我们上面讨论的唱片店也出售蓝光电影。
正如你可以在上图看到,有许多共同的状态和行为之间(通用代码)Album
和Movie
。
在代码中实现此类图时,您是否要编写(或复制并粘贴)整个代码Movie
?如果这样做,您就是在重复自己。如何避免代码重复?
这就是我们使用继承的地方。
继承是一种机制,其中一个对象获取父对象的所有状态和行为。
继承使用父子关系(IS-A关系)。
可见性/访问修饰符会影响从一个类继承到另一类的内容。
在Java中,根据经验,我们创建实例变量private
和实例方法public
。
在这种情况下,我们可以肯定地说以下内容是继承的:
公共实例方法。
私有实例变量(只能通过public getter和setter方法访问私有实例变量)。
Java有五种继承类型。它们是单个,多层,分层,多个和混合的。
类允许单,多级和分层继承。接口允许多重继承和混合继承。
一个类只能扩展一个类,但是它可以实现任何数量的接口。一个接口可以扩展多个接口。
一,IS-A关系
IS-A关系是指继承或实现。
泛化使用从专门化类到泛化类的IS-A关系。
一个类的实例HAS-A引用另一个类的实例。
在这种关系中,A类和B类的存在并不相互依赖。
对于聚合部分,我们将看到Student
类和ContactInfo
类的示例。
class ContactInfo { private String homeAddress; private String emailAddress; private int telephoneNumber; //12025550156}public class Student { private String name; private int age; private int grade; private ContactInfo contactInfo;//Student HAS-A ContactInfo public void study() { System.out.println("Study"); }}
Student
HAS-A ContactInfo
。ContactInfo
可以在其他地方使用-例如,公司的Employee
类也可以使用ContactInfo
该类。因此Student
可以没有就可以存在ContactInfo
,ContactInfo
也可以没有就可以存在Student
。这种类型的关系称为聚合。
在这种关系中,没有类A就不能存在B类,但是没有B 类就可以存在A 类。
为了让您对合成有所了解,让我们看一看Student
该类和StudentId
该类的示例。
class StudentId { private String idNumber;//A-123456789 private String bloodGroup; private String accountNumber;}public class Student { private String name; private int age; private int grade; private StudentId studentId;//Student HAS-A StudentId public void study() { System.out.println("Study"); }}
Student
HAS-A StudentId
。Student
没有就可以存在,StudentId
但没有StudentId
就不能存在Student
。这种关系称为组合。
现在,让我们回到上面讨论的以前的唱片店示例。
我们可以用Java实现此图,以避免代码重复。
代码重用:子类继承父类的所有实例成员。
您可以更灵活地更改代码:就地更改代码就足够了。
您可以使用多态:方法覆盖需要IS-A关系。
抽象是隐藏实现细节并仅向用户显示功能的过程。
提取的一个常见示例是,按下加速器会提高汽车的速度。但是驾驶员不知道踩油门踏板会如何提高速度–他们不必知道这一点。
从技术上讲,抽象意味着一些不完整的东西或将在以后完成。
在Java中,我们可以通过两种方式实现抽象:抽象类(0到100%)和接口(100%)。
关键字abstract
可以应用于类和方法。abstract
和final
或static
永远不会在一起。
抽象类是包含关键字的类abstract
。
抽象类无法实例化(无法创建抽象类的对象)。它们可以具有构造函数,静态方法和最终方法。
一种抽象方法是包含关键字的方法abstract
。
抽象方法没有实现(没有方法主体,并且以半冒号结尾)。不应将其标记为private
。
如果一个类中至少存在一个抽象方法,则整个类应该是抽象的。
我们可以有一个没有抽象方法的抽象类。
在一个抽象类中,我们可以同时具有任意数量的抽象方法以及非抽象方法。
抽象类的第一个具体子类必须为所有抽象方法提供实现。
如果这没有发生,则子类也应标记为抽象。
在现实世界中,实现将由最终用户未知的人员提供。用户不知道实现类和实际实现。
让我们考虑一个抽象概念用法的示例。
abstract class Shape { public abstract void draw();}class Circle extends Shape{ public void draw() { System.out.println("Circle!"); }}public class Test { public static void main(String[] args) { Shape circle = new Circle(); circle.draw(); }}
强制子类实现抽象方法。
停止拥有该类的实际对象。
保持有班级参考。
保留通用的类代码。
接口是类的蓝图。
接口是100%抽象的。此处不允许使用构造函数。它表示IS-A关系。
注意:接口仅定义必需的方法。我们无法保留通用代码。
接口只能有抽象方法,不能有具体方法。默认情况下,接口方法是public
和abstract
。因此,在界面内部,我们无需指定public
和abstract
。
因此,当一个类实现接口的方法而未指定该方法的访问级别时,编译器将抛出错误说明“Cannot reduce the visibility of the inherited method from interface”
。因此,必须将实现的方法的访问级别设置为public
。
默认情况下,接口变量public
,static
和final
。
例如:
interface Runnable { int a = 10; //similar to: public static final int a = 10; void run(); //similar to: public abstract void run();}public class InterfaceChecker implements Runnable{ public static void main(String[] args) { Runnable.a = 5;//The final field Runnable.a cannot be assigned. }}
让我们看一个解释界面概念的示例:
interface Drawable { void draw();}class Circle implements Drawable{ public void draw() { System.out.println("Circle!"); }}public class InterfaceChecker { public static void main(String[] args) { Drawable circle = new Circle(); circle.draw(); }}
通常,我们在单独的类中实现接口方法。假设我们需要在接口中添加新方法。然后,我们也必须在单独的类中实现该方法。
为了克服这个问题,Java 8引入了默认和静态方法,这些方法在接口内部实现方法,这与抽象方法不同。
默认方式
public interface DefaultInterface { void sleep(); default void run() { System.out.println("I'm running!"); }}public class InterfaceCheckers implements DefaultInterface{ public void sleep() { System.out.println("Sleeping..."); } public static void main(String[] args) { InterfaceCheckers checker = new InterfaceCheckers(); checker.run(); checker.sleep(); }}/* Output: I'm running! Sleeping... */
静态方法
与类的静态方法类似,我们可以通过它们的接口名称来调用它们。
public interface DefaultInterface { void sleep(); static void run() { System.out.println("I'm running!"); }}public class InterfaceCheckers implements DefaultInterface{ public void sleep() { System.out.println("Sleeping..."); } public static void main(String[] args) { InterfaceCheckers checker = new InterfaceCheckers(); DefaultInterface.run(); checker.sleep(); }}/* Output: I'm running! Sleeping... */
标记界面
这是一个空接口。例如,可序列化,可克隆和远程接口。
public interface Serializable { //No fields or methods}
它们帮助我们在Java中使用多重继承。
它们提供抽象。
它们提供松散的耦合:对象彼此独立。
强制子类实现抽象方法。
停止拥有该类的实际对象。
保持有班级参考。
注意:请记住,我们不能在界面内部保留通用代码。
如果要定义可能需要的方法和通用代码,请使用抽象类。
如果只想定义必需的方法,请使用interface。
多态是对象采取多种形式的能力。
当超类引用子类对象时,OOP中会发生多态。
所有Java对象都具有多个IS-A关系(至少所有对象都将通过IS-A测试,以了解其自身的类型和对象的类),因此被视为多态的。
我们可以通过引用变量访问对象。参考变量只能是一种类型。声明后,引用变量的类型无法更改。
引用变量可以声明为类或接口类型。
单个对象可以由许多不同类型的引用变量引用,只要它们是对象的相同类型或超类型即可。
如果一个类具有名称相同但参数不同的多个方法,则称为方法重载。
方法重载规则:
必须具有不同的参数列表。
可能有不同的返回类型。
可能具有不同的访问修饰符。
可能引发不同的异常。
class JavaProgrammer{ public void code() { System.out.println("Coding in C++"); } public void code(String language) { System.out.println("Coding in "+language); }}public class MethodOverloader { public static void main(String[] args) { JavaProgrammer gosling = new JavaProgrammer(); gosling.code(); gosling.code("Java"); }}/* Output: Coding in C++ Coding in Java */
注意:静态方法也可以重载。
class Addition { public static int add(int a,int b) { return a+b; } public static int add(int a,int b,int c) { return a+b+c; }}public class PolyTest { public static void main(String[] args) { System.out.println(Addition.add(5, 5)); System.out.println(Addition.add(2, 4, 6)); }}
注意:我们可以重载main()方法,但是Java虚拟机(JVM)调用main()方法,该方法接收String数组作为参数。
public class PolyTest { public static void main() { System.out.println("main()"); } public static void main(String args) { System.out.println("String args"); } public static void main(String[] args) { System.out.println("String[] args"); }}//Output: String[] args
编译器仅知道引用类型。
它只能在引用类型中查找方法。
输出方法签名。
在运行时,JVM遵循确切的运行时类型(对象类型)来查找方法。
必须将编译时方法签名与实际对象类中的方法匹配。
如果子类具有与超类中声明的方法相同的方法,则称为方法重写。
方法覆盖规则:
必须具有相同的参数列表。
必须具有相同的返回类型:尽管协变返回允许我们更改覆盖方法的返回类型。
不得具有限制性更强的访问修饰符:可以具有限制性更小的访问修饰符。
不得引发新的或更广泛的检查异常:可能引发较窄的检查异常,并且可能引发任何未检查的异常。
只能重写继承的方法(必须具有IS-A关系)。
方法覆盖的示例:
public class Programmer { public void code() { System.out.println("Coding in C++"); }}public class JavaProgrammer extends Programmer{ public void code() { System.out.println("Coding in Java"); }}public class MethodOverridder { public static void main(String[] args) { Programmer ben = new JavaProgrammer(); ben.code(); }}/* Output: Coding in Java */
注意:静态方法不能被覆盖,因为方法在运行时被覆盖。静态方法与类相关联,而实例方法与对象相关联。因此,在Java中,该main()
方法也不能被覆盖。
注意:构造函数可以重载,但不能覆盖。
class Person{ void eat() { System.out.println("Person is eating"); }}class Student extends Person{ void study() { System.out.println("Student is studying"); }}public class InheritanceChecker { public static void main(String[] args) { Person alex = new Person();//New Person "is a" Person alex.eat(); Student jane = new Student();//New Student "is a" Student jane.eat(); jane.study(); Person mary = new Student();//New Student "is a" Person mary.eat(); //Student chris = new Person(); //New Person isn't a Student. }}
在中Person mary = new Student();
,此对象创建非常好。
mary
是Person
类型引用变量,new Student()
它将创建一个新Student
对象。
mary
study()
在编译时无法访问,因为编译器仅知道引用类型。由于study()
引用类型类中没有,因此无法访问它。但是在运行时mary
将是Student
类型(运行时类型/对象类型)。
在这种情况下,我们可以说“在运行时mary
将是Student
类型,因此请允许我调用它” 来说服编译器。我们怎样才能说服这样的编译器?这是我们使用投射的地方。
我们可以在编译时创建mary
一个Student
类型,并可以study()
通过强制转换进行调用。
((Student)mary).study();
接下来,我们将介绍铸造。
Java类型转换分为两种类型:
加宽转换(隐式):自动类型转换。
缩小转换(显式):需要显式转换。
在基本long
类型中,是比更大的类型int
。像在对象中一样,父类的类型比子类大。
引用变量仅引用对象。强制转换引用变量不会更改堆上的对象,但会通过实例成员可访问性以另一种方式标记同一对象。
一,拓宽铸造
Superclass superRef = new Subclass();
二。缩小铸造
Subclass ref = (Subclass) superRef;
缩小范围时必须小心。缩小范围时,我们说服编译器进行编译而不会出现任何错误。如果我们错误地说服它,则会收到运行时错误(通常是ClassCastException
)。
为了正确执行缩小,我们使用instanceof
运算符。它检查IS-A关系。
class A { public void display(){ System.out.println("Class A"); }}class B extends A{ public void display(){ System.out.println("Class B"); }}public class Test { public static void main(String[] args) { A objA = new B(); if(objA instanceof B){ ((B)objA).display(); } }}/** * Output: Class B */
如前所述,在使用new
关键字创建对象时,我们必须记住一件重要的事情:引用类型应与对象类型相同或为父类型。