image-20220726155905046

1、Java 中浮点数的精度丢失问题以及解决方案

在 Java 中写入new BigDecimal(0.1) 所创建的 BigDecimal 正好等于 0.1,但是它实际上等于 0.1000000000000000055511151231257827021181583404541015625

Java 的浮点数只能用来进行科学计算或工程计算,在大多数的商业计算中,一般采用java.math.BigDecimal 类来进行精确计算.

使用步骤:

  1. 用 float 或者 double 变量构建 BigDecimal 对象。通常使用 BigDecimal 的构造方法或者静态方法的 valueOf() 方法把基本类型的变量构建成 BigDecimal 对象。
  2. 通过调用 BigDecimal 的加,减,乘,除等相应的方法进行算术运算。
  3. 把 BigDecimal 对象转换成 float,double,int 等类型。

BigDecimal 类 的构造函数:

1
2
3
4
BigDecimal(int var)  //创建一个具有参数所指定整数值的对象。
BigDecimal(double var) //创建一个具有参数所指定双精度值的对象。
BigDecimal(long var) //创建一个具有参数所指定长整数值的对象。
BigDecimal(String var) //创建一个具有参数所指定以字符串表示的数值的对象。

BigDecimal 类的加,减,乘,除等相应的方法:

1
2
3
4
BigDecimal add(BigDecimal augend)  //加法运算
BigDecimal subtract(BigDecimal subtrahend) //减法运算
BigDecimal multiply(BigDecimal multiplicand) //乘法运算
BigDecimal divide(BigDecimal divisor) //除法运算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
void testDouble() {
double d7 = 303.1;
double d8 = 1000.0;
BigDecimal b7 = new BigDecimal(Double.toString(d7));
BigDecimal b8 = new BigDecimal(Double.toString(d8));
System.out.println(b7.divide(b8).doubleValue());
//0.3031
}

@Test
void testToString() {
double d7 = 303.1;
double d8 = 1000.0;
BigDecimal b7 = new BigDecimal(d7);
BigDecimal b8 = new BigDecimal(d8);
System.out.println(b7.divide(b8).doubleValue());
//0.30310000000000004
}
---0627

总结

(1)需要精确的表示两位小数时我们需要把他们转换为 BigDecimal 对象,然后再进行运算。
(2)使用 BigDecimal(double val) 构造函数时仍会存在精度丢失问题,建议使用 BigDecimal(String val)

2、Java反射中getMethods()与getDeclaredMethods()区别

getMethods()获取的是所有public方法,包括:

getMethods() 无法获取当前类中被声明为private的方法

  • 类自身声明的public方法、
  • 父类中的public方法、
  • 实现的接口方法。

getDeclaredMethods()获取的是类中所有方法,也就是源文件中有哪些方法,就会获取到哪些,包括:

  • 类自身的方法、
  • 重写的父类的方法、
  • 实现的接口方法。

遇到的问题

使用getMethods() 方法无法获取到 Servlet中 被声明为==private== 的crud的方法,

导致for循环遍历methods[] 无法匹配到 与operate 相同的method(), 例如 operate=“del” ,method(): del()

出现==throw new RuntimeException(“operate值非法”);== 的问题

将getMethods()改成 getDeclaredMethods() 即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置编码
request.setCharacterEncoding("UTF-8");

String operate = request.getParameter("operate");
if(StringUtil.isEmpty(operate)){
operate = "index" ;
}
//Method []methods=this.getClass().getMethods();
Method []methods=this.getClass().getDeclaredMethods();

