🌻 嵌套类、内部类、局部类等是 Java 面向对象中的难点,但好好研究清楚感觉也不是特别难。博主再次学习这部分内容,以次巩固知识。
🌻 博主其他介绍相关内容的文章: 嵌套类、内部类、静态嵌套类...
🌻 博主以官方教程为基础开始学习,其中的概念都是以官方教程为主,这样学习起来比较放心。
一、嵌套类(Nested Classes)
(1) 嵌套类、内部类、静态嵌套类概念
✏️ The Java programming language allows you to define a class within another class. Such a class is called a nested class.
📜 Java 语言允许您在一个类中定义其他的类,这样的类叫做嵌套类
📜 嵌套类:定义在其他类中的类
public class OuterClass {
class NestedClass {
}
static class StaticNestedClass {
}
}
🌻 上面示例中的 NestedClass 类和 StaticNestedClass 类都是被定义在 OuterClass 类中的类
🌻 NestedClass 和 StaticNestedClass 是嵌套类
✏️ Nested classes are divided into two categories: non-static and static. ① Non-static nested classes are called inner classes. ② Nested classes that are declared static are called static nested classes.
📜 嵌套类被分为两种:静态的和非静态的
📜 【非静态嵌套类】非静态嵌套类没有被static
关键字修饰,也叫做内部类
📜 【静态嵌套类】静态嵌套类被static
关键字修饰
public class OuterClass {
class InnerClass { // 内部类(非静态嵌套类)
}
static class StaticNestedClass { // 静态嵌套类
}
}
📜 嵌套类外层的类叫做外部类(outer class)
📜 最外层的外部类叫做顶级类(Top-level Class)
public class A {
class B {
class C {
}
}
}
🌻 B 的外部类是 A
🌻 C 的外部类是 B 和 A
🌻 A 是 B 和 C 的顶级类
(2) 嵌套类的特点
✏️ A nested class is a member of its enclosing class(外部类). ① Non-static nested classes (inner classes)
have access to other members of the enclosing class, even if they are declared private
. ❓ ② Static nested classes do not have access to other members of the enclosing class.❓ As a member of the OuterClass, a nested class can be declared private, public, protected, or package private
. 【Recall(回想) that outer classes can only be declared public or package private.】
📜 嵌套类(内部类或静态嵌套类)属于它的外部类的成员。
📜 非静态嵌套类(内部类)可以访问它的外部类的所有成员(静态成员和实例成员),即使是private
成员
📜 静态嵌套类无法直接访问外部类的实例成员(若想访问,需创建外部类实例)
📜 作为外部类的一个成员,嵌套类可以被 private、public、protected、package-private(没有修饰符) 等修饰
🌻 顶级类只能被 public 修饰,或没有访问修饰符
public class TopLevel {
private static int six = 6;
private int five = 5;
static class StaticNested {
class Hello {
void test() {
// 访问外部类(此处是顶级类)的静态成员
System.out.println(six); // 6
// 创建外部类实例后, 才可访问外部类的实例成员
TopLevel topLevel = new TopLevel();
System.out.println(topLevel.five); // 5
}
}
}
}
class TestMain {
public static void main(String[] args) {
// 创建一个静态嵌套类
TopLevel.StaticNested staticNested = new TopLevel.StaticNested();
// 通过静态嵌套类实例创建静态嵌套类里面的内部类实例
TopLevel.StaticNested.Hello hello = staticNested.new Hello();
// 6
// 5
hello.test();
}
}
(3) 何时使用嵌套类?
博主其他嵌套类的文章
✏️ ① It is a way of logically grouping classes that are only used in one place: If a class is useful to only one other class, then it is logical to embed(嵌套) it in that class and keep the two together. Nesting such "helper classes" makes their package more streamlined.
📜 这是一种对仅在一个地方使用的类进行逻辑分组的方法: 如果一个类(如:A)仅仅在另一个类(如:B)中被使用,那么把它嵌套到另一个类中(把 A 嵌套到 B 中)是合乎逻辑的。嵌套这样的帮助类可以让程序包呈现流线型,更加高效。
✏️ ② It increases encapsulation: Consider two top-level classes, A and B, where B needs access to members of A that would otherwise be declared private
. By hiding class B within class A, A's members can be declared private
and B can access them. In addition, B itself can be hidden from the outside world.
📜 嵌套类增加了封装性: ① 考虑顶级类 A 和顶级类 B;② B 中需要访问 A 中被声明为private
的成员;③ 通过把 B 隐藏在类 A 中(把 B 弄成 A 的嵌套类),A 的成员可以被声明为private
,并且 B 可以访问到 A 中的私有成员。另外,B 类本身也可以不向外界暴露。
🌻 上图:A 类中定义的私有属性和私有方法无法被类 C 访问和调用;B 被嵌套到类 A 中,B 可以访问或调用类 A 中的私有属性和私有方法。
✏️ ③ It can lead to more readable and maintainable code: Nesting small classes within top-level classes places the code closer to where it is used.
📜 嵌套类可以使代码更具可读性和可维护性: 将类嵌套在顶级类中,能使代码更接近它被使用的位置。
二、内部类(Inner Classes)
✏️ As with instance methods and variables, an inner class is associated with an instance of its enclosing class and has direct access to that object's methods and fields. Also, because an inner class is associated with an instance, it cannot define any static members itself.
📜 与实例方法和实例变量一样,内部类与其外部类的实例相关联,并且可以直接访问该对象的方法和字段。此外,由于内部类与实例相关联,它本身不能定义任何静态成员。
public class OuterClass {
class InnerClass {
}
}
✏️ An instance of InnerClass can exist only within an instance of OuterClass and has direct access to the methods and fields of its enclosing instance.
📜 InnerClass 的实例仅存在于 OuterClass 的实例中,InnerClass 的实例可以直接访问其外部类的方法和属性。✏️ To instantiate an inner class, you must first instantiate the outer class.
📜 要创建内部类对象必须先创建外部类对象
下面是创建一个 InnerClass 对象的语法:
public class OuterClass {
class InnerClass {
void print() {
System.out.println("print something");
}
}
}
class TestDemo {
public static void main(String[] args) {
// 先有外部类实例后, 才能创建内部类实例
OuterClass outer = new OuterClass();
// 通过外部类对象, 创建内部类对象
OuterClass.InnerClass inner = outer.new InnerClass();
// output: print something
inner.print();
}
}
✏️ There are two special kinds of inner classes: local classes and anonymous classes.
📜 局部类和匿名类是两种特殊的内部类。
三、静态嵌套类
✏️ As with class methods and variables, a static nested class is associated with its outer class. And like static class methods, a static nested class cannot refer directly to instance variables or methods defined in its enclosing class: it can use them only through an object reference.
📜 与类方法和类变量一样,静态嵌套类与其外部类相关联。和类方法一样,静态嵌套类不能直接引用其外部类中定义的实例变量或方法(只能通过对象引用使用外部类的实例成员)
✏️ A static nested class interacts with the instance members of its outer class (and other classes) just like any other top-level class. In effect, a static nested class is behaviorally a top-level class that has been nested in another top-level class for packaging convenience.
📜 静态嵌套类与其外部类的实例成员交互,就像与任何其他顶级类交互一样。实际上,静态嵌套类在行为上就是一个顶级类。只是为了方便打包,把它嵌套在另一个顶级类中而已。
✏️ You instantiate a static nested class the same way as a top-level class.
📜 就像实例化一个顶级类一样实例化静态嵌套类。
public class OuterClass {
private static final int sixFive = 65;
static class StaticNestedClass {
void hundred() {
System.out.println(sixFive + 35);
}
}
}
class TestDemo {
public static void main(String[] args) {
OuterClass.StaticNestedClass snc = new OuterClass.StaticNestedClass();
// 100
snc.hundred();
}
}
四、内部类、静态嵌套类官方案例
一个顶级类:
public class TopLevelClass {
void accessMembers(OuterClass outer) {
System.out.println(outer.outerField);
System.out.println(OuterClass.staticOuterField);
}
}
public class OuterClass {
String outerField = "1.Outer field";
static String staticOuterField = "2.Static outer field";
class InnerClass {
void accessMembers() {
System.out.println(outerField);
System.out.println(staticOuterField);
}
}
static class StaticNestedClass {
void accessMembers(OuterClass outer) {
System.out.println(outer.outerField);
System.out.println(staticOuterField);
}
}
public static void main(String[] args) {
System.out.println("Inner class:");
System.out.println("------------");
OuterClass outerObject = new OuterClass();
// 通过外部类实例创建内部类实例
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
innerObject.accessMembers();
System.out.println("\nStatic nested class:");
System.out.println("--------------------");
// 创建静态嵌套类实例
StaticNestedClass staticNestedObject = new StaticNestedClass();
staticNestedObject.accessMembers(outerObject);
System.out.println("\nTop-level class:");
System.out.println("--------------------");
// 创建外部类实例
TopLevelClass topLevelObject = new TopLevelClass();
topLevelObject.accessMembers(outerObject);
}
}
五、匿名类和局部类
① 📖 类的五大成员:① 属性;② 方法;③ 构造器;④ 代码块;⑤ 嵌套类
② 📖 嵌套类的分类
✏️ There are two special kinds of inner classes: local classes and anonymous classes.
📜 局部类和匿名类是两种特殊的内部类。
(1) 局部类(Local Classes)
✏️ Local classes are classes that are defined in a block, which is a group of zero or more statements between balanced braces. You typically find local classes defined in the body of a method.
📜 代码块(block):在花括号中的零条或多条语句
📜 局部类是定义在代码块中的类
📜 通常您看到的局部类都是定义在方法体中的
✏️ You can define a local class inside any block. For example, you can define a local class in a method body, a for loop, or an if clause.
📜 您可以把局部类定义在任何代码块中(如方法体、for 循环、if 子句)
✏️ A local class has access to the members of its enclosing class.
📜 局部类可以直接访问它的外部类的所有成员(即使是 private 成员)
📜 局部类不能被访问修饰符(public、protected、private)修饰。局部类的地位就是一种局部变量。
📜 局部类可以被final
关键字修饰(因为局部变量也可以用final
修饰,被final
修饰的局部类不能被其他局部类继承)
📜 局部类的作用域仅仅在定义它的代码块中
局部类例子:
public class OuterClass {
private int five = 5;
private void m() {
System.out.println("OuterClass_m()");
}
public void test() {
/* LocalClass 是一个局部类(被定义在方法体中) */
final class LocalClass {
private void test() {
System.out.println("LocalClass_five = " + five);
m();
}
}
// 创建一个局部类对象
LocalClass localClass = new LocalClass();
localClass.test();
}
}
class TestMain {
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
// LocalClass_five = 5
// OuterClass_m()
outerClass.test();
}
}
📜 外部类和局部类的成员重名的时候遵循就近原则(若想访问外部类的成员:外部类类名.this.成员
)
public class OuterClass {
private int num = 5;
private void m() {
System.out.println("OuterClass_m()");
}
public void test() {
class LocalClass {
int num = 6;
private void test() {
System.out.println(num); // 6
System.out.println(OuterClass.this.num); // 5
m();
OuterClass.this.m();
// OuterClass.this 是调用方法的外部类的对象
}
private void m() {
System.out.println("LocalClass_m()");
}
}
LocalClass localClass = new LocalClass();
localClass.m();
localClass.test();
}
}
class TestMain {
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
// 1. LocalClass_m()
// 2. 6
// 3. 5
// 4. LocalClass_m()
// 5. OuterClass_m()
outerClass.test();
}
}
✏️ a local class has access to local variables. However, a local class can only access local variables that are declared final. When a local class accesses a local variable or parameter of the enclosing block, it captures that variable or parameter.
📜 局部类可以访问局部变量。然而,局部类只能访问被声明为final
的局部变量。当局部类访问了局部变量或参数的时候,局部类捕获了该局部变量或参数。
✏️ Starting in Java SE 8, a local class can access local variables and parameters of the enclosing block that are final or effectively final. A variable or parameter whose value is never changed after it is initialized is effectively final.
📜 从 jdk8 开始,局部类可以访问 final 或有效 final 的局部变量或参数。
📜 有效 final:假如一个变量或参数的值在被初始化后从来也没有被改变过,那么这个变量或参数就是有效 final
public class OuterClass {
public void test(int v) {
int n = 5; // n 是有效 final 的变量
// n = 1; // n 不能第二次赋值, 否则就不是有效final
// v = 2; // v 不能第二次赋值, 否则就不是有效final
class LocalClass {
private void m() {
System.out.println(n + 6); // 11
System.out.println(v + v); // 16
}
}
LocalClass localClass = new LocalClass();
localClass.m();
}
}
class TestMain {
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
// 11
// 16
outerClass.test(8);
}
}
📜 局部类中不能定义任何的static
成员(编译时常量除外)
(2) 匿名类(Anonymous Classes)
✏️ Anonymous classes enable you to make your code more concise(简洁的). They enable you to declare and instantiate a class at the same time. They are like local classes except that they do not have a name. Use them if you need to use a local class only once.
📜 匿名类可以让您的代码更加简洁。匿名类可以让您在声明类的同时创建该类的一个实例。匿名类类似局部类,只是匿名类没有类名。如果您只需要使用一次局部类,您可以考虑使用匿名类。
📜 当接口或抽象类的实现类在整个项目中只使用过一次的时候,可以考虑使用匿名类(Anonymous Class)
① 基于接口的匿名类
看下面的代码,引出匿名类:
public class DemoMain {
public static void main(String[] args) {
}
}
interface InterPhone {
void playGame(String name);
}
🌻 在上面的代码中,有一个接口 InterPhone
🌻 若需要使用该接口,需要进行以下几步:① 写一个类实现(implements)该接口,并给予该接口中的抽象方法以具体实现;② 创建该类的实例;③ 通过该类的实例调用 playGame 方法【代码如下】
public class DemoMain {
public static void main(String[] args) {
// (2) 创建该类(ApplePhone)的实例
ApplePhone applePhone = new ApplePhone();
// (3) 通过该类的实例调用 playGame 方法
// output: ApplePhone_playGame(String name)_王者荣耀
applePhone.playGame("王者荣耀");
}
}
interface InterPhone {
void playGame(String name);
}
/* (1) 写一个类实现(implements)该接口,并给予该接口中的抽象方法以具体实现 */
class ApplePhone implements InterPhone {
@Override
public void playGame(String name) {
System.out.println("ApplePhone_playGame(String name)_" + name);
}
}
🌻 假如 ApplePhone 在整个项目过程中, 只使用了一次,以后永远不会使用:可以通过 匿名类完成 InterPhone 接口的使用,无需创建 ApplePhone 类【代码如下】
public class DemoMain {
public static void main(String[] args) {
/*
applePhone 的编译类型是:InterPhone
applePhone 的运行类型是:匿名类
匿名类类名是:外部类类名$数字(如:DemoMain$1)
数字是匿名类对象的编号
new: 创建匿名类对象
*/
InterPhone applePhone = new InterPhone() {
@Override
public void playGame(String name) {
System.out.println("AnonymousClass_" + name);
}
};
// 匿名类类名:class com.gq.DemoMain$1
System.out.println(applePhone.getClass());
// output: AnonymousClass_王者荣耀
applePhone.playGame("王者荣耀");
}
}
interface InterPhone {
void playGame(String name);
}
② 基于类的匿名类
非抽象类:
public class DemoTest {
public static void main(String[] args) {
/*
c1 的运行类型是:class com.gq.Computer
*/
Computer c1 = new Computer();
System.out.println(c1.getClass());
// Computer_open()
c1.open();
/*
c2 的运行类型是:class com.gq.DemoTest$1
*/
Computer c2 = new Computer() {
/**
* 匿名类中重写 Computer 类中的 open 方法
*/
@Override
public void open() {
System.out.println("AnonymousClass_open()");
}
};
System.out.println(c2.getClass());
// AnonymousClass_open()
c2.open();
}
}
class Computer {
public void open() {
System.out.println(getClass().getSimpleName() + "_open()");
}
}
抽象类:
public class DemoTest {
public static void main(String[] args) {
Computer c = new Computer() {
@Override
public void open() {
System.out.println("Anonymous_open()❀");
}
};
// Anonymous_open()❀
c.open();
}
}
/**
* 抽象类
*/
abstract class Computer {
public abstract void open();
}
基于非抽象类的匿名类的另一个例子:
public class DemoTest {
public static void main(String[] args) {
// output: 庆医吃苹果, 花费68888.0元
// 庆医和68888会传给 Person 的构造器
new Person("庆医", 68888) {
@Override
public void eat(String food) {
super.eat(food);
}
}.eat("苹果");
}
}
class Person {
private String name;
private double money;
public Person(String name, double money) {
this.name = name;
this.money = money;
}
public void eat(String food) {
System.out.println(name + "吃" + food + ", 花费" + money + "元");
}
}
(3) 匿名类细节
📖 匿名类既是一个类的定义,同时它本身也是一个对象。从语法上看,它既有定义类的特征,也有创建对象的特征
📖 匿名类中可以直接访问外部类中的所有成员(即使是 private)【但是如果匿名类是在静态方法中,则只能访问外部类中的静态成员】
📖 匿名类不能被访问修饰符修饰
📖 匿名类只有在实例相关的代码块中使用的时候才可直接访问外部类中的实例成员
📖 外部类和匿名类的成员重名的时候遵循就近原则(若想访问外部类的成员:外部类类名.this.成员
)
(4) 匿名类最佳实践
📖 可把匿名类当做参数传递 (感觉上是把方法作为参传递)
public class DemoTest {
public static void main(String[] args) {
int v = test(new ITest() {
@Override
public int math(int v) {
return v + 666;
}
}, 1);
// v = 667
System.out.println("v = " + v);
}
private static int test(ITest iTest, int val) {
return iTest.math(val);
}
}
interface ITest {
int math(int v);
}
六、Exercise
看下面的代码,思考打印结果:
public class TestDemo {
public TestDemo() {
Inner inner1 = new Inner();
inner1.n = 10;
// inner1 = com.gq.TestDemo$Inner@1540e19d
System.out.println("inner1 = " + inner1);
Inner inner2 = new Inner();
// inner2 = com.gq.TestDemo$Inner@677327b6
System.out.println("inner2 = " + inner2);
// output: 5
System.out.println(inner2.n);
}
/* 内部类 */
class Inner {
public int n = 5;
}
public static void main(String[] args) {
TestDemo demo = new TestDemo();
Inner inner = demo.new Inner();
// inner = com.gq.TestDemo$Inner@14ae5a5
System.out.println("inner = " + inner);
// output: 5
System.out.println(inner.n);
}
}
七、我亦无他,唯手熟尔
☘️ 博主是参照清华大学 韩顺平老师的课程学习 Java 的。期间也包含自己对 Java 官方教程的理解,但大部分是看着韩顺平老师的视频教程学习的
☘️ 韩顺平老师分享了 卖油翁和 老黄牛精神的故事,以此来激励学习
☘️ 我把两个故事好好分享一下 ...
(1) 卖油翁
📜 康肃公陈尧咨擅于射箭,世上没有第二个人能跟他相媲美,他也就凭着这种本领而自夸。有一天,他在家里的射箭场地射箭🏹。有个卖油的老翁👨🔬放下担子,站在那里斜着眼睛看着他,很久都没有离开。卖油的老头看他射十箭中了八九箭,但只是微微点点头😀。
📜 陈尧咨问卖油翁:“你也懂得射箭🏹吗?我的箭法不是很高明吗?”卖油的老翁👨🔬说:“没有别的(奥妙), 不过是手法熟练罢了。” 陈尧咨气愤地说:“你怎么敢轻视我射箭🏹的本领!”老翁👨🔬说:“凭我倒油的经验就可以懂得这个道理。”于是拿出一个葫芦放在地上,把一枚铜钱💰盖在葫芦口上,慢慢地用油杓舀油注入葫芦里,油从钱孔注入而钱💰却没有湿。于是说:“ 我也没有别的(奥妙),只不过是手熟练罢了。” 陈尧咨笑着将他送走了。
☘️ 此文章旨在告诉人们,只要努力最终都可以很精通某件事,熟练了就好了,如何熟练? 多练即可 ... (自勉)
☘️ 熟能生巧: Practice makes perfect.
(2) 老黄牛精神
老黄牛🐂一心耕作,心不旁鹜。尽管它吃的是草,但挤出的却是奶,可它却从来不牛,也从来不张扬,不自夸,更不浮躁,也不满足。它总是那么一个劲,总是那样进取和开拓的状态,总是在默默地耕耘着,奋斗着。
结束,如有错误请不吝赐教!