Java核心类基础知识笔记:字符串与编码、StringBuilder、StringJoiner、包装类型、JavaBean、枚举类、BigInterger、BigDecimal、Math工具类Random工具类SecureRandom工具类

一、字符串与编码

1、在Java中,String是一个引用类型,它本身也是一个class。但是,因为String太常用了,所以Java提供了"..."这种字符串字面量表示方法。

String s1 = "Hello!";
// 实际上字符串在String内部是通过一个char[]数组表示的,因此,按下面的写法也是可以的
String s2 = new String(new char[] {'H', 'e', 'l', 'l', 'o', '!'});

2、Java字符串的一个重要特点就是字符串不可变。这种不可变性是通过内部的private final char[]字段,以及没有任何修改char[]的方法实现的。

3、字符串比较:当我们想要比较两个字符串是否相同时,要特别注意,我们实际上是想比较字符串的内容是否相同。必须使用equals()方法而不能用==

  要忽略大小写比较,使用equalsIgnoreCase()方法

4、从String的不变性设计可以看出,如果传入的对象有可能改变,我们需要复制而不是直接引用。

5、小结:

  Java字符串String是不可变对象;

  字符串操作不改变原字符串内容,而是返回新字符串;

  常用的字符串操作:提取子串、查找、替换、大小写转换等;

  Java使用Unicode编码表示Stringchar

  转换编码就是将Stringbyte[]转换,需要指定编码;转换为byte[]时,始终优先考虑UTF-8编码。

二、StringBuilder 字符串缓冲区

1、Java编译器对String做了特殊处理,使得我们可以直接用+拼接字符串。但是在循环中,每次循环都会创建新的字符串对象,然后扔掉旧的字符串。这样,绝大部分字符串都是临时对象,不但浪费内存,还会影响GC效率。

2、为了能高效拼接字符串,Java标准库提供了StringBuilder,它是一个可变对象,可以预分配缓冲区,这样,往StringBuilder中新增字符时,不会创建新的临时对象。

StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {
    sb.append(',');
    sb.append(i);
}
String s = sb.toString();

  StringBuilder还可以进行链式操作:sb.append("Mr ").append("Bob").append("!").insert(0, "Hello, ");

3、如果我们查看StringBuilder的源码,可以发现,进行链式操作的关键是,定义的append()方法会返回this,这样,就可以不断调用自身的其他方法。

4、注意:对于普通的字符串 + 操作,并不需要我们将其改写为StringBuilder,因为Java编译器在编译时就自动把多个连续的 + 操作编码为StringConcatFactory的操作。

  在运行期,StringConcatFactory会自动把字符串连接操作优化为数组复制或者StringBuilder操作。

  你可能还听说过StringBuffer,这是Java早期的一个StringBuilder的线程安全版本,它通过同步来保证多个线程操作StringBuffer也是安全的,但是同步会带来执行速度的下降。StringBuilderStringBuffer接口完全相同,现在完全没有必要使用StringBuffer

三、StringJoiner

  用指定分隔符拼接字符串数组时,使用StringJoiner或者String.join()更方便;

1、类似用分隔符拼接数组的需求很常见,所以Java标准库还提供了一个StringJoiner来干这个事。同时可以给StringJoiner指定“开头”和“结尾”。

public class Main {
    public static void main(String[] args) {
        String[] names = {"Bob", "Alice", "Grace"};
        var sj = new StringJoiner(", ", "Hello ", "!");
        for (String name : names) {
            sj.add(name);
        }
        System.out.println(sj.toString());
    }
}

2、String还提供了一个静态方法join(),这个方法在内部使用了StringJoiner来拼接字符串,在不需要指定“开头”和“结尾”的时候,用String.join()更方便:

四、包装类型

1、Java的数据类型分两种:

  • 基本类型:byteshortintlongbooleanfloatdoublechar

  • 引用类型:所有classinterface类型

  引用类型可以赋值为null,表示空,但基本类型不能赋值为null

2、如何把一个基本类型视为对象(引用类型)? 比如,想要把int基本类型变成一个引用类型,我们可以定义一个Integer类,它只包含一个实例字段int,这样,Integer类就可以视为int的包装类(Wrapper Class)。

  实际上,因为包装类型非常有用,Java核心库为每种基本类型都提供了对应的包装类型:

3、直接把int变为Integer的赋值写法,称为自动装箱(Auto Boxing),反过来,把Integer变为int的赋值写法,称为自动拆箱(Auto Unboxing)。

  注意:自动装箱和自动拆箱只发生在编译阶段,目的是为了少写代码。

  装箱和拆箱会影响代码的执行效率,因为编译后的class代码是严格区分基本类型和引用类型的。并且,自动拆箱执行时可能会报NullPointerException

4、不变类:所有的包装类型都是不变类。我们查看Integer的源码可知,它的核心代码如下:

public final class Integer {
    private final int value;
}

  因此,一旦创建了Integer对象,该对象就是不变的。

5、对两个Integer实例进行比较要特别注意:绝对不能用==比较,因为Integer是引用类型,必须使用equals()比较。

  代码示例解析:==比较,较小的两个相同的Integer返回true,较大的两个相同的Integer返回false,这是因为Integer是不变类,编译器把Integer x = 127;自动变为Integer x = Integer.valueOf(127);,为了节省内存,Integer.valueOf()对于较小的数,始终返回相同的实例,因此,==比较“恰好”为true,但我们绝不能因为Java标准库的Integer内部有缓存优化就用==比较,必须用equals()方法比较两个Integer

6、小结:

  Java核心库提供的包装类型可以把基本类型包装为class

  自动装箱和自动拆箱都是在编译期完成的(JDK>=1.5);装箱和拆箱会影响执行效率,且拆箱时可能发生NullPointerException

  包装类型的比较必须使用equals()

  整数和浮点数的包装类型都继承自Number

  包装类型提供了大量实用方法。

五、JavaBean

  JavaBean是一种符合命名规范的class,它通过gettersetter来定义属性。

1、在Java中,有很多class的定义都符合这样的规范:

  • 若干private实例字段;
  • 通过public方法来读写实例字段。

  如果读写方法符合以下这种命名规范:

// 读方法:
public Type getXyz()
// 写方法:
public void setXyz(Type value)

  那么这种class被称为JavaBean

  注意:boolean字段比较特殊,它的读方法一般命名为isXyz()

2、JavaBean主要用来传递数据,即把一组数据组合成一个JavaBean便于传输。

3、要枚举一个JavaBean的所有属性,可以直接使用Java核心库提供的Introspector

public class Main {
    public static void main(String[] args) throws Exception {
        BeanInfo info = Introspector.getBeanInfo(Person.class);
        for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
            System.out.println(pd.getName());
        }
    }
}

六、枚举类

1、在Java中,我们可以通过static final来定义常量。但是使用这些常量来表示一组枚举值的时候,有一个严重的问题就是,编译器无法检查每个值的合理性。

2、enum定义枚举类:为了让编译器能自动检查某个值在枚举的集合内,并且,不同用途的枚举需要不同的类型来标记,不能混用,我们可以使用enum来定义枚举类:

enum Weekday {
    SUN, MON, TUE, WED, THU, FRI, SAT;
}

  注意到定义枚举类是通过关键字enum实现的,我们只需依次列出枚举的常量名。

3、使用枚举类的好处:

  enum常量本身带有类型信息,即Weekday.SUN类型是Weekday,编译器会自动检查出类型错误。

  不可能引用到非枚举的值,因为无法通过编译。

  不同类型的枚举不能互相比较或者赋值,因为类型不符。

4、enum类型

  通过enum定义的枚举类,和其他的class有什么区别?答案是没有任何区别。enum定义的类型就是class,只不过它有以下几个特点:

  • 定义的enum类型总是继承自java.lang.Enum,且无法被继承;
  • 只能定义出enum的实例,而无法通过new操作符创建enum的实例;
  • 定义的每个实例都是引用类型的唯一实例;
  • 可以将enum类型用于switch语句。

5、因为enum是一个class,每个枚举的值都是class实例,因此存在一些实例方法。

6、小结:

  Java使用enum定义枚举类型,它被编译器编译为final class Xxx extends Enum { … }

  通过name()获取常量定义的字符串,注意不要使用toString()

  通过ordinal()返回常量定义的顺序(无实质意义);

  可以为enum编写构造方法、字段和方法

  enum的构造方法要声明为private,字段强烈建议声明为final

  enum适合用在switch语句中。

七、BigInterger

1、在Java中,由CPU原生提供的整型最大范围是64位long型整数。使用long型整数可以直接通过CPU指令进行计算,速度非常快。

  如果我们使用的整数范围超过了long型怎么办?这个时候,就只能用软件来模拟一个大整数。java.math.BigInteger就是用来表示任意大小的整数。BigInteger内部用一个int[]数组来模拟一个非常大的整数:

2、对BigInteger做运算的时候,只能使用实例方法,例如,加法运算:add()

3、和long型整数运算比,BigInteger不会有范围限制,但缺点是速度比较慢。

4、小结:

  BigInteger用于表示任意大小的整数;

  BigInteger是不变类,并且继承自Number

  将BigInteger转换成基本类型时可使用longValueExact()等方法保证结果准确。

八、BigDecimal

  和BigInteger类似,BigDecimal可以表示一个任意大小且精度完全准确的浮点数。

1、BigDecimalscale()表示小数位数;通过BigDecimalstripTrailingZeros()方法,可以将一个BigDecimal格式化为一个相等的,但去掉了末尾0的BigDecimal。

2、在比较两个BigDecimal的值是否相等时,要特别注意,使用equals()方法不但要求两个BigDecimal的值相等,还要求它们的scale()相等。

3、必须使用compareTo()方法来比较,它根据两个值的大小分别返回负数、正数和0,分别表示小于、大于和等于。

  注意:总是使用compareTo()比较两个BigDecimal的值,不要使用equals()!

4、如果查看BigDecimal的源码,可以发现,实际上一个BigDecimal是通过一个BigInteger和一个scale来表示的,即BigInteger表示一个完整的整数,而scale表示小数位数。

public class BigDecimal extends Number implements Comparable<BigDecimal> {
    private final BigInteger intVal;
    private final int scale;
}

5、小结:

  BigDecimal用于表示精确的小数,常用于财务计算;

  比较BigDecimal的值是否相等,必须使用compareTo()而不能使用equals()

九、常用工具类

1、Math:Math类就是用来进行数学计算的,它提供了大量的静态方法来便于我们实现数学计算

2、Java标准库还提供了一个StrictMath,它提供了和Math几乎一模一样的方法。

  这两个类的区别在于,由于浮点数计算存在误差,不同的平台(例如x86和ARM)计算的结果可能不一致(指误差不同),因此,StrictMath保证所有平台计算结果都是完全相同的,而Math会尽量针对平台优化计算速度,所以,绝大多数情况下,使用Math就足够了。

3、Random:Random用来创建伪随机数。所谓伪随机数,是指只要给定一个初始的种子,产生的随机数序列是完全一样的。

  有童鞋问,每次运行程序,生成的随机数都是不同的,没看出伪随机数的特性来。

  这是因为我们创建Random实例时,如果不给定种子,就使用系统当前时间戳作为种子,因此每次运行时,种子不同,得到的伪随机数序列就不同。

  如果我们在创建Random实例时指定一个种子,就会得到完全确定的随机数序列

4、SecureRandom:有伪随机数,就有真随机数。实际上真正的真随机数只能通过量子力学原理来获取,而我们想要的是一个不可预测的安全的随机数,SecureRandom就是用来创建安全的随机数的。

  SecureRandom无法指定种子,它使用RNG(random number generator)算法。JDK的SecureRandom实际上有多种不同的底层实现,有的使用安全随机种子加上伪随机数算法来产生安全的随机数,有的使用真正的随机数生成器。实际使用的时候,可以优先获取高强度的安全随机数生成器,如果没有提供,再使用普通等级的安全随机数生成器。

原文地址:https://www.cnblogs.com/goloving/p/14809062.html