Java面向对象:多态

三. 面向对象

  • 面向对象特征:多态

    1. 向上转型(upcasting):子类型 --> 父类型,又被称为自动类型转换
    2. 向下转型(downcasting):父类型 --> 子类型,又被称为强制类型转换(需要加强制类型转换符)
    3. 无论是向上转型还是向下转型,两种类型间必须要有继承关系(否则无法编译通过)
  • 向上转型:
public class Animal {
    public void move(){
        System.out.println("动物会移动");
    }
}

public class Cat extends Animal{
    public void move() {
        System.out.println("猫会跑");
    }
    public void catchMouse(){		//不是继承的方法,是该子类对象特有的行为
        System.out.println("猫抓老鼠");
    }
}

public class Bird extends Animal{
    public void move() {
        System.out.println("鸟会飞");
    }
}

public class OverrideTest {
    public static void main(String[] args) {
/*	
	1. Animal的Cat之间存在继承关系,Animal是父类,Cat是子类
	2. Cat is a Animal(是合理的)
	3. new Cat()创建的对象类型是Cat,a1的数据类型是Animal,存在类型转换
		子类型转换成父类型,称为向上转型/upcasting,自动类型转换
	4. java中允许父类型引用指向子类型对象
*/
        Animal a1 = new Cat();		//使用多态语法机制,向上转型
        Bird b1 = new Cat();		//编译报错,不存在继承关系
        a1.move();		//输出猫会跑
/*
	1. java程序永远分为编译阶段和运行阶段
	2. 先分析编译阶段,再分析运行阶段,编译不通过则无法运行
	3. 编译阶段编译器检查a1的数据类型为Animal,且Animal.class中有move()方法,所以编译通过
		这个过程称为静态绑定/编译阶段绑定
	4. 在运行阶段,JVM堆内存中真实创建的是Cat对象,所以调用的是Cat对象的move()方法
		发生程序的动态绑定/运行阶段绑定
	5. 无论Cat类是否重写move()方法,运行阶段都是调用Cat对象的move()方法,因为创建的是Cat对象
	6. 父类型引用指向子类型对象的机制导致程序存在编译阶段绑定和运行阶段绑定两种不同的形态/状态
		这种机制称为一种多态语法机制
*/
		a1.catchMouse();		//编译报错
		//编译阶段编译器检查a1的类型是Animal,而Animal.class中没有catchMouse方法
		//静态绑定失败
	}
}
  • 向下转型:

    当调用的方法是子类型中特有的,在父类型中不存在时,需要进行向下转型

/*
(接上面程序)如何使a1执行catchMouse()方法:
	a1是Animal类型数据,无法直接调用catchMouse()方法,需要将a1强制类型转换为Cat类型
	父类(Animal)转换为子类(Cat),称为向下转型/downcasting/强制类型转换
*/
	Cat c1 = (Cat)a1;		//强制类型转
	c1.catchMouse();		//编译通过

/*
以下程序编译通过,但在运行阶段出现异常:java.lang.ClassCastException
在JVM堆内存中真实存在的对象是Bird类型,无法转换成Cat对象(两者间没有继承关系)
类型转换异常,在“向下转型”时会发生
*/
	Animal a2 = new Bird();
	Cat c2 = (Cat)a2;
	c2.move();
	c2.catchMouse();
  • 向上转型只要编译通过,运行不会出问题;向下转型编译通过,运行可能错误

  • 使用instanceof运算符避免ClassCastException异常

  • instanceof语法格式:(引用 instanceof 数据类型名)

    执行结果是布尔类型(true/false):

    (a instanceof Animal)结果为true:引用a指向的对象是Animal类型

    结果为false:引用a指向的对象不是Animal类型

Animal a2 = new Bird();
if (a2 instanceof Cat){		//用instanceof判断所属类型
    Cat c2 = (Cat)a2;
}else if (a2 instanceof Bird){
    Bird b2 = (Bird)a2;
    b2.move();
}
  • 多态的作用:以主人喂养宠物为例

    提倡面向抽象编程,不要面对具体编程

//不使用多态机制
public class Cat {
    public void eat(){
        System.out.println("猫吃鱼");
    }
}

public class Dog {
    public void eat(){
        System.out.println("狗吃骨头");
    }
}

public class Master {		//缺点:Master的扩展力差
    public void feed(Cat c){
        c.eat();
    }
    public void feed(Dog d){		//只要添加新宠物类,Maste类就要添加新方法
        d.eat();
    }
}

public class Test {
    public static void main(String[] args) {
        Master m = new Master();
        Cat c = new Cat();
        Dog d = new Dog();
        m.feed(c);
        m.feed(d);
    }
}

//使用多态机制:降低程序的耦合度【解耦合】,提高程序扩展力
public class Pet {
    public void eat(){
    }
}
public class Cat extends Pet{		//Cat继承Pet
public class Dog extends Pet{		//Dog继承Pet
public class Master {
    public void feed(Pet p){		//Master面向抽象的Pet,不再面向具体的宠物
        p.eat();
    }
}
public class Test {
    public static void main(String[] args) {
        Master m = new Master();
        Cat p1  = new Cat();
        Dog p2 = new Dog();
        m.feed(p1);		//向上转型
        m.feed(p2);
    }
}
  • final关键字:
    1. final关键字表示最终的、不可变的
    2. final修饰的类无法被继承
    3. final修饰的方法无法被覆盖
    4. final修饰的变量赋值后,不能重新赋值
    5. final修饰的实例变量必须手动赋值,不能采用系统默认值
    6. final修饰的引用,一旦指向某个对象后,不能再指向其他对象(被指向的对象无法被垃圾回收器回收)
    7. final修饰的实例变量一般与static一起使用,称为常量
public final class A {
}
public class B extends A{		//编译报错,final修饰的类无法继承
}

public class A {
    public final void C(){
    }
}
public class B extends A{
    public void C(){		//编译报错,final修饰的方法无法覆盖
    }
}

public class A {
    public static void main(String[] args) {
        final  int i = 10;
        i = 20;		//编译报错:无法为最终变量i分配值
        final int k;
        k = 10;		//可以赋值
        k = 20;		//编译报错:可能已分配变量k
    }
}

public class A {
    final int a;		//编译报错,实例变量有默认值,且final修饰的变量不可二次赋值
    
    final int b = 10;		//编译通过,手动赋值
    
    final int a;		//编译通过,构造方法。两种解决方法本质相同
    public A(){
        this.a = 10;
    }
}

public class User {
    int i;
	public User(int i) {
        this.i = i;
    }
}
public class Test {
    public static void main(String[] args) {
        User u = new User(10);
        //程序执行到这里表示以上对象已经成为垃圾数据,等待垃圾回收器的回收
        u = new User(20);
        final User s = new User(10);
        s = new User(20);		//编译报错:无法为最终变量s分配值
        s.i = 20;
        //编译通过,final修饰的引用s中保存的地址不能变,但s指向的对象内部数据可以改
    }
}
  • 常量:
    1. final修饰的变量是不可变的,一般与static一起使用,称为常量
    2. 语法格式:public static final 类型 常量名 = 值;
    3. java规范中要求所有常量名全部大写,每个单词之间用下划线连接
public class Test{
	public static final String GUO_JI = "中国";
	public static final double PI = 3.14159;
	System.out.println("国籍" + Test.GUO_JI);
	Syetem.out.println("圆周率" + PI);
}
  • package/包
    1. 包又称为package,引入包机制主要为了方便程序的管理

    2. 定义package:

      在java源程序的第一行编写package语句,只能编写一条语句

    3. 语法结构:package 包名;

    4. 包名命名规范:公司域名倒序 + 项目名 + 功能名;

      例如:org.apache.tomcat.core;

    5. 包名全部小写,包名也是标识符,遵守标识符命名规则

    6. 一个包对应一个目录,org.apache.tomcat.core对应org\apache\tomcat\core

      运行java命令:创建包名对应的路径,java org.apache.tomcat.core.Test01

      另一种方式:

      编译:javac -d 编译后存放路径 java原文件路径

      运行:切换到org所在路径,执行java org.apache.tomcat.core.Test01

package Test.dayx;
public class Test01 {
}

package Test.dayx;
public class Test02 {
    public static void main(String[] args) {
        Test.dayx.Test01 t = new Test.dayx.Test01();		//完整写法
        System.out.println(t);
        Test01 t = new Test01();		//Test01与Test02在同一包内,可以省略包名
    }
}

package Test;
public class Test03{
	public static void main(String[] args){
		//编译报错,编译器在当前路径下找不到Test01
		Test01 tt = new Test01();		//Test01和Test03不在同一包内,不能省略包名
		Test.dayx.Test01 tt = Test.dayx.new Test01();		//编译通过
		System.out.println(tt);
	}
}
  • import/导入
    1. import语句用来导入其他包中的类(同一个包下不用、java.lang.*不用)
    2. 语法格式:import 包名.类名; import 包名.*;
    3. import语句写在package语句下、class语句上
package Test;
import Test.dayx.Test01;
public class Test00{
	public static void main(String[] args){
		Test01 t = new Test01();		//编译通过
		String s = "abc";		//java.lang.*系统自动导入、language包是java的核心类
	}
}

​ 举例:java.util.Scanner

import java.util.Scanner;		//或者写成java.util.*,编译时自动把*变成具体类名
public class ImportTest {
    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        String str = s.next();
        System.out.println(str);
    }
}
  • super关键字:
    1. this能出现在实例方法和构造方法中,不能用在静态方法中

