Lesson11_继承

36 篇文章 0 订阅
33 篇文章 0 订阅

Lesson11_封装_继承

回顾

public

protected

不写

private

UML

  • 类名、属性、方法

  • + - #

this

  • 当前对象的引用
  • this. 成员变量的前缀
  • this() / this(xxx) 构造方法中的第一条语句,用于复用自己其它的构造方法

static

  • 静态的方法不能访问非静态的成员
  • 非静态的方法可以访问静态的成员吗?
  • 静态的方法可以访问静态的成员吗?
  • 类所属的,所以可以直接通过类名.static成员 Arrays.sort(xxx);
  • 一个类可以创建多个对象。这些对象共享同一份静态数据
  • 静态变量保存在方法区

构造方法

  • 给新建的对象属性赋值
  • 没有返回类型
  • 方法名必须和类名完全相同

静态代码块、普通代码块、构造方法

分别执行几次,先后顺序是什么

  • 静态代码块 类加载的时候,执行一次,在普通代码块和构造方法之前
  • 普通代码块 每创建一个对象,都会执行一次,在静态代码块之后,构造方法之前
  • 构造方法 调用哪个执行哪个,不调用不执行

单例模式

  • 含义:例,实例,对象。单例,有且只有一个对象。单例模式确保实例的数量为1

  • 饿汉式

    类加载时就创建对象,会较早的占据内存空间。浪费一点内存,访问时速度快。牺牲空间,换取时间。
    
    public class SingleTon {
        private static SingleTon singleTon = new SingleTon();// 自己创建
    
        private SingleTon(){
            System.out.println("私有化构造方法,别人就new不了自己了");
        }
    
        // 私有化属性后,提供的公开访问方法
        public static SingleTon getInstance(){
            return singleTon;
        }
    }
    
  • 懒汉式

    不着急,什么时候用,什么时候创建。首次使用对象时,会浪费时间。牺牲时间,换取空间。
    
    public class SingleTon2 {
        private static SingleTon2 singleTon = null;// 不着急,等用的时候再创建
    
        private SingleTon2(){
            System.out.println("私有化构造方法,别人就new不了自己了");
        }
    
        // 私有化属性后,提供的公开访问方法
        public static SingleTon2 getInstance(){
            if(singleTon == null){ // 首次使用的时候突然发现还没有创建对象
                singleTon = new SingleTon2(); // 等待对象的创建
            }
            return singleTon;// 后续使用时不再创建,直接返回即可
        }
    }
    
    

方法重载

  • 概念:一个类中,不同参的多个方法,互为重载方法。这种现象是方法重载

    System.out.println();
    System.out.println(1);
    System.out.println(2.5);
    System.out.println(false);
    System.out.println("abc");
    
    // 同一个类的多个构造方法,也叫方法重载
    public Person(){}
    
    public Person(String name, int age){
        this.name = name;
        this.age = age;
    }
    

package

  • 打包 :资源分类管理,避免类名冲突

  • 规范的包名:全小写,域名倒置 (域名 www.baidu.com ==> com.baidu.xxx业务)

  • 通常是类的第一句非注释性语句。

  • 程序中的包的概念,在操作系统中就是文件夹

  • java中的常用包

    • java.lang:包含一些java预言的核心类,如String、Math、Integer、System和Thread,提供常用功能
    • java.awt:包含了构成抽象窗口工具集的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
    • java.net:包含执行与网络相关的操作类。
    • java.io:包含能提供多种输入/输出功能的类。
    • java.util:包含一些工具类,如定义系统特性、使用与日期日历相关的函数。
  • 如果我们要使用其他包的类,需要使用import导入,从而可以在本类中直接通过类名来调用,否则就需要书写类的完整包名和类名。import后,便于编写代码,提高可维护性。

    • 注意:

      • Java会默认导入java.lang包下所有的类,因此这些类我们可以直接使用。
      • 如果导入两个同名的类,只能用包名+类名来显示调用相关类
      • import java.util.*;//导入该包下所有的类。会降低编译速度,但不会降低运行速度。
    • 静态导入:静态导入(static import)是在JDK1.5新增加的功能,其作用是用于导入指定类的静态属性,这样我们可以直接使用静态属性。

      package cn.sxt;
       //以下两种静态导入的方式二选一即可
      import static java.lang.Math.*;//导入Math类的所有静态属性
      import static java.lang.Math.PI;//导入Math类的PI属性
       
      public class Test2{
          public static void main(String [] args){
              System.out.println(PI);
              System.out.println(random());
          }
      }
      

继承

  • 继承是面向对象的核心特性,是面向对象的学习重点。同时继承是代码复用的重要方式,可以表示类与类之间的关系,是所有面向对象语言不可缺少的组成部分。

    把若干个类的共同属性和方法提取出来,放到一个类中。利用extends关键字实现子类继承父类,子类继承父类的属性和方法,这就是继承。

  • 概念:根据已知类(父类)构建新的类(子类),重用已知类的属性和方法。继承让我们更加容易实现类的扩展。 比如,我们定义了人类,再定义Boy类就只需要扩展人类即可。实现了代码的重用,不用再重新发明轮子(don’t reinvent wheels)。

  • 目的:偷懒。通过已有的类稍作修改,就能快速构建新类。体现的是is-a的关系。兔子是一种动物,父类更抽象,子类更具体。现实世界中的继承无处不在。

  • 当一个类的属性与行为均与现有类相似,属于现有类的一种时,这一个类可以定义为现有类的子类。

    或者换成相反的角度来看,如果多个类具有相同的属性和行为,我们可以抽取出共性的内容定义父类,这时再创建相似的类时只要继承父类即可。

  • 子类拥有父类的所有属性与方法,无需重新定义。并且可以直接使用非私有的父类成员。从类与类之间的设计关系来看,子类必须属于父类的一种时,才会继承。

    我们在完成一个庞大项目体系的时候,都是将共性的内容抽取出,后续构建过程是从各种父类“向外”扩散的。

  • 举例

    父类		子类
    手机		小米手机
    动物		兔子
    花		 梅花
    操作系统   Windows
    枪		 手枪
    
  • 关键字 extends

    • 扩展,子类继承父类,拥有了父类的属性和方法,也可以拥有自己特有的属性和方法。
    • 父类的私有属性和方法,子类不能直接访问。不同包下的父类默认方法,子类也不可以直接访问。
  • 哪些成员不可以被继承

  • 辟谣

    不能继承 != 不能访问

    e.g. 父类的私有属性,子类无法继承。但是可以通过父类公开的访问方法来间接访问这些私有属性

  • 继承使用要点

    • 父类也称作超类、基类、派生类等。
    • Java中只有单继承,没有像C++那样的多继承。多继承会引起混乱,使得继承链过于复杂,系统难于维护。
    • Java中类没有多继承,接口有多继承。
    • 子类继承父类,可以得到父类的全部属性和方法 (除了父类的构造方法),但不见得可以直接访问(比如,父类私有的属性和方法)。
    • 如果定义一个类时,没有调用extends,则它的父类是:java.lang.Object。
  • 意义:

    • 继承的出现减少了代码冗余,提高了代码的复用性。
    • 让类与类之间产生了关系,为后面要讲的多态的提供了前提。
    • 体现不同抽象层次。
    • 继承的出现,更有利于功能的扩展。
  • 特点、优点

    • 子类继承了父类的成员
    • 具有层次结构
    • 代码重用
    • 可轻松自定义类
    • 父类字段和方法可用于子类
    • 从抽象到具体形成类的继承体系
  • 注意:

    • java是单继承,只能继承一个类。要慎用继承,将唯一的一个继承机会留给最关键的父类或者是无法避免需要继承的地方。
    • 不要仅为了获取其他类中某个功能而去继承
    • 继承需要符合is-a的关系(可以理解为:“子类 is a 父类”);父类更通用更抽象,子类更特殊更具体(因为父类相对于子类而言,父类中有的方法,子类全都有;而子类有的属性和方法父类未必有,所以子类更具体,父类更抽象)。
    • 继承背后的思想就是基于已经存在的类来构建新类,当从已存在的类继承时,就重用了它的方法和属性,还可以添加新的方法和属性来定制新类以应对需求(新类称之为子类,被扩建的类称之为父类)。在java当中,除了Object类之外,所有的类都是子类,且都有唯一的一个父类。继承在OOP中不可或缺,创建一个类时,总是在继承(因为顶层是Object类,所以当你创建一个类时,都在间接或直接的继承了Object父类,即你创建的类永远为子类)
  • 继承的使用:

    • 继承关系的产生通常是为了定义出功能更为具体、更为强大的子类。所以,定义子类后,一般创建子类对象使用。子类可以直接使用父类非私有的成员变量与成员方
    • (注:如果成员变量没有使用private修饰,则子类也可直接访问。)
    • 通过继承之后,父类的属性、方法,子类都会继承(也就是说父类有的东西,子类都有,但是子类有的父类不一定有)。
  • 特征

    • 子类拥有父类非private的属性、方法。

    • 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。

    • 子类可以用自己的方式实现父类的方法。

    • Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如A类继承B类,B类继承C类,所以按照关系就是C类是B类的父类,B类是 A类的父类,这是Java继承区别于C++继承的一个特性。

    • 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。

    • 子类继承了父类,就继承了父类的方法和属性。

    • 在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。

    • 在Java中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”。

    • 关于继承的规则:

      子类不能直接访问父类中私有的(private)的成员变量和方法。

