java基础——有多少是你不知道的?

一、&&和||

1、下面的程序你猜会打印什么呢?
在这里插入图片描述

结果为以下:
在这里插入图片描述

2、解释
&&符号当左边为false的时候就不再执行右边的判断了,因为程序已经可以确定为false了,所以b++并没有执行

||符号当左边为true的时候就不在执行左边的判断了,因为程序已经可以确定为true了,所以d++被执行了

3、这种判断机制的用途
可以解决先后判断问题,例如以下程序
在这里插入图片描述
先判断非空再判断字符串长度是没有错的,因为s为空的时候,就不会去执行s.length()==5

反过来,先判断字符串长度的话会立马抛出空指针异常
在这里插入图片描述

二、Integer和int

1、下面的题你能做对吗?输出结果是什么呢?
在这里插入图片描述

结果:
在这里插入图片描述

2、解释
Integer是int的包装类,Integer引用的是堆内存新建的对象,int引用的是java常量池里的数据

Integer和int会有拆箱装箱机制
int和Integer比较时,Integer会自动拆箱为int
int赋值Integer数据时,Integer会自动拆箱为int
Integer赋值int数据时,int会自动装箱为Integer
int和int用==比较时,比较的是值的大小
int和Integer用==比较时,Integer会自动拆箱为int,比较的是值的大小
Integer和Integer用==比较时,比较的是两个对象是否是同一个的引用

int自动装箱的小细节:对于-128~127的数据,int会判断缓存里有没有相对应值的Integer对象,如果有,直接返回该对象,否则新创建一个对象,并把对象放到缓存中。这样做是为了对于频繁使用的数据只保存一份数据,减少内存的浪费

package com.wu.hello.main;

public class Main {
    public static void main(String[] args) {
        int a=0;
        int b=0;
//        true,int和Integer用==比较时,Integer会自动拆箱为int,比较的是值的大小
        System.out.println(a==b);

        Integer c=new Integer(1);
        Integer d=new Integer(1);
//        false,Integer和Integer用==比较时,比较的是两个对象是否是同一个的引用
        System.out.println(c==d);

        Integer e=2;
        Integer f=2;
//        true,int自动装箱的小细节:对于-128~127的数据,int会判断缓存里有没有相对应值的Integer对象,
//        如果有,直接返回该对象,否则新创建一个对象,并把对象放到缓存中。
        System.out.println(e==f);

        Integer g=200;
        Integer h=200;
//        false,int自动装箱的小细节:对于-128~127的数据,int会判断缓存里有没有相对应值的Integer对象,
//        如果有,直接返回该对象,否则新创建一个对象,并把对象放到缓存中。
        System.out.println(g==h);

        Integer i=new Integer(200);
        int j=new Integer(200);
//        true,int和Integer用\=\=比较时,Integer会自动拆箱为int,比较的是值的大小
        System.out.println(i==j);
    }
}

三、String、StringBuffer、StringBuilder的区别

1、区别

StringStringBufferStringBuilder
内存位置java常量池/堆空间堆空间堆空间
线程安全问题线程安全线程安全线程不安全
是否可变不可变可变可变
效率最慢一般最快
使用场景不需要更改时使用多线程单线程

2、String是不可变的是什么意思?
例如字符串s=“abc”,如果想要s=“adc”,不可以直接把字符 ‘b’ 修改为 ‘d’ ,而要新创建一个对象"adc",让s去引用它

3、String为什么是最慢的?
因为String对象是不可变的,每次修改的时候都需要产生一个新的对象
例如字符串拼接:“abc”+“d”,需要新创建一个字符串对象"abcd",而StringBuffer和StringBuilder可以直接修改当前对象

4、什么线程不安全?
在多线程的情况下会出现错误,例子:多个线程同时修改StringBuilder对象

package com.wu.hello.main;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        StringBuilder s=new StringBuilder();
        for (int i = 0; i < 10; i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 1000; j++){
                        s.append("a");
                    }
                }
            }).start();
        }
        Thread.sleep(1000);
        System.out.println(s.length());
    }
}

在这里插入图片描述

报了异常,按道理应该输出10000的,因为多线程问题,使得结果发生了错误,这就是线程不安全

5、StringBuilder线程不安全的原因

因为多个线程操作的是同一个StringBuilder对象,而StringBuilder的方法调用时没有加锁,导致多个线程同时进入方法,出现不一致问题,例如线程1进入方法后获取了数据,线程2修改了字符串,那么线程1拿到的就是脏数据

而StringBuffer的每个方法是加了synchronized的,所以多线程是没问题的

在这里插入图片描述

6、同样地,HashMap 、HashSet都是非线程安全的,如果在多线程环境下,可以使用 ConcurrentHashMap、ConcurrentHashSet来代替,当然效率也会降低一些

四、i+1<i居然是成立的?

1、笔试题:是否存在数字 i+1<i?存在吗?
在计算机里面是存在的,Int类型的数占32位,当int是最大值的时候,再加1,那么就会变为最小值

package com.wu.hello.main;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        int maxValue = Integer.MAX_VALUE;
        System.out.println(maxValue);

        System.out.println(maxValue+1<maxValue);
    }
}

在这里插入图片描述

五、一脸懵逼的null问题

1、先看程序

package com.wu.hello.main;

public class NULL {

    public static void haha(){
        System.out.println("haha");
    }

    public static void main(String[] args) throws InterruptedException {
        ((NULL)null).haha();
    }
}

2、这嘛玩意?这个程序真的可以跑的吗?真的!
在这里插入图片描述

3、解释
其实NULL是类的名字,在java里面,null可以转成任何对象。转了之后也就相当于以下程序:

package com.wu.hello.main;

public class NULL {

    public static void haha(){
        System.out.println("haha");
    }

    public static void main(String[] args) throws InterruptedException {
        NULL n=null;
        n.haha();
    }
}

所以,你大概懂了吧!haha()是static方法,null对象同样可以调用!当然的,不是static方法的话就会报空指针异常了

在这里插入图片描述

六、整数除法向上取整你知道多少种?

package com.wu.hello.main;

public class Main {

    public static void main(String[] args) {
        int a=20;
        int b=6;

        //方法1
        System.out.println((int)Math.ceil((double) a/b));

        //方法2
        System.out.println(a/b+(((a%b)!=0)?1:0));

        //方法3
        System.out.println((a+b-1)/b);

    }
}


在这里插入图片描述

最常用的向上取整就是方法3啦

七、这也能运行?

1、先看程序

package com.wu.hello.main;

public class Main {

    public static void main(String[] args) {
        https://www.baidu.com/;
        System.out.println("haha");
    }
}

2、你觉得这个程序可以运行吗?
答案是可以滴
在这里插入图片描述

3、解释
实际上的程序是这样的:

package com.wu.hello.main;

public class Main {

    public static void main(String[] args) {
        https:
        System.out.println("haha");
    }
}

实际上https: 是goto语句的写法,这种写法已经快被弃用了,原因是goto语句会使程序变得杂乱无章,程序维护困难。

可以看以下程序理解goto用法:

package com.wu.hello.main;

public class Main {

    public static void main(String[] args) {
        https:
        while (true){
            break https;
        }
        System.out.println("haha");
    }
}

java对c++的goto语法做了优化,只能接在break、continue后面,跳出循环或者是重新执行循环

八、多线程的bug?

1、认真看看以下程序,线程1和线程3都可以证明stop等于true,线程2为什么会停不下来呢?

package com.wu.hello.main;


public class Main {

    static boolean stop = false;

    public static void main(String[] args) {


//        线程1
        new Thread(()->{
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            stop=true;
            System.out.println("线程1执行完毕,stop="+stop);
        }).start();

//        线程2
        new Thread(()->{
            int i=0;
            while (!stop){
                i++;
            }
            System.out.println("线程2执行完毕,i="+i);
        }).start();

//        线程3
        new Thread(()->{
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("线程3执行完毕,stop="+stop);
        }).start();

    }
}


在这里插入图片描述

2、解释

首先我们看百度的解释:
(1)、JIT

即时编译(Just-in-time Compilation,JIT)是一种通过在运行时将字节码翻译为机器码,从而改善字节码编译语言性能的技术。

(2)、热点代码

在Java的编译体系中,一个Java的源代码文件变成计算机可执行的机器指令的过程中,需要经过两段编译,第一段是把.java文件转换成.class文件。第二段编译是把.class转换成机器指令的过程。

当JVM发现某个方法或代码块运行特别频繁的时候,就会认为这是“热点代码”(Hot Spot Code)。JIT会把部分“热点代码”class翻译成本地机器相关的机器码,并进行优化,然后再把翻译后的机器码缓存起来,以备下次使用。

正是因为jit的问题,while循环被多次执行后被jit给优化了,程序已经被修改了

3、证明
在VM中添加-Xint参数,使用解释执行的意思
在这里插入图片描述
在这里插入图片描述
可以看到,程序执行了22428641次,最终停了下来,解决了这个问题,但是不是用jit会大大减低程序的性能,因为虚拟机不会给我们做优化了,使得被优化不会出错的程序也得不到优化

4、一个好得解决方案
对于多线程的共享资源,可以使用volatile来修饰变量,volatile可以帮我们保证程序的可见性、有序性,但是仍然不能保证原子性

package com.wu.hello.main;


public class Main {

    static volatile boolean stop = false;

    public static void main(String[] args) {


//        线程1
        new Thread(()->{
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            stop=true;
            System.out.println("线程1执行完毕,stop="+stop);
        }).start();

//        线程2
        new Thread(()->{
            int i=0;
            while (!stop){
                i++;
            }
            System.out.println("线程2执行完毕,i="+i);
        }).start();

//        线程3
        new Thread(()->{
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("线程3执行完毕,stop="+stop);
        }).start();

    }
}

