类(class)由类加载器加载,在内存中存储在方法区,包含类的信息、运行时常量池和非final成员变量(即静态成员变量等)。

加载一个类

类被装载(加载)、链接、初始化的过程被称为类的加载。整个过程请参阅JVM内存模型。其中,包含执行静态代码(包括静态代码块和静态成员变量赋初值)的初始化过程是可选的。类仅会在以下4种情况下被初始化:

  1. 实例化对象、读取或设置类的静态成员变量、调用类的静态方法
  2. 被反射调用,如forName(全类名)方法
  3. 子类被初始化而父类还没被初始化时
  4. 被指定为Main-Class的类会被JVM首先加载

其它情况有的只是加载了类,没有执行静态代码(初始化)(如使用“类名.class”、使用forName时指定不初始化、通过子类调用父类的静态代码)。类只会被加载一次,并在加载后通过类名直接使用其中的静态方法、静态成员变量和静态内部类。类只有在实例化后成对象后才能访问或执行其中的非静态成员变量、代码块、方法和内部类。

类的部件

成员变量

成员变量即实例化时对象拥有的属性(property)/字段(field)(前提是该成员变量是非静态的)。成员变量在类加载时即被初始化为0或null,除非该成员变量被final修饰。
当类被封装后,成员变量只能通过构造器进行初始化赋值,使用get方法获取成员变量值,使用set方法对成员变量进行变更。根据不同的修饰符(modifier),有静态属性、final(常量)属性等。

代码块

代码块又称构造代码块、匿名代码块,就像一个没有方法名的方法。它会在构造方法执行前执行。根据不同的修饰符,有静态代码块等。

方法

方法(method)即是命名代码块。方法之间是通过方法名和参数列表区分的。可以通过调用方法名来执行方法体。换言之,方法体仅会在调用方法时执行。根据不同的修饰符,有静态方法、final方法、抽象方法等。
通过方法重载(overload),在一个类中,同一个方法名可以使用不同的参数。
不同的构造器也是方法重载的一种方式。
方法重载时,方法参数列表的参数类型、个数和顺序至少一种不同。方法的修饰符、返回类型、抛出异常可以相同,也可以不同。

内部类

内部类是类的成员类。内部类和外部类可以相互直接访问,而其它类要用到一个类的(非静态)内部类,则需要先实例化外部类对象,并导入内部类的包名后,才能使用内部类。
内部类可分为成员内部类、静态内部类、局部内部类和匿名内部类4种。详细的介绍参见内部类笔记。

对象

类的实例化称为对象(object)。可以使用一个引用来指向实例化的对象。类能够被封装,在实例化时被继承,在继承中实现多态。
对象在内存中存储在堆中,由一个在虚拟机栈中的引用指向堆中的地址。
静态代码与对象无关,没有下面的特性。

封装

一般对对象的属性进行封装,使用构造器对对象进行初始化,使用get方法获取对象的值,使用set方法对对象的属性进行变更。
即将对象的属性设为private,使用public构造器、get方法、set方法对对象进行初始化、获取和赋值。这即是对象的封装性。

创建和初始化

使用new关键字为对象申请和分配内存空间,后面跟着构造器方法。可以使用无参构造器,也可以使用自定义构造器。

  1. new Student(); // 无参构造器
  2. new Student("Jam Lin", 12, "Male"); // 自定义构造器

使用一个引用指向创建出来的对象,以便后续使用。

  1. Student stu = new Student();

构造器

在类中使用和类名相同的名字命名的方法,即类的构造器(constructor)。当构造器没有参数时,为无参构造器。当类没有显式的构造器时,会在编译时自动生成一个无参构造器。可以通过无参构造器为新对象指定一个默认值。构造器没有返回值。

  1. public Student() {
  2. this.age = 10; // 为age属性指定默认值为10
  3. }

有参数时,为自定义构造器。使用自定义构造器可以为对象的属性赋值。当类中存在自定义构造器时,便不会自动生成无参构造器了。

  1. public Student(String name, int age, String gender) {
  2. this.name = name;
  3. this.age = age;
  4. this.gender = gender;
  5. }

