Java 面向对象

 

面向对象的三大特征

  •         封装 (Encapsulation)

  •         继承 (Inheritance)

  •         多态 (Polymorphism)

面向对象的三大核心特性:

可重用性:代码重复使用,减少代码量,提高开发效率。面向对象的三大基本特征(继承、封装和多态)都围绕这个核心。
可扩展性:指新的功能可以很容易地加入到系统中来,便于软件的修改。
客观理性:能够将功能与数据结合,方便管理。

 

面向过程和面向对象

面向过程:做某件事情都需要自己亲历亲为,按照步骤去完成                             C
面向对象:做某件事情不需要自己亲历亲为,只需指定特定的对象去完成即可    JAVA、C++

 

接下来,以一个经典的例子进行说明。
例:人把大象关进冰箱。

面向过程的分析过程:

  • 第一步:把冰箱门打开;
  • 第二步:将大象放进冰箱;
  • 第三步:把冰箱门关闭;

面向对象的分析过程:

  • 第一步:分析动作是由那些实体发出的;
  • //人 ,冰箱,大象
  • 第二步:定义主体,为其增加属性和功能;
  • //人,人需要有打开关闭冰箱,及将大象放入冰箱的功能;
  • //冰箱,冰箱需要具有能开门和关门的属性;
  • //大象,大象需要具有能够进入冰箱的功能

联系:
二者均可实现代码重用和模块化编程;但面向过程简单直接,容易理解(直男式解决方案),面向对象更为复杂,模块化程度更高。从开发角度来看,面向对象比面向过程复杂,从维护和扩展功能的角度上来看,面向对象更容易操作。

优缺点:

1、面向过程的优缺点

效率高, 面向过程强调代码的胆小精悍,善于结合数据结构来开发高效率的程序。
流程明确,具体步骤清楚,便于节点分析。
缺点是:需要深入的思考,耗费精力,代码重用性低,扩展能力差,维护起来难度比较高,对复杂业务来说,面向对象的模块话难度较高,耦合度也比较高。

2、面向对象的优缺点

结构清晰,程序便于模块化,结构化,抽象化,更加符合人类的思维方式;
封装性,将事务高度抽象,从而便于流程中的行为分析,也便于操作和自省; 
容易扩展,代码重用率高,可继承,可覆盖;
实现简单,可有效地减少程序的维护工作量,软件开发效率高。
面向对象的缺点:效率低,面向对象在面向过程的基础上高度抽象,从而和代码底层的直接交互非常少机会,从而不适合底层开发和游戏甚至多媒体开发;复杂性,对于事务开发而言,事务本身是面向过程的,过度的封装导致事务本身的复杂性提高。

 面向过程面向对象
设计思路自顶向下、层次化、分解自底向上、对象化、综合
程序单元函数模块对象
设计方法程序=算法+数据结构程序=对象=数据+方法
优点相互独立,代码共享,性能相对较高接近人的思维方式, 使用灵活,易维护、易复用、易扩展
缺点修改、维护困难性能相对较低


抽象

所谓的抽象,就是把同一类事物中共有的特征(属性)和行为(功能、方法)进行抽取,归纳,总结。

抽象的过程其实就是面向对象编程的核心思想

 

类:用来描述一类具有相同特征(属性)和相同行为(方法)的对象。(可以比喻为模板)

b0e95887450c4dfcb321b786d585d12a.png

通过上图创建对应的简单类:

public class Dog {
    String breed;
    int size;
    String colour;
    int age;
 
    void eat() {
    }
 
    void run() {
    }
 
    void sleep(){
    }
 
    void name(){
    }
}
  • Java中用class关键字来描述类

    • 成员属性(变量):对应的就是事物的属性
    • 成员方法:对象事物的行为
  • 类的定义:

public class 类名{
    //成员变量
    //成员方法
}

  • 定义类:就是定义类的成员,包括成员变量和成员方法
  • 类的成员:
    • 成员变量:和之前定义的变量几乎时一样的,只不过位置发生了改变,成员变量位于类中,任何方法之外。
    • 成员方法:和之前定义的方法几乎是一样的,只不过把static关键字去掉
  • 类和对象的关系:
    • 类是对象的抽象
    • 对象是类的实例化

对象

对象(Object)是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位。

它们是有形的,如一个人、一件物品;也可以是无形的,如一个计划、一次交易。(万事万物,皆对象

  • 创建对象

类名 对象名 = new 类名();

  • 使用对象:

        调用成员变量:

对象名.成员变量;

        调用成员方法:

对象名.方法名(参数列表);

  • 成员变量(属性)都有默认值,所以在声明成员变量时,可以不用初始化(默认值和数据类型有关:)        
数据类型默认值
byte0
short0
int0
long0
float0.0
double0.0
booleanfalse
char空字符
引用类型null

全局变量(成员变量)

  • 定义在方法的外部,类的内部。使用范围是整个类
  • 不需要初始值
  • 存储在堆内存中(对象存在时才在存在)

局部变量

(方法中的变量)

  • 定义在方法的内部或者某一个语句块的内部,适用范围仅限于方法内或者语句块内
  • 必须有初始值
  • 存储在栈内存

栈内存和堆内存的区别

成员方法

语法:

访问修饰符 返回值类型 方法名称(参数列表){
    方法体
}

    • 访问修饰符:public
    • 返回值类型由返回值决定
  • 成员变量可以直接在成员方法中使用,但是main方法中如果调用成员变量和方法必须通过对象.属性名\方法名(参数列表)的形式来调用

  • 成员方法之间的调用,直接写方法名(参数列表)即可

        访问修饰符:

8d6d5bb4af1445df9ae4aef4ac79a0cd.png

 

  • private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
  • default (即缺省,什么也不写,不使用任何关键字): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
  • protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
  • public : 对所有类可见。使用对象:类、接口、变量、方法

 

构造方法

对象一建立就会调用构造方法,可以创建对象,给成员变量(属性)初始化。  

  • 方法名必须与类名相同
  • 可以有 0 个、1 个或多个参数
  • 没有任何返回值,包括 void
  • 默认返回类型就是对象类型本身
  • 只能与 new 运算符结合使用

初识构造方法 

在没有学习构造方法之前,我们一般会这样写:(赋值)

class Student {
    String name;
    String gender;
    int age;
    long studentID;
}
public class TestDemo2 {
    public static void main(String[] args) {
        Student stu1 = new Student();
        stu1.name = "张三";                  // 给stu1对象的各个成员变量赋值
        stu1.gender = "男";
        stu1.age = 19;
        stu1.studentID = 231245431;
        // 打印输出
        System.out.println("姓名:" + stu1.name + " 性别:" + stu1.gender + " 年龄:" + stu1.age + " 学号:" + stu1.studentID);
    }
}

 在上述代码中,我们创建了一个学生类,类中包含:姓名、年级、年龄、学号四个变量。但是在赋值时候,需要在主函数中一个一个赋值,尤为繁琐。那怎么样才能一次性都赋值好呢?这就需要用到构造方法,构造方法能做到这一点。

 

来看一个例子:

class Student {
    String name;   //学生类的属性
    int age;
    public void eat() {
        System.out.println(name + "在吃饭");   // 学生类的行为(方法)
    }
    public Student() {
        System.out.println("这是自定义的一个不带参数的构造方法");
    }
}
public class TestDemo3 {
    public static void main(String[] args) {
        Student stu = new Student();
    }
}

运行结果如下:

ac8fb2ba18794c1780e488e086e3467e.png

        对于这个输出结果,你是否感到疑惑:在TestDemo3中没有调用构造方法,他怎么自己就调用了

Student stu=new Student();

原因就是:在类的对象被创建时,该构造方法将被自动调用!

        在使用new关键字进行实例化对象的时候,程序一定做了两件事:           

                调用合适的构造方法

                在堆区分配对象需要的内存

看到这,你可能又有问题了,我们之前实例化对象的时候明明没定义构造方法呀!怎么能说实例化对象时程序一定调用构造方法呢?

是这样的,当一个类中没有提供任何构造方法,系统默认提供一个无参数的构造方法,就像这样:

Student{

}

但如果类中显式地定义了一个或多个构造方法,则系统不再提供默认构造方法。

在一个类中,与类名相同的方法就是构造方法。每个类可以具有多个构造方法,但要求它们各自包含不同的方法参数,比如这样:

Student() {
    System.out.println("这是自定义的一个没有参数的构造方法");
}

Student(String name) {
    this.name = name;
    System.out.println("这是自定义的带有一个参数的构造方法1");
}

Student(String name,int age) {
    this.name = name;
    this.age=age;
    System.out.println("这是自定义的带有一个参数的构造方法2");
}
  • 该示例就定义了三个构造方法,分别是一个无参构造方法和两个有参构造方法。
  • 如果在一个类中定义多个具有不同参数的同名方法,称作方法的重载。
  • 这三个构造方法的名称都与类名相同,均为Student。在实例化该类时可以调用不同的构造方法进行初始化。

构造方法的使用

例如:

class Student {
    String name;
    String gender;
    int age;
    long studentID;
    // 该构造方法可由IDEA自动生成
    public Student(String name, String gender, int age, long studentID) {  
        this.name = name;
        this.gender = gender;
        this.age = age;
        this.studentID = studentID;
    }
}
public class TestDemo2 {
    public static void main(String[] args) {
        Student stu1 = new Student("张三", "男", 20, 2022130005);
        System.out.println("姓名:" + stu1.name + " 性别:" + stu1.gender + " 年龄:" + stu1.age + " 学号:" + stu1.studentID);
    }
}

这样就把所有的值都赋值好了~

另外,类中的构造方法可以通过idea自动生成:

在代码-生成-构造函数-选择需要的参数-确定

3bb6d2a68ddd4817bdd63b732f4a1208.png1e864605c6584ffa8aa685f69e29e92b.png

b773c3bf05e44a5ea78379caf38cd1d2.png1ff0d9a1eb5647dc83e55e5fed82b4cb.png

 

this

对于上面的代码,发现一个this.name,这个又是什么意思呢,不应该是 "对象名.成员变量" 吗?

首先,咱先来看一段代码

class Date {
    int year;
    int month;
    int day;
    // 通过构造函数传参来赋值
    public Date(int year, int month, int day) {
        year = year;
        month = month;
        day = day;
    }
 
    public void printDate() {
        System.out.println(year + "/" + month + "/" + day);
    }
}

 
public class TestDemo_date {
    public static void main(String[] args) {
        Date date1 = new Date(2024, 4, 15);
        date1.printDate();
    }
}

这段代码有什么问题吗?细心的同学可能以经发现了 :在Date类中,传入构造方法的参数名称和Date的成员变量名字相同,那么在Date类中的构造方法中就会出现一个问题:

public Date(int year, int month, int day) {
        // 那函数体中到底是谁给谁赋值?成员变量给成员变量?参数给参数?参数给成员变量?
        // 成员变量给参数?估计自己都搞不清楚了
        year = year;            // 当传入的参数相同是怎么办!
        month = month;
        day = day;
}

那上面的代码会输出2024/4/15吗?

那我们来看:

5a812145ba464fa881a7b205a100a53a.png

Intellij给出的警告:

da5a1fe8b65747a9aeb7b619f1f70e1f.png

输出竟然是默认值,我们明明在代码中给出赋值,但为什么又没有赋值成功呢?

解释:
        我们以为左边的变量是成员变量,但实际上,左边变量是局部变量!即,局部变量赋值给了局部变量,成员变量根本没变,依旧是默认值!

        方法中的变量为局部变量,存储在栈中,作用范围是方法内;我们想通过构造器初始化的是成员变量,存储在堆中,作用范围是本类内部。
当成员变量&局部变量重名时,优先使用局部变量。关键还是看有没有局部变量,有局部变量优先使用局部变量,否则属性一就近原则!

        其实当构造方法的参数与类所定义的属性同名时,对于方法中的year = year,虚拟机不清楚在这里year到底是传过来的参数还是对象的成员变量,那么为了让虚拟机知道我们是把参数2024赋值给当前对象的成员变量year,我们就需要借助java中this这个关键字:

class Date {
    int year;
    int month;
    int day;
    // 添加了this关键字的构造函数
    public Date(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }
 
    public void printDate() {
        System.out.println(year + "/" + month + "/" + day);
    }
}
 
public class TestDemo_date {
    public static void main(String[] args) {
        Date date1 = new Date(2022, 4, 2);
        date1.printDate();
    }
}

运行以下:

867d81dc73a241768d81a622c8aef700.png

成功!

this 是什么?

那,为什么,this.变量名=变量名 就赋值好了呢?

比较官方的解释是:

Java虚拟机(JVM)会给每个对象分配一个this,来代表当前对象

        this其实可以理解为对象的一个属性(成员变量),但是这个属性是隐藏的.即this相当于对象的一个隐藏属性。
        和对象的其他属性一样,在 new 一个新对象的时候,会在堆内存为对象分配空间,属性就储存在这份空间中。且该this属性的值就是对象在堆内存中地址,即this指向该对象(this代表该对象)
        综上:this是对象的隐藏属性(本质就是一个普通的成员变量),和其他 non-static属性一样,在创建对象的时候会为每个新对象分配该对象的专属成员变量(this就是其中一个),this这个成员变量存储在堆内存中,该变量的值是所属对象在堆内存的地址。

使用注意事项:

this关键字可以用来区分当前类的属性&局部变量
使用this关键字可以用来访问本类的实例属性、实例方法、构造器(“实例”指的就是 non-static 修饰 的)

  • 访问本类实例属性:this.属性
  • 访问本类实例方法:this.方法名(参数列表)
  • 访问本类构造器: this(参数列表)

【注意】: this(参数列表)来访问本类构造器需要注意以下几点

  • 只能在构造器中使用 this(参数列表);即在一个构造器中访问本类的另外一个构造器。(默认构造器行首是 super();,)。
  • 显示使用 this()时,默认的 super()就被覆盖
  • this(参数列表)和 super(参数列表)在构造器中有且只能存在一个。
  • 若在构造器中使用 this(参数列表),则此语句只能位于构造器第一行
  • 类中的静态方法 static method 中不能使用 this。很简单理解:statc方法中不能出现成员变量(this依赖对象,而static不依0赖于对象,类名.静杰方法 时,没有对象啊,你让this情何以堪!它都不知道自己属于哪个对象!)

封装

官方解释是这样:

在面向对象程式设计方法中,封装(英语:Encapsulation)是指,一种将抽象性函式接口的实作细节部份包装、隐藏起来的方法。

封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。

要访问该类的代码和数据,必须通过严格的接口控制。

封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。

适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。

简单来说就是,通过private、protected、public这三种访问修饰符对成员变量进行限制保护,然后通过getter和setter对成员变量进行访问和更改,保证了成员变量不被随意更改。

下面是一个例子:


public class Student
{
    private String name;
    private char sex;
    private long Stu_id;

    public String getName() {
        return name;
    }

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

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    public long getStu_id() {
        return Stu_id;
    }

    public void setStu_id(long stu_id) {
        Stu_id = stu_id;
    }


}

以上实例中public方法是外部类访问该类成员变量的入口。

通常情况下,这些方法被称为getter和setter方法。

因此,任何要访问类中私有成员变量的类都要通过这些getter和setter方法。

Getter和Setter

通过如下例子说明 如何被访问:

public class stu
{
    public static void main(String[] args) {
        Student stu=new Student();
        stu.setStu_id(202213);
        stu.setName("张三");
        stu.setSex('男');

        System.out.println("姓名:"+stu.getName()+",性别:"+stu.getSex()+",学号:"+stu.getStu_id());
    }
}

运行结果如下:

e7d3ff73e9be454191020debc7717195.png

另外,getter和setter方法可以通过idea自动生成,与构造方法生成类似,详细如下图:

1e37859bea404aaebbc831e35de6c7da.pngfe1892e1b2c2472c809ee2cb31dd2d41.png

这样,我们就得到了:

47905011aa884524bed7fb4e5acd6f55.png

这样简单几步,比一个个手打快,若是类中又有多个成员变量,则效率更快~

封装优化

  1. this关键字
  2. 构造方法

注意事项

1. 如果你不提供构造方法,系统会给出无参数构造方法。

2. 如果你提供了构造方法,系统将不再提供无参数构造方法。

3. 构造方法是可以重载的,既可以定义参数,也可以不定义参数。

 

继承

继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。继承可以理解为一个对象从另一个对象获取属性的过程。

如果类A是类B的父类,而类B是类C的父类,我们也称C是A的子类,类C是从类A继承而来的。在Java中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类

继承中最常使用的两个关键字是extends和implements。

这两个关键字的使用决定了一个对象和另一个对象是否是IS-A(是一个)关系。

通过使用这两个关键字,我们能实现一个对象获取另一个对象的属性。

所有Java的类均是由java.lang.Object类继承而来的,所以Object是所有类的祖先类,而除了Object外,所有类必须有一个父类。

通过过extends关键字可以申明一个类是继承另外一个类而来的,一般形式如下:

// A.java
public class A {
    private int i;
    protected int j;
 
    public void func() {
 
    }
}
 
// B.java
public class B extends A {
}

以上的代码片段说明,B由A继承而来的,B是A的子类。而A是Object的子类,这里可以不显示地声明。

作为子类,B的实例拥有A所有的成员变量,但对于private的成员变量B却没有访问权限,这保障了A的封装性。

继承的好处

  • 减少了代码的冗余,提高了代码的复用性
  • 便于功能的扩展
  • 为之后多态性的使用,提供了前提
  • 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。

一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。

811fb0ef049744ddb51d069407cf70b0.png

需要注意的是,父类中声明为private的属性或方法,子类继承父类以后,仍然获取了父类中私有的结构,只有因为封装性的影响,使得子类不能直接调用父类的结构而已。

子类继承父类以后,还可以声明自己特有的属性或方法,从而实现功能的拓展。

public class Test{
    public static void main(String[] args) {
        Student stu = new Student();
        stu.eat() //可以正常继承并且调用
        stu.sleep() //由于该方法在父类中使用private进行修饰,无法调用
    }
}
public class Person{
    String name;
    private int age;
    
    private void sleep(){	//私有的
        System.out.println("睡觉");
    }
    public void eat(){		//公共的
        System.out.println("吃饭");
    }
}
public class Student extends Person{
    String major;
    public void study(){
        System.out.println("学习");
    }
}

Java中关于继承性的规定

  • 一个类可以被多个子类继承。
  • Java中类的单继承性:一个类只能有一个父类
  • 子父类是相对的概念。
  • 子类直接继承的父类,称为:直接父类,而间接继承的父类称为:间接父类(所继承的父类又继承了其他类)
  • 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法

1655934735f34a909534f8830fe46954.png

另外,如果我们没有显式的声明一个类的父类的话,则此类继承于 java.lang.Object 类,所有的java类(除 java.lang.Object 类之外)都直接或间接的继承于java.lang.Object 类,意味着所有的 java 类具有 java.lang.Object 类声明的功能。

方法的重写(override/overwrite)

定义

在子类中可以根据需要对从父类中继承来的方法进行改造, 也称 为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。

要求

  • 子类重写的方法必须和父类被重写的方法具有相同的 方法名称参数列表
  • 子类重写的方法的返回值类型 不能大于 父类被重写的方法的 返回值类型
  • 子类重写的方法使用的访问权限 不能小于 父类被重写的方法的 访问权限
  • 子类方法抛出的异常不能大于父类被重写方法的异常
  • 子类不能重写父类中声明为 private 权限的方法

需要注意

子类与父类中同名同参数的方法必须同时声明为非 static 的(即为重写),或者同时声明为 static 的(不是重写),因为 static 方法是属于类的,子类无法覆盖父类的方法。

下面我们来看一个例子

public class Person { 
    public String name; 
    public int age;
    public String getInfo() {
        return "Name: "+ name + "\n" +"age: "+ age;
    }
}

public class Student extends Person {
    public String school;
    public String getInfo() { //重写方法
        return "Name: "+ name + "\nage: "+ age
            + "\nschool: "+ school;
    }
    public static void main(String args[]){
        Student s1=new Student();
        s1.name="Bob";
        s1.age=20;
        s1.school="school2";
        System.out.println(s1.getInfo()); 
    }
}

运行结果为:

Name:Bob age:20 school:school2

从输出的结果我们可以看出 Student 类重写了 Person 类中的 getInfo() 方法

super关键字

Java 类中使用 super 来调用父类中的指定操作:

  • super 可用于访问父类中定义的属性
  • super 可用于调用父类中定义的成员方法
  • super 可用于在子类构造器中调用父类的构造器

注意

尤其当子父类出现同名成员时, 可以用 super 表明调用的是父类中的成员

  • super 的追溯不仅限于直接父类
  • superthis 的用法相像, this 代表本类对象的引用, super 代表父类的内存空间的标识

在对象的内部使用,可以代表父类对象。

        1、访问父类的属性:super.age

        2、访问父类的方法:super.eat()

super的应用:

        首先我们知道子类的构造的过程当中必须调用父类的构造方法。其实这个过程已经隐式地使用了我们的super关键字。

        这是因为如果子类的构造方法中没有显示调用父类的构造方法,则系统默认调用父类无参的构造方法。

        那么如果自己用super关键字在子类里调用父类的构造方法,则必须在子类的构造方法中的第一行。

        要注意的是:如果子类构造方法中既没有显示调用父类的构造方法,而父类没有无参的构造方法,则编译出错。

(补充说明,虽然没有显示声明父类的无参的构造方法,系统会自动默认生成一个无参构造方法,但是,如果你声明了一个有参的构造方法,而没有声明无参的构造方法,这时系统不会动默认生成一个无参构造方法,此时称为父类有没有无参的构造方法。)

一些例子

1、关键字 super 举例:使用 super 调用父类的方法

class protected Person {
    String name = "张三"; 
    protected int age;
    public String getInfo() {
        return "Name: " + name + "\nage: " + age;
    }
}
class Student extends Person {
    protected String name = "李四";
    private String school = "New Oriental";
    public String getSchool() {
        return school;
    }
    public String getInfo() {
        //使用super调用父类的方法
        return super.getInfo() + "\nschool: " + school;
    }}
public class StudentTest {
    public static void main(String[] args) {
        Student st = new Student();
        System.out.println(st.getInfo());
    }
}

2、调用父类的构造器的例子

  • 子类中所有的构造器默认都会访问父类中空参数的构造器
  • 当父类中没有空参数的构造器时, 子类的构造器必须通过 this (参数列表) 或者 super (参数列表) 语句指定调用本类或者父类中相应的构造器。 同时, 只能 ”二选一” 且必须放在构造器的首行
  • 如果子类构造器中既未显式调用父类或本类的构造器, 且父类中又没有无参的构造器, 则编译出错
public class Person {
    private String name;
    private int age;
    private Date birthDate;
    public Person(String name, int age, Date d) {
        this.name = name;
        this.age = age;
        this.birthDate = d;
    }
    public Person(String name, int age) {
        this(name, age, null);
    }
    public Person(String name, Date d) {
        this(name, 30, d);
    }
    public Person(String name) {
        this(name, 30);
    }
}
public class Student extends Person {
    private String school;
    public Student(String name, int age, String s{
        super(name, age);
        school = s;
    }
    public Student(String name, String s) {
        super(name);
        school = s;
    }
    // 编译出错: no super(),系统将调用父类无参数的构造器。
    public Student(String s) {
        school = s;
    }
}

this和super的区别

8810da79c1e9434bbb86cb5b21cc5c17.png

多态

多态(Polymorphism)是面向对象编程的核心概念之一。它源于希腊语,意为“多种形态”。多态性使得我们可以使用通用的接口来表示不同的对象,并且能够在运行时确定对象的具体类型,从而调用相应的方法。

简单来说,多态就是指同一个方法名在不同的对象上有不同的行为。

多态有两种主要形式:编译时多态(静态多态)和运行时多态(动态多态)。

实现条件

  • 存在继承关系,即有父类和子类。
  • 子类必须重写父类的方法。这意味着子类提供了对父类方法的新实现。
  • 父类的引用可以指向子类的对象,这是向上转型的体现。

示例

class Animal {
    void makeSound() {
        System.out.println("Animal makes a sound.");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Dog barks.");
    }
}

class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("Cat meows.");
    }
}

