Java基础学习——第五章 面向对象编程(中)

Java基础学习——第五章 面向对象编程(中)

一、面向对象的特征二:继承性

1. 继承性的优点

  1. 减少代码的冗余,提高了代码的复用性
  2. 便于功能的扩展
  3. 继承让类与类之间产生了关系,为多态性的使用提供了前提

2. 继承的格式

class A extends B{}

A:子类 / 派生类 / subclass
B:父类 / 超类 / 基类 / superclass

3. 继承性的规定

  1. 当子类继承了父类后,就继承了父类中声明的所有属性和方法。可以使用父类中定义的属性和方法,也可以声明新的属性和方法,实现功能的扩展

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

  3. 特别的,子类能继承父类中私有的(private)的属性和方法,只是由于封装性的限制不能直接调用。但是可以通过其他操作(如setter和getter)来访问这些private的属性和方法
    在这里插入图片描述

  4. Java只支持==单继承多层继承==,不允许多重继承:

  • 一个子类只能有一个父类:单继承
  • 一个父类可以派生出多个子类
  • 子父类是相对的概念,多个类之间是可以多层继承的
  • 子类继承父类后,就继承了直接父类以及所有间接父类的属性和方法
  1. 如果我们没有显式的声明一个类的父类,则此类继承于 java.lang.Object 类。即所有的java类都直接或间接地继承于Object类,即所有的java类都继承了Object类的功能。

4. 继承性例题

public class Day12_KidsTest {
    public static void main(String[] args) {
        Kids someKid = new Kids();
        //父类的private属性不能直接调用,但可以通过get和set方法访问
        someKid.setSex(1);
        someKid.setSalary(0);
        someKid.manOrWoman();
        someKid.employed();
    }
}

class ManKind {
    private int sex;
    private int salary;

    public ManKind() {
    }

    public ManKind(int sex, int salary) {
        this.sex = sex;
        this.salary = salary;
    }

    public void manOrWoman() {
        if (sex == 1) {
            System.out.println("man");
        } else if (sex == 0) {
            System.out.println("woman");
        }
    }

    public void employed() {
//        if (salary == 0) {
//            System.out.println("no job");
//        } else {
//            System.out.println("job");
//        }
        String info = (salary == 0)? "no job" : "job";
        System.out.println(info);
    }

    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }

    public int getSalary() {
        return salary;
    }

    public void setSalary(int salary) {
        this.salary = salary;
    }
}

class Kids extends ManKind {
    private int yearsOld;

    public Kids() {
    }

    public Kids(int sex, int salary, int yearsOld) {
        super(sex, salary);
        this.yearsOld = yearsOld;
    }

    public void printAge() {
        System.out.println(yearsOld);
    }
}

二、方法的重写

1. 方法重写的概念

  1. 在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。
  2. 重写后,子类的方法将覆盖父类的方法。通过子类对象调用该同名同参数方法时,实际执行的是子类中重写的方法;而通过父类对象调用该同名同参数方法时,仍然执行父类中声明的方法(子类对父类没有影响)

3. 重写的规定

  1. 子类重写的方法必须和父类被重写的方法具有==相同的方法名、形参列表==
    (因为在同一类中,允许存在多个同名但形参列表不同的重载方法,因此只有确定方法名+形参列表,才能确定一个唯一的方法)
  2. 子类重写的方法使用的==权限修饰符不小于父类被重写的方法的访问权限(>=)
    特殊情况:子类
    不能重写父类中声明为private权限的方法==
  3. 子类重写的方法的==返回值类型不大于父类被重写的方法==的返回值类型(<=)
  • 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型必须是void
  • 父类被重写的方法的返回值类型是引用数据类型(A类),则子类重写的方法的返回值类型是A类或A类的子类
  • 父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值必须是相同的基本数据类型
  1. 子类重写的方法==抛出的异常类型不大于父类被重写方法的异常类型==
  2. 子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为 static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。

3. 区分方法的重载与重写

3.1 从定义的角度来看
  1. 重载:在同一个类中,允许存在多个同名,但参数个数或者参数类型不同的方法。(两同一不同:同一类、同名的方法;不同的形参列表);此外,同一个类中的构造器也可以重载
  2. 重写:在子类中对从父类中继承来的方法进行改造,覆盖父类的方法。子类重写的方法必须和父类被重写的方法具有相同的方法名、形参列表
3.2 从编译和运行的角度来看
  1. 重载(不表现为多态性)指允许存在多个同名方法,而这些方法的参数不同。 编译器根据方法不同的形参列表, 对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法,它们的调用地址在编译期就绑定了。 Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。所以: 对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为==“早绑定”“静态绑定”== ;
  2. 而对于多态,只有等到方法调用的那一刻, 解释运行器才会确定所要调用的具体方法,这称为==“晚绑定”**“动态绑定” **重写是多态性的体现==

三、测试4种权限修饰符

在这里插入图片描述

1、类内部(4种都可以用)

package test.order;

/**
 * @ClassName Day12_Order
 * @Description 测试4种权限修饰符在同一个类内的使用
 */

public class Day12_Order {
    private int orderPrivate;
    int orderDefault;
    protected int orderProtected;
    public int orderPublic;

    private void methodPrivate() {
        //四种权限的属性和方法在类内部都可以直接调用
        orderPrivate = 1;
        orderDefault = 2;
        orderProtected = 3;
        orderPublic = 4;
    }

    void methodDefault() {
        //四种权限的属性和方法在类内部都可以直接调用
        orderPrivate = 1;
        orderDefault = 2;
        orderProtected = 3;
        orderPublic = 4;
    }

    protected void methodProtected() {
        //四种权限的属性和方法在类内部都可以直接调用
        orderPrivate = 1;
        orderDefault = 2;
        orderProtected = 3;
        orderPublic = 4;
    }

    public void methodPublic() {
        //四种权限的属性和方法在类内部都可以直接调用
        orderPrivate = 1;
        orderDefault = 2;
        orderProtected = 3;
        orderPublic = 4;
    }
}

2、同一个包的非子类或子类(public、protected、default可以用)

package test.order;

