面向对象的三大特征
-
封装 (Encapsulation)
-
继承 (Inheritance)
-
多态 (Polymorphism)
面向对象的三大核心特性:
可重用性:代码重复使用,减少代码量,提高开发效率。面向对象的三大基本特征(继承、封装和多态)都围绕这个核心。
可扩展性:指新的功能可以很容易地加入到系统中来,便于软件的修改。
客观理性:能够将功能与数据结合,方便管理。
面向过程和面向对象
面向过程:做某件事情都需要自己亲历亲为,按照步骤去完成 C
面向对象:做某件事情不需要自己亲历亲为,只需指定特定的对象去完成即可 JAVA、C++
接下来,以一个经典的例子进行说明。
例:人把大象关进冰箱。
面向过程的分析过程:
- 第一步:把冰箱门打开;
- 第二步:将大象放进冰箱;
- 第三步:把冰箱门关闭;
面向对象的分析过程:
- 第一步:分析动作是由那些实体发出的;
- //人 ,冰箱,大象
- 第二步:定义主体,为其增加属性和功能;
- //人,人需要有打开关闭冰箱,及将大象放入冰箱的功能;
- //冰箱,冰箱需要具有能开门和关门的属性;
- //大象,大象需要具有能够进入冰箱的功能
联系:
二者均可实现代码重用和模块化编程;但面向过程简单直接,容易理解(直男式解决方案),面向对象更为复杂,模块化程度更高。从开发角度来看,面向对象比面向过程复杂,从维护和扩展功能的角度上来看,面向对象更容易操作。
优缺点:
1、面向过程的优缺点
效率高, 面向过程强调代码的胆小精悍,善于结合数据结构来开发高效率的程序。
流程明确,具体步骤清楚,便于节点分析。
缺点是:需要深入的思考,耗费精力,代码重用性低,扩展能力差,维护起来难度比较高,对复杂业务来说,面向对象的模块话难度较高,耦合度也比较高。
2、面向对象的优缺点
结构清晰,程序便于模块化,结构化,抽象化,更加符合人类的思维方式;
封装性,将事务高度抽象,从而便于流程中的行为分析,也便于操作和自省;
容易扩展,代码重用率高,可继承,可覆盖;
实现简单,可有效地减少程序的维护工作量,软件开发效率高。
面向对象的缺点:效率低,面向对象在面向过程的基础上高度抽象,从而和代码底层的直接交互非常少机会,从而不适合底层开发和游戏甚至多媒体开发;复杂性,对于事务开发而言,事务本身是面向过程的,过度的封装导致事务本身的复杂性提高。
面向过程 | 面向对象 | |
---|---|---|
设计思路 | 自顶向下、层次化、分解 | 自底向上、对象化、综合 |
程序单元 | 函数模块 | 对象 |
设计方法 | 程序=算法+数据结构 | 程序=对象=数据+方法 |
优点 | 相互独立,代码共享,性能相对较高 | 接近人的思维方式, 使用灵活,易维护、易复用、易扩展 |
缺点 | 修改、维护困难 | 性能相对较低 |
抽象
所谓的抽象,就是把同一类事物中共有的特征(属性)和行为(功能、方法)进行抽取,归纳,总结。
抽象的过程其实就是面向对象编程的核心思想
类
类:用来描述一类具有相同特征(属性)和相同行为(方法)的对象。(可以比喻为模板)
通过上图创建对应的简单类:
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 类名();
- 使用对象:
调用成员变量:
对象名.成员变量;
调用成员方法:
对象名.方法名(参数列表);
- 成员变量(属性)都有默认值,所以在声明成员变量时,可以不用初始化(默认值和数据类型有关:)
数据类型 | 默认值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0 |
float | 0.0 |
double | 0.0 |
boolean | false |
char | 空字符 |
引用类型 | null |
全局变量(成员变量)
- 定义在方法的外部,类的内部。使用范围是整个类
- 不需要初始值
- 存储在堆内存中(对象存在时才在存在)
局部变量
(方法中的变量)
- 定义在方法的内部或者某一个语句块的内部,适用范围仅限于方法内或者语句块内
- 必须有初始值
- 存储在栈内存中
成员方法
语法:
访问修饰符 返回值类型 方法名称(参数列表){
方法体
}
-
- 访问修饰符:public
- 返回值类型由返回值决定
-
成员变量可以直接在成员方法中使用,但是main方法中如果调用成员变量和方法必须通过对象.属性名\方法名(参数列表)的形式来调用
-
成员方法之间的调用,直接写方法名(参数列表)即可
访问修饰符:
- 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();
}
}
运行结果如下:
对于这个输出结果,你是否感到疑惑:在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自动生成:
在代码-生成-构造函数-选择需要的参数-确定
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吗?
那我们来看:
Intellij给出的警告:
输出竟然是默认值,我们明明在代码中给出赋值,但为什么又没有赋值成功呢?
解释:
我们以为左边的变量是成员变量,但实际上,左边变量是局部变量!即,局部变量赋值给了局部变量,成员变量根本没变,依旧是默认值!
方法中的变量为局部变量,存储在栈中,作用范围是方法内;我们想通过构造器初始化的是成员变量,存储在堆中,作用范围是本类内部。
当成员变量&局部变量重名时,优先使用局部变量。关键还是看有没有局部变量,有局部变量优先使用局部变量,否则属性一就近原则!
其实当构造方法的参数与类所定义的属性同名时,对于方法中的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();
}
}
运行以下:
成功!
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());
}
}
运行结果如下:
另外,getter和setter方法可以通过idea自动生成,与构造方法生成类似,详细如下图:
这样,我们就得到了:
这样简单几步,比一个个手打快,若是类中又有多个成员变量,则效率更快~
封装优化
- this关键字
- 构造方法
注意事项
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中声明的所有的属性和方法。
需要注意的是,父类中声明为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中类的单继承性:一个类只能有一个父类
- 子父类是相对的概念。
- 子类直接继承的父类,称为:直接父类,而间接继承的父类称为:间接父类(所继承的父类又继承了其他类)
- 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
另外,如果我们没有显式的声明一个类的父类的话,则此类继承于 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
的追溯不仅限于直接父类super
和this
的用法相像,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的区别
多态
多态(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
如果 object
是 Class
类的一个实例,或者是 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
类的实例。根据检查结果,我们可以执行相应的操作。
多态的优点
多态性是面向对象编程的核心特性之一,具有许多优点,包括:
- 代码重用: 可以使用通用的接口来操作不同类型的对象,从而减少了代码的重复编写。
- 灵活性: 可以轻松地扩展程序,添加新的子类而无需修改现有的代码。
- 可维护性: 通过多态,我们可以将代码组织得更加清晰和易于维护。
- 简化接口: 多态性允许我们使用通用接口,而不必关心对象的具体类型。
- 提高代码的可读性: 代码更易于理解,因为它更符合现实世界的模型。
多态的实际应用
多态性在实际应用中广泛使用,特别是在面向对象编程的领域。以下是一些多态的实际应用场景:
- 图形绘制: 图形绘制程序可以使用多态性来处理不同类型的图形对象,如圆形、矩形和三角形。
- 汽车制造: 汽车制造公司可以使用多态性来处理不同型号和品牌的汽车,从而简化生产流程。
- 动态插件: 软件应用程序可以使用多态性来支持动态加载和卸载插件,从而增加灵活性。
- 电子商务: 电子商务平台可以使用多态性来处理不同类型的商品和付款方式。
- 游戏开发: 游戏开发中的角色和道具可以使用多态性来实现不同的行为。
总结
多态是 Java 面向对象编程的重要概念,它允许我们在不同的对象上调用相同的方法,实现代码的重用和可扩展性。在多态性的背后是方法的重写和动态绑定的机制。通过向上转型和 instanceof
运算符,我们可以更好地利用多态性。
希望本篇博客帮助你理解多态的概念和实现方式,并能够在实际编程中灵活运用多态性来提高代码的质量和可维护性。多态是 Java 编程中的一个强大工具,可以让你的代码更加灵活和易于扩展。
接口
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在Java中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
接口与类
接口与类相似点:
- 一个接口可以有多个方法。
- 接口文件保存在.java结尾的文件中,文件名使用接口名。
- 接口的字节码文件保存在.class结尾的文件中。
- 接口相应的字节码文件必须在与包名称相匹配的目录结构中。
接口与类的区别:
- 接口不能用于实例化对象。
- 接口没有构造方法。
- 接口中所有的方法必须是抽象方法。
- 接口不能包含成员变量,除了static和final变量。
- 接口不是被类继承了,而是要被类实现。
- 接口支持多重继承。
注意事项
- 接口不能被实例化
- 接口中所有的方法都是public方法,接口中抽象方法,可以不用abstract修饰
- 一个普通类实现接口,就必须将该接口的所有方法都实现(可使用alt+enter快捷键)
- 抽象类实现接口,可以不用实现接口的方法
- 一个类同时可以实现多个接口
- 接口中的属性只能是final,而且是 public static final修饰符
- 接口中属性的访问形式:接口名.属性名
- 接口不能继承其他的类,但是可以继承多个别的接口
- 接口的修饰符只能是public和默认,这点和类的修饰符是一样的
---持续更新中---