java常见的面试题及答案,java面试题下载

  java常见的面试题及答案,java面试题下载

  如何解决写爬虫IP受阻的问题?立即使用。

  说起内班这个词,我想很多人都很熟悉,但是会觉得陌生。原因是平时写代码可能用到的场景不多。最常使用的场景是有事件监控的时候,即使使用,也很少总结内部类的用法。今天就来一探究竟。

  一.内部类基础

  在Java中,一个类可以在另一个类或方法中定义。这样的类称为内部类。一般来说,广义的内部类包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。我们来看看这四个内部类的用法。

  1.成员内部类

  成员类是最常见的内部类,它被定义为位于另一个类内部,其形式如下:

  班级圈{

  双半径=0;

  公共圆(双半径){

  this.radius=radius

  }

  类绘制{//内部类

  公共void drawSahpe() {

  system . out . println( draw shape );

  }

  }

  }看起来像是班抽是班圈成员,叫外班。内部类可以无条件地访问外部类的所有成员属性和成员方法(包括私有成员和静态成员)。

  班级圈{

  私有双半径=0;

  公共静态int count=1;

  公共圆(双半径){

  this.radius=radius

  }

  类绘制{//内部类

  公共void drawSahpe() {

  system . out . println(radius);//外部类的私有成员

  system . out . println(count);//外部类的静态成员

  }

  }

  }但需要注意的是,当成员内部类有与外部类同名的成员变量或方法时,会被隐藏,即默认访问成员内部类的成员。如果要访问与外部类同名的成员,需要使用以下形式:

  虽然成员的内部类可以无条件地访问外部类的成员,但是外部类访问成员的内部类的成员就没有那么随意了。在外部类中,如果要访问成员内部类的成员,必须首先创建成员内部类的对象,然后通过引用该对象来访问它:

  班级圈{

  私有双半径=0;

  公共圆(双半径){

  this.radius=radius

  getDrawInstance()。drawSahpe();//必须先创建成员内部类的对象,然后才能访问它。

  }

  私有绘制getDrawInstance() {

  返回new Draw();

  }

  类绘制{//内部类

  公共void drawSahpe() {

  system . out . println(radius);//外部类的私有成员

  }

  }

  }成员内部类是依赖外部类而存在的,也就是说如果要创建成员内部类的对象,前提是必须有外部类的对象。在成员中创建类对象的一般方法如下:

  公共类测试{

  公共静态void main(String[] args) {

  //第一种方式:

  Outter Outter=new Outter();

  出去吧。Inner Inner=outter . new Inner();//它必须通过Outter对象创建

  //第二种方式:

  出去吧。inner inner 1=outter . getinnerinstance();

  }

  }

  类别输出器{

  private Inner inner=null

  公共Outter() {

  }

  公共内部getInnerInstance() {

  if(inner==null)

  Inner=new Inner();

  返回内部;

  }

  内部类{

  公共内部(){

  }

  }

  }内部类可以有私有访问、受保护访问、公共访问和包访问。例如,在上面的例子中,如果成员的内部类是用private修饰的,那么只能在外部类内部访问它;如果是用public装修的话,在哪里都可以访问;

  如果它用protected修饰,则只能在同一个包下访问,或者如果它继承了外部类,则只能访问它;如果是默认访问权限,只能在同一个包下访问。这和外部类有点不同,外部类只能通过public和包访问来修饰。

  2.局部内部类

  局部内部类是在方法或范围中定义的类。它不同于成员内部类,因为局部内部类的访问仅限于方法或范围。

  阶级人民{

  公众人物(){

  }

  }

  阶级男人{

  public Man(){

  }

  public People getWoman(){

  Class Woman扩展了People{ //局部内部类

  int age=0;

  }

  return new Woman();

  }

  }注意,局部内部类就像方法中的局部变量一样,不能有public、protected、private和static修饰符。

  3.匿名内部类

  匿名内部类应该是我们写代码时最常用的。在编写事件监控的代码时使用匿名内部类不仅方便,而且使代码更易于维护。以下代码是Android事件监控代码:

  scan _ Bt . setonclicklistener(new OnClickListener(){

  @覆盖

  公共void onClick(视图v) {

  //TODO自动生成的方法存根

  }

  });

  history _ Bt . setonclicklistener(new OnClickListener(){

  @覆盖

  公共void onClick(视图v) {

  //TODO自动生成的方法存根

  }

  });这段代码为两个按钮设置了监听器,其中使用了匿名内部类。在这段代码中:

  new OnClickListener() {

  @覆盖

  公共void onClick(视图v) {

  //TODO自动生成的方法存根

  }

  }是匿名内部类的使用。在代码中,需要为按钮设置一个侦听器对象。使用匿名内部类可以在实现父类或接口中的方法的同时产生一个对应的对象,但前提是父类或接口必须存在,才能这样使用。当然,像下面这样写也是可以的,和上面使用匿名内部类的效果一样。

  私有void setListener()

  {

  scan _ Bt . setonclicklistener(new listener 1());

  history _ Bt . setonclicklistener(new listener 2());

  }

  类Listener1实现视图。OnClickListener{

  @覆盖

  公共void onClick(视图v) {

  //TODO自动生成的方法存根

  }

  }

  类Listener2实现视图。OnClickListener{

  @覆盖

  公共void onClick(视图v) {

  //TODO自动生成的方法存根

  }

  }这种写法虽然也能达到同样的效果,但是冗长且难以维持。因此,一般使用匿名内部类来编写事件监控代码。类似地,匿名内部类不能有访问修饰符和静态修饰符。

  匿名类是唯一没有构造函数的类。因为它没有构造函数,匿名内部类的作用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译时被系统自动命名为Outter$1.class。

  一般来说,匿名内部类用于继承其他类或实现接口,不需要添加额外的方法,只需要实现或重写继承的方法即可。

  4.静态内部类

  静态类也是在另一个类中定义的类,只是在类前面添加了一个关键字static。静态类不需要依赖外部类,类似于类的静态成员属性,不能使用外部类的非静态成员变量或方法。这很容易理解,因为当没有外部类的对象时,您可以创建静态内部类的对象。如果允许访问外部类的非静态成员,将会产生冲突,因为外部类的非静态成员必须附加到特定的对象。

  公共类测试{

  公共静态void main(String[] args) {

  出去吧。Inner inner=new Outter。inner();

  }

  }

  类别输出器{

  公共Outter() {

  }

  静态类内部{

  公共内部(){

  }

  }

  }

  二.深入理解内部类

  1.为什么成员内部类可以无条件访问外部类的成员?

  在此之前,我们已经讨论过了成员内部类可以无条件访问外部类的成员,那具体究竟是如何实现的呢?下面通过反编译字节码文件看看究竟。事实上,编译器在进行编译的时候,会将成员内部类单独编译成一个字节码文件,下面是Outter.java的代码:

  公共类Outter {

  私有内部内部=空

  公共Outter() {

  }

  公共内部getInnerInstance() {

  if(inner==null)

  Inner=new Inner();

  返回内部;

  }

  受保护的内部类{

  公共内部(){

  }

  }

  }编译之后,出现了两个字节码文件:

  反编译outter$inner。班级文件得到下面信息:

  e:\ Workspace \ Test \ bin \ com \ cxh \ Test 2 javap-v Outter $ Inner

  编译自" Outter.java "

  公共类com . cxh . test2 . outterInner扩展java.lang.Object

  源文件:" Outter.java "

  内部类:

  # 24=# 22之首;//Inner=class com/cxh/test 2/Outterclass com/cxh/tes的内部

  t2/Outter

  次要版本:0

  主要版本:50

  常量池:

  const # 1=class # 2;//com/cxh/test2/OutterInner

  const # 2=Asciz com/cxh/test 2/Outter $ Inner;

  const # 3=class # 4;//java/lang/Object

  const # 4=Asciz Java/lang/Object;

  const # 5=Asciz this $ 0;

  const # 6=Asciz Lcom/cxh/test 2/Outter;

  const # 7=Asciz init

  const # 8=Asciz(Lcom/cxh/test 2/Outter;)V;

  const #9=Asciz代码;

  常量#10=字段#1。#11;//com/cxh/test 2/Outterinner。这$0:Lcom/cxh/t

  est 2/out ter;

  常数# 11=和类型# 5:# 6;//this $ 0:Lcom/cxh/test 2/Outter;

  常数#12=方法#3。#13;//java/lang/Object .init:()V

  常数# 13=和类型# 7:# 14;//init:()V

  const # 14=Asciz()V;

  const # 15=Asciz LineNumberTable

  const # 16=as ciz局部变量表;

  const # 17=Asciz this

  const # 18=Asciz Lcom/cxh/test2/Outter $ Inner;

  const #19=Asciz源文件;

  const # 20=Asciz Outter.java;

  const # 21=Asciz内部类

  const # 22=class # 23//com/cxh/test2/Outter

  const # 23=Asciz com/cxh/test 2/Outter;

  const # 24=Asciz Inner

  {

  最终通信。cxh。测试2。out ter这个$ 0;

  公共通讯。cxh。测试2。outter $ Inner(com。cxh。测试2。out ter);

  代码:

  堆栈=2,局部变量=2,参数大小=2

  0: aload_0

  1: aload_1

  2:putfield # 10;//Field this $ 0:Lcom/cxh/test 2/Outter;

  5: aload_0

  6:调用特殊# 12;//方法Java/语言/对象.init:()V

  9:返回

  行号表:

  第16行:0

  第18行:9

  本地变量表:

  开始长度槽名签名

  0 10 0 this Lcom/cxh/test 2/Outter $ Inner;

  }第11行到35行是常量池的内容,下面逐一第38行的内容:

  最终通信。cxh。测试2。out ter这个$ 0;这行是一个指向外部类对象的指针,看到这里想必大家豁然开朗了。也就是说编译器会默认为成员内部类添加了一个指向外部类对象的引用,那么这个引用是如何赋初值的呢?下面接着看内部类的构造器:

  公共通讯。cxh。测试2。outter $ Inner(com。cxh。测试2。out ter);从这里可以看出,虽然我们在定义的内部类的构造器是无参构造器,编译器还是会默认添加一个参数,该参数的类型为指向外部类对象的一个引用,所以成员内部类中的出此0指针便指向了外部类对象,因此可以在成员内部类中随意访问外部类的成员。

  从这里也间接说明了成员内部类是依赖于外部类的,如果没有创建外部类的对象,则无法对出此0引用进行初始化赋值,也就无法创建成员内部类的对象了。

  2.为什么局部内部类和匿名内部类只能访问局部final变量?

  想必这个问题也曾经困扰过很多人,在讨论这个问题之前,先看下面这段代码:

  公共类测试{

  公共静态void main(String[] args) {

  }

  公共无效测试(最终int b) {

  final int a=10

  新线程(){

  公共无效运行(){

  system . out . println(a);

  system . out . println(b);

  };

  }.start();

  }

  }这段代码将被编译成两个类文件:Test.class和test1.class .默认情况下,编译器将匿名内部类和局部内部类命名为outer 1 . class默认情况下,编译器将匿名内部类和局部内部类命名为outerx.class (x为正整数)。

  根据上图,测试方法中的匿名内部类被命名为Test$1。

  在前面的代码中,如果删除变量A和B前面的任何final,这段代码将不会编译。我们先考虑这个问题:

  在执行测试方法的时候,变量A的生命周期会结束,而Thread对象的生命周期很可能还没有结束,所以在Thread的run方法中继续访问变量A就变得不可能了,但是要怎样做才能达到这样的效果呢?Java采用了复制的方法来解决这个问题。反编译这段代码的字节码可以得到以下内容:

  我们看到run方法中有一条指令:

  bipush 10指令表示将操作数10压入堆栈,表示使用了局部变量。默认情况下,这个过程由编译器在编译期间执行。如果在编译时可以确定这个变量的值,编译器会在匿名内部类(局部内部类)的常量池中添加一个内容相等的文字,或者直接将对应的字节码默认嵌入到执行字节码中。这样,匿名内部类使用的变量是另一个局部变量,但它的值等于方法中局部变量的值,所以它完全独立于方法中的局部变量。

  这是另一个例子:

  公共类测试{

  公共静态void main(String[] args) {

  }

  公共无效测试(最终int a) {

  新线程(){

  公共无效运行(){

  system . out . println(a);

  };

  }.start();

  }

  }反编译得到:

  我们看到匿名内部类Test$1的构造函数包含两个参数,一个是对外部类对象的引用,另一个是int变量。显然,变量测试方法中的参数A是作为参数传入的,用来初始化匿名内部类中副本(变量A的副本)的赋值。

  也就是说,如果一个局部变量的值可以在编译时确定,那么就会直接在匿名内部创建一个副本。如果在编译期间无法确定局部变量的值,则副本将由构造函数初始化并赋值。

  从上面可以看出,run方法中访问的变量A根本不是test方法中的局部变量A。这样就解决了上面提到的生命周期不一致的问题。

  但是新的问题又来了。既然run方法中访问的变量A和test方法中的变量A不一样,那么在run方法中改变变量A的值会怎么样?

  是的,会造成数据不一致,以至于达不到初衷和要求。为了解决这个问题,java编译器将变量A限制为final变量,不允许变量A被改变(对于引用类型变量,不允许指向新的对象),从而解决了数据不一致的问题。

  至此,大家一定很清楚为什么方法中的局部变量和参数必须用final限定。

  3.静态内部类有特殊的地方吗?

  由前述可知,静态内部类是独立于外部类的,也就是说,内部类的对象可以在不创建外部类对象的情况下创建。此外,静态内部类不包含对外部类对象的引用。这位读者可以自己试着反编译一下类文件看看。没有到此0外部的引用。

  三.内部类的使用场景和好处

  为什么在Java中需要内部类?总结一下主要有以下四点:

  1.每个内部类都可以独立地继承一个接口的实现,所以外部类是否继承了一个接口的实现对内部类没有影响。类使多继承解决方案变得完整,

  2.方便把有一定逻辑关系的类组织在一起,也可以对外隐藏。

  3.写事件驱动很方便。

  4.写线程代码很方便。

  四.常见的与内部类相关的笔试面试题

  1.根据注释填写(1)、(2)和(3)处的代码。

  公共类测试{

  公共静态void main(String[] args){

  //初始化Bean1

  (1)

  bean1。我;

  //初始化Bean2

  (2)

  bean2。j;

  //初始化Bean3

  (3)

  bean3.k

  }

  类别Bean1{

  public int I=0;

  }

  静态类Bean2{

  public int J=0;

  }

  }

  类Bean{

  类别Bean3{

  public int k=0;

  }

  }从前面可以看出,对于成员的内部类,必须先生成外部类的实例化对象,然后才能生成内部类的实例化对象。但是,静态内部类可以生成内部类的实例化对象,而不生成外部类的实例化对象。

  创建静态内部类对象的一般形式是:外部类名,内部类名xxx=新建外部类名,内部类名()

  创建成员内部类对象的一般形式是:外部类名。内部类名xxx=外部类名。新的内部类名()

  因此,在(1)、(2)和(3)处的代码是:

  测试Test=new Test();

  测试。bean 1 bean 1=test . new bean 1();测试。Bean2 b2=新测试。bean 2();Bean Bean=new Bean();

  比恩。bean 3 bean 3=bean . new bean 3();2.以下代码的输出结果是什么?

  公共类测试{

  公共静态void main(String[] args) {

  Outter Outter=new Outter();

  outter.new Inner()。print();

  }

  }

  班级人数

  {

  private int a=1;

  内部类{

  private int a=2;

  公共作废打印(){

  int a=3;

  System.out.println(局部变量: a );

  System.out.println(内部类变量: this . a );

  System.out.println(外部类变量: outter . this . a );

  }

  }

  }最后补充一点知识:关于成员内部类的继承。一般来说,内部类很少用于继承。但是当用于继承时,有两点需要注意:

  1)成员内部类的引用方法必须是Outter。内心.

  2)构造函数中必须有对外部类对象的引用,通过这个引用调用super()。

  内部类{

  内部类{

  }

  }

  类InheritInner在内部扩展。内部{

  //InheritInner()无法编译,所以一定要添加形参。

  InheritInner(WithInner wi) {

  wi . super();//肯定有这个电话

  }

  公共静态void main(String[] args) {

  within ner wi=new within ner();

  inherit inner obj=new inherit inner(wi);

  }

  }更多java知识请关注java基础教程部分。以上是java内部类讲解的详细内容(附相关面试问题)。更多请关注我们的其他相关文章!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: