写在开头:今天我们来学习面向对象的第三部分:面向对象的三大特征 —— 继承、封装、多态。此部分是面向对象的精髓。话不多说,我们开始吧。
PS:本文是个人学习笔记,用于个人复习。文章内容引用尚学堂java300集教学。
三大特征
Ⅰ 继承
-
继承的概念和实现
(1) 概念:
继承是指派生自同一个基类的不同类的对象具有一些共同特征。继承可以使得子类具有父类的各种属性和方法,同时,子类可以重新定义某些属性,并重写某些方法。另外,为子类追加新的属性也是常见的作法。
(2) 作用:
继承的主要两个作用时是:代码复用,更加容易实现类的扩展;方面对事务建模。
(3) 实现
extends:通过extends实现继承
instanceof运算符:instanceof是二元运算符,左边是对象,右边是类;当对象是右边类或子类所创建对象时,返回true;否则返回false。
(4) 要点
- 父类也称作超类、基类。子类也称作派生类
- Java中类只有单继承,没有多继承。多继承会引起混乱,使得继承链过于复杂,系统难以维护。但是java接口有多继承。
- 子类继承父类,可以得到父类除构造方法外的全部属性和方法,但不一定可以直接访问(例如父类私有的属性和方法)
- 如果定义一个类时,没有调用extends,则它的父类是:java.lang.Object -
方法的重写override
子类通过重写父类的方法,可以用自身的行为替换父类的行为。方法的重写是实现多态的必要条件。
方法的重写需要符合下面的三个要点:
- “==” : 方法名、形参列表相同
- “<=” : 返回值类型和声明异常类型,子类小于等于父类
- “>=” : 访问权限,子类大于等于父类示例1:LOL(英雄联盟)这款游戏想必大部分人都玩过,在游戏中的物品和武器、防具、消耗品等等都是些典型的继承关系,这里我们以物品和武器为例。
源代码://父类:物品类 Class Item{ //属性 String name; //物品名称 int price; //物品价格 //有参构造函数 Item(String name, int price){ this.name = name; this.price = price; } //无参构造函数(注意,这里一定要有无参构造函数,否则子类构造函数会报错,原因和后边要说的super有关) Item(){ } //方法:打印物品信息 void information(){ System.out.print("物品名称:" + name + "\n价格:" + price); } } //子类:武器类 class Weapon extends Item{ //属性 int ap; //攻击力 //有参构造方法 Weapon(String name, int price, int ap){ this.name = name; this.price = price; this.ap = ap; } //重写父类方法 void information(){ System.out.print("物品名称:" + name + "\n价格:" + price + "\n攻击力:" + ap); } } //主函数 public class Lol { public static void main(String[] args) { //创建一个物品对象:生命药水 Item potion = new Item("生命药水", 35); potion.information(); System.out.println("\n==========="); //分割线 //创建一个武器对象:暴风大剑 Weapon bfsword = new Weapon("暴风大剑", 1300, 40); bfsword.information(); } }
运行结果:
练习:创建防具类Armor(继承于物品类),为其添加护甲属性(ac),重写information()函数并创建一个实例,调用information()方法
参考代码
运行结果:
-
final关键字
final关键字的作用:
- 修饰变量:被它修饰的变量不可改变,一旦赋了初值,就不能被重新赋值。
final int MAX_SPEED = 120;
- 修饰方法:该方法不可被子类重写,但是可以被重载。
final void study(){}
- 修饰类:修饰的类不能被继承。比如:Math、String等
final class A{} -
object类
(1) Object基本特性:
Object类是所有java类的根基类,也就意味着所有java对象都拥有Object类的属性和方法。如果在类的声明中未使用extends关键字指明其父类,则默认使用Object类。
(2) Object的一些方法:
- toString方法:
toString方法默认会返回“类名+@+16进制的hashcode”。在打印输出或者字符串连接对象时,会自动调用该对象的toString()方法。我们可以根据需要重写该方法。//toString()源码 public String toString(){ return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
- == 和 equals 方法
equals提供定义“对象内容相等”的逻辑。Equals方法默认就是比较两个对象的hashcode,是同一个对象的引用时返回true,否则返回false。同样,我们可以根据需要重写equals方法。//equals()源码 public boolean equals(Object obj){ return (this == obj); }
-
super关键字
super关键字的两个作用:
- 子类在构造方法中显式调用父类的构造方法:
Super可以在子类的构造方法中显式调用父类的构造方法;并且这行代码必须出现在子类构造方法的第一行。
我们在前边的item派生weapon的例子中,在item类中写了一个无参构造函数,并且注释如果没有此函数,则编译报错。当时没有说明原因,现在给大家解释一下:当一个子类在创建时,直接父类的构造方法会先被执行,而在子类中调用父类的构造方法就是通过super关键字来执行的,可是我们并没有在子类的构造方法的首句中使用super,于是系统会默认使用super()调用父类的无参构造方法。如果父类没有无参构造方法,那么编译就会报错。
当然,我们也可以在子类构造方法的首句显式的使用super(),并填写正确的参数,这样就会调用父类的有参构造方法,也就不添加无参构造方法了。Weapon(String name, int price, int ap){ super(name,price); this.ap = ap; }
- 可以在子类中充当临时父类对象:
可以在子类内部代表父类对象,从而在子类中访问父类的属性和方法。注意点:
- super”可以看作”是直接父类对象的引用。
- 使用super调用普通方法,语句没有位置限制,可以在子类中随便调用。
- 若是构造方法的第一行代码没有显式的调用super()或者this();那么java默认都会调用super(),含义是调用父类的无参数构造方法。这里的super()可以省略。 -
继承和组合
“组合”就是“将父类对象作为子类的属性”,然后,“子类”通过调用这个属性来获得父类的属性和方法。
通过“组合”,我们可以很方便的实现“代码复用”以及对事物的建模。相比较“继承”,组合更加的灵活。继承只能有一个父类,但是组合可以有多个属性。
一般来说,对于”is-a”关系建议使用继承,”has-a”关系建议使用组合。比如:Student is a Person 逻辑正确,Student has a Person 逻辑错误,所以继承关系合适;而Computer has a chip 逻辑正确,所以电脑和芯片用组合关系合适。
Ⅱ 封装
-
封装的作用和含义
封装是把对象的属性和操作结合为一个独立的整体,并尽可能隐藏对象内部实现细节。说通俗一点就是:把需要让用户知道的暴露出来,不需要让用户知道的全部隐藏起来。
我们程序设计要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合是仅暴露少量的方法给外部使用,尽量方便外部调用。
编程中封装的具体优点:
- 提高代码的安全性
- 提高代码的复用性
- “高内聚”:封装细节,便于修改内部代码,提高可维护性
- “低耦合”:简化外部调用,便于调用者使用,便于扩展和协作 -
访问控制符
Java是使用“访问控制符”来控制哪些细节需要封装,哪些细节需要暴露的。Java中4种“访问控制符”分别为private、default、protected、public。修饰符 同一个类 同一个包中 子类 所有类 Private * Default * * Protected * * * Public * * * * (1)private 表示私有,只有自己类能访问
(2)Default表示没有修饰符修饰,只有同一个包的类能访问
(3)Protected表示可以被同一个包的类以及其他包中的子类访问
- 若父类和子类在同一个包中,子类可访问父类的protected成员,也可访问父类对象的protected
- 若子类和父类不在同一个包中,子类可访问父类的protected成员,不能访问父类对象的protected成员
(4)Public表示可以被该项目的所有包中的所有类访问 -
Javabean封装
开发中封装的简单规则:
- 属性一般使用private访问权限,属性私有后,提供相应的get/set方法来访问相关属性,这些方法通常是public修饰的,以提供对属性的赋值与读取操作(注意:boolean变量的get方法是is开头!)
- 一些只用于本类的辅助性方法可以用private修饰,希望其他类调用的方法用public修饰。
示例2:我们将前边的item类和weapon类进行封装
//父类:物品类
class Item{
//属性
private String name; //物品名称
private int price; //物品价格
//有参构造函数
Item(String name, int price){
this.name = name;
this.price = price;
}
//get和set方法
public String getName(){
return name;
}
public int getPrice(){
return price;
}
public void setName(String name){
this.name = name;
}
public void setPrice(int price){
this.price = price;
}
//information方法
public void information(){
System.out.print("物品名称:" + getName() + "\n价格:" + getPrice());
}
}
//子类:武器类
class Weapon extends Item{
//属性
int ap; //攻击力
//有参构造方法
Weapon(String name, int price, int ap){
super(name,price);
this.ap = ap;
}
//get和set方法
public int getAp(){
return ap;
}
public void setAp(int ap){
this.ap = ap;
}
//重写父类方法:显示物品信息,包括攻击力
@Override
public void information(){
System.out.print("物品名称:" + this.getName() + "\n价格:" + this.getPrice() + "\n攻击力:" + this.getAp());
}
}
//主程序
public class Lol {
public static void main(String[] args) {
//创建一个物品对象:生命药水
Item potion = new Item("生命药水", 35);
potion.information();
System.out.println("\n==========="); //分界线
//创建一个武器对象:暴风大剑
Weapon bfsword = new Weapon("暴风大剑", 1300, 40);
bfsword.information();
}
}
Ⅲ 多态
-
多态的概念和实现
(1)概念
多态指得是同一个方法调用,由于对象不同可能会有不同的行为。对照现实就是同样的操作,对于不同的对象会有不同的行为;比如,同样是吼叫,狗是汪汪叫,而猫是喵喵叫。要点:
- 多态是方法的多态,不是属性的多态(多态与属性无关)
- 多态的存在要有3个必要条件:继承,方法重写,父类引用指向子类对象
- 父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了(2)实现
多态最常见的实现方式是父类引用做方法的形参,实参可以是任意的子类对象,可以通过同的子类对象实现不同的行为方式。
示例3:
//父类:动物类
class Animal{
public void shout(){
System.out.println("叫了一声!");
}
}
//子类1:狗类
class Dog extends Animal{
@Override
public void shout(){
System.out.println("汪汪汪~");
}
public void seeDoor(){
System.out.println("看门中...");
}
}
//子类2:猫类
class Cat extends Animal{
@Override
public void shout(){
System.out.println("喵喵喵~");
}
public void catchMouse(){
System.out.println("抓老鼠中~");
}
}
//子类3:鼠类
class Mouse extends Animal{
@Override
public void shout(){
System.out.println("吱吱吱~");
}
}
//主程序
public class TestPolymophism {
static void animalCry(Animal a) {
a.shout();
}
public static void main(String[] args) {
Mouse m1 = new Mouse();
animalCry(m1);
animalCry(new Cat());
Animal a1 = new Dog(); //向上可以自动转型;a1为编译类型,Dog对象才是运行时的类型
animalCry(a1); //传的具体是哪个类就调用哪一个类的方法,大大提高了程序的可扩展性
//编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换。否则,通不过编译器的检查。
Dog d1 = (Dog)a1; //向下需要强制类型转换
d1.seeDoor();
}
}
运行结果:
从上边的示例,我们可以看出多态的主要优势是提高了代码的可拓展性,符合开闭原则。但是多态也有弊端,就是无法调用子类特有的功能,比如,我们不能使用父类的引用变量调用Dog类特有的seeDoor()方法。
如果想要使用子类特有的功能,我们就要使用对象的转型。
- 对象的转型
父类引用指向对象,我们称这个过程为向上转型,属于自动类型转换
向上转型后的父类引用变量只能调用它编译类型的方法,不能调用它运行时类型的方法。这时,我们就需要进行类型的强制转换,我们称之为向下转型。
在向下转型的过程中,必须将引用变量转成真实的子类类型(运行时类型),否则会出现类型转换异常ClassCastException 。为了避免这种异常,我们可以使用instanceof运算符进行判断。
比如说,在上边的例子中我将a1转换为cat类型,并调用catchMouse()函数,则程序运行时就会报错。
原因就是引用变量是Cat,而真实的子类类型是Dog,Dog没有catchMouse()方法,在运行时必然报错。这时我们可以使用instanceof进行判断//报错 Cat c2 = (Cat)a1; c2.catchMouse();
if(a1 instanceof Dog){ Dog d2 = (Dog)a1; d2.seeDoor(); } else if(a1 instanceof Cat){ Cat c2 = (Cat)a1; c2.catchMouse(); }