Java_比较器枚举类和注解集合泛型
文章目录
- Java_比较器
- Java_自然排序:java.lang.Comparable
- Java_Comparable的实现
- Java_定制排序java.util.Comparator
- Java_System类
- Java_Math类
- Java_BigInteger类
- Java_BigDecimal类
- Java_枚举类
- Java_自定义枚举类
- Java_使用enum定义枚举类
- Java_Enum类的概念和方法
- Java_实现接口的枚举类
- Java_注解(Annotation)
- Java_注解的格式
- Java_常见的Annotation示例
- Java_JDK内置的三个基本注解
- Java_自定义注解 Annotation
- Java_JDK中的元注解
- Java_JDK中的元数据注解Retention类型
- Java_JDK中的元数据注解Target类型
- Java_JDK中的元数据注解@Documented和@Inherited
- Java_可重复的注解及可用于类型的注解
- Java 集合概述
- Java_集合框架的特点
- Java_集合框架的类型
- Java_集合框架的内容
- Java_集合的使用场景
- Java_Collection 集合
- Java_Collection 接口方法
- Java_Iterator迭代器
- Java_Iterator迭代器方法
- Java_List集合
- Java_Lsit常用的方法
- Java_List 实现类之一: ArrayList类
- Java_List 实现类之二: LinkedList类
- Java_List 实现类之三:Vector
- ArrayList和LinkedList的异同
- ArrayList和Vector的区别
- Java_Set集合
- Java_Set实现类之一:HashSet
- Java_元素放入HashSet集合的原理
- Java_Set为什么要求重写equals方法后要重写hashCode方法呢?
- Java_Set实现类之二:LinkedHashSet
- Java_Set实现类之三:TreeSet
- Java_Map集合
- Java_Map常用方法
- Java_ Map实现类之一:HashMap
- Java_HashMap的存储结构
- Java_元素放入HashMap集合的原理
- Java_HashMap负载因子
- Java_Map实现类之二:LinkedHashMap
- Java_Map实现类之三:TreeMap
- Java_Map实现类之四:Hashtable
- Java_Map实现类之五:Properties
- Java_Collections 工具类
- Java_CopyOnWriteArrayList
- Java_使用CopyOnWrite容器的注意事项
- Java_CopyOnWrite的缺点
- Java_ConcurrentHashMap
- Java_Queue(队列)
- Queue(队列)的实现 _BlockingQueue
- Java_Queue的ArrayBlockingQueue实现类
- Java_Queue的LinkedBlockingQueue实现类
- Java_Queue的ConcurrentLinkedQueue实现类
- Java_泛型机制
- Java_为什么存在泛型
- Java_自定义泛型接口
- Java_泛型的声明
- Java_泛型的实例化
- Java_泛型类
- Java_泛型方法
- Java_泛型在继承上的体现
- Java_通配符的使用
Java_比较器
- Java实现对象排序的方式有两种:自然排序和定制排序
Java_自然排序:java.lang.Comparable
Comparable
接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序。- 实现
Comparable
的类必须实现compareTo(Object obj)
方法,两个对象即 通过compareTo(Object obj)
方法的返回值来比较大小。 - 如果当前对象
this
大于形参对象obj
,则返回正整数,如果当前对象this
小于形参对象obj
,则返回负整数,如果当前对象this
等于形参对象obj
,则返回零。 - 实现
Comparable
接口的对象列表(和数组)可以通过Collections.sort
或Arrays.sort
进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。 - 对于类 C 的每一个
e1 和 e2
实例来说,当且仅当e1.compareTo(e2) == 0 与 e1.equals(e2)
具有相同的boolean
值时,类 C 的自然排序才叫做与equals
一致。建议(虽然不是必需的)最好使自然排序与equals
一致。
Java_Comparable的实现
Comparable
的典型实现:(默认都是从小到大排列的)- String:按照字符串中字符的Unicode值进行比较
Character
:按照字符的Unicode值来进行比较- 数值类型对应的包装类以及
BigInteger、BigDecimal
:按照它们对应的数值大小进行比较 Boolean:true
对应的包装类实例大于false
对应的包装类实例Date、Time
等:后面的日期时间比前面的日期时间大
package Java_9;import java.util.Arrays;
public class Comparable {public static void main(String[] args) {String [] all = new String[4];all[0] = "《红楼梦》";all[1] = "《西游记》";all[2] = "《三国演义》";all[3] = "《水浒传》";Arrays.sort(all);System.out.println(Arrays.toString(all));}
Java_定制排序java.util.Comparator
- 当元素的类型没有实现
java.lang.Comparable
接口而又不方便修改代码,
或者实现了java.lang.Comparable
接口的排序规则不适合当前的操作,那
么可以考虑使用Comparator
的对象来排序, java.util.Comparator
强行对多个对象进行整体排序的比较。- 重写
compare(Object o1,Object o2)
方法,比较o1和o2的大小
:如果方法返 回正整数,则表示o1大于o2
;如果返回0,表示相等
;返回负整数,表示o1小于o2。
- 可以将
Comparator
传递给sort
方法(如 Collections.sort 或 Arrays.sort),
从而允许在排序顺序上实现精确控制。 - 还可以使用
Comparator
来控制某些数据结构(如有序 set或有序映射)的
顺序,或者为那些没有自然顺序的对象collection
提供排序。
Java_System类
System类
代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang
包。System类
的构造器是private
的,所以无法创建该类的对象,也就是无法实
例化该类。System类
内部的成员变量和成员方法都是static
的,所以也可以很方便的进行调用。System类
成员变量:System类
内部包含in、out和err
三个成员变量,分别代表标准输入流(键盘输入),标准输出流(显示器)和标准错误输出流(显示器)
。System类
成员方法native long currentTimeMillis()
:该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。System类
的void exit(int status)
:该方法的作用是退出程序。其中status
的值为0代表正常退出,非零代表
异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。System类
的void gc():
该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。String getProperty(String key)
:该方法的作用是获得系统中属性名为key的属性对应的值。系统中常见的属性名以及属性的作用如下表所示:
Java_Math类
- java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回
值类型一般为double型。 abs 绝对值
sqrt 平方根
pow(double a,doble b) a的b次幂
log 自然对数
max(double a,double b)
min(double a,double b)
random() 返回0.0到1.0的随机数
Java_BigInteger类
- java.math包的BigInteger可以表示不可变的任意精度的整数。BigInteger 提供所有 Java 的基本整数操作符的对应物,并提供 java.lang.Math 的所有相关方法
构造器
BigInteger(String val):根据字符串构建BigInteger对象
常用方法
public BigInteger abs()
:返回此 BigInteger 的绝对值的 BigInteger。`BigInteger add(BigInteger val) :返回其值为 (this + val) 的 BigInteger
BigInteger subtract(BigInteger val) :返回其值为 (this - val) 的 BigInteger
BigInteger multiply(BigInteger val) :返回其值为 (this * val) 的 BigInteger
BigInteger divide(BigInteger val) :返回其值为 (this / val) 的 BigInteger。
整数相除只保留整数部分。BigInteger remainder(BigInteger val) :返回其值为 (this % val) 的 BigInteger。
BigInteger[] divideAndRemainder(BigInteger val):返回包含 (this / val) 后跟(this % val) 的两个 BigInteger 的数组。
BigInteger pow(int exponent) :返回其值为 (thisexponent) 的 BigInteger。
Java_BigDecimal类
- 一般的Float类和Double类可以用来做科学计算或工程计算,但在商业计算中,要求数字精度比较高,故用到java.math.BigDecimal类。
BigDecimal
类支持不可变的、任意精度的有符号十进制定点数。
构造器
public BigDecimal(double val)
public BigDecimal(String val)
常用方法
public BigDecimal add(BigDecimal augend)
- `public BigDecimal subtract(BigDecimal subtrahend)``
public BigDecimal multiply(BigDecimal multiplicand)
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
BigInteger bi = new BigInteger("12433241123");BigDecimal bd = new BigDecimal("12435.351");BigDecimal bd2 = new BigDecimal("11");System.out.println(bi);
// System.out.println(bd.divide(bd2));System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP));System.out.println(bd.divide(bd2, 15, BigDecimal.ROUND_HALF_UP));
Java_枚举类
- 枚举类就是对象只有有限的,确定的类,例如:星期,性别,季节等
- 在日常生活中这些事物的取值只有明确的几个固定值,此时描述这些事物的所有值都可以一一列举出来,而这个列举出来的类型就叫做枚举类型
枚举类的实现
- 使用public static final表示的常量描述较为繁琐,使用enum关键字来定义枚举类型取代常量,枚举类型是从Java5开始增加的一种引用数据类型
- 枚举值就是当前类的类型,也就是指向本类的对象,默认使用
public static final
关键字共同修饰,因此采用枚举类型.
的方式调用。 - 枚举类可以自定义构造方法,但是构造方法的修饰符必须是private,默认也是私有的
- 若枚举只有一个对象, 则可以作为一种单例模式的实现方式。
Java_自定义枚举类
- 私有化类的构造器,保证不能在类的外部创建其对象
- 在类的内部创建枚举类的实例。声明为:public static final
- 对象如果有实例变量,应该声明为private final,并在构造器中初始化
class Season{private final String SEASONNAME;//季节的名称
private final String SEASONDESC;//季节的描述
private Season(String seasonName,String seasonDesc){this.SEASONNAME = seasonName;this.SEASONDESC = seasonDesc;
}
public static final Season SPRING = new Season("春天", "春暖花开");
public static final Season SUMMER = new Season("夏天", "夏日炎炎");
public static final Season AUTUMN = new Season("秋天", "秋高气爽");
public static final Season WINTER = new Season("冬天", "白雪皑皑");
}
Java_使用enum定义枚举类
- 使用 enum 定义的枚举类默认继承了
java.lang.Enum
类,因此不能再继承其他类 - 枚举类的构造器只能使用
private
权限修饰符 - 枚举类的所有实例必须在枚举类中显式列出(, 分隔 ; 结尾)。列出的实例,系统会自动添加
public static final
修饰 - 必须在枚举类的第一行声明枚举类对象
注意:可以在 switch 表达式中使用Enum定义的枚举类的对象作为表达式, case 子句可以直接使用枚举值的名字, 无需添加枚举类作为限定。
public enum SeasonEnum {SPRING("春天","春风又绿江南岸"),SUMMER("夏天","映日荷花别样红"),AUTUMN("秋天","秋水共长天一色"),WINTER("冬天","窗含西岭千秋雪");private final String seasonName;private final String seasonDesc;private SeasonEnum(String seasonName, String seasonDesc) {this.seasonName = seasonName;this.seasonDesc = seasonDesc;}public String getSeasonName() {return seasonName;}public String getSeasonDesc() {return seasonDesc;}
}
Java_Enum类的概念和方法
- 所有的枚举类都继承自java.lang.Enum类,常用方法如下:
valueOf
:传递枚举类型的Class对象和枚举常量给静态方法valueOf,会得到与参数匹配的枚举常量,toString
:得到当前枚举常量的名称,equals
:在枚举类型中,可以直接使用==
来比较两个枚举常量是否相等,Enum
提供的equals()
方法也是直接使用==
实现的,是为了在Set,List和Map
中使用,getDeclaringClass
:得到的枚举常量所属枚举类型的Class
对象,可以用它来判断两个枚举常量是否属于同一个枚举类型ordinal
得到当前枚举常量的次序compareTo
:枚举类型实现了Comparable
接口,用来比较两个枚举常量的大小(按照声明的顺序排列)clone
:枚举类型不能配Clone,为了防止子类实现克隆方法,Enum
实现了一个仅抛出CloneNotSupportedException
异常的不变Clone()
三个特别重要的方法
values()
方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。valueOf(String str)
:可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。
toString()
:返回当前枚举类对象常量的名程
Java_实现接口的枚举类
- 枚举类和普通 Java 类一样,枚举类可以实现一个或多个接口
- 若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只要统一实现该方法即可。
- 若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式, 则可以让每个枚举值分别来实现该方法
Java_注解(Annotation)
- Java 增加了对元数据(MetaData) 的支持,注解(Annotation)又叫标注,是从Java5开始增加的一种引用数据类型。
- Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。
Java_注解的格式
访问修饰符 @interface 注解名称 {注解成员;
}
自定义注解自动继承java.lang.annotation.Annotation接口。
通过@注解名称的方式可以修饰包、类、 成员方法、成员变量、构造方法、参数、局部变量的声明等。
通过使用 Annotation, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
Annotation 可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在
Annotation
的“name=value”
对中
Java_常见的Annotation示例
- 使用 Annotation 时要在其前面增加
@
符号, 并把该Annotation
当成一个修饰符使用。用于修饰它支持的程序元素
生成文档相关的注解:
- @author 标明开发该类模块的作者,多个作者之间使用,分割
- @version 标明该类模块的版本
- @see 参考转向,也就是相关主题
- @since 从哪个版本开始增加的
- @param 对方法中某参数的说明,如果没有参数就不能写
- @return 对方法返回值的说明,如果方法的返回值类型是void就不能写
- @exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写
注意:
@param @return 和 @exception 这三个标记都是只用于方法的。
@param的格式要求:@param 形参名 形参类型 形参说明
@return 的格式要求:@return 返回值类型 返回值说明
@exception的格式要求:@exception 异常类型 异常说明
@param和@exception可以并列多个
package com.commany;
/**
* @author ryx
* @version 1.0
* @see Math.java
*/
public class JavadocTest {/*** 程序的主方法,程序的入口* @param args String[] 命令行参数*/public static void main(String[] args) {}/*** 求圆面积的方法* @param radius double 半径值* @return double 圆的面积*/public static double getArea(double radius){return Math.PI * radius * radius;}
}
Java_JDK内置的三个基本注解
@Override
: 限定重写父类方法, 该注解只能用于方法@Deprecated
: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择@SuppressWarnings
: 抑制编译器警告
package com.commany;
public class AnnotationTest{public static void main(String[] args) {@SuppressWarnings("unused")int a = 10;}@Deprecatedpublic void print(){System.out.println("过时的方法");}@Overridepublic String toString() {return "重写的toString方法()";}
}
Java_自定义注解 Annotation
- 定义新的
Annotation
类型使用@interface
关键字 - 自定义注解自动继承了
java.lang.annotation.Annotation
接口 Annotation
的成员变量在Annotation
定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组
。- 可以在定义
Annotation
的成员变量时为其指定初始值, 指定成员变量的初始值可使用default
关键字 - 如果只有一个参数成员,建议使用参数名为
value
- 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。
格式是“参数名 = 参数值”,如果只有一个参数成员,且名称为value,可以省略“value=”
- 没有成员定义的
Annotation
称为标记; 包含成员变量的Annotation
称为元数据 Annotation
注意:自定义注解必须配上注解的信息处理流程才有意义
@MyAnnotation(value="安岩师范")
public class MyAnnotationTest {public static void main(String[] args) {Class clazz = MyAnnotationTest.class;Annotation a = clazz.getAnnotation(MyAnnotation.class);MyAnnotation m = (MyAnnotation) a;String info = m.value();System.out.println(info);}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyAnnotation{String value() default "anyangshifan";
}
Java_JDK中的元注解
- JDK 的元 Annotation 用于修饰其他 Annotation 定义
- JDK5.0提供了4个标准的
meta-annotation
类型,分别是:Retention,Target,Documented,Inherited
- 包含成员变量的
Annotation
称为元数据 Annotation
- 元数据的理解:
String name = "安阳师范"
Java_JDK中的元数据注解Retention类型
@Retention
: 只能用于修饰一个Annotation
定义, 用于指定该Annotation
的生命周期,@Rentention
包含一个RetentionPolicy
类型的成员变量, 使用@Rentention
时必须为该value
成员变量指定值:RetentionPolicy.SOURCE
:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释RetentionPolicy.CLASS
:在class文件中有效(即class保留) ,当运行 Java 程序时,JVM 不会保留注解。 这是默认值
RetentionPolicy.CLASS
:在class文件中有效(即class保留) , 当运行 Java 程序时,JVM 不会保留注解。 这是默认值
public enum RetentionPolicy{SOURCE,CLASS,RUNTIME
}
@Retention(RetentionPolicy.SOURCE)
@interface MyAnnotation1{ }
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{ }
Java_JDK中的元数据注解Target类型
@Target
: 用于修饰 Annotation 定义, 用于指定被修饰的 Annotation 能用于修饰哪些程序元素。- @Target 也包含一个名为 value 的成员变量。
Java_JDK中的元数据注解@Documented和@Inherited
@Documented
: 用于指定被该元 Annotation 修饰的 Annotation 类将被javadoc 工具提取成文档。默认情况下,javadoc是不包括注解的。- 定义为
Documented
的注解必须设置Retention
值为RUNTIME
。 @Inherited
: 被它修饰的 Annotation 将具有继承性。如果某个类使用了被@Inherited
修饰的 Annotation, 则其子类将自动具有该注解。- 比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以继承父类类级别的注解,实际应用中,使用较少
Java_可重复的注解及可用于类型的注解
类型注解:
- 关于元注解@Target的参数类型ElementType枚举值多了两个:
TYPE_PARAMETER,TYPE_USE。
- 在Java 8之前,注解只能是在声明的地方所使用,Java8开始,注解可以应用在任何地方。
ElementType.TYPE_PARAMETER
表示该注解能写在类型变量的声明语句中(如:泛型声明)。ElementType.TYPE_USE
表示该注解能写在使用类型的任何语句中。
public class TestTypeDefine<@TypeDefine() U> {private U u;public <@TypeDefine() T> void test(T t){}
}
@Target({ElementType.TYPE_PARAMETER})
@interface TypeDefine{}
Java 集合概述
- 面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储,使用Array存储对象方面具有一些弊端,而Java 集合就像一种容器,可以动态地把多个对象的引用放入容器中
- Java 集合可以记住容器中对象的数据类型。
- 当需要在Java程序中记录单个数据内容时,则声明一个变量。
- 当需要在Java程序中记录多个类型相同的数据内容时,声明一个一维数组。
- 当需要在Java程序中记录多个类型不同的数据内容时,则创建一个对象。
- 当需要在Java程序中记录多个类型相同的对象数据时,创建一个对象数组。
- 当需要在Java程序中记录多个类型不同的对象数据时,则准备一个集合。
Java_集合框架的特点
- 集合框架必须是高性能的。基本集合(动态数组,链表,树,哈希表)的实现也必须是高效的。
- 该框架允许不同类型的集合,以类似的方式工作,具有高度的互操作性,并且易扩展
Java_集合框架的类型
- Java 集合类的特点:可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组
- Java中集合框架主要包括两种类型的容器框架是:java.util.Collection集合 和 java.util.Map集合。其中Collection集合中存取元素的基本单位是:单个元素,Map集合中存取元素的基本单位是:单对元素。
- 所有集合类都位于
java.util
包下。Java的集合类主要由两个接口派生而出:Collection 和 Map
Collection 和 Map 是 Java
集合框架的根接口,这两个接口又包含了一些子接口或实现类。
Java_集合框架的内容
- 集合框架是一个用来代表和操纵集合的统一架构。Java集合框架都包含如下内容:
- 接口:是代表集合的抽象数据类型。例如
Collection、List、Set、Map
等。之所以定义多个接口,是为了以不同的方式操作集合对象 - 实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构,例如:
ArrayList、LinkedList、HashSet、HashMap。
- 算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序。这些算法被称为多态,那是因为相同的方法可以在相似的接口上有着不同的实现。
- 除了集合,该框架也定义了几个
Map
接口和类。Map
里存储的是键/值对。尽管Map
不是集合,但是它们完全整合在集合中。
Java_集合的使用场景
- 在客户端:
将JSON对象或JSON数组转换为Java对象或Java对象构成的List
- 在服务端:
将Java对象或Java对象构成的List转换为JSON对象或JSON数组
Java_Collection 集合
java.util.Collection接口是List接口、Queue 接口以及Set接口的父接口,因此该接口里定义的方法既可用于操作List集合,也可用于操作Queue集合和Set集合。
Collection
接口有三种子类型,List
,Set
,Queue
,再下面是一些抽象类,最后是一些具体实现类,常用的实现类有ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap
等等。Set 接口继承 Collection
,集合元素不重复。List 接口继承 Collection
,允许重复,维护元素插入顺序。List
的具体类为ArrayList和LinkedLisst
Collection
接口是List、Set
接口的父接口,Collection
接口里定义的方法既可用于操作Set
集合,也可用于操作List
集合Collection 接口
是最基本的集合接口,一个Collection
代表一组Object
,即Collection
的元素,Java不提供直接继承自
Collection
的类,只提供继承于的子接口(如List和set
)。Collection
接口存储一组不唯一,无序的对象。
Java_Collection 接口方法
Java_Collection常用方法
方法 | 介绍 |
---|---|
boolean add(E e); | 向集合中添加对象 |
boolean addAll(Collection<? extends E> c | 用于将参数指定集合c中的所有元素添加到当前集合中 |
boolean contains(Object o); | 判断是否包含指定对象 |
boolean containsAll(Collection<?> c) | 判断是否包含参数指定的所有对象 |
boolean retainAll(Collection<?> c) | 保留当前集合中存在且参数集合中存在的所有对象 |
boolean remove(Object o); | 从集合中删除对象 |
boolean removeAll(Collection<?> c) | 从集合中删除参数指定的所有对象 |
void clear(); | 清空集合 |
int size(); | 返回包含对象的个数 |
boolean isEmpty(); | 判断是否为空 |
boolean equals(Object o) | 判断是否相等 |
int hashCode() | 获取当前集合的哈希码值 |
Object[] toArray() | 将集合转换为数组 |
Iterator iterator() | 获取当前集合的迭代器 |
package com.company;import java.util.ArrayList;
import java.util.Iterator;public class Collection {// 接口方法的测试public static void main(String[] args) {ArrayList al = new ArrayList(10);//初始容量为10al.add("1"); //添加al.add("1"); //添加al.add(2); //添加al.add(3); //添加al.add(4); //添加al.add(5); //添加ArrayList al1 = al;al1.add(4); //添加al1.add(5); //添加int le = al.size(); // 获取有效元素的个数System.out.println(le);//是否是空集合boolean b = al.isEmpty();System.out.println(b);// 是通过元素的equals方法来判断是否是同一个对象boolean b1 = al.contains(al1);System.out.println(b1);// 也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。boolean b2 = al.containsAll(al1);System.out.println(b2);// 通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素boolean b3 = al.remove("1");System.out.println(b2);// 取当前合集的差集boolean b4 = al.removeAll(al1);System.out.println(b4);// 集合是否相等boolean b5= al.equals(al1);System.out.println(b5);// 将集合转换为对象数组Object ay = al.toArray();System.out.println(ay);// 获取集合对象的哈希值System.out.println(al.hashCode());// 遍历返回迭代器对象,用于集合遍历//iterator.hasNext()如果存在元素的话返回trueIterator<String> iterator = al.iterator();while(iterator.hasNext()) {//iterator.next()返回迭代的下一个元素System.out.println(iterator.next());}}
}
Java_Iterator迭代器
- Iterator对象称为迭代器(设计模式的一种),主java.util.Iterator接口主要用于描述迭代器对象,可以遍历Collection集合中的所有元素
- 迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。
- Collection接口继承了Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
- Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建Iterator 对象,则必须有一个被迭代的集合。
- 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
Java_Iterator迭代器方法
Java_Iterator常用方法
方法 | 介绍 |
---|---|
boolean hasNext() | 判断集合中是否有可以迭代/访问的元素 |
E next() | 用于取出一个元素并指向下一个元素 |
void remove() | 用于删除访问到的最后一个元素 |
package com.company;import java.util.ArrayList;
import java.util.Iterator;public class Interator {// 迭代器测试public static void main(String[] args) {ArrayList al = new ArrayList();for (int i=0 ;i <10;i++){al.add(i);}Iterator iter = al.iterator();//回到起点while(iter.hasNext()){Object obj = iter.next();if(obj.equals(1)) {iter.remove();}}System.out.println(al);}
}
- Iterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的
remove方法
,不是集合对象的remove方法。
使用foreach循环遍历集合
- 遍历操作不需获取Collection或数组的长度,无需使用索引访问元素。
- 不断地从数组/集合中取出一个元素赋值给变量名并执行循环体,直到取完所有元素为止。
for (Object alf:al) {System.out.println(alf);}
Java_List集合
- java.util.List集合是Collection集合的子集合,该集合中允许有重复的元素并且有先后放入次序
- List集合类中
元素有序、且可重复
,集合中的每个元素都有其对应的顺序索引。List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。 - java.util.List集合的主要实现类有:ArrayList类、LinkedList类、Stack类、Vector类。
- ArrayList类的底层是采用动态数组进行数据管理的,支持下标访问,增删元素不方便。
- LinkedList类的底层是采用双向链表进行数据管理的,访问不方便,增删元素方便。
- 可以认为ArrayList和LinkedList的方法在逻辑上完全一样,只是在性能上有一定的差别,ArrayList更适合于随机访问而LinkedList更适合于插入和删除;
- Stack类的底层是采用动态数组进行数据管理的,该类主要用于描述一种具有后进先出特征的数据结构,叫做栈(last in first out LIFO)。
- Vector类的底层是采用动态数组进行数据管理的,该类与ArrayList类相比属于线程安全的类,效率比较低,基本不用。
Java_Lsit常用的方法
List除了从Collection
集合继承的方法外,List
集合里添加了一些根据索引来操作集合元素的方法
方法 | 介绍 |
---|---|
void add(int index, E element) | 向集合中指定位置添加元素 |
boolean addAll(int index, Collection<? extends E> c) | 向集合中添加所有元素 |
E get(int index) | 从集合中获取指定位置元素 |
int indexOf(Object o) | 查找参数指定的对象 |
int lastIndexOf(Object o) | 反向查找参数指定的对象 |
E set(int index, E element) | 修改指定位置的元素 |
E remove(int index) | 删除指定位置的元素 |
List subList(int fromIndex, int toIndex) | 返回从fromIndex到toIndex位置的子集合,用于获取子List |
Java_List 实现类之一: ArrayList类
ArrayList
是 List 接口的典型实现类、主要实现类ArrayList
本质上,ArrayList是对象引用的一个”变长”数组Arrays.asList(…)
方法返回的 List 集合,Arrays.asList(…)
返回值是一个固定长度的 List 集合
Java_List 实现类之二: LinkedList类
- 对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高
LinkedList
:双向链表,内部没有声明数组,而是定义了Node类型的first和last
,用于记录首末元素。同时,定义内部类Node
,作为LinkedList
中保存数据的基本结构。- Node除了保存数据,还定义了两个变量:
prev
变量记录前一个元素的位置
next
变量记录下一个元素的位置
新增方法:
void addFirst(Object obj)
void addLast(Object obj)
Object getFirst()
Object getLast()
Object removeFirst()
Object removeLast()
Java_List 实现类之三:Vector
- Vector 是一个古老的集合,大多数操作与ArrayList相同,区别之处在于Vector是线程安全的。
- 在各种list中,最好把ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList;Vector总是比ArrayList慢,所以尽量避免使用。
新增方法:
void addElement(Object obj)
void insertElementAt(Object obj,int index)
void setElementAt(Object obj,int index)
void removeElement(Object obj)
void removeAllElements()
ArrayList和LinkedList的异同
ArrayList和LinkedList
都线程不安全,相对线程安全的Vector
,执行效率高。ArrayList
是实现了基于动态数组的数据结构,LinkedList
基于链表的数据结构。对于随机访问get和set,
ArrayList觉得优于LinkedList
,因为LinkedList
要移动指针。对于新增和删除操作add
(特指插入)和remove,LinkedList
比较占优势,因为ArrayList
要移动数据。
ArrayList和Vector的区别
Vector和ArrayList
几乎是完全相同的,唯一的区别在于Vector是同步(synchronized)
,属于强同步类。因此开销就比ArrayList要大,访问要慢。- 正常情况下,使用
ArrayList而不是Vector
,因为同步完全可以由程序员自己来控制。 Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack。
Java_Set集合
- java.util.Set集合是Collection集合的子集合,与List集合平级,并且集合中元素没有先后放入次序,且不允许重复
- set集合没有提供额外的方法,参考Collection集合中的方法
- java.util.Set集合的主要实现类是:HashSet类 和 TreeSet类以及LinkedHashSet类。
- HashSet类的底层是采用哈希表进行数据管理的。
- TreeSet类的底层是采用红黑树进行数据管理的。
- LinkedHashSet类与HashSet类的不同之处在于内部维护了一个双向链表,链表中记录了元素的迭代顺序,也就是元素插入集合中的先后顺序,因此便于迭代。
Set
判断两个对象是否相同不是使用==
运算符,而是根据equals() 方法
Java_Set实现类之一:HashSet
HashSet
是Set
接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。HashSet
按Hash
算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
HashSet 特点:
- 不能保证元素的排列顺序
- HashSet 不是线程安全的
- 集合元素可以是 null
HashSet 集合判断两个元素相等的标准:
- 两个对象通过
hashCode()
方法比较相等,并且两个对象的equals()
方法返回值也相等。
对于存放在Set容器中的对象,对应的类一定要重写equals()hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”
Java_元素放入HashSet集合的原理
- 当向
HashSet
集合中存入一个元素时,HashSet
会调用该对象的hashCode()
方法来得到该对象的hashCode
值,然后根据hashCode
值,再由某种哈希算法计算出该元素在数组中的索引位置。 - 若该位置没有元素,则将该元素直接放入即可。
- 若该位置有元素,则使用新元素与已有元素依次比较哈希值,若哈希值不相同,则将该元素直接放入。
- 若新元素与已有元素的哈希值相同,则使用新元素调用equals方法与已有元素依次比较。若相等则添加元素失败,否则将元素直接放入即可。
- 如果两个元素的
equals()
方法返回 true,但它们的hashCode()
返回值不相等,hashSet
将会把它们存储在不同的位置,但依然可以添加成功。
package com.company;import java.util.HashSet;// HashSet 测试样例
public class Java_18 {public static void main(String[] args) {// 添加元素HashSet<String> sites = new HashSet<String>();sites.add("1");sites.add("2");sites.add("3");sites.add("4");sites.add("1"); // 重复的元素不会被添加System.out.println(sites);// 使用 contains()方法判断元素是否存在于当前的集合中System.out.println(sites.contains("3")); // 输出 true
// 使用 remove() 方法来删除指定元素 使用 clear() 删除所有元素System.out.println(sites.remove("3")); // 输出 true 表示已经成功删除System.out.println(sites);// 使用 size 方法可以计算HashSet中元素的数量,System.out.println(sites.size());// 使用 for-each遍历HashSetfor (String i : sites) {System.out.println(i);}}
}
Java_Set为什么要求重写equals方法后要重写hashCode方法呢?
- 当两个元素调用equals方法相等时证明这两个元素相同,重写hashCode方法后保证这两个元素得到的哈希码值相同,由同一个哈希算法生成的索引位置相同,此时只需要与该索引位置已有元素比较即可,从而提高效率并避免重复元素的出现。
hashCode
- 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。
- 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等。
- 对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
equals:
- 当一个类有自己特有的“逻辑相等”概念,当改写equals()的时候,总是要改写hashCode(),根据一个类的equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode()方法,它们仅仅是两个对象。因此,违反了“相等的对象必须具有相等的散列码”。
- 复写
equals
方法的时候一般都需要同时复写hashCode
方法。通常参与计算hashCode
的对象的属性也应该参与到equals()
中进行计算。
Java_Set实现类之二:LinkedHashSet
- LinkedHashSet 是 HashSet 的子类
- LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
- LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。
- LinkedHashSet 不允许集合元素重复。
Java_Set实现类之三:TreeSet
二叉树
二叉树主要指每个节点最多只有两个子节点的树形结构。
满足以下3个特征的二叉树叫做有序二叉树。
- a.左子树中的任意节点元素都小于根节点元素值;
- b.右子树中的任意节点元素都大于根节点元素值;
- c.左子树和右子树的内部也遵守上述规则;
TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。
由于TreeSet集合的底层采用红黑树进行数据的管理,当有新元素插入到TreeSet集合时,需要使用新元素与集合中已有的元素依次比较来确定新元素的合理位置。
比较元素大小的规则有两种方式:
- TreeSet 两种排序方法:
自然排序和定制排序
。默认情况下,TreeSet 采用自然排序 - 使用元素的自然排序规则进行比较并排序,让元素类型实现java.lang.Comparable接口;
- 使用比较器规则进行比较并排序,构造TreeSet集合时传入java.util.Comparator接口;
- 自然排序的规则比较单一,而比较器的规则比较多元化,而且比较器优先于自然排序;
自然排序:
- 自然排序:TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列
- 如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable
接口。 - 实现
Comparable
的类必须实现compareTo(Object obj)
方法,两个对象即通过compareTo(Object obj)
方法的返回值来比较大小。
Comparable 的典型实现:
BigDecimal、BigInteger
以及所有的数值型对应的包装类:按它们对应的数值大小
进行比较Character
:按字符的 unicode值来进行比较Boolean
:true 对应的包装类实例大于 false 对应的包装类实例String
:按字符串中字符的 unicode 值进行比较Date、Time
:后边的时间、日期比前面的时间、日期大
定序排序
- TreeSet的自然排序要求元素所属的类实现Comparable接口,如果元素所属的类没有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素或希望按照其它属性大小进行排序,则考虑使用定制排序。定制排序,通过Comparator接口来实现。需要重写
compare(T o1,T o2)方法
。 - 利用
int compare(T o1,T o2)
方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。 - 要实现定制排序,需要将实现
Comparator
接口的实例作为形参传递给TreeSet
的构造器。 - 此时,仍然只能向TreeSet中添加类型相同的对象。否则发生ClassCastException异常。
- 使用定制排序判断两个元素相等的标准是:通过Comparator比较两个元素返回了0。
public static List duplicateList(List list) {HashSet set = new HashSet();set.addAll(list);return new ArrayList(set);
}
public static void main(String[] args) {List list = new ArrayList();list.add(new Integer(1));list.add(new Integer(2));list.add(new Integer(2));list.add(new Integer(4));list.add(new Integer(4));List list2 = duplicateList(list);for (Object integer : list2) {System.out.println(integer);}
}
Java_Map集合
Map与Collection
并列存在,用于保存具有映射关系的数据。Map 中的 key 和 value
都可以是任何引用类型的数据,java.util.Map<K,V>
集合中存取元素的基本单位是:单对元素,其中类型参数如下:- K - 此映射所维护的键(Key)的类型,相当于目录。
- V - 映射值(Value)的类型,相当于内容。
Map 中的 key 用Set
来存放,不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()和equals()
方法
- Map集合的主要实现类有:HashMap类、TreeMap类、LinkedHashMap类、Hashtable类、Properties类。
- HashMap类的底层是采用哈希表进行数据管理的。
- TreeMap类的底层是采用红黑树进行数据管理的。
- LinkedHashMap类与HashMap类的不同之处在于内部维护了一个双向链表,链表中记录了元素的迭代顺序,也就是元素插入集合中的先后顺序,因此便于迭代。
- Hashtable类是古老的Map实现类,与HashMap类相比属于线程安全的类,且不允许null作为key或者value的数值。
- Properties类是Hashtable类的子类,该对象用于处理属性文件,key和value都是String类型的。
- Map集合是面向查询优化的数据结构, 在大数据量情况下有着优良的查询性能,经常用于根据key检索value的业务场景。
- 常用
String类作为Map的“键”
,key 和 value
之间存在单向一对一关系,即通过指定的key
总能找到唯一的、确定的value
Java_Map常用方法
方法 | 描述 |
---|---|
V put(K key, V value) | 将Key-Value对存入Map,若集合中已经包含该Key,则替换该Key所对应的Value,返回值为该Key原来所对应的Value,若没有则返回null |
V get(Object key) | 返回与参数Key所对应的Value对象,如果不存在则返回null |
boolean containsKey(Object key); | 判断集合中是否包含指定的Key |
boolean containsValue (Object value); | 判断集合中是否包含指定的Value |
V remove(Object key) | 根据参数指定的key进行删除 |
Set keySet() | 返回此映射中包含的键的Set视图 |
Collection values() | 返回此映射中包含的值的Set视图 |
Set<Map.Entry<K,V>>entrySet() | 返回此映射中包含的映射的Set视图 |
添加、删除、修改操作:
Object put(Object key,Object value)
:将指定key-value添加到(或修改)当前map对象中void putAll(Map m):
将m中的所有key-value对存放到当前map中Object remove(Object key):
移除指定key的key-value对,并返回valuevoid clear()
:清空当前map中的所有数据
package com.company;import java.util.HashMap;
import java.util.Map;//map 测试样例
public class Java_20 {public static void main(String[] args) {// 定义一个Map的容器对象Map<String, Integer > map1 = new HashMap<String, Integer >();map1.put("张三", 20);map1.put("李四", 18);map1.put("王五", 17);map1.put("张龙", 25);System.out.println(map1);// 添加重复的键值(值不同),会返回集合中原有(重复键)的值, System.out.println(map1.put("jack", 30)); //20Map<String, Integer> map2 = new HashMap<String, Integer>();map2.put("张龙", 100);map2.put("赵虎", 20);System.out.println("map2:" + map2);
// 从指定映射中将所有映射关系复制到此映射中。map1.putAll(map2);System.out.println("map1:" + map1);map1.remove("张龙"); // 删除张龙map1.clear(); // 删除所有元素}
}
元素查询的操作:
Object get(Object key):
获取指定key对应的valueboolean containsKey(Object key)
:是否包含指定的keyboolean containsValue(Object value)
:是否包含指定的valueint size()
:返回map中key-value对的个数boolean isEmpty()
:判断当前map是否为空boolean equals(Object obj)
:判断当前map和参数对象obj是否相等
package com.company;import java.util.HashMap;
import java.util.Map;//map 测试样例
public class Java_20 {public static void main(String[] args) {// 定义一个Map的容器对象Map<String, Integer > map1 = new HashMap<String, Integer >();map1.put("张三", 20);map1.put("李四", 18);map1.put("王五", 17);map1.put("张龙", 25);System.out.println(map1);// 添加重复的键值(值不同),会返回集合中原有(重复键)的值, System.out.println(map1.put("jack", 30)); //20Map<String, Integer> map2 = new HashMap<String, Integer>();map2.put("张龙", 100);map2.put("赵虎", 20);System.out.println("map2:" + map2);
// 从指定映射中将所有映射关系复制到此映射中。map1.putAll(map2);System.out.println("map1:" + map1);map1.remove("张龙"); // 删除张龙map1.clear(); // 删除所有元素map1.get("张三"); // 获取指定的key 对应的值
// containsValue 指定的 Value 的值System.out.println( map1.containsKey("账务")); // 判断是否具有指定的 key值System.out.println( map1.size()); // 查看当前的集合元素个数System.out.println( map1.isEmpty()); // 判断是否为空System.out.println( map1.equals(map2)); // 判断当前map和参数对象obj是否相等}
}
元视图操作的方法:
Set keySet()
:返回所有key构成的Set集合Collection values()
:返回所有value构成的Collection集合Set entrySet()
:返回所有key-value对构成的Set集合
Java_ Map实现类之一:HashMap
HashMap是 Map
接口使用频率最高的实现类。- 允许使用
null键和null值
,与HashSet
一样,HashMap
不保证映射的顺序。 - 所有的
key
构成的集合是Set:
无序的、不可重复的。所以,key
所在的类要重写:equals(),hashCode()
- 所有的
value构成的集合是Collection
:无序的、可以重复的。所以,value
所在的类要重写:equals()
- 一个
key-value构成一个键值对
,键值对
构成的集合是Set
:无序的、不可重复的 HashMap
判断两个key
相等的标准是:两个key 通过 equals() 方法返回 true,hashCode
值也相等。HashMap
判断两个value
相等的标准是:两个value 通过 equals() 方法返回 true
。
Java_HashMap的存储结构
HashMap
的内部存储结构其实是数组+链表+树
的结合- 当实例化一个
HashMap
时,会初始化initialCapacity和loadFactor
,在put
(添加)第一对映射关系时,系统会创建一个长度为initialCapacity
的Node
数组,这个长度在哈希表中被称为容量(Capacity)
,在这个数组中可以存放元素的位置我们称之为“桶”(bucket),
,每个bucket
都有自己的索引,系统可以根据索引快速的查找bucket
中的元素。 - 每个
bucket
中存储一个元素,即一个Node
对象,但每一个Node
对象可以带一个引用变量next
,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Node链
。也可能是一个一个TreeNode对象
,每一个TreeNode
对象可以有两个叶子结点left和right
,因此,在一个桶中,就有可能生成一个TreeNode
树。而新添加的元素作为链表的last
,或树的叶子结点
Java_元素放入HashMap集合的原理
- 使用元素的key调用hashCode方法获取对应的哈希码值,再由某种哈希算法计算在数组中的索引位置。
- 若该位置没有元素,则将该键值对直接放入即可。
- 若该位置有元素,则使用key与已有元素依次比较哈希值,若哈希值不相同,则将该元素直接放入。
- 若key与已有元素的哈希值相同,则使用key调用equals方法与已有元素依次比较。
- 若相等则将对应的value修改,否则将键值对直接放入即可。
具体实现过程
- 向
HashMap中添加entry1(key,value)
,需要首先计算entry1
中key
的哈希值(根据key
所在类的hashCode()
计算得到),此哈希值经过处理以后,得到在底层Entry[]
数组中要存储的位置下标i
。 - 如果位置
i
上没有元素,则entry1
直接添加成功。如果位置i
上已经存在entry2(或还有链表存在的entry3,entry4)
,则需要通过循环的方法,依次比较entry1中key和其他的entry
。如果彼此hash
值不同,则直接添加成功。如果hash
值相同,继续比较二者是否equals
。如果返回值为true
,则使用entry1的value去替换equals为true的entry的value
。 - 如果遍历一遍以后,发现所有的
equals
返回都为false
,则entry1
仍可添加成功。entry1指向原有的entry
元素。
HashMap的扩容
- 当
HashMap
中的元素越来越多的时候,hash
冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap
的数组进行扩容 HashMap
数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。
HashMap扩容时间
- 当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数,size*loadFactor 时 , 就 会 进 行 数 组 扩 容 , loadFactor 的默认 值(DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。
- 默认情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中元素个数超过160.75=12(这个值就是代码中threshold值,也叫做临界值)的时候就把数组的大小扩展为 216=32,即扩大一倍,然后重新计算每个元素在数组中的位置,非常消耗性能的操作
- 如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能
扩容样例分析
当HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成树,结点类型由Node变成TreeNode类型。当然,如果当映射关系被移除后,下次resize方法时判断树的结点个数低于6个,也会把树再转为链表。
**不要修改基于映射关系的key **
映射关系存储到HashMap中会存储key的hash值,这样就不用在每次查找时重新计算每一个Entry或Node(TreeNode)的hash值了,因此如果已经put到Map中的映射关系,再修改key的属性,而这个属性又参与hashcode值的计算,那么会导致匹配不上。
Java_HashMap负载因子
- 负载因子的大小决定了HashMap的数据密度。
- 负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长,造成查询或插入时的比较次数增多,性能会下降。
- 负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建议初始化预设大一点的空间。
- 按照其他语言的参考及研究经验,会考虑将负载因子设置为0.7~0.75,此时平均检索长度接近于常数
Java_Map实现类之二:LinkedHashMap
LinkedHashMap
是HashMap
的子类- 在
HashMap
存储结构的基础上,使用了一对双向链表来记录添加元素的顺序 - 与
LinkedHashSet
类似,LinkedHashMap
可以维护Map
的迭代顺序:迭代顺序与Key-Value
对的插入顺序一致
Java_Map实现类之三:TreeMap
- TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序。
- TreeMap 可以保证所有的 Key-Value 对处于有序状态。
- TreeSet底层使用红黑树结构存储数据
- TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0。
TreeMap 的 Key 的排序:
- 自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
- 定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现Comparable 接口
Java_Map实现类之四:Hashtable
- Hashtable是个古老的 Map 实现类,JDK1.0就提供了。不同于HashMap,Hashtable是线程安全的。
- Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用。
- 与HashMap不同,Hashtable 不允许使用 null 作为 key 和 value
- 与HashMap一样,Hashtable 也不能保证其中 Key-Value 对的顺序
- Hashtable判断两个key相等、两个value相等的标准,与HashMap一致
Java_Map实现类之五:Properties
- Properties 类是 Hashtable 的子类,该对象用于处理属性文件
- 由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型
- 存取数据时,建议使用
setProperty(String key,String value)方法和getProperty(String key)
方法
Java_Collections 工具类
- java.util.Collections类主要提供了对集合操作或者返回集合的静态方法。
Collections
是一个操作Set、List 和 Map
等集合的工具类,操作数组的工具类Arrays
Collections
中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
Collections 工具类的排序操作:(均为static方法)
reverse(List)
:反转 List 中元素的顺序shuffle(List)
:对 List 集合元素进行随机排序sort(List)
:根据元素的自然顺序对指定 List 集合元素按升序排序sort(List,Comparator):
根据指定的 Comparator 产生的顺序对 List 集合元素进行排序swap(List,int, int)
:将指定 list 集合中的 i 处元素和 j 处元素进行交换
Collections查找、替换常用方法:
Object max(Collection)
:根据元素的自然顺序,返回给定集合中的最大元素Object max(Collection,Comparator)
:根据 Comparator 指定的顺序,返回给定集合中的最大元素Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object)
:返回指定集合中指定元素的出现次数void copy(List dest,List src)
:将src中的内容复制到dest中boolean replaceAll(List list,Object oldVal,Object newVal)
:使用新值替换List 对象的所有旧值
Collections常用方法:同步控制
- Collections 类中提供了多个
synchronizedXxx()
方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
Java_CopyOnWriteArrayList
- Copy-On-Write 简称 COW ,是一种用于程序设计中的优化策略
COW 基本思路:
- 一开始大家共享同一个内容,当某个人想要修改这个内容的时候,才会真正的把内容复制出去,形成一个新的内容,然后再修改
- 在修改完内容后,在将原容器的引用指向新的容器,这样做可以对
CopyOnWrite
容器进行并发的读写,而不需要加锁,因为原本的容器不会修改内容 CopyOnWrite
容器也是一种读写分离的思想,读和写是不同的容器COW
本质上是一种延时懒惰的策略- 在Java的并发包中提供了两个使用
CopyOnWrite
机制实现的并发容器,CopyOnWriteArrayList
和CopyOneWruteArraySet
CopyOnWrite
容器在很多并发场景中使用
// CopyOnWriteArrayList 样例import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;// CopyOnWrite的应用场景
// CopyOnWrite并发容器用于读多写少的并发场景,比如白名单,黑名单,商品类目的访问和更新场景,
public class Java_80 {public static void main(String[] args) throws Exception {List<String> s = new ArrayList<String>();s.add("1");s.add("2");s.add("3");s.add("4");final CopyOnWriteArrayList<String> cs = new CopyOnWriteArrayList<String>(s);Thread t = new Thread(new Runnable() {int temp = 3;@Overridepublic void run() {while(temp>0){temp--;cs.add("----");System.out.println("输出的为 cs 复制 s 的内容");System.out.println(cs);}}});t.setDaemon(true);t.start();Thread.currentThread().sleep(1);for (String outCs :cs){System.out.println(cs.hashCode()); // 输出CS内每个值的的hash码System.out.println(s);}
// 此时可以看到输出hashCode的CS是不是同一CS}
}
Java_使用CopyOnWrite容器的注意事项
- 减少扩容开销,根据实际需要,初始化CopyOnWrite容器的大小,避免
CopyOnWrite
自动扩容 - 在往
CopyOnWrite
容器中,添加新的内容时,如果内容已经确定,使用addBlackList
方法进行添加,因为每次添加,容器都会进行复制,所以减少添加的次数,可以减少容器的复制次数,从而避免资源的浪费
Java_CopyOnWrite的缺点
CopyOnWrite
容器有很多优点,但是同时存在两个问题内存占用问题和数据一致问题
内存占用问题:
- 因为
CopyOnWrite
容器是写时复制机制,所以在进行写操作时,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象 - 在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存
- 如果这些旧的对象占用的内存比较大,比如说100M左右,那么再写入100M数据进去,内存就会占用200M,这时候就可能导致服务崩溃
解决内存占用问题的方法
- 针对内存占用问题,可以通过压缩容器中的元素的方法减少大对象的内存消耗,比如,如果使用元素全是十进制的数字,可以考虑将元素压缩成64进制的元素,或者不适用
CopyOnWrite容器
,而使用其他的并发容器,例如ConcurrentHashMap
数据一致性问题:
CopyOnWrite
容器只能保证数据的最终一致性,不能保证数据的实时一致性- 如果希望写入的数据马上可以被读取,此时不要使用
CopyOnWrite
容器
Java_ConcurrentHashMap
线程不安全的HashMap
ConcurrentHashMap
的出现是因为在多线程环境下使用HashMap
进行put
操作会引起死循环,导致CPU利用率接近100%,所以正在并发的情况下不能使用HashMap
效率低下的HashTable容器
- HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。
- 因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态,如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。
- 对于Hashtable而言,synchronized是针对整张Hash表的,即每次锁住整张表让线程独占。相当于所有线程进行读写时都去竞争一把锁,导致效率非常低下。
JDK 1.7 中的ConcurrentHashMap的锁分段技术
- HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁。
- 容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率即
ConcurrentHashMap
所使用的锁分段技术 ConcurrentHashMap
的锁分段技术将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。- ConcurrentHashMap可以做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持地尽量地小,不用对整个ConcurrentHashMap加锁。
JDK 1.8 的CurrentHahMap的实现原理
- 在JDK1.8中CurrentHahMap参考了HashMap的实现,采用了数组+链表+红黑树的实现方式来设计
- JDK8中彻底放弃了Segment转而采用的是Node,其设计思想也不再是JDK1.7中的分段锁思想。Segment 是 1.7 中CurrentHahMap的实现的概念
- JDK 8中 使用 Node:保存key,value及key的hash值的数据结构。其中value和next都用volatile修饰,保证并发的可见性。
- Java8 ConcurrentHashMap结构基本上和Java8的HashMap一样,不过保证线程安全性。
- JDK 1.8 中 ConcurrentHashMap 早期完全采用链表结构,但是当 ConcurrentHashMap在链表长度大于某个阈值时会将链表转换为红黑树进一步提高查找性能
- DK1.8版本的ConcurrentHashMap的数据结构已经接近HashMap,相对而言,ConcurrentHashMap只是增加了同步的操作来控制并发,
- ConcurrentHashMap从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树。
JDK 1.8 的CurrentHahMap的总结
- 数据结构:取消了Segment分段锁的数据结构,取而代之的是数组+链表+红黑树的结构。
- 保证线程安全机制:JDK1.7采用segment的分段锁机制实现线程安全,其中segment继承自ReentrantLock。JDK1.8采用CAS+Synchronized保证线程安全。
- 锁的粒度:原来是对需要进行数据操作的Segment加锁,现调整为对每个数组元素加锁(Node)。
- 链表转化为红黑树:定位结点的hash算法简化会带来弊端,Hash冲突加剧,因此在链表节点数量大于8时,会将链表转化为红黑树进行存储。
- 查询时间复杂度:从原来的遍历链表O(n),变成遍历红黑树O(logN)。
Java_Queue(队列)
- java.util.Queue集合是Collection集合的子集合,与List集合属于平级关系。
- java.util.Queue集合主要用于描述具有先进先出特征的数据结构,叫做队列(first in first out FIFO)。
- java.util.Queue集合的主要实现类是LinkedList类,因为该类在增删方面比较有优势。
Queue(队列)实现了阻塞接口:
- 实现阻塞接口的队列实质上就是一种带有一点扭曲的 FIFO 数据结构。不是立即从队列中添加或者删除元素,线程执行操作阻塞,直到有空间或者元素可用。
Queue(队列)的实现 _BlockingQueue
- BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。
- BlockingQueue阻塞队列,在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒
- BlockingQueue使用时也不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了
BlockingQueue的核心方法
方法 | 介绍 |
---|---|
boolean offer(E e) | 将一个对象添加至队尾,若添加成功则返回true |
E poll() | 从队首删除并返回一个元素 |
E peek() | 返回队首的元素(但并不删除) |
1. 放入数据
- offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.(本方法不阻塞当前执行方法的线程)
- offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败。
- put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.
2. 获取数据
- poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null;
- poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。
- take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入;
- drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。
Java_Queue的ArrayBlockingQueue实现类
- ArrayBlockingQueue在构造时需要指定容量, 并可以选择是否需要公平性,如果公平参数被设置true,等待时间最长的线程会优先得到处理(其实就是通过将ReentrantLock设置为true来 达到这种公平性的:即等待时间最长的线程会先操作)。
- 通常,公平性会使你在性能上付出代价,只有在的确非常需要的时候再使用它。它是基于数组的阻塞循环队 列,此队列按 FIFO(先进先出)原则对元素进行排序。
- ArrayBlockingQueue基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。
- ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于LinkedBlockingQueue
- ArrayBlockingQueue和LinkedBlockingQueue间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。
Java_Queue的LinkedBlockingQueue实现类
- LinkedBlockingQueue的容量是没有上限的(说的不准确,在不指定时容量为Integer.MAX_VALUE,不要然的话在put时怎么会受阻呢),但是也可以选择指定其最大容量,它是基于链表的队列,此队列按 FIFO(先进先出)排序元素。
- LinkedBlockingQueue基于链表的阻塞队列,同ArrayListBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理
- LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
- 如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。
- ArrayBlockingQueue和LinkedBlockingQueue是两个最普通也是最常用的阻塞队列,一般情况下,在处理多线程间的生产者消费者问题,使用这两个类足以。
Java_Queue的ConcurrentLinkedQueue实现类
- ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部,当我们获取一个元素时,它会返回队列头部的元素。
ConcurrentLinkedQueue使用特点
- 不允许null入列
- 在入队的最后一个元素的next为null
- 队列中所有未删除的节点的item都不能为null且都能从head节点遍历到
- 删除节点是将item设置为null, 队列迭代时跳过item为null节点
- head节点跟tail不一定指向头节点或尾节点,可能存在滞后性
ConcurrentLinkedQueue的实现方法
- offer(E e): 将指定元素插入此队列的尾部。
- poll() 获取并移除此队列的头,如果此队列为空,则返回 null。
- offer是往队列添加元素,poll是从队列取出元素并且删除该元素
- ConcurrentLinkedQueue中的add() 和 offer() 完全一样,都是往队列尾部添加元素
public static void main(String[] args) {ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();queue.offer("哈哈哈");System.out.println("offer后,队列是否空?" + queue.isEmpty());System.out.println("从队列中poll:" + queue.poll());System.out.println("pool后,队列是否空?" + queue.isEmpty());}
- peek()获取但不移除此队列的头;如果此队列为空,则返回 null
public static void main(String[] args) {ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();queue.offer("哈哈哈");System.out.println("offer后,队列是否空?" + queue.isEmpty());System.out.println("从队列中peek:" + queue.peek());System.out.println("从队列中peek:" + queue.peek());System.out.println("从队列中peek:" + queue.peek());System.out.println("pool后,队列是否空?" + queue.isEmpty());}
- remove( object o) 从队列中移除指定元素的单个实例(如果存在)
- remove一个已存在元素,会返回true,remove不存在元素,返回false
public static void main(String[] args) {ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();queue.offer("哈哈哈");System.out.println("offer后,队列是否空?" + queue.isEmpty());System.out.println("从队列中remove已存在元素 :" + queue.remove("哈哈哈"));System.out.println("从队列中remove不存在元素:" + queue.remove("123"));System.out.println("remove后,队列是否空?" + queue.isEmpty());}
- size() 返回此队列中的元素数量
- 如果此队列包含的元素数大于
Integer.MAX_VALUE,
则返回Integer.MAX_VALUE
。 - 需要小心的是,与大多数 collection 不同,size()方法不是一个固定时间操作。由于这些队列的异步特性,确定当前的元素数需要进行一次花费 O(n) 时间的遍历。所以在需要判断队列是否为空时,尽量不要用 queue.size()>0,而是用 !queue.isEmpty()
size() 和 isEmpty() 效率的示例
- 10000个人去饭店吃饭,10张桌子供饭,分别比较size() 和 isEmpty() 的耗时
public class Test01ConcurrentLinkedQueue {public static void main(String[] args) throws InterruptedException {int peopleNum = 10000;//吃饭人数int tableNum = 10;//饭桌数量ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();CountDownLatch count = new CountDownLatch(tableNum);//计数器//将吃饭人数放入队列(吃饭的人进行排队)for(int i=1;i<=peopleNum;i++){queue.offer("消费者_" + i);}//执行10个线程从队列取出元素(10个桌子开始供饭)System.out.println("-----------------------------------开饭了-----------------------------------");long start = System.currentTimeMillis();ExecutorService executorService = Executors.newFixedThreadPool(tableNum);for(int i=0;i<tableNum;i++) {executorService.submit(new Dinner("00" + (i+1), queue, count));}//计数器等待,知道队列为空(所有人吃完)count.await();long time = System.currentTimeMillis() - start;System.out.println("-----------------------------------所有人已经吃完-----------------------------------");System.out.println("共耗时:" + time);//停止线程池executorService.shutdown();}private static class Dinner implements Runnable{private String name;private ConcurrentLinkedQueue<String> queue;private CountDownLatch count;public Dinner(String name, ConcurrentLinkedQueue<String> queue, CountDownLatch count) {this.name = name;this.queue = queue;this.count = count;}@Overridepublic void run() {//while (queue.size() > 0){while (!queue.isEmpty()){//从队列取出一个元素 排队的人少一个System.out.println("【" +queue.poll() + "】----已吃完..., 饭桌编号:" + name);}count.countDown();//计数器-1}}
}
contains(Object o) 方法:如果此队列包含指定元素,则返回 true
public static void main(String[] args) throws InterruptedException {ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();queue.offer("123");System.out.println(queue.contains("123"));System.out.println(queue.contains("234"));}
- toArray() 方法:返回以恰当顺序包含此队列所有元素的数组
- toArray(T[] a) 方法返回以恰当顺序包含此队列所有元素的数组;返回数组的运行时类型是指定数组的运行时类型
public static void main(String[] args) throws InterruptedException {ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<String>();queue.offer("123");queue.offer("234");Object[] objects = queue.toArray();System.out.println(objects[0] + ", " + objects[1]);//将数据存储到指定数组String[] strs = new String[2];queue.toArray(strs);System.out.println(strs[0] + ", " + strs[1]);}
- iterator() 返回在此队列元素上以恰当顺序进行迭代的迭代器
public static void main(String[] args) throws InterruptedException {ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<String>();queue.offer("123");queue.offer("234");Iterator<String> iterator = queue.iterator();while (iterator.hasNext()){System.out.println(iterator.next());}}
Java_泛型机制
- 集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象在
JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决
。因为这个时候除了元素的类型不确定,其他的部分都是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。 - 从Java5开始增加泛型机制,也就是在集合名称的右侧使用<数据类型>的方式来明确要求该集合中可以存放的元素类型,若放入其它类型的元素则编译报错。
- 泛型只在编译时期有效,在运行时期不区分是什么类型。
Collection<E>,List<E>,ArrayList<E>
这个<E>
就是类型参数,泛型的本质就是参数化类型,也就是让数据类型作为参数传递,其中E相当于形式参数负责占位,而使用集合时<>中的数据类型相当于实际参数,用于给形式参数E进行初始化,从而使得集合中所有的E被实际参数替换,由于实际参数可以传递各种各样广泛的数据类型,因此得名为泛型。
Java_为什么存在泛型
- 使用泛型和Obeject都可以存储数据,
- 在集合中没有泛型的时候,任何类型都可以添加到集合中,类型不安全,而且从集合中读取出来的对象需要强制类型转换.
- 在集合中有泛型时候,只有指定类型才可以添加到集合中,类型安全,而且读出的对象不需要转换类型
- 泛型解决元素存储的安全性问题,好比商品、药品标签,不会弄错。但是获取数据元素时,需要类型强制转换的问题,好比不用每回拿商品、药品都要辨别。
- Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生
ClassCastException
异常。
package com.company;
// Java泛型样例
import java.util.ArrayList;
import java.util.Iterator;public class Java_FanXing {public static void main(String[] args) {ArrayList<Integer> list = new ArrayList<>();//类型推断list.add(78);list.add(88);list.add(77);list.add(66);//遍历方式一://for(Integer i : list){//不需要强转//System.out.println(i);//}//遍历方式二:Iterator<Integer> iterator = list.iterator();while(iterator.hasNext()){System.out.println(iterator.next());}}
}
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("Tom1",34);
map.put("Tom2",44);
map.put("Tom3",33);
map.put("Tom4",32);
//添加失败
//map.put(33, "Tom");
Set<Entry<String,Integer>> entrySet = map.entrySet();Iterator<Entry<String,Integer>> iterator = entrySet.iterator();while(iterator.hasNext()){Entry<String,Integer> entry = iterator.next();System.out.println(entry.getKey() + "--->" + entry.getValue());}
Java_自定义泛型接口
Java_泛型的声明
- interface List 和 class GenTest<K,V> 其中,T,K,V不代表值,而是表示类型。这里使用任意字母都可以。常用T表示,是Type的缩写。
Map<String,Integer> map = new HashMap<String,Integer>();
Java_泛型的实例化
- 一定要在类名后面指定类型参数的值(类型)。如:
List<String> strList = new ArrayList<String>();
和Iterator<Customer> iterator = customers.iterator();
T只能是类,不能用基本数据类型填充。但可以使用包装类填充
把一个集合中的内容限制为一个特定的数据类型
,这就是泛型背后的核心思想
Java_泛型类
- 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:
<E1,E2,E3>
- 泛型类的构造器如下:
public GenericClass(){}
。而下面是错误的:public GenericClass<E>(){}
- 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
- 泛型不同的引用不能相互赋值。尽管在编译时
ArrayList<String>和ArrayList<Integer>
是两种类型,但是,在运行时只有一个ArrayList
被加载到JVM中。 - 泛型如果不指定,将被擦除,泛型对应的类型均按照
Object
处理,但不等价于Object。
即:泛型要使用一路都用。要不用,一路都不要用。
如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
- 泛型的简化操作:
ArrayList<Fruit> flist = new ArrayList<>()
- 泛型的指定中不能使用基本数据类型,可以使用包装类替换.
class GenericTest {public static void main(String[] args) {// 1、使用时:类似于Object,不等同于ObjectArrayList list = new ArrayList();// list.add(new Date());//有风险list.add("hello");test(list);// 泛型擦除,编译不会类型检查// ArrayList<Object> list2 = new ArrayList<Object>();// test(list2);//一旦指定Object,编译会类型检查,必须按照Object处理
}
public static void test(ArrayList<String> list) {String str = "";for (String s : list) {str += s + ",";}System.out.println("元素:" + str);}
}
- 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
异常类不能是泛型的
- 不能使用
new E[]
。但是可以:E[] elements = (E[])new Object[capacity];
参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组
- 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型
- 子类不保留父类的泛型:按需实现,没有类型 擦除
- 子类保留父类的泛型:泛型子类的两种情况:全部保留,部分保留
- 子类除了
指定或保留
父类的泛型,还可以增加自己的泛型
样例代码一
class Father<T1, T2> {}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son1 extends Father {// 等价于class Son extends Father<Object,Object>{}
// 2)具体类型
class Son2 extends Father<Integer, String> {}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2> extends Father<T1, T2> {}
// 2)部分保留
class Son4<T2> extends Father<Integer, T2> {}
样例代码二;
class Father<T1, T2> {}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son<A, B> extends Father{//等价于class Son extends Father<Object,Object>{}
// 2)具体类型
class Son2<A, B> extends Father<Integer, String> {}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2, A, B> extends Father<T1, T2> {}
// 2)部分保留
class Son4<T2, A, B> extends Father<Integer, T2> {}
Java_泛型方法
- 方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
- 泛型方法的格式:
[访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常
public static <T> void fromArrayToCollection(T[] a, Collection<T> c) {for (T o : a) {c.add(o);}
}
public static void main(String[] args) {Object[] ao = new Object[100];Collection<Object> co = new ArrayList<Object>();fromArrayToCollection(ao, co);String[] sa = new String[20];Collection<String> cs = new ArrayList<>();fromArrayToCollection(sa, cs);Collection<Double> cd = new ArrayList<>();// 下面代码中T是Double类,但sa是String类型,编译错误。// fromArrayToCollection(sa, cd);// 下面代码中T是Object类型,sa是String类型,可以赋值成功。fromArrayToCollection(sa, co);
}
class Creature{}
class Person extends Creature{}
class Man extends Person{}
class PersonTest {public static <T extends Person> void test(T t){System.out.println(t);}
public static void main(String[] args) {test(new Person());test(new Man());//The method test(T) in the type PersonTest is not //applicable for the arguments (Creature)test(new Creature());}
}
Java_泛型在继承上的体现
public void testGenericAndSubClass() {Person[] persons = null;Man[] mans = null;// 而 Person[] 是 Man[] 的父类.persons = mans;Person p = mans[0];// 在泛型的集合上List<Person> personList = null;List<Man> manList = null;// personList = manList;(报错)
}
- 如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,
G<B>并不是G<A>的子类型!
- String是Object的子类,但是
List<String >并不是List<Object>的子类。
Java_通配符的使用
- 使用类型
通配符:?
比如:List<?> ,Map<?,?>
,List<?>是List<String>、List<Object>等各种泛型List的父类。
- 读取
List<?>
的对象list中的元素时,永远是安全的,因为不管list
的真实类型是什么,它包含的都是Object。
- 写入
list
中的元素时,不行。因为我们不知道lits
的元素类型,我们不能向其中添加对象。 唯一的例外是null,
它是所有类型的成员
将任意元素加入到其中不是类型安全的:
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // 编译时错误
- 因为我们不知道c的元素类型,我们不能向其中添加对象。
add方法
有类型参数E
作为集合的元素类型。传给add的任何参数
都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。
public static void main(String[] args) {List<?> list = null;list = new ArrayList<String>();list = new ArrayList<Double>();// list.add(3);//编译不通过list.add(null);List<String> l1 = new ArrayList<String>();List<Integer> l2 = new ArrayList<Integer>();l1.add("尚硅谷");l2.add(15);read(l1);read(l2);
}
public static void read(List<?> list) {for (Object o : list) {System.out.println(o);}
}//注意点1:编译错误:不能用在泛型方法声明上,返回值类型前面<>不能使用?
public static <?> void test(ArrayList<?> list){}//注意点2:编译错误:不能用在泛型类的声明上
class GenericTypeClass<?>{}//注意点3:编译错误:不能用在创建对象上,右边属于创建集合对象
ArrayList<?> list2 = new ArrayList<?>();
有限制的通配符
<?>
允许所有泛型的引用调用通配符指定上限
:上限extends:
使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
- 通配符指定下限:
下限super
:使用时指定的类型不能小于操作的类,即>=
<? extends Number> (无穷小 , Number]
:只允许泛型为Number及Number
子类的引用调用<? super Number> [Number , 无穷大)
:只允许泛型为Number及Number
父类的引用调用<? extends Comparable>
只允许泛型为实现Comparable
接口的实现类的引用调用
public static void printCollection3(Collection<? extends Person> coll) {//Iterator只能用Iterator<?>或Iterator<? extends Person>.why?Iterator<?> iterator = coll.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}
}
public static void printCollection4(Collection<? super Person> coll) {//Iterator只能用Iterator<?>或Iterator<? super Person>.why?Iterator<?> iterator = coll.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}
}
Java_比较器枚举类和注解集合泛型相关推荐
- 学妹问我Java枚举类与注解,我直接用这个搞定她!
很多人问我学妹长什么样,不多说 上图吧! 学妹问我Java枚举类与注解,我直接一篇文章搞定! 一.枚举类 ① 自定义枚举类 ② enum关键字定义枚举类 ③ enum 枚举类的方法 ④ enum 枚举 ...
- Java枚举类和注解
文章目录 枚举类和注解 枚举类的使用 枚举类的说明: 如何自定义枚举类?步骤: jdk 5.0 新增使用enum定义枚举类.步骤: 使用enum定义枚举类之后,枚举类常用方法:(继承于java.lan ...
- Java查漏补缺(08)关键字:static、单例设计模式、理解main方法、类的成员之四:代码块、final关键字、抽象类、接口、内部类、枚举类、注解、包装类
Java查漏补缺(08)关键字:static.单例设计模式.理解main方法.类的成员之四:代码块.final关键字.抽象类.接口.内部类.枚举类.注解.包装类 本章专题与脉络 1. 关键字:stat ...
- 新星计划Day2【JavaSE】 枚举类与注解
新星计划Day2[JavaSE] 枚举类与注解
- java6:枚举类和注解
一.枚举类和注解的整体框架 1.枚举类框架 2.注解框架 3.枚举类整体知识 /* 1.枚举类的第一种定义 定义私有化属性和私有化构造器 提供静态常量对象 2.enum定义枚举类 定义私有化属性和私有 ...
- 第九章:Java_枚举类和注解
一.枚举类 1.如何自定义枚举类. 枚举类:类的对象是有限个的,确定的. 1.1 私有化类的构造器,保证不能在类的外部创建其对象 1.2 在类的内部创建枚举类的实例.声明为:public static ...
- day06--java高级编程:多线程,枚举类,注解,反射,网络通讯
1 Day16–多线程01 1.1 程序概念 程序(program):是为完成特定任务.用某种语言编写的一组指令的集合.即指一段静态的代码,静态对象. 1.2 进程 1.2.1 概念 进程(proce ...
- java 枚举 注解_Java枚举类和注解梳理
1. 枚举类 1. 枚举类的使用 枚举类的理解:类的对象只有有限个,确定的.我们称此类为枚举类. 当需要定义一组常量时,强烈建议使用枚举类. 如果枚举类中只有一个对象,则可以作为单例模式的实现方式. ...
- 枚举类与注解(复习)
枚举类的使用 枚举类的使用 如何自定义枚举类 枚举类中的方法 注解 Annotation 自定义注解 jdk 中四个元注解 @Retention @Target @Documented @Inheri ...
最新文章
- java椭圆按钮_JAVA按钮重载如何实现椭圆按钮
- 2015蓝桥杯省赛---java---B---8(饮料换购)
- 嘀嗒出行被曝五一活动虚假宣传,官方回应:为打击黑产
- word 分栏后转html,分栏怎么让两边一样 怎样让word文档分栏而顺序不变
- 如何规划前端工程师职业发展路线?
- 如何一个动态创建对象?
- 使用YAML创建一个 Kubernetes Depolyment
- jpa删除数据后数据库无修改_jpa删除数据库
- 经典神经网络 -- ResNet : 设计原理与pytorch实现
- 【论文笔记】Recover Canonical-View Faces in the Wild with Deep Neural Network
- 阿里巴巴python开发面试_在阿里巴巴面试,是什么样的体验?
- stm32之SPI通信学习分析附源码
- cadence 提示lic找不到怎么办
- Spring_Ioc基本配置使用(基于xml)
- Python爬虫-安某某客新房和二手房
- 前端简历如何描述项目经历
- DD-WRT 的优点
- 用python中If-Else做奇偶数的判断
- 如何用php 图片合成一张图片,php图片合成方法(多张图片合成一张)
- mysql库表散列_数据库表--hash clustered table