Object、String、异常和包装类(Java初阶终章)

目录

  • 场景回顾
    • 问:在定义抽象方法时
    • 接口
      • 接口两大应用场景:
  • Java总万物之母:Object类
  • 接口优先原则
    • 问:为啥接口可以多继承
  • 克隆接口Cloneable
  • 2022/5/6
  • 深浅拷贝
    • 浅拷贝
    • 深拷贝
  • 克隆clone()
    • 问:clone出来的对象赋值是否会调用构造方法?
  • 在Java中产生一个新对象有两种方式
  • JDK中String类的声明
    • 问:为何`String`类被`final`修饰?
    • 什么时候会用到`final`修饰类
  • 1.创建字符串的四种方式
  • 2.字面量:直接写出来的数值称之为字面量
  • 3.字符串比较相等
  • 4.字符串常量池
    • 问:直接赋值一个,看看和str1相不相等能不能验证常量池中有hello?
  • 手工入池方法:String类提供的`intern`方法
    • 问:是直接移动过去,而不是在常量池拷贝一个?
  • 字符串的不可变性
    • 问:为何字符串的对象无法修改内容而其他类的对象能修改内容
  • 5.如何修改字符串的内容
  • 关于`StringBuilder`类的具体使用:
    • `StringBuilder` 和 String 类的转换
      • String变成`StringBuilder`的两种方式:
      • 还原:
    • 解释String、`StringBuilder`、`StringBuffer`的区别:
  • 做个小题
  • 字符串的常见操作
    • 1.字符串的比较
    • 2.字符和字符串的相互转换
      • String <-- char
      • 问:用`toString`行不行?
      • String --> char
      • 问:如何判断一个字符串的对象是由纯数字组成的?
    • 3.字符串和字节的相互转换
      • String --> byte[]
      • byte[] --> String
    • 4.字符串查找操作(*代表重点)
    • 5.字符串替换操作
      • 替换操作是否会修改原字符串的内容?
    • 6.字符串拆分操作
    • 7.字符串的截取处理
    • 8.其他常用方法(*代表重点)
      • 问:能否这样?
      • 练习:写一个方法将字符串的首字母大写处理
  • 异常
    • 异常的基本语法:
    • 1.程序中不处理异常
    • 2.使用try .. catch .. 处理异常
    • 3.Java中一切都是类,异常其实也是类,因此`Catch`代码块只能捕获相应的异常类型
      • Exception
    • 4.关于错误"堆栈"信息 `printStackTrace`方法
    • 5.关于`finally`代码块:无论是否有异常产生,最终都会执行`finally`代码块中的代码
    • 6.异常的处理流程
    • 7.`throws`和`throw`关键字 - 人为抛出异常
  • 2022/5/8
  • 异常体系
    • 受查异常和非受查异常
      • 非受查异常
      • 受查异常
        • 显示处理
    • 自定义异常
    • 异常体系
  • 包装类
    • 包装类的使用
      • 拆箱和装箱
    • 包装类和其基本数据类型的比较
    • 常量池

场景回顾

  1. 方法重载和方法重写的区别

  2. 什么是多态

向上转型和向下转型分别的应用场景

instanceof关键字的使用

抽象类:普通类的超集,知识比普通类多了一些抽象方法而已

使用关键字abstract定义抽象类,抽象方法所在的类一定是抽象类

子类继承抽象类仍然满足is a关系,Person和China;继承抽象父类时仍然是单继承

抽象方法:a.使用关键字abstract定义 b.只有方法声明没有方法体 =>抽象方法

子类继承抽象类必须覆写所有抽象方法(前提子类是普通类)

抽象类无法直接实例化子类,不能直接使用抽象类产生对象,必须通过子类向上转型来实例化对象 比如

Person per = new China();

问:在定义抽象方法时

abstractfinal能否同时出现?No

抽象方法必须被覆写,被final修饰的方法不能被覆写

在定义抽象类时

abstractfinal能否同时出现?No

抽象类必须得有子类,final修饰的类没有子类 = 》矛盾

接口

接口:比抽象类更加纯粹的一个抽象概念

接口中只有全局常量和抽象方法(JDK8之前)

接口使用interface关键字定义,子类实现接口使用implements实现接口

  • 接口定义时,public abstract final static 都可以省略!!!

接口两大应用场景:

  1. 表示规范/标准:USB接口,5G标准
  2. 表示具备一种能力/行为:ISwim,IRun

接口的子类之间是一种混合关系,不具备is a关系。比如Dog类和Cat类都具备跑的能力,但是类之间没关系。

接口的子类可以同时实现多个父接口

class Dog implements IRun,ISwim{}

  1. 接口和类之间的关系

接口和接口之间也存在继承关系。接口坚决不能继承一个类

IC接口同时继承多个父接口,继承了所有的抽象方法

子类在实现IC接口时,必须覆写所有的抽象方法

  • 如果一个类既需要继承一个类,同时实现多个接口时,

先使用extends继承一个类,然后使用implements实现多个父接口(有先后顺序)

class China extends Person implements IB,IA {}

关于接口的命名规范:为了区别接口和类,命名接口使用"I"开头,IRun,ISwim

  • 子类实现一个接口时,命名以相应的接口开头,以impl结尾

eg:如果是IRun的子类,RunImpl(非强制要求)

如果子类实现多个父接口,不需要使用此规范来命名


Java总万物之母:Object类

全名称:包名.类名

java.lang.Object

  1. Object类是Java中所有类的默认父类,无需使用extends来定义

class声明的类都有一个父类,Object类。

因为Object类是所有类的父类,使用Object引用来接收所有的类型,参数的最高统一化

Object obj1 = new Person();
Object obj2 = new String();
Object obj3 = new Dog();
  • Java中所有类型都可以发生向上转型变为Object类型
  • obj可以接收所有的类型,变成Object类型
void fun(Object obj){}
  1. Object类中的所有方法子类全都继承了下来

之所以System.out.println(任意的引用类型)=>默认都调用该类型的toString()

因为Object类存在toString方法

打印的带有地址的一串数字和字母 实际上是调用了Object类的toString方法

可以在子类覆写toString方法,使调用子类覆写后的方法

  1. Java中引用数据类型之间的相等比较使用equals方法!!!不能使用==比较的是地址

原因是Object类

  • ==比较的是数值,对于引用类型来讲,保存的内容是地址

  • 若需要比较两个对象的属性值是否相同,就需要按照比较规则覆写equals方法

Object equals 源码

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

此时要比较两个类的对象的属性值是否相等,需要覆写equals方法

Student stu2 = new Student("yizhou",score:81);
Student stu3 = new Student("yizho",score:81);
System.out.println(stu1 == stu2);
System.out.println(stu2 == stu3);
//覆写equals方法
System.out.println(stu2.equals(stu3));
//覆写equals方法
public boolean equals(Object obj){
	if(this == obj){
		return true;
	}
	//此时当前对象和obj指向的对象确实不是一个地址
	//若此时obj指向的对象和Student压根没关系,没有可比性,直接返回false
	if(obj instanceof Student){
		//obj这个引用指向的对象确实是Student类的对象且和当前对象不是一个对象
		//Object obj = new Student();
		Student stu = (Student) obj;
		return this.score == stu.score && this.name.equals(stu.name);
	}
	return false;
}

name属性是String类型,记住JDK的所有类型属性 比较都是用equals方法(都覆写过了)

啥时候用到向下转型,当一个父类引用实际上指向了一个子类实例时,我们需要调用子类独有的属性或方法时才会用到向下转型。

Person per = new China();

per是指向China对象的Person类型,

如果需要用到China独有的属性或方法,就需要把per还原为China类型!!!

  1. Object不仅是所有类(class)的父类

JDK对Object类做了扩展

Object类可以接收所有引用类型的对象(接口,数组,类)

在Java中,若一个方法参数或返回值是Object类型,说明该参数或者返回值可以是任意引用数据类型(数组,类,接口)


接口优先原则

当一个场景既可以使用抽象类也可以使用接口定义时,优先考虑使用接口(更灵活)

JDK内置的常用接口

java.lang.Comparable:当一个类实现了Comparable接口,表示该类具备了可比较的能力!

public int compareTo(Object o){
	return 0;
}

Object、String、异常和包装类(Java初阶终章)_第1张图片

覆写compareTo

@Override
public int compareTo(Object o ){
    if(this == o){
        return 0;
    }
    if(o instanceof Person){
        //当前传入的o就是Person类型的引用,向下转型还原为Person
        //要比较Person对象的大小关系,就要用到Person独有的属性,向下转型
        Person per = (Person) o;
        return this.age - per.age;
    }
    //报错,抛出异常
    throw new IllegalArgumentException("不是Person类型,无法比较!");
} 

问:为啥接口可以多继承

接口和抽象类的最大却别在于

抽象类是一个非常严格的is a关系,子类和抽象类是一颗继承树上的关系

接口和实现子类更多的是水平方向,表示混合关系的

一个子类通常都可以具备多个能力或者行为


克隆接口Cloneable

JDK另一个比较重要的接口 - 克隆接口

java.lang.Cloneable

class Animal implements Cloneable{}

啥事“克隆”
原对象和新产生对象确实是两个独立的对象,新产生的对象是通过原对象“拷贝”而来的,属性值和原对象完全一致。

JVM在运行时会检查所有实现了Cloneable接口的子类,赋予其克隆的能力。clone方法是Object提供的方法。

public interface Cloneable{}

类似Cloneable接口,把这种接口称之为“标记“接口,这个接口本身内部没有任何抽象方法,只有打上这个标记的子类才具备克隆的能力

要让一个类具备可复制的能力,实现Cloneable接口,覆写clone方法

public Animal clone(){
    Animal newAnimal = null;
    try{
        newAnimal = (Animal) super.clone();
    } catch(CloneNotSupportedException e){
        throw new RuntimeException(e);
    }
    return newAnimal;
}

2022/5/6


深浅拷贝

浅拷贝

最外层对象确实是由原对象的clone方法产生的新对象,属性值与原对象保持一致,原对象和新对象的内部若包含其他类的引用,这些引用指向的对象是相同的,并没有产生新对象。

深拷贝

最外层对象确实是由原对象的clone方法产生的新对象,属性值与原对象保持一致,原对象和新对象的内部若包含其他类的引用,这些引用指向的对象也是新对象。


克隆clone()

覆写后的clone方法

public A clone(){
    A a = null;
    try{
        a = (A) super.clone();
    } catch (CloneNotSupportedException e){
        throw new RuntimeException(e);
    }
    return a;
}

clone方法

protected native Object clone() throws CloneNotSupportedException;

在有继承关系的类之间,子类定义了和父类除了权限不同以外,其他方法名称,参数列表,返回值完全相同(向上转型类的返回值也可以)的方法

权限子类 > = 父类,private除外

A类的clone就是克隆A类的对象

返回值是A的对象,避免类型转换了

