【Java基础】java内部类构造方法

2021-01-29 21:04发布

4条回答
我自己打call
2楼 · 2021-02-02 09:38

与人讨论匿名内部类的构造方法问题,自己写代码看看原理到底是什么样子的。因为类是匿名的,所以就无从创建一个同名的构造方法了。但是可以直接调用父类的构造方法。测试代码如下:
Java代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

package testtest;  

   

public class Main {  

   

    public static void main(String[] args) {  

        InnerTest inner = new InnerTest();  

        Test t = inner.get(3);  

        System.out.println(t.getI());  

    }  

}  

   

class Test {  

   

    private int i;  

   

    public Test(int i) {  

        this.i = i;  

    }  

   

    public int getI() {  

        return i;  

    }  

}  

   

class InnerTest {  

   

    public Test get(int x) {  

        return new Test(x) {  

   

            @Override 

            public int getI() {  

                return super.getI() * 10;  

            }  

        };  

    }  

}

编译之后得到4个class文件:Test.class,InnerTest.class,InnerTest$1.class以及Main.class。容易看出来,Main.class是测试类的class文件,Test.class是超类Test的class文件,InnerTest.class是InnerTest 的class文件,最值得关注的就是匿名内部类的class文件InnerTest$1.class。

首先javap -c InnerTest$1

Java代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

Compiled from "Main.java" 

class testtest.InnerTest$1 extends testtest.Test{  

final testtest.InnerTest this$0;  

   

testtest.InnerTest$1(testtest.InnerTest, int);  

  Code:  

   0:   aload_0  

   1:   aload_1  

   2:   putfield    #1; //Field this$0:Ltesttest/InnerTest;  

   5:   aload_0  

   6:   iload_2  

   7:   invokespecial   #2; //Method testtest/Test."

很明显,虽然我们看来是匿名内部类,但编译的时候给这个类指定了名字

InnerTest$1,而且看出来是继承自Test:

Java代码

1

class testtest.InnerTest$1 extends testtest.Test

而且在这个类有构造方法: 

Java代码

1

testtest.InnerTest$1(testtest.InnerTest, int);

这里也很容易理解,两个参数,一个是匿名内部类的外部类引用直接传了进来,这也是我们能在内部类中直接访问外部类成员的实现原理。另外一个就是int类型的参数了。也就是说其实编译器自动的给我们添加了带参数的构造方法。继续往下看: 
7: invokespecial #2; //Method testtest/Test."

20200921文 - 做更棒的自己!
3楼 · 2021-02-02 11:00

因为匿名内部类没有类名(至少是明面上没有),不然为何叫匿名? 构造器是要有类名的。 不过有种方式可以起到构造器的作用,但有局限性,那就是“构造代码块”

我的网名不再改
4楼 · 2021-02-04 10:08

通过反射,我们可以获得一个类运行时的信息(属性,构造方法和普通方法),但是当我们是使用反射区获取一个类的内部类的构造方法时,会发现内部类默认的无参构造中会出现父类类型的参数

测试代码:

public class OuterClass { class InnerClass{ }}

注意:Class.forName()中的类名不能使用eclipse中的Copy qualified Name 来获取,应为内部类编译后会产生两个文件,具体做法是:到工程的根目录下,找到bin文件夹,java文件编译后的calss文件都放在这个目录下,找到编译后的内部类的class文件,复制类名,然后在前面加上包名即可

import java.lang.reflect.Constructor;public class Test {  public static void main(String[] args) {  try {   Class clazz=Class.forName("reflect.OuterClass$InnerClass");   Constructor[] constructors = clazz.getDeclaredConstructors();   for(Constructor c:constructors){    System.out.println(c);   }  } catch (ClassNotFoundException e) {   // TODO Auto-generated catch block   e.printStackTrace();  } }}

结果:

使用反射获取内部类的构造方法时,获取的并不时它的无参构造,而是带有父类类型的参数,具体原因如下

编译后的文件:

编译后产生了三个文件,外部类中的内部类编译后会产生一个  外部类类名$内部类类名.calss的文件

使用javap反编译命令对这两个文件进行反编译

OuterClass.class

OuterClass$InnerClass.class

可以看到,外部类中有一个public reflect.OuterClass的无参构造

在看内部类,是一个final reflect.OuterClass this$0 和 reflect.OuterClass$InnerClass(reflect.OuterClasss)的构造函数

 

对于这个貌似无法从JDK中查看原因,就来阐述一下我的理解,又不对的地方还望指教

对于final reflect.OuterClass this$0 我个人认为,内部类在编译后会与外部类分离,这个final类的的reflect.OuterClass类型的变量this$0 应该是一个与外部类建立联系的表示,而reflect.OuterClass$InnerClass(reflect.OuterClasss)可以从创建外部类的创建入手如果想要在外部类的外部获得内部类的对象,需要进行如下声明:OuterClass.InnerClass innerClass=new OuterClass().new InnerClass() ,可以看出,想要获得内部类对象需要先new OuterClass(),然后再new InnerClass(),在可以获得一个内部类的对象,这也就就是了为甚麽获得一个内部类需要先new出一个外部类,因为内部类中的默认构造中有一个外部类类型的参数

验证:

static关键字可以用于修饰变量和方法,代码块等,不能修饰再类上,但是并不是绝对的,static关键字是可以修饰内部类,且用static关键字修饰的内部类和不同内部类也有很大的不同

static关键字修饰内部类

代码:

public class OuterClass { static class InnerClass{ }}

静态内部类(InnerClass)意味着创建一个static内部类的对象,不需要一个外部类对象,不能从一个static内部类的一个对象访问一个外部类对象

测试代码:

public class Main { public static void main(String[] args) {  InnerClass in1 = new InnerClass();//或  OuterClass.InnerClass in2 = new OuterClass.InnerClass(); }}

使用以上两种方式都可以获得内部类的实例,但什么可以这样写?

对其进行反编译:

OuterClass$InnerClass.class

反编译之后,外部类并没有什么太大变化,但是内部类和之前有明显的差别

final reflect.OuterClass this$0 已经没有了,而且reflect.OuterClass$InnerClass(reflect.OuterClasss)也变成了reflect.OuterClass$InnerClass(),reflect.OuterClasss类型的参数已经没有了,

而此时可以用InnerClass in1 = new InnerClass();对其进行实例化,比没有依赖于外部类

OuterClass.InnerClass in2 = new OuterClass.InnerClass();也并没有先new OuterClass()这一操作

这也就验证了上面我的猜想

对于final reflect.OuterClass this$0 ,内部类在编译后会与外部类分离,这个final类的的reflect.OuterClass类型的变量this$0 应该是一个与外部类建立联系的表示,而reflect.OuterClass$InnerClass(reflect.OuterClasss)可以从创建外部类的创建入手,如果想要在外部类的外部获得内部类的对象,需要进行如下声明:OuterClass.InnerClass innerClass=new OuterClass().new InnerClass() ,可以看出,想要获得内部类对象需要先new OuterClass(),然后再new InnerClass(),在可以获得一个内部类的对象,这也就就是了为甚麽获得一个内部类需要先new出一个外部类,因为内部类中的默认构造中有一个外部类类型的参数


路小雨xiaoyu
5楼 · 2021-03-11 09:42

构造方法 Constructor

    1.构造方法与类名相同,没有返回值,连void都没有


    2.具有可见性


3 .不写时,编译器默认生成一个无参构造方法


    4.将所有实例属性初始化为0


5.如果程序编写了一个构造方法,而不再自动生成构造方法


 


6.无参构造方法


      1>无参构造方法可以方便地创建对象


            无论是人工创建还是通过反射创建都很方便


      2>无参构造方法创建的对象,属性值要么是0,要么是n


通常在使用之前还需要再次给属性赋一个有用的值


 


   在java中有一个概念---bean


     bean类要求有一个无参构造方法


     可以使用工具自动创建对象


 


1.构造器必须与类同名(如果一个源文件中有多个类,那么构造器必须与公共类同名)


2.每个类可以有一个以上的构造器


3.构造器可以有0个、1个或1个以上的参数


4.构造器没有返回值


5.构造器总是伴随着new操作一起调用


 


Getter/Setter访问器方法


目的是不让属性直接暴露出来,而是通过方法(getter/setter)间接地访问属性


 好处是:


    可以控制只读、只写


    还可以在赋值时进行转换、验证等操作


    可以在获取值时进行转换或修改


 20170708181742365.png


继承


   没有指定父类,默认都从Object类继承


   使用extends关键字可以指定父类


 


  如果要覆盖或重写父类的方法,最好标记@Override注解(Annotation)


注解是可以附加到类元数据上的对象


   反射是获取类元数据信息的编程接口


 


抽象类


用abstract标记


不需要实例化,只为继承而存在


 


抽象方法


    不为调用,只为继承


     用abstract标记,没有方法体!


抽象类中既可以有抽象方法也可以有非抽象方法!


 


在继承体系中子类可以通过super关键字访问父类


    调用父类构造方法


         Super()


    调用本类构造方法


         This()


访问父类的属性


       Super.属性名


 调用父类的方法


       Super.方法名()


 


 


 


如果父类有无参构造方法,子类可以不显式调用super(),如果没有无参构造方法,必须手动显式调用super(xxx),否则报编译错误。


当父类同时存在无参和有参构造方法时,默认调用无参构造方法。


当未写任何构造方法时,编译期自动帮你添加一个无参构造方法,其他同上。


编译器是会自动在子类的构造行数中, 在首行添加"super();", 这么一个语句,用来初始化父类的数据;


如果没有的话, 编译的时候,无法找到父类的无参构造函数,就无法绑定运行时候调用哪个父类的构造函数,编译的时候就会报错啦;


所以记得在以前学习的时候, 很多书籍都是让重载构造函数的时候, 记得添加一个无参的构造函数



相关问题推荐

  • 回答 156

    对于每一位才开始接触JAVA的新手来说,先不要管算法和数据结构,大多数简单的程序不需要用到算法和数据结构,所以当你真正需要时再去学习。编程一段时间以后,你就会知道在哪些地方用到他们。这时知道算法的名字并了解它们的功能,然后动手去实践。当我们在去...

  • 回答 93

    2个都很好就业,更关键的是要学得到东西

  • 回答 12
    已采纳

    获取Map集合中所有的key可以通过map集合的keySet()方法获取例如:    Map map = new HashMap();    map.put(xx,xx); //存放数据    //.... 省略    Set set = map.keySet();    //可以通过迭代器进行测试    Iterator iter = set.iter...

  • 回答 56
    已采纳

    不同年龄,不同掌握程度,学历,找工作城市,面试能力这是一个多方面影响的结果,如果是平均值的话,全国平均薪资14k左右

  • 回答 38

    具体学多久,根据自己的学习力,自律性、解决问题能力来决定若系统性学习,跟着讲师的节奏走,大概半年左右,有专业的讲师把课程进行规划,尽心系统学习,有问题,讲师会帮忙解决,学习的效率很高,避免了自学中出现各种问题解决不了,而耽误很多时间,可能会...

  • 回答 23
    已采纳

    (1)idea启动时会有两个快捷方式,安装完后默认生成在桌面的是32位的idea的快捷方式,如果我们使用这个快捷方式运行大项目,一般都会很卡。解决方法是找到idea的安装目录,然后进入bin文件夹,找到名称为idea64的应用程序,右键他生成桌面快捷方式。以后每次...

  • BIO与NIO、AIO的区别2020-05-19 15:59
    回答 4
    已采纳

    IO的方式通常分为几种,同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO。一、BIO     在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要...

  • Java方法的命名规则2021-04-06 19:07
    回答 31

    ava是一种区分字母的大小写的语言,所以我们在定义变量名的时候应该注意区分大小写的使用和一些规范,接下来我们简单的来讲讲Java语言中包、类、变量等的命名规范。(一)Package(包)的命名Package的名字应该都是由一个小写单词组成,例如com、xuetang9、compan...

  • 回答 2

    public class Point {    private int x;    private int y;    public int getX() {        return x;    }    public void setX(int x) {        this.x = x;    }    public int getY() {        return y;    } ...

  • 回答 6

    经典版单例模式public class Singleton {        private static Singleton uniqueInstance;//利用一个静态常量来记录singleton类的唯一实例。     private Singleton() {     }     public static  Singleton getInstance()...

  • 回答 3

    哈希表的长度一般是定长的,在存储数据之前我们应该知道我们存储的数据规模是多大,应该尽可能地避免频繁地让哈希表扩容。但是如果设计的太大,那么就会浪费空间,因为我们跟不用不到那么大的空间来存储我们当前的数据规模;如果设计的太小,那么就会很容易发...

  • 回答 14

    1. DOM(Document Object Model)        DOM是用与平台和语言无关的方式表示XML文档的官方W3C标准。DOM是以层次结构组织的节点或信息片断的集合。这个层次结构允许开发人员在树中寻找特定信息。分析该结构通常需要加载整个文档和构造层次结构,然后才...

  • 回答 19

    1)作用不同: throw用于程序员自行产生并抛出异常; throws用于声明在该方法内抛出了异常2) 使用的位置不同: throw位于方法体内部,可以作为单独语句使用; throws必须跟在方法参数列表的后面,不能单独使用。3)内容不同: throw抛出一个异常对象,且只能是...

  • 回答 11

    基本执行过程如下:1)程序首先执行可能发生异常的try语句块。2)如果try语句没有出现异常则执行完后跳至finally语句块执行;3)如果try语句出现异常,则中断执行并根据发生的异常类型跳至相应的catch语句块执行处理。4)catch语句块可以有多个,分别捕获不同类型...

  • 回答 20

    100-199 用于指定客户端应相应的某些动作。 200-299 用于表示请求成功。 300-399 用于已经移动的文件并且常被包含在定位头信息中指定新的地址信息。 400-499 用于指出客户端的错误。 400 语义有误,当前请求无法被服务器理解。 401 当前请求需要用户验证...

  • 回答 16

    异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译...

没有解决我的问题,去提问