Java基础
基础篇
Java语言有哪些特点?
1、简单易学、有丰富的类库
2、面向对象(Java最重要的特性,让程序耦合度更低,内聚性更高)
3、与平台无关性(JVM是Java跨平台使用的根本)
4、可靠安全
5、支持多线程
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 | 8 | 0.0d | Double |
| boolean | - | false | Boolean |
| char | 2 | \u0000(null) | Character |
注:
1.int是基本数据类型,Integer是int的封装类,是引用类型。int默认值是0,而Integer默认值是
null,所以Integer能区分出0和null的情况。一旦java看到null,就知道这个引用还没有指向某个对象,
再任何引用使用前,必须为其指定一个对象,否则会报错。
2.基本数据类型在声明时系统会自动给它分配空间,而引用类型声明时只是分配了引用空间,必须
通过实例化开辟数据空间之后才可以赋值。数组对象也是一个引用对象,将一个数组赋值给另一个数组
时只是复制了一个引用,所以通过某一个数组所做的修改在另一个数组中也看的见。
虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何
供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机
中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素
占8位。这样我们可以得出boolean类型占了单独使用是4个字节,在数组中又是1个字节。使用int的原
因是,对于当下32位的处理器(CPU)来说,一次处理数据是32位(这里不是指的是32/64位系统,而
是指CPU硬件层面),具有高效存取的特点。
标识符的命名规则
标识符的含义:
- 是指在程序中,我们自己定义的内容,譬如,类的名字,方法名称以及变量名称等等,都是标识符。
命名规则:(硬性要求)
- 标识符可以包含英文字母,0-9的数字,$以及_
- 标识符不能以数字开头
- 标识符不是关键字
命名规范:(非硬性要求)
- 类名规范:首字符大写,后面每个单词首字母大写(大驼峰式)。
- 变量名规范:首字母小写,后面每个单词首字母大写(小驼峰式)。
- 方法名规范:同变量名。
面向对象和面向过程的区别
-
面向过程
- 优点:性能高,节省资源。
- 缺点:不利于维护,复用和扩展
-
面向对象
- 优点:易维护、易复用、易扩展。低耦合。
- 缺点: 性能比面向过程低。
Java 和 C++的区别
- 都是面向对象的语言,都支持封装、继承和多态
- Java 不提供指针来直接访问内存,程序内存更加安全
- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多 继承,但是接口可以多继承。
- Java 有自动内存管理机制,不需要程序员手动释放无用内存
关于 JVM JDK 和 JRE 最详细通俗的解答
Jre: 是Java的运行时环境
Jdk: 是Java的开发工具包,包含 jre ,编译器和工具, 他能够创建和编译程序。
Jvm: 是运行 Java 字节码的虚拟机. 不同的系统采用相同的字节码,这就是Java的移植性好的原因;
使用字节码的好处是什么?
- 字节码只面向虚拟机,在一定程度上解决了传统解释型语言执行效率低的问题
- 使Java程序能够在不同的机器上运行(移植性好)
- JIT 编译器能够让程序的运行速度更快;
为什么引进JIT编译器可以让运行速度更快?
注: 热点代码: 经常需要被调用的方法和代码块
- 因为在:
.class->机器码这一步jvm 类加载器会首先 加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对 比较慢。- 当引进了 JIT 编译器,(运行时编译)。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言
- JIT编译器只需要编译的是热点代码,jvm 会根据每次的运行情况来进行一些优化,因此运行次数越多,执行速度越快
- JDK 9 引入了一种新的编译模式: AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就 避免了 JIT 预热等各方面的开销。JDK 支持分层编译和 AOT 协作使用。但是 , AOT 编译器的编译质量是肯定比不上 JIT 编译器的。
封装 继承 多态
-
封装 : 封装就是把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法
注意:如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。
-
继承 : 继承是 使用已存在的类的定义作为基础建立新类的技术,
-
新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。
-
通过使用继承我们能够非常方便地
复用以前的代码。 -
关于继承的三个注意点:
- 子类拥有父类
非私有的属性和方法。 - 子类
可以拥有自己属性和方法,即子类可以对父类进行扩展。 - 子类可以用自己的方式实现父类的方法.(重写)
- 子类拥有父类
-
-
多态 多态就是指程序中定义的
引用变量所指向的具体类型和通过该引用变量发出的方法调用在编译时并不确定,而是在程序运行期间才确定-
即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
-
在Java中有两种形式可以实现多态:
- 继承(多个子类对同一方法的重写)
- 接口(实现接口并覆盖接口中同一方法)
-
自动装箱与拆箱 ?
- 装箱:将基本类型用它们对应的引用类型包装起来
- 拆箱:将包装类型转换为基本数据类型
$$$$ 重载和重写的区别
重写: 发生在继承关系中,也就是父类和子类之间
1.发生在父类与子类之间
2.方法名,参数列表,返回类型(子类中方法的返回类型是父类中返回类型的子类)必须相同
3.访问修饰符的限制 一定要大于被重写方法的访问修饰符(public > protected > default > private)
4.重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常
重载: 发生在同一个类中
- 方法名必须相同
- 参数类型不同、个数不同、顺序不同
- 方法返回值和访问修饰符可以不同,发生在编译时。
构造器 Constructor 是否可被 override?
- 因为父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override(重写),
- 但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况(例如:无参构造器,和全参构造器)。
构造方法的作用
一个类的构造方法的作用是什么 ?
若一个类没有声明构造方法,该程序能正确执行吗?为什么?
- 主要作用 是完成对类
对象的初始化工作。 - 可以执行。
- 因为一个类即使没有声明构造方法也会有默认的
不带参数的构造方法。
构造方法有哪些特性
- 名字与类名相同;
- 没有返回值,但
不能用void声明构造函数; - 生成类的对象时自动执行,无需调用。
接口和抽象类的区别(重点)
- 接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。
- 类可以实现很多个接口,但是只能继承一个抽象类(多实现,单继承)
- 类可以 不去实现 抽象类和接口 声明的所有方法,但是,在这种情况下,类也必须得声明成是抽象的。
- 抽象类可以在不提供接口方法实现的情况下实现接口。
- Java 接口中声明的变量默认都是 final 的。抽象类可以包含非 final 的变量。
- 接口 是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含 main 方法的话是可以被调用的。
instanceof 关键字的作用
用来测试一个对象是否为一个类的实例
boolean result = obj instanceof Class
其中 obj 为一个对象,`Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。
注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。
到底是 值还是引用?
值传递和引用传递的区别?
值传递: 是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量
引用传递: 一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。 所以对引用对象进行操作会同时改变原对象.
一般认为,java 内的传递都是值传递.
对象的相等 与 指向他们的引用相等,两者有什么不同?
对象的相等, 比较的是 两块内存中存放的内容是否相等.
而引用相等, 比较的是 他们指向的内存地址是否相等;
其实就是说: 两个对象相等,他们的引用不一定相同, 因为两块内存空间可以存放相同的数据
== 和 equals() ?
-
==: 基本数据类型比较的是值,引用数据类型比较的是内存地址 -
equals(): 它的作用也是判断两个对象是否相等。 -
但它一般有两种使用情况:
- 情况1:类没有覆盖(重写) equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
- 情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
$$$$ hashCode 和equals() (重要)
面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写 hashCode 方法?”
一,hashcode() 的性质:
hashcode(): 是对象的散列码. 他只能保证离散, 不能保证唯一.
也就是说:
- 如果两个对象相等,他们的 hashcode() 一定相等.
- 但是 hashcode() 相等, 这两个对象不一定相等;
hashcode() 方法存在的意义是: 提高两个对象 比较的效率;
当两个对象进行比较的时候: 先比较 hashcode(), 如果hashcode() 不相等, 说明这两个对象一定不相等,
如果hashcode() 相等 才会 进一步使用 equals() 方法 比较对象的的 值.
- 也就是说: 只有当 hashcode() 相等的时候, 才能调用 equals();
二, 如果只重写 equals() 不重写 hashcode()
Object类的 hashcode()方法 无法保证 两个相等对象的 hashcode() 相等, 这样也就无法equals();
三, 如果只重写 hashcode() , 不重写equals()
因为 hashcode() 无法保证唯一性, 所以两个不相等的对象,他们的hashcode() 是有可能相等的.
如果不重写equals(), 无法比较他们是不是真的相等.
结论:
所以建议两个方法同时重写.
hashcode() 是为了提高比较效率的, 因为equals() 方法还是比较耗费效率的.
equals() 是为了在 hashcode() 无法区分两个对象是否相等的时候, 用来比较他们时候真的相等的手段.
static 关键字:
在一个静态方法内调用一个非静态成员为什么是非法的?
- 静态方法是在
类加载的时候加载到内存中的,而非静态成员是在实例化的时候加载到内存中 - 因此调用时,非静态成员还
不存在,因此无法调用。
静态方法和实例方法有何不同
- 在外部调用静态方法时,可以使用
"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。-------- > 调用静态方法可以无需创建对象。 - 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制.
成员变量与局部变量的区别有那些?
- 从语法形式上看: 成员变量是属于类的, 而局部变量是在 方法中定义的变量或者是方法的参数;
- 成员变量可以被
public,private,static等修饰符所修饰,而局部变量不能被访问控制修饰符及static所修饰;但是,成员变量和局部变量都能被final所修饰; - 从变量在
内存中的存储方式来看,成员变量是对象的一部分,而对象存在于堆内存,局部变量存在于栈内存 - 从变量
在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。 - 成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情况例外被 final 修饰的成员变量也必须显示地赋值);而局部变量则不会自动赋值
final 关键字
-
修饰变量
- 基本数据类型,数值一旦初始化后不可被更改
- 引用类型,初始化后
不可再指向其他的对象
-
修饰类
- 表明这个类不能被继承。
- final 类中的所有成员方法都会被隐式地指定为 final 方法。
-
修饰方法 : 不可被重写。
String 的一些问题
字符型常量和字符串常量的区别
- 字符型常量:形式上是由单引号引起来的一个字符。相当于一个整形值( ASCII 值),可以参加运算,占两个字节
- 字符串常量:形式上是由双引号引起来的若干个字符,代表的是一个内存地址值,占若干个字节,至少一个 字符为结束标志。
创建了几个对象?
String str = new String("abc");
创建了两个,"abc"本身创建在常量池,通过 new 又创建在堆中。
String StringBuffer 和StringBuilder 的区别是什么 ?
-
可变性:
- String : private final char value[] 因为
final修饰的变量 --- 不可以变 - StringBuffer 和 StringBuilder : char[] value --- 可以变
- String : private final char value[] 因为
-
线程安全:
- String 和 StringBuffer 都是线程安全的,String是常量,StringBuffer 加了同步锁
- StringBuilder 不是线程安全的, 因为没有锁
-
性能
- 每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
- StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。
- 相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结:
- 操作少量的数据 = String
- 单线程操作 --
字符串缓冲区下操作大量数据 =StringBuilder - 多线程操作 --
字符串缓冲区下操作大量数据 =StringBuffer
String 可以被子类继承吗?
- 既然 String 是 final 的,所以不能被继承。
String.trim()方法去掉的是哪些字符?
- trim 去掉字符串首尾的空白字符。
Java序列化中如果有些字段不想进行序列化 怎么办
- 对于不想进行序列化的变量,使用
transient关键字修饰。 transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;- 当对象被反序列化时,被
transient修饰的变量值不会被持久化和恢复。 transient只能修饰变量,不能修饰类和方法
反射
什么是反射?
Java反射机制是在运行状态中,对于任意一个类, 都能够知道这个类的所有属性和方法.对于任意一个对象,都能够调用他的任意一个方法和属性, 这种动态获取信息以及动态调用对象的方法的功能称之为 Java 的反射机制.
| 类 | 含义 |
|---|---|
| java.lang.Class | 代表整个字节码.代表一个类型,映射整个类 |
| java.lang.reflect.Method | 代表字节码中的方法字节码, 映射类中的方法 |
| java.lang.reflect.Constructor | 代表字节码中的构造方法字节码.映射类中的构造方法 |
| java.lang.reflect.Field | 戴白字节码中的属性, 映射类中的成员变量(静态变量+实例变量) |
获取Class对象的三种方式
- 通过 对象的getClass() 方法
Class stuClass = stu.getClass();
- 通过 类的静态属性 class
Class stuClass1 = Student.class;
- 通过Class类的静态方法 forName()
try {
Class stuClass2 = Class.forName("com.zhong.fanshe.Student");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
通过反射获取 构造方法
实现Java反射的类:
Class:表示正在运行的Java应用程序中的类和接口(注意: 所有获取对象的信息都需要Class类来实现。)Field:提供有关类和接口的属性信息,以及对它的动态访问权限。Constructor:提供关于类的单个构造方法的信息以及它的访问权限Method:提供类或接口中某个方法的信息
反射机制的优缺点
优点:
- 能够运行时动态获取类的实例,提高灵活性;
- 与动态编译结合
缺点:
- 使用反射性能较低,需要解析字节码,将内存中的对象进行解析。
- 相对不安全,破坏了封装性(因为通过反射可以获得私有方法和属性)
性能低的优化方案:
1、通过setAccessible(true)关闭JDK的安全检查来提升反射速度;
2、多次创建一个类的实例时,有缓存会快很多
3、ReflflectASM工具类,通过字节码生成的方式加快反射速度