JavaSE-面向对象
类
类的五大成分:成员变量、构造器、方法、代码块、内部类
类:是对象共同特征的描述;对象:是真实存在的具体实例
- 实例化对象:
类名 对象名 = new 类名(); - 访问属性:
对象名.成员变量 - 访问行为:
对象名.方法名(…)
public class 类名 {
[修饰符] 数据类型 成员变量名 [= 默认值];
[修饰符] 构造器名(形参列表)
[修饰符] 方法返回值类型 方法名(形参列表)
}
定义成员变量
成员变量的语法格式:[修饰符] 数据类型 变量名称 [= 初始化值];
- 修饰符:修饰符可省略,也可以是
public、protected、private、static、final - 数据类型:基本数据类型和引用数据类型均可
- 成员变量名:只要是一个合法的标识符即可
- 默认值:不指定初始化值时,变量存在默认值
| 数据类型 | 默认值 |
|---|---|
| byte、short、char、int、long | 0 |
| float、double | 0.0 |
| boolean | false |
| 类、接口、数组、String | null |
定义方法
方法的语法格式:[修饰符] 方法返回值类型 方法名(形参列表){}
- 修饰符:修饰符可省略,也可以是
public、protected、private、static、final、abstract其中 public protected private 只能三选一;abstract final 只能二选一 - 方法返回值类型:基本数据类型和引用类型均可;如果声明了返回值类型,则方法体内必须有一个有效的 return 语句,该语句返回一个变量或表达式,且该变量的数据类型必须与此处的类型匹配;如果方法没有返回值,则必须使用 void 来声明没有返回值
- 形参列表:由零组到多组
形参类型 形参名组合而成,多组参数之间以英文逗号隔开。一旦在定义方法时指定了形参列表,则调用该方法时必须传入对应的参数值
定义构造器
方法的语法格式:[修饰符] 构造器名(形参列表){}
- 修饰符:修饰符可省略,也可以是
public、protected、private其中之一 - 构造器名:必须与类名一致
- 形参列表:和定义方法形参列表的格式完全相同
如果程序员没有为一个类编写构造器,系统会自动为该类提供一个默认的构造器;但如果程序提供了自定义的构造器,系统将不再为该类提供构造器。此时就需要手动添加一个无参构造器了,以防初始化对象时报错
- 无参构造器(默认):初始化对象时,成员变脸的数据均采用默认值
- 有参构造器:在初始化对象时,同时可以为对象进行赋值
public class Person {
public String name;
public int age;
public void say(String content) {
System.out.println(content);
}
}
对象
创造对象的根本途径是构造器,通过 new 官方尖子来调用某个类的构造器即可创建这个类的实例
// 创建对象
Person p = new Person();
// 访问对象的实例变量
p.name = '张三';
// 访问对象的方法
p.say("hello world!");
方法详解
方法的语法格式
- 访问修饰符:方法允许被访问的权限范围
- 返回值类型:如果方法不返回任何值,则返回值类型指定为void;如果方法具有返回值,则需要指定返回值的类型,并且在方法体中使用 return 语句返回值
- 形参:在方法声明时用于接收外界传入的数据(参数可以有多个,多个参数间以逗号隔开,每个参数由参数类型和参数名组成,以空格隔开)
- 实参:调用方法时实际传给方法的数据
- 方法名:定义方法的名字,必须使用合法的标识符,见名知意
// 方法声明格式
修饰符 返回值类型 方法名 (形参列表) {
方法体代码
return 返回值;
}
// 方法的调用
方法名(实参列表)
方法的使用说明
- 方法的编写顺序无所谓;方法与方法之间是平级关系,不能嵌套定义
- 方法不调用就不执行, 调用时必须严格匹配方法的参数情况;方法如果没有形参列表,调用的时候则不能传入参数值,否则报错
- 方法的返回值类型为void(无返回值),方法内则不能使用return返回数据;如果方法的返回值类型写了具体类型,方法内部则必须使用return返回对应类型的数据
- return语句下面,不能编写代码,因为永远执行不到,属于无效的代码
- 有返回值的方法调用时可以选择定义变量接收结果,或者直接输出调用,甚至直接调用;无返回值方法的调用只能直接调用
return关键字单独使用:return; 可以立即跳出并结束当前方法的执行; return关键字单独使用可以放在任何方法中
方法的参数传递机制
Java的参数传递机制“值传递”:在传输实参给方法的形参的时候,并不是传输实参变量本身, 而是传输实参变量中存储的值,这就是值传递
- 基本类型和引用类型的参数在传递时都是值传递
- 基本数据类型的参数传递存储的是数据值
- 引用数据类型的参数传递存储的是地址值
方法重载
同一个类中,出现多个方法名称相同,但是形参列表是不同的,那么这些方法就是重载方法
- 只要是同一个类中,方法名称相同、形参列表不同,那么他们就是重载的方法,其他都不管(如:修饰符,返回值类型都无所谓)
- 形参列表不同指的是:形参的个数、类型、顺序不同,不关心形参的名称
方法重写
子类可以重写父类的方法,这种子类包含父类同名方法的现象称为方法重写(Override),也被称为方法覆盖
方法的重写要遵循“两同两小一大”规则
- “两同”:方法名相同,形参列表相同
- “两小”:子类方法返回值类型应比父类方法返回值类型更小或相等;子类方法声明抛出的异常应比父类方法抛出的异常更小或相等
- “一大”:子类方法的访问权限应比父类方法的访问权限更大或相等
尤其指出:覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法
- 私有方法不能被重写
- 子类不能重写父类的静态方法,如果重写会报错的
@Override重写注解
- @Override是放在重写后的方法上,作为重写是否正确的校验注解
- 加上该注解后如果重写错误,编译阶段会出现错误提示
- 建议重写方法都加@Override注解,代码安全,优雅!
形参个数可变的方法
可变形参:JDK1.5 之后,Java允许定义形参个数可变的参数,从而允许为方法指定数量不确定的形参。如果在定义方法时,在最后一个形参的类型后添加三点(…),则表明形参可以接受多个参数值,多个参数值被当成数组传入
public static void test(int a, String... books) {
for (String book : books) {
System.out.println(book);
}
}
public static void main(String[] args) {
test(5, "Java编程思想", "Java并发编程");
}
代码块
代码块是类的5大成分之一,定义在类中方法外,主要用来对单个对象进行初始化操作
代码块分为两大类:静态代码块 和 普通代码块(构造代码块)
\[代码块 \begin{cases} \text{静态代码块} \begin{cases} \text{格式:static {}}\\ \text{特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次} \\ \text{使用场景:在类加载的时候做一些静态数据初始化的操作,以便后续使用} \end{cases} \\ \text{普通代码块} \begin{cases} \text{格式:{}}\\ \text{特点:每次创建对象,并且在构造器执行前,都会执行该代码块中的代码} \\ \text{使用场景:初始化实例资源} \end{cases} \end{cases} \]类初始化的顺序(最顶层父类就是 java.lang.Object)
- 先执行最顶层父类的静态初始化块,然后依次向下,直到执行当前类的静态初始化块(类初始化阶段)
- 先执行最顶层父类的普通代码块、最顶层父类的构造器,然后依次向下,直到执行当前类的普通代码块、当前类的构造器(对象初始化阶段)
接口
接口不是类,而是对类的一组需求描述,这些类要遵守从接口描述的统一格式进行定义
抽象类是从多个类中抽象出来的模板,如果将这种抽象进行得更加彻底,则可以提炼出一种更加特殊的抽象类——接口(interface),接口更像是一种规范
由于接口定义的是一种规范,因此接口里不能包含构造器和初始化块定义。接口里可以包含成员变量(只能是静态常量)、方法(抽象实例方法、默认方法、静态方法、私有方法)、内部类(内部接口、枚举)定义
接口的定义
定义接口的语法格式
[修饰符] interface 接口名 extends 父接口1,父接口2{
定义常量
抽象方法
默认方法、静态方法、私有方法(JDK9)
内部类、接口、枚举
}
- 修饰符可以是public或者省略,如果省略了public访问修饰符,则默认采用包权限访问控制符,即只有在相同包结构下才可以访问该接口
- 接口名应与类名采用相同的命名规则。接口名通常使用形容词
- 接口支持多继承,即一个接口可以有多个直接父接口;但接口只能继承接口,不能继承类
- 子接口将会继承父接口里定义的所有常量、抽象方法
- 一个接口继承多个父接口时,多个接口排在 extends 关键字之后,多个父接口之间以英文逗号隔开
- 因为接口里定义的是多个类共同的公共行为规范,因此接口里的所有成员,包括常量、方法、内部类和内部枚举都是public修饰的。定义接口成员时,可以省略访问控制修饰符,如果指定访问控制修饰符,则只能使用public
- 在接口中定义变量时,系统默认使用 public static final 修饰符。而接口中没有构造器和初始化块,因此接口里定义的成员变量只能在定义时指定默认值
- 在接口中定义普通抽象实例方法时,系统默认使用 public abstract 修饰符
- 接口里的内部类、内部接口、内部枚举默认都采用 public static 修饰符
- 接口里的普通抽象方法没有方法体,但默认方法、静态方法、私有方法都必须有方法体
Java8允许在接口中定义默认方法
- 默认方法必须用 default 修饰,该方法不能使用 static 修饰
- 无论程序是否指定,默认方法总是使用 public 修饰
- 不能直接使用接口来调用默认方法,因为默认方法是属于对象的,所以需要使用接口的实现类的实例来调用默认方法
Java8允许在接口中定义静态方法
- 静态方法必须使用 static 修饰,该方法不能使用 default 修饰
- 无论程序是否指定,静态方法总是使用 public 修饰
- 静态方法必须用本身的接口名来调用
Java9允许在接口中定义私有方法
- 私有方法必须使用 private 修饰
- 只能在本类中被其他的默认方法或者私有方法访问
接口的实现
接口不能用于创建实例,所以需要使用接口的实现类来创建实例
实现接口的语法格式:[修饰符] class 类名 implements 接口1,接口2{}
- implements 部分必须放在 extends 部分之后
- 一个类实现了一个或多个接口后,这个类必须完全实现这些接口里所定义的全部抽象方法(也就是重写这些抽象方法);否则,该类将保留从父接口那里继承到的抽象方法,该类也必须定义为抽象类
接口的注意事项
- 接口不能创建对象
- 一个类实现多个接口,多个接口中有同样的静态方法不冲突;因为静态方法是通过接口名.方法名()进行调用的
- 一个类继承了父类,同时又实现了接口,父类中和接口中有同名方法,默认用父类的
- 一个类实现了多个接口,多个接口中存在同名的默认方法,不冲突,这个类重写该方法即可(不会使用任何一个接口的方法,只使用自己重写的方法)
- 一个接口继承多个接口,是没有问题的,如果多个接口中存在规范冲突则不能多继承
内部类
内部类就是定义在一个类里面的类,里面的类可以理解成(寄生),外部类可以理解成(宿主)
- 内部类通常可以方便访问外部类的成员,包括私有的成员
- 内部类提供了更好的封装性,内部类本身就可以用private protectecd static等修饰,封装性可以做更多控制
- 如果外部类成员变量、内部类成员变量与内部类里方法的局部变量同名,则可以通过使用 this、外部类类名.this 作为限定来区分
静态内部类:有static修饰,属于外部类本身
- 静态内部类创建对象的格式:外部类名.内部类名 对象名 = new 外部类名.内部类构造器
- 范例:Outer.Inner in = new Outer.Inner();
- 静态内部类可以访问外部类的静态成员,不能访问外部类的实例成员
成员内部类:无static修饰,属于外部类的对象
- 成员内部类创建对象的格式:外部类名.内部类名 对象名 = new 外部类构造器.new 内部类构造器();
- 范例:Outer.Inner in = new Outer().new Inner();
- 可以直接访问外部类的静态成员,实例方法中可以直接访问外部类的实例成员
- JDK16之前,成员内部类中不能定义静态成员,JDK 16开始也可以定义静态成员了
- 注意:在成员内部类中访问所在外部类对象 ,格式:外部类名.this
局部内部类:局部内部类放在方法、代码块、构造器等执行体中(了解即可)
- 由于局部内部类不能在外部类的方法以外的地方使用,因此局部内部类不能使用访问控制符和 static 修饰符修饰
- 局部内部类是一个非常鸡肋的语法,在实际开发中很少使用。这是因为局部内部类的作用域太小了:只能在当前方法中使用。大部分时候,定义一个类之后是希望多次复用这个类,但局部内部类无法离开它所在的方法,因此很少使用
匿名内部类:本质上是一个没有名字的局部内部类,定义在方法中、代码块中等
- 匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类或实现一个接口
- 匿名内部类不能是抽象类,因为在系统创建匿名内类时,会立即创建匿名内部类的对象。因此不允许将匿名内部类定义成抽象类
- 匿名内部类不能定义构造器。由于匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以定义初始化块,可以通过实例初始化块来完成构造器需要完成的事情
匿名内部类语法格式:
new 类|抽象类名|或者接口名() {
重写方法;
};