在这里插入图片描述

5、详细说说volatile
线程安全要考虑三个方面:可见性、有序性、原子性

可见性: 一个线程对共享变量修改,另一个线程能看到最新的结果

有序性: 一个线程内代码按编写顺序执行

原子性: 一个线程内多行代码以一个整体运行,期间不能有其它线程代码插队

程序的可见性怎么就如本例子,解决了线程2读不到stop最新值的问题

原子性就是,比如以下程序,一个线程对a+=5,另一个线程a-=5,那么a的结果一定为0?答案是否定的,5和-5也有可能出现,因为a+=5虽然是一行代码,但是编译了过后是多条指令,包括了取数据,计算,回存等等,这些操作不能被保证是原子的,线程1取到数据后,线程2把数据给修改了,线程1就读到了脏数据

package com.wu.hello.main;


import java.util.concurrent.CountDownLatch;

public class Main {
    
    static int a=0;

    public static void main(String[] args) throws InterruptedException {
        
        CountDownLatch latch = new CountDownLatch(2);

        //        线程1
        new Thread(() -> {
            a += 5;
            latch.countDown();
        }).start();

        //        线程2
        new Thread(() -> {
            a -= 5;
            latch.countDown();
        }).start();

        latch.await();

        System.out.println("a=" + a);
    }

}

有序性就是,下面的一个程序,显而易见打印的情况可能有3种:
x=0
y=0

x=1
y=1

x=0
y=1

那么,有没可能出现x=1而y=0的情况呢?答案是可能的,因为程序在编译的时候会对程序进行优化,在不改变单个线程结果的情况下,代码的执行顺序是可以改变的,也是说线程1可能会先 执行x=1;再执行y=1;,导致出现x=1而y=0的情况

package com.wu.hello.main;


import java.util.concurrent.CountDownLatch;

public class Main {

    static int x=0;
    static int y=0;

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch latch = new CountDownLatch(2);

        //        线程1
        new Thread(() -> {
            y=1;
            x=1;
        }).start();

        //        线程2
        new Thread(() -> {
            System.out.println("x="+x);
            System.out.println("y="+y);
        }).start();
    }

}

volatile修饰变量可以解决这个问题

package com.wu.hello.main;


import java.util.concurrent.CountDownLatch;

public class Main {

    static volatile int x=0;
    static volatile int y=0;

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch latch = new CountDownLatch(2);

        //        线程1
        new Thread(() -> {
            y=1;
            x=1;
        }).start();

        //        线程2
        new Thread(() -> {
            System.out.println("x="+x);
            System.out.println("y="+y);
        }).start();
    }

}

所以总结时volatile可以保证可见性、有序性,而原子性需要用锁机制解决

觉得写得不错就点个赞吧!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Java中,由于浮点数的精度问题,可能会出现无限循环小数。对于这种情况,可以使用BigDecimal类来处理。 BigDecimal类可以表示任意精度的十进制数,它可以处理大于64位的整数和小数。在使用BigDecimal类时,需要注意以下几点: 1. 使用BigDecimal类时,应该使用其构造方法来初始化BigDecimal对象,而不是使用double类型的值来初始化。 例如: ``` BigDecimal bd = new BigDecimal("0.1"); ``` 2. 在进行浮点数运算时,应该使用BigDecimal类提供的方法,而不是使用double类型的运算符。 例如: ``` BigDecimal a = new BigDecimal("0.1"); BigDecimal b = new BigDecimal("0.2"); BigDecimal c = a.add(b); ``` 3. 在比较两个BigDecimal对象的值时,应该使用compareTo()方法,而不是使用等于(==)或不等于(!=)运算符。 例如: ``` BigDecimal a = new BigDecimal("0.1"); BigDecimal b = new BigDecimal("0.2"); if (a.compareTo(b) < 0) { System.out.println("a < b"); } else if (a.compareTo(b) > 0) { System.out.println("a > b"); } else { System.out.println("a = b"); } ``` 关于java无限循环小数判定问题,可以通过以下代码来解决: ``` public static boolean isRepeatingDecimal(BigDecimal num) { BigDecimal[] arr = num.divideAndRemainder(BigDecimal.ONE); BigDecimal decimalPart = arr[1]; String decimalStr = decimalPart.toString().substring(2); // 去掉小数点和整数部分 if (decimalStr.length() < 2) { return false; // 小数部分长度小于2,不可能循环 } int halfLength = decimalStr.length() / 2; for (int i = 1; i <= halfLength; i++) { String str = decimalStr.substring(0, i); if (decimalStr.matches("^(" + str + ")+(.*)" + str + "$")) { return true; // 存在循环节 } } return false; // 不存在循环节 } ``` 该方法接收一个BigDecimal对象,判断其是否为无限循环小数,返回true或false。 以上是关于Java中处理无限循环小数的基础知识和方法介绍,希望能对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旧林墨烟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值