本文是Java基础课程的第五课。主要介绍Java中的字符串,包括字符串基本的声明、使用,字符串在JVM中的内存分配,字符串常用的API,字符串与基本数据类型的转换等内容
字符(char
)类型是Java的基本数据类型之一,用来存储单个字符。在开发过程中,往往多个字符一起才能表达一个有意义的数据。Java提供了字符串(String
)类型,用来处理一连串的字符。字符串便是由若干字符组成的序列。
Java中,声明和初始化一个字符串的语法格式如下:
String 变量名 = "初始值";
// 或
String 变量名 = new String("初始值");
下面是一个示例:
public class Test {
public static void main(String[] args) {
String str1 = "Hello world";
String str2 = new String("Hello World");
}
}
说明:
String
对象,那它的值就无法改变了。String
类型用一个char
类型的数组来存储,处理字符串的。多个字符串连接在一起,也可以将字符串与基本类型变量连接。最常用的连接字符串的方法是使用+
操作符。
下面是一个示例:
public class Test {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "World";
String str3 = str1 + str2;
String str4 = "Hello" + "World";
int num1 = 123;
String str5 = num1 + str1;
String str6 = str1 + num1;
System.out.println("str1 = " + str1);
System.out.println("str2 = " + str2);
System.out.println("str3 = " + str3);
System.out.println("str4 = " + str4);
System.out.println("str5 = " + str5);
System.out.println("str6 = " + str6);
}
}
说明:
+
操作符进行连接时,会先将基本类型转换为字符串类型,然后进行连接操作。在程序中,比较两个数据是否相等是一种很常见的操作,基本数据类型的变量之间使用==
操作符便可以判断变量中保存的数据是否相等。而字符串之间的比较有所不同。
下面是一个示例:
public class Test {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");
System.out.println("(str1 == str2) = " + (str1 == str2));
System.out.println("(str1 == str3) = " + (str1 == str3));
System.out.println("str1.equals(str3) = " + str1.equals(str3));
}
}
说明:
==
操作符比较str1
和str2
,结果为true
,事实上,==
操作符比较的是变量中存储的内容(数据或内存地址编号)。字符串是引用类型,str1 == str2
的结果为true
,意味着str1
和str2
两个变量中存储着同样的内存地址编号,即引用了同一块内存(这一点在下面会详细介绍)。==
操作符比较str1
和str2
,结果为false
,意味着str1
和str3
两个变量分别存储了不同的内存地址编号。String
类型提供了equals(Object anObject)
方法,可以比较字符串的字面值是否相同,str1.equals(str3)
的返回结果为true
。因此,如果要比较字符串的字面值是否相同,需要使用equals(Object anObject)
方法。观察下面的示例:
public class Test {
public static void main(String[] args) {
// 基本数据类型
int num1 = 1;
int num2 = 1;
System.out.println("(num1 == num2) = " + (num1 == num2));
// 数组,引用数据类型
char[] char1 = {'H'};
char[] char2 = {'H'};
System.out.println("(char1 == char2) = " + (char1 == char2));
// String,引用数据类型
String str1 = "Hello";
String str2 = "Hello";
System.out.println("(str1 == str2) = " + (str1 == str2));
}
}
num1
和num2
两个基本数据类型的变量进行==
运算,这两个变脸中存储的都是基本数据类型的值,即1
,因此返回结果是true
;char1
和char2
两个数组进行==
运算,因为它们是引用类型,分别在堆内存中申请了内存空间,所以char1
和char2
两个变量中存储的是不同的内存地址编号,故而返回结果为false
。同为引用类型的String
型的变量,str1
和str2
使用==
操作符比较时,返回的结果是true
,即str1
和str2
引用的内存地址相同,这是什么原因呢?
事实上,这是Java有意为之,由于字符串这一类型的数据是程序中出现频率最高的数据类型,并且字符串内容重复的概率也非常的高,如果全部分开申请内存的话,将会非常耗费内存,Java为了节省内存考虑,设计了字符串池这一内存区域(字符串池在JDK 7
以前设计在方法区中,而在JDK 8
以后设计在堆内存中),下面用图示说明上例中两个变量str1
和str2
中存储的内存地址编号为何相同:
说明:
String str1 = "Hello"
时,首先在栈中main
方法对应栈帧的局部变量表中,为变量str1
分配内存,查找字符串池中是否有一块内存中已经存储了Hello
这一字符串字面量,没有的话,字符串池中分配内存并存储字符串字面量"Hello"
,赋值操作会将字符串池中已经分配好的内存空间的首地址存储到变量str1
在局部变量表的对应位置。String str2 = "Hello"
时,栈中main
方法对应栈帧的局部变量表中,为变量str2
分配内存,查找字符串池中是否有一块内存中已经存储了Hello
这一字符串字面量,有的话,引用该内存地址。System.out.println("(str1 == str2) = " + (str1 == str2))
,由于变量str1
和str2
引用的内存地址相同,故返回结果为true
。进一步观察下面的另一个例子:
public class Test {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");
System.out.println("(str1 == str2) = " + (str1 == str2));
System.out.println("(str1 == str3) = " + (str1 == str3));
}
}
前面的例子已经进行了分析,变量str1
和str2
引用了字符串池中相同的内存地址,str1 == str2
的结果为true
。运行str1 == str3
的结果为false
,这又是什么原因呢?
原因就在于使用了new
运算符,下面用图示来说明:
说明:
new String("Hello")
时,会判断字符串池中是否已经存储了Hello
这一字符串字面量,没有的话,字符串池中会分配内存并存储Hello
这一字符串字面量,同时,堆内存中也会分配内存并存储Hello
这一字符串字面量,最终,返回堆内存中分配好的内存空间的首地址。因此,变量str1
和str3
中存储的是不同的内存地址编号,str1 == str3
的结果为false
。new
运算符时,总会在堆内存中分配内存空间。再来观察一个例子:
public class Test {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "HelloWorld";
String str3 = "Hello" + "World";
String str4 = str1 + "World";
System.out.println("(str2 == str3) = " + (str2 == str3));
System.out.println("(str2 == str4) = " + (str2 == str4));
}
}
str2 == str3
的结果为true
,即变量str2
和str3
引用的内存地址相同;而str2 == str4
的结果为false
,即变量str2
和str4
引用的内存地址不同,这又是怎么回事呢?
str2 == str3
的结果为true
,原因在于字符串字面量的连接,在编译阶段就已经完成了,String str3 = "Hello" + "World"
和String str3 = "HelloWorld"
,对于JVM来说并没有什么区别;而str2 == str4
的结果为false
,是由于字符串变量参与了字符串的连接,这一过程发生在程序运行时,会在堆内存中申请内存空间。下面用图示来说明:
说明:
String str2 = "HelloWorld"
时,字符串池中分配内存并存储HelloWorld
这一字符串字面量,变量str2
引用该内存地址。String str3 = "Hello" + "World"
时,会在字符串池中查找HelloWorld
这一字符串字面量,有则直接返回内存地址,故变量str3
和str2
引用相同的内存地址。String str4 = str1 + "World"
时,会将字符串变量str1
中的字符串Hello
和字符串字面量World
进行连接,并在堆内存中分配内存空间进行存储,最终变量str4
引用该堆内存中的内存地址。最后来观察一个例子:
public class Test {
public static void main(String[] args) {
// 引用数据类型的变量相互赋值
int[] nums1, nums2;
nums1 = new int[1];
nums1[0] = 3;
nums2 = nums1;
nums2[0] = 4;
System.out.println("nums1[0] = " + nums1[0]);
// 字符串类型的变量相互赋值
String str1, str2;
str1 = "Hello";
str2 = str1;
str2 = "World";
System.out.println("str1 = " + str1);
}
}
打印nums1[0]
的结果为4
,作为引用类型的变量,将数组nums2
赋值给数组nums1
时,实际拷贝的是内存地址编号,变量nums1
和nums2
引用了同一内存地址,改变数组nums2
中元素的值,其实就是改变数组nums1
中元素的值。打印str1
的结果为Hello
,同样作为引用类型,在执行str2 = str1
后,字符串变量str1
和str2
也引用同一内存地址,可是改变变量str2
的值,变量str1
却没有发生变化,这又是为什么呢?
事实上,在程序运行str2 = "World"
之后,字符串变量str1
和str2
也并不再引用同一内存地址了,仍然用图示来说明:
说明:
str1 = "Hello"
时,首先查找字符串池中是否有一块内存中已经存储了Hello
这一字符串字面量,没有的话,字符串池中分配内存并存储字符串字面量"Hello"
,赋值操作会将字符串池中已经分配好的内存空间的首地址存储到变量str1
在局部变量表的对应位置。str2 = str1
时,变量str2
和str1
引用字符串池中同一内存地址。str2 = "World"
时,首先查找字符串池中是否有一块内存中已经存储了World
这一字符串字面量,没有的话,字符串池中分配内存并存储字符串字面量"World"
,赋值操作会将字符串池中已经分配好的内存空间的首地址存储到变量str2
在局部变量表的对应位置。注意,这一过程中变量str1
并没有受到影响。在开发工程中,经常需要对字符串进行各种操作,熟练掌握字符串的各种操作,对编码过程很有帮助。
API(Application Programming Interface),即应用程序编程接口,对这一概念的定义大多数都晦涩难懂。这里通俗的理解这一概念:
所以,API就是在编程过程中,实体或系统之间进行信息交换的媒介。
更直白的理解,当某一应用需要与其他应用进行信息交换时,需要通过某一媒介,告知对方应用一些信息,同时获取本应用所需要的信息,同时,该媒介应当隐藏这些应用的其他细节,这一媒介便是API,与这一媒介具有同样性质的事物,都可以称之为API。
开发人员在使用API时,无需关心API后面隐藏的实现细节,仅需要关心API的名称、输入什么数据、获得什么数据这三点就足够了。
Java中的字符串提供给开发人员许多非常有用的API,通过它们,可以很轻松的获得字符串的一些常用信息,或者对字符串进行一些加工处理。
Java中字符串常用的API有:
方法 | 返回值类型 | 方法说明 |
---|---|---|
equals(Object anObject) |
boolean |
将此字符串与指定的对象比较 |
equalsIgnoreCase(String anotherString) |
boolean |
忽略大小写比较两个字符串的字面值是否相等 |
length() |
int |
获取字符串的长度 |
charAt(int index) |
char |
获取指定索引处的 char 值 |
indexOf(String str) |
int |
获取指定子字符串在原字符串中第一次出现处的索引 |
lastIndexOf(String str) |
int |
获取指定子字符串在原字符串中最右边出现处的索引 |
startsWith(String prefix) |
boolean |
测试原字符串是否以指定的前缀开始 |
endsWith(String suffix) |
boolean |
测试原字符串是否以指定的后缀结束 |
toLowerCase() |
String |
获取原字符串对应的小写字符串 |
toUpperCase() |
String |
获取原字符串对应的大写字符串 |
substring(int beginIndex) |
String |
截取原字符串,从传入参数beginIndex 为下标的位置开始截取到末尾 |
substring(int beginIndex, int endIndex) |
String |
截取原字符串,从参数beginIndex 为下标的位置开始截取到参数endIndex 为下标的位置 |
trim() |
String |
去掉原字符串首尾的空格 |
split(String regex) |
String[] |
将原字符串按照传入参数regex 分割为字符串数组 |
replace(String regex, String replacement) |
String |
将原字符串中指定的内容替换成另外的内容 |
下面是一个示例:
public class Test {
public static void main(String[] args) {
// 验证用户名长度,长度在6至20之间为合法,否则为不合法
Scanner input = new Scanner(System.in);
System.out.println("请输入用户名");
String name = input.next();
if (name.length() >= 6 && name.length() <= 20) {
System.out.println("用户名长度合法");
} else {
System.out.println("用户名长度不合法");
}
}
}
说明:
length()
方法可以获取字符串的长度。下面是一个示例:
public class Test {
public static void main(String[] args) {
/*
从键盘上输入email,对email进行验证,合法的email的条件是
1. 必须包含“@”和“.”
2. “@”必须在“.”的前面且不能连着
3. “@”不在开头和结尾,并且只能出现一次
*/
Scanner input = new Scanner(System.in);
System.out.println("请输入Email");
String email = input.next();
int atIndex = email.indexOf("@");
int dotIndex = email.indexOf(".");
if (atIndex == -1 || dotIndex == -1) { //必须包含“@”和“.”
System.out.println("Email非法,不存在@或.");
} else if (atIndex >= dotIndex - 1) { //@必须在.的前面且不能连着
System.out.println("Email非法,@必须在.的前面且不能连着");
} else if (email.startsWith("@") || email.endsWith("@")) { //“@”不在开头和结尾
System.out.println("Email非法,“@”不能出现在开头和结尾");
} else if(email.lastIndexOf("@") != atIndex){ // “@”只能出现一次
System.out.println("Email非法,要求@只能出现一次");
}else {
System.out.println("Email合法");
}
}
}
说明:
@
”和“.
”,“@
”必须在“.
”的前面且不能连着,“@
”不在开头和结尾,并且只能出现一次。indexOf(String str)
方法可以获取指定子字符串在原字符串中第一次出现处的索引,没有找到则返回-1
。startsWith(String str)
方法可以判断原字符串是否以指定的前缀开始。endsWith(String str)
方法可以判断原字符串是否以指定的后缀结束。lastIndexOf(String str)
方法可以获取指定子字符串在原字符串中最后一次出现处的索引,没有找到则返回-1
。下面是一个示例:
public class Test {
public static void main(String[] args) {
// 将新闻标题中的“爪洼”换成“Java”
String title = "爪洼技术发展这些年";
System.out.println("替换前的标题:" + title);
title = title.replace("爪洼", "Java");
System.out.println("替换后的标题:" + title);
}
}
说明:
爪洼
”换成“Java
”。replace(String regex, String replacement)
方法可以将原字符串中指定的内容替换成另外的内容。下面是一个示例:
public class Test {
public static void main(String[] args) {
// 截取路径中的文件名称
String path = "C:\\HTML\\front\\assets\\img\\pc\\logo.png";
int startIndex = path.lastIndexOf("\\");
int endIndex = path.lastIndexOf(".");
String fileName = path.substring(startIndex + 1, endIndex);
System.out.println(path + "路径中的文件是:" + fileName);
}
}
说明:
substring(int beginIndex, int endIndex)
方法可以获取指定索引处的 char
值。下面是一个示例:
public class Test {
public static void main(String[] args) {
/*
检测输入的单词是否是回文单词
回文单词即单词倒过来与原单词一样,比如“level”、“pop”、“noon”等
*/
Scanner input = new Scanner(System.in);
System.out.println("请输入需要检测的单词:");
String word = input.next();
// 检测回文单词的逻辑
boolean isPalindromeWord = true;
for (int i = 0; i < word.length() / 2; i++) {
if (word.charAt(i) != word.charAt(word.length() - 1 - i)) {
isPalindromeWord = false;
}
}
System.out.println(word + (isPalindromeWord ? "是" : "不是") + "回文单词");
}
}
说明:
charAt(int index)
方法可以从指定的开始位置和结束位置截取字符串。下面是一个示例:
public class Test {
public static void main(String[] args) {
// 字符串格式化
String str1 = String.format("见过,%s及%s", "晁天王", "众位头领");
System.out.println(str1);
String str2 = String.format("字母a的大写是:%c", 'A');
System.out.println(str2);
String str3 = String.format("3 > 7的结果是:%b", 3 > 7);
System.out.println(str3);
String str4 = String.format("100的一半是:%d", 100 / 2);
System.out.println(str4);
//使用printf代替format方法来格式化字符串
System.out.printf("50元的书打8.5折扣是:%f 元", 50 * 0.85);
}
}
说明:
见过,晁天王及众位头领
字母a的大写是:A
3 > 7的结果是:false
100的一半是:50
50元的书打8.5折扣是:42.500000 元
String
类的format(String format, Object... args)
方法用于创建格式化的字符串。该方法无需String
类型的变量,直接使用类名String
即可调用。该方法第一个参数是被格式化的字符串,第二个参数是替换格式符的字符串,第二个参数中的…
表示方法可变参数,即参数的个数根据格式符个数来确定。字符串格式化就是使用第二个可变参数中的值按照顺序替换第一个参数中的格式符。format(String format, Object... args)
方法的格式符定义如下:
格式符 | 说明 | 示例 |
---|---|---|
%s |
字符串类型 | "李逵" |
%c |
字符类型 | 'm' |
%b |
布尔类型 | true |
%d |
整数类型(十进制) | 100 |
%x |
整数类型(十六进制) | FF |
%o |
整数类型(八进制) | 77 |
%f |
浮点类型 | 99.99 |
+
来连接字符串,使得字符串的构建更方便。System.out.printf(String format, Object ... args)
具有相同的功能,但是只能用于一次输出。在实际开发中,经常会遇到字符串类型与基本类型的转换操作。
基本类型的数据转换成字符串类型有通常有两种方法:
String
类型。String
类型提供的valueOf(基本类型数据 变量名)
方法将基本类型转换成字符串类型。下面是一个示例:
public class Test {
public static void main(String[] args) {
// 通过连接字符串将基本类型转换为String类型
int num1 = 10;
double num2 = 3.14;
boolean boolean1 = true;
String sNum1 = "" + num1;
String sNum2 = "" + num2;
String sBoolean1 = "" + boolean1;
// 通过valueOf(基本类型数据 变量名)方法将基本类型转换为String类型
int num3 = 10;
double num4 = 3.14;
boolean boolean2 = true;
String sNum3 = String.valueOf(num3);
String sNum4 = String.valueOf(num4);
String sBoolean2 = String.valueOf(boolean2);
}
}
说明:
+
运算符将基本类型与字符串类型连接时,Java首先自动将基本类型转换成字符串类型,然后连接在一起.。String
类的valueOf(基本类型数据 变量名)
方法可以将基本类型转换成字符串类型,该方法无需String
类型的变量,直接使用类名String
即可调用。将字符串类型转换为基本类型,需要使用基本类型的包装类。Java为每一种基本类型都提供了对应的包装类,包装类提供了一些常用的操作,其中就包括将字符串类型转换成基本类型。基本类型的包装类及其转换方法如下表:
基本类型 | 包装类 | 方法 | 方法说明 |
---|---|---|---|
byte |
Byte |
parseByte(String s) |
将字符串转换为byte 类型 |
short |
Short |
parseShort(String s) |
将字符串转换为short 类型 |
int |
Integer |
parseInt(String s) |
将字符串转换为int 类型 |
long |
Long |
parseLong(String s) |
将字符串转换为long 类型 |
float |
Float |
parseFloat(String s) |
将字符串转换为float 类型 |
double |
Double |
parseDouble(String s) |
将字符串转换为double 类型 |
boolean |
Boolean |
parseBoolean(String s) |
将字符串转换为boolean 类型 |
下面是一个示例:
public class Test {
public static void main(String[] args) {
String str1 = "1", str2 = "true", str3 = "3.14";
byte num1 = Byte.parseByte(str1);
short num2 = Short.parseShort(str1);
int num3 = Integer.parseInt(str1);
long num4 = Long.parseLong(str1);
float num5 = Float.parseFloat(str3);
double num6 = Double.parseDouble(str3);
boolean boolean1 = Boolean.parseBoolean(str2);
}
}
说明:
char
类型的转换可以通过字符串的charAt(int index)
方法完成。如下例:public class Test {
public static void main(String[] args) {
String str = "Hello World";
char char1 = str.charAt(6);
System.out.println("Hello World中第6位下标是字符" + char1);
// “Hello World中第6位下标是字符W”
}
}