JAVA基础

Java基础

一.面向对象

1. 类和对象

什么是类?

  • 类是一组相关属性和行为的集合,可以看成是一类事物的模板,使用事物的属性特征行为特征来描述该类事物。
  • 属性:事物的状态信息
  • 行为:事物能够做什么

什么是对象?

  • 是一类事物的具体体现。对象是类的一个实例。对象具备该类事物的属性和行为。

类与对象的关系

  • 类是对一类事物的描述,是抽象的
  • 对象是一类事物的实例,是具体的。
  • 类是对象的模板,对象是类的实体。

对象内存图:

[对象调用方法时,根据对象中方法标记(地址值),去类中寻找方法信息。这样哪怕是多个对象,方法信息 只保存一份,节约内存空间]

位置

作用范围

初始化值

内存中位置

生命周期

成员变量,实例变量

类中,方法外

类中

默认值

堆内存

跟随对象

局部变量

方法中或者形参

方法中

必须先定义,赋值,才能使用

栈空间

跟随方法

2. 面向对象三大特性:

封装

利用抽象数据类型将数据和操作封装在一起,其成为一个不可分割的独立的一个实体,数据被保护在抽象数据类型的内部(变量隐藏在对象的内部),尽可能的隐藏内部细节,只保留一些对外接口使之与外部发生联系。用户无需知道对象内部的实现细节,但可以通过对象对外提供的接口来访问该对象。

  • 减少耦合: 可以独立地开发、测试、优化、使用、理解和修改
  • 减轻维护的负担: 可以更容易被程序员理解,并且在调试的时候可以不影响其他模块
  • 有效地调节性能: 可以通过剖析确定哪些模块影响了系统的性能
  • 提高软件的可重用性
  • 降低了构建大型系统的风险: 即使整个系统不可用,但是这些独立的模块却有可能是可用的。

继承

继承实现了IS-A关系,应遵循李氏替换原则,子类对象必须能够替换掉所有父类对象。新类的定义可以增加新的数据或新的功能,也可以⽤⽗类的功能,但不能选择性地继承⽗类。通过使⽤继承我们能够⾮常⽅便地复⽤以前的代码。

  • 继承的概念
    • 继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义,以及追加属性和方法
  • 实现继承的格式
    • 继承通过extends实现
    • 格式:class 子类 extends 父类 { }
      • 举例:class Dog extends Animal { }
  • 继承带来的好处
    • 继承可以让类与类之间产生关系,子父类关系,产生子父类后,子类则可以使用父类中非私有的成员。

父类引用指向子类对象:向上转型。

Animal animal = new Cat();

super

  • this&super关键字:
    • this:代表本类对象的引用
    • super:代表父类存储空间的标识(可以理解为父类对象引用)
  • this和super的使用分别
    • 成员变量:
      • this.成员变量    –   访问本类成员变量
      • super.成员变量 –   访问父类成员变量
    • 成员方法:
      • this.成员方法  – 访问本类成员方法
      • super.成员方法 – 访问父类成员方法
  • 构造方法:
    • this(…)  –  访问本类构造方法
    • super(…)  –  访问父类构造方法

多态

首先多态的实现离不开继承和接口。在设计程序时,我们可以将参数数据设置为父类型,但是在调用程序时,则可以根据情况,传入该父类型的某个子类的实例,这样就实现了多态,对于父类型来说,可以是普通的类,抽象类,接口。对于子类型来说,则要重写父类的某些方法,或者是实现抽象类/接口的某些抽象方法。

两种:

  • 编译时多态:方法的重载
  • 运行时多态:程序中定义的对象引用所指向的具体类型在运行期间才确定

运行时多态要有三个条件:

  • 继承
  • 覆盖重写
  • 向上转型

多态的好处和弊端(记忆)

  • 好处
    提高程序的扩展性。定义方法时候,使用父类型作为参数,在使用的时候,使用具体的子类型参与操作
  • 弊端
    不能使用子类的特有成员
多态中的转型(应用)
  • 向上转型
    父类引用指向子类对象就是向上转型
  • 向下转型
    格式:子类型 对象名 = (子类型)父类引用;
  • 代码演示
class Fu {
    public void show(){
        System.out.println("Fu..show...");
    }
}

class Zi extends Fu {
    @Override
    public void show() {
        System.out.println("Zi..show...");
    }

    public void method(){
        System.out.println("我是子类特有的方法, method");
    }
}

public class Test3Polymorpic {
    public static void main(String[] args) {
        // 1. 向上转型 : 父类引用指向子类对象
        Fu f = new Zi();
        f.show();
        // 多态的弊端: 不能调用子类特有的成员
        // f.method();

        // A: 直接创建子类对象
        // B: 向下转型

        // 2. 向下转型 : 从父类类型, 转换回子类类型
        Zi z = (Zi) f;
        z.method();
    }
}
多态中转型存在的风险和解决方案 (应用)
  • 风险
    如果被转的引用类型变量,对应的实际类型和目标类型不是同一种类型,那么在转换的时候就会出现ClassCastException
  • 解决方案
    • 关键字
      instanceof
    • 使用格式
      变量名 instanceof 类型
      通俗的理解:判断关键字左边的变量,是否是右边的类型,返回boolean类型结果
  • 代码演示
abstract class Animal {
    public abstract void eat();
}

class Dog extends Animal {
    public void eat() {
        System.out.println("狗吃肉");
    }

    public void watchHome(){
        System.out.println("看家");
    }
}

class Cat extends Animal {
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

public class Test4Polymorpic {
    public static void main(String[] args) {
        useAnimal(new Dog());
        useAnimal(new Cat());
    }

    public static void useAnimal(Animal a){  // Animal a = new Dog();
                                             // Animal a = new Cat();
        a.eat();
        //a.watchHome();

//        Dog dog = (Dog) a;
//        dog.watchHome();  // ClassCastException  类型转换异常
      
        // 判断a变量记录的类型, 是否是Dog
        if(a instanceof Dog){
            Dog dog = (Dog) a;
            dog.watchHome();
        }
    }

}

3. 构造方法

格式和执行时机

  • 方法名和类名相同,大小写也要一致。
  • 没有返回值类型,没有void
  • 没有具体的返回值,不能用return带回结果数据。

public class java_base {
    public static void main(String[] args) {
        //创建对象
        Student s = new Student();
        s.show();
    }
}
class Student {
    private String name;
    private int age;

    //构造方法
    public Student() {
        System.out.println("无参构造方法");     //无参构造方法
    }

    public void show() {
        System.out.println(name + "," + age);  //null,0
    }
}

构造方法的作用

  • 用于给对象的数据(属性)进行初始化

package cn.jian.test.constructor;

public class Student {
    /*
        格式:

               1. 方法名需要跟类名相同, 大小写也要一致
               2. 没有返回值类型, 连void都没有
               3. 没有具体的返回值(不能由return带回具体的结果)
     */
    private String name;
    private int age;

    // 1. 如果一个类中没有编写任何构造方法, 系统将会提供一个默认的无参数构造方法
    public Student(){}

    // 2. 如果手动编写了构造方法, 系统就不会再提供默认的无参数构造方法了
    public Student(String name,int age){
        this.name = name;
        this.age = age;
        System.out.println("我是构造方法");
    }

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

package cn.jian.test.constructor;

public class demoStudent {
    public static void main(String[] args) {
        Student student1 = new Student();
        Student student2 = new Student("贱贱",24);
        student1.show();    //null....0
        student2.show();    //贱贱....24
    }
}

4. 代码块

代码块概述

在Java中,使用 { } 括起来的代码被称为代码块

代码块分类

  • 局部代码块
    • 位置: 方法中定义
    • 作用: 限定变量的生命周期,及早释放,提高内存利用率
  • 构造代码块
    • 位置: 类中方法外定义
    • 特点: 每次构造方法执行的时,都会执行该代码块中的代码,并且在构造方法执行前执行
    • 作用: 将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性
  • 静态代码块
    • 位置: 类中方法外定义
    • 特点: 需要通过static关键字修饰,随着类的加载而加载,并且只执行一次
    • 作用: 在类加载的时候做一些数据初始化的操作
package cn.jian.test.constructor;

public class Student {

    private String name;
    private int age;

    /*
            构造代码块:
                位置:类中方法外定义
                特点:每次构造方法执行时,都会执行该代码块中的代码,并且在构造方法执行前执行
                作用:将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性(重要)
         */
    {
        System.out.println("我是构造代码块");
    }
    /*
           静态代码块:
               位置:类中方法外定义
               特点:需要通过static关键字修饰,随着类的加载而加载,并且只执行一次
               作用:在类加载的时候做一些数据初始化的操作
        */
    static {
        System.out.println("我是静态代码块");
    }

    /*
        格式:
               1. 方法名需要跟类名相同, 大小写也要一致
               2. 没有返回值类型, 连void都没有
               3. 没有具体的返回值(不能由return带回具体的结果)
     */
    // 1. 如果一个类中没有编写任何构造方法, 系统将会提供一个默认的无参数构造方法
    public Student(){
        System.out.println("我是空参构造");
    }

    // 2. 如果手动编写了构造方法, 系统就不会再提供默认的无参数构造方法了
    public Student(String name,int age){
        this.name = name;
        this.age = age;
        System.out.println("我是有参构造方法");
    }
    
    public void show(){
        System.out.println(name + "...." + age);
    }
}
package cn.jian.test.constructor;

public class demo2 {
    public static void main(String[] args) {
         /*
       局部代码块
           位置:方法中定义
           作用:限定变量的生命周期,及早释放,提高内存利用率
    */
        {
            System.out.println("我是局部代码块");
        }
        Student stu = new Student();
        System.out.println("==============");
        Student stu2 = new Student("jian",24);
        stu2.show();

        /*我是局部代码块
          我是静态代码块
          我是构造代码块
          我是空参构造
          ==============
          我是构造代码块
          我是有参构造方法
          jian....24
        */
    }
}

5. 抽象类与接口

抽象类

抽象类和抽象方法都使用 abstract 关键字进行声明。抽象类一般会包含抽象方法,抽象方法一定位于抽象类中。

抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类。

public abstract class AbstractClassExample {

    protected int x;
    private int y;

    public abstract void func1();

    public void func2() {
        System.out.println("func2");
    }
}

public class AbstractExtendClassExample extends AbstractClassExample {
    @Override
    public void func1() {
        System.out.println("func1");
    }
}

// AbstractClassExample ac1 = new AbstractClassExample(); // 'AbstractClassExample' is abstract; cannot be instantiated
AbstractClassExample ac2 = new AbstractExtendClassExample();
ac2.func1();

接口

接口的概述(理解)

  • 接口就是一种公共的规范标准,只要符合规范标准,大家都可以通用。
  • Java中接口存在的两个意义
    1. 用来定义规范
    2. 用来做功能的拓展

接口的特点(记忆)

  • 接口用关键字interface修饰
public interface 接口名 {}
  • 类实现接口用implements表示
public class 类名 implements 接口名 {}
  • 接口不能实例化
    我们可以创建接口的实现类对象使用
  • 接口的子类
    要么重写接口中的所有抽象方法
    要么子类也是抽象类

接口的成员特点(记忆)

  • 成员特点
    • 成员变量
      只能是常量
      默认修饰符:public static final
    • 构造方法
      没有,因为接口主要是扩展功能的,而没有具体存在
    • 成员方法
      只能是抽象方法
      默认修饰符:public abstract
      关于接口中的方法,JDK8和JDK9中有一些新特性,后面再讲解
public interface InterfaceExample {
    
    default void func2(){
        System.out.println("func2");
    }
    
    void func1();

    int x = 123;
    // int y;               // Variable 'y' might not have been initialized
    public int z = 0;       // Modifier 'public' is redundant for interface fields
    // private int k = 0;   // Modifier 'private' not allowed here
    // protected int l = 0; // Modifier 'protected' not allowed here
    // private void fun3(); // Modifier 'private' not allowed here
}

接口中静态方法【应用】

  • 格式
    public static 返回值类型 方法名(参数列表) {   }
  • 范例
public static void show() {
}
  • 注意事项
    • 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
    • public可以省略,static不能省略

2.4接口中私有方法

  • 私有方法产生原因
    Java 9中新增了带方法体的私有方法,这其实在Java 8中就埋下了伏笔:Java 8允许在接口中定义带方法体的默认方法和静态方法。这样可能就会引发一个问题:当两个默认方法或者静态方法中包含一段相同的代码实现时,程序必然考虑将这段实现代码抽取成一个共性方法,而这个共性方法是不需要让别人使用的,因此用私有给隐藏起来,这就是Java 9增加私有方法的必然性
  • 定义格式
    • 格式1
      private 返回值类型 方法名(参数列表) {   }
    • 范例1
private void show() {  
}

    • 格式2
      private static 返回值类型 方法名(参数列表) {   }
    • 范例2
private static void method() {  
}

  • 注意事项
    • 默认方法可以调用私有的静态方法和非静态方法
    • 静态方法只能调用私有的静态方法

3. 比较

  • 从设计层面上看,抽象类提供了一种 IS-A 关系,那么就必须满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。
  • 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
  • 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
  • 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。

4. 使用选择

使用接口:

  • 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法;
  • 需要使用多重继承。

使用抽象类:

  • 需要在几个相关的类中共享代码。
  • 需要能控制继承来的成员的访问权限,而不是都为 public。
  • 需要继承非静态和非常量字段。

在很多情况下,接口优先于抽象类,因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。

接⼝和抽象类的区别是什么?

  1. 接⼝的⽅法默认是 public  ,所有⽅法在接⼝中不能有实现(Java 8 开始接⼝⽅法可以有默认 实现),⽽抽象类可以有⾮抽象的⽅法。
  2. 接⼝中除了 static  、final  变量,不能有其他变量,⽽抽象类中则不⼀定。
  3. ⼀个类可以实现多个接⼝,但只能实现⼀个抽象类。接⼝⾃⼰本身可以通过  extends  关键字扩展多个接⼝。
  4. 接⼝⽅法默认修饰符是  public ,抽象⽅法可以有  public  、protected 和default 这些修饰 符(抽象⽅法就是为了被重写所以不能使⽤   private   关键字修饰!)。
  5. 从设计层⾯来说,抽象是对类的抽象,是⼀种模板设计,⽽接⼝是对⾏为的抽象,是⼀种⾏为的规范。

备注:

  1. 在 JDK8 中,接⼝也可以定义静态⽅法,可以直接⽤接⼝名调⽤。实现类和实现是不
    可以调⽤的。如果同时实现两个接⼝,接⼝中定义了⼀样的默认⽅法,则必须重写,不
    然会报错。  (详⻅ issue:https://github.com/Snailclimb/JavaGuide/issues/146。
  2. jdk9 的接⼝被允许定义私有⽅法 。
    总结⼀下 jdk7~jdk9 Java 中接⼝概念的变化(相关阅读):
  3. 在jdk 7 或更早版本中,接⼝⾥⾯只能有常量变量和抽象⽅法。这些接⼝⽅法必须由选择实 现接⼝的类实现。
  4. jdk 8 的时候接⼝可以有默认⽅法和静态⽅法功能。
  5. Jdk 9 在接⼝中引⼊了私有⽅法和私有静态⽅法。

重写与重载

重写:发生在运行期,子父类当中,方法名,参数列表要相同,内部逻辑可以改变。

条件:两小一大

  • 返回值小于等于父类
  • 抛出的异常要小于等于父类
  • 访问权限大于等于父类方法

重载:发生在同一个当中,方法名相同,参数列表不同,个数不同,顺序不同,构成重载关系。(签名不同)

  • 重载与方法的返回值以及访问修饰符无关。

6.内部类和lambda表达式

6.1 内部类的基本使用(理解)

  • 内部类概念
    • 在一个类中定义一个类。举例:在一个类A的内部定义一个类B,类B就被称为内部类
  • 内部类定义格式
    • 格式&举例:
/*
	格式:
    class 外部类名{
    	修饰符 class 内部类名{
    	
    	}
    }
*/

class Outer {
    public class Inner {
        
    }
}

  • 内部类的访问特点
    • 内部类可以直接访问外部类的成员,包括私有
    • 外部类要访问内部类的成员,必须创建对象
  • 示例代码:
/*
    内部类访问特点:
        内部类可以直接访问外部类的成员,包括私有
        外部类要访问内部类的成员,必须创建对象
 */
public class Outer {
    private int num = 10;
    public class Inner {
        public void show() {
            System.out.println(num);
        }
    }
    public void method() {
        Inner i = new Inner();
        i.show();
    }
}

6.2 成员内部类(理解)

  • 成员内部类的定义位置
    • 在类中方法,跟成员变量是一个位置
  • 外界创建成员内部类格式
    • 格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;
    • 举例:Outer.Inner oi = new Outer().new Inner();
  • 私有成员内部类
    • 将一个类,设计为内部类的目的,大多数都是不想让外界去访问,所以内部类的定义应该私有化,私有化之后,再提供一个可以让外界调用的方法,方法内部创建内部类对象并调用。
    • 示例代码:
class Outer {
    private int num = 10;
    private class Inner {
        public void show() {
            System.out.println(num);
        }
    }
    public void method() {
        Inner i = new Inner();
        i.show();
    }
}
public class InnerDemo {
    public static void main(String[] args) {
		//Outer.Inner oi = new Outer().new Inner();
		//oi.show();
        Outer o = new Outer();
        o.method();
    }
}

  • 静态成员内部类
    • 静态成员内部类访问格式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
    • 静态成员内部类中的静态方法:外部类名.内部类名.方法名();
    • 示例代码
class Outer {
    static class Inner {
        public void show(){
            System.out.println("inner..show");
        }

        public static void method(){
            System.out.println("inner..method");
        }
    }
}

public class Test3Innerclass {
    /*
        静态成员内部类演示
     */
    public static void main(String[] args) {
        // 外部类名.内部类名 对象名 = new 外部类名.内部类名();
        Outer.Inner oi = new Outer.Inner();
        oi.show();

        Outer.Inner.method();
    }
}

6.3 局部内部类(理解)

  • 局部内部类定义位置
    • 局部内部类是在方法中定义的类
  • 局部内部类方式方式
    • 局部内部类,外界是无法直接使用,需要在方法内部创建对象并使用
    • 该类可以直接访问外部类的成员,也可以访问方法内的局部变量
  • 示例代码
class Outer {
    private int num = 10;
    public void method() {
        int num2 = 20;
        class Inner {
            public void show() {
                System.out.println(num);
                System.out.println(num2);
            }
        }
        Inner i = new Inner();
        i.show();
    }
}
public class OuterDemo {
    public static void main(String[] args) {
        Outer o = new Outer();
        o.method();
    }
}

6.4 匿名内部类(应用)

  • 匿名内部类的前提
    • 存在一个类或者接口,这里的类可以是具体类也可以是抽象类
  • 匿名内部类的格式
    • 格式:new 类名 ( ) {  重写方法 }    new  接口名 ( ) { 重写方法 }
    • 举例:
new Inter(){
    @Override
    public void method(){}
}

  • 匿名内部类的本质
    • 本质:是一个继承了该类或者实现了该接口的子类匿名对象
  • 匿名内部类的细节
    • 匿名内部类可以通过多态的形式接受
Inter i = new Inter(){
  @Override
    public void method(){
        
    }
}

  • 匿名内部类直接调用方法
interface Inter{
    void method();
}

class Test{
    public static void main(String[] args){
        new Inter(){
            @Override
            public void method(){
                System.out.println("我是匿名内部类");
            }
        }.method();	// 直接调用方法
    }
}

6.5 匿名内部类在开发中的使用(应用)

  • 匿名内部类在开发中的使用
    • 当发现某个方法需要,接口或抽象类的子类对象,我们就可以传递一个匿名内部类过去,来简化传统的代码
  • 示例代码:
/*
    游泳接口
 */
interface Swimming {
    void swim();
}

public class TestSwimming {
    public static void main(String[] args) {
        goSwimming(new Swimming() {
            @Override
            public void swim() {
                System.out.println("铁汁, 我们去游泳吧");
            }
        });
    }

    /**
     * 使用接口的方法
     */
    public static void goSwimming(Swimming swimming){
        /*
            Swimming swim = new Swimming() {
                @Override
                public void swim() {
                    System.out.println("铁汁, 我们去游泳吧");
                }
            }
         */
        swimming.swim();
    }
}

7.lambda表达式

7.1体验Lambda表达式【理解】

  • 代码演示
/*
    游泳接口
 */
interface Swimming {
    void swim();
}

public class TestSwimming {
    public static void main(String[] args) {
        // 通过匿名内部类实现
        goSwimming(new Swimming() {
            @Override
            public void swim() {
                System.out.println("铁汁, 我们去游泳吧");
            }
        });

        /*  通过Lambda表达式实现
            理解: 对于Lambda表达式, 对匿名内部类进行了优化
         */
        goSwimming(() -> System.out.println("铁汁, 我们去游泳吧"));
    }

    /**
     * 使用接口的方法
     */
    public static void goSwimming(Swimming swimming) {
        swimming.swim();
    }
}

  • 函数式编程思想概述
    在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”
    面向对象思想强调“必须通过对象的形式来做事情”
    函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”
    而我们要学习的Lambda表达式就是函数式思想的体现

7.2Lambda表达式的标准格式【理解】

  • 格式:
    (形式参数) -> {代码块}
    • 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
    • ->:由英文中画线和大于符号组成,固定写法。代表指向动作
    • 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容
  • 组成Lambda表达式的三要素:
    • 形式参数,箭头,代码块

7.3Lambda表达式练习1【应用】

  • Lambda表达式的使用前提
    • 有一个接口
    • 接口中有且仅有一个抽象方法
  • 练习描述
    无参无返回值抽象方法的练习
  • 操作步骤
    • 定义一个接口(Eatable),里面定义一个抽象方法:void eat();
    • 定义一个测试类(EatableDemo),在测试类中提供两个方法
      • 一个方法是:useEatable(Eatable e)
      • 一个方法是主方法,在主方法中调用useEatable方法
  • 示例代码
//接口
public interface Eatable {
    void eat();
}
//实现类
public class EatableImpl implements Eatable {
    @Override
    public void eat() {
        System.out.println("一天一苹果,医生远离我");
    }
}
//测试类
public class EatableDemo {
    public static void main(String[] args) {
        //在主方法中调用useEatable方法
        Eatable e = new EatableImpl();
        useEatable(e);

        //匿名内部类
        useEatable(new Eatable() {
            @Override
            public void eat() {
                System.out.println("一天一苹果,医生远离我");
            }
        });

        //Lambda表达式
        useEatable(() -> {
            System.out.println("一天一苹果,医生远离我");
        });
    }

    private static void useEatable(Eatable e) {
        e.eat();
    }
}

7.4Lambda表达式练习2【应用】

  • 练习描述
    有参无返回值抽象方法的练习
  • 操作步骤
    • 定义一个接口(Flyable),里面定义一个抽象方法:void fly(String s);
    • 定义一个测试类(FlyableDemo),在测试类中提供两个方法
      • 一个方法是:useFlyable(Flyable f)
      • 一个方法是主方法,在主方法中调用useFlyable方法
  • 示例代码
public interface Flyable {
    void fly(String s);
}

public class FlyableDemo {
    public static void main(String[] args) {
        //在主方法中调用useFlyable方法
        //匿名内部类
        useFlyable(new Flyable() {
            @Override
            public void fly(String s) {
                System.out.println(s);
                System.out.println("飞机自驾游");
            }
        });
        System.out.println("--------");

        //Lambda
        useFlyable((String s) -> {
            System.out.println(s);
            System.out.println("飞机自驾游");
        });

    }

    private static void useFlyable(Flyable f) {
        f.fly("风和日丽,晴空万里");
    }
}

7.5Lambda表达式练习3【应用】

  • 练习描述
    有参有返回值抽象方法的练习
  • 操作步骤
    • 定义一个接口(Addable),里面定义一个抽象方法:int add(int x,int y);
    • 定义一个测试类(AddableDemo),在测试类中提供两个方法
      • 一个方法是:useAddable(Addable a)
      • 一个方法是主方法,在主方法中调用useAddable方法
  • 示例代码
public interface Addable {
    int add(int x,int y);
}

public class AddableDemo {
    public static void main(String[] args) {
        //在主方法中调用useAddable方法
        useAddable((int x,int y) -> {
            return x + y;
        });

    }

    private static void useAddable(Addable a) {
        int sum = a.add(10, 20);
        System.out.println(sum);
    }
}

7.6Lambda表达式的省略模式【应用】

  • 省略的规则
    • 参数类型可以省略。但是有多个参数的情况下,不能只省略一个
    • 如果参数有且仅有一个,那么小括号可以省略
    • 如果代码块的语句只有一条,可以省略大括号和分号,和return关键字
  • 代码演示
public interface Addable {
    int add(int x, int y);
}

public interface Flyable {
    void fly(String s);
}

public class LambdaDemo {
    public static void main(String[] args) {
//        useAddable((int x,int y) -> {
//            return x + y;
//        });
        //参数的类型可以省略
        useAddable((x, y) -> {
            return x + y;
        });

//        useFlyable((String s) -> {
//            System.out.println(s);
//        });
        //如果参数有且仅有一个,那么小括号可以省略
//        useFlyable(s -> {
//            System.out.println(s);
//        });

        //如果代码块的语句只有一条,可以省略大括号和分号
        useFlyable(s -> System.out.println(s));

        //如果代码块的语句只有一条,可以省略大括号和分号,如果有return,return也要省略掉
        useAddable((x, y) -> x + y);
    }

    private static void useFlyable(Flyable f) {
        f.fly("风和日丽,晴空万里");
    }

    private static void useAddable(Addable a) {
        int sum = a.add(10, 20);
        System.out.println(sum);
    }
}

7.7Lambda表达式的使用前提【理解】

  • 使用Lambda必须要有接口
  • 并且要求接口中有且仅有一个抽象方法

7.8Lambda表达式和匿名内部类的区别【理解】

  • 所需类型不同
    • 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
    • Lambda表达式:只能是接口
  • 使用限制不同
    • 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
    • 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
  • 实现原理不同
    • 匿名内部类:编译之后,产生一个单独的.class字节码文件
    • Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成

8.缓存池

new Integer(123) 与 Integer.valueOf(123) 的区别在于:

  • new Integer(123) 每次都会新建一个对象
  • Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。

Integer x = new Integer(123);
Integer y = new Integer(123);
System.out.println(x == y);    // false
Integer z = Integer.valueOf(123);
Integer k = Integer.valueOf(123);
System.out.println(z == k);   // true

valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。

返回一个表示指定 int 值的 Integer 实例。 如果不需要新的 Integer 实例,则通常应优先使用此方法而不是构造函数 Integer(int),因为此方法可能会通过缓存频繁请求的值来显着提高空间和时间性能。 此方法将始终缓存 -128 到 127(含)范围内的值,并且可能缓存此范围之外的其他值。

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

在 Java 8 中,Integer 缓存池的大小默认为 -128~127。

static final int low = -128;
static final int high;
static final Integer cache[];

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

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

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

编译器会在缓冲池范围内的基本类型自动装箱过程调用 valueOf() 方法,因此多个 Integer 实例使用自动装箱来创建并且值相同,那么就会引用相同的对象。

Integer m = 123;
Integer n = 123;
System.out.println(m == n); // true

基本类型对应的缓冲池如下:

  • boolean values true and false
  • all byte values
  • short values between -128 and 127
  • int values between -128 and 127
  • char in the range \u0000 to \u007F

在使用这些基本类型对应的包装类型时,就可以直接使用缓冲池中的对象。

如果在缓冲池之外:

Integer m = 323;
Integer n = 323;
System.out.println(m == n); // false

二. API

2.1API概述

  • 什么是API:application programming interface:应用程序编程接口。
  • java中的API:值得是jdk中提供的各种功能的java类,这些类将底层的实现封装了起来。

Math

概述:无构造方法,内部的方法都是静态的,可以通过类名.方法名进行调用

方法名

说明

public static int abs(int a)

返回参数的绝对值

double ceil(double a)

返回大于等于参数的最小整数

double floor(double a)

返回小于等于参数的最大整数

int round(float a)

四舍五入

int max(int a, int b)

返回较大值

int min(int a,int b)

返回较小值

double pow(double a,double b)

a的b次幂

double random()

【0.0,1.0)

System

