1.面向对象和面向过程的 区别
区别:
面向对象和面向过程的 区别
面向过程:就是分析解决问题所需要的步骤,然后用函数把这些步骤一步步实现,使用的时候一个个一次调用就行。
面向对象:把构成的问题分解为一个个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为。
2.面向过程和面向对象的优缺点
面向过程
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展
面向对象
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
缺点:性能比面向过程低
2.Java与C++的异同
1.都是基于面向对象的语言
2.java不提供指针访问内存,c++提供指针访问内存
3.垃圾回收机制不同:java不需要开发者手动回收内存,有java回收机制自动回收;c++需要开发者自己手动回收。java的内存管理相对c++更加安全。
4.java不支持多继承,c++支持多继承。
3.JVM、JDK和JRE的区别
- JVM(java virtual machine)java虚拟机
- JRE(java runtime environment)java运行时环境
- JDK(java development kit)java开发工具包,不仅包含jre和jvm,还提供javac编译器和javadoc等其他开发工具。
4.java语言的特点
- 面向对象
- 跨平台
- 垃圾回收机制
- 支持多线程
- 支持便捷的网络编程
- 编译和解释
- 安全
5.面向对象的特征
三大特征:封装、继承、多态
- 封装
解释:封装是隐藏对象的属性和实现细节,只对外提供可访问或者修改的接口。
目的:封装是为了简化编程和增加程序的安全性,使用者无需了解对象的具体实现,只需要调用对象的具体方法就可满足目的。
- 继承
解释:继承是在已有类的基础上定义新的类。子类拥有父类所有的的属性和方法,但是子类不能拥有父类的私有属性和方法,也不能访问和使用。
目的:为了代码的复用,子类可以复用父类的代码
- 多态
解释:多态是同一个行为具有多个不同表现形式或形态的能力。相同对象调用相同的方法,参数也相同但是它们的表现形式不一样。
目的:对台是为了增加程序的可扩展性和维护性,java使用继承和接口的2大特性实现多态。
6.重载和重写的区别
重载:描述一个类中有多个方法名相同,但是他们的参数、类型、返回值和参数顺序可能不同,表现形式也就不一样。(重载不能只是反回值类型一样)
重写:重写描述的是子类对父类的某个方法的逻辑进行重写,但是重写的只是方法的内容,方法名、参数、类型、顺序、返回值都不一样。--除了处理逻辑不一样,其他的都一样。
7.接口和抽象类的区别
- 接口需要被实现,抽象类需要被继承
- 接口里的方法都是public abstract 的,抽象类也允许非抽象的方法(在JDK8中,接口被允许定义defalut方法,JDK9中还允许定义private私有方法)。
- 一个类允许实现多个接口,但是只允许继承一个抽象类
- 接口是对类的规范,规范的是行为及能力。而抽象类是对类的抽象,抽象的是逻辑。
8.静态方法和成员方法的区别
- 静态方法属于class类,成员方法属于示例化的对象
- 静态方法只能使用静态方法和属性,不能使用成员方法和属性;因为静态属性和方法在对象还没实例化的时候就存在类中。
--就是不允许一个已存在的事物使用一个不存在的事物。
9.子类与父类初始化顺序
父类静态代码块 > 子类静态代码块 > 父类普通成员变量和代码块 > 子类普通成员变量和代码块 > 父类构造函数 > 子类构造函数
10.自动拆箱和装箱
自动装箱是指:将基本数据类型转为对应的包装类对象的过程。
自动拆箱是指:将包装类型转为对应的基本数据类型。
1.自动装箱实际上是调用包装类对象的valueof方法,如:Integer.valueof(1)
2.自动拆箱实际上是调用包装类的xxxValue方法,如:Integer.intValue()
3.在自动装箱的时候,如果包装类允许缓存并且值在缓存的范围内,那么装箱产生的对象会被缓存到常量池中。
Integer、Byte、Short、Long、Character包装类型具有缓存池,
Float、Double、Boolean不具有缓存池。
4.包装类的缓存池缓存范围基本都为:-128—— 127之间,除了Character的缓存范围为0——127。
11.String为什么不可变
11.1什么是不可变对象
类内部所有字段都是final修饰的
类内部所有的字段都是私有的(被private)修饰
类不能被继承
类不能对外提供修改内部状态的方法,setter方法也不行
类内部的字段如果是引用,也就是说可以指向可变的对象,那外部也不能获取到这个引用。
对象不可变:一个对象在创建完之后就不能更改其状态(不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象状态也不能改变),则这个对象是不可变的
String的两个成员变量, value[]是修饰的,就是说一旦初始化就不能修改, 并且在String类的外部不能访问这个成员变量。
/** The value is used for character storage. */
private final char value[];
String a = "ABCabc";
System.out.println("a = " + a);
a = a.replace('A', 'a');
System.out.println("a = " + a);
打印结果为:
a = ABCabc
a = aBCabc
那么a的值看似改变了,其实也是同样的误区。再次说明, a只是一个引用, 不是真正的字符串对象,在调用a.replace('A', 'a')时, 方法内部创建了一个新的String对象,并把这个心的对象重新赋给了引用a。String中replace方法的源码可以说明问题:
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}
String对象真的不可变吗?
那么用什么方式可以访问私有成员呢? 没错,用反射, 可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。下面是实例代码:
public static void testReflection() throws Exception {
//创建字符串"Hello World", 并赋给引用s
String s = "Hello World";
System.out.println("s = " + s); //Hello World
//获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");
//改变value属性的访问权限
valueFieldOfString.setAccessible(true);
//获取s对象上的value属性的值
char[] value = (char[]) valueFieldOfString.get(s);
//改变value所引用的数组中的第5个字符
value[5] = '_';
System.out.println("s = " + s); //Hello_World
}
打印结果为:
s = Hello World
s = Hello_World
11.2 JAVA语言为什么把String类型设计成不可变——设计、效率、安全
1. 便于实现字符串常量池(string pool)
java 中,会大量使用string常量,如果每次声明一个string都创建一个string对象,会造成极大的空间资源浪费。java 提出了string pool的概念,在堆中开辟一块存储空间string pool,当初始化一个string变量时,如果该字符串已经存在,就不会去创建一个新的字符串,而是返回已经在存在了的字符串的引用。
2. 使多线程安全
在并发场景下,多个线程同时读一个资源,是安全的,不会引发竞争,但对资源进行写操作时是不安全的,不可变对象不能被写,所以保证了多线程的安全。
// 不可变的String
public String appendStr(String s) {
s += "bbb";
return s;
}
// 可变的StringBuilder
public StringBuilder appendSb(StringBuilder sb) {
return sb.append("bbb");
}
public static void main(String[] args) {
Son son = new Son();
String s = "aaa";
String ns = son.appendStr(s);
System.out.println("String aaa>>>" + s);
// StringBuilder做参数
StringBuilder sb = new StringBuilder("aaa");
StringBuilder nsb = son.appendSb(sb);
System.out.println("StringBuilder aaa >>>" + sb.toString());
}
3. 避免安全问题
在网络连接和数据库连接中字符串常常作为参数,例如,网络连接地址URL,文件路径path,反射机制所需要的String参数。其不可变性可以保证连接的安全性。如果字符串是可变的,黑客就有可能改变字符串指向对象的值,那么会引起很严重的安全问题。
4. 加快字符串的处理速度
由于String是不可变的,保证了hashcode的唯一性,于是在创建对象时其hashcode就可以放心的缓存了,不需要重新计算。这也就是Map喜欢将String作为Key的原因,处理速度要快过其它的键对象。所以HashMap中的键往往都使用String。
12. final关键字的作用
- final修饰类,该类不能被继承,该类成为最终类,无法被继承。简称为“断子绝孙类”。
- final修饰方法,这个方法将成为最终方法,无法被子类重写。但是,该方法仍然可以被继承。
- final修饰引用
-- 如果引用为基本数据类型,则该引用为常量,该值无法修改;
-- 如果引用时类的成员变量,则必须当场赋值,否则编译会报错;
-- 如果引用为引用数据类型,比如对象、数组,则该对象、数组本身可以修改,但指向该对象或数组的地址的引用不能修改。
final class Demo {
String name;
//final int age; //3. 此处不赋值会报错
final int age = 10;
}
public class Person {
public static void main(String[] args) {
//1. 基本数组类型为常量,无法修改
final int i = 9;
//i = 10;
// 2. 地址不能修改,但是对象本身的属性可以修改
Demo p = new Demo();
p.name = "lisi";
final int[] arr = {1,2,3,45};
arr[3] = 999;
// arr = new int[]{1,4,56,78};
}
}
13.StringBuilder和StringBuffer区别
1.都是可变字符串
2.StringBuilder是线程不安全的,StringBuffer是线程安全的
单线程考虑StringBuilder,多线程有竞争关系的使用StringBuffer。类似于HashMap和HashTable的关系。
14. equals知识点类型
== 如果是基础数据类型,则比较的是值,如果是引用数据类型则比较的是内存地址
equals 比较的是对象的值。因此在java中比较2个对象的值是否相等
Object默认的equals实际就是==
public boolean equals(Object obj) {
return (this == obj);
}
- 自反性:x.equals(x) == true 永远成立
- 非空性:x.equals(null) == false 永远成立
- 对称性:如果x.equals(y) == true,那y.equals(x) == true
- 传递性:如果x.equals(y) == tue,并且y.equals(z) == true,那么一定满足x.equals(z) == true
- 一致性:如果x.equals(y) == true,那么只要x和y的值不变,那么x.equals(y) == true 永远成立
为什么必须重写hashCode方法
参与equals函数的字段,也必须都参与hashCode 的计算。
默认hashcode算法实现
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
15. 深拷贝和浅拷贝
深拷贝:拷贝所有的内容,除了基本数据类型变量复制一份,连引用类型变量也复制一份。
浅拷贝:复制基本数据类型变量,对于引用类型的变量,直接返回这个引用本身。
16.IO流分类
按照流的流向,分为:输入流和输出流。
按照操作单元,分为:字节流和字符流。
使用字节流还是字符流?
考虑通用性,应该使用字节流。如果只是文本文件的操作,可以使用字符流。
17.Java异常体系结构
在Java中,异常分为 Exception和Error,这2个类都继承自Throwable。
Exception:
Exception异常是程序本身可以处理的。Exception 分为运行时异常(RuntimeException)和 非运行时异常(CheckedException)。
RuntimeException: RuntimeException(运行时异常)是在程序运行时可能会发生的异常,如NullPointException, 这类异常往往是不可预料的,编译器也不会要求你手动try catch或throws。
CheckedException: CheckedException(非运行时异常)是RuntimeException以外的异常,如IOException, 这类异常要求必须显示的try catch或throws , 如果不处理,那么编译就不会通过。
Error
Error错误是程序无法处理的,表示程序或JVM出现了很严重的,无法解决的问题。
18. Comparable和Comparator
Comparable: 自然排序接口。实现了它的类意味着就支持排序。
Comparator: 外部比较器。无需让需要排序的对象实现排序逻辑,而是根据Comparator定义的逻辑来排序。
Comparator相较于Comparable更加的灵活。
19. 为什么要慎用 Arrays.asList()?
因为Arrays.asList这个方法返回的根本就不是我们期盼的ArrayList, 而是Arrays类内部实现的ArrayList,这个内部类只支持访问和set操作, 并不支持remove,add,clear等修改操作。
20.Java中引用的类型
Java中引用类型总共有四种: 强引用,软引用,弱引用,虚引用。
- 强引用(Strong Reference): Java程序中绝大部分都是强引用,一般使用new关键字创建的对象就是强引用。 只要强引用存在,强引用的对象就不会被回收,除非不可达(参考jvm部分)
- 软引用(Soft Reference): 软引用一般不会被回收,但是当堆内存不够的时候, 比如几乎快要发生OOM的时候,就会回收掉软引用对象。
- 弱引用(Weak Reference): 只要垃圾回收开始,就会回收掉弱引用的对象。
- 虚引用(Phantom Reference,又称幽灵引用): 和其他几种引用不同,虚引用不决定对象的生命周期, 它在任何时候都可能被回收掉。
强引用
Object o=new Object();
当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
软引用(SoftReference)
String str=new String("abc"); // 强引用
SoftReference<String> softRef=new SoftReference<String>(str); // 软引用
应用:
软引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。
- 如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建
- 如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出
这时候就可以使用软引用
Browser prev = new Browser(); // 获取页面进行浏览
SoftReference sr = new SoftReference(prev); // 浏览完毕后置为软引用
if(sr.get()!=null){
rev = (Browser) sr.get(); // 还没有被回收器回收,直接获取
}else{
prev = new Browser(); // 由于内存吃紧,所以对软引用的对象回收了
sr = new SoftReference(prev); // 重新构建
}
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
弱引用(WeakReference)
弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。
String str=new String("abc");
WeakReference<String> abcWeakRef = new WeakReference<String>(str);
str = null;
如果这个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用 WeakReference 来记住此对象。
虚引用(PhantomReference)——相当于没有引用
虚引用通过PhantomReference类实现,虚引用完全类似于没有引用。虚引用对对象本身没有太大影响,对象甚至感觉不到虚引用的存在。如果一个对象只有一个虚引用时,那么它和没有引用的效果大致相同。虚引用主要用于跟踪对象被垃圾回收的状态,虚引用不能单独使用,虚引用必须和引用队列联合使用。
Java 4种引用的级别由高到低依次为:
-
强引用 > 软引用 > 弱引用 > 虚引用
在实际程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。
21.JVM热点问题(hotspot虚拟机)
21.1 jvm主要包括四个部分
- 类加载器(ClassLoader):将class文件加载到JVM中
- 执行引擎:负者将字节码指令解释编译成对应平台上的机器码
- 内存区(也叫运行时数据区)
- 本地方法接口
21.2 jvm内存区布局简单可分为三大块:
- 线程共占内存:堆内存;方法区
- 线程独享内存区域:虚拟机栈;本地方法栈;程序计数器
- 直接内存
堆内存的大小值可以通过配置-xms(设置最大值)和-xmx(设置最小值)来设置,当堆超过最大值就会抛出OOM(OutOfMemoryError)异常
21.3堆内存通常被分为三块区域
在JDK1.8之前分为:新生代;老年代;永久代
在JDK1.8之后将最初的永久代取消分为:新生代;老年代;元空间
方法区:用于存储已经被JVM加载的类型信息,常量,静态变量,代码缓存等信息。
程序计数器:程序寄存器是线程独享的一块很小的内存区域,保存当前线程所执行字节码的位置,包括执行的指令,跳转,异常处理等。
虚拟机栈:java虚拟机栈是用来描述java方法的执行,每个方法被执行时就会同步创建一个栈帧(且多和线程联系在一起;每当创建一个线程,JVM就会为这个线程创建一个对应的java栈)用于存放局部变量表,操作栈,方法返回值等信息;每一个方法的从调用到执行完毕就对应着一个栈帧在java虚拟机栈中入栈到出栈的过程。
本地方法栈:本地方法栈与java虚拟机栈类似,只不过java虚拟机栈是为执行java方法服务的,本地方法栈是为本地方法(native)服务的。
22. 类加载
类从被加载到内存中开始到卸载出内存为止整个生命周期包括以下7个阶段:
1.加载、 2.验证、 3.准备 、4.解析、 5.初始化、 6.使用、 7.卸载
加载:是指JVM读取Class文件,并根据CLASS文件创建对象;
- 验证:确保class文件符合虚拟机要求,保障虚拟机的自身的安全。
- 准备:主要是在方法区中为类变量分配内存空间,并这是变量的初始值。初始值是指根据数据类型的默认值。final和非final类型的变量在准备节点的数据初始化过程不同。
- 解析:解析类,包括接口、字段、方法,解析时会把常量池中的符号引用替换为直接引用。
符号引用是指以一组符号来描述所引用的目标;直接引用是可以直接指向目标的指针。两者重要区别:使用直接引用,引用目标必定已经存在于虚拟机的内存中了,而符号引用不一定。
- 初始化:通过执行类的构造器的<client>方法为类进行初始化。<client>方法是在编译节点由编译器自动收集类中的静态语句块和变量的赋值操作组成的。一个类中既没有静态变量赋值操作也没有静态语句块时,编译器不会为该类生成<client>方法。
准备阶段:static 修饰,p和apple会被编译器赋予对应类型的默认初值(null和0.0)
初始化阶段:由于static字段执行顺序是由字段在源文件中出现的顺序决定的,所以会先执行new Price(2.7),分配对象空间并对其做初始化,在这个时候apple的值还是0.0,所以最终结果为-2.7
class Price {
static Price P = new Price(2.7);
static double apple = 20;//加上final后 输出结果为17.3
double Price;
public Price(double orange) {
Price = apple - orange;
}
}
public class PriceTest {
public static void main(String[] args) {
System.out.println(Price.P.Price);//结果为-2.7
}
}
当使用final时,字面量20会在编译期加入Price类的常量池中的CONSTANT_Double_info,在遇到final字段时,在编译时编译器将会为该静态属性赋予ConstantValue属性,ConstantValue属性的作用是通知虚拟机自动为静态变量赋值 。
对于类变量,有两种方式赋值:
1.在类构造器<clinit>方法中
2.使用ConstantValue属性,具有该属性的静态字段将会在类加载的准备阶段被赋予所指定的的值(这也是final static 字段必须手动赋值的原因)。所以最终结果为17.3。
23.JVM的Client模式与Server模式
JVM有两种运行模式Server与Client\
两种模式的区别在于:
Client模式启动速度较快,Server模式启动较慢;但是启动进入稳定期长期运行之后Server模式的程序运行速度比Client要快很多。这是因为Server模式启动的JVM采用的是重量级的虚拟机,对程序采用了更多的优化;而Client模式启动的JVM采用的是轻量级的虚拟机。所以Server启动慢,但稳定后速度比Client远远要快。
查看运行模式:/Java/jdk/jre/lib/amd64/jvm.cfg
第一行的模式 被作为虚拟机默认的运行模式,如果想让虚拟机按照另外一种模式运行,将第一行和第二行代码交换位置。
Server模式
-server KNOWN
-client IGNORE
Client模式
-client KNOWN
-server KNOWN
https://www.toutiao.com/i6882293332907655680/
https://blog.csdn.net/qq_38186433/article/details/106759491
https://blog.csdn.net/qq_39327985/article/details/81839685