第九章 面向对象三大特征:封装

1. 封装基本概念

1.1 封装的作用

第一个作用:保证内部结构的安全。
第二个作用:屏蔽复杂,暴露简单。

在代码级别上,封装有什么用? 一个类体当中的数据,假设封装之后,对于代码的调用人员来说, 不需要关心代码的复杂实现,只需要通过一个简单的入口就可以访问了; 另外,类体中安全级别较高的数据封装起来,外部人员不能随意访问, 来保证数据的安全性。

1.2 怎么进行封装

第一步:属性私有化(使用private关键字进行修饰。)
第二步:对外提供简单的操作入口。
注意:
set和get方法都是实例方法,不能带static。
不带static的方法称为实例方法,实例方法的调用必须先new对象。
set和get方法写的时候有严格的规范要求:(大家要按照规矩来)
set方法长这个样子:
public void set+属性名首字母大写(1个参数){
xxx = 1个参数;
}
get方法长这个样子:
public 返回值类型 get+属性名首字母大写(无参){
return xxx;
}

第十章 static、this关键字

1. static关键字

1.1 static关键字概述

  1. - static修饰的统一都是静态的,都是类相关的,不需要new对象。直接采用“类名.”访问。(需注意的是**静态的使用“引用.”也行**,为了更好区分建议静态采用类名访问)
  2. - 当一个属性是类级别的属性,所有对象的这个属性的值是一样的,建议定义为静态变量。
  3. - 静态方法有一个优点,是不需要new对象,直接采用类名调用,极其方便(在同一个类中静态修饰的可以省略"类名.")。工具类就是为了方便,所以工具类中的方法一般都是static的。
  1. class VarTest{
  2. // 以下实例的,都是对象相关的,访问时采用“引用.”的方式访问。需要先new对象。
  3. // 实例相关的,必须先有对象,才能访问,可能会出现空指针异常。
  4. // 成员变量中的实例变量
  5. int i;
  6. // 实例方法
  7. public void m2(){
  8. // 局部变量
  9. int x = 200;
  10. }
  11. // 以下静态的,都是类相关的,访问时采用“类名.”的方式访问。不需要new对象。
  12. // 不需要对象的参与即可访问。没有空指针异常的发生。
  13. // 成员变量中的静态变量
  14. static int k;
  15. // 静态方法
  16. public static void m1(){
  17. // 局部变量
  18. int m = 100;
  19. }
  20. }

1.2 空指针异常进一步分析

  1. public class StaticTest03{
  2. public static void main(String[] args){
  3. // 通过"类名."的方式访问静态变量
  4. System.out.println(Chinese.country);
  5. // 创建对象
  6. Chinese c1 = new Chinese("1111111", "张三");
  7. System.out.println(c1.idCard); // 1111111
  8. System.out.println(c1.name); // 张三
  9. System.out.println(c1.country); // 中国
  10. // c1是空引用
  11. c1 = null;
  12. // 分析这里会不会出现空指针异常?
  13. // 不会出现空指针异常。
  14. // 因为静态变量不需要对象的存在。
  15. // 实际上以下的代码在运行的时候,还是:System.out.println(Chinese.country);
  16. System.out.println(c1.country);
  17. // 这个会出现空指针异常,因为name是实例变量。
  18. //System.out.println(c1.name);
  19. }
  20. }
  21. class Chinese{
  22. // 实例变量
  23. String idCard;
  24. String name;
  25. // 静态变量
  26. static String country = "中国";
  27. //构造方法
  28. public Chinese(String x, String y){
  29. idCard = x;
  30. name = y;
  31. }
  32. }

1.3 静态代码块

  1. - static静态代码块在什么时候执行

类加载时执行。并且只执行一次;在main方法执行之前执行

  1. - 是一个特殊的时机,类加载时机

1.4 实例语句块(代码块)

  1. - 代码块在什么时候执行

在类加载是并没有执行,只要是构造方法执行,必然在构造方法执行之前,自动执行“实例语句块”中的代码。

  1. - 是一个特殊的时机,对象构建时机

