Java基础系列(6)- 面向对象(中)

面向对象特征之二:继承性

为描述和处理个人信息,定义类Person

import java.util.Date;

class Person {
    public String name;
    public int age;
    public Date birthDate;

    public String getInfo() {
        // ... 
    }
}

为描述和处理学生信息,定义类 Student

import java.util.Date;

class Student {
    public String name;
    public int age;
    public Date birthDate;
    public String school;

    public String getInfo() {
        // ...
    }
}

通过继承,简化Student类的定义:

class Student extends Person {
    public String school;
}

Student 类继承了父类Person的所有属性和方法,并增加了一个属性 school。Person中的属性和方法,Student都可以使用。

为什么要有继承?

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可

继承性的格式

class A extends B {}

A:子类,派生类,subclass

B:父类,超类,基类,superclass

继承的作用

  • 继承的出现减少了代码冗余,提高了代码的复用性。
  • 继承的出现,更有利于功能的扩展。
  • 继承的出现让类与类之间产生了关系,提供了多态的前提。

注意:不要仅为了获取其他类中某个功能而去继承

继承的体现

  • 子类继承了父类,就继承了父类的方法和属性。
  • 在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
  • 在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展” 。

继承的规则

  • 子类不能直接访问父类中私有的(private)的成员变量和方法。
  • Java只支持单继承和多层继承,不允许多重继承

方法重写

重写定义

在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法

重写应用

重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法

重写的规定

方法的声明

权限修饰符 返回值类型 方法名(形参列表) {
    //方法体
}

约定俗成:子类中的叫重写的方法,父类中的叫被重写的方法

重写的的规定

  1. 子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
  2. 子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
  3. 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限(子类不能重写父类中声明为private权限的方法)
  4. 返回值类型
    1. 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
    2. 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
    3. 父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
  5. 子类方法抛出的异常不能大于父类被重写方法的异常

子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。

重写举例

public class Person {
    public String name;
    public int age;

    public String getInfo() {
        return "Name: " + name + "\n" + "age: " + age;
    }
}

public class Student extends Person {
    public String school;

    public String getInfo() { //重写方法
        return "Name: " + name + "\nage: " + age
                + "\nschool: " + school;
    }

    public static void main(String args[]) {
        Student s1 = new Student();
        s1.name = "Bob";
        s1.age = 20;
        s1.school = "school2";
        System.out.println(s1.getInfo());//Name:Bob age:20 school:school2
    }
}

super关键字的使用

super理解为:父类的

super可以用来调用:属性、方法、构造器

super的使用

  1. 我们可以在子类的方法或构造器中,通过使用super.属性super.方法的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略super.
  2. 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用super.属性的方式,表明调用的是父类中声明的属性
  3. 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用super.方法的方式,表明调用的是父类中被重写的方法

注意

  1. 尤其当子父类出现同名成员时,可以用super表明调用的是父类中的成员
  2. super的追溯不仅限于直接父类
  3. super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识

super 调用构造器

  1. 我们可以在子类的构造器中显式的使用super(形参列表)的方式,调用父类中声明的指定的构造器
  2. super(形参列表)的使用,必须声明在子类构造器的首行。
  3. 我们在类的构造器中,针对于this(形参列表)super(形参列表)只能二选一,不能同时出现
  4. 在构造器的首行,没有显式的声明this(形参列表)super(形参列表),则默认调用的是父类中空参的构造器:super()。包括子类也是,如果没有填写,默认super()而不是 this()
  5. 在类的多个构造器中,至少有一个类的构造器中使用了super(形参列表),调用父类中的构造器

this 和 super 的区别

No. 区别点 this super
1 访问属性 访问本类中的属性,如果本类没有此属性则从父类中继续查找 直接访问父类中的属性
2 调用方法 访问本类中的方法,如果本类没有此方法则从父类中继续查找 直接访问父类中的方法
3 调用构造器 调用本类构造器,必须放在构造器的首行 调用父类构造器,必须放在子类构造器的首行

子类对象的实例化过程

从结果上来看:(继承性)

  1. 子类继承父类以后,就获取了父类中声明的属性或方法
  2. 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性

从过程上来看:

当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。

明确:

虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建了一个对象

多态性

理解多态性

可以理解为一个事物的多种形态

何为多态性

即对象的多态性,父类的引用指向子类的对象(或子类的对象赋给父类的引用)

多态的使用

编译时类型和 运行时类型。编译时类型由声明
该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边;运行时,看右边

  • 若编译时类型和运行时类型不一致 , 就出现了对象的多态性 (Polymorphism)
  • 多态情况下 , “ 看左边 ” : 看的是父类的引用(父类中不具备子类特有的方法)
    “ 看右边 ” : 看的是子类的对象(实际运行的是子类重写父类的方法)

多态性的使用前提

  1. 类的继承关系
  2. 方法的重写
  3. 对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)

虚拟方法调用

正常的方法调用

Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();

虚拟方法调用( 多态情况下 )

子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法
确定的。

Person e = new Student();
e.getInfo(); // 调用Student 类的getInfo()

编译时类型和运行时类型

编译时e 为Person 类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo() 方法。—— 动态绑定

面试题

1.多态是编译时行为还是运行时行为

运行时行为

2.方法的重载与重写

二者的定义细节:略

从编译和运行的角度看:

重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。

所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”;

而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。

引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。”

Object 类的使用

向下转型的使用

有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子特有的属性和方法不能调用

如果要调用子类特有的属性和方法,可以使用向下转型,即使用强制类型转换符

Person p1 = new Person();
P1.eat();

Man man = new Man();
man.eat();
man.age = 25;
man.earnMoney;

// 对象的多态性,父类的引用指向子类的对象
Persion p2 = new Man();
// 多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法
P2.eat();
p2.walk();

//但是调用子类特有的方法时,是不能调用的
p2.earnMoney();  // 比如earnMoney是子类特有的方法,则这里不能调用

// 可以进行强制的类型转换
Man m1 = (Man)p2;
m1.earnMoney();

instanceof 的使用

a instanceof A:判断对象a是否是类A的实例,如果是,返回 true,如果不是,返回false

// 可以转换
Man m1 = (Man)p2;
m1.earnMoney();

// 不可以转换
Woman w1 = (Woman)p2;
m1.goShopping();

为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行 instanceof的判断

if(p2 instanceof Woman){
    Woman w1 = (Woman)p2;
	m1.goShopping();
}

== 运算符和 equals() 的区别

== 运算符:

  1. 可以使用在基本数据类型变量和引用数据类型变量中
  2. 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等(不一定类型要相同)
  3. 如果比较的是引用数据类型变量:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体

equals() 方法的使用:

  1. 是一个方法,而非运算符

  2. 只能适用于引用数据类型

  3. Object 类中 equals() 的定义:

    public boolean equals(Object obj) {
        return (this == obj);
    }
    说明:Object 类中定义的 equals() 和 == 的作用是相同的:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
    
  4. 像String、Date、File、包装类等都重写了Object类中的equals() 方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的 "实体内容"是否相同

  5. 通常情况下,我们自定义的类如果使用 equals() 的话,也通常是比较两个对象的 "实体内容" 是否相同。那么我们就需要对Object类中的 equals() 进行重写

    @Override
    public boolean equals(Object obj){
        if (this == obj){
            return true;
        }
        
        if(obj instanceof Customer){
            Customer cust = (Customer)obj;
            // 比较两个对象的每个属性是否相同
            if(this.age == cust.age && this.name.equals(cust.name)){
                return true;
            }else{
                return false;
            }
        }
    }
    

    重写 equals() 方法的原则

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

    面试回答==和equals的区别

    1 == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址

    2 equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也是==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equals是比较值的错误观点。

    3 具体要看自定义类里有没有重写Object的equals方法来判断。

    4 通常情况下,重写equals方法,会比较类中的相应属性是否都相等。

