JAVA基础知识总结(2025速通)三

文章目录

  • 前言
  • final关键字
    • 1、修饰属性或者局部变量【掌握】
    • 2、方法【掌握】
    • 3、类【掌握】
    • 4、应用场景【理解】
      • 1、常量
      • 2、修饰属性
      • 3、修饰局部变量
      • 4、修饰方法
      • 5、修饰类
  • Object类详解
    • 1、常用方法【掌握】
    • 2、了解的方法【了解】
    • 3、学习反射之后要掌握的方法【了解】
    • 4、学习线程之后要掌握的方法【了解】
  • 字符串处理类
    • 1、java.lang.String类的使用【掌握】
    • 2、正则表达式【了解】
    • 3、StringBuffer【掌握】
    • 4、StringBuilder【掌握】
    • 5、字符串处理类性能分析【理解】
    • 6、String、StringBuffer、StringBuilder区别与联系【记忆】
    • 7、String和基础数据类型的转换【掌握】
  • 日期时间相关类
    • 1、Date【掌握】
    • 2、DateFormat【掌握】
    • 3、Calendar【了解】
  • Wrapper包装类
    • 1、包装类【掌握】
    • 2、自动装箱和自动拆箱【掌握】
    • 3、包装类的缓存分析【记忆】
    • 4、常用方法【记忆】
  • BigInteger类和BigDecimal类
    • 1、BigInteger 类【了解】
    • 2、BigDecimal 类【掌握】
  • 异常
    • 1、异常的概述【记忆】
    • 2、为什么要异常【理解】
    • 3、异常的继承体系【记忆】
    • 4、运行时异常和编译异常【理解】
    • 5、try-catch-finally 异常三连【掌握】
    • 6、throw 抛出异常【掌握】
    • 7、throws 声明异常【掌握】
    • 8、自定义异常【掌握】
  • 结语

前言

本文是Java基础第三期,包括如下知识点:
JAVA基础知识总结(2025速通)三_第1张图片

final关键字

表示最终的,最后的,不可改变的。在程序中是不可变的,可以用final修饰。final可以修饰属性、局部变量、方法、类。

1、修饰属性或者局部变量【掌握】

表示属性或局部变量一旦初始化好就不允许修改它的值,这里的不允许修改指的是两种情况,基础数据类型是值不可改变,引用数据类型是指引用不可改变。修饰属性或局部变量的特点是一样的,但是修饰局部变量时还有特殊的用途,在应用内部类时如果要引用局部变量,那么局部变量必须是final修饰的。(详见内部类章节)

final修饰的属性如果同时被static修饰,那么它代表静态且不可改变的,在声明常量时会这样应用。

2、方法【掌握】

final修饰的方法不能被重写。如果用final修饰了一个方法说明这个方法已经满足了需求,为防止被改错,不允许他人修改,子类重写编译会报错。

3、类【掌握】

final修饰的类不能被继承。如果用final修饰一个类,代表这个类已经不需要进行拓展了,不允许任何类继承。

例如:String字符串类,没有必要对字符串进行拓展了,而且String类有很多特殊的处理,用final修饰,还可以防止了有人继承后写错。

    public final class Demo001 {
        public final int i = 0;
        public static final double D = 3.1415926;
        public final int add(int a, int b){
            return a + b;
        }
    }

4、应用场景【理解】

1、常量

常量分两种,预定义常量和自定义常量。

预定义常量,系统已经定义好的常量,如数字1、字符串"abc"等。

自定义常量,是开发者自己定义的常量,一般用static final修饰,如:public static final String COMMA = “,”;

自定义常量在开发中一般用于定义一些通用的数据,类似于查字典时的拼音,固定的写法会定义成常量,需要调用时所有人都调用常量。如果需要修改,修改代码中的常量值,所有的其它代码都不要再修改了。

2、修饰属性

由于final修饰的属性不可改变,所以final的属性要在构造方法中初始化。

  • 可以用于防止后续代码修改属性值,如自定义异常中的属性,多数情况不允许后续修改,可以定义为final的。
  • 可以用于在构造方法中初始化一些属性,保证在后续调用时该属性一定被初始化过,例如某属性是核心属性,贯穿整个类。

3、修饰局部变量

需要在声明时就赋值,多数都是内部类时应用。由于内部类调用局部变量时,此局部变量必须是不允许被修改的,final可以保证不可再次修改,而且不用final修饰会编译报错。

4、修饰方法

很少使用,多数是因为要操作底层逻辑、数据结构或内存等情况会使用,和native关键字配合使用的情况相对多些。

5、修饰类

很少使用,一般在被继承后可能导致错误时使用,例如String类,字符串在内存处理时有特殊的逻辑,如果有子类,可能会导致整体混乱,出现严重错误,所以用final修饰,拒绝被继承。

Object类详解

所有类的超类,Java中所有的类都直接或间接的继承了Object,Object是所有类的老祖宗。

1、常用方法【掌握】

1、equals(Object obj)

比较两个对象是否相同,相同返回true,否则返回false。

    public boolean equals(Object obj) {
        return (this == obj);
    }

“==”比较的是两个对象的地址,两个对象分别创建(new),即使值相同,返回也是false,因为创建对象时都是在内存中开辟了新的空间,所以对象的地址不相同。

equals方法会根据实际情况进行重写,例如String类,字符串是否相同往往指的就是值是否相同,基本没有比较两个地址是否相同的应用场景,所以重写了equals方法。

再比如自定义一个Citizen(公民)类,判断是否是同一个公民可以通过身份证号码判断,没有必要用身高去比对,而且身高也是会发生变化的,由此可以重新equals方法。

public class Citizen {

    /**
     * 身份证号码
     */
    private String idNo;
    private Double height;

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        /*getClass() != o.getClass() 是判断参数o对象是否是Citizen类的对象,后面会讲解getClass方法*/
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Citizen citizen = (Citizen) o;
        return idNo != null ? idNo.equals(citizen.idNo) : citizen.idNo == null;
    }

}

如果真的有需求要判断两个对象地址是否相同,仍然可以用“==”做判断。

