Java高级

第一部分 面向对象高级

一、静态

static读作静态,可以用来修饰成员变量,也能修饰成员方法。我们先来学习static修饰成员变量

1.1 static修饰成员变量

Java中的成员变量按照有无static修饰分为两种:类变量、实例变量

image-20231117163719483

静态变量是属于类的,只需要通过类名就可以调用:类名.静态变量

实例变量是属于对象的,需要通过对象才能调用:对象.实例变量

1.2 static修饰成员变量的应用场景

如果某个数据只需要一份,且希望能够被共享(访问、修改),则该数据可以定义成类变量

1.3 static修饰成员方法

成员方法根据有无static也分为两类:类方法、实例方法

image-20231117164220650

有static修饰的方法,是属于类的,称为类方法;调用时直接用类名调用即可。

无static修饰的方法,是属于对象的,称为实例方法;调用时,需要使用对象调用。

原理:

类方法:static修饰的方法,可以被类名调用,是因为它是随着类的加载而加载的;所以类名直接就可以找到static修饰的方法

实例方法:非static修饰的方法,需要创建对象后才能调用,是因为实例方法中可能会访问实例变量,而实例变量需要创建对象后才存在。

所以实例方法,必须创建对象后才能调用。

1.4 工具类

如果一个类中的方法全都是静态的,那么这个类中的方法就全都可以被类名直接调用,由于调用起来非常方便,就像一个工具一下,所以把这样的类就叫做工具类

举例:

public class MyUtils{
    public static String createCode(int n){
        //1.定义一个字符串,用来记录产生的验证码
        String code = "";
        //2.验证码是由所有的大写字母、小写字母或者数字字符组成
        //这里先把所有的字符写成一个字符串,一会从字符串中随机找字符
        String data = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKMNOPQRSTUVWXYZ";

        //3.循环n次,产生n个索引,再通过索引获取字符
        Random r = new Random();
        for(int i=0; i<n; i++){
            int index = r.nextInt(data.length());
            char ch = data.charAt(index);
            //4.把获取到的字符,拼接到code验证码字符串上。
            code+=ch;
        }
        //最后返回code,code的值就是验证码
        return code;
    }
}

接着可以在任何位置调用MyUtilscreateCOde()方法产生任意个数的验证码

//比如这是一个登录界面
public class LoginDemo{
    public static void main(String[] args){
        System.out.println(MyUtils.createCode());
    }
}

//比如这是一个注册界面
public class registerDemo{
    public static void main(String[] args){
        System.out.println(MyUtils.createCode());
    }
}

工具类里的方法全都是静态的,推荐用类名调用为了防止使用者用对象调用。我们可以把工具类的构造方法私有化

public class MyUtils{
    //私有化构造方法:这样别人就不能使用构造方法new对象了
    private MyUtils(){
    }
    //类方法
    public static String createCode(int n){
       ...
    }
}

1.5 static注意事项

image-20231117165531764

1.6 static应用(代码块)

代码块根据有无static修饰分为两种:静态代码块、实例代码块

1.6.1 静态代码块

image-20231117165736093

public class Student {
    static int number = 80;
    static String schoolName = "黑马";
    // 静态代码块
    static {
        System.out.println("静态代码块执行了~~");
        schoolName = "黑马";
    }
}

静态代码块不需要创建对象就能够执行

public class Test {
    public static void main(String[] args) {
        // 目标:认识两种代码块,了解他们的特点和基本作用。
        System.out.println(Student.number);
        System.out.println(Student.number);
        System.out.println(Student.number);
        System.out.println(Student.schoolName); // 黑马
    }
}

静态代码块,随着类的加载而执行,而且只执行一次

1.6.2 实例代码块

image-20231117165908025

实例代码块的作用和构造器的作用是一样的,用来给对象初始化值;而且每次创建对象之前都会先执行实例代码块

实例代码块每次创建对象之前都会执行一次

1.7 static应用(单例设计模式)

二、继承

2.1 概述

image-20231117170201620

2.2 继承的好处

可以把重复的代码提取出来,作为父类,然后让其他类继承父类就可以了,这样可以提高代码的复用性

image-20231120091840674

先写一个父类 People,用来设计Teacher和Consultant公有的成员

public class People{
    private String name;
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name=name;
    }

}

再写两个子类Teacher继承People类,同时在子类中加上自己特有的成员

public class Teacher extends People{
    private String skill; //技能
    public String getSkill(){
        return skill;
    }
    public void setSkill(String skill){
        this.skill=skill;
    }
    public void printInfo(){
        System.out.println(getName()+"具备的技能:"+skill);
    }
}

consultant类

public class Consultant extends People{
    private int number;

    public int getNumber(){
        return number;
    }
    public void setNumber(int number){
        this.number = number;
    }
}

最后再写一个测试类,再测试类中创建Teacher、Consultant对象,并调用方法

public class Test {
    public static void main(String[] args) {
        // 目标:搞清楚继承的好处。
        Teacher t = new Teacher();
        t.setName("播仔");
        t.setSkill("Java、Spring");
        System.out.println(t.getName());
        System.out.println(t.getSkill());
        t.printInfo();
    }
}

2.3 权限的修饰符

权限修饰符是用来限制类的成员(成员变量、成员方法、构造器...)能够被访问的范围

image-20231120093221526

举例:

public class Fu {
    // 1、私有:只能在本类中访问
    private void privateMethod(){
        System.out.println("==private==");
    }

    // 2、缺省:本类,同一个包下的类
    void method(){
        System.out.println("==缺省==");
    }

    // 3、protected: 本类,同一个包下的类,任意包下的子类
    protected void protectedMethod(){
        System.out.println("==protected==");
    }

    // 4、public: 本类,同一个包下的类,任意包下的子类,任意包下的任意类
    public void publicMethod(){
        System.out.println("==public==");
    }

    public void test(){
        //在本类中,所有权限都可以被访问到
        privateMethod(); //正确
        method(); //正确
        protectedMethod(); //正确
        publicMethod(); //正确
    }

}

在和Fu类同一个包下,创建一个测试类Demo,演示同一个包下可以访问到哪些权限修饰的方法

public class Demo {
    public static void main(String[] args) {
        Fu f = new Fu();
        // f.privateMethod();   //私有方法无法使用
        f.method();
        f.protectedMethod();
        f.publicMethod();
    }
}

在另一个包下创建一个Fu类的子类,演示不同包下的子类中可以访问哪些权限修饰的方法

public class Zi extends Fu {
    //在不同包下的子类中,只能访问到public、protected修饰的方法
    public void test(){
        // privateMethod(); // 报错
        // method(); // 报错
        protectedMethod();  //正确
        publicMethod(); //正确
    }
}

在和Fu类不同的包下,创建一个测试类Demo2,演示一下不同包的无关类,能访问到哪些权限修饰的方法

public class Demo2 {
    public static void main(String[] args) {
        Fu f = new Fu();
        // f.privateMethod(); // 报错
        // f.method();        //报错
        // f.protecedMethod();//报错
        f.publicMethod();   //正确
        Zi zi = new Zi();
        // zi.protectedMethod();
    }
}

2.4 单继承、Object

Java语言只支持单继承,不支持多继承,但是可以多层继承

public class Test {
    public static void main(String[] args) {
        // 目标:掌握继承的两个注意事项事项。
        // 1、Java是单继承的:一个类只能继承一个直接父类;
        // 2、Object类是Java中所有类的祖宗。
        A a = new A();
        B b = new B();
        ArrayList list = new ArrayList();
        list.add("java");
        System.out.println(list.toString());
    }
}
class A {} //extends Object{}
class B extends A{}
// class C extends B , A{} // 报错
class D extends B{}

2.5 方法重写

当子类觉得父类方法不好用,或者无法满足父类需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写,重写后,方法的访问遵循就近原则

注意:

1)重写的方法上面,可以加一个注解@Override,用于标注这个方法是复写的父类方法

2)子类复写父类方法时,访问权限必须大于或者等于父类方法的权限:public > protected > 缺省

3)重写的方法返回值类型,必须与被重写的方法返回值类型一样,或者范围更小

4)私有方法、静态方法不能被重写,如果重写会报错。

总结:声明不变,重新实现

举例:子类重写Object的toString()方法,以便返回对象的内容

有一个Student类,这个类会默认继承Object类

 public class Student extends Object{
    private String name;
    private int age;
    public Student() {
    } 
    public Student(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;
    }
}

其实Object类中有一个toString()方法,直接通过Student对象调用Object的toString()方法,会得到对象的地址值

public class Test {
    public static void main(String[] args) {
        Student s = new Student("播妞", 19);
        // System.out.println(s.toString());
        System.out.println(s);
    }
}

不想调用父类Object的toString()方法,那就可以在Student类中重新写一个toSting()方法,用于返回对象的属性值

package com.itheima.d12_extends_override;

public class Student extends Object{
    private String name;
    private int age;

    public Student() {
    }

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

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

}

2.6 子类中访问成员的特点

在子类中访问其他成员(成员变量、成员方法),是依据就近原则的

举例:

public class F {
    String name = "父类名字";
    public void print1(){
        System.out.println("==父类的print1方法执行==");
    }
}

再定义一个子类,有一个同名的name成员变量,有一个同名的print1成员方法

public class Z extends F {
    String name = "子类名称";
    public void showName(){
        String name = "局部名称";
        System.out.println(name); // 局部名称
    }

    @Override
    public void print1(){
        System.out.println("==子类的print1方法执行了=");
    }

    public void showMethod(){
        print1(); // 子类的
    }

}

接下来写一个测试类,观察运行结果,我们发现都是调用的子类变量、子类方法

public class Test {
    public static void main(String[] args) {
        // 目标:掌握子类中访问其他成员的特点:就近原则。
        Z z = new Z();
        z.showName();
        z.showMethod();
    }
}

如果子类和父类出现同名变量或者方法,优先使用子类的;此时如果一定要在子类中使用父类的成员,可以加this或者super进行区分

public class Z extends F {
    String name = "子类名称";
    public void showName(){
        String name = "局部名称";
        System.out.println(name); // 局部名称
        System.out.println(this.name); // 子类成员变量
        System.out.println(super.name); // 父类的成员变量
    }
    @Override
    public void print1(){
        System.out.println("==子类的print1方法执行了=");
    }
    public void showMethod(){
        print1(); // 子类的
        super.print1(); // 父类的
    }
}

2.7 子类中访问构造器的特点

子类全部构造器,都会先调用父类构造器,再执行自己

image-20231120110544458

如果不想使用默认的super()方式调用父类构造器,还可以手动使用super(参数)调用父类有参数构造器

image-20231120110625153

有时候我们也需要访问自己类的构造器。语法如下

this(): 调用本类的空参数构造器
this(参数): 调用本类有参数的构造器

image-20231120110655892

访问本类成员:
    this.成员变量   //访问本类成员变量
    this.成员方法   //调用本类成员方法
    this()         //调用本类空参数构造器
    this(参数)      //调用本类有参数构造器

访问父类成员:
    super.成员变量  //访问父类成员变量
    super.成员方法  //调用父类成员方法
    super()        //调用父类空参数构造器
    super(参数)     //调用父类有参数构造器

注意:this和super访问构造方法,只能用到构造方法的第一句,否则会报错。

三、多态

3.1 概述

多态是在继承、实现情况下的一种现象,表现为:对象多态、行为多态

image-20231120152201091

3.2 多态的好处

在多态形式下,右边的代码是解耦合的,更便于扩展和维护

定义方法时,使用父类类型作为形参,可以接收一切子类对象,扩展行更强,更便利

3.3 类型转换

在多态形式下,不能调用子类特有的方法,比如在Teacher类中多了一个teach方法,在Student类中多了一个study方法,这两个方法在多态形式下是不能直接调用的

多态形式下不能直接调用子类特有方法,但是转型后是可以调用的。这里所说的转型就是把父类变量转换为子类类型

//如果p接收的是子类对象
if(父类变量 instanceof 子类){
    //则可以将p转换为子类类型
    子类 变量名 = (子类)父类变量;
}

image-20231120155442438

如果类型转换错了,就会出现类型转换异常ClassCastException,比如把Teacher类型转换成了Student类型

image-20231120155451256

原本是什么类型,才能还原成什么类型

四、final关键字

4.1 特点

final关键字是最终的意思,可以修饰类、修饰方法、修饰变量

- final修饰类:该类称为最终类,特点是不能被继承
- final修饰方法:该方法称之为最终方法,特点是不能被重写。
- final修饰变量:该变量只能被赋值一次。

4.2 常量

在实际运用当中经常使用final来定义常量

被 static final 修饰的成员变量,称之为常量。

通常用于记录系统的配置信息

public class Constant {
    //常量: 定义一个常量表示学校名称
    //为了方便在其他类中被访问所以一般还会加上public修饰符
    //常量命名规范:建议都采用大写字母命名,多个单词之前有_隔开
    public static final String SCHOOL_NAME = "传智教育";
}

五、抽象

5.1 概述

在Java中有一个关键字叫abstract,它就是抽象的意思,它可以修饰类也可以修饰方法

- 被abstract修饰的类,就是抽象类
- 被abstract修饰的方法,就是抽象方法(不允许有方法体)
//abstract修饰类,这个类就是抽象类
public abstract class A{
    //abstract修饰方法,这个方法就是抽象方法
    public abstract void test();
}

类的成员(成员变量、成员方法、构造器)都可以有

// 抽象类
public abstract class A {
    //成员变量
    private String name;
    static String schoolName;
    //构造方法
    public A(){
    }
    //抽象方法
    public abstract void test();
    //实例方法
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

抽象类是不能创建对象的,如果抽象类的对象就会报错

image-20231120161133657

抽象类虽然不能创建对象,但是它可以作为父类让子类继承。而且子类继承父类必须重写父类的所有抽象方法

//B类继承A类,必须复写test方法
public class B extends A {
    @Override
    public void test() {
    }
}

子类继承父类如果不复写父类的抽象方法,要想不出错,这个子类也必须是抽象类

//B类继承A类,此时B类也是抽象类,这个时候就可以不重写A类的抽象方法
public abstract class B extends A {

}

5.2 抽象类的好处

image-20231120161253109

分析需求发现,该案例中猫和狗都有名字这个属性,也都有叫这个行为,所以我们可以将共性的内容抽取成一个父类,Animal类,但是由于猫和狗叫的声音不一样,于是我们在Animal类中将叫的行为写成抽象的

public abstract class Animal {
    private String name;
    //动物叫的行为:不具体,是抽象的
    public abstract void cry(); 
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

接着写一个Animal的子类,Dog类

public class Dog extends Animal{
    public void cry(){
        System.out.println(getName() + "汪汪汪的叫~~");
    }
}

再写一个Animal的子类,Cat类

public class Cat extends Animal{
    public void cry(){
        System.out.println(getName() + "喵喵喵的叫~~");
    }
}

再写一个测试类,Test类

public class Test2 {
    public static void main(String[] args) {
        // 目标:掌握抽象类的使用场景和好处.
        Animal a = new Dog();
        a.cry();    //这时执行的是Dog类的cry方法
    }
}

假设现在系统有需要加一个Pig类,也有叫的行为,这时候也很容易原有功能扩展。只需要让Pig类继承Animal,复写cry方法就行

public class Pig extends Animal{
    @Override
    public void cry() {
        System.out.println(getName() + "嚯嚯嚯~~~");
    }
}

创建对象时,让Animal接收Pig,就可以执行Pig的cry方法

public class Test2 {
    public static void main(String[] args) {
        // 目标:掌握抽象类的使用场景和好处.
        Animal a = new Pig();
        a.cry();    //这时执行的是Pig类的cry方法
    }
}

总结:

1.用抽象类可以把父类中相同的代码,包括方法声明都抽取到父类,这样能更好的支持多态,提高代码的灵活性。
2.反过来用,我们不知道系统未来具体的业务实现时,我们可以先定义抽象类,将来让子类去实现,以方便系统的扩展。

5.3 模板方法模式

设计模式是解决某一类问题的最优方案

模板方法模式主要解决方法中存在重复代码的问题

比如A类和B类都有sing()方法,sing()方法的开头和结尾都是一样的,只是中间一段内容不一样。此时A类和B类的sing()方法中就存在一些相同的代码。

image-20231120162646681

可以写一个抽象类C类,在C类中写一个doSing()的抽象方法。再写一个sing()方法

// 模板方法设计模式
public abstract class C {
    // 模板方法
    public final void sing(){
        System.out.println("唱一首你喜欢的歌:");

        doSing();

        System.out.println("唱完了!");
    }

    public abstract void doSing();

}

写一个A类继承C类,复写doSing()方法

public class A extends C{
    @Override
    public void doSing() {
        System.out.println("我是一只小小小小鸟,想要飞就能飞的高~~~");
    }
}

再写一个B类继承C类,也复写doSing()方法

public class B extends C{
    @Override
    public void doSing() {
        System.out.println("我们一起学猫叫,喵喵喵喵喵喵喵~~");
    }
}

再写一个测试类Test

public class Test {
    public static void main(String[] args) {
        // 目标:搞清楚模板方法设计模式能解决什么问题,以及怎么写。
        B b = new B();
        b.sing();
    }
}

六、接口

6.1 概述

Java提供了一个关键字interface,用这个关键字来定义接口这种特殊结构

public interface 接口名{
    //成员变量(常量)
    //成员方法(抽象方法)
}

接口是用来被类实现(implements)的,我们称之为实现类。

一个类是可以实现多个接口的(接口可以理解成干爹),类实现接口必须重写所有接口的全部抽象方法,否则这个类也必须是抽象类

public interface A{
    //这里public static final可以加,可以不加。
    public static final String SCHOOL_NAME = "黑马程序员";
    //这里的public abstract可以加,可以不加。
    public abstract void test();
}

写好A接口之后,在写一个测试类

public class Test{
    public static void main(String[] args){
        //打印A接口中的常量
        System.out.println(A.SCHOOL_NAME);
        //接口是不能创建对象的
        A a = new A();
    }
}

再定义一个B接口,里面有两个方法testb1(),testb2()

public interface B {
    void testb1();
    void testb2();
}

再定义一个C接口,里面有两个方法testc1(), testc2()

public interface C {
    void testc1();
    void testc2();
}

再写一个实现类D,同时实现B接口和C接口,此时就需要复写四个方法

// 实现类
public class D implements B, C{
    @Override
    public void testb1() {

    }

    @Override
    public void testb2() {

    }

    @Override
    public void testc1() {

    }

    @Override
    public void testc2() {

    }

}

定义一个测试类Test

public class Test {
    public static void main(String[] args) {
        // 目标:认识接口。
        System.out.println(A.SCHOOL_NAME);

        // A a = new A();
        D d = new D();
    }

}

6.2 案例演示

image-20231120165938568

首先我们写一个学生类,用来描述学生的相关信息

public class Student {
    private String name;
    private char sex;
    private double score;

    public Student() {
    }

    public Student(String name, char sex, double score) {
        this.name = name;
        this.sex = sex;
        this.score = score;
    }

    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 double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

}

写一个StudentOperator接口,表示学生信息管理系统的两个功能

public interface StudentOperator {
    void printAllInfo(ArrayList<Student> students);
    void printAverageScore(ArrayList<Student> students);
}

写一个StudentOperator接口的实现类StudentOperatorImpl1,采用第1套方案对业务进行实现

public class StudentOperatorImpl1 implements StudentOperator{
    @Override
    public void printAllInfo(ArrayList<Student> students) {
        System.out.println("----------全班全部学生信息如下--------------");
        for (int i = 0; i < students.size(); i++) {
            Student s = students.get(i);
            System.out.println("姓名:" + s.getName() + ", 性别:" + s.getSex() + ", 成绩:" + s.getScore());
        }
        System.out.println("-----------------------------------------");
    }

    @Override
    public void printAverageScore(ArrayList<Student> students) {
        double allScore = 0.0;
        for (int i = 0; i < students.size(); i++) {
            Student s = students.get(i);
            allScore += s.getScore();
        }
        System.out.println("平均分:" + (allScore) / students.size());
    }

}

再写一个StudentOperator接口的实现类StudentOperatorImpl2,采用第2套方案对业务进行实现

public class StudentOperatorImpl2 implements StudentOperator{
    @Override
    public void printAllInfo(ArrayList<Student> students) {
        System.out.println("----------全班全部学生信息如下--------------");
        int count1 = 0;
        int count2 = 0;
        for (int i = 0; i < students.size(); i++) {
            Student s = students.get(i);
            System.out.println("姓名:" + s.getName() + ", 性别:" + s.getSex() + ", 成绩:" + s.getScore());
            if(s.getSex() == '男'){
                count1++;
            }else {
                count2 ++;
            }
        }
        System.out.println("男生人数是:" + count1  + ", 女士人数是:" + count2);
        System.out.println("班级总人数是:" + students.size());
        System.out.println("-----------------------------------------");
    }

    @Override
    public void printAverageScore(ArrayList<Student> students) {
        double allScore = 0.0;
        double max = students.get(0).getScore();
        double min = students.get(0).getScore();
        for (int i = 0; i < students.size(); i++) {
            Student s = students.get(i);
            if(s.getScore() > max) max = s.getScore();
            if(s.getScore() < min) min = s.getScore();
            allScore += s.getScore();
        }
        System.out.println("学生的最高分是:" + max);
        System.out.println("学生的最低分是:" + min);
        System.out.println("平均分:" + (allScore - max - min) / (students.size() - 2));
    }

}

再写一个班级管理类ClassManager,在班级管理类中使用StudentOperator的实现类StudentOperatorImpl1对学生进行操作

public class ClassManager {
    private ArrayList<Student> students = new ArrayList<>();
    private StudentOperator studentOperator = new StudentOperatorImpl1();

    public ClassManager(){
        students.add(new Student("迪丽热巴", '女', 99));
        students.add(new Student("古力娜扎", '女', 100));
        students.add(new Student("马尔扎哈", '男', 80));
        students.add(new Student("卡尔扎巴", '男', 60));
    }

    // 打印全班全部学生的信息
    public void printInfo(){
        studentOperator.printAllInfo(students);
    }

    // 打印全班全部学生的平均分
    public void printScore(){
        studentOperator.printAverageScore(students);
    }

}

再写一个测试类Test,在测试类中使用ClassMananger完成班级学生信息的管理

public class Test {
    public static void main(String[] args) {
        // 目标:完成班级学生信息管理的案例。
        ClassManager clazz = new ClassManager();
        clazz.printInfo();
        clazz.printScore();
    }
}

想切换班级管理系统的业务功能,随时可以将StudentOperatorImpl1切换为StudentOperatorImpl2

6.3 接口JDK8的新特性

public interface A {
    /**
     * 1、默认方法:必须使用default修饰,默认会被public修饰
     * 实例方法:对象的方法,必须使用实现类的对象来访问。
     */
    default void test1(){
        System.out.println("===默认方法==");
        test2();
    }

    /**
     * 2、私有方法:必须使用private修饰。(JDK 9开始才支持的)
     *   实例方法:对象的方法。
     */
    private void test2(){
        System.out.println("===私有方法==");
    }

    /**
     * 3、静态方法:必须使用static修饰,默认会被public修饰
     */
     static void test3(){
        System.out.println("==静态方法==");
     }

     void test4();
     void test5();
     default void test6(){

     }

}

接下来我们写一个B类,实现A接口。

B类作为A接口的实现类,只需要重写抽象方法,对于默认方法不需要子类重写

public class B implements A{
    @Override
    public void test4() {

    }

    @Override
    public void test5() {

    }

}

写一个测试类,观察接口中的三种方法

public class Test {
    public static void main(String[] args) {
        // 目标:掌握接口新增的三种方法形式
        B b = new B();
        b.test1();  //默认方法使用对象调用
        // b.test2();   //A接口中的私有方法,B类调用不了
        A.test3();  //静态方法,使用接口名调用
    }
}

6.4 注意事项

一个接口可以继承多个接口

public class Test {
    public static void main(String[] args) {
        // 目标:理解接口的多继承。
    }
}

interface A{
    void test1();
}
interface B{
    void test2();
}
interface C{}

//比如:D接口继承C、B、A
interface D extends C, B, A{

}

//E类在实现D接口时,必须重写D接口、以及其父类中的所有抽象方法。
class E implements D{
    @Override
    public void test1() {

    }

    @Override
    public void test2() {

    }

}
1.一个接口继承多个接口,如果多个接口中存在相同的方法声明,则此时不支持多继承
2.一个类实现多个接口,如果多个接口中存在相同的方法声明,则此时不支持多实现
3.一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会有限使用父类的方法
4.一个类实现类多个接口,多个接口中有同名的默认方法,则这个类必须重写该方法。

一个接口可以继承多个接口,接口同时也可以被类实现

七、内部类

当一个类的内部,包含一个完整的事物,且这个事物没有必要单独设计时,就可以把这个事物设计成内部类

public class Car{
    //内部类
    public class Engine{

    }

}

内部类有四种形式,分别是成员内部类、静态内部类、局部内部类、匿名内部类

7.1 成员内部类

成员内部类就是类中的一个普通成员,类似于成员变量、成员方法

public class Outer {
    private int age = 99;
    public static String a="黑马";

    // 成员内部类
    public class Inner{
        private String name;
        private  int age = 88;

        //在内部类中既可以访问自己类的成员,也可以访问外部类的成员
        public void test(){
            System.out.println(age); //88
            System.out.println(a);   //黑马

            int age = 77;
            System.out.println(age); //77
            System.out.println(this.age); //88
            System.out.println(Outer.this.age); //99
        }

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

}

成员内部类创建对象,格式如下

//外部类.内部类 变量名 = new 外部类().new 内部类();
Outer.Inner in = new Outer().new Inner();
//调用内部类的方法
in.test();

既可以访问内部类成员、也可以访问外部类成员

如果内部类成员和外部类成员同名,可以使用类名.this.成员区分

7.2 静态内部类

静态内部类,其实就是在成员内部类的前面加了一个static关键字。静态内部类属于外部类自己持有

public class Outer {
    private int age = 99;
    public static String schoolName="黑马";

    // 静态内部类
    public static class Inner{
        //静态内部类访问外部类的静态变量,是可以的;
        //静态内部类访问外部类的实例变量,是不行的
        public void test(){
            System.out.println(schoolName); //99
            //System.out.println(age);   //报错
        }
    }

}

静态内部类创建对象时,需要使用外部类的类名调用

//格式:外部类.内部类 变量名 = new 外部类.内部类();
Outer.Inner in = new Outer.Inner();
in.test();

7.3 局部内部类

局部内部类是定义在方法中的类,和局部变量一样,只能在方法中有效。所以局部内部类的局限性很强,一般在开发中是不会使用的。

public class Outer{
    public void test(){
        //局部内部类
        class Inner{
            public void show(){
                System.out.println("Inner...show");
            }
        }

     //局部内部类只能在方法中创建对象,并使用
       Inner in = new Inner();
        in.show();
    }

}

7.4 匿名内部类

7.4.1 概述和使用

实际开发中用得最多的一种内部类,叫匿名内部类

匿名内部类是一种特殊的局部内部类;所谓匿名,指的是程序员不需要为这个类声明名字

new 父类/接口(参数值){
    @Override
    重写父类/接口的方法;
}

匿名内部类本质上是一个没有名字的子类对象、或者接口的实现类对象

先定义一个Animal抽象类,里面定义一个cry()方法,表示所有的动物有叫的行为,但是因为动物还不具体,cry()这个行为并不能具体化,所以写成抽象方法

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

我想要在不定义子类的情况下创建Animal的子类对象,就可以使用匿名内部类

public class Test{
    public static void main(String[] args){
        //这里后面new 的部分,其实就是一个Animal的子类对象
        //这里隐含的有多态的特性: Animal a = Animal子类对象;
        Animal a = new Animal(){
            @Override
            public void cry(){
                System.out.println("猫喵喵喵的叫~~~");
            }
        }
        a.eat(); //直线上面重写的cry()方法
    }
}

匿名内部类在编写代码时没有名字,编译后系统会为自动为匿名内部类生产字节码,字节码的名称会以外部类$1.class的方法命名

匿名内部类的作用:简化了创建子类对象、实现类对象的书写格式

7.4.2 应用场景

一般我们会主动的使用匿名内部类

只有在调用方法时,当方法的形参是一个接口或者抽象类,为了简化代码书写,而直接传递匿名内部类对象给方法。这样就可以少写一个类

public interface Swimming{
    public void swim();
}
public class Test{
    public static void main(String[] args){
        Swimming s1 = new Swimming(){
            public void swim(){
                System.out.println("狗刨飞快");
            }
        };
        go(s1);
        Swimming s1 = new Swimming(){
            public void swim(){
                System.out.println("猴子游泳也还行");
            }
        };
        go(s1);
    }
    //形参是Swimming接口,实参可以接收任意Swimming接口的实现类对象
    public static void go(Swimming s){
        System.out.println("开始~~~~~~~~");
        s.swim();
        System.out.println("结束~~~~~~~~");
    }

}

八、枚举

8.1 认识枚举

8.1.1 概述和原理

枚举是一种特殊的类

public enum 枚举类名{
    枚举项1,枚举项2,枚举项3;
}

其实枚举项就表示枚举类的对象,只是这些对象在定义枚举类时就预先写好了,以后就只能用这几个固定的对象

public enum A{
    X,Y,Z;
}

想要获取枚举类中的枚举项,只需要用类名调用就可以了

public class Test{
    public static void main(String[] args){
        //获取枚举A类的,枚举项
        A a1 = A.X;
        A a2 = A.Y;
        A a3 = A.Z;
    }
}

8.1.2 枚举深入

在枚举类中定义构造器、成员变量、成员方法

public enum A{
    //定义枚举项
    X,Y,Z("张三"); //枚举项后面加括号,就是在执行枚举类的带参数构造方法。

