方法重载和方法重写的区别
什么是多态
向上转型和向下转型分别的应用场景
instanceof关键字的使用
抽象类:普通类的超集,知识比普通类多了一些抽象方法而已
使用关键字abstract定义抽象类,抽象方法所在的类一定是抽象类
子类继承抽象类仍然满足is a关系,Person和China;继承抽象父类时仍然是单继承
抽象方法:a.使用关键字abstract定义 b.只有方法声明没有方法体 =>抽象方法
子类继承抽象类必须覆写所有抽象方法(前提子类是普通类)
抽象类无法直接实例化子类,不能直接使用抽象类产生对象,必须通过子类向上转型来实例化对象 比如
Person per = new China();
abstract和final能否同时出现?No
抽象方法必须被覆写,被final修饰的方法不能被覆写
在定义抽象类时
abstract和final能否同时出现?No
抽象类必须得有子类,final修饰的类没有子类 = 》矛盾
接口:比抽象类更加纯粹的一个抽象概念
接口中只有全局常量和抽象方法(JDK8之前)
接口使用interface
关键字定义,子类实现接口使用implements
实现接口
public
abstract
final
static
都可以省略!!!接口的子类之间是一种混合关系,不具备is a关系。比如Dog类和Cat类都具备跑的能力,但是类之间没关系。
接口的子类可以同时实现多个父接口
class Dog implements IRun,ISwim{}
接口和接口之间也存在继承关系。接口坚决不能继承一个类
IC接口同时继承多个父接口,继承了所有的抽象方法
子类在实现IC接口时,必须覆写所有的抽象方法
先使用extends继承一个类,然后使用implements实现多个父接口(有先后顺序)
class China extends Person implements IB,IA {}
关于接口的命名规范:为了区别接口和类,命名接口使用"I"开头,IRun,ISwim
eg:如果是IRun的子类,RunImpl(非强制要求)
如果子类实现多个父接口,不需要使用此规范来命名
全名称:包名.类名
java.lang.Object
extends
来定义class声明的类都有一个父类,Object类。
因为Object类是所有类的父类,使用Object引用来接收所有的类型,参数的最高统一化
Object obj1 = new Person();
Object obj2 = new String();
Object obj3 = new Dog();
void fun(Object obj){}
之所以System.out.println(任意的引用类型)
=>默认都调用该类型的toString()
因为Object类存在toString
方法
打印的带有地址的一串数字和字母 实际上是调用了Object类的toString
方法
可以在子类覆写toString
方法,使调用子类覆写后的方法
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类型!!!
JDK对Object类做了扩展
Object类可以接收所有引用类型的对象(接口,数组,类)
在Java中,若一个方法参数或返回值是Object类型,说明该参数或者返回值可以是任意引用数据类型(数组,类,接口)
当一个场景既可以使用抽象类也可以使用接口定义时,优先考虑使用接口(更灵活)
JDK内置的常用接口
java.lang.Comparable
:当一个类实现了Comparable接口,表示该类具备了可比较的能力!
public int compareTo(Object o){
return 0;
}
覆写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关系,子类和抽象类是一颗继承树上的关系
接口和实现子类更多的是水平方向,表示混合关系的
一个子类通常都可以具备多个能力或者行为
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;
}
最外层对象确实是由原对象的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的对象,避免类型转换了
public static void main(String[] args){
Animal animal1 = new Animal();
Animal animal2 = animal1.clone();
}
//Animal类的无参构造 只调用了一回!!
不会!!!
new类();
= 》 当有new
关键字时,就在堆上开辟该类相应属性的空间给属性赋默认值。
clone()
产生对象,调用clone
时,JVM会开辟与原对象内存大小完全相同的新空间,并将对象中属性的值从原对象中复制一份。(不推荐)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
修饰类你的这个类不希望有别的版本,到此为止。
所有使用者用的这个类完全相同,没有别的实现。
*方式一:直接赋值
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);
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”
所有引用数据类型在比较是否相等时,使用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
为空的问题
当使用直接赋值法产生字符串对象时,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仍然使用直接赋值法产生对象,但是该内容已经在常量池中存在,此时并不会新产生对象,而是复用已有对象。
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");
产生常量池一个对象,堆中也产生一个对象
String str1 = new String("hello");
在栈中产生一个对象str1
,指向堆中的"hello"
String str1 = new String("hello");
String str2 = new String("hello");
String str3 = new String("hello");
程序进行到第2行,常量池中存在hello
,在堆中又产生一个新的字符串对象,再然后str2
指向了新的字符串对象**(从右向左进行代码)**
有new就有新空间,这三行代码产生四个字符串对象,其中一个在产量池中,其余三个在堆中。
String str1 = new String("hello");
String str2 = new String("hello");
String str3 = new String("hello");
不行,str1指向了堆中普通的字符串"hello",和常量池中的“hello”永远不相等
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 又错了
str1.intern();
把abc挪到常量池中
此时常量池中存在abc对象了,str2会复用这个对象
str1.intern();
此时并没有赋值,所以只是移动,并没有拷贝
所谓的字符串不可变指的是字符串对象的内容不能变,而不是字符串引用不能变
String str = "hello";
str = str + "world";
str += "!!!";
System.out.println(str);
这里的不可变指的“hello”,“world”,“helloworld”,“!!!“,“helloworld!!!”这些字符串对象一旦声明后就无法修改其内容
先产生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数组
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
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
StringBuilder
、StringBuffer
的区别: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个
"abc"就是字符串字面量,第一次出现,由JVM产生此字符串对象并保存到常量池中。
在堆中产生一个普通的字符串对象,值从常量池中拷贝过来
弄懂这两个问题就好办了
str
指向堆中的good
,ch
作为一个普通的字符数组,不在常量池中change()
中;一个新产生的形参str
指向堆中的good
,新产生的字符数组指向堆中的a b c
;堆中产生对象test ok
,形参str
指向对象test ok
,ch
的首位字符改为g
。change()
结束,两个形参出栈,两个变量都没了~,返回主方法ex.str
,还是指向的good
,ex.ch
指向的是gbc
ch
和形参指向的是同一个地址,所以改了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
字符串的内部实际上就是使用字符数组来存储
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);
public char charAt(int index) |
普通 | 取得指定索引位置的字符,索引从0开始 |
---|
String str = "hello";
System.out.println(str.charAt(1));
//e
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;
}
将字符串保存到文件中或是通过网络传输都要用到字节数组
比如在聊天室发送奥利给这个消息,这个消息其实是字节码的对象,我能发出去,你们能收到实际上就是把字符串按照相应的编码编成了字节数组,你们那边收到字节数组以后再把字节数组还原为字符串。
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编码将字符串转为字节数组
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个字节
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
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
才能修改
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. 你的这个格式是个特殊字符,需要转义处理\\.
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
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]{
//异常的出口,最终会执行的代码块
}
int[] data = {1, 2, 3};
System.out.println("before...");
System.out.println(data[100]);
System.out.println("after...");
//before...
//ArrayIndexOutOfBoundsException..
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...
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
如果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的引用
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
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代码块中写返回值
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
}
throws
和throw
关键字 - 人为抛出异常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
异常也是对象,异常对象,这个对象默认由JVM产生
异常体系:JDK内部异常的继承关系
Java中的异常分为两大类,一类称为受查异常(红色),另一类称为非受查异常(蓝色)
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
File file = new ("test.txt");
try{
Scanner scanner = new Scanner(new FileInputStream(file));
} catch (FileNotFoundException e){
e.printStackTrace();
}
//test.txt(系统找不到指定的文件)
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)
try...catch.finally
的使用流程运行时异常就是非受查异常的一种,null,数组越界,类型转换
IOException
就是受查异常的一宗
包装类:就是把8大基本类型封装到类之中
a. Object类
可以接收所有引用数据类型(数组
,类
,接口
),为了让Object类
可以接收Java中一切内容,引入包装类
,把基本类型的数值封装到类的对象之中就产生了包装类
。
b. 基本类型
的默认值其实在很多场景下会造成误导,引用类型
的默认值就是null
比如 double
-> 0.0
有一个扣费的需求,当前费用*当前费率(打几折) = 实际扣款数
如果费率使用double
类型,默认值0.0
使用的是double
的包装类Double
,默认值为null
需要有一个类型把基本类型封装
到类中
JDK中的包装类 - 以下8种
以整型为例
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
,基本类型
的默认值就是其数据类型默认值
Integer
,Double
-> 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
方法,咋样都不会错