• Java常用类(一)


    基本数据类型的包装类

    我们之前写过八种基本数据类型并不是对象,为了将基本类型数据和对象之间实现互相转化,Java 为每一个基本数据类型提供了相应的包装类
    在这里插入图片描述

    ⭐ 包装类基本知识

    Java 是面向对象的语言,但并不是“纯面向对象”的,因为我们经常用到的基本数据类型就不是对象。但是我们在实际应用中经常需要将基本数据转化成对象,以便于操作。比如:将基本数据类型存储到 Object[ ]数组或集合中的操作等等。

    为了解决这个不足,Java 在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。

    包装类位于 java.lang 包,八种包装类和基本数据类型的对应关系:
    在这里插入图片描述
    在这八个类名中,除了 Integer 和 Character 类以外,其它六个类的类名和基本数据类型一致,只是类名的第一个字母大写而已。
    在这里插入图片描述

    Number 类是抽象类,因此它的抽象方法,所有子类都需要提供实现。Number 类提供了抽象方法:intValue()、longValue()、floatValue()、doubleValue(),意味着所有的“数字型”包装类都可以互相转型。

    下面我们通过一个简单的示例认识一下包装类。

    【示例】初识包装类

    public class WrapperClassTest {
    	public static void main(String[ ] args) {
    		Integer i = new Integer(10); //从 java9 开始被废弃
    		Integer j = Integer.valueOf(50); //官方推荐
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    示例内存分析如图所示:
    在这里插入图片描述

    ⭐ 包装类的用途

    对于包装类来说,这些类的用途主要包含两种:

    1. 作为和基本数据类型对应的类型存在,方便涉及到对象的操作,如 Object[ ]、集合等的操作。
    2. 包含每种基本数据类型的相关属性如最大值、最小值等,以及相关的操作方法(这些操作方法的作用是在基本数据类型、包装类对象、字符串之间提供相互之间的转化!)。

    【示例】包装类的使用

    public class Test {
    /** 测试 Integer 的用法,其他包装类与 Integer 类似 */
    void testInteger() {
    // 基本类型转化成 Integer 对象
    Integer int1 = new Integer(10); //已经被废弃,不推荐使用
    Integer int2 = Integer.valueOf(20); // 官方推荐这种写法
    // Integer 对象转化成 int
    int a = int1.intValue();
    // 字符串转化成 Integer 对象
    Integer int3 = Integer.parseInt("334");
    Integer int4 = new Integer("999");
    // Integer 对象转化成字符串
    String str1 = int3.toString();
     // 一些常见 int 类型相关的常量
    System.out.println("int 能表示的最大整数:" + Integer.MAX_VALUE); 
    }
    public static void main(String[ ] args) {
    		Test test = new Test();
    		test.testInteger();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    执行结果如图所示:

    ⭐ 自动装箱和拆箱

    自动装箱(autoboxing)和拆箱(unboxing):将基本数据类型和包装类自动转换。
    在这里插入图片描述

    自动装箱:

    基本类型的数据处于需要对象的环境中时,会自动转为“对象”。

    我们以 Integer 为例:

    Integer i = 5
    
    • 1

    编译器会自动转成:

    Integer i = Integer.valueOf(5)
    
    • 1

    这就是 Java 的自动装箱。

    自动拆箱:

    每当需要一个值时,对象会自动转成基本数据类型,没必要再去显式调用intValue()、doubleValue()等转型方法。

    Integer i = Integer.valueOf(5);
    int j = i;
    
    • 1
    • 2

    编译器会自动转成:

    int j = i.intValue();
    
    • 1

    这样的过程就是自动拆箱。

    自动装箱/拆箱的本质是:

    自动装箱与拆箱的功能是编译器来帮忙,编译器在编译时依据您所编写的语法,决定是否进行装箱或拆箱动作。

    【示例】自动装箱

    Integer i = 100;//自动装箱
    //相当于编译器自动为您作以下的语法编译:
    Integer i = Integer.valueOf(100);//调用的是 valueOf(100),而不是 new Integer(100)
    
    • 1
    • 2
    • 3

    【示例】自动拆箱

    Integer i = 100;
    int j = i;//自动拆箱
    //相当于编译器自动为您作以下的语法编译:
    int j = i.intValue();
    
    • 1
    • 2
    • 3
    • 4

    自动装箱与拆箱的功能是所谓的“编译器蜜糖(Compiler Sugar)”,虽然使用这个功能很方便,但在程序运行阶段得了解 Java 的语义。

    如下所示的程序是可以通过编译的:
    【示例】包装类空指针异常问题

    	public class Test1 {
    		public static void main(String[ ] args) {
    			Integer i = null;
    			int j = i;
    		}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    执行结果如图所示:
    在这里插入图片描述

    运行结果之所以会出现空指针异常,是因为如上代码相当于:

    	public class Test1 {
    		public static void main(String[ ] args) {
    			Integer i = null; 
    			int j = i.intValue();
    		}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    包装类的缓存问题

    整型、char类型所对应的包装类,在自动装箱时,对于-128~127之间的值会进行缓存处理,其目的是提高效率。

    缓存原理为:如果数据在-128~127这个区间,那么在类加载时就已经为该区间的每
    个数值创建了对象,并将这256个对象存放到一个名为cache的数组中。每当自动装箱过程发生时(或者手动调用valueOf()时),就会先判断数据是否在该区间,如果在则直接获取数组中对应的包装类对象的引用,如果不在该区间,则会通过new调用包装类的构造方法来创建对象。

    下面我们以Integer类为例,看一看Java为我们提供的源码,加深对缓存技术的理解,如下所示。

    【示例】Integer 类相关源码

    	public static Integer valueOf(int i) {
     		if (i >= IntegerCache.low && i <= IntegerCache.high)
     		return IntegerCache.cache[i + (-IntegerCache.low)];
     		return new Integer(i);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这段代码中我们需要解释下面几个问题:

    1. IntegerCache类为Integer类的一个静态内部类,仅供Integer类使用。
    2. 一般情况下 IntegerCache.low为-128,

    IntegerCache.high为127,IntegerCache.cache为内部类的一个静态属性,如示例所示。

    【示例】IntegerCache 类相关源码

    private static class IntegerCache {
     static final int low = -128;
     static final int high;
     static final Integer cache[ ];
     static {
     // high value may be configured by property
     int h = 127;
     String integerCacheHighPropValue =
     sun.misc.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() {}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    由上面的源码我们可以看到,静态代码块的目的就是初始化数组cache的,这个过程
    会在类加载时完成。

    下面我们做一下代码测试,如示例所示。

    【示例 8-9】包装类的缓存测试

    	public class Test3 {
    		public static void main(String[ ] args) {
    			Integer in1 = -128;
    			Integer in2 = -128;
    		System.out.println(in1 == in2);//true 因为 123 在缓存范围内
    		System.out.println(in1.equals(in2));//true
    			Integer in3 = 1234;
    			Integer in4 = 1234;
    		System.out.println(in3 == in4);//false 因为 1234 不在缓存范围内
    		System.out.println(in3.equals(in4));//true
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    注意:

    🐟	自动装箱调用的是 valueOf()方法,而不是 new Integer()方法。
    🐟	自动拆箱调用的 xxxValue()方法。
    🐟	包装类在自动装箱时为了提高效率,对于-128~127 之间的值会进行缓存处理。超过范围后,对象之间不能再使用==进行数值的比较,而是使用 equals 方法。
    
    • 1
    • 2
    • 3

    ⭐ 自定义一个简单的包装类

    public class MyInteger {
     private int value;
     private static MyInteger[] cache = new MyInteger[256];
     public static final int LOW = -128;
     public static final int HIGH = 127;
     static {
     //[-128,127]
     for(int i=MyInteger.LOW;i<=HIGH;i++){
     //-128,0;-127,1;-126,2;
     cache[i+128] = new MyInteger(i);
     }
     }
     public static MyInteger valueOf(int i) {
     if(i>=LOW&&i<=HIGH) {
     return cache[i+128];
     }
     return new MyInteger(i);
     }
     @Override
     public String toString() {
     return this.value+"";
     }
     public int intValue(){
     return value;
     }
     private MyInteger(int i) {
     this.value = i;
     }
     public static void main(String[] args) {
     MyInteger m = MyInteger.valueOf(30);
     System.out.println(m);
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    ⭐ 字符串相关类

    String 类代表不可变的字符序列
    StringBuilder 类和 StringBuffer 类代表可变字符序列。
    在这里插入图片描述

    这三个类的用法,在实际开发中经常用到,必须掌握好。

    String 类源码分析

    String 类对象代表不可变的 Unicode 字符序列,因此我们可以将 String 对象称为“不
    可变对象”。 那什么叫做“不可变对象”呢?指的是对象内部的成员变量的值无法再改变。

    我们打开 String 类的源码,如图所示:
    在这里插入图片描述

    我们发现字符串内容全部存储到 value[ ]数组中,而变量 value 是 final 类型的,也就是常量(即只能被赋值一次)。 这就是“不可变对象”的典型定义方式。

    我们发现 String 的某些方法,比如:substring()是对字符串的截取操作,但本质是读取原字符串内容生成了新的字符串。测试代码如下:

    【示例】String 类的简单使用

    	public class TestString1 {
    		public static void main(String[ ] args) {
    			String s1 = new String("abcdef");
    			String s2 = s1.substring(2, 4);
    			// 打印:ab199863
    			System.out.println(Integer.toHexString(s1.hashCode()));
    			// 打印:c61, 显然 s1 和 s2 不是同一个对象
    			System.out.println(Integer.toHexString(s2.hashCode()));
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在遇到字符串常量之间的拼接时,编译器会做出优化,即在编译期间就会完成字符串的拼接。因此,在使用(==)进行 String 对象之间的比较时,我们要特别注意,如示例所示。

    【示例】字符串常量拼接时的优化

    	public class TestString2 {
    		public static void main(String[ ] args) {
    		//编译器做了优化,直接在编译的时候将字符串进行拼接
    		String str1 = "hello" + " java";//相当于 str1 = "hello java";
    		String str2 = "hellojava";
    		System.out.println(str1 == str2);//true
    		String str3 = "hello";
    		String str4 = " java";
    		//编译的时候不知道变量中存储的是什么,所以没办法在编译的时候优化
    		String str5 = str3 + str4;
    		System.out.println(str2 == str5);//false
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    StringBuffer 和 StringBuilder 可变字符序列
    在这里插入图片描述

    StringBuffer 和 StringBuilder 都是可变的字符序列。

    🐟	StringBuffer 线程安全,做线程同步检查, 效率较低。
    🐟	StringBuilder 线程不安全,不做线程同步检查,因此效率较高。建议采用该类。
    
    • 1
    • 2

    常用方法列表:

    🐟	重载的 public StringBuilder append(…)方法
    🐟	可以为该 StringBuilder 对象添加字符序列,仍然返回自身对象。
    🐟	方法 public StringBuilder delete(int start,int end)
    🐟	可以删除从 start 开始到 end-1 为止的一段字符序列,仍然返回自身对象。
    🐟	方法 public StringBuilder deleteCharAt(int index)
    🐟	移除此序列指定位置上的 char,仍然返回自身对象。
    🐟	重载的 public StringBuilder insert(…)方法可以为该 StringBuilder 对象在指定位置插入字符序列,仍然返回自身对象。
    🐟	方法 public StringBuilder reverse()
    🐟	用于将字符序列逆序,仍然返回自身对象。
    🐟	方法 public String toString() 返回此序列中数据的字符串表示形式。
    🐟	和 String 类含义类似的方法:
    	⭐	public int indexOf(String str)
    	⭐	public int indexOf(String str,int fromIndex)
    	⭐	public String substring(int start)
    	⭐	public String substring(int start,int end)
    	⭐	public int length() 
    	⭐	char charAt(int index)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    【示例】StringBuffer/StringBuilder 基本用法

    	public class TestStringBufferAndBuilder{
    		public static void main(String[ ] args) {
    			/**StringBuilder*/
    			StringBuilder sb = new StringBuilder();
    			for (int i = 0; i < 7; i++) {
    			sb.append((char) ('a' + i));//追加单个字符
    	}
    			System.out.println(sb.toString());//转换成 String 输出
    			sb.append(", I can sing my abc!");//追加字符串
    			System.out.println(sb.toString());
    			/**StringBuffer,下面的方法同样适用 StringBuilder*/
    			StringBuffer sb2 = new StringBuffer("北京最美丽");
    			sb2.insert(0, "爱").insert(0, "我");//插入字符串
    			System.out.println(sb2);
    			sb2.delete(0, 2);//删除子字符串
    			System.out.println(sb2);
    			sb2.deleteCharAt(0).deleteCharAt(0);//删除某个字符
    			System.out.println(sb2.charAt(0));//获取某个字符
    			System.out.println(sb2.reverse());//字符串逆序
    		}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    下节我们继续带大家了解Java常用类!

  • 相关阅读:
    Day_15 > 内存相关的函数
    Python数学建模—线性规划
    如何使用 LeiaPix 让照片动起来
    【JVM】JVM的垃圾回收机制
    vue数据代理
    leetcode42 接雨水
    rocketmq安装与踩坑
    Java修仙传之神奇的ES2(巧妙的查询及结果处理篇)
    【2022】Nginx使用ngx_http_log_module模块定义日志
    C++/Qt音视频通话开发MetaRTC源码解读,视频推流和拉流
  • 原文地址:https://blog.csdn.net/Wwd631255/article/details/131236226