首页 > 代码库 > 大数据第十一天

大数据第十一天

继承中成员方法的关系

案例演示

子父类中存在同名和不同名的成员方法

结论

通过子类对象去访问一个实例方法

首先在子类中找(是否子类进行了重写,或者是子类特有的方法)

然后在父类中找(子类没重写,而是从父类继承而来的)

 

/*

    继承中成员方法的关系:

       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)
  • 即使子类有同名的变量,访问的也是父类的变量

成员方法

  • 编译看左边,运行看右边(父类的引用运行时访问的是子类重写的方法)

静态方法

  • 编译看左边,运行看左边(父类的引用始终访问的是父类的静态方法)
  • 所以前面说静态方法不能算方法的重写

 

 

总结:

    成员变量和静态方法没有多态性

    只有被子类重写的成员方法才有多态性

大数据第十一天