一、項目地址 https://github.com/LinFeng-BingYi/DailyAccountBook 二、新增 1. 在表格中設置選項列表,讓用戶更快地編輯動賬記錄 1.1 功能詳述 為表格中以下欄位設置選項列表: 1. 需求強度(由"基本需求"更名) 溫飽:基本維持生存且不鋪張浪費的 ...
一元運算符之正負號
Java支持多種一元運算符,一元運算符中的“一元”是指一個操作數。我們初中學過的正負號就屬於一元運算符,因為正負號後面只有一個數字。
正數使用+
表示,其中+
可以省略;負數使用-
表示。如果變數的值是數值類型,也可以在變數前面加上正負號。
/**
* 正負號的表示
*
* @author iCode504
* @date 2023-10-06 19:49
*/
public class PlusAndMinusSign {
public static void main(String[] args) {
int intValue1 = 20; // 正數,加號可忽略
int intValue2 = -40; // 負數
System.out.println("intValue1 = " + intValue1);
System.out.println("intValue2 = " + intValue2);
// 變數的前面也可以加上正負號
int intValue3 = 40;
int intValue4 = -intValue3;
System.out.println("intValue3 = " + intValue3);
System.out.println("intValue4 = " + intValue4);
// 加上符號的變數也可以參與運算,以下兩個變數相乘得到的結果是相同的
int intValue5 = intValue3 * intValue4; // 推薦寫法
int intValue6 = intValue3 * -intValue3; // 不推薦,可讀性變差
System.out.println("intValue5 = " + intValue5);
System.out.println("intValue6 = " + intValue6);
// 負數前面加上負號為正數(負負得正)
int intValue7 = -(-20);
int intValue8 = -intValue4; // intValue4本身的值就是負數
System.out.println("intValue7 = " + intValue7);
System.out.println("intValue8 = " + intValue8);
}
}
運行結果:
根據intValue7
和intValue8
的輸出結果我們可以得知,負號可以改變數值的正負,正數加了負號變負數,負數加負號可以變正數(負負得正)。
編寫代碼不推薦int intValue6 = intValue3 * -intValue3;
這種寫法,雖然能得到預期結果,但是右側計算的表達式可讀性變差,可能會造成誤解。
算數運算符
算術運算符的基本使用
在大多數編程語言中,算術運算符基本上由加+
、減-
、乘*
、除/
、取餘%
(也稱“取模”,也就是兩個數相除的餘數)組成,以上五個運算符在Java中也完全適用。
/**
* 算術運算符--加減乘除、取餘
*
* @author iCode504
* @date 2023-10-08 7:01
*/
public class MathOperators1 {
public static void main(String[] args) {
int intValue1 = 22;
int intValue2 = 5;
// 加減乘除運算
int result1 = intValue1 + intValue2;
System.out.println("intValue1 + intValue2 = " + result1);
int result2 = intValue1 - intValue2;
System.out.println("intValue1 - intValue2 = " + result2);
int result3 = intValue1 * intValue2;
System.out.println("intValue1 * intValue2 = " + result3);
// 兩個整除相除,只保留整數部分,不會進行四捨五入操作
int result4 = intValue1 / intValue2;
System.out.println("intValue1 / intValue2 = " + result4);
// 兩個整數取餘:22對5取餘得到的結果是2
int result5 = intValue1 % intValue2;
System.out.println("intValue1 % intValue2 = " + result5);
}
}
運行結果:
兩個整數運算得到的結果是整數,兩個浮點數運算得到的結果是浮點數,整數和浮點數進行運算時得到的結果是浮點數(因為整數類型會自動提升為浮點類型)。
/**
* 整數和浮點數的運算、byte/short/char類型的運算
*
* @author iCode504
* @date 2023-09-28 15:47:46
*/
public class MathOperators2 {
public static void main(String[] args) {
// 定義兩個變數intValue1,intValue2並賦值
int intValue1 = 20;
int intValue2 = 40;
// 直接輸出intValue1和intValue2相加的和
// 註意:下方輸出時,需要對要計算的表達式加上括弧,防止intValue1和intValue2轉換成字元串類型
System.out.println("intValue1 + intValue2 = " + (intValue1 + intValue2));
System.out.println("----------分割線----------");
// byte、short、char進行運算時,會自動提升為int類型計算。
// 如果轉換成想要的小範圍數據類型,需要進行強制類型轉換
byte byteValue = 30;
short shortValue = 50;
char charValue = 30;
// 錯誤寫法:
// byte byteValue1 = byteValue + shortValue;
// 正確寫法: 將計算的結果轉換成小範圍數據類型。註意:強制類型轉換時需要考慮到數據溢出的問題。
byte byteValue1 = (byte) (byteValue + shortValue);
short shortValue1 = (short) (shortValue + charValue);
char charValue1 = (char) (byteValue + charValue); // 得到的結果是Unicode字元表中對應的字元
System.out.println("byteValue1 = " + byteValue1);
System.out.println("shortValue1 = " + shortValue1);
System.out.println("charValue1 = " + charValue1);
System.out.println("----------分割線----------");
// 浮點數參與計算:整數會自動提升為浮點類型
double doubleValue1 = 0.1;
double doubleValue2 = 0.2;
int intValue3 = 30;
System.out.println("doubleValue1 + intValue3 = " + (doubleValue1 + intValue3));
System.out.println("doubleValue1 + doubleValue2 = " + (doubleValue1 + doubleValue2));
}
}
運行結果:
浮點數計算為什麼不准確?
從上述結果我們發現一個問題,double
類型的值0.1
和0.2
相加得到的結果並不是0.3
,而是0.30000000000000004
,為什麼?
假設有兩個浮點數0.1
和0.2
,如果兩個值賦值給float
類型和double
類型,相加計算是不是0.3?
我們使用Java代碼來測試一下:
/**
* 浮點數0.1和0.2分別使用float類型和double類型計算
*
* @author iCode504
* @date 2023-10-06 17:00:21
*/
public class DecimalCalculation1 {
public static void main(String[] args) {
// float類型相加計算
float floatValue1 = 0.1f;
float floatValue2 = 0.2f;
System.out.println("floatValue1 + floatValue2 = " + (floatValue1 + floatValue2));
// double類型相加計算
double doubleValue1 = 0.1;
double doubleValue2 = 0.2;
System.out.println("doubleValue1 + doubleValue2 = " + (doubleValue1 + doubleValue2));
double doubleValue3 = 0.5;
double doubleValue4 = 0.8;
System.out.println("doubleValue3 + doubleValue4 = " + (doubleValue3 + doubleValue4));
}
}
運行結果:
此時發現一個問題:doubleValue1 + doubleValue2 = 0.30000000000000004
並沒有得到我們預期的結果,為什麼?
事實上,0.1 + 0.2
的結果在大多數編程語言中進行運算時也會得到上述結果,點我查看
眾所周知,電腦在底層計算使用的是二進位。無論是整數還是浮點數都會轉換成二進位數進行運算。以下是小數轉為二進位數運算的基本流程
flowchart LR 十進位數 --> 二進位數 --> 科學計數法形式表示二進位數 --> 指數補齊 --> 二進位數相加 --> 還原成十進位數十進位小數轉為二進位小數
小數轉為二進位數的規則是:將小數乘以2,然後取整數部分作為二進位數的一部分,然後再將小數部分繼續乘以2,再取整數部分,以此類推,直到小數部分為0所達到的精度。
將0.2轉換成二進位:
\[0.2 \times 2 = 0.4 \to 取整數部分0 \]\[0.4 \times 2 = 0.8 \to 取整數部分0 \]\[0.8 \times 2 = 1.6 \to 取整數部分1 \]\[0.6 \times 2 = 1.2\to取整數部分1 \]\[0.2 \times 2 = 0.4\to整數部分為0 \]此時我們發現,我們對得到的小數怎麼乘以2,小數位永遠都不是0。因此,使用計算器計算0.2得到的二進位數字為
\[0.00110011...(無限迴圈0011) \]同理,0.1轉換成二進位數是:
\[0.000110011...(無限迴圈0011) \]二進位小數轉為科學計數法表示
當然,電腦不能存儲無限迴圈小數。Java的double
是雙精度浮點類型,64位,因此在存儲時使用64位存儲double
浮點數。要想表示儘可能大的數據,就需要使用到科學計數法來表示數據。
十進位和二進位數都可以轉換成相應的科學計數法來表示。
十進位的科學計數法的表示方式是整數只留個位數,且個位數主要是1到9,通過乘以10的指數來表示。例如:89999用科學計數法表示為\(8.9999\times10^4\),0.08586用十進位科學計數法表示為\(8.586\times10^{-2}\)。
二進位的科學計數法的表示方式和十進位的類似。它的個位數使用1來表示,通過乘以2的指數來表示。
例如,0.1的二進位數轉換成科學計數法表示,小數點需要向右移動4位得到整數部分1;同理,0.2需要向右移動3位。因此0.1和0.2的二進位用科學計數法表示如下:
\[1.10011...\times2^{-4}(0011無限迴圈) \]\[1.10011...\times2^{-3}(0011無限迴圈) \]科學計數法的數據轉成二進位表示
Java的double類型是雙精度浮點數,IEEE 754標準對64位浮點數做出瞭如下的規定:
- 最高1位是符號位,0表示正號,1表示負號。
- 其後面的11位用來存儲科學計數法中指數的二進位。以上述二進位科學計數法為例,這11位數字存儲的就是-4的二進位。
- 剩下的52位存儲二進位科學計數法中小數點的後52位。以上述二進位科學計數法為例,存儲的就是
10011...
之後的52位數字。
既然記憶體已經給出了11位用於表示指數。那麼轉換成十進位數預設範圍就是\([0, 2^{11}]\),即\([0,2048]\)。但此時還有一個問題,以上述的二進位科學計數法為例,它的指數是-4,是負數,如何表示負數?需要在11位的頭部在單獨拿出一位來表示嗎?
並不是,IEEE 754標准將指數為0的基數定為1023(1是1024,相當於存儲\([-1023,1024]\)範圍的數),指數-4會轉換成1023 - 4 = 1019
,再將1019轉換成二進位:1111111011,前面我們說過,指數為11位,需要在前面補零,得到的結果為:01111111011。
剩下的52位也需要處理,但是二進位科學計數法的小數部分也是一個無限迴圈小數。此時就需要進行舍入計算,0舍1入(類似四捨五入),舍入計算會讓數據丟失精度。
此時得到的0.1的二進位:
\[0\ 01111111011\ 1001100110011001100110011001100110011001100110011010 \]0.2的二進位如下:
\[0\ 01111111100\ 1001100110011001100110011001100110011001100110011010 \]此時需要對二進位科學計數法提取公因數,為了減少精度損失,遵循小指數轉換成大指數的原則。這裡較大的指數是-3,因此需要將0.1的二進位科學計數法再乘以2,得到結果如下:
\[0\ 01111111011\ (0.)100110011001100110011001100110011001100110011001101 \]0.1原有的最後一位需要捨去,讓給小數點前的0。此時0.1和0.2的二進位的指數均為-3、
此時0.1+0.2的小數部分得到的結果是:
\[10.0110011001100110011001100110011001100110011001100111 \]指數補齊
根據上述結果,我們會發現兩個問題:
- 整數部分不符合科學計數法的規則。
- 二進位數整體得到的結果超過52位。
首先需要將將結果轉換成二進位科學計數法,小數點向左移動一位(相當於乘以2):
\[1.00110011001100110011001100110011001100110011001100111 \]指數部分也需要加1,因為指數由-3(1020)變為-2(1021)
\[01111111101 \]根據0舍1入的原則,將超出52位的小數部分做舍入計算,得到的結果為:
\[0\ 01111111101\ (1.)0011001100110011001100110011001100110011001100110100 \]還原成十進位數
將二進位科學計數法轉換成正常的二進位數,原有的指數是-2,還原時小數點需向左移動兩位:
\[0.010011001100110011001100110011001100110011001100110100 \]再轉換為十進位為:
\[0.30000000000000004 \]經過上述的複雜推導,我們可以總結出一個結論:使用基本數據類型的浮點數進行運算並不准確(尤其是在金融貨幣領域對小數點精度要求比較高的不能使用)。那麼,有什麼辦法可以解決浮點數計算不准確的問題?
方法一(現階段推薦):轉換成整數計算,得到結果再除以10的n次方。
還是以0.1 + 0.2為例,我們可以轉換成整數計算,整數計算的結果再除以10,示例代碼如下:
/**
* 浮點數計算: 計算0.1 + 0.2的精確結果
*
* @author ZhaoCong
* @date 2023-10-09 18:13:35
*/
public class DecimalCalculation2 {
public static void main(String[] args) {
double doubleValue1 = 0.1;
double doubleValue2 = 0.2;
// 將doubleValue1和doubleValue2轉換成整數
int tempValue1 = (int) (doubleValue1 * 10);
int tempValue2 = (int) (doubleValue2 * 10);
int tempResult = tempValue1 + tempValue2;
double result = (double) tempResult / 10;
System.out.println("result = " + result);
}
}
運行結果:
此時能得到精確的結果。
方法二:使用BigDecimal
類(這個類後續會講到,小白可以直接跳過)精確運算
import java.math.BigDecimal;
/**
* 使用BigDecimal類精確計算浮點數
*
* @author iCode504
* @date 2023-10-09 22:26
*/
public class DecimalCalculation3 {
public static void main(String[] args) {
double doubleValue1 = 0.1;
double doubleValue2 = 0.2;
// 將double類型的值轉換成字元串
String doubleValueString1 = String.valueOf(doubleValue1);
String doubleValueString2 = String.valueOf(doubleValue2);
// 使用BigDecimal類進行運算
BigDecimal decimal1 = new BigDecimal(doubleValueString1);
BigDecimal decimal2 = new BigDecimal(doubleValueString2);
BigDecimal resultDecimal = decimal1.add(decimal2);
double result = resultDecimal.doubleValue();
System.out.println("result = " + result);
}
}
運行結果:
負數的除法和取餘規則
負數的除法規則:兩個負數相除得到的結果是正數,正數除以負數或者負數除以整數結果是負數。
/**
* 負數的除法運算
*
* @author iCode504
* @date 2023-10-07 19:57
*/
public class DivideOperators {
public static void main(String[] args) {
int intValue1 = 20;
int intValue2 = -10;
int intValue3 = 5;
int intValue4 = -5;
// 情況一:被除數為正數,除數為負數,得到的結果是負數
int result1 = intValue1 / intValue2;
System.out.println("result1 = " + result1);
// 情況二:被除數為負數,除數為正數,得到的結果是負數
int result2 = intValue2 / intValue3;
System.out.println("result2 = " + result2);
// 情況三:被除數和除數都是負數,得到的結果是正數
int result3 = intValue2 / intValue4;
System.out.println("result3 = " + result3);
}
}
運行結果:
負數的取餘規則:被除數如果是正數,求餘的結果就是正數;反之,結果為負數。
/**
* 負數的取餘運算
*
* @author iCode504
* @date 2023-10-07 22:12
*/
public class ModOperators {
public static void main(String[] args) {
int intValue1 = 20;
int intValue2 = -13;
int intValue3 = 7;
int intValue4 = -3;
// 情況一:被除數為正數,除數為負數,得到的結果是正數
int result1 = intValue1 % intValue2;
System.out.println("result1 = " + result1);
// 情況二:被除數為負數,除數為正數,得到的結果是負數
int result2 = intValue2 % intValue3;
System.out.println("result2 = " + result2);
// 情況三:被除數和除數都是負數,得到的結果是負數
int result3 = intValue2 % intValue4;
System.out.println("result3 = " + result3);
}
}
運行結果:
賦值運算符
賦值運算符=
我們知道,創建Java變數的一般語法是:數據類型 變數名 = 變數值。其中=
是賦值運算符,它的作用是將右側的值賦值給左邊的變數。
- 變數值一般是:常量、已經賦值的變數名或者是可以計算出新數值的表達式。
- 賦值運算符
=
左側的變數名唯一。
基本數據類型的變數可以直接賦值,因為基本數據類型保存的是實際值。
/**
* 賦值運算符 = 的基本使用
*
* @author iCode504
* @date 2023-10-06 6:40
*/
public class AssignmentOperator1 {
public static void main(String[] args) {
// 將20賦值給number1
int number1 = 20;
System.out.println("number1 = " + number1);
// 將已經賦值的變數名number1賦值給number2
int number2 = number1;
System.out.println("number2 = " + number2);
// 可以計算出新數值的表達式賦值給新變數
int number3 = 30 + 40;
System.out.println("number3 = " + number3);
int number4 = number1 + number2;
System.out.println("number4 = " + number4);
}
}
運算結果:
由number1
和number2
的輸出結果可知:變數number1
存儲的值20賦值給了number2
,此時number2
的值也是20。
變數number3
和number4
右側是可以計算的表達式,即30 + 40
能夠直接計算出結果,前面已經賦值的number1 + number2
也能計算出結果。
引用數據類型存儲的是一個地址值引用。例如:Object
和String
是類,屬於引用數據類型。此時我們創建這兩個類型的對象並賦值給變數,然後直接輸出變數。
/**
* 賦值運算符--引用數據類型變數賦值並輸出
*
* @author iCode504
* @date 2023-10-06 23:50
*/
public class AssignmentOperator2 {
public static void main(String[] args) {
// 第一組:創建兩個Object對象分別賦值給object1和object2
Object object1 = new Object();
Object object2 = new Object();
// 輸出兩個地址值
System.out.println("object1 = " + object1);
System.out.println("object2 = " + object2);
System.out.println("--------------------");
// 第二組:讓object1指向object2
object2 = object1;
System.out.println("object1 = " + object1);
System.out.println("object2 = " + object2);
System.out.println("--------------------");
// 第三組:創建兩個String對象分別賦值給string1和string2
String string1 = new String();
String string2 = new String();
System.out.println("string1 = " + string1);
System.out.println("string2 = " + string2);
}
}
運行結果:
前兩組輸出結果的格式我們發現,它們是以java.lang.Object
、@
和變數在物理記憶體中的地址(十六進位數)。
- 其中
java.lang.Object
叫做全限定類名。全限定類名是指當前類所屬的包名(包名會在後續文章中講到)和類名組成。Object
是類名,java.lang
是Object
類所在的包名。 @
後面的就是變數在記憶體中的存儲地址。如果你使用上述命令將代碼輸出,那麼得到的地址值和上述的內容不同,因為變數的地址值是記憶體隨機分配的。
第一組的object1
和object2
分別創建了Object對象,相當於在棧記憶體和堆記憶體中分別開闢了兩塊不同的空間,棧記憶體中存儲的變數地址和堆記憶體中開闢的記憶體地址一一對應,因此object1
和object2
的地址值不同。第一組的object1
和object2
在記憶體的表現形式如下:
第二組,我們發現object1
賦值給了object2
,在棧記憶體中的表現形式是當前變數object2
的地址值賦值給object1
。原來object2
在堆記憶體中創建的對象不再被引用,虛擬機後續會對此對象進行回收。
我們發現第三組兩個String
對象的輸出結果什麼都看不到,它們也是引用數據類型,難道不輸出地址值嗎?事實上,在源碼層面,String
做了進一步處理。
我們使用new String()
創建對象時,會調用String
的構造器(構造器,也叫做構造方法,後續會講到),打開源碼觀察這個構造器:
在調用空參構造器時就已經初始化一個空字元串值了,因此我們在輸出String
對象時輸出的是空字元串,此時我們看不到任何內容就顯得比較合理了。
其他賦值運算符
假設有一個int
類型變數intValue
的值是20,此時我在此基礎上再加上20再賦值給intValue
,得到的表達式如下:
int intValue = 20;
intValue = intValue + 20; // 此時intValue的結果為40
Java給我們提供了+=
運算符可以簡化當前的代碼intValue = intValue + 20;
,使用+=
可以簡化成如下形式:
int intValue = 20;
intValue += 20; // 得到的結果也是40,相當於intValue = intValue + 20;
除了+=
以外,-=
、*=
、/=
和%=
的作用機制和+=
完全相同。
賦值運算符 | 說明 | 使用 |
---|---|---|
+= |
加並賦值運算符:先相加,得到的結果再賦值 | i = i + 20 可以簡寫成i += 20 |
-= |
減並賦值運算符:先相減,得到的結果再賦值 | i = i - 20 可以簡寫成i -= 20 |
*= |
乘並賦值運算符:先相乘,得到的結果再賦值 | i = i * 20 可以簡寫成i *= 20 |
/= |
除並賦值運算符:先相除,得到的結果再賦值 | i = i / 20 可以簡寫成i /= 20 |
%= |
取餘並賦值運算符:先取餘,得到的結果再賦值 | i = i % 20 可以簡寫成i %= 20 |
以下是5個運算符在代碼中的應用:
/**
* 其他賦值運算符+=、-=、*=、/=和%=的使用
*
* @author iCode504
* @date 2023-10-07 20:14
*/
public class AssignmentOperator3 {
public static void main(String[] args) {
int intValue1 = 20;
int intValue2 = 30;
int intValue3 = 40;
int intValue4 = 50;
int intValue5 = 60;
intValue1 += 30;
intValue2 -= 40;
intValue3 *= 50;
intValue4 /= 10;
intValue5 %= 7;
System.out.println("intValue1 = " + intValue1);
System.out.println("intValue2 = " + intValue2);
System.out.println("intValue3 = " + intValue3);
System.out.println("intValue4 = " + intValue4);
System.out.println("intValue5 = " + intValue5);
}
}
運行結果:
byte
、short
、char
三者使用上述賦值運算符時,不需要進行強制類型轉換:
/**
* byte、short、char使用賦值運算符
*
* @author iCode504
* @date 2023-10-07 20:34
*/
public class AssignmentOperator4 {
public static void main(String[] args) {
byte byteValue1 = 30;
byte byteValue2 = 40;
short shortValue = 10;
char charValue = 'a';
byteValue1 += byteValue2;
System.out.println("byteValue1 = " + byteValue1);
byteValue1 += 10;
System.out.println("byteValue2 = " + byteValue2);
charValue += byteValue1;
shortValue += charValue;
byteValue2 += shortValue;
System.out.println("charValue = " + charValue);
System.out.println("shortValue = " + shortValue);
System.out.println("byteValue2 = " + byteValue2);
}
}
運行結果:
使用賦值運算符的優勢包括:
1. 簡潔性:使用+=
可以在一行內同時完成加法計算和賦值操作,讓代碼更加簡潔。例如:i += 20
就是i = i + 20
的簡化寫法(其他賦值運算符亦同理)。
2. 性能優勢:在某些情況下,賦值運算符要比單獨的加法和賦值操作更快。
總的來說,使用賦值運算符可以增加代碼的簡潔性,提高性能,並使代碼更易於閱讀和理解。
參考資料: