JAVA自学笔记,面向对象的核心技术。

类的封装

封装是面向对象编程的核心思想,将对象的属性和行为封装起来,其载体就是类。如果我们直接把代码全部写在main方法里。代码完全暴露,我可以任意删改,那么程序可能会无法运行,下面举两个小例子。
在这里插入图片描述
在这里插入图片描述
上面的例子中我们可以看到,所有的代码都在main方法中,完全暴露在外面,别人想怎么改就怎么改,这对我们代码并不安全。那么我们如何防止其他人修改呢?最好的办法当然是将厨师打包成类。
在这里插入图片描述
在这里插入图片描述
这个代码中我们将厨师封装成一个类,将厨师的工作定义成厨师类行为,当我们想让厨师做菜,只能通过调用对象的成员方法的方式实现,而我们却不知道这个方法到底是怎么写的,所以就无法随意修改。其实按照日常的生活场景来讲,顾客去餐馆吃饭,下单的是服务员,上菜的也是服务员,顾客根本没有接触厨师的机会,所以厨师这个角色是对顾客隐藏起来的,那么我们就可以用private关键字来修饰厨师类中的属性,将其私有化。将对象的属性和行为封装起来的载体就是类,类通常对客户隐藏其实现细节,这就是封装的思想。

类的继承

继承在面向对象开发思想中是一个非常重要的概念。它使整个程序架构具有一定的弹性,在程序中服用已经定义完善的类不仅可以减少软件开发周期,还可以提高提高软件的可维护性和可拓展性。我们前面也介绍过类的继承,其基本思想是基于父类的扩展,制定出一个新的子类,子类可以继承父类原有的属性和方法,也可以增加原来父类所不具备的属性和方法,或者直接重写父类中的某些方法。比如同样是汽车,但是房车是其中比较特殊的一种他也拥有汽车类中的一些属性和方法,但是它又在汽车的基础上扩展出了许多新属性和方法。
在这里插入图片描述
在这里插入图片描述
之前的文章中讲过,继承的关键字是extends。这个例子中,Pad继承了父类Computer类,虽然Pad类中没有定义任何成员方法,但仍可以调用父类的方法。这个方法就是从父类那里继承过来的。

方法的重写

继承并不只是能扩展父类的功能,还可以重写父类的成员方法。重写就是在子类中将父类的成员方法名称保留,重新编写成员方法的实现内容,更改成员方法的储存权限,或是修改成员方法的返回值类型。
在继承中还有一种特殊的重写方式,子类与父类的成员方法返回值,方法名称,参数类型及个数完全相同,唯一不同的是方法实现内容,这种特殊重写方式被称为重构。另外在重写父类的方法时,修改方法的权限修饰只能从小的范围到大的范围,假设父类的修饰权限为protected,继承后子类中的方法只能修改为public,不能修改为private。我们来看一个小例子。
在这里插入图片描述
在这里插入图片描述
从这个结果我们可以看出,虽然子类调用了父类的方法,但实现的是子类重写后的逻辑,而不是父类原有的逻辑,如果父类声明的对象是由子类实例化的,那么这个对象所调用的方法也是被子类重写过的。在Java中一个类只可以有一个父类,也就是说,Java只支持单继承不支持多继承。

super关键字

如果子类重写了父类的方法,就再也无法调用到父类的方法了吗?如果想在子类的方法中实现父类原有的方法怎么办?为了解决这种需求,Java中提供了super关键字。
super关键字的用法和this关键字类似,this关键字代表本类对象,super关键字代表父类对象。

super.property;//调用父类的属性
super.method();//调用父类的方法

在这里插入图片描述
在这里插入图片描述
注意,如果在子类构造方法中使用类似super()的构造方法,其他初始化代码只能写在super()之后,不能写在前面,否则会报错。在继承机制中,创建一个子类对象,将包含一个父类子对象,这个对象与父类创建的对象是一样的。两者的区别在于后者来自外部,而前者来自子类对象的内部。当实例化子类对象时,父类对象也相应被实例化,换句话说,在实例化子类对象时,Java编译器会在子类的构造方法中自动调用父类的无参构造方法,但有参构造方法并不能被自动调用,只能依赖于super关键字显示地调用父类的构造方法。

