第8章-面向对象基础
面向过程
- 面向过程( Procedure Oriented)的思想,主要是起源在软件发展的早期
- 面向过程的主要通过一定的步骤或过程,来完成某种任务或业务需求;这些业务需求的特点是:逻辑简单、关系清晰
- 生活场景
- 每天早起步骤:起床、穿衣、刷牙、洗脸、吃饭...
- 开车的步骤:开车门、调整座椅、系安全带、踩离合、启动、挂档、加油...
- ...
- 面向过程的关注或切入点是按步骤或过程
- 面向过程程序的特点:代码量小、简单易懂、执行效率高,但不能面对复杂业务、复用程度低、安全性低、可扩展能力差
- 典型的面向过程的编程语言有C、Fortran、Basic、Pascal 等
面向对象
- 面向对象(Object Oriented)的思想,则是在软件慢慢发展中诞生的
- 面向对象,从现实世界出发,对实际的事物进行抽象,针对抽象,再描述其特征与行为,最后组织各种对象的特征和行为,完成业务需求,更贴近实际的生活、更灵活、复用度也更高
- 生活场景
- 每天早起的事物是某个人,这个人具有姓名、年龄这种天然的特征,并具备起床、穿衣、刷牙、洗脸、吃饭...等行为;其他人也具备这些特征与行为,这是一个典型实际事物的抽象;然后,通过抽象的人的特征与行为组合,完成早起功能;更符合现实生活中的理解
- 开车的过程,参与的事物有某人和某车,人具有姓名、年龄、是否有驾照、驾龄等特征;车则具有品牌、发动机型号、门、方向盘、安全带等特征;同时人具备打开、调整、系带、踩、操作按钮等行为;车则具备门打开、方向盘转动、安全带扣上、安全带松开等行为;通过组合人和车的行为,完成开车的过程
- ...
- 面向对象的关注或切入点是实际的事物,并进行抽象,且聚焦于抽象的特征与行为
- 面向对象程序的特点:代码量相对更多、逻辑相对复杂、执行效率相对要差,但能面对更复杂的业务、程序逻辑更符合现实、复用程度高、代码安全性更高、可扩展能力更好、更容易被重构
- 典型的面向对象的编程语言有:Java、C#、Python等
面向对象主要概念
类:是对具有共同特征和行为的实际事物的抽象,一个模板,并非实际存在;如将实实在在的张三、李四等具有共特征的有血有肉的人抽象成人类
属性:类具有的共同特征;如人类都有手、脚、眼睛、姓名、性别、年龄等
方法:类具有的共同行为;如人类都能说话、吃饭、睡觉等
对象:类的实例;如上述说的张三、李四、王五等实实在在的人员
注意:类是对象的模板,对象是类的实例,一个类可以创建无数个对象
面向对象三大特征
封装,Encapsulation
继承,Inheritance
多态,Polymorphism
后面会展开单独讲解
面向对象示例
下图就是一个典型的面向对象的UML(Unified Modeling Language)类图,体现了上述的面向对象的三大特征,后面将会围绕此示例进行讲解
定义类
Java是面向对象的语言,所有的代码,都必须写在类中
关键字是class
类主要包含
- 属性,即特征,可以有0~n个属性
- 方法,即行为,可以有0~n个方法
语法规则
访问修饰符 [其他修饰符] class 类名称{
//定义属性,也叫成员变量
访问修饰符 数据类型 属性名称;
... //可定义多个属性
//定义方法
访问修饰符 [其他修饰符] 返回值数据类型 方法(参数1,参数2 ...){
//方法休
return 表达式;
}
... //可定义多个方法
}语法解释:
- class:类定义关键字
- 类名称:遵守关键字定义规则,使用大驼峰,如MyClass、OrderDetail等
- []:表示可选
- 访问修改符:包括public、protected、(default)、private,后面会讲解,暂时都使用public
- 其他修改符:包括static、abstract等,后面会讲解,暂时不使用
- 类体:接在类名称后,使用{}包括的部分
- 属性:参考定义变量中的成员变量,只是多了访问修饰符
- 方法:参考定义方法,支持普通方法重载,遵守方法重载规则
应用实例
应用实例1,定义示例中的工作人员类
package com.bjpowernode.demo.school;
/**
* 工作人员类
*/
public class Employee {
//属性,也叫成员变量
//姓名
public String name;
//性别
public char sex;
//年龄
public int age;
//密码
public String password;
//方法
/**
* 吃饭
*
* @param foodName 食物名称
*/
public void eatFood(String foodName) {
System.out.println(this.name + "吃了" + foodName + "。");
}
/**
* 吃饭,普通方法重载
* @param foodName 食物名称
* @param drinkName 饮料名称
*/
public void eatFood(String foodName,String drinkName) {
System.out.println(this.name + "吃了" + foodName + ",喝了" + drinkName + "。");
}
/**
* 睡觉
*
* @param withOthers 一起睡的人
*/
public void sleep(String[] withOthers) {
String result;
//如果withOther不为空且长度大于0,表示和别人睡觉;否则,表示一个人睡觉
if (withOthers != null && withOthers.length > 0) {
String others = "";
//循环遍历拼接睡觉人,方式1
//使用foreach循环遍历
for (String withOther : withOthers) {
//拼接睡觉人,尾部加个逗号
others = others + withOther + ",";
}
//去掉尾部多余的逗号
others = others.substring(0, others.length() - 1);
// 循环遍历拼接睡觉人,方式2,与方式1有相同效果使用了String的方法【扩展】
// others = String.join(",",withOthers);
result = this.name + "和[" + others + "]一起睡觉。";
} else {
result = this.name + "一个人睡觉。";
}
//输出
System.out.println(result);
}
/**
* 汇报工作
*/
public String report() {
String result;
System.out.println(this.name + "汇报工作中...");
//模拟汇报业务逻辑,如果性别为女,通过;否则,不通过
if (this.sex == '女') {
result = "通过";
} else {
result = "不通过";
}
System.out.println(this.name + "汇报完成。");
return result;
}
}
使用类
即生成类的对象,所以先有类,再有对象
关键字是new
遵守变量定义规则,只是类型由基本数据类型变成了自定义的类
语法规则
- 方式1,定义引用,同时赋值
//定义变量(引用),同时赋值
类名称 引用名称 = new 类名称();- 方式2,定义引用,后续单独赋值
//定义变量(引用)
类名称 引用名称;
//给变量赋值
引用名称 = new 类名称();
//修改变量值
引用名称 = new 类名称();语法解释:
- new:生成类对象关键字
- 类名称:引用名称前的类名称和new后而的类名称,两个类名称必须一致;但不一定相同,后面会讲解
- 引用名称:也叫变量,遵守标识符定义规则,使用小驼峰
应用实例
实例1,测试上述的工作人员类,设置其属性、调用其方法;注意:查看out目录,会在编译后,生成两个.class文件,分别是Employee.class和EmployeeTest.class
package com.bjpowernode.demo;
import com.bjpowernode.demo.Employee;
/**
* 学校业务测试类
*/
public class EmployeeTest {
/**
* 入口方法
*
* @param args
*/
public static void main(String[] args) {
//工作人员1
System.out.println("------------工作人员1------------");
//定义引用,并指向新建的对象
Employee zs = new Employee();
//设置属性
zs.name = "张三";
zs.sex = '男';
zs.age = 25;
zs.password = "123456";
//使用属性,输出员工信息
System.out.println("当前工作人员信息:[" + zs.name + "," + zs.sex + "," + zs.age + "]");
//使用方法
zs.eatFood("白米饭"); //吃饭
zs.sleep(new String[]{"老婆", "孩子"}); //睡觉
String zsResult = zs.report(); //汇报
System.out.println(zs.name + "的汇报结果:" + zsResult);
//工作人员2
System.out.println("------------工作人员2------------");
//定义引用
Employee ls;
//指向新建的对象
ls = new Employee();
//设置属性
ls.name = "李四";
ls.sex = '女';
ls.age = 18;
zs.password = "111111";
//使用属性,输出员工信息
System.out.println("当前工作人员信息:[" + ls.name + "," + ls.sex + "," + ls.age + "]");
ls.eatFood("面包","葡萄酒");
ls.sleep(null);
String lsResult = ls.report();
System.out.println(ls.name + "的汇报结果:" + zsResult);
}
}
【练习】
练习实例内容,完成代码编写;掌握定义类和使用类
编写程序,定义汽车类Car,要求如下:
- 具有车牌、品牌、颜色、价格、使用年限属性
- 具备显示车辆详细信息的方法,在方法中输出当前车辆对象的车牌、品牌、颜色、价格和使用年限
- 具备驾驶方法,形式参数为驾驶人,在方法中输出"驾驶人正在驾驶xx品牌炸街。";如当前对象的品牌是"老头乐",驾驶方法的输入参数为"张二爷",则输出"张二爷正在驾驶老头乐炸街。"
定义一个CarTest类,在main方法中定义类对象,给属性赋值,并调用方法
编写程序,定义图书信息类BookInformation,要求如下
- 具有编号、名称、作者、价格、页数属性
- 具备显示图书信息的方法,在方法中输出当前对象的编号、名称、作者、价格、页数
- 具备翻页方法,形式参数为要翻页的页号,其逻辑为:
- 页号为负数,提示:翻页失败,页号错误
- 页号小于页数,提示:翻页成功,成功翻到xx页
- 页号超过对象页数,提示:翻页失败,超过书箱的最大页数
定义一个BookInformationTest类,在main方法中定义类对象,给属性赋值,并调用方法
编写程序,定义学校类School,要求如下【扩展】:
- 具有编号、校名、类型(是幼儿园、小学、初中、高中、大学)、员工列表等属性;其中员工列表是上述应用实例中的Employee类型数组
- 具备输出员工信息方法,接收形式参数为性别,根据参数输出该性别的员工的详细信息
- 具备获取员工平均年龄的方法,返回平均年龄
另外,定义一个SchoolTest类,在main方法中定义类对象,给属性赋值,并调用方法
属性默认值
区别于局部变量,成员变量(属性)定义后,哪怕不赋初始值,也会有一个默认值
不同数据类型的默认值,并不相同,具体如下
不同数据类型的成员变量默认值 注意:所有非8种基本数据类型的都为引用数据类型,像Java中的类、第三方类、自定义类、数组、枚举等,默认值都为null
应用实例
应用实例1,不同数据类型的成员变量默认值
package com.bjpowernode.demo;
/**
* 不同数据类型的成员变量默认值
*/
public class DefaultDemo {
//成员变量
public byte byteVariable;
public short shortVariable;
public int intVariable;
public long longVariable;
public float floatVariable;
public double doubleVariable;
public char charVariable;
public boolean booleanVariable;
public String stringVariable;
public Student studentVariable;
/**
* 入口方法,可以放置在项目的任意类中,不影响类本身的属性与普通方法
* @param args
*/
public static void main(String[] args) {
//创建对象
DefaultDemo defaultDemo = new DefaultDemo();
//输出默认值
System.out.println("byte:"+defaultDemo.byteVariable);
System.out.println("short:"+defaultDemo.shortVariable);
System.out.println("int:"+defaultDemo.intVariable);
System.out.println("long:"+defaultDemo.longVariable);
System.out.println("float:"+defaultDemo.floatVariable);
System.out.println("double:"+defaultDemo.doubleVariable);
System.out.println("char:"+defaultDemo.charVariable);
System.out.println("boolean:"+defaultDemo.booleanVariable);
System.out.println("String:"+defaultDemo.stringVariable);
System.out.println("Student:"+defaultDemo.studentVariable);
}
}输出的内容为:
构造方法
概述
- 构造方法是一种特殊的方法
- 主要用于创建对象时调用,做一些对象必要的初始化操作,多用于给属性设置初始值;就如女娲造人,模子一样,捏人的时候都,基本都是有胳膊有腿的
- 定义类时,如果不提供自定义构造方法,Java会提供一个默认无参构造方法
- 但,如果定义了一个有参构造方法,Java就不再提供默认无参构造方法;如果还需要,可以自己定义一个
- 存在多个构造方法重载时,一般在创建对象时,要指定一个构造方法
语法规则
与普通方法类似
但,有些不一样:
- 方法名称与类名称完全相同
- 没有返回值类型
- 方法体中一般不需要return
- 不能像普通方法一样通过”对象.方法“进行多次调用,是在创建对象的时候被调用一次
语法规则
访问修饰符 [其他修饰符] 类名称([参数类型 参数名,参数类型 参数名,...]){
//方法体
}
定义构造方法
无参构造方法(也是默认构造方法)
public 类名称(){
//方法体
}有参数构造方法,可多个,多个属于构造方法重载,遵守方法重载规则
public 类名称(参数1,参数2){
//方法体
}
使用构造方法
在使用new关键字生成对象的同时,选择一个合适的构造方法
使用方法如下
//新建对象,使用上述的默认构造方法
类名称 对象引用名 = new 类名称();
//新建对象,使用上述带参构造方法
类名称 对象引用名 = new 类名称(值1,值2);应用实例
实例1,分别定义多个构造方法,使用不同构造方法创建对象,查看属性值
定义构造方法
package com.bjpowernode.demo;
/**
* 工作人员类
*/
public class Employee {
//属性,也叫成员变量
//姓名
public String name;
//性别
public char sex;
//年龄
public int age;
//密码
public String password;
//构造方法
/**
* 无参构造方法,如果不定义其他构造方法,此方法默认提供;如果定义了其他构造方法,不再默认提供,需要手写提供
* 【构造方法重载】
*/
public Employee() {
}
/**
* 全参数构造方法,多用于初始化所有属性
* 【构造方法重载】
* @param name 姓名
* @param sex 性别
* @param age 年龄
*/
public Employee(String name, char sex, int age, String password) {
this.name = name; //this.name表示当前对象的属性,name表示当前构造传递过来的局部变量
this.sex = sex;
this.age = age;
this.password = password;
}
/**
* 部分参数构造方法,多用于初始化部分属性
* @param name 姓名
*/
public Employee(String name){
this.name = name;
}
//方法
/**
* 吃饭
*
* @param foodName 食物名称
*/
public void eatFood(String foodName) {
System.out.println(this.name + "吃了" + foodName + "。");
}
/**
* 吃饭,普通方法重载
* @param foodName 食物名称
* @param drinkName 饮料名称
*/
public void eatFood(String foodName,String drinkName) {
System.out.println(this.name + "吃了" + foodName + ",喝了" + drinkName + "。");
}
/**
* 睡觉
*
* @param withOthers 一起睡的人
*/
public void sleep(String[] withOthers) {
String result;
//如果withOther不为空且长度大于0,表示和别人睡觉;否则,表示一个人睡觉
if (withOthers != null && withOthers.length > 0) {
String others = "";
//循环遍历拼接睡觉人,方式1
//使用foreach循环遍历
for (String withOther : withOthers) {
//拼接睡觉人,尾部加个逗号
others = others + withOther + ",";
}
//去掉尾部多余的逗号
others = others.substring(0, others.length() - 1);
// 循环遍历拼接睡觉人,方式2,与方式1有相同效果使用了String的方法【扩展】
// others = String.join(",",withOthers);
result = this.name + "和[" + others + "]一起睡觉。";
} else {
result = this.name + "一个人睡觉。";
}
//输出
System.out.println(result);
}
/**
* 汇报工作
*/
public String report() {
String result;
System.out.println(this.name + "汇报工作中...");
//模拟汇报业务逻辑,如果性别为女,通过;否则,不通过
if (this.sex == '女') {
result = "通过";
} else {
result = "不通过";
}
System.out.println(this.name + "汇报完成。");
return result;
}
}使用构造方法
package com.bjpowernode.demo;
public class EmployeeTest {
public static void main(String[] args) {
//使用类
//1、使用无参构造方法
System.out.println("--------------使用无参构造方法--------------");
//使用无参构造方法
Employee e1 = new Employee();
//给属性赋值
e1.name = "张三";
e1.sex = '女';
e1.age = 28;
e1.password = "123456";
//调用方法
e1.eatFood("牛排", "红酒");
String[] others = {"男人", "小孩"};
e1.sleep(others);
String e1Result = e1.report();
System.out.println(e1.name + "汇报的结果:" + e1Result);
//2、使用全参数构造方法
System.out.println("--------------使用全参数构造方法--------------");
//使用全参数构造方法
Employee e2 = new Employee("李四",'男',35,"999999");
e2.eatFood("丝瓜", "山泉水");
e2.sleep(null);
String e2Result = e2.report();
System.out.println(e2.name + "汇报的结果:" + e2Result);
//3、使用部分参数构造方法
System.out.println("--------------使用部分参数构造方法--------------");
//使用部分参数构造方法
Employee e3 = new Employee("王小二");
//给属性赋值
e3.sex = '女';
e3.age = 45;
e3.password = "111111";
//调用方法
e3.eatFood("汉堡", "可乐");
String[] e3Other = {"老公"};
e3.sleep(e3Other);
String e3Result = e3.report();
System.out.println(e1.name + "汇报的结果:" + e3Result);
}
}
【练习】
提问
下面的构造方法定义正确吗?应该如何修改?
public void ConstructorDemo(){
//方法体
}下面的代码是否正确?应该如何修改?
package com.bjpowernode.demo;
/**
* 构造方法
*/
public class ConstructorDemo {
//成员变量
public int no;
public String name;
/**
* 有参构造方法
*/
public ConstructorDemo(int no,String name){
this.no = no;
this.name = name;
}
/**
* 入口方法
* @param args
*/
public static void main(String[] args) {
//创建对象
ConstructorDemo constructorDemo = new ConstructorDemo();
}
}
练习实例内容,完成代码编写;掌握默认值、构造方法的定义和使用
重构上一个练习的Car类,添加一个无参构造方法、一个全部参数初始化构造方法;在CarTest类中使用每个构造方法进行测试
重构 上一个练习的BookInformation类,添加一个无参构造方法、一个只初始化编号和名称的构造方法、一个全部参数初始化构造方法;在BookInformationTest类中使用每个构造方法进行测试
重构上一个练习的School类,添加一个无参构造方法、一个只初始化员工列表的构造方法、一个全部参数初始化构造方法;在SchoolTest类中使用每个构造方法进行测试
对象内存分配
在程序运行过程中,JVM负责资源分配,主要分配资源的地方在栈和堆中,如下图
JVM结构 分配资源,主要针对变量、引用、对象、常量等进行,如下图
- 局部变量:分配在栈中
- 局部引用:分配在栈中
- 对象:分配在堆中
- 常量:分配在方法区(元空间)
并且,堆空间相对较大,可配置;针对堆中的资源分配,由JVM进行,并有GC(Garbage Collection,垃圾回收机制)支持
应用实例
实例1,对象空间分配
实例代码
package com.bjpowernode.demo;
/**
* 资源分配
*/
public class StudentTest {
/**
* 入口方法
* @param args
*/
public static void main(String[] args) {
//定义基本数据类型局部变量,变量本身和值都存放于栈中
int i = 10;
//定义引用数据类型局部变量,也叫局部引用,引用s1存放于栈中,new Student()生成的对象存放于堆中,并将引用s1指向这个堆中这个对象
Student s1 = new Student();
//定义引用数据类型局部变量,也收局部引用,引用s2存放于栈中
Student s2;
//new Student()生成的对象存放于堆中,并将引用s2指向这个堆中这个对象
s2=new Student();
}
}资源分配结果
注意:s1和s2赋值后面的语句是一样,但是两个完全不同的对象
空指针异常
从上面内存分配可知,8种基本数据类型是直接存储字面量
区别于基本数据类型,引用数据类型存储的是对象在堆中的地址,需要指向实际的对象;然后,通过”引用.属性“、”引用.方法“来使用指向 的对象的属性和方法
如果引用数据类型的变量没有指向实际的对象,则其值为null;此时,不能访问该变量的子属性和方法
但是,在程序中,如果只定义了引用,没有将引用指向对象,将有可能产生空指针异常(NullPointerException),也俗称NPE
这是初学者开发过程中和项目过程中常见的异常
应用实例
实例1,体验空指针异常
实例代码
package com.bjpowernode.demo;
/**
* 空指针异常实例
*/
public class NpeDemo {
//成员变量
int no;
String name;
Student student;
Student[] students;
int[] ids;
/**
* 入口方法
*
* @param args
*/
public static void main(String[] args) {
//【局部变量】
//String类型空指针异常
String myName = null;
myName.length(); //将会产生[空指针异常]
//自定义类型空指针异常
Student student = null;
student.study("数学"); //将会产生[空指针异常]
//【成员变量】
NpeDemo npeDemo = new NpeDemo();
System.out.println(npeDemo.no); //正常访问,使用默认值
npeDemo.name.length(); //将会产生[空指针异常]
npeDemo.student.study("数学"); //将会产生[空指针异常]
npeDemo.students[0].study("语言"); //将会产生[空指针异常]
System.out.println(npeDemo.ids[0]); //将会产[生空指针异常]
}
}输出结果,运行代码查看
【练习】
- 练习实例内容,熟悉空指针产生的原因与特点,并记住这个常见的异常类,熟悉通过异常栈找到问题的代码行数
对象比较
8种基本数据类型的数据之间进行比较是否相等时,直接使用比较运算符“==”,比较的是实际的值,简单、方便且效率很高
引用数据类型的对象之间进行比较时,由于对象更复杂,有很多属性和方法,直接使用”==“进行比较对象,比较的是引用指向的对象在堆中的地址是否相同,而不是对象内容本身或其中的属性或方法
应用实例
应用实例1,基本数据类型的==比较
下面的代码,输出是true还是false?
package com.bjpowernode.demo;
/**
* 基本数据类型比较
*/
public class BaseTypeCompareDemo {
public static void main(String[] args) {
//定义基本数据类型变量one
int one = 10;
//定义基本数据类型变量two
int two = 10;
//比较
System.out.println(one == two); //输出的是true还是false?
//定义基本数据类型变量first
double first = 1.234;
//定义基本数据类型变量second
double second = 1.234;
//比较
System.out.println(first == second); //输出的是true还是false?
}
}
应用实例2,对象的==比较
下面的代码,输出的是true还是false?
package com.bjpowernode.demo;
/**
* 对象使用==比较
*/
public class ObjectCompareDemo {
public static void main(String[] args) {
//定义引用s1,指向新建对象
Student s1 = new Student();
//定义引用s2,指向新建对象
Student s2 = new Student();
//使用==比较引用s1、s2
System.out.println(s1==s2); //输出的是true还是false?
}
}下面的代码,输出的是true还是false?
package com.bjpowernode.demo;
/**
* 对象使用==比较
*/
public class ObjectCompareDemo {
public static void main(String[] args) {
//定义引用s1,指向新建对象
Student s1 = new Student();
//定义引用s2,指向s1
Student s2 = s1;
//使用==比较引用s1、s2
System.out.println(s1==s2); //输出的是true还是false?
}
}
【练习】
- 练习示例内容,熟悉对象使用==比较的特点
- 两个值为null的引用使用==比较结果是true还是false?其中一个不为null呢?
字符串的==和equals对比
字符串也是引用类型,==比较也遵守上面的对象比较规则
但,使用字符串字面量并不全遵守引用数据类型对象比较规则
应用实例
应用实例1,字符串使用==比较
下面的代码,输出的是true还是false?
package com.bjpowernode.demo;
/**
* 字符串使用==比较
*/
public class StringDemo {
/**
* 入口方法
* @param args
*/
public static void main(String[] args) {
//定义s1,指向新建对象
String s1 = new String("abc");
//定义s2,指向新建对象
String s2 = new String("abc");
//使用
System.out.println(s1 == s2); //输出的是true还是false?
}
}下面的代码,输出的是true,还是false?
package com.bjpowernode.demo;
/**
* 字符串使用==比较
*/
public class StringDemo {
/**
* 入口方法
* @param args
*/
public static void main(String[] args) {
//定义s1,指向新建对象
String s1 = "abc";
//定义s2,指向新建对象
String s2 = "abc";
//使用
System.out.println(s1 == s2); //输出的是true还是false?
}
}
上面的应用实例中,只是s1、s2定义的方式不同,但使用”==“进行对象比较的结果并不相同,是因字符串常量池在做怪
字符串常量池
产生原因:在程序中对字符串的定义、使用、连接操作非常多,会产生很多内容相同的字符串对象,浪费空间、影响程序运行效率
存放位置:位于JVM堆中的一块独立区域,只存放字符串的字面量
定义方式:
//定义字符串的引用s1,指向了字符串字面量,该字面量会存储于【字符串常量池】
String s1 = "abc";原理:
- 当新定义一个字符串引用,如s1,使用某个字符串常量时,如"abc"
- 先去字符串常量池中查看是否已经有”abc"这个字符串字面量,如果有,直接返回地址赋值给引用s1
- 如果在字符串常量池中没有“abc”这个字符串字面量,则往字符串常量池添加这个字面量,然后再返回地址赋值给引用s1
字符串字面量与new String()方式使用对比:
package com.bjpowernode.demo;
/**
* 字符串字面量与new String()方式使用对比
*/
public class StringDemo {
/**
* 入口方法
*
* @param args
*/
public static void main(String[] args) {
//字符串引用s1直接使用字符串字面量赋值,字面量"abc"将存储于【字符串常量池】中
String s1 = "abc";
//同上
String s2 = "abc";
//字符串引用s3使用新建对象赋值,与普通对象定义与赋值相同;"abc"将存储于【堆】中
String s3 = new String("abc");
//同s3
String s4 = new String("abc");
//输出
System.out.println(s1 == s2); //true
System.out.println(s1 == s3); //false
System.out.println(s1 == s4); //false
System.out.println(s2 == s3); //false
System.out.println(s2 == s4); //false
System.out.println(s3 == s4); //false
}
}
equals方法
这是Object对象的一个方法
在String类型中进行了覆盖,定义了比较的逻辑,直接比较的内容
由于String是使用char[]保存的,故比较的逻辑是一个一个char进行比较
此时,就不管实际内容是存储于堆,还是字符串常量池,比较的都是实际的char[]的值,因此会相等
应用实例
应用实例1,使用equals进行比较
package com.bjpowernode.demo;
/**
* 字符串使用equals进行比较
*/
public class StringDemo {
/**
* 入口方法
*
* @param args
*/
public static void main(String[] args) {
//字符串引用s1直接使用字符串字面量赋值,字面量"abc"将存储于【字符串常量池】中
String s1 = "abc";
//同上
String s2 = "abc";
//字符串引用s3使用新建对象赋值,与普通对象定义与赋值相同;"abc"将存储于【堆】中
String s3 = new String("abc");
//同s3
String s4 = new String("abc");
//输出
System.out.println(s1.equals(s2)); //true
System.out.println(s1.equals(s3)); //true
System.out.println(s1.equals(s4)); //true
System.out.println(s2.equals(s3)); //true
System.out.println(s2.equals(s4)); //true
System.out.println(s3.equals(s4)); //true
}
}
原则:字符串String比较时,推荐使用equals方法
字符串连接的一些特点【扩展】
- 字符串连接运算中,字面量与字面量的连接结果在常量池,会在编译进进行优化
- 字符串连接运算中,只要其中有一个是变量,结果就在堆中,而不是字符串常量池中;如果有字面量与变量连接,相当于在堆中new String()定义了新的对象;如果全是变量连接,相当于使用了StringBuilder(后面会讲解)
- 连接结果调用intern()方法,则会将堆中字符串对象放入字符串常量池,并返回此对象地址
应用实例
package com.bjpowernode.demo;
/**
* 字符串连接的一些特点
*/
public class StringPoolDemo {
public static void main(String[] args) {
//特点1:字符串连接运算中,字面量与字面量的连接结果在常量池,会在编译进进行优化
System.out.println("-----------特点1-----------");
String s1 = "a" + "b" + "c"; //会直接优化成String s1="abc";
String s2 = "abc";
System.out.println(s1 == s2); //true
//特点2:字符串连接运算中,只要其中有一个是变量,结果就在堆中,而不是字符串常量池中
System.out.println("-----------特点2-----------");
String fullName = "尼古拉斯赵四";
String firstName = "尼古拉斯";
String lastName = "赵四";
String fullNameConnect1 = "尼古拉斯" + "赵四"; //会直接优化成String fullNameConnect1="尼古拉斯赵四";
String fullNameConnect2 = firstName + lastName; //相当于使用了StringBuilder
String fullNameConnect3 = firstName + "赵四"; //相当于在堆中new String()定义了新的对象
String fullNameConnect4 = "尼古拉斯" + lastName; //相当于在堆中new String()定义了新的对象
System.out.println(fullName == fullNameConnect1); //true
System.out.println(fullName == fullNameConnect2); //false
System.out.println(fullName == fullNameConnect3); //false
System.out.println(fullName == fullNameConnect4); //false
//特点3:连接结果调用intern()方法,则会将堆中字符串对象放入字符串常量池,并返回此对象地址
System.out.println("-----------特点3-----------");
String fullNameConnect2Intern = fullNameConnect2.intern();
System.out.println(fullName == fullNameConnect2Intern);
//特点4:如果是常量,则常量的连接,也会在编译期优化,【了解】
System.out.println("-----------特点4-----------");
String fullN = "尼古拉斯赵四";
final String firstN = "尼古拉斯";
final String lastN = "赵四";
String fullNConnect = firstN + lastN; //编译时会进行优化
System.out.println(fullN == fullNConnect);
}
}注意:字符串的处理和字符 串常量池在不同的JDK版本中有不同的特性
【练习】
- 练习实例内容,熟悉字符串的==和equals对比
实战和作业
- 编写程序,描述人的特征与行为
- 定义人类(Person),具有身份证号、姓名、性别、年龄、身高等属性;具备吃饭、睡觉、打豆豆等方法
- 定义人类应用类(PersonApp),测试人类,生成多个人类对象,并设置属性值、使用属性值、调用方法
- 编写程序,描述一个计算器的特征与行为
- 定义计算器类(Calculator),具有操作1、操作数2两个属性;具有加、减、乘、除四个方法,使用操作数1、操作数2完成方法逻辑
- 定义计算器测试类(CalculatorTest),利用计算器类进行进行算术运算
- 编写程序,描述一个电脑的特征与行为
- 定义主板类
- 属性:型号、厂商、价格等
- 构造方法:无参构造、全参构造
- 普通方法:加电方法(逻辑自定义)、断电方法(逻辑自定义)、输出主板所有信息方法
- 定义处理器类
- 属性:型号、厂商、价格、内核数量、频率
- 构造方法:无参构造、全参构造
- 普通方法:开始处理方法(逻辑自定义)、结束处理方法(逻辑自定义)、输出处理器所有信息方法
- 定义内存类
- 属性:型号、厂商、价格、容量、频率
- 构造方法:无参构造、全参构造
- 普通方法:开始存储方法(逻辑自定义)、释放存储方法(逻辑自定义)、输出内存所有信息方法
- 定义电脑类
- 属性:型号、名称、厂商、价格、保修周期(如3年)、主板、处理器、内存(可以是多个)
- 构造方法:无参构造、全参构造
- 普通方法:
- 开机方法:逻辑自定义,在其中会调用主板的加电方法、处理器的开始处理方法、内存的开始存储方法
- 关机方法:逻辑自定义,在其中会调用主板的断电方法、处理器的结束处理方法、内存的释放存储方法
- 显示电脑信息方法:显示电脑信息和里面的主板信息、处理器信息、内存信息
- 定义电脑测试类,组织出上述的电脑类对象,并调用开机方法、关机方法、显示电脑信息方法进行测试
- 定义主板类
- 编写程序,要求如下
- 定义丈夫类(Husband)
- 具有身份证号、姓名、妻子(Wife类型)属性
- 具备无参和有参构造方法
- 具备获取妻子姓名的普通方法
- 定义妻子类(Wife)
- 具有身份证号、姓名、丈夫(Husband类型)属性
- 具备无参和有参构造方法
- 具备获取丈夫姓名的普通方法
- 定义测试类AppTest类,
- 分别生成丈夫对象、妻子对象
- 使用丈夫对象的获取妻子姓名的普通方法获取其妻子姓名并输出
- 使用妻子对象的获取丈夫姓名的普通方法获取其丈夫姓名并输出
- 定义丈夫类(Husband)