疯狂Java程序员的基本素养学习笔记

学习一门技术,就要深入,投身一门职业,就要专注。作为一名Java程序员,不仅要知道怎么去用这些技术、框架,而且要知道这些技术、框架是怎么运行的、怎么实现的。知其然而知其所以然。学习不能浮躁,循序渐进。


一、数组及其内存管理

栈内存、堆内存、方法区

问题:

Java中,声明并创建数组的过程中,内存是如何分配的?

Java数组的初始化方式有哪几种?

基本类型数组和引用类型数组,在初始化时的内存分配机制的区别?

 

数组,复合结构,Java语言的数组变量是引用类型的变量。Java语言是典型的静态语言,Java数组也是静态的,数组初始化之后,所占的内存空间、长度是不可变的,必须经过初始化后才能使用。

  • 初始化方式

静态初始化,显示指定每个数组元素的初始值,由系统决定数组长度

动态初始化,只指定数组长度,系统为数组元素分配初始值

注意:静态初始化和动态初始化不能同时使用,即不能同时指定数组的长度和为每个数组元素分配初始值。

Java数组变量是引用类型的变量,数组变量并不是数组本身,它指向堆内存中的数组对象(相当于指针)

 

Java是强类型语言,JavaScript是弱类型语言。

Java程序中的引用变量并不需要经过所谓的初始化操作,需要进行初始化的是引用变量所引用的对象

  • 基本类型的数组,数组元素的值直接存储在对应的数组元素中

int [] a;

方法栈中定义一个a数组变量,引用类型的变量,并未指向任何有效的内存,没有真正指向实际的数组对象,因此还不能使用数组对象。

a = new int[]{2,1,3};

静态初始化,系统决定长度,a指向堆内存空间。

注意:所有局部变量都放在栈内存中保存,即存在各自的方法栈内存中。引用类型的变量所引用的对象总是存储在堆内存中。堆内存中的对象通常不允许直接访问。main方法声明的变量都属于局部变量,故都保存在main方法栈区中。

Runtime异常:NullPointerException(空指针异常),当通过引用变量来访问实例属性,或调用非静态方法时,如果该引用变量还未引用一个有效的对象,程序就会引发NullPointerException运行时异常。

  • 引用类型数组,数组元素里存储的还是引用,它指向另一块内存,该内存存储了该引用变量所引用的对象

注意:Java语言避免访问堆内存中的数据可以保护程序更加健壮,如果程序直接访问并修改堆内存中的数据,可能会破坏内存中的数据完整性,导致程序Crash。

Java允许将多维数组当成一维数组处理。通过数组的length属性获取数组的长度前提是把N维数组当成数组元素N-1维数组的一维数组


二、对象及其内存管理

问题:

为什么要创建这么多的实例?

对象创建对系统的开销

Java对象的内存分配机制


可以参考:

http://imtianx.cn/2016/11/19/java%20%E7%9F%A5%E8%AF%86%E4%B9%8B%20%E5%AF%B9%E8%B1%A1%E5%8F%8A%E5%85%B6%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/


Java内存管理

内存分配:特指创建Java对象时JVM为该对象在堆内存中所分配的内存空间

内存回收:当Java对象失去引用,变成垃圾时,JVM的垃圾回收机制自动清理对象,并回收该对象所占用的内存


注意:Java程序依然会有内存泄漏


JVM的垃圾回收机制由一条后台线程完成,非常消耗性能。创建没用的对象,坏处:

不断分配内存使得系统中可用的内存减少

大量已分配内存的回收使得垃圾回收的负担加重

都会降低程序的运行性能

  • Java程序的变量

成员变量(类体内定义的变量):非静态变量/实例变量,静态变量/类变量

局部变量:形参、方法内的局部变量、代码块内的局部变量,局部变量的作用时间很短,存储在栈内存中


注意:

static就是一个标志,static只能修饰类里的成员,不能修饰外部类,不能修饰局部变量、局部内部类,static的作用是将实例成员变为类成员

 

Java中定义成员变量时要注意前向引用同一个JVM中,每个类对应一个Class对象,但每个类可以创建多个Java对象


