又到了
金三银四、金九银十
的时候了,是时候收藏一波面试题了,面试题可以不学,但不能没有!
一个合格的计算机打工人
,收藏夹里必须有一份 JAVA八股文面试题 ,特别是即将找工作的计算机人,希望本篇博客对你有帮助!
ps:本文参考了诸多大佬的面试题帖子,例如:白大锅、哪吒、英雄哪里出来 。都是牛逼级别的存在,大家也可以去关注一手,学习进步!
祝各位计算机人都可以在就业季里,顺利通过面试,找到钱多事少离家近
的满意工作
面试题系列
博客导航:
- JavaSE 八股文 面试宝典
⇦当前位置
- MySQL 八股文 面试宝典
- Spring、MyBatis、SpringMVC 八股文 面试宝典
- SpringBoot框架 八股文 面试宝典
未完待续 ...
- 如果有一天你 厌倦上班、厌倦996 ,想要
考研备考
的话:408 全套初复试笔记汇总 传送门
后续随着自己的学习,也会陆续补充新的面试题,如果对大家起到帮助的话,
求赞 、求收藏、求关注!
1、纯面向对象语言,能够直接反应现实生活中的对象
2、平台无关性。
Java利用Java虚拟机运行字节码,无论在哪个操作系统上对java程序进行编译,编译后的程序可在其他平台上运行
3、解释性语言。编译器把java代码编译成平台无关的之间代码,然后在JVM上解释运行,具有很好的可移植性
4、安全性和健壮性。Java提供了异常处理和垃圾回收机制,去除了C++中难以理解的指针特性
5、Java提供了很多内置类库(多线程、网络通信)还有对WEB应用开发的支持
面向对象编程 是利用 类和对象 编程一种思想。
万物可归类 ,类是对于世界事物的高度抽象,不同的事物之间有不同的关系:一个类自身与外界的封装关系,一个父类和子类的继承关系,一个类和多个类的多态关系……
万物皆对象 ,对象是具体的世界事物,面向对象的三大特征:封装、继承、多态
封装说明一个类行为和属性与其他类的关系,低耦合,高内聚;
继承是父类和子类层次的关系;
多态是类和类层次的关系;
封装
将客观对象抽象成类,封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据
对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法
属性的封装:使用者只能通过事先定制好的方法来访问数据,可以方便地加入逻辑控制,限制对属性的 不合理操作;
方法的封装:使用者按照既定的方式调用方法,不必关心方法的内部实现,便于使用; 便于修改,增强代码的可维护性;
继承
继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的功能使之更适合特殊的需求
在本质上是特殊到一般的关系,即常说的is-a关系。
子类继承父类,表明子类是一种特殊的父类,并且具有父类所不具有的一些属性或方法。从多种实现类中抽象出一个基类,使其
具备多种实现类的共同特性,当实现类用extends关键字继承了基类(父类)后,实现类就具备了这些相同的属性。父类中通过
private定义的变量和方法不会被继承,不能在子类中直接操作父类通过private定义的变量以及方法。
多态
相比于封装和继承,Java多态是三大特性中比较难的一个,封装和继承最后归结于多态
多态指的是类和类的关系,两个类由于继承关系,存在有方法的重写,故而可以在调用时有父类引用指向子类对象
多态必备三个要素:继承,重写,父类引用指向子类对象。
面向过程: 是分析解决问题的步骤,然后用函数把这些步骤—步一步地实现,然后在使用的时候——调用则可。性能较高,所以
单片机、嵌入式开发等—般采用面向过程开发
面向对象: 是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事物在解决
整个问题的过程中所发生的行为。面向对象有封装、继承、多态的特性,所以易维护、易复用、易扩展。可以设计出低耦合的系
统。但是性能上来说,比面向过程要低。
JDK:Java开发工具包(Java Development Kit),提供了Java的开发环境和运行环境。
JRE:Java运行环境(Java Runtime Environment),提供了Java运行所需的环境。
JDK中包含JRE,JDK中有一个jre的目录,里面包含两个文件夹bin和lib,bin就是JVM,lib就是JVM工作所需要的类库.
如果只运行Java程序,安装JRE即可。要编写Java程序需安装JDK.
字节序是指多字节数据在计算机内存中存储或网络传输时个字节的存储顺序
通常由小端和大端两组方式。
- 小端:低位字节存放在内存的低地址端,高位字节存放在内存的高地址端。
- 大端:高位字节存放在内存的低地址端,低位字节存放在内存的高地址端。
Java语言的字节序是大端
Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。
Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这
个变为可能,因为它知道底层硬件平台的指令长度和其他特性。
1、父类静态代码块(只执行一次)
2、子类静态代码块(只执行一次)
3、父类构造代码块
4、父类构造函数
5、子类构造代码块
6、子类构造函数
7、普通代码块
先后顺序:静态成员变量、成员变量、构造方法。
详细的先后顺序:
父类静态变量、父类静态代码块、子类静态变量、子类静态代码块、父类非静态变量、父类非静态代码块、父类构造函数、子类
非静态变量、子类非静态代码块、子类构造函数。
类内部 | 本包 | 子类 | 外部包 | |
---|---|---|---|---|
public | ✔ | ✔ | ✔ | ✔ |
protected | ✔ | ✔ | ✔ | × |
default | ✔ | ✔ | × | × |
private | ✔ | × | × | × |
(1)接口
1.接口使用interface修饰;
2.接口不能实例化;
3.类可以实现多个接口;
① Java8之前,接口的方法都是抽象方法,省略了public abstract;
② Java8之后,接口可以定义静态方法,静态方法必须有方法体,普通方法没有方法体,需要被实现;
(2)抽象类
1.抽象类使用abstract修饰;
2.抽象类不能被实例化;
3.抽象类只能单继承;
4.抽象类中包含抽象方法和非抽象方法,非抽象方法需要有方法体;
如果一个类继承了抽象类:
① 如果实现了所有的抽象方法,子类可以不是抽象类;
② 如果没有实现所有的抽象方法,子类仍然是抽象类;
接口和抽象类都遵循”面向接口而不是实现编码”设计原则,它可以增加代码的灵活性,可以适应不断变化的需求。
下面有几个点可以帮助你回答这个问题:在 Java 中,你只能继承一个类,但可以实现多个接口。所以一旦你继承了一个类,你就
失去了继承其他类的机会了。
接口通常被用来表示附属描述或行为如: Runnable 、 Clonable 、 Serializable 等等,因此当你使用抽象类来表示行为时,你的类
就不能同时是 Runnable 和 Clonable( 注:这里的意思是指如果把 Runnable 等实现为抽象类的情况 ) ,因为在 Java 中你不能继承
两个类,但当你使用接口时,你的类就可以同时拥有多个不同的行为。
在一些对时间要求比较高的应用中,倾向于使用抽象类,它会比接口稍快一点。如果希望把一系列行为都规范在类继承层次内,并
且可以更好地在同一个地方进行编码,那么抽象类是一个更好的选择。有时,接口和抽象类可以一起使用,接口中定义函数,而在
抽象类中定义默认的实现。
1.抽象不能被实例化;
2.抽象类可以有抽象方法,只需声明,无需实现;
3.有抽象方法的类一定是抽象类;
4.抽象类的子类必须实现抽象类的所有抽象方法,不然子类还是抽象类;
5.抽象方法不能声明静态、不能被static、final修饰;
1、为了程序的结构能够更加清晰从而便于维护。假设Java语言支持多重继承,类C继承自类A和类B,如果类A和B都有自定义的成
员方法f(),那么当代码中调用类C的f()会产生二义性。
Java语言通过实现多个接口间接支持多重继承,接口由于只包含方法定义,不能有方法的实现,类C继承接口A与接口B时即使
它们都有方法f(),也不能直接调用方法,需实现具体的f()方法才能调用,不会产生二义性。
2、多重继承会使类型转换、构造方法的调用顺序变得复杂,会影响到性能。
Java提供了两种用于多态的机制,分为编译时多态和运行时多态,分别是重载与覆盖。
编译时多态主要指方法的重载,即通过参数列表的不同来区分不同的方法。
运行时多态主要指继承父类和实现接口时,可使用父类引用指向子类对象。
- 重载:重载是指同一个类中有多个同名的方法,但这些方法有不同的参数,在编译期间就可以确定调用哪个方法。
- 覆盖:覆盖是指派生类重写基类的方法,使用基类指向其子类的实例对象,或接口的引用变量指向
其实现类的实例对象,在程序调用的运行期根据引用变量所指的具体实例对象调用正在运行的那个
对象的方法,即需要到运行期才能确定调用哪个方法。
- 覆盖是父类与子类之间的关系,是垂直关系;重载是同一类中方法之间的关系,是水平关系。
- 覆盖只能由一个方法或一对方法产生关系;重载是多个方法之间的关系。
- 覆盖要求参数列表相同;重载要求参数列表不同。
- 覆盖中,调用方法体是根据对象的类型来决定的,而重载是根据调用时实参表与形参表来对应选择
方法体。- 重载方法可以改变返回值的类型,覆盖方法不能改变返回值的类型。
1.重载是多态的集中体现,在类中,要以统一的方式处理不同类型数据的时候,可以使用重载;
2.重写的使用是建立在继承关系上的,子类在继承父类的基础上,增加新的功能,可以用重写;
3.简单总结:
重载是多样性,重写是增强剂;
目的是提高程序的多样性和健壮性,以适配不同场景使用时,使用重载进行扩展;
目的的是在不修改原方法及源代码的基础上对方法进行扩展或增强时,使用重写;
4.在里氏代换原则中,子类对父类的方法尽量不要重写和重载。(可以采用final的手段强制来遵守)
设计模式
cgilb实现动态代理,核心原理就是方法的重写;
详细解答:
Java的重载(overload) 最重要的应用场景就是构造器的重载,构造器重载后,提供多种形参形式的构造器,可以应对不同的业务需
求,加强程序的健壮性和可扩展性,比如我们最近学习的Spring源码中的ClassPathXmlApplicationContext,它的构造函数使用重
载一共提供了10个构造函数,这样就为业务的选择提供了多选择性。在应用到方法中时,主要是为了增强方法的健壮性和可扩展
性,比如我们在开发中常用的各种工具类,比如我目前工作中的短信工具类SMSUtil, 发短信的方法就会使用重载,针对不同业务场
景下的不同形参,提供短信发送方法,这样提高了工具类的扩展性和健壮性。
总结:重载必须要修改方法(构造器)的形参列表,可以修改方法的返回值类型,也可以修改方法的异常信息即访问权限;
使用范围是在同一个类中,目的是对方法(构造器)进行功能扩展,以应对多业务场景的不同使用需求。提高程序的健壮性和扩展
性。
java的重写(override) 只要用于子类对父类方法的扩展或修改,但是在我们开发中,为了避免程序混乱,重写一般都是为了方法的扩展,比如在cglib方式实现的动态代理中,代理类就是继承了目标类,对目标类的方法进行重写,同时在方法前后进行切面织入。
方法重写时,参数列表、返回值的类型不能修改,异常可以减少或者删除,但是不能抛出新的异常或者更广的异常;
方法的权限可以降低限制,但是不能做更严格的限制;
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
// false ,在 JavaSE规范 中对 instanceof 运算符的规定就是:如果 obj 为 null,那么将返回false。
System.out.println(null instanceof Object);
(1)用来修饰一个引用
1.如果引用是基本数据类型,则该引用为常量,在编译阶段会存入常量池,该值无法被修改;
2.如果引用是引用数据类型,比如对象、数组,则该对象、数组本身内容可以修改,但指向该对象或数组的地址的引用不能修改;
3.如果引用时类的成员变量,则必须当场赋值,否则编译会报错;
(2)用来修饰一个方法
1.当使用final修饰方法时,这个方法将成为最终方法,无法被子类重写;
2.但该方法仍然可以被继承;
3.JVM会尝试将其内联,以提高运行效率
(3)用来修饰一个类
1.当用final修饰一个类时,该类成为最终类,无法被继承;
比如:String类就是最终类,看JDK源码是被final修饰的;
除此之外,编译器对fianl域要遵守的两个重排规则:
在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序
初次读一个包含final域的对象的引用,与随后初次读这个final域,,两个操作之间不能重排序
static的主要作用有两个:
- 为某种特定数据类型或对象分配与创建对象个数无关的单一的存储空间。
- 使得某个方法或属性与类而不是对象关联在一起,即在不创建对象的情况下可通过类直接调用方法
或使用类的属性。具体而言static又可分为4种使用方式:
- 修饰成员变量。用static关键字修饰的静态变量在内存中只有一个副本。只要静态变量所在的类被加
载,这个静态变量就会被分配空间,可以使用’‘类.静态变量’‘和’‘对象.静态变量’'的方法使用。- 修饰成员方法。static修饰的方法无需创建对象就可以被调用。static方法中不能使用this和super关
键字,不能调用非static方法,只能访问所属类的静态成员变量和静态成员方法。- 修饰代码块。JVM在加载类的时候会执行static代码块。static代码块常用于初始化静态变量。static
代码块只会被执行一次。- 修饰内部类。static内部类可以不依赖外部类实例对象而被实例化。静态内部类不能与外部类有相同
的名字,不能访问普通成员变量,只能访问外部类中的静态成员和静态成员方法。
补充:
Java中是否可以覆盖(override)一个private或者是static的方法?
Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何
实例都不相关,所以概念上不适用。
Java中也不可以覆盖private的方法,因为private修饰的变量和方法只能在当前类中使用,如果是其他的类继承当前类是不能访问
到private变量或方法的,当然也不能覆盖。
1.静态变量属于类本身,在类加载的时候就会分配内存,可以通过类名直接访问;
2.非静态变量属于类的对象,只有在类的对象产生时,才会分配内存,通过类的实例去访问;
3.静态方法也属于类本身,但此时没有类的实例,内存中没有非静态变量,所以无法调用;
(1)强引用
Java中默认声明的就是强引用;
Object obj = new Object(); obj = null;
只要强引用存在,垃圾回收器将永远不会回收被引用的对象(内存不足也不会回收)。如果想被回收,可以将对象置为null;
(2)软引用
在内存足够的时候,软引用不会被回收。只有在内存不足时,系统才会回收软引用对象;
如果回收了软引用对象之后,仍然没有足够的内存,才会抛出内存溢出异常;
使用方式:
// 注意:wrf这个引用也是强引用,它是指向SoftReference这个对象的, // 这里的软引用指的是指向new String("str")的引用,也就是SoftReference类中T SoftReference<String> wrf = new SoftReference<String>(new String("str"));
使用场景:
创建缓存的时候,创建的对象放进缓存中,当内存不足时,JVM就会回收早先创建的对象
(3)弱引用
进行垃圾回收时,只要垃圾回收器发现它,弱引用就会被回收
使用方式:
WeakReference<String> wrf = new WeakReference<String>(str);
使用场景:
Java源码中的
java.util.WeakHashMap
中的key就是使用弱引用,我的理解就是,一旦我不需要某个引用,JVM会自动帮我处理它,这样我就不需要做其他操作。
(4)虚引用
虚引用的回收机制跟弱引用差不多,但是它被回收之前,会被放入
ReferenceQueue
中。注意哦,其它引用是被JIVM回收后才被传入
ReferenceQueue
中的。由于这个机制,所以虚引用大多被用于引用销毁前的处理工作。还有就是,虚引用创建的时候,必须带有
ReferenceQueue
,使用例子:
PhantomReference<String> prf = new PhantomReference<String>(new String("str"), new ReferenceQueue<>());
可用场景:
对象销毁前的一些操作,比如说资源释放等。
object.finalize()
虽然也可以做这类动作,但是这个方式即不安全又低效上诉所说的几类引用,都是指对象本身的引用,而不是指
Reference的
四个子类的引用(SoftReference
等)。
(5)引用队列
引用队列可以与软引用、弱引用、虚引用一起配合使用;
当垃圾回收机制准备回收一个对象时,如果发现它还有引用时,就会在回收对象之前,把这个引用加入到引用队列中。
程序可以通过判断引用队列中是否加入了该引用,来判断被引用的对象是否将要被垃圾回收,这样可以在对象被回收之前一些必要
的措施。
- hashCode:通过对象计算出的散列码。用于map型或equals方法。
需要保证同一个对象多次调用该方法,总返回相同的整型值。- equals:判断两个对象是否一致。需保证equals方法相同对应的对象hashCode也相同。
- toString: 用字符串表示该对象
- clone:深拷贝一个对象
1.对于基本类型,== 比较的是值;
2.对于引用类型,== 比较的是地址;
3.equals 不能比较基本数据类型;
4.如果没有重写equals,equals 就相对于 ==;
5.如果重写了equals方法,equals比较的是对象的内容;
牛客网面试题 ①
Java中的Object对象里的一个方法:
public native int hashcode();
(1) hashcode()方法的使用
hashcode()方法主要配合基于散列的集合一起使用,比如HashSet、HashMap、HashTable。
当集合需要添加对象时,先调用这个对象的hashcode()方法,得到对应的hashcode值,实际上hashcode中会有一个table保存以及
存进去的对象的hashcode值。如果table中没有该hashcode值,则直接存入。
如果有,就调用equals方法与新元素进行比较,相同就不存了,不同就存入。
(2)equals和hashcode的关系
1.如果equals为true,hashcode一定相等;
2.如果equals为false,hashcode不一定不相等;
3.如果hashcode值相等,equals不一定相等;
4.如果hashcode值不等,equals一定不等;
(3)重写equals方法时,一定要重写hashcode方法
(4)百度百科
hashcode方法返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。
hashCode 的常规协定是:
在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。
以下情况不是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)
当equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等
的哈希码。
(5)小白解释
1.hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有
例如内存中有这样的位置
0 1 2 3 4 5 6 7
而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。
但如果用hashcode那就会使效率提高很多。
我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,9除8的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除 8求余数直接找到存放的位置了。
2.但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),例如9除以8和17除以8的余数都是1,那么这是不是合法的,回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。
也就是说,我们先通过 hashcode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。
那么。重写了equals(),为什么还要重写hashCode()呢?
想想,你要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到桶,光重写equals()有什么用啊。
有可能。在产生hash冲突时,两个不相等的对象就会有相同的hashcode值当hash冲突产生时,般有以下几种方式来处理:
**拉链法:**每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节
点可以用这个单向链表进行存储.
**开放定址法:**一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入
**再哈希:**又叫双哈希法,有多个不同的Hash函数.当发生冲突时,使用第二个,第三个…等哈希函数计算地址,直到无冲突
(1)为什么要使用克隆?
想对一个对象进行复制,又想保留原有的对象进行接下来的操作,这个时候就需要克隆了
(2)如何实现对象克隆?
1.实现Cloneable接口,重写clone方法;
2.实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深克隆;
3.BeanUtils,apache和Spring都提供了bean工具,只是这都是浅克隆;
(3)深克隆和浅克隆区别是什么?
浅克隆:仅仅克隆基本类似变量,不克隆引用类型变量;
深克隆:即克隆基本类型变量,又克隆引用类型变量;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fIMvpovs-1678417682138)(01 JavaSE八股文.assets/image-20220428102117822.png)]
(4)代码实例
// 后续在补充
1.JavaBean 类必须是一个公共类,并将其访问属性设置为 public
2.JavaBean 类必须有一个空的构造函数:类中必须有一个不带参数的公用构造器,此构造器也应该通过调用各个特性的设置方法
来设置特性的缺省值。
3.一个javaBean类不应有公共实例变量,类变量都为private
4.持有值应该通过一组存取方法(getXxx 和 setXxx)来访问:对于每个特性,应该有一个带匹配公用 getter 和 setter 方法的专用
实例变量。
属性为布尔类型,可以使用 isXxx() 方法代替 getXxx() 方法。
通常属性名是要和 包名、类名、方法名、字段名、常量名作出区别的:
public class User {
private String busName;
private String pCount;
private Boolean isRunning;
//正确的命名方式,驼峰式的
public String getBusName() {
return busName;
}
public void setBusName(String busName) {
this.busName = busName;
}
// 这是什么?
public String getpCount() {
return pCount;
}
public void setpCount(String pCount) {
this.pCount = pCount;
}
// 这个也是不允许的
public Boolean getIsRunning() {
return isRunning;
}
public void setIsRunning(Boolean isRunning) {
this.isRunning = isRunning;
}
}
1.JavaBean属性命名尽量使用常规的驼峰命名规则;
2.属性名第一个单词尽量避免只使用一个字母:eBook、eName;
3.boolean属性名避免使用”is“开头的名称;
4.随着JDK,Eclipse,Spring等软件版本的不断提高,低版本的出现的问题可能在高版本中解决了,低版本原本正常的代码可能在
高版本环境下不再支持。
标识符的含义:
是指在程序中,我们自己定义的内容,譬如,类的名字,方法名称以及变量名称等等,都是标识符。
命名规则:(硬性要求)
标识符可以包含英文字母,O-9的数字,$以及_
标识符不能以数字开头
标识符不是关键字
命名规范:(非硬性要求)
类名规范:首字符大写,后面每个单词首字母大写(大驼峰式)。
变量名规范:首字母小写,后面每个单词首字母大写(小驼峰式)。
方法名规范:同变量名。
当新对象被创建的时候,构造方法会被调用。每一个类都有构造方法。在程序员没有给类提供构造方法的情况下,Java编译器会
为这个类创建一个默认的构造方法。
Java中构造方法重载和方法重载很相似。可以为一个类创建多个构造方法。每一个构造方法必须有它自己唯一的参数列表。
Java不支持像C++中那样的复制构造方法,这个不同点是因为如果你不自己写构造方法的情况下,Java不会创建默认的复制构造
方法。
值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.
引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。
一般认为,java内的基础类型数据传递都是值传递. java中实例对象的传递是引用传递
Java提供了只包含一个compareTo()方法的Comparable接口。这个方法可以个给两个对象排序。具体来说,它返回负数,0,正
数来表明已经存在的对象小于,等于,大于输入对象。
Java提供了包含compare()和equals()两个方法的Comparator接口。compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。equals()方法需要一个对象作为参数,它用来决定输入参数是否和comparator
相等。只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时候,这个方法才返回true。
byte: 占用1个字节,取值范围-128 ~ 127
short: 占用2个字节,取值范围-215 ~ 215-1
int:占用4个字节,取值范围-231 ~ 231-1
long:占用8个字节
float:占用4个字节
double:占用8个字节
char: 占用2个字节
boolean:占用大小根据实现虚拟机不同有所差异
基本类型 | 大小(字节) | 默认值 | 封装类 |
---|---|---|---|
byte | 1 | (byte)0 | Byte |
short | 2 | (short)0 | Short |
int | 4 | 0 | Integer |
long | 8 | 0L | Long |
float | 4 | o.of | Float |
double | 8 | o.od | Double |
boolean | - | flase | Boolean |
char | 2 | \u000(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个字节。使用in的原因是,对于当下32位的处理器
(CPU)来说,一次处理数据是32位(这里不是指的是32/64位系统,而是指CPU硬件层面),具有高效存取的特点。
对于Java基本数据类型,均对应一个包装类。
装箱就是自动将基本数据类型转换为包装器类型,如int->Integer,调用方法:
Integer
的valueOf(int)
方法拆箱就是自动将包装器类型转换为基本数据类型,如Integer->int,调用方法:
Integer
的intValue
方法
①、在Java SE5之前,如果要生成一个数值为10的Integer
对象,必须这样进行:
Integer i = new Integer(10);
而在从Java SE5开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要这样就可以了:
Integer i = 10;
② 面试题
下面的代码会输出什么?
public class Main {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}
// 运行结果
true
false
为什么会出现上述这样的结果呢?
输出结果表面i1和i2指向的是同一个对象,而i3和i4指向的不是同一个对象。
此时只需要看一下源码便知究竟,下面是Integer的valueOf(int)方法的具体实现:
public static Integer valueOf(int i) { if(i >= -128 && i <= IntegerCache.high) return IntegerCache.cache[i + 128]; else return new Integer(i); }
其中IntegerCache的实现为:
private static class IntegerCache { static final int high; static final Integer cache[]; static { final int low = -128; // high value may be configured by property int h = 127; if (integerCacheHighPropValue != null) { // Use Long.decode here to avoid invoking methods that // require Integer's autoboxing cache to be initialized int i = Long.decode(integerCacheHighPropValue).intValue(); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - -low); } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); } private IntegerCache() {} }
从这2段代码可以看出,在通过valueof方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向
IntegerCache.cache 中已经存在的对象的引用;否则创建—个新的Integer对象。
上面的代码中i⑴和i2的数值为100,因此会直接从cache中取已经存在的对象,所以1和i2指向的是同一个对象,而i3和i4则是分
别指向不同的对象。
面试题2:以下代码输出什么?
public class Main {
public static void main(String[] args) {
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}
// 输出结果
false
false
原因:在某个范围内的整形数值的个数是有限的,而浮点数却不是
牛客网关于自动装箱的面试题
byte的范围是-128~127。
字节长度为8位,最左边的是符号位,而127的二进制为01111111,所以执行+1操作时,01111111变为10000000。
大家知道,计算机中存储负数,存的是补码的兴衰。左边第一位为符号位。
那么负数的补码转换成十进制如下:
一个数如果为正,则它的原码、反码、补码相同;一个正数的补码,将其转化为十进制,可以直接转换。
已知一个负数的补码,将其转换为十进制数,步骤如下:
先对各位取反;
将其转换为十进制数;
加上负号,再减去1;
例如10000000,最高位是1,是负数,①对各位取反得01111111,转换为十进制就是127,加上负号得-127,再减去1得-128;
false,因为浮点数不能完全精确的表示出来
+=操作符会进行隐式自动类型转换,此处a+=b隐式的将加操作的结果类型强制转换为持有结果的类型
a=a+b则不会自动进行类型转换
byte a = 127; byte b = 127; b = a + b; // 报编译错误:cannot convert from int to byte b += a;
以下代码是否有错,有的话怎么改?
short s1= 1;
s1 = s1 + 1;
// 有错误. short类型在进行运算时会自动提升为int类型,也就是说s1+1的运算结果是int类型,而s1是short类型此时编译器会报错.
正确写法:
short s1= 1;
s1 += 1;
// +=操作符会对右边的表达式结果强转匹配左边的数据类型,所以没错
答:
1.不属于;
2.八大基本数据类型:byte、short、char、int、long、float、double、boolean
1.String str = “i”; 会将其分配到常量池中,常量池中没有重复的元素。如果常量池中存在i,就将i的地址赋给变量,如果没有就创建
一个再赋值给变量;
2.String str = new String(“i”); 会将对象分配给堆中,即使内存一样,还是会创建一个新的对象;
Java intern函数详解
对象1:new StringBuilder()
对象2:new String(“a”)
对象3:常量池中的“a”
对象4:new String(“b”)
对象5:常量池中的“b”
深入刨析StringBuilder中的toString():
对象6:new String(“ab”)
强调一下,toString的调用,在字符串常量池中,没有生成“ab"
附加题
String s1 = new String("1") + new String("1"); // s1变量记录的地址为:new String
s1.intern(); // 在字符串常量池中生成"11"。
// 如何理解:
// jdk6:创建了一个新的对象"11",也就有新的地址;
// jdk7:此时常量池中并没有创建"11",而是创建了一个指向堆空间中new String("11")的地址;
String s2 = "11";
System.out.println(s1 == s2); // jdk6:false; jdk7:true
将对象封装到StringBuilder中,调用reverse方法反转;
package com.clz.java;
public class ReverseString {
public static void main(String[] args) {
String str = "Hello World!";
StringBuilder stringBuilder = new StringBuilder(str);
String ret = stringBuilder.reverse().toString();
System.out.println(ret);
}
}
(1)String
String
是只读字符串,它并不是基本数据类型,而是一个对象。从底层源码来看是一个final类型
的字符数组,所引用的字符串不能被改变,一经定义,无法在增删改。
private final char value[];
如果对String类型对象修改,需要新建对象,将老字符和新增加的字符一并存进去。
每次
+
操作,隐形在堆山newl一个跟原字符串相同的StringBuilder
对象,在调用append
方法,拼接字符串。
StringBuilder
和StringBuffer
它们两都继承了AbstractStringBuilder
抽象类:
/**
* The value is used for character storage.
*/
char[] value; // 可变数组
(2)StringBuilder
StringBuilder,采用无final修饰的字符数组进行保存,因此可变
线程不安全,效率高,多用于单线程
(3)StringBuffer
StringBuffer,采用无final修饰的字符数组进行保存,可理解为实现线程安全的StringBuilder。
线程安全,由于加锁的原因,效率不如StringBuilder,多用于多线程;
不频繁的字符串操作使用String,操作频繁的情况不建议使用String
StringBuilder > StringBuffer > String
- 节省空间:字符串常量存储在JVM的字符串池中可以被用户共享。
- 提高效率:String会被不同线程共享,是线程安全的。在涉及多线程操作中不需要同步操作。
- 安全:String常被用于用户名、密码、文件名等使用,由于其不可变,可避免黑客行为对其恶意修改。
成员内部类:作为成员对象的内部类。可以访问private及以上外部类的属性和方法。外部类想要访问内部类属性或方法时,必须要
创建一个内部类对象,然后通过该对象访问内部类的属性或方法。外部类也可访问private修饰的内部类属性。
局部内部类:存在于方法中的内部类。访问权限类似局部变量,只能访问外部类的final变量。
匿名内部类:只能使用一次,没有类名,只能访问外部类的final变量。
静态内部类:类似类的静态成员变量。
Java异常分为Error(程序无法处理的错误),Exception(程序本身可以处理的异常),这两个类均继承Throwable
Error常见的有
StackOverFlowError、OutOfMemoryError
等; Exception可分为 运行时异常 和 非运行时异常 ;
对于运行时异常,可以利用 try~catch 的方式进行处理,也可以不处理;
对于编译时异常,必须处理,不处理的话程序无法通过编译;
作用位置:
throw在方法体内部,throws在方法签名处、声明处;
作用对象:
throw抛出具体异常对象,throws声明可能抛出异常的异常类型;
作用效果:
throw每次只能抛出一个异常对象(源头,制造异常),throws代表该方法可能会抛出的异常列表(提醒调用者);
final 用于声明属性、方法和类,分别表示属性不可变、方法不可覆盖、类不可继承
finally 用于抛异常,finally代码块内语句无论是否发生异常,都会在执行finally,常用于一些流的关闭。
finalize方法用于垃圾回收。
一般情况下不需要我们实现 finalize,当对象被回收的时候需要释放一些资源,比如socket链接,在对象初始化时创建,整个生命周期内有效,那么需要实现 finalize 方法,关闭这个链接。
但是当调用finalize方法后,并不意味着gc会立即回收该对象,所以有可能真正调用的时候,对象又不需要回收了,然后到了真正要回收的时候,因为之前调用过一次,这次又不会调用了,产生问题。所以,不推荐使用finalize方法。
会执行,并且finally的执行早于try里面return
结论:
- ① 不管有木有出现异常,finally块中代码都会执行;
- ② 当 try 和 catch 中有 return 时,finally 仍然会执行;
- ③ finally 是在 return 后面的表达式运算后执行的 (此时并没有返回运算后的值,而是先把要返回的值保存起来,管
finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在 finally 执行前确定的;
- ④ finally 中最好不要包含 return,否则程序会提前退出,返回值不是 try 或 catch 中保存的返回值;
当遇到下面情况不会执行。
① 当程序在进入try语句块之前就出现异常时会直接结束。
② 当程序在try块中强制退出时,如使用
System.exit(0)
,也不会执行finally块中的代码。 其它情况下,在
try/catch/finally
语句执行的时候,try块先执行,
当有异常发生,catch和finally进行处理后程序就结束了,
当没有异常发生,在执行完finally中的代码后,后面代码会继续执行。
值得注意的是,当try/catch语句块中有return时,finally语句块中的代码会在return之前执行。
如果try/catch/finally块中都有return语句,finally块中的return语句会覆盖try/catch模块中的return语句。
1.NullPointerException:空指针异常;
2.SQLException:数据库相关的异常;
3.IndexOutOfBoundsException:数组下角标越界异常;
4.FileNotFoundException:打开文件失败时抛出;
5.IOException:当发生某种IO异常时抛出;
6.ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出此异常;
7.NoSuchMethodException:无法找到某一方法时,抛出;
8.ArrayStoreException:试图将错误类型的对象存储到一个对象数组时抛出的异常;
9.NumberFormatException:当试图将字符串转换成数字时,失败了,抛出;
10.IllegalArgumentException 抛出的异常表明向方法传递了一个不合法或不正确的参数。
11.ArithmeticException当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。
Java虽然提供了丰富的异常处理类,但是在项目中还会经常使用自定义异常,其主要原因是Java提供的异常类在某些情况下还
是不能满足实际需球。
例如以下情况:
① 系统中有些错误是符合Java语法,但不符合业务逻辑。
② 在分层的软件结构中,通常是在表现层统一对系统其他层次的异常进行捕获处理。
泛型,即 “参数化类型”,解决不确定对象具体类型的问题,在编译阶段有效。
在泛型使用过程中,操作的数据类型被指定为一个参数,这个参数类型在类中称为泛型类、接口中称为泛型接口、方法中称为泛
型方法。
泛型是Java SE1.5之后的特性,《Java 核心技术》中对泛型的定义是:
“泛型”意味着编写的代码可以被不同类型的对象所重用
“泛型”,顾名思义,“泛指的类型”。我们提供了泛指的概念,但具体执行的时候却可以有具体的规则来约束,比如我们用的非常多的
ArrayList就是个泛型类,ArrayList作为集合可以存放各种元素,如Iteger、String
,自定义的各种类型等。但在我们使用的使用通
过具体的规则来约束,如我们可以约束集合中只存放Integer类型的元素,如:
List<Integer> iniData = new ArrayList<>();
使用泛型的好处
以集合来举例,使用泛型的好处是我们不必因为添加元素类型的不同而定义不同类型的集合,如整型集合类,浮点型集合类,字符串集合类,我们可以定义一个集合来存放整型、浮点型,字符串型数据,而这并不是最重要的,因为我们只要把底层存储设置了
Object即可,添加的数据全部都可向上转型为Object。更重要的是我们可以通过规则按照自己的想法控制存储的数据类型。
Java编译器生成的字节码是不包含泛型信息的,泛型类型信息将在编译处理是被擦除,这个过程被称为泛型擦除。
Collection继承结构图
Map结构图
1.Collection是最基本的集合接口,Collection派生了两个子接口list和set,分别定义了两种不同的存储方式;
2.Colleections是包装类(集合工具类),它包含各种有关集合操作的静态方法(对集合的搜索、排序、线程安全化等);
此类不能实例化,就像一个工具类,服务于Collection框架。
List是一个有序队列,在JAVA中有两种实现方式:
- ① ArrayList 使用数组实现,是容量可变的非线程安全列表,随机访问快,集合扩容时会创建更大的数组,把原有数组复制到新数组。
- ② LinkedList 本质是双向链表,与 ArrayList 相比插入和删除速度更快,但随机访问元素很慢。
① ArrayList、Vector 和 LinkedList 都是可以 动态改变长度的数组。
② ArrayList和Vector都是基于存储元素的 Object[] array来实现的,
它们会在内存中开辟一块连续的空间来存储,支持下标、索引访问。
但在涉及插入元素时可能需要移动容器中的元素,插入效率较低。
当存储元素超过容器的初始化容量大小,ArrayList与Vector均会进行扩容。
③ LinkedList采用双向列表实现,对数据索引需要从头开始遍历,因此随机访问效率较低,
但在插入元素的时候不需要对数据进行移动,插入效率较高。
④ Vector是线程安全的,其大部分方法是直接或间接同步的。
ArrayList不是线程安全的,其方法不具有同步性质。LinkedList也不是线程安全的。
Set 即集合,该数据结构不允许元素重复且无序。JAVA对Set有三种实现方式:
① HashSet 通过 HashMap 实现,HashMap 的 Key 即 HashSet 存储的元素,Value系统自定义一个名为 PRESENT 的 Object 类型常量。判断元素是否相同时,先比较hashCode,相同后再利用equals比较,查询 O(1)
② LinkedHashSet 继承自 HashSet,通过 LinkedHashMap 实现,使用双向链表维护元素插入顺序。
③ TreeSet 通过 TreeMap 实现的,底层数据结构是红黑树,添加元素到集合时按照比较规则将其插入合适的位置,保证插入后的集合仍然有序。查询 O(logn)
equals和hashCode这两个方法都是从object类中继承过来的,equals主要用于判断对象的内存地址引用是否是同一个地址;
hashCode根据定义的哈希规则将对象的内存地址转换为一个哈希码。
HashSet中存储的元素是不能重复的,主要通过hashCode与equals两个方法来判断存储的对象是否相同:
- 如果两个对象的hashCode值不同,说明两个对象不相同。
- 如果两个对象的hashCode值相同,接着会调用对象的equals方法,如果equlas方法的返回结果为true,那么说明两个对象相同,否则不相同。
TreeMap 是底层利用红黑树实现的Map结构,底层实现是一棵平衡的排序二叉树,由于红黑树的插入、删除、遍历时间复杂度都为O(logN),所以性能上低于哈希表。
但是哈希表无法提供键值对的有序输出,红黑树可以按照键的值的大小有序输出。
JDK8 之前底层实现是数组 + 链表,JDK8 改为数组 + 链表/红黑树。主要成员变量包括存储数据的table 数组、元素数量 size、
加载因子 loadFactor。
HashMap 中数据以键值对的形式存在,键对应的 hash 值用来计算数组下标,如果两个元素 key 的hash 值一样,就会发生哈希
冲突,被放到同一个链表上。
table 数组记录 HashMap 的数据,每个下标对应一条链表,所有哈希冲突的数据都会被存放到同一条链表,Node/Entry 节点包
含四个成员变量:key、value、next 指针和 hash 值。在JDK8后链表超过8会转化为红黑树。
若当前数据/总数据容量>负载因子,Hashmap将执行扩容操作。
默认初始化容量为 16,扩容容量必须是 2 的幂次方、最大容量为 1<< 30 、默认加载因子为 0.75。
1.HashMap是线程不安全的,HashTable是线程安全的;在多线程访问HashMap需要提供额外的同步机制。
2.HashMap是Hashtable的轻量级实现,HashMap中运行键和值为null(最多一个key为null),HashTable不允许;
3.HashTable的默认容量是16,以2的倍数扩容;HashTable是11,以2*n+1扩容;(扩容因子:0.75)
4.Hashtable使用Enumeration进行遍历,HashMap使用Iterator进行遍历。
如果对Map进行插入、删除或定位一个元素的操作更频繁,HashMap是更好的选择。
如果需要对key集合进行有序的遍历,TreeMap是更好的选择。
List(对付顺序的好帮手):List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象
Set(注重独—无二的性质):不允许重复的集合。不会有多个元素引用相同的对象。
Map(用Key来搜索的专家):使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重
复,典型的Key是String类型,但也可以是任何对象。
HashTable: 哈希表的线程安全版,效率低
ConcurrentHashMap:哈希表的线程安全版,效率高,用于替代HashTable
Vector:线程安全版Arraylist
Stack:线程安全版栈
BlockingQueue及其子类:线程安全版队列
在JDK1.7中,HashMap采用头插法插入元素,因此并发情况下会导致环形链表,产生死循环。
虽然JDK1.8采用了尾插法解决了这个问题,但是并发下的put操作也会使前一个key被后一个key覆盖。由于HashMap有扩容机制
存在,也存在A线程进行扩容后,B线程执行get方法出现失误的情况。
克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。
因此,应该由集合类的具体实现来决定如何被克隆或者是序列化。
Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代器实例的迭代方法。
迭代器可以在迭代的过程中删除底层集合的元素,但是不可以直接调用集合的remove(Object Obj)删除,可以通过迭代器的remove()方法删除。
① fail-fast 直接在容器上进行,在遍历过程中,一旦发现容器中的数据被修改,
就会立刻抛出ConcurrentModificationException异常从而导致遍历失败。
常见的使用fail-fast方式的容器有
HashMap
和ArrayList
等。 ② fail-safe 这种遍历基于容器的一个克隆。因此对容器中的内容修改不影响遍历。
常见的使用fail-safe方式遍历的容器有
ConcurrentHashMap
和CopyOnWriteArrayList
。
PriorityQueue是一个基于优先级堆的无界队列,它的元素是按照自然顺序(natural order)排序的。
在创建的时候,我们可以给它提供一个负责给元素排序的比较器。
PriorityQueue不允许null值,因为他们没有自然顺序,或者说他们没有任何的相关联的比较器。
最后,PriorityQueue不是线程安全的,入队和出队的时间复杂度是O(log(n))。
1、按流划分:输入流和输出流;
2、按操作单位划分:字节流和字符流;
字节流:inputStream、outStream;
字符流:reader、writer;
3、按流的角色划分:节点流和处理流
Java l0流的40多个类都是从如下4个抽象类基类中派生出来的:
InputStream/Reader
:所有的输入流的基类,前者是字节输入流,后者是字符输入流。outputStream/writer
:所有输出流的基类,前者是字节输出流,后者是字符输出流。
序列化是一种将对象转换成字节序列的过程,用于解决在对对象流进行读写操作时所引发的问题。
序列化可以将对象的状态写在流里进行网络传输,或者保存到文件、数据库等系统里,并在需要的时候把该流读取出来重新构造
成一个相同的对象。
序列化是通过实现Serializable接口,该接口没有需要实现的方法,implement Serialiable只是为了标注该对象是可被序列化的,
使用一个输出流(FileOutStream)来构造一个ObjectOutputStream对象,接着使用ObjectOutputStream对象的writeObject方法
就可以将参数的obj对象输出到磁盘,需要恢复的实话使用输入流。
序列化是将对象转换为容易传输的格式的过程,最主要的目的就是传递和保存对象,保存对象的完整性和可传递性
序列化:将java对象转化为字节序列,由此可以通过网络对象进行传输。
反序列化:将字节序列转化为java对象。
具体实现:实现 ==Serializable接口,==或实现 Externalizable接口 中的
writeExternal()
与readExternal()
方法。
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。
与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。
系统运行一个程序即是一个进程从创建,运行到消亡的过程。
简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如:CPU时间、内存空间、文件、输入输出设备的使用权等等。、
换句话说,当程序在执行时,将会被操作系统载入内存中。线程是进程划分成的更小的运行单位。
线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。
从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫做轻量级进程。
线程与进程的区别归纳:
- ① 地址空间和其它资源 :进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
- ② 通信 :进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信,需要进程同步和互斥手段的辅助,以保证数据的一致性。
- ③ 调度和切换 :线程上下文切换比进程上下文切换要快得多。
- ④ 在多线程OS中,进程不是一个可执行的实体。
新建(new) :新创建了一个线程对象。
可运行(runnable) :线程对象创建后,其他线程 (比如main线程) 调用了该对象的
start()
方法。 该状态的线程位于可运行线程中,等待被线程调度选中,获取cpu的使用权。
运行(running) :可运行状态(runnable)的线程获得了cpu时间片 (timeslice),执行程序代码。
阻塞(block) :阻塞状态是指线程因为某种原因放弃了cpu使用权,也即让出了cpu timeslice,暂时停止运行。
直到线程进入可运行( runnable)状态,才有机会再次获得cpu timeslice转到运行(running )状态。
阻塞的情况分三种:
① 等待阻塞:运行( running )的线程执行
o.wait()
方法,JVM会把该线程放入等待队列(waitting queue)中。 ② 同步阻塞:运行( running )的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lockpool)中。
③ 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)
或t.join()
方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时join()等待线程终止或者超时、或者/O处理完毕时,线程重新转入可运行(runnable)状态。 死亡(dead) :线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
反射机制使得Java具有 动态获取程序信息和动态调用对象方法 的能力。可以通过以下类调用反射API:
Class类:获得类属性方法
Field类:获得类的成员变量
Method类:获取类的方法信息
Construct类:获取类的构造方法等信息
在程序的运行过程中
- 构造任意一个类的对象、
- 获取任意一个类的成员变量和成员方法、
- 获取任意一个对象所属的类信息、
- 调用任意一个对象的属性和方法。
Java中对象可以分为 实例对象 和 ==Class对象,==每一个类都有一个Class对象,其包含了与该类有关的信息。
获取Class对象的方法:
- 类名.class
- 实例对象.getClass()
- Class.forName(“类的全限定名”)
- 基本类型的包装类,可以调用包装类的Type属性来获得包装类的Class对象
// 1 new
// 2 clone()
// 3 反射机制
// 用 Class.forName方法获取类,在调用类的newinstance()方法
Class<?> cls = Class.forName("com.dao.User");
User u = (User) cls.newInstance();
// 4 序列化、反序列化
// 将一个对象实例化后,进行序列化,再反序列化,也可以获得一个对象(远程通信的场景下使用)
ObjectOutputStream out = new ObjectOutputStream (new FileOutputStream("D:/data.txt"));
// 序列化对象
out.writeObject(user1);
out.close();
// 反序列化对象
ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:/data.txt"));
User user2 = (User) in.readObject();
System.out.println("反序列化user:" + user2);
in.close();
优点:
- 能够运行时动态获取类的实例,提高灵活性;
- 与动态编译结合;
缺点:
- 使用反射性能较低,需要解析字节码,将内存中的对象进行解析。解决方案:
- ① 通过
setAccessible(true)
关闭JDK的安全检查来提升反射速度;- ② 多次创建一个类的实例时,有缓存会快很多;
- ③
ReflectASM
工具类,通过字节码生成的方式加快反射速度;- 相对不安全,破坏了封装性(因为通过反射可以获得私有方法和属性)
Java 注解用于为 Java 代码提供 元数据 ;
作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的;
其可以用于提供信息给编译器,在编译阶段时给软件提供信息进行相关的处理,在运行时处理写相应代码,做对应操作;
元注解可以理解为 ==注解的注解,==即在注解中使用,实现想要的功能。其具体分为:
@Retention
:表示注解存在阶段是保留在源码,还是在字节码(类加载)或者运行期(JVM中运行)
@Target
:表示注解作用的范围;
@Documented
:将注解中的元素包含到 Javadoc 中去;
@Inherited
:一个被@Inherited注解了的注解修饰了一个父类,如果他的子类没有被其他注解修饰,则它的子类也继承了父类的注解;
@Repeatable
:被这个元注解修饰的注解可以同时作用一个对象多次,但是每次作用注解又可以代表不同的含义;