java面向对象编程的三大特性

面向对象(中)面向对象的三大特性

封装性

介绍

  1. 为什么需要封装?封装的作用和含义?

    例子:我要用洗衣机,只需要按一下开关和洗涤模式就可以了。有必要了解洗衣机内部的结构吗?有必要碰电动机吗?答案是不需要的,我们只需要按下开关让它自己运行就好了。

  2. 我们程序设计追求 “高内聚,低耦合”。

    高内聚:类的内部数据操作细节自己完成,不允许外部干涉;

    低耦合:仅对外暴露少量的方法用于使用。

  3. 隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。

    通俗的说就是把该隐藏的隐藏起来,该暴露的暴露出来,这就是封装性的设计思想。

一、问题的引入

  1. 当我们创建一个类的对象以后,就可以通过 “对象.属性” 的方式,对对象的属性进行赋值;此时赋值操作要受到属性的数据类型和存储范围的制约,除此之外,没有其它制约条件!

  2. 但是在实际问题中,我们往往需要给属性赋值时加入额外的条件,这个条件不能再属性声明时体现,所以我们只能通过方法来进行限制条件的添加。比如以下代码中的 年龄限制方法(setAge())

public class Test_18 {
    public static void main(String[] args) {
        Animal animal = new Animal();
        //animal.name = "小白";
        // 加上权限修饰符后提示:'age' has private access in 'com.laoyang.java.Animal'
        //animal.age = 18;
        animal.eat();
        animal.show();

        // 年龄必须大于0,如果年龄小于0,则把年龄设置为 0
        animal.setAge(18);
        animal.show();

        System.out.println(animal.getAge());
    }
}

class Animal {
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setAge(int a) {
        if (a >= 0) {
            age = a;
        } else {
            age = 0;
        }
    }

    public int getAge() {
        return age;
    }

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

    public void show() {
        System.out.println("Animal{" + "name=" + name + ", age=" + age + '}');
    }
}

同时我们还需要避免用户在使用 “对象.属性” 的方式对属性进行赋值,所以需要将属性声明为私有的(private),此时针对于属性就体现了代码的封装性!

Ps:这里的 get 和 set 方法在以后的项目中会经常用到!

二、封装性的体现(体现不等同封装性!)

我们将类的属性进行私有化(private),同时提供公共的(public)方法来获取(get)和设置(set)对应属性的值

拓展

封装性的体现

  1. 我们将类的属性进行私有化(private),同时提供公共的(public)方法来获取(get)和设置(set)对应属性的值

  2. 不对外暴露的私有方法

  3. 单例模式

三、权限修饰符(封装性的体现需要权限修饰符来配合)

  1. public:公开
  2. private:私有的
  3. protected:包内可见的,且对子类可见
  4. default:默认权限,也可以叫做 “缺省”(不写权限修饰符的时候就是用的这个)

权限修饰符都可以用来修饰类的内部结构:属性、方法、构造器、内部类

注意:

  1. 默认权限不要手动加上,手动加上的话就会报错!

  2. 只能使用 default(缺省) 或 public 来修饰类!

总结

java 提供了4种权限修饰符来修饰类和类的内部结构,体现类以及类的内部结构在被调用时的可见性的大小(简单说就是调用的时候能不能看见对应的值)

练习

修饰符的使用

这里暂时只演示三种修饰符:public、default(缺省)、private(还有一种在继承性中进行演示)

package com.laoyang.java;

public class Test_19 {
    private int orderPrivate;
    int orderDefault;
    public int orderPublic;

    private void methodPrivate(){
        orderPrivate = 1;
        orderDefault = 2;
        orderPublic = 3;
    }

    void methodDefault(){
        orderPrivate = 1;
        orderDefault = 2;
        orderPublic = 3;
    }

    public void methodPublic(){
        orderPrivate = 1;
        orderDefault = 2;
        orderPublic = 3;
    }
}

class OrderTest{
    public static void main(String[] args) {
        Test_19 test = new Test_19();
        test.orderPublic = 1;
        test.orderDefault = 2;
        /*
           出了 Test_19 类之后,私有的结构就不可以调用了
           报错提示:'orderPrivate' has private access in 'com.laoyang.java.Test_19'
         */
        //test.orderPrivate = 3;

        test.methodPublic();
        test.methodDefault();
        /*
           出了 Test_19 类之后,私有的结构就不可以调用了
           报错提示:'methodPrivate()' has private access in 'com.laoyang.java.Test_19'
         */
        //test.methodPrivate();
    }
}

在一个新的包下创建一个类来测试 private 权限修饰符的效果

package com.laoyang.test;

import com.laoyang.java.Test_19;

/**
 * 对应 Test_19 类,测试权限修饰符效果
 */
public class Test_19s {
    public static void main(String[] args) {
        Test_19 test = new Test_19();
        test.orderPublic = 1;
        /*
           出了 Test_19 类之后,缺省的结构就不可以调用了
           报错提示:'orderDefault' is not public in 'com.laoyang.java.Test_19'. Cannot be accessed from outside package
         */
        //test.orderDefault = 2;
        /*
           出了 Test_19 类之后,私有的结构就不可以调用了
           报错提示:'orderPrivate' has private access in 'com.laoyang.java.Test_19'
         */
        //test.orderPrivate = 3;

        test.methodPublic();
        /*
           出了 Test_19 类之后,缺省的结构就不可以调用了
           报错提示:'methodDefault()' is not public in 'com.laoyang.java.Test_19'. Cannot be accessed from outside package
         */
        //test.methodDefault();
        /*
           出了 Test_19 类之后,私有的结构就不可以调用了
           报错提示:'methodPrivate()' has private access in 'com.laoyang.java.Test_19'
         */
        //test.methodPrivate();
    }
}

使用权限修饰符之后,在不同的类、方法中都会有不同的效果在这里插入图片描述
打勾的表示使用对应修饰符之后可以使用的区域

封装与隐藏

创建程序,在其中定义两个类:Person 和 PersonTest 类。定义如下:

  1. 用 setAge() 设置人的合法年龄 (0~130),用 getAge() 返回人的年龄。

  2. 在 PersonTest 类中实例化 Person 类的对象 person, 调用 setAge() 和 getAge()方法,体会 Java 的封装性。

package com.laoyang.java;

public class PersonTest {
    public static void main(String[] args) {
        Person person = new Person();
        person.setAge(180);
        System.out.println(person.getAge());
    }
}

class Person {
    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if (age >= 0 && age <= 130) {
            this.age = age;
        }
        System.out.println("传入的数据不合法!");
    }
}

这种题其实是要分两个类去写的,但是我这里为了简单就写在一个类中了,请大家自行遵守编程规范…

小问题

  1. 构造器的作用是什么?使用中有哪些注意点(>=3条)

    作用:① 创建对象 ② 初始化对象结构

    注意点:① 不能互相调用 ② 必须和类名保持一致 ③ 显式声明时一定要创建一个无参构造器

  2. 关于类的属性的赋值,有几种赋值的方式。谈谈赋值的先后顺序

    ① 构造器中初始化值

    ② 对象.方法 或 对象.属性 赋值

    ③ 显式赋值(手动赋值)

    ④ 默认初始化值

    ④ -> ③ -> ① -> ②

  3. this关键字可以用来调用哪些结构,简单说明一下其使用。

    属性:this.属性

    方法:this.方法

    构造器:this()、this(形参列表)

    this 用于调用当前对象!

  4. Java中目前学习涉及到的四种权限修饰符都有什么?并说明各自的权限范围

    default(缺省,默认修饰符)、public(公开的)、private(私有的)、protected(父子)

    在这里插入图片描述

  5. 创建Circle类,提供私有的radius属性,提供相应的get和set方法,提供求圆面积的方法。

    public class CircleTest{
        public static void main(String[] args){
            Circle circle = new Circle();
            circle.setRadius(5);
            System.out.println(circle.findArea());
        }
    }
    
    class Circle{
        private double radius;
        
        public void setRadius(double radius){
            this.radius = radius;
        }
        
        public double getRadius(){
            return radius;
        }
        
        public double findArea(){
            return Math.PI * radius * radius;
        }
    }
    