问:clone出来的对象赋值是否会调用构造方法?

public static void main(String[] args){
    Animal animal1 = new Animal();
    Animal animal2 = animal1.clone();
}
//Animal类的无参构造    只调用了一回!!

不会!!!

在Java中产生一个新对象有两种方式

  1. 通过构造方法产生对象

new类(); = 》 当有new关键字时,就在堆上开辟该类相应属性的空间给属性赋默认值。

  1. 通过clone()产生对象,调用clone时,JVM会开辟与原对象内存大小完全相同的新空间,并将对象中属性的值从原对象中复制一份。(不推荐)

JDK中String类的声明

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence, Constable, ConstantDesc{}

问:为何String类被final修饰?

被final修饰的类无法被继承,String类不存在子类。

这样的话,就可以保证所有使用JDK的人,大家用到的String类仅此一个,大家都相同。

假设String允许继承,每个人都可以继承String类,修改它的方法等实现。

String str1 = new MyString1();//向上转型
String str2 = new MyString2();
String str3 = new MyString3();
//MyString1和MyString2、3都是完全不同的子类,都可以向上转型变为String

继承和方法覆写在带来灵活性的同时,也会带来很多子类行为不一致导致的问题。

什么时候会用到final修饰类

你的这个类不希望有别的版本,到此为止。

所有使用者用的这个类完全相同,没有别的实现。

1.创建字符串的四种方式

*方式一:直接赋值

String str = "hello world";

方式二:通过构造方法产生对象

String str2 = new String("hello world");

方式三:通过字符数组产生对象

char[] data = new char[]{'a', 'b', 'c'};
String str = new String(data);

*方式四:通过String的静态方法valueOf(任意数据类型) = >转为字符串

String str = String.valueOf(10);

2.字面量:直接写出来的数值称之为字面量

10 -> int字面量

10.1 -> double字面量

true -> boolean字面量

"abc" ->String字面量 -》就是一个字符串的对象

String 引用数据类型

String str = "hello world";//字符串字面量,也是字符串的对象

String str = "hello world";
String str1 = str;
str1 = "hello";
System.out.println(str);
//hello world

"hello"也是字符串的字面量,是一个新的字符串对象,str1实际上指向了新的字符串对象“hello”

str仍旧指向原字符串对象“hello world”

3.字符串比较相等

所有引用数据类型在比较是否相等时,使用equals方法比较,JDK常用类,都已经覆写了equals方法,String,Integer

引用数据类型使用 == 比较的仍然是数值(地址是否相等)

String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2);//true
//并不是内容相同,只是说明str1和str2指向了相同的地址空间
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1 == str2);//false
System.out.println(str1.equals(str2));//true 区分大小写
System.out.println(str1.equalsIgnoreCase(str2));//true 不区分大小写
  • 牵扯到用户输入就一定要做判空处理
String useName = null;
//Systme.out.println(useName.equals("先平"));//空指针异常
System.out.println("先平".equals(useName));//false

因为我们要比较的特定内容本身就是字符串的字面量,一定不是空对象,把要比较的内容放在equals的前面。就可以方便处理userName为空的问题

4.字符串常量池

当使用直接赋值法产生字符串对象时,JVM会维护一个字符串的常量池,若该对象在堆中还不存在,则产生一个新的字符串对象加入字符串的常量池中。

当继续使用直接赋值法产生字符串对象时,JVM发现该引用指向的内容在常量池中已经存在了,则此时不再新建字符串对象,而是复用已有对象。

String str1 = "hello";//字符串常量,第一次出现,此时常量池中不存在,新建一个该对象加入常量池中。
String str2 = "hello";
String str3 = "hello";
System.out.println(str1 == str2);//true
Systme.out.println(str2 == str3);//true  这三个引用指向了相同的内存!
//str2和str3仍然使用直接赋值法产生对象,但是该内容已经在常量池中存在,此时并不会新产生对象,而是复用已有对象。

Object、String、异常和包装类(Java初阶终章)_第2张图片

String str1 = new String("hello");
String str2 = new String("hello");
String str3 = new String("hello");
System.out.println(str1 == str2);//false
System.out.println(str2 == str3);//false

new String("hello");产生常量池一个对象,堆中也产生一个对象

Object、String、异常和包装类(Java初阶终章)_第3张图片

String str1 = new String("hello");在栈中产生一个对象str1,指向堆中的"hello"

Object、String、异常和包装类(Java初阶终章)_第4张图片

String str1 = new String("hello");
String str2 = new String("hello");
String str3 = new String("hello");

程序进行到第2行,常量池中存在hello,在堆中又产生一个新的字符串对象,再然后str2指向了新的字符串对象**(从右向左进行代码)**

Object、String、异常和包装类(Java初阶终章)_第5张图片

有new就有新空间,这三行代码产生四个字符串对象,其中一个在产量池中,其余三个在堆中。

问:直接赋值一个,看看和str1相不相等能不能验证常量池中有hello?

String str1 = new String("hello");
String str2 = new String("hello");
String str3 = new String("hello");

不行,str1指向了堆中普通的字符串"hello",和常量池中的“hello”永远不相等

手工入池方法:String类提供的intern方法

public native String intern();

调用intern方法会将当前字符串引用指向的对象保存到字符串常量池中。

a. 若当前常量池中已经存在了该对象,则不再产生新的对象,返回常量池中的String对象

b. 若当前常量池中不存在该对象,则将该对象入池,返回入池后的地址。

String str1 = new String("hello");
//str1 = str1.intern();//true
str1.intern();//常量池中已经存在“hello”,不再产生新的对象,返回常量池中字符串对象地址,但是str1没有接收
String str2 = "hello";
System.out.println(str1 == str2);//false ??? wtf
char[] data = new char[] {'a', 'b', 'c'};
String str1 = new String(data);//这是字符串数组,还没有字符串对象呢
str1.intern();
String str2 = "abc";
System.out.println(str1 == str2);//true??? wtf 又错了

Object、String、异常和包装类(Java初阶终章)_第6张图片

str1.intern();把abc挪到常量池中

Object、String、异常和包装类(Java初阶终章)_第7张图片

此时常量池中存在abc对象了,str2会复用这个对象

Object、String、异常和包装类(Java初阶终章)_第8张图片

问:是直接移动过去,而不是在常量池拷贝一个?

str1.intern();此时并没有赋值,所以只是移动,并没有拷贝

字符串的不可变性

所谓的字符串不可变指的是字符串对象的内容不能变,而不是字符串引用不能变

String str = "hello";
str = str + "world";
str += "!!!";
System.out.println(str);

这里的不可变指的“hello”,“world”,“helloworld”,“!!!“,“helloworld!!!”这些字符串对象一旦声明后就无法修改其内容

Object、String、异常和包装类(Java初阶终章)_第9张图片

先产生hello,str指向hello;产生world,str拼接world,str指向拼接后的helloworld,产生!!!,str指向str拼接!!!后的字符串对象。

问:为何字符串的对象无法修改内容而其他类的对象能修改内容

字符串其实就是一个字符数组 =》char[],字符串保存的值实际上在数组中保存

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence, Constable, ConstantDesc{
    private final byte[] value;
}

String外部无法访问这个value数组,String 并没有提供关于value属性的getter和setter

对于String类的外部而言,value完全无法使用

因此字符串对象的内容无法修改 -> String类的外部拿不到这个value数组

Object、String、异常和包装类(Java初阶终章)_第10张图片

  • 字符串保存的值实际上在数组中保存

5.如何修改字符串的内容

a. 在运行时通过反射破坏value数组的封装

b. 更换使用StringBuilder或者StringBuffer

若要频繁进行字符串的拼接,使用StringBuilder类的append方法

StringBuilder类可以修改对象的内容

public static void main(String[] args){
    StringBuilder sb = new StringBuilder();
    sb.append("hello");
    sb.append("world");
    sb.append("!!!");
    System.out.println(sb);
}
  • StringBuffer线程安全,性能较差

StringBuilder直接拼接


关于StringBuilder类的具体使用:

StringBuilder类和String类是两个独立的类,StringBuilder就是为了解决字符拼接问题产生的。

因为String的对象无法修改内容,为了方便字符串的拼接操作,产生了StringBuilder类,StringBilder类的对象是可以修改内容的

StringBuilder 和 String 类的转换

  • StringBuilder 调用toString()方法 -> String

  • StringBuilder 使用StringBuilder构造方法或者append方法 <- String

String变成StringBuilder的两种方式:

StringBuilder sb = new StringBuilder("hello");

sb.append("123");

还原:

String str = sb.toString();

因为StringBuilder类可以修改内容,因此具备一些String类不具备的修改内容的功能,除了拼接append方法外

a. 字符串反转从挨揍,sb提供的reverse();

sb.reverse();

b. 删除指定范围的数据

sb.delete(5,10);//[)左闭右开

delte(int start, int end):删除从start索引开始end之前的所有内容[start, end)

c. 插入操作

insert(int start, 各种数据类型):将新元素插入当前sb对象,插入后新数值起始索引为start

sb.insert(4, 10);//hell10o

解释String、StringBuilderStringBuffer的区别:

  1. String的对象无法修改,俩sb的对象内容可以修改。
  2. Stringbuffer是线程安全的操作,性能较差;StringBuilder是线程不安全,性能较高。

要使用String类,就采用直接赋值的方式

String str = "鹏哥NB";

要比较内容是否相等使用equals方法

做个小题

输出结果是什么?

public class exercise {
    String str = new String("good");
    char[] ch = {'a', 'b', 'c' };

    public static void main(String[] args) {
        exercise ex = new exercise();
        ex.change(ex.str, ex.ch);
        System.out.print(ex.str + " and ");
        System.out.print(ex.ch);
    }

    public void change(String str, char ch[]) {
        str = "test ok";
        ch[0] = 'g';
    }
}
//good and gbc   ??? wtf

方式1:产生几个字符串对象?

char[] data = {'a', 'b', 'c'};
String str3 = new String(data);//1个

这时候内存池还没任何字符串常量,str3是new出来的,所以不入池,只在堆上生产了一个普通的字符串对象,是由字符数组data赋值来的。

方式2:

String str4 = new String("abc");//2个

  1. "abc"就是字符串字面量,第一次出现,由JVM产生此字符串对象并保存到常量池中。

  2. 在堆中产生一个普通的字符串对象,值从常量池中拷贝过来

弄懂这两个问题就好办了

Object、String、异常和包装类(Java初阶终章)_第11张图片

  1. 首先主方法中产生good常量池;复制到堆中;str指向堆中的goodch作为一个普通的字符数组,不在常量池中
  2. 接着进入change()中;一个新产生的形参str指向堆中的good,新产生的字符数组指向堆中的a b c;堆中产生对象test ok,形参str指向对象test okch的首位字符改为g
  3. change()结束,两个形参出栈,两个变量都没了~,返回主方法
  4. 打印ex.str,还是指向的goodex.ch指向的是gbc
  5. 因为主方法中的ch和形参指向的是同一个地址,所以改了

