本章主要介绍继承、super、从写 、instanceof、final关键子。
面向对象
继承
概念
继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
1:学生 是人
2:狗 是动物
3:球队 包含 球员 整体与部分的关系,部分可以删除和增加
4:笔记本包含 cpu 整体与部分的关系,部分不可以删除和增加
5:航母编队 包含(航母 护卫舰 驱逐舰 舰载机 核潜艇)
1:如果没有继承,出现类和类的关系无法描述
2:如果没有继承,类和类之间有关系会出现类和类的描述代码的重复
继承特点好坏处
1:描述类和类之间的关系
2:降低类和类之间的重复代码
1:降低对象和对象之间的代码重复使用静态变量
2:降低类和类之间的代码重复使用继承
好处
- 提高了代码的复用性
- 简化了代码的维护工作
- 是构造多态的基础
坏处
- 不符合低耦合高内聚的思想,让类与类的关系更紧密,类与类的关系复杂
- 打破了封装性
封装的好处在只能从类暴露的方法去访问私有变量,但是继承则可以从子类方法通过继承的父类方法间接去访问父类的私有成员。
另外,如果要想让子类继承共性的属性(成员变量),则不能进行封装,因为private的成员变量和方法都不能被继承,这构成了冲突。
123456789101112 > 继承细节;> 1:类名的设定,被继承的类称之为父类(基类),继承的类称之为子类> 2:子类并不能继承父类中所有的成员> 1:父类定义完整的成员 静态成员,非静态,构造方法。静态变量和静态方> 法都可以通过子类名.父类静态成员的形式调用成功。> 2:所有的私有成员不能继承,private修饰的成员。> 3:构造函数不能被继承>> 3:如何使用继承> 1:不要为了使用继承而继承。工人和学生都有共性的成员,不要为了节省代> 码,让工人继承学生。>
>
继承的使用场景
存在“is a”的关系时,例如猫is a 动物,狗is a 动物。
super关键字
1234567891011121314151617181920212223242526272829303132333435363738394041424344 > 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> }> }>
>
子类对象为什么可以访问父类的成员。
1:this.y=y+x;有一个隐式的super super.x
super关键字的作用
super关键字作用
1:主要存在于子类方法中,用于指向子类对象中父类对象。
2:访问父类的属性
3:访问父类的函数
4:访问父类的构造函数
super注意事项
this和super很像,this指向的是当前对象的调用,super指向的是当前调用对象的父类。Demo类被加载,执行main方法,Son.class加载,发现有父类Father类,于是Father类也加载进内存。类加载完毕,创建对象,父类的构造方法会被调用(默认自动无参),然后执行子类相应构造创建了一个子类对象,该子类对象还包含了一个父类对象。该父类对象在子类对象内部。this super只能在有对象的前提下使用,不能在静态上下文使用。
子类的构造函数默认第一行会默认调用父类无参的构造函数,隐式语句super();
1:父类无参构造函数不存在,编译报错。
123456 > Son(int y) {> //super();隐式语句> this.y = y + x;> System.out.println("这是子类的有参构造");> }>
>
在子类构造函数第一行通过super关键字调用父类任何构造函数。如果显式调用父类构造函数,编译器自动添加的调用父类无参数的构造就消失。构造函数间的调用只能放在第一行,只能调用一次。super() 和this()不能同时存在构造函数第一行。
12345678910111213 > Son(int y) {> super(y);// 子类显式调用父类构造函数> this.y = y + x;> System.out.println("这是子类的有参构造");> }>> Son(int y) {> this(); //不能同时存在构造函数第一行> super(y);> this.y = y + x;> System.out.println("这是子类的有参构造");> }>
>
重写(Override)
12345678910 > 定义Father类> 1:姓名,吃饭方法,吃窝窝头。> 2:定义Son类,继承Father> 1:Son类中不定义任何成员,子类创建对象,仍然可以调用吃饭的方法。> 2:父类的吃饭的方法,Son不愿吃。Son自己定义了吃饭的方法。> 1:此时父类中有一个吃饭的方法,子类中有2个吃饭的方法,一模一样,只是方法体不一样。> 2:一个类中两个函数一模一样,是不允许的。> 1:编译运行,执行了子类的方法。> 2:使用父类的方法,在子类方法中,使用super.父类方法名。>
>
1234567891011121314151617181920212223 > 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编译失败,比父类的返回值类型更大
1234567891011121314151617181920212223242526272829303132333435 > 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:定义猫继承动物重写父类吃和叫的方法
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 > 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();> }> }>
instanceof关键字
1:快速演示instanceof
2:instanceof是什么?
1:属于比较运算符:
2:instanceof关键字:该关键字用来判断一个对象是否是指定类的对象。 3:用法:
对象 instanceof 类;
该表达式是一个比较运算符,返回的结果是boolea类型 true|false
注意:使用instanceof关键字做判断时,两个类之间必须有关系。
3:案例
定义一个功能表函数,根据传递进来的对象的做不同的事情,如果是狗让其看家,如果是猫让其抓老鼠
1:定义动物类
2:定义狗类继承动物类
3:定义猫类继承动物类
4:定义功能根据传入的动物,执行具体的功能
5:instanceof好处
1:可以判断对象是否是某一个类的实例
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192 > 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[]); // 不可转换的类型>
>
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
123456789101112131415161718192021 > 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);>> }>> }>
>
5:使用final
1:final关键字主要用于修饰类、类成员、方法、以及方法的形参。
2: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可以互换位置
5:常量一般被修饰为final
3:fianl修饰类:
1:该类是最终类,不能被继承。
1:将父类加final修饰,子类继承,就会报错。
2:查看api文档发现String类是final的。Integer类也是final的
1:为了防止代码功能被重写
2:该类没有必要进行扩展
4:final修饰方法:
1:该方法是最终方法,不能被重写
2:当一个类被继承,那么所有的非私有函数都将被继承,如果函数不想被子类继承并重写可以将该函数final修饰
3:当一个类中的函数都被修饰为final时,可以将类定义为final的。
123456789101112131415161718192021222324 > 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();>> }>
>
5:final关键字修饰形参
1:当形参被修饰为final,那么该形参所属的方法中不能被篡改。
2: 项目中主要用于一些只用来遍历未知数据的函数。将未知变量声明为final的。增强数据的安全性。
1234567891011121314151617181920 > 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]);> }> }>
>
10:思考
为什么子类一定要访问父类的构造函数呢
1:子类继承了父类的属性,如果要使用父类的属性必须初始化,创建子类对象,必须先初始化父类属性
必须调用父类的构造方法。
2:为什么调用父类无参的构造函数
设计java语言之时,只知道编译器会默认添加无参的构造函数,有参的无法确定。
但是可以通过super关键字显式调用父类指定构造函数。
3:为什么super()this()语句要放在构造函数的第一行
子类可能会用到父类的属性,所以必须先初始化父类。
问题
- 静态和非静态的区别。说一下内存。
- 成员变量和静态变量的区别?
- 静态的特点以及注意事项?
- 什么时候使用静态?
- 继承的好处?
- java改良多继承的原因?
- 当使用一个已存在的继承体系时,该如何更快应用
- 什么时候用继承?
- super和this的特点?
- 覆盖的特点,何时应用,注意事项?
- 子类的实例化过程?为什么是这样的实例化过程?
- super语句,和this语句为什么不能同时存在,super为什么要定义在第一行?