java笔记

一. 静态成员

  • 关键词static

  • 修饰对象

    • 静态常量(类常量)
    • 静态变量(类变量)
    • 静态方法(类方法)
    • 静态类(对内部类的修饰)
  • 应用场景: 多个类需要共享同一个数据 故用关键词static 修饰 使其方法/属性/常量 固定一个内存空间 让每一个类都可以使用

  • 调用静态成员的格式 ——类名.静态类成员

  • 代码演示

    public class StaticTest{
        static double PI=3.14;//静态常量
        static int id;//静态变量
        public static void method1(){//静态方法
            //do Something
        }
        public void method (){
            
            System.out.println(StaticTest.PI);//调用静态常量
            System.out.println(StaticTest.id);
            StaticTest.method1();
    	}
    }
  • static作用

    1. 给各类提供共享的数据和方法
    2. 打破虽创建同一个类名但他们却各自独立的情况 改变静态变量/常量 同类调用的时候也是改变后的变量
    3. 总的来说就是static可以让类之间不用继承 也可以一起使用的变量和方法(当然是在类之间没有继承关系但又要共享同一内容的情况下)
    public class StaticTest(){
        static int i=47;
        public StaticTest(){}
        public static void main (String []args){
         		StaticTest staticTest1=new StaticTest();
            	StaticTest staticTest2=new StaticTest();//虽创建同一个类名taticTest 但是jvm分给他们的是不同的内存空间
            	staticTest1.i=60;//static给他们搭起了桥梁 联通了起来 可以使他们共享改变后的i值
            	System.out.println(staticTest2.i);//打印出来是i=60;//而不是i=47;
    	}
    }
  • 注意:

    1. 静态类成员依旧遵循权限问题 如:静态类方法的私有属性 不能被其他类所调用
    2. ==静态方法体中不能调用非静态类方法和变量==
    3. 静态方法体也不能用this关键词(因为不能调用非静态的属性和方法)
    4. 用static关键修饰的变量必须是 类的属性(就是类的全局变量而非成员方法中的变量[成员方法的变量称作为局部变量])
    //错误例子
    public class StaticTest(){
        static int id;
        static double PI;
        public void Test1(){
            //do something
    	}
        public static Test2(){
            //do something
    	}
        public static void Test3(){
            static int money;//必报错 不能修饰方法的变量
            Test1();//编译器会报错!!!在静态方法中只能调用静态方法!!!
            return this;//不能用this 不过我不明白为什么要写return this 可能为例子才写的吧
        }
    }

​ 5.成员变量又称实例变量,因为new(实例对象);而静态变量不需要实例化对象,直接通过类名调用该变量或方法,因此称之为类变量。

二、常量

  • 关键词 final

  • 修饰对象

    1. 变量(局部变量、全局变量)
    2. 对象
    3. 方法
  • 作用

    1. 对于变量——使其不能被更改 变量变成了常量

      注意:fina关键词定义的变量必须在声明时对其进行赋值操作

    tip:表示常量的变量要用全大写

    final double PI=3.14;//数学中的常量 不可更改!局部常量
    static final double PI=3.14//全局常量 只能在定义时才能赋值 其余不改变!
    1. 对于对象 ——使其只能指向固定的一个对象

    注意:但还是可以改对象的变量值 要想根本不能改变 就必须 用static final修饰

    public final class java1 {
        int id=101;
        public static void main(String[] args) {
            //final 固定指向了java1
            java1 java1 = new java2();//不能指向其他类 会报错 
            java1.id=11;//只固定指向java1 但没限制对该类的变量赋值变值操作
        }
    }
    class java2{
      .....
    }
    1. 对于类——不能被继承 你想想final的作用就是不想被更改 如果能被继承 不就是会遇到子类更改所继承的变量值吗?
    2. 对于方法 ——不能被重写

三.、内部类

[内部类和外部类之间的相互调用 - 没有名字~ - 博客园 (cnblogs.com)](https://www.cnblogs.com/rgever/p/8902758.html#:~:text=外部类访问内部类:必须建立内部类的对象. 内部类访问外部类:内部类可以直接访问外部类的成员包括私有成员,因为外部类持有内部类的引用. 特例:内部类写在外部类的方法中 (即局部变量的位置) 1、内部来外部类均可定义变量%2F常量.,2、只能被final%2Fabstract修饰. 3、只能访问被final%2Fabstract修饰的变量. 4、可以直接访问外部类中的成员,因为还持有外部类的引用. 标签%3A javase复习.)

1. 定义

一个类的内部又完整嵌套了另一个类结构。被嵌套的类称之为内部类,嵌套其他类的类称之为外部类。

2.基本语法

class Outer{//外部类
    class Inner{//内部类
        
    }
}

3.特点

可以直接访问私有属性

4.分类

定义在外部类的局部位置上(比如在方法和代码块 )

4.1局部内部类

基本语法

class OuterClass{
    void test(){
        class InnerClass{
            ...
        }
    }
}

特点

  • 可以访问外部类的所有成员(属性和方法) 私有成员也可以

  • 不可以添加访问修饰符,但是可以用final修饰

  • ==作用域==作用在定义的方法或代码块

  • 外部类在方法中可以创建内部类对象,然后就可以调用内部类的方法

  • 外部其他类不能访问 局部内部类(因为局部内部类地位是属于一个局部变量)

  • 外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类成员,则可以使用[==外部类.this.成员==]去访问

    class OuterClass{
        private int m1=100;
        void m2(){
            System.out.println("外部类方法");
        }
        void test(){
            class InnerClass{
                private int m1=800;
                void test1(){
                    System.out.println(m1);//就近原则 输出800
                    System.out.println("访问外部类的m1="+OuterClass.this.m1);
                }
            }
        }
    }

4.2匿名(局部)内部类(==重点==)

基本语法

new 类或接口(参数列表){//本身接口就是不能new(实例化) 但是呢这是匿名内部类就是可以使用new关键字
	类体	
};

特点

  • 没有类名
  • 运行类型是匿名内部类
  • 基于抽象类的匿名内部类必须实现抽象类的方法
  • 基于类的匿名内部类的形参列表会传入到类中的构造器中
class OuterClass{
    void test(){
        IA tiger=new IA(){//匿名内部类 
          //基于接口的匿名内部类
          //0.接口是不能实例化的 所以
          //1.底层jdk已经new好了一个类 Outer04$1 
          //2.然后implements接口IA 
          //3.最后赋值给类型为接口类型IA 变量名为tiger
            public void cry(){
                  //重写接口的方法
            }
        };
        tiger.cry();
        
        Father father=new Father("jack"){
            //基于类的匿名内部类
            //0.创建一个匿名内部类Outer04$2 
            //1.然后extends类Father
            //2.最后赋值给变量名为father类型为Father的变量
            void test(){
                //重写test方法
                System.out.println("基于类的匿名内部类的方法test")
            }
        };
        father.test();
        
        Son son=new Son(){//必须实现抽象方法test
            void test(){
                ...
            }
        };
        son.test();
    }
}
interface IA{//定义一个接口
    public void cry();
}
class Father{
    Father(String name){
        System.out.println(name);
    }
    void test(){
        
    }
}
abstract class Son{
    abstract void test();
}
  • 匿名内部类仍然遵循动态绑定和继承的规则(super)
class Outer{
    private int m1=100;
    void test(){
        Person p=new Person(){
          void teach(){
              //重写
          }  
        };
        p.teach();
        //另一种写法(直接调用)
        new Person(){
            void test(){
               //重写
            }
            void ok(String str){
                super.ok(str);//调用的是匿名内部类继承的Person类
            }
        }.ok("jack");
        
    }
    
}
class Person{
    void teach(){
        ....
    }
    void ok(String str){
        System.out.println(str);
    }
}
  • 匿名内部类本身也是返回对象
  • 匿名内部类可以访问外部类所有成员(包括私有成员)
class Outer{
    private int m1=100;
    void test(){
        Person p=new Person(){
          void teach(){
              //重写
              System.out.println(m1);//100
          }  
        };
        p.teach();
    } 
}
class Person{
    void teach(){
        ...
    }
}
  • 外部其他类不能访问 匿名内部类(因为局部内部类地位是属于一个局部变量)
  • 外部类和匿名内部类的成员重名时,默认遵循就近原则,如果想在匿名内部类中访问外部类成员,则可以使用[==外部类.this.成员==]去访问
class Outer{
    private int m1=100;
    void test(){
        Person p=new Person(){
          int m1=88;
          void teach(){
              //重写
              System.out.println(m1);//就近原则88
              System.out.println(Outer.this.m1);//100
          }  
        };
        p.teach();
    } 
}
class Person{
    void teach(){
        ...
    }
}

作用

为了简化开发
同时解决只用一次类而产生不必要的损耗

最佳实践

将匿名内部类当做实参直接传递到函数中
class Practice{
    public static void main(String args[]){
        test(new IA(){
            //实现接口IA的show方法
            show(){
                 System.out.println("....");
            }
        });
        Picture picture =new Picture();
        picture.show();
    }//调用test方法 将匿名内部类当做实参传入test当中
    public static void test(IA ia){
        ia.show();
    }
}
interface IA{
    void show();
}
//传统写法
//单独写一个类去实现这个接口
class Picture implements IA{
    void show(){
        System.out.println("....");//灵活性不如匿名内部类传参 因为一旦改变 所有为Picture类型的对象的内容都要改变(对于只是使用一次类的情况来说)
    }
}

定义在外部类的成员位置

4.3成员内部类

基本语法

class OuterClass{
    int m1;
    int m2;
    void f1(){
        ...
    }
    class InnerClass{//成员内部类
        ...
    }
}

特点

  • 成员内部类可以直接访问外部类的所有成员,包括私有

  • 可以任意添加访问修饰符(public protected 默认 private) 因为它本质是一个成员

  • 作用域在所处的整个类体当中

  • 外部类访问成员内部类,需要创建对象,再调用访问

    class OuterClass{
       private int m1;
       private  m2;
        void f1(){
            ...
        }
        class InnerClass{//成员内部类
            void say(){
                System.out.println("这是成员内部类的方法say")
            }
        }
        //外部类使用成员内部类方法
        //先创建 后调用
        InnerClass in=new InnerClass();
        in.say();
    }
  • 外部其它类访问成员内部类,需要先创建外部类,再创建内部类

    //两种方式
    class OtherOuterClass{
        public static void main(String args[]){
            OuterClass outClass=new OuterClass();
            OuterClass.InnerClass in=outerClass.new InnerClass();
            OuterClass.InnerClass getIn=outerClass.getInnerClass();
        }
    }
    class OuterClass{
        int m1;
        int m2;
        void f1(){
            ...
        }
        class InnerClass{//成员内部类
            ...
        }
        public InnerClass getInnerClass(){
            return new InnerClass();
        }
    }
  • 外部类和成员内部类的成员重名时,默认遵循就近原则,如果想访问外部类成员,则可以使用[==外部类.this.成员==]去访问

  • 外部其他类无法访问成员内部类的私有方法私有的成员内部类

    解决措施

    //可以通过在外部类的非私有方法中创建内部类并调用其私有方法
    public class OtherOutClass {
        public static void main(String[] args) {
    
            OutClass outClass = new OutClass();
            outClass.use();
        }
    }
    class OutClass{
        private  class InClass{
            private void say(){
                System.out.println("这是成员内部类私有方法say");
            }
        }
        void use(){
            InClass inClass = new InClass();
            inClass.say();//外部类可以调用内部类私有成员
        }
    }

4.4静态(成员)内部类

定义 对成员内部类进行static的修饰

特点

  • 只能访问外部类静态成员,非静态成员不能访问

  • 可以添加访问修饰符(public protected 默认 private)

  • 作用域在整个类体

  • 可以直接访问外部类的静态成员

  • 外部类访问静态内部类,需要先创建后调用,调用可以直接通过类名访问(前提要遵守访问权限)

    class OtherOuterClass{
        public static void main(String args[]){
            OuterClass.InnerClass in=new OuterClass.InnerClass();
            in.say();
            //下面会报错 外部其他类无法访问私有的静态内部类的成员
            OuterClass.InnerClass1 getIn=new OuterClass.say();
        }
    }
    class OuterClass{
        int m1;
        static int m2;
        static class InnerClass{
            void say(){
                
            }
        }
        private static class InnerClass1{
            void say(){
                
            }
        }
        
    }

    image-20230111092200198

  • 外部类和静态内部类的成员重名时,默认遵循就近原则,如果想访问外部类成员(该成员一定要是静态的),则可以使用[==外部类.成员==]去访问

    class OuterClass{
        private static int m1=100;
        static class InnerClass{
            int m1=200;
            void say(){
                System.out.println(m1);//100
                System.out.println(OuterClass.m1);//200
    		}
        }
    }

四、枚举类

0.应用场景:

​ 需要创建某一个类,这个类的值是固定不变且有限个,即只读不修改

1.定义

枚举是一组常量的集合,枚举是一种特殊的类,只包含一组有限且特定的对象。

2.实现方式

2.1 自定义实现枚举

1.不需要setXXX方法,因为枚举对象值通常只读
2.对枚举对象/属性使用final+static共同修饰,实现底层优化
3.枚举对象名通常使用全部大写,常量的命名规范
4.枚举对象个根据需要,也可以有多个属性
5.构造器私有化,避免在其他类中创建并初始化!
public class enumCode {
    public static void main(String[] args) {
        
    }
}
class Season{
    String name;
    String desc;
    
    public final static Season SPRING=new Season("春天","温暖");
    public final static Season SUMMER=new Season("夏天","炎热");
    public final static Season AUTUMN=new Season("秋天","凉爽");
    public final static Season WINTER=new Season("冬天","寒冷");

    private Season(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }


    public String getDesc() {
        return desc;
    }


}

2.2 使用关键字enum实现枚举

1.用关键字enum代替class
2.多个对象值用逗号隔开
3.构造器依旧私有化
4.在枚举类的第一行写上对象值 
public class enumCode {
    public static void main(String[] args) {
        System.out.println(Season.SPRING);
    }
}
enum Season{

    SPRING("春天","温暖"),//其实就是代替public final static Season SPRING=new Season("春天","温暖");
    SUMMER("夏天","炎热"),
    AUTUMN("秋天","凉爽"),
    WINTER("冬天","寒冷");
    String name;
    String desc;


    private Season(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }


    public String getDesc() {
        return desc;
    }

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

3.特点

  • 使用enum关键字开发一个枚举类时,会默认继承Enum类(通过反编译javap)
public static final Season SPRING=new Season("春天","温暖")

image-20230112151529481

  • 用关键词enum修饰的类,是继承java.lang.Enum类

    public class EnumPr {
        public static void main(String[] args) {
            Gender boy1=Gender.BOY;
            Gender boy2=Gender.BOY;//本质就是 public static final Gender BOY=new Gender();
            System.out.println(boy1);//先去调用Gender类的toString方法 本类没有 就调用父类Enum类的toString方法
            System.out.println(boy1==boy2);//静态对象赋初值 当然是一样的啦
        }
    }
    enum Gender{
        BOY,Girl;
    }

    image-20230112154137235

  • 如果使用枚举类的无参构造器时,实参列表的和小括号可以省略

enum Season{

    SPRING("春天","温暖"),//其实就是代替public final static Season SPRING=new Season("春天","温暖");
    SUMMER("夏天","炎热"),
    AUTUMN("秋天","凉爽"),
    WINTER("冬天","寒冷"),
        other();//省略写成other 调用Season无参构造器;
    String name;
    String desc;

	private Season(){
        //无参构造器
    }
    private Season(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }
}

4.Enum类常用的成员方法

  • name()

    //以上面枚举类的Season为例
    public class EnumMethod{
        public static void main (String[] args){
            Season autumn=Season.AUTUMN;
            System.out.println(autumn.name());
        }
    }
  • ordinal()

    //枚举的静态值的次序 从零开始编号
    autumn.ordinal()
  • values()

    //返回枚举类型的数组 取出枚举类中所有静态值(枚举对象)
    Season []values=Season.values()
        for(Season season:values){//增强for循环
            System.out.println(season);//根据枚举类的toString方法打印
        }
  • valueOf()

    //用于查找枚举对象
    Season autumn=Season.valueOf("AUTUMN")
  • compareTo()

    //用于比较两个枚举对象的编号 作差法
    System.out.println(Season.AUTUMN.compareTo(Season.SUMMER))

五、注解

1.分类

1.1Override

  • 定义

    @Target(ElementType.METHOD)//元注解 所用只用于修饰方法
    @Retention(RetentionPolicy.SOURCE)
    public @interface Override {
    }
  • 作用

    用于说明在子类继承父类并且同时重写(override)了父类的方法 便于校验是否重写父类方法

  • 特点

    只能用于修饰方法,不能修饰其他类,包和属性

1.2Deprecated

  • 定义

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
    public @interface Deprecated {
    }
    
  • 作用

    表明某元素已经过时 但==不代表不能调用== 用于版本升级的注解说明

  • 特点

    可以修饰包,变量,方法,文件,字段,参数等

1.3SupressWarnings

  • 定义

    @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface SuppressWarnings {
        String[] value();
    }
  • 作用

    用于抑制警告

  • 特点

    作用域根据放置的位置相关 可以修饰变量,方法,文件,字段,参数等

2.元注解(了解)

2.1 定义

​ 修饰注解的注解称之为元注解

2.2种类

  • Retention(保留)

    作用:只能用于修饰一个元注解定义,用于指定元注解可以保留多长时间 @Retention中包含了一个成员RetentionPolicy类型的成员变量,使用@Retention是必须为该value成员变量指定值

    • RetentionPolicy.**SOURCE **:编译器使用后,直接丢弃这种策略的注解

    • RetentionPolicy.**CLASS **:编译器将把注释记录在class文件中,当运行Java程序时,JVM不会保留注解

    • RetentionPolicy.RUNTIME:编译器将把注释记录在class文件中,当运行Java程序时,JVM会保留注解,程序可以通过反射获取该注解

  • Target

    指定注解可以修饰哪些元素

  • Documented

    指定该注解是否会在Javadoc(Java文档document)体现

  • Inherited

    子类会继承父类的注解

六、集合

0.数组与集合

  • 数组

    1)创建数组,长度必须指定,一旦指定无法修改

    2)保存必须是同一类型的元素

    3)使用数组进行增加元素代码较为麻烦

    Person[] pers=new Person[3];
    Person[]pers2=new Person[pers.length+1];
    pers2[pers2.length-1]=new Person();
  • 集合

    1)可以==动态保存==任意多个对象,使用方便

    2)提供add,remove,set,get方法便于操作对象

    3)使用集合添加,删除元素代码 更加简洁

  • 集合框架图

    1. 单列集合

    image-20230113114537292