Object、String、异常和包装类(Java初阶终章)_第12张图片


字符串的常见操作

1.字符串的比较

No. 方法名称 类型 描述
1. public boolean equals(Object anObject) 普通 区分大小写的比较
2. public boolean equalsIgnoreCase(String anotherString) 普通 不区分大小写的比较
3. public int compareTo(String anotherString) 普通 比较两个字符串大小关系
public static void main(String[] args){
	String str1 = "abc";
	String str2 = "Abc";
	System.out.println(str1.compareTo(str2));
}
//32

按照“字典序”排列字符串

就是按照字符串内部的字符的ASCII码大小排序

A 65 a:97

2.字符和字符串的相互转换

字符串的内部实际上就是使用字符数组来存储

String <-- char

public String(char value[]) 构造 将字符数组的内容转为字符串

问:用toString行不行?

char[] ch = new char[] {'a', 'b', 'c'};
String str1 = ch.toString();
String str = new String(ch);
System.out.println(str1);//[C@1b6d3586
System.out.println(str);//abc

ch也是个对象,而且是字符数组的对象,默认用的是Object类toString方法,所以打印的是字符数组的地址,而不是里面的abc的值

将字符数组的部分内容转为字符串对象

public String(char value[], int offset, int count) 构造 将部分字符数组的内容变为字符串

String str1 = new String(ch, 1, 2);

String --> char

  1. 取出字符串中指定索引的字符
public char charAt(int index) 普通 取得指定索引位置的字符,索引从0开始
String str = "hello";
System.out.println(str.charAt(1));
//e
  1. 将字符串中的内容转为字符数组String => char[]
public char[] toCharArray() 普通 将字符串变为字符数组返回
String str = "hello";
char[] data = str.toCharArray();
data[0] = 'H';
System.out.println(str);
//Hello

此时产生了一个新的字符数组,将字符串的内容复制过去

String对象不可变!!!内容改不了!!!

问:如何判断一个字符串的对象是由纯数字组成的?

“123" => true

“123a” =>false

public static boolean isNumber(String str){
    char[] data = str.toCharArray();
    for(char c : data){
        if(c < '0' || c > '9'){
            return false;
        }
        //if(!Character.isDigit(c)){//char类型的包装类
        //	return false;
    	//}
    }
    return true;
}

3.字符串和字节的相互转换

将字符串保存到文件中或是通过网络传输都要用到字节数组

比如在聊天室发送奥利给这个消息,这个消息其实是字节码的对象,我能发出去,你们能收到实际上就是把字符串按照相应的编码编成了字节数组,你们那边收到字节数组以后再把字节数组还原为字符串

String --> byte[]

public byte[] getBytes() 普通 将字符串以字节数组的形式返回
public byte[] getBytes(String charsetName) throws UnsupportedEncodingException 普通 编码转换处理 。按照指定的编码格式转为字节数组
String str = "你好中国";
byte[] data = str.getBytes();//按照当前默认的字符编码转为字节
byte[] data1 = str.getBytes(StandardCharsets.UTF_8);
System.out.println(Arrays.toString(data));
//[-28, -67, -96, -27, -91, =67, -28, -72, -83, -27, -101, -67]
//[-28, -67, -96, -27, -91, =67, -28, -72, -83, -27, -101, -67]//默认UTF_8格式
public static void main(String[] args) throws Exception{
    String str = "你好中国";
    byte[] data1 = str.getBytes(charsetName: "gbk");
    System.out.println(Arrays.toString(data1));
}
//[-60, -29, -70, -61, -42, -48, -71, -6]//按照GBK编码将字符串转为字节数组

byte[] --> String

public String(byte bytes[]) 构造 将字节数组变为字符串
public String(byte bytes[], int offset, int length) 构造 将部分字节数组的内容变为字符串
byte[] data = new byte[] {97, 98, 99};
String str = new String(data);
System.out.println(str);
//abc

在UTF_8编码下,一个汉字3个字节

在GBK编码下,一个汉字2个字节

4.字符串查找操作(*代表重点)

NO. 方法名称 类型 描述
1* public boolean contains(CharSequence s) 普通 判断一个子字符串是否存在
2 public int indexOf(String str) 普通 从头开始查找指定字符串的位置,查到了返回位置的开始索引,如果查不到返回-1
3 public int indexOf(String str, int fromIndex) 普通 从指定位置开始查找子字符串位置
4 public int lastIndexOf(String str) 普通 由后向前查找子字符串位置
5 public int lastIndexOf(String str, int fromIndex) 普通 从指定位置由后向前查找
6* public boolean startsWith(String prefix) 普通 判断是否以指定字符串开头
7 public boolean startsWith(String prefix, int toffset) 普通 从指定位置开始判断是否以指定字符串开头
8* public boolean endsWith(String suffix) 普通 判断是否以指定字符串结尾
String str = "hello world";
System.out.println(str.contains("world"));
System.out.println(str.startsWith("hello"));
System.out.println(str.startsWith("hello1"));
System.out.println(str.endsWith("world"));
System.out.println(str.endsWith("world1"));
//true
//true
//false
//true
//false

5.字符串替换操作

String类的所有针对字符串的操作方法都不会修改原字符串,

NO. 方法名称 类型 描述
1 public String replaceAll(String regex, String replacement) 普通 替换所有的指定内容
2 public String replaceFirst(String regex, String replacement) 普通 替换首个内容(regex:正则表达式)
String str = "helloworld";
System.out.println(str.replaceAll("1", "_"));
System.out.println(str.replaceFirst("1", "_"));
System.out.println(str);
//he__owor_d
//he_loworld
//helloworld

替换操作是否会修改原字符串的内容?

String类的所有针对字符串的操作方法都不会修改原字符串,而是产生了一个新的字符串!!!

字符串的不可变性!!!

只有StringBuilder才能修改

6.字符串拆分操作

NO. 方法名称 类型 描述
1. public String[] split(String regex) 普通 将字符串全部拆分
2. public String[] split(String regex, int limit) 普通 将字符串部分拆分,该数组长度就是limit极限
public static void main(String[] args){
    String str = "Hello Java Hello Rocket";
    String[] data1 = str.split(" ");
    //按照空格将str分开,拆分后的字符串数组长度为2
    String[] data2 = str.split(" ", 2);
    //拆分IP地址
    String str1 = "192.168.1.1";
    String[] data = str.split(".");
    String[] data = str.split("\\.");
    System.out.println(Arrays.toString(data1));
    System.out.println(Arrays.toString(data2));
    System.out.println(Arrays.toString(data));
}
//[Hello, Java, Hello, Rocket]
//[Hello, Java Hello Rocket]
//[]//没拆成,说明.1.是个特殊字符,需要转义处理"\\." 2.这个格式在字符串中不存在
//[192, 168, 1, 1]
  • split()若字符串中没有指定拆分的子串,拆分后仍然得到原字符串
String str = "Hello Java Hello Rocket";
String[] data1 = str.split("-");
System.out.println(Arrays.toString(data1));
System.out.println(data1.length());
//[Hello Java Hello Rocket]
//1 长度为1

当按照指定的格式拆分字符串得到了一个空数组

a. 你的这个格式在字符串中不存在

b. 你的这个格式是个特殊字符,需要转义处理\\.

7.字符串的截取处理

NO. 方法名称 类型 描述
1. public String substring(int beginIndex) 普通 从指定索引截取到结尾
2. public String substring(int beginIndex, int endIndex) 普通 截取部分内容[)
String str = "helloworld";
System.out.println(str.substring(5));//world
System.out.println(str.substring(0, 5));//hello

8.其他常用方法(*代表重点)

NO. 方法名称 类型 描述
1.* public String trim() 普通 去掉字符串中的左右空格,保留中间空格
2.* public String toUpperCase() 普通 字符串转大写
3.* public String toLowerCase() 普通 字符串转小写
4. public native String intern() 普通 字符串入池操作
5. public String concat(String str) 普通 字符串连接,等同于"+",不入池
6.* public int length() 普通 取得字符串长度
7.* public boolean isEmpty() 普通 判断是否为空字符串,但不是null,而是长度为0
String str = "  hello world  ";
System.out.println(str.trim());
System.out.println(str.toUperCase());
System.out.println("HELLO".toLowerCase());
System.out.println("hello".length());
//hello world
//  HELLO WORLD
//hello
//5

问:能否这样?

String str = null;
//str.isEmpty();

不能,isEmpty()为成员方法,只能判断字符串的长度是否为0,不能判断null

写一个方法判断传入的str是否为空(要么是null,要么长度为0)

if(str == null || str.isEmpty())

练习:写一个方法将字符串的首字母大写处理

不考虑空格问题,纯字母