/**
 * @ClassName Day12_OrderTest
 * @Description 测试4种权限修饰符在同一个包的非子类中的使用
 */

public class Day12_OrderTest {
    public static void main(String[] args) {
        Day12_Order order = new Day12_Order();
        order.orderDefault = 1;
        order.orderProtected = 2;
        order.orderPublic = 3;

        order.methodDefault();
        order.methodProtected();
        order.methodPublic();

        //在同一个包的其他类(非子类)中,不能调用Order类的private属性和方法
        //'orderPrivate' has private access in 'test.order.Day12_Order'
        order.orderPrivate = 4;
        //'methodPrivate()' has private access in 'test.order.Day12_Order'
        order.methodPrivate();
    }
}
  • 注意:同一个包的子类能继承父类中的private属性和方法,但由于封装性的限制不能直接调用

3、不同包的子类(public、protected可以用)

package test.suborder;

import test.order.Day12_Order;

/**
 * @ClassName Day12_SubOrder
 * @Description 4种权限修饰符在不同包的子类中的使用
 */

public class Day12_SubOrder extends Day12_Order {

    public void method() {
        orderProtected = 1;
        orderPublic = 2;

        methodProtected();
        methodPublic();

        //在不同包的子类中,不能直接调用Order类的private、缺省(default)属性和方法
        //'orderDefault' is not public in 'test.order.Day12_Order'. Cannot be accessed from outside package
        orderDefault = 3;
        //'orderPrivate' has private access in 'test.order.Day12_Order'
        orderPrivate = 4;
        //'methodDefault()' is not public in 'test.order.Day12_Order'. Cannot be accessed from outside package
        methodDefault();
        //'methodPrivate()' has private access in 'test.order.Day12_Order'
        methodPrivate();
    }
}
  • 注意:不同包的子类能继承父类中的private、缺省(default)属性和方法,但由于封装性的限制不能直接调用

4、不同包的非子类(public可以用)

package test.suborder;

import test.order.Day12_Order;

/**
 * @ClassName Day12_test
 * @Description 4种权限修饰符在不同包的非子类中的使用
 */

public class Day12_test {
    public static void main(String[] args) {
        Day12_Order order = new Day12_Order();

        order.orderPublic = 1;
        order.methodPublic();

        //在不同包的其他类(非子类)中,不能调用Order类的private、缺省(default)、protected属性和方法
        //'orderPrivate' has private access in 'test.order.Day12_Order'
        order.orderPrivate = 2;
        //'orderDefault' is not public in 'test.order.Day12_Order'. Cannot be accessed from outside package
        order.orderDefault = 3;
        //'orderProtected' has protected access in 'test.order.Day12_Order'
        order.orderProtected = 4;
        //'methodPrivate()' has private access in 'test.order.Day12_Order'
        order.methodPrivate();
        //'methodDefault()' is not public in 'test.order.Day12_Order'. Cannot be accessed from outside package
        order.methodDefault();
        //'methodProtected()' has protected access in 'test.order.Day12_Order'
        order.methodProtected();
    }
}

四、关键字:super

1. 问题的由来

  1. 当子类重写了父类的方法后,子类重写的方法将覆盖父类被重写的方法,通过子类对象调用该同名同参数方法时,实际执行的是子类重写后的方法。如果想再调用父类中被重写的方法,就需要使用super关键字进行区分
  2. this关键字使用的场景:在类的方法中,如果形参和类的属性出现重名的情况,则使用this关键字进行区分。this表示当前类的对象或正在创建的对象
  3. super关键字使用的场景:当子类重写了父类的方法后,如果仍要调用父类中同名同参数的方法,就需要使用super关键字进行区分。super表示父类内存空间的标识

2. super关键字的使用

  • 在Java类中使用super来调用父类中的指定成员
2.1 super调用父类的属性和方法
  1. 在子类的方法或构造器中,使用 “super.属性” 或 “super.方法”,显式的调用父类中声明的属性或方法。但通常情况下,我们都选择省略super
  2. 特殊情况:当子类和父类定义了同名的属性时,若想在子类中调用父类的同名属性,则必须显式的使用 “super.属性”,表明调用的是父类中声明的属性
  3. 特殊情况:当子类重写了父类的方法后,若想在子类中调用父类被重写的方法,则必须显式的使用 “super.方法”,表明调用的是父类中声明的方法
2.2 super调用父类的构造器
  1. 在子类的构造器中显式的使用 “super(形参列表)” 调用父类中指定的构造器
  2. 如果要在子类的构造器中调用父类的构造器,则必须要将 “super(形参列表)” 声明在首行
  3. 在类的构造器中,最多只能声明一个 “this(形参列表)” 或 “super(形参列表)” ,即最多只能调用一个本类中重载的构造器或父类中指定的构造器(二选一)!
  4. 在类的构造器中,如果没有显式的声明 “this(形参列表)” 或 “super(形参列表)”,则默认在首行声明了一个父类中空参的构造器 “super()”。
    • 当父类中没有空参构造器时,子类的构造器必须通过 “this(形参列表)” 或者 "super(形参列表)"语句指定调用本类或者父类中相应的构造器
    • 如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有空参构造器,则编译出错
  5. 在类的多个构造器中,至少有一个构造器使用了 “super(形参列表)”
package test;

/**
 * @ClassName Day12_SuperTest
 * @Description super关键字的使用
 */

public class Day12_SuperTest {
    public static void main(String[] args) {
        SuperStudent stu = new SuperStudent();
        stu.show();
    }
}

class SuperPerson {
    //属性
    String name;
    int age;
    String id = "1002"; //身份证号

    //构造器
    public SuperPerson() {
    }

    public SuperPerson(String name) {
        this.name = name;
    }

    public SuperPerson(String name, int age) {
        //在构造器中使用 "this(形参列表)" 调用当前类中指定的其他构造器,放在首行
        this(name);
        this.age = age;
    }

    //方法
    public void eat() {
        System.out.println("人:吃饭");
    }
}

class SuperStudent extends SuperPerson {
    //属性
    String major;
    String id = "1001"; //学号,注意父类和子类中的同名属性不会覆盖

    //构造器
    public SuperStudent() {
    }

