3、继承 extends
3.1什么是继承,有什么用?
继承:在现实世界当中也是存在的,例如:家里有矿,不用很努力也可以继承。
继承的作用:
基本作用:子类继承父类,代码可以得到复用。
主要(重要)作用:因为有了继承关系,才有了后期的方法覆盖和多态机制。
3.2、继承的相关特性
(1)B 类继承 A 类,则称A类为超类(superclass)、父类、基类,B 类则称为子类(subclass)、派生类、扩展类。
class A{}
class B extends A{}
(2)java中的继承只支持单继承,不支持多继承,C++ 中支持多继承,这也是 java 体现简单性的一点,换句话说,
java中 不允许这样写代码:class B extends A, C{} 这是错误的。
(3)虽然 java 中不支持多继承,但有的时候会产生间接继承的效果,例如:class C extends B, class B extends A,
也就是说,C 直接继承 B,其实 C 还间接继承 A。
(4)java 中规定,子类继承父类,除构造方法不能继承外,剩下的都可以继承。
但是私有的属性无法在子类中直接访问。父类中 private 修饰的不能在子类中直接访问。可以通过间接的手段来访问。
(5)java 中的类没有显示的继承任何类,则默认继承 Object 类,Object 类是 java 语言提供的根类,
也就是说,一个对象与生俱来就有 Object 类型中所有的特征。Object 是所有类的超类,类体系结构中的根。
java 这么庞大的一个继承结构,最顶点是:Object。
(6)继承也存在一些缺点,例如:CreditAccount 类继承 Account 类会导致它们之间的耦合度非常高,
Account 类发生改变之后会马上影响到 CreditAccount 类。
继承也是存在缺点的:耦合度高,父类修改,子类受牵连。
java 只允许单继承。不允许多继承。java是简单的。
C++ 支持多重继承。C++ 更接近现实一些。因为在现实世界中子女同时继承父母两方特征。
本质上,子类继承父类之后,是将父类继承过来的方法归为自己所有。
实际上调用的也不是父类的方法,是他子类自己的方法。
在实际开发中,满足什么条件的时候,我可以使用继承呢?
凡是采用“is a”能描述的,都可以继承。
假设以后的开发中有一个 A 类,有一个 B 类,A 类和 B 类确实也有重复的代码,那么它们两个之间就可以继承吗?
不一定,还是要看一看它们之间是否能够使用 is a 来描述。
任何一个类,没有显示继承任何类,默认继承 Object,那么 Object 类当中有哪些方法呢?
一定要看源代码
package java.lang;
public class Object {
private static native void registerNatives();
static {
registerNatives();
}
public final native Class> getClass();
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
protected native Object clone() throws CloneNotSupportedException;
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
public final void wait() throws InterruptedException {
wait(0);
}
protected void finalize() throws Throwable { }
}
java 为什么比较好学呢?
是因为 Java 内置了一套庞大的类库,程序员不需要从 0 开始写代码,程序员可以基于这套庞大的类库进行“二次”开发。
(开发速度较快,因为 JDK 内置的这套库实现了很多基础的功能。)
例如:String 是 SUN 编写的字符串类、System 是 SUN 编写的系统类。
JDK源代码是在什么位置?
例:
C:\Program Files\Java\jdk-13.0.2\lib\src.zip
你现在能看懂以下代码了吗?
System.out.println(“Hello World!”);
System.out中,out 后面没有小括号,说明 out 是变量名。
另外 System 是一个类名,直接使用类名 System.out,说明 out 是一个静态变量。
System.out 返回一个对象,然后采用“对象.”的方式访问 println() 方法。
System.out.println(“Hello World!”);
以上代码中:System、out、println都是标识符。
private static native void registerNatives();
注意:当源代码当中一个方法以“;”结尾,并且修饰符列表有“native”关键字
表示底层调用 C++ 写的 dll 程序(dll 动态链接库文件)
@HotSpotIntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;
已有对象 a,想创建一个和 a 一模一样的对象,你可以调用这个克隆方法。
底层也是调用 C++
Sytem.out.println(引用);
当直接输出一个“引用”的时候,println() 方法会先自动调用“引用.toString()”,然后输出 toString() 方法的执行结果。
什么时候我们会考虑使用“方法覆盖”呢?
子类继承父类之后,当继承过来的方法无法满足当前子类的业务需求时,
子类有权利对这个方法进行重新编写,有必要进行“方法的覆盖”。
方法覆盖又叫做:方法重写,英语单词叫做:Override、Overwrite,都可以。
比较常见的:方法覆盖、方法重写、override
结论:
当子类对父类继承过来的方法进行“方法覆盖”之后,子类对象调用该方法的时候,一定执行覆盖之后的方法。
当我们代码怎么编写的时候,在代码级别上构成了方法覆盖呢?
条件一:两个类必须有继承关系。
条件二:重写之后的方法和之前的方法具有:
相同的返回值类型、
相同的方法名、
相同的形式参数列表。
条件三:访问权限不能更低,可以更高。
条件四:重写之后的方法不能比之前的方法抛出更多的异常,可以更少。
几个注意事项:
注意1:方法覆盖只是针对于方法,和属性无关。
注意2:私有方法无法覆盖。
注意3:构造方法不能被继承,所以构造方法不能被覆盖。
注意4:方法覆盖只是针对于“实例方法”,“静态方法的覆盖”没有意义。
一定要注意:方法覆盖/重写的时候,建议将父类的方法复制粘贴,这样比较保险。
关于 Object 类中的 toString() 方法
1、toString() 方法的作用是什么?
作用:将“java 对象”转换成“字符串形式”。
2、Object类中toString()方法的默认实现是什么?
public String toString(){
return getClass().getName() + “@” + Integer.toHexString(hashCode());
}
toString:方法名的意思是转换成 String
含义:调用一个 java 对象的 toString() 方法就可以将该 java 对象转换成字符串的表示形式。
3、那么 toString() 方法给的默认实现够用吗?
不够,所以实体类常常重写 toString() 方法。
重写toString() 可以理解为是对象在打印输出时候的一种格式化。这样做符合业务逻辑,显示结果人性化。
方法覆盖
1、什么时候考虑使用方法覆盖?
父类中的方法无法满足子类的业务需求,子类有必要对继承过来的方法进行覆盖。
2、什么条件满足的时候构成方法覆盖?
第一:有继承关系的两个类
第二:具有相同方法名、返回值类型、形式参数列表
第三:访问权限不能更低。
第四:抛出异常不能更多。
3、关于 Object 类中 toString() 方法的覆盖?
toString() 方法存在的作用就是:将 java 对象转换成字符串形式。
大多数的 java 类 toString() 方法都是需要覆盖的。
因为 Object 类中提供的 toString() 方法输出的是一个 java 对象的内存地址。
至于 toString() 方法具体怎么进行覆盖?
格式自己定义,或者听(听项目要求的。)
4、方法重载和方法覆盖有什么区别?
方法重载发生在同一个类当中。
方法覆盖是发生在具有继承关系的父子类之间。
方法重载是一个类中,方法名相同,参数列表不同。
方法覆盖是具有继承关系的父子类,并且重写之后的方法必须和之前的方法一致:
方法名一致、参数列表一致、返回值类型一致。
多态的基础语法:
1、向上转型和向下转型的概念。
第一个:向上转型
子—>父 (upcasting)
又被称为自动类型转换:Animal a = new Cat();
第二个:向下转型
父—>子 (downcasting)
又被称为强制类型转换:Cat c = (Cat)a;
什么时候需要向下转型?
需要调用或者执行子类对象中特有的方法。
必须进行向下转型,才可以调用。
向下转型有风险吗?
容易出现 ClassCastException(类型转换异常)
怎么避免这个风险?
instanceof 运算符,可以在程序运行阶段动态的判断某个引用指向的对象,是否为某一种类型。
养成好习惯,向下转型之前一定要使用 instanceof 运算符进行判断。
注意:java中允许向上转型,也允许向下转型。
*****(五颗星)无论是向上转型,还是向下转型,两种类型之间必须有继承关系,没有继承关系编译器报错。
什么是多态?
多种形态,多种状态。编译和运行两个不同的状态。
java 程序分为编译阶段和运行阶段。
编译期叫做静态绑定。
运行期叫做动态绑定。
Animal a = new Cat();
// 编译的时候编译器发现 a 的类型是Animal,所以编译器会去Animal类中找 move() 方法
// 找到了,绑定,编译通过。但是运行的时候和底层堆内存当中的实际对象有关
// 真正执行的时候会自动调用:“堆内存中真实对象”的相关方法。
a.move();
多态的典型代码:父类型的引用指向子类型的对象。(java 中允许这样写代码!)
多态表示多种形态:
编译的时候一种形态。
运行的时候另一种形态。
2、多态指的是:
父类型引用指向子类型对象。
包括编译阶段和运行阶段。
编译阶段:绑定父类的方法。
运行阶段:动态绑定子类型对象的方法。
多种形态。
分析程序一定要分析编译阶段的静态绑定和运行阶段的动态绑定。只有编译通过的代码才能运行。没有编译,根本轮不到运行。
3、什么时候必须使用“向下转型”?
当你需要访问的是子类对象中“特有”的方法。此时必须进行向下转型。
java.lang.ClassCastException:类型转换异常。
运算符:
instanceof
第一:instanceof 可以在运行阶段动态判断引用指向的对象类型。
第二:instanceof 的语法:
(引用 instanceof 类型)
第三:instanceof 运算符的运算结果只能是:true/false
第四:Animal c = new Cat(); c 是一个引用,c 变量保存了内存地址指向了堆中的对象。
假设 (c instanceof Cat) 为 true 表示:
c 引用指向的堆内存中 java 对象是一个Cat。
假设 (c instanceof Cat) 为false表示:
c 引用指向的堆内存中的 java 对象不是一个Cat。
程序员要养成一个好习惯:
任何时候,任何地点,对类型进行向下转型时,一定要使用 instanceof 运算符进行判断。
java规范中要求的。这样可以很好的避免:ClassCastException。
if(al instanceof Cat){ // 如果al 是一只Cat
Cat y = (Cat)a6; // 再进行强制类型转换
y.catchMouse();
}
软件开发原则有七大原则(不属于java,这个开发原则属于整个软件业):
其中有一条最基本的原则:OCP(开闭原则)
什么是开闭原则?
对扩展开放(你可以额外添加,没问题),对修改关闭(最好很少的修改现有程序)。
在软件的扩展过程当中,修改的越少越好。
高手开发项目不是仅仅为了实现客户的需求,还需要考虑软件的扩展性。
什么是软件扩展性?
假设电脑桌的内存条部件坏了,我们可以买一个新的插上,直接使用。
这个电脑的设计就考虑了“扩展性”。内存条的扩展性很好。
面向父类型编程,面向更加抽象进行编程,不建议面向具体编程。因为面向具体编程会让软件的扩展力很差。
多态在开发中有什么作用?
非常重要:五颗星。。。。(多态你会天天用,到处用!!!!)
多态在开发中的作用是:
降低程序的耦合度,提高程序的扩展力。
public class Master{
public void feed(Dog d){}
public void feed(Cat c){}
}
以上的代码中表示:Master和Dog以及Cat的关系很紧密(耦合度高)。导致扩展力很差。
public class Master{
public void feed(Pet pet){}
}
以上的代表中表示:Master 和 Dog 以及 Cat 的关系就脱离了,Master 关注的是 Pet 类。
这样 Master 和 Dog 以及 Cat 的耦合度就降低了,提高了软件的扩展性。