常见面试问题整理系列之--Java基础

1. 面向对象三大特性
面向对象三个基本特征:封装、继承、多态;
Java语言以对象为中心,最小单位为类。
封装:封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口;
继承:继承是从已有类得到继承信息创建新类的过程;
多态:多态是指允许不同子类型的对象对同一消息作出不同的响应。

2. 创建对象的方式
(1)使用new语句创建对象 这是最常见的方式
(2)使用反射手段,调用java.lang.Class或java.lang.reflect.Constructor类的newInstance()方法
(3)调用对象的clone方法 需要实现Cloneable接口
(4)运用反序列化手段 调用java.io.ObjectInputStream对象的readObject方法
(5)创建子类对象时,如果父类对象未被创建......
其中1,2,5都会调用构造函数,3是在内存上对已有对象的影印 所以不会调用构造函数,4是从文件中还原类的对象 也不会调用构造函数;
Class.newInstance只能调用无参的构造函数;Constructor.newInstance可以调用有参和私有的构造函数;
Employee emp = (Employee) Class.forName("com.test.Employee").newInstance();
or
Employee emp = Employee.class.newInstance();

Constructor<Employee> constructor = Employee.class.getConstructor();
Employee emp2 = constructor.newInstance();

无论何时调用一个对象的clone方法,JVM都会创建一个新的对象,将前面对象的内容全部拷贝进去;用clone方法创建对象并不会调用任何构造函数;要使用clone方法,我们需要先实现Cloneable接口并实现其定义的clone方法
Employee emp3 = (Employee) emp2.clone();

为了反序列化一个对象,我们需要让类实现Serializable接口;
ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));
Employee emp4 = (Employee) in.readObject();

3. 什么是对象的序列化与反序列化,用在什么地方,如何使用
Java中的序列化指的是将对象转换成以字节序列的形式来表示,这些字节序列包含了对象的数据信息,一个序列化后的对象可以被写到数据库或文件中,也可用于网络传输。一般当我们使用缓存cache(内存空间不够有可能会本地存储到硬盘)或远程调用RPC(网络传输)的时候,经常需要让我们的实体类实现Serializable接口,目的就是让其可序列化(交互双方的一个公共标准);
序列化后的最终目的是为了反序列化,恢复成原先的Java对象,所以序列化后的字节序列都是可以恢复成Java对象的,这个过程就是反序列化;
一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。也可以认为在将持久化的对象反序列化后,被transient修饰的变量将按照普通类成员变量一样被初始化;
一个静态变量不管是否被transient修饰,均不能被序列化,因为是类级别的;
最后,为什么要不被序列化呢,主要是为了节省存储空间。

4. JVM的实现原理、内存模型
Java语言既是编译型语言,又是解释型语言:Java源码通过javac命令被编译成.class文件,这种字节码文件不面向任何平台,只面向JVM(Java Virtual Machine);JVM是Java跨平台的关键部分,其向上提供给Java字节码程序的接口完全相同,而向下适应不同平台的接口则互不相同,为特定平台提供特定机器码,使用java命令解释执行;
程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭,因此这几个区域的内存分配和回收都具备确定性,就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了;而Java堆区和方法区的分配和回收是动态的,是GC需要关注的部分。

