【Java常用类型】枚举、包装类、Math、Random、UUID、数字格式化、BigDecimal、高精度计算、String、字符串常量池、StringBuilder、Date、Calendar

常用类型

枚举(Enum)

自定义类实现枚举效果

如果我们要实现一个季节类,并且变量取值只能是春夏秋冬,应该这么写:

public class Season {
	public static final Season SPRING = new Season();
	public static final Season SUMMER = new Season();
	public static final Season FALL = new Season();
	public static final Season WINTER = new Season();
	
	private Season() {} // 构造方法私有是为了防止外面主动创建新的对象
}
public class Main {
	public static void main(String[] args) {
		//这样写是不行的, 对象取值只能是类中定义的常量
		// Season season = new Seaon();
		Season spring = Season.SPRING; 
		Season summer = Season.SUMMER;
		Season fall = Season.FALL;
		Season winter = Season.WINTER;
		
		test(spring); // 春天
		test(summer); // 夏天
		test(fall);   // 秋天
		test(winter); // 冬天
	}
	
	// 自定义类不可以使用 switch 语句, 只能使用 if else 语句来判断
	public static void test(Season season) {
		if (season == Season.SPRING) {
			System.out.println("春天");
		} else if (season == Season.SUMMER) {
			System.out.println("夏天");
		} else if (season == Season.FALL) {
			System.out.println("秋天");
		} else if (season == Season.WINTER) {
			System.out.println("冬天");
		}
	}
	
}

枚举类型(Enum Type)

如果一个变量的取值只可能是固定的几个值,可以考虑使用枚举类型

  • 枚举由一组预定义的常量构成

枚举的本质实际上就跟我们上面的写法差不多,但是在Java中的使用更加简单:

public enum Season {
	SPRING, SUMMER, FALL, WINTER
}
public class Main {
	public static void main(String[] args) {
		Season s = Season.WINTER;
		System.out.println(s.name()); // WINTER
		System.out.println(s.ordinal()); // 该枚举元素的索引, 3
		test(s); // 冬天
	}
	
	// 枚举类型可以使用 switch 语句, 自定义类不可以
	public static void test(Season season) {
		switch (season) {
		case SPRING:
			System.out.println("春天");
			break;
		case SUMMER:
			System.out.println("夏天");
			break;
		case FALL:
			System.out.println("秋天");
			break;
		case WINTER:
			System.out.println("冬天");
			break;
		}
	}
}

枚举的使用注意

枚举的本质是类,所有枚举类型最终都隐式继承java.lang.Enum

枚举定义完常量后,可以再定义成员变量、方法等内容(这时最后一个常量要以分号结束)

枚举的构造方法权限必须是 无修饰符 或者 private

  • Java 会主动调用构造方法初始化每一个常量,你不能主动调用构造方法

自定义构造方法的枚举:

public enum Season {
	SPRING(5, 15),
	SUMMER(25, 35),
	FALL(15, 25),
	WINTER(-5, 5);
	
	private int min; // 最低气温
	private int max; // 最高气温
	Season(int min, int max) {
		this.min = min;
		this.max = max;
	}
	public int getMin() {
		return min;
	}
	public int getMax() {
		return max;
	}
	
}
public static void main(String[] args) {
	Season s = Season.SUMMER;
	System.out.println(s.getMin()); // 25
	System.out.println(s.getMax());	// 35
}

包装类

基本使用

对比引用类型,基本类型存在的一些缺陷:

  • 无法表示不存在的值(null 值)
  • 不能利用面向对象的方式去操作基本类型(比如直接用基本类型调用方法)
  • 当方法参数是引用类型时,基本类型无法传递

解决方案:可以自己将基本类型包装成引用类型

在这里插入图片描述

  • 其实Java中已经内置了基本类型的包装类(都在java.lang包中)
    在这里插入图片描述
  • 数字类型的包装类(Byte\Short\Integer\Long\Float\Double)最终都继承自java.lang.Number

