首页 > 其他分享 >第7周 7.2 成员访问权限与内部类

第7周 7.2 成员访问权限与内部类

时间:2024-10-14 08:50:28浏览次数:1  
标签:Class Outer void public 7.2 访问 Inner 权限 class

7.2.1 作用域

在Java中,我们经常看到publicprotectedprivate这些修饰符。在Java中,这些修饰符可以用来限定访问作用域。

public

定义为publicclassinterface可以被其他任何类访问:

//package abc;

public class Hello {
    public void hi() {
    }
}

上面的Hellopublic,因此,可以被其他包的类访问:

//package xyz;

class Main {
    void foo() {
        // Main可以访问Hello
        Hello h = new Hello();
    }
}

定义为publicfieldmethod可以被其他类访问,前提是首先有访问class的权限:

//package abc;

public class Hello {
    public void hi() {
    }
}

上面的hi()方法是public,可以被其他类调用,前提是首先要能访问Hello类:

//package xyz;

class Main {
    void foo() {
        Hello h = new Hello();
        h.hi();
    }
}

private

定义为privatefieldmethod无法被其他类访问:

//package abc;

public class Hello {
    // 不能被其他类调用:
    private void hi() {
    }

    public void hello() {
        this.hi();
    }
}

实际上,确切地说,private访问权限被限定在class的内部,而且与方法声明顺序无关。推荐把private方法放到后面,因为public方法定义了类对外提供的功能,阅读代码的时候,应该先关注public方法:

//package abc;

public class Hello {
    public void hello() {
        this.hi();
    }

    private void hi() {
    }
}

由于Java支持嵌套类,如果一个类内部还定义了嵌套类,那么,嵌套类拥有访问private的权限:

// private
public class Main {
    public static void main(String[] args) {
        Inner i = new Inner();
        i.hi();
    }

    // private方法:
    private static void hello() {
        System.out.println("private hello!");
    }

    // 静态内部类:
    static class Inner {
        public void hi() {
            Main.hello();
        }
    }
}

定义在一个class内部的class称为嵌套类(nested class),Java支持好几种嵌套类。

protected

protected作用于继承关系(下次课会讲到继承,这里先了解下)。定义为protected的字段和方法可以被子类访问,以及子类的子类:

//package abc;

public class Hello {
    // protected方法:
    protected void hi() {
    }
}

上面的protected方法可以被继承的类访问:

//package xyz;

class Main extends Hello {  //extends 继承关键字
    void foo() {
        // 可以访问protected方法:
        hi();
    }
}

package

最后,包作用域是指一个类允许访问同一个package的没有publicprivate修饰的class,以及没有publicprotectedprivate修饰的字段和方法。

//package abc;
// package权限的类:
class Hello {
    // package权限的方法:
    void hi() {
    }
}

只要在同一个包,就可以访问package权限的class、field和method:

//package abc;

class Main {
    void foo() {
        // 可以访问package权限的类:
        Hello h = new Hello();
        // 可以调用package权限的方法:
        h.hi();
    }
}

注意,包名必须完全一致,包没有父子关系,com.apache和com.apache.abc是不同的包。

局部变量

在方法内部定义的变量称为局部变量,局部变量作用域从变量声明处开始到对应的块结束。方法参数也是局部变量。

//package abc;

public class Hello {
    void hi(String name) { // 1
        String s = name.toLowerCase(); // 2
        int len = s.length(); // 3
        if (len < 10) { // 4
            int p = 10 - len; // 5
            for (int i=0; i<10; i++) { // 6
                System.out.println(); // 7
            } // 8
        } // 9
    } // 10
}

我们观察上面的hi()方法代码:

  • 方法参数name是局部变量,它的作用域是整个方法,即1 ~ 10;
  • 变量s的作用域是定义处到方法结束,即2 ~ 10;
  • 变量len的作用域是定义处到方法结束,即3 ~ 10;
  • 变量p的作用域是定义处到if块结束,即5 ~ 9;
  • 变量i的作用域是for循环,即6 ~ 8。

使用局部变量时,应该尽可能把局部变量的作用域缩小,尽可能延后声明局部变量。

