面向对象—【类与对象】【类的定义与对象创建】【对象的使用】【方法创建与使用】【方法进阶使用】【构造方法】

@


源码:
Gitee https://gitee.com/drip123456/java-se
GIthub https://github.com/Drip123456/JavaSE

专栏: JavaSE笔记专栏

面向对象基础篇

我们在前面已经学习了面向过程编程,也可以自行编写出简单的程序了。我们接着就需要认识 面向对象程序设计(Object Oriented Programming)它是我们在Java语言中要学习的重要内容,面向对象也是高级语言的一大重要特性。

面向对象是新手成长的一道分水岭,有的人秒懂,有的人直到最后都无法理解。

这一章开始难度就上来了,所以说请各位小伙伴一定认真。

类与对象

类的概念我们在生活中其实已经听说过很多了。

人类、鸟类、鱼类... 所谓类,就是对一类事物的描述,是抽象的、概念上的定义,比如鸟类,就泛指所有具有鸟类特征的动物。比如人类,不同的人,有着不同的性格、不同的爱好、不同的样貌等等,但是他们根本上都是人,所以说可以将他们抽象描述为人类。

对象是某一类事物实际存在的每个个体,因而也被称为实例(instance)我们每个人都是人类的一个实际存在的个体。

在这里插入图片描述

所以说,类就是抽象概念的人,而对象,就是具体的某一个人。

  • A:是谁拿走了我的手机?
  • B:是个人。(某一个类)
  • A:我还知道是个人呢,具体是谁呢?
  • B:是XXX。(具体某个对象)

而我们在Java中,也可以像这样进行编程,我们可以定义一个类,然后进一步创建许多这个类的实例对象。像这种编程方式,我们称为面向对象编程

类的定义与对象创建

前面我们介绍了什么是类,什么是对象,首先我们就来看看如何去定义一个类。

比如现在我们想要定义一个人类,我们可以右键src目录,点击创建新的类:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们在对类进行命名时,一般使用英文单词,并且首字母大写,跟变量命名一样,不能出现任何的特殊字符。

在这里插入图片描述

可以看到,现在我们的目录下有了两个.java源文件,其中一个是默认创建的Main.java,还有一个是我们刚刚创建的类。

我们来看看创建好之后,一个类写了哪些内容:

public class Person {
    
}

可以发现,这不是跟一开始创建的Main中写的格式一模一样吗?没错,Main也是一个类,只不过我们一直都将其当做主类在使用,也就是编写主方法的类,关于方法我们会在后面进行介绍。

现在我们就创建好了一个类,既然是人类,那么肯定有人相关的一些属性,比如名字、性别、年龄等等,那么怎么才能给这个类添加一些属性呢?

我们可以将这些属性直接作为类的成员变量(成员变量相当于是这个类所具有的属性,每个实例创建出来之后,这些属性都可能会各不相同)定义到类中。

public class Person {   //这里定义的人类具有三个属性,名字、年龄、性别
    String name;   //直接在类中定义变量,表示类具有的属性
    int age;
    String sex;
}

可能会有小伙伴疑问,这些变量啥时候被赋值呢?实际上这些变量只有在一个具体的对象中才可以使用。

那么现在人类的属性都规定好了,我们就可以尝试创建一个实例对象了,实例对应的应该是一个具体的人:

new 类名();
public static void main(String[] args) {
    new Person();   //我们可以使用new关键字来创建某个类的对象,注意new后面需要跟上 类名()
  	//这里创建出来的,就是一个具体的人了
}

实际上整个流程为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

只不过这里仅仅是创建出了这样的一个对象,我们目前没有办法去操作这个对象,比如想要修改或是获取这个人的名字等等。

对象的使用

既然现在我们知道如何创建对象,那么我们怎么去访问这个对象呢,比如我现在想要去查看或是修改它的名字。

我们同样可以使用一个变量来指代某个对象,只不过引用类型的变量,存储的是对象的引用,而不是对象本身:

public static void main(String[] args) {
  	//这里的a存放的是具体的某个值
  	int a = 10;
  	//创建一个变量指代我们刚刚创建好的对象,变量的类型就是对应的类名
  	//这里的p存放的是对象的引用,而不是本体,我们可以通过对象的引用来间接操作对象
    Person p = new Person();
}

至于为什么对象类型的变量存放的是对象的引用,比如:

public static void main(String[] args) {
    Person p1 = new Person();
    Person p2 = p1;
}

这里,我们将变量p2赋值为p1的值,那么实际上只是传递了对象的引用,而不是对象本身的复制,这跟我们前面的基本数据类型有些不同,p2和p1都指向的是同一个对象(如果你学习过C语言,它就类似于指针一样的存在)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以来测试一下:

public static void main(String[] args) {
    Person p1 = new Person();
    Person p2 = p1;
    System.out.println(p1 == p2);    //使用 == 可以判断两个变量引用的是不是同一个对象
}

但是如果我们像这样去编写:

public static void main(String[] args) {
    Person p1 = new Person();   //这两个变量分别引用的是不同的两个对象
    Person p2 = new Person();
    System.out.println(p1 == p2);   //如果两个变量存放的是不同对象的引用,那么肯定就是不一样的了
}

实际上我们之前使用的String类型,也是一个引用类型,我们会在下一章详细讨论。我们在上一章介绍的都是基本类型,而类使用的都是引用类型。

现在我们有了对象的引用之后,我们就可以进行操作了:

在这里插入图片描述

我们可以直接访问对象的一些属性,也就是我们在类中定义好的那些,对于不同的对象,这些属性都具体存放值也会不同。

比如我们可以修改对象的名字:

public static void main(String[] args) {
    Person p = new Person();
    p.name = "小明";   //要访问对象的属性,我们需要使用 . 运算符
    System.out.println(p.name);   //直接打印对象的名字,就是我们刚刚修改好的结果了
}

注意,不同对象的属性是分开独立存放的,每个对象都有一个自己的空间,修改一个对象的属性并不会影响到其他对象:

public static void main(String[] args) {
    Person p1 = new Person();
    Person p2 = new Person();
    p1.name = "小明";   //这个修改的是第一个对象的属性
    p2.name = "大明";   //这里修改的是第二个对象的属性
    System.out.println(p1.name);  //这里我们获取的是第一个对象的属性
}

关于对象类型的变量,我们也可以不对任何对象进行引用:

public static void main(String[] args) {
    Person p1 = null;  //null是一个特殊的值,它表示空,也就是不引用任何的对象
}

注意,如果不引用任何的对象,那肯定是不应该去通过这个变量去操作所引用的对象的(都没有引用对象,我操作谁啊我)

虽然这样可以编译通过,但是在运行时会出现问题:

public static void main(String[] args) {
    Person p = null;   //此时变量没有引用任何对象
    p.name = "小红";   //我任性,就是要操作
    System.out.println(p.name);
}

我们来尝试运行一下这段代码:

在这里插入图片描述

此时程序在运行的过程中,出现了异常,虽然我们还没有学习到异常,但是各位可以将异常理解为程序在运行过程中出现了问题,此时不得不终止程序退出。

这里出现的是空指针异常,很明显是因为我们去操作一个值为null的变量导致的。在我们以后的学习中,这个异常是出现频率最高的。

我们来看最后一个问题,对象创建成功之后,它的属性没有进行赋值,但是我们前面说了,变量使用之前需要先赋值,那么创建对象之后能否直接访问呢?

public static void main(String[] args) {
    Person p = new Person();
    System.out.println("name = "+p.name);
    System.out.println("age = "+p.age);
    System.out.println("sex = "+p.sex);
}

我们来看看运行结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以看到,如果直接创建对象,那么对象的属性都会存在初始值,如果是基本类型,那么默认是统一为0(如果是boolean的话,默认值为false)如果是引用类型,那么默认是null

方法创建与使用

前面我们介绍了类的定义以及对象的创建和使用。

现在我们的类有了属性,我们可以为创建的这些对象设定不同的属性值,比如每个人的名字都不一样,性别不一样,年龄不一样等等。只不过光有属性还不行,对象还需要具有一定的行为,就像我们人可以行走,可以跳跃,可以思考一样。

而对象也可以做出一些行为,我们可以通过定义方法来实现(在C语言中叫做函数)

方法是语句的集合,是为了完成某件事情而存在的。完成某件事情,可以有结果,也可以做了就做了,不返回结果。比如计算两个数字的和,我们需要得到计算后的结果,所以说方法需要有返回值;又比如,我们只想吧数字打印在控制台,只需要打印就行,不用给我结果,所以说方法不需要有返回值。

方法的定义如下:

返回值类型 方法名称() {
		方法体...
}

首先是返回值类型,也就是说这个方法完成任务之后,得到的结果的数据类型(可以是基本类型,也可以是引用类型)当然,如果没有返回值,只是完成任务,那么可以使用void表示没有返回值,比如我们现在给人类编写一个自我介绍的行为:

public class Person {
    String name;
    int age;
    String sex;

  	//自我介绍只需要完成就行,没有返回值,所以说使用void
    void hello(){
      	//完成自我介绍需要执行的所有代码就在这个花括号中编写
      	//这里编写代码跟我们之前在main中是一样的(实际上main就是一个函数)
      	//自我介绍需要用到当前对象的名字和年龄,我们直接使用成员变量即可,变量的值就是当前对象的存放值
        System.out.println("我叫 "+name+" 今年 "+age+" 岁了!");
    }
}

注意,方法名称同样可以随便起,但是规则跟变量的命名差不多,也是尽量使用小写字母开头的单词,如果是多个单词,一般使用驼峰命名法最规范。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在我们给人类定义好了一个方法(行为)那么怎么才能让对象执行这个行为呢?

public static void main(String[] args) {
    Person p = new Person();
    p.name = "小明";
    p.age = 18;
    p.hello();    //我们只需要使用 . 运算符,就可以执行定义好的方法了,只需要 .方法名称() 即可
}

像这样执行定义好的方法,我们一般称为方法的调用,我们来看看效果:

在这里插入图片描述

比如现在我们要让人类学会加法运算,我们也可以通过定义一个方法的形式来完成,只不过,要完成加法运算,我们需要别人给人类提供两个参与加法运算的值才可以,所以我们这里就要用到参数了:

//我们的方法需要别人提供参与运算的值才可以
//我们可以为方法设定参数,在调用方法时,需要外部传入参数才可以
//参数的定义需要在小括号内部编写,类似于变量定义,需要填写 类型和参数名称,多个参数用逗号隔开
int sum(int a, int b){   //这里需要两个int类型的参数进行计算

}

那么现在参数从外部传入之后,我们怎么使用呢?

int sum(int a, int b){   //这里的参数,相当于我们在函数中定义了两个局部变量,我们可以直接在方法中使用
    int c = a + b;   //直接c = a + b
}

那么现在计算完成了,我们该怎么将结果传递到外面呢?首先函数的返回值是int类型,我们只需要使用return关键字来返回一个int类型的结果就可以了:

int sum(int a, int b){
    int c = a + b;
    return c;   //return后面紧跟需要返回的结果,这样就可以将计算结果丢出去了
  	//带返回值的方法,是一定要有一个返回结果的!否则无法通过编译!
}

我们来测试一下吧:

public static void main(String[] args) {
    Person p = new Person();
    p.name = "小明";
    p.age = 18;
    int result = p.sum(10, 20);    //现在我们要让这个对象帮我们计算10 + 20的结果
    System.out.println(result);    //成功得到30,实际上这里的println也是在调用方法进行打印操作
}

注意:方法定义时编写的参数,我们一般称为形式参数,而调用方法实际传入的参数,我们成为实际参数。

