注册 登录
Java基础教程

第一章: 开启Java学习之旅

第二章: 掌握计算机基础知识

第三章: 掌握命令行基础知识

第四章: 我的第一个Java程序

第五章: Java编程必备基础

第六章: Java编程的核心:控制结构

第七章: Java面向对象基础

第八章: Java面向对象进阶

第九章: Java字符串类型

第十章: Java数组与数据结构

第十一章: Java高级数据结构

第十二章: Java并发编程基础

首页 > Java基础教程 > 第八章: Java面向对象进阶 > 8.1节: 类的封装与继承

8.1节: 类的封装与继承

薯条老师 2021-11-30 10:34:03 161056 0

编辑 收藏

广州番禺Python, Java小班周末班培训

第四期线下Java, Python小班周末班已经开课了,培训的课程有Python爬虫,Python后端开发,Python办公自动化,Python大数据分析, Java中高级工程师培训。授课详情请点击:http://chipscoco.com/?cate=6    

8.1.1 类的封装

封装的一个很重要的性质,就是藏起来。在面向对象的程序设计中,需将数据类型的属性以及方法的实现细节藏起来。将属性与方法全部暴露给客户端,有违面向对象的设计思想。抽象一个数据类型时,最佳实践是将内部属性隐藏起来,只对外提供对属性进行操作的方法。

我们在使用电脑时,看不到电脑的内部构造,也无需知道电脑的内部构造和运行机制,只需通过电脑提供的外部接口来操作。电脑的内部构造,就好比是类或对象的属性,电脑的运行机制就好比是方法的实现细节。电脑的内部构造和实现细节对用户来说是透明的,因为设计师通过封装隐藏了电脑内部的实现细节和复杂性。

第七章讲到了访问控制符,在定义类/对象的属性和方法时,加上一个protected或private控制符就可以让属性和方法藏起来。属性或方法被隐藏以后,其它类是无法直接访问的,只能通过类提供的公有方法来访问。

代码实例-通过公有方法访问私有属性

class Person{
    private int deposit;
    // 构造函数
    public Person(int deposit){
        this.deposit = deposit;
    }
    
    
    // 定义get_deposit方法来获取存款
    public int getDeposit(){
        return this.deposit;
    }
}

 
public class HelloJava{
    public  static void main(String[] args) {
        // 通过new来实例化一个Person对象,实参1000对应的是形参deposit
        Person kitty = new Person(1000);
        // 通过公有方法来方法问kitty对象的私有属性deposit
        System.out.println(kitty.getDeposit());
    }
 
}

读者请注意,这里的公有方法并非单指用public修饰的方法。不加任何访问控制符时,此时的方法对同一个包中的所有类都是可见的。在定义类时,读者应该秉持这样的设计原则,凡是需要隐藏起来的就不应该暴露给客户端,比如类属性,对象属性,或特定的只供内部使用的方法。

8.1.2 类的继承

通过类的继承,可以实现代码的复用。假设有两个类A,B, B类继承自A类,那么A类就是B类的父类或基类,B类是A类的子类或派生类。

为什么继承可以实现代码的复用?因为子类或派生类通过继承获得了父类或基类的属性和方法。

Java类继承的语法:

[modifier] Child extends Parent{
    ;    
}

modifier表示类的访问控制符,不再赘述。Child表示子类,extends是Java中的关键字,用来表示继承关系,Parent则表示父类。

在Java中,Object是所有类型的顶层基类, 即,在定义类类型时,该数据类型会隐式地继承于Object。

在面向对象设计中,子类继承父类时应该具备逻辑上的继承关系,没有这种逻辑关系的继承是毫无意义的,比如一头猪继承于一个亿万富豪。再者父类充当的是一个基类的角色,定义的是该类型共有的属性和操作方法,这样子类才能在父类已有的基础上,实现代码的复用和扩展,否则只会带来设计上的冗余。在Java中只有单继承,所谓的单继承是指子类只能有一个直接父类。现在来写一个简单的代码实例,定义一个Cat类,以及定义一个Babby类,Babby继承于Cat:

class Cat{
    static final boolean hasATail = true;
    static void  catchMice(){
        // 不会抓老鼠的猫不是好猫
        System.out.println("A cat that doesn't catch a mouse is not a good cat!");
    }
}


class Babby extends Cat{
    ;
}

子类在继承时会获得父类的属性和方法,以上文代码为例,Babby类继承Cat类时会继承Cat类的hasATail属性以及catchMice方法,读者可以在主类中测试:

class Cat{
    static final boolean has_a_tail = true;
    static void  catchMice(){
        // 不会抓老鼠的猫不是好猫
        System.out.println("A cat that doesn't catch a mouse is not a good cat!");
    }
}
 
 
class Babby extends Cat{
    ;
}

 
public class HelloJava{
    public  static void main(String[] args) {
        // 执行继承而来的catchMice方法
        Babby.catchMice();
    }
}

8.1.3 构造顺序

子类继承了父类的属性和方法,但父类的对象属性是由父类的构造函数初始化的,所以Java会先执行父类的默认构造函数,然后再执行子类的构造函数。

子类在初始化时会递归地执行所有父类的构造函数,举个简单的例子,C类继承于B类,B类继承于A类,那么C类在实例化时会先执行B类的构造函数,而B类在执行构造函数前又会先执行A类的构造函数。

代码实例-测试构造函数的执行顺序:

class Cat{
    public Cat(){
        System.out.println(3);
    }
}
 
 
class Babby extends Cat{
    public Babby(){
        System.out.println(2);
    }
}
 
 
class Kitty extends Babby{
    public Kitty(){
        System.out.println(1);
    }
}

 
public class HelloJava{
    public  static void main(String[] args) {
        // 实例化一个Kitty对象,测试构造函数的执行顺序
        Kitty kitty = new Kitty();
    }
 
}

执行以上代码时,程序输出为3,2,1, 这说明Java是先执行Cat类的构造函数,再执行Babby类的构造函数,最后才执行Kitty类的构造函数。

8.1.4 super引用

子类通过继承获得了父类的属性和方法,当在子类中定义了与父类同名的属性或方法时,又该如何访问父类的属性和方法呢?答案是通过super关键字,super引用的是父类对象。

代码实例-访问同名对象属性

class Cat{
    protected int age;
    public Cat(){
        this.age = 10;
    }
}

 
class Babby extends Cat{
    private int age;
    public Babby(){
        this.age=15;
        // 通过this访问自身的age属性
        System.out.println(this.age);
        // 通过super来访问父类的对象属性age
        System.out.println(super.age);
    }
}

如果父类中没有默认的构造函数(无参的构造函数),则子类需要显式地通过super来调用父类的构造函数,否则Java会报错。现在来修改Cat类,添加一个带参数的构造函数:

class Cat{
    static final boolean hasATail = true;
    protected int age;
    
    public Cat(int gae){
        this.age = age;
    }
}

子类Babby也需要做相应的修改,在构造函数中显式地调用父类的构造函数来初始化:

class Babby extends Cat{
    public Babby(int age){
        // 通过super来调用父类的构造函数,()里的参数需与父类构造函数中的参数一致
        super(age);
        System.out.println(this.age);
    }
}

在Babby类的构造函数中,通过super来显式地调用父类Cat的构造函数,然后再输出this.age, 这里的this.age其实是继承而来的age属性。读者可将代码super(age)删掉,以分析Java抛出的错误信息。

8.1.5 课后习题

(1) 简述你对封装的理解。

(2) 学习面向对象,需重点掌握抽象和封装的基础概念。请简述抽象和封装的区别。

(3) 写一个简单的代码实例,来测试类构造函数的执行顺序。

(4) Java中的this指向的是当前的实例对象,而super指向的是当前对象的父类对象。请读者思考,为什么Java要提供这两个关键字。

(5) 如何理解通过继承可以实现代码的复用?

8.1.6 高薪就业班


欢迎 发表评论:

请登录

忘记密码我要注册

注册账号

已有账号?请登录