为了提高代码的复用,将共性代码的代码进行抽取。 抽取到单独的一个类中进行封装。这时学生和工人类中就需要定义姓名和年龄了。可是学生和工人怎么获取到Person类中的姓名和年龄呢?可以让学生和Person产生一个关系,java中提供了一个关键字可以完成这个动作。extends(继承)。
例子1:父子类继承实例演示。
package cn.itheima.day17; class Person{ String name; int age; } class Student extends Person{ //Student就称之为子类,Person就成为父类,或者超类,或者基类。 //子类可以获取到父类中的成员(当然,不是全部。一会要逐一说明) public void study(){ System.out.println("study"); } } class Work extends Person{ public void work(){ System.out.println("work"); } } public class ExtendsDemo { public static void main(String[] args) { Student s = new Student(); s.name="heima"; s.age = 30; s.study(); } }
继承: 好处: 1)继承的出现,提高了代码的复用性。 2)继承的出现,让类与类之间产生了关系。而这个关系出现,就导致面向对象的第三个特征,多态的产生。 简单说:继承就是多态的前提。 Java中支持单继承,不支持多继承(其实更确切的说,是java将多继承进行改良,避免了安全隐患的产生)。 classA{void show(){System.out.println("a");} classB{}{void show(){System.out.println("b");} classC extends A,B{}//这就是多继承,一个子类同时有多个父类。在java中是允许的。 C c= new C(); c.show();//到底是运行a,还是运行b呢?所以这就是安全隐藏。为了避免,所以java不允许这样继承。 简单说:一个儿子只能有一个爹,不能同时有多个爹。 Java中可以存在多层(多重)继承。 classA{} classB extends A{} classC extends B{} 这时就出现了继承体系。 记住一个原则: 1)A类中定义是这个体系中的最共性的功能。 2)要使用这个体系的功能,建议建立最子类的对象来完成调用。 父类的由来都是子类不断向上抽取而来的。就代表着,A中定义的功能是该体系中最共性的功能。所以要了解这个体系的功能,只要参考A类中的功能即可。 了解后,需要建立对象来调用这些功能,那么建立哪个类的对象好呢? 建议建立C的对象,因为C中可以使用A类中的共性功能,也可以使用C类中的特有功能。 简单说:要使用一个继承体系,原则: 1)参阅父类功能, 2)建立最子类对象。 什么时候定义继承呢? 继承是用于程序设计的。只要一个事物是另一个事物的一种。就可以用继承体现。 例如:猫或者虎都是猫科这类事物中的一种。就是可以进行继承。狗或者狼都是犬科,也可以继承。猫科和犬科都是哺乳动物中的一种,也可以进行继承。简单的分析:怎么判断是另一个事物的中的一种呢? 那么可以先视为可以继承,用更简单的方式判断,一个类如果继承了另一个类,那么这个类是否应该具备另一个类的所有的成员。如果可以。继承有效,如果不可以,那么无法继承。
例子2:继承的错误表达实例演示。
class A{
void method1(){}
void method2(){}
}
class B {//extends A//如果为了提高复用,让B继承A这样,B就不用在定义method1()方法了,
//但是B也获取到它不应该具备的method2().那么这样的继承不可以存在。
//但是我们发现,虽然A和B之间没有继承关系,但是他们有共性的内容,那么就可以向上抽取。
void method1(){}
void method3(){}
}
class C{
void method1(){}
}
class A extends C{
void method2(){}
}
class B extends C{
void method3(){}
}
注意的是:不要为了获取另一个类中的部分功能,而继承。这样是仅仅为了提高复用性而继承,并没有判断事物之间的关系。这样不可取。 继承出现后,在子父类中的成员特点。 成员: 成员变量。 成员函数。 构造函数。 在子父类中成员变量的特点:当子父类中出现同名的成员变量时,这时为了区分两个变量,在子类,用this调用的是子类的变量。用 super调用的是父类中的变量。 super其实和this的用法很相似。 this代表是本类对象的引用 super代表的是父类的存储空间。 this可以区分局部变量和成员变量重名的情况。 super可以用于区分子父类中成员变量重名的情况。 属性在类中通常都是私有的,而且父类定义完了以后,子类一般是不会定义同名变量的。而是直接使用父类的变量即可所以开发并不常见。而面试却很常见。 注意:在访问上,子类不可以直接访问父类中的私有成员。可以通过间接的形式访问。
例子3:成员变量的实例演示。
package cn.itheima.day17;
class Fu{
protected int num = 3;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
class Zi extends Fu{
private int num = 5;
public void show(){
int num = 6;
System.out.println("num="+num);
System.out.println("num="+this.num);
System.out.println("num="+super.num);
}
}
public class ExtendsDemo2 {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
子父类中成员函数的特点: 特殊情况: 当子父类中出现一模一样的函数时,子类对象在调用该函数时,运行的是子类中的函数。父类中的函数好像被覆盖一样。这就是函数的另一个特性:覆盖(复写,重写)override 函数的另一个特性: 重载 overload 覆盖:在子父类中,如果出现一模一样的函数时,就会发生覆盖操作。
例子4:成员函数的实例演示。
package cn.itheima.day17;
class Fu1{
void show(){
System.out.println("fu show run");
}
}
class Zi1 extends Fu1{
void show(){
System.out.println("zi show run");
}
}
class Telephone{
void show(){
System.out.println("number");
}
void call(){
System.out.println("call");
}
}
/*在日后的几年中,手机也在不断的升级。来电显示功能,既可以显示号码,
* 又可以显示姓名,还可以显示大头贴。
解决办法:可以在原来的TelPhone类中对show功能的源代码进行修改。
这种解决动作,一旦修改,改的就不仅仅是一个代码而是一堆,那就是灾难。
解决方法二:重新对新手机进行描述,并定义新功能。但是有些功能没有变化,
只要继承自原来的老版手机可以获取该功能。新手机定义一个新的来电显示功能,
newShow.但是继承了TelPhone以后,新手机就有了两个来电显示功能。Show和newShow.
这样的设计是不合理,因为新手机没有必要具备show功能。
那么重新设计,应该是这样:
原手机中已有来电显示功能,而新手机,具备该功能,这时该功能的内容有所不同。
所以没有必要定义新的功能,只要沿袭原来的功能声明,并定义自己新的功能实现即可。
发现继承的好处,可以有利于程序的扩展。不需要改动原来代码的情况下,就可以实现
程序的功能扩展定义。*/
class NewTelephone extends Telephone{
void show(){
//System.out.println("number");//发现来电号码显示父类已经定义完了,
//子类没有必须重复定义,只要使用父类的已有功能即可。
//这时只要调用父类已有功能。因为父类和子类出现了一模一样
//的方法,为了区分,可以使用super关键字来完成。
super.show();
System.out.println("name");
System.out.println("picture");
}
}
public class ExtendsDemo3 {
public static void main(String[] args) {
Zi1 z = new Zi1();
z.show();
NewTelephone tp = new NewTelephone();
tp.show();
tp.call();
}
}
子父类中的构造函数的特点:通过结果发现,子类的构造函数运行前,父类的构造函数先运行了,为什么呢? 原因在于在子类的构造函数的第一行,其实就一条默认的隐式语句 super(); super():和this():用法是一样的。 this();调用了本类中的构造函数。 super():调用了父类中的构造函数。 子类的构造函数中为什么都有一个默认的super()语句呢? 子类在实例化的时候,因为子类继承了父类的成员数据,所以必须要先看父类是如何对自己的成员进行初始化的。子类的所有构造函数默认都会访问父类中空参数的构造函数。当父类中没有定义空参数的构造时,子类必须通过super语句或者this语句,明确指定要访问的子类中或者父类中的构造函数。 简单说:子类的构造函数无论如何,都必须要访问父类中的构造函数。要明确父类的初始化过程. super语句用于访问父类的初始化,而初始化动作要先完成,所以super语句必须定义在构造函数的第一行。那么就和曾经的this语句冲突了。因为this语句也要定义在构造函数的第一行。所以一个构造函数中,只能有一个要么this语句,要么super语句。而且不冲突,因为子类中至少会有一个构造函数会去访问父类中的构造函数。一样可以完成父类的初始化。这个就是子类的实例化过程。
例子5:子父类构造函数的初始化的实例演示。
package cn.itheima.day17;
class Fu2{
int num;
public Fu2(){
num = 4;
System.out.println("fu run");
}
public Fu2(int x) {
System.out.println("fu..."+x);
}
}
class Zi2 extends Fu2{
public Zi2() {
this(20);
System.out.println("zi run..."+num);
}
public Zi2(int x) {
//super(90);
System.out.println("zi2...."+x);
}
}
public class ExtendsDemo4 {
public static void main(String[] args) {
//Zi2 zi1 = new Zi2();
Zi2 zi2 = new Zi2(2);
}
}
继承的弊端:打破的封装性。如果恶意继承并进行不正确的覆盖,会导致原功能的错误。 不让其他类继承可以解决这个问题。这就需要一个关键字来完成 :final(最终) final:作为一个修饰符。 1)它可以修饰类,可以修饰方法,可以修饰变量。 2)final修饰的类是一个最终类,不可以被继承。 3)final修饰的方法不可以被覆盖。 4)final修饰的变量的是一个常量,只能被赋值一次。这个赋值指的是显示初始化赋值。 什么时候将变量修饰成final的呢? 通常在程序中会使用一些不会变化的数据。也就是常见的一些常量值。比如 3.14。那么这个数据直接使用是可以的,但是不利于阅读,所以一般情况下,都会被该数据起个容易阅读的名称。final double PI = 3.14; final修饰的常量定义一般都有规范书写,被final修饰的常量名称,所有字母都大写。如果由多个单词所组成,每一个单词间用“_”连接。
例子6:final关键字的应用实例。
package cn.itheima.day17;
/*final*/ class Fu3{ //final修饰的类不能被继承
/*final*/ void show(){ //final修饰的方法不能被覆盖
System.out.println("调用系统资源");
}
void method(){
System.out.println("method run");
}
}
class Zi3 extends Fu3{
final int num; //final修饰成员变量,最终化的值应该是显示初始化值。
public static final int aa = 45; //全局常量
public Zi3() {
num = 8;
}
public Zi3(int x) {
num = x;
}
void show(){
final int MY_NUMBER = 4;
System.out.println("haha...系统被干掉啦"+num);
}
}
public class FinalDemo {
public static void main(String[] args) {
Zi3 z=new Zi3();
z.show();
Zi3 z1=new Zi3(55);
z1.show();
}
}
//饿汉式
class Single{
private static final Single s = new Single();
private Single(){}
public static Single getInstance(){
return s;
}
}
*/
抽象类。 两个类DemoA DemoB里面都有共性的功能,可以进行抽取。可是功能的声明相同,但是功能的具体内容不同。这时,我们只对相同的功能声明进行抽取。而不抽取功能的内容。 抽象类就是子类不断向上抽取而来的,只抽取了子类的功能声明,而没有抽取子类的具体的功能内容。所以功能是抽象的,需要定义在抽象类中。 抽象类的特点: 1)抽象类和抽象方法必须用abstract关键字修饰。 2)抽象方法一定要存放在抽象类中。 3)抽象类不可以被实例化。也就是不可以通过该类建立对象。因为抽象类建立对象后,调用抽象方法是没有意义。 4)只有抽象类的子类将抽象类中的抽象方法全都覆盖掉,该子类就可以了建立对象了。如果只是部分覆盖,那么该子类还是一个抽象类。 实例演示: abstract class Demo{ abstract void show();//这时这个函数就看不懂了,因为没有方法体。 //这个就需要被标识一下成看不懂的函数。需要一个关键字来修饰一下。abstract(抽象)当类中出现了抽象方法后,该类也必须标识成抽象的。 } class DemoA extends Demo { void show(){ System.out.println("showa"); } } class DemoB extends Demo { void show(){ System.out.println("showb"); } } 抽象类什么时候定义。 当我们分析事物时,对对象进行描述时,其实就不断把对象中的共性内容向上抽取。在抽取过程中,发现对象具备相同的功能,但是功能的细节不同。这时在定义类时,该功能是没有具体的实现的,是由具体的对象来完成的。那么该功能就是抽象的。抽象类可以定义事物的共性内容,而且定义抽象功能,可以强迫子类去实现。
例子7: 预热班学生:学习。 就业班学生:学习。 可以对这两个的学生进行共性抽取。形成一个父类:学员。
package cn.itheima.day17;
/**
* 需求:预热班学生:学习。就业班学生:学习。
* 可以对这两个的学生进行共性抽取。形成一个父类:学员。
* @author wl-pc
*/
abstract class XueYuan{
abstract void study();
}
class YuRenBanXueYuan extends XueYuan{
@Override
void study() {
System.out.println("JAVA SE");
}
}
class JiuYeBanXueYuan extends XueYuan{
@Override
void study() {
System.out.println("JAVA EE");
}
}
public class AbstractDemo {
public static void main(String[] args) {
YuRenBanXueYuan xueyuan1 = new YuRenBanXueYuan();
xueyuan1.study();
JiuYeBanXueYuan xueyuan2 = new JiuYeBanXueYuan();
xueyuan2.study();
}
}
例子8:公司中程序员有姓名,工号,薪水,工作内容。项目经理除了有姓名,工号,薪水,还有奖金,工作内容。对给出需求进行数据建模。
package cn.itheima.day17; /* 需求:公司中程序员有姓名,工号,薪水,工作内容。 项目经理除了有姓名,工号,薪水,还有奖金,工作内容。 对给出需求进行数据建模。 分析: 这里有两个具体事物。 1,程序员 属性: name id pay 行为: work() 2,项目经理。 属性: name id pay bonus 行为:work() 发现这两个事物具备共性内容。为了提高代码的复用性。 两个事物间是不具备继承关系的。因为两个事物不存在谁是谁中一种。 但是,可以确定是无论程序员,还是经理,他们都是公司员工。 他们都是员工的一种。而且员工都具备一些基本属性和行为。 这时就可以将两个事物向上抽取,出一个员工类。该类中定义就是两个事物共性的内容。 */ abstract class Employee{ private String name; private String id; private double pay; public Employee(String name, String id, double pay) { super(); this.name = name; this.id = id; this.pay = pay; } public abstract void work(); } class Manager extends Employee{ private double bonus; public Manager(String name, String id, double pay, double bonus) { super(name, id, pay); this.bonus = bonus; } @Override public void work() { System.out.println("manager"); } } class Programmer extends Employee{ public Programmer(String name, String id, double pay) { super(name,id,pay); } @Override public void work() { System.out.println("code"); } } public class AbstractDemo2 { public static void main(String[] args) { Manager manager =new Manager("zhangsan", "12", 5000.00, 3000.00); manager.work(); Programmer programmer =new Programmer("lisi", "03", 7000.00); programmer.work(); } }
抽象类的一些细节: 1,抽象类中是否有构造函数。 有。只要是class定义的类,里面肯定有构造函数。抽象类中的构造函数,用于给子类提供实例化。其实抽象类和一般类没什么区别。该怎么描述事物,就怎么描述。只不过有些功能,是该类中无法确定的内容,所以比普通类多了抽象方法。 2,抽象类中是否可以不定义抽象方法。 可以不定义抽象方法,没有抽象方法的抽象类存在意义仅仅是不让该类创建对象。因为创建的没意义。这种情况在java awt中有具体体现。 3,抽象关键字和哪些关键字不可以共存。 final:如果方法被抽象,就需要被覆盖,而final是不可以被覆盖,所以冲突。 编译提示:非法的修饰符组合:abstract和 final private:如果函数被私有了,子类无法直接访问,怎么覆盖呢? 编译提示:非法的修饰符组合:abstract 和 private static : 不需要对象,类名既可以调用抽象方法。而调用抽象方法没有意义。 编译提示:非法的修饰符组合:abstract和 static 子父类中覆盖的一些细节。 1,子类覆盖父类必须要保证权限大于等于父类. 重点。一定要注意权限。 2,静态覆盖静态。开发的时候,一般没有静态覆盖静态的情况。 实例演示: final class Fu{ //abstract void show(); //staticvoid method(){} } class Zi extends Fu{ //publicvoid show(){} //staticvoid method(){} }