2、hashCode()

返回对象的哈希码,可以理解成是当前对象的内存地址。注意:hashCode不一定是内存地址,为了方便理解,可以按照内存地址去学习。

public native int hashCode();

native关键字修饰的方法调用的是本地原生方法,简单说就是调用了非Java语言编写的、基于本地操作系统的方法。

hashCode就是一个调用了由本地函数完成的方法,返回当前对象的内存地址。

为什么又说不一定是内存地址呢?

hashCode第一次调用时返回的是对象的内存地址,后续调用时返回值不会变化,但不一定是内存地址了,比如触发了垃圾回收。

Java在进行垃圾回收时会将内存中对象的地址重新分配,这样会导致内存地址发生了变化。而第一次调用hashCode方法的返回值会被存储起来,之后的调用直接返回这个值就保证了hashCode值不变。这样做的目的是为了程序中利用hashCode做运算时不会发生多次调用返回值不同的问题,例如HashMap(集合中学习)的原理就用到了hashCode方法。

重写了equals方法后,也要重写hashCode方法,以满足Object.hashCode通用约定。equals方法比较两个对象的地址,hashCode方法返回对象的地址,两个方法的设计之初就是相互影响的。这样做的目的是为了满足程序中利用hashCode算法实现的逻辑和功能。例如HashMap的实现原理就利用到了hashCode和equals,具体在学习HashMap时详细说明。

Object.hashCode通用约定(摘自《Effective Java》):

  • 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,那么,对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,这个整数可以不同,即这个应用程序这次执行返回的整数与下一次执行返回的整数可以不一致。
  • 如果两个对象根据equals(Object)方法是相等的,那么调用这两个对象中任一个对象的hashCode方法必须产生同样的整数结果。
  • 如果两个对象根据equals(Object)方法是不相等的,那么调用这两个对象中任一个对象的hashCode方法,不要求必须产生不同的整数结果。然而,程序员应该意识到这样的事实,对于不相等的对象产生截然不同的整数结果,有可能提高散列表(hashtable)的性能。

3、toString()

将对象转化成字符串,格式是:类名+@+哈希码,类名是要包括包名,哈希码就是调用了hashCode方法。

    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

日常开发也会时常重写toString方法,以方便程序调试、输出日志等。通常重写toString方法会遵循以下几个原则:

  • 简洁性:避免冗长的输出,只包含关键信息。
  • 一致性:如果有多个类,保持toString的格式一致,便于理解和管理。
  • 安全性:不要在toString中暴露敏感信息。

2、了解的方法【了解】

1、clone()

克隆方法,可以复制一个对象,创造一个相同的新对象。

    protected native Object clone() throws CloneNotSupportedException;

克隆有两种,浅克隆和深克隆。

1、浅克隆

基础数据类型(包括像String这种特殊的引用数据类型)会开辟新的内存空间,引用数据类型不会开辟新的空间,而是将对象的引用复制过来。

例如:

Computer

public class Computer implements Cloneable {

    public double price;
    public String name;
    public Cpu cpu;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Computer{" +
                "price=" + price +
                ", name='" + name + '\'' +
                ", cpu=" + cpu +
                '}';
    }
}

Cpu

public class Cpu {

    public String name;

    @Override
    public String toString() {
        return "Cpu{" +
                "name='" + name + '\'' +
                '}';
    }
}

Main

public class Main {

    public static void main(String[] args) throws CloneNotSupportedException {
        Computer c1 = new Computer();
        c1.name = "computer 1";
        c1.price = 12345.67;
        Cpu cpu = new Cpu();
        cpu.name = "intel";
        c1.cpu = cpu;
        Computer c2 = (Computer) c1.clone();
        System.out.println(c1 == c2);
        System.out.println(c1.cpu == c2.cpu);
        System.out.println("----------------------------------");
        System.out.println(c1);
        System.out.println(c2);
        System.out.println("----------------------------------");
        c2.name = "computer 2";
        c2.price = 2345.67;
        c2.cpu.name = "amd";
        System.out.println("----------------------------------");
        System.out.println(c1);
        System.out.println(c2);
    }

}

输出结果:

false
true
----------------------------------
Computer{price=12345.67, name='computer 1', cpu=Cpu{name='intel'}}
Computer{price=12345.67, name='computer 1', cpu=Cpu{name='intel'}}
----------------------------------
----------------------------------
Computer{price=12345.67, name='computer 1', cpu=Cpu{name='amd'}}
Computer{price=2345.67, name='computer 2', cpu=Cpu{name='amd'}}

修改了c2的cup属性值,c1的cpu属性值也被修改了,由此可以看出cpu是引用克隆。而c1的price和name没有同时发生修改,基础数据类型的值并不是引用克隆。

2、深克隆

基础数据类型和引用数据类型的属性都会开辟新的内存空间。

例如:

Computer

public class Computer implements Cloneable {

    public double price;
    public String name;
    public Cpu cpu;

    @Override
    protected Computer clone() throws CloneNotSupportedException {
        Object resultObj = super.clone();
        Computer result = (Computer) resultObj;
        result.cpu = this.cpu.clone();
        return result;
    }

    @Override
    public String toString() {
        return "Computer{" +
                "price=" + price +
                ", name='" + name + '\'' +
                ", cpu=" + cpu +
                '}';
    }
}

Cpu

public class Cpu implements Cloneable {

    public String name;

    @Override
    protected Cpu clone() throws CloneNotSupportedException {
        return (Cpu) super.clone();
    }

    @Override
    public String toString() {
        return "Cpu{" +
                "name='" + name + '\'' +
                '}';
    }
}

Main

public class Main {

    public static void main(String[] args) throws CloneNotSupportedException {
        Computer c1 = new Computer();
        c1.name = "computer 1";
        c1.price = 12345.67;
        Cpu cpu = new Cpu();
        cpu.name = "intel";
        c1.cpu = cpu;
        Computer c2 = c1.clone();
        System.out.println(c1 == c2);
        System.out.println(c1.cpu == c2.cpu);
        System.out.println("----------------------------------");
        System.out.println(c1);
        System.out.println(c2);
        System.out.println("----------------------------------");
        c2.name = "computer 2";
        c2.price = 2345.67;
        c2.cpu.name = "amd";
        System.out.println("----------------------------------");
        System.out.println(c1);
        System.out.println(c2);
    }

}

输出结果:

false
false
----------------------------------
Computer{price=12345.67, name='computer 1', cpu=Cpu{name='intel'}}
Computer{price=12345.67, name='computer 1', cpu=Cpu{name='intel'}}
----------------------------------
----------------------------------
Computer{price=12345.67, name='computer 1', cpu=Cpu{name='intel'}}
Computer{price=2345.67, name='computer 2', cpu=Cpu{name='amd'}}

修改了c2的三个属性值,c1的属性值并没有被修改了,由此可以看出所有属性克隆时都在内存中开辟了新的空间。

2、finalize()

此方法在回收垃圾时调用,是当垃圾回收开始时自动调用的。垃圾回收的触发有自己的机制,不受开发者控制,即使调用System.gc()方法也无法手动调用垃圾回收。

    protected void finalize() throws Throwable { }

3、registerNatives()

是一个native修饰的方法,该方法牵扯掉底层加载动态库,暂时只需要了解它是本地注册的方法即可。

    private static native void registerNatives();

3、学习反射之后要掌握的方法【了解】

1、getClass()

获取当前类的类。

    public final native Class getClass();

4、学习线程之后要掌握的方法【了解】

1、notify()

唤醒线程,在学习线程之后会详细说明。

    public final native void notify();

2、notifiall()

唤醒所有线程,在学习线程之后会详细说明。

    public final native void notifyAll();

3、wait()

wait方法有三个,都是方法的重载,在学习线程之后会详细说明。

    public final native void wait(long timeout) throws InterruptedException;

    public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos >= 500000 || (nanos != 0 && timeout == 0)) {
            timeout++;
        }

        wait(timeout);
    }

    public final void wait() throws InterruptedException {
        wait(0);
    }

字符串处理类

Java中处理字符串常用的类有三个:String、StringBuffer、StringBuilder。String是最基础的字符串,也是最常用的字符串。StringBuffer和StringBuilder有一些特性,要看实际情况判断要用什么。

1、java.lang.String类的使用【掌握】

String是java表示字符串的类,被称为不可变字符串,同时它也是一个final修饰的类,不可以被继承。

String是Java中操作最多的数据类型,Java对String进行了优化和特殊处理,在内存中开辟了一个针对String的区域,被称为字符串常量池。通过特殊的处理以达到全局角度效率最高、资源消耗最低。

不可变字符串指的是字符串一旦创建就会在字符串常量池中创建一个字符串,如果对这个字符串修改,会在常量池中创建一个修改后的新字符串,并不会在原字符串上直接修改。

1、常用构造方法

String():定义一个没有字符的字符串,相当于""。

String(String):通过一个字符串,创建一个新的字符串对象,这两个字符串的内容是相同的,但内存地址不同。

String(byte[]):byte是Java中直接处理的最小单元,它不仅可以存放一个整型,它也可以表示二进制,而一定数量的二进制就可以表示一个字符串,所以提供了byte[]的构造方法。

String(byte[], 编码集):不同编码集表示字符内容的二进制是不相同的,如果解析的编码集不匹配会出现中文乱码问题,编码集参数可以指定具体用什么编码集解析。

String(char[]):用一个字符数组创建一个字符串。

2、常用方法

equals(Object):比较两个字符串的值是否相同。注意和==的区别,==比较的是地址,equals比较的是值。

length():字符串的长度。

replace(old,new):将字符串中所有的old,替换成new。

replaceAll(正则表达式,str):根据正则表达式,替换字符串中所有符合正则表达式的字符串。注意正则表达式,因为正则表达式中有一些特殊的字符,比如说“.”,在正则表达式中代表所有的字符,如果替换会替换所有的字符,而不是替换“.”这个字符,如果要替换点,需要用转移字符,详见转义字符。

split(正则表达式):按照正则表达式拆分字符串,返回拆分后的字符串数组。

substring(begin, end):截取字符串,从begin开始截,一直截到end,满足左包含右不包含规约。

3、了解方法

charAt(int):返回int索引的字符。

compareTo(String):比较两个字符串的字典顺序大小。

compareToIgnoreCase(String) :不区分大小写比较两个字符串的字典顺序大小。

concat(String):连接字符串,平时用的比较多的是连接符(+)进行拼接。

contains(str):判断这个字符串是否包含str字符串。

endsWith(String):字符串是否以String结尾。

equalsIgnoreCase(String):不区分大小写的比较两个字符串的值是否相同。

hashCode():哈希码。

indexOf(String):String在字符串中第一次出现的索引。

lastIndexOf(String):从后向前数,String在字符串中第一次出现的索引。注意,是从后向前找第一次出现的索引,这个索引值还是从前向后数的。

startWith(String):判断字符串是否以String开头。

toCharArray():将字符串转化成char数组。

valueOf(参数):将参数转化成字符串。所有的数据类型,都可以写成:对象+“”,这代表字符串连接,会自动将+前的对象转化成字符串,其实是调用了它的toString方法。

toLowerCase():将字符串中所有的大写字母转换成小写。

toUpperCase():将字符串中所有的小写字母转换成大写。

trim():去掉字符串中前后所有的空格。

