掘金 后端 ( ) • 2024-04-15 07:21

这里的java基础笔记是从面向对象开始做笔记的,像前面的数据类型这些东西各位自行百度吧,没啥难的

JDK11文档

static关键字

  1. 相当于vue的vuex,被static修饰的属性和方法可以在程序内通过类名.属性/方法访问到(也可以被对象.属性/方法访问到【不推荐】)
  2. static修饰的方法只能访问到static修饰的属性或别的方法,但是非static修饰的方法可以访问到被static修饰的属性和方法
  3. 同一个类中访问static修饰的属性和方法可以不带类名直接访问

代码块

静态代码块

package org.example.demo01;
public class Student {
    static int number = 80;
    // 静态代码块执行了
    static {
        System.out.println("静态代码块执行了");
        number += 10;
    }
}

在test.java里面如下

package org.example.demo01;

public class Test {
    public static void main(String[] args) {
        // 通过下面的打印也可以触发静态代码块的执行
        System.out.println(Student.number); // 90
    }
}
  1. 静态代码块格式为static{}
  2. 静态代码块在类加载时被执行,且只执行一次(即使上述代码多次打印叶志华会执行一次静态代码块里面的代码,适合做类的初始化操作,例如给类变量进行初始化赋值)

实例代码块

  1. 格式为 {}
  2. 每次创建对象时,执行示例代码块,并在构造器前执行
  3. 和构造器一样,都是用来完成对象的初始化赋值的,例如:对实例变量进行初始化赋值

单例模式设计

饿汉式单例

pubilc class A {
    // 2. 定义一个类变量记住类的一个对象
    private static A a = new A();
    // 1. 私有构造器,防止创建对象,只允许通过类名调用返回这个对象,保证一个类只能有一个对象
    private A(){}
    // 3. 定义一个类方法返回对象
    pubilc static A getObject(){
        return a;
    }
    
}

单例模式确保了一个类只能有一个对象

好处:例如电脑的任务管理器,无论启动多少次都只有一个窗口,它就是被设计为了单例模式的,避免浪费内存

继承

父类代码如下

package org.example.entends;

public class A {
     public static int num;
    
    public void getNum(){
        System.out.println("访问到了父类的非私有方法");
    }
}

子类代码如下

package org.example.entends;
public class B extends A{
    public void getANum(){
        // 打印父类的非私有成员
        System.out.println(num);
        // 调用父类的非私有方法
        getNum();
    }
}
  1. 子类不能调用父类私有的成员方法和私有成员变量
  2. 子类创建对象是由子类和父类共同完成的(即子类创建的对象可以调用子类和父类的非私有化的成员方法和成员变量)
  3. java是单继承的,java的类不支持多继承,但是支持多层继承

好处:将多个类可以复用的成员方法和成员变量提取出来作为父组件可以大大减少代码量

权限修饰符

在下面范围内能访问到的权限修饰符

  1. 在本类中:private,缺省,protected,public
  2. 同一个包下其他类里:缺省,protected,pubilc
  3. 任意包下的子类里(父子继承关系):protected,pubilc
  4. 任意包下的任意类里:public

方法重写

什么是方法重写?

  1. 当子类觉得父类的某个方法不好用,或者无法满足自己的需求时,子类可以重写一个方法名称,参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写
  2. 注意:重写后,方法的访问,Java会遵循就近原则
  3. 子类重写的父类的方法名要加@Override注解,如下所示:
父类有print()
    
子类继承父类重写print如下所示
    @Override
    pubilc void print(){
	    System.out.println("6666")
	}
  1. 子类重写的方法权限修饰符必须大于或者等于父类的权限

  2. 静态方法和私有方法不能被重写,否则报错

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

  • 先在子类局部范围找
  • 然后子类成员范围找
  • 然后父类成员范围找,如果还没找到就报错

子类构造器的特点

  • 子类的全部构造器都会先调用父类的构造器再调执行自己
  • 默认情况下,子类全部构造器的第一行代码都是super()写不写都有,它会调用父类的无参构造器
  • 如果父类没有无参数构造器,则我们在子类构造器第一行手写super(...),制定调用父类有参数构造器
package org.example.entends;
//  父类
class F{
    public F(){
        System.out.println("==父类无参构造器执行了==");
    }
    public F(String name){}
}
// 子类
class Z extends F{
    public Z(){
        // 在super里面加个参数就会去调用有参构造器就不会对无参构造器发起调用了
        super("靳");
        System.out.println("==子类的无参构造器执行了==");
    }
    public Z(String name){
        super("靳");
        System.out.println("子类的有参数构造器执行了");
    }
}

public class Test {
    public static void main(String[] args) {
        Z z = new Z();
        Z z2 = new Z("萧寂");
    }
}

this调用兄弟构造器

// 例如有个需求,默认如果学生不填写学校的话,默认为萧寂大学,但是目前有参构造器需要传入三个参数,其中需要传学校字段,那这个怎么做呢???就需要用到现在的调用兄弟构造器了,如下所示

public Student(String name, int age){
    this(name,age,"萧寂大学")
}

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

// 这样的话,创建学生如果只传入两个字段的话,由于方法重载的缘故,会执行第一个有参构造,第一个有参构造通过this关键字再去执行第二个,也就是调用了兄弟构造器

多态

什么是多态

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

具体代码体现

People p1 = new Student();
p1.run()
    
People p2 = new Teacher();
p2.run()
    
如上所示,老师和学生都是属于人,都有跑的这个行为,这就是多态(本质上来说就是相当于继承,子类对父类的方法进行了继承,然后创建子类对象,那么所有的子类对象都会有父类的方法,也可以在每个子类中对方法进行重写)

示例代码

package org.example.duotai;

// 父类
class People {
    public  String name = "父类People的名称";
    public void run(){
        System.out.println("人可以跑");
    }
}

// 学生类
class Student extends People{
    public  String name = "子类Student的名称";
    // 方法重写
    @Override
    public void run() {
        System.out.println("学生跑得贼快");
    }
}

// 老师类
class Teacher extends People{
    public  String name = "子类Teacher的名称";
    // 方法重写
    @Override
    public void run() {
        System.out.println("老师跑得贼快");
    }
}


public class Test {
    public static void main(String[] args) {
        // people这个就是多态的象征
        // 在这里Student和Teacher都是属于People的
        // 但是p1还是指向Teacher对象,p2还是指向Student对象的
        People p1 = new Teacher();
        p1.run();  // Student的run
        go(p1); // 将子类对象传递过去 
        System.out.println(p1.name); // 这里打印的还是父类的name
        
        People p2 = new Student();
        p2.run();  // Teacher的run
        go(p2); // 将子类对象传递过去
        System.out.println(p2.name); // 这里打印的还是父类的name

        // 以上可以总结
        // 对于子类的方法,编译看左边(父类类型),运行看右边(运行的是子类方法)
        // 对于子类的变量,编译看左边(父类类型),运行看左边(运行打印的是父类变量)
    }

	// 这里可以接收父类类型的参数,接受一切子类对象
	public static void go(People p){
        
    }
}

多态的前提

  • 有继承/实现关系;存在父类引用子类对象;存在方法重写

多态注意事项

  • 多态是对象、行为的多态,java中的属性(成员变量)不谈多态

多态的好处

  • 在多态形势下,右边对象是解耦合的,更便于扩展和维护。
  • 定义方法时,使用父类类型的形参,可以接收一切子类对象,扩展性更强,更便利

多态的弊端

  • 因为编译看左边的父类类型,因此子类能调用的方法只能来源于父类或者是子类重写的方法,不能调用子类独有的方法(例如非重写的方法都是子类独有的,如果想使用多态,又想调用自己独有的方法,可以看下面的类型转换的代码)

多态下的类型转换问题

  • 自动类型转换:父类 变量名 = new 子类(); 例如:People p = new Teacher();
  • 强制类型转换: 子类 变量名 = (子类)父类变量 例如:Teacher t = (Teacher)p;

强制类型转换的一个注意事项

  • 存在继承/实现关系就可以在编译阶段进行强制类型转换,编译阶段不会报错
  • 运行时,如果发现对象的真实类型与强转后的类型不同,就会报类型转换异常(ClassCastException)错误出来

如下示例代码

package org.example.duotai;

// 父类
class People {
    public  String name = "父类People的名称";
    public void run(){
        System.out.println("人可以跑");
    }
}

// 学生类
class Student extends People{
    public  String name = "子类Student的名称";
    // 方法重写
    @Override
    public void run() {
        System.out.println("学生跑得贼快");
    }

    public String getName(){
        return name;
    }
}

// 老师类
class Teacher extends People{
    public  String name = "子类Teacher的名称";
    // 方法重写
    @Override
    public void run() {
        System.out.println("老师跑得贼快");
    }
}


public class Test {
    public static void main(String[] args) {
        // 自动类型转换
        // 父类 变量名 = new 子类();
        People p1 = new Teacher();

        // 强制类型转换
        // 子类 变量名 = (子类)父类变量
        People p2 = new Student();
        Student s1 = (Student) p2;
        // 这时候转换成学生对象了类型了,就可以调用学生类的独有方法了
        System.out.println(s1.getName());

        // 强制类型转换存在的问题,如下
        Teacher t1 = (Teacher) p2; // 可以看出编译时没报错,但是运行起来就报错了(ClassCastException),因为p2是学生类型,不能转成老师类型
    }
}

为了解决编译时这种异常,JAVA建议

  • 使用instanceof关键字,判断当前对象的真实类型,再进行强转
p instanceof Student
    
    // 如下判断完类型再去强转
    if(p2 instanceof Student){
        Student s1 = (Student) p2;
    }

final

  • final关键字是最终的意思,可以修饰(类,方法,变量)
  • 修饰类:该类为最终类,特点是不能被继承了
  • 修饰方法:该方法被称为最终方法,特点是不能被重写了
  • 修饰变量:该变量只能被赋值一次

final修饰变量的注意

  • final修饰基本类型的变量,变量存储的数据不能被改变
  • final修饰引用类型的变量,变量存储的地址不能被改变,但地址所指向对象的内容是可以被改变的

抽象类(特殊的父类)

抽象类注意事项、特点

  • 抽象类不一定有抽象方法,有抽象方法的类一定是抽象类
  • 类该有的成员(成员变量,方法,构造器)抽象类都可以有
  • 抽象类最主要的特点:抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现。
  • 一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。

抽象类的场景和好处

  • 父类知道每个子类都要做某个行为,但每个子类要做的情况不一样,父类就定义成抽象方法,交给子类去重写实现我们设计这样的抽象类,就是为了 更好的支持多态。

