Java基础知识(十一)

Author Avatar
子语 2017 - 10 - 22
  • 在其它设备中阅读本文章

继承性

继承性的作用是解决代码重用问题。

继承问题的引出

范例:定义两个类Person和Student

  1. class Person{
  2. private String name;
  3. private int age;
  4. public void setName(String name){
  5. this.name = name;
  6. }
  7. public void setAge(int age){
  8. this.age = age;
  9. }
  10. public String getName(){
  11. return this.name;
  12. }
  13. public int getAge(){
  14. return this.age;
  15. }
  16. }
  17. class Student{
  18. private String name;
  19. private int age;
  20. private String school;
  21. public void setName(String name){
  22. this.name = name;
  23. }
  24. public void setAge(int age){
  25. this.age = age;
  26. }
  27. public void setSchool(String school){
  28. this.school = school;
  29. }
  30. public String getName(){
  31. return this.name;
  32. }
  33. public int getAge(){
  34. return this.age;
  35. }
  36. public String getSchool(){
  37. return this.school;
  38. }
  39. }

由代码可见Studen和Person存在代码重复。在自然关系上,Student是Person的一种,只是Student描述的更细致,范围更小。

实现继承

继承使用关键字extends实现,语法如下:class 子类 extends 父类{}
子类也被称为派生类,父类也被称为基类、超类或super类
范例:实现继承

  1. class Person { // 父类
  2. private String name;
  3. private int age;
  4. public String getName() {
  5. return name;
  6. }
  7. public void setName(String name) {
  8. this.name = name;
  9. }
  10. public int getAge() {
  11. return age;
  12. }
  13. public void setAge(int age) {
  14. this.age = age;
  15. }
  16. }
  17. class Student extends Person { // 继承Person类
  18. }
  19. public class Demo {
  20. public static void main(String[] args) {
  21. Student stu = new Student();
  22. stu.setName("张三");
  23. stu.setAge(18);
  24. System.out.println("姓名:" + stu.getName() + ",年龄:" + stu.getAge());
  25. }
  26. }

Student继承了Person,可以使用Person类中的方法。
范例:在Student中添加属性和方法

  1. class Student extends Person { // 继承Person类
  2. private String school;
  3. public String getSchool() {
  4. return school;
  5. }
  6. public void setSchool(String school) {
  7. this.school = school;
  8. }
  9. }

由上述代码,可知继承性的优点:

(1)子类可以直接使用父类的属性和方法,进行代码重用;
(2)子类可以扩充属于自己的操作。

继承的限制

Java中继承存在如下限制:
1、Java不允许多重继承,但允许多层继承。
C++允许多继承,即一个子类可以同时继承多个父类。但该操作在Java中是不允许的。多继承是为了使子类可以同时拥有多个父类的操作。Java中使用多层继承替代,语法如下:

  1. class A{}
  2. class B extends A{}
  3. class C extends B{}

相当于C是B的子类,是A的孙子类。多层继承没有层数限制,但最好不超过三层。
2、子类继承父类时,会继承父类全部操作。对于私有操作属于隐式继承,对于非私有操作属于显式继承。

  1. class A {
  2. private String msg;
  3. public String getMsg() {
  4. return msg;
  5. }
  6. public void setMsg(String msg) {
  7. this.msg = msg;
  8. }
  9. }
  10. class B extends A {
  11. }
  12. public class Demo {
  13. public static void main(String[] args) {
  14. B b = new B();
  15. b.setMsg("Hello");
  16. System.out.println(b.getMsg()); // Hello
  17. }
  18. }

上述代码显示B类中也存在属性msg,因为如果msg不存在,setMsg()设置的内容就不能保存,即getMsg()无法输出内容。

  1. class B extends A {
  2. public void fun() {
  3. System.out.println(msg); // 报错,无法访问
  4. }
  5. }

但是在B类中无法直接访问msg,因为msg是A类的私有属性,只能间接访问。
3、在实例化子类对象之前,会先调用父类构造方法(默认是无参构造方法),以保证父类对象先实例化,而后在实例化子类对象。

  1. class A {
  2. public A() {
  3. System.out.println("A 构造方法");
  4. }
  5. }
  6. class B extends A {
  7. public B() {
  8. System.out.println("B 构造方法");
  9. }
  10. }
  11. public class Demo {
  12. public static void main(String[] args) {
  13. B b = new B();
  14. // A 构造方法
  15. // B 构造方法
  16. }
  17. }

