Java基础之面向对象

面向对象

三大基本特征

  • 封装

    将客观事物封装成类,并将自己的数据和方法进行隐藏,仅对可信的类开放

    简单的说,一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。

  • 继承

    使用现有的类的所有功能,并且不重新编写原来的类而对这些功能拓展

    通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。

继承与实现
  • 继承:抽象出一个功能重复较多的父类,让子类都继承这个方法
    不可多继承(避免菱形继承导致的方法调用有歧义)
    根本目的是复用,关键字extends
  • 实现:多个类处理的目标一样,但是方式不同,可以抽象出接口让实现类实现
    可以实现多个接口
    根本目的是标准,关键字implements
继承与组合
  • 继承:是一种is-a的关系,只有当子类真正是超类的子类型时,或者必须从子类向超类向上转型时才建议使用继承

  • 组合:是一种has-a的关系,更安全、简单、灵活

  • 多态

    所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。

    这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。
    最常见的多态就是将子类传入父类参数中,运行时调用父类方法时通过传入的子类决定具体的内部结构或行为。

    多态的分类

    • 特设多态:为个体的特定类型的任意集合定义一个共同的接口。
      可以理解成函数名相同但函数签名不同

    • 参数多态: 指定一个或多个类型不靠名字而是靠标识类型的抽象符号
      可以理解成泛型

    • 子类型:一些有共同超类的子类
      理解成子类向超类的转换

      Java中的多态
      Java中的多态就是同一操作对于不同对象有不同的解释,产生不同的结果,也就是运行期的多态(动态绑定),能满足以下三个条件:

      • 有类继承或接口实现
      • 子类要重写父类的方法
      • 父类的引用指向子类的对象

      静态多态:例如函数的多态,是编译期的多态

重载与重写
  • 重载:

    存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。

    应该注意的是,返回值不同,其它都相同不算是重载。

  • 重写:存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。

    为了满足里式替换原则,重写有以下三个限制:

    • 子类方法的访问权限必须大于等于父类方法;
    • 子类方法的返回类型必须是父类方法返回类型或为其子类型。
    • 子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型。

    使用 @Override 注解,可以让编译器帮忙检查是否满足上面的三个限制条件。

五大原则

  • 单一职责原则(Single-Responsibility Principle)

    核心思想:一个类,只做好一件事,只有一个引起它的变化

    单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。职责过多,可能引起它变化的原因就越多,这将导致职责依赖,相互之间就产生影响,从而大大损伤其内聚性和耦合度。通常意义下的单一职责,就是指只有一种单一功能,不要为类实现过多的功能点,以保证实体只有一个引起它变化的原因。 专注,是一个人优良的品质;同样的,单一也是一个类的优良设计。交杂不清的职责将使得代码看起来特别别扭牵一发而动全身,有失美感和必然导致丑陋的系统错误风险。

  • 开放封闭原则(Open-Closed Principle)

    核心思想:软件实体应该是可扩展的,但不可修改的,即对扩展开放,对修改封闭。

    1. 对扩展开放:有新的需求或者需求变化时,可以对现有代码扩展
    2. 对修改封闭:类一旦设计完成,就可以独立完成其工作,不需要尝试的修改

    实现开放封闭原则的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以修改就是封闭的;而通过面向对象的继承和多态机制,又可以实现对抽象类的继承,通过覆写其方法来改变固有行为,实现新的拓展方法,所以就是开放的。 “需求总是变化”没有不变的软件,所以就需要用封闭开放原则来封闭变化满足需求,同时还能保持软件内部的封装体系稳定,不被需求的变化影响。

  • 里氏替代原则(Liskov-Substitution Principle)

    核心思想:子类必须能够替换其基类。

    在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将基类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换基类,但是基类不一定能替换子类。 里氏替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了Liskov替换原则,才能保证继承复用是可靠地。实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过Extract Abstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责。

里氏替换原则是关于继承机制的设计原则,违反了Liskov替换原则就必然导致违反开放封闭原则。

  • 依赖倒置原则(Dependency-Inversion Principle)

    核心思想:依赖于抽象,高层模块不依赖于底层模块,但二者都依赖于抽象;抽象不依赖于具体,具体依赖于抽象

    我们知道,依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标。 抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心。
    依赖于抽象是一个通用的原则,而某些时候依赖于细节则是在所难免的,必须权衡在抽象和具体之间的取舍,方法不是一成不变的。依赖于抽象,就是对接口编程,不要对实现编程。

  • 接口隔离原则(Interface-Segregation Principle)

    核心思想:使用多个小的专门的接口,而不是使用大的总接口
    具体而言,接口隔离原则体现在:接口应该是内聚的,应该避免“胖”接口。一个类对另外一个类的依赖应该建立在最小的接口上,不要强迫依赖不用的方法,这是一种接口污染。

    接口有效地将细节和抽象隔离,体现了对抽象编程的一切好处,接口隔离强调接口的单一性。而胖接口存在明显的弊端,会导致实现的类型必须完全实现接口的所有方法、属性等;而某些时候,实现类型并非需要所有的接口定义,在设计上这是“浪费”,而且在实施上这会带来潜在的问题,对胖接口的修改将导致一连串的客户端程序需要修改,有时候这是一种灾难。在这种情况下,将胖接口分解为多个特点的定制化方法,使得客户端仅仅依赖于它们的实际调用的方法,从而解除了客户端不会依赖于它们不用的方法。 分离的手段主要有以下两种:
    1、委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销。
    2、多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。