方法重写

  • 是指在子类中,子类对继承来的方法不太满意,要重新修改一下,可以根据需要对从父类中继承来的方法进行改造,叫方法重写,或方法改写。用自身的行为替换父类的行为。在程序执行时,子类的方法将覆盖父类的方法。

  • 当父类的功能无法满足子类的需求时,这个时候我们需要重写父类的方法。出现方法重写的前提是两个类存在继承关系。

  • 方法重写必须保证方法名参数列表不变 (方法名+参数列表 == 方法签名

  • *返回类型可以修改(协变),但必须是父类方法返回类型的子类(更具体)

  • *访问修饰符可以修改,但必须是发扬光大

  • *子类抛出的异常不能大于父类被重写的方法的异常。

  • *注解符号:@Override 加上这个注解,就会自动帮助我们判断代码是否符合方法重写的语法规则

  • 子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。

  • 父类静态方法、私有方法不能被重写。

    public class TestOverride {
        public static void main(String[] args) {
            Vehicle v1 = new Vehicle();
            Vehicle v2 = new Horse();
            Vehicle v3 = new Plane();
            v1.run();
            v2.run();
            v3.run();
            v2.stop();
            v3.stop();
        }
    }
     
    class Vehicle { // 交通工具类
        public void run() {
            System.out.println("跑....");
        }
        public void stop() {
            System.out.println("停止不动");
        }
    }
    class Horse extends Vehicle { // 马也是交通工具
        public void run() { // 重写父类方法
            System.out.println("四蹄翻飞,嘚嘚嘚...");
        }
    }
     
    class Plane extends Vehicle {
        public void run() { // 重写父类方法
            System.out.println("天上飞!");
        }
        public void stop() {
            System.out.println("空中不能停,坠毁了!");
        }
    }  
    
  • 总结:
    • 重写方法必须和被重写的方法具有相同的方法名称、参数列表和返回值。也就是说方法重写的是方法体。
    • 重写方法不能使用比被重写方法更严格的访问权限。访问权限只能变大(例如:当被重写方法中的访问权限是protected,重写方法里可以使用public和protected访问修饰符,但被重写的方法中访问权限是public,那么在重写方法中就只能使用public访问修饰符)即:子类的权限修饰符必须要大于或者等于父类的权限修饰符。
    • 父类中的私有方法不能被重写。因为被使用的属性和方法只能在本类中可以使用或者访问,对其子类而言是不可见的。
    • 在子类重写的方法中继续调用父类,父类被重写的方法可以通过调用supper.方法名获取。
    • 子类重写方法中的返回值类型以及异常类型可以与被重写方法中的相同,也可以是其子类。

关键字

super

  • 父类也叫超类,基类,所以super表示父类的东西。本质不是引用变量(这点和this不同,this是对象的引用),super只是关键字,不是指对象的引用。
  • 用法
    • super. 当父类和子类的成员重名时,用作前缀,表示是父类型的成员,用于调用父类中非私有的属性和方法。
    • super() / super(xxx) 调用父类的构造方法,必须写在构造方法中,必须是第一条语句。
    • 在子类构造中:
      • 没有super,创建子类对象时,都会先调用父类无参的构造方法
      • 有super,创建子类对象时,都会先调用super所代表父类的构造方法。
    • 创建子类对象的时候都会先调用父类的构造方法,但是不会创建子类的对象。只是给父类属性赋值用的。
  • 注意:
    • 父类最好有一个无参构造
    • 当this调用本类属性或方法、super调用父类属性或方法时,可以同时出现,也就是this.*和super.*可以同时出现;
    • 当this调用本类构造器、super调用父类构造器时,不可以同时出现,也就是this()和super()不能同时出现。

final

  • 含义:断子绝孙、最终

  • 修饰类

    • 类不能被继承(没有子类了)
  • 修饰方法

    • 方法不能被重写(这是最终版本,后代可以用,不能改),但是可以重载。
  • 修饰变量

    • 修饰基本数据类型的变量

      final的是数据本身,所以数据不可修改

    • 修饰引用数据类型的变量

      final的是堆中数据的引用,引用的指向不可变(引用变量保存的地址不可变),但是堆中的数据可以修改

    • 经常和static一起使用,常量不能改变,多个对象共享,提升为类变量。

    • 方法内部可以定义常量,但是不能用static修饰,因为局部变量位于方法内部,是在运行时存在于栈内存的,而static是在加载时就存在于方法区内(jdk1.8之前)的。

instanceof运算符

instanceof是二元运算符,左边是对象,右边是类;当对象是右面类或子类所创建对象时,返回true;否则,返回false。比如:

public class Test{
    public static void main(String[] args) {
        Student s = new Student("高淇",172,"Java");
        System.out.println(s instanceof Person);//true
        System.out.println(s instanceof Student);//true
    }
}

子类对象的实例化过程

  • 普通对象实例化过程

    • 当JVM遇到new关键字后,通过指令在堆内存中开辟空间,分配地址。
    • 给对象的属性进行默认初始化。
    • 给对象的属性进行显示初始化。
    • 通过构造函数给属性进行初始化。
    • 将地址信息赋值给引用变量。
    • 注意:JVM可能会进行代码的重排序,也就有可能将最后一步移至第二步。
  • 子类对象实例化过程

    1. JVM会去读取指定路径下的Person.class文件,并加载进内存,并会先加载Person的父类(如果有直接父类的情况下)。
    2. 在堆内存中开辟空间,分配地址。
    3. 并在对象空间中,对对象中的属性进行默认初始化
    4. 调用对应的构造函数,进行初始化
    5. 在构造函数中,第一行会先调用父类中的构造函数进行初始化。
    6. 父类初始化完毕后,再对子类的属性,进行显示初始化。
    7. 指定构造函数的特定初始化
    8. 初始化完毕后,将堆内存中的地址值赋给引用变量。
    9. 即过程为:子类属性默认初始化—>父类构造函数初始化—>子类属性显示初始化。
  • 思考:为什么子类实例化的时候要访问父类中的构造函数?

    1. 子类继承了父类,获取到了父类中的内容(属性),所以在使用父类内容之前要先看父类是如何对自己的内容进行初始化的。所以子类在构造对象时,就必须访问父类中的构造函数,因此要在子类的构造函数中加入super语句。
    2. 如果父类中没有定义空构造函数,那么子类的构造函数必须用super明确调用父类中对应的构造函数。
    3. 子类构造函数中如果使用this调用了本类构造函数时,则调用this的这个构造函数中super便没有了,因为super和this都只能定义在第一行。但是可以保证的是,子类中肯定会有其它的构造函数访问父类的构造函数。

Object常用方法

  • Object是级别最高的class,所有的类都是Object的子类,也就意味着所有的java对象都拥有Object类的属性和方法。如果在类的声明中未使用extends关键字指明其父类,则默认继承Object类。

    public class Person {
        ...
    }
    //等价于:
    public class Person extends Object {
        ...
    }
    
  • 常见方法

    • Object类中定义有:public boolean equals(Object obj)方法,提供定义“对象内容相等”的逻辑。比如,我们在公安系统中认为id相同的人就是同一个人、学籍系统中认为学号相同的人就是同一个人。比较两个对象是否相等。Object用的是==,也就是比较两个对象的hashcode,是同一个对象的引用时返回 true 否则返回 false。,所以经常被重写。以Person为例:

      ​ Object 的 equals 方法默认就是比较两个对象的hashcode,是同一个对象的引用时返回 true 否则返回 false。但是,我们可以根据我们自己的要求重写equals方法。比较两个对象是否相等。Object用的是==,所以经常被重写。以Person为例:

    • @Override
          public boolean equals(Object o) {
              if (this == o) return true; // 自己和自己一定相等,没必要浪费CPU继续下去了
              
              if (o == null || this.getClass() != o.getClass()) return false; 
              // this是equals的调用者,o是传入的对象引用。
              // this不可能为null(否则报异常),有 != 无,所以直接return false;
              // getClass() != o.getClass() 如果是不同类型,则不可能相等
              //(根据需要,具体情况具体分析。如果比较的是年龄,比如宝宝和狗狗的年龄可以相同,人和树的年龄可以相同,则这个条件必须去掉)
      
              // 向下转型,Object --> Person 才能拥有Person具备的成员
              Person person = (Person) o;
      
              // 依次取出所有参与比较的属性的值,一一比较,只要有一个不同,则return false
              if (age != person.age) return false;
              if (Double.compare(person.score, score) != 0) return false;
              return name.equals(person.name);
          }
      

      JDK提供的一些类,如String、Date、包装类等,重写了Object的equals方法,调用这些类的equals方法, x.equals (y) ,当x和y所引用的对象是同一类对象且属性内容相等时(并不一定是相同对象),返回 true 否则返回 false。

    • 重写equals的原则:

      • 对称性: 如果x.equals(y)返回是“ true ”, 那么y.equals(x) 也应该返回是“true”。
      • 自反性:x.equals(x)必须返回是“true”。
      • 传递性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
      • 一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你 重复x.equals(y)多少次,返回都是“true”。
      • 任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。
    • 总结:

      • Object类原始的功能是实现判断两个对象是否具有相同的引用;要求判断两个对象状态的相等性;
      • 在JDK标准库中提供一些类,比如前面所讲的String,后续要讲的Date。它们都对equals方法进行了改写;
      • 经常被改(重)写,可以根据需求对其进行改写。
      • Object类的equals()默认比较两个引用是否指向同一个对象(实例)
    • hashCode:

      • hashcode相等,两个对象不一定相等,需要通过equals方法进一步判断;
      • hashcode不相等,两个对象一定不相等;
      • equals方法为true,则hashcode肯定一样;
      • equals方法为false,则hashcode不一定不一样;
    • 重温:"=="和equals

      • == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型 就是比较内存地址。
      • equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也 是==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中 用的比较多,久而久之,形成了equals是比较值的错误观点。
      • 具体要看自定义类里有没有重写Object的equals方法来判断。
      • 通常情况下,重写equals方法,会比较类中的相应属性是否都相等。
    • toString() ** 把对象的信息以字符串的形式展现。Object默认返回的是对象的16进制哈希值,所以经常被重写

    • getClass() 获取对象的真实类型信息(完全限定名 = 包名+类名)

    • hashCode() 第12章集合讲到Hash结构时会讲

    • notify() 第15章多线程通信时会讲

    • notifyAll() 同上

    • wait() 同上

    • wait(xxx) 同上

继承树追溯

  • 属性/方法查找顺序:(比如:查找变量h)

    • 查找当前类中有没有属性h
    • 依次上溯每个父类,查看每个父类中是否有h,直到Object
    • 如果没找到,则出现编译错误。
    • 上面步骤,只要找到h变量,则这个过程终止。
  • 构造方法调用顺序:

    • 构造方法第一句总是:super(…)来调用父类对应的构造方法。所以,流程就是:先向上追溯到Object,然后再依次向下执行类的初始化块和构造方法,直到当前子类为止。静态初始化块调用顺序,与构造方法调用顺序一样,不再重复。
    • 当父类与子类中都有静态代码块与普通代码块时的顺序:父类静态–>子类静态–>父类普通–>父类构造–>子类普通–>子类构造。
    • hashCode() 第12章集合讲到Hash结构时会讲

    • notify() 第15章多线程通信时会讲

    • notifyAll() 同上

    • wait() 同上

    • wait(xxx) 同上

继承树追溯

  • 属性/方法查找顺序:(比如:查找变量h)

    • 查找当前类中有没有属性h
    • 依次上溯每个父类,查看每个父类中是否有h,直到Object
    • 如果没找到,则出现编译错误。
    • 上面步骤,只要找到h变量,则这个过程终止。
  • 构造方法调用顺序:

    • 构造方法第一句总是:super(…)来调用父类对应的构造方法。所以,流程就是:先向上追溯到Object,然后再依次向下执行类的初始化块和构造方法,直到当前子类为止。静态初始化块调用顺序,与构造方法调用顺序一样,不再重复。
    • 当父类与子类中都有静态代码块与普通代码块时的顺序:父类静态–>子类静态–>父类普通–>父类构造–>子类普通–>子类构造。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值