public static String firstUpper(String str){
    //1.判空处理
    if(str == null || str.isEmpty()){
        return null;
    }
    //2.边界条件
    if(str.length() == 1){
        return str.toUpperCase();
    }
    //3.此时str长度大于1
    //截取 + 大写操作
    return str.substring(0, 1).toUpperCase() + str.substring(1);

异常

异常:程序没按照预计的结果运行,在运行的过程中发生了错误

//除〇异常
System.out.println(10 / 0);
//ArithmeticException

//数组越界异常
int[] data = new int[] {1, 3, 5};
System.out.println(data[10]);
//ArrayIndexOutOfBoundsException

异常的基本语法:

try{
    //可能会产生异常的代码,除0,数组越界,空指针等
}[catch ... 0..N]{
    //出现异常以后如何处理
}[finally]{
    //异常的出口,最终会执行的代码块
}

1.程序中不处理异常

int[] data = {1, 2, 3};
System.out.println("before...");
System.out.println(data[100]);
System.out.println("after...");
//before...
//ArrayIndexOutOfBoundsException..
  • 当程序出现异常后,出现异常之后的代码不会执行

2.使用try … catch … 处理异常

int[] data = {1, 2, 3};
System.out.println("before...");
try{
    //可能会产生异常的代码
    System.out.println(data[100]);
} catch(ArrayIndexOutOfBoundsException) {//捕获异常
    System.out.println("异常产生");
}
System.out.println("after...");
//before...
//异常产生了
//after...
  • 异常出现后,会直接跳到catch代码块,catch执行完毕后向下执行
int[] data = {1, 2, 3};
System.out.println("before...");
try{
    //可能会产生异常的代码
    System.out.println(data[100]);//出现异常,跳到catch代码块
    System.out.println("try中的其他代码块");//本行代码不会被执行
} catch(ArrayIndexOutOfBoundsException) {//捕获异常
    System.out.println("异常产生");
}
System.out.println("after...");
//before...
//异常产生
//after...

3.Java中一切都是类,异常其实也是类,因此Catch代码块只能捕获相应的异常类型

int[] data = {1, 2, 3};
data = null;
System.out.println("before...");
try{
    //可能会产生异常的代码
    System.out.println(data[100]);//出现异常,跳到catch代码块
    System.out.println("try中的其他代码块");//本行代码不会被执行
} catch(ArrayIndexOutOfBoundsException) {//捕获异常
    System.out.println("异常产生");
}
System.out.println("after...");
//before...
//NullPointerException

try中只能捕获数组越界异常,若是遇到其他异常,则仍会报错,上面的异常是空指针异常

int[] data = {1, 2, 3};
data = null;
System.out.println("before...");
try{
    System.out.println(data[100]);
    System.out.println("try中的其他代码块");
} catch(ArrayIndexOutOfBoundsException) {
    System.out.println("异常产生");
} catch(NullPointerException){
    System.out.println("空指针异常");
}
System.out.println("after...");
//before...
//空指针异常
//after...

Exception

有一个异常类是所有异常类的父类,Exception

如果catch块中捕获的是Exception这个类型,就可以接收到所有异常类型

int[] data = {1, 2, 3};
System.out.println("before...");
try{
    System.out.println(data[100]);
    System.out.println("try中的其他代码块");
} catch(Exception e) {//Exception a 也可以
    System.out.println("异常产生");
}
System.out.println("after...");
//before...
//异常产生
//after...

一般不推荐用Exception,因为不知道异常产生的原因,最好用明确指定的异常类型

若此时不太清楚可能产生哪些异常,就使用Exception共同的父类。所有异常的子类都会向上转型变为Exception的引用

4.关于错误"堆栈"信息 printStackTrace方法

输出程序出现异常的位置以及原因,就调用异常对象的printStackTrace方法

int[] data = {1, 2, 3};
System.out.println("before...");
try{
    System.out.println(data[100]);
    System.out.println("try中的其他代码块");
} catch(Exception e) {
    System.out.println("异常产生");
    e.printStackTrace();
}
System.out.println("after...");
//before...
//异常产生
//after...
//ArrayIndexOutOfBoundsException

5.关于finally代码块:无论是否有异常产生,最终都会执行finally代码块中的代码

int[] data = {1, 2, 3};
try{
    System.out.println(data[100]);
} catch(ArrayIndexOutOfBoundsException e){
    e.printStackTrace();
} finally{
    System.out.println("finally代码块");
}
System.out.println("异常体系之后的代码");
//ArrayIndexOutOfBoundsException
//finally代码块
//异常体系之后的代码

一般来说资源的释放,方法的关闭操作都在finally代码块中

比如关闭数据库的连接,关闭文件的操作都在finally代码块中,无论是否有异常产生,都能保证资源会正常释放

try{
    int[] data = {1, 2, 3};
    data[100] = 10;
    return 10;
} catch (ArrayIndexOutOfBoundsException e){
    e.printStackTrace();
    return 20;
} finally{
    System.out.println("finally 代码块");
    return 30;
}
//ArrayIndexOutOfBoundsException
//finally代码块
//30

finally最终都会执行的代码块,若finally中存在返回值,会覆盖掉try或者catch中的返回值

不要在finally代码块中写返回值

6.异常的处理流程

  • 程序先执行try中的代码
  • 如果try中的代码出现异常,就会结束try中的代码,看和catch中的异常类型是否匹配
  • 如果找到匹配的异常类型,就会执行catch中的代码
  • 如果没有找到匹配的异常类型,就会将异常向上传递到上层调用者
  • 无论是否找到匹配的异常类型,finally中的代码都会被执行到(在该方法结束之前执行)
  • 如果上层调用者也没有处理异常,就继续向上传递
  • 一直到main方法也没有合适的代码处理异常,就会交给JVM来进行处理,此时程序就会异常终止
public static void main(String[] args){
    fun();
    System.out.println("after fun");
}

public static void fun(){
    int[] arr = {1, 2, 3};
    System.out.println(arr[100]);//fun方法内部并没有处理这个异常,产生异常之后向上抛出,抛给调用者主方法,主方法中也没有处理,继续向上抛出异常,抛给主方法的调用者JVM
}

7.throwsthrow关键字 - 人为抛出异常

throws:用在方法声明上,表示该方法可能会产生的异常类型,但是本方法中不处理该异常。若异常产生抛出给调用者

public static void fun() throws ArithmeticException{
    int[] arr = {1, 2, 3};
    System.out.println(arr[100]);
}

throw:用在方法的内部,表示人为产生异常对象并抛出

public static void fun() throws ArithmeticException{
    int[] arr = {1, 2, 3};
    System.out.println(arr[1]);
    throw new NullPointerException("没事干,抛个异常玩玩~~");
}
//NullPointerException

2022/5/8

异常体系

异常也是对象,异常对象,这个对象默认由JVM产生

异常体系:JDK内部异常的继承关系

Java中的异常分为两大类,一类称为受查异常(红色),另一类称为非受查异常(蓝色)

Object、String、异常和包装类(Java初阶终章)_第13张图片

受查异常和非受查异常

非受查异常

RuntimeException以及其子类包括Error及其子类称之为非受查异常,编译阶段可以不显示进行异常处理(try catch/ throws 抛出) 蓝色部分的类 为 非受查异常

RuntimeException

运行时异常,编译阶段不报错,运行时报错

ArrayIndexofBoundsException-数组越界异常

NullPointerException-空指针异常

ClassCastException-类型转换异常

Error - 程序内部错误,一般来说,出现这种错误,程序是没法正常执行下去的,只能退出程序。

OutofMemoryError堆内存溢出错误

StackOverflowError 栈溢出错误

String str = null;
System.our.println(str.length());
//NullPointerException
//运行时异常,编译阶段没有进行try catch显示进行异常的处理,编译能通过。在运行时报错

没有try catch也可以通过,但是运行时出错

受查异常

除了这些非受查异常意外的其他异常类属于受查异常,必须在编译阶段显示进行异常的处理,否则编译就会出错 ,上图中红色部分的类 受查异常

public FileInputStream(@NotNull File file) throws FileNotFoundException{}:受查异常,调用者必须显示进行异常的处理

File file = new File("test.txt");
Scanner scanner = new Scanner(new FileInputStream(file));
//java:未报告的异常错误java.io.FileNotFoundException
显示处理
  1. 进行try…catch捕获这个异常
File file = new ("test.txt");
try{
    Scanner scanner = new Scanner(new FileInputStream(file));
} catch (FileNotFoundException e){
    e.printStackTrace();
}
//test.txt(系统找不到指定的文件)
  1. 调用者也可以使用throws向上抛出异常
public static void main(String[] args) throws FileNotFoundException{
    File file = new File("test.txt");
    Scanner scanner = new Scanner(new FileInputStream(file));
}
//test.txt(系统找不到指定的文件)

自定义异常

JDK内部已经帮我们提前定义好了很多的异常类,但是在某些业务场景下,出现的错误需要我们自定义异常类(用户登录的时候,用户名不对,密码不对,这种错误就需要我们来自定义异常类)

自定义异常只需要继承相关的两个父类就可以

  • 若希望这个异常必须显示处理 - 继承Exception父类

  • 若这个异常不需要显示异常 - 继承RuntimeException父类

import java.util.Scanner;

public class MyExceptionTest {
    private static final String USER_NAME = "显平";
    private static final String PASSWORD = "yizhou";

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String userName = scanner.nextLine();
        System.out.println("请输入密码:");
        String pass = scanner.nextLine();
        if (!USER_NAME.equals(userName)) {
            //用户名错误异常
            throw new UserNameException("用户名错误");
        }
        if (!PASSWORD.equals(pass)) {
            //密码错误异常
            throw new PasswWordException("密码错误");
        }
    }
}

