【Java编程的逻辑】类的基础 & 类的继承

类的基本概念

  1. pubilc: 可以修饰类、类方法、类变量、实例变量、实例方法。构造方法,表示可被外部访问
  2. private:可以修饰类、类方法、类变量、实例变量、实例方法。构造方法,表示不可以被外部访问,只能在类内部被使用
  3. static:修饰类变量和类方法,它也可以修饰内部类
  4. this:表示当前实例,可以用于调用其他构造方法,访问实例变量,访问实例方法
  5. final:修饰类变量、实例变量,表示只能被赋值一次,也可以修饰实例方法和局部变量

类的继承

基本概念

  1. 每个类有且只有一个父类,没有声明父类的,其父类为Object,子类继承了父类非private的属性和方法,可以增加自己的属性和方法,以及重写父类的方法实现。
  2. new过程中,父类先进行初始化,子类可以通过super调用父类相应的构造方法,没有使用super的情况下,调用父类的默认构造方法。如果父类没有默认的构造方法,则必须指定
  3. 子类变量和方法与父类重名的情况下,可通过super强制访问父类的变量和方法
  4. 子类对象可以赋值给父类引用变量,这叫多态;实际执行调用的是子类实现,这叫动态绑定。

这里对多态、动态绑定给出一个示例进行说明。
这里以图形为例,所有图形(Shape)都有一个表示颜色的属性,有一个表示绘制的方法

public class Shape {

    private String color;

    private static final String DEFAULT_COLOR = "black";

    public Shape() {
        this(DEFAULT_COLOR);
    }

    public Shape(String color) {
        this.color = color;
    }

    public void draw() {
        System.out.println("draw Shape");
    }

    // 省略color的getter/setter
}

再来一个圆(Circle)继承自Shape,但它有额外的属性:半径

public class Circle extends Shape{
    // 半径
    private double r;

    @Override
    public void draw() {
        System.out.println("draw circle, the r is " + 
                    this.r + ",using color is: " + getColor());
    }

}

再来一条直线(Line)继承自Shape,它有额外的属性:长度

public class Line extends Shape{
    private int length;

    public Line(int length, String color) {
        super(color);
        this.length = length;
    }

    @Override
    public void draw() {
        System.out.println("Draw line, the color is " + getColor());
    }

}

最后就进行测试,我们新建一个Circle和一个Line,将它们放入一个Shape的集合。然后执行draw方法

public static void main(String[] args) {
    List<Shape> shapes = new ArrayList<>();
    shapes.add(new Line(998, "RED"));
    shapes.add(new Circle());
    for(Shape shape: shapes) {
        shape.draw();
    }
}

最后的输出是

Draw line, the color is RED
draw circle, the r is 0.0,using color is: black

在shapes.add中,参数是Shape,而实际的类型则分别是Circle和Line。子类对象赋值给父类引用变量,这叫向上转型

变量shape可以引用任何Shape子类类型的对象,这叫多态,即一种类型的变量,可引用多重实际类型对象。这样,对于变量shape,它就有两种类型:类型Shape,我们称之为静态类型,类型Circle/Line,我们称之为动态类型
shape.draw()调用的是其对应的动态类型的draw方法,这称之为方法的动态绑定

继承的细节

构造方法

前面说到:子类可以通过super调用父类相应的构造方法,没有使用super的情况下,调用父类的默认构造方法。如果父类没有默认的构造方法,则必须指定。
这里举例说明一下

public class Base {
    private String name;
    public Base(String name) {
        this.name = name;
    }
}

父类只有一个带参数的构造方法,没有默认构造方法。
它的子类都必须在构造方法中通过super调用Base的带参构造方法,否则会报错:
如果子类不指定父类的构造方法

静态绑定

当子类和父类中有重名的变量或方法。
private变量和方法只能在类内访问,访问也永远是当前类的,即:在子类中访问的是子类的;在父类中访问的是父类的。
public变量和方法,则要看如何访问它。
1. 在类内,访问的是当前类的,但子类可以通过super.明确指定访问父类的。
2. 在类外,则要看访问变量的静态类型:静态类型是父类,则访问父类的变量和方法;静态类型是子类,则访问的是子类的变量和方法

public class Base {
    public static String s = "static_base";
    public String m = "base";
    public static void staticTest() {
        System.out.println("base static " + s);
    }
}
public class Child extends Base{
    public static String s = "static_child";
    public String m = "child";
    public static void staticTest() {
        System.out.println("child static " + s);
    }
}
public static void main(String[] args) {
    Child child = new Child();
    Base b = child;
    System.out.println(b.m);
    System.out.println(b.s);
    b.staticTest();

    System.out.println(child.m);
    System.out.println(child.s);
    child.staticTest();
}

输出

base
static_base
base static static_base
child
static_child
child static static_child

当通过b(静态类型Base)访问时,访问的是Base的变量和方法,当通过child(静态类型Child)访问时,访问的是Child的变量和方法,这称之为静态绑定,即访问绑定到变量的静态类型。

重载和重写

重载是指方法名称相同但参数签名不同(参数个数、类型或顺序不同)
重写是指子类重写与父类相同的参数签名的方法

当有多个重名函数的时候,在决定要调用哪个函数的过程中,首先是按照参数类型进行匹配的,换句话说,寻找在所有重载版本中最匹配的,然后才看变量的动态类型,进行动态绑定。

父子类型转换

子类型的对象可以赋值给父类型的引用变量,这是向上转型。
那么父类型的变量可以赋值给子类型的变量吗? 或者说可以向下转型吗?

Base b = new Child();
// 将变量b的类型强制转换为Child并赋值为c
Child c = (Child)b;

因为b的动态类型就是Child,所以上面那样是不会出错的。
但是,

Base b = new Base();
Child c = (Child)b;

语法上不会有错,但是运行时会抛出错误,错误为类型转换异常。

一个父类的变量能不能转换为一个子类的变量,取决于这个父类变量的动态类型(即引用的对象类型)是不是这个子类或者这个子类的子类

给定一个父类的变量能不能知道她是否可以安全的向下转型呢?

if(b instanceof Child) {
    Child c = (Child)b;
}

继承访问权限protected

protected表示虽然不能被外部任意访问,但可被子类访问,还表示可以被同一包中的其他类访问,不管其他类是不是该类的子类。

可见性重写

重写方法时,一般并不会修改方法的可见性。
但是,重写时,子类方法不能降低父类方法的可见性。
父类如果是public,则子类也必须是public;父类如果是protected,子类可以是protected,也可以是public。 即子类可以升级父类方法的可见性但不能降低。

why?

继承反映的是“is-a”的关系,即子类对象也属于父类,子类必须支持父类所有对外的行为,将可见性降低就会减少子类对外的行为,从而破坏“is-a”的关系。

继承实现的基本原理

类加载过程

所谓类的加载是指将类的相关信息加载到内存。在Java中,类是动态加载的,当第一次使用这个雷电 时候才会加载,加载一个类时,会查看其父类是否已经加载,如果没有,则会加载其父类。

一个类的信息主要包括以下部分:

  • 类变量 (静态变量)
  • 类初始化代码
    • 定义静态变量时的赋值语句
    • 静态初始化代码块
  • 类方法
  • 实例变量
  • 实例初始化代码
    • 定义实例变量时的赋值语句
    • 实例初始化代码块
    • 构造方法
  • 实例方法
  • 父类信息引用

为什么说继承是把双刃剑

什么是封装呢? 封装就是隐藏实现细节,提供简化接口,使用者只需要关注怎么用,不需要关注内部是怎么实现的。

封装是如何被破坏的

这里直接看代码的示例来说明

public class Base {
    private static final int MAX_NUM = 1000;
    private int[] arr = new int[MAX_NUM];
    private int count;
    // 添加单个数字
    public void add(int num) {
        if(count < MAX_NUM) {
            arr[count++] = num;
        }
    }
    // 添加一串数组
    public void addAll(int[] nums) {
        for(int num : nums) {
            // 内部通过add,添加单个数字方法实现
            add(num);
        }
    }
}
public class Child extends Base{
    // 添加过的数字的总和
    private long sum;
    // 添加一个数字,并计算和
    @Override
    public void add(int num) {
        super.add(num);
        sum+=num;
    }
    // 添加一个数字,并计算和
    @Override
    public void addAll(int[] nums) {
        super.addAll(nums);
        for (int i = 0; i < nums.length; i++) {
            sum+=nums[i];
        }
    }
    // 得到当前数字的和
    public long getSum() {
        return sum;
    }
}
public static void main(String[] args) {
    Child child = new Child();
    child.addAll(new int[]{1,2,3});
    System.out.println(child.getSum());
}

输出结果

12

其实期望得到的结果应该是6。但是由于子类的addAll方法调用了父类的addAll,而父类的addAll方法是通过add来添加的,由于动态绑定,子类的add方法被执行了。所以每个数字被统计了两次。

可以看出,如果子类不知道父类方法的实现细节,它就不能正确地进行扩展了。
所以仅仅知道父类能做什么是不够的,还需要知道父类是怎么做的,而父类的实现细节也不能随意修改,否则可能影响子类。

当然,对于这个示例,可以通过编码的方法来避免,但是这只是一个举例,说明继承可能会破坏封装。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值