Java基础复习(一):基础语法&数组&字符串

Java关键字和保留字(只写了一部分不好记的):

catch:

用于捕获 try 语句中的异常。在 try 块中可能会抛出异常,而在 catch 块中可以捕获这些异常并进行处理。catch 块可以有多个,每个 catch 块可以捕获特定类型的异常。在 catch 块中,可以根据需要进行异常处理,例如输出错误信息、进行日志记录、恢复程序状态等。

try {
    int num = Integer.parseInt("abc");
} catch (NumberFormatException e) {
    System.out.println("Invalid number format");
}

enum:

用于定义一组固定的常量(枚举)。

public enum PlayerType {
    TENNIS,
    FOOTBALL,
    BASKETBALL
}

abstract:

用于声明抽象类,以及抽象方法。

abstract class Animal {
    abstract void makeSound();

    public void sleep() {
        System.out.println("The animal is sleeping.");
    }
}

class Dog extends Animal {
    void makeSound() {
        System.out.println("The dog barks.");
    }
}

extends:

用于指示一个类是从另一个类或接口继承的。

class Animal {
    public void eat() {
        System.out.println("动物正在吃东西");
    }
}

class Dog extends Animal {
    public void bark() {
        System.out.println("狗在汪汪叫");
    }
}

public class ExtendsDemo {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat();
        dog.bark();
    }
}

Animal 类中有一个 eat() 方法,输出字符串 "动物正在吃东西"。Dog 类继承自 Animal 类,并定义了一个 bark() 方法,输出字符串 "狗在汪汪叫"。

 final:

用于表示某个变量、方法或类是最终的,不能被修改或继承

 

①、final 变量:表示一个常量,一旦被赋值,其值就不能再被修改。这在声明不可变的值时非常有用。

final double PI = 3.14159265359;

②、final 方法表示一个不能被子类重写的方法。这在设计类时,确保某个方法的实现不会被子类修改时非常有用。

class Animal {
    final void makeSound() {
        System.out.println("动物发出声音.");
    }
}

class Dog extends Animal {
    // 错误: 无法覆盖来自 Animal 的 final 方法
    // void makeSound() {
    //     System.out.println("狗吠叫.");
    // }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.makeSound();
    }
}

③、final 类表示一个不能被继承的类。这在设计类时,确保其不会被其他类继承时非常有用。String 类就是 final 的

final class Animal {
    void makeSound() {
        System.out.println("动物发出声音.");
    }
}

// 错误: 类型 Dog 无法继承 final 类 Animal
// class Dog extends Animal {
//     void makeSound() {
//         System.out.println("狗吠叫.");
//     }
// }

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.makeSound();
    }
}

finally:

和 try-catch 配合使用,表示无论是否处理异常,总是执行 finally 块中的代码。

try {
    int x = 10 / 0;  // 抛出异常
} catch (Exception e) {
    System.out.println("发生了异常:" + e.getMessage());
} finally {
    System.out.println("finally 块被执行");
}

  instanceof:

用于判断对象是否属于某个类型(class)

例如,假设有一个 Person 类和一个 Student 类,Student 类继承自 Person 类,可以使用 instanceof 运算符来判断一个对象是否为 Person 类或其子类的实例:
Person p = new Student();
if (p instanceof Person) {
    System.out.println("p is an instance of Person");
}
if (p instanceof Student) {
    System.out.println("p is an instance of Student");
}

interface:

用于声明接口。会定义一组方法的签名(即方法名、参数列表和返回值类型),但没有方法体。其他类可以实现接口,并提供方法的具体实现。

public interface MyInterface {
    void method1();
    int method2(String param);
}

  private:

一个访问权限修饰符,表示方法或变量只对当前类可见。

public class MyClass {
    private int x; // 私有属性 x,只能在当前类的内部访问

    private void foo() {
        // 私有方法 foo,只能在当前类的内部调用
    }
}

在这个示例中,MyClass 类有一个私有属性 x 和一个私有方法 foo()。这些成员只能在 MyClass 类的内部访问和调用,对其他类不可见。

protected:

一个访问权限修饰符,表示方法或变量对同一包内的类和所有子类可见。

package com.example.mypackage;

public class MyBaseClass {
    protected int x; // 受保护的属性 x,可以被子类和同一包中的其他类访问

    protected void foo() {
        // 受保护的方法 foo,可以被子类和同一包中的其他类调用
    }
}

package com.example.mypackage;

public class MySubClass extends MyBaseClass {
    public void bar() {
        x = 10; // 可以访问 MyBaseClass 中的受保护属性 x
        foo(); // 可以调用 MyBaseClass 中的受保护方法 foo
    }
}

在这个示例中,MyBaseClass 类有一个受保护的属性 x 和一个受保护的方法 foo()。这些成员可以被子类和同一包中的其他类访问和调用。MySubClass 类继承自 MyBaseClass 类,并可以访问和修改 MyBaseClass 中的受保护成员。

public:

一个访问权限修饰符,除了可以声明方法和变量(所有类可见),还可以声明类。main() 方法必须声明为 public。

public class MyClass {
    public int x; // 公有属性 x,可以被任何类访问

    public void foo() {
        // 公有方法 foo,可以被任何类调用
    }
}

在这个示例中,MyClass 类有一个公有属性 x 和一个公有方法 foo()。这些成员可以被任何类访问和调用,无论这些类是否在同一个包中。

static:

表示该变量或方法是静态变量或静态方法

public class MyClass {
    public static int x; // 静态变量 x,属于类的成员

    public static void foo() {
        // 静态方法 foo,属于类的成员
    }
}

在这个示例中,MyClass 类有一个静态变量 x 和一个静态方法 foo()。这些成员属于类的成员,可以通过类名直接访问,不需要创建对象。

strictfp:

strict floating-point

并不常见,通常用于修饰一个方法,用于限制浮点数计算的精度和舍入行为。当你在类、接口或方法上使用 strictfp 时,该范围内的所有浮点数计算将遵循 IEEE 754 标准的规定,以确保跨平台的浮点数计算的一致性。

不同的硬件平台和 JVM 实现可能对浮点数计算的精度和舍入行为有差异,这可能导致在不同环境中运行相同的浮点数计算代码产生不同的结果。使用 strictfp 关键字可以确保在所有平台上获得相同的浮点数计算结果,避免计算结果的不一致问题。

但请注意,使用 strictfp 可能会对性能产生影响,因为可能需要更多的计算和转换来确保遵循 IEEE 754 标准。因此,在使用 strictfp 时,需要权衡精度和一致性与性能之间的关系。

public strictfp class MyClass {
    public static void main(String[] args) {
        double a = 0.1;
        double b = 0.2;
        double result = a + b;
        System.out.println("Result: " + result);
    }
}

输出:

Result: 0.30000000000000004

super:

可用于调用父类的方法或者字段

class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }

    public void eat() {
        System.out.println(name + " is eating.");
    }
}

public class Dog extends Animal {
    public Dog(String name) {
        super(name); // 调用父类的构造方法
    }

    public void bark() {
        System.out.println(name + " is barking.");
    }

    public void eat() {
        super.eat(); // 调用父类的方法
        System.out.println(name + " is eating bones.");
    }
}

synchronized:

用于指定多线程代码中的同步方法、变量或者代码块

public class MyClass {
    private int count;

    public synchronized void increment() {
        count++; // 同步方法
    }

    public void doSomething() {
        synchronized(this) { // 同步代码块
            // 执行一些需要同步的操作
        }
    }
}

this:

可用于在方法或构造方法中引用当前对象

public class MyClass {
    private int num;

    public MyClass(int num) {
        this.num = num; // 使用 this 关键字引用当前对象的成员变量
    }

    public void doSomething() {
        System.out.println("Doing something with " + this.num); // 使用 this 关键字引用当前对象的成员变量
    }

    public MyClass getThis() {
        return this; // 返回当前对象本身
    }
}

throw:

主动抛出异常

throws:

用于声明异常。

transient:

修饰的字段不会被序列化

public class MyClass implements Serializable {
    private int id;
    private String name;
    private transient String password;

    public MyClass(int id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }

    // 省略 getter 和 setter 方法

    @Override
    public String toString() {
        return "MyClass{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

在这个示例中,MyClass 类实现了 Serializable 接口,表示该类的对象可以被序列化。该类有三个成员变量,分别是 id、name 和 password。其中,password 成员变量被标记为 transient,表示在序列化过程中忽略这个成员变量。

volatile:

保证不同线程对它修饰的变量进行操作时的可见性,即一个线程修改了某个变量的值,新值对其他线程来说是立即可见的。

public class MyThread extends Thread {
    private volatile boolean running = true;

    @Override
    public void run() {
        while (running) {
            // 线程执行的代码
        }
    }

    public void stopThread() {
        running = false;
    }
}

在这个示例中,MyThread 类继承了 Thread 类,重写了 run() 方法。MyThread 类有一个成员变量 running,被标记为 volatile,表示这个变量是共享的,可能会被多个线程同时访问。在 run() 方法中,使用 while 循环检查 running 变量的值,如果 running 为 true,就继续执行循环体中的代码。在另一个方法 stopThread() 中,将 running 变量的值设置为 false,表示需要停止线程。

Java注释:

01、单行注释

单行注释通常用于解释方法内某单行代码的作用。

public void method() {
    int age = 18; // age 用于表示年龄
}

但如果写在行尾的话,其实是不符合阿里巴巴的开发规约的

 

正确的单行注释如上图中所说,在被注释语句上方另起一行,使用 // 注释。

public void method() {
    // age 用于表示年龄
    int age = 18;
}

02、多行注释

多行注释使用的频率其实并不高,通常用于解释一段代码的作用。

/*
age 用于表示年纪
name 用于表示姓名
*/
int age = 18;
String name = "沉默王二";

以 /* 开始,以 */ 结束,但不如用多个 // 来得痛快,因为 * 和 / 不在一起,敲起来麻烦。

// age 用于表示年纪
// name 用于表示姓名
int age = 18;
String name = "沉默王二";

03、文档注释

文档注释可用在三个地方,类、字段和方法,用来解释它们是干嘛的。

/**
 * 微信搜索「沉默王二」,回复 Java
 */
public class Demo {
    /**
     * 姓名
     */
    private int age;

    /**
     * main 方法作为程序的入口
     *
     * @param args 参数
     */
    public static void main(String[] args) {

    }
}

Java数据类型:

数据类型默认值大小
booleanfalse不确定
char'\u0000'2 字节
byte01 字节
short02 字节
int04 字节
long0L8 字节
float0.0f4 字节
double0.08 字节

Java数据类型转换:

数据类型转换的目的是确保在表达式求值时,不同类型的数据能够相互兼容。

01、自动类型转换

自动类型转换(自动类型提升)是 Java 编译器在不需要显式转换的情况下,将一种基本数据类型自动转换为另一种基本数据类型的过程。这种转换通常发生在表达式求值期间,当不同类型的数据需要相互兼容时。自动类型转换遵循以下规则:

  • 如果任一操作数是 double 类型,其他操作数将被转换为 double 类型。
  • 否则,如果任一操作数是 float 类型,其他操作数将被转换为 float 类型。
  • 否则,如果任一操作数是 long 类型,其他操作数将被转换为 long 类型。
  • 否则,所有操作数将被转换为 int 类型。

需要注意的是,自动类型转换只发生在兼容类型之间。例如,从较小的数据类型(如 int)到较大的数据类型(如 long 或 double)的转换是安全的,因为较大的数据类型可以容纳较小数据类型的所有可能值。

byte -> short -> int -> long -> float -> double
char -> int -> long -> float -> double

02、强制类型转换

强制类型转换是 Java 中将一种数据类型显式转换为另一种数据类型的过程。与自动类型转换不同,强制类型转换需要程序员显式地指定要执行的转换。强制类型转换在以下情况中可能需要:

  • 将较大的数据类型转换为较小的数据类型。
  • 将浮点数转换为整数。
  • 将字符类型转换为数值类型。

需要注意的是,强制类型转换可能会导致数据丢失或精度降低,因为目标类型可能无法容纳原始类型的所有可能值。因此,在进行强制类型转换时,需要确保转换后的值仍然在目标类型的范围内。

double -> float -> long -> int -> char -> short -> byte

Java基本数据类型缓存池:

基本数据类型的包装类除了 Float 和 Double 之外,其他六个包装器类(Byte、Short、Integer、Long、Character、Boolean)都有常量缓存池。

  • Byte:-128~127,也就是所有的 byte 值
  • Short:-128~127
  • Long:-128~127
  • Character:\u0000 - \u007F
  • Boolean:true 和 false

Java运算符:

Java流程控制语句:

if-else      switch    fro    while    do-while     break      continue
 

数组:

数组的声明方式分两种。

先来看第一种:

int[] anArray;

再来看第二种:

int anOtherArray[];

数组与 List

在 Java 中,数组与 List 关系非常密切。List 封装了很多常用的方法,方便我们对集合进行一些操作,而如果直接操作数组的话,有很多不便,因为数组本身没有提供这些封装好的操作,所以有时候我们需要把数组转成 List。

List 会在集合框架一节详细介绍,这里先来个开胃菜,方便大家回头过来复盘。

“怎么转呢?”三妹问到。

最原始的方式,就是通过遍历数组的方式,一个个将数组添加到 List 中。

int[] anArray = new int[] {1, 2, 3, 4, 5};

List<Integer> aList = new ArrayList<>();
for (int element : anArray) {
    aList.add(element);
}

更优雅的方式是通过 Arrays 类(戳链接了解详情)的 asList() 方法:

List<Integer> aList = Arrays.asList(anArray);

不过需要注意的是,Arrays.asList 的参数需要是 Integer 数组,而 anArray 目前是 int 类型。

可以这样写:

List<Integer> aList1 = Arrays.asList(1, 2, 3, 4, 5);

或者换另外一种方式。

List<Integer> aList = Arrays.stream(anArray).boxed().collect(Collectors.toList());

还有一个需要注意的是,Arrays.asList 方法返回的 ArrayList 并不是 java.util.ArrayList,它其实是 Arrays 类的一个内部类:

private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable{}

如果需要添加元素或者删除元素的话,需要把它转成 java.util.ArrayList

new ArrayList<>(Arrays.asList(anArray));

字符串为什么不可变:

  • String 类被 final 关键字修饰,所以它不会有子类,这就意味着没有子类可以重写它的方法,改变它的行为。
  • String 类的数据存储在 char[] 数组中,而这个数组也被 final 关键字修饰了,这就表示 String 对象是没法被修改的,只要初始化一次,值就确定了。

为什么要这样设计?

第一,可以保证 String 对象的安全性,避免被篡改,毕竟像密码这种隐私信息一般就是用字符串存储的。

第二,保证哈希值不会频繁变更。毕竟要经常作为哈希表的键值,经常变更的话,哈希表的性能就会很差劲。

字符串常量池:

String s = new String("二哥");

这段代码创建了两个对象,

使用 new 关键字创建一个字符串对象时,Java 虚拟机会先在字符串常量池中查找有没有‘二哥’这个字符串对象,如果有,就不会在字符串常量池中创建‘二哥’这个对象了,直接在堆中创建一个‘二哥’的字符串对象,然后将堆中这个‘二哥’的对象地址返回赋值给变量 s。”

“如果没有,先在字符串常量池中创建一个‘二哥’的字符串对象,然后再在堆中创建一个‘二哥’的字符串对象,然后将堆中这个‘二哥’的字符串对象地址返回赋值给变量 s。”

在 Java 中,栈上存储的是基本数据类型的变量和对象的引用,而对象本身则存储在堆上。

字符串常量池的作用

通常情况下,我们会采用双引号的方式来创建字符串对象,而不是通过 new 关键字的方式,就像下面 👇🏻 这样,这样就不会多此一举:

String s = "三妹";

当执行 String s = "三妹" 时,Java 虚拟机会先在字符串常量池中查找有没有“三妹”这个字符串对象,如果有,则不创建任何对象,直接将字符串常量池中这个“三妹”的对象地址返回,赋给变量 s;如果没有,在字符串常量池中创建“三妹”这个对象,然后将其地址返回,赋给变量 s。

字符串常量池在内存中的什么位置呢?

分为三个阶段。

Java 7 之前

在 Java 7 之前,字符串常量池位于永久代(Permanent Generation)的内存区域中,主要用来存储一些字符串常量(静态数据的一种)。永久代是 Java 堆(Java Heap)的一部分,用于存储类信息、方法信息、常量池信息等静态数据。

而 Java 堆是 JVM 中存储对象实例和数组的内存区域,也就是说,永久代是 Java 堆的一个子区域。

换句话说,永久代中存储的静态数据与堆中存储的对象实例和数组是分开的,它们有不同的生命周期和分配方式。

但是,永久代和堆的大小是相互影响的,因为它们都使用了 JVM 堆内存,因此它们的大小都受到 JVM 堆大小的限制。

于是,当我们创建一个字符串常量时,它会被储存在永久代的字符串常量池中。如果我们创建一个普通字符串对象,则它将被储存在堆中。如果字符串对象的内容是一个已经存在于字符串常量池中的字符串常量,那么这个对象会指向已经存在的字符串常量,而不是重新创建一个新的字符串对象。

Java 7

需要注意的是,永久代的大小是有限的,并且很难准确地确定一个应用程序需要多少永久代空间。如果我们在应用程序中使用了大量的类、方法、常量等静态数据,就有可能导致永久代空间不足。这种情况下,JVM 就会抛出 OutOfMemoryError 错误。

因此,从 Java 7 开始,为了解决永久代空间不足的问题,将字符串常量池从永久代中移动到堆中。这个改变也是为了更好地支持动态语言的运行时特性。

Java 8

到了 Java 8,永久代(PermGen)被取消,并由元空间(Metaspace)取代。元空间是一块本机内存区域,和 JVM 内存区域是分开的。不过,元空间的作用依然和之前的永久代一样,用于存储类信息、方法信息、常量池信息等静态数据。

与永久代不同,元空间具有一些优点,例如:

  • 它不会导致 OutOfMemoryError 错误,因为元空间的大小可以动态调整。
  • 元空间使用本机内存,而不是 JVM 堆内存,这可以避免堆内存的碎片化问题。
  • 元空间中的垃圾收集与堆中的垃圾收集是分离的,这可以避免应用程序在运行过程中因为进行类加载和卸载而频繁地触发 Full GC。

永久代、方法区、元空间

  • 方法区是 Java 虚拟机规范中的一个概念,就像是一个接口吧;
  • 永久代是 HotSpot 虚拟机中对方法区的一个实现,就像是接口的实现类;
  • Java 8 的时候,移除了永久代,取而代之的是元空间,是方法区的另外一种实现,更灵活了。

永久代是放在运行时数据区中的,所以它的大小受到 Java 虚拟机本身大小的限制,所以 Java 8 之前,会经常遇到 java.lang.OutOfMemoryError: PremGen Space 的异常,PremGen Space 就是方法区的意思;而元空间是直接放在内存中的,所以只受本机可用内存的限制。

详解 String.intern() 方法:

美团技术团队深入解析 String.intern()

StringBuilder和StringBuffer:

StringBuffer和StringBuilder的区别

由于字符串是不可变的,所以当遇到字符串拼接(尤其是使用+号操作符)的时候,就需要考量性能的问题,你不能毫无顾虑地生产太多 String 对象,对珍贵的内存造成不必要的压力。

于是 Java 就设计了一个专门用来解决此问题的 StringBuffer 类。

public final class StringBuffer extends AbstractStringBuilder implements Serializable, CharSequence {

    public StringBuffer() {
        super(16);
    }
    
    public synchronized StringBuffer append(String str) {
        super.append(str);
        return this;
    }

    public synchronized String toString() {
        return new String(value, 0, count);
    }

    // 其他方法
}

不过,由于 StringBuffer 操作字符串的方法加了 synchronized 关键字进行了同步,主要是考虑到多线程环境下的安全问题,所以如果在非多线程环境下,执行效率就会比较低,因为加了没必要的锁。

于是 Java 就给 StringBuffer “生了个兄弟”,名叫 StringBuilder,说,“孩子,你别管线程安全了,你就在单线程环境下使用,这样效率会高得多,如果要在多线程环境下修改字符串,你到时候可以使用 ThreadLocal 来避免多线程冲突。”

public final class StringBuilder extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
    // ...

    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }

    // ...
}

除了类名不同,方法没有加 synchronized,基本上完全一样。

实际开发中,StringBuilder 的使用频率也是远高于 StringBuffer,甚至可以这么说,StringBuilder 完全取代了 StringBuffer。

字符串相等判断:Java中的equals()与==的区别与用法:

  • “==”操作符用于比较两个对象的地址是否相等。
  • .equals() 方法用于比较两个对象的内容是否相等。
String alita = new String("小萝莉");
String luolita = new String("小萝莉");

System.out.println(alita.equals(luolita)); // true
System.out.println(alita == luolita); // false

字符串拼接:

append方法源码解析

“好了,三妹,来看一下 StringBuilder 类的 append() 方法的源码吧!”

public StringBuilder append(String str) {
    super.append(str);
    return this;
}

这 3 行代码其实没啥看的。我们来看父类 AbstractStringBuilder 的 append() 方法:

public AbstractStringBuilder append(String str) {
    if (str == null)
        return appendNull();
    int len = str.length();
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
}

1)判断拼接的字符串是不是 null,如果是,当做字符串“null”来处理。appendNull() 方法的源码如下:

private AbstractStringBuilder appendNull() {
    int c = count;
    ensureCapacityInternal(c + 4);
    final char[] value = this.value;
    value[c++] = 'n';
    value[c++] = 'u';
    value[c++] = 'l';
    value[c++] = 'l';
    count = c;
    return this;
}

2)获取字符串的长度。

3)ensureCapacityInternal() 方法的源码如下:

private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity));
    }
}

由于字符串内部是用数组实现的,所以需要先判断拼接后的字符数组长度是否超过当前数组的长度,如果超过,先对数组进行扩容,然后把原有的值复制到新的数组中。

4)将拼接的字符串 str 复制到目标数组 value 中。

str.getChars(0, len, value, count)

5)更新数组的长度 count。

String.concat 拼接字符串

“除了可以使用 + 号操作符,StringBuilder 的 append() 方法,还有其他的字符串拼接方法吗?”三妹问。

“有啊,比如说 String 类的 concat() 方法,有点像 StringBuilder 类的 append() 方法。”

String chenmo = "沉默";
String wanger = "王二";
System.out.println(chenmo.concat(wanger));

可以来看一下 concat() 方法的源码。

public String concat(String str) {
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    int len = value.length;
    char buf[] = Arrays.copyOf(value, len + otherLen);
    str.getChars(buf, len);
    return new String(buf, true);
}

1)如果拼接的字符串的长度为 0,那么返回拼接前的字符串。

2)将原字符串的字符数组 value 复制到变量 buf 数组中。

3)把拼接的字符串 str 复制到字符数组 buf 中,并返回新的字符串对象。

我一行一行地给三妹解释着。

“和 + 号操作符相比,concat() 方法在遇到字符串为 null 的时候,会抛出 NullPointerException,而“+”号操作符会把 null 当做是“null”字符串来处理。”

如果拼接的字符串是一个空字符串(""),那么 concat 的效率要更高一点,毕竟不需要 new StringBuilder 对象。

如果拼接的字符串非常多,concat() 的效率就会下降,因为创建的字符串对象越来越多。

“还有吗?”三妹似乎对字符串拼接很感兴趣。

“有,当然有。”

String.join 拼接字符串

String 类有一个静态方法 join(),可以这样来使用。

String chenmo = "沉默";
String wanger = "王二";
String cmower = String.join("", chenmo, wanger);
System.out.println(cmower);

第一个参数为字符串连接符,比如说:

String message = String.join("-", "王二", "太特么", "有趣了");

输出结果为:王二-太特么-有趣了

来看一下 join 方法的源码:

public static String join(CharSequence delimiter, CharSequence... elements) {
    Objects.requireNonNull(delimiter);
    Objects.requireNonNull(elements);
    // Number of elements not likely worth Arrays.stream overhead.
    StringJoiner joiner = new StringJoiner(delimiter);
    for (CharSequence cs: elements) {
        joiner.add(cs);
    }
    return joiner.toString();
}

里面新建了一个叫 StringJoiner 的对象,然后通过 for-each 循环把可变参数添加了进来,最后调用 toString() 方法返回 String。

StringUtils.join 拼接字符串

“实际的工作中,org.apache.commons.lang3.StringUtils 的 join() 方法也经常用来进行字符串拼接。”

String chenmo = "沉默";
String wanger = "王二";
StringUtils.join(chenmo, wanger);

该方法不用担心 NullPointerException。

StringUtils.join(null)            = null
StringUtils.join([])              = ""
StringUtils.join([null])          = ""
StringUtils.join(["a", "b", "c"]) = "abc"
StringUtils.join([null, "", "a"]) = "a"

来看一下源码:

public static String join(final Object[] array, String separator, final int startIndex, final int endIndex) {
    if (array == null) {
        return null;
    }
    if (separator == null) {
        separator = EMPTY;
    }

    final StringBuilder buf = new StringBuilder(noOfItems * 16);

    for (int i = startIndex; i < endIndex; i++) {
        if (i > startIndex) {
            buf.append(separator);
        }
        if (array[i] != null) {
            buf.append(array[i]);
        }
    }
    return buf.toString();
}

内部使用的仍然是 StringBuilder。

拆分字符串:

public class Test {
    public static void main(String[] args) {
        String cmower = "沉默王二,一枚有趣的程序员";
        if (cmower.contains(",")) {
            String [] parts = cmower.split(",");
            System.out.println("第一部分:" + parts[0] +" 第二部分:" + parts[1]);
        } else {
            throw new IllegalArgumentException("当前字符串没有包含逗号");
        }
    }
}
第一部分:沉默王二 第二部分:一枚有趣的程序员
String cmower = "沉默王二.一枚有趣的程序员";
if (cmower.contains(".")) {
    String [] parts = cmower.split("\\.");
    System.out.println("第一部分:" + parts[0] +" 第二部分:" + parts[1]);
}

由于英文逗点属于特殊符号,所以在使用 split() 方法的时候,就需要使用正则表达式 \\. 而不能直接使用 .

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值