    //定义空构造器
    public A(){

    }

    //成员变量
    private String name;
    //定义带参数构造器
    public A(String name){
        this.name=name;
    }

    //成员方法
    public String getName(){
        return name;
    }
    ...

}

虽然枚举类中可以像类一样,写一些类的其他成员,但是一般不会这么写,如果你真要这么干的话,到不如直接写普通类来的直接

8.2 枚举的应用场景

枚举一般表示一组信息,然后作为参数进行传输

举例:用户进入应用时,需要让用户选择是女生、还是男生,然后系统会根据用户选择的是男生,还是女生推荐不同的信息给用户观看

先定义一个枚举类,用来表示男生、或者女生

public class Constant{
    BOY,GRIL
}

再定义一个测试类,完成用户进入系统后的选择

public class Test{
    public static void main(String[] args){
        //调用方法,传递男生
        provideInfo(Constant.BOY);
    }
    public static void provideInfo(Constant c){
        switch(c){
            case BOY:
                System.out.println("展示一些信息给男生看");
                break;
            case GRIL:
                System.out.println("展示一些信息给女生看");
                break;
        }
    }
}

枚举一般表示几个固定的值,然后作为参数进行传输

九、泛型

9.1 认识泛型

在定义类、接口、方法时,同时声明了一个或者多个类型变量(如:),称为泛型类、泛型接口、泛型方法、它们统称为泛型

比如我们前面学过的ArrayList类就是一个泛型类

image-20231121102913169

ArrayList集合的设计者在定义ArrayList集合时,就已经明确ArrayList集合时给别人装数据用的,但是别人用ArrayList集合时候,装什么类型的数据他不知道,所以就用一个<E>表示元素的数据类型

当别人使用ArrayList集合创建对象时,new ArrayList<String> 就表示元素为String类型,new ArrayList<Integer>表示元素为Integer类型

泛型的好处:在编译阶段可以避免出现一些非法的数据。

泛型的本质:把具体的数据类型传递给类型变量

9.2 自定义泛型类

泛型类,在实际工作中一般都是源代码中写好,我们直接用的,就是ArrayList这样的,自己定义泛型类是非常少的

//这里的<T,W>其实指的就是类型变量,可以是一个,也可以是多个。
public class 类名<T,W>{

}
定义一个MyArrayList<E>泛型类,模拟一下自定义泛型类的使用

//定义一个泛型类,用来表示一个容器
//容器中存储的数据,它的类型用<E>先代替用着,等调用者来确认<E>的具体类型。
public class MyArrayList<E>{
    private Object[] array = new Object[10];
    //定一个索引,方便对数组进行操作
    private int index;

    //添加元素
    public void add(E e){
        array[index]=e;
        index++;
    }

    //获取元素
    public E get(int index){
        return (E)array[index];
    }

}

写一个测试类,来测试自定义的泛型类MyArrayList是否能够正常使用

public class Test{
    public static void main(String[] args){
        //1.确定MyArrayList集合中,元素类型为String类型
        MyArrayList<String> list = new MyArrayList<>();
        //此时添加元素时,只能添加String类型
        list.add("张三");
        list.add("李四");

         //2.确定MyArrayList集合中,元素类型为Integer类型
        MyArrayList<Integer> list1 = new MyArrayList<>();
        //此时添加元素时,只能添加String类型
        list.add(100);
        list.add(200);

    }

}

9.3 自定义泛型接口

泛型接口其实指的是在接口中把不确定的数据类型用<类型变量>表示

//这里的类型变量,一般是一个字母,比如<E>
public interface 接口名<类型变量>{

}

我们现在要做一个系统要处理学生和老师的数据,需要提供2个功能,保存对象数据、根据名称查询数据,要求:这两个功能处理的数据既能是老师对象,也能是学生对象

public class Teacher{

}
public class Student{

}

定义一个Data<T>泛型接口,T表示接口中要处理数据的类型

public interface Data<T>{
    public void add(T t);

    public ArrayList<T> getByName(String name);

}

写一个处理Teacher对象的接口实现类

//此时确定Data<E>中的E为Teacher类型,
//接口中add和getByName方法上的T也都会变成Teacher类型
public class TeacherData implements Data<Teacher>{
    public void add(Teacher t){

    }

    public ArrayList<Teacher> getByName(String name){

    }

}
写一个处理Student对象的接口实现类

//此时确定Data<E>中的E为Student类型,
//接口中add和getByName方法上的T也都会变成Student类型
public class StudentData implements Data<Student>{
    public void add(Student t){
    }  
    public ArrayList<Student> getByName(String name){   
    }
}

实际工作中,一般也都是框架底层源代码把泛型接口写好,我们实现泛型接口就可以了

9.4 泛型方法

public <泛型变量,泛型变量> 返回值类型 方法名(形参列表){

}

在返回值类型和修饰符之间有定义的才是泛型方法

image-20231121144301250

举例:

public class Test{
    public static void main(String[] args){
        //调用test方法,传递字符串数据,那么test方法的泛型就是String类型
        String rs = test("test");

        //调用test方法,传递Dog对象,那么test方法的泛型就是Dog类型
        Dog d = test(new Dog()); 
    }

    //这是一个泛型方法<T>表示一个不确定的数据类型,由调用者确定
    public static <T> test(T t){
        return t;
    }

}

9.5 泛型限定

泛型限定的意思是对泛型的数据类型进行范围的限制。有如下的三种格式

<?> 表示任意类型

<? extends 数据类型> 表示指定类型或者指定类型的子类

<? super 数据类型> 表示指定类型或者指定类型的父类

演示一下,假设有Car作为父类,BENZ,BWM两个类作为Car的子类

class Car{}
class BENZ extends Car{}
class BWN extends Car{}

public class Test{
    public static void main(String[] args){
        //1.集合中的元素不管是什么类型,test1方法都能接收
        ArrayList<BWM> list1 = new ArrayList<>();
        ArrayList<Benz> list2 = new ArrayList<>();
        ArrayList<String> list3 = new ArrayList<>();
        test1(list1);
        test1(list2);
        test1(list3);

        //2.集合中的元素只能是Car或者Car的子类类型,才能被test2方法接收
        ArrayList<Car> list4 = new ArrayList<>();
        ArrayList<BWM> list5 = new ArrayList<>();
        test2(list4);
        test2(list5);

        //2.集合中的元素只能是Car或者Car的父类类型,才能被test3方法接收
        ArrayList<Car> list6 = new ArrayList<>();
        ArrayList<Object> list7 = new ArrayList<>();
        test3(list6);
        test3(list7);
    }

    public static void test1(ArrayList<?> list){

    }

    public static void test2(ArrayList<? extends Car> list){

    }

    public static void test3(ArrayList<? super Car> list){

    }

}

9.6 泛型擦除

泛型只能编译阶段有效,一旦编译成字节码,字节码中是不包含泛型的

泛型只支持引用数据类型,不支持基本数据类型

十、常用API

API(Application Programming interface)意思是应用程序编程接口

image-20231121161710273

10.1 Object类

找到其中两个方法

image-20231121161756334

10.1.1 toString()方法

public String toString()
    调用toString()方法可以返回对象的字符串表示形式。
    默认的格式是:“包名.类名@哈希值16进制”
public class Student{
    private String name;
    private int age;

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

}

测试类

public class Test{
    public static void main(String[] args){
        Student s1 = new Student("赵敏",23);
        System.out.println(s1.toString()); 
    }
}

在Student类重写toString()方法,那么我们可以返回对象的属性值

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

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

    @Override
    public String toString(){
        return "Student{name=‘"+name+"’, age="+age+"}";
    }
}

10.1.2 equals(Object o)方法

public boolean equals(Object o)
    判断此对象与参数对象是否"相等"

写一个测试类

public class Test{
    public static void main(String[] args){
        Student s1 = new Student("赵薇",23);
        Student s2 = new Student("赵薇",23);
        //equals本身也是比较对象的地址,和"=="没有区别
        System.out.println(s1.equals(s2)); //false
         //"=="比较对象的地址
        System.out.println(s1==s2); //false
    }
}

如果我们在Student类中,把equals方法重写了,就按照对象的属性值进行比较

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

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

