第四章 面向对象编程(OOP) v1.0
第一部分 类与对象
一、类
1.类的基本概念
五大成分:成员变量、构造器,方法,代码块,内部类
2.类的定义
/**
* @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{} - 构造代码块:构建对象时调用,初始化实例资源,格式:
{}
示例:
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.方法定义
修饰符 函数返回类型 方法名(形参列表){
//方法主体
}2.可变参数
可变参数用在形参中可以接受多个参数,在方法内部视作一个数组
格式:修饰符 方法返回类型 方法名(变量类型...变量名){ }
注意事项:一个形参列表中只能有一个可变参数,且必须在形参列表最后面
3.类中特殊方法
a.构造函数
用于在创建对象时初始化对象,即为对象成员变量赋初始值,构造函数名称必须与其类名相同;构造函数不能有明确的返回类型(Java提供一个默认的无参数构造函数)。
访问修饰符 class 类名 {
访问修饰符 数据类型 属性;
访问修饰符 类名() { //构造函数(可以有多个)
属性 = ... ;
}
}b.getter方法和setter方法
声明为私有访问类型的变量只能通过类中公共的getter/setter方法被外部类访问
//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.方法重写
定义:子类对父类的允许访问的方法的实现过程进行重新编写,返回值和形参都不改变。
优点:子类可以根据需要,定义特定于自己的行为。也就是说子类能够根据需要实现父类的方法。
函数重写的规则:
- 一般使用@Override注解来标识
- 必须有相同的参数列表和返回类型。
- 访问权限不能比父类中被重写的方法的访问权限更低,即不能降低访问权限。
- 声明为final或static的方法与构造方法不能被重写。
- 如果不能继承一个方法,则不能重写这个方法。
2.方法重载
定义:在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
要求:被重载的方法必须改变参数列表(参数个数或类型或顺序不一样),最常用的地方是构造器的重载。
函数重载的规则:
- 被重载的方法必须改变参数列表,且无法以返回值类型作为重载函数的区分标准。
- 被重载的方法可以改变返回类型、访问修饰符。
- 被重载的方法可以声明新的或更广的检查异常。
- 方法能够在同一个类中或者在一个子类中被重载。
四、面向对象的三大特征
1.封装
定义:把对象的属性和操作(或服务)结合为一个独立的整体,并尽可能隐藏对象的内部实现细节
实现方式:将类的变量声明为private,并提供public getter和setter方法来修改和查看变量的值
封装的优点:
- 良好的封装能够减少耦合
- 类内部的结构可以自由修改
- 可以对成员变量进行更精确的控制(可以实现校验输入等)
- 隐藏信息,实现细节
2.继承
class 子类名 extends 父类名/超类名/基类名 {
子类名(){
super(); //生成子类对象时,(若未调用父类构造函数,)会默认先调用父类的无参数构造函数
}
//其余代码
}继承方式:一个类从另一个类继承时,继承所有父类的非私有变量和方法(构造函数不会被子类继承)。
访问父类的方法:通过super关键字来访问父类或调用父类构造方法(private方法除外)
3.多态
多态是指同类型的对象,执行同一个行为具有多个不同的表现形式或形态的能力
优点:①消除类型之间的耦合关系 ②可替换性 ③可扩充性 ④接口性 ⑤灵活性 ⑥简化性
存在必要条件:①继承 ②重写 ③父类引用指向子类对象
多态中成员访问特点:
- 方法调用:编译看左边,运行看右边(调用的实际存储的对象方法)<行为多态>
- 变量调用:编译看左边,运行也看左边(调用的是生成对象类型存储的变量)<变量不多态>
Animal dog=new Dog();
dog.run(); //调用的是子类Dog的成员方法run
Syetem.out.println(dog.name); //调用的是父类的成员变量name4.抽象(第四个特征)
五、匿名类与内部类
1.匿名类
匿名类是一种即时继承现有类的方法,在创建对象时直接对其方法进行更改
声明方式:在创建对象时及时更改其方法并生成
类名 c = new 类名() {
@Override
访问修饰符 函数返回类型 方法名{
//函数主体
}
}; //此即为匿名类
//@Override注解用于使代码更容易理解,因为当方法被重写时,它更加明显。2.内部类
Java支持嵌套类,一个类可以是另一个类的成员。创建一个内部类很简单,只要在类中编写一个类。
作用:
- 实现更好的封装,普通类(非内部类)的访问修饰符不能为private或protected,而内部类可以。当我们将内部类声明为private时,只有外部类可以访问内部类,很好地隐藏了内部类。
- 内部类可以继承(extends)或实现(implements)其他的类或接口,而不受外部类的影响。
- 内部类可以直接访问外部类的成员和方法;相反的,外部类不能直接访问内部类的成员。
六、枚举类型
1.简介
枚举类是一种特殊类,它和普通类一样可以使用构造器、定义成员变量和方法,也能实现一个或多个接口,但枚举类不能继承其他类。枚举类型使用的最常用类型就是枚举常量。
2.定义枚举类型
修饰符 enum 类名 {
枚举常量1, 枚举常量2, ... //注意:枚举类第一行默认罗列枚举对象名称,这些值是用逗号分隔的
}
类名 变量名=类名.常量; //引用枚举常量3.常用情景
- switch语句
- 当变量(特别是方法参数)只能从一小组可能的值中取出一个时,总是应该使用枚举。
七、Object类与Objects类
1.Object类的作用
在Java中,一个类要么默认继承了Object类,要么间接继承了Object类,其有两个常用方法
- toString():默认是返回当前对象在堆内存中的地址信息:类的全限名@内存地址
- equals():默认是比较当前对象与另一个对象的地址是否相同,相同返回true,不同返回false
2.Objects类的作用
Objects类从JDK 1.7开始Objects类加入Java,提供了很多对象操作的辅助方法,其有两个常用方法
- equals():先在底层进行非空判断,再进行equals比较
- 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 子类名|父类名]>
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.定义抽象类
abstract class 抽象类名 {
访问修饰符 abstract void 抽象方法名();
//其余代码
}3.抽象类的特点
- 如果一个类声明为抽象类,则不能被实例化(不能创建该类型的对象)。
- 要使用抽象类,必须从另一个类继承它。
- 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
4.模板方法模式
public abstract class Student {
public final void print(){ //这是一个模板方法,定义通用结构
System.out.println("这是一个学生,下面输出学生的信息:");
System.out.println(information()); //使用抽象方法
}
public abstract String information(); //抽象方法交由子类实现
}三、接口
1.基本概念
意义:一个接口是一个完全抽象的类,只包含抽象方法。
实现方式:当类实现接口的时候,类要实现接口中所有的方法。接口通常以interface来声明。
2.定义接口
interface Animal {
String NAME="Animal"; //常量(public static final可省略不写)
void eating(); //由于规范思想,方法默认public(abstract public可省略不写)
} //接口声明class Dog implements Animal {
public void barking() {
System.out.println("Woof-Woof");
}
public void eating() {
System.out.println("crunch-crunch");
}
} //类接口的实现3.接口的规则
- JDK8之前,类中只能有抽象方法与常量(
public static final修饰) - 接口不能被实例化,但是可以被实现类创建。
- 一个类可以实现多个接口,且一个接口能继承另一个接口,这和类之间的继承比较相似。
- 若一个类继承了父类,同时又实现了接口,父类中和接口中有同名方法,默认用父类的。
4.接口的特性
- 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
- 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
- 接口中的方法默认都是公有的,声明时public关键字可以省略。
- 接口中的常量声明时
public static final可省略不写。
5.对接口的扩展
Java8:除了常量与抽象方法之外,还可以有两种非抽象的方法:static方法和default方法。
static方法:接口的静态方法必须用接口名来调用(实例不能调用),默认会使用static修饰
default方法:类似之前写的普通实例方法,只能由实现类对象调用,默认会使用public修饰
Java9:允许接口中存在private方法。
private方法:只能在本类中被其他的默认方法或者私有方法访问。
四、Lambda表达式(Java 8)
1.使用Lambda表达式
(参数列表) -> {
// Lambda表达式体
return //返回值
} //用于简化函数式接口的匿名内部类的写法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.进一步简化
- 参数类型可以省略不写。
- 如果只有一个参数,参数类型可以省略,同时()也可以省略。
- 如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写,同时要省略分号!
- 如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写。此时,如果这行代码是return语句,必须省略return不写,同时也必须省略";"不写
Arrays.sort(array, (o1, o2) -> o2 - o1);3.优缺点
优点:
- 代码简洁,开发迅速
- 方便函数式编程
- 非常容易进行并行计算
- 引入Lambda,改善了集合操作(引入Stream API)
缺点:
- 代码可读性变差
- 在非并行计算中,计算未必有传统的 for 性能要高
- 不容易进行调试
注:通过Lambda 表达式实现的接口不是普通的接口,而是函数式接口,这种接口只能有一个方法。为了防止在函数式接口中声明多个抽象方法,Java 8 提供了一个声明函数式接口注解:@FunctionalInterface,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。
第三部分 对象的比较与类型转换
1.强制类型转换
强制类型转换方式:(转换数据类型)待转变量名
2.向上转型和向下转型
对于类,有两种类型的转换,分别是向上转型(子类->父类)与向下转型(父类->子类)。
向上转型:父类名 childObj = new 子类名();
向上转型就是把子类对象直接赋给父类引用,不用强制转换,可以调用父类类型中的所有成员,不能调用子类类型中特有成员(可以调用重写的父类成员方法),最终运行效果看子类的具体实现。
向下转型:子类名 childObj = (子类名)fatherObj;
向下转型可以调用子类中所有的成员,当父类引用对象是父类本身,在向下转型的过程中是不安全的,编译不会出错,但是运行时会出现强制类型转换异常,一般使用 instanceof 运算符来避免出此类错误。
instanceof关键字
变量名 instanceof 类名:用于判断对象是否属于某个指定的类或其子类的实例,是则返回true