public class TestString {
    public static void main(String[] args) {
        testString();
    }
    private static void testString() {
        System.out.println("------------构造方法------------");
        String s1 = "abc";
        String s2 = new String(s1);
        byte[] byteArr = s1.getBytes();
        String s3 = new String(byteArr);
        System.out.println("s1 = " + s1);
        System.out.println("s2 = " + s2);
        System.out.println("s3 = " + s3);
        System.out.println("------------其它方法------------");
        System.out.println("s1.charAt(0) = " + s1.charAt(0));
        String s4 = "A";
        String s5 = "a";
        String s6 = "B";
        System.out.println("s4.compareTo(s5) = " + s4.compareTo(s5));
        System.out.println("s4.compareTo(s6) = " + s4.compareTo(s6));
        System.out.println("s4.compareToIgnoreCase(s5) = " + s4.compareToIgnoreCase(s5));
        System.out.println("s4.compareToIgnoreCase(s6) = " + s4.compareToIgnoreCase(s6));
        System.out.println("s1.concat(s4) = " + s1.concat(s4));
        System.out.println("s1.contains(s4) = " + s1.contains(s4));
        System.out.println("s1.contains(s5) = " + s1.contains(s5));
        String s7 = "c";
        System.out.println("s1.endsWith(s7) = " + s1.endsWith(s7));
        System.out.println("s1 == s2 -->" + (s1 == s2));
        System.out.println("s1.equals(s2) -->" + s1.equals(s2));
        System.out.println("s4.equals(s5) -->" + s4.equals(s5));
        System.out.println("s4.equalsIgnoreCase(s5) -->" + s4.equalsIgnoreCase(s5));
        String s8 = "abcabc";
        System.out.println("s8.indexOf(s5) -->" + s8.indexOf(s5));
        System.out.println("s8.lastIndexOf(s5) -->" + s8.lastIndexOf(s5));
        System.out.println("s8.length() -->" + s8.length());
        System.out.println("s8.replace(s5, s4) -->" + s8.replace(s5, s4));
        String s9 = "\\w";
        System.out.println("s8.replaceAll(s9, s4) -->" + s8.replaceAll(s9, s4));
        String[] s8Arr = s8.split(s5);
        System.out.print("s8.split(s5) -->");
        for(String s : s8Arr){
            System.out.print(s + "\t");
        }
        System.out.println();
        System.out.println("s8.startWith(s5) -->" + s8.startsWith(s5));
        System.out.println("s8.substring(1, 4) -->" + s8.substring(1, 4));
        char[] charArr = s8.toCharArray();
        System.out.print("s8.toCharArray() -->");
        for(char s : charArr){
            System.out.print(s + "\t");
        }
        System.out.println();
        String s10 = "abcABC";
        System.out.println("s10.toLowerCase() -->" + s10.toLowerCase());
        System.out.println("s10.toUpperCase() -->" + s10.toUpperCase());
        String s11 = "    abc   ABC   ";
        System.out.println(s11 + "--> s11.trim() -->" + s11.trim());
        int i = 123;
        System.out.println("String.valueOf(i) -->" + String.valueOf(i));
        System.out.println("String.valueOf(i)相当于i + \"\" -->" + (i + ""));

    }
}

------------构造方法------------
s1 = abc
s2 = abc
s3 = abc
------------其它方法------------
s1.charAt(0) = a
s4.compareTo(s5) = -32
s4.compareTo(s6) = -1
s4.compareToIgnoreCase(s5) = 0
s4.compareToIgnoreCase(s6) = -1
s1.concat(s4) = abcA
s1.contains(s4) = false
s1.contains(s5) = true
s1.endsWith(s7) = true
s1 == s2 -->false
s1.equals(s2) -->true
s4.equals(s5) -->false
s4.equalsIgnoreCase(s5) -->true
s8.indexOf(s5) -->0
s8.lastIndexOf(s5) -->3
s8.length() -->6
s8.replace(s5, s4) -->AbcAbc
s8.replaceAll(s9, s4) -->AAAAAA
s8.split(s5) -->	bc	bc
s8.startWith(s5) -->true
s8.substring(1, 4) -->bca
s8.toCharArray() -->a	b	c	a	b	c
s10.toLowerCase() -->abcabc
s10.toUpperCase() -->ABCABC
    abc   ABC   --> s11.trim() -->abc   ABC
String.valueOf(i) -->123
String.valueOf(i)相当于i + "" -->123

4、转义字符

在字符串中有一些有特殊意义的字符串,例如回车\n、制表符\t等,这个反斜杠(\)就是转移字符,它的作用是处理一下特殊字符。

