zoukankan      html  css  js  c++  java
  • Java 基础知识的一些易错点

    1、正确使用 equals()

    Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。

    String str = null;
    if (str.equals("abcd")) {
      ...
    } else {
      ...
    }

    如果变量str为null,会抛出空指针异常,如果没有catch来捕获处理(我们一般不会在equals()上加try),程序直接就终止运行了。

    abcd".equals(str)

    把常量写在前面,“abcd”!=null,结果为false,不会抛出异常。

    但2个都是变量呢?

    最推荐下面的方式:使用工具类Objects(JDK7自带的)

    Objects.equals(str,"abcd")

    就算2个都是变量,2个都是null,都不会抛出异常。如果2个都是null,null==null,返回true。

    Objects的部分源码如下:

    public static boolean equals(Object a, Object b) {
            // 如果a==null的话此时a.equals(b)就不会得到执行,避免出现空指针异常。
            return (a == b) || (a != null && a.equals(b));
    }

    ||、&&都是断路的,如果||前面为true,就不会执行后面的判断;如果&&前面为false,就不会执行后面的判断。

    equals()的作用范围比==大。

    ==只能判断相同类型的数据,比如2个都是数值型(数值型归为一类)、都是字符串,都是User类型。如果2个的类型不同,比如 if(1==“1”),一个是数值型、一个是String,通不过编译。

    equals()则无此要求,不管2个的数据类型相不相同都可以。

    2、基本类型、包装类型值的比较

    2个都是基本类型,或者2个都是基本类型的常量,

    或者基本类型、包装类型(包装类型属于引用类型),或者基本类型、基本类型的常量,或者包装类型、基本类型的常量,

    只要2个不全是包装类型,进行比较,不管是使用==、还是equals(),都是使用值进行比较,都可以。

    如果2个都是包装类型,==比较的是2个对象的地址,肯定不相同。

    有特例:如果2个都是Integer,当数值在-128 ~127时,会将创建的 Integer 对象缓存起来,当下次再出现该数值时,直接从缓存中取出对应的Integer对象。

    Integer x = 3;  
    Integer y = 3;  //2个都是Integer,值相同,且在-128~127之间,不会创建一个新的Integer对象,而是直接指向x指向的对象,即x、y都指向堆中的同一个Integer对象
    System.out.println(x == y); // 都是引用类型,==根据地址进行比较,x、y的地址是相同的,返回true

    包装类型都重写了equals(),都是使用值进行比较,2个都是包装类型应该使用equals()来比较。

    3. 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类来表示浮点数可解决浮点数精度丢失的问题,因为是以字符串的形式存储数值。

    BigDecimal a = new BigDecimal("1.0");  //参数是字符串,传递数值会丢失精度
    BigDecimal b = new BigDecimal("0.9");
    BigDecimal c = new BigDecimal("0.8");
    BigDecimal x = a.subtract(b);// 0.1
    BigDecimal y = b.subtract(c);// 0.1
    System.out.println(x.equals(y));// true 

    进行数学运算也要使用BigDecimal中提供的方法,确保精度不丢失。

    BigDecimal类重写了equals(),是根据值进行比较2个BigDecimal对象。

    大小比较:

    BigDecimal a = new BigDecimal("1.0");
    BigDecimal b = new BigDecimal("0.9");
    System.out.println(a.compareTo(b));// 1

    a.compareTo(b) : 返回 -1 表示前面一个小,0 表示相等, 1表示前面一个大。

    保留几位小数:

    BigDecimal m = new BigDecimal("1.255433");
    BigDecimal n = m.setScale(3,BigDecimal.ROUND_HALF_DOWN); //第一个参数指定保留几位小数,第二个参数指定后面部分的处理方式,BigDecimal.ROUND_HALF_DOWN即四舍五入
    System.out.println(n);// 1.255

    BigDecimal的构造函数:

    //推荐这种
    BigDecimal a = new BigDecimal("1.6");
    System.out.println(a); //1.6
    
    //其次是这种,会先执行Double的toString()方法将1.6转换为字符串,再调用上面一种方式创建BigDecimal对象。相比第一种,开销大一些
    BigDecimal b = BigDecimal.valueOf(1.6);
    System.out.println(b); //1.6
    
    //其它直接传入数值的都不推荐,因为会丢失精度
    BigDecimal c=new BigDecimal(1.6);
    System.out.println(c); //1.600000000000000088817841970012523233890533447265625

    第二种的源码如下:

    public static BigDecimal valueOf(double val) {
         return new BigDecimal(Double.toString(val));
    }

    BigDecimal 主要用来操作(大)浮点数,BigInteger 主要用来操作大整数(超过 long 型)。

    BigDecimal 的实现利用到了 BigInteger, 所不同的是 BigDecimal 加入了小数位的概念

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

    Reference:《阿里巴巴Java开发手册》

    • 【强制】所有的 POJO 类属性必须使用包装数据类型。
    • 【强制】RPC 方法的返回值和参数必须使用包装数据类型。
    • 【推荐】所有的局部变量使用基本数据类型。

    比如我们如果自定义了一个Student类,其中有一个属性是成绩score,如果用Integer而不用int定义,一次考试,学生可能没考,值是null,也可能考了,但考了0分,值是0,这两个表达的状态明显不一样.

    说明 :POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或者入库检查,都由使用者来保证。

     数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。

     比如显示成交总额涨跌情况,即正负 x%,x 为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线。所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。

    5. 数组转List

    Arrays.asList()这个静态方法可以将数组转换为List:

    String[] myArray = { "Apple", "Banana", "Orange" };
    
    List<String> myList = Arrays.asList(myArray);
    //也可以直接传入数组元素
    List<String> myList = Arrays.asList("Apple","Banana", "Orange");

    看一下这个方法的源码:

    public static <T> List<T> asList(T... a) {
            return new ArrayList<>(a);
        }
    
        /**
         * @serial include
         */
        private static class ArrayList<E> extends AbstractList<E>
            implements RandomAccess, java.io.Serializable
        {
            private static final long serialVersionUID = -2764017481108945198L;
            private final E[] a;
    
            ArrayList(E[] array) {
                a = Objects.requireNonNull(array);
            }
    
            @Override
            public int size() {
                return a.length;
            }
    
            @Override
            public Object[] toArray() {
                return a.clone();
            }
    
            @Override
            @SuppressWarnings("unchecked")
            public <T> T[] toArray(T[] a) {
                int size = size();
                if (a.length < size)
                    return Arrays.copyOf(this.a, size,
                                         (Class<? extends T[]>) a.getClass());
                System.arraycopy(this.a, 0, a, 0, size);
                if (a.length > size)
                    a[size] = null;
                return a;
            }
    
            @Override
            public E get(int index) {
                return a[index];
            }
    
            @Override
            public E set(int index, E element) {
                E oldValue = a[index];
                a[index] = element;
                return oldValue;
            }
    
            @Override
            public int indexOf(Object o) {
                E[] a = this.a;
                if (o == null) {
                    for (int i = 0; i < a.length; i++)
                        if (a[i] == null)
                            return i;
                } else {
                    for (int i = 0; i < a.length; i++)
                        if (o.equals(a[i]))
                            return i;
                }
                return -1;
            }
    
            @Override
            public boolean contains(Object o) {
                return indexOf(o) != -1;
            }
    
            @Override
            public Spliterator<E> spliterator() {
                return Spliterators.spliterator(a, Spliterator.ORDERED);
            }
    
            @Override
            public void forEach(Consumer<? super E> action) {
                Objects.requireNonNull(action);
                for (E e : a) {
                    action.accept(e);
                }
            }
    
            @Override
            public void replaceAll(UnaryOperator<E> operator) {
                Objects.requireNonNull(operator);
                E[] a = this.a;
                for (int i = 0; i < a.length; i++) {
                    a[i] = operator.apply(a[i]);
                }
            }
    
            @Override
            public void sort(Comparator<? super E> c) {
                Arrays.sort(a, c);
            }
        }

    1、虽然是new ArrayList(),但方法返回值声明是List,所以此方法的结果是List,要声明为List:

    List<String> myList = Arrays.asList(myArray);  

    不能换为ArrayList

    2、它new的这个ArrayList,不是集合中ArrayList,而是Arrays的内部类ArrayList。

    虽然表面上是List,但底层仍是数组:

    private final E[] a;  //E是泛型

    3、再看一下这个内部类的构造函数:

    ArrayList(E[] array) {
       a = Objects.requireNonNull(array);
    }

    直接传的数组,java只有值传递(浅拷贝),传递引用类型时传的是地址,操作的其实就是实际的数组(传入的那个数组)。

    内部类ArrayList中的数组只有一个元素,这个元素就是传入的数组,所以调用size(),返回值是1;get(0)返回的是传入的数组;get(1)报错,显示下标超出范围,因为下标只有0。

    4、看一下这个内部类的继承关系图:

    private static class ArrayList<E> extends AbstractList<E>  //这个内部类继承了AbstractList
    public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>  //AbstractList又implements List

    就是说内部类ArrayList implements List,这个List就是集合中的那个List接口,定义了add()、remove()、set()、get()等一些列操作List集合的方法。

    我们看看上面内部类ArrayList的源码,只实现了set()、get()等方法,并没有实现add()、remove()、clear()之类的方法,

    所以这个内部类的对象可以使用add()、remove()这些方法,有代码提示、也可以通过编译,因为implments List,这些方法定义都有;但一运行会报运行时异常,因为这些方法都是抽象的,内部类ArrayList没有提供实现。

    5、内部类ArrayList显然很鸡肋,如何转换为集合中的ArrayList类?

    方法很多,下面这种是最简单的:

    ArrayList list = new ArrayList<>( Arrays.asList("a", "b", "c") )  //使用集合中ArrayList类的构造函数,传入内部类ArrayList对象即可

    怎么知道ArrayList是内部类还是集合中的那个?我们看一下内部类的声明:

    private static class ArrayList<E>

    private,内部类ArrayList只能在Arrays中使用,所以上面的ArrayList是集合中的那个。

    也正是因为private,List<String> myList = Arrays.asList(myArray);   这个方法的返回值要声明为List,不能声明为内部类ArrayList(Arrays类外不能使用此内部类)。

    数组转List集合的完整方式:

    String[] myArray = { "Apple", "Banana", "Orange" };  //数组
    
    List<String> myList = Arrays.asList(myArray);  //内部类ArraysList,中间人,桥梁

    //以上2句代码也可以写为
    List<String> myList = Arrays.asList( "Apple", "Banana", "Orange" );
    ArrayList list = new ArrayList<>( mylist );  //集合中的ArrayList

    数组反转:

            String[] arr= {"gailun", "huangzi", "zhaoxin"};
            List<String> list = Arrays.asList(arr); //将数组转换为内部类ArrayList
            Collections.reverse(list); //调用Collections工具类的静态方法实现数组反转,数组已被修改
    
            System.out.println(list); //[zhaoxin, huangzi, gailun]
    
            for (String s1:arr)
                System.out.println(s1);
            
            // zhaoxin
            // huangzi
            // gailun

    上面已经说过,创建内部类ArrayList对象时,传的是数组地址,操作的就是数组本身,对ArrayList的反转就是对数组的反转。

    不管是使用增强for循环、还是使用while+iterator遍历集合|数组,都不能在循环中增、删集合|数组元素,因为:

    1、操作的是临时变量,并不是原本的数组元素,只能进行读操作

    2、增、删元素后,迭代次数发生改变,与原本的迭代次数不一致,会导致迭代出错

    6、枚举的使用

    在java5中引入了枚举,枚举是一种特殊的类,与其它类不同,枚举使用关键字enum,不使用关键字class。

    枚举常用来代替一组常量,比如4个季节:

    public enum Season {
            SPRING,  //逗号,一般全大写
            SUMMER, 
            AUTUMN,
            WINTER;  //分号
    }

    把enum当做class即可。enum其实就是一种特殊的class,使用enum修饰会自动继承java.lang.Enum类。

    用户的状态(在线、离线、忙碌、Q我吧)、

    用户的身份(普通用户、VIP、SVIP)、

    订单状态(已付款、已发货、待取件、待收货、待退款)、

    商品库存状态(库存充足、即将售罄、缺货)、

    后台管理的角色(普通管理员、最高级别管理员)、

    登录角色(教职工、学生、游客).......

    等等,需要用一组常量来表示的,都可以使用枚举。

    枚举可以单独存在(就像类一样,单独写成一个文件),也可以内嵌在某个类中(相当于内部类):

    public class Test {
        private Season season; //声明为枚举类型
        // season=Season.SPRING;  //赋值
    
        public enum Season { //相当于内部类的形式
            SPRING,
            SUMMER,
            AUTUMN,
            WINTER;
        }
    
        public void getSeason1(Season season){
            //实用==与枚举值进行比较
            if (season==Season.SPRING)
                System.out.println("now is spring");
        }
    
        public void getSeason2(Season season){
            //在switch中使用枚举
            switch (season){
                case SPRING:
                    System.out.println("now is spring");
                    break;
                case SUMMER:
                    System.out.println("now is summer");
                    break;
                case AUTUMN:
                    System.out.println("now is autumn");
                    break;
                case WINTER:
                    System.out.println("now is winter");
                    break;
            }
    
        }
    
    }

    比如说订单状态可以作为枚举,写在Order类中,也可以把订单状态单独拿出来,写在一个单独的.java文件中。

  • 相关阅读:
    生成操作 嵌入的资源
    用JavaScript操作CSS滤镜实现最近新闻旁边的“new”
    26个ASP.NET常用性能优化方法
    kafka集群搭建(windows环境下)
    AtCoder Beginner Contest 215 F Dist Max 2(二分、尺取)
    CodeForces 1487C Minimum Ties(建图、模拟)
    C#获取 URL参数
    编程给程序员带来哪些坏习惯
    【转载】IIS和服务器安全设置教程
    [转载]在IE8下动易SiteWeaver后台编辑器按钮没有反应的解决方案
  • 原文地址:https://www.cnblogs.com/chy18883701161/p/12489083.html
Copyright © 2011-2022 走看看