接口

package org.example.接口;

// interface定义接口
interface A {
    String SCHOOL_NAME = "萧寂大学";
    // 上面的代码等同于下面这个,可以发现 public static final 是灰色的
    // 也就是说在接口内部定义的变量就默认是常量,不可被改变
    //  public static final String SCHOOL_NAME = "萧寂大学";

    // 成员方法(默认是抽象方法,可以被重写,不能加方法体)
    void test();
    // 等同于下面代码 public abstract在接口内部默认添加的
    // public abstract void test();

    // 接口内不能有构造器或者静态代码块
    // 接口不能被创建对象
}

class B implements A{

    @Override
    public void test() {
        // 重写A接口的方法
    }
}


class Test {
    public static void main(String[] args) {
        // 可以发现这个接口中的变量能被类名.调用到
        System.out.println(A.SCHOOL_NAME);
    }
}
  • 接口是通过implements实现的,不是继承来的,实现接口的类称为实现类,也就是接口的子类
  • 修饰符 class 实现类名 implements 接口1,接口2,接口3....
  • 通过上面的格式可以看出一个类可以实现多个接口,实现类实现多个接口必须重写完全部接口的全部抽象方法,否则实现类需要定义为抽象类

案例

package org.example.接口;

interface C {
    void testc1();

    void testc2();
}

interface D {
    void testd1();

    void testd2();
}

class E implements C, D {

    @Override
    public void testc1() {

    }

    @Override
    public void testc2() {

    }

    @Override
    public void testd1() {

    }

    @Override
    public void testd2() {

    }
}

class test {
    public static void main(String[] args) {
        E e = new E();
        e.testd1();
    }
}

接口的好处

  • 弥补了类单继承的不足,一个类同时可以实现多个接口
  • 让程序可以面向接口编程,这样程序员就可以灵活方便的切换各种业务实现,代码如下所示
package org.example.接口;

interface Driver {
    void drive();
}

interface Singer {
    void sing();
}

class Student {
    public void getStudent(){
        System.out.println("我是学生");
    }
}

class E extends Student implements Driver, Singer {

    @Override
    public void drive() {
        // 司机开车
        System.out.println("司机开车");
    }

    @Override
    public void sing() {
        // 歌手唱歌
        System.out.println("歌手唱歌");
    }
}

class test {
    public static void main(String[] args) {
        // 由于extends只能单继承,所以一个类只能有一个父类,但是有了接口后,子类可以任意去切换,实现了自由

        // 希望E是学生类型(不能调用司机和歌手的方法)
        Student student = new E();
        student.getStudent();
        
        // 希望E是司机类型(不能调用歌手的方法,否则报错)
        Driver driver = new E();
        driver.drive();
        // 希望E是歌手(不能调用司机方法,否则报错)
        Singer singer = new E();
        singer.sing();
    }
}

同理,当多个类实现同一个接口时,创建任意类对象时都会重写这个接口的方法,从而实现可以灵活的更换某个对象,例如AB俩个实现类都实现了C接口,如果new A();调用A.getXXX(),但是某天不需要是A了,需要改成B,那这里直接把new的对象换成B就好了

jdk8之后新增的三种接口的方法

package org.example.接口;

interface F {
    // 1.默认方法,必须使用default修饰,默认会被public修饰,可以省略public
    // jdk8开始支持的
    // 默认方法就是实例方法,对象的方法,必须使用实现类对象可以访问到
    // 好处:例如很多实现类实现了这个接口,某天后需要对接口新增功能,则所有实现类都必须重写接口,维护成本较大,通过定义默认方法,则所有的实现类都会默认可以调用这个方法,不需要一个一个重写了,维护成本低
    public default void test1(){
        System.out.println("默认方法");
        test2(); // 这里可以调用私有方法
    };

    // 2.私有方法,必须使用private进行修饰
    // jdk9开始支持的
    // 私有方法也是实例方法 - 对象的方法
    // 由于是私有的,所以应该在当前接口内被调用
    private void test2(){
        System.out.println("私有方法");
    }

    // 3.静态方法
    // jdk8之后支持
    // 必须使用static修饰,默认会被public修饰
    // 可以通过接口名.调用,如下 F.test3()
    public static void test3(){
        System.out.println("静态方法");
    }
}

class G implements F{

}

class TestE {
    public static void main(String[] args) {
        G g = new G();
        g.test1(); // 调用成员方法

        F.test3(); // 调用静态方法
    }
}

接口的多继承

  • 一个接口可以继承多个接口
package org.example.接口;

interface H {
    void test1();
}
interface I {
    void test2();
}
interface L {
}

// 接口的多继承

interface K extends H,I,L {
}

// 好处
// 如下代码:当项目中接口比较多,就需要一个一个实现比较麻烦
// 当一个接口继承了其它接口的话,直接就可以只实现这一个接口就好
// 实现K接口后,必须重写K继承的所有接口的方法(也是验证了实现K接口就实现了其它接口的目的)
//class M implements H,I,L{};
class M implements K{
    @Override
    public void test1() {

    }

    @Override
    public void test2() {

    }
};

接口其他注意事项(了解)

  • 1、一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承。
  • 2、一个类实现多个接因,如果多个接口中存在方法签名冲突,则此时不支持多实现。
  • 3、一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优先用父类的。
  • 4、一个类实现了多个接口,多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可。

内部类

成员内部类

package org.example;

class ChengYuanNeiBuLei {
    private int age = 99;
    // 成员内部类
    public class Inner{
        private String name;
        private int age = 88;
        // jdk16才开始定义静态成员的
        // public static String schoolname;

        // 内部类同样有构造器和成员方法
        public Inner() {
        }

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

        /**
         * 获取
         * @return name
         */
        public String getName() {
            return name;
        }

        /**
         * 设置
         * @param name
         */
        public void setName(String name) {
            this.name = name;
        }

        public String toString() {
            return "Inner{name = " + name + "}";
        }

        // 了解内部类的访问特点
        // 在方法内定义age = 77  内部类定义age = 88 外部类定义age = 99
        // 依次打出77 88 99
        public void test (){
            int age = 77;
            System.out.println(age); // 77
            // 访问内部类自身的属性
            System.out.println(this.age); // 88
            // 访问外部类的属性(外部类名.this.属性)
            System.out.println(ChengYuanNeiBuLei.this.age); // 99
        }
    }
}

class Test{
    public static void main(String[] args) {
        // 了解成员内部类和其特点
        // 语法如下
        // 外部类.内部类 变量名 = 外部类对象.内部类对象
        ChengYuanNeiBuLei.Inner in = new ChengYuanNeiBuLei().new Inner();
        in.setName("萧寂");
        System.out.println(in.getName());
        // 调用test方法
        in.test();
    }
}

静态内部类

package org.example.JingTaiNeiBuLei;

class JingTaiNeiBuLei {
    private int age;
    public static String schoolName = "萧";
    // 静态内部类(特点和外部类没啥区别)
    public static class Inner {
        private String name;
        public static int o;

        public Inner() {
        }

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

        /**
         * 获取
         *
         * @return name
         */
        public String getName() {
            return name;
        }

        /**
         * 设置
         *
         * @param name
         */
        public void setName(String name) {
            this.name = name;
        }

        public String toString() {
            return "Inner{name = " + name + "}";
        }

        // 静态内部类访问外部成员方法
        // 静态内部类可以直接访问外部类的静态成员,不能访问外部类的实例成员
        public static void test(){
            System.out.println(schoolName); // 外部schoolName是可以直接访问
            // System.out.println(age); // 报错,不能被访问,因为age是对象的变量,不是静态变量
        }
    }
}

class Test {
    public static void main(String[] args) {
        // 了解静态内部类
        // 静态内部类和之前的静态变量和静态方法一样可以被类名.调用
        JingTaiNeiBuLei.Inner in = new JingTaiNeiBuLei.Inner();
        in.setName("萧寂");
        System.out.println(in.getName());
        // 调用方法
        in.test();
    }
}

局部内部类

  • 定义在方法中,代码块中,构造器等执行体中
  • 语法如下(鸡肋语法,看看就好,不做过多描述,开发不常用)
public class Test {
    public static void main (String[] args) {
        
    }
    public static void  go(){
        class A{
            
        }
        abstract class B{
            
        }
        interface C{
            
        }
    }
}

匿名内部类(重点,用处较多)

  • 就是一种特殊的局部内部类;所谓匿名;指的就是不需要为这个类声明名字
new 类或接口(参数值...){
    类体(一般是方法重写);
};

案例代码

package 匿名内部类;

abstract class Animail {
    public void cry(){
        System.out.println("动物的叫声");
    }
}

class Cat extends Animail{
    @Override
    public void cry(){
        System.out.println("猫的叫声:喵喵喵~~");
    }
}

class Tset{
    public static void main(String[] args) {
        // 在开发中,我们使用猫这个对象会使用一下方法
        Animail cat = new Cat();
        cat.cry();

        /**
         * 但是当我们开发功能比较多的时候,
         * 我们不可能需要某一模块就专门创建一个类
         * 因此就有了匿名内部类
         * 格式如下:
         */
        Animail cat2 = new Animail() {
            @Override
            public void cry() {
                System.out.println("猫的叫声:喵喵喵~~");
            }
        };
        cat2.cry();
        // 上面代码new Animail() {}相当于创建了Animail的子类对象并继承了Animail,会去重写Animail里面的抽象方法,并去执行
    }
}

匿名内部类大多数是作为参数传递到方法,在方法内部完成一系列操作的

匿名内部类可以方便快速的创建出子类对象

枚举类

  • 一种特殊的类

枚举类的格式:

修饰符 enum 枚举类名{
    名称1,名称2,...;
    其他成员...;
}
  • 枚举类的第一行只能罗列一些名称,这些名称都是常量,并且每个常量记住的都是枚举类的一个对象。
  • 枚举类的构造器都是私有的(写不写都只能是私有的),因此,举类对外不能创建对象。
  • 枚举都是最终类,不可以被继承。
  • 枚举类中,从第二行开始,可以定义类的其他各种成员。编译器为枚举类新增了几个方法,并且枚举类都是继承:iava.lang.Enum类的,从enum类也会继承到一些方法
package org.example.枚举类;

enum A {
    // 注意:枚举类的第一行必须罗列的是枚举对象的名字
    X,Y,Z;
    private String name;

    public String getName() {
        return name;
    }

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

class test{
    public static void main(String[] args) {
       A a1 = A.X;
       System.out.println(a1);

       // 1.枚举类的构造器都是私有的,不能对外创建对象
       //  A a = new A();

        // 2.枚举类的第一行都是常量,记住的是枚举类的对象
        A a2 = A.Y;

        // 3.枚举类提供了一些额外的API
        A[] as = A.values(); // 拿到全部对象
        A a3 = A.valueOf("X"); // 根据常量名得到对象
        System.out.println(a3.name()); // 拿到名字X
        System.out.println(a3.ordinal()); // 拿到索引
    }
}

抽象枚举

package org.example.枚举类;

public enum B {
    // 这里的属于匿名内部类类似的写法
    // 本质调用的是无参构造器
    // 传入参数的话就是调用有参构造器
    // 内部重写的还是抽象方法(快捷键打个 方法名 回车)
    X(){
        @Override
        public void go(){
            System.out.println("X的内容");
        }
    },Y("萧寂"){
        @Override
        public void go() {
            System.out.println(getName()+"在跑~~");
        }
    };

    private String name;
    
    // 以下的构造器,成员方法都是正常的
    // 提功有参构造器必须同时声明无参构造器,否则上面的X,Y声明报错
    B(){

    }

    B(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    // 定义抽象方法
    public abstract void go();
}

class Test{
    public static void main(String[] args) {
        B y = B.Y;
        // 调用y对象的go方法
        y.go();

        B x = B.X;
        // 调用X对象的go方法
        x.go();
    }
}

包装类

基本数据类型 对应的包装类(引用数据类型) byte Byte short Short int Integer long Long char Character float Float double Double boolean Boolean

示例代码

package org.example.包装类;

import java.util.ArrayList;

public class A {
}

class Test{
    public static void main(String[] args) {
        // 掌握包装类的使用
        // 将12转换成对象,打印出了对象的值
        Integer a = Integer.valueOf(12);
        System.out.println(a);

        // 自动装箱(自动把基本数据类型转换成对象)
        Integer a1 = 24;
        System.out.println(a1);

        // 自动拆箱(自动把包装类型的对象转换成基本数据类型)
        int a2 = a1;
        System.out.println(a2);

        // 泛型和集合只支持引用数据类型,不支持基本数据类型
        // ArrayList<int> list = new ArrayList<>(); // 报错
        ArrayList<Integer> list = new ArrayList<>();
        list.add(12); // 自动装箱
        list.add(13); // 自动装箱

        int rs = list.get(1); // 自动拆箱
        System.out.println(rs);

    }
}

包装类的其他常见操作

  • 可以把基本类型的数据转换成字符串类型.
  • 可以把字符串类型的数值转换成数值本身对应的数据类型

示例代码

        // 1. 把基本类型的数据转换成字符串类型.
        Integer i = 23;
        String rs1 = Integer.toString(i);
        System.out.println(rs1 + "1"); // 231,23转成了字符串再做拼接

        String rs2 = i.toString();
        System.out.println(rs2 + "1"); // 打印结果同上

        String rs3 = i + "";
        System.out.println(rs3 + "1"); // 打印结果同上

        System.out.println("=====================");
        // 2. 把字符串类型的数值转换成数值本身对应的数据类型
        String ageStr = "29";
        // int age =  Integer.parseInt(ageStr); // 29
        // 下面这个和上面这个有相同效果并且支持int和double类型
        int age =  Integer.valueOf(ageStr); // 29
        System.out.println(age+1); // 30

        String scoreStr = "99.5";
        // double score =  Double.parseDouble(scoreStr); // 29
        double score =  Double.valueOf(scoreStr); // 29
        System.out.println(score+0.5); // 30

泛型

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

格式:

ArrayList<String> arrayList = new ArrayList<>();
String是约束类型,否则集合内任意类型都可以被加入,造成数据混乱

作用:

  • 泛型提供了在编译阶段约束所能操作的数据类型,并自动进行检查的能力!这样可以避免强制类型转换,及其可能出现的异常

泛型本质就是把具体的数据类型作为参数传给类型变量

泛型类

泛型类定义格式

修饰符 class 类名<类型变量,类型变量,...> {}

public class Student<E> {}

自定义封装一个泛型类

package org.example.泛型;

// 建议都使用大写的EDKV这种定义
// 1.简单定义泛型
class MyArrayList<E>{
    public boolean add(E e){
        return true;
    }

    public E get(int index){
        return null;
    }

}
// 2.定义多个泛型的类型
class MyArrayList2<E,T>{
    public boolean add(E e,T t){
        return true;
    }
}

// 3.使用继承关系的泛型
// 定义父类
class Animail{}
// 子类继承
class Dog extends Animail{}
class Cat extends Animail{}
// 使用继承关系的泛型类型
class MyArrayList3<E extends Animail>{
    public boolean add(E e){
        return true;
    }
}

class Test {
    public static void main(String[] args) {
        // 掌握泛型类的定义和使用
        // 1.自定义泛型的定义和使用
        MyArrayList<String> list = new MyArrayList<>();
        list.add("aaa");
        // list.add(123); // 报错(参数类型错误)

        // 2.使用多个类型约束的泛型
        MyArrayList2<String,Number> list2 = new MyArrayList2<>();
        list2.add("aaa",111);
        // list2.add("aaa"); // 报错(参数不够)
        // list2.add(123); // 报错(参数不够,且第一个类型为string)
        // list2.add(111,"aaa") // 报错,参数类型错误
        
        // 3.使用继承关系的泛型
        // 则下面的泛型类型必须是Animail本身或者是继承了Animail的子类类型,否则报错
        MyArrayList3<Cat> list3 = new MyArrayList3<>();
        Cat cat = new Cat();
        list3.add(cat); 
    }
}

泛型接口

泛型接口的定义格式

修饰符 interface 接口名<类型变量,类型变量,...> {}

public interface A<E,D,K,L,...> {}

示例代码

package org.example.泛型;

import java.util.ArrayList;

interface Data<T> {
    void add(T t);
    ArrayList<T> getByName(String name);
}

// 定义学生和老师类
class Student{}
class Teacher{}

// 老师实现类
class TeacherData implements Data<Teacher>{

    @Override
    public void add(Teacher teacher) {

    }

    @Override
    public ArrayList<Teacher> getByName(String name) {
        return null;
    }
}
// 学生实现类
class StudentData implements Data<Student>{
    @Override
    public void add(Student student) {

    }

    @Override
    public ArrayList<Student> getByName(String name) {
        return null;
    }
}

class Test1{
    public static void main(String[] args) {
       // 泛型接口的定义和使用
       // 场景:系统需要处理学生和老师的数据,需要提供两个功能,保存对象数据,根据名称查询数据
        
    }
}

泛型方法

简单的泛型方法使用

package org.example.泛型;

class C {
}

class Test3{
    public static void main(String[] args) {
        // 掌握泛型方法的定义和使用
        System.out.println(test("java"));
        System.out.println(test(123));
        System.out.println(test(true));
    }

    public static <T> T test(T t){
        return t;
    }
}

示例代码

package org.example.泛型;

import java.util.ArrayList;

// 定义汽车父类
class Car {
}

// 定义宝马车
class BMW extends Car{

}
// 定义奔驰车
class BENZ extends Car{

}

class Test3{
    public static void main(String[] args) {
        // 需求:所有的汽车可以一起参加比赛
        // 创建arrayList
        ArrayList<Car> cars = new ArrayList<>();
        cars.add(new BENZ());
        cars.add(new BMW());
        go(cars);

        ArrayList<BMW> bmws = new ArrayList<>();
        bmws.add(new BMW());
        bmws.add(new BMW());
        go(bmws);

        ArrayList<BENZ> benzs = new ArrayList<>();
        benzs.add(new BENZ());
        benzs.add(new BENZ());
        go(benzs);
    }

    // 限定了所有的类型都是继承或者直接为Car的,否则就报错进不来
    // public static <T extends Car> void go(ArrayList<T> cars){}

    // 下面这个也不报错,在ArrayList里面?类型代表一切类型,这样相当于不加类型,任何类型都可以进来了
    // public static void go(ArrayList<?> cars){}

    // 下面这个也可以实现同等效果,将ArrayList的?类型的范围缩小了,只限制于Car及其子类
    public static void go(ArrayList<? extends Car> cars){}
}

注意事项:

  • 泛型是工作在编译阶段的,一旦程序编译成class文件,class文件就不存在泛型了,这就是泛型擦除
  • 泛型是不支持基本数据类型的,只能支持对象类型(引用数据类型)

常用API

object

  • object类是java中所有类的祖宗类,因此,Java中所有类的对象都可以直接使用object类中提供的一些方法

常见的:toString() 、equals()

toString()和equals()方法需要在类里面写完成员变量后再重写一下,equals重写会有弹出框一直无脑下一步就好,否则打印或者比较的都是地址值

由于Object的APi太多了,各位可以查询api文档或者询问GPT怎么使用

这里贴一下JDK11的在线文档,打开就是object的所有方法

  • 1、0bject中tostring方法的作用是什么?存在的意义是什么?
    • 基本作用:返回对象的字符串形式。
    • 存在的意义:让子类重写,以便返回子类对象的内容
  • 2、0bject中equals方法的作用是什么?存在的意义是什么?
    • 基本作用:默认是比较两个对象的地址是否相等,
    • 存在的意义:让子类重写,以便用于比较对象的内容是否相同。

objects

  • equals接收两个参数为两个对象,与object自带的equals相比,当相比较的数据其中一个为null时,传统的euqals会直接抛出异常,而objects的equals会返回false代表不相等
  • isNull 比较对象是否为null
  • nonNull 比较对象不为null

StringBuilder

