JavaGuide易错点总结

Posted jayv

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaGuide易错点总结相关的知识,希望对你有一定的参考价值。

基础知识易错点

1. object.equals("str") 容易报空指针异常,应使用"str".equals(object);

  • 还可以使用JDK7引入的工具类object#equals : objects.equals(null, "str"); // false

    • java.util.Objects#equals源码:

      public static boolean equals(Object a, Object b) {
          // 可以避免空指针异常。如果a==null的话此时a.equals(b)就不会得到执行,避免出现空指针异常。
              return (a == b) || (a != null && a.equals(b));
          }
      
  • Java中equals方法造成空指针异常的原因及解决方案

    • 每种原始类型都有默认值一样,如int默认值为 0,boolean 的默认值为 false,null 是任何引用类型的默认值,不严格的说是所有 Object 类型的默认值。
    • 可以使用 == 或者 != 操作来比较null值,但是不能使用其他算法或者逻辑操作。在Java中null == null将返回true。
    • 不能使用一个值为null的引用类型变量来调用非静态方法,否则会抛出异常
  • 所有整型包装类(如Integer)对象值的比较必须使用equals方法。

  • ==比较的是值,只不过基本数据类型变量存的是值,引用数据类型变量存放的是对象的地址罢了

  • equals不能用于比较基本数据类型的变量,equals()方法存在于Object类中,底层还是return (this == obj) 实现的

  • 特殊:String的equals() 是被重写过的,而重写过的equals方法用法和==相同,即String中equals比较的是值而不是地址

  • 对象的相等,比的是内存中存放的内容是否相等。而引用相等,比较的是他们指向的内存地址是否相等。

多态就是同一个接口,使用不同的实例而执行不同操作, 如空调是一个接口,不同品牌对空调的实现落地就是不同的实例执行不同的操作

2. BigDecimal:防止浮点型精度丢失

  • float a = 1.0f - 0.9f;
    float b = 0.9f - 0.8f;
    System.out.println(a == b);// false:精度丢失
    
  • BigDecimal a = new BigDecimal("1.0");
    BigDecimal b = new BigDecimal("0.9");
    BigDecimal c = new BigDecimal("0.8");
    BigDecimal x = a.subtract(b);// a-b=0.1
    BigDecimal y = b.subtract(c);// b-c=0.1
    System.out.println(x.equals(y));// true 
    System.out.println(a.compareTo(b));// a>b结果为1
    
  • Reference:《阿里巴巴Java开发手册》

    • 技术图片
  • BigDecimal 主要用来操作(大)浮点数,BigInteger 主要用来操作大整数(超过 long 类型)

3. 基本数据类型与包装数据类型的使用标准

  • 【强制】所有的 POJO 类属性必须使用包装数据类型。

  • 【强制】RPC 方法的返回值和参数必须使用包装数据类型。

  • 【推荐】所有的局部变量使用基本数据类型。

4. 集合ArrayList

  • 技术图片

  • Arrays.asList()是泛型方法,传入的对象必须是对象数组。它的底层实现:

    • /**
       *返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁,与           Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。
       */ 
      public static <T> List<T> asList(T... a) {
          return new ArrayList<>(a);
      }
      
  • 对Arrays.asList()的集合使用集合的修改方法:add()、remove()、clear()会抛UnsupportedOperationException

  • 正确将数组转为集合的方法:List list = new ArrayList<>(Arrays.asList("a", "b", "c"))

  • Collections.reverse(list);   // list集合反转为数组
    s=list.toArray(new String[0]);//反转为数组没有指定类型的话会报错,[0]为了节省空间,只声明类型
    

    5.不要在 foreach 循环里进行元素的 remove/add 操作

    如果要进行remove操作,可以调用迭代器的 remove 方法而不是集合类的 remove 方法。因为如果列表在 任何时间从结构上修改创建迭代器之后,以任何方式除非通过迭代器自身remove/add方法,迭代器都将抛出一个ConcurrentModificationException,这就是单线程状态下产生的 fail-fast 机制

    fail-fast 机制 :多个线程对 fail-fast 集合进行修改的时,可能会抛出ConcurrentModificationException,单线程下也会出现这种情况,上面已经提到过。

    java.util包下面的所有的集合类都是fail-fast的,而java.util.concurrent包下面的所有的类都是fail-safe的。

    Interator<T> iterator=list.iterator(); //创建list的迭代器后才能在foreach里进行remoe/add操作
    while(iterator.hasNext()){...}   // 不能直接for(T item:list){list.remove(item)}
    

    6. 泛型(JDK5引入)

  • 泛型提供了编译时类型含权检测机制,该机制允许程序员在编译时检测到非法的类型

  • 类型擦除Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉

  • List<Integer> list = new ArrayList<>();
    Class<? extends List> clazz = list.getClass();
    Method add = clazz.getDeclaredMethod("add", Object.class); // 返回反映clazz对象所表示的类的指定已声明方法
    add.invoke(list, "kl");  //通过反射添加String类型,可以添加String类型的元素
    

