面向对象简介
Java语言的最大的特点在于面向对象的编程设计,并且面向对象的编程设计也在由于Java自身的发展而不断发展。同时很多起初不支持面向对象的编程也都开始转向了面向对象,但是依然有许多的开发者认为面向过程会比较好,或者说使用函数式编程比较好。
最早并且一直到现在流行的编程语言C、C++、Java。其中C语言已经变为了面向过程开发的代表,而像C++或者是Java都是面向对象的编程语言。
所谓的面向过程指的是面对于一个问题的解决方案,更多的情况下是不会做出重用的设计思考的,而面向对象的主要的设计形式是模块化设计,并且可以进行重用配置。在整个的面向对象的设计里面更多情况下考虑的是标准。在使用的时候根据标准进行拼装。而对于面向对象设计有三个主要的特征:
封装性:内部的操作对外部而言不可见,当内部的操作都不可直接使用的时候才是安全的;
继承性:在已有结构的基础上继续进行功能的扩充;
多态性:是在继承性的基础上扩充而来的概念,指的是类型的转换处理。
在进行面向对象程序的开发之中一般还有三个步骤:
OOA:面向对象分析;
OOD:面向对象设计;
OOP:面向对象编程。
正常情况下是按从上到下的顺序,但是现在我们反过来:编程-->设计-->分析
从编程中我们要知道:面向对象能干什么,结构是什么
而后由程序再回到设计,再回到分析
生活中的场景基本都可以进行“面向对象的分析”
类与对象简介
面向对象是一个非常庞大的话题,但是任何庞大的话题都有其核心的组成:类与对象。类是对某一类事物的共性的抽象概念,而对象描述的是一个具体的产物。例如:你一眼就可以区分出我和别人。
我和其他人都一样,都是一个个具体可以使用的个体产物。
但是这些个体都有一些共性的标志:中国人。
但是人和人是不同的,所谓的人和人之间的不同依靠的是我们各自的属性,
每一个属性的集合就构成了一个对象,但是所有的属性都应该是群体的定义,
而群体的定义就形成了一个类。
类是一个模板,而对象才是类可以使用的实例,先有类再有对象。
在类之中一般都会有两个组成:
成员属性(Field):有些时候为了简化称其为属性;
例如:一个人的年龄、姓名都是不同的,所以这些对于整体来讲就称为属性;
操作方法(Method):定义对象具有的处理行为;
例如:这个人可以唱歌、跳舞、游泳、运动;
类是对象产生的依据,对象是类的具体使用。对象能使用的范畴不能超过类的定义。类中有的方法才是对象可以使用的方法,类中有的属性才是对象可以进行数据保存的位置。先有类再有对象。
类与对象的定义及使用
在Java之中类是一个独立的结构体,所以需要使用class来进行定义,而在类之中主要由属性和方法所组成,那么属性就是一个个具体的变量,方法就是可以重复执行的代码。
范例:定义一个类
class Person { // 定义一个类
String name ; // 人员姓名
int age ; // 人的年龄
public void tell() {
System.out.println("姓名:" + name + "、年龄:" + age) ;
}
}
分析:
属性没有设置默认值
String name ; String是引用数据类型,默认值是空值
int age ; int整型,默认值是0
以前的方法是主方法,并且在主类中直接调用:pulic static void ...
现在的方法是通过对象来进行调用:public void 没有加 static
这是二者的区分
在上面这个类之中定义有两个属性(name、age)和一个方法(tell()),现在有了类之后,如果要想使用类,那么就必须通过对象来完成,而如果要产生对象,那么就必须使用如下的语法格式来完成:
语法1:
声明并实例化对象:类名称 对象名称 = new 类名称();
语法2:
分步骤完成:
声明对象:类名称 对象名称 = null; 强烈建议大家这样写(=null)
实例化对象:对象名称 = new 类名称()。
当获取了实例化对象之后,那么就需要通过对象进行类中的操作调用,此时有两种调用方式:
调用类中的属性:实例化对象.成员属性;
调用类中的方法:实例化对象.方法名称()。
区分属性和方法:属性后面是没有括号的,方法后面是有括号的。
范例:使用对象操作类
一个类 class 普通类
另外一个类 public class 主类
现在是两个类在同一个文件中,讲课这样用,以后开发中,必须:一个Java文件一个class类
代码:
class Person { // 定义一个类
String name ; // 人员姓名
int age ; // 人的年龄
public void tell() {
System.out.println("姓名:" + name + "、年龄:" + age) ;
}
}
public class JavaDemo {
public static void main(String args[]) {
Person per = new Person() ; // 声明并实例化对象
per.name = "张三" ;
per.age = 18 ;
per.tell() ; // 进行方法的调用
}
}
D:\fgqjava>javac JavaDemo.java
D:\fgqjava>java JavaDemo
姓名:张三、年龄:18
如果此时程序你并没有进行对象属性内容的设置,则该数据内容为其对应数据类型的默认值。String是引用数据类型所以默认值为null,而int为基本类型,其默认值为0。代码如下:注释掉属性--per.name和per.age
class Person { // 定义一个类
String name ; // 人员姓名
int age ; // 人的年龄
public void tell() {
System.out.println("姓名:" + name + "、年龄:" + age) ;
}
}
public class JavaDemo {
public static void main(String args[]) {
Person per = new Person() ; // 声明并实例化对象
//per.name = "张三" ; // 通过对象调用属性
//per.age = 18 ;
per.tell() ; // 进行方法的调用
}
}
D:\fgqjava>javac JavaDemo.java
D:\fgqjava>java JavaDemo
姓名:null、年龄:0
对象实例化操作初步分析(对象内存分析)
对象实例化操作初步分析,最麻烦的问题就是对象实例化。
Java之中类属于引用数据类型,引用数据类型最大的困难之处在于要进行内存的管理,同时在进行操作的时候也会发生有内存关系的变化,所以本次针对于之前的程序的内存关系进行一些简单的分析。
范例:以下面的程序为主进行分析
public class JavaDemo {
public static void main(String args []) {
Person per = new Person() ; // 声明并实例化对象
per.name = "张三" ;
per.age = 18 ;
per.tell() ; // 进行方法的调用
}
}
如果要进行内存分析,那么首先给出两块最为常用的内存空间:
堆内存:保存的是对象的具体信息,在程序之中堆内存空间的开辟是通过new完成的;
例如:fgq(名字)--某个人(对象具体的信息)
栈内存:保存的是一块堆内存的地址,即:通过地址找到堆内存,而后找到对象内容,
但是为了分析简化起见,可以简单的理解为:对象名称保存在了栈内存之中。
堆栈内存:
堆内存上都应该有一个所谓的地址编号,不同的堆内存有不同的编号。
整个程序中都有一个关键字new,
这个关键字new是进行整个内存分析之中最为头疼,最为重要的关键字。
内存分析
分析代码:Person per = new Person() ;
如下图:
堆内存靠new来开辟,new拥有开辟空间的最高级别,任何情况下必须开辟。
堆内存 存储具体的数据,以Person为例,Person中的数据是naem、age;
per.name 如果没有赋值,则其值为null;name = null;
per.age 如果没有赋值,则其值为0; age = 0;
所有的堆内存都有其对应的物理的内存地址(OX0001),且必须存在;
Person在整个程序代码中定义堆的内容即:name = null; age = 0;
即:Person决定了保存的数据内容是什么。
栈内存 存的是堆内存的地址(OX0001)
在程序之中,可以简单理解为存储的是对象名称(per)
分析代码:
per.name = "张三" ;
per.age = 18 ;
最后输出的结果就是:张三、18
在之前进行分析的时候可以发现对象的实例化有两种语法,一种是之前使用的声明并实例化对象,另外一种就是分步完成,所以下面针对于分步的内存操作进行分析。
范例:定义程序代码
...
public class JavaDemo {
public static void main(String args []) {
Person per = null ; // 声明对象
per = new Person() ; // 实例化对象
per.name = "张三" ;
per.age = 18 ;
per.tell() ; // 进行方法的调用
}
}
D:\fgqjava>javac JavaDemo.java
D:\fgqjava>java JavaDemo
姓名:张三、年龄:18
对此代码进行内存分析
下面通过内存分析进行操作,如下图:
Person per = null ; 直接是null没有指向
per = new Person() ;
new开辟堆内存空间,且都是默认值,同时开辟新的空间后,都会有一个地址保存在栈内存中
接下来的2步骤就是赋值,与上面的相同
per.name = "张三" ;
per.age = 18 ;
需要特别引起注意的是,所有的对象在调用类中的属性或方法的时候必须要实例化完成后才可以执行。
范例:错误的代码
注释掉实例化对象
...
public class JavaDemo {
public static void main(String args []) {
Person per = null ; // 声明对象
//per = new Person() ; // 实例化对象
per.name = "张三" ;
per.age = 18 ;
per.tell() ; // 进行方法的调用
}
}
D:\fgqjava>javac JavaDemo.java
D:\fgqjava>java JavaDemo
Exception in thread "main" java.lang.NullPointerException
at JavaDemo.main(JavaDemo.java:13)
这个显示:JavaDemo.java:13 第13行出错
代码之中只是声明了对象,但是并没有为对象进行实例化,所以此时无法调用。而此时程序出现的 NullPointerException (空指向异常)就是没有在堆内存开辟后时所产生的问题,并且只有引用数据类型存在有此问题。
NullPointerException 会一直伴随你的开发人生,只有引用数据类型才会有 NullPointerException ,而基本类型是不存在有这些东西的。记住一点:对象必须实例化之后才可以使用,否则无法使用。
对象引用分析(引用传递分析)
类本身属于引用数据类型,既然是引用数据类型,那么就牵扯到内存的引用传递,所谓的引用传递的本质:同一块堆内存空间可以被不同的栈内存所指向,也可以更换指向。
范例:定义一个引用传递的分析程序
class Person {
String name ;
int age ;
public void tell() {
System.out.println("姓名:" + name + "、年龄:" + age) ;
}
}
public class JavaDemo {
public static void main(String args[] ) {
Person per1 = new Person() ;
per1.name = "张三" ;
per1.age = 18 ;
Person per2 = per1 ; // 引用传递,per1是Person型的,per2也是Person型的。同类型接收是可以的。
per2.age = 80 ;
per1.tell() ;
}
}
D:\fgqjava>javac JavaDemo.java
D:\fgqjava>java JavaDemo
姓名:张三、年龄:80
内存分析
例如:
int x = 10 ;
int y = x ;
所以y=10
Person per2 = per1 ; // 引用传递
per1给了per2,那么per1的地址和per2的地址是相同的,指向同一个空间:一个堆内存空间被两个栈内存所指向。
per2.age = 80 ; 把堆内存空间中age=18改为age=80,per1的内容也同时变为age=80
这个时候的引用传递是直接在主方法之中定义的,也可以通过方法实现引用传递处理。(因为方法可以接收参数的)
范例:利用方法实现引用传递处理
class Person {
String name ;
int age ;
public void tell() {
System.out.println("姓名:" + name + "、年龄:" + age) ;
}
}
public class JavaDemo {
public static void main(String args[] ) {
Person per = new Person() ;
per.name = "张三" ;
per.age = 18 ;
change(per) ; // 等价于:Person temp = per ;
per.tell() ;
}
public static void change(Person temp) {
temp.age = 80 ;
}
}
D:\fgqjava>javac JavaDemo.java
D:\fgqjava>java JavaDemo
姓名:张三、年龄:80
与之前的差别最大的地方在于,此时的程序是将Person类的实例化对象(内存地址、数值)传递到了change()方法之中,由于传递的是一个Person类型,那么change()方法接收的也是Person类型。
内存分析
如果temp要和per指向同一个空间,此时会有一个问题出现:
当方法执行完毕后,正常情况下,方法不可能一直占着它,
如果方法执行完毕了,对于程序而言就意味着:temp断开连接。
引用传递可以发生在方法上,这个时候一定要观察方法的参数类型,同时也要观察方法的执行过程。
引用传递与垃圾产生分析
经过了一系列的分析之后已经确认,所有的引用传递的本质就是一场堆内存的调系游戏。(堆内存可以被任何人所指向)
但是对于引用传递如果处理不当,那么也会造成垃圾的产生,那么本次将针对于垃圾产生原因进行简单分析。
范例:定义一个要分析的程序
class Person {
String name ;
int age ;
public void tell() {
System.out.println("姓名:" + name + "、年龄:" + age) ;
}
}
public class JavaDemo {
public static void main(String args[] ) {
Person per1 = new Person() ; // 声明并实例化对象
Person per2 = new Person() ;
per1.name = "张三" ;
per1.age = 18 ;
per2.name = "李四" ;
per2.age = 19 ;
per2 = per1 ; // 引用传递
per2.age = 80 ;
per1.tell() ; // 进行方法调用
}
}
D:\fgqjava>javac JavaDemo.java
D:\fgqjava>java JavaDemo
姓名:张三、年龄:80
此时已经明确发生了引用传递(per2 = per1 ;),并且也成功的完成了引用传递的处理操作,但是下面来观察一下其内存的分配与处理流程。
一个栈内存只能够保存有一个堆内存的地址数据,如果发生更改,
则之前的地址数据将从此栈内存中彻底消失。
int x = 10 ;
int y = 20 ;
y = x ; 此处意味着y=20就不存在了,y就等于x=10。
per2 = per1 ;
per2=per1; 此时per1的值就是per2的值,原来的per2的值就不存在了,
per2就与原来的堆内存空间断开连接,此时这个堆内存空间就没人要了,即:垃圾空间
per2.age = 80 ;
此时per2=80,改的内容就是per1的空间内容:age=18改为age=80
所谓的垃圾空间指的是没有任何栈内存指向的堆内存空间。所有的垃圾将被GC(GarbageCollector、垃圾收集器)不定期进行回收并且释放无用内存空间,但是如果垃圾过多,一定将影响到GC的处理性能,从而降低整体的程序性能,那么在实际的开发之中,对于垃圾的产生应该越少越好。