java面向对象基础概念

类与实例

要理解面向对象的概念,首先我们要搞清楚类是什么?对象是什么?
对象就是一个看得到摸得着闻得到的一个具体的概念。
准确的说,对象自包含的实体,用一组可识别的特征和行为来标识。

那类呢?类就属于一个抽象的概念了,是具有相同的属性和功能的对象的抽象的集合。

具体到代码中,class标记着一个类,而用new出来的,就是一个真实的对象,也就是实例。

如下代码示例:

public class Cat{
  public void Shout(){
    System.out.println("喵");
  }
}

Cat cat = new Cat();

其中Cat就是一个类, cat是用类Cat的一个对象,也就是一个真实的对象实例。

构造方法

构造方法,又叫构造函数,其实就是对类进行初始化,
构造方法无返回值,也不需要void,在new对象的时候调用

如下代码示例:

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

  public void Shout(){
    System.out.println("喵");
  }

  Cat cat = new Cat("小猫1");
}

类Cat中的方法public Cat(String name)就是一个构造函数。
这样我们在新建一个对象的时候,就可以给Cat对象传递一个名字。

重载

方法重载,提供了创建多个同名方法的能力,但是这些同名方法需要使用不同的参数来进行区分。

如下代码示例:

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

  public Cat(){
    this.name = "无名";
  }

  public void Shout(){
    System.out.println("喵");
  }

  Cat cat = new Cat("小猫1");
}

我们可以看到在类Cat中,有两个构造方法Cat(),只不过两个方法参数不同。
ps: 重载不一定非要构造方法,其他函数也可以重载。

面向对象四大特征之封装(隐藏属性)

封装也叫数据隐藏或数据访问保护。

类通过public、protect、private等关键字,暴漏出有限的方法,授权外部仅能通过类提供的方法来访问内部数据。

我们可以分析一段示例代码来看下封装的特性。

public class Account {
    //账户唯一ID
    private String id;
    //创建时间
    private long createTime;
    //账户余额
    private BigDecimal balance;
    //账户最后变动日期
    private long balanceLastModifiedTime;

    public Account() {
        this.id = UUID.randomUUID().toString();
        this.createTime = System.currentTimeMillis();
        this.balance = BigDecimal.ZERO;
        this.balanceLastModifiedTime = System.currentTimeMillis();
    }

    public String getId() { return this.id; }
    public long getCreateTime() { return this.createTime; }
    public BigDecimal getBalance() { return this.balance; }
    public long getBalanceLastModifiedTime() { return this.balanceLastModifiedTime;  }

    public void increaseBalance(BigDecimal increasedAmount) throws Exception{
        this.balance.add(increasedAmount);
        this.balanceLastModifiedTime = System.currentTimeMillis();
    }

    public void decreaseBalance(BigDecimal decreasedAmount) throws Exception{
        this.balance.subtract(decreasedAmount);
        this.balanceLastModifiedTime = System.currentTimeMillis();
    }
}

从代码实现上,我们可以看到代码包含四个属性。

我们参照封装特性,对账户这四个属性的访问方式进行了限制。调用者只允许通过暴漏的六个方法来访问或者修改账户里的数据。

之所以这样设计,是因为从业务的角度来说,id、createTime 在创建账户的时候就确定好了,之后不应该再被改动。

对于账户余额 balance这个属性,从业务的角度来说,只能增或者减,不会被重新设置。所以,我们在Account类中,只暴露了increaseBalance()和decreaseBalance()方法,并没有暴露set方法。

例子中的 private、public等关键字就是Java语言中的访问权限控制语法。private关键字修饰的属性只能该类本身访问,可以保护其不被类之外的代码直接访问。

封装的意义

如果我们对类中属性的访问不做限制,那任何代码都可以访问、修改类中的属性,虽然这样看起来更加灵活,但从另一方面来说,过度灵活也意味着不可控,属性可以随意被以各种奇葩的方式修改,而且修改逻辑可能散落在代码中的各个角落,势必影响代码的可读性、可维护性。

除此之外,类仅仅通过有限的方法暴露必要的操作,也能提高类的易用性。如果我们把类属性都暴露给类的调用者,调用者想要正确地操作这些属性,就势必要对业务细节有足够的了解。而这对于调用者来说也是一种负担。相反,如果我们将属性封装起来,暴露少许的几个必要的方法给调用者使用,调用者就不需要了解太多背后的业务细节,用错的概率就减少很多。

