<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流!
一、面向对象概念
面向对象是相对面向过程而言,面向对象和面向过程都是一种思想,面向过程强调的是功能行为,面向对象将功能封装进对象,强调具备了功能的对象,面向对象是基于面向过程的。
举例:要执行去饭店吃饭的过程,按照面向过程的思想考虑,需要构造一个函数,函数参数为某个饭店,函数过程是获得这些饭店里的各种菜谱、厨师、服务等等,返回一桌菜。这样设计,有一些问题,比方如果下一次再去另一家饭店吃饭,调用这个函数,将另一家饭店作为参数传递进去,又需要对另一家饭店重新编写过程,而这两家饭店内部流程有很多共性,比方都能提供厨师,选择菜谱,提供服务等等,而仅仅在具体流程的内部有所不同,这样就可以把这些共性提取成一个饭店对象,将所有共性功能封装,简化了代码编写。否则每到一家新的饭店,就要修改函数代码。另一方面,饭店之间的共性和个性关系可以采用面向对象的继承关系来表示,同样也简化了代码编写,提高了代码重用性。从函数调用者的角度来看,按照面向过程的思想需要不断的修改函数内部代码,而按照面向对象的思想,可以简单调用对象的功能,而忽略其中的具体实现细节,从执行者变成了指挥者,将复杂问题简单化。
面向对象三个特征:封装、继承和多态。开发其实就是找对象使用,如果没有合适的对象,就创建对象使用,并维护对象间的关系,保证各个开发者都能使用,提高代码重用性。
二、类与对象的关系
类和对象的关系:类就是对现实生活中事物的描述,对象就是这类事物实实在在存在的一个个体。比如:张三、李四是现实生活中的某一对象,他们都有姓名、性别、身高、体重等,提取这些对象的共性内容,对具体进行抽象,形成人类,在Java中,就用class来定义一个类,具体对象就是对应Java在堆内存中用new建立的实体。
下面定义了一个car类:
class car{
//描述颜色
String color="红色";
//描述轮胎数
int num=4;
//运行行为
void run(){
...
}
}
类的属性对应的就是类中定义的变量,类的行为对应类中定义的函数(方法),变量和函数(方法)称为类的成员变量和成员函数(方法)。创建一个car对象使用语句car c=new car();。
对象一旦被建立,在堆内存中就会分配一段空间用来存储该对象的内容,而在栈中会建立该对象的引用,对象被建立,所有成员变量都默认被初始化成默认值,比如String类成员变量默认初始化为null,可以在定义类时对成员变量进行显示初始化。
获取对象成员变量:对象名.成员变量名,调用对象成员方法:对象名.成员方法名()。
car c1=c;系统在栈中建立引用c1,c1和c指向堆内存中同一块地址,对c1的成员变量进行任何修改都会反映到c上。
三、成员变量和局部变量
成员变量作用于整个类中,局部变量作用于函数中或者语句中(比如for循环语句中定义的局部变量)。成员变量存储在堆内存中,对象被释放时,该堆内存才被释放。局部变量存在于栈内存中,当函数或语句执行完,局部变量栈内存即释放。
四、匿名对象
new car().run();创建一个匿名car对象,调用run()方法, new car().color="黑色"; 创建另一个匿名car对象,设置成员color值,new car();创建第三个匿名car对象,其color值为默认值“红色”,num值为默认值4。当对对象的方法只调用一次时,可以用匿名对象来完成,简化书写,如果对多个对象分别设置成员变量值,则不能使用匿名对象,必须指定对象名称,否则无法找到对应的成员变量。匿名对象还可以作为实际参数传递给函数,当然,函数中只是调用该匿名对象的方法,如果需要设置对象的成员变量且能再函数外部获取,必须指定对象名称。
五、封装
封装是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。好处:将变化隔离,便于使用,提高重用性,提高安全性。封装原则:将不需要对外提供的内容都隐藏起来,把属性都隐藏,提供公共方法对其访问。
下列代码构建了一个人类:
class Person{
int age;
void speak(){
System.out.peinrln("age="+age);
}
}
按照以上方式创建人类,可以执行下列语句:Person p=new Person(); p.age=-20;p对象年龄被赋值为-20,显然错误,原因在于Person类中的age成员没有定义成private,外界可直接访问修改。成员被定义成private后,类外不能被访问。
class Person{
private int age;
void speak(){
System.out.peinrln("age="+age);
}
}
age被定义成私有化后,外界无法访问,那么类就必须提供一个对外的方法,可以访问私有成员age。
class Person{
private int age;
//获得age
public int getAge(){
return age;
}
//设置age
public int setAge(int age){
this.age=age;
}
void speak(){
System.out.peinrln("age="+age);
}
}
这样,成员age就被封装在Person类中。私有仅仅是封装的一种表现形式 ,只要访问权限不在使用者访问权限范围内,那么对于使用者来说数据就是封装的。成员age私有化,通过对外提供公共方法对其进行访问,可以在访问方式中加入逻辑判断等语句,提高代码的健壮性。
六、构造函数
特点:函数名与类名相同,不用定义返回值类型,不可以写return语句。作用:给对象进行初始化。对象一建立就会调用与之对应的构造函数。当一个类中没有定义构造函数时,系统会默认给该类加入一个空参数的构造函数:类名(){ }。当类中定义了自定义的构造函数后,默认的构造函数就不建立。构造函数可以重载。
构造函数和一般函数在写法上不同,在运行上也不同。构造函数是在对象一建立就运行,给对象初始化。而一般方法是对象调用才执行,是给对象添加对象具备的功能。一个对象建立,构造函数只运行一次,而一般方法可以被该对象调用多次。
当分析事物时,该事物一存在就具备一些特性和行为,那么将这些内容定义在构造函数中。
七、构造代码块
构造代码块的作用是给对象进行初始化,而且优先于构造函数执行。构造代码块是给所有对象进行初始化,而构造函数是给对应的对象进行初始化。构造代码块中定义的是不同对象共性的初始化内容。
对象创建的过程:加载字节码—初始化静态成员变量和执行静态初始化代码块(仅执行一次)—初始化所有成员变量,全部赋原始初值(比如int型值为0,String型值为null)—按照类中定义的显示初始化值给成员变量赋值—执行初始化代码块—调用构造函数。
八、this关键字
当局部变量和成员变量重名时,需要使用this.变量名来引用成员变量。this代表所在函数所属对象的引用。当定义类中功能时,该函数内部要用到调用该函数的对象时,这是用this来表示这个对象。
构造函数内部调用另一个构造函数必须用this。不能在构造函数内部直接调用另一个构造函数,构造函数不同于一般函数,它只在初始化时被调用。在构造函数内部调用另一构造函数,必须放在函数内部第一行,因为构造函数必须先执行,同样,在一个构造函数内部不能同时调用另外两个构造函数,因为不能同时放在第一行。
九、static关键字
static是一个修饰符,用于修饰成员变量或成员函数,当成员被静态修饰后,出了可以被对象调用外,还可以直接被类名调用:类名.静态成员。成员变量被修饰成static后,就不再被存储在堆内存中,而是被存储在静态存储区中,静态存储区中存储了类中的方法,静态成员等。
static特点:(1)随着类的加载而加载,也就是说静态成员会随着类的消失而消失,说明它的生命周期相对于静态成员最长;(2)优先于对象而存在;(3)被所有对象共享;(4)可以直接被类名所调用。
实例变量(即非static变量)和类变量(即static变量)的区别:(1)存放位置不同,类变量随着类的加载而存在于静态存储区中,实例变量随着对象的建立而存在于堆内存中;(2)生命周期不同,类变量生命周期最长,随着类的消失而消失,而实例变量生命周期随着对象的消失而消失。
静态方法只能访问静态变量,非静态方法可以访问静态成员和非静态成员。静态方法中不可以使用this,super挂不能剪子,因为静态方法优先于对象存在。
使用static修饰符有利有弊,好处可以对对象的共享数据进行单独空间的存储,节省空间,没有必要为每一个对象都存储一份,可以直接被类名调用。弊端:生命周期过长,访问有局限性。
十、main函数
主函数是一个特殊的函数,作为程序的入口,可以被JVM调用。主函数的定义:
public 代表着该函数访问权限是最大的
static 代笔主函数随着类的加载就已经存在了
void 主函数没有具体的返回值
main 不是关键字,但是是一个特殊的单词,可以被JVM识别
String[ ] 主函数参数,参数类型是一个String型数组。
主函数定义的格式是固定的,能被JVM识别。JVM在调用主函数时,如果命令行不带有参数,传入的是new String[0]。可以在命令行中输入java 类名 参数1 参数2 ...向主函数传递参数。
十一、什么时候使用静态
当对象中出现共享数据时,该数据被静态所修饰,对象中的特有数据定义成非静态存在于堆内存中。
当功能内部没有访问到非静态数据(对象的特有数据),该功能可以定义成静态的。避免在使用该方法前需创建对象,节省内存。
十二、静态的应用—工具类
每一个应用程序都有共性的功能,比如对数组排序,取数组最大值等,可以将这些功能进行抽取,独立封装成工具类,以便于复用。虽然可以通过工具类对象使用工具方法,对数据进行操作,但也有问题:(1)对象是用于封装数据的,但工具类对象并未封装数据;(2)操作数组的每一个方法都没有用到工具类某个特有数据。于是,将工具类中的工具方法定义为静态的。当工具类方法都定义成静态的以后,可以方便使用,直接类名.方法(),但是该类还是可以被其他程序建立对象。为了更为严谨,强制让该类不能建立对象,将该类默认的空参数构造函数定义为private。工具类只把需对外提供功能的方法定义为public,其他用于内部操作的方法定义成private,避免被误调用。
十三、静态代码块
static {...}静态代码块的特点:随着类的加载而执行,只执行一次。
class staticcode{ static{ System.out.println("a"); } } public class staticdemo{ static{ System.out.println("b"); } public static void main(String[] args){ new staticcode(); new staticcode(); System.out.println("over"); } static{ System.out.println("c"); } }
程序运行结果如下:
当类被加载,即使没有创建对象,静态代码块也被执行,比如语句:工具类.工具方法();此时工具类的静态代码块会被执行,但仅仅是声明一个类并不会执行,比如语句:类A a=null;此时类A中的静态代码块不会被执行。
十四、单例设计模式
设计模式是解决某一类问题最行之有效的办法,Java中有23种设计模式。单例设计模式解决一个类在内存中只存在一个对象。想要保证对象唯一:(1)为了避免其它程序过多建立该类对象,先禁止其它程序建立该类对象;(2)同时为了让其它程序可以访问到该类对象,只好在本类中,自定义一个对象;(3)为了方便其它程序对自定义对象的访问,可以对外提供一些访问方式。
代码如下:
class single{ private static single s=new single(); private single(){ } public static single getInstance(){ return s; } }
上例在对象被使用前先初始化创建,称为饿汉式。下例是对象在被使用时才初始化创建,称为懒汉式,也称为对象的延迟加载。
代码如下:
class single1{ private static single s=null; private single(){ } public static single getInstance(){ if(s==null) return new single(); return s; } }
single类加载进内存时,single对象就已经存在于堆内存中,而single1类加载进内存时,single对象要等到被使用时才创建在堆内存中。
采用懒汉式要注意多线程的安全问题,需要对getInstance()方法加同步。
class single1{ private static single s=null; private single(){ } public static synchronized single getInstance(){ if(s==null) return new single(); return s; } }
加同步后,程序运行就比较低效,可加双重判断,减少判断锁的次数,但代码编写较为复杂。
class single1{ private static single s=null; private single(){ } public static single getInstance(){ if(s==null){ synchronized(single.class){ if(s==null) return new single(); } } return s; } }
实际编程时,多采用饿汉式。
十五、继承
类是对对象的抽取,父类是对子类的抽取,父类封装了子类共有的属性和方法,子类继承自父类,先有父类,再有子类。继承提高了代码的复用性,让类与类之间产生了关系,有了这个关系,才有了多态的特性。不能为了获取其他类的功能,简化代码而继承,必须是类与类之间有所属关系is a才可以继承。
在Java中只支持单继承,支持多层继承,不支持多继承,因为多继承容易带来安全隐患,当多个父类中定义了相同功能,但功能内容不同时,子类对象不确定要运行哪一个。Java用多实现来实现多继承。
如果子类和父类中出现了非private同名成员变量时,子类要访问本类中的同名成员变量用this,子类要访问本类中的成员变量用super。如果子类中并未建立父类中的属性或覆盖父类中的方法,那么this.属性或this.方法()等效于super.属性或super.方法(),this和super同指向父类。
class father{ int i=0; void f(){ System.out.println("father.f(),i="+i); } }
class son extends father{ void f(){ System.out.println("son.f().i="+this.i); } public static void main(String[] args){ new son().f(); } }
运行结果如下:
如果子类中的方法和父类中的方法签名相同,则子类方法将父类方法覆盖(重写),调用子类对象的同名方法,将执行子类方法。即使该子类对象被声明为父类对象,也会调用子类方法,这就是多态。比如father f=new son();son.f();程序输出"son.f().i=0"。
当子类继承父类,沿袭了父类的功能到子类中,但是子类虽具备该功能,功能的内容却和父类不一致,这时,没必要定义新功能,而是使用覆盖特性,保留父类的功能定义,并重写功能内容。子类覆盖父类方法,必须保证子类权限大于等于父类权限,才可以覆盖,否则编译失败,父类静态方法只能由子类静态方法覆盖。父类中的private方法不能被覆盖,如果子类定义了该private同名方法,其实是定义了一个新方法,与父类无关。
覆盖必须保证父类和子类方法签名相同,包括返回值。比如:
class father{
int f(){...}
}
class son extends father{
void f(...)
}
此时子类对象含有两个方法,int f()和void f(),编译不通过,因为如果调用f(),编译器无法确定是哪个f()。
十六、final关键字
final作为一个修饰符,可以修饰类、变量、函数。被final修饰的类不能被继承。
继承对封装性是一个挑战,有些子类覆盖父类方法,将可能出现不合适的代码。为了避免被继承,被子类覆盖功能,用final修饰类,或者用final修饰方法,该方法不允许被覆盖。
被final修饰的变量是常量,只能被赋值一次,final既能修饰成员变量,也能修饰局部变量。当在描述事物时,一些数据的出现值是固定的,为了增强程序阅读性,给这些值都起个名字,方便阅读,而这些值不需要改变,所以加上final修饰,作为常量,常量的书写规范要求所有字母都大写,如果由多个单词组成,单词间用_连接。往往常量成员变量还要设置成static。public static final修饰的变量称为全局变量。
内部类定义在类的局部位置上时,只能访问该局部被final修饰的局部变量。
十七、抽象类
当多个类中出现了相同功能,但是功能主体不同,这时可以进行向上抽取,只抽取功能定义,而不抽取功能主体,用abstract修饰,语法为abstract 返回值 函数名(参数列表);。抽象方法一定在抽象类中,抽象类不能用new创建对象,因为调用抽象方法没意义。抽象类中的方法要被使用,必须由子类覆盖其所有的抽象方法后,建立子类对象调用。如果子类只覆盖了部分抽象方法,该子类仍是一个抽象类。
抽象类可以强制其子类覆盖抽象方法。抽象类可以不定义抽象方法,这样做,仅仅是为了不让实例化该抽象类。
十八、模板方法模式
需求:获取一段程序运行的时间。原理:获取程序开始和结束的时间并相减即可。
abstract class gettime{ //该方法不能被覆盖,用final修饰 public final void gettime(){ long start=System.currentTimeMillis(); runcode();//调用子类的具体实现的方法 long end=System.currentTimeMillis(); System.out.println("耗时"+(end-start)+"毫秒"); } public abstract void runcode();//方法体不明确,交由子类具体实现 } class sub extends gettime{ public void runcode(){ for(int i=0;i<500;i++) System.out.println(i); } public static void main(String[] args){ new sub().gettime(); } }
上例展示了模板方法设计模式。在定义功能时,功能的一部分是确定的,但是有一部分是不确定的。而确定的部分在使用不确定的部分,这时,就将不确定的部分暴露出去由该类的子类去完成,提高了扩展性和复用性。模板中不确定的方式不一定是抽象的,有时有默认设置。
十九、接口
当抽象类中的方法都是抽象的。那么该类可以通过接口interface的形式来表示。接口中常定义常量和抽象方法,常量用public static final修饰,方法用public abstract修饰,且都由系统自动添加。接口中的成员都是public的。类实现接口用关键字implements。如果类中还有没实现接口中的某个方法,该类要被定义成抽象类。
一个类可以同时实现多个接口。若多个接口中有同名方法,子类实现一次即同时实现,解决了多继承的安全问题。先继承后实现,可以扩展类的功能。
类与类之间是继承关系,类与接口是实现关系,接口与接口之间是继承关系。
interface A{ } interface B extends A{ } interface C{ } interface D extends A,C{ }
上面几个写法都是正确的。
接口的特点:接口是对外暴露的规则,是程序的功能扩展,降低了类之间的耦合度,可以用来多实现,类与接口之间是实现关系,而且类可以继承一个类的同时实现多个接口,接口与接口之间可以有继承关系。继承表示的是is a的关系,实现表示的是is like的关系。基本功能定义在父类中,扩展功能定义在接口中。
二十、多态
多态:某种事物的多种体现形态。
1、多态的体现
abstract class Animal{ abstract void eat(); } class Cat extends Animal{ public void eat(){ System.out.println("吃鱼"); } public void catchMouse(){ System.out.println("抓老鼠"); } } class Dog extends Animal{ public void eat(){ System.out.println("吃骨头"); } public void kanjia(){ System.out.println("看家"); } } class duotaidemo{ public static void f(Animal a){ a.eat(); } public static void main(String[] args){ Animal a1=new Cat(), a2=new Dog(); f(a1);f(a2); } }
程序输出:
吃鱼
吃骨头
父类的引用指向了其子类对象,父类的引用也可以接收自己的子类对象,这就是多态的体现,它大大提高了程序的扩展性。
2、多态的前提:类与类之间应存在继承或实现关系,通常还应有覆盖。
3、多态的好处:多态的出现大大提高了程序的扩展性。
4、多态的弊端:提高了扩展性,但只能用父类的引用访问父类中的成员。
5、多态的应用
Animal a=new Cat();类型提升,将Cat对象向上转型为Animal对象,如果想要调用Cat类的特有方法时,可强制将父类引用转成子类类型,Cat c=(Cat)a;向下转型。不能将父类对象转成子类类型,只能将指向子类对象的父类引用提升或强转,多态自始至终都是子类对象在做着变化。
abstract class Animal{ abstract void eat(); } class Cat extends Animal{ public void eat(){ System.out.println("吃鱼"); } public void catchMouse(){ System.out.println("抓老鼠"); } } class Dog extends Animal{ public void eat(){ System.out.println("吃骨头"); } public void kanjia(){ System.out.println("看家"); } } class duotaidemo{ public static void f(Animal a){ if(a instanceof Cat) ((Cat)a).catchMouse(); if(a instanceof Dog) ((Dog)a).kanjia(); } public static void main(String[] args){ Animal a1=new Cat(), a2=new Dog(); f(a1);f(a2); } }
涉及到多态的转型时,要先加上判断语句instanceof,避免程序运行时跑出类型转换异常。
6、多态出现时代码中的特点(多态使用注意事项)
在多态中成员函数的特点:在编译时期,参阅引用型变量所属的类中是否有调用的方法,如果有,编译通过,如果没有编译失败。在运行时期,参阅对象所属的类中是否有调用的方法。
class father{
int num;
void f(){ }
static void h(){ }
}
class son extends father{
int num,n;
void f(){ }
void g(){ }
void h(){ }
}
father obj=new son();
obj.g();编译失败 obj.f();编译成功,实际运行子类f()。
简单总结就使:成员函数在多态调用时,编译看左边,运行看右边。
在多态中,成员变量的特点:无论编译和运行,都参考左边(引用型变量所属的类)
obj.num=1;编译成功,设置的是父类的num obj.n=1;编译失败,父类没有成员变量n
在多态中,静态成员函数的特点:无论编译和运行,都参考左边。
obj.h();调用父类的静态成员函数h()。
6、多态的示例
需求:电脑运行实例,电脑运行基于主板。
interface PCI{ public void open(); public void close(); } class MainRoard{ public void run(){ System.out.println("MainBoard run"); } public void usePCI(PCI p){//接口型引用指向自己的子类对象 if(p!=null) p.open(); p.close(); } } class NetCard implements PCI{ public void open(){ System.out.println("NetCard open"); } public void close(){ System.out.println("NetCard close"); } } class SoundCard implements PCI{ public void open(){ System.out.println("SoundCard open"); } public void close(){ System.out.println("SoundCard close"); } } class duotaidemo{ public static void main(String[] args){ MainRoard MB=new MainRoard(); mb.run(); mb.usePCI(null); mb.usePCI(new NetCard()); mb.usePCI(new SoundCard()); } }
需求:数据库的操作。(1)连接数据库,(2)操作数据,增删改查(CRUD),(3)关闭数据库连接。数据是用户信息。
interface UserInfoDao{ public void add(User user); public void delete(User user); } class UserInfoByJDBC implements UserInfoDao{ public void add(User user){ /*1、JDBC连接数据库; 2、使用sql添加语句添加数据; 3、关闭连接。 */ } public void delete(User user){ /*1、JDBC连接数据库; 2、使用sql添加语句删除数据; 3、关闭连接。 */ } } class UserInfoByHibernate implements UserInfoDao{ public void add(User user){ /*1、Hibernate连接数据库; 2、使用sql添加语句添加数据; 3、关闭连接。 */ } public void delete(User user){ /*1、Hibernate连接数据库; 2、使用sql添加语句删除数据; 3、关闭连接。 */ } } class DBOperate{ pulic static void main(String[] args){ UserInfoDao ui1=new UserInfoByJDBC(), ui2=new UserInfoByHibernate(); } }
二十一、Object类
Object类是类层次结构的根类,是Java中所有对象的直接或间接父类,该类定义了所有对象都具备的功能。Object类中提供了对对象是否相同的比较方法,如果自定义类中也有比较相同的功能,没必要重新定义,只要沿袭父类中的功能,建立自己特有比较内容即可,这就是覆盖。注意在覆盖equals(Object)方法时,先要对参数进行类型判断,避免运行时出现类型转换异常。
Object类的equals(Object)方法默认比较地址,toString()方法默认返回对象的类型和哈希地址值。
二十二、内部类
将一个类定义在另一个类的里面,对里面那个类就成为内部类。内部类的访问规则:(1)内部类可以直接访问外部类的成员,包括私有成员;(2)外部类要访问内部类,必须建立内部类对象。 在外部 创建内部类格式如下:Inner inner=new Outer ().new Inner(); 在内部类中访问某个外部类成员格式如下:Outer.this.成员变量。
之所以内部类可以直接访问外部类中的成员是因为内部类中持有了一个外部类的引用,该引用写法是外部类名.this。
当内部类在成员位置上,就可以被成员修饰符修饰。比如private修饰后,该内部类在外部类中就进行了封装。static修饰后,内部类就具备static的特性。当内部类被static修饰后,只能直接访问外部类中的static成员,出现了访问局限。
在外部其他类中,访问static内部类的非静态成员格式:new Outer.Inner().非静态成员; 在外部其他类中,访问static内部类的静态成员格式:Outer.Inner.静态成员。当内部类中定义了静态成员,该内部类必须是static的。当外部类中的静态方法访问内部类时,内部类也必须是static的。
内部类可以定义在局部,包括方法内部。
class Outer{ int x=3; void method(){ class Inner{ void f(){ System.out.println(Outer.this.x); } } new Inner().f(); } } class InnerDemo{ public static void main(String[] args){ new Outer().method(); } }
局部内部类不能定义静态变量,因为若内部类内部定义了静态变量,该内部类就必须是static ,而static只能修饰成员。
class Outer{ int x=3; void method(final int a){ final int y=1;//内部类若要访问所在的局部变量,该变量必须是final的 class Inner{ void f(){ System.out.println(y); System.out.println(a); } } new Inner().f(); } } class InnerDemo{ public static void main(String[] args){ new Outer().method(2); } }
内部类若要访问所在的局部变量,该变量必须是final的。原因在于,局部变量在局部代码中有效,局部代码执行完毕则变量被释放,如果此时内部类再引用该变量则出现错误,为了保证不会出现这种情况,将内部类要使用的外部传递过来的局部变量定义成final的。
二十三、匿名内部类
匿名内部类其实就是内部类的简写形式。定义匿名内部类的前提:内部类必须是继承一个类或者实现一个接口。
interface absdemo{ public void show(); } class Outer{ int x=-3; public void f(){ new absdemo(){ public void show(){ System.out.println(x>0?x:-x); } }.show(); } } class InnerDemo{ public static void main(String[] args){ new Outer().f(); } }
匿名内部类的格式:new 父类或者接口(){定义子类的内容},其实匿名内部类是一个匿名子类对象。匿名内部类中定义的方法最好不要超过3个,否则要调用方法就需要重复new 匿名内部类对象。