继承性(inheritance)

一、好处

  1. 解决代码冗余,提高代码复用性,将重复代码放入父类中,然后由子类继承使用

  2. 便于功能的扩展,将重复的功能代码放入父类中,然后子类继承使用

  3. 为多态性提供了前提

二、格式与体现

继承性的格式:class A extends B{ } (A表示子类,B表示父类)

体现:一旦子类 A 继承了父类 B 以后,子类 A 中就获取了父类 B 中的声明结构(主要为属性和方法)

注意:父类中声明为 private 的属性或方法,子类继承父类以后,仍然认为获取了父类中的私有结构;只有因为封装性的影响,使得子类不能直接调用父类的结构而已

子类继承父类以后,还可以声明自己持有的属性或方法,实现功能的扩展

三、java 中对继承性的规定:

  1. 一个类可以被多个子类继承

  2. 一个类只能有一个父类(如果没有手动继承一个父类,那么默认就是 java.lang.Object)

  3. 子父类是相对的概念

  4. 子类直接继承父类,称为:直接父类。间接继承的父类称为:间接父类

  5. 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法

例子:现有A、B、C三个类,B 继承了 A,C 继承了 B(此时的 C 就有了 A 和 B 中的所有公共结构)

四、说明

  1. 如果我们没有显式的声明一个类的父类的话,则此类继承于 java.lang.Object 类

  2. 所有 java 类(除 java.lang.Object 类之外)都直接或间接继承与 java.lang.Object 类

  3. 意味着:所有的 java 类具有 java.lang.Object 类声明的功能

五、案例

创建父类:Test_01

package test.test1;

public class Test_01 {
    String name;
    private int age;

    public int getAge(){
        return age;
    }

    public void setAge(int age){
        this.age = age;
    }

    public Test_01() {
    }

    public Test_01(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void eat(){
        System.out.println("吃饭");
    }

    public void goToBed(){
        System.out.println("睡觉");
    }
}

创建子类,继承父类:Test_02(继承关键字:extends

package test.test1;

public class Test_02 extends Test_01 {
//    String name;
//    int age;
    String profession;

    public Test_02() {
    }

    public Test_02(String name, int age, String profession) {
        this.name = name;
        //this.age = age;
        setAge(age);
        this.profession = profession;
    }

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

    /**
     * 玩游戏
     */
    public void playGames() {
        System.out.println("玩游戏");
    }

    public void show() {
        System.out.println("name=" + this.name + ";age=" + getAge());
    }
}

编写测试类,查看效果

package test.test1;

/**
 * 继承性(inheritance)
 * @date 2021/4/12
 */
public class Test_03 {
    public static void main(String[] args) {
        Test_01 test_01 = new Test_01();
        test_01.name="小明";
        //test_01.age = 18;

        Test_02 test_02 = new Test_02();
        test_02.eat();
    }
}

练习

继承性小练习

(1) 定义一个ManKind类,包括

  • 成员变量 int sex 和 int salary ;
  • 方法void manOrWoman():根据sex的值显示“man”(sex == 1)或者“woman”(sex == 0);
  • 方法void employeed():根据salary的值显示“no job”(salary==0)或者“ job”(salary!=0)。
package test.test2;

public class ManKind {
    int sex;
    int salary;

    /**
     * 根据sex的值显示“man”(sex==1)或者“woman”(sex==0);
     */
    public void manOrWoman(){
        if (sex == 1){
            System.out.println("man");
        }else if (sex == 0){
            System.out.println("woman");
        }
    }

    /**
     * 根据salary的值显示“no job”(salary==0)或者“ job”(salary!=0)。
     */
    public void employeed(){
        if (salary == 0){
            System.out.println("no job");
            return;
        }
        System.out.println("job");
    }
}

(2) 定义类Kids继承ManKind,并包括

  • 成员变量int yearsOld;
  • 方法printAge( )打印yearsOld的值。
package test.test2;

public class Kids extends ManKind {
    int yearsOld;

    /**
     * 打印yearsOld的值
     */
    public void printAge(){
        System.out.println("我今年已经" + yearsOld + "岁了");
    }
}

(3) 定义类 KidsTest,在类的 main 方法中实例化 Kids 的对象 someKid,用该对象访问其父类的成员变量及方法。

package test.test2;

/**
 * 练习
 */
public class KidsTest {
    public static void main(String[] args) {
        Kids someKid = new Kids();
        // 访问变量
        someKid.sex = 1;
        someKid.salary = 10000;
        someKid.yearsOld = 19;
        // 访问方法
        someKid.manOrWoman();
        someKid.employeed();
        someKid.printAge();
        System.out.println("性别:" + someKid.sex + ";薪资:" + someKid.salary);
    }
}

计算圆柱的体积

(1) 创建 Circle(圆)类和 Cylinder(圆柱)类

package test.test3;

public class Circle {
    /**
     * 半径
     */
    private double radius;

    public Circle() {
        radius = 1;
    }

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

    public double getRadius() {
        return radius;
    }

    /**
     * 计算圆的面积
     */
    public double findArea() {
        return Math.PI * radius * radius;
    }
}
package test.test3;

public class Cylinder extends Circle {
    /**
     * 高
     */
    private double length;

    public Cylinder() {
        length = 1;
    }

    public double getLength() {
        return length;
    }

    public void setLength(double length) {
        this.length = length;
    }

    /**
     * 计算圆柱体积
     */
    public double findVolume() {
        return findArea() * length;
    }
}

(2) 在 CylinderTest 类中创建 Cylinder 类的对象,设置圆柱的底面半径和高,并输出圆的面积和圆柱的体积。

package test.test3;

/**
 * CylinderTest
 */
public class CylinderTest {
    public static void main(String[] args) {
        Cylinder cylinder = new Cylinder();
        cylinder.setRadius(5);
        cylinder.setLength(3.2);
        System.out.println("圆的面积为:" + cylinder.findArea());
        System.out.println("圆柱的体积为:" + cylinder.findVolume());
    }
}

方法重写

介绍

重写:子类继承父类后,可以对父类中同名同参数的方法进行覆盖操作

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

规定

方法的声明权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型 { // 方法体 }

  1. 约定俗称:子类中的叫 “重写的方法”,父类中的叫 “被重写方法”

  2. 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同

  3. 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符

  4. 返回值类型

    ① 父类被重写的方法返回值类型是 void,那么子类重写的方法的返回值类型只能是 void

    ② 父类被重写的方法返回值类型是 A 类型,那么子类重写的方法的返回值类型可以是 A 类型或者 A 类的子类

    ③ 父类被重写的方法返回值类型是基本数据类型(比如:int),则子类重写的方法返回值类型必须是相同的基本数据类型(必须也是 int)

  5. 子类重写的方法抛出的异常类型不能大于父类被重写的方法抛出的异常类型

    注意: 子类不能重写父类中的 private 方法!

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

@Override:注解,此处标识当前方法为重写方法

案例

父类:Person

package test.test4;

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

    public Person() {}

    public Person(String name, int age) {
        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;
    }

    public void eat() {
        System.out.println("吃饭");
    }

    public void walk(int distance) {
        System.out.println("走路,走了" + distance + "公里");
    }

    private void show(){
        System.out.println("显示");
    }

    public Object info(){
        return null;
    }

    public int basic(){
        return 0;
    }
}

子类:Student

package test.test4;

public class Student extends Person {
    private String major;

    public Student() {
    }

    public Student(String major) {
        this.major = major;
    }

    public String getMajor() {
        return major;
    }

    public void setMajor(String major) {
        this.major = major;
    }

    public void study() {
        System.out.println("学习,专业是:" + this.major);
    }

    /**
     * 重写父类中的eat方法
     */
    @Override
    public void eat(){
        System.out.println("吃了两桶饭");
    }

    /*
      错误重写
      private void eat(){}

      报错提示:'eat()' in 'test.test4.Student' clashes with 'eat()' in 'test.test4.Person'; attempting to assign weaker access privileges ('private'); was 'public'
    */

    /**
     * 此方法不够成重写!
     * 因为父类中的权限修饰符为:private
     */
    public void show(){
        System.out.println("关闭");
    }

    /**
     * 在子类中的重写方法返回类型可以是父类的子类类型
     * 比如这里的 String 就是 Object 类型的子类
     * @return
     */
    @Override
    public String info(){
        return null;
    }

    /**
     * 在子类中的重写方法返回类型如果是基本数据类型,则不能使用它的子类替换!
     * 错误写法:public double basic(){return 1;}
     * @return
     */
    @Override
    public int basic(){
        return 1;
    }
}

测试类:OverrideTest

package test.test4;

/**
 * 方法重写(override/overwrite)
 */
public class OverrideTest {
    public static void main(String[] args) {
        Student student = new Student("计算机科学与技术");
        // 返回:走路,走了10公里
        student.walk(10);
        // 返回:学习,专业是:计算机科学与技术
        student.study();
        // 返回:吃了两桶饭
        student.eat();
        // 返回:关闭
        student.show();
    }
}

大家可以根据最后返回的数据为基础来查看是否重写了对应的方法

protected 权限修饰符的使用

  1. 先定义一个公共类,提供四种修饰符的变量和方法

    package test.test5;
    
    public class Order {
        int i;
        public int j;
        protected int k;
        private int m;
    
        /**
         * 在当前类中的方法中不管是哪一种权限修饰符都可以正常使用
         */
       void showI(){
           i = 1;
           j = 2;
           k = 3;
           m = 4;
       }
    
       public void showJ(){
           i = 1;
           j = 2;
           k = 3;
           m = 4;
       }
    
       protected void showK(){
           i = 1;
           j = 2;
           k = 3;
           m = 4;
       }
    
       private void showM(){
           i = 1;
           j = 2;
           k = 3;
           m = 4;
       }
    }
    
  2. 在同一个包下定义测试类,在进行调用查看效果

    package test.test5;
    
    /**
     * protected 权限修饰符的演示
     */
    public class ProtectedTest {
        public static void main(String[] args) {
            Order order = new Order();
            order.i = 1;
            order.j = 2;
            order.k = 3;
    
            order.showI();
            order.showJ();
            order.showK();
    
            /*
                私有的(private)属性和方法无法在其它类中使用
                order.m = 4;
                order.showM();
             */
        }
    }
    

    在此,我们可以看到,同一包下,除了私有的(private)修饰符修饰的变量和方法无法调用外,其它的都可以正常调用

  3. 在其它包下创建一个Order的子类

    package test.test5.test5_5;
    
    import test.test5.Order;
    
    public class SubOrder extends Order {
        public void method(){
            j = 2;
            k = 3;
            showJ();
            showK();
    
            /*
                在不同包的子类中无法调用 私有的(private) 和 缺省的(default) 属性和方法
                i = 1;
                k = 4;
                showJ();
                showM();
             */
    
        }
    }
    

    在不同包下并继承了父类的情况下,私有的和缺省的 都不能直接调用

  4. 在其它包下创建一个普通的测试类

    package test.test5.test5_5;
    
    import test.test5.Order;
    
    public class OrderTest {
        public static void main(String[] args) {
            Order order = new Order();
            order.j = 2;
            order.showJ();
    
            /*
                不同包下的普通类(非子类)要使用Order类,不能调用声明为缺省(default)、私有(private)和protected的属性和方法
                order.i = 1;
                order.k = 3;
                order.m = 4;
                order.showI();
                order.showK();
                order.showM();
             */
        }
    }
    

    在不同包并没有继承任何父类的情况下,只有公共的(public)修饰的变量和方法可以进行调用

在这里插入图片描述

多态性

介绍

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

什么是多态性:① 对象多态性:父类的引用指向子类的对象

多态性的使用:虚拟方法调用

有了对象的多态性以后,我们在编译期只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法

多态性的使用前提

  1. 类的继承关系

  2. 方法的重写

案例

创建父类:Person

package test.test8;

public class Person {
    String name;
    int age;

    public void eat() {
        System.out.println("吃饭");
    }

    public void walk() {
        System.out.println("走路");
    }
}

创建子类:Man 和 Woman

package test.test8;

public class Man extends Person {
    boolean isSmoking;

    public void earnMoney(){
        System.out.println("男人挣钱养家");
    }

    @Override
    public void eat() {
        System.out.println("多吃饭,练腹肌");
    }

    @Override
    public void walk() {
        System.out.println("走出社会王的步伐");
    }
}
package test.test8;

public class Woman extends Person {
    boolean isBeauty;

    public void goShopping() {
        System.out.println("女人喜欢购物");
    }

    @Override
    public void eat() {
        System.out.println("多吃饭,长不高");
    }

    @Override
    public void walk() {
        System.out.println("走出蛇皮怪的步伐");
    }
}

测试类

package test.test8;

/**
 * 多态性(Polymorphism)
 * @date 2021/4/14
 */
public class PersonTest {
    public static void main(String[] args) {
        Person person = new Person();
        person.eat();

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

        System.out.println("----------------------------------");

        // 对象多态性:父类的引用指向子类的对象
        Person person1 = new Man();
        // 多态的使用:当调用子父类同名同参的方法时,实际执行的是子类重写父类的方法(虚拟方法无法调用)
        person1.eat();
        person1.walk();
        
        // 错误提示:Cannot resolve method 'earnMoney' in 'Person'
        // person1.earnMoney();

        Person person2 = new Woman();
        person2.eat();
    }
}

向下转型

父类:Person

package test.test10;

/**
 * Person
 * @date 2021/4/14
 */
public class Person {
    String name;
    int age;

    int id = 1;

    public void eat() {
        System.out.println("吃饭");
    }

    public void walk() {
        System.out.println("走路");
    }
}

子类:Man 和 Woman

package test.test10;

/**
 * Man
 * @date 2021/4/14
 */
public class Man extends Person {
    boolean isSmoking;

    int id = 2;

    public void earnMoney(){
        System.out.println("男人挣钱养家");
    }

    @Override
    public void eat() {
        System.out.println("多吃饭,练腹肌");
    }

    @Override
    public void walk() {
        System.out.println("走出社会王的步伐");
    }

}
package test.test10;

/**
 * Woman
 * @date 2021/4/14
 */
public class Woman extends Person {
    boolean isBeauty;

    public void goShopping() {
        System.out.println("女人喜欢购物");
    }

    @Override
    public void eat() {
        System.out.println("多吃饭,长不高");
    }

    @Override
    public void walk() {
        System.out.println("走出蛇皮怪的步伐");
    }
}

测试类:PersonTest

package test.test10;

/**
 * 多态性(Polymorphism)
 * @date 2021/4/14
 */
public class PersonTest {
    public static void main(String[] args) {
        Person person = new Person();
        person.eat();

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

        System.out.println("----------------------------------");

        // 对象多态性:父类的引用指向子类的对象
        Person person1 = new Man();
        // 多态的使用:当调用子父类同名同参的方法时,实际执行的是子类重写父类的方法(虚拟方法无法调用)
        person1.eat();
        person1.walk();
        /*
           错误提示:Cannot resolve method 'earnMoney' in 'Person'
         */
        // person1.earnMoney();

        Person person2 = new Woman();
        person2.eat();

        System.out.println("----------------------------------");

        Person person3 = new Man();
        System.out.println(person3.id);

        System.out.println("----------------------------------");

        // 父类是不能直接去调用子类中特有的方法和属性,编译时 person3 是 Person 类型
        //person3.earnMoney();

        person3.name = "Tom";
        //person3.isSmoking = false;

        /*
         有了对象的多态性后,内存中实际上是加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时只能调用父类中声明的属性和方法;
         子类特有的属性和方法是不能调用的
         
         问:那我们要如何调用子类特有的属性和方法呢?
         答:可以使用强制类型转换(这种行为被称为 "向下转型");对象中还有一种被称为向上转型,也就是所谓的自动转换
         */
        Man man1 = (Man)person3;
        man1.earnMoney();
        man1.isSmoking = false;

        /*
         注意:只要是强制转换就会有一定的风险,基本数据类型中的强制转换可能会导致精度丢失,而对象中的强制转换有可能会导致转换失败
         例子如下:(这里无法转 Woman 的原因是因为 person3 声明的是 Man 类型的)
         异常提示:Exception in thread "main" java.lang.ClassCastException: test.test10.Man cannot be cast to test.test10.Woman
         */
        //Woman woman = (Woman) person3;
        //woman.goShopping();
    }
}

instanceof 关键字的使用

为了避免向下转型出现的问题,可以在转型之前使用 instanceof 判断两个对象是否可以进行转型

格式:if(变量 instanceof 类){ }

进行对比的 “类” 需要是一个与变量有所关系的(子类、父类)!

package test.test10;

/**
 * 多态性(Polymorphism)
 * @date 2021/4/14
 */
public class PersonTest {
    public static void main(String[] args) {
        // 此处省略部分代码.....

        //为了避免上面 “向下转型” 中出现的问题,提供了一个关键字:instanceof
        /*
         instanceof 关键字的使用
         a instanceof A :判断对象 a 是否是类 A 的实例,如果是,则返回 true;如果不是,则返回 false

         使用场景:为了避免在向下转型时出现 ClaaCastException 的异常,我们在向下转型之前,需要先进行 instanceof 判断了,一旦返回true,就进入向下转型;如果返回false,则不进行向下转型

         如果 a instanceof A 返回 true,则 a instanceof B 也返回 true(其中 B 是 A 的父类)
         */
        if (person3 instanceof Woman){
            Woman woman = (Woman) person3;
            woman.goShopping();
            System.out.println("Woman 类型强制转换成功");
        }

        if (person3 instanceof Man){
            Man man2 = (Man) person3;
            man2.earnMoney();
            System.out.println("Man 类型强制转换成功");
        }

        if (person3 instanceof Person){
            System.out.println("PERSON");
        }

        if (person3 instanceof Object){
            System.out.println("Object");
        }

        // 问题一:编译时通过,运行时不通过
        // 例子一
        //Person p1 = new Man();
        //Woman woman = (Woman) person3;
        // 例子二
        //Person p2 = new Person();
        //Man m = (Man) p2;

        // 问题二:编译时通过,运行时也通过
        //Object object = new Woman();
        //Person p = (Person) object;

        // 问题三:编译不通过(不相关的两个类是不能赋值的)
        //String str = new Date();
    }
}

练习

练习一

建立InstanceTest 类,在类中定义方法 method(Person e); 在method中:

(1) 根据e的类型调用相应类的getInfo()方法。

(2) 根据e的类型执行: 如果e为Person类的对象,输出: “a person”; 如果e为Student类的对象,输出: “a student” “a person ” 如果e为Graduate类的对象,输出: “a graduated student” “a student” “a person”

package test.test11;

/**
 * InstanceTest
 * @date 2021/5/8
 */
public class InstanceTest {
    public static void main(String[] args) {
        InstanceTest instanceTest = new InstanceTest();
        instanceTest.method(new Graduate());
    }

    public void method(Person e){
        System.out.println(e.getInfo());
        System.out.println("----------------------------");

        // 从小到大进行判断
        if (e instanceof Graduate){
            System.out.println("a graduated student");
        }

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

        if (e instanceof Person){
            System.out.println("a person");
        }
    }
}

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

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

class Student extends Person {
    protected String school = "pku";

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

class Graduate extends Student {
    public String major = "IT";

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

练习二

定义三个类,父类 GeometricObject 代表几何形状,子类 Circle 代表圆形,MyRectangle 代表矩形。

定义一个测试类 GeometricTest,编写 equalsArea 方法测试两个对象的面积是否相等(注意方法的参数类型,利用动态绑定技术),编写 displayGeometricObject 方法显示对象的面积(注意方法的参数类型,利用动态绑定技术)。

package test.test11;

/**
 * GeometricObject
 */
public class GeometricObject {
    protected String color;
    protected double weight;

    public GeometricObject() {
    }

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

    public double findArea(){
        return 0;
    }
}

/**
 * 圆形
 */
class Circle extends GeometricObject{
    private double radius;

    public Circle() {
    }

    public Circle(double radius) {
        this.radius = radius;
    }

    public Circle(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;
    }

    /**
     * 计算圆的面积
     * @return
     */
    @Override
    public double findArea(){
        return Math.PI * getRadius() * getRadius();
    }
}

/**
 * 矩形
 */
class MyRectangle extends GeometricObject{
    private double width;
    private double heigth;

    public double getWidth() {
        return width;
    }

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

    public double getHeigth() {
        return heigth;
    }

    public void setHeigth(double heigth) {
        this.heigth = heigth;
    }

    public MyRectangle() {
    }

    public MyRectangle(double width, double heigth) {
        this.width = width;
        this.heigth = heigth;
    }

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

    /**
     * 计算矩形的面积
     * @return
     */
    @Override
    public double findArea(){
        return width * heigth;
    }
}

测试类

package test.test11;

/**
 * GeometricTest
 * @date 2021/5/8
 */
public class GeometricTest {
    public static void main(String[] args) {
        GeometricTest geometricTest = new GeometricTest();
        geometricTest.equalsArea(new Circle("红色", 10, 5), new MyRectangle("绿色", 12, 5, 3));
        geometricTest.displayGeometricObject(new Circle("红色", 10, 5), new MyRectangle("绿色", 12, 5, 3));

        System.out.println("---------------------------");

        Circle circle = new Circle("红色", 10, 5);
        MyRectangle myRectangle = new MyRectangle("绿色", 12, 5, 3);
        System.out.println(geometricTest.equalsArea1(circle, myRectangle));
        System.out.println("圆的面积:" + geometricTest.displayGeometricObject1(circle));
        System.out.println("矩形的面积:" + geometricTest.displayGeometricObject1(myRectangle));
    }

    public void equalsArea(Circle circle, MyRectangle myRectangle) {
        if (circle.findArea() == myRectangle.findArea()) {
            System.out.println("两个对象的面积相等");
        } else {
            System.out.println("两个对象的面积不相等");
        }
    }

    public void displayGeometricObject(Circle circle, MyRectangle myRectangle) {
        System.out.println("圆的面积:" + circle.findArea());
        System.out.println("矩形的面积:" + myRectangle.findArea());
    }

    /**
     * 或者可以写成以下格式
     */
    public boolean equalsArea1(GeometricObject g1, GeometricObject g2) {
        return g1.findArea() == g2.findArea();
    }

    public double displayGeometricObject1(GeometricObject geometricObject) {
        return geometricObject.findArea();
    }
}

这里写了两种方式,在实际开发中建议使用相对简约且效果无异的方式进行编写

练习三、继承成员变量和继承方法的区别

说明:

  1. 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。(编译看左边,运行看右边)
  2. 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量(编译、运行都看左边)
package test.test11;

/**
 * 小练习:继承成员变量和继承方法的区别
 * @date 2021/5/8
 */
public class FieldMethoodTest {
    public static void main(String[] args) {
        Sub s = new Sub();
        System.out.println(s.count);  // 返回20
        s.display();    // 返回20
        Base b = s;
        /*
            ==:对于引用数据类型来说,比较的是两个引用数据类型的地址值是否相同
         */
        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;

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

小问题

1、什么是多态性?什么是虚拟方法调用?

一种事务的多种形态,就可以称为多态

java中的多态叫做 “对象的多态性” :父类的引用指向子类的对象(比如 A a = new B();)

虚拟方法调用:调用方法时,编译时看左边类型,运行时看右边类型

2、一个类可以有几个直接父类?一个父类可以有多少个子类?子类能获取直接父类的父类中的结构吗?子类能获取父类中 private 权限的属性或方法吗?

一个类只能有一个直接父类;一个父类可以有 n 个子类;子类可以获取直接父类中声明的结构;子类可以获取父类中private权限的结构(因为可以将对应的属性设置get/set方法,私有的方法也可以放在公共方法中然后在进行使用)

一定要注意这里说的是获取!而不是调用,private 声明在父类中的属性和方法是不能直接进行调用的!

3、方法重写的具体规则有哪些?

① 需要满足继承性

② 返回类型(基本数据类型和void必须保持一致)、方法名以及参数列表需要保持一致

③ 子类的权限修饰符不能小于父类的

④ 子类抛出的异常类型可以比父类小一点,也可以和父类一致

4、super 调用构造器,有哪些具体的注意点?

格式:super() | super(形参列表)

① 需要定义在子类构造器的首行

② 子类的每一个构造器都会默认引用一个空的 super()

③ 如果父类没有定义空参构造器,那么子类构造器中必须指定对应的父类构造器才行

Object 类的使用

说明

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

  2. 如果在类的声明中未使用 extends 关键字指明其父类,则默认父类为 java.lang.Object 类

  3. Object 类中的功能(属性、方法)就具有通用性

    属性:无

    方法:equals() / toString() / getClass() / hashCode() / clone() / finalize() / wait() / notify() / notifyAll()

  4. Object 类只声明一个空参的构造器

在这里插入图片描述

==操作符与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() 方法进行重写

    重写的原则:比较两个对象的实体内容是否相同

案例:重写 equals 方法

package test.test12;

import java.util.Date;
import java.util.Objects;

/**
 * ObjectTest1
 * @date 2021/5/8
 */
public class ObjectTest {
    public static void main(String[] args) {
        String name = new String("小明");
        String name2 = new String("小明");
        System.out.println(name.equals(name2));  //返回true

        User user = new User("小明", 18);
        User user2 = new User("小明", 18);
        System.out.println(user.equals(user2));  //返回false

        // 两个类不一致的对象是不能使用 == 进行比较的,但是使用重写后的equals是可以正常编译运行的
        //System.out.println(name == user);   //编译不通过
    }
}

class User {
    String name;
    int age;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 重写的原则:比较两个对象的实体内容是否相同
     * PS:equals() 方法也可以快捷重写,代码可能会有点不太一致,但是效果都是一样的
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o instanceof User) {
//            User user = (User) o;
//            // 确认类型是一致后,在将里面的内容拆分进行比较
//            if (this.age == user.age && this.name.equals(user.name)){
//                return true;
//            }
//            return false;

            // 简写(此处强转 o 是因为如果不转换的话就点不出对应的属性)
            User user = (User) o;
            return this.age == user.age && this.name.equals(user.name);
        }
        return false;
    }
}

虽然 IDEA 提供了快速生成的 equals() 重写方法,但是建议大家还是要自己会手动编写一个 equals() 的重写方法
在这里插入图片描述

练习

练习一

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

package test.test12;

import java.util.Objects;

/**
 * OrderTest
 * @date 2021/5/8
 */
public class OrderTest {
    public static void main(String[] args) {
        OrderTest orderTest = new OrderTest();
        Order order = new Order(1,"Tme");
        Order order1 = new Order(1,"Tme");
        System.out.println(order.equals(order1));
    }
}

class Order {
    private int orderId;
    private String 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;
    }

    public Order() {
    }

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

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

了解:(内存结构)

  • 创建的对象数据存放在常量池中,如果第一次对象创建了一个 AA 放入常量池,第二次又创建一个对象赋值为 AA,那么这个时候这两个对象指向的都是常量池中的同一个数据(因为常量池中有复用性)

练习二

请根据以下代码自行定义能满足需要的MyDate类,在MyDate类中覆盖 equals方法,使其判断当两个MyDate类型对象的年月日都相同时,结果为 true,否则为 false。

public boolean equals(Object o)

package test.test12;

/**
 * MyDateTest
 * @date 2021/5/8
 */
public class MyDateTest {
    public static void main(String[] args) {
        MyDate m1 = new MyDate(14, 3, 1976);
        MyDate m2 = new MyDate(14, 3, 1976);
        if (m1 == m2) {
            System.out.println("m1==m2");
        } else {
            System.out.println("m1!=m2");  // m1 != m2
        }

        if (m1.equals(m2)) {
            System.out.println("m1 is equal to m2");  // m1 is equal to m2
        } else {
            System.out.println("m1 is not equal to m2");
        }
    }

}

class MyDate {
    private int day;
    private int month;
    private int year;

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public MyDate(int day, int month, int year) {
        this.day = day;
        this.month = month;
        this.year = year;
    }

    /**
     * 重写 equals 方法
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj){
            return true;
        }

        if (obj instanceof MyDate){
            MyDate myDate = (MyDate) obj;
            return this.day == myDate.day && this.month == myDate.month && this.year == myDate.year;
        }

        return false;
    }
}

toString() 方法的使用

说明

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

toString 源码

public String toString() {
       return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

案例:重写 toString() 方法

package test.test13;

/**
 * toString()方法的使用
 * @date 2021/5/8
 */
public class ToStringTest {
    public static void main(String[] args) {
        Student student = new Student("小明", 14);
        /*
            重写 toString 方法前返回:test.test13.User@1b6d3586
            重写 toString 方法后返回:将赋的值进行打印
            我们在直接输出student其实默认就是调用了toString方法
         */
        System.out.println(student);
        System.out.println(student.toString());
    }
}

class Student {
    private String name;
    private int 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;
    }

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 重写 toString() 方法
     */
    @Override
    public String toString() {
        return "User{" + "name='" + name + ", age=" + age + '}';
    }
}

练习

练习一

定义两个类,父类GeometricObject代表几何形状,子类Circle代表圆形。
在这里插入图片描述

package test.test13;

/**
 * GeometricObject
 * @date 2021/5/8
 */
public class GeometricObject {
    protected String color;
    protected double weight;

    public GeometricObject() {
    }

    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;
    }
}

class Circle extends GeometricObject {
    private double radius;

    public Circle() {
        color = "white";
        weight = 1.0;
    }

    public Circle(double radius) {
        this();
        this.radius = radius;
    }

    public Circle(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 boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o instanceof Circle) {
            Circle circle = (Circle) o;
            return this.radius == circle.radius;
        }
        return false;
    }

    @Override
    public String toString() {
        return "Circle{" +
                "radius=" + radius +
                '}';
    }

    /**
     * 计算圆的面积
     */
    public double findArea() {
        return Math.PI * getRadius() * getRadius();
    }
}

写一个测试类,创建两个Circle对象,

判断其颜色是否相等;

利用equals方法判断其半径是否相等;

利用 toString() 方法输出其半径。

package test.test13;

/**
 * CircleTest
 * @date 2021/5/8
 */
public class CircleTest {
    public static void main(String[] args) {
        Circle circle = new Circle(1.3);
        Circle circle1 = new Circle("white", 2.0, 1.5);
        // 比较两个对象的颜色是否相同
        System.out.println(circle.getColor().equals(circle1.getColor()));
        
        // 比较两个对象的半径是否相等
        System.out.println(circle.equals(circle1));
        
        // 打印圆的半径
        System.out.println(circle.toString());
        System.out.println(circle1.toString());
    }
}

包装类

IDEA 在普通 java 项目中进行单元测试

步骤

  1. 选择当前工程,点击 Mark Directory as - 选择绿色文件夹 - 在 https://mvnrepository.com/ 找到 JUnit 的 jar 包,然后点击 Project Structure - Libraries 导入下载好的 jar 包

  2. 创建 java 类,进行单元测试

    这里的类是有要求的:① 必须是 public 的 ② 此类需提供午餐的构造方法

  3. 在此类中声明测试方法

    此时的单元测试方法:权限为 public,没有返回值,没有形参

  4. 单元测试方法上需要添加 @Test 注解,并且需要引入对应的包:import org.junit.Test;

  5. 声明好方法以后就可以在里面编写代码块了

  6. 写完代码以后,将鼠标指针放进对应的方法中右键点击 "Run’对应方法名” 即可运行

案例

import org.junit.Test;

/**
 * JUnitTest  单元测试
 *
 * @date 2021/5/11
 */
public class JUnitTest {
    @Test
    public void testEquals() {
        System.out.println("第一个");
    }

    @Test
    public void testTwo() {
        if (1 + 1 == 2) {
            System.out.println("第二个");
        }
    }
}

包装类的使用

说明

  1. java 提供了 8 种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
  2. 需要掌握的:基本数据类型、包装类、String三者之间的转换

在这里插入图片描述

案例一:基本数据类型转包装类

import org.junit.Test;

/**
 * WrapperTest  包装类的使用
 *
 * @date 2021/5/11
 */
public class WrapperTest {
    /**
     * 基本数据类型 转 包装类
     * 实现方式:调用包装类的构造器
     */
    @Test
    public void testOne(){
        int i = 10;
        // 编译不通过,因为基本数据类型不能调用toString方法就行打印
        //System.out.println(i.toString());

        Integer integer = new Integer(i);
        System.out.println(integer.toString());    //返回10

        System.out.println("------------------------------------");

        String number1 = "123";
        Integer integer1 = new Integer(number1);    // 返回123
        System.out.println(integer1);

        String number2 ="123aaa";
        /*
          错误提示:java.lang.NumberFormatException: For input string: "123aaa"
         */
        //Integer integer2 = new Integer(number2);    // 运行报错
        //System.out.println(integer2);

        System.out.println("------------------------------------");

        Float f = new Float(12.8f);
        System.out.println(f);      // 返回 12.8
        Float f1 = new Float("10.5");
        System.out.println(f1);     // 返回 10.5,如果String的值也是带字母或者特殊字符以及文字的那么也会导致报错

        System.out.println("------------------------------------");

        Boolean bool = new Boolean(true);
        System.out.println(bool);    // 返回true

        Boolean bool1 = new Boolean("true");
        System.out.println(bool1);   // 返回true

        Boolean bool2 = new Boolean("true123");
        /*
            Boolean 内部进行了优化,源码如下:
            public static boolean parseBoolean(String s) {
                return ((s != null) && s.equalsIgnoreCase("true"));
            }
            从源码中我们可以看出,它传入的是一个String类型的值,然后用这个值去进行比较,如果不等于 null 并且为 true 时就返回一个true,如果等于或不为true则返回 false
            PS:Boolean 是不区分大小写的,比如传入的值为:TruE、TRue、TrUe、FalSE等值的时候返回的还是 true 或 false
         */
        System.out.println(bool2);   // 返回false

        System.out.println("------------------------------------");

        Order1 order = new Order1();
        System.out.println(order.isMale);   // 返回 false

        System.out.println(order.isFemale);  // 返回 null,因为这是一个包装类(类是引用数据类型,所以默认为 null)
    }
}

class Order1{
    boolean isMale;
    Boolean isFemale;
}

案例二、包装类转基本数据类型

import org.junit.Test;

/**
 * WrapperTest  包装类的使用
 *
 * @date 2021/5/11
 */
public class WrapperTest {
	/**
     * 包装类 转 基本数据类型
     */
    @Test
    public void testTow() {
        Integer integer = new Integer(10);
        int i = integer.intValue();
        System.out.println(i + 10);

        System.out.println("------------------------------------");

        Float f1 = new Float(12.7);
        float f = f1.floatValue();
        System.out.println(f + 8);

        // 。。。其它的包装类也是调用对应的方法即可转换为基本数据类型进行对应的操作
    }
}

自动装箱 与 自动拆箱(JDK5.0 新特性)

import org.junit.Test;

/**
 * WrapperTest  包装类的使用
 *
 * @date 2021/5/11
 */
public class WrapperTest {
    /**
     * JDK5.0新特性:自动装箱 和 自动拆箱
     */
    @Test
    public void testThree(){
        /*
            自动装箱:基本数据类型 转 包装类
         */
        int num1 = 10;
        Integer integer = num1;     // 自动装箱

        boolean bool = true;
        Boolean bools = bool;       // 自动装箱

        /*
            自动拆箱:包装类 转 基本数据类型
         */
        System.out.println(integer.toString());
        int num2 = integer;     // 自动拆箱
    }
}

我们可以清除的看到,使用自动装箱和自动拆箱的方式进行基本数据类型和包装类之间的转换,明显比上面两种案例中的写法更加的简单直白

案例三、基本数据类型、包装类 转 String 类型

import org.junit.Test;

/**
 * WrapperTest  包装类的使用
 *
 * @date 2021/5/11
 */
public class WrapperTest {
    /**
     * 基本数据类型、包装类 转 String类型
     */
    @Test
    public void testFour(){
        int i = 10;
        // 方式一、使用连接运算
        String str1 = i + "";

        //方式二、调用 String 重载的 valueOf() 方法
        float f = 15.5f;
        String str2 = String.valueOf(f);
        System.out.println(str2);   // 返回的实际值为 "15.5"

        Double db = new Double(52.0);
        String str3 = String.valueOf(db);
        System.out.println(str3);   // 返回的实际值为 "52.0"
    }
}

案例四、String 转 基本数据类型、包装类

import org.junit.Test;

/**
 * WrapperTest  包装类的使用
 *
 * @date 2021/5/11
 */
public class WrapperTest {
    /**
     * String 转 基本数据类型、包装类
     */
    @Test
    public void testFives(){
        String str = "123";
        /*
            数值型数据在转换时可能会报:NumberFormatException(boolean类型不会报)
            因为数值类型在转换时,传进来的字符串为数字以外的值则无法进行转换
            但是boolean类型在内部进行了优化,只要不为true,那么就会返回false
         */
        int i = Integer.parseInt(str);
        System.out.println(i);      // 返回 123

        double b = Double.parseDouble(str);
        System.out.println(b);  // 返回 123.0

        boolean bool = Boolean.parseBoolean(str);
        System.out.println(bool);  // 返回 false
    }
}

注意:

数值型数据在转换时可能会报:NumberFormatException(boolean类型不会报);因为数值类型在转换时,传进来的字符串为数字以外的值则无法进行转换;但是 boolean 类型在内部进行了优化,只要不为 true,那么返回的就是 false

练习

练习一

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

提示:数组一旦创建,长度就固定不变,所以在创建数组前就需要知道它的长度。而向量类 java.util.Vector 可以根据需要动态伸缩。

  1. 创建 Vector 对象:Vector v=new Vector();
  2. 给向量添加元素:v.addElement(Object obj); //obj必须是对象
  3. 取出向量中的元素:Object obj=v.elementAt(0);
  4. 注意第一个元素的下标是0,返回值是Object类型的。
  5. 计算向量的长度:v.size();
  6. 若与最高分相差10分内:A等;20分内:B等;30分内:C等;其它:D等
import java.util.Scanner;
import java.util.Vector;

/**
 * ScoreTest  练习
 *
 * @date 2021/5/11
 */
public class ScoreTest {
    public static void main(String[] args) {
        // 1.创建 Scanner 对象,获取键盘输入的数据
        Scanner scanner = new Scanner(System.in);

        //2. 创建 Vector 对象,初始化最大值
        Vector v = new Vector();
        // 最大值
        int max = 0;

        // 3. 通过循环的方式给Vector中添加数组
        while (true) {
            System.out.print("请输入学生成绩(输入负数退出):");
            int fraction = scanner.nextInt();

            // 4. 当输入负数时,跳出循环
            if (fraction < 0) {
                break;
            } else if (fraction > 100) {
                System.out.println("输入的成绩非法,请重新输入");
                continue;
            }

            /*
                3.1:添加操作:v.addElement(Object obj);  //obj必须是对象
                JDK5.0之前的写法:
             */
            //Integer inScore = new Integer(fraction);
            //v.addElement(inScore);      // 使用多态

            //JDK5.0之后的写法:
            v.addElement(fraction);     //自动装箱

            // 5. 获取学生成绩的最大值
            if (fraction > max) {
                max = fraction;
            }
        }

        System.out.println("成绩最大值为:" + max);

        // 6. 遍历 Vector,得到每个学生的成绩
        for (int i = 0; i < v.size(); i++) {
            Object obj = v.elementAt(i);

            //JDk5.0之前
//            Integer integer = (Integer) obj;
//            int score = integer.intValue();

            //JDK5.0之后
            int score = (int) obj;

            if (max - score <= 10) {
                System.out.println("第" + (i + 1) + "位学生的成绩为:" + obj + ";等级为 A");
            } else if (max - score <= 20) {
                System.out.println("第" + (i + 1) + "位学生的成绩为:" + obj + ";等级为 B");
            } else if (max - score <= 30) {
                System.out.println("第" + (i + 1) + "位学生的成绩为:" + obj + ";等级为 C");
            } else {
                System.out.println("第" + (i + 1) + "位学生的成绩为:" + obj + ";等级为 D");
            }
        }
    }
}

该练习中使用了 JDK5.0 之前的写法和之后的写法,效果都是一样的

练习二

如下两个题目输出结果相同吗?各是什么:1.0 和 1

import org.junit.Test;

/**
 * IntegerTest 面试题
 *
 * @date 2021/5/11
 */
public class IntegerTest {
    @Test
    public void testOne(){
        // 一、
        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
    }
}

说明

o1 返回的是 1.0:因为后面比较的类型需要保持一致,否则就会导致编译失败;但是如果出现可以自动转换的时候,那么最后不管返回的是哪一边的数据,都会是级别较高的类型

o2 返回的是 1:因为这个是分开隔开的,并没有想 o1 那样进行比较或选择,所以最后进入那个结构中,返回的就是那个结构中的类型

练习三

下列代码判断返回的分别是什么?

import org.junit.Test;

/**
 * IntegerTest 面试题
 *
 * @date 2021/5/11
 */
public class IntegerTest {
	@Test
    public void testTwo(){
        Integer i = new Integer(1);
        Integer j = new Integer(1);
        System.out.println(i == j);     // 返回 false,==比较的是地址

        Integer m = 1;
        Integer n = 1;
        System.out.println(m == n);    // 返回 true,自动装箱,相当于比较的就是两个int值

        Integer x = 128;
        Integer y = 128;
        System.out.println(x == y);    // 返回 false,这里的 Integer 相当于 new 了两个对象
    }   
}

说明

Integer 内部定义了 IntegerCache 结构,该结构中定义了一个 Integer[] 数组;保存了-128 至 127 范围的整数,如果我们使用自动转型装箱的方式,给 Integer 赋值的范围在 -128 ~ 127 之间,那么就可以直接使用数组中的元素了,就不需要我们手动去 new 了;所以第三个判断最后的结果为 false,因为它定义的值不在 Integer 的数组中!

源码如下:

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1]
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

其它关键字的使用

this 关键字的使用

了解

  1. 在Java中,this关键字比较难理解,它的作用和其词义很接近。

    它在方法内部使用,即这个方法所属对象的引用;

    它在构造器内部使用,表示该构造器正在初始化的对象。

  2. this 可以调用类的属性、方法和构造器

  3. 什么时候使用this关键字呢?

    当在方法内需要用到调用该方法的对象时,就用this。 具体的:我们可以用this来区分属性和局部变量。

    比如:this.name = name;

说明

  1. this 可以用于修饰或调用属性、方法、构造器

  2. this 修饰属性和方法

    • this:可以理解为 当前对象 或 当前正在创建的对象

    • 在类的方法中,我们可以使用 “this.属性” 或 “this.方法” 的方式,调用当前对象属性或方法,但是通常情况下,我们都选择省略 this 关键字

    特殊情况下:如果方法的形参和类的属性同名时,我们必须添加 this 关键字用于区分(加上 this 关键字的变量就是属性)!

    • 在类的构造器中,我们可以使用 “this.属性” 或 “this.方法” 的方式,调用当前对象属性或方法,但是通常情况下,我们都选择省略 this 关键字

    特殊情况下:如果构造器的形参和类的属性同名时,我们必须添加 this 关键字用于区分(加上 this 关键字的变量就是属性)!

  3. this 调用构造器