7. hashCode()

  • hashCode()作用是获取哈希码,也叫散列码,返回一个int整数即该对象的内存地址,作用是确定该对象在哈希表的索引位置,定义在Object类中

  • 为什么要有hashCode:如HashSet检查重复时,根据加入的对象的hashCode作比较,若有hashCode相同的对象出现,则通过equals比较内存地址是否相同;若hashCode不同,可避免equals操作而大大减少equals次数,提高执行速度

  • 为什么重写 equals 时必须重写 hashCode 方法:两个对象相等,则hashCode一定相同,调用equals进行比较后返回true;但是,两个对象hashCode相同,它们不一定相等。因此euqals方法被覆盖过,则hashCode方法也必须被覆盖。

    • hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
  • 为什么两个对象有相同的 hashcode 值,它们也不一定是相等的?

    • 因为 hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode。因此需要equals来判断两个对象是否真的相同

常量池

  • Java 基本类型的包装类的大部分都实现了常量池技术,即 Byte,Short,Integer,Long,Character,Boolean;

  • 前面 4 种包装类默认创建了数值[-128,127] 的相应类型的缓存数据,Character创建了数值在[0,127]范围的缓存数据,Boolean 直接返回True Or False。如果超出对应范围仍然会去创建新的对象

  • 两种浮点数类型的包装类 Float,Double 并没有实现常量池技术

  • Integer i=40;Java 在编译的时候会直接将代码封装成 Integer i=Integer.valueOf(40);,从而使用常量池中的对象。Integer i=new Integer(40)会创建对象


随笔

  • Java 程序在执行子类的构造方法之前,如果没有用 super()来调用父类特定的构造方法,则会调用父类中无参构造,若父类没有无参构造则编译出错

  • StringBuilderStringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中使用字符数组保存字符串char[] value ,两种对象都是可变的。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 没有加同步锁,所以是非线程安全的。

  • String 中用 final 保存字符串,private final char value[],所以 String 对象是不可变的。可以理解为常量,线程安全,每次对 String 类型改变时,都会生成新的 String 对象,然后将指针指向新的对象

  • StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用

  • transient 关键字只能修饰变量来禁止该变量不被序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复

  • 两种常用键盘输入方法:

    Scanner input = new Scanner(System.in)         BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
    String s = input.nextLine();			       String s = input.readLine();
    

