这篇文章来写Java基础类库,后面还会有开发者支持类库。
Java基础类库
StringBuffer类
String类是在所有项目开发的过程一定会用到的一个功能类,并且这个类有如下的特点:
- 每一个字符串的常量都属于一个String类的匿名对象,并且不可更改;
- String有两个常量池:静态常量池、运行时常量池;
- String类对象实例化建议使用直接赋值的形式完成,这样可以直接将对象保存在对象池下面方便下次重用;
虽然String类很好使用,但是如果认真思考也会发现一个弊端,也就是内容不允许修改,虽然大部分情况下都不会涉及到字符串内容的频繁修改,但还是会有这样的情况发生,为了解决这个问题,专门提供有一个StringBuffer类可以实现字符串内容的修改处理。
我们来看一个例子:
public class TestDemo {
public static void main(String[] args) {
String str = "Hello";
change(str);
System.out.println(str);
}
public static void change(String temp) {
temp += " World!";
}
}
当我们运行的时候会发现,内容并没有改变,如果希望内容是可以改变的时候,就应该使用StringBuffer类。
StringBuffer并不像String类一样拥有两种实例化的方式,StringBuffer必须像普通类对象那样,首先进行对象实例化,然后才可以调用方法执行处理,而这个时候可以考虑使用StringBuffer类中的如下方法:
构造方法:public StringBuffer();
追加方法:public StringBuffer append();
public class TestDemo {
public static void main(String[] args) {
//String str = "Hello";
StringBuffer str = new StringBuffer("Hello");
change(str);
System.out.println(str);
}
public static void change(StringBuffer temp) {
//temp += " World!";
temp.append(" World!");
}
}
而我们写这样一个程序可以看到它的结果是true:
public class TestDemo {
public static void main(String[] args) {
String str1 = "HelloWorld!";
String str2 = "Hello" + "World" + "!";
System.out.println(str1 == str2);
}
}
那么这条语句的作用就和上述的“+”运算一样:
StringBuffer str = new StringBuffer();
str.append("Hello").append("World").append("!");
可以看到所有的“+”在编译后都变成了append()方法,并且在程序中StringBuffer对象和String对象本来就可以可以直接互相转换:
- String类对象变为StringBuffer可以依靠StringBuffer类的构造方法或者使用append()方法;
- 所有的类对象都可以通过toString()方法将其变为String类对象;
在StringBuffer类中除了可以支持有字符串内容的修改之外,实际上也提供了String类所不具备的方法:
- 在第a个位置插入数据b:public StringBuffer insert(int a, 类型 b);
- 删除第start个位置到第end个位置的数据:public synchronized StringBuffer delete(int start, int end);
- 还有一个最有特点的操作,字符反转:public synchronized StringBuffer reverse();
实际上与StringBuffer类还有一个类似的类,StringBuilder类,这个类是在JDK1.5的时候提供的,该类中提供的方法与StringBuffer功能相同,但是在StringBuilder中方法没有synchronized,所以最大的区别是StringBuffer类中的方法属于线程安全的,而StringBuilder的方法是非线程安全的。
解释一下String、StringBuffer、StringBuilder的区别?
- String类是字符串的首选类型,其最大的特点是内容不允许修改;
- StringBuffer与StringBuilder类的内容允许修改;
- StringBuffer是在JDK1.0的时候提供的,属于线程安全操作,而StringBuilder是在JDK1.5之后提供的,属于非线程安全操作。
CharSeuence接口
CharSequence是一个描述字符串结构的接口,在这个接口中一般发现有三种常用的子类:String类、StringBuffer类、StringBuilder类:
而我们再来看一下他们分别的构造方法,会发现:
以及append()方法:
会发现都有CharSequence的存在,那就说明只要有字符串就可以为CharSequence接口实例化:
public class TestDemo {
public static void main(String[] args) {
CharSequence str = "HelloWorld!";
}
}
CharSequence本身是一个接口,在该接口中也定义了有如下的操作方法:
- 获取指定索引字符:char charAt(int index);
- 获取字符串的长度:int length();
- 截取部分字符串:CharSequence subSequence(int start, int end);
所以我们以后见到CharSequence就知道描述的是一个字符串。
AutoCloseable接口
AutoCloseable主要是用与日后进行资源开发的处理上,以实现资源的自动关闭(释放资源),例如:在进行文件、网络、数据库的开发中,由于服务器的资源有限,所以在使用后一定要关闭资源,这样才可以被更好的使用。
为了更好地说明问题,我们同通过一个消息的发送处理来完成:
interface IMessage{
public void send(); //消息发送
}
class NetMessageImpl implements IMessage{//消息的处理机制
private String msg;
public NetMessageImpl(String msg) {
this.msg = msg;
}
public boolean open() { //获取资源链接
System.out.println("【open】获取消息发送资源链接");
return true;
}
@Override
public void send() {
if(this.open()) {
System.out.println("【send】发送消息:" + this.msg);
}
}
public void close() {
System.out.println("【close】关闭消息发送通道");
}
}
public class TestDemo {
public static void main(String[] args) {
NetMessageImpl nm = new NetMessageImpl("HelloWorld!"); //定义要发送的内容
nm.send(); //消息发送
nm.close(); //关闭连接
}
}
既然所有的资源在处理后都要进行关闭操作,那么能否实现一种自动关闭的功能?于是推出了AutoCloseable的接口,这个接口是在JDK1.7的时候提供的,并且只提供有一个方法:
- 关闭方法:void close() throws Exception;
那我们来看一下这个接口是否能自动关闭:
interface IMessage extends AutoCloseable{
public void send(); //消息发送
}
public class TestDemo {
public static void main(String[] args) {
NetMessageImpl nm = new NetMessageImpl("HelloWorld!"); //定义要发送的内容
nm.send(); //消息发送
//nm.close(); //关闭连接
}
}
当我们加了AutoCloseable之后,理论上就可以不用再写close()方法了,但是我们执行后却发现没有执行资源关闭方法,要想实现自动关闭操作,除了要使用AutoCloseable之外,还需要结合异常处理语句才可以正常执行:
interface IMessage extends AutoCloseable{
public void send(); //消息发送
}
class NetMessageImpl implements IMessage{//消息的处理机制
...
}
public class TestDemo {
public static void main(String[] args) {
try(IMessage nm = new NetMessageImpl("HelloWorld!")){
nm.send();
}catch(Exception e){}
}
}
再编译一下,运行就会看到自动执行关闭操作了。在以后接触到资源关闭问题时,往往都会见到AutoCloseable接口的使用,这个操作必须要和异常处理结合才能使用。
Runtime类
Runtime描述的是运行时的状态,也就是说在整个JVM中,Runtime类时唯一一个与JVM运行状态有关的类,并且默认会提供有一个该类的实例化对象。
由于在每一个JVM进程中只允许提供有一个Runtime类的对象,所以这个类的构造方法就被私有化了,也就证明该类使用的是单例设计模式,并且单例设计模式一定会有一个提供有static方法获取本类实例。

