Java学习笔记 -- 第八章 面向对象(三):三大特征

写在开头:今天我们来学习面向对象的第三部分:面向对象的三大特征 —— 继承、封装、多态。此部分是面向对象的精髓。话不多说,我们开始吧。
PS:本文是个人学习笔记,用于个人复习。文章内容引用尚学堂java300集教学。

三大特征

    Ⅰ 继承
    Ⅱ 封装
    Ⅲ 多态

Ⅰ 继承

  1. 继承的概念和实现
    (1) 概念:
      继承是指派生自同一个基类的不同类的对象具有一些共同特征。继承可以使得子类具有父类的各种属性和方法,同时,子类可以重新定义某些属性,并重写某些方法。另外,为子类追加新的属性也是常见的作法。
    (2) 作用:
      继承的主要两个作用时是:代码复用,更加容易实现类的扩展;方面对事务建模。
    (3) 实现
      extends:通过extends实现继承
      instanceof运算符:instanceof是二元运算符,左边是对象,右边是类;当对象是右边类或子类所创建对象时,返回true;否则返回false。
    (4) 要点
      - 父类也称作超类、基类。子类也称作派生类
      - Java中类只有单继承,没有多继承。多继承会引起混乱,使得继承链过于复杂,系统难以维护。但是java接口有多继承。
      - 子类继承父类,可以得到父类除构造方法外的全部属性和方法,但不一定可以直接访问(例如父类私有的属性和方法)
      - 如果定义一个类时,没有调用extends,则它的父类是:java.lang.Object

  2. 方法的重写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()方法
    参考代码
    运行结果:

  3. final关键字
      final关键字的作用:
        - 修饰变量:被它修饰的变量不可改变,一旦赋了初值,就不能被重新赋值。
         final int MAX_SPEED = 120;
        - 修饰方法:该方法不可被子类重写,但是可以被重载。
         final void study(){}
        - 修饰类:修饰的类不能被继承。比如:Math、String等
         final class A{}

  4. 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);
    }
    
  5. 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()可以省略。

  6. 继承和组合
      “组合”就是“将父类对象作为子类的属性”,然后,“子类”通过调用这个属性来获得父类的属性和方法。
      通过“组合”,我们可以很方便的实现“代码复用”以及对事物的建模。相比较“继承”,组合更加的灵活。继承只能有一个父类,但是组合可以有多个属性。
      一般来说,对于”is-a”关系建议使用继承,”has-a”关系建议使用组合。比如:Student is a Person 逻辑正确,Student has a Person 逻辑错误,所以继承关系合适;而Computer has a chip 逻辑正确,所以电脑和芯片用组合关系合适。

Ⅱ 封装

  1. 封装的作用和含义
      封装是把对象的属性和操作结合为一个独立的整体,并尽可能隐藏对象内部实现细节。说通俗一点就是:把需要让用户知道的暴露出来,不需要让用户知道的全部隐藏起来。
      我们程序设计要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合是仅暴露少量的方法给外部使用,尽量方便外部调用。
      编程中封装的具体优点:
        - 提高代码的安全性
        - 提高代码的复用性
        - “高内聚”:封装细节,便于修改内部代码,提高可维护性
        - “低耦合”:简化外部调用,便于调用者使用,便于扩展和协作

  2. 访问控制符
      Java是使用“访问控制符”来控制哪些细节需要封装,哪些细节需要暴露的。Java中4种“访问控制符”分别为private、default、protected、public。

    修饰符 同一个类 同一个包中 子类 所有类
    Private *
    Default * *
    Protected * * *
    Public * * * *

    (1)private 表示私有,只有自己类能访问
    (2)Default表示没有修饰符修饰,只有同一个包的类能访问
    (3)Protected表示可以被同一个包的类以及其他包中的子类访问
       - 若父类和子类在同一个包中,子类可访问父类的protected成员,也可访问父类对象的protected
       - 若子类和父类不在同一个包中,子类可访问父类的protected成员,不能访问父类对象的protected成员
    (4)Public表示可以被该项目的所有包中的所有类访问

  3. 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. 多态的概念和实现
    (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()方法。
  如果想要使用子类特有的功能,我们就要使用对象的转型。

  1. 对象的转型
      父类引用指向对象,我们称这个过程为向上转型,属于自动类型转换
    向上转型后的父类引用变量只能调用它编译类型的方法,不能调用它运行时类型的方法。这时,我们就需要进行类型的强制转换,我们称之为向下转型。
      在向下转型的过程中,必须将引用变量转成真实的子类类型(运行时类型),否则会出现类型转换异常ClassCastException 。为了避免这种异常,我们可以使用instanceof运算符进行判断。
      比如说,在上边的例子中我将a1转换为cat类型,并调用catchMouse()函数,则程序运行时就会报错。
    //报错
    Cat c2 = (Cat)a1;
    c2.catchMouse();
    
      原因就是引用变量是Cat,而真实的子类类型是Dog,Dog没有catchMouse()方法,在运行时必然报错。这时我们可以使用instanceof进行判断
    if(a1 instanceof Dog){
    	Dog d2 = (Dog)a1;
    	d2.seeDoor();
    }
    else if(a1 instanceof Cat){
    	Cat c2 = (Cat)a1;
    	c2.catchMouse();
    }
    
发布了11 篇原创文章 · 获赞 3 · 访问量 907

猜你喜欢

转载自blog.csdn.net/IceTiger_/article/details/105380828