  • StringBuilder代表可变字符串对象,相当于是一个容器,它里面装的字符串是可以改变的,就是用来操作字符串的,。
  • 好处:StringBuilder比String更适合做字符串的修改操作,效率会更高,代码也会更简洁。

示例代码

package org.example.StringBuilder;
class Test{
    public static void main(String[] args) {
        // 搞清楚StringBuilder的用法和作用
        StringBuilder s = new StringBuilder("Ji");
        // 1.拼接内容(向后追加)
        s.append(12);
        s.append("萧寂");
        s.append(true);
        System.out.println(s);
        // 支持链式编程
        s.append("哈哈哈").append("我").append("爱你");
        // 打印
        System.out.println(s);

        // 2.翻转
        s.reverse();
        System.out.println(s);

        // 3. 返回字符串长度
        System.out.println(s.length());

        // 4. StringBuilder对象又转成String类型
        String rs =  s.toString();
        System.out.println(rs);

    }
}

为什么操作字符串建议使用StringBuilder,而不是原来学过的String

package org.example.StringBuilder;

public class Test1 {
    public static void main(String[] args) {
        // 掌握StringBuilder的好处.
        // 需求: 要拼接100万次abc.

        // String rs = "";
        // for (int i = 0; i < 1000000; i++) {
        //     rs= rs +"abc";
        // }
        // // 可以发现等待时间会很长,底层要不断地去计算
        // System.out.println(rs);

        // 使用StringBuilder演示
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 1000000; i++) {
            sb.append("abc");
        }
        // 可以发现几乎1-2秒就出来了结果,非常快
        System.out.println(sb);
    }
}
  • 对于字符串的操作,如频繁的拼接、修改等,建议用StringBuilder,效率更高!
  • 注意:如果操作字符串较少,或者不需要操作,以及定义字符串变量,还是建议用String

StringBuffer

StringBuffer与StringBuilder

  • StringBuffer的用法与StringBuilder是一模一样的
  • 但 StringBuilder是线程不安全的 StringBuffer是线程安全的

Stringjoiner

  • JDK8开始才有的,跟StringBuilder一样,也是用来操作字符串的,也可以看成是一个容器,创建之后里面的内容是可变的。
  • 好处:不仅能提高字符串的操作效率,并且在有些场景下使用它操作字符串,代码会更简洁
package org.example.StringJoiner;

import java.util.StringJoiner;

public class Test {
    public static void main(String[] args) {
        // 掌握Stringjoiner的使用

        // 默认携带三个参数
        // 参数一:间隔符号
        // 参数二:开始符号
        // 参数三:结尾符号
        // 参数二和参数三可以省略(因为是起始符号因此会一起省略或者一起出现)
        StringJoiner s = new StringJoiner(", ","[","]");
        s.add("java1");
        s.add("java2");
        s.add("java3");
        s.add("java4");
        System.out.println(s); // [java1, java2, java3, java4]

    }
}

Math

  • 代表数学,是一个工具类,里面提供的都是对数据进行操作的一些静态方法
package org.example.Math;

public class Test {
    public static void main(String[] args) {
        // 绝对值
        System.out.println(Math.abs(-12)); // 12
        System.out.println(Math.abs(123)); // 123
        System.out.println(Math.abs(-3.14)); // 3.14

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

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

        // 四舍五入
        System.out.println(Math.round(3.4999)); // 3
        System.out.println(Math.round(3.50001)); // 4

        // 取最大值
        System.out.println(Math.max(10,20)); // 20

        // 取最小值
        System.out.println(Math.min(10,20)); //10

        // 取次方
        System.out.println(Math.pow(2,3)); // 2的3次方 8.0
        System.out.println(Math.pow(3,2)); // 3的2次方 9.0

        // 取随机数
        System.out.println(Math.random()); // [0.0,1.0)范围内任意随机数
    }
}

System

  • System代表程序所在的系统,也是一个工具类。
package org.example.System;

public class Test {
    public static void main(String[] args) {
        // System常见两个方法
        // 终止当前运行的Java虚拟机
        // 该参数用作状态代码;按照惯例,非零状态代码表示异常终止
        // System.exit(0); // 人为的终止虚拟机
        // System.out.println("123"); // 无打印,虚拟机直接终止了

        // 获取当前系统的时间(毫秒值)
        long time = System.currentTimeMillis();
        System.out.println(time);
    }
}

Runtime

  • 代表程序所在的运行环境。
  • Runtime是一个单例类。
package org.example.Runtime;

import java.io.IOException;

public class Test {
    public static void main(String[] args) throws IOException, InterruptedException {
        // 了解Runtime常见方法

        // 获取与当前Java应用程序关联的运行时对象
        Runtime r = Runtime.getRuntime();

        // 终止当前运行的Java虚拟机
        // 该参数用作状态代码;按照惯例,非零状态代码表示异常终止
        // r.exit(0);
        // System.out.println(11); // 关闭虚拟机后打印不出来

        //获取虚拟机能够使用的处理器数
        System.out.println(r.availableProcessors()); // 本人电脑是20

        // 获取Java虚拟机中的内存总量
        System.out.println(r.totalMemory()/1024.0/1024.0+"MB"); // 1024 = 1K 1024*1024 = 1M

        // 返回Java虚拟机中的可用内存量
        System.out.println(r.freeMemory()/1024.0/1024.0+"MB");

        // 启动某个应用程序,并返回代表该程序的对象
        Process p = r.exec("D:\\ruanjian\\FSCapture\\FSCapture.exe");
        Thread.sleep(5000); // 让程序在这里停止5秒继续往下走
        p.destroy(); // 销毁,关闭程序
    }
}

BigDecimal

  • 用于解决浮点型运算时,出现结果失真的问题。
  • 浮点型运算时,直接加减乘除可能会出现运算结果失真
package org.example.BigDecimal;

import java.beans.beancontext.BeanContext;
import java.math.BigDecimal;

public class Test {
    public static void main(String[] args) {
        // 解决小数运算失真的问题
        double a = 0.1;
        double b = 0.2;
        System.out.println(a+b); // 0.30000000000000004

        // BigDecimal有俩类型,Double和String类型
        // 开发常用String类型的,不推荐使用Double

        // 1.把他们变成字符串封装成BigDecimal对象来运算
        // BigDecimal a1 = new BigDecimal(Double.toString(a));
        // BigDecimal b1 = new BigDecimal(Double.toString(b));

        // 下面这两行代码等同于上面这两行(推荐用以下方式:把小数转换成字符串再得到BigDecimal对象来使用(更简洁))
        BigDecimal a1 = BigDecimal.valueOf(a);
        BigDecimal b1 = BigDecimal.valueOf(b);

        // a1 + b1 加
        BigDecimal c1 = a1.add(b1);
        System.out.println(c1); // 0.3

        // a1 - b1 减
        BigDecimal d1 = a1.subtract(b1);
        System.out.println(d1); // 0.1

        // a1 * b1 乘
        BigDecimal e1 = a1.multiply(b1);
        System.out.println(e1); // 0.02

        // a1 / b1 除
        // 案例一这个除法有问题,如 1 / 3 是一个无限小数,程序不知道保留几位就报错了
        BigDecimal G1 = BigDecimal.valueOf(1);
        BigDecimal J1 = BigDecimal.valueOf(3);
        // BigDecimal f1 = G1.divide(J1);
        // System.out.println(f1); // 报错

        // 除法建议使用以下方法,在参数2这里,明确要保留几位,这样就不会有问题
        // 当然还有参数三,可以指定是否使用四舍五入等方式(这里不细分,各位可以去百度)
        BigDecimal f1 = G1.divide(J1,2);
        System.out.println(f1);

        // 将上面的结果BigDecimal类型的转成double类型,这里以除法结果为例
        double rs = f1.doubleValue();
        System.out.println(rs);
    }
}

日期时间

JDK8之前传统的日期、时间

Date和SimpleDateFormat

