欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java基础——基础语法冷知识

程序员文章站 2022-06-22 20:19:49
这篇文章不是面面俱到的基础知识集合,只是我个人的学习笔记。说人话就是:这篇文章是JavaSE基础知识全解的真子集,并不适合完全初学的小伙伴。且以下所有内容仅代表个人观点,不一定正确。欢迎辩证~版本说明发布日期1.0发布文章第一版2020-10-24文章目录概述在编程之前为什么需要配置环境变量?exe和路径的爱恨情仇环境变量path的作用自己定义环境变量并引用基础语法基本数据类型中的冷知识(基于64位系统)基本数据类型与内存的纠缠整数变量的赋值强转,不强转?1.0和1.1的爱...

这篇文章不是面面俱到的基础知识集合,只是我个人的学习笔记。说人话就是:这篇文章是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使用的是同一块内存空间。什么意思呢?看下图:
    Java基础——基础语法冷知识
  • 像"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)

本文地址:https://blog.csdn.net/w764476876/article/details/109259887