Java基础知识要点

Java基础
一、 基本概念 3
(一) JVM 3
(二) i++ 4
(三) 类型转换 4
(四) 程序结构 5
(五) 运算符 5
(六) 异常 6
(七) 反射 8
二、 传递与引用 8
(一) 传值与传引用 8
(二) 静态变量与私有变量 8
(三) 输入/输出流 8
(四) 序列化 11
三、 循环、条件、概率 11
(一) 典型递归问题 11
(二) 循环与条件问题 12
(三) 概率问题 12
四、 Java内存管理 12
(一) 垃圾回收 12
(二) 内存管理 13
(三) Clone 17
Java克隆(Clone)是Java语言的特性之一,但在实际中应用比较少见。但有时候用克隆会更方便更有效率。 17
五、 面向对象 19
(一) 面向对象的基本概念 19
(二) 类与对象 19
(三) 嵌套类 19
(四) 集合类 19
(五) 构造函数与析构函数 19
(六) 复制构造函数和赋值函数 19
(七) 多态的概念 19
六、 继承与接口 19
(一) 基础知识 19
(二) Super 19
(三) This 19
(四) 不能继承的情况 19
(五) 抽象类与接口 19
Java基础(笔试部分)
一、基本概念
(一)JVM


JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。 JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。编译虚拟机的指令集与编译微处理器的指令集非常类似。
我们主要关注ClassLoader的知识。
1) ClassLoader的基本概念
与C或C++编写的程序不同,Java程序并不是一个可执行文件,而是由许多独立的类文件组成的,每一个文件对应一个Java类。此外,这些类文件并非全部都装入内存,而是根据程序需要逐步载入。
2) ClassLoader的加载流程
3) 一些重要的方法
① loadClass
② defineClass
③ findSystemClass
④ resolveClass
⑤ findLoadedClass
⑥ findClass
⑦ getSystemClassLoader
⑧ forName
(二)i++
i++和++i使用的不同点在于一个是在程序执行完毕后自增,一个是程序开始执行前自增。
对于比较长的表达式:程序从左向右执行,以int j = 0;x= ++j + j++ + j++ + j++;为例。
程序执行顺序为:
① ++j;(此时先会执行自增然后执行+操作,此时x = 1,j = 1)
② j++;(此时程序先会执行+操作然后才会自增,此时为 x= 1 + 1;j = 2)
③ j++;(此时程序先会执行+操作然后才会自增,此时为 x= 1 + 1 + 2;j = 3)
④ j++;(同理x = 1 + 1 + 2 +3 ; j = 4)
  那么假如此时我将x 换为 j 即(j = ++j + j++ + j++ + j++;)那么此时的j该等于什么呢?是7?还是4?
答案是7,这是为什么呢?原来在Java中采用了中间缓存变量的机制,这是什么意思呢?就是说在Java中在进行计算的时候Java会创建一个缓存变量来作为中间变量使用以储存中间值,即以上计算在Java中实际上是这样的:
① 中间变量temp保存右侧表达式每步的结果。即temp =(++j) +( j++) +( j++) + (j++);
② 变量 j 的值有j自己保存即右侧表达式计算之后j = 4;
③ 赋值运算是指把右侧表达式的值赋给左侧变量即 j = temp;此时运算结束,j = 7;
(三)类型转换
1.数据类型的转换种类
基本数据类型之间的转换、字符串型与其他数据类型之间的转换、其他实用数据类型的转换。
2.简单数据类型的转换
① 自动类型转换
低级变量可以自动转为高级变量
(byte1、char2、short2)— int4 — long8 — float4 — double8
② 强制类型转换
高级变量转成低级变量必须强制转换,可能会丢失精度。
③ 包装类过渡类型转换
使用对应的封装类来实现转换
Byte-Character-Short-Integer-Long-Float-Double
3.字符串型与其他数据类型的转换
几乎所有从Object类派生的子类都有toString()方法。使得数据能够转换为String类型。包括8种基本数据类型的封装类。
4.将字符型直接作为数值转换为其他数据类型
① 将字符转换成相应的ASCII码使得'1'——49
② 用Character的getNumericValue(char ch);使得'1'——1


