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

  1. 先执行最顶层父类的静态初始化块,然后依次向下,直到执行当前类的静态初始化块(类初始化阶段)
  2. 先执行最顶层父类的普通代码块、最顶层父类的构造器,然后依次向下,直到执行当前类的普通代码块、当前类的构造器(对象初始化阶段)

接口

接口不是类,而是对类的一组需求描述,这些类要遵守从接口描述的统一格式进行定义

抽象类是从多个类中抽象出来的模板,如果将这种抽象进行得更加彻底,则可以提炼出一种更加特殊的抽象类——接口(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 类|抽象类名|或者接口名() {
    重写方法;
};