注解、反射、集合

annotation

内置注解

override、deprecated(弃用)、SupressWarnings(镇压警告)

元注解 注解的注解

Target注解的作用是:描述注解的使用范围(即:被修饰的注解可以用在什么地方) 。
Reteniton注解的作用是:描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时)。
Documented注解的作用是:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。
Inherited注解的作用是:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)。

自定义注解

public class test04 {
    @MyAnnotation2(age = 12,name = "dudu")
    public void test(){};

    @MyAnnotation3("dudu")
    public void test2(){};
}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2 {
    //注解的参数:参数类型+参数名();
    String name() default "";
    int age() default 0;
    int id() default -1;

    String[] schools() default {"清华","北大"};

}
//只有一个参数的时候,参数名字为value,则可以省略
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation3 {
    String value();
}

接口回忆

普通类:只有具体实现
抽象类:具体实现和约束(抽象方法)都有
接口:只有约束 无法自己写方法,定义的所有方法都是抽象的public abstract的
接口可以多继承
实现了接口的类就必须重写接口中的方法
接口不能实例化

反射

得到class类的方式

5种

        Person person = new Student();
        System.out.println("这个人是" + person.name);
        //通过类名获得
        Class c1 = Student.class;
        //通过对象获得
        Class c2 = person.getClass();
        //通过forname获得
        Class c3 = Class.forName("org.example.reflection.Student");

        //基本内置类型的包装类都有一个Type属性
        Class c4 = Integer.TYPE;

        //获得父类类型
        Class c5 = c1.getSuperclass();
        System.out.println(c5);
        //Java 中 Class 对象的 toString() 默认输出 class 关键字 + 类的全限定名。

类加载内存分析

栈,堆,方法区(特殊的堆)
三步:加载、链接、初始化

类加载过程的第一步,主要完成下面 3 件事情:
通过全类名获取定义此类的二进制字节流。
将字节流所代表的静态存储结构转换为方法区的运行时数据结构。
在内存中生成一个代表该类的 java.lang.Class 对象,作为方法区这些数据的访问入口

类初始化

public static void main(String[] args) throws ClassNotFoundException {
        //1.主动引用
        //Son son = new Son();

        //2.反射也会产生主动引用
        //Class.forName("org.example.reflection.Son");

        //System.out.println(Son.m);

        //不会产生类的初始化的方式
        //System.out.println(Son.b);

        //Son[] array = new Son[10];


        System.out.println(Son.M);


    }

获得类的运行时结构

可以通过class获得的信息:
实现的全部接口、继承的父类、全部的构造器、全部的方法、全部的field、注解

动态创建对象执行方法

//获得class对象
       Class c1 =  Class.forName("org.example.reflection.user");
        /*
       //构造一个对象
        user user = (user)c1.newInstance();//调用了无参构造器
        System.out.println(user);

        //通过构造器创建对象
        Constructor c = c1.getDeclaredConstructor(String.class, int.class, int.class);
        user user2 = (user)c.newInstance("dudu",03,23);
        System.out.println(user2);*/

        //通过反射调用普通方法
        user u = (user) c1.newInstance();
        Method setName = c1.getMethod("setName",String.class);//通过反射获取一个方法
        setName.invoke(u,"kuang");//invoke表示激活,把对象也传进去了
        System.out.println(u.getName());

        //通过反射操作属性
        user uu = (user) c1.newInstance();
        Field name = c1.getDeclaredField("name");
        name.setAccessible(true);//不能直接操作私有属性,需要取消安全检测

        name.set(uu,"kuang2");
        System.out.println(uu.getName());

获得注解信息

集合

动态保存多个对象;提供了一系列操作对象的方法;

框架体系

单列集合、双列集合 存放键值对

collection接口和常用方法

contains是否有某个元素,add,remove,isEmpty,size,clear

遍历方式
方式1:
迭代器遍历快捷键itit
next作用:指针下移+返回当前值
一次循环后希望再次遍历需要重置迭代器
方式2:
增强for循环,底层仍然是迭代器,快捷键I
方式3,普通for遍历

list接口方法

list接口是collection的子接口
常见三种实现类:ArrayList,LinkedList,Vector(遍历方式都是一样的三种,大概明白了点为什么要面向接口编程)

如果直接使用具体实现类,当修改具体类或想切换到其他实现时,可能需要大面积修改代码。
而使用接口,只需替换接口的实现类即可,其他代码无需修改。

元素存储有序、且可重复、支持索引


subList返回的是一个前闭后开的区间
add,addAll,remove,set,get,indexOf

ArrayList注意事项与源码

  • 存放所有的元素,包括空值,甚至可以加入多个空值
  • 是由数组来实现数据存储的
  • 基本等同意vector,但ArrayList是线程不安全的,多线程的情况下不适合使用

    transient关键字用来表示 瞬态(临时)变量,不会被序列化保存
    对无参构造器的扩容进行分析 对有参构造器的扩容进行分析
    jdk17源码改了,但是机制还是没变
    图解源码

vector注意事项与源码

linkedList

继承了list和deque接口,维护了一个双向链表
javaguide源码分析

ArrayList改查效率高,LinkedList增删效率高,大多数业务以查询为主,用ArrayList的情况较多

set接口方法

  • 无序(添加与取出数据的顺序不一致)
  • 没有索引
  • 不能重复,最多只包含一个null值

    取出的顺序虽然和添加数据的顺序不一致,但是每次取出的顺序是一致的

遍历方式只有迭代器与增强for循环

hashSet


hashset还是set那些性质,底层是hashmap

        //不能加入相同元素/数据
        set.add("apple");
        set.add("apple");//添加不了
        set.add(new Dog("tom"));//是不同的对象
        set.add(new Dog("tom"));//可以加入

        //String类型??加入不了
        set.add(new String("dudu"));
        set.add(new String("dudu"));//加入不了

源码介绍
hashmap的底层是数组+链表+红黑树

    public static void main(String[] args) {
        HashSet set = new HashSet();
        set.add("dudu");

        /*执行add
        public boolean add(E e) {
            return map.put(e, PRESENT)==null;present是一个占位对象
        }*/

        /*执行put,该方法会执行hash(key)得到key对应的hash值(不是hashcode)
        public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }

        /*执行putVal
        * final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;//定义辅助变量
        //table是hashmap的一个数组,类型是一个node数组
        if ((tab = table) == null || (n = tab.length) == 0)//table如果是空或者大小为0
            n = (tab = resize()).length;//第一次扩容到16

         */
        //根据key,得到hash,去计算key应该放到table表的哪个索引位置
        //并把这个位置的对象赋给p
       /* if ((p = tab[i = (n - 1) & hash]) == null)//等于是mod(n-1),数组的下标
            tab[i] = newNode(hash, key, value, null);p为null,表示还没有存放元素,就创建一个Node
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))//哈希值相同且(key相同),判断的是第一个节点
                e = p;//此时就不加入,赋给e
            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);
                        // 结点数量达到阈值(默认为 8 ),执行 treeifyBin 方法
                    // 这个方法会根据 HashMap 数组来决定是否转换为红黑树。
                    // 只有当数组长度大于或者等于 64 的情况下,才会执行转换红黑树操作,以减少搜索时间。否则,就是只是对数组扩容。
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))//如果有相同的节点,跳出循环
                        break;
                    p = e;//等于p向下移动一个
                }
            }
            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;
    }
*/

        System.out.println(set);
    }

扩容也比较讲究,有个阈值,达到阈值就按照2倍进行扩容(加入的节点数量大于阈值,factor一般是0.75);
每个链表元素个数达到8之后,如果数组大小也达到了64就将链表进化成红黑树

LinkedHashSet

是hashset的子类,底层维护了一个数组+双向链表,链表维护了元素的次序

map接口

//Map与collection并列存在,保存的是键值对,hashset中的元素对应的是这里的键key,present对应的是这里的值value
//所以key不允许重复,可以为null值,但是最多只有一个
一对k-v放在一个hashMap$Node中
为了方便遍历创建了一个EntrySet集合,存放的元素类型Entry,而一个Entry对象就有k,v(这个好像就是用来方便遍历的?)
EntrySet其实是一个内部类,定义的元素类型是Map.Entry,实际上还是存放的hashMap$Node
static class Node<K,V> implements Map.Entry<K,V>
巴拉巴拉反正就是把hashMap$Node放到EntrySet中方便遍历啦,Map.Entry提供重要的方法

六种遍历方法

public class Map_ {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("no1","dudu");
        map.put("no2","Amy");
        map.put("no3","Bob");
        //Map与collection并列存在,保存的是键值对,hashset中的元素对应的是这里的键key,present对应的是这里的值value
        //所以key不允许重复,可以为null值,但是最多只有一个
        //当有相同的k时就等价于替换
        map.put("no1","dada");
        //k-V之间是单向一对一关系
        System.out.println(map);
        //六种遍历方式
        //第一组:key
        Set keyset = map.keySet();//取出的keyset是一个set
        //增强for
        for (Object key : keyset) {
            System.out.println(key +"-"+ map.get(key));
        }
        //迭代器
        Iterator iterator = keyset.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println(next +"-"+ map.get(next));
        }
        System.out.println("================");

        //第二组:values
        //增强for
        Collection values = map.values();//取出的value是一个collection
        for (Object value : values) {
            System.out.println(value);
        }
        //迭代器
        Iterator iterator2 = values.iterator();
        while (iterator2.hasNext()) {
            Object next = iterator2.next();
            System.out.println(next);
        }
        System.out.println("================");

        //通过entrySet来获取
        //增强for
        Set enrtySet = map.entrySet();
        for (Object entry : enrtySet) {
            Map.Entry m = (Map.Entry) entry;//将entry转换为entrySet,向下转型(obj->Map.Entry)
            //向下转型(从父类类型转为子类类型)是需要显式强制类型转换的,需要使用括号明确指定类型。向下转型是加了一部分,向上转型是抛弃一部分,向下转型比较危险
            System.out.println(m.getKey()+"-"+m.getValue());

        }
        //迭代器
        Iterator iterator3 = enrtySet.iterator();
        while (iterator3.hasNext()) {
            Object next =  iterator3.next();
            //向下转型
            Map.Entry m = (Map.Entry) next;
            System.out.println(m.getKey()+"-"+m.getValue());

        }
    }
}

map接口的常用方法
put添加,根据键删除remove,根据键取值get,获取元素个数size,isEmpty,clear,查找键是否存在containsKey

HashMap

HashMap没有实现同步,线程不安全
Map接口的常用类:HashMap、Hashtable、properties

底层还是数组+链表+红黑树
table数组-node节点-实现了Map.entry接口

源码分析同hashset
扩容触发机制:一个链表上的大于8但table数组小于64时进行扩容,如果达到64并且一个链表上的node数量大于8的话会进行树化;
扩容还是按照二倍进扩容,达到阈值就进行扩容(自己设计程序验证,增强源码阅读能力)

Hashtable & Properties

与Hashmap类似,Hashtable是线程安全的(synchronized),键与值都不能为空
底层的数组是Hashtable$Entry[],初始化大小为11,阈值为8,按照2倍+1的原则扩容

Properties 继承于 Hashtable,用于管理配置信息的类。
由于 Properties 继承自 Hashtable 类,因此具有 Hashtable 的所有功能,同时还提供了一些特殊的方法来读取和写入属性文件。
Properties 类常用于存储程序的配置信息,例如数据库连接信息、日志输出配置、应用程序设置等。使用Properties类,可以将这些信息存储在一个文本文件中,并在程序中读取这些信息

选择集合实现类

collections工具类

是一个用来操作set,list,map等集合的工具类
sort(比较器comparator的用法),shuffle(随机打乱),reverse,swap
max(list),max(list,new Comparator),min,frequency,copy,replaceAll

ex06
默认的hashcode是根据对象的内存地址来计算的

public class ex6
{
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        person p1 = new person("AA",1001);
        person p2 = new person("BB",1002);
        hashSet.add(p1);
        hashSet.add(p2);
        p1.name = "CC";
        hashSet.remove(p1);//有两个输出,删除失败,删除的位置上没有node
        System.out.println(hashSet);
        hashSet.add(new person("CC",1001));
        System.out.println(hashSet);//有三个输出,在新的位置上加入这个对象
        hashSet.add(new person("AA",1001));
        System.out.println(hashSet);//四个输出,在(AA,1001)后面加上这个对象

    }
}

TreeSet,TreeMap,LinkedHashSet源码没看

posted @ 2025-03-11 21:20  huhulahu  阅读(9)  评论(0)    收藏  举报