字符串相关类
String
String
是一个final
类,是不可变的字符序列- 字符串是常量,值在创建之后就不能改变
- 在底层中,
String
对象的字符内容是存储在一个字符数组value[]
中的 - 内存解析:
对上图的注释:
1.方法区不会存储相同的常量值
2.对字符串字面值的任何更改,都会直接创建新的字符串值,而不是在原来的字符串上进行增删改
- String对象的创建
//本质上是给底层的char[]赋值
String s = "hello";
String s1 = new String();
String s2 = new String(String original);
String s3 = new String(char[] a);
String s4 = new String(char[] a, int startIndex, int count);//截取一部分
- 关于默认为空和和传入为空字符串的内存解析
- 关于字面量值直接创建和
new
方法创建的内存解析
- 字符串对象是如何创建、存储的
关于字符串存储内存解析的小结:
1.常量与常量的拼接结果在常量池,常量池不会存储相同的内容
2.只要其中有一个结果是变量,结果就在堆中
3.如果拼接的结果调用intern()
方法,返回值就在常量池中
- 练习
解析:首先明确,
String
比较特殊,是final
修饰的,是不可变的。change
方法中,str
将引用地址赋给形参,形参也指向常量池中的"good
",str = "test ok"
这句,在常量池中创建了"test ok
",并把形参指向"test ok
",属性str
的指向"good
"并未改变。
char[]
可变,把引用地址赋给形参,所以形参的改变直接引起实参的改变,{'t', 'e', 's', 't'}
变为{'b', 'e', 's', 't'}
参考链接
String常用方法
int length()
:返回字符串长度char charAt(int index)
:返回索引处的字符boolean isEmpty()
:判断是否是空字符串String toLowerCase()
:将所有字符转换为小写String toUpperCase()
:将所有字符转换为大写String trim()
:返回字符串的副本,忽略首尾空白(不改变原本字符串)boolean equals(Object obj)
:比较字符串内容是否相同boolean equalsIgnoreCase(String anotherString)
:忽略大小写比较内容是否相同String concat(String str)
:将指定字符串连接到此字符串的结尾,相当于“+”int compareTo(String anotherString)
:比较两个字符串的大小String substring(int beginIndex)
:返回一个新的字符串,它是此字符串的从beginIndex
开始截取到最后的一个子字符串String substring(int beginIndex, int endIndex)
:返回一个新的字符串,它是此字符串的从beginIndex
开始截取到endIndex
(不包含)的一个子字符串boolean endsWith(String suffix)
:测试此字符串是否以指定的后缀结束boolean startsWith(String prefix)
:测试此字符串是否以指定的前缀开始boolean startsWith(String prefix, int toffset)
:测试此字符串从指定索引开始的字符串是否以指定的前缀开始boolean contains(CharSequence s)
:测试字符串是否包含指定的char
值序列int indexOf(String str)
:返回指定子字符串在此字符串中第一次出现处的索引int indexOf(String str, int fromIndex)
:返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始int lastIndexOf(String str)
:返回指定子字符串在此字符串中最右边出现处的索引int lastIndexOf(String str, int fromIndex)
:返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
注:indexOf和lastIndexOf方法如果未找到都是返回-1
String replace(char oldChar, char newChar)
:返回一个新的字符串,它是通过用newChar
替换此字符串中出现的所有oldChar
得到的String replace(CharSequence target, CharSequence replacement)
:使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串String replaceAll(String regex, String replacement)
:使用给定的replacement
替换此字符串所有匹配给定的正则表达式的子字符串String replaceFirst(String regex, String replacement)
:使用给定的replacement
替换此字符串匹配给定的正则表达式的第一个子字符串boolean matches(String regex)
:告知此字符串是否匹配给定的正则表达式- ```String[] split(String regex)``:根据给定正则表达式的匹配拆分此字符串
String[] split(String regex, int limit)
:根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中
- 字符串->基本数据类型、包装类:
integer.parseXxx(String s)
- 基本数据类型->字符串:
String.valueOf(xxx x)
- 字符数组->字符串:
String(char[])
、String(char[],int offset,int length)
- 字符串->字符数组:
string.toCharArray()
、string.getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
- 字节数组->字符串:
String(byte[])
、String(byte[],int offset,int length)
- 字符串->字节数组:
string.getBytes()
、string.getBytes(String charsetName)
练习:
- 模拟一个trim方法,去除字符串两端的空格。
- 将一个字符串进行反转。将字符串中指定部分进行反转。比如“abcdefg”
反转为”abfedcg”- 获取一个字符串在另一个字符串中出现的次数。
比如:获取“ ab”在 “abkkcadkabkebfkabkskab” 中出现的次数- 获取两个字符串中最大相同子串。比如:
str1 = "abcwerthelloyuiodef“;str2 = “cvhellobnm”
提示:将短的那个串进行长度依次递减的子串与较长的串比较。- 对字符串中字符进行自然顺序排序。
提示:1)字符串变成字符数组。 2)对数组排序,选择,冒泡,Arrays.sort();。3)将排序后的数组变成字符串。
StringBuffer
- 是可变的字符序列,没有
final
声明,可以对字符串内容进行增删改,不会产生新的对象 - 大多数方法与
String
相同 - 作为参数传递时,方法内部可以改变值
StringBuffer
对象必须使用构造器生成,有三个构造器StringBuffer()
:厨师容量为16的字符串缓冲区StringBuffer(int size)
:构造指定容量的字符串缓冲区StringBuffer(String str)
:将内容初始化为指定字符串内容
StringBuffer的常用方法
append(xxx)
:字符串拼接,接在尾部delete(int start, int end)
:删除指定位置的内容replace(int start, int end, String str)
:把[start, end)位置替换为strinsert(int offset, xxx)
:在指定位置插入xxxreverse()
:把当前字符序列逆转
以下自己看吧,不写注释了
int indexOf(String str)
String substring(int start,int end)
int length()
char charAt(int n )
void setCharAt(int n ,char ch)
- 当
append
和insert
时,如果原来value
数组长度不够,可扩容StringBuffer()
支持方法链操作。比如s
是一个StringBuffer()
,s.append(xxx)
后返回修改后的s
,可以继续追加各种操作方法
StringBuilder
StringBuilder
和StringBuffer
非常类似,也是可变字符序列,提供的相关方法也一样。区别见下面
StringBuffer | 可变字符序列、效率低、线程安全 |
---|---|
StringBuilder | 可变字符序列、效率高、线程不安全 |
String
、StringBuffer
、StringBuilder
一般选StringBuilder
,因为执行效率相当高
例题:
public static void main(String[] args) {
String str = null;
System.out.println(str);//null
// System.out.println(str.length());//没有长度,会报错,空指针异常
StringBuffer sb = new StringBuffer();
sb.append(str);
System.out.println(sb);//null
System.out.println(sb.length());//4
// StringBuffer sb2 = new StringBuffer(null);//会报错,空指针异常
// StringBuffer sb1 = new StringBuffer(str);//会报错,空指针异常
}
//因为根据StringBuffer源码,如果str是null,就賦予str = "null" 这个字符串,而不是null了.详见下方链接和源码
StringBuffer
中append
的源码:
append接收到一个null的话,就会把它转换为"null"
StringBuffer
中构造器的源码:
传入的str是null,还要去调它的length,肯定会报空指针异常
日期时间API
JDK 8之前的日期时间API
java.lang.System
提供的public static long currentTimeMillis()
用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差java.util.Date
类,表示特定的瞬间,精确到毫秒- 构造器:
Date()
Date(long date)
:long date
用来指定毫秒数以设置时间
- 常用方法:
getTime()
:返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数toString()
: 把此 Date 对象转换为以下形式的String
:dow mon dd hh:mm:ss zzz yyyy
其中:dow
是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat),zzz
是时间标准。具体的toString
方法可以自己重写
- 构造器:
还有一些API,比如SimpleDateFormat、Calendar等,不想记录了,因为JDK 8提供了新的更好用的方法。以后碰到再学习吧
JDK 8提供的新的日期时间API
- 最常用的API:
java.time
(基础包)、java.time.format
(格式化和解析时间和日期) - 常用类:直接说最常用类吧,
LocalDate
、LocalTime
、LocalDateTime
方法 | 描述 |
---|---|
now() | static方法,获取当前的日期、时间、日期+时间 |
of(xxx…) | static方法,设置指定的年、月、日、时、分、秒 |
get各种年月日时分秒的组合() | 获取相关值 |
with各种年月日时分秒的组合(x) | 设置相关值 |
plus各种年月日时分秒的组合(x) | 当前对象添加对应的属性值 |
minus各种年月日时分秒的组合(x) | 当前对象减去对应的属性值 |
//now():获取当前的日期、时间、日期+时间
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
//of():设置指定的年、月、日、时、分、秒。没有偏移量
LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 6, 13, 23, 43);
//getXxx():获取相关的属性
System.out.println(localDateTime.getDayOfMonth());
System.out.println(localDateTime.getDayOfWeek());
System.out.println(localDateTime.getMonth());
System.out.println(localDateTime.getMonthValue());
System.out.println(localDateTime.getMinute());
//withXxx():设置相关的属性
LocalDate localDate1 = localDate.withDayOfMonth(22);
LocalDateTime localDateTime2 = localDateTime.withHour(4);
LocalDateTime localDateTime3 = localDateTime.plusMonths(3);
LocalDateTime localDateTime4 = localDateTime.minusDays(6);
Instant
方法 | 描述 |
---|---|
now() | static方法,获取本初子午线对应的标准时间(比北京时间慢8个小时) |
ofEpochMilli(long x) | 通过给定的毫秒数,获取Instant实例 |
atOffset(ZoneOffset offset) | 结合即时的偏移来创建一个 OffsetDateTime。{比如,instant.atOffset(ZoneOffset.ofHours(8)) ,设置了+8的偏移,把子午时间变成北京时间} |
toEpochMilli() | 获取自1970年1月1日0时0分0秒(UTC)开始的毫秒数 |
//now():获取本初子午线对应的标准时间
Instant instant = Instant.now();
//atOffset():添加时间的偏移量
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
//toEpochMilli():获取自1970年1月1日0时0分0秒(UTC)开始的毫秒数 ---> Date类的getTime()
long milli = instant.toEpochMilli();
//ofEpochMilli():通过给定的毫秒数,获取Instant实例 -->Date(long millis)
Instant instant1 = Instant.ofEpochMilli(1550475314878L);
格式化与解析日期时间
// 方式一:预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
//格式化:日期-->字符串
LocalDateTime localDateTime = LocalDateTime.now();
String str1 = formatter.format(localDateTime);
System.out.println(localDateTime);
System.out.println(str1);//2019-02-18T15:42:18.797
//解析:字符串 -->日期
TemporalAccessor parse = formatter.parse("2019-02-18T15:42:18.797");
System.out.println(parse);
// 方式二:
// 本地化相关的格式。如:ofLocalizedDateTime()
// FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT :适用于LocalDateTime
DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
//格式化
String str2 = formatter1.format(localDateTime);
System.out.println(str2);//2019年2月18日 下午03时47分16秒
// 本地化相关的格式。如:ofLocalizedDate()
// FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT : 适用于LocalDate
DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
//格式化
String str3 = formatter2.format(LocalDate.now());
System.out.println(str3);//2019-2-18
// 重点: 方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
//格式化
String str4 = formatter3.format(LocalDateTime.now());
System.out.println(str4);//2019-02-18 03:52:09
//解析
TemporalAccessor accessor = formatter3.parse("2019-02-18 03:52:09");
System.out.println(accessor);
其他的就不说了,碰到再学吧
Java比较器
Java实现对象的排序有两种方式:
- 自然排序:
java.lang.Comparable
- 定制排序:
java.util.Comparator
像String
、包装类等,都实现并重写了Comparable
接口,可以直接调用。
自然排序
- 实现
Comparable
的类必须实现compareTo(Object obj)
方法,两个对象即通过compareTo(Object obj)
方法的返回值来比较大小。如果当前对象this
大于形参对象obj
,则返回正整数,如果当前对象this
小于形参对象obj
,则返回负整数,如果当前对象this
等于形参对象obj
,则返回零 - 实现
Comparable
接口的对象列表(和数组)可以通过Collections.sort
或Arrays.sort
进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器 - 对于类
C
的每一个e1
和e2
来说,当且仅当e1.compareTo(e2) == 0
与e1.equals(e2)
具有相同的boolean
值时,类C
的自然排序才叫做与equals
一致。建议(虽然不是必需的)最好使自然排序与equals
一致。 - 对于自定义类来说,如果需要排序,我们可以让自定义类实现
Comparable
接口,重写compareTo(obj)
方法。在compareTo(obj)
方法中指明如何排序
class Goods implements Comparable {
private String name;
private double price;
//按照价格,比较商品的大小
@Override
public int compareTo(Object o) {
if(o instanceof Goods) {
Goods other = (Goods) o;
if (this.price > other.price) {
return 1;
} else if (this.price < other.price) {
return -1;
}
return 0;
//这里代表price相等。如果需要的话,可以在此定义新的排序方法,比如按name排序,来替换return 0.
}
//方式二:return Double.compare(this.price, other.price);
throw new RuntimeException("输入的数据类型不一致");
}
//构造器、getter、setter、toString()方法略
}
public class ComparableTest{
public static void main(String[] args) {
Goods[] all = new Goods[4];
all[0] = new Goods("《红楼梦》", 100);
all[1] = new Goods("《西游记》", 80);
all[2] = new Goods("《三国演义》", 140);
all[3] = new Goods("《水浒传》", 120);
Arrays.sort(all);
System.out.println(Arrays.toString(all));
}
}
定制排序
- 当元素的类型没有实现
java.lang.Comparable
接口而又不方便修改代码, 或者实现了java.lang.Comparable
接口的排序规则不适合当前的操作,那 么可以考虑使用Comparator
的对象来排序,强行对多个对象进行整体排序的比较。 - 重写
compare(Object o1,Object o2)
方法,比较o1
和o2
的大小:如果方法返回正整数,则表示o1
大于o2
;如果返回0
,表示相等;返回负整数,表示o1
小于o2
Goods[] all = new Goods[4];
all[0] = new Goods("War and Peace", 100);
all[1] = new Goods("Childhood", 80);
all[2] = new Goods("Scarlet and Black", 140);
all[3] = new Goods("Notre Dame de Paris", 120);
Arrays.sort(all, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Goods g1 = (Goods) o1;
Goods g2 = (Goods) o2;
return g1.getName().compareTo(g2.getName());
}
});
Comparable与Comparator的对比
Comparable
接口的方式一旦确定,保证Comparable
接口实现类的对象在任何位置都可以比较大小。Comparator
接口属于临时性的比较。
BigInteger 和 BigDecimal
BigInteger
可以表示不可变的任意精度的整数BigDecimal
支持不可变的、任意精度的有符号十进制定点数- 常用方法
- 代码示例
public void test2() {
BigInteger bi = new BigInteger("1243324112234324324325235245346567657653");
BigDecimal bd = new BigDecimal("12435.351");
BigDecimal bd2 = new BigDecimal("11");
System.out.println(bi);
System.out.println(bd.divide(bd2));
System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP));
System.out.println(bd.divide(bd2, 25, BigDecimal.ROUND_HALF_UP));
}
枚举类
- 类的对象只有有限个,确定的。当要定义一组常量时,建议使用枚举类
- 如果枚举类中只有一个对象,则可以作为单例模式的实现方式。
- 可以使用
enum
关键字定义枚举类。 - 使用
enum
定义的枚举类默认继承了java.lang.Enum
类,因此不能再继承其他类 - 枚举类的构造器只能使用
private
权限修饰符 - 枚举类的所有实例必须在枚举类中显式列出(, 分隔 ; 结尾)。列出的实例系统会自动添加
public static final
修饰 - 必须在枚举类的第一行声明枚举类对象
public enum SeasonEnum {
SPRING("春天","春风又绿江南岸"),
SUMMER("夏天","映日荷花别样红"),
AUTUMN("秋天","秋水共长天一色"),
WINTER("冬天","窗含西岭千秋雪");
private final String seasonName;
private final String seasonDesc;
private SeasonEnum(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}
- 可以在
switch
表达式中使用Enum
定义的枚举类的对象作为表达式,case
子句可以直接使用枚举值的名字, 无需添加枚举类作为限定 - Enum类中的常用方法:
values()
方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。valueOf(String str)
:可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException
。toString()
:返回当前枚举类对象常量的名称
- 实现接口的枚举类
- 枚举类可以实现一个或多个接口
- 若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只要统一实现该方法即可
- 若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式, 则可以让每个枚举值分别来实现该方法
interface Info{
void show();
}
//使用enum关键字枚举类
enum Season1 implements Info{
//1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
SPRING("春天","春暖花开"){
@Override
public void show() {
System.out.println("春天在哪里?");
}
},
SUMMER("夏天","夏日炎炎"){
@Override
public void show() {
System.out.println("宁夏");
}
},
AUTUMN("秋天","秋高气爽"){
@Override
public void show() {
System.out.println("秋天不回来");
}
},
WINTER("冬天","冰天雪地"){
@Override
public void show() {
System.out.println("大约在冬季");
}
};
//2.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
//2.私有化类的构造器,并给对象属性赋值
private Season1(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//4.其他诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
// //4.其他诉求1:提供toString()
//
// @Override
// public String toString() {
// return "Season1{" +
// "seasonName='" + seasonName + '\'' +
// ", seasonDesc='" + seasonDesc + '\'' +
// '}';
// }
// @Override
// public void show() {
// System.out.println("这是一个季节");
// }
}
注解(Annotation)
Annotation
其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用Annotation
,- 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。
- JDK内置的三个基本注解
@Override
: 限定重写父类方法, 该注解只能用于方法@Deprecated
: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择@SuppressWarnings
: 抑制编译器警告
就不多说了,遇到再说吧