0.类和对象深入解释
在Java中,类(Class
)和对象(Object
) 是两个核心概念,它们共同构成了面向对象编程(OOP
)的基础。
类(Class)
-
定义:类是一个模板或蓝图,它描述了具有相同属性和行为的一组对象的共同特征。在Java中,类通过关键字
class
来定义。 -
组成:类通常由成员变量(也称为属性或字段)和方法(也称为函数或行为)组成。成员变量表示对象的状态,而方法则定义了对象的行为。
-
特性:类具有封装性,可以将数据和操作数据的方法结合在一起,形成一个不可分割的单位。此外,类还支持继承和多态等面向对象特性。
对象(Object)
-
定义:对象是类的实例化,是类的具体表示。每个对象都是类的一个实例,它拥有类定义的属性和方法。
-
创建:在Java中,对象是通过使用new关键字和类的构造函数来创建的。例如,MyClass obj = new MyClass();这行代码创建了一个MyClass类的对象obj。
-
状态和行为:对象的状态由它的成员变量(属性)表示,而对象的行为则由它的方法定义。通过调用对象的方法,可以改变对象的状态或执行某些操作。
-
唯一性:每个对象在内存中都有一个唯一的地址,这使得每个对象都是独一无二的。即使两个对象属于同一个类,它们的状态也可能不同。
关系:
- 类是对象的模板:类是创建对象的蓝图或原型。通过定义类,我们指定了对象将拥有的属性和方法。
- 对象是类的实例:对象是类的具体实现。当我们创建一个对象时,我们实际上是在创建一个类的实例,该实例将拥有类定义的属性和方法。
示例:
// 定义一个类
class Person {
// 成员变量(属性)
String name;
int age;
// 方法(行为)
void speak() {
System.out.println("Hello, my name is " + name + " and I am " + age + " years old.");
}
}
public class Main {
public static void main(String[] args) {
// 创建对象
Person person1 = new Person();
Person person2 = new Person();
// 设置对象的状态
person1.name = "Alice";
person1.age = 30;
person2.name = "Bob";
person2.age = 25;
// 调用对象的方法
person1.speak(); // 输出: Hello, my name is Alice and I am 30 years old.
person2.speak(); // 输出: Hello, my name is Bob and I am 25 years old.
}
}
在这个示例中,Person是一个类,它有两个属性(name和age)和一个方法(speak)。Main类中的main方法创建了两个Person类的对象(person1和person2),并设置了它们的状态,然后调用了它们的方法。
1.成员变量和局部变量的区别
局部变量:在方法中的变量
成员变量:类中方法外的变量
例:
public class student {
String name;//成员变量
public void learn {
int i = 0;//局部变量
System.out.println(i);
}
}
区别:
2. System.out.printf( )的使用
我们在前面经常使用System.out.println()
,自带换行,并且可进行字符串之间,字符串和变量的连接。
下面我们介绍剩下的两种:
System.out.print()
System.out.printf()
System.out.print()
不进行换行,其他功能和System.out.println()
一样System.out.printf()
用于对内容的格式化输出:
首先用//整型 int num = 100; System.out.printf("%d",num); //字符串 String name = "lshh"; System.out.printf("%s",name); //字符型 char ch = 'a'; System.out.prinf("%c",ch);
%s/d/f/c...
进行占位,在“”后用英文逗号隔开,跟上实际的变量
prinf
也支持多个变量,以及字符和变量混合//多个变量根据类型写上占位符,在后边用逗号跟上变量即可 System.out.printf("%d %s %c", a, b, c); //和字符一起 System.out.printf("在%s我的年龄是:%d", year, age); // 使用 %x 格式化整数为十六进制 int hexValue = 255; System.out.printf("The integer value in hexadecimal is: %x%n", hexValue); // 使用 %o 格式化整数为八进制 int octalValue = 255; System.out.printf("The integer value in octal is: %o%n", octalValue); // 使用 %e 格式化浮点数为科学计数法 System.out.printf("The double value in scientific notation is: %e%n", doubleValue); // 使用 %% 输出百分号字符 System.out.printf("This is a percent sign: %%%n");
常用的格式说明符:
符号 | 功能 |
---|---|
%s | 用于字符串 |
%d | 用于整型 |
%c | 用于字符型 |
%f | 用于浮点型 |
%t | 用于时间/日期的格式化 |
%x | 格式化整数为十六进制 |
%o | 格式化整数为八进制 |
%e | 格式化浮点数为科学计数法 |
%% | 输出百分号字符 |
需要我们注意的是:
System.out.printf()
不会进行自动换行
如果需要换行,可以使用如下符号:
\n
%n
(无关平台,在不同的平台上自动转化为合适的换行符)
System.out.printf("-我想和你在一起\n");
System.out.printf("-我...%n我也喜欢你\n");
输入结果为:
printf
还有一个重要的功能:控制输出精度
我们以浮点型为例:%m.nf
- m :控制输出的宽度,超出在最左边补空格(小数点也算一位)
- n:控制小数的位数,四舍五入
// 使用 %f 格式化浮点数(默认保留6位小数)
double doubleValue = 3.141592653589793;
System.out.printf("The double value is: %f%n", doubleValue);
// 使用 %5.2f 格式化浮点数(宽度为5,保留2位小数)
System.out.printf("The double value rounded to 2 decimal places is: %.2f%n", doubleValue);
3.String类
我们学过对象之后,就可以很好的理解String
被成为引用类型了
-
因为
String
是Java定义好的一个类,定义在java.lang
包中,是Java的核心包,所以在使用的时候不需要导包; -
Java程序中所有的字符串文字(例如:“abcdef”),都被成为此类的对象。
-
字符串的内容是不会发生改变的,它的对象在创建之后不可以再修改
例如: String name = "lshhh"; name = "lsh";
实际上是创建了两个字符串,把后一个字符串的地址赋值给了
name
,前一个的字符串内容没有被改变
4.字符串的创建方法
-
直接赋值:
String name = "lshh";
-
使用
new
关键字调用类的构造方法构造方法 说明 public String()
创建空白字符串,不含任何内容 public String(String original)
根据传入的字符串,创建字符串对象 public String(char[] chs)
根据字符数组,创建字符串对象 public String(byte[] chs)
根据字节数组,创建字符串对象 示例:
//2.使用new的方式来获取一个字符串对象 //空参构造:可以获取一个空白的字符串对象 String s2 = new String(); System.out.println("@" + s2 +"!");//s2是一个空的字符串"" //传递一个字符串,根据传递的字符串内容再创建一个新的字符串对象 String s3 = new String( original: "abc");//一般不适用,冗余 System.out.println(s3); //传递一个字符数组,根据字符数组的内容再创建一个新的字符串对象 //需求:我要修改字符串的内容。 //abc -- > {'a', 'b', 'c'} -- > {'Q','b', 'c' } -- > "'Qbc" char[] chs = {'a', 'b', 'c', 'd'}; String s4 = new String(chs); System.out.println(s4);//abcd //传递一个字节数组,根据字节数组的内容再创建一个新的字符串对象 byte[] bytes = {97, 98, 99, 100};//根据ASSIC码输出对应的字符 String s5 = new String(bytes); System.out.println(s5);//abcd
5.字符串两种创建方式的区别
我们再来看内存模型:
除此之外:存储直接赋值
的字符串的地方叫串池(StringTable)
- 串池从JDK7开始从方法区挪到了堆内存
- 串池全名:字符串常量池
-
直接赋值
示例来自黑马程序员
我们可以看到,在串池中,当存在
''abc''
的时候,就直接把地址又给了s2
,这种操作称之为:复用
总的来说:用双引号直接赋值的时候,系统会检查该字符串在串池中是否存在:- 不存在:创建新的
- 存在:复用
-
new 调用构造方法
可以看到,在堆内存中创建空间,同时每个字符串的地址都是不重复的这和我们前面说的对象的唯一性是符合的:
- 唯一性:每个对象在内存中都有一个唯一的地址,这使得每个对象都是独一无二的。即使两个对象属于同一个类,它们的状态也可能不同。
这时我们可能产生疑问:直接赋值的字符串对象当使用复用的时候,地址是一样的呀,这还符合对象的唯一性吗?
这并不意味着字符串对象失去了唯一性,对象是字符串(例如:“abcdef”),它在内存中的地址是唯一的,但是这个地址可以被多个变量所引用。
6.字符串常用方法
6.1equals比较字符串
示例:
String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2);//true
String s3 = new String("abc");
System.out.println("abc");
System.out.println(s1 == s3);//false
为什么同样的字符串,比较的结果却不同:
==比较的到底是什么?
-
对于基本数据类型
int a = 10; int b = 20; System.out.println(a == b);//false
基本数据类型比较的是数据值
-
引用数据类型
String s1 = new String("abc"); String s2 = new String("abc"); System.out.println(s1 == s2);//false
引用数据类型比较的是地址值
现在我们对前面的代码进行解释:
- 第一个
字符串字面量存储在字符串常量池中,会使用复用,地址一样String s1 = "abc"; String s2 = "abc"; System.out.println(s1 == s2);//true
- 第二个
用String s3 = new String("abc"); System.out.println("abc"); System.out.println(s1 == s3);//false
new关键字
创建的对象是在堆内存中的,在堆中,不论字符串是不是已经存在,都会重新创建一个新的字符串,并分配内存。所以地址一定是不一样的
字符串内容比较:
boolean equals()
:完全一样为trueboolean equalslgnoreCase
:不区分大小写(只适用于英文)
示例:
//1.创建两个字符串对象
String s1 = new String( original: "abc");
String s2 = "Abc";
//2 .== 号比较
//基本数据类型:比的是数据值
//引用数据类型:比的是地址值
System.out.println(s1 == s2);//false
//3.比较字符串对象中的内容是否相等
boolean result1 = s1.equals(s2);
System.out.println(result1);
//4.比较字符串对象中的内容是否相等,忽略大小写
//1一壹 这不行
//忽略大小写只能是英文状态下的a A
boolean result2 = s1.equalsIgnoreCase(s2);
System.out.println(result2);//true
使用Scanner
键盘输入字符串比较:
//1.假设我现在键盘录入一个abc
Scanner sc = new Scanner(System. in);
System.out.println("请输入一个字符串”);
String str1 = sc.next();//abc
//2.代码中再定义一个字符串abc
String str2 = "abc";
//3.用 == 比较,这两者能一样吗?
System.out.println(str1 ==str2);//false
结果false,意味着Scanner
键盘录入的abc
,并不在字符串常量池中存储,是在堆中的;
6.2遍历字符串
-
public char charAt(int index)
:根据索引返回字符 -
public int length()
:返回此字符串的长度 -
数组的长度:
数组名.length
-
字符串的长度:
字符串对象.length()
1.public char charAt(int index)
示例:
//1.键盘录入一个字符串
Scanner sc = new Scanner(System. in) ;
System.out.println("请输入一个字符串”);
String str = sc.next();
//2.进行遍历
for (int i = 0; i < str.length(); i++) {
//i 依次表示字符串的每一个索引
ghar c = str. charAt(i);
System.out.println(c);
}
输出结果:
6.3 截取字符串
substring(int beginIndex, int endIndex)
:从beginIndex
位置截取到endIndex
位置;包头不包尾;
- 包头不包尾:指的是截取的字符串包含
beginIndex
位置,但是不包含endIndex
位置的字符substring(int beginIndex)
:表示从beginIndex
位置截取到字符串的末尾;- 它的返回值是
String
类型,返回的结果是截取的字符串,对原来的字符串不产生影响
示例:手机号屏蔽:
String phoneNumber = "13112349468";
//2.截取手机号码前面三位
String start = phoneNumber.substring(0, 3);
//3.截取手机号码后面四位
String end = phoneNumber.substring(7);
//4.拼接
String result = start + " **** " + end;
System.out.println(result);
6.4 字符串替换
replace(旧值,新值)
:返回值是String类型,返回的结果是一个新的字符串,对原有的字符串不产生影响
示例:敏感词替换:
String talk="你玩的真好,以后不要再玩了,TMD,CNM";
//2.定义一个敏感词库
String[] arr = {"TMD","CNM", "SB", "MLGB"};
//3.循环得到数组中的每一个敏感词,依次进行替换
for (int i = 0; i < arr.length; i++) {
talk = talk.replace(arr[i], " *** ");
}
//4.打印结果
System.out.println(talk):
6.5 StringBuilder类
我们在连接字符串的时候,基本的方法是使用+
作连接符,但是在处理多个字符串连接的时候,这样效率偏低
String str = "abc";
for (int i = 0; i <= 10000; i++) {
str += str;
}
运行效率是很低的;如何提高字符串的连接效率呢:
我们首先要知道字符串连接的逻辑:
//例如
String s1 = "a";
String s2 = "b";
String s3 = "c";
String s4 = "d";
String s = s1 + s2 + s3 + s4;
在运行的时候,我们说过字符串对象一旦创建就不能再修改;
所以步骤为:
s1 + s2
作为一个新的字符串再和剩下的字符串加下s1 + s2
和s3
相加,又创建了一个新的字符串- 这个新的字符串再加
s4
之后又创建一个新的字符串,把地址传给s
这样的步骤时间效率是很低的,并且浪费了大量的内存。
所以我们引入了StringBuilder
类
我们创建了StringBuilder
类的对象后,这个对象就像一个缓存的中间容器,可以把字符串都暂时存储再这个容器中进行连接,而不会产生新的字符串。
a.StringBuider的构造方法
在对象的讲解中,我们知道,一个完整的对象是包含无参和有参的构造方法:
现在我们来看:
- 无参:
public StringBuider()
:创建一个空白可变字符串对象,不含有任何内容 - 有参:
public StringBuider(String str)
:根据传入的字符串的内容,创建一个可变的字符串
String str = "lshh";
StringBuider s = new StringBuider(str);
StringBuider c = new StringBuider();
值得注意的是:上面我们只是创建了
StringBuilder
类型的容器,并不是字符串
b.StringBuider成员方法
方法名 | 说明 |
---|---|
public StringBuilder append(任意类型) | 添加数据,并返回对象本身 |
public StringBuilder reverse() | 反转容器中的内容 |
public int length() | 返回长度(字符出现的个数) |
public String toString() | 通过toString()就可以实现把StringBuilder转换为String |
示例:
//1.创建对象
StringBuilder sb = new StringBuilder();
StringBuilder sa = new StringBuilder("abc");
//2.添加字符串(任意类型)
sb.append("aaa");
sb.append(1);
sb.append(2.3);
sb.append(true);
System.out.println(sb);//aaa12.3true
//3.反转
sa.reverse();
System.out.println(sa);//cba
//4.获取长度
System.out.println(sa.length());
//5..再把StringBuilder变回字符串
String str = sb.toString();
System.out.println(str);//就可以调用String的方法了
6.6 链式编程的理解
在我们上面的代码中使用了
s.append("aaa");
s.append(1);
s.append(2.3);
s.append(true);
去添加数据,但是这样写代码显得冗余,我们可以这样写:
s.append("aaa").append(1).append(2.3).append(true);
因为append()
返回的类型依然是StringBuilder
类型,所以我们可以直接使用s.append()
调用StringBuilder
的方法;
因为上面的代码是链形的,所以称之为链式编程
6.7 StringJoiner 类
StringJoiner概述
-
StringJoiner
跟StringBuilder
一样,也可以看成是一个容器,创建之后里面的内容是可变的。 -
作用:提高字符串的操作效率,而且代码编写特别简洁,但是目前市场上很少有人用。因为它是JDK8出现的。
a.构造方法
方法名 | 说明 |
---|---|
public StringJoiner(间隔符号) | 创建一个StringJoiner对象,指定拼接时的间隔符号 |
public StringJoiner(间隔符号,开始符号,结束符号) | 创建一个StringJoiner对象,指定拼接时的间隔符号、开始符号、结束符号 |
StringJoiner s = new StringJoiner("---");
//1---2---3
StringJoiner s = new StringJoiner(",", "[","]");
//[1,2,3]
b.成员方法
方法名 | 说明 |
---|---|
public StringJoiner add(添加的内容) | 添加数据,并返回对象本身 |
public int length() | 返回长度(字符出现的个数) |
public String toString() | 返回一个字符串(该字符串就是拼接之后的结果) |
c.示例
//1.创建对象
StringJoiner sj = new StringJoiner( ", ", "[", "]");
//2.添加元素
sj.add("aaa").add("bbb").add("ccc");
int len = sj.length();
System.out.println(len);//15
//3.打印
System.out.println(sj);//[aaa, bbb, ccc]
//4.转换为字符串类型,方便调用String的方法
String str = sj.toString();
System.out.println(str);//[aaa, bbb, ccc]
7.字符串相关类的底层原理
7.1 字符串存储的内存原理
前面我们已经详细讲过,这里简略总结:
- 直接赋值的字符串在字符串常量池中存储,会有复用
new
出来的字符串对象,在堆中存储,每个字符串都会开辟空间分配地址
7.2 == 比较的是什么
- 基本数据类型比较数据值
- 引用数据类型比较地址值
7.3 字符串拼接的底层原理
a. 没有变量在"="右侧参与赋值
public class Test {
public static void main(String[] args) {
String s = "a" + "b" + "c";
System.out.println(s);
}
}//结果:"abc"
这是会触发字符串的优化机制,在.java
文件编译为.class
文件的时候就会自动连接起来"abc"
;实际上在串池中存储的就是"abc"
b. 有变量参与赋值
public class Test {
public static void main(String[] args) {
String s1 = "a";
String s2 = s1 + "b";
String s3 = s2 + "c";
System.out.println(s3);
}
}
String s1 = "a"
在串池中存储"a"
String s2 = s1 + "b"
,在串池中存储“b”
,调用StringBuilder
类创建一个容器存储"ab"
,再使用ToString
方法返回字符串类型,同时要创建一个String
类的对象来接收字符串。(都是使用new
创建,所以都在堆内存中)String s3 = s2 + "c"
:重复上面的步骤
所以上面的代码在堆中创建了四个对象(两个
StringBuilder
,两个String
),这是JDK8之前的版本的做法
在JDK8之后的版本中:
public class Test {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "c";
String s4 = s1 + s2 + s3;
System.out.println(s3);
}
}
采用的是预估长度的做法:
- 首先预估字符串需要的长度,之后创建一个该长度的数组,把这些字符放在这个数组中,之后再返回字符串类型。
- 但是当每行代码都是含有变量的赋值的时候,就会在每行进行预估,创建数组,返回字符串。时间和空间效率依然不是很高
所以为了提高效率,在进行字符串拼接的时候,推荐使用StringBuilder
7.4 StringBuider提高效率
简略说明:
- 所有要拼接的内容都会往StringBuilder中放,不会创建很多无用的空间,节约内存
- 容量:
- 默认创建一个长度为16的字节数组
- 添加的内容长度小于16,直接存储
- 添加的内容大于16会扩容(原来的容量*2 + 2)
- 如果扩容之后还不够,以实际长度为准
标签:详讲,Java,String,对象,System,println,字符串,out From: https://blog.csdn.net/2302_80203877/article/details/143250537