继承
----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------ 继承 1:现实生活中一些事物之间是有一定的关系的。例如:学生是人,猫是猫科动物。 Java中的体现 1:描述一个学生类 1:姓名年龄属性,学习的方法 2:描述一个工人类 1:姓名年龄属性,工作的方法 3:描述一个人类 1:姓名年龄属性,说话的方法。 4:发现学生类和人类有关系,学生和工人都是人。所以人有的属性和行为学生和工人都会有。出现了类与类之间代码重复 class Person { String name; int age; // 静态变量(类变量)对象和对象之间的代码重复使用静态变量 static String country = "CN"; Person() { } void speak() { System.out.println(name + ":哈哈,我是人!!!"); } } // 让学生类和人类产生关系,发现学生is a 人,就可以使用继承 class Student { String name; int age; Student() { } void study() { System.out.println("姓名:" + name + "年纪:" + age + ":好好学习"); } } class Worker { String name; int age; void work() { System.out.println(name + ":好好工作,好好挣钱。"); } } class Demo1 { public static void main(String[] args) { Student s = new Student(); s.name = "jack"; s.age = 20; s.study(); Worker w = new Worker(); w.name = "rose"; w.work(); } } 总结:发现问题: 1:出现类和类有关系 它们之间的关系无法描述。 具备共性的代码出现重复定义。 1.1. 继承特点 1:描述类和类之间的关系 2:降低类和类之间的重复代码 1:降低对象和对象之间的代码重复使用静态变量 2:降低类和类之间的代码重复使用就继承 但是一定要符合is a 的关系。 1.2. extends关键字 继承是所有的OOP语言和Java语言中不可缺少的组成部分。 Java中的继承使用extends关键字实现。 1:学生是人,工人是人。 2:谁继承谁? 学生继承人,发现学生里的成员变量,姓名和年龄,人里边也都进行了定义。有重 复代码将学生类的重复代码注释掉,创建学生类对象,仍然可以获取到注释的成员。这就是因为继承的关系,学生类(子类)继承了人类(父类)的部分 class Person { String name; int age; // 静态变量(类变量)对象和对象之间的代码重复使用静态变量 static String country = "CN"; Person() { } void speak() { System.out.println(name + ":哈哈,我是人!!!"); } } // 让学生类和人类产生关系,发现学生is a 人,就可以使用继承 class Student extends Person { Student() { } void study() { System.out.println("姓名:" + name + "年纪:" + age + ":好好学习"); } } class Worker extends Person { void work() { System.out.println(name + ":好好工作,好好挣钱。"); } } class Demo1 { public static void main(String[] args) { Student stu = new Student(); stu.name = "jack"; stu.age = 20; stu.study(); stu.speak(); System.out.println(stu.country); System.out.println(Student.country); Worker worker = new Worker(); worker.name = "rose"; System.out.println(worker.country); worker.work(); worker.speak(); System.out.println(); } } 案例:测试继承 子类并能否继承父类中所有的成员? 提示: 1:父类定义完整的成员 静态成员变量,非静态成员变量(私有和非私有),构造方法(有参数构造无参数构造)静态方法,非静态成员方法。 2:创建子类继承父类。 尝试在子类中访问父类的private成员变量。 尝试一个子类继承多个父类。 3:创建子类对象,尝试通过子类对象名来访问父类的成员。 结论: 子类并不能继承父类中所有的成员 1.父类的所有的私有成员不能继承(private修饰的成员),但是可以通过父类的get set方法访问。 2.构造函数不能被继承 3.Java只支持单继承,不支持多继承。 3:如何使用继承 继承的出现是为了描述is a关系出现的,不要为了仅仅使用某个类的方法,就盲目的继承一个类。一个父类和它的子类之间必须是存在关系的。 1:不要为了使用继承而继承。工人和学生都有共性的成员,不要为了节省代码,让工人继承学生。 /* 如何使用继承:验证是否有 is a 的关系 例如:学生是人, 小狗是动物 注意:不要为了使用某些功能而继承,java只支持单继承 */ class DK { void Ip4S() { System.out.println("好玩"); } } class BGir extends DK { } class Demo { public static void main(String[] args) { new BGir().Ip4S(); } } 理解继承细节; 1:类名的设定,被继承的类称之为父类(基类),继承的类称之为子类 2:和传统的理解相反,子类并不是父类的一个子集。子类应当比父类包含更多的属性和行为。 3:父类的所有的私有成员不能继承 4:使用继承要符合is a关系。不要为了使用继承而继承 5:Java只支持单一继承,不支持多继承但是可以多重继承例如C继承B, B继承A 1.3. super关键字 子类继承它的父类中所有可以访问的成员变量和方法。子类不能够继承父类构造方法,但是父类的构造方法能从子类调用吗?学过了关键字this,都知道this关键字是一个当前对象引用通过this可以调用本类构造方法。如果当前对象有父类。如何在子类调用父类的构造方法?可以使用super关键字。那么super关键字的出现是指的是该类的父类。 super用途: super关键字可以用于调用父类的构造方法。 super关键字可以调用父类的成员 1.3.1. 子类访问父类的成员变量 案例: 1:定义Father(父类)类 1:成员变量int x=1; 2:构造方法无参的和有参的,有输出语句 2:定义Son类extends Father类 1:成员变量int y=1; 2:构造方法无参和有参的。有输出语句 1:this.y=y+x; 3:创建Son类对象 Son son=new Son(3); System.out.println(son.y); //4 class Father { int x = 1; Father() { System.out.println("这是父类无参构造"); } Father(int x) { this.x = x; System.out.println("这是父类有参构造"); } void speak() { System.out.println("我是父亲"); } } class Son extends Father { int y = 1; Son() { System.out.println("这是子类的无参构造"); } Son(int y) { this.y = y + x; System.out.println("这是子类的有参构造"); } void run() { super.speak(); // 访问父类的函数 System.out.println("我是儿子"); } } class Demo6 { public static void main(String[] args) { Son s = new Son(3); System.out.println(s.y);// 4 } } 4:子类对象为什么可以访问父类的成员。 1:this.y=y+x;有一个隐式的super super.x 5:super关键字作用 1:主要存在于子类方法中,用于指向子类对象中父类对象(不严谨)。 2:访问父类的属性 3:访问父类的函数 4:访问父类的构造函数 6:super注意 this和super很像,this指向的是当前对象的调用,super指向的是当前调用对象的父类。Demo类被加载,执行main方法,Son.class加载,发现有父类Father类,于是Father类也加载进内存。类加载完毕,创建对象,父类的构造方法会被调用(默认自动无参),然后执行子类相应构造创建了一个子类对象,该子类对象还包含了一个父类对象。该父类对象在子类对象内部。this super只能在有对象的前提下使用,不能在静态上下文使用。 1.3.2. super调用父类构造方法 1.创建一个子类对象时,子类构造方法会在开始执行时,先去调用它的父类的构造方法。 案例:验证该说法 提示:自定义父类(无参数构造),自定义子类(无参数构造),子类继承父类。 使用子类无参数构造创建对象。测试父类无参数构造是否执行。 2.子类默认调用父类无参数构造 如果在子类的构造函数中没有显示的调用父类的构造方法,编译器会自动的在子类构造函数第一行使用super调用父类的无参数构造。 案例:验证该说法。 提示:自定义父类(父类自定义有参数构造),自定义子类,子类继承父类。 总结:子类的构造函数默认第一行会默认调用父类无参的构造函数,隐式语句super(); 如果父类无参构造函数不存在,编译报错。 3.子类显式调用父类构造函数 在子类构造函数第一行通过super关键字调用父类任何构造函数。如果显式调用父类构造函数,编译器自动添加的调用父类无参数的构造就消失。构造函数间的调用只能放在第一行,只能调用一次。super() 和this()不能同时存在构造函数第一行。 案例:验证上述说法 1.3.3. super调用父类的方法 关键字不仅仅可以引用父类的构造方法,也可以引用父类的方法。格式如下; super.父类方法名(); class Father { void run(){ System.out.println("father"); } } class Son extends Father { public Son() { } void sleep(){ super.run(); } } 本案例中super是没有必要的,因为子类继承了父类。那么什么时候才需要使用super呢?在即将学到的重写中就可以看到。 4:super思考 如果开发者自定义了一个类,没有显示的进行类的继承,那么该类中成员函数是否可以使用super关健健字?可以使用,继承了Object类,Object类是所有类的父类。 class Demo7 { public void print(){ System.out.println(super.toString()); } public static void main(String[] args){ new Demo7().print(); System.out.println(); } } 5:继承练习 1.4. 重写(Override) 当子类继承父类,也就继承了父类的方法,有时候,子类需要修改父类中的方法。Java支持这种做法。叫做覆盖(overriding) 案例:测试覆盖 提示:父类和子类中定义同名的函数。 1:定义Father类 1:姓名,吃饭方法,吃窝窝头。 2:定义Son类,继承Father 1:Son类中不定义任何成员,子类创建对象,仍然可以调用吃饭的方法。 2:父类的吃饭的方法,Son不愿吃。Son自己定义了吃饭的方法。 1:此时父类中有一个吃饭的方法,子类中有2个吃饭的方法,一模一样,只是方法体不一样。 2:一个类中两个函数一模一样,是不允许的。 1:编译运行,执行了子类的方法。 2:使用父类的方法,在子类方法中,使用super.父类方法名。 class Father { String name; void eat() { System.out.println("吃窝窝"); } } class Son extends Father { public void eat() { // 继承可以使得子类增强父类的方法 System.out.println("来俩小菜"); System.out.println("来两杯"); System.out.println("吃香喝辣"); System.out.println("来一根"); } } class Demo8 { public static void main(String[] args) { Son s = new Son(); //执行子类的方法 s.eat(); } } 3:该现象就叫做重写(覆盖 override) 1: 在继承中,子类可以定义和父类相同的名称且参数列表一致的函数,将这种函数 称之为函数的重写. 4:前提 1:必须要有继承关系 5:特点 1:当子类重写了父类的函数,那么子类的对象如果调用该函数,一定调用的是重写过后的函数。 可以通过super关键字进行父类的重写函数的调用。 2: 继承可以使得子类增强父类的方法 6:细节 1: 函数名必须相同 2:参数列表必须相同 3: 子类重写父类的函数的时候,函数的访问权限必须大于等于父类的函数的访 问权限否则编译报错 4:子类重写父类的函数的时候,返回值类型必须是父类函数的返回值类型或该返回值类型的子类。不能返回比父类更大的数据类型: 如子类函数返回值类型是Object 1:定义 A B C 类 B extends A 2:Father类中定义A getA(); 3:Son 类中重写getA(); 方法,尝试将返回值修改为B,C ,Object 1:B编译通过 2:C 编译失败 ,没有继承关系 3:Object编译失败,比父类的返回值类型更大 class A { } class B extends A { } class C { } class Father { String name; void eat() { System.out.println("吃窝窝"); } // 定义一个函数,获取A类的对象, A getA() { return new A(); } } class Son extends Father { public void eat() { // 继承可以使得子类增强父类的方法 System.out.println("来两杯"); System.out.println("来俩小菜"); super.eat(); System.out.println("来一根"); } // B类是A类的子类 B getA() { return new B(); } } class Demo8 { public static void main(String[] args) { Son s = new Son(); s.eat(); } } 7:子类对象查找属性或方法时的顺序: 1:原则:就近原则。 如果子类的对象调用方法,默认先使用this进行查找,如果当前对象没有找到属性或方法,找当前对象中维护的super关键字指向的对象,如果还没有找到编译报错,找到直接调用。 8:重载和重写的不同 1:重载(overload): 1:前提: 所有的重载函数必须在同一个类中 2:特点: 函数名相同,参数列表不同,与其他的无关(访问控制符、返回值类型) 3:不同: 个数不同 、 顺序不同、 类型不同 2:重写(override): 1:前提: 继承 2:特点: 函数名必须相同、参数列表必须相同。 子类的返回值类型要等于或者小于父类的返回值 9:重写练习 描述不同的动物不同的叫法 1:定义动物类 有名字,有吃和叫的方法 2:定义狗继承动物重写父类吃和叫的方法 3:定义猫继承动物重写父类吃和叫的方法 class Animal{ int x=1; String name; void eat(){ System.out.println("吃东西"); } void shout(){ System.out.println("我是动物"); } } class Dog extends Animal{ void eat(){ System.out.println("啃骨头"); } void shout(){ System.out.println("旺旺"); } void eat(String food){ System.out.println("吃:"+food); } } class Cat extends Animal{ void eat(){ System.out.println("吃老鼠"); } void shout(){ System.out.println("喵喵"); } } class Demo9{ public static void main(String[] args){ Dog d=new Dog(); d.shout(); d.eat(); Cat c=new Cat(); c.shout(); c.eat(); System.out.println(); } } 1.5. instanceof 关键字 1:快速演示instanceof Person p=new Person(); System.out.println( p instanceof Person); 2:instanceof是什么? 1:属于比较运算符: 2:instanceof关键字:该关键字用来判断一个对象是否是指定类的对象。 3:用法: 对象 instanceof 类; 该表达式是一个比较运算符,返回的结果是boolea类型 true|false 3:案例 定义一个功能表函数,根据传递进来的对象的做不同的事情,如果是狗让其看家,如果是猫让其抓老鼠 1:定义动物类 2:定义狗类继承动物类 3:定义猫类继承动物类 4:定义功能根据传入的动物,执行具体的功能 5:instanceof好处 1:可以判断对象是否是某一个类的实例 package oop01; /* instanceof 比较运算符 检查是否是类的对象 1:可以判断对象是否是某一个类的实例 用法 对象 instanceof 类; 案例 定义一个功能函数,根据传递进来的对象的做不同的事情 如果是狗让其看家,如果是猫让其抓老鼠 1:定义动物类 2:定义狗类继承动物类 3:定义猫类继承动物类 4:定义功能根据传入的动物,执行具体的功能 */ class Animal { String name; void eat() { System.out.println("吃东西"); } void shout() { System.out.println("我是动物"); } } class Dog extends Animal { void eat() { System.out.println("啃骨头"); } void shout() { System.out.println("旺旺"); } } class Cat extends Animal { void eat() { System.out.println("吃老鼠"); } void shout() { System.out.println("喵喵"); } } class Demo11 { public static void main(String[] args) { Demo11 d = new Demo11(); // 对象 instanceof 类; System.out.println(d instanceof Demo11); d.doSomething(new Dog()); d.doSomething(new Cat()); } // 定义一个功能函数,根据传递进来的对象的做不同的事情 // 如果是狗让其看家,如果是猫让其抓老鼠 // 对象 instanceof 类; void doSomething(Animal a) { if (a instanceof Dog) { a.eat(); a.shout(); System.out.println("小狗看家"); } else if (a instanceof Cat) { a.eat(); a.shout(); System.out.println("抓老鼠"); } } } 练习: byte[] bs = new byte[] { 1, 2, 3 }; int[] is = new int[] { 1, 2, 3 }; String[] ss = new String[] { "jack", "lucy", "lili" }; System.out.println(bs instanceof byte[]); // true System.out.println(is instanceof int[]); // true System.out.println(ss instanceof String[]); // true // System.out.println(bs instanceof int[]); // 不可转换的类型 1.6. final关键字 常量: 1:定义静态方法求圆的面积 2:定义静态方法求圆的周长 3:发现方法中有重复的代码,就是PI,圆周率。 1:如果需要提高计算精度,就需要修改每个方法中圆周率。 4:描述一个变量 1:方法都是静态的,静态只能访问静态,所以变量也定义为静态的。 public static double PI=3.14; 1:如果定义为public后,新的问题,类名.PI=300; 改变了PI的值。 2:修改为private,修改为private后进行了封装,需要getset公共访问方法。 3:现有的知识不能解决这样的问题。可以使用final class Demo12 { public static final double PI = 3.14; // 静态常量 public static double getArea(double r) { return PI * r * r; } public static double getLength(double r) { return PI * r * 2; } public static void main(String[] args) { // Demo12.PI=300; 无法为最终变量 PI 指定值 System.out.println(Demo12.PI); } } 使用final 1:final关键字主要用于修饰类、类成员、方法、以及方法的形参。 final修饰成员属性: 1:说明该成员属性是常量,不能被修改。 public static final double PI=3.14; 1:public :访问权限最大 2:static :内存中只有一份 3:final :是一个常量 4:常量名大写 5:必须初赋值。 2:使用类名.成员。修改该成员的值,报错。--常量不能被修改 1:基本数据类型,final使值不变 2:对象引用,final使其引用恒定不变,无法让其指向一个新的对象,但是对象自身却可以被修改。 3:该关键字一般和static关键字结合使用 1:常量可以优先加载,不必等到创建对象的时候再初始化。 4:final和static可以互换位置 final修饰类: 1:该类是最终类,不能被继承。 1:将父类加final修饰,子类继承,就会报错。 2:查看api文档发现String类是final的。Integer类也是final的 1:为了防止代码功能被重写 2:该类没有必要进行扩展final修饰方法 final修饰方法 1:该方法是最终方法,不能被重写 2:当一个类被继承,那么所有的非私有函数都将被继承,如果函数不想被子类继承并重写可以将该函数final修饰 3:当一个类中的函数都被修饰为final时,可以将类定义为final的。 class Father2{ final void eat(){ System.out.println("eating...."); } } class Son2 extends Father2{ //该方法是最终方法,不能被重写 void eat(){ System.out.println("eating...."); } } class Demo12 { public static void main(String[] args) { // Demo12.PI=300; 无法为最终变量 PI 指定值 System.out.println(Demo12.PI); Son2 s=new Son2(); s.eat(); } final关键字修饰形参 1:当形参被修饰为final,那么该形参所属的方法中不能被篡改。 2: 项目中主要用于一些只用来遍历未知数据的函数。将未知变量声明为final的。增强数据的安全性。 class Demo14 { public static void main(String[] args) { System.out.println(); String[] arr = { "think in java", "java就业教程", "java核心技术" }; print(arr); } // 该方法,打印书名。 public static void print(final String[] arr) { //arr = null; ,无法重新赋值 for (int x = 0; x < arr.length; x++) { System.out.println(arr[x]); } } } 思考 为什么子类一定要访问父类的构造函数呢 1:子类继承了父类的属性,如果要使用父类的属性必须初始化,创建子类对象,必须先初始化父类属性 必须调用父类的构造方法。 2:为什么调用父类无参的构造函数 设计java语言之时,只知道编译器会默认添加无参的构造函数,有参的无法确定。 但是可以通过super关键字显式调用父类指定构造函数。 3:为什么super()this()语句要放在构造函数的第一行 子类可能会用到父类的属性,所以必须先初始化父类。 ----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------