面向对象思想
- Java语言是一种面向对象的程序设计语言,而面向对象思想是一种程序设计思想,我们在面向对象思想的指引下,使用Java语言去设计、开发计算机程序。 这里的对象泛指现实中一切事物,每种事物都具备自己的属性和行为。面向对象思想就是在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,描述成计算机事件的设计思想。 它区别于面向过程思想,强调的是通过调用对象的行为来实现功能,而不是自己一步一步的去操作实现。
面向过程和面向对象的区别
面向过程:“面向过程”(Procedure Oriented)是一种以过程为中心的编程思想,简称 OP。“面向过程”也可称之为“面向记录”编程思想,就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。所以面向过程的编程方式关注点不在“事物”上,而是做这件事分几步,先做什么,后做什么。例如:早晨起来:起床、穿衣、洗漱、上班,只要按照这个步骤来,就能实现“一天”的功能,整个这个过程中关注的是一步一步怎么做,并没有关注“人”这个事物。
面向对象:“面向对象”(Object Oriented)是一种以对象为中心的编程思想,简称 OO。随着计算机技术的不断提高,计算机被用于解决越来越复杂的问题。一切事物皆对象,通过面向对象的方式,将现实世界的事物抽象成对象。通过面向对象的方法,更利于用人理解的方式对复杂系统进行分析、设计与编程。同时,面向对象能有效提高编程的效率,通过封装技术,可以像搭积木的一样快速开发出一个全新的系统。面向对象将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。
package demo01; import java.util.Arrays; /* 面向过程:当需要实现一个功能的时候,每一个具体的步骤都要亲力亲为,详细处理每一个细节。 面向对象:当需要实现一个功能的时候,不关心具体的步骤,而是找一个已经具有该功能的人,来帮我做事儿。 */ public class Demo01PrintArray { public static void main(String[] args) { int[] array = { 10, 20, 30, 40, 50, 60 }; // 要求打印格式为:[10, 20, 30, 40, 50] // 使用面向过程,每一个步骤细节都要亲力亲为。 System.out.print("["); for (int i = 0; i < array.length; i++) { if (i == array.length - 1) { // 如果是最后一个元素 System.out.println(array[i] + "]"); } else { // 如果不是最后一个元素 System.out.print(array[i] + ", "); } } System.out.println("=============="); // 使用面向对象 // 找一个JDK给我们提供好的Arrays类, // 其中有一个toString方法,直接就能把数组变成想要的格式的字符串 System.out.println(Arrays.toString(array)); } }
使用面向对象编程思想开发系统,在现代开发中会将面向对象贯穿整个过程,
一般包括:OOA/OOD/OOP:
- OOA:面向对象分析(Object-Oriented Analysis)
- OOD:面向对象设计(Object-Oriented Design)
- OOP:面向对象编程(Object-Oriented Programming)
面向过程和面向对象有什么关系呢?
- 面向过程其实是最为实际的一种思考方式,就算是面向对象的方法也是含有面向过程的思想。可以说面向过程是一种基础的方法。它考虑的是实际地实现。一般的面向过程是从上往下步步求精。面向对象主要是把事物给对象化,对象包括属性与行为。当程序规模不是很大时,面向过程的方法还会体现出一种优势。因为程序的流程很清楚,按着模块与函数的方法可以很好的组织。但对于复杂而庞大的系统来说,面向过程显得就很无力了。
面向对象分析方法分析问题的思路和步骤:
- 根据问题需要,选择问题所针对的现实世界中的实体。
- 从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了概念世界中的类。
- 把抽象的实体用计算机语言进行描述,形成计算机世界中类的定义。即借助某种程序 语言,把类构造成计算机能够识别和处理的数据结构。
- 将类实例化成计算机世界中的对象。对象是计算机世界中解决问题的最终工具。
面向对象的三大特征
- 封装 (Encapsulation)
- 继承 (Inheritance)
- 多态 (Polymorphism)
类和对象的概念
软件存在的意义就是为了解决现实世界当中的问题,它必然模拟现实世界,也就是说现实世界中有什么,软件中就对应有什么。面向对象编程思想中关注点是“对象”或者“事物”,那么在编程语言当中要想创建对象则必须先有类,那么类和对象分别是什么,它们的区别和联系是什么呢?
面向对象的思想概述
- 类(Class)和对象(Object)是面向对象的核心概念。
- 类是对一类事物的描述,是抽象的、概念上的定义
- 对象是实际存在的该类事物的每个个体,因而也称为实例(instance)。
- “万事万物皆对象”
什么是类
类:是一组相关属性和行为的集合。可以看成是一类事物的模板,使用事物的属性特征和行为特征来描述该类事物。类是现实世界当中具有共同特征的事物进行抽象形成的模板或概念。而对象是实际存在的个体。例如:“汽车”就是一个类(所有的汽车都有方向盘、发动机、都能形式,这是它们的共同特征),“你家的那个汽车”就是一个真实存在的对象。通过类可以创建对象,对象又被称为实例(instance),这个过程也可以称为实例化。对象1、2、3 具有共同特征,进行抽象形成了类,所以从对象到类称为抽象。
可以理解为:类 = 抽象概念的人;对象 = 实实在在的某个人
- 面向对象程序设计的重点是类的设计
- 类的设计,其实就是类的成员的设计
- 现实中的属性:对应类中的成员变量
- 现实中的行为:对应类中的成员方法
类的语法格式
创建Java自定义类
步骤:
- 定义类(考虑修饰符、类名)
- 编写类的属性(考虑修饰符、属性类型、属性名、初始化值)
- 编写类的方法(考虑修饰符、返回值类型、方法名、形参等)
package demo01; /* 定义一个类,用来模拟“学生”事物。其中就有两个组成部分: 属性(是什么): 姓名 年龄 行为(能做什么): 吃饭 睡觉 学习 对应到Java的类当中: 成员变量(属性): String name; // 姓名 int age; // 年龄 成员方法(行为): public void eat() {} // 吃饭 public void sleep() {} // 睡觉 public void study() {} // 学习 注意事项: 1. 成员变量是直接定义在类当中的,在方法外边。 2. 成员方法不要写static关键字。 */ public class Student { // 成员变量 String name; // 姓名 int age; // 姓名 // 成员方法 public void eat() { System.out.println("吃饭饭!"); } public void sleep() { System.out.println("睡觉觉!"); } public void study() { System.out.println("学习!"); } }
类的访问机制:
- 在一个类中的访问机制:类中的方法可以直接访问类中的成员变量。 (例外:static方法访问非static,编译不通过。)
- 在不同类中的访问机制:先创建要访问类的对象,再用对象访问类中 定义的成员
类的使用格式(对象的创建和使用)
什么是对象
对象:是一类事物的具体体现。对象是类的一个实例,必然具备该类事物的属性和行为。
- 对象的属性以变量形式存在,并且这里所说的变量是成员变量当中的实例变量。实例变量就是对象级别的变量,这样的变量要求必须先存在对象,通过对象才能访问。例如:“中国人”这个类,有一个属性是“身份证号”,每一个中国人的“身份证号”都是不一样的,所以身份证号必须使用一个真实存在的“中国人对象”来访问。不能使用“中国人”这个类去访问身份证号。一个类可以实例化 N 多个对象,假设通过“中国人”这个类创建了 100 个“中国人对象”,那么“身份证号”必然会有 100 个实例变量空间去存储。
- 对象的行为以方法形式表示,并且这里所说的方法是成员方法。对象调用方法时,根据对象中方法标记(地址值),去类中寻找方法信息。成员方法多个对象共用的一份。当多个对象的引用指向同一个内存空间(变量所记录的地址值是一样的)只要有任何一个对象修改了内存中的数据,随后,无论使用哪一个对象进行数据获取,都是修改后的数据。
类与对象的关系
- 类是对一类事物的描述,是抽象的。
- 对象是一类事物的实例,是具体的。
- 类是对象的模板,对象是类的实体。
通常情况下,一个类并不能直接使用,需要根据类创建一个对象,才能使用。
1.导包:也就是指出需要使用的类,在什么位置。
- 格式:import 包名称.类名称;
对于和当前类属于同一个包的情况,可以省略导包语句不写。
2. 创建对象
- 格式:类名称 对象名 = new 类名称();
3. 使用,分为两种情况:(也就是,想用谁,就用对象名点儿谁)
- 使用成员变量:对象名.成员变量名
- 使用成员方法:对象名.成员方法名(参数)
注意事项:
- 如果成员变量没有进行赋值,那么将会有一个默认值,规则和数组一样。
我们现在创建对象使用上面定义的类
package demo01; /* 注意事项: 如果成员变量没有进行赋值,那么将会有一个默认值,规则和数组一样。 */ public class Demo02Student { public static void main(String[] args) { // 1. 导包。 // 我需要使用的Student类,和我自己Demo02Student位于同一个包下,所以省略导包语句不写 // 2. 创建,格式: // 类名称 对象名 = new 类名称(); // 根据Student类,创建了一个名为stu的对象 Student stu = new Student(); // 3. 使用其中的成员变量,格式: // 对象名.成员变量名 System.out.println(stu.name); // null System.out.println(stu.age); // 0 System.out.println("============="); // 改变对象当中的成员变量数值内容 // 将右侧的字符串,赋值交给stu对象当中的name成员变量 stu.name = "赵丽颖"; stu.age = 18; System.out.println(stu.name); // 赵丽颖 System.out.println(stu.age); // 18 System.out.println("============="); // 4. 使用对象的成员方法,格式: // 对象名.成员方法名() stu.eat(); stu.sleep(); stu.study(); } }
注意事项:
- 如果创建了一个类的多个对象,对于类中定义的属性,每个对象都拥有各自的一套副本,且互不干扰。
- 对象调用方法时,根据对象中方法标记(地址值),去类中寻找方法信息。成员方法多个对象共用的一份
- 当多个对象的引用指向同一个内存空间(变量所记录的地址值是一样的)只要有任何一个对象修改了内存中的数据,随后,无论使用哪一个对象进行数据获取,都是修改后的数据。
面向对象在理解
/* * 一、设计类,其实就是设计类的成员 * * 属性 = 成员变量 = field = 域、字段 * 方法 = 成员方法 = 函数 = method * * 创建类的对象 = 类的实例化 = 实例化类 * * 二、类和对象的使用(面向对象思想落地的实现): * 1.创建类,设计类的成员 * 2.创建类的对象 * 3.通过“对象.属性”或“对象.方法”调用对象的结构 * * 三、如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static的) * 意味着:如果我们修改一个对象的属性a,则不影响另外一个对象属性a的值。 * * 四、对象的内存解析 */ //测试类 public class PersonTest { public static void main(String[] args) { //2. 创建Person类的对象 Person p1 = new Person(); //Scanner scanner = new Scanner(System.in); //调用对象的结构:属性、方法 //调用属性:“对象.属性” p1.name = "Tom"; p1.isMale = true; System.out.println(p1.name); //调用方法:“对象.方法” p1.eat(); p1.sleep(); p1.talk("Chinese"); //******************************* Person p2 = new Person(); System.out.println(p2.name);//null System.out.println(p2.isMale); //******************************* //将p1变量保存的对象地址值赋给p3,导致p1和p3指向了堆空间中的同一个对象实体。 Person p3 = p1; System.out.println(p3.name);//Tom p3.age = 10; System.out.println(p1.age);//10 } } //1.创建类,设计类的成员 class Person{ //属性 String name; int age = 1; boolean isMale; //方法 public void eat(){ System.out.println("人可以吃饭"); } public void sleep(){ System.out.println("人可以睡觉"); } public void talk(String language){ System.out.println("人可以说话,使用的是:" + language); } }
对象的内存解析
为了更好的理解上面的程序,先来看看java 虚拟机是如何管理它的内存的,请看下图:
程序计数器:
- 概念:可以看做当前线程所执行的字节码的行号指示器。
- 特点:线程私有的内存
java 虚拟机栈(重点):
- 概念:描述的是 java 方法执行的内存模型。(每个方法在执行的时候会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法从调用直至完成的过程,就对应一个栈帧从入栈到出栈的过程。)
- 特 点 : 线 程 私 有, 生 命 周期 和 线 程 相同 。 这 个 区域 会 出 现 两种 异 常 :StackOverflowError 异常: 若线 程请求 的深 度大于 虚拟 机所允 许的 深度 。OutOfMemoryError 异常:若虚拟机可以动态扩展,如果扩展是无法申请到足够的内存。
本地方法栈:
- 概念:它与虚拟机栈所发挥的作用是相似的,区别是 java 虚拟机栈为执行 java 方法服务,而本地方法栈是为本地方法服务。
- 特点:线程私有,也会抛出两类异常:StackOverflowError 和 OutOfMemoryError。
java 堆(重点):
- 概念:是被所有线程共享的一块区域,在虚拟机启动时创建。
- 特点:线程共享,存放的是对象实例(所有的对象实例和数组),GC 管理的主要区域。可以处于物理上不连续的内存空间。
方法区(重点):
- 概念:存储已被虚拟机加载的类信息、常量、静态变量,即时编译器编译后的代码等数据。
- 特点:线程共享的区域,抛出异常 OutOfMemory 异常:当方法区无法满足内存分配需求的时候。
以上所描述内容,有看得懂的,也有看不懂的,例如:线程、本地方法等,这个需要大家在学习后面内容之后,返回来再看一看,那个时候你就全部明白了。针对于目前来说,大家必须要知道 java 虚拟机有三块主要的内存空间,分别是“虚拟机栈(后面简称栈)”、“方法区”、“堆区”,方法区存储类的信息,栈中存储方法执行时的栈帧以及局部变量,堆区中主要存储 new 出来的对象,以及对象内部的实例变量。其中垃圾回收器主要针对的是堆内存,方法区中最先有数据,因为程序执行之前会先进行类加载。栈内存活动最频繁,因为方法不断的执行并结束,不断的进行压栈弹栈操作。将目前阶段需要掌握的内存空间使用一张简单的图表示出来,这个图是大家需要掌握的:
两个对象,调用同一方法内存图
使用对象类型,作为参数传递到方法中内存图
同理:使用对象类型,作为方法的返回值返回的是对象的内存地址值
成员变量与局部变量区别
变量根据定义位置的不同,我们给变量起了不同的名字。
- 在方法体外,类体内声明的变量称为成员变量。
- 在方法体内部声明的变量称为局部变量。
package demo03; /* 局部变量和成员变量 1. 定义的位置不一样【重点】 局部变量:在方法的内部 成员变量:在方法的外部,直接写在类当中 2. 作用范围不一样【重点】 局部变量:只有方法当中才可以使用,出了方法就不能再用 成员变量:整个类全都可以通用。 3. 默认值不一样【重点】 局部变量:没有默认值,如果要想使用,必须手动进行赋值 成员变量:如果没有赋值,会有默认值,规则和数组一样 4. 内存的位置不一样(了解) 局部变量:位于栈内存 成员变量:位于堆内存 5. 生命周期不一样(了解) 局部变量:随着方法进栈而诞生,随着方法出栈而消失 成员变量:随着对象创建而诞生,随着对象被垃圾回收而消失 */ public class Demo01VariableDifference { String name; // 成员变量 public void methodA() { int num = 20; // 局部变量 System.out.println(num); System.out.println(name); } public void methodB(int param) { // 方法的参数就是局部变量 // 参数在方法调用的时候,必然会被赋值的。 System.out.println(param); int age; // 局部变量 // System.out.println(age); // 没赋值不能用 // System.out.println(num); // 错误写法! System.out.println(name); } }
如下图所示:
- 成员变量:类中,方法外
- 局部变量:方法中或者方法声明上(形式参数)
- 成员变量:类中
- 局部变量:方法中
- 成员变量:有默认值
- 局部变量:没有默认值。必须先定义,赋值,最后使用
- 成员变量:堆内存
- 局部变量:栈内存
- 成员变量:随着对象的创建而存在,随着对象的消失而消失
- 局部变量:随着方法的调用而存在,随着方法的调用完毕而消失
封装
为什么需要封装?封装的作用和含义?
我们程序设计追求“高内聚,低耦合”。
- 高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合 :仅对外暴露少量的方法用于使用。
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提 高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露 的暴露出来。这就是封装性的设计思想。
原则
- 将属性隐藏起来,若需要访问某个属性,提供公共方法对其访问。
封装表现:
- 方法就是一个最基本封装体。
- 类其实也是一个封装体。
封装的基本步骤
- 使用 private 关键字来修饰成员变量。
- 对需要访问的成员变量,提供对应的一对 getXxx 方法 、 setXxx 方法。
Java中通过将数据声明为私有的(private),再提供公共的(public) 方法:getXxx()和setXxx()实现对该属性的操作,以实现下述目的:
- 隐藏一个类中不需要对外提供的实现细节;
- 使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑, 限制对属性的不合理操作;
- 便于修改,增强代码的可维护性;
代码示例
/* * 面向对象的特征一:封装与隐藏 3W:what? why? how? * 一、问题的引入: * 当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值。这里,赋值操作要受到 * 属性的数据类型和存储范围的制约。除此之外,没有其他制约条件。但是,在实际问题中,我们往往需要给属性赋值 * 加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。(比如:setLegs()) * 同时,我们需要避免用户再使用"对象.属性"的方式对属性进行赋值。则需要将属性声明为私有的(private). * -->此时,针对于属性就体现了封装性。 * * 二、封装性的体现: * 我们将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值 * * 拓展:封装性的体现:① 如上 ② 不对外暴露的私有的方法 ③ 单例模式 ... * * * 三、封装性的体现,需要权限修饰符来配合。 * 1.Java规定的4种权限(从小到大排列):private、缺省、protected 、public * 2.4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类 * 3.具体的,4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类 * 修饰类的话,只能使用:缺省、public * * 总结封装性:Java提供了4种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小。 * 描述现实生活中的人的事物 * 属性: 姓名 年龄 * 功能: 说话 * * 出现安全问题: age问题,可能出现赋值为负数的情况 * 负数不会导致程序问题,违反生活中的真实情况 * * 提高安全问题: 让外面的类,不允许直接调用我的成员变量 * 新的关键字 private 私有 属于成员修饰符,不能修饰局部变量 * 被private修饰的成员,只能在自己的本类中被使用 * * 对私有变量,提供公共的访问方式: 方法 */ public class Person { //人的姓名,成员变量 String name; //人的年龄,成员变量 private int age ; //变量age被私有,提供方法,让外面的类使用 //定义方法,对age变量进行赋值,方法名字,必须set开头 public void setAge(int a){ //对变量参数a进行范围的限制 if(a<0 || a > 200){ //如果a超过范围,手动将age赋值为20 age = 20; }else{ //如果a没有超过范围,直接对age赋值 age = a; } } //定义方法,对变量age获取值使用,方法名字get public int getAge(){ return age; } //定义人的说话功能,方法中,要求说出自己的姓名和年龄 public void speak(){ System.out.println(name+"..."+age); } }
测试类
package demo04; /* * 定义好的Person类进行测试 * 创建对象,对象调用属性和方法 */ public class PersonTest { public static void main(String[] args) { //创建Person类的对象 new Person p = new Person(); //对成员变量赋值 //p.age = -200; //对成员变量age赋值,只能调用Set方法赋值 p.setAge(50); p.name = "张三"; //调用类中方法 p.speak(); //输出成员变量age值,必须调用get方法 System.out.println(p.getAge()); } }
四种访问权限修饰符
可见,public具有最大权限。private则是最小权限。被private修饰的成员,只能在本类进行访问,针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作
- 提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰
- 提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰
编写代码时,如果没有特殊的考虑,建议这样使用权限:
- 成员变量使用 private ,隐藏细节。
- 构造方法使用 public ,方便创建对象。
- 成员方法使用 public ,方便调用方法。
小贴士:不加权限修饰符,其访问能力与default修饰符相同
封装优化之this关键字
- this 可以看做一个变量,它是一个引用,存储在 Java 虚拟机堆内存的对象内部,this 这个引用保存了当前对象的内存地址指向自身,任何一个堆内存的 java 对象都有一个 this,也就是说创建 100 个 java 对象则分别对应 100 个 this。
- this 指向“当前对象”,也可以说 this 代表“当前对象”,this 可以使用在实例方法中以及构造方法中,语法格式分别为“this.”和“this(..)”。
- this不能出现在 static的方法当中,这是为什么呢?首先static 的方法,在调用的时候是不需要创建对象的,直接采用“类名”的方式调用,也就是说static 方法执行的过程中是不需要“当前对象”参与的,所以 static 的方法中不能使用 this,因为 this 代表的就是“当前对象”。
代码举例
/* * this关键字的使用: * 1.this可以用来修饰、调用:属性、方法、构造器 * * 2.this修饰属性和方法: * this理解为:当前对象 或 当前正在创建的对象 * * 2.1 在类的方法中,我们可以使用"this.属性"或"this.方法"的方式,调用当前对象属性或方法。但是, * 通常情况下,我们都选择省略"this."。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式 * 的使用"this.变量"的方式,表明此变量是属性,而非形参。 * * 2.2 在类的构造器中,我们可以使用"this.属性"或"this.方法"的方式,调用当前正在创建的对象属性或方法。 * 但是,通常情况下,我们都选择省略"this."。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式 * 的使用"this.变量"的方式,表明此变量是属性,而非形参。 * * 3. this调用构造器 * ① 我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器 * ② 构造器中不能通过"this(形参列表)"方式调用自己 * ③ 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了"this(形参列表)" * ④ 规定:"this(形参列表)"必须声明在当前构造器的首行 * ⑤ 构造器内部,最多只能声明一个"this(形参列表)",用来调用其他的构造器 * * this: 1、this是一个关键字,全部小写。 2、this是什么,在内存方面是怎样的? 一个对象一个this。 this是一个变量,是一个引用。this保存当前对象的内存地址,指向自身。 所以,严格意义上来说,this代表的就是“当前对象” this存储在堆内存当中对象的内部。 3、this只能使用在实例方法中。谁调用这个实例方法,this就是谁。 所以this代表的是:当前对象。 4、“this.”大部分情况下是可以省略的。 5、为什么this不能使用在静态方法中?????? this代表当前对象,静态方法中不存在当前对象。 */ public class ThisDemo { public static void main(String[] args) { Customer c1 = new Customer("张三"); c1.shopping(); Customer c2 = new Customer("李四"); c2.shopping(); Customer.doSome(); } } // 顾客类 class Customer { // 属性 // 实例变量(必须采用“引用.”的方式访问) String name; //构造方法 public Customer() { } public Customer(String s) { name = s; } // 顾客购物的方法 // 实例方法 public void shopping() { // 这里的this是谁?this是当前对象。 // c1调用shopping(),this是c1 // c2调用shopping(),this是c2 //System.out.println(this.name + "正在购物!"); // this. 是可以省略的。 // this. 省略的话,还是默认访问“当前对象”的name。 System.out.println(name + "正在购物!"); } // 静态方法 public static void doSome() { // this代表的是当前对象,而静态方法的调用不需要对象。矛盾了。 // 错误: 无法从静态上下文中引用非静态 变量 this //System.out.println(this); } } class Student { // 实例变量,怎么访问?必须先new对象,通过“引用.”来访问。 String name = "zhangsan"; // 静态方法 public static void m1() { //System.out.println(name); // this代表的是当前对象。 //System.out.println(this.name); // 除非你这样 Student s = new Student(); System.out.println(s.name); } //为什么set和get方法是实例方法? public void setName(String s) { name = s; } public String getName() { return name; } // 什么时候方法定义为实例方法,什么时候定义为静态方法? // 如果方法中直接访问了实例变量,该方法必须是实例方法。 }
在实例方法中,或者构造方法中,为了区分局部变量和实例变量,这种情况下:this. 是不能省略的。
/* 1、this可以使用在实例方法中,不能使用在静态方法中。 2、this关键字大部分情况下可以省略,什么时候不能省略呢? 在实例方法中,或者构造方法中,为了区分局部变量和实例变量, 这种情况下:this. 是不能省略的。 */ public class ThisTest03{ public static void main(String[] args){ Student s = new Student(); s.setNo(111); s.setName("张三"); System.out.println("学号:" + s.getNo()); System.out.println("姓名:" + s.getName()); Student s2 = new Student(2222, "李四"); System.out.println("学号:" + s2.getNo()); System.out.println("姓名:" + s2.getName()); } } // 学生类 class Student{ //学号 private int no; //姓名 private String name; //构造方法无参 public Student(){ } // 上面的有参构造方法也增强以下可读性 public Student(int no, String name){ this.no = no; this.name = name; } public void setNo(int no){ //no是局部变量 //this.no 是指的实例变量。 this.no = no; // this. 的作用是:区分局部变量和实例变量。 } public int getNo(){ return no; //return this.no; } public void setName(String name){ this.name = name; } public String getName(){ // getName实际上获取的是“当前对象”的名字。 //return this.name; // 严格来说,这里是有一个 this. 的。只不过这个 this. 是可以省略的。 return name; } }
this 还有另外一种用法,使用在构造方法第一行(只能出现在第一行,这是规定,记住就行),通过当前构造方法调用本类当中其它的构造方法,其目的是为了代码复用。调用时的语法格式是:this(实际参数列表)
/* 1、this除了可以使用在实例方法中,还可以用在构造方法中。 2、新语法:通过当前的构造方法去调用另一个本类的构造方法,可以使用以下语法格式: this(实际参数列表); 通过一个构造方法1去调用构造方法2,可以做到代码复用。 但需要注意的是:“构造方法1”和“构造方法2” 都是在同一个类当中。 3、this() 这个语法作用是什么? 代码复用。 4、死记硬背: 对于this()的调用只能出现在构造方法的第一行。 */ public class ThisTest04{ public static void main(String[] args){ // 调用无参数构造方法 Date d1 = new Date(); d1.detail(); // 调用有参数构造方法 Date d2 = new Date(2008, 8, 8); d2.detail(); } } /* 需求: 1、定义一个日期类,可以表示年月日信息。 2、需求中要求: 如果调用无参数构造方法,默认创建的日期为:1970年1月1日。 当然,除了调用无参数构造方法之外,也可以调用有参数的构造方法来创建日期对象。 */ class Date{ // 以后写代码都要封装,属性私有化,对外提供setter and getter //年 private int year; //月 private int month; //日 private int day; // 构造方法无参 // 调用无参数构造方法,初始化的日期是固定值。 public Date(){ //错误: 对this的调用必须是构造器中的第一个语句 //System.out.println(11); this(1970, 1, 1); } // 构造方法有参数 public Date(int year, int month, int day){ this.year = year; this.month = month; this.day = day; } // 提供一个可以打印日期的方法 public void detail(){ //System.out.println(year + "年" + month + "月" + day + "日"); System.out.println(this.year + "年" + this.month + "月" + this.day + "日"); } //setter and getter public void setYear(int year){ // 设立关卡(有时间可以设立关卡) this.year = year; } public int getYear(){ return year; } public void setMonth(int month){ // 设立关卡(有时间可以设立关卡) this.month = month; } public int getMonth(){ return month; } public void setDay(int day){ // 设立关卡(有时间可以设立关卡) this.day = day; } public int getDay(){ return day; } }
总结一下this的用法
构造器(构造方法)
- 我们对封装已经有了基本的了解,接下来我们来看一个新的问题,依然以Person为例,由于Person中的属性都被private了,外界无法直接访问属性,必须对外提供相应的set和get方法。当创建人对象的时候,人对象一创建就要明确其姓名和年龄,那该怎么做呢?使用构造方法,那什么是构造方法呢?从字面上理解即为构建创造时用的方法,即就是对象创建时要执行的方法。既然是对象创建时要执行的方法,那么只要在new对象时,知道其执行的构造方法是什么,就可以在执行这个方法的时候给对象进行属性赋值。
构造方法的作用:
- 当一个对象被创建时候,构造方法用来初始化该对象,给对象的成员变量赋初始值。
注 意:
- Java语言中,每个类都至少有一个构造器
- 默认构造器的修饰符与所属类的修饰符一致
- 一旦显式定义了构造器,则系统不再提供默认构造器
- 一个类可以创建多个重载的构造器,具体调用哪个构造方法,那要看调用的时候传递的实际参数列表符合哪个构造方法了
- 父类的构造器不可被子类继承
- 构造方法名和类名一致。
- 构造方法返回值类型不需要写,写上就报错,包括 void 也不能写
- 无参数构造方法又叫做缺省构造器,或者默认构造方法。
- 一般在开发中为了方便编程,建议程序员手动的将无参数构造方法写上,因为不写无参数构造方法的时候,这个默认的构造方法很有可能就不存在了,另外也是因为无参数构造方法使用的频率较高。
代码演示
/* * 类的结构之三:构造器(或构造方法、constructor)的使用 * construct:建设、建造。 construction:CCB constructor:建设者 * * 一、构造器的作用: * 1.创建对象 * 2.初始化对象的信息 * * 二、说明: * 1.如果没有显式的定义类的构造器的话,则系统默认提供一个空参的构造器 * 2.定义构造器的格式:权限修饰符 类名(形参列表){} * 3.一个类中定义的多个构造器,彼此构成重载 * 4.一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器 * 5.一个类中,至少会有一个构造器。 */ public class PersonTest { public static void main(String[] args) { //创建类的对象:new + 构造器 Person p = new Person(); p.eat(); Person p1 = new Person("Tom"); System.out.println(p1.name); } } class Person { //属性 String name; int age; //构造器 public Person() { System.out.println("Person()....."); } public Person(String n) { name = n; } public Person(String n, int a) { name = n; age = a; } //方法 public void eat() { System.out.println("人吃饭"); } public void study() { System.out.println("人可以学习"); } }
构造方法和一般方法区别
- 构造方法在对象创建时就执行了,而且只执行一次。
- 一般方法是在对象创建后,需要使用时才被对象调用,并可以被多次调用。
注意:
- 可以在类的构造器中使用"this(形参列表)"的方式,调用本类中重载的其他的构造器!
- 明确:构造器中不能通过"this(形参列表)"的方式调用自身构造器
- 如果一个类中声明了n个构造器,则最多有 n - 1个构造器中使用了 "this(形参列表)"
- "this(形参列表)"必须声明在类的构造器的首行!
- 在类的一个构造器中,最多只能声明一个"this(形参列表)"
总结:属性赋值过程
截止到目前,我们讲到了很多位置都可以对类的属性赋值。现总结这几个位 置,并指明赋值的先后顺序。赋值的位置:
- ① 默认初始化,实例变量没有手动赋值的时候,实际上系统会默认赋值,实例变量是在构造方法执行的过程中完成初始化的,完成赋值的。
- ② 显式初始化
- ③ 构造器中初始化
- ④ 通过“对象.属性“或“对象.方法”的方式赋值
赋值的先后顺序: ① - ② - ③ - ④
标准代码——JavaBean
JavaBean 是 Java语言编写类的一种标准规范。符合 JavaBean 的类,要求类必须是具体的和公共的,并且具有无参数的构造方法,提供用来操作成员变量的 set 和 get 方法。
步骤如下图所示:
/* 成员变量 使用private修饰 构造方法 提供一个无参构造方法 提供一个带多个参数的构造方法 成员方法 提供每一个成员变量对应的setXxx()/getXxx() 提供一个显示对象信息的show() */ public class Student { //成员变量 private String name; private int age; //构造方法 public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } //成员方法 public void setName(String name) { this.name = name; } public String getName() { return name; } public void setAge(int age) { this.age = age; } public int getAge() { return age; } public void show() { System.out.println(name + "," + age); } }