Java面向对象程序设计(2)封装,继承和多态
封装,继承和多态
面向对象的三大特征是:封装,继承和多态
访问修饰符
java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围) :
访问级别 | 访问控制修饰符 | 同类 | 同包 | 子类 | 不同包 |
---|---|---|---|---|---|
公开 | public | √ | √ | √ | √ |
受保护 | protected | √ | √ | √ | × |
默认 | 无需修饰符 | √ | √ | × | × |
私有 | private | √ | × | × | × |
封装
封装是把抽象出的数据 (属性) 和对数据操纵的 方法 封装再一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(方法)才能对数据进行操作
封装实现的步骤:
- 将属性进行私有化
- 提供公共的 set 方法用于对属性进行赋值
- 提供公共的 get 方法用于获得属性的值
set方法与构造器结合
当我们使用构造器进行初始化时,属性的赋值有一定要求(要求在set方法中体现),为了减少冗余的的代码,可以使用set方法在构造器内部初始化对象
继承
当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。 继承可以解决代码复用的问题。
基本语法
class 子类 extends 父类{
}
- 子类会自动获得父类的属性和方法
- 父类又叫基类,超类
- 子类又叫派生类
继承的规则
- 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问。
- 子类在用构造器初始化时,会先调用父类的构造器,完成父类的初始化
- 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过
- super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
- java 所有类都是 Object 类的子类, Object 是所有类的基类,父类构造器的调用将一直追溯到顶级父类(Object类)
- Java是单继承机制。一个类只能继承一个父类
super关键字
使用super可以访问父类的属性,方法和构造器
访问属性和方法:
super.属性名,super.方法名(参数列表),super不能访问父类的私有属性和私有方法,当子类和父类的属性和方法重名时,如果想要访问父类的成员,必须使用super关键字,没有重名时,使用super,this和直接访问的作用是一样的。
访问构造器:
使用super调用父类的构造器能给编程带来便利:父类的属性调用父类构造器进行初始化,而子类的属性交给子类构造器初始化。
super和this的比较
区别点 | this | super |
---|---|---|
访问属性和方法 | 从本类开始向上查找 | 从父类开始向上查找 |
调用构造器 | 调用本类的构造器 | 调用父类的构造器,子类都会隐式的调用super |
意义 | 表示当前对象 | 表示父类对象 |
方法重写/覆盖
当我们要实现子类的一个方法,它与父类的某个方法有类似的作用,我们让这个方法与父类的方法具有相同的函数名,返回类型和参数列表,于是子类的这个方法覆盖了父类的方法,或者说我们在子类中重写了父类的这个复方
注意:
- 子类的形参列表和方法名称必须和父类完全一样,返回类型可以一样,也可以是父类返回类型的子类
- 子类方法不能缩小父类方法的访问权限
多态
多态是指方法和对象具有多种状态,多态建立在继承和封装的基础上
方法的多态
方法重载和方法覆盖体现了方法的多态,对于方法重载,同一个方法名,我们传入不同的参数,它调用不同的方法。对于方法重写,同一个方法名,当对象是父类对象时调用父类方法,子类会调用子类的方法
对象的多态
对象的多态体现一个父类对象变量既可以引用父类对象也可以引用子类对象,所以多态的前提是存在继承关系,深入理解对象的多态要先理解以下几句话。
- 对象的编译类型和运行类型可以不一致
- 编译类型是指编译器在编译.java文件为.class文件时(javac命令),认定对象的的状态,运行类型是指在运行java文件时(java命令),对象实际的状态
- 用new操作符和构造器初始化对象时,右边的是编译类型,左边的是运行类型,编译类型不可变,但运行类型是可以改变的,这体现了多态
Animal animal = new Cat();//编译类型为animal,运行类型Cat
animal = new Dog()//运行类型发生变化,为Dog
向上转型
当父类的引用指向了子类的对象就发生了向上转型,使用向上转型可以减少冗余的代码。当多个类具有相同的方法,可以在父类中写一个方法,参数设置为父类对象,当调用这个方法时可以使用向上转型,那么它在运行时实际还是把这个对象看出子类对象。
语法:父类类型 对象名 = new 子类类型()
public class Test {
public static void main(String[] args) {
Car car1 = new Benz()
show(car1);
Car car2 = new BMW()
show(car2);
}
}
class Car {
public void run() {
System.out.println("这是父类run()方法");
}
public void speed() {
System.out.println("speed:0");
}
public void show(Car car) {//父类实例作为参数
car.run();
car.speed();
}
}
class BMW extends Car {
public void run() {
System.out.println("这是BMW的run()方法");
}
public void speed() {
System.out.println("speed:80");
}
}
class Benz extends Car {
public void run() {
System.out.println("这是Benz的run()方法");
}
public void speed() {
System.out.println("speed:100");
}
public void price() {
System.out.println("prict:18w");
}
}
如上面的示例,如果没有使用向上转型,那么如果想要输出每个子类的信息就需要在每个子类中写对应的show()方法。但同时向上转型存在一个问题:不能调用子类的特有方法,这是在编译过程中,这个对象被看作父类对象,而父类对象没有这个方法,当调用父类和子类共同的方法时,看对象的运行类型。
向下转型
如前面向上转型提到的问题:不能调用子类特有方法,于是出现了向下转型,向下转型是强制类型转换,子类对象引用了父类类型。进行向下转型只改变了这个对象的引用而没有改变对象,父类的引用必须指向当前目标类型的对象,进行向下转型后就可以使用子类的全部成员
语法:子类类型 引用名 = (子类类型) 父类引用
//接上面的java类
public class Test {
public static void main(String[] args) {
Car car1 = new Benz();
show(car1);
Benz benz = (Benz) car1;
benz.price()//向下转型调用子类特有方法
// BMW benz = (BMW) car1; 这一句会报错,因为当前这个对象指向的Benz类型的对象
}
}
动态绑定机制
Java的动态绑定机制可以由以下两句话总结
- 当调用对象方法时,方法会和对象的运行类型绑定
- 调用属性对象时,没有绑定机制,属性在那里声明就在那里使用
看一个例子
public class DynamicBinding {
public static void main(String[] args) {
//a 的编译类型 A, 运行类型 B
A a = new B();//向上转型
System.out.println(a.sum());//输出30
System.out.println(a.sum1());//输出20
}
}
class A {//父类
public int i = 10;
//动态绑定机制:绑定B类的getI()方法
public int sum() {
return getI() + 10;//20 + 10
}
public int sum1() {
return i + 10;//使用A类的 i = 10,10 + 10
}
public int getI() {
return i;
}
}
class B extends A {//子类
public int i = 20;
public int getI() {//子类 getI()
return i;//使用B类的 i = 20
}
}
多态的应用
多态的应用由多态数组和多态参数,它们能够很好的解决代码复用的问题
instanceof关键字
语法:对象变量 instanceof 对象类型
instanceof 关键字可以用来判断某个对象的运行类型是否是目标类型,使用instanceof关键字和向下转型还可以方便的调用子类的特有方法。
多态数组
数组定义为父类,但是里面的元素可以是父类对象也可以是子类对象,这样可以方便的遍历数组,对不同的对象做相似的处理。
类定义:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String say(){
return name + "\t" + age;
}
public String getName() {
return name;
}
}
class Student extends Person{
private int score;
public Student(String name, int age, int score) {
super(name, age);
this.score = score;
}
@Override
public String say(){
return super.say() + "成绩是:" + score;
}
//特有方法
public void examine(){
System.out.println(super.getName() + "正在考试ing~~~");
}
}
class Teacher extends Person{
private String major;
public Teacher(String name, int age, String major) {
super(name, age);
this.major = major;
}
@Override
public String say() {
return super.say() + "教授学科是:" + major;
}
//特有方法
public void teach() {
System.out.println(super.getName() + "正在上课ing~~~");
}
测试类:
public class PersonTest {
public static void main(String[] args) {
Person[] person = new Person[3];
person[0] = new Person("jack",23);
person[1] = new Student("Tom",23,80);
person[2] = new Teacher("Milan",23,"Math");
//遍历数组调用say方法,并调用特有方法
for (int i = 0; i < person.length; i++) {
System.out.println(person[i].say());
if(person[i] instanceof Student){//判断运行类型
((Student) person[i]).examine();//向下转型
}else if(person[i] instanceof Teacher){
((Teacher) person[i]).teach();
}
}
}
}
多态参数
方法定义的形参类型是父类类型,实参可以是子类类型,在前面向上转型中其实可以直接把子类实参(BMW对象,Benz对象)传给形参(Car对象)。多态参数的使用非常广泛(在jdk的源码中),它能够大大的提高代码的复用性
Object类
equals()方法
对于基本数据类型,我们可以用 == 运算符来判断值是否相等,但是对于引用类型即对象,== 运算符只能用来判断对象变量是否指向了同一个对象,而如果要判断对象本身即属性的值是否相等就需要用到 equals() 方法。如果类中没有重写 equals() 方法,调用的就是Object类中的方法,它只能判断是否指向同一个引用
@Override
public boolean equals(Object o) {
if (this == o)//指向同一个对象则一定相等
return true;
if (o == null || getClass() != o.getClass())//对象为空或者不是同一个类,则一定不相等
return false;
Employee employee = (Employee) o;//向下转型
return salary == employee.salary && age == employee.age && name.equals(employee.name);
}
hashcode()方法
返回该对象的哈希码值,由object类定义的hashcode()能根据不同的对象返回不同的整数值,这是通过对象的内部地址经过转换成一个整数实现的重写该方法能够提高哈希表的性能。在学习集合部分后再对该方法作补充。
toString()方法
默认返回:全类名+@+哈希值的十六进制。
子类往往重写 toString 方法,用于返回对象的属性信息
public String toString()
{
return "Employee[name=" + name
+", salary=" + salary
+", age=" + age
+"]"
}
finalize()方法
- 当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作【演示】
- 什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用 finalize 方法。
- 垃圾回收机制的调用,是由系统来决定(即有自己的 GC 算法), 也可以通过 System.gc() 主动触发垃圾回收机制。
学习总结来源于韩顺平老师循序渐进学Java