面向对象的三大基本特征:封装、继承、多态
1.封装
封装最好理解了。封装是面向对象的特征之一,是对象和类概念的主要特性。
封装,就是把事物的属性和行为包装到对象中,这个对象只对外公布需要公开的属性和行为,而这个公布也是可以有选择性的公布给其他对象。
在Java中能使用private
、protected
、public
三种修饰符或不用(默认defalut
)对外部对象访问该对象的属性和行为进行限制。
2.继承
继承是面向对象的基本特征之一,继承机制允许创建分等级层级的类。继承就是子类继承父类的特征和行为,使得子类对象具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
Java使用extends
关键字来实现继承:
class Person {
private String name;
private int age;
public String getName() {...}
public void setName(String name) {...}
public int getAge() {...}
public void setAge(int age) {...}
}
class Student extends Person {
// 不要重复name和age字段/方法,
// 只需要定义新增score字段/方法:
private int score;
public int getScore() { … }
public void setScore(int score) { … }
}
当Student
继承Person
时,Student
就获得了Person
的所有功能,我们只需要为Person
编写新的功能就好。
可见,通过继承,Student
只需要编写额外的功能,不再需要重复代码。
在面向对象编程(OOP)
中,我们把Person
称为超类(super class)、父类(parent class)、基类(base class),把Student
称为子类(subclass),扩展类(extended class)。
继承树
在定义类的时候,凡是没有明确写extends
的类,也就是没有继承于别的类的,编译器会自动加上extends Object
。所以,任何类,除了Object
,都会继承自某个类。下图是Person
、Student
的继承树:
Java只允许ckass继承一个类,一个类有且只有一个父类。只有Object
特殊,它没有父类。
protected
继承有个特点,就是子类无法访问父类的private
修饰的属性或者private
修饰的方法。
这使得继承的作用被削弱了。为了让子类可以访问到父类的字段,我们可以把private
修饰的属性或方法改为protected
。用protected
修饰的属性可以被子类访问:
class Person {
protected String name;
protected int age;
}
class Student extends Person {
public String hello() {
return "Hello, " + name; // OK!
}
}
protected
关键字可以把属性和方法的访问权限控制在继承树内部,一个protected
属性和方法可以被其子类,以及子类的子类所访问。
super
super
关键字表示父类(超类)。子类引用父类的字段时,可以使用super.filedName
。例如:
class Student extends Person {
public String hello() {
return "Hello, " + super.name;
}
}
实际上,这里使用super.name
,或者this.name
,或者name
,效果都是一样的。编译器会自动定位到父类的name
字段。
但是在某些时候,就必须使用super
。举个栗子:
public class Main {
public static void main(String[] args) {
Student s = new Student("Xiao Ming", 12, 89);
}
}
class Person {
protected String name;
protected int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
this.score = score;
}
}
运行上面的代码,会得到一个编译错误,大概是说在Student
的构造方法中,无法调用Person
的构造函数。
因为在Java中,任何class
的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确的调用父类的构造方法,编译器会自动帮我们加上一句super();
(表示自动调用父类的构造方法),所以Student
类的构造方法实际上是这样的:
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
super(); // 自动调用父类的构造方法
this.score = score;
}
}
但是因为Person
类中没有无参的构造方法,所以会编译失败。
解决办法是调用Person
类存在的某个构造方法。例如:
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
super(name, age); // 调用父类的构造方法Person(String, int)
this.score = score;
}
}
这样就可以正常编译了!
因此我们得出结论:如果父类没有默认的构造方法,子类就必须显式调用super()
并给出参数以便让编译器定位到父类的一个合适的构造方法。
子类不会继承父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。
3.多态
同一操作作用于不同的对象,可以有不同的解释,产生不同的结果,这就是多态。一种接口多种实现。
实现多态有两种方式: 重写(Override
)与重载(Overload
)
重写(Override)
重写(Override)
:发生在继承关系中,子类如果定义了一个与父类方法名、形参完全相同的方法,被称为重写。
子类方法重写父类方法的原则
方法名、形参、返回值相同。
子类的方法权限比父类的大或者相等。
如果子类中的方法与父类中的某一方法具有相同的方法名、返回值类型和参数,则新方法将覆盖原有的方法。如果需要使用父类中原有的方法,可以用super
关键字调用,super
关键字引用了当前类的父类。
方法名相同,方法参数相同,但方法返回值不同,也是不同的方法。在Java程序中,出现这种情况,编译器会报错。
在重写方法上,加上@Override
可以让编译器帮助检查是否正确重写了父类的方法。(@Override
不是必要的)
调用super
在子类的重写方法中,如果要调用父类的被重写的方法,可以通过super
来调用。例如:
class Person {
protected String name;
public String hello() {
return "Hello, " + name;
}
}
Student extends Person {
@Override
public String hello() {
// 调用父类的hello()方法:
return super.hello() + "!";
}
}
final
继承可以允许子类重写父类的方法。如果一个父类不允许子类对它的某个方法进行重写,可以把该方法标记为final
。用final
修饰的方法不能被重写:
class Person {
protected String name;
public final String hello() {
return "Hello, " + name;
}
}
Student extends Person {
// compile error: 不允许覆写
@Override
public String hello() {
}
}
如果一个类不希望任何其他类继承它,也可以把这个类标记为final
。
final
还能用来修饰一个类的属性,用final
修饰的属性在初始化后不能被修改。且对final
修饰的属性重新赋值会报错。
重载(Overload)
重载(Overload)
:方法重载是让类以统一的方式处理不同数据类型的一种手段。多个同名函数同时存在,具有不同的参数个数或者不同的参数类型。
Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义。
调用方法时通过传递给它们的不同参数个数和参数类型来决定具体使用哪个方法,这就是多态性。
重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不相同。无法以返回值作为重载函数的区分标准。