5. GC原理
要请求垃圾收集,可以将相关对象设置为null或调用System.gc() ,但后者将会严重影响代码性能,因为一般每一次显式调用System.gc()都会停止所有的响应,去检查内存中是否有可回收的对象,这样会对程序的正常运行造成极大的威胁。另外,调用该方法并不能保证JVM立即进行垃圾回收,仅仅是通知JVM要进行垃圾回收了,具体回收与否完全由JVM决定,这样做是费力不讨好;
垃圾回收器通常是作为一个单独的低优先级的守护线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收;
有两种算法可以判定对象是否存活:
(1)引用计数法,但是它很难解决两个对象之间相互循环引用的情况;
(2)可达性分析法,在主流的商用程序语言(如我们的Java)的主流实现中,都是通过可达性分析算法来判定对象是否存活的;
堆分新生代与老年代,具体操作流程:
新生代:包含Eden与两个Survivor,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块Survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又被送入第二块Survivor space S1,S0和Eden被清空,然后下一轮S0与S1交换角色,如此循环往复。如果对象的复制次数达到16次,该对象就会被送到老年代中;
与垃圾回收相关的JVM参数:
-Xms / -Xmx — 堆的初始大小 / 堆的最大大小
-Xmn — 堆中新生代的大小(同时设置XX:NewSize=XX:MaxNewSize),实际可用空间 = Eden + 1个Survivor,即90%
-Xss — 每个线程堆栈的大小,一般来说如果栈不是很深的话,1M绝对够用了
-XX:NewSize / -XX:MaxNewSize — 设置新生代大小/新生代最大大小
-XX:NewRatio — 新生代与老年代的比例,2代表新生代占整个堆空间的1/3,老年代占2/3
常见垃圾回收方式:
标记清除法:这是垃圾收集算法中最基础的,分为标记和清除两个阶段,它的思想是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是效率不高,标记和清除的效率都很低。此外会产生大量不连续的内存碎片,从而导致以后程序在分配较大对象时由于没有充足的连续内存而提前触发一次 GC 操作;
复制算法:为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完后就将还存活的对象复制到第二块内存上,然后一次性清除完第一块内存,再将第二块上的对象复制到第一块。但是这种方式内存的代价太高,每次基本上都要浪费一半的内存;于是将该算法进行了改进,内存区域不再是按照1: 1去划分,而是将内存划分为8: 1: 1三部分,较大那份内存是Eden区,其余是两块较小的内存区叫Survior区,每次都会优先使用Eden区,若Eden区满则将对象复制到第二块内存区上,然后清除Eden区,如果此时存活的对象太多,以至于Survivor不够时,会将这些对象通过分配担保机制复制到老年代中;
标记整理法:这种方法主要是为了解决标记清除法产生大量内存碎片的问题。当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候先将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了,但是会增加停顿时间;
分代收集法:现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法,新生代又分为Eden和两个Survivor,大小比例默认8: 1: 1。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记整理法或标记清除法。
大对象直接进入老年代:JVM中有个参数配置-XX: PretenureSizeThreshold,令大于这个设置值的对象直接进入老年代,目的是为了避免在Eden和Survivor区之间发生大量的内存复制。
Java对象使用后设置为null并不会减少内存占用,设置为null只是栈中指向的引用为null,但是new出来的对象还是存在于堆里面的,需要等到GC算法调用时才能将没有栈指向的对象给回收掉。

6. Java中的集合类有哪些
Java集合大致可分为Set、List、Queue、Map四种体系;
Set:代表无序、不可重复;
List:代表有序、可重复;
Queue:代表一种队列集合的实现;
Map:代表具有映射关系的集合。
Java集合主要有两个接口派生而出:Collection和Map,Collection接口是Set、List、Queue接口的父接口;
Set与Collection基本相同,没有额外提供其它方法,实际上Set就是Collection,只是行为略有不同,Set不允许包含重复元素,添加同一对象两次到集合中,第二次会操作失败,add返回false;
List集合允许使用重复元素,可以通过索引来访问指定位置的元素;
Map与Set的关系:如果把Map里所有key放在一起看,它们就组成了一个Set集合(所有的key没有顺序,key与key之间不重复),实际上Map确实包含了一个keySet方法,用户返回Map里所有key组成的Set集合;
Map与List的关系:如果把Map里所有value放在一起看,它们就组成了一个List,元素与元素之间可以重复,每个元素可以根据索引来查找,只是Map中索引不再使用整数值,而是以另外一个对象作为索引。

