Java 如何实践面向对象?这些你每天都在用吗

292 阅读6分钟

1.封装

1.1访问权限修饰符

  • public: 修饰的类、方法、变量,在当前包和其他包都有访问权限;
  • protected: 应用于继承的封装限制,允许子类和同一个包的类访问,禁止跨包非子类的访问;
  • default: 不加任何访问修饰符,允许当前包的类访问,禁止跨包访问,无论是不是子类;
  • private: 限制最严格,仅允许当前类访问,当前包的其他类和跨包都禁止访问;

Java 定义了 package 包,用来区分类名的命名空间。
包采用了树形目录的存储方式,每个包是一个命名空间,不允许命名重复,不同包中类名可以重复。
例如 package org.apache.commons.lang;,可以理解为一个目录,下面有很多 .java 文件,每个 java 文件都有一个同名的类

- org
  - apache
    - commons
      - lang
        ArrayUtils.java  // 在其中的 public class ArrayUtils 中是当前类
        BooleanUtils.java // public class BooleanUtils 相对于 ArrayUtils 是同一个包的类
        MyArrayUtils.java // 自定义继承 ArrayUtils,是 ArrayUtils 的子类
      - collections
        ArrayStack.java // public class ArrayStack 是相对于 ArrayUtils 不同包的非子类
        Buffer.class  
        MyArrayUtils.java // 自定义继承 ArrayUtils,是 ArrayUtils 的子类
访问权限修饰符同一个类同一个包的类子类不同包的非子类修饰对象
public类,方法,变量
protected×方法,变量
不写(缺省)××类,方法,变量
private×××方法,变量

public class SomeClass {
   public int i;  // 可以被所有类访问
   protected boolean b;  // 可以被所有子类和本包的类访问
   void print() {}  // 可以被本包的类访问
   private class InnerClass {}  // 只能被本类访问
}

抽象方法不能用 private 访问权限修饰符,因为抽象方法需要继承的子类实现,不能定义为私有的。

变量一般不定义为 public,这样失去了封装的控制,一般定义为 private,通过 publicgettersetter 对外暴露。如果是内部类的变量,可以直接暴露修改。

main() 方法必须定义为 public,否则,Java 解释器无法运行该类。

1.2其他修饰符

  • static: 修饰方法、变量和代码块
    表达被修饰的方法和变量是否需要实例化使用,有 static 修饰时可以不实例化,直接使用 类名.方法/变量 来表达,只有一份。
    静态变量存储于类中,无论实例化多少对象,都只有一份静态变量;

    静态方法只能使用类的静态变量,禁止使用 this/super 关键字,因为不在实例化的对象中;
    静态代码块在 JVM 加载类时会执行,和静态方法的区别是项目启动即执行,而静态方法是启动时初始化,在被调用的时候被动执行。

  • final: 修饰类,方法和变量
    修饰的类不可继承,修饰的方法不可被子类重新定义(重写 override),但是可以被子类使用和重载,修饰的变量不可修改。
    经常与 static 连用,设置全局常量,final 修饰变量不可修改,static 修饰变量在实例化前引用。
    abstract 修饰符反相关,一个对象如果是 final 的就不是 abstract 的,反之。

// 修饰变量
public class Person {
  public static final String NAME = 'tom';
  public final String getName() { return "hello";}
}

// 修饰方法
public class Employee extends Person {
  @Override
  public final String getName() {}  // compilation error: overriden method is final

  public final String getName() {
    String s = super.getName();  // 子类可以使用父类的方法,并重载
    return s + 'sub';
  }
}

// 修饰类
final class User {}
class Teacher extends User {}  // compilation error: cannot inherit from final class
  • abstract: 修饰类和方法
    抽象类不可实例化,如果一个类包含抽象方法,则一定是抽象类,抽象类可以没有抽象方法。抽象方法的实现由子类完成,抽象方法不可被 static, final 修饰。

  • synchronized: 并发控制,修饰代码块和方法
    代码块可以指定锁住的对象,方法被锁住就是该方法。

  • volatile: 并发控制,修饰变量
    被修饰变量保证不同线程对这个变量操作的实时可见性,同时禁止指令重排。

  • transient: 修饰变量,不能修饰本地变量
    被修饰的变量所属类需要实现 Serializable 接口,用来标记不需要序列化的变量。
    注意,当 transientfinal 连用时,被修饰变量依然会被序列化,因为 final 将变量引用为常量表达式,此时忽略了 transient 关键字。

  • native: 修饰方法
    当 java 和其他语言协作时,使用 native 修饰,被修饰方法是原生函数实现,如果是 C/C++ 实现的则编译成了 DLL 被 java 调用。

2.继承

2.1抽象类

顾名思义,用于定义结构,抽象类不可以实例化,一般会包含抽象方法,但也可以写一般方法。
一般使用场景是被子类继承,实现所有抽象方法后,子类可以被实例化。

public abstract class AbstractClass {
  protected int x;

  public abstract int getX();

  public void someFunc(){}
}

public class AbstractExtend extends AbstractClass {
  @Override
  public int getX(){}
}

2.2接口

是抽象类的延伸,所有变量和方法必须是 public,变量都是 staticfinal 的。
因为只能继承一个类,所以抽象类使用上有局限,而一个类可以实现多个接口;但接口对字段的封装特性做了约束,有局限,抽象类可以定义多种访问权限。
一般需要使用多重继承的使用接口,需要在多个类中共享代码、控制访问权限使用抽象类。

接口具有特殊性,成员的访问权限编译器会自动补全,可以少定义,但不能定义错。
变量:public static final
抽象方法: public abstract
静态方法:public static
内部类和内部接口:public static

3.多态

3.1重写 Override

子类实现了父类声明上完全相同的一个方法,要求:

  • 子类方法的访问权限必须大于等于父类方法
  • 子类方法的返回类型必须是父类方法返回类型或者其子类型

这两个要求是为了实现里式替换原则 LSP,即在任何条件下可以使用父类的地方,可以用子类对象替换,保证原有承昫的逻辑行为和正确性。

3.2重载 Overload

同一个类中,方法重名时,如果参数类型、个数、顺序至少一个不同是重载。
注意,返回值类型不同,不是重载。

4.Object 通用方法

有一些功能是通用性的,Java 定义在了 Object 中。

public final native Class<?> getClass();

public native int hashCode();

public boolean equals(Object obj) {
    return (this == obj);
}

protected native Object clone() throws CloneNotSupportedException;

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

public final native void notify();

public final native void notifyAll();

public final native void wait(long timeout) throws InterruptedException;

protected void finalize() throws Throwable { }

5.总结

Java 在语言层面较好的支持了面向对象的编程范式。在封装中要注意访问权限,继承关系,可变性;继承中灵活使用抽象类和接口,以支持访问权限定义和多重继承;在多态中通过重写和重载复用代码,提高开发效率。