1.5 代码执行顺序

  1. public class CodeOrder{
  2. // 静态代码块
  3. static{
  4. System.out.println("A");
  5. }
  6. // 入口
  7. // A X Y C B Z(最终执行顺序)
  8. public static void main(String[] args){
  9. System.out.println("Y");
  10. new CodeOrder();
  11. System.out.println("Z");
  12. }
  13. // 构造方法
  14. public CodeOrder(){
  15. System.out.println("B");
  16. }
  17. // 实例语句块
  18. {
  19. System.out.println("C");
  20. }
  21. // 静态代码块
  22. static {
  23. System.out.println("X");
  24. }
  25. }

2. this关键字

2.1 this在内存中的表示

image.png

this只能使用在实例方法中。谁调用这个实例方法,this就是谁;所以this代表的是:当前对象。
this不能使用在静态方法:this代表当前对象,静态方法中不存在当前对象。

2.2 this啥时候不能省略

  1. /*
  2. 1、this可以使用在实例方法中,不能使用在静态方法中。
  3. 2、this关键字大部分情况下可以省略,什么时候不能省略呢?
  4. 在实例方法中,或者构造方法中,为了区分局部变量和实例变量,
  5. 这种情况下:this. 是不能省略的。
  6. */
  7. public class ThisTest03{
  8. public static void main(String[] args){
  9. Student s = new Student();
  10. s.setNo(111);
  11. s.setName("张三");
  12. System.out.println("学号:" + s.getNo());
  13. System.out.println("姓名:" + s.getName());
  14. Student s2 = new Student(2222, "李四");
  15. System.out.println("学号:" + s2.getNo());
  16. System.out.println("姓名:" + s2.getName());
  17. }
  18. }
  19. // 分析一下:以下代码哪里写的不好。
  20. // 学生类
  21. class Student{
  22. //学号
  23. private int no;
  24. //姓名
  25. private String name;
  26. //构造方法无参
  27. public Student(){
  28. }
  29. //构造方法有参
  30. /*
  31. public Student(int i, String s){
  32. no = i;
  33. name = s;
  34. }
  35. */
  36. // 上面的构造方法也增强以下可读性
  37. public Student(int no, String name){
  38. this.no = no;
  39. this.name = name;
  40. }
  41. // setter and getter方法
  42. /*
  43. public void setNo(int i){
  44. no = i;
  45. }
  46. */
  47. /*
  48. public void setNo(int no){ // 就近原则。
  49. no = no; //这两个no都是局部变量no,和实例变量no没关系。
  50. }
  51. */
  52. public void setNo(int no){
  53. //no是局部变量
  54. //this.no 是指的实例变量。
  55. this.no = no; // this. 的作用是:区分局部变量和实例变量。
  56. }
  57. public int getNo(){
  58. return no;
  59. //return this.no;
  60. }
  61. /*
  62. public void setName(String s){
  63. name = s;
  64. }
  65. */
  66. /*
  67. public void setName(String name){ // 就近原则
  68. name = name; //这两个name都是局部变量name,和实例变量name没关系。
  69. }
  70. */
  71. public void setName(String name){
  72. this.name = name;
  73. }
  74. /*
  75. public String getName(){
  76. return name;
  77. }
  78. */
  79. public String getName(){ // getName实际上获取的是“当前对象”的名字。
  80. //return this.name; // 严格来说,这里是有一个 this. 的。只不过这个 this. 是可以省略的。
  81. return name;
  82. }
  83. }