    public SuperStudent(String major) {
        //默认有一个super();
        this.major = major;
    }

    public SuperStudent(String name, int age, String major) {
        //在子类的构造器中使用 "super(形参列表)" 调用父类中指定的构造器,放在首行
        super(name, age);
        this.major = major;
    }

    @Override
    public void eat() {
        System.out.println("学生:吃饭");
    }

    public void show() {
        //super和this用来区分父类和子类中同名的属性:this表示当前类的对象或正在创建的对象;super表示父类内存空间的标识
        System.out.println("身份证:" + super.id + ", 学号:" + this.id);
        //子类中重写的方法
        this.eat();
        //父类中被重写的方法
        super.eat();
    }
}

3. this和super关键字的区别

在这里插入图片描述

五、子类对象实例化全过程

  1. 从结果上来看:子类继承了父类以后,就继承了直接父类以及所有间接父类的属性和方法。创建子类的对象后,在堆空间中就会加载所有父类中声明的属性
    在这里插入图片描述

  2. 从过程上来看

    • 当使用子类的构造器创建对象时,需要保证先初始化父类,此时一定会直接或间接地调用父类的构造器,进而调用间接父类的构造器……直到调用了 java.lang.Object 类中空参的构造器
    • 当子类继承父类后,“继承”父类中所有的属性和方法,因此子类有必要知道父类如何为对象进行初始化。正因为==调用过直接父类以及所有间接父类的构造器==,所以在子类对象的内存空间中才能看到父类的成员,子类对象才能调用这些属性和方法
    • 虽然创建子类对象时,调用了父类的构造器,但自始至终就创建过一个对象,即new的子类对象
      在这里插入图片描述

实验——继承性 & super

package test.account;

/**
 * @ClassName Account
 * @Description 实验——继承性 & super
 */

public class Account {
    private int id; //账号
    private double balance; //余额
    private double annualInterestRate; //年利率

    public Account(int id, double balance, double annualInterestRate) {
        this.id = id;
        this.balance = balance;
        this.annualInterestRate = annualInterestRate;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public double getAnnualInterestRate() {
        return annualInterestRate;
    }

    public void setAnnualInterestRate(double annualInterestRate) {
        this.annualInterestRate = annualInterestRate;
    }

    //获取月利率
    public double getMonthlyInterest() {
        return annualInterestRate / 12;
    }

    //取钱
    public void withdraw(double amount) {
        if (balance >= amount) {
            balance -= amount;
            return;
        }
        System.out.println("余额不足!");
    }

    //存钱
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }
}
package test.account;

/**
 * @ClassName CheckAccount
 * @Description 创建 Account 类的一个子类 CheckAccount 代表可透支的账户,
 * 该账户中定义一个属性 overdraft 代表可透支限额。
 * 在 CheckAccount 类中重写 withdraw 方法
 */

public class CheckAccount extends Account {

    private double overdraft;

    public CheckAccount(int id, double balance, double annualInterestRate, double overdraft) {
        super(id, balance, annualInterestRate);
        this.overdraft = overdraft;
    }

    public double getOverdraft() {
        return overdraft;
    }

    public void setOverdraft(double overdraft) {
        this.overdraft = overdraft;
    }

    @Override
    public void withdraw(double amount) {
        if (amount <= getBalance()) {
            //setBalance(getBalance() - amount);
            super.withdraw(amount); //直接调用父类中的withdraw方法
        } else {
            if (amount - getBalance() <= overdraft) {
                overdraft -= (amount - getBalance());
                //setBalance(0);
                super.withdraw(getBalance());
            } else {
                System.out.println("超出可透支额的限度!");
            }
        }
    }
}
package test.account;

/**
 * @ClassName AccountTest
 * @Description
 *
 * 写一个用户程序测试 Account 类。
 * 在用户程序中,创建一个账号为 1122、余额为 20000、年利率 4.5%的 Account 对象。
 * 使用 withdraw 方法提款 30000 元,并打印余额。
 * 再使用 withdraw 方法提款 2500 元,
 * 使用 deposit 方法存款 3000 元,然后打印余额和月利率。
 *
 * 写一个用户程序测试 CheckAccount 类。
 * 在用户程序中,创建一个账号为 1122、余额为 20000、年利率 4.5%,可透支限额为 5000 元的 CheckAccount 对象。
 * 使用 withdraw 方法提款 5000 元,并打印账户余额和可透支额。
 * 再使用 withdraw 方法提款 18000 元,并打印账户余额和可透支额。
 * 再使用 withdraw 方法提款 3000 元,并打印账户余额和可透支额。
 */

public class AccountTest {
    public static void main(String[] args) {
        Account account = new Account(1122, 20000, 0.045);
        account.withdraw(30000);
        System.out.println("您的账户余额为:" + account.getBalance());
        account.withdraw(2500);
        account.deposit(3000);
        System.out.println("您的账户余额为:" + account.getBalance());
        System.out.println("月利率为:" + (account.getAnnualInterestRate() * 100) + "%");

        CheckAccount check = new CheckAccount(1122, 20000, 0.045, 5000);
        check.withdraw(5000);
        System.out.println("您的账户余额为:" + check.getBalance());
        System.out.println("您的可透支额度为:" + check.getOverdraft());
        check.withdraw(18000);
        System.out.println("您的账户余额为:" + check.getBalance());
        System.out.println("您的可透支额度为:" + check.getOverdraft());
        check.withdraw(3000);
        System.out.println("您的账户余额为:" + check.getBalance());
        System.out.println("您的可透支额度为:" + check.getOverdraft());
    }
}

六、面向对象的特征三:多态性

1. 理解多态性

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

2. 何为多态性:对象的多态性

  1. 对象的多态性:父类的引用指向子类的对象(将子类对象赋给父类的引用类型变量),即子类的对象可以替代父类的对象使用
  2. 一个变量只能有一种确定的数据类型,但一个引用类型变量可能指向(引用)多种不同类型的对象
  3. 子类可看做是特殊的父类, 所以父类类型的引用类型变量可以指向子类的对象:向上转型(upcasting)。
Person p = new Student(); //Person类型的引用类型变量p,指向Student类型的对象

