解析Java中的封装,继承和多态中的设计思想

了解Java的人都知道Java的三大特性封装继承和多态,闲来无事,今天来剖析下这三种特性的本质和设计思想。

封装

封装的本质是具体实现的隐藏,保证类内部的稳定和迭代,其结果通常返回一个带有特征和行为的数据类型。
在一个编译单元中,定义的变量的作用域有public,private,protected或默认作用域(包作用域)。
当我们作为服务提供方时,不仅要考虑代码的稳定运行,还需要考虑对外暴露哪些方法和属性,这些设计影响到以后的版本更新,因为你不知道服务消费方会调用你的哪些方法,因此,需要将自己绝大多数的内部实现进行隐藏,对外只暴露一个公共的接口才是安全可靠的。
例如ArrayList的add()方法:

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

其中的ensureCapacityInternal主要是ArrayList内部用来判断所属容器大小的,设计为private一方面可以阻止来自外部的调用,另一方面也可以在后续版本进行优化或改造。

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

继承

Java中的一切元素都可以看做类和对象来对待,对继承来说,父类是一个大而抽象的概念,子类则是对父类的具体细节实施。

  1. 不要为了继承而继承
    例如Java流家族中InputStream,使用FileInputStream来实现的目的是为了以文件流的方式来处理相应的业务逻辑,使用ByteArrayInputStream来实现的目的是为了更快的处理字节数据等等。若单个类即可满足基本的业务逻辑和功能,则不需要为了继承再单独声明继承。

  2. 继承和组合:
    组合是在一个类中包含另外的已有的类,来组合协调实现功能,这点有些类似于代理,相比继承,组合更加简单灵活,比如ArrayList:的内部是一个Object类型的数组,每次add的时候都是数组数值+1。
    组合不会依赖继承层次结构,而且组合更加灵活,因为可以动态地选择类型,而继承要求必须在编译时知道确切类型

  3. Object的继承家族
    在Java设计之初,开发者默认所有类的父类都是Object,这种单根性的设计思想维护了类的稳定和延续,也方便了第三方类库的加载,即所有类都可以转换成Object类(子类的向上转型),虽然子类中可能含有父类中没有的属性和方法,但对编译器来说,顶多是无法识别一些多余的数据,它认为这是安全可靠的(这也是泛型最初的设计思想),但父类的向下转型却无法直接进行。
    其实这种模式也很好理解,例如:

public class Main {
    public static void main(String[] args) {
     Cat cat=new MyCat();
    }
}
class Cat{
    public String name;
}
class MyCat extends Cat{
    
}

其本质是栈中Cat的引用被指向为MyCat所声明的实例,即MyCat可以当做Cat类型来处理,类型还是Cat的类型,此时二者内存中的地址是一样的:

Cat和MyCat内存地址是一样的因此也可以使用这种形式进行函数调用

public class Main {
    public static void main(String[] args) {
        MyCat myCat = new MyCat();
        new f().f(myCat)//此时入参被认为是一个Cat类型;
    }
}
class Cat{
    public String name;
}
class MyCat extends Cat{
}
class f{
 String f(Cat cat){
     return cat.name;
 }
}

实现了类的复用和解耦(详见多态)

  1. 子类的方法重写
    子类继承父类后,可以实现父类所有的public方法,这也是开发者需要特别注意的,举例如下:
class Cat{
   private void f(){
       System.out.println("private");
   }
    public static void main(String[] args) {
        Cat cat=new MyCat();
        cat.f();
    }
}
class MyCat extends Cat{
    public void f(){
        System.out.println("public");
    }
}
//输出private

本来是将子类实现赋值给父类引用,但为何调用的还是父类的方法?其实是因为重写只能作用于父类的public方法,若父类为private则无法重写,实际调用的还是父类方法,这也是实际开发中需要避免的问题。

5.继承的初始化步骤
调用子类方法后,编译器会查找该类是否存在继承关系,若有父类,则从最初的父类开始,依次调用构造方法进行实例化,若有static静态域,则先初始化static,其步骤如下:

初始化执行顺序
执行父类静态代码
执行子类静态代码
初始化父类构造函数
初始化子类构造函数

多态

多态实现的目的是为了类之间的解耦,即同一接口可调用不同的实现方式。

  1. 论向上转型
    Java初学者通常对
    接口=new 实现类 或 父类=new 子类的行为感到疑惑,尽管二者不是同一类型,但编译和运行却没有问题,这是因为Java使用了动态绑定的方式进行引用实现的调用,即在在运行时动态判断所调用的对象实例,从而调用恰当的方法,区别于静态绑定(通过static或者final的方式在加载时进行绑定)
    向上转型的类型问题:
    向上转型后,类的类型还是父类的类型,但引用所指的实例是子类的实例:
public class Main {
    public static void main(String[] args) {
        Cat cat=new MyCat();
        System.out.println(cat.name);
    }
}
class Cat{
   public String name="cat";
}
class MyCat extends Cat{
    public String name="mycat";
}
//输出 cat

通过debug发现,向上转型后引用中含有的属性如下:
在这里插入图片描述包含子类的name和一父类的name,在Cat类型进行调用时输出的则是cat的name ,虽然通常在开发中不会直接对外暴露属性,大可不用担心这种问题的发生;

2.向下转型
由于向上转型会丢失子类具体的类型信息,那么为了重新获取类型信息,就需要在继承层次中向下移动,也就是向下转型。
向上转型对编译器来说永远是安全的,因为父类不会具有比子类更多的接口。因此,每条发送给父类接口的消息都能被接收执行。
但是对于向下转型,你无法知道子类的具体类型,还面临因转换造成的ClassCastException问题,Java中无法直接将父类强转为子类,必须通过多态的形式向上转型后,才可以进行向下转型,例如:

public class Main{
    public static void main(String[] args) {
       Cat cat=new MyCat();
        ((MyCat) cat).t();
    }
}
class Cat{

}
class MyCat extends Cat{
    public String s="a";
    public void t(){
        System.out.println("mycat");
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值