(四)程序结构
(API级程序员部分例如断言assert的使用等)
(五)运算符
1.运算符种类:
① 单目运算符(只对一个参数进行运算)
② 双目运算符(对两个参数进行运算)
③ 三目运算符(对三个参数进行运算)
2.运算符优先级(1级最高到14级最低) 从上到下为:
① . () [] (.点运算符、[]数组下表运算符、一元)
② ++ -- ~ +(正) -(负) (++ --算术运算符、一元)
③ * / % (算术运算符)
④ + - (算术运算符)
⑤ >>  <<  >>> (位运算符)
⑥ >  <  >=  <=  instanceof (关系运算符, instanceo是否为接口)
⑦ == != (关系运算符)
⑧ &     (位运算符,可以充当布尔逻辑运算符)
⑨ ^     (位运算符,可以充当布尔逻辑运算符)
⑩ | (位运算符,可以充当布尔逻辑运算符)
11 && (逻辑运算符)
12 || (逻辑运算符)
13 ?: (条件运算符、三元)
14 =  +=  -=  *=  /=  %=  ^= (扩展赋值运算符)
&=  <<=  >>= (扩展赋值运算符)
即一、二、三元顺序走,算数位移论关系,与先或后逻辑定,扩展赋值垫最后。
3.一些注意事项(定义var 为变量、Expression为表达式、op为运算符)
① 运算表达式中会进行自动类型转换。因为在运算表达式的时候,计算器会把数据和运算符都读入到栈中,会把所有数据类型自动转换成最高级变量。
② Java编程规范中提到:对于三目运算符Expression1?Expression2:Expression3,如果后两个表达式有一个是常量表达式,另外一个是T类型(例如char类型),而常量表达式也可以被T 类型所表示时,则输出的结果为T类型。例如:
char x = 'x'; int i = 10;
false?i : x   //结果为120
false?10 : x  //结果为x


③ 短路运算符&& ||
Expression1 && Expression2 Expression1为假则Expression2不执行
Expression1 || Expression2 Expression1 为真则Expression2 不执行
④ << >> <<<运算符
<< >> <<<都是var 1<< var2 其中var2在运算前需要先进行模32运算,
即5 << 32 等同于5 << 0。
(六)异常
1.什么是异常?
Java程序运行时,常常会出现一些非正常现象,这种情况被成为运行错误,根据其性质可以分为错误和异常。所有抛出(throw)的异常都必须从Throwable派生而来,Throwable有两个子类Error和Exception。
Error一般包括死循环、内存泄露等。Error对象一般由Java虚拟机生成并抛弃(通常Java程序不会对这类异常进行处理)
Exception异常时程序执行时遇到的非正常情况或意外行为。一般一下情况都会引发:代码或调用的代码(如共享库)中有错误,操作系统资源不可用、公共语言库运行遇到意外情况(如无法验证代码)等,常见的有:数组下标越界、算法溢出(超出数值表达范围)、除数为零、无效参数、内存溢出等这种情况程序本身可以解决,由异常代码调整程序的运行方向,使程序仍可继续运行,直到正常结束。
Java要求必须捕获非运行时异常(原因是程序遇到了意外情况,如IO异常等),但对运行时异常(RuntimeException)可以不做处理。
2.异常的关键字
关键字有5个:try、catch、throw、throws、finally,其中try、catch、finally三个语句块应注意的问题:
① try、catch、finally三个语句块均不能单独使用,三者可以组成try-catch-finally、try-catch、try-finally三种结构,catch语句可以有多个或一个,finally最多有一个。
② try、catch、finally三个代码块中变量的作用域为代码块内部,分别独立不能访问,如果要在三块中都能访问,需定义到代码块外面。
③ 若有多个catch块,只会匹配其中一个异常类并执行catch代码块,而不会在执行别的catch块,并且匹配的catch语句的顺序是由上到下的。如果异常被catch,程序会继续向下执行,如果最终没有catch,则程序终止。
④ throw与throws的区别:throw的关键字用于方法体内部,用来抛出一个Throwable类型的异常。throws用于方法体外部的方法声明部分,用来声明方法可能会抛出那些异常。
3.Java异常和C++异常的区别
C++异常处理模型中,它给予了程序员最大的自由度和发挥空间,允许程序员抛出任何想要的异常对象(无论是系统定义的还是自定义的对象)。但是由于各个子系统的基础运行库不适一个厂商或一个人定义的,这导致C++中异常处理的不统一和复杂,而JavaJDK已经报异常类(Exception)封装好了,所以不用像C++中用catch()这样的语法。


