Java进阶(四)多态

四、多态

1.面向对象三大特征之三:多态

a.多态的概述、多态的形式

Ⅰ.什么是多态?
  • 同类型的对象,执行同一个行为,会表现出不同的行为特征。(例如:猫和狗都是动物类型,执行的行为不一样,猫跑的行为是快,狗跑的行为是非常快。)
Ⅱ.多态的常见形式
父类类型 对象名称 = new 子类构造器;
// 接口也是一种父类——干爹
接口 对象名称 = new 实现类构造器;

Animal.java

/**
 * 动物类:抽象类
 */
public abstract class Animal {

    /**
     * 跑:抽象方法
     */
    public abstract  void run();
}

Dog.java

public class Dog extends Animal {

    /**
     * 跑
     */
    @Override
    public void run() {
        System.out.println("狗跑的很快!");
    }
}

Turtle.java

public class Turtle extends Animal{

    /**
     * 跑
     */
    @Override
    public void run() {
        System.out.println("乌龟跑的很慢!");
    }
}

Test.java

public class Test {
    public static void main(String[] args) {
        // 多态的形式
        // 父类类型 对象名称 = new 子类构造器;
        // Dog dog = new Dog();
        Animal dog = new Dog();
        dog.run();

        // Turtle turtle = new Turtle();
        Animal turtle = new Turtle();
        turtle.run();
    }
}

Ⅲ.多态中成员访问特点
  • 方法调用:编译(编写代码)看左边,运行看右边。
  • 变量调用:编译(编写代码)看左边,运行也看左边。(多态侧重行为多态/方法多态)

Animal.java

/**
 * 动物类:抽象类
 */
public abstract class Animal {
    public String name = "父类动物";

    /**
     * 跑:抽象方法
     */
    public abstract  void run();
}

Dog.java

public class Dog extends Animal {

    public String name = "子类狗";

    /**
     * 跑
     */
    @Override
    public void run() {
        System.out.println("狗跑的很快!");
    }
}

Turtle.java

public class Turtle extends Animal{

    public String name = "子类乌龟";

    /**
     * 跑
     */
    @Override
    public void run() {
        System.out.println("乌龟跑的很慢!");
    }
}

Test.java

public class Test {
    public static void main(String[] args) {
        // 多态的形式
        // 父类类型 对象名称 = new 子类构造器;
        // Dog dog = new Dog();
        Animal dog = new Dog();
        // 方法调用:编译(编写代码)看左边,运行看右边。
        dog.run();
        // 变量调用:编译(编写代码)看左边,运行也看左边。(多态侧重行为多态/方法多态)
        System.out.println(dog.name);

        // Turtle turtle = new Turtle();
        Animal turtle = new Turtle();
        // 方法调用:编译(编写代码)看左边,运行看右边。
        turtle.run();
        // 变量调用:编译(编写代码)看左边,运行也看左边。(多态侧重行为多态/方法多态)
        System.out.println(turtle.name);
    }
}

Ⅳ.多态的前提
  • 有继承/实现关系;有父类引用指向子类对象;有方法重写。

b.多态的好处

优势:

  • 在多态形式下,右边对象可以实现解耦合,便于扩展和维护。

    Animal animal = new Dog();
    animal.run();
    
    /**
    * 比赛:所有动物可以参加比赛
    */
    public static void competition(Animal animal) {
        System.out.println("比赛开始!");
        animal.run();
        System.out.println("比赛结束!");
    }
    
  • 定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利。

多态下会产生的一个问题:

  • 多态下不能使用子类的独有功能。

    Dog.java

    public class Dog extends Animal {
    
        public String name = "子类狗";
    
        /**
         * 跑
         */
        @Override
        public void run() {
            System.out.println("狗跑的很快!");
        }
    
        /**
         * 看门:狗类的独有功能
         */
        public void lookDoor() {
            System.out.println("狗在看门!");
        }
    }
    
    

    Test.java

    public class Test {
        public static void main(String[] args) {
    
            Animal dog = new Dog();
            dog.run();
            
            // 多态下不能使用子类的独有功能
            // dog.lookDoor();
    
        }
    }
    
    

c.多态下引用数据类型的类型转换

自动类型转换(从子到父)

  • 子类对象赋值给父类类型的变量指向。