是不是越来越感觉我们真的在跟一个对象进行交互?只要各位有了这样的体验,基本上就已经摸到面向对象的门路了。

关于return关键字,我们还需要进行进一步的介绍。

在我们使用return关键字之后,方法就会直接结束并返回结果,所以说在这之后编写的任何代码,都是不可到达的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

return后编写代码,会导致编译不通过,因为存在不可达语句。

如果我们的程序中出现了分支语句,那么必须保证每一个分支都有返回值才可以:

在这里插入图片描述

只要有任何一个分支缺少了return语句,都无法正常通过编译,总之就是必须考虑到所有的情况,任何情况下都必须要有返回值。

当然,如果方法没有返回值,我们也可以使用return语句,不需要跟上任何内容,只不过这种情况下使用,仅仅是为了快速结束方法的执行:

void test(int a){
    if(a == 10) return;    //当a等于10时直接结束方法,后面无论有没有代码都不会执行了
    System.out.println("Hello World!");   //不是的情况就正常执行
}

最后我们来讨论一下参数的传递问题:

void test(int a){   //我们可以设置参数来让外部的数据传入到函数内部
    System.out.println(a);
}

实际上参数的传递,会在调用方法的时候,对参数的值进行复制,方法中的参数变量,不是我们传入的变量本身,我们来下面的这个例子:

void swap(int a, int b){   //这个函数的目的很明显,就是为了交换a和b的值
    int tmp = a;
    a = b;
    b = a;
}

那么我们来测试一下:

public static void main(String[] args) {
    Person p = new Person();
    int a = 5, b = 9;   //外面也叫a和b
    p.swap(a, b);
    System.out.println("a = "+a+", b = "+b);   //最后的结果会变成什么样子呢?
}

我们来看看结果是什么:

在这里插入图片描述

我们发现a和b的值并没有发生交换,但是按照我们的方法逻辑来说,应该是会交换才对,这是为什么呢?实际上这里仅仅是将值复制给了函数里面的变量而已(相当于是变量的赋值)

在这里插入图片描述

所以说我们交换的仅仅是方法中的a和b,参数传递仅仅是值传递,我们是没有办法直接操作到外面的a和b的。

那么各位小伙伴看看下面的例子:

void modify(Person person){
    person.name = "lbwnb";   //修改对象的名称
}
public static void main(String[] args) {
    Person p = new Person();
    p.name = "小明";     //先在外面修改一次
    p.modify(p);        //调用方法再修改一次
    System.out.println(p.name);    //请问最后name会是什么?
}

我们来看看结果:

在这里插入图片描述

不对啊,前面不是说只是值传递吗,怎么这里又可以修改成功呢?

确实,这里同样是进行的值传递,只不过各位小伙伴别忘了,我们前面可是说的清清楚楚,引用类型的变量,仅仅存放的是对象的引用,而不是对象本身。那么这里进行了值传递,相当于将对象的引用复制到了方法内部的变量中,而这个内部的变量,依然是引用的同一个对象,所以说这里在方法内操作,相当于直接操作外面的定义对象。

在这里插入图片描述

方法进阶使用

有时候我们的方法中可能会出现一些与成员变量重名的变量:

//我们希望使用这个方法,来为当前对象设定名字
void setName(String name) {
   
}

此时类中定义的变量名称也是name,那么我们是否可以这样编写呢:

void setName(String name) {
    name = name;    //出现重名时,优先使用作用域最接近的,这里实际上是将方法参数的局部变量name赋值为本身
}

我们来测试一下:

public static void main(String[] args) {
    Person p = new Person();
    p.setName("小明");
    System.out.println(p.name);
}

我们发现,似乎这样做并没有任何的效果,name依然是没有修改的状态。那么当出现重名的时候,因为默认情况下会优先使用作用域最近的变量,我们怎么才能表示要使用的变量是类的成员变量呢?

Person p = new Person();
p.name = "小明";    //我们之前在外面使用时,可以直接通过对象.属性的形式访问到