4.异常处理的常见问题
1) 过于庞大的try代码块
设置多个异常抛出点。Catch语句中尽量指定异常类型。
2) 异常的完整性
如果一个方法运行时可能会向上层调用者函数抛出异常,那么你就必须
“must be caught or declared to be thrown”即异常必须被catch或显示声明。
5.RuntimeException异常
在Java处理中,一般有两类异常:其一就是通过throw语句,程序员在代码中人为的抛出异常(系统运行时动态的检测到一个错误,但可以再静态编译时由静态语法检测来判断可能会抛出哪些类型的异常),另一种是系统运行时异常,例如:“空字符串”、“无效句柄”等,这类异常是无法通过静态编译是的静态语法来检测出来的,所以Java处理模型中的“must be caught or declared to be thrown”规则不适用于RuntimeException,但是JVM却要求我们有效的捕获并处理此类异常。当然,RuntimeException也可以被程序员显示的抛出,并通过catch(RuntimeException)或catch(Exception)来捕获并处理的。
6.final、finally、finalize的区别
1) final
① final成员
类中定义成员变量为final,意味着这个变量一旦被初始化就不可改变,这其中包含两个含义:对于基本类型是值不可改变;对于引用类型是其引用不可改变。而它的初始化可以有两个地方:一是其定义处,二是构造方法里。注:二者只能选择其一。
方法的参数定义为final,如果参数为基本类型没有什么意义,因为它传递的是值,如果参数类型为引用类型,那么就意味着使用的是原来的句柄。
② final方法
一、即这个方法可以被子类继承但是不能重写。
二、即允许编译器把对此方法的调用转化被inline(行内)调用机制。此时会在调用该final方法时,直接讲方法主体插入到调用处而不是进行例行的方法调用,即不需要保存断点、压栈等,这样可能会提高程序效率,但是如果这个方法主体非常大,那么多次调用会使主体代码膨胀,反而可能影响效率所以要谨慎使用final定义方法。
③ final类
该类是无法被继承的,而类中的成员可以不是final也可以是final,但是方法全是final的。
2) finally
finally是对Java异常处理模型的最佳补充。finally结构式代码总会执行,而不管有无异常发生。使用finally可以维护对象的内部状态,并可以清理非内存资源。
3) finalize
根据Java语言规范,JVM保证调用fianlize函数调用前,这个对象是不可达的,但是JVM不保证这个函数一定会被调用。另外,规范还保证finalize函数最多运行一次。
通常,finalize用于一些不容易控制,并且非常重要的资源的释放。例如:一些IO的操作,数据的连接。这些资源的释放对于整个程序是非常关键的。这时候我们应该以通过程序本身管理这些资源为主,以finalize函数释放资源方式为辅,形成一种双保险的管理机制,而不是仅仅依靠finalize来释放资源。
(七)反射
什么是Reflection(反射)?其他语言有这种特性么?
反射主要是指程序可以访问、检测、修改它自己本身的状态或行为的一种能力。
(Java中反射是一种强大的工具,它能够灵活的创建代码。这些代码可以在运行时装配,无须再组件之间进行链接。反射允许在编写与执行时,使程序代码能够接入装载到JVM中的类的内部信息,而不是源代码中选定的类的协作的代码。这是反射成为构建灵活应用的主要工具。需注意的是如果使用不当,反射的成本会很高。)
Java中的类反射Reflection是Java程序开发语言的特征之一,它允许运行中的JAVA程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性。Java的这一能力在实际应用中也许用的不是很多,但是在其他语言中根本不存在这一特性。