final

Java还提供了一个final修饰符。final与访问权限不冲突,它有很多作用。

用final修饰class可以阻止被继承:

//package abc;

// 无法被继承:
public final class Hello {
    private int n = 0;
    protected void hi(int t) {
        long i = t;
    }
}

用final修饰method可以阻止被子类覆写:

//package abc;

public class Hello {
    // 无法被覆写:
    protected final void hi() {
    }
}

用final修饰field可以阻止被重新赋值:

//package abc;

public class Hello {
    private final int n = 0;
    protected void hi() {
        this.n = 1; // error!
    }
}
|           this.n = 1; // error!

无法为最终变量n分配值

用final修饰局部变量可以阻止被重新赋值:

//package abc;

public class Hello {
    protected void hi(final int t) {
        t = 1; // error!
    }
}
|           t = 1; // error!

不能分配最终参数t

最佳实践

  • 如果不确定是否需要public,就不声明为public,即尽可能少地暴露对外的字段和方法。

  • 把方法定义为package权限有助于测试,因为测试类和被测试类只要位于同一个package,测试代码就可以访问被测试类的package权限方法。

  • 一个.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同。

小结

Java内建的访问权限包括public、protected、private和package权限;

Java在方法内部定义的变量是局部变量,局部变量的作用域从变量声明开始,到一个块结束;

final修饰符不是访问权限,它可以修饰class、field和method;

一个.java文件只能包含一个public类,但可以包含多个非public类。

7.2.2内部类

在Java程序中,通常情况下,我们把不同的类组织在不同的包下面,对于一个包下面的类来说,它们是在同一层次,没有父子关系:

java.lang
├── Math
├── Runnable
├── String
└── ...
还有一种类,它被定义在另一个类的内部,所以称为内部类(Nested Class)。Java的内部类分为好几种,通常情况用得不多,但也需要了解它们是如何使用的。

Inner Class

如果一个类定义在另一个类的内部,这个类就是Inner Class:

class Outer {
    class Inner {
        // 定义了一个Inner Class
    }
}

上述定义的Outer是一个普通类,而Inner是一个Inner Class,它与普通类有个最大的不同,就是Inner Class的实例不能单独存在,必须依附于一个Outer Class的实例。示例代码如下:

// inner class
public class Main {
    public static void main(String[] args) {
        Outer outer = new Outer("Nested"); // 实例化一个Outer
        Outer.Inner inner = outer.new Inner(); // 实例化一个Inner
        inner.hello();
    }
}

class Outer {
    private String name;

    Outer(String name) {
        this.name = name;
    }

    class Inner {
        void hello() {
            System.out.println("Hello, " + Outer.this.name);
        }
    }
}
Main.main(null);
Hello, Nested

观察上述代码,要实例化一个Inner,我们必须首先创建一个Outer的实例,然后,调用Outer实例的new来创建Inner实例:

Outer.Inner inner = outer.new Inner();

这是因为Inner Class除了有一个this指向它自己,还隐含地持有一个Outer Class实例,可以用Outer.this访问这个实例。所以,实例化一个Inner Class不能脱离Outer实例。

Inner Class和普通Class相比,除了能引用Outer实例外,还有一个额外的“特权”,就是可以修改Outer Class的private字段,因为Inner Class的作用域在Outer Class内部,所以能访问Outer Class的private字段和方法。

观察Java编译器编译后的.class文件可以发现,Outer类被编译为Outer.class,而Inner类被编译为Outer$Inner.class。

Anonymous Class

还有一种定义Inner Class的方法,它不需要在Outer Class中明确地定义这个Class,而是在方法内部,通过匿名类(Anonymous Class)来定义。示例代码如下:

// Anonymous Class
public class Main {
    public static void main(String[] args) {
        Outer outer = new Outer("Nested");
        outer.asyncHello();
    }
}

class Outer {
    private String name;

    Outer(String name) {
        this.name = name;
    }

