Java基础(面向对象三——继承、final关键字、抽象类、接口)

时间:2021-03-06 18:22:46


一、继承

继承:继承是面向对象的三大特点之一,当多个类中存在相同的属相和行为时,可以把这些共有的内容抽取到单独一个类中,那么多个类无需在定义这些属性和行为,只需要使用extends关键字继承抽取出来的那个类即可,而被继承的那个类我们通常称之为父类(或超类),而继承父类的类我们通常称之为子类,子类可以直接访问父类没有被私有化的属性和行为,而让类与类之间参生继承关系是使用extends关键字实现的,class Student extends Person{}   如:人可以分为学生、工人,而不管是学生还是工人都具备人的的共有的属性姓名和年龄,所以学生和工人就是子类,而人就是父类

继承出现带来的好处:

1、提高了代码的复用性

2、继承让类与类之间参生了关系,拥有了多态的特性

继承的体系结构:就是对事物进行不断地向上抽取,就出现了体系结构

1、要了解这个体系中最共性的内容,就要看最顶层的类

2、要使用这个体系的功能,就要使用最底层的类来创建对象

如何使用一个继承体系中的功能呢?

要想使用体系,首先要查阅父类中的描述,因为父类中定义的功能是该体系中最共性的功能,通过了解共性功能,就可以知道该体系的基本功能,那么该体系就可以基本使用了。

在具体调用时,要建立最子类对象,原因:

1、因为有可能父类不能创建对象(是一个抽象类)

2、创建子类对象可以使用更多的功能,包括基本的也包括子类特有的功能

简单一句话:就是查阅父类功能,建立子类对象,使用功能

继承的特点:

java只支持单继承(其实确切的说是java对多继承进行了优化,避免了安全问题),不支持多继承。

一个类只能有一个父类,不可以有多个父类

class Student extends Person{}    OK

class Student extends Person1,Person2{}   Error

java支持多重(层)继承(继承体系)

class A{}

class B extends A{}

class C exteds B{}

注:使用多层继承时,此时C类中就具备了A类和B类中所有的未私有化的属性和方法,

为什么Java不支持多继承?

因为类与类多继承的话,容易带来安全隐患。如:当多个父类中定义了相同功能,当功能内容不同时,子类对象不确定要运行哪个一个。但是Java保留了这种机制,并用另一种体现形式来完成。叫多实现

继承小练习:

没有学习继承前:

<span style="font-size:14px;">class Student 
{
String name;
int age;
public void study(){
System.out.println("学生学习");
}
}
class Work
{
String name;
int age;
public void work(){
System.out.println("工人工作");
}
}</span>

通过示例我们发现Student和Work这两个类都用于name和age这两个属性,出现了代码的重复,我们能不能把这两条属性抽取出来呢?

学习继承后:

<span style="font-size:14px;">//常见一个人的父类
class Person
{
// 抽取共有属性
 String name;
int age;
}
//创建一个学生类
class Student extends Person //通过extends关键字继承父类*有的属性
{
public void study(){ //子类特有的行为
System.out.println("学生学习");
}
}
class Work extends Person //通过extends关键字继承父类*有的属性
{
public void work(){ //子类特有的行为
System.out.println("工人工作");
}
}</span>

通过向上抽取共有属性,形成一个新的类Person,因为不管是Student还是Word都具备Person中的属性,所以我们可以通过继承(extends)来提高代码的复用性

注:

1、千万不要为了获取其他类的功能,简化代码而继承,必须是类与类之间有所属关系才可以继承,什么是所属关系呢?就是谁是谁中的一员,或谁是谁中的一种。比如:上面我们为什么不让Work去继承Student呢?,按理说我们让Work继承了Student也就等于Work中也具备了name和age这两个属性,但是Student就没有一些特殊的东西么?比如:学生是需要学习的,如果你让Work继承了Student那么是不是说明Work也具备了学习的行为呢?这样就不好了吧,所以说千万不要为了获取其他类的功能,简化代码而去继承

2、还有一点:Java中继承是先有的父类然后才有的子类,类的祖先是Object类

子父类出现后类中成员(成员变量、成员函数、构造函数)的特性

1、成员变量

当子类和父类中出现了非私有化同名变量时

<span style="font-size:14px;">class Person 
{
int num = 3;
}
class Student extends Person
{
int num = 5;
public void show(){
System.out.println("num="+num);
}
/*
注:当子类中出现父类相同变量时,默认打印的是子类中的num,因为num前面省略了this,当子类想要访问父类中的num时,
需要在要打印的num前面加上super
this:代表本类对象的引用
super:代表父类对象的引用
*/
}
class Demo
{
public static void main(String [] agrs){
Student s = new Student();
s.show();
}
}
</span>

2、成员函数

当子类中出现了非私有化同名函数时

<span style="font-size:14px;">class Person 
{
public void show(){
System.out.println("我是父类");
}
}
class Student extends Person
{
public void show(){
System.out.println("我是子类");
}
/*
注:当子类中出现父类同名函数时,当使用子类对象调用函数,会运行子类函数中的内容,就如同父类函数被覆盖一样,这种情况是函数的另一个特性:重写
重写:当子类继承了父类,沿袭了父类的功能到子类中,但是子类虽具备该功能,但是功能的内容却和父类的内容不一致时,这时候我们可以重写父类中的该功能,保留父类功能定义,重写功能主体
重写注意事项:1、子类继承父类,必须保证子类权限大于等于父类权限,才可以重写,否则编译失败
2、静态只能重写静态
3、要重写的函数必须和父类中的函数一模一样,包括返回值类型
*/
}
class Demo
{
public static void main(String [] agrs){
Student s = new Student();
s.show();
}
}</span>


注:重载和重写的区别:

重载:只看同名函数的参数列表

重写:要求和父类中的函数一模一样,包括返回值类型

3、构造函数

示例:

class Person 
{
person(){
System.out.println("Person被初始化了");
}
}
class Student extends Person
{
Student(){
System.out.println("Student被初始化了");
}
}
class Demo
{
public static void main(String [] args){
Student stu = new Student();
}
}
Java基础(面向对象三——继承、final关键字、抽象类、接口)
通过运行发现在对子类对象初始化时,父类的构造函数也会运行,那是因为子类的构造函数默认第一行有一条隐式的语句“super()”,super();会访问父类中的无参的构造函数,即子类中所有的构造函数默认都有super();,如果父类中自定义了构造函数,那么就需要手动在子类中的构造函数中加上对应的父类构造函数

注:为什么子类一定要访问父类中的构造函数呢?

因为父类中的数据子类可以直接获取,所以子类在建立对象时,需要先查看父类父类是如何对这些数据进行初始化的,所以子类对象在对象初始化时,要先访问一个父类中的构造函数,如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定,

注:super语句一定要放在子类构造函数的第一行

构造函数小总结:

子类中所有的构造函数,默认都会访问父类中的空参数的构造函数,因为子类每一个构造函数内第一行都有一个隐式的super();,当父类中没有空参数构造函数是,子类必须手动通过super语句形式来指定要访问父类 中的构造函数,当然子类的构造函数第一行也可以手动指定this();语句来访问本类中的构造函数,但就算是这样子,子类中也至少会有一个构造函数会访问父类中的构造函数

小知识点:

为什么this和super不能同时存在于一个构造函数中?

1、因为他们都是只能写在构造函数第一行

2、因为初始化动作要先做

小练习:

class Person 
{
String name;
int age;
//自定义构造函数
Person(String name,int age){
this.name = name;
this.age = age;
}
}
class Student extends Person
{
Student(String name,int age){
//因为父类中没有默认构造函数,所以需要手动指定super语句
super(name,age);
}
}

二、final关键字

由于继承的出现打破了对象的封装性,使得子类可以随便复写父类的中的功能 ,为了防止特有的功能不能被复写,该怎么实现呢?下面就来介绍一下final这个关键字的特殊用途吧

final关键字的特点:

1、可以修饰类,和成员

2、f被final修饰的类不可以被继承,为了避免被继承后复写功能,可以使用final修饰该类

3、被final修饰的方法不可以被复写

4、被修饰的变量是一个常量,只能赋值一次,final既可以修饰成员变量有可以修饰局部变量

当在描述事物时,一些数据的出现值是固定的,那么这时为了增强阅读性,都给这些值起个名字方便阅读,而这个值不需要改变,所以加上final修饰,作为常量,常量的书写格式为所有字母都大写,当有多个单词组成时,单词之间使用下划线隔开

5、内部类定义在类中的局部位置上时,只能访问该局部被final修饰的局部变量

示例:

final class Person    //加上final表示该类是一个最终类,不可以被继承
{
//表示这是一个全局常量
public static final double PI = 3.14;
//表示该方法不可以被子类复写
public final void show(){
System.out.println("不可以别复写的方法");
}
}
class Student extends Person//会编译出错,因为Person类被final修饰,不可以被继承
{
public void show(){}//会编译出错,因为show方法被final所修饰,不可以被复写
}
class Demo
{
public static void main(String[] args)
{
Student stu = new Student();
}
}

三、抽象类

抽象定义:

抽象就是从多个事物中将共性的,本质的内容抽取出来

 例如:狼和狗共性都是犬科,犬科就是抽象出来的概念

抽象类:

Java中可以定义没有方法主体的方法,该方法的具体体现由子类完成,该方法称为抽象方法,有抽象方法的类一定是抽象类

抽象方法的由来:

当对个类中出现了相同功能,但是功能主体不同,这时可以进行向上抽取,这时只抽取功能定义,不抽取功能主体,那么只对功能声明,没有功能主体的方法称为抽象方法

例如:狼和狗都有吼叫的方法,可是吼叫内容是不一样的。所以抽 象出来的犬科虽然有吼叫功能,但是并不明确吼叫的细节

抽象的好处:可以强制子类必须完成复写

抽象的特点:

1、抽象方法一定定义在抽象类中

2、抽象方法和抽象类都必须被abstract所修饰

3、抽象类不可以被实例化,因为调用抽象方法没意义

抽象类是具体事物抽取出来的,本身是不具体的,没有对应的实例

4、抽象类中的方法要被使用,必须由子类复写所有抽象方法后,建立子类对象调用,如果子类只覆盖了部分抽象方法,那么该类还是一个抽象类

抽象类练习:

    /* 
假如我们在开发一个系统时需要对员工进行建模,员工包含 3 个属性:
姓名、工号以及工资。经理也是员工,除了含有员工的属性外,另为还有一个
奖金属性。请使用继承的思想设计出员工类和经理类。要求类中提供必要的方
法进行属性访问。

员工类:name id pay

经理类:继承了员工,并有自己特有的bonus。

*/

//员工类,也是父类
abstract class Employee
{
private String name;//姓名
private String id; //工号
private double pay; //工资

//自定义构造函数初始化
Employee(String name,String id,double pay)
{
this.name = name;
this.id = id;
this.pay = pay;
}
//因为不管是普通员工还是经理都需要工作,只是工作的内容不同,所以只抽取功能定义,强制子类实现功能主体
public abstract void work();//抽象的工作方法

}

//经理类,继承员工类
class Manager extends Employee
{
private int bonus;//特有的奖金属性
Manager(String name,String id,double pay,int bonus)//子类的构造方法
{
super(name,id,pay);//调用超类中的构造器
this.bonus = bonus;
}
public void work()//经理类的工作方法内容
{
System.out.println("manager work");
}
}

//普通员工类,继承员工类
class Pro extends Employee
{
Pro(String name,String id,double pay)
{
super(name,id,pay);
}
public void work()//普通员工类的工作方法内容
{
System.out.println("pro work");
}
}

class AbstractDemo
{
public static void main(String[] args)
{
new Manager("manager","001",10000,2000).work();
new Pro("pro","020",5000).work();
}
}
抽象类和一般类的不同:

1、该如何描述事物就如何描述事物,只不过该事物出现了一些看不懂的东西,这些不确定的部分,也是该事物的功能,需要明确出现,但是无法定义主体,通过抽象方法来表示

