封装、继承和多态
封装
封装(Encapsulation)是面向对象的三大特征之一(另外两个是继承和多态),它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。
访问控制符
Java 提供了 3 个访问控制符:private、 protected 和 public ,代表 3 种不同的访问级别,再加上一个默认的访问控制级别(不使用任何访问控制符),共有 4 个访问控制级别。
-
private(当前类访问权限):类中的一个的成员被 private 修饰,它只能在当前类的内部被访问;
-
default(包访问权限):类中的一个成员或者一个外部类不使用任何访问控制符修饰,它能被当前包下其他类访问;
-
protected(子类访问权限):类中的一个的成员被 protected 修饰,它既可以被当前包下的其他类访问,又可以被不同包的子类访问;
-
public(公共访问权限):类中的一个成员或者一个外部类使用 public 修饰,它能被所有类访问。
private | default | protected | public | |
---|---|---|---|---|
同一个类中 | ✔ | ✔ | ✔ | ✔ |
同一个包中 | ✔ | ✔ | ✔ | |
子类中 | ✔ | ✔ | ||
全局范围内 | ✔ |
继承
- Java 使用 extends 作为继承的关键字,子类扩展了父类,获得父类的全部成员变量和方法。
- Java 只能单继承,只有一个直接父类,但可以有无限多个间接父类。当一个 Java 类并未显式指定直接父类时,默认继承
java.lang.Object
,因此java.lang.Object
是所有类的直接或间接父类。
重写父类方法
重写父类方法应遵循 “两同两小一大“ 规则:
- “两同” 指方法名相同、形参列表相同;
- “两小” 指子类方法返回值类型和抛出的异常类型应比父类方法的更小或相等;
- “一大” 指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
class B {
public void show() {
System.out.println("B");
}
}
public class A extends B{
@Override
public void show() {
System.out.println("A"); //重写父类方法
}
}
重载(Overload)和重写(Override)区别:
- 重载指的是同一类中多个同名方法;
- 重写指的是子类和父类的同名方法。
super关键字
- 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作;
- 访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
class B {
private int x;
public B(int x) {
this.x = x;
}
public void show() {
System.out.println("x:" + x);
}
}
public class A extends B{
private int x;
public A(int x, int x1) {
super(x1);
this.x = x;
}
@Override
public void show() {
super.show(); //调用被覆盖的父类方法
}
public static void main(String[] args) {
A a = new A(1, 2);
a.show(); //x:2
}
}
父类构造器
子类继承了父类的全部变量和方法,所以实例化子类时,必须先将其父类实例化。调用父类构造器的方式是 super(),参数为父类构造器所需参数。使用 super 调用父类构造器必须出现放在子类构造器的第一行,而 this 调用同一个类中重载的构造器也要放在第一行,所以 super() 和 this() 不能同时出现。
不管是否使用 super 显式调用父类构造器,子类构造器总会调用父类构造器一次,总共会出现三种情况:
- 子类构造器第一行使用 super 显式调用父类构造器;
- 子类构造器第一行使用 this 调用重载的子类构造器,在本类重载的构造器中调用父类构造器;
- 子类构造器第一行既没有 super 调用,也没有 this 调用,系统在第一行隐私调用父类无参构造器。
多态
多态:相同类型的变量调用同一个方法时呈现出多种不同的行为特征。
产生原因:Java 允许把一个子类对象直接赋给一个父类引用变量,无须任何类型转换。当把一个子类对象赋给父类引用变量时,会出现编译类型和运行类型不一致的情况,此时调用子类和父类的同名方法时(这里的同名指的是子类重写了父类方法),总是表现出子类方法的行为特征。例如:B b = new A()
编译类型看左边,运行类型看右边,因此编译类型为 B,运行类型为 A,当 b 调用 A 和 B 的同名的方法时,运行的总是 A 中的方法。
class B {
public String book = "B";
public void base() {
System.out.println("父类普通方法");
}
public void test() {
System.out.println("父类被覆盖的方法");
}
public String getBook() {
return book;
}
}
public class A extends B{
public String book = "A";
@Override
public void test() {
System.out.println("子类覆盖父类方法");
}
public void sub() {
System.out.println("子类普通方法");
}
@Override
public String getBook() {
return book;
}
public static void main(String[] args) {
B b = new B();
System.out.println(b.book); //B
b.base(); //父类普通方法
b.test(); //父类被覆盖的方法
System.out.println(b.getBook()); //B
A a = new A();
System.out.println(a.book); //A
a.base(); //父类普通方法
a.test(); //子类覆盖父类方法
System.out.println(a.getBook()); //A
//编译看左边,运行看右边,编译和运行不一致
B b1 = new A();
//访问的是父类的属性,与方法不同,实例变量不具有多态性
System.out.println(b1.book); //B
//访问父类继承的方法
b1.base(); //父类普通方法
//访问的是子类的同名方法
b1.test(); //子类覆盖父类方法
//B没有提供sub()方法,就算A有,也无法通过编译
//b1.sub(); //错误
System.out.println(a.getBook()); //A
}
}
当代码运行 B b1 = new A()
时,编译类型为 B,运行类型为 A。
当调用 b1.test()
方法时(B 中有 test() 方法,A 中将其覆盖了),实际运行的是 A 的 test() 方法,方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征,这就是多态。
当执行 b1.base()
方法时,因为子类继承了父类的该方法,并且没有重写,所以运行一致。
当执行 b1.sub()
方法时,由于父类没有 sub() 方法,而编译时类型为父类,所以无法通过编译。
当执行 b1.book
获取同名实例变量时,返回的是父类的实例变量,与方法不同,对象的实例变量则不具备多态性,返回的数据看编译时的类型。
当执行 b1.getBook()
方法时,实际运行的是 A 的 getBook() 方法 ,内部访问的实例变量是 A 的,方法是表现出多态特性。
注意:只有调用子类重写父类的方法才表现出多态性,直接访问公开的同名实例变量时不表现多态性,返回的数据看左边类型(编译类型)。