Skip to content

第四章 面向对象编程(OOP) v1.0 ​

第一部分 类与对象 ​

一、类 ​

1.类的基本概念 ​

五大成分:成员变量、构造器,方法,代码块,内部类

2.类的定义 ​

java
/**
 * @projectName(项目名称): project_name
 * @package(包): package_name.file_name
 * @className(类名称): type_name
 * @description(类描述): 一句话描述该类的功能
 * @author(创建人): user
 * @createDate(创建时间): 20XX/XX/XX
 * @updateUser(修改人): user
 * @updateDate(修改时间): 20XX/XX/XX
 * @updateRemark(修改备注): 说明本次修改内容
 * @version(版本): v1.0
 */
访问修饰符 class 类名{
    //类主体(属性、方法定义)
}

3.代码块 ​

  • 静态代码块:类加载时调用,初始化静态数据,格式:static{}
  • 构造代码块:构建对象时调用,初始化实例资源,格式:{}

示例:

java
public class MyClass {
    {
    	System.out.println("==实例代码块执行==")  //创建MyClass类的实例时调用,属于类的资源
	}
	static {
    	System.out.println("==静态代码块执行==")  //加载MyClass类时调用,属于对象资源
	}
}

4.修饰符 ​

Java支持4种不同的访问权限

default(即缺省):在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。

private:在同一类内可见。使用对象:变量、方法。

public:对所有类可见。使用对象:类、接口、变量、方法

protected:对同一包内的类和所有子类可见。使用对象:变量、方法。

静态变量:该类的任何对象,有且只有一个静态成员的实例存在,该静态成员将被所有对象共享。

声明形式:访问修饰符 static 数据类型 属性名=属性;

访问:直接通过类名.静态成员变量名访问,或对象名.静态成员变量名访问

静态方法:静态方法声明独立于对象的静态方法,可以在不创建对象的情况下调用。

声明形式:访问修饰符 static 函数返回类型 函数名(参数列表){}

访问:直接通过类名.函数名(参数列表)访问

static访问注意事项:

  • main方法始终是静态的
  • 静态方法只能访问静态的成员,不可以直接访问实例成员。
  • 实例方法可以访问静态的成员,也可以访问实例成员。
  • 静态方法中不可以出现this关键字。
  • 若类无需创建对象(工具类),可以将其构造器私有化,使其不能创建对象而直接通过类名访问使用

静态代码块:属于类的一部分,与类一起优先加载一次,自动触发执行。

使用final关键字标记一个常量,使该常量只能被赋值一次,在程序运行期间不能改变。

使用final关键字标记一个方法或类,这样做可以限制方法,使其不能被重写,并且不能使其成为子类。

声明方式:访问修饰符 final 数据类型 属性名=属性;

使用public static final修饰的成员变量为常量,必须有初始值,执行过程中值不能改变。

常量的作用和好处:可以用于做系统的配置信息,方便程序的维护,同时也能提高可读性。

5.this关键字 ​

可以使用this关键字指定访问当前类的对象的成员变量、成员方法

6.创建对象 ​

对象创建语句:类名 对象名=new 类名();

二、方法 ​

1.方法定义 ​

java
修饰符 函数返回类型 方法名(形参列表){
    //方法主体
}

2.可变参数 ​

可变参数用在形参中可以接受多个参数,在方法内部视作一个数组

格式:修饰符 方法返回类型 方法名(变量类型...变量名){ }

注意事项:一个形参列表中只能有一个可变参数,且必须在形参列表最后面

3.类中特殊方法 ​

a.构造函数 ​

用于在创建对象时初始化对象,即为对象成员变量赋初始值,构造函数名称必须与其类名相同;构造函数不能有明确的返回类型(Java提供一个默认的无参数构造函数)。

java
访问修饰符 class 类名 {
  访问修饰符 数据类型 属性;
  访问修饰符 类名() {  //构造函数(可以有多个)
     属性 = ... ;
  }
}
b.getter方法和setter方法 ​

声明为私有访问类型的变量只能通过类中公共的getter/setter方法被外部类访问

java
//getter方法(get+变量名)
public String getColor() {
    return color;
}
//setter方法(set+变量名)
public void setColor(String c) {
    thCharacterObj.is.color=c;
}

4.参数传递机制(值传递) ​

在传输实参给方法的形参的时候,并不是传输实参变量本身,而是传输实参变量中存储的值,这就是值传递。