7. LinkedList与ArrayList的区别
二者都允许null元素;
ArrayList的内部实现是基于内部动态数组Object[],所以从概念上讲,它更像数组,但LinkedList的内部实现是基于一组连接的记录,所以它更像一个链表结构。ArrayList更适合get和set,因为LinkedList本质是双向链表,需要从头移动指针进行查找,而ArrayList可以直接按序号索引元素;LinkedList更适合add和remove,因为ArrayList需要移动数据(直接在数组末尾add性能也很高,但是如果按照下标来插入、删除元素,就需要通过Arrays.copyof函数来移动部分受影响的元素,性能变差);
ArrayList在要增加数据时,都会调用ensureCapacity方法来确保有足够的容量。当容量不够时,就设置新的容量为旧容量的1.5倍(默认容量为10),如果还不够,就直接将容量设置为所需容量,之后用Arrays.copyof方法将元素拷贝到新数组。因此,当容量不够时,每次增加元素都需要将原来的元素拷贝到一个新数组中,非常耗时,因此建议在事先知道元素数量的情况下再使用ArrayList,否则使用LinkedList;
ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间,就存储密度来说,ArrayList是优于LinkedList的;
总之,当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能,当操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。

8. 对HashMap的理解
(1)HashMap的功能是通过key能够快速地找到value;
(2)HashMap是基于数组来实现哈希表的,数组就好比内存储空间,数组的index就好比内存的地址;
(3)HashMap的每个记录就是一个Entry<K, V>对象,数组中存储的就是这些对象,其中包含了key、value、hash、next四种结构元素;
(4)HashMap的哈希函数(put操作):当key为空,通过putForNullKey方法把元素放到最开始位置(table[0]),否则根据传递的key值得到hashCode(int hash=key.hashCode();),然后用这个hashCode与数组长度进行运算,得到一个int值,就是Entry要存储在数组的位置(下标);
(5)HashMap解决冲突:使用链地址法,每个Entry对象都有一个引用next来指向链表的下一个Entry(如果链表长度超过阈值8,就把链表转换成红黑树,链表长度低于6,就把红黑树转回链表);
(6)HashMap的装填因子:默认为0.75(用于判断是否需要扩展HashMap的容量,扩大为原来的两倍,HashMap的默认length是16);
(7)HashMap的get操作:当key为空,则直接去哈希表的第一个位置table[0]对应的链表上查找,否则根据key算出hash值(数组下标),之后获取对应的Entry,然后判断Entry里的key,hash值或者通过equals比较是否与要查找的相同,相同则返回value,否则遍历该链表,直到找到为止,否则返回null(在HashMap中,通过get方法返回null,可能是HashMap中没有该键,也可能是该键对应的值为null,因此不能用get方法判断HashMap中是否存在某个键,需要使用containsKey方法);
(8)HashMap是非线程安全的,多线程环境下可以采用concurrent并发包下的ConcurrentHashMap,或使用如下初始方式:Map m = Collections.synchronizedMap(new HashMap(...));
(9)HashMap实现了Serializable接口,因此支持序列化,实现了Cloneable接口,能被克隆。

9. hashCode与equals方法的关系
hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率;
如果equals方法得到的结果为true,两个对象hashCode值必定相等;
如果equals方法得到的结果为false,两个对象hashCode值不一定不同;
如果两个对象hashCode值不等,equals方法的结果必定为false;
如果两个对象hashCode值相等,equals方法得到的结果未知。
在重写equals方法时,必须重写hashCode方法。

10. HashMap与HashTable方法的区别
(1)二者的存储结构与解决冲突的方法都是相同的;
(2)HashTable在不指定容量的情况下默认为11,而HashMap为16;HashTable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂;
(3)HashTable中的key和value都不允许为null,而HashMap中key和value都允许为null(key只能有一个为null,value可以有多个为null);但是如果在HashTable中有类似put(null, null)的操作,编译同样可以通过,因为key和value都是Object类型,但运行时会抛出NullPointerException异常;
(4)HashTable扩容时,将容量变为原来的2倍+1,而HashMap扩容时,将容量变为原来的2倍;
(5)HashTable计算hash值,直接用key的hashCode(),而HashMap重新计算了key的hash值;HashTable在求hash值对应的位置索引时,用的取模运算,而HashMap在求位置索引时,则用与运算,且这里一般先用hash&0x7FFFFFFF后,再对length计算,目的是将负的hash值转换为正值;
(6)HashMap拿掉了HashTable的contains方法(比较的是value是否是equal的),因为此方法容易引起误解;两个类都包含containsKey和containsValue方法;
(7)HashTable是线程安全的,HashMap是线程非安全的。

