Java 相关细节问题(持续更新)

类中静态代码块、构造块、构造方法的执行顺序

静态代码优先于非静态的代码,是因为被static修饰的成员都是类成员,会随着JVM加载类的时候加载而执行,而没有被static修饰的成员也被称为实例成员,需要创建对象才会随之加载到堆内存。所以:静态块>非静态构造块>构造函数。

下面来讨论当构造函数中包含(this(),super())时,这些方法的调用顺序:

执行构造器(构造方法)的时候,在执行方法体之前存在隐式三步:

1,super语句,可能出现以下三种情况:
1)构造方法体的第一行是this语句,则不会执行隐式三步,
2)构造方法体的第一行是super语句,则调用相应的父类的构造方法,
3)构造方法体的第一行既不是this语句也不是super语句,则隐式调用super(),即其父类的默认构造方法,这也是为什么一个父类通常要提供默认构造方法的原因;
2,初始化非静态变量;
3,构造代码块。
由此可知,构造代码块优先于构造方法的方法体,但是this关键字跟super关键字不能同时出现,而且只能在代码的第一行。如果出现了this关键字,隐式三步就不会执行。

public class Demo {
     public static int a = 0;
     static {
            a = 10;
            System.out.println("静态代码块在执行a=" + a);
        }

        {
            a = 8;
            System.out.println("非静态代码块在执行a=" + a);
        }


        public Demo() {

            this("带参构造方法在执行a="+a); // 调用另外一个构造方法
            System.out.println("无参构造方法开始执行:");
            System.out.println("无参构造方法在执行a=" + a);
            System.out.println("无参构造方法执行完毕!");
        }


        public Demo(String n) {
            System.out.println("有参构造方法开始执行:");
            System.out.println(n);
            System.out.println("有参构造方法执行完毕!");
        }


        public static void main(String[] args) {
            Demo t = null;
            System.out.println("!!!!!!!!!!!!!!!!!!!!!");
            t = new Demo();
        }
}
/**
静态代码块在执行a=10
!!!!!!!!!!!!!!!!!!!!!
非静态代码块在执行a=8
有参构造方法开始执行:
带参构造方法在执行a=10
有参构造方法执行完毕!
无参构造方法开始执行:
无参构造方法在执行a=8
无参构造方法执行完毕!
**/
  1. 构造代码块优先于构造方法的方法体。但此处的无参构造函数首行的 this() 语句会先于构造块执行。当通过this调用有参构造时,参数已经传入有参构造函数 public Demo(String n) ,即此时 a==10;
  2. 然后执行构造代码块(在调用了有参构造会依然遵循:构造代码块优先于构造方法的方法体),修改 a=8 并输出。

参考

Java静态代码块、构造代码块、构造方法的执行顺序

简单java类的一对多映射关系

这里使用公司部门和雇员之间的一对多的关系映射,主要记录代码实现细节以及注意事项。

class Dept {
    private String name ;
    private int num ;
    private String loc ;
    private Emp emps[] ;

    public Dept(String name,int num,String loc){
        this.name = name ;
        this.num = num ;
        this.loc = loc ;
    }

    /***********************************/
    public void setEmps(Emp [] emps){           // 此处设置数组时,不学先行实例化数组,同时给出数组长度
    this.emps = new Emp [emps.length] ;
        for(int i = 0; i<emps.length; i++){
            this.emps[i] = emps[i] ;
        }
    }
    /**************************************/

    public Emp[] getEmps(){
        return this.emps ;
    }
    public String getInfo(){
        return "部门编号:" + this.num + ", 部门名称:" + this.name + ", 部门地址:" + this.loc ;
    }
}

class Emp{
    private String name ;
    private String job ;
    private int num ;
    private double comm ;
    private double sal ;
    private Dept dept ;
    private Emp mgr ;

    public Emp(int num,String name,String job,double comm,double sal){
        this.name = name ;
        this.num = num ;
        this.job = job ;
        this.sal = sal ;
        this.comm = comm ;
    }
    public void setMgr(Emp mgr){
        this.mgr = mgr ;
    }
    public void setDept(Dept dept){
        this.dept = dept ;
    }

    public Emp getMgr(){
        return this.mgr ;
    }
    public Dept getDept(){
        return this.dept ;
    }

    public String getInfo(){
        return "雇员编号:" + this.num + ", 雇员姓名:" + this.name + ", 雇员工作:" + this.job + ", 雇员工资:" + this.sal + ", 雇员奖金:" + this.comm;
    }
}

public class Demo {
    public static void main(String args[]) {
    // 第一步:根据数据表结构设置数据
        // 产生各自独立对象
        Dept Dept_Account = new Dept("ACCOUNTING",10,"ShangHai") ;
        Emp ea = new Emp(7369,"Smith","CLERK",800.00,0.00) ;
        Emp eb = new Emp(7902,"Ford","MANAGER",2450.00,0.00) ;
        Emp ec = new Emp(7839,"King","PRESIDENT",8000.00,0.00) ;
        // 设置雇员和领导关系
        ea.setMgr(eb) ;
        eb.setMgr(ec) ;
        // 设置关于和部门关系
        ea.setDept(Dept_Account) ;
        eb.setDept(Dept_Account) ;
        ec.setDept(Dept_Account) ;

        Dept_Account.setEmps(new Emp[]{ea,eb,ec}) ;
    // 第二步:根据数据表描述取得设置的数据   
        System.out.println(ea.getInfo()) ;
        System.out.println("\t-|" + ea.getMgr().getInfo()) ;
        System.out.println("\t-|" + ea.getDept().getInfo()) ;

        System.out.println(Dept_Account.getInfo()) ;
        /**************************************/
        for(int i = 0 ; i < Dept_Account.getEmps().length ; i ++){              // 循环查找,代码链实现
            System.out.println("\t-|" + Dept_Account.getEmps()[i].getInfo()) ;
            if( Dept_Account.getEmps()[i].getMgr() != null ){
                System.out.println("\t\tMGR: -|" + Dept_Account.getEmps()[i].getMgr().getInfo()) ;
            }
        }
        /**************************************/
    }   
}

运行结果:
这里写图片描述

Java为什么不支持泛型数组

在编写Java列表(泛型类型)的时候,链表转数组功能出现以下错误:

Note: LinkDemo.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

然后使用 javac -Xlint LinkDemo.java 进行编译,出现如下错误:

LinkDemo.java:77: warning: [unchecked] unchecked cast
this.retArray = (T[])new Object[this.count] ;
^
required: T[]
found: Object[]
where T is a type-variable:
T extends Object declared in class Link
LinkDemo.java:163: warning: [overrides] Class Student overrides equals, but neither it nor any superclass overrides hashCode method
class Student {
^
2 warnings

发现是java不支持泛型数组所致。

原因,转自知乎

根本的原因是:数组在创建的时候必须知道内部元素的类型,而且一直都会记得这个类型信息,每次往数组里添加元素,都会做类型检查。

但因为Java泛型是用擦除(Erasure)实现的,运行时类型参数会被擦掉。比如:

List<String> l = new ArrayList<String>();
l.add("hello");
String str=l.get(0);

运行时,类型参数<String>都被擦掉,只有在最后读取内部元素的时候,才插入一个类型转换。看起来就像下面这样,

List l = new ArrayList<String>();
List.add("hello");
String str=(String)List.get(0);

所以,如果像下面这样初始化泛型数组的话,

List<String>[] l = new ArrayList<String>[10];    //Error

运行时编译器只能看到ArrayList,而看不到泛型的String类型参数。数组由于无法确定所持有元素的类型,所以不允许初始化。

Java Language Specification明确规定:数组内的元素必须是“物化”的。

It is a compile-time error if the component type of the array being initialized is not reifiable.

对“物化”的第一条定义就是不能是泛型:

A type is reifiable if and only if one of the following holds:

    It refers to a non-generic class or interface type declaration.

    ... ...

因为Array的具体实现是在虚拟机层面,嵌地非常深,也查不到源码。只好用javap反编译看看具体初始化数组的字节码。我们反编译下面一段代码:初始化一个String数组和一个int数组。

String[] s=new String[]{“hello”};
int[] i=new int[]{1,2,3};

反编译的片段如下:

Code:
   0: iconst_1
   1: anewarray     #2                  // class java/lang/String
   4: dup
   5: iconst_0
   6: ldc           #3                  // String hello
   8: aastore
   9: astore_1
  10: iconst_3
  11: newarray       int
  13: dup
  14: iconst_0
  15: iconst_1
  ... ...

其中:

"1: anewarray    #2":创建String数组
"11: newarray    int":创建int数组

anewarray和newarray都是虚拟机内部用来创建数组的命令。最多只能有2的8次方256个操作码,光创建数组就占了不止一个,可见数组的地位有多特殊。
其中newarray用atype来标记数组类型。anewarray用index来标记。从描述里可以看到,数组除了元素类型,还有一个必须确定的是长度,因为数组是一段连续内存。

查一下 Java Virtual Machine 对anewarray命令的描述,

anewarray <type>
<type> indicates what types of object references are to be stored in the array. It is either the name of a class or interface, e.g. java/lang/String, or, to create the first dimension of a multidimensional array, <type> can be an array type descriptor, e.g.[Ljava/lang/String;

比如anewarray字节码命令的格式就是anewarray后面跟一个具体的元素类型。所以不能确定的确切类型,就无法创建数组。

最初在还没有泛型的时候,数组就一直是以“The Special One”的形象出现。那个时候所有容器都还只是持有Object。在其他容器都不太关心类型安全的年代,数组就特立独行地坚持类型检查,它的使命就是提供一个“类型安全”和效率更高的容器。所以类型检查和长度限制都被写到了字节码的规范里。至于到了支持泛型的年代,泛型“泛化”的本质就和数组“精确高效”的主旨根本上是相违背的。而且要改的话就要在字节码里动刀了。还是历史包袱的问题。

参考

java为什么不支持泛型

相关学习网站

博客:
Luc–博客园–Google

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值