由于Runtime类属于单例设计模式,那么要想获取实例化对象就可以依靠类中的getRuntime()方法:
- 获取实例化对象:public static Runtime getRuntime();
我们来实现一下,随便输出些什么东西,比如availableProcessors()这个方法会返回电脑的CPU内核个数:
public class TestDemo {
public static void main(String[] args) {
Runtime run = Runtime.getRuntime(); //获取实例化对象
System.out.println(run.availableProcessors());
}
}
返回的数字就是电脑的CPU内核个数,比如我的是8,就说明最佳访问量是8个人可以同时访问;
除了这个方法,Runtime还有四个重要的操作方法:
- 获取最大可用空间:public native long maxMemory();
- 获取可用内存空间:public native long totalMemory();
- 获取空闲内存空间:public native long freeMemory();
- 手工进行GC处理:public native void gc();
观察内存状态:
public class TestDemo {
public static void main(String[] args) {
Runtime run = Runtime.getRuntime(); //获取实例化对象
System.out.println("MAX_MEMORY:" + run.maxMemory());
System.out.println("TOTAL_MEMORY:" + run.totalMemory());
System.out.println("FREE_MEMORY:" + run.freeMemory());
}
}
MAX_MEMORY:2118123520
TOTAL_MEMORY:134217728
FREE_MEMORY:131615136
可以看到最大可用空间为2118123520,我们用计算器除1024三次,就得到了单位为G的内存空间1.97265625G,而一般这个值默认为计算机系统配置的1/4;而totalMemory的值默认为本机系统配置的1/64。
这样看不出什么,我们来玩一下:
public class TestDemo {
public static void main(String[] args) {
Runtime run = Runtime.getRuntime(); //获取实例化对象
System.out.println("【1】MAX_MEMORY:" + run.maxMemory());
System.out.println("【1】TOTAL_MEMORY:" + run.totalMemory());
System.out.println("【1】FREE_MEMORY:" + run.freeMemory());
String str = "";
for(int i = 0; i < 30000; i++) {
str += i;
}
//产生大量的垃圾空间
System.out.println("【2】MAX_MEMORY:" + run.maxMemory());
System.out.println("【2】TOTAL_MEMORY:" + run.totalMemory());
System.out.println("【2】FREE_MEMORY:" + run.freeMemory());
}
}
这时候有意思的就来了:
【1】MAX_MEMORY:2118123520
【1】TOTAL_MEMORY:134217728
【1】FREE_MEMORY:131615184
【2】MAX_MEMORY:2118123520
【2】TOTAL_MEMORY:434110464
【2】FREE_MEMORY:226679728
我们看到大量的内存被占用,而我们再加上回收机制后:
public class TestDemo {
public static void main(String[] args) {
Runtime run = Runtime.getRuntime(); //获取实例化对象
System.out.println("【1】MAX_MEMORY:" + run.maxMemory());
System.out.println("【1】TOTAL_MEMORY:" + run.totalMemory());
System.out.println("【1】FREE_MEMORY:" + run.freeMemory());
String str = "";
for(int i = 0; i < 30000; i++) {
str += i;
}
//产生大量垃圾空间
System.out.println("【2】MAX_MEMORY:" + run.maxMemory());
System.out.println("【2】TOTAL_MEMORY:" + run.totalMemory());
System.out.println("【2】FREE_MEMORY:" + run.freeMemory());
run.gc();
//释放后
System.out.println("【3】MAX_MEMORY:" + run.maxMemory());
System.out.println("【3】TOTAL_MEMORY:" + run.totalMemory());
System.out.println("【3】FREE_MEMORY:" + run.freeMemory());
}
}
【1】MAX_MEMORY:2118123520
【1】TOTAL_MEMORY:134217728
【1】FREE_MEMORY:131615184
【2】MAX_MEMORY:2118123520
【2】TOTAL_MEMORY:495976448
【2】FREE_MEMORY:423627328
【3】MAX_MEMORY:2118123520
【3】TOTAL_MEMORY:8388608
【3】FREE_MEMORY:7178112
事情变得有意思起来了,total也在改变,这是在JDK1.9之后才会改变total的。那么问题来了,什么是GC,如何处理?
- GC(Garbage Collector)垃圾收集器,是由系统自动调用的一个垃圾释放功能,或者使用Runtime类中的gc()shou'dong调用。
System类
System一直陪着我们编写程序,之前使用系统输出就是System方法,而后在System类中又有一些定义的操作:
- 数组拷贝:public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
- 获取当前的日期时间数值:public static native long currentTimeMillis();
- 进行垃圾回收:public static void gc();
我们在开发中可以实现操作耗时的统计:
public class TestDemo {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
String str = "";
for(int i = 0; i < 30000; i++) {
str += i;//产生大量垃圾空间
}
long end = System.currentTimeMillis();
System.out.println("操作耗时:" + (end - start));
}
}
当然,单位是毫秒。
再System中也有一个GC方法,当我们打开这个GC操作后会发现:
public static void gc() {
Runtime.getRuntime().gc();
}
这个GC方法就相当于执行了Runtime中的GC。
Cleaner类
Cleaner是在JDK1.9之后提供的一个对象清理操作,其主要功能是进行finally()方法的替代。
在C++中有两种特殊的函数:构造函数、析构函数(对象手工回收),在Java中所有的垃圾回收空间都是通过GC自动回收的,所以很多情况下是不需要使用这类析构函数的,所以Java没有提供这方面的支持。
但是Java仍然提供了给用户收尾的操作,每一个实例化对象在回收之前都会给一个喘息的机会,最初实现对象收尾处理的方法是Object类中的finalize()方法,这个方法定义如下:
可以看到这个方法不建议再使用了,但是这个替换意思是不建议使用这个方法了,而是说子类可以继续使用这个方法名称。但是这个方法最大的特点就是抛出了一个Throwable异常类型,而这个异常类型分为两个子类型:Error、Exception,平常所处理的都是Exception。
我们可以来看一下finalize():
class Member{
public Member() {
System.out.println("【一个对象】");
}
@Override
protected void finalize() throws Throwable {
System.out.println("【对象回收】");
throw new Exception("【一个异常】");
}
}
public class TestDemo {
public static void main(String[] args) throws InterruptedException {
Member m = new Member(); //一个对象new出来
m = null;//成为垃圾
}
}
【一个对象】
但是看不到回收,因为时间太快了,我们来手动回收一下:
class Member{
...
}
public class TestDemo {
public static void main(String[] args) throws InterruptedException {
Member m = new Member(); //一个对象new出来
m = null;//成为垃圾
System.gc();
}
}
【一个对象】
【对象回收】
这时候我们如果有其它操作,并不会影响他最终的回收:
class Member{
...
}
public class TestDemo {
public static void main(String[] args) throws InterruptedException {
Member m = new Member(); //一个对象new出来
m = null;//成为垃圾
System.gc();
System.out.println("【其他操作】");
}
}
【一个对象】
【其他操作】
【对象回收】
可以看到,回收是最后进行的,不会影响其他的操作。但是从JDK1.9开始就不建议使用这个操作了,而对于对象的回收释放。从JDK1.9开始建议开发者使用Autocloseable或者使用java.lang.ref.Cleaner类进行回收处理(Cleaner也支持Autocloseable处理),我们来实现一下:
class Member implements Runnable{
public Member() {
System.out.println("【一个对象】");
}
@Override
public void run() { //执行清楚操作
System.out.println("【对象回收】");
}
}
class MemberClearing implements AutoCloseable{
//实现清除处理
private static final Cleaner cleaner = Cleaner.create(); //创建一个清除处理
private Member member;
private Cleaner.Cleanable cleanable;
public MemberClearing() {
this.member = new Member(); //创建新对象
this.cleanable = this.cleaner.register(this, this.member); //注册使用的对象
}
@Override
public void close() throws Exception{
this.cleanable.clean(); //启动多线程
}
}
public class TestDemo {
public static void main(String[] args) throws InterruptedException {
try (MemberClearing mc = new MemberClearing()){
//中间可以写一些相关代码
}catch(Exception e) {
}
}
}
在新一代的清楚回收处理的过程中,更多的情况下考虑的是多线程的使用,即为了防止有可能造成的延迟处理,所以许多对象回收前的处理都是通过一个单独的线程完成的,来保证整体性能的提高。
对象克隆
所谓的对象克隆指的就是对象的复制,而且属于全新的复制。即使用已有对象的内容创建一个新的对象,如果需要对象克隆要使用到Object类中的clone()方法:
- 克隆方法:protected native Object clone() throws CloneNotSupportedException;
所有的类都会继承Object父类,所有的类一定会有clone()方法,但并不是所有的类都希望被克隆,所以要想实现对象的克隆,那么对象所在的类就要实现一个Clonable接口,这个接口并没有任何的方法提供,因为它描述的是一种能力。
实现一下:
class Member implements Cloneable{
private String name;
private int age;
public Member(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return "【" + super.toString() + "】name = " + this.name + "、age = " + this.age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
Member m1 = new Member("紫薇一号", 30);
Member m2 = (Member)m1.clone();
System.out.println("【m1】" + m1 + "\n【m2】" + m2);
}
}
在开发中如果不是特殊情况,很少有对象克隆的需求。
数字操作类
程序就是一个数字处理的过程,所以Java语言本身也提供了数字处理的支持:
Math数学计算类
Math类的主要功能是进行数学计算的操作类,提供有基础的计算公式,这个类的构造方法被私有化了但不是单例,也就是说所有的方法都是static方法,即这些方法都可以通过类名称直接调用。
public class Test {
public static void main (String []args) {
System.out.println("90 度的正弦值:" + Math.sin(Math.PI/2));
System.out.println("0度的余弦值:" + Math.cos(0));
System.out.println("60度的正切值:" + Math.tan(Math.PI/3));
System.out.println("1的反正切值: " + Math.atan(1));
System.out.println("π/2的角度值:" + Math.toDegrees(Math.PI/2));
System.out.println(Math.PI);
}
}
90 度的正弦值:1.0
0度的余弦值:1.0
60度的正切值:1.7320508075688767
1的反正切值: 0.7853981633974483
π/2的角度值:90.0
3.141592653589793
在Math中还有四舍五入的处理方法,但是这个方法在进行处理的时候直接将小数点后的所有位都进行进位处理了,这样肯定不方便,现在最方便的方法是进行指定位数的保留。
class MathUtil{
private MathUtil() {}
/**
* 实现四舍五入效果
* @param num 要四舍五入的数
* @param scale 要四舍五入的位数
* @return 四舍五入的结果
*/
public static double round(double num, int scale) {
return Math.round(num * Math.pow(10, scale)) / Math.pow(10, scale);
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
System.out.println(MathUtil.round(19.569874562, 2));
}
}
Math类中提供的基本上都是基础的数学公式,需要的时候要自己重新整合。
Random随机数生成类
java.util.Random类的主要功能是产生随机数的,这个类主要依靠内部提供给的方法来完成:
- 产生一个不大于边界的随机正整数:public int nextInt(int bound);
大数字处理类
在进行数学计算的过程中,还有一个大数字的操作类,可以实现海量数字的计算(当然能提供的也只有基础运算),如果有一个数的值超过了double类型,那么就没有任何一种数据类型可以保存下这个内容,最早的时候通过String来保存:
String str1 = "123";
String str2 = "234";
这个时候想要运算的话,就要进行逐位拆分,每一位为自己计算而后自己独立控制进位处理,但这样的难度是非常高的,所以为了解决这类问题,提供有两个大数字操作类:BigInteger、BigDecimal:
- public class BigInteger extends Number implements Comparable<BigInteger>
- public class BigDecimal extends Number implements Comparable<BigDecimal>
之前说当数字很大的时候只能用字符串操作描述数字操作,所以这一点可以用来观察两个大数值操作类的操作方法:
- BigInteger类构造:public BigInteger(String val, int radix);
- BigDecimal类构造:public BigDecimal(String val);
public class TestDemo {
public static void main(String[] args) throws Exception {
BigInteger str1 = new BigInteger("12365153811335161514468468464");
BigInteger str2 = new BigInteger("12365446828138531468464");
System.out.println(str1.add(str2));
System.out.println(str1.subtract(str2));
System.out.println(str1.multiply(str2));
System.out.println(str1.divide(str2));
}
}
12365166176781989652999936928
12365141445888333375937000000
152900651975819446309219285807262767310742994519296
999976
当然当数字大到一定程度的时候计算是非常缓慢的,任何电脑都是有极限的,既然是在进行数学运算除法的时候无法进行整除处理,就可以使用其他的除法计算来求余数:
- 求余:public BigInteger[] divideAndRemainder(BigInteger val) 数组第一个元素为商,第二个为余数;
public class TestDemo {
public static void main(String[] args) throws Exception {
BigInteger str1 = new BigInteger("12365153811335161514468468464");
BigInteger str2 = new BigInteger("12365446828138531468464");
BigInteger result[] = str1.divideAndRemainder(str2);
System.out.println("商" + result[0]);
System.out.println("余" + result[1]);
}
}
商999976
余3753920505370759711600
如果在开发中真的进行计算的时候,该计算没有超过基本数据类型的位数,强烈不建议使用大数运算,因为性能有要求。
而BigDecimal的运算与BigInteger的运算是类似的,就是一个可以进行小数运算一个进行整数运算而已。
但是在使用BigDecimal时有一个数据进位的问题,在这个类中定义有如下的除法运算:
- 除法操作:public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode);
而这个 RoundingMode是JDK1.5的时候出现的,但是在JDK1.9的时候过期了。现在通过BigDecimal实现四舍五入处理:
class MathUtil{
public static double round(double num, int scale) {
return new BigDecimal(num).divide(new BigDecimal(1.0), scale, RoundingMode.HALF_UP).doubleValue();
}
}
public class TestDemo {
public static void main(String[] args) throws Exception {
System.out.println(MathUtil.round(9.23555, 2));
}
}
Math处理由于使用了基本数据类型,所以性能一定高于大数字处理类的性能,所以如果不是必须的情况下,不建议直接使用大数字处理类。
日期操作类
从整体的Java来讲一直强调简单Java类的主要设计来自数据表的结构,在数据表的结构中常用的类型:数字、字符串、日期,所以现在的程序中还差日期。
Java中有一个jaa.util.Date的类,这个类如果直接实例化就可以获取当前的日期时间:
public class TestDemo {
public static void main(String[] args) throws Exception {
Date date = new Date();
System.out.println(date);
}
}
这时候还要进一步去观察Date类中的构造方法:
public Date() {
this(System.currentTimeMillis());
}
public Date(long date) {
fastTime = date;
}
通过以上的源代码分析,可以得出:Date类中只是对long数据的一种包装,所以Date类中一定有日期与long数据类型之间转换的方法:
- 将long转为Date:public Date(long date);
- 将Date转为long:public long getTime();
public class TestDemo {
public static void main(String[] args) throws Exception {
Date date = new Date();
long current = date.getTime(); //Date转long
current += 864000 * 1000; //10天的秒数
System.out.println(new Date(current)); //long 转Date
}
}
long之中可以保存毫秒的数据级,这样方便程序处理。
SimpleDateFormat日期处理类
虽然Date可以获取当前的日期时间,但是默认情况下Date类输出的时间结构并不被我们所习惯,所以我们要进行一个格式化的处理,为了可以格式化日期,在java.text包中提供有SimpleDateFormat程序类:
- 【DateFormat】将日期格式化:public final String format(Date date);
- 【DateFormat】将字符串转为日期:public Date parse(String source) throws ParseException;
- 构造方法:public SimpleDateFormat(String pattern);
- |-日期格式:年(yyyy)、月(MM)、日(dd)、时(HH)、分(mm)、秒(ss)、毫秒(SSS)
我们来看一下格式:
字母 | 描述 | 示例 |
---|---|---|
G | 纪元标记 | AD |
y | 四位年份 | 2001 |
M | 月份 | July or 07 |
d | 一个月的日期 | 10 |
h | A.M./P.M. (1~12)格式小时 | 12 |
H | 一天中的小时 (0~23) | 22 |
m | 分钟数 | 30 |
s | 秒数 | 55 |
S | 毫秒数 | 234 |
E | 星期几 | Tuesday |
D | 一年中的日子 | 360 |
F | 一个月中第几周的周几 | 2 (second Wed. in July) |
w | 一年中第几周 | 40 |
W | 一个月中第几周 | 1 |
a | A.M./P.M. 标记 | PM |
k | 一天中的小时(1~24) | 24 |
K | A.M./P.M. (0~11)格式小时 | 10 |
z | 时区 | Eastern Standard Time |
' | 文字定界符 | Delimiter |
" | 单引号 | ` |
我们来实现一下:
public class TestDemo {
public static void main(String[] args) throws Exception {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
String str = sdf.format(date);
System.out.println(str);
}
}
2020-03-26 17:17:27.641
除了可以将日期转化为字符串之后,也可以将字符串转换为日期格式:
public class TestDemo {
public static void main(String[] args) throws Exception {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
String str = sdf.format(date);
System.out.println(str);
String str1 = "2020-03-26 17:17:27.641";
Date date1 = sdf.parse(str1);
System.out.println(date1);
}
}
2020-03-26 17:22:16.535
Thu Mar 26 17:17:27 CST 2020
如果在进行字符串定义的时候,所使用的日期时间数字超过了自定的一个合理范围,就会自动进行进位处理。
通过这些我们可以发现,String字符串可以向所有类型转换,基本类型、日期类型,所以String是一个万能的类型。
重拾Java高级特性,果然还是Java好学一些,前几天看了一个项目流程,又要学DB,又要学Maven,又要学JDBC,又要学MyBatis,还有一系列的东西,果然还是单纯的Java学起来轻松,当然其他的也不会放松,我们下次见👋