-
2019-04-10 20:42:12
面向对象编程的三大特征:封装、继承、多态
一:封装
对于封装而言,一个对象它所封装的是自己的属性和方法,所以他是不需要依赖其他对象就可以完成自己的操作。
封装的三大好处:
1、良好的封装能够减少耦合。
2、类内部的结构可以自由修改。
3、可以对成员进行更精确的控制。
4、隐藏信息,实心细节。
封装可以使我们容易的修改类的内部实现,二无需修改使用了该类的客户代码。
二:继承
继承是使用已存在的类定义作为基础建立新类的技术,新类的定义课增加新的数据或新的功能,也可以用父类的功能,但不能选择性的继承父类。
继承定义了类如何相互关联,共享特征。对于若干相同或者相识的类,我们可以抽象出他们共有的行为或者属相并且将其定义成一个父类或者超类,然后用这些类继承改父类,他们不仅可以拥有父类的属性,方法,还可以定义自己独有的属性或者方法。
继承需要记住三句话:
1、子类拥有父类非private的属性和方法。
2、子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
3、子类可以用自己的方式实现父类的方法。
继承少不了三个东西:构造器、protected关键字、向上转型
构造器(Constructor):
除了private的属性和方法不能被继承,还有一样子类继承不了------构造器。构造器只能被调动,不能被继承,调用父类的构造器方法,我们使用super()即可。对于继承,子类会默认调用父类的构造器,但是如果没有默认的父类构造器,子类必须显示的指定父类的构造器,而且必须是在子类构造器中做的第一件事。
在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。
protected关键字:
对于protected而言,他指明就类用户而言,他是private,但是对于任何继承与此类的子类而言或者其他任何位于同一个包的类而言,他确实可以访问的。
三:多态
实现多态有三个必要条件:继承,重写,向上转型
继承:在多态中必须存在有继承关系的子类和父类。
重写:子类对父类中某些方法进行重新定义,在调用者些方法时就会调用子类的方法。
向上转型:在多态中需要将子类的引用赋给父类对象,只有这样改引用才能够具备既能调用父类的方法和子类的方法。
多态问题????????????
两个子类A\B继承了父类,A子类要进行微小改变,B子类不需要改变,完全继承父类。所以对A积蓄方法重写,不完全继承父类,从而实现多态。
实现形式:
在Java中两种形式可以实现多态:继承和接口
基于继承实现多态:
实现机制主要表现在父类和继承该父类的一个或者多个子类对某些方法的重写,多个子类对同一方法重现可以表现不同的行为。
当子类重写父类的方法被调用时,只有对象继承链中最末端的方法才会被调用。
对于应用子类的父类类型,在吹引用时,他适用于继承该福诶的所有子类,子类对象不同,对方法的实现也就不同,执行相同动作产生的行为也就不同。
如果父类是抽象类,那么子类必须实现父类中所有的抽象方法,这样改父类所有的子类一定存在同一的对外接口,但其内部的具体实现可以各异。这样我们就可以使用顶层类提供的同意接口处理该层次的方法。基于接口实现多态: 继承是通过重写父类的同一方法的几个不同子类来体现的,那么就是通过实现接口并覆盖接口同一方法的几个不同类体现的。 在接口的多态中,只想接口引用必须是指定这实现了接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。
继承都是单继承,只能为一组相关的类提供一致的服务接口。但是接口可以是多继承,多实现,他能够录用一组相关或者不相关的接口进行组合与扩充,能够对外提供一致的服务接口。
重载和重写的区别?
-
重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
-
重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。
更多相关内容 -
-
Java面向对象编程三大特征 - 封装
2020-03-25 20:45:49本文关键字:Java、面向对象、三大特征、封装。封装是面向对象编程中的三大特征之一,在对封装性进行解释时我们有必要先了解一些面向对象的思想,以及相关的概念。写在前面:博主是一只经过实战开发历练后投身培训事业的“小山猪”,昵称取自动画片《狮子王》中的“彭彭”,总是以乐观、积极的心态对待周边的事物。本人的技术路线从Java全栈工程师一路奔向大数据开发、数据挖掘领域,如今终有小成,愿将昔日所获与大家交流一二,希望对学习路上的你有所助益。同时,博主也想通过此次尝试打造一个完善的技术图书馆,任何与文章技术点有关的异常、错误、注意事项均会在末尾列出,欢迎大家通过各种方式提供素材。
- 对于文章中出现的任何错误请大家批评指出,一定及时修改。
- 有任何想要讨论和学习的问题可联系我:zhuyc@vip.163.com。
- 发布文章的风格因专栏而异,均自成体系,不足之处请大家指正。
Java面向对象编程三大特征 - 封装
本文关键字:Java、面向对象、三大特征、封装
封装是面向对象编程中的三大特征之一,在对封装性进行解释时我们有必要先了解一些面向对象的思想,以及相关的概念。当我们想要去描述一系列的关系时我们要用到的最基本结构就是类,其中存在着成员变量和方法,用于记录属性和表达行为,相关知识请进传送门: Java中的基本操作单元 - 类和对象。一、名词解读
为了解释封装的概念和作用,需要先来了解一下几个相关的概念,这有助于我们接下来的理解。
1. 权限修饰符
当我们在一个类中定义成员变量时,会指定一个变量的类型,除此之外,还会有修饰符的部分,在此给出定义成员变量的规范格式:
// 定义变量 [修饰符] 变量类型 变量名称; [修饰符] 变量类型 变量名称 = 初始值;
修饰符起到的作用从字面就可以解释,起到一个修饰和限定的作用,可以使用在成员变量之前的修饰符可以是:public、protected、private、final、static。
修饰符与修饰符之间的顺序没有强制要求,其中public、protected、private被称为权限修饰符,可以用来限定类的属性和方法的访问权限,指明在哪些包的哪些类中能够调用到这些属性或方法,是一种一定会存在的修饰符。需要注意的是,这三个单词不能同时出现,当这三个单词都不出现的时候会被认为是默认访问权限,所以权限修饰符一共有四种:private、默认、protected、public。2. 权限对应关系表
- private:私有权限,只能在定义的类中访问,在其他类中创建的实例均无法访问
- 默认:同包可访问权限,在没有声明权限修饰符时为默认权限,允许在同包的其他类访问
- protected:受保护权限,允许有继承关系的子类访问
- public:公共权限,允许任何类访问
3. 属性访问
由于权限修饰符在封装特性中的作用只是实现封装性的一种途径,所以在这里只演示private与public的作用,权限修饰符的其他作用将在后续的文章中继续介绍。
src └──edu └──sandtower └──bean │ Person.java └──test │ Test.java
以上为实体类与测试类所在的目录结构,Person实体类所在包:edu.sandtower.bean,Test测试类所在包:edu.sandtower.test,相应代码如下:
package edu.sandtower.bean; public class Person{ // 声明公开属性 public String name; // 声明私有属性 private double money; }
package edu.sandtower.test; import edu.sandtower.bean.Person; public class Test{ public static void main(String[] args){ // 在test包中的Test类中创建Person实例 Person person = new Person(); person.name = "小张";// 编译通过,可以访问name属性 person.money = 500.0;// 编译失败,无法访问money属性 } }
从上面的例子可以看出,虽然依然是使用Person自己的实例在进行属性的调用,但是我们是在另外一个包中的类发生的调用,所以是不能够访问到private修饰的属性的,在刚开始学习时一定要注意区分。
二、概念阐释
1. 封装有什么用
通过使用权限修饰符,我们可以限定类的成员的被访问权限,那为什么要这样做呢?在很多场景下,我们需要确保我们对属性值的操作均是有效操作,不能违背某些规则。
比如,我们定义了一个Person类,具有name和money两个属性,在买东西时需要扣掉相应的金额,原始写法如下:public class Person{ public String name; public double money; }
public class Test{ public static void main(String[] args){ Person person = new Person(); person.money = 500;// 初始金额500元 System.out.println("购买一张桌子,花费200元"); person.money -= 200; System.out.println("购买二手PSP,花费350元"); person.money -= 350; System.out.println("目前余额为:" + person.money);// -50 } }
可以看到,经过代码操作以后可能会导致money的属性为负。看官甲:你自己不加判断赖代码?没错,这个问题我们可以增加判断代码来解决,由于这个操作是对money属性值的操作,我们将它封装成一个方法写在实体类中,于是有了改进之后的代码:
public class Person{ public String name; public double money; // 定义一个方法,用于设置money属性的值 public void setMoney(double money){ if(money >= 0){ this.money = money; } } }
public class Test{ public static void main(String[] args){ Person person = new Person(); person.money = 500;// 初始金额500元 System.out.println("购买一张桌子,花费200元"); person.setMoney(person.money - 200); System.out.println("购买二手PSP,花费350元"); person.setMoney(person.money - 350); System.out.println("目前余额为:" + person.money);// 300 } }
经过上面的改进,我们可以确保money的值不为负数,同时可以看到,当在实体类中定义方法后,使用者需要修改属性值时直接调用方法就可以保证不出问题。但是由于属性值依然可以被直接访问,还不能保证万无一失,于是我们利用权限修饰符使得变量不能被直接访问,同时需要定义一个能够取得属性值的方法。
public class Person{ public String name; // 声明money属性为private权限 private double money; // 定义一个方法,用于设置money属性的值 public void setMoney(double money){ if(money >= 0){ this.money = money; } } // 定义一个方法,用于获取money属性的值 public double getMoney(){ return this.money; } }
public class Test{ public static void main(String[] args){ Person person = new Person(); person.setMoney(500);// 初始金额500元,此时已经不能使用对象.属性的方法赋值 System.out.println("购买一张桌子,花费200元"); person.setMoney(person.getMoney() - 200); System.out.println("购买二手PSP,花费350元"); person.setMoney(person.getMoney() - 300); System.out.println("目前余额为:" + person.getMoney());// 300 } }
通过以上的案例,我们可以看到进行封装有以下几个作用:
- 防止类的属性被外部代码随意的修改和访问,保证数据的完备性
- 将对属性的操作转换为方法,更加灵活和安全
- 使用封装可以隐藏实现的细节:使用者只需要作用,不需要知道过程
- 在类的定义结构中修改,提高了代码的可维护性,同时又可以不影响外部的使用
- 通过封装方法可以有效减少耦合
- 耦合:模块与模块之间,代码与代码之间的关联程度,对属性封装后,和调用相关的代码就会变得相对简单,可以降低耦合
2. 如何进行封装
在进行封装时都是出于对属性保护的考虑,可以按照以下两个步骤来进行:
- 使用权限修饰符
- 使用private作用在属性上,关闭直接访问的入口
- 使用public作用在方法上,提供调用的入口
- 定义与属性存取相关的方法
在属性关闭后,我们需要通过方法来获取属性的值以及对属性值进行修改。由于有了方法结构,我们就可以对存入的数据进行判断,对不符合逻辑的数据进行处理。
3. 常规封装方法
明白了封装的作用后,我们可以通过自定义方法的方式完成对属性的封装。封装方法和类中定义的其他方法在结构上没有任何的区别,同样都是普通的方法,区别主要在于体现在用途方面:
- 普通方法主要表达该类所能产生的行为
- 封装方法主要为属性的访问和使用提供了一个入口,作用相对单一
在进入到框架的学习之后,很多对实体类属性自动赋值的操作都是通过调用封装方法实现的,所以我们必须要知道常规封装方法的名称定义和类型设置规则。
对于属性来说我们只会进行两种操作:存和取。那么相应的封装方法应该有一对儿- get代表取用:既然是取值,那么就要把属性值进行返回,方法的返回值类型与属性类型相同
- set代表存储:既然是存值,那么就要在参数列表中接收想要存入的值,类型与属性类型相同
对于命名方面只要遵从驼峰命名法就好,以get或set开头,大写属性名称的首字母,其余不变,看下面一个例子:
public class Person{ // 使用private声明属性 private String name; private double money; // 使用public声明方法,作为操作属性的入口 public void setName(String name){ this.name = name; } public String getName(){ return this.name; } public void setMoney(double money){ // 如有需要,可以在方法中可以自定义其他逻辑 this.money = money; } public double getMoney(){ return this.money; } }
由于常规封装方法定义的格式和名称都相对固定,所以一般的编译器都自带自动生成封装方法的功能,这样既方便又能降低出错率,大家一定要掌握。
- Eclipse:
属性定义完成后,选择source菜单 -> Generate Getters and Setters…
点击Select All(选择所有属性) -> Generate
- IDEA:
与Eclipse类似,在定义好类的私有属性后,使用快捷键:Alt + Insert
可以一次性选择多个属性 -> 点击OK
扫描下方二维码,加入官方粉丝微信群,可以与我直接交流,还有更多福利哦~ -
面向对象编程及其三大特征(JAVA)
2022-01-09 12:42:35面向对象编程及其三大特征(JAVA)面向对象编程及其三大特征(JAVA)
文章目录:一.面向对象编程介绍:
面向过程与面向对象:
面向过程(Procedure Oriented 简称PO :如C语言):
注重过程。当解决一个问题的时候,面向过程会把事情拆分成: 一个个函数和数据(用于方法的参数) 。然后按照一定的顺序,执行完这些方法(每个方法看作一个过程),等方法执行完了,事情就搞定了。面向对象(Object Oriented简称OO :如C++,JAVA等语言):
注重对象。当解决一个问题的时候,面向对象会把事物抽象成对象的概念,就是说这个问题里面有哪些对象,然后给对象赋一些属性和方法,然后让每个对象去执行自己的方法,问题得到解决。面向对象的本质:
以建立模型体现出来的抽象思维过程和面向对象的方法。
二.面向对象编程的三大特征:
封装:
封装的概念:
封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别。将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。
封装的优点:
- 把对象的私有数据和公共数据分离开,保护了私有数据,减少了可能的模块间干扰,达到降低程序复杂性、提高可控性的目的。
- 清楚的体现了系统之间的松散耦合关系,提高系统的独立性;
- 提高了程序的可复用性和可维护性,降低了程序员保持数据与操作内容的负担。
封装举例(Person类)
//Person类:对Person的属性及方法进行封装,并用private进行私有保护 class Person { private String name; private int age; public String getName() { return name; } public int getAge() { return age; } public Person(String name, int age) { this.name = name; this.age = age; } public String getPersonInfo() { return "姓名:" + this.name + ",年龄:" + this.age; } } //主类 public class Test { public static void main(String[] args) { //产生Person类的对象p1 Person p1 = new Person("张三", 18); //通过get方法对private方法进行读操作 System.out.println(p1.getName()+p1.getAge()); //通过对象调用实例变量与实例方法 System.out.println(p1.getPersonInfo()); } }
执行结果:
张三18
姓名:张三,年龄:18继承:
继承是面向对象最显著的一个特性。
继承的概念:
继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。
继承的优点:
- 简化代码,提高复用性;
- 增加可维护性;
继承类型:
继承注意事项:
- 子类不继承其父类的构造方法。
- 当使用无参数的super()时,父类的无参数构造方法就会被调用;
- 当使用带有参数的super()方法时,父类的有参数构造方法就会被调用。
- 子类继承其父类的所有public和protected成员,但不能继承其父类的private成员。
- 重写父类中的方法:
当一个子类中一个实例方法具有与其父类中的一个实例方法相同的签名(指名称、参数个数和类型)和返回值时,称子类中的方法“重写”了父类的方法。
调用重写方法时,调用的为子类重写的方法。 - 隐藏父类中的方法:
如果一个子类定义了一个静态类方法,而这个类方法与其父类的一个类方法具有相同的签名(指名称、参数格式和类型)和返回值,则称在子类中的这个类方法“隐藏”了父类中的该类方法。
调用隐藏方法时,取决于对象是父类对象的调用还是子类对象的调用 - 可以在子类中通过使用关键字super()来调用父类中被重写的方法和访问父类中被隐藏的字段。
继承举例(Animal类):
动物类(公共类,拥有公用的属性和方法):
public class Animal { public String name; public Animal(String name) { this.name = name; } public void eat(){ System.out.println(name+"正在吃"); } public void sleep(){ System.out.println(name+"正在睡"); } public void introduction() { System.out.println("大家好!我是"+ name); } }
鸟类:
public class Birld extends Animal { public Birld(String name) { super(name); } public void fly(){ System.out.println(name+"正在飞"); } }
狗类:
public class Dog extends Animal { public Dog(String name) { super(name); } public void run(){ System.out.println(name+"正在跑"); } }
多态:
相比于封装和继承,Java多态是三大特性中相对更难理解的一个,封装和继承最后归结于多态。
多态的两种分类:
- 重载式多态(方法重载),也叫编译时多态。也就是说这种多态再编译时已经确定好了。重载大家都知道,方法名相同而参数列表不同的一组方法就是重载。在调用这种重载的方法时,通过传入不同的参数最后得到不同的结果。
- 重写式多态(方法重写),也叫运行时多态。这种多态通过动态绑定(dynamic binding)技术来实现,是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。也就是说,只有程序运行起来,你才知道调用的是哪个子类的方法。
这种多态通过函数的重写以及向上转型来实现,我们下面代码中的例子就是一个重写式多态。
向上转型:
Person(父类):
public class Person { private int money=100; public void say(){ System.out.println("我是人类"); } }
Student(子类):
public class Student extends Person { private int money=1; public void say(){ System.out.println("我是学生"); } }
Application:
public class Application { public static void main(String[] args) { Student student=new Student(); Person p1=new Person(); //多态性的体现 /* 向上转型 从子到父 父类的引用指向子类的对象 */ Person p2=new Student(); //成员变量不具有多态性:编译看左边,执行看左边 System.out.println(p1.getMoney()); System.out.println(p2.getMoney()); //成员方法具有多态性:编译看左边,执行看右边 p1.say();//执行父类say方法 p2.say();//执行子类say方法 } }
p2是person的引用,指向了子类Student的对象
成员变量不具有多态性
成员方法具有多态性
故可得到执行结果:
100
100
我是人类
我是学生为什么要发生向上转型?
- 减少重复代码,使代码变得简洁。
- 提高系统扩展性。
- 用于参数统一化,假设父类有n个子类,方法要接受子类的实例,如果没有向上转型,就需要定义n个方法接收不同的对象。
向下转型:
Person(父类):
public class Person { public void run (){System.out.println("run");} }
Student(子类):
public class Student extends Person{ public void go (){System.out.println("go");} }
Application:
public class Application { public static void main(String[] args) { /* 向上转型 从子到父 父类的引用指向子类的对象 */ Person obj=new Student(); //高<--低 obj.run();//此处的run为Student继承person的run(); // obj.go(); //编译报错 父类中无此方法 //-------------------------------------------------------------------------- // student 将这个对象转换为Student类型,我们就可以使用Student的方法了。 /* 向下转型 从父到子 父亲引用转为子类对象 */ Student obj1=(Student)obj; //低<--高 obj1.go(); // 即为强行转换 // ((Student)obj).go(); //-------------------------------------------------------------------------- Student obj2=new Student(); Person person=obj2; //高<--低 自动转换 // person.go(); //低转高(子类转换为父类)丢失了子类的方法go(); } }
运行结果:
run
go为什么要发生向下转型?
- 当父类需要调用子类的扩充方法时,需要向下转型。
- 一般不操作向下转型,有安全隐患
-
javascript面向对象三大特征之多态实例详解
2020-10-16 15:55:47主要介绍了javascript面向对象三大特征之多态,结合实例形式详细分析了javascript面向对象程序设计中多态的概念、原理,并结合实例形式总结了多态的实现方法与使用技巧,需要的朋友可以参考下 -
Java面向对象编程三大特征 - 多态
2020-04-06 11:43:48本文关键字:Java、面向对象、三大特征、多态。多态是面向对象编程的三大特征之一,是面向对象思想的终极体现之一。在理解多态之前需要先掌握继承、重写、父类引用指向子类对象的相关概念。写在前面:博主是一只经过实战开发历练后投身培训事业的“小山猪”,昵称取自动画片《狮子王》中的“彭彭”,总是以乐观、积极的心态对待周边的事物。本人的技术路线从Java全栈工程师一路奔向大数据开发、数据挖掘领域,如今终有小成,愿将昔日所获与大家交流一二,希望对学习路上的你有所助益。同时,博主也想通过此次尝试打造一个完善的技术图书馆,任何与文章技术点有关的异常、错误、注意事项均会在末尾列出,欢迎大家通过各种方式提供素材。
- 对于文章中出现的任何错误请大家批评指出,一定及时修改。
- 有任何想要讨论和学习的问题可联系我:zhuyc@vip.163.com。
- 发布文章的风格因专栏而异,均自成体系,不足之处请大家指正。
Java面向对象编程三大特征 - 多态
本文关键字:Java、面向对象、三大特征、多态
文章目录
多态是面向对象编程的三大特征之一,是面向对象思想的终极体现之一。在理解多态之前需要先掌握继承、重写、父类引用指向子类对象的相关概念,对继承还没有完全明白的同学可进传送门: Java面向对象编程三大特征 - 继承。一、抽象类
在继承中,我们已经了解了子父类的关系以及如何对子父类进行设计,如果已经存在多个实体类,再去定义父类其实是不断的抽取公共重合部分的过程,如果有需要将会产生多重继承关系。在抽取整理的过程中,除了属性可以复用,有很多方法一样也可以复用,假如以图形举例:矩形、圆形,都可以具有周长和面积两个方法,但是计算的方式完全不同,矩形和圆形之间肯定不能构成子父类关系,那么只能是同时去继承一个父类,那么问题就来了,这两个类都有什么共同点?
除了都是图形好像并没有什么共同点,矩形有两组边长,圆形是通过半径来描述,如果非要往一起联系的话。。。Wait a moment(灵光一闪中,请勿打扰)!!!难道说是都可以计算出周长和面积?细细想来,也是能说出一番道理的,但是这好抽象啊!
如果真的是这样,也只能有一个模糊的思路,既然描述图形的属性不能够共用那就分别放在两个子类中吧,那么计算周长和面积的方法要怎么搞?如果在父类中定义相应的方法,那参数列表怎么写?方法体怎么填?这个坑好像有点大,接下来,我们就要华丽地将这个坑填平。1. 抽象与抽象类
在上面的例子中,我们遇到了一个情况,有两个在逻辑上看似相关的类,我们想要把他们联系起来,因为这样做可以提高效率,但是在实施的过程中发现这个共同点有点太过模糊,难以用代码描述,甚至于还不如分开用来的方便,这时就要引出抽象的概念,对应的关键词为:abstract。
- abstract可以修饰方法,修饰后被称为抽象方法
- abstract可以修饰类,修饰后被称为抽象类
- abstract不能与static修饰符同时使用
- abstract不能与final修饰符同时使用
那么使用了abstract又能如何呢?这代表指定的方法和类很难表述,那么。。。就不用表述了!对于矩形类(Rectangle)与圆形类(Circle)的父类:图形类(Figure),我们只能总结出他具有计算周长和面积的方法,而具体的实现方法我们无法给出,只有明确了图形之后,才能给出具体的实现,于是我们使用抽象来描述这两个方法,被abstract修饰的方法不需要有方法体,且不能为private,由于抽象方法没有方法体,那么如果被代码调用到了怎么办呢?以下两个限制规则可以杜绝这个问题:
- 抽象方法只能存在于抽象类中(接口在另外的文章中讨论)
- 抽象类无法被直接实例化(匿名内部类的用法暂不做讨论)
既然抽象类不能被实例化,那么自然也就不会调用到没有方法体的那些方法了,那这些方法该怎么被调用呢?我们需要一步一步的来梳理,至少目前我们已经能够清晰的得到如下的关系图了:
2. 抽象类的特点
抽象类的本质依然是一个类(class),所以具备着一个普通类的所有功能,包括构造方法等的定义,总结一下,抽象类具有以下的几个特点:
- 抽象类由abstract修饰
- 抽象类中允许出现抽象方法
- 抽象类不能通过构造器直接实例化
- 可以在抽象类中定义普通方法供子类继承
现在,我们已经可以将抽象父类用代码描述出来:
// 定义抽象类:图形类 public abstract class Figure{ // 定义计算周长的抽象方法:getC() public abstract double getC(); // 定义计算面积的抽象方法:getS() public abstract double getS(); // 定义描述图形的非抽象方法:print() public void print(){ System.out.println("这是一个图形"); } }
3. 天生的父类:抽象类
现在我们已经有了一个抽象类,其中也定义了抽象方法,抽象类不能被直接实例化保证了抽象方法不会被直接调用到。回忆一下我们的出发点,费劲巴力的弄出个抽象类就是为了提取出两个类比较抽象的共同点,那么下一步自然是继承了。
- 抽象类不能直接实例化,是天生的抽象类
- 如果一个类继承了抽象类,那么必须重写父类中的抽象方法
- 如果抽象类中定义了构造方法,可以被子类调用或在实例化子类对象时执行
- 如果抽象类的子类依然是抽象类,可以不重写抽象方法,将重写操作留给下一级子类
二、重写
重写指的是子父类之间方法构成的关系,当子类继承父类时,父类中可能已经存在了某些方法,那么子类实例就可以直接进行调用。在有些时候由于子父类之间的差异,对于已经存在的方法想要做一些修改,这个时候我们可以利用重写,在子类中定义一个与父类中的方法完全相同的方法,包括返回值类型和方法签名(方法名 + 参数列表),此时就会构成重写。这样,子类实例在调用方法时就可以覆盖父类中的方法,具体的过程在后半部分阐述。
1. 重写与重载的区别
我们在刚开始接触方法的时候了解到了一个概念:重载,与重写有些类似,容易混淆,如果知识点已经模糊可以进传送门:Java程序的方法设计。总结一下,重写和重载有以下区别:
- 重载是同一个类中方法与方法之间的关系
- 重写是子父类间(接口与实现类间)方法与方法之间的关系
- 构成重载:方法名相同,参数列表不同,返回值类型可以不同
- 构成重写:方法名相同,参数列表相同,返回值类型相同或为对应类型的子类
- 构成重载的方法之间权限修饰符可以不同
- 重写方法的权限修饰符一定要大于被重写方法的权限修饰符
有关于权限修饰符的作用如果不明确可以进传送门:Java面向对象编程三大特征 - 封装。明确了重写的含义之后,我们终于可以再度提笔,完成我们之前的例子:
// 定义矩形类 public class Rectangle extends Figure{ // 定义构造器 public Rectangle(double height, double width) { this.height = height; this.width = width; } // 定义长和宽 public double height; public double width; // 重写计算周长方法 @Override public double getC() { return 2 * (this.height + this.width); } // 重写计算面积方法 @Override public double getS() { return this.height + this.width; } // 可选覆盖 @Override public void print(){ System.out.println("矩形"); } }
// 定义圆形类 public class Circle extends Figure{ // 定义构造器 public Circle(double radius) { this.radius = radius; } // 定义半径 public double radius; // 重写计算周长方法 @Override public double getC() { return 2 * Math.PI * this.radius; } // 重写计算面积方法 @Override public double getS() { return Math.PI * Math.pow(this.radius, 2); } // 可选覆盖 @Override public void print(){ System.out.println("圆形"); } }
2. 方法重写的规则
- 重写的标识为@Override
- 方法的重写发生在子类或者接口的实现类中
- 被final声明的方法不能被重写
- 被static声明的方法不能被重写,只能声明同结构的静态方法,但是此时不构成重写
- 受限于权限修饰符,子类可能只能重写部分父类中的方法
3. 父类方法的显式调用
从上面的代码中可以看到,子类继承父类后,如果存在抽象方法则比如重写,由于父类中的方法是抽象的,所以无法调用。对于普通的方法,可以选择性的重写,一旦重写我们可以认为父类的方法被覆盖了,其实这样的形容是不准确的,在初学阶段可以认为是覆盖。
比较规范的说法是:通过子类实例无法直接调用到父类中的同名方法了,但是在内存中依然存在着父类方法的结构,只不过访问不到而已。另外,我们同样可以在子类中显式的调用出父类方法,这要用到super关键字。- super指代父类对象
- super可以调用可访问的父类成员变量
- super可以调用可访问的父类成员方法
- super可以调用可访问的父类构造方法
- 不能使用super调用父类中的抽象方法
- 可以使用super调用父类中的静态方法
如果我们需要在子类中调用父类方法或构造器,可以将代码修改如下:
// 定义抽象类:图形类 public abstract class Figure{ // 在抽象类中定义构造器,在子类实例创建时执行 public Figure(){ System.out.println("Figure init"); } // 定义计算周长的抽象方法:getC() public abstract double getC(); // 定义计算面积的抽象方法:getS() public abstract double getS(); // 定义描述图形的非抽象方法:print() public void print(){ System.out.println("这是一个图形"); } }
// 定义矩形类 public class Rectangle extends Figure{ // 定义构造器 public Rectangle(double height, double width) { super();// 会调用默认的无参构造,代码可省略 this.height = height; this.width = width; } // 定义长和宽 public double height; public double width; // 重写计算周长方法 @Override public double getC() { return 2 * (this.height + this.width); } // 重写计算面积方法 @Override public double getS() { return this.height + this.width; } // 可选覆盖 @Override public void print(){ super.print();// 调用父类方法 System.out.println("矩形"); } }
// 定义圆形类 public class Circle extends Figure{ // 定义构造器 public Circle(double radius) { super();// 会调用默认的无参构造,代码可省略 this.radius = radius; } // 定义半径 public double radius; // 重写计算周长方法 @Override public double getC() { return 2 * Math.PI * this.radius; } // 重写计算面积方法 @Override public double getS() { return Math.PI * Math.pow(this.radius, 2); } // 可选覆盖 @Override public void print(){ super.print();// 调用父类方法 System.out.println("圆形"); } }
三、父类引用指向子类对象
前面提到的概念消化完毕后,我们看一下子父类对象实例化的形式以及方法的执行效果。
1. 父类引用指向父类对象
如果父类是一个抽象类,则在等号右侧不能直接使用new加构造方法的方式实例化,如果一定要得到父类实例,就要使用匿名内部类的用法,这里不做讨论。
如果父类是一个普通类,那么我们在初始化时,等号左侧为父类型引用,等号右侧为父类型对象(实例),这个时候其实和我们去创建一个类的对象并没有什么分别,不需要想着他是某某类的父类,因为此时他不会和任何子类产生关系,只是一个默认继承了Object类的普通类
,正常使用就好,能调用出的内容也都是父类中已定义的。2. 子类引用指向子类对象
在进行子类实例化时,由于在子类的定义中继承了父类,所以在创建子类对象时,会先一步创建父类对象。在进行调用时,根据权限修饰符,可以调用出子类及父类中可访问的属性和方法。
public class Test{ public static void main(String[] args){ Rectangle rectangle = new Rectangle(5,10); // 调用Rectangle中定义的方法,以子类重写为准 rectangle.print(); System.out.println(rectangle.getC());// 得到矩形周长 System.out.println(rectangle.getS());// 得到矩形面积 Circle circle = new Circle(5); // 调用Circle中定义的方法,以子类重写为准 circle.print(); System.out.println(circle.getC());// 得到圆形周长 System.out.println(circle.getS());// 得到圆形面积 } }
3. 引用与对象之间的关系
在刚开始学习编程时,我们接触了基本数据类型,可以直接用关键字声明,定义变量赋值后使用,并不需要使用new关键字。对于引用与对象的关系可以先参考之前的文章回顾一下:Java中的基本操作单元 - 类和对象。在这里我们重点要说明的是:等号左侧的引用部分,与等号右侧的部分在程序运行层面有怎样的关联。
与基本数据类型不同,在类中可以定义各种属性和方法,使用时也需要先创建对象。等号左侧的部分依然是一个类型的声明,未赋值时虽然默认情况下是null,但在程序编译运行时,也会在栈中进行存储,记录了相应的结构信息,他所指向的对象必须是一个和它兼容的类型。
类的声明引用存放在栈中,实例化得到的对象存放在堆中。- 在代码编写阶段,能够调用出的内容以等号左侧类型为准
- 在程序运行阶段,具体的的执行效果以等号右侧实例为准
下图为引用与实例在内存中的关系示意图,有关于Java对象在内存中的分布将在另外的文章中说明:
4. 父类引用指向子类对象
了解了引用与对象的关系之后,就有了一个疑问,如果等号左侧的声明类型与等号右侧的实例类型不一致会怎么样呢?如果我们要保证程序能够通过编译,并且顺利执行,必须要保证等号两边的类型是兼容的。完全不相关的两个类是不能够出现在等号左右两边的,即使可以使用强制类型转换通过编译,在运行时依然会抛出异常。
于是我们就联想到了子父类是否有可能进行兼容呢?会有两种情况:子类引用指向父类对象,父类引用指向子类对象,下面我们来一一讨论。- 子类引用指向父类对象为什么无法使用
子类引用指向父类对象指的是:等号左侧为子类型的声明定义,等号右侧为父类型的实例。首先,结论是这种用法是不存在的,我们从两方面来分析原因。
第一个方面,是否符合逻辑?也就是是否会有某种需求,让Java语言为开发者提供这样一种用法?显然是否定的,我们定义子类的目的就是为了扩展父类的功能,结果现在我们却在用老旧的、功能贫乏的父类实例(等号右侧)去满足已经具备了强劲的、功能更为强大的子类声明(等号左侧)的需要,这显然是不合理的。
另一方面,在程序运行时是否能够办到?如果我们真的写出了相关的代码,会要求我们添加强制转换的语句,否则无法通过编译,即使通过,在运行时也会提示无法进行类型转换。这就相当于把一个只能打电话发短信的老人机强制转换为能安装各种APP的智能机,这显然是办不到的。- 父类引用指向子类对象有什么样的意义
父类引用指向子类对象指的是:等号左侧为父类型的定义,等号右侧为子类型的实例。这种情况是会被经常使用的,类似的还有:接口指向实现类。那么,这种用法应该如何解释,又为什么要有这样的用法呢?
首先,我们先来理解一下这代表什么含义,假如:父类为图形,子类为矩形和圆形。这就好比我声明了一个图形对象,这个时候我们知道,可以调用出图形类中定义的方法,由于图形类是一个抽象类,是不能直接实例化的,我们只能用他的两个子类试试看。public class Test{ public static void main(String[] args){ // figure1指向Rectangle实例 Figure figure1 = new Rectangle(5,10); System.out.println(figure1.getC());// 得到矩形周长 System.out.println(figure1.getS());// 得到矩形面积 // figure2指向Circle实例 Figure figure2 = new Circle(5); System.out.println(figure2.getC());// 得到圆形周长 System.out.println(figure2.getS());// 得到圆形面积 } }
从上面的结果来看,这好像和子类引用指向子类对象的执行效果没什么区别呀?但是需要注意此时使用的是父类的引用,区别就在于,如果我们在子类中定义了独有的内容,是调用不到的。在上面已经解释了运行效果以等号右侧的实例为准,所以结果与直接创建的子类实例相同并不难理解。
重点要说明一下其中的含义:使用Figure(图形)声明,代表我现在只知道是一个图形,知道能执行哪些方法,如果再告知是一个矩形,那就能算出这个矩形的周长和面积;如果是一个圆形,那就能算出这个圆形的周长和面积。我们也可以这样去描述:这个图形是一个矩形或这个图形是一个圆形。
如果从程序运行的角度去解释,我们已经知道,子类对象在实例化时会先实例化父类对象,并且,如果子类重写了父类的方法,父类的方法将会隐藏。如果我们用一个父类引用去指向一个子类对象,这就相当于对象实例很强大,但是我们只能启用部分的功能,但是有一个好处就是相同的指令,不同的子类对象都能够执行,并且会存在差异。这就相当于一部老人机,只具备打电话和发短信的功能,小米手机和魅族手机都属于升级扩展后的智能机,当然保有手机最基本的通讯功能,这样使用是没问题的。四、多态
学习了上面的内容后,其实你已经掌握了多态的用法,现在我们来明确总结一下。
1. 什么是多态
多态指的是同一个父类,或同一个接口,发出了一个相同的指令(调用了同一个方法),由于具体执行的实例(子类对象或实现类对象)不同,而有不同的表现形态(执行效果)。
就像上面例子中的图形一样,自身是一个抽象类,其中存在一些抽象方法,具体的执行可以由子类对象来完成。对于抽象类的抽象方法,由于子类必须进行重写,所以由子类去执行父类的抽象方法必然是多态的体现,对于其他的情况则未必构成多态,因此总结了以下三个必要条件。2. 多态的必要条件
- 存在子父类继承关系
- 子类重写父类的方法
- 父类引用指向子类对象
只有满足了这三个条件才能构成多态,这也就是文章前三点用这么长的篇幅来铺垫的原因。
3. 多态的优点
使用多态有多种好处,特别是一个抽象类有多个子类,或一个接口存在多个抽象类时,在进行参数传递时就会非常的灵活,在方法中只需要定义一个父类型作为声明,传入的参数可以是父类型本身,也可以是对应的任意子类型对象。于是,多态的优点可以总结如下:
- 降低耦合:只需要与父类型产生关联即可
- 可维护性(继承保证):只需要添加或修改某一子类型即可,不会影响其他类
- 可扩展性(多态保证):使用子类,可以对已有功能进行快速扩展
- 灵活性
- 接口性
扫描下方二维码,加入官方粉丝微信群,可以与我直接交流,还有更多福利哦~
-
哪些是面向对象程序设计的三大特征
2021-01-30 05:54:441、封装性:具备封装性(Encapsulation)的面向对象编程隐藏了某一方62616964757a686964616fe78988e69d8331333433653366法的具体运行步骤,取而代之的是通过消息传递机制发送消息给它。封装是通过限制只有特定类的对象... -
Python 面向对象程序设计(一)
2020-12-21 03:31:40面向对象程序设计 # 面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是一种程序设计范型,同时也是一种程序开发的方法。 #对象指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,... -
面向对象程序设计的三大特征是什么?有哪三大特征?
2021-03-18 08:32:58面向对象程序设计三大特征1、多态(Polymorphism)2、继承(Inheritance)3、封装(Encapsulation)多态、继承、封装就是面向对象程序设计的三大特征,下面一起来详细的介绍一下。多态(Polymorphism)多态(Polymorphi... -
Java面向对象编程三大特征 - 继承
2020-03-31 18:24:08本文关键字:Java、面向对象、三大特征、继承。继承是面向对象编程的三大特征之一,继承将面向对象的编程思想体现的更加淋漓尽致,允许类和类之间产生关联。 -
Java面向对象的三大特征是什么?
2021-03-15 02:34:47面向对象的三大核心特性简介面向对象...1、继承的概念继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类... -
C语言实现面向对象编程
2021-01-27 11:19:50面向对象编程(OOP)并不是一种特定的语言或者工具,它只是一种设计方法、设计思想。它表现出来的三个最基本的特性就是封装、继承与多态。很多面向对象的编程语言已经包含这三个特性了,例如 Smalltalk、C++、Java。... -
谈谈对面向对象三大基本特征的理解
2021-05-25 15:41:43谈谈对面向对象三大基本特征的理解 面向对象三大基本特征:封装、 继承、 多态 封装可以隐藏代码实现的细节,使得代码模块化;继承可以扩展已存在的代码模块;它们的目的都是代码重用;而多态实现了另一个目的:... -
西北工业大学软件学院面向对象程序设计理论课程大作业
2019-06-11 12:56:54西北工业大学软件学院面向对象程序设计理论课程大作业,欢迎大家下载! -
面向对象编程的三大特征简述
2018-01-04 20:38:15面向对象编程的三大特性,简要阐述如下 (1).继承: 继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继 承... -
面向对象编程三大特征---封装、继承和多态
2021-06-23 11:53:05面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 通过继承创建的新类称为“子类”或“派生类”。 ... -
Java面向对象三大特征
2022-01-19 21:04:40是面向对象三大特征之一(封装,继承,多态) 是面向对象编程语言对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界是无法直接操作的 封装原则 将类的某些信息隐藏在类内部,不允许外部程序直接访问... -
简述面向对象编程的三大特性
2019-09-26 19:48:31封装:把对象的属性私有化,同时提供可以被外界访问这些属性的方法。(如果属性不想被外界访问,那大可不必提供方法给外界访问;但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了) 继承 继承:... -
C#面向对象程序设计知识点汇总
2021-05-18 21:35:41包含所有C#面向对象化编程的重点知识,都是平时一点点的积累,比较全面的知识点 -
面向对象程序设计(OOP)的三大基本特征(定义、特性、优缺点、java中的表现形式及应用)和五大基本原则
2019-03-10 16:57:31一、面向对象程序设计(OOP)的三大基本特征(定义、特性、优缺点、java中的表现形式、应用) 1.封装 封装的定义 通俗的说:普通人对计算机进行应用操作,只需知道如何在计算机上连网,如何浏览网页,下载东西... -
matlab面向对象的程序设计.pdf
2021-09-06 19:17:50matlab,官方指南,程序设计,英文 -
面向对象的三大特征
2021-05-28 18:22:50Java知识点总结:想看的可以从这里进入 3、 面向对象三大特征 面向对象的三大特征:继承、封装、多态 3.1、封装 封装是面向对象编程的核心思想,将对象的属性和行为封装隐藏起来。而这些封装起来的属性和行为通常... -
Delphi面向对象程序设计课件 第三章Delphi面向对象编程思想.ppt
2021-08-16 16:48:45Delphi面向对象程序设计课件 -
简述面向对象的三大特征
2020-09-03 21:21:44面向对象的三大特征: 一、封装 1、类:封装对象的属性和行为(方法); 2、方法:封装的是一段特定的业务逻辑功能; 3、访问控制修饰符:封装具体的访问权限。 二、继承 1、作用:代码复用; 2、超类... -
Python的学习心得和知识总结(六)|Python 面向对象编程(一)
2020-12-20 23:38:56前面我们已经做了大量的基础的学习 和 准备工作,从今天开始正式进入Python的面向对象编程。对于Python而言,它是支持前面的面向过程、OOP和函数式编程等大量编程范式。 至于OOP这个东西也比较简单,我们在C++的时候... -
简述Python中的面向对象编程的概念
2021-01-21 17:29:53面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。 面向过程的程序设计把计算机程序视为一系列的命令集合,即一组... -
Java面向对象的三大特征
2021-01-26 17:37:44Java面向对象的三大特征为:封装、继承和多态。 1.封装 Java中的封装是指一个类把自己内部的实现细节进行隐藏,只暴露对外的接口(setter和getter方法)。封装又分为属性的封装和方法的封装。把属性定义为私有的,... -
你怎么理解面向对象?详细说明面向对象的三大特征。
2018-11-16 21:45:29详细说明面向对象的三大特征。 面向对象 把数据及对数据的操作方法放在一起,作为一个相互依存的整体–对象。对同类对象抽象其共性,形成类。类中的大多数数据,只能在本类的方法进行处理。类通过一个简单的外部... -
java面向对象程序设计第3版习题参考解答 .doc
2021-12-29 19:05:37解答参考,欢迎下载 -
初学者python笔记(面向对象编程、类与对象)
2020-12-22 05:06:58文章目录面向对象设计类与对象的概念1、类与对象的本质2、面向对象设计 到 面向对象编程3、类实例化4、例如一个学校类面向对象编程1、案例2、面向对象语言和面向对象编程之间的关系3、类的属性4、类的其他一些属性5...