广州番禺Python, Java小班周末班培训
第四期线下Java, Python小班周末班已经开课了,培训的课程有Python爬虫,Python后端开发,Python办公自动化,Python大数据分析, Java中高级工程师培训。授课详情请点击:http://47.107.239.253/?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) 如何理解通过继承可以实现代码的复用?