没有特殊说明,个人全部学习笔记都是从廖老师那里摘抄过来的,侵删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
来产生安全的随机数。