同样的,我们如果想要在方法中访问到当前对象的属性,那么可以使用this关键字,来明确表示当前类的示例对象本身:

void setName(String name) {
    this.name = name;   //让当前对象的name变量值等于参数传入的值
}

这样就可以修改成功了,当然,如果方法内没有变量出现重名的情况,那么默认情况下可以不使用this关键字来明确表示当前对象:

String getName() {
    return name;    //这里没有使用this,但是当前作用域下只有对象属性的name变量,所以说直接就使用了
}

我们接着来看方法的重载。

有些时候,参数类型可能会多种多样,我们的方法需要能够同时应对多种情况:

int sum(int a, int b){
    return a + b;
}
public static void main(String[] args) {
    Person p = new Person();
    System.out.println(p.sum(10, 20));    //这里可以正常计算两个整数的和
}

但是要是我们现在不仅要让人类会计算整数,还要会计算小数呢?

在这里插入图片描述

当我们使用小数时,可以看到,参数要求的是int类型,那么肯定会出现错误,这个方法只能用于计算整数。此时,为了让这个方法支持使用小数进行计算,我们可以将这个方法进行重载。

一个类中可以包含多个同名的方法,但是需要的形式参数不一样,方法的返回类型,可以相同,也可以不同,但是仅返回类型不同,是不允许的!

int sum(int a, int b){
    return a + b;
}

double sum(double a, double b){    //为了支持小数加法,我们可以进行一次重载
    return a + b;
}

这样就可以正常使用了:

public static void main(String[] args) {
    Person p = new Person();
  	//当方法出现多个重载的情况,在调用时会自动进行匹配,选择合适的方法进行调用
    System.out.println(p.sum(1.5, 2.2));
}

包括我们之前一直在使用的println方法,其实也是重载了很多次的,因为要支持各种值的打印。

注意,如果仅仅是返回值的不同,是不支持重载的:

在这里插入图片描述

当然,方法之间是可以相互调用的:

void test(){
    System.out.println("我是test");   //实际上这里也是调用另一个方法
}

void say(){
    test();   //在一个方法内调用另一个方法
}

如果我们这样写的话:

void test(){
    say();
}

void say(){
    test();
}

各位猜猜看会出现什么情况?
会导致栈溢出异常,这里大家不用细想,之后我们会在异常一章列讲解

在这里插入图片描述

此时又出现了一个我们不认识的异常,实际上什么原因导致的我们自己都很清楚,方法之间一直在相互调用,没有一个出口。

方法自己也可以调用自己:

void test(){
    test();
}

像这样自己调用自己的行为,我们称为递归调用,如果直接这样编写,会跟上面一样,出现栈溢出错误。但是如果我们给其合理地设置出口,就不会出现这种问题,比如我们想要计算从1加到n的和:

int test(int n){
    if(n == 0) return 0;
    return test(n - 1) + n;    //返回的结果是下一层返回的结果+当前这一层的n
}

是不是感觉很巧妙?实际上递归调用在很多情况下能够快速解决一些很麻烦的问题,我们会在后面继续了解。

构造方法

我们接着来看一种比较特殊的方法,构造方法。

我们前面创建对象,都是直接使用new关键字就能直接搞定了,但是我们发现,对象在创建之后,各种属性都是默认值,那么能否实现在对象创建时就为其指定名字、年龄、性别呢?要在对象创建时进行处理,我们可以使用构造方法(构造器)来完成。

实际上每个类都有一个默认的构造方法,我们可以来看看反编译的结果:

public class Person {
    String name;
    int age;
    String sex;

    public Person() {    //反编译中,多出来了这样一个方法,这其实就是构造方法
    }
}

构造方法不需要填写返回值,并且方法名称与类名相同,默认情况下每个类都会自带一个没有任何参数的无参构造方法(只是不用我们去写,编译出来就自带)当然,我们也可以手动声明,对其进行修改:

posted @ 2024-02-28 22:25  笠大  阅读(19)  评论(0编辑  收藏  举报