Java 集合框架,泛型,包装类

news2024/11/29 2:38:54

文章目录

  • 集合框架
  • 泛型
    • Java 中的泛型
    • 裸类型(了解)
    • 原理
    • 泛型的上界
    • 泛型方法
    • 通配符
  • 包装类
  • ArrayList
    • 构造
    • 常见操作
  • LinkedList
  • Stack
  • Queue
  • PriorityQueue
  • Map
    • Map.Entry<K, V>
    • Map 常用方法
  • Set
    • 常用方法

集合框架

img

  • Vector 一个古老的集合类,实现了一个动态数组,现在已经不常用
  • Stack
  • ArrayList 顺序表
  • LinkedList 链表+队列+双端队列
  • PriorityQueue 优先级队列
  • HashSet 集合(不重复)(哈希表实现)
  • HashMap 哈希表
  • TreeSet 有序集合(红黑树)
  • TreeMap 有序键值对(红黑树)

泛型

Java 中的泛型

Java 中的泛型是通过在类、接口或方法的声明中使用尖括号 < > 来实现的。

public class Box<T> { // 类型参数也可以指定多个,用 ',' 分隔
    private T value;

    public Box(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

Java 中泛型的类型参数不能直接是基本数据类型, 而应该是引用类型

Box<int> intBox = new Box<>(42); // 错误的
Box<Integer> intBox = new Box<>(42); // 正确的
Box<Integer> intBox = new Box<Integer>(42); // 后面的Integer可以写也可以省略

规范:类型形参一般使用一个大写字母表示,常用的名称有:

  • E 表示 Element
  • K 表示 Key
  • V 表示 Value
  • N 表示 Number
  • T 表示 Type
  • S, U, V 等等 - 第二、第三、第四个类型

裸类型(了解)

裸类型就是不指定类型实参

Box box = new box();

注意:一般不用裸类型,裸类型是 Java 为了兼容老版本

原理

Java 泛型的原理是基于类型擦除(Type Erasure)的概念。在编译时,Java 编译器会擦除泛型类型的信息,将泛型代码转换为普通的非泛型代码。这样,泛型的类型信息只存在于编译期,而在运行时是不可见的。

  1. 类型参数擦除: 在编译时,泛型类型参数会被替换为它们的边界或者 Object 类型。例如,List<String> 在运行时会被擦除为 List<Object>
  2. 桥方法(Bridge Methods): 泛型类和泛型接口的类型擦除可能导致擦除后的类或接口缺少某些方法。为了解决这个问题,编译器会生成桥方法,以确保子类或实现类仍然具有正确的类型。
  3. 泛型数组的限制: 由于数组在运行时需要知道元素的确切类型,Java 不允许创建泛型数组。因此,使用泛型数组可能会导致编译器警告或错误。

泛型的上界

泛型的上界是指泛型类型参数的限制,用于指定该参数必须是某个特定类型或其子类型。在Java中,通过使用 extends 关键字来指定上界。上界限制了可以传递给泛型类型参数的类型范围。

public class Box<T extends Number> {
    private T value;

    public Box(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public static void main(String[] args) {
        // 使用泛型的上界,创建一个存储整数的盒子
        Box<Integer> intBox = new Box<>(42);

        // 使用泛型的上界,创建一个存储双精度浮点数的盒子
        Box<Double> doubleBox = new Box<>(3.14);

        // 下面的代码会导致编译错误,因为String不是Number的子类型
        // Box<String> stringBox = new Box<>("Hello");
    }
}

一个泛型上界的典型运用

// 求数组中的最大值
class Alg<T extends Comparable<T>> { // 设置上界 Comparable<T>
    public T findMax(T[] array) {
        T max = array[0];
        for (int i = 1; i < array.length; ++i) {
            if (max.compareTo(array[i]) < 0) { // 因为此处要对两个类型进行比较,所以必须是限定继承了Comparable接口
                max = array[i];
            }
        }
        return max;
    }
}

泛型方法

对上面的 Alg 类,我们发现 findMax() 方法的调用不需要依赖对象,所以可以设置为静态方法。

但是静态方法因为不直接访问类的实例,也就无法获取类中的泛型信息,无法使用泛型类型参数 T

要解决这个问题,可以把静态方法设置为泛型方法,将泛型参数放在方法的返回类型之前:

class Alg {
    public static <T extends Comparable<T>> T findMax(T[] array) {
        T max = array[0];
        for (int i = 1; i < array.length; ++i) {
            if (max.compareTo(array[i]) < 0) {
                max = array[i];
            }
        }
        return max;
    }
}

通配符

? 通配符: 表示未知类型,可以用在方法参数、方法返回类型、变量等地方。例如,在一个方法中接受一个未知类型的集合:

public static void printList(List<?> list) {
    for (Object element : list) {
        System.out.print(element + " ");
    }
    System.out.println();
}

通配符的上界

通配符是为了处理泛型协变问题而引入的,特别是 ? extends T 这种形式

比如我们有这样一个继承关系:

class Fruit {
    // ...
}

class Apple extends Fruit {
    // ...
}

class Box<T> {
    private T value;

    public Box(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

Apple 是 Fruit 的子类,那么我们可以认为 Box<Apple>Box<Fruit> 的子类,可以写出以下代码:

public class Test {
    private static void fun(Box<Fruit> box) {
        System.out.println(box.getValue());
    }
    public static void main(String[] args) {
        Box<Apple> box = new Box<>(new Apple());
        fun(box); // 将 Box<Apple> 传给 Box<Fruit>,报错
    }
}

解决方式:

public class Test {
    private static void fun(Box<? extends Fruit> box) { // 加入通配符 ? extends Fruit ,正确
        System.out.println(box.getValue());
    }
    public static void main(String[] args) {
        Box<Apple> box = new Box<>(new Apple());
        fun(box);
    }
}

但是 fun 方法里是不能对 box 里的元素进行修改,因为 box 里面放的是 Fruit 及其子类,没有一个类型可以让 Fruit 及其子类 都能接收。

通配符的下界

使用 ? super T 通配符时,表示可以接受类型为 TT 的超类型的对象。这主要用于对泛型集合进行写入操作,允许向集合中添加 T 类型及其子类型的元素。

<? super 下界>

<? super Integer> // 表示可以传入的实参的类型是 Integer 或者 Integer 的父类

例子:

class Food {

}

class Fruit extends Food {
    // ...
}

class Apple extends Fruit {
    // ...
}

class Box<T> {
    private T value;

    public Box(T value) {
        this.value = value;
    }

    public void setValue(T newValue) {
        this.value = newValue;
    }

    public T getValue() {
        return value;
    }
}

public class Test {
    static void fun(Box<? super Fruit> box) { 
        // box 内可以存放 Fruit 及其子类的对象
        box.setValue(new Fruit()); // 行
//        box.setValue(new Food()); // 不行
        box.setValue(new Apple()); // 行
    }
    public static void main(String[] args) {
        Box<Food> box = new Box<>(new Food());
        // 类型参数 Food 是 Fruit 的超类,可以传参
        fun(box);
    }
}

因为 Box 里的元素是 Fruit 类型及其父类类型,那么这些类型一定可以接收 Fruit 的子类对象。而如果你读数据,读出来的是 Fruit 类型或其父类类型,你没有一个很好的类型去接收,如果使用向下转型又不安全。所以通配符的下界适合写数据,不适合读数据。

包装类

装箱:基本数据类型->包装类类型

int a = 10;
Integer b = a; // 自动装箱
Integer c = Integer.valueOf(a); // 手动装箱

拆箱:包装类类型->基本数据类型

Integer a = 20; // 自动装箱
int b = a;  // 自动拆箱
double d1 = a; // 自动拆箱

double d2 = a.doubleValue(); // 手动拆箱

特殊案例:

public static void main(String[] args) {
    Integer a = 100;
    Integer b = 100;
    Integer c = 200;
    Integer d = 200;
    System.out.println(a == b);
    System.out.println(c == d);
}
/* 输出:
true
false
*/

为什么值都是 100 就相等,值都是 200 的却不相等了呢?

装箱底层其实就是调用的 valueOf(),查看 valueOf() 源码:

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

可以看到,当 i 在 -128 ~ 127 的时候就直接返回 cache 里面的对象了,如果超过了这个范围就会 new 一个新的对象。这就是导致上述问题的原因。

所以涉及引用类型比较大小,都应该使用对应的比较方法,而不是 == 号,包装类也不例外。

ArrayList

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  • ArrayList 实现了 RandomAccess, Cloneable, java.io.Serializable 接口,表明它是可随机访问的,可以 clone 的,支持序列化的。
  • ArrayList 不是线程安全的
  • ArrayList 底层是一段连续的空间,并且可以动态扩容, 是一个动态类型的顺序表

构造

方法解释
ArrayList()构造一个初始容量为10的空列表
ArrayList(int initialCapacity)构造一个初始容量为 initialCapacity 的空列表
ArrayList(Collection<? extends E> c)利用其他 Collection 构造 ArrayList

问题1:调用无参构造后,ArrayList 的容量是多少?

答案:0

img

elementDataArrayList 底层维护的用来存储元素的数组,在调用无参构造后,只是把 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 这个空数组的引用赋给了它,所以容量还是 0

在第一次调用 add 时:

img

结论:第一次调用 add 的时候,底层数组 elementData 的容量才变成了10,如果只是调用无参构造方法,容量是0.


问题2:扩容是几倍扩

img

查看扩容方法 grow ,其中 int newCapacity = oldCapacity + (oldCapacity >> 1); 可以得知的 1.5 倍扩容


使用指定容量构造

img

如果指定的容量 > 0,就直接给你开对应容量的数组;如果 = 0,就把空数组传过来;如果 < 0 ,则抛非法参数异常

常见操作

方法解释
boolean add(E e)尾插,返回true
void add(int index, E element)在指定位置插入,如果index越界会抛异常
boolean addAll(Collection<? extends E> c)尾插一个集合的元素,可能抛空指针异常
boolean addAll(int index, Collection<? extends E> c)指定位置的版本,可能抛越界或空指针异常
E remove(int index)删除指定位置的元素,返回被删除的元素
boolean remove(Object o)删除第一个出现的 o
E get(int index)获取 index 位置的元素
E set(int index, E element)设置 index 下标的元素为 element
void clear()清空
boolean contains(Object o)查看 o 是否在 ArrayList 中
int indexOf(Object o)返回第一个 o 所在位置下标
int lastIndexOf(Object o)返回最后一个 o 所在位置下标
List<E> subList(int fromIndex, int toIndex)截取部分,返回的引用仍指向原顺序表

遍历

for循环,println(arrayList),foreach 都和普通数组一样。

迭代器

Iterator<Integer> it = arrayList.iterator(); // 获取迭代器对象, 也可以用listIterator()
while (it.hasNext()) { // 判断是否有下一个
    System.out.println(it.next()); // 访问,并向后走一步
}
  • iterator()Iterable接口定义的方法,返回的迭代器只能向前遍历,并且不支持在遍历过程中修改集合。
  • listIterator()List接口定义的方法,返回的迭代器可以向前和向后遍历,同时支持在遍历过程中对集合进行修改。
  • 迭代器获取的时候也可以传入下标,来获取指定位置的迭代器

LinkedList

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

底层由链表实现

  • 支持无参构造和集合元素构造
  • 提供的常用操作方法和 ArrayList 的大同小异

Stack

方法解释
Stack()构造一个空的栈
E push(E item)入栈,并返回 item
E pop()出栈,并返回出栈的元素
E peek()获取栈顶元素
int size()获取栈中有效元素的个数
boolean empty()检测栈是否为空

Queue

Queue 是个接口,所以你并不能 new 它,而是 new 它的子类 LinkedList

Queue<Integer> queue = new LinkedList<>();
方法解释
boolean offer(E e)入队
E poll()出队
E peek()获取队头元素

PriorityQueue

默认创建小堆,容量为11

创建大堆,需要传比较器:

PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2 - o1;
    }
});

如果是自定义类,也可以通过实现 Comparable 接口来指定比较规则

也可以用 lambda 表达式:

PriorityQueue<Integer> priorityQueue = new PriorityQueue<>((o1, o2) -> o2 - o1);

优先级队列的扩容说明:

  • 容量小于 64 时,2倍扩容
  • 容量大于等于64,1.5倍扩容
  • 容量超过 MAX_ARRAY_SIZE,按照 MAX_ARRAY_SIZE 扩容

Map

Map 是接口,K-V模型,使用时需要实例化它的子类 TreeMap 或 HashMap

Map.Entry<K, V>

Map.Entry<K, V> 是 Map 内部实现的用来存放 <key, value> 键值对映射关系的内部类,该内部类中主要提供了 <key, value> 的获取,value 的设置以及 key 的比较方式

方法解释
K getKey()返回 entry 中的 key
V getValue()返回 entry 中的 value
V setValue(V value)将 value 替换为指定的 value

注意:Key 不能设置

Map 常用方法

方法解释
V get(Object key)返回 key 对应的 value
V getOrDefault(Object key, V defaultValue)返回 key 对应的 value,如 key 不存在,返回默认值
V put(K key, V value)设置 key 对应的 value
V remove(Object key)删除 key 对应的映射关系
Set<K> keySet()返回所有 key 的不重复集合
Collection<V> values()返回所有 value 的可重复集合
Set<Map.Entry<K, V>> entrySet()返回所有的 key-value 映射关系
boolean containsKey(Object key)判断是否包含 key
boolean containsValue(Object value)判断是否包含 value

Set

Set 是接口,K 模型,使用时需要实例化它的子类 TreeSet 或 HashSet

常用方法

方法解释
boolean add(E e)添加元素,重复元素不会被添加成功
void clear()清空
boolean contains(Object o)判断 o 是否在集合中
Iterator<E> iterator()返回迭代器
boolean remove(Object o)删除
int size()返回元素个数
boolean isEmpty()判断是否为空
Object[] toArray()将Set转换为数组
boolean containsAll(Collection<?> c)判断集合 c 中的元素是否在Set中全部存在
boolean addAll(Collection<? extends E> c)将集合 c 中的元素添加到 Set 中,可以达到去重的效果

实现 Set 接口的常用类还有 LinkedHashSet,是在 HashSet 的基础上维护了一个双向链表来记录元素的插入次序

TreeSet 底层是 TreeMap 实现,查看源码:

public TreeSet() {
    this(new TreeMap<E,Object>()); // 因为是 K 模型,第二个类型参数给的是Object,是用来占位的
}

下面简单了解一下源码:

默认负载因子是0.75:

img

默认桶的初始大小为16:

img

只是初始化,桶的容量是 0,在第一次 put 的时候才会给哈希桶分配 16 的容量

桶的最大容量:

img

树化的条件1:链表的长度>=8

img

树化的条件2:桶的容量>=64

img

树退化的条件

img

哈希桶

img

计算哈希值的方式:

img

h >>> 16 位将高位移到低位,然后与原来的 h 异或,这样得到的 h ,低 16 位既有高位的信息又有低位的信息。

因为在 Java 的哈希表实现中,会使用二的幂次作为哈希表的大小,并使用位掩码进行索引计算。由于使用了二的幂次,高位的哈希值可能会在索引计算中失去一些信息,导致一些哈希冲突。为了减少这种冲突,采用了一种位传播的策略。

在 Java 中,哈希表的数组长度为2的幂。这是因为在使用二进制表示时,取模运算(%)可以被优化为位运算,即使用掩码进行操作,而不是昂贵的除法运算。这种优化可以提高性能。

例如,如果哈希表的长度为2^n(其中n是非负整数),那么对于任意正整数k,k % (2^n) 可以等效为 k & (2^n - 1)。

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

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

相关文章

【EI会议征稿】第四届机械设计与仿真国际学术会议(MDS 2024)

【高录用快检索】第四届机械设计与仿真国际学术会议&#xff08;MDS 2024) 2024 4th International Conference on Mechanical Design and Simulation 2024年第四届机械设计与仿真国际学术会议&#xff08;MDS 2024) 将于2024年03月01-03日在中国西安召开。MDS 2024将围绕“…

C# NAudio 音频库

C# NAudio 音频库 NAudio安装NAudio简述简单示例1 NAudio安装 项目>NuGet包管理器 搜索NAudio点击安装&#xff0c;自动安装依赖库。 安装成功后工具箱会新增NAudio.WinForms控件 NAudio简述 NAudio为.NET平台下的开源库&#xff0c;采用ML-PL协议&#xff0c;开源地址…

雷达模糊函数及MATLAB仿真

文章目录 前言一、雷达模糊函数二、Matlab 仿真1、单脉冲模糊函数①、MATLAB 源码②、仿真结果1&#xff09;不确定函数三维图2&#xff09;不确定函数的等高图3&#xff09;模糊函数的三维图4&#xff09;模糊函数的等高图 2、单脉冲多普勒频率轴上的切面①、MATLAB 源码②、仿…

srs webrtc推拉流环境搭建(公网)

本地环境搭建 官方代码https://github.com/ossrs/srs 拉取代码&#xff1a; git clone https://github.com/ossrs/srs.gitcd ./configure make ./objs/srs -c conf/https.rtc.confsrs在公网上&#xff0c;由于srs是lite-ice端&#xff0c;导致他不会主动到srs获取自己的公网i…

【python】OpenCV—Rectangle, Circle, Selective Search(1.2)

文章目录 1 画框画圈1.1 画矩形框1.2 画圆 / 点1.3 椭圆 2 Selective Search3 Resize 1 画框画圈 1.1 画矩形框 # Copy the image img_rgb_copy img_rgb.copy()# Draw a rectangle cv2.rectangle(img_rgb_copy, pt1 (405, 90), pt2 (740, 510),color (255, 0, 0), thickne…

德迅云安全和您聊聊关于DDOS高防ip的一些方面

德迅DDoS防护服务是以省骨干网的DDoS防护网络为基础&#xff0c;结合德迅自研的DDoS攻击检测和智能防护体系&#xff0c;向您提供可管理的DDoS防护服务&#xff0c;自动快速的缓解网络攻击对业务造成的延迟增加&#xff0c;访问受限&#xff0c;业务中断等影响&#xff0c;从而…

【Android 标题文字居中 快速实现】

背景&#xff1a; Android App系统默认setTitle左起展示(图左)&#xff0c;IOS App默认居中展示(图右)。现在美工设计 在Android中标题同样居中显示。 解决&#xff1a; 方案一&#xff1a;(传统方式,比较繁琐) 设置ToolBar样式&#xff0c;内嵌TextView来展示&#xff0c;具…

reids面试题

1 redis是单线程吗&#xff1f; Redis是单线程 主要是指Redis的网络10和键值对读写是由一个线程来完成的&#xff0c;Redis在处理客户端的请求时包括获取(socket 读)、解析、执行、内容返回(socket 写) 等都由一个顺序串行的主线程处理&#xff0c; 但Redis的其他功能&#xff…

2023.11.16 hivesql高阶函数之json

目录 1.数据准备 2.操作 -- 方式1: 逐个(字段)处理, get_json_object UDF函数 最大弊端是一次只能解析提取一个字段 -- 方式2: 逐条处理. json_tuple 这是一个UDTF函数 可以一次解析提取多个字段 -- 方式3: 在建表时候, 直接处理json, row format SerDe 能处理Json的SerDe类…

React函数组件状态Hook—useState《进阶-对象数组》

React函数组件状态-state 对象 state state 中可以保存任意类型的 JavaScript 值&#xff0c;包括对象。但是&#xff0c;你不应该直接修改存放在 React state 中的对象。相反&#xff0c;当你想要更新⼀个对象时&#xff0c;你需要创建⼀个新的对象&#xff08;或者将其拷⻉⼀…

Ubuntu18.04安装Loam保姆级教程

系统环境&#xff1a;Ubuntu18.04.6 LTS 1.Loam的安装前要求&#xff1a; 1.1 ROS安装&#xff1a;参考我的另一篇博客 Ubuntu18.04安装ROS-melodic保姆级教程_灬杨三岁灬的博客-CSDN博客还是那句话&#xff0c;有时候加了这行也不好使&#xff0c;我是疯狂试了20次&#xf…

基于51单片机步进电机加减速正反转数码管显示( proteus仿真+程序+原理图+设计报告+讲解视频)

基于51单片机步进电机加减速正反转数码管显示( proteus仿真程序原理图设计报告讲解视频&#xff09; &#x1f4d1;1.主要功能&#xff1a;&#x1f4d1;讲解视频&#xff1a;&#x1f4d1;2.仿真&#x1f4d1;3. 程序代码&#x1f4d1;4. 设计报告&#x1f4d1;5. 设计资料内容…

如何快速本地搭建悟空CRM结合内网穿透工具高效远程办公

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;数据结构、Cpolar杂谈 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. 无需公网IP&#xff0c;使用cpolar实现悟空CRM远程访问二. 通过公网来访问公司…

移动机器人路径规划(三)--- 基于采样的路径规划Sample-basedpath finding

目录 1 基于采样的路径规划的优点和一些重要概念 2 概率路图 Probabilistic Road Map 3 快速搜索随机树Rapidly-exploring Random Tree 3.1 RRT 3.2 RRT Connect 4 RRT算法的优化 4.1 RRT* 4.2 Kinodynamic-RRT* 4.3 Anytime-RRT* 5 Advanced Sampling-based Methods…

在混料配料输送系统中使用485modbus转profinet网关案例

485Modbus转Profinet网关是一种在工业自动化控制系统中常用的设备&#xff0c;能够实现不同通信协议之间的转换&#xff0c;对于混料配料输送系统的优化和控制具有重要作用。 通过使用485Modbus转Profinet网关&#xff0c;混料配料输送系统能够实现与不同设备之间的通信和数据交…

linux下安装python3.8(有坑)

1安装包下载 ###直接官网下载linux版本&#xff0c;找到对应的包 https://www.python.org/downloads/source/2安装包解压 tar -zxvf Python-3.8.0.tgz 3编译安装 1&#xff09;设置安装目录&#xff0c;比如在此创建在 /usr/local/python3 &#xff1a; mkdir -p /usr/loca…

clip4clip:an empirical study of clip for end to end video clip retrieval

广告深度学习计算&#xff1a;阿里妈妈智能创意服务优化使用CPU/GPU分离的多进程架构&#xff0c;加速阿里妈妈智能创意服务。https://mp.weixin.qq.com/s/_pjhXrUZVzFRtiwG2LhnkwCLIP4Clip: CLIP 再下一城&#xff0c;利用CLIP实现视频检索 - 知乎前言&#xff1a; OpenAI 的论…

networkx使用draw画图报错:TypeError: ‘_AxesStack‘ object is not callable

一、问题描述 在使用networkx的draw绘图时nx.draw(g,posnx.spring_layout(g))&#xff0c;报错&#xff1a;TypeError: _AxesStack object is not callable 二、原因 可能是当前python环境下的networkx和matplotlib的版本不匹配。我报错时的networkx2.8&#xff0c;matplotl…

DBeaver还原mysql数据库

DBeaver还原mysql数据库 DBEaver还原mysql数据库新建一个要还原的数据库选择工具》恢复数据库 DBEaver还原mysql数据库 新建一个要还原的数据库 选中数据库,右键新建一个数据库&#xff0c;字符集和排序规则默认的即可 选择工具》恢复数据库 选中刚刚创建好的数据库&#x…

2—10岁女童羽绒服,黑色长款也太好看了吧

冬天怎么能没有一件暖呼呼的羽绒服呢&#xff1f; 黑色长款羽绒服也赞了吧 大长款连帽&#xff0c;防风保暖设计 时尚与美观度都兼具呢&#xff01;好穿又耐穿&#xff01;