比如在一个字符串如果需要用引号("),则需要用转移字符,否则会被当做是声明字符串的另外一个引号,做了匹配就会报错。

System.out.println("String.valueOf(i)相当于i + \"\" -->" + (i + ""));

2、正则表达式【了解】

又叫做规则表达式,它可以表示一系列字符串的规则,制定好规则后,用正则表达式校验,解决开发中一些不确定但需要验证的字符串,例如:邮箱、手机号等。

正则表达式在前端应用比较广泛,所以前端工程师对正则表达式的要求相对高,后台开发人员要知道正则表达式是什么,知道一些简单的语法和符号即可。

需要掌握的东西:

预定义字符类

. 任何字符(与行结束符可能匹配也可能不匹配)

\d 数字:[0-9]

\D 非数字: [^0-9]

\s 空白字符:[ \t\n\x0B\f\r]

\S 非空白字符:[^\s]

\w 单词字符:[a-zA-Z_0-9]

\W 非单词字符:[^\w]

字符

x 字符 x

\ 反斜线字符

\t 制表符 (‘\u0009’)

\n 新行(换行)符 (‘\u000A’)

\r 回车符 (‘\u000D’)

3、StringBuffer【掌握】

是带有缓存区的线程安全的字符序列,可以表示一个字符串。它和String之间区别在于StringBuffer有缓存区,而且在进行修改操作时StringBuffer都是操作原始对象,而String都会去创建新的对象,修改变量指向地址。这样String的效率要比StringBuffer更低,而且修改次数越多效率越低。

1、构造方法

StringBuffer():创建一个16个缓存区空字符串的对象。

StringBuffer(String):创建一个内容是String的对象,缓冲区默认是16。

2、常用方法

append(String):在对象后追加String字符串。

length():表示字符串的长度。

public class TestStringBuffer {
    public static void main(String[] args) {
        StringBuffer stringBuffer = new StringBuffer("a");
        System.out.println(stringBuffer + "的长度是:" + stringBuffer.length());
        stringBuffer.append("bc");
        System.out.println(stringBuffer + "的长度是:" + stringBuffer.length());
    }
}

3、了解方法

delete(start,end):删除[start,end)索引的字符串。

insert(index,String):在index索引处插入String。

reverse():反转字符串,abc变成cba。

4、StringBuilder【掌握】

有缓冲区的字符串,它和StringBuffer很相似,两个类的方法都是一样的,区别在于StringBuffer是线程安全的,StringBuilder是线程不安全的。

1、相似的部分

构造方法、常用方法和理解方法都参照StringBuffer即可。

2、应用场景

如果确定不涉及到线程安全问题时用StringBuilder,否则用StringBuffer。StringBuilder的效率要比StringBuffer效率高,因为StringBuilder不需要考虑线程安全问题,省去了一些操作。

最简单的判断原则是:如果是局部变量就不涉及线程安全问题,选用StringBuilder,如果是属性,可能会涉及线程安全问题,优先考虑StringBuffer。

5、字符串处理类性能分析【理解】

public class Test {
    public static void main(String[] args) {
        int length = 100000;
        long start = System.currentTimeMillis();
        testString(length);
        long fir = System.currentTimeMillis();
        testStringBuffer(length);
        long sec = System.currentTimeMillis();
        testStringBuilder(length);
        long thi = System.currentTimeMillis();
        System.out.println("String --> " + (fir - start));
        System.out.println("StringBuffer --> " + (sec - fir));
        System.out.println("StringBuilder --> " + (thi - sec));
    }
    public static void testString(int length){
        String s = "a";
        for(int i = 0; i < length; i++){
            s += i;
            System.out.println("String --> " + i);
        }
    }
    public static void testStringBuffer(int length){
        StringBuffer s = new StringBuffer("a");
        for(int i = 0; i < length; i++){
            s.append(i);
            System.out.println("StringBuffer --> " + i);
        }
    }
    public static void testStringBuilder(int length){
        StringBuilder s = new StringBuilder("a");
        for(int i = 0; i < length; i++){
            s.append(i);
            System.out.println("StringBuilder --> " + i);
        }
    }
}

String --> 41299
StringBuffer --> 927
StringBuilder --> 829

相差四十余倍,循环次数越多,相差越大。

6、String、StringBuffer、StringBuilder区别与联系【记忆】

  • String类是不可变类,即一旦一个String对象被创建后,包含在这个对象中的字符序列是不可改变的,直至这个对象销毁。
  • StringBuffer类则代表一个字符序列可变的字符串,可以通过append、insert、reverse、setChartAt、setLength等方法改变其内容。一旦生成了最终的字符串,调用toString方法将其转变为String。
  • JDK1.5新增了一个StringBuilder类,与StringBuffer相似,构造方法和方法基本相同。不同是StringBuffer是线程安全的,而StringBuilder是线程不安全的,所以性能略高。通常情况下,创建一个内容可变的字符串,应该优先考虑使用StringBuilder。
        StringBuilder:JDK1.5开始 效率高 线程不安全
        StringBuffer:JDK1.0开始 效率低 线程安全

7、String和基础数据类型的转换【掌握】

1、String转基础数据类型。

因为封装类有自动装箱和拆箱功能,所以只需要把String转换成封装类,就自动转换成基础数据类型了。

可以调用封装类的valueOf方法将String转换成封装类。

也可以调用封装类的构造方法,封装类都有一个String参数的构造方法。

还可以调用封装类的静态方法parseXXX(String)(XXX是对应的数据类型)。

2、基础数据类型转String

可以调用String的valueOf(基础数据类型)。

基础数据类型+“”,会自动转换成String。任何的引用数据类型+“”(引号之间是空的,什么都没有),都会转换成字符串,它自动调用了toString方法。

以int为例:

int i = 0;
String s = i + "";
/*相当于:*/
int i = 0;
Integer _i = i;
String s = _i.toString() + "";

日期时间相关类

1、Date【掌握】

表示时间的类,继承了Object类,实现了序列化、克隆和比较器接口。java.util.Date不是final的,它是可以被继承的。有一个子类是java.sql.Date,注意这个类,平时开发不用这个类,由于两个类的名字相同,而且是父子类关系,所以很容易犯错,引包时也要注意。

计算机中表示时间的方式是用一个long类型保存一个整数,这个整数时从1970年1月1日00:00:00:000开始,每过一毫秒加一,从0时区开始,我们是东8区,所以当这个long值为0时,表示的是8点。

1、构造方法

无参构造方法:当前时间。

参数是long的构造方法:表示long毫秒的时间。

2、常用方法

after:是否是在某日期时间之后。

before:是否是在某日期时间之前。

equals:是否和某日期时间相同。equals方法重写了Object中的方法,比较的是两个对象表示的日期时间是否是同一个。

compareTo:比较两个时间,如果当前时间比参数时间大,返回正数,如果小,返回负数,如果相同返回0。

getTime:返回日期时间的毫秒数。

public class TestDate {
    public static void main(String[] args) {
        Date d1 = new Date();
        System.out.println(d1);
        Date d2 = new Date(0);
        System.out.println(d2);
        System.out.println(d1.after(d2));
        System.out.println(d1.before(d2));
        System.out.println(d1.getTime());
        Date d3 = new Date(d2.getTime());
        System.out.println(d2 == d3);
        System.out.println(d2.equals(d3));
        Date d4 = new Date(d1.getTime() + 100000);
        System.out.println(d1.compareTo(d2));
        System.out.println(d1.compareTo(d4));
        System.out.println(d1.compareTo(d1));
    }
}

Sat Feb 15 11:13:33 CST 2020
Thu Jan 01 08:00:00 CST 1970
true
false
1581736413409
false
true
1
-1
0

2、DateFormat【掌握】

DateFormat是日期格式类,它是一个抽象类,是不能被实例化对象的。在DateFormat中定义了很多属性和方法,为它的子类服务。SimpleDateFormat是DateFormat的子类,它是一个可以被实例化的类,一般日期格式化时都用SimpleDateFormat。

SimpleDateFormat

1、构造方法

有一个字符串参数的构造方法,这个字符串表示格式化的规则。

最常用的格式是:yyyy-MM-dd HH:mm:ss和yyyy-MM-dd。

2、常用方法

format(日期对象):将一个日期对象格式化成一个字符串,格式化的规则是创建时传入的字符串。

parse(String):将字符串按照指定的格式转化成一个日期对象。

3、格式化规则

public class TestDateFormat {
    public static void main(String[] args) throws ParseException {
        DateFormat dfDate2Str = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
        String dateStr = dfDate2Str.format(new Date());
        System.out.println(dateStr);
        DateFormat dfStr2Date = new SimpleDateFormat("yyyy-MM-dd");
        Date strDate = dfStr2Date.parse("2020-02-15");
        System.out.println(strDate);
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d = df.parse("2020-02-15 13:13:13");
        System.out.println(d);
        String str = df.format(new Date());
        System.out.println(str);
    }
}

3、Calendar【了解】

Calendar是一个用于处理日期和时间的类。它提供了许多方法来获取和设置日期、时间以及执行日期和时间的计算操作。Calendar类是一个抽象类,通过调用其静态方法getInstance()来获取一个Calendar对象的实例。

Calendar常用方法及示例

public static void main(String[] args){
    Calendar calendar = Calendar.getInstance();
    System.out.println("年:"+calendar.get(Calendar.YEAR));
    System.out.println("月:"+(calendar.get(Calendar.MONTH) + 1));
    System.out.println("日:"+calendar.get(Calendar.DAY_OF_MONTH));
    System.out.println("时:"+calendar.get(Calendar.HOUR_OF_DAY));
    System.out.println("分:"+calendar.get(Calendar.MINUTE));
    System.out.println("秒:"+calendar.get(Calendar.SECOND));
    System.out.println("星期:"+calendar.get(Calendar.DAY_OF_WEEK));
}

年:2024
月:7
日:9
时:14
分:43
秒:7
星期:3

Wrapper包装类

包装类又叫封装类,Java的数据类型有两种,基础数据类型是基础的,从狭义的角度看它们不是面向对象的,在引用数据类型中,有八个引用数据类型对应了八个基础数据类型,这个八个引用数据类型就叫做基础数据类型的封装类。

封装类是final修饰的类,不能被继承。

1、包装类【掌握】

六个基础数据类型的封装类都是它们的首字母大写,只有int的封装类是Integer,char的封装类是Character。

基础数据类型 包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

2、自动装箱和自动拆箱【掌握】

基础数据类型和它的封装类是不同的数据类型,而且是两种体现,基础数据类型的封装类是不可以直接赋值的,但是从JDK5开始,优化了这部分开发的简便性,基础数据类型和它的封装类可以相互赋值。这个语法被称为自动装箱和拆箱。

public class testInteger {
    public static void main(String[] args) {
        /*基础数据类型赋值*/
        int i1 = 1;
        /*封装类赋值,用的是自动装箱*/
        /*在JDK5之前版本,应用写成:*/
        /*Integer i2 = new Integer(2);*/
        /*否则报错*/
        Integer i2 = 2;
        /*封装类赋值,用的是自动装箱*/
        Integer i3 = i1;
        /*自动拆箱,将封装类赋值给基础数据类型*/
        i1 = i2;
    }
}

3、包装类的缓存分析【记忆】

Integer有缓存机制,这个缓存区的范围是byte的范围,在这个范围内的数据用基础数据类型表示,地址是相同的,超出范围会用封装类创建新的对象,地址就不相同了。

public static void testInt(){
    Integer i1 = 1;
    Integer i2 = 1;
    System.out.println("i1 == i2 --> " + (i1 == i2));
    System.out.println("i1.equals(i2) --> " + i1.equals(i2));
    Integer i3 = 1;
    Integer i4 = new Integer(1);
    System.out.println("i3 == i4 --> " + (i3 == i4));
    System.out.println("i3.equals(i4) --> " + i3.equals(i4));
    Integer i5 = 128;
    Integer i6 = 128;
    System.out.println("i5 == i6 --> " + (i5 == i6));
    System.out.println("i5.equals(i6) --> " + i5.equals(i6));
}

i1 == i2 --> true
i1.equals(i2) --> true
i3 == i4 --> false
i3.equals(i4) --> true
i5 == i6 --> false
i5.equals(i6) --> true

4、常用方法【记忆】

包装类的常用方法不多,实际工作中还是基础数据类型用的多,而包装类多应用在VO类中。

1、常用方法示例

public static void main(String[] args) {
        /*compareTo:只返回三个值:要么是0,-1,1*/
        Integer i1 = new Integer(6);
        Integer i2 = new Integer(12);
        System.out.println(i1.compareTo(i2));
        /*intValue() :作用将Integer--->int*/
        Integer i3 = 130;
        int i = i3.intValue();
        System.out.println(i);
        /*parseInt(String s) :String--->int:*/
        int i4 = Integer.parseInt("12");
        System.out.println(i4);
        /*toString:Integer--->String*/
        Integer i5 = 130;
        System.out.println(i5.toString());

    }

2、VO类

VO类有称为实体类,用于定义一个程序中管理的实体,例如学生成绩管理系统,可以定义一个Student类。标准的VO类中只有属性和属性的get/set方法,最多在有toString、equals和hashCode方法的重写。

封装类在VO中应用最为广泛,要求所有的属性不能是基础数据类型,必须是它的封装类。

public class Student{
    private String name;
    private Integer age;
    private Double height;
}

BigInteger类和BigDecimal类

1、BigInteger 类【了解】

BigInteger是存放大整数的,因为整型的范围是有限的,实际应用中可能出现超出范围的情况,这种情况可以用BigInteger解决。

1、构造方法

BigInteger(String):传入一个字符串,这个字符串表示一个10进制的数

2、常用方法

subtract(BigInteger):减

add(BigInteger):加

multiply(BigInteger):乘

divide(BigInteger):除

public class TestBigInteger {
    public static void main(String[] args) {
        BigInteger i1 = new BigInteger("10000000000000000000000");
        BigInteger i2 = new BigInteger("2");
        System.out.println(i1.add(i2));
        System.out.println(i1.subtract(i2));
        System.out.println(i1.multiply(i2));
        System.out.println(i1.divide(i2));
    }
}

10000000000000000000002
9999999999999999999998
20000000000000000000000
5000000000000000000000

2、BigDecimal 类【掌握】

是用来表示更高精度的浮点型的数据类型,float的精度是7位,double是15位,在金融、医疗等领域开发时,这个精度是远远不够用的,可以用BigDecimal处理。

1、构造方法

BigDecimal(String):创建一个用String表示的数字的对象。

注意:有很多方法重载,可以传入整型、浮点型,不能用传入浮点型的构造方法,因为浮点型在计算机中是一个近似值,如果用浮点型,创建出来的对象也是一个近似值,浮点型应该采用valueOf方法。

2、常用方法

valueOf(double):将double转化成BigDecimal对象,是一个静态方法。

add(BigDecimal):加。

subtract(BigDecimal):减。

multiply(BigDecimal):乘。

divide(BigDecimal,int1,int2):除,保留int1位小数点,int2是进制方式(进一法、退一法、四舍五入等,用BigDecimal类中的常量表示)。

public class TestBigDecimal {
    public static void main(String[] args) {
        BigDecimal b1 = new BigDecimal("100");
        System.out.println(b1);
        BigDecimal b2 = new BigDecimal("1.5");
        System.out.println(b2);
        System.out.println(b1.add(b2));
        System.out.println(b1.subtract(b2));
        System.out.println(b1.multiply(b2));
        System.out.println(b1.divide(b2, 5, ROUND_CEILING));
        System.out.println(b1.divide(b2, 5, ROUND_FLOOR));
        BigDecimal b3 = new BigDecimal(0.1);
        BigDecimal b4 = BigDecimal.valueOf(0.1);
        System.out.println(b3);
        System.out.println(b4);
    }
}

100
1.5
101.5
98.5
150.0
66.66667
66.66666
0.1000000000000000055511151231257827021181583404541015625
0.1

异常

1、异常的概述【记忆】

在程序中,错误可能产生于程序员没有预料到的各种情况,或者超出了程序员可控范围,例如用户错误操作,或者没有数据、文件等情况,为了能够及时地有效地处理程序中的这些情况,Java引入了异常概念处理问题。

2、为什么要异常【理解】

在实际开发中,可能会遇到一些需要特殊的情况需要处理,有多种方式都可以处理,举一个例子说明。

用户输入两个整数,输出相除的结果。代码如下:

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请录入第一个数:");
        int num1 = sc.nextInt();
        System.out.println("请录入第二个数:");
        int num2 = sc.nextInt();
        if(num2 == 0){
            System.out.println("对不起,除数不能为0");
        }else{
            System.out.println("商:"+num1/num2);
        }
    }