  • 代表日期和时间以及时间的格式化
package org.example.Date;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Test {
    public static void main(String[] args) throws ParseException {
        // 创建一个Data的对象,代表系统当前时间信息的
        Date d = new Date();
        System.out.println(d); // Sun Apr 07 21:30:53 CST 2024

        // 得到时间毫秒值
        long time = d.getTime();
        System.out.println(time); // 1712496653452

        // 把时间毫秒值转换为日期对象:2s之后的时间是多少
        time+=2*1000;
        Date d2 = new Date(time);
        System.out.println(d2); // Sun Apr 07 21:30:55 CST 2024

        // 把日期对象的时间通过setTime的方法进行修改
        Date d3 = new Date();
        d3.setTime(time);
        System.out.println(d3); // Sun Apr 07 21:30:55 CST 2024

        // 将时间日期格式化
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE a");
        System.out.println(sdf.format(time)); // 2024-04-07 21:30:55 周日 下午

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

Calendar

  • 代表的是系统此刻时间对应的日历。
  • 通过它可以单独获取、修改时间中的年、月、日、时、分、秒等
package org.example.Calendar;

import java.util.Calendar;
import java.util.Date;

public class Test {
    public static void main(String[] args) {
        // 1. 得到系统此刻时间对应的日历对象
        Calendar now = Calendar.getInstance();
        System.out.println(now); // java.util.GregorianCalendar[time=1712497323373,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=31,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2024,MONTH=3,WEEK_OF_YEAR=15,WEEK_OF_MONTH=2,DAY_OF_MONTH=7,DAY_OF_YEAR=98,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=1,AM_PM=1,HOUR=9,HOUR_OF_DAY=21,MINUTE=42,SECOND=3,MILLISECOND=373,ZONE_OFFSET=28800000,DST_OFFSET=0]

        // 2.获取日历中的某个信息
        System.out.println(now.get(Calendar.YEAR)); // 2024
        System.out.println(now.get(Calendar.DAY_OF_YEAR)); // 一年中的第几天

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

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

        // 5.修改日历中的某个信息
        now.set(Calendar.MONTH,9); // 修改月份为10月份
        now.set(Calendar.DAY_OF_YEAR,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,-10);
        System.out.println(now);

    }
}

JDK8开始新增的日期、时间

  • LocalDate:年,月,日,星期
package org.example.JDK8之后新增的时间日期;
import java.time.LocalDate;
public class Test {
    public static void main(String[] args) {
        // 获取本地日期对象
        LocalDate ld = LocalDate.now(); // 年月日
        System.out.println(ld); // 2024-04-09

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

        // 直接修改某个信息
        LocalDate ld2 = ld.withYear(2099); // 返回值为新日期对象,并不会修改原来日期对象
        System.out.println(ld2); // 2099-04-09
        System.out.println(ld); // 2024-04-09

        System.out.println(ld.withMonth(2)); // 修改月份 2024-02-09

        // 把某个信息加多少
        System.out.println(ld.plusYears(2)); // 当前基础加2年
        System.out.println(ld.plusMonths(2)); // 当前基础加两个月

        // 把某个信息减多少
        System.out.println(ld.minusYears(2)); // 当前基础减2年
        System.out.println(ld.minusMonths(2)); // 当前基础减两个月

        // 获取指定日期的LocalDate对象
        LocalDate ld3 = LocalDate.of(2099,12,12);
        LocalDate ld4 = LocalDate.of(2099,12,12);
        System.out.println(ld3);

        // 判断2个日期对象是否相等,在前还是后
        System.out.println(ld3.equals(ld4)); // true 相等
        System.out.println(ld3.isAfter(ld4)); // false ld3不在ld4后面
        System.out.println(ld3.isBefore(ld4)); // false ld3也不在ld4前面
    }
}
  • LocalTime:时,分,秒,纳秒
package org.example.JDK8之后新增的时间日期;

import java.time.LocalTime;

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

        // 获取时间中的信息
        System.out.println(lt.getHour()); // 时
        System.out.println(lt.getMinute()); // 分
        System.out.println(lt.getSecond()); // 秒
        System.out.println(lt.getNano()); // 纳秒

        // 修改时间
        System.out.println(lt.withHour(10));
        System.out.println(lt.withMinute(10));
        System.out.println(lt.withSecond(10));
        System.out.println(lt.withNano(10));

        // 加多少
        System.out.println(lt.plusHours(10));
        System.out.println(lt.plusMinutes(10));
        System.out.println(lt.plusSeconds(10));
        System.out.println(lt.plusNanos(10));

        // 减多少
        System.out.println(lt.minusHours(10));
        System.out.println(lt.minusMinutes(10));
        System.out.println(lt.minusSeconds(10));
        System.out.println(lt.minusNanos(10));
        
        // 获取指定时间的对象
        LocalTime lt2 = LocalTime.of(12,12,12);
        LocalTime lt3 = LocalTime.of(12,12,12);

        // 判断两个时间对象 是否相等 在前还在在后
        System.out.println(lt2.equals(lt3)); // true 相等
        System.out.println(lt2.isAfter(lt3));// false lt2不在lt3后面
        System.out.println(lt2.isBefore(lt3));// false lt2不在lt3前面
    }
}
  • LocalDateTime:年,月,日,星期,时,分,秒,纳秒(本地日期)
package org.example.JDK8之后新增的时间日期;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

public class LocalDateTimeTest {
    public static void main(String[] args) {
        LocalDateTime ldt = LocalDateTime.now(); // 年月日时分秒 纳秒
        System.out.println(ldt);

        // 可以获取日期和时间的全部信息
        System.out.println(ldt.getYear()); // 年
        System.out.println((ldt.getMonthValue())); // 月
        System.out.println(ldt.getDayOfMonth());// 日
        System.out.println(ldt.getDayOfYear()); // 一年中的第几天
        System.out.println(ldt.getDayOfWeek().getValue()); // 周几
        System.out.println(ldt.getHour());// 时
        System.out.println(ldt.getMinute()); // 分
        System.out.println(ldt.getSecond()); // 秒
        System.out.println(ldt.getNano()); // 纳秒

        // 修改时间信息
        System.out.println(ldt.withYear(2029));
        System.out.println(ldt.withMinute(59));

        // 加多少
        System.out.println(ldt.plusYears(2));
        System.out.println(ldt.plusMinutes(3));

        // 减多少
        System.out.println(ldt.minusYears(2));
        System.out.println(ldt.minusMinutes(3));

        // 获取指定日期和时间LocalDataTime对象
        LocalDateTime ldt2 = LocalDateTime.of(2029,12,12,12,12,12,2001);
        LocalDateTime ldt3 = LocalDateTime.of(2029,12,12,12,12,12,2001);

        // 判断2个日期,时间对象,是否相等,在前还是后
        System.out.println(ldt2.equals(ldt3)); // true
        System.out.println(ldt2.isAfter(ldt3)); // false ld3不在ld2后面
        System.out.println(ldt2.isBefore(ldt3)); // false ld3不在ld2前面

        // 把LocalDataTime转换成LocalDate和LocalTime
        LocalDate ld = ldt.toLocalDate();
        LocalTime lt = ldt.toLocalTime();
        System.out.println(ld);
        System.out.println(lt);
    }
}
  • ZoneId:时区
  • ZonedDateTime:带时区的时间
package org.example.时区;

import java.time.Clock;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class Test {
    public static void main(String[] args) {
        // 获取系统默认时区
        ZoneId zoneid = ZoneId.systemDefault();
        System.out.println(zoneid.getId()); // Asia/Shanghai
        System.out.println(zoneid); // Asia/Shanghai

        // 获取Java支持的全部时区ID
        System.out.println(ZoneId.getAvailableZoneIds());

        // 把某个时区Id封装成ZoneId对象
        ZoneId zoneId1 = ZoneId.of("America/New_York");

        // 带时区的时间(当前时区的时间)
        ZonedDateTime now = ZonedDateTime.now(zoneId1);
        System.out.println(now); // 2024-04-09T11:13:39.566356-04:00[America/New_York]

        // 获取世界标准时间(北京时间 = 世界标准时间 + 8小时)
        System.out.println(ZonedDateTime.now(Clock.systemUTC()));

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

        // 可以通过上面的now2.各种调用时间的方法,与上面的几个时间方法使用一致,这里不再演示
    }
}
  • Instant:时间戳/时间线
package org.example.Instant;

import java.time.Instant;

public class Test {
    public static void main(String[] args) {
        // 创建Instant的对象,获取此刻时间信息
        Instant now = Instant.now();
        System.out.println(now);

        // 获取总秒数(从1970年到现在)
        long second = now.getEpochSecond();
        System.out.println(second);

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

        // 下面的不演示了,也是跟上面一样的操作时间的方法,通过now.调用,也是关于时间的增减判断之类的
        
        // 使用场景
        // 用于记录用户某个操作的时间点

        Instant now1 = Instant.now();
        // 代码执行....
        Instant now2 = Instant.now();
    }
}
  • DateTimeFormatter:用于时间的格式化和解析
package org.example.DateTimeFormatter;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class DateTimeFormatterTest {
    public static void main(String[] args) {
        // SimpleDateFormat 线程不安全
        // 代替SimpleDateFormat 线程安全
        // 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); // 得到格式化后的时间
        // 第二种方案
        System.out.println(now.format(formatter)); // 结果和上面一样(反向格式化)

        // 解析时间(一般使用LocalDateTime提供的解析方法来解析)
        String dateStr = "2029年12月12日 12:12:11";
        System.out.println(LocalDateTime.parse(dateStr, formatter)); // 解析到的日期对象
    }
}
  • Period:时间间隔(年,月,日)
package org.example.Period;

import java.time.LocalDate;
import java.time.Period;

public class PeriodTest {
    public static void main(String[] args) {
        // 创建Period对象,再封装两个日期对象
        // 开始时间
        LocalDate start = LocalDate.of(2029,8,10);
        // 结束时间
        LocalDate end = LocalDate.of(2029,8,15);

        Period period = Period.between(start,end);

        // 间隔多少年
        System.out.println(period.getYears());
        // 间隔多少月
        System.out.println(period.getMonths());
        // 间隔多少天
        System.out.println(period.getDays());
    }
}
  • Duration:时间间隔(时,分,秒,纳秒) 可以用于计算两个时间对象相差的天数、小时数、分数、秒数、纳秒数;支持LocalTime、LocalDateTime、Instant等时间。
package org.example.Duration;

import java.time.Duration;
import java.time.LocalDateTime;

public class DurationTest {
    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);

        // 获取两个时间对象间隔的信息
        // 间隔多少天
        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());
    }
}

数组排序

package org.example;

import java.util.Arrays;
import java.util.Comparator;

class Student {
    private int age;
    private double height;

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

    public Student() {
    }

    public int getAge() {
        return age;
    }

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

    public double getHeight() {
        return height;
    }

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

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

class Test{
    public static void main(String[] args) {
        // 定义数组
        Student[] student = new Student[4];
        student[0] = new Student(19,172.5);
        student[1] = new Student(17,173.6);
        student[2] = new Student(22,165.3);
        student[3] = new Student(13,155.8);

        // 根据年龄进行排序
        // 参数一:需要排序的数组
        // 参数二:比较器对象(用来指定比较器的比较规则)
        Arrays.sort(student, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                // 制定比较规则了:左边对象o1 右边对象 o2
                // 约定1:认为左边对象大于右边对象请您返回正整数
                // 约定2:认为左边对象小于右边对象请您返回负整数
                // 约定3:认为左边对象等于右边对象请您一定返回0

                // 根据年龄升序排序
                // return o1.getAge()-o2.getAge();
                // 根据年龄降序排序
                // return o2.getAge()-o1.getAge();

                // 小数类型排序处理(因为返回的是int类型,小数不能直接强转成int进行排序,不然数据不准确了)
                // 传统的就是通过if判断
                // o1.getHeight()>o2.getHeight() 返回1 等这种方法去返回1 -1 0 但是比较麻烦
                // double类型提供一种方法可以直接返回0 1 -1
                // 如下所示
                // return Double.compare(o1.getHeight(),o2.getHeight()); // 根据身高升序排序
                return Double.compare(o2.getHeight(),o1.getHeight()); // 根据身高降序排序
            }
        });
        System.out.println(Arrays.toString(student));

        // 直接数字(小数)的排序
        double[] prices = {99.8,128,100};
        Arrays.sort(prices); // 默认升序排序
        System.out.println(Arrays.toString(prices)); // [99.8, 100.0, 128.0]
        // 手动反转数组顺序实现降序
        for (int i = 0; i < prices.length / 2; i++) {
            double temp = prices[i];
            prices[i] = prices[prices.length - 1 - i];
            prices[prices.length - 1 - i] = temp;
        }

        System.out.println(Arrays.toString(prices)); // 输出:[128.0, 100.0, 99.8]
    }
}

JDK8新特性:Lambda表达式

  • Lambda表达式是JDK8开始新增的一种语法形式;作用:用于简化名内部类的代码写法
(被重写方法的形参列表)->{
    被重写方法的方法体代码。
}

Lambda表达式的省略写法(进一步简化Lambda表达式的写法)

  • 参数类型可以省略不写,
  • 如果只有一个参数,参数类型可以省略,同时()也可以省略。
  • 如果Lambda表达式中的方法体代码只有一行代码,可以省略大括号不写,同时要省略分号!此时,如果这行代码是return语句,也必须去掉return不写,
package org.example.LambdaTest1;

