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


一、继承

继承:继承是面向对象的三大特点之一,当多个类中存在相同的属相和行为时,可以把这些共有的内容抽取到单独一个类中,那么多个类无需在定义这些属性和行为,只需要使用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();
	}
}

通过运行发现在对子类对象初始化时,父类的构造函数也会运行,那是因为子类的构造函数默认第一行有一条隐式的语句“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中的一种。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值