面向对象简介
面向对象和面向过程
- 面向过程:面向过程性能比面向对象高。类调用时需要实例化,开销较大,耗资源。当性能作为最主要的考量因素的时候。如单片机、嵌入式开发、Unix/Linux一般用面向过程开发
- 面向对象:面向对象易维护、易复用、易拓展。面向对象有封装、继承、多态等特点。可设计出低耦合的系统,更加灵活、易维护。但是面向对象性能比面向过程低
OOP特性
封装
封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式
public class ClassName {
// 成员变量:private 隐藏对象的属性和实现细节
private String name;
// 成员方法:public 对外提供公共访问方式
public void eat() {
System.out.println("eat...");
}
}
成员变量
类中的位置 | 作用域 | 初始化值 | 存储位置 | 生命周期 | |
---|---|---|---|---|---|
成员变量 | 类中,方法外 | 类中 | 有默认值 | 堆内存 | 随对象创建而存在,随对象的消失而消失 |
局部变量 | 方法中、形式参数 | 方法中 | 没有默认初始值,使用前必须赋值 | 栈内存 | 随方法调用而存在,随方法完毕而消失 |
成员方法
特点:方法定义在类中,不能在方法中嵌套方法
参数传递
基本类型:传值,形参的改变对实参无影响
引用类型:传址,形参的改变对实参有影响
什么是值传递和引用传递?
- 值传递:基本变量,传递的是该变量的副本,改变副本不影响原变量。一般认为Java内传递都是值传递
- 引用传递:对象变量,传递的是该对象地址的副本, 并不是原对象本身。对引用操作会同时改变原对象
对象被当作参数传递到方法,此方法可改变对象属性,并返回变化后的结果,这里到底是值传递还是引用传递?
值传递。Java语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的
对象的相等与指向ta们的引⽤相等吗?两者有什么不同?
- 对象相等,比的是内存中存放的内容是否相等
- 引用相等,比的是ta们指向的内存地址是否相等
创建⼀个对象用什么运算符?对象实体与对象引用有何不同?
new运算符,new创建对象实例(存放在在堆内存中),对象引用指向对象实例(存放在栈内存中)
⼀个对象引用可以指向0个或1个对象(⽓球要么系,要么不系)
⼀个对象实体可以有n个引用指向它(张三有很多外号)
可变参数:方法的参数列表类型已定,参数数量不确定
- 格式:修饰符 返回值类型 方法名(数据类型...变量名) {}
- 原理:底层是数组,根据存储参数个数不同,创建不同长度的数组
- 特点:
- 一个方法的参数列表只能有一个可变参数
- 方法参数有多个,可变参数需写在最尾端
- 演示
public class VarArgsDemo { public static void main(String[] args) { System.out.println( add(10, 20, 30, 40, 50) ); } private static int add(int...arr) { int sum = 0; for (int e:arr){ sum += e; } return sum; } }
方法重载
方法重载:在同一个类中,允许存在一个以上的同名函数,只要它们的参数个数或者参数类型不同即可
- 影响:参数列表(参数个数、参数类型、参数顺序)
- 不影响:参数名称(a-->x,b-->y)、方法返回值类型(int--double)、修饰符
构造方法
-
作用:用于创建对象,并对其进行初始化赋值,对象一建立就自动调用相对应的构造函数
-
特点:
- 方法名与类名相同,构造方法是可以重载的,既可以定义参数,也可以不定义参数
- 没有返回类型,不能⽤ void 声明构造函数,没有返回值
- 构造方法在初始化(new)对象时自动执行,无需调用,一般不能显式地直接调用
- 如果不提供构造方法,系统会给出无参数构造方法,数值类型初始化为0,布尔类型,初始化为false(类中必定有构造方法);如果提供了构造方法,系统将不再提供无参数构造方法
- 一般情况下,我们的自定义类都要手动给出无参构造方法
- 不能被 static、final、synchronized、abstract 和 native 修饰
- 不能被实例化的,如接口,就没有构造方法
在Java中定义一个不做事且没有参数的构造方法的作用?
Java程序在执⾏⼦类的构造⽅法之前,如果没有⽤super()来调⽤⽗类特定的构造⽅法,则会调⽤⽗类中“没有参数的构造⽅法”因此,如果⽗类中只定义了有参数的构造⽅法,⽽在⼦类的构造⽅法中⼜没有⽤super()来调⽤⽗类中特定的构造⽅法,则编译时将发⽣错误,因为Java程序在⽗类中找不到没有参数的构造⽅法可供执⾏。解决办法是在⽗类⾥加上⼀个不做事且没有参数的构造⽅法
格式 | 作用 | 调用 | |
---|---|---|---|
成员方法 | 可以任意起名,必须有返回类型,可以没有返回值 | 用于完成特定功能 | 普通成员方法是由创建好的对象调用,可以调用多次 |
构造方法 | 构造方法和类名相同,并且没有返回类型,也没有返回值 | 用于创建对象,并进行初始化值 | 在创建对象时被调用的,一个对象建立,只调用一次相应构造函数 |
继承
继承:子类继承父类的属性和行为,使得子类对象具有与父类相同的属性和行为(父类更通用,子类更具体)
作用:实现代码的复用
特点:
- 抽象类和抽象方法都要用 abstract 进行修饰
- 子类可以直接访问父类中的非私有的属性和行为
- 抽象类不能被实例化,不能直接 new 抽象类对象,必须要子类继承且重写覆盖抽象方法
- 抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类
- 继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类
格式:
- 抽象类:abstract class 类名 {}
- 抽象方法:修饰符 abstract 返回值类型 方法名 (参数列表)
数据特点:
- 成员变量:抽象类中可以有变量,也可以有常量
- 成员方法:抽象类中可以有抽象方法,也可以有非抽象方法
- 构造方法:抽象类是类,有构造方法。虽然本身不能实例化。但是可以给子类实例化使用
抽象类中是否有构造方法?能不能被实例化?如果不能,为什么有构造方法?
抽象类有构造方法,抽象类不能被实例化,抽象类中的构造方法供子类实例化调用
抽象关键字abstract不可以和哪些关键字共存?
- private:私有内容子类继承不到,所以不能重写。但是abstract修饰的方法要求被重写。两者冲突
- final:final修饰的方法不能被重写。而abstract修饰的方法,要求被重写。两者冲突
- static:假如一个抽象方法能通过static修饰,那么这个方法,就可以直接通过类名调用。而抽象方法是没有方法体的,这样的调用无意义。所以,不能用static修饰
抽象类中可不可以没有抽象方法?如果可以,这样的类有什么用吗?
抽象类可以没有抽象方法。抽象类中没有抽象方法的作用,只是为了不让别的类建立该抽象类对象
抽象方法是否可同时是静态的(static), 是否可同时是本地方法 (native),是否可同时被synchronized?
都不能
抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的
本地方法是由本地代码(如 C 代码)实现的方法,而抽象方法是没有实现的,也是矛盾的
synchronized和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的
this:所在类的当前对象的引用(地址值),即对象自己的引用,谁调用this所在的方法,this就代表谁
- 特点:
- 用于任何实例方法内,指向当前对象
- this 的值指向对其调用当前方法的对象
- this 关键字可在需要当前类类型的对象引用时使用,this不能用在static方法中
- this.属性
- this.方法
- this():调用构造方法,只能放在构造方法第一行
- 使用场景:
- 用于区分同名的成员变量和局部变量
// 形参变量与成员变量重名,导致成员变量名被隐藏,方法中的变量名,无法访问到成员变量,从而赋值失败 public class Student { private String name; private int age; public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } }
- 用于区分同名的方法参数与成员变量
- 用于调用该类的另一个构造方法
- 在定义函数时,该函数内部要用到调用该函数的对象时,因为此时对象还没建立,故this代表此对象构造函数间调用,这个时候,this(参数) 必须作为第一条语句存在
- 用于区分同名的成员变量和局部变量
super:在子类的成员方法中访问父类的成员变量、方法及构造方法
- 特点:
- 在构造器使用 super() 调用父类中的其他构造方法时,该语句必须处于构造器首行,否则编译器报错
- super不能用在static方法中
- 适用场景:
- 子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量
局部变量、本类的成员变量、父类的成员变量重名
public class ParentClass {
int num = 22;
}
public class ChildClass extends ParentClass{
int num = 10;
public void methodZi(){
int num = 199;
System.out.println(num); // 局部变量 199
System.out.println(this.num); // 本类成员变量 10
System.out.println(super.num); // 父类成员变量 22
}
}
public class Test {
public static void main(String[] args) {
ChildClass child = new ChildClass();
child.methodZi();
}
初始化顺序
存在继承的情况下,初始化顺序为:
- 父类(静态变量、静态语句块)
- 子类(静态变量、静态语句块)
- 父类(实例变量、普通语句块)
- 父类(构造函数)
- 子类(实例变量、普通语句块)
- 子类(构造函数)
多态
多态介绍
多态: 一个对象在程序不同运行时刻代表的多种状态,父类或者接口的引用指向子类对象
作用:消除类型之间的耦合关系
优点:多态的存在提高了程序的扩展性和后期可维护性
缺点:虽可预先使用,但只能访问父类已有功能,运行的是子类的功能内容,不能预先使用子类定义的特有功能
对象的多态性:相同的事物,调用其相同的方法,参数也相同时,但表现的行为却不同(Person + Teacher)
实现多态:动态绑定(dynamic binding),在执行期间判断引用对象的类型,根据实际类型调用其相应的方法
多态分类:
- 编译器的多态:静态多态,方法重载(Overload),方法名一样,参数列表不同
- 运行期的多态:动态多态,方法重写(Override),方法名一样,参数列表一样
实现多态:
- 方法重写(子类继承父类并重写父类中已有的或抽象的方法);
- 对象造型(父类型引用子类型对象,同样的引用调用同样的方法会根据子类对象不同而表现出不同行为)
父类名 对象名 = new 子类名(); 接口名 对象名 = new 实现类();
多态前提:
- 存在着继承或者实现关系
- 有方法的重写
- 父类(接口)引用指向子类(实现)对象
Java中实现多态的机制:
父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定吗,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法
接口
定义:
特点:
- 多个类的公共规范(USB),引用数据类型,最重要的是抽象方法
- 接口不能直接new,需要一个类来实现,实现类必须覆盖重写接口全部抽象方法
- 如果说类的内部封装了成员变量、构造方法和成员方法,那么接口的内部主要就是封装了方法
- 接口也会被编译成 .class 文件,但一定要明确它并不是类,而是另外一种引用数据类型
格式:
public interface 接口名称 {
// 1.变量:[pubic] [static] [final] 数据类型 常量名称 = 数据值;
// 常量必须进行赋值,而且一旦赋值不能改变
public static final int NUM = 10;
// 2.抽象方法(JDK 7及以前):[public] [abstract] 返回值类型 方法名称(参数列表);
// 实现类必须覆盖重写接口所有抽象方法,除非实现类是抽象类
public abstract void method();
// 3.默认方法(JDK 8):[public] default 返回值类型 方法名称(参数列表){ //方法体 }
public default void methodDefult(){
System.out.println("这是默认方法");
}
// 4.静态方法(JDK 8):[public] static 返回值 方法名(参数列表){ //方法体 }
// 应该通过接口名称来调用,不能通过实现类的对象调用接口静态方法
public static void methodStatic(){
System.out.println("这是静态方法");
}
// 5.私有方法(JDK 9):private [static] 返回值类型 方法名(参数列表) { //方法体 }
// private 的方法只有接口自己才能调用,不能被实现类或别人使用
}
数据特点:
- 成员变量:是常量,默认修饰 public static final
- 成员方法:都是抽象的,默认修饰 public abstract
关系:
- 类与类:继承关系。类与类只能单继承,可以多重继承
- 类和接口:实现关系。类可以多实现接口。类在继承一个类的同时,可以实现多个接口
- 接口和接口:继承关系。接口可以多继承接口
接口和抽象类的异同:
- 同:
- 不能够实例化
- 可以作为引用类型
- 一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类
- 异:
- 抽象类只能被单继承(一个类只能继承一个抽象类)
接口可以多实现,避免了多继承的局限性(一个类可以实现多个接口)- 数据特点:
- 抽象类
成员变量:可以是变量,也可以是常量,修饰符可以是 private、默认、protected、public
成员方法:可以有抽象方法和具体方法
构造方法:有构造方法- 接口
成员变量:常量。默认修饰 public static final
成员方法:都是抽象方法,不能有静态方法。默认修饰 public abstract
构造方法:没有构造方法- 抽象类中定义的是继承体系中的共性功能
接口中定义的是继承体系中的扩展功能- 抽象类被继承是"is a"关系:xx是yy的一种
接口被实现是"like a"关系:xx像yy的一种- 从设计层面来说,抽象是对类的抽象,⼀种模板设计,接口是对行为的抽象,⼀种行为的规范
转型
定义:当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误,不能调用子类方法,而父类想要调用子类特有的方法,必须做向下转型
分类:
- 向上转型:多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。当父类引用指向一个子类对象时,便是向上转型
// 父类类型 变量名 = new 子类类型(); Animal a = new Cat();
- 向下转型:父类类型向子类类型向下转换,这个过程是强制的。已经向上转型的子类对象,将父类引用转为子类引用,可使用强制类型转换格式
// 子类类型 变量名 = (子类类型) 父类变量名; Cat c =(Animal) a;
特点:
- 子类以父类的身份出现需要向上转型(upcast),做事情时还是以自己的方法实现,其中向上转型是由JVM自动实现的,是安全的;向下转型(downcast)是不安全的,需要强制转换。子类以父类的身份出现时自己特有的属性和方法将不能使用
演示:
// 测试
public class AnimalTest {
public static void main(String[] args) {
//向上转型
Animal cat1 = new Cat();
cat1.eat();
Animal dog1 = new Dog();
dog1.eat();
//向下转型
Cat cat2 = (Cat) cat1;
cat2.catchMouse();
Dog dog2 = (Dog) dog1;
dog2.watchHouse();
}
}
// 动物
abstract class Animal {
abstract void eat();
}
// 猫
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("eat fish...");
}
public void catchMouse() {
System.out.println("catch mouse...");
}
}
// 狗
public class Dog extends Animal {
@Override
public void eat(){
System.out.println("eat bone");
}
public void watchHouse() {
System.out.println("watch house...");
}
}
转型的过程中,一不小心就会遇到这样的问题:运行时,却报出了ClassCastException
类型转换异常
原因:明明创建 Cat 对象,当然不能转换成 Dog 对象,两个类型并没有任何继承关系,不符合类型转换的定义
public class AnimalTest {
public static void main(String[] args) {
//向上转型
Animal cat = new Cat();
//向下转型
Dog dog = (Dog) cat;
dog.watchHouse();
}
}
Exception in thread "main" java.lang.ClassCastException: com.hefery.Cat cannot be cast to com.hefery.Dog
为了避免 ClassCastException 的发生,Java 提供了instanceof
关键字,给引用变量做类型的校验
public class AnimalTest {
public static void main(String[] args) {
// 向上转型
Animal animal = new Cat();
// 向下转型
if (animal instanceof Cat) {
Cat cat2 = (Cat) animal;
cat2.catchMouse();
} else if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.watchHouse();
}
}
}