面向对象三大基本特征:封装、继承、多态
一、封装
1、概念
随着我们系统越来越复杂,类会越来越多,那么类之间的访问边界必须把握好,面向对象的开发原则要遵循“高内聚、低耦合”,而“高内聚,低耦合”的体现之一:
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合:仅对外暴露少量的方法用于使用
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的讲,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
最贴合代码的解释:将类的细节隐藏起来,通过 private 修饰符阻止外界访问。通过给 private 修饰的成员提供 public 的访问方法类实现访问。
2、实现步骤
(1)使用 private
修饰成员变量
private 数据类型 变量名 ;
(2)代码实现
public class Student{
private String stuName;
private int stuAge;
// 当类中拥有显示的构造方式,手动编写无参的构造方法。
public Student(){
}
public Student(String stuName, int stuAge) {
this.stuName = stuName;
this.stuAge = stuAge;
}
//提供公共的get/set方法,方便外部类调用
public String getStuName() {
return stuName;
}
public void setStuName(String stuName){
// 局部变量可以和属性重名。局部变量拥有更高的优先级。
// 局部变量和属性重名的时候,想访问属性,需要在属性前加this.
this.stuName = stuName;
}
public int getStuAge(){
return stuAge;
}
public void setStuAge(int stuAge){
this.stuAge = stuAge;
}
}
public class TestStudent { //测试类
public static void main(String[] args) {
Student stu = new Student("Tom",20); //创建对象时利用构造方法直接赋值
Student stu1 = new Student();
stu1.setStuName("jack");
stu1.setStuAge(21);
System.out.println(stu.getStuName()); //Tom
System.out.println(stu.getStuAge()); //20
System.out.println(stu1.getStuName()); //jack
System.out.println(stu1.getStuAge()); //21
}
}
运行结果为:
二、继承
1、继承的由来
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系。如图所示:
其中,多个类可以称为子类,也叫派生类;多个类抽取出来的这个类称为父类、超类(superclass)或者基类。
继承描述的是事物之间的所属关系,这种关系是:is-a
的关系。例如,图中猫属于动物,狗也属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。
2、继承的好处
- 提高代码的复用性。
- 提高代码的扩展性。
- 类与类之间产生了关系,是学习多态的前提。
3、继承的格式
通过 extends
关键字,可以声明一个子类继承另外一个父类,定义格式如下:
【修饰符】 class 父类 {
...
}
【修饰符】 class 子类 extends 父类 {
...
}
4、代码实现
// 宠物类(父类)
public class Pet {
private String name;
private int age;
private String type;
public Pet() {
}
public Pet(String name, int age, String type) {
this.name = name;
this.age = age;
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public void play(){
System.out.println("和主人玩儿");
}
}
//Dog类(Pet 的子类)
public class Dog extends Pet{
private String gender;
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public void work(){ //Dog 本类的方法
System.out.println("工作");
}
@Override
public void play() { //重写父类 Pet 的 play() 方法
//super.play();
System.out.println("玩儿丢飞盘~~~~~~~~~~~~~~~~~~~~~~");
}
}
//Cat类(Pet 的子类)
public class Cat extends Pet {
// 子类编写一个跟父类方法签名一模一样的方法,此时叫做子类重写父类的方法
// 方法重写发生在父子类中,子类和父类的方法签名一致。
@Override
public void play() { //重写父类 Pet 的 play() 方法
//super.play();// super关键字在子类中代指父类对象。this在子类中代指子类对象
System.out.println("玩儿毛线球~~~~~~~~~~~~~~~~");
}
}
//测试类
public class TestOne {
public static void main(String[] args) {
Cat cat = new Cat();
cat.setName("小白");
cat.setAge(4);
cat.setType("波斯猫");
cat.play(); //子类一旦重写了父类的方法,再调用时:一定是调用子类重写的方法,体现子类的特征。 //玩儿毛线球~~~~~~~~~~~~~~~~
Dog dog = new Dog();
dog.play(); //子类一旦重写了父类的方法,再调用时:一定是调用子类重写的方法,体现子类的特征。 //玩儿丢飞盘~~~~~~~~~~~~~~~~~~~~~~
dog.setGender("公的"); //公的
System.out.println(dog.getGender());
dog.work(); //工作
}
}
结果为:
继承总结:
① 当一个类显示的使用extends关键字继承自另一个类的时候,
② 这个类就有一个显示的父类。Cat继承自Pet,Cat是子类,Pet是父类。
③ 当一个子类继承自一个父类的时候,可以继承到父类所有的属性和方法,不包括构造方法。
④ java语言中使用单继承,一个java类只能有一个父类。
⑤ java语言中的继承是遗传性的,爷爷可以通过父亲把属性或者方法遗传给孙子。
⑥ 当一个类显示的继承自另一个类的时候,这个类有一个显示的父类。
⑦ 当一个类没有显示的继承的时候,它默认继承自Object。Object是所有Java类的总父类。
5、继承的特点:
(1)特点一:成员变量
①父类成员变量私有化(private)
i 父类中的成员,无论是公有(public)还是私有(private),均会被子类继承。
ii 子类虽会继承父类私有(private)的成员,但子类不能对继承的私有成员直接进行访问,可通过继承的get/set方法进行访问。如图所示:
②父子类成员变量重名
i 当父类的成员变量私有化时,在子类中是无法直接访问的,所以是否重名不影响,如果想要访问父类的私有成员变量,只能通过父类的get/set方法访问;
ii 当父类的成员变量非私有时,在子类中可以直接访问,所以如果有重名时,就需要加“super."进行区别。
使用格式:
super.父类成员变量名
(2)特点二:成员方法—方法重写
我们说父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于子类时,我们可以进行方法重写 (Override)
注意事项:
i @Override:写在方法上面,用来检测是不是有效的正确覆盖重写。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留
ii 必须保证父子类之间方法的名称相同,参数列表也相同。
iii 子类方法的返回值类型必须【小于等于】父类方法的返回值类型(小于其实就是是它的子类,例如:Student < Person)。
注意:如果返回值类型是基本数据类型和void,那么必须是相同
iiii 子类方法的权限必须【大于等于】父类方法的权限修饰符。
小扩展提示:public > protected > 缺省 > private
iiiii 几种特殊的方法不能被重写
- 静态方法不能被重写
- 私有等在子类中不可见的方法不能被重写
- final方法不能被重写
(3)特点三:构造方法
i 构造方法的名字是与类名一致的。
所以子类是无法继承父类构造方法的。
ii 构造方法的作用是初始化实例变量的,而子类又会从父类继承所有成员变量
所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super()
,表示调用父类的实例初始化方法,父类成员变量初始化后,才可以给子类使用。
问:如果父类没有无参构造怎么办?
答:此时子类代码报错。
解决办法:在子类构造器中,用super(实参列表),显示调用父类的有参构造解决。
结论:
子类对象实例化过程中必须先完成从父类继承的成员变量的实例初始化,这个过程是通过调用父类的实例初始化方法来完成的。
i super():表示调用父类的无参实例初始化方法,要求父类必须有无参构造,而且可以省略不写;
ii super(实参列表):表示调用父类的有参实例初始化方法,当父类没有无参构造时,子类的构造器首行必须写super(实参列表)来明确调用父类的哪个有参构造(其实是调用该构造器对应的实例初始方法)
iii super()和super(实参列表)都只能出现在子类构造器的首行
(4)特点四:单继承限制
i Java只支持单继承,不支持多继承。
//一个类只能有一个父类,不可以有多个父类。
class C extends A{} //正确
class C extends A,B... //error
ii Java支持多层继承(继承体系)。
提示:顶层父类是Object类。所有的类默认继承Object,作为父类。
class A{}
class B extends A{}
class C extends B{}
iii 子类和父类是一种相对的概念。
例如:B类对于A来说是子类,但是对于C类来说是父类
iiii 一个父类可以同时拥有多个子类
三、多态
1、多态的概念
任何一个需要父类引用的位置,都可以传递一个子类对象。
2、定义格式
父类类型 变量名 = 子类对象;
3、多态的实现步骤
(1)有继承,有父子类。
(2)父类定义方法,子类重写方法。
(3)父类的引用,子类的对象。
4、代码实现
class Father{
public void methodOne(){
System.out.println("父类的methodOne");
}
}
class Son extends Father{
public void methodOne(){
System.out.println("子类重写的methodOne");
}
}
class Test{
public static void main(String[] args){
Father f = new Son();// 创建对象多态
f.methodOne();// 子类一旦重写父类方法,再次调用的一定是子类重写的方法。
// Son son = new Son();
methodTwo(new Son());// 参数定义是父类类型,传递参数时传递子类对象。
//多态数组
Father[] nums = new Father[5];// 声明一个父类类型的对象数组
nums[0] = new Son();// 为数组元素赋值时传递子类对象。
nums[1] = new Son();
nums[2] = new Son();
nums[3] = new Son();
nums[4] = new Son();
}
public static void methodTwo(Father f){// 方法定义的参数是父类类型参数。
f.methodOne();
}
}
5、编译时类型与运行时类型不一致问题
Pet pet = new Pet();
编译时 运行时
编译时,看“父类”,只能调用父类声明的方法,不能调用子类扩展的方法;
运行时,看“子类”,一定是执行子类重写的方法体;