用if-else堵漏洞的缺点:

  • 代码臃肿,业务代码和处理异常的代码混在一起。

  • 可读性差

  • 程序员需要花费大量的精力来维护这个漏洞

  • 程序员很难堵住所有的漏洞。

      public static void main(String[] args){
          Scanner sc = new Scanner(System.in);
          System.out.println("请录入第一个数:");
          int num1 = sc.nextInt();
          System.out.println("请录入第二个数:");
          int num2 = sc.nextInt();
          try{
              System.out.println("商:"+num1/num2);
          }catch (ArithmeticException e){
              System.out.println("对不起,除数不能为0!");
          }
      }
    

通过异常的方式处理可以将业务和异常处理代码分开,让代码可读性变强,尤其在更复杂的业务情况,显得尤为明显。

3、异常的继承体系【记忆】

在程序运行中,不一定出现问题就是异常,也有可能是错误,如何区分,需要先了解异常的基础体系。

Throwable是Java中所有可以抛出的类的超类,在JDK中,它有两个子类,分别是错误(Error)和异常(Exception),其中错误是大问题,不仅需要代码层面解决的问题,一般这种情况需要修改代码,修改量可能比较大。异常是Java提供处理特殊情况的机制,就是用代码解决的问题,可以通过程序处理异常。

异常又分为运行时异常和编译时异常。

4、运行时异常和编译异常【理解】

1、编译时异常

是JDK要求程序员必须手动处理的异常,一般是一些比较大较复杂的问题会被声明为编译时异常,如果不手动处理编译会报错。

一般处理编译时异常的方式有两种:

  • 向上一层抛出。有句话叫做:“有问题找领导”,遇到你解决不了的问题,就要汇报给领导,寻求领导的帮助。在程序中,如果这个异常不适合在当前代码解决,可以抛出这个异常,交给调用这个方法的上层处理。
  • 捕获异常。如果程序出现了异常,这个异常可以用代码去捕获它,捕获到了异常就说明真的出现了异常,那么可以在捕获之后处理这个异常。如果没有捕获到,说明没有发生异常,正常执行程序即可。

2、运行时异常

这些异常都很小,不必处理,像马路上的石子,开车时直接压过去就可,不像编译时异常,是一个大石头,开车时必须处理这个石头。

如何处理运行时异常:

  • 不处理。因为运行时异常危害比较小,所以有时可以不处理,当然不处理时也要在逻辑上思考不会出现,如果出现要如何通过代码解决这个异常,才会选用这种方法。
  • 在执行可能出现异常代码之前,进行各种校验,避免出现异常。
  • 运行时异常同样可以用处理编译时异常的处理方案,但是很少会用处理编译时异常的方式来处理运行时异常。

5、try-catch-finally 异常三连【掌握】

1、try

监视,后跟一个代码块,表示尝试执行代码块中的代码。

2、catch

捕获,后跟一个括号,括号中声明一个异常,表示要捕获这种异常,然后括号后跟一个代码块。如果try的代码块发生了这个异常,那么用catch捕获这个异常,然后执行catch代码块代码,try代码块中发生异常之后的代码不会继续执行。如果try代码块中没有发生异常,catch的代码不会执行。