2.3 this(实际参数列表)用法

  1. /*
  2. 1、this除了可以使用在实例方法中,还可以用在构造方法中。
  3. 2、新语法:通过当前的构造方法去调用另一个本类的构造方法,可以使用以下语法格式:
  4. this(实际参数列表);
  5. 通过一个构造方法1去调用构造方法2,可以做到代码复用。
  6. 但需要注意的是:“构造方法1”和“构造方法2” 都是在同一个类当中。
  7. 3、this() 这个语法作用是什么?
  8. 代码复用。
  9. 4、死记硬背:
  10. 对于this()的调用只能出现在构造方法的第一行。
  11. */
  12. public class ThisTest04{
  13. public static void main(String[] args){
  14. // 调用无参数构造方法
  15. Date d1 = new Date();
  16. d1.detail();
  17. // 调用有参数构造方法
  18. Date d2 = new Date(2008, 8, 8);
  19. d2.detail();
  20. }
  21. }
  22. /*
  23. 需求:
  24. 1、定义一个日期类,可以表示年月日信息。
  25. 2、需求中要求:
  26. 如果调用无参数构造方法,默认创建的日期为:1970年1月1日。
  27. 当然,除了调用无参数构造方法之外,也可以调用有参数的构造方法来创建日期对象。
  28. */
  29. class Date{ // 以后写代码都要封装,属性私有化,对外提供setter and getter
  30. //年
  31. private int year;
  32. //月
  33. private int month;
  34. //日
  35. private int day;
  36. // 构造方法无参
  37. // 调用无参数构造方法,初始化的日期是固定值。
  38. public Date(){
  39. //错误: 对this的调用必须是构造器中的第一个语句
  40. //System.out.println(11);
  41. /*
  42. this.year = 1970;
  43. this.month = 1;
  44. this.day = 1;
  45. */
  46. this(1970, 1, 1);
  47. }
  48. // 构造方法有参数
  49. public Date(int year, int month, int day){
  50. this.year = year;
  51. this.month = month;
  52. this.day = day;
  53. }
  54. // 提供一个可以打印日期的方法
  55. public void detail(){
  56. //System.out.println(year + "年" + month + "月" + day + "日");
  57. System.out.println(this.year + "年" + this.month + "月" + this.day + "日");
  58. }
  59. //setter and getter
  60. public void setYear(int year){
  61. // 设立关卡(有时间可以设立关卡)
  62. this.year = year;
  63. }
  64. public int getYear(){
  65. return year;
  66. }
  67. public void setMonth(int month){
  68. // 设立关卡(有时间可以设立关卡)
  69. this.month = month;
  70. }
  71. public int getMonth(){
  72. return month;
  73. }
  74. public void setDay(int day){
  75. // 设立关卡(有时间可以设立关卡)
  76. this.day = day;
  77. }
  78. public int getDay(){
  79. return day;
  80. }
  81. }

2.4 小结:

  1. 只要负责调用的方法a和被调用的方法b在同一个类当中:<br /> this. 可以省略<br /> 类名. 可以省略

第十一章 面向对象三大特征:继承

1. 继承的作用、相关特性

1.1 继承的作用

基本作用:子类继承父类,代码可以得到复用。(这个不是重要的作用,是基本作用。)
主要(重要)作用:因为有了继承关系,才有了后期的方法覆盖和多态机制。

1.2 继承的相关特性

  1. - B类继承A类,则称A类为超类(superclass)、父类、基类,B类则称为子类(subclass)、派生类、扩展类。
  2. - java 中的继承只支持**单继承**,不支持多继承,C++中支持多继承,这也是 java 体现简单性的一点,换句话说,java 中不允许这样写代码:class B extends A,C{ } 这是错误的。
  3. - 虽然 java 中不支持多继承,但有的时候会产生间接继承的效果,例如:class C extends Bclass B extends A,也就是说,C 直接继承 B,其实 C 还间接继承 A
  4. - java 中规定,子类继承父类,除构造方法不能继承之外,剩下都可以继承。但是私有的属性无法在子类中直接访问。(父类中private修饰的不能在子类中直接访问。可以通过间接的手段来访问;即通过gettersetter方法来间接访问)
  5. - java 中的类没有显式的继承任何类,则默认继承 Object类,Object类是java 语言提供的根类(老祖宗类),也就是说,一个对象与生俱来就有Object类型中所有的特征。
  6. - 继承也存在一些缺点,例如:CreditAccount 类继承 Account 类会导致它们之间的耦合度非常高Account 类发生改变之后会马上影响到 CreditAccount 类。

本质上,子类继承父类之后,是将父类继承过来的方法归为自己所有。 实际上调用的也不是父类的方法,是他子类自己的方法(因为已经继承过来了 就属于自己的)。

1.3 print进一步分析

  1. public class Test{
  2. // 静态变量
  3. static Student stu = new Student();
  4. // 入口
  5. public static void main(String[] args){
  6. //拆分为两行
  7. Student s = Test.stu;
  8. s.exam();
  9. //合并代码
  10. Test.stu.exam();
  11. //和上面的代码进行对比,可以知道out对象是System类的静态变量,
  12. //print()是out的实例方法
  13. System.out.println("Hello World!");
  14. }
  15. }
  16. class Student{
  17. // 实例方法
  18. public void exam(){
  19. System.out.println("考试。。。。。");
  20. }
  21. }

1.4 object类的一些方法

  1. /*
  2. public class Object {
  3. // 注意:当源码当中一个方法以“;”结尾,并且修饰符列表中有“native”关键字
  4. // 表示底层调用C++写的dll程序(dll动态链接库文件)
  5. private static native void registerNatives();
  6. // 静态代码块
  7. static {
  8. // 调用registerNatives()方法。
  9. registerNatives();
  10. }
  11. // 无参数构造方法
  12. @HotSpotIntrinsicCandidate
  13. public Object() {}
  14. @HotSpotIntrinsicCandidate
  15. public final native Class<?> getClass();
  16. @HotSpotIntrinsicCandidate
  17. public native int hashCode();
  18. // public: 公开的
  19. // boolean: 方法的返回值类型
  20. // equals(相等):方法名
  21. // (Object obj) 形参
  22. public boolean equals(Object obj) {
  23. //方法体
  24. return (this == obj);
  25. }
  26. // 已有对象a,想创建一个和a一模一样的对象,可以调用这个克隆方法。
  27. @HotSpotIntrinsicCandidate
  28. protected native Object clone() throws CloneNotSupportedException;
  29. // toString()方法执行结束之后返回一个字符串。
  30. public String toString() {
  31. return getClass().getName() + "@" + Integer.toHexString(hashCode());
  32. }
  33. @HotSpotIntrinsicCandidate
  34. public final native void notify();
  35. @HotSpotIntrinsicCandidate
  36. public final native void notifyAll();
  37. public final void wait() throws InterruptedException {
  38. wait(0L);
  39. }
  40. public final native void wait(long timeoutMillis) throws InterruptedException;
  41. public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
  42. if (timeoutMillis < 0) {
  43. throw new IllegalArgumentException("timeoutMillis value is negative");
  44. }
  45. if (nanos < 0 || nanos > 999999) {
  46. throw new IllegalArgumentException("nanosecond timeout value out of range");
  47. }
  48. if (nanos > 0 && timeoutMillis < Long.MAX_VALUE) {
  49. timeoutMillis++;
  50. }
  51. wait(timeoutMillis);
  52. }
  53. @Deprecated(since="9")
  54. protected void finalize() throws Throwable { }
  55. }
  56. */
  57. public class ExtendsTest05 {
  58. // ExtendsTest05默认继承Object
  59. // ExtendsTest05类当中是有toString()方法
  60. // 不过toString()方法是一个实例方法,需要创建对象才能调用。
  61. /*
  62. public String toString() {
  63. return getClass().getName() + "@" + Integer.toHexString(hashCode());
  64. }
  65. */
  66. public static void main(String[] args){
  67. // 分析这个代码可以执行吗?
  68. //ExtendsTest05.toString();
  69. // 先new对象
  70. ExtendsTest05 et = new ExtendsTest05();
  71. String retValue = et.toString();
  72. // 2f92e0f4 可以“等同”看做对象在堆内存当中的内存地址。
  73. // 实际上是内存地址经过“哈希算法”得出的十六进制结果。
  74. System.out.println(retValue); // ExtendsTest05@2f92e0f4
  75. // 创建对象
  76. Product pro = new Product();
  77. String retValue2 = pro.toString();
  78. System.out.println(retValue2); // Product@5305068a
  79. // 以上两行代码能否合并为一行!!!可以
  80. System.out.println(pro.toString()); //Product@5305068a
  81. // 如果直接输出“引用”呢???????
  82. System.out.println(pro); //Product@5305068a
  83. System.out.println(100);
  84. System.out.println(true);
  85. // Product@5305068a
  86. System.out.println(pro); // println方法会自动调用pro的toString()方法。
  87. }
  88. }
  89. class Product{
  90. /*
  91. public String toString() {
  92. return getClass().getName() + "@" + Integer.toHexString(hashCode());
  93. }
  94. */
  95. }

