java底层源码收集
[toc]
源码方法的概念和用法
system中的arraycopy的概念和用法
System.arraycopy()
是Java中用于复制一个数组的方法。它的语法如下:
public static void arraycopy |
其中,参数含义如下:
src
:源数组,需要复制的数组。srcPos
:源数组的开始位置,复制的起始位置。dest
:目标数组,复制的结果存放的位置。destPos
:目标数组的起始位置,从该位置开始存放复制后的数据。length
:需要复制的元素个数。注意,该方法只能复制数组中的内容,并不能复制数组对象的引用。
下面是一个示例:
int[] sourceArray = {1, 2, 3, 4, 5}; |
可以看到,通过 System.arraycopy()
方法,我们将源数组中的元素复制到了目标数组中。
除了基本类型的数组,System.arraycopy()
方法还可以用于复制对象类型的数组。示例代码如下:
Person[] sourceArray = new Person[3]; |
在这个示例中,我们定义了一个
Person
类,用于存储一个人的姓名和年龄信息。我们创建了一个Person
类型的数组sourceArray
,并向其中添加了三个元素。然后,我们创建了一个与sourceArray
同样大小的Person
类型的数组destinationArray
,并调用System.arraycopy()
方法将sourceArray
中的元素复制到destinationArray
中。最后,我们输出destinationArray
的内容。需要注意的是,在复制对象类型的数组时,
System.arraycopy()
方法只会复制数组对象的引用,而不会复制对象本身。因此,如果修改sourceArray
中的元素,那么destinationArray
中对应的元素也会发生变化。
另外需要注意的是,
System.arraycopy()
方法的性能比通过循环逐个复制数组元素的方式要高效得多。这是因为System.arraycopy()
方法使用了底层的系统级别的内存拷贝操作,而循环逐个复制则需要进行多次内存读写操作,开销较大。需要注意的是,如果使用
System.arraycopy()
方法复制数组时,源数组和目标数组重叠部分的数据会被覆盖。因此,在进行这种操作时,一定要确保源数组和目标数组没有重叠部分,否则会导致不可预期的结果。在Java中,还有一个与
System.arraycopy()
方法功能类似的方法Arrays.copyOf()
,它的使用方法类似于System.arraycopy()
方法。但是,与System.arraycopy()
方法不同的是,Arrays.copyOf()
方法可以在复制数组的同时指定新数组的长度,如果新数组长度大于源数组长度,则剩余的元素会被填充为默认值。示例代码如下:
int[] sourceArray = {1, 2, 3, 4, 5}; |
在这个示例中,我们将
sourceArray
复制到destinationArray
中,并将destinationArray
的长度设置为10。由于sourceArray
的长度为5,因此在复制完成后,destinationArray
的后5个元素会被填充为默认值0。输出结果为:
[1, 2, 3, 4, 5, 0, 0, 0, 0, 0] |
总之,
System.arraycopy()
方法是Java中用于复制数组的高效方法之一。它可以用于复制基本类型和对象类型的数组,并且使用简单,性能高效。
UsernamePasswordToken的概念和用法
UsernamePasswordToken
是 Shiro 中用于表示用户名密码的类。在使用 Shiro 进行身份认证时,通常需要使用UsernamePasswordToken
类来封装用户提交的用户名和密码信息。
UsernamePasswordToken
类继承自org.apache.shiro.authc.AuthenticationToken
接口,该接口代表一个身份认证令牌。AuthenticationToken
接口包含了一些通用的方法,用于获取认证信息。
UsernamePasswordToken
类的构造方法如下:
public UsernamePasswordToken(String username, String password) |
其中,
username
和password
分别表示用户提交的用户名和密码信息。此外,UsernamePasswordToken
类还提供了一些方法,用于获取和设置用户名、密码、是否记住我等信息,例如:
// 获取用户名 |
在使用 Shiro 进行身份认证时,通常需要将用户提交的
UsernamePasswordToken
对象传递给 Shiro 的SecurityManager
进行认证。示例代码如下:
// 创建一个 UsernamePasswordToken 对象,封装用户提交的用户名和密码信息 |
在上面的示例中,我们创建了一个
UsernamePasswordToken
对象,并将用户提交的用户名和密码信息封装在该对象中。然后,我们获取了一个SecurityManager
实例,并将UsernamePasswordToken
对象传递给SecurityManager
的authenticate()
方法进行身份认证。如果身份认证失败,将会抛出AuthenticationException
异常。需要注意的是,为了防止密码在内存中被明文存储,
UsernamePasswordToken
中的密码是通过char[]
类型存储的,而不是字符串。如果需要获取密码的字符串表示,可以使用String.valueOf(token.getPassword())
方法。此外,为了提高安全性,应该在使用完UsernamePasswordToken
对象后及时清除密码信息,例如:
// 使用完 token 对象后,清除密码信息 |
总之,
UsernamePasswordToken
是 Shiro 中用于封装用户提交的用户名和密码信息的类,它是进行身份认证的重要组成部分。
二
除了用于封装用户名和密码信息外,
UsernamePasswordToken
还可以用于实现基于验证码的身份认证。在这种情况下,UsernamePasswordToken
除了封装用户名和密码信息外,还需要封装验证码信息。为了实现基于验证码的身份认证,可以自定义一个
CaptchaUsernamePasswordToken
类,该类继承自UsernamePasswordToken
类,并添加一个captcha
属性用于存储验证码信息。示例代码如下:
public class CaptchaUsernamePasswordToken extends UsernamePasswordToken { |
在上面的示例中,我们创建了一个
CaptchaUsernamePasswordToken
类,该类继承自UsernamePasswordToken
类,并添加了一个captcha
属性用于存储验证码信息。在构造方法中,除了调用super(username, password)
方法传递用户名和密码信息外,还需要将验证码信息传递给CaptchaUsernamePasswordToken
对象进行封装。在进行身份认证时,可以将
CaptchaUsernamePasswordToken
对象传递给SecurityManager
进行认证。示例代码如下:
// 创建一个 CaptchaUsernamePasswordToken 对象,封装用户名、密码和验证码信息 |
在上面的示例中,我们创建了一个
CaptchaUsernamePasswordToken
对象,并将用户名、密码和验证码信息封装在该对象中。然后,我们获取了一个SecurityManager
实例,并将CaptchaUsernamePasswordToken
对象传递给SecurityManager
的authenticate()
方法进行身份认证。如果身份认证失败,将会抛出AuthenticationException
异常。总之,
UsernamePasswordToken
是 Shiro 中用于封装用户名和密码信息的类,可以用于实现基于密码和基于验证码的身份认证。在使用时,我们可以根据具体的需求来选择使用UsernamePasswordToken
或CaptchaUsernamePasswordToken
类。
StringUtils.IsNotEmpty()的概念和用法
StringUtils.isNotEmpty()
是 Apache Commons Lang3 库中的一个静态方法,用于判断字符串是否不为空。其语法如下:
public static boolean isNotEmpty(CharSequence cs) |
其中,
cs
参数表示要判断的字符串,可以是一个CharSequence
对象或者是一个字符串字面量。
StringUtils.isNotEmpty()
方法的返回值为boolean
类型,如果cs
不为空且长度不为 0,则返回true
,否则返回false
。需要注意的是,如果cs
参数为null
,则该方法也会返回false
。使用示例:
import org.apache.commons.lang3.StringUtils; |
在上面的示例中,我们使用了
StringUtils.isNotEmpty()
方法来判断四个不同的字符串。第一个字符串为null
,第二个字符串为空字符串,第三个字符串为一个空格字符串,第四个字符串为非空字符串。根据方法的返回值,我们可以看到只有第三个和第四个字符串被判定为非空字符串。因此,
StringUtils.isNotEmpty()
方法是一个非常实用的工具方法,可以在日常开发中用于判断字符串是否为空,避免因为空字符串而引发的空指针异常。
shiro中判断验证码是否正确的流程
在 Shiro 中,判断验证码是否正确的源码流程如下:
- 首先,客户端向服务端发送登录请求,请求中包含用户名、密码和验证码等信息。
- 服务端接收到登录请求后,通过
CaptchaUsernamePasswordToken
类将用户名、密码和验证码信息封装成一个 token 对象。- 服务端通过
SecurityUtils.getSubject()
方法获取当前用户的主体对象Subject
。- 服务端通过
subject.login(token)
方法将封装好的 token 对象传递给SecurityManager
进行身份认证。- 在进行身份认证时,
CaptchaMatcher
类会被调用,用于判断验证码是否正确。CaptchaMatcher
类继承自SimpleCredentialsMatcher
类,重写了doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)
方法。doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)
方法首先会将token
转换为CaptchaUsernamePasswordToken
对象,并从该对象中获取验证码信息。- 然后,
doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)
方法会从info
中获取正确的验证码信息。- 最后,
doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)
方法将用户输入的验证码信息和正确的验证码信息进行比较,如果相同则返回true
,否则返回false
。以上就是 Shiro 中判断验证码是否正确的源码流程。可以看出,Shiro 在身份认证时使用了
CaptchaMatcher
类来判断验证码是否正确,该类继承自SimpleCredentialsMatcher
类,重写了doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)
方法。在该方法中,Shiro 通过获取CaptchaUsernamePasswordToken
对象和AuthenticationInfo
对象中的验证码信息来进行比较,以判断验证码是否正确。
shiro中判断验证码如何代码实现
在 Shiro 中,判断验证码的实现通常需要编写一个自定义的
CaptchaMatcher
类。该类继承自SimpleCredentialsMatcher
类,重写了doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)
方法,用于判断验证码是否正确。具体实现过程如下:
- 首先,在
CaptchaMatcher
类中定义一个常量CAPTCHA_SESSION_KEY
,用于存储验证码的 Session Key 值。
public class CaptchaMatcher extends SimpleCredentialsMatcher { |
- 然后,重写
doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)
方法,用于判断验证码是否正确。在该方法中,首先将token
转换为CaptchaUsernamePasswordToken
对象,并从该对象中获取验证码信息。然后,从info
中获取正确的验证码信息。最后,将用户输入的验证码信息和正确的验证码信息进行比较,以判断验证码是否正确。
|
在上面的实现中,我们首先将
token
转换为CaptchaUsernamePasswordToken
对象,然后通过getCaptcha()
方法获取用户输入的验证码信息。接着,通过SecurityUtils.getSubject().getSession().getAttribute(CAPTCHA_SESSION_KEY)
方法从 Session 中获取正确的验证码信息。最后,将用户输入的验证码信息和正确的验证码信息进行比较,以判断验证码是否正确。需要注意的是,在比较验证码信息时,我们使用了 Apache Commons Lang3 库中的
StringUtils.equalsIgnoreCase()
方法,该方法可以忽略验证码信息的大小写,以提高验证码匹配的容错率。
- 最后,将自定义的
CaptchaMatcher
类配置到 Shiro 的配置文件中,以启用验证码校验功能。
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> |
在上面的配置中,我们通过
credentialsMatcher
属性指定了CaptchaMatcher
类,以替换默认的SimpleCredentialsMatcher
类。这样,在进行身份认证时,Shiro 将会使用自定义的CaptchaMatcher
类来判断验证码是否正确。
- 在前端页面中添加验证码输入框,并在表单中添加该输入框的名称。
<label for="captcha">验证码:</label> |
在上面的示例中,我们使用
img
标签显示验证码图片,并将该图片的src
属性设置为/captcha.jpg
,以从服务器获取验证码图片。此外,我们还添加了一个input
标签,用于输入验证码。需要注意的是,我们需要将该输入框的名称设置为captcha
,以便在后端代码中获取用户输入的验证码信息。
- 在后端代码中生成和保存验证码信息。
|
在上面的示例中,我们首先使用
generateCaptcha()
方法生成一个验证码字符串,然后将该字符串保存到 Session 中。最后,我们使用generateCaptchaImage(captcha)
方法生成验证码图片,并将其输出到客户端浏览器。需要注意的是,我们在上面的代码中使用了
SecurityUtils.getSubject().getSession()
方法获取当前用户的 Session 对象,并将验证码信息保存到 Session 中。这样,当用户提交表单时,我们就可以从 Session 中获取正确的验证码信息,并将其与用户输入的验证码信息进行比较,以判断验证码是否正确。完整的验证码校验代码示例:
public class CaptchaMatcher extends SimpleCredentialsMatcher { |
- 在 Shiro 配置文件中启用验证码校验
在 Shiro 配置文件中,我们需要启用验证码校验功能,以便在用户登录时对验证码进行校验。具体来说,我们需要将上面的
CaptchaMatcher
类配置为自定义的凭证匹配器,并将该匹配器与登录认证过滤器一起使用。完整的 Shiro 配置文件示例:
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> |
在上面的示例中,我们首先将自定义的
CaptchaMatcher
类配置为凭证匹配器,并将其注入到MyRealm
中。然后,我们定义了一个名为captchaFilter
的过滤器,该过滤器会拦截所有以/login
开头的请求,并对验证码进行校验。最后,我们将该过滤器与登录认证过滤器一起使用,并将其添加到 Shiro 过滤器链中。需要注意的是,我们在上面的代码中通过
captchaEnabled
属性启用了验证码校验功能。当该属性的值为true
时,Shiro 会自动调用CaptchaFilter
中的onFilterConfigSet()
方法,以便在过滤器初始化时生成和保存验证码信息。这样,当用户登录时,Shiro 会自动将用户输入的验证码与保存在 Session 中的正确验证码进行比较,以判断验证码是否正确。如果验证码校验失败,Shiro 会自动将错误信息保存到
shiroLoginFailure
属性中,以便在前端页面中显示错误提示信息。
Shiro中的subject接口中的getPrincipal()的概念和用法
在 Shiro 中,
Subject
是一个用户的安全主体,表示与应用程序交互的当前用户。Subject
中的getPrincipal()
方法用于获取当前用户的身份信息,通常是用户名、邮箱、手机号码等唯一标识用户身份的信息。getPrincipal()
方法返回的对象通常是一个java.lang.Object
类型,开发者可以根据实际情况将其转换为特定的类型。在 Shiro 中,
Principal
表示当前用户的身份信息,可以通过getPrincipal()
方法获取。在认证成功后,Shiro 会将用户的身份信息保存在Subject
中,以便在后续的操作中进行访问和使用。通常情况下,我们可以通过调用getPrincipal()
方法来获取当前用户的身份信息,并进行相应的业务处理,如记录日志、权限控制等。下面是一个简单的示例,演示了如何使用
getPrincipal()
方法获取当前用户的身份信息:
// 获取当前用户 |
在上面的示例中,我们首先通过
SecurityUtils.getSubject()
方法获取当前用户的Subject
对象,然后调用isAuthenticated()
方法判断用户是否已经登录。如果用户已经登录,我们就可以通过getPrincipal()
方法获取用户的身份信息,并将其转换为特定类型,这里我们将其转换为字符串类型。最后,我们可以使用获取到的用户信息进行相应的业务处理。需要注意的是,
getPrincipal()
方法返回的对象类型取决于用户在认证过程中设置的身份验证信息。例如,如果用户使用用户名和密码进行身份验证,则getPrincipal()
方法通常返回用户名。如果用户使用电子邮件地址或手机号码进行身份验证,则getPrincipal()
方法通常返回相应的电子邮件地址或手机号码。开发者应该根据实际情况将返回的对象转换为特定的类型,并进行相应的处理。
使用EasyExcel读写一个类
使用 EasyExcel 读写一个类,一般分为以下几个步骤:
- 定义一个实体类,并使用
@ExcelProperty
注解指定每个字段对应的 Excel 表头。- 使用
ExcelReader
创建一个读取器,并使用read
方法读取 Excel 文件。- 使用
ExcelWriter
创建一个写入器,并使用write
方法将数据写入 Excel 文件。下面我们来具体介绍这几个步骤。
1. 定义一个实体类
假设我们需要读写一个学生信息表,包括学生姓名、学生性别和学生年龄。那么我们可以定义一个
Student
类,代码如下:
public class Student { |
在这个类中,我们使用
@ExcelProperty
注解为每个字段指定了对应的 Excel 表头,例如@ExcelProperty("姓名")
表示姓名字段对应的 Excel 表头为“姓名”。2. 使用 ExcelReader 读取 Excel 文件
读取 Excel 文件的步骤如下:
- 创建一个
ExcelReader
对象。- 定义一个
AnalysisEventListener
对象,用于处理读取到的 Excel 数据。- 调用
read
方法读取 Excel 文件。下面是一个示例代码:
public class ReadExcelDemo { |
在这个示例中,我们首先创建了一个
ExcelReader
对象,它接受四个参数:
- 第一个参数是 Excel 文件的输入流。
- 第二个参数是 Excel 文件的类型,这里我们指定为
.xlsx
格式。- 第三个参数是读取 Excel 文件时使用的读取器,这里我们使用默认的读取器,所以指定为
null
。- 第四个参数是一个
AnalysisEventListener
对象,用于处理读取到的 Excel 数据。在这里,我们实现了invoke
方法,用于处理每一行读取到的数据,以及doAfterAllAnalysed
方法,用于在读取完成后进行一些操作。然后我们调用
read
方法读取 Excel 文件,它接受一个Sheet
对象作为参数,表示需要读取的工作表信息。在这里,我们指定要读取第 1 个工作表,从第 1 行开始读
写入 Excel 文件的步骤如下:
- 创建一个
ExcelWriter
对象。- 调用
write
方法写入数据。- 调用
finish
方法保存 Excel 文件。下面是一个示例代码:
public class WriteExcelDemo { |
在这个示例中,我们首先准备了一个要写入的学生列表。然后,我们创建了一个
ExcelWriter
对象,它接受两个参数:
- 第一个参数是 Excel 文件的输出流。
- 第二个参数是 Excel 文件的类型,这里我们指定为
.xlsx
格式。然后,我们使用
EasyExcel
工具类提供的writerSheet
方法创建了一个WriteSheet
对象,并通过调用head
方法指定表头的类为Student.class
。然后,我们调用write
方法将数据写入 Excel 文件。最后,我们调用
finish
方法保存 Excel 文件。
easyexcel导入导出代码示例
以下是使用 EasyExcel 进行导入和导出的示例代码:
导出 Excel:
// 定义 Excel 表头 |
导入 Excel:
// 构造 ExcelReader 对象 |
其中,ExcelStudentDTO
是一个用于接收 Excel 数据的类,定义如下:
|
补充一些说明:
ExcelWriter
和ExcelReader
分别代表导出和导入 Excel 的对象,都需要通过EasyExcel
工具类的静态方法write()
和read()
进行构造。Sheet
代表一个 Excel 的 Sheet,其中第一个参数是 sheet 的序号,第二个参数是从第几行开始读取/写入,默认是从第 0 行开始。AnalysisEventListener
是 EasyExcel 提供的一个读取监听器,需要实现其中的invoke()
方法,将读取到的数据转换为需要的对象,并插入到数据库中。ExcelProperty
是 EasyExcel 提供的一个注解,用于标识 Excel 表头中的每个字段。autoCloseStream()
方法用于在写入 Excel 数据完成后自动关闭输出流。excelType()
方法用于指定导出的 Excel 类型,支持ExcelTypeEnum.XLS
和ExcelTypeEnum.XLSX
。finish()
方法用于关闭 ExcelWriter 或 ExcelReader 对象,释放资源。需要注意的是,以上代码仅为示例代码,实际应用中需要根据具体需求进行修改和优化。同时,EasyExcel 还提供了很多其他功能,例如设置样式、校验数据、读取大文件等,具体使用方法可以参考官方文档。
JAVA规范中常用的设计模式
- 单例模式(Singleton Pattern):确保一个类只有一个实例,并提供全局访问点。
- 工厂模式(Factory Pattern):定义一个用于创建对象的接口,让子类决定实例化哪一个类。
- 抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
- 建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
- 原型模式(Prototype Pattern):通过复制现有的实例来创建新的实例。
- 适配器模式(Adapter Pattern):将一个类的接口转换成客户希望的另外一个接口。
- 桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。
- 装饰器模式(Decorator Pattern):动态地给一个对象添加一些额外的职责,同时又不改变其结构。
- 外观模式(Facade Pattern):为一组子系统提供一个一致的接口,定义一个高层接口,使得这些子系统更加容易使用。
- 享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度的对象。
- 组合模式(Composite Pattern):将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
- 代理模式(Proxy Pattern):为其他对象提供一种代理以控制对这个对象的访问。
以上这些设计模式都是常用的经典设计模式,每个模式都有特定的应用场景和解决问题的方法。在实际编程中,根据具体需求选择适合的设计模式,可以提高代码的可维护性和可扩展性。
instanceof 的概念和用法
instanceof
是 Java 中的一个关键字,用于判断一个对象是否属于某个特定的类或其子类的实例。它的用法如下:
boolean result = object instanceof SomeClass; |
其中
object
是需要判断的对象,SomeClass
是需要判断的类,result
表示判断结果。如果object
是SomeClass
类的实例或其子类的实例,则返回true
,否则返回false
。以下是一个使用
instanceof
判断对象类型的示例:
public class Animal { |
在上面的示例中,我们创建了三个类
Animal
、Cat
和Dog
,其中Cat
和Dog
都是Animal
的子类。我们创建了两个对象animal1
和animal2
,分别是Cat
和Dog
的实例。然后使用instanceof
判断它们的类型,输出结果如注释所示。在实际编程中,
instanceof
通常用于判断一个对象是否属于某个特定的类或其子类的实例,以便进行相应的操作。它也常用于处理异常,比如在捕获异常时,可以使用instanceof
判断抛出的异常是否是某个特定类型的异常,从而进行相应的处理。
两个对象的 hashCode() 相同,则 equals() 不一定为 true
不一定。虽然
hashCode()
相同的两个对象equals()
为true
是可能的,但是也有可能不相等。这是因为hashCode()
是将对象映射到整数值的一种算法,而equals()
是用于比较两个对象是否相等的方法,二者并不是等价的。在 Java 中,如果两个对象
equals()
返回true
,则它们的hashCode()
应该相同。这是因为在 HashMap、HashSet 等集合中,对象的存储和查找都是依据hashCode()
和equals()
方法来实现的,如果hashCode()
不同,那么就不可能查找到该对象,而如果equals()
不同,则不符合对象相等的原则。但是如果两个对象的
hashCode()
相同,它们并不一定相等。这是因为在散列表(如 HashMap、HashSet)中,会出现哈希冲突的情况,即不同的对象被映射到了同一个散列表桶中,此时需要使用equals()
进一步比较这些对象是否相等。以下是一个
hashCode()
相同但equals()
不同的示例:
public class MyClass { |
在上面的示例中,我们定义了一个
MyClass
类,并重写了它的hashCode()
和equals()
方法。其中hashCode()
方法只使用了对象的id
属性,而equals()
方法比较了id
和name
两个属性。在main()
方法中,我们创建了两个对象obj1
和obj2
,它们的id
相同,但是name
不同。虽然它们的hashCode()
相同,但是由于equals()
方法的实现,它们并不相等。
String的概念和用法以及原理
在 Java 中,
String
是一个类,用于表示字符串类型的数据。String
对象是不可变的,即一旦创建,它的值就不能被修改。String
对象可以通过字面值或者构造方法来创建。例如,使用字面值创建一个字符串对象:
也可以使用构造方法来创建一个字符串对象:
String s1 = "hello"; |
String
类提供了许多方法,用于对字符串对象进行操作,比如拼接、截取、查找、替换等等。在 Java 中,字符串类型的底层实现是通过字符数组来实现的。
String
类中有一个名为value
的字符数组,用于存储字符串的字符序列。例如,上面创建的字符串s1
对应的字符数组就是{'h', 'e', 'l', 'l', 'o'}
。由于字符串对象是不可变的,因此每当对字符串对象进行修改时,都会创建一个新的字符串对象,而原来的字符串对象则不会发生改变。例如,对字符串
s1
进行拼接操作:
s1 = s1 + " world"; |
这个操作会创建一个新的字符串对象
"hello world"
,而原来的字符串对象"hello"
则不会发生改变。Java 还提供了一个特殊的字符串池(String Pool)机制,用于在创建字符串对象时进行优化,以减少内存占用。当使用字面值创建字符串对象时,Java 会首先在字符串池中查找是否已经存在相同的字符串,如果存在,则直接返回字符串池中的对象,否则创建一个新的对象并存放在字符串池中。例如:
String s3 = "hello"; |
在这个例子中,
s3
和s4
其实是同一个对象,因为它们都指向字符串池中的"hello"
。这种字符串池机制可以提高字符串对象的重用率,从而降低内存占用。
String的源码解析
String是Java语言中非常重要的类,用来表示不可变的Unicode字符序列,也是Java中使用最为频繁的类之一。下面是Java 11中String类的部分源码讲解:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { |
String类的源码解析还可以从以下几个方面深入
- String的不可变性
String类中的char[]数组是被final修饰的,也就是说String对象一旦被创建,其中的字符序列就不能再被修改了。如果对一个字符串进行拼接、替换等操作,实际上是创建了一个新的String对象,而原来的String对象并没有被修改。
- 字符串常量池
在Java中,字符串常量池是一块特殊的内存区域,用于存储字符串常量。由于String对象是不可变的,因此可以将相同的字符串共享在字符串常量池中,以节省内存空间。当创建一个字符串对象时,JVM会先在字符串常量池中查找是否存在相同的字符串,如果存在则直接返回引用,否则在字符串常量池中新建一个字符串并返回其引用。
- String的性能问题
由于String对象的不可变性,当需要对字符串进行拼接、替换等操作时,每次都会创建一个新的String对象,这会导致频繁的内存分配和回收,影响程序的性能。为了解决这个问题,Java提供了StringBuilder和StringBuffer两个类,它们是可变的字符串类,可以高效地进行字符串操作。
- String的编码问题
在Java中,字符串默认使用Unicode编码,每个字符占用两个字节。但是在实际应用中,可能需要使用其他编码方式,比如GBK、UTF-8等。为了解决这个问题,Java提供了String类的getBytes方法,可以将字符串转换成指定编码的字节数组。同时,还可以使用String类的构造方法将字节数组转换成字符串。
综上所述,String是Java中非常重要的一个类,它提供了丰富的字符串操作方法,并具有不可变性和字符串常量池等特性。但是需要注意的是,频繁的字符串操作可能会导致性能问题,需要使用可变的字符串类来提高效率。同时,字符串的编码问题也需要注意,尤其是在多语言环境下,需要使用合适的编码方式来避免出现乱码等问题。
Stream流式操作 API
在 Java 8 中,集合框架新增了
stream
流式操作 API,它提供了一种便捷的方式将List
、Set
、Map
等集合类型转为另一种集合类型或数组。具体来说,可以使用
stream
方法将集合转为一个流,然后调用collect
方法将流转为指定的集合类型。例如,将List
转为Set
可以使用以下代码:
List<String> list = Arrays.asList("foo", "bar", "baz"); |
上述代码使用了 Java 8 中的
Stream
和Collectors
类,stream()
方法将List
转为一个流,Collectors.toSet()
方法将流转为Set
类型。类似地,还可以将List
转为数组:
List<String> list = Arrays.asList("foo", "bar", "baz"); |
上述代码中,
toArray()
方法将流转为数组,使用方法引用String[]::new
来指定数组类型。除了
Collectors.toSet()
和toArray()
方法外,Collectors
类中还提供了一些其他方法,例如将流转为List
、Map
等。需要注意的是,
Collectors.toSet()
和Collectors.toList()
方法返回的集合类型是不可变的,如果需要修改集合,可以使用HashSet
、ArrayList
等可变集合类型。
Arrays工具类的常用的方法
Arrays 是 Java 中一个用于操作数组的工具类,提供了很多方便的方法。以下是 Arrays 常用的一些方法:
toString()
:将数组转换成字符串形式并返回;equals(Object[] a, Object[] b)
:判断两个数组是否相等;sort(T[] a)
:对数组进行排序;binarySearch(T[] a, T key)
:在有序数组中搜索指定的值;fill(T[] a, T val)
:将数组的所有元素都设置为指定的值;copyOf(T[] original, int newLength)
:将原数组复制到一个新数组中,并返回新数组;asList(T... a)
:将一个数组转换成一个 List 集合。需要注意的是,
asList()
方法返回的 List 集合不支持添加或删除元素,只能通过 set() 方法修改元素值。除了上述方法,Arrays 还提供了其他一些方法,例如
sort()
方法的重载版本,以及对基本类型数组的支持方法。