强制类型转换(从父到子)

  • 此时必须进行强制类型转换:子类 对象变量 = (子类)父类类型的变量。

  • 作用:可以解决多态下的劣势,可以实现调用子类独有的功能。

    /**
     * 目标:学习多态形式下的类中转换机制
     */
    public class Test {
        public static void main(String[] args) {
            // 自动类型转换
            Animal animal = new Dog();
            animal.run();
    
            // 强制类型转换
            Animal animal1 = new Turtle();
            animal1.run();
            
            // 从父类类型到子类类型 必须强制类型转换
            Turtle turtle = (Turtle) animal1;
            // 子类的独有功能
            turtle.layEggs();
        }
    }
    
    
  • 注意:如果转型后的类型和对象真实类型不是同一种类型,那么在转换的时候就会出现ClassCastException

    /**
     * 目标:学习多态形式下的类中转换机制
     */
    public class Test {
        public static void main(String[] args) {
            // 自动类型转换
            Animal animal = new Dog();
            animal.run();
    
            // 强制类型转换
            Animal animal1 = new Turtle();
            animal1.run();
            
            // 从父类类型到子类类型 必须强制类型转换
            Turtle turtle = (Turtle) animal1;
            // 子类的独有功能
            turtle.layEggs();
    
            // 转型后的类型和对象真实类型不是同一种类型
            // 强制类型转换 编译阶段不报错(注意:有继承或者实现关系编译阶段可以强制)但是运行可能出错
            Dog dog = (Dog) animal1;
        }
    }
    
    

    Java建议强制转换前,使用instanceof判断当前对象的真实类型,再进行强制转换。

    /**
     * 目标:学习多态形式下的类中转换机制
     */
    public class Test1 {
        public static void main(String[] args) {
            // 自动类型转换
            Animal animal = new Dog();
            animal.run();
    
            // 强制类型转换
            Animal animal1 = new Turtle();
            animal1.run();
    
           	// 变量名 instanceof 真实类型
    		// 判断关键字左边的变量指向的对象的真实类型,是否是右边的类型或者其子类类型,是则返回true,反之返回false。
            if (animal1 instanceof Turtle) {
                Turtle turtle = (Turtle) animal1;
                // 子类的独有功能
                turtle.layEggs();
            } else if (animal1 instanceof Dog) {
                Dog dog = (Dog) animal1;
                // 子类的独有功能
                dog.lookDoor();
            }
            
            competition(new Dog());
            competition(new Turtle());
        }
        
        /**
         * 比赛:所有动物可以参加比赛
         */
        public static void competition(Animal animal) {
            // 通用功能
            animal.run();
            
            if (animal instanceof Turtle) {
                Turtle turtle = (Turtle) animal;
                // 子类的独有功能
                turtle.layEggs();
            } else if (animal instanceof Dog) {
                Dog dog = (Dog) animal;
                // 子类的独有功能
                dog.lookDoor();
            }
        }
    }
    
    

总结:

  1. 引用数据类型的类型转换,有几种方式?
    • 自动类型转换、强制类型转换。
  2. 强制类型转换能解决什么问题?强制类型转换需要注意什么?
    • 可以转成真正的子类类型,从而调用子类独有功能。
    • 有继承关系/实现关系的2个类型就可以进行强制转换,编译无问题。
    • 运行时,如果强制转换后的类型不是对象真实类型则报错。

d.多态的综合案例

需求:

  • 使用面向对象编程模拟:设计一个电脑对象,可以安装2个USB设备
  • 鼠标:被安装时可以完成接入、调用点击功能、拔出功能。
  • 键盘:被安装时可以完成接入、调用打字功能、拔出功能。

分析:

  • 定义一个USB的接口(申明USB设备的规范必须是:可以接入和拔出)。
  • 提供2个USB实现类代表鼠标和键盘,让其实现USB接口,并分别定义独有功能。
  • 创建电脑对象,创建2个USB实现类对象,分别安装到电脑中并触发功能的执行。

USB.java

/**
 * USB接口
 */
public interface USB {

    /**
     * 接入USB设备
     */
    void connect();

    /**
     * 断开USB设备
     */
    void disconnect();

}

Keyboard.java

/**
 * 键盘类
 */
public class Keyboard implements USB{

    // 品牌
    private String brand;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public Keyboard() {
    }

    public Keyboard(String brand) {
        this.brand = brand;
    }

    /**
     * 接入USB设备
     */
    @Override
    public void connect() {
        System.out.println(brand + "成功连接电脑!");
    }

    /**
     * 断开USB设备
     */
    @Override
    public void disconnect() {
        System.out.println(brand + "成功断开电脑!");
    }

    /**
     * 敲键盘
     */
    public void pressKeyboard() {
        System.out.println("敲键盘!");
    }

}

Mouse.java

/**
 * 鼠标类
 */
public class Mouse implements USB{

    // 品牌
    private String brand;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public Mouse() {
    }

    public Mouse(String brand) {
        this.brand = brand;
    }

    /**
     * 接入USB设备
     */
    @Override
    public void connect() {
        System.out.println(brand + "成功连接电脑!");
    }

    /**
     * 断开USB设备
     */
    @Override
    public void disconnect() {
        System.out.println(brand + "成功断开电脑!");
    }

    /**
     * 点击鼠标
     */
    public void clickMouse() {
        System.out.println("点击鼠标!");
    }
}

Computer.java

public class Computer {
    // 品牌
    private String brand;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public Computer() {
    }

    public Computer(String brand) {
        this.brand = brand;
    }

    /**
     * 电脑开机
     */
    public void start() {
        System.out.println(brand + "电脑开机!");
    }

    /**
     * 安装USB设备
     */
    public void mountUSBDevice(USB usb) {
        // 接入USB设备
        usb.connect();
        
        if (usb instanceof Keyboard) {

            // 强制转换成键盘类
            Keyboard keyboard = (Keyboard) usb;
            // 敲键盘
            keyboard.pressKeyboard();

        } else if (usb instanceof Mouse) {

            // 强制转换成鼠标类
            Mouse mouse = (Mouse) usb;
            // 点击鼠标
            mouse.clickMouse();

        }

        // 断开USB设备
        usb.disconnect();
    }
}

Test.java

