面向对象【基础】

专栏系列文章

Java必备基础知识:数组、排序和查找

Java必备基础知识:控制结构 超详细!(分支、循环、break、continue)-CSDN博客

Java必备基础知识:输入与输出-CSDN博客


文章目录

文章目录

一、类与对象

1、什么是“类”和“对象”?

2、类和对象的创建、使用

二、成员方法

1、修饰符

2、返回值类型

3、方法名

4、参数列表

三、成员方法传参机制

四、重载

五、可变参数

六、作用域

七、构造器

八、this


前言

在面向对象编程(OOP)的世界中,“类”与“对象”是两个最基础且核心的概念。把类比作蓝图,它定义了某一类事物共有的属性与行为;而对象则是根据这张蓝图创造出的具体实体,具有状态和行为。理解类与对象的关系,是掌握面向对象编程思想的第一步。本文将从类与对象的定义出发,介绍其创建与使用方法,并逐步讲解成员方法、传参机制、重载、可变参数、作用域、构造器及this关键字等关键内容。


一、类与对象

1、什么是“类”和“对象”?

我们从生活中的例子入手,比如“电视”:

  • 所有的电视都有共同的特征:颜色、尺寸、品牌等,这些我们称之为属性
  • 所有的电视都有一些能做的事:开机、换台、调节音量等,这些我们称之为行为

那么“电视”就是一个“类”——它是对所有电视的抽象描述。而一台具体的电视,比如“三星牌子的大屏液晶电视”,就是电视中的一个对象。它是“电视类”的具体示例,有明确的属性值,也能执行具体的行为。

所以,就是“具有相同属性和行为的事务的统称”,是抽象的。对象就是一个示例,是具体实际的。

2、类和对象的创建、使用

类主要由“属性”和“行为”组成,定义时语法格式如下:

public class 类名 {
    // 属性(成员变量):描述类的特征
    数据类型 属性名1;
    数据类型 属性名2;
    
    // 行为(方法):描述类能做什么
    修饰符 方法返回值类型 方法名1(参数列表) {
        // 方法体:具体行为的实现
    }
    
    修饰符 方法返回值类型 方法名2(参数列表) {
        // 方法体
    }
}

 属性一般是基本数据类型,也可以是引用类型(数组、对象),属性如果不赋值,会有默认值(规则同数组)。

 接下来我们定义一个学生类:

// 学生类
public class Student {
    // 属性(成员变量)
    String name;  // 姓名
    int age;      // 年龄
    String id;    // 学号
    
    // 行为(方法):学习
    public void study() {
        System.out.println(name + "正在学习Java!");
    }
    
    // 吃饭
    public void eat() {
        System.out.println(name + "在食堂吃饭。");
    }
}

定义完类之后,我们就可以创建实例了,步骤如下:

  1. 创建对象:类名 对象名 = new 类名();  
  2. 为对象的属性赋值:对象名.属性名 = 值;
  3. 访问对象的属性:对象名.属性名
  4. 调用对象的方法:对象名.方法名();

我们接着上面创建好的学生类再来创建具体学生:

public class TestStudent {
    public static void main(String[] args) {
        // 1. 创建一个学生对象(实例化)
        Student stu1 = new Student();  // stu1是对象名(可以自己取)
        
        // 2. 给stu1的属性赋值
        stu1.name = "小明";
        stu1.age = 18;
        stu1.id = "2023001";
        
        // 3. 调用stu1的方法
        stu1.study();  // 输出:小明正在学习Java!
        stu1.eat();    // 输出:小明在食堂吃饭。
        
        // 再创建一个学生对象
        Student stu2 = new Student();
        stu2.name = "小红";
        stu2.age = 19;
        stu2.id = "2023002";
        
        stu2.study();  // 输出:小红正在学习Java!
    }
}

 类和对象总结:

  • 类是抽象的:它不特指某个具体事物,只是描述 “这类事物有什么特征、能做什么”(比如 “学生”);
  • 对象是具体的:它是类的 “实例”,有明确的属性值,能执行具体的行为(比如 “小明” 这个学生);
  • 一个类可以创建多个对象:就像用一个手机模板可以生产出无数部具体的手机。 

二、成员方法

我们已知成员方法的基本格式:

修饰符 返回值类型 方法名(参数列表) {
    // 方法体:具体的行为逻辑
}

接下来拆开理解各个部分:

1、修饰符

类的修饰符主要有三种:public(公有的)、protected(保护的)、private(私有的)。用来控制类、属性、方法的访问权限。

如果不写修饰符,则称“默认权限”,其访问范围是:同一包中的可访问、不同包中不可访问。所以也称“包权限”。

在日常的使用中,属性用 private 修饰、对外的方法用 public 修饰、仅子类继承的方法用 protected 修饰。

修饰符同一包中(不同类)不同包的子类中不同包的非子类中使用场景
public可访问可访问可访问希望被广泛访问的成员(如工具类的通用方法、类的对外接口方法)。
protected可访问可访问不可访问仅允许子类继承或同包类使用的成员(如父类中需要被子类重写的方法、共享的工具属性)。
private不可访问不可访问不可访问类内部的私有实现细节(如需要隐藏的属性、仅类内部调用的辅助方法)。

2、返回值类型

方法执行完可能会产生一个结果,返回值类型就是描述这个结果的 “数据类型”。如果方法执行后有返回值:返回值类型要写具体的数据类型(如int String Student等),并且方法体中必须用return关键字返回对应类型的值。否则,返回值类型写void(表示 “无返回值”)。

3、方法名

遵循 Java 命名规范:首字母小写,多个单词时从第二个单词开始首字母大写。

4、参数列表

方法执行时可能需要外部传入一些数据,参数列表就是用来定义这些 “输入数据” 的。格式为:数据类型 参数名1, 数据类型 参数名2, ...。

public class Student {
    String name;
    
    // int score是参数:接收外部传入的分数
    public void printScore(int score) {  
        System.out.println(name + "的考试成绩是:" + score + "分");
    }
}

// 调用方法
public class Test {
    public static void main(String[] args) {
        Student stu = new Student();
        stu.name = "小明";
        // 调用时传入实际参数(比如95)
        stu.printScore(95);  // 输出:小明的考试成绩是:95分
        stu.printScore(88);  // 输出:小明的考试成绩是:88分
    }
}

三、成员方法传参机制

调用方法时,实际参数会 “传递” 给形式参数,这里有个重要规则:参数传递是 “值传递”

  • 如果参数是基本数据类型(int double boolean等):传递的是 “值的副本”,修改形式参数不会影响实际参数。
  • 如果参数是引用数据类型(类、数组等):传递的是 “地址的副本”,通过形式参数修改对象的属性,会影响实际参数(因为它们指向同一个对象)。

举个例子:

public class Test {
    // 尝试修改参数值
    public static void changeNum(int num) {
        num = 100;  // 修改形式参数
    }
    
    public static void main(String[] args) {
        int a = 10;
        changeNum(a);  // 传入实际参数a
        System.out.println(a);  // 输出:10(a的值没被改变)
    }
}
public class Student {
    String name;
}

public class Test {
    // 尝试修改对象的属性
    public static void changeName(Student s) {
        s.name = "小李";  // 通过形式参数修改对象属性
    }
    
    public static void main(String[] args) {
        Student stu = new Student();
        stu.name = "小张";
        changeName(stu);  // 传入实际参数stu(对象)
        System.out.println(stu.name);  // 输出:小李(对象属性被改变)
    }
}

但是要注意,如果代码是这样的:

// 尝试将参数置空或换新对象
    public void test300(Person p) {

        // p = null; 
        // p = new Person(22, 8000);
        
    }
}

执行上面注释代码时只是改变方法内p的指向,并不会影响外部传入的Person对象本身。

四、重载

方法重载是Java中一种重要的特性,它允许在同一个类中定义多个同名方法,要求是它们的参数列表不同即--“同名不同参”。

实现重载有以下几点注意:

        1、方法的重载发生在同一个类中

        2、方法名必须相同

        3、参数列表必须不同,可以是参数类型、个数、顺序(类型不同)中的任意一个不同

        4、与返回类型无关

        5、与访问修饰符无关

下面我们来看一个例子:

public class OverloadingExample {
    
    // 无参数
    public void display() {
        System.out.println("无参数方法");
    }
    
    // 类型不同 
    public void display(int num) {
        System.out.println("整型参数: " + num);
    }
    
    // 个数不同
    public void display(int num1, int num2) {
        System.out.println("两个整型参数: " + num1 + ", " + num2);
    }
    
    // 顺序不同 
    public void display(String str, int num) {
        System.out.println("字符串和整型: " + str + ", " + num);
    }
    
    // 与上一个方法参数顺序不同
    public void display(int num, String str) {
        System.out.println("整型和字符串: " + num + ", " + str);
    }
    
    // 注意:仅返回类型不同不是重载,会编译错误
    // public int display() { return 0; } // 错误
    
    public static void main(String[] args) {
        OverloadingExample obj = new OverloadingExample();
        
        obj.display();          
        obj.display(10);          
        obj.display(10, 20);     
        obj.display("Hello", 30); 
        obj.display(40, "World"); 
    }
}

 当调用重载方法时,编译器会根据参数类型选择最合适的方法,那么就涉及到自动类型转换和方法的“具体性”。

首先是自动类型转换的影响,在调用重载方法时会按照以下顺序尝试匹配:

  • 精确匹配:参数类型完全匹配
  • 自动类型转换匹配:如果没有精确匹配,尝试自动类型转换
  • 装箱/拆箱匹配:基本类型与对应包装类之间的转换
  • 可变参数匹配:最后考虑可变参数方法。

浅看一个例子:

public class OverloadDemo {
    public void test(int i) {
        System.out.println("int: " + i);
    }
    
    public void test(double d) {
        System.out.println("double: " + d);
    }
    
    public static void main(String[] args) {
        OverloadDemo demo = new OverloadDemo();
        demo.test(5);    // 调用test(int)
        demo.test(5.0);  // 调用test(double)
        demo.test('a');  // 调用test(int) - char自动转为int
    }
}

 那么什么是"最具体"的方法呢?当多个重载方法都匹配调用时,Java会选择"最具体"的方法。方法A比方法B更具体,方法A的参数类型可以隐式转换为方法B的参数类型,但反过来不行。

public class MostSpecific {
    public void print(Object o) {
        System.out.println("Object");
    }
    
    public void print(String s) {
        System.out.println("String");
    }
    
    public void print(Integer i) {
        System.out.println("Integer");
    }
    
    public static void main(String[] args) {
        MostSpecific demo = new MostSpecific();
        demo.print("hello"); // 输出"String" - 比Object更具体
        demo.print(123);     // 输出"Integer" - 比Object更具体
        demo.print(45.67);   // 输出"Object" - 没有更具体的匹配
    }
}

五、可变参数

我们先来理解一下可变参数,可变参数本质上是一个语法糖,它让方法可以接受任意数量(包括零个)的指定类型参数。在方法内部,可变参数被当作数组处理,它的基本语法格式是:

// 方法声明格式
[访问修饰符] 返回类型 方法名(参数类型... 参数名) {
    // 方法体
}

 用法示例:

public static void printInfo(String title, String... messages) {
    System.out.println("标题: " + title);
    System.out.println("内容: ");
    for (String msg : messages) {
        System.out.println("- " + msg);
    }
}

// 调用
printInfo("通知");  // 只有标题,没有内容
printInfo("警告", "系统即将关闭", "请保存工作");

 好啦 现在我们来讲一讲它的注意事项:

  1. 位置限制:可变参数必须是方法参数的最后一个参数。试想,如果有连续的相同类型的参数+固定参数,就会存在参数匹配问题。
  2. 个数限制:一个方法只能有一个可变参数
  3. null处理:可以传入null(可变参数数组为null),但需要小心NullPointerException
  4. 重载问题:可变参数方法重载可能导致歧义,因为重载判断条件之一是参数列表的个数不同。

最刚开始的时候,我们提到可变参数在方法内部是以数组形式处理的,其实它本质上也可以和数组参数相互代替。下面通过几个具体示例展示这种用法:

// 这两种声明几乎等效
void func(String... strs);
void func(String[] strs);

// 但调用方式不同
func("a", "b", "c");  // 只能用可变参数形式
func(new String[] {"a", "b", "c"});  // 两种都可以

public class ArrayConversionExample {
    
    // 可变参数方法
    public static void printNames(String... names) {
        System.out.println("共有 " + names.length + " 个名字:");
        
        // 作为数组使用
        for (String name : names) {
            System.out.println("- " + name);
        }
    }
    
    public static void main(String[] args) {
        // 方式1:直接传递多个参数
        printNames("张三", "李四", "王五");
        
        // 方式2:传递数组
        String[] nameArray = {"赵六", "钱七", "孙八", "周九"};
        printNames(nameArray); // 可以直接传递数组
        
        // 方式3:创建并传递匿名数组
        printNames(new String[] {"吴十", "郑十一"});
    }
}

 所以总结下来就是:

可变参数在方法内部就是数组:可以使用所有数组操作(length属性、下标访问等)。

调用方式灵活:可以传递多个独立参数,可以传递数组:method(new T[]{a, b, c}),也可以传递现有数组变量:method(arr)。

空参数处理:不传参数时,方法内收到的是一个长度为0的数组,不是null。

与数组参数的互操作性:可变参数方法可以接受数组参数,数组参数方法也可以接受可变参数方法返回的数组。

六、作用域

作用域指的是程序中变量、方法或类等标识符的有效访问范围。它决定了在程序的哪些位置可以引用某个标识符。作用域主打一个“就近原则”。

当内层作用域声明了与外层同名的变量时,使用最近的那个变量。当然内层可以访问外层变量,比如下面的this.value,但反之不行。

public class ShadowDemo {
    int value = 10;  // 类作用域
    
    public void demo() {
        int value = 20;  // 遮蔽类变量
        
        System.out.println(value);    // 20(局部变量)
        System.out.println(this.value); // 10(类变量)
    }
}

还有一个特殊的static--静态方法/代码块只能访问静态成员(会在后续文章详细讲解):

public class StaticDemo {
    static int classVar = 10;
    int instanceVar = 20;
    
    static {
        System.out.println(classVar);  // √
        // System.out.println(instanceVar);  // ×
    }
}

七、构造器

构造器是Java中用于初始化对象的特殊成员方法,在创建对象时自动调用,用于初始化对象。构造器名称必须与类名完全相同,并且没有返回类型(连void也没有),也不能被static、final、abstract等修饰。

在之前的例子中,有些类没有定义任何构造器,但实际上编译器会自动提供一个无参的默认构造器:

public class Book {
    // 编译器会自动提供:public Book() {}
}

Book book = new Book();

 一旦定义了构造器,编译器就不再提供默认构造器。

构造器不能被直接调用,通常是创建实例时通过new关键字调用:

public class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

构造器常常和重载结合使用,能提高代码的可用性。

public class Rectangle {
    private int width;
    private int height;
    
    // 无参构造器
    public Rectangle() {
        this.width = 10;
        this.height = 5;
    }
    
    // 带参构造器
    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }
}

八、this

简单来说,this 关键字代表当前对象的引用。它只能在类的非静态方法(包括构造方法)内部使用,指向正在调用该方法的那个对象实例。我们可以把 this 理解成“我自己”或者“当前这个对象”。

this 最常用的场景是:解决实例变量和局部变量的命名冲突。

public class Student {
    private String name; // 实例变量
    private int age;     // 实例变量

    // 构造方法的参数名与实例变量名相同
    public Student(String name, int age) {
        // 如果不使用 this,左边的 name 和 age 指的是实例变量
        // 右边的 name 和 age 指的是构造方法的参数
        this.name = name; // 将参数 name 的值赋给当前对象的实例变量 name
        this.age = age;   // 将参数 age 的值赋给当前对象的实例变量 age
    }

    // Setter 方法也存在同样的问题
    public void setName(String name) {
        this.name = name; // 明确表示将参数赋值给当前对象的实例变量
    }

    // Getter 方法中,虽然没必要,但有时为了清晰也会使用
    public String getName() {
        return this.name; // 明确表示返回当前对象的实例变量 name
    }
}

此外,this还用于在构造方法中调用其他构造方法,但是必须是第一条语句。

public class Rectangle {
    private int width;
    private int height;
    private String color;

    // 构造方法1:接收宽和高
    public Rectangle(int width, int height) {
        this(width, height, "Black"); // 调用构造方法2,并设置默认颜色为 "Black"
        // 注意:this(...) 必须是第一条语句
    }

    // 构造方法2:接收宽、高和颜色
    public Rectangle(int width, int height, String color) {
        this.width = width;
        this.height = height;
        this.color = color;
    }
}

但是this的使用也有一些注意事项:

  1. this 不能用在静态方法中:静态方法( static )属于类,而不是某个对象实例。因此在静态方法中不存在“当前对象”的概念,使用 this 会导致编译错误。
  2. this 的本质是引用:和引用一个普通对象一样,我们可以用 this 来访问成员变量和方法(this.成员名),也可以将 this 作为返回值或参数传递。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值