​ 2. 双列结合(键值对)image-20230113114904284

1. 分类

1.1单列集合

1.1.1Collection(接口)
  • 常用方法
    1. add 添加单个元素
    2. remove 删除指定元素
    3. contains 查找元素是否存在
    4. size 获取元素个数
    5. isEmpty 判断是否为空
    6. clear 清空
    7. addAll 添加多个元素
    8. containsAll 查找多个元素是否都存在
    9. removeAll 删除多个元素
@SuppressWarnings("all")
public class CollectionMethod {
    public static void main(String[] args) {
        //接口回调 类似于向上转型 子类指向父类  实现类指向接口 接口调用实现类方法
        List list=new ArrayList();
        //add()
        list.add(1);// boolean add(Object) 任意类型
        list.add("粥粥");
        list.add(true);
        //删除
        list.remove(0);
        System.out.println(list);
        //查找
        System.out.println(list.contains(0));
        //个数
        System.out.println(list.size());
        //清空
        list.clear();
        System.out.println(list);
        //添加多个元素
        List list1=new ArrayList();
        list1.add("三体");
        list1.add("悲伤逆流成河");
        list.addAll(list1);
        System.out.println(list);
        //删除多个元素
        list.removeAll(list1);
        System.out.println(list);
        //查找多个元素
        System.out.println(list.containsAll(list1));
        //判断是否为空
        System.out.println(list.isEmpty());
    }
}
  • 遍历元素方式

    • 使用迭代器

      1. 介绍

        1)用于遍历Collection集合中的元素

        2)所有实现接口Collection的集合类都有iterator()方法,用以返回一个实现了Iterator接口的对象,既可以返回一个迭代器

        3)Iterator的结构

        4)Iterator仅用于遍历集合,本身并不存放对象

      2. 原理

        image-20230113140746562

      @SuppressWarnings("all")
      public class Iterator_ {
          public static void main(String[] args) {
              Collection col=new ArrayList();
              col.add(new Book("三国演义","罗贯中"));
              col.add(new Book("红楼梦","曹雪芹"));
              col.add(new Book("西游记","吴承恩"));
              Iterator iterator = col.iterator();
              while (iterator.hasNext()) {//判断下一个位置是否还有更多的元素
                  Object next =  iterator.next();
                  System.out.println(next);
              }
      //        iterator=col.iterator();//该为重置iterator迭代器
              //快捷键itit 显示快捷键ctrl+j
              while (iterator.hasNext()) {//返回false 无法执行while语句
                  Object next = iterator.next();
                  System.out.println(next);
              }
          }
      }
      class Book{
          //封装Book 
          private String name;
          private String author;
      
          public Book(String name, String author) {
              this.name = name;
              this.author = author;
          }
      }
      
    • 增强for循环

      底层还是利用迭代器 是迭代器的优化形式

      public class Iterator_ {
          public static void main(String[] args) {
              Collection col=new ArrayList();
              col.add(new Book("三国演义","罗贯中"));
              col.add(new Book("红楼梦","曹雪芹"));
              col.add(new Book("西游记","吴承恩"));
          	for(Object book:col){
                  System.out.println(book);
              }
          }
      }