class LambdaTest1 {
    public static void main(String[] args) {
        // 认识lambda表达式(下面这个是传统写法)
        // Animal a = new Animal(){
        //     @Override
        //     public void run() {
        //         System.out.println("狗跑得贼快");
        //     }
        // };
        // a.run();

        // lambda表达式是匿名内部类简化版
        // 注意:Lambda表达式并不是说能简化全部匿名内部类的写法,只能简化函数式接口的匿名内部类。
        // 函数式接口: 是接口并且接口里面只能有一个抽象方法
        // 注意:将来我们见到的大部分函数式接口,上面都可能会有一个@Functionalnterface的注解,有该注解的接口就必定是函数式接口。

        // 下面这个是错误示范:非接口
        // Animal a = ()->{
        //
        // }

        // 这个下面是正确写法
        // 有参数的需要留下参数,和前端的箭头函数差不多
        Swimming s = ()->{
            System.out.println("我今天去游泳了");
        };
        s.swim();
    }
}

interface Swimming{
    void swim();
}

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

可变参数

package org.example.可变参数;

import java.util.Arrays;

public class Test {
    public static void main(String[] args) {
        // 可变参数
        test(); // 不报错
        test(10); // 传入一个数据
        test(10,20,30); // 传入多个数据
        test(new int[]{10,20,30,40}); // 传入数组
    }

    // 下面参数格式为int...nums 或者int... nums这样多个空格也可以
    // 可变参数本质就是数组且一个形参列表中,只能有一个可变参数
    // 可变参数必须放在形参列表的最后面
    public static void test(int...nums){
        System.out.println(nums.length); // 这里打印的是参数长度
        System.out.println(Arrays.toString(nums)); // 这里打印的是数据
    }
}

集合进阶

  • Collection(单列集合,每个元素(数据)只会包含一个值)

Collection

Collection集合的常用方法

  • Collection是单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的。因此可以理解为会使用Collection的方法则其他单列集合也会有这些方法,也是同样的使用方法的

方法如下

  • 1.public boolean add(Ee):添加元素,添加成功返回true.
  • 2.public void clear():清空集合的元素
  • 3.public boolean isEmpty():判断集合是否为空 是空返回true,反之
  • 4.public int size():获取集合的大小。
  • 5.public boolean contains(object obj):判断集合中是否包含某个元素。
  • 6.public boolean remove(Ee):删除某个元素:如果有多个重复元素默认删除前面的第一个!
  • 7.public 0bject[] toArray():把集合转换成数组
package org.example.Collection集合;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;

public class Test2 {
    public static void main(String[] args) {
        Collection<String> c = new ArrayList<>(); // 多态写法

        // 1.public boolean add(Ee):添加元素,添加成功返回true.
        c.add("java1");
        c.add("java1");
        c.add("java2");
        c.add("java3");
        c.add("java3");
        System.out.println(c); // [java1, java1, java2, java3, java3]

        // 2.public void clear():清空集合的元素
        // c.clear();
        // System.out.println(c); // []

        // 3.public boolean isEmpty():判断集合是否为空 是空返回true,反之。
        System.out.println(c.isEmpty()); // false

        // 4.public int size():获取集合的大小。
        System.out.println(c.size()); // 5

        // 5.public boolean contains(object obj):判断集合中是否包含某个元素。
        System.out.println(c.contains("java1")); // true
        System.out.println(c.contains("Java1")); // false

        // 6.public boolean remove(Ee):删除某个元素:如果有多个重复元素默认删除前面的第一个
        c.remove("java1");
        System.out.println(c); // [java1, java2, java3, java3]

        // 7.public 0bject[] toArray():把集合转换成数组
        Object[] arr = c.toArray();
        System.out.println(Arrays.toString(arr)); // [java1, java2, java3, java3]
        // 上边默认的是object类型数组,如果我们只想转换成String类型数组怎么操作呢??如下
        String[] arr2 = c.toArray(new String[c.size()]);
        System.out.println(Arrays.toString(arr2)); // [java1, java2, java3, java3]

        // 把一个集合的全部数据倒入另一个集合中去
        Collection<String> c1 = new ArrayList<>();
        c1.add("java1");
        c1.add("java2");
        c1.add("java3");
        c1.add("java4");
        Collection<String> c2 = new ArrayList<>();
        c2.add("java5");
        c2.add("java6");
        c2.add("java7");
        c2.add("java8");
        c1.addAll(c2);
        System.out.println(c1); // [java1, java2, java3, java4, java5, java6, java7, java8]
        System.out.println(c2); // [java5, java6, java7, java8]

    }
}

Collection的遍历方式

迭代器

  • 迭代器是用来遍历集合的专用方式(数组没有迭代器),在java中迭代器的代表是Iterator。
package org.example.Collection集合;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class Test3 {
    public static void main(String[] args) {
        // 迭代器
        // 创建集合
        Collection<String> c= new ArrayList<>();
        c.add("aaa");
        c.add("bbb");
        c.add("ccc");
        c.add("ddd");
        c.add("eee");
        // 获取到迭代器对象
        Iterator<String> it = c.iterator();
        // System.out.println(it.next()); // aaa
        // System.out.println(it.next()); // bbb
        // System.out.println(it.next()); // ccc
        // System.out.println(it.next()); // ddd

        // 使用循环结合迭代器遍历集合
        // it.hasNext()如果有下一个返回true,否则为false
        while (it.hasNext()){
            String ele = it.next();
            System.out.println(ele);
        }
    }
}

增强for

for(元素的数据类型 变量名:数组或者集合){}

package org.example.Collection集合;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class Test4 {
    public static void main(String[] args) {
        // 迭代器
        // 创建集合
        Collection<String> c= new ArrayList<>();
        c.add("aaa");
        c.add("bbb");
        c.add("ccc");
        c.add("ddd");
        c.add("eee");

        // 使用增强for循环
        for(String ele:c){
            System.out.println(ele);
        }
        System.out.println("----------------------");
        String[] names = {"java","js","node"};
        for (String name : names) {
            System.out.println(name);
        }
    }
}

lambda表达式

  • 得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的方式来遍历集合
package org.example.Collection集合;

import java.util.ArrayList;
import java.util.Collection;

public class Test5 {
    public static void main(String[] args) {
        // lambda表达式
        // 创建集合
        Collection<String> c= new ArrayList<>();
        c.add("aaa");
        c.add("bbb");
        c.add("ccc");
        c.add("ddd");
        c.add("eee");

        c.forEach(s->{
            System.out.println(s);
        });
    }
}

Collection不支持for循环,因为无索引

Collection工具类

  • Collections是一个用来操作集合的工具类
package org.example.Collection集合;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

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

    public Student() {
    }

    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 double getHeight() {
        return height;
    }

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

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

public class Test6 {
    public static void main(String[] args) {
        // 工具类Collections
        // 创建集合
        List<String> list= new ArrayList<>();

        // 为集合批量添加数据
        Collections.addAll(list,"张三","李四","王五","麻子");
        System.out.println(list); // [张三, 李四, 王五, 麻子]

        // 打乱list集合中的元素顺序(因为只有list集合是有序地,所以这个方法只支持list集合)
        Collections.shuffle(list);
        System.out.println(list); // 每次运行随机打乱顺序

        // 对list集合中的元素进行升序排序
        // list为int类型时默认升序排序
        List<Integer> list1 = new ArrayList<>();
        Collections.addAll(list1,2,3,10,1);
        Collections.sort(list1);
        System.out.println(list1);

        // 学生对象排序规则制定
        List<Student> students = new ArrayList<>();
        students.add(new Student("蜘蛛精",23,169.7));
        students.add(new Student("紫霞",22,169.8));
        students.add(new Student("至尊宝",26,165.5));
        students.add(new Student("牛魔王",22,183.5));
        // Collections.sort(students); // 报错,因为不是int类型的,程序不知道排序方法
        Collections.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                // 按照身高排序(升序)
                // return Double.compare(o1.getHeight(),o2.getHeight());

                // 按照年龄排序(升序)
                return o1.getAge()-o2.getAge();
            }
        });
        System.out.println(students);
    }
}

list

  • List系列集合:添加的元素是有序、可重复、有索引。
  • ArrayList、LinekdList:有序、可重复、有索引。
  • List集合因为支持索引,所以多了很多与索引相关的方法,当然,Collection的功能List也都继承了,因此list也能用Collection的所有方法
package org.example.list集合;

import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>(); // 是一行经典代码,ArrayList用的比较多,又因为是list实现类,因此后续想更换集合只需要换个名,不需要更改逻辑和方法
        list.add("java");
        list.add("java1");
        list.add("jav2");
        list.add("java3");
        System.out.println(list); // [java, java1, jav2, java3]

        // 在某个索引位置插入元素
        list.add(2,"萧寂");
        System.out.println(list); // [java, java1, 萧寂, jav2, java3]

        // 根据索引删除元素,返回被删除元素
        System.out.println(list.remove(2)); // 萧寂
        System.out.println(list); // [java, java1, jav2, java3]

        // 返回集合中指定位置的元素
        System.out.println(list.get(3)); // java3

        // 修改索引位置处的元素,修改成功后,会返回原来的数据
        System.out.println(list.set(3, "萧")); // java3
        System.out.println(list); // [java, java1, jav2, 萧]

    }
}

List支持的遍历方式

  • for循环(因为有索引)
  • 迭代器
  • 增强for循环
  • Lambda表达式
package org.example.list集合;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Test1 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>(); // 是一行经典代码,ArrayList用的比较多,又因为是list实现类,因此后续想更换集合只需要换个名,不需要更改逻辑和方法
        list.add("java");
        list.add("java1");
        list.add("jav2");
        list.add("java3");
        System.out.println(list); // [java, java1, jav2, java3]

        // for循环
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        // 迭代器
        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
        }
        // 增强for循环
        for (String s : list) {
            System.out.println(s);
        }
        // lambda表达式
        list.forEach(s->{
            System.out.println(s);
        });
    }
}

ArrayList和LinkedList区别

ArrayList:

  • ArrayList:有序,可重复,有索引。
  • 基于数组实现的
  • 查询速度快(注意:是根据索引查询数据快):查询数据通过地址值和索引定位,查询任意数据耗时相同,
  • 删除效率低:可能需要把后面很多的数据进行前移。
  • 添加效率极低:可能需要把后面很多的数据后移,再添加元素;或者也可能需要进行数组的扩容。

LinkedList:

  • LinkedList:有序,可重复,有索引。
  • 基于双链表实现的。
  • 查询慢, 无论查询哪个数据都要从头开始找,
  • 链表增删相对快(但对首尾元素进行增删改查的速度是极快的。LinkedList新增了:很多首尾操作的特有方法。)
  • 只是在首尾增删元素,用LinkedList来实现很合适!
