目录
1. 面向对象---概述
1.1 啥是面向对象编程?
简单的来说:
把意思进行翻译一下:
其实在我们之前写的代码里,就已经完成了 面向对象编程 这样的操作了。
面向对象编程例子:
解析:我们在编程中,就是找了 各种各样 的 ‘东西’(对象),来帮助我们实现我们想要的功能。
运行结果:
这种找东西过来解决问题的套路,就是我们说的:面向对象编程。
1.2 为什么要面向对象编程
因为在实际现实生活中,也是一种采用面向对象的过程。
比如:
1. 我想完成煮饭这个操作,在现实生活中是不是可以用 :电饭煲 来解决问题。
2. 我想去某某地方,在现实生活中是不是可以用 :汽车 、 高铁 来解决问题。
3. 我想跟远方的父母说话,在现实生活中是不是可以用 :手机 来解决问题。
结论:
在现实生活中,想做什么事情是不是找了 一个个 东西(对象)来帮你的呢?
那么我们写程序要干什么事情,也是找 一个个东西(对象)来完成,
这样解决问题更符合我们人类的思维习惯、编程更简单、更好理解。
这就是为什么要面向对象编程的原因!
1.3 面向对象学什么?
我们之前找的 东西 全都是别人已经写好的 完成品。
而在实际情况中,不可能找到所有符合你要求的 东西。
比如你在 拧 一个螺丝,而这螺丝的型号所需要的扳手在现有的 东西 中没有,
那么这时候就需要你来完成 造扳手 这个步骤了。
那么重点学习什么?
总结一
面向对象编程:就是需要我们来 找东西 或者 设计 东西 来编程。
2 创建和设计 类
2.1 什么是 类?
可以把类理解为一个设计图纸。
我们造 房子 第一步是要干嘛? 当然是需要先 设计图纸 啊
2.2 什么是对象?
根据 类(设计图)造出来的对象(实体化)。
结论: 在Java中,必须要先 设计类,才能创建对象并使用。
类(设计图):是对象共同特征的描述
如同:车,就有 宝马、奔驰等区别。
如同:人民币 就有 100元 和 5元的区别
对象:是真实存在的具体实例
详细说明类和对象定义:
类:一组具有相同特征的对象的抽象,类中定义了所有该类对象所共同具有的属性和方法。
如同:每个有都是 眼睛、鼻子、嘴巴。这就是类定义的共同属性。
对象:某一个类的实例化,使用new关键字创建一个对象。
虽然每个人都有共同特征,但由于各自属性不同,有的人却可以帅的像彭于晏,有点却那么‘不尽人意’。
加深对类和对象的概念:
类就是一类对象的统称。
对象就是这一类具体化的一个实例。
一个 类 可以创建出 许许多多的 对象。
注释:
2.3 如何定义类
重点先学习前两个
1. 成员变量:说白了就是 变量 如:身高、体重、年龄的值。
2. 成员方法:说白了就是 我们平常写的 子方法,实现一些步骤,功能的。
如: 我们之前写的完成 数组的排序,打印人物信息等。
代码示例:
如何得到类的对象:
如何使用对象:
访问属性: 对象名 . 成员变量
访问行为:对象名 . 方法名(....参数... );(没有参数就不写)
代码示例:
运行结果:
注释: 因为我 定义类 和 使用 类 是两个.Java文件所以有两个 public class
如果是在同一个 .Java文件里面定义,只需要把 不是主类 前面的 public 去掉就可以了
注释:刚刚我们创建了一个 a1 对象,可以对里面的类型进行赋值,读写。
当然也可以创建更多的对象,彼此之间不会有影响。(类似于C语言的 结构体)
例如我们用同一个 类 ,创建两个对象。
注释:
在上面创建两个对象的代码下面加上这么一句: a1 = a2;问,会怎么样?
答:因为 a1 和 a2 里面保存的都是地址,a1 = a2;相当于就是把 a2 的地址赋值给a1。
也就是 改变了 a1 指向的位置,变成指向由a2创建出来的对象位置了。
总结二 定义类的补充说明和注意事项
1. 成员变量的完整定义格式是:修饰符 数据类型 变量名称 = 初始化值;
修饰符就是 public 如:
提醒:修饰符不写时默认是 default (下面讲封装的时候会讲解)
注释:无须在意 public 的意义,后面会讲
2. 一般无需指定初始化值,存在默认值。
(Java支持在 定义类 的时候 赋值操作 ,)
但是极其不建议这样操作:
1. 它本身就存在默认值,不需要额外去赋值。
2. 一个类的定义是 多用的,并不是专属的。
2. 类名首字母建议大写,且有意义,满足“大驼峰模式”。(从第一个单词开始首字母就大写,多个单词都是首字母大写)
如定义汽车 类: Car
如定义学生 类: Student
如定义中国汽车 类: ChinaCar
3. 一个Java文件中可以定义多个class类,
但只能一个类是public修饰,
而且public修饰的类名必须是 代码文件名。(public修饰的称为:主类)
实际开发中建议还是一个文件定义一个class类。
意思就是说:把每个模块、功能单独的放在一个.Java文件中,再由一个类似于主Java文件 来进行调用、调试。
这样做的好处是方便以后排查问题,使代码可读性变强,总之就是好。
3. 对象内存机制
这段代码在执行时,内存里面都发生了什么变化?
动图演示:时长3分钟
最终结果:
步骤一一解析:
1. 首先会加载 public class Test,在方法区中生成 Test.class文件。(需要javac完成)
2. 然后虚拟机会将class里面的main方法在栈中开辟.现在才开始执行main方法中的第一条语句
3. Car c1 = new Car(); 这行代码会触碰 Car类,因此它也会马上把 Car类 加载到 方法区中来。
现在才真正开始执行这条代码:Car a1 == 定义了一个引用类型的变量,因为它是属于局部变量,在栈区中开辟空间。(但是现在暂时里面还没装东西)
new Car() == 会在堆区中开辟一块空间 :
因为没有赋值,所以都是默认值。在堆区中类并不会保存类里面的方法,而是保存下了这个方法的引用地址,以后要调用这个方法的时候,根据地址找到所在的类里面的方法,然后再在 栈中开辟方法的空间去执行。所以说类里面的方法都是在 栈区中开辟空间去执行的。
4. 接下来这个 堆区中的 对象会把自身的 地址 赋给 C1里面保存。让C1执行这个对象的位置。
因此,C1里面保存的是 对象位置的 地址。
证明一下C1里面保存的确实是地址。
这个就是地址:
6. 接下来开始执行第二条语句: c1.name = "奔驰";
这行代码会根据 c1 里面保存的地址,找到堆区里面的对象。并对里面的
成员变量 name 进行赋值。
7. 第三条代码一样,c1.num = 16.8; 赋值语句。
8. 四、五语句代码内容都是 根据c1里面保存的地址找到对象,在找对象里面相对应的内容进行 输出打印。
9. 第六行代码 c1.start();
前面步骤一样,然后就是根据对象里面保存的 成员方法引用地址 找到 类里面的 start方法。然后把这个start方法提取到 栈区 里面来执行。
start方法在栈区里面执行的时候,由于它知道是 c1 调用它的,所以start方法里面的name
会取 c1 里面 name的值。
10. 第七行代码:c1.run() 操作跟上一个方法引用,都是根据地址去调用。取的都是c1里面保存的值。
下面的步骤全部同理可得。
温馨提示:垃圾回收
4. static 关键字
1、修饰属性
2、修饰方法
3、代码块(本课件中会介绍)
4、修饰类(后面讲内部类会讲到)
注释: 在类中, 但是方法外部定义的变量. 这样的变量我们称为 "字段" 或 "属性" 或 "成员变量"(三种称呼都可以, 一般不会严 格区分).
a) 修饰属性
Java静态属性和类相关, 和具体的实例无关.
换句话说, 同一个类的不同实例共用同一个静态属性.
示例代码:里面的 conut属性是被 static修饰过的。
class Student {
//成员属性
public int age;//年龄
public double height;//身高
public String name;//名字
public String sex;//性别
// static修饰的属性
public static int conut;
//自我介绍
public void Print(){
System.out.println("我的名字叫:" + name);
System.out.println("我的身高是:" + height);
System.out.println("我的年龄是:" + age);
System.out.println("我的性别是:" + sex);
System.out.println("我的财产有:" + conut + "元");
}
}
public class Test_4_14 {
public static void main(String[] args) {
Student c1 = new Student();
c1.height = 181;
c1.age = 23;
c1.name = "小明";
c1.sex = "男";
c1.conut = 666;
c1.Print();
System.out.println("---------------");
Student c2 = new Student();
c2.Print();
}
}
运行结果:
从这结果可以看出,在代码里我并没有给 c2 里面任何属性赋值,按理说打印出来的应该全都是默认值才对。但打印里面的conut值时却是 666,是我们当初给c1赋值的数值。
而 conut 正是被 static修饰的 属性。
结论:
1. 当一个实例变量被static关键字修饰,他就表示类的属性,该类的所有对象共享这个属性,所有对象的属性值大家都一样。
2. 被static修饰过后的属性,会影响到下一个调用这个类而创建的对象。
3. static修饰的属性在JVM方法区中存储,所有该类对象共享此属性。
4. count被static所修饰,所有类共享。且不属于对象,
注释:
static修饰的属性,直接通过类名称就可以方法,无需通过对象访问。
访问方式为:类名 . 属性。
示例:
知识考验: 在java中,能否在一个方法的内部定义一个static变量?(如果不能请解释)
答案:肯定是不能的!!!
解析:
1.由于JAVA规定,方法内定义的都是局部临时变量,且由于内存分配,会创建一个栈帧保存局部变量表、操作数栈,动态链栈等,在方法结束后,栈帧会出栈并释放掉所有局部变量。这个时候定义一个静态变量那会不会造成内存泄漏呢?会的,由于静态变量生命周期同类的对象一致。因此不能。
2.在人性化的设计中,静态变量大都用来供外界访问或类中各个方法共享。你在一个方法中定义了一个静态变量,那对于其他方法来说,前者内部是不可见的。且对于外界来说同样也是不可见的。这样便毫无意义。
————————————————
版权声明:本文为CSDN博主「jiangkun0331」的原创文章
原文链接:https://blog.csdn.net/jiangkun0331/article/details/106575702/
关于局部变量和全局变量的知识点:第八个(11条消息) 关于Java知识点补充集合_逆袭者—MA的博客-CSDN博客
b) 修饰方法
如果在任何方法上应用 static 关键字,此方法称为静态方法。(类方法,工具方法)
1. 静态方法属于类,而不属于类的对象。
2. 可以直接调用静态方法,而无需创建类的实例。
3. 静态方法可以访问静态数据成员,并可以更改静态数据成员的值。
提问:为什么主方法是个静态方法
解析:
主方法是一个程序的入口,如果主方法是个成员方法,是需要通过对象调用吧?入口都没有,从哪产生对象呢。
程序从主方法开始执行,主方法要能调用起来,静态方法,直接调用,无需产生对象。
提问:
从static的语义,静态,无需产生对象就能调用的方法
那么静态方法能否访问成员变量和成员方法?
那么成员方法能否访问静态变量和静态方法?
静态方法存在,不代表已经创建了对象,而成员变量和方法都是需要对象来调用的。
能调用成员方法了,就代表已经创建了一个对象。在没有对象的时候,静态方法和变量都可以直接调用,现在都有对象岂不是更加应该可以。
所以:
静态方法访问成员变量和成员方法(错误)
成员方法访问静态变量和静态方法(可以)
1. 静态方法 不能访问 非静态数据成员
2. 无需创建实例对象 就可以直接调用
注意事项1:
静态方法和实例无关, 而是和类相关. 因此这导致了两个情况:
1.1 静态方法不能直接使用非静态数据成员或调用非静态方法(非静态数据成员和方法都是和实例相关的).
1.2 this和super两个关键字不能在静态上下文中使用(this 是当前实例的引用, super是当前实例父类实例的引用, 也是和当前实例相关).
注意事项2:
我们曾经写的方法为了简单, 都统一加上了 static.
但实际上一个方法具体要不要带 static, 都需要是情形而定.
main 方法为 static 方法
小结:
class Person {
public int age;//实例变量 存放在对象内
public String name;//实例变量
public String sex;//实例变量
public static int count;//类变量也叫静态变量,编译时已经产生,属于类本身,且只有一份。存放在方法区
public final int SIZE = 10;//被final修饰的叫常量,也属于对象。 被final修饰,后续不可更改
public static final int COUNT = 99;//静态的常量,属于类本身,只有一份 被final修饰,后续不可更改
//实例成员函数
public void eat() {
int a = 10;//局部变量
System.out.println("eat()!");
}
//实例成员函数
public void sleep() {
System.out.println("sleep()!");
}
//静态成员函数
public static void staticTest(){
//不能访问非静态成员
//sex = "man"; error
System.out.println("StaticTest()");
}
}
public class Test_4_12 {
public static void main(String[] args){
//产生对象 实例化对象
Person person = new Person();//person为对象的引用
System.out.println(person.age);//默认值为0
System.out.println(person.name);//默认值为null
//System.out.println(person.count);//会有警告!
//正确访问方式:
System.out.println(Person.count);
System.out.println(Person.COUNT);
Person.staticTest();
//总结:所有被static所修饰的方法或者属性,全部不依赖于对象。
person.eat();
person.sleep();
}
}
观察以下代码, 分析内存布局.(需要仔细看)
重点知识点总结:
1. 被static修饰过:类属性也叫静态变量,编译时已经产生,属于类本身(不属于对象),且只有一份。存放在方法区(所以该类的对象共享此变量)
2. ;//被final修饰的叫常量,也属于对象。 被final修饰,后续不可更改
3. :所有被static所修饰的方法或者属性,全部不依赖于对象。
4. 若在类中定义了常量(定义是赋值),一般用static 和 final 共同修饰,全局变量。
常量命名的规则:所以单纯全部大写,多个单词使用下划线分割
static final String STUDENT_SCHOOL = "清华大学";
5. 用使用类属性,我们通常直接使用 类名称.属性名称 ; 不推荐使用对象来调用类属性,不规范。(因为它是归属于这个类的,而不是归属某个特定的对象,这要清楚)
6. 在静态方法中只能调用静态方法或者静态属性,static家族之间可以相互调用。不能直接调用成员方法和成员属性,必须通过对象来调用
7. 在成员方法中既可以调用成员方法,也可调用静态方法(此时都已经产生了该类对象,一定是可以访问静态域)。
我们平常调用的工具都是 static修饰的,通过类名字直接调用(重点)
类不能用static修饰,不然还怎么调用是吧?
final的补充说明:
小知识:
提问1:
问:如果main程序中没有出现Student(类名称),直接调用它类里面的静态变量可以吗?
像这样:
答案:是可以的。
原因:虽然没有new一个对象出来,但是静态变量是用类名字来访问的。在程序执行的时候,只要碰到了一个类的名字,就会在方法区中生成class字节码文件,静态变量也同样在里面,所以可以直接调用! ! !
提问2:没有创建出对象,里面保存的地址还是 null,下面这两条语句会报错吗?
答案:也是可以的。
因为per的类型是Person;per.country相当于是第一条语句等价于类名字调用。
虽然此时per里面没有保存任何对象的地址,但它的类型还在,所以可以直接调用静态变量。
举个例子:虽然你现在的孩子没出现,但他的一些隐式属性是必然可以确定的,比如说:国籍
任然是一个类的引用那么什么样情况下会报错呢?
—定使用了一个值为null的引用去访问成员属性或者成员方法了。
数据属性的内存布局:
5. 封装
面向对象一共有三大特性:封装,继承和多态
这次先来了解什么是 封装:
5.1 概念----什么叫封装?
1. > 开篇就在讨论一个问题: 软件开发的本质就是对程序复杂程度的管理. 如果一个软件代码复杂程度 太高, 那么就无法继续维护. 如何管理复杂程度? 封装就是最基本的方法.
2. 在我们写代码的时候经常会涉及两种角色: 类的实现者和类的调用者.
3. 封装的本质就是让类的调用者不必太多的了解类的实现者是如何实现类的, 只要知道如何使用类就行了.
4. 这样就降低了类使用者的学习和使用成本, 从而降低了复杂程度.
提问:
咱们之前写的程序中,Student类的name和age属性,在类的外部主方法中或者其他地方都可以直接调用这个属性,甚至可以随意修改,合理吗?
例举我们生活:对银行卡这个类来说,银行卡的卡号,余额,密码这三个属性(尤其是是密码)如果直接在银行卡上面显示出来,甚至我们可以随意修改卡号,余额等,这样根本就是不合理,不显示的。所以我们不能让别人随意的修改,就是会了保证安全性
在举例:在我们开机启动电脑的时候,其实是电脑内部多个属性间相互配合显示的,而这些属性对于我们用户来说不可见,也不需要了解(太麻烦),所以用户正在需要的是按下一个键(开机键),一个方法内部就帮我们自动调配好了,电脑就直接开机了,这才是我们想要的。(易用性)
上面说的这一切,都是需要 ‘封装’来帮忙
5.2 private实现封装
·在Java中,所谓的权限修饰符,指的是,你修饰的属性,方法,类,到底可见的范围有多大
一共有四大范围修饰符,可见范围从小到大依次为:
1. private
2 default(不要写这个关键字,啥权限也没有就是default)-包访问权限
3 protected
4. public
这里只简绍最常用的两个 private 和 public:私有的
被private修饰的属性和方法,只在当前类的内部可见,出了类的大括号{ },对外部就完全隐藏了,外部不知道有其存在。当一个属性被private封装后,外部要想使用必须通过该类提供的getter和setter方法
(getter和setter方法就是个普通的成员方法,只是命名上有规则)
公共的,公开的
被public修饰的东东,在当前程序(项目)中都是可见的,都是可以使用的。
提问:能不能用private修饰一个类?
答:private不能修饰外部类!
类定义出来是不就得产生对象,让外部使用,private修饰一个类,定义之后,外部根本都不知道,那还那么调用呢。
注释:
如果一个类的成员没有任何权限修饰,那么它门就是缺省包访问权限,用friendly来表示,注
意friendly不是Java中的关键字,这里是个人喜欢的方式用它表示而已。
关于修饰符的详细讲解:去看看这个人写的就了解了。我这里只是简单的简绍。(12条消息) java 访问修饰符不写默认是什么_【Java面试题】访问修饰符public、private、protected、default(默认不写) 区别..._凡一一的博客-CSDN博客
private/ public 这两个关键字表示 "访问权限控制" .
被 public 修饰的成员变量或者成员方法, 可以直接被类的调用者使用.
被 private 修饰的成员变量或者成员方法, 不能被类的调用者使用.
换句话说, 类的使用者根本不需要知道, 也不需要关注一个类都有哪些 private 的成员. 从而让类调用者以更低的 成本来使用类.
案例1:
直接使用 public
class Person {
public String name = "张三";
public int age = 18;
}
class Test {
public static void main(String[] args) {
Person person = new Person();
System.out.println("我叫" + person.name + ", 今年" + person.age + "岁");
}
}
// 执行结果
我叫张三, 今年18岁
1. 这样的代码导致类的使用者(main方法的代码)必须要了解 Person 类内部的实现, 才能够使用这个类. 学习成本较 高
2. 一旦类的实现者修改了代码(例如把 name 改成 myName), 那么类的使用者就需要大规模的修改自己的代码. 维成本较高.
案例2:
使用 private 封装属性, 并提供 public 方法供类的调用者使用.
class Person {
private String name = "张三";
private int age = 18;
public void show() {
System.out.println("我叫" + name + ", 今年" + age + "岁");
}
}
class Test {
public static void main(String[] args) {
Person person = new Person();
person.show();
}
}
// 执行结果
我叫张三, 今年18岁
*此时字段已经使用 private 来修饰.
类的调用者(main方法中)不能直接使用. 而需要借助 show 方法. 此时类的使 用者就不必了解 Person 类的实现细节.
*同时如果类的实现者修改了字段的名字, 类的调用者不需要做出任何修改(类的调用者根本访问不到 name, age 这样的字段).
温馨提示:
那么问题来了~~ 类的实现者万一修改了 public 方法 show 的名字, 岂不是类的调用者仍然需要大量修改代码 嘛?
这件事情确实如此, 但是一般很少会发生. 一般类的设计都要求类提供的 public 方法能比较稳定, 不应该频繁发 生大的改变. 尤其是对于一些基础库中的类, 更是如此. 每次接口的变动都要仔细考虑兼容性问题.
注意事项:
private 不光能修饰字段, 也能修饰方法
通常情况下我们会把字段设为 private 属性, 但是方法是否需要设为 public, 就需要视具体情形而定.
一般我们希 望一个类只提供 "必要的" public 方法, 而不应该是把所有的方法都无脑设为 public.
阿里的编码规范:
所谓的 getter 和 setter方法 都是我们程序员自己写的方法,目的是给别人提供可以显示或修改我们内容的某些参数。这样命名是方便大家理解,一看就知道这个方法是干嘛的,默认规则。
5.3 getter和setter方法
当我们使用 private 来修饰字段的时候, 就无法直接使用这个字段了.
强行调用会报错:
解决办法:此时如果需要获取或者修改这个 private 属性, 就需要使用 getter / setter 方法.
代码示例:
class Person {
private String name;//实例成员变量
private int age;
public void setName(String name){
//name = name;//不能这样写
this.name = name;//this引用,表示调用该方法的对象
}
public String getName(){
return name;
}
public void show(){
System.out.println("name: "+name+" age: "+age);
}
}
public static void main(String[] args) {
Person person = new Person();
person.setName("小李");
String name = person.getName();
System.out.println(name);
person.show();
}
// 运行结果
小李
name: 小李 age: 0
注意事项
getName 即为 getter 方法, 表示获取这个成员的值.
setName 即为 setter 方法, 表示设置这个成员的值
当set方法的形参名字和类中的成员属性的名字一样的时候,如果不使用this, 相当于自赋值. this 表示当前实例 的引用.
不是所有的字段都一定要提供 setter / getter 方法, 而是要根据实际情况决定提供哪种方法.
在 IDEA 中可以使用 alt + insert (或者 alt + F12) 快速生成 setter / getter 方法. 在 VSCode 中可以使用鼠标右键 菜单 -> 源代码操作 中自动生成 setter / getter 方法.
6.构造方法
6.1 基本语法
构造方法是一种特殊方法, 使用关键字new实例化新对象时会被自动调用, 用于完成初始化操作
new 执行过程
· 为对象在堆中分配内存空间
· 调用对象的构造方法,为对象的成员变量赋值。
语法规则
1.方法名称必须与类名称相同
2.构造方法没有返回值类型声明(不是void)
3.每一个类中一定至少存在一个构造方法(若没有明确定义,则系统自动生成一个无参构造)
当我们去new一个对象出来时候,构造方法会被自动调用的。
class student{
private int age;
private String name;
//构造方法块
public student() {
System.out.println("student的构造方法");
}
}
public class Test_4_15 {
public static void main(String[] args) {
student num = new student();
}
}
运行结果:
构造方法的目的还是给里面的成员变量赋值
注意事项:
如果类中没有提供任何的构造函数,那么编译器会默认生成一个不带有参数的构造函数
无参构造函数里面什么都没有。
若类中定义了构造方法,则默认的无参构造将不再生成.
构造方法支持重载. 规则和普通方法的重载一致.
代码示例:
class Person {
private String name;//实例成员变量
private int age;
private String sex;
//默认构造函数 构造对象
public Person() {
this.name = "小王";
this.age = 10;
this.sex = "男";
}
//带有3个参数的构造函数
public Person(String name,int age,String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public void show(){
System.out.println("name: "+name+" age: "+age+" sex: "+sex);
}
}
public class Test_4_12{
public static void main(String[] args) {
Person p1 = new Person();//调用不带参数的构造函数 如果程序没有提供会调用不带参数的构造函数
p1.show();
Person p2 = new Person("小丽",18,"女");//调用带有3个参数的构造函数
p2.show();
}
}
运行结果:
构造方法内存机制:(图中已经写了第一步的过程)
空间大小由类中的成员变量的属性决定。
第二步:调用对象的构造方法,为所以对象的成员变量赋值。
重点:那些成员变量的默认值,其实都是在构造方法里面进行赋值的。是在构造方法的首行,不用写,系统自动帮我们完成的,我们是看不见的。(无参的也一样)
当构造方法结束,该对象的初始化就完成了。
提醒:赋值顺序 。先是系统会自动全部初始化,然后你想重新为哪个变量重新赋值也是可以的。
提一个问题:我们能不能根据对象重新再调用一遍构造方法,让他为我们再次重新赋值。
答案:这肯定是不可以的。
解析:这是什么概念?相当于自己调用自己。
一只小鸡在破壳而出的时候,构造方法就已经赋值完成,一只小鸡的初始属性已经被定义好了。现在你说,我想让小鸡重新调用构造方法(相当于让小鸡重新回到蛋壳里面),这明显的不合理的。(也可以这么想,你觉的自己长歪了,想回娘胎里‘回炉重造’)
JVM产生对象时调用构造方法,对象实例化结束,无法在程序中手动调用构造方法再次实例化对象的!!!。
再次提问:在我们定义成员变量的时候赋值,在构造方法里面也进行赋值,那么结果倒是会是谁的值呢?
运行结果:为什么会这样呢?
解析:
成员变量的赋值就是在构造方法中进行的。
都是在第一行完成初始化的:先是把所以变量赋成了默认值 0、null这些。然后系统在看你有没有在定义成员变量的时候定义值,如果定义了,再把默认值改成你定义的值。这些步骤都是在构造方法第一行完成的操作。然后才会去执行第一行下面的语句。(默认首行就干这个事情)
所以是先把 age赋成了 0(默认值),然后根据定义把 age赋成了 10(定义值),然后我们程序又重新给age赋成了 20;
6.2 this关键字
this表示当前对象引用(注意不是当前对象). 可以借助 this 来访问对象的字段和方法.
让我们更加了解this的作用。
案例1:目的,想让shou方法打印我们输入的赋值参数。
class student{
private String name;
private int age;
private String sex;
public student(String name, int age, String sex){
//赋值
name = name;
age = age;
sex = sex;
System.out.println("student 类的有参构造");
}
public void show(){
System.out.println("名字" + name + " 年龄" + age + " 性别" + sex);
}
}
public class Test_4_15 {
public static void main(String[] args) {
student arr = new student("薛之谦", 18, "男");
arr.show();
}
}
运行结果:我们发现,构造方法确实被调用了,为什么参数没有被赋值?还都是默认值
解析:因为赋值方法里面的形参和成员变量 名称一样。
在程序设计理念中,按照就近匹配原则。编译器会找最近的相同名字的变量在哪(java,c++都一样)
这样子写就相当于形参自己等于自己。(对类中的成员变量没有任何影响) 18 = 18;男 = 男
那么有人会问,大不了把形参的名字改成不一样不就可以了。
答: 可以是可以,但这样不规范。没人知道我们是在给谁赋值。
为了打破这个就近匹配原则,使得从类中找这个同名变量呢?
java就引出了 this 关键字。
示例代码:使用 this.
class student{
private String name;
private int age;
private String sex;
public student(String name, int age, String sex){
//赋值
this.name = name;
this.age = age;
this.sex = sex;
System.out.println("student 类的有参构造");
}
public void show(){
System.out.println("名字: " + name + " 年龄: " + age + " 性别: " + sex);
}
}
public class Test_4_15 {
public static void main(String[] args) {
student arr = new student("薛之谦", 18, "男");
arr.show();
}
}
运行结果:变成了我们希望的结果
this调用类中的成员方法
问:在一个类中,一个方法里面调用另一个方法这样可以吗?
我们发现这样是可以啊,但是为什么啊!之前不是到说一个方法要由对象来调用吗?
运行结果:确实可以。
解析:其实在我们在一个方法里面调用另一个方法时,编译器自动在预编译的时候会帮我们在前面加上 this关键字。
相当于这样: 用this关键字来调用是不是更加符合逻辑呢。
this表示当前对象的引用,就算我们不写也会帮我们自动加上。
代码示例:代码这样写更就规范,更好逻辑性。
class Person {
private String name;//实例成员变量
private int age;
private String sex;
//默认构造函数 构造对象
public Person() {
//this调用构造函数
this("薛之谦", 38, "男");//必须放在第一行进行显示
//调用了下面定义的 构造方法 快速赋值。
}
//这两个构造函数之间的关系为重载。
public Person(String name,int age,String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public void show() {
System.out.println("name: "+name+" age: "+age+" sex: "+sex);
}
}
public class Test_4_12 {
public static void main(String[] args) {
Person person1 = new Person();//调用不带参数的构造函数
person1.show();
//自定义内容
Person person2 = new Person("周杰伦",42,"男");//调用带有3个参数的构造函数
person2.show();
}
}
运行结果:
我们会发现在构造函数的内部,我们可以使用this关键字,构造函数是用来构造对象的,对象还没有构造好, 我们就使用了this,那this还代表当前对象吗?当然不是,
this代表的是当前对象的引用。
补充说明: this是不能出现在静态方法中的。
this调用构造方法:
观察下面三个构造方法,重复进行了多种同样的操作。怎么可以更加方便呢?用this调用构造方法
若不同参数的构造方法之间出现了重复的调用,可以使用 this(参数) 调用其他的构造方法
注释:this() 之间是没有小数点的,直接括号调用。
this就表示在当前这个类中 调用 谁谁谁
使用 this 有几个规则:
this调用其他的构造方法必须放在当前构造方法的首行
案例:把this调用构造方法放在第二行看看。
this调用构造方法不能成"环"(这个很好理解,就是不能死循环)
案例:
3.this表示当前对象的引用->了解即可,不需要掌握写法,知道原理即可。
示例: 目的是 在类里面定义了个方法: 输出 this关键字
且我们还在main里面 输出的 引用变量
class Person {
private String name;//实例成员变量
private int age;
private String sex;
public void who(){
System.out.println(this);
}
//默认构造函数 构造对象
public Person() {
//this调用构造函数
this("薛之谦", 38, "男");//必须放在第一行进行显示
//调用了下面定义的 构造方法 快速赋值。
}
//这两个构造函数之间的关系为重载。
public Person(String name,int age,String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public void show() {
System.out.println("name: "+name+" age: "+age+" sex: "+sex);
}
}
public class Test_4_15 {
public static void main(String[] args) {
Person person1 = new Person();//调用不带参数的构造函数
person1.who();
System.out.println("person1 = " + person1);
System.out.println("------------------");
Person person2 = new Person("周杰伦",42,"男");//调用带有3个参数的构造函数
person2.who();
System.out.println("person2 = " + person2);
}
}
运行结果: 发现上面两个打印的结果是一样的
this表示当前对象的引用,我当前是通过哪个对象调用的属性或者方法this就代表谁
解析:
第一个this是由 person1 创建的对象,所以这里的 this就代表 person1,person2原理一样。
this就想是一面镜子,当前的属性或者方法是哪个对象调用的this就指代这个对象。
注释:打印this出来的是一个地址,但不能内存中真正保存的地址,java中程序员是不可能获取到某块地址的真正地址是,这个地址是由系统模拟出来的值。
7. 认识代码块
字段的初始化方式有:
1. 就地初始化
2. 使用构造方法初始化
3. 使用代码块初始化
前两种方式前面已经学习过了, 接下来我们介绍第三种方式
7.1 什么是代码块
使用 { } 定义的一段代码.
根据代码块定义的位置以及关键字,又可分为以下四种:
普通代码块
构造块
静态块
同步代码块(后续讲解多线程部分再谈)
7.2 普通代码块
普通代码块:定义在方法中的代码块
避免变量重名
//普通代码块
public class Test_4_12{
public static void main(String[] args) {
{ //直接使用{ }定义,普通方法块
int x = 66 ;
System.out.println("x1 = " + x);
}
int x = 99 ;
System.out.println("x2 = " +x);
}
}
7.3 构造代码块
构造块: 定义在类中的代码块(不加修饰符)。
也叫:实例代码块。构造代码块一般用于初始化实例成员变量。
示例代码:
class Person{
private String name;//实例成员变量
private int age;
private String sex;
//构造函数
public Person() {
System.out.println("测试一");
}
//实例代码块
{
this.name = "宝贝";
this.age = 13;
this.sex = "男";
System.out.println("测试二");
}
//成员方法
public void show(){
System.out.println("测试三");
System.out.println("name: "+name+" age: "+age+" sex: "+sex);
}
}
public class Test_4_12 {
public static void main(String[] args) {
Person p1 = new Person();
p1.show();
}
}
运行结果:实例代码块优先于构造函数执行。
注释:
1.就算 main方法里面没有 p1.show();
在 new Person();的时候: new实例化新对象时会被自动调用, 用于完成初始化操作
2. 有几个对象,构造块就被调用几次
案例:
提问:如果我们在 实例代码块(构造块)里面对name赋值,却由在构造方法里面对name赋值,那结果会是谁的?
运行结果:变成了 构造方法里面赋的值
原因还是 构造块 执行顺序高于构造方法,构造方法相当于是后赋值的。
7.4 静态代码块
使用static定义的代码块。一般用于初始化静态成员属性。
示例代码:
class Person{
private String name;//实例成员变量
private int age;
private String sex;
private static int count = 0;//静态成员变量 由类共享数据 方法区
//构造函数(构造方法)
public Person(){
System.out.println("测试一");
}
//实例代码块
{
this.name = "天线宝宝";
this.age = 6;
this.sex = "中性";
System.out.println("测试二");
}
//静态代码块
static {
count = 10;//只能访问静态数据成员
System.out.println("测试三");
}
//成员方法
public void show(){
System.out.println("测试四");
System.out.println("name: "+name+" age: "+age+" sex: "+sex);
}
}
public class Test_4_12 {
public static void main(String[] args) {
Person p1 = new Person();
System.out.println("-----------------");
Person p2 = new Person();//静态代码块是否还会被执行?
System.out.println("-----------------");
p1.show();
}
}
运行结果:
结论:
1. 不论有没有调用这个 类,只要创建(new)的时候就会开辟空间,开辟空间时里面就会有程序自动运行。
2. 在类加载时,静态代码块不管生成多少个对象,其只会执行一次,且是最先执行的。
3. 静态代码块执行完毕后, 实例代码块(构造块)执行,再然后是构造函数执行。(里面的成员方法才是最逊的)
提问:如果我们在主类中也写一个 静态代码块,那么执行顺序由是怎么样的?
运行结果: 果然还是主类中的 静态代码块 最先执行。
主类中的静态块优先于主方法执行。JVM要执行主方法,首先得加载主类,主类一加载,静态块就执行了。
提问2: 在类中定义了一个静态变量, 赋值。在静态代码块里也进行了赋值,那么最终会是谁的值呢?
运行结果:是 100;
解析:
静态变量存在于方法区中,类定义的时候就会有初始值(就是在我们在定义静态变量的时候就已经被赋值了),初始值为10,这个类就被放入方法区中。---> 这个类只是定义了,还没真正的加载。(这时就已经是 10了)在类未加载之前定义。
静态代码块什么时候执行?:
当主方法中使用了Animal,就需要把Animal从方法区加载内存,类加载--》静态代码块就执行了, age = 10-> 100
8. 补充说明
8.1 toString方法
我们刚刚注意到,我们在把对象的属性进行打印的时候,都自己实现了show函数,其实,我们大可不必。接下来我们看一些示例代码:
class Person {
private String name;
private int age;
public Person(String name,int age) {
this.age = age;
this.name = name;
}
public void show() {
System.out.println("name:"+name+" " + "age:"+age);
}
}
public class Test_4_12 {
public static void main(String[] args) {
Person person = new Person("薛之谦",38);
person.show();
//我们发现这里打印的是一个地址的哈希值 原因:调用的是Object的toString方法
System.out.println(person);
}
}
运行结果:
可以使用 toString 这样的方法来将对象自动转成字符串.
当一个引用类型的变量调用println函数打印时,默认输出的都是引用类型的地址。(不是真正的内存地址,Java中程序员是无法知道任何确切的内存地址。)
//我们发现这里打印的是一个地址的哈希值
原因:调用的是Object的toString方法
代码示例2: 在类里面加上这部分,是重点。
//重写Object的toString方法
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
完整代码:
class Person {
private String name;
private int age;
public Person(String name,int age) {
this.age = age;
this.name = name;
}
public void show() {
System.out.println("name:"+name+" " + "age:"+age);
}
//重写Object的toString方法
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test_4_12 {
public static void main(String[] args) {
Person person = new Person("薛之谦",38);
person.show();
//我们发现这里打印的是一个地址的哈希值 原因:调用的是Object的toString方法
System.out.println(person);
}
}
运行结果:
注意事项:
1. toString 方法会在 println 的时候被自动调用.
2. 将对象转成字符串这样的操作我们称为 序列化.
3. toString 是 Object 类提供的方法, 我们自己创建的 Person 类默认继承自 Object 类, 可以重写 toString 方法实现我们自己版本的转换字符串方法. (关于继承和重写这样的概念, 后面会介绍).
4. @Override 在 Java 中称为 "注解", 此处的 @Override 表示下面实现的 toString 方法是重写了父类的方法. 关于 注解后面的以后会介绍.
5. IDEA快速生成Object的toString方法快捷键:alt+f12(insert)
检查toString 是否拼写正常,看这个蓝色的标记:
7.2 匿名对象
匿名只是表示没有名字的对象.
没有引用的对象称为匿名对象.
匿名对象只能在创建对象时使用.
如果一个对象只是用一次, 后面不需要用了, 可以考虑使用匿名对象.
示例代码:
class Person {
private String name;
private int age;
public Person(String name,int age) {
this.age = age;
this.name = name;
}
public void show() {
System.out.println("name:"+name+" " + "age:"+age);
}
}
public class Main {
public static void main(String[] args) {
new Person("caocao",19).show();//通过匿名对象调用方法
}
}
// 执行结果
name:caocao age:19
内容重点总结
1. 一个类可以产生无数的对象,类就是模板,对象就是具体的实例。
2. 类中定义的属性,大概分为几类:类属性,对象属性。其中被static所修饰的数据属性称为类属性, static修饰的 方法称为类方法,特点是不依赖于对象,我们只需要通过类名就可以调用其属性或者方法。
3. 静态代码块优先实例代码块执行,实例代码块优先构造函数执行。
4. this关键字代表的是当前对象的引用。并不是当前对象。
还有待完善~~~~
注释:部分知识点借鉴了 黑马程序员 的java视频课(B站免费看)