Java基础中一些需要注意的问题

  • Integer的比较问题
    以下输出结果为false的是?
    Integer i01=59;
    int i02=59;
    Integer i03=Integer.valueOf(59);
    Integer i04=new Integer(59);
    
    A:System.out.println(i01==i02);
    B:System.out.println(i01==i03);
    C:System.out.println(i03==i04);
    D:System.out.println(i02==i04);

    这题的答案是C,为什么呢?
    解答

    • Java中基本类型的包装类大部分都实现了常量池技术,这些类包括Byte,Short,Integer,Long,Character
      本题中的Integer内部有一个IntegerCache类 ,该类的作用是将数值等于-128-127区间的Integer实例
      缓存到cache数组中。当使用valueOf方法创建Integer实例时,会直接复用缓存中的实例
      即,Integer i03=Integer.valueOf(59);这种方式并不会创建对象,而是直接从缓存中拿值是59的
      实例,跟Integer i01=59;一样,i01是装箱操作,其本质也是调用valueOf方法,从缓存中拿59的实例
      所以他们的对象地址是一样的,即是获取的同一个对象,如果获取的值并不在-128到127范围内,
      那就会在堆中创建对象了。
    • 我们很好判断A,B是对的,那为什么D也是对的呢?因为在i02与i04对比的时候,会将i04进行拆箱操作
      再去比较,即不管i04是不是new出来的,在与基本类型比较时,都会拆箱后再去比较。
    • C是错误的,因为在比较时,两者都是Integer类型,并不会进行拆箱操作,所以比较对象地址,为false

    总结

    • 无论如何,Integer与new Integer不会相等。不会经历拆箱过程
    • 两个都是非new出来的Integer,如果数在-128到127之间,则是true,否则为false
      java在编译Integer i2 = 128的时候,被翻译成-> Integer i2 = Integer.valueOf(128);
      而valueOf()函数会对-128到127之间的数进行缓存
    • 两个都是new出来的,都为false
    • int和integer(无论new否)比,都为true,因为会把Integer自动拆箱为int再去比

    看大佬做的图

    **练习**
    Integer a = 1;
    Integer b = 1;
    Integer c = 500;
    Integer d = 500;
    System.out.print(a == b);
    System.out.print(c == d);
    
    答案是true,false,原理就是上面的解析。
  • BigDecimal的使用
    • BigDecimal作用
      BigDecimal主要用来操作(大)浮点数,BigInteger 主要用来操作大整数(超过long类型)。
      BigDecimal可以防止出现浮点数精度丢失问题。例如以下代码就会因为精度丢失而产生问题

      float a = 1.0f - 0.9f;
      float b = 0.9f - 0.8f;
      System.out.println(a);// 0.100000024
      System.out.println(b);// 0.099999964
      System.out.println(a == b);// false
    • BigDecimal的大小比较
      a.compareTo(b) : 返回 -1 表示 a 小于 b,0 表示 a 等于 b , 1表示 a 大于 b

      BigDecimal a = new BigDecimal("1.0");
      BigDecimal b = new BigDecimal("0.9");
      System.out.println(a.compareTo(b));// 1
    • BigDecimal保留几位小数
      通过 setScale方法设置保留几位小数以及保留规则。保留规则一般是向上向下取整

      BigDecimal m = new BigDecimal("1.255433");
      BigDecimal n = m.setScale(3,BigDecimal.ROUND_HALF_DOWN);
      System.out.println(n);// 1.255
    • BigDecimal的使用注意事项
      我们在使用BigDecimal时,为了防止精度丢失,推荐使用它的BigDecimal(String)构造方法来创建对象。
      而不是使用BigDecimal(double),因为会存在精度损失的风险。

      BigDecimal bigDecimal = new BigDecimal("1.23");//推荐
      BigDecimal bigDecimal2 = new BigDecimal(1.23);//不推荐
  • Arrays.asList()的使用
    • Arrays.asList()将一个数组转换为一个List集合。但我们并不能使用该集合的,add/remove/clear方法,不然会抛出UnsupportedOperationException,通过这个方式获得的集合是Arrays的一个内部类,
      并没有实现集合的修改方法,而且当我们对原数组修改时,集合中的元素也会被修改

    • 第二个需要注意的点是:我们使用该方法传递的数组必须是对象数组,而不是基本类型。
      Arrays.asList()是泛型方法,传入的对象必须是对象数组。

      int[] myArray = {1, 2, 3};
      List myList = Arrays.asList(myArray);
      System.out.println(myList.size());//1
      System.out.println(myList.get(0));//数组地址值
      System.out.println(myList.get(1));//报错:ArrayIndexOutOfBoundsException
      int[] array = (int[]) myList.get(0);
      System.out.println(array[0]);//1

      当传入一个原生数据类型数组时,Arrays.asList()的真正得到的参数就不是数组中的元素,
      而是数组对象本身!此时List 的唯一元素就是这个数组,这也就解释了上面的代码。
      使用包装类型数组就可以解决这个问题

      Integer[] myArray = {1, 2, 3};
    • 怎么正确的将数组转换成ArrayList

      List list = new ArrayList<>(Arrays.asList("a", "b", "c"))
  • foreach循环中修改元素的问题

    不能在foreach中进行元素的add/remove操作,不然会抛出ConcurrentModificationException,
    如果要进行修改操作,可以通过迭代器的修改方法。

    //下面这段代码就是问题代码,
    //经过测试,新版jdk中,foreach循环中可以调用集合remove方法不会报错,而add方法仍然报错
    ArrayList<String> strings = new ArrayList<>();
    strings.add("hello");
    strings.add("world");
    for(String s:strings){
        if (strings.contains("hello")){
            strings.add("wanYi");
        }
    }
    
    //使用iterator方式
    ArrayList<String> strings = new ArrayList<>();
    strings.add("hello");
    strings.add("world");
    Iterator<String> iterator = strings.iterator();
    while (iterator.hasNext()){
        String s = iterator.next();
        if (Objects.equals(s,"hello")){
            iterator.remove();
        }
    }
    
    //更简单的方式,使用集合的removeIf方法
    strings.removeIf(s -> Objects.equals(s, "hello"));
  • 异常需要注意的地方
    异常
    • 如果try块中抛出了一个在catch子句中说明的异常类,那么程序将跳过try语句的其余代码,
      执行catch子句中的处理器代码。
    • 无论是否抛出异常finally语句块最终都会执行。原因是:编译器会将finally块中的代码复制两份
      并分别添加在try和catch的后面
    • 当try块中包含return语句时,在执行return语句前会先执行finally块,如果finally块中也有return语句,这个return语句的返回值会将try块中return语句的返回值覆盖掉。
    • 如果try块中抛出异常,finally中也抛出相同类型异常,那么原始异常将会丢失,转而抛出finally中的异常

    示例1

    public static void main(String[] args) {
        int result = test3();
        System.out.println(result);
    }
    
    public static int test3() {
        //try 语句块中有 return 语句时的整体执行顺序
        int i = 1;
        try {
            i++;
            System.out.println("try block, i = " + i);
            return i;
        } catch (Exception e) {
            i++;
            System.out.println("catch block i = " + i);
            return i;
        } finally {
            i = 10;
            System.out.println("finally block i = " + i);
        }
    }
    
    //输出结果如下:
    try block, i = 2
    finally block i = 10
    2
        
    /*
    	当try块中有return语句时,仍然会执行finally块的代码,
    	并且返回值是try块中的i值
    */

    示例2

    public static int test4() {
        //finally 语句块中有 return 语句
        int i = 1;
        try {
            i++;
            System.out.println("try block, i = " + i);
            return i;
        } catch (Exception e) {
            i++;
            System.out.println("catch block i = " + i);
            return i;
        } finally {
            i++;
            System.out.println("finally block i = " + i);
            return i;
        }
    }
    
    //输出结果如下:
    try block, i = 2
    finally block i = 3
    3
    
    /*
    	当try块和finally块中都有return时,返回的是finally中的i
    */

    处理异常时的建议

    public void retrieveObjectById(Long id) {
        try {
            //..some code that throws SQLException
        } catch (SQLException ex) {
            throw new RuntimeException("Exception in retieveObjectById”, ex);
        } finally {
            //clean up resultset, statement, connection etc
        }
    }
    
    /*
    	当有异常时就进行处理,抛出,如果只是ex.printStacktrace();会使得异常并没有中断程序
    	进而调用代码继续执行,导致更多的异常。
    */