1.面向过程的简介
结构化程序设计,也叫面向过程程序设计,首先采用结构化分析(Structured Analysis,SA)方法对系统进行需求分析,然后使用结构化设计(Structured Design,SD)方法对系统进行概要设计,详细设计,最后采用结构化编程(Structured Programming,SP)方法来实现系统。
其设计思想可概括为:自顶而下,逐步精分,模块化。
结构化程序设计里最小的程序单元为函数,每个函数都负责一个功能
该方法有局限性: 1.开发者需要将客观世界模型分解成一个个功能,用以完成一定的数据处理
2.当客户的需求发生变化时,需要自顶而下的修改各个模块,维护成本高
2.面向对象
(1)简介
向对象(Object Oriented)是一种更加优秀的程序设计方法。它由面向对象分析(OOA),面向对象设计(OOD),面向对象编程(OOP)三部分组成。
它的基本思想是使用类,对象,继承,封装,消息等进行程序设计。
(2)面向对象的类
面向对象程序设计中,其最小的程序单元是类,类可以生成系统中的多个对象。
类里面有属性(成员变量),这类事物的共同特征。即状态数据,一堆不同类型的变量。
类里还有方法 (method), 操作这些状态数据的方法,为这类事物的行为特征提供实现。
3.类与对象
(1)类的设计
类的定义语法:
[访问权限修饰符] class 类名{
成员变量
成员方法 (指的都是不带static修饰的方法)
}
访问权限修饰词有四个:private,public,protected,默认
- 在一个.java源文件里定义的普通类,修饰词只能是public或者默认的
以person为例: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(this.name+"喜欢工作");
}
// 可变长参数: 数据类型... 变量名, 注意:该变量是数组。 可变长参数只能放到最后面。
public long calculate(long b,int... a){
int sum = 0;
for (int i = 0; i < a.length; i++) {
sum+=a[i];
}
return sum;
}
}
(2)扩展:可变长参数
可变长参数: 数据类型... 变量名, 注意:该变量是数组,有一堆的数据。
可变成参数只能放到最后面。
(3)对象
类是对象的抽象(模版),对象是类的具体(实例)。
类定义完成后,可以使用new关键字创建对象。 创建对象的过程通常称之为实例化。
语法:new 类名();
如果想要访问实例化出来的对象,通常都会使用变量来接收一下该对象。
格式:类名 变量名 = new 类名();
在java中除了8种基本数据类型之外,都是引用类型,包括我们自定义的类;而引用类型声明的变量名,有一个专业称呼,即引用变量,简称引用。
叫引用变量:是因为引用变量里存储的不是对象,而是对象在内存中的地址信息。 也就是引用通过地址信息指向对象。 取代了面向过程中的指针。
(4)成员访问
1.基本的成员访问:指的就是如何使用类里的成员变量(属性),方法
语法: 引用变量.成员变量
引用变量.成员方法
public static void main(String[] args) {
//实例化一个Person对象
Person p = 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
//访问对象的属性,并进行赋值。
p.name = "小明";
p.age = 18;
p.gender = '男';
System.out.println(p.name);
System.out.println(p.age);
System.out.println(p.gender);
//访问对象的方法
p.eat("蛋糕");
p.work();
p.play("蛋仔派对");
long sum = p.calculate(4,5,6,1);
System.out.println(sum);
}
(5)静态和非静态
1.static修饰的属性,叫做静态属性;
2. static修饰的方法,叫做静态方法
3. 没有static修饰的属性,叫非静态属性,也叫成员变量
4. 没有static修饰的方法,叫非静态方法,也叫成员方法
static修饰的属性和方法,都是属于类的,需要使用类名调用。非static修饰的属性和方法,是属于对象的,通常使用引用变量来调用。
注意:静态方法中,只能直接访问本类中的静态成员。不能访问非静态成员
非静态方法中,可以访问本类中的非静态成员和静态成员
在类里的静态方法,在main方法里直接使用方法名(有参传参)调用(main方法是一个静态方法)
非静态方法,在main方法里要调用该方法,需要创建一个该类的对象(引用变量)进行调用
格式:引用变量.方法名(有参传参);
(5)this关键字
this.是隐藏在成员方法里的成员变量前面的关键字。
在一个类的非静态方法中(成员方法)中,使用this代表当前对象。因为实例方法需要使用对象调用,哪个对象调用这个方法, this就是谁。
下面以cat类进行演示:
public class Cat {
static 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;
}
}
当形式参数的名字与成员变量的名字不同时,this关键字可以省略,但是如果形式参数的名字与成员变量的名字相同时,this关键字一点要写,指定操作的是当前对象。
(6)null和NullPointerException
null:1. 引用类型的默认值。
2.表示引用变量里的地址被清空,即没有对象的地址信息
String str = null; Person p = null;把str和p对象指空,此时访问对象的成员是就会发生空指针异常。
NullPointerExcedption: 1. 空指针异常, 运行时发生的
2. 变量里没有地址信息,却使用变量来访问对象的成员。就会发生该异常
(7)扩展:在其他包下的一个类想要调用另一个包下的类
1. 在一个类A中,如果想要使用别的类B,并且这两个类不在同一个包下,那么就需要:
import 导入操作,即将其(类B)引入到该类中,才能使用
2. 类的名字:
- 类名: 是类的短名
- 类全名: 是从最外层的包(src下)开始写的名字,比如Computer这个类 * Computer就是短名,简称类名 全名是:com.oop.pojo.Computer(是在com下,oop下,pojo下的Computer类)
导包的格式:import 类全名;
4.构造方法
(1)特点
构造方法,也是一个方法,它和普通的方法有点区别:
-
语法不同:
-
构造方法没有返回值(即返回值类型这个位置不存在)。
-
构造方法的名字,必须和类名保持一致。
-
构造方法,不能使用static修饰
-
-
执行时机不同。
-
普通的方法,可以随时调用。构造方法是在实例化对象的时候调用执行的。 即new关键字调用的
-
-
构造方法可以重载
-
参数类型列表不同即可
-
如果程序员在定义类时,没有提供任何构造器,系统会自动提供一个无参数构造器
-
一旦程序员自己提供了构造器,系统不在自动提供无参构造器,如果想要,需要程序员自己定义无参构造器
-
- 构造器的作用就是用来给成员变量初始化的
(2)定义
无参构造器:
格式:public 类名(){ }
以person2类为例:
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.age = 18;
this.gender = gender;
System.out.println("--两个参数构造器--");
}
public void showInfo(){
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Gender: " + gender);
}
}
进行测试:
(3)构造器的调用:
使用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();
}
}
(4)构造器中调用本类的其他构造器
在构造方法中,是希望给某些属性进行初始化的赋值操作。一个类中可能写了多个构造方法,每一个 构造方法,都为指定的属性进行了初始化的赋值。但是在不同的构造方法中,可能会存在重复的赋值部分。
在person3中构造无参构造器、一个参数的构造器、两个参数的构造器和三个参数的构造器,其中存在重复的赋值部分 ,即可在构造器中调用其他参数的构造器。
构造器的调用:
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();
}
}
5.代码块
(1)构造代码块(动态代码块)
1. 动态代码块(构造代码块)
语法:{
代码片段
}
2. 位置: 与构造器并列,都是在类体中
3. 特点: 构造器执行一次,动态代码块就执行一次,并先于构造器执行。
4.作用: 一般用于统计一个类型创建了多少个对象,或者提前给成员变量赋值
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;
{
System.out.println("--动态块执行了--");
a = 100; //给a和b实例化
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;
}
}
可以看出动态代码快先于构造器执行,并且构造器执行了几次,动态代码块就执行了几次,还对bd对象的属性进行实例化。bd2调用了全参构造器,把动态代码块的实例化覆盖了。
(2)构造静态代码块
1.. 静态代码块(静态块)
语法: static{
代码片段
}
2.位置:与构造器并列,都是在类体中
3.特点:只执行一次。 在类加载器将该类的信息加载到内存时,执行的。
4.作用:一般用于加载静态资源到内存中,比如图片,音乐,视频等。
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("--静态块执行了--");
}
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;
}
}
可以看出静态代码块先于构造器执行,并且只执行一次。可以给静态的成员实例化
当类体中同时有静态代码块和动态代码块时,静态代码块先于动态代码块执行,并且只执行一次
静态代码块给静态成员实例化时,会被动态代码块的实例化覆盖。
静态代码块的总结:
-
Java静态代码块中的代码会在类加载JVM时运行,且只被执行一次
-
静态块常用来执行类属性的初始化
-
静态块优先于各种代码块以及构造函数,如果一个类中有多个静态代码块,会按照书写顺序依次执行
-
静态代码块可以定义在类的任何地方中除了方法体中【这里的方法体是任何方法体】
-
静态代码块不能访问普通变量
(3).扩展:在继承关系中查看动态代码和静态代码的执行情况
6.JVM内存管理机制和GC
Java语言本身是不能操作内存的,它的一切都是交给JVM来管理和控制的,因此Java内存区域的划分也就是JVM的区域划分,在说JVM的内存划分之前,我们先来看一下Java程序的执行过程,如下图:
Java代码被编译器编译成字节码之后,JVM开辟一片内存空间(也叫运行时数据区),通过类加载器加载到运行时数据区来存储程序执行期间需要用到的数据和相关信息,在这个数据区中,它由以下几部分组成: 虚拟机栈,堆,程序计数器,方法区,本地方法栈
(1)虚拟机栈
虚拟机栈是Java方法执行的内存模型,栈中存放着栈帧,每个栈帧分别对应一个被调用的方法,方法的调用过程对应栈帧在虚拟机中入栈到出栈的过程。
栈是线程私有的,也就是线程之间的栈是隔离的;当程序中某个线程开始执行一个方法时就会相应的创建一个栈帧并且入栈(位于栈顶),在方法结束后,栈帧出栈。
下图表示了一个Java栈的模型以及栈帧的组成:
栈帧:是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。
每个栈帧中包括:
局部变量表:用来存储方法中的局部变量(非静态变量、函数形参)。当变量为基本数据类型时,直接存储值,当变量为引用类型时,存储的是指向具体对象的引用。
操作数栈:Java虚拟机的解释执行引擎被称为"基于栈的执行引擎",其中所指的栈就是指操作数栈。 指向运行时常量池的引用:存储程序执行时可能用到常量的引用。
方法返回地址:存储方法执行完成后的返回地址。
栈的总结:
1. 一个线程对应一个栈,main方法就是一个线程。 栈空间是私有的,不能被其他线程访问
2. 栈的数据结构的特点: 先进后出 first input last output : filo
3. 每个方法在调用时,在栈里都会被分配一个栈帧空间. 栈帧用于存储该方法的所有局部变量。 方法执行完毕,栈帧消失。
public class PersonTest {
public static void main(String[] args) {
int a = 18;
int b = 10;
long c = add(a,b);
System.out.println("c = " + c);
Person2 p = new Person2("小明",18,'女');
p.showInfo();
}
public static long add(int a, int b) {
int x = a;
int y = b;
int z = x + y;
return z;
}
}
执行机制:
(2) 堆
堆是用来存储对象本身和数组的,在JVM中只有一个堆,因此,堆是被所有线程共享的。
从jdk1.7开始,字符串常量池在堆中
(3)方法区
方法区是一块所有线程共享的内存逻辑区域,在JVM中只有一个方法区,用来存储一些线程可共享的内容,它是线程安全的,多个线程同时访问方法区中同一个内容时,只能有一个线程装载该数据,其它线程只能等待。
方法区可存储的内容有:类的全路径名、类的直接超类的权全限定名、类的访问修饰符、类的类型(类或接口)、类的直接接口全限定名的有序列表、运行时常量池(字段,方法信息,静态变量,类型引用(class))等。
(4)垃圾回收机制
java的垃圾回收机制,在程序运行时,就已经跟着启动了。 会主动去处理堆里的没有被任何引用指向的对象。这样的对象都会被认为是垃圾。 并不是程序员要调用System.gc()该功能就会立马处理。
如果这样的垃圾没有被处理,会出现堆内存溢出。但是,一般情况下gc还是会尽快处理的。不会出现内存溢出。
如果我们在编程时,对象用了几次后,不再使用了,那么应该尽快将引用变量赋值为null。即将对象视为垃圾,等待gc处理。
System.gc():
7.值传递和址传递
值传递: 基本数据类型之间的赋值操作(本质是值的副本传递)。
改变形参的值,不会改变实际参数的值。
地址传递: 引用数据类型之间的变量的赋值操作(本质是地址的副本传递)
通过形参改变对象的数据,那么实际参数指向的对象被改变了。除非形参在改变对象前,指向了新对象。
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 在方法中给a赋值了,但是m()方法在执行完后栈帧消失,即没有改变a的值
System.out.println(Arrays.toString(rs)); //{'m','x','p'};
System.out.println(Arrays.toString(chs)); //{'m','x','p'};
}
public static char[] m(int a,char[] chs){
a = 10;
// chs = new char[3];
chs[1] ='x';//因为通过址传递,直接改变了值
return chs;
}
}
因此可见:在Java中所有的参数传递,不管基本类型还是引用类型,都是值传递,或者说是副本传递。 只是在传递过程中:
如果是对基本数据类型的数据进行操作,由于原始内容和副本都是存储实际值,并且是在不同的栈区,因此形参的操作,不影响原始内容。
如果是对引用类型的数据进行操作,分两种情况,一种是形参和实参保持指向同一个对象地址,则形参的操作,会影响实参指向的对象的内容。一种是形参被改动指向新的对象地址(如重新赋值引用),则形参的操作,不会影响实参指向的对象的内容。
形参被改动指向新的对象地址:原始的值没有被改变
8.析构方法
析构方法,是对象的生命周期中最后的一个方法。执行时机是当这个对象被销毁之前。执行了这个方法 之后,空间就会被销毁,这个对象也就不存在了。
@Override protected void finalize() throws Throwable { super.finalize(); }
另外finalize()是一个被protected关键词修饰的方法,可以确保该方法不会被该类以外的代码调用。在每个Java类中都有finalize()方法,我们可以复写当前类中的finalize()方法
finalize()析构方法负责回收Java对象所占用的内存,该方法一般是在对象被垃圾收集器回收之前调用。通常我们会在finalize()方法中,指定对象销毁时要执行的操作,比如关闭对象打开的文件、IO流、释放内存资源等清理垃圾碎片的工作。
finalize()析构方法具有以下这些特点:
-
垃圾回收器是否会执行finalize方法,以及何时执行该方法,是不确定的;
-
finalize()方法有可能会使对象复活,恢复到可触及的状态;
-
垃圾回收器执行finalize()方法时,如果出现异常,垃圾回收器不会报告异常,程序会继续正常运行。
即使我们调用了finalize()方法,JVM也不一定就会立刻执行垃圾回收操作,这个取决于当前系统的内存占用情况。
代码演示:
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) throws Exception{
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();//对象c1被销毁
Thread.sleep(2000);
System.out.println(c2.getCount()); //1
}
}
因为finalize()方法具有不确定性,也就是说,该方法到底会不会起作用是不一定的!所以我们在程序中可以调用 System.gc()方法或者 Runtime.gc()方法,来显式地提醒垃圾回收器尽快去执行垃圾回收操作。执行结果如下: