Java 第一弹 类与对象 对象存储 参数传递 封装 构造方法

1.面对对象的概念

1.1.面向过程

  • C语言
  • 当开始一个功能时,看重中间过程,每一个步骤都需要自己去完成。
  • 优点:面向过程的性能比面向对象高,因为面向对象把所有的事务看成对象,涉及到对象的实例化。
  • 缺点:不容易维护,不容易扩展

1.2.面向对象

  • Java js C#
  • 将功能封装成对象,不看重中间过程,有对象则用对象,没有对象则创造对象,之后还要维护对象之间的关系
  • 优点:容易维护,容易扩展
  • 缺点:内存开销大,性能低

1.3.举例

在这里插入图片描述

面向对象相对于面向过程省去的那些步骤都通过对象完成,例如洗衣服通过洗衣机完成,买电脑通过秘书完成,吃饭通过出去买(实际上是店家做完饭)。

比如下列代码有一个数组,我想通过指定格式打印数组。

面向过程我需要把具体的执行流程写出来,然后打印,在此期间,我们注重的是这个过程;但是如果我们创造一个类,把这个过程封装成一个函数,当作这个类的成员方法,然后再创造这个类的对象,通过这个对象调用这个方法也可以实现功能,这就是面向对象。

public static void main(String[] args) {
        int[] arrs = {10, 12, 89, 90, 78, 11};
        /**
         * 把数组按照指定格式输出[10, 12, 89, 90, 78, 11]
         */
        //面向过程
        System.out.print("[");
        for (int i = 0; i < arrs.length; i++) {
            if (i == arrs.length - 1) {
                System.out.print(arrs[i] + "]");
            } else {
                System.out.print(arrs[i] + ",");
            }
        }

        //面向对象
        //Arrays:把数组按照相应的格式进行输出
        System.out.println(Arrays.toString(arrs));
    }

1.4.面向对象三大特征(重要)

  • 封装
  • 继承
  • 多态

三大特征在下面会讲。

2.类和对象

2.1.简单说明

  • 类是对一类事物描述,是抽象的、概念上的定义。

  • 对象是实际存在的该类事物的每个个体,是具体的。

  • 以上的描述很抽象,下面用一个实际的例子来说明。

  • 如果将对象比作汽车,那么类就是汽车的图纸。

  • 在这里插入图片描述

  • 对象叫做类的实例化(Instance),类不占内存,对象才占内存。

2.2.举例说明

/**
 * Car类
 * 汽车图纸包含两部分
 * 属性:颜色 价格 品牌
 * 行为:行驶 停止

 * 映射到类里面也包含两部分
 * 成员变量(属性):直接定义在类里面方法之外的变量
 * 成员方法(行为):把static去掉
 *
//汽车图纸类 不能直接使用
public class Car {
    //成员变量(属性)
    //成员变量保存在堆里,在堆里的变量都有默认值 String -> null
    String color;
    int price;
    String brand;
    double width;
    double length;


    //成员方法(行为)
    public void run() {
        System.out.println("一辆汽车颜色是" + color + ",价格是" + price
                + ",品牌是" + "brand" + ",长度是" + length + ",宽度是" + width
                + "的小汽车在路上嗡嗡嗡地跑着");
    }

    public void stop() {
        System.out.println("汽车停止行驶了");
    }
}

/**
 * TestCar类
 * 用来测试Car类
 */
public class TestCar {
    public static void main(String[] args) {
        //创建一辆小汽车
        Car car1 = new Car();//new出来的在堆里面
        car1.brand = "五菱宏光";
        car1.color = "红色";
        car1.length = 8;
        car1.width = 2;
        car1.price = 1000000;
        car1.run();
        car1.stop();
    }
}

3.对象的存储(重要)

3.1.数据默认值问题

当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。除了基本数据类型之外都是引用类型。

3.2.内存分配

运行一段程序,需要申请内存,内存都是归jvm进行管理的。

内存分为五部分:

  1. 栈(Stack):主要存放局部变量(定义在方法里的变量)。
  2. 堆(Heap):主要存放new出来的东西,在堆里面的变量都是成员变量,都有默认值,参考3.1。
  3. 方法区(Method Area):主要存放class文件。
  4. 本地方法区(Native Method Area):与操作系统有关。(不做了解)
  5. 寄存器(Register):主要与CPU有关。(不做了解)

下面用一个具体的例子说明程序运行时内存的具体分配流程,主要用到栈、堆和方法区(只是初步学习,静态变量、常量等的内存分配情况在以后的文章中会涉及到)。

先写一个Car类

public class Car {
    //成员变量(属性)
    //成员变量保存在堆里,在堆里的变量都有默认值 String -> null
    String color;
    int price;
    String brand;
    double width;
    double length;
    //成员方法(行为)
    public void run() {
        System.out.println("一辆汽车颜色是" + color + ",价格是" + price
                + ",品牌是" + "brand" + ",长度是" + length + ",宽度是" + width
                + "的小汽车在路上嗡嗡嗡地跑着");
    }

    public void stop() {
        System.out.println("汽车停止行驶了");
    }
}

再写一个TestCar类

public class TestCar {
    public static void main(String[] args) {
        Car car1 = new Car();
        car1.brand = "五菱宏光";
        car1.color = "红色";
        car1.length = 8;
        car1.width = 2;
        car1.price = 1000000;
        car1.run();
        car1.stop();

        Car car2 = new Car();
        car2.brand = "玛莎拉蒂";
        car2.color = "白色";
        car2.length = 4;
        car2.width = 2;
        car2.price = 4000000;
        car2.run();
    }
}
  1. 首先先把编译后的两个class文件加载到方法区,包括属性和方法,注意Car类和TestCar类都要加载。加载成员方法时,每个成员方法会有一个地址,后面会用到。
  2. 然后运行TestCar里的main方法,main方法进栈(只要方法执行,那么这个方法就要进栈,执行完后就出栈),注意栈这种数据结构是先进后出,一般先进栈的我们都画在底部。
  3. 执行Car car1 = new Car();注意这条语句执行的时候分为两步首先Car car1;car1保存在栈,然后car1 = new Car();
    (main方法在栈里,方法里的变量成为局部变量,所以说栈里面存放局部变量)。
  4. new的过程其实是在堆里开辟一块空间,从方法区加载成员变量和成员方法。加载成员变量后给变量赋值,加载成员方法很特殊,它的值为方法区方法的地址值(也可以说指向方法区的方法)。new出来的这一块空间会有一个地址值,栈里的car1的值为这个地址(也可以说成car1指向那块内存)。
  5. 然后执行car1.brand = “五菱宏光”;通过0x111找到堆里的对象,然后找到对应的成员变量,然后给这些变量赋值,这些变量的值不再是默认值(只简单说明一个属性,其他属性与此相同)。
  6. 然后执行run(),只要方法执行,那这个方法就要进栈。通过car1找到堆里的内存,通过方法名找到对应方法,堆里的方法是指向方法区的,所以形成了栈指向堆,堆指向方法区,方法体是加载在方法区里的,堆里只存了方法的地址。然后执行方法体,执行完后,此方法出栈(注意是main先进栈,然后run进栈,然后run先出栈,这就对应了栈的特点,与数据结构相联系),这里我们只举例run方法的执行,stop方法的执行与此相同。
  7. 然后main方法执行完毕,main方法出栈。

废话少说,直接上图👇
在这里插入图片描述

4.成员变量和局部变量

我们主要清楚二者区别即可:

  1. 定义的位置不同
    • 成员变量定义在类内部,方法外部
    • 局部变量定义在方法内部,形参也可以看作局部变量。
  2. 作用域不同
    • 成员变量作用在类的内部
    • 局部变量作用在方法内部
  3. 默认值
    • 成员变量有默认值
    • 局部变量没有默认值,在没有赋值的情况下使用会报错
  4. 内存中的位置
    • 成员变量保存在堆里
    • 局部变量保存在栈里
  5. 访问修饰符
    • 成员变量可以使用四个修饰符
    • 局部变量不能通过访问修饰符修饰,只能在方法内使用

用代码来看一下:

public class Demo2 {
    //类的属性=成员变量
    String name;
    static int age;

    public static void setAge(int age1){//age1是形参,是一个局部变量
        String sex = "male";//sex没赋值的时候,编译不通过
        age1 = age;
        System.out.println(sex);
    }

    //main是静态方法,一个静态方法里面只能调用静态变量和静态方法
    public static void main(String[] args) {
        System.out.println(age);//0
        //System.out.println(age1);//报错:找不到age1变量
    }
}

5.参数传递

5.1.按值传递

按值传递是指一个参数传递给一个函数时,函数接受的是原始值的一个副本。因此,如果函数修改了参数,仅仅只是修改了副本,原始值保持不变。

废话少说,直接上代码👇

public class Demo3 {
    public static void main(String[] args) {
        int a = 10;
        int b = 10;
        System.out.println("a = " + a);//10
        System.out.println("b = " + b);//10
        System.out.println("=========================");
        change(a, b);// 100 ,100
        System.out.println("=========================");
        System.out.println("a = " + a);//10
        System.out.println("b = " + b);//10
    }

    //把形参扩大十倍
    public static void change(int a, int b) {

        a *= 10;
        b *= 10;
        System.out.println("a = " + a);
        System.out.println("b = " + b);
    }
}

在这里插入图片描述

5.2.按引用传递

按引用传递是指当将一个参数传递给一个函数时,函数接受的是原始值的内存地址,而不是值得副本。因此,如果函数修改了参数,原始值也会随之改变。

废话少说,直接上代码👇

public class Demo4 {
    public static void main(String[] args) {
        Car car = new Car();
        car.brand = "奔驰";
        System.out.println(car.brand);//奔驰
        System.out.println("======================");
        info(car);//宝马
        System.out.println("======================");
        System.out.println(car.brand);//宝马
    }

    public static void info(Car car) {
        car.brand = "宝马";
        System.out.println(car.brand);
    }
}

在这里插入图片描述

6.封装

先来写一段代码

public class Student {
    String stuNo;
    String stuName;
    //private只能在当前类的内部去使用,出了此类不能用
    private int age;

    //个人描述
    public void desc() {
        System.out.println("我的学号是:" + stuNo + ",我的姓名是:" + stuName + ",我的年龄是:" + age);
    }

    public void setAge(int age1) {
        //对年龄进行限制
        if (age1 >= 0 && age1 < 120) {
            age = age1;
        } else {
            System.out.println("你输入的年龄不合法");
        }
    }

    public int getAge() {
        return age;
    }
}

public class TestStudent {
    public static void main(String[] args) {
        Student stu = new Student();//new的就是一个构造方法
        stu.stuNo = "1001";
        stu.stuName = "尼古拉斯赵四";
        //stu.age = -50;
        stu.setAge(-50);//年龄不能是负数
        
        //比如我想单独获取age属性的值
        System.out.println(stu.getAge());
        stu.desc();
    }
}

首先,我们写了一个Student类和一个TestStudent类,我们在TestStudent类里面创建一个main方法,在main方法里创建一个Student对象,我们可以直接通过’对象名.属性’的方式赋值,但是这就出现了第一个问题,直接通过这种方式赋值可能会出现逻辑错误,比如给age赋值-50,显然年龄不可能是负数,这明显与实际情况不符,所以我们不能使用’对象名.属性’的方式进行赋值,而是通过创建一个方法,通过’对象名.方法名’进行赋值,因为我们可以在方法里写逻辑代码对属性进行限制,所以我们写了setAge(int age1)方法,并且这个方法是用public进行修饰的,方法里写了对年龄限制的逻辑代码,实现了对年龄的限制,第一个问题解决了。(关于修饰符的问题,我们在以后的文章进行讲解)。

但是还有第二个问题,我们发现还是可以通过’对象名.属性’对属性进行赋值,解决这个问题的方法就是用private对属性进行修饰。用private修饰的变量只能在类内部使用,除了类就不能使用。

解决了以上两个问题,就实现了对象的封装。

总结一下封装实现的步骤:

  1. 修改属性的可见性为private。
  2. 创建getter和setter方法(可以自动生成)。
  3. 在getter和setter方法中写入对属性的业务逻辑代码,以此实现对属性的限制。

7.构造方法

解决了上面两个问题就实现了封装,但是又出现了第三个问题,当类中有很多属性时,用setter方法赋值很繁琐也很容易忘记,为了解决这个问题,我们就会用到了构造器,也称为构造方法。

构造方法是java中的一个特殊的方法,它的最大的作用就是创建对象。

//语法
public School() {
}
 /** 注意:
 * 1.构造方法无返回值
 * 2.构造方法名与类名一致
 */

在定义一个类的时候,如果没有写构造方法,程序默认会提供一个无参的构造方法,所以说在java中至少有一个构造方法,那就是这个默认的构造方法,其实当没有写构造方法的时候,School sc = new School();就是用了程序默认提供的无参构造函数。

如果手动写了构造方法,系统将不会再提供无参构造方法,例如👇:

//一个参数的构造函数
public School(String name1) {
	System.out.println("一个参数的构造方法执行了");
    this.name = name1;
}
//两个参数的构造函数
public School(String name1, String address1) {
	System.out.println("两个参数的构造方法执行了");
	this.name = name1;
	this.address = address1;
}

上面的代码我们写了两个构造方法,那么当我们再通过School sc = new School();创建对象就会报错。所以说,如果你定义了有参构造方法,一般要再定义上无参构造方法,因为极大情况下你都要使用无参构造方法创建对象。

其实写无参构造方法和有参构造方法原理就是用到了方法的重载,重载会在后面章节讲到。

下面通过一个例子来说明下👇

public class School {

    private String name;
    private String address;

    public String getName() {
        return name;
    }

    public void setName(String name1) {
        this.name = name1;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address1) {
        this.address = address1;
    }
     //没参数的构造函数
    public School() {
        System.out.println("无参构造方法执行了");
    }
    //一个参数的构造函数
    public School(String name1) {
        System.out.println("一个参数的构造方法执行了");
        this.name = name1;
    }
    //两个参数的构造函数
    public School(String name1, String address1) {
        System.out.println("两个参数的构造方法执行了");
        this.name = name1;
        this.address = address1;
    }
}

public class TestSchool {

    public static void main(String[] args) {

        School sc = new School();
        sc.setName("布鲁弗莱大学");
        sc.setAddress("济南");
        System.out.println(sc.getName() + sc.getAddress());

        School sc1 = new School("春田花花幼稚园");
        System.out.println(sc1.getName() + sc1.getAddress());

        School sc2 = new School("国际希望小学", "北京海淀区中关村");
        sc2.setAddress("济南");
        System.out.println(sc2.getName() + sc2.getAddress());
    }
}

总结一下构造方法的作用(虽然没啥卵用,但还是要知道官方书面的表达):

无参构造方法:创建对象。

有参构造方法:创建对象的同时给对象的属性赋值。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值