美文网首页
java核心技术第六篇之断言、日志、包装类型和工具类

java核心技术第六篇之断言、日志、包装类型和工具类

作者: 小小一技术驿站 | 来源:发表于2019-08-04 11:07 被阅读0次

    JDK1.5新特性:

        1.自动拆装箱.

        2.泛型

        3.可变参数

        4.静态导入

        5.增强for循环

        6.互斥锁

        7.枚举

    8.注解

    JDK1.6新特性:

    1.Desktop类和SystemTray类

    2.使用JAXB2来实现对象与XML之间的映射

    3.StAX

    4.使用Compiler API

    5.轻量级Http Server API

    6.插入式注解处理API(Pluggable Annotation Processing API)

    7.用Console开发控制台程序

    8.对脚本语言的支持

    9.Common Annotations

    JDK1.7新特性:

    1 对集合类的语言支持;

    2 自动资源管理;

    3 改进的通用实例创建类型推断;

    4 数字字面量下划线支持;

    5 switch中使用string;

    6 二进制字面量0b001;

    7 简化可变参数方法调用。

    8 泛型简化,出现菱形泛型

    9 异常的多个catch合并,每个异常用或|

    10 try-with-resources 语句

    JDK1.8 新特性:

    一、接口的默认方法:Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,

    二、Lambda 表达式:Collections.sort(names, (a, b) -> b.compareTo(a));Java编译器可以自动推导出参数类型,所以你可以不用再写一次类型。

    三、函数式接口:每一个lambda表达式都对应一个类型,通常是接口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。

    因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。

    四、方法与构造函数引用:Java 8 允许你使用 :: 关键字来传递方法或者构造函数引用,上面的代码展示了如何引用一个静态方法,我们也可以引用一个对象的方法:

    五、Lambda 作用域:在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。

    六、访问局部变量:lambda表达式中访问外层的局部变量

    七、访问对象字段与静态变量:lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的:

    八、访问接口的默认方法:Lambda表达式中是无法访问到默认方法的

    九、Date API:Java 8 在包java.time下包含了一组全新的时间日期API。新的日期API和开源的Joda-Time库差不多,但又不完全一样

    十、Annotation 注解:在Java 8中支持多重注解了,Java 8允许我们把同一个类型的注解使用多次,只需要给该注解标注一下@Repeatable即可,

    断言的使用方式:

    断言是一种测试和调测阶段所使用的战术工具.

    断言的初衷:在一个具有自我保护能力的程序中,断言很常用.假设确信某个属性符合要求,并且代码的执行依赖于这个属性.例如,需要计算

    double y = Math.sqrt(x);

    我们确信,这里的x是一个非负数的值.原因是:是另外一个计算的结果,而这个结果不可能是负值;或者x是一个方法的参数,而这个方法要求它的调用者只能提供一个正整数.

    然而,还是希望进行检查,以避免让"不是一个数"的数值参与计算操作.当然,也可以抛出一个异常:

    if(x<0) throw new IllegalArgumentException("x<0");

    但是需要注意的是,这段代码会一直保留在程序中,即使测试完毕也不会自动的删除.如果在程序中包含有大量的这种检查,程序运行起来会相当慢.

    断言机制允许在测试期间向代码中插入一些检查语句.当代码发布时,这些插入的检查语句将会被自动的移走.

    开启断言:2.在myEclipse中,Windows -> Preferences ->Java ->Installed JREs ->点击正使用的JDK ->Edit ->Default VM Arguments文本框中输入:-ea或者-enableassertions

    断言的格式:

        java中语音引入关键字assert.这个关键字有两种表现形式:

    1.assert 条件:  例如:assert x>=0;  想要断言x是一个非负数值,只需要简单的使用这条语句.

    2.assert 条件:表达式; 例如:assert x>=0 : x;  或者将x的实际值传递给AssertionError对象,从而可以在后面显示出来.

    这两个格式都会对条件进行检测,如果结果为false,则抛出一个AssertionError异常.

    在第二种格式中,表达式将被传入AssertionEeror的构造器,并转换成一个消息字符串.

    注释:"表达式"部分的唯一目的是产生一个消息字符串.AssertionErro对象并不存储表达式的值,因此,不可能在以后得到它.正如JDK文档所描述的那样:如果使用表达式的值,

    就会鼓励程序员试图从断言中恢复程序的运行,这不符合断言机制的初衷.

    启用和禁用断言

    在默认情况下,断言是被禁用的。可以在程序运行时用-enableassertions或-ea选项启用它:

    java -enableassertions MyApp

    启用或者禁用断言不必重新编译程序。启动或者禁用断言是类加载器的功能。当断言被禁用时,类加载器将跳过断言代码,因此,不会降低程序的运行速度。

    也可以在某个类或者某个包中使用断言,例如:

    java -ea:MyClass -ea:com.mycompany.mylib... MyApp

    这条命令将开启MyClass类以及在com.mycompany.mylib包和它的子包中的所有类的断言。选项-ea将开启默认包中的所有类的断言。

    也可以使用-disableassertions或-da禁用某个特定的类和包的断言:

    java -ea:... -da:MyClass MyApp

    有些类不是由类加载器加载,而是直接由虚拟机加载。可以使用这些开关有选择地启用或禁用哪些类中的断言。

    然而,启用和禁用所有断言的-ea和-da开关并不能应用到那些没有类加载器的“系统类”上。对于这些系统类而言,需要使用-enablesystemassertions/-esa开关启动断言。

    使用断言完成参数检查

    API:java.long.ClassLoader 1.0  子类MLet

    void setDefaultAssertionStatus(Boolean b) //1.4

    对于通过类加载器加载的所有类来说,如果没有显示的说明类或者包的断言 状态,就启用或禁用断言.

    void setClassAssertionSatus(String className,boolean b) //1.4

    对于给定的类和它的内部类,启用或禁用断言.

    void setPackageAssertionStatus(String packageName,boolean b) //1.4

    对于给定包和其子包中的所有类,启用或禁用断言.

    void clearAssertionStatus()  //1.4

    移去所有类和包的显示断言状态设置,并禁用所有通过这个类加载器加载的类的断言.

    java中的3中处理系统错误的机制:

    抛出一个异常

    日志

    使用断言

    什么时候应该使用断言呢?

    断言失败是致命的、不可恢复的错误

    断言检查只用于开发和测试阶段

    因此,不应该使用断言向程序的其他部分通告发生了可恢复性的错误,或者,不应该作为程序向用户通告问题的手段。断言只应该是在测试阶段确定程序内部错误的位置。

    断言是一种测试和调试阶段所使用的战术性工具;而日志记录是一种在程序的整个生命周期都可以使用的策略性工具。

    public class App

    {

        public static void main( String[] args )

        {

            int a = -1;

            assert a > 0 : "liuni是笨蛋";

            System.out.println( "Hello World!" );

        }

    }

    程序中记录日志一般有两个目的:Troubleshooting和显示程序运行状态。好的日志记录方式可以提供我们足够多定位问题的依据。日志记录大家都会认为简单,但如何通过日志可以高效定位问题并不是简单的事情。这里列举下面三个方面的内容,辅以代码示例,总结如何写好日志,希望对他人有所启发和帮助:

    怎样记日志可以方便Troubleshooting

    程序运行状态可以记哪些

    应该避免怎样的日志方式

    怎样记日志可以方便Troubleshooting?

    1. 对外部的调用封装

    程序中对外部系统与模块的依赖调用前后都记下日志,方便接口调试。出问题时也可以很快理清是哪块的问题

    [java] view plain copy

    LOG.debug("Calling external system:" + parameters);   

      Object result = null;   

      try {   

          result = callRemoteSystem(params);   

          LOG.debug("Called successfully. result is " + result);   

      } catch (Exception e) {   

          LOG.warn("Failed at calling xxx system . exception : " + e);   

      }   

    2.状态变化

    程序中重要的状态信息的变化应该记录下来,方便查问题时还原现场,推断程序运行过程

    [java] view plain copy

    boolean isRunning;   

    isRunning = true;   

    LOG.info("System is running");   

    //...   

    isRunning = false;   

    LOG.info("System was interrupted by " + Thread.currentThread().getName());   

    3.系统入口与出口:

    这个粒度可以是重要方法级或模块级。记录它的输入与输出,方便定位

    [java] view plain copy

    void execute(Object input) {   

          LOG.debug("Invoke parames : " + input);   

          Object result = null;   

          //business logic 

          LOG.debug("Method result : " + result);   

      }   

    4.业务异常:

    任何业务异常都应该记下来:

    [java] view plain copy

    try {   

          //business logical   

      } catch (IOException e) {   

          LOG.warn("Description xxx" , e);   

      } catch (BusinessException e) {   

          LOG.warn("Let me know anything");   

      } catch (Exception e) {   

          LOG.error("Description xxx", e);   

      }   

    5.非预期执行:

    为程序在“有可能”执行到的地方打印日志。如果我想删除一个文件,结果返回成功。但事实上,那个文件在你想删除之前就不存在了。最终结果是一致的,但程序得让我们知道这种情况,要查清为什么文件在删除之前就已经不存在

    [java] view plain copy

    int myValue = xxxx;   

    int absResult = Math.abs(myValue);   

    if (absResult < 0) {   

        LOG.info("Original int " + myValue + "has nagetive abs " + absResult);   

    }   

    6.很少出现的else情况:

    else可能吞掉你的请求,或是赋予难以理解的最终结果

    [java] view plain copy

    Object result = null;   

    if (running) {   

        result = xxx;   

    } else {   

        result = yyy;   

        LOG.debug("System does not running, we change the final result");   

    }   

    日志记录:

    程序运行状态可以记哪些?

    程序在运行时就像一个机器人,我们可以从它的日志看出它正在做什么,是不是按预期的设计在做,所以这些正常的运行状态是要有的。

    1. 程序运行时间:

    [java] view plain copy

    long startTime = System.currentTime();   

    // business logical   

    LOG.info("execution cost : " + (System.currentTime() - startTime) + "ms");    

    2. 大批量数据的执行进度:

    [java] view plain copy

    LOG.debug("current progress: " + (currentPos * 100 / totalAmount) + "%");   

    3.关键变量及正在做哪些重要的事情:

    执行关键的逻辑,做IO操作等等

    [java] view plain copy

    String getJVMPid() {   

        String pid = "";   

        // Obtains JVM process ID   

        LOG.info("JVM pid is " + pid);   

        return pid;   

      }   

      void invokeRemoteMethod(Object params) {   

          LOG.info("Calling remote method : " + params);   

        //Calling remote server   

    }   

    应该避免怎样的日志方式?

    1. 混淆信息的Log

    日志应该是清晰准确的: 当看到日志的时候,你知道是因为连接池取不到连接导致的问题么?

    [java] view plain copy

    Connection connection = ConnectionFactory.getConnection();   

    if (connection == null) {   

        LOG.warn("System initialized unsuccessfully");   

    }   

    2. 记错位置

    产品代码中,使用console记录日志,导致没有找到日志。

    [java] view plain copy

    } catch (ConfigurationException e) { 

            e.printStackTrace(); 

          } 

    3. 记错级别

    记错级别常常发生,常见的如:混淆代码错误和用户错误,如登录系统中,如果恶意登录,那系统内部会出现太多WARN,从而让管理员误以为是代码错误。可以反馈用户以错误,但是不要记录用户错误的行为,除非想达到控制的目的。

    [java] view plain copy

    LOG.warn("Failed to login by "+username+"); 

    4. 遗漏信息

    这里可能包含两种情况:(1)用户自己少写了信息,导致毫无参考价值;(2)用户调用log的方式导致丢失信息,如下例,没有stack trace.

    [java] view plain copy

    } catch (Exception ex) { 

            log.error(ex); 

          } 

    */

    ----------String--------------

    构造:

    String(String original):把字符串数据封装成字符串对象

    String(char[] value):把字符数组的数据封装成字符串对象

    String(char[] value, int index, int count):把字符数组中的一部分数据

    方法:

    判断功能:

    boolean equals(Object obj):比较字符串的内容是否相同

    boolean equalsIgnoreCase(String str):比较字符串的内容是否相同,忽略大小写

      boolean startsWith(String str):判断字符串对象是否以指定的str开头

    boolean endsWith(String str):判断字符串对象是否以指定的str结尾

    获取功能:

    int length():获取字符串的长度,其实也就是字符个数

    char charAt(int index):获取指定索引处的字符

    int indexOf(String str):获取str在字符串对象中第一次出现的索引

    String substring(int start):从start开始截取字符串

    String substring(int start,int end):从start开始,到end结束截取字符串。包括start,不包括end

    转换功能:

    char[] toCharArray():把字符串转换为字符数组

    String toLowerCase():把字符串转换为小写字符串

    String toUpperCase():把字符串转换为大写字符串

    String replace(char oldChar, char newChar)通过用 newChar 替换此字符串中出现的所有 oldChar ,返回处理后的字符串。

    其它功能:

    String trim():去除字符串两端空格

    String[] split(String str):按照指定符号分割字符串

    --------StringBuider(可变字符串)------------

    构造:

    就一个

    方法:

    public int capacity():返回当前容量 (理论值)

        public int length():返回长度(已经存储的字符个数)

    public StringBuilder append( 任意类型 ):添加数据,并返回添加的数据

    public StringBuilder reverse():反转功能

    StringBuilder replace(int start, int end, String str) 使用给定 String 中的字符替换此序列的子字符串中的字符。

    --------Integer(包装类)---------------

    构造:

    不需要,直接类名.方法名调用,如下

    方法:

    Integer.parseInt(String str):将字符串转int类型(只能数字内容的字符串,不然报错)

    ---------ArrayList(集合)-------------------

    构造:

    ArrayList();就学了空参创建

    方法:

    public boolean add(E e):添加元素,成功返回true,就业班学false

    public void add(int index,E element):在指定的索引处添加一个元素

    public E get(int index):返回指定索引处的元素

    public E set(int index,E element):修改指定索引处的元素,返回被修改的元素

    public int size():返回集合中的元素的个数

    public boolean remove(Object o):删除指定的元素,返回删除是否成功

    E remove(int index) 移除此列表中指定位置上的元素,并返回删除的数据。

    ---------FileWriter(输出普通流)-----------

    构造:

    FileWriter(String fileName); 传入一个文件的路径

    方法:

    void write(int c)  写入一个字符,int类型传入

    void write(String str) 写入一个字符串

    void write(String str, int startIndex, int len) 写入字符串的一部分

    void write(char[] cbuf) 写入一个字符数组

    void write(char[] cbuf, int startIndex, int len)写入字符数组的一部分

    void flush()  将内存中的数据刷新到文件中

    void close()  数据刷新到文件中,关流释放系统底层资源(关闭后永久关闭,直到下次程序运行)

    ---------BufferedWriter(输出缓冲流)-----------

    构造:

    BufferedWriter(new FileWriter(String fileName));传入一个FileWriter对象,FileWriter包含一个文件的路径

    方法 (同FileWriter,多了一个方法):

    void newLine()  写入换行(\r\n)

    ---------FileReader(输入普通流)-----------

    构造:

    FileReader(String fileName); 传入一个文件的路径

    方法:

    int read()  读取单个字符,若是读取成功返回参数的int类型表现形式,若读取失败,返回-1

    int read(char[] cbuf)  读取指定char数组长度个字符,并存入char数组,若是读到文件尾部,则停止读取,并返回读取字符的个数(不是char数组长度),若是一个都没读到,返回-1。

    int read(char[] cbuf, int startIndex, int len)  读取 startIndex 索引开始, len 个字符(length简拼)

    void reset() 重置流(删除前面读的记录)

    ---------BufferedReader(输入缓冲流)-----------

    构造:

    FileReader(String fileName); 传入一个文件的路径

    方法 (同FileReader,多了一个方法)

    String ReadLine()  一次读一行的内容,返回读到的内容,没读到返回null

    -----------Arrays(工具类,考试不推荐使用)-----------------

    构造:

    不需要构造,因为该类不需要创建对象

    方法:

    String toString(各种数组,集合不行) 返回该数组的全部内容的特定格式(遍历数组)

    方法重载:

    1.方法名相同

    2.参数列表不同(数量、类型、顺序)

    方法覆盖:

    1.访问修饰符相同或更宽

    2.返回值类型相同

    3.方法名相同

    4.参数表相同

    5.static 静态只能覆盖静态 非静态只能覆盖非静态

    6.子类方法不能抛出比父类更多的异常

    三大修饰符:

    static

    可以修饰属性、方法和初始化代码块,不可以修饰局部变量

    1. 静态属性、方法为全类所有,可通过类名直接调用

    2. 静态初始化代码块在类加载时执行,仅执行一次

    final

    可以修饰局部变量、成员变量、方法和类

    1. final修饰的变量一旦被赋值,就不能改变

    2. final修饰的方法不可以被覆盖

    3. final修饰的类不能被继承

    abstract

    可以修饰类和方法

    1. 抽象类只能声明引用,不能创建对象

    2. 抽象方法只有声明,没有实现(不写代码块)

    3. 如果一个类中有抽象方法,那这个类必须是抽象类

    4. 子类继承一个抽象类,如果不希望子类变成抽象类,那子类必须实现父类中的全部抽象方法

    PS:多个修饰符修饰同一方法、类时的使用问题

    1. abstract 不能与 final 一起修饰方法、类

    抽象类、方法需要被继承、覆盖才可使用,final修饰的类、方法不可被继承、覆盖

    2. abstract 不能与 private 一起修饰方法(private只能修饰内部类)

    理由同上,私有方法不能被继承

    3. abstract 不能与 static 一起修饰方法(static只能修饰类的成员,不能修饰类本身)

    抽象类需要有子类的实现类才能调用内部方法,而静态类可以直接通过本类名调用内部方法

    4. private 与 static 与 final 可以连用(属性、方法都可以)

    权限修饰符:private default protect public 可以修饰方法

        public default 可以修饰类

    接口:interface

    1. 接口是特殊的抽象类(interface 代替了 abstract class)

    2. 接口中所有属性都是公开静态常量

    3. 接口中所有方法都是公开抽象方法

    4. 一个接口可以继承多个接口(extends 多个接口,接口之间用逗号隔开)

    5. 一个类只可以继承一个父类,但是可以实现多个接口

    PS: 抽象类与接口的不同点

    1. 抽象类可以有构造方法,接口不可以有构造方法

    2. 抽象类不可以多继承,接口可以多继承

    接口的实现(被子类继承):implements

    1. 一个类实现接口,如果不希望作为抽象类,就必须实现接口中全部方法

    接口的作用;

    1. 接口与多继承

    1. 可以让子类继承主要类型,让次要类型作为一个接口让子类实现.

    2. 单继承具备简单性(相对于多继承),使用接口实现的多继承并不会破坏其简单性

    2. 接口与解耦合

    接口抽象出子类中的共性,利用多态来解耦合

    3. 接口回调

    实现某些接口,由函数库中方法自动调用。

    例:

    定义实体类是实现Comrparable<E>接口(中的comepareTo方法,返回值为int)

    在调用java.util.Arrays.sort数组排序方法时,可以排序存放实体类的数组

    Object类:

    Object是所有类的父类,如果一个类没有定义直接继承的父类,就会直接继承Object类(自动加上extends Object)

    所有的类都会继承Object类中的全部公开方法。

    Obeject类中的全部方法:

    1. finalize

    该方法在垃圾回收时,被垃圾回收器调用

    (什么是垃圾? -- 没有引用指向的对象)

    1. JVM中有自动垃圾回收机制,程序员只需要创建对象、分配空间,不需要回收垃圾

    2. JVM只有在内存不够用的时候才会进行垃圾回收

    (可能会浪费空间,但减少了垃圾回收对CPU的占用)

    2. getClass

    作用:得到对象的"实际"类型

    补充:instanceof 可看做是不是:  引用 instanceof 类型

    该引用的实际类型 是不是 某类型

    比较两个对象的实际类型是不是相等:

    1. 使用 instanceof 只能得出某对象的实际乐行是不是某类型的子类

    2. 可以使用getClass得到 对象的实际类型

    语法:m1.getClass() == m2.getClass()

    3. equals

    作用: 比较两个对象

    1. 可以在子类中覆盖equals方法,作用为使两个对象可以使用自定义的标准相互比较

    2. 如果不覆盖equals方法,默认比较两个对象的地址值(即使用 == 比较两个引用类型)

    3. 字符串类覆盖了equals方法

    覆盖equals方法的五个步骤:

    1. 判断 this == obj (地址相同直接返回true)

    2. 判断 obj == null (this一定不是null,否则无法调用equals方法)

    3. 判断 两个对象的实际类型是否相同(使用getClass方法)

    4. 强制类型转换

    5. 依次比较两个对象的属性是否相等

    4. toString

    作用:打印

    在控制台打印对象时,自动调用该对象的toString方法

    包装类:

    将基本数据类型封装为对象数据类型:

    int -- Integer

    char -- Character

    其他基本数据类型 -- 首字母大写

    int Integer String 之间的相互转换:

    1. Integer ii = new Integer( int i )

      Integer ii = new Integer( String s )

    2. int i = ii.intValue()

      int i = Integer.paresInt( String s )

    3. String s = ii.toString()

      String s = String.valueOf( int i )

    正则表达式:

    作用: 使用正则表达式可以匹配出想要的字符串

    1. [] [a-zA-Z0-9] [1235] 中括号可以匹配多个字符,或者范围内字符,范围使用-分隔,多组范围直接连着写

    2. {} {5} {5,10} 限制前一个字符内容长度,或者长度范围,范围使用逗号分隔

    3. 特殊意义字符: \:转义字符,想要使用\ 需要使用转义字符 \\

      +:放在字符后面,代表匹配一个或多个该字符,想要使用+,需要使用转义字符 \\+

      .:匹配任意一个字符,使用字符本身,需要转义 \\.

      \\d 匹配任意字母

      \\w 匹配任意字母或者数字

      ( | ) 逻辑或,匹配 | 前面或者后面的字符,例:(J|j)ava  可以匹配 Java 或者 java

      ^: 代表必须是字符串的开头,例:^[a-z]  字符串必须以小写字母开头

      $: 代表前一个字符必须是匹配内容的结尾,例:^[a-z][0-9]$ 字符串必须以数字结尾

      \\_ 下划线

    内部类:

    成员内部类:

    1. 成员内部类可以访问外部类中所有的、静态的、私有的属性和方法

    2. 成员内部类中不可以定义静态的属性和方法

    3. 创建成员内部类对象,需要先创建一个外部类对象(依赖于独立的外部类对象,因此不可以有静态成员)

    创建成员内部类对象:

    外部类 out = new 外部类();

    外部类.内部类 引用名 = out.new 外部类.内部类();

    (类型是 外部类.内部类,作为一个外部类的成员,需要用一个外部类对象来创建)

    静态内部类:

    1. 静态内部类只能访问外部类中所有的静态属性和方法

    2. 静态内部类可以定义普通的和静态的属性和方法

    3. 创建静态内部类对象,可以直接使用该类的类型创建(静态成员不依赖于外部类的对象)

    创建静态内部类对象:

    外部类.静态内部类 引用名 = new 外部类.静态内部类();

    (不依赖外部类对象,直接用类型创建)

    局部内部类:

    作用同下

    匿名内部类:

    new 接口名() { 接口的实现代码 }

    用途:

    需要一个仅仅使用一次的对象时,直接在方法内部创建、使用,不需要打断思路,另开一个实现类

    缺点:

    代码可读性差

    集合框架:

    为什么使用集合: 数组的扩容、插入、删除操作十分繁琐

    集合的类型:

    Collection<Object>  接口

    -->  List<Object>  接口

    -->  Set<Object>  接口

    Map<Key, Value>    接口

    集合中的方法:

    Collecion :

    add (Object obj) -- boolean 添加元素

    contains (Object obj)  -- boolean 查看是否包含某元素

    isEmpty() -- boolean 集合是否为空

    remove (Object obj) -- boolean 删除某元素

    clear() -- void 清空集合

    size() -- int 查看集合中元素个数

    List :  元素是对象类型,元素有下标(有顺序),元素可以重复

    add (Object obj) / add (int index, Object obj)

    添加元素,可以直接添加,或者插入到指定的index

    get (int index)  set (int index, Object obj)

    根据下标,获取或者修改元素

    indexOf (Object obj) -- int

    获取元素下标

    Set: 元素是对象类型,元素没有下标(没有顺序),元素不可以重复

    Set集合没有自己特有的方法,全部方法继承自Collection

    Map: 元素是键值对类型,键不可以重复,值可以重复

    get (Object key) -- Object

    根据键,获取对应的值

    put (Object key, Object value) -- void

    添加一个键值对

    remove (Object key) -- void

    删除一个键值对,根据值

    ketSet() -- Set

    获取全部的键,放到一个Set里面返回

    values() -- Collection

    获取全部的值,放到一个Collection中返回

    containsKey/containsValus

    查看Map中是否包含某个Key/Values

    size()

    查看Map中键值对个数

    isEmpty()

    clear()

    entrySet()

    获取全部的键值对(Map.Entry类型),放到Set中返回

    集合的实现类:

    List 的实现类:

    ArrayList 

    1. 使用数组实现

    2. 增删慢,查询快

    LinkedList

    1. 使用链表实现

    2. 增删快,查询慢

    Vector

    1. JDK1.0遗留的产物

    2. 重量级,线程安全,速度慢(多线程访问同一对象时,不会出现同步问题)

    Set 的实现类:

    HashSet

    1. 集合中的元素无序,不重复(可以用来去除一组元素中重复的数据)

    2. new HashSet() 默认容量16,加载因子0.75

    3. newHashSet(int 容量, float )

    LinkedHashSet

    1. 遍历时可以保留添加到集合中的顺序(Set集合中的元素是无序的)

    Set 集合是如何实现不重复元素的?

    添加进Set集合中的元素要想做到不重复需要:

    1. 需要覆盖 equals() 方法 -- 变成比较元素内容而不是比较元素地址

    2. 需要覆盖 hashCode() 方法 -- 使哈希码与元素内容有关,

    保证相同元素有相同哈希码,

    尽量保证不同元素哈希码不同,

    可节省运算、比较次数

    如何实现元素不重复的?

    1. 每个对象都有自己的 hashCode(hashSet使用数组加链表实现)

    -- hashCode决定对象在集合中的存放位置,

      初始hashCode值由对象地址决定

    2. 如果两个对象的hashCode相同,就使用 equals() 方法进行比较,

      去掉重复元素,不重复的挂到该hashCode对应数组位置中的链表里

    3. 如果两个对象hashCode不同,那么放入集合的位置就可能不同,

      两个对象就不会进行 equals() 方法比较的过程,因此必须修改hashCode方法

      让内容可能相同的对象hashCode相同,进行比较去重复。

      同时也要尽量保证内容不同的对象的hashCode不同,尽量加快运算速度。

    equals方法:

    1. 判断两个对象地址是否相同(直接等于)

    2. 判断参数对象是否为空

    3. 判断两个对象类型是否相同(使用getClass方法)

    使用 instanceof 有可能遇到父子类的情况

    4. 将参数对象类型强转成本类对象类型

    5. 逐个比较两个对象的属性

    Map 的实现类:

    HashMap

    1. 键不可以重复,同样需要覆盖 equals 方法和 hashCode 方法

    2. 允许键/值为 null

    LinkedHashMap

    1. 遍历时,保留了键的放入顺序

    HashTable

    1. 类似于 Vector ,重量级,速度慢,线程安全

    2. 当键/值为 null 时,会抛出异常

    Map 集合的三种遍历方式:

    键遍历:

    使用 keySet() 方法,拿到全部键的Set,再遍历 Set 即可(配合 get(key) 方法拿值)

    值遍历:

    使用 values() 方法,拿到全部值的Collection

    键值对遍历:

    1.使用 entrySet() 方法,拿到全部的键值对Set

    装有键值对的Set的泛型需要注意写法:

    例: Set<Map.Entry<Integer, String>> set = map.entrySet();

    键值对的类型为:Map.Entry , 例: Map.Entry entry  = (Map.Entry)iter.next();

    键值对的迭代器遍历时,需要将从Set中取出

    的值强转成 Map.Entry 类型

    ↑↑以上是JDK5.0之前泛型没出的时候的做法

    加泛型之后,不需要在强转了

    2. 使用Map.Entry类中的 getKey() / getValue() 方法,获取键值对中的键和值

    PS:用迭代器遍历集合

    Iterator iter = list.iterator(); --> List 和 Set 中有iterator方法,获取迭代器

    //创建一个迭代器对象

    while( iter.hasNext() ) {

    Object value = iter.next();

    //对拿到的value进行需要的操作

    }

    foreach遍历

    1. foreach封装了迭代器遍历

    2. 迭代器对与集合长度及变化做了安全监测,

    3. 定义了异常:ConcurrentModificationException 并发修改异常

    4. 在使集合创建迭代器时,会为变量expectedModCount赋值(赋予当前modCount的值)

    5. foreach遍历集合时,先调用hasNext方法,指针后移,如果索引等于集合长度,

    则结束循环,如果索引小于集合长度,

    则通过next()取出指针指向的集合内对象地址,执行循环体。

    6. 在next()中进行集合长度及变化的安全监测,如果索引大于集合长度,则说明在

    上一次循环的循环体中,出现了修改集合长度的操作,则直接抛出

    并发修改异常;集合之中有一个成员变量modCount,

    记录了集合被增删元素的次数,next()中会先比较expectedModCount和

    modCount的值是否相同,及监测集合有没有被修改过,如果不同就抛出

    并发修改异常

    PS: 7. 安全监测是在next方法中进行的,因此若是删除集合的倒数第二个元素:

    在循环体执行删除语句,删除后集合长度-1,循环体结束进入下一次循环判断

    hasNext方法:索引长度刚好等于集合长度,结束循环,不进入next方法。

    所以:可以在foreach遍历集合中,删除倒数第二个元素。

        8. iterator的remove方法对集合修改安全监测进行了屏蔽处理,

    使用iterator中的方法删除元素,不会抛出异常

    结论:Iterator做出了安全监测,集合对象不可以在foreach循环遍历集合中,

    做出增加删除元素的操作,否则抛出并发修改异常。

    (除了删除倒数第二个元素)

    集合工具类:

    Collections :(静态方法)

    1. Collections.shuffle(list) 打乱一个List集合中元素的排列顺序

    2. Collections.sort(list) 按照自然顺序排序一个List集合内的元素

    3. Collections 只能作用于List集合

    Properties:持久的属性集

    1. Properties继承自Map集合

    2. Properties中有方法可以将集合中内容保存在文本文件中

    3. Properties限定泛型,只能装String类型数据(name,value)

    4. Properties中的常用方法:

    1. setProperties(String, String)

    2. getProperties(String key)

    3. stringPropertyNames(获取集合中全部的name的 Set 集合)

    4. store(OutputStream,String comments) -- 将集合中数据通过输出流保存在文本文件中

    自定义一个输出流,文本文件必须以.properties结尾,commetns为注释,可以为null

    5. load(InputStream) -- 通过一个输入流将文件中内容传入Properties对象中

    相关文章

      网友评论

          本文标题:java核心技术第六篇之断言、日志、包装类型和工具类

          本文链接:https://www.haomeiwen.com/subject/flwedctx.html