文章目录
- Java 之封装、继承,多态
- 一、封装
- 1.封装的基本介绍
- 2. 封装的实现
- 3. 将构造器与 setXxx 方法结合
- 二、继承
- 1. 继承的基本介绍
- 2. 基本语法
- 3.继承的深入理解
- 4. 继承的本质分析(内存存在形式)
- 5. 子类创建的内存布局
- 6. super 关键字
- 6.1 super 的基本介绍
- 6.2 基本语法
- 6.3 使用时的注意事项
- 6.4 super 带来的好处
- 6.5 super 和 this 的比较
- 7. 方法重写/覆盖(override)
- 7.1 基本介绍
- 7.2 使用方式、细节、注意事项
- 7.3 方法重载和方法重写(覆盖)的比较
- 三、多态
- 1. 基本介绍
- 2. 具体体现
- 2.1 方法的多态
- 2.2 对象的多态
- 3. 多态的使用
- 3.1 多态的向上转型
- 3.2 多态的向下转型
- 3.3 instanceof 比较操作符
- 4. 动态绑定机制
Java 之封装、继承,多态
一、封装
1.封装的基本介绍
封装(encapsulation,有时称为数据隐藏)就是把抽象出的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作(方法),才能对数据进行操作。
实现封装的关键在于,绝对不能让类中的方法直接访问其他类的属性(成员变量)。程序只能通过对象的方法与对象数据进行交互。封装给对象赋予了“黑盒”特征,这是提高重用性和可靠性的关键。这意味着一个类完全可以改变存储数据的方式,只要仍旧使用同样的方法操作数据,其他对象就不会知道也不用关心这个类所发生的变化。
2. 封装的实现
可以分为3步进行实现:
- 将属性进行私有化 privat (即不能直接修改属性)
- 提供一个公共的( public )set 方法,用于对属性判断并赋值。
public void SetXxx(类型 参数名) { //Xxx表示某个属性
//可以添加数据验证的业务逻辑
属性 = 参数名;
}
- 提供一个公共的 ( public )get 方法,用于获取属性的值
public 数据类型 getXxx() {
//可以进行权限判断,Xxx表示某个属性
return xxx;
}
案例分析:
一个 Person 类,有姓名,年龄,工资,职位属性。在主类中设置 Person 类对象的属性,以及查看。
public class Encap {
public static void main(String[] args) {
Person person = new Person();//创建对象
//调用set 方法进行属性的设置
person.setName("熊");
person.setAge(30);
//······
//调用get 方法查看属性
person.getName();
person.getAge();
//·····
}
}
class Person {
private String name;
private int age;
private double salary;
private String job;
public String getName() {
return name;
}
public void setName(String name) {
//控制 name 的长度在2-6字符之间
if (name.length() >= 2 && name.length() <= 6)
this.name = name;
else {
System.out.println("name 的长度必须在2-6个字符之间!!!");
System.out.println("请重新设置!");
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
//对设置的年龄进行判断,是否在1-120之间
//否则设置默认值 18
if (age >= 1 && age <= 120)
this.age = age;
else {
System.out.println("年龄必须在1-120岁之间!!!");
this.age = 18;
}
}
public double getSalary() {
//进行权限控制
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
}
分析:属性是私有的,所以我们不能直接修改、查看它的值,但是在创建对象的过程,调用构造器却可以直接跳过 set 方法,进行值的设置。要解决这个问题只需在构造器中也调用 set 方法。
3. 将构造器与 setXxx 方法结合
//下面只写构造器
public Person(String name, int age, double salary) {
setName(name);
setAge(age);
setSalary(salary);
}
二、继承
继承: 继承的基本思想是,可以基于已有的类创建新的类。继承已存在的类就是复用(继承)这些类的方法,而且可以增加一些新的方法和字段(成员变量)。
1. 继承的基本介绍
当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。
继承的示意图:
2. 基本语法
// class 子类 extends 父类 {
//}
//比如 Pupil 类继承 Student 类
class Pupil extends Student {
}
关键字 extends 表明正在构造的新类派生于一个已存在的类。这个已存在的类称为 超类(superclass)、基类(base class) 或 父类(parent class) ;新类称为 子类(subclass)、派生类(derived class)、或孩子类(child class)。
3.继承的深入理解
-
子类继承了父类所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问。
-
子类必须调用父类的构造器, 完成父类的初始化。
-
当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过(怎么理解。) [举例说明]
import pg1; public Base { private String name; private int age; public Base(String name, int age) { this.name = name; this.age = age; } }
import pg2; public class Sub { //具有父类的所有属性 public Sub() { //如果什么都不写,默认为 super() 即调用父类的无参构造器 super("xiong",12);//调用父类对应参数的构造器 } }
-
如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)。
-
super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)。
-
super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器。
-
java 所有类都是 Object 类的子类, Object 是所有类的基类。
-
父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)。
-
子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制。
思考:如何让 A 类继承 B 类和 C 类? 【A 继承 B, B 继承 C】
-
不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系。
4. 继承的本质分析(内存存在形式)
分析:当子类继承父类,创建子类对象时,内存中到底发生了什么?
public class ExtendsTheory {
public static void main(String[] args) {
Son son = new Son();//内存的布局
//?-> 这时请大家注意,要按照查找关系来返回信息
//(1) 首先看子类是否有该属性
//(2) 如果子类有这个属性,并且可以访问,则返回信息
//(3) 如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息..)
//(4) 如果父类没有就按照(3)的规则,继续找上级父类,直到 Object... System.out.println(son.name);
//返回就是大头儿子
//System.out.println(son.age);//返回的就是 39
//System.out.println(son.getAge());//返回的就是 39
System.out.println(son.hobby);//返回的就是旅游
}
}
class GrandPa { //爷类
String name = "大头爷爷";
String hobby = "旅游";
}
class Father extends GrandPa {//父类
String name = "大头爸爸";
private int age = 39;
public int getAge() {
return age;
}
}
class Son extends Father { //子类
String name = "大头儿子";
}
5. 子类创建的内存布局
6. super 关键字
6.1 super 的基本介绍
super 代表父类的引用,用于访问父类的属性、方法、构造器。
6.2 基本语法
-
访问父类的属性,但是不能访问父类的 private 属性.
//语法 super.属性名;
-
访问父类的方法,但不能访问父类的 private 方法
//语法 super.方法名(参数列表);
-
访问父类的构造器。
//语法 super(参数列表); //只能放在构造器中,并且必须是构造器的第一条语句 //只能出现一句
6.3 使用时的注意事项
-
super 的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用 super 去访问爷爷类的成员。
如果多个基类(上级类)中都有同名的成员,使用 super 访问遵循就近原则。A->B->C
-
this 查找属性或方法规则:
-
先找本类,如果有,则调用。
-
如果没有,则找父类(如果有,并可以调用,则调用) 。
-
如果父类没有,则继续找父类的父类,整个规则,就是一样的,直到 Object 类 。
提示:如果查找方法或属性的过程中,找到了,但是不能访问, 则报错, cannot access 。
如果查找方法的过程中,没有找到,则提示方法或属性不存在。
-
-
super 查找属性或方法的规则:
//例如 class A extends B; class B extends C; //从某种意义上说,C类也是A类的父类, //即A继承了C的属性和方法 //所以 super 会先从 B 类开始查找,没有 //就往C类查找,一直往上,直到 Object 类
-
直接从父类开始查找,如果有,则调用。
-
如果没有,则找上一级类(如果有,并可以调用,则调用)。
-
如果上一级类没有,则继续找上一级类的父类,整个规则,就是一样的,直到 Object 类。
提示:如果查找属性或方法的过程中,找到了,但是不能访问, 则报错, cannot access。
如果查找属性的过程中,没有找到,则提示属性或方法不存在。
-
6.4 super 带来的好处
- 调用父类构造器的好处 (分工明确,父类属性由父类初始化,子类属性由子类初始化)。
- 当子类中有和父类中的成员(属性或方法)重名时,为了访问父类的成员,必须通过 super。如果没有出现重名,使用 super、this、直接访问是一样的效果。
- super 的访问不限于父类,如果爷爷类和本类中有同名的成员,也可以使用super 去访问爷爷类的成员;如果多个基类(上级类)中都有同名的成员,使用 super 访问遵循就近原则。A->B->C, 当然也需要遵守访问权限的规则。
6.5 super 和 this 的比较
No. | 区别点 | this | super |
---|---|---|---|
1 | 访问属性 | 访问本类中的属性,如果本类中没有此属性,则从父类中继续查找 | 从父类开始查找属性 |
2 | 调用方法 | 访问本类中的方法,如果本类没有此方法,则从父类继续查找 | 从父类开始查找 |
3 | 调用构造器 | 调用本类的构造器,必须放在构造器的首行 | 调用父类的构造器,必须放在子类构造器的首行 |
4 | 特殊 | 表示当前的对象 | 子类中访问父类对象 |
7. 方法重写/覆盖(override)
7.1 基本介绍
方法覆盖(重写)就是子类有一个方法和父类的某个方法的名称、返回类型、形式参数一样,那么就称为子类的这个方法覆盖了父类的方法。
7.2 使用方式、细节、注意事项
方法重写(覆盖)需要满足以下几点:
-
子类的方法的形参列表、方法名称要和父类方法的形参列表、方法名称完全一样。
-
子类方法的返回类型和父类方法的返回类型一样,或者子类方法的返回类型是父类方法的返回类型的子类。
//例如 class String extends Object; //即 String 类是 Object 类的子类 //父类 的方法: public Object getInfo(){ return null; } //子类的方法: public String getInfo(){ return null; } //则这2个方法构成方法的重写(覆盖) //因为String 类是 Object 类的子类
-
子类方法不能缩小父类方法的访问权限
public > protect > 默认 > private
7.3 方法重载和方法重写(覆盖)的比较
名称 | 发生范围 | 方法名 | 形参列表 | 返回类型 | 修饰符 |
---|---|---|---|---|---|
重载(overload) | 本类中 | 必须一样 | 类型、个数或者顺序至少有一个不同 | 无要求 | 无要求 |
重写(override) | 父子类中 | 必须一样 | 要相同 | 子类方法的返回类型和父类方法的返回类型一样,或者子类方法的返回类型是父类方法的返回类型的子类。 | 子类方法不能缩小父类方法的访问范围 |
三、多态
1. 基本介绍
一个对象变量可以指示多种实际类型的现象称为 多态 。
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
2. 具体体现
2.1 方法的多态
重写和重载就体现多态。
//重载
public int sum(int a, int b){}
public int sum(int a){}
//调用方法,传入不同的参数调用不同的方法
//体现了多态
sum(1);
sum(3,4);
//重写
class A {
public void say(){}
}
class B extends A {
public void say(){}//重写了A类的方法
}
//调用过程体现了多态
A a = new A();
B b = new B();
a.say();
b.say();
2.2 对象的多态
一些知识点的介绍:
-
一个对象的编译类型和运行类型可以不一致。
-
编译类型在定义对象时,就确定了,不能再更改。
-
运行类型是可以改变的。
-
编译类型看声明时,= 号的左边,运行类型看 = 的右边。
//说明: //Dog extends Animal //Cat extends Animal Animal animal = new Dog(); //animal 的编译类型为 Animal //运行类型为 Dog animal = new Cat(); //animal 的运行类型变成了 Cat, //编译类型没有改变
3. 多态的使用
多态的前提是:两个对象(类)存在继承关系。
3.1 多态的向上转型
-
本质: 父类的引用指向了子类的对象。
-
语法: 父类类型 引用名 = new 子类类型();
Animal animal = new Dog();
-
特点: 编译类型看左边,运行类型看右边。
可以调用父类中的所有成员(包括属性和方法),但是要在遵守访问权限的前提下。
不能调用子类中特有的成员。
Animal animal = new Dog(); //此时,animal 可以调用 Animal 的所有成员,遵循访问权限 //但是,animal 不能调用 Dog 中特有的成员
案例讲解:
public class Ploy {
public static void main(String[] args) {
Animal animal = new Dog();
animal.cry();//可以调用
//animal.dogCry() 不能调用
//因为在编译阶段,能够调用的成员由编译类型决定
//即在 Animal 中没有 dogCry() 方法
//在调用animal.cry() 时,
//将会按照从子类(运行类型)开始查找方法
//然后调用,规则我前面我们讲的方法调用规则一致。
}
}
class Animal {
private String name;
public void cry() {
System.out.println("叫叫叫");
}
}
class Dog extends Animal {
private int age;
public void dogCry() {
System.out.println("汪汪汪");
}
}
- 最终的运行效果看子类(运行效果),即调用方法时,按照从子类(运行类型)开始查找方法 ,然后调用,规则我前面我们讲的方法调用规则一致。
3.2 多态的向下转型
-
语法: 子类类型 引用名 = (子类类型)父类引用;
Dog dog = (Dog)animla;
-
只能强转父类的引用,不能强转父类的对象。
-
要求父类的引用必须指向的是当前目标类型的对象。
Dog dog = (Dog) animla; //即,要求 animal 指向的是 Dog 类型的对象。
-
当向下转型后,可以调用子类类型中所有的成员。
-
属性没有重写一说,属性的值看编译类型。
public class PolyDetail02 { public static void main(String[] args) { //属性没有重写之说!属性的值看编译类型 Base base = new Sub();//向上转型 System.out.println(base.count);// ? 看编译类型 10 Sub sub = new Sub(); System.out.println(sub.count);//? 20 } } class Base { //父类 int count = 10;//属性 } class Sub extends Base {//子类 int count = 20;//属性 }
3.3 instanceof 比较操作符
instanceOf 比较操作符,用于判断对象的 运行类型 是否为 XX 类型或 XX 类型的子类型,如果是则返回 true,否则返回 false。
注意是看运行类型,即具体指向的对象类型。
class Base { //父类
int count = 10;//属性
}
class Sub extends Base {//子类
int count = 20;//属性
}
Base b = new Base();
Sub s = new Sub();
Base b0 = new Sub();
System.out.println(s instanceof Sub);//true
System.out.println(b0 instanceof Sub);//true
System.out.println(b instanceof Sub);//fale
4. 动态绑定机制
Java 的动态绑定机制:
- 当调用对象方法时,该方法会和该对象的内存地址/运行类型绑定。
- 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。