大家好!我是未来村村长,就是那个“请你跟我这样做,我就跟你这样做!”的村长👨🌾!
||To Up||
未来村村长正推出一系列【To Up】文章,该系列文章重要是对Java开发知识体系的梳理,关注底层原理和知识重点。”天下苦八股文久矣?吾甚哀,若学而作苦,此门无缘,望去之。“该系列与八股文不同,重点在于对知识体系的构建和原理的探究。
该篇文章没有系统的梳理那些常见的Java基础知识点,而是作者在读《Java核心技术卷Ⅰ》发现的一些高校教材忽略但是面试经常问到的知识点,进行一个详细整理。
一、位运算符
1、原码、反码与补码
- 原码:指十进制数的二进制形式,用最高位表示符号位,‘1’表示负号,‘0’表示正号。其他位存放该数的二进制的绝对值。
- 反码:正数的反码还是等于原码,负数的反码就是他的原码除符号位外,按位取反。
- 补码:正数的补码等于他的原码,负数的补码等于其反码+1。
我们知道的原码是十进制的二进制形式,那反码和补码的作用是干啥的呢?用来计算。我们常说的位运算就是基于补码来进行计算,求补码我们常常用到反码,虽然两者之间除了计算关系以外没有必然联系。
//5的原码:0000 0101
//-5的原码:1000 0101
//-5的反码:1111 1010
//-5的补码:1111 1011
2、&、|、~、^
- &:如果两边都为1,结果为1,否则为0
- |:如果两边任一边为1,结果为1,否则为0
- ~:取反,1为0,0为1
- ^:如果两边相同则为0,不同则为1
//5的二进制为101,6的二进制位110
5 & 6;
//计算结果100,十进制为4[0*1+0*2+0*4]
5 | 6;
//计算结果111,十进制为7[1*1+1*2+1*4]
~ 5;
//计算结果010,十进制为2
5 ^ 6;
//计算结果为011,十进制为3
3、<<(左移)和>>(右移)
x<<a的过程如下:
- x转换为二进制的补码形式
- x的补码向左移动a位
- 移位后的数转换为十进制
5<<1;
//0000 0101 向左移动1位
//0000 1010 变成10[0*1+1*2+0*4+1*8]
5>>1;
//0000 0010 变成2
二、类与对象
1、常见关系
- uses-a:依赖关系,如果一个类的方法使用或操纵另一个类的对象,则称为一个类依赖于另一个类。
- is-a:继承关系,一个类通过extends实现对另一个类的拓展
- has-a:聚合关系,一个类的对象含有另一个类的对象
2、修饰符
修饰符 | 说明 |
---|---|
static | 可修饰成员变量和方法,被static修饰的变量和方法会在类加载时执行加载,未被static修饰的变量和方法称为实例变量和实例方法。静态方法不能操纵实例变量。 |
final | 可修饰类、方法和成员变量,被final修饰的类不能被继承,被final修饰的方法不能被重写,被final修饰的成员变量为常量,在程序执行过程中不可变。 |
abstract | 可修饰类和方法,被abstract修饰的类称为抽象类,抽象类中可以有抽象方法,也可以没有。被abstract修饰的方法,仅有方法头没有实现,含有abstract方法的类必须是abstract类。abstract不能和final同用。 |
default | 为了解决接口中不能有默认方法的问题。 |
private | 访问控制符,可修饰方法和成员变量。 |
protected | 访问控制符,可修饰方法和成员变量。 |
public | 访问控制符,可修饰类、成员变量和方法,不可修饰局部变量。 |
native | Java平台有个用户和本地C代码进行互操作的API,称为Java Native Interface (Java本地接口)。native 关键字告诉编译器(其实是JVM)调用的是该方法在外部定义,这里指的是C。 |
在继承关系中,子类重写父类的方法其访问权限不能降级。私有方法不能通过对象进行访问,子类不继承父类的private方法和private变量。private可以和static并用。
被abstract修饰的方法不能被private修饰,默认被public修饰,且不能被static修饰,只能为实例方法。
3、方法传参
Java语言中的方法参数总是按值调用,即传入方法的参数是参数值的副本。在Java中有两种参数类型,一是基本数据类型,二是对象引用。
public int sum(int arr[]){
int sum = 0;
for(int i=0;i<arr.length;i++) sum+=arr[i];
return sum;
}
//该方法在传入参数arr1时
int arr1[];
sum(arr1);
//可看作下列形式
public int sum(int arr[]){
int arr[] = arr1;//建立一个副本
int sum = 0;
for(int i=0;i<arr.length;i++) sum+=arr[i];
return sum;
}
所以方法:
-
不能修改基本数据类型的参数
-
可以改变对象参数的状态,这里改变的是状态(对象的成员变量或数组对象中的元素值)
-
不能让一个对象参数引用一个新的对象
注意看最后一点,假设A是一个类,如下操作是无法执行成功的,我们只能通过对象去找到具体的实例进行修改
public void change(A a,A b){
A temp = a;
a = b;
b = temp;
}
//因为执行过程其实可以看作,对对象的赋值其实是对对象副本的赋值
public void change(A a,A b){
A temp1 = a;
A temp2 = b;
A temp = temp1;
temp1 = temp2;
temp2 = temp;
}
4、初始化块
初始化数据字段的方法除了在声明时赋值和在构造器中设置值,还有第三种机制——初始化块。在一个类的声明中,可以包含任意多个代码块,只要构造这个类的对象,这些块就会被执行。
class People{
private static int nextNo = 1;
private int no;
{
no = nextNo;
nextNo++;
}
}
//每当new一个对象时,就会执行代码块中的表达式
People people = new People();
5、对象包装器与自动装箱
Integer、Long、Float、Double、Short、Byte、Character、Boolean对应int、long、fooat等基本类型,我们将这些类称为包装器(wrapper)。
Java中对于基本类型和其对应对象的转换会自动完成,我们称为自动装箱和自动拆箱。
(1)自动装箱:
list.add(3);
//自动转换为
list.add(Integer.valueOf(3));
(2)自动拆箱:
int n = list.get(i);
//自动转换为
int n = list.get(i).intValue();
三、继承
1、子类构造器
如果子类的构造器没有显式地调用父类的构造器,将自动调用父类的无参构造器。如果父类没有无参构造器,并且子类没有显式调用父类的其他有参构造器,则会发生编译错误。
super();
super的含义有两个:
- 调用父类的方法
- 调用父类的构造器
this的含义也有两个:
- 隐式参数的引用
- 调用该类的其他构造器
2、Object类
Object类是Java.lang下的核心类,如果某个类没有明确指出继承一个父类,则Object被认为是这个类的父类。Object提供了以下方法:getClass、hashCode、equals、toString、notify、notifyAll、wait、clone、finalize、registerNatives。
(1)equals
Object类中的equals方法用于检测一个对象是否等于另一个对象,对于未重写equals方法的类会默认使用Object的equals方法。该方法如下,使用==进行比较。
public boolean equals(Object obj) {
return (this == obj);
}
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对象是否是同一引用,如果是则返回True,如果不是的话会将其转换为char[]数组进行逐一比较。
(2)hashCode
public native int hashCode();
这里的hashCode使用native修饰,说明是一个外部方法。哈希码是由对象计算出的整型值,两个不同的对象的哈希码基本不相同,但是不一定。我们在重写hashCode方法和equals方法时要遵循这样的规则:x.equals(y)返回true,则x.hashCode()与y.hashCode必须返回相同的值。即equals返回true,则两者的哈希码必须相等。两者的哈希码相等但两者不一定相等。
String类的hashCode()方法如下:其中value代表该字符串的char数组。
//private final char value[];
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
(3)getClass
public final native Class<?> getClass();
同样是一个Native Method,这里利用反射机制,可以通过对象来获取类对象。
3、深拷贝与浅拷贝【常问】
深拷贝和浅拷贝都是对象的拷贝。Object中的clone方法声明为protected,所以不能直接调用。
浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。
深拷贝:深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
我们一般在实现clone方法前,先实现Cloneable接口,这个接口指示一个类提供了一个安全的clone方法。
Object中的clone方法是一个浅拷贝方法,方法声明为protected我们需要对其进行重写,浅拷贝只对Object进行克隆。
class Person implements Cloneable{
Head head = handsomeHead;
//创建了一个Peison类,类中有head对象赋值handsomeHead
public Person clone() throws CloneNotSupportedException{
return (Person) super.clone();
}
}
我们也可以写一个深拷贝,深拷贝还需要对fields中的对象变量进行克隆。
class Person implements Cloneable{
Head head = handsomeHead;
public Person clone() throws CloneNotSupportedException{
Person cloned = (Person) super.clone();
cloned.head = (Head) head.clone();
}
}
四、反射机制
1、Class类
有一句话“面向对象的世界里,万物皆对象”,在Java中类也是对象,我们写的每一个类都是对象都是java.lang.Class类的对象。称为类的类型对象,一个 Class
对象对应一个加载到 JVM 中的一个 .class
文件。我们可以通过这个 Class
对象看到类的结构,就好比一面镜子。所以我们形象的称之为:反射。
我们可以看到Class的构造方法是一个私有方法,说明只有JVM能通过调用该方法创建该类的对象。
private Class(ClassLoader loader) {
classLoader = loader;
}
我们回顾一下类的加载机制,类的加载会经历五个过程:
- 加载:通过类的完全限定名称获取定义该类的字节流,在堆中生成一个代表该类的Class对象【饿汉单例】,作为方法区该类各种数据的访问入口
- 验证:确保class文件的字节流的信息符合虚拟机要求
- 准备:为类变量(static)分配内存并设置初始值。
- 解析:将常量池符号引用替换为直接引用
- 初始化:虚拟机执行类构造器<clinit>()方法的过程
Class有几个重要的方法:
public T newInstance();//创建对象
public String getName();//返回完整类名带包名
public String getSimpleName();//返回类名
public Field[] getFields();//返回类中public修饰的属性
public Field[] getDeclaredFields();//返回类中所有的属性
public Field getDeclaredField(String name);//根据属性名name获取指定的属性
public native int getModifiers();//获取属性的修饰符列表
public Method[] getDeclaredMethods();//返回类中所有的实例方法
public Method getDeclaredMethod(String name, Class<?>… parameterTypes);//根据方法名name和方法形参获取指定方法
public Constructor<?>[] getDeclaredConstructors();//返回类中所有的构造方法
public Constructor getDeclaredConstructor(Class<?>… parameterTypes);//根据方法形参获取指定的构造方法
public native Class<? super T> getSuperclass();//返回调用类的父类
public Class<?>[] getInterfaces();//返回调用类实现的接口集合
2、反射的原理与作用
反射的概念:反射使Java代码能够发现有关已加载类的字段、方法和构造函数的信息,并在安全限制内使用反射的字段、方法和构造函数对其底层对应项进行操作。
Java反射机制的实现要借助于4个类:Class,Constructor,Field,Method;通过这四个对象我们可以粗略的看到一个类的各个组成部分,其中最核心的就是Class类,它是实现反射的基础,必须先获得Class才能获取Method、Constructor、Field。
- Class:类的类型对象
- Constructor:类的构造器对象
- Field:类的属性对象
- Method:类的方法对象
反射的作用:在运行时判断任意一个对象所属的类,构造任意一个类的对象,判断任意一个类所具有的成员变量和方法,调用任意一个对象的方法。例如动态代理、工厂模式。
3、获取类以及其成员变量、构造方法和方法
(1)获取类
要想获取类的成员变量、构造方法和方法,就必须先获取类的类型对象,获取class对象有以下方式:
方式 | 备注 |
---|---|
Class.forName(“完整类名带包名”) | 静态方法 |
对象.getClass() | |
任何类型.class |
(2)获取构造方法
在Class对象中获取全部Constructor的方法如下:
Constructor[] constructors = xxxClass.getDeclaredConstructors();
Constructor类的相关方法如下,从方法中我们可以知道当我们从class获取了Constructor后,就可以返回其方法名等信息以及使用该构造器创建实例对象。
public String getName();//返回构造方法名
public int getModifiers();//获取构造方法的修饰符列表,返回的修饰符是一个数字
public Class<?>[] getParameterTypes();//返回构造方法的修饰符列表
public T newInstance(Object … initargs);//创建对象
获取指定的构造方法,通过该构造方法获取实例对象
Constructor construction = xxxClass.getDeclaredConstructor(int.class, String.class, String.class, boolean.class);//根据parameterTypes获取指定的构造方法,对应的字段类型
Object xxxObject = constructionc1.newInstance(123, "xxx", "xxx", true);//Constructor类的newInstance方法
(3)获取成员变量
在Class对象中获取全部Fields的方法如下
Field[] fields = xxxClass.getFields();
Field类相关方法如下,从方法中我们可以知道获取了Field对象后,不仅能返回该类相关成员变量的信息,还能设置属性值
public String getName();//返回属性名
public int getModifiers();//获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号
public Class<?> getType();//以Class类型,返回属性类型
public void set(Object obj, Object value);//设置属性值
public Object get(Object obj);//读取属性值
通过class获取指定类型的field,并为其赋值
Class studentClass = Class.forName("xxx");
Object obj = studentClass.newInstance();
Field Field1 = studentClass.getDeclaredField("Field1Name");
noField.set(obj,xxxValue);
(4)获取方法
在Class对象中获取全部Method的方法如下
Method[] methods = xxxClass.getDeclaredMethods();
Method类相关方法如下,通过其方法我们可以知道,其不仅能返回方法的相关信息,还能对返回方法进行调用。
public String getName();//返回方法名
public int getModifiers();//获取方法的修饰符列表
public Class<?> getReturnType();//以Class类型,返回方法类型
public Class<?>[] getParameterTypes();//返回方法的修饰符列表
public Object invoke(Object obj, Object… args);//调用方法
获取指定方法并调用
Class xxxClass = Class.forName("xxx");
Object obj = xxxClass.newInstance();
Method xxxMethod = xxxClass.getDeclaredMethod("MethodName",ParameterType1,ParameterType2);//注:没有形参就不传
Object resValues = xxxMethod.invoke(obj, xxx, xxx);//接收返回的结果,需要强转型
(5)创建实例
我们可以通过获取的类,直接创建实例对象,也可以通过获取相应的构造方法来创建实例对象。
Object obj = constructor.newInstance(ParameterTypes);
Object obj = xxxClass.newInstance();//无参构造
五、泛型机制
1、泛型的原理
泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
Java泛型是用于编译时期的类型检查,是伪泛型。因为无论何时定义了一个泛型类型,都会自动提供一个相应的原始类型。这个原始类型的名字就是去掉类型参数后的泛型类型名。类型变量会被擦除,Java编译器进行类型擦除步骤如下:
- 检查泛型类型,获取目标原始类型
- 擦除泛型变量,并替换为限定类型(对于限定则替换为Object)
- 调用相关函数,将结果强制转换为目标类型
例如:
class xxxClass<T>{
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
擦除后
class xxxClass{
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
泛型的相关作用如下:
- 保证了类型的安全性,当定义泛型类型后,如果使用两种不同的类型在编译时期即会报错
- 消除编译时期的强制转换,使代码具有可读性,但实际上是将强制转换过程交给Java编译器执行
2、泛型的运用
(1)泛型类
public class 类名 <T,V> {
T value1;
V value2;
V mehthod(V value){
return value;
}
}
(2)泛型接口
public interface 接口名 <T,V> {
T value1;
V value2;
V mehthod(V value);
}
(3)泛型方法
public <T> T genercMethod(T t){
return t;
}
3、通配符和限定符
严格的泛型类型使用起来不够灵活,Java设计者设计了通配符来使泛型的运用更加灵活:
-
通配符?:在泛型中可以使用
?
来代表任意类型。 -
父类限定super:super是类型上界,意味着我们只能传递给该类、接口或方法xxxClass类或者该类的子类。
<? super xxxClass>
- 子类限定extends:extends是类型下界,意味着我们只能传递给该类、接口或方法xxxClass类或者该类的父类。
<? extends xxxClass>
4、泛型使用限制
- 不能实例化类型变量(对象)
- 不能构造泛型数组,但可以声明
- 不能在静态字段或方法中引用类型变量
- 注意擦除后的冲突:若两个接口类型是同一接口的不同参数化[泛型参数],一个类或类型变量不能作为这两个接口的子类
——————————————————————
作者:未来村村长
参考:《Java核心技术卷Ⅰ》
个人网站:www.76pl.com
👨🌾点个关注,白嫖不迷路👩🌾
——————————————————————