Collections 工具类和 Arrays 工具类常见方法

  • 排序:

    • void reverse(List list)//反转
      void shuffle(List list)//随机排序
      void sort(List list)//按自然排序的升序排序
      void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑
      void swap(List list, int i , int j)//交换两个索引位置的元素
      void rotate(List list, int distance)//旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面。
      
  • 查找、替换

    • int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的
      int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)
      int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
      void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素。
      int frequency(Collection c, Object o)//统计元素出现次数
      int indexOfSubList(List list, List target)//统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target).
      boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素
      
  • 同步控制

    • HashSet,TreeSet,ArrayList,LinkedList,HashMap,TreeMap 都是线程不安全的,线程安全的实现类有vector,stack,hashtable

    • JUC集合包中的List和Set实现类包括: CopyOnWriteArrayList, CopyOnWriteArraySet和ConcurrentSkipListSet。实现线程安全,支持高并发

      • 1, CopyOnWriteArrayList相当于线程安全的ArrayList,它实现了List接口。CopyOnWriteArrayList是支持高并发的。

        2,CopyOnWriteArraySet相当于线程安全的HashSet,它继承于AbstractSet类。CopyOnWriteArraySet 内部包含一个CopyOnWriteArrayList对象(聚合关系),它是通过CopyOnWriteArrayList实现的。

    • JUC集合包中Map的实现类包括: ConcurrentHashMap和ConcurrentSkipListMap

      • ConcurrentHashMap是线程安全的哈希表(相当于线程安全的HashMap);它继承于AbstractMap类,并且实现ConcurrentMap接口。ConcurrentHashMap是通过“锁分段”来实现的,它支持并发。

        -ConcurrentSkipListMap是线程安全的有序的哈希表(相当于线程安全的TreeMap); 它继承于AbstractMap类,并且实现ConcurrentNavigableMap接口。ConcurrentSkipListMap是通过“跳表”来实现的,它支持并发。

        ConcurrentSkipListSet是线程安全的有序的集合(相当于线程安全的TreeSet);它继承于AbstractSet,并实现了NavigableSet接口。ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的,它也支持并发。

    • JUC集合包中Queue的实现类包括: ArrayBlockingQueue, LinkedBlockingQueue, LinkedBlockingDeque, ConcurrentLinkedQueue和ConcurrentLinkedDeque

      • (01) ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列。

        (02) LinkedBlockingQueue是单向链表实现的(指定大小)阻塞队列,该队列按 FIFO(先进先出)排序元素。

        (03) LinkedBlockingDeque是双向链表实现的(指定大小)双向并发阻塞队列,该阻塞队列同时支持FIFO和FILO两种操作方式。

        (04) ConcurrentLinkedQueue是单向链表实现的无界队列,该队列按 FIFO(先进先出)排序元素。

        (05) ConcurrentLinkedDeque是双向链表实现的无界队列,该队列同时支持FIFO和FILO两种操作方式。

  • ArrayList:

    • 排序 : sort()、查找 : binarySearch()、比较: equals()、填充 : fill()、转列表: asList()

? 转字符串 : toString()、复制: copyOf()

  • 技术图片

    • 所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类, 子类:Exception、Error
    • Error(错误):是程序无法处理的错误, 与代码无关,而是 JVM的问题,发生errror会选择线程终止
  • Exception(异常):是程序本身可以处理的异常。重要子类:RuntimeException(JVM异常)NullPointerExceptionArithmeticExceptionArrayIndexOutOfBoundsException下标越界

    • public string getMessage():返回异常发生时的简要描述
    • public string toString():返回异常发生时的详细信息
    • public string getLocalizedMessage():返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同
    • public void printStackTrace():在控制台上打印 Throwable 对象封装的异常信息
    • 当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。

线程

  • 线程有6种状态:

    技术图片

技术图片

  • 线程创建之后它将处于 NEW(新建) 状态,调用 start() 方法后开始运行,线程这时候处于 READY(可运行) 状态。可运行状态的线程获得了 cpu 时间片(timeslice)后就处于 RUNNING(运行) 状态。

  • 当线程执行 wait()方法之后,线程进入 WAITING(等待)状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 TIME_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)方法或 wait(long millis)方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞) 状态。线程在执行 Runnable 的run()方法之后将会进入到 TERMINATED(终止) 状态。

文件IO

  • Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的:

    • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
    • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
  • 按照操作单元划分,可以划分为字节流和字符流;

  • 按照流的角色划分为节点流和处理流。

技术图片
  • 字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。
  • BIO (Blocking I/O): 同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机 1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
  • NIO (Non-blocking/New I/O): NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 NIO 提供了与传统 BIO 模型中的 SocketServerSocket 相对应的 SocketChannelServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
  • AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步 IO 的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO 操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。

以上是关于JavaGuide易错点总结的主要内容,如果未能解决你的问题,请参考以下文章

C++基础知识 易错点 总结(待补)

C++基础知识 易错点 总结(待补)

易错点总结

OO易错点总结

vue的使用易错点总结

指针指向空间的申请和释放易错点