首页 > 编程语言 >Java 内部类

Java 内部类

时间:2023-02-19 16:11:06浏览次数:31  
标签:部类 Java 内部 静态 public static class

目录

1、初识内部类

如果一个事物的内部包含另一个事物,那么这是一个类的内部包含另一个类。

例如:身体和心脏的关系,又如汽车和发动机的关系。

把 A 类定义在 B 类的内部,A——内部类(嵌套类),B——外部类(宿主类)。

内部类的特点

  • 内部类提供了更好的封装,把内部类隐藏在外部类之中,不允许同一个包下的其他类访问该内部类。
  • 内部类成员可以直接访问外部类的私有数据。
  • 匿名内部类适合那些仅需要一次使用的类。
  • 方法里定义的内部类称为局部内部类。
  • 内部类比外部类可以多使用三个修饰符:private、protected、static。
  • 非静态内部类不能拥有静态成员。

内部类的分类

  • 成员内部类
    • 非静态内部类
    • 静态内部类
  • 局部内部类
  • 匿名内部类

成员内部类可以看成外部类的成员,可以使用四个访问控制符

外部类仅可以使用两个访问控制符:公共访问权限、包访问权限

private:本类访问
省略不写:包访问
protected:子父类访问
public:公共访问

定义内部类语法格式

修饰符 class 外部类名称 {
    修饰符 class 内部类名称 {
        
    }
}

内用外:随意访问

外用内:使用内部类对象

2、非静态内部类(实例内部类)

Cow.java

// 外部类 奶牛
public class Cow {
	private double weigth;

	public void test() {
		CowLeg cowLeg = new CowLeg(10, "黑白相间");
		cowLeg.show();
	}

	public Cow() {
	}

	public Cow(double weigth) {
		this.weigth = weigth;
	}

	// 非静态内部类 奶牛的腿
	private class CowLeg {
		private double length;
		private String color;

		public CowLeg() {
		}

		public CowLeg(double length, String color) {
			this.length = length;
			this.color = color;
		}

		public void show() {
			System.out.println("本牛腿的颜色是:" + color + ", 长度是:" + length);
			System.out.println("奶牛重量为:" + weigth);
		}
	}
}

测试

public class Application {
	public static void main(String[] args) {
		Cow cow = new Cow(20);
		cow.test();
	}
}

在外部类里包含一个 test() 方法,该方法里创建了一个内部类对象,并调用该对象 show() 方法。

在外部类里使用非静态内部类时,与平时使用普通类并没有太大区别。

编译上面程序,生成了两个字节码文件,一个是 Cow.class,另一个是 Cow$CowLeg.class,前者是外部类 Cow 的 class 文件,后者是 CowLeg 的字节码文件。

成员内部类(静态内部类、非静态内部类)的字节码文件总是这种形式:OuterInner$InnerClass.class

在非静态内部类里可以直接访问外部类的私有成员,是因为在非静态内部类的对象里保存了一个它所寄生外部类对象的引用。

当调用非静态内部类的实例方法时,必须有一个非静态内部类的实例,非静态内部类实例必须寄生在外部类实例里。

image-20230214201149568

非静态内部类对象中保留外部类对象的引用内存示意图

变量重名怎么访问?

当在非静态内部类的方法内访问某个变量时,查找的顺序是:

本方法所在的局部变量 > 内部类成员变量 > 外部类成员变量

如果外部类成员变量、内部类成员变量、局部变量重名,则可通过使用 this、外部类类名.this 作为限定来区分。

public class Outer {
   public static void main(String[] args) {
      new Outer().show();
   }

   private String name = "外部类成员变量";

   public void show(){
      new Inner().test();
   }


   class Inner{
      private String name = "内部类成员变量";

      public void test() {
         String name = "局部变量";
         System.out.println(name); // 局部变量
         System.out.println(this.name); // 内部类成员变量
         System.out.println(Outer.this.name); // 外部类成员变量
      }
   }
}

内部类访外部类,直接用;

外部类访内部类,须在外部类方法中显式定义内部类对象来访问

public class OuterClass {
   public static void main(String[] args) {
      OuterClass outer = new OuterClass();// 只创建了外部类对象,还未创建内部类对象
      outer.showInner();
   }

   private int outerProp = 20;

   private void showInner() {
      // 访问内部类实例变量,需要显示创建内部类对象
      InnerClass innerClass = new InnerClass();
      System.out.println(innerClass.innerProp);
      innerClass.showOuter();
   }

   private class InnerClass {
      private int innerProp = 10;

      public void showOuter() {
         // 非静态内部类直接访问外部类成员
         System.out.println(outerProp);
      }
   }
}

非静态内部类对象和外部类对象的关系是什么样?

非静态内部类对象有则外部类对象一定有

外部类对象有但非静态内部类对象不一定有

1、不允许在外部类的静态成员中直接使用非静态内部类

public class A {
   class B {
   }