在这个示例中,有一个父类Animal 和两个子类 Dog 和Cat。子类都重写了父类的makesound()方法。

现在,我们可以创建一个父类引用,但将其指向不同的子类对象,以实现多态性。

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();

        animal1.makeSound(); // 输出 "Dog barks."
        animal2.makeSound(); // 输出 "Cat meows."
    }
}

在这个示例中,我们创建了两个父类引用 animal1 和 animal2 ,分别指向 Dog 和 Cat 对象。当我们调用makesound() 方法时,根据对象的实际类型,将执行相应子类的方法。

这就是多态的体现:相同的方法调用产生了不同的行为,具体的实现取决于对象的类型。

instanceof 运算符

在某些情况下,我们需要在运行时检查对象的类型,以便根据对象的类型采取不同的行动。这时可以使用 instanceof 运算符。

instanceof 运算符用于检查一个对象是否是特定类的实例,或者是否是其子类的实例。它的语法如下:

object instanceof Class

如果 objectClass 类的一个实例,或者是 Class 类的子类的一个实例,instanceof 运算符返回 true,否则返回 false

让我们通过一个示例来演示 instanceof 运算符的使用:

class Animal {
    void makeSound() {
        System.out.println("Animal makes a sound.");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Dog barks.");
    }
}

class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("Cat meows.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();

        if (animal1 instanceof Dog) {
            System.out.println("animal1 is a Dog.");
        }

        if (animal2 instanceof Cat) {
            System.out.println("animal2 is a Cat.");
        }
    }
}