2、抽象类比一般类多了抽象函数,就是可以再类中定义抽象方法,抽象类不可以被实例化

3、抽象类中可以不定义抽象方法,这样做仅仅是不让该类建立对象

4、抽象类和一般类一样都可以定义构造函数,但是抽象类是不可以实例化的,定义构造函数只是为了子类实例化调用

注:1、被abstract修饰的函数不能同时被privatefinalstatic修饰。

      原因:

                final:被final修饰的类不能有子类,修饰的方法不能被复写。而被abstract修饰的类一定是一个父类,别修饰的方法子类一定要复写 冲突。

                private:抽象类中的私有的抽象方法,不被子类所知,就无法被复写。

                           而抽象方法出现的就是需要被复写。

                static:如果static可以修饰抽象方法,那么连对象都省了,直接类名调用就可以了。

                            可是抽象方法运行没意义

模板方法设计:

模板方法设计就是在定义功能时,功能的部分是确定的,但是有一部分是不确定的,而确定的部分在使用不确定的部分,那么这时将不确定的部分暴露出去,由子类完成

注:模板方法设计中不确定的部分不一定都是抽象的

如:

    /* 
需求:获取一段程序的运行时间
原理:将程序开始执行到执行结束的时间相减就是程序的运行时间了
思路:1、由于想要获取的程序时未知的,可以将其抽象出去,通过子类继承复写的方式获得程序
2、利用java中提供的当前时间功能记录下程序开始执行和执行结束的时间,然后相减
*/
abstract class GetTime
{
public final void getTime()//加final表示不能被复写
{
long start=System.currentTimeMillis();//开始执行时间
program();
long end=System.currentTimeMillis();//执行结束时间
System.out.println("毫秒"+(end-start));
}
abstract void program();//由于程序不确定,故抽象出去
}

//要获取执行时间的程序
class GetProgram extends GetTime
{
void program()
{
for(int x=0;x<1000;x++)
System.out.print(x+" ");
}
}
class TemplateDemo
{
public static void main(String[] args)
{
new GetProgram().getTime();
}
}

四、接口

理解:可以理解为接口是一个特殊的抽象类,当抽象类中的方法都是抽象的,可以使用接口来表示,接口用interface来定义,格式为: interface 接口名{}

接口中的成员修饰符是固定的:

成员常量:public static final

成员函数:public abstract

注:1、接口中的成员都是public的

        2、在声明变量时,可以不写public static final,因为系统会默认给接口中的变量加上

        3、在声明函数时,可以不写public abstract  因为系统会默认给接口中的函数加上

接口的特点:

1、接口是对外暴露的规则

2、接口中的方法都是抽象的

3、接口中的变量都是全局静态常量

4、接口和抽象类一样,都不可以实例化

5、接口可以用来多实现(通过implements关键字完成),但是必须复写实现接口中的所有抽象方法,否则该类也会变成一个抽象类

6、接口的出现降低了耦合性

7、类与接口之间是实现关系,而且类可以 继承一个类的同时实现多个接口

8、接口与接口之间可以有继承关系

注:实现多个接口时,接口中可以出现同名函数,但是不可以出现返回值类型不同的同名函数,因为接口中定义的方法是抽象方法,没有方法体,所以可以多实现,但是多实现并不代表没有限制,当多个接口中出现返回值类型不同的同名抽象方法时,子类对象会像继承一样不知道要调用哪一个

接口和抽象类的区别:

相同点:都是向上抽取,只抽取功能定义,不抽取功能主体

不同点:1、抽象类中可以没有抽象方法,而接口中的方法都是抽象的

                2、抽象类中可以定义构造函数,而接口中不可以定义构造函数

                3、接口中的成员修饰符是固定的,而抽象类中成员修饰是可以改变的

                4、抽象类体现的是继承关系,而接口体现的是实现关系,同时接口与接口之间有继承关系

                5、抽象类中定义的是体系结构中的共性的内容。接口中定义的是对象的扩展功能。

                6、抽象类被继承表示的是:"is a"的关系。xx是yy中的一种。接口被实现表示的是: "like a"的关系。xx像yy中的一种。