1. 继承、封装和多态
① 多态
-
多态: 是指程序中定义的引用变量所指向的具体对象类型和通过该引用变量发出的方法调用在编译时并不确定,而是在程序运行期间才确定。因为在程序运行时才确定具体的类,不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序呈现不同的运行状态,这就是多态性。
-
多态存在的三个必要条件: 继承(没有继承,就不会有父类和子类)、重写、父类引用指向子类对象(这时就可以在运行时调用对应子类的方法)。
-
向上转型: 父类引用指向子类对象,
Animal a = new Cat();
-
向下转型: 子类引用指向父类对象,
Cat c = (Cat)a;
-
具体例子:
public class Test2 { public static void main(String[] args) { Animal a = new Cat(); // 向上转型 a.eat(); // 调用的是 Cat 的 eat Cat c = (Cat)a; // 向下转型 c.work(); // 调用的是 Cat 的 work } } abstract class Animal { abstract void eat(); } class Cat extends Animal { public void eat() { System.out.println("吃鱼"); } public void work() { System.out.println("抓老鼠"); } }
② 封装
-
封装: 隐藏对象的属性(private)和实现细节,仅对外公开接口,并控制每个接口的访问权限。
-
目的: 保护或者防止代码(数据)被我们无意中破坏,增强安全性和简化编程。
-
封装的优点:
① 良好的封装能够减少耦合。
② 类内部的结构可以自由修改。
③ 可以对成员变量进行更精确的控制。
④ 隐藏属性和实现细节。 -
自己所理解的封装: 类的属性设置为private权限,通过public权限的getter和setter方法提供修改类属性的接口。
-
具体示例:
public class Subject implements Cloneable{ private String name; private String teacher; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getTeacher() { return teacher; } public void setTeacher(String teacher) { this.teacher = teacher; } }
③ 继承
- 继承: 具有相同的特征(属性)和行为(方法)时,可以将相同的部分抽取出来放到一个类中作为基类,其它类继承这个基类获取相同的属性和方法。
- 目的: 继承是为了复用父类代码。两个类若存在
IS-A的关系
就可以使用继承
。
2. 抽象与接口
① 抽象类与抽象方法
-
在Java中使用abstract关键字修饰的类,叫抽象类;使用abstract关键字修饰的方法叫抽象方法。
-
抽象类和抽象方法的定义格式如下:
访问权限 abstract class 抽象类名称{ 访问权限 abstract 返回值类型 方法名称([参数1, 参数2, ...]); }
抽象方法和抽象类的规则:
- 抽象类可以没有抽象方法,有抽象方法必须定义为抽象类。
- 抽象类不能被实例化,只有继承并实现抽象类的子类才能实例化。
- 抽象类的访问权限为
public、default
,抽象方法的访问权限为public、protected、default
。jdk1.8以前抽象方法的默认访问权限为protected,jdk1.8时为default。 - 子类继承抽象类,必须实现抽象类中的所有抽象方法;若没有全部实现,则需要将子类声明为抽象类,否则编译器会报错。
- 子类使用extends继承抽象类,一个子类只能继承一个抽象类。
- 抽象类不能使用
final
修饰,抽象方法不能使用static、final
修饰。原因:使用final修饰的抽象方法和抽象类,不能被子类继承;static声明方法表明这个方法在不生成类的实例时可直接被类调用,而abstract方法不能被调用。 - 外部抽象类不允许使用static声明,而内部抽象类允许使用static声明。
- 抽象类可以有自己的成员变量、构造函数,子类必须继承父类的一个构造函数。
对8的解释
- Animal是一个抽象类,子类Cat的构造函数会默认先执行父类的构造函数;
- 父类不存在无参构造函数,此时只能显式调用父类的有参构造函数。
- 父类构造函数的调用必须在第一行,此时调用参数只能通过子类构造函数的入参传入
- 也就要求Cat必须定义一个,含有入参
name
的构造函数public abstract class Animal { public Animal(String name){ System.out.println(name); } public abstract void play(); } public class Cat extends Animal { public Cat(String name) { super(name); } @Override public void play() { } }
② 接口
- 接口使用
interface关键字
修饰,严格上来说接口并不是一个类。 - 如果一个类只包含抽象方法和全局常量,可以将其定义为一个接口。注意: 接口只有抽象方法和全局常量,没有构造函数。
接口的特性:
-
接口中的属性默认都是
public static final
的,比如以下定义:int x = 123; public static final int z = 0; // Modifier 'public'、static、final is redundant for interface fields
-
接口中的方法默认都是
public abstract
的。 -
子类通过implements关键字实现接口,一个类可以实现多个接口。
-
接口可以看做一个完全抽象类,不能实例化。
③ 抽象类与接口的区别
- 从设计层面上看,抽象类提供了一种 IS-A 关系,接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约。
- 从使用上看,一个类只能继承一个抽象类,但是能实现多个接口。
- 从成员变量看:接口中的属性都是
public static final
的,而抽象类中的属性与普通类一样。 - 从构造函数看:接口中不能有构造函数,而抽象类中可以有构造函数。
- 从成员方法看:接口中的方法都是
public abstract
的,而抽象类中除了拥有抽象方法,还可有普通方法。并且,抽象类中的抽象方法可以是public、protected、default
的。 - 从速度上看:抽象类速度更快一些,接口需要时间寻找类中的实现方法。
④ 抽象类与接口的使用选择
使用接口:
- 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法;
- 需要使用多重继承。
使用抽象类:
- 需要在几个相关的类中共享代码。
- 需要能控制继承来的成员变量和成员方法的访问权限,而不是都为 public。
- 需要继承非静态、非常量字段。
3. static和final关键字
① static关键字
静态成员变量
- 使用static关键字修饰的成员变量称为静态成员变量,又称为类变量。
- 这个变量属于类的,当类初次被加载时为静态成员变量分配内存。因此,静态成员变量在内存中只有一份。
- 类所有的实例都共享静态变量,访问方式:
类.静态变量
和对象.静态变量
。 - 关于初始化:可以在定义时初始化,也可以在构造函数中初始化。
- 静态变量的使用目的:
对象之间共享数据
,方便访问。如count计数器一般都设置为静态成员变量。
实例变量
- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
- 实例变量的访问方式:
对象.实例变量
。
静态成员方法
- 使用static关键字修饰的成员方法,又称类方法。静态方法在
类初次加载时就存在
,它不依赖于任何实例。 - 因此,静态成员方法不能是抽象方法
- 访问方式:
类.静态方法
和对象.静态方法
。 - 静态成员方法只能访问所属类的静态变量和静态方法,不能访问所属类的实例变量和实例方法。而实例方法能访问静态变量和静态方法。
- 静态成员方法中不能使用用this和super关键字。
静态语句块
-
使用static关键字修饰的语句块,叫做静态语句块。
static { System.out.println("123"); }
-
静态语句块可以置于类中的任何地方,并且可以有多个静态语句块。在类初次被加载时,会按照static块的顺序来执行每个静态语句块,并且只会执行一次。
-
静态语句块只会在类加载时执行一次,因此可以优化程序性能。
普通语句块
-
类实例化时被执行,可以有多个普通语句块,按照在代码中的先后顺序执行。格式如下:
{ System.out.println("普通语句块"); }
静态类
- 静态类只能是内部类。
② final关键字
final变量
-
final关键字修饰的变量表示常量,只能被赋值一次,赋值后值不再改变。
-
final变量在在使用前必须初始化,否则不能通过编译:① 定义时就初始化;② 也可以定义空白final,在构造函数中初始化。
// 定义时就初始化 public final int x=0; // 空白final public final int y; public Shape() { this.y = y; }
-
对于基本数据类型,final 使数值不变。
-
对于引用数据类型,final 使引用不变,即不能引用其它对象。但是引用对象内部的数据若不是final型,可以进行修改。
final方法
- final关键字修饰的方法不能被子类方法重写,但可以被继承。
- private关键字修饰的方法默认是final类型,不能被子类方法重写。
final类
- final关键字声明的类不能被继承,没有子类。
- final类的变量规则与普通类的变量规则一致,但是所有的方法法默认为final方法。
③ final与static的结合
- 对于变量,若使用static final修饰,表示
一旦赋值不能修改
,并且通过类名可以访问
。 - 对于方法,若使用static final修饰,表示该
方法不可被覆盖
,并且可以通过类名直接访问
。 - static final修饰的变量,必须在定义时初始化或通过静态语句块初始化
自己的看法:
- static修饰的变量,表示变量是全局的。它可以在定义时初始化或静态语句块中初始化(即类加载时初始化),否则将赋予默认值。
- 静态变量可以在普通语句块、构造函数、成员方法中,更新静态变量的值
- final修饰的变量,表示该变量不可变。它可以在定义时或构造函数中初始化,一旦初始化,无法修改
- static和final同时修饰,它在类加载时就应该被初始化,并且之后不再更新。
- 通过构造函数、普通语句块、成员方法等初始化static final 变量,都是在试图修改其值,这是不被允许的