public class Test {
    public static void main(String[] args) {

        // 创建电脑对象
        Computer computer = new Computer("苹果");
        // 开机
        computer.start();

        // 创建键盘对象
        USB keyboard = new Keyboard("罗技");
        // 创建鼠标对象
        USB mouse = new Mouse("雷蛇");

        // 安装键盘
        computer.mountUSBDevice(keyboard);
        // 安装鼠标
        computer.mountUSBDevice(mouse);
    }
}

2.内部类

a.内部类概述

内部类:

  • 内部类就是定义在一个类里面的类,里面的类可以理解成(寄生),外部类可以理解成(宿主)。

    public class People {
        
        // 内部类
    	public class Heart {
    	
    	}
    }
    

内部类的使用场景、作用:

  • 当一个事务的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事务提供服务,那么整个内部的完整结构可以选择使用内部类来设计。
  • 内部类通常可以方便访问外部类的成员,包括私有的成员。
  • 内部类提供了更好的封装性,内部类本身就可以用private protected等修饰,封装性可以做更多控制。

内部类的分类:

  • 静态内部类【了解】
  • 成员内部类/非静态内部类【了解】
  • 局部内部类【了解】
  • 匿名内部类【重点】

b.内部类之一:静态内部类

什么是静态内部类?

  • 有static修饰,属于外部类本身。
  • 它的特点和使用与普通类是完全一样的,类有的成分它都有,只是位置在别人里面而已。
public class Outer {
    
	// 静态成员内部类
	public static class Inner {
	
	}
}

静态内部类创建对象的格式:

格式:
外部类名.内部类名 对象名 = new 外部类名.内部类构造器;

例:
Outer.Inner inner = new Outer.Inner();

Outer.java

/**
 * 外部类
 */
public class Outer {

    /**
     * 静态成员内部类
     */
    public static class Inner {
        private String name;
        private int age;
        public static String schoolName;

        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 static String getSchoolName() {
            return schoolName;
        }

        public static void setSchoolName(String schoolName) {
            Inner.schoolName = schoolName;
        }

        public Inner() {
        }

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

        public void show() {
            System.out.println(name);
        }
    }
}

Test.java

public class Test {
    public static void main(String[] args) {
        Outer.Inner inner = new Outer.Inner();
        inner.setName("花千骨");
        inner.show();
    }
}

静态内部类的访问拓展:

  1. 静态内部类中是否可以直接访问外部类的静态成员?
    • 可以,外部类的静态成员只有一份,可以被共享访问。
  2. 静态内部类中是否可以直接访问外部类的实例成员?
    • 不可以,外部类的实例成员必须用外部类对象访问。

总结:

  1. 静态内部类的使用场景、特点、访问总结。
    • 如果一个类中包含了一个完整的成分,如汽车类中的发动机类。
    • 特点、使用与普通类是一样的,类有的成分它都有,,只是位置在别人里面而已。
    • 可以直接访问外部类的静态成员,不能直接访问外部类的实例成员。
    • 注意:开发中实际上用的还是比较少。

c.内部类之二:成员内部类

什么是成员内部类?

  • 无static修饰,属于外部类的对象。
  • JDK16之前,成员内部类中不能定义静态成员,JDK16开始也可以定义静态成员了。
public class Outer {
    
    // 成员内部类
	public class Inner {
	
	}
}

成员内部类创建对象的格式:

格式: 
外部类.内部类 对象名 = new 外部类构造器.new 内部类构造器();:
Outer.Inner in = new Outer().new Inner();

Outer.java

/**
 * 外部类
 */
public class Outer {

    /**
     * 成员内部类:不能加static修饰
     */
    public class Inner {
        private String name;
        private int age;
        // JDK16开始支持静态成员
        public static int demo;

        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 static int getDemo() {
            return demo;
        }

        public static void setDemo(int demo) {
            Inner.demo = demo;
        }

        public Inner() {
        }

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

        /**
         * 静态方法
         * 从JDK16开始支持
         */
        public static void test() {

        }

        /**
         * 普通实例方法
         */
        public void show() {
            System.out.println("姓名:" + name + "\t年龄:" + age);
        }
    }
}

Test.java

public class Test {
    public static void main(String[] args) {
        // 创建内部类对象
        Outer.Inner inner = new Outer().new Inner();
        inner.setName("花千骨");
        inner.setAge(20);
        // 内部类实例方法
        inner.show();
        // 内部类静态方法
        Outer.Inner.test();
    }
}

成员内部类的访问拓展:

  1. 成员内部类中是否可以直接访问外部类的静态成员?
    • 可以,外部类的静态成员只有一份,可以被共享访问。
  2. 成员内部类的实例方法中是否可以直接访问外部类的实例成员?
    • 可以,因为必须先有外部类对象,才能有成员内部类对象,所以可以直接访问外部类对象的实例成员。

总结:

  1. 成员内部类是什么样的、有什么特点?
    • 无static修饰,属于外部类的对象。
    • 可以直接访问外部类的静态成员,实例方法中可以直接访问外部类的实例成员。
  2. 成员内部类如何创建对象?
    • 外部类名.内部类名 对象名 = new 外部类构造器.new 内部类构造器();

案例:成员内部类

  • 请观察如下代码,写出合适的代码对应其注释要求输出的结果。

    class People {
        private int heartbeat = 150;
        
        public class Heart {
            private int heartbeat = 110;
            
            public void show() {
                int heartbeat = 78;
                
                // 78
                System.out.println(?);
                // 110
                System.out.println(?);
                // 150
                System.out.println(?);
            }
        }
    }
    

    注意:在成员内部类中访问所在外部类对象,格式:外部类名.this。