1.1.2List(接口)
  • 基本介绍

    List接口是Collection接口的子接口(上述的体系图)

  • 特点

    1)List集合类中元素有序(即添加的顺序和取出顺序一致),且可重复

    public class list_ {
        public static void main(String[] args) {
            List list = new ArrayList();
            list.add(1);
            list.add(1);
            list.add(2);
            list.add(3);
        }
    }

    2)List集合中的每个元素都有其对应的顺序索引,即支持索引(从零开始)

    //索引
    System.out.println(list.get(0));

    3)List容器中的元素都对应一个整数型的序号记载其在容器中位置,可以根据序号存取容器中的元素

  • 常用方法

    1. add(int Index ,Object obj) 插入单个元素(默认尾插)
    2. addAll(int index,Object obj) 插入多个元素
    3. get() 获取索引
    4. indexOf() 获取第一个出现元素的位置序号
    5. set(int index,Object obj) 替换某元素
    6. subList(int start,int end) 获取子list 是左闭右开
    public class adv_6 {
        public static void main(String[] args) {
            List list = new ArrayList();
            for (int i = 0; i < 11; i++) {
                System.out.println(list.add("hello"+i));
            }
            list.add(1,2);//在序号为1的位置插入元素2
            list.remove(5);
            list.set(6,"粥粥");
            Iterator iterator = list.iterator();//获取迭代器
            while (iterator.hasNext()) {
                Object next =  iterator.next();
                System.out.println(next);
            }
        }
    }
  • 遍历元素方式

    实现接口List的子类遍历方式都是一样的 (ArrayList Vector LinkedList)

    • 迭代器
    • 增强for
    • 普通for循环
    public class listFor {
        public static void main(String[] args) {
            List list =  
            //List list = new Vector();OK
            //List list = new LinkedList();OK
            list.add(1);
            list.add(2);
            list.add(3);
            list.add(4);
            //第一种 迭代器
            Iterator iterator = list.iterator();
            while (iterator.hasNext()) {
                Object elem = iterator.next();
                System.out.println(elem);
            }
            //第二种 增强for循环
            for (Object o :list) {
                System.out.println(o);
            }
            //第三种 普通for循环
            for (int i = 0; i < list.size(); i++) {
                System.out.println(list.get(i));
            }
        }
    }
  • List常用的实现子类

    • ArrayList

      1. 特点

        1)可以存放空值(null)

        2)是由可变数组来实现数据存储

        3)ArrayList基本等同于Vector,除了==ArrayList是线程不安全==(效率高),故此在多线程当中不建议使用ArrayList

      2. 源码分析(==重难点==)

        1)ArrayList中含有一个Object类型的数组(elementData)用于存放各种类型的数据对象

        2)当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0第一次添加,则扩容elementData为10,如需要再次扩容,则扩容为1.5倍

        //  调用无参构造器
        public ArrayList() {
                this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//初始化值为零
        }
        
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//返回一个类型为Object的数组 长度为零0
        
        public boolean add(E e) {
                ensureCapacityInternal(size + 1);  // Increments modCount!! 每次确认是否能够增加一个元素
                elementData[size++] = e;
                return true;
        }
        
        private void ensureCapacityInternal(int minCapacity) {
                ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
        
        private void ensureExplicitCapacity(int minCapacity) {
                modCount++;//纪录修改次数 在下标位置修改元素值 将null值改为赋值后的元素值
        
                // 当最小容量都大于当前数组长度时 申请扩容grow
                if (minCapacity - elementData.length > 0)
                    grow(minCapacity);
        }
        
        private void grow(int minCapacity) {
                // 纪录当前数组长度
                int oldCapacity = elementData.length;
                int newCapacity = oldCapacity + (oldCapacity >> 1);//第一次运行扩容newCapacity还是零  位运算向右移一位相当于数据除于2 (1+0.5=1.5倍)
                if (newCapacity - minCapacity < 0)
                    newCapacity = minCapacity;
                if (newCapacity - MAX_ARRAY_SIZE > 0)
                    newCapacity = hugeCapacity(minCapacity);
                //拷贝原先数据 然后在申请放置空值的空间
                elementData = Arrays.copyOf(elementData, newCapacity);
        }
        

        3)如果使用的是指定大小的构造器,则初始化的大小为规定的大小。如果需要扩容。则直接扩容为elemData数组的1.5倍

         public ArrayList(int initialCapacity) {
             //三种情况   
                if (initialCapacity > 0) {//大于零
                    this.elementData = new Object[initialCapacity];
                } else if (initialCapacity == 0) {//等于零 就按照之前调用无参构造器是一样的
                    this.elementData = EMPTY_ELEMENTDATA;
                } else {//小于零 负数报错
                    throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity); 
                }
         }
        //其余的方法跟无参的Arraylist是一样的 这就不复制粘贴了(doge)
    • Vector
      1. 定义
      public class Vector<E>
          extends AbstractList<E>
          implements List<E>, RandomAccess, Cloneable, java.io.Serializable
      1. 特点

        • 底层也是一个对象数组protected Object[]elementData;

        • Vector是线程同步的 ,即线程安全(效率不高);Vector类的操作方法有关键词synchronized

          public synchronized boolean add(E e) {
                  modCount++;
                  ensureCapacityHelper(elementCount + 1);
                  elementData[elementCount++] = e;
                  return true;
          }

          故在开发中,需要线程同步时,考虑使用Vector

      2. 源码分析

        1)当创建Vector对象时,如果使用的是无参构造器,则初始elementData容量为10,满后2倍扩容

        2)如果指定大小,每次直接扩容两倍

        //无参构造器调用——默认不定值为长度10
        public Vector() {
                this(10);
        }
        
        public synchronized boolean add(E e) {
                modCount++;//纪录填入对象数组的次数
                ensureCapacityHelper(elementCount + 1);
                elementData[elementCount++] = e;
                return true;
        }
        
        private void ensureCapacityHelper(int minCapacity) {
                // 判断最小需要长度(minCapacity)有无超过当前数组长度(elementData.length) 是否需要扩容grow
                if (minCapacity - elementData.length > 0)
                    grow(minCapacity);
        }
        
        public Vector(int initialCapacity, int capacityIncrement) {
                super();
                if (initialCapacity < 0)
                    throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
                                                       
                this.elementData = new Object[initialCapacity];
                this.capacityIncrement = capacityIncrement;//未调用该构造器则capacityIncrement=0
        }
        
        
        private void grow(int minCapacity) {
                //扩容机制
            	//capacityIncrement 是自定义增量大小 默认为零
                int oldCapacity = elementData.length;
                int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity)                                      		
                if (newCapacity - minCapacity < 0)
                    newCapacity = minCapacity;
                if (newCapacity - MAX_ARRAY_SIZE > 0)
                    newCapacity = hugeCapacity(minCapacity);
                elementData = Arrays.copyOf(elementData, newCapacity);
         }
        
        //指定大小
        public Vector(int initialCapacity) {
                this(initialCapacity, 0);
        }
        
    • LinkedList
      1. 特点

        1)LinkedList底层实现了双向链表双端队列

        2)可以添加任意元素(可以重复) 包括null

        3)线程不安全

        4)效率高

        1. 底层操作机制

          1)底层维护了一个双向链表

          2)维护了两个属性first 和last分别指向首节点尾结点

          3)每个节点(Node对象),里面又维护了prev,next,item三个属性,通过prev指向前一个,通过next指向后一个,最终实现双向链表

      2. 模拟双向链表

        • 创建 new LinkedList
        • 删除 remove(删除第一个)
        • 添加 add(尾插)
        public class LinkedList_ {
            public static void main(String[] args) {
                Node hjy = new Node("hjy");
                Node zz = new Node("zz");
                Node ggh = new Node("ggh");
                Node hmy = new Node("hmy");
                Node hxw = new Node("hxw");
                //连接顺序 hjy->zz->ggh->hmy->hxw
                //若添加一个新结点N
                /*
                Node N =new Node("new");
                hjy.next=zz;
                zz.next=ggh;
                ggh.next=N;
                N.next=hmy;
        
                hmy.next=hxw;
                hxw.prev=hmy;
                N.prev=ggh;
                hmy.prev=N;
                ggh.prev=zz;
                zz.prev=hjy;*/
                hjy.next=zz;
                zz.next=ggh;
                ggh.next=hmy;
                hmy.next=hxw;
                hxw.prev=hmy;
                hmy.prev=ggh;
                ggh.prev=zz;
                zz.prev=hjy;
                /*删除就重新指向原来的Node结点 N结点就被删除了*/
                //头结点 尾结点
                Node first=hjy;
                Node last=hxw;
        
                while(true){
                    if(first==null)
                        break;
                    System.out.println(first);
                    first=first.next;//移动头指针
        
                }
                while(true){
                    if(last==null)
                        break;
                    System.out.println(last);
                    last=last.prev;//移动尾指针
                    
                }
        
            }
        }
        //双向链表=Node+first+last
        //结点=value+prev+next
        class Node{
            Node prev;
            Node next;
            Object value;
            //数据域
            public Node(Object value) {
                this.value = value;
            }
        
            @Override
            public String toString() {
                return "Node{" +
                        "value=" + value +
                        '}';
            }
        }

        image-20230115085247656

        image-20230115090049932

      3. 源码分析

        //尾部添加元素
        public boolean add(E e) {
                linkLast(e);//尾部连接元素
                return true;
        }
        //尾插法
        void linkLast(E e) {
                final Node<E> l = last;
                final Node<E> newNode = new Node<>(l, e, null);
                last = newNode;
                if (l == null)
                    first = newNode;
                else
                    l.next = newNode;
                size++;
                modCount++;
        }
        //删除第一个元素
        public E remove() {
                return removeFirst();
        }
        public E removeFirst() {
                final Node<E> f = first;
                if (f == null)
                    throw new NoSuchElementException();
                return unlinkFirst(f);
        }
        private E unlinkFirst(Node<E> f) {
                // 纪录第一个元素的信息 item和next
                final E element = f.item;
                final Node<E> next = f.next;
            	//第一个元素的数据置空 next指针置空
                f.item = null;
                f.next = null; 
            	//头指针移动到下一个元素
                first = next;
                if (next == null)
                    last = null;
                else
                    next.prev = null;//当头指针移动到的元素的数据不为空,则前驱指针置空
                size--;//长度减一
                modCount++;//修改次数加一
                return element;
        }
    • ArrayList和LinkedList比较

      底层结构 增删的效率 改查的效率
      ArrayList 可变数组 较低 数组扩容 较高
      LinkedList 双向链表 较高 通过链表追加 较低

      如何选择ArrayList和LinkedList:

      1)如果==改查==的操作多,选择ArrayList

      2)如果==增删==的操作多,选择LinkedList

      3)一般来说,在程序中,大多情况都是查询,故大部分情况会选择ArrayList

1.1.3Set(接口)
  • 基本介绍

    1)无序(添加和取出的顺序不一致),==没有索引==

    2)不允许重复元素(数据),所以最多包含一个null

    public class HashSet_ {
        public static void main(String[] args) {
            HashSet hashSet = new HashSet();
            hashSet.add(1);
            hashSet.add(1);//加入不了 重复元素(数值)
    
            hashSet.add(new Dog("jack"));
            hashSet.add(new Dog("jack"));//可以加入 这一个新的对象只是名字相同罢了
    
            hashSet.add(new String("tom"));
            hashSet.add(new String("tom"));//加入不了 在后面的源码分析会解释
            System.out.println(hashSet);
        }
    }

    3)JDK中API的Set接口的常用使用类

    • HashSet
    • TreeSet
  • 常用方法

    因为List和Set都是Collection的子接口 所以常用方法和Collection接口一样

  • 遍历方式

    1. 可以使用迭代器
    2. 增强for
    3. 不能使用索引进行遍历
    public class Set_ {
        public static void main(String[] args) {
            Set set = new HashSet();
            set.add("d");
            set.add(1);
            set.add(2);
            set.add("8");
            System.out.println(set);
            //迭代器
            Iterator iterator = set.iterator();
            while (iterator.hasNext()) {
                Object next = iterator.next();
                System.out.println(next);
            }
            //增强for
            for (Object o :set) {
                System.out.println(o);
            }
    
        }
    }
  • HashSet

    1. 底层就是HashMap,HashMap底层是==数组+链表==

      image-20230115152959369

    2. 添加一个元素,先得==hash值== 会转成 索引值

    3. 找到存储数据表table 看这个索引位置是否已经存放的元素

    4. 如果有 调用==equals==比较 如果相同就放弃添加 如果不相同 则添加到最后

    5. 在java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8)==并且==table的大小>=**MIN_TREEIFY_CAPACITY(默认64)**,就会进行树化(红黑树),否则让采用下列扩容机制

      • 第一次添加,table数组扩容到16*加载因子(0.75)=12(并不是说占用了十二个下标的位置 而是说有十二个节点 )
      • 如果该hash值对应的数组使用到了临界值12,就会扩容到16乘2=32,新的临界值就是32*0.75=24,以此类推(每加入一次结点) 16(12)32(24)64(48)
    //源码分析
    public HashSet(){
        //构造器创建一个HashMap
        map=new HashMap();
    }
    //hashSet中add()
    public boolean add(E e) {
            return map.put(e, PRESENT)==null;
    }
    public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
    }
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; //hash$Node 封装了key-value属性
        	int n, i;
        	//检验table是否为空 
            if ((tab = table) == null || (n = tab.length) == 0)
                n = (tab = resize()).length;
        	//通过与hash按位与计算 得出下标 并且需要判断该hash后的下标位置是否为空
            if ((p = tab[i = (n - 1) & hash]) == null)
                //空则生成结点Node加入该hash下标的位置
                tab[i] = newNode(hash, key, value, null);
        	//与对应的位置元素发生冲突(hash值相同) 
        	//三种情况
        	//1. 值相同或者是equals比较返回true
        	//2. 该节点是否是树节点
        	//3. 结点不相同 加入hash值对应下标的tab表
            else {
                Node<K,V> e; K k;
                //如果key相同或者equals比较返回true 则不加入该位置的结点后面
                if (p.hash == hash &&
                    ((k = p.key) == key || (key != null && key.equals(k))))
                    e = p;
                //判断该节点是否是树节点
                else if (p instanceof TreeNode)
                    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                //连接结点
                else {
                //死循环
                    for (int binCount = 0; ; ++binCount) {
                        //如果加入该节点后面的是为空 则连接
                        if ((e = p.next) == null) {
                            p.next = newNode(hash, key, value, null);
                            //同时加入后 判断该hash值对应下标tab的链表的结点个数有无到达8个 是否进行树化
                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                treeifyBin(tab, hash);
                            break;
                        }
                        //若hash相同 key相同 或者equals返回true 则不加入链表中
                        if (e.hash == hash &&
                            ((k = e.key) == key || (key != null && key.equals(k))))
                            break;
                        //不同p指向下一个结点 p移动
                        p = e;
                    }
                }
                //不返回空 则表明元素未加入
                if (e != null) { // existing mapping for key
                    V oldValue = e.value;
                    if (!onlyIfAbsent || oldValue == null)
                        e.value = value;
                    afterNodeAccess(e);
                    return oldValue;
                }
            }
            ++modCount;
            if (++size > threshold)
                resize();
            afterNodeInsertion(evict);
            return null;//空表示该位置可以加入元素
        }
  • TreeSet

    package Collection_;
    
    import java.util.Comparator;
    import java.util.TreeSet;
    
    /**
     * @author LouisBrilliant
     * @version 1.0
     */
    public class TreeSet_ {//低层 其实就是TreeMap
        public static void main(String[] args) {
            //必须设定自己比较器方法 下面的add方法才能执行
            //TreeSet的去重方式
            TreeSet treeSet = new TreeSet(new Comparator() {//new 接口{} 匿名内部类  构造器的参数也是接口 其实就是面向编程
                @Override
                public int compare(Object o1, Object o2) {
                    return ((String)o1).length()-((String)o2).length();//这里需要返回一个表达式 因为底层会帮你进行比较(大于 小于 等于)
                }
            });
            /* 
            	Comparator<? super K> cpr = comparator;
            if (cpr != null) {
                do {
                    parent = t;
                    cmp = cpr.compare(key, t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else
                        return t.setValue(value);
                } while (t != null);
            }
            else {//其中没有比较器参数时 执行String 中的compareTo方法
                if (key == null)
                    throw new NullPointerException();
                @SuppressWarnings("unchecked")
                    Comparable<? super K> k = (Comparable<? super K>) key;//字符串了型的key已经实现了Comparable接口 也就是默认的比较器Compartor
                do {
                    parent = t;
                    cmp = k.compareTo(t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else
                        return t.setValue(value);
                } while (t != null);
            }
             */
            treeSet.add("lucy");
            treeSet.add("lucy2");
            treeSet.add("lucy35");
            treeSet.add("lucy467");
        }
    }
    
  • LinkedHashSet

    1. 介绍

      • LinkedHashSet是LinkedSet的子类
      • 数组+双向链表维护构成
      • LinkedHashSet更加元素的hashCode安排数组下标位置,同时通过双向链表维护元素的次序,使得元素看起来是以顺序插入
      • LinkedHashSet 是不允许添加重复元素
    2. debug分析(双向链表)

    image-20230118141125636

//AA->1->2->3->4->5->NULL(after)
//NULL<-AA<-1<-2<-3<-4<-5(before)
public class LinkedHashMap_ {
    public static void main(String[] args) {
        Set set = new LinkedHashSet();
        set.add(new String("AA"));//底层维护了Node类型 前后指针 使得数据放进取出有了顺序
        set.add(1);
        set.add(2);
        set.add(3);
        set.add(4);
        set.add(5);
    }
}

1.2双列集合

1.2.1Map(接口)
  • 定义

    用于保存具有映射关系的数据:key-value(双列元素)

  • 特点

    1. Map中key不能重复,value值可以重复
    2. key和value可以任何的数据类型,会封装到HashMap$Node对象中
    3. key和value存在的是单向一对一关系,即通过指定的key可以找到对应的value值
  • 源码解读

    1. key-value是被Node对象封装
    2. 同时Node implements Entry接口 实现getValue和getKey方法
    3. 因此Entry接口可以存放Node类型的结点
    4. 将类型为Entry的元素存放在EntrySet中 为了遍历方便
    public class Map_ {
        public static void main(String[] args) {
            Map map = new HashMap();
            map.put(1,"bzd1");
            map.put(2,"bzd2");
            map.put(3,"bzd3");
            map.put(4,"bzd4");
            Set set = map.entrySet();//将map中entry返回到set集合中
            for (Object o :set) {//向上转型
                Map.Entry entry=(Map.Entry) o;//向下转型(拥有了Node独有的方法getValue和getKey) 运行类型是Node类型 Node类型实现了接口Map.Entry
                System.out.println(entry.getKey()+"-"+entry.getValue());
            }
        }
    }
  • 常用方法

    1. put 添加
    2. remove 根据键删除映射关系
    3. get 根据键获取值
    4. size 获取元素个数
    5. isEmpty 判断个数是否为0
    6. clear 清除
    7. containsKey 查找键是否存在
  • 遍历方法(基于HashMap)

package Collection_;
import javax.imageio.stream.IIOByteBuffer;
import java.util.*;
@SuppressWarnings("all")
public class Map_ {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put(1,"bzd1");
        map.put(2,"bzd2");
        map.put(3,"bzd3");
        map.put(4,"bzd4");
        //遍历方式
        //1. map.entrySet 
        Set set = map.entrySet();
        //1.1 增强for循环
        for (Object o :set) {//向上转型
            Map.Entry entry=(Map.Entry) o;
            System.out.println(entry.getKey()+"-"+entry.getValue());
        }
        //1.2 迭代器
        System.out.println("entryset===================");

        Iterator iterator2 = set.iterator();
        System.out.println(iterator2.next().getClass());//返回类型是class java.util.HashMap$Node
        while (iterator2.hasNext()) {
            Object entry =  iterator2.next();//父类指向子类Node
            Map.Entry m=(Map.Entry)entry;//需要向下转型
            System.out.println(m.getKey()+":"+m.getValue());
        }
        // 2. map.keySet 获取所有的key值 以集合类型返回
        Set set1 = map.keySet();
        //2.1 通过增强for循环
        for (Object key :set1) {
            System.out.println(key+":"+map.get(key));
        }
        //2.2 通过迭代器
        Iterator iterator = set1.iterator();//获取迭代器
        while (iterator.hasNext()) {//判断下一个是否为空?
            Object key = iterator.next();//不为空 则向后移一位
            System.out.println(key+":"+map.get(key));
        }
        //3. map.values 获取map中所有的values值
        Collection values = map.values();//返回一个集合类Collection
        //3.1 普通for循环
        System.out.println("2.for循环================");
        for (int i = 0; i < values.size(); i++) {
            System.out.println(i+":"+map.get(i));
        }
        //3.2 增强for循环
        System.out.println("2.增强for循环==================");
        for (Object value :values) {
            int i=0;
            System.out.println(value);

        }
        //3.3 迭代器
        Iterator iterator1 = values.iterator();
        while (iterator1.hasNext()) {
            Object value =  iterator1.next();
            System.out.println(value);
        }


    }
}
1.2.2HashMap
  1. 特点

    image-20230201163557981

  2. 原理分析

    跟上述的HashSet一致,就不赘述了…(详情请看HashSet源码分析)

    Set set = map.entrySet(); //返回的是Node类型对象类型
    for (Object o :set) {//向上转型
                Map.Entry entry=(Map.Entry) o;
                //向下转型 运行类型是Node类型 Node类型实现了接口Map.Entry
                //就是HashMap$Node实现了接口Map$Entry接口 数据也是存储在Node对象的
                System.out.println(entry.getKey()+"-"+entry.getValue());
    }

    image-20230206221801417