在这个示例中,我们使用 instanceof 运算符检查 animal1 是否是 Dog 类的实例,以及 animal2 是否是 Cat 类的实例。根据检查结果,我们可以执行相应的操作。

多态的优点

多态性是面向对象编程的核心特性之一,具有许多优点,包括:

  1. 代码重用: 可以使用通用的接口来操作不同类型的对象,从而减少了代码的重复编写。
  2. 灵活性: 可以轻松地扩展程序,添加新的子类而无需修改现有的代码。
  3. 可维护性: 通过多态,我们可以将代码组织得更加清晰和易于维护。
  4. 简化接口: 多态性允许我们使用通用接口,而不必关心对象的具体类型。
  5. 提高代码的可读性: 代码更易于理解,因为它更符合现实世界的模型。

多态的实际应用

多态性在实际应用中广泛使用,特别是在面向对象编程的领域。以下是一些多态的实际应用场景:

  1. 图形绘制: 图形绘制程序可以使用多态性来处理不同类型的图形对象,如圆形、矩形和三角形。
  2. 汽车制造: 汽车制造公司可以使用多态性来处理不同型号和品牌的汽车,从而简化生产流程。
  3. 动态插件: 软件应用程序可以使用多态性来支持动态加载和卸载插件,从而增加灵活性。
  4. 电子商务: 电子商务平台可以使用多态性来处理不同类型的商品和付款方式。
  5. 游戏开发 游戏开发中的角色和道具可以使用多态性来实现不同的行为。

