JavaSE入门
1.基本语法类型
数据类型 | 关键字 | 内存占用 | 范围 |
---|---|---|---|
字节型 | byte | 1 | -128 ~ 127 |
短整型 | short | 2 | -32768 ~ 32767 |
整型 | int | 4 | -2^31 至 2^31 - 1 |
长整型 | long | 8 | -2^63 至 2^63 - 1 |
单度精度浮点数 | flaot | 4 | 无需关注 |
双精度浮点数 | double | 8 | 无需关注 |
字符型 | char | 2 | 0 ~ 65535 |
布尔型 | boolean | 没有明确规定 | true或false |
Java提供了上述的基本数据类型和其他编程语言如c/c++类似,同时Java还提供了以上基本数据的包装类。包装类就是一个类,学过其他面向对象的语言对类都不陌生。包装类相当于对基本数据类型封装一层,提供了一些基本数据的没有的能力。这些包装类允许你将基本类型当作对象来处理,从而可以利用Java的面向对象特性,如继承、多态等。基本数据类型和对应的包装类可以自由的转换,在Java中被称为装箱和拆箱。
基本数据类型 | 包装类 |
---|---|
byte | Byte |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
包装类有如下好处:
封装:包装类允许你将基本类型的数据封装成对象,这有助于更好地组织和管理数据。
方法调用:包装类提供了许多实用的方法,如数值转换、字符串转换等。
序列化:对象可以被序列化,而基本类型不能直接序列化。
使用集合框架:Java集合框架(如
List
,Set
,Map
等)要求存储的是对象而不是基本类型,因此需要使用包装类。空值支持:包装类支持
null
值,而基本类型总是有默认值(如int
的默认值为0
)。
public class WrapperExample {
public static void main(String[] args) {
// 创建包装类实例 // 创建包装类实例
Integer myInt = new Integer(10);
// 自动装箱 用整型构造包装类对象
Integer autoBoxedInt = 20;
// 将包装类转换为基本类型
int basicInt = myInt.intValue();
// 自动拆箱
int autoUnboxedInt = autoBoxedInt;
// 使用包装类的方法 将字符串装转成整型
Integer parsedInt = Integer.parseInt("123");
// 打印结果
System.out.println("Wrapped Int: " + myInt);
System.out.println("Auto-boxed Int: " + autoBoxedInt);
System.out.println("Unwrapped Int: " + basicInt);
System.out.println("Auto-unboxed Int: " + autoUnboxedInt);
System.out.println("Parsed Int: " + parsedInt);
}
}
命名规范
Java中合法的变量名和C/C++一样,但是有个约定俗成的规则就是类名是大驼峰,变量名和方法名是小驼峰
2.引用类型
Java中除了基本数据类型外就是引用类型,数组和String以及自定义类型都是属于引用类型。Java强调一切皆对象,在构建引用变量的时候一般用new关键字去创建对象。其实这点和c++也是类似的,本质都是在堆空间上申请资源,但是Java有gc机制不必像c++那样手动释放资源,JVM(Java虚拟机)会进行内存管理。
Java内存管理如下:
Java内存通常可以划分为以下几个部分:
- 栈(Stack)
- 堆(Heap)
- 方法区(Method Area)
- 程序计数器(Program Counter Register)
- 本地方法栈(Native Method Stack)
1. 栈(Stack)
栈存储了线程中的局部变量表、操作数栈、动态链接、方法出口等信息。每一个线程都有自己的栈,每当一个新线程启动时,JVM就会为这个线程创建一个新的栈。栈中的内存分配是线程私有的,随方法的执行而生,随方法的退出而消亡。
2. 堆(Heap)
堆是Java虚拟机中最大的一块内存区域,被所有线程共享,用于存放对象实例,几乎所有的对象实例都在这里分配内存。这是垃圾收集器管理的主要区域,也是大多数对象所在的地方。当对象不再被引用时,垃圾收集器就会回收这部分内存。
3. 方法区(Method Area)
方法区存放了每个类的信息(包括类名、方法信息、常量、静态变量等)、静态变量、常量池等内容。它是被各个线程共享的内存区域,类似于Java堆,但目的不是存放实例而是存放类的信息。
4. 程序计数器(Program Counter Register)
每个线程都有一个独立的程序计数器,用于指示当前线程正在执行的字节码指令的位置。它是唯一一个不会发生OutOfMemoryError的区域,因为它的大小在类加载的时候就已经确定了。
5. 本地方法栈(Native Method Stack)
与虚拟机栈所发挥的作用非常相似,区别在于:虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到的Native方法服务。(Native方法是Java底层用到的C/C++方法)
String类型本质也是一个类,它是专门用来存储字符串的,和C++的STL中的string不同,String在Java中是一个对象,不支持下表随机访问,但是可以通过String提供的方法进行字符的遍历
public class Test {
public static void main(String[] args) {
String str = "hello";
for(int i=0;i<str.length();i++){
//获取字符串中的每个字符
char ch = str.charAt(i);
System.out.println(ch);
}
}
}
以上使用String提供的charAt方法获取每个字符进行遍历,在创建对象的时候都要通过new关键字,上述的代码中看似是将字符串直接赋值给str,但是本质上还是产生了new的动作这种写法像当于简写,数组类型也可以这样简写。
public class Test {
public static void main(String[] args) {
int [] arr1 = new int[4];
int [] arr2 ={1,2,3,4};
int [] arr3 = new int[] {1,2,3,4};
for(int i=0;i<arr1.length;i++){
System.out.println(arr1[i]);
}
System.out.println("==========");
for(int i=0;i<arr2.length;i++){
System.out.println(arr2[i]);
}
System.out.println("==========");
for(int i=0;i<arr3.length;i++){
System.out.println(arr3[i]);
}
}
}
以上就是数组的几种创建方式,第一种方式在创建数组对象的时候指明了数组的空间大小,因为没有添加任何数据,打印结果是数据类型的默认值,整型的默认值就是0
关于引用
基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值; 而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址。所谓的 “引用” 本质上只是存了一个地址. Java 将数组设定成引用类型, 这样的话后续进行数组参数传参, 其实 只是将数组的地址传入到函数形参中. 这样可以避免对整个数组的拷贝(数组可能比较长, 那么拷贝开销就会很大).
如图所示,在main方法中定义的一个arr变量是整形数组的引用,arr在栈上的地址是0x123,它所存储的值是0x456也就是堆上数组的地址,学过c/c++对引用的理解应该很快不是很难
关于数组
Java中的数组和c/c++的数组也是类似,在一片连续的空间中存储数据。Java也有二维数组在理解上和c/c++类似,本质也是一维数组,不过数组中的每个元素相当于是一维数组,Java和c/c++不同的是Java中可以省略列号但是不可以省略行号。
public class Test {
public static void main(String[] args) {
int [][] arr =new int[3][];
arr[0] = new int [2];
arr[1] = new int [1];
arr[2] = new int [3];
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
if((i==0&&j==2)||(i==1&&j==1)){
break;
}
arr[i][j]=i+j;
}
}
for(int[] a:arr){
for(int e: a ){
System.out.print(" "+e);
}
System.out.println();
}
}
}
Java中的每个行存储的数据个数可以不同这叫做不规则数组,同时Java语法也支持C++11中的范围for去遍历数组元素
3.输入输入和循环控制运算符
在Java中通过创建Scanne类型的对象获取键盘输入的数据,在使用Scanne对象的时候需要通过import关键字导包,有点类似于C/C++中导入头文件。在Java中包本质上就是一个目录,导包就是导入某个目录下的某个Java文件中的某个类型,让编译器能够认识导入的符号。
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int age = scanner.nextInt();
String name = scanner.nextLine();
double score = scanner.nextDouble();
System.out.println(" "+age+" "+name+" "+score);
}
}
上述代码中通过Scanner提供的方式获取对应的整型,字符串,浮点看类数据,当然Scanner方法不止这么一点,还有一些其他的方法。在使用Sacnner类型的时候在new对象时需使用system中的in字段来构造对象。这是单行数据输入,还有对应的多行数据输入,如下图代码所示。*
public class Test {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while (in.hasNextLine()) {
int a = in.nextInt();
int b = in.nextInt();
System.out.println(a + b);
}
}
}
多行输入的时候,通过hasNextLine方法来判断键盘中是否还有要输入的数据,通常在我们刷题的时候需要这种方式来读取OJ中的多行测试用例。关于输出我们使用System.out提供的打印方法进行数据输出。
关于导包
导包除了直接指定外,还有静态导包和通配符导包,如下方代码所示。
import static java.lang.System.out;
public class MyClass {
public static void main(String[] args) {
out.println("Hello, World!");
// 直接使用 System 类中的静态方法 println()
}
}
import java.util.*; // 导入 java.util 包下的所有类
public class MyClass {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
}
}
循环控制和运算符
循环控制和运算符和其他编程语言也是类似的比如C/C++,循环就是for while switch 语句,运算符就是加减乘除取模和左移右移等操作;不同类型的数据之间相互运算时,数据类型小的会被提升到数据类型大的
int a = 10;
long b = 20;
int c = a + b; // 编译出错: a + b==》int + long--> long + long 赋值给int时会丢失数据
long d = a + b; // 编译成功:a + b==>int + long--->long + long 赋值给long
//byte与byte的运算
byte a = 10;
byte b = 20;
byte c = a + b;
上述代码中byte 和 byte 都是相同类型, 但是出现编译报错. 原因是, 虽然 a 和 b 都是 byte, 但是计算 a + b 会先将 a
和 b 都提升成 int, 再进行计算, 得到的结果也是 int, 这是赋给 c, 就会出现上述错误.
由于计算机的 CPU 通常是按照 4 个字节为单位从内存中读写数据. 为了硬件上实现方便, 诸如 byte 和 short
这种低于 4 个字节的类型, 会先提升成 int, 再参与计算.
4.方法的使用
Java中方法也就是C/C++中的函数,Java中的方法也支持函数重载,不过和C/C++不同的是Java中的方法需要定义在类中,而且每个类中都可以定义一个main方法,main方法的主体写法是固定的,如下图所示。
public class Test {
public static void main(String[] args) {
}
}
main函数被static修饰是属于是静态方法,返回值是viod,参数是String类型用来接受输入的不定参数,学过c/c++玩过Liunx的很容易理解这个参数就是来接收在命令行中输入的一些参数选项,不过在Java中基本上用不到这个参数。在Java中方法不仅支持重载也支持递归语法。
public class Test { public static void func(char a){ System.out.println("func_char_a"); } public static void func(double a){ System.out.println("func_double_a"); } public static int func(int n){ if(n<=2){ return n; } return func(n-1)+func(n-2); } public static void main(String[] args) { System.out.println(func(5)); func(1.2); func('a'); } }
和c/c++一样如果方法递归层数太多也会栈溢出,被static修饰的方法属于是静态方法,Java类中也有this的概念,被static修饰的方法是属于类的,该方法中也没有this。
5.类和对象
Java和c++一样都是面向对象的编程语言,在Java中使用class关键字来定义类,类是对对象的抽象,在类中中包含对象的属性和方法。类的实例化就是用类去创建一个对象的过程,Java 中使用 new 关键字配合类名来实例化对象。
class Students{
private int age;
private String name;
public Students(String name,int age){
this.age=age;
this.name=name;
}
@Override
public String toString() {
return "Students{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
public class Test {
public static void main(String[] args) {
Students s = new Students("lisi",10);
System.out.println(s);
}
}
Java文件中被public修饰的类只能有一个且文件名和类名一致,public修饰的方法在类外可以通过对象来调用,public修饰的属性在类外可以被访问,被private修饰的方法和类。上述代码中的toString方法是被重写了,如果在类中不重写toString方法,调用打印方法的时候直接输出的结果是是对象的地址;同时Java中的构造方法和C++一样,方法名和类名一样没有返回值,也支持重载,与C++略有不同的是Java可以显示使用this来指明类的属性进行初始化。在未实现构造方法时,编译器会默认提供无参的构造方法,但只要实现一个构造方法编译器不就会在提供默认无参的构造方法。
封装
和其他面向对象编程语言一样Java中也有封装的概念,将数据和操作数据的方法进行结合,隐藏对象的属性,和实现细节,仅仅对外公开接口来和对象进行交互。Java中也是通过private protected public 等关键字来限制类属性和类方法的范围权限,学过c++的对这些都不是很陌生。static关键字修饰的属性是属于类的也就是全体对象的,通过类就可以直接调用。
class Students{
private int age;
private String name;
static public String classRoom = "8班";
public Students(String name,int age){
this.age=age;
this.name=name;
}
static public String getClassRoom() {
return classRoom;
}
@Override
public String toString() {
return "Students{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
public class Test {
public static void main(String[] args) {
Students s = new Students("lisi",10);
System.out.println(s+Students.getClassRoom());
}
}
在Java中为了保证的类的封装性,一般会提供一组set和get方法来修改或获取每个类的属性。关于静态变量因为属于每个类的全体对象的可以通过对象去调用,但是不建议这么做,规范的写法都是通过类对调用静态方法。
代码块
学过其他编程语言的都知道被{ }括起来的都是代码块,在Java中一般都有4中代码块;普通代码块(方法中括起来代码) 、构造代码块、静态代码块、同步代码块(涉及多线程);构造代码块和构造方法是有一定区别的,构造代码块是定义在类中的代码块(不加修饰符) ,也可称为实例代码块,主要是用于初始化实例成员变量,也就是非静态成员变量。静态代码块,主要是用来初始化静态成员变量的代码块
class Students{
private int age;
private String name;
static public String classRoom = "8班";
{
this.name="111";
this.age=12;
System.out.println("构造代码块");
}
public Students(String name,int age){
this.age=age;
this.name=name;
System.out.println("构造函数");
}
static {
//静态代码块,
Students.classRoom = "8班";
System.out.println("静态代码块!");
}
static public String getClassRoom() {
return classRoom;
}
@Override
public String toString() {
return "Students{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
public class Test {
public static void main(String[] args) {
Students s = new Students("lisi",10);
System.out.println(s+Students.getClassRoom());
}
}
实例代码块每次创建对象都会执行一次,静态代码块不管生成多少个对象只执行一次。静态成员是类的属性,因此是在JVM加载类时开辟空间的,所以静态代码块要比实例代码块先执行如果一个类包含多个静态代码块,则按照定义的顺序,也就是从上往下执行(合并).
内部类
将一个类定义在另一个类或者一个方法的内部,称为内部类,内部类外面的类可以称为外部类,内部类也是封装的一种体现;内部类必须定义在外部类的 { } 内部,内部类和外部类是共用一个Java源文件,但是编译之后,内部类会形成单独的字节码文件。内部类分为两大类:成员内部类(定义位置跟成员所处位置相同),局部内部类(定义在方法体或者 {} 中)。
成员内部类包含:实例内部类,静态内部类;局部内部类包含:局部内部类,匿名内部类
实例内部类
class Students{
private int age;
private String name;
private static String classRoom;
class Inner{
//实例内部类,可以直接访问外部类中:任意访问限定修饰符的成员
int age = Students.this.age; //访问外部类同名的成员,需要使用类名.this.成员名
String room = Students.classRoom;
//实例内部类中,不能定义有static修饰的成员,如果非要定义,则需要使用 final 修饰的常量
//常量是在程序编译的时候确定的,一旦初始化,就不能改变
private static final int a = 10;
public void func() {
//实例内部类的非静态方法中,包含了一个指向外部类对象的引用,可以使用this.访问
this.age = 10;
}
}
}
public class Test {
public static void main(String[] args) {
Students students = new Students();
Students.Inner Inner1 = students.new Inner();
Students.Inner inner2 = new Students().new Inner();
}
}
实例内部类就是指没有被 static 修饰的内部类,实例内部类所以他所处的位置是于外部类成员位置相同的,因此也可以使用public private限定符来约束;在实例方法中,访问相同的成员时,优先访问自己的,如果要访问外部类的,使用类名.this.同名成员来访问实例内部类非静态方法中,包含了一个指向外部类的对象的引用实例内部类不能有static修饰的成员,如果非要,需要用 final 修饰;如果外部类要访问内部类的成员,必须要先有内部类的对象
final关键字可以暂时理解为c/c++中的const关键字,不可变的。
final修饰变量:修饰了变量或者字段,表示常量,是不能被修改的
final修饰方法:表示方法不能被重写
final修饰类:表示该类不能被继承
class Students{
private int age;
private String name;
private static String classRoom;
static class Inner{
//静态内部类如果要访问外部类的成员变量需要通过外部类对象
Students students = new Students();
int age = students.age;
//静态内部类可以直接访问外部类的静态变量
String room = Students.classRoom;
}
}
public class Test {
public static void main(String[] args) {
Students.Inner inner = new Students.Inner();
}
}
静态内部类不依赖于对象,也就是说,不需要先创建外部对象那我们就可以直接使用外部类类名.内部静态类类名来实例化,静态类中不能直接访问外部类非静态成员,如果非要访问外部类非静态成员,我们需要先创建外部类对象
局部内部类是在方法或构造函数或初始化块中定义的类。它们只能在其声明的方法内使用,并且不能是静态的
public class Test {
void method() {
class LocalInner {
void display() {
System.out.println("Local Inner Class");
}
}
LocalInner localInner = new LocalInner();
localInner.display();
}
public static void main(String[] args) {
Test test = new Test();
test.method();
}
}
局部内部类也就是定义在外部类的方法体中或者 {} 中,这种内只能在定义的地方使用,一般使用非常的少,因为是局部的,他并不能被public,static等修饰符修饰,也有自己独立字节码文件:外部类类名$数字内部类类名.class,这种几乎不使用.
继承
Java和其他面向对象一样支持继承语法,继承是代码复用的一种手段,Java中的继承语法是通过extends关键字实现的。在Java中只支持单继承不支持多继承,一个子类只能有一个父类,一个父类可以有多个子类
class Person{
public int age;
public String name;
Person(int age,String name){
this.age=age;
this.name=name;
}
public void eat(){
System.out.println("吃饭====");
}
public void sleep(){
System.out.println("睡觉====");
}
}
class Students extends Person {
public String classRoom;
Students(int age,String name, String classRoom){
super(age,name);
this.classRoom=classRoom;
}
public void work(){
System.out.println("写作业");
}
}
public class Test {
public static void main(String[] args) {
Students s = new Students(10,"张三","8班");
}
}
super关键字相当于父类的this,在构造方法中可以使用super去初始化父类的属性,当父类和子类有同名属性的时候也可以通过super去显示的指明父类的属性进行调用,和this一样在静态方法中不能使用super
子类成员变量和父类成员变量同名,则优先访问子类的
变量优先访问遵循就近原则,自己有优先自己的,自己没有则向父类中找,都没有则报错
子类成员方法和父类成员方法同名(没有构成重载),则优先访问子类的
子类成员方法和父类成员方法同名(构成了重载),则根据调用方法传递的参数选择合适的成员访问,都没有则报错
suprt和this
都是Java中的关键字,都只能在类的非静态方法中使用,用来访问非静态成员和变量; super() 和 this() 不能在同一构造方法中作为两条独立的调用语句存在
错误实例说明
class Parent {
// 父类构造器
public Parent() {
System.out.println("Parent Constructor Called.");
}
}
class Child extends Parent {
int x;
// 错误的构造方法
public Child() {
super(); // 调用父类构造器
this(5); // 调用本类带参数的构造器
}
// 带参数的构造方法
public Child(int x) {
this.x = x;
}
public static void main(String[] args) {
Child child = new Child(); // 这行代码会编译失败
}
}
这个例子中,Child 类的无参构造方法首先尝试调用父类的构造方法 super();,然后尝试调用同一个类中的另一个构造方法 this(5);。这是不允许的,因为 super() 和 this() 必须是构造方法中的第一条语句,并且不能同时存在于一个构造方法中。除非其中一个是作为另一个的结果间接调用。
构造顺序
构造顺序是在子类实例化的时候,必须先调用父类的构造方法,将父类继承下来的成员构造完整之后,在调用子类的构造方法,将子类自己的成员初始化完整。对于代码块来说静态代码块永远是最早执行,但是父类比子类先执行
class Person {
public String name;
public String sex;
public int age;
{
System.out.println("Person 实例代码块");
}
static {
System.out.println("Person 静态代码块");
}
public Person() {
System.out.println("Person 构造方法");
}
}
class Student extends Person {
public float score;
{
System.out.println("Student 实例代码块");
}
static {
System.out.println("Student 静态代码块");
}
public Student() {
System.out.println("Student 构造方法");
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student();
}
}
访问权限
1.private:最严格的访问级别,只允许在声明它的类内部访问。
2.default(无关键字):如果未指定任何访问修饰符,则默认为包私有(package-private)。这意味着只有在同一包中的其他a类可以访问该成员。当成员变量(或方法、构造函数等)具有默认的访问权限时表示该成员只能在定义它的类所在的同一个包内的类中访问。
3.protected:允许在同一包中的类以及不同包中的子类访问该成员。
4.public:最宽松的访问级别,允许所有类从任何位置访问该成员。
多态
和C++一样Java中多态也是需要重写方法的,子类在重写父类方法的时候,一般必须于与父类的方法原型一致:返回值类型(可以不同,但必须具有父子关系构成协变),参数列表也要完全一致。访问权限不能比父类中被重写方法的访问权限更低,比如:父类的成员方法被public修饰, 那么子类中重写该方法的权限必须大于等于public;父类被 static,final,private修饰的方法都不能被重写,重写方法的时候,可以使用 @Override 注解来显式指定,这个注解能帮我们进行一些合法的校验。
class Person{
public int age;
public String name;
Person(int age,String name){
this.age=age;
this.name=name;
}
public void eat(){
System.out.println("人在吃饭");
}
}
class Students extends Person {
public String classRoom;
Students(int age,String name, String classRoom){
super(age,name);
this.classRoom=classRoom;
}
@Override
public void eat(){
System.out.println("学生在吃饭");
}
}
public class Test {
public static void main(String[] args) {
Person p = new Person(11,"李四");
Person s = new Students(10,"张三","8班");
p.eat();
s.eat();
}
}
Java中的多态也是通过父类引用接收子类或者父类对象,根据接收对象实际类型来选择对应的重写方法。多态也是动态绑定的一种,动态绑定:也称之为后期绑定,就是在编译的时候,还不确定调用哪个方法,需要等到程序运行的时候,才能具体确认调用哪个类的方法,典型代表就是多态。与之对应的是静态绑定:也称之为早期绑定,就是在编译的时候,已经根据用户所传递的实参类型确定了调用哪个方法,典型代表就是方法重载
向上向下转型
用父类来接收子类发生了向上转型,是从小范围向大范围的转换,是安全的;与之对应的向下转型,用子类来接收父类,是从大范围转为小范围,是不安全的。
向上转型的3种方法,一种是直接对象赋值,一种是方法参数是父类接收参数,一种是返回值是父类接收返回值
class Person{
public int age;
public String name;
public void eat(){
System.out.println("人在吃饭");
}
}
class Students extends Person {
public String classRoom;
Students(){
super();
}
@Override
public void eat(){
System.out.println("学生在吃饭");
}
}
public class Test {
public static void func1(Person p){
p.eat();
}
public static Person func2(String name){
if(name.equals("person")){
return new Person();
}
if(name.equals("student")){
return new Students();
}
return null;
}
public static void main(String[] args) {
Person p1 = new Person();
Person s1 = new Students();
func1(new Students());
func1(new Person());
Person p2 = func2("person");
Person p3 =func2("student");
}
}
向上转型的优点:让代码实现更简单灵活。向上转型的缺点:不能调用子类特有方法,向下转型只能通过强转方式。
class Person{
public int age;
public String name;
public void eat(){
System.out.println("人在吃饭");
}
}
class Students extends Person {
public String classRoom;
Students(){
super();
}
@Override
public void eat(){
System.out.println("学生在吃饭");
}
}
public class Test {
public static void main(String[] args) {
Person p = new Students();
if (p instanceof Students) {
Students s = (Students) p;
s.eat();
}
}
}
instanceof 是 Java 中的一个关键字,用于判断一个对象是否属于某个类或其子类,或者是否实现了某个接口。instanceof 运算符返回一个布尔值:如果对象是所指定的类(或接口)的实例,则返回 true;否则返回 false。在进行向下转型的时候需要通过instanceof进行判断。
接口和抽象类
在Java中,被 abstract 修饰的类为抽象类, 抽象类中被 abstract 修饰的方法为抽象方法,抽象方法不能有具体的实现,抽象类不可以实例化。继承抽象类的子类一定要重写父类中的抽象方法。
abstract class Person{
public int age;
public String name;
abstract public void eat();
}
}
class Students extends Person {
public String classRoom;
Students(){
super();
}
@Override
public void eat(){
System.out.println("学生在吃饭");
}
}
public class Test {
public static void main(String[] args) {
Person p = new Students();
p.eat();
}
}
接口
在Java中,可以把接口看作:多个类的公共规范,是一种引用数据类型,使用interface 关键字创建
public interface ITestInterface {
public static final int a = 10;
int b = 10; //接口中的变量默认是 public static final修饰的
public abstract void test1();
void test2(); //接口中的方法默认是public abstract 修饰的
}
创建接口的时候,用大写字母 I 开口,显然易见是表示接口;接口的方法和属性不要加任何修饰符,保持代码的简洁;Java中只能单继承但是接口是可以多继承的,使用关键字implements ;接口不能直接使用,必须要有一个类来实现接口,并实现接口中的所有抽象方法。接口不能实例化,虽然能出现变量,但是是静态的是在类加载就开辟的,接口只能理解成是特殊的类,并不是类,也不存在构造方法!
interface IRunning {
void run();
}
interface IWorking {
void work();
}
abstract class Person{
public int age;
public String name;
abstract public void eat();
}
class Students extends Person implements IRunning,IWorking {
public String classRoom;
Students(){
super();
}
@Override
public void eat(){
System.out.println("学生在吃饭");
}
@Override
public void run(){
System.out.println("学生在跑");
}
@Override
public void work(){
System.out.println("学生在写作业");
}
}
class Teacher extends Person implements IRunning,IWorking {
Teacher(){
super();
}
@Override
public void eat(){
System.out.println("老师在吃饭");
}
@Override
public void run(){
System.out.println("老师在跑");
}
@Override
public void work(){
System.out.println("老师在工作");
}
}
public class Test {
public static void main(String[] args) {
IRunning iRunning1 =new Students();
iRunning1.run();
IRunning iRunning2 = new Teacher();
iRunning2.run();
IWorking working1 = new Students();
working1.work();
IWorking working2 = new Teacher();
working1.work();
}
}
interface IRunning { void run(); } interface IWorking { void work(); } interface ITest extends IWorking,IRunning{ void eat(); }
Java中由于没有多继承,于是就有了接口的这种语法,接口就是行为继承,可以使用接口类型接收继承了该接口的类类型,调用该类重写的接口方法实现多态,对比c++这是Java语言的一种创新。和类的继承一样,接口类型也只能调用被重写的方法。
Object 是Java默认提供的一个类,Java中除了 Object 类,所有的类都是存在继承关系,也就是说,都会默认继承 Object 类,即所有对象的都可以使用 Object 的引用来接收.该类提供了很多方法,其中比较常用的就是toString方法,当我们需要打印对象时就包重写toString方法,将对象转成特定格式的字符串,除了toString方法外还有equals方法来判断对象是否相等。
class Person{
private String name;
public String getName() {
return name;
}
public Person(String name){
this.name = name;
}
@Override
public boolean equals(Object o ){
if(this == o){
return true;
}
Person p = (Person) o;
return p.getName().equals(this.name);
}
}
public class Test {
public static void main(String[] args) {
Person p1 = new Person("lisi");
Person p2 = new Person("lisi");
Person p3 = new Person("zhangsan");
System.out.println(p1.equals(p2));
System.out.println(p1.equals(p3));
}
}
6.String
string类型是用来存储字符串的,可以使用+拼接字符串,在源码中string有两个比较重要的字段value数组和hash,vaule实际存储字符串。字符串是不能被改变的,每次修改都会产生一个新的对象,原因是vaule被 priavte 权限修饰符,表示这个成员变量只能在该类中被访问,而且 String 类并没有提供 getValue 和 setValue 方法,也即没有提供能让人操作这个字符串的方法
字符串常量池在 JVM 中是StringTable类,实际上是一个固定大小的HashTable也就是哈希表,在不同JDK版本下的字符串常量池的位置以及默认大小是不同的,JDK1.8约等于Java8,字符串常量池的位置在堆中,可以设置大小,有范围限制,默认是1009。在Java程序中,类似于:1,2,3,3.14,“hello” 等字面常量经常被使用,为了程序的运行速度更快,更节省内存,Java为8中基本数据类型和String类都提供了常量池
StringBuffer和StringBuilder
可变性:与
String
类不同,StringBuilder
和StringBuffer
是可变的,这意味着你可以在创建之后改变它们的内容。性能优势:相比使用
String
拼接字符串,使用StringBuilder
或StringBuffer
更高效,特别是在需要频繁修改字符串内容的情况下。API 方法:两者都提供了类似的方法来操作字符串,如 append(), insert(), delete(), replace(), reverse(),toString()等。
主要区别
线程安全性:
StringBuilder 不是线程安全的,它没有同步机制,因此在多线程环境中使用时需要额外的同步控制。
StringBuffer 是线程安全的,它在所有修改字符串内容的方法上都有同步机制,因此适合多线程环境。
性能:
由于 StringBuffer 添加了同步机制,所以在单线程环境下,它的性能比 StringBuilder 差。
在单线程环境中推荐使用 StringBuilder,因为它没有同步开销,性能更高。
常用方法
-
charAt(int index): 获取指定索引处的字符。
1String str = "Hello, World!"; 2char c = str.charAt(7); // 'W'
-
concat(String str): 将指定字符串连接到当前字符串末尾。
1String str = "Hello"; 2String result = str.concat(", World!"); // "Hello, World!"
-
endsWith(String suffix): 测试此字符串是否以指定的后缀结束。
1String str = "Hello, World!"; 2boolean ends = str.endsWith("World!"); // true
-
equals(Object anObject): 测试两个字符串是否相等。
1String str1 = "Hello"; 2String str2 = "hello"; 3boolean equal = str1.equals(str2); // false
-
equalsIgnoreCase(String str): 忽略大小写比较两个字符串是否相等。
1String str1 = "Hello"; 2String str2 = "hello"; 3boolean equalIgnoreCase = str1.equalsIgnoreCase(str2); // true
-
indexOf(String str): 返回指定子字符串首次出现的位置。
1String str = "Hello, World!"; 2int index = str.indexOf("World"); // 7
-
lastIndexOf(String str): 返回指定子字符串最后一次出现的位置。
1String str = "Hello, World!"; 2int lastIndex = str.lastIndexOf("o"); // 8
-
length(): 返回字符串的长度。
1String str = "Hello, World!"; 2int len = str.length(); // 13
-
replace(char oldChar, char newChar): 替换字符串中的字符。
1String str = "Hello, World!"; 2String replaced = str.replace('l', 'L'); // "HeLLo, WorLd!"
-
split(String regex): 根据给定的正则表达式分割字符串。
1String str = "a,b,c,d"; 2String[] parts = str.split(","); // ["a", "b", "c", "d"]
-
startsWith(String prefix): 测试此字符串是否以指定的前缀开始。
1String str = "Hello, World!"; 2boolean starts = str.startsWith("Hello"); // true
-
substring(int beginIndex) 或 substring(int beginIndex, int endIndex): 返回一个新的字符串,它是此字符串的一个子串。
1String str = "Hello, World!"; 2String subStr = str.substring(7, 12); // "World"
-
toLowerCase() 和 toUpperCase(): 分别转换字符串为小写或大写形式。
1String str = "Hello, World!"; 2String lower = str.toLowerCase(); // "hello, world!" 3String upper = str.toUpperCase(); // "HELLO, WORLD!"
-
trim(): 删除字符串两端的空白字符。
1String str = " Hello, World! "; 2String trimmed = str.trim(); // "Hello, World!"
7.异常
Java和c++一样都提供了异常处理机制,对于Java中的异常,可能在程序编译时发生,也可能在程序运行时发生,所以根据他们发生的时机不同,异常分为:编译时异常和运行时异常;在Java中,处理异常主要有这五个关键字:thorw抛出异常,try里面放可能出现的异常代码,catch捕捉异常,finally 必须执行的特定代码,thorws声明异常。
在写程序时,有些特定的代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源:网络连接、数据库 连接、IO流等,在程序正常或者异常退出时,必须要对资源进进行回收。另外,因为异常会引发程序的跳转,可能 导致有些语句执行不到,finally就是用来解决这个问题的,finally 中的代码一定会执行的,一般在 finally 中进行一些资源清理的扫尾工作
自定义异常
自定义一个类,然后继承 Exception 或者 RuntimeException 类
实现一个带有 String 类型参数的构造方法,参数也就是出现异常的原因
public class TestException extends RuntimeException {
public TestException(String message) {
super(message);
}
}
如果自定义异常类继承了 Exception类,默认是受检查异常(编译时异常);自定义异常类继承了 RuntimeException,默认是不受检查异常(运行时异常)
示例 1:基本的 try-catch 语句
假设我们想要读取文件的内容,但是文件可能不存在,因此我们需要捕获并处理 FileNotFoundException
。
import java.io.FileReader;
import java.io.IOException;
public class BasicTryCatchExample {
public static void main(String[] args) {
try {
FileReader fileReader = new FileReader("example.txt");
// 进行文件读取操作...
} catch (FileNotFoundException e) {
System.out.println("File not found: " + e.getMessage());
}
}
}
示例 2:多重 catch 语句
如果我们想要同时捕获多种类型的异常,可以使用多重 catch
语句。
import java.io.FileReader;
import java.io.IOException;
public class MultipleCatchExample {
public static void main(String[] args) {
try {
FileReader fileReader = new FileReader("example.txt");
// 进行文件读取操作...
} catch (FileNotFoundException e) {
System.out.println("File not found: " + e.getMessage());
} catch (IOException e) {
System.out.println("IO Error: " + e.getMessage());
}
}
}
示例 3:使用 throws
如果我们不想在方法内部处理异常,而是希望调用者去处理,可以使用 throws
关键字。
import java.io.FileReader;
import java.io.IOException;
public class ThrowsExample {
public static void readFile() throws IOException {
FileReader fileReader = new FileReader("example.txt");
// 进行文件读取操作...
}
public static void main(String[] args) {
try {
readFile();
} catch (IOException e) {
System.out.println("Error reading file: " + e.getMessage());
}
}
}
示例 4:使用 finally
finally
块总是会被执行,无论是否发生异常。它通常用于释放资源,如关闭文件流等。
import java.io.FileReader;
import java.io.IOException;
public class FinallyExample {
public static void main(String[] args) {
try {
FileReader fileReader = new FileReader("example.txt");
// 进行文件读取操作...
} catch (FileNotFoundException e) {
System.out.println("File not found: " + e.getMessage());
} finally {
// 释放资源
System.out.println("Finally block executed");
}
}
}
示例 5:抛出自定义异常
我们还可以创建自定义异常类,并在适当的时候抛出。
public class CustomException extends Exception {
public CustomException(String message) {
super(message);
}
}
public class CustomExceptionExample {
public static void checkAge(int age) throws CustomException {
if (age < 18) {
throw new CustomException("Age is less than 18");
}
}
public static void main(String[] args) {
try {
checkAge(15);
} catch (CustomException e) {
System.out.println("Custom Exception: " + e.getMessage());
}
}
}
8.泛型
Java也提供泛型和c++的模板编程有点类似,类名后面的 <类型形参列表>这是一个占位符,表示当前类是一个泛型类。实例化泛型类的语法是:类名<类型实参>变量名 = new 泛型类<类型实参>(构造方法实参),但是泛型在实例化的话的时候不能使基本数据类型。
class Person<T>{
//code....
}
public class Test {
public static void main(String[] args) {
//new的时候可以省略<>中的类型
Person<Integer>p = new Person<>();
}
}
泛型在编译时最后所有的 T 变成了 Object 类型,这就是擦除机制,所以在Java中,泛型机制是在编译级别实现的,运行期间不会包含任何泛型信息。在Java中,数组是一个很特殊的类型,数组是在运行时存储和检查类型信息, 泛型则是在编译时检查类型错误。而且Java设定擦除机制就只针对变量的类型和返回值的类型,所以在编译时候压根不会擦除,不能 new 泛型数组
泛型的上界
泛型的上界就是对泛型类传入的类型变量做一定的约束,可以通过类型边界来进行约束。
class Person{
//code....
}
class Students extends Person{
}
public class Test<T extends Person> {
public static void main(String[] args) {
Test<Person>t1 = new Test<>();
Test<Students>t2 = new Test<>();
interface ITest{
}
class Person{
//code....
}
class Students extends Person implements ITest{
}
public class Test<T extends ITest> {
public static void main(String[] args) {
Test<Students>t2 = new Test<>();
}
上述代码中第一个例子Test 类中 <>表示只接收 Person 或 Person 的子类作为 T 的类型实参。第二个例子中在实例化的时候只能设置实现了ITest接口的类型。给泛型设置了上界,则会擦除到边界处,也就不会擦除成 Object
通配符
泛型T是确定的类型,一旦传类型了,就定下来了,而通配符的出现,就会使得更灵活,或者说更不确定,就好像是一个垃圾箱,可以接收所有的泛型类型,但又不能让用户随意更改.
class Demo<T>{
private T data;
public Demo(T data){
this.data=data;
}
public T getData(){
return this.data;
}
}
public class Test {
public static void func(Demo<?>test){
System.out.println(""+test.getData());
}
public static void main(String[] args) {
Demo<Integer>d1 = new Demo<>(1);
Demo<String>d2 = new Demo<>("1234");
func(d1);
func(d2);
}
}
通配符的上下界
语法:<? extends 上界> 例如:<? extends Person>表示只能接收的实参类型是 Person 或者 Person的子类;语法:<? extends 下界> 例如:<? super Person> 表示只能接收的实参类型是 Person 或者 Person的父类; 通配符的上界,不能进行写入数据,只能进行读取数据;通配符的下界,不能进行读取数据,只能写入数据
7.反射,枚举,lambda表达式
反射
Java的反射(reflection)机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到,那么我们就可以修改部分类型信息;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射(reflection)机制。
类名 | 用途 |
---|---|
Class 类 | 代表类的实体,在运行的Java程序中表示类和接口 |
Field 类 | 代表类的成员变量/类的属性 |
Method 类 | 代表类的方法 |
Constructor 类 | 代表类的构造方法 |
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionExample {
public static void main(String[] args) throws Exception {
// 获取 String 类的 Class 对象
Class<?> stringClass = String.class;
System.out.println("Class Name: " + stringClass.getName());
// 获取 String 类的所有公共构造函数
Constructor<?>[] constructors = stringClass.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("Constructor: " + constructor);
}
// 使用反射创建 String 对象
Constructor<?> stringConstructor = stringClass.getConstructor(String.class);
Object stringInstance = stringConstructor.newInstance("Hello, World!");
System.out.println("Created String: " + stringInstance);
// 获取 String 类的所有方法
Method[] methods = stringClass.getMethods();
for (Method method : methods) {
System.out.println("Method: " + method);
}
// 获取 String 类的 length 方法并调用
Method lengthMethod = stringClass.getMethod("length");
int length = (int) lengthMethod.invoke(stringInstance);
System.out.println("Length: " + length);
// 获取 String 类的字段
Field[] fields = stringClass.getFields();
for (Field field : fields) {
System.out.println("Field: " + field);
}
}
}
枚举
枚举的基本定义
枚举类型定义使用 enum
关键字,后面跟枚举类型的名称,然后列出一组固定的值,每个值之间用逗号分隔。最后通常以一个大括号 {}
结束,并且可以包含一些方法定义和其他元素。
示例定义
public enum Color {
RED,
GREEN,
BLUE
}
枚举的特点
固定值:一旦定义了枚举,就不能向其中添加新的值。
类型安全:枚举类型是强类型的,不能将任意值赋给枚举变量。
隐式实现接口:枚举类型隐式实现了 java.lang.Comparable
接口。
自定义构造函数:可以为枚举类型定义构造函数来初始化每个枚举值。
方法定义:枚举可以定义方法,甚至可以定义抽象方法让枚举值去实现。
静态初始化器:可以在枚举类型中定义静态初始化块来执行一些初始化工作。
继承:枚举类型默认继承了 java.lang.Enum
类,因此不能再继承其他类。
枚举的使用示例
定义枚举类型
public enum Color {
RED("Red"),
GREEN("Green"),
BLUE("Blue");
private final String colorName;
Color(String colorName) {
this.colorName = colorName;
}
public String getColorName() {
return colorName;
}
public static void printColors() {
for (Color color : values()) {
System.out.println(color.getColorName());
}
}
}
使用枚举类型
public class EnumExample {
public static void main(String[] args) {
// 输出枚举值
System.out.println(Color.RED); // 输出 "Color.RED"
// 输出枚举值的名称
System.out.println(Color.RED.name()); // 输出 "RED"
// 输出枚举值的颜色名称
System.out.println(Color.RED.getColorName()); // 输出 "Red"
// 遍历所有枚举值
Color.printColors(); // 输出 "Red", "Green", "Blue"
// 获取枚举值对应的枚举常量
Color color = Color.valueOf("GREEN");
System.out.println(color.getColorName()); // 输出 "Green"
}
}
枚举的常用方法
values():返回枚举类型的所有值组成的数组。
valueOf(String name):返回一个枚举常量,该常量的名称与指定的字符串匹配。
name():返回枚举常量的名称。
ordinal():返回枚举常量在枚举类型中的位置(从 0 开始)。
枚举的优势
类型安全:使用枚举可以确保在编译期间检查类型,防止非法值被赋给枚举变量。
清晰易懂:枚举提供了一种更清晰的方式来表示一组相关的常量。
易于维护:修改枚举值只需要在一个地方进行修改即可,减少了代码的冗余。
枚举的注意事项
枚举类型虽然可以定义方法,但通常不推荐在枚举类型中定义复杂的方法逻辑。
枚举类型默认是 final
的,不能被继承。
枚举类型默认实现了 Comparable
接口,可以按照定义顺序进行排序。
lambda表达式
Lambda 表达式允许你在代码中以简洁的方式定义匿名函数,即没有名字的函数
Lambda 表达式的语法结构如下:
(parameters) -> expression
或者
(parameters) -> { statements; }
其中:parameters:参数列表,可以是零个或多个参数。如果只有一个参数,可以省略圆括号。
->:箭头符号,表示 lambda 表达式的开始。
expression 或 statements:表达式或语句块,这是 lambda 表达式的主体部分。
函数式接口
Lambda 表达式本质上是一个函数式接口的实例化,函数式接口:一个接口有且只有一个抽象方法我们就把它叫做函数式接口,如果在某个接口上声明了 @FunctionalInterface 那么就默认该接口是一个函数式接口
//无返回值无参数
@FunctionalInterface
interface NoParameterNoReturn {
void test();
}
//无返回值一个参数
@FunctionalInterface
interface OneParameterNoReturn {
void test(int a);
}
//无返回值多个参数
@FunctionalInterface
interface MoreParameterNoReturn {
void test(int a,int b);
}
//有返回值无参数
@FunctionalInterface
interface NoParameterReturn {
int test();
}
//有返回值有参数
@FunctionalInterface
interface NoParameterReturn {
int test(int a, int b);
}
上述的函数式接口可以通过如下lambda表达式实现
//1.无返回无参数
public static void main(String[] args) {
NoParameterNoReturn noParameterNoReturn = ()->{
System.out.println("hahaha");
};
noParameterNoReturn.test();
}
//2.无返回值,一个参数
public static void main(String[] args) {
OneParameterNoReturn oneParameterNoReturn = a -> {
System.out.println("a="+a);
};
oneParameterNoReturn.test(5);
}
//3.无返回值,多个参数
public static void main(String[] args) {
MoreParameterNoReturn moreParameterNoReturn = (int x,int y)->{
System.out.println("a+b="+(x+y));
};
moreParameterNoReturn.test(5,6);//11
}
//4.有返回值,无参数
public static void main(String[] args) {
NoParameterReturn noParameterNoReturn = ()->{
return 10;
};
System.out.println(noParameterNoReturn.test());
}
//写法二
public static void main(String[] args) {
NoParameterReturn noParameterNoReturn = ()->10;
System.out.println(noParameterNoReturn.test());
}
//5.有返回值,有参数
public static void main(String[] args) {
MoreParameterReturn moreParameterReturn = (int a,int b)->{
return a+b;
};
System.out.println(moreParameterReturn.test(4, 6));
}
Lambda 表达式还支持访问其定义作用域之外的变量(称为捕获变量或闭包变量)。这些变量在 Lambda 表达式的作用域外定义,但可以在 Lambda 体内部访问。
捕获变量的规则
有效最终(Effectively Final):如果一个变量在 Lambda 表达式中被捕获,那么它必须是“有效最终”的。这意味着:
如果变量是 final 类型,那么它必须确实是 final 类型。
如果变量不是 final 类型,那么它在 Lambda 表达式定义之前必须不可改变(即在定义之后不再被修改)。
不可修改:一旦一个非 final 的变量被捕获,就不能在 Lambda 表达式定义之后被修改。如果试图修改这样一个变量,编译器将报错。
下面通过几个示例来展示如何在 Lambda 表达式中捕获变量:示例 1:有效最终的变量
int num = 5; Runnable runnable = () -> System.out.println(num); // num 是有效最终的 runnable.run(); // 输出 "5"
在这个例子中,
num
是一个非final
类型的变量,但它在 Lambda 表达式定义之前没有被修改过,因此它是“有效最终”的。示例 2:final 类型的变量
final int num = 5; Runnable runnable = () -> System.out.println(num); // num 是 final 类型的 runnable.run(); // 输出 "5"
在这个例子中,
num
是final
类型的变量,因此可以直接被捕获。示例 3:不可修改的变量
int num = 5; Runnable runnable = () -> System.out.println(num); num = 10; // 编译错误:num 已被捕获,不能修改 runnable.run(); // 输出 "5"
在这个例子中,试图在 Lambda 表达式定义之后修改
num
会导致编译错误。示例 4:局部变量的捕获
public class LambdaCaptureExample { public static void main(String[] args) { int num = 5; // 局部变量被捕获 Runnable runnable = () -> System.out.println(num); num = 10; // 编译错误:num 已被捕获,不能修改 runnable.run(); // 输出 "5" } }
局部变量也可以被捕获,只要它们满足“有效最终”的条件。
8.Java集合
Java 集合框架 Java Collection Framework ,又被称为容器 和其实现类 classes 。 其主要表现为将多个元素 container ,是定义在 java.util 包下的一组接口 interfaces element 置于一个单元中,用于对这些元素进行快速、便捷的存储 store 、检索 retrieve 、 管理 manipulate ,即平时我们俗称的增删查改
每个容器其实都是对某种特定数据结构的封装:
1. Collection:是一个接口,包含了大部分容器常用的一些方法
2. List:是一个接口,规范了ArrayList和LinkedList中要实现的方法 ArrayList:实现了List接口,底层为动态类型顺序表 LinkedList:实现了List接口,底层为双向链表
3. Stack:底层是栈,栈是一种特殊的顺序表
4. Queue:底层是队列,队列是一种特殊的顺序表
5. Deque:是一个接口
6. Set:集合,是一个接口,里面放置的是K模型 HashSet:底层为哈希桶,查询的时间复杂度为O(1) TreeSet:底层为红黑树,查询的时间复杂度为O( ),关于key有序的
7. Map:映射,里面存储的是K-V模型的键值对 HashMap:底层为哈希桶,查询时间复杂度为O(1) TreeMap:底层为红黑树,查询的时间复杂度为O( ),关于key有序
TreeSet和TreeMap中的key都要能比较,因此在初始化的时候需要我们传入一个比较器本质是一个接口,直接使用lambda表达式去实例化容器
//优先级队列底层是堆,默认是建小堆,传入的lambda表达式是建大堆
PriorityQueue<Integer> queue1 = new PriorityQueue<>((((o1, o2) -> o2-o1)));
for(int i=0;i<5;i++){
queue1.add(i+1);
}
while (!queue1.isEmpty()){
System.out.println(queue1.peek());
queue1.poll();
}
Map.Entry 是Map内部实现的用来存放键值对映射关系的内部类
**1.Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者HashMap **
**2. Map中存放键值对的Key是唯一的,value是可以重复的 **
**3. 在TreeMap中插入键值对时,key不能为空,否则就会抛NullPointerException异常,value可以为空。但 是HashMap的key和value都可以为空。 **
4. Map中的Key可以全部分离出来,存储到Set中来进行访问(因为Key不能重复)。
5. Map中的value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复)。
6. Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行 重新插入。
public class test {
public static void main(String[] args) {
HashSet<Integer>hashset = new HashSet<>();
HashMap<String,Integer>hashmap =new HashMap<>();
Random random = new Random();
String [] Key = new String[10];
for(int i=0;i<10;i++) {
int ret = (int) random.nextLong();
hashset.add(ret);
String key="key_"+i+" ";
Key[i]=key;
hashmap.put(key,i+1);
}
for(Map.Entry<String,Integer>entry:hashmap.entrySet()){
System.out.println(entry+" "+entry.getKey()+" "+entry.getValue());
}
System.out.println(hashmap.size());
1. Set是继承自Collection的一个接口类
**2. Set中只存储了key,并且要求key一定要唯一 **
**3.TreeSet的底层是使用Map来实现的,其使用key与Object的一个默认对象作为键值对插入到Map中的 **
4.Set最大的功能就是对集合中的元素进行去重
**5.实现Set接口的常用类有TreeSet和HashSet,还有一个LinkedHashSet,LinkedHashSet是在HashSet的基础 上维护了一个双向链表来记录元素的插入次序。 **
时间复杂度为O( ),关于key有序**
TreeSet和TreeMap中的key都要能比较,因此在初始化的时候需要我们传入一个比较器本质是一个接口,直接使用lambda表达式去实例化容器
//优先级队列底层是堆,默认是建小堆,传入的lambda表达式是建大堆
PriorityQueue<Integer> queue1 = new PriorityQueue<>((((o1, o2) -> o2-o1)));
for(int i=0;i<5;i++){
queue1.add(i+1);
}
while (!queue1.isEmpty()){
System.out.println(queue1.peek());
queue1.poll();
}
Map.Entry 是Map内部实现的用来存放键值对映射关系的内部类
**1.Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者HashMap **
**2. Map中存放键值对的Key是唯一的,value是可以重复的 **
**3. 在TreeMap中插入键值对时,key不能为空,否则就会抛NullPointerException异常,value可以为空。但 是HashMap的key和value都可以为空。 **
4. Map中的Key可以全部分离出来,存储到Set中来进行访问(因为Key不能重复)。
5. Map中的value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复)。
6. Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行 重新插入。
public class test {
public static void main(String[] args) {
HashSet<Integer>hashset = new HashSet<>();
HashMap<String,Integer>hashmap =new HashMap<>();
Random random = new Random();
String [] Key = new String[10];
for(int i=0;i<10;i++) {
int ret = (int) random.nextLong();
hashset.add(ret);
String key="key_"+i+" ";
Key[i]=key;
hashmap.put(key,i+1);
}
for(Map.Entry<String,Integer>entry:hashmap.entrySet()){
System.out.println(entry+" "+entry.getKey()+" "+entry.getValue());
}
System.out.println(hashmap.size());
1. Set是继承自Collection的一个接口类
2. Set中只存储了key,并且要求key一定要唯一
3.TreeSet的底层是使用Map来实现的,其使用key与Object的一个默认对象作为键值对插入到Map中的
4.Set最大的功能就是对集合中的元素进行去重
5.实现Set接口的常用类有TreeSet和HashSet,还有一个LinkedHashSet,LinkedHashSet是在HashSet的基础 上维护了一个双向链表来记录元素的插入次序。
6.Set中的Key不能修改,如果要修改,先将原来的删除掉,然后再重新插入
7. TreeSet中不能插入null的key,HashSet可以。