自动装箱、拆箱(Autoboxing and Unboxing)

  • 自动装箱:Java 编译器会自动基本类型转换为包装类(调用 valueOf 方法)
  • 自动拆箱:Java 编译器会自动包装类转换为基本类型(调用 xxxValue 方法)

自动装箱示例:Java 编译器会帮我们做很多工作,过一遍下面的示例即可。

Integer i1 = 10; // 自动装箱
// 等价于 Integer i1 = Integer.valueOf(10);

Object num = 10; // 自动装箱
// 等价于: Object num1 = Integer.valueOf(10);
public static void main(String[] args) {
	add(20); // 自动装箱
	// 等价于 add(Integer.valueOf(20)); 
}
static void add(Integer num) {}

自动拆箱示例:平时写代码这些都是不需要我们操心的,了解原理即可。

Integer i1 = 10;
int i2 = i1; // 自动拆箱
// 等价于: int i2 = i1.intValue();

System.out.println(i1 == 10); // 自动拆箱
// 等价于: System.out.println(i1.intValue() == 10);

Integer[] array = { 11, 22, 33, 44 }; // 包装类数组
	int result = 0;
	for (Integer i : array) {
	    // i.intValue() % 2 == 0
		if (i % 2 == 0) { // 自动拆箱
			// result += i.intValue();
			result += i; //自动拆箱
		}
	}

包装类的判等

  • 包装类的判等,不要使用 ==!= 运算符,应该使用 equals 方法
// 缓存范围: [-128, 127]
Integer i1 = 88;
Integer i2 = 88;
Integer i3 = 888;
Integer i4 = 888;

// 不推荐, 比较的是内存地址
System.out.println(i1 == i2); // true, 为什么呢? 缓存, 具体看下面
System.out.println(i3 == i4); // false

// 推荐, 先比内存地址, 再比值
System.out.println(i1.equals(i2)); // true
System.out.println(i3.equals(i4)); // true

你可能会好奇为什么 i1 == i2 会返回 true,是因为:

  • IntegerCache 类中缓存了 [-128, 127] 范围的 Integer 对象(因为很常用)
  • Integer.valueOf 方法会优先去 IntegerCache 缓存中获取 Integer 对象
// 缓存范围: [-128, 127]
Integer i1 = 88; // 从IntegerCache缓存中取值
Integer i2 = Integer.valueOf(88); // 从IntegerCache缓存中取值
Integer i3 = new Integer(88); // new 出来的对象必然是一个新的地址

System.out.println(i1 == i2); // true
System.out.println(i2 == i3); // false

查看 Java 源码可以知道 valueOf 方法与 new 对象不完全等价, valueOf先判断缓存中是否有该值,没有再去new
在这里插入图片描述

包装类使用注意

  • 基本类型数组】与【包装类数组】之间是不能自动装箱、拆箱
public static void main(String[] args) {
	// 基本类型数组不会自动装箱
	int[] nums1 = { 11, 22 };
	// test1(nums1); // error
	// Integer[] nums2 = nums1; // error
	
	// 包装类数组不会自动拆箱
	Integer[] nums3 = { 11, 22 };
	// test2(num3); // error
	// int[] nums4 = num3; //error
}

static void test1(Integer[] nums) {}
static void test2(int[] nums) {}

Math

  • java.lang.Math 类提供了常见的数学计算功能

Math 类中提供了两个常用的数学常量

// 自然常数,自然对数函数的底数
public static final double E = 2.7182818284590452354;
// 圆周率
public static final double PI = 3.14159265358979323846;

常用方法:

Math.abs(-100);		// 求绝对值: 100
Math.max(100, 200);	// 求最大值: 200
Math.min(100, 200); // 求最小值: 100
Math.floor(3.9);	// 向下取整: 3.0
Math.ceil(3.1);		// 向上取整: 4.0
Math.round(3.5);	// 四舍五入: 4
Math.pow(4, 2);		// 4的2次方: 16.0
Math.sqrt(16);		// 16的平方根: 4.0
//角度转为弧度
double degree = 90; // 角度
double radian = Math.toRadians(degree); // 弧度
//三角函数
System.out.println(Math.sin(radian));
System.out.println(Math.cos(radian));
System.out.println(Math.tan(radian));
Math.random(); // 生成[0.0, 1.0)范围的随机数

Random

  • java.util.Random 可以更方便地生成各种随机数
    常用方法:
// 生成各种随机数
Random r = new Random();
r.nextBoolean();
r.nextInt();
r.nextLong();
r.nextFloat();

// 生成[0, 99]范围的整数
int num1 = (int)(Math.random() * 100); // [0, 100)
int num2 = new Random().nextInt(100);  // [0, 100)

// 生成[10, 99]范围的整数
// [0, 89] + 10
int num3 = (int)(Math.random() * 90) + 10; // [0, 89) + 10
int num4 = new Random().nextInt(90) + 10;  // [0, 89) + 10

输出4位的大写字母验证码:

// 输出4位的大写字母验证码
Random r = new Random();
for (int i = 0; i < 4; i++) {
	char c = (char)('A' + r.nextInt(26));
	System.out.print(c);
}
System.out.println();

UUID

UUID(Universally Unique Identifier),通用唯一标识符

  • UUID 的目的是让分布式系统中的所有元素都能有唯一的标识符,而不需要通过中央控制端来做标识符的指定

可以利用 java.util.UUID 类的 randomUUID 方法生成一个 128 bit(32 位 16 进制数)的随机 UUID

// 3b6d07be-6d4e-4c30-b5e8-8e57fedaa342
System.out.println(UUID.randomUUID());

数字格式化(printf、format)

可以使用 System.out.printf 或者 System.out.format 输出格式化的字符串
可以使用 String.format 创建格式化的字符串
在这里插入图片描述

long n = 461012;
System.out.format("%d%n", n);	   // "461012"
System.out.format("%08d%n", n);	   // "00461012"
System.out.format("%+8d%n", n);	   // " +461012"
System.out.format("%,8d%n", n);	   // " 461,012"
System.out.format("%+,8d%n%n", n); // "+461,012"

double pi = Math.PI;
System.out.format("%f%n", pi);	   // "3.141593"
System.out.format("%.3f%n", pi);   // "3.142"
System.out.format("%8.3f%n", pi);  // "   3.142"
System.out.format("%08.3f%n", pi); // "0003.142"
System.out.format("%-8.3f%n", pi); // "3.142"

String str = String.format("The PI is %.2f", Math.PI);
System.out.println(str); // The PI is 3.14

DecimalFormat

使用 java.text.DecimalFormat 可以更好地控制前 0、后 0、前缀、后缀、分组分隔符、十进制分隔符等

public class Main {
	public static void main(String[] args) {
		customFormat("###,###.###", 123456.789); // 123,456.789
		customFormat("###.###", 123456.789);	 // 123456.789
		customFormat("000000.000", 123.78);		 // 000123.780
		customFormat("$###,###.###", 12345.67);	 // $12,345.67
		
	}
	
	static void customFormat(String pattern, double value) {
		DecimalFormat fmt = new DecimalFormat(pattern);
		System.out.println(fmt.format(value));
	}
}

字符串与数字互转

字符串转数字:使用包装类的 valueOfparseXX 方法

Integer i1 = Integer.valueOf("12");   // 12
int i2 = Integer.parseInt("12");	  // 12
int i3 = Integer.parseInt("FF", 16);  // 255(十六进制解析FF)

Float f1 = Float.valueOf("12.34");	  // 12.34
Float f2 = Float.parseFloat("12.34"); // 12.34

数字转字符串:使用字符串的 valueOf 方法、包装类的 toString 方法

String str1 = String.valueOf(12.34);	 // 12.34
String str2 = Integer.toString(255);	 // 255
String str3 = Integer.toString(255, 16); // ff(255转为16进制), 可以解析任何进制
String str4 = Float.toString(12.34f);	 // 12.34

高精度计算

floatdouble 存储的只是小数的近似值,并非精确值。因此不适合用来进行高精度计算

double d1 = 0.7;
double d2 = 0.7;
System.out.println(d1 * d2); // 0.48999999999999994

为什么会这样呢?因为计算机底层都是二进制运算,0.7转二进制过程如下:结果是无限循环

0.7 = 0b0.101100110...
0.7 * 2 = 1.4 取出整除部分1
0.4 * 2 = 0.8 取出整除部分0
0.8 * 2 = 1.6 取出整除部分1
0.6 * 2 = 1.2 取出整除部分1
0.2 * 2 = 0.4 取出整除部分0
0.4 * 2 = 0.8 取出整除部分0
0.8 * 2 = 1.6 取出整除部分1
0.6 * 2 = 1.2 取出整除部分1
0.2 * 2 = 0.4  取出整除部分0
...

建议使用 java.math.BigDecimal 来进行高精度计算

  • 一般使用字符串初始化 BigDecimal,因为 floatdouble 存储的是近似值,不是精确值
BigDecimal v1 = new BigDecimal("0.7");
BigDecimal v2 = new BigDecimal("0.7");
System.out.println(v1.add(v2)); 	 // 加, 1.4
System.out.println(v1.subtract(v2)); // 减, 0.0
System.out.println(v1.multiply(v2)); // 乘, 0.49
System.out.println(v1.divide(v2));	 // 除, 1
System.out.println(v1.setScale(3));	 // 保留3位小数, 0.700

String

Java 中用 java.lang.String 类代表字符串:

  • Java 9 以前,底层使用 char[] 存储字符数据
    从 Java 9 开始,底层使用 byte[] 存储字符数据
  • 所有字符串字面量(比如 "abc" )都是 String 类的实例
  • String 对象一旦创建完毕,它的字符内容不可以修改

在这里插入图片描述

首先来看一段代码:并思考一下这段代码的输出是否与你想的不一样。

public class Main {
	public static void main(String[] args) {
		String s = "555";
		s += "555";
		s = "666";
		test(s);
		System.out.println(s); // 666
	}
	static void test(String str) {
		str += "555";
	}
}

String s = "555";s += "555";s = "666" 等操作都会在堆空间开辟一块新的内存空间,因为 String 是不可变字符串,每次要修改栈空间的变量sstr的值,只能改变它的指针指向,令指针指向堆空间新的内存。

我们知道,成员变量、函数形参等都是开辟在栈空间的(因为随时可能销毁),而上面代码中的 sstr 实际上在栈空间不是同一块布局,test 方法修改的是 str 的指向的值,所以 s 的值不会变。

在这里插入图片描述

字符串常量池(String Constant Pool)

Java 中有个字符串常量池(String Constant Pool,简称 SCP)

  • 从 Java 7 开始属于堆空间的一部分(以前放在方法区)

当遇到字符串字面量时,会去查看 SCP

  • 如果 SCP 中存在与字面量内容一样的字符串对象 A 时,就返回 A
  • 否则,创建一个新的字符串对象 D,并加入到 SCP 中,返回 D
String s1 = "mj"; // SCP中不存在字符串对象"mj"
String s2 = "mj"; // SCP中存在字符串对象"mj", 直接返回SCP中的"mj"
// 因此上面两个字符串对象是同一个对象
System.out.println(s1 == s2); // true

在这里插入图片描述

字符串的初始化

在 C语言里,字符数组就是字符串;在 Java 中,String 底层是由 char[] 组成的,但是他们不完全是一个东西。

在这里插入图片描述

利用 Java 的断点调试功能来验证上图:右半部分中的表示不是真实的内存地址,但是可以理解为标识不同则内存地址不同,相同则内存地址相同。可以看到 s1s2s3s4css5s6 的标识都是不同的;但是 s1s2s3s4 指向的value 的标识都是30,与上图展示的一致;s5s6 指向的value 的标识都是31。

在这里插入图片描述

intern 方法

A.intern 方法的作用:

  • 如果 SCP 中存在与 A 内容一样的字符串对象 C 时,就返回 C
  • 否则,将 A 加入到 SCP 中,返回 A