addFirst 在该列表开头插入指定的元素
addLast 将指定的元素追加到此列表的末尾
getFirst 返回此列表中的第一个元素
getLast 返回此列表中的最后一个元素
removeFirst 从此列表中删除并返回第一个元素
removeLast 从此列表中删除并返回最后一个元素

使用LinkenList设计队列(先进先出,后进后出)和栈(先进后出,后进先出)

package org.example.list集合;

import java.util.LinkedList;

public class Test3 {
    public static void main(String[] args) {
        // LinkedList 双链表 先进先出 有序

        // 设计队列(先进先出,后进后出)
        // 创建一个队列
        LinkedList<String> queue = new LinkedList<>();
        // 入队
        queue.addLast("第1"); // 从尾部添加元素
        queue.addLast("第2");
        queue.addLast("第3");
        queue.addLast("第4");
        System.out.println(queue); // [第1, 第2, 第3, 第4]
        // 出队
        System.out.println(queue.removeFirst()); // 第1
        System.out.println(queue.removeFirst()); // 第2
        System.out.println(queue); // [第3, 第4]

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

        // 设计栈(先进后出,后进先出),可以理解为弹匣
        LinkedList<String> stack = new LinkedList<>();
        // 入栈
        stack.addFirst("第一颗子弹");
        stack.addFirst("第二颗子弹");
        stack.addFirst("第三颗子弹");
        stack.addFirst("第四颗子弹");
        System.out.println(stack); // [第四颗子弹, 第三颗子弹, 第二颗子弹, 第一颗子弹]
        // 出栈
        System.out.println(stack.removeFirst()); // 第四颗子弹
        System.out.println(stack.removeFirst()); // 第三颗子弹
        System.out.println(stack); // [第二颗子弹, 第一颗子弹]
    }
}

ArrayList和LinkedList底层采用的数据结构不同,应用场景不同

Set

  • Map(双列集合,每个元素包含两个值(键值对))
  • 无序:添加数据的顺序和获取出的数据顺序不一致;不重复;无索引;
  • Hashset: 无序、不重复、无索引,增删改查比较快;
  • LinkedHashset:有序、不重复、无索引,增删改查比较快。
  • Treeset:按照大小默认升序排序、不重复、无索引,增删改查比较快。
package org.example.list集合;

import java.util.*;

public class Test4 {
    public static void main(String[] args) {
       // 创建一个set集合的对象
       //  Set<Integer> set = new HashSet<>(); // 无序不重复无索引 无序是运行一次后这个顺序就定下来了,不是每次运行都是随机的
       //  Set<Integer> set = new LinkedHashSet<>(); // 有序,不重复,无索引
        Set<Integer> set = new TreeSet<>(); // 可排序(默认升序),不重复,无索引,括号内重写new Comparator<String>可以自定义排序规则
        set.add(111);
        set.add(222);
        set.add(222);
        set.add(222);
        set.add(333);
        set.add(444);
        set.add(333);
        System.out.println(set); // [444, 333, 222, 111]

    }
}

两种集合的特点

package org.example.Collection集合;

import java.util.ArrayList;
import java.util.HashSet;

public class Test {
    public static void main(String[] args) {
        // 确认ArrayList集合特点
        ArrayList<String> list = new ArrayList<>();
        list.add("java1");
        list.add("java2");
        list.add("java3");
        list.add("java3");
        System.out.println(list); // [java1, java2, java3, java3] 有序有索引,可重复

        // HashSet集合特点
        HashSet<String> set = new HashSet<>();
        set.add("java1");
        set.add("java2");
        set.add("java3");
        set.add("java3");
        System.out.println(set); // [java3, java2, java1] 无序无索引不可重复
    }
}

怎样选择适合的集合??

1、如果希望记住元素的添加顺序,需要存储重复的元素,又要频繁的根据索引查询数据?
    用ArrayList集合(有序、可重复、有索引),底层基于数组的。(常用)
    
2、如果希望记住元素的添加顺序,且增删首尾数据的情况较多?
	用LinkedList集合(有序、可重复、有索引),底层基于双链表实现的
    
3.如果不在意元素顺序,也没有重复元素需要存储,只希望增删改查都快?
	用HashSet集合(无序,不重复,无索引),底层基于哈希表实现的。(常用)
    
4.如果希望记住元素的添加顺序,也没有重复元素需要存储,且希望增删改查都快?
	用LinkedHashSet集合(有序,不重复,无索引),底层基于哈希表和双链表。
    
5.如果要对元素进行排序,也没有重复元素需要存储?且希望增删改查都快?
	用Treeset集合,基于红黑树实现。

集合的并发修改异常

Map集合

  • Map集合称为双列集合,格式:{key1=value1,key2=value2,key3=value3,…,一次需要存一对数据做为一个元素
  • Map集合的每个元素“key=value”称为一个键值对/键值对对象/一个Entry对象,Map集合也被叫做“键值对集合’
  • Map集合的所有键是不允许重复的,但值可以重复,键和值是一一对应的,每一个键只能找到自己对应的值
  • 需要存储-一对应的数据时,就可以考虑使用Map集合来做

集合体系

  • TreeMap<k,v>
  • hashMap<k,v>
  • LinkedHashMap<k,v>

Map集合体系的特点 注意:Map系列集合的特点都是由键决定的,值只是一个附属品,值是不做要求

  • HashMap(由键决定特点):无序、不重复、无索引;(用的最多)
  • LinkedHashMap (由键决定特点):由键决定的特点:有序、不重复、无索引。
  • TreeMap (由键决定特点):按照大小默认升序排序、不重复、无索引。
package org.example.Map;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

public class Test {
    public static void main(String[] args) {
        // Map<String,Integer> map = new HashMap<>(); // 经典代码,按照键 无序 不重复 无索引
        Map<String,Integer> map = new LinkedHashMap<>(); //按照键 有序 不重复 无索引
        map.put("手表",100);
        map.put("手表",220); // 这个重复的键后面的会覆盖前面的
        map.put("手机",2000);
        map.put("java",80);
        System.out.println(map); // {手表=220, 手机=2000, java=80}

        Map<Integer,String> map1 = new TreeMap<>(); // 有序,默认升序排序
        map1.put(23,"java");
        map1.put(25,"mysql");
        map1.put(10,"js");
        map1.put(18,"html");
        System.out.println(map1); // {10=js, 18=html, 23=java, 25=mysql}
    }
}

Map集合相关方法

Map是双列集合的祖宗,它的功能是全部双列集合都可以继承过来使用的

package org.example.Map;

import java.util.*;

public class Test1 {
    public static void main(String[] args) {
        // map集合常用方法
        Map<String,Integer> map = new LinkedHashMap<>();
        map.put("手表",100);
        map.put("手机",2000);
        map.put("java",80);
        map.put(null,null);
        System.out.println(map); // {手表=100, 手机=2000, java=80, null=null}

        // 1. 获取集合大小
        System.out.println(map.size()); // 4

        // 2. 清空集合
        // map.clear();
        // System.out.println(map); // {}

        // 3. 判断集合是否为空,为空返回true,反之false;
        System.out.println(map.isEmpty()); // false

        // 4. 根据键获取值
        System.out.println(map.get("手表")); // 100

        // 5. 根据键删除某个数据
        System.out.println(map.remove("手表")); // 100
        System.out.println(map); // {手机=2000, java=80, null=null}

        // 6. 判断是否包含某个键
        System.out.println(map.containsKey("手表")); // false

        // 7. 判断是否包含某个值
        System.out.println(map.containsValue(100)); // false

        // 8. 获取map集合的全部键
        Set<String> keys = map.keySet();
        System.out.println(keys); // [手机, java, null]

        // 9. 获取map集合的全部值
        Collection<Integer> values = map.values();
        System.out.println(values); // [2000, 80, null]

        // 10. 把其他map集合的数据倒入到自己集合中来
        Map<String,Integer> map2 = new LinkedHashMap<>(); //按照键 有序 不重复 无索引
        map2.put("手表2",100);
        map2.put("手机2",2000);
        Map<String,Integer> map3 = new LinkedHashMap<>(); //按照键 有序 不重复 无索引
        map3.put("手表3",100);
        map3.put("手机3",2000);
        map2.putAll(map3); // 如果两个集合有重复的键还是会被覆盖的
        System.out.println(map2); // {手表2=100, 手机2=2000, 手表3=100, 手机3=2000}
        System.out.println(map3); // {手表3=100, 手机3=2000}

    }
}

Map集合的遍历方式

  • 键找值

    • 先获取Map集合全部的键,再通过遍历键来找值

    • package org.example.Map;
      
      import java.util.Collection;
      import java.util.LinkedHashMap;
      import java.util.Map;
      import java.util.Set;
      
      public class Test2 {
          public static void main(String[] args) {
              // map集合常用方法
              Map<String,Double> map = new LinkedHashMap<>();
              map.put("手表",100.03);
              map.put("手机",2000.52);
              map.put("java",80.79);
              System.out.println(map); // {手表=100.03, 手机=2000.52, java=80.79}
      
              // 键找值方式遍历
              Set<String> keys = map.keySet();
              System.out.println(keys); // [手表, 手机, java]
              // 遍历全部的键,根据键获取对应的值
              for (String key : keys) {
                  double value = map.get(key);
                  System.out.println(key+"---"+value);
              }
      
          }
      }
      
  • 键值对

    • 把“键值对“看成一个整体进行遍历(难度较大)

    • package org.example.Map;
      
      import java.util.LinkedHashMap;
      import java.util.Map;
      import java.util.Set;
      
      // 键值对方式遍历
      public class Test3 {
          public static void main(String[] args) {
              // map集合常用方法
              Map<String,Double> map = new LinkedHashMap<>();
              map.put("手表",100.03);
              map.put("手机",2000.52);
              map.put("java",80.79);
              System.out.println(map); // {手表=100.03, 手机=2000.52, java=80.79}
      
              // 使用增强for循环的方法
              // 1、调用Map集合提供entrySet方法,把Map集合转换成键值对类型的Set集合
              Set<Map.Entry<String, Double>> entries = map.entrySet();
              System.out.println(entries); // [手表=100.03, 手机=2000.52, java=80.79]
              for (Map.Entry<String, Double> entry : entries) {
                  String key = entry.getKey(); // 获取当前循环的值的键
                  double value = entry.getValue();
                  System.out.println(key+"----"+value); // 数据
      
              }
          }
      }
      
  • Lambda

    • JDK1.8开始之后的新技术(非常的简单)

    • package org.example.Map;
      
      import java.util.LinkedHashMap;
      import java.util.Map;
      import java.util.Set;
      
      // Lambda表达式方式遍历
      public class Test4 {
          public static void main(String[] args) {
              // map集合常用方法
              Map<String,Double> map = new LinkedHashMap<>();
              map.put("手表",100.03);
              map.put("手机",2000.52);
              map.put("java",80.79);
              System.out.println(map); // {手表=100.03, 手机=2000.52, java=80.79}
      
              map.forEach((k,v)->{
                  System.out.println(k+"----"+v);
              });
          }
      }
      

集合的嵌套

需求 要求在程序中记住如下省份和其对应的城市信息,记录成功后,要求可以查询出湖北省的城市信息

  • 江苏省=南京市,扬州市,苏州市,无锡市,常州市
  • 湖北省=武汉市,孝感市,十堰市,宜昌市,鄂州市
  • 河北省=石家庄市,唐山市,邢台市,保定市,张家口市 分析

定义一个Map集合,键用表示省份名称,值表示城市名称,注意:城市会有多个。

package org.example.Map;

import java.util.*;

public class Test5 {
    public static void main(String[] args) {
        // - 江苏省=南京市,扬州市,苏州市,无锡市,常州市
        // - 湖北省=武汉市,孝感市,十堰市,宜昌市,鄂州市
        // - 河北省=石家庄市,唐山市,邢台市,保定市,张家口市

        // 省份可以无序,但是省份下面的城市是有序的
        Map<String, List<String>> map = new HashMap<>();
        // 创建list集合代表城市信息
        List<String> city1 = new ArrayList<>();
        Collections.addAll(city1,"南京市","扬州市","苏州市","无锡市","常州市");
        map.put("江苏省",city1);

        List<String> city2 = new ArrayList<>();
        Collections.addAll(city2,"武汉市","孝感市","十堰市","宜昌市","鄂州市");
        map.put("湖北省",city2);

        List<String> city3 = new ArrayList<>();
        Collections.addAll(city3,"石家庄市","唐山市","邢台市","保定市","张家口市");
        map.put("河北省",city3);

        System.out.println(map);
        List<String> cities = map.get("湖北省");
        for (String city : cities) {
            System.out.println(city);
        }

        map.forEach((a,b)->{
            System.out.println(a+"-------"+b);
        });
    }
}

Stream

  • 也叫Stream流,是Jdk8开始新增的一套API(java.util.stream.*),可以用于操作集合或者数组的数据。
  • 也叫Stream流,是Jdk8开始新增的一套API(java.util.stream.*),可以用于操作集合或者数组的数据。

stream流初体验

package org.example.Stream;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class Test {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        Collections.addAll(names,"张三丰","张无忌","周芷若","赵敏","张强");
        System.out.println(names);

        // 找出姓张,且是三个字的名字,存入到一个新的集合中去

        // 使用原来方法实现
        List<String> list = new ArrayList<>();
        for (String name : names) {
            if(name.startsWith("张")&&name.length()==3){
                list.add(name);
            }
        }
        System.out.println(list);

        // 使用stream流实现
        List<String> list2 = names.stream().filter(s -> {
            return s.startsWith("张");
        }).filter(a -> {
            return a.length() == 3;
        }).collect(Collectors.toList());
        System.out.println(list2);
    }
}

集合和数组获取Stream的方式

package org.example.Stream;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Test1 {
    public static void main(String[] args) {
        // 获取list集合的stream流
        List<String> names = new ArrayList<>();
        Collections.addAll(names,"张三丰","张无忌","周芷若","赵敏","张强");
        // 流的获取
        Stream<String> stream = names.stream();


        // 获取set集合的stream流
        Set<String> set = new HashSet<>();
        Collections.addAll(set,"刘德华","张曼玉","蜘蛛精","玛德","德玛西亚");
        // 流的获取
        Stream<String> stream1 = set.stream();
        stream1.filter(s->s.contains("德")).forEach(s->{
            System.out.println(s);
        });

        // 获取Map集合的stream流
        Map<String,Double> map = new HashMap<>();
        map.put("古力娜扎",172.3);
        map.put("迪丽热巴",168.3);
        map.put("玛尔扎科",166.3);
        map.put("卡尔扎巴",168.3);
        Set<String> strings = map.keySet();
        // 流的获取
        Stream<String> stream2 = strings.stream();

        // 获取到map所有的值
        Collection<Double> values = map.values();
        Stream<Double> stream3 = values.stream(); // map值流,可以对map集合的值做处理

        Set<Map.Entry<String, Double>> entries = map.entrySet();
        Stream<Map.Entry<String, Double>> stream4 = entries.stream(); // map的每一项的数据流,对每一项数据整体做处理(键值都包含了)

        // 返回键中包含"巴"的数据
        stream4.filter(e->{
            return e.getKey().contains("巴");
        }).forEach(s->{
            System.out.println(s);
        });


        // 获取数组的stream流(两种方式)
        String[] names2 = {"张翠山","东方不败","唐大山","孤独求败"};
        // 流的获取
        Stream<String> stream5 = Arrays.stream(names2);
        Stream<String> stream6 = Stream.of(names2);


    }
}

Stream常见的中间方法

package org.example.Stream;

import java.util.*;
import java.util.stream.Stream;

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

    public Student() {
    }

    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 double getHeight() {
        return height;
    }

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

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Double.compare(height, student.height) == 0 && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, height);
    }
}

public class Test2 {
    public static void main(String[] args) {
        List<Double> scores = new ArrayList<>();
        Collections.addAll(scores,88.5,100.0,60.0,99.0,9.5,99.6,25.0);
        // 需求1,找出成绩大于等于60分的数据,并升序后,再输出\
        // sorted默认升序
        scores.stream().filter(s->{
            return s>=60;
        }).sorted().forEach(s->{
            System.out.println(s);
        });
        System.out.println("-------------------------");

        List<Student> students = new ArrayList<>();
        students.add(new Student("蜘蛛精", 23, 169.7));
        students.add(new Student("蜘蛛精", 23, 169.7));
        students.add(new Student("紫霞", 22, 169.8));
        students.add(new Student("至尊宝", 26, 165.5));
        students.add(new Student("牛魔王", 22, 183.5));
        // 需求2:找出年龄大于等于23,且年龄小于等于20岁的学生,并按照年龄降序输出
        students.stream().filter(s->{
            return s.getAge()>=23&&s.getAge()<=30;
        }).sorted((s1,s2)->{
            return s1.getAge()-s2.getAge();
        }).forEach(s->{
            System.out.println(s);
        });
        System.out.println("-------------------------");


        // 需求3:取出身高最高的前三名学生,并输出
        // limit得到排序后的前几位
        students.stream().sorted((s1,s2)->{
            return Double.compare(s2.getHeight(),s1.getHeight());
        }).limit(3).forEach(s->{
            System.out.println(s);
        });
        System.out.println("-------------------------");

        // 需求4:取出身高倒数的2名学生,并输出
        // skip 跳过前几个
        students.stream().sorted((s1,s2)->{
            return Double.compare(s2.getHeight(),s1.getHeight());
        }).skip(students.size()-2).forEach(s->{
            System.out.println(s);
        });
        System.out.println("-------------------------");
        // 需求5:找出身高超过168的学生叫什么名字,要求去除重复的名字,再输出
        // distinct去重
        students.stream().filter(s->{
            return s.getHeight()>168;
        }).map(s->{
            return s.getName();
        }).distinct().forEach(s->{
            System.out.println(s);
        });
        System.out.println("------");
        // 希望内容一样就去除重复,重写(hashCode,equals)
        // 如下代码: 先不重写student的hashCode和equals可以发现并没有被真的去重,当重写完后会发现才是真的去重了
        students.stream().filter(s->{
            return s.getHeight()>168;
        }).distinct().forEach(s->{
            System.out.println(s);
        });

        System.out.println("---------------------------");
        // 将两个流合并为一个流
        Stream<String> st1 = Stream.of("张三", "李四");
        Stream<String> st2 = Stream.of("王五", "麻子","哈哈");
        Stream<String> concat = Stream.concat(st1, st2);
        concat.forEach(s->{
            System.out.println(s);
        });

    }
}

Stream常见的终结方法

  • 终结方法指的是调用完成后,不会返回新Stream了,没法继续使用流了。
  • 收集Stream流 : 就是把Stream流操作后的结果转回到集合或者数组中去返回
  • Stream流:方便操作集合/数组的手段;集合/数组:才是开发中的目的
package org.example.Stream;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

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

    public Student() {
    }

    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 double getHeight() {
        return height;
    }

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

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Double.compare(height, student.height) == 0 && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, height);
    }
}

public class Test3 {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("蜘蛛精", 23, 169.7));
        students.add(new Student("蜘蛛精", 23, 169.7));
        students.add(new Student("紫霞", 22, 169.8));
        students.add(new Student("至尊宝", 26, 165.5));
        students.add(new Student("牛魔王", 22, 183.5));
        students.add(new Student("铁扇公主", 20, 173.5));

        // 需求1:请计算出身高超过168的学生有几人
        // count统计数量
        System.out.println(students.stream().filter(s -> {
            return s.getHeight() > 168;
        }).count());

        // 需求2:请找出身高最高的学生对象,并输出
        // get:获取到中间方法返回的数据并直接返回
        // max:获取到身高最高的学生对象并返回
        System.out.println(students.stream().max((o1, o2) -> {
            return Double.compare(o1.getHeight(), o2.getHeight());
        }).get());

        // 需求3:请找出身高最矮的学生对象,并输出
        // min:获取到身高最矮的学生对象并返回
        System.out.println(students.stream().min((o1, o2) -> {
            return Double.compare(o1.getHeight(), o2.getHeight());
        }).get());

        // 需求4:请找出身高超过170的学生对象,并放到一个新集合中去并返回
        // collect:收集流,注意流只能被收集一次
        System.out.println(students.stream().filter(s -> {
            return s.getHeight() > 170;
        }).collect(Collectors.toList())); // 放到list集合中

        System.out.println(students.stream().filter(s -> {
            return s.getHeight() > 170;
        }).collect(Collectors.toSet())); // 放到Set集合中(如果碰到重复的会去重)

        // 需求5:请找出身高超过170的学生对象,并把学生对象的名字和身高存入到一个map集合返回
        // 去下重再收集
        // toMap里面参数1是键,参数2是值
        System.out.println(students.stream().filter(a -> {
            return a.getHeight() > 170;
        }).distinct().collect(Collectors.toMap(a -> a.getName(), a -> a.getHeight())));

    }
}