二、传递与引用
(一)传值与传引用
对于基本数据类型,Java是传值的副本,对于一切对象类型变量,Java都是传引用的副本(实质上复制指向地址的指针)。
C++中对于基本数据类型,一样也是传值的副本。而对于对象型变量传递的是真实的引用而不是引用副本。
(二)静态变量与私有变量
静态变量Static:
静态变量与静态方法,甚至静态类(只有内部类才可以定义为静态的,普通不可以),这些被定义为静态的属性,会先于其他对象被创建,而且只会在类第一次被加载的时候进行初始化,可以通过类名访问。


Private私有变量:
私有变量除了类内方法能有调用,类外不可见,即使是它的子类。它可用来实现封装特性。


(三)输入/输出流


1.输入输出流的概念与介绍
在Java类库中,IO部分的内容是很庞大的,因为它涉及的领域很广泛:标准输入输出,文件的操作,网络上的数据流,字符串流,对象流,zip文件流....
流是一个很形象的概念,当程序需要读取数据的时候,就会开启一个通向数据源的流,这个数据源可以是文件,内存,或是网络连接。类似的,当程序需要写入数据的时候,就会开启一个通向目的地的流。这时候你就可以想象数据好像在这其中“流”动一样,如下图:


Java中的流分为两种,一种是字节流,另一种是字符流,分别由四个抽象类来表示(每种流包括输入和输出两种所以一共四个):InputStream,OutputStream,Reader,Writer。Java中其他多种多样变化的流均是由它们派生出来的:  


在这其中InputStream和OutputStream在早期的Java版本中就已经存在了,它们是基于字节流的,而基于字符流的Reader和Writer是后来加入作为补充的。以上的层次图是Java类库中的一个基本的层次体系。
   在这四个抽象类中,InputStream和Reader定义了完全相同的接口:
int read()
int read(char cbuf[])
int read(char cbuf[], int offset, int length)
而OutputStream和Writer也是如此:
int write(int c)
int write(char cbuf[])
int write(char cbuf[], int offset, int length)
这六个方法都是最基本的,read()和write()通过方法的重载来读写一个字节,或者一个字节数组。
注:
① BufferedReader是Reader的一个子类,它具有缓冲的作用,避免了频繁的从物理设备中读取信息。它有以下两个构造函数:
BufferedReader(Reader in);
BufferedReader(Reader in, int sz);
这里的sz指定缓冲区的大小。
② InputStreamReader是InputStream和Reader之间的桥梁,由于System.in是字节流,需要用它来包装之后变为字符流供给BufferedReader使用。
③ PrintWriter out1 = new PrintWriter(new BufferedWriter(new FileWriter("IODemo.out")));
这句话体现了Java输入输出系统的一个特点,为了达到某个目的,需要包装好几层。首先,输出目的地是文件IODemo.out,所以最内层包装的是FileWriter,建立一个输出文件流,接下来,我们希望这个流是缓冲的,所以用BufferedWriter来包装它以达到目的,最后,我们需要格式化输出结果,于是将PrintWriter包在最外层。


2.字节流与字符流的区别
要把一片二进制数据数据逐一输出到某个设备中,或者从某个设备中逐一读取一片二进制数据,不管输入输出设备是什么,我们要用统一的方式来完成这些操作,用一种抽象的方式进行描述,这个抽象描述方式起名为IO 流,对应的抽象类为OutputStream 和InputStream ,不同的实现类就代表不同的输入和输出设备,它们都是针对字节进行操作的。
在应用中,经常要完全是字符的一段文本输出去或读进来,用字节流可以吗?计算机中的一切最终都是二进制的字节形式存在。对于“中国”这些字符,首先要得到其对应的字节,然后将字节写入到输出流。读取时,首先读到的是字节,可是我们要把它显示为字符,我们需要将字节转换成字符。由于这样的需求很广泛,人家专门提供了字符流的包装类。
底层设备永远只接受字节数据,有时候要写字符串到底层设备,需要将字符串转成字节再进行写入。字符流是字节流的包装,字符流则是直接接受字符串,它内部将串转成字节,再写入底层设备,这为我们向IO 设别写入或读取字符串提供了一点点方便。字符向字节转换时,要注意编码的问题,因为字符串转成字节数组,其实是转成该字符的某种编码的字节形式,读取也是反之的道理。


(四)序列化
1) 什么是序列化?
序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。


2) Java怎么实现序列化?
序列化对象还是比较简单的,只要让他实现Serializable接口就行了(这是一个“标记接口tagginginterface”,没有任何方法)。
序列化一个对象,必须先创建一个OutputStream,然后把它嵌进ObjectOutputStream。这是就能用writeObject()方法把对象写入OutputStream。读的时候需要把InputStream嵌到ObjectInputStream中,然后在用readObject()。不过这样读出来的只是一个Object的reference,因此在使用之前还得先下传。(序列化不止保存了对象的副本,还把它引用的对象也保存了起来,并跟踪这些对象的reference把他们引用的对象也保存了起来即“对象网”)
三、循环、条件、概率
(一)典型递归问题
1.假如我有一个数组,现在写一个程序输出全部组合数,例如{1,2}那么输出的结果为1,2,12,21一共4个 结果。
2.(进阶)现有5个数1,2,2,3,4,写一个程序输出全部排列,如:12234,21234等,但要求打印结果不能有重复。
3.使用递归计算斐波那契数列的通项f(n),已知f1=1,f2=1,以后每项都是前两项的和。
4.求一个字符串中出现次数最多的那个字母及次数,如果重复则都求出。
(二)循环与条件问题
(三)概率问题
1.放回抽样问题
① 一个鱼塘养鱼若干,请用一个办法尽量准确地估算其中鱼的数量。
2.独立事件
① 一个普通的骰子,连续2次都是1,那么第三次抛是1点的概率是小于1/6、还是等于1/6,还是大于1/6?
② 一个普通的骰子,连续10次都是1,那么第11次抛是1点的概率是小于1/6、还是等于1/6,还是大于1/6?


四、Java内存管理
C++程序员觉得内存管理太重要了,所以一定要自己进行管理;Java/C#程序员觉得内存管理太重要了,所以一定不能自己进行管理。从一定意义上来说两者都是对的,因为内存管理涉及堆、栈、哈希表、内存泄露等诸多方面。
(一)垃圾回收
1.GC 是什么? 为什么要有GC?
GC 是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放已分配内存的显示操作方法。
2.垃圾回收的优点和原理。并考虑2 种回收机制。
Java 语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java 程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,Java 中的对象不再有"作用域"的概念,只有对象的引用才有"作用域"。垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清楚和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。回收机制有分代复制垃圾回收和标记垃圾回收,增量垃圾回收。
3.垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?
对于GC 来说,当程序员创建对象时,GC 就开始监控这个对象的地址、大小以及使用情况。通常,GC 采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC 确定一些对象为"不可达"时,GC 就有责回收这些内存空间。可以。程序员可以手动执行System.gc(),通知GC 运行,但是Java 语言规范并不保证GC 一定会执行。


扩展:以下几点或许可以作为编写程序的准则
① 不要试图去假定垃圾收集的时间,一切都是未知的。
② Java中提供了一些和垃圾收集打交道的类,而且提供了一种强制执行垃圾收集的方法——调用System.gc(),但这同样是一个不确定的方法(它只是向JVM提出申请垃圾收集)。
③ 挑选合适自己的垃圾回收器。(默认收集器,增量收集器(实时性),标记/清除收集器(有较高配置和闲置资源)等)
④ 关键的也是很难把握的问题是内存泄露。
⑤ 尽早释放无用的对象的引用。


问题:
1.Java中的垃圾收集器相对于以前的语言的优势是什么?
2.以下说法那些是正确的?
a. Java虚拟机中的自动垃圾回收器阻止程序运行溢出内存
b. 一段程序可以建议垃圾回收执行,但不能强迫它执行
c. 垃圾回收是一个独立的平台
d. 当一个对象的引用全都被置为空时,这个对象就可以变为能被垃圾回收
3.下列代码那几行的sobj符合垃圾收集器的收集标准?
1. Object sobj = new Object();
2. Object sobj = null;
3. Object sobj = new Object();
4. Sobj = new Object();


(二)内存管理
? java是如何管理内存的
Java的内存管理就是对象的分配和释放问题。(两部分)
分配:内存的分配是由程序完成的,程序员需要通过关键字new为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间。
  释放:对象的释放是由垃圾回收机制决定和执行的,这样做确实简化了程序员的工作。但同时,它也加重了JVM的工作。这也是Java程序运行速度较慢的原因之一。因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。
? 什么叫java的内存泄露
在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连(也就是说仍存在该内存对象的引用);其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。
与C++内存泄露概念的区别:
在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,然后却不可达,由于C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄露。


? JVM的内存区域组成
java把内存分两种:一种是栈内存,另一种是堆内存
① 在函数中定义的基本类型变量和对象的引用变量都在函数的栈内存中分配;
② 堆内存用来存放由new创建的对象和数组以及对象的实例变量
在函数(代码块)中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量所分配的内存空间;在堆中分配的内存由java虚拟机的自动垃圾回收器来管理


堆和栈的优缺点
堆的优势是可以动态分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的。缺点就是要在运行时动态分配内存,存取速度较慢;
栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。另外,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
? Java中数据在内存中是如何存储的
a) 基本数据类型
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。在编译器内部,遇到时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。
b) 对象
在Java中,创建一个对象包括对象的声明和实例化两步,下面用一个例题来说明对象的内存模型。
  假设有类Rectangle定义如下:
Java代码
class Rectangle{


   double width,height;


   Rectangle(double w,double h){
  width=w;
height=h;
}
}
(1)声明对象时的内存模型
用Rectangle rect;声明一个对象rect时,将在栈内存为对象的引用变量rect分配内存空间,但Rectangle的值为空,称rect是一个空对象。空对象不能使用,因为它还没有引用任何“实体”。
(2)对象实例化时的内存模型
   当执行rect=new Rectangle(3,5);时,会做两件事:
在堆内存中为类的成员变量width,height分配内存,并将其初始化为各数据类型的默认值;接着进行显式初始化(类定义时的初始化值);最后调用构造方法,为成员变量赋值。
返回堆内存中对象的引用(相当于首地址)给引用变量rect,以后就可以通过rect来引用堆内存中的对象了。
c) 创建多个不同的对象实例
一个类通过使用new运算符可以创建多个不同的对象实例,这些对象实例将在堆中被分配不同的内存空间,改变其中一个对象的状态不会影响其他对象的状态。例如:
Java代码
Rectangle r1=new Rectangle(3,5);
Rectangle r2=new Rectangle(4,6);
此时,将在堆内存中分别为两个对象的成员变量width、height分配内存空间,两个对象在堆内存中占据的空间是互不相同的。如果有:
Java代码
Rectangle r1=new Rectangle(3,5);
Rectangle r2=r1;
则在堆内存中只创建了一个对象实例,在栈内存中创建了两个对象引用,两个对象引用同时指向一个对象实例。
d) 包装类
基本型别都有对应的包装类:如int对应Integer类,double对应Double类等,基本类型的定义都是直接在栈中,如果用包装类来创建对象,就和普通对象一样了。例如:int i=0;i直接存储在栈中。 Integer i(i此时是对象) = new Integer(5);这样,i对象数据存储在堆中,i的引用存储在栈中,通过栈中的引用来操作对象。
e) String
String是一个特殊的包装类数据。可以用用以下两种方式创建:
  1. String str = new String("abc");
