尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

【学习记录6】面向对象(上)——类和对象、内存底层、构造方法

【学习记录6】面向对象(上)——类和对象、内存底层、构造方法 一、面向对象编程(Object Oriented Programing)一神速熟悉面向对象1. 表格结构和类结构我们在现实生活中思考问题、发现问题、处理问题往往都会用“表格”作为工具。实际上“表格思维”就是一种典型的面向对象思维。以公司雇员表为例ID姓名岗位基本工资绩效工资入职日期1001高小一程序员2000009月1日1002高小二前台500009月2日1003高小三销售员3000150009月1日1004高小四财务500009月3日上面这个雇员表可以将公司所有员工信息“结构化”、“标准化”让管理者可以方便的进行统计和管理。我们也经常将表中的“列”叫做“字段”英文中统一叫做“field”。显然field定义了表的结构。我们可以通过增加新的field(列)让表中所有的行增加数据ID姓名岗位基本工资绩效工资入职日期工作地点1001高小一程序员2000009月1日北京1002高小二前台500009月2日北京1003高小三销售员3000150009月1日郑州1004高小四财务500009月3日上海面向对象编程中类对应表的结构表的field我们可以定义出“雇员类”通过上图可以看到雇员类和雇员表的结构完全一样。只不过雇员类增加了数据的类型而已。2. 表格的动作和类的方法每个公司的雇员都要有相关的动作。比如所有雇员每天的工作标准动作有参加晨会领取当天任务午休提交工作日志我们可以在雇员表中将动作信息也包含进去ID姓名岗位基本工资绩效工资入职日期工作地点晨会动作说明1001高小一程序员2000009月1日北京1. 8:30打卡1002高小二前台500009月2日北京2. 开晨会1003高小三销售员3000150009月1日郑州3. 9:00结束1004高小四财务500009月3日上海4. 喊口号:”加油”新增的列“雇员动作说明”显然是对所有的雇员都有用每个雇员都有这个动作。 在类中就是定义成方法当然我们也可以根据需求为雇员定义多个动作。比如午休、提交工作日志、领取工资等等。3.对象对应“表中的行数据”前面主要讲解的是“表结构和类的对应关系”。那么表中的数据和什么对应呢表中的一行一行的数据都在表结构的约束范围内大家的结构都是相同的。如下表ID姓名岗位基本工资绩效工资入职日期工作地点晨会动作说明1001高小一程序员2000009月1日北京1. 8:30打卡2. 开晨会3. 9:00结束4. 喊口号:”加油”1002高小二前台500009月2日北京1003高小三销售员3000150009月1日郑州1004高小四财务500009月3日上海显然每一行数据都有“姓名”、“基本工资”等“列”也都有标准的“晨会动作”。在面向对象编程中下面三句话大家记住表结构对应类结构一行数据对应一个对象表中所有数据对应这个类的所有对象因此上面的四行数据我们使用四个对象就需要这样表示假设有对应的构造方法如下代码是示意非真实代码emp1 new Employee(ID:1001, name:高小一, job:程序员, baseSalary:20000, salary2:0, hiredate:9月1日, address:北京);emp2 new Employee(ID:1002, name:高小二, job:前台, baseSalary:5000, salary2:0, hiredate:9月2日, address:北京);emp3 new Employee(ID:1003, name:高小三, job:销售员, baseSalary:3000, salary2:15000, hiredate:9月1日, address:郑州);emp4 new Employee(ID:1004, name:高小四, job:财务, baseSalary:5000, salary2:0, hiredate:9月2日, address:上海);有的人可能注意到了创建对象的时候没有传入“晨会动作”这个列是因为“晨会动作”是一个所有数据都有的标准动作没必要再重复为每个对象创建“标准晨会动作”。二面向过程和面向对象思想面向过程和面向对象的区别面向过程和面向对象都是对软件分析、设计和开发的一种思想,它指导着人们以不同的方式去分析、设计和开发软件。C语言是一种典型的面向过程语言Java是一种典型的面向对象语言。面向过程适合简单、不需要协作的事务重点关注如何执行。面向过程时我们首先思考“怎么按步骤实现”并将步骤对应成方法一步一步最终完成。 这个适合简单任务不需要过多协作的情况下。比如如何开车我们很容易就列出实现步骤但是当我们思考比较复杂的设计任务时比如“如何造车”就会发现列出1234这样的步骤是不可能的。那是因为造车太复杂需要很多协作才能完成。此时面向对象思想就应运而生了。面向对象(Oriented-Object)思想更契合人的思维模式。我们首先思考的是“怎么设计这个事物”比如思考造车我们就会先思考“车怎么设计”而不是“怎么按步骤造车的问题”。这就是思维方式的转变。比如我们用面向对象思想思考“如何设计车”天然的我们就会从“车由什么组成”开始思考。发现车由如下对象组成为了协作我们找轮胎厂完成制造轮胎的步骤发动机厂完成制造发动机的步骤这样发现大家可以同时进行车的制造最终进行组装大大提高了效率。但是具体到轮胎厂的一个流水线操作仍然是有步骤的还是离不开执行者、离不开面向过程思维因此面向对象可以帮助我们从宏观上把握、从整体上分析整个系统。但是具体到实现部分的微观操作就是一个个方法仍然需要面向过程的思路去处理。我们千万不要把面向过程和面向对象对立起来。他们是相辅相成的。面向对象离不开面向过程面向对象和面向过程思想的总结都是解决问题的思维方式都是代码组织的方式。面向过程是一种“执行者思维”解决简单问题可以使用面向过程。面向对象是一种“设计者思维”解决复杂、需要协作的问题可以使用面向对象。面向对象离不开面向过程宏观上通过面向对象进行整体设计微观上执行和处理数据仍然是面向过程。二、类对于一个类来说有三种成员属性field、方法method、构造器constructor。1.对象和类的详解类可以看做是一个模版或者图纸系统根据类的定义来造出对象。我们要造一个汽车类就是这个图纸规定了汽车的详细信息然后根据图纸将汽车造出来。类我们叫做class。对象我们叫做Object,instance(实例)。总结类可以看成一类对象的模板对象可以看成该类的一个具体实例。类是用于描述同一类型的对象的一个抽象概念类中定义了这一类对象所应具有的共同的属性、方法。2.类的属性field 成员变量属性用于定义该类或该类对象包含的数据或者说静态特征。属性作用范围是整个类体。在定义成员变量时可以对其初始化如果不对其初始化Java使用默认的值对其初始化。属性定义格式[修饰符] 属性类型 属性名 [默认值] ;3. 方法method方法用于定义该类或该类实例的行为特征和功能实现。方法是类和对象行为特征的抽象。面向对象中整个程序的基本单位是类方法是从属于类和对象的。[修饰符] 方法返回值类型 方法名(形参列表) {// n条语句}【示例1-1】编写简单的学生类public class SxtStu { //属性成员变量 int id; int age; String sname; //方法 public void study(){ System.out.println(正在学习勿扰); } public void kickball(){ System.out.println(踢球中为我加油); } }4. 构造方法构造器 constructor构造方法是负责初始化装修不是建房子。即构造器用于对象的初始化而不是创建对象声明格式[修饰符] 类名(形参列表){ //n条语句}构造器4个要点构造器通过new关键字调用构造器虽然有返回值但是不能定义返回值类型(返回值的类型肯定是本类)不能在构造器里使用return返回某个值。如果我们没有定义构造器则编译器会自动定义一个无参的构造方法。如果已定义则编译器不会自动添加构造器的方法名必须和类名一致课堂练习定义一个“点”Point类用来表示二维空间中的点有两个坐标。要求如下可以生成具有特定坐标的点对象。提供可以计算该“点”距另外一点距离的方法。public class Point { double x,y; Point(double _x,double _y){ x _x; //x是成员变量系统默认在x前加this.即this.x y _y; //y是成员变量系统默认在y前加this.即this.y } //计算该“点”距另外一点距离的方法 public double getDistance(Point p){ return Math.sqrt((x-p.x)*(x-p.x)(y-p.y)*(y-p.y)); } public static void main(String[] args){ Point p1 new Point(3.0,4.0); Point origin new Point(0.0,0.0); System.out.println(p1.getDistance(origin)); } }构造方法的重载构造方法也是方法。与普通方法一样构造方法也可以重载。【示例4-6】构造方法重载(创建不同用户对象)public class User { int id; String name; String pwd; public User(){ } public User(int id){ this.id id; //this.id为对象的局部变量 } public User(int id,String name){ this.id id; this.name name; } public User(int id,String name,String pwd){ this.id id; this.name name; this.pwd pwd; } public static void main(String[] args){ User u new User(); User u1 new User(1001,张三); User u2 new User(1002,张三,123456); } }新手雷区如果方法构造中形参名与属性名相同时需要使用this关键字区分属性与形参。this.id 表示属性idid表示形参id小tipsIDE快速生成构造器右键→单击“Generate”→点击“Constructor”→即可选择多选按Shift键→单击“OK”键老鸟建议本节课是为了让初学者更深入了解程序底层执行情况为了完整的体现内存分析流程会有些新的名词比如线程、Class对象。大家暂时可以不求甚解的了解后期学了这两个概念再回头来看我们这篇内存分析肯定收获会更大。明确目标我们是通过“分析内存”帮助理解而不是为分析而分析把问题搞复杂。三、内存底层1. 简单内存分析(帮助理解面向对象)大致流程个人总结可能不严谨类的信息都加载到方法区method area一个方法区里可以有多个类的信息。分别有定义的方法代码常量池static属性和方法。首先找到main()方法main 方法运行就生成main()的栈帧。栈里面存方法栈帧、局部变量args:null为main 函数的形参为空。一个方法对应一帧如果调用别的方法就会在上方继续开辟新的栈帧。new SxtStu()就在堆开辟一块内存地址为0x12的对象实体。将内存地址0x12赋值给s1s1:0x12。横线上面部分是属性一开始默认全都是零下面部分是方法。紫色连线表示s1 这个局部变量通过地址0x12指向堆里面真实的学生对象。比如打印s1.id通过s1:0x12找到.id:0打印s1.sname通过s1:0x12找到.sname:null。s1.id 1001;s1.sname 高淇; 给.id和.sname赋值。黄色连线表示将.sname和方法区中常量池里的高淇关联起来。橙色连线和红色连线表示study()和kickball()使用方法区中对应的study()代码和kickball()代码。方法调用就创建栈帧方法执行完毕栈帧直接销毁。【示例3-1】编写Person类并分析内存public class Person { String name; int age; public void show(){ System.out.println(name); } public static void main(String[] args){ //创建p1对象 Person p1 new Person(); p1.age 24; p1.name 张三; p1.show(); //创建p2对象 Person p2 new Person(); p2.age 35; p2.name 李四; p2.show(); Person p3 p1; Person p4 p1; p4.age 80; System.out.println(p1.age); } }参数传值机制Java中方法中所有参数都是“值传递”也就是“传递的是值的副本”。 也就是说我们得到的是“原参数的复印件而不是原件”。· 基本数据类型参数的传值传递的是值的副本。 副本改变不会影响原件。· 引用类型参数的传值传递的是值的副本。但是引用类型指的是“对象的地址”。因此副本和原参数都指向了同一个“地址”改变“副本指向地址对象的值也意味着原参数指向对象的值也发生了改变”。【示例】多个变量指向同一个对象2. JAVA虚拟机内存模型概念Java虚拟机的内存可以分为三个区域栈stack、堆heap、方法区method area。虚拟机栈简称栈的特点栈描述的是方法执行的内存模型。每个方法被调用都会创建一个栈帧存储局部变量、操作数、方法出口等JVM为每个线程创建一个栈用于存放该线程执行方法的信息实际参数、局部变量等栈属于线程私有不能实现线程间的共享栈的存储特性是“先进后出后进先出”栈是由系统自动分配速度快栈是一个连续的内存空间堆的特点堆用于存储创建好的对象和数组(数组也是对象)JVM只有一个堆被所有线程共享堆是一个不连续的内存空间分配灵活速度慢堆被所有的线程所共享在堆上的区域会被垃圾回收器做进一步划分例如新生代、老年代的划分。方法区也是堆特点方法区是JAVA虚拟机规范可以有不同的实现。i. JDK7以前是“永久代”ii. JDK7部分去除“永久代”静态变量、字符串常量池都挪到了堆内存中iii. JDK8是“元数据空间”和堆结合起来。JVM只有一个方法区被所有线程共享方法区实际也是堆只是用于存储类、常量相关的信息用来存放程序中永远是不变或唯一的内容。类信息【Class对象反射机制中会重点讲授】、静态变量、字符串常量等常量池主要存放常量如文本字符串、final常量值。3. 垃圾回收机制(Garbage Collection)Java引入了垃圾回收机制令C程序员最头疼的内存管理问题迎刃而解。Java程序员可以将更多的精力放到业务逻辑上而不是内存管理工作上大大的提高了开发效率。1垃圾回收原理内存管理Java的内存管理很大程度就是堆中对象的管理其中包括对象空间的分配和释放。i. 对象空间的分配使用new关键字创建对象即可ii. 对象空间的释放将对象赋值null即可。垃圾回收过程任何一种垃圾回收算法一般要做两件基本事情i. 发现无用的对象ii. 回收无用对象占用的内存空间。垃圾回收机制保证可以将“无用的对象”进行回收。无用的对象指的就是没有任何变量引用该对象。Java的垃圾回收器通过相关算法发现无用对象并进行清除和整理。2垃圾回收相关算法引用计数法堆中的每个对象都对应一个引用计数器当有引用指向这个对象时引用计数器加1而当指向该对象的引用失效时引用变为null引用计数器减1最后如果该对象的引用计算器的值为0时则Java垃圾回收器会认为该对象是无用对象并对其进行回收。优点是算法简单缺点是“循环引用的无用对象”无法别识别。【示例3-2】循环引用演示代码中s1和s2互相引用对方导致他们引用计数不为0但是实际已经无用但无法被识别。public class Student { String name; Student friend; public static void main(String[ ] args) { Student s1 new Student(); Student s2 new Student(); s1.friend s2; s2.friend s1; s1 null; s2 null; } }引用可达法根搜索算法程序把所有的引用关系看作一张图从一个节点GC ROOT开始寻找对应的引用节点找到这个节点以后继续寻找这个节点的引用节点当所有的引用节点寻找完毕之后剩余的节点则被认为是没有被引用到的节点即无用的节点。3通用的分代垃圾回收机制(先了解学到高级可以再看)分代垃圾回收机制是基于这样一个事实不同的对象的生命周期是不一样的。因此不同生命周期的对象可以采取不同的回收算法以便提高回收效率。我们将对象分为三种状态年轻代、年老代、永久代。同时将处于不同状态的对象放到堆中不同的区域。年轻代所有新生成的对象首先都是放在Eden区。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象对应的是Minor GC每次 Minor GC 会清理年轻代的内存算法采用效率较高的复制算法频繁的操作但是会浪费内存空间。当“年轻代”区域存放满对象后就将对象存放到年老代区域。年老代在年轻代中经历了N(默认15)次垃圾回收后仍然存活的对象就会被放到年老代中。因此可以认为年老代中存放的都是一些生命周期较长的对象。年老代对象越来越多我们就需要启动Major GC和Full GC(全量回收)来一次大扫除全面清理年轻代区域和年老代区域。永久代用于存放静态文件如Java类、方法等。持久代对垃圾回收没有显著影响。JDK7以前就是“方法区”的一种实现。JDK8以后已经没有“永久代”了使用metaspace元数据空间和堆替代。Minor GC:用于清理年轻代区域。Eden区满了就会触发一次Minor GC。清理无用对象将有用对象复制到“Survivor1”、“Survivor2”区中。Major GC用于清理年老代区域。Full GC用于清理年轻代、年老代区域。 成本较高会对系统性能产生影响。JVM调优和Full GC在对JVM调优的过程中很大一部分工作就是对于Full GC的调节。有如下原因可能导致Full GC年老代Tenured被写满永久代Perm被写满System.gc()被显式调用上一次GC之后Heap的各域分配策略动态变化……4内存泄漏指堆内存由于某种原因程序未释放造成内存浪费导致运行速度减慢甚至系统崩溃等。老鸟建议在实际开发中经常会造成系统的崩溃。如下这些操作我们应该注意这些使用场景。 请大家学完相关内容后回头过来温习下面的内容。不要求此处掌握相关细节。如下四种情况时最容易造成内存泄露的场景请大家开发时一定注意创建大量无用对象比如大量拼接字符串时使用了String而不是StringBuilder。静态集合类的使用像HashMap、Vector、List等的使用最容易出现内存泄露这些静态变量的生命周期 和应用程序一致所有的对象也不能被释放。各种连接对象IO流对象、数据库连接对象、网络连接对象未关闭IO流对象、数据库连接对象、网络连接对象等连接对象属于物理连接和硬盘或者网 络连接不使用的时候一定要关闭。监听器的使用不当释放对象时没有删除相应的监听器其他要点程序员无权调用垃圾回收器。程序员可以调用System.gc()该方法只是通知JVM并不是运行垃圾回收器。尽量少用会申请启动Full GC成本高影响系统性能。Object对象的finalize方法是Java提供给程序员用来释放对象或资源的方法但是尽量少用4. this关键字this的用法普通方法中this总是指向调用该方法的对象。构造方法中this总是指向正要初始化的对象。创建对象的四步this的其他要点this()调用重载的构造方法避免相同的初始化代码。但只能在构造方法中用并且必须位于构造方法的第一句。this不能用于static方法中。this是作为普通方法的“隐式参数”由系统传入到方法中。【示例3-3】this的用法详解public class TestThis { int a, b, c; TestThis(){ System.out.println(正在初始化对象this); } TestThis(int a, int b){ //TestThis(); //这样是无法调用构造方法的 this(); //调用无参的构造方方法并且必须位于第一行 a a; //这里都是指的局部变量而不是成员变量 this.a a; //这样就区分了成员变量和局部变量这种情况占了this使用情况大多数 this.b b; } TestThis(int a, int b, int c){ this(a,b); //调用带参的构造方法并且必须位于第一行 this.c c; } void sing(){ } void eat(){ System.out.println(当前对象this); this.sing(); //调用本类中sing();this.写不写都可以 System.out.println(你妈妈喊你回家吃饭); } public static void main(String[] args){ TestThis hi new TestThis(2,3); hi.eat(); } }5. static 关键字静态变量(类变量)、静态方法(类方法)static声明的属性或方法。静态变量/静态方法生命周期和类相同在整个程序执行期间都有效。它有如下特点为该类的公用变量属于类被该类的所有实例共享在类载入时被初始化。static变量只有一份。一般用“类名.类变量/方法”来调用。在static方法中不可直接访问非static的成员。静态初始化块构造方法用于对象的普通属性初始化。静态初始化块用于类的初始化操作初始化静态属性。在静态初始化块中不能直接访问非static成员。注意事项静态初始化块执行顺序学完继承再看这里上溯到Object类先执行Object的静态初始化块再向下执行子类的静态初始化块直到类的静态初始化块为止。构造方法执行顺序和上面顺序一样【示例3-4】static关键字的使用public class TestStatic { int id; //id String name; //账户名 static String company 北京尚学堂; //公司名称 public TestStatic(int id, String name){ this.id id; this.name name; } public void login(){ System.out.println(name); } public static void printCompany(){ //login(); //调用非静态成员编译会报错 System.out.println(company); } public static void main(String[] args){ TestStatic u new TestStatic(101,高小七); TestStatic.printCompany(); TestStatic.company 北京阿里爷爷; TestStatic.printCompany(); } }运行结果北京尚学堂北京阿里爷爷运行时的内存分配图【示例3-5】static静态初始化块public class TestStatic2 { static String company; //公司名称 //static静态初始化块 static{ System.out.println(执行类的初始化工作); company 百战程序员; printCompany(); } public static void printCompany(){ System.out.println(company); } public static void main(String[] args){ } }运行结果执行类的初始化工作百战程序员6.变量分类变量有三种类型局部变量、成员变量和静态变量。局部变量、成员变量、静态变量的核心区别类型声明位置从属于生命周期作用域局部变量方法或语句块内部方法/语句块从声明处开始到方法或语句块结束成员变量(实例变量)类内部方法外部对象对象创建成员变量也跟着创建。对象消失成员变量也跟着消失静态变量(类变量)类内部static修饰类类被加载静态变量就有效
返回列表