Java史上最全面试题,精心整理100家互联网企业面经,祝你面试成功。面试必过(2023优化版)已发布在个人微信公众号【面向Offer学编程】,优化版首先修正了读者反馈的部分答案存在的错误,同时根据最新面试总结,删除了低频问题,添加了一些常见面试题,对文章进行了精简优化,欢迎大家关注。
文章目录
- Java概述
- 什么是B/S架构?什么是C/S架构
- 何为编程
- 什么是Java
- jdk1.5之后的三大版本
- 3 Jdk和Jre和JVM的区别
- 什么是跨平台性?原理是什么
- Java语言有哪些特点
- 什么是字节码?采用字节码的最大好处是什么
- 什么是Java程序的主类?应用程序和小程序的主类有何不同?
- Java应用程序与小程序之间有那些差别?
- Java和C++的区别
- Oracle JDK 和 OpenJDK 的对比
- JAVA创建对象的几种方式
- Java中Object类常用方法
- 什么是java序列化,如何实现 java 序列化?或者请解释Serializable接口的作用。
- 为什么有些java类要实现Serializable接口
- 什么是序列化
- 除了实现Serializable接口还有什么序列化方式
- instanceof 关键字的作用
- import java和javax有什么区别?
- 为什么说Java语言“编译与解释并存”?
- 基础语法
- 面向对象
- IO流
- 反射
- 常用API
- String相关
- 字符型常量和字符串常量的区别
- 什么是字符串常量池?
- String 是最基本的数据类型吗
- String有哪些特性
- String为什么是不可变的吗?
- String真的是不可变的吗?
- 是否可以继承 String 类
- String str="i"与 String str=new String(“i”)一样吗?
- String s = new String(“xyz”);创建了几个字符串对象
- 如何将字符串反转?
- String可以被子类继承吗?
- String和byte[]两者之间如何转换?
- 数组有没有 length()方法?String 有没有 length()方法
- String 类的常用方法都有那些?
- 在使用 HashMap 的时候,用 String 做 key 有什么好处?
- String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的
- 如何比较两个字符串?
- Date相关
- 包装类相关
Java概述
什么是B/S架构?什么是C/S架构
- B/S(Browser/Server),浏览器/服务器程序,比如说淘宝网
- C/S(Client/Server),客户端/服务端,桌面应用程序,比如说QQ
何为编程
- 编程就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程。
- 为了使计算机能够理解人的意图,人类就必须要将需解决的问题的思路、方法、和手段通过计算机能够理解的形式告诉计算机,使得计算机能够根据人的指令一步一步去工作,完成某种特定的任务。这种人和计算机之间交流的过程就是编程。
什么是Java
- Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程 。
jdk1.5之后的三大版本
- Java SE(J2SE,Java 2 Platform Standard Edition,标准版)
Java SE 以前称为 J2SE。它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序。Java SE 包含了支持 Java Web 服务开发的类,并为Java EE和Java ME提供基础。
- Java EE(J2EE,Java 2 Platform Enterprise Edition,企业版)
Java EE 以前称为 J2EE。企业版本帮助开发和部署可移植、健壮、可伸缩且安全的服务器端Java 应用程序。Java EE 是在 Java SE 的基础上构建的,它提供 Web 服务、组件模型、管理和通信 API,可以用来实现企业级的面向服务体系结构(service-oriented architecture,SOA)和 Web2.0应用程序。2018年2月,Eclipse 宣布正式将 JavaEE 更名为 JakartaEE
Java ME 以前称为 J2ME。Java ME 为在移动设备和嵌入式设备(比如手机、PDA、电视机顶盒和打印机)上运行的应用程序提供一个健壮且灵活的环境。Java ME 包括灵活的用户界面、健壮的安全模型、许多内置的网络协议以及对可以动态下载的连网和离线应用程序的丰富支持。基于 Java ME 规范的应用程序只需编写一次,就可以用于许多设备,而且可以利用每个设备的本机功能。
3 Jdk和Jre和JVM的区别
看Java官方的图片,Jdk中包括了Jre,Jre中包括了JVM
-
JDK :Jdk还包括了一些Jre之外的东西 ,就是这些东西帮我们编译Java代码的, 还有就是监控Jvm的一些工具 Java Development Kit是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE。所以安装了JDK,就无需再单独安装JRE了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)等
-
JRE :Jre大部分都是 C 和 C++ 语言编写的,他是我们在编译java时所需要的基础的类库 Java Runtime Environment包括Java虚拟机和Java程序所需的核心类库等。核心类库主要是java.lang包:包含了运行Java程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等,系统缺省加载这个包
如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。
-
Jvm:在倒数第二层 由他可以在(最后一层的)各种平台上运行 Java Virtual Machine是Java虚拟机,Java程序需要运行在虚拟机上,不同的平台有自己的虚拟机,因此Java语言可以实现跨平台。
什么是跨平台性?原理是什么
- 所谓跨平台性,是指java语言编写的程序,一次编译后,可以在多个系统平台上运行。
- 实现原理:Java程序是通过java虚拟机在系统平台上运行的,只要该系统可以安装相应的java虚拟机,该系统就可以运行java程序。
Java语言有哪些特点
- 简单易学(Java语言的语法与C语言和C++语言很接近)
- 面向对象(封装,继承,多态)
- 平台无关性(Java虚拟机实现平台无关性)
- 支持网络编程并且很方便(Java语言诞生本身就是为简化网络编程设计的)
- 支持多线程(多线程机制使应用程序在同一时间并行执行多项任)
- 健壮性(Java语言的强类型机制、异常处理、垃圾的自动收集等)
- 安全性好
什么是字节码?采用字节码的最大好处是什么
-
字节码:Java源代码经过虚拟机编译器编译后产生的文件(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。
-
采用字节码的好处:
Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。
-
先看下java中的编译器和解释器:
Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节码(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行,这就是上面提到的Java的特点的编译与解释并存的解释。
Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。
什么是Java程序的主类?应用程序和小程序的主类有何不同?
- 一个程序中可以有多个类,但只能有一个类是主类。在Java应用程序中,这个主类是指包含main()方法的类。而在Java小程序中,这个主类是一个继承自系统类JApplet或Applet的子类。应用程序的主类不一定要求是public类,但小程序的主类要求必须是public类。主类是Java程序执行的入口点。
Java应用程序与小程序之间有那些差别?
- 简单说应用程序是从主线程启动(也就是main()方法)。applet小程序没有main方法,主要是嵌在浏览器页面上运行(调用init()线程或者run()来启动),嵌入浏览器这点跟flash的小游戏类似。
Java和C++的区别
我知道很多人没学过C++,但是面试官就是没事喜欢拿咱们Java和C++比呀!没办法!!!就算没学过C++,也要记下来!
- 都是面向对象的语言,都支持封装、继承和多态
- Java不提供指针来直接访问内存,程序内存更加安全
- Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。
- Java有自动内存管理机制,不需要程序员手动释放无用内存
Oracle JDK 和 OpenJDK 的对比
- Oracle JDK版本将每三年发布一次,而OpenJDK版本每三个月发布一次;
- OpenJDK 是一个参考模型并且是完全开源的,而Oracle JDK是OpenJDK的一个实现,并不是完全开源的;
- Oracle JDK 比 OpenJDK 更稳定。OpenJDK和Oracle JDK的代码几乎相同,但Oracle JDK有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到Oracle JDK就可以解决问题;
- 在响应性和JVM性能方面,Oracle JDK与OpenJDK相比提供了更好的性能;
- Oracle JDK不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本;
- Oracle JDK根据二进制代码许可协议获得许可,而OpenJDK根据GPL v2许可获得许可。
JAVA创建对象的几种方式
1、用new关键字创建对象,需要使用构造器。
2、使用反射机制创建对象,用Class类或Constructor类的newInstance()方法。需要使用构造器。当使用Class类里的newInstance()方法,调用的是无参构造方法。当使用java.lang.reflect.Constructor类里的newInstance方法,调用的是有参构造方法。
3、通过object类的clone方法
需要实现Cloneable接口,重写object类的clone方法。无论何时我们调用一个对象的clone方法,JVM就会创建一个新的对象,将前面对象的内容全部拷贝进去。用clone方法创建对象并不会调用任何构造函数。
4、使用反序列化
通过ObjectInputStream的readObject()方法反序列化类当我们序列化和反序列化一个对象,JVM会给我们创建一个单独的对象。为了反序列化一个对象,我们需要让我们的类实现Serializable接口。在反序列化时,JVM创建对象并不会调用任何构造函数。
Java中Object类常用方法
Object 是 Java 类库中的一个特殊类,也是所有类的父类。当一个类被定义后,如果没有指定继承的父类,那么默认父类就是 Object 类。由于 Java 中的所有类都是由 Object 类派生出来的,因此在 Object 类中定义的方法,在其他类中都可以使用。
Object clone() | 创建与该对象的类相同的新对象 |
---|---|
boolean equals(Object) | 比较两对象是否相等 |
void finalize() | 当垃圾回收器确定不存在对该对象的更多引用时,对象垃圾回收器调用该方法 |
Class getClass() | 返回一个对象运行时的实例类 |
int hashCode() | 返回该对象的散列码值 |
void notify() | 激活等待在该对象的监视器上的一个线程 |
void notifyAll() | 激活等待在该对象的监视器上的全部线程 |
String toString() | 返回该对象的字符串表示 |
void wait() | 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待 |
什么是java序列化,如何实现 java 序列化?或者请解释Serializable接口的作用。
我们有时候将一个java对象变成字节流的形式传出去或者从一个字节流中恢复成一个java对象,例如, 要将java对象存储到硬盘或者传送给网络上的其他计算机,这个过程我们可以自己写代码去把一个java 对象变成某个格式的字节流再传输。
但是,jre本身就提供了这种支持,我们可以调用 OutputStream 的 writeObject 方法来做,如果要让 java帮我们做,要被传输的对象必须实现 serializable 接口,这样,javac编译时就会进行特殊处理, 编译的类才可以被 writeObject 方法操作,这就是所谓的序列化。需要被序列化的类必须实现
Serializable 接口,该接口是一个mini接口,其中没有需要实现方法,implements Serializable只是 为了标注该对象是可被序列化的。
为什么有些java类要实现Serializable接口
为了网络进行传输或者持久化
什么是序列化
将对象的状态信息转换为可以存储或传输的形式的过程
除了实现Serializable接口还有什么序列化方式
Json序列化 FastJson序列化 ProtoBuff序列化
instanceof 关键字的作用
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);
import java和javax有什么区别?
刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来使用。然而随着时间的推移,javax 逐渐地扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包确实太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准 API 的一部分。
所以,实际上 java 和 javax 没有区别。这都是一个名字。
为什么说Java语言“编译与解释并存”?
高级编程语言按照程序的执行方式分为编译型和解释型两种。简单来说,编译型语言是指编译器针对特
定的操作系统将源代码一次性翻译成可被该平台执行的机器码;解释型语言是指解释器对源程序逐行解
释成特定平台的机器码并立即执行。比如,你想阅读一本英文名著,你可以找一个英文翻译人员帮助你
阅读,有两种选择方式,你可以先等翻译人员将全本的英文名著(也就是源码)都翻译成汉语,再去阅读,也
可以让翻译人员翻译一段,你在旁边阅读一段,慢慢把书读完。
Java 语言既具有编译型语言的特征,也具有解释型语言的特征,因为 Java 程序要经过先编译,后解释 两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(*.class 文件),这种字节码必须由 Java 解释器来解释执行。因此,我们可以认为 Java 语言编译与解释并存。
基础语法
数据类型
Java有哪些数据类型
定义:Java语言是强类型语言,对于每一种数据都定义了明确的具体的数据类型,在内存中分配了不同大小的内存空间。
分类
- 基本数据类型
- 数值型
- 整数类型(byte,short,int,long)
- 浮点类型(float,double)
- 字符型(char)
- 布尔型(boolean)
- 数值型
- 引用数据类型
- 类(class)
- 接口(interface)
- 数组([])
Java基本数据类型图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yIWOy5QM-1655350327290)(https://hexojava.oss-cn-hangzhou.aliyuncs.com/img/171744c434465b69~tplv-t2oaga2asx-zoom-in-crop-mark:1304:0:0:0.png)]
switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上
- 在 Java 5 以前,switch(expr)中,expr 只能是 byte、short、char、int。从 Java5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。
基本数据类型在 Java虚拟机中一定存储在栈中吗?为什么?
不对,基本数据类型不一定存储在栈中,因为基本类型的存储位置取决于声明的作用域,来看具体的解释。
- 当基本数据类型为局部变量的时候,比如在方法中声明的变量,则存放在方法栈中的,当方法结束系统会释放方法栈,在该方法中的变量也会随着栈的销毁而结束,这也是局部变量只能在方法中使用的原因;
- 当基本数据类型为全局变量的时候,比如类中的声明的变量,则存储在堆上,因为全局变量不会随着某个方法的执行结束而销毁。
用最有效率的方法计算 2 乘以 8
- 2 << 3(左移 3 位相当于乘以 2 的 3 次方,右移 3 位相当于除以 2 的 3 次方)。
Math.round(11.5) 等于多少?Math.round(-11.5)等于多少
- Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加 0.5 然后进行下取整。
float f=3.4;是否正确
- 不正确。3.4 是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换float f =(float)3.4; 或者写成 float f =3.4F;。
short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗
- 对于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 类型,因此 s1+1 运算结果也是 int型,需要强制转换类型才能赋值给 short 型。
- 而 short s1 = 1; s1 += 1;可以正确编译,因为 s1+= 1;相当于 s1 = (short(s1 + 1);其中有隐含的强制类型转换。
编码
Java语言采用何种编码方案?有何特点?
- Java语言采用Unicode编码标准,Unicode(标准码),它为每个字符制订了一个唯一的数值,因此在任何的语言,平台,程序都可以放心的使用。
注释
什么Java注释
定义:用于解释说明程序的文字
分类
- 单行注释
格式: // 注释文字 - 多行注释
格式: /* 注释文字 */ - 文档注释
格式:/** 注释文字 */
作用
- 在程序中,尤其是复杂的程序中,适当地加入注释可以增加程序的可读性,有利于程序的修改、调试和交流。注释的内容在程序编译的时候会被忽视,不会产生目标代码,注释的部分不会对程序的执行结果产生任何影响。
注意事项:多行和文档注释都不能嵌套使用。
访问修饰符
访问修饰符 public,private,protected,以及不写(默认)时的区别
- 定义:Java中,可以使用访问修饰符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
- 分类
- private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
- default (即缺省,什么也不写,不使用任何关键字): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
- protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
- public : 对所有类可见。使用对象:类、接口、变量、方法
访问修饰符图
运算符
&和&&的区别
- &运算符有两种用法:(1)按位与;(2)逻辑与。
- &&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true 整个表达式的值才是 true。&&之所以称为短路运算,是因为如果&&左边的表达式的值是 false,右边的表达式会被直接短路掉,不会进行运算。
注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。
关键字
变量命名有哪些规则
由数字,字母,_,$组成,且开头不能是数字
Java 有没有 goto
- goto 是 Java 中的保留字,在目前版本的 Java 中没有使用。
volatile有什么作用
为了提高运行效率,编译器会自动对其优化,将经常被访问到的数据缓存起来,但这样会带来线程安全问题,比如一个线程修改了数据,但缓存中没有即时对应修改,就会造成后来访问该数据的线程读到错误数据,使用volatile可以阻止数据进行缓存,保证线程安全,但这样做会影响性能,能不用就不用
final 有什么用?
用于修饰类、属性和方法;
- 被final修饰的类不可以被继承
- 被final修饰的方法不可以被重写
- 被final修饰的变量不可以被改变,被final修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的
final finally finalize区别
- final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表 示该变量是一个常量不能被重新赋值。
- finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块 中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
- finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调 用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的 最后判断。
this关键字的用法
-
this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。
-
this的用法在java中大体可以分为3种:
-
this表示当前类,this.属性名可以给当前类的属性赋值,可以在本类中除静态方法外的任何方法(包括构造器、私有方法)中使用,这里要注意static的特性(1.遵循静态调用 2·stati关键字不能与this、super关键字共用)。
另外,若本类成员变量与方法内局部变量同名,this.属性名代表的是什么呢??
当然是局部变量了,因为Java遵循就近原则,通俗地讲,就是谁离我近,我就代表谁。
代码实现:class Student{ String name; public void hello(){ this.name=name; } }
用法二:this.方法
这里比较好理解,this代表本类,this.方法即调用方法,除了静态方法中不可使用,本类其他方法包括私有方法均可使用此格式调用其他方法,只是我们通常省略this关键字。
代码实现:class Student{ String name; public void hello(){ this.name=name; } // this.name=name;this需要在方法内使用 public void hi(){ this.hello(); hello(); he(); } private void he(){ this.name=name; hello(); this.he(); } }
用法三:this()
此格式用于构造器内,比如我们可以在无参构造内调用含参构造,那么这时候就需要在this()传入参数来实现,同理要想在含参构造内调用无参构造,只需在构造器代码第一行写this()即可,但是注意,this()与this(参数列表)不可同时使用!
代码实现:public Student(){ this("name"); this.name=name; this.he(); } public Student(String name){ // this();两个this不能一起使用 } 注意: static void play(){ // this.name=name;静态不能调用非静态 }
-
-
super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。
-
super也有三种用法:
-
1.普通的直接引用
与this类似,super相当于是指向当前对象的父类的引用,这样就可以用super.xxx来引用父类的成员。
-
2.子类中的成员变量或方法与父类中的成员变量或方法同名时,用super进行区分
class Person{ protected String name; public Person(String name) { this.name = name; } } class Student extends Person{ private String name; public Student(String name, String name1) { super(name); this.name = name1; } public void getInfo(){ System.out.println(this.name); //Child System.out.println(super.name); //Father } } public class Test { public static void main(String[] args) { Student s1 = new Student("Father","Child"); s1.getInfo(); } }
-
3.引用父类构造函数
- super(参数):调用父类中的某一个构造函数(应该为构造函数中的第一条语句)。
- this(参数):调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)。
-
this与super的区别
- super: 它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员数据或函数,基类与派生类中有相同成员定义时如:super.变量名 super.成员函数据名(实参)
- this:它代表当前对象名(在程序中易产生二义性之处,应使用this来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用this来指明成员变量名)
- super()和this()类似,区别是,super()在子类中调用父类的构造方法,this()在本类内调用本类的其它构造方法。
- super()和this()均需放在构造方法内第一行。
- 尽管可以用this调用一个构造器,但却不能调用两个。
- this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
- this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。
- 从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。
static存在的主要意义
- static的主要意义是在于创建独立于具体对象的域变量或者方法。以致于即使没有创建对象,也能使用属性和调用方法!
- static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。
- 为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。
static的独特之处
- 1、被static修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法不属于任何一个实例对象,而是被类的实例对象所共享。
怎么理解 “被类的实例对象所共享” 这句话呢?就是说,一个类的静态成员,它是属于大伙的【大伙指的是这个类的多个对象实例,我们都知道一个类可以创建多个实例!】,所有的类对象共享的,不像成员变量是自个的【自个指的是这个类的单个实例对象】…我觉得我已经讲的很通俗了,你明白了咩?
- 2、在该类被第一次加载的时候,就会去加载被static修饰的部分,而且只在类第一次使用时加载并进行初始化,注意这是第一次用就要初始化,后面根据需要是可以再次赋值的。
- 3、static变量值在类加载的时候分配空间,以后创建类对象的时候不会重新分配。赋值的话,是可以任意赋值的!
- 4、被static修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问。
static应用场景
- 因为static是被类的实例对象所共享,因此如果某个成员变量是被所有对象所共享的,那么这个成员变量就应该定义为静态变量。
- 因此比较常见的static应用场景有:
1、修饰成员变量 2、修饰成员方法 3、静态代码块 4、修饰类【只能修饰内部类也就是静态内部类】 5、静态导包
static注意事项
- 1、静态只能访问静态。
- 2、非静态既可以访问非静态的,也可以访问静态的。
流程控制语句
break ,continue ,return 的区别及作用
- break 跳出总上一层循环,不再执行循环(结束当前的循环体)
- continue 跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)
- return 程序返回,不再执行下面的代码(结束当前的方法 直接返回)
在 Java 中,如何跳出当前的多重嵌套循环
-
在Java中,要想跳出多重循环,可以在外面的循环语句前定义一个标号,然后在里层循环体的代码中使用带有标号的break 语句,即可跳出外层循环。例如:
public static void main(String[] args) { ok: for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { System.out.println("i=" + i + ",j=" + j); if (j == 5) { break ok; } } } } 复制代码
面向对象
面向对象和面向过程的区别
面向对象和面向过程的区别
面向过程,就是遇到一个问题时,将解决问题的方法拆分成一个个函数和数据,然后按一定的顺序执行完。这是一个具体的、流程化的过程。比如洗衣服的流程,先加洗衣粉、再加水、再把衣服扔进洗衣机、洗衣机洗衣、再烘干。
面向对象,就是将解决问题的方法模型化、抽象化成对象。然后给这些对象赋予属性和方法,并让对象来执行这些操作。对于洗衣服的例子,就可以抽象出人和洗衣机两个对象,操作步骤就是人.放洗衣粉()、人.加水()、人.放入洗衣机()、洗衣机.洗衣服()、洗衣机.烘干()。
优缺点 | 面向过程 | 面向对象 |
---|---|---|
优点 | 性能更好,因为类调用的时候需要实例化,开销比较大。一些注重性能的程序设计一般采用面向过程开发,比如Linux,单片机 | 易维护、易拓展、易复用,由于面向对象的封装、继承、多态等特性,能设计出低耦合的系统,方便拓展,复用。 |
缺点 | 难以维护,难以拓展,难以复用 | 性能比面向过程低 |
面向对象三大特性
面向对象的特征有哪些方面
面向对象的特征主要有以下几个方面:
封装
面向对象的封装性指的是将对象封装成一个高度自治和相对封闭的个体,对象状态(属性)由这个对象自己的行为(方法)来读取和改变
抽象
抽象就是找出一些事物的相似和共性之处,然后将这些事物归为一个类,这个类只考虑这些事物的相似和共性之处,并且会忽略与当前主题和目标无关的那些方面,将注意力集中在与当前目标有关的方面。就是把现实生活的对象,抽象为类。
继承
在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并可以加入若干新的内容,或修改原来的方法使之更适合特殊的需要,这就是继承。比如,遗产的继承。
多态
多态指的是程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底在哪个类中实现的方法,必须在由程序运营期间才能决定。
什么是多态机制?Java语言是如何实现多态的?
- 所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
- 多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。
多态的实现
- Java实现多态有三个必要条件:继承、重写、向上转型。
- 继承:在多态中必须存在有继承关系的子类和父类。
- 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
- 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。
对于Java而言,它多态的实现机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。
面向对象五大基本原则是什么(可选)
- 单一职责原则SRP(Single Responsibility Principle)
类的功能要单一,不能包罗万象,跟杂货铺似的。 - 开放封闭原则OCP(Open-Close Principle)
一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。 - 里式替换原则LSP(the Liskov Substitution Principle LSP)
子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~ - 依赖倒置原则DIP(the Dependency Inversion Principle DIP)
高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的抽象是中国人,而不是你是xx村的。 - 接口分离原则ISP(the Interface Segregation Principle ISP)
设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。
类与接口
抽象类和接口的对比
- 抽象类是用来捕捉子类的通用特性的。接口是抽象方法的集合。
- 从设计层面来说,抽象类是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
相同点
- 接口和抽象类都不能实例化
- 都位于继承的顶端,用于被其他实现或继承
- 都包含抽象方法,其子类都必须覆写这些抽象方法
不同点
参数 | 抽象类 | 接口 |
---|---|---|
声明 | 抽象类使用abstract关键字声明 | 接口使用interface关键字声明 |
实现 | 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现 | 子类使用implements关键字来实现接口。它需要提供接口中所有声明的方法的实现 |
构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
访问修饰符 | 抽象类中的方法可以是任意访问修饰符 | 接口方法默认修饰符是public。并且不允许定义为 private 或者 protected |
多继承 | 一个类最多只能继承一个抽象类 | 一个类可以实现多个接口 |
字段声明 | 抽象类的字段声明可以是任意的 | 接口的字段默认都是 static 和 final 的 |
- 最大的不同点,就是抽象类中能提供某些方法的实现,而JDK8之前,接口不能提供方法的实现。如果向一个抽象类中添加一个具体实现的方法,那么它所有子类都具有了这个方法,而接口做不到这一点。
- 抽象类需要使用extents关键字继承,但由于Java的单继承特性,使得一个类只能继承一个抽象类,这样就有局限性。而一个类可以实现多个接口,这时接口的优势就体现出来了。
- 抽象类中方法和变量的访问修饰符可以是任意的,public、private都行,但接口中方法和变量的访问修饰符必须是public,而且默认是public。
- 接口中的属性必须是static final,但抽象类中的属性可以是任意的。
不过在JDK8中,新增了接口可以实现默认方法的新特性。在方法前加default关键字,就能在接口中实现该方法,继承该接口的类也默认可以调用这个方法。
备注:Java8中接口中引入默认方法和静态方法,以此来减少抽象类和接口之间的差异。
现在,我们可以为接口提供默认实现的方法了,并且不用强制子类来实现它。
- 接口和抽象类各有优缺点,在接口和抽象类的选择上,必须遵守这样一个原则:
- 行为模型应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量少用抽象类。
- 选择抽象类的时候通常是如下情况:需要定义子类的行为,又要为子类提供通用的功能。
普通类和抽象类有哪些区别?
- 普通类不能包含抽象方法,抽象类可以包含抽象方法。
- 抽象类不能直接实例化,普通类可以直接实例化。
一个类实现了多个接口中的重名默认方法,调用时会使用哪个?
首先判断本来是否重写了默认方法,如果重写了,就直接调用本类的方法。如果本类没重写,那么判断其父类是否重写了,本类和父类中方法优先级最高。
如果以上无法判断,那么子接口的优先级更高。例如类C继承了接口B,接口B继承了接口A,在接口A和B中都实现了一个重名默认方法,那么,B的优先级更高。
如果继承的多个接口是同级的,比如接口A和接口B没有关系,那么就得在本类中显示声明要调用哪一个接口中的实现。
抽象类能使用 final 修饰吗?
- 不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类
创建一个对象用什么关键字?对象实例与对象引用有何不同?
- new关键字,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)
变量与方法
成员变量与局部变量的区别有哪些
- 位置:成员变量是在类中,方法外定义的变量。局部变量是在方法中定义的变量。
- 作用域:成员变量可以被整个类中的方法或代码块使用。局部变量只针对特定范围有效。
- 生命周期:成员变量随着对象的创建而创建,随着对象的销毁而销毁。局部变量当方法执行完,或语句结束后,就会被销毁。
- 初始值:成员变量有默认初始值。局部变量没有初始值,需要先赋值再使用。
- 使用原则:就近原则,首先在局部范围找,有就使用;接着在成员位置找。
在Java中定义一个不做事且没有参数的构造方法的作用
- Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。
在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?
- 帮助子类做初始化工作。
一个类的构造方法的作用是什么?若一个类没有声明构造方法,改程序能正确执行吗?为什么?
- 主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。
构造方法有哪些特性?
- 名字与类名相同;
- 没有返回值,但不能用void声明构造函数;
- 生成类的对象时自动执行,无需调用。
静态变量和实例变量区别
- 静态变量: 静态变量由于不属于任何实例对象,属于类的,所以在内存中只会有一份,在类的加载过程中,JVM只为静态变量分配一次内存空间。
- 实例变量: 每次创建对象,都会为每个对象分配成员变量内存空间,实例变量是属于实例对象的,在内存中,创建几次对象,就有几份成员变量。
静态变量与普通变量区别
static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
静态方法和实例方法有何不同?
静态方法和实例方法的区别主要体现在两个方面:
- 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
- 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制
在一个静态方法内调用一个非静态成员为什么是非法的?
- 由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。
什么是方法的返回值?返回值的作用是什么?
- 方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作!
Java泛型
Java中的泛型是什么 ? 使用泛型的好处是什么?
这是在各种Java泛型面试中,一开场你就会被问到的问题中的一个,主要集中在初级和中级面试中。那些拥有Java1.4或更早版本的开发背景的人都知道,在集合中存储对象并在使用前进行类型转换是多么的不方便。泛型防止了那种情况的发生。它提供了编译期的类型安全,确保你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException。
Java的泛型是如何工作的 ? 什么是类型擦除 ?
这是一道更好的泛型面试题。泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如List在运行时仅用一个List来表示。这样做的目的,是确保能和Java 5之前的版本开发二进制类库进行兼容。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型。根据你对这个泛型问题的回答情况,你会得到一些后续提问,比如为什么泛型是由类型擦除来实现的或者给你展示一些会导致编译器出错的错误泛型代码。请阅读我的Java中泛型是如何工作的来了解更多信息。
什么是泛型中的限定通配符和非限定通配符 ?
这是另一个非常流行的Java泛型面试题。限定通配符对类型进行了限制。有两种限定通配符,一种是<? extends T> 它通过确保类型必须是T的子类来设定类型的上界,另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。另一方面<?>表示了非限定通配符,因为<?>可以用任意类型来替代。更多信息请参阅我的文章泛型中限定通配符和非限定通配符之间的区别。
List<? extends T>和List <? super T>之间有什么区别 ?
这和上一个面试题有联系,有时面试官会用这个问题来评估你对泛型的理解,而不是直接问你什么是限定通配符和非限定通配符。这两个List的声明都是限定通配符的例子,List<? extends T>可以接受任何继承自T的类型的List,而List<? super T>可以接受任何T的父类构成的List。例如List<? extends Number>可以接受List或List。在本段出现的连接中可以找到更多信息。
如何编写一个泛型方法,让它能接受泛型参数并返回泛型类型?
编写泛型方法并不困难,你需要用泛型类型来替代原始类型,比如使用T, E or K,V等被广泛认可的类型占位符。泛型方法的例子请参阅Java集合类框架。最简单的情况下,一个泛型方法可能会像这样:
public V put(K key, V value) {
return cache.put(key, value);
}
Java中如何使用泛型编写带有参数的类?
这是上一道面试题的延伸。面试官可能会要求你用泛型编写一个类型安全的类,而不是编写一个泛型方法。关键仍然是使用泛型类型来代替原始类型,而且要使用JDK中采用的标准占位符。
编写一段泛型程序来实现LRU缓存?
对于喜欢Java编程的人来说这相当于是一次练习。给你个提示,LinkedHashMap可以用来实现固定大小的LRU缓存,当LRU缓存已经满了的时候,它会把最老的键值对移出缓存。LinkedHashMap提供了一个称为removeEldestEntry()的方法,该方法会被put()和putAll()调用来删除最老的键值对。当然,如果你已经编写了一个可运行的JUnit测试,你也可以随意编写你自己的实现代码。
Array中可以用泛型吗?
这可能是Java泛型面试题中最简单的一个了,当然前提是你要知道Array事实上并不支持泛型,这也是为什么Joshua Bloch在Effective Java一书中建议使用List来代替Array,因为List可以提供编译期的类型安全保证,而Array却不能。
内部类
什么是内部类?
- 在Java中,可以将一个类的定义放在另外一个类的定义内部,这就是内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。
内部类的分类有哪些
虽然内部类和普通的类一样,都可以继承类,实现接口,而且都可以定义成员(属性,方法),但是它们之间还是有区别的;比如成员内部类就不能定义静态成员(静态变量,静态方法),而静态内部类就可以定义静态成员。
内部类可以分为四种:成员内部类、局部内部类、匿名内部类和静态内部类。
内部类和静态内部类的区别
内部类:
1、内部类中的变量和方法不能声明为静态的。
2、内部类实例化:B是A的内部类,实例化B:A.B b = new A().new B()。
3、内部类可以引用外部类的静态或者非静态属性及方法。
静态内部类
1、静态内部类属性和方法可以声明为静态的或者非静态的。
2、实例化静态内部类:B是A的静态内部类,A.B b = new A.B()。
3、静态内部类只能引用外部类的静态的属性及方法。
inner classes——内部类
static nested classes——静态嵌套类
其实人家不叫静态内部类,只是叫习惯了,从字面就很容易理解了。
内部类依靠外部类的存在为前提,而静态嵌套类则可以完全独立,明白了这点就很好理解了。
非静态内部类中的变量和方法不能声明为静态的原因
静态类型的属性和方法,在类加载的时候就会存在于内存中。使用某个类的静态属性和方法,那么这个类必须要加载到虚拟机中。但是非静态内部类并不随外部类一起加载,只有在实例化外部类之后才会加载。
我们设想一个场景:在外部类并没有实例化,内部类还没有加载的时候如果调用内部类的静态成员或方法,内部类还没有加载,却试图在内存中创建该内部类的静态成员,就会产生冲突。所以非静态内部类不能有静态成员变量或静态方法。
-
定义在类内部的静态类,就是静态内部类。
public class Outer { private static int radius = 1; static class StaticInner { public void visit() { System.out.println("visit outer static variable:" + radius); } } } 复制代码
-
静态内部类可以访问外部类所有的静态变量,而不可访问外部类的非静态变量;静态内部类的创建方式,
new 外部类.静态内部类()
,如下:Outer.StaticInner inner = new Outer.StaticInner(); inner.visit(); 复制代码
静态内部类特点
静态内部类使用static修饰,可以定义非静态成员,也可以定义静态成员。
静态内部类中的方法(成员方法、静态方法)只能访问外部类的静态成员,不能访问外部类的非静态成员。
静态内部类可以被4大权限修饰符修饰,被public修饰而任意位置的其他类都可以访问,被private修饰只能被外部类内部访问。
静态内部类内部的静态成员,可以直接使用外部类.静态内部类.静态成员访问。
成员内部类
-
定义在类内部,成员位置上的非静态类,就是成员内部类。
public class InnerClassTest { private static String staticMember = "外部类静态变量"; private String member = "外部类成员变量"; private static void staticMethod() {} // 外部类静态方法 private void ordinaryMethod() {} // 外部类普通方法 class Inner { private String innerStaticMember = "成员内部类静态变量 "; private String innerMember = "成员内部类成员变量"; private void visit() { System.out.println("成员内部类访问外部类静态成员: " + staticMember); // ok System.out.println("成员内部类调用外部类成员变量: " + member); System.out.println("成员内部类调用外部类静态方法: "); staticMethod(); System.out.println("成员内部类调用外部类普通方法: "); ordinaryMethod(); } } } 复制代码
-
成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。成员内部类依赖于外部类的实例,它的创建方式
外部类实例.new 内部类()
,如下:Outer outer = new Outer(); Outer.Inner inner = outer.new Inner(); inner.visit(); 复制代码
局部内部类
-
定义在方法中的内部类,就是局部内部类。
// 定义在方法体内的局部内部类 public class InnerClassTest { private static String staticMember = "外部类静态变量"; private String member = "外部类成员变量"; private String sameNameMember = "外部类同名变量"; private void ordinaryMethod() { System.out.println("外部类普通方法"); class LocalInner { // static String localStaticMember = "局部内部类静态变量"; // 编译报错 String localMember = "局部内部类变量"; void visit() { System.out.println("局部内部类访问外部类静态成员: " + staticMember); System.out.println("局部内部类访问外部类成员变量: " + member); System.out.println("局部内部类静态方法访问本类变量: " + localMember); } } LocalInner localInner = new LocalInner(); localInner.visit(); } } 复制代码
-
定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。局部内部类的创建方式,在对应方法内,
new 内部类()
,如下:public static void testStaticFunctionClass(){ class Inner { } Inner inner = new Inner(); } 复制代码
局部内部类特点
- 局部内部类不能被权限修饰符修饰。
- 局部内部类只能在方法内部使用。
- 局部内部类不能定义静态成员。
- 局部内部类可以直接访问方法内部的局部变量和方法参数。
- 局部内部类可以访问外部类的静态成员、非静态成员。
匿名内部类
匿名内部类就是没有名字的内部类,日常开发中使用的比较多。匿名内部类可以使你的代码更加简洁,你可以在定义一个类的同时对其进行实例化。 它与局部类很相似,不同的是它没有类名,如果某个局部类你只需要用一次,那么你就可以使用匿名内部类.
-
匿名内部类就是没有名字的内部类,日常开发中使用的比较多。
public class Outer { private void test(final int i) { new Service() { public void method() { for (int j = 0; j < i; j++) { System.out.println("匿名内部类" ); } } }.method(); } } //匿名内部类必须继承或实现一个已有的接口 interface Service{ void method(); } 复制代码
-
除了没有名字,匿名内部类还有以下特点:
- 匿名内部类必须继承一个抽象类或者实现一个接口。
- 匿名内部类不能定义任何静态成员和静态方法。
- 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
- 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
-
匿名内部类创建方式:
new 类/接口{ //匿名内部类实现部分 } 复制代码
匿名内部类还有以下特点:
匿名内部类必须继承一个抽象类或者实现一个接口。
匿名内部类不能定义任何静态成员和静态方法。
当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
匿名内部类包括以下部分:
- 操作符new
- 一个需要实现的接口或要继承的类, 本例是以实现接口为例,实现了InnerService接口
- 一对括号,如果是匿名子类,与实例化普通类的语法类似,如果有构造参数,要带上构造参数;如果是实现一个接口,只需要一对空括号即可;
- 一段被"{}"括起来类声明主体;
- 末尾的";"号(因为匿名类的声明是一个表达式,是语句的一部分,因此要以分号结尾)。
内部类的优点
匿名内部类的访问权限 :
- 匿名内部类可以访问外部内的所有成员;
- 匿名内部类不能访问外部类未加final修饰的变量(注意:JDK1.8即使没有用final修饰也可以访问)
- 属性屏蔽,类似于成员内部类,匿名内部类定义的类型(如变量)会屏蔽其作用域范围内的其他同名类型(变量)
内部类的优点
我们为什么要使用内部类呢?因为它有以下优点:
- 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!
- 内部类不为同一包的其他类所见,具有很好的封装性;
- 内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
- 匿名内部类可以很方便的定义回调。
内部类有哪些应用场景
- 一些多算法场合
- 解决一些非面向对象的语句块。
- 适当使用内部类,使得代码更加灵活和富有扩展性。
- 当某个类除了它的外部类,不再被其他的类使用时。
局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final?
-
局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final呢?它内部原理是什么呢?先看这段代码:
public class Outer { void outMethod(){ final int a =10; class Inner { void innerMethod(){ System.out.println(a); } } } } 复制代码
-
以上例子,为什么要加final呢?是因为生命周期不一致, 局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了final,可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。
内部类相关,看程序说出运行结果
public class Outer {
private int age = 12;
class Inner {
private int age = 13;
public void print() {
int age = 14;
System.out.println("局部变量:" + age);
System.out.println("内部类变量:" + this.age);
System.out.println("外部类变量:" + Outer.this.age);
}
}
public static void main(String[] args) {
Outer.Inner in = new Outer().new Inner();
in.print();
}
}
复制代码
运行结果:
局部变量:14
内部类变量:13
外部类变量:12
复制代码
重写与重载
构造器(constructor)是否可被重写(override)
- 构造器不能被继承,因此不能被重写,但可以被重载。
重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?
-
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
-
重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分
public class Animal { //父类的eat()方法 public void eat() { System.out.println("吃东西"); } } public class Dog extends Animal { //Dog重写了父类Animal的eat()方法 @Overwrite public void eat() { System.out.println("啃骨头"); } }
-
重写:发生在父类和子类中,方法名、参数列表必须完全相同。而且返回值的级别应该比父类要小,抛出的异常级别也要比父类小,访问修饰符的级别要高于父类。例如父类返回LIst,子类就可以返回ArrayList;父类抛出Exception,子类就可以抛出IOException;父类的方法是public,子类可以是private。
public class Animal { //父类的eat()方法 public void eat() { System.out.println("吃东西"); } } public class Dog extends Animal { //Dog重写了父类Animal的eat()方法 @Overwrite public void eat() { System.out.println("啃骨头"); } }
对象相等判断
== 和 equals 的区别是什么
-
**==:**用于比较引用和比较基本数据类型时具有不同的功能,具体如下:
(1)、基础数据类型:比较的是他们的值是否相等,比如两个int类型的变量,比较的是变量的值是否一样。
(2)、引用数据类型:比较的是引用的地址是否相同,比如说新建了两个User对象,比较的是两个User的地址是否一样。
-
equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
-
情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
-
情况2:类覆盖了 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类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。
对equals重新需要注意五点:
1、自反性:对任意引用值X,x.equals(x)的返回值一定为true;
2、对称性:对于任何引用值x,y,当且仅当y.equals(x)返回值为true时,x.equals(y)的返回值一定为true;
3、传递性:如果x.equals(y)=true, y.equals(z)=true,则x.equals(z)=true ;
4、 一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变;
5、非空性:任何非空的引用值X,x.equals(null)的返回值一定为false 。
String s=“abce"是一种非常特殊的形式,和new 有本质的区别。它是java中唯一不需要new 就可以产生对象的途径。以String s=“abce”;形式赋值在java中叫直接量,它是在常量池中而不是象new一样放在压缩堆中。这种形式的字符串,在JVM内部发生字符串拘留,即当声明这样的一个字符串后,JVM会在常量池中先查找有有没有一个值为"abcd"的对象。如果有,就会把它赋给当前引用.即原来那个引用和现在这个引用指点向了同一对象,如果没有,则在常量池中新创建一个“abcd””,下一次如果有Strings1=“abcd”;又会将s1指向“abcd”这个对象,即以这形式声明的字符串,只要值相等,任何多个引用都指向同一对象。而String s=new String(“abcd”);和其它任何对象一样.每调用一次就产生一个对象,只要它们调用。也可以这么理解:String str=“hello”;先在内存中找是不是有“hello”这个对象,如果有,就让str指向那个“hello”。如果内存里没有"hello”,就创建一个新的对象保存"hello”.String str=new String(“hello”)就是不管内存里是不是已经有"hello"这个对象,都新建一个对象保存"hello"
hashCode 与 equals (重要)
- HashSet如何检查重复
- 两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?
- hashCode和equals方法的关系
- 面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”
hashCode()介绍
- hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode()函数。
- 散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
为什么要有 hashCode
我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:
- 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
hashCode()与equals()的相关规定
- 如果两个对象相等,则hashcode一定也是相同的
- 两个对象相等,对两个对象分别调用equals方法都返回true
- 两个对象有相同的hashcode值,它们也不一定是相等的
因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
对象的相等与指向他们的引用相等,两者有什么不同?
- 对象的相等 比的是内存中存放的内容是否相等而 引用相等 比较的是他们指向的内存地址是否相等。
值传递
当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递
- 是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的
为什么 Java 中只有值传递
- 首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。 它用来描述各种程序设计语言(不只是Java)中方法参数传递方式。
- Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。
- 下面通过 3 个例子来给大家说明
example 1
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);
}
复制代码
-
结果:
a = 20 b = 10 num1 = 10 num2 = 20
-
在swap方法中,a、b的值进行交换,并不会影响到 num1、num2。因为,a、b中的值,只是从 num1、num2 的复制过来的。也就是说,a、b相当于num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。
通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 example.
example 2
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(arr[0]);
change(arr);
System.out.println(arr[0]);
}
public static void change(int[] array) {
// 将数组的第一个元素变为0
array[0] = 0;
}
复制代码
-
结果:
1 0
-
array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的时同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。
通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。
很多程序设计语言(特别是,C++和Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员(甚至本书的作者)认为Java程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。
example 3
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
Student s1 = new Student("小张");
Student s2 = new Student("小李");
Test.swap(s1, s2);
System.out.println("s1:" + s1.getName());
System.out.println("s2:" + s2.getName());
}
public static void swap(Student x, Student y) {
Student temp = x;
x = y;
y = temp;
System.out.println("x:" + x.getName());
System.out.println("y:" + y.getName());
}
}
复制代码
-
结果:
x:小李 y:小张 s1:小张 s2:小李
-
通过上面两张图可以很清晰的看出:
方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap方法的参数x和y被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝
-
总结
Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按值传递的。
-
下面再总结一下Java中方法参数的使用情况:
- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型》
- 一个方法可以改变一个对象参数的状态。
- 一个方法不能让对象参数引用一个新的对象。
值传递和引用传递有什么区别
- 值传递:指的是在方法调用时,传递的参数是按值的拷贝传递,传递的是值的拷贝,也就是说传递后就互不相关了。
- 引用传递:指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引用的地址,也就是变量所对应的内存空间的地址。传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。
枚举类
枚举类中的声明
访问修辞符 enum 枚举名 {
枚举成员,
枚举成员,
...
};
Java枚举类的使用规则
至于枚举你也有所了解了,Java中的枚举也是一样的。而Java中枚举类的使用,也有特定的规则和场景。如果你看了以下的规则不明白的话,没有关系,继续向下学你就会明白,因为我在下面都会有讲解到这些规则。如下几个规则:
- 类的对象是确定的有限个数。
- 当需要定义一组常量时,建议使用枚举。
- 如果枚举类中只有一个对象,则可以作为单例模式的实现方法。
- 枚举类不能被继承
- 枚举类不能被单独的new创建对象
- 枚举类中的枚举成员是用
,
隔开的,多个枚举成员之间用_
隔开- 如果枚举类中的只有一个或多个枚举成员,其他什么都没有,我们在用
,
隔开的同时。最后可以省略;
结束符。
Java包
JDK 中常用的包有哪些
- java.lang:这个是系统的基础类;
- java.io:这里面是所有输入输出有关的类,比如文件操作等;
- java.nio:为了完善 io 包中的功能,提高 io 包中性能而写的一个新包;
- java.net:这里面是与网络有关的类;
- java.util:这个是系统辅助类,特别是集合类;
- java.sql:这个是数据库操作的类。
import java和javax有什么区别
- 刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来说使用。然而随着时间的推移,javax 逐渐的扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。
所以,实际上java和javax没有区别。这都是一个名字。
IO流
java 中 IO 流分为几种?
- 按照流的流向分,可以分为输入流和输出流;
- 按照操作单元划分,可以划分为字节流和字符流;
- 按照流的角色划分为节点流和处理流。
Java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。
- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
按操作方式分类结构图:
按操作对象分类结构图:
字符流和字节流有什么区别?
要把一片二进制数据数据逐一输出到某个设备中,或者从某个设备中逐一读取一片二进制数据,不管输入输出设备是什么,我们要用统一的方式来完成这些操作,用一种抽象的方式进行描述,这个抽象描述方式起名为IO流,对应的抽象类为OutputStream和InputStream ,不同的实现类就代表不同的输入和输出设备,它们都是针对字节进行操作的。
在应用中,经常要完全是字符的一段文本输出去或读进来,用字节流可以吗?
计算机中的一切最终都是二进制的字节形式存在。对于“中国”这些字符,首先要得到其对应的字节,然后将字节写入到输出流。读取时,首先读到的是字节,可是我们要把它显示为字符,我们需要将字节转换成字符。由于这样的需求很广泛,人家专门提供了字符流的包装类。
底层设备永远只接受字节数据,有时候要写字符串到底层设备,需要将字符串转成字节再进行写入。字符流是字节流的包装,字符流则是直接接受字符串,它内部将串转成字节,再写入底层设备,这为我们向IO设别写入或读取字符串提供了一点点方便。
BIO,NIO,AIO 有什么区别?
- 简答
- BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
- NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
- AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
- 详细回答
- BIO (Blocking I/O): 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
- NIO (New I/O): NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的
Socket
和ServerSocket
相对应的SocketChannel
和ServerSocketChannel
两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发 - AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。
Files的常用方法都有哪些?
- Files. exists():检测文件路径是否存在。
- Files. createFile():创建文件。
- Files. createDirectory():创建文件夹。
- Files. delete():删除一个文件或目录。
- Files. copy():复制文件。
- Files. move():移动文件。
- Files. size():查看文件个数。
- Files. read():读取文件。
- Files. write():写入文件。
反射
什么是反射机制?
- JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
- 静态编译和动态编译
- 静态编译:在编译时确定类型,绑定对象
- 动态编译:运行时确定类型,绑定对象
反射机制优缺点
- 优点: 运行期类型的判断,动态加载类,提高代码灵活度。
- 缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。
反射机制的应用场景有哪些?
- 反射是框架设计的灵魂。
- 在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。
- 举例:①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;②Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中; 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性
Java获取反射的三种方法
1.通过new对象实现反射机制 2.通过路径实现反射机制 3.通过类名实现反射机制
public class Student {
private int id;
String name;
protected boolean sex;
public float score;
}
public class Get {
//获取反射机制三种方式
public static void main(String[] args) throws ClassNotFoundException {
//方式一(通过建立对象)
Student stu = new Student();
Class classobj1 = stu.getClass();
System.out.println(classobj1.getName());
//方式二(所在通过路径-相对路径)
Class classobj2 = Class.forName("fanshe.Student");
System.out.println(classobj2.getName());
//方式三(通过类名)
Class classobj3 = Student.class;
System.out.println(classobj3.getName());
}
}
复制代码
JAVA复制
将一个对象的引用复制给另外一个对象,一共有三种方式。第一种方式是直接赋值,第二种方式 是浅拷贝,第三种是深拷贝。所以大家知道了哈,这三种概念实际上都是为了拷贝对象。
直接赋值复制
直接赋值。在 Java 中,A a1 = a2,我们需要理解的是这实际上复制的是引用,也就是
说 a1 和 a2 指向的是同一个对象。因此,当 a1 变化的时候,a2 里面的成员变量也会跟
着变化。
浅拷贝
创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,
那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。
因此,原始对象及其副本引用同一个对象。
class Resume implements Cloneable{
public Object clone() {
try {
return (Resume)super.clone();
} catch (Exception e) {
e.printStackTrace();
}
}
深拷贝
深拷贝不仅复制对象本身,而且复制对象包含的引用指向的所有对象。
class Student implements Cloneable { String name;
int age;
Professor p;
Student(String name, int age, Professor p) {
this.name = name;
this.age = age;
this.p = p;
}
public Object clone() {
Student o = null;
try {
o = (Student) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.toString());
}
o.p = (Professor) p.clone();
return o;
}
}
序列化(深 clone 一中实现)
在 Java 语言里深复制一个对象,常常可以先使对象实现 Serializable 接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象。
网络编程
网络编程的面试题可以查看我的这篇文章重学TCP/IP协议和三次握手四次挥手,内容不仅包括TCP/IP协议和三次握手四次挥手的知识,还包括计算机网络体系结构,HTTP协议,get请求和post请求区别,session和cookie的区别等,欢迎大家阅读。
常用API
String相关
字符型常量和字符串常量的区别
形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符
含义上: 字符常量相当于一个整形值(ASCII值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置)
占内存大小 字符常量只占两个字节 字符串常量占若干个字节(至少一个字符结束标志)
什么是字符串常量池?
字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。
什么是字符串常量池?
- 字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。
String 是最基本的数据类型吗
- 不是。Java 中的基本数据类型只有 8 个 :byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(referencetype),Java 5 以后引入的枚举类型也算是一种比较特殊的引用类型。
这是很基础的东西,但是很多初学者却容易忽视,Java 的 8 种基本数据类型中不包括 String,基本数据类型中用来描述文本数据的是 char,但是它只能表示单个字符,比如 ‘a’,‘好’ 之类的,如果要描述一段文本,就需要用多个 char 类型的变量,也就是一个 char 类型数组,比如“你好” 就是长度为2的数组 char[] chars = {‘你’,‘好’};
但是使用数组过于麻烦,所以就有了 String,String 底层就是一个 char 类型的数组,只是使用的时候开发者不需要直接操作底层数组,用更加简便的方式即可完成对字符串的使用。
String有哪些特性
- 不变性:String 是只读字符串,是一个典型的 immutable 对象,对它进行任何操作,其实都是创建一个新的对象,再把引用指向该对象。不变模式的主要作用在于当一个对象需要被多线程共享并频繁访问时,可以保证数据的一致性。
- 常量池优化:String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用。
- final:使用 final 来定义 String 类,表示 String 类不能被继承,提高了系统的安全性。
String为什么是不可变的吗?
-
简单来说就是String类利用了final修饰的char类型数组存储字符,源码如下图所以:
String str = "Hello"; str = str + " World"; System.out.println("str=" + str);
String真的是不可变的吗?
- 我觉得如果别人问这个问题的话,回答不可变就可以了。 下面只是给大家看两个有代表性的例子:
1 String不可变但不代表引用不可以变
String str = "Hello";
str = str + " World";
System.out.println("str=" + str);
复制代码
-
结果:
str=Hello World
-
解析:
-
实际上,原来String的内容是不变的,只是str由原来指向"Hello"的内存地址转为指向"Hello World"的内存地址而已,也就是说多开辟了一块内存区域给"Hello World"字符串。
2.通过反射是可以修改所谓的“不可变”对象
// 创建字符串"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
-
解析:
-
用反射可以访问私有成员, 然后反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。但是一般我们不会这么做,这里只是简单提一下有这个东西。
是否可以继承 String 类
- String 类是 final 类,不可以被继承。
String str="i"与 String str=new String(“i”)一样吗?
- 不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。
String s = new String(“xyz”);创建了几个字符串对象
- 两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象。
如何将字符串反转?
- 使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。
String可以被子类继承吗?
String是final的,不能被继承
String和byte[]两者之间如何转换?
String—>byte[]通过String类的getBytes方法;byte[]—>String通过new String(byte[])构造器
数组有没有 length()方法?String 有没有 length()方法
- 数组没有 length()方法 ,有 length 的属性。String 有 length()方法。JavaScript中,获得字符串的长度是通过 length 属性得到的,这一点容易和 Java 混淆。
String 类的常用方法都有那些?
- indexOf():返回指定字符的索引。
- charAt():返回指定索引处的字符。
- replace():字符串替换。
- trim():去除字符串两端空白。
- split():分割字符串,返回一个分割后的字符串数组。
- getBytes():返回字符串的 byte 类型数组。
- length():返回字符串长度。
- toLowerCase():将字符串转成小写字母。
- toUpperCase():将字符串转成大写字符。
- substring():截取字符串。
- equals():字符串比较。
在使用 HashMap 的时候,用 String 做 key 有什么好处?
- HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。
String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的
可变性
- String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。
线程安全性
- String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
性能
- 每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
如何比较两个字符串?
String内部实现了Comparable接口,有两个比较方法:compareTo(String anotherString) 和compareToIgnoreCase(String str)。
-
compareTo(String anotherString)
与传入的anotherString字符串进行比较,如果小于传入的字符串返回负数,如果大于则返回证书。当两个字符串值相等时,返回0.此时eqauls方法会返回true。
-
equalsIgnoreCase(String str)
该方法与compareTo方法类似,区别只是内部利用了Character.toUpperCase等方法进行了大小写转换后进行比较。
对于三者使用的总结
- 如果要操作少量的数据用 = String
- 单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
- 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
Date相关
包装类相关
自动装箱与拆箱
-
装箱:将基本类型用它们对应的引用类型包装起来;
Integer i = Integer.valueOf(1); //手动装箱 Integer j = 1; //自动装箱
-
拆箱:将包装类型转换为基本数据类型;
Integer i0 = new Integer(1); int i1 = i0; //自动拆箱 int i2 = i0.intValue(); //手动拆箱
基本数据类型和包装类的区别
- 定义不同。包装类属于对象,基本数据类型不是
- 声明和使用方式不同。包装类使用new初始化,有些集合类的定义不能使用基本数据类型,例如 ArrayList
- 初始值不同。包装类默认值为null,基本数据类型则不同的类型不一样(具体见上表)
- 存储方式和位置不同,从而性能不同。基本数据类型存储在栈(stack)中,包装类则分成引用和实例,引用在栈(stack)中,具体实例在堆(heap)中。可以通过程序来验证速度的不同。
int 和 Integer 有什么区别
- Java 是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java 为每一个基本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
- Java 为每个原始类型提供了包装类型:
- 原始类型: boolean,char,byte,short,int,long,float,double
- 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
Integer a= 127 与 Integer b = 127相等吗
- 对于对象引用类型:==比较的是对象的内存地址。
- 对于基本数据类型:==比较的是值。
如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象,超过范围 a1==b1的结果是false
public static void main(String[] args) {
Integer a = new Integer(3);
Integer b = 3; // 将3自动装箱成Integer类型
int c = 3;
System.out.println(a == b); // false 两个引用没有引用同一对象
System.out.println(a == c); // true a自动拆箱成int类型再和c比较
System.out.println(b == c); // true
Integer a1 = 128;
Integer b1 = 128;
System.out.println(a1 == b1); // false
Integer a2 = 127;
Integer b2 = 127;
System.out.println(a2 == b2); // true
}
包装类的功能和特点?
- 功能丰富:包装类包含了有 hashCode、getClass 、max、min 等方法;
- 可定义泛型类型参数:例如 List list = new ArrayList<>(); ;而基本数据类型不可以定义泛型参数。
- 序列化:包装类实现了 Serializable 接口,所以包装类天然支持序列化和反序列化;
- 类型转换:包装类提供了方便的类型转换方法,比如 Integer 的 parseInt() 方法;
- 高频区域数据缓存:除了Double和Float,其余包装类都有高频区域的数据缓存
包装类和基本数据类型的选择?
-
我们知道正确的使用包装类,可以提供程序的执行效率,可以使用已有的缓存,一般情况下选择基本数据类型还是包装类原则有以下几个。
① 所有 POJO 类属性必须使用包装类;
② RPC 方法返回值和参数必须使用包装类;
③ 所有局部变量推荐使用基本数据类型。
基本数据类型和包装类的转换
- 通过包装类Integer.toString()将整型转换为字符串;
- 通过Integer.parseInt()将字符串转换为int类型;
- 通过valueOf()方法把字符串转换为包装类然后通过自动拆箱。
// TODO Auto-generated method stub
//基本数据类型转换为字符串
int t1 = 12;
String t2 = Integer.toString(t1);
System.out.println("int转换为String:" + t2);
//字符串转换为基本数据类型
//通过paerInt方法
int t3 = Integer.parseInt(t2);
//通过valeOf,先把字符串转换为包装类然后通过自动拆箱
int t4 = Integer.valueOf(t2);
System.out.println("t3:" + t3);
System.out.println("t4:" + t4);