Java内部类和静态嵌套类?推荐| Java Debug 笔记

3,944 阅读8分钟

本文正在参加「Java主题月 - Java Debug笔记活动」,详情查看活动链接

提问:Java内部类和静态嵌套类?

Java中的内部类和静态嵌套类之间的主要区别是什么?设计/实施在选择其中之一方面是否起作用?

高分回答:

很多的知识点,真的需要写出来才会掌握!!! \color{purple}很多的知识点,真的需要写出来才会掌握!!!{~}

在Java教程说:

术语:嵌套类分为两类:静态和非静态。声明为静态的嵌套类简称为静态嵌套类。非静态嵌套类称为内部类。

通常,大多数程序员可以互换地使用“嵌套”和“内部”这两个术语,但是我将使用正确的术语“嵌套类”,它涵盖内部和静态两个方面。

类可以无限嵌套,例如,类A可以包含类B,而类B包含类C,类C包含类D,等等。但是,很少有多个级别的类嵌套,这通常是不好的设计。

创建嵌套类的三个原因:

组织:有时将一个类分类到另一个类的命名空间中似乎是最明智的,尤其是当它不会在任何其他上下文中使用时
访问:嵌套类具有对其包含的类的变量/字段的特殊访问权限(确切地说,哪个变量/字段取决于嵌套类的类型,无论是内部类还是静态类)。
便利:再次为每个新类型创建一个新文件很麻烦,尤其是当该类型仅在一个上下文中使用时

Java中有四种嵌套类。简而言之,它们是:

静态类:声明为另一个类的静态成员
内部类:声明为另一个类的实例成员
本地内部类:在另一个类的实例方法中声明
匿名内部类:类似于本地内部类,但编写为返回一次性对象的表达式

让我详细说明。

静态类

静态类是最容易理解的种类,因为它们与包含类的实例无关。

静态类是声明为另一个类的静态成员的类。就像其他静态成员一样,此类实际上只是一个使用包含类作为其命名空间的衣架,例如,在披萨包中声明为Rhino类的静态成员的Goat类被称为pizza.Rhino.Goat。

package pizza;

public class Rhino {

    ...

    public static class Goat {
        ...
    }
}

坦白地说,静态类是一个非常不值钱的功能,因为类已经被包划分为名称空间。创建静态类的唯一真正可以想象的原因是,此类可以访问其包含类的私有静态成员,但是我发现这对于存在静态类功能是一个很蹩脚的理由。

内部类

内部类是声明为另一个类的非静态成员的类:

package pizza;

public class Rhino {

    public class Goat {
        ...
    }

    private void jerry() {
        Goat g = new Goat();
    }
}

与静态类一样,内部类通过其包含的类名称pizza.Rhino.Goat被限定为合格,但是在包含的类内部,可以通过其简单名称来对其进行识别。然而,一个内部类的每一个实例被绑定到其包含类的特定实例:如上所述,山羊中创建杰里,被隐式地绑定到犀牛实例这在杰里。否则,我们在实例化Goat时使关联的Rhino实例显式:

Rhino rhino = new Rhino();
Rhino.Goat goat = rhino.new Goat();

(注意,您在奇怪的新语法中将内部类型称为Goat:Java从rhino部分推断出包含类型。而且,是的,新的rhino.Goat()对我来说也更有意义。)

那么,这有什么好处呢?好吧,内部类实例可以访问包含的类实例的实例成员。这些封闭的实例成员都是内部类内部称为经由只是他们的简单的名称,而不是通过 这个(这在内部类指的是内部类的实例,而不是相关的包含类实例):

public class Rhino {

    private String barry;

    public class Goat {
        public void colin() {
            System.out.println(barry);
        }
    }
}

在内部类中,您可以将包含类的这个称为Rhino.this,并且可以使用它来引用其成员,例如Rhino.this.barry。

本地内部类

局部内部类是在方法主体中声明的类。这样的类仅在其包含方法内是已知的,因此只能实例化它,并在其包含方法内对其成员进行访问。这样做的好处是,本地内部类实例与该实例相关联并且可以访问其包含方法的最终局部变量。当实例使用其包含方法的最终局部变量时,即使变量超出范围(实际上是Java的有限的封闭版本),该变量仍保留其在实例创建时所持有的值。

因为本地内部类既不是类也不是包的成员,所以不会使用访问级别声明它。(但是请注意,它自己的成员具有与普通班级一样的访问级别。)

如果在实例方法中声明了本地内部类,则在创建实例时,内部类的实例将与包含方法的this所持有的实例绑定,因此可以像在实例中那样访问包含类的实例成员。内部阶级。本地内部类仅通过其名称实例化,例如,本地内部类Cat被实例化为new Cat(),而不是您可能期望的new this.Cat()。

匿名内部类

匿名内部类是编写本地内部类的一种在语法上方便的方法。最常见的是,本地内部类每次在其包含方法运行时最多仅实例化一次。那么,如果我们可以将本地内部类的定义及其单个实例组合为一种方便的语法形式,那就太好了,而且如果我们不必为该类想一个名字(无用的次数越少),那也就越好。您的代码包含的名称越好)。匿名内部类允许这两种情况:

new *ParentClassName*(*constructorArgs*) {*members*}

这是一个返回未命名类的新实例的表达式,该实例扩展了ParentClassName。您不能提供自己的构造函数。而是隐式提供了一个简单地调用超级构造函数的函数,因此提供的参数必须适合超级构造函数。(如果父级包含多个构造函数,则称为“最简单”的构造函数,由一组相当复杂的规则确定的“最简单”,这些规则不值得详细学习,只需注意NetBeans或Eclipse告诉您的内容即可。)

另外,您可以指定一个接口来实现:

new *InterfaceName*() {*members*}

这样的声明创建了一个未命名类的新实例,该实例扩展了Object并实现InterfaceName。同样,您不能提供自己的构造函数。在这种情况下,Java隐式提供了一个无参,无所事事的构造函数(因此,在这种情况下永远不会有构造函数参数)。

即使您不能为匿名内部类提供构造函数,也可以使用初始化程序块(放置在任何方法外部的{}块)进行所需的任何设置。

需要清楚的是,匿名内部类只是使用一个实例创建本地内部类的一种较不灵活的方式。如果您想要一个实现多个接口的本地内部类,或者在扩展除Object之外的某个类或指定其自己的构造函数的同时实现接口的方法,则必须创建一个常规的名为local内部类的类。

文章翻译自 am2dgbqfb6mk75jcyanzabc67y-ac4c6men2g7xr2a-stackoverflow-com.translate.goog/questions/7…

作者建议:居然说静态内部类很蹩脚~ 不服啊

有一条需要注意:内部类不随外部类加载而加载,内部类加载依赖外部类加载。

经典的静态内部类的单例模式

public class Singleton {
    private static class LazyHolder {    
       private static final Singleton INSTANCE = new Singleton(); // final修饰不可继承,方法不可重写    
     }    
     private Singleton (){}    
     public static final Singleton getInstance() {    
        return LazyHolder.INSTANCE;     // 内部类加载由jvm加载保证顺序,保证线程安全。
     } 

}

翻源码的时候偶然看到使用静态内部类的例子:

构造和编码URI的模板类:UriComponentsBuilder类中

// 内部类
private static class CompositePathComponentBuilder implements PathComponentBuilder {
                // 内部类初始化
		private final LinkedList<PathComponentBuilder> builders = new LinkedList<>();
                   // 添加路径
                public void addPathSegments(String... pathSegments) {
			if (!ObjectUtils.isEmpty(pathSegments)) {
				PathSegmentComponentBuilder psBuilder = getLastBuilder(PathSegmentComponentBuilder.class);
				FullPathComponentBuilder fpBuilder = getLastBuilder(FullPathComponentBuilder.class);
				if (psBuilder == null) {
					psBuilder = new PathSegmentComponentBuilder();
					this.builders.add(psBuilder);
					if (fpBuilder != null) {
						fpBuilder.removeTrailingSlash();
					}
				}
				psBuilder.append(pathSegments);
			}
		}
}   

内部类加载的时候

      /**
	 * 默认构造函数。受保护以防止直接实例化。
	 * @see #newInstance()
	 * @see #fromPath(String)
	 * @see #fromUri(URI)
	 */
	protected UriComponentsBuilder() {
		this.pathBuilder = new CompositePathComponentBuilder();
	}

内部类使用的时候addPathSegments

     // 将路径段附加到现有路径。每个路径段都可以包含* URI模板变量,并且不应包含任何斜杠
	@Override
	public UriComponentsBuilder pathSegment(String... pathSegments) throws IllegalArgumentException {
		this.pathBuilder.addPathSegments(pathSegments); // 
		resetSchemeSpecificPart();
		return this;
	}

更新一下,我在这篇文章中写了关于线程池ThreadPoolExecutor它里面涉及到的内部类和静态内部类juejin.cn/post/696234…

欢迎关注我的专栏StackOverFlow,我会筛选优质的问答,面试常考!!! \color{red}欢迎关注我的专栏StackOverFlow,我会筛选优质的问答,面试常考!!!{~}

有最新、优雅的实现方式,我也会在文末写出我对本问答的见解 \color{red}有最新、优雅的实现方式,我也会在文末写出我对本问答的见解{~}

真心感谢帅逼靓女们能看到这里,如果这个文章写得还不错,觉得有点东西的话

求点赞👍 求关注❤️ 求分享👥 对8块腹肌的我来说真的 非常有用!!!

如果本篇博客有任何错误,请批评指教,不胜感激 !❤️❤️❤️❤️