Java.lang包中的常用类 --8个包装类型、Math、Class
学习目标
- 掌握8个包装类型
- 掌握自动拆箱和自动装箱
- 了解Math
- 了解Object
- 了解Class
1.包装类
1.1. 什么是包装类
● 在Java中万事万物皆对象,但是基本数据类型的值就不是对象,那感觉就是这8个基本数据类型就很另类了;
● 因此为了让基本数据类型也具有对象的性质,JDK中定义了8个引用类型与前面所讲的8个基本数据类型相对应,而这8个引用类型都称之为包装类型;这8个类 都定义在java.lang包中,因此使用的时候也不需要显示导入;
● 包装类中定义了很多方法,静态或者实例的方法,丰富了对基本数据类型的操作;
● 之所以现在要讲包装类型,不仅是因为它们是常用类,而且 在后面要讲的集合的那一章里面,集合中是只能放对象而不能放基本数据类型的值的;因此必须要知道每个基本数据类型其所对应的包装类型;
● 这8个包装类的名称 除了 Integer Character名字不一样之外,其他都是 基本数据类型名称的首字母大写;
实际开发中, 需要从数据库中对接数据, 此时有可能对接null值, 且数据库中的数据存在数据类型区分, 所以无法存储null
的基本类型不能应对该需求场景
● 简单对比基本数据类型与包装类型
● 基本数据类型 与包装类作用一致的。
● 为什么要提供与基本数据类型作用一致的包装类?
1. java是一门面向对象的语言。不能根据基本数据类型创建对象 不太符合面向对象的思想。
2. 丰富基本数据类型的操作 提供了8个包装类型。 包含一些属性/方法、创建对象、
3. 学习集合。是一个容器。存储引用数据类型的数据。
● 基本数据类型 VS 包装类型?
● 相同点: 代表具体的一个数据 作用是一致的。
● 不同点: 一个是类(属性/方法、创建对象) 一个是基本数据类型
基本数据类型 | 默认值 | 包装类型 | 默认值 |
---|---|---|---|
byte | 0 | Byte | null |
short | 0 | Short | null |
int | 0 | Integer | null |
long | 0L | Long | null |
float | 0.0F | Float | null |
double | 0.0D | Double | null |
char | \u0000 | Character | null |
boolean | false | Boolean | null |
public static void method1() {
byte by2 = 123;
Byte by1;
Short sh1;
Integer in1;
Long lo1;
Float f1;
Double d1;
Character c1;
Boolean b1;
}
1.2. Integer
● 由于JDK存在8个包装类型,而且特征都是类似的。
● 因此我们在学习的时候以Integer以及Character为例,学习所有的包装类型。
1.2.1 层级
public final class Integer extends Number implements Comparable<Integer> {
}
1.2.2 常用构造
● 自从JDK9之后包装类的构造方法都不建议使用了,创建包装类对象的方式都直接使用字面量赋值。
● 所有的包装类型都是一样。
1.2.3 创建对象
private static void demo1() {
//过时了 不建议使用
Integer num1 = new Integer(100);
//将string的数据转换成Integer对象的数据
Integer num2 = new Integer("100");
System.out.println(num1);
System.out.println(num2);
int num3 = 100;
System.out.println(num3);
Integer num4 = 100;
}
1.2.4 装箱与拆箱
jdk5+提供:为了简化双方的转换成本, 提供了自动封箱与自动拆箱
- 自动装箱: 基本数据类型的数据转换成包装类型的对象 包装类.valueOf(基本数据类型 变量);
- 自动拆箱: 包装类对象转换成基本类型的数据 包装类引用.xxxValue()
总结一句话就是:两者使用上没差别 可以自动转;
int n1 = 10;
//自动封箱
Integer i1 = n1;
//自动拆箱
int n2 = i1;
注意:
- 自动封箱与拆箱只能发生在相同类型之间
- 包装类型之间不存在大小关系, 其相互平级, 不能发生自动类型转换
1.2.5 整数缓存池
private static void demo3() {
Integer num1 = new Integer("100");
Integer num2 = new Integer(100);
System.out.println(num1);
System.out.println(num2);
System.out.println("(num1==num2):" + (num1 == num2));//false
//整数缓存池-----> 数组里面存储的整数 Integer (-128-127) 256
Integer num3 = Integer.valueOf(100);
//基本数据转包装类对象 自动装箱 底层: Integer.valueOf()
System.out.println("(num1==num3):" + (num1 == num3));//false
Integer num4 = 100;
System.out.println("(num4==num3):" + (num4 == num3));//true 一块内存 new
Integer num5 = 200;
Integer num6 = 200;
System.out.println("(num5==num6):" + (num5 == num6));//false
}
● 整数缓存池。IntegerCache.cache[] -128-127之间的数字. 256个对象 new 256次
● 假如Integer num1 = 100; Integer num2 = 100; Integer num3 = 100; Integer num4 = 100; 其实内存中只有1个Integer对象。
● 优势: 提高资源的使用率 多次使用1个对象。避免内存的过渡浪费
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer[] cache;//存储都是Integer对象
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
哪些包装类中具备整数缓存池思想?
- Integer-----> IntegerCache.cache Integer[] cache -128-127
- Byte-----> ByteCache.cache Byte[] cache -128-127
- Short-----> ShortCache.cache Short[] cache -128-127
- Long-----> LongCache.cache Long[] cache -128-127
- Character----> CharacterCache.cache Character[] cache 0-127 128 ASCII
1.2.6 其它方法
public static void method2() {
//此赋值方式和String非常相似,注意in1,in2都是对象,都是引用
//in1和in2中所存储的依然的堆空间的某块内存的起始地址值
Integer in1 = 66;
Integer in2 = 999;
//静态的成员变量 代表此类型的表叔范围
System.out.println(Integer.MIN_VALUE);
System.out.println(Integer.MAX_VALUE);
//静态方法parseInt() 把字符串转换为int类型 如果出现非数字的字符则会报异常 NumberFormatException 例如"abc";
int a1 = Integer.parseInt(“123”);
//静态方法valueOf() 功能是把字符串转换为其所对应的Integer对象 同样的如果出现非数字的字符则会报异常 NumberFormatException 例如"abc";
Integer in3 = Integer.valueOf(“123”);
// compareTo实例方法用于比较两个Integer对象的大小,返回值只有三种情况: 1表示第一个数大 0表示两个数相等
System.out.println("compareTo: " + in2.compareTo(1999));
System.out.println("parse: " + a1);
System.out.println(in3);
}
private static void demo6() {
//Integer其他功能方法
Integer num1 = 100;
//1.Object.hashcode()获得对象的hash值
System.out.println(num1.hashCode());//与对象的数据是一致的
//2.与其他类型的转换
float value = num1.floatValue();
//3.运算
System.out.println(Integer.sum(10, 20));
System.out.println(Integer.max(10, 1));
System.out.println(Integer.min(10, 1));
System.out.println(Integer.toHexString(1000));
//将10进制的整型的数据转换成无符号16进制的字符串
//4.String与整型之间相互转换
int num = 100;
//int 转 String +
System.out.println(num + "");
System.out.println(String.valueOf(num));
Integer num2 = 200;
System.out.println(num2.toString());
System.out.println(num2 + "");
System.out.println(String.valueOf(num2));
System.out.println("-------------------------");
//String转int
String str = "1234";//转10进制
/* Integer i = new Integer(str);
System.out.println(Integer.valueOf(str));
System.out.println(Integer.valueOf(str, 10));*/
//将指定的字符串数据 转换成指定进制的数值
// 2 0 1
// 8 0-7
// 10 0-9
// 16 0-F
// 36 0-Z
int anInt = Integer.parseInt(str);
System.out.println(anInt);
System.out.println(Character.MIN_RADIX);
System.out.println(Character.MAX_RADIX);
}
1.3 Character
了解里面常用的方法即可。
public static void method6() {
// 静态方法
System.out.println(Character.toUpperCase('a'));
System.out.println(Character.toLowerCase('A'));
Character character = 'a';
Character character1 = Character.valueOf('a');
char[] chars = {'2', 'a', 'A'};
for (int i = 0; i < chars.length; i++) {
System.out.print(chars[i]);
System.out.println(Character.isDigit(chars[i]) ? "是数字字符" : "不是数字字符");
System.out.println(Character.isLetter(chars[i]) ? " isLetter " : " not letter");
System.out.println(Character.isUpperCase(chars[i]) ? " isUpper " : " not Upper");
System.out.println(Character.isLowerCase(chars[i]) ? " isLower " : " not lower");
}
int digit(char ch, int radix) 将字符转换成指定进制的数字
int result = Character.digit('Z', 36);
System.out.println(result);//-1
}
1.4 值不可变性
● 8个包装类数据是不可变的,其对象也都是不可变的对象;
● 原因都是其内部用来存储值的成员变量都是使用final修饰的,一旦赋值不可改变。
/**
* 2,Integer 包装类型都是 是不可变的对象 每次重新赋值 都会有新的对象
*/
public static void method3() {
//如下代码 是有2个字符串对象 而str只是个引用而已,引用中存储的是地址
// String str = "abc";
// str="java";
//同样的in1也是引用类型 所有in1中存储的也是地址。下面的代码中有两个Integer对象;
Integer in1 = 127;
in1 = 128;
}
包装类内部用于存储值的成员变量源码
/**
* The value of the {@code Integer}.
*
* @serial
*/
private final int value;
1.5 进制与位运算
程序中使用的进制的表示
/**
* 进制
* 二进制(逢2进1) 以0b/0B 打头
* 八进制(逢8进1) 以数字0打头
* 十进制(逢10进1)
* 十六进制(逢16进1 A-10 b-11 c-12 d-13 e-14 f-15)
* 0x或者0X 16进制的1位相当于二进制的4位
*/
public static void method7() {
int a = 012; // 0000 1010
System.out.println("八进制 0: " + a);
int b = 0Xaf; // 15+10*16
System.out.println("16进制: " + b);
int c = 0b10101111;
System.out.println("2进制: " + c);
}
位运算: 左移与右移
/**
* << 左移动相当于乘2的指数值 >>向右移动 相当于除2的指数值
*/
public static void method8() {
// 0011 1100
System.out.println(3 << 2); // 3*2^2
// 1010 0010
System.out.println(10 >> 2); // 10/2^2
}
逻辑符号用于数值运算
● &表示两边都为1 结果才是1
● |表示两边只要有一个是1 结果就是1
/**
* & |
*/
public static void method9() {
/**
* 1111
* 1010 &表示上下都为1才是1 |是只要有一个为1就是1
* ------------
* 1010
*/
System.out.println(15 & 10);
System.out.println(15 | 0);
}
1.6 使用场景
//使用场景
//在以后类中 属性/形参/返回值类型 建议使用包装类型维护
public class UserInfo {
//private int id;//0
//private Integer id;//null
//public double zengZhangLv;
//public Double zengZhangLv;//增长率 null%
//增长率 0.0% 由于某些原因忘记赋值或者程序报错,对于基本类型有默认值,比如0.0
//在这种情况下 可能会认为0.0% 也是ok的数据。
public Integer demo(int num){
//局部变量 建议使用基本数据类型
//形参与返回值类型建议使用包装类型 可以结合null值进行提前预判 规避一些不正常的现象产生。
//如果真的不想用 其实影响也不是很大。我们了解这种情况即可。
return null;
}
}
2.Math
● Math单词表示数学。因此Math类内部提供的都是和数学运算相关的方法;
● Math类内部所有的方法都是静态方法,都是功能性方法;
/**
* Math类的常用方法
* 数学运算相关方法
*/
public static void method1() {
System.out.println("绝对值:" + Math.abs(-32));
System.out.println(Math.PI);
System.out.println("次方: " + Math.pow(12.5, 2));
System.out.println("开根号: " + Math.sqrt(16));
System.out.println("max: " + Math.max(16, 10));
System.out.println("min: " + Math.min(16, 10));
System.out.println("[0,1) 浮点数: " + Math.random());
// 四舍五入值
System.out.println(Math.round(5.5));
// ceil 向上取整 取最小
System.out.println(Math.ceil(5.3));
// floor 向下取整 取最大
System.out.println(Math.floor(5.3));
//+ - *
System.out.println(Math.addExact(10, 20));
System.out.println(Math.subtractExact(10, 20));
System.out.println(Math.multiplyExact(10, 20));
}
3.Object
超级父类。需要了解Object类中的每个方法。
3.1 常用方法
方法 | 作用 |
---|---|
String toString() | 返回对象的字符串表示形式。 |
protected Object clone(); | 创建并返回此对象的副本 |
boolean equals(Object obj) | 比较2个对象是否相等 |
Class<?> getClass() | 返回此 Object的运行时类。获得当前正在运行的类或者接口的class类对象。 |
int hashCode() | 返回对象的哈希码值 |
void notify() | 唤醒正在此对象监视器上等待的单个线程 |
void notifyAll() | 唤醒等待此对象监视器的所有线程。 |
void wait() | 当前线程等待 |
void wait(long timeoutMillis) | 时间内等待 |
void wait(long timeoutMillis, int nanos) | — |
3.2 toString
● 面向对象本质: 使用对象封装数据 使用类管理代码
● 打印输出对象,就是想看到属性的数据。底层默认执行toString的方法,打印输出的16进制的hash值
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class UserInfo /*extends Object*/{
private Integer id;
private String name;
private Integer age;
// @Override
// public String toString() {
// return "UserInfo{" +
// "id=" + id +
// ", name='" + name + '\'' +
// ", age=" + age +
// '}';
// }
}
public static void main(String[] args) {
UserInfo userInfo1 = new UserInfo(1,"张三",18);
UserInfo userInfo2 = new UserInfo(1,"张三",18);
//打印输出引用类型对象 底层默认Object.toString()
System.out.println(userInfo1.toString());
System.out.println(userInfo2);
}
3.3 equals+hashcode
为什么重写equals+hashcode?
在正常生活中 我们会认为2个对象 是一个对象。 true
比较2个对象是否一致/相等
1.比较运算符: == 内存地址值
2.比较对象: equals+hashcode
- 在jvm规范: 2个对象的hashcode不同 这2个对象就是不等的。
- 2个对象的hashcode相同 不能直接认为2个对象相同的 hash算法有弊端。hash会经常出现碰撞。
- 2个对象equals 结果true 这2个对象的hash必须相同。
- jvm硬性规定: 重写equals 也必须重写hashcode。equals+hashcode规则一致的。
需求: 比较2个对象是否一致/相等
public boolean equals(Object obj) {
return (this == obj);
}
public native int hashCode();
@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class UserInfo {
private Integer id;
private String name;
private Integer age;
}
public static void main(String[] args) {
UserInfo userInfo1 = new UserInfo(1, "张三", 18);
UserInfo userInfo2 = new UserInfo(2, "张三", 20);
//打印输出引用类型对象 底层默认Object.toString()
System.out.println(userInfo1.toString());
System.out.println(userInfo2);
//System.out.println("(userInfo1==userInfo2):" + (userInfo1 == userInfo2));
System.out.println("(userInfo1.equals(userInfo2)):" + userInfo1.equals(userInfo2));
int code1 = userInfo1.hashCode();
int code2 = userInfo2.hashCode();
System.out.println(code1);
System.out.println(code2);
String s1 = "Ma";
String s2 = "NB";
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
}
@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
//@EqualsAndHashCode //类中所有的属性都参与
public class UserInfo /*extends Object*/ {
private Integer id;
private String name;
private Integer age;
//2个对象相等的规则: id name age 都相同的时候
@Override
public boolean equals(Object obj) {
//this obj
if (obj == null) return false;
if (!(obj instanceof UserInfo)) return false;
UserInfo userInfo = (UserInfo) obj;
return
Objects.equals(name, userInfo.name)
;
}
//重写父类的hashcode
@Override
public int hashCode() {
return Objects.hash(name);
}
}
3.4 clone
克隆对象。属于创建对象的一种方式
Object里面的clone()方法
protected native Object clone() throws CloneNotSupportedException;
@Setter
@Getter
@Accessors(fluent = true)
@ToString
public class GirlFriend extends Object implements Cloneable {
//Cloneable是一个标记接口。类实现Cloneable接口 代表这个类对象可以被克隆的。
private Integer id;
private String name;
private int age;
private String[] hobby;
//思考:复制的是数据还是内存地址值?
//基本的数据类型: 数据
//引用数据类型: 内存地址值
//值的传递
@Override
public GirlFriend clone() {
GirlFriend clone = null;
try {
clone = (GirlFriend) super.clone();
//对属性执行遍历式克隆
//clone.hobby(Arrays.copyOf(this.hobby,hobby.length));
clone.hobby(this.hobby.clone());
} catch (CloneNotSupportedException e) {
System.out.println("类必须实现Cloneable接口");
e.printStackTrace();
}
return clone;
}
public GirlFriend() {
System.out.println("无参构造...........");
}
}
浅克隆与深克隆
1.浅克隆(浅复制/copy)
- 是否真的可以克隆成功? 为什么会有CloneNotSupportedException?
- 代码可能会报错。 类一定要实现Cloneable标记接口。
- 克隆的对象与源对象是否是同一个对象?
- 不是同一个
- 克隆的对象是否执行了构造方法?
- 没有执行构造方法。jvm底层调用C创建新的对象
- 克隆对象的属性的数据是否有值? 数据是否与原对象的属性的数据是一致的?
- 有值 与源对象的数据是一致的。
5.修改了一个对象的属性的数据 是否会影响另外一个对象的数据?- 在浅克隆的操作下 修改一个对象的数据 可能会影响另外一个对象的数据
- 属性: 基本数据类型 没有任何影响 复制的是数据
- 包装类型+String—> 值不可变 修改一个对象 值不变的
- 其他的引用数据类型 数据就会发生改变 数组,自定义的类(浅克隆的弊端)
- 深克隆/序列化-----> 解决浅克隆的问题
- 需求: 修改一个对象的数据 不要影响另外一个对象的数据、
- 操作: 对象在哪里克隆的 就在那里修改
public static void main(String[] args) {
String[] hobby = new String[]{"大提琴", "show", "sing"};
GirlFriend girlFriend1 = new GirlFriend().id(1).name("欧阳娜娜").age(18).hobby(hobby);
//克隆一个女友对象 Object.clone();
//girlFriend1.clone
//1.在其他类中 无法访问clone protected
//2.在父类clone方法中 返回值类Object----> GirlFriend
//建议在子类中重写父类的方法
GirlFriend cloneGirlFriend = girlFriend1.clone();
System.out.println("girlFriend1:" + girlFriend1);
System.out.println("cloneGirlFriend:" + cloneGirlFriend);
cloneGirlFriend.id(2);
cloneGirlFriend.name("欧阳娜娜2");
cloneGirlFriend.age(20);
cloneGirlFriend.hobby()[0] = "小提琴";
System.out.println("---------------------------");
System.out.println("girlFriend1:" + girlFriend1);
System.out.println("cloneGirlFriend:" + cloneGirlFriend);
}
● 克隆对单例是否有影响?
public class Cat implements Cloneable {
private Cat() {
}
private final static Cat instance = new Cat();
public static Cat getInstance() {
return instance;
}
@Override
public Cat clone() throws CloneNotSupportedException {
return this;
}
}
class Test{
public static void main(String[] args) throws CloneNotSupportedException {
/*for (int i = 0; i < 100; i++) {
System.out.println(Cat.getInstance());
}*/
//克隆是否可以打破单例? 可以打破单例
Cat cat1 = Cat.getInstance();
Cat cloneCat = cat1.clone();
System.out.println(cat1);
System.out.println(cloneCat);
System.out.println(cat1==cloneCat);
//有且只有1个cat对象
}
}
3.5 finalize
final VS finally VS finalize?
1. final 修饰符
类 变量 方法
2. finally
异常处理机制的关键字。 try....catch...finally try...finally
释放物理资源/释放监视器
释放物理资源: jdk1.7+ 建议使用try...with....reources 省略finally代码块
3.finalize
是Object.finalize(); 满足于GC回收无用引用关联的对象,释放内存。不需要手动调用。 目前方法过时了。
4.Class
● Class类代表当前程序中正在运行的类或者接口的Class类对象。
● Class类是反射机制的基础。
● 目前我们只先了解获得Class类对象的3种方式,与反射相关的技术点我们到后面学习。
● 在开发中,本质上下面3种方式,我们都可以使用。
private static void demo4() {
//需求: 获得UserInfo.class在运行期间的Class类对象
//1. Class getClass() 不推荐 与对象的耦合度太高了 不太建议
UserInfo userInfo = new UserInfo();
Class userInfoClass = userInfo.getClass();//userInfoClass就等价于获得UserInfo.class
System.out.println(userInfoClass.toString());//class com.java .lang.UserInfo
System.out.println(userInfoClass.getName());
System.out.println(userInfoClass.getSimpleName());
//2.使用静态属性 class 类型的名称.class 推荐使用的 在本项目内访问本项目内其他的类或者接口
//任意一个类型里面 都有一个静态属性 class
Class aClass = int.class;
Class userInfoClass = UserInfo.class;
Class cloneableClass = Cloneable.class;
System.out.println(aClass);
System.out.println(userInfoClass.toString());
System.out.println(userInfoClass.getName());
System.out.println(userInfoClass.getSimpleName());
//3. Class.forName("类/接口的全限定名称"); 类/接口的路径 包名+类名 com.java .lang.UserInfo
//推荐 加载第3方类库资源里面的类或者接口 (jar)
try {
Class aClass = Class.forName("com.java .lang.UserInfo");
System.out.println(aClass.getName());
System.out.println(aClass.getSimpleName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}