java-深入理解java嵌套类内部类以及内部类builder构建构造函数(代码片段)

hguisu hguisu     2022-12-30     390

关键词:

一、什么是嵌套类及内部类


  可以在一个类的内部定义另一个类,这种类称为嵌套类(nested classes),它有两种类型:静态嵌套类和非静态嵌套类。静态嵌套类使用很少,最重要的是非静态嵌套类,也即是被称作为内部类(inner)。嵌套类从JDK1.1开始引入。其中inner类又可分为三种:
  其一、在一个类(外部类)中直接定义的内部类;
  其二、在一个方法(外部类的方法)中定义的内部类;
  其三、匿名内部类。

使用嵌套类的好处:

  • 嵌套类可以访问外部类的所有数据成员和方法,即使它是私有的。
  • 提高可读性和可维护性:因为如果一个类只对另外一个类可用,那么将它们放在一起,这更便于理解和维护。
  • 提高封装性:给定两个类A和B,如果需要访问A类中的私有成员,则可以将B类封装在A类中,这样不仅可以使得B类可以访问A类中的私有成员,并且可以在外部隐藏B类本身。
  • 减少代码的编写量。

二、静态嵌套类


 如下所示代码为定义一个静态嵌套类,

  public class StaticTest  
          private static String name = "javaJohn";         
    private String id = "X001";
    static class Person
      private String address = "swjtu,chenDu,China";
      public String mail = "josserchai@yahoo.com";//内部类公有成员
      public void display()
        //System.out.println(id);//不能直接访问外部类的非静态成员
        System.out.println(name);//只能直接访问外部类的静态成员
        System.out.println("Inner "+address);//访问本内部类成员。
      
    
  
    public void printInfo()
      Person person = new Person();
      person.display();
      //System.out.println(mail);//不可访问
      //System.out.println(address);//不可访问
      System.out.println(person.address);//可以访问内部类的私有成员
      System.out.println(person.mail);//可以访问内部类的公有成员
  
    
    public static void main(String[] args) 
    StaticTest staticTest = new StaticTest();
    staticTest.printInfo();
  
  

  在静态嵌套类内部,不能访问外部类的非静态成员,这是由Java语法中"静态方法不能直接访问非静态成员"所限定。若想访问外部类的变量,必须通过其它方法解决,由于这个原因,静态嵌套类使用很少。注意,外部类访问内部类的的成员有些特别,不能直接访问,但可以通过内部类来访问,这是因为静态嵌套内的所有成员和方法默认为静态的了。同时注意,内部静态类Person只在类StaticTest 范围内可见,若在其它类中引用或初始化,均是错误的。

三、在外部类中定义内部类


  1、内部类分为成员内部类、静态嵌套类、方法内部类、匿名内部类。

      几种内部类的共性:
      A、内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类命和$符号。
      B、内部类不能用普通的方式访问。内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否是private的。

       如下所示代码为在外部类中定义两个内部类及它们的调用关系:

public class Outer  
        int outer_x = 100;
    class Inner
      public int y = 10;
      private int z = 9;
      int m = 5;
      public void display()
        System.out.println("display outer_x:"+ outer_x);
      
      private void display2()
        System.out.println("display outer_x:"+ outer_x);
      
    
    void test()
      Inner inner = new Inner();
      inner.display();
      inner.display2();
      //System.out.println("Inner y:" + y);//不能访问内部内变量
      System.out.println("Inner y:" + inner.y);//可以访问
      System.out.println("Inner z:" + inner.z);//可以访问
      System.out.println("Inner m:" + inner.m);//可以访问
      InnerTwo innerTwo = new InnerTwo();
      innerTwo.show();
    
    class InnerTwo
      Inner innerx = new Inner();
      public void show()
        //System.out.println(y);//不可访问Innter的y成员
        //System.out.println(Inner.y);//不可直接访问Inner的任何成员和方法
        innerx.display();//可以访问
        innerx.display2();//可以访问
        System.out.println(innerx.y);//可以访问
        System.out.println(innerx.z);//可以访问
        System.out.println(innerx.m);//可以访问
      
    
    public static void main(String args[])
      Outer outer = new Outer();
      outer.test();
    
  

  总结:

        1、对于内部类,通常在定义类的class关键字前不加public 或 private等限制符,若加了没有任何影响。

        2、内部类中可以直接访问外部类的数据成员和方法。

        3、另外,就是要注意,内部类Inner及InnterTwo只在类Outer的作用域内是可知的,如果类Outer外的任何代码尝试初始化类Inner或使用它,编译就不会通过。同时,内部类的变量成员只在内部内内部可见,若外部类或同层次的内部类需要访问,需采用示例程序中的方法,不可直接访问内部类的变量。

四、方法内部类


 顾名思义,把类放在方法内。

class Outer 
    public void doSomething()
        class Inner
            public void seeOuter()
			
            
            
    


A、方法内部类只能在定义该内部类的方法内实例化,不可以在此方法外对其实例化。
B、方法内部类对象不能使用该内部类所在方法的非final局部变量。

      因为方法的局部变量位于栈上,只存在于该方法的生命期内。当一个方法结束,其栈结构被删除,局部变量成为历史。但是该方法结束之后,
在方法内创建的内部类对象可能仍然存在于堆中!例如,如果对它的引用被传递到其他某些代码,并存储在一个成员变量内。
      正因为不能保证局部变量的存活期和方法内部类对象的一样长,所以内部类对象不能使用它们。
下面是完整的例子:

class Outer 
    public void doSomething()
        final int a =10;
        class Inner
            public void seeOuter()
                System.out.println(a);
            
           
        Inner in = new Inner();
        in.seeOuter(); 
    
    public static void main(String[] args) 
        Outer out = new Outer();
        out.doSomething();
    

五、匿名内部类


  没有名字的内部类。表面上看起来它们似乎有名字,实际那不是它们的名字。

匿名内部类即没有名字的内部类,如我们在临时创建新的线程时,经常会这么写:

new Thread(new Runnable()
   @Override
   public void run()
     // do something
   

);

本来应该传给new Thread()构造函数一个实现了Runnable接口的类,但是如果这个类只用到一次,那么还要给他命名岂不是很麻烦,所以就省略了名字,即用匿名内部类来代替。但我们可以发现,即使省略了类名,上面的代码看上去还是废话很多,因为其实我们只关心run方法里面的内容,其他都是没用的废话。
 


A、继承式的匿名内部类。
   

 class Car 
    public void drive()
        System.out.println("Driving a car!");
    

    
class Test
    public static void main(String[] args) 
        Car car = new Car()
            public void drive()
                System.out.println("Driving another car!");
            
        ;
        car.drive();
    


结果输出了:Driving another car! Car引用变量不是引用Car对象,而是Car匿名子类的对象。


B、接口式的匿名内部类。

interface  Vehicle 
    public void drive();

    
class Test
    public static void main(String[] args) 
        Vehicle v = new Vehicle()
            public void drive()
                System.out.println("Driving a car!");
            
        ;
        v.drive();
    


上面的代码很怪,好像是在实例化一个接口。事实并非如此,接口式的匿名内部类是实现了一个接口的匿名类。而且只能实现一个接口。


C、参数式的匿名内部类。

class Bar
    void doStuff(Foo f)

interface Foo
    void foo();

class Test
    static void go()
        Bar b = new Bar();
        b.doStuff(new Foo()
            public void foo()
                System.out.println("foofy");
            
        );
    

4、lambda表达式

lambda表达式与匿名内部类在JVM层面有本质不同,但有的人将lambda看做匿名内部类的语法糖,主要用途就是简化代码和增加代码的可读性。

lambda表达式即匿名表达式,也被称为闭包。lambda 表达式的语法格式如下:

(parameters) -> expression 或 (parameters) -> statements;

以下是lambda表达式的重要特征:

    可选类型声明parameters:不需要声明参数类型,编译器可以统一识别参数值。
    可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
    可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
    可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

// 举例如下:

() -> System.out.println(x);
str -> System.out.println(str);
(int x, int y) -> x+y;
(int x, int y) -> 
      int temp1 = x+y;
      int temp2 = x-y;
      return temp1*temp2;

有了lambda表达式之后,我们可以大大简化上面创建线程的代码

new Thread(() -> doSomething())

函数式接口

