Java基础学习笔记
阿里JAVA开发手册 密码:1ky4
-
经典HelloWord开启java大门:
//定义一个公开类,起名为HelloWord public class Hello { //类体:类体中不允许直接编写java语句(除声明变量外) public static void main(String[] args) { //表示定义一个公开的静态主方法 //方法体… System.out.println("Hello, world!"); // 向控制台输出信息 } }
-
Class类的理解
1、类的加载过程:
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。
2、换句话说,Class的实例就对应着一个运行时类。
3、加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
-
JDK、JRE、JVM的区别
三层的嵌套关系。JDK>JRE>JVM
1、JDK:编译Java源码,生成Java字节码。
/bin包含:
1)、java:启动JVM,运行Java程序
2)、javac:Java的编译器,将Java源文件(.java)编译为字节码文件(.class)
3)、jar:将.class文件打包成一个.jar文件,便于发布;
4)、javadoc:用于从Java源码中自动提取注释并生成文档;
5)、jdb:Java调试器,用于开发阶段的运行调试。
2、JRE:Java运行时的环境,负责运行Java虚拟机,包含JVM+javase标准类库
3、JVM:即java虚拟机,能够识别 class字节码文件并调用操作系统向上的 API 完成动作。(实现跨平台的根本原因)
-
JDK中的主要包介绍
-
标识符命名规则与规范
1、规则:
1)、由字母、数字、下划线和美元符号构成
2)、不能以数字开头
3)、区分大小写,无长度限制
4)、关键字不能作为标识符
2、规范:
1)、驼峰式命名法
2)、见名知意
3)、类名、接口名首字母大写,后面每个单词首字母大写
方法名与变量名首字母小写,后面每个单词首字母大写
常量名每个单词大写
-
编码进制:
1、二进制:0、1,以0b或0B开头
2、八进制:以0开头,转二进制时每一位表示3位2进制
3、十进制:满十进一
4、十六进制:以0x或0X开头,转二进制时每一位表示4位2进制
-
变量(字节数)可分为:
1、基本数据类型的变量:
1)、整数类型:byte(1字节=8bit),short(2),int(4),long(8)
2)、浮点数类型:float(4),double(8)
3)、字符类型:char(2)----Unicode码
4)、布尔类型:boolean(1 or 4)----true和false
2、引用数据类型的变量:
除了基本类型(primitive type)和枚举类型(enumeration type),剩下的都是引用类型。
-
long l=128;是否正确?
不正确。声明long型变量,必须以"l"或"L"结尾,即long l = 128 L;
-
byte b=128;是否正确?
不正确。一个字节占8个位;
byte的取值范围:-128~127(-2^7~2^7-1)
short的取值范围:-32768 ~ 32767(-2^15~2^15-1)
int取值范围:-2147483648 ~ 2147483647(-2^31~2^31-1)
……
-
String 是最基本的数据类型吗?
不是,参考数据类型分类;
-
float f=1.2;是否正确?
不正确。对于float类型,需要加上f后缀。浮点型默认是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型会造成精度损失,因此需要强制类型转换float f =(float)1.2; 或者写成float f =1.2F;(了解类型转换:自动类型转换与强制类型转换)。
-
当short s1 = 1时、s1 = s1 + 1与s1 += 1有错吗?
1、 s1 = s1 + 1编译报错;由于1是int类型,因此s1+1运算结果也是int 型,需要强制转换类型才能赋值给short型。
2、s1 += 1编译正确(推荐使用);因为s1+= 1相当于s1 = (short)(s1 + 1),其中有隐含的强制类型转换。
-
java中的Math.round(-1.5)等于多少?
等于 -1,因为在数轴上取值时,中间值(0.5)向右取整,所以正 0.5 是往上取整,负 0.5 是直接舍弃。(四舍五入)
-
Sting与Stringbuffer与Stringbulider的区别(底层均使用char[]存储)
Java提供两种类型的字符串:String与Stringbuffer/ Stringbulider
两者的区别:
String类型是只读字符串,字符串内容不允许改变
Stringbuffer/ Stringbulider表示字符串对象可以修改
1、 Stringbuffer:有synchronized关键字修饰,表示线程安全的,效率低
2、 Stringbulider:无synchronized关键字修饰,表示非线程安全的,效率高
-
String s = new String("123");方式创建对象,在内存中创建了几个对象
两个对象,一个是静态区的“123”(字符串常量池),一个是用new创建在堆上的对象。
例:
String s1 = "123"; String s2 = "456"; String s3 = "123456"; String s4 = "123" + "456"; String s5 = s1 + "456"; String s6 = "123" + s2; String s7 = s1 + s2; System.out.println(s3 == s4); //true System.out.println(s3 == s5); //false System.out.println(s3 == s6); //false System.out.println(s3 == s7); //false System.out.println(s5 == s6); //false System.out.println(s5 == s7); //false System.out.println(s6 == s7); //false
-
位运算符的使用:
1、<< :在一定范围内,每向左移1位,相当于 * 2
2、>> :在一定范围内,每向右移1位,相当于 / 2
3、使用位运算符可实现最高效的2 * 8:2 << 3 或 8 << 1
-
&和&&的区别:
1、&&:短路与运算,当&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算
2、&:非短路与运算,当&左边的表达式的值是false,右边的表达式不会被直接短路掉,继续进行运算
3、| 和 || 同理
-
switch-case结构中括号的表达式:
只能是如下的6种数据类型之一:
byte 、short、char、int、枚举类型(JDK5.0新增)、String类型(JDK7.0新增)
-
Break与continue的区别
1、break适用于switch语句与循环语句,用于结束循环或选择
2、continue适用于循环语句,用于跳出本次循环
3、采用标签法,可指定结束或跳出的位置
-
Scanner键盘输入的使用:
1、导包:import java.util.Scanner;
2、Scanner的实例化:Scanner scan = new Scanner(System.in);
3、调用Scanner类的相关方法(next() / nextXxx()),来获取指定类型的变量
-
一维数组的声明方式(二维同理):
1、方式一、int[] arr = new int[]{1,2,3};
2、方式二、int[] arr = {1,2,3};
3、方式三、int[] arr = new int[2];arr[0] = 1;arr[1] = 2;
-
类、对象、面向过程与面向对象的理解
1、类是封装对象的属性和方法的载体
2、对象是类抽象出来的一个实例
3、面向过程注重的是具体的实现过程,因果关系
4、面向对象注重的是对象要完成哪些功能,独立体
5、生命周期使用OO面对对象的方式:OOA(分析)、OOD(设计)、OOP(编程)
-
JVM的基本了解
1、方法区内存:存储字节码代码片段、静态变量
2、堆内存:new实例变量、数组
3、栈内存:局部变量
4、四个类加载器:启动类加载器、扩展类加载器、应用类加载器、自定义类加载器
-
类的加载过程
-
JVM中字符串常量池存放位置说明:
1、jdk 1.6 (jdk 6.0 ,java 6.0):字符串常量池存储在方法区
2、jdk 1.7:字符串常量池存储在堆空间
3、jdk 1.8:字符串常量池存储在方法区
-
理解面对对象的三大特征
封装、继承、多态
1、封装:就是隐藏对象的属性和实现细节,仅对外提供公共访问方式,提高程序的维护性。
2、继承:主要思想就是将子类的对象作为父类的对象来使用,提高代码复用。
3、多态:父类引用变量指向子类对象。降低耦合度,提高扩展力。
-
Java创建对象的四种方式:
1、利用new方式创建对象
2、利用反射机制创建对象
通过调用Construtor构造器的newInstance方法来创建Java对象
3、利用反序列化创建对象
4、利用java.lang.Object类中的clone()方法创建对象
5、String赋值时(String s = "abc")会自动创建一个对象
-
如何实现对象克隆:
1、实现Cloneable接口并重写Object类中的clone()方法;
2、实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
-
深拷贝和浅拷贝的区别
1、浅克隆:当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
2、深克隆:除了对象本身被复制外,对象所包含的所有成员变量也将复制。
-
可变个数形参的方法使用:
1、格式:
权限修饰符 返回值类型 方法名(数据类型 ... 变量名){}
2、调用可变个数形参的方法时,传入的参数个数可以是:0个、1个……
3、可变个数形参在方法的形参中,必须声明在末尾
4、可变个数形参在方法的形参中,最多只能声明一个可变形参
-
方法的重载与重写的区别
1、方法的重载:
(1)、在同一个类中,仅与方法名与参数有关,与返回值类型,修饰符类型无关;
(2)、要求方法名相同,参数列表不同(数量不同、顺序不同、类型不同)
2、方法的重写(覆盖)
(1)、发生在继承的过程中,对父类方法进行重新定义;
(2)、子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同;
(3)、重写的方法的权限修饰符只能更高,不能更低(不能private与final修饰)
(4)、重写的方法抛出的异常只能更少不能更多
-
this关键字与super关键字的区别
1、this关键字只能用在实例方法中,不能用在静态方法中,表示当前对象;
this关键字可以用在构造方法中,表示调用其重载的构造方法,只能出现在构造方法第一行
2、super关键字只能出现在构造方法的第一行,表示调用其父类的构造方法
-
java.lang.Object类的说明
1、Object类是所有Java类的根父类
2、Object类中的方法:
clone()、hashcode()、equals()、toString()、finalize()、getClass()、wait()、notify()、notifyAll()
-
hashCode()和 equals()方法为什么要同时重写
equals()是object类中的方法,没有重写equals()时与‘==’运算符等价,用于比较两个变量(基本数据类型)是否相等,比较两个对象地址是否相等;重写equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true。而hashCode()默认返回的是这个对象的内存地址,要求相等的对象必须拥有相等的hashcode,所以重写了equals()方法必须要同时重写hashcode()。
-
Java中==与equals()的区别:
1、==是运算符,用于比较两个变量是否相等,比较两个对象地址是否相等(基本数据类型==比较的是值,引用数据类型==比较的是内存地址);
2、equals()是Object类的方法,用于比较两个对象是否相等;但它一般有两种使用情况:
情况1:无重写 equals() 方法,等价于通过“==”比较。
情况2:重写equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
总结:
基本数据类型比较使用==,比较他们的值。默认情况下,当对象使用==比较时,比较的是内存地址,如果需要比较对象的内容,需要重写equal与hashCode方法。
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");
}
}
}
注:
(1)、String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。
(2)、当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。
-
基本数据类型与对应的包装类
为了使基本数据类型的变量具有类的特征,引入包装类。
-
在实际开发中为什么定义变量时使用的是Integer而不是int
Integer允许为null值,int默认0,使用Integer可以避免空指针异常。
-
类型间的相互转换:(基本数据类型、包装类、String)
-
static静态代码块与实例代码块用法
1、静态代码块在类加载时执行,且只执行一次
2、实例代码块在构造方法执行前执行
-
如何判定属性和方法应该使用static关键字:
1、关于属性
1)、属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
2)、类中的常量也常常声明为static
2、关于方法
1)、操作静态属性的方法,通常设置为static的
2)、工具类中的方法,习惯上声明为static的。 比如:Math、Arrays、Collections
-
抽象类与接口的区别
1、抽象类:
由abstract修饰,用于降低接口与实现类之间的实现难度;
抽象类实现接口方法不需要重写接口方法;
抽象类可以有构造方法,但是不能用
2、接口类:
由inferface修饰,属于完全抽象,用于降低耦合度;
JDK7及之前,接口类中只有常量与无方法体的抽象方法;
JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
接口类无构造方法,接口之间可多继承
接口中不能定义构造器的,即接口不可以实例化
-
Java8中关于接口的新规范
1、接口中定义的静态方法,只能通过接口来调用。
2、通过实现类的对象,可以调用接口中的默认方法。如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法(重写优先)
3、如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没重写此方法的情况下,默认调用的是父类中的同名同参数的方法(类优先原则)
4、如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没重写此方法的情况下,报错。(接口冲突)
5、故必须在实现类中重写接口方法
-
匿名内部类的使用:
匿名内部类的格式: new 父类或者接口(){定义子类的内容}
1、匿名内部类不能定义任何静态成员、方法。
2、匿名内部类中的方法不能是抽象的;
3、匿名内部类必须实现接口或抽象父类的所有抽象方法。
4、匿名内部类不能定义构造器;
5、匿名内部类访问的外部类成员变量或成员方法必须用static修饰;
6、内部类可以访问外部类私有变量和方法。
-
异常的体系结构(异常种类)
* java.lang.Throwable
* |-----java.lang.Error:一般不编写针对性的代码进行处理。
* |-----java.lang.Exception:可以进行异常的处理
* |------编译时异常(checked)(即受检异常)
* |-----IOException(读写异常)
* |-----FileNotFoundException(文件路径无法找到异常)
* |-----ClassNotFoundException(类无法找到异常)
* |------运行时异常(unchecked,RuntimeException)
* |-----NullPointerException(空指针异常)
* |-----ArrayIndexOutOfBoundsException(数组下标越界异常)
* |-----ClassCastException(类转化异常)
* |-----NumberFormatException(数字格式化异常)
* |-----InputMismatchException(输入不匹配异常)
* |-----ArithmeticException(算数异常)
-
运行时异常与受检异常有何异同
1、运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。
2、受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。
3、Java编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常。
-
Java中throw与throws的区别,如何自定义异常
一、throw 和throws 关键字的区别
1、写法上 : throw 在方法体内使用,throws 函数名后或者参数列表后方法体前
2、意义 : throw 强调动作,而throws 表示一种倾向、可能但不一定实际发生
3、throws 后面跟的是异常类,可以一个,可以多个,多个用逗号隔开。throw 后跟的是异常对象,或者异常对象的引用。
4、throws 用户抛出异常,当在当前方法中抛出异常后,当前方法执行结束(throws 后,如果有finally语句的话,会执行到finally语句后再结束。)。可以理解成return一样。
即:
throws:用于方法名之后,表示上抛异常(可以同时抛出多个异常)
throw:用于方法体中,表示手动抛出单个异常
二、自定义异常
前面所讲的异常,都是系统自带的,系统自己处理,但是很多时候项目会出现特有问题,而这些问题并未被java所描述并封装成对象,所以对于这些特有的问题可以按照java的对问题封装的思想,将特有的问题进行自定义异常封装。在Java中要想创建自定义异常,需要继承Throwable或者他的子类Exception。
即:(1)继承 Exception 或 RuntimeException(2)定义构造方法(3)使用异常
//格式: public class XXXException extends Exception 或者 RuntimeException{ //添加一个空参数的构造方法 //添加一个带异常信息的构造方法 }
-
Error和Exception的区别
1、Error表示系统级的错误和程序不必处理的异常,比如内存溢出等;
2、Exception表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,就不会发生的情况。
-
java异常处理的抓抛方式
1、手动的生成一个异常对象,并抛出(throw)
2、用户将异常抛给方法的调用者,并没真正将异常处理掉(throws)
3、抓捕并处理异常(try-catch-finally)
-
final、finally与finalize关键字的区别
1、final关键字:
修饰的类不能被继承;
修饰的方法不能被重写;
修饰的变量为常量,一经赋值不允许重新赋值
2 、finally关键字
用在try…catch语句中,finally代码块必须执行
3、 finalize关键字
Object类中的一个方法,由垃圾收集器(GC)在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行其他清理工作。
-
进程与线程
1、进程:一个应用程序,资源分配的最小单位。
2、线程:一个进程的执行单元,程序执行的最小单位。
3、一个进程可启动多个线程,java至少有两个线程并发(垃圾回收器GC、main主程序)
-
java的垃圾回收机制
1、垃圾回收器是由java虚拟机(JVM)自动执行,不能人为地干预;
2、只有在虚拟机空闲或者当前堆内存不足时,才会触发执行垃圾回收线程;
3、当对象不在被引用或处于引用的隔离岛状态,对象就具备了回收的条件;
4、由系统垃圾回收器GC负责调用Object类中finalize()方法实现资源清理工作。
-
单核CPU与多核CPU的理解
1、单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。但是由于CPU时间单元特别短,因此感觉不出来。
2、多核CPU,才能更好的发挥出多线程的效率。
-
多线程的三大特征
原子性、可见性、有序性
-
多线程的实现方式(抢占与均分)
1、继承java.lang.Thread类,重写run方法,使用start()方法启动线程
2、实现java.lang.Runnable接口,重写run方法,使用start()方法启动线程
3、JDK 5.0新增,实现Callable接口,重写call方法,使用start()方法启动线程,调用get()方法会使之后线程阻塞(等待机制)
//JDK 5.0新增,使用线程池(对创建的多个线程进行管理)
//选择继承Thread还是实现Runnable创建线程主要在继承与实现的各自优缺点。
-
Thread类、Runnable接口与Callable接口的区别:
1、实现 Runnable 接口相比继承 Thread 类有如下优势
(1)、可以避免由于 Java 的单继承特性而带来的局限
(2)、增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的
(3)、线程池只能放入实现 Runable 或 Callable 类线程,不能直接放入继承 Thread 的类
2、实现 Runnable 接口和实现 Callable 接口的区别
(1)、Runnable 是自从 java1.1 就有了,而 Callable 是 1.5 之后才加上去的
(2)、实现 Callable 接口的任务线程能返回执行结果,而实现 Runnable 接口的任务线程不能返回结果
(3)、Callable 接口的 call()方法允许抛出异常,而 Runnable 接口的 run()方法的异常只能在内部消化,不能继续上抛
(4)、加入线程池运行,Runnable 使用 ExecutorService 的 execute 方法,Callable 使用 submit 方法
注:Callable 接口支持返回执行结果,此时需要调用 FutureTask.get()方法实现,此方法会阻塞主线程直到获取返回结果,当不调用此方法时,主线程不会阻塞
-
为什么实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大
1、call()可以有返回值。
2、call()可以抛出异常,被外面的操作捕获,获取异常的信息
3、Callable是支持泛型的
-
线程的生命周期(用户线程与守护线程)
新建状态、就绪状态、运行状态、死亡状态、阻塞状态
-
Java中Thread中sleep()与Object中wait()的区别:
1、类的不同:
sleep() 来自Thread,wait() 来自Object。
2、释放锁:
(1)、sleep()调用此方法会暂停该线程指定的时间,但监控依然保持,不会释放同步资源锁,到时间自动恢复,即:不释放锁,时间到会自动恢复;
(2)、wait()调用此方法会暂时退让出同步资源锁,进入等待队列,只有待调用notify()/notifyAll()方法后,才会唤醒指定的线程或者所有线程,进入锁池 ,即:释放锁,通过notify()/notifyAll()直接唤醒。
3、使用位置不同
(1)、sleep()方法可以在任何地方使用;
(2)、wait()方法则只能在同步方法或同步块中使用(wait与notify是java同步机制中重要的组成部分。结合与synchronized关键字使用,可以建立很多优秀的同步模型。notify只是让之前调用wait的线程有权利重新参与线程的调度)。
4、都可以被 interrupted 方法中断
-
notify()和notifyAll()的区别
1、notifyAll()会唤醒所有的线程,notify()之后唤醒一个线程。
2、notifyAll() 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。
-
什么叫线程安全:
1、存在竞争的线程是不安全,不存在竞争的线程是安全的;
2、当多个线程同时运行某段代码时,如果每次运行结果和单线程运行的结果是一致,而其他的变量的值也与预期一致,就是线程安全的。
-
线程安全问题存在的条件
1、多线程并发
2、存在共享数据
3、共享数据有修改行为
-
解决线程安全的措施
1、尽量使用局部变量代替实例变量与静态变量(局部变量不存在共享)
2、如果必须使用实例变量,则可考虑创建多个对象,一个对象一个线程
3、如不能使用局部变量,对象也不能创建多个,则考虑线程同步机制synchronized,加锁。
-
BigDecimd类的用法
java.lang.Math提供了一系列静态方法用于科学计算;
BigDecimd类用于处理精度高的大数据;
-
注解:(框架 = 注解 + 反射机制 + 设计模式)
1、@Override: 限定重写父类方法, 该注解只能用于方法
2、@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
3、@SuppressWarnings: 抑制编译器警告
4、元注解 :对现有的注解进行解释说明的注解。
jdk 提供的4种元注解:
Retention:指定所修饰的 Annotation 的生命周期:SOURCE\CLASS(默认行为\RUNTIME)只声明为RUNTIME生命周期的注解,才能通过反射获取。
Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素
Documented:表示所修饰的注解在被javadoc解析时,保留下来。
Inherited:被它修饰的 Annotation 将具继承性。
-
自定义注解
1、使用:
(1)、注解声明为:@interface
(2)、内部定义成员,通常使用value表示
(3)、可以指定成员的默认值,使用default定义
(4)、如果自定义注解没成员,表明是一个标识作用。
2、举例:
@Inherited @Repeatable(MyAnnotations.class) @Retention(RetentionPolicy.RUNTIME) @Target({TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE}) public @interface MyAnnotation { String value() default "hello"; }
注:如果注解有成员,在使用注解时,需要指明成员的值。自定义注解必须配上注解的信息处理流程(使用反射)才意义。
-
枚举类enum的说明:
1、枚举类:类的对象只有有限个,确定的。
2、当需要定义一组常量时,强烈建议使用枚举类
3、如果枚举类中只一个对象,则可以作为单例模式的实现方式。
-
泛型:
1、允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。
2、定义:a<E1,E2,E3……>
-
集合与数组
1、数组
1)、初始化后,数组长度就不可修改。
2)、数组一旦定义,其元素类型无法改变
3)、数组仅能存储有序、可重复的数据
2、集合
解决数组存储数据方面的弊端。
-
集合的分类、遍历方式
1、Collection父接口
|----Collection接口:单列集合,以单个方式存储元素对象
增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
* |----List接口:存储有序可重复的数据。
* |----ArrayList类:底层数组,初始化容量10,默认扩容到原容量的1.5倍,线程不安全的,检索效率高、增删效率低;
* |----LinkedList类:底层是双向链表,检索效率低、增删效率高;
* |---- Vector类:底层数组,作为List接口的古老实现类;线程安全的,效率低;底层都创建了长度为10的数组。在扩容方面,默认扩容为原来的数组长度的2倍。
*
* |----Set接口:存储无序不可重复的数据(主要参考map)
* |----HashSet类:线程不安全的;可以存储null值
* |----LinkedHashSet类:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历,但是并不是说LinkedHashSet是有序的在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。对于频繁的遍历操作,LinkedHashSet效率高于HashSet。
* |----TreeSet类:可以照添加对象的指定属性,进行排序。
2、Map父接口
|----Map接口:双列数据,以键值对key-value方式存储元素对象(无序不可重复)
添加:put(Object key,Object value)
删除:remove(Object key)
修改:put(Object key,Object value)
查询:get(Object key)
* |----HashMap类:底层jdk7及之前是数组与单向链表的结合体,jdk8是数组、单向链表与红黑树的结合体,插入与删除的原理是底层调用hashcode()得到哈希值,在经过哈希算法将其转换成数组下标;HashMap初始化容量16,默认加载因子是0.75,当达到75%开始扩容,扩容为原容量的2倍数。HashMap线程不安全的,效率高;允许存储null的key和value;
* |----LinkedHashMap类:保证在遍历map元素时,可以照添加的顺序实现遍历。原因:在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。对于频繁的遍历操作,此类执行效率高于HashMap。
* |----TreeMap类:底层使用红黑树,保证照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
* |----Hashtable:线程安全的,效率低;不能存储null的key和value
3、遍历方式:
1)、foreach方式遍历
代码:
//for(集合元素的类型 局部变量 : 集合对象) for(Object obj : coll){ System.out.println(obj); }
2)、Iterator迭代器遍历
代码:
Iterator iterator = coll.iterator(); //hasNext():判断是否还有下一个元素 while(iterator.hasNext()){ //next():①指针下移 ②将下移以后集合位置上的元素返回 System.out.println(iterator.next()); }
注:添加的对象,所在的类要重写equals()方法与hashcode方法(重)
-
什么是HashMap,其插入与删除的底层原理(同理,HashSet是基于HashMap实现)
1、数组的特点:查询效率高,插入,删除效率低。
链表的特点:查询效率低,插入删除效率高。
在HashMap底层使用数组加(链表或红黑树)的结构完美的解决了数组和链表的问题,使得查询和插入,删除的效率都很高。
2、 HashMap 在 Map.Entry 静态内部类实现中存储 key-value 对。HashMap 使用哈希算法,在 put 和 get 方法中,它使用 hashCode()和 equals()方法。当我们通过传递 key-value 对调用 put 方法的时候,HashMap 使用 Key hashCode()和哈希算法来找出存储 key-value 对的索引。Entry 存储在LinkedList 中,所以如果存在 entry,它使用 equals()方法来检查传递的 key 是否已经存在,如果存在,它会覆盖 value,如果不存在,它会创建一个新的 entry 然 后保存。当我们通过传递 key 调用 get 方法时,它再次使用 hashCode()来找到数组中的索引,然后使用 equals()方法找出正确的 Entry,然后返回它的值。
其它关于 HashMap 比较重要的问题是容量、负荷系数和阀值调整。HashMap 默认的初始容量是 32,负荷系数是 0.75。阀值是为负荷系数乘以容量,无论何时我们尝试添加一个 entry,如果 map 的大小比阀值大的时候,HashMap 会对 map 的内容进行重新哈希,且使 用更大的容量。容量总是 2 的幂,所以如果你知道你需要存储大量的 key-value对,比如 缓存从数据库里面拉取的数据,使用正确的容量和负荷系数对 HashMap 进行初始化是个不错的做法。
-
Collection和Collections的区别
1、Collection是一个接口,是Set、List等容器的父接口;
2、Collections是个一个操作Collection和Map的工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、排序、线程安全化等等。
-
在Queue(队列)中poll()和remove()的区别
1、相同点:都是返回第一个元素,并在队列中删除返回的对象。
2、不同点:如果没有元素poll()会返回null,而remove()会直接抛出NoSuchElementException 异常。
-
什么是序列化Serialize、反序列化DeSerialize,为什么要进行序列化(Java.io.Serializable):
1、序列化Serialize:Java对象状态存储在内存中;
2、反序列化DeSerialize:将内存中数据恢复成Java对象状态;
3、参与序列化与反序列化的对象,必须实现Serializable接口(JVM自动生成序列化版本号)只要是分布式开发,就必须要进行序列化操作;
简单总结起来,进行对象序列化的话的主要原因就是实现对象持久化和进行网络传输。
-
IO流的理解:
1、往程序中去,叫做输入(Input),或者叫做读(Read);
2、往程序中出,叫做输出(Output),或者叫做写(Write);
3、Java中“类名”以stream结尾的是字节流
4、Java中“类名”以read/write结尾的是字符流
-
Java IO的分类
1、Java BIO(同步并阻塞):服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。BIO适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
2、Java NIO(New IO,Non-Blocking IO)(同步非阻塞):服务器实现模式为一个请求一个线程,即当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面,所以所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。BIO与NIO一个比较重要的不同,是我们使用BIO的时候往往会引入多线程,每个连接一个单独的线程;而NIO则是使用单线程或者只使用少量的多线程,每个连接共用一个线程。NIO适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
3、Java AIO(NIO.2)(异步非阻塞):服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。AIO适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
-
Java NIO和IO的主要区别
1、NIO面向流与IO面向缓冲.(最大区别)
Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。
2、阻塞与非阻塞IO
Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
3、选择器(Selectors)
Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。
-
反射的理解
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息(操作字节码文件),并能直接操作任意对象的内部属性及方法。
-
反射机制的使用:
通过反射,获取对应的运行时类中所有的属性、方法、构造器、父类、接口、父类的泛型、包、注解、异常等。
@Test public void test1(){ //使用反射机制获取对应类的字节码 Class clazz = Class.forName(“路径(包+类名)”); //newInstance()调用对象无参构造方法,完成对象的创建 Object obj = clazz.newInstance(); //getFields():获取当前运行时类及其父类中声明为public访问权限的属性 //getDeclaredFields():获取当前运行时类中声明的所属性。(不包含父类中声 明的属性) //getMethods():获取当前运行时类及其所父类中声明为public权限的方法 //getDeclaredMethods():获取当前运行时类中声明的所方法。(不包含父类中声明的方法) //getConstructors():获取当前运行时类中声明为public的构造器 //getDeclaredConstructors():获取当前运行时类中声明的所的构造器 //getSuperclass():获取运行时类的父类 //getGenericSuperclass():获取运行时类的带泛型的父类 //getAnnotations():获取运行时类声明的注解 … … }
Java8新特性:
1、Lambda表达式的使用:
1、基本格式:
lambda形参列表 -> lambda体
(1)、-> :lambda操作符 或 箭头操作符
(2)、lambda形参列表:接口中的抽象方法的形参列表
(3)、lambda体:重写抽象方法的方法体
2、语法:
(1)、lambda形参列表的参数类型可以省略,编译器类型推断
(2)、lambda形参列表只一个参数时,其一对()也可以省略
(3)、lambda体应由一对{ }包裹
(4)、lambda体仅为一条return语句,可省略这一对{}和return关键字
2、引用的使用(Lambda表达式深层次的表达)
方法引用通过方法的名字来指向一个方法,可以使语言的构造更紧凑简洁,减少冗余代码。
方法引用使用一对冒号 ::
1、方法引用的基本格式:
(1)、对象名 :: 非静态方法名
(2)、类名 :: 静态方法名
(3)、类名 :: 非静态方法名
2、构造器引用的基本格式
(1)、类名 :: new
3、数组引用的基本格式
(1)、数组类型[] :: new
注:
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用
要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致
3、Stream API(函数式编程风格)
Stream 、Collection集合与IO流的区别
1、IO流:(java.io),关注的是数据的传输
2、Collection集合:(java.util.Collection),关注的是数据的存储,与内存打交道
3、Stream流:(java.util.stream),关注的是对数据的运算,与CPU打交道;
4、Stream API的使用
使用流程
例(Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);)
1、Stream流的创建:
(1)、通过集合:
1)、default Stream stream() : 返回一个顺序流(串行流)
2)、default Stream parallelStream() : 返回一个并行流
(2)、通过数组
Stream stream(T[] array): 返回一个数组流,T对应重载类型
(3)、通过Stream的of()
Stream of(T... values) : 返回一个流,可以接收任意数量的参数
(4)、创建无限流
1)、迭代方式: public static Stream iterate(final T seed, final UnaryOperator f)
2)、生成方式: public static Stream generate(Supplier s)
2、Stream流的中间操作
(1)、筛选与切片
1)、filter(Predicate p):接收Lambda,从流中排除某些元素
2)、distinct():筛选,通过流所生成元素的hashCode()和equals()去除重复元素
3)、limit(long maxSize):截断流,使其元素不超过给定数量
4)、skip(long n):跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足 n个,则返回一个空流。与limit(n)互补。
(2)、映射
1)、map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
2)、mapToDouble(ToDoubleFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
3)、mapToInt(ToIntFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
4)、mapToLong(ToLongFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
5)、flatMap(Function f):接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
(3)、排序
1)、sorted():产生一个新流,其中按自然顺序排序
2)、sorted(Comparator com):产生一个新流,其中按比较器顺序排序
3、终止操作
(1)、匹配与查找
1)、allMatch(Predicate p):检查是否匹配所有元素
2)、anyMatch(Predicate p):检查是否至少匹配一个元素
3)、noneMatch(Predicate p):检查是否没有匹配所有元素
4)、findFirst():返回第一个元素
5)、findAny():返回当前流中的任意元素
6)、count():返回流中元素总数
7)、max(Comparator c):返回流中最大值
8)、min(Comparator c) :返回流中最小值
9)、forEach(Consumer c) :内部迭代(使用Collection接口需要用户去做迭代, 称为部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)
(2)、归约
1)、reduce(T iden, BinaryOperator b):可以将流中元素反复结合起来,得到一个值。返回T
2)、reduce(BinaryOperator b):可以将流中元素反复结合起来,得到一个值。返回 Optional
(3)、收集
1)、collect(Collector c):将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法;Collector需要使用Collectors实用类提供实例。 Java8中java.util.stream.Collectors详解参考
注:Stream操作是延迟执行的,不会存储元素、不会改变源对象,相反,会返回一个持有结果的新Stream。
5、Optional 类的使用:
java.util.Optional类:为了解决java中的空指针问题而生(指传入对象是否为空问题)
1、Optional对象的创建:
(1)、of()方法:不支持null对象
(2)、ofNullable()方法:支持null对象
(3)、 empty()方法:可以直接创建一个空的Optional对象
2、Optional对象操作方法:
(1)、isPresent():判断是否包含值,包含值则返回true,否则返回false
Boolean aBoolean =Optional.empty().isPresent(); //false
(2)、get():获取包含的值,若有值则返回该值,否则抛出NoSuchElementException异常
Optional.ofNullable(1).get()
(3)、orElse(str):如果调用对象有值则返回该值,否则返回参数str(注:str类型与调用对象一致)
Optional.ofNullable("1").orElse("调用对象有值,str未返回");
(4)、orElseGet(supplier):如果调用对象有值则返回该值,否则返回Lambda表达式获取的值
Optional.ofNullable(null).orElseGet(() -> "结果");
(5)、orElseThrow(exceptionSupplier):如果调用对象有值则返回该值,否则抛出Lambda表达式中Supplier继承的异常
Optional.ofNullable(null).orElseThrow(() -> new RuntimeException("Optional对象不存在"));
(6)、ifPresent(consumer):如果调用的对象有值则使用对象的值调用consumer处理,否则不进行任何操作
Optional.ofNullable(list).ifPresent(po -> { po.stream().forEach(p -> { System.out.println(p.getUuid()); }); });
(7)、map():如果调用的对象(可以是任何类型的对象)有值,则进行处理,并且返回处理后的Optional对象,否则返回Optional.empty();
(8)、flatMap():如果调用的对象(必须是Optional对象)有值,则进行处理,并且返回Optional类型的值,否则返回Optional.empty();
(9)、filter(predicate):通过传入的Predicate限定条件对Optional对象中的值进行过滤,如果Optional对象中有值并且满足Predicate过滤条件,就返回包含这些值的Optional对象,否则返回空Optinal对象Optional.empty();
Java 9的新特性
1、接口的私有方法:
在Java8中,接口中的方法支持添加default关键字来添加默认实现:
public interface Test { /** * 在Java8中,接口中的方法支持添加default关键字来添加默认实现 * */ default String testString(){ return "默认实现"; } }
在Java9中,接口中可以存在私有方法:
public interface Test { /** * 声明一个私有方法 * * 私有方法必须要提供方法体进行实现 * 此方法只能被接口中的其他私有方法或是default默认实现调用 * */ private void innerPrivate(){ System.out.println("接口中的私有方法!"); } private void getPrivate(){ innerPrivate(); } default String testString(){ getPrivate(); return "默认实现"; } }
2、集合类新增工厂方法:
Java9 可以通过调用集合中静态方法of(),快速创建只读集合:
public static void main(String[] args) { /** * 只读集合: * 通过此方法创建的集合与通过Arrays创建的List比较类似,也是无法进行修改的 * */ Map<String, Integer> map = Map.of("key1", 18, "key2", 20); //注意Set中元素顺序并不一定是添加顺序 Set<String> set = Set.of("value1", "value2", "value3"); System.out.println(map); System.out.println(set); }
3、增强的Stream API:
Stream接口中添加4个新的中间操作方法
(1)、takeWhile():返回从开头开始的尽量多的元素
(2)、dropWhile():返回剩余的元素
(3)、ofNullable():允许创建空Stream
(4)、iterate()的新重载方法:提供一个Predicate (判断条件)来指定结束迭代的时间。
4、模块化系统:用模块来管理各个package,通过声明某个package暴露,其实就是package外再裹一层,不声明默认就是隐藏。因此,模块化使得代码组织上更安全,因为它可以指定哪些部分可以暴露,哪些部分隐藏。
5、jShell命令:交互式编程环境
6、多版本兼容jar包
7、钻石操作符的使用升级:能够与匿名实现类共同使用钻石操作符:<>
8、try语句语法改进:在try子句中使用已经初始化过的资源,此时资源默认变成final的
9、String存储结构变更:String不用使用char[]进行存储,改成了byte[]加上编码标记
10、全新的HTTP客户端API
11、Deprecated的相关API
12、javadoc的HTML 5支持
13、Javascript引擎升级
14、Nashorn
15、java的动态编译器
Java10的新特性:
局部变量类型推断:
public class Test { /** * var关键字 * 1、var仅适用于定义局部变量,必须进行设定初始值变量 * 2、Java10的var关键字,能够直接让局部变量自动进行类型推断,但不支持在lambda中使用 * 3、Java11后,var关键字支持在lambda中使用 * */ //var a = "Hello World!"; //var仅适用于定义局部变量 public static void main(String[] args) { var a = "Hello World!"; System.out.println(a); } }
var关键字:
1、var仅适用于定义局部变量,必须进行设定初始值变量
2、Java10的var关键字,能够直接让局部变量自动进行类型推断,但不支持在lambda中使用
3、Java11后,var关键字支持在lambda中使用
Java11的新特性:
标准化HttpClient API
在Java 9 中引入了增强的 HttpClient API 作为实验性功能,用于取代之前比较老旧的HttpURLConnection类,该API支持同步和异步,而在JAVA11中成为正式可用状态,标准化Http Client API,并提供对WebSocket和HTTP2的支持。
public class Test { public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException { //使用HttpClient.newHttpClient()实例创建HttpClient实例 HttpClient client = HttpClient.newHttpClient(); //HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).connectTimeout(Duration.ofSeconds(10)).build(); //使用HttpRequest.newBuilder()实例创建HttpRequest实例(构造一个Http请求实体) HttpRequest request = HttpRequest.newBuilder().GET().uri(new URI("https://www.baidu.com")).build(); //发送请求并获取响应对象,注意send方法后面还需要一个响应体处理器——可选择ofString直接吧响应实体转换为String字符串 HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); //HttpResponse<InputStream> response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); //输出响应实体 System.out.println(response.body()); } }
Java12的新特性:
增强switch语法
/** * Java12引入全新的switch语法 * 注:增强switch语法表达式在Java14才正式开放使用,故项目的代码级别需要调整到14以上 * */ var res = switch (obj) { //这里和之前的switch语句是一样的,但是注意这样的switch是有返回值的,所以可以被变量接收 //case后直接添加匹配值,匹配值可以存在多个,需要使用逗号隔开,使用 -> 来返回如果匹配此case语句的结果 case [匹配值, ...] -> "匹配结果"; case ... //根据不同的分支,可以存在多个case //可以使用花括号来添加代码块 case [匹配值, ...] -> { //注意处理完成后需要使用yield关键字返回最终结果,不使用return yield "相应匹配结果"; } //传统的:写法,通过yield指定返回结果,同样不需要break case [匹配值, ...]: yield "相应匹配结果"; //注意,表达式要求必须涵盖所有的可能,所以是需要添加default的 default ->"default结果"; };
注:增强switch语法表达式在Java14才正式开放使用,故项目的代码级别需要调整到14以上
Java17的新特性:
SpringBoot 3宣布不再支持 Java 8,最低要求Java 17,因此有必要学习一下Java17的新特性
密封类型:
使用final关键字不允许该类被继承,但是这样有一个缺点,如果添加了final关键字,那么无论是谁,包括我们自己也是没办法实现继承的。而在Java 17之前想要实现——某类允许被指定类继承,其他类不允许继承就会很麻烦,因此Java 17可使用密封类型来实现该功能:
1、标准格式:
public sealed [abstract] [class/interface] 类名 [extends 父类] [implements 接口, ...] permits [子类, ...]{ //内容 }
2、案例:
父类:
//父类:指定B继承,必须要有子类继承父类 public sealed class 父类名A permits 子类名B{ //sealed关键字:表示此类为密封类型 //permits关键字:表示允许继承的类型,多个子类使用逗号隔开 }
子类:
//子类:继承A,必须继承自父类 public [final/sealed/non-sealed] class 子类名B extends 父类名A { //final类型:任何类不能再继承当前类,到此为止,已经封死了。 //sealed类型:同父类,需要指定由哪些类继承。 //non-sealed类型:重新开放为普通类,任何类都可以继承。 }
3、注意事项:
(1)、可以基于普通类、抽象类、接口,也可以是继承自其他接抽象类的子类或是实现其他接口的类等
(2)、必须有子类继承,且不能是匿名内部类或是lambda的形式
(3)、sealed写在原来final的位置,但是不能和final、non-sealed关键字同时出现,只能选择其一
(4)、继承的子类必须显式标记为final、sealed或是non-sealed类型。