java匿名内部类的使用 (比如new对象后的大括号, List<String> list = new ArrayList<String>() { { }}这用用法等)

本文深入探讨Java中匿名内部类的使用场景与实现原理,包括接口实现类、重写对象方法及初始化集合类实例的技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

PS: 觉得写得可以,喜欢就点个赞啦亲们

今天在看别人的代码的时候,发现有

Yyy uu=new Xxx(){
public void aaa(){
//这里写代码。。。
}
}

这种形式,以前偶尔看见过,也知道是匿名内部类的情况,但一直没有仔细去研究,今天特意花点时间去写了点很简单也易懂的例子,初学时需要的技术不在于复杂程度,能让人看得懂的代码才是好代码,希望能帮助大家:

一、例子1::匿名写一个接口实现类。看下我这个例子(直接copy后在自己的IDE练习即可):

a.新建一个接口:

public interface InterfaceTest {
	public String getName();
	public String getAge();
}

b.新建一个测试类:

package test;

public class NewInterfaceDemo {

	public static void main(String[] args) {

		InterfaceTest test=new InterfaceTest() {//直接就new InterfaceTest() 敲回车后下面的方法会自动出来的
			@Override
			public String getName() {
				return "小明";
			}
			@Override
			public String getAge() {
				return "5";
			}
		};
		System.out.println(test.getName()+test.getAge()+"岁啦");
	}
}

运行后打印出:
在这里插入图片描述

这是最简单的匿名内部类的例子,实际上看得懂就可以举一反三了,比如不是接口,而是抽象类的抽象方法,也是可以直接new 抽象类(){抽象方法}的。以上的例子相当于写了一个实现类,如:


public class InterfaceTestImpl implements InterfaceTest {

	@Override
	public String getName() {
		return "小明";
	}
	@Override
	public String getAge() {
		return "5";
	}
	
	public static void main(String[] args) {
		InterfaceTest test=new InterfaceTestImpl();
		System.out.println(test.getName()+test.getAge()+"岁啦");
	}
}

二、例子2:new对象后的大括号。

有时候我们看到的new一个Class,虽然这个class不是抽象类也不是接口,就是一个实实在在的类,然后后面也可以跟一个大括号,这又是怎么回事呢?我们可以直接拿非常常见的HashMap类来做这个例子:

public class Test {
	 public static void main(String[] args) {
	        Map<String, String> map2=new HashMap<String,String>(){
	        	@Override
	        	public String put(String var1, String var2) {
	        		var1="key_value";//a行
	        		var2="重写后的值";//b行
	        		return super.put(var1, var2);
	        	}
	        };
	        map2.put("start_key", "不知道start_key的值是不是这个");
	        System.out.println("找到start_key的值:      "+map2.get("start_key"));
	        System.out.println("虽然没有明显把key_value当key赋值,尝试尝试key_value的值:      "+map2.get("key_value"));
	}
}

这例子够简单吧,我们这里看下输出:
在这里插入图片描述

明明后面是写了map2.put(“start_key”, “不知道start_key的值是不是这个”);,为什么输出map2.get(“start_key”)的值是null呢?原因是代码中的a行b行重写了hashmap的put方法,把值给改过来了,实际上他们操作的是map2.put(“key_value”,“重写后的值”)! 所以从这里可以看出,在new 对象后,后面还跟着大括号,又直接写了个方法,这种情况下就是重写当前类的这个方法了,不过需要注意的是:该重写的方法只对当前对象有用!

三、List list = new ArrayList() { { }}这种写法:

不知道这种写法怎么说,就是new对象后,后面跟着两个大括号,比如:

 List<String> list = new ArrayList<String>() {
	            {
	                add("a");
	                add("b");
	            }
	        };
	        System.out.println("list长度:  "+list.size());

然后这代码输出的是list长度:2 事实上就可以知道,在new ArrayList的时候,创建构造函数时顺便给list对象添加了a b两个值了,所以list的长度为2
再弄个hashmap的:

  Map<String, String> map=new HashMap<String,String>(){
	        	{
	        		put("haha", "heiehi");
	        	}
	        };
	        System.out.println(map.get("haha"));

输出heihei

好了 这是怎么实现的呢?
从表面上看,其实内大括号使用的是this.add()方法,也就是说,这个方法也只是当前对象有效,其他你再new ArrayList()的时候,所得的对象长度就是0 而不是2了。
如果深入了解,我们可以把这个文件进行编译,源代码为:

public class BianyiClass {
	public static void main(String[] args) {
		 List<String> list = new ArrayList<String>() {
	            {
	                add("a");
	                add("b");
	            }
	        };
	}
}

提取出来放入D盘:
在这里插入图片描述

然后进行编译:

在这里插入图片描述

得到class文件:

在这里插入图片描述

可以看到产生了两个class文件,我们使用java自带的javap进行反编译查看BianyiClass.class文件:
在这里插入图片描述
太长了复制出来看一下:

