本次整理的是枚举类、注解、异常、常用类的知识。
没看过之前笔记的内容,或者学起来有些吃力的朋友们,可以看我之前整理的笔记。
非常详细易懂,对0基础小白友好:
Java面向对象(初级)笔记整理/图解快速回顾/图解快速入门(一)
一、枚举类
1、什么是枚举类?
在一个类前使用enum关键字修饰的类,我们就把它叫做枚举类。
使用enum关键字修饰后,不可以使用class声明枚举类
例如:
正确范例:
enum season {
//可以在里面定义一些枚举类的对象,
//语法如下,直接声明,不使用new关键字
SPRING, SUMMER, AUTUMN, WINTER;//这些都是对象
}
2、为什么要有枚举类?
枚举类的作用就是让一个类的对象的数量以及个体得以限制,防止出现不必要的混乱。
例如,想像以下场景:
我定义了一个季节类(season),之后在主方法中创建四个对象:春夏秋冬,四个季节对象。
public class Enum_ {
public static void main(String[] args) {
//四个季节对象
season spring = new season("温和", "春天");
season summer = new season("炎热", "夏天");
season autumn = new season("凉爽", "秋天");
season winter = new season("寒冷", "冬天");
}
}
//季节类
class season {
//两个属性 温度 名称
String temperature;
String name;
//构造器
public season(String temperature, String name) {
this.temperature = temperature;
this.name = name;
}
}
我们知道,季节只有四个,而没有第五个和第六个,而我们的season类并没有设置一些限制,让我们的季节不可创建超过四个对象。所以以下的情况是可能出现的:
这显然是不合理的。
其中一种解决方法是:我们可以自定义season类只能有四个对象,让对象存储在类中,并将构造器私有化,有点像之前的单例模式:
//季节类
class season {
//两个属性 温度 名称
String temperature;
String name;
//四个季节对象 在类中创建
season spring = new season("温和", "春天");
season summer = new season("炎热", "夏天");
season autumn = new season("凉爽", "秋天");
season winter = new season("寒冷", "冬天");
//构造器私有化 不可以在本类之外新创建对象
private season(String temperature, String name) {
this.temperature = temperature;
this.name = name;
}
}
如代码所示,这样将构造器私有化,并直接在本类中创建对象,我们就不可以在外部新创建季节对象,所以就保护了四个季节的完整性。
而我们第二个方法就是使用枚举类:
使用枚举类在内部创建对象时,直接输入对象名+【参数列表】即可,中间使用“,”分割。
(后面细节部分再详细说)
//季节类
enum season {
//四个季节对象
SPRING("温和", "春天"),
SUMMER("炎热", "夏天"),
AUTUMN("凉爽", "秋天"),
WINTER("寒冷", "冬天");
//两个属性 温度 名称
String temperature;
String name;
//构造器
season(String temperature, String name) {
this.temperature = temperature;
this.name = name;
}
}
枚举类同样不可以在其他类中创建其他的枚举类对象,所以也实现了限制:
3、枚举类使用细节
(1)实现枚举类时会自动继承Enum类,并且是final类型(即不可继承也不可被继承)。
如图:
(2)创建对象时可以简化
使用语法如下:
【对象名】+【参数列表】, 【对象名】+【参数列表】;
如此创建出来的对象,都是public static final类型的。
此外如果使用的是无参构造器,参数列表则也可以省略:
【对象名】 ,【对象名】;
(3)枚举类对象,必须放在枚举类的首行。
如图:
(4)枚举类可以实现接口
(5)枚举类的方法
这里我放个图,自己看看就好,不用记。
二、注解
1、什么是注解?
如果看过之前笔记,或者有些基础的各位,可能对注解并不陌生:
在之前,我们都是将其当做一种注释一样的东西,但是如果往深处说,注解和注释并不完全相同。
但是就目前阶段,我们把注解当做是一种更高级的注释,完全可以。
2、注解的作用
例如@Override注解表示方法重写,可以让编译器在编译阶段就检查出来,子类是否真正的重写了父类的方法。
但如果加入了@Override注解,但是其实并没有重写父类的方法,编译器就会报错,不允许程序运行。
而如果子类重写了父类的方法,但是我们没有加入@Override注解,那么依然可以正常运行。
总结一下就是,假如注解可以帮助编译器和程序员,更好的阅读和避免代码的出错。
3、常见的注解
(1)@Override: 限定某个方法,是重写父类方法, 该注解只能用于方法
(2)@Deprecated: 用于表示某个程序元素(类, 方法等)已过时
在jdk包中,会有很多Java设计者们给我们提供的类和方法,有些方法已经过时,就会有这个注解
(3)@SuppressWarnings: 抑制编译器警告
有一些不关紧要的警告,可以使用这个来让警告抑制掉。
4、元注解
有一些注解是可以修饰其他注解的,这些直接叫元注解。
如:
1) Retention //指定注解的作用范围,三种 SOURCE,CLASS,RUNTIME
2) Target // 指定注解可以在哪些地方使用
3) Documented //指定该注解是否会在 javadoc 体现
4) Inherited //子类会继承父类注解
这些认识一下就可以,目的是在看源码的时候不要害怕,在开发中用的少
三、异常(Exception)
1、什么是异常?
异常是Java程序员必须要面对的,可能出现的程序问题,它不同于编译的语法错误,而往往是一些运行过程或编译过程中可能出现的例外(Exception)情况,需要程序员在这些例外情况出现时,在代码中进行相应的处理。
2、异常的类型
(1)运行异常
如图所示,我定义了一个方法,方法返回的是两个数相除得到的结果:
public class Exception_ {
public static void main(String[] args) {
//这里我故意让除数等于零,这样会出现异常
Math_.divide(2,0);
}
}
class Math_ {
//除法方法
public static int divide(int a, int b) {
return a / b;
}
}
如果我填入的除数,等于0,我们知道一个数是不可以除以0的,因为结果是无穷。
运行结果,会抛出异常 :
这种在程序运行阶段出现的异常就是运行异常。
(2)编译异常
编译异常在我们编写代码的时候就会出现,出现编译异常时,编译器就会让程序员来处理这个异常,不然就不可以让代码运行。
如,我这里写了一个操作文件的代码(现在看不懂没关系,后面IO流再讲给各位)
这里就会出现一些编译异常:
处理异常,我们使用try-catch处理,(后面细节讲):
3、异常的继承结构图(记住)
可以看到,所有的异常类,都继承了Exception类,而Exception类又继承了Throwable类,Throwable类实现了Serialisable接口。
(实现了Serialisable接口就是可以作为文件传输、网络传输的。)
可以注意到,所有的运行时异常,都继承了RuntimeException类,而编译异常则是直接继承了Exception类,这个要特别注意。
4、try-catch
try-catch块可以用来捕获异常,当try包含的代码内出现了异常(编译异常或运行异常),那么代码就会立刻跳转到catch块中的代码中,不会再执行出现异常后的代码。
例如:
public class try_catch {
public static void main(String[] args) {
//这里我故意让除数等于零,这样会出现异常
//这时,我使用try块来包裹这个代码
try {
Math_.divide(2,0);
//这里如果代码没有异常,就会继续执行下面的代码
System.out.println("没有出现异常");
} catch (Exception e) {
System.out.println("出现了异常,直接跳转到catch块中");
e.printStackTrace();
}
}
}
class Math_ {
//除法方法
public static int divide(int a, int b) {
return a / b;
}
}
这里我把除数设置为0,所以会出现异常,直接跳转到catch块中的代码,而不会再执行try块的代码
运行结果:
如果不除以0,就会正常执行,这里我改为2 / 2:
public class try_catch {
public static void main(String[] args) {
//这里我故意让除数等于零,这样会出现异常
//这时,我使用try块来包裹这个代码
try {
Math_.divide(2,2);
//这里如果代码没有异常,就会继续执行下面的代码
System.out.println("没有出现异常");
} catch (Exception e) {
System.out.println("出现了异常,直接跳转到catch块中");
e.printStackTrace();
}
}
}
class Math_ {
//除法方法
public static int divide(int a, int b) {
return a / b;
}
}
我们再来看看catch块中的语法:
catch (Exception e) {
e.printStackTrace();
}
这里的catch块有一个参数,传入的是一个Exception对象 e,这个e就是我们在try块中捕获的异常。(Exception类,是Java中自带的一个类,就是专门用于捕获异常、处理异常的,从上面的异常结构图也可以看到)
这时我们就可以对异常对象e进行处理,例如上面的代码中就是把异常对象e的信息打印了出来。
当然,我们也可以缩小catch处理的异常范围,上面的Exception是所有Exception的父类,所以范围是最大的。
如果我们缩小一下范围,如果捕获到相应的异常也是可以进行处理的:
public class try_catch {
public static void main(String[] args) {
//这里我故意让除数等于零,这样会出现异常
//这时,我使用try块来包裹这个代码
try {
Math_.divide(2,0);
//这里如果代码没有异常,就会继续执行下面的代码
System.out.println("没有出现异常");
} catch (ArithmeticException e) {
System.out.println("出现了异常,直接跳转到catch块中");
e.printStackTrace();
}
}
}
class Math_ {
//除法方法
public static int divide(int a, int b) {
return a / b;
}
}
一个try catch可以有多个catch,从而处理不同类型的异常。
此时,多个catch块需要遵守以下规则:
(1)catch子类的异常必须放在父类异常之前
(2)不可以catch相同的异常,因为没有意义
(3)try捕获到异常时,会按顺序依次匹配catch的异常,如果匹配到,就会执行该catch的异常,并跳过接下来的catch块。
那么现在有一个问题:
如果我们try中捕获到的异常和catch块中需要处理的异常不相同会怎么办呢?
也就是,有异常产生,但是没有相应的catch处理会发生什麽?
请看下面的例子:
我故意的把catch块中需要处理的异常设置为ClassCastException,在这个例子中,是不会出现类型转换异常的,所以我们捕获到了算数异常后,但是不会被catch块处理,因为捕获的异常不是ClassCastException。
public class try_catch {
public static void main(String[] args) {
//这里我故意让除数等于零,这样会出现异常
//这时,我使用try块来包裹这个代码
try {
Math_.divide(2,0);
//这里如果代码没有异常,就会继续执行下面的代码
System.out.println("没有出现异常");
} catch (ClassCastException e) {//这里我故意设置为ClassCastException
System.out.println("出现了异常,直接跳转到catch块中");
e.printStackTrace();
}
}
}
class Math_ {
//除法方法
public static int divide(int a, int b) {
return a / b;
}
}
运行结果:
可以看到,出现了一个特殊的情况,代码既没有执行try块中发生异常后的语句,也没有进入到catch块中处理,因为没有输出我们设置的这两句话:
"没有出现异常"
"出现了异常,直接跳转到catch块中"
那么,代码是怎么运行的呢?
因为我们没有处理这个算数异常,所以方法就会默认的把这个异常抛出(Throw)。
5、抛出异常Throws
当一个运行异常没有被处理时,就会默认被抛出。
编译异常需要在运行前就选择 是要Throws抛出,还是要进入try-catch中处理。
想要手动Throw异常,需要在类的后面添加 【throws关键字】 + 【异常类型】
被抛出的异常会沿着栈中的方法,进入上一个调用的方法,如图:
如上图,f1出现了异常,并选择抛出,抛出的异常进入了f2中。
此时,f2就出现了异常,f2需要进行try-catch处理,或者进行抛出。
如果f2进行了try-catch处理,那么这个异常也就处理完毕了,就可以运行接下来的代码。
如果f2也选择抛出,就会继续抛回给调用f2的方法,直到main方法。
main方法同样可选择进行try-catch处理,或者进行抛出。
main方法抛出异常后,会进入到jvm(java虚拟机中),jvm处理异常,就会直接对异常的信息进行打印,然后直接退出程序。
所以我们就可知道,为什么上面的程序会只有打印信息,没有我们设置的语句。
6、异常的处理顺序图
画一个图就可以全解释清楚了:
7、finally
try -catch 处理异常的时候,还可以添加一个finally处理。
无论try包围的代码块中有没有异常出现,都要执行finally中的代码。
往往用于资源关闭的处理。
public class FinallyExample {
public static void main(String[] args) {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream("somefile.txt");
// 读取文件操作
int data = fileInputStream.read();
while(data != -1){
System.out.print((char) data);
data = fileInputStream.read();
}
//catch块也可以有多个,从而处理不同类型的异常
} catch (FileNotFoundException e) {
System.out.println("文件未找到");
} catch (IOException e) {
System.out.println("读取文件时出错");
} finally {
// 无论是否发生异常,都要执行的代码
if (fileInputStream != null) {
try {
//这里在finally关闭了文件流
fileInputStream.close();
} catch (IOException e) {
System.out.println("关闭文件时出错");
}
}
}
}
}
注意:try 和finally,没有catch,则不可以使用,否则程序直接崩溃。
8、自定义异常类
我们除了可以使用jdk自带的异常以外,还可以自己定义一些异常类。
当我们创建一个类后,让其继承Exception类,这样这个类就成为了一个自定义编译异常类。
如果继承RuntimeException类,那么这个就是一个自定义运行异常类。
例如:
public class DIYException {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int key = scanner.nextInt();
//在主方法中使用自定义异常类
//使用throw new来使用:
if (key < 10) {
//如果key小于10,那么就抛出一个LessThanTenException
throw new LessThanTenException("输入小于10 请重新输入!");
}
else System.out.println("key = " + key);
}
}
//自定义异常类(假如我不想输入小于10)
class LessThanTenException extends RuntimeException {
//可以在构造器中设置异常提示信息
public LessThanTenException(String mes) {
super(mes);
}
}
运行结果:
9、抛出异常对象throw
使用throw关键字来抛出一个具体的异常对象,不要和throws弄混。
throw是用来抛出异常对象的,而throws是加在方法定义后,用来处理可能出现的异常。
四、包装类
我们知道java是一门面向对象的语言,而我们的基本数据类型如int,double,char都不支持对象的处理方法。为此,Java的设计者们就设计了很多包装类,来便于java面向对象的处理机制。
如Integer,Double,String…这些类里有许多方法,它们对处理基本数据类型提供了很大的便利,我们来一一介绍。
1、常见的包装类和基本数据类型
我做一个表:
包装类 | 基本数据类型 |
---|---|
Integer | int |
Double | double |
Float | float |
Character | char |
Boolean | boolean |
Byte | byte |
Long | long |
Short | short |
包装类名字基本就是其基本数据类型的全称和首字母大写。
其中一个特殊的包装类是String。
String我们平时都是当做基本数据类型来使用,但实际上它是一个包装类。
2、装箱和拆箱
把基本数据类型封装到包装类中,就是装箱:
//在构造器中传入一个int型变量,
//这样就可以把变量封装到新创建出来的对象里
Integer integer = new Integer(10);
还可以更简洁一些:
Integer integer = 10;
这里jdk5以后的版本,内部会自动进行装箱。这里的自动装箱调用的是Integer的valueOf方法,记住,后面要考的。
而如果我们需要把integer对象中的数据取出来,那么就是拆箱:
public class PackClass {
public static void main(String[] args) {
Integer integer = 10;
System.out.println("integer内部的值是 " + integer.intValue());
}
}
我们这里希望打印出来integer的值,就是访问其内部的值,也就是一个拆箱的过程。
我们使用integer.intValue()这个方法,就可以访问它的值:
而jdk5以后也提供了自动拆箱的机制,代码也可以修改如下:
public class PackClass {
public static void main(String[] args) {
Integer integer = 10;
System.out.println("integer内部的值是 " + integer);
}
}
直接打印integer就是一个自动拆箱,运行结果一样。
3、包装类和String的转换
开发过程中,有可能会需要将包装类转换为String类型。
Integer转换为String:
public class PackClass {
public static void main(String[] args) {
Integer integer = 10;
//方法一:在后面加入一个空串—— ""
String str1 = integer + "";
//方法二:使用toString方法
String str2 = integer.toString();
//方法三:使用String的valueOf方法
String str3 = String.valueOf(integer);
System.out.println(str1);
System.out.println(str2);
System.out.println(str3);
}
}
运行结果:
4、Integer和Character类的常用方法(认识)
public class WrapperMethod {
public static void main(String[] args) {
System.out.println(Integer.MIN_VALUE); //返回最小值
System.out.println(Integer.MAX_VALUE);//返回最大值
System.out.println(Character.isDigit('a'));//判断是不是数字
System.out.println(Character.isLetter('a'));//判断是不是字母
System.out.println(Character.isUpperCase('a'));//判断是不是大写
System.out.println(Character.isLowerCase('a'));//判断是不是小写
System.out.println(Character.isWhitespace('a'));//判断是不是空格
System.out.println(Character.toUpperCase('a'));//转成大写
System.out.println(Character.toLowerCase('A'));//转成小写
}
}
5、Integer的创建机制
来看这一段代码,思考运行结果是什么:
public class Integer_ {
public static void main(String[] args) {
//请判断会输出什么?
Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1 == i2);
//请判断会输出什么?
Integer a1 = 128;
Integer a2 = 128;
System.out.println(a1 == a2);
}
}
这两段代码看似好像没什么区别,都是自动装箱给Integer对象赋值,并使用==判断。
这里的关键就是在于“==”号的判断机制,我们在之前讲过:
== 号判断基本数据类型,就是比较大小。
== 号判断引用类型,就是判断是否是同一个对象。
那么我们既然把数据装箱到了对象中,那么是不是结果就是true true呢?
运行结果:
这就很奇怪了吧,既然都是装箱,不管是true还是false,这两个应该都是一样的结果,怎么会一个true一个false呢?
先上结论:只要装箱的是-128到127的数据,那么就不会创建新对象。而超过这个范围就要创建新对象。所以会产生这样的结果。
原因就在Integer的源码中:
这个静态内部类 IntegerCache中,定义了一个静态变量 low = -128 h = 127(h可以通过配置文件修改定义),我们在过来看Integer的valueOf代码:
我们知道,自动装箱是自动调用了Integer的 valueOf方法,而我们看到方法中,如果给的数值在-128~127之间,那么就会返回一个已有的IntegerCache.cache[i + (-IntegerCache.low)]对象,
如果在这个范围之外,就会新创建一个Integer对象。
下面是IntegerCache的一段源码 我们主要看下面是如何给Integer cache[]数组赋值的。
通过这个for循环,我们可以知道,这个cache数组里面已经创建了很多Integer对象, 并且都装入了-128到127的int数据。
所以当我们自动装箱127这个数据时,也就是执行以下代码时:
Integer i1 = 127;
Integer i2 = 127;
在Integer内部的cache数组中,早就已经创建好了一个Integer对象,并封装好了127这个数据,所以在我们执行代码的时候就不会创建新的对象。
但是当我们执行以下代码就不一样了:
Integer a1 = 128;
Integer a2 = 128;
128这个数据没有被Integer的cache数组所包装,所以没有这个对象,从上面valueOf的方法来看,就需要新创建一个对象。
所以a1和a2这两步操作创建了两个新对象。
这样,再结合==号判断引用类型的特性就知道,a1和a2指向的并不是一个对象。
而i1和i2指向的是同一个对象。
所以,我们就知道上面的代码为什么会出现这样的结果了。
结论:只要装箱的是-128到127的数据,那么就不会创建新对象。而超过这个范围就要创建新对象。
那么如果我想要比较两个Integer对象的值是否相同应该怎样比较呢?
当然是用.equals()方法啦。
6、String类(重要)
(1)String类的类继承图
(2)String类创建对象的两种方式(!)
方式一:自动装箱
String s1 = "water boy";
方式二:使用构造器
String s2 = new String("water boy");
这两个方式看似没区别实际上在底层有着本质的区别!
我画一个图来表示这两种方式的不同:
使用方法一创建的s1,发现在常量池里没有常量“water boy”,所以会直接在常量池里创建“water boy常量”,然后s1就会直接指向常量池的“water boy”的地址 0x11,也就是说,s1这个引用存储的就是0x11。
而方法二创建的s2使用了构造器,所以会创建一个String对象,并且让s2指向这个对象的地址0x1122,而这个String对象里面有一个属性value,这个value里存储的就是这个String的值的地址,也就是“water boy”的地址0x11(如果没有“water boy”这个常量,那么也会创建一个)。
所以可见两种方式创建的String对象是有很大的不同的。 最大的区别就是是否创建对象。
所以如果使用s1 == s2 来判断的话由于 == 判断的是地址(对象),所以s1 == s1返回的就是false。
而如果使用s1.equals(s2)返回的就是true,因为String类已经重写了equals方法,从而判断value的值是否相同。
(3)intern方法
String的intern方法,会直接返回value的地址,如s2.intern()返回的就是“water boy”的地址0x11。
所以判断 s1 == s2.intern() 这个结果返回的是true。
(4)String的特性
①字符串常量的值一旦被赋值,就不可以改变。
这句话很容易被误解,这里指的字符串是常量池里的字符串常量。
现在常量池里存放了一个字符串:“water boy”,它的地址是0x11,那么这个字符串常量对象就不可以修改它的值,也就是0x11这个空间内存放的值“water boy”不可以修改。
但是这不意味着String对象指向的值不可以修改。
我们知道String对象里存放了一个value,value里存放的是常量池里字符串常量的地址,这个value的指向可以更改。
例如:我可以输入:
s2 = “aaaa”;
这会在常量池里创建一个新的字符串常量“aaaa”,然后s2的值的指向就变成了这样:
可以看到,String对象的value发生了改变,而且原有的“water boy”并没有改变。
②String类是一个final类
③String s = “abc” + “hello”;等价于 String = “abchello”;
这一点说明,常量池里只有“abchello”这一个常量,并没有创建“abc” “hello” “abchello”三个。
④String a = “abc”;String b = “hello”; String c = a + b;创建机制
这里会创建三个常量对象,并且会创建String对象。
如图:a b的创建没有问题,直接指向常量池
之后会创建String对象,并通过内部机制来进行拼接,在常量池里创建“abchello”
(5)String类常用方法
equals 区分大小写,判断内容是否相等
equalslgnoreCase 忽略大小写的判断内容是否相等length/获取字符的个数,字符串的长度
indexOf 获取字符在字符串中第1次出现的索引,索引从0开始,如果找不到,返回-1
lastIndexOf 获取字符在字符串中最后1次出现的索引,索引从0开始,如找不到,返回-1
substring 截取指定范围的子串
trim 去前后空格
charAt 获取某索引处的字符,注意不能使用Str[index]这种方式.
toUpperCase 将字符全部转为大写
toLowerCase 将字符全部转为小写
replace 替换字符串中的字符
split 分割字符串
案例: String poem="锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦"
public class Main {
public static void main(String[] args) {
String poem = "锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦";
// 使用逗号作为分隔符来分割字符串
String[] parts = poem.split(",");
// 输出分割后的每一部分
for (String part : parts) {
System.out.println(part);
}
}
}
compareTo //比较两个字符串的大小
如果两个字符串不一样那么返回的就是第一个不一样的字符的ASCII码的差值;
如果一个字符串是另一个字符串的前半部分,那么返回的就是两个字符串长度之差。
举例:
public class CompareTo_ {
public static void main(String[] args) {
//两个字符串的不相同,
//就将第一个不相同的字符的ASCII码的值的差返回
String str1 = "abcde";
String str2 = "abttt";
System.out.println(str1.compareTo(str2));
//两个字符串一个字符串是另一个字符串的前一部分
//则返回它们的长度的差值
String str3 = "abcdefgh";
System.out.println(str1.compareTo(str3));
}
}
第一个不同的字符是c和t,c和t的ASCII码分别是99和116,所以返回值是-17。
str1和str3的前半部分完全一样,但是长度不一样,所以返回的是他们长度的差值。
结果:
toCharArray //转换成字符数组
format //格式字符串,%s字符串%c字符%d整型%.2f浮点型案例,将一个人的信息格式化输出.(就是c语言的传参方式)
7、StringBuffer类
String类的缺点很明显,每次更新字符串都要开辟新的空间,原有的空间并不会被修改或释放。
所以Java设计者提供了一个StringBuffer类,提高我们的开发效率。
(1)StringBuffer特点
StringBuffer是可变的字符串序列,可以对常量池中字符串本身的内容进行修改。
StringBuffer是可变长度的。
StringBuffer是一个容器。
StringBuffer的方法和String类似。
StringBuffer是一个final类,继承了抽象类AbstractStringBuffer,实现了Serializable接口。
StringBuffer的字符串存储在char[] value数组中,是字符串变量,所以长度可变,并且不用更换字符串值的地址,所以效率比String更高。(String存储的则是字符串常量)如图:
(2)String与StringBuffer的互相转换
在开发中经常需要将它们互相转换,因为如果使用String类并且经常改变String的值,会在常量池中创建非常多不必要的临时对象,久而久之就会影响程序运行的效率。
而如果我们使用StringBuffer中的append方法就可以有效的减少临时对象的产生。
转换方法:
public class StringBuffer_ {
public static void main(String[] args) {
//String转为StringBuffer
//方法一:在构造器中传入String,
//注意返回的才是StringBuffer,对str没有影响
String str = "hello";
StringBuffer stringBuffer = new StringBuffer(str);
//方法二:使用append方法
StringBuffer stringBuffer1 = new StringBuffer();
stringBuffer1.append(str);
//StringBuffer转为String
//方法一:使用构造器
String s = new String(stringBuffer);
//方法二:使用toString()方法
String s1 = stringBuffer.toString();
}
}
(3)StringBuffer的常用方法
①append() 在末尾追加
可以用来在StringBuffer后追加字符串。
可以填入int 、double等类型的数据,会自动转换为字符并追加到后面。
public class StringBuffer_ {
public static void main(String[] args) {
//append方法
StringBuilder sb = new StringBuilder("hello");
sb.append(",world");
System.out.println(sb); //hello,world
sb.append(100);
System.out.println(sb); //hello,world100
}
}
②delete() 删除
可以指定位置来删除字符串内容,后面的内容会补上位置。
需要输入两个参数,第一个是开始的位置序号,第二个是结束的位置序号。(不删除结束的序号位置的值)
注意,hello中的h的序号是0
例如:
public class StringBuffer_ {
public static void main(String[] args) {
//delete方法
StringBuffer sb = new StringBuffer("hello");
//删除序号为2和序号为3的值,左闭右开区间[2,4),4不包含。
sb.delete(2, 4);
System.out.println(sb); //heo
}
}
③replace()修改
修改指定位置的值
依然是指定位置,第一个是开始的位置序号,第二个是结束的位置序号,最后一个参数是用于替换的字符串。并且依然是左闭右开区间,也就是结束位置序号的字符不变。
public class StringBuffer_ {
public static void main(String[] args) {
//replace方法
StringBuffer sb = new StringBuffer("hello");
//替换了序号为1、2的位置为4444,后面的字符自动往后移动
sb.replace(1,3,"4444");
System.out.println(sb); //h4444lo
}
}
④insert()插入
insert有很多重载方法,第一个参数一般都是开始插入的序号,第二个就是需要插入的内容:
public class StringBuffer_ {
public static void main(String[] args) {
//insert方法
StringBuffer sb = new StringBuffer("hello");
//在序号为1的字符前,插入sss
sb.insert(1,"sss");
System.out.println(sb); //hsssello
}
}
⑤lenth()长度
没啥可说的,就是返回一个StringBuffer的长度。
扩展:append方法可以添加null的String类的值,
public class StringBuffer_ {
public static void main(String[] args) {
//StringBuffer存储null字符串:
String str = null;
StringBuffer sb = new StringBuffer();
sb.append(str); //ok 不报错
System.out.println(sb.length()); //4
System.out.println(sb); //null
//在底层 sb存储了'n' 'u' 'l' 'l' 这四个字符
//所以打印时会显示null,长度是4
}
}
8、StringBuilder类
和StringBuffer基本一致,且增加、删除操作的效率更高,但是线程不安全。在被多线程操作的时候,容易出错。
在单线程情况下,StringBuilder可以用于替换StringBuffer。
方法和StringBuffer一样,不聊了。
9、如何选择使用各个“String类”?
没有大量的增加和修改操作时 | String |
有大量的增加和修改操作时 | StringBuffer和StringBuilder |
单线程,有大量的增加和修改操作时 | StringBuilder |
多线程,有大量的增加和修改操作时 | StringBuffer |
10、Math类
Math类的常用方法都比较简单,只要了解一下方法名和其功能就可以了,主要方法有:
1. **绝对值**:
- `Math.abs(int a)`:返回`int`值的绝对值。
- `Math.abs(double a)`:返回`double`值的绝对值。2. **向上取整**:
- `Math.ceil(double a)`:返回大于或等于参数`a`的最小`double`值,等于一个整数。3. **向下取整**:
- `Math.floor(double a)`:返回小于或等于参数`a`的最大`double`值,等于一个整数。4. **四舍五入**:
- `Math.round(float a)`:返回最接近参数`a`的`int`。
- `Math.round(double a)`:返回最接近参数`a`的`long`。5. **乘方**:
- `Math.pow(double a, double b)`:返回第一个参数的第二个参数次幂的值。6. **开方**:
- `Math.sqrt(double a)`:返回正平方根。7. **自然对数**:
- `Math.log(double a)`:返回参数的自然对数(底是`e`)。
8. **底数为10的对数**:
- `Math.log10(double a)`:返回参数的以10为底的对数。9. **最大值和最小值**:
- `Math.max(int a, int b)`:返回两个`int`值中的较大值。
- `Math.min(double a, double b)`:返回两个`double`值中的较小值。10. **三角函数**:
- `Math.sin(double a)`:返回角`a`(以弧度为单位)的正弦值。
- `Math.cos(double a)`:返回角`a`(以弧度为单位)的余弦值。
- `Math.tan(double a)`:返回角`a`(以弧度为单位)的正切值。11. **反三角函数**:
- `Math.asin(double a)`:返回一个值的反正弦值,返回值范围在`-π/2`到`π/2`之间。
- `Math.acos(double a)`:返回一个值的反余弦值,返回值范围在`0.0`到`π`之间。
- `Math.atan(double a)`:返回一个值的反正切值,返回值范围在`-π/2`到`π/2`之间。12. **指数和对数函数**:
- `Math.exp(double a)`:返回`e`的参数次幂。
- `Math.log1p(double x)`:返回`1+x`的自然对数,即`ln(1+x)`。
其中一个值得讲的是random()方法,我们如何使用random()方法来产生任意范围的随机数?
Math.random() 产生的值是0~1的任意数,可以是小数。
假如现在我想要产生一个2~8的任意数,那么应该怎样写代码?
首先,我们可以把随机数的范围扩大一些 ,原先的范围是 0~1 差值是1,我们现在需要的 8-2=6,差值是6,所以我们可以让Math.random() * 6,这样产生的值就是0~6,
然后,我们需要让Math.random() * 6 + 2,这样产生的值就是2~8了。
但是由于可能产生小数数值,如果我们想要只产生整数,就需要使用(int)来转换。
而由于(int)转换是向下取整的,我们产生的数最大也就是8,当产生7.12,7.57这样的数时,都会转换为整数7,所以这样就会让产生8的概率变得几乎为0。
所以我们需要扩大范围到2~9,然后使用(int)来向下取整,这样就可以了。
(int)(Math.random() * 7 + 2)
11、Array类
方法了解一下即可
1) toString - 返回数组的字符串形式
使用方法:Arrays.toString(arr)
说明:这个方法可以将一个数组转换成一个字符串形式,方便查看数组中的元素。
2) sort - 排序(自然排序和定制排序)
使用方法:Integer arr[] = {1, -1, 7, 0, 89}; Arrays.sort(arr);
说明:这个方法用于对数组进行排序。如果是数值类型数组,它会按照数值大小进行自然排序;如果是对象数组,可以通过提供Comparator来实现定制排序。
3) binarySearch - 通过二分搜索法进行查找,要求数组必须是排好序的
使用方法:int index = Arrays.binarySearch(arr, 3);
说明:这个方法用于在已排序的数组中查找指定的值。如果找到,返回搜索键的索引;否则返回一个负数。
4) copyOf - 数组元素的复制
使用方法:Integer[] newArr = Arrays.copyOf(arr, arr.length);
说明:这个方法用于复制数组,可以指定要复制的长度。
5) fill - 数组元素的填充
使用方法:Integer[] num = new Integer[]{9, 3, 21}; Arrays.fill(num, 99);
说明:这个方法用于将指定的值赋值给数组的每个元素。
6) equals - 比较两个数组元素内容是否完全一致
使用方法:boolean equals = Arrays.equals(arr, arr2);
说明:这个方法用于比较两个数组的元素是否完全相同。
7) asList - 将一组值转换成list
使用方法:List<Integer> asList = Arrays.asList(2, 3, 4, 5, 6, 1);
说明:这个方法用于将数组转换为List集合。注意,返回的List集合是基于原始数组的,因此对返回的List集合的操作会影响到原始数组。
12、System类
exit()方法:用于退出程序
currentTimeMillens():返回当前时间距离1970 - 1 -1的毫秒数
System.gc():运行垃圾回收机制
13、BigInteger类和BigDecimal类
int型和long型变量,我们知道都是整数数据,但是它们能够存储的数的大小都是有上限的。
数据类型 | 存储上限 |
int | -2^16 ~ 2^16 -1 (负的二的十六次方到二的十六次方减一) |
long | -2^64 ~ 2^64 -1 (负的二的64次方到二的64次方减一) |
如果超过了long的存储范围,那我们就需要更大的类型来存储:BigInteger
BigInteger存储的数据进行四则运算时,不可以使用 +-*/ 来运算,而是调用其内部的方法add 加subtract减 multiply乘 divide除。
而如果小数的精度超过了float型变量和double型变量的上限,那么就需要使用BigDecimal类存储。
float | 可存储6~9位小数 |
double | 可存储15~16位小数 |
(这里提一嘴,这里的float和double的精度都不是一个固定的数值,因为和底层运行机制有关,如果感兴趣可以看一看这篇文章)
14、日期类
(1)第一代日期类 Date
可以使用Date来获取当前的时间:
import java.util.Date;
public class Date_ {
public static void main(String[] args) {
//需要先创建Date对象
Date date = new Date();
//直接打印Date即可
System.out.println(date);
}
}
运行结果:
可以看到,虽然运行结果是现在的时间,但是格式却不方便我们查看。
我们可以使用SimpleDateFormat方法来输出我们想要的格式:
import java.text.SimpleDateFormat;
import java.util.Date;
public class Date_ {
public static void main(String[] args) {
//需要先创建Date对象
Date date = new Date();
//创建SimpleDateFormat对象,
//并将想要输出的格式放入构造器中
SimpleDateFormat sdf =
new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
//使用SimpleDateFormat对象的format方法,
//把date传入进去即可 打印
System.out.println(sdf.format(date));
}
}
这里输出的格式中,用yyyy代表年份、MM代表月份、dd代表日期、HH代表小时、mm代表分钟、ss代表秒钟。如果想要输出不同的格式,只需要把以上这些进行排列组合,再添加一些字就可以了 。
我们还可以把一个字符串转换为Date类型
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Date_ {
public static void main(String[] args) throws ParseException {
//需要先创建Date对象
Date date = new Date();
//创建SimpleDateFormat对象,
//并将想要输出的格式放入构造器中
SimpleDateFormat sdf =
new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
//将字符串转换为Date对象 使用SimpleDateFormat的parse方法
//前提:字符串的格式必须和自定义的格式"yyyy年MM月dd日 HH:mm:ss"一致
String str = "2024年03月19日 19:24:16";
Date parse = sdf.parse(str);
System.out.println(parse);
}
}
运行结果:
使用缺陷 :
Date类在处理时区方面没有考虑周全,并且月份是从0开始的,不符合人们使用的直觉,所以逐渐被取代。
(2)第二代日期类Calendar
Calendar类在Date类的基础上有了一些改进,但是依然有些复杂、难用。
import java.text.ParseException;
import java.util.Calendar;
public class Date_ {
public static void main(String[] args) throws ParseException {
//Calendar 类是一个抽象类,需要通过getInstance方法获取对象
//Calendar calendar = new Calendar();
Calendar calendar = Calendar.getInstance();
System.out.println(calendar);
}
}
将未经处理的Calendar对象打印,运行结果:
(后面还有很长很长……)
这里打印的是calendar对象内部的属性信息,有year、month、day、week等等。
如果想要打印方便查看的格式需要程序员自己组合,比较麻烦……例如:
import java.util.Calendar;
public class Date_ {
public static void main(String[] args){
//Calendar 类是一个抽象类,需要通过getInstance方法获取对象
//Calendar calendar = new Calendar();
Calendar calendar = Calendar.getInstance();
//程序员需要自己定义输出格式,非常繁琐,
//而且月份还是从0开始,所以要加1
System.out.println(calendar.get(Calendar.YEAR) + "年"
+ (calendar.get(Calendar.MONTH)+1) + "月"
+ calendar.get(Calendar.DATE) + "日");
}
}
运行结果:
我的评价是不如Date
(3)第三代日期类
Calendar类和Date类的月份都是从0开始,导致输出的时候还要手动的加1才正确。
并且它们都是线程不安全的,并且它们都是可变的(日期和时间应该不可变)。
所以有了第三代日期类。
LocalDate、LocalTime、LocalDateTime(JDK8)
LocalDate只包含日期信息,LocalTime只包含时间信息,而LocalDateTime则包含时间和日期信息。
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
public class Date_ {
public static void main(String[] args){
//LocalDate构造器私有化了,
//不可以直接new创建对象
//需要使用now()方法获取
LocalDate now = LocalDate.now();
System.out.println(now); //2024-03-19
//LocalTime和LocalDateTime也是一样
LocalTime now1 = LocalTime.now();
System.out.println(now1); //19:54:26.403
LocalDateTime now2 = LocalDateTime.now();
System.out.println(now2); //2024-03-19T19:55:26.861
}
}
当然,它们还可以从方法中直接获取到年份、月份等信息,从而自定义输出,这里就不赘述。
先写到这,下篇写集合,这是个大头,专门出一期来讲讲吧~