飞道的博客

Java基础(一)——基础语法冷知识

368人阅读  评论(0)

这篇文章不是面面俱到的基础知识集合,只是我个人的学习笔记。说人话就是:这篇文章是JavaSE基础知识全解的真子集,并不适合完全初学的小伙伴。且以下所有内容仅代表个人观点,不一定正确。欢迎辩证~

版本 说明 发布日期
1.0 发布文章第一版 2020-10-24
1.1 调整部分标题,让其语义更直接 2020-10-28
整数变量的赋值问题中,将int a改为long a。这是一处笔误。
修正自动类型转换的小转大原则中的数学公式错误
1.2 修正小节“switch与default的故事”的错误内容。default在前并不会始终执行 2020-11-10

概述

  • 本篇文章只罗列了我所知道的,基本不涉及类知识的一些不太容易被人知晓的冷知识。不过其实这些知识大多对生产没有太大用处。但是说不定哪天这些东西会帮助自己避坑呢?

在编程之前

为什么需要配置环境变量?

exe和路径的爱恨情仇

  • Windows执行可执行文件时,只能识别当前目录下的exe文件,而javac和java这两个可执行文件,都在jdk\bin中。我们当然不能把所有.java文件都扔到这个目录里面去执行,这时候就需要用到path环境变量。

环境变量path的作用

  • path环境变量让Windows执行exe之前,先去path变量中从上到下(win7是从前到后)遍历,如果其中存在对应的exe,则可以直接执行。
  • 正因为如此,我们才需要把jdk\bin目录加入到path环境变量中

自己定义环境变量并引用

  • 而为了方便今后jdk路径变更以及Java EE的使用,我们又将jdk的路径单独设置一个环境变量JAVA_HOME,然后path中填写 J A V A H O M E JAVA_HOME JAVAHOME\bin就完事儿了。以后如果要变更路径,去改JAVA_HOME就行啦。
  • PS:java11之后不需要配置classpath了。

基础语法

基本数据类型中的冷知识(基于64位系统)

基本数据类型与内存的纠缠

  • 或许大家知道基本数据类型存储在栈区中,数组和引用数据类型存储在堆区中。但是大家可能容易忽略一个小细节。
int a = 1;
int b = 1;
  • 上面这行代码,a和b使用的是同一块内存空间。什么意思呢?看下图:

  • 像"1"这样的基本数据类型的值,我们称为直接量。int b = 1;时,会先查找栈中是否存在直接量"1",如果找到了,那么直接使用这个直接量。
  • 所以真正存储在栈区中的其实是直接量,相同的直接量,不会重复占用多个内存空间,这就是上面代码a和b使用同一地址的原因。而不同的值会被分配到不同的地址去,所以如果a=1、b=2,那么他们的地址又是不同的。
  • 不同类型的相同值,也是会被分配到不同地址,很好理解嘛,因为他们需要的内存大小都不一样嘛。比如1.0和1.0F,他们的在栈中的地址是不一样的。
  • 尽管这个特性看起来很像是“引用”,但是我们需要避免这样去称呼他们。因为“引用”指的是利用栈区中的内存地址,指向堆区中的数据。

整数变量的赋值问题

  • 我们都知道byte a = 1000; short b = 1000000之类的赋值会报错:不兼容的数据类型,从int到byte(short)。因为byte和short太小了。
  • 但你们知道long a = 9999999998;会报什么错么?不兼容的数据类型?并不是,报错的内容是:整数太大。
  • 这是为什么呢?因为在不加任何符号的情况下,1000、1000000、9999999998这些数字都是分配了4个字节的内存进行存储,并且类型为int。而我们小学二年级就学过int最大的表示范围大概是21亿,显然9999999998大于了这个数字。
  • 所以我们才需要使用long a = 9999999998L;来解决这个问题。
  • 说到这儿,就像顺道说一下,浮点数(例如1.0)的默认类型是double,不是float哟~所以float b = 1.0F才能正确给float赋值哟~~~~

强转,不强转?

  • 看了上面两小节之后,细的朋,哦不,细心的朋友们就要问了:byte a = 1;为什么不报错?float b = 1.0;为什么报错?
  • 这个问题我也只知道一个很浅显的答案:对于byte范围内的值,JVM会自动将int(例如1)处理为byte,所以前者不报错。而JVM表示并不想把doule处理成float,即使这个值在float的表示范围内。
  • 底层原因的话,猜测一下?可能是因为float和double是科学技术法表示的,转起会恶心到JVM?望有大佬能够明确一下答案。

1.0和1.1的爱恨情仇

  • 我画你猜,a和b那个是true?那个是false?:
boolean a = 1.0F == 1.0;
boolean b = 1.1F == 1.1;
  • 答案是,a真b假。
  • 哦哟,搞啥子哦?为啥子喃?简单说一说:因为float和double都是近似值,并且精度不同,所以同样是1.1,他们真实的值都有误差,并且误差程度不同,自然值就不同啦~而1.0之所以相同,纯粹是巧合,因为刚好float和double都能精确表示1.0。
  • 也就是说,99.9%的情况,float和double都是没法相等的。不信的话,给你们个网址,自己试一试浮点数的误差会有多大:浮点数网站

==?equals?

  • 我们小学2年级都学过:==比较的是地址,equals通常比较的是具体的内容(具体得看重写的方法是怎么写的)。
  • 这句话莫得毛病,但是点小有问题:基本数据类型的==比较,比的是数值;而引用类型比的是地址。不然为什么1.0 == 1.0F是true呢?是吧,哈哈哈。

boolean类型有多大?

  • 这个问题标准答案是:没有答案。对,就是这样,因为JAVA官方没有指明一个boolean占多大内存。但是大众普遍认为大小是1个字节,也不能算错吧。但更合理的猜测,不应该是一个bit么?哈哈。