int a = 1, b = 2, c = 3;
String s1 = String.format("%d%d%d", a, b, c);
String s2 = String.format("%d%d%d", a, b, c);
// 去常量池中寻找"123", 发现没有, 就将s1的值"123"放入常量池
String s3 = s1.intern(); // s3、s1都指向了常量池中的"123"
String s4 = s2.intern(); // 返回常量池的"123"
String s5 = "123"; // s5指向常量池中的"123"

System.out.println(s1 == s2); // false
System.out.println(s1 == s3); // true
System.out.println(s1 == s4); // true
System.out.println(s1 == s5); // true

字符串的常用方法(截取)

// 去除左右的空格: "123  456"
" 123  456  ".trim();

// 转为大写字母: "ABC"
"abc".toUpperCase();
// 转为小写字母: "abc"
"ABC".toLowerCase();

// 是否包含某个字符串: true
"123456".contains("34");
// 是否以某个字符串开头: true
"123456".startsWith("123");
// 是否以某个字符串结尾: true
"123456".endsWith("456");

// 将字符串分隔为数组: [1, 2, 3, 4]
"1_2_3_4".split("_");

// 比较字符串的大小: <
"abc".compareTo("adc");
// 忽略大小写比较字符串的大小: <
"abc".compareTo("ADC");

String s1 = "abc";
String s2 = new String("abc");
// 查看字符串的内容是否相等: true
s1.equals(s2);
// 忽略大小写查看字符串的内容是否相等: true
"abc".equalsIgnoreCase("ABC");

// 字符串的替换
String str = "hello123";
// 由于String是不可变字符串, 必须用变量接收
str = str.replace("123", "666"); // hello666

穿插个知识点,Eclipse 中 抽取代码块的快捷键:先选中代码块,ALT + SHIFT + M

在这里插入图片描述

字符串截取内存分析图

String str = "abcdea";
// 字符串的截取区间: [2, 4)
str.substring(2, 4);  // cd
str.substring(2); 	  // cdea

str.indexOf("a"); 	  // 0
str.lastIndexOf("a"); // 5
str.indexOf("z"); 	// -1

在这里插入图片描述

StringBuilder

可变字符串(StringBuilder)

在进行大量字符串的改动操作时(比如拼接、替换)

  • 使用 String 会非常消耗内存、降低程序性能
  • 使用 StringBuilder 可以节省内存、提高程序性能
String s1 = "";
s1 += "123";
s1 += "456";

StringBuilder s2 = new StringBuilder();
s2.append("123").append("456");

StringStringBuilder 性能对比:50000 次字符串拼接操作

public static void main(String[] args) {
	testString(); 	// 2511ms
	testStringBuilder(); // 2ms
}

static void testString() {
	long begin = System.currentTimeMillis();
	String str = "";
	for (int i = 0; i < 50000; i++) {
		str += i;
	}
	long end = System.currentTimeMillis();
	System.out.println("String ->" + (end - begin) + "ms");
}
static void testStringBuilder() {
	long begin = System.currentTimeMillis();
	StringBuilder str = new StringBuilder();
	for (int i = 0; i < 50000; i++) {
		str.append(i);
	}
	long end = System.currentTimeMillis();
	System.out.println("StringBuilder ->" + (end - begin) + "ms");
}
String -> 2511ms
StringBuilder -> 2ms

StringBuilder 的常用方法有:appendinsertdeletereplacereverse等;

注意:

  • StringBuilder 并不是 String 的子类 或者 父类
  • StringBuilderString 都实现了 CharSequence 接口

StringBuilder 的 append 原理

StringBuilder 的原理类似动态数组 ArrayList:底层会自动扩容;

在这里插入图片描述

源码中可以看到扩容的算法:(value.length << 1) + 2 即 新容量是原来容量的 2 倍 + 2

在这里插入图片描述

Date

