soword科技言
永久公益免费API接口
提供永久免费的API接口,查看更多API接口,如果您有其他免费API资源,请联系我们,造福人类。
提供商务开发:小程序,系统,APP
定制开发,免费评估,免费咨询,价格便宜,售后保障,前往开发服务中心联系开发客服中心
Java中的面向对象的编程原理:面向初学者的OOP概念
面向对象的编程提供了一种可持续的方式来编写意大利面条式代码。它使您可以将程序作为一系列补丁来添加。
保罗·格雷厄姆

面向对象编程的基础

面向对象的编程是一种编程范例,其中所有内容都表示为一个对象。

对象之间传递消息。每个对象决定如何处理收到的消息。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();
}}

像上面的代码示例一样,我们可以定义我们的类,实例化它(创建对象)并指定这些对象的状态和行为。

现在,我们已经介绍了面向对象编程的基础知识。让我们继续进行面向对象编程的原理。

面向对象编程的原理

这是面向对象编程范例的四个主要原理。了解它们对于成为一名成功的程序员至关重要。

  1. 封装形式

  2. 遗产

  3. 抽象化

  4. 多态性

现在,让我们更详细地看看每个。

封装形式

封装是将代码和数据包装到一个单元中的过程。

这就像一个包含多种药物的胶囊,是一种有助于保护实例变量的技术。

这可以通过使用private类以外的任何东西都不能访问的访问修饰符来实现为了安全地访问私有国家,我们必须提供公共获取和设置方法。(在Java中,这些方法应遵循JavaBeans命名标准。)

假设有一家唱片店,出售不同艺术家的音乐专辑,并管理着唱片商。

图4:没有封装的类图

如果您查看图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

专辑的价格和份数不能为负值。我们如何避免这种情况?这是我们使用封装的地方。

图5:带有封装的类图

在这种情况下,我们可以阻止库存管理者分配负值。如果他们尝试为相册的价格和份数分配负值,我们会将它们分配为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

通过封装,我们阻止了库存管理者分配负值,这意味着我们可以控制数据。

Java封装的优点

  1. 我们可以将类设为只读:对于只读类,我们应该仅提供getter方法。对于只写类,我们应该只提供setter方法。

  2. 控制数据:我们可以通过向setter方法提供逻辑来控制数据,就像在上面的示例中限制了库存管理者分配负值一样。

  3. 数据隐藏:其他类无法直接访问类的私有成员。

遗产

假设我们上面讨论的唱片店也出售蓝光电影。

图6:电影,StockKeeper类图

正如你可以在上图看到,有许多共同的状态和行为之间(通用代码)AlbumMovie

在代码中实现此类图时,您是否要编写(或复制并粘贴)整个代码Movie如果这样做,您就是在重复自己。如何避免代码重复?

这就是我们使用继承的地方。

继承是一种机制,其中一个对象获取父对象的所有状态和行为。

继承使用父子关系(IS-A关系)。

那么到底是什么继承了呢?

可见性/访问修饰符会影响从一个类继承到另一类的内容。

在Java中,根据经验,我们创建实例变量private和实例方法public

在这种情况下,我们可以肯定地说以下内容是继承的:

  1. 公共实例方法。

  2. 私有实例变量(只能通过public getter和setter方法访问私有实例变量)。

Java中的继承类型

Java有五种继承类型。它们是单个,多层,分层,多个和混合的。

类允许单,多级和分层继承。接口允许多重继承和混合继承。

图7:Java继承类型

一个类只能扩展一个类,但是它可以实现任何数量的接口。一个接口可以扩展多个接口。

图8:解释继承关键字。

人际关系

一,IS-A关系

IS-A关系是指继承或实现。

一个。概括

泛化使用从专门化类到泛化类的IS-A关系。

图9:泛化图

二。HAS-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");
}}

图10:类图显示了泛化关系

StudentHAS-A ContactInfoContactInfo可以在其他地方使用-例如,公司的Employee类也可以使用ContactInfo该类。因此Student可以没有可以存在ContactInfoContactInfo可以没有就可以存在Student这种类型的关系称为聚合。

b。组成

在这种关系中,没有类A就不能存在B类,但是没有B 就可以存在类。

为了让您对合成有所了解,让我们看一看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");
}}

图11:类图显示了组成关系

StudentHAS-A StudentIdStudent没有可以存在,StudentId没有StudentId就不能存在Student这种关系称为组合。

现在,让我们回到上面讨论的以前的唱片店示例。

图12:具有继承的类图

我们可以用Java实现此图,以避免代码重复。

继承的优点

  1. 代码重用:子类继承父类的所有实例成员。

  2. 您可以更灵活地更改代码:就地更改代码就足够了。

  3. 您可以使用多态:方法覆盖需要IS-A关系。

抽象化

抽象是隐藏实现细节并仅向用户显示功能的过程。

