续写面向对象概述

版权声明:@2018 https://blog.csdn.net/weixin_40442684/article/details/81173652

3 继承与多态

3.1 继承的基本概念

什么是继承?

继承就是可以直接使用前辈的属性和方法。

为什么需要继承?

自然界中如果没有继承,那么一切都处于混沌状态。在软件开发中我们可以借鉴自然界的机制,已经有的东西我们希望能够直接拿来用(复用)而不需要重复做。

案例:定义人和员工类

人类

public class Person {
    private String id ;//身份证号码
    private String name;//姓名
    private String sex;//性别

    public Person(String id, String name, String sex) {
        this.id = id;
        this.name = name;
        this.sex = sex;
    }

    public void print(){
        System.out.println(id);
        System.out.println(name);
        System.out.println(sex);
    }
}

员工类

public class Employee {
    private String id ;//身份证号码
    private String name;//姓名
    private String sex;//性别
    private double salary;//工资

    public Employee(String id, String name, String sex, int salary) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.salary = salary;
    }

    public void print(){
        System.out.println(id);
        System.out.println(name);
        System.out.println(sex);
        System.out.println(salary);
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        Person p = new Person("32311","小明","男");
        Employee e = new Employee("452422","小敏","女",8900);
        p.print();
        e.print();
    }
}

   这样做存在的问题

  1. 重复劳动:人有姓名,性别等属性,员工中又重复定义了,将来可能在学生、教师等类中还要重复。
  2. 信息不一致:比如人的性别用0-1表示,而员工类中可能用“男/女”来表示,导致使用上的混乱。

那么能否在员工类中使用已有的人类的姓名、性别等呢———继承。

3.2 类的继承

如何使用继承呢?

把上面的员工类改写一下——继承Person类。

public class Employee extends Person{
    private int salary ;//工资

    public Employee(String id, String name, String sex, int salary) {
        super(id, name, sex);
        this.salary = salary;
    }

    public void print(){
        super.print();
        System.out.println(salary);
    }
}

使用继承就很好地解决了上面提出的两个问题。

Extends是“拓展、延伸”的含义,public class Employee extends Person就是声明一个Employee的类,它是在Person类的基础上进行拓展,专业术语称之为继承自Person类。

Super是“超”的含义,Person类相对于Employee类就是超类,通俗一点说法就是父类。

子类继承了父类的属性和方法,当然也可以在此基础上进行拓展——加和改。在这个案例中加了“薪水”属性,改了“打印”方法。这里有一个专业术语——重写(override)。

继承不仅仅解决了刚才的两个问题,还使得类有层次结构,逻辑上也更加合理。比如“员工”也是“人”。

3.3访问权限控制(补充)

在此之前已经讲过private和public

Private:私有的,外部是不可访问的。

Public:公有的,外部可以访问。

Default默认的,子类和父类不在同一包中时,default型的成员变量是不能够在子类中被访问。即是说,如果子类和父类在同一包中,那么父类的default型的成员变量是能够被子类访问的

Protected:保护的,外部访问如果是来自子类是可以的,其它的拒绝。

也就是外部分为两种:子类和非子类,对于子类而言,Protected等同于public,而对于非子类Protected又等同于private。

比如在Employee类中,如果name是私有的,那么这个访问就是被拒绝的,而如果是保护的则可以。

public void print(){
    super.print();
    System.out.println(name);
    System.out.println(salary);
}

即使是保护的成员,对于非子类的外部访问也是被拒绝的,比如在测试类中就是不可以的。

public class Test {
    public static void main(String[] args) {
        Person p = new Person("32311","小明","男");
        Employee e = new Employee("452422","小敏","女",8900);
        p.print();
        e.print();
        System.out.println(e.name);
    }
}

 

3.4 多态

多态就是将同一个消息发送给不同对象,它们所做的响应可能是不同的。 比如说动物都有“叫”的方法,但是狗是“汪汪”,而猫是“喵喵”,当它们接收到被打的消息时,所做的响应是不同的。

案例:

定义一个动物类

public class Animal {
    public void shout(){
        System.out.println("哼哼……");
    }
}

定义一个狗类,继承动物类,重写“叫”的方法

public class Dog extends Animal{
    @Override
    public void shout() {
        System.out.println("汪汪……");
    }
}

定义一个猫类,继承动物类,重写“叫”的方法

public class Cat extends Animal{
    @Override
    public void shout() {
        System.out.println("喵喵……");
    }
}

Test测试类

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

        hit(d);
        hit(c);
    }

    private static void hit(Cat c) {
        c.shout();
    }

    private static void hit(Dog d) {
        d.shout();
    }

在测试程序中定义了两个“打”的方法,这种做法是比较笨拙的,有几种动物就需要定义几个方法,这个可能没完没了。有没有一个一劳永逸的方法?

不要传递Dog或者Cat等对象,而是传递一个Animal类对象就可以解决上面的问题。测试程序修改为:

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

        hit(d);
        hit(c);
    }


    private static void hit(Animal a){
        a.shout();
    }
}

这里涉及两个知识点:

  1. 赋值兼容性规则:

hit方法形参是Animal类型,而实参是Dog或者是Cat,所以类型不一致,为什么还可以呢?

所谓赋值兼容性规则,指的是凡是需要用到父类引用的地方,都可以使用它的子类引用去代替。比如某人请求派接他,结果对方派了个三轮车去接了,虽然他可能不满意,但是他也没有办法,因为对方是按照他的要求做的。

  1. 多态

形参一定是父类的引用,而实参可以是它的任何一个子类引用。

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

        Animal a = d;
        a.shout();

        a = c;
        a.shout();

 

3.5 final

当用final修饰类的时候,此类不可被继承,即final没有子类,
==>说的是类。不用final修饰的类,都是可以被extends
final影响继承,但是不涉及继承。

1) final修饰的类不能被继承
2) final修饰的变量其值不能被改变
3final修饰的局部变量有利于垃圾回收

4 抽象类与接口

4.1 抽象类

在上一章动物多态性的案例中,我们提到了动物有“叫”的方法

public class Animal {
    public void shout(){
        System.out.println("哼哼……");
    }
}

实际上,我们能确定的是动物是有“叫”的方法,但是具体怎么叫是无法描述的!

那么在java中如何来描述和解决这样的问题呢——抽象方法。

抽象方法只有声明——没有实现。

如果类中含有抽象方法,那么它就是抽象类。

抽象类不能实例化对象,下面的代码就是错误的:

Animal a1 = new Animal();

既然不能实例化对象,那么抽象类还有什么用呢?

抽象类是去完成上层规划,不去考虑具体实现细节,这个可以交由后代去实现。

4.2 接口

抽象类指的是其中含有抽象方法,而所谓接口,指的是其中所有的方法都是抽象方法()。

为什么要使用接口呢?

如何各个形状类自行其是,比如三角形面积返回值类型是float,矩形返回值类型是double,而圆的方法名使用拼音。那么对于使用者必然造成困扰——太乱了。

而使用接口就起到了统一规划的目的,它的实现者(继承者)必须要实现它规划好的方法,不能更改。这样就增加了类的一致性,便于使用和维护。

4.3 匿名内部类

在上面的案例中,如果还需要显示一个正方形的信息,和之前的圆形以及矩形类似,需要以下步骤:

  1. 定义一个类并实现形状接口
public class Rectangular implements Shape
  1. 定义一个对象
Rectangular r = new Rectangular(p,3,5);
  1. 使用该对象

show(r);

如果这个对象我们仅仅就使用一次,上面的步骤就过于麻烦了,可以考虑简化——匿名内部类。

show(new Shape() {
    @Override
    public double length() {
        return 3*5;
    }

    @Override
    public double area() {
        return 5*4;
    }
});

 

5 泛型

5.1 泛型的引入

在之前的堆栈中,,我们只能储存int数据,如果需要储存float数据,就必然必须再定义一个堆栈。我们发现代码基本上是一样的,只是将其中的部分int改成了float,但是我们比不得不重写一遍。

但是如果以后还要储存double,String……等类型数据该怎么办?有没有一劳永逸的办法只写一遍呢。——泛型。

public class FloatStack {
        private float [] data;//数据存储区
        private int capacity;//堆栈容量
        private int size;//堆栈当前实际大小
        private int top;//栈顶位置

        public FloatStack(int capacity) {
            this.capacity = capacity;
            data = new float[capacity];
            size = 0;
            top = -1;
        }

        public boolean isFull() {
            return size == capacity;
        }

        public boolean isEmpty() {
            return size == 0;
        }

        public boolean push(float d) {
            if (isFull()) {
                return false;
            }

            top++;
            size++;
            data[top] = d;

            return true;
        }

        public Float pop() {
            if (isEmpty()) {
                return null;
            }
            float d=data[top];
            top--;
            size--;

            return d;
        }


        public int getSize() {
            return size;
        }

        public void print() {
        }
    }

 

5.2 泛型的定义和使用

使用泛型定义一个通用堆栈。也就是把刚才的特定类型改为“某种”类型,即泛指某种类型(这就是泛型的由来)。就这么一下点的改动带来的确实本质性的变化——一劳永逸,体现了通用。

public class CommonStack <T>{
        private T [] data;//数据存储区
        private int capacity;//堆栈容量
        private int size;//堆栈当前实际大小
        private int top;//栈顶位置

        public CommonStack(int capacity) {
            this.capacity = capacity;
            data = (T[]) new Object[capacity];
            size = 0;
            top = -1;
        }

        public boolean isFull() {
            return size == capacity;
        }

        public boolean isEmpty() {
            return size == 0;
        }

        public boolean push(T d) {
            if (isFull()) {
                return false;
            }

            top++;
            size++;
            data[top] = d;

            return true;
        }

        public T pop() {
            if (isEmpty()) {
                return null;
            }
            T d=data[top];
            top--;
            size--;

            return d;
        }


        public int getSize() {
            return size;
        }

        public void print() {
        }
    }

使用泛型的时候需要指定具体类型。

CommonStack<Float> s2 = new CommonStack<>(5);

这个叫类型参数化,也就是将类型作为参数。在此之前我们说的参数都是数据,只不过数据是有类型的。普通参数“()”,类型参数用“<>”。

猜你喜欢

转载自blog.csdn.net/weixin_40442684/article/details/81173652