3. 多态的使用:虚拟方法调用

  1. Java引用类型变量有两种类型: 编译时类型和运行时类型。 编译时类型由声明该变量时使用的类型决定, 运行时类型由实际赋给该变量的对象决定。 简称: 编译时, 看左边;运行时, 看右边
  2. 若编译时类型和运行时类型不一致, 就出现了对象的多态性。在多态情况下:
  • “编译时, 看左边” : 看的是父类的引用(父类中不具备子类特有的方法,即如果一个引用类型变量声明为父类的类型,但实际指向的是子类的对象,那么在编译时,只能调用父类中声明的方法)
  • “运行时, 看右边” : 看的是子类的对象(虽然在编译时调用的是父类中声明的方法,但在运行时实际执行的是子类重写父类的方法——虚拟方法调用)
  1. 虚拟方法调用:子类对父类的方法进行了重写,在多态情况下,将父类中被重写的方法称为虚拟方法。父类类型的引用变量根据赋给它的不同子类对象,动态地调用子类中重写的方法。这样的方法调用在编译期是无法确定的,因此==多态是一种运行时行为!==
  2. 对象的多态性,只适用于方法,不适用于属性。属性不具备多态性,只看引用类型变量声明的类型(即属性的编译和运行都看左边)。
  • 如果父类和子类具有同名的属性,且具有不同的初始化值。在多态情况下,该属性的值应为父类中初始化的值
//正常情况下:
Student s = new Student();
s.school = “pku”; //合法,Student类有school属性
s.study(); //合法,Student类有study方法
s.eat(); //合法,Student类有eat方法

//多态情况下:虚拟方法调用
Person p = new Student();
p.school = “pku”; //非法,属性是在编译时确定的,编译时p为Person类型,无法调用子类Student中特有的属性
p.study(); //非法,编译时p为Person类型,无法调用子类Student中特有的方法
p.eat(); //合法,编译时p为Person类型,有eat方法;但实际运行的是子类Student中重写的eat方法

4. 多态性的使用前提

  1. 存在类的继承关系
  2. 子类对父类的方法进行了重写

5. 多态性的意义

  • 没有多态性,抽象类和接口就没有存在的意义。因为抽象类和接口是不能创建对象的,因此在使用抽象类和接口时,我们一定会提供其子类或实现类的对象。
  • 一旦方法的形参是引用数据类型的,如果没有多态性,意味着我们要声明大量重载的方法,方法的通用性会极大地缩小

6. 多态性的应用举例

package test;

/**
 * @ClassName Day12_PolymorphismTest
 * @Description 多态性的应用举例
 */

public class Day12_AnimalTest {
    public static void main(String[] args) {
        Day12_AnimalTest test = new Day12_AnimalTest();
        test.func(new Dog()); //Animals animals = new Dog();
        test.func(new Cat()); //Animals animals = new Cat();

    }

    public void func(Animals animals) {
        animals.eat();
        animals.shout();
    }
}

class Animals {

    public void eat() {
        System.out.println("动物:进食");
    }

    public void shout() {
        System.out.println("动物:叫");
    }
}

class Dog extends Animals {
    @Override
    public void eat() {
        System.out.println("狗:吃骨头");
    }

    @Override
    public void shout() {
        System.out.println("狗:汪汪汪");
    }
}

class Cat extends Animals {
    @Override
    public void eat() {
        System.out.println("猫:吃鱼");
    }

    @Override
    public void shout() {
        System.out.println("猫:喵喵喵");
    }
}

7. 向下转型(upcasting)

7.1 向上转型和向下转型
  • 在多态情况下,内存中实际加载了子类特有的属性和方法,但是由于引用类型变量声明为父类类型,导致编译时只能调用父类的属性和方法,而不能调用子类特有的属性和方法。
  • 此时,如何才能调用子类特有的属性和方法?——向下转型:使用强制类型转换符 "()"
  • 注意:强制类型转换 转换的是引用数据类型变量的类型,即左边编译时的类型
//父类的引用指向子类的对象(将子类对象赋给父类的引用变量)————向上转型(多态)
Person p = new Student();
p.school = “pku”; //非法,属性是在编译时确定的,编译时p为Person类型,无法调用子类Student中特有的属性
p.study(); //非法,编译时p为Person类型,无法调用子类Student中特有的方法

//将父类类型的引用变量 强制类型转换 为子类类型————向下转型
Student s = (Student)p;
s.school = “pku”; //合法,此时引用变量p被强制类型转换为子类Student类型,能调用子类中的属性
s.study(); //合法,此时引用变量p被强制类型转换为子类Student类型,能调用子类中的方法

在这里插入图片描述

7.2 instanceof关键字
  1. 强制类型转换的风险:在基本数据类型变量的强制转换中可能会有精度损失;而在多态情况下,父类类型的引用类型变量的强制转换需要预先知道子类对象的类型,即强转后的类型必须与子类对象的实际类型保持一致
  2. 为了避免向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
x instanceof A //如:p instanceof Student

检验x是否为类A的对象,返回值为boolean型
3. 规定:若类B是类A的父类,且 a instanceof A 返回true,则 a instanceof B 也返回true
也就是说,假设引用类型变量a实际指向的是类A的对象,则他指向的对象不仅是类A的对象,也是类A的父类的对象——苹果是苹果的对象,苹果也是水果的对象

public class Day13_InstanceTest {
    public static void main(String[] args) {
        Day13_InstanceTest test = new Day13_InstanceTest();
        test.method(new Person2());
        test.method(new Student2());
        test.method(new Graduate2());
    }

    public void method(Person2 e) {
        System.out.println(e.getInfo()); //虚拟方法调用,根据e实际指向的对象类型调用相应类的方法
        
        if (e instanceof Graduate2) {
            System.out.println("a graduated student");
        }

        if (e instanceof Student2) {
            System.out.println("a student");
        }

        if (e instanceof Person2) { //Condition 'e instanceof Person2' is always 'true'
            System.out.println("a person");
        }
    }
}

class Person2 {
    protected String name="person";
    protected int age=50;

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

class Student2 extends Person2 {
    protected String school="pku";

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

class Graduate2 extends Student2{
    public String major="IT";

