Java 是面向对象的程序设计语言,支持面向对象的三大特征:封装、继承、多态。Java 提供 privateprotectedpublic三种访问控制修饰符来实现良好的封装,提供了 extends 关键字让子类继承父类,通过继承关系实现复用时,子类对象可以直接赋给父类变量,即这个变量具有多态性。

子类继承父类就可以继承到父类的成员变量和方法,如果访问控制允许,子类实例可以直接调用父类里定义的方法。 继承是实现类复用的重要手段

类和对象是面向对象的核心

  1. [修饰符] class 类名 {
  2. // 零个到多个构造器定义……
  3. // 零个到多个成员变量……
  4. // 零个到多个方法……
  5. }
  • 修饰符不是必填项。支持 publicfinalabstarct,或者不填
  • 构造器是一个类创建对象的根本途径。如果一个类没有构造器,系统会为该类提供一个默认的无参构造器,但若一个类有构造器,系统将不再为该类提供构造器
  • 一个类中各成员之间的定义顺序没有任何影响,各成员之间可以相互调用,但是 static 修饰的成员不能访问没有 static 修饰的成员
  • 成员变量(也称字段)一般指 field,它的语法格式:[修饰符] 类型 成员变量名 [= 默认值]

    • 修饰符不是必填项。支持 publicprotectedprivatestaticfinal。其中 publicprotectedprivate 三个最多只能定义一个,可以与 staticfinal 组合起来修饰成员变量
    • 类型支持基本类型和引用类型

      this 变量

      在非静态方法内部,可以使用一个隐含的变量 this,它始终指向当前实例。
  • 如果没有命名冲突,可以省略 this

    1. class Person {
    2. private String name;
    3. public String getName() {
    4. return name;
    5. }
    6. }
  • 如果有局部变量和成员变量重名,那么局部变量优先级更高,访问成员变量时必须加上 this

    1. class Person {
    2. private String name;
    3. public void setName(String name) {
    4. this.name = name;
    5. }
    6. }

    static 关键字

    static 是一个特殊的关键字,它可用于修饰方法、成员变量等成员。static 修饰的成员表明它属于这个类本身,而不属于这个类的实例,通过把 static 修饰的成员变量和方法称为静态变量、静态方法,并且静态成员不能直接访问非静态成员。
    Java 中属性 JavaBean 指的是一组 setter 方法和 getter 方法。比如某个类有 age 属性,则这个类具有 setAge()getAge() 两个方法

    静态字段和静态方法

  • 静态字段

class 中用定义的字段 field,称为实例字段,其中用 static 修饰的字段,称为静态字段。实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都共享该字段,也就是静态字段并不属于实例,它属于 class 本身的字段

  • 静态方法

static 修饰的方法称为静态方法。静态方法属于 class 而不属于实例,因此静态方法内部,无法访问 this 变量和实例字段,只能访问静态字段。一般静态方法多用于工具类中

方法参数

  • 方法可以包含 0 个或任意个参数。调用方法时,必须严格按照参数定义一一传递。
  • 方法也可以定义可变参数。写法是 类型… ,调用方法时可以传递多个相同类型的参数
  • 方法参数传递方式只有值传递
    • 基本类型参数传递,是调用方值的复制。双方各自的后续修改,互不影响

基本类型参数传递时,系统将调用方的变量值复制赋值给接收方,并为调用方和接收方分配两块不同的内存栈,双方各自在这块栈中保存局部变量。

  • 引用类型参数传递,调用方的变量和接收方的变量指向的是同一个对象。双方任意一方修改这个对象都会影响双方

系统创建一个对象时,对象本身保存在内存堆中,指向该对象的引用变量保存在内存栈中。引用类型参数传递时,系统将调用方的变量值复制赋值给接收方,但是由于该变量值只是一个引用变量(指向),所以系统只是复制了“引用”,并没有复制对象本身,这就导致当前存在两个引用指向同一个对象本身,那么双方任意一方通过引用指向修改对象本身时,其他一方都会受到影响

内部类

定义在 class 内部中的 class,称为内部类 Nested Class

  1. class Outer {
  2. class Inner {
  3. // 定义了一个Inner Class
  4. }
  5. }

Inner 是一个内部类,Inner Class 的实例不能单独存在,必须依附于一个 Outer Class 的实例

  1. Outer outer = new Outer();
  2. Outer.Inner inner = outer.new Inner();
  3. inner.hello();

重载

构造器重载

同一个类中存在多个构造器,多个构造器的参数列表不同,即被称为构造器重载。通常情况下,会在一个构造方法内部调用另一个构造方法,便于代码复用。

方法重载

同一个类中方法名相同,但参数列表不同,这种情况称为方法重载。通常情况下,方法重载的返回值类型都是相同的;方法名相同,参数列表相同,但方法返回值类型不同的方法不能作为方法重载。

封装

封装是面向对象编程语言对客观世界的模拟,将对象的状态信息隐藏在对象内部不允许外部直接访问,将方法暴露对外,调用者只能通过预定义的方法来操作成员变量

Java 引入包 package 机制,提供了类的多层命名空间,用于解决类的命名冲突、类文件管理等问题。包可以是多层结构,用 . 隔开(例如 java.util )。包没有父子关系,**java.util** **java.util.zip** 是不同的包,两者没有任何继承关系。

最佳实践

  • 为了避免命名冲突,使用域名倒写来作为包名
  • 约定 Java 文件对应的目录层次和包的层次一致

    访问修饰符

    访问控制级别从小到大

    private (当前类私有访问) > default (同一包中访问) > protected (子类可访问) > public (公开访问)

private default protected public
同一个类中 ✔️ ✔️ ✔️ ✔️
同一个包中 ✔️ ✔️ ✔️
子类中 ✔️ ✔️
全局范围内 ✔️

一个 .java 文件只能包含一个 public 类,但可以包含多个非 public 类。如果有 public 类,文件名必须和 public 类的名字相同。

继承

继承是实现代码复用的重要手段,Java 使用 extends 关键字来实现继承。在 OOP 术语中,Person 类称为 父类(super)Student 类称为 子类。子类继承父类除构造方法外的所有功能。java.lang.Object 类是所有类的直接或间接父类,并且 Java 的继承具有单继承特点,即每个子类都只有一个直接父类。

  1. class Person {
  2. private String name;
  3. private int age;
  4. public String getName() {...}
  5. public void setName(String name) {...}
  6. public int getAge() {...}
  7. public void setAge(int age) {...}
  8. public Person(String name,int age) {
  9. this.name = name;
  10. this.age = age;
  11. }
  12. }
  13. class Student extends Person {
  14. // 不要重复name和age字段/方法,
  15. // 只需要定义新增score字段/方法:
  16. private int score;
  17. public int getScore() { }
  18. public void setScore(int score) { }
  19. public Student(String name,int age,int score) {
  20. super(name,age);
  21. this.score = score;
  22. }
  23. }

super 关键字

  • 子类引用父类的字段时,可以使用 super.[fieldName] 语法
  • 子类必须使用 super() 调用父类构造函数并赋值相对应参数
    Override 注解
    大部分时候,子类总是以父类为基础,在基础上额外添加其他字段和方法,但有时子类需要重写父类的方法,Java 提供 Override 注解来实现重写 ```java public class Person { protected void check() {
    1. System.out.println("Person.Check");
    } }

public class Student { @Override protected void check() { super.check(); System.out.println(“Student.check”); } }

  1. <a name="zaGrc"></a>
  2. #### `final` 关键字
  3. - 用 `final` 修饰 `class` 可以阻止被继承
  4. - 用 `final` 修饰 `method` 可以阻止被子类重写
  5. - 用 `final` 修饰 `field` 可以阻止被重新赋值,未设置默认值时可以在构造器中赋值
  6. - 用 `final` 修饰局部变量可以阻止被重新赋值
  7. <a name="Ai3pV"></a>
  8. #### 组合
  9. 继承是 is 关系,组合是 has 关系。具有has关系不应该使用继承,而是使用组合
  10. ```java
  11. class Book {
  12. protected String name;
  13. public String getName() {...}
  14. public void setName(String name) {...}
  15. }
  16. class Student extends Person {
  17. protected Book book;
  18. protected int score;
  19. }

多态

Java 引用类型分为编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋值给该变量的对象决定。如果编译时类型和运行时类型不一致,这种情况就称之为多态,即运行期才能动态决定调用的具体类型。
多态是另一个实现代码复用的重要手段,它允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。

  1. public class Animal {
  2. public void sound() {
  3. System.out.println("Animal.Sound");
  4. }
  5. }
  6. public class Cat extends Animal {
  7. @Override
  8. public void sound() {
  9. System.out.println("Cat.sound");
  10. }
  11. }
  12. public class Dog extends Animal {
  13. @Override
  14. public void sound() {
  15. System.out.println("Dog.sound");
  16. }
  17. }
  18. public class Main {
  19. public static void main(String[] args) {
  20. Animal[] animals = new Animal[] {
  21. new Dog(),
  22. new Cat()
  23. };
  24. for (Animal animal : animals) {
  25. animal.sound();
  26. }
  27. // Dog.sound
  28. // Cat.sound
  29. }
  30. }