  • System类的常用方法

方法名

说明

public static void exit(int status)

终止当前运行的java虚拟机,非零表示异常终止

public static long currentTimeMills()

返回当前时间(毫秒值)

s

  • SimpleDateFormat类概述
    SimpleDateFormat是一个具体的类,用于以区域设置敏感的方式格式化和解析日期。
    我们重点学习日期格式化和解析
  • SimpleDateFormat类构造方法

方法名

说明

public   SimpleDateFormat()

构造一个SimpleDateFormat,使用默认模式和日期格式

public SimpleDateFormat(String pattern)

构造一个SimpleDateFormat使用给定的模式和默认的日期格式

  • SimpleDateFormat类的常用方法
    • 格式化(从Date到String)
      • public final String format(Date date):将日期格式化成日期/时间字符串
    • 解析(从String到Date)
      • public Date parse(String source):从给定字符串的开始解析文本以生成日期
  • 示例代码
import java.text.SimpleDateFormat;
import java.util.Date;

public class SimpleDataFormatDemo {
    public static void main(String[] args) throws ParseException {
        //public Date():分配一个 Date对象,并初始化,以便它代表它被分配的时间,精确到毫秒
        Date d = new Date();
        //public long getTime():获取的是日期对象从1970年1月1日 00:00:00到现在的毫秒值
        System.out.println(d.getTime());
        System.out.println(d.getTime() * 1.0 / 1000 / 60 / 60 / 24 / 365 + "年");
        System.out.println("================");

        //public Date(long date):分配一个 Date对象,并将其初始化为表示从标准基准时间起指定的毫秒数
        long time = 1000 * 60 * 60;
        long l = System.currentTimeMillis();
        d.setTime(l);
        System.out.println(d);
        System.out.println("==================");

        SimpleDateFormat sdf1 = new SimpleDateFormat();
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年MM月dd日HH:mm:ss");

        String format1 = sdf1.format(d);
        String format2 = sdf2.format(d);
        System.out.println(format1);
        System.out.println(format2);

        String s = "2048-08-08 11:11:11";
        SimpleDateFormat sdf3 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date format3 = sdf3.parse(s);
        System.out.println(format3);
    }
}

Jdk8时间日期类:

  • LocalDate          表示日期(年月日)
  • LocalTime          表示时间(时分秒)
  • LocalDateTime  日期+时间

string类

  1. String 类在 java.lang 包下,所以使用的时候不需要导包,被声明为final,不可被继承
  2. String 类代表字符串,Java 程序中所有的双引号字符串,都是 String 类的对象
  3. 内部使用char 字符串不可变,它们的值在创建后不能被更改
原始注解:
String 类表示字符串。 Java 程序中的所有字符串文字,例如“abc”,都是作为此类的实例实现的。
字符串是常量;它们的值在创建后无法更改。字符串缓冲区支持可变字符串。因为 String 对象是不可变的,所以它们可以被共享。例如:
       字符串 str = "abc";
相当于:
       字符数据[] = {'a', 'b', 'c'};
       字符串 str = 新字符串(数据);
   
以下是有关如何使用字符串的更多示例:
       System.out.println("abc");
       String cde = "cde";
       System.out.println("abc" + cde);
       String c = "abc".substring(2,3);
       String d = cde.substring(1, 2);
   
String 类包括检查序列的单个字符、比较字符串、搜索字符串、提取子字符串以及创建字符串副本的方法,其中所有字符都转换为大写或小写。大小写映射基于 Character 类指定的 Unicode 标准版本。
Java 语言为字符串连接运算符 (+) 以及将其他对象转换为字符串提供了特殊支持。字符串连接是通过 StringBuilder(或 StringBuffer)类及其 append 方法实现的。字符串转换是通过 toString 方法实现的,由 Object 定义并由 Java 中的所有类继承。有关字符串连接和转换的更多信息,请参阅 Gosling、Joy 和 Steele,Java 语言规范。
除非另有说明,否则将 null 参数传递给此类中的构造函数或方法将导致抛出 NullPointerException。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

不可变的好处:

1. 可以缓存 hash 值

因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。

2. String Pool 的需要

如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。

3. 安全性

String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。

4. 线程安全

String 不可变性天生具备线程安全,可以在多个线程中安全地使用。(StringBuffer同样安全,synchronized锁)

参考:Program Creek : Why String is immutable in Java?

package cn.jian.test.api;

public class demo_string {
    /*
        String类常见构造方法:

            public String() : 创建一个空白字符串对象,不含有任何内容
            public String(char[] chs) : 根据字符数组的内容,来创建字符串对象
            public String(String original) : 根据传入的字符串内容,来创建字符串对象
            String s = “abc”;  直接赋值的方式创建字符串对象,内容就是abc
         注意:
                String这个类比较特殊, 打印其对象名的时候, 不会出现内存地址
                而是该对象所记录的真实内容.
                面向对象-继承, Object类
     */
    public static void main(String[] args) {
        // public String() : 创建一个空白字符串对象,不含有任何内容
        String s1 = new String();
        System.out.println(s1);

        // public String(char[] chs) : 根据字符数组的内容,来创建字符串对象
        char[] chs = {'a','b','c'};
        String s2 = new String(chs);
        System.out.println(s2);         //abc

        // public String(String original) : 根据传入的字符串内容,来创建字符串对象
        String s3 = new String("123");
        System.out.println(s3);         //123
    } 
}

String, StringBuffer and StringBuilder

1. 可变性

  • String 不可变  String类中使用final关键字修饰字符数组来保存字符串, private final char[]  value。Java9之后,改为private final byte[] value.
  • StringBuffer 和 StringBuilder 可变 都继承自AbstractStringBuilder类,其中也是使用字符数组保存字符串 char[]  value ,没有用final关键词修饰。

2. 线程安全