由结果可知,在实例化子类对象前,会先实例化父类对象。对于子类构造方法来说相当于隐藏一个”super()”.

  1. class B extends A {
  2. public B() {
  3. super(); // 父类有无参构造方法时,加不加都一样
  4. System.out.println("B 构造方法");
  5. }
  6. }

何时要在子类构造方法中添加super():如果父类中没有无参构造方法,就必须使用super调用父类的有参构造方法。

  1. class A {
  2. public A(String title) {
  3. System.out.println("A 构造方法");
  4. }
  5. }
  6. class B extends A {
  7. public B() {
  8. // 子类默认调用无参构造,但A中没有无参构造
  9. System.out.println("B 构造方法");
  10. }
  11. }

上述代码执行后,不会调用A中的有参构造,因此需要在B类中的构造方法添加super():

  1. class A {
  2. public A(String title) {
  3. System.out.println("A 构造方法");
  4. }
  5. }
  6. class B extends A {
  7. public B(String title) {
  8. super(title);
  9. System.out.println("B 构造方法");
  10. }
  11. }

super()必须放在子类构造方法的第一行。而this()也应该放在构造方法的首行。

问题:子类构造方法未添加super(),系统默认使用super()调用父类的无参构造方法。如果在子类构造方法中添加this(),那么子类是不是无法调用父类构造方法?

  1. class B extends A {
  2. public B() { // 报错,构造递归调用
  3. this();
  4. System.out.println("B 构造方法");
  5. }
  6. }

由结果可知,super()和this()不能同时存在。不论子类怎么修改,子类构造方法执行前都必须先执行父类的构造方法。

方法覆写

继承性的特点是子类可以对父类已有的功能进行扩展。子类在定义属性或方法时有可能与父类重名,该操作就称为覆写。
1、方法覆写:子类定义一个与父类方法的方法名、参数类型及个数、返回值都相同的方法。

  1. class A {
  2. public void fun() {
  3. System.out.println("A中的方法");
  4. }
  5. }
  6. class B extends A {
  7. }
  8. public class Demo {
  9. public static void main(String[] args) {
  10. B b = new B();
  11. b.fun(); // A中的方法
  12. }
  13. }

此时B中没有fun(),所以调用的是从A继承的fun().
范例:方法覆写

  1. class A {
  2. public void fun() {
  3. System.out.println("A中的方法");
  4. }
  5. }
  6. class B extends A {
  7. public void fun(){ // 方法覆写
  8. System.out.println("覆写的方法");
  9. }
  10. }
  11. public class Demo {
  12. public static void main(String[] args) {
  13. B b = new B();
  14. b.fun(); // 覆写的方法
  15. }
  16. }

当方法覆写后,此时会调用子类中覆写的方法。
2、覆写结果的分析要素:

(1)实例化的是那个类;
(2)该对象调用的方法是否被覆写,如果未覆写将调用父类中的方法。

  1. class B extends A {
  2. public String fun(){
  3. System.out.println("覆写的方法");
  4. // 报错,B中fun()无法覆盖A中fun(),返回类型不兼容
  5. return "Hello";
  6. }
  7. }

进行方法覆写时,不能改变方法中的返回值和参数个数。
3、方法覆写的使用原则:父类方法不能满足子类需求,但又必须使用该方法名时,要进行方法覆写。
方法覆写时还要考虑到权限问题,被子类覆写的方法不能拥有比父类更高的访问控制权限。

访问控制权限:public>default>private,private的访问权限最严格。即如果父类方法使用public方法,子类覆写此方法时,只能使用public。父类使用的是default,子类覆写时,只能用default或public.

范例:正确覆写

  1. class A {
  2. void fun() {
  3. System.out.println("A中的方法");
  4. }
  5. }
  6. class B extends A {
  7. public void fun(){
  8. System.out.println("覆写的方法");
  9. }
  10. }

