Java 面向对象

前言

  • 面向对象的程序设计:程序设计是通过对象对程序进行设计,对象代表一个实体,实体可以清楚地被识别。

  • Java 作为一种面向对象语言,支持以下基本概念

    • 对象
    • 封装
    • 继承
    • 多态
    • 抽象
    • 实例
    • 方法
    • 消息解析
  • 面向对象的特征

    • 封装,最常见的是把属性私有化封装在一个类里面,只能通过方法去访问。
    • 继承,子类继承父类,从而继承了父类的方法和属性。
    • 多态,多态分操作符的多态和类的多态。类的多态指父类引用指向子类对象,并且有继承,有重写。
    • 抽象,比如一个类,抽象出了一些属性和方法,使得开发过程中更加易于理解。
  • Java 面向对象设计

  • Java 面向对象 快速入门

1、类

  • 类:类是一个模板,它描述一类对象的行为和状态。类可以看成是创建 Java 对象的模板。

  • 一个类可以拥有多个方法。

1.1 类变量

  • 一个类可以包含以下类型变量

    • 成员变量
    • 局部变量
    • 类变量(静态变量)
  • 成员变量

    • 成员变量是定义在类中,方法体之外的变量。成员变量可以被类中方法、构造方法和特定类的语句块访问。
    • 在创建实例时创建类的成员变量,并且当对象被销毁时被销毁。
    • 未显式分配值的所有成员变量在声明期间自动分配一个初始值。成员变量的初始化值取决于成员变量的类型。













































元素类型 初始值
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0d
char “\u0000”
boolean false
对象引用 null
  • 局部变量

    • 在方法、构造方法或者语句块中定义的变量被称为局部变量。
    • 变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
  • 类变量(静态变量)

    • 类变量也声明在类中,方法体之外,但必须声明为 static 类型
    • 静态变量在类加载时初始化。

1.2 Object 类

  • Object 类是所有类的父类。声明一个类的时候,默认是继承了 Object

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    package java.lang;

    /**
    * Class {@code Object} is the root of the class hierarchy.
    * Every class has {@code Object} as a superclass. All objects,
    * including arrays, implement the methods of this class.
    *
    * @author unascribed
    * @see java.lang.Class
    * @since JDK1.0
    */
    public class Object {

    private static native void registerNatives();
    static {
    registerNatives();
    }

    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;

    public final void wait(long timeout, int nanos) throws InterruptedException {
    if (timeout < 0) {
    throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
    throw new IllegalArgumentException(
    "nanosecond timeout value out of range");
    }

    if (nanos > 0) {
    timeout++;
    }

    wait(timeout);
    }

    public final void wait() throws InterruptedException {
    wait(0);
    }

    protected void finalize() throws Throwable { }
    }
    1
    2
    3
    4
    // 继承 Object
    public class Hello extends Object {

    }
  • Object 类提供了一些常用的方法

关键字 说明
toString 返回当前对象的字符串表达
finalize 垃圾回收的时候调用,是由虚拟机 JVM 调用的,一旦一个对象被回收,它的 finalize() 方法就会被调用
equals 判断两个对象的内容是否相同
等号 ==
这不是 Object 的方法,但是用于判断两个对象是否相同,更准确的讲,用于判断两个引用,是否指向了同一个对象
hashCode 返回一个对象的哈希值
getClass 返回一个对象的类对象
wait
notify
notifyAll
线程同步相关方法
  • final, finally, finalize 的区别

    • final 修饰类,方法,基本类型变量,引用的时候分别有不同的意思。
      • 修饰类 表示该类不能被继承。
      • 修饰方法 表示该方法不能被重写。
      • 修饰基本类型变量 表示该变量只能被赋值一次。
      • 修饰引用 表示该引用只有一次指向对象的机会。
    • finally 是用于异常处理的场面,无论是否有异常抛出,都会执行。
    • finalize 是 Object 的方法,所有类都继承了该方法。当一个对象满足垃圾回收的条件,并且被回收的时候,其 finalize() 方法就会被调用。
  • 垃圾回收

    • GC 是 Garbage Collection 的缩写,即垃圾回收。
    • 这里所谓的垃圾,指的是那些不再被使用的对象,JVM 的垃圾回收机制使得开发人员从无聊、容易犯错的手动释放内存资源的过程中解放出来。
    • 开发人员可以更加专注的进行业务功能的开发,而资源回收的工作交由更加专业的垃圾回收机制自动完成。

1.3 抽象类

  • 抽象类:在类中声明一个方法,这个方法没有实现体,是一个 “空” 方法,这样的方法就叫抽象方法,使用修饰符 abstract 修饰,当一个类有抽象方法的时候,该类必须被声明为抽象类

  • 在 Java 中,我们使用抽象类来定义抽象概念。抽象概念必须有一些抽象方面。例如,抽象概念是 Shape,而抽象方面是如何计算面积。抽象概念在 Java 中变成抽象类,抽象方面成为抽象方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 用 abstract 修饰抽象类,不能够直接被实例化,子类会被要求实现抽象方法
    public abstract class Hero {

    String name;
    float hp;
    float armor;
    int moveSpeed;

    public abstract void attack(); // 抽象方法 attack,Hero 的子类会被要求实现 attack 方法
    }
  • 抽象类可以没有抽象方法,一旦一个类被声明为抽象类,就不能够被直接实例化。

    1
    2
    3
    4
    5
    6
    7
    // 用 abstract 修饰抽象类,不能够直接被实例化
    public abstract class Hero {
    String name;
    float hp;
    float armor;
    int moveSpeed;
    }
  • abstract class(抽象类)和 interface(接口)的区别

    • 相同

      • 抽象类和接口都可以有实体方法。
    • 使用方式

      • 抽象类只能够通过继承被使用。子类只能继承一个抽象类,不能继承多个。
      • 接口必须通过实现被使用。子类可以实现多个接口。
    • 实现方法

      • 抽象类不仅可以提供抽象方法,也可以提供实现方法。
      • 接口只能提供抽象方法,不能提供实现方法。但是在 Java8 版本开始,接口可以提供实现方法了,前提是要在方法前加一个 default 修饰符。
    • 属性定义

      • 抽象类可以定义
        • public, protected, package, private
        • 静态和非静态属性
        • final 和非 final 属性
      • 但是接口中声明的属性,只能是以下类型,即便没有显式的声明
        • public
        • 静态
        • final 的
  • 抽象类是否可实现(implements)接口?

    • 可以,比如 MouseAdapter 鼠标监听适配器 是一个抽象类,并且实现了 MouseListener 接口。
  • 抽象类是否可继承实体类(concrete class)?

    • 可以,所有抽象类,都继承了 Object。

1.4 内部类(嵌套类)

  • 在任何类外部声明的类是顶级类。内部类(嵌套类)是声明为其他类或作用域的成员的类。

  • 内部类(嵌套类)分为四种

  • 非静态内部类

    • 可以直接在一个类里面定义,只有一个外部类对象存在的时候,才有意义。可以直接访问外部类的实例属性和方法。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      public class Hero {
      private String name; // 姓名
      float hp; // 血量
      float armor; // 护甲
      int moveSpeed; // 移动速度

      // 非静态内部类 // 只有一个外部类对象存在的时候,才有意义:战斗成绩只有在一个英雄对象存在的时候才有意义
      class BattleScore {
      int kill;
      int die;
      int assit;

      public void legendary() {
      if (kill >= 8)
      System.out.println(name + "超神!"); // 可以直接访问外部类的实例属性和方法 name
      else
      System.out.println(name + "尚未超神!");
      }
      }
      }
    • 只有一个外部类对象存在的时候,才有意义,new 外部类().new 内部类();

      1
      2
      3
      4
      5
      6
      7
      8
      9
      public static void main(String[] args) {
      Hero garen = new Hero();
      garen.name = "盖伦";

      // 实例化内部类
      BattleScore score = garen.new BattleScore(); // BattleScore 对象只有在一个英雄对象存在的时候才有意义,所以其实例化必须建立在一个外部类对象的基础之上
      score.kill = 9;
      score.legendary();
      }
  • 静态内部类

    • 在一个类里面声明一个静态内部类,static 修饰,不需要一个外部类的实例为基础,可以直接实例化。不可以访问外部类的实例属性和方法。
    • 除了可以访问外部类的私有静态成员外,静态内部类和普通类没什么大的区别。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      public class Hero {
      public String name;
      protected float hp;

      private static void battleWin() {
      System.out.println("battle win");
      }

      // 静态内部类
      static class EnemyCrystal { // 敌方的水晶
      int hp = 5000;

      // 如果水晶的血量为 0,则宣布胜利
      public void checkIfVictory() {
      if (hp == 0) {
      Hero.battleWin();
      System.out.println(name + " win this game"); // 不能直接访问外部类的对象属性
      }
      }
      }
      }
    • 不需要一个外部类的实例为基础,可以直接实例化,new 外部类.静态内部类();

      1
      2
      3
      4
      5
      6
      public static void main(String[] args) {

      // 实例化静态内部类
      Hero.EnemyCrystal crystal = new Hero.EnemyCrystal();
      crystal.checkIfVictory();
      }

