初识Java 5-1 实现隐藏

news2024/11/24 18:26:31

目录

库单元:package

代码组织

独一无二的包名

Java访问权限修饰符

包访问权限

接口访问权限(public)

不可访问(private)

继承访问权限(protected)

包访问权限与公共构造器

接口与实现

类的访问权限

新特性:模块


 

本笔记参考自: 《On Java 中文版》


        对于一些项目而言,大部分的时间和金钱并不是消耗在编程阶段,而是消耗在了之后的维护阶段。重构存在的一个主要原因,就是为了重写那些可以正常工作的代码,提高其的可读性、可理解性和可维护性。

        在实际进行开发的过程中会发现,有些库的用户希望能依赖于他们目前使用的那部分代码,而库的创建者却要求能自由修改和完善自己的代码。为了解决这一矛盾,Java提供了访问权限修饰符,包括:publicprotected、包内访问(默认权限,无关键字)private。在此之上,为了能够将类进行归纳、打包,Java还提供了package关键字。

库单元:package

        一个包内含了一组类,这些类由同一个命名空间组织在一起。例如,使用Java提供的类ArrayList,该类就被放置在命名空间java.util中,可以通过两种方式进行用:

// 方法一:指定全名
java.util.ArrayList list = new java.util.ArrayList();

        而为了使代码看起来更加简洁,可以使用import关键字:

// 方法二:使用import关键字
import java.util.ArrayList;

public class SingleImport {
    public void main(String[] args) {
        ArrayList list = new ArrayList(); // 可直接使用
    }
}

    也可以使用“*”导入java.util的其他类:import java.util.*;

        上述的这种导入提供了一种管理密码空间的机制:通过完全控制Java中的命名空间,为每一个类创建唯一的标识符组合(所以,包名的设计也是需要经过考虑的。)

        一个Java源代码文件就是一个编译单元(又称转译单元)。对一个编译单元,有几点限制:

  1. 每个编译单元中只能有一个public类;
  2. public类的名字必须与文件同名(包括大小写,但不包括文件扩展名)。

        若一个编译单元有除了public类之外的其他类,那么在该编译单元所处的包外是无法发现这些类的,因为这些类只是public类的支持类。

代码组织

        编译一个.java文件时,文件中的每一个类都会生成一个输出文件(扩展名是.class)。输出文件的名字就是其在.java文件中对应类的名字。这些.class文件可以通过jar归档器打包成一个Java档案文件(JAR),JAR通过Java解释器进行使用。

        库,就是一组如上所述的类文件(.class文件)。每个源文件通常都由一个public类和任意数量的非public类组成,因此每个源文件中都有一个公共组件。可以使用package关键字把这些组件打包到一个命名空间中

        package语句必须出现在文件的第一个非注释处,形如:

package bagname; // bagname就是包名,在本地中可以理解为文件所处的文件夹的名字

        上述语句将一个编译单元包括到了bagname这个库中。同时,这个编译单元中如果存在public类,那么要调用这个类,就必须先使用bagname这一命名空间。

        现在,假设在一个项目中存在着文件MyClass.java,其存在于路径test/hiding/mypackage中:

package hiding.mypackage;

package hiding.mypackage;

public class MyClass {
    private static MyClass m;

    MyClass() {
        // ...
    }

    public static MyClass Make() {
        System.out.println("Hello, MyClass test");
        return m;
    }
}

        若要使用MyClass,就必须使用import关键字使得example.mypackage中的名称可用。例如:

// 位于文件夹test/hiding中
package hiding;
import hiding.mypackage.*; // [1]

public class QualifiedMyClass {
    public static void main(String[] args) {
        MyClass m = new MyClass(); // [1]
        // example.mypackage.MyClass m = new example.mypackage.MyClass(); // [2]
    }
}

        其中,[1]和[2]使用其一即可。Java通过packageimport关键字来分隔命名空间,防止冲突。

    此处将QualifiedMyClass.java文件放入了hiding包内。若不进行打包,则在进行编译或运行时,可能会遇到NoClassDefFoundError错误。另外,进行打包后,类的全称会发生改变,例如MyClass的全称就是hiding.mypackage.MyClass(若使用java指令运行MyClass时,需要使用全称,并且建议在test文件夹中运行)。


独一无二的包名

        为了整合包所有的组件,一个好的办法是将属于该包的所有.class文件放到一个目录中(利用好操作系统的分层结构)。这种方式解决了两个问题:①创建唯一的包名;②寻找可能隐藏在目录结构中的类。

        按照惯例,package的名称通常由两个部分组成:

  1. 第一部分,由创建者的反向的因特网域名构成;
  2. 第二部分,由机器上的目录组成。

        例如,将mp.csdn.net进行颠倒,就会得到net.csdn.mp。通过使用net.csdn.mp,我们就可以得到一个用于唯一认定自己的类的全局名称。现在可以创建一个simple库,就可以进一步细分,得到以下的包名:

package net.csdn.mp.simple;

        像这种的包名就可以被用作命名空间,用来保护位于其中的类:

// 包名的第二部分就是 net.csdn.mp.simple
// 创建一个包
package net.csdn.mp.simple;

public class Vector {
    public Vector() {
        System.out.println("这个类的全名应该是:net.csdn.mp.simple.Vector");
    }
}

        这个组件可以通过下列语句进行引用:

import net.csdn.mp.simple.Vector;

        假设上述的Vector.java源文件在本地中储存的位置是:

/home/user/Java/test/net/csdn/mp/simple

        上述路径的后半部分net.csdn.mp.simple组成了包名。而路径的第一部分则由CLASSPATH环境变量提供。为此,就需要在本地机器中,为CLASSPATH添加路径(由于笔者使用的是Ubuntu系统,故提供当前系统下添加路径的方法)

export CLASSPATH=/home/user/Java/test

        进行这种类型的路径添加,一个主要的好处是可以在当前的包外使用自定义的库。例如,当添加了上述路径时,可以在与test平行的目录other中,使用MyClass类:

// 位置为:other/HelloWorld.java

import net.csdn.mp.simple.Vector;

public class HelloWorld {
    public static void main(String[] args) {
        Vector v = new Vector();
    }
}

        可以运行程序,得到下列输出:

冲突

        若通过*导入的两个库中包含了相同的名称,例如:

import net.csdn.mp.simple.*; // 包含了一个自定义的Vector类
import java.util.*; // 包含了标准库中的Vector类

        此时,若按照如下方式创建Vector类,就有可能发生冲突:

Vector v = new Vector();

        尝试编译,发生报错:

        正确的方式是对需要使用的Vector类进行指定

java.util.Vector v = new java.util.Vector();

        因此,在这种情况下就不需要通过import关键字进行导入,除非还使用了库中的其他内容。或者,如果使用单类导入的形式,也可以避免发生冲突。

    Java不存在C语言拥有的条件编译,这是因为Java能够自动跨平台。但有时,为了进行调试,还是需要使用条件编译的,为此可以通过package关键字改变导入的包,将程序中使用的代码从调试版本切换到生产版本,以此实现条件编译的功能。

Java访问权限修饰符

        Java的访问权限修饰符包括:publicprotectedprivate。这些修饰符放在类中成员(包括字段和方法)定义的前面,控制被修饰定义的访问。而若不使用访问权限修饰符,成员会拥有默认的“包访问权限”。

包访问权限

        默认访问权限没有关键字,通常称为包访问权限(又称“友好访问权限”)。这种权限只允许当前包中的所有其他类访问该成员,而对此包之外的所有类,该成员显示为private(处于隐藏状态)。由于一个编译单元只属于一个包,所以一个编译单元中的所有类都可以通过包访问权限进行相互访问。

        包访问权限的存在,要求将类分组到一个包中。所以,在Java中,以合理的方式组织文件中的定义方式是重要的。

        类控制着那些代码可以访问其成员。授予成员访问权限的几个方法如下所示:

  1. 将成员设置为public,这样就允许任意代码访问该成员;
  2. 不为成员添加任何访问权限修饰符——赋予成员包访问权限。这样处于同一个包内的其他类就可以访问该成员;
  3. 当使用继承时,子类可以访问父类的protected成员和public成员,若两个类处于同一个包中,子类可以进一步访问父类的包访问权限成员;
  4. 提供可以读取和更改值的访问器(accessor)和修改器(mutator)方法。

接口访问权限(public)

        若使用public修饰,这意味着其之后的成员对所有人而言都是可用的,包括开发者和客户。假设存在一个包含了以下编译单元的desert包:

// 位于目录 /example/hiding/desert 中
package hiding.desert;

public class Cookie { // 具有public权限,可从包外进行访问
    public Cookie() {
        System.out.println("这是一个类Cookie的构造器");
    }

    void bite() { // 默认的包访问权限,无法从包外进行访问
        System.out.println("随便写点什么");
    }
}

    记得将hiding目录设置为CLASSPATH指定的路径之一。使用语句javac -classpath ./ xxx.java可以在编译时将CLASSPATH设定到当前目录。

        现在可以让其他程序使用类Cookie了:

// 位于example/中
package example;

// 位于目录 /hiding 中
import hiding.dessert.*;

public class EatingTheFood {
    public static void main(String[] args) {
        Cookie c = new Cookie();
    }
    // c.bite(); // 无法访问
}

        程序运行的结果如下:

        在上述的Cookie类中,由于其的构造器和类都是public的,所以它的对象可被创建。但方法bite()只有包访问权限,所以在其包外是无法进行访问的。

默认包

        若两个类处于同一目录中,那么即使这两个类没有明确的包名,它们也可以进行相互的访问。例如:

// 位置是hiding/Cake.java
class Cake {
    public static void main(String[] args) {
        Pie x = new Pie();
        x.f();
    }
}

        下面的文件Pie.javaCake.java处于同一目录中:

// 位置是hiding/Pie.java
class Pie {
    void f() {
        System.out.println("这条语句存在于Pie.java中");
    }
}

        被放在同一目录中的类会被视为属于当前目录的“默认包”的隐含部分。因此,这种文件会为该目录中的其他文件提供包访问权限。


不可访问(private)

        若一个类存在使用private修饰的成员,那么除了包含此成员的类及其成员之外,其他任何类不可访问该成员(即使是同一个包中的其他类)。通常,可以自由修改和替换通过private修饰的成员(虽然隐藏可以通过反射进行规避)

    特别是在涉及多线程时,使用private是十分重要的。

class Sundae {
    private Sundae() { // 该构造器被隐藏,无法从Sundae外被调用
        System.out.println("一个被隐藏的Sundae构造器");
    }

    static Sundae makeASundae() {
        return new Sundae(); // 在Sundae类中,拥有调用构造器Sundae()的权限
    }
}

public class IceCream {
    public static void main(String[] args) {
        Sundae x = Sundae.makeASundae(); // 通过调用静态方法,可以通过指定构造器进行对象创建
    }
}

        程序运行的结果如下:

        上述程序展示了private的一个用法:控制对象的创建方式,防止特定的构造器(或所有的构造器)被调用。

    若确定某方法是类的“辅助”方法,将其设为private能够为后期的维护与修改保留选择。字段也是如此,除非需要公开底层实现,否则最好将字段设为private


继承访问权限(protected)

        protected关键字多被用于处理继承的概念。通过继承,可以通过一个现有类(即基类),在不修改现有类的情况下向类中添加新成员,或改变现有成员的行为。使用extends声明新类继承了现有类:

class SubClass extends BaseClass { // ...

        子类对基类的访问权限有几种:

  1. 子类可以访问基类的public成员和protected成员;
  2. 若子类和基类位于同一个包中,子类可以访问基类所有的包访问权限成员。

        protected关键字也提供了包访问权限。其与public的区别在于,若不在同一个包中,那么包外的类可以访问public成员,但是不可以访问protected成员。

        以之前提到的Cookie.java为例,它的bite()方法只有包访问权限:

void bite() { // 默认的包访问权限,无法从包外进行访问
    System.out.println("随便写点什么");
}

        但如果有子类需要访问该方法,那么就必须改变bite()的权限。public允许所有的访问,这或许不会是我们想要的。为此,就需要使用protected关键字了:

// 位于目录 /hiding/dessert 中
package hiding.dessert;

public class Cookie { // 具有public权限,可从包外进行访问
    public Cookie() {
        System.out.println("这是一个类Cookie的构造器");
    }

    protected void bite() { // 默认的包访问权限,无法从包外进行访问
        System.out.println("随便写点什么");
    }
}

        这样就可以让任何继承Cookie的类访问bite()了:

// 位置是hiding/ChocolateChip.java
package hiding;

import hiding.dessert.Cookie;

public class ChocolateChip extends Cookie {
    public ChocolateChip() {
        System.out.println("这是ChocolateChip的构造器");
    }

    public void chomp() {
        bite(); // 使用了Cookie的protected方法
    }

    public static void main(String[] args) {
        ChocolateChip c = new ChocolateChip();
        c.chomp();
    }
}

        程序运行的结果是:


包访问权限与公共构造器

        若一个类只具有包访问权限,现在给这个类一个public构造器,会发现编译器不会进行任何报错:

// 位置是hiding/packageaccess/PublicConstructor.java
package hiding.packageaccess;

class PublicConstructor {
    public PublicConstructor() { // ...
    }
}

        但这不代表没有问题,因为这是一个虚假陈述——实际上无法从包外访问这个public构造器。如果尝试从外部对其进行调用,就会发现报错:

// 位置是hiding/CreatePackageAccessObject.java
package hiding;

import hiding.packageaccess.*;

public class CreatePackageAccessObject {
    public static void main(String[] args) {
        new PublicConstructor();
    }
}

        发生了报错:

接口与实现

        访问控制也被称为实现隐藏。将数据方法包装在类中,并与实现隐藏相结合,称为封装。其结果就是具有特征和行为的数据类型。

        访问权限控制在数据类型的内部设置了访问边界:

  1. 确定客户程序员可以使用和不可使用的内容;
  2. 将接口和实现分离。

类的访问权限

        访问权限修饰符还决定了库内部的那些类可以提供给用户使用。要控制对类的访问,访问权限修饰符就必须出现在关键字class前面:

public class Weight { // ...

        这种类的使用之前已经多次进行过展示,这里就不再赘述了。值得一提的是,在进行类的设计时会额外添加一些限制:每个编译单元(即文件)都只能有一个public,且该类的名字必须和文件名完全一致。

        当然,编译单元中可以没有public类。这种类通常都是用来完成一些其他public类分发的任务,不使用public可以让这种类隐藏在包中,这样可以让实现变得更加灵活。

        关于赋予权限,有这样的一些建议:

  • 应该尽量将字段设置为private权限;
  • 方法应该具有和类相同的访问权限(包访问权限);
  • 类不应该是privateprotected的,类的访问权限有两种:包访问权限和public。若想防止对该类的访问,可以将其构造器全部设置为private(转而使用静态方法创建对象)。
class Soup1 {
    private Soup1() { // 不允许外部访问构造器
        System.out.println("隐藏的Soup1构造器");
    }

    public static Soup1 makeSoup() {
        return new Soup1(); // 使用静态方法返回对象
    }
}

class Soup2 {
    private Soup2() {
        System.out.println("隐藏的Soup2构造器");
    }

    private static Soup2 s2 = new Soup2(); // 声明静态对象

    public static Soup2 access() {
        return s2;
    }

    public void f() {
        System.out.println("被Soup2对象调用的f()");
    }
}

public class Lunch {
    void testPrivate() {
        // Soup1 soup1 = new Soup1(); // 编译报错,Soup1是隐藏的
    }

    void testStatic() {
        Soup1 s = Soup1.makeSoup();
    }

    void testSingleon() {
        Soup2.access().f();
    }

    public static void main(String[] args) {
        Lunch lh = new Lunch();
        lh.testStatic();
        lh.testSingleon();
    }
}

        程序执行的结果如下:

        上述程序中,类Soup2使用的是单例模式,从始至终只创建一个对象。

新特性:模块

        在JDK 9之前,Java程序的运行会依赖整个Java库。这意味着,哪怕只是使用库的一个组件,编译器也会将整个Java库包含在里面。

        JDK 9引入了模块的概念:将代码划分为一个一个的模块。由这些模块指定它们依赖的模块,并且定义可用或者不可用的模块。在此之后,若使用库组件,就只会获得对应的模块及其的依赖项,这就省去了不必要的导入。

     另外,使用“逃生舱口”(escape hatch)可以调用隐藏的库组件,但因此产生的可能问题需要由程序员自己负责。

        为了更好地探索这个新的系统,Java提供了新的命令行标识,比如:

  1.  显示所有可用模块:
    java --list-modules
  2. 若要查看模块的内容,例如base模块,可以使用命令:

    java --describe-module java.base

