面向对象的编程提供了一种可持续的方式来编写意大利面条式代码。它使您可以将程序作为一系列补丁来添加。
保罗·格雷厄姆
面向对象的编程是一种编程范例,其中所有内容都表示为一个对象。
对象之间传递消息。每个对象决定如何处理收到的消息。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直接访问类的状态。Albumpublic
如果库存管理员创建相册并将状态设置为负值怎么办?这可以由库存管理员有意或无意地完成。
为了说明,让我们看一个解释上面的图和语句的示例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");
}}
StudentHAS-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");
}}
StudentHAS-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对象。
marystudy()在编译时无法访问,因为编译器仅知道引用类型。由于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关键字创建对象时,我们必须记住一件重要的事情:引用类型应与对象类型相同或为父类型。