面向对象四大特征之抽象(隐藏方法)

封装主要讲的是如何隐藏信息、保护数据,而抽象讲的是如何隐藏方法的具体实现,让调用者只需要关心方法提供了哪些功能,并不需要知道这些功能是如何实现的。

常借助编程语言提供的接口类(比如 Java 中的 interface关键字语法)或者抽象类(比如 Java 中的 abstract关键字语法)这两种语法机制,来实现抽象这一特性。

示例代码:

public interface IPictureStorage {
  void savePicture(Picture picture);
  Image getPicture(String pictureId);
  void deletePicture(String pictureId);
  void modifyMetaInfo(String pictureId, PictureMetaInfo metaInfo);
}

public class PictureStorage implements IPictureStorage {
  // ...省略其他属性...
  @Override
  public void savePicture(Picture picture) { ... }
  @Override
  public Image getPicture(String pictureId) { ... }
  @Override
  public void deletePicture(String pictureId) { ... }
  @Override
  public void modifyMetaInfo(String pictureId, PictureMetaInfo metaInfo) { ... }
}

在上面的这段代码中,我们利用Java中的interface接口语法来实现抽象特性。调用者在使用图片存储功能的时候,只需要了解IPictureStorage这个接口类暴露了哪些方法就可以了,不需要去查看PictureStorage类里的具体实现逻辑。

抽象的意义

抽象作为一种只关注功能点不关注实现的设计思路,正好帮我们的大脑过滤掉许多非必要的信息。

换一个角度来考虑,我们在定义(或者叫命名)类的方法的时候,也要有抽象思维,不要在方法定义中,暴露太多的实现细节,以保证在某个时间点需要改变方法的实现逻辑的时候,不用去修改其定义。

面向对象四大特征之继承

继承是用来表示类之间的is-a关系,比如猫是一种哺乳动物。从继承关系上来讲,继承可以分为两种模式,单继承和多继承。单继承表示一个子类只继承一个父类,多继承表示一个子类可以继承多个父类,比如猫既是哺乳动物,又是爬行动物。

为了实现继承这个特性,编程语言需要提供特殊的语法机制来支持,比如 Java 使用 extends 关键字来实现继承

继承的意义

继承最大的一个好处就是代码复用。假如两个类有一些相同的属性和方法,我们就可以将这些相同的部分,抽取到父类中,让两个子类继承父类。这样,两个子类就可以重用父类中的代码,避免代码重复写多遍。

不过,过度使用继承,继承层次过深过复杂,就会导致代码可读性、可维护性变差。为了了解一个类的功能,我们不仅需要查看这个类的代码,还需要按照继承关系一层一层地往上查看“父类、父类的父类……”的代码。

所以,继承这个特性也是一个非常有争议的特性。很多人觉得继承是一种反模式。我们应该尽量少用,甚至不用。

面向对象四大特征之多态

多态是指,子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。对于多态这种特性,纯文字解释不好理解,我们还是看一个具体的例子。

示例代码:

public class DynamicArray {
  private static final int DEFAULT_CAPACITY = 10;
  protected int size = 0;
  protected int capacity = DEFAULT_CAPACITY;
  protected Integer[] elements = new Integer[DEFAULT_CAPACITY];
  
  public int size() { return this.size; }
  public Integer get(int index) { return elements[index];}
  //...省略n多方法...
  
  public void add(Integer e) {
    ensureCapacity();
    elements[size++] = e;
  }
  
  protected void ensureCapacity() {
    //...如果数组满了就扩容...代码省略...
  }
}

public class SortedDynamicArray extends DynamicArray {
  @Override
  public void add(Integer e) {
    ensureCapacity();
    int i;
    for (i = size-1; i>=0; --i) { //保证数组中的数据有序
      if (elements[i] > e) {
        elements[i+1] = elements[i];
      } else {
        break;
      }
    }
    elements[i+1] = e;
    ++size;
  }
}

public class Example {
  public static void test(DynamicArray dynamicArray) {
    dynamicArray.add(5);
    dynamicArray.add(1);
    dynamicArray.add(3);
    for (int i = 0; i < dynamicArray.size(); ++i) {
      System.out.println(dynamicArray.get(i));
    }
  }
  
  public static void main(String args[]) {
    DynamicArray dynamicArray = new SortedDynamicArray();
    test(dynamicArray); // 打印结果:1、3、5
  }
}

