4.面向对象你真的掌握的很好吗?

本文介绍了封装的概念及其在Java中的实现方式,并探讨了类之间的关系、更改器与访问器方法的区别,以及局部变量声明、值传递与引用传递等知识点。同时,文章还详细讲解了对象构造过程中的执行顺序,并给出了实用的类设计技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 封装是什么?

封装其实就是实现了数据的隐藏,决不能让类中的方法直接访问其他类的实例字段,所以一般我们会将那些实例字段设置为private,只能通过对象的方法与对象数据进行交互。

@Data
public class Person{

    public int id;

    private int age;

    public void setId(int id) {
        if (id <= 0) {
            return;
        }
        this.id = id;
    }

    public void setAge(int age) {
        if (age <= 0) {
            return;
        }
        this.age = age;
    }
}

可以看到,其他类对于age字段,的修改,只能通过setter方法,但对于id字段,可以任意修改,比如改成负的,别人大可以不去调用setId方法来实现数据的更改,因为它设置成了public

2. 类之间的关系

这个一般在看uml图类之间的关系时用的到,先来说说概念。

依赖,两个类之间是use-a,一种使用关系,比如在类A的方法中将B做为形参,返回值,局部变量,静态方法调用,这些都算是使用关系,如下代码所示。

public class Person{

    //作为形参使用
    public void usePhone(Phone phone) {
        phone.call();
    }
    public void usePhone2() {
        //局部变量使用
        Phone phone = new Phone();
        phone.call();
    }
    public void usePhone3() {
        //作为静态方法调用
        Phone.play();
    }
    //作为返回值
    public Phone getPhone() {
        return new Phone();
    }

}

class Phone{

    public void call() {

    }
    public static void play() {

    }
}

聚合,这种关系比较简单,即has-a关系,对象B作为类A的成员变量存在,如下所示。

public class Person{
    private Phone phone = new Phone();
}

class Phone{

}

继承,这种关系就更简单了,使用extends关键字即可,父类的属性和方法,子类都有。

public class Person{
}

class Man extends Person {

}

在uml图中,对应的箭头关系是这样的,书中没有详细介绍关联,原文提到了关联的标准记法不是很清晰,所以我认为掌握上面三种就够了。

在这里插入图片描述

3. 更改器方法与访问器方法

这个概念比较新奇,之前没怎么听过,但其实具体的用法我们都见过了。

更改器方法(mtator method),调用该方法会导致原对象状态的变化。

反之,访问器方法(accessor method),只访问对象而不更改对象的方法。

看下面的例子,可以看到前两个方法使得原有的对象属性发生了改变,后面的方法,一个是访问获取属性,另一个是返回了新的对象,这种都算是访问器方法。

public class Demo2 {
    public static void main(String[] args) {
        Student student = new Student();
        //更改状态
        student.setName("dd");
        student.addId();
        //未更改原对象状态
        student.getName();
        Student student1 = student.newStudent();
    }


}

@Data
class Student {

    private int id;

    private String name;

    //原对象状态变化了
    public void setName(String name) {
        this.name = name;
    }
    //原对象状态变化了
    public void addId() {
        this.id += 1;
    }

    //只是获取了该对象的字段
    public String getName() {
        return name;
    }
    //看似更改了
    public Student newStudent() {
        Student student = new Student();
        student.setId(1);
        student.setName("bb");
        return student;
    }
}

4. 用var声明局部变量

虽然我们现在用的是jdk8,但未来的升级是大势所趋,所以来点小插曲。

在jdk10中,可以使用var来声明局部变量,因为编译器可以通过后面的赋值来判断类型,但需要强调的是,var关键字,只能用于方法中的局部变量,其他地方无法推测类型。

public class Demo2 {
    public static void main(String[] args) {
        var a = 10;
        var s = "hello bb";
    }

}

5. 值传递与引用传递

Java是值传递还是引用传递?

先给结论,值传递

首先字面解释下,什么是值传递?就是在方法传参的时候,传入的形参变化不会导致实参的变化,比如你传一直大象给我,我借用过程中,创建了一个大象的副本,即便这个大象副本受伤了,最终还到你受伤的,还是那只完好无损的大象。

