一、面向过程:
结构化程序设计,也叫面向过程程序设计,首先采用结构化分析(Structured Analysis,SA)方法对系统进行需求分析,然后使用结构化设计(Structured Design,SD)方法对系统进行概要设计,详细设计,最后采用结构化编程(Structured Programming,SP)方法来实现系统。使用何种SA,SD,SP的方式可以较好的保证软件系统的开发进度和质量。
结构化程序设计,主张以功能(过程)/步骤为中心,将软件系统进行逐步细分。每个功能都负责对一些数据进行处理:每一个功能都会接收一些数据,然后也会输出一些数据。其设计思想可概括为:自顶而下,逐步精分,模块化。
函数:
结构化程序设计里最小的程序单元为函数,每个函数都负责一个功能。用于接受一些数据,进行处理,然后输出一些数据。整个软件系统都是由一个个函数组成。其中作为启动程序的函数,我们称之为主函数,主函数里会调用普通函数,普通函数之间依次调用,从而完成整个软件系统的功能。
由于采用自顶而下的设计方法,因此在设计阶段就需要考虑每个模块应该分解成哪些子模块,每个子模块又分解成哪些更小的模块…….依次类推,直到将模块细化成一个一个函数。
每个函数都是具有输入和输出的子系统。函数的输入数据包含函数的形参,全局变量,和常量等等;函数的输出数据包含函数的返回值及传出参数等。
局限性:
- 设计不够直观,与人类的习惯思维不一致,开发者需要将客观世界模型分解成一个个功能,用以完成一定的数据处理
- 适应性差,可扩展性不强。当客户的需求发生变化时,需要自顶而下的修改各个模块,维护成本高
二、面向对象:
简介:
面向对象(Object Oriented)是一种更加优秀的程序设计方法。它由面向对象分析(OOA),面向对象设计(OOD),面向对象编程(OOP)三部分组成。
它的基本思想是使用类,对象,继承,封装,消息等进行程序设计。它从现实世界中客观存在的事物出发来构造软件系统,在系统构造中尽可能的运用人类的自然思维方式,强调直接以现实世界中的事物为中心来思考问题,认识问题。并根据这些事物的本质特点,把他们抽象地表示为系统中的类,作为系统的基本构成单元,使得软件系统的组件可以直接映射现实世界,并保持客观世界事务及其相互关系的本来面貌。
即:将程序中要使用的数据,以及功能,抽象出来一类事务比如person,然后创建该类事务的某些实例(个体),将数据封装到这些对象上,然后调用对象的行为来完成相应的功能。
面向对象的类:
面向对象程序设计中,其最小的程序单元是类,类可以生成系统中的多个对象。而这些对象直接映射成客观世界的各种事物(各个实例)。
软件系统是由多个类组成,类代表了客观世界中具有某种特征的一类事物,这类事物往往有一些内部的状态数据,比如人,有姓名,身高,体重,性别,年龄,爱好等各种状态数据。
状态数据,即Field1,Field2,Field3.......----->成员变量,属性,字段。
操作这些状态数据的方法,为这类事物的行为特征提供实现,即方法Method
状态数据+方法 =类定义
面向对象比面向过程的编程粒度要大:
面向对象的单位是类,面向过程的程序单位是函数,
因此面向对象比面向过程要简单、易用。
三、类与对象
类:
在现实生活中,我们是根据具体的对象----->发现有共同的特点----->总结归纳成一类事物。
而在计算机编程中,则是,先将类定义出来------>有该类的具体对象。
类的定义语法:
[访问权限修饰符] class 类名{
成员变量
成员方法
}
注意:
- 类名:大驼峰命名法
- 访问权限修饰词:在一个.java源文件里定义的普通类,修饰词只能是public或默认
- 成员变量:描述对象的共同特征(状态数据),格式:变量的声明
- 成员方法:描述对象的共同行为,或操作状态数据,不带static修饰的方法
class Person { // 描述一个人的共同特征 String name; int age; char gender; // 描述一个人的行为(功能) void eat() {} void sleep() {} void beat() {} }
注意:
一个文件中,只能有一个public修饰的类,且该类的名字必须与文件名一样。如果还有别的类,其余的类只能是默认修饰类(即没有public)。
例如,如果 文件名 为 Gong.java,则该文件中只能有一个公共类,并且该类必须使用 public 修饰符进行声明,如下所示: 但是,非公共类可以存在于同一个源文件中,并且可以有多个非公共类。
// Gong.java public class Gong{ // 公共类的内容 } // 非公共类(可以有多个) class FeiGong { // 非公共类的内容 }
对象:
类是对象的抽象(模版),对象是类的具体(实例)。
类定义完成后可以使用new关键字创建对象。
创建对象的过程通常称之为实例化。
实例化语法:
new 类名();
引用变量:
如果需要访问实例化对象,需要使用变量来接收对象,然后访问变量。
语法为:
类名 变量名 = new 类名();
引用类型声明的变量名,有一个专业称呼,即引用变量,简称引用。
为什么叫引用变量:
因为引用变量里面存储的不是对象,而是对象在内存中的地址信息。
引用变量通过地址信息指向对象,取代了面向过程中的指针。
成员访问:
即:使用类里的成员变量(属性),方法
基本的成员访问:
引用变量.成员变量
引用变量.成员方法
// 实例化一个Person对象 Person xiaoming = new Person(); // 访问对象的属性 xiaoming.name = "xiaoming"; xiaoming.age = 10; xiaoming.gender = '男 '; System.out.println(xiaoming.name); System.out.println(xiaoming.age); System.out.println(xiaoming.gender); // 访问对象的方法 xiaoming.eat(); xiaoming.sleep(); xiaoming.beat();
静态和非静态:
- static修饰的属性,叫做静态属性;
- static修饰的方法,叫做静态方法
- 没有static修饰的属性,叫非静态属性,也叫成员变量
- 没有static修饰的方法,叫非静态方法,也叫成员方法
区别:
1.访问:
static修饰的属性或方法属于类本身,而不是类的某个特定实例。这意味着它们可以在没有创建类的实例的情况下被访问。可以直接通过类名来访问这些属性和方法,而不需要创建对象实例。
非static修饰的属性或方法属于对象,而不是整个类,只能通过创建完对象具体实例之后,通过引用变量来访问调用。
2.内存中的存储:
static修饰的变量在类加载时会被分配到数据区的方法区中,这个区域是被类的所有实例共享的。因此,如果一个static变量发生改变,所有访问这个变量的类的实例都会看到这个改变
。可以理解为在类里面设置了一个公共值。
3.限制在方法使用:
在static修饰的方法中,不能使用this或super关键字,因为这些方法属于类,而this和super是属于类实例的。
同样,static方法不能直接访问类的非static属性,因为非static属性是属于具体对象的。
4.方法调用的限制:
非static修饰的方法可以访问static修饰的变量,因为static变量在类加载的时候就已经存在了。但是,static方法不能访问非static修饰的变量,因为这些变量是在创建对象实例时才被初始化的。
即:
静态方法中,只能直接访问本类中的静态成员。不能访问非静态成员
非静态方法中,可以访问本类中的非静态成员和静态成员
总的来说,static关键字提供了一种在类级别上共享数据和行为的方式,而不是在对象实例级别上。这在需要跨多个对象实例共享数据或实现工具方法时非常有用,如计数器或工具函数等。
this关键字:
在一个类的非静态方法中(成员方法)中,使用this代表当前对象
1.只能在非静态方法中使用
2.在非静态方法中可以直接访问成员变量和成员方法
3.在非静态方法中可以直接访问成员变量和成员方法前,隐藏着this.
4.this.是可以显式书写的。
5.this是一个代词,代替的是一个对象。谁调用方法,this就指代谁。
public class Person { String name; int age; char gender; int height; int weight; //通过形参给成员变量赋值 void setInfo(String name1, int age1, char gender1, int height1, int weight1) { name = name1; age = age1; gender = gender1; height = height1; weight = weight1; } //什么时候this.不能省略: //当局部变量(包括方法的形参)与成员变量重名时,如果想要调用成员变量,this.是不能省略的,否则是局部变量。 void setInfo(String name, int age, char gender, int height, int weight) { this.name = name; this.age = age; this.gender = gender; this.height = height; this.weight = weight; } }
什么时候this.不能省略:
当局部变量(包括方法的形参)与成员变量重名时,如果想要调用成员变量,this.是不能省略的,否则是局部变量。
实例化并调用方法赋值:
public class PersonTest{ public static void main(String[] args){ Person p = new Person(); p.setInfo(lili,28,女,168,110)// } }
null和NullPointerException:
null:
引用类型的默认值。
表示引用变量里的地址被清空,即没有对象的地址信息
给引用变量赋值为null,表示变量中没有任何地址。
String str = null;
引用变量里面存的地址的样子:类全限定名@16进制的hashCode值Person p = null;
NullPointerExcedption:
空指针异常, 运行时发生的(编译检查时不会出错)
运行时异常(非编译时异常)
当访问变量,属性或者变量方法时,该变量里并没有存储地址时,就会发生异常。
变量里没有地址信息,却使用变量来访问对象的成员。就会发生该异常public class PersonTest2 { 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 } }
四、构造方法
对象的一生由构造方法开启,到析构方法结束。
- 构造方法,也叫构造器
- 一般用来实例化对象,使用new关键字来调用,一般还用来初始化成员变量
- 构造器定义语法:
访问权限修饰词 类名(【形参列表】){
//方法体
}
- 构造方法的名字必须和类名一致。修饰词可以使用访问权限修饰词(public protected,private,默认)
- 执行时机:创建对象时执行的。
- 构造器也可以重载
- 如果程序员不提供构造器,系统会默认提供一个无形参列表的构造器。
- 如果程序员提供构造器,系统不提供默认构造器
- 程序员默认约定:自定义类的时候,提供至少两个构造器,一个无参,一个全参。
构造方法与普通方法的区别:
1.语法不同:
构造方法没有返回值(不存在)
构造方法的名字必须与类名一致
构造方法不能使用static修饰
2.执行时机不同;
构造方法是在实例化对象的时候调用执行的,new关键字调用的
普通方法可以随时调用
3.构造方法可以重载
注意参数列表不同
参考程序员的默认约定
定义:public class Person { /** * 无参构造 */ Person() { System.out.println("无参的构造方法执行了 "); } /** * 有参构造 * @param name */ Person(String name) { System.out.println("有参的构造方法执行了 "); } }
public class Computer{ String brand; String model; String color; String price; public Computer(){//无参构造器 } //定义一个构造器,除了实例化对象外,还可以进行初始化成员变量 public Computer(String brand,String model,String color,String price){//全参构造器 this.brand=brand; this.model=model; this.color=color; this.price=price; } public void open(){ System.out.println("开机"); } public void close(){ System.out.println("关机"); } public String show(){ return brand+model+color+price; } }
调用构造器:
用new关键字调用,有参传参 (ctrl+p 可以查看所有重载的参数;alt+回车 创建变量) //调用无参构造器,来创建一个电脑: Computer c1= new Computer; c1.brand="戴尔"; c1.model="D"; c1.color="白色"; c1.price="3500"; //调用全参构造器 同时初始化 Computer c2 = new Computer("戴尔","D","白色","3500");
构造器中调用本类的其他构造器:
在构造方法中,是希望给某些属性进行初始化的赋值操作。
一个类中可能写了多个构造方法,每一个构造方法,都为指定的属性进行了初始化的赋值。但是在不同的构造方法中,可能会存在重复的赋值部分。
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.name = name;
this.age = age;
this.gender = gender;
this.height = height;
this.weight = weight;
}上述代码中,name、age、gender的赋值在两个构造方法中重复了。所以,在下面的构造方法中,只需要 调用上面的构造方法,既可以完成对这三个属性进行初始化赋值的操作。
Person(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}//1.必须在某个构造器中调用其他的构造器
//2.只能使用语法:this(有参传参)来调用其他构造器
//3.this(有参传参)必须在首行首句上 前面不能加任何的语句
//4.目的就是减少代码的书写量
//5.注意:千万不要出现死循环的调用:A里调用B,B里调用A
Person(String name, int age, char gender, int height, int weight) {
// 使用关键字 this() 来调用当前类中的构造方法。
this(name, age, gender);
this.height = height;
this.weight = weight;
}
五、代码块
构造(动态)代码块:
语法:
{
代码片段
}
位置: 与构造器并列,都是在类体中
特点: 构造器执行一次,动态代码块就执行一次,并先于构造器执行(依赖构造方法)
作用: 一般用于统计一个类型创建了多少个对象(创建一次对象就调用一次),或者提前给成员变量赋值
package com.oop.codeblock; import java.util.Arrays; public class BlockDemo { int a; int b; { System.out.println("--动态块执行了--"); a = 100; b = 200; } public BlockDemo(){ System.out.println("--无参构造器--"); } public BlockDemo(int a, int b){ System.out.println("--有参构造器--"); this.a = a; this.b = b; } /** * String toString(): 将对象的成员信息变成一串字符串并返回 * 该方法,在将引用变量放在输出语句中时,会默认调用 * @return */ public String toString(){ return "a = " + a + ", b = " + b; } 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() } }
静态代码块(也叫静态块、静态初始化块):
静态代码块(静态块)
语法:
static{
代码片段
}
位置:与构造器并列,都是在类体中
特点:1.只执行一次。 在类加载器将该类的信息加载到内存时,执行的。
2.静态块优先于各种代码块以及构造函数,如果一个类中有多个静态代码块,会按照书写顺序依次执行
3.静态代码块可以定义在类的任何地方中除了方法体中【这里的方法体是任何方法体】
4.静态代码块不能直接访问成员变量,不能用this.关键字
作用:一般用于执行类属性的初始化 ,加载静态资源到内存中,比如图片,音乐,视频等。
public class BlockDemo { 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){ System.out.println("--有参构造器--"); this.a = a; this.b = b; } /** * String toString(): 将对象的成员信息变成一串字符串并返回 * 该方法,在将引用变量放在输出语句中时,会默认调用 * @return */ public String toString(){ return "a = " + a + ", b = " + b; } 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() } }
六、JVM内存管理机制和GC
Java语言本身是不能操作内存的,它的一切都是交给JVM来管理和控制的,因此Java内存区域的划分也就是JVM的区域划分,在说JVM的内存划分之前,我们先来看一下Java程序的执行过程,如下图:
Java代码被编译器编译成字节码之后,JVM开辟一片内存空间(也叫运行时数据区),通过类加载器加载到运行时数据区来存储程序执行期间需要用到的数据和相关信息,在这个数据区中,它由以下几部分组成:
虚拟机栈,堆,程序计数器,方法区(元空间),本地方法栈
虚拟机栈:
虚拟机栈是Java方法执行的内存模型,栈中存放着栈帧,每个栈帧分别对应一个被调用的方法,方法的调用过程对应栈帧在虚拟机中入栈到出栈的过程。
栈帧:
是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。
每个栈帧中包括:
局部变量表:用来存储方法中的局部变量(非静态变量、函数形参)。当变量为基本数据类型时,直接存储值,当变量为引用类型时,存储的是指向具体对象的引用。
操作数栈:Java虚拟机的解释执行引擎被称为"基于栈的执行引擎",其中所指的栈就是指操作数栈。
指向运行时常量池的引用:存储程序执行时可能用到常量的引用。
方法返回地址:存储方法执行完成后的返回地址。
栈是线程私有的,也就是线程之间的栈是隔离的;当程序中某个线程开始执行一个方法时就会相应的创建一个栈帧并且入栈(位于栈顶),在方法结束后,栈帧出栈。
1.栈是一个存储结构(模型):先进后出
2.每个线程都有自己的栈,栈是私有的,因此多线程的栈空间不可共享
3.在线程中所涉及到的任何一个方法,在执行到该方法时,都会在栈中得到一块私有空间。即栈帧
4.局栈帧用来存储该方法中的所有部变量。基本类型的数据变量存储的是值,引用数据类型的变量存储的是地址值。
5.方法执行完,对应的栈帧销毁释放,腾出内存空间。
6.main方法就是一个线程。
堆:
1.堆空间是用来存储所有的引用类型的对象(包括数组对象)
2.一个程序会启动一个JVM,一个JVM只有一个堆空间,该程序中的所有线程共享这个堆空间
3. jdk1.7之前,方法区中是有常量池的,
jdk1.7以后,运行时常量池被移到堆中了。
方法区:
方法区是一块所有线程共享的内存逻辑区域,在JVM中只有一个方法区,用来存储一些线程可共享的内容,
它是线程安全的,多个线程同时访问方法区中同一个内容时,只能有一个线程装载该数据,其它线程只能等待。
方法区可存储的内容有:类的全路径名、类的直接超类的权全限定名、类的访问修饰符、类的类型(类或接口)、类的直接接口全限定名的有序列表、运行时常量池(字段,方法信息,静态变量,类型引用(class))、jdk的版本号等。
GC垃圾回收机制:
java的垃圾回收机制,在程序运行时,就已经跟着启动了。 会主动去处理堆里的没有被任何引用指向的对象。这样的对象都会被认为是垃圾。 并不是程序员要调用System.gc()该功能就会立马处理。
- 垃圾:
堆里的对象,没有任何变量存储其地址时。
- 什么时候被清理:
当垃圾回收机制在检查堆内存时,如果判断该对象没有地址被存储,就会慢慢的销毁它,销毁前调用析构方法finalize()
- 如果程序员不想再使用某一个对象:
应该将其地址清空,即变量赋值为null。
注意:赋值为null时,垃圾回收机制并不一定会立即销毁它
- System.gc():
通知垃圾回收机制,尽快进行销毁工作。
值传递和地址传递:
值传递: 基本数据类型之间的赋值操作(本质是值的副本传递)。
改变形参的值,不会改变实际参数的值。
地址传递: 引用数据类型之间的变量的赋值操作(本质是地址的副本传递)
通过形参改变对象的数据,那么实际参数指向的对象被改变了。除非形参在改变对象前,指向了新对象。
public static void main(){
int a = 10;
char[] chs = new char[]{'A','B','C'}
System.out.println(a);
System.out.println(chs)
}
public void m(int a, char[] chs){
a = 5;
chs[1] = 'G';
}
有图:
最终结果为:
a=10;
chs={A,G,C};
在Java中所有的参数传递,不管基本类型还是引用类型,都是值传递,或者说是副本传递。 只是在传递过程中:
如果是对基本数据类型的数据进行操作,由于原始内容和副本都是存储实际值,并且是在不同的栈区,因此形参的操作,不影响原始内容。
如果是对引用类型的数据进行操作,分两种情况,一种是形参和实参保持指向同一个对象地址,则形参的操作,会影响实参指向的对象的内容。一种是形参被改动指向新的对象地址(如重新赋值引用),则形参的操作,不会影响实参指向的对象的内容。
八、析构方法
析构方法,是对象的生命周期中最后的一个方法。执行时机是当这个对象被销毁之前。执行了这个方法 之后,空间就会被销毁,这个对象也就不存在了。
@Override
protected void finalize() throws Throwable {
}
finalize()析构方法负责回收Java对象所占用的内存,该方法一般是在对象被垃圾收集器回收之前调用。
通常我们会在finalize()方法中,指定对象销毁时要执行的操作,比如关闭对象打开的文件、IO流、释放内存资源等清理垃圾碎片的工作。
finalize()析构方法具有以下这些特点:
垃圾回收器是否会执行finalize方法,以及何时执行该方法,是不确定的;
finalize()方法有可能会使对象复活,恢复到可触及的状态;
垃圾回收器执行finalize()方法时,如果出现异常,垃圾回收器不会报告异常,程序会继续正常运行。
在大多数情况下,Java的内存和垃圾回收都是由JVM的GC机制来自动完成。如果我们想手动实现,就可以使用finalize()方法,但该方法的执行与否是不确定的。也就是说, 即使我们调用了finalize()方法,JVM也不一定就会立刻执行垃圾回收操作,这个取决于当前系统的内存占用情况。
另外finalize()是一个被protected关键词修饰的方法,可以确保该方法不会被该类以外的代码调用。在每个Java类中都有finalize()方法,我们可以复写当前类中的finalize()方法
测试:
定义一个计数器Counter类型;
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地址,c1指向的对象,变成垃圾 c1 = null; //通知 GC尽快处理 System.gc(); //让main线程阻塞一会,GC处理后在结束。 Thread.currentThread().sleep(15000); System.out.println("数量:"+c2.getCount()); //输出1,说明对象被销毁了一个 }
结果:
数量: 1
数量: 2
--该对象已经被销毁---
数量: 1