- 内部类可以把一些逻辑相关的类组织在一起,并控制内部类的可见性。与组合完全不同。内部类可以与外围类通信。
- 内部类的创建需要外部类的实例,并且在对象结构中含有指向外部对象的引用,可以使用 OuterClass.this 访问外部类对象的实例,借助这个引用内部类可以访问外部类的所有成员。
- 静态内部类的创建不依赖外部类。
- 静态内部类又叫嵌套类。
- 内部类和静态内部类的创建方法。
public class OuterClass {
//外部类的私有、静态成员。观察内部类是否可以访问?
private static InnerC2 oc2;
class InnerC1{
int cli ;
InnerC1 increment(){
cli++;
System.out.println(cli);
//可以方法链
return this;
}
void createic2(){
//访问外部类成员,可以带上this指针也可以不用,但是如果出现内外变量重名的情况需要带上。
OuterClass.this.oc2 = new InnerC2(); //使用外部类的引用创建另一内部类对象
OuterClass.this.oc2.setName("i like vim");
oc2.setName("i like IDEA");
System.out.println(oc2.getName()); // 内访外 任何成员完全没问题
}
}
class InnerC2{
String name;
void setName(String name){
this.name = name;
}
String getName(){
return this.name;
}
}
//静态内部类,观察他的静态特性体现在无需外部类实例即可创建的特性。以及正常的静态变量访问性。
static class InnerC3{
InnerC3(){
System.out.println("Static InnerC3 has Constructure!");
//不可访问,还是因为没有外部类的实例。
//记住,静态内部类和普通内部类相比是不需要外部类的实例的因此也就没有外部类this引用。因此下面语句是错误的。
//OuterClass.this.oc2 = new InnerC2();
oc2 = new OuterClass().new InnerC2();
}
void mess(){System.out.println("I am Static Inner class!");}
}
//在外部类的实例方法中,可以简单的创建内部类的对象
//若是static的方法则出错。因为非静态内部类的创建是依赖外部类的实例,必须先创建外部类才能创建内部类
InnerC1 getInnerC1Instance(){
return new InnerC1();
}
InnerC2 getInnerC2Instance(){
return new InnerC2();
}
public static void main(String[] args) {
OuterClass id = new OuterClass();
//普通内部类的创建。无论如何,都必须先创建外部类的实例,才能创建内部类。创建内部类的时候同时会有一个引用链接到外部类。
//对于内部类中的实例方法,可以使用 OuterClass.this 引用外部类的实例对象
InnerC1 ic1 = id.getInnerC1Instance();
InnerC2 ic2 = id.new InnerC2();
ic1.increment().increment().increment();
ic2.setName("使用实例的new语法创建的内部类对象");
System.out.println(ic2.getName());
//在创建内部类的同时创建外部类。
//否则提示错误:Must qualify the allocation with an enclosing instance of type OuterClass (e.g. x.new A() where x is an instance of OuterClass).
OuterClass.InnerC1 iic1 = new OuterClass().new InnerC1();
iic1.createic2();
OuterClass.InnerC2 iic2 = new OuterClass().new InnerC2();
iic2.setName("helloword");
System.out.println(iic2.getName());
//创建静态内部类。并不需要外部类的实例参考。因此只需一个new即可
//InnerC3 ic3 = id.InnerC3();
InnerC3 ic3 = new OuterClass.InnerC3();
OuterClass.InnerC3 ic33 = new OuterClass.InnerC3();
ic3.mess();
}
}
我们从上面可以知道以下几条信息:
内部类可以访问外部类的所有成员,无论是什么成员什么访问权。
内部类和静态内部类的创建语法,还有内部类依赖于外部类实例的要求。
内部类访问外部类实例通过隐式的 外部类.this
指针 来访问外部类成员,并且因为内部类可看作外部类的成员,因此可以访问外部类的所有成员,包括私有和静态成员。
静态内部类的静态:体现在实例化不依赖外部类实例上面。而不是其他静态概念中的‘唯一’,静态内部类拥有构造方法因此可以new实例化。可以把静态内部类看做和外部类一样的顶层类,只不过是带有命名空间限制(即需要外部类引用)来创建。而因为这个特点,所以要注意静态内部类没有 外部类.this 指针
, 就不能访问所属外部类的实例了,但是外部类(必须是顶层类或者其他可以不依赖对象存在的静态类)的静态成员变量和静态成员方法可以正常访问。注意观察 InnerC1.createic2()
中访问外部类的静态私有成员的语法。
-
另外,非静态内部类不能创建静态变量和静态方法,否则会提示错误
The method m cannot be declared static; static methods can only be declared in a static or top level type
。static final型变量即常量可以创建。因为非静态内部类对象是要依附在外部类对象上,生命周期捆绑在一起的。而静态成员的生命周期是与类在方法区载入卸载相关的。 -
非静态内部类也不能有静态类。
-
可以看到,内部类对象是和外部类对象捆绑在一起的,只要注意互访的语法就可以了。
-
内部类加上接口向上转型非常适合隐藏细节。
interface animal{
void eat();
void createAnimal();
}
interface human{
void earnMoney();
void sleep();
}
public class AnimalWorld {
class elephant implements animal{
int cli ;
elephant increment(){
cli++;
System.out.println(cli);
return this;
}
@Override
public void eat() {
// TODO Auto-generated method stub
}
@Override
public void createAnimal() {
// TODO Auto-generated method stub
}
}
class chinese implements human{
String name;
void setName(String name){
this.name = name;
}
String getName(){
return this.name;
}
@Override
public void earnMoney() {
// TODO Auto-generated method stub
}
@Override
public void sleep() {
// TODO Auto-generated method stub
}
}
//私有的修饰权限,只有本类才能创建实例。
private animal getElephant(){
return new elephant();
}
//继承和包内的修饰权限。
protected human getChinese(){
return new chinese();
}
public static void main(String[] args) {
AnimalWorld aw = new AnimalWorld();
animal a = aw.getElephant();
human h = aw.getChinese();
}
}
外部类的两个返回内部类实例的方法被权限修饰符修饰,禁止向下转型成animal或者chinese类型(只有外部类的子类可以转型),使得外部无法实现基于类型的编码。
- 以上可以看到内部类的两个作用: 向上转型 和 隐藏细节。
- 还可以实现方法内声明和语句块声明。
animal getDog(String dogname){
class Dog implements animal{
String name;
Dog(String dogname){
name = dogname;
}
@Override
public void eat() {
System.out.println(name + ": pizza is good taste!");
}
@Override
public void createAnimal() {
}
}
return new Dog(dogname);
}
public static void main(String[] args) {
AnimalWorld aw = new AnimalWorld();
aw.getDog("大狗").eat();
}
//Output:
//大狗: pizza is good taste!
- 语句块局部类使用
animal getDog(String dogname){
class Dog implements animal{
String name;
boolean ate ;
Dog(String dogname){
name = dogname;
//ate = false;
}
@Override
public void eat() {
ate = true;
System.out.println(name + ": pizza is good taste!");
}
@Override
public animal createAnimal() {
if(ate){
class LittleDog implements animal{
@Override
public void eat() {
// TODO Auto-generated method stub
}
@Override
public animal createAnimal() {
// TODO Auto-generated method stub
return null;
}
}
System.out.println("createing.........");
return new LittleDog();
}
System.out.println("i has not ate! dont want to create!");
return null;
}
}
return new Dog(dogname);
}
public static void main(String[] args) {
AnimalWorld aw = new AnimalWorld();
animal a = aw.getElephant();
human h = aw.getChinese();
//一个没吃,一个吃了
animal one = aw.getDog("大狗");
one.createAnimal();
animal two = aw.getDog("另一个狗");
two.eat();
two.createAnimal();
}
//Output:
//i has not ate! dont want to create!
//另一个狗: pizza is good taste!
//createing.........
- 匿名局部类使用:
human getAmerica(){
//这个类是完全匿名的,因此外部无法new他的实例。
return new human(){
@Override
public void earnMoney() {
System.out.println("earnMoney");
}
@Override
public void sleep() {
System.out.println("earnMoney");
}
//以下方法被隐藏。
public void privateMethod(){
System.out.println("you can not see me.");
}
}; //因为这是一个语句。所以必须有这个结束符号。
}
public static void main(String[] args) {
AnimalWorld aw = new AnimalWorld();
aw.getAmerica().earnMoney();
}
//Output:
//earnMoney
- 匿名内部类的构造,接受传参,构造块
1.若匿名内部类的父类(接受接口,抽象类,普通类。在这里是抽象类和普通类)需要一个带参的构造调用,则简单的传参即可。
2.若要向匿名内部类内部传递参数,则这个参数必须是final型的。而第一条中传给匿名内部类父类的有参构造方法的参数不用是final型的。
3.匿名内部类因为没有名字,所以外部不能new这个类,因此没有构造方法,但对变量的初始化可以放在实例构造语句块中进行。
interface human{
void earnMoney();
void sleep();
}
abstract class asiaHuman implements human{
String name;
int age;
asiaHuman(String n, int a){
name = n;
a = age;
}
}
class Main
{
int para = 2;
//匿名内部类传值实验
human getKorean(String name, int age, final int sleeptime){ //书上说外部给匿名内部类中使用的参数必须加final,但是我实验不加final也可以??
//直接给匿名内部类父类传值
return new asiaHuman("xiaoming", age){
int moneypertime = 200;
int sum;
//匿名内部类不能有构造方法
//asiaHuman(String a, int r){
//}
//只能使用构造块
{
sum = moneypertime * sleeptime + para;
}
@Override
public void earnMoney() {
System.out.println("when sleeping, i earned " + sum + " money");
}
@Override
public void sleep() {
System.out.println("let me sleep " + sleeptime);
}
};
}
public static void main(String[] args) {
AnimalWorld aw = new AnimalWorld();
aw.getKorean("",1, 111).earnMoney();
}
}
-
创建匿名内部类是创建内部类的语法糖。
-
局部类声明关键在于:
局部域声明。可以在作用域外使用。 即类只能在作用域的范围内声明。但是由于是在堆中创建,所以生命周期比声明范围大。
返回前向上转型为接口类型。 如果要隐藏类内实现,可以将局部类派生方法设置为私有接口防止向下转型。
无论是方法内局部类, 语句块局部类,还是匿名局部内部类。都会和其他类一样编译,命名为 外部类$内部类名, 匿名类命名为 外部类$1,2,3这样。 -
抽象工厂模式可以把对应产品的工厂放在产品内部使用内部类的形式并且实现抽象工厂接口实现。
//使用匿名内部工厂的写法
interface Service{
void method1();
void method2();
}
interface ServiceFactory{
Service getService();
}
//产品1
class ImplementService1 implements Service{
//将构造方法私有化,因为使用了内部类,因此内部类拥有初始化该产品的权限
private ImplementService1(){}
@Override
public void method1() {
}
@Override
public void method2() {
}
//工厂作为静态,且只有一个工厂即可。
public static ServiceFactory fact = new ServiceFactory(){
@Override
public Service getService() {
return new ImplementService1();
}
};
}
//产品2
class ImplementService2 implements Service{
private ImplementService2(){}
@Override
public void method1() {
}
@Override
public void method2() {
}
public static ServiceFactory fact = new ServiceFactory(){
@Override
public Service getService() {
return new ImplementService2();
}
};
}
public class FactoryTest {
static void ServiceConsumer(ServiceFactory sf){
sf.getService().method1();
sf.getService().method2();
}
public static void main(String[] args) {
ServiceConsumer(ImplementService1.fact);
ServiceConsumer(ImplementService2.fact);
}
}
- 内部类可以在接口中声明。
放在接口中的任何成员都会变成public static的,即内部类成为了静态类。也可以让内部类实现外部的接口。
内部类在接口中声明的特性表示让类定义成为接口共同约定的一部分,即创建了公共代码,可以让该接口的所有不同实现公用。
一个典型的例子就是将单元测试的main方法写在一个static内部类中,然后再放在要测试的类中。
interface i1{
int a =1;
class interclass implements i1{
void method(){
System.out.println(a);
}
}
}
- 一个内部类嵌套多少层不重要,他可以透明访问所有他嵌入的外围类的所有成员!
使用内部类最好的原因是:每个内部类都能独立继承一个(接口的)实现,所以无论外围类是否已经继承某个(接口的)实现,对于内部类都没有影响。
- 因为这个特性,所以一个外围类可以有多个实现不同接口的内部类。
- 内部类在独立于外围类继承接口的同时,与外围类也出于类似继承的关系,但是却比继承更加紧密即可以访问外围类的全部成员。
- 内部类使得多重继承的解决方案变得完整。即内部类允许继承多个非接口类型(类或者抽象类)。即如下的意思:
class D{}
abstract class E{}
class Z extends D{
E makeE() { return E(){ }; }
}
public class Main{
static void takesD(D d){}
static void takesE(E e){}
public static final main(String args[]){
Z z = new Z();
takeD(z);
takeE(z.makeE());
}
}
上面,创建了一个匿名内部类,同时继承了D和E,是非接口的继承。若要实现的是接口,还可以有下面的继承方式。
interface A{}
interface B{}
class Z implemnets A,B{}
Class Y implements A {
B makeB(){
return new B(){};
}
}
可以总结为:
1.若是多个都是接口,则简单的使用多接口实现就可以了。
2.若有接口,也有其他的,则选择实现接口,有的使用匿名内部类形式即可。
3.若都不是接口形式,可以使用逐层嵌套的匿名内部类实现多重继承。
- 闭包是一个可调用的对象,他包含了一些信息,这些信息来自创建他的作用域。对于内部类,他的闭包是外围类对象内部。因此,可以把一个闭包封装在内部类中实现回调效果,详细看 java编程思想212页。
- 若一个顶层类继承了一个内部类,则他的构造方法中一定是形式为:
class outer{
class inner{}
}
class a extends outer.inner{
a(outer o){
a.super();
}
}
- 若子类和父类都有一个相同名字的内部类,则他们各自会各用各的。即内部类不会发生多态。这两个内部类是完全独立的实体,在自己的命名空间。若两个内部类也是继承关系,则会有多态行为。
- 使用方法中的内部类而不使用匿名内部类唯一的理由是需要多个类对象。