Test.java

public class Test2 {

    public static void main(String[] args) {
        People.Heart heart = new People().new Heart();
        heart.show();
    }
}


class People {

    private int heartbeat = 150;

    /**
     * 成员内部类
     */
    public class Heart {
        private int heartbeat = 110;

        public void show() {
            // 局部变量
            int heartbeat = 78;

            // 78 局部变量
            System.out.println(heartbeat);
            // 110 成员内部类成员变量
            System.out.println(this.heartbeat);
            // 150
            System.out.println(People.this.heartbeat);
        }
    }
}

d.内部类之三:局部内部类

局部内部类(鸡肋语法,了解即可)

  • 局部内部类放在方法、代码块、构造器等执行体中。
  • 局部内部类的类文件名为:外部类$N内部类.class。

Test.java

public class Test {
    public static void main(String[] args) {

        /**
         * 局部内部类
         */
        class Cat {
            private String name;
            public static int onlineNumber = 100;

            public String getName() {
                return name;
            }

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

        Cat cat = new Cat();
        cat.setName("小花");
        System.out.println(cat.getName());
    }
}

e.内部类之四:匿名内部类概述★

匿名内部类:

  • 本质上是一个没有名字的局部内部类,定义在方法中、代码块中等。
  • 作用:方便创建子类对象,最终目的为了简化代码编写。

匿名内部类格式:

new|抽象类名|接口名() {
	重写方法;
};

Employee a = new Employee() {
    public void work() {
        
    }
};

a.work();

Test.java

public class Test {
    public static void main(String[] args) {
//        Animal tiger = new Tiger();
//        tiger.run();
        
        // 匿名内部类 不需要再定义子类去实现抽象方法
        Animal tiger = new Animal() {
            @Override
            public void run() {
                System.out.println("老虎跑得快!");
            }
        };

        tiger.run();
    }
}

abstract class Animal {
    public abstract void run();
}

//class Tiger extends Animal {
//
//    @Override
//    public void run() {
//        System.out.println("老虎跑得快!");
//    }
//}

特点总结:

  • 匿名内部类是一个没有名字的内部类。
  • 匿名内部类写出来就会产生一个匿名内部类的对象。
  • 匿名内部类的对象类型相当于是当前new的那个类型的子类类型。

总结:

  1. 匿名内部类的作用?

    • 方便创建子类对象,最终目的是为了简化代码编写。
  2. 匿名内部类的格式?

    Employee a = new Employee() {
        public void work() {
            
        }
    };
    
    a.work();
    
  3. 匿名内部类的特点?

    • 匿名内部类是一个没有名字的内部类。
    • 匿名内部类写出来就会产生一个匿名内部类的对象。
    • 匿名内部类的对象类型相当于是当前new的那个类型的子类类型。

f.匿名内部类常见使用形式

匿名内部类在开发中的使用形式了解:

  • 某个学校需要让老师、学生、运动员一起参加游泳比赛

    /**
     * 游泳接口
     */
    public interface Swimming {
        void swim();
    }
    
    
    /**
     * 测试类
     */
    public class JumppingDemo {
        public static void main(String[] args) {
            // 需求:goSwimming方法
        }
        
        // 定义一个方法让所有角色进来一起比赛
        public static void goSwimming(Swimming swimming) {
            swimming.swim();
        }
    }
    

Test.java

/**
 * 目标:掌握匿名内部类的使用形式(语法)
 */
public class Test {
    public static void main(String[] args) {
//        Swimming student = new Student();
//        go(student);

        // 学生参加游泳比赛
        Swimming student = new Swimming() {
            @Override
            public void swim() {
                System.out.println("学生自由泳!");
            }
        };
        go(student);

        // 老师参加游泳比赛
        Swimming teacher = new Swimming() {
            @Override
            public void swim() {
                System.out.println("老师蛙泳!");
            }
        };
        go(teacher);

        // 运动员参加游泳比赛
        // 匿名内部类作为实参
        go(new Swimming() {
            @Override
            public void swim() {
                System.out.println("运动员蝶泳!");
            }
        });

    }

    /**
     * 学生 老师 运动员可以一起参加游泳比赛
     */
    public static void go(Swimming swimming) {
        System.out.println("开始游泳!");
        swimming.swim();
        System.out.println("结束游泳!");
    }
}

/**
 * 游泳接口
 */
interface Swimming {
    void swim();
}

//class Student implements Swimming {
//
//    @Override
//    public void swim() {
//        System.out.println("学生自由泳!");
//    }
//}

使用总结:匿名内部类可以作为方法的实际参数进行传输。

g.匿名内部类真实使用场景演示

匿名内部类真实使用场景:

