大家好,我是 程序员青戈,一个被Bug耽误了才艺的程序员专注于Java领域的知识分享和技术交流,每天都会给大家带来Java学习的干货教程
微信搜索公众号 Java学习指南,回复 面试 领取一线大厂面试题一套加号主微信 xia_qing2012,可以进技术交流学习群一起共同进步哦
面向过程 :面向过程性能比面向对象高。 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发。但是,面向过程没有面向对象易维护、易复用、易扩展。
面向对象 :面向对象易维护、易复用、易扩展。 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,面向对象性能比面向过程低。
JRE(Java runtime environment )是java运行环境,它主要包含两个部分,jvm 的标准实现和 Java 的一些基本类库。
JDK(Java Development Kit)是Java开发工具包,包含JRE和一些好用的小工具。例如:javac.exe,java.exe,jar.exe 等。
Java虚拟机(JVM),即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口
。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。
Jdk编译出来的虚拟机理解的代码叫做字节码
(即扩展名为.class
的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台(windows、Linux、Mac等)的解释器是不同的,但是实现的虚拟机是相同的。
Java源程序经过编译器编译后变成字节码
,字节码由虚拟机
解释执行,虚拟机将每一条要执行的字节码送给解释器
,解释器将其翻译成特定机器上的机器码
,然后在特定的机器上运行。
注意:在 .class翻译成机器码这一步JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了
JIT 编译器
,而JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存
的语言。
Java语言采用Unicode编码标准,Unicode(标准码),它为每个字符制订了一个唯一的数值,因此在任何的语言,平台,程序都可以放心的使用。
基本类型 | 大小(字节) | 默认值 | 封装类 |
---|---|---|---|
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。推介平时多使用&&,因为它效率更高些。
&还可以用作位运算符。当&两边操作数或两边表达式的结果不是boolean类型时,&用于按位与运算符的操作。
==解读:
对于基本类型和引用类型 == 的作用效果是不同的,如下所示:
举个例子看看:
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。
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作为Java中的关键字可以用于三个地方。用于修饰类、类属性和类方法。
特征:凡是引用final关键字的地方皆不可修改!
修饰类:表示该类不能被继承;
修饰方法:表示方法不能被重写;
修饰变量:表示变量只能一次赋值以后值不能被修改(常量)。
Object是所有类的父类,任何类都默认继承Object。
clone
保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常
equals
在Object中与==是一样的,子类一般需要重写该方法
hashCode
该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到
getClass
final方法,获得运行时类型
wait
使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生:
notify
唤醒在该对象上等待的某个线程
notifyAll
唤醒在该对象上等待的所有线程
toString
转换成字符串,一般子类都有重写,否则打印句柄
重写是子类方法重写父类的方法,重写的方法名不变,而类的构造方法名必须与类名一致,假设父类的构造方法如果能够被子类重写则子类类名必须与父类类名一致才行,所以 Java 的构造方法是不能被重写的。而重载是针对同一个的,所以构造方法可以被重载。
从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
强引用
最普遍的一种引用方式,如String s = “abc”,变量s就是字符串“abc”的强引用,只要强引用存在,则垃圾回收器就不会回收这个对象。
软引用(SoftReference)
用于描述还有用但非必须的对象,如果内存足够,不回收,如果内存不足,则回收。一般用于实现内存敏感的高速缓存,软引用可以和引用队列ReferenceQueue联合使用,如果软引用的对象被垃圾回收,JVM就会把这个软引用加入到与之关联的引用队列中。
弱引用(WeakReference)
弱引用和软引用大致相同,弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
虚引用(PhantomReference)
就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。 虚引用主要用来跟踪对象被垃圾回收器回收的活动。
虚引用与软引用和弱引用的一个区别在于:
虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
封装、继承、多态
封装:
封装就是把抽象的数据和对数据进行的操作封装在一起,数据被保存在内部,程序的其他部分只有通过被授权的操作(成员方法)才能对数据进行操作。
java提供了四种控制修饰符控制方法和变量访问的权限:
`public`:对外公开
`protected`:对子类和同一包中的类公开
`没有修饰符号(default)`:向同一个包的类公开
`private`:只有类本身可以访问,不对外公开
继承:
继承是使用已存在的类的定义作为基础建立新类的技术。继承可以解决代码复用
问题,当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extend
语句来声明继承父类。
关于继承如下 3 点请记住:
多态:
所谓多态,就是指一个引用(类型)在不同情况下的多种状态,你也可以这样理解:父类型的引用指向子类型的对象。
多态存在的三个必要条件
多态有两个好处:
区别从下面三个方面阐述:
一、可变性
不可变
的,它使用final修饰的字符数组保存字符串(private final char value[]
)可变
的,它们都继承自AbstractStringBuilder类,在AbstractStringBuilder类中使用未被final修饰的字符数组保存字符串(char[] value;
)并且StringBuffer和StringBuilder的构造方法都是基于父类AbstractStringBuilder实现的二、线程安全性
String对象是不可变
的,也就是说String对象一旦被创建后就无法改变了,直到对象被销毁,String也可以理解为常量,线程安全
。
三、性能
新的 String 对象
,然后将指针指向新的 String 对象,所以经常改变内容的字符串如果使用 String ,会在内存中生成很多无用的对象, JVM 的 GC 会频繁工作,性能就会降低
。本身
进行操作,而不是生成新的对象并改变对象引用。所以多数情况下推荐使用 StringBuffer /StringBuilder,性能比较好
,特别是字符串对象经常改变的情况下。但是根据统计,相同使用情况下StringBuilder仅比StringBuffer多10%~15%左右的性能提升。多线程
环境中,则可以考虑使用StringBuffer,例如XML解析、HTTP参数解析和封装。单线程
环境中,则可以考虑使用StringBuilder,如SQL语句的拼装、JSON封装等。使用总结
String
单线程
操作字符串缓冲区下操作大量数据用StringBuilder
多线程
操作字符串缓冲区下操作大量数据用StringBuffer
什么是常量
用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 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 严格来说是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的包装类就是将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修饰的成员变量必须显示地赋值);而局部变量则不会自动赋值。非静态的方法可以调用静态的方法,但是静态的方法不可以调用非静态的方法。
类的静态成员(变量和方法)属于类本身,在类加载的时候就会分配内存,可以通过类名直接去访问;
非静态成员(变量和方法)属于类的对象,所以只有在类的对象产生(创建类的实例)时才会分配内存,然后通过类的对象(实例)去访问。
在一个类的静态成员中去访问其非静态成员之所以会出错是因为在类的非静态成员不存在的时候类的静态成员就已经存在了,访问一个内存中不存在的东西当然会出错。
在外部调用静态方法时,可以使用"类名.方法名
"的方式,也可以使用 "对象名.方法名
"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
静态方法在访问本类的成员时,只允许访问静态成员
(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。
在类加载的init阶段,类的类构造器中会收集所有的static块和字段并执行;
static块只执行一次。
static语句块,不是在实例化的时候被执行的;
在调用类中任何一个方法时,jvm进行类加载,static语句块是在类加载器加载该类的最后阶段进行初始化的。并且只会被初始化一次。 (注:若一次性调用多个方法,则只会执行一次static代码块。)
this:代表对象本身,可以理解为:指向对象本身的一个指针。
this的三种用法:
super:代指父类,可以用于调用父类的普通方法和构造方法。
super的用法:
我是
程序员青戈
,如果您也打算或者正在从事Java相关的工作,请您关注
我的公众号Java学习指南
,在这里您将收获全套的Java面试宝典和技术干货合集。
感谢大家的阅读,创作不易,能否请您小手点一点下方的 一键三连
支持一下作者呢谢谢~