String字符串
String是一个类,属于引用类型。
Java程序中一切使用""引起来的内容,都是这个类的实例,称为字符串对象。
字符串定义后是一个常量,值不可更改。字符串实际是一个字符数组。
String str="hello";//这句话在执行时,创建一个hello字符串对象,将其地址保存在str变量中
str="abc";//这里看似是在改变字符串的值,实际是又创建了一个字符串对象abc,将其地址保存在str变 量中
//以上两句话,在内存中会有两个字符串对象hello和abc,str只引用最后赋值的字符串地址abc
String str2="abc";
//字符串实际是一个字符数组
char[] list = {
'a','b','c'};
String str3= new String(list);
//这里str2和str3中保存的内容是一样的
String类使用时注意
由此可见,如果要频繁更改String类型变量的值,不要使用String对象操作字符串,效率很低又浪费内存空间。
System.out.println("程序执行开始");
String str="";
//10万次的循环,会创建10万个字符对象,但最终只有最后的字符串对象被str引用,其余字符串均为“垃圾”
for(int i=0;i<10000;i++){
str+=i;
}
System.out.println("程序执行结束");
如果要频繁更改字符串中的值,建议使用StringBuilder类或StringBuffer类
如何创建一个字符串对象
1.使用""赋值创建
String str="hello你好";
2.通过构造方法创建
常用构造方法 | 说明 |
---|---|
String() | 创建一个空白字符串对象,实际创建一个空字符数组 |
String(String str) | 创建一个指定的字符串对象,实际是创建一个形参字符串的副本 |
String(char[] list) | 创建一个指定字符数组的字符串对象 |
String(byte[] list) | 按默认编码格式创建一个指定字节数组的字符串对象 |
String(byte[] list,String charsetName) | 按指定的编码格式创建一个指定字节数组的字符串对象 |
不同方式创建字符串的过程
使用""赋值的形式创建
public class Test2 {
public static void main(String[] args) {
//这句话执行时,先判断字符串常量池中是否存在"ab",不存在则创建,将其地址保存到str1变量中
String str1 = "ab";
//这句话执行时,先判断字符串常量池中是否存在"ab",已存在不创建,直接使用已有的"ab"保存到str2中
String str2 = "ab";
//这句话执行时,+两端的内容如果都是通过""定义的字符串,拼接过程在编译时已经完成,所以判断字符串常量池中是否存在"ab"
//依然存在,将其保存到str3中
String str3 = "a" + "b";
//以上三句话,只会在字符串常量池创建一个字符串对象"ab",分别引用给三个变量
System.out.println(str1);
System.out.println(str2);
System.out.println(str3);
System.out.println(str1 == str2);//true
System.out.println(str1 == str3);//true
System.out.println(str2 == str3);//true
}
}
可以使用java自带的反编译工具javap对class文件进行反编译。
在class文件所在的目录下,进入控制台,输入**javap -c class文件名.class
使用构造方法String(String str)创建
public class Test3 {
public static void main(String[] args) {
//1.在字符串常量池中寻找"ab",不存在则创建
//2.在堆中new String(),将字符串常量池中的"ab"保存到该空间中
//3.将堆中new String()的地址保存到变量str1中
String str1=new String("ab");
//1.在字符串常量池中寻找"ab",存在直接使用
//2.在堆中new String(),将字符串常量池中的"ab"保存到该空间中
//3.将堆中new String()的地址保存到变量str2中
String str2=new String("ab");
System.out.println(str1);
System.out.println(str2);
//由于str1和str2保存的是堆中的两个区域,所以这里是false
System.out.println(str1==str2);
}
}
使用+拼接""和new出来的字符串对象
public class Test4 {
public static void main(String[] args) {
//在字符串常量池中定义"ab",保存到str1中
String str1 = "ab";
//1.在字符串常量池中定义"a"
//2.在堆中创建StringBuilder对象
//3.在字符串常量池中定义"b"
//4.在堆中创建String对象,将“b”保存其中
//5.将"a"和String对象添加到StringBuilder对象中
//6.将StringBuilder对象保存到变量str2中
String str2 = "a" + new String("b");//一共创建了4个对象"a","b",new String()、new StringBuilder()
System.out.println(str1);
System.out.println(str2);
//str1和str2是两个不同地址
System.out.println(str1 == str2);//false
}
}
总结
在使用字符串时,如果要比较其值是否相同,不要使用判断,因为判断的是内存地址。
所以在比较字符串是否相同时,要使用String重写的equals方法进行判断。
String中equals方法判断的原理,大致为:将两个字符串保存到字符数组中,再对每个字符逐一比较,
如果全部一致则返回。
在使用equals方法时,通常要将已知的非空字符串作为调用者。
password.equals("123123");//如果这样写,在password变量为null的情况下,会有空指针异常
"123123".equals(password);//这样将已知非空的字符串作为调用者,就能避免空指针异常
字符串常用方法
方法名 | 返回值 | 作用 |
---|---|---|
length() | int | 获取字符串的长度 |
trim() | String | 去除字符串首尾的所有空格 |
toLowerCase() | String | 转换字符串为小写 |
toUpperCase() | String | 转换字符串为大写 |
isEmpty() | boolean | 判断是否为一个空字符串 |
getBytes() | byte[] | 按默认编码格式将字符串转换为字节数组 |
toCharArray() | char[] | 将字符串转换为字符数组 |
equalsIgnoreCase(String str) | boolean | 忽略大小写比较指定字符串是否相同 |
equals(String str) | boolean | 判断字符串是否相同 |
charAt(int index) | char | 获取index位置上的字符串 |
indexOf(String str) | int | 获取str第一次出现的位置,如果没有返回-1 |
concatains(字符序列) | boolean | 判断指定的字符序列(字符串)是否存在于原字符串中 |
concat(String str) | String | 将str拼接到原字符串末尾 |
startsWith(String str) | boolean | 判断是否以指定字符串开头 |
endsWith(String str) | boolean | 判断是否以指定字符串结尾 |
substring(int index) | String | 截取原字符串在[index,数组长度)区间内的字符。(从指定位置截取至末尾,包含指定位置) |
substring(int from,int to) | String | 截取原字符串在[from,to)区间内的字符,(从from截取至to,包含from不包含to) |
split(String reg) | String[] | 按指定字符串或正则表达式切分原字符串。如果指定内容不在末尾,n个指定字符串能得到n+1个子串;如果指定内容在末尾,n个指定字符能得到n个字串(不包含末尾的无效字符) |
String.valueOf(参数) | String | 将一个参数转换为字符串,参数可以是原始类型,也可以是任意对象。 |
replace(char oldChar,char new Char) | String | 使用newChar替换oldChar |
lastIndexOf(String str) | int | 获取str最后一次出现的位置,如果没有返回-1 |
字符串与原始类型之间的转换
原始类型转换为字符串
String.valueOf(原始类型参数);
int num=123;
String str=String.valueOf(num); System.out.println(str.length());
字符串转换为原始类型
使用原始类型对应的包装类,调用其pareseXXX(字符串)方法
String num="123";
int i=Integer.parseInt(num);
可变字符串
String字符串对象是一个常量,在定义后其值不可改变。
如果使用String类的对象,对其频繁更新时,就会不停地创建新的对象,重新引用。
所以如果要执行1000次重新赋值的过程,就要创建1000个字符串对象,花费很多时间和内存空间,所以效率很低。这时就需要使用可变字符串类。
/*
* 可变字符串
* */
public class Test1 {
public static void main(String[] args) {
//定义一个空的可变字符串对象
StringBuilder sb = new StringBuilder();
//定义一个普通字符串对象
String str = "";
//1970 1 1 0:0:0 到当前这个瞬间,经过了多少毫秒。记录开始时间
long begin = System.currentTimeMillis();
//模拟循环多次频繁操作字符串,记录花费的时间
for (int i = 1000000; i > 0; i--) {
//如果使用字符串,每次循环都会创建一个新的String字符串对象,重写引用给str,需要花费大量时间
// str+=i;
//如果使用可变字符串,只有一个对象sb参与,每次都在操作这个对象,不创建新对象,很快就能结束
sb.append(i);
}
//记录结束时间
long end = System.currentTimeMillis();
System.out.println(end - begin);
}
}
StringBuilder类
用于表示可变字符串的一个类,是非线程安全的,建议在单线程环境下使用,效率略高于StringBuffer。
StringBuffer类
用于表示可变字符串的一个类,是线程安全的,建议在多线程环境下使用,效率略低于StringBuilder。
StringBuilder和StringBuffer中的方法作用都一致,只不过StringBuffer中的方法使用了synchronized关键字修饰,表示一个同步方法,在多线程环境下不会出现问题。
所以这里以StringBuilder为例。
构造方法
常用构造方法 | 作用 |
---|---|
StringBuilder() | 创建一个大小为16的字符数组。类似于String str=“”; |
StringBuilder(int capacity) | 创建一个指定大小的字符数组 |
StringBuilder(String str) | 创建一个str长度+16的字符串数组后,将str添加到其中。类似于String str=“初始值”; |
普通方法
方法 | 作用 |
---|---|
append(Object obj) | 将指定内容添加到原可变字符串对象末尾 |
delete(int start,int end) | 删除[start,end)范围内的字符 |
deleteCharAt(int index) | 删除指定索引的字符 |
Insert(int index,Object obj) | 将obj添加到index位置上 |
replace(int start,int end,String str) | 将[start,end)范围内的字符替换为str |
reverse() | 翻转原字符串 |
注意
-
以上方法都是在直接操作原字符串,每个方法调用后,原字符串都会发生变化。
-
StringBuilder或StringBuffer中并没有重写equlas方法,所以要比较两个可变字符串对象的值是否
相同时,需要将可变字符串对象转换为String对象后,调用equals方法比较。
可变字符串与不可变字符串之间的转换
有些方法如indexOf()、charAt()等,在String和StringBuilder中都存在,可以不用转换。
但有些方法如getBytes()、contains()等,只能通过String调用,这时就需要进行转换。
不可变字符串转换为可变字符串
通过创建一个可变字符串对象,将不可变字符串作为参数实现转换。
//定义一个不可变字符串对象
String str="hello";
//创建一个可变字符串对象,将不可变字符串对象作为参数
StringBuilder sb = new StringBuilder(str);
可变字符串转换为不可变字符串
通过调用可变字符串的toString()方法实现转换
//创建一个可变字符串对象
StringBuilder sb = new StringBuilder("hello");
//调用toString()转换为String类型
String str=sb.toString();
System类
这个类包含了一些系统相关的信息和操作数组的方法。其中的方法和属性都是静态的
常用属性和方法 | 作用 |
---|---|
System.in | 获取系统输入流对象,通常用于获取输入信息 |
System.out | 获取系统打印输出流对象,通常用于打印普通信息 |
System.err | 获取系统打印输出流对象,通常用于打印异常信息 |
System.exit(int statues) | 终止虚拟机运行,0表示正常结束 |
System.getenv(String key) | 获取系统指定的环境变量信息 |
System.arraycopy(原数组,原数组起始位置,目标数组,目标数组起始位置,原数组要赋值的元素数量) | 将原数组中指定长度的元素赋值到数组中 |
System.currentTimeMills() | 获取从1970.1.1 0:0:0(UTC)至今经过了多少毫秒。中国是UTC(+8)所以是从1970.1.1 8:0:0至今经过了多少毫秒,返回long类型 |
Runtime类
Runtime类的对象,用于表示程序运行时对象(程序运行环境对象)。
包含了程序运行环境相关的信息。常用于获取运行环境信息(如虚拟机内存)或执行某个命令。
特点
这个类不是一个抽象类,但不能创建对象,因为其构造方法是私有的。
但是它提供了一个静态方法getRuntime(),通过这个方法,可以获取一个Runtime类的对象。
这就是java中的一种设计模式–单例模式(只能有一个对象创建)
public class Test {
public static void main(String[] args) throws IOException, InterruptedException {
//通过静态方法getRuntime()获取一个Runtime类的对象
Runtime runtime = Runtime.getRuntime();
//获取程虚拟机空闲内存,单位为字节
System.out.println("当前虚拟机空闲内存" + runtime.freeMemory() / 1024 / 1024 + "MB");
//获取虚拟机总内存
System.out.println("当前虚拟机最大内存" + runtime.totalMemory() / 1024 / 1024 + "MB");
//获取虚拟机支持的最大内存
System.out.println("当前虚拟机支持的最大内存" + runtime.maxMemory() / 1024 / 1024 + "MB");
//让系统运行某个指令或程序,返回当前运行的进程对象
Process mspaint = runtime.exec("mspaint");//打开画图工具
Thread.sleep(3000);
//通过进程对象调用destroy()销毁进程,关闭程序
mspaint.destroy();
runtime.exec("calc");//打开计算器
runtime.exec("notepad");//打开记事本
//打开某个可执行文件
runtime.exec("D:\\xiaopw84in1111\\disland.xiaopw84in1\\smynesc.exe");
//300s后关机
runtime.exec("shutdown -s -t 300");
//取消关机任务
runtime.exec("shutdown -a");
}
}
方法调用时传值问题
Person类
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Test类
public class Test {
/*
* 如果参数是一个原始类型,当前方法中对该参数的所有操作,不会影响实参
* */
public static void fun1(int i) {
i = 123;
System.out.println(i);
}
/*
* 如果参数是一个字符串,当前方法中字符串"重新赋值",实际是创建了一个新字符串对象,不会影响实参
* */
public static void fun2(String str) {
str = "new";
System.out.println(str);
}
/*
* 如果参数是一个引用类型,当前方法中直接对该参数做操作,操作的就是实参,会影响实参
* */
public static void fun3(Person p) {
p.setName("吴彦祖");
System.out.println(p.getName());
}
/*
* 如果参数是一个引用类型,当前方法中又创建了一个新的对象,操作的就是方法中的对象,不会影响实参
* */
public static void fun4(Person p) {
p = new Person();
p.setName("易烊千玺");
System.out.println(p.getName());
}
/*
* 如果参数是数组(引用类型),当前方法中在直接操作数组,会影响实参
* */
public static void fun5(int[] list) {
list[0] = 111;
System.out.println(list[0]);
}
public static void main(String[] args) {
//方法参数是原始类型,方法内部的操作不影响实参
int i = 0;
fun1(i);//123
System.out.println(i);//0
//方法参数是字符串,方法内部用新字符串重新赋值,不影响实参
String str = "old";
fun2(str);//new
System.out.println(str);//old
//方法参数是引用类型,方法内部直接操作该参数,会影响实参
Person p = new Person();
p.setName("阿张");
fun3(p);//吴彦祖
System.out.println(p.getName());//吴彦祖
//方法参数是引用类型,方法内部新创建了一个对象进行操作,不会影响实参
Person p2 = new Person();
p2.setName("阿王");
fun4(p2);//易烊千玺
System.out.println(p2.getName());//阿王
//方法参数是数组,方法内部直接操作该数组中的元素,会影响实参
int[] list={
3,2,1};
fun5(list);
System.out.println(list[0]);
}
}
总结
参数只有是引用类型(类、数组、接口),且方法中直接操作该参数时,才会对实参造成影响。
fun3(Person p)参数为Person对象,方法中调用setxxx,是在操作实参。
fun5(int[] list)参数为数组,方法中直接操作某个索引的元素,是在操作实参。
public static void fun(char[] list,Person p){
list[0]='x';//这里在直接操作数组,会影响实参
p=new Person();//这里对参数p重新赋值了,不会影响实参
p.setName("小李"); System.out.println(list[0]+"\t"+p.getName());
}
public static void main(String[] args){
Person p= new Person();
p.setName("小美");
char[] list={
'a','b','c'};
fun(list,p); System.out.println(list[0]+"\t"+p.getName());
}
//输出结果:
//x 小李 方法内部打印方法执行的情况
//x 小美 数组参数直接操作,发生改变;Person参数创建了一个新对象重新赋值,没有改变
包装类
Java是纯面向对象语言,宗旨是将一切事物视为对象处理。
但原始类型不属于对象,不满足面向对象的思想,但原始类型使用时无需创建对象,保存在栈中,效率更改。
为了让原始类型也有对象的类类型,达到"万物皆对象"的思想,所以就有了包装类的概念。
**包装类就是原始类型对应的类类型。**包装类通常用于将字符串转换为对应的原始类型。
在web应用中,从浏览器中获取到后台的数据,全是String类型,一定要使用转换的方法。
包装类 | 原始类型 | 将字符串转换为原始类型 |
---|---|---|
Byte | byte | Byte.parseByte(String str) |
Short | short | Short.parseShort(String str) |
Integer | int | Integer.parseInt(String str) |
Long | long | Long.parseLong(String str) |
Float | float | Float.parseFloat(String str) |
Double | double | Double.parseDouble(String str) |
Boolean | boolean | Boolean.parseBoolean(String str) |
Character | char | 无 |
特点
-
八个原始类型中,除了int和char之外,其余类型的包装类,都是将首字母改为大写。int为Integer,char为Character。
-
除了Character类之外,其余类都有至少两个构造方法:参数为原始类型或字符串的构造方法。Character的构造方法只有一个,参数为char变量。
-
除了Character类之外,其余类都有静态方法parse原始类型(String str),用于将字符串转换为相应的原始类型
- 数值型的包装类的parseXXX()方法,如果不是一个真正的对应类型的数,转换时会抛出NumberFormatException异常,如"123abc"、"123.456"都不能使用Integer.parseInt()转换
- Boolean类型中的parseBoolean()方法,参数如果是"true"这个单词,无论大小写,都能转换为真正的boolean值的true,只要不是"true"这个单词,转换结果都为false
-
除了Boolean类之外,其余类都有MAX_VALUE和MIN_VALUE这两个静态属性,用于获取对应原始类型支持的最大最小范围
-
所有包装类中都有一个**compareTo(参数1,参数2)**方法,用于比较两个参数
- 如果是数值型,参数1>参数2返回1,参数1<参数2返回-1,相同返回0
- 如果是Boolean型,两个参数相同返回0,不同时,如果参数1为true返回1,否则返回-1
- 如果是Character型,返回参数1-参数2的值。
-
所有包装类中都有toString()方法,用于将包装类对象转换为String字符串对象
装箱和拆箱
- 所有包装类都有一个静态方法valueOf(原始类型),将某个原始类型的数据转换为相应的包装类对象,这个过程称为装箱。
//手动装箱
int i=123;//定义一个原始类型的数据
Integer aInteger=Integer.valueOf(i);//调用包装类的valueOf()方法将原始类型转换为包 装类对象
- 所有包装类都有一个原始类型Value()方法,用于将包装类对象装换为原始类型,这个过程称为拆箱
//手动装箱
int i=123;//定义一个原始类型的数据
Integer aInteger=Integer.valueOf(i);//调用包装类的valueOf()方法将原始类型转换为包 装类对象
- 所有包装类都有一个原始类型Value()方法,用于将包装类对象转换为原始类型,这个过程称为拆**** 箱
//手动拆箱
Integer aInteger=new Integer(123);//创建一个包装类对象
int i = aInteger.intValue();//调用包装类的"原始类型Value()"方法将其转换为原始类型
- 自动装箱拆箱。在jdk1.5之后,加入了自动装箱拆箱的特性,可以直接在原始类型和对应的包装类中互相赋值
//自动装箱
Integer aInteger=123;
//自动拆箱
int i=aInteger;
- 自动装箱池
//以下代码的输出结果:
Integer i1=new Integer(100);
Integer i2=new Integer(100);
//i3中保存的100,在byte范围内,保存在"自动装箱池"中
Integer i3=100;
//i4中保存的100,在byte范围内,如果有现成的,直接引用
Integer i4=100