• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
安绍峰
独学而无友,则孤陋而寡闻。
博客园    首页    新随笔    联系   管理    订阅  订阅
第7周 7.2 成员访问权限与内部类

7.2.1 作用域

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

public

定义为public的class、interface可以被其他任何类访问:

//package abc;

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

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

//package xyz;

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

定义为public的field、method可以被其他类访问,前提是首先有访问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

定义为private的field、method无法被其他类访问:

//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的没有public、private修饰的class,以及没有public、protected、private修饰的字段和方法。

//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

独学而无友,则孤陋而寡闻。
posted on 2024-10-14 08:42  安绍峰  阅读(85)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3