    @Override
    public String toString(){
        return "Student{name=‘"+name+"’, age="+age+"}";
    }

    //重写equals方法,按照对象的属性值进行比较
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

}

小结:

public String toString()
    返回对象的字符串表示形式。默认的格式是:“包名.类名@哈希值16进制”
    【子类重写后,返回对象的属性值】

public boolean equals(Object o)
    判断此对象与参数对象是否"相等"。默认比较对象的地址值,和"=="没有区别
    【子类重写后,比较对象的属性值】

10.1.3 clone()方法

public Object clone()
    克隆当前对象,返回一个新对象

想要调用clone()方法,必须让被克隆的类实现Cloneable接口。如我们准备克隆User类的对象

public class User implements Cloneable{
    private String id; //编号
    private String username; //用户名
    private String password; //密码
    private double[] scores; //分数

    //public User(int i, String zhangsan, String wo666, double[] scores) {
   // }

    public User(String id, String username, String password, double[] scores) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.scores = scores;
    }

    public User(int i, String zhangsan, String wo666, double[] scores) {

    }

    public String getId() {
        return id;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public double[] getScores() {
        return scores;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

写一个测试类,克隆User类的对象

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        User u1 = new User(1,"zhangsan","wo666",new double[]{99.0,99.5});
        //调用方法克隆得到一个新对象
        User u2 = (User) u1.clone();
        System.out.println(u2.getId());
        System.out.println(u2.getUsername());
        System.out.println(u2.getPassword());
        System.out.println(u2.getScores()); 
    }
}

10.2 Objects类

Objects是一个工具类,提供了一些方法可以对任意对象进行操作。主要方法如下

image-20231214134524814

public class Test{
    public static void main(String[] args){
        String s1 = null;
        String s2 = "itheima";

        //这里会出现NullPointerException异常,调用者不能为null
        System.out.println(s1.equals(s2));
        //此时不会有NullPointerException异常,底层会自动先判断空
        System.out.println(Objects.equals(s1,s2));

        //判断对象是否为null,等价于==
        System.out.println(Objects.isNull(s1)); //true
        System.out.println(s1==null); //true

        //判断对象是否不为null,等价于!=
        System.out.println(Objects.nonNull(s2)); //true
        System.out.println(s2!=null); //true
    }

}

10.3 基本类型包装类

Java中8种基本数据类型都用一个包装类与之对一个,如下图所示

image-20231214135153558

我们学习包装类,主要学习两点:

  1. 创建包装类的对象方式、自动装箱和拆箱的特性;

  2. 利用包装类提供的方法对字符串和基本类型数据进行相互转换

10.3.1 创建包装类对象

创建包装类对象的方法,以及包装类的一个特性叫自动装箱和自动拆箱。我们以Integer为例

//1.创建Integer对象,封装基本类型数据10
Integer a = new Integer(10);

//2.使用Integer类的静态方法valueOf(数据)
Integer b = Integer.valueOf(10);

//3.还有一种自动装箱的写法(意思就是自动将基本类型转换为引用类型)
Integer c = 10;

//4.有装箱肯定还有拆箱(意思就是自动将引用类型转换为基本类型)
int d = c;

//5.装箱和拆箱在使用集合时就有体现
ArrayList<Integer> list = new ArrayList<>();
//添加的元素是基本类型,实际上会自动装箱为Integer类型
list.add(100);
//获取元素时,会将Integer类型自动拆箱为int类型
int e = list.get(0);

10.3.2 包装类数据类型转换

在开发中,经常使用包装类对字符串和基本类型数据进行相互转换。

把字符串转换为数值型数据:包装类.parseXxx(字符串)

public static int parseInt(String s)
    //把字符串转换为基本数据类型

将数值型数据转换为字符串:包装类.valueOf(数据);

public static String valueOf(int a)
    //把基本类型数据转换为字符串

写一个测试类演示一下

//1.字符串转换为数值型数据
String ageStr = "29";
int age1 = Integer.parseInt(ageStr);

String scoreStr = 3.14;
double score = Double.prarseDouble(scoreStr);

//2.整数转换为字符串,以下几种方式都可以(挑中你喜欢的记一下)
Integer a = 23;
String s1 = Integer.toString(a);
String s2 = a.toString();
String s3 = a+"";
String s4 = String.valueOf(a);

10.4 StringBuilder类

StringBuilder代表可变字符串对象,相当于是一个容器,它里面的字符串是可以改变的,就是用来操作字符串的。

好处:StringBuilder比String更合适做字符串的修改操作,效率更高,代码也更加简洁。

10.4.1 StringBuilder方法演示

public class Test{
    public static void main(String[] args){
        StringBuilder sb = new StringBuilder("itehima");
        //1.拼接内容
        sb.append(12);
        sb.append("黑马");
        sb.append(true);

        //2.append方法,支持临时编程
        sb.append(666).append("黑马2").append(666);
        System.out.println(sb); //打印:12黑马666黑马2666

        //3.反转操作
        sb.reverse();
        System.out.println(sb); //打印:6662马黑666马黑21

        //4.返回字符串的长度
        System.out.println(sb.length());

        //5.StringBuilder还可以转换为字符串
        String s = sb.toString();
        System.out.println(s); //打印:6662马黑666马黑21
    }

}

StringBuilder比String效率更高。

10.4.2 StringBuilder应用案例

public class Test{
    public static void main(String[] args){
        String str = getArrayData( new int[]{11,22,33});
        System.out.println(str);
    }

    //方法作用:将int数组转换为指定格式的字符串
    public static String getArrayData(int[] arr){
        //1.判断数组是否为null
        if(arr==null){
            return null;
        }
        //2.如果数组不为null,再遍历,并拼接数组中的元素
        StringBuilder sb = new StringBuilder("[");
        for(int i=0; i<arr.length; i++){
            if(i==arr.legnth-1){
                sb.append(arr[i]).append("]");;
            }else{
                sb.append(arr[i]).append(",");
            }
        }
        //3、把StirngBuilder转换为String,并返回。
        return sb.toString();
    }

}

10.5 StringJoiner类

StringJoiner号称是拼接神器,不仅效率高,而且代码简洁

public class Test{
    public static void main(String[] args){
        StringJoiner s = new StringJoiner(",");
        s.add("java1");
        s.add("java2");
        s.add("java3");
        System.out.println(s); //结果为: java1,java2,java3
        //参数1:间隔符
        //参数2:开头
        //参数3:结尾
        StringJoiner s1 = new StringJoiner(",","[","]");
        s1.add("java1");
        s1.add("java2");
        s1.add("java3");
        System.out.println(s1); //结果为: [java1,java2,java3]
    }
}

使用StringJoiner改写前面把数组转换为字符串的案例

public class Test{
    public static void main(String[] args){
        String str = getArrayData( new int[]{11,22,33});
        System.out.println(str);
    }

    //方法作用:将int数组转换为指定格式的字符串
    public static String getArrayData(int[] arr){
        //1.判断数组是否为null
        if(arr==null){
            return null;
        }
        //2.如果数组不为null,再遍历,并拼接数组中的元素
        StringJoiner s = new StringJoiner(", ","[","]");
        for(int i=0; i<arr.length; i++){
            //加""是因为add方法的参数要的是String类型
            s.add(String.valueOf(arr[i]));
        }
        //3、把StringJoiner转换为String,并返回。
        return s.toString();
    }

}

10.6 Math类

该类提供了很多个进行数学运算的方法,如求绝对值,求最大值,四舍五入等

public class MathTest {
    public static void main(String[] args) {
        // 目标:了解下Math类提供的常见方法。
        // 1、public static int abs(int a):取绝对值(拿到的结果一定是正数)
        //    public static double abs(double a)
        System.out.println(Math.abs(-12)); // 12
        System.out.println(Math.abs(123)); // 123
        System.out.println(Math.abs(-3.14)); // 3.14

        // 2、public static double ceil(double a): 向上取整
        System.out.println(Math.ceil(4.0000001)); // 5.0
        System.out.println(Math.ceil(4.0)); // 4.0

        // 3、public static double floor(double a): 向下取整
        System.out.println(Math.floor(4.999999)); // 4.0
        System.out.println(Math.floor(4.0)); // 4.0

        // 4、public static long round(double a):四舍五入
        System.out.println(Math.round(3.4999)); // 3
        System.out.println(Math.round(3.50001)); // 4

        // 5、public static int max(int a, int b):取较大值
        //   public static int min(int a, int b):取较小值
        System.out.println(Math.max(10, 20)); // 20
        System.out.println(Math.min(10, 20)); // 10

        // 6、 public static double pow(double a, double b):取次方
        System.out.println(Math.pow(2, 3)); // 2的3次方   8.0
        System.out.println(Math.pow(3, 2)); // 3的2次方   9.0

        // 7、public static double random(): 取随机数 [0.0 , 1.0) (包前不包后)
        System.out.println(Math.random());
    }

}

10.7 System类

这是系统类,提供了一些获取获取系统数据的方法。比如获取系统时间

  • /**
    
    * 目标:了解下System类的常见方法。
    */
      public class SystemTest {
      public static void main(String[] args) {
    
          // 1、public static void exit(int status):
       //   终止当前运行的Java虚拟机。
          //   该参数用作状态代码; 按照惯例,非零状态代码表示异常终止。
          System.exit(0); // 人为的终止虚拟机。(不要使用)
    
          // 2、public static long currentTimeMillis():
          //    获取当前系统的时间
       //    返回的是long类型的时间毫秒值:指的是从1970-1-1 0:0:0开始走到此刻的总的毫秒值,1s = 1000ms
          long time = System.currentTimeMillis();
          System.out.println(time);
    
       for (int i = 0; i < 1000000; i++) {
              System.out.println("输出了:" + i);
          }
    
       long time2 = System.currentTimeMillis();
       System.out.println((time2 - time) / 1000.0 + "s");
    
    }
    }
    
