面向对象概念
什么是对象(object)
Object(对象)相当于中文语义“东西”。Object是指一个具体事物实例,比如飞机、狗、运气、哲学等看得见的,看不见的,有形的、无形的、具体、抽象的都是对象,总之“一切皆对象”。
面向对象(Object Oriented)
面向对象,是指面向客观事物之间的关系。人类日常的思维方式是面向对象的,自然界事物之间的关系是对象与对象之间的关系。
面向对象的定义:
首先根据客户需求抽象出业务对象;
然后对需求进行合理分层,构建相对独立的业务模块;
之后设计业务逻辑,利用多态、继承、封装、抽象的编程思想,实现业务需求;
最后通过整合各模块,达到高内聚、低耦合的效果,从而满足客户需求。
类对象 引用
类
类是一个概念(名词)抽象的定义。类,是用来描述事物类型的,是抽象的。不占用内存。
类定义了该类型对象的数据结构,称之为“成员变量”,同时,也定义了一些可以被调用的功能,称之为“方法”。
类是用于构建对象的模板,在类中声明对象的属性和方法。
对象
对象就是一件东西, 是任何存在是事物都是对象,一切皆对象。
对象是类的具体实例。对象是具体的,占用存储空间。
对象的实质就是内存中一块存储区域,其数据结构由定义它的类来决定。
引用
即引用变量,业务逻辑上引用对象的代词,本质上存储的值是对象的首地址。通过首地址间接引用了对象。引用类型变量的值,不是直接的对象,是通过地址间接访问对象。引用本身不是对象。
方法
对象的行为(功能) 如:方块的移动(行为) 方法的行为(功能)的实现是利用算法操作对象数据实现
java的内存结构
Point p=new Point();
方法区
方法区存放类的信息。Java程序运行时,首先会通过类装载器载入类文件的字节码信息,经过解析后将其装入方法区。类的各种信息(基本信息和方法的定义)都在方法区保存。
栈
栈用于存放程序运行过程当中所有的局部变量。一个运行的java程序从开始到结束会有多次方法的调用。JVM会为每一个方法的调用在栈中分配一个对应的空间,这个空间成为方法的栈帧。
一个栈帧对应一个正在调用中的方法,栈帧中存储了该方法的参数、局部变量等数据。当某一个方法调用完成后,其对应的栈帧被清除。
堆
JVM在其内存空间中开辟一个成为“堆”的存储空间,这部分空间用于存储使用new关键字创建的对象。
继承
被继承的类型是父类型(Super Class)继承类型是子类型(Sub Class) 。如:T(Sub Class) 型方块继承了 Tetromino(Super Class)(4格方块) extends关键字可以实现类的继承。
1. 子类继承父类中所有可继承资源。属性、方法(私有除外)、所有子类共享了父类的方法.
2. 私有方法不继承,构造器不可继承
3. 一个子类的对象可以向上造型为父类的类型。--向上造型。
父类型定义的引用变量可以直接引用子类类型实例。java编译器会根据引用类型(不是对象的类型)检查调用方法是否匹配。
Tetromino t = new I(); Tetromino t = new T();
4. 子类型可以利用重写(覆盖)修改父类行的方法。如: paint() 方法, 修改了系统默认的绘制功能。重写的方法,调用的是子类型对象的方法(动态绑定)
5. 属性是根据变量类型进行访问的
6. 默认构造器现象
7. java语言不支持多重继承,一个类只能继承一个父类。但一个父类可以有多个子类。 java是单继承的。
class A{ int a=5; public void t(){ System.out.println(“A”); } } class B extends A{ int a=7; int b=9; public void t(){ System.out.println(“B”); } public void test(){ System.out.println(“BBB”); } } public static void main(String[] args) A a=new B(); a.a;// 5 a.b;//报错,A类中没有属性b a.t();// B a.test();//报错,A类中没有方法test() }
构造方法
构造器是用来创建对象的。构造方法是创建并且初始化属性的过程。
构造器方法 和 方法的差别
1. 都在类中定义,都可以重载(一个类有多个构造器)
2. 构造器方法。业务: 构造器描述对象的创建和初始化过程。语法: 名称与类名一致,不能定义返回值,使用new运算符调用
2. 方法。业务:是对象的行为(功能) 语法:名称与类型不同,一定定义返回值,使用对象引用调用
默认构造器现象
1. Java类一定有构造器
2. 如果类没有声明任何构造器,Java编译器自动添加默认构造器。
3. 如果类声明了构造器,Java编译器就不再添加任何默认构造器。
继承中的构造方法
1. 构造器是不可继承的
2. 子类一定调用父类构造器!
a、默认情况下, 子类构造器调用父类无参数构造器。如果子类的构造方法中没有调用父类的构造方法,Java编译器会自动的加入对父类无参数构造方法的调用。(如果该父类没有无参的构造方法,会有编译错误)
b、如果父类没有无参数构造器,子类必须使用super() 调用父类的构造器。编程建议: (Java Bean规范)所有类都提供无参数构造器!
3. 继承时候, java 对象的初始化过程
1) 递归加载类
2) 递归分配空间
3) 递归调用构造器
4) 返回对象的引用
new运算 :先加载类,再分配空间,再调用构造器。
this 和super关键字
this 关键字
1. this 在方法中代表运行时当前这个对象
2. this 是方法的第一个隐藏参数,接收方法调用者
3. 在方法中可以通过this关键字表示“调用该方法的那个对象”;
4. 在类的内部代表对象本身,你应该看到过this.xxx(),this.xxx这种用法吧,this就代表这个类的对象,比如
public class A { private String name; public void setName(String name) { //this就代表A的一个对象 //当你实例一个A时,A a1 = new A();this就是那个a1,只是this在内部用,而a1在外部用 //A a2 = new A()同理,这时在a2的内部,this就代表a2 this.name = name; } }
5. this的第二种用法,是用在构造函数里的。当在构造器里要调用自己的其他构造器时,就用this,this必须放在第一行
public class A { private String name; public A() { this("no name"); } public A(String name) { this.name = name; //other codes } }
super关键字
super代表父类
1. 显式的调用父类的方法。当从一个类继承时,子类和父类都有一个同名方法,也就是子类覆盖了父类的方法,可是又想调用父类的方法,那么就要用super,像继承swing的类时就有个好例子。
public class MyPanel extends JPanel { @Override public void paint(Graphics g) { super.paint(g);//如果不用super,那么就会调用自己的paint方法 } }
2. 用在构造器,和this的用法一样,super也可以用在构造器,this是调用自己的其他构造器,那么super当然就是调用父类的构造器了。super和this用在构造器的话,前者表示调用父类的构造器,后者表示调用本类的其他构造器,他们两个都必须是写在构造器里的第一行。
public class Person { private String name; private int age; public Person() { name = ""; age = 0; } public Person(String n, int a) { name = n; age = a; } } public class Student extends Person { private String id;//学号 public Student(String name, int age) { super(name, age); //必须写在第一行,子类无法直接访问父类的私有属性,所以通过调用父类的构造器类初始化属性 } public Student(String id, String name, int age) { this(name, age); //因为本类已经有个构造器初始化name和age了,所以交给他来做就行了,也必须写在第一行 this.id = id; } }
instanceof 运算符
是检查引用的对象的类型兼容性。
t instance of I : 检查 t引用的对象是否是"I的实例" ,如果是I类型的就返回true, 否则返回false。
instanceof 最常见的使用方式, 保护(安全的)类型转换。
if(t instanceof I){ I i = (I)t;// 使用instanceof 保护的类型安全的转换 }
重载和重写
方法名可以与类名一样
重写
是子类型定义与类型"一样的方法",目的子类修改父类的行为(功能)!最重要的目的是 "修改"! 修改以后,在运行期间执行子类对象的方法。父类型的方法被替换掉了(修改了)。
语法
1. 子类方法名与父类一样, 参数相同
2. 修饰词可以放大, 异常可以具体化(小类型)
3. 父类的私有方法, 不能继承, 就不能被重写!
4. 运行期间动态调用对象类型的方法
重载
重载是类中(含父子类型) 方法名一样,功能类似,参数不同的方法
语法
1. 方法名一样, 参数列表不同的方法
2. 目的是使API的设计更加优雅
3. 根据方法名和参数的类型调用对应的方法
重载的方法:
println(int); println(65);//65 println(char); println((char)65);//'A' println(double);
重载
1、方法重载是让类以统一的方式处理不同类型数据的一种手段。多个同名函数同时存在,具有不同的参数个数/类型。重载是一个类中多态性的一种表现。
2、Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义。调用方法时通过传递给它们的不同参数个数和参数类型来决定具体使用哪个方法, 这就是多态性。
3 、重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准。
重写
1、父类与子类之间的多态性,对父类的函数进行重新定义。
如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写。在Java中子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。
方法重写又称方法覆盖。
2、子类方法重写父类方法遵循“两同两小一大”的规则
两同:1)方法名 2)形参列表
两小:1)返回值类型比父类更小或相等 2)异常比父类方法更小或相等
一大: 子类权限比父类大或相等
两者的返回值类型首先必须是一个类层次,再谈大小。返回值类型为基本类型时,父类子类的方法,返回值类型一样。
注意
1. 调用重载的方法,根据参数类型识别
2. 重写的方法是动态绑定到对象类型的方法。动态绑定:java对象是在运行期间动态绑定到对象。 在编译期间没办法确认绑定的对象是什么。 重写的方法是根据对象类型调用对应的方法。
3. 私有方法不能被子类重写!
public class Super { public void test(int a) { System.out.println("Super test(int)"); } // 不能被子类重写 private void test2(int a) { System.out.println("Super test2(int)"); } }
public class Sub extends Super { public void test(int a) { System.out.println("Sub test(int)"); } public void test2(int a) { System.out.println("Sub test2(int)"); } }
public class Goo { public void test(Super obj) { System.out.println("test(super)"); obj.test(5); } public void test(Sub obj) { System.out.println("test(sub)"); obj.test(5); } }
public static void main(String[] args) { Super obj = new Sub(); obj.test(5);// obj 引用的对象是Sub类型,执行Sub的方法 obj = new Super(); obj.test(5);// obj 引用的对象是Super类型,执行Super的方法 Goo goo = new Goo(); Super obj = new Sub(); goo.test(obj);// 调用重载方法,根据参数类型识别test(Super) Super obj = new Sub(); obj.test2(5);// 编译错误,Super test2(int)。 }
访问控制修饰符
访问控制(访问修饰): 控制属性、方法、类的可见范围
public: 修饰类,属性,方法, 构造器,内部类等。在任何地方都可见
protected:只能修饰类的成员,在类体中使用,在当前package中可见(很少使用package作为访问范围),保护的成员主要留给子类使用的资源
默认的:当前package中可见,很少使用
private: 只能修饰类的成员,在类体中使用,是彻底封装,仅在类内部可见。
使用原则:尽可能私有。留给子类使用保护。减少使用公用,默认的几乎不使用。
常见方式:私有属性,公用访问方法(get set 方法)。
修饰符 | 本类 | 同一个包中的类 | 子类 | 其他类 |
public | 可以访问 | 可以访问 | 可以访问 | 可以访问 |
protected | 可以访问 | 可以访问 | 可以访问 | No |
default | 可以访问 | 可以访问 | No | No |
private | 可以访问 | No | No | No |
public class Koo { public int a = 5;// 公有的 private int b = 6;// 私有的 int c = 8;// 默认访问修饰,在当前包(package)中有效 protected int d = 10;// 保护的属性: 当前包,和子类中 } class Xoo extends Koo { // Koo 类声明在 test.sub包中,Xoo声明在test包中 public void test() { // super 是对父类型对象的引用, 可以访问父类型属性/方法 // 一般情况下可以省略, 也可以不省略 System.out.println(super.a);// 公有的随处可见 // System.out.println(b);//私有,子类不可见 // System.out.println(c);//默认的, 不同包 不可见 System.out.println(d);// 保护的, 子类中可见 }
} }
Java 源文规格
package {0,1}
import {0,n}
public class {0, 1} 只能有一个公有类!文件名与公有类名一致
class {0, n} 一个文件中的多个类都是默认的。
如果 没有公有类,文件名与任意类名一致。只有公有类才能在其他包中访问,默认类只能在当前包中访问!
一般情况下:一个文件一个公有类!
Javadoc
Javadoc是Sun公司提供的一个技术,它从程序源代码中抽取类、方法、成员等注释形成一个和源代码配套的API帮助文档。也就是说,只要在编写程序时以一套特定的标签作注释,在程序编写完成后,通过javadoc命令可以生成帮助文档。
static关键字
static 静态的,可以修饰类的成员,在类体中使用。可以修饰 属性,方法,代码块。静态的资源都是属于类的,
static修饰成员变量
用static修饰的成员变量不属于对象的数据结构。static变量是属于类的变量,通常可以通过类名来引用。static成员变量和类的信息一起存储在方法区,而不是在堆中。
一个类的static成员只有”一份“,无论该类创建了多少对象,是大家公有的
静态属性:是属于类的属性,在类加载期间初始化,并且只有一份。如:图片素材只加载一份就够了。
// 静态变量只有一份,一般使用类名访问 public class Demo01 { public static void main(String[] args) { Cat tom = new Cat(5); Cat kitty = new Cat(4); System.out.println(tom.age + "," + kitty.age + "," + Cat.numOfCats); } } class Cat { int age; static int numOfCats;// numOfCats是类的而不是对象的。大家公有 public Cat(int age) { this.age = age; Cat.numOfCats++;// Cat.可以省略 } }
static修饰方法
静态方法:是属于类方法,使用类名调用,经常作为工具方法、工厂方法,静态方法没有隐含参数this,与当前对象无关,不能访问当前对象(this)的方法、属性。
静态方法不能访问非静态属性,调用非静态方法。
如果方法与当前对象(this)无关就可以定义为静态方法。与当前对象的有关的方法,使用非静态方法。
static修饰的方法则不需要针对某些对象进行操作,其运行结果仅仅与输入的参数有关。调用时直接用类名调用。
静态方法在调用时,没有隐式的传递对象引用,因此在static方法中不可以使用this关键字。
由于static在调用时没有具体的对象,因此在static方法中不能对非静态成员(对象成员)进行访问。
static方法的作用在于提供一些“工具方法”和“工厂方法”等。
static修饰代码块
静态代码块:在类中声明,在类加载期间执行,只执行一次。用于加载 一次的静态资源。如:图片素材
public class Demo01 { public static void main(String[] args) { Foo f1 = new Foo(); Foo f2 = new Foo(); } } class Foo { // System.out.println("HI");//编译错误!类中不能有语句 // 没有static修饰的代码块,在该类对象被创建的时候执行。 { // 代码块,很少使用,代码块在创建对象时候执行,类似于构造器中的语句 System.out.println("HI"); } // 静态代码块,比较有用, 是类的代码块,在类加载期间执行,只执行一次,用于加载静态资源,如配置文件、图片素材等 static{ System.out.println("Foo class loaded"); }; }
final关键字
final修饰类
final关键字修饰的类不可被继承。使一个类不能被继承的意义在于:可以控制滥用继承对系统造成的危害。final类不能被继承,final类可以阻止子类进行重写修改。
Java API 中常见final类:String Math Integer 包装类。这些类是final的就是为了避免用户对API的修改!一般很少使用final类,final类阻止了继承,也阻止了动态代理技术使用(Struts Hibernate Spring )
final修饰方法
final关键字修饰的方法,不可以被重写。使一个方法不能被重写的意义在于:防止子类在定义新方法时造成的“不经意重写”。final 的方法,不能在子类中重写!阻止了子类重写,也就阻止了子类的修改。很少使用,影响动态代理技术(继承并重写所有方法)
final修饰成员变量
final关键字修饰成员变量,意为不可改变。该成员变量需在初始化时赋值,对象一旦创建即不可改变。final 的实例变量,初始化以后不能再改。final 变量用的多,“final变量”只能初始化不能再修改了。
final引用类型的引用值(地址值)不能再改变,也就是不能改变引用关系了。但是被引用的对象属性可以更改。
final int a = 5;//变量a的值不能再次修改 final int[] ary = {5,6}; //ary 是引用变量,值是地址值,通过地址间接引用了数组,变量ary的值不能再改了,ary指向的对象可以修改 ary = new int[3];//编译错误 ary[0]+=3;//正确
public class Demo01 { public static void main(String[] args) { final int a;// 声明/定义 局部变量 int b; a = 5;// 初始化 b = 5; // a = 8;// 编译错误,不能再修改! b = 8; test(6, 8); } public static void test(final int a, int b) { // a = 5;// 编译错 b = 6; System.out.println(a + "," + b); } }
常量
static final 常量。定义软件中,不变化常数:CELL_SIZE=25
命名:使用全大写的命名,多个单词下划线隔开
区别:常量 与 字面量
public static final double PI = 3.14159; PI 是常量 3.14159 字面量
对象数组
所谓对象数组 ,是对象的元素不是基本类型,而是引用类型。
对象数组的初始化和基本类型数组的初始化方式一样,只不过元素是对象的引用而已。需要注意的是:基本类型数组元素是有默认初始值的(例如,int类型数组的元素默认初始值为0);而对象数组创建后,其元素的默认值为null,不创建元素对象!
Cell[] cells = new Cell[4];//创建数组,但元素是null
二维数组:Java本质上没有二维数组,将元素是数组的一维数组作为二维数组。是数组的数组。
int[][] arr=new int[3][]; arr[0]=new int[2]; arr[1]=new int[3]; arr[2]=new int[2]; arr[1][1]=100;
抽象类
业务方面:表达了抽象的概念,与具体相反。抽象概念包含抽象行为,如:饮料一定可以打开,由具体饮料类型决定。
语法
1. 使用abstract关键字定义抽象类
2. 抽象类中可以定义抽象的方法
3. 抽象类可以定义变量,引用子类型对象
4. 抽象类不能通过new关键字直接创建对象
5. 抽象类只能被继承
6. 继承抽象类,必须实现全部的抽象方法
7. 抽象类可以继承抽象类,不需要实现抽象方法
8. 抽象类可以继承普通类
为什么要使用抽象类?
因为java是面向对象的编程,而现实中有很多对象,都是具体的,要一一写具体类来描述它们很麻烦 ,所以把它们抽象出来 做抽象类,具体的就是你自己去通过继承一个抽象类来描述具体类。
抽象类的意义在于“被继承”。抽象类为其子类“抽象”出了公共的部分,通常也定义了子类所必须具体实现的抽象方法。抽象类声明的变量可以接收子类对象。与具体实现分量,解耦合
案例:
//直接创建 Tetromino 对象是业务逻辑不合理的!Tetromino 在业务层面是抽象的概念,代表任何4格方块 //逻辑上应该使用 abstract 定义 Tetromino 类,使用 abstract 限制 直接创建 Tetromino 类实例,达到业务的合理性。 abstract class Tetromino{}
// TimerTask: 是抽象类, 里面有一个抽象方法, 这个方法就是被定时执行的方法。 public class MyTask extends TimerTask { @Override public void run() { System.out.println("HI"); } public static void main(String[] args) { Timer timer = new Timer();// 创建了一个定时器 // schedule: 计划, timer.schedule(new MyTask(), 1000, 1000);// 被执行的任务, 第一次执行延迟时间, 每次的间隔时间执行run方法 } }
接口
接口就是一种特殊抽象类, 全部方法都是抽象方法,全部属性都是常量。在业务逻辑上表示纯抽象概念.是理想的软件结构描述设计工具。
1. 可以定义变量,引用子类实例
2. 不能直接创建对象
3. 只能被实现(一种继承关系)
4. 接口之间可以继承,通过extends关键字继承另外一个接口。子接口继承了父接口中定义的所有方法。
5. 类可以实现多个接口, 实现多继承关系。
6. 接口中的属性,只能是常量!接口中声明的方法, 只能是抽象方法
7. 接口使用interface定义
使用接口的好处
1. 接口是规范,使得代码看起来规范点,风格统一点。
2. 只需要关注做什么(实现的功能)。而不用关心具体是怎么做的。方便更新以及后期维护。
3. 解耦合
定义一个接口,在你的程序中用实现该接口的任意类来实例化类,你都可以直接调用接口中的方法,这就是解耦合。
interface Runner { /* public static final */int DEFAULT_SPEED = 100; /* public abstract */int getSpeed();// 获取速度 void run();// 跑 } interface Hunter extends Runner { void hunt();// 打猎 } class Cat implements Hunter, Runner { public int getSpeed() { return DEFAULT_SPEED; } public void hunt() { System.out.println("拿耗子"); } public void run() { System.out.println("跑猫步"); } }
抽象类和接口
1. 抽象类有构造器,但不能直接创建对象,一般是创建子类对象时,调用抽象类构造器。接口没有构造器,接口不是类,用class关键字定义的是类。接口用interface定义。类一定有构造器是正确的。
2. 接口就跟U盘的USB接口一样,可以插拔。。
当你要完成一个功能的时候,两个事物联系不大,可以用接口。抽象类着重继承关系。如果两个东西可以看成继承关系,用抽象类。
例如:你要弄一个有警报器的门。你可以定义一个抽象door类,门有close和open的方法。也定义到这个抽象类里。但这个警报器,跟门一般关系不大。所以,你可以用接口。然后这个带警报器的门就可以是继承door这个类并实现警报器接口。
3. 接口就是标准,是用来隔离具体实现的(或者说是和具体实现解耦)。接口的作用就是提供一种规范,便于扩展,利于解耦。
内部类
内部类的共性
内部类分为: 成员内部类、静态内部类、方法内部类、匿名内部类。
① 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号。
② 内部类不能用普通的方式访问。内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否是private的。
③ 内部类声明成静态的,就不能随便的访问外部类的成员变量了,此时内部类只能访问外部类的静态成员变量。
④ 当一个类存在的价值仅仅是为某一个类服务时,应当使其成为那个类的内部类。
⑤ 内部类一般情况下对外不可见,除了包含它的外部类外,其他类无法访问到它。
成员内部类
class Outer { class Inner { } }
编译上述代码会产生两个文件:Outer.class和Outer$Inner.class。
创建内部类对象:
Outer outer = new Outer( ); Inner inner = outer.new Inner( );
静态内部类
静态内部类中可以定义静态或者非静态的成员。静态的含义是该内部类可以像其他静态成员一样,没有外部类对象时,也能够访问它。静态嵌套类仅能访问外部类的静态成员和方法。
class Outer { static class Inner { } }
创建内部类对象
Outer.Inner n = new Outer.Inner();
方法内部类
把类放在方法内
class Outer { public void doSomething() { class Inner { public void seeOuter() { } } } }
1. 方法内部类只能在定义该内部类的方法内实例化,不可以在此方法外对其实例化。
2. 方法内部类对象不能使用该内部类所在方法的非final局部变量。
因为方法的局部变量位于栈上,只存在于该方法的生命期内。当一个方法结束,其栈结构被删除,局部变量成为历史。但是该方法结束之后,在方法内创建的内部类对象可能仍然存在于堆中!
例如,如果对它的引用被传递到其他某些代码,并存储在一个成员变量内。正因为不能保证局部变量的存活期和方法内部类对象的一样长,所以内部类对象不能使用它们。
3. 在静态方法中定义的内部类是StaticNested Class,这时候不能在类前面加static,静态方法中的StaticNested Class与普通方法中的内部类的应用方式很相似,它除了可以直接访问外部类中的static的成员变量,还可以访问静态方法中的局部变量,但是该局部变量前必须加final修饰符。
public class Outer { public void doSomething() { final int a = 10; class Inner { public void seeOuter() { System.out.println(a); } } Inner in = new Inner(); in.seeOuter(); } public static void main(String[] args) { Outer out = new Outer(); out.doSomething(); } }
匿名内部类
匿名内部类就是没有名字的内部类。什么情况下需要使用匿名内部类?如果满足下面的一些条件,使用匿名内部类是比较合适的:
1. 类在定义后马上用到。
2. 类非常小(SUN推荐是在4行代码以下)
3. 给类命名并不会导致你的代码更容易被理解。
在使用匿名内部类时,要记住以下几个原则:
1)匿名内部类不能有构造方法。
2)匿名内部类不能定义任何静态成员、静态方法。
3)匿名内部类不能是public,protected,private,static。
4)只能创建匿名内部类的一个实例。
一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。
A、继承式的匿名内部类
public class Car { public void drive() { System.out.println("Driving a car!"); } public static void main(String[] args) { Car car = new Car() { public void drive() { System.out.println("Driving another car!"); } }; car.drive(); } }
结果输出了:Driving another car! Car引用变量不是引用Car对象,而是Car匿名子类的对象。
B、接口式的匿名内部类。
public interface Vehicle { public void drive(); } class Test { public static void main(String[] args) { Vehicle v = new Vehicle() { public void drive() { System.out.println("Driving a car!"); } }; v.drive(); } }
上面的代码很怪,好像是在实例化一个接口。事实并非如此,接口式的匿名内部类是实现了一个接口的匿名类。而且只能实现一个接口。
C、参数式的匿名内部类。
class Bar { void doStuff(Foo f) { f.foo(); } } interface Foo { void foo(); } class Test { static void go() { Bar b = new Bar(); b.doStuff(new Foo() { public void foo() { System.out.println("foofy"); } }); } }
匿名内部类的应用
匿名内部类应用在接口回调、事件监听等场合。
接口回调
回调模式:如果一个方法的参数是接口类型,则在调用该方法时,需要创建并传递一个实现此接口类型的对象;而该方法在运行时会调用到参数对象中.所实现的方法(接口中定义的)
interface Action { public void doSth(); } public class Test { // 该方法需要一个Action接口类型的参数,其逻辑为将此参数的doSth方法重复执行n此。 public static void repeat(int n, Action action) { for(int i = 0; i < n; i++){ action.doSth(); } } public static void main(String[] args) { // 通过匿名类传递参数,此处的语义可以解释为: // 通过接口回调传递了一个方法给repeat,让repeat将其执行5此。 repeat(5, new Action() {
@Override public void doSth() { System.out.println("HelloWorld!"); } }); } }
事件监听机制
事件监听可以看成是回调模式在Swing组件响应应用户事件中的应用。
事件源对象:通过“注册”的方式(.add..Listener),使事件源对象关联一个实现了监听器接口的对象,该对象实现了事件发生时的处理程序。当事件发生时(如用户单击了鼠标),会将该事件的信息封装成“事件对象”,然后调用监听器的相关方法,处理事件。
1. ActionListener
ActionListener一般用于监听表示要开始某种操作的事件,如:按钮上的单击,文本框中输入回车等。
public class Test { public static void main(String[] args) { JButton button = new JButton(); // actionPerformed方法用于实现处理Action事件的操作, 其参数ActionEvent用于封装Action事件的信息,它的getSource方法可以获取该事件的事件源对象。 button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JButton button = (JButton)e.getSource(); button.setText("Don’t click me"); } }); } }
MouseListener
MouseListener用于监听鼠标事件,其中包括鼠标进入事件源区域,移出事件源区域、事件源区域内的单击、按下、释放等事件的监听方法。
public interface MouseListener extends EventListener { public void mouseClicked(MouseEvent e); public void mousePressed(MouseEvent e); public void mouseReleased(MouseEvent e); public void mouseEntered(MouseEvent e); public void mouseExited(MouseEvent e); }
MouseEvent的getX()、getY()方法可以用于获取鼠标在事件源区域点击的位置。getButton()方法可以判断鼠标点击的是左键还是右键。getClickCount()方法可以用于判断是单击还是双击。
KeyListener
KeyListener用于监听键盘事件,其中包括键按下、键释放以及键入(必须确保有Unicode字符的产生,例如组合键将不会产生键入事件)的事件。
public interface KeyListener extends EventListener { public void keyTyped(KeyEvent e); public void keyPressed(KeyEvent e); public void keyReleased(KeyEvent e); }
KeyEvent的getKeyCode()方法用于获得所按下(释放)的键值,可以和KeyEvent所封装的形如VK_XX比对以确定所按下的键。
this.requestFocus(); this.addKeyListener( new KeyAdapter() { public void keyPressed(KeyEvent e) { int key = e.getKeyCode(); switch(key){ case KeyEvent.VK_RIGHT: ....; break; case KeyEvent.VK_LEFT: ....; break; case KeyEvent.VK_DOWN: ....; break; case KeyEvent.VK_UP: ....;break; case KeyEvent.VK_SPACE: ....;break; case KeyEvent.VK_P: ....;break; } } });
Listener和Adapter
有些场合,仅仅需要响应Listener的某些方法,但作为实现Listener接口的对象又不得不实现其所有的方法,尽管某些方法只提供空的实现。
JDK对某些Listener提供了相应的所谓Adapter,即实现了该Listener接口,而所有方法都为空实现的抽象类。在实践中,只需要继承该抽象类,重写需要的方法即可。
public abstract class KeyAdapter implements KeyListener { public void keyTyped(KeyEvent e) {} public void keyPressed(KeyEvent e) {} public void keyReleased(KeyEvent e) {} }
经常用匿名内部类的有
//监听机制 this.addKeyListener( new KeyAdapter() { public void keyPressed(KeyEvent e) { } }
//TimerTask timer.schedule(newTimerTask(){ public void run(){} }, 1000, 1000);
//Comparator Collections.sort(list, new Comparator<String>(){ public int compare(String o1, String o2) { return o1.length() - o2.length(); } });