链接:面向对象基础 - 廖雪峰的官方网站

构造方法

  • 构造方法也要加上修饰词 public
  • 如果我们自定义了一个构造方法,那么,编译器就不再自动创建默认构造方法
  • 没有在构造方法中初始化字段时,引用类型的字段默认是null,数值类型的字段用默认值,int类型默认值是0,布尔类型默认值是false

方法重载 Overload

  • 方法名相同,但各自的参数不同,称为方法重载Overload
  • 注意:方法重载的返回值类型通常都是相同的

继承 extends

  • Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有Object特殊,它没有父类。
  • 继承有个特点,就是子类无法访问父类的private字段或者private方法。为了让子类可以访问父类的字段,我们需要把private改为protected。用protected修饰的字段可以被子类访问。
  • super.xx 调用父类的字段和方法
  • super() 调用父类的构造方法
  • 子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。
  • 向上转型
    • 子类可以给父类赋值(向上转型),即将父类的指针指向子类的实例。因为父类所具有的字段和方法子类都有。 ```java Person p = new Student();

//or Student s = new Student(); Person p = s;

  1. - 向下转型
  2. - 但是父类不能给子类赋值(向下转型),即不能用子类的指针指向父类的实例,因为子类的很多字段和方法都是父类的实例不具备的。
  3. - 也有向下转型成功的例子
  4. ```java
  5. Person p1 = new Student(); // upcasting, ok
  6. Person p2 = new Person();
  7. Student s1 = (Student) p1; // ok 因为p1实际上本身就是指向学生实例,所以向下转型成功
  8. Student s2 = (Student) p2; // runtime error! ClassCastException! 转型失败
  • 要向下转型时,先用 instanceof 判断一个变量的实例实际上是否是指定类型。
    Person p = new Student();
    if (p instanceof Student) {
    // 只有判断成功才会向下转型:
    Student s = (Student) p; // 一定会成功
    }
    

多态 override

覆写 Override

  • 子类如果定义了一个与父类方法签名完全相同的方法,被称为覆写(Override)
  • 覆写方法的前面要加上@Override

    class Student extends Person {
      @Override
      public void run() {
          System.out.println("Student.run");
      }
    }
    
  • OverrideOverload 的区别

    • Overload:新方法,不同的方法
      • 方法签名不同(即参数不同)
      • 返回值不同(但是 Java 编译器会报错)
    • Override:同样的方法
      • 方法签名相同,并且返回值也相同
  • 覆写 Object 方法:因为所有的class最终都继承自Object, 因此可以覆写Object的几个重要方法

    • toString():把instance输出为String
    • equals():判断两个instance是否逻辑相等
    • hashCode():计算一个instance的哈希值

      class Person {
      ...
      // 显示更有意义的字符串:
      @Override
      public String toString() {
         return "Person:name=" + name;
      }
      
      // 比较是否相等:
      @Override
      public boolean equals(Object o) {
         // 当且仅当o为Person类型:
         if (o instanceof Person) {
             Person p = (Person) o;
             // 并且name字段相同时,返回true:
             return this.name.equals(p.name);
         }
         return false;
      }
      
      // 计算hash:
      @Override
      public int hashCode() {
         return this.name.hashCode();
      }
      }
      
  • 覆写时可通过 super() 调用父类被覆写的方法 ```java class Person { protected String name; public String hello() {

      return "Hello, " + name;
    

    } }

Student extends Person { @Override public String hello() { // 调用父类的hello()方法: return super.hello() + “!”; } }

<a name="k3Hqy"></a>
## final

- `final` 修饰的方法不能被覆写
```java
class Person {
    protected String name;
    public final String hello() {    //final 修饰的方法不能被覆写
        return "Hello, " + name;
    }
}

Student extends Person {
    // compile error: 不允许覆写
    @Override
    public String hello() {
    }
}
  • final修饰的类不能被继承 ```java final class Person { protected String name; }

// compile error: 不允许继承自Person Student extends Person { }


- `final` 修饰的字段是常量,必须在创建对象时初始化,而不能被重新赋值(如果是类中的字段,可在构造函数中初始化赋值该字段)

<a name="VKzHR"></a>
# 抽象类 abstract

- 抽象类不能被实例化
- **抽象类**本身被设计成只用于继承,强迫子类实现其定义的抽象方法。父类的抽象方法只需定义,无需实现,相当于**只给子类定义了规范**,目的是让子类去覆写它
- 抽象类 和 抽象方法都要用 **abstract** 修饰
```java
public class Main {
    public static void main(String[] args) {
        Person p = new Student();    //尽量引用高层类型,避免引用实际子类型 —— 面向抽象编程
        p.run();
    }
}

abstract class Person {                //抽象类要使用 abstract 修饰
    public abstract void run();        //抽象方法要使用 abstract 修饰
}

class Student extends Person {
    @Override
    public void run() {                //子类覆写父类的抽象方法
        System.out.println("Student.run");
    }
}
  • 尽量引用高层类型,通过高层抽象类去引用具体子类的实例,避免引用实际子类型 —— 面向抽象编程
  • 抽象类规定高层类的接口,从而保证所有子类都有相同的接口实现——从而发挥多态的威力

    Person s = new Student();
    Person t = new Teacher();
    Person e = new Employee();
    // 不关心Person变量的具体子类型,不关心新的子类是如何实现run()方法的
    s.run();
    t.run();
    e.run();
    
  • 如果子类继承了抽象类,没有实现抽象方法,则该子类仍是一个抽象类

接口 interface

  • 如果一个抽象类没有字段所有方法全部都是抽象方法,可以把该抽象类改写为接口:interface ```java interface Person { void run(); String getName(); }

//相当于: abstract class Person { public abstract void run(); public abstract String getName(); }


- 子类继承实现接口,使用修饰符 `implements` ,而不是使用 `extends`
```java
class Student implements Person {    //继承接口使用 implements
    private String name;

    public Student(String name) {
        this.name = name;
    }

    @Override    //覆写接口的方法,可以省略 @Override
    public void run() {
        System.out.println(this.name + " run");
    }

    @Override
    public String getName() {
        return this.name;
    }
}
  • 一个类只能继承一个父类,但是可以继承实现多个interface

    class Student implements Person, Hello { // 实现了两个interface
      ...
    }
    
  • 接口可以继承另一个接口,但是通过 extends 继承。这相当于扩展了接口的方法 ```java interface Hello { void hello(); }

interface Person extends Hello { //接口通过 extends 继承另一个接口 void run(); String getName(); }


- 在接口中,可以定义`default`方法。例如,把`Person`接口的`run()`方法改为`default`方法。实现类可以不必覆写default方法。
```java
interface Person {
    String getName();
    default void run() {
        System.out.println(getName() + " run");
    }
}

静态字段 static

  • static修饰的字段,称为静态字段
  • 静态字段只有一个共享“空间”,所有实例都会共享该字段。因此,对于静态字段,无论修改哪个实例的静态字段,效果都是一样的,所有实例的静态字段都被修改了
  • 因此,推荐用类名来访问静态字段,而不是用实例(因为静态字段实际上并不属于实例) ```java public class Main { public static void main(String[] args) {
      Person ming = new Person("Xiao Ming", 12);
      Person hong = new Person("Xiao Hong", 15);
      Person.number = 88;    //推荐用类名来访问静态字段
      System.out.println(Person.number);
    
    } }

class Person { public String name; public int age;

public static int number;

public Person(String name, int age) {
    this.name = name;
    this.age = age;
}

}

<a name="lEjRH"></a>
## 静态方法

- 用`static`修饰的方法称为静态方法
- 调用静态方法则不需要实例变量,通过类名就可以调用
```java
public class Main {
    public static void main(String[] args) {
        Person.setNumber(99);    //通过类名就可以调用静态方法
        System.out.println(Person.number);
    }
}

class Person {
    public static int number;

    public static void setNumber(int value) {
        number = value;
    }
}
  • 静态方法属于class而不属于实例,因此,静态方法内部,无法访问this变量,也无法访问实例字段,它只能访问静态字段
  • 接口(interface)不能定义实例字段,但可以有可以有 final 类型的静态字段 ```java public interface Person { public static final int MALE = 1; public static final int FEMALE = 2; }

//或者简写为: public interface Person { // 编译器会自动加上public statc final,因为interface的字段只能是public static final类型 int MALE = 1; int FEMALE = 2; }


<a name="C7qsE"></a>
# 包 package

- 实例:eg. "`Person.java`"文件
```java
package ming; // 申明包名ming

public class Person {
}

就可以通过 ming.Person 访问 Person

  • 没有定义包名的class,它使用的是默认包,非常容易引起名字冲突,因此,不推荐不写包名的做法。
  • 不用publicpreotectedprivate修饰的字段和方法就是包作用域的,位于同一个包的类可以访问包作用域的字段和方法
  • 包的引用 ```java mr.jun.Arrays arrays = new mr.jun.Arrays(); //使用完整类名引用mr.jun.Arrays类

//或 // 导入mr.jun包的所有class: import mr.jun.*; //or: //import mr.jun.Arrays; Arrays arrays = new Arrays(); ```

作用域

  • public
    • 定义为publicclassinterface可以被其他任何类访问
    • 定义为publicfieldmethod可以被其他类访问,前提是首先有访问class的权限
    • 一个.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同
  • private
    • 定义为privatefieldmethod的访问权限被限定在类里,无法被其他类访问,除非是内部嵌套类
    • 因此一般把 public 的方法定义写在前面(对外提供的功能),方便阅读
  • protected
    • 用于继承,可以被子类(及子类的子类…)访问
  • 包作用域
    • 不用publicpreotectedprivate修饰的字段和方法就是包作用域的,位于同一个包的有访问权限
  • 局部变量
    • 尽可能把局部变量的作用域缩小,尽可能延后声明局部变量