一、面向过程和面向对象
1.1 简介
面向对象(Object Oriented)是一种更加优秀的程序设计方法。它由面向对象分析(OOA),面向对象设计(OOD),面向对象编程(OOP)三部分组成。
它的基本思想是使用类,对象,继承,封装,消息等进行程序设计。它从现实世界中客观存在的事物出发来构造软件系统,在系统构造中尽可能的运用人类的自然思维方式,强调直接以现实世界中的事物为中心来思考问题,认识问题。并根据这些事物的本质特点,把他们抽象地表示为系统中的类,作为系统的基本构成单元,使得软件系统的组件可以直接映射现实世界,并保持客观世界事务及其相互关系的本来面貌。
面向对象(Object Oriented 简称 OO):面向对象会把事物抽象成对象的概念,先抽象出对象,然后给对象赋一些属性和方法,然后让每个对象去执行自己的方法。
面向过程(Procedure Oriented 简称 PO):把事情拆分成几个步骤(相当于拆分成一个个的方法和数据),然后按照一定的顺序执行。
1.2 面向对象的类
面向对象程序设计中,其最小的程序单元是类,类可以生成系统中的多个对象。而这些对象直接映射成客观世界的各种事物(各个实例)。
1.3 优缺点对比
面向过程:
优点:效率高,因为不需要实例化对象。
缺点:耦合度高,扩展性差,不易维护(例如:每个步骤都要有,不然就不行)
面向对象:
优点:耦合低(易复用),扩展性强,易维护,由于面向对象有封装、继承、多态性的特点,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
缺点:效率比面向过程低。
1.4 案例比较
面向过程:
1、打开冰箱门 我的行为
2、把大象装进去 我的行为
3、关上冰箱门 我的行为
面向对象:
1、打开冰箱门 冰箱的行为
2、把大象装进去 大象的行为
3、关上冰箱门 冰箱的行为
总结:
面向过程: 关心的是每一个步骤,有前因,有后果。
面向对象: 秉着一切皆是对象的思想,关心的是这是哪个对象的行为,哪个对象的数据。
二、类与对象
2.1 类的设计 Person
类的定义语法:
[访问权限修饰符] class 类名{
成员变量
成员方法
}
1.访问权限修饰词:
- 在一个.java源文件里定义的普通类,修饰词只能是public或者默认的。
2.类名:
- 大驼峰命名。
3.成员变量:
- 用来描述对象的共同特征,也就是状态数据
- 格式,即变量的声明
4. 成员方法:
- 用来描述对象的共同的行为, 或者用来操作状态数据
- 指的都是不带static修饰的方法
public class Person {
//成员变量: 这类事物的共同特征。即状态数据,一堆不同类型的变量。也可以叫属性/全局变量
String name;
int age;
char gender;
//成员方法: 这类事物的共同行为,在这里可以直接操作成员变量。
public void eat(String food){
System.out.println("吃的食物是:"+food);
}
public void play(String game){
System.out.println(name+"喜欢玩"+game);
}
public void work(){
System.out.println(name+"喜欢工作");
}
2.2 对象
类定义完成后,可以使用new关键字创建对象。 创建对象的过程通常称之为实例化。
new 类名();
2.3 引用变量
如果想要访问实例化出来的对象,通常都会使用变量来接收一下该对象。
即如下格式:
类名 变量名 = new 类名();
java中除了8种基本数据类型之外,都是引用类型,包括我们自定义的类;而引用类型声明的变量名,有一个专业称呼,即引用变量,简称引用。
为什么叫引用变量呢?
因为引用变量里存储的不是对象,而是对象在内存中的地址信息,也就是引用通过地址信息指向对象。 取代了面向过程中的指针。
2.4 成员访问
成员访问,指的就是如何使用类里的成员变量(属性),方法。
(1)基本的成员访问语法
引用变量.成员变量/引用变量.成员方法
(2)静态和非静态
1. static修饰的属性,叫做静态属性。
2. static修饰的方法,叫做静态方法。
3. 没有static修饰的属性,叫非静态属性,也叫成员变量。
4. 没有static修饰的方法,叫非静态方法,也叫成员方法。
static修饰的属性和方法,都是属于类的,需要使用类名调用。非static修饰的属性和方法,是属于对象的,通常使用引用变量来调用。
注意:1.静态方法中,只能直接访问本类中的静态成员。不能访问非静态成员。
2.非静态方法中,可以访问本类中的非静态成员和静态成员。
public class Person {
//成员变量: 这类事物的共同特征。即状态数据,一堆不同类型的变量。也可以叫属性/全局变量。
String name;
int age;
char gender;
//成员方法: 这类事物的共同行为,在这里可以直接操作成员变量。
public void eat(String food){
System.out.println("吃的食物是:"+food);
}
public void play(String game){
System.out.println(name+"喜欢玩"+game);
}
public void work(){
System.out.println(name+"喜欢工作");
}
//可变长参数:数据类型... 变量名, 注意:该变量是数组。 可变长参数只能放到最后面。
public long calculate(long b,int... a){
int sum = 0;
for(int i : a){
sum += i;
}
return sum;
}
}
public class PersonTest {
public static void main(String[] args) {
//创建Person类型的一个变量
Person p1 = new Person();
//注意:成员变量都是有默认值的,在没赋值之前.
System.out.println(p1.name);//引用类型的默认值是null
System.out.println(p1.age);//byte,short,int,long的默认值是0
System.out.println(p1.gender);//char的默认值\u0000
//给对象的成员变量赋值
p1.age = 18;
p1.name = "michael";
p1.gender = '男';
System.out.println(p1.name);
System.out.println(p1.age);
System.out.println(p1.gender);
//上述是成员变量的访问,也可以使用该方式去访问成员方法
p1.eat("蛋糕");
p1.play("王者荣耀");
p1.work();
long sum = p1.calculate(4,5,6);
System.out.println(sum);
}
}
null
0
michael
18
男
吃的食物是:蛋糕
michael喜欢玩王者荣耀
michael喜欢工作
11
2.5 this关键字
在一个类的非静态方法中(成员方法)中,使用this代表当前对象。因为实例方法需要使用对象调用,哪个对象调用这个方法, this就是谁。
this.是隐藏在成员方法里的成员变量前面的关键字。
public class Cat {
String name;
String brand;
String color;
public void setInfo(String a,String b,String c){
// 成员变量前,隐藏了this. 可以写出来,也可以省略不写。
this.name = a;
this.brand = b;
color = c;
}
public void setInformation(String name,String brand,String color){
// 当形式参数的名字与成员变量的名字相同时,如果想要在这个作用域内
// 表示成员变量,那么this.不能省略。
this.name = name;
this.brand = brand;
this.color = color;
}
public void showInfo(){
System.out.println("name:"+name+",brand:"+brand+",color:"+color);
}
public static void main(String[] args) {
//创建Cat对象
Cat c1 = new Cat();
//调用setInfo给c1的各个属性赋值
c1.setInfo("小白","中华田园","白色");
//调用showInfo()方法,显示对象的信息
c1.showInfo();
//调用setInformation修改对象的属性信息
c1.setInformation("小黑","吉林品牌","黑色");
c1.showInfo();
}
}
name:小白,brand:中华田园,color:白色
name:小黑,brand:吉林品牌,color:黑色
2.6 null和NullPointerException
(1)null:引用类型的默认值。 表示引用变量里的地址被清空,即没有对象的地址信息 String str = null; Person p = null; (2)NullPointException: 1. 空指针异常,运行时发生的 2. 变量里没有地址信息,确使用变量来访问对象的成,就会发生该异常。
public class PersonTest02 {
public static void main(String[] args) {
String str = "Hello World";
System.out.println(str.length());//11
str = null;//清空地址信息
System.out.println(str.length());//没有地址指向对象,确访问了对象的成员,NullPointerException
Person person = new Person();
System.out.println(person.name);//null
person = null;
System.out.println(person.name);//NullPointerException
}
}
2.7 包的概念
关键字:package。用来管理各种源文件,即.java文件。
如:所有的实体类,一般都放在com.公司名.项目名.模块名.实体
reg: com.yc.oop.mgr.pojo(vo,entity)
所有的工具类一般都放在 com.公司名.项目名.模块名.工具
reg: com.yc.oop.mgr.util
所有的控制类: reg: com.yc.oop.mgr.controller
所有服务类: reg: com.yc.oop.mgr.service
在一个类A中,如果想要使用别的类B,并且这两个类不在同一个包下,那么就需要
1.import. 导入操作,即将其引入到该类中,才能使用。
2. 类的名字:
类名: 类的短名 。 类全名: 从最外层的包开始写的名字。
比如Computer这个类:Computer就是短名,简称类名。 全名:com.oop.pojo.Computer
如果不导包,就要在new后面写类全名。
三、 构造方法
3.1 介绍和定义
1. 类体中,程序员没有提供任何构造器时,系统已经提供了一个无参数构造器 public 类名(){}
2. 程序员一旦提供构造器,系统就不再提供那个无参数构造器。
3. 构造器也是一个方法,但是没有返回值,类型这个位置,名字与类名一致。
4. 构造器不能使用static修饰。
5. 构造器可以重载,只需要形参类型列表不一样就可以。
6. 全参构造器就是用来给成员变量初始化的。
public class Person2 {
String name;
int age;
char gender;
//无参构造器
public Person2() {
System.out.println("-----无参构造器-----");
}
//全参构造器
public Person2(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
System.out.println("--全参构造器--");
}
public Person2(String name,char gender) {
this.name = name;
this.gender = gender;
this.age = 18;
System.out.println("--两个参数构造器--");
}
public void showInfo(){
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Gender: " + gender);
}
}
3.2 调用构造器
(1)构造器的调用:1. 使用new关键字 语法:new 构造方法(有参传参)
public class Person2Test {
public static void main(String[] args) {
//打印Person2的无参构造器实例化和初始化的一个对象
Person2 p1 = new Person2();
p1.showInfo();//因为无参构造器中没有初始化操作,因此都是默认值
//调用全参构造器实例化和初始化一个对象 ctrl+P用于查看重载的方法参数
Person2 p2 = new Person2("小明",20,'男');
p2.showInfo();
//调用两个参数的构造器实例化和初始化一个对象
Person2 p3 = new Person2("小红",'女');
p3.showInfo();
}
}
构造器中调用本类的其他构造器:
1.在构造器里可以使用this(有参传参)的方式调用本类中的其他构造器 。
2. this(有参传参)只能写在构造器的首行首句。
public class Person3 {
String name;
int age;
char gender;
public Person3() {
this.name = "";
this.age = -1;
this.gender = 'f';
}
public Person3(String name) {
this.name = name;
}
public Person3(String name,int age) {
//调用了本类中的具有一个参数的构造器
this(name);
this.age = age;
}
public Person3(String name,int age,char gender) {
//调用了本类中的具有两个参数的构造器
this(name,age);
this.gender = gender;
}
public void showInfo(){
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Gender: " + gender);
}
public static void main(String[] args) {
//调用两个参数构造器
Person3 p1 = new Person3("小黑",18);
p1.showInfo();
//调用全参构造器
Person3 p2 = new Person3("张三",29,'男');
p2.showInfo();
}
}
Name: 小黑
Age: 18
Gender:
Name: 张三
Age: 29
Gender: 男
四、代码块
4.1 动态代码块(构造代码块)
语法:
{
代码片段
}
位置: 与构造器并列,都是在类体中。
特点: 构造器执行一次,动态代码块就执行一次,并先于构造器执行。
作用: 一般用于统计一个类型创建了多少个对象,或者提前给成员变量赋值。
4.2 静态代码块(静态块)
语法:
static{
代码片段
}
位置: 与构造器并列,都是在类体中。
特点: 只执行一次。在类加载器将该类的信息加载到内存时执行的。
作用: 一般用于加载静态资源到内存中,比如图片,音乐,视频等。
String toString(): 将对象的成员信息变成一串字符串并返回。该方法,在将引用变量放在输出语句中时,会默认调用。
public class BlockDemo {
public static void main(String[] args) {
//调用无参构造器实例化对象
BlockDemo bd = new BlockDemo();
System.out.println(bd.toString());
//调用有参构造器实例化对象
BlockDemo bd2 = new BlockDemo(10,20);
System.out.println(bd2);//默认调用了toString()
}
int a;
int b;
static{
System.out.println("--静态块执行了--");
}
{
System.out.println("--动态块执行了--");
a = 100;
b = 200;
}
public BlockDemo() {
System.out.println("无参构造器");
}
public BlockDemo(int a, int b) {
this.a = a;
this.b = b;
System.out.println("--有参构造器--");
}
/**
* String toString(): 将对象的成员信息变成一串字符并返回
* 该方法,在将引用变量放在输出语句中时会默认调用
* @return
*/
public String toString() {
return "a=" + a + ", b=" + b;
}
}
--静态块执行了--
--动态块执行了--
无参构造器
a=100, b=200
--动态块执行了--
--有参构造器--
a=10, b=20
五、JVM内存管理机制和GC
Java语言本身是不能操作内存的,它的一切都是交给JVM来管理和控制的,因此Java内存区域的划分也就是JVM的区域划分,在说JVM的内存划分之前,我们先来看一下Java程序的执行过程,如下图:
图中可以看出:Java代码被编译器编译成字节码之后,JVM开辟一片内存空间(也叫运行时数据区),通过类加载器加载到运行时数据区来存储程序执行期间需要用到的数据和相关信息,在这个数据区中,它由以下几部分组成:虚拟机栈,堆,程序计数器,方法区,本地方法栈。
4.1 虚拟机栈
虚拟机栈是Java方法执行的内存模型,栈中存放着栈帧,每个栈帧分别对应一个被调用的方法,方法的调用过程对应栈帧在虚拟机中入栈到出栈的过程。
栈是线程私有的,也就是线程之间的栈是隔离的;当程序中某个线程开始执行一个方法时就会相应的创建一个栈帧并且入栈(位于栈顶),在方法结束后,栈帧出栈。
栈的总结:
1. 一个线程对应一个栈,main方法就是一个线程。 栈空间是私有的,不能被其他线程访问。
2. 栈的数据结构的特点: first input last output : filo 先进后出。
3. 每个方法在调用时,在栈里都会被分配一个栈帧空间. 栈帧用于存储该方法的所有局部变量。
方法执行完毕,栈帧消失。
栈帧:是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。
每个栈帧中包括:
局部变量表:用来存储方法中的局部变量(非静态变量、函数形参)。当变量为基本数据类型时,直接存储值,当变量为引用类型时,存储的是指向具体对象的地址。
操作数栈:Java虚拟机的解释执行引擎被称为"基于栈的执行引擎",其中所指的栈就是指操作数栈。
指向运行时常量池的引用:存储程序执行时可能用到常量的引用。
方法返回地址:存储方法执行完成后的返回地址。
4.2 堆
堆是用来存储对象本身和数组的,在JVM中只有一个堆,因此,堆是被所有线程共享的。
从jdk1.7开始,字符串常量池在堆中。
4.3 方法区
方法区是一块所有线程共享的内存逻辑区域,在JVM中只有一个方法区,用来存储一些线程可共享的内容,它是线程安全的,多个线程同时访问方法区中同一个内容时,只能有一个线程装载该数据,其它线程只能等待。
方法区可存储的内容有:类的全路径名、类的直接超类的权全限定名、类的访问修饰符、类的类型(类或接口)、类的直接接口全限定名的有序列表、运行时常量池(字段,方法信息,静态变量,类型引用(class))等。
4.4 垃圾回收机制
java的垃圾回收机制,在程序运行时,就已经跟着启动了。 会主动去处理堆里的没有被任何引用指向的对象。这样的对象都会被认为是垃圾。 并不是程序员要调用System.gc()该功能就会立马处理。
如果这样的垃圾没有被处理,会出现堆内存溢出。但是,一般情况下gc还是会尽快处理的。不会出现内存溢出。
如果我们在编程时,对象用了几次后,不再使用了,那么应该尽快将引用变量赋值为null。即将对象视为垃圾,等待gc处理。
五、值传递和址传递
值传递: 基本数据类型之间的赋值操作(本质是值的副本传递)。
改变形参的值,不会改变实际参数的值。
地址传递: 引用数据类型之间的变量的赋值操作(本质是地址的副本传递)。
通过形参改变对象的数据,那么实际参数指向的对象被改变了。除非形参在改变对象前,指向了新对象。
public class Test01 {
public static void main(String[] args) {
int a =5;
char[] chs = {'m','n','p'};
char[] rs = m(a,chs);
System.out.println("a="+a);//5
System.out.println(chs);//m,x,p
System.out.println(rs);//m,x,p
}
public static char[] m(int a,char[] chs){
a = 10;
chs[1] = 'x';
return chs;
}
}
a=5
mxp
mxp
六、析构方法
6.1 简介
析构方法,是对象的生命周期中最后的一个方法。执行时机是当这个对象被销毁之前。执行了这个方法之后,空间就会被销毁,这个对象也就不存在了。
@Override
protected void finalize() throws Throwable {
super.finalize();
}
finalize()析构方法负责回收Java对象所占用的内存,该方法一般是在对象被垃圾收集器回收之前调用。通常我们会在finalize()方法中,指定对象销毁时要执行的操作,比如关闭对象打开的文件、IO流、释放内存资源等清理垃圾碎片的工作。
finalize()析构方法具有以下这些特点:
-
垃圾回收器是否会执行finalize方法,以及何时执行该方法,是不确定的;
-
finalize()方法有可能会使对象复活,恢复到可触及的状态;
-
垃圾回收器执行finalize()方法时,如果出现异常,垃圾回收器不会报告异常,程序会继续正常运行。
6.2 案例演示
在大多数情况下,Java的内存和垃圾回收都是由JVM的GC机制来自动完成。如果我们想手动实现,就可以使用finalize()方法,但该方法的执行与否是不确定的。也就是说,即使我们调用了finalize()方法,JVM也不一定就会立刻执行垃圾回收操作,这个取决于当前系统的内存占用情况。
finalize()是一个被protected关键词修饰的方法,可以确保该方法不会被该类以外的代码调用。在每个Java类中都有finalize()方法,我们可以复写当前类中的finalize()方法。
public class Counter{
private static int count = 0; //计数器变量
public Counter(){
this.count++;
}
public int getCount(){
return this.count;
}
@Override
protected void finalize(){
this.count--;
System.out.println("--对象已经被销毁---");
}
}
public static void main(String[] args){
Counter c1 = new Counter();
System.out.println("数量:"+c1.getCount()); //输出1
Counter c2 = new Counter();
System.out.println("数量:"+c2.getCount()); //输出2
//准备销毁c1
c1 = null;
System.gc();
Thread.currentThread().sleep(1000);//先获取对象在休眠,暂停运行,1000指的是ms
System.out.println("数量:"+c2.getCount()); //输出1,说明对象被销毁了一个
}
数量: 1
数量: 2
--该对象已经被销毁---
数量: 1