类和对象(1) —— 类和对象的概念、类的实例化和初始化、构造方法和this关键词
目录
1. 基础概念
1.1 面向过程和面向对象
面向对象和面向过程,这两者都是生活上的概念;面向对象编程和面向过程编程是由生活概念衍生或拓展出来的编程概念。
一、面向过程
-
生活概念:
面向过程强调的是按照步骤或流程来解决问题,这与日常生活中的“按部就班”非常相似。例如,烹饪一道菜时,需要先准备食材,然后按照一定的顺序进行烹饪,最后装盘上桌。 -
编程概念:
面向过程是一种以事件为中心的编程思想,编程时把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数。
二、面向对象
-
生活概念:
面向对象强调的是将问题分解为多个相互关联的对象,并通过对象之间的交互来解决问题。这与日常生活中的“团队合作”或“分工合作”相似。例如,一个公司可以看作是由多个部门组成的对象集合,每个部门都有自己的职责和功能;我们不需要具体知道每个部门内部是如何运作,我们只需要知道每个部门能够做什么,通过部门之间的协作来实现公司的整体目标。 -
编程概念:
面向对象编程是一种以对象为基本单位的编程方式。它将数据和操作数据的函数(方法)封装在一起,通过对象间的交互来完成任务。面向对象编程适用于复杂、需要高度模块化和可扩展性的系统,如大型软件、游戏开发等。它注重整体性和对象之间的交互,通过类和对象的抽象来解决问题。
1.2 类和对象
类是多个对象所具有的相似属性和特点的抽象集合。
“类和对象”一般都是指编程上的概念(而不是生活上的概念)。前面说了,面向对象编程是一种以对象为基础单位的编程方式,而对象是依靠类的语法而得以存在的。
我们以一个例子来说明:(面向对象——洗衣服)
1. 在现代,洗衣服这个事情涉及到4个对象:人、衣服、洗衣粉、洗衣机。
(因为是面向对象,所以为了把衣服洗好,我们不需要关心衣服有多少对应多少洗衣粉、人要分几次才把衣服放完、洗衣机要滚动多少圈放多少次水…………我们只需要知道这4个对象的交互可以把衣服洗好)
2. 这4个对象并没有相似的属性和特点,所以我们需要用到4个类,以洗衣机类为例。
提取所有洗衣机实体的所具有的属性和特点:
我们可以使用下面这个“类”来描述洗衣机对象了:
洗衣机类:
属性:产品品牌,型号,产品重量,外观尺寸,颜色...
功能:定时....
该例子中的洗衣机对象:
- 属性:
- 产品品牌: 樱花
- 型号: XPB150-1505
- 产品重量: 39kg
- 外观尺寸: 850 x 500 x 1000(单位mm)
- 颜色: 白色
- ……
- 功能:
- 标准净洗
- 烘干
- 快洗
- ……
3. 类和对象是一种编程上的概念,而编程是讲究语法的,在Java中我们可以简单写成这样:
class WashMachine{
String brand = "樱花"; // 品牌
String type = "XPB150-1505"; // 型号
double weight = 30; // 重量
double length = 850; // 长
double width = 500; // 宽
double height = 1000; // 高
String color = "白色"; // 颜色
void washClothes(){ // 普通洗衣服
System.out.println("洗衣45分钟");
}
void dryClothes(){ // 脱水
System.out.println("脱水功能");
}
void quickWash(){ //快洗
System.out.println("快洗21分钟");
}
}
(这只是比较简单的写法,实际上还会用到很多访问限定符和其他的关键字)
2. 类的创建
2.1 类的基础创建格式
在java中定义类时需要用到class关键字:
class 类名{
成员变量;
成员方法;
}
注意:我们习惯是把所有的成员变量写在开头; 当然,如果成员变量的创建写在调用该成员变量的成员方法后面,语法上也是支持的。
类中包含2种成员:
- 成员变量:用于存储对象的状态。它们可以是基本数据类型(如int、float等)或引用类型(如String、数组等)。
- 成员方法:用于描述对象的行为或功能。
2.2 自动创建类 和 类的命名规则
除了自己来写类,我们也可以通过系统来帮我们创造一个无成员的类,具体步骤如下:
步骤1~3:
完成后会弹出下面这个窗口,步骤4~6:
最后会生成一个访问限定符为public的类:
【注意事项】和 类的命名规则
①. 我们自动创建类,其实是创建了一个.java文件。该类由public修饰,且该类的名字和文件的名字相同。
(观察上面的两张图,确实是这样)
②. 硬性要求:
- 一个.java文件内只允许存在一个由public修饰的类,且此类的名字必须和文件名相同;
- 如果该文件有其他类并列存在,则其他类不能被访问限定符修饰。
【所以最好的书写建议:一个文件一个类】
例如:
③. 访问限定符有public、protected、private(具体的功能在下一篇文章讲),它们不仅可以修饰类,还能修饰成员变量和成员方法。目前成员变量和方法我们都先用着public,main方法先用着public static。
④. 软性建议:类的命名采用大驼峰命名法,即每个单词的首字母都采用大写字母。
比如刚刚的洗衣机类:WashMachine。
3. 类实例化成对象 (对象的创建)
3.1 类类型
定义了一个类,就相当于在计算机中定义了一种新的类型,该类的类型就称为类类型。
例如:
public class Dog {
public String name;
public String color;
public void barks(){
System.out.println(name+":汪汪汪~");
}
public void wag(){
System.out.println(name+":摇尾巴~");
}
}
这里的Dog就是一种类类型。
类类型也是一种引用类型,用类类型创建的变量称为实例变量,实例变量存储的是地址。
3.2 什么是实例化?怎样实例化?
用类类型创建对象的过程,称为类的实例化,在java中采用new关键字,配合类名来实例化对象。
以刚刚的Dog类为例:
public class Main{
public static void main(String[] args) {
Dog dogh = new Dog(); //通过new实例化对象
dogh.name = "阿黄";
dogh.color = "黑黄色";
Dog dogs = new Dog();
dogs.name = "大白";
dogs.color = "白色";
}
}
- “对象的创建”,也叫“对象的实例化”,也可以叫做“类实例化成对象”;这几种说法都一样。
- 实例化具体是:new一个类,给出一块空间来存储一个对象,而该对象所需要的空间大小根据所属类的大小来判断(这就是为什么前面说:在面向对象编程中,对象是依靠类而存在的)。比如这里的“ Dog dogs = new Dog() ”。
- Dog类被new出来后,我们就说创建了一个Dog类的对象。
- 变量dogs被称为实例变量,引用着这个Dog类实例化后的对象。使用“ . ”来访问对象中的属性和方法。
类名后面的括号不能省略,这与后面要讲到的构造方法有关。
而后面的内容,比如“ dogs.name = "大白"; dogs.color = "白色"; ”,其实是属于对象的初始化。
3.3 实例化的意义 —— 类和对象的关系
做个比方:类相当于一个模板或蓝图,用于创建具体的对象。对象则是类的实例化结果,它是具体的实体,拥有类所定义的属性和方法。
4. 对象的初始化
我们知道,Java中对变量的初始化检查是很严格的:在main方法中变量没有初始化就使用,会编译报错。为什么在类中声明的成员变量不初始化也不会报错呢?
4.1 默认初始化
当我们new了一个对象的时候,如果没有赋值初始化,对象里面的属性(成员变量)会被赋予默认值。
例如:
public class Dog {
public String name;
public int age;
}
class Main{
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog.name);
System.out.println(dog.age);
}
}
输出结果:
默认值和数组的默认初始化是一样的:整数是0,小数是0.0,布尔型是false,引用类型都是null……
4.2 就地初始化
在声明成员变量时,就直接给出了初始值。
public class Dog {
public String name = "大白";
public int age = 4;
}
class Main{
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog.name);
System.out.println(dog.age);
}
}
其实在实例化3.2中的例子,像这种在main方法中直接对成员变量赋值的情况是很少的。因为一般一个类,所有的成员变量都是用private修饰,在其他类的main方法中就不能直接使用private修饰的成员变量了。
5. 类和对象中涉及到的生命周期和作用域(初步了解)
5.1 类、对象、实例变量
- 类:
- 生命周期:
- 从类加载开始,到程序运行结束后卸载。其中类加载发生在编译阶段。
- 作用域:
- 类的作用域受访问限定符限制,它们都属于包级的访问权限。
- 生命周期:
- 对象:
- 生命周期:
- 从对象被实例化开始(从new出来开始),到 程序结束 或 没再被实例变量引用 时被回收。
- 作用域:
- 由类实例化,所以也受访问限定符限制。
- 生命周期:
- 实例变量:
- 生命周期:
- 实例变量属于特殊的局部变量,可以引用实体对象。在main方法中被创建,到程序结束时回收。
- 作用域:
- 作用于方法体中。
- 生命周期:
5.2 成员变量 和 成员方法
成员变量和成员方法都是对象的属性和功能,所以是从对象被实例化开始存在,到对象被回收时结束。(生命周期与对象一致)
方法与函数的不同:
- 在C语言中,函数的定义必须写在所有函数的外面。而在Java中,方法的定义,必须写在类里面。
- 在C语言中,只要有头文件声明该函数的存在,在别的.c文件中就可以使用该函数。而在Java中,方法的使用还受到访问限定符的修饰,不是所有的包和类都可以使用该类下的方法。
5.3 局部变量 和 全局变量【与c不同】
局部变量
Java局部变量的定义:
在Java中,如果变量的创建或声明处于类的里面、方法的外面,则该变量是成员变量;如果变量的创建和声明处于方法的里面,则该变量是局部变量。
例如:
在Java中,局部变量不能被访问限定符修饰。
例如:
可以看到,用访问限定符public修饰局部变量,编译器会报错。
Java局部变量的生命周期与作用域,与C语言是类似的。从变量声明开始,到方法执行完毕或代码块结束时结束。定义域仅限于声明它的方法或代码块内部。
全局变量
在Java中已经不存在全局变量的概念了。因为在C语言中,全局变量的创建位于所有函数和代码块的外面;而在Java中,变量的声明至少也必须在类的里面。
不过也可以通过静态变量来时实现全局变量的功能,比如public类前提下的public static变量。静态变量的内容在下一篇博客会讲,这里简单提一下。
6. 构造方法
6.1 构造方法的概念和特性
构造方法的定义:
构造方法是一种没有返回值的成员方法。
构造方法的特性:
- 构造方法也叫构造器,是一种特殊的成员方法。
- 没有返回值类型,设置为void也不行。
- 名字必须与类名相同。
- 在整个对象的生命周期内只调用一次。
例如:
public class Person {
public String name;
public int age;
Person(){ //构造方法
name = "人";
age = 25;
}
}
class Test1{
public static void main(String[] args) {
Person person = new Person();
System.out.println(person.name);
System.out.println(person.age);
}
}
输出结果:
可以发现,此处的构造方法Person()方法是无返回值类型的。
如果构造方法的名字与类名不相同,则会报错。如果某方法与类名一致,但是有返回值,则该方法只是一个普通的成员方法,只不过名字恰好和类名相同。
构造方法也可以被访问限定符修饰,一般都是由public修饰。
6.2 构造方法的创建和使用
6.2.1 默认的构造方法
如果用户没有显式定义构造方法,编译器会生成一份默认的构造方法。
- 生成的默认构造方法一定是无参的。
- 默认构造方法的方法体一般是无内容的。(等学到继承会有点不同)
- 默认构造方法的访问修饰符与所在类的访问修饰符一致。
例1:(usage是IDEA给的提示,与代码无关)
例2:
6.2.2 无参数的构造方法
自己显式定义的没有参数的构造方法。在创建对象时,写法和无显式定义构造方法时一样,例如“Class c = new Class(); ”。
注意:一旦自己显示定义了构造方法,编译器就不会生成默认构造方法。(救急不救穷)
例如:
class Person {
public String name;
public int age;
Person(){ //显示定义构造方法
name = "person";
age = 0;
}
}
class Test2{
public static void main(String[] args) {
Person person = new Person(); //与无构造方法的写法相同
System.out.println(person.name);
System.out.println(person.age);
}
}
输出结果:
它并不等于:
这里报错是因为两个构造方法没有构造方法重载,因为两个方法的参数列表完全相同。
6.2.3 带多个参数的构造方法
显示定义了带参数的构造方法且要使用该方法时,在new阶段就要写够参数。例如“Class c = new = Class(参数1,参数2,……参数n); ”。
当然,还是救急不救穷。显示定义了构造方法,编译器就不会生成默认构造方法。
例如:
class Person {
public String name;
public int age;
Person(String s, int n){
name = s;
age = n;
}
}
class Test3{
public static void main(String[] args) {
Person person = new Person("小明",18); //补足参数列表
System.out.println(person.name);
System.out.println(person.age);
}
}
6.2.4 构造方法的重载
构造方法也是可以构成方法重载的。
因为构造方法名必须和类名相同,那么我们只需要保证参数列表不同。
至于在创建对象时使用的是哪一个构造方法?系统会根据你输入的参数个数、参数类型和参数顺序来判断所使用的构造方法。
例如:
class Person {
public String name;
public int age;
Person(){
}
Person(int n){
age = n;
}
Person(String s, int n){
name = s;
age = n;
}
}
class Test3{
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person(34);
Person p3 = new Person("小军",20);
System.out.println(p1.name + ": " + p1.age);
System.out.println(p2.name + ": " + p2.age);
System.out.println(p3.name + ": " + p3.age);
}
}
输出:
6.3 自动创建构造方法和成员方法
IDEA集成开发工具为我们提供了自动创建构造方法和成员方法的功能。
构造方法(Constructor)
详细步骤:
成员方法(Getter和Setter)
详细步骤:
创建成功后就会有get和set方法了:
6.4 构造方法的调用时期
对于一个简单的构造方法调用和对象的创建,例如:Person p = new Person("小明",18).
在程序层面只是简单的一条语句,在JVM层面需要做好多事情,下面简单介绍下:
1. 检测对象对应的类是否加载了,如果没有加载则加载
2. 为对象分配内存空间
3. 处理并发安全问题
比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突
4. 初始化所分配的空间
即:对象空间被申请好之后,对象中包含的成员已经设置好了初始值
5. 设置对象头信息(关于对象内存模型后面会介绍)
6. 调用构造方法,给对象中各个成员赋值
7. this关键字的功能
7.1 this的概念(用this指向当前对象)
先看一个日期类的例子:
public class Date {
public int year;
public int month;
public int day;
public void setDay(int year, int month, int day){
year = year;
month = month;
day = day;
}
public void printDate(){
System.out.println(year + "/" + month + "/" + day);
}
}
class Main{
public static void main(String[] args) {
// 构造三个日期类型的对象 d1 d2 d3
Date d1 = new Date();
Date d2 = new Date();
Date d3 = new Date();
// 对d1,d2,d3的日期设置
d1.setDay(2020,9,15);
d2.setDay(2020,9,16);
d3.setDay(2020,9,17);
// 打印日期中的内容
d1.printDate();
d2.printDate();
d3.printDate();
}
}
输出结果:
我们想要成员变量的year、month、day被形参初始化赋值,可得到的结果都是0,这是为什么?
因为当成员变量和局部变量重名时,使用局部变量的使用优先级会高于成员变量。
【这与C语言中,全局变量与局部变量重名时类似,局部变量的优先级也会高于全局变量】
这时候,Java提供一个this关键字解决了这个问题。还是刚刚的例子,我们用this关键字:
public class Date {
public int year;
public int month;
public int day;
public void setDay(int year, int month, int day){
this.year = year; 这里全用上了this
this.month = month;
this.day = day;
}
public void printDate(){
System.out.println(year + "/" + month + "/" + day);
}
}
class Main{
public static void main(String[] args) {
// 构造三个日期类型的对象 d1 d2 d3
Date d1 = new Date();
Date d2 = new Date();
Date d3 = new Date();
// 对d1,d2,d3的日期设置
d1.setDay(2020,9,15);
d2.setDay(2020,9,16);
d3.setDay(2020,9,17);
// 打印日期中的内容
d1.printDate();
d2.printDate();
d3.printDate();
}
}
输出结果:
为什么可以这样?
因为this可以引用当前对象。
比如有一个“ Person p = new Peron("小明") ”,对象中的构造方法有“ this.name = name ”,那么这条语句就相当于:p.name = name。
注意:this引用的是当前对象,而不是当前类。(这一点会在下一篇的static部分讲解)
7.2 用this区分成员变量和局部变量
这在刚刚的例子中就有所体现,这里补充一点:
this是“成员方法”第一个隐藏的参数,编译器会自动传递。在成员方法执行时,编译器会负责将调用成员方法 对象的引用传递给该成员方法,this负责来接收。
public class Date {
public int year;
public int month;
public int day;
public void setDay(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
}
}
这个类中的成员方法就相当于下面这个类中的成员方法:
public class Date {
public int year;
public int month;
public int day;
public void setDay(Date this, int year, int month, int day){ //隐藏的参数this
this.year = year;
this.month = month;
this.day = day;
}
}
7.3 用this调用构造方法
this调用构造方法的要求:
- 当存在多个构造方法的时候,可以用" this(参数表) "的方式在一个构造方法中调用另一个构造方法。
- this(……) 语句只能在构造方法中。(因为构造方法只能被调用一次)
- this(……) 语句必须处于构造方法的第一行。(这是硬性要求,Java语言的设计者为了保证成员变量在正确初始化之前不被使用)
- 构造方法不能递归调用。(因为构造方法只能被调用一次)
对于要求1的错误例子:只有一个构造方法的时候也调用this(…)
class Date {
public int year;
public int month;
public int day;
public Date(){
this(); //只有一个构造方法也使用this()
}
}
这个方法就相当于public Date(){ Date();}。形成了递归结构,而构造方法是不能递归调用的。
要求2的错误例子:this(…)写在了成员方法里面
要求3的错误例子:this(…)没写在第一行
要求4的错误例子:多个构造方法成环
7.4 this的类型 与 方法的链式调用
this的类型
this引用的是当前对象,对象的类型就是this的数据类型。(属于类类型)
比如刚刚的Date类,里面所有的this它的数据类型都是Date类型。
方法的链式调用
在Java中,方法的链式调用是一种常见的编程技术,它允许在一个表达式中连续调用多个方法。
链式调用的特点:
每次对象调用的成员方法,其返回值均是该对象。
例如:
class Person {
public String name;
public int age;
public Person setName(String name) { //返回类型是Person类型
this.name = name;
return this; //返回当前对象
}
public Person setAge(int age) { //返回类型是Person类型
this.age = age;
return this; //返回当前对象
}
}
class Main{
public static void main(String[] args) {
Person p = new Person().setName("小明").setAge(18); //链式调用
System.out.println(p.name+":"+p.age);
}
}
输出:
这里的调用逻辑是:先创建对象并调用构造方法,返回该对象后再调用setName方法,而setName的返回值也是该对象,再用返回值对象调用setAge方法,最终setAge方法返回对象给实例变量p。
注意:链式访问不是链式调用。链式访问是:一个方法的返回值,被作为另一个方法的参数。
假如有一个加法方法add:
System.out.println(add(add(1, 2), 3));
最里面add方法的返回值,也是外面add方法的参数;而外面add方法的返回值,同时也是println方法的参数。
本期分享完毕,感谢大家的支持Thanks♪(・ω・)ノ
原文地址:https://blog.csdn.net/2301_80030290/article/details/143646879
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!