Java继承
一、继承(面向对象的第二个特征)
1、继承的概述
我们知道一个学生类是通过多个具体的学生个体,将他们当中的共性内容抽取出来所形成的类或者描述。工人类亦是如此。但是现在发现学生类和工人类中也有许多共性内容,因此将它们当中的共性的内容继续向上抽取形成一个Person类。让学生类和工人类和这个Person类具备某种关系,Person类中的非私有内容学生类和工人类中隐形的有一份,子类可以拿来使用。这种关系就是继承。这时候,学生类和工人类就叫做子类,Person类就叫做父类(或者是超类/基类)。
注意:
(1)不是任意两个类之间都可以继承,要看它们是不是具备所属关系。(is a)
(2)Java不支持多继承。但是java保留这种机制,用多实现来体现。Java支持多层继承。也就是一个继承体系。
为什么不支持多继承?
当多个父类中定义了相同的功能,功能的内容不同时,子类对象不确定要运行哪一个。
如何使用一个继承体系中的功能?
查阅父类功能,创建子类对象使用。
继承:
(1)提高了代码的复用性。
(2)让类与类之间产生了关系。有了这个关系,才有了多态的特性。
示例:
class Person
{
String name;
int age;
}
class Student extends Person
{
void study()
{
//String name;
//int age;
System.out.println(“good study”);
}
}
class worker
{
void work()
{
//String name;
//int age;
System.out.println(“good work”);
}
}
class ExtendsDemo
{
public static void main(String[] args)
{
Student s=new Student();
s.name=”zhangsan”;
}
}
2、聚集关系
类与类之间的另外一种关系就是聚集关系(has a)。分为聚合和组合。它们的紧密程度不一样。
聚合:比如许多球员组成一个球队。球员与球队的关系就是聚合关系。
组合:比如心脏是人身体的一部分。心脏与人体的关系就是组合关系。比聚合紧密程度更高,人缺少了心脏是不行的。
3、子父类中变量的关系
如果子类中出现非私有的同名成员变量时:
(1)子类要访问本类中的变量,用this.。this代表的是本类对象的引用。一般默认为this.。(若子类中没有变量,父类中有变量,子类要访问变量,一般默认为super.,那么这里涉及到父类引用指向子类对象,这就是后面要讲的多态。)
(2)子类要访问父类中的变量,用super.。super代表的是父类对象的引用。(涉及到父类引用指向子类对象,这就是后面要讲的多态)
但是在main()中记住下面的情况:(面试)
class Fu
{
Int num=5;
...
}
class Zi extends Fu
{
int num=8;
...
}
class Ceshi
{
public static void main(String[] args)
{
Fu f=new Zi();
System.out.println(f.num);//5
Zi z=new Zi()
System.out.println(z.num);//8
}
}
4、子父类中函数的特点-覆盖
现象:当子类出现和父类一模一样的函数只是函数体不同,当子类对象调用该函数,会运行子类函数的内容。如同父类的函数被覆盖一样。这种情况是函数的另一个特点:重写(覆盖)。这种情况在日后的开发设计中非常重要。就该这么写。这种方式有利于功能的扩展。避免日后回去修改代码。这个是很头疼的。
重写的特点:沿袭父类功能,定义子类特有的内容。(有利于功能的扩展)
什么时候覆盖:
(1)必须保证子类权限大于等于父类权限,才可以覆盖,否则编译失败。但是要注意:如果父类方法的权限是private,子类的方法权限虽大于它,但仍然不能覆盖。因为父类中私有的方法子类沿袭不了。更别说复写了。(一个类中的成员只要被私有了,就一定不被暴露出去,别人想要,只有通过间接的方式去使用,即使建立了对象也拿不到,因为私有只在本类内部有效。)这么写编译通过,运行失败。
(2)静态只能覆盖静态。
目前学到的权限有:public、protected、默认权限(介于前两者之间)
如果子类中出现非私有的同名成员函数时:
(3)子类要访问本类中的函数,用this.。this代表的是本类对象的引用。一般默认为this.。(若子类中没有变量,父类中有变量,子类要访问变量,一般默认为super.,那么这里涉及到父类引用指向子类对象,这就是后面要讲的多态。)但是这种情况如果是无条件的递归,即死循环,是不行的。
(4)子类要访问父类中的函数,用super.。super代表的是父类对象的引用。(涉及到父类引用指向子类对象,这就是后面要讲的多态)
ps:重载和重写的区别要注意。
重载只看同名函数的参数列表。
重写是子父类方法要一模一样。
观察下面的代码:
class Fu
{
int show()
{
System.out.println(“fu show”);
return 1;
}
}
class Zi extends Fu
{
void show()
{
System.out.println(“zi show”);
}
}
class Ceshi
{
public static void main(String[] args)
{
Zi z=new Zi();
z.show();
}
}
这个程序在编译的时候会提醒:正在尝试不兼容类型的覆盖。因为子类中show()并没有复写父类中的show(),子类继承了父类,则子类也有个返回值为int类型的show(),怎一个类中有两个show(),这两个show又不是重载关系,因此调用时虚拟机不知道该调用哪一个。所以这种代码在日后的开发设计中是不允许写出来的。
5、子父类中构造函数的特点-子类实例化过程
现象:我们在创建子类对象调用相应的构造函数进行初始化的时候,父类中的空参数构造函数也会被调用。
原因:原来每一个子类的构造函数的第一行都有一个隐式的语句:super();。我们可以这样理解,this(),this(...)
表示子类的构造函数调用子类中的构造函数,用于本类构造函数之间的调用;super(),super(...)表示子类的构造函数调用父类中的构造函数,用于子父类构造之间的调用。
需要注意的是当父类中没有空参数的构造函数或者时想调用父类中其他的构造函数时我们必须手动通过super(...)语句来进行访问,否则对于父类中没有空参数的构造函数的情况会编译失败。由于子类中的每一个构造函数的第一行都有一个隐式的super();语句,因此子类的构造函数的第一行也可以手动指定this语句来访问本来中的指定构造函数,子类中至少会有一个构造函数访问父类中的构造函数。但是由于this和super语句都只能放在子类中的构造函数的第一行,因此二者只能选其一,这也不要紧,刚讲过子类中至少会有一个构造函数访问父类中的构造函数。但是下面这种情况不允许出现:
class Zi extends Fu
{
Zi()
{
this(6);
System.out.println(“zi run”);
}
Zi(int x)
{
this();
System.out.println(“zi-------”+x);
}
}
陷入了死循环。
总结:子类的实例化过程:逐级向上找。子类找父类,父类找它的父类。Java中有一个特别牛的类,是所有类的直接父类或间接父类。
6、Final关键字
a. Final可以修饰类、方法、变量。
b. Final修饰的类不可以被继承。继承虽说提高了代码的复用性,但是被继承以后子类可以重写父类中的方法,打破了类的封装性。
c. Final修饰的方法不可以被覆盖。
d. Final修饰的方法不可以被覆盖。
e. Fianl修饰的变量是一个常量。只能赋值一次。既然是常量就要符合常量的命名规则。全局静态常量:public static final int A;
f. 内部类只能访问被final修饰的局部变量。
7、抽象类
我们前面讲到子父类中函数的特点,它的特点就是复写。子类只需要沿袭父类中的功能,子类自己定义特有的功能。那么我们父类中的方法的方法体是不是可以为空,因为子类几乎是用不到。因此从子类中将相同的功能进行向上抽取形成一个父类,方法体为空,但是{}会显得有点多余,因此我们可以将{}换成;,它也是函数结束的一种标志。那么抽取出来的这个方法,因为没有方法体,看不懂,应该加上abstract进行标识一下。称之为抽象方法。抽象方法作为父类的成员存在,这个类如果建立对象调用这个方法完全没有意义,所以抽象方法必须存放在抽象类中,因此父类也因该加上abstract修饰符进行修饰。告诉别人这个类是抽象的,自然不能实例化,即不能创建对象。当然抽象类中也可以没有抽象方法。
到这里,我们知道了。如果事物具备相同的功能,但是方法体不一样那就向上抽取成抽象方法,方法体不确定,主体由子类自己来完成。
以后可能还会见到本地方法,它也是一个没有方法体的方法。
抽象:看不懂。抽象的好处,强迫子类去复写方法,让子类有自己的内容容易看懂函数是干什么的。这也是上面说将{}改成;最合理的解释。
抽象类的特点:
A、抽象方法一定在抽象类中。
B、抽象方法和抽象类都必须被abstract关键字修饰。
C、抽象类不可以用new创建对象。因为调用抽象方法没意义。
D、抽象类中的方法要被被使用,必须由子类复写其所有的抽象方法后,建立子类对象使用。如果子类只覆盖了部分抽象方法,那么子类还是一个抽象类。
abstract关键字不能和那些关键字共存:
1、fianl:被final修饰的类不能有子类。而被abstract修饰的类一定是一个父类。
2、private:抽象类中的抽象方法被私有,不能被子类所知,就无法被复写。
3、static:如果static可以修饰抽象方法,那么连对象都省了,直接类名调用就可以了。可是抽象方法运行没意义。
练习1:
假如我们在开发一个系统时需要对员工进行建模,员工包含3个属性。姓名、工号以及工资。经理也是员工,出来含有员工的属性外,另外还有一个奖金属性。请使用继承的思想设计出员工类和经理类。要求类中提供必要的方法进行属性访问。
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 String name;
//private String id;
//private double pay;
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 vodi work()
{
System.out.println(“pro work”);
}
}
练习2:(模板方法模式)
什么是模板方法呢?
在定义功能时,功能的一部分是确定的,但是有一部分是不确定的,而确定的部分在使用不确定的部分,那么这时就将不确定的部分暴露出去,有该子类去完成。
通过获取一段程序运行的时间来讲解这个问题。由于只有这段程序是不确定的。因此可以:将这部分有一个抽象函数封装起来,其他强制不让子类被复写,用final修饰符。已知获取本机的当前时间的功能java已经提供,java调用windows里边的都东西。在java.lang包中的一个类System提供了currentTimeMillis(),以秒计时。还有一点注意下,这段程序不能被计算机“秒杀”了,需要能计算出时间来。
代码如下:
abstract class GetTime
{
public final getTime()
{
long start=System.currentTimeMillis();
runcode();
long end=System.currentTimeMillis();
System.out.println(“毫秒:”+end-start);
}
public void runcode();
}
class SubTime extends GetTime
{
public void runcode()
{
for(int x=0;x<4000;x++)
{
System.out.println(x);
}
}
}
class TemplateDemo
{
public static void main(String[] args)
{
SubTime st=new SubTime();
st.getTime();
}
}
8、接口
接口是指所有方法都是抽象的抽象类,不可实例化。因此接口可以看成是特殊的类,编译后也会生成.class文件。
class:用于定义类。
interface:用于定义接口。
接口中的常见定义:常量和抽象方法。
它们都有固定的格式:
常量:public static final...
抽象方法:public abstract...
这些修饰符不写的话,系统会默认。但是为了阅读性好建议写上。
子类可以继承父类:父类中有直接可以让子类拿去用的东西。
子类类可以实现父接口:父接口中没有直接可以让子类拿去用的东西。
以后的开发设计中:接口作为子类的扩展功能,子类需要就去实现,不需要就不去实现;父类中的东西子类只要一继承,子类中就会有,子类没得选择。
接口的特点:
(1)接口是对外暴露的规则。
(2)接口是程序的功能扩展。
(3)接口可以用来多实现。
(4)类与接口之间是实现关系,而且类可以继承一个类的同时实现多个接口。
(5)接口与接口之间可以有继承。且可以实现多继承。但是多继承的时候应该注意:如果已经继承了另外多个接口的接口被一个子类所实现,那么该子类必然要重写父接口中所有的抽象方法,如果这时候,父接口中有返回值类型不同的同名函数,如:int show();和void show();,那么子类创建对象以后,要调用show()方法,虚拟机就不知道该找哪一个了,因此不同接口中不能出现一个里面有int show(),另外一个里面有void show()类似这种情况。否则编译失败。
为什么子类不可以多继承却可以多实现呢?
因为父类中的可能有重复的方法,子类创建对象去调用该方法,就不知到底改掉用哪一个。而对于接口就不会出现这种问题,接口中的方法都没有主体。都需要在子类中实现,同样的方法只需要实现一次就行了,子类创建对象去掉用,就直接调用子类中实现的方法就行了。(不同接口中有相同的函数和不同接口中有返回值类型不同同名函数的情况不是一种情况,注意区别)
实例:
/**
抽烟的功能作为子类的扩展功能,可要可不要。因此定义到接口中。
*/
interface Smoking
{
public abstract void smoking();
}
/**
学习的功能和睡觉的功能作为子类的基本功能,子类必须有这些功能。因此定义到类中。
*/
abstract class Student
{
abstract void study();//学习的内容不确定,因此定义 //成抽象方法,由子类实现
void sleep()
{
System.ou.println(“sleep”);
}
}
//定义子类
class BaseStudent extends Student implements Smoking
{
void study()
{
Sytem.out.println(“base study”);
}
public void smoking()
{
System.out.println(“smoking”);
}
}
class ChongciStudnet extends Student
{
void study()
{
System.out.println(“chongci study”);
}
}
2015-11-07至 2015-11-20著