一.面向对象的介绍
1.面向对象
是一种优秀的程序设计方法。由面向对象分析(OOA),面向对象设计(OOD),面向对象编程(OOP)三部分组成。
它的基本思想是使用类,对象,继承,封装,消息等进行程序设计。它从现实世界中客观存在的事物出发来构造软件系统,在系统构造中尽可能的运用人类的自然思维方式,强调直接以现实世界中的事物为中心来思考问题,认识问题。并根据这些事物的本质特点,把他们抽象地表示为系统中的类,作为系统的基本构成单元,使得软件系统的组件可以直接映射现实世界,并保持客观世界事务及其相互关系的本来面貌。
2.面向对象的类:
面向对象程序设计中,最小的程序单元就是类。类可以生成系统中的多个对象。而这些对象是类的具体实现(实例)。并且,这类食物往往有一些内部的状态数据,类代表了具有某个特征的一类事物。比如:人,都有姓名,年龄,性别,身高,体重等各种状态数据。这些状态数据被称之为成员变量,有些称为字段,有些成为属性。
类用来封装一类事物的状态数据,方法用来对状态数据操作的实现。所以可以得出:
类定义=状态数据+方法
3.类与对象
类的定义语法:[访问权限修饰符] class 类名{
成员变量
成员方法
}
解析:
1):访问权限修饰词,在一个.java文件里定义普通类,修饰词知恩阁是public或者默认的
2):类名:大驼峰命名法
3):成员变量:用来描述对象的共同特征,就是状态数据
格式:就是变量的声明
4):成员方法:
描述对象的共同行为,就是用来操作状态数据,都不带static修饰
4.对象:
类是对象的抽象(模板),对象是类的具体(实例)
类定义完成后,可以使用new关键字创建对象,创建对象的过程通常称之为实例化
语法: new 类名();
5.引用变量:
如果想要访问实例化出来的对象,通常会用变量来接收一下对象。格式如下:
类名 变量名 = new 类名();
java中除了八大基本数据类型之外,都是引用类型,包括我们自定义的类。引用变量里存储的不是对象,而是对象在内存中的地址信息。引用通过地址信息指向对象,取代了面向过程的指针。
6.成员访问:
1):基本的成员访问
访问,就是指如何使用类里的成员变量和方法。
实现:引用变量.成员变量
引用变量.成员方法
2):静态和非静态
static修饰的属性叫静态属性,修饰的方法叫静态方法。
没有static修饰的属性叫非静态属性,也叫成员变量,没有static修饰的方法叫非静态方法,也叫成员方法。
static修饰的属性和方法,都是属于类的,需要使用类名调用。没有static修饰的属性和方法是属于对象的通常使用引用变量来调用。
注意:
静态方法中,只能直接访问本类中的静态成员。不能访问非静态成员
非静态方法中,可以访问本类中的非静态成员和静态成员
7.this关键字
在一个类的成员方法中,使用this代表当前的对象(因为实例方法需要使用对象调用,哪个对象调用这个方法,this就是谁)
8.null和NullPointerException
null:引用类型的默认值。表示引用变量里的地址被清空,即没有对象的地址信息
NullPointerException:空指针异常,运行时发生
变量里没有地址信息,确使用变量来访问对象的成员,就会发生异常。
二.构造方法
1.介绍:
构造方法也是方法,但是没有返回值类型,返回值类型这个位置不存在,构造方法的名字必须和类名保持一致,不能用static修饰。
构造方法在使用new关键字实例化对象时调用执行
构造方法可以重载,参数类型列表不同即可
2.定义:
无参构造器
访问权限修饰词 类名 (){
代码块
}
有参构造器
访问权限修饰词 类名 (参数类型 变量){
代码块
}
3.this()关键字
构造方法,一般用来给属性初始化的赋值操作,一个类里可以有多个构造方法,每一个构造方法,都可以给指定的属性进行初始化赋值,但是不同的构造器方法中,可能会存在重复的复制部分,这个时候可以在构造器里面调用本类的其他构造器。
例如:
Person(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
Person(String name, int age, char gender, int height, int weight) {
// 使用关键字 this() 来调用当前类中的构造方法。
this(name, age, gender);
this.height = height;
this.weight = weight;
}
注意事项:在调用构造方法的时候,this()语句必须放在第一行,前面不能加任何语句
不能出现循环调用
三.代码块
1.构造代码块(动态代码块):
构造器调用一次,构造代码块就会执行一次,利用每次创建对象的时候都会提前调用一次构造代码块特性,可以利用其做统计创建对象的次数,也可以为成员变量初始化。
语法:{
代码块
}
位置:与构造器并列,都在类体中
作用:一般用于统计创建了多少个对象,或者提前给成员变量赋值
2.静态代码块
java静态代码块中的代码会在类加载jvm时运行,且只运行一次,静态代码块不需要实例化类就会被调用,一般情况下这些代码在项目启动时就执行了,静态块常用来执行类属性的初始化
语法:
static{
代码片段
}
位置:与构造器并列,都是在类中
特点:只执行一次,在类加载器将该类的信息加载到内存时执行
作用:一般用于加载静态资源,比如图片,视频,音乐等
注意:如果一个类中有多个静态代码块,会按照书写顺序依次执行,可以定义在类的任何地方除了方法体中,静态代码块不能访问普通变量
四.JVM内存管理机制
Java语言本身是不能操作内存的,它的一切都是交给JVM来管理和控制的。Java代码被编译器编译成字节码之后,JVM开辟一片内存空间(也叫运行时数据区),通过类加载器加载到运行时数据区来存储程序执行期间需要用到的数据和相关信息,在这个数据区中,它由以下几部分组成: 虚拟机栈,堆,程序计数器,方法区,本地方法栈。
1.虚拟机栈
1)定义:虚拟机栈是Java方法执行的内存模型,栈中存放着栈帧,每个栈帧分别对应一个被调用的方法,方法的调用过程对应栈帧在虚拟机中入栈到出栈的过程。
2)
-> 一个线程对应一个栈,main方法就是一个线程。 栈空间是私有的,不能被其他线程访问
->栈的数据结构的特点: first input last output : filo
->每个方法在调用时,在栈里都会被分配一个栈帧空间. 栈帧用于存储该方法的所有局部变量。 方法执行完毕,栈帧消失。
2.堆
存储对象本身和数组的,在JVM中只有一个堆,因此,堆是被所有线程共享的(从jdk1.7开始,字符串的常量池在堆中)
3.方法区
java的垃圾回收机制,在程序运行时,就已经跟着启动了。 会主动去处理堆里的没有被任何引用指向的对象。这样的对象都会被认为是垃圾。 并不是程序员要调用System.gc()该功能就会立马处理。如果这样的垃圾没有被处理,会出现堆内存溢出。但是,一般情况下gc还是会尽快处理的。不会出现内存溢出。如果我们在编程时,对象用了几次后,不再使用了,那么应该尽快将引用变量赋值为null。即将对象视为垃圾,等待gc处理。
4.值传递和地址传递
值传递:基本数据类型之间的赋值操作,本质是值的副本传递。改变形参的值,不会改变实际参数的值。
地址传递:引用数据类型之间的变量的赋值操作,本质是地址的副本传递。通过形参改变对象的数据,那么实际参数指向的对象被改变了。除非形参在改变对象前,指向了新对象。
五.析构方法
1.对象的生命周期中的最后一个方法,执行实际是当这个对象被销毁之前。执行了这个方法之后,空间就会被销毁,这个对象也就不存在了。
2.该方法负责回收java对象所占的内存。通常我们在该方法中,指定对象销毁时要执行的操作,比如关闭对象打开的文件,IO流,释放内存资源等清理垃圾碎片的工作
3.特点:
垃圾回收器是否会执行finalize方法,以及何时执行该方法,是不确定的;
finalize()方法有可能会使对象复活,恢复到可触及的状态;
垃圾回收器执行finalize()方法时,如果出现异常,垃圾回收器不会报告异常,程序会继续正常运行。
4.注意:因为finalize()方法具有不确定性,也就是说,该方法到底会不会起作用是不一定的!所以我们在程序中可以调用 System.gc()方法或者 Runtime.gc()方法,来显式地提醒垃圾回收器尽快去执行垃圾回收操作。
六.面向对象的三大特征
三大特征:封装,继承,多态
封装
1.简介
在Java中,封装是面向对象编程的一个基本原则,它意味着将对象的状态(数据)和行为(方法)打包在一起,并隐藏对象的内部实现细节。这样可以提高代码的模块化程度,使得代码更易于维护和修改。
2.属性封装
一个类中的某一些属性,不希望直接暴露给外界,让外界直接操作。因为如果让外界直接操作的话,对 这个属性进行的值的设置,可能不是我们想要的(可能不符合逻辑)。此时就需要将这个属性封装起来,不让外界直接访问。
封装的步骤通常包括:
1)将类的属性设置为私有。
2)提供公共的getter和setter方法来访问或修改这些属性。
例如:
public class Person {
String name;
private int age; // 1、将属性私有化起来,不让外界直接访问
// 2、给要访问的属性,设置对应的 setter/getter 方法
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
}
继承
1.简介
从已有的类中派生出新的类,新的类能吸收已有的类的属性和方法,并扩展新的能力。
已有的类,叫父类,又叫基类,超类。派生出来的新类叫子类,也叫派生类
使用关键字extends来表示子类继承了父类。
语法:
修饰词 class 子类名 extends 父类名{
//子类的类体
}
例如:
class A{} //父类
class B extends A{} //B是A的子类
class C extends B{} //C是B的子类
2.特点
1):Java只支持单继承,即一个类只能有一个父类;但是一个类可以有多个子类。
2):java支持多重继承,即一个类在继承自一个父类的同时,还可以被其他类继承。 可见继承具有传递性
3):子类继承了父类的所有成员变量和方法,包括私有的(但是没有访问权限),也包括静态成员
4):子类不能继承父类的构造器,只能调用父类的构造器,并一定至少有一个子类构造器调用了父类的构造器
5):子类在拥有父类的成员基础上,还可以添加新成员。
3.继承中的构造器
一个对象在实例化时,需要在堆上开辟空间,空间分为两部分,一部分是从父类继承到的属性,一部分是子类特有的属性。在实例化时,父类的那部分需要调用父类的构造器。注意,默认调用的是父类的无参构造器,如果父类中没有构造器就要显示调用父类。或者在父类中提供一个无参构造器。
在子类的构造器中,使用super(有参传参)调用父类的构造方法,必须放在构造器的第一行。所以super()和this()不能同时出现在构造器里。
4.继承中的重写方法
如果子类继承过来的方法满足不了子类,那么就对从父类继承过来的方法进行重写,重写会覆盖掉父类中的实现方法。
特点:子类只能重写父类中存在的方法
重写时,子类中的方法名和返回值类型相同,或者是其子类型,访问权限子类必须大于或者等于父类方法的访问权限
注解:@Override,可以检验一个方法是不是重写的方法。但并不是加了该注解就是重写,没有加就不是重写。
5.Object类型
Object类型是java中的根类。所有的类都直接或间接继承自Object。因此在Object类中定义的属性、方法,在所有的类中都有包含。 比如常用的方法 hashCode(),equals(),toString(),getClass(),wait(),notify(),notifyAll()等。
toString()方法:作用,将对象的信息变成字符串,一般都要重写该方法。该方法在使用输出预计打印对象的变量时,会自动调用
equals()方法:
源码如下:
public boolean equals(Object obj){
return this==obj; // == 比较的是地址值。
}
上述的源码的意义所在:比较this和传进来的obj 是不是同一个对象,如果是,则返回true,不是的话,返回false。
而我们大多时候的需求,并不是比较是不是同一个对象,而是比较两个对象的属性值是否相同。 因此:自定义类型时,应该重写eqauls方法。
重写要遵循一些原则:
->如果 obj = null,一定要返回false。
->如果 obj = this,一定要返回true。
->如果两个对象的类型不同,一定要返回false。
->如果 a.equals(b) 成立,则 b.equals(a) 也必须成立。
->如果 a.equals(b), b.equals(c) 成立,则 a.equals(c) 也必须成立。
hashCode()方法:该方法返回的是一个int类型的值。 表示对象在内存堆中的一个算法值。 在自定义类型时,一般都需要重写该方法
getClass()方法:获取一个用来描述指定类型的Class类对象。获取一个指定的对象的类型。 这个方法,不能被重写。
6.final修饰词
可以用来修饰类,属性,方法,局部变量。
1):被修饰的类不能被继承,不能有子类
2):被修饰的属性只能被赋值一次,不能被第二次赋值
3):被修饰的方法不能被重写
4):被修饰的局部变量,只能被赋值一次
7.static修饰词
static修饰的内容,都是公有资源,不属于对象,属于类,因此,都是类名调用
可以用来修饰方法,代码块,类
1):static和final可以一起修饰属性,该属性为一个常量。
2):static修饰的方法应该用类名调用,而且不能被重写
3):被static修饰的代码块,在类加载期间只能执行一次。
4):static修饰的类必须为内部类
多态
1.简介
多种形态。在java中就是指一个对象具有多种形态的特点,就是一个对象可以向上造型,也可以向下造型。有向上转型和向下转型。
2.向上转型
父类型的变量引用子类型的对象。
向上转型肯定会成功,是一个隐式转换。
向上转型后的对象,将只能够访问父类中的成员(编译期间,看变量类型)
如果调用的是重写过的方法,那么调用的一定是重写方法(运行期间,看对象)
应用场景:在定义方法时,形式参数是父类型的变量。这样更加灵活,可以传任意子类型的对象
语法:父类型 变量 = new 子类型();
例如:Animal a = new Cat()
a.noise();//此时运行的是子类的noise()方法
3.向下转型
父类型变量赋值给子类型的变量,需要强制转换,是一个显式转换。
可能会失败,失败的话,会报类造型异常ClassCastException
为了避免ClassCastException ,可以使用instanceof 来判断:变量指向的对象是否属于某一个类型
为什么向下转型:前提,父类型的变量引用子类型的对象,但是父类型的变量不能调用子类里的独有的功能,不能满足需求
语法:子类型 变量名 = (子类型)父类型变量
例如:
// 1、实例化一个Dog对象,并且向上转型
Animal animal = new Dog();
// 2、判断类型,判断animal指向的对象是不是一个Cat类型
if (animal instanceof Cat) {
System.out.println("animal 的确是一个Cat对象,可以进行向下转型 "); }
else {
System.out.println("animal不是一个 Cat对象,不能进行向下转型 "); }