类也是对象,所有类都是Class的实例。每个类初始化完成之后,系统都会为该类创建一个对应的Class实例,程序可以通过反射获取对应的Class实例。

Person.class;

Class.forName(“Person”);

类的实例也可以访问类变量,这样设计似乎有些不合理。类的对象访问类变量本质也是转换为类访问类变量

  • 实例变量的初始化时机(非静态变量)

每次创建Java对象都需要为实例变量分配内存空间,对实例变量执行初始化

1、定义实例变量时指定初始值

2、非静态初始化块中对实例变量指定初始值

3、构造器中对实例变量指定初始值

(第1/2方式比第3方式更早执行,1/2方式执行顺序与源代码中的排列顺序相同)

分析:

double w = 2;

double w;创建Java对象时系统根据该语句为对象分配内存

w = 2;这条语句会在Java类的构造器中执行

  • 类变量的初始化时机

每个JVM对一个Java类只初始化一次,为该类的类变量分配内存空间,指定默认值。

1、定义类变量时指定初始值

2、静态初始化块中对类变量指定初始值

(1/2方式的执行顺序与排列顺序相同)

 

创建Java对象时,程序总会依次调用每个父类的非静态初始化块、构造器执行初始化然后才调用本类的非静态初始化块、构造器执行初始化

 

注意:

super调用用于显示调用父类构造器,this调用用于显示调用本类中另一个重载的构造器。super和this调用都只能在构造器中使用,都必须作为构造器的第一行代码

只能使用其中之一,而且只能最多只能调用一次

  • 子类与父类中的实例变量、方法的区别

一个Java对象可以拥有多个同名的实例变量,子类定义的成员变量并不能完全覆盖父类中成员变量

当变量的编译时类型和运行时类型不同时,通过该变量访问引用的对象的实例变量时,该实例变量的值由声明该变量的类型决定

  • 父子实例的内存控制

Java继承中对成员变量和方法的处理是不同的。 

当通过变量调用方法时,方法的行为总是表现出它们实际类型的行为,访问它们的所知对象的实例变量时,实例变量的值总是表现出变量所引用类型的行为。(方法可以被重写,变量不存在重写

当子类使用public访问控制修饰符,父类不使用public修饰符,才可以通过javap看到编译器将父类的public方法直接转移到子类中

子类对象的存储内存中不存在父类对象,只存在一个子类对象,只是保存了它所有父类所定义的全部实例变量

Java程序中允许某个方法通过return this;返回调用该方法的Java对象,但不能直接使用return super;甚至不允许直接将super当成一个引用变量使用。super关键字本身并没有引用任何对象

1、子类方法不能直接使用return super;允许某个方法通过return this;返回调用该方法的Java对象

2、甚至不允许直接将super当成一个引用变量使用。Super == a;引起编译错误

子类中,一般用super.作为限定来修饰实例变量、方法

 

访问父类的静态变量/类变量,方式

1、使用父类的类名(推荐,代码可读性)

2、使用super.限定来访问

  • final修饰符

1、final修饰的变量,赋值后,不能重新赋值

final实例变量:

必须显示指定初始值

三种赋初始值的方式,都会抽到构造器中赋初始值(本质一样)


final类变量:

必须显示指定初始值

只能在定义时、静态初始化块中指定初始值

 

注意:

初始值在编译时就被确定下来,系统不会在静态初始化块中对该类变量赋初始值

 

final修饰符定义“宏变量”,定义时就赋初始值,编译器会把程序中所有用到该变量的地方直接替换成值

对于final实例变量,只有在定义该变量时指定初始值才会有“宏变量”的效果

对于final类变量,只有在定义时指定初始值才有“宏变量”的效果

2、final修饰的方法,不能重写

如果程序需要在匿名内部类中使用局部变量,必须使用final修饰符修饰

在任何内部类中访问的局部变量都应该使用final修饰

此处内部类为局部内部类,局部内部类才可以访问局部变量,普通静态内部类、非静态内部类不可能访问方法体内的局部变量

内部类可能产生隐式的“闭包”,闭包将使得局部变量脱离其所在的方法继续存在

内部类会扩大局部变量作用域的实例


3、final修饰的类,不能派生子类

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值