• Java基础学习笔记(五)—— 面向对象编程(2)


    面向对象特点:

    面向对象思想是一种更符合我们思考习惯的思想,它可以将复杂的事情简单化,并将我们从执行者变成了指挥者。 面向对象的语言中,包含了三大基本特征,即封装继承多态

    1 封装

    面向对象编程语言是对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界无法直接操作和修改。 封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。要访问该类的数据,必须通过指定的 方式。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性,提高了代码的复用性。

    1.1 private关键字

    使用 private 关键字来修饰成员变量。

    • private是一个权限修饰符,代表最小权限。
    • 可以修饰成员变量和成员方法。
    • 被private修饰后的成员变量和成员方法,只在本类中才能访问。

    private 数据类型 变量名;

    (2)对需要访问的成员变量,提供对应的一对 getXxx 方法 、 setXxx 方法

    • 对于Setter来说,不能有返回值,参数类型和成员变量对应
    • 对于Getter来说,不能有参数,返回值类型和成员变量对应;
    /*问题描述:定义Person的年龄时,无法阻止不合理的数值被设置进来。
    解决方案:用private关键字将需要保护的成员变量进行修饰。
    
    一旦使用了private进行修饰,那么本类当中仍然可以随意访问。
    但是!超出了本类范围之外就不能再直接访问了。
    间接访问private成员变量,就是定义一对儿Getter/Setter方法
    */
    
    public class Person {
    
        String name; // 姓名
        private int age; // 年龄
    
        public void show() {
            System.out.println("我叫:" + name + ",年龄:" + age);
        }
        
        // 这个成员方法,专门用于向age设置数据
        public void setAge(int num) {
            if (num < 100 && num >= 9) { // 如果是合理情况
                age = num;
            } else {
                System.out.println("数据不合理!");// 此时age取默认值0
            }
        }
        
        // 这个成员方法,专门用于获取age的数据
        public int getAge() {
            return age;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    public class DemoPerson {
    
        public static void main(String[] args) {
            Person person = new Person();
            person.show();
    
            person.name = "赵丽颖";
    //        person.age = -20; // 直接访问private内容,错误写法!
            person.setAge(20);
            person.show();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    注意:
    对于基本类型当中的boolean值,Getter方法一定要写成isXxx的形式,而setXxx规则不变。

    1.2 this关键字

    当方法的局部变量和类的成员变量重名的时候,根据“就近原则”,优先使用局部变量。
    如果需要访问本类当中的成员变量,需要使用格式:

    this.成员变量名

    public class Person {
    
        String name; // 我自己的名字
    
        // 参数name是对方的名字
        // 成员变量name是自己的名字
        public void sayHello(String name) {
            System.out.println(name + ",你好。我是" + this.name);
            System.out.println(this);// 地址值 
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    public class DemoPerson {
    
        public static void main(String[] args) {
            Person person = new Person();
            // 设置我自己的名字
            person.name = "王健林";
            person.sayHello("王思聪");
    		// 可以得出,this和person的地址值一致
            System.out.println(person); // 地址值
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    注意:
    this一定在方法内部,this所在方法被哪个对象调用,this就代表哪个对象

    1.3 构造方法

    构造方法是专门用来创建对象的方法,当我们通过关键字new来创建对象时,其实就是在调用构造方法

    public 类名称(参数类型 参数名称) {
        方法体
    }
    
    • 1
    • 2
    • 3

    注意:

    • 构造方法的名称必须和所在的类名称完全一样,就连大小写也要一样
    • 构造方法不要写返回值类型,连void都不写
    • 构造方法不能return一个具体的返回值
    • 如果没有编写任何构造方法,那么编译器将会默认赠送一个构造方法,没有参数、方法体什么事情都不做。一旦编写了至少一个构造方法,那么编译器将不再赠送
    • 一个标准的类通常有4个组成部分,这样标准的类也叫做Java Bean
      – 所有的成员变量都要使用private关键字修饰
      – 为每一个成员变量编写一对儿Getter/Setter方法
      – 编写一个无参数的构造方法
      – 编写一个全参数的构造方法
    public class Student {
    
        // 成员变量
        private String name;
        private int age;
    
        // 无参数的构造方法
        public Student() {
            System.out.println("无参构造方法执行啦!");
        }
    
        // 全参数的构造方法
        public Student(String name, int age) {
            System.out.println("全参构造方法执行啦!");
            this.name = name;
            this.age = age;
        }
    
        // Getter Setter
        public void setName(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public int getAge() {
            return age;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    public class DemoStudent {
    
        public static void main(String[] args) {
            Student stu1 = new Student(); // 无参构造
            System.out.println("============");
    
            Student stu2 = new Student("赵丽颖", 20); // 全参构造
            System.out.println("姓名:" + stu2.getName() + ",年龄:" + stu2.getAge());
            // 如果需要改变对象当中的成员变量数据内容,仍然还需要使用setXxx方法
            stu2.setAge(21); // 改变年龄
            System.out.println("姓名:" + stu2.getName() + ",年龄:" + stu2.getAge());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2 继承

    多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。
    其中,多个类可以称为子类,也叫派生类,单独那一个类称为父类、超类或者基类。

    继承:就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。

    • 提高代码的复用性
    • 类与类之间产生了关系,是多态的前提
    • 子类可以拥有父类的内容,还可以拥有自己专有的内容

    2.1 继承的格式

    通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:

    class 父类 { 
    	...
     }
     class 子类 extends 父类 { 
     	... 
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.2 继承后的特点

    在父子类的继承关系当中,如果子类父类中出现不重名的成员变量或成员方法,这时的访问是没有影响的。

    (1)成员变量重名

    如果父类与子类的成员变量重名,则创建子类对象时,访问有两种方式:

    • 直接通过子类对象访问成员变量:
      – 等号左边是谁,就优先用谁,没有则向上找。
    • 间接通过成员方法访问成员变量:
      – 该方法属于谁,就优先用谁,没有则向上找。

    子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用 super关键字:

    super.父类成员变量名

    • 局部变量: 直接写成员变量名
    • 本类的成员变量: this.成员变量名
    • 父类的成员变量: super.成员变量名

    注意:
    子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()super()this() 都必须是在构造方法的第一行,所以不能同时出现。

    public class Fu {
        int num = 10;
    }
    
    • 1
    • 2
    • 3
    public class Zi extends Fu {
        int num = 20;
        public void method() {
            int num = 30;
            System.out.println(num); // 30,局部变量
            System.out.println(this.num); // 20,本类的成员变量
            System.out.println(super.num); // 10,父类的成员变量
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    public class Test {
        public static void main(String[] args) {
            Zi zi = new Zi();
            zi.method();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (2)成员方法重名

    在父子类的继承关系当中,创建子类对象,访问成员方法的规则:
    创建的对象是谁,就优先用谁,如果没有则向上找。

    注意:
    无论是成员方法还是成员变量,如果没有都是向上找父类,绝对不会向下找子类的。

    (3)构造方法

    public class Fu {
        public Fu() {
            System.out.println("父类无参构造");
        }
    
        public Fu(int num) {
            System.out.println("父类有参构造");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    public class Zi extends Fu {
    
        public Zi() {
            super(); // 在调用父类无参构造方法
    //        super(20); // 在调用父类重载的构造方法
            System.out.println("子类构造方法!");
        }
    
        public void method() {
    //        super(); // 错误写法!只有子类构造方法,才能调用父类构造方法。
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
        public static void main(String[] args) {
            Zi zi = new Zi();
        }
    
    • 1
    • 2
    • 3

    继承关系中,父子类构造方法的访问特点:

    • 子类构造方法当中有一个默认隐含的super()调用,所以一定是先调用的父类构造,后执行的子类构造。
    • 子类构造可以通过super关键字来调用父类重载构造。
    • super的父类构造调用,必须是子类构造方法的第一个语句。不能一个子类构造调用多次super构造。

    子类必须调用父类构造方法,不写则赠送super();写了则用写的指定的super调用,super只能有一个,还必须是第一个。

    总结:继承的三个特点

    1. Java只支持单继承,不支持多继承(一个类的直接父类只能有一个)
    2. Java支持多层继承(继承体系)
    3. 子类和父类是一种相对的概念

    super关键字的用法有三种:

    1. 在子类的成员方法中,访问父类的成员变量。
    2. 在子类的成员方法中,访问父类的成员方法。
    3. 在子类的构造方法中,访问父类的构造方法。

    super关键字用来访问父类内容,而this关键字用来访问本类内容。用法也有三种:

    1. 在本类的成员方法中,访问本类的成员变量。
    2. 在本类的成员方法中,访问本类的另一个成员方法。
    3. 在本类的构造方法中,访问本类的另一个构造方法。 在第三种用法当中要注意: A. this(…)调用也必须是构造方法的第一个语句,唯一一个。
      B. super和this两种构造调用,不能同时使用。

    2.3 抽象类

    (1)抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束。

    修饰符 abstract 返回值类型 方法名 (参数列表)public abstract void run()
    • 1
    • 2
    • 3

    (2)抽象类:抽象方法所在的类,必须是抽象类才行。在class之前写上abstract即可。

    abstract class 类名字 { 
    }
    
    public abstract class Animal { 
    	public abstract void run()}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (3)抽象的使用

    public abstract class Animal {
    
        // 这是一个抽象方法,代表吃东西,但是具体吃什么(大括号的内容)不确定。
        public abstract void eat();
        // 这是普通的成员方法
        public void normalMethod() {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    public class Cat extends Animal {
    
        @Override
        public void eat() {
            System.out.println("猫吃鱼");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    public class Test {
    
        public static void main(String[] args) {
    //        Animal animal = new Animal(); // 错误写法!不能直接创建抽象类对象
    
            Cat cat = new Cat();
            cat.eat();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意:

    • 不能直接创建new抽象类对象,必须用一个子类来继承抽象父类,创建子类对象进行使用
    • 子类必须覆盖重写抽象父类当中所有的抽象方法,除非该子类也是抽象类
      覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号。
    • 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
    • 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。 理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。

    2.4 覆盖重写

    重写(Override):在继承关系当中,方法的名称一样,参数列表也【一样】。覆盖、覆写。
    重载(Overload):方法的名称一样,参数列表【不一样】。

    方法的覆盖重写特点:创建的是子类对象,则优先用子类方法。

    注意:

    • 必须保证父子类之间方法的名称相同,参数列表也相同。
      @Override:写在方法前面,用来检测是不是有效的、正确的覆盖重写。
      这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。

    • 子类方法的返回值必须【小于等于】父类方法的返回值范围。
      小扩展提示:java.lang.Object类是所有类的公共最高父类(祖宗类),java.lang.String就是Object的子类。

    • 子类方法的权限必须【大于等于】父类方法的权限修饰符。
      小扩展提示:public > protected > (default) > private
      备注:(default)不是关键字default,而是什么都不写,留空。

    2.5 继承案例

    群主发普通红包。某群有多名成员,群主给成员发普通红包。普通红包的规则:

    1. 群主的一笔金额,从群主余额中扣除,平均分成n等份,让成员领取。
    2. 成员领取红包后,保存到成员余额中。

    请根据描述,完成案例中所有类的定义以及指定类之间的继承关系,并完成发红包的操作。

    public class User {
    
        private String name; // 姓名
        private int money; // 余额,也就是当前用户拥有的钱数
    
        public User() {
        }
    
        public User(String name, int money) {
            this.name = name;
            this.money = money;
        }
    
        // 展示一下当前用户有多少钱
        public void show() {
            System.out.println("我叫:" + name + ",我有多少钱:" + money);
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getMoney() {
            return money;
        }
    
        public void setMoney(int money) {
            this.money = money;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    // 群主的类
    public class Manager extends User {
    
        public Manager() {
        }
    
        public Manager(String name, int money) {
            super(name, money);
        }
    
        public ArrayList<Integer> send(int totalMoney, int count) {
            // 首先需要一个集合,用来存储若干个红包的金额
            ArrayList<Integer> redList = new ArrayList<>();
    
            // 首先看一下群主自己有多少钱
            int leftMoney = super.getMoney(); // 群主当前余额
            if (totalMoney > leftMoney) {
                System.out.println("余额不足");
                return redList; // 返回空集合
            }
    
            // 扣钱,其实就是重新设置余额
            super.setMoney(leftMoney - totalMoney);
    
            // 发红包需要平均拆分成为count份
            int avg = totalMoney / count;
            int mod = totalMoney % count; // 余数,也就是甩下的零头
    
            // 除不开的零头,包在最后一个红包当中
            // 下面把红包一个一个放到集合当中
            for (int i = 0; i < count - 1; i++) {
                redList.add(avg);
            }
    
            // 最后一个红包
            int last = avg + mod;
            redList.add(last);
    
            return redList;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    // 普通成员
    public class Member extends User {
    
        public Member() {
        }
    
        public Member(String name, int money) {
            super(name, money);
        }
    
        public void receive(ArrayList<Integer> list) {
            // 从多个红包当中随便抽取一个,给我自己。
            // 随机获取一个集合当中的索引编号
            int index = new Random().nextInt(list.size());
            // 根据索引,从集合当中删除,并且得到被删除的红包,给我自己
            int delta = list.remove(index);
            // 当前成员自己本来有多少钱:
            int money = super.getMoney();
            // 加法,并且重新设置回去
            super.setMoney(money + delta);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    import java.util.ArrayList;
    
    public class MainRedPacket {
    
        public static void main(String[] args) {
            Manager manager = new Manager("群主", 100);
    
            Member one = new Member("成员A", 0);
            Member two = new Member("成员B", 0);
            Member three = new Member("成员C", 0);
    
            manager.show(); // 100
            one.show(); // 0
            two.show(); // 0
            three.show(); // 0
            System.out.println("===============");
    
            // 群主总共发20块钱,分成3个红包
            ArrayList<Integer> redList = manager.send(20, 3);
            // 三个普通成员收红包
            one.receive(redList);
            two.receive(redList);
            three.receive(redList);
    
            manager.show(); // 100-20=80
            // 6、6、8,随机分给三个人
            one.show();
            two.show();
            three.show();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    3 多态

    3.1 接口

    (1)接口的概述

    接口,是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、成员方法和构造方法,那么 接口的内部主要就是封装了方法,包含常量、抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法 (JDK 9)。

    接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。

    接口的使用,它不能创建对象,但是可以被实现(implements ,类似于被继承)。一个实现接口的类(可以看做是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。

    注意:
    引用数据类型:数组,类,接口。

    (2)接口的定义格式

    public interface 接口名称 {
        // 接口内容
    }
    
    • 1
    • 2
    • 3

    注意:
    换成了关键字interface之后,编译生成的字节码文件仍然是:.java —> .class

    (3)接口的使用

    类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类 似继承,格式相仿,只是关键字不同,实现使用 implements关键字

    ① 抽象方法的使用

    接口使用步骤:

    • 接口不能直接使用,必须有一个“实现类”来“实现”该接口。
      格式:

      public class 实现类名称 implements 接口名称 {
          // 重写接口中抽象方法【必须】 
          // 重写接口中默认方法【可选】
      }
      
      • 1
      • 2
      • 3
      • 4
    • 接口的实现类必须覆盖重写(实现)接口中所有的抽象方法。
      实现:去掉abstract关键字,加上方法体大括号。

    • 不能直接new接口对象使用,所以接口没有构造方法,应该创建实现类的对象,进行使用。

    注意:
    如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类。

    在任何版本的Java中,接口都能定义抽象方法

    [public] [abstract] 返回值类型 方法名称(参数列表);
    
    • 1
    public interface MyInterface {
    
        // 这是一个抽象方法
        public abstract void methodAbs1();
    
        // 这也是抽象方法
        abstract void methodAbs2();
    
        // 这也是抽象方法
        public void methodAbs3();
    
        // 这也是抽象方法
        void methodAbs4();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    注意:

    • 接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract,这两个关键字修饰符,可以选择性地省略
    • 方法的三要素,可以随意定义。

    ② 默认方法的使用

    从Java 8开始,接口里允许定义默认方法。

    • 接口的默认方法,可以通过接口实现类对象,直接调用。
    • 接口的默认方法,也可以被接口实现类进行覆盖重写。
    [public] default 返回值类型 方法名称(参数列表) {
        方法体
    }
    
    • 1
    • 2
    • 3

    注意:

    • 接口当中的默认方法,可以解决接口升级的问题
    • 默认方法是有方法体的
    public interface MyInterfaceDefault {
    
        // 抽象方法
        public abstract void methodAbs();
    
        // 新添加了一个抽象方法
    //    public abstract void methodAbs2(); 子类若是没有重写该方法会报错
    
        // 新添加的方法,改成默认方法,子类若是没有重写该方法不会报错
        public default void methodDefault() {
            System.out.println("这是新添加的默认方法");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    public class MyInterfaceDefaultA implements MyInterfaceDefault {
        @Override
        public void methodAbs() {
            System.out.println("实现了抽象方法,AAA");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    public class MyInterfaceDefaultB implements MyInterfaceDefault {
        @Override
        public void methodAbs() {
            System.out.println("实现了抽象方法,BBB");
        }
    
        @Override
        public void methodDefault() {
            System.out.println("实现类B覆盖重写了接口的默认方法");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    public class DemoInterface {
    
        public static void main(String[] args) {
            // 创建了实现类对象
            MyInterfaceDefaultA a = new MyInterfaceDefaultA();
            a.methodAbs(); // 调用抽象方法,实际运行的是右侧实现类。
    
            // 调用默认方法,如果实现类当中没有,会向上找接口
            a.methodDefault(); // 这是新添加的默认方法
            System.out.println("==========");
    
            MyInterfaceDefaultB b = new MyInterfaceDefaultB();
            b.methodAbs();
            b.methodDefault(); // 实现类B覆盖重写了接口的默认方法
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    ③ 静态方法的使用

    从Java 8开始,接口当中允许定义静态方法,但接口中不能有静态代码块 static {}

    [public] static 返回值类型 方法名称(参数列表) {
        方法体
    }
    
    • 1
    • 2
    • 3

    注意:

    • 静态方法的使用就是将abstract或者default换成static即可,带上方法体
    • 不能通过接口实现类的对象来调用接口当中的静态方法
      正确用法:通过接口名称,直接调用其中的静态方法,接口名称.静态方法名(参数);
    public interface MyInterfaceStatic {
    
        public static void methodStatic() {
            System.out.println("这是接口的静态方法!");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    public class MyInterfaceStaticImpl implements MyInterfaceStatic {
    }
    
    • 1
    • 2
    public class Demo03Interface {
    
        public static void main(String[] args) {
            // 创建了实现类对象
            MyInterfaceStaticImpl impl = new MyInterfaceStaticImpl();
    
            // 错误写法!
    //        impl.methodStatic();
    
            // 直接通过接口名称调用静态方法
            MyInterfaceStatic.methodStatic();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    ④ 私有方法的使用

    我们需要抽取一个共有方法,用来解决两个默认方法之间重复代码的问题。
    但是这个共有方法不应该让实现类使用,应该是私有化的。private 的方法只有接口自己才可以调用,不能被实现类或被人调用

    从Java 9开始,接口当中允许定义私有方法。

    • 普通私有方法,解决多个默认方法之间重复代码问题
    private 返回值类型 方法名称(参数列表) {
        方法体
    }
    
    • 1
    • 2
    • 3
    • 静态私有方法,解决多个静态方法之间重复代码问题
    private static 返回值类型 方法名称(参数列表) {
        方法体
    }
    
    • 1
    • 2
    • 3

    ⑤ 常量的使用

    接口当中也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰。
    从效果上看,这其实就是接口的【常量】。

    [public] [static] [final] 数据类型 常量名称 = 数据值;
    
    • 1

    注意:

    1. 接口当中的常量,可以省略 public static final ,注意:不写也照样是这样。
    2. 接口当中的常量,必须进行赋值;不能不赋值。
    3. 接口中常量的名称,使用完全大写的字母,用下划线进行分隔。(推荐命名规则)
    4. 一旦使用 final 关键字进行修饰,说明不可改变
    5. 访问接口当中的常量 接口名称.常量名称

    (4)接口的多实现

    一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。

    public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {
        // 覆盖重写所有抽象方法
    }
    
    • 1
    • 2
    • 3

    注意:

    • 如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
    • 如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类。
    • 如果实现类锁实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写。
    • 一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先用父类当中的方法。

    (5)接口的多继承

    类与类之间是单继承的。直接父类只有一个。
    类与接口之间是多实现的。一个类可以实现多个接口。
    接口与接口之间是多继承的。

    public interface MyInterfaceA {
        public abstract void methodA();
        public abstract void methodCommon();
        public default void methodDefault() {
            System.out.println("AAA");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    public interface MyInterfaceB {
        public abstract void methodB();
        public abstract void methodCommon();
        public default void methodDefault() {
            System.out.println("BBB");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    /*
    这个子接口当中有几个方法?答:4个。
    methodA 来源于接口A
    methodB 来源于接口B
    methodCommon 同时来源于接口A和B
    method 来源于我自己
     */
    public interface MyInterface extends MyInterfaceA, MyInterfaceB {
        public abstract void method();
        @Override
        public default void methodDefault() {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    public class MyInterfaceImpl implements MyInterface {
        @Override
        public void method() {
        }
        
        @Override
        public void methodA() {
        }
        
        @Override
        public void methodB() {
        }
        
        @Override
        public void methodCommon() {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    注意:

    • 多个父接口当中的抽象方法如果重复,没关系,因为抽象方法没有方法体
    • 多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,【而且带着default关键字】。

    3.2 多态

    (1)多态的概述

    多态是继封装、继承之后,面向对象的第三大特性。
    extends继承或者implements 实现,是多态性的前提。

    生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也 是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。

    多态: 是指同一行为,具有多个不同表现形式。

    (2)多态的格式与使用

    代码当中体现多态性,其实就是一句话:父类引用指向子类对象。

    父类名称 对象名 = new 子类名称();
    // 或者:
    接口名称 对象名 = new 实现类名称();
    
    • 1
    • 2
    • 3
    public class Fu {
        public void method() {
            System.out.println("父类方法");
        }
        public void methodFu() {
            System.out.println("父类特有方法");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    public class Zi extends Fu {
        @Override
        public void method() {
            System.out.println("子类方法");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    public class Demo {
        public static void main(String[] args) {
            // 使用多态的写法
            // 左侧父类的引用,指向了右侧子类的对象
            Fu obj = new Zi();
            obj.method(); // 子类方法
            obj.methodFu(); // 父类特有方法
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意:
    左父右子就是多态,把子类当做父类来使用(一只猫被当作动物来看待),因为子类就是一个父类。

    (3)多态中成员变量的使用

    访问成员变量的两种方式:

    • 直接通过对象名称访问成员变量:看等号左边是谁,优先用谁,没有则向上找。
    • 间接通过成员方法访问成员变量:看该方法属于谁,优先用谁,没有则向上找。
    public class Fu /*extends Object*/ {
    
        int num = 10;
    
        public void showNum() {
            System.out.println(num);
        }
        public void method() {
            System.out.println("父类方法");
        }
        public void methodFu() {
            System.out.println("父类特有方法");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    public class Zi extends Fu {
    
        int num = 20;
        int age = 16;
    
        @Override
        public void showNum() {
            System.out.println(num);
        }
        @Override
        public void method() {
            System.out.println("子类方法");
        }
        public void methodZi() {
            System.out.println("子类特有方法");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    
    public class Demo{
    
        public static void main(String[] args) {
            // 使用多态的写法,父类引用指向子类对象
            Fu obj = new Zi();
            System.out.println(obj.num); // 父:10
    //        System.out.println(obj.age); // 错误写法!
            System.out.println("=============");
    
            // 子类没有覆盖重写,就是父:10
            // 子类如果覆盖重写,就是子:20
            obj.showNum();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    注意:
    成员变量不可以覆盖重写,成员方法可以

    (4)多态中成员方法的使用

    在多态的代码当中,成员方法的访问规则是:看new的是谁,就优先用谁,没有则向上找。

    口诀:编译看左边,运行看右边。

    对比一下:
    成员变量:编译看左边,运行还看左边。
    成员方法:编译看左边,运行看右边。

    public class Fu {
        public void method() {
            System.out.println("父类方法");
        }
        public void methodFu() {
            System.out.println("父类特有方法");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    public class Zi extends Fu {
        @Override
        public void method() {
            System.out.println("子类方法");
        }
        public void methodZi() {
            System.out.println("子类特有方法");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    public class Demo0{
    
        public static void main(String[] args) {
            Fu obj = new Zi(); // 多态
    
            obj.method(); // 父子都有,优先用子
            obj.methodFu(); // 子类没有,父类有,向上找到父类
    
            // 编译看左边,左边是Fu,Fu当中没有methodZi方法,所以编译报错。
    //        obj.methodZi(); // 错误写法!
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (5)多态的好处在这里插入图片描述

    3.3 引用类型转换

    多态的转型分为向上转型与向下转型两种
    在这里插入图片描述
    (1)向上转型

    多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的

    父类类型 变量名 = new 子类类型(); 
    // 如:Animal a = new Cat();
    
    • 1
    • 2

    (2)向下转型

    父类类型向子类类型向下转换的过程,这个过程是强制的

    子类类型 变量名 = (子类类型) 父类变量名; 
    // 如:Cat c =(Cat) a;
    
    • 1
    • 2
    public abstract class Animal {
        public abstract void eat();
    }
    
    • 1
    • 2
    • 3
    public class Cat extends Animal {
        @Override
        public void eat() {
            System.out.println("猫吃鱼");
        }
        // 子类特有方法
        public void catchMouse() {
            System.out.println("猫抓老鼠");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    /*
    向上转型一定是安全的,没有问题的,正确的。但是也有一个弊端:
    对象一旦向上转型为父类,那么就无法调用子类原本【特有】的内容。
    
    解决方案:用对象的向下转型【还原】。
     */
    public class Demo{
    
        public static void main(String[] args) {
            // 对象的向上转型,就是:父类引用指向之类对象。
            Animal animal = new Cat(); // 本来创建的时候是一只猫
            animal.eat(); // 猫吃鱼
    
    //        animal.catchMouse(); // 错误写法!
    
            // 向下转型,进行“还原”动作
            Cat cat = (Cat) animal;
            cat.catchMouse(); // 猫抓老鼠
    
            // 下面是错误的向下转型
            // 本来new的时候是一只猫,现在非要当做狗
            // 错误写法!编译不会报错,但是运行会出现异常:
            // java.lang.ClassCastException,类转换异常
            Dog dog = (Dog) animal;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    (3)类型判断

    如何才能知道一个父类引用的对象,本来是什么子类?

    对象 instanceof 类名称
    
    • 1

    这将会得到一个boolean值结果,也就是判断前面的对象能不能当做后面类型的实例

    public class Demo02Instanceof {
    
        public static void main(String[] args) {
            Animal animal = new Cat(); // 本来是一只猫
            animal.eat(); // 猫吃鱼
    
            // 如果希望掉用子类特有方法,需要向下转型
            // 判断一下animal本来是不是Cat
            if (animal instanceof Cat) {
                Cat cat = (Cat) animal;
                cat.catchMouse();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3.4 接口多态综合案例

  • 相关阅读:
    shell脚本命令行参数 | while getopts
    Windows Install PowerCLI
    【现代控制理论】| 线性系统的状态空间法
    ArcGIS与MINIO系列文章(1)-MINIO搭建
    nodejs + express 实现 http文件下载服务程序
    如何设置让vs 在生成程序错误的情况下不去执行上一个可以执行的程序?
    暑期算法打卡----第二天
    NVIDIA全息VR显示专利:内含多种光学方案
    【Q&A】Troubleshooting R Studio
    kafka管理工具之kafka-ui的环境搭建笔记
  • 原文地址:https://blog.csdn.net/hu_wei123/article/details/125253745