  • String 不可变,因此是线程安全的。每次对 String 类型进⾏改变的时候,都会⽣成⼀个新的 String 对象,然后将指针指向新的 String 对象。
  • StringBuffer 是线程安全的,对⽅法加了同步锁或者对调⽤的⽅法加了同步锁,所以是线程安全的
  • StringBuilder 不是线程安全的

StackOverflow : String, StringBuffer, and StringBuilder

String.intern()

使用 String.intern() 可以保证相同内容的字符串变量引用同一的内存对象。

下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同对象,而 s3 是通过 s1.intern() 方法取得一个对象引用。intern() 首先把 s1 引用的对象放到 String Pool(字符串常量池)中,然后返回这个对象引用。因此 s3 和 s1 引用的是同一个字符串常量池的对象。

String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2);           // false
String s3 = s1.intern();
System.out.println(s1.intern() == s3);  // true

如果是采用 “bbb” 这种使用双引号的形式创建字符串实例,会自动地将新建的对象放入 String Pool 中

String s4 = "bbb";
String s5 = "bbb";
System.out.println(s4 == s5);  // true

在 Java 7 之前,字符串常量池被放在运行时常量池中,它属于永久代。而在 Java 7,字符串常量池被移到 Native Method 中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。

StringBuilder类

package cn.jian.test.api;

public class duilder {
    public static void main(String[] args) {
        //public StringBuilder():创建一个空白可变字符串对象,不含有任何内容
        StringBuilder sb = new StringBuilder();
        System.out.println("sb:" + sb);                     //sb:
        System.out.println("sb.length():" + sb.length());   //sb.length():0

        //public StringBuilder(String str):根据字符串的内容,来创建可变字符串对象
        StringBuilder sb2 = new StringBuilder("hello");
        System.out.println("sb2:" + sb2);                       //sb2:hello
        System.out.println("sb2.length():" + sb2.length());     //sb2.length():5

        sb.append("hello").append("world").append("java").append(100);//链式编程
        sb.reverse();											//相反的字符序列
        System.out.println("sb:" + sb);                     //sb:001avajdlrowolleh
    }
}

2.2Object通用方法

public final native Class<?> getClass()

public native int hashCode()

public boolean equals(Object obj)

protected native Object clone() throws CloneNotSupportedException

public String toString()

public final native void notify()

public final native void notifyAll()

public final native void wait(long timeout) throws InterruptedException

public final void wait(long timeout, int nanos) throws InterruptedException

public final void wait() throws InterruptedException

protected void finalize() throws Throwable {}

Objects

常用方法

方法名

说明

public static String toString(对象)

返回参数中对象的字符串表示形式。

public static String toString(对象, 默认字符串)

返回对象的字符串表示形式。

public static Boolean isNull(对象)

判断对象是否为空

public static Boolean nonNull(对象)

判断对象是否不为空

package cn.jian.test.api;

import java.util.Objects;

public class ObjectsDemo {
    public static void main(String[] args) {
        //public static String toString (对象):返回参数中对象的字符串表示形式。
        Student s1 = new Student("小罗同学", 50);
        String result = Objects.toString(s1);
        System.out.println(result);
        System.out.println(s1);

        //public static String toString (对象, 默认字符串):返回对象的字符串表示形式。如果对象为空, 那么返回第二个参数.
        Student s2 = new Student("小花同学", 23);
        Student s2 = null;
        String result2 = Objects.toString(s2, "随便写一个");
        System.out.println(result2);

        //public static Boolean isNull (对象):判断对象是否为空
        Student s3 = null;
        Student s3 = new Student();
        boolean result3 = Objects.isNull(s3);
        System.out.println(result3);

        //public static Boolean nonNull (对象):判断对象是否不为空
        Student s4 = new Student();
        Student s4 = null;
        boolean result4 = Objects.nonNull(s4);
        System.out.println(result4);
    }
}

2.3 包装类

  • 基本类型对应的包装类

基本数据类型

包装类

byte

Byte

short

Short

int

Integer

long

Long

float

Float

double

Double

char

Character

boolean

Boolean

int和String类型的相互转换(记忆)

  • int转换为String
    • 转换方式
      • 方式一:直接在数字后加一个空字符串
      • 方式二:通过String类静态方法valueOf()
    • 示例代码
public class IntegerDemo {
    public static void main(String[] args) {
        //int --- String
        int number = 100;
        //方式1
        String s1 = number + "";
        System.out.println(s1);
        //方式2
        //public static String valueOf(int i)
        String s2 = String.valueOf(number);
        System.out.println(s2);
        System.out.println("--------");
    }
}

  • String转换为int
    • 转换方式
      • 方式一:先将字符串数字转成Integer,再调用valueOf()方法
      • 方式二:通过Integer静态方法parseInt()进行转换
    • 示例代码
public class IntegerDemo {
    public static void main(String[] args) {
        //String --- int
        String s = "100";
        //方式1:String --- Integer --- int
        Integer i = Integer.valueOf(s);
        //public int intValue()
        int x = i.intValue();
        System.out.println(x);
        //方式2
        //public static int parseInt(String s)
        int y = Integer.parseInt(s);
        System.out.println(y);
    }
}

2.4关键字

final

  • fianl关键字的作用
    • final代表最终的意思,可以修饰成员方法,成员变量,类
  • final修饰类、方法、变量的效果
    • fianl修饰类:该类不能被继承(不能有子类,但是可以有父类)
    • final修饰方法:该方法不能被重写
    • final修饰变量:表明该变量是一个常量,不能再次赋值
      • 变量是基本类型,不能改变的是值
      • 变量是引用类型,不能改变的是地址值,但地址里面的内容是可以改变的
      • 举例
public static void main(String[] args){
    final Student s = new Student(23);
  	s = new Student(24);  // 错误
 	s.setAge(24);  // 正确
}

static

特点:

  • 被类的所有对象共享
    是我们判断是否使用静态关键字的条件
  • 随着类的加载而加载,优先于对象存在
    对象需要类被加载后,才能创建
  • 可以通过类名调用
    也可以通过对象名调用

三.异常

3.1 异常(记忆)

  • 异常的概述
    异常就是程序出现了不正常的情况
  • 异常的体系结构

3.2 编译时异常和运行时异常的区别(记忆)

  • 编译时异常
    • 都是Exception类及其子类
    • 必须显示处理,否则程序就会发生错误,无法通过编译
  • 运行时异常
    • 都是RuntimeException类及其子类
    • 无需显示处理,也可以和编译时异常一样处理
  • 图示

3.3 JVM默认处理异常的方式(理解)

  • 如果程序出现了问题,我们没有做任何处理,最终JVM 会做默认的处理,处理方式有如下两个步骤:
    • 把异常的名称,错误原因及异常出现的位置等信息输出在了控制台
    • 程序停止执行

3.4 查看异常信息 (理解)

控制台在打印异常信息时,会打印异常类名,异常出现的原因,异常出现的位置

我们调bug时,可以根据提示,找到异常出现的位置,分析原因,修改异常代码

3.5 throws方式处理异常(应用)

  • 定义格式
public void 方法() throws 异常类名 {
    
}

  • 示例代码
public class ExceptionDemo {
    public static void main(String[] args) throws ParseException{
        System.out.println("开始");
//        method();
          method2();

        System.out.println("结束");
    }

    //编译时异常
    public static void method2() throws ParseException {
        String s = "2048-08-09";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Date d = sdf.parse(s);
        System.out.println(d);
    }

    //运行时异常
    public static void method() throws ArrayIndexOutOfBoundsException {
        int[] arr = {1, 2, 3};
        System.out.println(arr[3]);
    }
}

  • 注意事项
    • 这个throws格式是跟在方法的括号后面的
    • 编译时异常必须要进行处理,两种处理方案:try…catch …或者 throws,如果采用 throws 这种方案,在方法上进行显示声明,将来谁调用这个方法谁处理
    • 运行时异常因为在运行时才会发生,所以在方法后面可以不写,运行时出现异常默认交给jvm处理

3.6 throw抛出异常 (应用)

  • 格式
    throw new 异常();
  • 注意
    这个格式是在方法内的,表示当前代码手动抛出一个异常,下面的代码不用再执行了
  • throws和throw的区别

throws

throw

用在方法声明后面,跟的是异常类名

用在方法体内,跟的是异常对象名

表示声明异常,调用该方法有可能会出现这样的异常

表示手动抛出异常对象,由方法体内的语句处理

  • 示例代码
public class ExceptionDemo8 {
    public static void main(String[] args) {
        //int [] arr = {1,2,3,4,5};
        int [] arr = null;
        printArr(arr);//就会 接收到一个异常.
                        //我们还需要自己处理一下异常.
    }

    private static void printArr(int[] arr) {
        if(arr == null){
            //调用者知道成功打印了吗?
            //System.out.println("参数不能为null");
            throw new NullPointerException(); //当参数为null的时候
                                            //手动创建了一个异常对象,抛给了调用者,产生了一个异常
        }else{
            for (int i = 0; i < arr.length; i++) {
                System.out.println(arr[i]);
            }
        }
    }

}

3.7 try-catch方式处理异常(应用)

  • 定义格式
try {
	可能出现异常的代码;
} catch(异常类名 变量名) {
	异常的处理代码;
}

  • 执行流程
    • 程序从 try 里面的代码开始执行
    • 出现异常,就会跳转到对应的 catch 里面去执行
    • 执行完毕之后,程序还可以继续往下执行
  • 示例代码
public class ExceptionDemo01 {
    public static void main(String[] args) {
        System.out.println("开始");
        method();
        System.out.println("结束");
    }

    public static void method() {
        try {
            int[] arr = {1, 2, 3};
            System.out.println(arr[3]);
            System.out.println("这里能够访问到吗");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("你访问的数组索引不存在,请回去修改为正确的索引");
        }
    }
}

  • 注意
    1. 如果 try 中没有遇到问题,怎么执行?
      会把try中所有的代码全部执行完毕,不会执行catch里面的代码
    2. 如果 try 中遇到了问题,那么 try 下面的代码还会执行吗?
      那么直接跳转到对应的catch语句中,try下面的代码就不会再执行了
      当catch里面的语句全部执行完毕,表示整个体系全部执行完全,继续执行下面的代码
    3. 如果出现的问题没有被捕获,那么程序如何运行?
      那么try…catch就相当于没有写.那么也就是自己没有处理.
      默认交给虚拟机处理.
    4. 同时有可能出现多个异常怎么处理?
      出现多个异常,那么就写多个catch就可以了.
      注意点:如果多个异常之间存在子父类关系.那么父类一定要写在下面

3.8 Throwable成员方法(应用)

  • 常用方法

方法名

说明

public String getMessage()

返回此 throwable 的详细消息字符串

public String toString()

返回此可抛出的简短描述

public void printStackTrace()

把异常的错误信息输出在控制台

  • 示例代码
public class ExceptionDemo02 {
    public static void main(String[] args) {
        System.out.println("开始");
        method();
        System.out.println("结束");
    }

    public static void method() {
        try {
            int[] arr = {1, 2, 3};
            System.out.println(arr[3]); //new ArrayIndexOutOfBoundsException();
            System.out.println("这里能够访问到吗");
        } catch (ArrayIndexOutOfBoundsException e) { //new ArrayIndexOutOfBoundsException();
//            e.printStackTrace();

            //public String getMessage():返回此 throwable 的详细消息字符串
//            System.out.println(e.getMessage());
            //Index 3 out of bounds for length 3

            //public String toString():返回此可抛出的简短描述
//            System.out.println(e.toString());
            //java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3

            //public void printStackTrace():把异常的错误信息输出在控制台
            e.printStackTrace();
//            java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
//            at com.itheima_02.ExceptionDemo02.method(ExceptionDemo02.java:18)
//            at com.itheima_02.ExceptionDemo02.main(ExceptionDemo02.java:11)

        }
    }
}

3.9 异常的练习 (应用)

  • 需求
    键盘录入学生的姓名和年龄,其中年龄为18 – 25岁,超出这个范围是异常数据不能赋值.需要重新录入,一直录到正确为止
  • 实现步骤
    1. 创建学生对象
    2. 键盘录入姓名和年龄,并赋值给学生对象
    3. 如果是非法数据就再次录入
  • 代码实现
    学生类
public class Student {
    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) {
        if(age >= 18 && age <= 25){
            this.age = age;
        }else{
            //当年龄不合法时,产生一个异常
            throw new RuntimeException("年龄超出了范围");
        }
    }

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


测试类

public class ExceptionDemo12 {
    public static void main(String[] args) {
        // 键盘录入学生的姓名和年龄,其中年龄为 18 - 25岁,
        // 超出这个范围是异常数据不能赋值.需要重新录入,一直录到正确为止。

        Student s = new Student();

        Scanner sc = new Scanner(System.in);
        System.out.println("请输入姓名");
        String name = sc.nextLine();
        s.setName(name);
       while(true){
           System.out.println("请输入年龄");
           String ageStr = sc.nextLine();
           try {
               int age = Integer.parseInt(ageStr);
               s.setAge(age);
               break;
           } catch (NumberFormatException e) {
               System.out.println("请输入一个整数");
               continue;
           } catch (AgeOutOfBoundsException e) {
               System.out.println(e.toString());
               System.out.println("请输入一个符合范围的年龄");
               continue;
           }
           /*if(age >= 18 && age <=25){
               s.setAge(age);
               break;
           }else{
               System.out.println("请输入符合要求的年龄");
               continue;
           }*/
       }
        System.out.println(s);

    }
}

3.10 自定义异常(应用)

  • 自定义异常概述
    当Java中提供的异常不能满足我们的需求时,我们可以自定义异常
  • 实现步骤
    1. 定义异常类
    2. 写继承关系
    3. 提供空参构造
    4. 提供带参构造
  • 代码实现
    异常类
public class AgeOutOfBoundsException extends RuntimeException {
    public AgeOutOfBoundsException() {
    }

    public AgeOutOfBoundsException(String message) {
        super(message);
    }
}


学生类

public class Student {
    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) {
        if(age >= 18 && age <= 25){
            this.age = age;
        }else{
            //如果Java中提供的异常不能满足我们的需求,我们可以使用自定义的异常
            throw new AgeOutOfBoundsException("年龄超出了范围");
        }
    }

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


测试类

public class ExceptionDemo12 {
    public static void main(String[] args) {
        // 键盘录入学生的姓名和年龄,其中年龄为 18 - 25岁,
        // 超出这个范围是异常数据不能赋值.需要重新录入,一直录到正确为止。

        Student s = new Student();

        Scanner sc = new Scanner(System.in);
        System.out.println("请输入姓名");
        String name = sc.nextLine();
        s.setName(name);
       while(true){
           System.out.println("请输入年龄");
           String ageStr = sc.nextLine();
           try {
               int age = Integer.parseInt(ageStr);
               s.setAge(age);
               break;
           } catch (NumberFormatException e) {
               System.out.println("请输入一个整数");
               continue;
           } catch (AgeOutOfBoundsException e) {
               System.out.println(e.toString());
               System.out.println("请输入一个符合范围的年龄");
               continue;
           }
           /*if(age >= 18 && age <=25){
               s.setAge(age);
               break;
           }else{
               System.out.println("请输入符合要求的年龄");
               continue;
           }*/
       }
        System.out.println(s);

    }
}

四.反射

每个类都有一个Class对象,包含了与类有关的信息,当编译一个新类时,会产生一个同名的.class文件,该文件内容保存着Class对象。

类加载相当于Class对象的加载,类只有在第一次使用时才会动态加载到jvm中

五. 集合

容器介绍

容器,就是可以容纳其他Java对象的对象。Java Collections Framework(JCF)为Java开发者提供了通用的容器,其始于JDK 1.2,优点是:

  • 降低编程难度
  • 提高程序性能
  • 提高API间的互操作性
  • 降低学习难度
  • 降低设计和实现相关API的难度
  • 增加程序的重用性

Java容器里只能放对象,对于基本类型(int, long, float, double等),需要将其包装成对象类型后(Integer, Long, Float, Double等)才能放到容器里。很多时候拆包装和解包装能够自动完成。这虽然会导致额外的性能和空间开销,但简化了设计和编程。

1.Collection

容器主要包括Collection和Map两种,Collection存储着对象的集合,Map存储着键值对的映射

1.1数组和集合的区别【理解】

  • 相同点
    都是容器,可以存储多个数据
  • 不同点
    • 数组的长度是不可变的,集合的长度是可变的
    • 数组可以存基本数据类型和引用数据类型
      集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类

1.2集合类体系结构【理解】

1.3Collection 集合概述和使用【应用】

  • Collection集合概述
    • 单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素
    • JDK 不提供此接口的任何直接实现.它提供更具体的子接口(如Set和List)实现
  • 创建Collection集合的对象
    • 多态的方式
    • 具体的实现类ArrayList
  • Collection集合常用方法

方法名

说明

boolean add(E e)

添加元素

boolean remove(Object o)

从集合中移除指定的元素

boolean removeIf(Object o)

根据条件进行移除

void   clear()

清空集合中的元素

boolean contains(Object o)

判断集合中是否存在指定的元素

boolean isEmpty()

判断集合是否为空

int size()

集合的长度,也就是集合中元素的个数

1.4Collection集合的遍历【应用】

  • 迭代器介绍
    • 迭代器,集合的专用遍历方式
    • Iterator iterator(): 返回此集合中元素的迭代器,通过集合对象的iterator()方法得到
  • Iterator中的常用方法
    boolean hasNext(): 判断当前位置是否有元素可以被取出
    E next(): 获取当前位置的元素,将迭代器对象移向下一个索引位置
  • Collection集合的遍历
public class IteratorDemo1 {
    public static void main(String[] args) {
        //创建集合对象
        Collection<String> c = new ArrayList<>();

        //添加元素
        c.add("hello");
        c.add("world");
        c.add("java");
        c.add("javaee");

        //Iterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
        Iterator<String> it = c.iterator();

        //用while循环改进元素的判断和获取
        while (it.hasNext()) {
            String s = it.next();
            System.out.println(s);
        }
    }
}

  • 迭代器中删除的方法
    void remove(): 删除迭代器对象当前指向的元素
public class IteratorDemo2 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("b");
        list.add("c");
        list.add("d");

        Iterator<String> it = list.iterator();
        while(it.hasNext()){
            String s = it.next();
            if("b".equals(s)){
                //指向谁,那么此时就删除谁.
                it.remove();
            }
        }
        System.out.println(list);
    }
}

1.5增强for循环【应用】

  • 介绍
    • 它是JDK5之后出现的,其内部原理是一个Iterator迭代器
    • 实现Iterable接口的类才可以使用迭代器和增强for
    • 简化数组和Collection集合的遍历
  • 格式
    for(集合/数组中元素的数据类型 变量名 :  集合/数组名) {
    // 已经将当前遍历到的元素封装到变量中了,直接使用变量即可
    }
  • 代码
public class MyCollectonDemo1 {
    public static void main(String[] args) {
        ArrayList<String> list =  new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        list.add("f");

        //1,数据类型一定是集合或者数组中元素的类型
        //2,str仅仅是一个变量名而已,在循环的过程中,依次表示集合或者数组中的每一个元素
        //3,list就是要遍历的集合或者数组
        for(String str : list){
            System.out.println(str);
        }
    }
}

2.List集合

2.1List集合的概述和特点【记忆】

  • List集合的概述
    • 有序集合,这里的有序指的是存取顺序
    • 用户可以精确控制列表中每个元素的插入位置,用户可以通过整数索引访问元素,并搜索列表中的元素
    • 与Set集合不同,列表通常允许重复的元素
  • List集合的特点
    • 存取有序
    • 可以重复
    • 有索引

2.2List集合的特有方法【应用】

方法名

描述

void add(int index,E   element)

在此集合中的指定位置插入指定的元素

E remove(int   index)

删除指定索引处的元素,返回被删除的元素

E set(int index,E   element)

修改指定索引处的元素,返回被修改的元素

E get(int   index)

返回指定索引处的元素

3.数据结构

3.1数据结构之栈和队列【记忆】

  • 栈结构
    先进后出
  • 队列结构
    先进先出

3.2数据结构之数组和链表【记忆】

  • 数组结构
    查询快、增删慢
  • 队列结构
    查询慢、增删快

4.List集合的实现类

4.1List集合子类的特点【记忆】

  • ArrayList集合
    底层是数组结构实现,查询快、增删慢
  • LinkedList集合
    底层是链表结构实现,查询慢、增删快

4.2LinkedList集合的特有功能【应用】

  • 特有方法

方法名

说明

public void addFirst(E e)

在该列表开头插入指定的元素

public void addLast(E e)

将指定的元素追加到此列表的末尾

public E getFirst()

返回此列表中的第一个元素

public   E getLast()

返回此列表中的最后一个元素

public E removeFirst()

从此列表中删除并返回第一个元素

public   E removeLast()

从此列表中删除并返回最后一个元素

5.泛型

5.1泛型概述【理解】

  • 泛型的介绍
    泛型是JDK5中引入的特性,它提供了编译时类型安全检测机制
  • 泛型的好处
    1. 把运行时期的问题提前到了编译期间
    2. 避免了强制类型转换
  • 泛型的定义格式
    • <类型>: 指定一种类型的格式.尖括号里面可以任意书写,一般只写一个字母.例如:  
    • <类型1,类型2…>: 指定多种类型的格式,多种类型之间用逗号隔开.例如: <E,T> <K,V>

5.2泛型类【应用】

  • 定义格式
修饰符 class 类名<类型> {  }
  • 示例代码
    • 泛型类
public class Generic<T> {
    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}

    • 测试类
public class GenericDemo1 {
    public static void main(String[] args) {
        Generic<String> g1 = new Generic<String>();
        g1.setT("杨幂");
        System.out.println(g1.getT());

        Generic<Integer> g2 = new Generic<Integer>();
        g2.setT(30);
        System.out.println(g2.getT());

        Generic<Boolean> g3 = new Generic<Boolean>();
        g3.setT(true);
        System.out.println(g3.getT());
    }
}

5.3泛型方法【应用】

  • 定义格式
修饰符 <类型> 返回值类型 方法名(类型 变量名) {  }
  • 示例代码
    • 带有泛型方法的类
public class Generic {
    public <T> void show(T t) {
        System.out.println(t);
    }
}

    • 测试类
public class GenericDemo2 {
    public static void main(String[] args) {
	    Generic g = new Generic();
        g.show("柳岩");
        g.show(30);
        g.show(true);
        g.show(12.34);
    }
}

5.4泛型接口【应用】

  • 定义格式
修饰符 interface 接口名<类型> {  }

  • 示例代码
    • 泛型接口
public interface Generic<T> {
    void show(T t);
}

    • 泛型接口实现类1
      定义实现类时,定义和接口相同泛型,创建实现类对象时明确泛型的具体类型
public class GenericImpl1<T> implements Generic<T> {
    @Override
    public void show(T t) {
        System.out.println(t);
    }
}

    • 泛型接口实现类2
      定义实现类时,直接明确泛型的具体类型
public class GenericImpl2 implements Generic<Integer>{
     @Override
     public void show(Integer t) {
          System.out.println(t);
     }
}

    • 测试类
public class GenericDemo3 {
    public static void main(String[] args) {
        GenericImpl1<String> g1 = new GenericImpl<String>();
        g1.show("林青霞");
        GenericImpl1<Integer> g2 = new GenericImpl<Integer>();
        g2.show(30);
      
        GenericImpl2 g3 = new GenericImpl2();
      	g3.show(10);
    }
}

5.5类型通配符

  • 类型通配符: <?>
    • ArrayList<?>: 表示元素类型未知的ArrayList,它的元素可以匹配任何的类型
    • 但是并不能把元素添加到ArrayList中了,获取出来的也是父类类型
  • 类型通配符上限: <? extends 类型>
    • ArrayListList <? extends Number>: 它表示的类型是Number或者其子类型
  • 类型通配符下限: <? super 类型>
    • ArrayListList <? super Number>: 它表示的类型是Number或者其父类型
  • 泛型通配符的使用
public class GenericDemo4 {
    public static void main(String[] args) {
        ArrayList<Integer> list1 = new ArrayList<>();
        ArrayList<String> list2 = new ArrayList<>();
        ArrayList<Number> list3 = new ArrayList<>();
        ArrayList<Object> list4 = new ArrayList<>();

        method(list1);
        method(list2);
        method(list3);
        method(list4);

        getElement1(list1);
        getElement1(list2);//报错
        getElement1(list3);
        getElement1(list4);//报错

        getElement2(list1);//报错
        getElement2(list2);//报错
        getElement2(list3);
        getElement2(list4);
    }
  
    // 泛型通配符: 此时的泛型?,可以是任意类型
    public static void method(ArrayList<?> list){}
    // 泛型的上限: 此时的泛型?,必须是Number类型或者Number类型的子类
    public static void getElement1(ArrayList<? extends Number> list){}
    // 泛型的下限: 此时的泛型?,必须是Number类型或者Number类型的父类
    public static void getElement2(ArrayList<? super Number> list){}

}

——————————————————

1.Set集合

1.1Set集合概述和特点【应用】

  • 不可以存储重复元素
  • 没有索引,不能使用普通for循环遍历

1.2Set集合的使用【应用】

存储字符串并遍历

public class MySet1 {
    public static void main(String[] args) {
      	//创建集合对象
        Set<String> set = new TreeSet<>();
      	//添加元素
        set.add("ccc");
        set.add("aaa");
        set.add("aaa");
        set.add("bbb");

//        for (int i = 0; i < set.size(); i++) {
//            //Set集合是没有索引的,所以不能使用通过索引获取元素的方法
//        }
      
      	//遍历集合
        Iterator<String> it = set.iterator();
        while (it.hasNext()){
            String s = it.next();
            System.out.println(s);
        }
        System.out.println("-----------------------------------");
        for (String s : set) {
            System.out.println(s);
        }
    }
}

2.TreeSet集合

2.1TreeSet集合概述和特点【应用】

  • 不可以存储重复元素
  • 没有索引
  • 可以将元素按照规则进行排序
    • TreeSet():根据其元素的自然排序进行排序
    • TreeSet(Comparator comparator) :根据指定的比较器进行排序

2.2TreeSet集合基本使用【应用】

存储Integer类型的整数并遍历

public class TreeSetDemo01 {
    public static void main(String[] args) {
        //创建集合对象
        TreeSet<Integer> ts = new TreeSet<Integer>();

        //添加元素
        ts.add(10);
        ts.add(40);
        ts.add(30);
        ts.add(50);
        ts.add(20);

        ts.add(30);

        //遍历集合
        for(Integer i : ts) {
            System.out.println(i);
        }
    }
}

2.3自然排序Comparable的使用【应用】

  • 案例需求
    • 存储学生对象并遍历,创建TreeSet集合使用无参构造方法
    • 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
  • 实现步骤
    1. 使用空参构造创建TreeSet集合
      • 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
    1. 自定义的Student类实现Comparable接口
      • 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法
    1. 重写接口中的compareTo方法
      • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
  • 代码实现
    学生类
public class Student implements Comparable<Student>{
    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 +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        //按照对象的年龄进行排序
        //主要判断条件: 按照年龄从小到大排序
        int result = this.age - o.age;
        //次要判断条件: 年龄相同时,按照姓名的字母顺序排序
        result = result == 0 ? this.name.compareTo(o.getName()) : result;
        return result;
    }
}


测试类

public class MyTreeSet2 {
    public static void main(String[] args) {
        //创建集合对象
        TreeSet<Student> ts = new TreeSet<>();
	    //创建学生对象
        Student s1 = new Student("zhangsan",28);
        Student s2 = new Student("lisi",27);
        Student s3 = new Student("wangwu",29);
        Student s4 = new Student("zhaoliu",28);
        Student s5 = new Student("qianqi",30);
		//把学生添加到集合
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);
		//遍历集合
        for (Student student : ts) {
            System.out.println(student);
        }
    }
}

2.4比较器排序Comparator的使用【应用】

  • 案例需求
    • 存储老师对象并遍历,创建TreeSet集合使用带参构造方法
    • 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
  • 实现步骤
    • 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
    • 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
    • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
  • 代码实现
    老师类
public class Teacher {
    private String name;
    private int age;

    public Teacher() {
    }

    public Teacher(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 "Teacher{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}


测试类

public class MyTreeSet4 {
    public static void main(String[] args) {
      	//创建集合对象
        TreeSet<Teacher> ts = new TreeSet<>(new Comparator<Teacher>() {
            @Override
            public int compare(Teacher o1, Teacher o2) {
                //o1表示现在要存入的那个元素
                //o2表示已经存入到集合中的元素
              
                //主要条件
                int result = o1.getAge() - o2.getAge();
                //次要条件
                result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
                return result;
            }
        });
		//创建老师对象
        Teacher t1 = new Teacher("zhangsan",23);
        Teacher t2 = new Teacher("lisi",22);
        Teacher t3 = new Teacher("wangwu",24);
        Teacher t4 = new Teacher("zhaoliu",24);
		//把老师添加到集合
        ts.add(t1);
        ts.add(t2);
        ts.add(t3);
        ts.add(t4);
		//遍历集合
        for (Teacher teacher : ts) {
            System.out.println(teacher);
        }
    }
}

2.4两种比较方式总结【记忆】

  • 两种比较方式小结
    • 自然排序: 自定义类实现Comparable接口,重写compareTo方法,根据返回值进行排序
    • 比较器排序: 创建TreeSet对象的时候传递Comparator的实现类对象,重写compare方法,根据返回值进行排序
    • 在使用的时候,默认使用自然排序,当自然排序不满足现在的需求时,必须使用比较器排序
  • 两种方式中关于返回值的规则
    • 如果返回值为负数,表示当前存入的元素是较小值,存左边
    • 如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
    • 如果返回值为正数,表示当前存入的元素是较大值,存右边

3.5成绩排序案例【应用】

  • 案例需求
    • 用TreeSet集合存储多个学生信息(姓名,语文成绩,数学成绩,英语成绩),并遍历该集合
    • 要求: 按照总分从高到低出现
  • 代码实现
    学生类
public class Student implements Comparable<Student> {
    private String name;
    private int chinese;
    private int math;
    private int english;

    public Student() {
    }

    public Student(String name, int chinese, int math, int english) {
        this.name = name;
        this.chinese = chinese;
        this.math = math;
        this.english = english;
    }

    public String getName() {
        return name;
    }

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

    public int getChinese() {
        return chinese;
    }

    public void setChinese(int chinese) {
        this.chinese = chinese;
    }

    public int getMath() {
        return math;
    }

    public void setMath(int math) {
        this.math = math;
    }

    public int getEnglish() {
        return english;
    }

    public void setEnglish(int english) {
        this.english = english;
    }

    public int getSum() {
        return this.chinese + this.math + this.english;
    }

    @Override
    public int compareTo(Student o) {
        // 主要条件: 按照总分进行排序
        int result = o.getSum() - this.getSum();
        // 次要条件: 如果总分一样,就按照语文成绩排序
        result = result == 0 ? o.getChinese() - this.getChinese() : result;
        // 如果语文成绩也一样,就按照数学成绩排序
        result = result == 0 ? o.getMath() - this.getMath() : result;
        // 如果总分一样,各科成绩也都一样,就按照姓名排序
        result = result == 0 ? o.getName().compareTo(this.getName()) : result;
        return result;
    }
}


测试类

public class TreeSetDemo {
    public static void main(String[] args) {
        //创建TreeSet集合对象,通过比较器排序进行排序
        TreeSet<Student> ts = new TreeSet<Student>();
        //创建学生对象
        Student s1 = new Student("jack", 98, 100, 95);
        Student s2 = new Student("rose", 95, 95, 95);
        Student s3 = new Student("sam", 100, 93, 98);
        //把学生对象添加到集合
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);

        //遍历集合
        for (Student s : ts) {
            System.out.println(s.getName() + "," + s.getChinese() + "," + s.getMath() + "," + s.getEnglish() + "," + s.getSum());
        }
    }
}

3.HashSet集合

3.1HashSet集合概述和特点【应用】

  • 底层数据结构是哈希表
  • 存取无序
  • 不可以存储重复元素
  • 没有索引,不能使用普通for循环遍历

4.2HashSet集合的基本应用【应用】

存储字符串并遍历

public class HashSetDemo {
    public static void main(String[] args) {
        //创建集合对象
        HashSet<String> set = new HashSet<String>();

        //添加元素
        set.add("hello");
        set.add("world");
        set.add("java");
        //不包含重复元素的集合
        set.add("world");

        //遍历
        for(String s : set) {
            System.out.println(s);
        }
    }
}

4.3哈希值【理解】

  • 哈希值简介
    是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
  • 如何获取哈希值
    Object类中的public int hashCode():返回对象的哈希码值
  • 哈希值的特点
    • 同一个对象多次调用hashCode()方法返回的哈希值是相同的
    • 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同

4.4哈希表结构【理解】

  • JDK1.8以前
    数组 + 链表


  • JDK1.8以后
    • 节点个数少于等于8个
      数组 + 链表
    • 节点个数多于8个
      数组 + 红黑树

4.5HashSet集合存储学生对象并遍历【应用】

  • 案例需求
    • 创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合
    • 要求:学生对象的成员变量值相同,我们就认为是同一个对象
  • 代码实现
    学生类
public class Student {
    private String name;
    private int age;

	//省略get set方法
    
    @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;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
}


测试类

public class HashSetDemo02 {
    public static void main(String[] args) {
        //创建HashSet集合对象
        HashSet<Student> hs = new HashSet<Student>();

        //创建学生对象
        Student s1 = new Student("林青霞", 30);
        Student s2 = new Student("张曼玉", 35);
        Student s3 = new Student("王祖贤", 33);

        Student s4 = new Student("王祖贤", 33);

        //把学生添加到集合
        hs.add(s1);
        hs.add(s2);
        hs.add(s3);
        hs.add(s4);

        //遍历集合(增强for)
        for (Student s : hs) {
            System.out.println(s.getName() + "," + s.getAge());
        }
    }
}
  • 总结
    HashSet集合存储自定义类型元素,要想实现元素的唯一,要求必须重写hashCode方法和equals方法

————————————————————————————

1.Map集合

1.1Map集合概述和特点【理解】

  • Map集合概述
interface Map<K,V>  K:键的类型;V:值的类型

  • Map集合的特点
    • 双列集合,一个键对应一个值
    • 键不可以重复,值可以重复
  • Map集合的基本使用
public class MapDemo01 {
    public static void main(String[] args) {
        //创建集合对象
        Map<String,String> map = new HashMap<String,String>();

        //V put(K key, V value) 将指定的值与该映射中的指定键相关联
        map.put("itheima001","林青霞");
        map.put("itheima002","张曼玉");
        map.put("itheima003","王祖贤");
        map.put("itheima003","柳岩");

        //输出集合对象
        System.out.println(map);
    }
}

1.2Map集合的基本功能【应用】

  • 方法介绍

方法名

说明

V   put(K key,V   value)

添加元素

V   remove(Object key)

根据键删除键值对元素

void   clear()

移除所有的键值对元素

boolean containsKey(Object key)

判断集合是否包含指定的键

boolean containsValue(Object value)

判断集合是否包含指定的值

boolean isEmpty()

判断集合是否为空

int size()

集合的长度,也就是集合中键值对的个数

  • 示例代码
public class MapDemo02 {
    public static void main(String[] args) {
        //创建集合对象
        Map<String,String> map = new HashMap<String,String>();

        //V put(K key,V value):添加元素
        map.put("张无忌","赵敏");
        map.put("郭靖","黄蓉");
        map.put("杨过","小龙女");

        //V remove(Object key):根据键删除键值对元素
//        System.out.println(map.remove("郭靖"));
//        System.out.println(map.remove("郭襄"));

        //void clear():移除所有的键值对元素
//        map.clear();

        //boolean containsKey(Object key):判断集合是否包含指定的键
//        System.out.println(map.containsKey("郭靖"));
//        System.out.println(map.containsKey("郭襄"));

        //boolean isEmpty():判断集合是否为空
//        System.out.println(map.isEmpty());

        //int size():集合的长度,也就是集合中键值对的个数
        System.out.println(map.size());

        //输出集合对象
        System.out.println(map);
    }
}

1.3Map集合的获取功能【应用】