向上转型:将子类类型安全的变为父类类型的赋值

  1. Student s = new Student();
  2. Person p = s; // upcasting, ok
  3. Object o1 = p; // upcasting, ok
  4. Object o2 = s; // upcasting, ok

向下转型:与向上转型相反,将父类类型强制转型为子类类型。向下转型很可能会使用,Java 提供 instanceof 操作符号用于判断一个实例是否是某种类型

  1. Person p = new Person();
  2. System.out.println(p instanceof Person); // true
  3. System.out.println(p instanceof Student); // false
  4. Student s = new Student();
  5. System.out.println(s instanceof Person); // true
  6. System.out.println(s instanceof Student); // true
  7. Student n = null;
  8. System.out.println(n instanceof Student); // false

抽象类和接口

抽象 abstract 和 接口 intertace 都是从多个子类中抽象出来的公共特征。抽象类的本质是定义接口规范约束子类,而接口就是比抽象还抽象的纯抽象接口,它甚至都没有字段。
通常情况下,定义 abstract 抽象类时,类名添加 AbstractBase 前缀,定义 interface 接口时,类型添加 I 前缀

抽象类和接口两者比较


abstract class interface
继承 只能 extends 一个 class 可以 implements 多个 interface
字段 可以定义实例字段 不能定义实例字段
抽象方法 可以定义抽象方法 可以定义抽象方法
非抽象方法 可以定义非抽象方法 可以定义 default 方法

接口继承

一个 interface 可以继承自另一个 interfaceinterface 继承自 interface 使用 extends,它相当于扩展了接口的方法。例如:

  1. interface Hello {
  2. void hello();
  3. }
  4. interface Person extends Hello {
  5. void run();
  6. String getName();
  7. }

设计继承关系

设计结构时,abstractinterface 通常会组合使用,公共逻辑放在 abstract class 中,接口规范放在 interface 中,接口的层级关系代表逻辑抽象程度。

典型案例:Java 集合类定义的一组接口、抽象类和具体子类

  1. ┌───────────────┐
  2. Iterable
  3. └───────────────┘
  4. ┌───────────────────┐
  5. Object
  6. ┌───────────────┐ └───────────────────┘
  7. Collection
  8. └───────────────┘
  9. ┌───────────────────┐
  10. └──────────│AbstractCollection
  11. ┌───────────────┐ └───────────────────┘
  12. List
  13. └───────────────┘
  14. ┌───────────────────┐
  15. └──────────│ AbstractList
  16. └───────────────────┘
  17. ┌────────────┐ ┌────────────┐
  18. ArrayList LinkedList
  19. └────────────┘ └────────────┘
  1. List list = new ArrayList();
  2. COllection collection = list;
  3. Iterable it = collection;

面向接口编程

“面向接口”编程是软件架构设计核心理论,其根本目的是希望通过面向接口编程来降低程序的耦合

典型案例:简单工厂

  1. public interface IOutput {
  2. /**
  3. * 读取内容
  4. */
  5. void getData();
  6. /**
  7. * 打印内容
  8. */
  9. void out();
  10. }
  11. public class PrinterA implements IOutput {
  12. @Override
  13. public void getData() {
  14. System.out.println("PrinterA.getData");
  15. }
  16. @Override
  17. public void out() {
  18. System.out.println("PrinterA.print");
  19. }
  20. }
  21. public class OutputFactory {
  22. public static IOutput getOutput(){
  23. return new PrinterA();
  24. }
  25. }
  26. public class Computer {
  27. private IOutput output;
  28. public Computer(IOutput output){
  29. this.output = output;
  30. }
  31. public void keyIn(String message){
  32. output.getData();
  33. }
  34. public void print(){
  35. output.out();
  36. }
  37. }
  38. public class Main {
  39. public static void main(String[] args) {
  40. IOutput output = OutputFactory.getOutput();
  41. var computer = new Computer(output);
  42. computer.keyIn("hello world");
  43. computer.print();
  44. }
  45. }