2. String str = "abc";


  第一种创建方式,和普通对象的的创建过程一样;
第二种创建方式,Java内部将此语句转化为以下几个步骤:
  (1) 先定义一个名为str的对String类的对象引用变量:String str;
(2) 在栈中查找有没有存放值为“abc”的地址,如果没有,则开辟一个存放字面值为“abc”的地址,接着创建一个新的String类的对象o,并将o的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。如果已经有了值为“abc”的地址,则查找对象o,并返回o的地址。
   (3) 将str指向对象o的地址。
值得注意的是,一般String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用。


为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。
Java代码
String str1=“abc”;
String str2=“abc”;
System.out.println(s1==s2);//true
注意,这里并不用str1.equals(str2);的方式,因为这将比较两个字符串的值是否相等。==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值。而我们在这里要看的是,str1与str2是否都指向了同一个对象。


  我们再接着看以下的代码。
Java代码
Stringstr1=new String(“abc”);
Stringstr2=“abc”;
System.out.println(str1==str2);//false
创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。
以上两段代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。
f) 数组
当定义一个数组,int x[];或int []x;时,在栈内存中创建一个数组引用,通过该引用(即数组名)来引用数组。x=new int[3];将在堆内存中分配3个保存int型数据的空间,堆内存的首地址放到栈内存中,每个数组元素被初始化为0。
g) 静态变量
用static的修饰的变量和方法,实际上是指定了这些变量和方法在内存中的“固定位置”-static storage,可以理解为所有实例对象共有的内存空间。static变量有点类似于C中的全局变量的概念;静态表示的是内存的共享,就是它的每一个实例都指向同一个内存地址。把static拿来,就是告诉JVM它是静态的,它的引用(含间接引用)都是指向同一个位置,在那个地方,你把它改了,它就不会变成原样,你把它清理了,它就不会回来了。
那静态变量与方法是在什么时候初始化的呢?对于两种不同的类属性,static属性与instance属性,初始化的时机是不同的。instance属性在创建实例的时候初始化,static属性在类加载,也就是第一次用到这个类的时候初始化,对于后来的实例的创建,不再次进行初始化。
我们常可看到类似以下的例子来说明这个问题
Java代码
class Student{
static int numberOfStudents=0;
Student(){
numberOfStudents++;
}
}
每一次创建一个新的Student实例时,成员numberOfStudents都会不断的递增,并且所有的Student实例都访问同一个 numberOfStudents变量,实际上int numberOfStudents变量在内存中只存储在一个位置上。


问题:
1.Java是如何管理内存的?
2.什么是Java中的内存泄露?
3.内存泄露的主要由什么引起的?简单介绍一下


(三)Clone
Java克隆(Clone)是Java语言的特性之一,但在实际中应用比较少见。但有时候用克隆会更方便更有效率。


? 对于克隆(Clone),Java有一些限制:
1、被克隆的类必须自己实现Cloneable 接口,以指示 Object.clone() 方法可以合法地对该类实例进行按字段复制。Cloneable 接口实际上是个标识接口,没有任何接口方法。
2、实现Cloneable接口的类应该使用公共方法重写 Object.clone(它是受保护的)。某个对象实现了此接口就克隆它是不可能的。即使 clone 方法是反射性调用的,也无法保证它将获得成功。
3、在Java.lang.Object类中克隆方法是这么定义的:
protected Object clone()throws CloneNotSupportedException
创建并返回此对象的一个副本。表明是一个受保护的方法,同一个包中可见。
按照惯例,返回的对象应该通过调用 super.clone 获得。


五、面向对象
(一)面向对象的基本概念
(二)类与对象
(三)嵌套类
(四)集合类
(五)构造函数与析构函数
(六)复制构造函数和赋值函数
(七)多态的概念
六、继承与接口
(一)基础知识
(二)Super
(三)This
(四)不能继承的情况
(五)抽象类与接口
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值