基本类型的参数传递

基础类型的参数传递存储的数据值,改变形参的参数值实参的数值不会发生改变

引用类型的参数传递

引用类型的参数传递存储的地址值,他们指向同一块内存地址,对同一块内存地址进行修改,值会发生改变。

5.方法引用 ​

使用情景:当要将一个方法传递给 Lambda 体,就是对一个方法的调用,那么此时就可以使用“方法引用”

格式:<对象|类>::方法名

例:构造器引用:People::new,方法引用:System.out::println

三、方法的重写与重载 ​

1.方法重写 ​

定义:子类对父类的允许访问的方法的实现过程进行重新编写,返回值和形参都不改变。

优点:子类可以根据需要,定义特定于自己的行为。也就是说子类能够根据需要实现父类的方法。

函数重写的规则:

  1. 一般使用@Override注解来标识
  2. 必须有相同的参数列表和返回类型。
  3. 访问权限不能比父类中被重写的方法的访问权限更低,即不能降低访问权限。
  4. 声明为final或static的方法与构造方法不能被重写。
  5. 如果不能继承一个方法,则不能重写这个方法。

2.方法重载 ​

定义:在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。

要求:被重载的方法必须改变参数列表(参数个数或类型或顺序不一样),最常用的地方是构造器的重载。

函数重载的规则:

  1. 被重载的方法必须改变参数列表,且无法以返回值类型作为重载函数的区分标准。
  2. 被重载的方法可以改变返回类型、访问修饰符。
  3. 被重载的方法可以声明新的或更广的检查异常。
  4. 方法能够在同一个类中或者在一个子类中被重载。

四、面向对象的三大特征 ​

1.封装 ​

定义:把对象的属性和操作(或服务)结合为一个独立的整体,并尽可能隐藏对象的内部实现细节

实现方式:将类的变量声明为private,并提供public getter和setter方法来修改和查看变量的值

封装的优点:

  1. 良好的封装能够减少耦合
  2. 类内部的结构可以自由修改
  3. 可以对成员变量进行更精确的控制(可以实现校验输入等)
  4. 隐藏信息,实现细节

2.继承 ​

java
class 子类名 extends 父类名/超类名/基类名 {
    子类名(){
    	super();  //生成子类对象时,(若未调用父类构造函数,)会默认先调用父类的无参数构造函数
    }
    //其余代码
}

继承方式:一个类从另一个类继承时,继承所有父类的非私有变量和方法(构造函数不会被子类继承)。

访问父类的方法:通过super关键字来访问父类或调用父类构造方法(private方法除外)

3.多态 ​

多态是指同类型的对象,执行同一个行为具有多个不同的表现形式或形态的能力

优点:①消除类型之间的耦合关系 ②可替换性 ③可扩充性 ④接口性 ⑤灵活性 ⑥简化性

存在必要条件:①继承 ②重写 ③父类引用指向子类对象

多态中成员访问特点:

  • 方法调用:编译看左边,运行看右边(调用的实际存储的对象方法)<行为多态>
  • 变量调用:编译看左边,运行也看左边(调用的是生成对象类型存储的变量)<变量不多态>
java
Animal dog=new Dog();
dog.run();  //调用的是子类Dog的成员方法run
Syetem.out.println(dog.name);  //调用的是父类的成员变量name

4.抽象(第四个特征) ​

五、匿名类与内部类 ​

1.匿名类 ​

匿名类是一种即时继承现有类的方法,在创建对象时直接对其方法进行更改

声明方式:在创建对象时及时更改其方法并生成

java
类名 c = new 类名() {
  @Override
  访问修饰符 函数返回类型 方法名{
    //函数主体
  }
};  //此即为匿名类
//@Override注解用于使代码更容易理解,因为当方法被重写时,它更加明显。

2.内部类 ​

Java支持嵌套类,一个类可以是另一个类的成员。创建一个内部类很简单,只要在类中编写一个类。

作用:

  1. 实现更好的封装,普通类(非内部类)的访问修饰符不能为private或protected,而内部类可以。当我们将内部类声明为private时,只有外部类可以访问内部类,很好地隐藏了内部类。
  2. 内部类可以继承(extends)或实现(implements)其他的类或接口,而不受外部类的影响。
  3. 内部类可以直接访问外部类的成员和方法;相反的,外部类不能直接访问内部类的成员。

六、枚举类型 ​

1.简介 ​

