As I was writing my first semester of teaching reflection, I got the idea to kick off a series of student questions called Coding Tangents. In this series, I’ll be tackling student questions with clear, easy-to-follow explanations that demystify common programming language syntax. In particular, I’d like to tackle the difference between public and private in Java today.

The Problem Students Encounter

很多时候,当我们教Java时,我们总是将许多语法留为机械过程。 换句话说,我们告诉学生诸如上市,静态的,and 私人的 will be explained to them later. In the meantime,they just have to trust that we will actually explain those concepts later.

几乎总是留给以后讨论的这些语法之一是私人的与上市。 这些关键字称为访问修饰符,我们将在本文中对其进行深入研究。

但首先,让我们看一些几乎可以肯定会引起有关访问修饰符问题的代码示例:



public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, World!");
  }
}

public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, World!");
  }
}



为了教授Java,我们经常被困在使用这些可怕的五行代码来介绍该语言。 毕竟,这是运行Java程序所需的最低要求。

结果,我们经常被迫告诉学生一些类似的事情:

不用担心外面的四行。 只需将要执行的任何代码放在中间块中即可。-无处不在的计算机科学教授

当然,如果您是新来的学生,那么这道理就没什么好说的了。 例如,这四个外线有什么作用? 什么是上市? 怎么样静态的,串[],or 系统输出文件?

幸运的是,我今天在这里讨论访问修饰符部分。

The Explanation Students Desire

现在,让我们从较高的角度讨论访问修饰符。

Access Modifier Overview

在Java中,访问修饰符是一种帮助我们摆脱困境的方法。 通常,它们用于对类,方法或变量进行某种程度的访问。

例如,如果我们想从现实世界中建模(例如汽车),则可能不希望该对象的某些方面暴露向公众公开(例如对雨刮器的单独控制)。 也许在内部,雨刷器是单独控制的,但是我们已经构建了系统,以使提供给用户的开关能够封装这种行为。 换句话说,两个刮水器按预期一起移动。

如果我们选择公开对每个抽头的单独控制,我们可能会发现许多用户不小心破坏了抽头功能。 毕竟,如果雨刷器无法完全同步,它们可能会相互撞毁。

这就是访问修饰符背后的高级思想。 我们使用它们来公开或隐藏某些功能,以改善整体用户体验。

Misconceptions

在这一点上,许多学生将开始认为访问修饰符是使代码免受黑客攻击的某种方式。 尽管这在很大程度上是不正确的,但该论点还是有其优点的。 当然,没有什么能阻止某人使用诸如反射之类的功能来访问私有字段和方法。 也就是说,访问修饰符可以帮助保护普通用户避免破坏对象的状态。

考虑一下雨刷示例。 当我们打开雨刮器时,我们期望它们都以相同的速度运动。 如果没有受限制的访问,我们可以更改抽头之一的默认速度。 然后,下次我们要打开雨刮器...AM!为了避免这个问题,我们封装(或隐藏)以下事实:在一个公开的(公共)方法中我们有两个单独的抽头。

封装是将复杂状态降低到一系列暴露行为的技术。 如果我要你扔一个球,那肯定不是从为手臂旋转请求一组矩阵变换开始的。 你只是丢球。 这就是封装(和抽象)背后的想法。

在此示例中,我们可以使用访问修饰符指定要公开的行为。 例如,我们可能希望允许用户访问扔命令,但可能不是旋转臂要么捡球命令。

现在,我们已经解决了一些误解,让我们开始谈谈语法。

Keywords

在Java中,实际上有四个访问修饰符:上市,私人的,package-私人的 (default),and 受保护的。 下表提供了每个关键字的代码访问级别:



上市私人的package-私人的受保护的同班ŤŤŤŤ同一包装中的不同类别ŤFŤŤ同一包中的子类ŤFŤŤ不同包装的不同类别ŤFFF不同包装中的子类ŤFFŤ



换句话说,我们可以按可访问性最低的顺序对关键字进行排名:

  1. 私人的package-私人的 (default)受保护的上市

在本教程的整个过程中,我不会继续探索包私有要么受保护的关键字,因为它们有些细微差别,但我认为它们很重要。

Classifying Actions as Public or Private

使用之前的投球示例,让我们尝试确定哪种访问修饰符在各种情况下都适用:

  • public扔抓住折腾沥青private旋转臂translationVertices捡球计算体积

请注意,所有高级操作都是公开的,而较低级操作是私有的。 那是因为我们不一定要向公众公开低级别的行为。 但是,为什么不呢? 让我们来看另一个例子。

假设高级功能依赖于系统的某些基础状态。 例如,投球依赖于了解诸如重力强度和球的特性之类的信息。 如果某人能够以某种方式访问较低级别的动作,则他们有可能操纵世界的这些基本假设。

如果我们能够访问类似的操作,将会发生什么setGravity要么setBall? 我们的高层行动会如何扔要么抓住更改?

使用setGravity命令,我可以告诉您重力实际上是您认为要传球之前的两倍。 届时,您需要先更新世界模型,然后才能显着增加掷力以适应重力变化。 但是,实际上,重力实际上并没有改变,因此您将球推翻了。