Object通用方法

所有的Java对象都继承Object对象,需要实现通用方法方法

public boolean equals(Object obj)

表示等价关系,两个对象具有等价关系需要满足以下五个条件:

  1. 自反性

    x.equals(x);//true
    
  2. 对称性:

    x.equals(y) == y.equals(x);//true
    
  3. 传递性

    if (x.equals(y) && y.equals(z))
        x.equals(z); // true
    
  4. 一致性

    x.equals(y) == x.equals(y);//多次调用结果不变
    
  5. 与null比较

    x.equals(null); //false,任何非null对象与null都是false
    

等价与相等

  • 对于基本类型,==判断两个值是否相等,基本类型没有equals()方法
  • 对于引用类型,==判断两个值是否引用同一个对象,而equals()判断引用对象是否等价

实现

  • 检查是否为同一个对象的引用,如果是直接返回 true;
  • 检查是否是同一个类型,如果不是,直接返回 false;
  • 将 Object 对象进行转型;
  • 判断每个关键域是否相等。
public class EqualExample {

    private int x;
    private int y;
    private int z;

    public EqualExample(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        EqualExample that = (EqualExample) o;

        if (x != that.x) return false;
        if (y != that.y) return false;
        return z == that.z;
    }
}
public native int hashCode()

ps: native关键字表示方法的实现由非本地Java代码提供。

hashCode()返回哈希值,而equals()用来判断两个对象是否相互等价。等价的两个对象hash值一定相等,但hash值相等的对象不一定等价,hash值计算有随机性,两个。

在覆盖equals()方法时也总是应该覆盖hashCode()方法,保证等价的两个对象散列值一定相同。HashSet和HashMap等集合类使用hashCode()方法来计算对象应该存储的位置,因此要将对象添加到这些集合类中需要实现对应的hashCode()

理想的哈希函数应该具有均匀性,即不相等的对象应该均匀分布到所有可能的哈希值。这就要求将所有域的值都考虑进来,可以把每个域都当成R进制的某一位,然后组成R进制整数。

R一般取31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失。且一个数和31相乘可以转换成移位和减法

示例

@Override
public int hashCode() {
    int result = 17;
    result = 31 * result + x;
    result = 31 * result + y;
    result = 31 * result + z;
    return result;
}
public String toString()

默认返回 类名@xxxx的形式,其中@后面的数组为哈希值的无符号十六进制表示。

可以覆写此方法实现格式化的输出

protected native Object clone() throws CloneNotSupportedException

protected方法,一个类不显式的重写clone()方法,其他类就不能直接调用该实例的clone()方法。同时如果要实现clone方法需要实现Cloneable接口。

浅拷贝
拷贝对象和原始对象引用同一个对象
一般采用的克隆方式是Object默认的

return (类名)super.clone()

Object中clone()负责建立正确的存储容量,并通过按位复制将所有二进制从原始对象复制到新对象的存储空间。

虽然复制对象和原有对象的引用不同,但是如果对象中有引用类型的字段,由于是浅拷贝,所以会共用一个对象

深拷贝
在重写clone方法时需要新建对象来复制值。

clone()的替代方案
使用clone()方法来拷贝对象即复杂又有风险,并且需要类型转换,所以可以使用拷贝构造函数或拷贝工厂来拷贝对象

成员变量与方法作用域

  • public:表明成员变量或方法对所有的类或对象都是可见的,所有类或对象都可以直接访问

  • private:表明该成员变量或者方法是私有的,只有当前类有访问权限,子类也没有访问权限

  • protected:仅对类自身、在同一个包下的其他类、其子类可见

  • default: 只有本身和同一个包内的其他类可见

抽象类与接口

抽象类

抽象类和抽象方法都需要使用abstract关键字进行声明,如果一个类中包含抽象方法,那么这个类必须声明为抽象类。抽象类和普通类最大的区别是,抽象类不能被实例化,只能被继承。

接口

接口是抽象类的延伸,在Java8之前可以看成一个完全抽象的类,不具有任何方法实现,从Java 8开始接口也可以有默认的方法实现,因为不支持默认的方法的接口的维护成本太高了.
接口的成员(字段+方法)都是默认public的,并且不允许定义为private或者protected,从Java 9开始允许把方法定义成private的,这样就能定义某些复用的代码且不会把方法暴露,接口的字段都是默认static和final的

二者的比较

从设计层面上看,抽象类提供了一种 IS-A 关系,需要满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。
从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。

使用选择
  • 使用接口:

需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Comparable 接口中的 compareTo() 方法;
需要使用多重继承。

  • 使用抽象类:

需要在几个相关的类中共享代码。
需要能控制继承来的成员的访问权限,而不是都为 public。
需要继承非静态和非常量字段。

super

  • 访问父类的构造函数:可以使用super()函数访问父类的构造函数,从而委托父类完成一些初始化的工作。应该注意到,子类一定会调用父类的构造函数来完成初始化工作,一般是调用父类的默认构造函数,如果需要调用其他的构造函数则可以使用super()

  • 访问父类的成员:如果子类覆写了父类的某个方法,就可以使用super.方法名()来调用父类的方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值