黑马程序员---继承,抽象,接口

     -----------android培训java培训、java学习型技术博客、期待与您交流!---------

继承(extends

 

       继承是面向对象的一个重要特征

 

             当多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继那个类即可。

 

             这时,多个类可以称为子类,单独的这个类称为父类或者超类。

 

             例如:猫和豹子都是猫科动物, 那么就可以说描述猫的类,是子类;而描述所有猫科动物的类,就是父类。

 

      继承的好处:

 

           1.   提高了代码的重用性。

 

           2.   继承让类与类之间产生了关系,才有了多态的特性,事物可以被描述,而事物之间会有关系,继承就是关系中的一种。

 

             (在继承前要分析是否真的有继承关系)

   

       注意:千万不要为了获取其他类的功能,简化代码而继承, 必须是类与类之间所属关系才可以继承,所属关系:is a (谁是谁中的一种)

 

                     父类是子类中不断抽取而来的,而抽取不是抽取代码,而是分析问题。 


                     问题领域不同,思考方式和问题中对象之间的关系体现也不一样。

 

       判断所属关系的方式

 

              有没有关系先继承以下,继承完看父类中的内容是不是子类都应具备的,如果父类中有的功能不是子类所具备的,那么他们之间就不应该继承,

 

              在Java中只支持单继承,不支持多继承(但Java保存了这种机制,用多实现来体现,后面会学到)

 

              因为多继承容易带来安全隐患

 

                     当多个父类中定义了相同的功能,但功能内容不同时,

 

                     子类对象不确定要运行哪一个。

 

       Java支持多层继承,也就是一个继承体系,如儿子继承父亲,父亲继承爷爷

 

              代码体现:

 

                     class  a{}

 

                     class  b extends a {}

 

                     class c extends b {}

 

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

 

                     想要使用继承体系,先查阅体系中父类的描述, 因为父类中定义的是该体系中共性的功能,

 

                     通过了解共性功能就可以知道该体系的基本功能,那么这个体系已经可以基本使用了。

 

                     那么在具体调用时,要创建最子类对象,

 

                            一是因为父类有可能不能创建对象,

 

                            二是创建子类对象可以使用更多的功能,包括父类的基本功能,也包括子类的特有功能。

 

              简单一句话:查阅父类的功能,创建子类对象使用功能。

 

       注意:Java中一个孩子只能有一个父亲。

 

       聚集关系

 

              1.   聚合:球队里面有球员,球员是球队中的一个,球队中少一个球员也可以。

       

              2.   组合:事物的联系比聚合更紧密,身体和心脏的关系,不能分割。

               

       下面我们来看一个继承程序示例

 

/*
继承代码演示
*/

//定义一个Person类
class Person
{
	//定义姓名和年龄属性
	String name;
	int age;

	//定义构造函数,对姓名和年龄进行初始化
	Person(String name , int age)
	{
		this.name = name;
		this.age = age;
	}
	//人有睡觉功能
	void sleep()
	{
		System.out.println("睡觉了,呼呼呼~");
	}
	//人具备说话功能,说出自己的姓名年龄
	void speak()
	{
		System.out.println("我叫:"+name+",今年"+age+"岁。");
	}
}

//定义一个学生类,继承Person类
class Student extends Person
{
	//定义构造函数
	Student(String name , int age)
	{
		//super关键字代表父类,因为姓名和年龄在父类中进行了初始化动作,在这里可以被直接调用
		super(name , age);
	}

	//学生有特有的学习功能
	void study()
	{
		System.out.println("我爱学习~");
	}
}

class   ExtendsDemo
{
	public static void main(String[] args) 
	{
		//建立学生对象,并传入姓名和年龄
		Student s = new Student("潘机智",20);
		//子类调用父类的speak功能
		s.speak();
		//子类可以单独设置name的值,所以不安全,还是建议把属性私有化
		s.name = "张三";
		s.speak();
		//子类调用父类睡觉功能
		s.sleep();
		//子类调用自己特有的学习功能
		s.study();
	}
}

    运行结果如图


 

      
 

       子父类出现后,类中成员的特点:

 

              类中成员:变量,函数,构造函数。

 

                     1.   变量

 

                            如果子父类中出现非私有的同名成员变量时,子类要访问本类中的变量,用this。

 

                            子类要访问父类中的同名变量用super(super和this的使用几乎一致)

 

                            this代表本类对象的引用,super代表父类对象的引用。

 

                     2.   函数

 

                            当子父类中出现和父类函数一摸一样的函数时,子类对象调用该函数会运行子类函数内容,如同父类的函数被覆盖一样。

 

       这种情况是函数的另一个特征:重写(覆盖)

 

              当子类继承父类,沿袭了父类的功能到子类中,子类虽具备该功能,但功能的内容却和父类不一致,


              这是没有必要定义新功能, 而是使用覆盖特性,保留父类的功能定义,并重写功能内容。

 

           (可以用覆盖的特性给程序提高扩展性)

 

              注意:1.   子类覆盖父类,必须保证子类权限大于等于父类权限才可以覆盖

 

                          2.   静态只能覆盖静态(一般不用但要作为了解)

 

                          3.   子类中的私有方式不能被重写。

 

              记住:重载只看同名函数的参数列表,重写还要看子父类重名函数是否一模一样(包括返回值类型)

 

                     3.   构造函数

 

                            在对子类对象进行初始化时,父类的构造函数也会运行,

 

                            那是因为子类的构造函数默认第一行有一条隐式的语句 super();

 

                            会访问父类中空参数的构造函数,而且子类中所有构造函数第一行都是super();

 

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

 

                                   因为父类中的数据子类可以直接获取,所以子类对象建立时,需要先查看父类是如何对这些数据进行初始化的,


                                   所以子类在对象初始化时,要先访问一下父类中的构造函数。如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定。

 

       注意:super语句一定定义在子类构造函数第一行(谁用了super谁就是子类)


                    在子类的构造函数中至少有一个访问父类。

 

       子类的实例化过程

 

              结论:

 

                       子类所有的构造函数,默认都会访问父类中的空参数构造函数,因为子类每一个构造函数第一行都有一句隐式super()。

 

                       当父类中没有空参数构造函数时,子类必须手动通过super语句的形式来指定要访问的构造函数。

 

              当然:

 

                       子类的构造函数第一行也可以手动指定this语句来访问本类中的构造函数,(意思就是子类中每一个构造函数不一定都会访问父类中的构造函数)

 

                       子类中至少会有一个构造函数会访问父类中的构造函数。

 

       为什么this()super()不能在同一个构造函数中?

     

              因为它俩都必须要在第一行,因为初始化动作要先做。它俩都是初始化语句。

 

       集成的弊端,打破了封装。

 

       final关键字(最终)

 

              因为有了继承,所以子类可以随意的复写父类中的内容,这就出现了安全隐患,那么就需要用final关键字来避免这个隐患的发生。

 

              final作为一个修饰符具有以下的特点

 

                     1.   final可以修饰类,方法,变量。

 

                     2.   final修饰的类不可以被继承。

 

                           这样就避免被继承、被子类复写功能。

 

                     3.   final修饰的方法不可以被覆盖。

 

                     4.   final修饰的变量是一个常量,只能被赋值一次。

 

                           final既可修饰成员变量,又可以修饰局部变量。

 

                           当描述事物时,一些数据的出现值是固定的,那么这时为了增强阅读性, 给这个值起个名字,方便阅读,


                           而这个值不需要改变,所以先加上final修饰。

 

                          作为常量:常量的书写规范,所有的字母都大写,如果有多个单词组成,单词间通过“-”下划线连接。(写代码阅读性很重要)

 

                   5.   内部类只能访问被final修饰的局部变量。

 

抽象(abstract)

 

       Java中可以定义没有方法体的方法,该方法的具体实现由子类完成,该方法称为抽象方法,包含抽象方法的类就是抽象类。

     

       抽象类的由来:

 

        多个对象都具备相同的功能,但是功能具体内容有所不同,那么在抽取过程中,


        只抽取了功能定义,并未抽取功能主体,那么只有功能声明,没有功能主体的方法称为抽象方法。

 

  特点:

 

       1.   抽象方法一定在抽象类中

 

       2.   抽象方法和抽象类都必须被abstract关键字修饰。

 

       3.   抽象类不可以用new创建对象,因为调用抽象方法没有意义。

 

       4.   抽象类中的抽象方法要被使用,必须由子类复写其所有的抽象方法后,建立对象调用。


             如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。

 

          (所以如果不想子类为抽象类,那么就复写所有抽象方法)

 

    下面通过一个实例来说明抽象类的使用

 

/*
抽象类使用示例

雇员示例
需求:
公司中程序员有姓名,工号,薪水,工作内容。
项目经理除了程序员的所有功能外还有特有的奖金功能。
对给出的需求进行数据建模

分析:
先找出设计的对象,通过名词提炼法来发现他们之间的联系。
程序员
	属性:姓名,工号,薪水
	行为:工作
经理
	属性:姓名,工号,薪水,奖金
	行为:工作

程序员和经理不存在直接继承关系,虽然经理有程序员的属性,奖金是多出来的属性,可以后加进去
但是经理和程序员的工作内容不同,无法继承。

经理和程序员都是公司的雇员,所以可以将程序员和经理相同的地方进行抽取,建立体系
*/

/*
描述雇员,因为不知道雇员具体的工作内容,所以工作行为是抽象方法,雇员类也会是抽象类
*/
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 Programmer extends Employee
{
	//定义构造函数,初始化程序员
	Programmer(String name , String id , double pay)
	{
		//使用super语句,访问父类的构造方法
		super(name , id ,pay);
	}

	//重写父类的工作抽象方法,并定义程序员特有的功能主体
	public void work()
	{
		System.out.println("程序员 工作");
	}
}

//定义类描述经理,继承雇员类
class Manager extends Employee
{
	//定义经理特有的奖金属性,也是私有的
	private double bouns;
	//定义构造函数,初始化经理
	Manager(String name , String id , double pay , double bouns)
	{
		//使用super语句访问父类构造函数
		super(name , id , pay);
		//奖金是经理特有的,所以要单独定义
		this.bouns = bouns;
	}

	//重写父类的工作抽象方法,并定义经理特有的功能主体
	public void work()
	{
		System.out.println("经理-------工作");
	}
	//重写输出方法
}

class AbstractDemo 
{
	public static void main(String[] args) 
	{
		//创建程序员和经理对象,并调用工作方法
		new Programmer("潘机智" , "18" , 8000).work();
		new Manager("张经理" , "5" , 20000 , 5000).work();
	
	}
}

 

    抽象类与一般类的区别

 

       1.   抽象类和一般类没有太大不同,该如何描述事物就如何描述事物,只不过该事物出现了一些看不懂的东西,


             这些不确定的部分也是该事物的功能,需要明确出来。无法定义主体就通过抽象方法来表示。

 

       2.    抽象类比一般类多了个抽象函数,就是在类中可以定义抽象方法。

 

       3.   抽象类不可以实例化。

 

       4.    抽象类虽然不能创建对象,但是也有构造函数。可以让子类实例化调用。

 

       abstract修饰的函数不可以同时被一些关键词修饰

 

              1.   private

 

                     如果把抽象方法私有了,那子类就不能发现它,不能完成复写,而抽象方法就是用来复写的。

 

              2.   static

 

                     用static修饰过的抽象方法可以被类名直接调用,就不用创建对象,  抽象方法内容就没有意义了。

 

              3.   final

 

                     final就是用来防止父类被子类随意覆盖的,如果在抽象方法被final修饰,那么就无法完成覆盖。

 

              抽象有一个特殊的地方

 

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

 

                     也可用于模板方法设计模式

 

                     什么是模板方法呢?

 

                              在定义功能时,功能的一部分是确定的,但是有一部分是不确定的,

 

                              而确定的部分在使用不确定的部分,那么这时就将不确定的部分暴露出去, 由该类的子类完成。

 

       我们可以用模板方法完成一个小程序

 

/*
模板方法代码示例

需求获取一段程序的运行时间。

原理:将程序开始执行到之行结束的时间相减,得到运行时间。

思路:
1.想要获取运行时间的程序未知,可以将其抽象出去,通过继承重写的方法获得
2.利用java中提供的当前时间功能,得到程序开始和结束的时间,相减得到运行时间
*/

//定义获取运行时间的类,因为要获取运行时间的程序未知,所以是抽象类
abstract class GetTime
{
	//定义获取运行时间方法,因为不想被复写,所以加final修饰符
	public final void getTime()
	{
		//利用java中已有的方法获取程序开始执行时间
		long start = System.currentTimeMillis();
		//调用程序执行方法
		program();
		//获取程序结束执行的时间
		long end = System.currentTimeMillis();
		//结束时间-开始时间得到程序执行时间,并输出
		System.out.println("毫秒"+(end - start));
	}
	//定义程序执行方法,因为程序未知,所以是抽象方法
	abstract void program();
}

//定义程序类
class GetProgram extends GetTime
{
	//复写program方法,写入要获取时间的程序
	void program()
	{
		for(int x=0 ; x<1000 ; x++)
		{
			System.out.println(x+" ");
		}
	}
}

class  TemolateDemo
{
	public static void main(String[] args) 
	{
		//创建程序类对象
		GetProgram gp = new GetProgram();

		//调用获取程序运行时间方法
		gp.getTime();
	}
}

                                  运行结果





 

接口(interface)

 

   当一个抽象类中的方法都是抽象的时候,这时可以将该抽象类用另一种形式定义和表示,就是接口。

 

       接口的出现将“多继承”通过另一种方式体现出来,即多实现。

 

       格式

 

              interface接口名{}

 

              class用来定义类,interface用来定义接口(可理解为特殊类)

 

       接口格式的特点

 

              1.   接口中常见的定义:常量,抽象方法

 

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

 

                     成员常量:public static final

 

                     成员函数:public abstract

 

              3.   接口中的成员都是公共的权限,都用public修饰

 

                    在使用中,常量可以不写public static final,方法可以不写public abstract,编译时Java会自动添加这些修饰符


                    因为这是固定的格式修饰符。但是为了阅读性,一般都要写上。

 

       接口的特点

 

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

 

              2.   接口是程序的功能扩展。

 

              3.   接口可以用来多实现

 

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

 

              5.   接口与接口之间可以有继承关系

 

      接口与抽象类


              共性:都是不断向上抽取出来的抽象的概念。

 

           区别:


                      1.   抽象类体现继承关系,一个类只能单继承。

 

                            接口体现实现关系,一个类可以多实现。同时接口与接口之间有继承关系。

 

                       2.   抽象类是继承,是 "is a "关系。

                    

                              接口是实现,是 "like a"关系。

            

                       3.   抽象类中可以定义非抽象方法,供子类直接使用。

 

                              接口的方法都是抽象,接口中的成员都有固定修饰符。

 

                        4.   抽象类中可以私有变量或方法。

 

                               接口中的常量和方法都是public修饰的权限。

 

              接口的实例小程序   

/*
接口使用演示示例
*/

//定义一个抽象类,用于描述汽车
abstract class Car
{
	//定义每个车都有的run方法,但不知道具体是怎么跑,所以是抽象的
	abstract void run();
	//汽车都有的加油方法,汽车加油的动作是一样的,所以是一般方法
	void jiaYou()
	{
		System.out.println("汽车加油");
	}
}

//定义一个接口,敞篷
interface Changpeng
{
	//有的汽车具有敞篷功能
	public abstract void chang();
}

//定义一个类,描述宝马汽车,继承汽车类,实现敞篷接口
class BMW 
{
	//复写汽车的run方法,定义自己特有的功能主体
	void run()
	{
		System.out.println("宝马——提速快!");
	}
	//复写敞篷方法,定义自己特有的主体功能
	public void chang()
	{
		System.out.println("宝马*****敞篷");
	}
}

//定义一个类描述奥拓,继承汽车类,但是奥拓不能敞篷
class AoTuo extends Car
{
	//复写run方法,定义自己特有的功能主体
	void run()
	{
		System.out.println("奥拓——提速太慢");
	}
}

class CarDemo 
{
	public static void main(String[] args) 
	{
		//创建宝马对象
		BMW b = new BMW();
		//调用敞篷,跑方法
		b.chang();
		b.run();

		//创建奥拓对象,调用run方法
		new AoTuo().run();
	}
}


       运行结果



 



谢谢大家的观看~      

                -----------android培训java培训、java学习型技术博客、期待与您交流!---------

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值