class UserNameException extends RuntimeException {
    public UserNameException(String msg) {
        super(msg);
    }
}

class PasswWordException extends RuntimeException {
    public PasswWordException(String msg) {
        super(msg);
    }
}
//请输入用户名:
//显平
//请输入密码:
//123
//Exception in thread "main" ExceptionTest.PasswWordException: 密码错误
//	at ExceptionTest.MyExceptionTest.main(MyExceptionTest.java:21)

异常体系

  1. try...catch.finally的使用流程
  2. 受查和非受查异常的关系以及常见异常

运行时异常就是非受查异常的一种,null,数组越界,类型转换

IOException就是受查异常的一宗

  1. throws和throw关键字的使用以及自定义异常类

包装类

包装类:就是把8大基本类型封装到类之中
a. Object类可以接收所有引用数据类型(数组接口),为了让Object类可以接收Java中一切内容,引入包装类,把基本类型的数值封装到类的对象之中就产生了包装类
b. 基本类型的默认值其实在很多场景下会造成误导,引用类型的默认值就是null
比如 double -> 0.0
有一个扣费的需求,当前费用*当前费率(打几折) = 实际扣款数
如果费率使用double类型,默认值0.0
使用的是double包装类Double,默认值为null
需要有一个类型把基本类型封装到类中
JDK中的包装类 - 以下8种

Object、String、异常和包装类(Java初阶终章)_第14张图片

包装类的使用

以整型为例

int -> Integer

Integer -> int

int val = 10;
//int -> Integer
Integer i1 = new Integer(val);   //装箱
Object obj = i1;//接收
//需要进行数学运算,就需要把包装类的对象还原为基本类型
int ret = i1.intValue();    	 //拆箱

拆箱和装箱

装箱:将基本类型的数值保存到包装类对象中

拆箱:将包装类对象中的数值还原为基本类型

Integer i2 = 10;//自动装箱
i2 += 20;//自动拆箱
System.out.println(i2);
//30

自动拆装箱 -> Java编译器的优化,使用包装类就和使用基本类型一模一样

Integer i2 = 10;
i2 += 20;
System.out.println(i2);

包装类和其基本数据类型的比较

有了包装类,使用包装类就和使用基本类型完全一致

a. 默认值不同!!!包装类的默认值都是null基本类型的默认值就是其数据类型默认值

IntegerDouble -> null

int -> 0

double -> 0.0

b. 比较相等,仍然使用equals方法比较,所有类对象的比较都使用equals方法!!!

Integer i1 = 130;
Integer i2 = 130;
System.out.println(i1 == i2);
System.out.println(i1.equals(i2));
//false
//true
i1 = 120;//这不是拆箱!!!只有在进行数学运算的时候才拆箱!!!
i2 = 120;//  + - * /
System.out.println(i1 == i2);
//true  wtf ???

常量池

同字符串常量池

当使用整型包装类的自动拆装箱时,JVM会缓存相应的数值

Integer常量池,默认在-128到127之间的取值,都会缓存到常量池中

i1 = 120;//i1第一次出现,创建一个Integer对象保存到常量池中
i2 = 120;//此时120这个值在常量池中已经有了,i2直接复用这个对象即可

不会的话,只要是引用类型的比较都一律使用equals方法,咋样都不会错

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