  • 方法介绍

方法名

说明

V   get(Object key)

根据键获取值

Set   keySet()

获取所有键的集合

Collection   values()

获取所有值的集合

Set<Map.Entry<K,V>>   entrySet()

获取所有键值对对象的集合

  • 示例代码
public class MapDemo03 {
    public static void main(String[] args) {
        //创建集合对象
        Map<String, String> map = new HashMap<String, String>();

        //添加元素
        map.put("张无忌", "赵敏");
        map.put("郭靖", "黄蓉");
        map.put("杨过", "小龙女");

        //V get(Object key):根据键获取值
//        System.out.println(map.get("张无忌"));
//        System.out.println(map.get("张三丰"));

        //Set<K> keySet():获取所有键的集合
//        Set<String> keySet = map.keySet();
//        for(String key : keySet) {
//            System.out.println(key);
//        }

        //Collection<V> values():获取所有值的集合
        Collection<String> values = map.values();
        for(String value : values) {
            System.out.println(value);
        }
    }
}

1.4Map集合的遍历【应用】

方式1

  • 遍历思路
    • 我们刚才存储的元素都是成对出现的,所以我们把Map看成是一个夫妻对的集合
      • 把所有的丈夫给集中起来
      • 遍历丈夫的集合,获取到每一个丈夫
      • 根据丈夫去找对应的妻子
  • 步骤分析
    • 获取所有键的集合。用keySet()方法实现
    • 遍历键的集合,获取到每一个键。用增强for实现
    • 根据键去找值。用get(Object key)方法实现
  • 代码实现
public class MapDemo01 {
    public static void main(String[] args) {
        //创建集合对象
        Map<String, String> map = new HashMap<String, String>();

        //添加元素
        map.put("张无忌", "赵敏");
        map.put("郭靖", "黄蓉");
        map.put("杨过", "小龙女");

        //获取所有键的集合。用keySet()方法实现
        Set<String> keySet = map.keySet();
        //遍历键的集合,获取到每一个键。用增强for实现
        for (String key : keySet) {
            //根据键去找值。用get(Object key)方法实现
            String value = map.get(key);
            System.out.println(key + "," + value);
        }
    }
}

(方式2)

  • 遍历思路
    • 我们刚才存储的元素都是成对出现的,所以我们把Map看成是一个夫妻对的集合
      • 获取所有结婚证的集合
      • 遍历结婚证的集合,得到每一个结婚证
      • 根据结婚证获取丈夫和妻子
  • 步骤分析
    • 获取所有键值对对象的集合
      • Set<Map.Entry<K,V>> entrySet():获取所有键值对对象的集合
    • 遍历键值对对象的集合,得到每一个键值对对象
      • 用增强for实现,得到每一个Map.Entry
    • 根据键值对对象获取键和值
      • 用getKey()得到键
      • 用getValue()得到值
  • 代码实现
public class MapDemo02 {
    public static void main(String[] args) {
        //创建集合对象
        Map<String, String> map = new HashMap<String, String>();

        //添加元素
        map.put("张无忌", "赵敏");
        map.put("郭靖", "黄蓉");
        map.put("杨过", "小龙女");

        //获取所有键值对对象的集合
        Set<Map.Entry<String, String>> entrySet = map.entrySet();
        //遍历键值对对象的集合,得到每一个键值对对象
        for (Map.Entry<String, String> me : entrySet) {
            //根据键值对对象获取键和值
            String key = me.getKey();
            String value = me.getValue();
            System.out.println(key + "," + value);
        }
    }
}

2.HashMap集合

2.1HashMap集合概述和特点【理解】

  • HashMap底层是哈希表结构的
  • 依赖hashCode方法和equals方法保证键的唯一
  • 如果键要存储的是自定义对象,需要重写hashCode和equals方法

2.2HashMap集合应用案例【应用】

  • 案例需求
    • 创建一个HashMap集合,键是学生对象(Student),值是居住地 (String)。存储多个元素,并遍历。
    • 要求保证键的唯一性:如果学生对象的成员变量值相同,我们就认为是同一个对象
  • 代码实现
    学生类
public class Student {
    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 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;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
}


测试类

public class HashMapDemo {
    public static void main(String[] args) {
        //创建HashMap集合对象
        HashMap<Student, String> hm = new HashMap<Student, String>();

        //创建学生对象
        Student s1 = new Student("林青霞", 30);
        Student s2 = new Student("张曼玉", 35);
        Student s3 = new Student("王祖贤", 33);
        Student s4 = new Student("王祖贤", 33);

        //把学生添加到集合
        hm.put(s1, "西安");
        hm.put(s2, "武汉");
        hm.put(s3, "郑州");
        hm.put(s4, "北京");

        //遍历集合
        Set<Student> keySet = hm.keySet();
        for (Student key : keySet) {
            String value = hm.get(key);
            System.out.println(key.getName() + "," + key.getAge() + "," + value);
        }
    }
}

3.TreeMap集合

3.1TreeMap集合概述和特点【理解】

  • TreeMap底层是红黑树结构
  • 依赖自然排序或者比较器排序,对键进行排序
  • 如果键存储的是自定义对象,需要实现Comparable接口或者在创建TreeMap对象时候给出比较器排序规则

3.2TreeMap集合应用案例一【应用】

  • 案例需求
    • 创建一个TreeMap集合,键是学生对象(Student),值是籍贯(String),学生属性姓名和年龄,按照年龄进行排序并遍历
    • 要求按照学生的年龄进行排序,如果年龄相同则按照姓名进行排序
  • 代码实现
    学生类
public class Student implements Comparable<Student>{
    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 +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        //按照年龄进行排序
        int result = o.getAge() - this.getAge();
        //次要条件,按照姓名排序。
        result = result == 0 ? o.getName().compareTo(this.getName()) : result;
        return result;
    }
}


测试类

public class Test1 {
    public static void main(String[] args) {
      	// 创建TreeMap集合对象
        TreeMap<Student,String> tm = new TreeMap<>();
      
		// 创建学生对象
        Student s1 = new Student("xiaohei",23);
        Student s2 = new Student("dapang",22);
        Student s3 = new Student("xiaomei",22);
      
		// 将学生对象添加到TreeMap集合中
        tm.put(s1,"江苏");
        tm.put(s2,"北京");
        tm.put(s3,"天津");
      
		// 遍历TreeMap集合,打印每个学生的信息
        tm.forEach(
                (Student key, String value)->{
                    System.out.println(key + "---" + value);
                }
        );
    }
}

3.3TreeMap集合应用案例二【应用】

  • 案例需求
    • 给定一个字符串,要求统计字符串中每个字符出现的次数。
    • 举例: 给定字符串是“aababcabcdabcde”,在控制台输出: “a(5)b(4)c(3)d(2)e(1)”
  • 代码实现
public class Test2 {
    public static void main(String[] args) {
      	// 给定字符串
        String s = "aababcabcdabcde";
		// 创建TreeMap集合对象,键是Character,值是Integer
        TreeMap<Character,Integer> tm = new TreeMap<>();

        //遍历字符串,得到每一个字符
        for (int i = 0; i < s.length(); i++) {
            //c依次表示字符串中的每一个字符
            char c = s.charAt(i);
          	// 判断当前遍历到的字符是否在集合中出现过
            if(!tm.containsKey(c)){
                //表示当前字符是第一次出现。
                tm.put(c,1);
            }else{
                //存在,表示当前字符已经出现过了
                //先获取这个字符已经出现的次数
                Integer count = tm.get(c);
                //自增,表示这个字符又出现了依次
                count++;
                //将自增后的结果再次添加到集合中。
                tm.put(c,count);
            }
        }
        //  a(5)b(4)c(3)d(2)e(1)
        //System.out.println(tm);
        tm.forEach(
                (Character key,Integer value)->{
                    System.out.print(key + "(" + value + ")");
                }
        );
    }
}

4.可变参数

4.1可变参数【应用】

  • 可变参数介绍
    • 可变参数又称参数个数可变,用作方法的形参出现,那么方法参数个数就是可变的了
    • 方法的参数类型已经确定,个数不确定,我们可以使用可变参数
  • 可变参数定义格式
修饰符 返回值类型 方法名(数据类型… 变量名) {  }
  • 可变参数的注意事项
    • 这里的变量其实是一个数组
    • 如果一个方法有多个参数,包含可变参数,可变参数要放在最后
  • 可变参数的基本使用
public class ArgsDemo01 {
    public static void main(String[] args) {
        System.out.println(sum(10, 20));
        System.out.println(sum(10, 20, 30));
        System.out.println(sum(10, 20, 30, 40));

        System.out.println(sum(10,20,30,40,50));
        System.out.println(sum(10,20,30,40,50,60));
        System.out.println(sum(10,20,30,40,50,60,70));
        System.out.println(sum(10,20,30,40,50,60,70,80,90,100));
    }

//    public static int sum(int b,int... a) {
//        return 0;
//    }

    public static int sum(int... a) {
        int sum = 0;
        for(int i : a) {
            sum += i;
        }
        return sum;
    }
}

4.2创建不可变集合【理解】

  • 方法介绍
    • 在List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合
      • 这个集合不能添加,不能删除,不能修改
      • 但是可以结合集合的带参构造,实现集合的批量添加
    • 在Map接口中,还有一个ofEntries方法可以提高代码的阅读性
      • 首先会把键值对封装成一个Entry对象,再把这个Entry对象添加到集合当中
  • 示例代码
public class MyVariableParameter4 {
    public static void main(String[] args) {
        // static <E>  List<E>  of(E…elements)  创建一个具有指定元素的List集合对象
        //static <E>  Set<E>  of(E…elements)    创建一个具有指定元素的Set集合对象
        //static <K , V>   Map<K,V>  of(E…elements) 创建一个具有指定元素的Map集合对象

        //method1();
        //method2();
        //method3();
        //method4();

    }

    private static void method4() {
        Map<String, String> map = Map.ofEntries(
                Map.entry("zhangsan", "江苏"),
                Map.entry("lisi", "北京"));
        System.out.println(map);
    }

    private static void method3() {
        Map<String, String> map = Map.of("zhangsan", "江苏", "lisi", "北京", "wangwu", "天津");
        System.out.println(map);
    }

    private static void method2() {
        //传递的参数当中,不能存在重复的元素。
        Set<String> set = Set.of("a", "b", "c", "d","a");
        System.out.println(set);
    }

    private static void method1() {
        List<String> list = List.of("a", "b", "c", "d");
        System.out.println(list);
        //list.add("Q");
        //list.remove("a");
        //list.set(0,"A");
        //System.out.println(list);

//        ArrayList<String> list2 = new ArrayList<>();
//        list2.add("aaa");
//        list2.add("aaa");
//        list2.add("aaa");
//        list2.add("aaa");

        //集合的批量添加。
        //首先是通过调用List.of方法来创建一个不可变的集合,of方法的形参就是一个可变参数。
        //再创建一个ArrayList集合,并把这个不可变的集合中所有的数据,都添加到ArrayList中。
        ArrayList<String> list3 = new ArrayList<>(List.of("a", "b", "c", "d"));
        System.out.println(list3);
    }
}

5.Stream流