    public String getInfo() {
        return "Name: "+ name + "\nage: "+ age + "\nschool: "+ school+"\nmajor:"+major;
    }
}
  1. 如果e指向的是Graduate类的对象,那他指向的对象是Graduate类的对象,也是父类Student和Person的对象,即 e instanceof Graduate, e instanceof Student 和 e instanceof Person都返回true
  2. 如果e指向的是Student类的对象,那他指向的对象是Student类的对象,也是父类Person的对象,但不是子类Graduate的对象,即 e instanceof Student 和 e instanceof Person返回true,e instanceof Graduate 返回false
  3. 如果e指向的是Person类的对象,那他指向的对象是Person类的对象,但不是子类Student和Graduate的对象,即 e instanceof Person 返回true,但 e instanceof Student 和 e instanceof Graduate 返回false
7.3 向下转型的几个常见问题
public class Day13_DownCastingTest {
    public static void main(String[] args) {
        //情况一:编译时通过,运行时不通过
        //举例一:
        People p1 = new Woman();
        //ClassCastException: test.Woman cannot be cast to test.Man
        Man m1 = (Man)p1;
        //举例二:
        People p2 = new People();
        //ClassCastException: test.People cannot be cast to test.Man
        Man m2 = (Man)p2;

        //情况二:编译通过,运行也通过
        Object obj = new Woman();
        //obj可以强转成Woman类型,则一定可以强转成Woman的父类People类型
        People p3 = (People)obj;

        //情况三:编译不通过
        Man m3 = new Woman(); //Woman不是Man的子类
    }
}

class People {}

class Man extends People {}

class Woman extends People {}

8. 多态性练习

8.1 多态情况下调用方法和属性
  1. 若子类重写了父类的方法,就意味着子类里定义的方法彻底覆盖了父类的同名方法,系统将不可能把父类里的方法转移到子类中:编译看左边,运行看右边
  2. 属性不存在重写,即使子类中定义了与父类同名的属性,这一属性依然不可能覆盖父类中同名的属性。即在子类对象的内存空间中两个同名的属性都存在,具体调用哪一个就看引用类型变量的类型:编译和运行都看左边
public class Day13_FieldMethodTest {
    public static void main(String[] args){
        Sub s = new Sub();
        System.out.println(s.count); //20
        s.display(); //20——子类重写了父类的方法
        Base b = s; //多态:父类的引用指向了子类的对象,内存空间中两个同名的count属性都存在
        System.out.println(b == s); //true——两个引用数据类型比较的是地址值
        System.out.println(b.count); //10——属性不存在多态性,属性编译运行都看左边
        b.display(); //20——虚拟方法调用:虽然在编译时调用的是父类的方法,但在运行时实际执行的是子类重写父类的方法
    }
}

class Base {
    int count = 10;

    public void display() {
        System.out.println(this.count);
    }
}

class Sub extends Base {
    int count = 20; //子类和父类声明了同名的属性

    public void display() {
        System.out.println(this.count);
    }
}
8.2 多态性的使用
/**
 * @ClassName Day13_GeometricTest
 * @Description 定义一个测试类GeometricTest,
 * 编写equalsArea方法测试两个对象的面积是否相等(注意方法的参数类型,利用动态绑定技术),
 * 编写displayGeometricObject方法显示对象的面积(注意方法的参数类型,利用动态绑定技术)
 */
public class Day13_GeometricTest {
    public static void main(String[] args) {
        Day13_GeometricTest test = new Day13_GeometricTest();
        Circle2 c1 = new Circle2("red", 1.0, 2.3);
        Circle2 c2 = new Circle2("white", 1.5, 3.3);
        test.displayGeometricObject(c1); //对象的多态性'
        System.out.println(test.equalsArea(c1, c2));


    }

    //动态绑定技术,虚拟方法调用,编译时调用的是父类的方法,运行时实际执行的是子类重写父类的方法
    public boolean equalsArea(GeometricObject o1, GeometricObject o2) {
        return o1.findArea() == o2.findArea();
    }

    public void displayGeometricObject(GeometricObject o) {
        System.out.println(o.findArea());
    }
}

class GeometricObject {
    protected String color;
    protected double weight;

    public GeometricObject(String color, double weight) {
        this.color = color;
        this.weight = weight;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

    public double findArea() {
        return 0.0;
    }
}

class Circle2 extends GeometricObject {
    private double radius;

    public Circle2(String color, double weight, double radius) {
        super(color, weight);
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    @Override
    public double findArea() {
        return Math.PI * radius * radius;
    }
}

class MyRectangle extends GeometricObject {
    private double width;
    private double height;

    public MyRectangle(String color, double weight, double width, double height) {
        super(color, weight);
        this.width = width;
        this.height = height;
    }

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    @Override
    public double findArea() {
        return width * height;
    }
}
8.3 考查多态的笔试题目
public class Day13_InterviewTest {
    public static void main(String[] args) {
        //对象的多态性
        Base1 base = new Sub1();
        //编译时调用的是父类的方法,运行时实际执行的是子类重写父类的方法
        base.add(1, 2, 3); //sub_1

        //向下转型
		Sub1 s = (Sub1)base;
        //如果类中同时存在可变个数形参和确定参数的重载方法,
        //且在调用时传的形参都符合二者的形参列表,则优先调用确定参数的方法
		s.add(1,2,3); //sub_2
    }
}

class Base1 {
    public void add(int a, int ... arr) {
        System.out.println("base");
    }
}

class Sub1 extends Base1 {
    //在java中,可变个数形参与相同类型的数组形参本质上是相同的
    //即public void add(int a, int ... arr)和public void add(int a, int[] arr)的形参列表是一致的
    //所以认为子类对父类的方法进行了重写。因此在多态情况下,运行时实际执行的是子类重写父类的方法
    public void add(int a, int[] arr) {
        System.out.println("sub_1");
    }

    //该方法只是子类中重载的方法,并不是对父类同名同参数方法的重写,所以实际运行时不会执行该方法
	public void add(int a, int b, int c) {
		System.out.println("sub_2");
	}
}

9. 谈谈对多态性的理解