    模块系统多被用于大型的第三方库。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/979237.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Java+SpringBoot+Vue前后端分离医疗挂号管理系统设计和实现

博主介绍:✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专…

pdf怎么转cad?几个简单方法分享给你

pdf怎么转cad?PDF文件转换为CAD文件是一项非常重要的任务,特别是对于那些需要进行工程、建筑和设计的专业人士来说。在过去,这项任务可能需要耗费大量时间和精力,但现在,随着技术的不断发展,已经有很多工具…

华为云云服务器评测| 之性能测试

文章目录 前言软件安装扩展知识 收集服务器负载信息指令解析开始压测后台运行 stress 运行 sysbench 测试网络带宽总结 测试磁盘 I/O 性能I/O 性能评估总结 前言 在当今数字化时代,云计算作为一种高效、灵活的计算方式,正日益受到企业和个人用户的广泛关…

如何远程访问Linux MeterSphere一站式开源持续测试平台

文章目录 前言1. 安装MeterSphere2. 本地访问MeterSphere3. 安装 cpolar内网穿透软件4. 配置MeterSphere公网访问地址5. 公网远程访问MeterSphere6. 固定MeterSphere公网地址 前言 MeterSphere 是一站式开源持续测试平台, 涵盖测试跟踪、接口测试、UI 测试和性能测试等功能&am…

基于Java+SpringBoot+Vue前后端分离校园商铺管理系统设计和实现

博主介绍:✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专…

LeetCode 15 三数之和

题目链接 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 题目解析 // 1. 排序双指针 // 2. 固定一个值nums[i] 然后去剩下的位置去找 两数之和符合nums[j]nums[k]是否等于-nums[i] // 3. 细节问题:由于题目中是不可以包含重复的三元组的…

功率放大器的功能是什么功能

功率放大器是一种电子设备,用于放大输入信号的功率,并输出对应增强后的信号。功率放大器的功能主要包括增强信号的功率、保持信号的形状和质量、提供足够的电流和电压驱动负载,以满足不同应用需求。 功率放大器的主要功能是增强信号的功率。输…

阿里巴巴API接口解析,实现按关键字搜索商品

要解析阿里巴巴API接口并实现按关键字搜索商品,你需要进行以下步骤: 了解阿里巴巴API接口文档:访问阿里巴巴开放平台,找到API文档,了解阿里巴巴提供的API接口以及相关的参数、返回值等信息。注册开发者账号&#xff1…

远传水表和流量计的区别

远传水表和流量计是两种用于测量和控制水流的设备,虽然在某些方面有重叠的功能,但它们之间也有一些区别。下面我们将详细介绍这两种设备的区别。 一、定义和作用 远传水表是一种能够远程传输用水数据的水表,可以通过无线通信技术将数据传输到…

轻松解决Idea中maven无法下载源码

今天在解决问题的时候想要下载源码,突然发现idea无法下载,这是真的蛋疼,没办法查看原因,最后发现问题的原因居然是因为Maven,由于我使用的idea的内置的Bundle3的Maven,之前没有研究过本地安装和内置的区别&…

前端面试0906

// 请给出输出结果 function foo(){ console.log(a); } function bar(){ var a 3; console.log(this.a); foo(); } var a 2; bar(); 2 2 // 请从下面的问题中挑选3道进行回答 1. 防抖和节流分别是什么,一般用在什么场景? 防抖(Debounc…

CocosCreator3.8研究笔记(七)CocosCreator 节点和组件的介绍

相信很多新手朋友,肯定会问,CocosCreator 中什么是节点?什么是组件? 一、什么是组件(Component)? Cocos Creator 3.8 的工作流程是以组件式开发为核心,即以组合而非继承的方式进行游…

安卓手机记事本数据转移到苹果手机上怎么操作?

国内有不少网友使用的手机都是小米、荣耀、OPPO、vivo等安卓手机,而再次更换手机时,就想要尝试一下不同的操作系统,例如更换一台苹果手机。不过在换手机之前,还有一件重要的事情需要去做,这就是安卓手机记事本数据转移…

RS485转0_20mA输出模块设计

文章目录 1. 简介2. 功能实现3. 测试4. 开源地址 1. 简介 结合以前发的文章,我们知道,模拟量输出有两种,一种是共地型,一种是共源型。 今天开源一款rs485隔离的转0-20ma输出模块的设计。 我设计模块的原因是为了测试公司的一款…

redis实战-redis实现分布式锁redisson快速入门

前言 集群环境下的并发问题 分布式锁 定义 需要满足的条件 常见的分布式锁 redis实现分布式锁 核心思路 代码实现 误删情况 逻辑说明 解决方案 代码实现 更为极端的误删情况 Lua脚本解决原子性问题 分布式锁-redission redisson的概念 快速入门 总结 前言 在…

Python之数值和内建函数

Python之数值和内建函数 内建常用数据类型 分类 数值型 int、float、complex、bool 序列sequence 字符串str、字节序列bytes、bytearray列表list、元组tuple 键值对 集合set、字典dict 取整 取整 int // round math.floor math.ceil说明:两条//斜杠是整除&…

(云HIS)云医院管理系统源码 SaaS模式 B/S架构 基于云计算技术

通过提供“一个中心多个医院”平台,为集团连锁化的医院和区域医疗提供最前沿的医疗信息化云解决方案。 一、概述 云HIS系统源码是一款满足基层医院各类业务需要的健康云产品。该系统能帮助基层医院完成日常各类业务,提供病患预约挂号支持、收费管理、病…

快速掌握高效批量分割长视频技巧,让你的视频制作更轻松

对于需要处理大量长视频的影视制作人员来说,视频分割是一项必不可少的任务。传统的视频分割方法需要手动进行,费时费力。今天,笔者将介绍一种快速批量分割长视频的方法,使用固乔智剪软件,提高视频制作效率。 1. 下载并…

C/C++内存布局——代码区、常量区、静态区(BSS段、Data段)

C/C内存分区 在C/C这种高级语言的层面看,一个程序的内存分区可以被分为:(从低地址到高地址)代码区、常量区、静态区(已初始化(BSS段)、未初始化(Data段))、堆…

景区AR虚拟三维场景沉浸式体验成为新兴的营销手段

科技的迅速崛起正在改变我们的世界,旅游业也在这股浪潮中掀起了一场全新的变革。增强现实(AR)技术正成为旅行中的一股强大力量,通过增添趣味和交互性,为旅程注入了前所未有的活力。本文将带您深入了解AR如何为旅游带来全新的体验,…