上篇讲了java的面向对象部分:1.类和对象 ;2.类的无参方法和带参方法;3.对象与封装;4.继承;5.多态。有代码,有注释,有颜色标注重点,有详细的步骤。
本篇会将剩余的java的接口、异常、对象引用与对象的区别、多态性理解、向上转型和向下转型、栈和堆等综合型的知识,亦是非常重要的难点!!!
一、接口
接口的特性:
接口不可以被实例化
实现类必须实现接口的所有方法(接口中的方法都需要由其子类实现,否则子类需要是抽象类方法再由其子类实现,与抽象类类似)
实现类可以实现多个接口
方法都是默认public abstract修饰的
在接口中声明的变量都是静态常量
接口主要是针对方法而言,约束性更加的宽泛
接口可以被多实现,多个接口之间使用逗号隔开
如果一个类中既有继承又有实现(使用接口叫实现),将继承写在前面,实现写在后面
接口用interface修饰
实现接口用implements
需要满足的条件:has-a
示例:
/*
* 接口中的方法都是默认public abstract 修饰的
* 接口也是abstract修饰的
*
* 接口是不能创建对象的
* 接口中的方法都需要由其子类实现,否则子类需要是抽象类方法再由其子类实现
* 在接口中声明的变量都是静态常量
* 接口可以被多实现,多个接口之间使用逗号隔开
* 如果一个类中既有继承又有实现,将继承写在前面,实现写在后面
*/
public interface MyInterface {
int num = 10;
public void show();
}
interface YouInterface {
public void you();
}
class Y extends A implements MyInterface ,YouInterface{
public void show(){
}
public void you(){
}
}
class A implements MyInterface{
int num = 10;
public void show(){
}
}
为什么使用接口:
一定程度上弥补了继承的缺陷,接口对内容的约束更加的宽泛,针对的是是否有这个东西,主要是来声明标准的
实际过程中,所有方法不可能只由一个人编写
接口就是把具有相同功能,但是本身却没有任何关系的类的功能抽象出来,然后让我们自定义具体的实现。看到一本书这样写到:“接口就是一份契约,由类实现契约。契约中我编写了某些大的方针与前提,而签了约的类可以具体问题具体分析来实现自己的功能”
接口就是个招牌。
比如说你今年放假出去杭州旅游,玩了一上午,你也有点饿了,突然看到前面有个店子,上面挂着KFC,然后你就知道今天中饭有着落了。
KFC就是接口,我们看到了这个接口,就知道这个店会卖炸鸡腿(实现接口)。
那么为神马我们要去定义一个接口涅,这个店可以直接卖炸鸡腿啊(直接写实现方法),是的,这个店可以直接卖炸鸡腿,但没有挂KFC的招牌,我们就不能直接简单粗暴的冲进去叫服务员给两个炸鸡腿了。
接口和抽象类的比较:
不同点:
抽象类只能是继承一个类
接口是可以被多个实现
抽象类中可以有抽象方法和普通方法
接口中只能有抽象方法
相同点:
都表示抽象的,都可以被其他类来实现其中的方法
都不能被实例化
都有抽象方法
二、异常
什么是异常:
异常是指在程序运行过程中所发生的不正常的事件,它会中断正在运行的程序
什么是异常处理:
Java编程语言使用异常处理机制为程序提供了错误处理的能力
为什么使用异常:
代码冗余、程序员要花很多精力去“堵漏洞”、在实际开发中不一定能够将每一个程序的漏洞都想到并使用if判断避免
@Test
public void test01(){
System.out.print("请输入被除数:");
if(tx.hasNextInt()==true){
int num1 = tx.nextInt();
System.out.print("请输入除数:");
int num2 = tx.nextInt();
if(num2==0){
System.out.println("除数不能为0");
}
System.out.println("商是:"+num1/num2);
}else{
System.out.println("请输入整形数值");
}
}
*如何使用:
trycatchfinallythrowthrows
try:
将可能出现异常的代码放入try中
catch:
在一段程序中catch可以有多个,来捕获异常类型,当出现了某种异常就会中断异常代码后面的内容转而执行对应的catch中的内容
*finally:
在程序中一定会被执行的内容,除非是遇到了exit()方法
当程序中有return的时候,会先执行finally中的内容,然后执行return
结论:
1、不管有木有出现异常,finally块中代码都会执行;
2、当try和catch中有return时,finally仍然会执行;
3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
最终结论:任何执行try 或者catch中的return语句之前,都会先执行finally语句,如果finally存在的话。 如果finally中有return语句,那么程序就return了,所以finally中的return是一定会被return的, 编译器把finally中的return实现为一个warning。
有return的情况下try catch finally的执行顺序:
下面是个测试程序
public class FinallyTest
{
public static void main(String[] args) {
System.out.println(new FinallyTest().test());;
}
static int test()
{
int x = 1;
try
{
x++;
return x;
}
finally
{
++x;
}
}
}
结果是2。
分析:
在try语句中,在执行return语句时,要返回的结果已经准备好了,就在此时,程序转到finally执行了。
在转去之前,try中先把要返回的结果存放到不同于x的局部变量中去,执行完finally之后,在从中取出返回结果,
因此,即使finally中对变量x进行了改变,但是不会影响返回结果。
它应该使用栈保存返回值。
throw:抛出异常
我们直接在程序中写异常信息
public class Student {
private String sex;
public String getSex() {
return sex;
}
public void setSex(String sex) {
/*
* 如果赋值的是男或者女则正常赋值否则给出异常提示
*
*/
if("男".equals(sex) || "女".equals(sex)){
this.sex = sex;
} else{
//抛出异常
try {
throw new Exception("性别赋值不正确");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void show(){
System.out.println("性别是:"+sex);
}
}
throws:声明异常
在程序中可能有的异常,我们在方法名后声明,但是这样并没有真正处理,只是将异常交给调用者去处理了,如果调用者不处理依然会出现异常,除非使用try—catch
@Test
public void test04(){
Demo1 d = new Demo1();
try {
d.show();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void show() throws Exception{ //声明异常
int[] arr = null;
System.out.println(arr[1]);
}
使用try—catch块捕获异常分为三种情况:
正常:try中的代码块没有发生异常,跳过catch并向后执行
出现异常:
try中的代码块发生异常,匹配catch,进入匹配的catch执行其中的代码,执行完跳出try—catch并向后执行
异常类型不匹配:
try中的代码块发生异常,在catch中没有找到匹配的异常,程序中断(除了finally中的代码,try—catch后的代码块也不执行)
*注意:
在使用catch捕获异常的时候,需要将异常的范围从小到大(子类的异常写在上面,父类的异常写在下面;因为父类的异常范围大,写在前面,后面的子类异常就不执行了)
发生异常时按顺序逐个匹配
只执行第一个与异常类型匹配的catch语句
常见的异常类型:
异 常 类 型
说 明
ArrayIndexOutOfBoundsException
数组下标越界异常
ClassCastException
类型转换异常
NullPointerException
空指针异常
ArithmeticException
数学异常
InputMismatchException
输入类型不匹配
Exception
异常层次结构的父类
ClassNotFoundException
不能加载所需的类
IllegalArgumentException
方法接收到非法参数
NumberFormatException
数字格式转换异常,如把"abc"转换成数字
示例:
import org.junit.Test;
import java.util.*;
public class Demo1 {
Scanner tx = new Scanner(System.in);
/**
* Throwable
* - - -Error : 错误,是人为造成的,必须解决
* - - - Exception : 异常
* - - - 运行时异常:在运行的时候才会出现的异常,可以暂时不解决
*
* - - - 编译时异常:写代码的同时编译时候出现的异常,必须解决了才可以继续向后执行
*
*
*
* 面试题:常见的异常有哪些,并举例
* ArrayIndexOutOfBoundsException 数组下标越界异常
* ClassCastException 类型转换异常
* NullPointerException 空指针异常
* ArithmeticException 数学异常
* InputMismatchException 输入类型不匹配
*
*/
/**
* InputMismatchException 输入类型不匹配
* ArithmeticException 数学异常
*/
@Test
public void test03(){
/*int[] arr = new int[2];
System.out.println(arr[2]);*/
/*String str = "tom";
Object obj = str;
int num = (int)obj;
System.out.println(num);
*/
int[] arr = null;
System.out.println(arr[1]);
}
@Test
public void test02(){
try{
//编写可能出现异常的代码
System.out.print("请输入被除数:");
int num1 = tx.nextInt();
System.out.print("请输入除数:");
int num2 = tx.nextInt();
System.out.println("商是:"+num1/num2);
}catch(InputMismatchException e){ //捕获异常代码,catch可以出现多次
//可以将异常信息打印出来
//1、打印堆栈中的信息
System.out.println("111111");
System.exit(0);
e.printStackTrace();
}catch(ArithmeticException e){
//2、在控制台直接打印异常的信息
String message = e.getMessage();
System.out.println(message);
}catch(Exception e){
e.printStackTrace();
}finally{
System.out.println("这是除了exit方法之外一定会被执行的内容");
}
System.out.println("程序结束");
}
@Test
public void test01(){
System.out.print("请输入被除数:");
if(tx.hasNextInt()==true){
int num1 = tx.nextInt();
System.out.print("请输入除数:");
int num2 = tx.nextInt();
if(num2==0){
System.out.println("除数不能为0");
}
System.out.println("商是:"+num1/num2);
}else{
System.out.println("请输入整形数值");
}
}
}
自定义异常:
声明一个类继承RuntimeException
重写有参无参构造方法
最后调用
public class MyException extends RuntimeException {
public MyException(String message){
super(message);
}
public MyException(){
super();
}
}
调用
public void setSex(String sex) {
/*
* 如果赋值的是男或者女则正常赋值否则给出异常提示
*
*/
if("男".equals(sex) || "女".equals(sex)){
this.sex = sex;
} else{
//抛出异常
try {
throw new MyException("性别赋值不正确~~~");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
日志:
如果项目上线了,出现问题怎么办呢?同城同区的可以安排开发人员去现场查看调试,如果非同城,派遣工作人员就不便,这个时候就可查阅日志,找到原因然后根据原因解决问题
使用步骤:
1:加入 log4j-1.2.17.jar 将光标放在项目名上新建文件夹 lib(为了统一管理,以后有类似文件都放在这个文件夹中)
2.需要将光标点在文件上,然后 Build-path add to build path ,才可以成功的加入到项目中
2:加入日志的配置文件
在src目录下新建文本文件 文件找不到点other搜索file
文件名后缀名是 .properties
将日志文件赋值到log4j.properties文件中
3:使用
在需要记录日志的类下面导包
然后输入下面内容
Logger logger = Logger.getLogger(Demo1.class.getName());//Demo1为当前类名
@Test
public void text06(){
try{
System.out.print("请输入被除数:");
int a = tx.nextInt();
logger.debug("被除數:"+a); //在需要记录的位置下输入
System.out.print("请输入除数:");
int b = tx.nextInt();
logger.debug("除數:"+b);
System.out.println("商是:"+a/b);
logger.debug("商:"+(a/b));
}catch(InputMismatchException e){
e.printStackTrace();
System.out.println("类型");
}catch(ArithmeticException e){
System.out.println(e.getMessage());
System.out.println("数学");
}
}
配置文件说明:
Log4j由三个重要的组件构成:日志信息的优先级,日志信息的输出目的地,
日志信息的输出格式。日志信息的优先级从高到低有ERROR、WARN、 INFO、DEBUG,
分别用来指定这条日志信息的重要程度;日志信息的输出目的地指定了日志将
打印到控制台还是文件中;而输出格式则控制了日志信息的显示内容。
------------------------------------------------
appender 的配置
------------------------------------------------
org.apache.log4j.ConsoleAppender-----------控制台
org.apache.log4j.FileAppender--------------文件
org.apache.log4j.DailyRollingFileAppender--每天产生一个日志文件
org.apache.log4j.RollingFileAppender-------文件大小到达指定尺寸的时候产生一个新的文件
org.apache.log4j.WriterAppender------------将日志信息以流格式发送到任意指定的地方
------------------------------------------------
记录器的配置
-------------------------------------------------
log4j.rootLogger = [ level ] , appenderName, appenderName, …
| |---追加器名
|
|
----日志的级别
------------------------------------------------
layout 的配置
------------------------------------------------
org.apache.log4j.HTMLLayout(以HTML表格形式布局),
org.apache.log4j.PatternLayout(可以灵活地指定布局模式),
org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),
org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
---------------------------------------------------------------------------
Log4J采用类似C语言中的printf函数的打印格式格式化日志信息,打印参数如下:
---------------------------------------------------------------------------
%p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
%r 输出自应用启动到输出该log信息耗费的毫秒数
%c 输出所属的类目,通常就是所在类的全名
%t 输出产生该日志事件的线程名
%n 输出一个回车换行符,Windows平台为“rn”,Unix平台为“n”
%d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921
%l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)
%m 输出代码中指定的消息
三、对象引用与对象的区别
为便于说明,我们先定义一个简单的类:
classVehicle{intpassengers;intfuelcap;intmpg;}
有了这个模板,就可以用它来创建对象:
Vehicle veh1 = new Vehicle();
通常把这条语句的动作称之为创建一个对象,其实,它包含了四个动作。
1)右边的“new Vehicle”,是以Vehicle类为模板,在堆空间里创建一个Vehicle类对象(也简称为Vehicle对象)。
2)末尾的()意味着,在对象创建后,立即调用Vehicle类的构造函数,对刚生成的对象进行初始化。构造函数是肯定有的。如果你没写,Java会给你补上一个默认的构造函数。
3)左边的“Vehicle veh1”创建了一个Vehicle类引用变量。所谓Vehicle类引用,就是以后可以用来指向Vehicle对象的对象引用。
4)“=”操作符使对象引用指向刚创建的那个Vehicle对象。
我们可以把这条语句拆成两部分:
Vehicle veh1;
veh1 = new Vehicle();
效果是一样的。这样写,就比较清楚了,有两个实体:一是对象引用变量,一是对象本身。
在堆空间里创建的实体,与在数据段以及栈空间里创建的实体不同。尽管它们也是确确实实存在的实体,但是,我们看不见,也摸不着。不仅如此,我们仔细研究一下第二句,找找刚创建的对象叫什么名字?有人说,它叫“Vehicle”。不对,“Vehicle”是类(对象的创建模板)的名字。一个Vehicle类可以据此创建出无数个对象,这些对象不可能全叫“Vehicle”。
对象连名都没有,没法直接访问它。我们只能通过对象引用来间接访问对象。
为了形象地说明对象、引用及它们之间的关系,可以做一个或许不很妥当的比喻。对象好比是一只很大的气球,大到我们抓不住它。引用变量是一根绳,可以用来系汽球。
如果只执行了第一条语句,还没执行第二条,此时创建的引用变量veh1还没指向任何一个对象,它的值是null。引用变量可以指向某个对象,或者为null。它是一根绳,一根还没有系上任何一个汽球的绳。执行了第二句后,一只新汽球做出来了,并被系在veh1这根绳上。我们抓住这根绳,就等于抓住了那只汽球。
再来一句:
Vehicle veh2;
就又做了一根绳,还没系上汽球。如果再加一句:
veh2 = veh1;
系上了。这里,发生了复制行为。但是,要说明的是,对象本身并没有被复制,被复制的只是对象引用。结果是,veh2也指向了veh1所指向的对象。两根绳系的是同一只汽球。
如果用下句再创建一个对象:
veh2 = new Vehicle();
则引用变量veh2改指向第二个对象。
从以上叙述再推演下去,我们可以获得以下结论:(1)一个对象引用可以指向0个或1个对象(一根绳子可以不系汽球,也可以系一个汽球);(2)一个对象可以有N个引用指向它(可以有N条绳子系住一个汽球)。
如果再来下面语句:
veh1 = veh2;
按上面的推断,veh1也指向了第二个对象。这个没问题。问题是第一个对象呢?没有一条绳子系住它,它飞了。多数书里说,它被Java的垃圾回收机制回收了。这不确切。正确地说,它已成为垃圾回收机制的处理对象。至于什么时候真正被回收,那要看垃圾回收机制的心情了。
由此看来,下面的语句应该不合法吧?至少是没用的吧?
new Vehicle();
不对。它是合法的,而且可用的。譬如,如果我们仅仅为了打印而生成一个对象,就不需要用引用变量来系住它。最常见的就是打印字符串:
System.out.println(“I am Java!”);
字符串对象“I am Java!”在打印后即被丢弃。有人把这种对象称之为临时对象。
对象与引用的关系将持续到对象回收。但是,关于这一点,打算在下文“简述Java回收机制”再说。
例题:
publicinterfaceIA{voidma();}publicinterfaceIBextendsIA{voidmb();}publicinterfaceIC{voidmc();}publicinterfaceIDextendsIB,IC{voidmd();}publicclassIEimplementsID{publicvoidmb() {}publicvoidma() {System.out.println("呵呵");}publicvoidmc() {System.out.println("哈哈");}publicvoidmd() {}}publicclassTestIE{publicstaticvoidmain(Stringargs[]){ICic=newIE();newIE().ma();//调用ma方法只能新建一个IE对象来调用newIE().mc();//调用mc方法可以新建一个IE对象来调用,也可以用ic.的方式调用ic.mc();}}publicclassTestIE{publicstaticvoidmain(Stringargs[]){ICic=newIE();System.out.println(icinstanceofIA);System.out.println(icinstanceofIB);System.out.println(icinstanceofIC);System.out.println(icinstanceofID);System.out.println(icinstanceofIE);//五个都是对的,因为instanceof是Java中的二元运算符,左边是对象,右边是类;当对象是右边类或子类所创建对象时,返回true;否则,返回false。}}
四、多态性理解
1. Java中的多态性理解(注意与C++区分)
Java中除了static方法和final方法(private方法本质上属于final方法,因为不能被子类访问)之外,其它所有的方法都是动态绑定,这意味着通常情况下,我们不必判定是否应该进行动态绑定—它会自动发生。
1. final方法会使编译器生成更有效的代码,这也是为什么说声明为final方法能在一定程度上提高性能(效果不明显)。
2. 如果某个方法是静态的,它的行为就不具有多态性:
class StaticSuper {public static String staticGet() {return "Base staticGet()";}public String dynamicGet() {return "Base dynamicGet()";}}class StaticSub extends StaticSuper {public static String staticGet() {return "Derived staticGet()";}public String dynamicGet() {return "Derived dynamicGet()";}}public class StaticPolymorphism {public static void main(String[] args) {StaticSuper sup = new StaticSub();System.out.println(sup.staticGet());System.out.println(sup.dynamicGet());}}
输出:
Base staticGet()
Derived dynamicGet()
构造函数并不具有多态性,它们实际上是static方法,只不过该static声明是隐式的。因此,构造函数不能够被override。
在父类构造函数内部调用具有多态行为的函数将导致无法预测的结果,因为此时子类对象还没初始化,此时调用子类方法不会得到我们想要的结果。
class Glyph {void draw() {System.out.println("Glyph.draw()");}Glyph() {System.out.println("Glyph() before draw()");draw();System.out.println("Glyph() after draw()");}}class RoundGlyph extends Glyph {private int radius = 1;RoundGlyph(int r) {radius = r;System.out.println("RoundGlyph.RoundGlyph(). radius = " + radius);}void draw() {System.out.println("RoundGlyph.draw(). radius = " + radius);}}public class PolyConstructors {public static void main(String[] args) {new RoundGlyph(5);}}
输出:
Glyph() before draw()
RoundGlyph.draw(). radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(). radius = 5
为什么会这样输出?这就要明确掌握Java中构造函数的调用顺序:
(1)在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制0;(2)调用基类构造函数。从根开始递归下去,因为多态性此时调用子类覆盖后的draw()方法(要在调用RoundGlyph构造函数之前调用),由于步骤1的缘故,我们此时会发现radius的值为0;(3)按声明顺序调用成员的初始化方法;(4)最后调用子类的构造函数。
只有非private方法才可以被覆盖,但是还需要密切注意覆盖private方法的现象,这时虽然编译器不会报错,但是也不会按照我们所期望的来执行,即覆盖private方法对子类来说是一个新的方法而非重载方法。因此,在子类中,新方法名最好不要与基类的private方法采取同一名字(虽然没关系,但容易误解,以为能够覆盖基类的private方法)。
Java类中属性域的访问操作都由编译器解析,因此不是多态的。父类和子类的同名属性都会分配不同的存储空间,如下:
// Direct field access is determined at compile time.class Super {public int field = 0;public int getField() {return field;}}class Sub extends Super {public int field = 1;public int getField() {return field;}public int getSuperField() {return super.field;}}public class FieldAccess {public static void main(String[] args) {Super sup = new Sub();System.out.println("sup.filed = " + sup.field +", sup.getField() = " + sup.getField());Sub sub = new Sub();System.out.println("sub.filed = " + sub.field +", sub.getField() = " + sub.getField() +", sub.getSuperField() = " + sub.getSuperField());}}
输出:
sup.filed = 0, sup.getField() = 1
sub.filed = 1, sub.getField() = 1,
sub.getSuperField() = 0
Sub子类实际上包含了两个称为field的域,然而在引用Sub中的field时所产生的默认域并非Super版本的field域,因此为了得到Super.field,必须显式地指明super.field。
五、向上转型和向下转型
在我的理解:java的向上和向下转型可以看成是类型的转换。
publicclassPerson{publicvoideat(){System.out.println("Person eatting...");}publicvoidsleep() {System.out.println("Person sleep...");}}publicclassSupermanextendsPerson{publicvoideat() {System.out.println("Superman eatting...");}publicvoidfly() {System.out.println("Superman fly...");}}
测试向上转型的主方法:
publicclassMain{publicstaticvoidmain(String[]args) {Personperson=newSuperman();person.sleep();//调用的是父类person的方法person.eat();// 调用的是Superman里面的eat方法,Superman重写了Person父类的方法//person.fly(); //报错了,丢失了Superman类的fly方法}}
运行的结果:Person sleep…Superman eatting…
分析:当在执行Person person = new Superman()时,我们来看看它的内存存储:
从此图我们可以看出 向上转型会丢失子类的新增方法,同时会保留子类重写的方法。
测试向下转型的主方法:
publicclassMain{publicstaticvoidmain(String[]args) {Personperson=newSuperman();Supermans=(Superman)person;//向下转型s.sleep();s.fly();s.eat();}}
运行的结果:Person sleep…Superman fly…Superman eatting…
分析:当在执行Superman s = (Superman)person;时,我们来看看他们的内存存储:
在这里我们看出 向下转型可以得到子类的所有方法(包含父类的方法)。
六、栈和堆
JAVA在程序运行时,在内存中划分5片空间进行数据的存储。分别是:1:寄存器。2:本地方法区。3:方法区。4:栈。5:堆。
基本,栈stack和堆heap这两个概念很重要,不了解清楚,后面就不用学了。
以下是这几天栈和堆的学习记录和心得。得些记录下来。以后有学到新的,会慢慢补充。
一、先说一下最基本的要点
基本数据类型、局部变量都是存放在栈内存中的,用完就消失。new创建的实例化对象及数组,是存放在堆内存中的,用完之后靠垃圾回收机制不定期自动消除。
二、先明确以上两点,以下示例就比较好理解了
示例1
main() int x=1;show () int x=2
主函数main()中定义变量int x=1,show()函数中定义变量int x=1。最后show()函数执行完毕。
以上程序执行步骤:
第1步——main()函数是程序入口,JVM先执行,在栈内存中开辟一个空间,存放int类型变量x,同时附值1。第2步——JVM执行show()函数,在栈内存中又开辟一个新的空间,存放int类型变量x,同时附值2。 此时main空间与show空间并存,同时运行,互不影响。第3步——show()执行完毕,变量x立即释放,空间消失。但是main()函数空间仍存在,main中的变量x仍然存在,不受影响。
示例2
main() int[] x=new int[3]; x[0]=20
主函数main()中定义数组x,元素类型int,元素个数3。
以上程序执行步骤第1步——执行int[] x=new int[3]; 隐藏以下几分支 JVM执行main()函数,在栈内存中开辟一个空间,存放x变量(x变量是局部变量)。 同时,在堆内存中也开辟一个空间,存放new int[3]数组,堆内存会自动内存首地址值,如0x0045。 数组在栈内存中的地址值,会附给x,这样x也有地址值。所以,x就指向(引用)了这个数组。此时,所有元素均未附值,但都有默认初始化值0。
第2步——执行x[0]=20 即在堆内存中将20附给[0]这个数组元素。这样,数组的三个元素值分别为20,0,0
示例3main() int[] x=new int[3]; x[0]=20 x=null;
以上步骤执行步骤第1、2步——与示例2完全一样,略。
第3步——执行x=null; null表示空值,即x的引用数组内存地址0x0045被删除了,则不再指向栈内存中的数组。此时,堆中的数组不再被x使用了,即被视为垃圾,JVM会启动垃圾回收机制,不定时自动删除。
示例4main() int[] x=new int[3]; int[] y=x; y[1]=100 x=null;
以上步骤执行步骤
第1步——与示例2第1步一致,略。第2步——执行int[] y=x, 在栈内存定义了新的数组变量内存y,同时将x的值0x0045附给了y。所以,y也指向了堆内存中的同一个数组。第3步——执行y[1]=100 即在堆内存中将20附给[0]这个数组元素。这样,数组的三个元素值分别为0,100,0第4步——执行x=null 则变量x不再指向栈内存中的数组了。但是,变量y仍然指向,所以数组不消失。
示例5
Car c=new Car;c.color="blue";Car c1=new Car;c1.num=5;
虽然是个对象都引用new Car,但是是两个不同的对象。每一次new,都产生不同的实体
示例6
Car c=new Car;c.num=5;Car c1=c;c1.color="green";c.run();
Car c1=c,这句话相当于将对象复制一份出来,两个对象的内存地址值一样。所以指向同一个实体,对c1的属性修改,相当于c的属性也改了。
三、栈和堆的特点
栈:
函数中定义的基本类型变量,对象的引用变量都在函数的栈内存中分配。栈内存特点,数数据一执行完毕,变量会立即释放,节约内存空间。栈内存中的数据,没有默认初始化值,需要手动设置。
堆:
堆内存用来存放new创建的对象和数组。堆内存中所有的实体都有内存地址值。堆内存中的实体是用来封装数据的,这些数据都有默认初始化值。堆内存中的实体不再被指向时,JVM启动垃圾回收机制,自动清除,这也是JAVA优于C++的表现之一(C++中需要程序员手动清除)。
注:
什么是局部变量:定义在函数中的变量、定义在函数中的参数上的变量、定义在for循环内部的变量
喜欢前端、后端java开发的可以加+qun:609565759,有详细视频、资料、教程,文档,值得拥有!!!希望可以一起努力,加油ヾ(◍°∇°◍)ノ゙!!!