java学习笔记: 基础知识: day07: 继承、多态
====
day07
java学习笔记: 基础知识: day07: 继承、多态
一、继承inheritance
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,多个类继承这个类就行了。
继承:子类继承父类,子类就可以拥有父类的成员变量和成员方法。构造方法不能继承。
1.格式:public class 子类名 extends 父类{}
注意:构造方法不能继承。
2.继承的好处
A.提高了代码的复用性。
B.提高了代码的可维护性。
C.让类与类之间产生了关系,是多态的前提。
3.继承的弊端:让类与类之间产生了关系。也就让类的耦合性增强了。
4.开发的原则:高内聚,低耦合。
内聚:就是自己完成某件事的能力。
耦合:类与类的关系。
高内聚:一个类或一个方法只完成一个功能,并且不依赖于其它的类或方法。就是自己完成某件事的能力。
低耦合:修改完一个方法后,其它的方法基本不需要动代码,维护起来很方便。类与类的关系尽量减少。
5.java中类的继承特点:
(1)java中类只支持单继承,不支持多继承。
(2)支持多层继承。
6.Java继承中成员变量的特点
(1)成员变量名称不一样,使用的时候非常简单。
(2)成员变量名称相同时:在子类方法中访问变量采取就近原则。
(3)成员方法的特点:就近原则。
子类方法和父类声明一致时,先找子类方法,再找父类方法。
7.就近原则:成员变量名称相同时,在子类方法中访问变量,
(1)在方法的局部范围找,如果有就使用。
(2)在子类的成员范围找,如果有就使用。
(3)在父类的成员范围找,如果有就使用。
(4)如果还找不到,就报错。
二、super关键字以及继承中的方法覆盖override
1.this和super
(1)this代表本类对象的引用。super代表父类的存储空间,也可以理解为代表父类对象的引用。
(2)用法:
A.访问成员变量:this.成员变量。super.成员变量
B.访问构造方法:this(...) 调用本类的构造方法。super(...) 调用父类的构造方法
C.访问成员方法:this.成员方法。super.成员方法
2.java继承中构造方法的访问特点
(1)子类构造方法执行前,都会先执行父类的无参构造方法。
为什么呢?
简而言之,
因为子类会继承父类的成员方法,父类必须先初始化好了,子类才能继承。
展开来说,
因为子类继承父类,会继承父类的非私有成员。
而子类在初始化的时候,可能会使用父类的数据。如果父类数据没有初始化,子类就不能使用这些数据,即子类无法继承。
所以,在子类初始化以前,一定要先完成父类数据的初始化,即先调用父类的构造方法。
(2)在子类的构造方法中,默认第一行有一条语句super(),即必须在构造方法的第一行。
如果子类没有写super(),系统会默认加上一条。如果我们写了构造方法,系统就不会给我们提供构造方法了。
问题:如果父类中没有无参构造方法,怎么办呢?
回答:A.在父类中添加一个无参构造方法。(推荐)B.可以通过super去访问父类的带参构造方法。
我们在写构造方法的时候,最好写上无参构造方法和全参构造方法。
3.java继承中成员方法的访问特点:也是就近原则
(1)子类中方法和父类中方法的声明相同时(这叫方法的覆盖override),调用的是子类中的方法。
(2)就近原则:通过子类对象调用方法时,A.在子类中找,有就使用。B.在父类中找,有就使用。C.如果没有就报错。
(3)Object是所有类的根类。所有类都直接或者间接地继承Object类。
4.重载overload:在同一个类中出现的方法名相同的情况
覆盖override:子类中出现了和父类中一模一样的方法声明的情况
方法覆盖override的应用:当子类需要父类的功能,而功能主体子类又有自己的特有内容的时候,就考虑使用方法覆盖。这样既保证了父类的功能,还添加了子类特有的功能。
5.方法覆盖override的注意事项
(1)@override注解 标记一个方法,表名该方法是覆盖父类的方法
(2)父类私有private的方法不能被覆盖
(3)子类覆盖父类方法时,访问权限不能更低。
子类覆盖父类时,建议访问权限一模一样。
6.访问权限
private<默认修饰符friendly<protected<public
public:本类、别的包的类、本包的类
protected:本包中的类、其他包子类中
friendly:只能在本包中的类
private:只能在本类中
7.分析案例的时候是从子类分析到父类,写代码的时候是从父类写到子类。
三、多态polymorphism
多态,即事物的多种形态。
同一个对象,在不同时刻体现出来的不同状态。
举例:猫:是猫,是动物。水:液体、固体、气体。
1.java多态的前提
(1)有继承关系
(2)有方法覆盖override(不是必须的)
(3)有父类引用指向子类对象 Fu f=new Zi();
Dog dog = new Dog();//我说这只狗是狗
Animal ani = new Dog();//我说这只狗是只小动物
2.多态中成员访问的特点
(1)成员变量:编译看左边,运行看左边
(2)成员方法:编译看左边,运行看右边
3.为什么成员变量和成员方法的访问不一样呢?
因为成员方法有覆盖override,而成员变量没有覆盖override。
4.多态的缺点:父类不能访问子类特有的方法。解决办法:向下转型。
多态的优点:提高了程序的扩展性。
具体体现:定义方法的时候,使用父类型作为参数,将来在使用的时候,使用具体的子类型参与操作。
fun(Animal a){...}
Cat c = new Cat();
调用:.fun(c);//等价于Animal a=new Cat();
5.多态中的转型问题
(1)A.向上转型:从子到父,父类引用指向子类对象。Animal ani=new Dog();
B.向下转型:从父到子,父类引用转为子类对象。
注:强制转换可能会发生ClassCastException类型转换异常
(2)在做向下转型之前,要先做判断
if(ani instanceof Dog){
Dog dog=(Dog)ani;
dog.lookDoor();
}
instanceof左边的对象应该是右边类的父类或同类对象。
6.多态转型的内存图解
图片:见“多态转型问题内存图解.bmp”
代码:
//file name: Animal.java
package com.itheima_05;
public class Animal {
public void eat() {
System.out.println("吃东西");
}
}
//file name: Cat.java
package com.itheima_05;
public class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
public void playGame() {
System.out.println("猫捉迷藏");
}
}
//file name: Dog.java
package com.itheima_05;
public class Dog extends Animal {
public void eat() {
System.out.println("狗吃骨头");
}
}
//file name: DuoTaiDemo.java
package com.itheima_05;
public class DuoTaiDemo {
public static void main(String[] args) {
//向上转型
Animal a = new Cat();
a.eat();
//向下转型
Cat c = (Cat) a;
c.eat();
c.playGame();
//向上转型
a = new Dog();
a.eat();
//向下转型
Cat cc = (Cat) a; //ClassCastException 类型转换异常
cc.eat();
cc.playGame();
}
}
7.正确的向上转型、向下转型代码
图片:见“向下转型之父类和子类的关系.bmp”
核心代码:
//向上转型
Animal ani = Dew Dog();
ani.eat();//狗吃东西
if(ani instanceof Dog){
Dog dog = (Dog)ani;//向下转型
dog.lookDoor();//狗看门
}
未修改前的完整代码:
//file name: Animal.java
package com.itheima_06;
public class Animal {
public void eat() {
System.out.println("吃东西");
}
}
//file name: Cat.java
package com.itheima_06;
public class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
public void playGame() {
System.out.println("猫捉迷藏");
}
}
//file name: Dog.java
package com.itheima_06;
public class Dog extends Animal {
public void eat() {
System.out.println("狗吃骨头");
}
public void lookDoor() {
System.out.println("狗看门");
}
}
//file name: DuoTaiTest.java
package com.itheima_06;
/*
* 动物:
* eat()
* 猫:
* eat()
* playGame()
* 狗:
* eat()
* lookDoor()
*/
public class DuoTaiTest {
public static void main(String[] args) {
Animal a = new Cat();
a.eat();
Cat c = (Cat) a;
c.eat();
c.playGame();
a = new Dog();
a.eat();
Dog d = (Dog) a;
d.eat();
d.lookDoor();
}
}
8.静态成员变量:随着类的加载而加载,随着程序的消失而消失。以后会学集合、数组,假如集合、数组被static修饰了,如果你的这个集合或数组比较庞大,占用内存比较大,那么在程序结束之前,它始终会占据着内存空间。如果内存比较小的话,就会出现内存溢出异常。解决的办法:1.加内存;2.不要用static来修饰这种比较占内存的集合。
9.构造器原理、多态原理
核心知识点:
有参构造拿到属性值后不能给成员变量赋值,因为name和age属性是父类中的,并且 用private修饰,不能直接 赋值,所以这里调用父类的构造方法把name和age这两个属性值传递给父类的有参构造,让父类去进行初始化。
代码:
//file name: Person.java
package com.itheima.gxff;
public class Person {
private String name;
private int age;
public Person() {
//这里调用的是Person类的有参构造
this("孙老师",18);
}
public Person(String name, int age) {
//这里接收到属性后可能给他本身 的成员变量进行赋值
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
//file name: Teacher.java
package com.itheima.gxff;
public class Teacher extends Person {
public Teacher() {
}
public Teacher(String name, int age) {
//有参构造拿到属性值后不能给成员变量赋值,因为
//name和age属性是父类中的,并且 用private修饰
//不能直接 赋值,所以这里调用父类的构造方法把name和age这两个属性值
//传递给父类的有参构造,让父类去进行初始化
super(name, age);
}
}
//file name: Test.java
package com.itheima.gxff;
public class Test {
public static void main(String[] args) {
// Person per = new Person();//这里调用的是Person的无参构造
// //打印的是默认值
// /*
// * 我想给Person类一个好的默认值,该怎么办呢?
// * 我可以利用Person类的构造方法解决问题
// */
// System.out.println(per.getName());
// System.out.println(per.getAge());
Teacher tea = new Teacher("天海老师", 20);
//这里我调用的是Teacher类的有参构造方法,把天海老师和20这两个属性值传过去
}
}
====