错误覆写

  1. class A {
  2. public void fun() {
  3. System.out.println("A中的方法");
  4. }
  5. }
  6. class B extends A {
  7. void fun(){
  8. System.out.println("覆写的方法");
  9. // 报错,正在尝试分配更低权限。
  10. }
  11. }

上述代码中子类使用default,比public权限更严格,不符合方法覆写原则。

问题:父类方法使用private声明,子类使用public声明该方法,是覆写吗?
答:从概念上,private声明权限高于public,因此从权限上而言符合覆写的要求。观察下述代码:

  1. class A {
  2. public void fun() {
  3. print();
  4. }
  5. private void print(){
  6. System.out.println("Hello");
  7. }
  8. }
  9. class B extends A {
  10. public void print(){
  11. System.out.println("World");
  12. }
  13. }
  14. public class Demo {
  15. public static void main(String[] args) {
  16. B b = new B();
  17. b.fun(); // Hello
  18. }
  19. }

从上述代码来看,子类并没有覆写print(),因为使用private定义的方法对于子类而言是不可见,因此子类定义的print()虽然符合覆写的要求,但是实际只是相当于定义了一个全新的方法,而不是方法覆写。而正确的覆写结果应该如下:

  1. class A {
  2. public void fun() {
  3. print();
  4. }
  5. public void print(){
  6. System.out.println("Hello");
  7. }
  8. }
  9. class B extends A {
  10. public void print(){
  11. System.out.println("World");
  12. }
  13. }
  14. public class Demo {
  15. public static void main(String[] args) {
  16. B b = new B();
  17. b.fun(); // World
  18. }
  19. }

5、默认情况下,子类对象调用是一定是覆写后的方法。

  1. class A {
  2. public void print(){
  3. System.out.println("Hello");
  4. }
  5. }
  6. class B extends A {
  7. public void print(){
  8. print(); // 等同于this.print()
  9. System.out.println("World");
  10. }
  11. }
  12. public class Demo {
  13. public static void main(String[] args) {
  14. B b = new B();
  15. b.print(); // 报错,方法递归调用,死循环
  16. }
  17. }

上述代码中,B类会优先调用B中print(),因此发生了递归调用。如果B中没有print(),则会调用父类中的。
范例:调用父类中方法super.方法名()

  1. class B extends A {
  2. public void print(){
  3. super.print();
  4. System.out.println("World");
  5. }
  6. }

super.方法名()与this.方法名()的区别:
(1)this.方法名()会优先查找本类中是否有目标方法,如果有则直接调用,没有就继续在父类中查找。
(2)super.方法名()会直接在父类中查找目标方法,不会在子类中查找。

问题:请说明重载(overloading)和覆写(override)的区别

No. 区别 重载 覆写
1 英文单词 Overloading Overrid
2 发生范围 发生在一个类中 发生在继承关系中
3 定义 方法名相同,参数类型及个数不相同 方法名称、参数类型及个数,方法返回值都相同
4 权限 没有权限限制 被覆写的方法不能拥有比父类更严格的权限

在方法重载时,返回值可以不同,但为了程序设计的统一性,应尽量保证返回值类型一致。

属性覆写

1、子类定义了与父类完全相同的属性名时,称为属性覆写。

  1. class A {
  2. String info = "Hello";
  3. }
  4. class B extends A {
  5. String info = "World";
  6. public void print(){
  7. System.out.println(super.info); // 调用父类属性
  8. System.out.println(this.info); // 调用本类属性
  9. }
  10. }
  11. public class Demo {
  12. public static void main(String[] args) {
  13. B b = new B();
  14. b.print();
  15. }
  16. }

由于在开发中,类的属性必须封装,而封装后,属性覆写就没有意义。因为父类定义的私有属性,子类不可见,因此不会互相影响。

问题:super和this的区别

No. 区别 this super
1 功能 调用本类中的操作 子类调用父类中的操作
2 形式 先从本类查找目标操作,再从父类中查找 只查找父类
3 特殊 表示本类的当前对象 super不能单独使用

在开发中,对于本类或父类的操作,最好加上this.或super.,这样便于代码调试。

继承综合实战:数组操作

要求:定义Array类,在类中可以进行整型数组的操作:由外部传入数组的数据,可以进行数据的保存和输出,并且在这个类上派生出两个子类:
(1)排序类:通过此类取得的数据可以进行排序;
(2)反转类:通过此类取得的数据采用倒序的方式输出。
开发时,先不考虑子类,先开发父类。

根据要求定义父类Array,实现其操作。

思路:开辟好数组后,根据索引,一一存放数据。

  1. class Array {
  2. private int data[]; // 数组
  3. private int foot; // 脚标
  4. // 开辟数组空间
  5. public Array(int len) {
  6. if (len > 0) {
  7. this.data = new int[len];
  8. } else { // 数组默认长度为1
  9. this.data = new int[1];
  10. }
  11. }
  12. // 为数组添加数据
  13. public boolean add(int num) {
  14. if (this.foot < this.a[this.foot++] = num; // 保存数据
  15. return true;
  16. }
  17. return false;
  18. }
  19. // 取得数组内容
  20. public int[] getData() {
  21. return this.data;
  22. }
  23. }
  24. public class Demo {
  25. public static void main(String[] args) {
  26. Array array = new Array(3);
  27. System.out.println(array.add(10)); // true
  28. System.out.println(array.add(20)); // true
  29. System.out.println(array.add(30)); // true
  30. // 超出数组长度,false
  31. System.out.println(array.add(40));
  32. int[] temp = array.getData();
  33. for (int x = 0; x < temp.length; x++) {
  34. System.out.println(temp[x]); // 10 20 30
  35. }
  36. }
  37. }

####定义子类。

思路:将Array类getData()返回的结果进行排序输出即可,因此要覆写父类的方法。

  1. // 定义一个排序数组的子类
  2. class SortArray extends Array {
  3. // Array中没有无参构造方法,
  4. // 需要明确调用父类的有参构造方法
  5. public SortArray(int len) {
  6. super(len);
  7. }
  8. // Array的getData()无法排序,进行方法覆写
  9. public int[] getData() {
  10. // 调用类库中的方法排序
  11. java.util.Arrays.sort(super.getData());
  12. return super.getData();
  13. }
  14. }
  15. public class Demo {
  16. public static void main(String[] args) {
  17. SortArray array = new SortArray(3);
  18. System.out.println(array.add(20)); // true
  19. System.out.println(array.add(30)); // true
  20. System.out.println(array.add(10)); // true
  21. // 超出数组长度,false
  22. System.out.println(array.add(40));
  23. int[] temp = array.getData();
  24. for (int x = 0; x < temp.length; x++) {
  25. System.out.println(temp[x]); // 10 20 30
  26. }
  27. }
  28. }

####定义反转子类,也要保持客户端操作不变,因此要覆写父类的方法。

  1. // 定义一个反转子类
  2. class ReverseArray extends Array {
  3. public ReverseArray(int len) {
  4. super(len);
  5. }
  6. public int[] getData() {
  7. int center = super.getData().length / 2;
  8. int head = 0;
  9. int tail = super.getData().length - 1;
  10. for (int x = 0; x < center; x++) {
  11. int temp = super.getData()[head];
  12. super.getData()[head] = super.getData()[tail];
  13. super.getData()[tail] = temp;
  14. head++;
  15. tail--;
  16. }
  17. return super.getData();
  18. }
  19. }
  20. public class Demo {
  21. public static void main(String[] args) {
  22. ReverseArray array = new ReverseArray(3);
  23. System.out.println(array.add(20)); // true
  24. System.out.println(array.add(10)); // true
  25. System.out.println(array.add(30)); // true
  26. // 超出数组长度,false
  27. System.out.println(array.add(40));
  28. int[] temp = array.getData();
  29. for (int x = 0; x < temp.length; x++) {
  30. System.out.println(temp[x]); // 10 20 30
  31. }
  32. }
  33. }

总结
子类扩充方法时,尽量根据需求覆写父类方法,而不是直接定义新方法。

This blog is under a CC BY-NC-SA 3.0 Unported License
本文链接:http://yov.oschina.io/article/Java/Java Base/Java基础知识(十一)/

Error: Comments Not Initialized