1.5 匿名类

  • 匿名类指的是在声明一个类的同时实例化它,使代码更加简洁精练。

  • 通常情况下,要使用一个接口或者抽象类,都必须创建一个子类。有的时候,为了快速使用,直接实例化一个抽象类,并 “当场” 实现其抽象方法。

  • 既然实现了抽象方法,那么就是一个新的类,只是这个类,没有命名。这样的类,叫做匿名类。

  • 匿名类本质上就是在继承其他类,实现其他接口。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 抽象类
    public abstract class Hero {
    String name;
    float hp;
    float armor;
    int moveSpeed;

    public abstract void attack();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public static void main(String[] args) {

    ADHero adh = new ADHero();

    adh.attack();
    System.out.println(adh); // 通过打印 adh,可以看到 adh 这个对象属于 ADHero 类

    // 创建 匿名类
    Hero h = new Hero() {

    @Override
    public void attack() { // 使用一个抽象类,当场实现 attack 抽象方法
    System.out.println("新的进攻手段");
    }
    };
    h.attack();

    System.out.println(h); // 通过打印 h,可以看到 h 这个对象属于 Hero$1 这么一个系统自动分配的类名,这个类就是匿名类。
    }
  • 在匿名类中使用外部的局部变量,外部的局部变量必须修饰为 final

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static void main(String[] args) {

    // 在匿名类中使用外部的局部变量,外部的局部变量必须修饰为 final
    final int damage = 5;

    Hero h = new Hero() {
    @Override
    public void attack() {
    System.out.printf("新的进攻手段,造成 %d 点伤害", damage);
    }
    };
    }
  • 在 jdk8 中,已经不需要强制修饰成 final 了,如果没有写 final,不会报错,因为编译器偷偷的帮你加上了看不见的 final

1.6 本地类

  • 本地类可以理解为有名字的匿名类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 抽象类
    public abstract class Hero {
    String name;
    float hp;
    float armor;
    int moveSpeed;

    public abstract void attack();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public static void main(String[] args) {

    // 创建 本地类
    class SomeHero extends Hero { // 与匿名类的区别在于,本地类有了自定义的类名
    @Override
    public void attack() {
    System.out.println(name + " 新的进攻手段");
    }
    }

    SomeHero h = new SomeHero();
    h.name = "地卜师";
    h.attack();
    }
  • 内部类与匿名类不一样的是,内部类必须声明在成员的位置,即与属性和方法平等的位置。

  • 本地类和匿名类一样,直接声明在代码块里面,可以是主方法,for 循环里等等地方。

1.7 封装类

  • 所有的基本类型,都有对应的类类型。比如 int 对应的类是 Integer,这种类就叫做封装类。
类型 数据类型 封装类 尺寸 长度 默认值 范围
整型 byte Byte 1 字节 8 位 0 -128 ~ 127
short Short 2 字节 16 位 0 -32768 ~ 32767
int Integer 4 字节 32 位 0 -2^31 ~ 2^31 -1
long Long 8 字节 64 位 0 -2^63 ~ 2^63 -1
浮点型 float Float 4 字节 32 位 0.0f N/A
double Double 8 字节 64 位 0.0d N/A
字符型 char Character 2 字节 16 位 \u0000 0 ~ 65535
布尔型 boolean Boolean N/A 1 位 fasle true/false
  • 基本类型 转 封装类

    • 不需要调用构造方法,通过 = 符号自动把 基本类型 转换为 类类型,叫做 装箱。

      1
      2
      3
      4
      5
      int i = 5;

      Integer it = new Integer(i); // 把一个基本类型的变量,转换为 Integer 对象

      Integer it = i; // 自动转换就叫 装箱
  • 封装类 转 基本类型

    • 不需要调用 IntegerintValue 方法,通过 = 就自动转换成 int 类型,叫做 拆箱。

      1
      2
      3
      4
      5
      Integer it = new Integer(5);

      int i = it.intValue(); // 把一个 Integer 对象,转换为一个基本类型的 int

      int i = it; // 自动转换就叫 拆箱

1.8 UML 图

  • UML 图:Unified Module Language,统一建模语言,可以很方便的用于描述类的属性,方法,以及类和类之间的关系。

    • 类图
    • 接口图
    • 继承关系图
    • 实现关系图

1.9 类对象

  • 所有的类,都存在一个类对象,这个类对象用于提供类本身的信息,描述这种类有几种构造方法,有多少属性,有哪些普通方法。

  • 获取类对象有 3 种方式

方式 说明
对象.getClass() 通常应用在,比如你传过来一个 Object 类型的对象,而我不知道你具体是什么类,用这种方法。
类名.class 该方法最为安全可靠,程序性能更高,这说明任何一个类都有一个隐含的静态成员变量 class
Class.forName() 通过 Class 对象的 forName() 静态方法来获取,用的最多,但可能抛出 ClassNotFoundException 异常。
  • 在一个 JVM 中,一种类,只会有一个类对象存在。所以以上三种方式取出来的类对象,都是一样的。

  • 准确的讲是一个 ClassLoader 下,一种类,只会有一个类对象存在。通常一个 JVM 下,只会有一个 ClassLoader。

2、对象

  • 对象:对象是类的一个实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。

  • 现在让我们深入了解什么是对象。看看周围真实的世界,会发现身边有很多对象,车,狗,人等等。所有这些对象都有自己的状态和行为。

  • 拿一条狗来举例,它的状态有:名字、品种、颜色,行为有:叫、摇尾巴和跑。

  • 对比现实对象和软件对象,它们之间十分相似。

  • 软件对象也有状态和行为。软件对象的状态就是属性,行为通过方法体现。

  • 在软件开发中,方法操作对象内部状态的改变,对象的相互调用也是通过方法来完成。

2.1 创建对象

  • 对象是根据类创建的。在 Java 中,使用关键字 ​new​ 来创建一个新的对象。创建对象需要以下三步:

    • 声明:声明一个对象,包括对象名称和对象类型。
    • 实例化:使用关键字 ​new​ 来创建一个对象。
    • 初始化:使用 ​new​ 创建对象时,会调用构造方法初始化对象。
  • 实例

    1
    2
    3
    4
    5
    6
    7
    public class Puppy {

    // 这个构造器仅有一个参数:name
    public Puppy(String name) {
    System.out.println("Puppy Name is :" + name );
    }
    }
    1
    2
    3
    4
    5
    public static void main (String []args) {

    // 下面的语句将创建一个 Puppy 对象
    Puppy myPuppy = new Puppy("tommy");
    }

2.2 对象转型

  • 对象转型:指当引用类型和对象类型不一致的时候,才需要进行类型转换。类型转换有时候会成功,有时候会失败。

  • 通常情况下,引用类型和对象类型是一样的。

    • 引用类型
    • 对象类型
  • 转换方式

    • 子类转父类 (向上转型) :所有的子类转换为父类,都是说得通的。说得通,就可以转。
    • 父类转子类 (向下转型) :有的时候行,有的时候不行,所以必须进行强制转换。
    • 没有继承关系的两个类,互相转换 :一定会失败。
    • 实现类转换成接口 (向上转型) :转换是能成功的。
    • 接口转换成实现类 (向下转型) :转换会失败。

2.3 对象类型

  • Java 提供了运行时运算符 instanceof 来检查对象的类类型。

  • 判断一个引用所指向的对象。

    1
    2
    // 判断引用 h1 指向的对象,是否是 ADHero 类型
    System.out.println(h1 instanceof ADHero);

2.4 对象序列化

  • 一个对象以流的形式进行传输,叫做序列化

    • 序列化:将对象写入到 IO 流中。
    • 反序列化:从 IO 流中恢复对象。
  • Java 序列化机制允许将实现序列化的 Java 对象转换为字节序列。

    • 这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。
    • 序列化机制使得对象可以脱离程序的运行而独立存在。
  • 所有可在网络上传输的对象都必须是可序列化的。

  • 所有需要保存到磁盘的对象都必须是可序列化的。

  • 一个对象要实现序列化,该对象所对应的类,必须是实现 Serializable 接口 或者 Externalizable 接口 之一。

实现 Serializable 接口 实现 Externalizable 接口
系统自动存储必要的信息 程序员决定存储哪些信息
Java 内建支持,易于实现,只需要实现该接口即可,无需任何代码支持 必须实现接口内的两个方法
性能略差 性能略好
  • 虽然 Externalizable 接口带来了一定的性能提升,但变成复杂度也提高了,所以一般通过实现 Serializable 接口进行序列化。

2.5 对象的引用

  • JDK1.2 之后,Java 对引用的概念做了扩充,将引用分为 强引用 (Strong Reference) 、软引用 (Soft Reference) 、弱引用 (Weak Reference) 和 虚引用 (Phantom Reference) 四种,这四种引用的强度依次递减。

  • 强引用:强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。比如我们常用的 A a = new A 就是强引用。

  • 软引用:如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可以和一个引用队列 ReferenceQueue 联合使用,如果软引用所引用的对象被垃圾回收器回收,Java 虚拟机就会把这个软引用加入到与之关联的引用队列中。

  • 弱引用:用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。弱引用相比较于软引用生命周期更短,且内存是否充足不影响回收。

  • 虚引用:最弱的一种引用关系。如果一个对象仅持有虚引用,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用必须和引用队列 ReferenceQueue 联合使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

3、属性

  • Java 类的属性分为二种

    • 类属性
    • 对象属性
  • 类属性

    • 当一个属性被 static 修饰的时候,就叫做类属性,又叫做静态属性。
    • 当一个属性被声明成类属性,那么所有的对象,都共享一个值。
  • 对象属性

    • 又叫实例属性,非静态属性。
    • 不同对象的 对象属性 的值都可能不一样。
  • 访问类属性

    • 有两种方式,这两种方式都可以访问类属性,访问即修改和获取,但是建议使用第二种 类.类属性 的方式进行,这样更符合语义上的理解。

    • 对象.类属性

      1
      teemo.copyright
    • 类.类属性

      1
      Hero.copyright

3.1 属性初始化

  • 对象属性初始化有三种

    • 声明该属性的时候初始化
    • 构造方法中初始化
    • 初始化块

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public class Hero {
      public String name = "some hero"; // 声明该属性的时候初始化
      protected float hp;
      float maxHP;

      {
      maxHP = 200; // 初始化块
      }

      public Hero() {
      hp = 100; // 构造方法中初始化
      }
      }
  • 类属性初始化有二种

    • 声明该属性的时候初始化
    • 静态初始化块

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      public class Hero {
      public String name;
      protected float hp;
      float maxHP;

      public static int itemCapacity = 8; // 声明的时候 初始化

      static {
      itemCapacity = 6; // 静态初始化块 初始化
      }
      }
  • 初始化顺序

    • 对于静态变量、静态初始化块、变量、初始化块、构造器,它们的初始化顺序依次是 (静态变量、静态初始化块)>(变量、初始化块)> 构造器

4、方法

  • Java 类的方法分为二种

    • 类方法
    • 对象方法
  • 类方法

    • 又叫做静态方法。被 static 修饰的方法。
    • 访问类方法,不需要对象的存在,直接就访问。
    • 如果一个方法,没有调用任何对象属性,那么就可以考虑设计为类方法。
  • 对象方法

    • 又叫实例方法,非静态方法。
    • 访问一个对象方法,必须建立在有一个对象的前提的基础上。
    • 如果方法里访问了对象属性,那么这个方法,就必须设计为对象方法。
  • 类方法有几个限制

    • 它们只能调用其他静态方法。
    • 它们只能访问静态数据。
    • 他们不能以任何方式引用 this 或 super。
  • 访问类方法

    • 和访问类属性一样,调用类方法也有两种方式,这两种方式都可以调用类方法,但是建议使用第二种 类.类方法 的方式进行,这样更符合语义上的理解。

    • 对象.类方法

      1
      garen.battleWin();
    • 类.类方法

      1
      Hero.battleWin();

4.1 构造方法

  • 通过一个类创建一个对象,这个过程叫做实例化,实例化是通过调用构造方法(又叫做构造器)实现的。

  • 构造方法方法名和类名一样(包括大小写),没有返回类型,实例化一个对象的时候,必然调用构造方法。

  • 每个类都有构造方法。如果没有显式地为类定义构造方法,Java 编译器将会为该类提供一个默认无参的构造方法。

  • 一旦提供了一个有参的构造方法,同时又没有显式的提供一个无参的构造方法,默认的无参的构造方法就失效了。

  • 在创建一个对象的时候,至少要调用一个构造方法。一个类可以有多个构造方法。

  • 和普通方法一样,构造方法也可以重载

  • 子类不能继承父类的构造方法,所以就不存在重写父类的构造方法。

  • 无参构造方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Hero {
    String name;
    float hp;
    float armor;
    int moveSpeed;

    // 无参构造方法
    public Hero() { // 方法名和类名一样(包括大小写),没有返回类型
    System.out.println("实例化一个对象的时候,必然调用构造方法");
    }
    }
  • 有参构造方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Hero {
    String name;
    float hp;
    float armor;
    int moveSpeed;

    // 有参构造方法
    public Hero(String heroname) { // 如果没有显式的提供一个无参的构造方法,那么默认的无参构造方法就失效了
    name = heroname;
    }
    }
  • 构造方法重载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class Hero {
    String name;
    float hp;
    float armor;
    int moveSpeed;

    // 无参构造方法
    public Hero() {

    }

    // 带一个参数的构造方法
    public Hero(String heroname) {
    name = heroname;
    }

    // 带两个参数的构造方法
    public Hero(String heroname, float herohp) {
    name = heroname;
    hp = herohp;
    }
    }

4.2 抽象方法

  • 抽象方法:在类中声明一个方法,这个方法没有实现体,是一个 “空” 方法,这样的方法就叫抽象方法,使用修饰符 abstract 修饰。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public abstract class Hero {

    String name;
    float hp;
    float armor;
    int moveSpeed;

    // 抽象方法 attack,Hero 的子类会被要求实现 attack 方法
    public abstract void attack();
    }
  • 实现 抽象方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class ADHero extends Hero {

    public void physicAttack() {
    System.out.println("进行物理攻击");
    }

    // 实现 抽象方法
    @Override
    public void attack() {
    physicAttack();
    }
    }
  • 抽象方法不可同时是 static 的,不可同时是 synchronized

4.3 方法重载

  • 方法重载(Overload):指的是方法名一样,但是参数(或返回值类型)不一样。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class ADHero extends Hero {

    public void attack() {
    System.out.println(name + " 进行了一次攻击 ,但是不确定打中谁了");
    }

    public void attack(Hero h1) {
    System.out.println(name + "对" + h1.name + "进行了一次攻击 ");
    }

    public void attack(Hero h1, Hero h2) {
    System.out.println(name + "同时对" + h1.name + "和" + h2.name + "进行了攻击 ");
    }
    }
  • 可变数量的参数 的方法

    1
    2
    3
    4
    5
    6
    7
    // 可变数量的参数,在方法里,使用操作数组的方式处理参数 heros 即可
    public void attack(Hero... heros) {

    for (int i = 0; i < heros.length; i++) {
    System.out.println(name + " 攻击了 " + heros[i].name);
    }
    }
  • Overload(重载)的方法是否可以改变返回值的类型

    • 可以,重载其实本质上就是完全不同的方法,只是恰好取了相同的名字。

4.4 方法重写(覆盖)

  • 方法重写(Override):子类可以继承父类的 对象方法,在继承后,重复提供该方法,就叫做方法的重写,又叫覆盖 。

    1
    2
    3
    4
    5
    6
    7
    8
    public class Item {
    String name;
    int price;

    public void effect() {
    System.out.println("物品使用后,可以有效果");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    public class LifePotion extends Item {

    // 重写父类方法
    @Override
    public void effect() {
    System.out.println("血瓶使用后,可以回血");
    }
    }
  • Overload(重载)和 Override(重写)的区别

    • Overload 是方法重载的意思,指的是在同一个类里面,方法名一样,但是参数不一样。
    • Override 是方法重写的意思,指的是子类继承了父类的某个方法后,重新又写了一遍。

4.5 方法隐藏

  • 方法隐藏:与重写类似,就是子类覆盖父类的 类方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Hero {
    public String name;
    protected float hp;

    // 类方法,静态方法,通过类就可以直接调用
    public static void battleWin(){
    System.out.println("hero battle win");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    public class ADHero extends Hero {

    // 方法隐藏,隐藏父类的 battleWin 方法
    public static void battleWin(){
    System.out.println("ad hero battle win");
    }
    }

4.6 传参

  • 变量有两种类型,基本类型和类类型。参数也是变量,所以传参分为:基本类型传参和类类型传参。

  • 基本类型传参

    • 赋值,在方法内,无法修改方法外的基本类型参数。
  • 类类型传参

    • 引用,类类型又叫引用,在方法内,可以修改方法外的引用类型参数。

5、引用

  • 引用:如果一个变量的类型是 类类型,而非基本类型,那么该变量又叫做引用。

  • 一个引用,同一时间,只能指向一个对象。

    1
    2
    3
    4
    5
    public static void main(String[] args) {

    Hero garen = new Hero();
    garen = new Hero();
    }
  • 多个引用,同一时间,可以指向同一个对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public static void main(String[] args) {

    Hero h1 = new Hero(); // 使用一个引用来指向这个对象
    Hero h2 = h1; // h2 指向 h1 所指向的对象
    Hero h3 = h1;
    Hero h4 = h1;
    Hero h5 = h4;

    // h1, h2, h3, h4, h5 五个引用,都指向同一个对象
    }

6、继承

  • 继承:子类拥有父类的属性和方法。继承使用关键字 extends

    1
    2
    3
    4
    public class Item {
    String name;
    int price;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // Weapon 不继承
    public class Weapon { // 独立设计 name 和 price 属性,同时多了一个属性 damage 攻击力
    String name;
    int price;
    int damage;
    }

    // Weapon 继承自 Item
    public class Weapon extends Item { // 虽然自己没有设计 name 和 price,但是通过继承 Item 类,也具备了 name 和 price 属性
    int damage;
    }

7、多态

  • 多态:同一个类型,调用同一个方法,却能呈现不同的状态。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class Item {
    String name;
    int price;

    public void effect() {
    System.out.println("物品使用后,可以有效果");
    }
    }

    public class LifePotion extends Item {

    public void effect(){
    System.out.println("血瓶使用后,可以回血");
    }
    }

    public class MagicPotion extends Item {

    public void effect(){
    System.out.println("蓝瓶使用后,可以回魔法");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public static void main(String[] args) {

    // 父类(接口)引用指向子类对象
    Item i1 = new LifePotion();
    Item i2 = new MagicPotion();

    // 调用的方法有重写
    System.out.print("i1 是 Item 类型,执行 effect 打印:");
    i1.effect(); // 输出:血瓶使用后,可以回血

    System.out.print("i2 也是 Item 类型,执行 effect 打印:");
    i2.effect(); // 输出:蓝瓶使用后,可以回魔法
    }
  • 操作符的多态:同一个操作符在不同情境下,具备不同的作用。

    • + 可以作为算数运算,也可以作为字符串连接。
  • 类的多态 条件

    • 父类(接口)引用指向子类对象。
    • 调用的方法有重写。

8、包

  • :主要用来对类和接口进行分类。当开发 Java 程序时,可能编写成百上千的类,因此很有必要对类和接口进行分类。把比较接近的类,规划在同一个包下。使用关键字 package

  • 使用同一个包下的其他类,直接使用即可。

  • 使用其他包下的类,必须 import 包名.类名

  • Import 语句

    • 在 Java 中,如果给出一个完整的限定名,包括包名、类名,那么 Java 编译器就可以很容易地定位到源代码或者类。​
    • Import​ 语句就是用来提供一个合理的路径,使得编译器可以找到某个类。
  • 实例

    1
    2
    3
    4
    5
    6
    7
    8
    package charactor;          // 在最开始的地方声明该类所处于的包名

    import property.Weapon; // Weapon 类在其他包里,使用必须进行 import
    import java.io.*;

    public class Hero {

    }

9、接口

  • 接口:接口就像是一种约定,实现某个接口,就相当于承诺了某种约定。

  • 定义接口

    • 接口在语法上使用关键字 interface
    • 声明一个方法,但是没有方法体,是一个 “空” 方法。

      1
      2
      3
      4
      public interface AD {

      public void physicAttack(); // 声明一个 “空” 方法
      }
  • 实现接口

    • 实现在语法上使用关键字 implements
    • 实现某个接口,就相当于承诺了某种约定。

      1
      2
      3
      4
      5
      6
      7
      public class Hero implements AD {                   // 实现接口

      @Override
      public void physicAttack() { // 实现了 AD 这个接口,就必须提供 AD 接口中声明的方法
      System.out.println("进行物理攻击");
      }
      }
  • abstract class(抽象类)和 interface(接口)的区别

    • 相同

      • 抽象类和接口都可以有实体方法。
    • 使用方式

      • 抽象类只能够通过继承被使用。子类只能继承一个抽象类,不能继承多个。
      • 接口必须通过实现被使用。子类可以实现多个接口。
    • 实现方法

      • 抽象类不仅可以提供抽象方法,也可以提供实现方法。
      • 接口只能提供抽象方法,不能提供实现方法。但是在 Java8 版本开始,接口可以提供实现方法了,前提是要在方法前加一个 default 修饰符。
    • 属性定义

      • 抽象类可以定义
        • public, protected, package, private
        • 静态和非静态属性
        • final 和非 final 属性
      • 但是接口中声明的属性,只能是以下类型,即便没有显式的声明
        • public
        • 静态
        • final 的
  • 接口是否可继承接口?

    • 可以,比如 List 就继承了接口 Collection。
  • 抽象类是否可实现(implements)接口?

    • 可以,比如 MouseAdapter 鼠标监听适配器 是一个抽象类,并且实现了 MouseListener 接口。
  • 抽象类是否可继承实体类(concrete class)?

    • 可以,所有抽象类,都继承了 Object。

9.1 接口默认方法

  • 接口默认方法:JDK8 新特性,指的是接口也可以提供具体方法了,而不像以前,只能提供抽象方法,这个方法有实现体,并且被声明为了 default

    1
    2
    3
    4
    5
    6
    7
    8
    public interface Mortal {
    public void die();

    // 接口默认方法,声明为 default
    default public void revive() {
    System.out.println("本英雄复活了");
    }
    }
  • 引入了默认方法后,实现了接口的类,不需要做任何改动,并且还能得到这个默认方法。

9.2 函数式接口

  • 如果接口中只有一个抽象方法,该接口称为函数式接口。

  • 可以包含多个默认方法或多个 static 方法。

  • 函数式接口其存在的意义,主要是配合 Lambda 表达式 来使用。

10、关键字

10.1 this

  • this 这个关键字,相当于普通话里的 “我”,this 即代表当前对象。
  • 可以在任何方法中使用来引用当前对象。

  • 通过 this 访问属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class Hero {
    String name;
    float hp;

    // 参数名和属性名一样,在方法体中,只能访问到参数 name
    public void setName1(String name) {
    name = name;
    }

    // 为了避免 setName1 中的问题,参数名不得不使用其他变量名
    public void setName2(String heroName) {
    name = heroName;
    }

    // 通过 this 访问属性
    public void setName3(String name) {

    // name 代表的是参数 name,this.name 代表的是属性 name
    this.name = name;
    }
    }
  • 通过 this 调用其他的构造方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class Hero {
    String name;
    float hp;

    // 带一个参数的构造方法
    public Hero(String name) {
    this.name = name;
    }

    // 带两个参数的构造方法
    public Hero(String name,float hp) {
    this.hp = hp;

    // 在一个构造方法中,调用另一个构造方法
    this(name);
    }
    }

10.2 super

  • super 父类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Hero {
    String name;
    float hp;
    float armor;
    int moveSpeed;

    public void useItem(Item i) {
    System.out.println("hero use item");
    i.effect();
    }
    }
  • 实例化子类,父类的构造方法一定会被调用,并且是父类构造方法先调用,子类构造方法会默认调用父类的无参的构造方法。

    1
    2
    3
    4
    public ADHero(String name) {
    super(name); // 子类显式调用父类带参构造方法
    System.out.println("AD Hero 的构造方法");
    }
  • 调用父类属性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class ADHero extends Hero {
    int moveSpeed = 400;

    public int getMoveSpeed() {
    return this.moveSpeed;
    }

    public int getMoveSpeed2() {
    return super.moveSpeed; // 调用父类属性
    }
    }
  • 调用父类方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class ADHero extends Hero {

    int moveSpeed = 400;

    public int getMoveSpeed() {
    return this.moveSpeed;
    }

    public int getMoveSpeed2() {
    return super.moveSpeed;
    }

    // 重写 useItem,并在其中调用父类的 userItem 方法
    public void useItem(Item i) {
    System.out.println("adhero use item");
    super.useItem(i);
    }
    }

10.3 其它关键字

关键字 介绍 示例
instanceof 检查对象的类类型 Boolean b = h1 instanceof ADHero;
extends 类继承
interface 定义接口
@interface 定义注解
implements 实现接口
package 定义包
import 引用包,使用其它包下的类
synchronized 声明需要线程同步的关键部分

11、修饰符

  • 下表显示了功能和修改器的所有可能组合。yes 表示我们可以使用该修饰符来控制对应实体的访问。
修饰符 变量 方法 构造函数 代码块
public yes yes yes yes no
protected no yes yes yes no
empty accessor yes yes yes yes yes
private no yes yes yes no
final yes yes yes no no
abstract yes no yes no no
static no yes yes no yes
native no no yes no no
transient no yes no no no
volatile no yes no no no
synchronized no no yes no yes

11.1 基本修饰符

11.1.1 static

  • static 表示 “全局” 或者 “静态” 的意思,用来修饰成员变量和成员方法,也可以形成静态 static 代码块。

  • 被 static 修饰的成员变量和成员方法独立于该类的任何对象。也就是说,它不依赖类特定的实例,被类的所有实例共享。

  • 只要这个类被加载,Java 虚拟机就能根据类名在运行时数据区的方法区内定找到他们。因此,static 对象可以在它的任何对象创建之前访问,无需引用任何对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Hero() {

    // 静态属性
    private static int intValue;

    // 静态方法
    public static void aStaticMethod() {
    }

    // JVM 在运行 main 方法的时候可以直接调用而不用创建实例
    public static void main(String[] args) {
    }
    }
  • 用 static 修饰的代码块表示静态代码块,当 Java 虚拟机加载类时,就会执行该代码块。是在类中独立于类成员的 static 语句块,可以有多个,位置可以随便放,它不在任何的方法体内。

  • 如果 static 代码块有多个,JVM 将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class Test {
    private static int a;
    private int b;

    public void f() {
    System.out.println("hello");
    }

    // 静态代码块
    static {
    Test.a = 3;

    Test t = new Test();
    t.f();
    t.b = 1000;
    }

    // 静态代码块
    static {
    Test.a = 4;
    }
    }
  • static 修饰 类、属性、方法、代码块 的时候分别有不同的意思。


























修饰类型 说明
静态类
属性 类属性,又叫做静态属性
方法 类方法,又叫做静态方法
代码块 表示静态代码块,当 Java 虚拟机加载类时,就会执行该代码块

11.1.2 final

  • final 修饰一个变量,有很多种说法,比如不能改变等等。

  • 准确的描述是 当一个变量被 final 修饰的时候,该变量只有一次赋值的机会。

  • final 修饰的变量在方法中,可以先初始化再赋值。但是如果是全局变量,必须在初始化的时赋值,不然会报错。

    1
    2
    // 全局变量
    public final int itemTotalnumber = 6;
  • static 和 final 一块用来修饰成员变量和成员方法,可简单理解为 “全局常量”。

  • 对于变量,表示一旦给值就不可修改,并且通过类名可以访问。

  • 对于方法,表示不可覆盖,并且可以通过类名直接访问。

    1
    2
    // 全局常量
    public static final int HOURS_OF_DAY = 24;
  • final 修饰 类、方法、基本类型变量、引用 的时候分别有不同的意思。


























修饰类型 说明
该类不能够被继承
方法 该方法不能够被重写
引用 该引用只有一次指向对象的机会
基本类型变量 该变量只有一次赋值机会

11.1.3 abstract

  • abstract 修饰抽象类、抽象方法。

11.2 访问修饰符

  • 访问修饰符的使用

    • 属性通常使用 private 封装起来。
    • 方法一般使用 public 用于被调用。
    • 会被子类继承的方法,通常使用 protected
    • package 用的不多,一般新手会用 package,因为还不知道有修饰符这个东西。
  • 成员变量有四种修饰符

修饰符 说明
private 私有的
package/friendly/default 不写,没有修饰符即代表 package/friendly/default
protected 受保护的
public 公共的
  • 作用域
修饰符 自身 同包子类 不同包子类 同包类 其他类
private 访问 不能继承 不能继承 不能访问 不能访问
package 访问 继承 不能继承 访问 不能访问
protected 访问 继承 继承 访问 不能访问
public 访问 继承 继承 访问 访问
  • 作用范围最小原则

    • 简单说,能用 private 就用 private,不行就放大一级,用 package,再不行就用 protected,最后用 public
    • 这样就能把数据尽量的封装起来,没有必要露出来的,就不用露出来了。

12、堆栈

  • Heap 堆

    • 所有的对象和它们相应的实例变量以及数组将被存储在这里。
    • 每个 JVM 只有一个堆区。
    • 由于方法区和堆区的内存由多个线程共享,所以存储的数据不是线程安全的。
  • Stack 栈 (在一些书籍里,会被翻译为堆栈,实际上指的就是单纯的这个栈)

    • 对每个线程会单独创建一个运行时栈。
    • 所有的局部变量将在栈内存中创建。
    • 栈区是线程安全的,因为它不是一个共享资源。
  • heap(堆)和 stack(栈)的区别

    • 存放的内容不一样

      • heap: 是存放对象的。
      • stack: 是存放基本类型(int, float, boolean 等等)、引用(对象地址)、方法调用。
    • 存取方式不一样

      • heap: 是自动增加大小的,所以不需要指定大小,但是存取相对较慢。
      • stack: 是固定大小的,并且是 FILO 先入后出的顺序,并且存取速度比较快。
    • 线程安全不一样

      • heap: 不是线程安全的。
      • stack: 是线程安全的。
文章目录
  1. 1. 前言
  2. 2. 1、类
    1. 2.1. 1.1 类变量
    2. 2.2. 1.2 Object 类
    3. 2.3. 1.3 抽象类
    4. 2.4. 1.4 内部类(嵌套类)
    5. 2.5. 1.5 匿名类
    6. 2.6. 1.6 本地类
    7. 2.7. 1.7 封装类
    8. 2.8. 1.8 UML 图
    9. 2.9. 1.9 类对象
  3. 3. 2、对象
    1. 3.1. 2.1 创建对象
    2. 3.2. 2.2 对象转型
    3. 3.3. 2.3 对象类型
    4. 3.4. 2.4 对象序列化
    5. 3.5. 2.5 对象的引用
  4. 4. 3、属性
    1. 4.1. 3.1 属性初始化
  5. 5. 4、方法
    1. 5.1. 4.1 构造方法
    2. 5.2. 4.2 抽象方法
    3. 5.3. 4.3 方法重载
    4. 5.4. 4.4 方法重写(覆盖)
    5. 5.5. 4.5 方法隐藏
    6. 5.6. 4.6 传参
  6. 6. 5、引用
  7. 7. 6、继承
  8. 8. 7、多态
  9. 9. 8、包
  10. 10. 9、接口
    1. 10.1. 9.1 接口默认方法
    2. 10.2. 9.2 函数式接口
  11. 11. 10、关键字
    1. 11.1. 10.1 this
    2. 11.2. 10.2 super
    3. 11.3. 10.3 其它关键字
  12. 12. 11、修饰符
    1. 12.1. 11.1 基本修饰符
      1. 12.1.1. 11.1.1 static
      2. 12.1.2. 11.1.2 final
      3. 12.1.3. 11.1.3 abstract
    2. 12.2. 11.2 访问修饰符
  13. 13. 12、堆栈
隐藏目录