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

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

版本 说明 发布日期
1.0 发布文章第一版 2020-10-24

概述

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

在编程之前

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

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太小了。
  • 但你们知道int a = 9999999998会报什么错么?不兼容的数据类型?并不是,报错的内容是:整数太大。
  • 这是为什么呢?因为在不加任何符号的情况下,1000、1000000、9999999998这些数字都是分配了4个字节的内存进行存储,并且类型为int。而某清华学子说过,我们小学二年级就学过int最大的表示范围大概是25亿,显然9999999998大于了这个数字。
  • 所以我们才需要使用int 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都是没法相等的。不信的话,给你们个网址,自己试一试浮点数的误差会有多大:https://www.h-schmidt.net/FloatConverter/IEEE754.html

==?

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

boolean类型有多大?

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

自动类型转换的一个小知识

  • 我们都知道,自动类型转换遵从小转大的原则。但是你知道么,这个小转大,并不是完全指内存的大小。比如最特殊的一个:long类型可以自动转换为float类型。
  • 又有小老弟懵逼了,其实原因很简单。long类型表示的范围是$-2^{63}$$2^{63}-1$。而float的表示范围大概是$\pm10^{38} \approx \pm2^{114}$。明显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匹配的其实不是默认情况,而是所有情况。什么意思呢?拿下面的例子说明:
int a = 1;
switch(a){
    
    
    default:
        System.out.print("default");
    case 1:
        System.out.print("1");
}
  • 上面这行代码执行结果是打印"default1",也就是说如果default在前面,后面就算有真正匹配的case,也会匹配进入default。这一点我就姑且称之为短途效应吧~(名字取得好,逼格少不了)
  • 至于穿透效应,其实就是带不带break;的问题。这里我就只是想说明一下,default语句块如果放到前面,也是需要考虑带不带break的。
  • 不过,说了这么多,有谁会在default后面写case呢?(无用知识+1)

猜你喜欢

转载自blog.csdn.net/w764476876/article/details/109259887
今日推荐