Java笔记day10
一、一个对象的内存调用
以手机举例
手机:
属性:品牌,颜色,价格
行为:打电话,发短信,学习
Phone:
成员变量:brand,color,price
成员方法:call,sendMessage,study
查看代码
public class Phone {
String brand; //定义成员变量
String color;
int price;
public void call(String s) { //定义成员方法,打电话
System.out.println("给" + s + "打电话");
}
public void sendMessage() {
System.out.println("群发短信"); //发短信
}
public void study() { //学习
System.out.println("学习");
}
}
class PhoneDemo {
public static void main(String[] args) {
Phone p1 = new Phone(); //通过手机类创建一个手机对象
System.out.println("品牌:" + p1.brand + ",颜色:" + p1.color + ",价格:" + p1.price); //访问成员变量
p1.brand = "华为"; //手动给成员变量进行赋值操作
p1.color = "黑色";
p1.price = 2999;
System.out.println("品牌:" + p1.brand + ",颜色:" + p1.color + ",价格:" + p1.price);
p1.call("杨老板"); //访问成员方法
p1.sendMessage();
p1.study();
}
}
内存调用过程
首先,Jvm先在方法区生成两个class文件,phone.class和phoneDemo.class,phone.class中有成员变量和成员方法,
phoneDemo.class里有main方法,在栈里生成一个main方法,main方法中存有p1,p1对应一个地址ox0001(假设),
对应到堆里面new出来phone区域,这一区域里有成员变量brand,color,price和方法区对应的地址值ox0002(假设)
此时并未赋值,所以输出时是堆里给出的默认值,null,null,0,然后执行赋值操作,华为,黑色,2999覆盖掉null,null,0
再次调用时,Jvm就会打印出输入的数据。后面调用方法时p1先在堆里ox0001里寻找成员方法所对应的地址值,这里是ox0002,
在方法区找到方法之后到栈中执行,执行完第一个方法后栈就会清理这一个方法,然后执行后面的方法,都是执行一个清理一个
全部执行完之后,清理掉main方法,程序执行完毕。
二、两个对象的内存调用
如果是两个独立的对象创建,就是和上面的过程类似,但如果是下面的情况,就会出现不同的结果
查看代码
class PhoneDemo {
public static void main(String[] args) {
Phone p1 = new Phone(); //通过手机类创建一个手机对象
System.out.println("品牌:" + p1.brand + ",颜色:" + p1.color + ",价格:" + p1.price); //访问成员变量
p1.brand = "华为"; //手动给成员变量进行赋值操作
p1.color = "黑色";
p1.price = 2999;
System.out.println("品牌:" + p1.brand + ",颜色:" + p1.color + ",价格:" + p1.price);
Phone3 p2 = p1;
p2.price = 3999;
System.out.println(p1.price); //输出是3999
System.out.println(p2.price); //输出是3999
}
}
输出都是3999,是因为p2.price = 3999;是直接在地址值所对应的堆那个区域里进行的修改,修改但之后p1.price和p2.price都
指向它,所以会输出同一个值。
三、成员变量和局部变量
成员变量与局部变量的区别:
1、在类中的位置不同
成员变量 类中方法外
局部变量 方法内或者方法声明上
2、在内存中的位置不同
成员变量 堆内存:
随着对象在堆内存中的创建而出现,所以成员变量是在堆内存中出现
局部变量 栈内存:
由于方法执行是在栈中执行,所以在方法中定义的局部变量,也就是存在与栈里。
3、生命周期不同
成员变量 随着对象的创建而存在,随着对象的消失而消失
局部变量 随着方法的调用而存在,随着方法的调用完毕而消失
4、初始化值不同
成员变量 有默认的初始化值
原因:由于成员变量随着对象的创建而存在,而对象的创建是在堆内存中创建,
而堆内存中的变量创建的时候系统会给予默认值,所以我们在代码中定义的时候可以不赋值。
局部变量 没有默认的初始化值,必须先定义,赋值,才能使用。
原因:由于局部变量随着方法的调用而存在,是在栈里面调用的,而栈里面的变量系统不会给予默认值,
所以在代码中定义的时候必须要赋值。
5、问题:
1)、成员变量的名称可以和局部变量的名称一样吗?
可以,调用的时候,访问的变量遵循就近原则。
2)、方法与方法之间的局部变量可以互相访问吗?
不可以,因为作用域的范围不同。
6、举例
查看代码
class VariableDemo {
int a; //直接在类中定义的叫成员变量
public static void main(String[] args) {
int a = 20; //在方法里面定义的叫局部变量
System.out.println(a);
}
}
四、类的举例
查看代码
class Animal { //定义一个动物类,叫的方法
public void shout() {
System.out.println("动物叫");
}
}
class Demo1 {
/*类是一个引用数据类型,所以它可以当作形参的数据类型
今后看到一个方法上的形参的数据类型是一个类的时候
实际上它需要的是一个对应类的对象的地址值
*/
public void fun1(Animal a) { //相当于Animal a = new Animal();
a.shout();
}
}
class Demo2 {
/*当方法的参数是基本数据类型的时候
调用方法的时候传入的是该基本数据类型实际的数值
*/
public void fun2(int x, int y) {
System.out.println(x + y);
}
}
public class ParameterDemo {
public static void main(String[] args) {
Demo1 d = new Demo1(); //要想调用fun1()这个方法,就要创建Demo1这个类的对象,只有对象才能去调用方法
Animal animal = new Animal(); //创建一个Animal类的对象
d.fun1(animal);
Demo2 d2 = new Demo2(); //创建Demo2的对象
int i = 10;
int j = 20;
d2.fun2(j, i);
}
}
五、匿名对象
1、匿名对象:简单来说,就是没有名字的对象
2、用内存的形式解释:
我们在学习匿名对象之前,一直都是在堆内存中new对象,然后将地址值赋给栈里面的变量,
这个变量也就是对象的名字,而匿名对象,就说明没有栈中变量引用。
3、匿名对象的使用场景
当调用方法的时候,仅仅只调用一次的时候,可以使用匿名对象
调用多次的时候,不适合使用匿名对象
4、那么,匿名存在的意义是什么?
某些情况下,我们就想使用一次某个对象中的功能,后续也不会使用它了
这时候,就可以使用匿名对象,使用完之后,就被JVM认为是一个垃圾空间(栈里面没有引用指向它),等待被回收。
5、匿名对象可以在调用方法的时候被当作参数进行传递
6、举例
查看代码
class Computer{
public void coding(){
System.out.println("敲代码");
}
}
class Demo3{ //注:同一个包下,类名不能重复
public void method(Computer c){
c.coding();
}
}
public class AnonymousDemo {
public static void main(String[] args) {
Computer com = new Computer(); //在没有学习匿名对象之前,我们如果想使用其他类中的方法,就必须创建对象
com.coding();
new Computer().coding(); //匿名对象
com.coding(); //使用第一个对象再调一次方法
Demo3 demo3 = new Demo3(); //创建Demo3的对象
System.out.println("=====匿名对象当作参数传递======"); //匿名对象当作参数传递
demo3.method(new Computer());
}
}
六、封装概要
定义一个学生类:
成员变量:姓名,年龄
成员方法:show()
在正常开发的过程中,会出现一个问题:
通过对象.变量名的形式给成员变量进行赋值,在赋值年龄的时候,居然可以赋值一些不合理的数据,比如我赋值10000
根据现实实际情况来看,年龄赋值10000是不合理。
所以,如果在赋值的时候,加一个判断,校验一下,如果合理,就允许赋值,如果不合理,就不允许赋值,
那么,在哪里加入判断比较合适呢?
在Java中测试类中一般情况下,只允许创建对象,调用方法或者访问成员变量
所以,把添加判断的逻辑代码放到其它类中,而在其它类中有成员变量,成员方法。
成员变量上只能是定义成员变量,加不了判断。所以只能另外写一个成员方法,来为年龄进行赋值,在赋值的时候加入判断
虽然可以加入了一个方法传参的形式去给成员变量进行赋值,并且在赋值的时候进行判断,但是如果不使用定义的方法,
就使用对象名.成员变量的形式去赋值,依旧可以赋值一些不合理的值。
这时有一个办法可以让外界不能直接的访问到成员变量就好了,这样如果想要进行对成员变量赋值,
就不能通过对象名.成员变量的形式去赋值,只能通过我们定义的方法去赋值。
有这样的方法,java替我们考虑到了这样的情况,提供了一个关键字给我们使用,这个关键字叫做:private
private: 私有的意思,使用private修饰的成员变量,在其他类中不能直接访问
说到现在的案例,主要引出一个思想:面向对象的三大特征之一-----封装
封装:
实际上就是隐藏对象的属性和相关实现细节,仅仅对外提供公共的访问的方式。
举例:
查看代码
class Student2 {
String name; //定义成员变量
private int age;
public void setAge(int x){
if(x>0 & x<=100){
age = x;
}else {
System.out.println("输入的年龄有误");
}
}
public void show() { //输出打印该对象的所有成员变量
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
public class StudentDemo1 {
public static void main(String[] args) {
Student2 s1 = new Student2(); //创建Student2的对象
s1.show();
s1.name = "李玉伟"; //给对象的成员变量进行赋值
s1.age = 18;
s1.show();
// s1.age = 10000; //这是不符合道理的
s1.setAge(100);
s1.show();
}
}
七、封装
封装:
是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
好处:
隐藏实现细节,提供公共的访问方式
提高了代码的复用性
提高安全性。
封装原则:
将不需要对外提供的内容都隐藏起来。
把属性隐藏,提供公共方法对其访问。
封装中的private关键字介绍:
1、是一个权限修饰符。
2、可以修饰成员(成员变量和成员方法)
3、被private修饰的成员只在本类中才能访问。
练习:
随机找身边的一个事物,定义一个类
成员变量用private修饰,提供公共的getXxx()和setXxx()方法
以及提供一个可以输出所有成员变量的show()方法
然后定义一个测试类,去创建对象使用看看
查看代码
class Person {
private String name;
private int age;
public void setName(String s) {
name = s;
}
public String getName() {
return name;
}
public void setAge(int i) {
age = i;
}
public int getAge() {
return age;
}
public void show() { //提供一个方法可以输出打印所有的成员变量
fun2(); //被private修饰的成员方法,只能在本类中进行访问
System.out.println("姓名:" + name + ",年龄:" + age);
}
private void fun2() {
System.out.println("这是一个被private修饰的成员方法");
}
}
public class PrivateDemo1 {
public static void main(String[] args) {
Person p1 = new Person(); //创建Person对象
//p1.name; //因为成员变量都被private私有的关键字修饰了,所以在其他类中访问不到。
//p1.age; //只用调用公共方法进行获取
String name = p1.getName();
int age = p1.getAge();
System.out.println(name + "---" + age);
p1.setName("杨万里"); //通过公共的方法对成员变量进行赋值
p1.setAge(18);
p1.show();
//p1.fun2(); //被private修饰成员方法,在其他类中也是不可以被访问的。
}
}
八、this关键字
查看代码
class Student3 {
private String name; //定义成员变量
private int age;
/*提供公共的getXxx()和setXxx()方法
回想一下,我们之前给变量进行命名的时候,有一个规则:见名知意,所以我们改一下
我们根据变量命名规则的见名之意的方式修改了形参的名字,但是运行程序后发现,
虽然我们也调用了方法,也进行了传参,但是,结果依旧没有成功赋值,这是为什么呢?
****因为变量的调用遵循就近原则****
我们理想上,是想让传进来的这个参数赋值给该对象的成员变量
实际上传进来的实参的值后面进行赋值的时候,还是赋值给方法上的变量,与成员变量没有关系
想的是,将传进来的name值赋值给当前对象的name值
*/
public void setName(String name) {
/*Student3.name这种写法,我们没有介绍过
如果有一个东西可以代表当前调用该方法的对象就好了
谁可以代替当前方法的对象呢?
java提供了一个关键字:this
Student3.name = name;
这样写,就代表将传进来的参数,赋值给当前调用该方法的对象的成员变量name
*/
this.name = name;
}
public String getName() {
return name; //其实这里隐藏了一个this关键字,代表的是返回当前调用该方法的对象的成员变量name
//这里相当于 return this.name
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void show() {
System.out.println("姓名:" + name + ",年龄:" + age);
}
}
public class StudentDemo2 {
public static void main(String[] args) {
Student3 s1 = new Student3(); //创建学生对象并对成员变量进行赋值
s1.setName("杨万里"); //对s1对象的姓名进行赋值
s1.setAge(18);
s1.show(); //调用show()方法查看所有成员变量值
}
}