继承中成员方法的关系
案例演示
子父类中存在同名和不同名的成员方法
结论:
通过子类对象去访问一个实例方法
首先在子类中找(是否子类进行了重写,或者是子类特有的方法)
然后在父类中找(子类没重写,而是从父类继承而来的)
/*
继承中成员方法的关系:
A:子类中的方法和父类中的方法声明不一样,这个太简单。
B:子类中的方法和父类中的方法声明一样,这个该怎么?
通过子类对象调用方法:
a:先找子类中,看有没有这个方法,有就使用
b:再看父类中,有没有这个方法,有就使用
c:如果没有就报错。
*/
class Father {
public void show() {
System.out.println("show Father");
}
}
class Son extends Father {
public void method() {
System.out.println("method Son");
}
//子类中重写父类的show方法
public void show() {
System.out.println("show Son");
}
}
class ExtendsDemo8 {
public static void main(String[] args) {
//创建对象
Son s = new Son();
s.show();
s.method();
//s.fucntion(); //找不到符号,子类中没有,父类中也没有
}
}
方法重写概述
方法重写概述
- 子类中出现了和父类中一模一样的方法声明
称为方法覆盖Override/重写OverWrite
使用特点:
- 如果方法名不同,就调用对应的方法
- 如果方法名相同,最终使用的是子类自己的(使用子类的引用的时候,在多态情况下,使用父类的引用,则有可能调用的是父类的静态方法)
方法重写的应用:
- 当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容。
- 方法的重写是多态实现的条件
/*
方法重写:子类中出现了和父类中方法声明一模一样的方法。
子类对象调用方法的时候:
先找子类本身,再找父类。
方法重写的应用:
当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法。
这样,即沿袭了父类的功能,又定义了子类特有的内容。
案例:
A:定义一个手机类。
B:通过研究,发明了一个新手机,这个手机的作用是在打完电话后,可以听天气预报。
*/
class Phone {
public void call(String name) {
System.out.println("给"+name+"打电话");
}
}
class NewPhone extends Phone {
public void call(String name) {
//System.out.println("给"+name+"打电话");
super.call(name);
System.out.println("可以听天气预报了");
}
}
class ExtendsDemo9 {
public static void main(String[] args) {
NewPhone np = new NewPhone();
np.call("tom");
}
}
方法重写的注意事项
- 父类中私有方法不能被重写,编译报错
- 子类重写父类方法时,访问权限不能更低(后面讲),否则编译报错
- 子类重写父类方法,返回值类型可以相同,或者是父类返回值类型的子类型
- 父类的实例方法(非静态方法),子类不能重新定义为静态方法
- 父类静态方法(类方法),子类也必须通过静态方法进行“重写”(虽然编译和使用都不报错,但其实这个算不上方法重写,但是现象确实如此,至于为什么算不上方法重写,多态中会讲解)
- 子类中重写父类方法,按照重写的原则(访问权限不能变小,返回值同类或者子类,方法名相同,形参列表相同);否则子类中定义的同名方法就是方法的重载(继承而来的方法和子类定义的方法构成重载),重载就必须让参数列表不同。如果子类方法只与继承自父类的方法返回值不同,不能构成重载
- 子类如果想重写父类的方法,最好是让方法的签名一模一样
(重写方法的一个重要用途就是:父类的引用能够指向子类的方法,但是静态方法的“重写”,在多态中依然调用的是父类的方法,所以,从这个角度上来讲,子类对父类的静态方法的重写不能算是真正方法的重写)
/*
方法重写的注意事项
A:父类中私有方法不能被重写
因为父类私有方法子类根本就无法继承
B:子类重写父类方法时,访问权限不能更低,最好就一致
C:父类静态方法,子类也必须通过静态方法进行重写
其实这个算不上方法重写,但是现象确实如此,至于为什么算不上方法重写,多态中讲解
子类重写父类方法的时候,最好声明一模一样。
否则同名的方法就是方法的重载了,就必须让形参列表不同
*/
class Father {
//private void show() {}
/*
public void show() {
System.out.println("show Father");
}
*/
//默认的访问权限
void show() {
System.out.println("show Father");
}
/*
public static void method() {
}
*/
public void method() {
}
}
class Son extends Father {
//private void show() {}
//子类中对父类方法的重写
/*
public void show() {
System.out.println("show Son");
}
*/
//public访问权限大于父类中默认的访问权限,ok
public void show() {
System.out.println("show Son");
}
//error,父类的method是实例方法,子类中重写变成了static类方法
//从多态的角度看,这个方法不能使用父类的引用来动态绑定,使用范围变小了
//public static void method() {
//}
//以下子类中和父类同样的方法签名,才是对方法的重写
public void method() {
}
}
class ExtendsDemo10 {
public static void main(String[] args) {
Son s = new Son();
s.show();
}
}
面试题
1:方法重写和方法重载的区别?方法重载能改变返回值类型吗?
方法重写:
在子类中,出现和父类中一模一样的方法声明的现象。(访问权限可以放大,返回值可以同类型,可以是父类返回值类型的子类)
方法重载:
同一个类中,出现的方法名相同,参数列表不同的现象。
方法重载能改变返回值类型,因为它和返回值类型无关。
Override/OverWrite:方法重写
Overload:方法重载
2:this关键字和super关键字分别代表什么?以及他们各自的使用场景和作用。
this:代表当前类的对象引用
super:代表父类实例存储空间的标识。
(可以理解为父类实例的引用,通过super可以访问父类实例的非私有成员)
场景:
成员变量:
this.成员变量
super.成员变量
构造方法:
this(...)
super(...)
成员方法:
this.成员方法
super.成员方法
继承练习
学生和教师案例
父类Person中成员private修饰,子类如何访问呢?不能用super,使用封装的方法
/*
学生案例和教师案例讲解
学生:
成员变量;姓名,年龄
构造方法:无参,带参
成员方法:getXxx()/setXxx()
教师:
成员变量;姓名,年龄
构造方法:无参,带参
成员方法:getXxx()/setXxx()
看上面两个类的成员,发现了很多相同的东西,所以我们就考虑抽取一个共性的类:
人:
成员变量;姓名,年龄
构造方法:无参,带参
成员方法:getXxx()/setXxx()
学生 继承 人
教师 继承 人
*/
//定义人类
class Person {
//姓名
private String name;
//年龄
private int age;
public Person() {
}
public Person(String name,int age) { //"tom",27
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;
}
}
//定义学生类,不能继承父类的private变量,使用公有的方法进行访问
class Student extends Person {
public Student() {}
public Student(String name,int age) { //"tom",27
//this.name = name; //error不能继承private变量
//this.age = age;
super(name,age);
}
}
//定义教师类,同样调用父类的构造方法进行教师对象的初始化
class Teacher extends Person {}
class ExtendsTest4 {
public static void main(String[] args) {
//创建学生对象并测试
//方式1,学生类继承了get/set方法,可以对父类的私有变量进行操作
Student s1 = new Student();
s1.setName("tom");
s1.setAge(27);
System.out.println(s1.getName()+"---"+s1.getAge());
//方式2,通过调用父类的构造方法进行私有变量的赋值
Student s2 = new Student("tom",27);
System.out.println(s2.getName()+"---"+s2.getAge());
//补全教师类中的代码并进行测试。
//创建教师对象,并访问其中的变量
}
}
思考:
上面代码中,子类的构造方法中,使用的是super调用父类的构造方法,那么,对成员变量的赋值,到底是赋给了父类的成员变量?还是子类的成员变量?
可以理解为:整个父类的对象都在子类对象中,值确实是赋给了父类的成员变量,但是子类通过方法可以使用,和自己的变量没有区别。这就是继承的好处。
从上面的例子可以看出,其实父类的private成员变量也被子类“继承”了,只不过是不让直接访问而已。
猫狗类抽象出动物类过程
从猫狗的共性抽取出一个动物类,让猫狗继承这个父类
案例练习的目的:
熟练继承关系中子类的方法重写,this,super关键字的使用,以及变量的访问等
/*
先找到具体的事物,然后发现具体的事物有共性,才提取出一个父类。
猫:
成员变量:姓名,年龄,颜色
构造方法:无参,带参
成员方法:
getXxx()/setXxx()
eat()
palyGame()
狗:
成员变量:姓名,年龄,颜色
构造方法:无参,带参
成员方法:
getXxx()/setXxx()
eat()
lookDoor()
共性:
成员变量:姓名,年龄,颜色
构造方法:无参,带参
成员方法:
getXxx()/setXxx()
eat()
把共性定义到一个类中,这个类的名字叫:动物。
动物类:
成员变量:姓名,年龄,颜色
构造方法:无参,带参
成员方法:
getXxx()/setXxx()
eat()
猫:
构造方法:无参,带参
成员方法:palyGame()
狗:
构造方法:无参,带参
成员方法:lookDoor()
*/
//定义动物类
class Animal {
//姓名
private String name;
//年龄
private int age;
//颜色
private String color;
public Animal() {}//空参构造
//带参构造
public Animal(String name,int age,String color) {
this.name = name;
this.age = age;
this.color = color;
}
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;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
//公有方法
public void eat() {
System.out.println("吃");
}
}
//定义猫类继承动物类
class Cat extends Animal {
public Cat() {}
public Cat(String name,int age,String color) {
super(name,age,color);//调用父类的构造方法对变量进行赋值
}
//定义猫类自己的方法
public void playGame() {
System.out.println("猫玩");
}
}
//定义狗类继承动物类
class Dog extends Animal {
public Dog() {}
public Dog(String name,int age,String color) {
super(name,age,color);
}
//定义狗类自己的方法
public void watchDoor() {
System.out.println("狗看门");
}
}
//测试类
class ExtendsTest5 {
public static void main(String[] args) {
//测试猫
//方式1,通过set方法对变量进行赋值
Cat c1 = new Cat();
c1.setName("Tom");
c1.setAge(3);
c1.setColor("白色");
System.out.println("猫的名字是:"+c1.getName()+";年龄是:"+c1.getAge()+";颜色是:"+c1.getColor());
c1.eat();
c1.playGame();
System.out.println("---------------");
//方式2,通过父类构造方法对变量赋值
Cat c2 = new Cat("杰瑞",5,"土豪金");
System.out.println("猫的名字是:"+c2.getName()+";年龄是:"+c2.getAge()+";颜色是:"+c2.getColor());
c2.eat();
c2.playGame();
//作业:两种方式完成狗类的初始化,并访问其变量和方法
}
}
final关键字
final关键字是最终的意思,可以修饰类,成员变量,成员方法。
它的特点是:
- 修饰类,类不能被继承(不能放在extends后)
- 修饰变量,变量就变成了常量,只能被赋值一次,不论子类还是本类中,都不能修改
(常量一般是大写字母表示,final int ONE = 1;)
- 修饰方法,方法不能被重写(子类只有使用权,没有修改权)
/*
继承的代码体现
由于继承中方法有一个现象:方法重写。
所以,父类的功能,就会被子类给覆盖掉。
有些时候,我们不想让子类去覆盖掉父类的功能,只能让他使用。(只有使用权,没有修改权)
这个时候,针对这种情况,Java就提供了一个关键字:final
final:最终的意思。常见的是它可以修饰类,方法,变量。
*/
class Fu {
public final void show() {
System.out.println("这是绝密资源,任何人都不能修改");
}
}
class Zi extends Fu {
// Zi中的show()无法重写Fu中final方法show(),编译报错
//public void show() {
//System.out.println("出错");
//}
}
class ZiDemo {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
final特性
/*
final可以修饰类,方法,变量
特点:
final可以修饰类,该类不能被继承。
final可以修饰方法,该方法不能被重写。
final可以修饰变量,该变量不能被重新赋值。因为这个变量其实是常量。
常量:
A:字面值常量
"hello",10,true
B:自定义常量
final int x = 10;
*/
//final class Fu //无法从最终Fu进行继承
class Fu {
public int num = 10;
public final int num2 = 20;
//final方法不能被重写
public final void show() {
}
}
class Zi extends Fu {
// Zi中的show()无法覆盖Fu中的show()
//public void show() {
//num = 100;
//System.out.println(num);
//无法为最终变量num2再次分配值
//num2 = 200;
//System.out.println(num2);
//}
}
class FinalDemo {
public static void main(String[] args) {
Zi z = new Zi();
z.show();//调用的是继承自父类的final方法
}
}
final关键字面试题
final修饰局部变量
- 在方法内部,该变量不可以被改变
- 在方法声明上,分别演示基本类型和引用类型作为参数的情况
基本类型,是这个参数的值不能被改变
引用类型,是这个参数指向的地址值不能被改变
/*
面试题:final修饰局部变量的问题
基本类型:基本类型的值不能发生改变。
引用类型:引用类型的地址值不能发生改变,但是,该对象的堆内存的值是可以改变的。
*/
//以下演示的是局部变量使用final修饰的情况
class Student {
int age = 10;
}
class FinalTest {
public static void main(String[] args) {
//局部变量是基本数据类型
int x = 10;
x = 100; //非final变量值可以被改变
System.out.println(x);
final int y = 10;
//无法为最终变量y再赋值,即使是相同的值也不行
//y = 100;
System.out.println(y);
System.out.println("--------------");
//局部变量是引用数据类型
Student s = new Student();
System.out.println(s.age);
s.age = 100;
System.out.println(s.age);//100,改变了引用对象的属性值
System.out.println("--------------");
final Student ss = new Student();//ss只能指向这个new出来的对象
System.out.println(ss.age);
ss.age = 100; //可以改变所指对象的属性值
System.out.println(ss.age);
//无法为最终变量ss分配值,error
//ss = new Student();
}
}
以下演示的是形参使用final修饰的情况
public void testFinalVar(final int i){
//i = 1; // error,不能再对final变量赋值
}
public void testFinalVar(final String str){
//str = null; //error不能再对final变量赋值
}
final修饰变量的初始化时机
在对象构造完毕前即可(非静态的常量)
/*
final修饰变量的初始化时机
A:被final修饰的变量只能赋值一次。
B:在构造方法完毕前。(非静态的常量)
*/
class Demo {
//int num = 10;
//final int num2 = 20;
//一旦在声明的时候赋值的话,就不能再任何地方修改了
int num;
final int num2;//声明的时候不赋值,可以在构造代码块或者构造方法中赋值
{
//num2 = 10;
}
public Demo() {
num = 100;//普通变量使用之前赋值即可
//由于声明时没有赋值,构造方法中可以为最终变量num2赋值
num2 = 200;
}
}
class FinalTest2 {
public static void main(String[] args) {
Demo d = new Demo();
System.out.println(d.num);
System.out.println(d.num2);
}
}
总结:
类中非static的final变量(实例final变量)可以在声明的时候赋值,如果声明的时候没有赋值的话,就必须在以下两个地方赋值,一个是构造代码块中,一个是构造方法中。如果这两个地方也没有赋值的话,编译报错。
如果是static修饰的final变量(类变量)的话,则只能在两个地方赋值:声明的时候,或者是在静态代码块中。
经过验证:
若类中的final成员变量在声明时没有赋值,并且存在多个构造方法的话,则在每个构造方法中都应该显示的为这个final变量赋值,或者可以抽取到构造代码块中进行赋值
多个构造方法中,不能互相调用
多态概述(Polymorphism)
多态:某一个事物,在不同时刻表现出来的不同状态。
在Java中,对一个事物的引用可以分成两种类型,一种是编译时的类型,一种是运行时的类型。
编译时的类型指的是声明这个变量时指定的类型;运行时类型指的是实际赋给这个变量的对象的类型。
举例:
之前写的例子全都是将某类对象的引用赋值给这个类的一个变量,这个变量的值就是这个对象的地址值:
Student s = new Student();
等号左边就是编译时的类型,s在编译时指定的类型是Student,等号右边是运行时实际赋值给这个变量的值,恰好也是一个Student类型的对象的引用。
如果等号左右两边的类型不一致,就有可能出现了多态。
举例:若Student类继承自Person类,则下面的写法是正确的
Person p = new Student();
Java语言是强类型的,基本数据类型之间存在自动向上转型的特性,在引用数据类型中,同样存在这样的情况,Student类继承了Person类,在语义范围上来看,一个Student类的对象确实是一个Person类的对象,两者是“is a”的关系。
举例:
猫可以是猫的类型。猫 m = new 猫();
同时猫也是动物的一种,从动物继承而来,也可以把猫称为动物。
动物 d = new 猫();
看一个多态的例子
class Father{
int age = 50;
public void speak(){
System.out.println("father speak()");
}
}
class Son extends Father{
int age = 30;
public void speak(){
System.out.println("son speak()");
}
//子类特有的方法talk
public void talk(){
System.out.println("son talk()");
}
}
class PolymTest{
public static void main(String[] args){
Father fa = new Father();
fa.speak(); //没有体现多态,只是通过父类引用调用父类方法
Father f = new Son(); //父类引用指向子类对象,体现多态
f.speak(); //调用方法,如果子类重写了该方法,则调用子类的
//f.talk(); //不能调用子类特有的方法
System.out.println(f.age);//50,成员变量没有多态性
Son s = new Son();
s.speak();
}
}
父类引用不能调用子类特有的方法,因为使用了父类的引用,就代表站在父类的角度来看待当前的子类对象,只能看到从父类继承而来的特性,或者是子类重写的父类的方法。成员变量没有多态性,只能看到父类的成员变量。
多态案例及成员访问特点
/*
多态:同一个对象(事物),在不同时刻体现出来的不同状态。
多态的前提:
A:要有继承关系。
B:要有方法重写。
其实没有也是可以的,但是如果没有这个就没有意义。
动物 d = new 猫();
d.show();
动物 d = new 狗();
d.show();
C:要有父类引用指向子类对象。
父 f = new 子();
多态中的成员访问特点:
A:成员变量
编译看左边,运行看左边。
B:构造方法
创建子类对象的时候,访问父类的构造方法,对父类的数据进行初始化。
C:成员方法
编译看左边,运行看右边。
D:静态方法
编译看左边,运行看左边。
(静态和类相关,算不上重写,所以,访问还是左边的)
由于成员方法存在方法重写,所以它运行看右边。
*/
class Fu {
public int num = 100;
public void show() {
System.out.println("show Fu");
}
public static void function() {
System.out.println("function Fu");
}
}
class Zi extends Fu {
public int num = 1000;
public int num2 = 200;
public void show() {
System.out.println("show Zi");
}
public void method() {
System.out.println("method zi");
}
public static void function() {
System.out.println("function Zi");
}
}
class DuoTaiDemo {
public static void main(String[] args) {
//要有父类引用指向子类对象。
Fu f = new Zi();
System.out.println(f.num);//成员属性没有多态性,这里访问的是父类的实例变量,100
//由于父类没有num2这个实例变量,所以编译报错,找不到符号
//System.out.println(f.num2);
f.show();//由于子类重写了父类的show方法,所以这里调用的是子类的show方法
//找不到符号,父类引用不能调用子类特有的方法
//f.method();
f.function();//调用的是父类的静态方法,虽然子类也有同样的静态方法
}
}
多态的前提条件
- 有继承关系(没有继承关系的话,不同类型的变量是不能赋值的)
- 有方法重写(没有方法的重写的话,始终调用的是父类的方法)
- 有父类引用指向子类对象(不使用父类的引用的话,始终调用的是子类的方法)
多态中成员访问特点
成员变量
- 编译看左边,运行看左边(父类的引用始终访问的是父类的变量,不论是不是static)
- 即使子类有同名的变量,访问的也是父类的变量
成员方法
- 编译看左边,运行看右边(父类的引用运行时访问的是子类重写的方法)
静态方法
- 编译看左边,运行看左边(父类的引用始终访问的是父类的静态方法)
- 所以前面说静态方法不能算方法的重写
总结:
成员变量和静态方法没有多态性
只有被子类重写的成员方法才有多态性