    • 在类的构造器中,可以使用 this(形参列表) 的方式,来调用本类中指定的其它构造器

    • 构造器中不能通过 this 的方式调用自己

    • 如果一个类中有 n 个构造器,那么最多只有 n-1 个构造器使用了 this

    • 规定:this 关键字必须声明在当前构造器的首行!

    • 构造器内部最多只能使用一个 this 关键字,来调用其它构造器

案例

public class Test_27 {
    public static void main(String[] args) {
        Test27 test = new Test27();
        test.setAge(1);
        System.out.println(test.getAge());

        test.eat();

        System.out.println("-------------");

        Test27 tests = new Test27("小明");

    }
}

class Test27 {
    // 属性
    private String name;
    private int age;

    // 构造器
    public Test27() {
        // 错误调用:不能自己调自己
        //this();
        this.eat();
    }

    public Test27(String name) {
        // 调用空参构造器
        this();
        this.name = name;
        //报错提示:Call to 'this()' must be first statement in constructor body
        //this();
    }

    public Test27(int age) {
        this.age = age;
    }

    public Test27(String name, int age) {
        // 调用有参构造器
        this(age);
        this.name = name;
        //this.age = age;
    }

    // getter、setter 方法
    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    /**
     * 当参数名和属性名相同时,如果不加上 this,那么就都会获取方法内的形参,而不会获取到对应的属性!
     * this 可以理解为当前,比如当前的对象名为test,那么this就相当于 test(意思就如:test.age = age)
     * 注意:这里不能把this换成对象名,因为对象名是类创建好后才有的(先有类在有对象)
     */
    public void setAge(int age) {
        this.age = age;
    }

