精华内容
下载资源
问答
  • JAVA 成员初始化顺序和内存分配过程

    千次阅读 2017-05-25 09:53:21
    成员初始化顺序属性、方法、构造方法和自由块都是类的成员,在创建类的对象时,类各成员的执行顺序: 父类静态成员和静态初始化快,按在代码出现的顺序依次执行。 子类静态成员和静态初始化块,按在代码出现...

    成员初始化顺序

    属性、方法、构造方法和自由块都是类中的成员,在创建类的对象时,类中各成员的执行顺序:

    1. 父类静态成员和静态初始化快,按在代码中出现的顺序依次执行。
    2. 子类静态成员和静态初始化块,按在代码中出现的顺序依次执行。
    3. 父类的实例成员和实例初始化块,按在代码中出现的顺序依次执行
    4. 执行父类的构造方法。
    5. 子类实例成员和实例初始化块,按在代码中出现的顺序依次执行。
    6. 执行子类的构造方法。

    最后,生成对象由main线程调用

    父静态->子静态->父变量->父初始化区->父构造–>子变量->子初始化区->子构造

    new ClassName();

    创建 ClassName类的一个实例时:

    1. 解释器截取new这个关键字时,就会为ClassName量身定做一个内存空间,这个时候也就是为该类中的所有成员变量分配内存空间之时
    2. 然后按照前面的顺序进行初始化,所有引用类型将其制成null 基本数据类型为0;
    3. 之后解释器会继续解释执行到 ClassName();这句话,也就是该类的构造器,调用指定的类的构造方法(根据用户的需求初始化对象)。
    4. 然而这里面有一种成员变量并不完全在此过程中被初始化,此成员变量为静态成员变量,它是在当类静态属性或方法第一次被调用或者该类第一次被创建对象时被初始化。

    父子类的创建和初始化过程

    当我们试图去创建一个子类时,java解释器发现该类继承了其他类,所以就会先去创建其父类,切记这个时候并没有为子类分配任何的内存空间而是直接越过自己的创建过程去创建父类,如果检查到父类也继承了其他类,java解释器就会依此类推继续创建父类的父类。直到最后一个根父类

    被分配内存后才会创建子类。而构造方法的调用则是从子类开始的,但是在子类的构造方法中必须去调用父类的构造方法,经常性的我们没有看到
    在子类的构造方法中显示的调用父类的构造方法,那是因为解释器隐式的调用了父类默认的无参构造方法,然而当我们通过重载机制将父类的默认
    构造方法覆盖时,那么在子类中就必须显示的调用父类的构造方法

    Java内存管理

    1. 内存分配策略

    按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的。

    静态存储分配

    是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间。

    这种分配策略要求程序代码中

    1. 不允许有可变数据结构(比如可变数组)的存在
    2. 也不允许有嵌套或者递归的结构出现

    因为它们都会导致编译程序无法计算准确的存储空间需求。

    栈式存储分配

    也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的。和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存。和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。

    静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例。堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放。

    2. JVM中的堆和栈

    JVM是基于堆栈的虚拟机。JVM为每个新创建的线程都分配一个堆栈,也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。

    java把内存分两种:一种是栈内存,另一种是堆内存

    栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
    栈(stack):是一个先进后出的数据结构,通常用于保存方法(函数)中的参数局部变量

    堆(heap):是一个可动态申请的内存空间(其记录空闲内存空间的链表由操作系统维护),是一个运行时数据区,C中的malloc语句所产生的内存空间就在堆中。

    3. 堆和栈优缺点比较

    栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享

    堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

    4. Java中的数据类型有两种

    一种是基本类型

    共有8种,即int, short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。

    这种类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量

    值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。

    另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:

      int a = 3; 
      int b = 3

    编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。

    特别注意的是,这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变化。相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。如上例,我们定义完a与b的值后,再令a=4;那么,b不会等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

    另一种是包装类数据

    如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。

    String是一个特殊的包装类数据。即可以用String str = new String(“abc”);的形式来创建,也可以用String str = “abc”;的形式来创建(作为对比,在JDK 5.0之前,你从未见过Integer i = 3;的表达式,因为类与字面值是不能通用的,除了String。而在JDK 5.0中,这种表达式是可以的!因为编译器在后台进行Integer i = new Integer(3)的转换)。前者是规范的类的创建过程,即在Java中,一切都是对象,而对象是类的实例,全部通过new()的形式来创建。Java中的有些类,如DateFormat类,可以通过该类的getInstance()方法来返回一个新创建的类,似乎违反了此原则。其实不然。该类运用了单例模式来返回类的实例,只不过这个实例是在该类内部通过new()来创建的,而getInstance()向外部隐藏了此细节。那为什么在String str = “abc”;中,并没有通过new()来创建实例,是不是违反了上述原则?其实没有。

    5.String在内存中的存放

    String是一个特殊的包装类数据,可以用用以下两种方式创建:

    String str = new String("abc");

    第一种创建方式是用new()来新建对象的,它会存放于中。每调用一次就会创建一个新的对象。

    String str = "abc";

    第二种创建方式先在栈中创建一个对String类的对象引用变量str,然后在栈中查找有没有存放值为”abc”的地址,如果没有,则开辟一个存放字面值为”abc”的地址,接着创建一个新的String类的对象o,并将o的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。如果已经有了值为”abc”的地址,则查找对象o,并返回o的地址,最后将str指向对象o的地址。

    值得注意的是,一般String类中字符串值都是直接存值的。但像String str = “abc”;这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用!

    6.数组在内存中的存放

    int x[] 或者int []x 时,在内存栈空间中创建一个数组引用,通过该数组名来引用数组。
    x = new int[5] 将在内存中分配5个保存int型数据的空间,堆内存的首地址放到栈内存中,每个数组元素被初始化为0。

    7.static变量在内存中的存放

    用 static的修饰的变量和方法,实际上是指定了这些变量和方法在内存中的“固定位置”-static storage。既然要有“固定位置”那么他们的 “大小”似乎就是固定的了,有了固定位置和固定大小的特征了,在栈中或堆中开辟空间那就是非常的方便了。如果静态的变量或方法在不出其作用域的情况下,其引用句柄是不会发生改变的。

    8. java中变量在内存中的分配

    1、类变量(static修饰的变量)

    在程序加载时系统就为它在堆中开辟了内存,堆中的内存地址存放于栈以便于高速访问。静态变量的生命周期一直持续到整个”系统”关闭

    2、实例变量

    当你使用java关键字new的时候,系统在堆中开辟并不一定是连续的空间分配给变量(比如说类实例),然后根据零散的堆内存地址,通过哈希算法换算为一长串数字以表征这个变量在堆中的”物理位置”。 实例变量的生命周期–当实例变量的引用丢失后,将被GC(垃圾回收器)列入可回收“名单”中,但并不是马上就释放堆中内存

    3、局部变量

    局部变量,由声明在某方法,或某代码段里(比如for循环),执行到它的时候在栈中开辟内存,当局部变量一但脱离作用域,内存立即释放


    展开全文
  • JAVA中初始化问题得到了很好的解决,我们需要了解一些初始化的知识来使我们少犯错误。

    在JAVA中,若某个主数据类型属于一个类成员,那么即使不显示初始化,也可以获得一个默认值。

    如下表格:

    主类型 默认值
    Boolean false
    Char '\u0000' (null)
    byte (byte) 0
    short (short) 0
    int  0
    long 0L
    float 0.0f
    double 0.0d

    一旦将变量作为类成员使用,就要注意由Java分配的默认值,这样做的目的是为了保证主类型成员变量得到初始化,有效遏制编程错误。但是在“局部”变量中,则并不会按照上表所示进行分配默认值。例如:

    import java.util.*;
    public class Project1 {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		class Dateonly{
    			int x;
    			float a;
    			void func(){
    				int x;  //此处编译报错
    				System.out.println(x);
    			}
    		}
    		Dateonly d = new Dateonly();
    		d.func();
    	}
    
    }
    对于func函数中的“局部”变量x来说,此时x会得到一些随机值,不会自动初始化为0,我们必须在正式使用x前分配一个适当的值,如果忘记,编译器会报错,所以,在func函数中的x必须初始化。

    对于主数据类型来说,类Dateonly中的x和a均会初始化为上表所示内容。

    在C++中并不会得到编译器发出的警告,但JAVA中明确了这一错误。
    在一个类中,初始化的顺序由变量在类中的顺序有关,即使变量定义分布在方法定义的中间,或者说在构建器内部或之间或外部或结尾,编译器都会在调用任何方法及构建器之前去初始化一遍每个变量。接下来看一段代码:

    import java.util.*;
    class Tag{
    	Tag(int marker){
    		System.out.println("Tag("+marker+")");
    	}
    }
    class Card{
    	Tag t1 = new Tag(1);
    	Card(){
    		System.out.println("Card()");
    		t3 = new Tag(33);
    	}
    	Tag t2 = new Tag(2);
    	void f(){
    		System.out.println("f()");
    	}
    	Tag t3 = new Tag(3);
    }
    public class Project9 {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		Card t =new Card();
    		t.f();
    	}
    
    }
    
    程序运行结果为:


    根据结果可以看到,主函数在未调用构建器之前,变量仍会进行一次编译器默认的初始化,进行创建Card对象t时首先对变量进行一次初始化,再进行对象的创建,其中t3句柄初始化了两次,从表面上看这样的初始化效率低下,但却能够保证足够的安全性。

    那么对于static静态数据的初始化问题来说,同样的事情仍然会发生,若是主数据类型,则初始化一个上述表格值;若指向的是一个对象的句柄,一般会得到“NULL”。若想在定义的同时进行初始化操作,由于static只有一个存储区域,无论创建多少个对象,都会遇到何时对其初始化的问题。下面给出一段代码:

    import java.util.*;
    class Bowl{
    	Bowl(int marker){
    		System.out.println("Bowl("+marker+")");
    	}
    	void f(int marker){
    		System.out.println("f("+marker+")");
    	}
    }
    class Table{
    	static Bowl b1 = new Bowl(1);
    	Table(){
    		System.out.println("Table()");
    		b2.f(1);
    	}
    	void f2(int marker){
    		System.out.println("f2("+marker+")");
    	}
    	static Bowl b2 = new Bowl(2);
    }
    class Cupboard{
    	Bowl b3 = new Bowl(3);
    	static Bowl b4 = new Bowl(4);
    	Cupboard(){
    		System.out.println("Cupboard()");
    		b4.f(2);
    	}
    	void f3(int marker){
    		System.out.println("f3("+marker+")");
    	}
    	static Bowl b5 = new Bowl(5);
    }
    public class Project10 {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		System.out.println("Creating new Cupboard() in main");
    		new Cupboard();
    		t2.f2(1);
    		t3.f3(1);
    	}
    	static Table t2 = new Table();
    	static Cupboard t3 = new Cupboard();
    }
    
    此程序运行结果为:


    可以看到的是,在主函数运行时期,首先进行static的初始化操作,创建Table对象t2时,先对Table中的两个static变量进行初始化,之后调用Table的构建器创建t2,值得注意的是在Cupboard对象t3创建过程中,先创建了一个非static的Bowl b3。static的初始化只有在必要的时候才会进行,如果不创建一个Table对象,而且永远不引用Table.b1或Table.b2,那么static Bowl b1和b2永远不会创建,然而在创建了第一个Table对象之后(或者发生第一次static访问的时候),它们才会创建,初始化的顺序是static对象,接着是非static对象。(这一点在运行结果中可以看出)

    有的时候,static初始化工作还可以放到一个特殊的“static构建从句”中,也可以叫做静态块,类似于:

    class Project{

        static int i;

        static {

            i=4;

        }

    }

    与其他static一样,此段代码只执行一次,下面给出一段程序:

    import java.util.*;
    class Cup{
    	Cup(int marker){
    		System.out.println("Cup("+marker+")");
    	}
    	void f(int marker){
    		System.out.println("f("+marker+")");
    	}
    }
    class Cups{
    	static Cup c1;
    	static Cup c2;
    	static{
    		c1 = new Cup(1);
    		c2 = new Cup(2);
    	}
    	Cups(){
    		System.out.println("Cups()");
    	}
    }
    public class ExplicitStatic {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		System.out.println("Inside main()");
    		Cups.c1.f(99);            //1
    	}
    	//static Cups x = new Cups();     //2
    	//static Cups y = new Cups();     //2
    
    }
    
    此段程序运行结果为:


    若注释掉1,不注释掉2,程序运行结果为:


    若1,2两处均注释掉,static初始化进程永不发生,程序运行结果为:


    进行如上操作的目的,就是为了验证之前提到的static初始化只会在必要的时候进行。。。

    那么当内含继承关系时,对基类子对象的正确初始化也至关重要,JAVA会在构建器中调用基类的构建器来进行初始化,而基类构建器具有执行基类初始化所需的所有知识和能力。JAVA会在导出类的构建器中插入对基类构建器的调用,看如下代码段:

    package access;
    import java.util.*;
    class Art{
    	Art(){
    		System.out.println("Art constructor");
    	}
    }
    class Drawing extends Art{
    	Drawing (){
    		System.out.println("Drawing constructor");
    	}
    }
    public class Cartoon extends Drawing{
    	public Cartoon(){
    		System.out.println("Cartoon constructor");
    	}
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		Cartoon x = new Cartoon();
    		System.out.println();
    		Drawing y = new Drawing();
    		System.out.println();
    		Art z = new Art();
    	}
    
    }
    
    此程序运行结果为:


    从结果可以证明构建过程是从基类“向外”扩散的,基类在导出类构建器可以访问它之前就已经完成了初始化。

    上例中的每个类均有默认的构建器,编译器无需考虑传递参数的问题,但是如果没有默认的构建器,或者想调用一个带有参数的基类构建器,就必须用“super”关键字显示调用,并配备相应的参数列表,看如下代码:

    package access;
    class Game {
    	Game(int i){
    		System.out.println("Game constructor");
    	}
    }
    class BoardGame extends Game{
    	BoardGame(int i){
    		super(i);
    		System.out.println("BoardGame constructor");
    	}
    }
    public class Chess extends BoardGame{
    	Chess(){
    		super(11);
    		System.out.println("Chess constructor");
    	}
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		Chess x = new Chess();
    	}
    
    }
    
    此程序运行结果为:




























    展开全文
  • http://java.dzone.com/articles/java-object-initialization?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+javalobby%2Ffrontpage+%28Javalobby+%2F+Java+Zone%29&utm_content= 我们这里

    原帖是这样描述的:

    http://java.dzone.com/articles/java-object-initialization?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+javalobby%2Ffrontpage+%28Javalobby+%2F+Java+Zone%29&utm_content=

    我们这里把问题简化方便分析。

    属性、方法、构造方法和自由块都是类中的成员,在创建类的对象时,类中各成员的执行顺序:

    1.父类静态成员和静态初始化快,按在代码中出现的顺序依次执行。2.子类静态成员和静态初始化块,按在代码中出现的顺序依次执行。

    3. 父类的实例成员和实例初始化块,按在代码中出现的顺序依次执行。4.执行父类的构造方法。

    5.子类实例成员和实例初始化块,按在代码中出现的顺序依次执行。6.执行子类的构造方法。

    最后,生成对象由main线程调用

    再强调一遍:父静态->子静态->父变量->父初始化区->父构造–>子变量->子初始化区->子构造

    知道了1+1=2,让我们来举个简单的栗子:

    package com.harryz.newclass;
    
    public class A extends B {
        public int a = 100;
     
        public A() {
            super();
            System.out.println(a);
            a = 200;
        }
     
        public static void main(String[] args) {
            System.out.println(new A().a);
        }
    }
     
    class B {
        public B() {
            System.out.println(((A) this).a);
        }
    }

    这里其实只有两个trick:

    第一,super()必须是A()构造方法中的第一行,也就是说Java必须保证构造方法的调用是符合上面顺序的;

    第二,在B()构造方法中调用((A) this).a时,A类被分配到内存空间,但是它的成员a还没有被初始化,所以a的值是默认值0;(诡异的用法。。。)

    //B()
    0
    //A()
    100
    //main
    200


    所以,别忘了内存的分配过程:


    new的时候
            栈                                                                       堆
    rectangle   ------------------------------------------> new Rectangle(3,2);


    new ClassName();//创建 ClassName类的一个实例时:

    解释器截取new这个关键字时,就会为ClassName量身定做一个内存空间,这个时候也就是为该类中的所有成员变量分配内存空间之时,然后按照前面的顺序进行初始化,所有引用类型将其制成null 基本数据类型为0;

    之后解释器会继续解释执行到 ClassName();这句话,也就是该类的构造器,调用指定的类的构造方法(根据用户的需求初始化对象)。

    然而这里面有一种成员变量并不完全在此过程中被初始化,此成员变量为静态成员变量,它是在当类静态属性或方法第一次被调用或者该类第一次被创建对象时被初始化。

    接下来是继承关系下父子类的创建和初始化过程:
    当我们试图去创建一个子类时,java解释器发现该类继承了其他类,所以就会先去创建其父类,切记这个时候并没有为子类分配任何的内存空间
    而是直接越过自己的创建过程去创建父类,如果检查到父类也继承了其他类,java解释器就会依此类推继续创建父类的父类。直到最后一个根父类
    被分配内存后才会创建子类。而构造方法的调用则是从子类开始的,但是在子类的构造方法中必须去调用父类的构造方法,经常性的我们没有看到
    在子类的构造方法中显示的调用父类的构造方法,那是因为解释器隐式的调用了父类默认的无参构造方法,然而当我们通过重载机制将父类的默认
    构造方法覆盖时,那么在子类中就必须显示的调用父类的构造方法


    补充Java内存管理知识:原博客地址

    1. 内存分配策略

    按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的。

    静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间。这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求。

    栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的。和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存。和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。

    静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例。堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放。

    2. JVM中的堆和栈

    JVM是基于堆栈的虚拟机。JVM为每个新创建的线程都分配一个堆栈,也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。

    java把内存分两种:一种是栈内存,另一种是堆内存

    栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。

    栈(stack):是一个先进后出的数据结构,通常用于保存方法(函数)中的参数,局部变量。

    堆(heap):是一个可动态申请的内存空间(其记录空闲内存空间的链表由操作系统维护),是一个运行时数据区,C中的malloc语句所产生的内存空间就在堆中。

    3. 堆和栈优缺点比较

    栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,详见第3点。

    堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

    4. Java中的数据类型有两种

    一种是基本类型

    共有8种,即int, short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。

    这种类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量。值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。

    另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:

           int a = 3; 
           int b = 3; 
      编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。

    特别注意的是,这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变化。相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。如上例,我们定义完a与b的值后,再令a=4;那么,b不会等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

    另一种是包装类数据

    如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。

    String是一个特殊的包装类数据。即可以用String str = new String("abc");的形式来创建,也可以用String str = "abc";的形式来创建(作为对比,在JDK 5.0之前,你从未见过Integer i = 3;的表达式,因为类与字面值是不能通用的,除了String。而在JDK 5.0中,这种表达式是可以的!因为编译器在后台进行Integer i = new Integer(3)的转换)。前者是规范的类的创建过程,即在Java中,一切都是对象,而对象是类的实例,全部通过new()的形式来创建。Java中的有些类,如DateFormat类,可以通过该类的getInstance()方法来返回一个新创建的类,似乎违反了此原则。其实不然。该类运用了单例模式来返回类的实例,只不过这个实例是在该类内部通过new()来创建的,而getInstance()向外部隐藏了此细节。那为什么在String str = "abc";中,并没有通过new()来创建实例,是不是违反了上述原则?其实没有。

     5.String在内存中的存放

    String是一个特殊的包装类数据,可以用用以下两种方式创建:

    String str = new String("abc");第一种创建方式是用new()来新建对象的,它会存放于堆中。每调用一次就会创建一个新的对象。

    String str = "abc";  第二种创建方式先在栈中创建一个对String类的对象引用变量str,然后在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象o,并将o的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。如果已经有了值为"abc"的地址,则查找对象o,并返回o的地址,最后将str指向对象o的地址。

    值得注意的是,一般String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用!

    6.数组在内存中的存放

    int x[] 或者int []x 时,在内存栈空间中创建一个数组引用,通过该数组名来引用数组。

    x = new int[5] 将在堆内存中分配5个保存int型数据的空间,堆内存的首地址放到栈内存中,每个数组元素被初始化为0。

    7.static变量在内存中的存放

    用 static的修饰的变量和方法,实际上是指定了这些变量和方法在内存中的“固定位置”-static storage。既然要有“固定位置”那么他们的 “大小”似乎就是固定的了,有了固定位置和固定大小的特征了,在栈中或堆中开辟空间那就是非常的方便了。如果静态的变量或方法在不出其作用域的情况下,其引用句柄是不会发生改变的。

    8. java中变量在内存中的分配

    1、类变量(static修饰的变量)

    在程序加载时系统就为它在堆中开辟了内存,堆中的内存地址存放于栈以便于高速访问。静态变量的生命周期一直持续到整个"系统"关闭

    2、实例变量

    当你使用java关键字new的时候,系统在堆中开辟并不一定是连续的空间分配给变量(比如说类实例),然后根据零散的堆内存地址,通过哈希算法换算为一长串数字以表征这个变量在堆中的"物理位置"。 实例变量的生命周期--当实例变量的引用丢失后,将被GC(垃圾回收器)列入可回收“名单”中,但并不是马上就释放堆中内存

    3、局部变量

    局部变量,由声明在某方法,或某代码段里(比如for循环),执行到它的时候在栈中开辟内存,当局部变量一但脱离作用域,内存立即释放


    展开全文
  • Java实现顺序表

    2019-10-18 08:48:45
    文章目录一、什么是顺序表?二、顺序表的实现 一、什么是顺序表?...我们可以将顺序表定义成一个类,将顺序表的基本操作定义成类的方法,初始化顺序表就是将这个类实例化成对象,销毁顺序表就是...



    一、什么是顺序表?

    顺序表是指用一组地址连续的存储单元依次存储各个元素,使得在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中的线性表。

    在Java中,可以借助数组来表示一组地址连续的存储单元,通过对数组进行操作来表示对顺序表进行操作。我们可以将顺序表定义成一个类,将顺序表的基本操作定义成类的方法,初始化顺序表就是将这个类实例化成对象,销毁顺序表就是销毁对象。

    一个标准的顺序表需要实现以下基本操作:

    1. 初始化顺序表

    2. 清空顺序表

    3. 检测顺序表是否为空

    4. 返回顺序表的元素个数

    5. 返回顺序表中指定位置元素的值

    6. 返回顺序表中第一个与指定值相同的元素的位置

    7. 向指定位置插入元素

    8. 删除指定位置的元素

    9. 遍历顺序表



    二、顺序表的实现

    1、接口的定义:

    interface Ilist{		//线性表的抽象数据Java接口
    	public void clear();
    	public boolean isEmpty();
    	public int length();
    	public Object get(int i) throws Exception;
    	public void insert(int i, Object x) throws Exception;
    	public void remove(int i) throws Exception;
    	public int indexOf(Object x);
    	public void display();
    }
    

    2、创建顺序表:

    class SqList implements Ilist{
    	private Object[] listElem;	//线性表存储空间
    	private int len;			//线性表当前长度
    	
    	//构造一个存储空间容量为maxSize的线性表
    	public SqList(int maxSize) {
    		len = 0;
    		listElem = new Object[maxSize];
    	}
    }
    

    3、清空顺序表:

    public void clear() {
    	len = 0;
    }
    

    4、判断顺序表是否为空:

    public boolean isEmpty() {
    	return len == 0;
    }
    

    5、顺序表的元素个数:

    public int length() {
    	return len;
    }
    

    6、返回指定位置的值:

    //查找线性表第i个元素并返回其值
    public Object get(int i) throws Exception{
    	if( i<0 || i> len - 1)
    		throw new Exception("第" + i + "个元素不存在");
    	return listElem[i];
    }
    

    7、插入元素:

    //在线性表第i个元素前加入一个值为x的元素
    public void insert (int i, Object x) throws Exception  {
    	if(len == listElem.length)
    		throw new Exception("顺序表已满");
    	if(i < 0 || i > len)
    		throw new Exception("插入位置不合法");
    	for(int j = len; j > i; j--)
    		listElem[j] = listElem[j - 1];
    	listElem[i] = x;
    	len++;
    }
    

    8、删除指定位置元素:

    //构造删除第i个元素的函数
    public void remove(int i) throws Exception {
    	if(i < 0 || i > len - 1)
    		throw new Exception("删除位置不合法");
    	for(int j = i; j < len - 1; j++)
    		listElem[j] = listElem[j + 1];
    	len--;
    }
    

    9、返回初次出现指定元素的位置:

    //构造返回线性表中初次出现值为x的元素的位置,若不存在则返回-1
    public int indexOf(Object x) {	
    	int j = 0;
    	while(j < len && !listElem[j].toString().equals(x.toString()))
    		j++;
    	if(j < len)
    		return j;
    	else
    		return -1;
    }
    

    10、遍历输出顺序表:

    public void display() {
    	for(int j = 0; j < len; j++)
    		System.out.print(listElem[j] + " ");
    	System.out.println();
    }
    


    附上全部代码:

    import java.util.Scanner;
    
    interface Ilist{		//线性表的抽象数据Java接口
    	public void clear();
    	public boolean isEmpty();
    	public int length();
    	public Object get(int i) throws Exception;
    	public void insert(int i, Object x) throws Exception;
    	public void remove(int i) throws Exception;
    	public int indexOf(Object x);
    	public void display();
    }
    class SqList implements Ilist{
    	private Object[] listElem;	//线性表存储空间
    	private int len;			//线性表当前长度
    	
    	//构造一个存储空间容量为maxSize的线性表
    	public SqList(int maxSize) {
    		len = 0;
    		listElem = new Object[maxSize];
    	}
    	
    	//清空函数
    	public void clear() {
    		len = 0;
    	}
    	
    	//判断是否为空函数
    	public boolean isEmpty() {
    		return len == 0;
    	}
    	
    	//线性表实际元素个数
    	public int length() {
    		return len;
    	}
    	
    	//查找线性表第i个元素并返回其值
    	public Object get(int i) throws Exception{
    		if( i<0 || i> len - 1)
    			throw new Exception("第" + i + "个元素不存在");
    		return listElem[i];
    	}
    	
    	//在线性表第i个元素前加入一个值为x的元素
    	public void insert (int i, Object x) throws Exception  {
    		
    		if(len == listElem.length)
    			throw new Exception("顺序表已满");
    		if(i < 0 || i > len)
    			throw new Exception("插入位置不合法");
    		for(int j = len; j > i; j--)
    			listElem[j] = listElem[j - 1];
    		listElem[i] = x;
    		len++;
    	}
    	
    	//构造删除第i个元素的函数
    	public void remove(int i) throws Exception {
    		
    		if(i < 0 || i > len - 1)
    			throw new Exception("删除位置不合法");
    		for(int j = i; j < len - 1; j++)
    			listElem[j] = listElem[j + 1];
    		len--;
    	}
    	
    	//构造返回线性表中初次出现值为x的元素的位置,若不存在则返回-1
    	public int indexOf(Object x) {
    		
    		int j = 0;
    		while(j < len && !listElem[j].toString().equals(x.toString()))
    			j++;
    		if(j < len)
    			return j;
    		else
    			return -1;
    	}
    	
    	//输出线性表中的元素
    	public void display() {
    		for(int j = 0; j < len; j++)
    			System.out.print(listElem[j] + " ");
    		System.out.println();
    	}
    	
    }
    public class Task_1_1 {
    	public static void main(String[] args) throws Exception {
    		Scanner in = new Scanner(System.in);
    		System.out.print("输入顺序表长度n:");
    		int n = in.nextInt(); 
    		//例如 输入'8'
    		
    		SqList L = new SqList(n);	//构造一个空顺序表
    		for(int i=0; i<n/2; i++) 	//存入前n/2个元素
    			L.insert(i, i+1);
    		System.out.print("输出顺序表中个元素的值:");
    		L.display();
    		//输出:1 2 3 4
    		
    		L.insert(2, 3);		//在2位置前插入值为'3'的元素
    		System.out.print("输出插入元素'3'后的顺序表:");
    		L.display();
    		//输出:1 2 3 3 4
    		
    		L.remove(4);		//删掉值为'4'的元素
    		System.out.print("输出删除元素'4'后的顺序表:");
    		L.display();
    		//输出:1 2 3 3 
    		
    		System.out.print("输入要查找的值:");
    		int pos = L.indexOf(in.nextInt());		//输入一个值,找到其位置
    		if(pos!=-1)
    			System.out.println("顺序表中初次出现值为x的元素的位置为:" + pos);
    		else
    			System.out.println("此顺序表中不包含值为'5'的数据元素!");
    		//输入:5
    		//输出:此顺序表中不包含值为'5'的数据元素!
    		
    		for(int i=0; i<L.length()-1; i++){ 		//删除掉相同元素
    			 for(int j=L.length()-1; j>i; j--){ 
    			      if( L.get(j).toString().equals(L.get(i).toString()) ){ 
    			        L.remove(j); 
    			      } 
    			 } 
    		} 
    		System.out.print("输出删除相同元素后的顺序表:");
    		L.display();
    		//输出:1 2 3
    	}
    }
    
    

    运行结果:
    在这里插入图片描述

    展开全文
  • #include<stdio.h> #include<stdlib.h> #define InitSize 10 //默认的最大长度 typedef struct { int *data;...//顺序表的最大容量 int length; //顺序表的当前长度 }SeqList; void Init...
  • 要帮助Penny,你需要知道一个顺序表是怎么初始化、插入、删除以及查找的。 【输入形式】 输入数据只有一组,有很多行。每行的格式可能是下列一种: insert a name delete name show search name 其中 a 是...
  • 带你使用Java编写一个顺序表并测试
  • Java顺序表的就地逆置算法

    千次阅读 2020-03-17 11:45:18
    假设已经定义有顺序表类SequenceList,该类定义了表的相关操作方法,试编写程序实现顺序表的就地逆置算法,即在原表的存储空间将顺序表(a1,a2,a3,…,an)逆置为(an,an-1,…,a1)。 import java.util.ArrayList; ...
  • 设计能存储int型元素的顺序表类SeqList。要求: 顺序表的最大容量在创建顺序表对象时指定; 实现从尾部追加(输入一组数,以-1结束)、打印顺序表的所有元素、插入元素至指定位置等基本操作。 实现向升序表插入...
  • #include <iostream> #include <cstdlib> using namespace std; #define ERROR 0 #define OK 1 ...#define LIST_INIT_SIZE 100 //根据实际情况,调整初始分配空间大小 #define LISTINC...
  • Java编写顺序表,在之前的顺序表的基础上进行了很大程度的完善。
  • Java 类加载与初始化

    千次阅读 2015-10-09 22:54:50
    类加载器动态加载链接初始化示例 类加载器 在了解Java的机制之前,需要先了解类在JVM(Java虚拟机)是如何加载的,这对后面理解java其它机制将有重要作用。 每个类编译后产生一个Class对象,存储在.class...
  • 注 意:在《Java编程思想》,说static{}子句是在类第一次加载时执行且执行一次(可能是笔误或翻译错误,因为此书的例子显示static是在第 一次初始化时执行的),《Java深度历险》说 static{}是在第一次实例化时...
  • 初始化顺序表L。 依次在顺序表L中插入元素a、b、c、e、f(从键盘输入数据)。 输出顺序表L。 输出顺序表L的长度。 输出顺序表L的第4个元素。 输出元素c的位置。 在第5个元素之前插入元素g。 输出顺序表L。 ...
  • 6.JAVA编程思想初始化和清除

    万次阅读 2016-04-02 00:22:24
    许多 C程序的错误都是由于程序员忘记初始化一个变量造成的。对于现成的库,若用户不知道如何初始化库的一个组件,就往往会出现这一类的错误。清除是另一个特殊的问题,因为用完一个元素后,由于不再关心,所以很容易...
  • 转载请注明出处(请尊重原创!谢谢~): ... 出自【zejian的博客】 关联文章:java数据结构与算法之顺序表与链表设计与实现分析 ...java数据结构与算法之改良顺序表与双链表类似ArrayList和LinkedList(带Iter
  • 例题:设顺序表va的数据元素递增有序。试写一算法,将x插入到顺序表的适当位置上,以保持该表的有序性。 #include #include #include #include "dseqlist.h" //不定长顺序表,自动增长 #define INITSIZE 10 ...
  • 删除顺序表中值相同的多余元素

    万次阅读 多人点赞 2018-06-11 10:11:00
    设计并验证一下算法:设顺序表L中的数据元素为整数且非递增有序,删除其值相同的多余元素,即顺序表L中相同的元素只保留一个,并逆置删除后的顺序表L。(1)根据键盘输入数据建立顺序表L。(2)输出顺序表L、删除值...
  • MyBatis - 初始化(十一)SQL初始化(下) 该系列文档是本人在学习 Mybatis 的源码过程总结下来的,可能对各位读者会不太友好,阅读前需要对 MyBatis 和 Spring 有一定的了解。比较适合刚接触,会使用但是一直没...
  • 数据结构按逻辑划分为两大类... 然而在Java 的世界里,提供了一个集合体系,这个集合体系工具是为了方便Java 开发者在内存级(数据结构就是在数据再内存的组织方式)组织数据,以便帮助开发者快速编写出高效优质的的
  • 编写一个程序,实现顺序表的各种基本运算

    万次阅读 多人点赞 2013-11-14 16:18:32
    (1)初始化顺序表L (2)采用尾插法依次插入a、b、c、d、e (3)输出顺序表L (4)输出顺序表L的长度 (5)判断顺序表L是否为空 (6)输出顺序表的第3个元素 (7)输出元素a的逻辑位置 (8)在第4个元素位置上插入...
  • 顺序表va的数据元素递增有序。试写一算法,将x插入到顺序表的适当位置上,以保持该表的有序性 先建立一个待插入的结点,然后依次与与链表的各结点的数据域比较大小,找到插入该结点的位置,最后插入该结点。
  • 直接上代码 ...#define LIST_INIT_SIZE 10//顺序表存储空间的初始分配量 #define LISTINCREMENT 5// 顺序存储空间的分配增量 #define OVERFLOW 0 #define ok 1 typedef struct{ int *element;//...
  • 顺序表的理解与使用

    千次阅读 2016-08-13 01:08:08
    一、前言 顺序表是线性表(链式表和顺序表)的一种,线性表是线性排列的一组具有相同数据类型的元素的有限集合,除第一个元素外,所有的元素都有...接下来会解析顺序表的概念和使用(含c,c++,java三种语言实现)。
  • Java实现线性表的顺序存储结构

    千次阅读 2016-04-05 22:20:43
    本文使用Java实现线性表的顺序存储结构,虽然...在Eclipse新建一个Java project,包括两个java源文件,SqList.java和SqListTest.java。 /**  * 线性表的动态分配顺序存储结构  *   * @author YangYong  *  */
  • 一维数组 杭州驾校网 ...Java中使用关键字new创建数组对象,格式为:数组名 = new 数组元素的类型 [数组元素的个数] 例子: int[] s ; s = new int[5] ; 或者也可以自己创建类 People p[ ]...
  • 实现顺序表的基本运算:初始化、插入、删除、求表的长度、判空、释放。 (1)从键盘输入数据到数组; (2)用数组的数据创建顺序表; (3)输出顺序表L; (4)输出顺序表L的长度; (5)判断顺序表L是否为空; (6...
  • 首先应该创建一个顺序表,从键盘输入数据; 显示顺序表 在插入之前先找到插入的位置; 将插入位置后面的数据往后移动; 完整的代码 #include&lt;stdio.h&gt; 2 #include&lt;stdlib.h&gt; 3 4 #...
  • 实验一 顺序表、单链表基本操作的实现 l 实验目的 1、顺序表 (1)掌握线性表的基本运算。 ...(2)掌握顺序存储的概念,学会对顺序存储数据结构进行操作。...(1)InitList(LIST *L,int ms)初始化线性表;...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 50,267
精华内容 20,106
关键字:

java中初始化顺序表L

java 订阅