说白了
getMethods()会获取到:
1、本类所有public方法 (无法获取到 private )、
因此 ,由于Servlet方法被声明为了private ,如果使用getMethods() 是无法获取到Servlet方法的
2、父类,以及父类的父类的public方法
3、实现的接口方法
getDeclaredMethods()会获取到本类源码文件中所有能看到的方法 ,******************************用这个
*/
for(Method m:methods)
{
String methodName=m.getName();
if(methodName.equals(operate))
{
try {
m.invoke(this,request,response);
return ;
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
throw new RuntimeException("operate值非法");
}

3、java Stack 类

注意:Java堆栈Stack类已经过时,Java官方推荐使用Deque替代Stack使用。Deque堆栈操作方法:push()、pop()、peek()。

  • 栈是Vector的一个子类,它实现了一个标准的后进先出的栈。
  • 堆栈只定义了默认构造函数,用来创建一个空栈。 堆栈除了包括由Vector定义的所有方法,也定义了自己的一些方法。
序号 方法描述
1 boolean empty() 测试堆栈是否为空。
2 Object peek( ) 查看堆栈顶部的对象,但不从堆栈中移除它。
3 Object pop( ) 移除堆栈顶部的对象,并作为此函数的值返回该对象。
4 Object push(Object element) 把项压入堆栈顶部。
5 int search(Object element) 返回对象在堆栈中的位置,以 1 为基数。

5、System.arraycopy()

复制数组

  • 它是一个静态本地方法,由虚拟机实现,效率自然比用java一个个复制高。
1
public static void arraycopy(Object src,int srcPos, Object dest,int destPos, int length);

参数

  • Object src:the source array. 源数组
  • int srcPos:starting position in the source array. 在源数组中,开始复制的位置
  • Object dest:the destination array. 目标数组
  • int destPos:starting position in the destination data. 在目标数组中,开始赋值的位置
  • int length:the number of array elements to be copied. 被复制的数组元素的数量

一、深度复制和浅度复制的区别

​ Java数组的复制操作可以分为深度复制和浅度复制,简单来说深度复制,可以将对象的值和对象的内容复制;浅复制是指对对象引用的复制。

二、System.arraycopy()方法实现复制

1、System中提供了一个native静态方法arraycopy(),可以使用这个方法来实现数组之间的复制。

  • 对于一维数组来说,这种复制属性值传递,修改副本不会影响原来的值。

  • 对于二维或者一维数组中存放的是对象时,复制结果是一维的引用变量传递给副本的一维数组,修改副本时,会影响原来的数组。

一维数组非引用类型不会影响原来的值,

二维数组或者 元素是引用类型的一维数组 复制的结果知识对对象引用的复制,会影响原来的数组

6、java队列(Queue)

1
public interface Queue<E> extends Collection<E> {}
  • 先进先出

队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。

LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.LinkedList;
import java.util.Queue;

public class Main {
public static void main(String[] args) {
//add()和remove()方法在失败的时候会抛出异常(不推荐)
Queue<String> queue = new LinkedList<String>();
//添加元素
queue.offer("a");
queue.offer("b");
queue.offer("c");
queue.offer("d");
queue.offer("e");
for(String q : queue){
System.out.println(q);
}
System.out.println("===");
System.out.println("poll="+queue.poll()); //返回第一个元素,并在队列中删除
for(String q : queue){
System.out.println(q);
}
System.out.println("===");
System.out.println("element="+queue.element()); //返回第一个元素
for(String q : queue){
System.out.println(q);
}
System.out.println("===");
System.out.println("peek="+queue.peek()); //返回第一个元素
for(String q : queue){
System.out.println(q);
}
}
}

7、java双端队列(Deque)

1
public interface Deque<E> extends Queue<E> {}
  • Deque是一个双端队列接口,继承自Queue接口,Deque的实现类是LinkedListArrayDequeLinkedBlockingDeque

    其中LinkedList是最常用的。

Deque有三种用途

  • 普通队列(一端进另一端出):
    Queue queue = new LinkedList()Deque deque = new LinkedList()
  • 双端队列(两端都可进出)
    Deque deque = new LinkedList()
  • 堆栈
    Deque deque = new LinkedList()

注意:Java堆栈Stack类已经过时,Java官方推荐使用Deque替代Stack使用。Deque堆栈操作方法:push()、pop()、peek()。

此接口定义在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。插入操作的后一种形式是专为使用有容量限制的 Deque 实现设计的;在大多数实现中,插入操作不能失败。

第一个元素 (头部) 最后一个元素 (尾部)
抛出异常 特殊值 抛出异常 特殊值
插入 addFirst(e) offerFirst(e) addLast(e) offerLast(e)
删除 removeFirst() pollFirst() removeLast() pollLast()
检查 getFirst() peekFirst() getLast() peekLast()

Deque 中有下面几种添加元素的方法:

  • add():可以使用 Deque的addFirst()方法在Deque的头部添加元素, 如果Deque不能添加元素,addFirst()方法会抛异常, offerFirst()方法则返回 false。
  • addLast()addLast()方法也可以在 Deque的尾部添加元素,这是Deque接口与从Queue接口继承的add()方法等效
  • addFirst() 可以使用 Deque的addFirst()方法在Deque的头部添加元素
  • offer() offer()方法可以在Deque的尾部添加元素,如果元素没满则添加成功返回true,否则返回false
  • offerFirst() offerFirst()方法是在 Deque 的头部添加元素,如果添加成功返回true,否则返回false
  • offerLast() offerLast()方法在Deque的尾部添加元素
  • push()push()方法是在Deque的头部添加元素,如果Deque中的元素满了,则会抛异常,

可以查看Deque中的元素

可以查看Deque中的第一个或者最后一个元素,查看元素意味着获取元素的引用而不是移除元素,有下面几种方法:

  • peek()Deque中的第一个元素并且不删除,如果Deque是空则返回null

  • peekFirst()返回第一个元素并且不删除,如果Deque是空则返回null,这和 peek()非常相似

  • peekLast()返回最后一个元素并且不删除,如果Deque是空则返回null

  • getFirst()getFirst()方法获取Deque的第一个元素并且不删除,如果Deque是空则抛异常

  • getLast() getLast()获取Deque的最后一个元素并且不删除,如果Deque是空则返回null

Deque接口扩展(继承)了 Queue 接口。在将双端队列用作队列时,将得到 FIFO(先进先出)行为。将元素添加到双端队列的末尾,从双端队列的开头移除元素。从 Queue 接口继承的方法完全等效于 Deque 方法,

Queue方法 等效Deque方法
add(e) addLast(e)
offer(e) offerLast(e)
remove() removeFirst()
poll() pollFirst()
element() getFirst()
peek() peekFirst()

双端队列也可用作 LIFO(后进先出)堆栈。应优先使用此接口而不是遗留 Stack 类。在将双端队列用作堆栈时,元素被推入双端队列的开头并从双端队列开头弹出。堆栈方法完全等效于 Deque 方法,如下表所示:

堆栈方法 等效Deque方法
push(e) addFirst(e)
pop() removeFirst()
peek() peekFirst()

Deque的使用场景
在一般情况,不涉及到并发的情况下,有两个实现类,可根据其自身的特性进行选择,分别是:

  • LinkedList 大小可变的链表双端队列,允许元素为插入null。
  • ArrayDeque 大下可变的数组双端队列,不允许插入null。
  • ConcurrentLinkedDeque 大小可变且线程安全的链表双端队列,非阻塞,不允许插入null。
  • LinkedBlockingDeque 为线程安全的双端队列,在队列为空的情况下,获取操作将会阻塞,直到有元素添加。

注意:LinkedList 和 ArrayDeque 是线程不安全的容器。

8、java中的list为空(size==0)与list为null的区别

  • 举个例子,我有一个空着的水杯(list),而你没有,那你是null,我的size为0。你想装水就需要自己去买个水杯(new ArrayList();),\

    但是我就可以直接装水(list.add(水))。你要是没有杯子直接倒水,水就流出去(NullPointerException)。

isEmpty() 或者(list.size() == 0)用于判断List内容是否为空,即表里一个元素也没有, 但是使用isEmpty()和size()的前提是,list是一个空集合,而不是null,所以为了避免异常,建议在使用或赋值list集合之前,做一次空集合创建处理,进行内存空间分配,

实测isEmpty速度要比size慢

  • null
    list==null,其实就是没有为这个list 分配空间 , 如果直接使用就会NullPointerException

例子:

1
2
3
4
5
6
7
@Test
public void test(){
List<Integer> list1=List.of();
List<Integer> list2=null;
System.out.println(list1.get(0));
System.out.println(list2.get(0));
}
  • 直接运行
    • list1.get(0)的异常是java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
    • list2.get(0)的异常是java.lang.NullPointerException