    // 其它方法
    public void eat() {
        System.out.println("吃饭");
        // this 是可以省略的
        this.study();
    }

    public void study() {
        System.out.println("学习");
    }
}

package 关键字的使用

  1. 为了更好的实现项目中类的管理,提供包的概念
  2. 使用 package 声明类或接口所属的包,声明在源文件的首行
  3. package 属于标识符,遵循标识符的命名规范(“见名知意”)
  4. 每 “.” 一次,代表一层文件目录(比如:com.laoyang.java)

在这里插入图片描述

注意:同一个包下,不能定义同名的接口、类;不同的包下可以定义同名的接口、类!

import 关键字的使用

  1. 在源文件中使用 import 结构导入指定的包下的类、接口

    比如之前数组中学到的 Arrays

  2. 声明在 package(包) 和 class(类)的声明之间

  3. 如果需要导入多个结构,则并列写出即可

  4. 可以使用 “xxx.*” 的方式导入 xxx 下所有的结构(类、接口)

  5. 如果使用的类或者接口时 java.lang 包下定义的,那么就可以省略 import 结构

  6. 如果使用的类或接口是本包下定义的,也可以省略 import 结构

  7. 如果在源文件中使用了不同包下的同名类,则必须至少有一个类需要以全类名的方式进行使用

比如

package com.laoyang.java;

import com.laoyang.exercise.exercisetwo.Account;
// 包下有相同类名、接口名时,无法使用import导入多个包,因为编译器无法识别你到底使用的是哪一个包下的类、接口
// import com.laoyang.exercise.practiceone.Account;

public class Test_29 {
 public static void main(String[] args) {
     Account account = new Account(1000);

     /*
       包下有相同类名、接口名时,无法使用import导入多个包,因为编译器无法识别你到底使用的是哪一个包下的类、接口
       但是 exercisetwo 包下的 Account 类中又没有全参的构造器,所以这个时候就会导致报错
       这种情况下需要大家使用全类名的方式编写程序,如下所示
      */
     // Account account1 = new Account(1000,2000,0.0123);
     com.laoyang.exercise.practiceone.Account account2 = new com.laoyang.exercise.practiceone.Account(1000, 2000, 0.0123);
 }
}
  1. 如果使用 “xxx.*” 的方式表明可以调用 xxx 包下的所有结构,但是如果使用的是 xxx 子包下的结构,则仍然需要使用 import 结构声明(比如 lang 包不用声明,当 lang 的子包还是需要声明的)