自动类型转换的小转大原则

  • 我们都知道,自动类型转换遵从小转大的原则。但是你知道么,这个小转大,并不是完全指内存的大小。比如最特殊的一个:long类型可以自动转换为float类型。
  • 又有小老弟懵逼了,其实原因很简单。long类型表示的范围是 − 2 63 -2^{63} 263 2 63 − 1 2^{63}-1 2631。而float的表示范围大概是 ± 1 0 38 ≈ ± 2 114 \pm10^{38} \approx \pm2^{114} ±1038±2114。明显float能表示的数字更大,所以能够自动转换。当然,自动转换之后,也不可避免地会产生严重的误差。

强制类型转换的精度问题

  • byte a = 128;的结果是多少呢?这就要涉及到强转的数值取舍了。小学二年级的时候都学过,128转换为二进制为:0000 0000 1000 0000。
  • 因为byte类型就1个字节,所以强转之后,肯定需要舍弃一半。java在进行强制转换时,舍弃的是高位部分,所以a的值最终为:1000 0000。也就是-128。

变量初始值

  • 以前一直以为基本数据类型始终会有一个初始值,后来试了试,结果试试就逝世。
  • 直接上结论吧
    • 类成员变量(包括静态的),无论是基础数据类型还是引用数据类型,在不初始化的情况下都有初始值。基本数据类型除了char,其他的初始值都是0(或者0.0)。char类型初始值是’’。而引用数据类型初始值统一为null。
    • 而方法中的局部变量,不论是基本数据类型,还是引用类型,只要不初始化,直接编译就报错。
  • 也就是说,下面这段代码,打印结果是0。如果把te.wa换成age,则直接编译报错。
public class VarTest {
   
	int wa;
	public static void main(String[] args) {
   
		int age;
		VarTest te = new VarTest();
		System.out.println(te.wa);
	}
}

运算符中的冷知识

除以一个0.0试试

  • 小学二年级就学过,java中除以0会报arithmeticExcetption。
  • 那大家知不知道java能否除以0.0呢?答案是可以说可以,也可以说不可以~
  • 例如:5 / 0.0的结果是infinity;0 / 0.0的结果是NaN。显然都不是一个正常的数字,所以我们肯定也不应该去除以0.0的哈。

赋值运算本身也会返回一个值

  • 什么意思呢?其实很(没)简(有)单(用)。举个栗子:
int a, b;
a = b = 3;
  • 这段代码执行完之后,a和b都被赋值为了3。因为b = 3这个表达式的结果是3。

算数运算结果的数据类型是什么?

  • 小伙伴们觉得下面这段代码执行完后,a等于多少?
byte a = 10;
a = a + 2;
  • 结果等于:报错~哈哈哈,想不到吧。其实这是JAVA编译器自动进行了优化处理。因为byte的运算(算术运算、移位运算等)的计算结果被编译器自动转换为了int。而int赋值给byte很显然是会报错的。
  • 所以当我们真的想写这样一段逻辑的话,需要使用如下写法:
byte a = 10;
a = (byte)(a + 2);
  • 或者!还可以如下:
byte a = 10;
a += 2;
  • 这就很骚了,原来a += 2;等价的不是a = a + 2;,而是a = (byte)(a + 2);
  • 同理,不光是+、也不光是short会有这种优化。可以自行尝试一下其他地方是否也有如此特性。

如何高效(装逼)地运算i*4?

  • 大家都知道java中的位运算效率是最高的。因为计算机底层就是二进制,如果直接对二进制进行操作,java不需要再费精力去将变量与二进制进行转换。
  • 所以要高效地运算,最好的方法就是采用位运算。而移位运算符刚好就具备倍乘、倍除的特性。
  • byte类型的9,二进制为0000 1001。左移一位,变为0001 0010,值为18。不用怀疑,就是二倍。同理,右移一位0000 0100,值为4。也不用怀疑,就是整数/2的结果。
  • 所以的所以,如何高效运算i*4?答案是i = i << 2;
  • 当然,我劝大家可别在实际开发中这样写,坏处很明显:容易被打…好吧,其实是可读性太低。并且如果除符号位的最高位是1,左移之后的结果就很迷了。综上,无用知识+1~

流程控制中的冷知识

for( ; ; ){}

  • for( ; ; ){}语句可以正常运行,结果是死循环。

switch与default的故事

  • 其实这一节不算冷门,但switch确实用得太少了,导致这个问题之前并不知道。
  • 众所周知,switch售价2000元…说岔了,众所周知,switch有一个default,用于匹配默认情况。但是default与switch之间有一些纠葛:
    • default无论放在前面还是后面,都只有当没有匹配到case的时候才会被执行;
    • default执行的时候,和case一样,如果没有break,则会继续执行后面case中的代码;
    • 结合前两点,当default戴上了心爱的小break,那么他无论放到switchz中的什么位置,对程序执行都是没有任何影响的。
  • 让我们感受一下这俩小伙:
public class SwitchTest {
   
    public static void main(String[] args) {
   
        for(int a = 0; a < 3; a++){
   
            switch(a){
   
                case 1:
                    System.out.println("1");
                default:
                    System.out.println("default");
                case 2:
                    System.out.println("2");
            }
            System.out.println("==========");
        }
    }
}
  • 这段代码执行结果如下。
default
2
==========
1
default
2
==========
2
==========
  • 但如果我们给System.out.print("default");后面加一个break;。结果又变成如下,完全符合我上面所说的。
default
==========
1
default
==========
2
==========

转载:https://blog.csdn.net/w764476876/article/details/109259887
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场