目录
继承
继承
多个事物发现有共同的属性和行为,那么要提高代码的复用性,将相同的代码抽取,抽取出来放在一个单独的类中
class Student
{
String name;
int age;
void study()
{
System.out.println("study");
}
}
class Worker
{
String name;
int age;
void work()
{
System.out.println("work");
}
}
然后抽取
class Student
{
void study()
{
System.out.println("study");
}
}
class Worker
{
void work()
{
System.out.println("work");
}
}
class Person
{
String name;
int age;
}
代码抽取到了Person类,但是现在的三个类之间没有一点关系,我们现在要搭建一个桥梁,让它们之间产生关系,就需要继承,它是让类鱼类之间产生关系的桥梁,继承使用的关键字是extends
//将学生和工人的共性抽取到Person这个类中
class Person {
String name;
int age;
}
//学生通过extends继承Person,即Person为Student的父类
class Student extends Person{
//学生类中的自己特有方法
public void study(){
System.out.println(name+"同学正在学习。。。。");
}
}
//工人通过extends继承Person,即Person为工人的父类
class Worker extends Person{
//工人类中的自己特有方法
public void work(){
System.out.println(name+"工人正在工作。。。。");
}
}
//测试类
public class Test{
public static void main(String[] args) {
Student s = new Student();
s.name = "小明";
s.study();
Worker w = new Worker();
w.name = "张三";
w.work();
}
}
继承的好处:
1、继承的出现提高了代码的复用性,提高软件开发效率。
2、继承的出现让类与类之间产生了关系,提供了多态的前提。
单继承&多继承&多重继承
Java只支持单继承,不支持多继承。一个类只能有一个父类,不可以有多个父类。
Java支持多层继承(继承体系)
class A{}
class B extends A{}
class C extends B{}
定义继承不要为了获取类中的某一个功能而去继承。
日常开发中小技巧:
多层次继承出现的继承体系中,通常看父类中的功能,了解该体系的基本功能,建立子类对象即可使用该体系功能。
多继承虽然能使子类同时拥有多个父类的特征,但是其缺点也是很显著的,主要有两方面:
- 如果在一个子类继承的多个父类中拥有相同名字的实例变量,子类在引用该变量时将产生歧义,无法判断应该使用哪个父类的变量。
- 如果在一个子类继承的多个父类中拥有相同方法,子类中有没有覆盖该方法,那么调用该方法时将产生歧义,无法判断应该调用哪个父类的方法。
子类中成员变量的特点
成员变量:如果子类和父类出现不同名的成员变量,这使得访问是没有一点问题的。
class Fu
{
//Fu中的成员变量。
int num = 5;
}
class Zi extends Fu
{
//Zi中的成员变量
int num2 = 6;
void show()
{
//访问父类中的num
System.out.println("Fu num="+num);
//访问子类中的num2
System.out.println("Zi num2="+num2);
}
}
class Demo
{
public static void main(String[] args)
{
Zi z = new Zi(); //创建子类对象
z.show(); //调用子类中的show方法
}
}
Fu类中的成员变量是非私有的,子类中可以直接访问,若Fu类中的成员变量私有了,子类是不能直接访问的。
当子类和父类中的成员变量相同的时候,在子类中若要访问父类中的成员变量,必须使用super关键字
class Fu
{
//Fu中的成员变量。
int num = 5;
}
class Zi extends Fu
{
//Zi中的成员变量
int num = 6;
void show()
{
//子父类中出现了同名的成员变量时
//在子类中需要访问父类中非私有成员变量时,需要使用super关键字
//访问父类中的num
System.out.println("Fu num="+super.num);
//访问子类中的num2
System.out.println("Zi num2="+this.num);
}
}
class Demo5
{
public static void main(String[] args)
{
Zi z = new Zi(); //创建子类对象
z.show(); //调用子类中的show方法
}
}
流程解析:
- 当程序执行
new Zi()
;这时会加载Zi
类字节码文件,但由于Zi
类继承了Fu
类,因此需要将父类字节码文件加载进方法区。- 当
Zi
和Fu
的字节码加载完毕后。会执行new Zi()
;即在堆内存中创建Zi
类对象,并为其分配内存地址0x99
;对象的内存空间分配结束之后,开始成员变量默认初始化,此时需要注意Fu
的num
同样也会在此对象中。- 紧接着开始
zi
构造函数压栈,在zi
的构造函数中有隐式的super()
语句,此时Fu
的构造函数也会压栈,Fu
的构造函数压栈后会将Fu
的num
显示初始化。- 接着
Fu
构造函数弹栈
,执行Zi
的构造函数,Zi
的num显示初始化。接着Zi构造函数弹栈。此时Zi对象在堆中创建完成,并将内存地址0x99
赋值给main
方法中的z
引用。接着调用zi
的show()
方法,show方法压栈。
注意:super和this的用法相似,this:代表本类的对象引用。super:代表的父类内存空间,而不是父类的引用。
子父类中的成员函数特点–重写&应用
当程序通过对象来调用方法的时候,会先在子类中寻找对应的方法如果没有,就回去父类寻找,就近原则,所以,如果子类和父类中的方法重名了,一摸一样的,那肯定会调用子类的方法。
这也叫覆盖(重写方法)
class Fu
{
void show()
{
System.out.println("Fu show");
}
}
class Zi extends Fu
{
//子类复写了父类的show方法
void show()
{
System.out.println("Zi show");
}
}
重写的注意事项
- 子类方法覆盖父类方法,必须要保证权限大于等于父类的权限。
class Fu()
{ void show(){}
public void method(){}
}
class Zi() extends Fu
{ public void show(){} //编译运行没问题
void method(){} //编译错误
}
public >缺省 > protected > private
2.静态只能覆盖静态,或者被静态覆盖
3.必须一模一样,函数的返回值,函数名,还有参数列表都要一样
总结:当一个类是另一个类中的一种时,可以通过继承,来扩展功能。如果父类具备的功能内容需要子类特殊定义时,使用重写
子父类中构造函数特点&super
在子类创建对象的时候,父类的构造函数会先执行,因为子类的第一行会有默认的隐式super()语句,调用父类的构造函数。
因为继承了父类,所以创建对象的时候会先看父类是如何对内容进行初始化的。才可以方便使用父类的内容。
当父类中没有空参数构造函数是,子类的构造函数是必须有显示super语句指定要访问父类中的构造函数。
看个程序就懂了
class Fu extends Object
{
Fu()
{
//super();
//显示初始化。
System.out.println("fu constructor run..A..");
}
Fu(int x)
{
//显示初始化。
System.out.println("fu constructor run..B.."+x);
}
}
class Zi extends Fu
{
Zi()
{
//super();
System.out.println("zi constructor run..C..");
}
Zi(int x)
{
super();
System.out.println("zi constructor run..D.."+x);
}
}
class Demo
{
public static void main(String[] args)
{
new Zi();
new Zi(3);
}
}
就是子类的构造函数如果没有参数可以不书写super语句,但是如果有参数的话,就必须书写super语句来访问父类的构造函数。
子类实例化的细节
如果子类的构造函数第一行写了this调用了本类其他构造函数,这时是没有super调用父类的语句的,因为this()或者super(),只能定义在构造函数的第一行,因为初始化动作要先执行。
父类构造函数中也有隐式的super。记住:只要是构造函数默认第一行都是super(); Java体系在设计,定义了一个所有对象的父类Object 。
类中的构造函数默认第一行都有隐式的super()语句,在访问父类中的构造函数。所以父类的构造函数既可以给自己的对象初始化,也可以给自己的子类对象初始化。如果默认的隐式super语句没有对应的构造函数,必须在构造函数中通过this或者super的形式明确调用的构造函数。
final关键字
继承有很多的好处,提高了代码的复用性等,但随之也会有很多的问题,我们现在父类不想被继承,方法不i想被重写,但是当子类继承了之后,他就可以重写了,所以为了针对这种情况,Java里面有一个关键字final,意思是最终的不可变的,他是一个修饰符,可以修饰类和方法还有局部变量
final修饰类不可以被继承,但是可以继承其他类。
final修饰的方法不可以被覆盖,但父类中没有被final修饰方法,子类覆盖后可以加final。
final修饰的变量称为常量,这些变量只能赋值一次,定义的时候必须有初始值,不能变。
final修饰的引用类型变量,表示该引用变量的引用不能变,而不是引用所指的对象中的数据还是可以变化的;
抽象类
产生&特点&细节
当编写一个类的时候,我们往往会为该类定义一些方法,这些方法是用来描述一些类的行为方式,这些方法都有具体的方法体。
会有一种特殊的情况,就是功能声明是相同的,但是功能主体不同,这是可以进行抽取,但只是抽取方法声明,不抽取方法主体。那么这种方法就是抽象方法。
个人理解就是抽像方法是定义了一个方法框架,没有{}的方法。
关键字abstrac是修饰抽象方法的,他也是可以修饰类的,就叫抽象类
abstract class 犬科
{
abstract void 吼叫();//抽象函数。需要abstract修饰,并分号;结束
}
class Dog extends 犬科
{
void 吼叫()
{
System.out.println("汪汪汪汪");
}
}
class Wolf extends 犬科
{
void 吼叫()
{
System.out.println("嗷嗷嗷嗷");
}
}
抽象类的特点
- 抽象类和抽象方法都需要被abstract来修饰的,抽象方法一定要定义在抽象类中。
- 抽象类可以不创建实例,是因为调用抽象方法没有一点意义
- 只有覆盖了抽象类中所有的抽象方法之后,其子类才可以实例化,否则子类还是一个抽象类
抽象关键字abstract不可以和哪些关键字共存?
1、final:fianl修饰的类是无法被继承的,而abstract修饰的类一定要有子类。
final修饰的方法无法被覆盖,但是abstract修饰的方法必须要被子类去实现的。2、static:静态修饰的方法属于类的,它存在与静态区中,和对象就没关系了。而抽象方法没有方法体,使用类名调用它没有任何意义。
3、private:私有的方法子类是无法继承到的,也不存在覆盖,而abstract和private一起使用修饰方法,abstract既要子类去实现这个方法,而private修饰子类根本无法得到父类这个方法。互相矛盾。
接口
实现&定义
当抽象类中的方法全部都是抽象方法的时候,那这个抽象类就可以叫做接口了,接口就是极度的抽象类。
接口的定义必须使用关键字interface,接口中只能定义常量
interface class Demo
{
abstract void show1();
abstract void show2();
}
接口中成员的特点:
1、接口中可以定义变量,但是变量必须有固定的修饰符修饰,
public static final
所以接口中的变量也称之为常量。2、接口中可以定义方法,方法也有固定的修饰符,
public abstract
3、接口中的成员都是公共的。
4、接口不可以创建对象。
5、子类必须覆盖掉接口中所有的抽象方法后,子类才可以
实例化
。否则子类是一个抽象类。
接口(全部是抽象方法)–抽象类(部分抽象方法)–类(没有抽象方法)
interface Demo//定义一个名称为Demo的接口。
{
public static final int NUM = 3;
public abstract void show1();
public abstract void show2();
}
//定义子类去覆盖接口中的方法。子类必须和接口产生关系,类与类的关系是继承,类与接口之间的关系是 实现。通过 关键字 implements
class DemoImpl implements Demo//子类实现Demo接口。
{
//重写接口中的方法。
public void show1(){}
public void show2(){}
}
接口多实现
接口最重要的体现就是解决了继承无法多实现的问题,将多继承通过多实现完成了
interface A
{
void abstract show1();
}
interface B
{
void abstract show2();
}
class C implements A,B// 多实现。同时实现多个接口。
{
public void show1(){}
public void show2(){}
}
其实核心原因就是在于多继承父类中功能有主体,而导致调用运行时,不确定运行哪个主体内容。
而在多实现里 因为接口中的功能都没有方法体,由子类来明确,来源确定就是实现他们的实现类。
类继承类同时实现接口
子类继承父类拓展自己的功能,如果父类没有的功能,子类想要拓展其他的功能,这就可以通过接口来实现
class Fu
{
public void show(){}
}
interface Inter
{
public abstract void show1();
}
class Zi extends Fu implements Inter
{
public void show1()
{}
}
接口的多实现
多个接口之间可以使用extends进行继承。
interface A{
abstract void show();
}
interface B{
abstract void show1();
}
interface C{
abstract void show2();
}
interface D extends A,B,C{
abstract void show3();
}
继承了多个接口,可能这几个接口里面有同样的方法,但是子类需要来实现这些接口才可以使用,所以子类实现这些方法后调用的话不会出现调用的不确定性。
没有抽象方法的抽象类的由来
当我们需要继承一个接口,我们想要实现接口的一部分方法,但是我们话是需要全部实现了才可以使用,有一种方法就是,用一个抽象类,来继承这个接口,把接口里面的方法全都空实现,然后再有一个正常的类来继承这个没有抽象方法的抽象类,然后重写自己需要使用的方法即可。
//拥有多个方法的接口
interface Inter{
abstract void show();
abstract void show1();
abstract void show2();
abstract void show3();
}
//作为过度的抽象类,此类将接口的所有方法都实现,这里的实现是空实现
abstract class AbsInter implements Inter{
public void show(){}
public void show1(){}
public void show2(){}
public void show3(){}
}
/*
//此类直接实现Inter,但只使用其他show和show2方法,这样导致其他两个方法也要实现
//不符合我们的要求
class SubInter2 implements Inter{
public void show() {
System.out.println("show");
}
public void show1() {
}
public void show2() {
System.out.println("show2");
}
public void show3() {
}
}
*/
//此类只使用接口中的show和show2方法,只要覆盖抽象类中的show和show2即可
class SubInter extends AbsInter{
public void show(){
System.out.println("show");
}
public void show2(){
System.out.println("show2");
}
}
接口和抽象类的区别
相同点:
- 都位于继承的顶端,用于被其他实现或继承;
- 都不能实例化;
- 都包含抽象方法,其子类都必须覆写这些抽象方法;
区别:
- 抽象类为部分方法提供实现,避免子类重复实现这些方法,提供代码重用性;接口只能包含抽象方法,极度的抽象类;
- 一个类只能继承一个直接父类(可能是抽象类),却可以实现多个接口;(接口弥补了Java的单继承)
二者的选用:
- 优先选用接口,尽量少用抽象类
- 需要定义子类的行为,又要为子类提供共性功能时才选用抽象类