沒有特殊說明,個人全部學習筆記都是從廖老師那裏摘抄過來的,侵刪java
兜兜轉轉到了大四,學過了C,C++,C#,Java,Python,學一門丟一門,到了最後仍是要把Java撿起來。因此奉勸你們,面向對象仍是要掌握一門,雖然Python好寫舒服,可是畢竟不能徹底面向對象,也沒有那麼多的應用場景,因此,奉勸看到本文的各位,仍是提早學好C#或者Java。正則表達式
在Java中,String
是一個引用類型,它自己也是一個class
。可是,Java編譯器對String
有特殊處理,便可以直接用"..."
(這裏的...是象徵字符串的)來表示一個字符串算法
Java字符串的一個重要特色就是字符串不可變。這種不可變性是經過內部的private final char[]
字段,以及沒有任何修改char[]
的方法實現的。編程
public class Main { public static void main(String[] args) { String s = "Hello"; System.out.println(s); s = s.toUpperCase(); System.out.println(s); } }
equals()
而不能用==
public class Main { public static void main(String[] args) { String s1 = "hello"; String s2 = "hello"; System.out.println(s1 == s2); System.out.println(s1.equals(s2)); } }
==
和equals()
比較都爲true
,但實際上那只是Java編譯器在編譯期,會自動把全部相同的字符串看成一個對象放入常量池,天然s1
和s2
的引用就是相同的。public class Main { public static void main(String[] args) { String s1 = "hello"; String s2 = "HELLO".toLowerCase(); System.out.println(s1 == s2); System.out.println(s1.equals(s2)); } }
equals()
方法。要忽略大小寫比較,使用equalsIgnoreCase()
方法。數組
String
類還提供了多種方法來搜索子串、提取子串。經常使用的方法有:緩存
// 是否包含子串: "Hello".contains("ll"); // true
注意到contains()
方法的參數是CharSequence
而不是String
,由於CharSequence
是String
的父類。安全
搜索子串的更多的例子:網絡
"Hello".indexOf("l"); // 2 "Hello".lastIndexOf("l"); // 3 "Hello".startsWith("He"); // true "Hello".endsWith("lo"); // true
0
開始的。"Hello".substring(2); // "llo" "Hello".substring(2, 4); "ll"
trim()
方法能夠移除字符串首尾空白字符。空白字符包括空格,\t
,\r
,\n
:" \tHello\r\n ".trim(); // "Hello"
注意:trim()
並無改變字符串的內容,而是返回了一個新字符串。app
另外一個strip()
方法也能夠移除字符串首尾空白字符。它和trim()
不一樣的是,相似中文的空格字符\u3000
也會被移除:dom
"\u3000Hello\u3000".strip(); // "Hello" " Hello ".stripLeading(); // "Hello " " Hello ".stripTrailing(); // " Hello"
String
還提供了isEmpty()
和isBlank()
來判斷字符串是否爲空和空白字符串:"".isEmpty(); // true,由於字符串長度爲0 " ".isEmpty(); // false,由於字符串長度不爲0 " \n".isBlank(); // true,由於只包含空白字符 " Hello ".isBlank(); // false,由於包含非空白字符
String s = "hello"; s.replace('l', 'w'); // "hewwo",全部字符'l'被替換爲'w' s.replace("ll", "~~"); // "he~~o",全部子串"ll"被替換爲"~~"
String s = "A,,B;C ,D"; s.replaceAll("[\\,\\;\\s]+", ","); // "A,B,C,D"
split()
方法,而且傳入的也是正則表達式:String s = "A,B,C,D"; String[] ss = s.split("\\,"); // {"A", "B", "C", "D"}
join()
,它用指定的字符串鏈接字符串數組:String[] arr = {"A", "B", "C"}; String s = String.join("***", arr); // "A***B***C"
valueOf()
。這是一個重載方法,編譯器會根據參數自動選擇合適的方法:String.valueOf(123); // "123" String.valueOf(45.67); // "45.67" String.valueOf(true); // "true" String.valueOf(new Object()); // 相似java.lang.Object@636be97c
int
類型:int n1 = Integer.parseInt("123"); // 123 int n2 = Integer.parseInt("ff", 16); // 按十六進制轉換,255
boolean
類型:boolean b1 = Boolean.parseBoolean("true"); // true boolean b2 = Boolean.parseBoolean("FALSE"); // false
Integer
有個getInteger(String)
方法,它不是將字符串轉換爲int
,而是把該字符串對應的系統變量轉換爲Integer
:Integer.getInteger("java.version"); // 版本號,11
String
和char[]
類型能夠互相轉換,方法是:char[] cs = "Hello".toCharArray(); // String -> char[] String s = new String(cs); // char[] -> String
char[]
數組,String
並不會改變:new String(char[])
建立新的String
實例時,它並不會直接引用傳入的char[]
數組,而是會複製一份,因此,修改外部的char[]
數組不會影響String
實例內部的char[]
數組,由於這是兩個不一樣的數組。public class Main { public static void main(String[] args) { char[] cs = "Hello".toCharArray(); String s = new String(cs); System.out.println(s); cs[0] = 'X'; System.out.println(s); } }
從
String
的不變性設計能夠看出,若是傳入的對象有可能改變,咱們須要複製而不是直接引用。
public class Main { public static void main(String[] args) { int[] scores = new int[] { 88, 77, 51, 66 }; Score s = new Score(scores); s.printScores(); scores[2] = 99; s.printScores(); } } class Score { private int[] scores; public Score(int[] scores) { // 這樣傳入的就是scores的複製 this.scores = Arrays.copyOf(scores, scores.length); // 使用以下方法也能夠 // this.scores = scores.clone(); } public void printScores() { System.out.println(Arrays.toString(scores)); } }
ASCII
編碼範圍從0
到127
,每一個字符用一個byte
表示。GB2312
使用兩個byte
表示一箇中文字符。Unicode
是全球統一編碼,其中的UTF-8
是變長編碼,英文字符爲1個byte
,中文字符爲3個byte
。
在Java中,char
類型實際上就是兩個字節的Unicode
編碼。若是咱們要手動把字符串轉換成其餘編碼,能夠這樣作:
byte[] b1 = "Hello".getBytes(); // 按ISO8859-1編碼轉換,不推薦 byte[] b2 = "Hello".getBytes("UTF-8"); // 按UTF-8編碼轉換 byte[] b2 = "Hello".getBytes("GBK"); // 按GBK編碼轉換 byte[] b3 = "Hello".getBytes(StandardCharsets.UTF_8); // 按UTF-8編碼轉換
byte[]
轉換爲String
,能夠這樣作:byte[] b = ... String s1 = new String(b, "GBK"); // 按GBK轉換 String s2 = new String(b, StandardCharsets.UTF_8); // 按UTF-8轉換
String
和char
在內存中老是以Unicode
編碼表示。String
作了特殊處理,使得咱們能夠直接用+
拼接字符串。String s = ""; for (int i = 0; i < 1000; i++) { s = s + "," + i; }
StringBuilder
,它是一個可變對象,能夠預分配緩衝區,這樣,往StringBuilder
中新增字符時,不會建立新的臨時對象:StringBuilder sb = new StringBuilder(1024); for (int i = 0; i < 1000; i++) { sb.append(','); sb.append(i); } String s = sb.toString();
StringBuilder
還能夠進行鏈式操做
:public class Main { public static void main(String[] args) { var sb = new StringBuilder(1024); sb.append("Mr ") .append("Bob") .append("!") .insert(0, "Hello, "); System.out.println(sb.toString()); } }
若是咱們查看StringBuilder
的源碼,能夠發現,進行鏈式操做的關鍵是,定義的append()
方法會返回this
,這樣,就能夠不斷調用自身的其餘方法。使用鏈式操做的關鍵點就在於返回自己。
你可能還據說過StringBuffer
,這是Java早期的一個StringBuilder
的線程安全版本,StringBuilder
和StringBuffer
接口徹底相同,如今徹底沒有必要使用StringBuffer
。
StringJoiner
來幹這個事:public class Main { public static void main(String[] args) { String[] names = {"Bob", "Alice", "Grace"}; var sj = new StringJoiner(", "); for (String name : names) { sj.add(name); } System.out.println(sj.toString()); } }
hello
和結尾的!
,因而咱們給StringJoiner
指定開頭和結尾public class Main { public static void main(String[] args) { String[] names = {"Bob", "Alice", "Grace"}; // param1 是須要給數組之間插入的字符串 para2和3是指定了StringJoiner的開頭和結尾 var sj = new StringJoiner(", ", "Hello ", "!"); for (String name : names) { sj.add(name); } System.out.println(sj.toString()); } }
String
還提供了一個靜態方法join()
,這個方法在內部使用了StringJoiner
來拼接字符串,在不須要指定「開頭」和「結尾」的時候,用String.join()
更方便:
String[] names = {"Bob", "Alice", "Grace"}; var s = String.join(", ", names);
byte
,short
,int
,long
,boolean
,float
,double
,char
class
和interface
類型null
,表示空,但基本類型不能賦值爲null
:String s = null; int n = null; // compile error!
int
基本類型變成一個引用類型,咱們能夠定義一個Integer
類,它只包含一個實例字段int
,這樣,Integer
類就能夠視爲int
的包裝類(Wrapper Class):Integer n = null; Integer n2 = new Integer(99); int n3 = n2.intValue();
基本類型 | 對應的引用類型 |
---|---|
boolean | java.lang.Boolean |
byte | java.lang.Byte |
short | java.lang.Short |
int | java.lang.Integer |
long | java.lang.Long |
float | java.lang.Float |
double | java.lang.Double |
char | java.lang.Character |
public class Main { public static void main(String[] args) { int i = 100; // 經過new操做符建立Integer實例(不推薦使用,會有編譯警告): Integer n1 = new Integer(i); // 經過靜態方法valueOf(int)建立Integer實例: Integer n2 = Integer.valueOf(i); // 經過靜態方法valueOf(String)建立Integer實例: Integer n3 = Integer.valueOf("100"); // 使用示範 System.out.println(n3.intValue()); } }
int
和Integer
能夠互換,因此Java能夠幫助咱們在int
和Integer
之間轉型Integer n = 100; // 編譯器自動使用Integer.valueOf(int) int x = n; // 編譯器自動使用Integer.intValue()
int
變爲Integer
的賦值寫法,稱爲自動裝箱(Auto Boxing),反過來,把Integer
變爲int
的賦值寫法,稱爲自動拆箱(Auto Unboxing)。自動裝箱和自動拆箱只發生在編譯階段,目的是爲了少寫代碼。
class
代碼是嚴格區分基本類型和引用類型的。而且,自動拆箱執行時可能會報NullPointerException
:Integer
的源碼可知,它的核心代碼以下:public final class Integer { private final int value; }
Integer
對象,該對象就是不變的。對兩個Integer
實例進行比較要特別注意:絕對不能用==
比較,由於Integer
是引用類型,必須使用equals()
比較。(引用類型必須用equals()比較)
編譯器把Integer x = 127;
自動變爲Integer x = Integer.valueOf(127);
,爲了節省內存,Integer.valueOf()
對於較小的數,始終返回相同的實例,所以,==
比較「剛好」爲true
,但咱們毫不能由於Java標準庫的Integer
內部有緩存優化就用==
比較,必須用equals()
方法比較兩個Integer
。
按照語義編程,而不是針對特定的底層實現去「優化」。
由於Integer.valueOf()
可能始終返回同一個Integer
實例,所以,在咱們本身建立Integer
的時候,如下兩種方法:
方法1:Integer n = new Integer(100);
方法2:Integer n = Integer.valueOf(100);
方法2更好,由於方法1老是建立新的Integer
實例,方法2把內部優化留給Integer
的實現者去作,即便在當前版本沒有優化,也有可能在下一個版本進行優化。
咱們把能建立「新」對象的靜態方法稱爲靜態工廠方法。Integer.valueOf()
就是靜態工廠方法,它儘量地返回緩存的實例以節省內存。
建立新對象時,優先選用靜態工廠方法而不是new操做符。
Integer
類自己還提供了大量方法,例如,最經常使用的靜態方法parseInt()
能夠把字符串解析成一個整數:int x1 = Integer.parseInt("100"); // 100 int x2 = Integer.parseInt("100", 16); // 256,由於按16進制解析
Integer
還能夠把整數格式化爲指定進制的字符串:public class Main { public static void main(String[] args) { System.out.println(Integer.toString(100)); // "100",表示爲10進制 System.out.println(Integer.toString(100, 36)); // "2s",表示爲36進制 System.out.println(Integer.toHexString(100)); // "64",表示爲16進制 System.out.println(Integer.toOctalString(100)); // "144",表示爲8進制 System.out.println(Integer.toBinaryString(100)); // "1100100",表示爲2進制 } }
Number
。在Java中,有不少class
的定義都符合這樣的規範:
若干private
實例字段;
經過public
方法(getter、setter方法)來讀寫實例字段。
public class Person { private String name; private int age; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public int getAge() { return this.age; } public void setAge(int age) { this.age = age; } }
// 讀方法: public Type getXyz() // 寫方法: public void setXyz(Type value)
boolean
字段比較特殊,它的讀方法通常命名爲isXyz()
:// 讀方法: public boolean isChild() // 寫方法: public void setChild(boolean value)
咱們一般把一組對應的讀方法(getter
)和寫方法(setter
)稱爲屬性(property
)。例如,name
屬性:
對應的讀方法是String getName()
對應的寫方法是setName(String)
只有getter
的屬性稱爲只讀屬性(read-only),例如,定義一個age只讀屬性:
對應的讀方法是int getAge()
無對應的寫方法setAge(int)
相似的,只有setter
的屬性稱爲只寫屬性(write-only)。
很明顯,只讀屬性很常見,只寫屬性不常見。
JavaBean能夠方便地被IDE工具分析,生成讀寫屬性的代碼,主要用在圖形界面的可視化設計中。
經過IDE,能夠快速生成getter
和setter
。例如,在Eclipse中,先輸入如下代碼,而後,點擊右鍵,在彈出的菜單中選擇「Source」,「Generate Getters and Setters」,在彈出的對話框中選中須要生成getter
和setter
方法的字段,點擊肯定便可由IDE自動完成全部方法代碼。
public class Person { private String name; private int age; }
Introspector.getBeanInfo(ClassName.class)
static final
來定義常量。例如,咱們但願定義週一到週日這7個常量,能夠用7個不一樣的int
表示public class Weekday { public static final int SUN = 0; public static final int MON = 1; public static final int TUE = 2; public static final int WED = 3; public static final int THU = 4; public static final int FRI = 5; public static final int SAT = 6; }
int
常量仍是String
常量,使用這些常量來表示一組枚舉值的時候,有一個嚴重的問題就是,編譯器沒法檢查每一個值的合理性。例如:if (weekday == 6 || weekday == 7) { if (tasks == Weekday.MON) { // TODO: } }
上述代碼編譯和運行均不會報錯,但存在兩個問題:
注意到Weekday
定義的常量範圍是0
~6
,並不包含7
,編譯器沒法檢查不在枚舉中的int
值;
定義的常量仍可與其餘變量比較,但其用途並不是是枚舉星期值。
enum
來定義枚舉類。public class Main { public static void main(String[] args) { Weekday day = Weekday.SUN; if (day == Weekday.SAT || day == Weekday.SUN) { System.out.println("Work at home!"); } else { System.out.println("Work at office!"); } } } enum Weekday { SUN, MON, TUE, WED, THU, FRI, SAT; }
Weekday
枚舉類型的變量賦值爲Color
枚舉類型的值。Weekday x = Weekday.SUN; // ok! Weekday y = Color.RED; // Compile error: incompatible types
前面講解過引用類型的比較須要使用equals()
,雖然enum
定義的是一種枚舉類型,可是卻能夠例外用==
來比較。這是由於enum
類型的每一個常量在JVM中只有一個惟一實例,因此能夠直接用==
比較。
經過enum
定義的枚舉類,和其餘的class
有什麼區別?
答案是沒有任何區別。enum
定義的類型就是class
,只不過它有如下幾個特色:
enum
類型老是繼承自java.lang.Enum
,且沒法被繼承;enum
的實例,而沒法經過new
操做符建立enum
的實例;enum
類型用於switch
語句。例如,咱們定義的Color
枚舉類:
public enum Color { RED, GREEN, BLUE; }
編譯器編譯出的class
大概就像這樣:
public final class Color extends Enum { // 繼承自Enum,標記爲final class // 每一個實例均爲全局惟一: public static final Color RED = new Color(); public static final Color GREEN = new Color(); public static final Color BLUE = new Color(); // private構造方法,確保外部沒法調用new操做符: private Color() {} }
因此,編譯後的enum
類和普通class
並無任何區別。可是咱們本身沒法按定義普通class
那樣來定義enum
,必須使用enum
關鍵字,這是Java語法規定的。
由於enum
是一個class
,每一個枚舉的值都是class
實例,所以,這些實例有一些方法:
返回常量名,例如:
String s = Weekday.SUN.name(); // "SUN"
返回定義的常量的順序,從0開始計數,例如:
int n = Weekday.MON.ordinal(); // 1
改變枚舉常量定義的順序就會致使ordinal()
返回值發生變化。
若是不當心修改了枚舉的順序,編譯器是沒法檢查出這種邏輯錯誤的。要編寫健壯的代碼,就不要依靠ordinal()
的返回值。由於enum
自己是class
,因此咱們能夠定義private
的構造方法,而且,給每一個枚舉常量添加字段:
public class Main { public static void main(String[] args) { Weekday day = Weekday.SUN; if (day.dayValue == 6 || day.dayValue == 0) { System.out.println("Work at home!"); } else { System.out.println("Work at office!"); } } } enum Weekday { MON(1), TUE(2), WED(3), THU(4), FRI(5), SAT(6), SUN(0); public final int dayValue; private Weekday(int dayValue) { this.dayValue = dayValue; } }
默認狀況下,對枚舉常量調用toString()
會返回和name()
同樣的字符串。可是,toString()
能夠被覆寫,而name()
則不行。咱們能夠給Weekday
添加toString()
方法。
public class Main { public static void main(String[] args) { Weekday day = Weekday.SUN; if (day.dayValue == 6 || day.dayValue == 0) { System.out.println("Today is " + day + ". Work at home!"); } else { System.out.println("Today is " + day + ". Work at office!"); } } } enum Weekday { MON(1, "星期一"), TUE(2, "星期二"), WED(3, "星期三"), THU(4, "星期四"), FRI(5, "星期五"), SAT(6, "星期六"), SUN(0, "星期日"); public final int dayValue; private final String chinese; private Weekday(int dayValue, String chinese) { this.dayValue = dayValue; this.chinese = chinese; } @Override public String toString() { return this.chinese; } }
注意:判斷枚舉常量的名字,要始終使用name()方法,毫不能調用toString()!
由於枚舉類天生具備類型信息和有限個枚舉常量,因此比int
、String
類型更適合用在switch
語句中。
long
,要是超過了這個範圍就須要用BigInteger
來表示數字。java.math.BigInteger
就是用來表示任何數字的。BigInteger
進行運算的時候只能用實例方法,並且和long
整形運算比起來速度較慢。BigInteger
和Integer
、Long
同樣,也是不可變類,而且也繼承自Number
類。由於Number
定義了轉換爲基本類型的幾個方法:
byte
:byteValue()
short
:shortValue()
int
:intValue()
long
:longValue()
float
:floatValue()
double
:doubleValue()
BigInteger
轉換成基本類型。若是BigInteger
表示的範圍超過了基本類型的範圍,轉換時將丟失高位信息,即結果不必定是準確的。若是須要準確地轉換成基本類型,可使用intValueExact()
、longValueExact()
等方法(沒有其餘的typeValueExact
方法),在轉換時若是超出範圍,將直接拋出ArithmeticException
異常。BigInteger i1 = new BigInteger("1234567890"); BigInteger i2 = new BigInteger("12345678901234567890"); BigInteger sum = i1.add(i2); // 12345678902469135780 BigInteger mul = i1.multiply(i2); //不知道多大了 System.out.println(i.multiply(i).longValueExact()); // java.lang.ArithmeticException: BigInteger out of long range // 使用longValueExact()方法時,若是超出了long型的範圍,會拋出ArithmeticException
BigInteger
相似,BigDecimal
能夠表示一個任意大小且精度徹底準確的浮點數。BigDecimal bd = new BigDecimal("123.4567"); System.out.println(bd.multiply(bd)); // 15241.55677489
BigDecimal
用scale()
表示小數位數,例如:BigDecimal d1 = new BigDecimal("123.45"); BigDecimal d2 = new BigDecimal("123.4500"); BigDecimal d3 = new BigDecimal("1234500"); System.out.println(d1.scale()); // 2,兩位小數 System.out.println(d2.scale()); // 4 System.out.println(d3.scale()); // 0
BigDecimal
的stripTrailingZeros()
方法,能夠將一個BigDecimal
格式化爲一個相等的,但去掉了末尾0的BigDecimal
:BigDecimal d1 = new BigDecimal("123.4500"); BigDecimal d2 = d1.stripTrailingZeros(); System.out.println(d1.scale()); // 4 System.out.println(d2.scale()); // 2,由於去掉了00 BigDecimal d3 = new BigDecimal("1234500"); BigDecimal d4 = d3.stripTrailingZeros(); System.out.println(d3.scale()); // 0 System.out.println(d4.scale()); // -2
BigDecimal
的scale()
返回負數,例如,-2
,表示這個數是個整數,而且末尾有2個0。BigDecimal
設置它的scale
,若是精度比原始值低,那麼按照指定的方法進行四捨五入或者直接截斷:import java.math.BigDecimal; import java.math.RoundingMode; public class Main { public static void main(String[] args) { BigDecimal d1 = new BigDecimal("123.456789"); BigDecimal d2 = d1.setScale(4, RoundingMode.HALF_UP); // 四捨五入,123.4568 BigDecimal d3 = d1.setScale(4, RoundingMode.DOWN); // 直接截斷,123.4567 System.out.println(d2); System.out.println(d3); } }
BigDecimal
作加、減、乘時,精度不會丟失,可是作除法時,存在沒法除盡的狀況,這時,就必須指定精度以及如何進行截斷:BigDecimal d1 = new BigDecimal("123.456"); BigDecimal d2 = new BigDecimal("23.456789"); BigDecimal d3 = d1.divide(d2, 10, RoundingMode.HALF_UP); // 保留10位小數並四捨五入 BigDecimal d4 = d1.divide(d2); // 報錯:ArithmeticException,由於除不盡
BigDecimal
作除法的同時求餘數:import java.math.BigDecimal; public class Main { public static void main(String[] args) { BigDecimal n = new BigDecimal("12.345"); BigDecimal m = new BigDecimal("0.12"); BigDecimal[] dr = n.divideAndRemainder(m); System.out.println(dr[0]); // 102 System.out.println(dr[1]); // 0.105 } }
divideAndRemainder()
方法時,返回的數組包含兩個BigDecimal
,分別是商和餘數,其中商老是整數,餘數不會大於除數。咱們能夠利用這個方法判斷兩個BigDecimal
是不是整數倍數:BigDecimal n = new BigDecimal("12.75"); BigDecimal m = new BigDecimal("0.15"); BigDecimal[] dr = n.divideAndRemainder(m); if (dr[1].signum() == 0) { // n是m的整數倍 }
BigDecimal
的值是否相等時,要特別注意,使用equals()
方法不但要求兩個BigDecimal
的值相等,還要求它們的scale()
相等:BigDecimal d1 = new BigDecimal("123.456"); BigDecimal d2 = new BigDecimal("123.45600"); System.out.println(d1.equals(d2)); // false,由於scale不一樣 System.out.println(d1.equals(d2.stripTrailingZeros())); // true,由於d2去除尾部0後scale變爲2 System.out.println(d1.compareTo(d2)); // 0
compareTo()
方法來比較,它根據兩個值的大小分別返回負數、正數和0
,分別表示小於、大於和等於。BigDecimal
的源碼,能夠發現,實際上一個BigDecimal
是經過一個BigInteger
和一個scale
來表示的,即BigInteger
表示一個完整的整數,而scale
表示小數位數:public class BigDecimal extends Number implements Comparable<BigDecimal> { private final BigInteger intVal; private final int scale; }
BigDecimal
也是從Number
繼承的,也是不可變對象。顧名思義,Math
類就是用來進行數學計算的,它提供了大量的靜態方法來便於咱們實現數學計算:
求絕對值:
Math.abs(-100); // 100 Math.abs(-7.8); // 7.8
取最大或最小值:
Math.max(100, 99); // 100 Math.min(1.2, 2.3); // 1.2
計算xy次方:
Math.pow(2, 10); // 2的10次方=1024
計算√x:
Math.sqrt(2); // 1.414...
計算ex次方:
Math.exp(2); // 7.389...
計算以e爲底的對數:
Math.log(4); // 1.386...
計算以10爲底的對數:
Math.log10(100); // 2
三角函數:
Math.sin(3.14); // 0.00159... Math.cos(3.14); // -0.9999... Math.tan(3.14); // -0.0015... Math.asin(1.0); // 1.57079... Math.acos(1.0); // 0.0
Math還提供了幾個數學常量:
double pi = Math.PI; // 3.14159... double e = Math.E; // 2.7182818... Math.sin(Math.PI / 6); // sin(π/6) = 0.5
生成一個隨機數x,x的範圍是0 <= x < 1
:
Math.random(); // 0.53907... 每次都不同
若是咱們要生成一個區間在[MIN, MAX)
的隨機數,能夠藉助Math.random()
實現,計算以下:
// 區間在[MIN, MAX)的隨機數 public class Main { public static void main(String[] args) { double x = Math.random(); // x的範圍是[0,1) double min = 10; double max = 50; double y = x * (max - min) + min; // y的範圍是[10,50) long n = (long) y; // n的範圍是[10,50)的整數 System.out.println(y); System.out.println(n); } }
有些童鞋可能注意到Java標準庫還提供了一個StrictMath
,它提供了和Math
幾乎如出一轍的方法。這兩個類的區別在於,因爲浮點數計算存在偏差,不一樣的平臺(例如x86和ARM)計算的結果可能不一致(指偏差不一樣),所以,StrictMath
保證全部平臺計算結果都是徹底相同的,而Math
會盡可能針對平臺優化計算速度,因此,絕大多數狀況下,使用Math
就足夠了。
Random
用來建立僞隨機數。所謂僞隨機數,是指只要給定一個初始的種子,產生的隨機數序列是徹底同樣的。
要生成一個隨機數,可使用nextInt()
、nextLong()
、nextFloat()
、nextDouble()
:
Random r = new Random(); r.nextInt(); // 2071575453,每次都不同 r.nextInt(10); // 5,生成一個[0,10)之間的int r.nextLong(); // 8811649292570369305,每次都不同 r.nextFloat(); // 0.54335...生成一個[0,1)之間的float r.nextDouble(); // 0.3716...生成一個[0,1)之間的double
有童鞋問,每次運行程序,生成的隨機數都是不一樣的,沒看出僞隨機數的特性來。
這是由於咱們建立Random
實例時,若是不給定種子,就使用系統當前時間戳做爲種子,所以每次運行時,種子不一樣,獲得的僞隨機數序列就不一樣。
若是咱們在建立Random
實例時指定一個種子,就會獲得徹底肯定的隨機數序列:
import java.util.Random; public class Main { public static void main(String[] args) { Random r = new Random(12345); for (int i = 0; i < 10; i++) { System.out.println(r.nextInt(100)); } // 51, 80, 41, 28, 55... } }
前面咱們使用的Math.random()
實際上內部調用了Random
類,因此它也是僞隨機數,只是咱們沒法指定種子。
有僞隨機數,就有真隨機數。實際上真正的真隨機數只能經過量子力學原理來獲取,而咱們想要的是一個不可預測的安全的隨機數,SecureRandom
就是用來建立安全的隨機數的:
SecureRandom sr = new SecureRandom(); System.out.println(sr.nextInt(100));
SecureRandom
沒法指定種子,它使用RNG(random number generator)算法。JDK的SecureRandom
實際上有多種不一樣的底層實現,有的使用安全隨機種子加上僞隨機數算法來產生安全的隨機數,有的使用真正的隨機數生成器。實際使用的時候,能夠優先獲取高強度的安全隨機數生成器,若是沒有提供,再使用普通等級的安全隨機數生成器:
import java.util.Arrays; import java.security.SecureRandom; import java.security.NoSuchAlgorithmException; public class Main { public static void main(String[] args) { SecureRandom sr = null; try { sr = SecureRandom.getInstanceStrong(); // 獲取高強度安全隨機數生成器 } catch (NoSuchAlgorithmException e) { sr = new SecureRandom(); // 獲取普通的安全隨機數生成器 } byte[] buffer = new byte[16]; sr.nextBytes(buffer); // 用安全隨機數填充buffer System.out.println(Arrays.toString(buffer)); } }
SecureRandom
的安全性是經過操做系統提供的安全的隨機種子來生成隨機數。這個種子是經過CPU的熱噪聲、讀寫磁盤的字節、網絡流量等各類隨機事件產生的「熵」。
在密碼學中,安全的隨機數很是重要。若是使用不安全的僞隨機數,全部加密體系都將被攻破。所以,時刻牢記必須使用SecureRandom
來產生安全的隨機數。