Java集合之单列集合

news2025/1/12 3:45:27

分类

集合分为单列集合(Collection)和双列集合(Map)

单列集合的体系结构

List集合和Set集合的区别

  •  List系列集合:添加元素是有序的(添加的顺序,而非数据的大小顺序)、可重复、有索引
  • Set系列集合:添加的元素是无序的(添加的顺序,而非数据的大小顺序,并且是可能不是有序的)、不可重复、无索引

contains方法细节

  • 底层是依赖equals方法进行判断自定义对象是否存在的
  • 要想通过contains方法判断自定义对象是否存在集合中,就必须在自定义对象类中重写equals方法,因为如果自定义类中没有重写equals方法,它会默认使用Object类中的equals方法进行判断,而Object类中的equals方法是通过地址值进行判断的。
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals( name, student.name );
    }
public class Test01 {
    public static void main(String[] args) {
        Student stu = new Student( "来一沓Java", 18 );
        Collection<Student> coll = new ArrayList<Student>();
        coll.add( stu );
        Student stu1 = new Student( "来一沓Java", 18 );
        boolean b = coll.contains( stu1 );
        System.out.println(b);//true
    }
}

集合遍历方式

Collection的通用遍历方式有四三种,增强for循环遍历、迭代器遍历、Lambda表达式遍历

 迭代器遍历

迭代器在Java中的类是Iterator,迭代器是集合专用的遍历方式

Collection集合获取迭代器

Iterator<E> iterator():返回迭代器对象,默认指向当前集合的0索引

Iterator中常用的方法

Boolean hasNext():判断当前位置是否有元素,有返回true,没有返回false

E next():获取当前位置的元素,并将迭代器对象移向下一个位置

public class Test01 {
    public static void main(String[] args) {
        Collection<String> col = new ArrayList<>();
        col.add( "今" );
        col.add( "天" );
        col.add( "学" );
        col.add( "习" );
        col.add( "了" );
        col.add( "吗" );
        col.add( "?" );

        Iterator<String> iterator = col.iterator();//获取迭代器对象
        while (iterator.hasNext()){//判断当前位置是否有元素
            String s = iterator.next();//获取当前位置的元素
            System.out.print( s);
        }
    }
}

 细节注意

  • 如果迭代器已经指向最后的位置且没有元素,还要强行调用next方法获取元素,就会报NoSuchElementException
  • 迭代器遍历完毕后,指针是不会复位的
  • 循环中只能调用一次next方法
  • 迭代器遍历时,不能用集合的方法进行增加或删除元素

增强for遍历

  •  增强for的底层就是一个迭代器,为了简化迭代器的代码书写的
  • 它是JDK5之后出现的,内部原理就是一个Iterator迭代器
  • 所有的单列集合和数组才能用增强for进行遍历
public class Test02 {
    public static void main(String[] args) {
        Collection<String> col = new ArrayList<>();
        col.add( "今" );
        col.add( "天" );
        col.add( "学" );
        col.add( "习" );
        col.add( "了" );
        col.add( "吗" );
        col.add( "?" );
        for (String s : col) {
            System.out.print(s);
        }
    }
}

注意细节

修改增强for中的变量,是不会改变集合原来的数据 

Lambda表达式遍历

JDK8开始出现的,是一种简单直接的遍历集合的方式

public class Test03 {
    public static void main(String[] args) {
        Collection<String> col = new ArrayList<>();
        col.add( "今" );
        col.add( "天" );
        col.add( "学" );
        col.add( "习" );
        col.add( "了" );
        col.add( "吗" );
        col.add( "?" );
        //forEach方法底层原理:其实也会自己遍历,一次得到每一个元素,把得到的元素,传递给下面的accept方法
        //col.forEach( new Consumer<String>() {
            //@Override
            //public void accept(String s) {
                //System.out.print(s);
           // }
        //} );
        col.forEach( s -> System.out.print(s) );
    }
}

List系列集合

List集合特有的方法

  • void add(int index,E element):在集合的指定位置插入元素,原来索引处后面的元素一次往后移动
  • E remove(int index):删除指定位置的元素并返回
  • E set(int index,E element):修改指定位置的元素,返回被修改的元素
  • E get(int index):返回指定位置的元素

细节注意

  • 调用方式的时候,如果方法出现了重载现象,会优先调用形参和实参类型一致的那个方法

 List集合的遍历方式

List集合遍历方法有五种,迭代器遍历、增强for、普通for循环、Lambda表达式、列表迭代器

 迭代器遍历

public class Test01 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add( 1 );
        list.add( 2 );
        list.add( 3 );

        Iterator<Integer> it = list.iterator();
        while (it.hasNext()){
            Integer next = it.next();
            System.out.print(next);
        }
    }
}

增强for遍历

public class Test02 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add( 1 );
        list.add( 2 );
        list.add( 3 );

        for (Integer integer : list) {
            System.out.print(integer);
        }
    }
}

普通for遍历

public class Test03 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add( 1 );
        list.add( 2 );
        list.add( 3 );
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get( i ));
        }
    }
}

Lambda表达式遍历

public class Test04 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add( 1 );
        list.add( 2 );
        list.add( 3 );
        list.forEach( integer -> System.out.print(integer) );
    }
}

列表迭代器遍历

public class Test05 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add( 1 );
        list.add( 2 );
        list.add( 3 );
        //获取一个列表迭代器对象,里面的指针默认指向0索引
        //列表迭代器中额外添加了一个方法,在遍历的时候,可以添加元素
        ListIterator<Integer> listIterator = list.listIterator();
        while (listIterator.hasNext()){
            Integer next = listIterator.next();
            System.out.print(next);
        }
    }
}

五种方式对比

  • 在遍历过程中需要删除元素,用迭代器
  • 在遍历过程中需要添加元素,用列表迭代器
  • 在遍历过程中不做任何操作,使用增强for或Lambda表达式
  • 遍历过程中需要用到索引,使用普通for遍历

数据结构(栈、队列、数组、链表)

  •  是计算机底层存储、组织数据的方式。是指数据相互之间是以什么方式排列在一起的。
  • 数据结构的学习思路:每种数据结构长啥样子-->如何添加数据-->如何删除数据。

特点:先进后出,后进先出 

队列

特点:先进先出,后进后出 

数组【查询快,增删慢】

  •  查询速度快:通过地址值和索引定位,查询任意数据耗时相同。(元素在内存中连续存储的)。
  • 删除效率低:要删除元素的同时,后面的元素一次往前挪。
  • 添加效率极低:添加位置后的每一个元素都要往后移,在添加元素。

单向链表【查询慢、增删快】

  • 每一个节点都有两个空间,第一个存储只是需要存储的值,第二个存储的是下一个节点的地址
  • 链表中的节点是独立的对象,在内存中是不连续的,每一个节点包含数值和下一个节点的地址

 双向链表

  • 每一个节点都有三个空间,第一个存储的是前一个节点的地址,第二个存储的是真实需要存储的值,第三个存储的是下一个节点的地址
  • 链表中的节点是独立的对象,在内存中是不连续的,每一个节点包含前一个节点的地址、需要存储的数值和下一个节点的地址

 ArrayList源码分析

 ArrayList集合底层原理

  • 利用空参创建的集合,在底层创建一个默认长度为0的数组
  • 添加第一个元素时,底层会创建一个新的长度为10的数组
  • 当存满时,会创建一个长度是原来数组的1.5倍,把元数组中的数据拷贝到新数组中,再将新数组赋值给原来的数组【存满时,会扩容1.5倍】
  • 如果一次添加多个元素,1.5倍不够放,新创建的数组的长度以实际长度为准

分析图

 LinkedList集合源码分析

底层数据结构是双向链表,查询慢,增删快,但是如果操作的是首尾相连元素,速度就很快了。

LinkedList集合特有方法

  • public void addFirst(E e):在该列表开头插入指定元素
  • public void addLast(E e):将指定的元素追加到列表的末尾
  • public E getFirst():返回列表中的第一个元素
  • public E getLast():返回列表中的最后一个元素
  • public E removeFirst():删除列表中的第一个元素并返回
  • public E removeLast():删除列表中的最后一个元素并返回

源码分析

public class Test01 {
    public static void main(String[] args) {
        LinkedList<String> lk = new LinkedList<>();
        lk.add( "aaa" );
        lk.add( "bbb" );
        lk.add( "ccc" );
    }
}
    public boolean add(E e) {//参数e表示现在要添加的元素
        linkLast(e);
        return true;
    }
    void linkLast(E 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++;
    }

下面代码表示链表中的一个节点 

private static class Node<E> {
    E item;//存储的数据
    Node<E> next;//前一个结点的地址
    Node<E> prev;//后一个节点的地址

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

        当第一次添加元素时,会调用第三段代码,会把 last(null)【一个节点当中存储下一个节点地址的值】 赋值给 l ,然后new Node<>(l,e,null)创建一个新节点,然后再把新节点赋值给 last 后,判断 l 是否为空,如果为空,就把新节点赋值给 first ,否则就把新节点的地址赋值给 l.next;

        当不是第一次添加元素时,会把同一个LinkedList集合最后一次添加的数据所在的节点赋值给 l ,后new Node<>(l,e,null)创建新节点,后再把新节点赋值给 last ,判断 l 是否为空,显然不为空,就将新节点的地址赋值给 l.next。

迭代器源码分析

public class Test01 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add( "aaa" );
        list.add( "bbb" );
        list.add( "ccc" );
        list.add( "ddd" );

        //在底层就是创建一个内部类的对象,这个内部类表示是ArrayList的迭代器,所以多次调用这个方法时,相当于创建多个迭代器的对象
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            String next = iterator.next();
            System.out.println(next);
        }
    }
}
    public Iterator<E> iterator() {
        return new Itr();
    }
private class Itr implements Iterator<E> {
        int cursor;       // 下一个要返回的元素的索引,相当于指针,指向要返回的数据索引
        int lastRet = -1; // 最后一次操作的索引
        int expectedModCount = modCount;

        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }


        public E next() {
            checkForComodification();
            int i = cursor;//记录当前指针指向的索引位置
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
}

        当调用iterator()方法时,会创建一个Itr的内部类对象,进入循环以后,判断cursor是否等于集合的长度,如果不是,hasNext()方法返回true,循环继续,当调用next()方法时,将cursor的值赋值给 i ,判断 i 是否大于等于集合长度,如果大于等于,就抛出异常,如果不是,cursor等于 i + 1【将当前的指针向后移动一位】,然后将 i 的值赋值给lastRet,并返回数组elementData中lastRet索引处的元素。

         modCount:表示集合变化的次数,每add或remove一次,modCount都会自增,当创建迭代器的时候,就会把变化的次数告诉迭代器。而checkForComodification();就是校验传入迭代器的次数与当前变化次数是否相同,不同报并发修改异常。

Set系列集合

  • 无序:存取顺序不一致
  • 不重复:数据有唯一性,可以利用数据去重
  • 无索引:没有带索引的方法,所以不能通过索引来操作集合
  • Set系列集合的方法基本上和Collection的API一致

HashSet

  • 无序、不重复、无索引

底层

  •  HashSet集合底层是通过哈希表【一种对于数据增删查改性能比较好的结构,JDK8之前由数组和链表组成,JDK8开始由数组、链表和红黑树组成】存储数据
  • 开始会创建一个默认长度为16,默认加载因子为0.75【扩容时机,当数组中存了 数组长度 x 0.75 个元素时,就会扩容到原来的2倍;JDK8之后,当链表长度大于8且数组长度大于等于64,链表会自动转成红黑树】的数组table
  • 根据元素的哈希值跟数组的长度计算出应存入的位置
  • 判断当前位置是否有元素,没有元素直接存入
  • 如果有元素,会调用equals方法比较属性
  • 如果属性值一样就不存入,如果不一样,存入数组,形成链表【JDK8以前:新元素存入数组,老元素挂在新元素下面   JDK8后:新元素直接挂在老元素的下面】

对象哈希值的特点

  • 如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
  • 如果重写hashCode方法,不同对象只要属性值相同,计算出的哈希值就是一样的
  • 部分情况下,不同属性值或不同地址值计算出来的哈希值可能是一样的【哈希碰撞】 

哈希值

  • 根据hashCode方法计算出的int类型的整数
  • 该方法定义在Object类中,所有对象都可调用,默认使用地址值进行计算
  • 一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值 

LinkedHashSet

  • 有序、不重复、无索引

 LinkedHashSet底层原理

  • 底层数据结构依旧是哈希表,只是每个元素又额外多了一个双链表机制记录存储的顺序
  • 当添加第二个元素之后,第一个元素会记录第二元素地址值,第二个元素也会记录第一个元素的地址值,依次往后
  • 如果后面添加的元素哈希值相同的情况下,新元素会以双向链表的形式直接挂在老元素的下面,在该元素之后添加的一个数据,如果不产生哈希冲突,就存入数组,和该元素形成双向链表

TreeSet

  • 可排序、不重复、无索引
  • TreeSet底层基于红黑树的数据结构实现排序,增删查改性能较好

TreeSet集合默认排序规则

  • 数值类型:Integer、Double,默认从小到大排序
  • 字符、字符串:按照字符在ASCII码中数字升序进行排序

 TreeSet集合比较方式一

  • 默认排序/自然排序:javabean类中实现Comparable接口指定比较规则
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) {
        return this.getAge()-o.getAge();
    }
}
public class Test02 {
    public static void main(String[] args) {
        Student s1 = new Student( "wi", 18 );
        Student s2 = new Student( "xn", 19 );
        Student s3 = new Student( "xg", 17 );

        TreeSet<Student> set = new TreeSet<>();
        set.add( s1 );
        set.add( s2 );
        set.add( s3 );

        System.out.println( set );
    }
}

 TreeSet集合比较方式二

  • 比较器排序:创建TreeSet的时候,传递比较器Comparator指定规则,默认使用第一种,当第一种不满足时,就使用第二种比较方式
public class Test03 {
    //"c","ab","df","java",四个字符串按照长度比较,如果一样长的按照首字母顺序排序
    public static void main(String[] args) {
        TreeSet<String> set = new TreeSet<>( new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                int i = o1.length() - o2.length();
                i = i == 0 ? o1.compareTo( o2 ) : i;
                return i;
            }
        } );
        set.add( "c" );
        set.add( "ab" );
        set.add( "df" );
        set.add( "java" );

        System.out.println( set );
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/496496.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

为什么在Ubuntu系统使用附加驱动更新Nvidia显卡驱动不起作用

1. 硬件环境 CPU&#xff1a;AMD Ryzen 9 5950x 16-core processor 32 GPU&#xff1a;双GeForce RTX 3090 操作系统&#xff1a;Ubuntu 22.04.2 LTS 64 位 主板&#xff1a;ASUS的ROG CROSSHAIR VIII EXTREME 2. 问题描述 使用上图所示的附加驱动程序更新Nvidia显卡驱动&am…

恢复item2和oh-my-zsh的配置

1. 首先正常安装item2 2. 加载onedrive里的传家宝iterm2_default_profile.json&#xff0c;让iterm2的配置生效 2. 然后正常安装oh-my-zsh (官方步骤&#xff1a; sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)&q…

C# 学习abstract

abstract 顾名思义&#xff1a;抽象 从微软官方文档来看&#xff1a;abstract 修饰符指示被修改内容的实现已丢失或不完整。 abstract 修饰符可用于类、方法、属性、索引和事件。 在类声明中使用 abstract 修饰符来指示某个类仅用作其他类的基类&#xff0c;而不用于自行进行…

linux内核调试的几个方法

参考 以下内容&#xff1a; Linux 笔记&#xff1a; https://xuesong.blog.csdn.net/article/details/109522945?spm1001.2014.3001.5502 printk: printk在内核源码中用来记录日志信息的函数&#xff0c;只能在内核源码范围内使用。用法和printf非常相似&#xff1b; printk…

InsCode体验报告

文章目录 前言一、InsCode是什么&#xff1f;二、体验过程1.创建项目2.在线IDE3.运行和部署项目4.浏览和学习项目5.分享和协作项目6.支持AI助手 三、体验感受优点缺点 总结 官方宣传视频 InsCode-AI 前言 作为一个大三计算机专业的学生&#xff0c;我对编程有着浓厚的兴趣和热…

Triloga 的任务 — Satta 系列来袭!

谁战胜了这些凶兽&#xff0c;谁就获得了力量&#xff0c;让我们通过装备体现出来&#xff01;来自神秘洞穴的疯狂昆虫外壳&#xff0c;黄昏之地燃烧中的部落的骨质盔甲&#xff0c;以及深海的美妙灯光。 Triloga 的任务——Satta 系列已在 The Sandbox 市场平台上架&#xff1…

JVM(类的加载与ClassLoader、双亲委派机制)

文章目录 1. 类的生命周期2. 类的加载过程3. 类加载器&#xff08;classloader)3.1 类加载器的作用3.2 类加载器的分类(JDK8)3.3 双亲委派机制3.3.1 双亲委派机制优势 3.4 查看某个类的类加载器对象3.5 使用ClassLoader获取流 1. 类的生命周期 类在内存中完整的生命周期&#…

( “ 图 “ 之 并查集 ) 684. 冗余连接 ——【Leetcode每日一题】

❓684. 冗余连接 难度&#xff1a;中等 树可以看成是一个连通且 无环 的 无向 图。 给定往一棵 n 个节点 (节点值 1&#xff5e;n ) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间&#xff0c;且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n…

基于 EKS Fargate 搭建微服务性能分析系统