  1. 实现代码的通用性:父类的引用指向子类的对象。编译时调用的是父类的方法,运行时实际执行的子类重写父类的方法(虚拟方法调用)
  2. 举例:
    • Object类中定义的public boolean equals(Object obj) {}
    • JDBC:使用Java程序操作(获取数据库连接、CRUD) 数据库(MySQL、Oracle、DB2、SQL Server)
  3. 如果没有多态性的话,抽象类和接口就没有意义;抽象类和接口的使用体现了多态性,因为抽象类和借口不能实例化

七、Object类的使用

1. Object类是所有Java类的根父类

  1. 如果在类的声明中未使用extends关键字指明其父类, 则默认父类为java.lang.Object类:
public class Person {
...
}

等价于:

public class Person extends Object {
...
}

例如:

method(Object obj){} //可以接收任何类作为其参数:对象的多态性
Person p = new Person();
method(p);
  1. 数组也可以看成一种特殊的类,继承与Object类,同样也能调用Object中声明的方法

2. Object类中的主要结构

  1. Object类中没有声明属性,且只声明了一个空参构造器
  2. Object类中的主要方法
    在这里插入图片描述

3. equals()方法

3.1 比较运算符 ==
  1. 可以用来比较基本数据类型变量或引用数据类型变量:
    • 基本数据类型变量比较数值:当两个变量的数值相等,返回true。不一定类型要相同,存在自动类型提升(如char型和int型进行比较,char型先自动类型提升为int型);但是两个变量的类型一定要能统一(如boolean型和int型就无法比较)
    • 引用数据类型变量比较地址值:当两个引用类型变量保存的地址值相同,即指向同一个对象时,返回true
3.2 equals()方法
3.2.1 Object类中的equals()方法
  1. 所有类都继承了Object类, 也就获得了equals()方法。 可以对equals()方法进行重写
  2. Object类中equals()的定义:指示其他某个对象是否与此对象“相等”,返回值为boolean类型
public boolean equals(Object obj) {
	return (this == obj);
}
  1. equals()方法只适用于比较两个引用数据类型变量,其作用和比较运算符 == 相同,比较两个引用数据类型变量的地址值是否相同,即是否指向同一个对象实体
  2. 格式:obj1.equals(obj2)
3.2.2 String/Date/File/包装类中的equals()方法
  • String、Date、File、包装类等都重写了Object类中的equals()方法,重写后的方法不再比较两个引用类型变量的地址值,而是比较两个引用类型变量指向的两个对象的“实际内容”(一般是相应的属性)是否相同。
3.2.3 自定义类重写equals()方法
  1. 自定义类的对象调用equals()方法,通常希望==比较两个对象的“实际内容”(相应的属性值)是否相同==。因此,我们需要对Object类中的equals()进行重写
  2. 重写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”,null.equals(x)会报空指针异常 ;
      x.equals(和x不同类型的对象)永远返回“false”
  3. 自定义类重写equals()方法示例:
class consumer {
    String name;
    int age;

    @Override
    public boolean equals(Object o) { //对象的多态性
        //如果两个引用类型变量保存的地址值相同,即指向同一个对象,那么相应的属性值一定相同
        if (this == o) {
            return true;
        }
        //如果形参指向的对象类型不是consumer类型,则返回false,即x.equals(和x不同类型的对象)永远返回false
        if (!(o instanceof consumer)) {
            return false;
        }

        //在多态情况下,形参o(引用类型变量)的类型是Object类的,但指向的对象是consumer类型的,
        //虽然对象内存空间中加载了子类consumer特有的属性和方法,但由于引用类型变量声明为父类Object类型,
        //导致编译时只能调用Object类的属性和方法,而不能调用子类consumer特有的属性和方法
        //向下转型:将引用形参的类型强转为consumer,使得形参o在编译时可以调用consumer类特有的属性和方法
        consumer consumer = (consumer) o;
        //比较int类型的属性age的值是否相同,比较String类型的属性name的实际内容是否相同
        return age == consumer.age && 
        		name.equals(consumer.name); //这里调用的equals方法是String类中重写的equals方法
    }
}
3.3 面试题:==和equals()的区别
  1. == 既可以比较基本数据类型变量也可以比较引用数据类型变量。对于基本数据类型变量就是比较值,对于引用数据类型变量就是比较地址值
  2. equals()是java.lang.Object类中声明的方法,如果该方法没有被重写过默认和==相同,用于比较引用数据类型变量的地址值;而在String等类中的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equals方法是比较值的错误观点
  3. 具体要看自定义类里有没有重写Object的equals方法来判断
  4. 通常情况下,重写equals方法,会比较类中的相应属性是否都相等
3.4 equals()重写练习

编写Order类,有int型的orderId, String型的orderName,相应的getter()和setter()方法,两个参数的构造器,重写父类的equals()方法:public boolean equals(Object obj), 并判断测试类中创建的两个对象是否相等。

public class Day13_OrderTest {
    public static void main(String[] args) {
        Order o1 = new Order(1001, "AA");
        Order o2 = new Order(1001, "BB");
        Order o3 = new Order(1001, "AA");
        System.out.println(o1.equals(o2));
        System.out.println(o1.equals(o3));
    }
}

class Order {
    private int orderId;
    private String orderName;

    public Order(int orderId, String orderName) {
        this.orderId = orderId;
        this.orderName = orderName;
    }

    public int getOrderId() {
        return orderId;
    }

    public void setOrderId(int orderId) {
        this.orderId = orderId;
    }

    public String getOrderName() {
        return orderName;
    }

