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、增、删元素后,迭代次数发生改变,与原本的迭代次数不一致,会导致迭代出错
来源:https://www.cnblogs.com/chy18883701161/p/12489083.html