Java面向对象(高级)图解笔记整理/0基础快速入门(四)

本次整理的是枚举类、注解、异常、常用类的知识。

没看过之前笔记的内容,或者学起来有些吃力的朋友们,可以看我之前整理的笔记。

非常详细易懂,对0基础小白友好

Java面向对象(初级)笔记整理/图解快速回顾/图解快速入门(一)

Java面向对象笔记整理/图解快速回顾/图解快速入门(二)

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、常见的包装类和基本数据类型

我做一个表:

包装类与基本数据类型对照表

包装类基本数据类型
Integerint
Doubledouble
Floatfloat
Characterchar
Booleanboolean
Bytebyte
Longlong
Shortshort

包装类名字基本就是其基本数据类型的全称和首字母大写。

其中一个特殊的包装类是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
    }
}

当然,它们还可以从方法中直接获取到年份、月份等信息,从而自定义输出,这里就不赘述。


先写到这,下篇写集合,这是个大头,专门出一期来讲讲吧~

  • 7
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值