11. 数组与链表的区别
数组的特点:
(1)在内存中,数组是一块连续的区域,通过数组下标进行访问,下标从0开始;
(2)数组需要预留空间,在使用前要先申请占内存的大小,可能会浪费内存空间;
(3)插入和删除数据效率低,插入数据时,这个位置后面的数据在内存中都要向后移;删除数据时,这个数据后面的数据都要向前移;
(4)随机读取效率高,因为数组是连续的,知道每一个数据的内存地址,可以直接找到指定的数据;
(5)不利于扩展,数组定义的空间不够时需要重新定义新数组然后复制。
数组的优点:
(1)随机访问性强
(2)查找速度快
数组的缺点:
(1)插入和删除效率低,因为要移动其它的元素
(2)可能浪费内存
(3)内存空间要求高,必须有足够的连续内存空间
(4)数组大小固定,不能动态扩展
适用场景:
(1)频繁查询,对存储空间要求不大,很少增加和删除的情况。
链表的特点:
(1)在内存中可以存在在任何地方,不要求连续;
(2)每一个数据都保存了下一个数据的内存地址,通过这个地址找到下一个数据;
(3)增加数据和删除数据很容易;
(4)查找数据时效率很低,因为不具有随机访问性,所以访问某个位置的数据都要从第一个数据开始;
(5)不指定大小,扩展方便,链表大小不用定义,数据随意增删。
链表的优点:
(1)插入删除速度快
(2)内存利用率高,不会浪费内存
(3)大小没有固定,扩展灵活
(4)不需要初始化容量
链表的缺点:
(1)不能随机查找,必须从第一个开始遍历,查找效率低
(2)因为含有大量的指针域,占用空间较大
适用场景:
(1)数据量较小,需要频繁增加,删除操作的场景。