      语法:super.属性/方法名 访问父类的属性/方法

      super(实参) 调用父类的构造方法

      super.大部分情况下可以省略

      super()只能出现在构造方法第一行,通过当前构造方法调用父类中的构造方法。在创建子类对象时,先初始化父类型特征

    2. super()表示通过子类的构造方法调用父类的构造方法(要想有儿子,先要有父亲)

    3. 当一个构造方法的第一行既没有this()又没有super()时,默认存在一个super();(必须保证父类的无参构造方法存在)

    4. this()和super()不能共存,都只能出现在构造方法第一行

    5. 父类的构造方法一定先执行(Object类的无参构造方法一定会执行,处于栈顶)

    6. 一个类的无参数构造方法最好写上,否则可能影响子类对象的构建

public class SuperTest01 {
    public static void main(String[] args) {
        new B();		//先输出“B类的无参构造方法”,再输出“A类的无参构造方法”
    }
}
class A{
    public A(){
        System.out.println("A类的无参构造方法");
    }
}
class B extends A{
    public B(){		//系统默认super()
        System.out.println("B类的无参构造方法");
    }
}

//接上例,若A类改为有参构造:
public A(int i){}
//则编译报错:实际参数列表和形式参数列表长度不同
class B extends A{
    public B(){
    	super();		//编译报错,super()只能出现在方法体第一行,且只能出现一次
    	super(123);		//调用父类中有参构造方法
        System.out.println("B类的无参构造方法");
    }
}

class B extends A{
    public B(){		//主方法new B();
        this("abc");		//调用B类的有参构造方法
        System.out.println("B类的无参构造方法");
    }
    public B(String name){		//默认有super(),执行A类的无参构造方法
        System.out.println("B类的有参构造方法");
    }		//输出顺序:A无参->B有参->B无参
}
  • 运用super()的举例:
public class SuperTest02 {
    public static void main(String[] args) {
        CreditAccount a = new CreditAccount("123",100.0,99.0);
    }
}

class Account{
    private String actno;
    private double balance;
    public Account(){}
    public Account(String actno,double balance){
        this.actno = actno;
        this.balance = balance;
    }
//setter and getter
}

class CreditAccount extends Account{
    private double credit;
    public CreditAccount(){}
    public CreditAccount(String actno,double balance,double credit){
        /*
        this.actno = actno;		//编译报错,父类中私有变量无法直接修改
        this.balance = balance;
         */
        super(actno,balance);		//使用super()方法调用父类构造方法
        this.credit = credit;
    }
//setter and getter
}
  • super是this指向对象中的一块空间
    1. this.和super.大部分情况下都可以省略
    2. 当想在子类中访问父类特征时,super.不能省略
public class SuperTest03 {
    public static void main(String[] args) {
        Vip v = new Vip("张三");
        v.Shopping();
    }
}
class Customer{
    String name;
    public Customer(){};
    public Customer(String name){
        this.name = name;
    }
}
class Vip extends Customer{
/*
	若在此处添加String name;语句,则输出变为null、张三、null
	java允许在子类中出现和父类同名变量
*/
    public Vip(){}
    public Vip(String name){
        super(name);
    }
    public void Shopping(){
        System.out.println(this.name);		//以下三条语句都输出张三
        System.out.println(super.name);
        System.out.println(name);		//可以省略this.
    }
}
  • super不是引用,不保存内存地址,也不指向任何对象。super只代表当前对象内部的一块父类型特征