    void asyncHello() {
        Runnable r = new Runnable() { //匿名类
            @Override
            public void run() {
                System.out.println("Hello, " + Outer.this.name);
            }
        }; // 匿名类对象,作为语句的一部分,必须用分号结束
        new Thread(r).start();
    }
}
Main.main(null);
Hello, Nested

观察asyncHello()方法,我们在方法内部实例化了一个Runnable。Runnable本身是接口,接口是不能实例化的,所以这里实际上是定义了一个实现了Runnable接口的匿名类,并且通过new实例化该匿名类,然后转型为Runnable。在定义匿名类的时候就必须实例化它,定义匿名类的写法如下:

Runnable r = new Runnable() {
    // 实现必要的抽象方法...
};

匿名类和Inner Class一样,可以访问Outer Class的private字段和方法。之所以我们要定义匿名类,是因为在这里我们通常不关心类名,比直接定义Inner Class可以少写很多代码。

观察Java编译器编译后的.class文件可以发现,Outer类被编译为Outer.class,而匿名类被编译为Outer$1.class。如果有多个匿名类,Java编译器会将每个匿名类依次命名为Outer$1、Outer$2、Outer$3……

除了接口外,匿名类也完全可以继承自普通类。观察以下代码:

// Anonymous Class
import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        HashMap<String, String> map1 = new HashMap<>();
        HashMap<String, String> map2 = new HashMap<>() {}; // 匿名类!
        HashMap<String, String> map3 = new HashMap<>() {
            {
                put("A", "1");
                put("B", "2");
            }
        };
        System.out.println(map3.get("A"));
    }
}
Main.main(null);
1

map1是一个普通的HashMap实例,但map2是一个匿名类实例,只是该匿名类继承自HashMap。map3也是一个继承自HashMap的匿名类实例,并且添加了static代码块来初始化数据。观察编译输出可发现Main$1.class和Main$2.class两个匿名类文件。

Static Nested Class

最后一种内部类和Inner Class类似,但是使用static修饰,称为静态内部类(Static Nested Class):

// Static Nested Class
public class Main {
    public static void main(String[] args) {
        Outer.StaticNested sn = new Outer.StaticNested();
        sn.hello();
    }
}

class Outer {
    private static String NAME = "OUTER";

    private String name;

    Outer(String name) {
        this.name = name;
    }

    static class StaticNested {
        void hello() {
            System.out.println("Hello, " + Outer.NAME);
        }
    }
}
Main.main(null);
Hello, OUTER

用static修饰的内部类和Inner Class有很大的不同,它不再依附于Outer的实例,而是一个完全独立的类,因此无法引用Outer.this,但它可以访问Outer的private静态字段和静态方法。如果把StaticNested移到Outer之外,就失去了访问private的权限。

小结
Java的内部类可分为Inner Class、Anonymous Class和Static Nested Class三种;

Inner Class和Anonymous Class本质上是相同的,都必须依附于Outer Class的实例,即隐含地持有Outer.this实例,并拥有Outer Class的private访问权限;

Static Nested Class是独立类,但拥有Outer Class的private访问权限。

7.2.3 包装类

原始数据类型与包装类

因为Java是一个完全的面向对象的语言,几乎所有的方法都可以直接通过对象.方法()调用,然而8种基本数据类型的存在就很鸡肋,不能直接用int. char. double.来调用想用的功能,也没有继承封装等面向对象的思想,因此引入了包装类(封装类):

基本数据类型 包装类型
byte java.lang.Byte(父类Number)
short java.lang.Short(父类Number)
int java.lang.Integer(父类Number)
long java.lang.Long(父类Number)
float java.lang.Float(父类Number)
double java.lang.Double(父类Number)
boolean java.lang.Boolean(父类Object)
char java.lang.Character(父类Object)

除了int和char,其他的都是直接首字母大写,前6个数值型的包装类都有一个共同的父类Number,Number又继承于Object

另外,还有两个包装类BigInteger和BigDecimal,没有相对应的基本类型,主要用于处理大数和浮点数高精度运算。BigInteger支持任意精度的整数,而BigDecimal支持任意精度的浮点数。

包装类在java.lang包,因此import java.lang.*可以导入所有的包装类。包装类属于引用类型。,具有方法和属性。
包装类既可以用new实例化,也可以使用静态方法valueOf将基本类型转换为包装类型:

包装类主要提供以下功能:

  • 封装基本数据类型,使其具有类对象的特性,如封装类对象可以作为参数传递,可以作为返回值,可以作为成员变量。
  • 为基本数据类型提供各种转换功能,如将字符串转换为基本数据类型,将基本数据类型转换为字符串。

有些数据结构(如 ArrayList 、 HashMap)不能存放原值类型,这时候需要使用包装类。

基本数据炻和其包装类之间的转换(以Interger为例):
1.基本数据类型转换为包装类:

Integer i = Integer.valueOf(10);
Integer k = new Integer(10);
Integer j = 10; 
i
10

2.包装类转换为基本数据类型:

int x = i.intValue();
int y = j;
j
10

基本数据类型与包装类的区别:

  • 在Java中,一切皆对象,但八大基本类型不是对象。
  • 声明方式不同,基本类型直接声明,包装类需要先new一个对象。
  • 存储方式及位置不同,基本类型直接存储在栈中,包装类存储在堆中。
  • 初始值不同,基本类型有相应的默认值,如int为0,boolean类型为false;包装类默认值为null。
  • 使用方式不同,比如与集合类合作使用时只能使用包装类。

装箱与拆箱

  • 装箱是指将一个基本类型转换为一个对应封装的对象,如将Int型封装为Integer对象。
  • 拆箱是指将一个封装对象转换为对应的基本类型,如将Integer对象拆箱为Int型。

请看下面的例子:

//手动装箱
int x=100; //定义一个基本类型变量x
Integer in=new Integer(x); //将基本数据类型x封装为Integer对象
System.out.println("封装类Integer类型in="+in); //输出Integer对象in

//手动拆箱
Float f=new Float(3.14F); ///将基本数据类型3.14F封装为Float对象
float y=f.floatValue(); //将Float对象f转换为基本数据类型float
System.out.println("基本类型float y"+y);

封装类Integer类型in=100
基本类型float y3.14
//自动装箱
int x=100; //定义一个基本类型变量x
Integer in=x; //将基本数据类型x封装为Integer对象
System.out.println("封装类Integer类型in="+in); //输出Integer对象in

//自动拆箱
Float f=new Float(3.14F); ///将基本数据类型3.14F封装为Float对象
float y=f; //将Float对象f转换为基本数据类型float
System.out.println("基本类型float y"+y);

使用自动装箱和拆箱时,需要注意自动装箱和拆箱可能引发空指针异常(NullPointerException)。
建议在以后程序开发时,使用手动拆箱和装箱。

常用方法

包装类提供了很多方法,这里介绍一些常用的方法:

  • toString():将基本数据类型转换为字符串。
  • valueOf():将字符串转换为基本数据类型。
  • parseXXX():将字符串转换为基本数据类型。
  • xxxValue():将包装类转换为基本数据类型。
  • compareTo():比较两个包装类的大小。
  • equals():比较两个包装类是否相等。
  • hashCode():返回对象的哈希码值。
  • clone():创建并返回此对象的副本。
  • getClass():返回此对象的运行时类。

7.2.4 Java常用类库

JDK为开发人员提供了丰富的基础类库作为开发Java软件的基础,可以提高开发效率,降低开发难度。

日期类 Date

java.util.Date类表示特定的瞬间,精确到毫秒,它由GMT(Greenwich Mean Time)时间表示,也称为UTC(Coordinated Universal Time)时间。

java.time.LocalDate.now()

日期格式化类 SimpleDateFormat

java.text.SimpleDateFormat

随机数类 Random

java.util.Random

标签:Class,Outer,void,public,7.2,访问,Inner,权限,class
From: https://www.cnblogs.com/bcd589/p/18438287