2. 方法覆盖

2.1 什么时候使用方法覆盖

父类中的方法无法满足子类的业务需求,子类有必要对继承过来的方法进行覆盖。

2.2 方法覆盖的构成条件

第一:有继承关系的两个类
第二:具有相同方法名、返回值类型、形式参数列表(返回值类型可以不同)

  1. // 父类
  2. class MyClass1{
  3. public Animal getAnimal(){
  4. return null;
  5. }
  6. }
  7. // 子类
  8. class MyClass2 extends MyClass1{
  9. // 重写父类的方法
  10. /*
  11. public Animal getAnimal(){
  12. return null;
  13. }
  14. */
  15. // 重写的时候返回值类型由Animal变成了Cat,变小了。(可以,java中允许)
  16. /*
  17. public Cat getAnimal(){
  18. return null;
  19. }
  20. */
  21. // 重写的时候返回值类型由Animal变成了Object。变大了。(不行,java中不允许)
  22. /*
  23. public Object getAnimal(){
  24. return null;
  25. }
  26. */
  27. }
  1. 第三:访问权限不能更低。<br /> 第四:抛出异常不能更多。

什么条件满足之后能够构成方法重载overload? 条件一:在同一个类当中 条件二:方法名相同 条件三:参数列表不同(个数、顺序、类型)

注意事项:
注意1:方法覆盖只是针对于方法,和属性无关。
注意2:私有方法无法覆盖。
注意3:构造方法不能被继承,所以构造方法也不能被覆盖。
注意4:方法覆盖只是针对于“实例方法”,“静态方法覆盖”没有意义。

2.3 object类的toString方法覆盖

  1. /*
  2. 关于Object类中的toString()方法
  3. 1、toString()方法的作用是什么?
  4. 作用:将“java对象”转换成“字符串的形式”。
  5. 2、Object类中toString()方法的默认实现是什么?
  6. public String toString() {
  7. return getClass().getName() + "@" + Integer.toHexString(hashCode());
  8. }
  9. toString: 方法名的意思是转换成String
  10. 含义:调用一个java对象的toString()方法就可以将该java对象转换成字符串的表示形式。
  11. 3、那么toString()方法给的默认实现够用吗?
  12. */
  13. public class OverrideTest04{
  14. public static void main(String[] args){
  15. // 创建一个日期对象
  16. MyDate t1 = new MyDate();
  17. // 调用toString()方法(将对象转换成字符串形式。)
  18. // 问:你对这个输出结果满意吗?不满意,希望输出:xxxx年xx月xx日
  19. // 重写MyDate的toString()方法之前的结果
  20. //System.out.println(t1.toString()); //MyDate@28a418fc
  21. // 重写MyDate的toString()方法之后的结果
  22. System.out.println(t1.toString());
  23. // 大家是否还记得:当输出一个引用的时候,println方法会自动调用引用的toString方法。
  24. System.out.println(t1);
  25. MyDate t2 = new MyDate(2008, 8, 8);
  26. System.out.println(t2); //2008年8月8日
  27. //创建学生对象
  28. Student s = new Student(1111, "zhangsan");
  29. // 重写toString()方法之前
  30. //System.out.println(s); //Student@87aac27
  31. // 重写toString()方法之后
  32. // 输出一个学生对象的时候,可能更愿意看到学生的信息,不愿意看到对象的内存地址。
  33. System.out.println(s.toString());
  34. System.out.println(s);
  35. }
  36. }
  37. // 日期类
  38. class MyDate {
  39. private int year;
  40. private int month;
  41. private int day;
  42. public MyDate(){
  43. this(1970,1,1);
  44. }
  45. public MyDate(int year,int month,int day){
  46. this.year = year;
  47. this.month = month;
  48. this.day = day;
  49. }
  50. public void setYear(int year){
  51. this.year = year;
  52. }
  53. public int getYear(){
  54. return year;
  55. }
  56. public void setMonth(int month){
  57. this.month = month;
  58. }
  59. public int getMonth(){
  60. return month;
  61. }
  62. public void setDay(int day){
  63. this.day = day;
  64. }
  65. public int getDay(){
  66. return day;
  67. }
  68. // 从Object类中继承过来的那个toString()方法已经无法满足我业务需求了。
  69. // 我在子类MyDate中有必要对父类的toString()方法进行覆盖/重写。
  70. // 我的业务要求是:调用toString()方法进行字符串转换的时候,
  71. // 希望转换的结果是:xxxx年xx月xx日,这种格式。
  72. public String toString() {
  73. return year + "年" + month + "月" + day + "日";
  74. }
  75. }
  76. class Student{
  77. int no;
  78. String name;
  79. public Student(int no, String name){
  80. this.no = no;
  81. this.name = name;
  82. }
  83. // 重写 方法覆盖
  84. public String toString() {
  85. return "学号:" + no + ",姓名:" + name;
  86. }
  87. }