1.2.3HashTable
  1. 介绍
  • 存放键值对
  • hashtable中键和值都不能为null 否则抛出NullPointerException
  • 使用方法和HashMap一样
  • HashTable是线程安全
  • 键无序
package Collection_;

import java.util.Hashtable;

/**
 * @author LouisBrilliant
 * @version 1.0
 */
//hashtable
public class Hashtable_ {
    public static void main(String[] args) {
        Hashtable table = new Hashtable();
        table.put("key","value");//俩参数 key-value
//        table.put("key",null);//空指针异常
        table.put("key","VALUE");//替换
        table.put("key2","VALUE");//替换
        table.put("key3","VALUE");
        table.put("key4","VALUE");
        table.put("key5","VALUE");
        table.put("key6","VALUE");
        table.put("key7","VALUE");
        //扩容机制 2x+1 临界值threshold=初始化Hashtable$entry[]为11 11*0.75=8
        //addEntry(hash,key,value,index) 添加K-V封装到Entry
        //当if(count>threshold)满足时 进行 扩容
        //int newCapacity =(oldCapacity<<1)+1 所以第一次扩容 11*2+1=23
        table.put("key8","VALUE");//替换
        table.put("key9","VALUE");//替换

        System.out.println(table);
    }

}
1.2.4Properties

读取文件

package Collection_;
import java.util.Properties;

/**
 * @author LouisBrilliant
 * @version 1.0
 */
public class properties_ {
    public static void main(String[] args) {
        //properties继承了Hashtable类并且实现了Map接口 也是键值对存储数据
        //Properties还可以用于从xxx.properties文件(配置文件)中加载数据到Properties类对象 进行读取和修改
        //因为properties继承了hashtable 所以key和value都不能为空
        //properties和hashtable类似
        Properties properties = new Properties();
//        properties.put(null,"wocao");//异常
//        properties.put(1,null);//异常
            //添加
            properties.put("1","hjy");
            //删除
            properties.remove("1");
            //查
            properties.getProperty("1");
            properties.get("1");
            
    }
}
1.2.5TreeMap
package Collection_;

import java.util.Comparator;
import java.util.TreeMap;

/**
 * @author LouisBrilliant
 * @version 1.0
 */
public class TreeMap_ {
    public static void main(String[] args) {
        /*
            public TreeMap(Comparator<? super K> comparator) {
                this.comparator = comparator;
        }
         */
        //跟TreeSet一样 都是可以面向接口编程
        TreeMap treeMap = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).length()-((String)o2).length();
            }
        });

    }
}

七、泛型