相关文章

  • codeforces round 977 (div.2) C2(访问set的第一个元素,观察数据规律-出现次序,用set记
    解题历程:我首先想到的是等效法,每一次操作可以等效为每次将第一个人抽出放入一组,后面的人往前移,而该组的人就是可以任意放置的人,当b中后面再出现与前一个相同的人时,就不进行操作,当b中出现不同的人时,就看看这组中有没有这个人,有的话就下一个循环,没有的话就看看这个新的人是否按a中......
  • python基础知识(十一)面向过程,面向对象,对象属性,魔法方法,继承,私有权限
    目录面向过程是什么什么是面向对象?面向对象的三大特性:继承多态类对象self关键字对象属性类外面访问属性类内部获取属性魔法方法无参init()方法有参init()方法str()方法del()方法继承基础什么是继承单继承多继承继承进阶方法重写调用父类方法多层继承......
  • Asp-Net-Core权限认证
    翻了很多的博客,文档,发现asp.netcore自带的权限认证还是比较复杂的,极少有哪篇文章把整个体系涉及到的知识点都讲清楚的,因此自己整理出了这篇文章,相当于自己的一个个人理解和总结吧关键概念认证和授权#asp.netcore中将权限认证分成了两个部分,一个是认证(Authentication),一个是......
  • 【汇编语言】第三章----寄存器(内存访问)(一)—— 内存中字的存储
    文章目录前言1.内存中字的存储2.问题3.问题分析与解答4.结论结语前言......
  • 一文了解 Linux 系统的文件权限管理
    文章目录引入Linux文件权限模型查看文件权限权限信息解析修改文件权限符号模式八进制数字模式引入在Linux操作系统中,我们想查看我们对文件拥有哪些权限时,可以在终端键入ls-l或ll命令,终端会输出当前路径下的文件信息,如文件名称、权限信息、文件所有者和所属组等信息......
  • 第106天:权限提升-WIN 系统&AD域控&NetLogon&ADCS&PAC&KDC&CVE 漏洞
    知识点1、WIN-域内用户到AD域控-CVE-2014-63242、WIN-域内用户到AD域控-CVE-2020-14723、WIN-域内用户到AD域控-CVE-2021-422874、WIN-域内用户到AD域控-CVE-2022-26923WIN-域控提权-CVE-2014-6324前提条件:1、需要域环境下一台主机普通用户账号密码2、一台主机的管理员权......
  • 2146: 【例7.2】与圆相关的计算
    include<bits/stdc++.h>usingnamespacestd;doubler,pi=3.14159;intmain(){cin>>r;cout<<fixed<<setprecision(4)<<r2<<"";cout<<fixed<<setprecision(4)<<r2pi<<&quo......
  • 第105天:权限提升-Linux系统&Docker挂载&Rsync未授权&Sudo-CVE&Polkit-CVE
    演示案例Linux-Rsync未授权访问覆盖-本地Linux-Docker组用户挂载目录-本地Linux-Sudo(CVE-2021-3156)-本地Linux-Polkit(CVE-2021-4034)-本地Rsync(未授权访问)Rsync是linux下一款数据备份工具,默认开启873端口https://vulhub.org/#/environments/rsync/common/借助Linux默认......
  • 第104天:权限提升-Linux系统&环境变量&定时任务&权限配置不当&MDUT自动化
    知识点总结#知识点:1、Linux提权-定时任务2、Linux提权-环境变量3、Linux提权-权限配置不当4、Linux提权-数据库自动化#系列内容:内核,数据库,第三方服务,SUID&GUID,定时任务,环境变量,SUDO,权限不当等脏牛漏洞(CVE-2016-5195)DirtyPipe(CVE-2022-0847)SUDO(CVE-2021-3156)Polki......
  • 第103天:权限提升-Linux系统&辅助项目&脏牛&Dirty&内核漏洞&SUID&GUID
    知识点梳理#知识点:1、Linux提权辅助项目-探针&漏扫2、Linux提权-配置SUID&内核CVE#系列内容:内核,数据库,第三方服务,SUID&GUID,定时任务,环境变量,SUDO,权限不当等脏牛漏洞(CVE-2016-5195)DirtyPipe(CVE-2022-0847)SUDO(CVE-2021-3156)Polkit(CVE-2021-4034)案例演示Linux-辅......