java.util.Date 是开发中经常用到的日期处理类(注意:不是 java.sql.Date

  • 包含了年、月、日、时、分、秒等信息
// date1 代表当前时间
Date date1 = new Date();
// System.currentTimeMillis 表示 1970-01-01 00:00:00 GMT 到现在时间的毫秒数
// date2 也代表当前时间
Date date2 = new Date(System.currentTimeMillis());
// date3 代表 1970-01-01 00:00:00 GMT 
Date date3 = new Date(0);

// Mon Apr 20 13:44:49 CST 2020
System.out.println(date1);
// Mon Apr 20 13:44:49 CST 2020
System.out.println(date2);
// Thu Jan 01 08:00:00 CST 1970
System.out.println(date3);

System.currentTimeMillis 返回的是从 1970-01-01 00:00:00 GMT 开始到现在经历的毫秒数

  • 1970-01-01 00:00:00 GMT、1970-01-01 08:00:00 CST 代表的是同一个时间
    • GMT(Greenwich Mean Time):格林尼治时间
    • CST(China Standard Time UT+8:00):中国标准时间

Date 常用方法:

Date d1 = new Date();
Date d2 = new Date();

// 设置毫秒数, d1比d2早(d2经历的时间比d1大)
d1.setTime(1000);
d2.setTime(2000);

// 获取毫秒数
d1.getTime(); // 1000
d2.getTime(); // 2000

// 比较时间
d2.after(d1);  // true
d1.before(d2); // true
d1.compareTo(d2); // -1(d2经历的时间比d1大)

SimpleDateFormat

java.text.SimpleDateFormat 常用来进行日期的格式化处理

SimpleDateFormat fmt = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
// 利用日期对象生成字符串
String str = fmt.format(new Date());
// 2020年04月20日 13:57:13
System.out.println(str); 

// 解析字符串为日期对象
Date date = fmt.parse(str);
// Mon Apr 20 13:57:13 CST 2020
System.out.println(date);

SimpleDateFormat 的模式字母:

在这里插入图片描述

Calendar

java.util.Calendar 也是开发中经常用到的日期处理类

  • 功能比 Date 更加丰富,Date 中很多过期的方法都迁移到了 Calendar

示例:目前是 2020年4月20日 星期一 下午 14:16:33

// 表示当前时间
Calendar c = Calendar.getInstance();
c.getTime(); // Mon Apr 20 14:16:33 CST 2020

c.get(Calendar.YEAR); // 年: 2020

// 月(取值范围[0, 11], 0是1月, 11是12月)
c.get(Calendar.MONTH); // 3

// 一月中的第几天(取值范围[1, 31])
c.get(Calendar.DAY_OF_MONTH); // 20

// 一周中的第几天(取值范围[1, 7], 1是星期天, 2是星期一...,7是星期六)
c.get(Calendar.DAY_OF_WEEK); // 2

// 一年中的第几天(取值范围[1, 366])
c.get(Calendar.DAY_OF_YEAR); // 111

c.get(Calendar.HOUR);   // 时: 2
c.get(Calendar.MINUTE); // 分: 16
c.get(Calendar.SECOND); // 秒: 33
c.get(Calendar.MILLISECOND); // 毫秒: 330

Calendar 的常用方法:

Calendar c = Calendar.getInstance();
// 设置时间为 2019年7月6日
c.set(2019, 06, 06);
// 2019年7月11日
c.add(Calendar.DAY_OF_MONTH, 5);
// 2019年9月11日
c.add(Calendar.MONTH, 2);

// 设置Date对象
c.setTime(new Date());
// 获得Date对象
c.getTime();

// 设置毫秒数
c.setTimeInMillis(System.currentTimeMillis());
// 获得毫秒数
c.getTimeInMillis();

打印格式化(很少用)

在这里插入图片描述

// 获取当前时间
Calendar c = Calendar.getInstance();
Date date = new Date();

System.out.format("%tB %te, %tY%n", date, date, date); // 四月 20, 2020
System.out.format("%tl:%tM %tp%n", c, c, c); // 2:24 下午
System.out.format("%tD%n", c); // 04/20/20
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值