Object

object是所有类的父类,在开始学习使用class关键字定义类时,就应用了继承原理。因为在Java中,所有类都直接或间接继承了java.long.Object类。Object类是比较特殊的类,它是所有类的父类,是Java类层中的最高层类。当创建一个类时,总是在继承,除非某个类已经指定要从其他类继承,否则它就是从java.long.Object类继承而来的,可见Java中的每个类都源于java.long.Object类,如String,Integer等类都是继承于Object类,除此之外自定义的类也都继承与Object类。由于所有类都是Object子类,所以在定义类时,省略了extends Object关键字。在Object类中主要包括clone(),finalize(),equals(),toString()等方法,其中常用的就是equals()和toString()方法。由于所有的类都是Object类的子类,所以任何类都可以重写Object类中的方法。下面介绍几个Object中的常见方法。

getClass()方法

getClass()方法是Object类定义的方法,它会返回对象执行时的Class实例,然后使用此实例调用getName()方法可以取得类的名称。

getClass().getName();

可以将getClass()方法与toString()方法联合使用。

toString()方法

toString()方法的功能是讲一个对象返回为字符串形式,它会返回一个String实例。在实际的应用中通常重写toString()方法,为对象提供一个特定的输出模式。当这个类转换为字符串或与字符串连接时,将自动调用重写的toString()方法。
在这里插入图片描述
在这里插入图片描述
在此例中重写父类Object的toString()方法,在子类的toString()方法中使用Object类中的getClass()方法获取当前运行的类名,定义一段输出字符串,当用户打印ObjectInstance类对象时,将自动调用toString()方法。

equals()方法

前面讲过equals方法,当时是比较“ == ”运算符和equals()方法,说明二者之间的区别,我们带着以前的知识,来看下面的小例子。
在这里插入图片描述
在这里插入图片描述
从本实例的结果可以看出,在自定义的类中使用equals()方法今次那个比较时,将返回false,因为equals()方法的默认实现是使用“ == "运算符比较两个对象的引用地址,而不是比较对象的内容,所以想要真正做到比较两个对象的内容,需要在自定义类中重写equals()方法。

类的多态

多态意为一个名字可具有多种语义,在程序设计语言中,多态性是指“一种定义,多种实现”,例如,运算符“+”作用于两个整形量时是求和,而作用与两个字符型量时则是将其连接在一起。利用多态可以使程序具有良好的扩展性,并可以对所有类对象进行通用的处理,类的多态性可以从两方面体现:一是方法的重载,二是类的上下转型。

方法的重载

在前面我们介绍过构造方法,知道构造方法的名称由类名决定,所以构造方法只有一个名称,但是如果希望以不同的方式来实例化对象,就需要使用多个构造方法来完成。由于这些构造方法都需要根据类名进行命名,为了让方法名相同而形参不同的构造方法同时存在,必须用到“方法重载”。虽然方法重载起源于构造方法,但是它也可以应用到其他方法中。
方法的重载就是在同一个类中允许同时存在一个以上的同名方法,只要这些方法的参数个数或类型不同即可。来看个例子。
在这里插入图片描述
在这里插入图片描述
本例中分别定义了6个方法,这6个方法中,每一个都有不相同的地方,所以构成了重载关系,虽然在方法的重载中可以使两个方法的返回值类型不同,但只有返回值类型不同并不足以区分两个方法的重载,还需要通过参数的个数以及参数的类型来设置。根据上面的例子可以总结出编译器是利用方法名,参数类型,参数个数以及参数顺序来确定类中的方法是否唯一。方法的重载使得方法以同意的名称被管理,使程序代码有条理。
重载和重写都体现了面向对象的多态性,但重载与重写是两个完全不同的概念,重载主要用于一个类内实现若干重载的方法,这些方法的名称相同而参数形式不同,而重写主要用于子类继承父类时,重新实现父类中的非私有方法。

向上转型

对象类型的转换在Java编程中经常遇到,主要包括向上转型和向下转型操作。我们先说向上转型。例如,平行四边形是四边形的一种,那么就可以将平行四边形对象看作是一个四边形对象。再比如,鸡是家禽的一种,而家禽是动物中的一类,那么也可以将鸡对象看作是一个动物对象。
在这里插入图片描述
在本例中,平行四边形类继承了四边形类,四边形类存在一个draw()方法,它的参数是Quadrangle类型,而在平行四边形类的主方法中调用draw()时给予的参数类型却是Parallelogram(平行四边形类)类型的。这类一直在强调一个问题,就是平行四边形也是一种类型的四边形,所以可以将平行四边形类的对象看作是一个四边形类的对象,这就相当于把子类对象赋值给父类类型的变量,这种技术被称为“向上转型”。试想一下正方形类对象可以作为draw()方法的参数,梯形类对象同样也可以作为draw()方法的参数,如果在四边形类的draw()方法中根据不同的图形对象设置不同的处理,就可以做到在父类中定义一个方法完成各个子类的功能,这样可以使同一份代码毫无差别的运用到不同类型之上,这就是多态机制的基本思想。由于向上转型是从一个较为具体的类到较为抽象的类的转换,所以它总是安全的。

向下转型

通过向上转型可以推理出向下转型是将较抽象类转换为较具体的类。但是这样的转型通常会出现问题,例如,不能说四边形是平行四边形的一种,因为这非常不合乎逻辑。可以说子类对象总是父类对象的一个实例,但父类对象不一定是子类的实例。我们来修改一下前面的例子。看一下程序如何处理这种情况。
在这里插入图片描述
在这里插入图片描述
可以看到如果将父类对象直接赋予子类,会发生编译器错误,因为父类对象不一定是子类的实例,比如说,一个四边形不一定是平行四边形,也有可能是梯形,正方形,菱形。越是具体的对象,具有的特性越多,越抽象的的对象具有的特性越少。在做向下转型操作时,将特性范围小的对象转换为特性范围大的对象肯定会出现问题。所以这时要告知编译器,这个四边形就是平行四边形。将父类对象强制转换为某个子类对象,这种方式称为显示类型转换。我们将前面的代码修改一下。
在这里插入图片描述
这样程序即可正常运行,通过这个例子可以说明,父类对象要变成子类的对象,必须通过显示类型转换才能实现,这种模式与我们之前讲过的基本类型转换一样。

instanceof关键字

当在程序中执行向下转型操作时,如果父类对象不是子类对象的实例,就会发生Class CastException异常,所以在执行向下转型之前需要养成一个良好的习惯,就是判断父类对象是否为子类对象的实例。这个判断通常使用instanceof操作符来完成。可以使用instanceof操作符判断是否一个类实现了某个接口,也可以用它来判断一个实例对象是否属于一个类。
使用方法如下:

myobject instanceof ExampleClass;

myobject:某类的对象引用。
ExampleClass:某个类。
instanceof操作符的表达式返回值为布尔值。如果返回值为true,说明myobject对象为ExampleClass的实例对象。如果返回值为false,说明myobject对象不是ExampleClass的实例对象。
在这里插入图片描述
在本例中将instanceof操作符与向下转型操作结合使用,在程序中定义了两个子类,分别继承四边形类。在主方法 中首先创建四边形类对象,然后使用instanceof操作符判断四边形类对象是否为平行四边形类的一个实例,是否为正方形类的一个实例,如判断结果为true,将进行向下转型。

抽象类和接口

通常可以说四边形具有四条边,或者更具体一点,平行四边形是具有对边平行且相等特性的特殊四边形,等腰三角形是其中两条边相等的三角形,这些描述都是合乎情理的,但对于图形对象却不能使用具体的语言进行描述,它有几条边,究竟是什么图形,没有人能说清楚,这种类在Java中被定义为抽象类。

抽象类与抽象方法

在解决实际问题时,一般将父类定义为抽象类,需要使用这个父类进行继承与多态处理。回想继承和多态原理,越是在上方的类越抽象,在多态机制中,并不需要将父类初始化对象,我们需要的只是子类对象,所以在Java语言中设置抽象类不可以实例化对象,因为图形类不能抽象出任何一种具体图形,但他的子类却可以。
Java中定义抽象类时,需要使用abstract关键字,其语法如下:

[权限修饰符] abstract class 类名{
类体
}

使用abstract关键字定义的类称为抽象类,而使用abstract关键字定义的方法称为抽象方法,抽象方法的定义语法如下:

[权限修饰符] abstract 方法返回值类型 方法名(参数列表);

从上面的语法可以看出,抽象方法是直接以分号结尾的,他没有方法体,抽象方法本身没有任何意义,除非它被重写,而继承这个抽象方法的抽象类必须被继承,事实上,抽象类除了被继承以外没有任何意义。继承抽象类的所有子类都需要将抽象类中的抽象方法进行覆盖,这样在多态机制中,就可以将父类修改为抽象类,将父类的方法设置为抽象方法,然后每个子类都重写这个方法来处理。但是需要注意,构造方法不能定义为抽象方法。
在这里插入图片描述
首先定义一个抽象类Market。然后在里面定义两个变量name和goods。最后定义一个抽象方法shop();
在这里插入图片描述
定义一个TaobaoMarket类,继承Market抽象类,实现其中的shop()抽象方法。
在这里插入图片描述
定义一个WallMarket类,继承自Market抽象类,实现其中的shop抽象方法。
在这里插入图片描述
定义一个GoShopping类,该类中分别使用实现WallMarket子类和TaobaoMarket子类创建抽象类的对象,并分别给抽象类中的成员变量赋不同的值,使用shop方法并分别输出结果。
在这里插入图片描述
运行结果如上图。
这次的几个类我都是分别放在了不同的类中,之前的例子都是在一个类里面创建好几个类。这次我把他们分开了。
在这里插入图片描述
他们处于同包不同类中。使用public修饰,可以直接调用。
综上所述,使用抽象类和抽象方法时,需要遵循以下原则:
1,在抽象类中,可以包含抽象方法,也可以不包含抽象方法,但是包含了抽象方法的类必须被定义为抽象类。
2,抽象类不能直接实例化,即使抽象类中没有声明抽象方法,也不能被实例化。
3,抽象类被继承后,子类需要实现其中所有的抽象方法。
4,如果继承抽象类的子类也被声明为抽象类,则可以不用实现父类中所有的抽象方法。
使用抽象类时,可能会出现这样的问题:程序中会有太多冗余的代码,同时这样的父类局限性很大,例如上面的例子中,也许某个不需要shop()方法的子类也必须重写shiop()方法。如果将这个shop()方法从父类中拿出,放在别的类里,又会出现新的问题,就是某些类想要实现“买东西”的场景,竟然需要继承两个父类。Java中规定,类不能同时继承多个父类,面临着中问题时,接口的概念便出现了。

接口的声明及实现

接口是抽象类的延伸,可以将它看做是纯粹的抽象类,接口中的所有方法都没有方法体。接口使用interface关键字进行定义,其语法如下:

[修饰符] interface 接口名 [extends 父接口名列表]{
[public] [static] [final] 常量;
[public] [abstract] 方法;
}

修饰符:可选,用于指定接口的访问权限,可选值为public.如果省略则使用默认的访问权限。
接口名:必选参数,用于执行接口的名称,接口名称必须是合法的Java标识符,一般情况要求首字母大写。
extends:父接口名列表:可选参数,用于指定要定义的接口继承于哪个父接口。当使用extends关键字时,父接口名为必选参数。
方法:接口中的方法只有定义而没有被实现。
一个类实现一个接口可以使用implements关键字,

public class Parallelogram extends Quadrangle implement drawTest{
......
}

在接口中定义的任何变量都自动式static和final的,因此,在接口中定义变量时,必须进行初始化,而且,实现接口的子类不能对接口中的变量重新赋值。
下面将多态技术和接口相结合,来看一个例子。
在这里插入图片描述
在这里插入图片描述
在本例中,第一个类与第二个类分别实现了drawTest接口,所以需要覆盖接口中的方法。在调用draw()方法时,首先将第一个类与第二个类向上转型为drawTest接口形式。这里也许很多读者会有译文,接口是否可以向上转型?其实在Java中无论是将一个类向上转型为父类对象,还是向上转型为抽象父类对象,或者向上转型为该类实现接口,都是没有问题的。然后使用d[i]数组中的每一个对象调用draw(),由于向上转型,所以d[i]数组中的每一个对象分别代表第一个类对象与第二个类对象,最后结果分别调用第一个类与第二个类中覆盖的draw()方法。

多重继承

在Java中类不允许多重继承,但是用接口就可以实现多重继承,因为一个类可以同时实现多个接口,这样可以将所有需要实现的接口放置在implenments关键字后并使用逗号隔开,但这可能会在一个类中产生庞大的代码量,因为继承一个接口时需要实现接口中的所有的方法。

class 类名 implements 接口1,接口2,接口3,.....,接口n

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从上面的例子可以看到,我们创建了Father和Mother接口,并定义了两个方法。然后创建了一个Children类(我把children单词拼错了QAQ),分别实现了Father和Mother中的方法,然后通过子类创建了两个接口对象。最后输出结果。
注意,使用多重继承时,可能出现变量或方法名冲突的情况,解决该问题时,如果变量冲突,则需要明确指定变量的接口,通过“接口名.变量”实现,如果出现方法冲突,只要实现一个方法即可。如果是接口继承接口,要使用extends关键字,假设接口2继承了接口1,那么我们在子类继承接口2的时候必须把接口1中的方法也全部实现。

区分抽象类和接口

抽象类和接口都包含可以由子类继承实现的成员,但抽象类是对根源的抽象,而接口是对动作的抽象,抽象类的功能要远超过接口,那为什么还要使用接口呢?这主要是由于定义抽象类的代价高,因为每个类只能继承一个类,在这个类中农,必须继承或编写出其子类的所有共性,因此,虽然接口在功能上会弱化许多,但它只是针对一个动作的描述,而且可以在一个类中同时实现多个接口,这样会降低设计阶段的难度。抽象类和接口的区别主要由以下几点,
1,子类只能继承一个抽象类,但可以实现任意多个接口。
2,一个类要实现一个接口必须实现接口中的所有方法,而抽象类不必。
3,抽象类中的成员变量可以是各种类型,而接口中的成员变量只能是public static final的。
4,接口中只能定义抽象方法,而抽象类中可以定义非抽象方法。
5,抽象类中可以有静态方法和静态代码块等,但接口不可以。
6,接口不能被实例化,没有构造方法,但抽象类可以有构造方法。
在这里插入图片描述

访问控制修饰符

前面介绍了面向对象的几个基本特性,其中包括封装性,封装实际上有两方面的含义:把该隐藏的隐藏起来,把该暴露的暴露出来,这两个方面都需要通过Java提供的“访问控制修饰符”来实现,
Java中的访问控制修饰符主要包括public,protected,private和default(缺省)。这些控制符控制着类和类成员变量以及成员方法 的访问权限。
在这里插入图片描述
声明类时,如果不适用public修饰符设置类的权限,则这个类默认为default修饰,Java语言中,类的权限设定会约束类成员的上的权限设定,例如,定义一个普通类,采用默认权限,该类中定义一个public方法,那么,该方法加不加public修饰符,它的访问权限都是default。使用访问控制修饰符时,需要遵循以下原则。
1,大部分顶级类都是用public修饰。
2,如果某个类主要用作其他类的父类,该类中包含的大部分方法只是希望被其子类重写,而不想被外界直接调用,则应该使用protected修饰。
3,类中的绝大部分属性都应该使用private修饰,除非一些static或者类似全局变量的属性。才考虑使用public修饰。
4,当定义的方法只是用于辅助实现该类的其他方法(即工具方法),应该使用private修饰。
5,希望允许其他类自由调用的方法应该使用public修饰。

final关键字

定义为final的类不能被继承。如果希望一个类不允许被任何类继承,并且不允许其他人对这个类进行任何改动,可以将这个类设置为final形式。

final class 类名{}