在上面的例子中,我们用到了三个语法机制来实现多态。

  • 第一个语法机制是编程语言要支持父类对象可以引用子类对象,也就是可以将 SortedDynamicArray 传递给 DynamicArray。
  • 第二个语法机制是编程语言要支持继承,也就是 SortedDynamicArray 继承了 DynamicArray,才能将 SortedDyamicArray 传递给 DynamicArray。
  • 第三个语法机制是编程语言要支持子类可以重写(override)父类中的方法,也就是 SortedDyamicArray 重写了 DynamicArray 中的 add() 方法。

通过这三种语法机制配合在一起,我们就实现了在test()方法中,子类SortedDyamicArray 替换父类 DynamicArray,执行子类 SortedDyamicArray 的 add() 方法,也就是实现了多态特性。

对于多态特性的实现方式,除了利用“继承加方法重写”这种实现方式之外,我们还有利用接口类语法去实现。

接下来,我们先来看如何利用接口类来实现多态特性。

我们还是先来看一段代码。

public interface Iterator {
  boolean hasNext();
  String next();
  String remove();
}

public class Array implements Iterator {
  private String[] data;
  
  public boolean hasNext() { ... }
  public String next() { ... }
  public String remove() { ... }
  //...省略其他方法...
}

public class LinkedList implements Iterator {
  private LinkedListNode head;
  
  public boolean hasNext() { ... }
  public String next() { ... }
  public String remove() { ... }
  //...省略其他方法... 
}

public class Demo {
  private static void print(Iterator iterator) {
    while (iterator.hasNext()) {
      System.out.println(iterator.next());
    }
  }
  
  public static void main(String[] args) {
    Iterator arrayIterator = new Array();
    print(arrayIterator);
    
    Iterator linkedListIterator = new LinkedList();
    print(linkedListIterator);
  }
}

在这段代码中,Iterator是一个接口类,定义了一个可以遍历集合数据的迭代器。Array 和 LinkedList都实现了接口类Iterator。我们通过传递不同类型的实现类(Array、LinkedList)到 print(Iterator iterator) 函数中,支持动态的调用不同的 next()、hasNext() 实现。

具体点讲就是,当我们往 print(Iterator iterator) 函数传递 Array 类型的对象的时候,print(Iterator iterator) 函数就会调用 Array 的 next()、hasNext() 的实现逻辑;当我们往 print(Iterator iterator) 函数传递 LinkedList 类型的对象的时候,print(Iterator iterator) 函数就会调用 LinkedList 的 next()、hasNext() 的实现逻辑。

多态的意义

多态特性能提高代码的可扩展性和复用性。为什么这么说呢?我们回过头去看讲解多态特性的时候,举的第二个代码实例(Iterator 的例子)。

在那个例子中,我们利用多态的特性,仅用一个 print() 函数就可以实现遍历打印不同类型(Array、LinkedList)集合的数据。当再增加一种要遍历打印的类型的时候,比如 HashMap,我们只需让 HashMap 实现 Iterator 接口,重新实现自己的 hasNext()、next() 等方法就可以了,完全不需要改动 print() 函数的代码。所以说,多态提高了代码的可扩展性。

如果我们不使用多态特性,我们就无法将不同的集合类型(Array、LinkedList)传递给相同的函数(print(Iterator iterator) 函数)。我们需要针对每种要遍历打印的集合,分别实现不同的 print() 函数,比如针对 Array,我们要实现 print(Array array) 函数,针对 LinkedList,我们要实现 print(LinkedList linkedList) 函数。而利用多态特性,我们只需要实现一个 print() 函数的打印逻辑,就能应对各种集合数据的打印操作,这显然提高了代码的复用性。

除此之外,多态也是很多设计模式、设计原则、编程技巧的代码实现基础,比如策略模式、基于接口而非实现编程、依赖倒置原则、里式替换原则、利用多态去掉冗长的 if-else 语句等等。

面向对象四大特征总结

  • 封装:隐藏属性
  • 抽象:隐藏方法具体实现
  • 继承:支持代码复用,但要避免过度使用
  • 多态:支持代码扩展

联系作者

微信公众号

xiaomingxiaola
(BossLiu)

QQ群

58726094


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 384276224@qq.com

×

喜欢就点赞,疼爱就打赏

日记本 网文世界