反之,引用传递指的就是,传入的形参和实参其实是同一份,同样如果大象受伤了,少胳膊少腿了,真正交还给你的,依旧是那只少胳膊少腿的大象。

那我们用代码验证下吧,分别用基本数据类型和引用数据类型做例子。

public class Demo4 {
    public static void main(String[] args) {
        //使用基本数据类型
        int x = 10;
        calculate(x);
        System.out.println(x);//输出 10

        //使用引用数据类型
        Object obj1 = new Object();
        Object obj2 = new Object();
        System.out.println(obj1.hashCode());
        System.out.println(obj2.hashCode());
        swap(obj1, obj2);
        System.out.println(obj1.hashCode());
        System.out.println(obj2.hashCode());
    }

    public static void calculate(int x) {
        x *= 3;
    }

    public static void swap(Object o1,Object o2) {
        Object temp = o1;
        o1 = o2;
        o2 = temp;
    }
}
10
1746572565
989110044
1746572565
989110044

从第一个例子来看,我们传入什么值最终返回的就是什么值,其实内部就是创建了一个形参x的副本,最终方法结束就销毁掉了。

第二个例子是两个对象进行了交换,而如果真的是引用传递,那么这中间就不会创建副本,最终的结果就是两个对象的hashcode也会对调,但我们可以发现没有任何的变化,所以在方法中的形参,都是以副本的方式存在的,最终方法结束,副本都被销毁。

我们可以得出的结论有三个。
1.方法不能修改基本数据类型的参数,即值类型传什么还是什么。
2.方法可以改变对象参数的状态(这个例子没有证明,但还是说明下,虽然都是用的副本,引用指向的是同一个对象,所以会导致真实对象发生变化)
3.方法不能让对象参数引用一个新的对象,即无法改变引用的指向。

6. 构造执行顺序

我们再来回顾下一个对象的构造过程中,是怎样进行初始化的,这里为了简便,暂时不考虑父类的情况。

public class Demo5 {
    public static void main(String[] args) {
        Demo5 demo5 = new Demo5();
    }

    {
        System.out.println("代码块执行");
    }

    private int id = setId();

    public  int setId() {
        System.out.println("初始化id");
        return 1;
    }
    static {
        System.out.println("静态代码块执行");
    }

    public Demo5() {
        System.out.println("构造器执行");
    }
}

静态代码块执行
代码块执行
初始化id
构造器执行

可以看到,静态代码块是最先执行的,这里没有放静态初始化方法,如果有,那么就要看这两个的顺序了,在前面的先执行。

之后是代码块和初始化方法,这个也是按顺序来,最终才是构造器方法执行。

但这里我还是忽略了一点,即字段的初始值是什么赋予的呢?即0或者null值,如果是static修饰,那么赋值将被排在static代码块前面,如果不是静态的,那么也会在普通代码块之前赋值。

7. 类设计技巧

本章的最后给出了一些类设计的技巧,让我们一起看一下吧。
1.一定要保证数据私有。

这个是封装性的体现,而一旦你将字段设置成了public,就会造成在类外不经意的修改,有些莫名其妙的方法就是这么来的,你可以想象下,如果String类里的属性都是public的,会有什么影响?

2.一定要对数据进行初始化。

我认为这是为了防止npe异常,如果通过null来调用某些方法,就会出现这种问题,而一旦有初始值了,只要不为null,就不会发生这种问题。

3.不要在类中使用过多的基本类型。

之前我理解的意思是不要用太多int,long之类的,而应该使用包装类,但其实并不是这个意思。
比如我一个Person类,有nameage字段,Student同样也有名称和年龄,如果使用继承的话,也应该在类中包含Person对象,这样未来修改起来也方便。

4.不是所有的域都需要独立的域访问器和域更改器。

比如一个员工的生日一般是不会变的,如果之前构造了员工对象,那么生日的setBirthday方法其实不应该再使用了。

5.将职责过多的类进行分解。
单一职责,这个还是得看实际的使用,我们不能让一个类有过多的职责,但有时也不一定只能有一项职责,还是按实际的来。

6.类名和方法名要能够体现它们的职责,不要乱命名。

7.优先使用不可变的类,可以防止线程安全问题。

薪火之道,薪尽火传,个人的得失虽然重要,但更重要的在于,世界因你的成就而获得多大的影响。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值