5.1体验Stream流【理解】

  • 案例需求
    按照下面的要求完成集合的创建和遍历
    • 创建一个集合,存储多个字符串元素
    • 把集合中所有以”张”开头的元素存储到一个新的集合
    • 把”张”开头的集合中的长度为3的元素存储到一个新的集合
    • 遍历上一步得到的集合
  • 原始方式示例代码
public class StreamDemo {
    public static void main(String[] args) {
        //创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        //把集合中所有以"张"开头的元素存储到一个新的集合
        ArrayList<String> zhangList = new ArrayList<String>();

        for(String s : list) {
            if(s.startsWith("张")) {
                zhangList.add(s);
            }
        }

//        System.out.println(zhangList);

        //把"张"开头的集合中的长度为3的元素存储到一个新的集合
        ArrayList<String> threeList = new ArrayList<String>();

        for(String s : zhangList) {
            if(s.length() == 3) {
                threeList.add(s);
            }
        }

//        System.out.println(threeList);

        //遍历上一步得到的集合
        for(String s : threeList) {
            System.out.println(s);
        }
        System.out.println("--------");

        //Stream流来改进
//        list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
        list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(System.out::println);
    }
}
  • 使用Stream流示例代码
public class StreamDemo {
    public static void main(String[] args) {
        //创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        //Stream流来改进
        list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(System.out::println);
    }
}

  • Stream流的好处
    • 直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印
    • Stream流把真正的函数式编程风格引入到Java中
    • 代码简洁

5.2Stream流的常见生成方式【应用

  • Stream流的三类方法
    • 获取Stream流
      • 创建一条流水线,并把数据放到流水线上准备进行操作
    • 中间方法
      • 流水线上的操作
      • 一次操作完毕之后,还可以继续进行其他操作
    • 终结方法
      • 一个Stream流只能有一个终结方法
      • 是流水线上的最后一个操作
  • 生成Stream流的方式
    • Collection体系集合
      使用默认方法stream()生成流, default Stream stream()
    • Map体系集合
      把Map转成Set集合,间接的生成流
    • 数组
      通过Arrays中的静态方法stream生成流
    • 同种数据类型的多个数据
      通过Stream接口的静态方法of(T… values)生成流
  • 代码演示
public class StreamDemo {
    public static void main(String[] args) {
        //Collection体系的集合可以使用默认方法stream()生成流
        List<String> list = new ArrayList<String>();
        Stream<String> listStream = list.stream();

        Set<String> set = new HashSet<String>();
        Stream<String> setStream = set.stream();

        //Map体系的集合间接的生成流
        Map<String,Integer> map = new HashMap<String, Integer>();
        Stream<String> keyStream = map.keySet().stream();
        Stream<Integer> valueStream = map.values().stream();
        Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();

        //数组可以通过Arrays中的静态方法stream生成流
        String[] strArray = {"hello","world","java"};
        Stream<String> strArrayStream = Arrays.stream(strArray);
      
      	//同种数据类型的多个数据可以通过Stream接口的静态方法of(T... values)生成流
        Stream<String> strArrayStream2 = Stream.of("hello", "world", "java");
        Stream<Integer> intStream = Stream.of(10, 20, 30);
    }
}

5.3Stream流中间操作方法【应用】

  • 概念
    中间操作的意思是,执行完此方法之后,Stream流依然可以继续执行其他操作
  • 常见方法

方法名

说明

Stream filter(Predicate predicate)

用于对流中的数据进行过滤

Stream limit(long maxSize)

返回此流中的元素组成的流,截取前指定参数个数的数据

Stream skip(long n)

跳过指定参数个数的数据,返回由该流的剩余元素组成的流

static  Stream concat(Stream a, Stream b)

合并a和b两个流为一个流

Stream distinct()

返回由该流的不同元素(根据Object.equals(Object) )组成的流

  • filter代码演示
public class StreamDemo01 {
    public static void main(String[] args) {
        //创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        //需求1:把list集合中以张开头的元素在控制台输出
        list.stream().filter(s -> s.startsWith("张")).forEach(System.out::println);
        System.out.println("--------");

        //需求2:把list集合中长度为3的元素在控制台输出
        list.stream().filter(s -> s.length() == 3).forEach(System.out::println);
        System.out.println("--------");

        //需求3:把list集合中以张开头的,长度为3的元素在控制台输出
        list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(System.out::println);
    }
}

  • limit&skip代码演示
public class StreamDemo02 {
    public static void main(String[] args) {
        //创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        //需求1:取前3个数据在控制台输出
        list.stream().limit(3).forEach(System.out::println);
        System.out.println("--------");

        //需求2:跳过3个元素,把剩下的元素在控制台输出
        list.stream().skip(3).forEach(System.out::println);
        System.out.println("--------");

        //需求3:跳过2个元素,把剩下的元素中前2个在控制台输出
        list.stream().skip(2).limit(2).forEach(System.out::println);
    }
}

  • concat&distinct代码演示
public class StreamDemo03 {
    public static void main(String[] args) {
        //创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        //需求1:取前4个数据组成一个流
        Stream<String> s1 = list.stream().limit(4);

        //需求2:跳过2个数据组成一个流
        Stream<String> s2 = list.stream().skip(2);

        //需求3:合并需求1和需求2得到的流,并把结果在控制台输出
//        Stream.concat(s1,s2).forEach(System.out::println);

        //需求4:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复
        Stream.concat(s1,s2).distinct().forEach(System.out::println);
    }
}

5.4Stream流终结操作方法【应用】

  • 概念
    终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作
  • 常见方法

方法名

说明

void forEach(Consumer action)

对此流的每个元素执行操作

long count()

返回此流中的元素数

  • 代码演示
public class StreamDemo {
    public static void main(String[] args) {
        //创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        //需求1:把集合中的元素在控制台输出
//        list.stream().forEach(System.out::println);

        //需求2:统计集合中有几个以张开头的元素,并把统计结果在控制台输出
        long count = list.stream().filter(s -> s.startsWith("张")).count();
        System.out.println(count);
    }
}

5.5Stream流的收集操作【应用】

  • 概念
    对数据使用Stream流的方式操作完毕后,可以把流中的数据收集到集合中
  • 常用方法

方法名

说明

R collect(Collector collector)

把结果收集到集合中

  • 工具类Collectors提供了具体的收集方式

方法名

说明

public static  Collector toList()

把元素收集到List集合中

public static  Collector toSet()

把元素收集到Set集合中

public static  Collector toMap(Function keyMapper,Function valueMapper)

把元素收集到Map集合中

  • 代码演示
public class CollectDemo {
    public static void main(String[] args) {
        //创建List集合对象
        List<String> list = new ArrayList<String>();
        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");

        /*
        //需求1:得到名字为3个字的流
        Stream<String> listStream = list.stream().filter(s -> s.length() == 3);

        //需求2:把使用Stream流操作完毕的数据收集到List集合中并遍历
        List<String> names = listStream.collect(Collectors.toList());
        for(String name : names) {
            System.out.println(name);
        }
        */

        //创建Set集合对象
        Set<Integer> set = new HashSet<Integer>();
        set.add(10);
        set.add(20);
        set.add(30);
        set.add(33);
        set.add(35);

        /*
        //需求3:得到年龄大于25的流
        Stream<Integer> setStream = set.stream().filter(age -> age > 25);

        //需求4:把使用Stream流操作完毕的数据收集到Set集合中并遍历
        Set<Integer> ages = setStream.collect(Collectors.toSet());
        for(Integer age : ages) {
            System.out.println(age);
        }
        */
        //定义一个字符串数组,每一个字符串数据由姓名数据和年龄数据组合而成
        String[] strArray = {"林青霞,30", "张曼玉,35", "王祖贤,33", "柳岩,25"};

        //需求5:得到字符串中年龄数据大于28的流
        Stream<String> arrayStream = Stream.of(strArray).filter(s -> Integer.parseInt(s.split(",")[1]) > 28);

        //需求6:把使用Stream流操作完毕的数据收集到Map集合中并遍历,字符串中的姓名作键,年龄作值
        Map<String, Integer> map = arrayStream.collect(Collectors.toMap(s -> s.split(",")[0], s -> Integer.parseInt(s.split(",")[1])));

        Set<String> keySet = map.keySet();
        for (String key : keySet) {
            Integer value = map.get(key);
            System.out.println(key + "," + value);
        }
    }
}

5.6Stream流综合练习【应用】

  • 案例需求
    现在有两个ArrayList集合,分别存储6名男演员名称和6名女演员名称,要求完成如下的操作
    • 男演员只要名字为3个字的前三人
    • 女演员只要姓林的,并且不要第一个
    • 把过滤后的男演员姓名和女演员姓名合并到一起
    • 把上一步操作后的元素作为构造方法的参数创建演员对象,遍历数据

演员类Actor已经提供,里面有一个成员变量,一个带参构造方法,以及成员变量对应的get/set方法

  • 代码实现
    演员类
public class Actor {
    private String name;

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

    public String getName() {
        return name;
    }

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


测试类

public class StreamTest {
    public static void main(String[] args) {
        //创建集合
        ArrayList<String> manList = new ArrayList<String>();
        manList.add("周润发");
        manList.add("成龙");
        manList.add("刘德华");
        manList.add("吴京");
        manList.add("周星驰");
        manList.add("李连杰");

        ArrayList<String> womanList = new ArrayList<String>();
        womanList.add("林心如");
        womanList.add("张曼玉");
        womanList.add("林青霞");
        womanList.add("柳岩");
        womanList.add("林志玲");
        womanList.add("王祖贤");
  
        /*
        //男演员只要名字为3个字的前三人
        Stream<String> manStream = manList.stream().filter(s -> s.length() == 3).limit(3);
  
        //女演员只要姓林的,并且不要第一个
        Stream<String> womanStream = womanList.stream().filter(s -> s.startsWith("林")).skip(1);
  
        //把过滤后的男演员姓名和女演员姓名合并到一起
        Stream<String> stream = Stream.concat(manStream, womanStream);
  
        //把上一步操作后的元素作为构造方法的参数创建演员对象,遍历数据
//        stream.map(Actor::new).forEach(System.out::println);
        stream.map(Actor::new).forEach(p -> System.out.println(p.getName()));
        */
  
        Stream.concat(manList.stream().filter(s -> s.length() == 3).limit(3),
                womanList.stream().filter(s -> s.startsWith("林")).skip(1)).map(Actor::new).
                forEach(p -> System.out.println(p.getName()));
    }
}

Git

git的下载和安装

下载网站:

https://git-scm.com/download/win

Git命令行操作

命令

作用

git init

初始化,创建 git 仓库

git status

查看 git 状态 (文件是否进行了添加、提交操作)

git add 文件名

添加,将指定文件添加到暂存区

git commit -m ‘提交信息’

提交,将暂存区文件提交到历史仓库

git log

查看日志( git 提交的历史日志)

分支管理操作(应用)

  • 创建和切换
    创建命令:git branch 分支名
    切换命令:git checkout 分支名
  • 新分支添加文件
    查看文件命令:ls
    总结:不同分支之间的关系是平行的关系,不会相互影响
  • 合并分支
    合并命令:git merge 分支名
  • 删除分支
    删除命令:git branch -d 分支名
  • 查看分支列表
    查看命令:git branch

生成SSH公钥

  • 推送代码之前,需要先配置SSH公钥
  • 生成SSH公钥步骤
    1. 设置Git账户
      • git config user.name(查看git账户)
      • git config user.email(查看git邮箱)
      • git config –global user.name “账户名”(设置全局账户名)
      • git config –global user.email “邮箱”(设置全局邮箱)
      • cd ~/.ssh(查看是否生成过SSH公钥)
    1. 生成SSH公钥
      • 生成命令: ssh-keygen –t rsa –C “邮箱” ( 注意:这里需要敲3次回车)
      • 查看命令: cat ~/.ssh/id-rsa.pub

55_配置SSH公钥

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部