背景 近期 Amazon Fargate 在中国区正式落地&#xff0c;因 Fargate 使用 Serverless 架构&#xff0c;更加适合对性能要求不敏感的服务使用&#xff0c;Pyroscope 是一款基于 Golang 开发的应用程序性能分析工具&#xff0c;Pyroscope 的服务端为无状态服务且性能要求不敏感&…

Java阶段二Day14

Java阶段二Day14 文章目录 Java阶段二Day14复习前日知识点SpringFramework版本SpringFramework核心SpringFramework创建工程SpringFramework相关概念bean对象的创建过程xml配置文件中的标签 基于XML管理bean对象类型属性的注入数组类型属性注入集合类型属性注入p命名空间引入外…

ConMask: Open-World Knowledge Graph Completion

目录 Abstract Introduction Model Relationship-Dependent Content Masking Target Fusion Loss Function [1711.03438] Open-World Knowledge Graph Completion (arxiv.org) Abstract 引入一个名为ConMask的开放世界KGC模型&#xff0c;该模型学习实体名称和部分文本…

数据结构与算法基础-学习-23-图之邻接矩阵与邻接表

目录 一、定义和术语 二、存储结构 1、邻接矩阵 1.1、邻接矩阵优点 1.2、邻接矩阵缺点 2、邻接表 3、邻接矩阵和邻接表的区别和用途 3.1、区别 3.2、用途 三、宏定义 四、结构体定义 1、邻接矩阵 2、邻接表 3、网数据类型&#xff08;造测试数据&#xff09; 五…

如何使用TRIZ理论来分析问题和解决问题?

文章目录 TRIZ基础现代TRIZ步骤 TRIZ基础 现代TRIZ 经典的TRIZ方法对专利进行分析,认为专利分为两个部分,一部分是需要解决的问题,一部分是解决问题的解决方案.首先是问题的分析,确定是否是初始问题,比如工具功能分析/特性传递等工具. 步骤 问题识别 主要是识别出初始问题;…

MATLAB实现建筑热平衡模型建立及节能温控方案

全球大约1/3的能源消耗于建筑。在能源紧张的今天&#xff0c;如何减少建筑的能源浪费是一个值得研究的课题。 本文在综合国内外建筑能耗模拟方法的基础上&#xff0c;采用热平衡法&#xff0c;针对一小型建筑建立了热特性仿真模型&#xff0c;选用武汉地区的气象数据&#xff…

JAVA11新特性

JAVA11新特性 概述 2018年9月26日,Oracle官方发布JAVA11.这是JAVA大版本周期变化后的第一个长期支持版本,非常值得关注.最新发布的JAVA11将带来ZGC HttpClient等重要特性,一共17个需要我们关注的JEP,参考文档http://openjdk.java.net/projects/jdk/11/ 181:基于嵌套的访问控制…

云计算适合大专生学吗?

云计算适合大专生学吗&#xff1f; 对于大专毕业生来说&#xff0c;云计算的确是一个不错的选择&#xff0c;因为云计算技术应用专业&#xff0c;主要就是专科院校在办学。不管你是计算机相关专业的&#xff0c;还是零基础想学习都是可以的&#xff1b;原因就在于云计算这门专业…

七款非常好用的 ChatGPT 开源插件

推荐7款很好用的 ChatGPT 开源插件 1. ChatGPT ProBot 这是一个基于chatGPT实现的Github机器人&#xff0c;可以让chatGPT帮你审核代码、重构代码&#xff0c;还可以在Github页面上和它进行聊天&#xff0c;咨询问题。 仓库地址: github.com/oceanlvr/Ch… 2.chatgpt-api 这…

如何在本地部署运行ChatGLMS-6B

在本篇技术博客中&#xff0c;将展示如何在本地获取运行代码和模型&#xff0c;并配置环境以及 Web GUI&#xff0c;最后通过 Gradio 的网页版 Demo 进行聊天。 官方介绍 ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM)…

图应用替换算法

文章目录 LRUSHiPBeladys MIN replacement(T-OPT)图应用基本知识CSR和CSCT-OPT替换算法使用 P-OPTRereference MatrixModified Rereference Matrix LRU 过于简单不做具体介绍 SHiP SHiP全称Signature-base Hit Predctor算法&#xff0c;其主打的是基于Signature(签名)进行Pr…

flutter学习之旅 -有状态的组件(StatefulWidget)

文章目录 StatefulWidget格式实现num增加finalfinal定义数组 我们重建一个项目 flutter create 项目名我们会看到 在Flutter中自定义组件其实就是一个类&#xff0c;这个类需要继承StatelessWidget/StatefulWidget StatelessWidget是无状态组件&#xff0c;状态不可改变的widg…