    this和super都不能用在静态方法中

public class ThisTest02 {
    public void doSome(){
    //输出“引用”时,会调用引用的toString()方法
        System.out.println(this);		//输出ThisTest02@1b6d3586
        System.out.println(super);		//编译报错:需要'.'
    }
    public static void main(String[] args) {
        ThisTest02 a = new ThisTest02();
        a.doSome();
    }
}
  • 题目:猜数字游戏
public class SuperTest05 {
    public static void main(String[] args) {
        A a = new A(20);
        B b = new B();
        b.setA(a);
        while (true){
            java.util.Scanner s = new java.util.Scanner(System.in);
            System.out.print("请输入整数:");
            int w = s.nextInt();
            b.guess(w);
        }
    }
}
class A{
    private int v;
    public A(int v) {
        this.v = v;
    }
    public int getV() {
        return v;
    }
    public void setV(int v) {
        this.v = v;
    }
}
class B{
    private A a;        	//在B对象中有一个A对象的引用
    public A getA() {		//对数据类型A设置setter and getter
        return a;
    }
    public void setA(A a) {
        this.a = a;
    }
    public void guess(int w){
        if (a.getV() == w){
            System.out.println("猜对了");
            System.exit(0);			//退出JVM
        }else if (a.getV() < w){
            System.out.println("猜小了");
        }else{
            System.out.println("猜大了");
        }
    }
}
  • 访问控制权限修饰符
    1. 访问控制权限修饰符用来控制元素的访问范围,修饰类、变量、方法、接口

    2. 访问控制权限修饰符包括:

      public 公开的,任何位置都能访问

      protected 同一个包下、子类中可以访问

      缺省 同一个包下可以访问

      private 私有的,只能在本类访问

      private < 缺省 < protected < public

    3. 某个数据只希望子类使用,使用protected修饰

    4. 属性和方法四种都能用;类和接口只能用public和缺省

package io.github.heisenberg;
public class User {
    protected int i = 10;		//定义一个protected变量i
    int j = 20;		//定义一个缺省变量j
}

package io.github.heisenberg;		//同一个包下
public class Test01 {
    public static void main(String[] args) {
        User u = new User();
        System.out.println(u.i);		//都能访问
        System.out.println(u.j);
    }
}

package io.github.real;		//换包
import io.github.heisenberg.User;
public class Test02 extends User {		//继承
    public void m(){
        System.out.println(i);		//编译通过
        System.out.println(j);		//编译报错
    }
}
  • 抽象类
    1. 类和类之间有共同特征,这些共同特征提取出来形成抽象类(类到对象是实例化,对象到类是抽象)
    2. 类本身是不存在的,所以抽象类无法创建对象(无法实例化)
    3. 抽象类也属于引用数据类型
    4. 抽象类无法实例化,只能被继承
    5. final不能和abstract一起使用,这两个关键字是对立的
    6. 抽象类无法实例化,但是有构造方法,供子类使用
    7. 抽象类语法:
[修饰符列表] abstract class 类名{
	类体;
}

public class AbstractTest01 {
    public static void main(String[] args) {
        new a();		//编译报错,抽象类无法实例化
    }
}
abstract class a{}		//抽象类
class b extends a{}		//编译通过,b类继承a类
final abstract class c{}		//编译报错:非法的修饰符组合
abstract class d extends a{}		//编译通过,抽象类的子类可以是抽象类

abstract class a{
    public a(String aa){}
}
class b extends a{}		//编译报错
//缺省构造器第一行存在super()语句,而父类没有无参构造方法
  • 抽象方法:
    1. 抽象方法表示没有实现的方法、没有方法体的方法,例如:

      public abstract void doSome();

    2. 抽象方法特点:没有方法体,以分号结尾、修饰符列表中有abstract关键字

    3. 抽象类中不一定有抽象方法,抽象方法只能出现在抽象类中

    4. 一个非抽象的类继承抽象类,必须将抽象类的抽象方法实现了

public class AbstractTest01 {
    public static void main(String[] args) {
        Animal a = new Bird();		//使用多态,面向抽象编程
        a.move();
    }
}
abstract class Animal{
    public abstract void move();
}
class Bird extends Animal{
//如果不写下面这行代码,编译报错,抽象方法不能出现在非抽象类中
    public void move(){		//方法覆盖/重写/实现
    	System.out.println("鸟在飞");
    }
}
abstract class Cat extends Animal{		//编译通过,Cat是抽象类
}
  • 判断题:java中凡是没有方法体的方法都是抽象方法

    错误,Object类中有很多方法没有方法体,都以“;”结尾,但不是抽象方法

    例如:public native int hashCode();方法底层调用了C++写的动态链接程序库

    修饰符列表中没有abstract。native表示调用JVM本地程序

