static关键字、继承、super关键字、this关键字、多态、(成员变量、静态成员方法、实例成员方法)在多态中的调用情况
学习内容活动地址:CSDN21天学习挑战赛
static关键字static关键字及应用
继承、多态
- static是静态的意思,可以用来修饰成员变量、成员方法。
- static修饰成员变量之后称为静态成员变量(类变量),修饰方法之后称为静态方法(类方法)。
- static修饰后的成员变量,可以被类的所有对象共享(访问、修改)。
静态成员变量
- 有static修饰,属于类、加载一次,内存中只有一份。
- 表示被类的所有对象共享的信息。
实例成员变量
- 无static修饰,属于对象,且每个对象的该信息不同。
静态成员方法
- 有static修饰,属于类。
- 用类名访问(建议),也可以用对象访问。
- 如果该方法是以执行一个通用功能为目的,或者需要方便访问,则可以申明成静态方法
实例成员方法
- 无static修饰,属于对象。
- 只能用对象访问。
- 表示对象自己的行为的,且方法中需要直接访问实例成员,则该方法必须申明成实例方法。
访问注意事项
- 静态方法只能访问静态的成员,不可以直接访问实例成员。
- 实例方法可以访问静态的成员,也可以访问实例成员。
- 静态方法中是不可以出现this关键字的。
- main方法中不能直接使用非静态成员的
继承分析静态方法、构造方法、初始化程序块的先后执行顺序
父类(Person)
package moming; public class Person { private String name; public Person() { System.out.println("父类无参构造"); } public Person(String name) { System.out.println("父类有参构造"); } static { System.out.println("父类静态程序块"); } { System.out.println("父类初始化程序块"); } }子类(User)
package moming; public class User extends Person{ private String name; public User() { //super("name"); //super()//默认存在 System.out.println("子类无参构造"); } static { System.out.println("子类静态程序块"); } { System.out.println("子类初始化程序块"); } }测试类(Test)
package moming; public class Test{ public static void main(String[] args) { User user = new User(); System.out.println("-------------"); User user1 = new User(); } }父类静态程序块 子类静态程序块 父类初始化程序块 父类无参构造 子类初始化程序块 子类无参构造 ------------- 父类初始化程序块 父类无参构造 子类初始化程序块 子类无参构造可以看到静态程序块先执行,且只执行一次,之后是父类的初始化程序块、构造方法,再到子类的初始化程序块、构造方法。
总结继承:是指子类拥有父类的全部特征和行为,这是类之间的一种关系。Java中提供一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立起父子关系。
继承设计规范:
- 子类们相同特征(共性属性,共性方法)放在父类中定义,子类独有的的属性和行为应该定义在子类自己里面。
- Java是单继承模式:一个类只能继承一个直接父类。 Java不支持多继承、但是支持多层继承,父类可以有多个子类。
- Java中所有的类都是Object类的子类。
继承的特点:
- 继承要有一定的层次结构,而且还要具备一定的可传递性。
- 子类继承了父类的所有属性和方法,但是不包括私有属性(private)和构造方法。
- 若父类包含抽象方法,子类在继承时必须进行方法重写。
- 子类继承父类的属性和方法同时也可以有自己的属性和方法。
- 继承可以减少重复代码、提高了复用性和维护性。
- 继承让类与类之间产生了关系,类的耦合性增强了,父类发生变化子类也会跟着改变。
父类(Animal)(基类或超类)
package cn.moming1; public abstract class Animal { public String name="哺乳动物"; //父类实例成员变量 private static String anmial1="动物类";//私有属性子类不能继承 public Animal() { //无参构造子类不能继承 } public Animal(String name) { //有参构造子类不能继承 this.name = name; } public abstract void survive(); //父类抽象方法 public void eat(){ //实例成员方法 System.out.println("动物吃东西"); } public void breathe(){ //实例成员方法 System.out.println("动物吃东西"); } }子类(Cat)(派生类)
package cn.moming1; public class Cat extends Animal{ String name="加菲猫"; //子类自己的属性 public void eat(){ //子类重写父类实例方法 System.out.println("猫吃鱼"); } @Override public void survive() { //子类必须重写父类抽象方法 System.out.println("猫抓老鼠"); } public void call(){ //子类自己的实例方法 System.out.println("猫叫"); } }子类(Dog)
package cn.moming1; public class Dog extends Animal{ public Dog() { //super(); //默认存在,调用父类的无参构造 } public Dog(String name) { //super(); //默认存在,调用父类的无参构造 //this.name=name; super(name); //调用父类有参构造,效果和this.name=name; 一样 //super 可以用来直接调用父类中的构造方法,使编写代码也更加简洁方便。 } public void eat(){ //子类重写了父类实例方法 super.eat(); //调用父类中的方法 } @Override public void survive() { //子类必须重写父类抽象方法 System.out.println("狗护家"); } }测试类(Test)
package cn.moming1; public class Test { public static void main(String[] args) { //Cat类 Cat cat = new Cat();//实例化Cat类,获取cat对象; //子类没有构造方法时,系统默认会分配一个无参构造,调用父类无参构造,super()默认存在 cat.eat();//调用重写的eat方法 cat.survive();//调用重写的survive抽象方法 cat.breathe();//(子类没有重写)就去父类找,调用继承父类的breathe方法 System.out.println(cat.name);//获取子类的属性 cat.call();//调用子类自己的实例方法call System.out.println("==================="); //Dog类 Dog dog = new Dog();//子类有构造方法,这里调用了子类无参构造,又因为super()默认调用父类无参构造 System.out.println(dog.name);//子类没有去父类获取 dog.eat();//因为用super关键字调用了父类的eat dog.survive();//调用重写的survive抽象方法 Dog dog1 = new Dog("哈士奇");//子类有参构造调用了父类的无参构造后,执行自己的构造方法体(this.name=name;或者super(name);),将哈士奇传进去 String name = dog1.name; System.out.println(name); } }猫吃鱼 猫抓老鼠 动物吃东西 加菲猫 猫叫 =================== 哺乳动物 动物吃东西 狗护家 哈士奇
super关键字访问特点
在子类方法中访问成员(成员变量、成员方法)满足:就近原则
先子类局部范围找 然后子类成员范围找 然后父类成员范围找,如果父类范围还没有找到则报错。 如果子父类中,出现了重名的成员,会优先使用子类的,此时如果一定要在子类中使用父类的, 可以通过super关键字,指定访问父类的成员。
方法重写
- 在继承体系中,子类出现了和父类中一模一样的方法声明,我们就称子类这个方法是重写的方法。
- 私有方法不能被重写。
- 子类重写父类方法时,访问权限必须大于或者等于父类 。
- 子类不能重写父类的静态方法,如果重写会报错的。
@Override重写注解
- @Override是放在重写后的方法上,作为重写是否正确的校验注解。
- 加上该注解后如果重写错误,编译阶段会出现错误提示。
子类继承父类后构造方法的特点
- 子类中所有的构造方法默认都会先访问父类中无参构造方法,再执行自己。
- 子类构造方法的第一行语句默认都是:super(),不写也存在。
- 子类构造方法中可以通过书写 super(…),手动调用父类的有参数构造方法
this关键字super:代表父类存储空间的标识。
由于子类不能继承父类的构造方法,因此,如果要调用父类的构造方法,可以使用 super 关键字。super 可以用来访问父类的构造方法、普通方法和属性。
super 关键字的用法:
- super() 必须是在子类构造方法的方法体的第一行。
- super.父类属性名:调用父类中的属性
- super.父类方法名:调用父类中的方法
- super():调用父类的无参构造方法
- super(参数):调用父类的有参构造方法
编译器会自动在子类构造方法的第一句加上super(); 来调用父类的无参构造方法,必须写在子类构造方法的第一句,也可以省略不写。通过 super 来调用父类其它构造方法时,只需要把相应的参数传过去。
this:代表本类对象的引用。
可用于任何实例方法内指向当前对象,也可指向对其调用当前方法的对象,或者在需要当前类型对象引用时使用。
this 关键字的用法:
- 当局部变量和成员变量发生冲突时,使用this.进行区分。
- this.属性名:表示当前对象的属性
- this.方法名(参数):表示调用当前对象的方法
- this( ) 用来访问本类的构造方法
- this( ) 不能在普通方法中使用,只能写在构造方法中。
- 在构造方法中使用时,必须是第一条语句。
多态super 和 this 关键字的区别
- 子类和父类中变量或方法名称相同时,用 super 关键字来访问。可以理解为 super 是指向自己父类对象的一个指针。在子类中调用父类的构造方法。
- this 是自身的一个对象,代表对象本身,可以理解为 this 是指向对象本身的一个指针。在同一个类中调用其它方法。
- this 和 super 不能同时出现在一个构造方法里面,因为 this 必然会调用其它的构造方法,其它的构造方法中肯定会有 super 语句的存在,所以在同一个构造方法里面有相同的语句,就失去了语句的意义,编译器也不会通过。
- this( ) 和 super( ) 都指的是对象,所以,均不可以在 static 环境中使用,包括 static 变量、static 方法和 static 语句块。
- 从本质上讲,this 是一个指向对象本身的指针, 然而 super 是一个 Java 关键字。
父类
package cn.moming3; public class Animal { public String name; // 父类名字 }子类
package cn.moming3; public class Cat extends Animal { private String name; // 子类名字 public Cat(String name1, String name2) { //有参构造 super.name = name1; // 通过super关键字来访问父类中的name属性 this.name = name2; // 通过this关键字来访问本类中的name属性 } public String toString() { //重写toString方法,方便打印 return "父类的名字" + super.name + ",子类的名字" + this.name; } }测试类
package cn.moming3; public class Test { public static void main(String[] args) { Cat cat = new Cat("Animal", "Cat"); System.out.println(cat); } }父类的名字Animal,子类的名字Cat
多态就是同一函数在不同类中有不同的实现。
- 面向对象的多态性,即“一个接口,多个方法”。
- 多态性体现在父类中定义的属性和方法被子类继承后,可以具有不同的属性或表现方式。
- 多态性允许一个接口被多个同类使用,弥补了单继承的不足。
测试类
package cn.moming1; public class Test { public static void main(String[] args) { Animal animal1 = new Dog(); animal1.eat(); Animal animal2 = new Cat(); animal2.eat(); } }狗吃骨头 猫吃鱼
多态的前提和体现
- 有继承/实现关系
- 有方法重写
- 有父类引用指向子类对象
多态中成员访问特点
- 成员变量:编译看左边,执行看左边
- 静态成员方法:编译看左边,执行看左边
- 非静态成员方法:编译看左边,执行看右边
父类
package cn.moming5; public class Animal { public int age = 1;//非静态成员变量 public static String name="动物";//静态成员变量 public void eat(){ //非静态成员方法 System.out.println("动物吃东西"); } public static void sleep(){ //静态成员方法 System.out.println("动物睡觉"); } }子类
package cn.moming5; public class Cat extends Animal{ public int age = 2;//非静态成员变量 public static String name = "猫";//静态成员变量 public void eat(){ //非静态成员方法 System.out.println("猫吃鱼"); } public static void sleep(){ //静态成员方法(将父类静态成员方法隐藏了,这不是重写) System.out.println("猫不睡觉"); } }测试类
package cn.moming5; public class Test { public static void main(String[] args) { Animal a = new Cat(); //多态 int age = a.age; //父类引用去获得被隐藏的成员变量 System.out.println(age); String name = a.name; //父类引用去获得被隐藏的成员变量 System.out.println(name); a.eat(); //非静态成员方法,编译看左,运行看右 a.sleep(); //静态成员方法,编译看左,运行看左 } }1 动物 猫吃鱼 动物睡觉为什么成员变量和非静态成员方法的访问不一样呢?
因为非静态成员方法有重写,而成员变量没有。
为什么静态成员方法和非静态成员方法的访问不一样呢?
当静态时,父类的所有函数跟随父类加载而加载了。也就是父类的函数(是先于对象建立之前就存在了,无法被后出现的子类对象所重写的,所以没发生重写,就算子类重写了也没有意义。
测试类代码解析
Animal a = new Cat();
声明类型为Animal的a变量,然后建立一个子类对象new Cat()赋值给了a,在测试代码中是通过类型为Animal的引用变量a去调用属性和方法的,也就是父类引用调用属性和方法。
成员变量:编译看左,运行看左
首先记住一句话:成员变量不能被重写。
- 在一个类中,子类中的实例成员变量如果和父类中的实例成员变量同名,那么即使他们类型不一样,只要名字一样。父类中的实例成员变量都会被隐藏。在子类中,父类的实例成员变量不能被简单的用引用来访问。而是,必须通过父类的引用获得父类被隐藏的实例成员变量,而在测试类中就是通过父类引用去获得被隐藏的成员变量。
- 换句话说就是若子类和父类均定义了同样名称的成员变量,则对于子类来说这是两个不同的变量。
- 而静态成员变量属于类,不能被重写。子类可以继承父类的静态成员变量和静态成员方法
静态成员方法:编译看左,运行看左
- 因为class文件在被类加载器加载时,如果有static方法,此时会分配内存。也就是说,静态方法在类加载时就已经加载了,在类实例对象建立前就已经存在了,所有无法被后出现的子类对象进行重写。
- 静态方法的调用不需要实例化,而是直接通过父类引用去调用就行,不实例化也就不能用多态了,也就没有所谓的父类引用指向子类实例。
- 子类可以继承父类的静态方法,但不能重写。如果父类中定义的静态方法在子类中被重新定义,那么在父类中定义的静态方法将被隐藏而非重写。
- 父类和子类中含有的其实是两个没有关系的方法,它们的行为也并不具有多态性
非静态成员方法(实例成员方法):编译看左,运行看右
- 前面提了在多态条件下将子类对象new Cat()赋值给了a,也就是说只有子类的函数覆盖了父类的函数这一个变化,但是a肯定是Animal这个类的引用。所以a所代表的是函数被重写后的Animal类(多态的意义)。
- 谁的引用去调用的方法就是谁的,如果被重写就调用重写后的方法。
补充
- Java中函数就是方法,是定义在类中的一段独立的代码块,用来实现某个功能;作用是提高代码的复用性和可读性。
函数的语法格式: 修饰符 返回值类型 函数名(形式参数类型1 参数名1...){ 函数体语句; return 返回值; }
多态的好处
提高了程序的扩展性,具体体现:定义方法的时候,使用父类型作为参数,将来在使用的时候,使用具体的子类型参与操作。
多态的弊端
不能使用子类的特有功能
多态中的转型
- 向上转型 :从子到父 父类引用指向子类对象
- 向下转型 :从父到子 父类引用转为子类对象
package cn.moming1; public class Test { public static void main(String[] args) { //向上转型 Animal animal2 = new Cat(); animal2.eat(); //向下转型 Cat cat = (Cat) animal2; cat.eat(); } }猫吃鱼 猫吃鱼
- 向下转型其实就是强制类型转换,向下转型前应该保证转型的健壮性,转型之前必须使用instanceof关键字进行判断。
package cn.moming1; public class Test { public static void main(String[] args) { //向上转型 Animal animal2 = new Cat(); animal2.eat(); System.out.println("============"); //向下转型 if(animal2 instanceof Dog){ Cat cat = (Cat) animal2; cat.eat(); }else { System.out.println("类型转换失败!"); } System.out.println("============"); if(animal2 instanceof Cat){ System.out.println("类型转换成功!"); Cat cat = (Cat) animal2; cat.eat(); }else { System.out.println("类型转换失败!"); } } }猫吃鱼 ============ 类型转换失败! ============ 类型转换成功! 猫吃鱼
- 在进行子类特有方法使用时,要进行向下转型,转型之前一定要先判断,否则容易发生ClassCastException
package cn.moming1; public class Test { public static void main(String[] args) { //向上转型 Animal animal2 = new Cat(); animal2.eat(); System.out.println("============"); //向下转型 Dog dog = (Dog) animal2; //报错类型不匹配 dog.eat(); } }猫吃鱼 ============ Exception in thread "main" java.lang.ClassCastException: class cn.moming1.Cat cannot be cast to class cn.moming1.Dog (cn.moming1.Cat and cn.moming1.Dog are in unnamed module of loader 'app') at cn.moming1.Test.main(Test.java:30)