  • 给按钮绑定点击事件。

Test.java

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * 目标:通过GUI变成 理解匿名内部类的真实使用场景
 */
public class Test {
    public static void main(String[] args) {
        // 1.创建窗口
        JFrame jFrame = new JFrame("登录界面");
        JPanel jPanel = new JPanel();
        jFrame.add(jPanel);

        // 2.创建一个按钮对象
        JButton jButton = new JButton("登录");
        
        // 匿名内部类的使用 绑定点击事件
        jButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                JOptionPane.showMessageDialog(jFrame, "登录成功!");
            }
        });
        
        // 匿名内部类简化
        jButton.addActionListener(e -> JOptionPane.showMessageDialog(jFrame, "登录成功!"));

        // 3.把按钮对象添加到窗口上展示
        jFrame.add(jButton);

        // 4.展示窗口
        // 设置窗口大小
        jFrame.setSize(400, 300);
        // 设置居中显示
        jFrame.setLocationRelativeTo(null);
        jFrame.setVisible(true);
    }
}

使用总结:

  • 开发中不是我们主动去定义匿名内部类的,而不是别人需要我们写或者我们可以写的时候才会使用。匿名内部类的的代码可以实现代码进一步的简化。

3.常用API

什么是API?

  • API(Application Programming Interface) 应用程序编程接口。
  • 简单来说:就是Java帮我们写好的一些方法,我们可以直接调用。

a.Object

Object类的作用:

  • 一个类要么默认继承了Object类,要么间接继承了Object类,Object类是Java中的祖宗类。
  • Object类的方法是一切子类都可以直接使用的,所以我们要学习Object类的方法。

Object类的常用方法:

方法名说明
public String toString()默认是返回当前对象在堆内存中的地址信息:类的全限名@内存地址 。
public Boolean equals(Object o)默认是比较当前对象与另一个对象的地址是否相同,相同返回true,不同返回false。
Ⅰ.toString()方法
方法名说明
public String toString()默认是返回当前对象在堆内存中的地址信息:类的全限名@内存地址 。

Student.java

public class Student {

    private String name;
    private char sex;
    private int age;

    public String getName() {
        return name;
    }

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

    public char getSex() {
        return sex;
    }

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

    public int getAge() {
        return age;
    }

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

    public Student() {
    }

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

Test.java

/**
 * 目标:掌握Object类中toString方法的使用。
 */
public class Test {
    public static void main(String[] args) {
        Student student = new Student("白子画", '男', 1000);

        String s = student.toString();
        
        System.out.println(s);
        System.out.println(student.toString());
        // 直接输出对象变量 默认可以省略toString调用不写
        System.out.println(student);
    }
}

问题引出:

  • 开发中直接输出对象,默认输出对象的地址其实是无意义的。
  • 开发中输出对象变量,更多的时候是希望看到对象的内容数据而不是对象的地址信息。

toString存在的意义:

  • 父类toString()方法存在的意义就是为了被子类重写,以便返回对象的内容信息,而不是地址信息。

    package com.javase.apiobject;
    
    public class Student {
    
        private String name;
        private char sex;
        private int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public char getSex() {
            return sex;
        }
    
        public void setSex(char sex) {
            this.sex = sex;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public Student() {
        }
    
        public Student(String name, char sex, int age) {
            this.name = name;
            this.sex = sex;
            this.age = age;
        }
    
        /**
         * 重写父类String
         *
         * @return 对象信息
         */
        @Override
        public String toString() {
            return "Student {name = " + name + ", sex=" + sex + ", age = " + age + "}";
        }
    }
    
    

    IDEA中重写toString()快捷方式:

    • 鼠标右键 -> Generate -> toString()
    • 输入toS -> public String toString() (generate via wizard) -> OK

总结:

  1. Object的toString方法的作用是什么》
    • 默认是打印当前对象的地址。
    • 让子类重写,以便返回子类对象的内容。
Ⅱ.equals()方法
方法名说明
public Boolean equals(Object o)默认是比较当前对象与另一个对象的地址是否相同,相同返回true,不同返回false。

问题思考:

  • 直接比较两个对象的地址是否相同完全可以用 ==替代equals。

equals存在的意义:

  • 父类equals方法存在的意义就是为了被子类重写,以便子类自己来定制比较规则。
package com.javase.apiobject;

import java.util.Objects;
import java.util.stream.Stream;

public class Student {

    private String name;
    private char sex;
    private int age;

    public String getName() {
        return name;
    }

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

    public char getSex() {
        return sex;
    }

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

    public int getAge() {
        return age;
    }

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

    public Student() {
    }

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

    /**
     * 重写父类String
     *
     * @return 对象信息
     */
    @Override
    public String toString() {
        return "Student {name = " + name + ", sex=" + sex + ", age = " + age + "}";
    }

    /**
     * 重写equals 定制相等规则
     * 两个对象的内容一样就认为是相等的
     *
     * @param o 被比较者
     * @return
     */
    @Override
    public boolean equals(Object o) {
        // 判断o是不是学生类型
        if (o instanceof Student) {
            // o强制转化为学生类型
            Student student2 = (Student) o;

            // 判断2个对象的内容是否一样 
//            if (this.name.equals(student2.name) && this.sex == student2.sex && this.age == student2.age) {
//                return true;
//            } else {
//                return false;
//            }
            return this.name.equals(student2.name) && this.sex == student2.sex && this.age == student2.age;
            
        } else {
            // 学生只能和学生比较 否则结果一定是false
            return false;
        }
    }
}

IDEA中重写equal()快捷方式:

  • 鼠标右键 -> Generate -> equals() and hashCode()
  • 输入eq -> public boolean equals(Object obj) (generate via wizard) -> OK

总结:

  1. Object的equals方法的作用是什么?
    • 默认是与另一个对象比较地址是否一样。
    • 让子类重写,以便比较两个子类对象的内容是否相同

b.Objects

Objects概述:

  • Objects类与Object还是继承关系,Objects类是从JDK1.7开始之后才有的。

官方在进行字符串比较时,没有对象自己的equals方法,而是选择了Objects的equals方法来比较两个对象。

    @Override
    public boolean equals(Object o) {
        // 1.判断是否是同一个对象 如果是返回true
        if (this == o) return true;
        // 2.如果o是null或o不是学生类型返回false
        if (o == null || getClass() != o.getClass()) return false;
        // 3.说明o一定是学生类型而且不为null
        Student student = (Student) o;
        // Objects.equals进行了非空校验更加安全
        return sex == student.sex && age == student.age && Objects.equals(name, student.name);
    }

Objects的equals方法比较的结果是一样的,但是更安全。

Objects的常见方法:

方法名说明
public static boolean equals(Object a, Object b)比较两个对象,底层会先进行非空判断,从而可以避免空指针异常。再进行equals比较。
public static boolean isNull(Object obj)判断变量是否为null,为null返回true,反之返回false。
Ⅰ.equals()方法
方法名说明
public static boolean equals(Object a, Object b)比较两个对象,底层会先进行非空判断,从而可以避免空指针异常。再进行equals比较。
    // Objects.equals(Object a, Object b) 源码
    public static boolean equals(Object a, Object b) {
        return a == b || a != null && a.equals(b);
    }

Test.java

import java.util.Objects;

/**
 * 目标:掌握Objects类的常用方法 重点:equals
 */
public class Test {
    public static void main(String[] args) {
        String string1 = null;
        String string2 = new String("花千骨");

        // 留下来隐患 可能出现空指针异常
        // Cannot invoke "String.equals(Object)" because "string1" is null
        //System.out.println(string1.equals(string2));

        // 更安全 结果也是对的
        // false
        System.out.println(Objects.equals(string1, string2));
        // ture
        System.out.println(Objects.equals(null, null));

    }

    // Objects.equals(Object a, Object b) 源码
//    public static boolean equals(Object a, Object b) {
//        return a == b || a != null && a.equals(b);
//    }
}

Ⅱ.isNull()方法
方法名说明
public static boolean isNull(Object obj)判断变量是否为null,为null返回true,反之返回false。
    // 源码
    public static boolean isNull(Object obj) {
        return obj == null;
    }

Test.java

import java.util.Objects;

/**
 * 目标:掌握Objects类的常用方法 重点:equals
 */
public class Test {
    public static void main(String[] args) {
        String string1 = null;
        String string2 = new String("花千骨");

        // true
        System.out.println(Objects.isNull(string1));
        // false
        System.out.println(Objects.isNull(string2));

    }
}

总结:

  1. 对象进行内容比较的时候建议使用什么?为什么?
    • 建议使用Objects提供的equals方法。
    • 比较结果是一样的,但是更安全。

c.StringBuilder

StringBuilder概述:

  • StringBuilder是一个可变的字符串类,我们可以把它看成是一个对象容器。
  • 作用:提高字符串的操作效率,如拼接、修改等。

StringBuilder构造器:

名称说明
public StringBuilder()创建一个空白的可变的字符串对象,不包含任何内容。
public StringBuilder(String str)创建一个指定字符串内容的可变字符串对象。

StringBuilder常用方法:

方法名称说明
public StringBuilder append(任意类型)添加数据并返回StringBuilder对象本身。
public StringBuilder reverse()将对象的内容反转。
public int length()返回对象内容长度。
public String toString()通过toString()就可以实现把StringBuilder转化为String。

Test.java

package com.javase.apistringbuilder;

/**
 * 目标:学会使用StringBuilder操作字符串 最终还需要知道它性能好的原因
 */
public class Test {
    public static void main(String[] args) {

        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("a");
        stringBuilder.append(1);
        stringBuilder.append(1.1);
        stringBuilder.append(true);
        stringBuilder.append("abc");
        System.out.println(stringBuilder);

        StringBuilder stringBuilder1 = new StringBuilder();
        // 支持链式编程
        stringBuilder1.append("a").append("b").append("c");
        System.out.println(stringBuilder1);

        // 反转再拼接
        stringBuilder1.reverse().append("111");
        System.out.println(stringBuilder1);

        // 注意:StringBuild只是拼接字符串的手段:效率好。
        // 最终的结果还是要恢复成String类型
        StringBuilder stringBuilder2 = new StringBuilder();
        stringBuilder2.append("123").append("456");
        // 恢复成String
        String rs = stringBuilder2.toString();
        System.out.println(rs);
        
        int a = 1;
        System.out.println();
    }
}

Ⅰ.String类拼接字符串原理图

String类拼接字符串原理图

Ⅱ.StringBuilder提高效率原理图

在这里插入图片描述

总结:

  1. 为什么拼接、反转字符串建议使用StringBuilder?
    • String:内容是不可变的、拼接字符串性能差。
    • StringBuilder:内容是可变的、拼接字符串性能好、代码优雅。
    • 定义字符串使用String。
    • 拼接、修改等操作字符串使用StringBuilder。
Ⅲ.案例:打印整型数组内容

需求:

  • 设计一个方法用于输出任意整型数组的内容,要求输出成如下格式:“该数组内容为:[11, 22 ,33 ,44, 55]”

分析:

  • 定义一个方法,要求该方法能够接收数组,并输出数组内容。(需要参数吗?需要返回值类型申明吗?)
  • 定义一个静态初始化的数组,调用该方法,并传入该数组。

Test.java

public class Test {
    public static void main(String[] args) {

        int[] array1 = null;
        System.out.println(toString(array1));

        int[] array2 = {11, 22, 33, 44, 55};
        System.out.println(toString(array2));

        int[] array3 = {};
        System.out.println(toString(array3));

    }

    public static String toString(int[] array) {

        if (array != null) {
            StringBuilder stringBuilder = new StringBuilder("[");

            for (int i = 0; i < array.length; i++) {
                stringBuilder.append(array[i]).append(i == array.length - 1 ? "" : ", ");
            }

            stringBuilder.append("]");

            return stringBuilder.toString();

        } else {
            return null;
        }

    }
}

d.Math

Math类:

  • 包含执行基本数字运算的方法,Math类没有提供公开的构造器。
  • 如何使用类中的成员呢?看类的成员是否都是静态的,如果是,通过类名就可以直接调用。

Math类的常用方法:

方法名说明
public static int abs(int a)获取参数绝对值
public static double ceil(double a)向上取整
public static double floor(double a)向下取整
public static int round(float a)四舍五入
public static int max(int a, int b)获取两个int值中的较大值
public static double pow(double a, double b)返回a的b次幂的值
public static double random()返回值为double的随机值,范围[0.0, 1.0)

Test.java

public class Test {
    public static void main(String[] args) {
        
        // 获取参数绝对值 10 10
        System.out.println(Math.abs(10));
        System.out.println(Math.abs(-10));

        // 向上取整 5.0
        System.out.println(Math.ceil(4.0000001));

        // 向下取整 4.0
        System.out.println(Math.floor(4.999999));

        // 四舍五入 4 4
        System.out.println(Math.round(4.499999));
        System.out.println(Math.round(4.5000001));

        // 获取两个int值中的较大值 100
        System.out.println(Math.max(10, 100));

        // 返回a的b次幂的值 8
        System.out.println(Math.pow(2, 3));

        // 返回值为double的随机值,范围[0.0, 1.0)
        System.out.println(Math.random());
    }
}

e.System

System类概述:

  • System的功能是通用的,都是直接用类名调用即可,所以System不能被实例化。

System类的常用方法:

方法名说明
public static void exit(int status)终止当前运行的Java虚拟机,非零表示异常终止。
public static long currentTimeMillis(),返回当前系统的时间毫秒值形式。
public static void arraycopy(数据源数组, 起始索引, 目的地数组, 起始索引, 拷贝个数)数组拷贝。

时间毫秒值:

  • 计算机认为时间是有起点的,起始时间:1970年1月1日 00:00:00
  • 时间毫秒值:指的是从1970年1月1日 00:00:00走到此刻的总的毫秒数,应该是很大的。1s = 1000ms。

原因:

  • 1969年8月,贝尔实验室的程序员肯汤普逊利用妻儿离开的一个月的机会,开始着手创造一个全新的革命性的操作系统,他使用B编程语言在老旧的PDP-7机器上开发出了Unix的一个版本。随后,汤普逊和同事丹尼斯里奇改进了B语言,开发出了C语言,重写了UNIX。
  • 1970年1月1日 算C语言的生日。

Test.java

import java.util.Arrays;

public class Test {
    public static void main(String[] args) {
        System.out.println("程序开始...");

        // JVM终止
        // System.exit(0);

        // 计算机认为时间有起源:返回1978-01-01 00:00:00 走到此刻的总的时间毫秒值
        long time = System.currentTimeMillis();
        System.out.println(time);

        // 进行时间的计算 进行性能分析
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            System.out.println("输出:" + i);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("用时:" + (endTime - startTime) / 10000.0 + "s");

        // 数组拷贝 arraycopy(数据源数组, 起始索引, 目的地数组, 起始索引, 拷贝个数)
        int[] array1 = {10, 20, 30, 40, 50, 60};
        // [0, 0, 0, 0, 0, 0] 变为 [0, 0, 40, 50, 60, 0]
        int[] array2 = new int[6];
        System.arraycopy(array1, 3, array2, 3, 2);
        // 把数组以字符串形式输出
        System.out.println(Arrays.toString(array2));

        System.out.println("程序结束!");

    }
}

f.BigDecimal

BigDecimal作用:

  • 用于解决浮点型运算精度失真的问题
public class Test {
    public static void main(String[] args) {
        System.out.println(0.09 + 0.01);  // 0.09999999999999999
        System.out.println(1.0 - 0.32);  // 0.6799999999999999
        System.out.println(1.015 * 100);  // 101.49999999999999
        System.out.println(1.301 / 100);  // 0.013009999999999999
        
        double c = 0.1 + 0.2;  // 0.30000000000000004
        System.out.println(c);
    }
}

使用步骤:

  • 创建对象BigDecimal封装浮点型数据(最好的方法是调用方法)