当我们公开较低级别的功能而不触发依赖属性的自动更新时,通常会发生这种情况。 在许多情况下,系统非常复杂,并且更改一个基础参数会导致系统故障。 结果,我们尝试封装功能以覆盖我们的所有基础。

User-Defined Classes

到现在为止,我们一直在谈论访问修饰符的原理,但是现实世界的后果是什么?我们如何实际使用它们? 为了帮助澄清这些问题,让我们花一些时间编写一些自己的类,以尝试证明它们之间的实际区别。上市和私人的。

Hello World Revisited

现在,我们已经看到了一些高级解释,让我们回顾一下“ Hello World”示例。



public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, World!");
  }
}

public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, World!");
  }
}



在这里我们可以看到我们两次使用public关键字:一次用于类,另一次用于main方法。 换句话说,我们选择向公众公开HelloWorld类和main方法。

为了使事情更有趣,让我们将打印内容包装在自己的私有方法中:



public class HelloWorld {
  public static void main(String[] args) {
    printHelloWorld();
  }

  private static void printHelloWorld() {
    System.out.println("Hello, World!"); 
  }
}

public class HelloWorld {
  public static void main(String[] args) {
    printHelloWorld();
  }

  private static void printHelloWorld() {
    System.out.println("Hello, World!"); 
  }
}



如果我们尝试运行此解决方案,则会发现该行为完全没有改变。 那是因为私有方法可以在自己的类中使用。 外你好,世界但是,没人知道print你好,世界()甚至存在。 实际上,我们可以尝试直接从同一文件夹中的另一个类调用该方法,但会发现自己出现错误:



public class CallPrivateMethod {
  public static void main(String[] args) {
    HelloWorld.printHelloWorld();  // ERROR
  }
}

public class CallPrivateMethod {
  public static void main(String[] args) {
    HelloWorld.printHelloWorld();  // ERROR
  }
}



如我们所见,我们隐藏了打印功能,因此只能由打印功能使用你好,世界类。 如果由于某种原因我们做了print你好,世界()方法公开,我们可以很好地运行它。

Windshield Wipers

现在,通过在Java中(至少在较高级别)实际实现雨刷器,使这一概念更进一步。 首先,我们将制作一个具有私人的一种刮水器的方法上市两个刮水器的方法:



public class Car {
    private boolean[] wipers;

    public Car() {
        this.wipers = new boolean[2];
    }

    private void turnOnWiper(int index) {
        this.wipers[index] = true;
    }

    public void turnOnWipers() {
        for (int i = 0; i < this.wipers.length; i++) {
            this.turnOnWiper(i);
        }
    }
}

public class Car {
    private boolean[] wipers;

    public Car() {
        this.wipers = new boolean[2];
    }

    private void turnOnWiper(int index) {
        this.wipers[index] = true;
    }

    public void turnOnWipers() {
        for (int i = 0; i < this.wipers.length; i++) {
            this.turnOnWiper(i);
        }
    }
}



在这里,我们创建了一个Car类,用于存储私人的抽头状态数组。 对于每个抽头,其状态为(真正)或关闭(假)。 要打开雨刮器,我们写了一个私人的使您可以按其索引打开抽头的方法。 然后,我们将所有内容与上市遍历所有抽头并打开它们的方法。

现在,忽略这里的现实问题,即雨刮器是串联而不是并联打开的,我们有一个很好的解决方案。 如果有人要实例化汽车,则他们将只能一次打开所有雨刷器。



public class CarBuilder {
    public static void main(String[] args) {
        Car car = new Car();
        car.turnOnWipers(); // Turns on wipers!
        car.turnOnWiper(1); // Compilation ERROR
        car.wipers[0] = false; // Compilation ERROR
    }
}

public class CarBuilder {
    public static void main(String[] args) {
        Car car = new Car();
        car.turnOnWipers(); // Turns on wipers!
        car.turnOnWiper(1); // Compilation ERROR
        car.wipers[0] = false; // Compilation ERROR
    }
}



有趣的事实:用户甚至都不知道游标的实现方式,因此我们可以随时控制更改基础架构。 当然,我们仍然必须提供相同的功能,但是如何实现取决于我们自己。 换句话说,我们可能会更改抽头数组以存储整数。 然后,对于每个刮水器,该整数将与速度相关。

现在,为什么不尝试自己扩大班级。 例如,我建议添加一种关闭雨刷器的方法。 然后,您可能想编写一种新的私有方法来关闭各个抽头,或者您可能会发现重构该抽头更有意义。打开刮水器也可以采用布尔值的方法。 由于用户永远不会看到这些方法,因此您可以完全控制基础实现。 编码愉快!

Open Forum

希望这可以帮助您了解专用关键字和公用关键字之间的区别,以及我们使用它们的原因。 如果没有,我很乐意接受您的任何反馈和问题。 随时使用下面的评论开始对话。 如果这对您有帮助,请与您的朋友分享。 我一直感谢您的支持!