面向对象
一、Java的三大特性
封装,继承,多态
面向对象编程的本质:以类的方式组织代码,以对象的方式组织(封装)数据
二、类与对象的关系
- 类是一种抽象的数据类型,是对一类事物的整体描述/定义,类是对象的模板
- 对象的是抽象概念的具体实例
从认识论的角度考虑是先有对象后有类,从代码运行角度考虑是先有类后有对象
一个类中只有属性和方法
三、类的创建与初始化
1、创建对象
使用new
关键字来创建对象,除了分配内存空间,还会给创建好的对象进行默认的初始化,以及对类中构造器的调用。
//类实例化后会返回一个自己的对象
Student student = new Student();
2、初始化对象(Constructor : コンストラクタ)
类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下两个特点
- 必须和类的名字相同
- 必须没有返回类型,也不能写void
构造器分为有参构造和无参构造,类中什么都不写的话默认有一个无参构造器,写了有参之后会覆盖默认的无参构造器,若需要使用无参,写出无参构造即可。
public class Person{
//使用new关键字,本质是在调用构造器
//无参构造
public Person(){
}
}
public class Student{
int age;
//有参构造
public Student(int num){
this.age = num;//初始化对象的值
}
//无参构造
public Student(){
//初始化对象的值
}
}
四、创建对象内存分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lPW1j1JN-1645893776977)(E:\开发学习记录\附件\内存分析.png)]
实际内存很复杂,这里只分析创建对象时的内存。分为两个空间,堆
和栈
。
堆
中有一块独特的区域叫方法区,类这个模板就存放在方法区中,除了类还有常量池,静态方法区等也都存放在方法区中。静态方法区和类是同级别的存在,和类一起加载。
new Person()
的时候会在堆中开辟一块空间,这就是对象。
Person p = new Person();
需要注意的是,p
不是对象,new Person()
才是对象,p
作为引用变量名,存放在栈中,保存的是指向对象的内存地址。
五、封装
程序设计追求高内聚,低耦合,高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合是仅暴露少量的方法给外部使用。
封装(数据的隐藏),通常应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问。
使用private
将类中的属性隐藏起来,让外部在调用这个类的时候无法直接访问类的属性。
属性私有,get/set
通过getter
和setter
来访问和修改属性。
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
六、继承
继承的本质是对某一批类的抽象
1、子类继承
使用extends
来继承父类,Java中只有单继承,没有多继承。所有的类都直接或间接继承Object
类。
子类继承了父类,就会拥有父类的全部方法。私有的东西无法被继承
父类和子类之间,从意义上来讲应该具有is a
的关系
public class Student extends Person{
//
public Student{
super();//隐藏代码,调用了父类的构造器,必须要在子类构造器的第一行
}
}
super
注意点super
调用父类的构造方法,必须在构造方法的第一个super
必须只能出现在子类的方法或者构造方法中super
和this
不能同时调用构造方法
- 与
this
的不同- 代表的对象不同
- this:本身调用者这个对象
- super:父类对象的应用
- 前提
- this:没有继承也可以使用
- super:只能在继承条件才可以使用
- 构造方法
- this:本类的构造
- super:父类的构造
- 代表的对象不同
2、方法重写
继承了父类的子类可以重写父类里面的方法。
在父类里面定义的方法无法满足子类的需求时,通过继承获得父类属性以及其他方法的同时,可以对不满足需求的方法进行重写,来实现更精细的操作。
// 假设有A、B两个类,其中A extends B
public class test{
A a = new A();// 普通操作,类名 对象名 = 在内存中开辟一块空间
// 父类的引用指向了子类,因为有父子关系所以ok
B b = new A();// 多态操作,父类类名 对象名 = 在内存中开辟一块空间
}
方法的调用只和左边有关系(定义的数据类型),上面的b
只可以调用B类中的方法,但是如果A类重写了B类中的方法,则方法体的执行按重写后的A类的方法执行。b无法调用A类中独有的方法
3、重写条件:需要有继承关系,子类重写父类的方法
- 方法名必须相同
- 参数列表必须相同
- 修饰符:范围可以扩大,不可以缩小
public
>Protected
>Default
>private
- 抛出的异常:范围可以缩小,不可以扩大
Exception
>ClassNotFoundException
- 子类的方法必须和父类一致:方法体不同
无法被重写的方法
static
方法,属于类,不属于对象final
常量private
方法
七、多态
同一方法可以根据发送对象的不同而采用多种不同的行为方式
一个对象的实际类型是确定的,但可以指向对象的引用类型有很多
- 多态存在的条件
- 有继承关系
- 子类重写父类的方法
- 父类引用指向子类对象
多态是方法的多态,属性没有多态性
//一个对象的实际类型是确定的
new Student();
new Person();
//可以指向的引用类型就不确定了:父类的引用指向子类
Student s1 = new Student();
Person s2 = new Student();
Object s3 = new Student();
类型转换
子类转换为父类,向上转型,低转高==自动转换
Student student = new Student();
Person person = student;
父类转换为子类,向下转型,高转低==强制转换
Person obj = new Student();
Student student = (Student)obj;
//or
((Student) obj).method();
子类转换为父类,可能会丢失自己的本来的一些方法
八、抽象类
abstract
修饰符可以用来修饰方法也可以修饰类,修饰方法就是抽象方法,修饰类就是抽象类
public abstract class Action{
//约束,其他地方实现
//抽象方法,只有方法名字,没有方法实现
public abstract void doSomething();
}
抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类
- 抽象类不能使用
new
关键字来创建对象,是用来让子类继承的 - 抽象方法只有方法的声明,没有方法体
- 子类继承抽象类,就必须实现抽象类没有实现的抽象方法(重写),否则也必须声明为抽象类
- 抽象类中可以写普通方法
- 提高开发效率,节省代码
由于继承只能单继承,与接口相比用的相对较少
九、接口
普通类:只有具体实现
抽象类:既有具体实现,也有规范(抽象方法)
接口:只有规范,约束和实现分离
声明类的关键字是class
,声明接口的关键字是interface
//interface 关键字,接口都需要有实现类
public interface UserService{
//接口中的所有方法都是抽象的 public abstract
void add(String name);
}
public interface TimeService{
//接口中定义的属性都是常量 public static final
int AGE = 26;
}
//接口可以多继承
public class UserServiceImpl implements UserService,TimeService{
@Override
public void add(String name){
//方法体
}
}
- 类通过
implements
实现接口 - 实现了接口的类,就需要重写接口中的所有方法
- 接口可以实现多继承
- 接口不能被实例化,没有构造方法
十、异常
异常处理框架
有三种类型的异常
- 检查性异常:用户错误或问题引起的异常,程序员难以预见
- 例如要打开一个不存在的文件,这些异常在编译时不能被简单的忽略
- 运行时异常:可能被程序员避免的异常,在编译时会忽略
- 例如
11/0
的计算,编译时忽略,运行时抛出ArithmeticException
- 例如
- 错误Error:错误不是异常,而是脱离程序员控制的问题,错误在代码中通常被忽略
- 例如当栈溢出时,一个错误就发生了,在编译时也检查不到
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f5yWt7in-1645893776978)(E:\开发学习记录\附件\异常体系结构.png)]
捕获和抛出异常
Java实际运行中发生的所有异常都是可以被捕捉到的
异常处理五个关键字try
,catch
,finally
,throw
,throws
如果不使用try
,catch
,程序报错后会停止运行,使用后会继续向下执行
try{
//try监控区域,在此区域内写的代码如果有异常即可通过catch捕获
}catch (ArithmeticException e){//参数为想要捕获的异常类型
//捕获异常,需要知道捕获的异常的类型
//捕获到相应异常之后执行catch内的代码
}catch (Throwable e){
//可以捕获多个异常,范围大的写在后面
}finally{
//无论是否发生异常,都会执行这里面的代码
//通常用于关闭IO流或资源,处理善后工作
//可以不写finally
}
如果预见到有可能会报错,可以主动抛出异常
if(a == 0){
throw new ArithmeticException();//主动抛出异常,一般在方法中使用
}
假设在方法中处理不了这个异常,可以在方法上抛出异常
public void run() throws Exception{
//方法体
}