    // 包装浮点数成BigDecimal对象
    public static BigDecimal valueOf(double val) {
        
    }
    
方法名说明
public BigDecimal add(BigDecimal b)加法
public BigDecimal subtract(BigDecimal b)减法
public BigDecimal multiply(BigDecimal b)乘法
public BigDecimal divide(BigDecimal b)除法
public BigDecimal divide(另一个BigDecimal, 精确几位, 舍入模式)除法

禁止使用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象。

  • 说明:BigDecimal(double)存在精度损失风险,在精度计算或值比较的场景中可能会导致业务逻辑异常。

    // 例如:实际的存储值为0.100000001490116119384765625
    BigDecimal g = new BigDecimal(0.1F);
    
  • 正例:优先推荐入参为String的构造方法,或使用BigDecimal的valueOf方法,此方法内部其实执行了Double的toString,而Double的toString按double的实际能表达的精度堆尾数进行了截断。

    // 正确方法
    BigDecimal recommend1 = new BigDecimal("0.1");
    BigDecimal recommend2 = BigDecimal.valueOf(0.1);
    

Test.java

import java.math.BigDecimal;
import java.math.RoundingMode;

public class Test {
    public static void main(String[] args) {

        // 浮点型运算的时候 可能会出现数据失真
        System.out.println("--------------------");
        System.out.println(0.09 + 0.01);  // 0.09999999999999999
        System.out.println(1.0 - 0.32);  // 0.6799999999999999
        System.out.println(1.015 * 100);  // 101.49999999999999
        System.out.println(1.301 / 100);  // 0.013009999999999999
        double sum = 0.1 + 0.2;  // 0.30000000000000004
        System.out.println(sum);
        System.out.println("--------------------");

        // 包装浮点型数据成为大数据对象BigDecimal
        System.out.println("--------------------");
        double a = 0.1;
        double b = 0.2;
        double c = a + b;
        System.out.println(c);
        System.out.println("--------------------");

        System.out.println("--------------------");
        // 加法
        BigDecimal a1 = BigDecimal.valueOf(a);
        BigDecimal b1 = BigDecimal.valueOf(b);
        BigDecimal cAdd = a1.add(b1);
        System.out.println(cAdd);
        // 减法
        BigDecimal cSub = a1.subtract(b1);
        System.out.println(cSub);
        // 乘法
        BigDecimal cMul = a1.multiply(b1);
        System.out.println(cMul);
        // 除法
        BigDecimal cDiv = a1.divide(b1);
        System.out.println(cDiv);

        // 运用BigDecimal计算是手段 目的是结果
        double rs = cDiv.doubleValue();
        System.out.println(rs);

        // 注意事项:BigDecimal是一定要精度运算
        BigDecimal a2 = BigDecimal.valueOf(10.0);
        BigDecimal b2 = BigDecimal.valueOf(3.0);
        BigDecimal c2 = a2.divide(b2, 2, RoundingMode.HALF_UP);
        System.out.println(c2);

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

    }
}

总结:

  1. BigDecimal的作用是什么?
    • 解决浮点型运算精度失真问题。
  2. BigDecimal的对象如何获取?
    • BigDecimal b1 = BigDecimal.valueOf(0.1);
// 正确方法
BigDecimal recommend1 = new BigDecimal("0.1");
BigDecimal recommend2 = BigDecimal.valueOf(0.1);

Test.java

import java.math.BigDecimal;
import java.math.RoundingMode;

public class Test {
    public static void main(String[] args) {

        // 浮点型运算的时候 可能会出现数据失真
        System.out.println("--------------------");
        System.out.println(0.09 + 0.01);  // 0.09999999999999999
        System.out.println(1.0 - 0.32);  // 0.6799999999999999
        System.out.println(1.015 * 100);  // 101.49999999999999
        System.out.println(1.301 / 100);  // 0.013009999999999999
        double sum = 0.1 + 0.2;  // 0.30000000000000004
        System.out.println(sum);
        System.out.println("--------------------");

        // 包装浮点型数据成为大数据对象BigDecimal
        System.out.println("--------------------");
        double a = 0.1;
        double b = 0.2;
        double c = a + b;
        System.out.println(c);
        System.out.println("--------------------");

        System.out.println("--------------------");
        // 加法
        BigDecimal a1 = BigDecimal.valueOf(a);
        BigDecimal b1 = BigDecimal.valueOf(b);
        BigDecimal cAdd = a1.add(b1);
        System.out.println(cAdd);
        // 减法
        BigDecimal cSub = a1.subtract(b1);
        System.out.println(cSub);
        // 乘法
        BigDecimal cMul = a1.multiply(b1);
        System.out.println(cMul);
        // 除法
        BigDecimal cDiv = a1.divide(b1);
        System.out.println(cDiv);

        // 运用BigDecimal计算是手段 目的是结果
        double rs = cDiv.doubleValue();
        System.out.println(rs);

        // 注意事项:BigDecimal是一定要精度运算
        BigDecimal a2 = BigDecimal.valueOf(10.0);
        BigDecimal b2 = BigDecimal.valueOf(3.0);
        BigDecimal c2 = a2.divide(b2, 2, RoundingMode.HALF_UP);
        System.out.println(c2);

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

    }
}

总结:

  1. BigDecimal的作用是什么?
    • 解决浮点型运算精度失真问题。
  2. BigDecimal的对象如何获取?
    • BigDecimal b1 = BigDecimal.valueOf(0.1);
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值