toString()方法的使用

  1. 当我们输出一个对象的引用时,实际上就是调用当前对象的toString()

Object类中 toString() 的定义:

public String toString(){
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
  1. 像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回“实体内容”信息
  2. 自定义类也可以重写toString()方法,当调用此方法时,返回对象的“实体内容”

包装类的使用

  • 针对八种基本数据类型定义相应的引用类型—包装类(封装类)

  • 有了类的特点,就可以调用类中的方法,Java才是真正的面向对象

基本数据类型转换为包装类

java 提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征

import org.junit.Test;

public class WrapperTest {
    //基本数据类型 ----> 包装类:调用包装类的构造器
    @Test
    public void test1(){

        int num1 = 10;
//      System.out.println(num1.toString()); //会报错,num1基本数据类型,没有toString方法
        Integer in1 = new Integer(num1);
        System.out.println(in1.toString());

        Integer in2 = new Integer("123");
        System.out.println(in2.toString());

//        会报异常,得是纯粹的数字
//        Integer in3 = new Integer("123abc");
//        System.out.println(in3.toString());

        Float f1 = new Float(12.3f);
        Float f2 = new Float("12.3");
        System.out.println(f1);
        System.out.println(f2);

        Boolean b1 = new Boolean(true);
        Boolean b2 = new Boolean("true");
        System.out.println(b2);
        Boolean b3 = new Boolean("true12");
        System.out.println(b2);   //false,只有传进去的是大小写true的字符串才会变成Boolean类的true

        Order order = new Order();
        System.out.println(order.isMale); //false     由于是基本数据类型,所以是false
        System.out.println(order.isFemale); //null   由于是类,所以是null
    }
}

class Order{
    boolean isMale;
    Boolean isFemale;
}

包装类转换为基本数据类型

import org.junit.Test;

public class WrapperTest {
    //包装类 ----> 基本数据类型:调用包装类 Xxx的 xxxValue()
    @Test
    public void test2(){
        Integer in1 = new Integer(12);

        int i1 = in1.intValue();
        System.out.println(i1+1);

        Float f1 = new Float(12.3);
        float f2 = f1.floatValue();
        System.out.println((f2+1));
    }
}

自动装箱 和 自动拆箱

import org.junit.Test;

public class WrapperTest {
    //自动装箱:基本数据类型 ----> 包装类
    int num2 = 10;
    Integer in1 = num2; //自动装箱
    
    boolean b1 = true;
    Boolean b2 = b1; //自动装箱
    
    //自动拆箱:包装类 ---->基本数据类型
    System.out.println(in1.toString());
    int num3 = in1; //自动拆箱
}

基本数据类型包装类与String的相互转换

import org.junit.Test;

public class WrapperTest {
    //String 类型 ----> 基本数据类型、包装类:调用包装类的 parseXxx()
    @Test
    public void test5() {
        String str1 = "123";
        //错误的情况
//        int num1 = (int)str1;
//        Integer in1 = (Integer)str1;

        int num2 = Integer.parseInt(str1);
        System.out.println(num2 + 1);

        String str2 = "true";
        boolean b1 = Boolean.parseBoolean(str2);
        System.out.println(b1);
    }

    //基本数据类型、包装类 ---->String类型,调用String重载的valueOf(Xxx xxx)
    @Test
    public void test4() {

        int num1 = 10;
        //方式1:连接运算
        String str1 = num1 + "";
        //方式2:调用String的valueOf(Xxx xxx)
        float f1 = 12.3f;
        String str2 = String.valueOf(f1); //"12.3"

        Double d1 = new Double(12.4);
        String str3 = String.valueOf(d1);
        System.out.println(str2);
        System.out.println(str3); //"12.4"
    }
}

基本类型、包装类与String类间的转换

posted @ 2021-10-04 23:51  dongye95  阅读(58)  评论(0编辑  收藏  举报