java中类和对象

文章目录

  • 一、this关键字
    • [1.1 this 的核心用法](#1.1 this 的核心用法)
    • [1.2 this 的核心禁忌](#1.2 this 的核心禁忌)
  • 二、super关键字
    • [2.1 super的核心用法](#2.1 super的核心用法)
  • 三、封装
    • [3.1 什么是封装?](#3.1 什么是封装?)
    • [3.2 封装的目的](#3.2 封装的目的)
    • [3.3 实现封装](#3.3 实现封装)
    • [3.4 代码示例](#3.4 代码示例)
  • 四、访问权限控制符
    • [4.1 包访问权限](#4.1 包访问权限)
    • [4.2 protected](#4.2 protected)

一、this关键字

this 是 Java 中的关键字,也是一个隐式引用------ 它永远指向「当前正在执行这个方法 / 构造器的对象实例」。

  • 当前:谁调用这个方法,this 就指向谁;
  • 隐式:非静态方法中,Java 会自动给你加 this,你不写也存在。
java 复制代码
class Person {
    String name;

    // 打印当前对象的name
    void printName() {
        // 这里的this可以省略,Java会自动补全
        System.out.println(this.name); 
    }
}

public class ThisDemo {
    public static void main(String[] args) {
        // 创建两个对象
        Person p1 = new Person();
        p1.name = "张三";
        Person p2 = new Person();
        p2.name = "李四";

        // p1调用printName → this指向p1 → 输出"张三"
        p1.printName(); 
        // p2调用printName → this指向p2 → 输出"李四"
        p2.printName(); 
    }
}

核心结论:this 是对象的 "自引用",方法属于哪个对象,this 就指向哪个对象。

1.1 this 的核心用法

用法1:区分同名的成员变量和局部变量(最常用)

这是新手最先接触的场景,解决 "变量名冲突" 问题。

java 复制代码
class Student {
    // 成员变量(对象级)
    private String name;
    private int age;

    // 构造器:参数名和成员变量名相同
    public Student(String name, int age) {
        // this.name → 成员变量(对象的name)
        // name → 构造器参数(局部变量)
        this.name = name;
        this.age = age;
    }

    // 普通方法:参数名和成员变量名相同
    public void setName(String name) {
        this.name = name;
    }

    // 测试
    public void show() {
        System.out.println("姓名:" + this.name + ",年龄:" + this.age);
    }
}

public class ThisUsage1 {
    public static void main(String[] args) {
        Student s = new Student("王五", 18);
        s.show(); // 输出:姓名:王五,年龄:18
        s.setName("赵六");
        s.show(); // 输出:姓名:赵六,年龄:18
    }
}

关键说明:

  • 变量查找遵循「就近原则」:如果不加 this,name = name 是 "局部变量赋值给自己",成员变量永远是默认值(null/0);
  • this.成员变量 明确告诉 Java:我要赋值的是 "当前对象的成员变量",而非局部变量。

用法 2:在构造器中调用当前类的其他构造器(this(参数))

减少代码冗余,统一初始化逻辑,是构造器复用的核心方式。

规则(必须遵守):

  1. this(...) 必须是构造器的第一行代码;
  2. 不能和 super(...)(调用父类构造器)同时使用;
  3. 不能递归调用(比如构造器 A 调用 this(),而 this() 又指向 A)。
java 复制代码
class Phone {
    private String brand;
    private String color;
    private int price;

    // 无参构造器:调用3参构造器,给默认值
    public Phone() {
        this("小米", "白色", 1999); // 第一行代码,调用3参构造器
    }

    // 2参构造器:调用3参构造器,price默认0
    public Phone(String brand, String color) {
        this(brand, color, 0); // 第一行代码,调用3参构造器
    }

    // 3参构造器:核心初始化逻辑
    public Phone(String brand, String color, int price) {
        this.brand = brand;
        this.color = color;
        this.price = price;
    }

    public void show() {
        System.out.println("品牌:" + brand + ",颜色:" + color + ",价格:" + price);
    }
}

public class ThisUsage2 {
    public static void main(String[] args) {
        Phone p1 = new Phone(); // 无参构造 → 默认值
        p1.show(); // 输出:品牌:小米,颜色:白色,价格:1999

        Phone p2 = new Phone("华为", "黑色"); // 2参构造 → price=0
        p2.show(); // 输出:品牌:华为,颜色:黑色,价格:0

        Phone p3 = new Phone("苹果", "紫色", 6999); // 3参构造
        p3.show(); // 输出:品牌:苹果,颜色:紫色,价格:6999
    }
}

如果没有 this(...),你需要在每个构造器中重复写 this.brand = brand; 这类代码,复用性差且易出错。

用法3:在内部类中访问外部类对象(外部类名.this)

当存在「成员内部类」时,内部类的 this 指向内部类对象,需用 外部类名.this 明确指向外部类对象

java 复制代码
// 外部类
class Outer {
    private String msg = "外部类的消息";

    // 成员内部类
    class Inner {
        private String msg = "内部类的消息";

        public void showMsg() {
            // this.msg → 内部类的成员变量
            System.out.println("内部类msg:" + this.msg); 
            // Outer.this.msg → 外部类的成员变量
            System.out.println("外部类msg:" + Outer.this.msg); 
        }
    }
}

public class ThisUsage5 {
    public static void main(String[] args) {
        // 创建内部类对象
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.showMsg();
        // 输出:
        // 内部类msg:内部类的消息
        // 外部类msg:外部类的消息
    }
}

1.2 this 的核心禁忌

  1. 静态方法 / 静态代码块中用 this,这是错误的:静态成员属于「类」,不属于「单个对象」,this 指向对象,静态环境无对象。
  2. this(...) 不在构造器第一行,Java 语法强制要求,确保构造器执行前先完成初始化
  3. 递归调用构造器,两个构造方法相互调用,造成栈溢出。
java 复制代码
class Test {
    int a;

    // 1. 静态方法中用this
    public static void test1() {
        this.a = 10; // ❌ 错误:静态方法无this
    }

    // 2. 构造器中this(...)不在第一行
    public Test() {
        int b = 20;
        this(10); // ❌ 错误:this(...)必须是构造器第一行
    }

    public Test(int a) {
        this.a = a;
    }

    // 3. 非静态方法中用this(正确)
    public void test2() {
        System.out.println(this.a); // ✅ 正确
    }
}

二、super关键字

super关键字适用于引用父类对象的成员方法,成员变量,构造方法

2.1 super的核心用法

用法1:当子类和父类中同时存在同一个成员变量或者同一个成员方法的时候,此时如果想要访问父类中的成员变量和成员方法,此时需要使用super点成员变量/成员方法的形式进行访问。

看以下代码Anmal类和dog类当中都存在name这个成员变量时:

java 复制代码
public class Animal {
    public String name = "招财";
    public int age;

    public void eat() {
        System.out.println(this.name + "正在吃饭");
    }
}
public class Dog extends Animal{

    public String name = "旺财";

    public void bark() {
        System.out.println(this.name + "正在汪汪叫");
    }
}

public static void main(String[] args) {
        Dog dog = new Dog();
        dog.bark();
}

此时在main中执行代码可以发现得到的

此时结果显示访问的是子类中的name,此时如果我们想要访问父类中的name该如何进行访问呢?此时我们只需要在name前面加上super关键字,表示指向父类中的name,此时再点击输出则输出父类中的name,如下图:

java 复制代码
public void bark() {
        System.out.println(super.name + "正在汪汪叫");
    }

当父类中的构造方法存在参数的情况下,子类在完成构造前需要先对父类进行构造,此时如何对父类进行构造就需要使用super关键字。

java 复制代码
public class Animal {
    public String name = "招财";
    public int age;

    public void eat() {
        System.out.println(this.name + "正在吃饭");
    }

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class Dog extends Animal{

    public String name = "旺财";

    public void bark() {
        System.out.println(super.name + "正在汪汪叫");
    }


}

在上面Animal类定义了一个构造方法,当dog类去继承他的时候,必须先完成父类的构造否则就会出现如下错误:

而对父类进行构造则需要使用super关键字,使用super关键字对父类进行构造

java 复制代码
public class Dog extends Animal{

    public String name = "旺财";

    public Dog(String name, int age) {
        super(name, age); //使用super关键字对父类进行构造
    }

    public void bark() {
        System.out.println(super.name + "正在汪汪叫");
    }
}

三、封装

3.1 什么是封装?

把对象的属性(成员变量)和行为(方法)结合成一个独立的整体,并隐藏对象的内部实现细节,只通过指定的"接口"(公开方法)对外提供访问方式。

用生活中的例子类比:

  • 你使用手机时,只需要点击屏幕,按按键(对外的接口),不需要知道内部的电路板,芯片是如何工作的(隐藏的细节)。
  • 手机厂商通过封装,把复杂的内部逻辑藏起来,只给你简单的操作方式,同时避免你误操作损坏内部元件。

3.2 封装的目的

  • 安全性:防止外部随意修改对象的核心数据,避免数据错误。
  • 易用性:对外提供简洁的接口,使用者无需关心内部实现。
  • 可维护性:内部逻辑修改,只要接口不变,外部代码无需改动。

3.3 实现封装

java中实现封装的核心手段主要通过下面三步:

  1. 私有化成员变量:用private修饰属性,使其仅在类内部可访问。
  2. 提供公开的访问方法:编写getter(获取属性值)和setter(设置属性值)方法(用public修饰),作为外部访问私有属性的唯一通道。
  3. 在访问方法中添加控制逻辑:比如数据校验,格式转换,保证数据的合法性。

3.4 代码示例

场景:模拟一个"银行账户"类

反例:不封装的代码(问题百出)

如果不做封装,成员变量直接用public,外部可以随意修改,会导致数据混乱、安全问题:

java 复制代码
//不封装的银行账户类
class UnEncasulatedAccount {
    //公开的成员变量,外部可直接修改
    public String accountNumber; //账号
    public double balance; //余额

    //无任何访问控制
    public UnEncasulatedAccount(String accountNumber, double balance) {
        this.accountNumber = accountNumber;
        this.balance = balance;
    }
}
//测试类
public class Demo1 {
    public static void main(String[] args) {
        //创建账户,初始余额为1000元
        UnEncasulatedAccount account = new UnEncasulatedAccount("6228052498596278",1000.0);

        //问题1:外部可以随意修改余额(比如改成负数)
        account.balance = -5000.0;
        System.out.println("账户余额:" + account.balance);  //输出:-5000(不合理)

        //问题2:外部可以随意修改账号(账号使唯一的,不应该被修改)
        account.accountNumber = "123456789";
        System.out.println("账号:" + account.accountNumber); //输出:123456789(数据混乱)
    }

问题分析:

  • 余额被改成负数,不符合现实逻辑;
  • 账号作为核心标识,被随意修改,导致数据混乱;
  • 外部直接操作内部数据,没有任何校验和控制。
java 复制代码
//封装的银行账户类
class EncasulatedAccount {
    //1.私有化成员变量:仅类内部可访问
    private String accountNumber; //账号(一旦创建不可修改)
    private double balance; //余额(只能合法修改)

    //2.构造方法:初始化账号和余额,对初始化余额做校验
    public EncasulatedAccount(String accountNumber, double balance) {
        //校验账号是否合法(非空、长度符合)
        if(accountNumber == null || !accountNumber.matches("\\d{19}")) {
            throw new IllegalArgumentException("账号格式错误!必须是19位数字");
        }
        this.accountNumber = accountNumber;

        //校验初始余额是否合法(不能为负)
        if(balance < 0) {
            throw new IllegalArgumentException("初始余额不能为负数!");
        }
        this.balance = balance;
    }

    //3.提供getter方法:获取账号(仅读,无setter,保证账号不可修改)
    public String getAccountNumber() {
        return accountNumber;
    }

    //4.提供getter方法:获取余额
    public double getBalance() {
        return balance;
    }

    //5.提供可控的setter/操作方法:修改余额(仅支持合法操作)
    //存款方法(只能加钱)
    public void deposit(double amount) {
        if(amount <= 0) {
            throw new IllegalArgumentException("存款金额必须大于0!");
        }
        this.balance += amount;
        System.out.println("存款成功!存入:" + amount + ",当前余额:" + this.balance);
    }

    //取款方法(只能减钱,且不能超余额)
    public void withdraw(double amount) {
        if(amount <= 0) {
            throw new IllegalArgumentException("取款金额必须大于0!");
        }
        if(amount > this.balance) {
            throw new IllegalArgumentException("余额不足!当前余额:" + this.balance);
        }
        this.balance -= amount;
        System.out.println("取款成功!取出:" + amount + ",当前余额:" + this.balance);
    }

    @Override
    public String toString() {
        return "账户信息:\n账号:" + accountNumber + "\n余额:" + balance;
    }
}
public class Demo2 {
    public static void main(String[] args) {
        try {
            // 1. 创建合法账户(初始余额1000)
            EncasulatedAccount account = new EncasulatedAccount("6228480402564890018", 1000.0);
            System.out.println(account);

            // 2. 合法操作:存款500
            account.deposit(500.0); // 输出:存款成功!存入:500.0,当前余额:1500.0

            // 3. 合法操作:取款300
            account.withdraw(300.0); // 输出:取款成功!取出:300.0,当前余额:1200.0

            // 4. 非法操作1:取款2000(余额不足)
            //account.withdraw(2000.0); // 抛出异常:余额不足!当前余额:1200.0

            // 5. 非法操作2:存款负数
            //account.deposit(-100.0); // 抛出异常:存款金额必须大于0!

            // 6. 无法直接修改账号(没有setAccountNumber方法)
            //account.accountNumber = "123456"; // 编译报错:accountNumber是private

        } catch (IllegalArgumentException e) {
            System.out.println("操作失败:" + e.getMessage());
        }
    }
}

代码核心讲解:

  1. 私有化属性:accountNumber 和 balance 用 private 修饰,外部无法直接访问 / 修改;
  2. 构造方法校验:创建账户时,校验账号格式和初始余额的合法性,避免创建无效账户;
  3. 仅开放必要接口:
    • 账号只提供 getter,无 setter,保证账号一旦创建就不可修改;
    • 余额不提供直接的 setter,而是通过 deposit()(存款)和 withdraw()(取款)方法修改,且每个方法都有数据校验;
  4. 异常处理:非法操作时抛出明确的异常信息,方便定位问题。

四、访问权限控制符

Java 有 4 种访问修饰符,可见范围:private < default < protected < public;
private 用于封装内部细节,public 用于对外提供功能,protected 用于子类继承,default 用于同包复用;

修饰符 本类 同包其他类 子类(不同包) 所有类(全局) 常用场景
private 类内部的变量、工具方法
default 同包内复用的方法 / 变量(无关键字)
protected 子类需要继承的方法 / 变量
public 对外提供的接口、公开方法

4.1 包访问权限

包访问权限(也叫「默认访问权限」/「包私有」)是 Java 四种访问权限中没有关键字的那个 ------ 当你给类、方法、变量不写任何访问修饰符(public/private/protected 都不写)时,它就默认是「包访问权限」。

核心规则:只有同一个包下的类能访问,不同包下的类(哪怕是子类)都访问不了。

在这里小编创建了三层包目录,在demo1包底下存在两个类:people和Test1类,在demo2包底下存在一个类Test2,在people定义一个包访问权限的成员变量id,如下:

java 复制代码
public class People {
    public String name;
    public int age;
    int id; //包访问权限

    public People(String name, int age, int id) {
        this.name = name;
        this.age = age;
        this.id = id;
    }

    public void introduce() {
        System.out.println("我的名字是" + name);
    }
}

而id这个类只能在Test1中进行使用,而不能在Test2中进行使用。

4.2 protected

protected(受保护)是java中用于控制类成员(变量、方法、构造器)可见性的修饰符,核心规则:

被protected修饰的成员,能被本类 + 同包类 + 不同包的子类访问,但不能被不同包的非子类访问

它的设计目的:让父类把"需要被子类继承复用"的成员开放给子类,同时屏蔽无关类的访问,是"封装"和"继承"之间的平衡。

为了让你直观理解,我用 "包 + 类关系" 拆分 4 种核心场景,每个场景配代码示例。

场景 1:本类中访问 → 完全可行

protected 成员和 private/public 一样,在定义它的类内部可以随意访问,这是所有访问修饰符的基础规则。

java 复制代码
// 包:MyProtected
package MyProtected;

public class ProtectedDemo1 {
    //定义protected成员
    protected String name = "父类名称";
    protected void sayHello() {
        System.out.println("父类的hello方法");
    }

    //本类内部访问protected成员可行
    public void test() {
        System.out.println(name); // ✅ 访问protected变量
        sayHello(); // ✅ 调用protected方法
    }

    public static void main(String[] args) {
        ProtectedDemo1 demo1 = new ProtectedDemo1();
        demo1.test();
    }
}

场景 2:同包的其他类访问 → 完全可行

protected 包含了 default(默认)的访问范围,同包内的任意类(无论是否是子类)都能直接访问 protected 成员。

java 复制代码
// 包:MyProtected
package MyProtected;

// 非子类,只是同包的普通类
public class Demo2 {
    public static void main(String[] args) {
        ProtectedDemo1 demo1 = new ProtectedDemo1();
        System.out.println(demo1.name);  // ✅ 同包,可访问protected变量
        demo1.sayHello(); // ✅ 同包,可调用protected方法
    }
}

场景 3:不同包的子类访问 → 分两种情况(核心易错点)

这是 protected 最容易踩坑的地方:不同包的子类访问父类的 protected 成员,必须通过 "子类对象" 或 "子类自身的 this",不能直接通过 "父类对象" 访问。

情况 3.1:通过子类对象 /this 访问 → 可行

java 复制代码
public class MyProtected2Demo extends ProtectedDemo1 {
    public void test1() {
        //方式1:通过this访问(子类自身)->可行
        System.out.println(this.name); // ✅ this代表子类对象,可访问
        this.sayHello();

        //方式2:通过子类实例访问->可行
        MyProtected2Demo child = new MyProtected2Demo();
        System.out.println(child.name);
        child.sayHello();
    }
}

情况 3.2:直接通过父类对象访问 → 不可行

java 复制代码
package MyProtected2;

import MyProtected.ProtectedDemo1;

public void test2() {
        //错误示例:直接new父类对象访问-》编译报错
        ProtectedDemo1 parent = new ProtectedDemo1();
        //System.out.println(parent.name); //编译报错 !
        // parent.sayHello(); //编译报错:sayHello() 在 MyProtected.ProtectedDemo1 中是 protected 访问控制

        //向上转型,再去调用父类方法和变量
        ProtectedDemo1 child = new MyProtected2Demo();
        //System.out.println(child.name); //编译报错
        //child.sayHello(); //编译报错
}

报错原因:Java 设计 protected 时,只允许子类 "继承并使用" 父类的 protected 成员,不允许子类 "以父类的身份" 去访问 ------ 本质是避免子类绕过规则,间接访问父类的 protected 成员。

场景 4:不同包的非子类访问 → 完全不可行

java 复制代码
// 包:MyProtected2(和MyProtected不同包)
package MyProtected2;

import MyProtected.ProtectedDemo1;

// 非子类,无关类
public class MyProtected2Demo2 {
    public static void main(String[] args) {
        ProtectedDemo1 protectedDemo1 = new ProtectedDemo1();
        System.out.println(protectedDemo1.name);  // ❌ 编译报错
        protectedDemo1.sayHello();  // ❌ 编译报错
        protectedDemo1.test();
    }
}

protected 易踩坑的 3 个关键点

  1. "不同包子类访问" 的限制:必须通过子类对象(this / 子类实例),不能通过父类对象,这是新手最容易错的点;
  2. 类的修饰符限制:protected 不能修饰外部类(外部类只能用 public 或 default),只能修饰类的成员(变量、方法、内部类、构造器);
  3. 构造器的 protected 用法:如果父类的构造器是 protected,那么只有同包类或子类能创建父类实例(常用于 "只能被继承,不能直接实例化" 的父类设计)。
java 复制代码
// 父类构造器为protected
public class Parent {
    protected Parent() {} // 构造器
}

// 不同包子类:可继承并调用构造器
public class Child extends Parent {
    public Child() {
        super(); // ✅ 子类可调用父类的protected构造器
    }
}

// 不同包非子类:无法创建Parent实例
public class Test {
    public static void main(String[] args) {
        new Parent(); // ❌ 编译报错
    }
}
相关推荐
*.✧屠苏隐遥(ノ◕ヮ◕)ノ*.✧2 小时前
Java 集合 (Collection)
java·开发语言
后端AI实验室2 小时前
我让AI review了自己写的代码,然后删掉了30%
java·ai
SunnyDays10112 小时前
Java 实战:将 Word 文档高效转换为多页 TIFF 图片(含批量处理)
java·word转tiff
一直都在5722 小时前
Java基础面经(二)
java·开发语言
银发控、2 小时前
record类
java·开发语言
jiayong232 小时前
可视化流程设计器技术对比:钉钉风格 vs BPMN
java·前端·钉钉
左左右右左右摇晃2 小时前
MyBatis & MyBatis-Plus 面试题整理
java·笔记
xiaoye37082 小时前
CentOS 7 搭建Maven私服
java·maven