java的继承、多态、抽象类、接口
文章目录
前言
最近在看java核心技术卷一。看到继承和接口,因为我感觉它们都差不多,只是在应用和特性上有差别,就想把它们和在一起写一篇笔记
提示:以下是本篇文章正文内容
一、继承 ( extends )
- 在数据设计的时候有一个简单的规则判断是否应该将数据设计为继承关系 —— is-a规则。 如
男人是一个人,就可以考虑是否将数据设计为继承关系,这里人是父类,男人是子类
反之,人是一个男人则要慎重考虑,因为可以很轻松举出反例,如:人是一个女人,人是一个中性人。这里人是子类,女人,中性人是父类
// 父类----人
class Person{ //人
String name; //名字
String sexOrgans // 性器官
}
// 子类----男人
class Man extends Person{ // 男人
// 子类继承父类全部的字段(全局变量,类字段,属性)和方法
}
- 使用final修饰符修饰类表示这个类不可被继承,并且这个类中的所有方法自动地成为final方法。字段则不会自动的被final自动修饰
- 使用final修饰符修饰方法表示这个方法表示这个方法不可被子类重写
- 子类继承父类的所有public修饰符修饰的字段和方法
方法的签名
方法的名字和参数列表称为方法的签名
// setName(String name):就是方法的签名
public void setName(String name) {
this.name = name;
}
方法的重写/覆盖(Override)
重写也被称为覆写。对一个方法的重写必须发生在具有继承关系的两个类中,即子类对父类方法的重写。它要求两个方法的参数个数和类型以及返回值类型都必须一致,也就是方法的签名必须一致,这样才称为方法重写。
在Java SE 5.0 以前的版本中,覆盖父类的方法时,要求返回类型必须是一样的。现在子类覆盖父类的方法时,允许其返回类型定义为原始类型的子类型。
class A{
public void fun(String data){
System.out.println(data);
}
}
class B extends A{
// 方法的重写/覆盖
public void fun(String data){
System.out.println("XXXX" + data);
}
}
同时子类对父类的方法覆写对权限有要求,子类方法的访问权限必须大于或等于父类方法的访问权限。也就是说如果父类方法的访问权限是default,那么子类必须是default或者public,如果父类方法是public访问权限,那么子类就只能是public访问权限了。然而,实际开发中,绝大多数的方法都是public访问权限,所以覆写方法也是public。
关于访问权限,还有一点需要注意的是,父类方法如果是private,根据JAVA的封装性原则,private是不让其它任何类访问的。那么,此时子类能否对父类的private方法进行覆写呢?
答案是否定的。
动态绑定与静态绑定
在运行时能够自动地选择适当的方法,称为动态绑定
动态绑定只是针对对象的方法,对于属性无效。因为属性不能被重写。
// 虚拟机在运行时能够自动地选择适当的方法,son类中没有定义method方法虚拟机会去查询父类的方法表
1 public class Son extends Father
2 Son son = new Son();
3 son.method();
方法表
动态绑定搜索调用方法事件开销巨大,所以,虚拟机预先为每一个类计算了一个方法表,其中列出了该类中的所有方法的签名和要调用的实际方法,
在真正调用的时候虚拟机仅查找这个表。在子类方法表中没有查到会查父类的方法表
动态绑定的过程
一、动态绑定的过程:
例子:
1 public class Son extends Father
2 Son son = new Son();
3 son.method();
1. 首先,编译器根据对象的声明类型和方法名,搜索相应类(Son)及其父类(Father)的“方法表”,找出所有访问属性为public的method方法。
可能存在多个方法名为method的方法,只是参数类型或数量不同。
2. 然后,根据方法的“签名”找出完全匹配的方法。
方法的名称和参数列表称为方法的签名。
在Java SE 5.0 以前的版本中,覆盖父类的方法时,要求返回类型必须是一样的。现在子类覆盖父类的方法时,允许其返回类型定义为原始类型的子类型。
1 public Father getFather(){...} //父类中的方法
2 public Son getFather(){...} //子类覆盖父类中的getFather()方法
3. 如果是private、static、final 方法或者是构造器,则编译器明确地知道要调用哪儿个方法,这种调用方式成为“静态调用”。
4. 调用方法。
如果子类Son中定义了 method() 的方法,则直接调用子类中的相应方法;如果子类Son中没有定义相应的方法,则到其父类中寻找method()方法。
二、Demo
1. 子类重写父类中的方法,调用子类中的方法
1 public class Father{
2 public void method(){
3 System.out.println("父类方法:"+this.getClass());
4 }
5 }
6 public class Son extends Father{
7 public void method(){
8 System.out.println("子类方法"+this.getClass());
9 }
10 public static void main(String[] args){
11 Father instance = new Son();
12 instance.method();
13 }
14 }
15 //结果:子类方法:class Son
2. 子类没有重写父类中的方法,所以到父类中寻找相应的方法
public class Father{
public void method(){
System.out.println("父类方法:"+this.getClass());
}
}
public class Son extends Father{
public static void main(String[] args){
Father instance = new Son();
instance.method();
}
}
//结果:父类方法:class Son
三、动态绑定只是针对对象的方法,对于属性无效。因为属性不能被重写。
1 public class Father{
2 public String name = "父亲属性";
3 }
4 public class Son extends Father{
5 public String name = "孩子属性";
6
7 public static void main(String[] args){
8 Father instance = new Son();
9 System.out.println(instance.name);
10 }
11 }
12 //结果:父亲属性
二、java 中,对象变量是多态的
- 一个对象变量可以指示多种实际类型的现象称为多态
- 在继承关系中,父类对象变量可以引用自身,也可以引用任何它的子类的对象
- 抽象类中,父类对象变量可以指向非抽象对象
- 接口中,接口的对象变量可以指向实现接口的对象
- 父类对象变量可以指向子类称为向上转型
- 子类对象变量可以通过强转父类从而达成子类对象变量指向父类,但有一个前提,这个父类变量本身就是子类
public class Test_1 {
public static void main(String[] args) {
// 父类对象变量可以引用自身,也可以引用任何它的子类的对象 ----多态
Human hm = new Man(); // 父类对象变量指向子类 向上转型
hm.output1(); // 输出:多态1
// Man m = new Human(); //编译不通过 子类指向父类----向下转型
//Man m = (Man)new Human(); // 编译通过,运行时异常(java.lang.ClassCastException)。强制向下转型
// m.output1();
// hm对象变量本身指向的就是Man子类
// Man m2 = hm; // 直接指向编译不通过
Man m2 = (Man)hm;
m2.output2(); // 输出:多态2
}
}
// 父类
class Human{
public void output1() {
System.out.println("多态1");
}
}
// 子类
class Man extends Human{
public void output2() {
System.out.println("多态2");
}
}
三、抽象类(abstract)
1、什么是抽象类?
- 类和类之间具有共同特征,将这些共同特征提取出来,形成的就是抽象类
- 类本身是不存在的,所以抽象类无法创建对象<无法实例化>
2、抽象类属于什么类型?
- 抽象类也是属于引用数据类型
3、抽象类怎么定义?
语法:
[修饰符列表] abstract class 类名{
类体;
}
4、抽象类是无法实例化的,无法创建对象的,所以抽象类是用来被子类继承的
5、final和abstract不能联合使用,这两个关键字是对立的
6、抽象类的子类可以是抽象类
7、抽象类虽然无法实例化,但是抽象类有构造方法,这个构造方法是供子类使用的
8、抽象类关联到一个观念:抽象方法。什么是抽象方法呢?
- 抽象方法表示没有实现的方法,没有方法体的方法。
- 如:public abstract void doSome( );
- 抽象方法的特点是:
- 特点1:没有方法体,以分号结尾
- 特点2:前面修饰符列表中有abstract关键字
9、抽象类中不一定有抽象方法,抽象方法必须出现在抽象类中
10、非抽象类继承抽象类必须将抽象类中的抽象方法重写
//抽象类的子类可以不实现继承的方法,但是这个子类也要声明为abstract。一种是子类实现抽象类的全部方法
abstract class Person{
int age;
public int getAge(int age) {
return age;
}
public abstract String fun();
}
abstract class Student extends Person{
}
class Worker extends Person{
public String fun() {
return null;
}
}
四、接口( interface )
- 接口是一种"引用数据类型"
- 接口是完全抽象的
- 接口的语法:[修饰符列表] interface 接口名{ }
- 接口支持多继承
- 接口中只有常量+抽象方法
- java8中允许在接口增加静态方法
- 接口中所有的元素都是public修饰的
- java9中接口可以是private修饰符修饰,可以是静态方法和实例方法,私有方法只能在接口本身的方法中使用,所以它们的用法有限,只能作为接口中其它方法的辅助方法
- 接口中抽象方法的public abstract可以省略
- 接口中常量的public static final可以省略
- 接口中方法不能有方法体
- 一个非抽象的类,实现接口的时候,必须将接口中所有方法加以实现
- 一个类可以实现多个接口
- extends和implements可以一起使用,extends在前,implements在后,implements是一个类实现一个接口用的关键字,是用来实现接口中定义的抽象方法
- 使用接口,写代码的时候,可以使用多态(父类型引用指向子类型对象)
接口的实现
- 类和类之间叫做继承,类和接口之间叫做实现,继承使用extends关键字;实现使用implements关键字完成
- 当一个非抽象的类实现接口的话,必须将接口中所有的抽象方法全部实现(重写)
public class Test02{
public static void main(String[] args){
//抽象方法无法实例化
//使用多态,子类型引用指向子类型的对象
MyMath mm = new MyMathImpl();
//调用接口里面的方法(面向接口编程)
int result_1 = mm.sum(10,20);
int result_2 = mm.sub(20,13);
System.out.println("加:" + result_1 + ",减法:" + result_2);
}
}
//接口:特殊的抽象类,完全抽象的
interface MyMath{
//常量,public static final省略
double PI = 3.1415;
//抽象方法;没有方法体
int sum(int a,int b);
int sub(int a,int b);
}
//实现MyMath接口
class MyMathImpl implements MyMath{
//要写上public ,不写系统会自动写上比public 权限低的关键字
public int sum(int a,int b){
return a+b;
}
public int sub(int a,int b){
return a-b;
}
}
接口是为了弥补Java中类与类只支持单继承带来的缺陷。单继承是为了简单出现的,现实世界中存在多继承
public class Text03{
public static void main(String[] args){
R r = new R();
M m = (R)m;
//编译不会报错
//运行时可能会出现ClassCastException异常
//继承是向下转型,两者必须要有继承关系,但是接口不一样,编译不会报错,运行可能会报错
//向下转型养成好习惯,转型前用if + instanceof进行判断
m.mm_1();
}
}
interface R{
void mm_1();
}
interface M{
void mm_2();
}
exdents和implements
public class Text04{
public static void main(String[] args){
Flyable f1 = new Cat();
f1.fly();
//同一个接口
Flyable f2 = new Pig();
//调用同一个fly()方法,最后的执行效果不同
f2.fly();
}
}
//动物类:父类
class Animal{
}
//接口通常提取的是行为动作
//能插拔的是接口
//接口:飞翔的接口
interface Flyable{
void fly();
}
//动物子类:猫类
class Cat extends Animal implements Flyable{
public void fly(){
System.out.println("有了翅膀会飞的猫!!!");
}
}
//飞猪
class Pig extends Animal implements Flyable{
public void fly(){
System.out.println("会飞的猪");
}
}
//蛇类,不想实现飞的功能,就可以不实现Flyable接口
//这样蛇就不会有翅膀了
calss Snake extends Animal{
}
//没写也是有继承的,默认继承Object
class Fish extends Object implements Flyable{
}
默认方法和静态方法
java8中接口提供了静态方法和默认方法(default)
默认方法可以调用其他方法,实现类可以直接调用默认方法,在接口中抽象方法不用实现,默认方法必须实现
为什么需要静态方法和默认方法
当一个接口需要扩展一个功能(方法)时,所有的实现类也要实现这个这个功能
默认方法的最典型用法是逐步为接口提供附加功能,而不破坏实现类。
此外,它们还可以用来为现有的抽象方法提供额外的功能
默认方法冲突
1、超类优先 一个接口继承一个超类,超类中有和接口中同名同参的方法,接口中的默认方法会被忽略
2、一个类实现两个接口,这个两个接口有同名同参的默认方法 T1.super.cpu();
3、一个类实现两个接口,一个接口为默认方法,一个接口为抽象方法,同名同参。程序保存,程序员必须解决这个二义性,保持程序的一致性
interface Phone{
// 接口没有字段,有常量
String NAME = "phone";
// 默认方法
default int price() {
return 2000;
}
// 静态方法
static String defaultName() {
return "黑色";
}
String name();
String colour();
}
interface T1 extends Phone{
default String cpu(){
return "晓龙";
}
}
interface T2 extends Phone{
default String cpu() {
return "联发科";
};
}
class SelectParameter implements T1,T2{
@Override
public String name() {
// TODO Auto-generated method stub
return null;
}
@Override
public String colour() {
// TODO Auto-generated method stub
return null;
}
// 解决一个类实现两个接口,这个两个接口有同名同参的默认方法
@Override
public String cpu() {
// TODO Auto-generated method stub
return T1.super.cpu();
}
}
声明
部分内容转载自
https://www.cnblogs.com/kuillldan/p/5905572.html
https://www.cnblogs.com/ericdream/archive/2012/01/07/2315697.html
《java核心技术卷一》
本文深入讲解Java中的继承、多态、抽象类与接口等核心概念,包括它们的基本定义、使用场景及注意事项。

2321

被折叠的 条评论
为什么被折叠?