3、finally

最终的,后跟一个代码块。无论是否发生了异常,在最后都会执行finally中的代码,即使try或者catch中执行了return,也会执行finally中的代码。

4、流程图

5、三个关键字的组合

tcf三个关键字不是必须都出现的,只有try是一定要出现的,catch和finally至少要出现一个。

1、第一种

try{
}catch(Exception e){
}finally{
}

执行try代码块,如果没有异常,继续执行finally代码块,如果发生异常,那么try代码块中发生异常代码之后的程序不会再执行,会直接进入catch代码块,执行完catch之后仍然会执行finally代码块代码。

public class ExceptionDemo {
    public static void main(String[] args) {
        fir("2020-02-19");
        System.out.println("---------------------");
        fir("2020年02月19日");
    }
    public static void fir(String str){
        try{
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
            Date date = df.parse(str);
            System.out.println("日期:" + date);
        }catch (ParseException e){
            System.out.println(str + "格式是错误的!");
        }finally {
            System.out.println("无论怎样,我finally都执行了!");
        }
    }
}

日期:Wed Feb 19 00:00:00 CST 2020
无论怎样,我finally都执行了!
---------------------
2020年02月19日格式是错误的!
无论怎样,我finally都执行了!

2、第二种

try{
}catch(Exception e){
}

和第一种类似,只是少了finally中的代码。其中catch代码块可以出现多个:

try{
} catch(Exception1 e){
} catch(Exception2 e){
} catch(Exception3 e){
} catch(ExceptionN e){
}

代表的含义是处理try中出现的异常,第一种的catch也可以出现多个。注意catch的顺序有语法要求,就是先写的catch中异常必须不能是后续catch中异常的父类。

public static void sec(String str) {
    try {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        Date date = df.parse(str);
        System.out.println("日期:" + date);
        int a = 1 / 0;
    } catch (ParseException e) {
        System.out.println(str + "格式是错误的!");
    } catch (Exception e){
        System.out.println("其它的异常!");
    }
}

日期:Wed Feb 19 00:00:00 CST 2020
其它的异常!
---------------------
2020年02月19日格式是错误的!

3、第三种

try{
}finally{
}

用的最少的一种,先执行try代码块,无论是否发生异常,都会执行finally中的代码,而且发生了什么异常都不会去处理。

public static void main(String[] args) {
    thi(10, 3);
    System.out.println("---------------------");
    thi(10, 0);
}

public static void thi(int a, int b){
    try {
        System.out.println("a / b = " + (a / b));
    } finally {
        System.out.println("无论怎样,我finally都执行了!");
    }
}

a / b = 3
无论怎样,我finally都执行了!
---------------------
无论怎样,我finally都执行了!
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at demo.ExceptionDemo.thi(ExceptionDemo.java:23)
	at demo.ExceptionDemo.main(ExceptionDemo.java:18)

最下面这段英文是发生异常的具体信息,是通过异常对象显示出来的,其实调用了对应异常的printStackTrace()方法。

这个方法是将栈中的异常信息都打印出来,栈的特点是后进先出,会最先打印代码报错的哪一行,然后在打印调用这个代码方法的为位置信息,依次向上,一直到程序的入口。

如果在开发中出现了异常,并且这个异常需要我们处理,处理方法是查看异常,从上向下找到第一个我们熟悉的Java类,然后定位异常代码行,逻辑分析,具体处理。

6、throw 抛出异常【掌握】

向外抛出一个异常,如果执行了有throw的这行代码,代表这个方法发生了异常情况,会向外抛出一个异常。throw是真地动,真实的向外抛出一个异常。

7、throws 声明异常【掌握】

在声明方法时,声明这个方法可能会抛出异常,可能抛出的异常写在throws之后,如果是可能抛出多个异常,那么每个异常直接用逗号分隔。

throws是停留在嘴上,意思是有可能会抛出异常,但是不一定真的抛出。

throw和throws经常配合使用,在特定的情况下(可能是用户输入操作等问题,也可能是业务逻辑等问题),需要向方法外抛出一个异常,这时用throw关键字,一旦用throw关键字抛出了编译时异常,就必须用throws关键字在方法上声明,表示这个方法可能会抛出声明异常。实际开发中throw和throws组合的应用还是挺多的,可以帮助我们更快捷的开发,有一种开发的思想叫做面向异常编程,就是利用了这两个关键字的组合。

throw很少抛出运行时异常,这种情况几乎为0,因为运行时异常并不要求必须处理。而throw经常抛出的是自定义异常。

    public static void main(String[] args) throws ParseException {
        try {
            sec(10, 0);
        } catch (Exception e) {
//            e.printStackTrace();
            System.out.println(e.getMessage());
        }
    }
    public static void sec(int a, int b) throws Exception{
        for(int i = 10; i >= 0; i--){
            if(i == 5){
                throw new Exception("除数必须大于5!");
            }
            System.out.println(a + " / " + i + " = " + (a / i));
        }
    }

10 / 10 = 1
10 / 9 = 1
10 / 8 = 1
10 / 7 = 1
10 / 6 = 1
除数必须大于5!

8、自定义异常【掌握】

所有的异常类都是继承于Exception类,JDK中已经定义的是这样写,那么如果需要自己定义一个异常也可以写一个继承于Exception的类,表示自定义异常。

权限修饰符+class+自定义异常类名+extends+XXXException{

}

  • 命名规范,自定义异常在命名时,也要仿照JDK,所有的异常都是以Exception结尾。

  • 构造方法,自定义异常一般会重写父类参数是String的构造方法。

  • 定义属性,自定义异常一般会在异常类中定义一些属性,用来表示这个异常的其它特点,如果定义了属性,可以多写几个构造方法。

    public class MyException extends Exception {
    private final int code;

      public MyException(String message, int code) {
          super(message);
          this.code = code;
      }
    

    }

结语

应该没有人会看到这里吧
JAVA基础知识总结(2025速通)三_第2张图片

你可能感兴趣的:(java,开发语言)