枚举类是一种特殊类,它和普通类一样可以使用构造器、定义成员变量和方法,也能实现一个或多个接口,但枚举类不能继承其他类。枚举类型使用的最常用类型就是枚举常量。

2.定义枚举类型 ​

java
修饰符 enum 类名 {
  枚举常量1, 枚举常量2, ...  //注意:枚举类第一行默认罗列枚举对象名称,这些值是用逗号分隔的
}
类名 变量名=类名.常量;  //引用枚举常量

3.常用情景 ​

  1. switch语句
  2. 当变量(特别是方法参数)只能从一小组可能的值中取出一个时,总是应该使用枚举。

七、Object类与Objects类 ​

1.Object类的作用 ​

在Java中,一个类要么默认继承了Object类,要么间接继承了Object类,其有两个常用方法

  1. toString():默认是返回当前对象在堆内存中的地址信息:类的全限名@内存地址
  2. equals():默认是比较当前对象与另一个对象的地址是否相同,相同返回true,不同返回false

2.Objects类的作用 ​

Objects类从JDK 1.7开始Objects类加入Java,提供了很多对象操作的辅助方法,其有两个常用方法

  1. equals():先在底层进行非空判断,再进行equals比较
  2. isNull():判断变量是否为null,为nul返回true,反之

2.toString()方法 ​

用于让子类重写该方法,以便于返回子类的内容

3.对象的比较与equals()方法 ​

a. "=="比较 ​

当创建对象时,变量存储的是对象的引用。故当使用(==)比较对象时,它实际上比较的是引用而非对象值。当有两个属性相同的对象,因有两个不同的对象(两个不同的引用或内存位置),所以相等性测试返回false。

b.equals()方法比较(用于语义相等性测试) ​

每个对象都有一个预定义的equals()方法,但为使它适用于该类,需要重写它,并检查我们需要的条件。

c.使用Objects类equals()方法比较(更加安全) ​

使用Objects类进行两个对象之间的比较:Objects.equals(obj1,obj2);

使用Objects类底层会先进行非空判断,从而可以避免空指针异常,更加安全。

八、泛型 ​

1.概述 ​

JDK5中引入的特征,可以在编译阶段约束操作的数据类型并进行检查(仅支持引用类型),格式:<数据类型>

核心思想:将出现泛型变量的地方全部替换成传输的真实数据类型

2.自定义泛型类 ​

格式:修饰符 class 类名<泛型变量>{ }

例:public class Generic<T>{ }

3.自定义泛型方法 ​

定义同时声明了泛型的方法是泛型方法(泛型类中的成员方法不是,只是使用了泛型)

格式:修饰符 <泛型变量> 返回值 方法名(泛型变量 t){ }

例:public <T> void show(T t){}

4.自定义泛型接口 ​

格式:修饰符 interface 接口名<泛型变量>{}

5.泛型的通配符(?)与上下限 ​

?可以在使用泛型时匹配一切类型,并使用extends与super定义泛型上下限

格式:<?[ extends|super 子类名|父类名]>

java
public static void go(ArrayList<? extends Car> cars){
    //方法体
}
public static void main(String[] args){
    ArrayList<BENZ> benzs = new ArrayList<>();
    ArrayList<BMW> bmws = new ArrayList<>();
    ArrayList<Dog> dogs = new ArrayList<>();
    benzs.go();  //可以调用go方法
    bmws.go();  //可以调用go方法
    //dogs.go();  //不能调用go方法,不是Car类的子类
}

第二部分 抽象与接口 ​

一、抽象 ​

意义:数据抽象为外界提供了仅有的基本信息,在表示基本特征的过程中不包括实现细节。

实现方式:在Java中,抽象是使用抽象类和接口实现的。

抽象方法:抽象方法指一些只有方法声明,而没有具体实现方法体的方法。

二、抽象类 ​

1.基本概念 ​

意义:如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

实现方式:抽象类不能直接实例化对象。通常以abstract来声明。

2.定义抽象类 ​

sql
abstract class 抽象类名 {
    访问修饰符 abstract void 抽象方法名();
    //其余代码
}

3.抽象类的特点 ​

  1. 如果一个类声明为抽象类,则不能被实例化(不能创建该类型的对象)。
  2. 要使用抽象类,必须从另一个类继承它。
  3. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

4.模板方法模式 ​

