内部类及其创建方式
引言
在定义类时,我们可以将一个类定义在另一个类的内部,此时这个类便被成为内部类。
内部类是一种非常有用的特性,因为它允许你吧一些逻辑相关的类组织在一起,并控制位于内部的类的可视性。然而必须要了解,内部类与组合是两种完全不同地方概念,这一点很重要。
在一开始,内部类看起来可能想hi是一种代码的隐藏机制,仅仅是用来将类的定义放置在其他的类的内部,但是,随着对于内部类的不断了解,我们会发现其远不止如此,它能够了解定义它的外部类,并能与之通信,利用内部类的种种特点,我们可以将代码变得更加优雅和清晰。
基本的内部类的创建方式
正如上面对于内部类的定义一样,当把一个类的定义置于另一个类的内部时,我们便完成了一个内部类的创建。
在下面,我们假设创建了一个披萨店的类,在其中定义了两个内部类,分别为奶酪披萨cheesePizza与香肠披萨sausagePizza。
package learning_java.InnerClassTest;
public class PizzaStore {
class cheesePizza {
private String name = "cheesePizza";
public void getName() {System.out.println("Got a " + name);}
}
class sausagePizza {
private String name = "sausagePizza";
public void getName() {System.out.println("Got a " +name);}
}
// 只有在该外部类的非静态方法中,才可以这样直接创建内部类的对象
public void orderPizza(String pizzaName) {
if (pizzaName == "cheesePizza") {
cheesePizza pizza = new cheesePizza();
pizza.getName();
}
else if (pizzaName == "sausagePizza"){
sausagePizza pizza = new sausagePizza();
pizza.getName();
}
else
System.out.println("Don't have this kind of pizza");
}
public static void main(String[] args) {
PizzaStore store = new PizzaStore();
store.orderPizza("cheesePizza");
}
}
在定义了两个披萨的内部类之后,我们又定义了一个订披萨的方法orderPizza,在这个方法里,我们会根据所输入的需要来创建披萨的对象。而我们在这个方法里面使用内部类的时候,看起来似乎与普通类并没有什么不同,在这里,实际的区别只是内部类的定义时嵌套在PizzaStore类里面的,但是接下来我们会看到,这并不是唯一的区别。
public class PizzaStore2 {
class cheesePizza {
private String name = "cheesePizza";
int size;
cheesePizza(int inputSize){
size = inputSize;
}
public void getName() {System.out.println("Got a " + name + " of size: " + size);}
}
class sausagePizza {
private String name = "sausagePizza";
int size;
sausagePizza(int inputSize){
size = inputSize;
}
public void getName() {System.out.println("Got a " + name + " of size: " + size);}
}
public sausagePizza getSausagePizza(int size){
return new sausagePizza(size);
}
public cheesePizza getCheesePizza(int size){
return new cheesePizza(size);
}
public void orderPizza(String pizzaName) {
if (pizzaName == "cheesePizza") {
cheesePizza pizza = new cheesePizza(5);
pizza.getName();
}
else if (pizzaName == "sausagePizza"){
sausagePizza pizza = new sausagePizza(5);
pizza.getName();
}
else
System.out.println("Don't have this kind of pizza");
}
// 如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须像在main()方法中这样,具体地知名这个对象的类型:OuterClassName.InnerClassName
public static void main(String[] args) {
PizzaStore2 store = new PizzaStore2();
store.orderPizza("cheesePizza");
PizzaStore2 store2 = new PizzaStore2();
PizzaStore2.cheesePizza pizza = store2.getCheesePizza(6);
pizza.getName();
}
}
在上面的代码中,我们为两个披萨写了其构造函数,并用getCheesePizza
与getSausagePizza
方法来返回这两个披萨的实例,而我们在main()
中声明其的时候用的是PizzaStore2.cheesePizza
。与这个定义类似,对于内部类,如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须像在main()方法中这样,具体地知名这个对象的类型:OuterClassName.InnerClassName。即,即使定义该外部类的内部类的静态方法也不行,除该外部类之外的所有类都不可以。
举个例子,如果在我们的披萨商店pizzaStore2中加入如下方法,可以看到其创建实例的方式与之前orderPizza
方法是完全相同的。
public static void test() {
cheesePizza pizza = new cheesePizza(5);
}
此时便会报错:
Error:(29, 29) java: 无法从静态上下文中引用非静态 变量 this
内部类与外部类的链接
到目前位置,内部类似乎还只是一种组织代码的模式,但是我们马上就能看到它的别的用途。当我们生成一个内部类的对象的同时,此对象与制造它的外围对象(enclosing object)之间就有了一个一种联系,这种联系使得它能够访问其外围对象的所有成员,而不需要任何特殊条件。此外,不仅于外围对象,内部类还拥有其外围类的所有元素的访问权。
public class PizzaStore3 {
private int materialsNum;
private static int storeNum = 0;
private static int allPizzaNum = 0;
public void showMaterialsNum() {System.out.println("the num of materials: " + materialsNum);}
public static void showStoreNum() {System.out.println("the num of store: " + storeNum);}
public static void showAllPizzaNum() {System.out.println("the num of all the pizza: " + allPizzaNum);}
public PizzaStore3(int materialsNum) {
this.materialsNum = materialsNum;
storeNum ++;
}
class cheesePizza implements Pizza{
private String name = "cheesePizza";
int size;
cheesePizza(int inputSize){
size = inputSize;
materialsNum -= size;
allPizzaNum ++;
}
public void getName() {System.out.println("Got a " + name + " of size: " + size);}
}
class sausagePizza implements Pizza{
private String name = "sausagePizza";
int size;
sausagePizza(int inputSize){
size = inputSize;
materialsNum -= size;
allPizzaNum ++;
}
public void getName() {System.out.println("Got a " + name + " of size: " + size);}
}
public sausagePizza getSausagePizza(int size){
return new sausagePizza(size);
}
public cheesePizza getCheesePizza(int size){
return new cheesePizza(size);
}
public void orderPizza(String pizzaName) {
Pizza pizza;
switch(pizzaName) {
case "cheesePizza": pizza = new cheesePizza(5);break;
case "sausagePizza": pizza = new sausagePizza(5); break;
default: System.out.println("Don't have this kind of pizza"); return;
}
pizza.getName();
}
public static void main(String[] args) {
PizzaStore3 store = new PizzaStore3(100);
store.showMaterialsNum();
PizzaStore3.showAllPizzaNum();
PizzaStore3.cheesePizza pizza = store.getCheesePizza(6);
pizza.getName();
store.showMaterialsNum();
PizzaStore3.showAllPizzaNum();
}
}
在这里,我们依然以我们的披萨店为例,这一次,我们给每个披萨店增加了一个int
类型的成员materialsNum
来记录店里的原料的数量,而我们每制作一个披萨就会消耗与其大小相同数量的原料,同时为我们的PizzaStore3
类增加了一个静态变量allPizzaNum
来记录我们所有的披萨店所制作的披萨的数量,每当我们调用披萨的构造函数制作出一张披萨时,我们就在这个全局的数量上加一。
之后,我们在main()
中新建了一个store,并在store中制作了一个pizza,我们将以上的信息打印出来:
the num of materials: 100
the num of all the pizza: 0
Got a cheesePizza of size: 6
the num of materials: 94
the num of all the pizza: 1
可以看到,无论对于对象的成员,还是类的静态成员,内部类都可以与其进行房访问并进行修改。当我们仔细查看内部类cheesePizza
与sausagePizza
时,其构造函数都用到了变量materials
,而这个变量并不是属于这两个内部类的,而是属于外围类pizzaStore
的private
字段,这里其实是一个引用,这使得内部类可以访问外围类的方法和字段,就像自己拥有它们似的,这带来了很大的方便。
所有内部类自动拥有对其外围类所有成员的访问权,但是这是如何做到的呢?其实,当某个外围类的对象创建了一个内部类的对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用,然后,在访问词外围类的成员时,就是用那个引用来选择外围类的成员。而这一系列过程中,编译器会帮助我们处理所有的细节,但现在可以看到,内部类的对象只能在与其外围类的对象的关联的情况下才能被创建(当内部类不是static
时),构建一个内部类的对象时,需要一个指向其外围类的对象的引用,如果编译器访问不到这个引用,就会报错。而内部类与其外围类的这种关系,便引出了我们的下一节的内容。
通过.this来在内部类中引用其外围类
如上一节中所讲,一个外部类中的内部类的对象,是与这个外部类的对象存在着一个链接的依存关系的,那么我们是不是可以在内部类中显式地调起这个外部类的对象呢?答案是肯定的。如果我们在内部类中生成对于外围类对象的引用,可以使用外部类的名字后面紧跟.this
,这样产生的引用自动对地具有正确的类型,这一点在编译期就会被知晓并受到检查,因此没有任何运行时的开销。我们在以下这个简化的披萨商店的类中来展示这一点。
public class PizzaStore4 {
void getStoreName() {System.out.println("PizzaStore4!");}
public class CheesePizza {
public PizzaStore4 getStore() {
// 在这里通过.this来引用其外围类
return PizzaStore4.this;
}
}
public CheesePizza cheesePizza() {return new CheesePizza();}
public static void main(String[] args) {
PizzaStore4 store = new PizzaStore4();
PizzaStore4.CheesePizza pizza = store.cheesePizza();
pizza.getStore().getStoreName();
}
}
输出:
PizzaStore4!
通过.new来创建内部类的对象
在第一节中,当我们创建内部类的对象时,用的都是外部类中提供的可以返回一个新的内部类的对象的方法,如getCheesePizza
与getSausagePizza
,而外部类中并不总是提供这样的方法,那么我们怎样直接得到一个内部类的对象呢?
正如内部类与外部类的对象的链接一节中所讲,每个内部类都是与其外部类的一个对象相链接的,因此在创建内部类的对象时,也需要体现出这一点,我们是不能够直接new
一个内部类的对象出来的,那如何与外部类的对象产生关联呢?这里,我们就需要使用.new
语法了,下面这个例子会介绍这个语法的使用方法。
public class PizzaSotre5 {
public class CheesePizza{}
public static void main(String[] args) {
PizzaSotre5 store = new PizzaSotre5();
// 通过.new来创建内部类的对象
PizzaSotre5.CheesePizza pizza = store.new CheesePizza();
}
}
由于我们是直接用PizzaStore
来创建的内部类对象pizza
,因此这样便直接解决了内部类对象与外部类对象的链接与内部类对象的作用域问题。实际上,这里我们不能去引用外部类的名字PizzaStore5
,而必须用对象来创捷内部类对象,我们不必也不能store.new PizzaStore5.CheesePizza()
。
由此,我们可以看到,在拥有外部类的对象之前,是不饿能创建内部类的对象的。这是因为内部类对象会自动地连接到创建它的外部类的对象上。但是,如果我们创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用。