Java基础9
super关键字
为什么需要super:
举例1: 子类继承父类以后,对父类的方法进行重写,那么在子类中,使用super关键字仍然可以对父类中重写的方法进行调用
举例2: 子类继承父类以后,发现子类和父类中定义了同名的属性,使用super关键字仍然可以在子类中区分这两个同名的属性
super的理解: 父类的
super可以调用的结果:属性、方法、构造器
super调用属性、方法
子类继承父类以后,我们就可以在子类的方法或构造器中,调用父类中声明的属性或方法。(满足封装性前提下)
调用时,需要使用 "super." 的结构,表示调用父类的属性或方法,一般情况下可以省略。
但是! 如果出现了子类重写了父类的方法或同名的属性时,则必须使用 "super." 结构,显示的调用父类中被重写的方法或属性。
public class Person { //属性 String name; private int age; int id = 1001; public void eat(){ System.out.println("人会吃饭!"); } public void sleep(){ System.out.println("人会睡觉!"); } }
public class Student extends Person{ String school; int id = 1002; public void eat(){ //重写 System.out.println("学生要多吃有营养的食物!"); } public void show(){ super.eat(); //调用父类中的eat方法 } public void show2(){ System.out.println(id); // 子类的id System.out.println(this.id); // 子类的id System.out.println(super.id); // 父类的id } }
public class StudentTest { public static void main(String[] args) { Student s = new Student(); s.eat(); //学生要多吃有营养的食物! s.show(); //人会吃饭! s.show2(); } }
super调用构造器
- 子类继承父类时,不会继承父类的构造器。只能通过 "super(形参列表)" 的方式调用父类指定的构造器
- 规定: "super(形参列表)" ,必须声明在子类构造器的首行
- 前面提到,在构造器的首行可以使用 "this(形参列表)" ,调用本类中重载的构造器, 结合上一点,在构造器的首行this和super只能二选一
- 如果在子类构造器的首行既没有显示调用 "super(形参列表)" ,也没调用 "this(形参列表)" 。则子类此构造器默认调用 "super( )"即父类的空参构造器
- 子类的任何一个构造器,要么调用本类中重载的构造器,要么调用父类的构造器,只能二选一。
- 一个类中声明有n个构造器,最多有n-1个构造器使用 "this(形参列表)",则剩下的那一个构造器一定使用 "super(形参列表)"
注意: 我们在通过子类的构造器创建对象时,一定在调用子类构造器的过程中,直接或间接的调用到父类的构造器。也正因为调用过父类的构造器,我们才会将父类中声明的属性或方法加载到内存中,供子类对象使用。
public class Person { //属性 String name; private int age; int id = 1001; public Person(){ System.out.println("Person()...."); } public Person(String name, int age, int id) { this.name = name; this.age = age; this.id = id; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void eat(){ System.out.println("人会吃饭!"); } public void sleep(){ System.out.println("人会睡觉!"); } }
public class Student extends Person{ String school; int id = 1002; public Student(){ super(); //super必须在构造器的首行! System.out.println("Student()...."); } public Student(String name,int age){ this.name = name; setAge(age); //这里age是private 所以只能通过方法赋值 } public void eat(){ //重写 System.out.println("学生要多吃有营养的食物!"); } public void show(){ super.eat(); //调用父类中的eat方法 } public void show2(){ System.out.println(id); // 子类的id System.out.println(this.id); // 子类的id System.out.println(super.id); // 父类的id } }
public class StudentTest { public static void main(String[] args) { Student s = new Student(); //输出:Person().... Student().... // Student s2 = new Student("小红",21); //输出:Person().... } }
练习
public class Test3 {
public static void main(String[] args) {
Sub s = new Sub();
}
}
class Base{
Base(){
method(100);
}
{
System.out.println("base");
}
public void method(int i){
System.out.println("base:" + i);
}
}
class Sub extends Base{
Sub(){
super.method(70);
}
{
System.out.println("sub");
}
@Override
public void method(int i) {
System.out.println("sub:" + i);
}
}
问:如何输出?
这里没有静态代码块,所以先调用父类的非静态代码块,然后执行构造器method(100) 但是并不是执行的父类Base中的method方法,执行的是子类Sub中的method方法,因为子类的method已经覆盖了父类(重写了) 所以执行子类的method(100)。
然后执行子类的代码块,最后是子类的构造器调用父类的method方法
子类对象实例化全过程
从结果的角度看:体现为类的继承性
当创建子类对象后,子类对象就获取了其父类中声明的所有的属性和方法,在权限允许的情况下,可以直接调用
从过程的角度来看:
当我们通过子类的构造器创建对象时,子类的构造器一定会直接或间接的调用到其父类的构造器,而其父类的构造器同样会调用其父类的父类的构造器....直到调用了object类中的构造器为止。
正因为调用过子类的所有父类的构造器,所以我们会将父类中声明的属性、方法加载到内存中,供子类的对象使用
明确:虽然创建子类对象时,调用了父类的构造器,但自始至终就创建过一个对象,即为new的子类对象。
class Creature { public Creature() { System.out.println("Creature无参数的构造器"); }} class Animal extends Creature { public Animal(String name) { //没有this也没有super默认调用父类的无参构造器 然后再执行下列语句 System.out.println("Animal带一个参数的构造器,该动物的name为" + name); } public Animal(String name, int age) { this(name); //调用Animal一个参数的构造器 System.out.println("Animal带两个参数的构造器,其age为" + age); }} public class Wolf extends Animal { public Wolf() { super("灰太狼", 3); System.out.println("Wolf无参数的构造器"); } public static void main(String[] args) { new Wolf(); }}
面向对象的特征三:多态性
体现:
子类对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用) 例如: Person P2 = new Man();
多态性的应用
虚拟方法调用
在多态的场景下,调用方法时: 编译时,认为方法是左边声明的父类的类型的方法(即被重写的方法) 执行时,实际执行的是子类重写父类的方法。
多态是运行时的行为,编译是看声明的类型(父类类型),运行时才看是属于哪一个子类
简称: 编译看左边,执行看右边
public class Person { String name; int age; public void eat(){ System.out.println("人吃饭。"); } public void sleep(){ System.out.println("人睡觉。"); } }
public class Man extends Person{ boolean isSmoking; public void eat(){ System.out.println("男人饭量比较大"); } public void sleep(){ System.out.println("男人休息时间要充足"); } }
public class PersonTest { public static void main(String[] args) { //多态性 Person p1 = new Man(); p1.eat(); //执行的是Man中的重写的方法 p1.sleep(); } }
使用前提
① 有类的继承关系 ② 要有方法的重写
注意:多态性只适用于方法,不适用于属性。
如果子类Man和父类Person都声明了属性id,Perspn p1 = new Man(); 调用p1.id 实际上得到的仍然会是父类的id号。
多态的优劣
好处:极大的减少了代码的冗余,不需要定义多个重载方法
弊端:在多态的场景下,我们创建了子类的对象,也加载了子类特有的属性和方法。但是由于声明为父类的引用,导致没有办法直接调用子类特有的属性和方法。
public class AnimalTest { public static void main(String[] args) { AnimalTest test = new AnimalTest(); test.adopt(new Dog()); //多态的体现 } public void adopt(Animal animal){ //多态性 Animal animal = new Dog() animal.jump(); //因为传进来的是Dog 所以调用的就会是Dog的方法而不是Animal animal.eat(); } //因为多态性的存在,我们不需要再写public void adopt(Dog dog) 和 adopt(Cat cat)方法 可以直接用上面的这个 } class Animal{ public void eat(){ System.out.println("动物进食"); } public void jump(){ System.out.println("动物会跳"); } } class Dog extends Animal{ public void eat(){ System.out.println("狗吃骨头"); } public void jump(){ System.out.println("狗急跳墙"); } public void watchDoor(){ System.out.println("狗能看家"); } } class Cat extends Animal{ public void eat(){ System.out.println("猫吃鱼"); } public void jump(){ System.out.println("猫会跳"); } public void catchMouse(){ System.out.println("猫抓老鼠"); } }
向下转型
public class Person { String name; int age; public void eat(){ System.out.println("人吃饭。"); } public void sleep(){ System.out.println("人睡觉。"); } }
public class Man extends Person{ boolean isSmoking; public void eat(){ System.out.println("男人饭量比较大"); } public void earnMoney(){ System.out.println("男人最好去赚钱"); } }
public class Woman extends Person{ public void eat(){ System.out.println("女人吃的比较少"); } public void walk(){ System.out.println("女人喜欢逛街"); } }
public class PersonTest { public static void main(String[] args) { //多态性 Person p1 = new Man(); p1.eat(); //执行的是Man中的重写的方法 //向下转型 Man m1 = (Man)p1; m1.earnMoney(); System.out.println(p1 == m1); //true p1,m1指向的堆空间的同一对象 //但是我们不能直接写 p1.earnMoney /* 向下转型可能会出现: 类型转换异常 (classCastException) */ Person p2 = new Woman(); Man m2 = (Man)p2; m2.earnMoney(); //错误: 找不到或无法加载主类 com.atgjwqz.exercise.CylinderTest /* 建议在向下转型之前,使用instanceof判断,避免出现类型转换异常 格式: a instanceof A : 判断对象a是否是A的实例 如果a instanceof A 返回true,则: a instanceof superA 返回也是true,其中A是superA的子类。 */ if(p2 instanceof Man){ Man m3 = (Man)p2; m3.earnMoney(); } } }
案例一
public class Person { String name; int age; public void eat(){ System.out.println("吃饭。"); } public void toilet(){ System.out.println("上洗手间"); } }
public class Man extends Person{ boolean isSmoking; public void eat(){ System.out.println("男人饭量比较大"); } public void toilet(){ System.out.println("男人去男厕所"); } public void smoke(){ System.out.println("有的男人抽烟"); } }
public class Woman extends Person{ public void eat(){ System.out.println("女人吃的比较少"); } public void toilet(){ System.out.println("女人去女厕所"); } public void makeup(){ System.out.println("女人有的会化妆"); } }
public class PersonTest { public static void main(String[] args) { PersonTest p = new PersonTest(); p.meeting(new Man(), new Woman(),new Man()); } public void meeting(Person... ps){ // 这里Person... ps 是多形参 ps相当于一个数组名里面有很多变量 所以我们需要遍历ps for(int i =0; i < ps.length; i ++){ if(ps[i] instanceof Man){ ps[i].eat(); ps[i].toilet(); Man m1 = (Man)ps[i]; m1.smoke(); } else if (ps[i] instanceof Woman) { ps[i].eat(); ps[i].toilet(); Woman w = (Woman) ps[i]; w.makeup(); } System.out.println(); } } }
案例二
public class FieldMethodTest { public static void main(String[] args) { Sub s = new Sub(); System.out.println(s.count); // 20 s.display(); // 20 Base b = s; System.out.println(b == s); //true System.out.println(b.count); // 10 这里编译还是看父类 属性不遵循多态性 b.display(); // 20 运行看子类 属于Sub,所以调用了sub的方法 } } class Base{ int count = 10; public void display(){ System.out.println(this.count); } } class Sub extends Base{ int count = 20; public void display(){ System.out.println(this.count); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具