一.面向对象编程
1.面向对象的基本元素:类(class)和对象
①类的声明
语法格式:
[修饰符] class 类名{属性声明;方法声明;
}
②对象的创建(new)
语法格式:
//方式1:给创建有名对象
类名 对象名 = new 类名();//方式2:创建匿名对象
new 类名()
③对象调用属性和方法
语法格式:
对象名.属性
对象名.方法
④匿名对象
- 如果一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
- 我们经常将匿名对象作为实参传递给一个方法调用。
2.对象的内存解析:
①内存分为三个区间(目前我们只需要了解这么多)
堆 :储存对象实例(new出来的对象)。
栈:存储局部变量(boolean、byte、char、short、int、float、long、double)、对象引用(对象在堆内存的首地址)。
方法区:储存类信息、常量、静态变量、即时编译器编译后的代码等数据。
②图解
③注意事项
1.创建一个类的多个对象(比如p1、p2),则每个对象都拥有当前类的一套"副本"(即属性)。当通过一个对象修改其属性时,不会影响其它对象此属性的值。
2.当声明一个新的变量使用现有的对象进行赋值时(比如p3 = p1),此时并没有在堆空间中创建新的对象。而是两个变量共同指向了堆空间中同一个对象。当通过一个对象修改属性时,会影响另外一个对象对此属性的调用。
总结:所以一定要理清楚赋值和创建对象
3.类的成员之一:成员变量(属性)
①成员变量vs局部变量
声明成员变量的要求:必须在类中,方法外;可以显式赋值,也可以不赋值,使用默认值;储存在堆中;
声明局部变量的要求:必须在方法的内部声明,形参,代码块局部变量;储存在栈中;必须手动初始化;
②对象属性的默认初始化
4.类的成员之二:方法(函数)
①方法的声明
语法格式:
[修饰符] 返回值类型 方法名([形参列表])[throws 异常列表]{方法体的功能代码
}
②调用实例的方法
语法格式:
对象.方法名([实参列表])
使用的注意点:必须先声明后使用,且方法必须定义在类的内部。
③类里面,的方法可以直接相互调用
④方法调用的内存解析
1.方法 没有被调用 的时候,都在 方法区 中的字节码文件(.class)中存储。
2.方法 被调用 的时候,需要进入到 栈内存 中运行。方法每调用一次就会在栈中有一个 入栈 动作,即给当前方法开辟一块独立的内存区域,用于存储当前方法的局部变量的值。
3.当方法执行结束后,会释放该内存,称为 出栈 ,如果方法有返回值,就会把结果返回调用处,如果没有返回值,就直接结束,回到调用处继续执行下一条指令。
4.再谈方法
①方法的重载
方法名相同,只需要参数列表必须不同(参数个数或参数类型)。与修饰符、返回值类型无关。
②可变个数的形参
解释:形参的类型可以确定,但是形参的个数不确定,那么可以考虑使用可变个数的形参。
语法格式:
方法名(参数的类型名 ...参数名) //这是新的写法,之前用的时数组。
注意事项:
1.在一个方法的形参中,最多只能声明一个可变个数的形参,且需要放在形参声明的最后。
2.方法参数部分指定类型的参数个数是可变多个:0个,1个或多个。3.可变个数形参的方法与同名的方法之间,彼此构成重载。
4.可变参数方法的使用与方法参数部分使用数组是一致的,二者不能同时声明,否则报错。
③方法的参数传递机制
Java里方法的参数传递方式只有一种: 值传递 。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。
所以在传递基本数据类型的时候,在方法里面对变量更改,是无法影响实参的;而引用数据类型传的是堆中的一串地址,所以我们改变引用数据类型的时候,是会影响实参的。
5.包(package),导入(import)
①package
语法格式:
package 顶层包名.子包名 ;
注意事项:
1.一个源文件只能有一个声明包的package语句。
2.package语句作为Java源文件的第一条语句出现。若缺省该语句,则指定为无名包。
3.包对应于文件系统的目录,package语句中用 “.” 来指明包(目录)的层次,每"."一次就表示一层文件目录。
为了使用定义在其它包中的Java类,需用import语句来显式引入指定包下所需要的类。相当于 import语句告诉编译器到哪里去寻找这个类 。
②import
语法格式:
import 包名.类名;
注意事项:
1.import语句,声明在包的声明和类的声明之间。
2.如果需要导入多个类或接口,那么就并列显式多个import语句即可
3.如果使用 a.* 导入结构,表示可以导入a包下的所有的结构。举例:可以使用java.util.*的方式,一次性导入util包下所有的类或接口。
4.如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。
5.如果已经导入java.a包下的类,那么如果需要使用a包的子包下的类的话,仍然需要导入。
6.如果在代码中使用不同包下的同名的类,那么就需要使用类的全类名的方式指明调用的是哪个类。
6.封装性(private)
①类的权限修饰符:public,缺省
解释说明:
1.用public修饰的类可以在其他的包中访问,且一个包中只能有一个public类。
2.缺省的则不可以在其它包中被访问。
②成员变量、成员方法、构造器、成员内部类的权限修饰符:public 、 protected 、 缺省 、 private 。
修饰符 | 本类内部 | 本包内 | 其他包的子类 | 其他包非子类 |
---|---|---|---|---|
private | √ | × | × | × |
缺省 | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
7.类的成员变量之三:构造器
构造器其实也是一种特别的方法:他用于构造对象。
语法格式:
[修饰符] class 类名{[修饰符] 构造器名(){// 实例初始化代码} [修饰符] 构造器名(参数列表){// 实例初始化代码}
}
注意事项:
1. 构造器名必须与它所在的类名必须相同。
2.它没有返回值,所以不需要返回值类型,也不需要void。3.构造器的修饰符只能是权限修饰符。
4.当我们没有显式的声明类中的构造器时,系统会默认提供一个无参的构造器并且该构造器的修饰符默认与类的修饰符相同。
5.当我们显式的定义类的构造器以后,系统就不再提供默认的无参的构造器了,所以当我们定义了有参的构造器之后,如果我们需要在调用无参的构造器,我们需要自己写,然后构造器是可以重载的。
8.this
①实例方法或构造器中使用当前对象的成员
情景提出:如果在一个类中定义的成员变量和方法的形参同名,我们写下名字的时候我们就无法辨别是那个,所有我们就需要this关键字,来调用本类中成员变量,所以可以用来辨别是成员变量还是局部变量。
语法格式:
this.局部变量名
②同一个类中构造器互相调用
语法格式:
this() //调用本类的无参构造器
this(实参列表) //调用本类的有参构造器
注意事项:
1.this()和this(实参列表)只能声明在构造器首行。
9.继承(extends)
语法格式:
[修饰符] class 类A {...
}
[修饰符] class 类B extends 类A {...
}
继承中的细节说明:
1.当子类对象被创建时,在堆中给对象申请内存时,就要看子类和父类都声明了什么实例变量,这些实例变量都要分配内存。
2.当子类对象调用方法时,编译器会先在子类模板中看该类是否有这个方法,如果没找到,会看它的父类甚至父类的父类是否声明了这个方法,遵循 从下往上 找的顺序,找到了就停止,一直到根父类都没有找到,就会报编译错误。
注意事项:
1.子类不能直接访问父类中私有的(private)的成员变量和方法。
2.支持一个父类可以被多个子类继承。3.java中声明类,如果没有显示声明他的父类的时候,则默认继承java.lang.Object这个类
10.方法的重写
子类中的方法名和父类中的方法名相同且形参也相同,就构成了重写。
重写的一些注意事项:
1.子类重写的方法的返回值类型 不能大于 父类被重写的方法的返回值类型。(子类<父类)(返回值是void的话必须相同)
2.子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限。但是注意父类的私有方法不能重写,跨包的父类缺省的方法也不能重写,因为他们都不提供被访问。
3.子类方法抛出的异常不能大于父类被重写方法的异常。
4.此外,子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。
11.再谈四种权限修饰符号
protected是继承中新产生的一种权限修饰符。
修饰符 | 本类 | 本包 | 其他包子类 | 其他包非子类 |
---|---|---|---|---|
private | √ | × | × | × |
缺省 | √ | √(本包子类非子类都可见) | × | × |
protected | √ | √(本包子类非子类都可见) | √(其他包仅限于子类可见) | × |
public | √ | √ | √ | √ |
注意事项:
1.成员权限修饰符>类的权限修饰符也没有意义(这点很容易理解)
12.super
①子类中调用父类被重写的方法
改关键字的作用:如果我们子类已经重写了父类的方法,但是还是想要调用父类中的该方法这时候我们就需要super这个关键字了。
语法格式:
super.父类的方法名
注意事项:
1.前面没有super或者没有this和有this:先从子类找匹配方法,如果没有,再从直接父类找,再没有,继续往上追溯。
2.前面有super:从当前子类的直接父类找,如果没有,继续往上追溯。
②子类中调用父类中同名的成员变量
super.父类的属性名
注意事项:
1.变量前面没有super.和this:在构造器、代码块、方法中如果出现使用某个变量,先查看是否是当前块声明的局部变量;如果不是局部变量,先从当前执行代码的本类去找成员变量;如果从当前执行代码的本类中没有找到,会往上找父类声明的成员变量(权限修饰符允许在子类中访问的)
2.变量前面有this:通过this找成员变量时,先从当前执行代码的本类去找成员变量;如果从当前执行代码的本类中没有找到,会往上找父类声明的成员变量(权限修饰符允许在子类中访问的)
3.变量前面super:通过super找成员变量,直接从当前执行代码的直接父类去找成员变量(权限修饰符允许在子类中访问的);如果直接父类没有,就去父类的父类中找(权限修饰符允许在子类中访问的)
③子类构造器中调用父类的构造器
语法格式
super(形参)
1.子类继承父类时,不会继承父类的构造器。只能通过super调用父类指定的构造器。
2.如果要显示调用父类的构造器必须要写在首行(这点和this调用构造器时类似)(因为this和super都必须写在首行所以这两个只能写其中一个)。
3.如果在子类构造器的首行既没有显示调用this或者super,则子类的构造器默认调用父类的空参构造器。(所以得出结论:子类的任何一个构造器中,要么会调用本类中重载的构造器,要么会调用父类的构造器)(所以在子类中我们一般会显示调用this或者super)。
13.子类对象实例化的过程
①先加载父类在加载子类
这个很好理解因为this和super都写在第一行嘛。
14.多态性:衍生出了,抽象和接口,instanceof
①使用要求和特点(重要)
形成多态的条件:① 类的继承关系 ② 方法的重写(必须要重写意味着:不能调用用子类添加的新的方法,也恰好说名了,多态的弊端)。③通过父类的引用调用重写的方法。
多态的特点:编译时看左边,运行时看右边(很重要一定要理解清楚这句话)。
②使用场景(一份代码一个文件)
public class Pet {private String nickname; //昵称public String getNickname() {return nickname;} public void setNickname(String nickname) {this.nickname = nickname;} public void eat(){System.out.println(nickname + "吃东西");} }
public class Dog extends Pet {//子类重写父类的方法public void eat() {System.out.println("狗子" + getNickname() + "啃骨头");} //子类扩展的方法public void watchHouse() {System.out.println("看家");} }
1.方法的形参声明体现多态
public class Person{private Pet pet;public void adopt(Pet pet) {//形参是父类类型,实参是子类对象this.pet = pet;} public void feed(){pet.eat();//pet实际引用的对象类型不同,执行的eat方法也不同} }
public class TestPerson {public static void main(String[] args) {Person person = new Person();Dog dog = new Dog();dog.setNickname("小白");person.adopt(dog);//实参是dog子类对象,形参是父类Pet类型,这里体现了多态person.feed();} }
2.方法返回值类型体现多态
public class PetShop {//返回值类型是父类类型,实际返回的是子类对象,这里返回值体现了多态public Pet sale(String type){switch (type){case "Dog":return new Dog();} return null;} }
public class TestPetShop {public static void main(String[] args) {PetShop shop = new PetShop();Pet dog = shop.sale("Dog");dog.setNickname("小白");dog.eat();} }
③多态的好处和弊端
好处:变量引用的子类对象不同,执行的方法就不同,实现动态绑定。代码编写更灵活、功能更强大,可维护性和扩展性更好了。
弊端:一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法,于是就有了下面的解决方案。
④向上转型与向下转型
向上转型(多态):子类变成了父类,所以叫做向上,而且向上转型是自动完成的。
多态中出现了一个问题:不能调用子类拥有,而父类没有的方法,所以我们这里就需要向上转型。
向下转型: 当左边的变量的类型(子类)<右边对象/变量的编译时类型(父类),我们就称为向下转
语法格式:
子类类型 变量名 = (子类类型)编译是父类但实际确是子类变量 //这点和强制转换有点类似
然后我们这时候我们在用上面的变量就可以调用 子类拥有,而父类没有的方法了。
但是我们这里强行转换容易出现问题,因为编译是父类但实际是子类变量 的子类可能和我们要强制转换的子类不一样就会导致出错,所以我们这里引出了一个新的关键字 instanceof
instanceof关键字的语法格式:
对象a instanceof 数据类型A //检验对象a是否是数据类型A的对象,返回值为boolean型
但是我们这里还需要注意一点:如果对象a属于类A的子类B,a instanceof A值也为true。
//然后下面这段代码是来测试instanceof关键字的
public class Main {public static void main(String[] args) {Pet pet = new Dog();pet.setNickname("111");if(pet instanceof Dog) { //pet是Dog的对象所以运行通过System.out.println("11111");}if(pet instanceof Pet){ //pet是Pet的子类对象所以运行通过System.out.println("22222");}}
}
⑤多态的其他注意事项
1.成员变量没有多态性
2.子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚方法。(应该和c++中的虚函数差不多一个意思)
14.object
1.objiect是所有类的祖先类
2.如果一个类没有特别指定父类,那么默认则继承自Object类。
object的类方法:
①equals()
equals 和 ==:
1.在==中,基本类型两个变量值相等为true,在引用类型中指向同一个对象时才会返回true。
2.equals中,对类File、String、Date及包装类来说比较的是值相等,其实在object实现的是==来判断,但是这些类都重写equals,所以可以用来比较值相等,所以我们一般在比较自己写的类的时候都要自己重写equals。
②toString()
Person now=new Person(); System.out.println(now); //同等于下面的代码 System.out.println(now.toString());
对于上面的代码我们没有重写 toString 的时候打印的对象的地址值(其实也不算是地址吧),我们重写之后一般就是用来打印对象的内容的,像String,File,Date或包装类等Object的子类,它们都重写了Object类的toString(),在调用toString()时,返回当前对象的实体内容。
15.静态修饰符号(static)
用static修饰的特点:随着类的加载而加载,优先于对象存在;修饰的成员被所有对象所共享;访问权限允许时,可不创建对象,直接被类调用。
①静态成员变量(或类变量,类属性)
//调用方法
对象.静态变量
类名.静态变量
②静态方法
//调用方法
对象.静态方法
类名.静态方法
静态方法的特点:
1.在static方法内部只能访问类的static修饰的属性或方法,不能访问类的非static的结构。
2.静态方法中不能调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时候无法传递this引用。
3.静态方法可以被子类继承,但不能被子类重写。
4.因为不需要实例就可以访问static方法,因此static方法内部不能有this,也不能有super。如果有重名问题,使用“类名.”进行区别。
16.类的成员之四:代码块
语法格式:
【修饰符】 class 类{static{静态代码块}
}
①静态代码块
作用:
1.可以对类的属性、类的声明进行初始化操作。
2.不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。(一般是给静态成员变量初始化的)
3.静态代码块的执行要先于非静态代码块。
4.静态代码块随着类的加载而加载,且只执行一次。
②非静态代码块(构造代码块)
作用:
1.和构造器一样,也是用于实例变量的初始化等操作。
2.可以对类的属性、类的声明进行初始化操作(构造代码块一般用于初始化实例成员变量)。
3.除了调用非静态的结构外,还可以调用静态的变量或方法。
4.每次创建对象的时候,都会执行一次。且先于构造器执行。
③实例变量的赋值顺序
16.final关键字:最终的,不可更改的
①修饰类
表示这个类不能被继承,没有子类。提高安全性,提高程序的可读性。
②修饰方法
表示这个方法不能被子类重写。
③修饰变量
final修饰某个变量(成员变量或局部变量),一旦赋值(这里是赋值不是初始化,说明可以在声明的时候不赋值,而是在之后赋值,赋值的位置有:显式赋值、或在初始化块赋值、实例变量还可以在构造器中赋值),它的值就不能被修改,即只能赋值一次。
17.抽象类和抽象方法(abstract)
抽象类的语法:
[权限修饰符] abstract class 类名{
}[权限修饰符] abstract class 类名 extends 父类{
}
抽象方法的语法:
[其他修饰符] abstract 返回值类型 方法名([形参列表]);
//注意没有方法体
注意事项:
1. 抽象类不能创建对象。
2. 抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的。
3. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
4. 抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
18.接口(interface)
接口的定义,它与定义类方式相似,但是使用 interface 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型,接口没有构造器和静态代码块。
①语法格式
[修饰符] interface 接口名{// 接口的成员列表:公共的静态常量公共的抽象方法公共的默认方法(JDK8以上)公共的静态方法(JDK8以上)私有方法(JDK9以上) }
②接口的成员说明
在JDK8.0 之前,接口中只允许出现:
(1)公共的静态的常量:其中 public static final 可以省略。
(2)公共的抽象的方法:其中 public abstract 可以省略。
在JDK8.0 时,接口中允许声明 默认方法 和 静态方法 :
(3)公共的默认的方法:其中public 可以省略,建议保留,但是default不能省略。
(4)公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略。
③类实现接口
//接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法 public class 类名称 implements 接口名称{// ... }
④接口的多实现
//一个类只能继承一个父类,而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现,并且一个类能继承一个父类,同时实现多个接口。 [修饰符] class 实现类 extends 父类 implements 接口1,接口2,接口3...{// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写 }//注意:一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。
⑤接口的多继承
一个接口能继承另一个或者多个接口,接口的继承也使用 extends 关键字,子接口继承父接口的方法。 public interface 子接口 extends 父接口1,父接口2... {//.... }
⑥接口与实现类对象构成多态引用
实现类实现接口,类似于子类继承父类,因此,接口类型的变量与实现类的对象之间, 也可以构成多态引用。通过接口类型的变量调用方法,最终执行的是你new的实现类 对象实现的方法体。
⑧使用接口的静态成员
接口不能直接创建对象,但是可以通过接口名直接调用接口的静态方法和静态常量。
⑦使用接口的静态方法和非静态方法(这个好像不常用)1.使用接口的静态方法
对于接口的静态方法,直接使用“ 接口名. ”进行调用即可,也只能使用“接口名."进 行调用,不能通过实现类的对象进行调用。
2.使用接口的非静态方法
对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用接口不能直接 创建对象,只能创建实现类的对象。
⑧还有一些命名冲突问题实在是太复杂了,开发一般也不会这样写,而且写起来语法我看起来非常的怪,所以这里就不在讲了。
19.类的成员之五:内部类(语法很特别)
①成员内部类
成员内部类分为静态局部内部类和非静态局部内部类:
1.创建内部类
[修饰符] class 外部类{[其他修饰符] [static] class 内部类{} }
2.实例化内部类
// 实例化静态内部类 外部类名.静态内部类名 变量 = 外部类名.静态内部类名(); 变量.非静态方法(); // 调用里面的方法//实例化非静态内部类 外部类名 变量1 = new 外部类(); // 需要先创建非静态内部类 外部类名.非静态内部类名 变量2 = 变量1.new 非静态内部类名(); 变量2.非静态方法();
3.内部类中访问外部类的成员
// 静态内部类访问外部类的成员 //如果是静态的可以直接访问 //如果是非静态的需要创建外部类的对象才可以访问//非静态内部类访问外部类的成员 //内部类想调用他外面这一层类的方法或者变量: //外部类名.this.方法名/成员变量 //这个写法我自己就感觉非常奇怪
③匿名内部类( 也可以叫做匿名子类)(这两点非常重要一定要理解重要)
1.使用
new 父类([实参列表]){重写方法... } //这个好像不可以用父类接受new 父接口(){重写方法... } //这个好像可以被父接口变量接受
2.个人感觉这有点像继承/实现过来重写了一下,但是不知道理解的是不是对的。
20.包装类
①基本的数据类型和包装类
基本数据类型 | 包装类 | 缓存对象 |
---|---|---|
byte | Byte | -128 ~ 127 |
short | Short | -128 ~ 127 |
int | Integer | -128 ~ 127 |
long | Long | -128 ~ 127 |
float | Float | 没有 |
double | Double | 没有 |
char | Character | 0 ~ 127 |
boolean | Boolean | true 和 false |
②装箱和拆箱
1.装箱和拆箱
int i = 10;// 装箱操作,新建一个 Integer 类型对象,将 i 的值放入对象的某个属性中 Integer i1 = Integer.valueOf(i); Integer i2 = new Integer(i);// 拆箱操作,将 Integer 对象中的值取出,放到一个基本数据类型中 int j = i1.intValue();
2.自动装箱和拆箱
int i = 10;Integer i1 = i; // 自动装箱 Integer i2 = (Integer)i; // 自动装箱int m = i1; // 自动拆箱 int n = (int)i1; // 自动拆箱
③还有其他知识不过太细节感觉没啥用这里就不在过多的赘述了。
21.注解
目前以我的了解是用来帮助帮助开发的一些规范的,可以提升效率,可以防止代码犯一些低级错误,现在可以先不用了解。
二.异常处理
编译时异常:
在程序编译期间发生的异常,称为编译时异常,也称为受检查异常
运行时异常:
在程序执行期间发生的异常,称为运行时异常,也称为非受检查异常。 RunTimeException以及其子类对应的异常,都称为运行时异常。
①异常的抛出
1.由虚拟机自动生成:程序运行过程中,虚拟机检测到程序发生了问题,那么针对当前代码,就会在后台自动创建一个对应异常类的实例对象并抛出。
2.由开发人员手动创建
throw new 异常类名(参数);
注意事项:
- 如果抛出编译时异常类型的对象,需要使用throws或者try...catch处理,否则编译不通过(这里必须要理解,我刚开始这里听的非常迷糊)。
- 抛出的异常必须是Throwable或其子类的实例
- 如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到JVM收到后中断程序----异常是按照类型来捕获的.
②异常的捕获
①try-catch-finally捕获异常
try{...... //可能产生异常的代码 } catch( 异常类型1 e ){...... //当产生异常类型与异常类型1相同或是异常1的子类的时候,异常被捕获。 } catch( 异常类型2 e ){...... //当产生异常类型与异常类型2相同或是异常2的子类的时候,异常被捕获。 } finally{...... //无论是否发生异常,都无条件执行的语句 }
finally的作用:finally中的代码一定会执行的,一般在finally中进行一些资源清理的扫尾工作。
②throws捕获异常
处在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常.
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{...}
③异常的处理流程
1.程序先执行 try 中的代码
2.如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.
3.如果找到匹配的异常类型, 就会执行 catch 中的代码
4.如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者.
5.无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行).
6.如果上层调用者也没有处理的了异常, 就继续向上传递.
7.一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止.
④自定义异常类
自定义异常前需要注意的点;
1.自定义异常通常会继承自 Exception 或者 RuntimeException。
2.建议大家提供至少两个构造器,一个是无参构造,一个是(String message)构造器。
3.自定义的异常只能通过throw抛出。//代码示例 class UserNameException extends Exception {public UserNameException(String message) {super(message);} }
三.泛型
1.定义泛型类
语法:
// 示例一: class 泛型类名称<类型形参列表> {// 这里可以使用类型参数 } class ClassName<T1, T2, ..., Tn> { }// 示例二: class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {// 这里可以使用类型参数 } class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {// 可以只使用部分类型参数 }
2.泛型类的使用
语法:
泛型类<类型实参> 变量名; // 定义一个泛型类引用 new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象
示例:
MyArray<Integer> list = new MyArray<Integer>(); // 也可以写成下面这样,原因是 类型推导 MyArray<Integer> list = new MyArray<>(); // 可以推导出实例化需要的类型实参为 Integer
注意:泛型只能接受类,所有的基本数据类型必须使用包装类!
3.泛型是如何编译的
擦除机制:在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制。
4.泛型的上界
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
语法:
class 泛型类名称<类型形参 extends 类型边界> {... }
示例:
public class MyArray<E extends Number> {... }// 只接受 Number 的子类型作为 E 的类型实参 MyArray<Integer> l1; // 正常,因为 Integer 是 Number 的子类型 MyArray<String> l2; // 编译错误,因为 String 不是 Number 的子类型
了解: 没有指定类型边界 E,可以视为 E extends Object
复杂示例:(这个还是要看一看,但是感觉不是很重要)
public class MyArray<E extends Comparable<E>> {... } // E必须是实现了 Comparable 接口的 // 然后E就可以在这个类里面比较大小的
5.泛型方法
语法:
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { }
示例:
public class Util {//静态的泛型方法 需要在static后用<>声明泛型类型参数public static <E> void swap(E[] array, int i, int j) {E t = array[i];array[i] = array[j];array[j] = t;} }
6.通配符
①通配符的使用
class Message<T> {private T message ;public T getMessage() {return message;}public void setMessage(T message) {this.message = message;} }public class TestDemo {public static void main(String[] args) {Message<Integer> message = new Message() ;message.setMessage(55);fun(message);} // 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改public static void fun(Message<?> temp){//temp.setMessage(100); 无法修改!System.out.println(temp.getMessage());} }
②通配符的上界,不能进行写入数据,只能进行读取数据。
语法:
<? extends 上界> <? extends Number>//可以传入的实参类型是Number或者Number的子类
这个图片表示:泛型的内容可以是这些类: (Message<? extends Fruit> temp)
示例:
class Food { } class Fruit extends Food { } class Apple extends Fruit { } class Banana extends Fruit { } class Message<T> { // 设置泛型private T message ;public T getMessage() {return message;}public void setMessage(T message) {this.message = message;} } public class TestDemo {public static void main(String[] args) {Message<Apple> message = new Message<>() ;message.setMessage(new Apple());fun(message);Message<Banana> message2 = new Message<>() ;message2.setMessage(new Banana());fun(message2);} // 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改// 可以想象,如果传进来的是GreenApple类,我们要改成 Banana,Apple都是存在问题的public static void fun(Message<? extends Fruit> temp){//temp.setMessage(new Banana()); //仍然无法修改!//temp.setMessage(new Apple()); //仍然无法修改!System.out.println(temp.getMessage());} }
③通配符的下界,不能进行读取数据,只能写入数据。
语法:
<? super 下界> <? super Integer>//代表 可以传入的实参的类型是Integer或者Integer的父类类型
这个图片表示:泛型的内容可以是这些类: (Plate<? super Fruit> temp)
示例:
class Food { } class Fruit extends Food { } class Apple extends Fruit { } class Plate<T> {private T plate ;public T getPlate() {return plate;}public void setPlate(T plate) {this.plate = plate;} } public class TestDemo {public static void main(String[] args) {Plate<Fruit> plate1 = new Plate<>();plate1.setPlate(new Fruit());fun(plate1);Plate<Food> plate2 = new Plate<>();plate2.setPlate(new Food());fun(plate2);}public static void fun(Plate<? super Fruit> temp){// 此时可以修改!!添加的是Fruit 或者Fruit的子类temp.setPlate(new Apple());//这个是Fruit的子类temp.setPlate(new Fruit());//这个是Fruit的本身//Fruit fruit = temp.getPlate(); 不能接收,这里无法确定是哪个父类System.out.println(temp.getPlate());//只能直接输出} }