系列文章专栏
文章目录
前言
重点介绍了面向对象的三大特征:封装性、继承性和多态性。
一、封装性
1.1 基本思想
- 程序设计的追求:高内聚,低耦合
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合:仅对外暴露少量的方法用于使用
- 封装性的设计思想:隐藏对象内部的复杂性,只对外公开简单的接口
- 引入
- 问题:通过"对象.属性"对属性进行赋值时受到属性的数据类型和存储范围的制约,但若要给属性赋值加入额外限制条件,无法在属性声明时体现,只能通过方法进行条件的添加
- 解决思路:将类的属性私有化 (private),同时,提供公共的 (public) 方法来获取 (getXxx) 和设置 (setXxx)
- 示例
public class AnimalTest {
public static void main(String []args) {
Animal a = new Animal();
a.age = 1; //属性没有私有化,可以直接调用
//a.legs = 4;//报错:The field Animal.legs is not visible
a.setLegs(6);
a.show();
a.setLegs(-6);
a.show();
System.out.println(a.getLegs());
}
}
class Animal{
String name;
int age;
private int legs; //属性私有化后,类以外无法直接调用,需分别构建方法对其进行设置和获取
//提供属性的设置方法(public)
public void setLegs(int m){
if(m >= 0 && m % 2 == 0){
legs = m;
}else{
legs = 0;//可以设置报异常
}
}
//提供属性的获取方法(public)
public int getLegs(){
return legs;
}
public void show(){
System.out.println("name=" + name + ",age=" + age + ",legs=" + legs)
}
}
- 封装性的体现:① 如上 ② 单例模式 ③ 不对外暴露的私有方法。。。
1.2 实现方法 ---- 权限修饰符
四种权限修饰符: private、default(缺省)、protected、public
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | √ | |||
缺省 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
补充说明
- 修饰类的内部结构(不包括代码块):属性、方法、构造器、内部类:4 种权限都可以使用
- 修饰类:只能使用缺省、public
- 缺省:类只可被同一个包内部的类访问
- public:类可在任意地方被访问
二、继承性
2.1 基本思想
- 引入:多个类中存在相同属性和行为时,将相同属性和行为抽取到单独一个类中,无需再反复定义这些属性和行为,只要继承那个类即可
- 优点:减少了代码的冗余,提高了代码的复用性;便于功能的扩展;为之后多态性的使用,提供了前提
- 格式:
class A extends B{}
- A:子类、派生类、subclass
- B:父类、超类、基类、superclass
- 说明
- 子类 A 继承父类 B 以后,子类 A 就获取了父类 B 中声明的结构:属性、方法。父类声明为 private 的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构,只是因为封装性的影响,使得子类不能直接调用父类的结构
- 子类继承父类以后,还可以声明自己特有的属性或方法,实现功能的拓展,两者之间的关系,不同于子集与集合的关系
- 规定
- 一个类可以被多个类继承,但一个类只能有一个父类(类的单继承性)
- 多层继承:子父类是相对的概念,子类直接继承的父类为直接父类;间接继承的父类为间接父类。子类继承父类后,就获取了直接父类以及所有间接父类中声明的属性和方法
- 补充:若没有显式声明一个类的父类,则此类继承于 java.lang.Object 类,所有的 java 类(除 java.long.Object 类之外)都直接或间接地继承于 java.lang.Object 类,即所有的 java 类具有 java.lang.Object 类声明的功能
- 示例
// 父类:Person类
public class Person{
String name;
private int age;
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("吃饭")
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
}
// 子类:Student类
public class Student extends Person{
String major;
public Student(){
}
public Student(String name,int age,String major){
this.name = name;
setAge(age);
this.major = major;
}
public void show(){
System.out.pringln("name="+name+",age="+getAge())
}
}
// 测试类
public class ExtendsTest {
public static void main(String []args) {
Student s1 = new Student();
s1.eat();
s1.setAge(10);
System.out.println(s1.getAge());
}
}
2.2 拓展一 ---- 方法的重写(override/overwrite)
- 含义:子类继承父类以后,可以对父类中的方法进行覆盖操作,重写以后,当创建子类对象以后,通过子类对象去调用子父类中同名同参数方法时,执行的是子类重写父类的方法
- 规定和格式:
权限修饰符 返回值类型 方法名(形参列表){方法体}
- 约定俗称:子类中叫重写的方法,父类中叫被重写的方法
- 子类重写的方法的方法名和形参列表必须和父类被重写的方法的方法名、形参列表相同
- 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限,特别的,子类不能重写父类中声明为 private 权限的方法
- 返回值类型
- 父类被重写的方法的返回值类型是 void,则子类重写的方法的返回值类型只能是void;
- 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类;
- 父类被重写的方法的返回值类型如果是基本数据类型(比如 double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须是 double)
- 子类方法抛出的异常不能大于父类被重写的方法抛出的异常
- 注意:子类与父类中同名同参数的方法必须同时声明为非 static 的(即为重写),或者同时声明为 static 的(不是重写)。因为 static 方法是属于类的,子类无法覆盖父类的方法
2.3 拓展二 ---- 子类对象实例化
- 从结果上看:子类继承父类以后,就获取了父类中声明的属性或方法,创建子类的对象时,在堆空间中,就会加载所有父类中声明的属性
- 从过程上看:通过子类构造器创建子类对象时,一定会直接或间接的调用其父类构造器,直到调用了 java.lang.Object 类中空参的构造器为止。正因为加载过所有的父类结构,所以才可以看到内存中有父类中的结构,子类对象可以考虑进行调用
- 明确:虽然创建子类对象时,调用了父类的构造器,但自始至终就创建过一个对象,即为new 的子类对象
三、多态性
3.1 基本思想
- 多态性的理解:对象的多态性指父类的引用指向子类的对象(或子类的对象赋值给父类的引用)
Person p2 = new Man();
- 多态的使用:虚拟方法调用(当调用子父类同名同参数方法时,实际调用的是子类重写父类的方法)。在编译期,只能调用父类声明的方法,但在执行期实际执行的是子类重写父类的方法,若编译时类型和运行时类型不一致,就出现了对象的多态性 (Polymorphism)
- 编译时,看左边,看的是父类的引用(父类中不具备子类特有的方法)
- 运行时,看右边,看的是子类的对象(实际运行的是子类重写父类的方法)
- 多态性的使用前提:类的继承关系;方法的重写
- 对象的多态性:只适用于方法,不适用于属性(编译和运行都看左边)
- 示例
//父类
public class Person{
String name;
int age;
public void eat(){
System.out.println("人,吃饭");
}
public void walk(){
System.out.println("人,走路");
}
}
//子类1
public class Woman extends Person{
boolean isBeauty;
public void eat(){
System.out.println("女生爱吃甜品");
}
public void walk(){
System.out.println("女生走路慢");
}
}
//子类2
public class Man extends Person{
boolean isSmoking;
public void eat(){
System.out.println("男生爱喝酒");
}
public void walk(){
System.out.println("男生走路快");
}
public void earnMoney(){
System.out.println("男生挣钱");
}
}
}
//测试类
public class PersonTest {
public static void main(String []args) {
Person p1 = new Woman(); //多态性
p1.eat(); //调用的是子类重写父类的方法
person p2 = new Man(); //多态性
PersonTest p3 = new PersonTest(); //多态性的优点体现
p3.func(new Woman());
p3.func(new Man());
}
public void func(Person person){
person.eat();
person.walk();
}
}
3.2 虚拟方法的补充
重载和重写的区别
- 从定义的角度看
- 重载:在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,是一个类中多态性的一种表现
- 重写:在子类中定义某方法与其父类有相同的名称和参数,子类的对象使用这个方法时,将调用子类中的定义,是父类与子类之间多态性的一种表现
- 从编译和运行的角度看
- 重载:早绑定或静态绑定,在方法调用之前,编译器就已经确定了所要调用的方法
- 多态:晚绑定或动态绑定,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法
3.3 向下转型
- 引入
- 问题:因为对象的多态性,编译时只能调用父类中声明的属性和方法,子类的属性和方法不能调用
- 解决:使用强制类型转换符,也可称为向下转型,进而调用子类所特有的属性和方法
person p2 = new Man(); //多态性
Man m1 = (Man) p2; //向下转型
m1.earnMoney(); //调用子类所特有的属性和方法
- 注意事项
- 问题:使用强转时,可能出现 ClassCastException 异常
- 解决:使用 instanceof 关键字。进行向下转型之前,先进行 instanceof 的判断,返回 true 则向下转型;返回 false 则不进行
- instanceof 的说明:a instanceof A,判断对象a是否是类A的实例,是则返回 true。若类B是类A的父类,a instanceof A返回 true,那么 a instanceof B也返回 true
if (p2 instanceof Man) {
Man m2 = (Man) p2;
m2.earnMoney();
System.out.println("*********Man************");
}
if (p2 instanceof Person) {
System.out.println("***********Person************");
}
if (p2 instanceof Object) {
System.out.println("***********object************");
}