    public void setOrderName(String orderName) {
        this.orderName = orderName;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Order)) {
            return false;
        }
        Order order = (Order) o;
        return orderId == order.orderId &&
                orderName.equals(order.orderName);
    }
}

4. toString()方法

4.1 Object类中的toString()方法
  1. Object类中toString()方法的定义:返回==调用该方法的对象的类名和引用地址==(通过hashcode值计算出该对象在堆空间中的地址值),返回值是String类型
  2. 在多态情况下(父类的引用指向了子类的对象),父类类型的引用类型变量调用toString方法,返回的是实际指向的子类对象的类名和引用地址。也就是说,实际哪个对象调用toString方法,就返回该对象的类名和引用地址
public String toString() {
	return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
  1. 当输出一个对象的引用数据类型变量时,实际上就是调用当前对象的toString()方法,System.out.println()自动调用了toString()方法
Date now=new Date();
//二者输出结果相同
System.out.println(now);
System.out.println(now.toString());
  1. 当进行String与其它引用数据类型变量的连接操作时, 自动调用该引用类型变量指向的对象的toString()方法
//二者输出结果相同
System.out.println(“now=+now);
System.out.println(“now=+now.toString());
4.2 String/Date/File/包装类中的toString()方法
  1. String、Date、File、包装类等都重写了Object类中的toString()方法,重写后的方法不再返回调用该方法的对象的类名和引用地址,而是返回对象的“实际内容”。
  2. 如String类重写了toString()方法, 返回字符串的值:
s1 = “hello”;
System.out.println(s1); //hello
System.out.println(s1.toString()); //相当于调用了String类重写的toString()方法
  1. 基本类型数据变量转换为String类型时,调用了对应包装类的toString()方法
int a=10; 
System.out.println(“a=+a); //a=10
4.3 自定义类重写toString()方法
  1. 自定义类的对象调用toString()方法时,通常希望==返回对象的“实际内容”(如对象的类型、属性值)==。因此,可以对Object类中的toString()方法进行重写
  2. 若当前类重写了toString()方法,当实例化一个类的对象并声明了其引用类型变量p时,System.out.println§; 也会自动调用toString()方法,即等价于System.out.println(p.toString());
@Override
public String toString() {
	return "Order{" + "orderId=" + orderId + ", orderName='" + orderName + '\'' + '}';
}

八、JUnit单元测试

JUnit单元测试步骤:

  1. 创建Java类,进行单元测试
    • 该Java类的要求是:① 权限是public;② 提供无参的公共构造器
  2. 在改类中声明单元测试方法
    • 该单元测试方法的要求是:① 权限是public;② 没有返回值;③ 没有形参
  3. 在单元测试方法上声明注解 @Test ,并导入JUnit:import org.junit.Test;
  4. 在方法体内声明测试代码
  5. 此时右键可以直接运行该单元测试方法
import org.junit.Test;

public class Day13_JUnitTest {
    @Test
    public void testEquals() {
        String s1 = "MM";
        String s2 = "MM";
        System.out.println(s1.equals(s2));
    }
}

九、包装类的使用

1. 包装类(Wrapper)的概念

  1. 针对8种基本数据类型定义相应的引用类型——包装类(封装类)
  2. 目的:使基本数据类型变量也具有类的特征,从而可以调用类中的方法
    在这里插入图片描述

2. 基本数据类型、包装类与String类之间的相互转换

在这里插入图片描述

2.1 基本数据类型与包装类之间的转换
  1. 基本数据类型 --> 包装类:装箱
    • 调用以实际值为形参的包装类构造器:Integer t = new Integer(11);
    • 调用以字符串为形参的包装类构造器:Float f = new Float(“32.1F”);
    • 自动装箱(JDK5.0新特性)
//基本数据类型 --> 包装类:调用包装类的构造器 / 自动装箱
@Test
public void test1() {
	//***********************调用包装类的构造器
    int num1 = 10;
    Integer in1 = new Integer(num1); //Integer包装类的构造器,形参为int类型的数值
    System.out.println(in1.toString()); //10 包装类重写了toString方法,返回对象的"实际内容"
    Integer in2 = new Integer("123"); //Integer包装类的另一个构造器,形参为字符串
    System.out.println(in2.toString()); //123

    Float f1 = new Float(12.3f);
    System.out.println(f1); //相当于省略了toString
    Float f2 = new Float("12.3");
    System.out.println(f2);

    Boolean b1 = new Boolean(true);
    Boolean b2 = new Boolean("true");
    Boolean b3 = new Boolean("true123");
    //false 在形参为字符串的情况下,只要传入的值的不是"true"(忽略大小写),就默认为false
    System.out.println(b3);
    
    //***********************自动装箱
    //正常来说,num1是int类型的变量,不能作为method方法的形参。需要对其进行手动装箱才能传入method方法
    method(new Integer(num1)); 
    //但是当把num1直接作为形参传入method方法时,也能编译成功,这是因为Java对其进行了自动装箱
    method(num1); //编译成功 相当于 Object obj = new Integer(num1)
    
    int num2 = 10;
    Integer in3 = num2; //自动装箱
    
    boolean b4 = true;
    Boolean b5 = b4;
}

public void method(Object obj) {
    System.out.println(obj);
}
  1. 包装类 --> 基本数据类型:拆箱
    • 调用包装类的方法:xxxValue()
    • 自动拆箱(JDK5.0新特性)
//包装类 --> 基本数据类型:调用包装类的方法xxxValue() / 自动拆箱
@Test
public void test2() {
    Integer in1 = new Integer(12);
    int i1 = in1.intValue();
    System.out.println(i1 + 1);

    Float fl1 = new Float(12.3);
    float f1 = fl1.floatValue();
    System.out.println(f1 + 1);
    
    //***********************自动拆箱
    //直接将一个Integer对象的引用in1赋给int型变量num1,相当于int num1 = in1.intValue();
    int num1 = in1;
}
2.2 (基本数据类型、包装类)与String类之间的转换
  1. 基本数据类型、包装类 --> String类型
    • 字符串的连接运算:+ “”,基本数据类型可以直接和字符串做连接运算;包装类的对象通过自动拆箱也可以直接和字符串做连接运算
    • 调用String类重载的valueOf()方法,形参列表可以是基本数据类型,也可以是包装类的对象
    • 调用包装类重写的toString()方法
//基本数据类型、包装类 --> String类型:调用String类重载的方法valueOf(Xxx xxx)
@Test
public void test3() {
    int num1 = 10;
    //方式1:字符串的连接运算
    String str1 = num1 + "";
    String str2 = new Integer(num1) + ""; //自动拆箱
    System.out.println(str2);

    //方式2:调用String类重载的valueOf()方法,形参列表可以是基本数据类型,也可以是包装类对象的引用
    float f1 = 12.3f;
    String str3 = String.valueOf(f1);
    System.out.println(str3); //"12.3"
    Double d1 = new Double(12.4);
    String str4 = String.valueOf(d1);

    //方式3:调用包装类重写的toString()方法
    String str5 = new Integer(num1).toString();
    System.out.println(str5);
}
  1. String类型 --> 基本数据类型、包装类
    • 调用包装类的parseXxx(String s)方法,返回值为基本数据类型
//String类型 --> 基本数据类型、包装类:调用包装类的parseXxx(String s)方法
@Test
public void test4() {
    String str1 = "123";
    int num1 = Integer.parseInt(str1);
    System.out.println(num1 + 1);

    String str2 = "true";
    boolean b1 = Boolean.parseBoolean(str2);
    System.out.println(b1); //true
    String str3 = "true1";
    boolean b2 = Boolean.parseBoolean(str3);
    System.out.println(b2); //false
}

2. 包装类例题

例1
  • 如下两个题目输出结果相同吗?各是什么:
  • 考察到的知识点:
  1. 三元运算符存在自动类型提升:表达式1和表达式2统一为double类型,所以最终的输出结果才是1.0而不是1
  2. 对象的多态性:Object o1 = new Integer(1);
  3. System.out.println()会自动调用toString()方法
  4. 虚拟方法调用:编译时调用的是父类Object的toString()方法,实际执行的是子类Integer重写的toString()方法
  5. 包装类重写的toString方法,返回对象的"实际内容"
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1); //1.0 三元运算符存在自动类型提升!!!
Object o2;
if (true)
o2 = new Integer(1);
else
o2 = new Double(2.0);
System.out.println(o2); //1 省略了toString
例2
public void method() {
	Integer i = new Integer(1);
	Integer j = new Integer(1);
	System.out.println(i == j); //false 比较的是两个引用类型变量的地址值
	
    Integer m = 1; //自动装箱,范围在[-128,127]内,实际上没有new新的对象
	Integer n = 1; //自动装箱,范围在[-128,127]内,实际上没有new新的对象
	//在Integer类的源码中定义了IntegerCache的结构,其中定义了一个Integer类型的数组,
	//默认缓存了-128~127的整数,如果使用自动装箱的方式,将[-128,127]范围的值转换为Integer类型的对象时
	//会直接从IntegerCache中获取,不用再去new新的对象,所以m和n实际指向了同一个对象实体
	System.out.println(m == n); //true
	
    Integer x = 128; //相当于new了一个Integer对象
	Integer y = 128; //相当于new了一个Integer对象
	System.out.println(x == y); //false
}
例3

利用Vector代替数组处理:从键盘读入学生成绩(以负数代表输入结束),找出最高分,并输出学生成绩等级。

  • 提示:数组一旦创建,长度就固定不变,所以在创建数组前就需要知道它的长度。而向量类java.util.Vector可以根据需要动态伸缩。
  • 创建Vector对象: Vector v=new Vector();
  • 给向量添加元素: v.addElement(Object obj); //obj必须是对象
  • 取出向量中的元素: Object obj=v.elementAt(0);
  • 注意第一个元素的下标是0,返回值是Object类型的。
  • 计算向量的长度: v.size();
  • 若与最高分相差10分内: A等; 20分内: B等;30分内: C等;其它: D等
import java.util.Scanner;
import java.util.Vector;

public class Day13_ScoreTest {
    public static void main(String[] args) {
        //1. 实例化Scanner,用于从键盘获取学生成绩
        Scanner scan = new Scanner(System.in);

        //2. 创建Vector对象
        Vector v = new Vector();

        //3.通过for(;;)或while(true)的方式,给Vector中添加数据
        int maxScore = 0;
        for (;;) {
            System.out.print("请输入学生成绩:");
            int score = scan.nextInt();
            if (score < 0) {
                break;
            }
            if (score > 100) {
                System.out.println("输入的数据非法,请重新输入!");
                continue;
            }
            //v.addElement(new Integer(score)); //手动装箱
            v.addElement(score); //自动装箱
            //4.获取学生成绩最大值
            if (maxScore < score) {
                maxScore = score;
            }
        }

        //5.遍历Vector中的元素,得到每个学生的成绩,并与最大成绩比较,得到每个学生的等级
        for (int i = 0; i < v.size(); i++) {
            char level;
            Object obj=v.elementAt(i);

            //Integer inScore = (Integer)obj;
            //int score = inScore.intValue(); //手动拆箱

            int score = (Integer)obj; //自动拆箱

            if (maxScore - score <=10) {
                level = 'A';
            } else if (maxScore - score <= 20) {
                level = 'B';
            } else if (maxScore - score <= 30) {
                level = 'C';
            } else {
                level = 'D';
            }

            System.out.println("Student-" + i + ", score is" + score + ", grade is" + level);
        }
    }
}

        //3.通过for(;;)或while(true)的方式,给Vector中添加数据
        int maxScore = 0;
        for (;;) {
            System.out.print("请输入学生成绩:");
            int score = scan.nextInt();
            if (score < 0) {
                break;
            }
            if (score > 100) {
                System.out.println("输入的数据非法,请重新输入!");
                continue;
            }
            //v.addElement(new Integer(score)); //手动装箱
            v.addElement(score); //自动装箱
            //4.获取学生成绩最大值
            if (maxScore < score) {
                maxScore = score;
            }
        }

        //5.遍历Vector中的元素,得到每个学生的成绩,并与最大成绩比较,得到每个学生的等级
        for (int i = 0; i < v.size(); i++) {
            char level;
            Object obj=v.elementAt(i);

            //Integer inScore = (Integer)obj;
            //int score = inScore.intValue(); //手动拆箱

            int score = (Integer)obj; //自动拆箱

            if (maxScore - score <=10) {
                level = 'A';
            } else if (maxScore - score <= 20) {
                level = 'B';
            } else if (maxScore - score <= 30) {
                level = 'C';
            } else {
                level = 'D';
            }

            System.out.println("Student-" + i + ", score is" + score + ", grade is" + level);
        }
    }
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值