Java基础知识
1. Java脑图
2. Java基础概念
2.1 Java语言的特点
- 简单易学;
- 面向对象(封装、继承、多态);
- 平台无关性(Java虚拟机实现);
- 可靠性
- 安全性
- 支持多线程(C++一开始没有内置的多线程机制,须调用操作系统的多线程功能,C++11开始引入多线程库,std::thread和std::async创建线程;Java提供多线程)
- 支持网络编程且方便(Java简化网络编程设计)
- 编译与解释并存
2.1.1 封装
- 概念:类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问。
- 好处:只能通过规定的方法访问数据;隐藏类的实例细节,方便修改和实现。
- 实现:
2.1.2 继承
- 概念:基于一个已存在的类(父类),创建一个新的类(子类),子类继承并拥有父类的属性和方法,同时子类还拥有父类不具有的属性和方法。
- 特点:一个类只能继承一个父类(不支持多继承),可以多重继承(Java继承区别于C++继承的一个特性);只能继承父类中非private成员属性和方法,private是父类所特有的不能继承;子类可以对父类进行扩展;子类可以用自己的方式实现父类的方法;提高类之间的耦合性(缺点,耦合度高联系越紧密,代码独立性越差)。
- 格式:
class 父类 {
}
class 子类 extends 父类 {
}
- 更多:https://www.runoob.com/java/java-inheritance.html
2.1.3 多态
- 概念:同一个行为具有多个不同表现形式或形态的能力,同一个接口,使用不同的实例而执行不同操作。
- 特点:消除类型之间的耦合关系;可替换性;可扩充性;接口性;灵活性;简化性。
- 图解:
- 注意:使用多态方式调用方法时,首先检查父类中是否有该方法,若没有,则编译错误;若有,再去调用子类的同名方法。
2.2 JVM和JDK和JRE
2.2.1 JVM
Java虚拟机,是运行Java字节码的虚拟机,目的是使用相同字节码都会给出相同的结果。
2.2.1.1 什么是字节码?字节码的好处?
JVM可以理解的代码,即.class文件,不面向任何特定的处理器(因此Java程序无需重新编译便可在不同操作系统的计算机上运行),只面向虚拟机。字节码解决传统解释型语言执行效率低的问题,又保留解释型语言可移植的特点。
2.2.1.2 Java程序从源代码到运行
.class -> 机器码:JVM类加载器加载字节码文件 -> 通过解释器逐行解释执行(执行速度较慢,而且有些方法和代码块经常被调用:热点代码),后来引进了JIT编译器,运行时编译。JIT完成第一次编译后会将字节码对应的机器码保存下来,下次直接用(机器码运行效率高于Java解释器,即为什么Java编译与解释共存)。
二八定律:消耗大部分系统资源的只有一小部分代码(热点代码),即JIT所需编译的部分。JDK9引入AOT,直接将字节码编译成机器码,避免JIT预热等各方面开销。
总结:JVM是运行Java字节码的虚拟机。字节码和不同系统的JVM实现是Java“一次编译,随处可运行”的关键所在。
2.2.2 JDK和JRE
JDK是功能齐全的Java SDK,拥有JRE所拥有的一切,还有编译器(javac)和工具(如javadoc和jdb),能够创建和编译程序。
JRE是Java运行的环境,运行已编译Java程序所需的所有内容的集合,包含JVM、Java类库、Java命令和其他一些基础构件,但是不能创建新程序。
只需要运行Java程序 -> JRE
进行Java编程 -> JDK
*但不绝对,不进行Java开发也可能需要JDK,比如JSP部署Web应用程序:应用程序服务器会将JSP转换成Java servlet,需要JDK编译servlet。
2.2.3 Oracle JDK和OpenJDK
pdf P6 1.1.3
2.3 Java和C++的区别
- 都面向对象,都支持封装、继承、多态;
- Java不提供指针来直接访问内存,程序内存更加安全;
- Java的类是单继承的,C++支持多重继承;虽然Java类不可以多继承,但接口可以多继承;
- Java有自动内存管理机制,不需要程序员手动释放无用内存;
- C语言中,字符串/字符数组最后都会有‘\0’表示结束;Java没有结束符。
2.4 什么是Java程序的主类?应用程序和小程序的主类的不同?
2.5 import java和javax的区别
刚开始,Java API所必需的包是java开头的包,javax只是扩展API包。后来javax扩展为Java API的组成部分。但是将扩展从javax包移动到java包太麻烦,会破坏一堆现有代码,因此决定javax包成为标准API的一部分。所以java和javax没有区别,都是一个名字。
2.6 为什么说Java语言“编译与解释并存”?
高级编程语言按照程序执行分为编译型和解释型。
编译型语言:编译器针对特定操作系统将源码一次性翻译成可被该平台执行的机器码。
解释型语言:解释器对源程序逐行解释成特定平台的机器码并立即执行。
Java语言两者特征均有,因为Java程序先编译后解释,编译生成字节码.class,字节码由Java解释器来解释执行。
3. Java语法
3.1 字符型常量和字符串常量的区别
- 字符型常量单引号的一个,字符串常量双引号的若干个;
- 字符型常量代表一个整型值(ASCII),可表达式运算,字符串常量代表一个地址值(该字符串在内存存放位置);
- 字符型常量2个字节,字符串常量若干个(char在Java占2个字节)。
3.2 注释
- 单行注释
- 多行注释
- 文档注释
3.3 标识符和关键字的区别
标识符=名字,被赋予特殊含义的标识符=关键字
3.4 Java常见的关键字
3.5 Java泛型是什么?类型擦除是什么?常用的通配符?
Java泛型是JDK5引入的一个新特性,提供编译时类型安全检测机制,允许在编译时检测到非法的类型。泛型本质是参数化类型,即所操作的数据类型被指定为一个参数。
Java泛型是伪泛型,因为Java在编译时所有泛型信息都会被擦掉,即类型擦除。
pdf P11 1.2.7
泛型使用方式:
- 泛型类
//此处T可以随便写为任意标识,常⻅的如T、E、K、V等形式的参数常⽤于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey(){
return key;
}
}
实例化泛型类:
Generic<Integer> genericInteger = new Generic<Integer>(123456);
- 泛型接口
public interface Generator<T> {
public T method();
}
实现泛型接口,不指定类型:
class GeneratorImpl<T> implements Generator<T>{
@Override
public T method() {
return null;
}
}
实现泛型接口,指定类型
class GeneratorImpl<T> implements Generator<String>{
@Override
public String method() {
return "hello";
}
}
- 泛型方法
public static < E > void printArray( E[] inputArray ){
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
使用:
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3 };
String[] stringArray = { "Hello", "World" };
printArray( intArray );
printArray( stringArray );
常用通配符:T,E,K,V,?
- ?:不确定的类型
- T(type):具体类型
- K:Java键值中的key
- V:Java键值中的value
- E(element)
3.6 ==和equals的区别
==:判断两个对象的地址是否相等,即是不是同一个对象(基本数据类型比较的是值,引用数据类型比较的是内存地址)
*java只有值传递,所以不管是比较哪个变量,本质都是值,只是引用类型变量存的值是对象的地址。
equals():判断两个对象是否相等,不用于比较基本数据类型的变量。equals()存在Object类中,而Object类是所有类的直接或间接父类。
Object类equals()方法:
public boolean equals(Object obj) {
return (this == obj);
}
equals()两种使用情况:
- 类没有覆盖equals()。通过equals()比较两个对象时,等价于==,使用的默认是Object类equals();
- 类覆盖了equals()。一般我们都覆盖equals()来比较两个对象的内容,若相等,则返回true。
public class test1 {
public static void main(String[] args) {
String a = new String("ab"); // a 为⼀个引⽤
String b = new String("ab"); // b为另⼀个引⽤,对象的内容⼀样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
if (aa == bb) // true
System.out.println("aa==bb");
if (a == b) // false,⾮同⼀对象
System.out.println("a==b");
if (a.equals(b)) // true
System.out.println("aEQb");
if (42 == 42.0) { // true
System.out.println("true");
}
}
}
说明:String中的equals()是被重写过的,因为Object的equals()比较对象的内存地址,String的equals()比较对象的值;创建String对象时,JVM会在常量池中查找有无已存在的值和要创建的值相同的对象,若有则把它赋给当前引用,若无则在常量池中重新创建一个String对象。
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;
}
3.7 hashCode()与equals()
3.7.1什么是hashCode()?
hashCode()作用:获取哈希码,即散列码,它返回一个int整数。哈希码的作用:确定该对象在哈希表中的索引位置。hashCode()定义在JDK的Object类中,即Java中任何类都包含hashCode()。注意:Object的hashCode()是本地方法,即C/C++实现,通常用来将对象的内存地址转换为整数返回。
public native int hashCode();
散列表存储的是键值对key-value,特点:根据”键”快速检索出对应“值”,其中利用散列码可快速找到所需对象。
3.7.2 为什么要有hashCode()?
例:HashSet如何检查重复?
对象加入HashSet时,HashSet先计算对象的hashcode值来判断对象加入的位置,同时与其他已加入的对象的hashcode值比较,没有相符的hashcode,HashSet会假设对象没有重复;但若由相同hashcode值的对象,则调用equals()检查hashcode相等的对象是否真的相同;若相同,HashSet则不会让其加入成功;若不同,则重新散列到其他位置。这也大大减少了equals()的次数,大大提高了执行速度。
3.7.3 为什么重写equals()时必须重写hashCode()?
若两对象相等,则hashcode一定相同。两对象相等,对两对象分别调用equals()均返回true。但两对象有相同的hashcode值,它们不一定相等。因此,equals()被覆盖,则hashCode()也须被覆盖。
*hashCode()的默认行为是对堆上的对象产生独特值,若没重写,则该class两个对象无论如何都不会相等(即使两个对象指向相同的数据)。
3.7.4 为什么两个对象有相同的hashcode值,也不一定相等?
hashCode()所使用的杂凑算法也许刚好让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(碰撞即不同的对象得到相同的hashCode)。
HashSet对比时,同样的hashcode有多个对象,会使用equals()判断是否真的相同,即hashcode只是用来缩小查找成本。
4.基本数据类型
4.1 Java的基本数据类型?对应的包装类型?各自占用字节?
*boolean依赖于JVM厂商的具体体现,逻辑上是占用1位,实际会考虑计算机高效存储。
Java里用long类型的数据一定要在数值后加上L,否则作为整型解析。
4.2 自动装箱与拆箱
装箱:将基本类型用其对应的引用类型包装起来;
拆箱:将包装类型转换成基本数据类型。
4.3 8种基本类型的包装池和常量池
pdf P16 1.3.3
5.方法(函数)
5.1 为什么Java只有值传递?
方法得到的是所有参数值的靠北。
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
swap(num1,num2);
System.out.println("num1 = " + num1);
System.out.println("num2 = " + num2);
}
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("a = " + a);
System.out.println("b = " + b);
}
output:
a = 20
b = 10
num1 = 10
num2 = 20
解析:
5.2 重载和重写
重载:同样的一个方法根据数据的不同做出不同处理。发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。
这种特征叫重载。
重载解析:编译器挑出具体执行哪个方法,通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应方法。如果编译器找不到匹配的参数,就会产生编译错误,因为根本不存在匹配或者没有一个比其他的更好。
Java允许重载任何方法,而不只是构造器方法。
方法的签名:完整描述一个方法,需要指出方法名以及参数类型。
注意:返回类型不是签名的一部分,即不能有两个名字相同、参数类型也相同但返回不同类型值的方法。
总结:重载是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。
重写:发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,要覆盖父类方法。
1、返回值类型、方法名、参数列表必须相同,抛出异常范围小于等于父类,访问修饰符范围大于等于父类;
2、若父类方法访问修饰符为private/final/static则子类就不能重写该方法,但被static修饰的能够被再次声明;
3、构造方法无法被重写。
总结:重写就是子类对父类的重新改造,外部样子不能改变,内部逻辑可以改变。
5.3 深拷贝和浅拷贝
浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝
深拷贝:对基本数据类型进行值传递,对引用数据类型创建一个新对象,并复制内容
5.4 方法的四种类型
1、无参无返回值
2、有参无返回值
3、有返回值无参
4、有返回值有参