  2. import static:导入指定类或接口中的静态结构

比如 import static java.lang.Math.*;
这样就导入了 Math 中所有的结构

比如在类中使用的时候我们就可以把 Math.random 修改成 random

/*
导入 import static java.lang.Math.*; 后
i 也可以得到对应的值,但是如果没有导入的话,就会导致报错!
*/
double i = random() * 10 + 1;
double j = Math.random() * 10 + 1;
System.out.println(i);
System.out.println(j);

练习

this 关键字练习

  1. 创建 Boy 和 Girl 类在这里插入图片描述

  2. 实现对应方法中的功能:marry(娶/嫁);shout(是否到了结婚年龄);compare(比较两个对象的大小)

public class Test_28 {
    public static void main(String[] args) {
        Boy boy = new Boy("罗密欧", 21);
        boy.shout();

        Girl girl = new Girl("朱丽叶", 23);
        girl.marry(boy);

        Girl girl1 = new Girl("祝英台", 18);
        int compare = girl.compare(girl1);
        System.out.println(compare);
        if (compare > 0) {
            System.out.println(girl.getName() + "大");
        } else if (compare < 0) {
            System.out.println(girl1.getName() + "大");
        } else {
            System.out.println("一样大");
        }
    }
}

class Boy {
    private String name;
    private int 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;
    }

    public Boy() {
    }

    public Boy(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void marry(Girl girl) {
        System.out.println("我想娶" + girl.getName());
    }

    public void shout() {
        if (this.age >= 22) {
            System.out.println("可以结婚了");
            return;
        }
        System.out.println("在等几年吧!");
    }
}

class Girl {
    private String name;
    private int 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;
    }

    public Girl() {
    }

    public Girl(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void marry(Boy boy) {
        System.out.println("我想嫁给" + boy.getName());
        // this: 当前对象
        boy.marry(this);
    }

    /**
     * 比较两个对象的大小
     * 返回整数:当前对象大
     * 返回负数:当前对象小
     * 返回 0:当前对象与形参对象相等
     */
    public int compare(Girl girl) {
        if (this.age > girl.age) {
            return 1;
        } else if (this.age < girl.age) {
            return -1;
        } else {
            return 0;
        }
        // 简化
        //return this.age - girl.age;
    }
}

super 关键字的使用

介绍

  1. super理解为:父类
  2. super可以用来调用属性、方法、构造器

super 调用属性和方法

我们可以在子类的方法或构造器中,通过使用"super.属性" 或 “super.方法”,显式的调用父类中声明的属性或方法;但是在通常情况下,我们习惯省略 “super” 关键字

package test.test6;

public class Person {
    int id = 101;
    String name;
    int age;

    public void eat(){
        System.out.println("吃饭");
    }
}
package test.test6;

public class Student extends Person {
    int id = 102;
    String major;

    @Override
    public void eat() {
        System.out.println("人每天都要吃饭");
    }

    public void show() {
        /*
           在一般情况下是可以省略 super 关键字的,如果出现相同名称的属性或方法,就需要使用super进行区分
           否则就会直接调用当前类中的属性或方法进行使用
           案例如下:
         */
        System.out.println("name=" + name + ";age=" + age);
        System.out.println("id = " + id);
        System.out.println("父类id = " + super.id);
    }

    public void study(){
        System.out.println("学习");
        this.eat();
        // 调用父类方法
        super.eat();
    }
}
package test.test6;

/**
 * super 关键字的使用
 * @date 2021/4/14
 */
public class SuperTest {
    public static void main(String[] args) {
        Student student = new Student();
        /*
           返回:name=null;age=0
                id = 102
                父类id = 101
         */
        student.show();
        /* 返回:学习
                人每天都要吃饭
                吃饭
         */
        student.study();
    }
}

注意

  1. 当子类和父类中定义了重名的属性时,如果想在子类中调用父类中声明的属性,则必须显式的使用 “super.属性” 的方式来表名调用父类中声明的属性

  2. 当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用 “super.方法” 的方式来表名调用父类中被重写的方法

super调用构造器

  1. 我们可以在子类构造器中显式的使用 “super(形参列表)” 的方式,调用父类中声明的指定的构造器

  2. “super(形参列表)” 的使用,必须声明在子类构造器的首行

  3. 我们在类的构造器中,针对于 “this(形参列表)” 或 “super(形参列表)” 只能二选一,不能同时出现

  4. 在子类的构造器中如果没有使用 “this(形参列表)” 或 “super(形参列表)”,那么默认调用的就是空的 “super()”

  5. 在类的多个构造器中,至少有一个类的构造器中使用了 “super(形参列表)”,调用父类中的构造器

package test.test6;

public class Person {
    int id = 101;
    String name;
    int age;

    public Person() {
        System.out.println("父类空参构造器");
    }

    public Person(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
}
package test.test6;

public class Student extends Person {
    int id = 102;
    String major;

    public Student() {}

    public Student(int id, String major) {
        // 默认引用 super()
        //super();
        this.id = id;
        this.major = major;
    }

    public Student(int id, String name, int age, String major) {
        super(id, name, age);
        this.major = major;
    }
    
    public void show() {
        /*
           在一般情况下是可以省略 super 关键字的,如果出现相同名称的属性或方法,就需要使用super进行区分
           否则就会直接调用当前类中的属性或方法进行使用
           案例如下:
         */
        System.out.println("name=" + name + ";age=" + age);
        System.out.println("id = " + id);
        System.out.println("父类id = " + super.id);
    }
}
package test.test6;

/**
 * super 关键字的使用
 * @date 2021/4/14
 */
public class SuperTest {
    public static void main(String[] args) {
        /*
            返回:name=小明;age=18
                 id = 102
                 父类id = 1003
         */
        Student student1 = new Student(1003,"小明",18,"S1");
        student1.show();

        System.out.println("--------------------------------");
        
		// 返回:父类空参构造器
        Student student2 = new Student(1004,"S2");
    }
}

注意:如果父类中没有空参构造器,那么子类的构造器中就无法默认调用空的 “super()”;就会导致报错!

子类对象实例化过程

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

  1. 子类继承父类以后,就获取了父类中声明的属性、方法

  2. 创建子类的对象,在堆空间中就会加载所有父类中声明的属性

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

明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象

经典面试题

一、重载和重写的区别?

答:

  1. 在同一个类中定义多个同名不同参的方法,以此构成重载(构造器也可以被重载)

  2. 子类继承父类以后,可以对父类中同名同参的方法进行覆盖操作,以此构成重写(构造器不能被重写)

  3. 重载不表现为多态性,而重写表现为多态性(因为编译的时候重载就已经确定调用的哪一个数据了,而重写在编译的时候认为使用的是父类的,但运行的时候执行的是子类的)

二、多态是编译时行为还是运行时行为?

答:多态属于运行时行为

三、final、finally、finalize 的区别?

四、== 和 equals 的区别?
答:

  1. == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
  2. equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也是==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equals是比较值的错误观点。
  3. 具体要看自定义类里有没有重写Object的equals方法来判断
  4. 通常情况下,重写equals方法,会比较类中的相应属性是否都相等。

五:下列代码中输出的是什么?

public void test() {
    char[] arr = new char[] { 'a', 'b', 'c' };
    System.out.println(arr);    // abc,因为cahr类型在内部自己重写了toString方法
    
    int[] arr1 = new int[] { 1, 2, 3 };
    System.out.println(arr1);   // 地址值
    
    double[] arr2 = new double[] { 1.1, 2.2, 3.3 };
    System.out.println(arr2);   //地址值
}

六、谈谈你对多态性的理解?

答:

  1. 实现代码的通用性

    比如:Object类中的 public boolean equals(Object obj) { } (这里的形参可以是object类型也可以是object它的子类)

  2. 抽象类、接口的使用肯定体现了多态性(因为它们都不能实例化)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值