首先在写完这篇博客时感谢几位作者的文章:
https://www.cnblogs.com/txbblog/p/10364558.html
https://blog.csdn.net/qq_40990854/article/details/81067371
今天看《Thinking in Java》时候看到这里:
public class Parcel7 {
public Contents contents() {
return new Contents() { // Insert a class definition
private int i = 11;
public int value() { return i; }
}; // Semicolon required in this case
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Contents c = p.contents();
}
} ///:~
然后自己敲了发现有报错:
自然作者大大在后面有一定程度的解释:创建一个继承自Contents的匿名内部类的对象
但是怀着疑问我又去查了一下匿名类。
经过查阅资料我们总结一下匿名类和匿名对象,之后可能你看作者的代码可能会理解。
一、匿名对象
匿名对象:通俗的来说就是——没有名字的对象!(这是最重要的概念)
特征:
1.语法上: 只创建对象,但是不用变量来接收
2. 匿名对象的使用:
(1).匿名对象也是一个对象,具有对象的所有功能
(2).每一次使用匿名对象时,都是一个新的对象, 每次创建匿名对象都是不同的对象,一个匿名对象,只能使用一次,即匿名对象只能调用一次
Eg:
package test;
public class TestAnonymousClass {
public void print() {
}
public static void main(String[] args) {
// TODO Auto-generated method stub
new TestAnonymousClass().print();
}
}
在这里的重点是:
new TestAnonymousClass(); //匿名对象的创建
new TestAnonymousClass().print(); //匿名对象调用方法
在这里我们没有像平常时候那样去new一个新对象,平时我们会如下做:
TestAnonymousClass() test = new TestAnonymousClass();
test.print();
我们可以看到如果我们想要创建一个匿名对象的话我们只需要去:new 类名(参数列表);
如果我们想要去调用自己创建的方法或者变量的话直接:new 类名(参数列表).方法名(参数列表);
3.那么我们想要知道匿名对象有什么好处那?
匿名对象的好处在以下场景应用会比较便利:
(1)、创建匿名对象直接调用方法,没有变量名
new Scanner(System.in);
(2)、一旦调用两次方法,就是创建了两个对象,造成空间浪费
new Scanner(System.in).nextInt();
new Scanner(System.in).nextInt();
PS:一个匿名对象,只能使用一次。
Eg:
package test;
public class TestAnonymousClass {
String name;
public static void main(String[] args) {
// TODO Auto-generated method stub
new TestAnonymousClass().name = "Tuo";
System.out.println("The result is: "+new TestAnonymousClass().name);
}
}
在上面的代码中我们加载了匿名对象并给这个类的“name”变量一个值,但是如果你去打印它就会发现:
这是因为:
匿名对象在创建完之后,直接被系统释放掉了。也就是说,在类中的全局变量是随着类的加载而加载,这样,在使用new TestAnonymousClass().name时,由于是匿名的,类就终结了,因此上一个初始化name属性的“tuo”就消失了。
如果不想让初始化的属性值消失,那么您可以在创建类时,将想保留的属性定义为静态的!
就像这样:
package test;
public class TestAnonymousClass {
static String name;
public static void main(String[] args) {
// TODO Auto-generated method stub
new TestAnonymousClass().name = "Tuo";
System.out.println("The result is: "+new TestAnonymousClass().name);
}
}
(3)、匿名对象可以作为方法的参数和返回值
1)作为参数
public class Test {
public static void input(Scanner sc){
System.out.println(sc);
}
public static void main(String[] args) {
// 普通方式
Scanner sc = new Scanner(System.in);
input(sc);
//匿名对象作为方法接收的参数
input(new Scanner(System.in));
}
}
2)作为返回值(这个大家应该比较熟悉,在《Thinking in Java》中也比较常见)
public class Test2 {
public static void main(String[] args) {
// 普通方式
Scanner sc = getScanner();
}
public static Scanner getScanner(){
//普通方式
//Scanner sc = new Scanner(System.in);
//return sc;
//匿名对象作为方法返回值
return new Scanner(System.in);
}
}
二、匿名类(匿名内部类)
如果要执行的任务需要一个对象,但却不值得创建全新的对象(原因可能是所需的类过于简单,或者是由于它只在一个方法内部使用),匿名类就显得非常有用。
1、定义匿名内部类的语法格式如下:
new 父类构造器(实参列表) | 实现接口(){
//匿名内部类的类体部分
}
(1)从上面的定义可以看出,匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类,或实现一个接 口。(这个能够很好地解释我刚开始遇到的问题)
(2)两条规则:
1)匿名内部类不能是抽象类。
2)匿名内部类不能定义构造器。由于匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以初始化块, 可以通过初始化块来完成构造器需要完成的工作。
2、具体实现有以下三种方式:
(1)、最常用的创建匿名内部类的方式是创建某个接口类型的对象。
package test;
interface Purchase{
public double getPrice();
public String getName();
}
public class TestAnonymousClass {
public void test(Purchase p){
System.out.println("购买了" + p.getName()
+ ",花费了" + p.getPrice());
}
public static void main(String[] args) {
// TODO Auto-generated method stub
TestAnonymousClass test = new TestAnonymousClass();
/**
调用test()方法时,需要传入一个Product参数,
此处传入其匿名内部类的实例
*/
test.test(new Purchase(){
public double getPrice(){
return 567.8;
}
public String getName(){
return "显卡";
}
});
}
}
在这里我们需要注意的是注释的文字以及下面的代码即:
new Purchase(){
public double getPrice(){
return 567.8;
}
public String getName(){
return "显卡";
}
}
Ps:
定义匿名内部类无须class关键字,而是在定义匿名内部类时直接生成该匿名内部类的对象。
最后贴出我们打印的内容:
(2)、当通过接口来创建匿名内部类时,匿名内部类不能显示创建构造器,因此匿名内部类里只有一个隐式的无参构造器, 故new接口名后的括号里不能传入参数值。如果通过继承父类来创建匿名内部类时,匿名内部类将拥有和父类相似的 构造器,此处的相似指的是拥有相同的形参列表。
package test;
abstract class Device{
private String name;
public abstract double getPrice();
public Device(){}
public Device(String name){
this.name = name;
}
// 此处省略了name的setter和getter方法
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
public class TestAnonymousClass {
public void test(Device d){
System.out.println("购买了" + d.getName()
+ ",花费了" + d.getPrice());
}
public static void main(String[] args){
TestAnonymousClass test = new TestAnonymousClass();
// 调用有参数的构造器创建Device匿名实现类的对象
test.test(new Device("电子示波器"){
public double getPrice(){
return 67.8;
}
});
// 调用无参数的构造器创建Device匿名实现类的对象
Device d = new Device(){
// 初始化块
{
System.out.println("匿名内部类的初始化块...");
}
// 实现抽象方法
public double getPrice(){
return 56.2;
}
// 重写父类的实例方法
public String getName(){
return "键盘";
}
};
test.test(d);
}
}
这里主要注意的代码即:
test.test(new Device("电子示波器"){
public double getPrice(){
return 67.8;
}
});
// 调用无参数的构造器创建Device匿名实现类的对象
Device d = new Device(){
// 初始化块
{
System.out.println("匿名内部类的初始化块...");
}
// 实现抽象方法
// 重写父类的实例方法
public String getName(){
return "键盘";
}
};
Ps:由于匿名内部类不能是抽象类,所以匿名内部类必须实现它的抽象父类或接口里包含的所有抽象方法。如果有需要,也可以 重写父类中的普通方法
如果我们删除上面代码main方法中对 public abstract double getPrice() 的重写,就会报错:
我们贴出结果:
(3)、在Java 8之前,Java要求被局部内部类、匿名内部类访问的局部变量必须使用final修饰,从Java 8开始这个限制取消 了,Java 8更加智能:如果局部变量被匿名内部类访问,那么该局部变量相对于自动使用了final修饰。
package test;
interface A{
void test();
}
public class TestAnonymousClass {
public static void main(String[] args){
int age = 8; // ①
/**
* 下面代码将会导致编译错误
* 由于age局部变量被匿名内部类访问了,因此age相当于被final修饰了age = 2;
* */
A a = new A(){
public void test(){
/**
* 在Java 8以前下面语句将提示错误:age必须使用final修饰
* 从Java 8开始,匿名内部类、局部内部类允许访问非final的局部变量
*/
System.out.println(age);
}
};
a.test();
}
}
Ps:Java 8将这个功能称为“effectively final”,它的意思是对于被匿名内部类访问的局部变量,可以用final修饰,也可以不用 final修饰,但必须按照有final修饰的方式来使用——也就是一次赋值后,以后不能重新赋值。
结果: