什么是 Java 内部类?它有什么作用?
Java 内部类是一种定义在另一个类中的类,它与外部类(即包含内部类的类)具有紧密的联系。内部类可以访问外部类的成员变量和方法,包括私有的成员。Java 提供了多种类型的内部类,包括:
- 成员内部类:定义在外部类的成员位置上,可以访问外部类的所有成员,包括私有成员。
- 局部内部类:定义在一个方法内部的类,作用域仅限于该方法。
- 匿名内部类:没有名字的内部类,通常用于实现接口或继承抽象类。
- 静态内部类:定义在外部类的成员位置上,使用 static 关键字。它不依赖于外部类的实例,因此不能访问外部类的非静态成员。
内部类的作用包括:
- 封装性:内部类可以隐藏在外部类中,只对需要它的代码可见。
- 访问控制:内部类可以访问外部类的私有成员,这使得内部类可以更好地封装和实现功能。
- 代码组织:内部类可以帮助组织和管理大型项目的代码结构。
- 多态性:匿名内部类常用于实现多态性,通过继承或实现接口来定义具体的实现。
- 实现特定功能:内部类可以用于实现与外部类相关的特定功能,例如事件监听器或回调机制。
使用内部类的一个典型例子是创建线程安全的单例模式,其中内部类可以访问外部类的私有构造函数来控制实例的创建。
下面是一个简单的成员内部类的示例:
java
public class OuterClass {
private int outerField = 9;
// 外部类的成员方法
public void outerMethod() {
System.out.println("Outer method is called");
}
// 成员内部类
class InnerClass {
public void display() {
// 直接访问外部类的成员
System.out.println("Outer field = " + outerField);
outerMethod();
}
}
}
// 使用外部类和内部类
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();
inner.display();
在这个示例中,InnerClass
是 OuterClass
的一个成员内部类,它可以访问 OuterClass
的私有字段 outerField
和公共方法 outerMethod
。
Java 中 String、StringBuffer 和 StringBuilder 的区别是什么?
Java 中的 String
、StringBuffer
和 StringBuilder
是三种不同的类,它们在处理字符串时有不同的特性和用途:
-
String:
String
类型是 Java 中的不可变对象(immutable)。这意味着一旦创建了一个String
对象,你就不能改变这个字符串中的任何字符。- 每次对
String
对象进行修改操作时,实际上都会创建一个新的String
对象。 String
对象通常用于不需要修改的文本数据。
-
StringBuffer:
StringBuffer
类似于String
,但它是可变的(mutable),这意味着你可以在原有对象上进行修改,如添加或删除字符。- 它是线程安全的,即多个线程可以同时访问一个
StringBuffer
对象,而不会影响程序的正确性。这通过在方法上使用synchronized
关键字来实现。 - 由于线程安全的特性,
StringBuffer
在多线程环境中更受欢迎,但可能会因为同步机制而有一些性能开销。
-
StringBuilder:
StringBuilder
与StringBuffer
类似,也是可变的,可以在原有对象上进行修改。- 与
StringBuffer
不同的是,StringBuilder
不是线程安全的。这意味着如果多个线程访问同一个StringBuilder
对象,程序的行为将是不确定的。 - 由于没有同步机制的开销,
StringBuilder
通常在单线程环境中提供更好的性能。
性能考虑:
- 对于单线程环境,如果你需要频繁修改字符串,使用
StringBuilder
通常是更好的选择,因为它没有线程安全的开销。 - 对于多线程环境,如果多个线程需要访问同一个字符串缓冲区,使用
StringBuffer
更合适,因为它提供了线程安全。
不变性:
String
的不变性意味着它的hashCode
值在创建后不会改变,这使得它非常适合作为键在HashMap
中使用。StringBuffer
和StringBuilder
的内容可以改变,所以它们不适合作为HashMap
的键。
示例:
java
String str = "Hello";
str += " World"; // 实际上创建了一个新的String对象
StringBuffer sb = new StringBuffer("Hello");
sb.append(" World"); // 在原有对象上修改
StringBuilder sbd = new StringBuilder("Hello");
sbd.append(" World"); // 在原有对象上修改
在 Java 11 中,String
类增加了一些新的不可变工厂方法,如 isBlank()
,这些方法提供了更多便利性,进一步减少了对 StringBuffer
和 StringBuilder
的需求。
Java 的反射机制?如何应用反射?
Java 的反射机制是一种在运行时(Runtime)检查或修改程序行为的能力。它允许程序在运行时动态地获取类的基本信息(如类名、方法、字段等),以及创建和操作对象。
反射的主要应用包括:
- 动态创建对象:可以在运行时创建任何类的实例,即使这个类的字节码事先未知。
- 访问私有字段和方法:可以访问类的私有(private)字段和方法。
- 动态调用方法:可以调用任何对象的方法,无论其访问权限是什么。
- 获取类的信息:可以获取类的名称、字段、方法等信息。
- 实现通用数组:可以动态地创建和操作数组。
- 实现泛型类的类型检测:可以在运行时检测泛型的类型。
如何使用反射:
-
获取 Class 对象 :要使用反射,首先需要获取想要操作的类的
Class
对象。这可以通过直接使用.class
来获取,或者通过实例的getClass()
方法。 -
创建对象实例 :使用
Class
对象的newInstance()
方法(Java 9 之前)或getDeclaredConstructor().newInstance()
方法(Java 9 及以后)来创建类的实例。 -
访问字段 :使用
getField()
或getDeclaredField()
方法来获取字段的Field
对象,然后使用set()
或get()
方法来访问字段的值。 -
调用方法 :使用
getMethod()
或getDeclaredMethod()
方法来获取方法的Method
对象,然后使用invoke()
方法来调用方法。 -
获取类的信息 :使用
Class
对象提供的方法,如getName()
、getFields()
、getMethods()
等,来获取类的信息。
使用反射的注意事项:
- 性能开销:反射通常比直接代码调用要慢,因为它涉及到类型检查和动态解析。
- 安全问题:反射可以破坏封装性,允许代码访问私有字段和方法,这可能会带来安全风险。
- 编译时检查:使用反射调用方法时,会丧失一些编译时的类型检查,可能导致运行时错误。
在实际应用中,反射通常用于框架和库的开发,例如 ORM(对象关系映射)、依赖注入、单元测试等场景。