java
public abstract class Student {
    public final void print(){  //这是一个模板方法,定义通用结构
        System.out.println("这是一个学生,下面输出学生的信息:");
        System.out.println(information());  //使用抽象方法

    }
    public abstract String information();  //抽象方法交由子类实现
}

三、接口 ​

1.基本概念 ​

意义:一个接口是一个完全抽象的类,只包含抽象方法。

实现方式:当类实现接口的时候,类要实现接口中所有的方法。接口通常以interface来声明。

2.定义接口 ​

java
interface Animal {
    String NAME="Animal";  //常量(public static final可省略不写)
    void eating();  //由于规范思想,方法默认public(abstract public可省略不写)
}  //接口声明
java
class Dog implements Animal {
  public void barking() {
 	System.out.println("Woof-Woof");
  }
  public void eating() {
	System.out.println("crunch-crunch");
  }
}  //类接口的实现

3.接口的规则 ​

  1. JDK8之前,类中只能有抽象方法与常量(public static final修饰)
  2. 接口不能被实例化,但是可以被实现类创建。
  3. 一个类可以实现多个接口,且一个接口能继承另一个接口,这和类之间的继承比较相似。
  4. 若一个类继承了父类,同时又实现了接口,父类中和接口中有同名方法,默认用父类的。

4.接口的特性 ​

  1. 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
  2. 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
  3. 接口中的方法默认都是公有的,声明时public关键字可以省略。
  4. 接口中的常量声明时public static final可省略不写。

5.对接口的扩展 ​

  • Java8:除了常量与抽象方法之外,还可以有两种非抽象的方法:static方法和default方法。

  • static方法:接口的静态方法必须用接口名来调用(实例不能调用),默认会使用static修饰

  • default方法:类似之前写的普通实例方法,只能由实现类对象调用,默认会使用public修饰

  • Java9:允许接口中存在private方法。

  • private方法:只能在本类中被其他的默认方法或者私有方法访问。

四、Lambda表达式(Java 8) ​

1.使用Lambda表达式 ​

java
(参数列表) -> {
    // Lambda表达式体
    return //返回值
}  //用于简化函数式接口的匿名内部类的写法
java
Arrays.sort(array, new Comparator<Integer>() {
    @Override
    public int compare(<Integer> o1, <Integer> o2) {
        return o2 - o1;  //(默认升序)0为相等,负值为o1<o2,正值为o1>o2
    }
});
//使用Lambda表达式
Arrays.sort(array, (Integer o1, Integer o2) -> {
        return o2 - o1;
});

2.进一步简化 ​

  1. 参数类型可以省略不写。
  2. 如果只有一个参数,参数类型可以省略,同时()也可以省略。
  3. 如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写,同时要省略分号!
  4. 如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写。此时,如果这行代码是return语句,必须省略return不写,同时也必须省略";"不写
java
Arrays.sort(array, (o1, o2) -> o2 - o1);

3.优缺点 ​

优点:

  1. 代码简洁,开发迅速
  2. 方便函数式编程
  3. 非常容易进行并行计算
  4. 引入Lambda,改善了集合操作(引入Stream API)

缺点:

  1. 代码可读性变差
  2. 在非并行计算中,计算未必有传统的 for 性能要高
  3. 不容易进行调试

注:通过Lambda 表达式实现的接口不是普通的接口,而是函数式接口,这种接口只能有一个方法。为了防止在函数式接口中声明多个抽象方法,Java 8 提供了一个声明函数式接口注解:@FunctionalInterface,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。

第三部分 对象的比较与类型转换 ​

1.强制类型转换 ​

强制类型转换方式:(转换数据类型)待转变量名

2.向上转型和向下转型 ​

对于类,有两种类型的转换,分别是向上转型(子类->父类)与向下转型(父类->子类)。

向上转型:父类名 childObj = new 子类名();

向上转型就是把子类对象直接赋给父类引用,不用强制转换,可以调用父类类型中的所有成员,不能调用子类类型中特有成员(可以调用重写的父类成员方法),最终运行效果看子类的具体实现。

向下转型:子类名 childObj = (子类名)fatherObj;

向下转型可以调用子类中所有的成员,当父类引用对象是父类本身,在向下转型的过程中是不安全的,编译不会出错,但是运行时会出现强制类型转换异常,一般使用 instanceof 运算符来避免出此类错误。

instanceof关键字 ​

变量名 instanceof 类名:用于判断对象是否属于某个指定的类或其子类的实例,是则返回true