大家好,我是 程序员青戈,一个被Bug耽误了才艺的程序员👦专注于Java领域的知识分享和技术交流,每天都会给大家带来Java学习的干货教程📚
微信搜索公众号 Java学习指南,回复 面试 领取一线大厂面试题一套😋加号主微信 xia_qing2012,可以进技术交流学习群一起共同进步哦😊
文章目录
- Java基础
- Java语言的特点
- 面向对象和面向过程的区别
- 什么是JDK,什么是JRE,它们有什么关系
- 什么是Java虚拟机
- 什么是字节码?
- Java语言采用何种编码方案?
- Java数据类型有哪些
- &和&&的区别和联系
- == 和 equals 的区别是什么?
- 两个对象的 hashCode() 相同, 那么 equals() 也一定为 true吗?
- 如何理解final关键字:
- Object类有哪些公用方法?
- Error、Exception和RuntimeException的区别,作用又是什么?
- 重载和重写的区别?
- Java 构造方法能否被重写和重载?
- Java的四种引用,强弱软虚,用到的场景
- Java 面向对象编程三大特性是什么?
- String、StringBuffer 和 StringBuilder 的区别是什么?
- String 类的常用方法都有那些?
- 什么是Java常量池?
- instanceof 关键字的作用
- 自动装箱与拆箱
- int和Integer的区别是什么?
- 为什么设计包装类型?
- 抽象类必须要有抽象方法吗?
- 普通类和抽象类有哪些区别?
- 接口和抽象类有什么区别?
- 成员变量与局部变量的区别有那些?
- 为什么不能从静态的方法里调用非静态的方法或变量?
- 静态方法和实例方法有何不同?
- static修饰变量、代码块时什么时候执行?执行几次?
- Java的构造方法有哪些特性?
- 说说你对this、super的理解?
Java基础
Java语言的特点
- 简单易学;
- 面向对象(封装,继承,多态);
- 平台无关性(Java虚拟机实现平台无关性);
- 可靠性;
- 安全性;
- 编译与解释并存。
面向对象和面向过程的区别
-
面向过程 :面向过程性能比面向对象高。 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发。但是,面向过程没有面向对象易维护、易复用、易扩展。
-
面向对象 :面向对象易维护、易复用、易扩展。 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,面向对象性能比面向过程低。
什么是JDK,什么是JRE,它们有什么关系
-
JRE(Java runtime environment )是java运行环境,它主要包含两个部分,jvm 的标准实现和 Java 的一些基本类库。
-
JDK(Java Development Kit)是Java开发工具包,包含JRE和一些好用的小工具。例如:javac.exe,java.exe,jar.exe 等。
什么是Java虚拟机
Java虚拟机(JVM),即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口
。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。
什么是字节码?
Jdk编译出来的虚拟机理解的代码叫做字节码
(即扩展名为.class
的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台(windows、Linux、Mac等)的解释器是不同的,但是实现的虚拟机是相同的。
Java源程序经过编译器编译后变成字节码
,字节码由虚拟机
解释执行,虚拟机将每一条要执行的字节码送给解释器
,解释器将其翻译成特定机器上的机器码
,然后在特定的机器上运行。
注意:在 .class翻译成机器码这一步JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了
JIT 编译器
,而JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存
的语言。
Java语言采用何种编码方案?
Java语言采用Unicode编码标准,Unicode(标准码),它为每个字符制订了一个唯一的数值,因此在任何的语言,平台,程序都可以放心的使用。
Java数据类型有哪些
基本类型 | 大小(字节) | 默认值 | 封装类 |
---|---|---|---|
byte | 1 | (byte) 0 | Byte |
short | 2 | (short)0 | Short |
int | 4 | 0 | Integer |
long | 8 | 0L | Long |
float | 4 | 0.0f | Float |
double | 4 | 0.0fD | Double |
boolean | - | false | Boolean |
char | 2 | \u0000(null) | Character |
注意:
- int是基本数据类型,Integer是int的封装类,是引用类型。int默认值是0,而Integer默认值是null,所以Integer能区分出0和null的情况。一旦java看到null,就知道这个引用还没有指向某个对象,在任何引用使用前,必须为其指定一个对象,否则会报错。
- 基本数据类型在声明时系统会自动给它分配空间,而引用类型声明时只是分配了引用空间,必须通过实例化开辟数据空间之后才可以赋值。数组对象也是一个引用对象,将一个数组赋值给另一个数组时只是复制了一个引用,所以通过某一个数组所做的修改在另一个数组中也看的见。
- 虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位。这样我们可以得出boolean类型占了单独使用是4个字节,在数组中又是1个字节。使用int的原因是,对于当下32位的处理器(CPU)来说,一次处理数据是32位(这里不是指的是32/64位系统,而是指CPU硬件层面),具有高效存取的特点。
&和&&的区别和联系
&和&&都可以用作逻辑与运算符,但是要看使用时的具体条件来决定。
-
操作数1&操作数2,操作数1&&操作数2,
-
表达式1&表达式2,表达式1&&表达式2,
情况1:当上述的操作数是boolean类型变量时,&和&&都可以用作逻辑与运算符。
情况2:当上述的表达式结果是boolean类型变量时,&和&&都可以用作逻辑与运算符。
表示逻辑与(and),当运算符两边的表达式的结果或操作数都为true时,整个运算结果才为true,否则,只要有一方为false,结果都为false。
&和&&的区别(不同点):
- &逻辑运算符称为逻辑与运算符,&&逻辑运算符称为短路与运算符,也可叫逻辑与运算符。
- 对于&:无论任何情况,&两边的操作数或表达式都会参与计算。
- 对于&&:当&&左边的操作数为false或左边表达式结果为false时,&&右边的操作数或表达式将不参与计算,此时最终结果都为false。
综上所述,如果逻辑与运算的第一个操作数是false或第一个表达式的结果为false时,对于第二个操作数或表达式是否进行运算,对最终的结果没有影响,结果肯定是false。推介平时多使用&&,因为它效率更高些。
&还可以用作位运算符。当&两边操作数或两边表达式的结果不是boolean类型时,&用于按位与运算符的操作。
== 和 equals 的区别是什么?
==解读:
对于基本类型和引用类型 == 的作用效果是不同的,如下所示:
- 基本类型:比较的是值是否相同;
- 引用类型:比较的是引用是否相同;
举个例子看看:
String x = "a";
String y = "a";
String z = new String("a");
System.out.println(x==y); // true
System.out.println(x==z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true
代码解读:因为 x 和 y 指向的是同一个引用,所以==
是true
,而 new String()
方法则重写开辟了内存空间,所以==
结果为 false
,而 equals
比较的一直是值
,所以结果都为 true
。
equals 解读:
equals 本质上就是 ==,只不过 String 和 Integer 等重写了 equals 方法,把它变成了值比较。看下面的代码就明白了。
首先来看默认情况下 equals 比较一个有相同值的对象,代码如下:
class Cat {
public Cat(String name) {
this.name = name;
}
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Cat c1 = new Cat("程序员青戈");
Cat c2 = new Cat("程序员青戈");
System.out.println(c1.equals(c2)); // false
输出结果出乎我们的意料,竟然是 false?这是怎么回事,看了 equals 源码就知道了,源码如下:
public boolean equals(Object obj) {
return (this == obj);
}
原来 equals 本质上就是 ==
那问题来了,两个相同值的 String 对象,为什么返回的是 true?代码如下:
String s1 = new String("叶子");
String s2 = new String("叶子");
System.out.println(s1.equals(s2)); // true
同样的,当我们进入 String 的 equals 方法,找到了答案,代码如下:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
原来是 String 重写了 Object 的 equals 方法,把引用比较改成了值比较。
总结 :== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比较,只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。
两个对象的 hashCode() 相同, 那么 equals() 也一定为 true吗?
答案是否定的,两个对象的 hashCode() 相同,equals() 不一定 true。
String s1 = "keep";
String s2 = "brother";
System. out. println(String.format("s1:%d | s2:%d", s1.hashCode(), s2.hashCode()));
System. out. println(s1.equals(s2));
结果:
s1:1179395 | s2:1179395
false
从上面的结果可以看到keep
和brother
的 hashCode() 相同,然而 equals() 则为 false,因为在散列表中,hashCode() 相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得出键值对相等。
如何理解final关键字:
final作为Java中的关键字可以用于三个地方。用于修饰类、类属性和类方法。
特征:凡是引用final关键字的地方皆不可修改!
-
修饰类:表示该类不能被继承;
-
修饰方法:表示方法不能被重写;
-
修饰变量:表示变量只能一次赋值以后值不能被修改(常量)。
Object类有哪些公用方法?
Object是所有类的父类,任何类都默认继承Object。
-
clone
保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常 -
equals
在Object中与==是一样的,子类一般需要重写该方法 -
hashCode
该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到 -
getClass
final方法,获得运行时类型 -
wait
使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。调用该方法后当前线程进入睡眠状态,直到以下事件发生:
- 其他线程调用了该对象的notify方法
- 其他线程调用了该对象的notifyAll方法
- 其他线程调用了interrupt中断该线程
- 时间间隔到了,此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常
-
notify
唤醒在该对象上等待的某个线程 -
notifyAll
唤醒在该对象上等待的所有线程 -
toString
转换成字符串,一般子类都有重写,否则打印句柄
Error、Exception和RuntimeException的区别,作用又是什么?
- Error和Exception都是Throwable的子类,RuntimeException是Exception的子类。
- Error用于指示合理应用程序不应该试图捕获的错误。
- Exception指出合理的应用程序需要捕获的条件。分为已检查异常和未检查异常。
- RuntimeException是未检查异常,不需要try catch或在方法上声明,主要子类:NullPointer、Arithmatic、ArrayIndexOutOfBounds、ClassCast。
重载和重写的区别?
- 重载: 发生在同一个类中,方法名必须相同,实质表现就是多个具有不同的参数个数或者类型的同名函数(返回值类型可随意,不能以返回类型作为重载函数的区分标准),返回值类型、访问修饰符可以不同,发生在编译时。
- 重写: 发生在父子类中,方法名、参数列表必须相同,是父类与子类之间的多态性,实质是对父类的函数进行重新定义。返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。
Java 构造方法能否被重写和重载?
重写是子类方法重写父类的方法,重写的方法名不变,而类的构造方法名必须与类名一致,假设父类的构造方法如果能够被子类重写则子类类名必须与父类类名一致才行,所以 Java 的构造方法是不能被重写的。而重载是针对同一个的,所以构造方法可以被重载。
Java的四种引用,强弱软虚,用到的场景
从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
-
强引用
最普遍的一种引用方式,如String s = “abc”,变量s就是字符串“abc”的强引用,只要强引用存在,则垃圾回收器就不会回收这个对象。 -
软引用(SoftReference)
用于描述还有用但非必须的对象,如果内存足够,不回收,如果内存不足,则回收。一般用于实现内存敏感的高速缓存,软引用可以和引用队列ReferenceQueue联合使用,如果软引用的对象被垃圾回收,JVM就会把这个软引用加入到与之关联的引用队列中。 -
弱引用(WeakReference)
弱引用和软引用大致相同,弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。 -
虚引用(PhantomReference)
就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。 虚引用主要用来跟踪对象被垃圾回收器回收的活动。
虚引用与软引用和弱引用的一个区别在于:
虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
Java 面向对象编程三大特性是什么?
封装、继承、多态
封装:
封装就是把抽象的数据和对数据进行的操作封装在一起,数据被保存在内部,程序的其他部分只有通过被授权的操作(成员方法)才能对数据进行操作。
java提供了四种控制修饰符控制方法和变量访问的权限:
`public`:对外公开
`protected`:对子类和同一包中的类公开
`没有修饰符号(default)`:向同一个包的类公开
`private`:只有类本身可以访问,不对外公开
继承:
继承是使用已存在的类的定义作为基础建立新类的技术。继承可以解决代码复用
问题,当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extend
语句来声明继承父类。
关于继承如下 3 点请记住:
- 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
多态:
所谓多态,就是指一个引用(类型)在不同情况下的多种状态,你也可以这样理解:父类型的引用指向子类型的对象。
多态存在的三个必要条件
- 要有继承;
- 要有重写;
- 父类引用指向子类对象。
多态有两个好处:
- 应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。//继承
- 派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。
String、StringBuffer 和 StringBuilder 的区别是什么?
区别从下面三个方面阐述:
一、可变性
- String类长度是
不可变
的,它使用final修饰的字符数组保存字符串(private final char value[]
) - StringBuffer和StringBuilder长度是
可变
的,它们都继承自AbstractStringBuilder类,在AbstractStringBuilder类中使用未被final修饰的字符数组保存字符串(char[] value;
)并且StringBuffer和StringBuilder的构造方法都是基于父类AbstractStringBuilder实现的
我们可以从下面的继承关系图上一目了然:
二、线程安全性
-
String对象是
不可变
的,也就是说String对象一旦被创建后就无法改变了,直到对象被销毁,String也可以理解为常量,线程安全
。 -
StringBuffer对方法加了同步锁或者调用方法的时候加了
同步
修饰,所以是线程安全
的,但是性能会非常受影响。
-
StringBuilder
没有对方法加同步锁
,所以是非线程安全
的,但是性能比较好。
三、性能
- String 是不可变的对象, 因此在每次对 String 类型进行改变的时候,都会生成一个
新的 String 对象
,然后将指针指向新的 String 对象,所以经常改变内容的字符串如果使用 String ,会在内存中生成很多无用的对象, JVM 的 GC 会频繁工作,性能就会降低
。 - 使用 StringBuffer 类和StringBuilder类时,每次都会对 StringBuffer/StringBuilder 对象
本身
进行操作,而不是生成新的对象并改变对象引用。所以多数情况下推荐使用 StringBuffer /StringBuilder,性能比较好
,特别是字符串对象经常改变的情况下。但是根据统计,相同使用情况下StringBuilder仅比StringBuffer多10%~15%左右的性能提升。 - 如果在频繁进行字符串运算(如拼接、替换、删除等),并且运行在
多线程
环境中,则可以考虑使用StringBuffer,例如XML解析、HTTP参数解析和封装。 - 如果在频繁进行字符串运算(如拼接、替换、和删除等),并且运行在
单线程
环境中,则可以考虑使用StringBuilder,如SQL语句的拼装、JSON封装等。
使用总结
- 如果要操作少量的数据用
String
单线程
操作字符串缓冲区下操作大量数据用StringBuilder
多线程
操作字符串缓冲区下操作大量数据用StringBuffer
String 类的常用方法都有那些?
- indexOf():返回指定字符的索引。
- charAt():返回指定索引处的字符。
- replace():字符串替换。
- trim():去除字符串两端空白。
- split():分割字符串,返回一个分割后的字符串数组。
- getBytes():返回字符串的 byte 类型数组。
- length():返回字符串长度。
- toLowerCase():将字符串转成小写字母。
- toUpperCase():将字符串转成大写字符。
- substring():截取字符串。
- equals():字符串比较。
什么是Java常量池?
什么是常量
用final修饰的成员变量表示常量,值一旦给定就无法改变!
final修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。
常量池分类
常量池大体可以分为:静态常量池,运行时常量池。
静态常量池存在于class文件中,比如经常使用的javap -verbose中,常量池总是在最前面把?
运行时常量池呢,就是在class文件被加载进了内存之后,常量池保存在了方法区中,通常说的常量池 值的是运行时常量池。所以呢,讨论的都是运行时常量池
常量池的好处
常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。
(1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
(2)节省运行时间:比较字符串时,==
比equals()快。对于两个引用变量,只用==
判断引用是否相等,也就可以判断实际值是否相等。
java中基本类型的包装类的大部分都实现了常量池技术,即Byte,Short,Integer,Long,Character,Boolean
这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。
举个Integer的例子:
Integer i1 = 40;
Integer i2 = 40;
System.out.println(i1==i2);//输出true
两种浮点数类型的包装类Float,Double并没有实现常量池技术。
Double d1=1.2;
Double d2=1.2;
System.out.println(d1==d2);//输出false
String类和常量池
- String对象创建方式
String str1 = "abcd";
String str2 = new String("abcd");
System.out.println(str1==str2);//false
这两种不同的创建方法是有差别的,第一种方式是在常量池中拿对象(如果常量池没有则新建一个字符串常量),第二种方式是直接在堆内存空间创建一个新的对象。只要使用new方法,便需要创建新的对象
。
连接表达式 +
(1)只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。
(2)对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中。
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";
String str4 = str1 + str2;
System.out.println(str3 == str4);//false
String str5 = "string";
System.out.println(str3 == str5);//true
instanceof 关键字的作用
instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例,用法为:
boolean result = obj instanceof Class
其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。
注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。
int i = 0;
System.out.println(i instanceof Integer);//编译不通过 i必须是引用类型,不能是基本类型
System.out.println(i instanceof Object);//编译不通过
Integer integer = new Integer(1);
System.out.println(integer instanceof Integer);//true
System.out.println(null instanceof Object); //false,在 JavaSE规范 中对 instanceof 运算符的规定就是:如果 obj 为 null,那么将返回 false。
自动装箱与拆箱
装箱:将基本类型用它们对应的引用类型包装起来;
拆箱:将包装类型转换为基本数据类型。
//自动装箱
Integer a = 100; //系统自动执行了Integer a = Integer.valueOf(100);
//自动拆箱
int b = a; //系统自动执行了int b = a.intValue();
int和Integer的区别是什么?
-
int是基本数据类型,Integer是int的包装类就是将int类型包装成Object对象;
-
Integer变量必须实例化后才能使用;int变量不需要;
-
Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值 ;
-
Integer的默认值是null;int的默认值是0。
为什么设计包装类型?
对象封装有很多好处,可以把属性也就是数据跟处理这些数据的方法结合在一起,比如Integer就有parseInt()
等方法来专门处理int型相关的数据。
另一个非常重要的原因就是在Java中绝大部分方法或类都是用来处理类类型对象的,如ArrayList集合类就只能以类作为他的存储对象,而这时如果想把一个int型的数据存入list是不可能的,必须把它包装成类,也就是Integer才能被List所接受。所以Integer等包装类的存在是很必要的。
抽象类必须要有抽象方法吗?
不需要,抽象类不一定非要有抽象方法。
举个栗子:
public abstract class Person {
public static void say() {
System.out.println("hello world");
}
}
这段代码中抽象类并没有抽象方法但完全可以正常运行。
普通类和抽象类有哪些区别?
- 普通类不能包含抽象方法,抽象类可以包含抽象方法。
- 抽象类不能直接实例化,普通类可以直接实例化。
接口和抽象类有什么区别?
- 构造函数:抽象类可以有
构造函数
;接口不能有。 - 实现:抽象类的子类使用
extends
来继承;接口必须使用implements
来实现接口。 - 实现数量:类可以实现很
多个
接口;但是只能继承一个
抽象类。 - 访问修饰符:接口中的方法默认使用
public
修饰;抽象类中的方法可以是任意
访问修饰符。 - 内部定义:接口中不能包含静态方法;抽象类中可以包含静态方法。
成员变量与局部变量的区别有那些?
- 从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被
public
,private
,static
等修饰符所修饰,而局部变量不能被访问控制修饰符及static所修饰;成员变量和局部变量都能被final
所修饰; - 从变量在内存中的存储方式来看,成员变量是
对象
的一部分,而对象存在于堆内存
,局部变量存在于栈内存
; - 从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失;
- 成员变量如果没有被赋初值,则会自动以类型的
默认值
而赋值(一种情况例外被final修饰但没有被static修饰的成员变量必须显示地赋值);而局部变量则不会自动赋值。
为什么不能从静态的方法里调用非静态的方法或变量?
非静态的方法可以调用静态的方法,但是静态的方法不可以调用非静态的方法。
类的静态成员(变量和方法)属于类本身,在类加载的时候就会分配内存,可以通过类名直接去访问;
非静态成员(变量和方法)属于类的对象,所以只有在类的对象产生(创建类的实例)时才会分配内存,然后通过类的对象(实例)去访问。
在一个类的静态成员中去访问其非静态成员之所以会出错是因为在类的非静态成员不存在的时候类的静态成员就已经存在了,访问一个内存中不存在的东西当然会出错。
静态方法和实例方法有何不同?
-
在外部调用静态方法时,可以使用"
类名.方法名
"的方式,也可以使用 "对象名.方法名
"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。 -
静态方法在访问本类的成员时,只允许访问
静态成员
(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。
static修饰变量、代码块时什么时候执行?执行几次?
在类加载的init阶段,类的类构造器中会收集所有的static块和字段并执行;
static块只执行一次。
static语句块,不是在实例化的时候被执行的;
在调用类中任何一个方法时,jvm进行类加载,static语句块是在类加载器加载该类的最后阶段进行初始化的。并且只会被初始化一次。 (注:若一次性调用多个方法,则只会执行一次static代码块。)
Java的构造方法有哪些特性?
- 名字与类名相同。
- 没有返回值,但不能用void声明构造函数。
- 生成类的对象时自动执行,无需调用。
说说你对this、super的理解?
this:代表对象本身,可以理解为:指向对象本身的一个指针。
this的三种用法:
- 普通的直接引用,this相当于是指向当前对象本身;
- 形参与成员名字重名,用this来区分;
- 引用构造函数,this(参数):调用本类中另一种形式的构造方法(应该为构造方法中的第一条语句)
super:代指父类,可以用于调用父类的普通方法和构造方法。
super的用法:
- 调用父类构造方法:super()(无参构造方法)或 super(参数)(有参构造方法)
- 调用父类普通方法:super.方法名(参数)
this和super的区别:
我是
程序员青戈
,如果您也打算或者正在从事Java相关的工作,请您关注
我的公众号Java学习指南
,在这里您将收获全套的Java面试宝典和技术干货合集。
感谢大家的阅读,创作不易,能否请您小手点一点下方的 一键三连
支持一下作者呢😊谢谢~