第十二章 面向对象三大特征:多态

1. 向上转型和向下转型的概念

1.1 向上转型

子—->父 (upcasting) :又被称为自动类型转换:Animal a = new Cat();

1.2 向下转型

父—->子 (downcasting) : 又被称为强制类型转换:Cat c = (Cat)a; 需要添加强制类型转换符。
什么时候需要向下转型?
需要调用或者执行子类对象中特有的方法,必须进行向下转型,才可以调用。
向下转型有风险吗?
容易出现ClassCastException(类型转换异常)
怎么避免这个风险?
instanceof运算符,可以在程序运行阶段动态的判断某个引用指向的对象
是否为某一种类型。
养成好习惯,向下转型之前一定要使用instanceof运算符进行判断。

  1. 不管是向上转型还是向下转型,首先他们之间必须有继承关系,这样编译器就不会报错。

题外话: 与别人聊天的时候,要专业一些,说向上转型和向下转型,不要说自动类型转换,也不要说强制类型转换,因为自动类型转换和强制类型转换是使用在基本数据类型方面的,在引用类型转换这里只有向上和向下转型。

2. 多态

2.1 多态的基础语法

  1. public class Test01{
  2. public static void main(String[] args){
  3. Animal a1 = new Animal();
  4. a1.move(); //动物在移动!!!
  5. Cat c1 = new Cat();
  6. c1.move(); //猫走猫步!
  7. Bird b1 = new Bird();
  8. b1.move(); //鸟儿在飞翔!!!
  9. // 代码可以这样写吗?
  10. /*
  11. 1、Animal和Cat之间有继承关系吗?有的。
  12. 2、Animal是父类,Cat是子类。
  13. 3、Cat is a Animal,这句话能不能说通?能。
  14. 4、经过测试得知java中支持这样的一个语法:
  15. 父类型的引用允许指向子类型的对象。
  16. Animal a2 = new Cat();
  17. a2就是父类型的引用。
  18. new Cat()是一个子类型的对象。
  19. 允许a2这个父类型引用指向子类型的对象。
  20. */
  21. Animal a2 = new Cat();
  22. Animal a3 = new Bird();
  23. // 没有继承关系的两个类型之间存在转型吗?
  24. // 错误: 不兼容的类型: Dog无法转换为Animal
  25. // Animal a4 = new Dog();
  26. // 调用a2的move()方法
  27. /*
  28. 什么是多态?
  29. 多种形态,多种状态。
  30. 分析:a2.move();
  31. java程序分为编译阶段和运行阶段。
  32. 先来分析编译阶段:
  33. 对于编译器来说,编译器只知道a2的类型是Animal,
  34. 所以编译器在检查语法的时候,会去Animal.class
  35. 字节码文件中找move()方法,找到了,绑定上move()
  36. 方法,编译通过,静态绑定成功。(编译阶段属于静态绑定。)
  37. 再来分析运行阶段:
  38. 运行阶段的时候,实际上在堆内存中创建的java对象是
  39. Cat对象,所以move的时候,真正参与move的对象是一只猫,
  40. 所以运行阶段会动态执行Cat对象的move()方法。这个过程
  41. 属于运行阶段绑定。(运行阶段绑定属于动态绑定。)
  42. 多态表示多种形态:
  43. 编译的时候一种形态。
  44. 运行的时候另一种形态。
  45. */
  46. a2.move(); //cat走猫步!
  47. // 调用a3的move()方法
  48. a3.move(); //鸟儿在飞翔!!!
  49. // ======================================================================
  50. Animal a5 = new Cat(); // 底层对象是一只猫。
  51. // 分析这个程序能否编译和运行呢?
  52. // 分析程序一定要分析编译阶段的静态绑定和运行阶段的动态绑定。
  53. // 只有编译通过的代码才能运行。没有编译,根本轮不到运行。
  54. // 错误: 找不到符号
  55. // why??? 因为编译器只知道a5的类型是Animal,去Animal.class文件中找catchMouse()方法
  56. // 结果没有找到,所以静态绑定失败,编译报错。无法运行。(语法不合法。)
  57. //a5.catchMouse();
  58. // 假设代码写到了这里,我非要调用catchMouse()方法怎么办?
  59. // 这个时候就必须使用“向下转型”了。(强制类型转换)
  60. // 以下这行代码为啥没报错????
  61. // 因为a5是Animal类型,转成Cat,Animal和Cat之间存在继承关系。所以没报错。
  62. Cat x = (Cat)a5;
  63. x.catchMouse(); //猫正在抓老鼠!!!!
  64. // 向下转型有风险吗?
  65. Animal a6 = new Bird(); //表面上a6是一个Animal,运行的时候实际上是一只鸟儿。
  66. /*
  67. 分析以下程序,编译报错还是运行报错???
  68. 编译器检测到a6这个引用是Animal类型,
  69. 而Animal和Cat之间存在继承关系,所以可以向下转型。
  70. 编译没毛病。
  71. 运行阶段,堆内存实际创建的对象是:Bird对象。
  72. 在实际运行过程中,拿着Bird对象转换成Cat对象
  73. 就不行了。因为Bird和Cat之间没有继承关系。
  74. 运行时出现异常,这个异常和空指针异常一样非常重要,也非常经典:
  75. java.lang.ClassCastException:类型转换异常。
  76. java.lang.NullPointerException:空指针异常。这个也非常重要。
  77. */
  78. //Cat y = (Cat)a6;
  79. //y.catchMouse();
  80. // 怎么避免ClassCastException异常的发生???
  81. /*
  82. 新的内容,运算符:
  83. instanceof (运行阶段动态判断)
  84. 第一:instanceof可以在运行阶段动态判断引用指向的对象的类型。
  85. 第二:instanceof的语法:
  86. (引用 instanceof 类型)
  87. 第三:instanceof运算符的运算结果只能是:true/false
  88. 第四:c是一个引用,c变量保存了内存地址指向了堆中的对象。
  89. 假设(c instanceof Cat)为true表示:
  90. c引用指向的堆内存中的java对象是一个Cat。
  91. 假设(c instanceof Cat)为false表示:
  92. c引用指向的堆内存中的java对象不是一个Cat。
  93. 程序员要养成一个好习惯:
  94. 任何时候,任何地点,对类型进行向下转型时,一定要使用
  95. instanceof 运算符进行判断。(java规范中要求的;
  96. 因为开发过程中代码可能是别人写的,某一方法的参数列表可能是不同的对象)
  97. 这样可以很好的避免:ClassCastException
  98. */
  99. System.out.println(a6 instanceof Cat); //false
  100. if(a6 instanceof Cat){ // 如果a6是一只Cat
  101. Cat y = (Cat)a6; // 再进行强制类型转换
  102. y.catchMouse();
  103. }
  104. }
  105. }

2.2 多态在开发中的作用

降低程序的耦合度,提高程序的扩展力。
public class Master{
public void feed(Dog d){}
public void feed(Cat c){}
}
以上的代码中表示:Master和Dog以及Cat的关系很紧密(耦合度高)。导致扩展力很差。
public class Master{
public void feed(Pet pet){
pet.eat();
}
}
以上的代表中表示:Master和Dog以及Cat的关系就脱离了,Master关注的是Pet类。
这样Master和Dog以及Cat的耦合度就降低了,提高了软件的扩展性。

面向对象的三大特征:封装、继承、多态 一环扣一环,有了封装,有了这种整体的概念之后,对象和对象之间产生了继承,有了继承之后,才有了方法的覆盖和多态。

软件开发原则: 七大原则最基本的原则:OCP(对扩展开放,对修改关闭) 目的是:降低程序耦合度,提高程序扩展力。面向抽象编程,不建议面向具体编程。

2.3 方法覆盖与多态

1、方法覆盖需要和多态机制联合起来使用才有意义。
Animal a = new Cat();
a.move();
要的是什么效果?
编译的时候move()方法是Animal的。
运行的时候自动调用到子类重写move()方法上。

假设没有多态机制,只有方法覆盖机制,你觉得有意义吗?
没有多态机制的话,方法覆盖可有可无。
没有多态机制,方法覆盖也可以没有,如果父类的方法无法满足
子类业务需求的时候,子类完全可以定义一个全新的方法。
方法覆盖和多态不能分开。

  1. 2、静态方法存在方法覆盖吗?<br /> 多态自然就和对象有关系了。<br /> 而静态方法的执行不需要对象。<br /> 所以,一般情况下,我们会说静态方法“不存在”方法覆盖。<br /> 不探讨静态方法的覆盖。
  1. public class OverrideTest05{
  2. public static void main(String[] args){
  3. // 静态方法可以使用“引用.”来调用吗?可以
  4. // 虽然使用“引用.”来调用,但是和对象无关。
  5. Animal a = new Cat(); //多态
  6. // 静态方法和对象无关。
  7. // 虽然使用“引用.”来调用。但是实际运行的时候还是:Animal.doSome()
  8. //可以这样理解:引用只能调用父类的,想调用子类的必须用类名来调
  9. a.doSome();
  10. Animal.doSome();
  11. Cat.doSome();
  12. }
  13. }
  14. class Animal{
  15. // 父类的静态方法
  16. public static void doSome(){
  17. System.out.println("Animal的doSome方法执行!");
  18. }
  19. }
  20. class Cat extends Animal{
  21. // 尝试在子类当中对父类的静态方法进行重写
  22. public static void doSome(){
  23. System.out.println("Cat的doSome方法执行!");
  24. }
  25. }

3、方法覆盖“返回值类型”
对于返回值类型是基本数据类型来说,必须一致。
对于返回值类型是引用数据类型来说,重写之后返回值类型可以变的更小(但意义不大,实际应用 中没人这样写)

  1. public class OverrideTest07{
  2. public static void main(String[] args){
  3. // 一般重写的时候都是复制粘贴;不动、不改
  4. }
  5. }
  6. class Animal{
  7. /*
  8. public double sum(int a, int b){
  9. return a + b;
  10. }
  11. */
  12. /*
  13. public long sum(int a, int b){
  14. return a + b;
  15. }
  16. */
  17. /*
  18. public int sum(int a, int b){
  19. return a + b;
  20. }
  21. */
  22. }
  23. class Cat extends Animal{
  24. // 重写
  25. // 错误: Cat中的sum(int,int)无法覆盖Animal中的sum(int,int)
  26. /*
  27. public int sum(int a, int b){
  28. return a + b;
  29. }
  30. */
  31. /*
  32. public double sum(int a, int b){
  33. return a + b;
  34. }
  35. */
  36. //错误: Cat中的sum(int,int)无法覆盖Animal中的sum(int,int)
  37. /*
  38. public long sum(int a, int b){
  39. return a + b;
  40. }
  41. */
  42. }
  43. // 父类
  44. class MyClass1{
  45. public Animal getAnimal(){
  46. return null;
  47. }
  48. }
  49. // 子类
  50. class MyClass2 extends MyClass1{
  51. // 重写父类的方法
  52. /*
  53. public Animal getAnimal(){
  54. return null;
  55. }
  56. */
  57. // 重写的时候返回值类型由Animal变成了Cat,变小了。(可以,java中允许)
  58. /*
  59. public Cat getAnimal(){
  60. return null;
  61. }
  62. */
  63. // 重写的时候返回值类型由Animal变成了Object。变大了。(不行,java中不允许)
  64. /*
  65. public Object getAnimal(){
  66. return null;
  67. }
  68. */
  69. }