lambda表达式能够良好工作还离不开一个函数式接口。函数式接口是指有且仅有一个抽象方法的接口,如上面的Runnable只具有一个抽象方法void run(),就是一个函数式接口,所以函数式接口本质上和普通接口没有什么区别。

函数式接口可以使用@FunctionalInterface注解标识,被该注解标注的接口具有多个非抽象方法时,则会编译报错。

lambda表达式可以直接赋值给对应函数式接口声明的引用,如:

Runnable runnable = () -> System.out.println("I am running");
runnable.run() // 输出 I am running
new Thread(runnable).start(); // 输出 I am running

因此,我们可以直接将lambda表达式传递给以函数式接口作为参数的方法。

六、使用 builder 模式解决构造方法参数过多的情况


      静态工厂和构造方法都有一个限制:它们不能很好地扩展到很多可选参数的情景。

请考虑一个代表包装食品上的营养成分标签的例子。这些标签有几个必需的属性——每次建议的摄入量,每罐的份量和每份卡路里 ,以及超过 20个可选的属性——总脂肪、饱和脂肪、反式脂肪、胆固醇、钠等等。大多数产品都有非零值,只有少数几个可选属性。应该为这样的类编写什么样的构造方法或静态工厂?传统上,程序员使用了可伸缩(telescoping constructor)构造方法模式,在这种模式中,只提供了一个只所需参数的构造函数,另一个只有一个可选参数,第三个有两个可选参数,等等,最终在构造函数中包含所有可选参数。这就是它在实践中的样子。为了简便起见,只显示了四个可选属性:

public class NutritionFacts 

    private final int servingSize; // (mL) required
    private final int servings; // (per container) required
    private final int calories; // (per serving) optional
    private final int fat; // (g/serving) optional
    private final int sodium; // (mg/serving) optional
    private final int carbohydrate; // (g/serving) optional

    public NutritionFacts(int servingSize, int servings) 
        this(servingSize, servings, 0);
    

    public NutritionFacts(int servingSize, int servings,
            int calories) 
        this(servingSize, servings, calories, 0);
    

    public NutritionFacts(int servingSize, int servings,
            int calories, int fat) 
        this(servingSize, servings, calories, fat, 0);
    

    public NutritionFacts(int servingSize, int servings,
            int calories, int fat, int sodium) 
        this(servingSize, servings, calories, fat, sodium, 0);
    

    public NutritionFacts(int servingSize, int servings,
            int calories, int fat, int sodium, int carbohydrate) 
        this.servingSize = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
        this.sodium = sodium;
        this.carbohydrate = carbohydrate;
    

当想要创建一个实例时,可以使用包含所有要设置的参数的最短参数列表的构造方法:

NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);

通常情况下,这个构造方法的调用需要许多你不想设置的参数,但是你不得不为它们传递一个值。 在这种情况下,我们为 fat 属性传递了 0 值。「只有」六个参数可能看起来并不那么糟糕,但随着参数数量的增加,它会很快失控。
简而言之,可伸缩构造方法模式是有效的,但是当有很多参数时,很难编写客户端代码,而且很难读懂它。读者不知道这些值是什么意思,并且必须仔细地计算参数才能找到答案。一长串相同类型的参数可能会导致一些细微的bug。如果客户端意外地反转了两个这样的参数,编译器并不会抱怨,但是程序在运行时会出现错误行为
当在构造方法中遇到许多可选参数时,另一种选择是 JavaBeans 模式,在这种模式中,调用一个无参数的构造函数来创建对象,然后调用 setter 方法来设置每个必需的参数和可选参数:

public class NutritionFacts 
    // Parameters initialized to default values (if any)
    private int servingSize = -1; // Required; no default value
    private int servings = -1; // Required; no default value
    private int calories = 0;
    private int fat = 0;
    private int sodium = 0;
    private int carbohydrate = 0;
    public NutritionFacts()  
    // Setters
    public void setServingSize(int val)  servingSize = val; 
    public void setServings(int val)  servings = val; 
    public void setCalories(int val)  calories = val; 
    public void setFat(int val)  fat = val; 
    public void setSodium(int val)  sodium = val; 
    public void setCarbohydrate(int val)  carbohydrate = val; 

这种模式没有伸缩构造方法模式的缺点。有点冗长,但创建实例很容易,并且易于阅读所生成的代码:

NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);

不幸的是,JavaBeans 模式本身有严重的缺陷。由于构造方法在多次调用中被分割,所以在构造过程中
JavaBean 可能处于不一致的状态。该类没有通过检查构造参数参数的有效性来执行一致性的选项。在不一致的状态下尝试使用对象可能会导致与包含 bug 的代码大相径庭的错误,因此很难调试。一个相关的缺点是,JavaBeans 模式排除了让类不可变的可能性,并且需要在程序员的部分增加工作以确保线程安全。
通过在对象构建完成时手动「冻结」对象,并且不允许它在解冻之前使用,可以减少这些缺点,但是这种变体在实践中很难使用并且很少使用。 而且,在运行时会导致错误,因为编译器无法确保程序员在使用对象之前调用freeze 方法。
幸运的是,还有第三种选择,它结合了可伸缩构造方法模式的安全性和 JavaBean 模式的可读性。 它是 Builder模式 的一种形式。客户端不直接调用所需的对象,而是调用构造方法 (或静态工厂),并使用所有必需的参数,并获得一个 builder 对象。然后,客户端调用 builder 对象的 setter 相似方法来设置每个可选参数。最后,客户端调用一个无参的 build 方法来生成对象,该对象通常是不可变的。Builder 通常是它所构建的类的一个静态成员类。以下是它在实践中的示例:

//Builder Pattern
public class NutritionFacts
    public final int servingSize;
    public final int servings;
    public final int calories;
    public final int fat;
    public final int sodium;
    public final int carbohydrate;

    //静态内部类Builder
    public static class Builder
        //必选变量
        private final int servingSize;
        private final int servings;

        //可选变量
        private final int calories = 0;
        private final int fat = 0;
        private final int sodium= 0;
        private final int carbohydrate= 0;

        //Builder的构造函数
        public Builder(int servingSize, int servings)
            this. servingSize = servingSize;
            this.servings = servings;
        


        //Builder的成员方法返回其自身,所以可以链式调用
        //类似于setter()方法
        public Builder calories(int val)
            calories = val;
            return this;
        
        public Builder fat(int val)
            fat = val;
            return this;
        
        public Builder sodium(int val)
            sodium =  val;
            return this;
        
        public Builder carbohydrate(int val)
            carbohydrate = val;
            return this;
        

        //Builder的build方法,返回外部类的实例
        public NutritionFacts build()
            return new NutritionFacts(this);
        
    

    //外部类的构造函数
    private NutritionFacts(Builder build)
        servingSize = build.servingSize;
        servings = build.servings;
        calories = build.calories;
        fat = build.fat;
        sodium = build.sodium;
        carbohydrate = build.carbohydrate;
    


NutritionFacts 类是不可变的,所有的参数默认值都在一个地方。builder 的 setter 方法返回 builder 本身,这样调用就可以被链接起来,从而生成一个流畅的 API。下面是客户端代码的示例:

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).sodium(35).carbohydrate(27).build();


 

java基础8:深入理解内部类

更多内容请关注微信公众号【Java技术江湖】这是一位阿里Java工程师的技术小站,作者黄小斜,专注Java相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点Docker、ELK,同时也分享技术干货和学... 查看详情

java内部类嵌套类

...erClass{...classNestedClass{...}}如果内部类是static的那么被称作嵌套类或者内部静态类```classOuterClass{...staticclassStaticNestedClass{...}}```使用 查看详情

java嵌套类

...系,那么可以将内部类声明为static,这通常称为嵌套类,想要理解static应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向它的外围类对象,然而,当内部类时static时,就不是这样了,嵌套类意味着:1)要创建... 查看详情

java嵌套类,内部类和外部类

1.嵌套类,内部类    嵌套类是指被定义在一个类内部的类;     JAVA的嵌套类有很多种类:1.静态成员类;2.非静态成员类;3.匿名类;4.局部类;其中,除了静态成员类之外,其他的都是内部类,因为... 查看详情

深入kotlin-嵌套类和内部类(代码片段)

嵌套类和内部类嵌套类kotlin中,嵌套类和内部类是两种不同的类。所谓嵌套类是指定义在类体内的类。classOuterClass privatevalname:String="Anna" classNestedClass funnestedMethod()="Attila" funmain(args:Array<String&g 查看详情

请问java中匿名内部类有啥用,举个例子,谢谢

...部类声明为static。这通常称为嵌套类(nestedclass)。想要理解static应用于内部类时的含义,你就必须记住,普通的内部类对象隐含地保存了一个引用,指向创建它的外围类对象。然而,当内部类是static的时,就不是这样了。嵌套... 查看详情

Java内部类和静态嵌套类

】Java内部类和静态嵌套类【英文标题】:Javainnerclassandstaticnestedclass【发布时间】:2010-09-0909:04:20【问题描述】:Java中内部类和静态嵌套类的主要区别是什么?设计/实现是否在选择其中一个方面发挥作用?【问题讨论】:JoshuaBl... 查看详情

Java内部类的用途是啥?嵌套类和内部类是一样的吗? [复制]

】Java内部类的用途是啥?嵌套类和内部类是一样的吗?[复制]【英文标题】:WhataretheusesofinnerclassesinJava?Arenestedclassesandinnerclassesthesame?[duplicate]Java内部类的用途是什么?嵌套类和内部类是一样的吗?[复制]【发布时间】:2011-02-252... 查看详情

匿名内部类

...内部类有:成员内部类,方法内部类,匿名内部类,静态嵌套内部类。    2、内部类在java虚拟机编译后还是会称为class文件。比如有一个A类,一个B类,其中B类是A类的内部类编译之后有两个class文件:A.class和A$B.class   ... 查看详情

markdown内部类,嵌套类,java(代码片段)

查看详情

(转)java基础——嵌套类内部类匿名类

本文内容分转自博客:http://www.cnblogs.com/mengdd/archive/2013/02/08/2909307.html 将相关的类组织在一起,从而降低了命名空间的混乱。  一个内部类可以定义在另一个类里,可以定义在函数里,甚至可以作为一个表达式的一部分。... 查看详情

java内部类详解(代码片段)

...竟。下面是本文的目录大纲:  一.内部类基础  二.深入理解内部类  三.内部类的使用场景和好处  四.常见的与内部类相关的笔试面试题  若有不正之处,请多谅解并欢迎批评指正。  请 查看详情

java内部类的静态嵌套类

...类中可以定义静态或者非静态的成员。从技术上讲,静态嵌套类不属于内部类。因为内部类与外部类共享一种特殊关系,更确切地说是对实例的共享关系。而静态嵌套类则没有上述关系。它只是位置在另一个类的内部,因此也被... 查看详情

kotlin内部类与嵌套类(代码片段)

kotlin内部类与嵌套类简单的说,kotlin嵌套类相当于java的静态内部类,kotlin内部类相当于java普通内部类。classOutClassvalocval="一个外部类变量值"//嵌套类,相当于Java的静态内部类classNestedClassfuntest1()="嵌套... 查看详情

kotlin内部类与嵌套类(代码片段)

kotlin内部类与嵌套类简单的说,kotlin嵌套类相当于java的静态内部类,kotlin内部类相当于java普通内部类。classOutClassvalocval="一个外部类变量值"//嵌套类,相当于Java的静态内部类classNestedClassfuntest1()="嵌套... 查看详情

对java的匿名内部类理解

对Java的匿名内部类理解JAVA面向对象三大特性:封装、继承、多态。多态:可以理解为大千世界中的对象在不同的环境下可能会有多种形态。Java中多态存在的前提:必须存在继承、父类的引用指向子类的对象。匿名内部类:就是... 查看详情

30根据官方教程详解嵌套类内部类静态嵌套类局部类匿名类...(代码片段)

文章目录一、嵌套类(NestedClasses)(1)嵌套类、内部类、静态嵌套类概念(2)嵌套类的特点(3)何时使用嵌套类?二、内部类(InnerClasses)三、静态嵌套类四、内部类、静态嵌套类官方案例五、匿名类和局部类(1)局... 查看详情

深入浅析java中staticclass及静态内部类和非静态内部类的不同

。在java中我们可以有静态实例变量、静态方法、静态块。类也可以是静态的。java允许我们在一个类里面定义静态类。比如内部类(nestedclass)。把nestedclass封闭起来的类叫外部类。在java中,我们不能用static修饰顶级类(toplevelcla... 查看详情