如果将某个类设置为final形式,则类中的所有方法都被隐式地设置为final形式,但是final类中的成员变量可以被定义为final或非final形式。
在这里插入图片描述
在这里插入图片描述
将方法定义为final类型可以防止子类修改该类的定义与实现方式,同时定义final的方法的执行效率要高于非final方法。在权限修饰中曾提到过private修饰符,如果一个父类的某个方法被设置为private修饰符,子类将无法访问该方法,自然无法覆盖该方法,所以一个定义为private的方法隐式被指定为final类型,这样无需将一个定位为private的方法在定义为final类型。
在这里插入图片描述
在这里插入图片描述
从这个例子可以看出,final方法不能被覆盖,例如doit2方法不能再子类中被重写,但是在父类中定义了一个private final的doit方法,同时在子类中也定义了一个doit方法,从表面上看,子类中的doit方法覆盖了父类的doit方法,但是覆盖必须满足一个对象向上转型为它的基本类型并调用相同的方法这样一个条件。例如,在主方法中使用“Parents p = s;”语句执行向上转型操作,对象p只能调用正常覆盖的doit3方法,却不能调用doit方法,可见子类中的doit方法并不是正常覆盖,而是生成一个新方法。因为父类的doit方法用了private修饰符,子类无法访问到,换个说法,就是父类中的doit方法子类看不到。相当于没有这个方法,所以子类中定义一个新的doit方法时,相当于定义了新方法。虽然名称相同,但并不是同一个。

final变量

final关键字可以用于声明变量,一旦该变量被设定,就不可以在改变该变量的值,通常,由final定义的变量为常量。例如,在类中定义PI值。

final double PI = 3.14;

当程序中使用PI这个常量时,它的值就是3.14,如果在程序中再次对定义为final的常量赋值,编译器将不会接受。
final关键字定义的变量必须在声明时对其进行赋值操作。final除了可以修饰基本数据类型的常量,还可以修饰对象引用。由于数组也可以被看做一个对象来引用,所以final可以修饰数组。一旦一个对象引用被修饰为final后,他只能恒定指向一个对象,无法将其改变。一个既是static又是final的字段只占据一段不能改变的储存空间。为了深入了解final关键字。来个小例子。
在这里插入图片描述
在这里插入图片描述
根据上面的例子可以看出,被定义为final 的常量定义时需要使用大写字母命名,并且中间使用下划线进行连接,这是Java中的编码规则。同时,定义为final的数据无论是常量,对象引用,还是数组,在主函数中都不可以被改变。
我们知道一个被定义为final的对象引用只能指向位移一个对象,不可以将它再指向其他对象。但是一个对象本身的值确实可以改变的,那么为了使一个常量真正做到不可更改,可以将常量声明为staticfinal。
在这里插入图片描述
在这里插入图片描述
从运行结果中可以看出,定义为final的常量不是恒定不变的,将随机数赋予定义为final的常量,可以做到每次运行程序是改变啊
的值。但是a2与a1不同,由于它被声明为static final 形式,所以在内存中为a2开辟了一个恒定不变的区域,每当再次实例化一个FinalStaticData对象时,仍然指向a2这块内存区域,所以a2的值保持不变。a2是在装载时被初始化,并不是每次创建新对象时都被初始化,而a1会在重新实例化对象时被更改。在Java中定义全局常量,通常使用public static final修饰,这样的常量只能在定义时被赋值。
可以将方法的参数定义为final类型,这预示着无法在方法中更改参数引用所指向的对象。

内部类

前面说过在一个文件中定义两个类,但其中任何一个类都不在另一个类的内部,而如果在类中再定义一个类,则将在类中再定义的哪个类成为内部类,这里可以想象一下汽车和发动机的关机,很显然,此处不能单独用属性或者方法表示一个发动机,发动机是一个类,而发动机又在汽车之中,汽车也是一个类,正如同内部类在外部类之中,这里的发动机类就好比是一个内部类,内部类可分为成员内部类,局部内部类以及匿名类。
使用内部类可以节省编译后产生的字节码,“.class”文件的大小,而且在实现事件监听时,采用内部类很容易实现,但是,使用内部类的最大问题会使结构不清晰,所以在程序开发时,不需要刻意使用内部类

成员内部类

在一个类中使用内部类,可以在内部类中直接存取所在类的私有成员变量,成员内部类语法如下。

public class OuterClass{//外部类
private class InnerClass{//内部类

}
}

在内部类中可以随意使用外部类的成员方法以及成员变量,尽管这些类成员被修饰为private。
内部类的实例一定要绑定在外部类的实例上,如果外部类中初始化一个内部类对象,那么内部类对象就会绑定在外部类对象上。内部类初始化方式与其他类初始化方式相同,都是使用new关键字。
在这里插入图片描述
在本例中,外部类创建内部类实例与其他类创建对象引用时相同。内部类可以访问它的外部类成员,但内部类的成员只有在内部类的范围之内是可知的,不能被外部类使用。内部类对象与外部类对象关系非常紧密,内外可以交互使用彼此类中定义的变量。
如果在外部类和非静态方法之外实例化内部类对象,需要使用“外部类.内部类”的形式指定该对象的类型。
在例子中的主方法中如果不适用doit()方法返回内部类对象引用,可以直接使用内部类实例化内部类对象,但由于是在主方法中实例化内部类对象,必须在new操作符之前提供一个外部类的引用。
在这里插入图片描述
在这里可以看出,在实例化内部类对象时,不能再new操作符之前使用外部类名称实例化内部类对象,而应该使用外部类的对象来创建其内部类的对象。

内部类向上转型为接口

如果将一个权限修饰符private的内部类向上转型为其父类对象,或者直接向上转型为一个接口,在程序中就可以完全隐藏内部类的具体实现过程。可以再外部提供一个接口,在接口中声明一个方法,如果在实现该接口的内部类中实现该接口的方法,就可以定义多个内部类以不同的方式实现接口中的同一方法,而在一般的类中是不能多次实现接口中的同一方法的,这种技巧经常被用在Swing编程中,可以在一个类中做出多个不同的响应事件。
在这里插入图片描述
在这里插入图片描述
从上面的例子中可以看出,OuterClass2类中定义了一个修饰权限为private的内部类,这个内部类实现了OutInterface接口,然后修改doit方法。由于该方法返回一个外部接口类型,这个接口可以作为外部使用的接口。它包含一个f()方法,在继承此接口的内部类中实现了该方法,如果某个类继承了外部类,由于内部的权限不可以向下转型为内部类InnerClass,同时也不能访问f()方法,但是却可以访问接口中的f()方法。例如,InterfaceInner类中最后一条语句,接口引用调用f()方法,从执行结果可以看出,这条语句执行的是内部类中的f()方法,很好的对继承该类的子类隐藏了实现细节,仅为编写子类的人留下一个接口和一个外部类,同时也可以调用f()方法,但是f()方法的具体实现过程却被很好的隐藏了,这就是内部类最基本的用途。注意,非内部类不能被声明为private或protected访问类型。

使用this关键字获取内部类与外部类的引用

如果在外部类中定义的成员变量与内部类的成员变量名称相同,可以使用this关键字。
在这里插入图片描述
在类中,如果遇到内部类与外部类的成员变量重名的情况,可以使用this关键字进行处理,例如,在外部类中使用this.x语句可以调动内部类的成员变量x,而使用TheSameName.this.语句可以调用外部类的成员变量x,即使用外部类名称后跟一个点操作符和this关键字便可以获取外部类的一个引用。
在内存中,所有对象均被放置在堆中,方法以及方法中的形参或局部变量放置在栈中,在本例中,栈中的doit()方法指向内部类的对象,而内部类的对象与外部类的对象时相互依赖的,Outer.this对象指向外部类对象。
综上锁住,使用成员内部类时,应遵循以下原则:
1,可以有各种修饰符,可以用private,public,protected,static,final,abstract等修饰。
2,如果内部类有static限定,就是类级别的,否则为对象级别的。类级别可以通过外部类直接访问,对象级别需要先生成外部的对象后才能访问。
3,内外部类不能重名。
4,非静态内部类中不能生命任何static成员。
5,内部类可以互相调用。

局部内部类

内部类不仅可以再类中进行定义,也可以在类的局部位置定义,如在类的方法或任意的作用域中均可以定义内部类。
在这里插入图片描述
在本例中可以看出,内部类被定义在了doit()方法内部,但是有一点值得注意,内部类InnerClass2是doit方法的一部分,并非OuterClass3类中的一部分,所以在doit方法的外部不能访问该类内部,但是该内部类可以访问当前代码块的常量以及此外部类的所有成员。
有的小伙伴注意到,doit方法的参数被设置为final类型。如果需要在方法体中使用局部变量,该局部变量需要被设置为final类型,换句话说,在方法中定义的内部类只能访问方法中的final类型的局部变量,这是因为在方法中定义的局部变量相当于一个常量,它的生命周期超出方法运行的生命周期,由于该局部变量被设置为final,所以不能再内部类中改变该局部变量的值。

匿名内部类

我们把上面的例子修改一下,在doit方法中将return语句和内部类定义语句合并在一起。
在这里插入图片描述
在本例中可以看出,将doit方法修改的有点莫名其妙,但这种写法确实被Java编译器认可,在doit方法内部首先返回一个OutInterface2的引用,然后在return语句中插入一个定义内部类的代码,由于这个类没有名称,所以这里将该内部类称为匿名内部类。实质上,这种内部类的作用就是创建一个实现于OutInterface2接口的匿名类对象。
匿名内部类的所有实现代码都需要在大括号之间进行编写。

return new A(){
//内部类体
}

其中A指类名。
由于匿名内部类没有名称,所以匿名内部类使用默认构造方法来生成OutInterface2对象。在匿名内部类定义结束后,需要加分号标识,这个分号并不是代表定义内部类结束的标识,而是代表创建OutInterface2引用表达式的标识。
匿名内部类编译以后,会产生以“外部类名$序号”为名称的.class文件,需要以1~n排列,分别代表1到n个匿名内部类。
使用匿名内部类应该遵循以下原则:
1,匿名内部类没有构造方法,
2,匿名内部类不能定义静态成员。
3,匿名内部类不能用private,public,static,final等修饰。
4,只可以创建一个匿名类实例。

静态内部类

在内部类钱添加修饰符static,这个内部类就变为静态内部类了。一个静态内部类中可以声明静态成员,但是在非静态内部类中不可以声明静态成员,静态内部类有一个最大的特点,就是不能使用外部类的非静态成员,所以静态内部类在程序开发中比较少见。
可以这样任意,普通的内部类对象隐式地在外部保存了一个引用,指向创建它的外部类对象。但是如果内部类被定义为static,就会有更多的限制,静态内部类有以下两个特点。
1,如果创建静态内部类的对象,不需要创建其外部类的对象。
2,不能从静态内部类的对象中访问非静态外部类对象。
在这里插入图片描述
上面代码中,在内部类的doitInner()方法中调用成员变量x,由于Inner被修饰为static形式,而成员变量x却是非static类型的,所以在doitInner()方法中不能调用x变量。
进行程序测试时,如果在每一个Java文件中都设置一个主方法,将出现很多额外代码,而程序本身并不需要这些主方法,为了解决这个问题,可以将主方法写入静态内部类中。
在这里插入图片描述
如果编译这个例子,将生成一个名称为StaticInnerClass美元符号Inner的独立类和一个StaticInnerClass类。
只要使用java StaticInnerClass美元符号(只能用汉字表示,两个美元符号中间的字会变字体,看不到美元符号)Inner,就可以运行主方法中的内容,这样当完成测试,需要将所有.class文件打包时,只要删除StaticInnerClass$Inner独立类即可。

内部类的继承

内部类和替他普通类一样可以被继承,但是继承内部类比继承普通类复杂,需要设置专门的语法来完成。
在这里插入图片描述
在某个类继承外部类时,必须硬性给予这个类一个带参数的构造方法,并且该构造方法的参数必须是该内部类的外部类引用,就像例子中的ClassA a,同时在构造方法体中使用a.super()语句。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值