提取的一个常见示例是,按下加速器会提高汽车的速度。但是驾驶员不知道踩油门踏板会如何提高速度–他们不必知道这一点。

从技术上讲,抽象意味着一些不完整的东西或将在以后完成。

在Java中,我们可以通过两种方式实现抽象:抽象类(0到100%)和接口(100%)。

关键字abstract可以应用于类和方法。abstractfinalstatic永远不会在一起。

一,抽象类

抽象类是包含关键字的类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();
}}

图13:类图,显示了抽象类和具体类之间的关系

我们什么时候要将一个类标记为抽象?

  1. 强制子类实现抽象方法。

  2. 停止拥有该类的实际对象。

  3. 保持有班级参考。

  4. 保留通用的类代码。

接口

接口是类的蓝图。

接口是100%抽象的。此处不允许使用构造函数。它表示IS-A关系。

注意:接口仅定义必需的方法。我们无法保留通用代码。

接口只能有抽象方法,不能有具体方法。默认情况下,接口方法是publicabstract因此,在界面内部,我们无需指定publicabstract

因此,当一个类实现接口的方法而未指定该方法的访问级别时,编译器将抛出错误说明“Cannot reduce the visibility of the inherited method from interface”因此,必须将实现的方法的访问级别设置为public

默认情况下,接口变量publicstaticfinal

例如:

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();
}}

图14:类图,显示了接口和具体类之间的关系

接口中的默认方法和静态方法

通常,我们在单独的类中实现接口方法。假设我们需要在接口中添加新方法。然后,我们也必须在单独的类中实现该方法。

为了克服这个问题,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中使用多重继承。

  • 它们提供抽象。

  • 它们提供松散的耦合:对象彼此独立。

什么时候要将类更改为接口?

  1. 强制子类实现抽象方法。

  2. 停止拥有该类的实际对象。

  3. 保持有班级参考。

注意:请记住,我们不能在界面内部保留通用代码。

如果要定义可能需要的方法和通用代码,请使用抽象类

如果只想定义必需的方法,请使用interface

多态性

多态是对象采取多种形式的能力。

当超类引用子类对象时,OOP中会发生多态。

所有Java对象都具有多个IS-A关系(至少所有对象都将通过IS-A测试,以了解其自身的类型和对象的类),因此被视为多态的。

我们可以通过引用变量访问对象。参考变量只能是一种类型。声明后,引用变量的类型无法更改。

引用变量可以声明为类或接口类型。

单个对象可以由许多不同类型的引用变量引用,只要它们是对象的相同类型超类型即可

方法重载

如果一个类具有名称相同但参数不同的多个方法,则称为方法重载。

方法重载规则:

  1. 必须具有不同的参数列表。

  2. 可能有不同的返回类型。

  3. 可能具有不同的访问修饰符。

  4. 可能引发不同的异常。

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

多态性要遵循的规则

编译时间规则

  1. 编译器仅知道引用类型。

  2. 它只能在引用类型中查找方法。

  3. 输出方法签名。

运行时间规则

  1. 在运行时,JVM遵循确切的运行时类型(对象类型)来查找方法。

  2. 必须将编译时方法签名与实际对象类中的方法匹配。

方法覆盖

如果子类具有与超类中声明的方法相同的方法,则称为方法重写。

方法覆盖规则:

  1. 必须具有相同的参数列表。

  2. 必须具有相同的返回类型:尽管协变返回允许我们更改覆盖方法的返回类型。

  3. 不得具有限制性更强的访问修饰符:可以具有限制性更小的访问修饰符。

  4. 不得引发新的或更广泛的检查异常:可能引发较窄的检查异常,并且可能引发任何未检查的异常。

  5. 只能重写继承的方法(必须具有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();,此对象创建非常好。

maryPerson类型引用变量,new Student()它将创建一个新Student对象。

marystudy()在编译时无法访问,因为编译器仅知道引用类型。由于study()引用类型类中没有,因此无法访问它。但是在运行时mary将是Student类型(运行时类型/对象类型)。

在这种情况下,我们可以说“在运行时mary将是Student类型,因此请允许我调用它” 来说服编译器我们怎样才能说服这样的编译器?这是我们使用投射的地方。

我们可以在编译时创建mary一个Student类型,并可以study()通过强制转换进行调用

((Student)mary).study();

接下来,我们将介绍铸造。

对象类型转换

Java类型转换分为两种类型:

  1. 加宽转换(隐式):自动类型转换。

  2. 缩小转换(显式):需要显式转换。

在基本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关键字创建对象时,我们必须记住一件重要的事情:引用类型应与对象类型相同为父类型。



2023-03-22 10:04:19

新人小程序+APP定制199元起


发放福利,助力中小企业发展,真正在互联网中受益

点击询问定制

广告服务展示