D:\>javap  -verbose BianyiClass.class//这是直接在cmd窗口写的,下面是反编译出来的代码
Classfile /D:/BianyiClass.class
  Last modified 2018-11-16; size 344 bytes
  MD5 checksum a180fea3ce00b955e16a1fff9242e64a
  Compiled from "BianyiClass.java"
public class test.BianyiClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#15         // java/lang/Object."<init>":()V
   #2 = Class              #16            // test/BianyiClass$1
   #3 = Methodref          #2.#15         // test/BianyiClass$1."<init>":()V
   #4 = Class              #17            // test/BianyiClass
   #5 = Class              #18            // java/lang/Object
   #6 = Utf8               InnerClasses
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               BianyiClass.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Utf8               test/BianyiClass$1
  #17 = Utf8               test/BianyiClass
  #18 = Utf8               java/lang/Object
{
  public test.BianyiClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 6: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class test/BianyiClass$1
         3: dup
         4: invokespecial #3                  // Method test/BianyiClass$1."<init>":()V
         7: astore_1
         8: return
      LineNumberTable:
        line 9: 0
        line 15: 8
}
SourceFile: "BianyiClass.java"
InnerClasses:
     static #2; //class test/BianyiClass$1

有点眼花缭乱。。。。。。。。。。。。。。。。。。。缓一下神。。。。。。。。。。。

仔细看一看,发现其实查看时,自动有备注的,哈哈,继续往下,发现
class test/BianyiClass$1
这个备注,说明了这个编译文件,new了一个对象,在BianyiClass$1里面,然后我们继续javapBianyiClass$1文件吧:
命令为:javap  -verbose BianyiClass$1.class

把得到的信息粘贴出来:


D:\>javap  -verbose BianyiClass$1.class
Classfile /D:/BianyiClass$1.class
  Last modified 2018-11-16; size 468 bytes
  MD5 checksum edefcd30c6aa2ed4935340e149a2c92f
  Compiled from "BianyiClass.java"
final class test.BianyiClass$1 extends java.util.ArrayList<java.lang.String>
  minor version: 0
  major version: 52
  flags: ACC_FINAL, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#18         // java/util/ArrayList."<init>":()V
   #2 = String             #19            // a
   #3 = Methodref          #5.#20         // test/BianyiClass$1.add:(Ljava/lang/Object;)Z
   #4 = String             #21            // b
   #5 = Class              #22            // test/BianyiClass$1
   #6 = Class              #24            // java/util/ArrayList
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               Signature
  #12 = Utf8               Ljava/util/ArrayList<Ljava/lang/String;>;
  #13 = Utf8               SourceFile
  #14 = Utf8               BianyiClass.java
  #15 = Utf8               EnclosingMethod
  #16 = Class              #25            // test/BianyiClass
  #17 = NameAndType        #26:#27        // main:([Ljava/lang/String;)V
  #18 = NameAndType        #7:#8          // "<init>":()V
  #19 = Utf8               a
  #20 = NameAndType        #28:#29        // add:(Ljava/lang/Object;)Z
  #21 = Utf8               b
  #22 = Utf8               test/BianyiClass$1
  #23 = Utf8               InnerClasses
  #24 = Utf8               java/util/ArrayList
  #25 = Utf8               test/BianyiClass
  #26 = Utf8               main
  #27 = Utf8               ([Ljava/lang/String;)V
  #28 = Utf8               add
  #29 = Utf8               (Ljava/lang/Object;)Z
{
  test.BianyiClass$1();
    descriptor: ()V
    flags:
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/util/ArrayList."<init>":()V
         4: aload_0
         5: ldc           #2                  // String a
         7: invokevirtual #3                  // Method add:(Ljava/lang/Object;)Z
        10: pop
        11: aload_0
        12: ldc           #4                  // String b
        14: invokevirtual #3                  // Method add:(Ljava/lang/Object;)Z
        17: pop
        18: return
      LineNumberTable:
        line 9: 0
        line 11: 4
        line 12: 11
        line 13: 18
}
Signature: #12                          // Ljava/util/ArrayList<Ljava/lang/String;>;
SourceFile: "BianyiClass.java"
EnclosingMethod: #16.#17                // test.BianyiClass.main
InnerClasses:
     static #5; //class test/BianyiClass$1

又是眼花缭乱。。。
注意看这一句:final class test.BianyiClass$1 extends java.util.ArrayList<java.lang.String> 原来是new了一个ArrayList的子类,只不过这个子类是匿名子类。
这个大括号里面的内容,实际上是创建构造函数时的内容,抽取出来看一下:

{
  test.BianyiClass$1();
    descriptor: ()V
    flags:
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/util/ArrayList."<init>":()V
         4: aload_0
         5: ldc           #2                  // String a
         7: invokevirtual #3                  // Method add:(Ljava/lang/Object;)Z
        10: pop
        11: aload_0
        12: ldc           #4                  // String b
        14: invokevirtual #3                  // Method add:(Ljava/lang/Object;)Z
        17: pop
        18: return
      LineNumberTable:
        line 9: 0
        line 11: 4
        line 12: 11
        line 13: 18
}

然后再看这句:
11 7: invokevirtual #3 // Method add:(Ljava/lang/Object;)Z
还有这句:
15 14: invokevirtual #3 // Method add:(Ljava/lang/Object;)Z
明白了吧,实际上在初始化对象时,这个对象已经添加两次信息了,也就是那个a和b!
至此,就已经知道执行原理了!

PS: 觉得写得可以,喜欢就点个赞啦亲们

<think>嗯,用户想了解JavaList<String>初始化为new ArrayList<>()的作用和用途。首先,我需要回忆一下Java中的集合框架,特别是List接口和ArrayList类的关系。记得List是一个接口,不能直接实例化,所以通常会用它的实现类,比如ArrayList或LinkedList。用户提到的写法是List<String> list = new ArrayList<>();,这里用了多态,将接口类型的引用指向实现类的对象。 接下来,为什么这么做呢?可能的原因包括提高代码的灵活性。比如,如果以后需要更换实现类为LinkedList,只需要修改右边,而左边不用动。这符合面向接口编程的原则,降低耦合。不过要确保不调用特定实现类的方法,比如ArrayList特有的方法,否则在更换实现时会出错。 然后,初始化方式方面,用户可能想知道如何添加初始元素。常见的初始化方法包括逐个add,或者使用大括号初始化,但后者可能有内存泄漏的问题,因为会创建匿名内部类。还有Java 9引入的List.of()方法,可以快速创建不可变列表。此外,第三方库如Guava的Lists.newArrayList()也是一种方式。 另外,用户提供的引用中提到了不同的初始化方式,比如使用List接口声明变量,而用ArrayList或LinkedList实例化。需要解释这种做法的好处,比如提高代码的可维护性和扩展性。同时,也要注意ArrayList和LinkedList的区别,比如ArrayList基于数组,查询快,增删慢;LinkedList基于链表,增删快,查询慢。 可能还需要提到类型推断,即Java 7引入的钻石操作符<>,允许在实例化时不重复指定泛型类型,编译器会自动推断。这样写起来更简洁。 用户的问题中还涉及到ArrayList的扩容机制,可能需要简单说明一下ArrayList的动态扩容过程,比如默认初始容量是10,当元素超过容量时,会增长到原来的1.5倍,这会影响性能,因此在知道大概数据量时,可以指定初始容量以减少扩容次数。 最后,总结这种初始化方式的主要目的是实现多态,提高代码的灵活性和可维护性,同时遵循面向对象的设计原则。需要提醒用户注意,如果后续需要用到特定实现类的方法,可能需要直接声明为ArrayList类型,但一般情况下,使用接口类型更好。</think>### JavaListArrayList的初始化方式及作用分析 #### 1. **基本概念与初始化语法** Java中`List<String> list = new ArrayList<>()`是**面向接口编程**的典型实践。具体分析如下: - **`List`是接口**,定义了列表的通用方法(如`add()`, `get()`); - **`ArrayList`是实现类**,基于数组的动态列表; - **多态特性**:接口引用`List`指向实现类对象`ArrayList`,便于后续更换实现(如替换为`LinkedList`)[^2][^3]。 #### 2. **代码灵活性优势** ```java List<String> list = new ArrayList<>(); // 后续可灵活调整为其他List实现类 list = new LinkedList<>(); // 合法操作(前提是变量声明为List接口类型) ``` - **场景对比**:若声明为`ArrayList<String> list = new ArrayList<>()`,则无法直接替换为`LinkedList`[^3]。 #### 3. **初始化方式扩展** - **空列表初始化**(最常见): ```java List<String> list = new ArrayList<>(); // 初始为空列表 list.add("a"); list.add("b"); ``` - **双大括号初始化**(匿名内部类,需谨慎使用): ```java List<String> list = new ArrayList<>() {{ add("a"); add("b"); }}; // 可能引发内存泄漏问题[^1] ``` - **Java 9+的工厂方法**: ```java List<String> list = List.of("a", "b"); // 不可变列表 ``` - **第三方库辅助**(如Guava): ```java List<String> list = Lists.newArrayList("a", "b"); // 需引入Guava依赖[^4] ``` #### 4. **ArrayList的扩容机制** - **默认容量**:初始为10,当元素超出容量时自动扩容为1.5倍; - **性能优化**:预分配容量可减少扩容次数: ```java List<String> list = new ArrayList<>(100); // 指定初始容量 ``` #### 5. **典型应用场景** - **高频查询操作**:`ArrayList`基于数组实现,随机访问效率高($O(1)$时间复杂度); - **动态增删场景**:若需频繁插入/删除元素,可替换为`LinkedList`($O(1)$插入删除)。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值