类变量/静态变量package com.study.static_;
通过static关键词声明,是该类所有对象共享的对象,任何一个该类的对象去访问他的时候,取到的都是相同的词,同样任何一个该类的对象去修改,所修改的也是同一个对象.
如何定义及访问?
遵循相关访问权限
访问修饰符 static 数据类型 变量名;(推荐)
static 访问修饰符 数据类型 变量名;都可
通过类名.类变量名(推荐)或对象名.类变量名
使用细节
1.想让某个类的所有对象使用同一个变量时可以使用类变量
2.与普通属性的区别是是否是共享
3.声明和修饰访问修饰符 static 数据类型 变量名; 类名.类变量名,需满足访问权限
4.实例变量不可以用类名.变量名.
5.类变量是在类加载的时候就初始化了.也就是说,即使没有创建对象,只要类加载了就可以使用类变量了
6.类变量的生命周期是随类的加载开始,随类的消亡而销毁
类变量内存剖析
1.静态变量是被对象共享的
2.类加载的时候就生成了
类方法
声明和调用
访问修饰符 static 数据返回类型 方法名(){};(推荐)
static 访问修饰符 数据返回类型 方法名(){};都可
遵循相关访问权限
通过类名.类方法名(推荐)或对象名.类方法
最佳实践
当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率,如排序.打印数组...
注意事项
1.类方法和普通方法都随着类的加载而加载,而构造器是创建对象的时候初始化,将结构信息存储在方法区
2.类方法中无this参数
3.可以通过类名和对象名调用,普通方法和对象名有关,需要通过对象名调用
4.类方法中不允许使用和对象有关的关键词,如super,this,普通方法可以
5.类方法中只能访问静态变量和静态方法
6.普通成员方法,既可以访问普通方法又可以访问静态方法
静态VS非静态
在Java中,每次使用new
关键字创建类的实例时,都会在内存(主要是堆内存)中为该实例分配一块新的空间。这块空间是独立的,用于存储该实例的所有属性(无论是静态的还是非静态的,但这里有一个重要的区别需要注意)。
对于非静态属性(也称为实例变量或成员变量),它们是属于类的每个实例的。每个实例都会拥有自己独立的这些属性的副本。因此,当你修改一个实例的非静态属性时,这个修改只会影响那个特定的实例,而不会影响到其他实例或类的其他任何部分。
然而,对于静态属性(也称为类变量),情况就不同了。静态属性是属于类的,而不是属于类的任何特定实例。这意味着无论创建多少个类的实例,这些实例都会共享同一个静态属性。因此,对静态属性的修改会影响到所有实例。
main方法package com.study.main;
1.main方法是java虚拟机调用
2.因java虚拟机需要调用main方法,故main方法访问权限必须是public
3.java虚拟机在执行main方法是不必创建对象,故该方法必须是static
4.该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数
5.java执行的程序 String[] args数组名,传入参数
特别说明
1.main方法中,我们可以直接调用main方法所在类的静态方法和静态属性
2.但不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能调用这个对象去访问类中的非静态成员
package com.study.main;
public class Main01 {
private static String name = "韩顺平教育";
public static void hi(){
System.out.println("Main01的 hi方法");
}
private int n1=1000;
private static int n2=2000;
public void cry(){
System.out.println("Main01的cry方法");
}
public static void main(String[] args) {
//1.静态方法可以访问本类的静态成员
System.out.println("name="+name);
hi();
//2.静态方法main 不可以访问本类的非静态成员
// System.out.println("n1"+n1);x
// cry();也不行,需要创建对象来用
new Main01().cry();
}
}
代码块package com.study.codeblock;
基本介绍
代码块又称初始化块,属于类中的成员[即 是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过{}包围起来
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显示调用,而是加载类时(静态),或创建对象时隐式调用
基本语法
[修饰符]{
代码
};
说明注意:
1.修饰符可选,要写的话,也只能写static
2.代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的,叫普通代码块
3.逻辑语法可以为任何逻辑语句(输入,输出,方法调用,循环,判断)
4.;可写可不写
5.普通代码块是在创建对象时调用
理解:
1.相当于另一种形式的构造器(对构造器的补充机制),可以做初始化的操作
2.场景.如果多个构造器中都有重复的语句,可以抽取到初始化中,提高代码的重用性
不管调用哪个构造器创建都会先调用代码块
代码块的调用优先于构造
细节
1.static{代码块}称为静态代码块,作用是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次,如果是普通代码块,每创建一个代码块,就执行
2.***类什么时候加载***:
-
- 创建对象实例时(new)
- 创建子类对象实例,父类也会被加载
- 使用类的静态成员时(静态属性,静态方法),静态父子类也遵循b
3.普通代码块,在创建对象实例时,会被隐式的调用,被创建一次就调用一次,如果只是使用类的静态成员,普通代码块不会被执行
小结:
1.static代码块是在类加载时调用,只会执行一次
2.普通代码块是在创建对象时调用的,创建一次调用一次
3.静态代码块只能调用静态成员,普通代码块可以调用任意成员,静态代码块只会加载一次,加载完成后才能编译
****细节(重点)****
创建一个对象时,在一个类中调用顺序是:
1.调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按定义顺序调用)
2.调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通变量初始化,则按定义顺序调用)
3.调用构造方法
4.构造器的最前面其实隐含了super()和调用普通代码块:先super再普通代码块再执行构造器写的代码
5.创建一个子类对象,他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法调用顺序如下:
- 父类静态代码块和静态属性(优先级一样,但按定义顺序)
- 子类静态代码块 静态属性
- 父类普通代码块和普通属性初始化
- 父类的构造方法
- 子类普通代码块和普通属性初始化
- 子类的构造方法
package com.study.codeblock;
public class CodeBlockDetail04 {
public static void main(String[] args) {
//1.进行类的加载
//先加载父类A01静态 在加载子类B02静态
//2.创建对象
//先从子类的构造器开始
new B02();
}
}
class A02{
private static int n1=getVal01();
static {
System.out.println("A02的一个静态代码块");//(2)
}
{
System.out.println("A02的一个普通代码块");//(5)优先级一样但在前面
}
public int n2 = getVal02();
public static int getVal01() {
System.out.println("getVal01");//(1)
return 10;
}
public int getVal02() {
System.out.println("getVal02");//(6)
return 10;
}
public A02(){
//super();
//普通代码块和普通属性初始化
System.out.println("A02的构造器");//(7)
}
}
class B02 extends A02{
private static int n3=getVal03();
static {
System.out.println("B02的一个静态代码块");//(4)
}
{
System.out.println("B02的一个普通代码块");//(8)
}
public int n4 = getVal04();
public static int getVal03() {
System.out.println("getVal03");//(3)
return 10;
}
public int getVal04() {
System.out.println("getVal04");//(9)
return 10;
}
public B02(){
//super
//普通代码块
System.out.println("B02的构造器");//(10)
}
}
/*getVal01
A02的一个静态代码块
getVal03
B02的一个静态代码块
A02的一个普通代码块
getVal02
A02的构造器
B02的一个普通代码块
getVal04
B02的构造器
*/
单例设计模式
1.所谓单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方式
2.单例模式有两种:饿汉式 懒汉式
饿汉式
饿汉式(Eager Initialization)设计模式是一种在类加载时就立即初始化其实例的设计模式。这种设计模式的特点是,不管你最终是否使用这个类的实例,它都会在类被加载到JVM时就被创建。饿汉式模式通常用于那些不依赖于具体实例创建顺序的、且其创建过程不复杂(即不会消耗太多资源或时间)的场景。
步骤如下:
1.构造器私有化->防止直接new
2.类的内部创建一个静态对象
3.向外暴露一个静态的公共方法
记忆和理解技巧
- “饿”:可以理解为“迫不及待”,即类一加载就立即创建实例,不等待任何条件。
- “汉”:可以联想到“汉子”,即这个实例就像是一个坚定不移的汉子,一旦创建就保持不变,供所有需要的人使用。
- 单例的保证:通过私有化构造方法和提供一个公共的静态方法来保证单例的实现。
懒汉式
懒汉式(Lazy Initialization)设计模式是另一种实现单例模式的方式,与饿汉式不同,懒汉式在类被加载时并不立即创建实例,而是在第一次需要时才进行实例化。这种方式可以延迟对象的创建,从而减少资源消耗,特别是在实例创建过程复杂或耗时的情况下更为有利。以下是懒汉式单例模式的详细解释:
懒汉式单例模式的实现
懒汉式单例模式通常包含以下几个关键点:
- 私有静态变量:用于存储类的唯一实例,但初始时不进行实例化。
- 私有构造方法:防止外部通过
new
关键字创建类的实例。 - 公共静态方法:提供一个公共的静态方法来获取类的实例。如果实例不存在,则创建它;如果已存在,则直接返回。
饿汉式VS懒汉式
1.两者主要区别在于创建对象的时机不同,饿汉式是在类加载时就创建了对象实例,而懒汉式是在使用时创建
2.饿汉式不存在线程安全的问题,懒汉式存在
3.饿汉式存在浪费资源的可能.懒汉式不存在
final关键词(package com.study.final_;)
使用final关键字的情况
1.当不希望类被继承时,用final修饰
2.不希望父类的某个方法被子类覆盖/重写可以用final修饰
3.不希望某个属性被修改也可以用final修饰->常量
4.当不希望某个局部变量被修改也可以用final修饰->局部常量
细节
1.final修饰的属性叫常量,命名为XXX_YYY_OOO
2.final修饰的属性在定义时必须赋初值,并且以后不能修改,赋值可在如下位置之一:
(1)定义时
(2)构造器中
(3)代码块中
3.如果final修饰的属性是静态的,则初始化的位置只能是(1)定义时(2)静态代码块 不能在构造器中赋值
4.final类不能继承,但能实例化对象
5.如果累不是final类,但含有final方法,该方法虽不能被重写,但该类可以被继承,也可以使用
6.如果一个类已经是final类了,那里面的方法就不用final修饰了,因为已经不能被继承了自然就不会被重写
7.final不能修饰构造器
8.final和static搭配使用效率更高,不会导致类的加载,底层编译器做了优化处理
9.包装类(Integer Double Float Boolean,等都是final),String也是final类
抽象类(package com.study.abstract_;)
1.用abstract关键字来修饰一个类或一个方法时,这个类就是抽象类/抽象方法
访问修饰符 abstract 类名{}/访问修饰符 abstract 返回类型 方法名(参数列表);//没有方法体
2.抽象类的价值更多在于设计,设计者设计好后,让子类继承并实现抽象类
细节
1.抽象类不能实例化
2.抽象类不一定要包含抽象方法,还可以有实现的方法
3.一个类含有抽象方法就必须是抽象类,抽象方法不能有方法体
4.abstract只能修饰类和方法,不能修饰属性和其他
5.抽象类本质还是类,可以有任何类有的成员
6.如果一个类继承了抽象类,就必须重写所有的抽象方法,即必须有方法体
7.抽象方法不能使用private final static来修饰,因为这些关键字和重写相违背
抽象-模板设计模式
在Java中,抽象类模板设计模式(Template Method Pattern)是一种行为型设计模式,它定义了一个操作中的算法的骨架,而将一些步骤延迟到子类中。这使得子类可以在不改变算法结构的情况下,重新定义算法中的某些特定步骤。这种设计模式特别适用于那些包含多个算法步骤,且这些步骤中某些是固定的,而某些是可变的场景。
抽象类模板设计模式的关键组成部分
- 抽象类(Abstract Class):
-
- 定义了一个或多个抽象方法,这些方法在子类中被实现。
- 包含了一个模板方法,这个模板方法定义了算法的骨架,它将调用在抽象类中定义的抽象方法。
- 具体子类(Concrete Subclasses):
-
- 实现抽象类中定义的抽象方法。
- 继承抽象类,并通过实现抽象方法,提供算法的具体步骤。
举例
package com.study.abstract_;
abstract public class Template {//抽象类-模板设计模式
public abstract void job();//该方法会动态绑定,该方法会和运行类型绑定
public void calculateTime() {
long startTime = System.currentTimeMillis();
job();
long endTime = System.currentTimeMillis();
System.out.println("任务执行时间" + (endTime - startTime) + "ms");
}
}
package com.study.abstract_;
public class TemplateTest {
public static void main(String[] args) {
AA aa = new AA();
aa.calculateTime();
BB bb = new BB();
bb.calculateTime();
}
}
package com.study.abstract_;
public class AA extends Template {
public void job() {
long num = 0;
for (long i = 1; i <= 89989989; i++) {
num += i;
}
}
}
package com.study.abstract_;
public class BB extends Template {
public void job() {
long num = 0;
for (long i = 1; i <= 8000450; i++) {
num *= i;
}
}
}
接口package com.study.Interface_;***
接口就是给出一些没有实现的方法,封装到一起,到某个要使用的时候,再根据具体情况把这些方法写出来:
语法 :
interface 接口名{
//属性
//方法
}
class 类名 implements 接口{
自己属性
自己方法
必须实现接口的抽象方法
}
接口应用场景
接口使用细节
1.接口不能实例化
2.接口中的所有方法都是public 方法,接口里的抽象方法可以省略关键字abstract
3.一个普通类实现接口,就必须实现该接口的所有抽象方法,IDEA中快捷键Alt+Enter来快速实现所有方法
4.抽象类实现抽象方法可以不用实现接口中的方法
5.一个类同时可以实现多个接口
6.接口中的属性只能是final的,而且是public static final修饰
7.接口中属性的访问形式,接口名.属性名
8.接口不能继承其他的类,但可以继承多个别的接口
9.接口的修饰符只能是public 和默认,这点和类的修饰符一样
通过接口名直接访问:
通过实现了接口的类的实例访问
通过实现了接口的类的类名访问
接口VS实现
当子类继承了父类,就自动拥有了父类的功能
如果子类需要扩展功能,可以通过实现接口的方式扩展
实现接口就像是对java单继承机制的一种补充
package com.study.Interface_;
public class InterfaceDetail04 {
public static void main(String[] args) {
littleMonkey wukong = new littleMonkey("悟空");
wukong.climbing();
wukong.fish();
wukong.fly();
}
}
interface Fish{
void fish();
}
interface Flu{
void fly();
}
class Monkey {
private String name;
public Monkey(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void climbing() {
System.out.println(getName()+ "会爬树");
}
}
class littleMonkey extends Monkey implements Fish, Flu{
public littleMonkey(String name) {
super(name);
}
@Override
public void fish() {
System.out.println(getName()+"通过努力学会了游泳");
}
@Override
public void fly() {
System.out.println(getName()+"通过努力学会了飞翔");
}
}
继承的价值主要在于:解决代码的复用性和可维护性
接口的主要价值在于:设计,设计好各种规范,让其他类去实现这些方法,更加的灵活
接口比继承更加灵活,在一定程度上实现代码解耦[即:接口规范性+动态绑定]
接口多态特性
1.创建多个类接收接口,每个类中都提供接口中声明方法的具体实现,这些实现可以各不相同,体现出接口的多态
2.多态性的真正力量在于能够使用接口类型的引用来引用实现了该接口的任何对象
3.多态数组,让接口类型的引用分别指向不同的实例对象
4.多态传递现象:让一个接口继承一个接口,访问接口的变量 接口.属性
访问父类super.属性 访问本类 this.属性
package com.study.Interface_;
public class InterfaceExmple {
}
interface Animal {
void makeSound();
}
class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("小狗汪汪叫");
}
}
class Cat1 implements Animal {
@Override
public void makeSound() {
System.out.println("我嘞个骚刚");
}
public void food(){
System.out.println("coke爱看美女");
}
}
class TestPolymorphism {
public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat1();
// 调用同一个方法,但根据对象的实际类型执行不同的操作
//myDog.makeSound(); // 输出: Woof!
//myCat.makeSound(); // 输出: Meow!
//定义接口数组,让接口数组的引用指向实例对象
// Animal[] animal = {myDog, myCat};
Animal[] animal1 = new Animal[2];
animal1[0] = new Dog();
animal1[1] = new Cat1();
for(int i=0;i<animal1.length;i++){
animal1[i].makeSound();//动态绑定,输出不同
//想要调用Cat1的特有方法需要进行类似于向下转型的操作
if(animal1[i] instanceof Cat1){
((Cat1)animal1[i]).food();
}
}
}
}
package com.study.Interface_;
/**
* 演示多态传递现象
*/
public class InterfacePloyPass {
public static void main(String[] args) {
//接口类型的变量可以指向,实现了接口对象的实例
IG ig = new Teacher();
//如果IG 继承了 IH 接口,而Teacher类实现了IG 接口
//那么实际上就相当于 Teacher类也实现了IH 接口
//这就是 接口多态传递现象
IH ih = new Teacher();
}
}
interface IH{}
interface IG extends IH{}
class Teacher implements IG{
}
内部类***package com.study.innerclass;
一个类的内部又完整的嵌套了另一个类结构.被嵌套的称为内部类.
类的五大成员
属性 方法 构造器 代码块 内部类
内部类的分类
定义在外部类局部位置上(比如方法内)
1.局部内部类(有类名)
2.匿名内部类(没有类名,*******)
定义在外部类的成员位置上:
3.成员内部类(没有static修饰)
4.静态内部类(使用static修饰)
局部内部类
1.局部内部类定义在外部类的局部位置,通常在方法,并且有类名
2.//不能添加访问修饰符,但可以用final修饰,他类似于局部变量,这也导致了外部其他类不能访问局部内部类
3.可以直接访问外部类的所有成员,包括私有成员
4.作用域:仅仅在定义它的方法和代码块中
5.外部类可以在方法中,创建内部类的对象实例,然后调用方法
6.如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员可以使用(外部类名.this.成员)去访问
LocallnnerClass
匿名内部类*****
本质还是类,而且是内部类,该类有没名字,同时还是一个对象
基本语法
new 类或接口(参数列表){
类体
};
这个类或接口会被隐式的继承或实现
细节
1.匿名内部类就是一个类的定义,同时它本身也是一个对象
2.可以直接访问外部类的所有成员包括私有
3.不能添加访问修饰符,因为他只是一个局部变量
4.作用域:仅仅在它定义的方法或代码块中
5.外部其他类不能访问匿名内部类
6.如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类成员,则可以使用(外部类名.this.成员)去访问
7.匿名内部类必须继承一个父类或实现一个接口,但不能同时继承一个父类和实现一个接口,一般是在定义的时候隐式实现,
package com.study.innerclass;
public class AnonymousInnerClass {
public static void main(String[] args) {
new Outer04().method();
}
}
class Outer04 {
private int n1 = 10;
public void method() {
//基于接口的匿名内部类
//对于只是用一次的类去创建对象实现接口比较麻烦
//可以使用匿名内部类来简化开发
//tiger的编译类型是?A等号左边,运行类型就是匿名内部类XXX=>Outer04$1
/*
* 我们看底层,底层会分配一个类名
* class XXX implements A{
* @Override
* public void cry() {
System.out.println("老虎叫唤...");
}
}
* */
//jdk底层创建匿名内部类,立即就创建了Outer04$1实例,并把地址返回给tiger
//匿名内部类使用一次就不能再使用,但对象会存在
A tiger = new A() {
public void cry() {
System.out.println("老虎叫唤...");
}
};
System.out.println("tiger的运行类型" + tiger.getClass());
//tiger的运行类型class com.study.innerclass.Outer04$1
tiger.cry();
//演示基于类的匿名内部类
//编译类型Father,运行类型Outer04$2按顺序...
//Father father = new Father("jack");带大括号就是匿名内部类了
//底层会创建匿名内部类
/*
class Outer04$2 extends Father{
@Override
public void test() {
System.out.println("匿名内部类重写了test方法");
}
}
*/
//参数列表会传递给构造器
Father father = new Father("jack") {
@Override
public void test() {
System.out.println("匿名内部类重写了test方法");
}
};//隐式的继承了Father类
System.out.println("father的运行类型是" + father.getClass());
//father的运行类型是class com.study.innerclass.Outer04$2
father.test();
//基于抽象类的匿名内部类
B b = new B(){
@Override
public void eat(){
System.out.println("重写了抽象类的方法eat()");
}
};
b.eat();
}
}
interface A {
public void cry();
}
class Father {
public Father(String name) {
System.out.println("接收到了name="+name);
}
public void test() {
}
}
abstract class B{
abstract void eat();
}
匿名内部类的最佳实践
将匿名内部类当做实参传递,更加简洁高效
package com.study.innerclass;
public class InnerClassExercise {
public static void main(String[] args) {
CellPhone cellphone = new CellPhone();
cellphone.alarmClock(new Bell(){
@Override
public void ring(){
System.out.println("懒猪起床了");
}
});
cellphone.alarmClock(new Bell(){//隐式的实现了Bell接口
@Override
public void ring(){
System.out.println("上课了");
}
});
}
}
interface Bell{
void ring();
}
class CellPhone{
public void alarmClock(Bell bell){//形参是接口类型
bell.ring();//传进来的bell运行类型是匿名内部类型,调用ring的时候
//会访问匿名内部类的ring()方法,导致匿名内部类中怎样重写就怎样输出
}
}
成员内部类
定义在外部类的成员位置,并且没有static修饰
1.可以直接访问外部类的所有成员包括私有
2.可以添加任意访问修饰符,因为他的地位就是一个成员
3.作用域:整个类体
4.外部类可以通过创建对象的方式来访问成员内部类
5.外部其他类访问成员内部类的方式看例子, 在外部其他类创建外部类的对象.内部类 内部类对象 = 外部类的对象.new 内部类
也可以写一个方法
6.如果外部类和成员内部类的成员重名时,成员内部类访问的话,默认遵循就近原则,如果想访问外部类成员,则可以使用(外部类名.this.属性)去访问
package com.study.innerclass;
public class MemberInnerClass01 {
public static void main(String[] args) {
Outer03 outer03 = new Outer03();
outer03.t1();
//外部其他类访问成员内部类的方式
//将Inner03成员内部类看做一个属性来访问;相当于把 new Inner03()当做Outer03的成员
Outer03.Inner03 inner08 = outer03.new Inner03();
//第二种方式,在外部类中,编写一个方法可以放回Inner03对象
Outer03.Inner03 inner03 = outer03.getInner03();
inner03.say();
}
}
class Outer03{
private int n1 = 10;
public String name = "张三";
private void hi(){
System.out.println("hi");
}
class Inner03{
private double sal = 99.8;
public void say(){
System.out.println("n1="+n1+",name="+name);
hi();
}
}
Inner03 inner03 = new Inner03();
public void t1(){
//使用成员内部类
System.out.println(inner03.sal);
Inner03 in = new Inner03();
in.say();
}
public Inner03 getInner03(){
return new Inner03();
}
}
静态内部类
1.放在外部类的成员位置
2.使用static修饰,可以添加任意访问修饰符
3.可以访问外部类的所有静态成员,包括私有,但不能访问非静态成员
4.作用域:同其他的成员,为整个类体
5.静态内部类可以访问外部类的所有静态成员
6.外部类访问静态内部类需要创建对象 在访问
7.外部其他类想访问
//方式1
Outer10.Inner10 inner10 = new Outer10.Inner10();
inner10.say();//通过对象名直接访问但需遵循访问权限
//方式2
//编写一个一个方法,可以返回静态内部类的对象实例
Outer10.Inner10 inner101 = outer10.getInner10();
inner101.say();
Outer10.Inner10 inner102 = outer10.getInner10_();
inner102.say();
8.如果外部类和静态内部类的成员重名时,静态内部类访问的话,默认遵循就近原则,如果想访问外部类成员,则可以使用(外部类名.属性)去访问