接口没有构造器。

this

this是指向当前对象的引用。使用该方法的对象是谁,this就指向哪个对象。

继承

一个类可以继承(extends)一个父类,获取其中的全部public属性和对象。private属性和对象也能继承,但不能访问。不能继承final类。

方法重写

当子类继承了一个父类时,可以重写(覆盖,override)父类的方法,以满足子类的需要。在子类对象调用被重写的方法时,只会调用子类的方法。
重写时,方法的名字、参数列表必须相同,返回类型是原类型或其子类型(基本数据类型只能是原类型),访问控制修饰符可以被扩大不能被缩小,抛出异常可以被缩小不能被扩大。当一个方法的方法名和参数列表与父类一致时,即说明该方法是被重写的方法,必须满足上述要求。当一个方法仅方法名相同,参数列表不同时,该方法与父类的方法没有重写关系,只是一个普通的方法。
当父类是一个抽象类时,子类需要重写父类的全部抽象方法,否则子类也要被声明为抽象类。
当父类的final方法被“重写”时,重写无效,依旧调用父类的方法。

super

super是指向当前对象的父类的对象的引用。使用该方法的对象是谁,super就指向哪个对象的父类的对象。使用super()可以调用父类的构造器。

多态

父类相同但子类不同的对象,使用相同名称的方法,这种对象的特性称为多态。对象通过父类调用不同子类相同名称的方法,即父类引用指向子类对象(向上转型),是多态的一种形式。多态实现的主要形式为:父类引用指向子类对象、接口引用指向实现类对象、方法重写(例如通过抽象类、接口、匿名内部类、Lambda表达式)、继承。
例如,猫的叫声和狗的叫声不同,让猫和狗继承动物类,让动物类去叫,根据传入的对象不同,叫声也不同:

  1. Animal a = new Dog();
  2. a.cry();
  3. // Animal类中:
  4. cry() {
  5. this.cry();
  6. } // 这里也可以声明为抽象方法
  7. // Dog类中:
  8. cry() {
  9. sout("汪汪汪");
  10. }

可以使用instanceof关键字来判断一个对象是否属于一个类。

父类引用指向子类对象

当使用了一个父类引用指向子类对象时:

  1. Father father = new Son();

该引用有以下几个特性:

  1. 该引用可以使用父类方法和属性,不能使用子类特有的方法和属性
  2. 该引用可以使用子类重写的方法(即子类和父类共有的方法)
  3. 该引用可以被强转为子类的引用,前提是属于该子类(可以使用instanceof判断)

    接口

    接口(interface)类似于类,是三种引用类型之一。接口存在以下特点:

  4. 接口能被多个类或接口“继承”;而类只能继承一个类。

  5. 接口所有的方法均默认被public abstract修饰,故全为抽象方法;所有成员变量均默认被public static final修饰,故全为静态常量。
  6. “继承”接口的类称为接口的实现类,该类负责对接口内存在的抽象方法进行实现(implements),一般为此类添加Imp或Impl后缀。对接口抽象方法的实现方式与方法重写完全一致。
  7. 接口不具有构造方法,故不能实例化对象。但接口拥有可以指向实现类对象的引用。
  8. 接口的实现类,其对象的多态有些特殊。当一个接口的引用指向接口的实现类对象时,实现类实现的另一个接口的引用也可以指向该对象(但需要强转)。例如以下接口和实现类: 类 对象 接口 - 图1存在如下代码:
    1. A a = new C();
    2. a.aa(); // 调用自己接口的方法,OK
    3. a.bb(); // 调用其它接口的方法,编译报错
    4. B b = (B)a; // 将a强转为b
    5. b.bb(); // OK
    接口为开发人员提供了一个方法的目录,为底层实现提供了准备。同时,一个接口的实现类可以对接口进行空实现(即方法体为空),后面的继承类只需要重写该方法即可。(即interface A; class B implements A;(空实现) class C extends B;)
    JDK1.8中,接口还可以具有默认方法和静态方法。