  • 接口
    1. 接口也是一种引用数据类型,编译后生成.class字节码文件

    2. 接口是完全抽象的(抽象类是半抽象)或者说接口是特殊的抽象类

    3. 接口定义语法:

      [修饰符列表] interface 接口名{}

    4. 接口支持多继承(一个接口可以继承多个接口)

    5. 接口中只包含两部分内容:常量、抽象方法

    6. 接口中所有元素都是public修饰的

    7. 接口中抽象方法的public abstract可以省略;接口中的常量public static final可以省略

    8. 接口中的方法都是抽象方法,不能有方法体

public class InterfaceTest01 {
    public static void main(String[] args) {
    	System.out.println(MyMath.PI);		//访问接口的常量
    }
}
interface A{}		//定义接口
interface B extends A{}		//接口继承
interface C extends A,B{}		//编译通过,多继承
interface MyMath{
	public static final double PI = 3.14;
    double PI = 3.14;		//编译通过,public static final可以省略
    public abstract int sum(int a,int b);
    int sum(int a,int b);		//编译通过,可以省略public abstract
    int sum(int a,int b){};		//编译报错:接口抽象方法不能带有主体
}
  • 类和类之间叫做继承,类和接口之间叫做实现

    继承使用extends关键字,实现使用implements关键字

  • 当一个非抽象类实现接口时,必须将接口中所有的抽象方法全部实现

interface MyMath{
    double PI = 3.14;
    int sum(int a,int b);
}
abstract class MyMathImp implements MyMath{}		//编译通过
class MyMath01 implements MyMath{}		//编译报错,非抽象类中不能有抽象方法
class MyMath02 implements MyMath{
	public int sum(int a,int b){		//编译通过,实现抽象方法
		return a + b;
	}
	//方法实现时public不能省略,缺省的访问权限低于public
	int sum (int a,int b){		//编译报错
        return a + b;
    }
}
public class InterfaceTest01 {
    public static void main(String[] args) {
        MyMath mm = new MyMath02();		//多态,面向接口编程
        int result1 = mm.sum(10,20);
        System.out.println(result1);
    }
}
  • 一个类可以同时实现多个接口,弥补了类和类之间只能单继承的缺陷
  • 接口和接口之间没有继承关系也可以强制类型转换,但运行时可能出现ClassCastException异常
public class InterfaceTest02 {
    public static void main(String[] args) {
        M m = new E();
        K k = (K)m;		//编译通过,运行时出现ClassCastException异常
	//添加instanceof判断:
		if (m instanceof K){
			K k = (K)m;
		}
        A a = new D();
        D a1 = (D)a;		//向下转型
        a1.b();		//输出b
    }
}
interface K{}
interface M{}
class E implements M{}

interface A{
    void a();
}
interface B{
    void b();
}
interface C{
    void c();
}
class D implements A,B,C{		//编译通过,实现多个接口
    public void a() {}
    public void b() {
    	System.out.println("b");
    }
    public void c() {}
}
  • 继承和实现都存在时:extends关键字在前,implements关键字在后
public class InterfaceTest03 {
    public static void main(String[] args) {
        Flyable f1 = new Pig();
        f1.fly();
    }
}
class Animal{}
interface Flyable{
    void fly();
}
class Pig extends Animal implements Flyable{
    public void fly(){
        System.out.println("飞猪");
    }
}
  • 接口可以将调用者和实现者解耦合:调用者面向接口调用,实现者面向接口编写实现

    is a表示继承关系;has a表示关联关系,以属性形式存在;like a表示实现关系,通常为类实现接口

public class InterfaceTest04 {
    public static void main(String[] args) {
        Menu m = new Chinese();
        Customer c = new Customer(m);
        c.order();
    }
}
interface Menu{
    void jiDan();
    void rouSi();
}
class Chinese implements Menu{		//Cook like a menu,厨师像菜单一样,实现
    public void jiDan(){
        System.out.println("中餐厨师炒鸡蛋");
    }
    public void rouSi() {
        System.out.println("中餐师傅炒肉丝");
    }
}
class English implements Menu{
    public void jiDan() {
        System.out.println("西餐厨师炒鸡蛋");
    }
    public void rouSi() {
        System.out.println("西餐厨师炒肉丝");
    }
}
class Customer{
    private Menu m;		//Customer has a menu,设置为属性
    public void order(){
        m.jiDan();
        m.rouSi();
    }
    public Customer(Menu m) {
        this.m = m;
    }
    //无参构造方法、setter and getter
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值