   public static void main(String[] args) {
      // 静态内容不能访问非静态内容。
      // 此处报错
      // new B();
   }
}

2、不允许在非静态内部类中定义静态成员

public class A {
	// 非静态内部类
	class B {
		// 都报错
		static int a;
		static void print(){}
		static {}
	}
}

非静态内部类里不能有静态方法、静态成员变量、静态代码块。

非静态内部类可以有实例代码块,作用与外部类实例代码块作用相同。

3、静态内部类(重点)

static 关键字的作用:把类的成员变成类相关,而不是实例相关。

static 关键字可以修饰内部类,但不可修饰外部类。

使用 static 修饰的内部类称为:静态内部类(类内部类)

静态内部类属于外部类本身,不属于外部类的某个对象。

静态内部类可以包含:静态成员、非静态成员。

根据静态成员不能访问非静态成员的规则:静态内部类里不能访问外部类的实例成员,只能访问外部类的类成员。

即使静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员。

public class Demo01 {

   private int a1 = 10;
   private static int a2 = 20;

   private static class StaticInnerClass {
      private static int a3 = 30;

      public static void show() {
         // 访问外部类非静态成员报错
         // System.out.println(a1);
         System.out.println(a2);
         System.out.println(a3);
      }
   }
}

为什么静态内部类的实例方法也不能访问外部类的实例变量?

因为静态内部类是外部类的类相关的,而不是外部类的对象相关的。

也就是说:静态内部类对象不是寄生在外部类的实例中,而是寄生在外部类的类本身中。

当静态内部类对象存在时,并不存在一个被它寄生的外部类对象,静态内部类对象只持有外部类的类引用,没有持有外部类对象的引用。

如果允许静态内部类的实例方法访问外部类的实例成员,但找不到被寄生的外部类对象,会引起错误。

外部类不能直接访问静态内部类的成员但可以使用 静态内部类的类名.xxx 来访问类成员;

创建一个静态内部类对象来访问实例成员。

public class A {
   public static void main(String[] args) {
      new A().show();
   }

   static class B{
      private static int a1 = 10;
      private int a2 = 20;
   }

   public void show(){
      System.out.println(B.a1);
      System.out.println(new B().a2);
   }
}

除此之外,java 还允许在接口里定义内部类,接口里定义的内部类默认使用 public static 修饰,也就是说:接口里的内部类只能是静态内部类

类似的,接口里定义内部接口,默认也是使用 public static 修饰,接口里的内部接口也只能是静态内部接口。

例如:MMap<K,V> 接口里定义了一个内部接口 Entry<K,V> 。

4、内部类的使用

1、在外部类内部使用内部类

与平常使用普通类没有什么区别,需要注意的是:不要在外部类的静态成员中使用非静态内部类。

因为静态成员无法访问非静态成员。

2、在外部类以外定义和创建非静态内部类对象

语法格式:

外部类类名.内部类类名 变量名称 = new 外部类类名().new 内部类类名();

代码:

public class Out {
    class In{
   }
}
public class MyTest {
	public static void main(String[] args) {
		// 在外部类以外创建非静态内部类对象
		Out.In in = new Out().new In();
		// =========上面一句和下面三句效果等价=========
		// 1、创建非静态内部类对象的引用
				Out.In in2;
		// 2、创建非静态内部类对象所寄生的外部类对象
				Out out = new Out();
		// 3、通过外部类对象创建内部类对象
				in2 = out.new In();
	}
}

3、在外部类外部定义和创建静态内部类对象

语法格式:

外部类类名.内部类类名 变量名称 = new 外部类类名.内部类类名();

代码:

public class Out {
	static class In{
	}
}
public class MyTest {
	public static void main(String[] args) {
		Out.In in = new Out.In();
	}
}

4、调用静态内部类的构造器时无需使用外部类对象,所以创建静态内部类的子类也比较简单。

public class Out {
   static class In{
   }
}
public class SubIn extends Out.In {}

使用静态内部类比使用非静态内部类要简单很多,只要把外部类当成静态内部类的包空间即可~

因此当程序需要使用内部类时,优先考虑使用静态内部类。

5、局部内部类

局部内部类在开发中很少使用,了解即可~

一般定义类需要重复使用,而局部内部类作用域只在方法之内,作用域太小。

如果把一个内部类放在方法里定义,那这个内部类就是一个局部内部类,局部内部类仅在方法里有效。

由于局部内部类无法在方法以外的地方使用,也就无法使用访问控制符合 static 修饰符。

public static void main(String[] args) {
   class InBase{
      int a;
   }
   class InSub extends InBase{
      int b;
   }
   InSub inSub = new InSub();
   inSub.a = 10;
   inSub.b = 20;
   System.out.println(inSub.a);
   System.out.println(inSub.b);

}

6、匿名内部类

特点:

  • 适合只需要一次使用的类
  • 创建匿名内部类时会立即创建一个该类的实例,类的定义立即消失
  • 用完一次后,无法重复使用

规则:

  • 匿名内部类不能是抽象类。创建匿名内部类时会立即创建一个该类的实例。
  • 匿名内部类里不能定义构造器。因为匿名内部类没有类名。
  • 匿名内部类中允许定义代码块,来完成需要在构造器中做的事。

语法格式:

new 实现接口() | 父类构造器(参数列表){
    // 匿名内部类类体
}

匿名内部类必须实现一个接口或继承一个父类,但最多只能实现一个接口或继承一个父类。

最常用的创建匿名内部类的方式创建某个接口类型的对象。

示例代码:

interface Product{
   String getName();
   double getPrice();
}

public class AnonymousTest {

   public static void main(String[] args) {
      AnonymousTest test = new AnonymousTest();
      test.show(new Product() {
         @Override
         public String getName() {
            return "名侦探柯南";
         }

         @Override
         public double getPrice() {
            return 60;
         }
      });
   }


   void show(Product product){
      System.out.println("名称:" + product.getName());
      System.out.println("花费:" + product.getPrice());
   }

}

当通过实现接口来创建匿名内部类时,匿名内部类里不能显式定义构造器,内部只有一个隐式无参构造器,所以 new 接口名称() 括号里不能传任何参数。

但如果通过继承父类来创建匿名内部类时,匿名内部类将拥有和父类相同参数列表的构造器。

public abstract class Device {
   private String name;
   public abstract double getPrice();

   public Device() {
   }

   public Device(String name) {
      this.name = name;
   }

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }
}
public class AnonymousTest2 {

   public static void main(String[] args) {
      AnonymousTest2 test2 = new AnonymousTest2();
      // 调用有参构造器创建Device匿名实现类对象
      test2.show(new Device("笔记本") {
         // 实现抽象方法
         @Override
         public double getPrice() {
            return 100;
         }
      });

      System.out.println("-------------------");
      // 调用无参构造器创建Device匿名实现类对象
      AnonymousTest2 test3 = new AnonymousTest2();
      test3.show(new Device() {
         // 实例代码块
         {
            System.out.println("匿名内部类的实例代码块");
         }

         // 实现抽象方法
         @Override
         public double getPrice() {
            return 60;
         }
         // 重写父类实例方法
         @Override
         public String getName(){
            return "轻薄笔记本";
         }
      });
   }


   public void show(Device device) {
      System.out.println("名称:" + device.getName());
      System.out.println("价格:" + device.getPrice());
   }
}

标签:部类,Java,内部,静态,public,static,class
From: https://www.cnblogs.com/sunzhongjie/p/17134918.html

相关文章

  • 61-CICD持续集成工具-Jenkins自动化部署JAVA程序
    BlueOcean插件实现可视化注意:安装完插件,需要重启Jenkins才能生效参数化构建执行命令脚本[root@jenkinsscript]#catwheel-deploy-rollback.sh#!/bin/bash##*********......
  • java的filter如何实现utf-8编码转换
    importjavax.servlet.*;importjavax.servlet.annotation.WebFilter;importjavax.servlet.http.HttpServletRequest;importjava.io.IOException;@WebFilter("/*")......
  • 深入理解Java字符串常量池
    “先从这道面试题开始吧!”Strings=newString("二哥");“这行代码创建了几个对象?”“不就一个吗?”三妹不假思索地回答。“不,两个!”我直接否定了三妹的答案,“使用ne......
  • Java如何判断两个字符串是否相等?
    “这个问题也可以引申为.equals()和‘==’操作符有什么区别。”.equals()就好像我们普通人,看见阿丽塔以为是洛丽塔,看见洛丽塔以为是阿丽塔,看起来一样就觉得她们是同......
  • Java字符串拼接
    “哥,你让我看的《Java开发手册openinnewwindow》上有这么一段内容:循环体内,拼接字符串最好使用StringBuilder的append()方法,而不是+号操作符。这是为什么呀?”三妹......
  • Java字符串分割
    “哥,我感觉字符串拆分没什么可讲的呀,直接上String类的split()方法不就可以了!”三妹毫不客气地说。“假如你真的这么觉得,那可要注意了,事情远没这么简单。”我微笑着说......
  • 漏洞分析-log4j RCE-JAVA篇
    0x00原理分析log4j的介绍:log4j是java打印输出日志的一个API,只要引入了log4j的jar包或者是在xml配置文件内配置好log4j即可输入java运行时产生的日志内容,一般用于记录网......
  • Java入门博客
    开始准备 新建一个file->new->project->emptyproject   新建一个file->new->module->java 打开projectstructure,修改这两处   修改注释的颜色 ......
  • Java基础知识点(带返回值方法的定义和调用
    一:带返回值方法的定义方法的返回值其实就是方法运行的最终结果。如果要在调用处根据方法的结果,去编写另外一段逻辑,为了在调用处拿到方法的结果,就需要定义带返回值的方法。eg......
  • LeetCode-53. 最大子数组和(Java)
    一、前言:......