    ### 10.8 Runtime类

这个类可以用来获取JVM的一些信息,也可以用这个类去执行其他的程序。

  • /**
    
     * 目标:了解下Runtime的几个常见方法。
    */
       public class RuntimeTest {
       public static void main(String[] args) throws IOException, InterruptedException {
    
           // 1、public static Runtime getRuntime() 返回与当前Java应用程序关联的运行时对象。
           Runtime r = Runtime.getRuntime();
    
           // 2、public void exit(int status) 终止当前运行的虚拟机,该参数用作状态代码; 按照惯例,非零状态代码表示异常终止。
           // r.exit(0);
    
           // 3、public int availableProcessors(): 获取虚拟机能够使用的处理器数。
           System.out.println(r.availableProcessors());
    
           // 4、public long totalMemory() 返回Java虚拟机中的内存总量。
           System.out.println(r.totalMemory()/1024.0/1024.0 + "MB"); // 1024 = 1K     1024 * 1024 = 1M
    
           // 5、public long freeMemory() 返回Java虚拟机中的可用内存量
           System.out.println(r.freeMemory()/1024.0/1024.0 + "MB");
    
           // 6、public Process exec(String command) 启动某个程序,并返回代表该程序的对象。
           // r.exec("D:\\soft\\XMind\\XMind.exe");
           Process p = r.exec("QQ");
        Thread.sleep(5000); // 让程序在这里暂停5s后继续往下走!!
        p.destroy(); // 销毁!关闭程序!
    
    }
    }

10.9 BigDecimal类

为了解决计算精度损失的问题,Java给我们提供了BigDecimal类,它提供了一些方法可以对数据进行四则运算,而且不丢失精度,同时还可以保留指定的小数位

public class Test {
    public static void main(String[] args) {
        System.out.println(0.1 + 0.2);
        System.out.println(1.0 - 0.32);
        System.out.println(1.015 * 100);
        System.out.println(1.301 / 100);
    }
}
public class Test2 {
    public static void main(String[] args) {
        // 目标:掌握BigDecimal进行精确运算的方案。
        double a = 0.1;
        double b = 0.2;

        // 1、把浮点型数据封装成BigDecimal对象,再来参与运算。
        // a、public BigDecimal(double val) 得到的BigDecimal对象是无法精确计算浮点型数据的。 注意:不推荐使用这个,
        // b、public BigDecimal(String val)  得到的BigDecimal对象是可以精确计算浮点型数据的。 可以使用。
        // c、public static BigDecimal valueOf(double val): 通过这个静态方法得到的BigDecimal对象是可以精确运算的。是最好的方案。
        BigDecimal a1 = BigDecimal.valueOf(a);
        BigDecimal b1 = BigDecimal.valueOf(b);

        // 2、public BigDecimal add(BigDecimal augend): 加法
        BigDecimal c1 = a1.add(b1);
        System.out.println(c1);

        // 3、public BigDecimal subtract(BigDecimal augend): 减法
        BigDecimal c2 = a1.subtract(b1);
        System.out.println(c2);

        // 4、public BigDecimal multiply(BigDecimal augend): 乘法
        BigDecimal c3 = a1.multiply(b1);
        System.out.println(c3);

        // 5、public BigDecimal divide(BigDecimal b): 除法
        BigDecimal c4 = a1.divide(b1);
        System.out.println(c4);

//        BigDecimal d1 = BigDecimal.valueOf(0.1);
//        BigDecimal d2 = BigDecimal.valueOf(0.3);
//        BigDecimal d3 = d1.divide(d2);
//        System.out.println(d3);

        // 6、public BigDecimal divide(另一个BigDecimal对象,精确几位,舍入模式) : 除法,可以设置精确几位。
        BigDecimal d1 = BigDecimal.valueOf(0.1);
        BigDecimal d2 = BigDecimal.valueOf(0.3);
        BigDecimal d3 = d1.divide(d2,  2, RoundingMode.HALF_UP); // 0.33
        System.out.println(d3);

        // 7、public double doubleValue() : 把BigDecimal对象又转换成double类型的数据。
        //print(d3);
        //print(c1);
        double db1 = d3.doubleValue();
        double db2 = c1.doubleValue();
        print(db1);
        print(db2);
    }

    public static void print(double a){
        System.out.println(a);
    }

}

10.10 Date类

Java中是由Date类的对象用来表示日期或者时间。

Date对象记录的时间是用毫秒值来表示的。Java语言规定,1970年1月1日0时0分0秒认为是时间的起点,此时记作0,那么1000(1秒=1000毫秒)就表示1970年1月1日0时0分1秒

image-20231214154323956

public class Test1Date {
    public static void main(String[] args) {
        // 目标:掌握Date日期类的使用。
        // 1、创建一个Date的对象:代表系统当前时间信息的。
        Date d = new Date();
        System.out.println(d);

        // 2、拿到时间毫秒值。
        long time = d.getTime();
        System.out.println(time);

        // 3、把时间毫秒值转换成日期对象: 2s之后的时间是多少。
        time += 2 * 1000;
        Date d2 = new Date(time);
        System.out.println(d2);

        // 4、直接把日期对象的时间通过setTime方法进行修改
        Date d3 = new Date();
        d3.setTime(time);
        System.out.println(d3);
    }

}

10.11 SimpleDateFormat类

我们把Date对象转换为指定格式的日期字符串这个操作,叫做日期格式化,

反过来把指定格式的日期符串转换为Date对象的操作,叫做日期解析。

image-20231214154629100

记住常用的几种日期/时间格式

字母     表示含义
yyyy    年
MM      月
dd      日
HH      时
mm      分
ss      秒
SSS     毫秒

"2022年12月12日" 的格式是 "yyyy年MM月dd日"
"2022-12-12 12:12:12" 的格式是 "yyyy-MM-dd HH:mm:ss"
按照上面的格式可以任意拼接,但是字母不能写错
public class Test2SimpleDateFormat {
    public static void main(String[] args) throws ParseException {
        // 目标:掌握SimpleDateFormat的使用。
        // 1、准备一些时间
        Date d = new Date();
        System.out.println(d);

        long time = d.getTime();
        System.out.println(time);

        // 2、格式化日期对象,和时间 毫秒值。
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss EEE a");

        String rs = sdf.format(d);
        String rs2 = sdf.format(time);
        System.out.println(rs);
        System.out.println(rs2);
        System.out.println("----------------------------------------------");

        // 目标:掌握SimpleDateFormat解析字符串时间 成为日期对象。
        String dateStr = "2022-12-12 12:12:11";
        // 1、创建简单日期格式化对象 , 指定的时间格式必须与被解析的时间格式一模一样,否则程序会出bug.
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d2 = sdf2.parse(dateStr);
        System.out.println(d2);
    }

}

image-20231214160419330

public class Test3 {
    public static void main(String[] args) throws ParseException {
        // 目标:完成秒杀案例。
        // 1、把开始时间、结束时间、小贾下单时间、小皮下单时间拿到程序中来。
        String start = "2023年11月11日 0:0:0";
        String end = "2023年11月11日 0:10:0";
        String xj = "2023年11月11日 0:01:18";
        String xp = "2023年11月11日 0:10:57";

        // 2、把字符串的时间解析成日期对象。
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        Date startDt = sdf.parse(start);
        Date endDt = sdf.parse(end);
        Date xjDt = sdf.parse(xj);
        Date xpDt = sdf.parse(xp);

        // 3、开始判断小皮和小贾是否秒杀成功了。
        // 把日期对象转换成时间毫秒值来判断
        long startTime = startDt.getTime();
        long endTime = endDt.getTime();
        long xjTime = xjDt.getTime();
        long xpTime = xpDt.getTime();

        if(xjTime >= startTime && xjTime <= endTime){
            System.out.println("小贾您秒杀成功了~~");
        }else {
            System.out.println("小贾您秒杀失败了~~");
        }

        if(xpTime >= startTime && xpTime <= endTime){
            System.out.println("小皮您秒杀成功了~~");
        }else {
            System.out.println("小皮您秒杀失败了~~");
        }
    }

}

10.12 Calendar类

Calendar类表示日历,它提供了一些比Date类更好用的方法。

image-20231214160742805

image-20231214160756985

public class Test4Calendar {
    public static void main(String[] args) {
        // 目标:掌握Calendar的使用和特点。
        // 1、得到系统此刻时间对应的日历对象。
        Calendar now = Calendar.getInstance();
        System.out.println(now);

        // 2、获取日历中的某个信息
        int year = now.get(Calendar.YEAR);
        System.out.println(year);

        int days = now.get(Calendar.DAY_OF_YEAR);
        System.out.println(days);

        // 3、拿到日历中记录的日期对象。
        Date d = now.getTime();
        System.out.println(d);

        // 4、拿到时间毫秒值
        long time = now.getTimeInMillis();
        System.out.println(time);

        // 5、修改日历中的某个信息
        now.set(Calendar.MONTH, 9); // 修改月份成为10月份。
        now.set(Calendar.DAY_OF_YEAR, 125); // 修改成一年中的第125天。
        System.out.println(now);

        // 6、为某个信息增加或者减少多少
        now.add(Calendar.DAY_OF_YEAR, 100);
        now.add(Calendar.DAY_OF_YEAR, -10);
        now.add(Calendar.DAY_OF_MONTH, 6);
        now.add(Calendar.HOUR, 12);
        now.set(2026, 11, 22);
        System.out.println(now);
    }

}

10.13 JDK8日期类

10.13.1 为什么JDK8要新增日期类

/**

目标:搞清楚为什么要用JDK 8开始新增的时间类。
*/
public class Test {
public static void main(String[] args) {
    // 传统的时间类(Date、SimpleDateFormat、Calendar)存在如下问题:
    // 1、设计不合理,使用不方便,很多都被淘汰了。
    Date d = new Date();
    //System.out.println(d.getYear() + 1900);

  Calendar c = Calendar.getInstance();
  int year = c.get(Calendar.YEAR);
 System.out.println(year);

        // 2、都是可变对象,修改后会丢失最开始的时间信息。

        // 3、线程不安全。

        // 4、不能精确到纳秒,只能精确到毫秒。
        // 1秒 = 1000毫秒
        // 1毫秒 = 1000微妙
        // 1微妙 = 1000纳秒
}
}

10.13.2 JDK8日期、时间、日期时间

image-20231214161209528

JDK8新增的日期类分得更细致一些,比如表示年月日用LocalDate类、表示时间秒用LocalTime类、而表示年月日时分秒用LocalDateTime类等;除了这些类还提供了对时区、时间间隔进行操作的类等。它们几乎把对日期/时间的所有操作都通过了API方法,用起来特别方便。

先学习表示日期、时间、日期时间的类;有LocalDate、LocalTime、以及LocalDateTime类。

LocalDate类的基本使用

public class Test1_LocalDate {
    public static void main(String[] args) {
        // 0、获取本地日期对象(不可变对象)
        LocalDate ld = LocalDate.now(); // 年 月 日
        System.out.println(ld);

        // 1、获取日期对象中的信息
        int year = ld.getYear(); // 年
        int month = ld.getMonthValue(); // 月(1-12)
        int day = ld.getDayOfMonth(); // 日
        int dayOfYear = ld.getDayOfYear();  // 一年中的第几天
        int dayOfWeek = ld.getDayOfWeek().getValue(); // 星期几
        System.out.println(year);
        System.out.println(day);
        System.out.println(dayOfWeek);

        // 2、直接修改某个信息: withYear、withMonth、withDayOfMonth、withDayOfYear
        LocalDate ld2 = ld.withYear(2099);
        LocalDate ld3 = ld.withMonth(12);
        System.out.println(ld2);
        System.out.println(ld3);
        System.out.println(ld);

        // 3、把某个信息加多少: plusYears、plusMonths、plusDays、plusWeeks
        LocalDate ld4 = ld.plusYears(2);
        LocalDate ld5 = ld.plusMonths(2);

        // 4、把某个信息减多少:minusYears、minusMonths、minusDays、minusWeeks
        LocalDate ld6 = ld.minusYears(2);
        LocalDate ld7 = ld.minusMonths(2);

        // 5、获取指定日期的LocalDate对象: public static LocalDate of(int year, int month, int dayOfMonth)
        LocalDate ld8 = LocalDate.of(2099, 12, 12);
        LocalDate ld9 = LocalDate.of(2099, 12, 12);

        // 6、判断2个日期对象,是否相等,在前还是在后: equals isBefore isAfter
        System.out.println(ld8.equals(ld9));// true
        System.out.println(ld8.isAfter(ld)); // true
        System.out.println(ld8.isBefore(ld)); // false
    }

}

LocalTime类的基本使用

public class Test2_LocalTime {
    public static void main(String[] args) {
        // 0、获取本地时间对象
        LocalTime lt = LocalTime.now(); // 时 分 秒 纳秒 不可变的
        System.out.println(lt);

        // 1、获取时间中的信息
        int hour = lt.getHour(); //时
        int minute = lt.getMinute(); //分
        int second = lt.getSecond(); //秒
        int nano = lt.getNano(); //纳秒

        // 2、修改时间:withHour、withMinute、withSecond、withNano
        LocalTime lt3 = lt.withHour(10);
        LocalTime lt4 = lt.withMinute(10);
        LocalTime lt5 = lt.withSecond(10);
        LocalTime lt6 = lt.withNano(10);

        // 3、加多少:plusHours、plusMinutes、plusSeconds、plusNanos
        LocalTime lt7 = lt.plusHours(10);
        LocalTime lt8 = lt.plusMinutes(10);
        LocalTime lt9 = lt.plusSeconds(10);
        LocalTime lt10 = lt.plusNanos(10);

        // 4、减多少:minusHours、minusMinutes、minusSeconds、minusNanos
        LocalTime lt11 = lt.minusHours(10);
        LocalTime lt12 = lt.minusMinutes(10);
        LocalTime lt13 = lt.minusSeconds(10);
        LocalTime lt14 = lt.minusNanos(10);

        // 5、获取指定时间的LocalTime对象:
        // public static LocalTime of(int hour, int minute, int second)
        LocalTime lt15 = LocalTime.of(12, 12, 12);
        LocalTime lt16 = LocalTime.of(12, 12, 12);

        // 6、判断2个时间对象,是否相等,在前还是在后: equals isBefore isAfter
        System.out.println(lt15.equals(lt16)); // true
        System.out.println(lt15.isAfter(lt)); // false
        System.out.println(lt15.isBefore(lt)); // true

    }

}

LocalDateTime类的基本使用

public class Test3_LocalDateTime {
    public static void main(String[] args) {
        // 0、获取本地日期和时间对象。
        LocalDateTime ldt = LocalDateTime.now(); // 年 月 日 时 分 秒 纳秒
        System.out.println(ldt);

        // 1、可以获取日期和时间的全部信息
        int year = ldt.getYear(); // 年
        int month = ldt.getMonthValue(); // 月
        int day = ldt.getDayOfMonth(); // 日
        int dayOfYear = ldt.getDayOfYear();  // 一年中的第几天
        int dayOfWeek = ldt.getDayOfWeek().getValue();  // 获取是周几
        int hour = ldt.getHour(); //时
        int minute = ldt.getMinute(); //分
        int second = ldt.getSecond(); //秒
        int nano = ldt.getNano(); //纳秒

        // 2、修改时间信息:
        // withYear withMonth withDayOfMonth withDayOfYear withHour
        // withMinute withSecond withNano
        LocalDateTime ldt2 = ldt.withYear(2029);
        LocalDateTime ldt3 = ldt.withMinute(59);

        // 3、加多少:
        // plusYears  plusMonths plusDays plusWeeks plusHours plusMinutes plusSeconds plusNanos
        LocalDateTime ldt4 = ldt.plusYears(2);
        LocalDateTime ldt5 = ldt.plusMinutes(3);

        // 4、减多少:
        // minusDays minusYears minusMonths minusWeeks minusHours minusMinutes minusSeconds minusNanos
        LocalDateTime ldt6 = ldt.minusYears(2);
        LocalDateTime ldt7 = ldt.minusMinutes(3);

        // 5、获取指定日期和时间的LocalDateTime对象:
        // public static LocalDateTime of(int year, Month month, int dayOfMonth, int hour,
        //                                  int minute, int second, int nanoOfSecond)
        LocalDateTime ldt8 = LocalDateTime.of(2029, 12, 12, 12, 12, 12, 1222);
        LocalDateTime ldt9 = LocalDateTime.of(2029, 12, 12, 12, 12, 12, 1222);

        // 6、 判断2个日期、时间对象,是否相等,在前还是在后: equals、isBefore、isAfter
        System.out.println(ldt9.equals(ldt8));
        System.out.println(ldt9.isAfter(ldt));
        System.out.println(ldt9.isBefore(ldt));

        // 7、可以把LocalDateTime转换成LocalDate和LocalTime
        // public LocalDate toLocalDate()
        // public LocalTime toLocalTime()
        // public static LocalDateTime of(LocalDate date, LocalTime time)
        LocalDate ld = ldt.toLocalDate();
        LocalTime lt = ldt.toLocalTime();
        LocalDateTime ldt10 = LocalDateTime.of(ld, lt);

    }

}

10.13.3 JDK日期(时区)

image-20231214163329068

public class Test4_ZoneId_ZonedDateTime {
    public static void main(String[] args) {
        // 目标:了解时区和带时区的时间。
        // 1、ZoneId的常见方法:
        // public static ZoneId systemDefault(): 获取系统默认的时区
        ZoneId zoneId = ZoneId.systemDefault();
        System.out.println(zoneId.getId());
        System.out.println(zoneId);

        // public static Set<String> getAvailableZoneIds(): 获取Java支持的全部时区Id
        System.out.println(ZoneId.getAvailableZoneIds());

        // public static ZoneId of(String zoneId) : 把某个时区id封装成ZoneId对象。
        ZoneId zoneId1 = ZoneId.of("America/New_York");

        // 2、ZonedDateTime:带时区的时间。
        // public static ZonedDateTime now(ZoneId zone): 获取某个时区的ZonedDateTime对象。
        ZonedDateTime now = ZonedDateTime.now(zoneId1);
        System.out.println(now);

        // 世界标准时间了
        ZonedDateTime now1 = ZonedDateTime.now(Clock.systemUTC());
        System.out.println(now1);

        // public static ZonedDateTime now():获取系统默认时区的ZonedDateTime对象
        ZonedDateTime now2 = ZonedDateTime.now();
        System.out.println(now2);

        // Calendar instance = Calendar.getInstance(TimeZone.getTimeZone(zoneId1));
    }

}

10.13.4 JDK8日期(Instant类)

接下来,我们来学习Instant这个类。通过获取Instant的对象可以拿到此刻的时间,该时间由两部分组成:从1970-01-01 00:00:00 开始走到此刻的总秒数+不够1秒的纳秒数。

image-20231214163517238

该类提供的方法如下图所示,可以用来获取当前时间,也可以对时间进行加、减、获取等操作。

image-20231214163637341

作用:可以用来记录代码的执行时间,或用于记录用户操作某个事件的时间点。

/**

目标:掌握Instant的使用。
*/
public class Test5_Instant {
public static void main(String[] args) {
   // 1、创建Instant的对象,获取此刻时间信息
    Instant now = Instant.now(); // 不可变对象

        // 2、获取总秒数
        long second = now.getEpochSecond();
        System.out.println(second);

        // 3、不够1秒的纳秒数
        int nano = now.getNano();
        System.out.println(nano);

        System.out.println(now);

        Instant instant = now.plusNanos(111);

        // Instant对象的作用:做代码的性能分析,或者记录用户的操作时间点
        Instant now1 = Instant.now();
        // 代码执行。。。。
        Instant now2 = Instant.now();

        LocalDateTime l = LocalDateTime.now();

}
}

10.13.5 JDK8日期(格式化器)

DateTimeFormater可以对日期进行格式化和解析。它代替了原来的SimpleDateFormat类。

image-20231214164127123

需要用到的方法,如下图所示

image-20231214164141239

/**

目标:掌握JDK 8新增的DateTimeFormatter格式化器的用法。
*/
public class Test6_DateTimeFormatter {
public static void main(String[] args) {
    // 1、创建一个日期时间格式化器对象出来。
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
// 2、对时间进行格式化
LocalDateTime now = LocalDateTime.now();
System.out.println(now);

String rs = formatter.format(now); // 正向格式化
System.out.println(rs);

// 3、格式化时间,其实还有一种方案。
String rs2 = now.format(formatter); // 反向格式化
System.out.println(rs2);

// 4、解析时间:解析时间一般使用LocalDateTime提供的解析方法来解析。
String dateStr = "2029年12月12日 12:12:11";
LocalDateTime ldt = LocalDateTime.parse(dateStr, formatter);
System.out.println(ldt);
}
}

10.13.6 JDK8日期(Period类)

一个叫Period类、一个叫Duration类;这两个类可以用来对计算两个时间点的时间间隔。

其中Period用来计算日期间隔(年、月、日),Duration用来计算时间间隔(时、分、秒、纳秒)

image-20231214164445746

先来演示Period类的用法,它的方法如下图所示。可以用来计算两个日期之间相隔的年、相隔的月、相隔的日。只能两个计算LocalDate对象之间的间隔

image-20231214164502561

/**

目标:掌握Period的作用:计算机两个日期相差的年数,月数、天数。
*/
public class Test7_Period {
public static void main(String[] args) {
    LocalDate start = LocalDate.of(2029, 8, 10);
    LocalDate end = LocalDate.of(2029, 12, 15);

// 1、创建Period对象,封装两个日期对象。
Period period = Period.between(start, end);

// 2、通过period对象获取两个日期对象相差的信息。
System.out.println(period.getYears());
System.out.println(period.getMonths());
System.out.println(period.getDays());

}
}

10.13.7 JDK8日期(Duration类)

Duration类是用来表示两个时间对象的时间间隔。

可以用于计算两个时间对象相差的天数、小时数、分数、秒数、纳秒数;支持LocalTime、LocalDateTime、Instant等时间

image-20231214164611627

public class Test8_Duration {
    public static void main(String[] args) {
        LocalDateTime start = LocalDateTime.of(2025, 11, 11, 11, 10, 10);
        LocalDateTime end = LocalDateTime.of(2025, 11, 11, 11, 11, 11);
        // 1、得到Duration对象
        Duration duration = Duration.between(start, end);

        // 2、获取两个时间对象间隔的信息
        System.out.println(duration.toDays());// 间隔多少天
        System.out.println(duration.toHours());// 间隔多少小时
        System.out.println(duration.toMinutes());// 间隔多少分
        System.out.println(duration.toSeconds());// 间隔多少秒
        System.out.println(duration.toMillis());// 间隔多少毫秒
        System.out.println(duration.toNanos());// 间隔多少纳秒

    }

}

十一、算法和数据结构

11.1 Arrays类

11.1.1 Arrays基本使用

Arrays是操作数组的工具类,它可以很方便的对数组中的元素进行遍历、拷贝、排序等操作。

image-20231214165714201

/**

目标:掌握Arrays类的常用方法。
*/
public class ArraysTest1 {
public static void main(String[] args) {
    // 1、public static String toString(类型[] arr): 返回数组的内容
    int[] arr = {10, 20, 30, 40, 50, 60};
    System.out.println(Arrays.toString(arr));

// 2、public static 类型[] copyOfRange(类型[] arr, 起始索引, 结束索引) :拷贝数组(指定范围,包前不包后)
int[] arr2 = Arrays.copyOfRange(arr, 1, 4);
System.out.println(Arrays.toString(arr2));

// 3、public static copyOf(类型[] arr, int newLength):拷贝数组,可以指定新数组的长度。
int[] arr3 = Arrays.copyOf(arr, 10);
System.out.println(Arrays.toString(arr3));

// 4、public static setAll(double[] array, IntToDoubleFunction generator):把数组中的原数据改为新数据又存进去。
double[] prices = {99.8, 128, 100};
//                  0     1    2
// 把所有的价格都打八折,然后又存进去。
Arrays.setAll(prices, new IntToDoubleFunction() {
    @Override
    public double applyAsDouble(int value) {
        // value = 0  1  2
        return prices[value] * 0.8;
    }
});
System.out.println(Arrays.toString(prices));

// 5、public static void sort(类型[] arr):对数组进行排序(默认是升序排序)
Arrays.sort(prices);
System.out.println(Arrays.toString(prices));

}
}

11.1.2 Arrays操作对象数组

准备一个Student类,代码如下

public class Student implements Comparable<Student>{
    private String name;
    private double height;
    private int age;

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

    //...get、set、空参数构造方法、有参数构造方法...自己补全

    // 指定比较规则
    // this  o
    @Override
    public int compareTo(Student o) {
        // 约定1:认为左边对象 大于 右边对象 请您返回正整数
        // 约定2:认为左边对象 小于 右边对象 请您返回负整数
        // 约定3:认为左边对象 等于 右边对象 请您一定返回0
        /* if(this.age > o.age){
            return 1;
        }else if(this.age < o.age){
            return -1;
        }
        return 0;*/

        //上面的if语句,也可以简化为下面的一行代码
        return this.age - o.age; // 按照年龄升序排列
        // return o.age - this.age; // 按照年龄降序排列
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", height=" + height +
                ", age=" + age +
                '}';
    }

    public double getHeight() {
        return height;
    }

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

再写一个测试类

排序方法1:让Student类实现Comparable接口,同时重写compareTo方法。Arrays的sort方法底层会根据compareTo方法的返回值是正数、负数、还是0来确定谁大、谁小、谁相等。

public class Student implements Comparable<Student>{
    private String name;
    private double height;
    private int age;

    //...get、set、空参数构造方法、有参数构造方法...自己补全

    // 指定比较规则
    // this  o
    @Override
    public int compareTo(Student o) {
        // 约定1:认为左边对象 大于 右边对象 请您返回正整数
        // 约定2:认为左边对象 小于 右边对象 请您返回负整数
        // 约定3:认为左边对象 等于 右边对象 请您一定返回0
        /* if(this.age > o.age){
            return 1;
        }else if(this.age < o.age){
            return -1;
        }
        return 0;*/

        //上面的if语句,也可以简化为下面的一行代码
        return this.age - o.age; // 按照年龄升序排列
        // return o.age - this.age; // 按照年龄降序排列
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", height=" + height +
                ", age=" + age +
                '}';
    }

}

排序方法2:在调用Arrays.sort(数组,Comparator比较器);时,除了传递数组之外,传递一个Comparator比较器对象。Arrays的sort方法底层会根据Comparator比较器对象的compare方法方法的返回值是正数、负数、还是0来确定谁大、谁小、谁相等。代码如下

public class ArraysTest2 {
    public static void main(String[] args) {
        // 目标:掌握如何对数组中的对象进行排序。
        Student[] students = new Student[4];
        students[0] = new Student("蜘蛛精", 169.5, 23);
        students[1] = new Student("紫霞", 163.8, 26);
        students[2] = new Student("紫霞", 163.8, 26);
        students[3] = new Student("至尊宝", 167.5, 24);

        // 2、public static <T> void sort(T[] arr, Comparator<? super T> c)
        // 参数一:需要排序的数组
        // 参数二:Comparator比较器对象(用来制定对象的比较规则)
        Arrays.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                // 制定比较规则了:左边对象 o1   右边对象 o2
                // 约定1:认为左边对象 大于 右边对象 请您返回正整数
                // 约定2:认为左边对象 小于 右边对象 请您返回负整数
                // 约定3:认为左边对象 等于 右边对象 请您一定返回0

//                if(o1.getHeight() > o2.getHeight()){
//                    return 1;
//                }else if(o1.getHeight() < o2.getHeight()){
//                    return -1;
//                }
//                return 0; // 升序
                 return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
                // return Double.compare(o2.getHeight(), o1.getHeight()); // 降序
            }
        });
        System.out.println(Arrays.toString(students));
    }
}
博客内容均系原创,未经允许严禁转载!
您可以通过 RSS 订阅本站文章更新,订阅地址:https://www.sunqizheng1997.com/feed/什么是 RSS ?
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