总结

多态是 Java 面向对象编程的重要概念,它允许我们在不同的对象上调用相同的方法,实现代码的重用和可扩展性。在多态性的背后是方法的重写和动态绑定的机制。通过向上转型和 instanceof 运算符,我们可以更好地利用多态性。

希望本篇博客帮助你理解多态的概念和实现方式,并能够在实际编程中灵活运用多态性来提高代码的质量和可维护性。多态是 Java 编程中的一个强大工具,可以让你的代码更加灵活和易于扩展。

接口

接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。

接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在Java中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

接口与类

接口与类相似点:

  • 一个接口可以有多个方法。
  • 接口文件保存在.java结尾的文件中,文件名使用接口名。
  • 接口的字节码文件保存在.class结尾的文件中。
  • 接口相应的字节码文件必须在与包名称相匹配的目录结构中。

接口与类的区别:

  • 接口不能用于实例化对象。
  • 接口没有构造方法。
  • 接口中所有的方法必须是抽象方法。
  • 接口不能包含成员变量,除了static和final变量。
  • 接口不是被类继承了,而是要被类实现。
  • 接口支持多重继承。

注意事项

  1.   接口不能被实例化
  2.   接口中所有的方法都是public方法,接口中抽象方法,可以不用abstract修饰
  3.   一个普通类实现接口,就必须将该接口的所有方法都实现(可使用alt+enter快捷键)
  4.   抽象类实现接口,可以不用实现接口的方法
  5.   一个类同时可以实现多个接口
  6.   接口中的属性只能是final,而且是 public static final修饰符
  7.   接口中属性的访问形式:接口名.属性名
  8.   接口不能继承其他的类,但是可以继承多个别的接口
  9.   接口的修饰符只能是public和默认,这点和类的修饰符是一样的

---持续更新中---

  • 29
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值