12. 对类加载过程的理解
BootStrapClassLoader对应JRE/lib/rt.jar;
ExtClassLoader对应JRE/lib/ext/*.jar;
AppClassLoader对应Classpath指定的所有jar或目录(默认的类加载器);
双亲委托机制:
当一个类加载器接收到一个类加载任务时,不会立即展开加载,而是将加载任务委托给它的父类加载器去执行,每一层的类加载器都采用相同的方式,直到委托给最顶层的启动类加载器为止。如果父类加载器无法加载委托给它的类,便将类的加载任务退回给下一级类加载器去执行加载。

13. Java中如何使用JDBC创建一个事务并提交

java.sql.Connection conn = null;
java.sql.PreparedStatement stmt = null;
try {
    conn = DriverManager.getConnection("jdbc:oracle:thin:@host:1521:SID", "username", "passwd");
    // 将自动提交设置为false,若设置为true,则数据库会把每一次数据更新认定为一个事务并自动提交
    conn.setAutoCommit(false);
    stmt = conn.createStatement();
    // 将A账户中的金额减少500
    stmt.execute("update t_account set amount = amount - 500 where account_id = 'A'");
    // 将B账户中的金额增加500
    stmt.execute("update t_account set amount = amount + 500 where account_id = 'B'");
    // 提交事务
    conn.commit();
} catch (Exception e){
        conn.rollback();
} finally {
    stmt.close();
    conn.close();
}

 
14. Java如何实现多继承
使用内部类可以实现多继承,直接上代码:

public class Test {
    private class MyCall extends Call {
        
    }
    private class MySendMessage extends SendMessage {
        
    }
    private MyCall call = new MyCall();
    private MySendMessage sm = new MySendMessage();
    
    public void call(String phoneNumber) {
        call.call(phoneNumber);
    }
    public void send(String phoneNumber) {
        sm.send(phoneNumber);
    }
    
    public static void main(String[] args) {
        Test t = new Test();
        t.call("110");
        t.send("119");
    }
}
class Call {
    public void call(String phoneNumber) {
        System.out.println("call the number: " + phoneNumber);
    }
}
class SendMessage {
    public void send(String phoneNumber) {
        System.out.println("send the number: " + phoneNumber);
    }
}

 
15. String、StringBuilder、StringBuffer比较
三个类的主要区别在于运行速度与线程安全;
运行速度:StringBuilder > StringBuffer > String
原因:StringBuilder和StringBuffer是变量,String是常量;若String str = "abc"; str += "de";,str被初始化为abc,之后jvm创建一个新对象"abcde",将这个新对象赋值给str,而原来的"abc"会被垃圾回收机制回收,所以String对象的操作实际上是一个不断创建新对象并且将旧对象回收的过程;而对StringBuilder和StringBuffer的操作是直接对变量进行更改,不包含对象的创建与回收操作;
String str = "abc" + "de"; //这个操作作用和String str = "abcde";相同,所以会很快,
String str1 = "abc", str2 = "de"; str1 = str1 + str2; //这个操作会很慢;
线程安全:StringBuilder是线程不安全的,StringBuffer是线程安全的;
String:适用于少量的字符串操作的情况;
StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况;
StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况。

16. String与Integer的转换
Integer转换为String:
Integer i = 1;
System.out.println(i.toString());
System.out.println(Integer.toString(i));
System.out.println(String.valueOf(i));
String转换为Integer:
String str = "...";
Integer i = null;
if (str != null) {
    i = Integer.valueOf(str);
}
(也可以使用构造方法)

17. Java中去除字符串空格用什么函数
trim:去掉首尾的空格;
replace:去掉所有的空格,包括首尾、中间;将oldchar替换为newchar;
str = str.replace(" ", ""); //去掉所有空格;
replaceAll:去掉所有的空格;参数为regex(正则表达式);
str = str.replaceAll("\\s*", ""); //替换大部分空白字符,不限于空格;
str = str.replaceAll(" +", ""); //去掉所有空格;
\s可以匹配空格、制表符、换页符等空白字符的其中任意一个。

18. 自动装箱与拆箱的原理
自动装箱:当给一个Integer对象赋int值时,会调用Integer.valueOf方法,如果数值在-128~127之间,就会被缓存在内存中;如果不在这个范围内,就去new一个Integer对象。
自动拆箱:作用在包装类型上的二元运算符会导致拆箱操作,拆箱的实质是调用intValue方法;而==与!=二元运算符比较特殊,一个包装类型和一个基本类型或表达式(即包含算数运算)进行==或!=比较,会首先对包装类型进行拆箱操作,之后再比较;而对两个包装类型进行==或!=操作,会直接比较对象地址。

19. 值传递与引用传递的原理
值传递:函数接收的是原始值的一个copy,此时内存中存在两个相等的基本类型,即实际参数和形式参数,后面方法的操作都是对形参的修改,不影响实际参数的值;
引用传递:函数接收的是原始值的内存地址,在方法执行中,形参和实参内容相同,指向同一块内存地址,方法执行中对引用的操作将会影响到实际对象;
public void swap(Student x, Student y) {
    Student temp = x;
    x = y;
    y = temp;
}
swap方法执行过程:
(1)将对象a、b的拷贝分别赋给x、y,此时a和x指向同一对象,b和y指向同一对象;
(2)swap方法完成x、y的交换,a、b并没有变化;
(3)方法执行完毕,x、y不再使用,a、b依旧指向之前的对象;
综上,Java的参数传递方式为:值传递。

20. 方法覆盖与方法重载的比较
方法重载:发生在编译时,被称为编译时多态,编译器可以根据参数类型选择使用哪个方法;
方法覆盖(重写):发生在运行时,被称为运行时多态,因为在编译期编译器不知道也没法知道去调用哪个方法,JVM会在代码运行时做出决定;
方法的重写要遵循“两同两小一大”规则,“两同”即方法名相同、形参列表相同;“两小”即子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出异常类应该比父类方法声明抛出的异常类更小或相等;“一大”指的是子类方法的访问权限应比父类方法的权限更大或相等;尤其需要指出的是,覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法,一个是实例方法;
方法重载只与“两同一不同”有关:同类名,同方法名,不同参数列表。

21. 接口与抽象类的比较
(1)接口是公开的,public static final的Field,public abstract的method;抽象类可以有私有方法和私有变量,可以有构造器;
(2)接口是has-a关系;抽象类是is-a关系;
(3)一个类只能继承一次,但是可以实现多个接口;
(4)一般的应用,最顶层是接口,之后是抽象类,然后是实现类;
(5)举例:Door具有open和close功能,如果现在想添加一个报警功能alarm,最好的方案是设计一个抽象类Door,包含open和close方法;再设计一个接口包含alarm方法;因为Door都具有open和close方法而不一定具有alarm功能,具有alarm功能的不一定是Door,open与alarm本质上不是同一个层面上的东西;open和close对于Door来说是is-a关系,alarm对于Door来说是has-a关系。

22. 泛型的作用
Java1.5引入了泛型,所有的集合接口都在使用它。作用:
(1)泛型允许我们为集合提供一个可以容纳的对象类型,因此,如果你添加其它类型的任何元素,它会在编译时报错;这避免了在运行时出现ClassCastException,因为你将在编译时得到报错信息;
(2)泛型也使得代码整洁,我们不需要使用显式转换和instanceof操作符。

23. 什么是迭代器(Iterator)
迭代器是一种设计模式,它是一个对象,可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构;
java.lang.Iterable接口,被Collection继承;
iterator方法在set和list接口中都有定义,但是ListIterator仅存在于list接口中(或实现类中);
ListIterator有add方法,可以向List中添加对象,而Iterator不能;
ListIterator和Iterator都有hasNext和next方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious和previous方法,可以实现逆向(顺序向前)遍历,Iterator就不可以;
ListIterator可以定位当前的索引位置,nextIndex和previousIndex可以实现,Iterator没有此功能;
都可实现删除对象,但是ListIterator可以实现对象的修改,set方法可以实现,Iierator仅能遍历,不能修改。 

24. Error与Exception的比较
Java将所有的错误封装成一个对象,其根本父类为Throwable,Throwable有两个子类Error和Exception;
Error是Throwable的子类,用于指示合理的应用程序(JVM)不应该试图捕获的严重问题;
Exception类及其子类是Throwable的一种表现形式,它指出了合理的应用程序(JVM)想要捕获的条件;
RuntimeException是可能在Java虚拟机正常运行期间抛出的异常的超类,无需在throws中进行说明。RuntimeException也被称为未检查异常(unchecked Exception)(其余的Exception为已检查异常),未检查异常是因为程序员没有进行必要的检查,因为疏忽和错误而引起的异常;
已检查异常:定义方法时必须声明所有可能会抛出的已检查异常;在调用这个方法时,必须对其进行捕获,否则就将其传递下去。

25. 成员变量与局部变量的比较
成员变量:方法外部,类的内部定义的变量,成员变量存储在堆中的对象里面,由垃圾回收器负责回收;
局部变量:方法或语句块内部定义的变量,形式参数是局部变量,局部变量的数据存放于栈内存中。栈内存中的局部变量随着方法的消失而消失,不能使用访问控制符。
成员变量无需显式初始化;局部变量分为形参、方法局部变量、代码块局部变量,除了形参之外,局部变量都必须显式初始化。
在同一个类里,成员变量的作用范围是整个类内有效,一个类里不能定义两个同名的成员变量,即使一个是类Field,一个是实例Field也不行;
一个方法里不能定义两个同名的局部变量,即使一个是方法局部变量,一个是代码块局部变量或形参也不行;
Java允许局部变量和成员变量同名,如果方法里的局部变量和成员变量同名,局部变量会覆盖成员变量,如果需要在这个方法里引用被覆盖的成员变量,则可以使用this(对于实例Field)或类名(对于类Field)作为调用者来限定访问成员变量。

猜你喜欢

转载自www.cnblogs.com/yuanfei1110111/p/10991596.html