枚举enum概念方式注意事项常用的方法注解Annotation注解的理解 基本的 Annotation @Override@Deprecated@SuppressWarningsjdk元注解元注解的基本介绍元注解的种类 (了解)@Retention 注解@Target@Documented@Inherited异常Exception 错误ErrorThrowable基本概念错误Error运行时异常RuntimeException编译时异常异常处理(捕捉,抛出,自定义))try-catch-finally(CTRL+ALT+T)异常捕获异常方法:Exception ethrows概念细节自定义异常类throw常用类(多读源码)包装类Wrapper(final)概念八种数据类型的包装类 final 类包装类和基本数据的转换包装类与字符串的转换包装类的常用方法:自己查Integer:Double:Character:Blooean:String类(final)概念:创建and内存分布: 创建String 对象的两种方式:String常见方法:StringBuffer: 概念:String StringBuffer转换and内存分布:String VS StringBufferStringBuffer常用方法:StringBuilder:概念:用法一样,速度快,但是线程不安全。StringBuilder>StringBuffer>StringMath(final)(静态方法)Math常用的方法(静态方法)Arrays (静态方法)常用方法(静态方法)源码分析冒泡定制排序 System(静态方法)常用方法:BigInteger 加减乘除方法BigDecimal加减乘除方法Date第一代日期类:(Date)第二代日期类(Calendar)(自由,组合)第三代日期类(LocalDateTime)时间戳Instant集合概念:Collection(有序单列集合的接口)概念:Collections类List概念:ArrayList概念:Vector(基本不用)概念:LinkedList概念:增删改查CRUD底层结点的演示(简单模拟)ArrayList VS Vector VS LinkedListArrayList与Vector的区别和适用场景ArrayList有三个构造方法:Vector有四个构造方法:适用场景分析:ArrayList与LinkedList的区别和适用场景Arraylist:LinkedList:适用场景分析:Set概念:HashSet概念:HashSet 底层机制说明模拟底层:底层流程:LinkedHashSet概念:TreeSet概念:TreeSet VS HashSetList VS SetMap(无序双列集合的接口)概念:源码剖析:遍历:HashMap实现类概念:LinkedHashMap实现类概念:HashTable实现类(基本不用) 子类Properties(重点)概念:TreeMap实现类概念:总结:泛型 Generics概念:泛型类 泛型接口概念:泛型通配符(使用层面)泛型方法概念:基本用法泛型类中的泛型方法泛型方法与可变参数静态方法与泛型泛型方法总结泛型上下边界泛型类泛型方法关于泛型数组要提一下最后
枚举enum
概念
1)是有限的几个值(spring, summer, autumn, winter)
2) 只读,不需要修改
1) 枚举是一组常量的集合。
2) 枚举属于一种特殊的类,里面只包含一组有限的特定的对象。
方式
自定义枚举类:
自定义了对象, 固定 对外暴露对象
public static final Season SPRING = new Season("春天", "温暖");将构造器私有化,目的防止 直接 new
去掉setXxx方法, 防止属性被修改
优化,可以加入 final 修饰符(static final底层优化,防止被类加载)
系统定义:
class ---> enum
第一行必须创建对象(默认public static final修饰)
对象:SPRING("春天", "温暖"), WINTER("冬天", "寒冷");
enum Mju{ SPRING("春天", "温暖"), WINTER("冬天", "寒冷"); 属性 构造 方法 }
注意事项
1) 使用 enum 关键字后,就不能再继承其它类了,因为 enum 会隐式继承 Enum(final),而 Java 是单继承机制。 枚举类和普通类一样,可以实现接口
2) 创建对象简化成 SPRING("春天", "温暖"), 这里必须知道,它调用的是哪个构造器.
3) 如果使用无参构造器创建枚举对象,则实参列表和小括号都可以省略
4) 当有多个枚举对象时,使用,间隔,最后有一个分号结尾 ;
5) 枚举对象必须放在枚举类的行首.
常用的方法
我们一起来举例说明 enum 常用的方法的使用,对 Season2 测试. EnumMethod.java
1) toString:Enum 类已经重写过了,返回的是当前对象 名,子类可以重写该方法,用于返回对象的属性信息
2) name:返回当前对象名(常量名),子类中不能重写
3) ordinal:返回当前对象的位置号,默认从 0 开始
4) values:返回当前枚举类中所有的常量
5) valueOf:将字符串转换成枚举对象,要求字符串必须 为已有的常量名,否则报异常!
6) compareTo:比较两个枚举常量,比较的就是编号!
演示 Enum 类的各种方法的使用
public class EnumMethod { public static void main(String[] args) { //使用 Season2 枚举类,来演示各种方法 Season2 autumn = Season2.AUTUMN; //输出枚举对象的名字 System.out.println(autumn.name()); ----------------------------------------name() //ordinal() 输出的是该枚举对象的次序/编号,从 0 开始编号 //AUTUMN 枚举对象是第三个,因此输出 2 System.out.println(autumn.ordinal()); ----------------------------------------ordinal() //从反编译可以看出 values 方法,返回 Season2[] //含有定义的所有枚举对象 Season2[] values = Season2.values(); ----------------------------------------values() System.out.println("===遍历取出枚举对象(增强 for)===="); for (Season2 season: values) { //增强 for 循环 System.out.println(season); } //valueOf:将字符串转换成枚举对象,要求字符串必须 为已有的常量名,否则报异常 //执行流程 //1. 根据你输入的 "AUTUMN" 到 Season2 的枚举对象去查找 //2. 如果找到了,就返回,如果没有找到,就报错 Season2 autumn1 = Season2.valueOf("AUTUMN"); ----------------------------------------valueof() System.out.println("autumn1=" + autumn1); System.out.println(autumn == autumn1); //compareTo:比较两个枚举常量,比较的就是编号 //老韩解读 //1. 就是把 Season2.AUTUMN 枚举对象的编号 和 Season2.SUMMER 枚举对象的编号比较 //2. 看看结果 /* public final int compareTo(E o) { 韩顺平循序渐进学 Java 零基础 第 487页 return self.ordinal - other.ordinal; } Season2.AUTUMN 的编号[2] - Season2.SUMMER 的编号[3] */ System.out.println(Season2.AUTUMN.compareTo(Season2.SUMMER));----------------------------------------compareTo } }
注解Annotation
注解的理解
1) 注解(Annotation)也被称为元数据(Metadata),用于修饰解释 包、类、方法、属性、构造器、局部变量等数据信息。
2) 和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。
3) 在 JavaSE 中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在 JavaEE 中注解占据了更重要的角色,
例如用来配置应用程序的任何切面,代替 java EE 旧版中所遗留的繁冗代码和 XML 配置
基本的 Annotation
介绍 三个基本的 Annotation:
使用 Annotation 时要在其前面增加 @ 符号, 并把该 Annotation 当成一个修饰符使用。用于修饰它支持的程序元 素
1) @Override: 限定某个方法,是重写父类方法, 该注解只能用于方法
2) @Deprecated: 用于表示某个程序元素(类, 方法等)已过时
3) @SuppressWarnings: 抑制编译器警
@Override
@Override:子类的fly方法重写了父类的 fly 。
1.@Override 注解在子类重写了父类的方法上,表示子类的fly方法重写了父类的 fly 。编译器会去检查该方法是否真的重写了父类的方法,如果没有构成重写,则编译错误 。
2.查看@Override注解类的源码为:
/* @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { } */3.修饰范围:只能修饰方法,(类包属性等不可。
@Target(ElementType.METHOD),
3.@interface是注解类。
4.@Target是修饰注解的注解,称为元注解。
@Deprecated
@Deprecated:该元素已经过时
@Deprecated 修饰某个元素, 表示该元素已经过时 即不在推荐使用,但是仍然可以使用 (升级过渡使用)
查看 @Deprecated 注解类的源码:
/* @Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @interface Deprecated { } */修饰范围方法,类,字段,包,参数等
@Target(value={CONSTRUCTOR, FIELD,LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
@SuppressWarnings
@SuppressWarnings({"rawtypes"}):抑制编译器警告
@ SuppressWarnings({""}): 表示当我们不希望看到这些警告
在{""} 中,可以写入你希望抑制(不显示)警告信息
可以指定的警告类型有:
// all,抑制所有警告 // boxing,抑制与封装/拆装作业相关的警告 //cast,抑制与强制转型作业相关的警告 //dep-ann,抑制与淘汰注释相关的警告 //deprecation,抑制与淘汰的相关警告 //fallthrough,抑制与 switch 陈述式中遗漏 break 相关的警告 //finally,抑制与未传回 finally 区块相关的警告 //hiding,抑制与隐藏变数的区域变数相关的警告 //incomplete-switch,抑制与 switch 陈述式(enum case)中遗漏项目相关的警告 //javadoc,抑制与 javadoc 相关的警告 //nls,抑制与非 nls 字串文字相关的警告 //null,抑制与空值分析相关的警告 //rawtypes,抑制与使用 raw 类型相关的警告 //resource,抑制与使用 Closeable 类型的资源相关的警告 //restriction,抑制与使用不建议或禁止参照相关的警告 //serial,抑制与可序列化的类别遗漏 serialVersionUID 栏位相关的警告 //static-access,抑制与静态存取不正确相关的警告 //static-method,抑制与可能宣告为 static 的方法相关的警告 //super,抑制与置换方法相关但不含 super 呼叫的警告 //synthetic-access,抑制与内部类别的存取未最佳化相关的警告 //sync-override,抑制因为置换同步方法而遗漏同步化的警告 //unchecked,抑制与未检查的作业相关的警告 //unqualified-field-access,抑制与栏位存取不合格相关的警告 //unused,抑制与未用的程式码及停用的程式码相关的警告
关于 SuppressWarnings 作用范围是和你放置的位置相关
比如 @SuppressWarnings 放置在 main 方法,那么抑制警告的范围就是 main 通常我们可以放置具体的语句, 方法, 类.
看看 @SuppressWarnings 源码 /* @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); } */
(1) 放置的位置就是 TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE (2) 该注解类有数组 String[] values() 设置一个数组比如 {"rawtypes", "unchecked", "unused"}
jdk元注解
元注解的基本介绍
JDK 的元 Annotation 用于修饰其他 Annotation 元注解: 本身作用不大,讲这个原因希望同学们,看源码时,可以知道他是干什么.
元注解的种类 (了解)
1) @Retention //指定注解的作用范围,三种 SOURCE,CLASS,RUNTIME 2) @Target // 指定注解可以在哪些地方使用 3) @Documented //指定该注解是否会在 javadoc 体现 4) @Inherited //子类会继承父类注解
@Retention 注解
表示只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 可以保留多长时间, @Rentention 包含一个 RetentionPolicy类型的成员变量, 使用 @Rentention 时必须为该 value 成员变量指定值:
@Retention() 的三种值 1) RetentionPolicy.SOURCE: 编译器使用后,直接丢弃这种策略的注释 2) RetentionPolicy.CLASS: 编译器将把注解记录在 class 文件中. 当运行 Java 程序时, JVM 不会保留注解。 这是默认值
3) RetentionPolicy.RUNTIME:编译器将把注解记录在 class 文件中. 当运行 Java 程序时, JVM 会保留注解. 程序可以 通过反射获取该注解
源码
@Target
@Documented
@Inherited
异常Exception 错误Error
Throwable异常根类 也是object子类,类似丞相
Throwable
基本概念
出现一个异常,就会导致整个系统崩溃
-
Java语言中,将程序执行中发生的不正常情况称为异常。(开发过程中的语法错误和逻辑错误不是异常)
-
Error(错误):Java虚拟机无法解决的严重问题。(如:JVM系统内部错误,资源耗尽等严重情况(StackOverflowError[栈溢出]和OOM(out of memory),Error是严重问题,程序会崩溃))
-
Exception:其他编程错误或偶然外在因素导致的一般性问题,可以使用针对性代码处理。列如空指针访问,读取不存在文件,网络连接中断。
注意事项:1.运行时期异常抛不掉,只能通过修改代码解决。 2.当一段代码中有多个异常时,又没有处理时,只会显示第一个异常信息。 3.可以同时抛多个异常,异常类型之间用逗号分隔 4.可以直接抛多个异常的父类,代替抛这些子类 RuntimeException a = new NullPointerException(); RuntimeException b = new ArithmeticException(); RuntimeException c = new ArrayIndexOutOfBoundsException(); 5.throws一般解决的时编译时期异常。
错误Error
StackOverflowError——栈溢出
OutOfMemoryError——内存不足
运行时异常RuntimeException
概念:
编译器检查不出来,一般是编程时逻辑错误。程序员应该避免这种错误
对于这类异常,可以不做处理
常见(子)
1.NullPointerException——空指针异常
需要对象的地方却是null时抛出
2.ArithmeticException——算数异常
数学算数(4/0)抛出
3.ArrayIndexOutBoundsException——数组下标索引越界异常
数组长度为5,下标等于或超过了5时抛出
4.ClassCastException——类型转换异常
将对象强转为不是实例的子类时抛出(C c = (C)b)
5.NumberFormatException——数字格式异常
将字符串转换成基本数据类型时抛出
编译时异常
概念:
常见
编译器要求必需处理的异常;---try-catch
FileNotFoundException
ClassNotFoundException
异常处理(捕捉,抛出,自定义))
非检查异常(unckecked exception):Error 和 RuntimeException 以及他们的子类。
javac在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。所以如果愿意,我们可以编写代码处理(使用try…catch…finally)这样的异常,也可以不处理。对于这些异常,我们应该修正代码,而不是去通过异常处理器处理 。这样的异常发生的原因多半是代码写的有问题。如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。
检查异常(checked exception):除了Error 和 RuntimeException的其它异常。
javac强制要求程序员为这样的异常做预备处理工作(使用try…catch…finally或者throws)。在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序,于是程序员就应该为这样的异常时刻准备着。如SQLException , IOException,ClassNotFoundException 等。 需要明确的是:检查和非检查是对于javac来说的,这样就很好理解和区分了。 ———————————————— 原文链接:Java基础之异常处理(try...catch....finally ,throw,throws)_奋斗的哼哼-CSDN博客
try-catch-finally(CTRL+ALT+T)
发生异常JVM会进行一系列处理(底层
系统将异常封装成Exception 对象 e,传递给catch
异常捕获
-
try-catch
-
如果异常发生了,则try异常发生后面的代码不会执行,直接进入到 catch 块 程序
-
如果异常没有发生,则顺序执行 try 的代码块,不会进入到 catch
-
-
try-catch-finally
如果希望不管是否发生异常,最后都执行-finally段代码(比如关闭连接,释放资源等)-
除非(System.exit(1))退出程序finally才不执行
细节:
含有return语句的话,catch内的return会临时储存结果,最后返回含有return语句的方法体。都含有的话,只会返回finally内的return;
-
try-catch-catch-catch-finally
-
如果 try 代码块有可能有多个异常 可以使用多个 catch 分别捕获不同的异常,相应处理
要求子类异常写在前面,父类异常写在后面 iub
执行流程: * 1.当try中没有异常时:try之前 --> try --> finally --> finally后面的代码 * 2.当try中有异常时但是catch没有捕获该异常时:try之前 --> try中异常产生前 --> finally * 3.当try中有异常时但是catch捕获该异常时: * try之前 -->try中异常产生前 -->catch --> finally -->finally后面的代码 * * 剩余:try...catch...finally 的注意事项 * * 注意事项: * 1.catch()中定义的异常必须跟try中产生的异常类型一致,要不然抓取不到该异常,catch{}中的代码就不会执行 * 2.可以有多个catch,try产生的异常对象和哪一个catch中定义的异常类匹配,就执行哪一个catch{}中的代码 * 3.当有多个catch时,一般把定义的大的范围的异常的catch代码块放在最下面。 * 4.finally代码块只在jvm退出时,不执行,其他情况都执行。
-
-
try-finally 善后工作
相当于没有捕获异常,可能直接崩掉/退出。不管try是否发生异常,finally内必须执行某个业务逻辑,
异常方法:Exception e
-
e.getMessage()。sout方法-获得异常产生的原因 的方法String
-
e.printStackTrace();方法-打印异常信息(类型)、产生的原因、异常位置 的方法void
-
e.toString();方法-打印异常类型和产生的原因String
throws
概念
获取异常并抛出給该方法的调用者(方法)来处理!!必须处理或再次抛出
细节
1.对于编译异常,程序中必须处理,比如 try-catch 或者 throws
2.对于运行时异常,程序中如果没有处理,默认就是 throws ---> main方法有默认的throws
3.子类重写父类的方法时或调用方法时,对抛出异常的规定:子类重写的方法, 所抛出的异常要么一致,要么为其子类型;
4.在 throws 过程中,如果有方法 try-catch , 就相当于处理异常,就可以不必 throw
5.可以抛出多个异常
public void f() throws FileNotFoundException,NullPointerException,ArithmeticException {} // 或throws Exception{}
自定义异常类
一般情况下,我们自定义异常是继承 RuntimeException
即把自定义异常做成运行时异常,好处是,我们可以使用默认的处理机制即比较方便
继承Exception属于编译异常
继承RuntimeException属于运行异常
throw new NullPointerException("参数个数不对");
public class CustomException { public static void main(String[] args) /*throws AgeException*/ { int age = 180; //要求范围在 18 – 120 之间,否则抛出一个自定义异常 if(!(age >= 18 && age <= 120)) { //这里我们可以通过构造器,设置信息 throw new AgeException("年龄需要在 18~120 之间"); } System.out.println("你的年龄范围正确."); } } //自定义一个异常 //老韩解读 //1. 一般情况下,我们自定义异常是继承 RuntimeException //2. 即把自定义异常做成 运行时异常,好处时,我们可以使用默认的处理机制 //3. 即比较方便 class AgeException extends RuntimeException { public AgeException(String message) {//构造器 super(message); } }
throw
而throw使用在函数内。抛出的是异常对象。
throw new AgeException();
常用类(多读源码)
包装类Wrapper(final)
Diagrams结构 CTRL+ALT+B("子") CTRL+ALT+P("父")
概念
为什么需要包装类:JAVA是面向对象的语言,很多类和方法中的参数都需使用对象(例如集合),但基本数据类型却不是面向对象的,这就造成了很多不便
为了解决该问题,我们引入了包装类,顾名思义,就是将基本类型“包装起来“,使其具备对象的性质,包括可以添加属性和方法,位于java.lang包下。
拆装箱的概念:将基本数据类型转为包装类的过程叫“装箱”,将包装类转为基本数据类型的过程叫“拆箱”
自动拆装箱:Java为了简便拆箱与装箱的操作,提供了自动拆装箱的功能,
对于Integer,
拆箱的过程就是通过Integer 实体调用intValue()方法;
装箱的过程就是调用了 Integer.valueOf(int i) 方法,帮你直接new了一个Integer对象
建议使用valueOf() 方法创建一个包装类实例而不是直接使用构造方法,因为该方法可以走缓存提高性能
———————————————— 原文链接:Java数据类型—包装类_【CSDN官方推荐】-CSDN博客
八种数据类型的包装类 final 类
boolean --> boolean char --> Character byte --> Byte short --> Short int --> Integer long --> Long float --> Float double --> Double
包装类和基本数据的转换
自动装箱\拆箱
int n2 = 200; //自动装箱 int->Integer Integer integer2 = n2; //底层使用的是 Integer.valueOf(n2) //自动拆箱 Integer->int int n3 = integer2; //底层仍然使用的是 integer2.intValue()方法
public static void main(String[] args) { //演示 int <--> Integer 的装箱和拆箱 //jdk5 前是手动装箱和拆箱 //手动装箱 int->Integer int n1 = 100; Integer integer = new Integer(n1); Integer integer1 = Integer.valueOf(n1); //手动拆箱 //Integer -> int int i = integer.intValue(); //==================================================== //jdk5 后,就可以自动装箱和自动拆箱 int n2 = 200; //自动装箱 int->Integer Integer integer2 = n2; //底层使用的是 Integer.valueOf(n2) //自动拆箱 Integer->int int n3 = integer2; //底层仍然使用的是 integer.intValue()方法 }
包装类与字符串的转换
Interger ---> String Interger = 100; 方法一: String str1 = i+""; //只是重新复值给了String类型 方法二: String str2 = i.toString(); 方法三: String str3 =String.valueOf(i); String ---> Interger 方法一: Integer i1 = Integer.parseInt(str); 方法二: Integer i2 = Integer.valueOf(str); 方法三: Integer i3 = new Integer(str);
包装类的常用方法:自己查
Integer:
方法 | 返回值 | 功能 |
---|---|---|
byteValue() | byte | 以 byte 类型返回该 Integer 的值 |
shortValue() | short | 以 short 类型返回该 Integer 的值 |
intValue() | int | 以 int 类型返回该 Integer 的值 |
toString() | String | 返回一个表示该 Integer 值的 String 对象 |
equals(Object obj) | boolean | 比较此对象与指定对象是否相等 |
compareTo(Integer anotherlnteger) | int | 在数字上比较两个 Integer 对象,如相等,则返回 0; 如调用对象的数值小于 anotherlnteger 的数值,则返回负值; 如调用对象的数值大于 anotherlnteger 的数值,则返回正值 |
valueOf(String s) | Integer | 返回保存指定的 String(参数也可以是int) 值的 Integer 对象 |
parseInt(String s) | int | 将数字字符串转换为 int 数值 |
Double:
方法 | 返回值 | 功能 |
---|---|---|
byteValue() | byte | 以 byte 类型返回该 Double 的值 |
doubleValue() | double | 以 double 类型返回该 Double 的值 |
fioatValue() | float | 以 float 类型返回该 Double 的值 |
intValue() | int | 以 int 类型返回该 Double 的值(强制转换为 int 类型) |
longValue() | long | 以 long 类型返回该 Double 的值(强制转换为 long 类型) |
shortValue() | short | 以 short 类型返回该 Double 的值(强制转换为 short 类型) |
isNaN() | boolean | 如果此 Double 值是一个非数字值,则返回 true,否则返回 false |
isNaN(double v) | boolean | 如果指定的参数是一个非数字值,则返回 true,否则返回 false |
toString() | String | 返回一个表示该 Double 值的 String 对象 |
valueOf(String s) | Double | 返回保存指定的 String 值的 Double 对象 |
parseDouble(String s) | double | 将数字字符串转换为 Double 数值 |
Character:
方法 | 描述 |
---|---|
void Character(char value) | 构造一个新分配的 Character 对象,用以表示指定的 char 值 |
char charValue() | 返回此 Character 对象的值,此对象表示基本 char 值 |
int compareTo(Character anotherCharacter) | 根据数字比较两个 Character 对象 |
boolean equals(Character anotherCharacter) | 将此对象与指定对象比较,当且仅当参数不是 null,而 是一个与此对象 包含相同 char 值的 Character 对象时, 结果才是 true |
boolean isDigit(char ch) | 确定指定字符是否为数字,如果通过 Character. getType(ch) 提供的字 符的常规类别类型为 DECIMAL_DIGIT_NUMBER,则字符为数字 |
boolean isLetter(int codePoint) | 确定指定字符(Unicode 代码点)是否为字母 |
boolean isLetterOrDigit(int codePoint) | 确定指定字符(Unicode 代码点)是否为字母或数字 |
boolean isLowerCase(char ch) | 确定指定字符是否为小写字母 |
boolean isUpperCase(char ch) | 确定指定字符是否为大写字母 |
char toLowerCase(char ch) | 使用来自 UnicodeData 文件的大小写映射信息将字符参数转换为小写 |
char toUpperCase(char ch) | 使用来自 UnicodeData 文件的大小写映射信息将字符参数转换为大写 |
Blooean:
方法 | 返回值 | 功能 |
---|---|---|
booleanValue() | boolean | 将 Boolean 对象的值以对应的 boolean 值返回 |
equals(Object obj) | boolean | 判断调用该方法的对象与 obj 是否相等。当且仅当参数不是 null,且与调用该 方法的对象一样都表示同一个 boolean 值的 Boolean 对象时,才返回 true |
parseBoolean(String s) | boolean | 将字符串参数解析为 boolean 值 |
toString() | string | 返回表示该 boolean 值的 String 对象 |
valueOf(String s) | boolean | 返回一个用指定的字符串表示的 boolean 值 |
System.out.println(Integer.MIN_VALUE); //返回最小值 System.out.println(Integer.MAX_VALUE);//返回最大值 System.out.println(Character.isDigit('a'));//判断是不是数字 System.out.println(Character.isLetter('a'));//判断是不是字母 System.out.println(Character.isUpperCase('a'));//判断是不是大写 System.out.println(Character.isLowerCase('a'));//判断是不是小写 System.out.println(Character.isWhitespace('a'));//判断是不是空格 System.out.println(Character.toUpperCase('a'));//转成大写 System.out.println(Character.toLowerCase('A'));//转成小写
Integer x = 128; Integer y = 1;
类加载就会创造好 -127-128
如果 i 在 IntegerCache.low(-128)~IntegerCache.high(127),就直接从数组返回
如果不在 -128~127,就会 new Integer(i)
Integer i11=127; int i12=127;
Integer i11=128; int i12=127;
只要有基本数据类型,判断的都是值是否相同 System.out.println(i11==i12); True
String类(final)
概念:
String 是 final 类,不能被其他的类继承
不可改变字符序列 一个字符串对象一旦被分配,内容不可变。
有属性 private final char value[]; 本质还是Char数组:
堆中创建空间 里面维护了value属性 用于存放字符串内容的引用 实际在常量区
一定要注意:value 是一个 final 类型, 不可以修改(地址)(需要功力):即 value 不能指向新的地址,但是单个字符内容是可以变化
字符串的字符使用 Unicode 字符编码,一个字符(不区分字母还是汉字)占两个字节
String 类有很多构造器,构造器的重载
String 类实现了
Serializable【String 可以串行化:可以在网络传输】
Comparable [String 对象可以比较大小]
常量相加看的是池,有变量相加看的是堆;str = “”+b; b如果是final修饰的会在常量池,那么也是直接指向池
方法里不加this的String,在常量池找不着会新创建String并指向
String str = new String(“hsp”);//=”hsp“也一样 public void change(String str) { str = "java";//常量池找不着创建新常量 }
创建and内存分布:
创建String 对象的两种方式:
方式一:String s = “hspedu”;//s=“haha”;
方式二:String s2 =new String(“hspedu“);
创建String都会查找常量池有没有指定数据。
法一: 有就直接指向池 没有就创建,不会改变
法二: 堆中创建空间(就是创建对象) 里面维护了value属性 有就通过value指向池
private final char value[];
.intern(): 直接找常量池池中有就返回,没有就创建再返回
内存分布:
String常见方法:
equals 前面已经讲过了. 比较内容是否相同,区分大小写
equalsIgnoreCase 忽略大小写的判断内容是否相等
查
indexOf("h") 获取字符或字符串在字符串对象中第一次出现的索引,索引从 0 开始,如果找不到,返回-1
indexOf("h",x)从索引x开始查
a = str1.indexOf(str2,(a+str2.length()-1));//第一次出现的位置a(加上str2的长度),从a截取后再次判断lastIndexOf("l") 获取字符或字符串在字符串中最后一次出现的索引,索引从 0 开始,如果找不到,返回-1
lastIndexOf("l",x)到索引x结束
细节:fafa lastIndexOf("fa",2)->faf 也会找到第二个fa 返回2
int x=0;int y = c.indexOf("s",x);if (y==-1){break;}x=y+1;System.out.print(y+" ");} chars = c.toCharArray();for (int i = 0; i < chars.length; i++) {if (chars[i]=='s'){System.out.print(i+" ");}}substring(2,5)或(6) 截取指定范围的子串2-5 或 6到末尾
trim 去前后空格
char charAt(int) 获取某索引处字符
startsWith(),endsWith()判断此字符串是否以指定的前\后缀开始\结束
contains(), 判断是否包含此字符串
=拼接=替换=分割=转换=比较[char]大小=格式化=====
toUpperCase 转换成大写 toLowerCase 转换成小写
concat 拼接字符串
String replace(str,str2) 替换字符串中的字符 返回字符串需要接收
split(" +") 分割字符串, 对于某些分割字符,返回字符串数组 +可多个空格
return s.split(ss).length-1; ss在s出现多少次matches正则
toCharArray 字符串转换成字符数组,返回字符数组
.valueOf(chars);转字符串(构造方法也可)
"xyz".compareTo("xzy") 比较两个字符串的大小(char)x+(char)y+(char)z,如果前者大,则返回正数,后者大,则返回负数,如果相等,返回 0
(1) 如果长度相同,并且每个字符也相同,就返回 0
(2) 如果前面的部分都相同,就返回 str1.len - str2.len
(3) 如果长度相同或者不相同,进行比较时取出每个对应下标的字符进行比较,相同就下标加一,不同就返回并弹出 如cc - ca==c-a的as码 cd - ac == c-a
format 格式字符串
%s , %d , %.2f %c 称为占位符 这些占位符由后面变量来替换
%s 表示后面由字符串来替换
%d 是整数来替换
%.2f 表示使用小数来替换,替换后,只会保留小数点两位, 并且进行四舍五入的处理
%c 使用 char
double d = 123.456789; String c = String.format("%.2f",d);String formatStr = "我的姓名是%s 年龄是%d,成绩是%.2f 性别是%c.希望大家喜欢我!";
例:String info2 = String.format(formatStr, name, age, score, gender);
StringBuffer:
概念:
StringBuffer 的直接父类 是 AbstractStringBuilder
StringBuffer 实现了
Serializable, 即 StringBuffer 的对象可以串行化:可以在网络传输
在父类中 AbstractStringBuilder
有属性 char[] value,本质还是Char数组:
产生的字符串是变量
堆中创建空间 里面维护了value属性 用于存放字符串内容
StringBuffer 是一个 final 类,不能被继承
因为 StringBuffer 字符内容是存在 char[] value, 所有在变化(增加/删除),不用每次都更换地址(即不是每次创建新对象), 所以效率高于 String
字符串变量 扩容机制(16)
String StringBuffer转换and内存分布:
String——>StringBuffer
方式 1: 使用构造器
注意: 返回的才是 StringBuffer 对象,对 str 本身没有影响 StringBuffer stringBuffer = new StringBuffer(str);
方式 2 :使用的是 append 方法
StringBuffer stringBuffer1 = new StringBuffer(); stringBuffer1 = stringBuffer1.append(str);
StringBuffer ->String
StringBuffer stringBuffer3 = new StringBuffer("韩顺平教育");
方式 1: 使用构造器来搞定
String s1 = new String(stringBuffer3);
方式 2: 使用 StringBuffer 提供的 toString 方法
String s = stringBuffer3.toString();
String VS StringBuffer
StringBuffer常用方法:
增 append() "" String
删 delete() [0,2)
改 replace() [2,3)
查 indexOf() ("x") 返回指定字符(或字符串)的下标 无-1
charAt() (2) 返回指定下标的字符
substring (2,4) 返回指定下标范围的字符串
插 insert() (3,"x")
长 length()
反转 stringBuilder.reverse();
public static void main(String[] args) { StringBuffer s = new StringBuffer("hello"); //增 s.append(',');// "hello," s.append("张三丰");//"hello,张三丰" s.append("赵敏").append(100).append(true).append(10.5);//"hello,张三丰赵敏 100true10.5" System.out.println(s);//"hello,张三丰赵敏 100true10.5" //删 /* * 删除索引为>=start && <end 处的字符 * 解读: 删除 11~14 的字符 [11, 14) */ s.delete(11, 14); System.out.println(s);//"hello,张三丰赵敏 true10.5" //改 //老韩解读,使用 周芷若 替换 索引 9-11 的字符 [9,11) s.replace(9, 11, "周芷若"); System.out.println(s);//"hello,张三丰周芷若 true10.5" //查找 //指定的子串在字符串第一次出现的索引,如果找不到返回-1 int indexOf = s.indexOf("张三丰"); System.out.println(indexOf);//6 //下标 String s = s.charAt(2); //插 //老韩解读,在索引为 9 的位置插入 "赵敏",原来索引为 9 的内容自动后移 s.insert(9, "赵敏"); System.out.println(s);//"hello,张三丰赵敏周芷若 true10.5" //长度 System.out.println(s.length());//22 System.out.println(s); }
StringBuilder:
概念:用法一样,速度快,但是线程不安全。
StringBuilder 继承 AbstractStringBuilder 类
实现了 Serializable ,说明 StringBuilder 对象是可以串行化(对象可以网络传输,可以保存到文件)
StringBuilder 是 final 类, 不能被继承
StringBuilder 对象字符序列仍然是存放在其父类 AbstractStringBuilder 的 char[] value; // 因此,字符序列是堆中
StringBuilder 的方法,没有做互斥的处理,即没有 synchronized 关键字,因此在单线程的情况下使用 // StringBuilder StringBuilder stringBuilder = new StringBuilder();
StringBuilder>StringBuffer>String
Math(final)(静态方法)
Math常用的方法(静态方法)
abs 绝对值
int abs = Math.abs(-9);//9
pow 求幂
double pow = Math.pow(2, 4);//2的4次方//16
ceil 向上取整,返回>=该参数的最小整数(转成double);
double ceil = Math.ceil(3.9);//4.0
floor 向下取整,返回<=该参数的最大整数(转成double)
double floor = Math.floor(4.001);//4.0
round 四舍五入 Math.floor(该参数+0.5)
long round = Math.round(5.51);//6
sqrt 求开方
double sqrt = Math.sqrt(9.0);//3.0
random 求随机数
random 返回的是 0 <= x < 1 之间的一个随机小数
Math.random() * (b-a) 返回的就是 0 <= 数 <= b-a (int)(2 + Math.random()*6) = 2 <= x <= 7
(int)(a + Math.random() * (b-a +1) )
max , min 返回最大值和最小值
int min = Math.min(1, 9);
int max = Math.max(45, 90);
System.out.println("min=" + min);
System.out.println("max=" + max);
Pi
Arrays (静态方法)
常用方法(静态方法)
Arrays.toString() 遍历显示数组
sort 排序
这里体现了接口编程的方式 , 看看源码,就明白
binarySearch(arr,x) 通过二分搜索法进行查找,要求必须排好,不存在return -(low + 1)
copyOf(arr, arr.length) 数组元素的复制(长度不足会补足0或null)
fill(arr,x) 数组元素的全部填充为x
equals(arr,arrs) 比较两个数组元素内容是否完全一致
asList 将一组值,转换成 list
List asList = Arrays.asList(2,3,4,5,6,1);
源码分析
public static void main(String[] args) { Integer arr[] = {1, -1, 7, 0, 89}; //sort 重载的,也可以通过传入一个接口 Comparator 实现定制排序 //调用 定制排序 时,传入两个参数 (1) 排序的数组 arr //实现了 Comparator 接口的匿名内部类 , 要求实现 compare 方法 //先演示效果,再解释 //这里体现了接口编程的方式 , 看看源码,就明白 //(1) Arrays.sort(arr, new Comparator() //(2) 最终到 TimSort 类的 private static <T> void binarySort(T[] a, int lo, int hi, int start, // Comparator<? super T> c)() //(3) 执行到 binarySort 方法的代码, 会根据动态绑定机制 c.compare()执行我们传入的 // 匿名内部类的 compare () // while (left < right) { // int mid = (left + right) >>> 1; // if (c.compare(pivot, a[mid]) < 0) // right = mid; // else // left = mid + 1; // } //(4) new Comparator() { // @Override // public int compare(Object o1, Object o2) { // Integer i1 = (Integer) o1; // Integer i2 = (Integer) o2; // return i2 - i1; // } // } //(5) public int compare(Object o1, Object o2) 返回的值>0 还是 <0 // 会影响整个排序结果, 这就充分体现了 接口编程+动态绑定+匿名内部类的综合使用 // 将来的底层框架和源码的使用方式,会非常常见 //Arrays.sort(arr); // 默认排序方法 //定制排序 Arrays.sort(arr, new Comparator() { @Override public int compare(Object o1, Object o2) { Integer i1 = (Integer) o1; Integer i2 = (Integer) o2; return i2 - i1; } }); System.out.println("===排序后==="); System.out.println(Arrays.toString(arr));// }
冒泡定制排序
public static void main(String[] args) { int[] arr = {1, -1, 8, 0, 20}; //***************************** bubble(arr, new Comparator() { @Override public int compare(Object o1, Object o2) { int i1 = (Integer) o1; int i2 = (Integer) o2; return i2 - i1;// return i2 - i1; } }); System.out.println("==定制排序后的情况=="); System.out.println(Arrays.toString(arr)); } //结合冒泡 + 定制 public static void bubble(int[] arr, Comparator c) { int temp = 0; for (int i = 0; i < arr.length - 1; i++) { for (int j = 0; j < arr.length - 1 - i; j++) { //数组排序由 c.compare(arr[j], arr[j + 1])返回的值决定 if (c.compare(arr[j], arr[j + 1]) > 0) { temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } //==========用法================================================= public void sort(Employee[] all){ Arrays.sort(all, new Comparator<Employee>() { @Override public int compare(Employee o1, Employee o2) { return o1.age-o2.age; } });
System(静态方法)
常用方法:
exit(0) 退出当前程序
arraycopy 复制数组元素,比较适合底层调用,(底层代码
System.arraycopy(src, 0, dest, 0, src.length);从src的0位置 开始拷贝src.length个元素 到dest的0位置;
src:源数组
srcPos: 源数组的索引位置
dest : 目标数组
destPos: 目标数组的哪个索引位置
length: 拷贝多少个数据
一般使用 Arrays.copyOf 完成复制数组
currentTimeMillens: 返回当前时间距离 1970-1-1 的毫秒数
System.out.println(System.currentTimeMillis());
Scanner sc = new Scanner(System.in);//空格为分割符 sc.useDelimiter(" ");
sc.nextLine();读取整行数据,结束符只能是回车
BigInteger
long不够用
long l = 23788888899999999999999999999l;
//System.out.println("l=" + l);
BigInteger bigInteger = new BigInteger("23788888899999999999999999999");
BigInteger bigInteger2 new BigInteger("99999999999999999999999999999999999999999999999999999999999999999999999999999999999");
System.out.println(bigInteger);
加减乘除方法
在对 BigInteger 进行加减乘除的时候,需要使用对应的方法,不能直接进行 + - * / BigInteger add = bigInteger.add(bigInteger2); bigInteger.subtract(bigInteger2); bigInteger.multiply(bigInteger2); bigInteger.divide(bigInteger2);
BigDecimal
double不够用
double d = 1999.11111111111999999999999977788d; // System.out.println(d);
BigDecimal bigDecimal = new BigDecimal("1999.11"); BigDecimal bigDecimal2 = new BigDecimal("3"); System.out.println(bigDecimal);
加减乘除方法
//1. 如果对 BigDecimal 进行运算,比如加减乘除,需要使用对应的方法 //2. 创建一个需要操作的 BigDecimal 然后调用相应的方法即可 bigDecimal.add(bigDecimal2); bigDecimal.subtract(bigDecimal2); bigDecimal.multiply(bigDecimal2); bigDecimal.divide(bigDecimal2);//可能抛出异常 ArithmeticException //在调用 divide 方法时,指定精度即可. BigDecimal.ROUND_CEILING //如果有无限循环小数,就会保留 分子 的精度 bigDecimal.divide(bigDecimal2, BigDecimal.ROUND_CEILING);
Date
第一代日期类:(Date)
Date d1 = new Date();
格式化
SimpleDateFormat sdf = new SimpleDateFormat("yyyy 年 MM 月 dd 日 hh:mm:ss E"); sdf.format(d1);
//1. 获取当前系统时间 //2. 这里的 Date 类是在 java.util 包 //3. 默认输出的日期格式是国外的方式, 因此通常需要对格式进行转换 Date d1 = new Date(); //获取当前系统时间 System.out.println("当前日期=" + d1); Date d2 = new Date(9234567); //通过指定毫秒数得到时间 System.out.println("d2=" + d2); //获取某个时间对应的毫秒数 -----------格式化--------------- //老韩解读 //1. 创建 SimpleDateFormat 对象,可以指定相应的格式 //2. 这里的格式使用的字母是规定好,不能乱写 SimpleDateFormat sdf = new SimpleDateFormat("yyyy 年 MM 月 dd 日 hh:mm:ss E"); String f = sdf.format(d1); // format:将日期转换成指定格式的字符串 System.out.println("当前日期=" + f); -----------String->Date---------------- //老韩解读 //1. 可以把一个格式化的 String 转成对应的 Date //2. 得到 Date 仍然在输出时,还是按照国外的形式,如果希望指定格式输出,需要转换 //3. 在把 String -> Date , 使用的 sdf 格式需要和你给的 String 的格式一样,否则会抛出转换异常 String s = "1996 年 01 月 01 日 10:20:30 星期一"; Date p = sdf.parse(s); System.out.println("parse=" + sdf.format(p));//格式化输出
第二代日期类(Calendar)(自由,组合)
Calendar c = Calendar.getInstance();//受保护构造器
System.out.println("年:" + c.get(Calendar.YEAR));。。。。。。。。。。
//老韩解读 //1. Calendar 是一个抽象类, 并且构造器是 private //2. 可以通过 getInstance() 来获取实例 //3. 提供大量的方法和字段提供给程序员 //4. Calendar 没有提供对应的格式化的类,因此需要程序员自己组合来输出(灵活) //5. 如果我们需要按照 24 小时进制来获取时间, Calendar.HOUR ==改成=> Calendar.HOUR_OF_DAY Calendar c = Calendar.getInstance(); //创建日历类对象//比较简单,自由 System.out.println("c=" + c); //2.获取日历对象的某个日历字段 --------------------单个------------------------- System.out.println("年:" + c.get(Calendar.YEAR)); // 这里为什么要 + 1, 因为 Calendar 返回月时候,是按照 0 开始编号 System.out.println("月:" + (c.get(Calendar.MONTH) + 1)); System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH)); System.out.println("小时:" + c.get(Calendar.HOUR)); System.out.println("分钟:" + c.get(Calendar.MINUTE)); System.out.println("秒:" + c.get(Calendar.SECOND)); ---------------------组合--------------------------------------- //Calender 没有专门的格式化方法,所以需要程序员自己来组合显示 System.out.println(c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) + 1) + "-" +c.get(Calendar.DAY_OF_MONTH) +" " + c.get(Calendar.HOUR_OF_DAY) + ":" + c.get(Calendar.MINUTE) + ":" + c.get(Calendar.SECOND) );
第三代日期类(LocalDateTime)
LocalDateTime ldt = LocalDateTime.now()
格式化
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); dateTimeFormatter.format(ldt);
//老韩解读 //1. 使用 now() 返回表示当前日期时间的 对象 LocalDateTime ldt = LocalDateTime.now(); //LocalDate.now();//LocalTime.now() System.out.println(ldt); ------------------格式化------------------------- //2. 使用 DateTimeFormatter 对象来进行格式化 // 创建 DateTimeFormatter 对象 DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String format = dateTimeFormatter.format(ldt); System.out.println("格式化的日期=" + format); ------------------单个----------------------------- System.out.println("年=" + ldt.getYear()); System.out.println("月=" + ldt.getMonth()); System.out.println("月=" + ldt.getMonthValue()); System.out.println("日=" + ldt.getDayOfMonth()); System.out.println("时=" + ldt.getHour()); System.out.println("分=" + ldt.getMinute()); System.out.println("秒=" + ldt.getSecond()); -------------------年 天-------------------------- LocalDate now = LocalDate.now(); //可以获取年月日 LocalTime now2 = LocalTime.now();//获取到时分秒 --------------------加减-------------------------- 时间进行加或者减 //提供 plus 和 minus 方法可以对当前时间进行加或者减 //看看 890 天后,是什么时候 把 年月日-时分秒 LocalDateTime localDateTime = ldt.plusDays(890); System.out.println("890 天后=" + dateTimeFormatter.format(localDateTime)); //看看在 3456 分钟前是什么时候,把 年月日-时分秒输出 LocalDateTime localDateTime2 = ldt.minusMinutes(3456); System.out.println("3456 分钟前 日期=" + dateTimeFormatter.format(localDateTime2));
时间戳Instant
//1.通过 静态方法 now() 获取表示当前时间戳的对象 Instant now = Instant.now(); System.out.println(now); //2. 通过 from 可以把 Instant 转成 Date Date date = Date.from(now); //3. 通过 date 的 toInstant() 可以把 date 转成 Instant 对象 Instant instant = date.toInstant()
集合
Iterable() | ||||
---|---|---|---|---|
Collection() | ||||
List(有序) | Set(无序) | |||
Vector | ArrayList | LinkedList | HashSet | TreeSet |
LinkedHashSet |
Map() | ||
---|---|---|
HashMap | TreeMap | Hashtable |
LinkedHashMap | properties |
概念:
动态存储任意对象,有一系列方法。
Collection 单列集合
Map 双列集合
Collection 接口的接口 对象的集合(单列集合) ├——-List 接口:元素按进入先后有序保存,可重复 │—————-├ LinkedList 接口实现类, 链表, 插入删除, 没有同步, 线程不安全 │—————-├ ArrayList 接口实现类, 数组, 随机访问, 没有同步, 线程不安全 │—————-└ Vector 接口实现类 数组, 同步, 线程安全 │ ———————-└ Stack 是Vector类的实现类 └——-Set 接口: 仅接收一次,不可重复,并做内部排序 ├—————-└HashSet 使用hash表(数组)存储元素 │————————└ LinkedHashSet 链表维护元素的插入次序 └ —————-TreeSet 底层实现为二叉树,元素排好序
Map 接口 键值对的集合 (双列集合) ├———Hashtable 接口实现类, 同步, 线程安全 ├———HashMap 接口实现类 ,没有同步, 线程不安全- │—————–├ LinkedHashMap 双向链表和哈希表实现 │—————–└ WeakHashMap ├ ——–TreeMap 红黑树对所有的key进行排序 └———IdentifyHashMap ————————————————
Collection(有序单列集合的接口)
概念:
特点:
Collection接口是单列集合的最顶层接口,定义了一些通用的方法。
方法:
add(E e)添加元素;
clear()清空元素;
remove(E e)移除元素;
size()元素数量;
toArray()集合转数组;
contains(E e)判断元素是否存在;
isEmpty()判断集合是否为空;
containsAll():查找多个元素是否都存在
————————————————
Collections类
排序:List可排序
Collections.sort(list, new Comparator<Book>() { @Override
}
添加多个元素到集合addAll(list,"凡凡","霍尊","潘帅","林俊杰");
也可通过构造器添加Set<String> hashSet = new HashSet<>(list);
books.removeIf(b -> (b.getPress()).equals("清华大学出版社"));//删除
reverse(List):反转 List 中元素的顺序 Collections.reverse(list);
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)Object min(Collection,Comparator)参考max即可
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
为了完成一个完整拷贝,我们需要先给dest 赋值,大小和list.size()一样
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值 //如果list中,有tom 就替换成 汤姆
public static void main(String[] args) { //创建ArrayList 集合,用于测试. List list = new ArrayList(); list.add("tom"); list.add("smith"); list.add("king"); list.add("milan"); list.add("tom"); // reverse(List):反转 List 中元素的顺序 Collections.reverse(list); System.out.println("list=" + list); // shuffle(List):对 List 集合元素进行随机排序 // for (int i = 0; i < 5; i++) { // Collections.shuffle(list); // System.out.println("list=" + list); // } // sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序 Collections.sort(list); System.out.println("自然排序后"); System.out.println("list=" + list); // sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序 //我们希望按照 字符串的长度大小排序 Collections.sort(list, new Comparator() { @Override public int compare(Object o1, Object o2) { //可以加入校验代码. return ((String) o2).length() - ((String) o1).length(); } }); System.out.println("字符串长度大小排序=" + list); // swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换 //比如 Collections.swap(list, 0, 1); System.out.println("交换后的情况"); System.out.println("list=" + list); //Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素 System.out.println("自然顺序最大元素=" + Collections.max(list)); //Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素 //比如,我们要返回长度最大的元素 Object maxObject = Collections.max(list, new Comparator() { @Override public int compare(Object o1, Object o2) { return ((String)o1).length() - ((String)o2).length(); } }); System.out.println("长度最大的元素=" + maxObject); //Object min(Collection) //Object min(Collection,Comparator) //上面的两个方法,参考max即可 //int frequency(Collection,Object):返回指定集合中指定元素的出现次数 System.out.println("tom出现的次数=" + Collections.frequency(list, "tom")); //void copy(List dest,List src):将src中的内容复制到dest中 ArrayList dest = new ArrayList(); //为了完成一个完整拷贝,我们需要先给dest 赋值,大小和list.size()一样 for(int i = 0; i < list.size(); i++) { dest.add(""); } //拷贝 Collections.copy(dest, list); System.out.println("dest=" + dest); //boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值 //如果list中,有tom 就替换成 汤姆 Collections.replaceAll(list, "tom", "汤姆"); System.out.println("list替换后=" + list); } }
//泛型 Comparator匿名内部类排序 List<Book> list = new ArrayList<>(); //List<Book> list = new LinkedList<>(); //List<Book> list = new Vector<>(); Collections.sort(list, new Comparator<Book>() { @Override public int compare(Book o1, Book o2) { double a = o1.getPrice()-o2.getPrice(); if (a>0){ return 1; }else if (a<0){ return -1; }else { return 0; } } }); //自定义 public static void sort(List list) { int listSize = list.size();//长度 for (int i = 0; i < listSize - 1; i++) { for (int j = 0; j < listSize - 1 - i; j++) { //取出对象Book Book book1 = (Book) list.get(j); Book book2 = (Book) list.get(j + 1); if (book1.getPrice() > book2.getPrice()) {//交换 list.set(j, book2); list.set(j + 1, book1); } } } }
List
概念:
List 集合类中元素有序(即添加顺序和取出顺序一致)、且可重复(null)
List 集合中的每个元素都有其对应的顺序索引,即支持索引
构造方法可以直接传集合进行复制元素;Set<String> linkedHashSet = new HashSet<>(list);
特殊方法
get(int index,E e) 获取指定位置的元素;get(int)
books.removeIf(b -> (b.getPress()).equals("清华大学出版社"));//删除
remove(int index)移除指定位置的元素,并返回; remove(Object)
add(int index,E e) 将元素添加插入到指定位置;
set(int index,E e) 用元素修改替换指定位置的元素;
*int indexOf(Object obj):返回obj在集合中首次出现的位置
*int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
*List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合 左开右闭[a,b)
ArrayList
概念:
结构(数组)
像String类似,ArrayList里维护了一个Object类型的数组elementData
transient Obiect[] elementData;// transient表示不会被序列化
和Vector一样,区别是Vector是线程安全
特点:
查询快,增删慢,
主要用于查询遍历数据,为最常用集合之一;
线程不安全
底层分析:
数组结构是有序的元素序列,在内存中开辟一段连续的空间,在空间中存放元素,每个空间都有编号,通过编号可以快速找到相应元素,因此查询快;数组初始化时长度是固定的,要想增删元素,必须创建一个新数组,把源数组的元素复制进来,随后源数组销毁,耗时长,因此增删慢。
创建ArrayList对象时,
如果用的无参构造器,则初始elementData容量为0,第一次添加数据,则扩容为10.后续扩容为1.5倍;
如果使用有参构造器,则初始elementData容量为指定大小,后续扩容为1.5倍;
//permits all elements,including null,ArrayList 可以加入多个null;
内存储的是包装类,
创建,扩容流程
装箱->add->判断是否需要扩容->(无参第一次扩10)->(扩容方法)->添加数据到elementData
Vector(基本不用)
概念:
结构(数组)
Vector里也维护了一个Object类型的数组elementData
Protected Obiect[] elementData;
特点:
线程安全(同步)synchronized
查询快,增删慢
Vector可以设置增长因子,而ArrayList不可以。
Vector是一种老的动态数组,是线程同步的,效率很低,一般不赞成使用。
底层分析:
和ArrayList一样,都是数组实现,因此具有相似的特性,它们之间的区别在于Vector是线程安全的,效率低,ArrayList是线程不安全的,但效率高。
ps:Vector在JDK1.0就出现了,在JDK1.2集合出现的时候,Vector就归为List的实现类之一,这时候ArrayList才出现。Vector是一个古老的集合,《Java编程思想》中提到了它有一些遗留的缺点,因此不建议使用。
创建Vector对象
如果用的无参构造器(内还是调用的有参构造器),则初始elementData容量为10,后续扩容为2倍;
如果使用有参构造器,则初始elementData容量为指定大小,后续扩容为2倍;
源码:
public static void main(String[] args) { //无参构造器 //有参数的构造 Vector vector = new Vector(8); for (int i = 0; i < 10; i++) { vector.add(i); } vector.add(100); System.out.println("vector=" + vector); //老韩解读源码 //1. new Vector() 底层 /* public Vector() { this(10); } 补充:如果是 Vector vector = new Vector(8); 走的方法: public Vector(int initialCapacity) { this(initialCapacity, 0); } 2. vector.add(i) 2.1 //下面这个方法就添加数据到vector集合 public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; } 2.2 //确定是否需要扩容 条件 : minCapacity - elementData.length>0 private void ensureCapacityHelper(int minCapacity) { // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } 2.3 //如果 需要的数组大小 不够用,就扩容 , 扩容的算法 //newCapacity = oldCapacity + ((capacityIncrement > 0) ? // capacityIncrement : oldCapacity); //就是扩容两倍. private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } */ }
LinkedList
概念:
结构(双向链表)
底层维护了一个双向链表
LinkedList中维护了两个属性first和last分别指向首节点和尾结点每个节点(Node对象),
里面又维护了prev、next、item三个属性
#final Node<E> newNode = new Node<>(prev, item, next);
prev链接前一个节点,next链接后一个节点,item存储的内容
特点:
LinkedList底层实现了双向链表和双端队列特点
线程不安全
查询慢,增删快();
底层分析:
链表分为单向和双向,就是一条链子和两条链子的区别;
多出的那条链子记录了元素的顺序,因此单向链表结构无序,双向链表结构有序;
链表结构没有索引,因此查询慢;没有get(int)
链表的增删只需在原有的基础上连上链子或切断链子,因此增删快。
特有方法:
getFirst() 返回开头元素;
getLast() 返回结尾元素;
pop() 从所在堆栈中获取一个元素;
push(E e) 将元素推入所在堆栈;
addFirst(E e) 添加元素到开头,头插;
addLast(E e) 添加元素到结尾,尾插; ————————————————
增删改查CRUD底层
//演示一个添加指定位置结点的 linkedList.add(2, 6); //演示一个删除结点的 linkedList.remove(); // 这里默认删除的是第一个结点 //linkedList.remove(2); System.out.println("linkedList=" + linkedList); //修改某个结点对象 linkedList.set(1, 999); System.out.println("linkedList=" + linkedList); //得到某个结点对象 //get(1) 是得到双向链表的第二个对象 Object o = linkedList.get(1); System.out.println(o);//999 //因为LinkedList 是 实现了List接口, 遍历方式 System.out.println("===LinkeList遍历迭代器===="); Iterator iterator = linkedList.iterator(); *itit *foreach *for 源码解读: //老韩源码阅读. /* 1. LinkedList linkedList = new LinkedList(); public LinkedList() {} 2. 这时 linkeList 的属性 first = null last = null 3. 执行 添加 public boolean add(E e) { linkLast(e); return true; } 4.将新的结点,加入到双向链表的最后 void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++; } */ /* 老韩读源码 linkedList.remove(); // 这里默认删除的是第一个结点 1. 执行 removeFirst public E remove() { return removeFirst(); } 2. 执行 public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); } 3. 执行 unlinkFirst, 将 f 指向的双向链表的第一个结点拿掉 private E unlinkFirst(Node<E> f) { // assert f == first && f != null; final E element = f.item; final Node<E> next = f.next; f.item = null; f.next = null; // help GC first = next; if (next == null) last = null; else next.prev = null; size--; modCount++; return element; } */
结点的演示(简单模拟)
public class LinkedList01 { public static void main(String[] args) { //模拟一个简单的双向链表 Node jack = new Node("jack"); Node tom = new Node("tom"); Node hsp = new Node("老韩"); //连接三个结点,形成双向链表 //jack -> tom -> hsp jack.next = tom; tom.next = hsp; //hsp -> tom -> jack hsp.pre = tom; tom.pre = jack; //头尾 Node first = jack;//让first引用指向jack,就是双向链表的头结点 Node last = hsp; //让last引用指向hsp,就是双向链表的尾结点 //演示,从头到尾进行遍历 System.out.println("===从头到尾进行遍历==="); while (true) { if(first == null) { break; } //输出first 信息 System.out.println(first); first = first.next; } //演示,从尾到头的遍历 first->last //演示链表的添加对象/数据,是多么的方便 //要求,是在 tom --------- 老韩直接,插入一个对象 smith //1. 先创建一个 Node 结点,name 就是 smith Node smith = new Node("smith"); //下面就把 smith 加入到双向链表了 smith.next = hsp; smith.pre = tom; hsp.pre = smith; tom.next = smith; //让first 再次指向jack first = jack;//让first引用指向jack,就是双向链表的头结点 //遍历 } } //定义一个Node 类,Node 对象 表示双向链表的一个结点 class Node { public Object item; //真正存放数据 public Node next; //指向后一个结点 public Node pre; //指向前一个结点 public Node(Object name) { this.item = name; } public String toString() { return "Node name=" + item; } }
ArrayList VS Vector VS LinkedList
ArrayList与Vector的区别和适用场景
ArrayList有三个构造方法:
public ArrayList(int initialCapacity)//构造一个具有指定初始容量的空列表。 public ArrayList() //默认构造一个初始容量为10的空列表。 public ArrayList(Collection<? extends E> c)//构造一个包含指定 collection 的元素的列表
Vector有四个构造方法:
public Vector()//使用指定的初始容量和等于0的容量增量构造一个空向量。 public Vector(int initialCapacity)//构造一个空向量,使其内部数据数组的大小,其标准容量增量为零。 public Vector(Collection<? extends E> c)//构造一个包含指定 collection 中的元素的向量 public Vector(int initialCapacity,int capacityIncrement)//使用指定的初始容量和容量增量构造一个空的向量
ArrayList和Vector都是用数组实现的,主要有这么三个区别: (1).Vector是多线程安全的,线程安全就是说多线程访问同一代码,不会产生不确定的结果。而ArrayList不是,这个可以从源码中看出,Vector类中的方法很多有synchronized进行修饰,这样就导致了Vector在效率上无法与ArrayList相比; (2)两个都是采用的线性连续空间存储元素,但是当空间不足的时候,两个类的增加方式是不同。 (3)Vector可以设置增长因子,而ArrayList不可以。 (4)Vector是一种老的动态数组,是线程同步的,效率很低,一般不赞成使用。
适用场景分析:
1.Vector是线程同步的,所以它也是线程安全的,而ArrayList是线程异步的,是不安全的。如果不考虑到线程的安全因素,一般用ArrayList效率比较高。 2.如果集合中的元素的数目大于目前集合数组的长度时,在集合中使用数据量比较大的数据,用Vector有一定的优势。
ArrayList与LinkedList的区别和适用场景
Arraylist:
优点:ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。 缺点:因为地址连续, ArrayList要移动数据,所以插入和删除操作效率比较低。
LinkedList:
优点:LinkedList基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址,对于新增和删除操作add和remove,LinedList比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景 缺点:因为LinkedList要移动指针,所以查询操作性能比较低。
适用场景分析:
当需要对数据进行对此访问的情况下选用ArrayList,当需要对数据进行多次增加删除修改时采用LinkedList。
Set
概念:
特点:
无序(不能用索引方获取)(即添加的顺序和取出的顺序不一致)
没有索引,但是他的位置是固定的.
不允许重复元素(最多包含一个null)
无参构造器第一次添加时,table 数组扩容到 16, 临界值(threshold)是 16*加载因子(loadFactor)是0.75 = 12 如果table 数组使用到了临界值 12,就会扩容到 16 * 2 = 32, 新的临界值就是 32*0.75 = 24, 依次类推
方法
Set与Collection 接口中的方法基本一致,没有进行功能上的扩充;
ps:必须重写hashCode方法和equals方法,以保证key唯一。
HashSet
概念:
结构(数组+单向链表->红黑树)
HashMap底层数组+单向链表+红黑树
在java8中,如果一条链表的元素个数到达TREELFY_THRESHOLD(默认是8),并且table的大小>=Min_TREELFY_CAPACITY(默认64)就会树化
table 就是 HashMap 的一个数组,类型是 Node[] HashMap$Node[K,V]这里面V给定了常量
特点:
HashSet实际上是HashMap(底层就是(构造器)new HashMap对象)
查询快,元素无序(位置是固定的),元素不可重复(return false),没有索引;线程不安全
重写hashCode方法和equals方法,以保证对象唯一性
底层分析:
哈希表底层用数组+单向链表实现,
即使用链表处理冲突,同一Hash值的元素都存储在一个链表里,但是当位于一个链表中的元素较多,即Hash值相等的元素较多,通过key值依次查找的效率降低。
JDK1.8之后,哈希表底层采用数据+单向链表+红黑树实现,当链表长度超过阈值(8)时,并且table超过64,链表将转换为红黑树,极大缩短查询时间。
ps:哈希值是一个十进制的整数,是对象的地址值,是一个逻辑地址,不是实际存储的物理地址,由系统随机给出。Object类的int hashCode()方法,可以获取对象的哈希值。
创建一个HashSet对象 hashMap
底层维护了一个Node<K,V>类型的table数组
Set<Map.Entry<K,V>> entrySet; HashMap$Node
因为 static class Node<K,V> implements Map.Entry<K,V> interface Entry<K,V>
无参构造器第一次添加时,table 数组扩容到 16,resize(); 临界值(threshold)是 16*加载因子(loadFactor)是0.75 = 12 如果table 数组使用到了临界值 12,就会扩容到 16 * 2 = 32, 新的临界值就是 32*0.75 = 24, 依次类推
为什么不能重复?!!看底层机制!!!
add HashSet 底层add机制 hash()+equals())
添加一个元素时,先得到hash值,hash值转成索引
找到存取数据表table,判断索引位置有没有元素,
没有加入当前位置
有就调用equals(重写(String))比较,相同对象(固定哈希)不同成员,构成链表
相同就放弃添加
不同就以链表形式添加得到最后
在java8中,如果一条链表的元素个数到达TREELFY_THRESHOLD(默认是8),并且table的大小>=Min_TREELFY_CAPACITY(默认64)就会树化
到达8,每新添一个元素,table就会扩容一次16-32-64。
HashSet底层是HashMap, 第一次添加时,table 数组扩容到 16, 临界值(threshold)是 16*加载因子(loadFactor)是0.75 = 12(”**缓存**“) 如果table 数组使用到了临界值 12,就会扩容到 16 * 2 = 32, 新的临界值就是 32*0.75 = 24, 依次类推
table数组 (hashCode为"索引")链表 链表储存相同对象(固定哈希)不同成员,构成链表 for(int i = 1; i <= 7; i++) {//在table的另外一条链表上添加了 8个B对象 hashSet.add(new B(i));// }没有重写hashCode和equals x64
HashSet 底层机制说明
模拟底层:
public static void main(String[] args) { //模拟一个HashSet的底层 (HashMap 的底层结构) //1. 创建一个数组,数组的类型是 Node[] //2. 有些人,直接把 Node[] 数组称为 表 Node[] table = new Node[16]; //3. 创建结点 Node john = new Node("john", null); table[2] = john; Node jack = new Node("jack", null); john.next = jack;// 将jack 结点挂载到john Node rose = new Node("Rose", null); jack.next = rose;// 将rose 结点挂载到jack Node lucy = new Node("lucy", null); table[3] = lucy; // 把lucy 放到 table表的索引为3的位置. System.out.println("table=" + table); } } class Node { //结点, 存储数据, 可以指向下一个结点,从而形成链表 Object item; //存放数据 Node next; // 指向下一个结点 public Node(Object item, Node next) { this.item = item; this.next = next; } }
底层流程:
HashSet hashSet = new HashSet(); hashSet.add("java");//到此位置,第1次add分析完毕. hashSet.add("php");//到此位置,第2次add分析完毕 hashSet.add("java"); System.out.println("set=" + hashSet); /* 老韩对HashSet 的源码解读 1. 执行 HashSet() public HashSet() { map = new HashMap<>(); } 2. 执行 add() public boolean add(E e) {//e = "java" return map.put(e, PRESENT)==null;//(static) PRESENT = new Object(); } 3.执行 put() , 该方法会执行 hash(key) 得到key对应的hash值 算法h = key.hashCode()) ^ (h >>> 16) public V put(K key, V value) {//key = "java" value = PRESENT 共享 return putVal(hash(key), key, value, false, true); } 4.执行 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 语句表示如果当前table 是null, 或者 大小=0 //就是第一次扩容,到16个空间. if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //(1)根据key,得到hash 去计算该key应该存放到table表的哪个索引位置 //并把这个位置的对象,赋给 p //(2)判断p 是否为null //(2.1) 如果p 为null, 表示还没有存放元素, 就创建一个Node (key="java",value=PRESENT) //(2.2) 就放在该位置 tab[i] = newNode(hash, key, value, null) if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { //一个开发技巧提示: 在需要局部变量(辅助变量)时候,在创建 Node<K,V> e; K k; // //如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样 //并且满足 下面两个条件之一: //(1) 准备加入的key 和 p 指向的Node 结点的 key 是同一个对象 //(2) p 指向的Node 结点的 key 的equals() 和准备加入的key比较后相同 //就不能加入 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; //再判断 p 是不是一颗红黑树, //如果是一颗红黑树,就调用 putTreeVal , 来进行添加 else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else {//如果table对应索引位置,已经是一个链表, 就使用for循环比较 //(1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后 // 注意在把元素添加到链表后,立即判断 该链表是否已经达到8个结点 // , 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树) // 注意,在转成红黑树时,要进行判断, 判断条件 // if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64)) // resize(); // 如果上面条件成立,先table扩容. // 只有上面条件不成立时,才进行转成红黑树 //(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD(8) - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; //size 就是我们每加入一个结点Node(k,v,h,next), size++ if (++size > threshold) resize();//扩容 afterNodeInsertion(evict); return null; } */ }
LinkedHashSet
概念:
结构(数组+双向链表->红黑树**);
JDK1.8之后:哈希表(数组+双向链表+红黑树),当链表长度超过阈值(8)时并且table超过64,链表将转换为红黑树。
特点:
查询快,元素有序,元素不可重复,没有索引;
哈希表保证了元素的唯一性。线程不安全,效率高。
重写hashCode方法和equals方法,以保证对象唯一性
底层分析:
作为HashSet的子类,只是比它多了一条链表,这条链表用来记录元素顺序,因此LinkedHashSet其中的元素有序。
LinkedHashSet 底层维护的是一个LinkedHashMap(是HashMap的子类)
数组是 HashMap$Node[] 存放的元素/数据(结点类型)是 LinkedHashMap$Entry类型
static class Entry<K,V> extends HashMap,Node<K,V>{}
添加第一次时,直接将 数组table 扩容到 16 ,存放的结点类型是 LinkedHashMap$Entry
//继承关系是在内部类完成. static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } }
LinkedHashSet底层数据结构采用链表和哈希表共同实现,链表保证了元素的顺序与存储顺序一致,哈希表保证了元素的唯一性。线程不安全,效率高。
TreeSet
概念:
数据结构: (红黑树 )
特点:
查询快,元素有序,元素不可重复,没有索引;
重写hashCode方法和equals方法,以保证对象唯一性
底层分析:
TreeSet实现了继承于Set接口的SortedSet接口 ,非线程安全
TreeSet底层数据结构采用二叉树来实现,元素唯一且已经排好序;
唯一性同样需要重写hashCode和equals()方法,二叉树结构保证了元素的有序性。
根据构造方法不同,分为自然排序(无参构造)和定制排序(有参构造)
自然排序要求元素必须实现Compareable接口,并重写里面的compareTo()方法,元素通过比较返回的int值来判断排序序列,返回0说明两个对象相同,不需要存储;
二.如果里面存储自定义类型,当存入自定义的引用类型的时候就必须考虑到元素不可重复的这个特性,不然会报Exception in thread "main" java.lang.ClassCastException: testIoc.Teacher cannot be cast to java.lang.Comparable这种错误,所以要对自定义类型进行处理,**必须实现 Comparable与Compared接口, @Override public int compareTo(Teacher s){ int num = this.userName.length()-s.userName.length(); int num = num==0?this.userName.compareTo(s.userName):num; int num2 = num1==0?this.age-s.age:num1; return nums; }定制器排需要在TreeSet初始化是时候传入一个实现Comparator接口的比较器对象,或者采用匿名内部类的方式new一个Comparator对象,重写里面的compare()方法;
/*1. 构造器把传入的比较器对象,赋给了 TreeSet的底层的 TreeMap的属性this.comparator public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; } 2. 在 调用 treeSet.add("tom"), 在底层会执行到 if (cpr != null) {//cpr 就是我们的匿名内部类(对象) do { parent = t; //动态绑定到我们的匿名内部类(对象)compare cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else //如果相等,即返回0,这个Key就没有加入 return t.setValue(value); } while (t != null); }*/ ============================= TreeSet treeSet = new TreeSet(new Comparator() { @Override public int compare(Object o1, Object o2) { //下面 调用String的 compareTo方法进行字符串大小比较 //如果老韩要求加入的元素,按照长度大小排序 return ((String) o2).compareTo((String) o1); 这里排序会进行判断,相同就不会加入(长度相同,内容相同等) // return ((String) o1).length() - ((String) o2).length(); } });特有方法:
first() 返回第一个元素;
last() 返回最后一个元素;
comparator() 返回排序比较器;
解读
public static void main(String[] args) { //老韩解读 //1. 当我们使用无参构造器,创建TreeSet时,仍然是无序的 //2. 老师希望添加的元素,按照字符串大小来排序 //3. 使用TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类) // 并指定排序规则 //4. 简单看看源码 //老韩解读 /* 1. 构造器把传入的比较器对象,赋给了 TreeSet的底层的 TreeMap的属性this.comparator public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; } 2. 在 调用 treeSet.add("tom"), 在底层会执行到 if (cpr != null) {//cpr 就是我们的匿名内部类(对象) do { parent = t; //动态绑定到我们的匿名内部类(对象)compare cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else //如果相等,即返回0,这个Key就没有加入 return t.setValue(value); } while (t != null); } */ // TreeSet treeSet = new TreeSet(); TreeSet treeSet = new TreeSet(new Comparator() { @Override public int compare(Object o1, Object o2) { //下面 调用String的 compareTo方法进行字符串大小比较 //如果老韩要求加入的元素,按照长度大小排序 //return ((String) o2).compareTo((String) o1); return ((String) o1).length() - ((String) o2).length(); } }); //添加数据. treeSet.add("jack"); treeSet.add("tom");//3 treeSet.add("sp"); treeSet.add("a"); treeSet.add("abc");//3 System.out.println("treeSet=" + treeSet); }
TreeSet VS HashSet
-
TreeSet 是二差树(红黑树的树据结构)实现的,Treeset中的数据是自动排好序的,不允许放入null值
-
HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束
要求放入的对象必须实现HashCode()方法,放入的对象,是以hashcode码作为标识的,而具有相同内容的String对象,hashcode是一样,所以放入的内容不能重复。但是同一个类的对象可以放入不同的实例
适用场景分析:HashSet是基于Hash算法实现的,其性能通常都优于TreeSet。为快速查找而设计的Set,我们通常都应该使用HashSet,在我们需要排序的功能时,我们才使用TreeSet。
小结:
-
Set具有与Collection完全一样的接口,因此没有任何额外的功能,不像前面有两个不同的List。实际上Set就是Collection,只 是行为不同。(这是继承与多态思想的典型应用:表现不同的行为。)
-
Set不保存重复的元素。 Set 存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必须重写hashCode方法和equals方法,以保证对象的唯一性。
-
Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。
List VS Set
(1)、List,Set都是继承自Collection接口,Map则不是 (2)、List特点:元素有放入顺序,元素可重复 ,Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的,加入Set 的Object必须定义equals()方法 ,另外list支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。) (3).Set和List对比: Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。 List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。
Map(无序双列集合的接口)
概念:
特点:
元素包含两个值(key,value)即键值对, key不允许重复,value可以重复, key与value是一一对应的。元素无序;
Entry<K,V>,k就是set的抽象实现类,v就是collection的抽象实现类
Map接口是双列集合的最顶层接口,
ps:Map集合必须保证保证key唯一,作为key,必须重写hashCode方法和equals方法,以保证key唯一。
通用的方法。
put(key , value) 添加元素; 添加相同key的元素会替换value
remove(key) 删除key对应元素;
containsKey(key) 判断是否存在key对应元素;
get(key) 获取key对应元素;
KeySet() 获取所有的key,存到Set集合中;
Values()获取所有的Values,存到Collection集合中;
entrySet() 获取所有的元素,存到Set集合中;K getKey() V getValue()
isEmpty:判断个数是否为 0
clear:清除 k-v
Set<Map.Entry<K,V>> entrySet;
entrySet()
Set<Map.Entry<K,V>> entrySet;储存的实际是HashMap$Node中key和value的哈希地址。(后者实现了前者)
Entry<K,V>,k就是set,v就是collection HashMap$Node[K,V] ,k就是set,v就是collection
final class Key extends AbstrcatSet{} final class Value extends AbstrcatCollection{}
k-v 最后是 HashMap$Node node = newNode(hash, key, value, null)
k-v 为了方便程序员的遍历,还会 创建 EntrySet 集合 ,该集合存放的元素的类型 Entry, 而一个Entry对象就有k,v
EntrySet<Entry<K,V>> 即:transient Set<Map.Entry<K,V>> entrySet;1
entrySet 中, 定义的类型是 Map.Entry ,但是实际上存放的还是 HashMap$Node2
这是因为 static class Node<K,V> implements Map.Entry<K,V>3 interface Entry<K,V>
当把 HashMap$Node 对象 存放到 entrySet 就方便我们的遍历, 因为 Map.Entry 提供了重要方法K getKey(); V getValue();
方便遍历
K getKey() V getValue()
//老韩解读 //1. k-v 最后是 HashMap$Node node = newNode(hash, key, value, null) //2. k-v 为了方便程序员的遍历,还会 创建 EntrySet 集合 ,该集合存放的元素的类型 Entry, 而一个Entry // 对象就有k,v EntrySet<Entry<K,V>> 即: transient Set<Map.Entry<K,V>> entrySet; //3. entrySet 中, 定义的类型是 Map.Entry ,但是实际上存放的还是 HashMap$Node // 这时因为 static class Node<K,V> implements Map.Entry<K,V> //4. 当把 HashMap$Node 对象 存放到 entrySet 就方便我们的遍历, 因为 Map.Entry 提供了重要方法 // K getKey(); V getValue();Set set = map.entrySet(); System.out.println(set.getClass());// HashMap$EntrySet for (Object obj : set) { //System.out.println(obj.getClass()); //HashMap$Node //为了从 HashMap$Node 取出k-v //1. 先做一个向下转型 Map.Entry entry = (Map.Entry) obj; System.out.println(entry.getKey() + "-" + entry.getValue() ); }
源码剖析:
public static void main(String[] args) { HashMap map = new HashMap(); map.put("java", 10);//ok map.put("php", 10);//ok map.put("java", 20);//替换value System.out.println("map=" + map);// /*老韩解读HashMap的源码+图解 1. 执行构造器 new HashMap() 初始化加载因子 loadfactor = 0.75 HashMap$Node[] table = null 2. 执行put 调用 hash方法,计算 key的 hash值 (h = key.hashCode()) ^ (h >>> 16) public V put(K key, V value) {//K = "java" value = 10 return putVal(hash(key), key, value, false, true); } 3. 执行 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 数组为null, 或者 length =0 , 就扩容到16 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //取出hash值对应的table的索引位置的Node, 如果为null, 就直接把加入的k-v //, 创建成一个 Node ,加入该位置即可 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k;//辅助变量 // 如果table的索引位置的key的hash相同和新的key的hash值相同, // 并 满足(table现有的结点的key和准备添加的key是同一个对象 || equals返回真) // 就认为不能加入新的k-v if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode)//如果当前的table的已有的Node 是红黑树,就按照红黑树的方式处理 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个,到8个,后 //就调用 treeifyBin 方法进行红黑树的转换 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && //如果在循环比较过程中,发现有相同,就break,就只是替换value ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; //替换,key对应value afterNodeAccess(e); return oldValue; } } ++modCount;//每增加一个Node ,就size++ if (++size > threshold[12-24-48])//如size > 临界值,就扩容 resize(); afterNodeInsertion(evict); return null; } 5. 关于树化(转成红黑树) //如果table 为null ,或者大小还没有到 64,暂时不树化,而是进行扩容. //否则才会真正的树化 -> 剪枝 final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); } */ }
遍历:
//第一组: 先取出 所有的Key , 通过Key 取出对应的Value Set keyset = map.keySet(); //(1) 增强for System.out.println("-----第一种方式-------"); for (Object key : keyset) { System.out.println(key + "-" + map.get(key)); } //(2) 迭代器 System.out.println("----第二种方式--------"); Iterator iterator = keyset.iterator(); while (iterator.hasNext()) { Object key = iterator.next(); System.out.println(key + "-" + map.get(key)); } //第二组: 把所有的values取出 Collection values = map.values(); //这里可以使用所有的Collections使用的遍历方法 //(1) 增强for System.out.println("---取出所有的value 增强for----"); for (Object value : values) { System.out.println(value); } //(2) 迭代器 System.out.println("---取出所有的value 迭代器----"); Iterator iterator2 = values.iterator(); while (iterator2.hasNext()) { Object value = iterator2.next(); System.out.println(value); } //第三组: 通过EntrySet 来获取 k-v Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>> //(1) 增强for System.out.println("----使用EntrySet 的 for增强(第3种)----"); for (Object entry : entrySet) { //将entry 转成 Map.Entry Map.Entry m = (Map.Entry) entry; System.out.println(m.getKey() + "-" + m.getValue()); } //(2) 迭代器 System.out.println("----使用EntrySet 的 迭代器(第4种)----"); Iterator iterator3 = entrySet.iterator(); while (iterator3.hasNext()) { Object entry = iterator3.next(); //System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue) //向下转型 Map.Entry Map.Entry m = (Map.Entry) entry; System.out.println(m.getKey() + "-" + m.getValue()); } Set<Map.Entry<String,String>> emptySet = map.entrySet(); for (Map.Entry e :emptySet) { System.out.println(e.getKey()+":"+e.getValue()); }为什么不能重复?!!看底层机制!!!
add HashSet 底层add机制 hash()+equals())
添加一个元素时,先得到hash值,hash值转成索引
找到存取数据表table,判断索引位置有没有元素,
没有加入当前位置
有就调用equals(重写(String))比较,相同对象(固定哈希)不同成员,构成链表
相同就放弃添加
不同就以链表形式添加得到最后
在java8中,如果一条链表的元素个数到达TREELFY_THRESHOLD(默认是8),并且table的大小>=Min_TREELFY_CAPACITY(默认64)就会树化
HashSet底层是HashMap, 第一次添加时,table 数组扩容到 16, 临界值(threshold)是 16*加载因子(loadFactor)是0.75 = 12(”**缓存**“) 如果table 数组使用到了临界值 12,就会扩容到 16 * 2 = 32, 新的临界值就是 32*0.75 = 24, 依次类推
table数组 (hashCode为"索引")链表 链表储存相同对象(固定哈希)不同成员,构成链表 for(int i = 1; i <= 7; i++) {//在table的另外一条链表上添加了 8个B对象 hashSet.add(new B(i));// }没有重写hashCode和equals x64
HashMap实现类
概念:
数据结构: (数组+单向链表->红黑树)
JDK1.8之前:哈希表(数组+单向链表);JDK1.8之后:哈希表(数组+单向链表+红黑树),当链表长度超过阈值(8)时,链表将转换为红黑树。
特点:
查询快,元素无序,key不允许重复但可以为null,value可以重复。
底层分析:
和HashSet底层相类似,不赘述。
LinkedHashMap实现类
概念:
数据结构:(数组+双向链表->红黑树)
JDK1.8之前:哈希表(数组+双向链表);JDK1.8之后:哈希表(数组+双向链表+红黑树),当链表长度超过阈值(8)时,链表将转换为红黑树。
特点:
查询快,元素有序,key不允许重复但可以为null,value可以重复。
底层分析:
和LinkedHashSet底层相类似,不赘述。
HashTable实现类(基本不用) 子类Properties(重点)
概念:
数据结构:哈希表
特点:
key、value
不可以为null
查询快,元素无序,key不允许重复并且不可以为null,value可以重复也不能为null。
线程安全;
底层分析:
底层有数组 Hashtable$Entry[] 初始化大小为 11
临界值 threshold 8 = 11 * 0.75
当 if (count >= threshold) 满足时,就进行扩容
扩容: 按照自己的扩容机制来进行即可.按照 int newCapacity = (oldCapacity << 1) + 1; 的大小扩容.23
执行 方法 addEntry(hash, key, value, index); 添加K-V 封装到Entry(见Map概念)
HashTable和Vector一样是古老的集合,有遗留缺陷,在JDK1.2之后 被更先进的集合取代了;HashTable是线程安全的,速度慢,HashMap是线程不安全的,速度快;
ps:Hashtable的子类Properties现在依然活跃,Properties集合是一个唯一和IO流结合的集合。
使用特点和Hashtable类似
还可以用于从properties文件中,加载properties对象,并进行读取和修改。
通常作为配置文件,作用于Io流。
TreeMap实现类
概念:
数据结构:红黑树
特点:
查询快,元素有序,key不允许重复并且不可以为null,value可以重复。
可排序
/使用默认的构造器,创建TreeMap, 是无序的(也没有排序) /* 老韩要求:按照传入的 k(String) 的大小进行排序 */ // TreeMap treeMap = new TreeMap(); TreeMap treeMap = new TreeMap(new Comparator() { @Override public int compare(Object o1, Object o2) { //按照传入的 k(String) 的大小进行排序 //按照K(String) 的长度大小排序 return ((String) o2).compareTo((String) o1); //return ((String) o2).length() - ((String) o1).length(); } }); treeMap.put("jack", "杰克"); treeMap.put("tom", "汤姆"); treeMap.put("kristina", "克瑞斯提诺"); treeMap.put("smith", "斯密斯"); treeMap.put("hsp", "韩顺平");//加入不了 System.out.println("treemap=" + treeMap); /* 老韩解读源码: 1. 构造器. 把传入的实现了 Comparator接口的匿名内部类(对象),传给给TreeMap的comparator public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; } 2. 调用put方法 2.1 第一次添加, 把k-v 封装到 Entry对象,放入root Entry<K,V> t = root; if (t == null) { compare(key, key); // type (and possibly null) check root = new Entry<>(key, value, null); size = 1; modCount++; return null; } 2.2 以后添加 Comparator<? super K> cpr = comparator; if (cpr != null) { do { //遍历所有的key , 给当前key找到适当位置 parent = t; cmp = cpr.compare(key, t.key);//动态绑定到我们的匿名内部类的compare if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else //如果遍历过程中,发现准备添加Key 和当前已有的Key 相等,就不添加 return t.setValue(value); } while (t != null); } */ }底层分析:
和TreeSet底层相类似,不赘述。
SortedMap的子类
//将Map转为List List<Map.Entry<String,String>> list = new ArrayList<>(map.entrySet()); Collections.sort(list, new Comparator<Map.Entry<String, String>>() { public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) { return o2.getValue().compareTo(o1.getValue()); } }); //重新排序 //运用lambda表达式 //Collections.sort(list,((o1, o2) -> o2.getValue().compareTo(o1.getValue()))); for(Map.Entry<String,String> entry:list){ System.out.println("key:"+entry.getKey()+",:value:"+entry.getValue()); } ———————————————— 版权声明:本文为CSDN博主「梧桐和风」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/wthfeng/article/details/51934704
————————————————
:Java集合类总结,详细且易懂!_WgRui的博客-CSDN博客
总结:
泛型 Generics
概念:
泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。
泛型,即“参数化类型”。表示数据类型的数据类型
就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之类型形参),然后在使用/调用时传入具体的类型(类型实参)。具体查看泛型类,泛型接口,泛型方法,
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。
也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
特性
泛型只在编译阶段有效。
对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
没有继承性
泛型类 泛型接口
概念:
泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制。
如果不传入泛型类型实参的话,默认Object。
泛型数组不能初始化
静态方法不能使用泛型
class Test<E>{ private E var; E[] es ; public Test(E var){ //泛型构造方法形参key的类型也为T,T的类型由外部指定 this.var=var; } public E getVar(){//泛型方法getKey的返回值类型为T,T的类型由外部指定 return var; } }
泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中
注意:接口的属性都是private static final的,所以属性不能使用泛型
当实现泛型接口的类,传入泛型实参时:
虽然我们只创建了一个泛型接口Generator<T>
接口继承泛型接口给定<类型参数>,类实现泛型接口给定<类型参数>,实现接口的类里的重写方法就已改变为给定的类型;
所以我们可以为T传入无数种实参,形成无数种类型的Generator接口。
限制相同
泛型通配符(使用层面)
同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
方法参数内部的泛型不确定
public void showKeyValue1(List<?> obj){}//可接收任意的泛型类型 Object public void showKeyValue1(List<? extends AA> c){}//上限 List<? super AA> c
类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参 。
再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型,
可以把?看成所有类型的父类。是一种真实的类型。
可以解决当具体类型不确定的时候,这个通配符就是 ? ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。
泛型方法
前言:
在java中,泛型类的定义非常简单,但是泛型方法就比较复杂了。
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。
概念:
只有声明了<T>的方法才是泛型方法,
泛型类中的使用了泛型的成员方法并不是泛型方法。
<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
数量可以为多个
public <T,K> T genericMethod(T t){ T t1 = t; return t1; }
基本用法
光看上面的例子有的同学可能依然会非常迷糊,我们再通过一个例子,把我泛型方法再总结一下。
泛型类中
返回值为泛型的方法并不是泛型方法,参数为泛型的方法也不是泛型方法,只是泛型类中的普通方法。通配符也是。
必须声明<T>
泛型方法可以出现在任何地方和任何场景中使用。
注意
在泛型类<T>中声明了一个泛型方法<T>,<T>可以相同也可以不同
泛型类中的泛型方法
class GenerateTest<T>{ public void show_1(T t){ System.out.println(t.toString()); } public <E> void show_3(E t){ System.out.println(t.toString()); } public <T> void show_2(T t){ System.out.println(t.toString()); } } public static void main(String[] args) { Apple apple = new Apple(); Person person = new Person(); //提要:Fruit是apple的父类 GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>(); //apple是Fruit的子类,所以这里可以 generateTest.show_1(apple); //编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person //generateTest.show_1(person); //使用这两个方法都可以成功 generateTest.show_2(apple);generateTest.show_2(person); //使用这两个方法也都可以成功 generateTest.show_3(apple);generateTest.show_3(person); }
泛型方法与可变参数
public static void main(String[] args) { printMsgs("111",222,"aaaa","2323.4",55.55); String[] str = {"111", "222", "aaaa", "2323.4", "55.55"}; printMsg(str); } public static <T> void printMsgs( T... arrs){ for(T t : arrs){ System.out.println(t); } } public static <T> void printMsg( T[] arrs){ for(T t : arrs){ System.out.println(t); } }
静态方法与泛型
类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;静态成员随类加载而创建,优先于对象。
如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。
public static T mm(){} class上的<T>访问不到 public static <T> void show(T t){}
泛型方法总结
泛型方法能使方法独立于类而产生变化,以下是一个基本的指导原则:
无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化,那么就应该使用泛型方法。另外对于一个static的方法而言,无法访问泛型类型的参数。所以如果static方法要使用泛型能力,就必须使其成为泛型方法。
泛型上下边界
在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下边界的限制,
如:类型实参只准传入某种类型的父类或某种类型的子类。
为泛型添加上边界,即传入的类型实参必须是指定类型的子类型
为泛型添加下边界,即传入的类型实参必须是指定类型的父类型(不限于直接父类)
泛型的上下边界添加,必须与泛型的声明在一起 。
泛型类
public class Generic<T extends Number>{ private T key; public Generic(T key) { this.key = key; } public void showKeyValue1(Generic<? super Number> obj){} public T getKey(){ return key; }
泛型方法
添加上下边界限制的时候,必须在权限声明与返回值之间的<T>上添加上下边界,即在泛型声明的时候添加
public <T extends Number> T showKeyName(Generic<T> container){ System.out.println("container key :" + container.getKey()); T test = container.getKey(); return test; }
关于泛型数组要提一下
看到了很多文章中都会提起泛型数组,经过查看sun的说明文档,在java中是”不能创建一个确切的泛型类型的数组”的。
也就是说下面的这个例子是不可以的:
List<String>[] ls = new ArrayList<String>[10]; 1 而使用通配符创建泛型数组是可以的,如下面这个例子:
List<?>[] ls = new ArrayList<?>[10]; 1 这样也是可以的:
List<String>[] ls = new ArrayList[10]; 1 下面使用Sun的一篇文档的一个例子来说明这个问题:
List<String>[] lsa = new List<String>[10]; // Not really allowed. Object o = lsa; Object[] oa = (Object[]) o; List<Integer> li = new ArrayList<Integer>(); li.add(new Integer(3)); oa[1] = li; // Unsound, but passes run time store check String s = lsa[1].get(0); // Run-time error: ClassCastException.
这种情况下,由于JVM泛型的擦除机制,在运行时JVM是不知道泛型信息的,所以可以给oa[1]赋上一个ArrayList而不会出现异常,但是在取出数据的时候却要做一次类型转换,所以就会出现ClassCastException,如果可以进行泛型数组的声明,上面说的这种情况在编译期将不会出现任何的警告和错误,只有在运行时才会出错。
而对泛型数组的声明进行限制,对于这样的情况,可以在编译期提示代码有类型安全问题,比没有任何提示要强很多。
下面采用通配符的方式是被允许的:数组的类型不可以是类型变量,除非是采用通配符的方式,因为对于通配符的方式,最后取出数据是要做显式的类型转换的。
List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type. Object o = lsa; Object[] oa = (Object[]) o; List<Integer> li = new ArrayList<Integer>(); li.add(new Integer(3)); oa[1] = li; // Correct. Integer i = (Integer) lsa[1].get(0); // OK
最后
本文中的例子主要是为了阐述泛型中的一些思想而简单举出的,并不一定有着实际的可用性。另外,一提到泛型,相信大家用到最多的就是在集合中,其实,在实际的编程过程中,自己可以使用泛型去简化开发,且能很好的保证代码质量。 ————————————————
:java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一_s10461的博客-CSDN博客_java 泛型方法
JUnit测试框架