多线程:
有的线程只是创建了一个类,这个类实现了Runnable接口 !
而有的线程是创建了多个实现了Runnable接口的类 ! 这个时候,不同的线程可以共享一些其他类
Thread.yield()方法的作用是暂时放弃当前线程对CPU的占用权,将它让给其它线程。
Thread.yield()方法是用在run()方法中的!
sleep:让当前线程睡眠一段时间,睡眠期间释放CPU的占有权,时间到了之后,重新抢占CPU。
sched_yield:会让当前线程让出CPU占有权,然后把线程加入到同等优先级队列的末尾,然后让另一个级别等于或高于当前线程的线程先运行。如果没有符合条件的线程,那么这个函数将会立刻返回然后继续执行当前线程的程序。
thread.join()方法:挂起当前线程,直到被调用的线程执行完毕之后,再执行该线程 !
thread.join()方法不是用在run()方法中的 !
当我们继承Thread类的时候,在调用start方法的时候,如果在方法里面有了start()方法,则不会执行run方法!
synchronized
的表现形式:
- 对于普通同步方法 ,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的class对象。
- 对于同步方法块,锁是Synchonized括号配置的对象。
synchronized 是 Java 语言的一个关键字,它允许多个线程同时访问共享的资源,以避免多线程编程中的竞争条件和死锁问题。
synchronized可以用来给对象或者方法进行加锁,当对某个对象或者代码块加锁时,同时就只能有一个线程去执行。这种就是互斥关系,被加锁的区域称为临界区,而里面的资源就是临界资源。当一个线程进入临界区的时候,另一个线程就必须等待。
synchronized可以限制对某个资源的访问,但是它锁的并不是资源本身,可以锁住某个对象,只有线程拿到这把锁之后才能够去访问临界资源。
synchronized基础用法
1、通过对象进行锁
在代码里,可以通过创建一个对象,这样要想拿到临界资源,就必须先获得到这个对象的锁。
2、通过this
使用this代表锁住的是当前对象,这种方法等同直接把synchronized关键字加在方法前。
3、锁定静态方法
锁定静态方法需要通过类.class,或者直接在静态方法上加上关键字。但是,类.class不能使用this来代替。注:在同一个类加载器中,class是单例的,这也就能保证synchronized能够只让一个线程访问临界资源。
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)
进程是一个运行的程序;
程序的多次执行,都会开多个进程;比如QQ运行2次,就会打开QQ应用程序的两个进程;
进程是一个活动的状态;
进程是程序从磁盘加载到内存中,称为进程;
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
多进程是指操作系统能同时运行多个任务(程序)。
多线程是指在同一程序中有多个顺序流在执行。
Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。
实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。
run()方法是多线程程序的一个约定。
所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。
实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。
三、Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
Client继承了Thread方法!
Thread类实现了Runnable接口!
实现Runnable接口,需要写Thread类。而继承Thread类,就不用写Thread类了。
private final ReentrantLock lock = new ReentrantLock();
方法 | 说明 |
interrupt() | 中断线程 |
join() | 等待该线程终止 |
join(long millis) | 等待该线程终止的时间最长为millis毫秒 |
run() | |
setPriority(int newPriority) | 更改线程的优先级 |
sleep(long millis) | |
start() | 使该线程开始执行;Java虚拟机调用该线程的run方法 |
yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
Thread类的常用方法
Thread 类和 Runnable 类的实现和继承依据的是JAVA不支持多继承。
多线程在实际开发中的应用场景。比如登录,其实登录的背后有非常多的后续流程,在登录完成之后,有的是需要调用一些接口的,比如优惠券接口、短息接口、微信公众号模版接口去发送一些登录提醒的信息。我们可以看出,登录的背后是一个比较耗时的过程。当你验证密码成功之后,你在做后续的流程,如果整个代码量你是做出同步的情况,就会存在一个很大的缺陷。比如当你调用短息接口,如果他没有及时响应,就会导致登录的时间会很长。而http协议本身就是同步的形式。而采用多线程,可以把这些比较耗时的代码,通过多线程的形式把他封装起来,做异步的时间处理。这样,一般登录成功,只需要验证账号密码正确即可。
多线程做异步,也存在一个比较大的缺陷,多线程做异步的话,他是会消耗你当前服务器的CPU资源,多线程做异步,我们的服务器端都是共享的,既然共享的情况下,大家都使用一个服务器端不停地去创建线程做异步就比较消耗到cpu资源。所以一般我们在实际开发当中,只要使用多线程,就会整合到线程池,而且一定要配置一下线程池的名称,因为在以后的生产环境中,发生一些cpu彪高情况,我们就可以根据这个线程的名称来进行定位,哪一个部分出错了。但是一般大项目做异步会把他改成mq的形式来做异步,小的项目使用多线程是可以的。因为大项目做异步使用mq,因为mq它做异步的形式是完全解耦的,也就是说,我们登录成功之后,把后续流程比较耗时的,直接封装成一个message把他投到我们的mq服务器端,然后再通过一个单独的消费者去获取这个message,然后再异步的形式去发短信,做异步处理。但是这样做的话,它的延迟比较大,你要避免消息不要一直堆积在mq的服务器端。不然的话,我明明登录成功了,你很久才发登录成功的提醒。这就需要优化,让我们的消费者做集群消费、批量消费。
小项目做异步可以用多线程,大项目要改成mq的形式做异步。
一个进程是一个独立的运行环境,可以被看做一个程序或者一个应用。而线程是在进程中执行的一个任务。Java运行环境是一个包含了不同的类和程序的单一进程。线程可以被称为轻量级进程。线程需要较少的资源来创建和驻留在进程中,并且可以共享进程中的资源。
MD5:
UUID的生成算法保证了生成的标识符是几乎不重复的。UUID常用作数据库的主键。
UUID(Universally Unique Identifier)。
java的uuid实现理论上是唯一的,因为他是真随机。
UUID是32位. 实际上是16字节. 也就是 long long
。
UUID 的目的是让分布式系统中的所有元素都能有唯一的识别信息。如此一来,每个人都可以创建不与其它人冲突的 UUID,就不需考虑数据库创建时的名称重复问题。
雪花算法:
使用时间戳+机器码+流水号,一个字段实现了时间顺序、机器编码、创建时间。去中心化,方便排序,随便多表多库复制,并可抽取出生成时间,雪花ID主要是用在数据库集群上,去中心化,ID不会冲突又能相对排序。
只有分库分表的时候,才建议使用 UUID 来作为主键。
在分库分表的情况下,插入的 id 都是专门的 id 服务生成的,如果要严格按照自增的话,那么一般就会通过 redis 来生成,按批次去获得,比如一次性获取几百个,用完了再去获取,但是如果 redis 服务挂了,功能就完全没法用了。
UUID与Token 之间的关系:
在用户进行登录时使用UUID动态生成TOKEN,根据当前时间毫秒数+随机数利用hash算法生成几乎可以保证不重复.
String token = UUID.randomUUID().toString().replace("-","");//使用replace将生成的字符串去除时间产生的“-”。
使用md5加密方法对密码进行加密登录:
String md5 = DigestUtils.md5DigestAsHex(要加密的内容:密码等);
一般情况下,企业登录不会直接使用密码(防止系统被攻破造成用户数据泄露),而是采取加密后的数据.
非对称性加密,
add()和offer()都是向队列中添加一个元素。一些队列有大小限制,因此如果想在一个满的队列中加入一个新项,调用add()方法会抛unchecked异常,而调用offer()方法会返回false.
//ctrl+alt+v比var好使
Integer继承了Number类 !
1.类型:Integer是一个类,而int是一个基本数据类型。
2.包装和拆箱:Integer是一个包装类,可以将int类型的数据包装成lnteger对象,也可以将Integer对象拆箱为int类型的数据。这样可以在需要使用对象的地方使用Integer,而在需要使用基本数据类型的地方使用int。
3.空值处理:Integer可以表示空值,即可以赋值为null,而int不能表示空值。
4.方法和属性:Integer类提供了一些方法和属性,可以对整数进行操作和获取相关信息,如比较大小、转换为字符串等。而int作为基本数据类型,没有这些方法和属性。
5.性能:由于Integer是一个对象,所以在进行运算时需要进行装箱和拆箱的操作,会带来定的性能开销。而int作为基本数据类型,直接进行运算,性能更高。
总的来说,如果需要对整数进行一些特殊操作或者需要表示空值,可以使用Integer 类。而在一般情况下,使用int类型即可满足需求,并且性能更好。
Java中的loop:
java中loop只是continue和break的标记。可以在多层嵌套循环中,跳出到指定层。否则只能跳出当前循环。
成员变量的声明和赋值应该分开理解。在类中不能单独对成员变量进行赋值!
class A{Integer a; a = 12;}//这样写就会报错!
类体中只能有变量和方法,而在方法外的成员变量位置不能出现赋值语句,否则会报错。
类中成员变量的赋值方式:
1.在类体中定义类成员变量时,直接对成员变量进行赋值
2.通过构造方法赋值
3.使用setter方法赋值。
这三种方法最后达成的效果是一样的,都是对对象进行的赋值。以下对其做一下简单的分析:
1.在类体中定义类成员变量时,直接对成员变量进行赋值
在C++中类是完全抽象的,不可以在类中对变量直接进行初始化。在 Java中可以直接对类对象直接赋值了。
使用这种方法,在定义变量的同时直接对类成员进行了初始化,不需要借用构造方法。这种赋值的方法适用于那些被认为不可变的成员,例如常量。如果一个经常改变的的成员使用了这种方式进行赋值,在类被继承后,使用者可能无法预知此变量的初值,而错误使用了,导致程序发生错误。 例如,程序员在使用继承下来的变量price时,大意忘记了初始化price变量,而使用了在父类中的直接赋值。最终导致无法预知的错误。对于final和static变量,可以节约内存。
2.通过构造函数方法赋值
构造函数是创建类必须要调用的函数。因此,通过构造函数对变量赋值是最合乎规矩的方法。而且多个构造函数形成重载,有利于构造对象的灵活性。对于在赋值时需要指定类型的变量,更合适使用构造函数的方法赋值。
3.使用setter方法
这是对类进行封装后,对设置变量留下的处理接口。这是在类进行实例化之后,对对象里的变量进行设置使用的。
String s = Integer.toBinaryString(7);//将数字转化为二进制字符串!
str.substring(1,3);//1和3是下标,包含1,但是不包含3。
迭代器(Iterator)
Iterator 接口是 Java 集合框架中的一个核心接口,用于遍历集合中的元素。
定义了一种迭代器的行为,允许按顺序访问集合中的元素,而不需要暴露集合内部的结构。
使用 Iterator
接口遍历集合时,实际上在使用设计模式中的迭代器模式。
迭代器模式允许你访问一个聚合对象的元素,而不需要暴露其内部表示。
hasNext()
方法:
hasNext()
方法用于检查集合中是否还有元素可供遍历。
next()
方法:
next()
方法用于获取迭代器的下一个元素,并将迭代器的指针移动到下一个位置。
如果在调用 next()
方法之前没有调用 hasNext()
方法进行检查,并且集合已经遍历完毕,那么 next()
方法会抛出 NoSuchElementException
异常。
String中的compareTo()方法:
String s1 = "你好";
String s2 = "世界";
int i = s1.compareTo(s2);//compareTo()
方法用于比较两个字符串的字典顺序。
返回值是整型,它是比较对应字符的大小(ASCLL码顺序),如果第一个字符和参数的第一个字符不等,结束比较,返回他们之间的长度差值,如果第一个字符和参数的第一个字符相等,则以第二个字符和参数的第二个字符做比较,以此类推,直至比较的字符或被比较的字符有一方结束。
StringBuffer的拼接效率要比直接使用“+”的效率要高!
buffer.append(1);
buffer.append('a');
buffer.append("你好");
由于 buffer
是一个字符串缓冲区(StringBuffer),所以整数会被自动转换为字符串形式并追加到缓冲区的末尾。类似地,字符和字符串也会被转换为字符串并追加到缓冲区。
函数式编程是一种"编程范式"(programming paradigm
),一种编写程序的方法论
主要的编程范式有三种:命令式编程,声明式编程和函数式编程
相比命令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而非设计一个复杂的执行过程
// 命令式编程
var array = [0, 1, 2, 3]
for(let i = 0; i < array.length; i++) {
array[i] = Math.pow(array[i], 2)
}
// 函数式方式
[0, 1, 2, 3].map(num => Math.pow(num, 2))
JAVA的函数式接口 !
一个接口中只有一个方法 !
@FunctionalInterface
该注解主要是:一种信息性注释类型,用于指示接口类型声明是Java语言规范中定义的功能接口。
函数式接口是只包含一个抽象方法声明的接口
java.lang.Runnable 就是一种函数式接口,在 Runnable 接口中只声明了一个方法 void run()
Java中的函数式编程体现就是Lambda表达式
@FunctionalInterface
- 放在接口定义的上方:如果接口是函数式接口,编译通过;如果不是,编译失败。
代码简介,开发迅速
方便函数式编程
非常容易进行并行计算
代码可读性差
不容易进行调试
在非并行计算中,很多未必有传统的for性能高
被static修饰的变量称为静态变量,静态变量属于整个类,而局部变量属于方法,只在该方法内有效,所以static不能修饰局部变量
ArrayList是线程不安全的:
主要原因是ArrayList是非同步
的,没有同步机制
,并且其底层实现是基于数组
,而数组的长度是固定
的。
当对 ArrayList 进行增删操作时,需要改变数组的长度,这就会导致多个线程可能同时操作同一个数组,从而引发线程安全问题。
内部类:在类的内部再定义一个类。
1. 成员内部类:
[修饰符] class 外部类{
[修饰符] class 内部类{ }
}
成员内部类特点:
1.不能使用static关键字,但是可以使用 static final关键字定义常量
2.内部类可以直接访问外部类的成员(包括私有成员)
3.内部类如果想要调用外部类的方法,需要使用 外部类名.this.外部方法名
4.可以使用final修饰内部类,表示不能被继承
5.编译以后也会有自己独立的字节码文件, Outer$Inner.class
6.外部函数的static成员的,不允许访问成员的内部类
7.可以在外部类里创建内部类对象,然后在通过方法返回,同事,也可以使用 new Outer().new Inner() 在外部直接访问内部类
2.静态内部类
静态内部类也被成为 嵌套类,静态内部类不会持有外部类对象的引用。
[修饰符] class 外部类{
[其他修饰符] static class 内部类{ }
}
静态内部类特点:
1. 使用static关键字修饰
2. 在静态内部类里,可以使用static关键字定义静态成员
3.只能访问外部的静态成员,不能访问外部的非静态成员
4. 外部类可以通过 静态内部类名.静态成员名 访问静态内部类的静态成员
静态内部类就像是一个单独的类,只不过借用了外部类的命名空间
3. 局部内部类
局部内部类定义在外部类的方法中,就像局部变量一样,并不是外部类的成员。
局部内部类在方法外是无法访问到的,但它的实例可以从方法中返回,并且实例在不在被引用之前会一直存在。
[修饰符] class 外部类{
[修饰符] 返回值类型 方法名([形参列表]){
[final/abstract] class 内部类{
}
}
}
局部内部类的特点:
- 定义在类的某个方法里,而不是直接定义在类里
- 局部内部类前面不能有权限修饰符
- 局部内部类里不能使用static声明变量
- 局部内部类访问外部类的静态成员
- 如果这个局部内部类所在的方法是静态的,它无法访问外部类的非静态成员
- 局部内部类可以访问外部函数的局部变量,但 这个局部变量必须要非final修饰。JDK8以后,final可以省略
匿名对象:
new Person().eat();
匿名对象的作用:如果一个对象只用一次可以尝试使用匿名对象,这样可以节省一些内存,没有东西指向它,优先被GC回收
匿名对象是指没有名字的对象。实际上,对于对象实例化操作来讲,对象真正有用的部分是在堆内存中,而栈内存中只是保存了一个对象的引用名称(严格来讲是对象在堆内存的地址),所谓匿名对象是指,只开辟了堆内存空间,而没有栈内存指向的对象。
匿名对象是只开辟了堆内存空间,没有栈内存指向的对象!
因为匿名对象没有栈内存指向,所以只能使用一次,之后就变成无法寻找的垃圾对象,会被垃圾回收器回收。
/** * Arrays.fill(arr, 2,4,6);//给第2位(0开始)到第4位(不包括)赋值6 * Arrays.fill(arr, 2,4,6);//给第2位(0开始)到第4位(不包括)赋值6 * Arrays.sort(strArray, Collections.reverseOrder()); * int[] arr = {3,2,1,5,4}; * Arrays.sort(arr,0,3);//给第0位(0开始)到第3位(不包括)排序 * String str = Arrays.toString(arr); // Arrays类的toString()方法能将数组中的内容全部打印出来 * Arrays.toString(); //将数组中的内容全部打印出来 * int[] arr1 = {1,2,3}; * int[] arr2 = {1,2,3}; * System.out.println(Arrays.equals(arr1,arr2)); * //输出:true * //如果是arr1.equals(arr2),则返回false,因为equals比较的是两个对象的地址,不是里面的数,而Arrays.equals重写了equals,所以,这里能比较元素是否相等。 * Arrays.binarySearch(); //二分查找法找指定元素的索引值(下标) * int[] arr = {10,20,30,40,50}; * System.out.println(Arrays.binarySearch(arr, 30)); * //输出:2 (下标索引值从0开始) * Arrays.copeOf() 和Arrays.copeOfRange(); //截取数组 * int[] arr = {10,20,30,40,50}; * int[] arr1 = Arrays.copyOf(arr, 3); * int []arr = {10,20,30,40,50}; * int []arr1 = Arrays.copyOfRange(arr,1,3); */
Comparator怎么理解呀 ?
Arrays.sort(nums,2,6);//对数组中指定的下标进行排序 ! 排序的长度是(6-2)! ! !
Arrays.sort(nums,Comparator<Integer>(){
public int compare(Integer o1,Integer o2) {
return o1 - o2;//Comparator是比较器,当compare中的参数的顺序与return 中的顺序相同的时候,是升序,当其顺序相反的时候是降序!
}
})
String split 这个方法默认返回一个数组, 如果没有找到分隔符,会把整个字符串当成一个长度为1的字符串数组 返回到结果, 所以此处结果就是1
String s = "今天是,2024年,12月";
String[] arr = s.split(",");//实现对字符串的剪切
"a|bc|8";对“|”进行剪切,这样表示:str.split(“\\|”); "2021年11月18日;英语,数学,语文;"剪切多个分隔符 str.split("[,;]");
"2018年11月18日abcd85gg688" 将数字作为分隔符进行剪切 str.split("\\d+");
System.out.println('A'+0);
这段代码的执行涉及到字符编码和数字转换。首先,'A'
是一个字符,代表ASCII码表中的大写字母A,其对应的十进制数值是65。当我们在字符串连接操作符+
两边使用字符和数字时,实际上是在进行字符和数字之间的隐式转换。
因为byte是有符号单字节整形,所以存储数字范围是[-128·127]
从理论上来讲,两个不同的对象通过 hashCode() 方法计算后的值可能相同。所以,不能使用 hashCode() 方法来判断两个对象是否相等,必须要通过 equals() 方法来判断。
构造函数是一种特殊类型的方法,它用于创建对象并对对象进行初始化。在 Java 中,构造函数的名称必须与类名完全相同,不返回任何值,且不能被显式调用,而是在创建对象时自动调用。构造函数的作用是初始化对象的状态,为对象的属性赋初值。
在 Java 中,当创建子类的实例时,子类的构造函数会隐式地调用父类的构造函数,以确保父类的状态得到正确的初始化。如果子类的构造函数没有显式调用父类的构造函数,编译器会自动插入对父类的无参构造函数的调用。这是为了确保父类在子类实例化时能够得到正确的初始化,也符合面向对象编程中继承的特性,确保子类能够正常地继承父类的属性和方法。
private修饰的成员变量,根据权限修饰符的访问控制范围,只有在类内部才能被访问
局部变量不像类变量那样存在准备阶段。即使程序员没有为类变量赋值也没有关系,它仍然有一个默认的初始值。但局部变量就不一样了,如果没有给它赋初始值,是不能使用的。
类对象整个内存只有一份,静态变量和静态方法属于类而不依赖与实例对象,所以必须通过类名引用。
静态成员变量和静态方法的生命周期与类的生命周期一致,而普通成员变量和普通方法的生命周期与该类的对象实例的生命周期相关。
从JDK1.8开始,方法区与堆内存开始共享同一个物理内存(但方法区是堆外内存)这种改变是为了提高内存利用率和性能。虽然堆内存和元空间在物理内存上不是完全共享的,但它们之间存在一些交互。例如,当一个对象实例调用一个静态方法时,JVM会查找该对象实例的类方法区(位于堆内存中的每个对象),然后跳转到元空间中相应的静态方法字节码(由该类的元数据定义),因此,从技术上讲,堆中的实例方法可以调用方法区(元空间)中的static静态方法,因为它们之间存在交互和共享。所以RayUser中的实例方法test1()可以调用静态方法test2(),即运行test1()不会报错。
通过 JVM 说明了实例方法可以访问静态方法,但还是不能说明实例对象可以调用static的变量和方法
通过字节码文件我们可以看出,编译器会帮忙转译为类名调用:编译器把user.age;
转化为 -> RayUser.age;
把user.test2();
转化为 -> RayUser.test2();
从底层角度:实例对象不能调用静态变量和静态方法❗️即使使用实例对象调用静态变量和方法,编译器也会帮忙转译为类名调用
❗️但会无谓增加编译器解析成本,所以即使能够编译且运行也不推荐这么做
Java中var是Java10版本新出的特性,用它来定义局部变量。
var只能在方法内定义变量,不允许定义类的成员变量。
var 定义变量必须赋初始值,------》以后不能在赋初始值。
Java是强类型语言,每个变量都有固定的变量类型。
Java 是一种强类型(Strongly Typed)语言。这意味着在 Java 中,每个变量都必须有预先定义的数据类型,且数据类型一旦定义后,在变量的生命周期内不能改变。编译器会检查数据类型,以确保它们用于预定的目的。
强类型的优点:
类型安全:编译器在编译时会进行类型检查,减少运行时错误的可能性。
可读性:由于数据类型都是明确指定的,这有助于提高代码的可读性和可维护性。
性能优化:由于类型是已知的,所以编译器可以进行更有效的代码优化。
尽管 Java 是强类型语言,你仍然可以进行明确的类型转换(也称为强制类型转换或类型铸造),但这需要使用特定的语法。
while(i < j && nums[i] == nums[++i]);
这段代码是一个循环条件,它的作用是在一个数组 nums 中,从索引 i 开始,找到第一个不等于 nums[i] 的元素的索引。当 i 小于 j 且 nums[i] 等于 nums[++i] 时,循环继续执行。这里的 ++i 表示先将 i 加 1,然后返回新的 i 值。
编译阶段:报错
运行阶段:报错
真有意思 !
泛型:
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参列表,普通方法的形参列表中,每个形参的数据类型是确定的,而变量是一个参数。在调用普通方法时需要传入对应形参数据类型的变量(实参),若传入的实参与形参定义的数据类型不匹配,则会报错。
参数化类型
是:以方法的定义为例,在方法定义时,将方法签名中的形参的数据类型
也设置为参数(也可称之为类型参数),在调用该方法时再从外部传入一个具体的数据类型和变量。
泛型的本质是为了将类型参数化, 也就是说在泛型使用过程中,数据类型被设置为一个参数,在使用时再从外部传入一个数据类型;而一旦传入了具体的数据类型后,传入变量(实参)的数据类型如果不匹配,编译器就会直接报错。这种参数化类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
map.computeIfAbsent()方法:
Map<String,Integer> map = new HashMap<>();
String key = "key";
Integer value = map.computeIfAbsent(key , k -> key.length());
如果map集合中存在key,那么这个方法的返回值就是key对应的value;如果没有对应的key,则会自动创建key值,并把key.length()作为value值!而返回值也是这个key对应的value值!
List接口继承了Collection接口!
map.values()的返回值是Conllection<Integer>
computeIfAbsent 方法是 Map 接口中的一个方法,它可以用于在执行某个键的映射值不存在时,通过提供的函数计算并将其放入到 Map 中。相比于传统的 if-else 语句,使用 computeIfAbsent 可以提供更简洁和优雅的代码。
使用 computeIfAbsent 可以避免显式的 if-else 逻辑,通过传递一个 lambda 函数来自动处理键不存在的情况。这种方式允许我们将逻辑集中在一个地方,提高代码的可读性和可维护性。
List集合
在Java中,ArrayList是一个使用非常频繁的集合类型,它的底层是Object数组,所以它拥有数组所拥有的特性。ArrayList在调用无参构造方法时创建的是一个0的空数组,当调用add()方法添加元素时,ArrayList才会触发扩容机制。
初始值是10。
ArrayList每次扩容为旧容量的1.5倍。
Set集合
set集合是无序的,也就是说,你往set集合中,按照一定的顺序添加的数据,当你输出的时候,是无序的!
set.toArray()方法可以将set集合转换为数组,这个方法中的可以加入对应的参数,
set.toArray(new String[0])是将set集合(List集合同样适用)转换为对应的String数组。
toArray()方法在底层会比较集合的长度与数组的长度两者的大小。
如果集合的长度>数组的长度:数据在数组中放不下,此时会根据实际数据的个数,重新创建数组
如果集合的长度<=数组的长度:数据在数组中放得下,此时不会创建新的数组,而是直接使用
Map集合
map.keySet()方法
Map.of()方法(Map的不可变集合,它的键是不可重复的)中的参数是有上限的,最多只能有20个参数,10个键值对!
Map.ofEntries()方法也是创建不可变集合的方法!这个方法的参数是Map.Entry类型的数组!
Map.copyOf(map),是一种更为简洁的,用来创建不可变map集合的方法!
Map.Entry和String一样,都是一种类型的名字,只不过Map.entry是静态内部类。
java.util.HashMap
是 Java 集合框架中的一个常用实现类,实现了 Map
接口。
map集合的一些用法 ,map集合的遍历方式还是很特别的
map.forEach((k,v)-> System.out.println(k+"-->"+v));
Stream流
stream流的作用:结合了Lambda表达式,简化集合、数组的操作
Stream流中间方法:方法调用完毕后,还可以调用其他方法
Stream流的终结方法:
list.stream().forEach(System.out::println);好像是被 list.forEach(System.out::println);取代了,也就是说此时可以不用谢stream()了!forEach方法的参数类型是Consumer函数式接口,可以用lambda表达式!
Stream接口中静态方法of的细节
方法的形参是一个可变参数,可以传递一堆零散的数据,也可以传递数组
但是数组必须是引用数据类型,如果传递的是基本上数据类型,是会把整个数组当成一个元素放到Stream当中。
Stream流的中间方法
注意1:中间方法,返回新的Stream流,原来的Stream流只能使用一次,建议使用链式编程
注意2:修改Stream流中的数据,不会影响原来集合或者数组中的数据
filter方法中的参数是一个函数式接口,匿名内部类是要重写一个返回值为boolean类型的方法,如果返回值为true,表示当前数据留下;如果返回值为false,表示当前数据舍弃不要!
Stream.concat(list.stream(),list2.stream()).forEach(System.out::println);
map方法中的函数式接口中的两个泛型参数,
第一个类型:流中原本的数据类型
第二个类型:要转成之后的类型
apply的形参s:依次表示流里面的每一个数据
返回值:表示转换之后的数据
list.stream().map(new Function<String, Integer>() { @Override public Integer apply(String s) { String[] split = s.split("-"); return Integer.valueOf(split[1]); } }).forEach(System.out::println);
toArray()方法的参数的作用:负责创建一个指定类型的数组
toArray()方法的底层,会依次得到流里面的每一个数据,并把数据放到数组当中
toArray()方法的返回值:是一个装着流里面所有数据的数组
String[] array = list.stream().toArray(new IntFunction<String[]>() { @Override public String[] apply(int value) { return new String[value]; } });
apply的形参:流中数据的个数,要跟数组的长度保持一致!
list.toArray()方法和list.stream().toArray()方法的形参的类型是不一样的!
list.stream().toArray()方法中可以不写入参数!
Map<String, String> map1 = list.stream().filter(s -> s.startsWith("张")) .collect(Collectors.toMap(new Function<String, String>() { @Override public String apply(String s) { return s.split("-")[0]; } }, new Function<String, String>() { @Override public String apply(String s) { return s.split("-")[1]; } }));
toMap:参数一表示键的生成规则;参数二表示值的生成规则
Function:
泛型一:表示流中每一个数据的类型
泛型二:表示Map集合中键的数据类型
HashTable、HashMap是啥???
Java基础篇:什么是hashCode 以及 hashCode()与equals()的联系-CSDN博客
在Java中,当你将一个对象作为HashMap的键时,实际上是使用该对象的hashCode()和equals()方法来确定键的唯一性。在这个例子中,list1和list2的内容相同,所以它们的hashCode()值也相同。然而,它们是不同的对象,因此它们的内存地址不同。
当你调用map1.containsKey(list2)时,HashMap会首先计算list2的hashCode()值,然后在内部数组中找到对应的桶(bucket)。由于list1和list2具有相同的hashCode()值,它们会被映射到同一个桶中。然后,HashMap会遍历这个桶中的所有键,使用equals()方法来检查是否有与list2相等的键。在这种情况下,由于list1和list2的内容相同,equals()方法会返回true,因此containsKey(list2)返回true。
需要注意的是,虽然list1和list2的内容相同,但它们是两个不同的对象,它们的内存地址是不同的。这就是为什么输出结果为true的原因。
在Java中,对象的hashCode()方法用于计算对象的哈希值。哈希值是一个整数,通常用于确定对象在哈希表中的位置。当两个对象相等时(即它们的equals()方法返回true),它们应该有相同的哈希值。
在你的例子中,list1和list2具有相同的内容,因此它们的equals()方法返回true。然而,由于它们是两个不同的对象,它们的内存地址是不同的。因此,它们的hashCode()方法可能会返回不同的值。但是,由于它们的内容相同,所以它们的hashCode()方法可能仍然返回相同的值。所以结果的返回值true是一个意外吗?
需要注意的是,hashCode()方法的设计目标是尽量减少不同对象之间的哈希冲突,而不是确保每个不同的对象都具有唯一的哈希值。因此,即使两个对象的内容相同,它们的哈希值也可能不同。
hashCode相等,equals也不一定相等, 两个类也不一定相等
equals相同, 说明是同一个对象, 那么hashCode一定相同
在Java中任何一个对象都具备equals(Object obj)和hashcode()这两个方法,因为他们是在Object类中定义的。
equals(Object obj)方法用来判断两个对象是否“相同”,如果“相同”则返回true,否则返回false。 hashcode()方法返回一个int数,在Object类中的默认实现是“将该对象的内部地址转换成一个整数返回”。
规范1:若重写equals(Object obj)方法,有必要重写hashcode()方法,确保通过equals(Object obj)方法判断结果为true的两个对象具备相等的hashcode()返回值。说得简单点就是:“如果两个对象相同,那么他们的hashcode应该 相等”。不过请注意:这个只是规范,如果你非要写一个类让equals(Object obj)返回true而hashcode()返回两个不相等的值,编译和运行都是不会报错的。不过这样违反了Java规范,程序也就埋下了BUG。
规范2:如果equals(Object obj)返回false,即两个对象“不相同”,并不要求对这两个对象调用hashcode()方法得到两个不相同的数。说的简单点就是:“如果两个对象不相同,他们的hashcode可能相同”。 根据这两个规范,可以得到如下推论: 1、如果两个对象equals,Java运行时环境会认为他们的hashcode一定相等。 2、如果两个对象不equals,他们的hashcode有可能相等。 3、如果两个对象hashcode相等,他们不一定equals。 4、如果两个对象hashcode不相等,他们一定不equals。
1.hashcode相等两个类一定相等吗?equals呢?相反呢? 2. java中hashcode和equals的区别和联系_hashcode相等equals一定相等吗-CSDN博客
在Java中,List集合的hashCode()方法是基于其元素的hashCode值计算得到的。如果两个List集合具有相同的元素(包括顺序),那么它们的hashCode()方法应该返回相同的值。
这是因为List集合的hashCode()方法遵循以下规则:
- 初始值为1。
- 对于每个元素e,将其hashCode值乘以31并加到当前哈希值上。
- 返回最终的哈希值。
由于相同内容的List集合包含相同的元素,因此它们的元素具有相同的hashCode值。因此,根据上述规则,两个相同内容的List集合将具有相同的hashCode值。
需要注意的是,这种相等性检查仅适用于内容相同的List集合。如果两个List集合的元素相同但顺序不同,它们的hashCode值可能仍然不同,因为List集合的顺序也会影响其hashCode值的计算。
在map集合中,数组是不能作为key的!
Map在进行put的时候,如果key作为以数组或其他非字符串为键的时候,java内部是视为其Object类型的,因此put到内存中的时候,它存在于一个具体的地址。
Java使用数组作为Map的key_java map的key可以是数组吗-CSDN博客
Map中使用数组作为key的用法_map的键是数组-CSDN博客
ArrayList<Object> list = new ArrayList<>();这个集合不仅能add("字符串"),而且还能add(1)数字!这个Object我也是真的服了 !
JAVA关键字都是小写。
while()中表达式的判断,在C语言中大于0的int值都会被认为是true,而java中没有这个机制,必须是boolean类型的
导包只可以导到当前层,不可以再导入包里面的包中的类。
String是不可修改的,且java运行环境中对string对象有一个常量池保存。
数组的插入和删除效率比较低,读取的效率高,因为地址是连续的。
标识符可以包括这4种字符:字母、下划线、$、数字;开头不能是数字;不能是关键字
。
Java中的char类型采用Unicode编码,可以表示所有的Unicode字符;而C++中的char类型采用ASCII编码或者扩展的ASCII编码,只能表示部分字符。
java内部都是用unicode的,所以java其实是支持中文变量名的,比如string 世界 = "我的世界";这样的语句是可以通过的。
Unicode编码是一种计算机字符编码标准,而UTF-8 UTF-16 UTF-32是Unicode的具体实现(怎么存储在计算机)。
Unicode的优势主要在于对网络方面,宽字符能够更好地支持跨平台数据传递。
Java文件的编码可能有多种多样,但是Java编译器会自动将这些编码按照Java文件的编码格式正确读取后产生class文件,这里的class文件编码是Unicode编码。
JVM加载class文件时,使用Unicode编码方式读取class文件。值得注意的是:Java程序通过JVM实现了跨平台的特性。
Java语言中,中文字符所占的字节数取决于字符的编码方式,一般情况下,采用ISO8859-1编码方式时,一个中文字符与一个英文字符一样只占1个字节;采用GB2312或GBK编码方式时,一个中文字符占2个字节;而采用UTF-8编码方式时,一个中文字符会占3个字节。
三元操作符如果遇到可以转换为数字的类型,会自动类型提升。
基本数据类型
在Java中,如果你输入一个小数,系统默认的是double类型的
对于基本数据类型的变量,可以使用相应的运算符进行比较,例如,可以使用“==”运算符来判断两个整型变量是否相等,使用“==”运算符来判断两个浮点型变量是否相等。
float(单精度浮点数有效数字8位)、double(双精度浮点数有效数字16位)
byte 1个字节
智能硬件的数据传输大部分协议都是按字节一位一位来解析的,对于字节的运算十分频繁,如果不对byte研究透彻,就很容易犯一些特别基础的错误。
byte类型进行变量之间的计算时,是会将类型提升到int类型再进行计算的。
byte类型的值与int类型的值之间的比较问题:
尽管字节型的10在Java中并不完全等于10(在内存中可能会被表示为01100100二进制),但编译器和运行时系统会将其视为等同的整数值。
当使用 ==
运算符比较两个对象时,如果它们的数据类型不同,Java会先将两边的值提升到相同的类型再进行比较。对于数字来说,这种提升不会影响原始值的比较。
* 表达式的数据类型自动提升, 关于类型的自动提升,注意下面的规则。 ①所有的byte,short,char型的值将被提升为int型; ②如果有一个操作数是long型,计算结果是long型; ③如果有一个操作数是float型,计算结果是float型; ④如果有一个操作数是double型,计算结果是double型;
float 4个字节
double 8个字节
boolean 1个字节
boolean的默认值是false。
int(整型)、short(短整型)、long(长整型)
- Java中的四类八种基本数据类型
- 第一类,整数类型:byte、short int long
- 第二类:浮点型:float、double
- 第三类:逻辑型:boolean
- 第四类:字符型:char
Java中的byte,short,char进行计算时都会提升为int类型。
Java中的char是Unicode编码。
Unicode编码占两个字节,就是16位,足够存储一个汉字。
包装类
Integer e = Integer.valueOf(59);与Integer e = 59;等价。
方法 | 描述 |
valueOf(String s) | 返回一个Integer物体保持在指定的值String |
valueOf(int i) | 返回表示指定的int值的Integer实例 |
toString() | 返回表示此Integer值的String对象 |
toString(int i) | 返回指定整数的String对象 |
parseInt(String s) | 将字符串参数解析为带符号的十进制整数(int num = Integer.parseInt("12");) |
intValue() | 返回此值Integer为int |
hashCode() | 返回此Integer的哈希码 |
Integer中的常用方法
Java是一门面向对象语言,但在JAVA中不能定义基本数据类型的对象,为了能将基本数据类型视为对象进行处理,JAVA提出了包装类的概念,他主要是将基本数据类型封装在包装类中。
Integer类最常用的方法就是将表示数字的字符串转换为数字(int)类型的parseInt()。
valueOf(String str)返回值类型是Integer。
Integer a =100 ;
Integer b =100 ;
Integer c =1000 ;
Integer d =1000 ;
System.out.println(a == b);//true
System.out.println(c == d);//false
两个Integer类型进行 “==” 比较,如果其值在-128至127,那么返回true,否则返回false, 这跟Integer.valueOf()的缓冲对象有关。
双等号“ == ”和equals()方法的不同之处:
双等号“ == ”,对于基本数据类型,比较的是它们的值。
对于非基本类型,比较的是它们在内存中的存放地址,或者说是比较两个引用是否引用内存中的同一个对象。
equals()是在Object基类中定义的方法。
这个方法的初始行为是比较对象的内存地址,但在一些类库中,这个方法被覆盖掉了,如String,Integer,Date等。在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。
对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,它们之间的比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号“ == ”进行比较的,所以比较后的结果跟双等号“ == ”的结果相同。
Integer a = 1000; Integer b = 1000;因为a,b的值大于127,不在[-128, 127]范围内,所以虚拟机会在堆中重新new一个 Integer对象来存放1000,创建两个对象就会产生两个这样的空间。两个空间的地址不同,返回到栈中的引用的值也就不同,所以这时候a == b返回false。
装箱、拆箱操作发生在:引用类型与值类型之间。
基本数据类型转化成包装类是装箱 (如: int --> Integer)。
包装类转化成基本数据类型就是拆箱 (如:Integer --> int)。
Integer i =7;
String s = String.valueOf(i);
System.out.println(s);
System.out.println(s.getClass().getSimpleName());
System.out.println(i.getClass().getSimpleName());
//判断某一变量的数据类型(用注解的形式)。
集合
List接口和Set接口都继承了Collection接口。
Map接口和Collection接口是相互独立的。
心得:虽然map是以键值对的形式来存储数据的,其实数组也是以“键值对”的形式存储数据的,只是它的键是数组的下标。map集合好像是一种单纯的映射关系。
方法 | 描述 |
clear() | 从此映射中删除所有映射(可选操作) |
containsKey(Object key) | 如果此映射包含指定键的映射,则返回true |
containsValue(Object value) | 如果此映射将一个或多个键映射到指定值,则返回true |
get(Object o) | 返回指定键映射到的值,如果此映射不包含键的映射,则返回null |
isEmpty() | 如果此映射不包含键-值,则返回true |
keySet() | 返回此映射中包含的键的Set视图 |
put(K key, V value) | 将指定的值与此映射中的指定键相关联(可选操作) |
putAll(Map<k,v> m) | 将指定映射中的所有映射复制到此映射(可选操作) |
map集合中常用的方法
HashMap可以存null键和null值:
为什么HashMap的键值可以为null,而ConcurrentHashMap不行?_concurrenthashmap为什么不能存空值-CSDN博客
list集合的一些用法 String []a = Arrays.copyOfRange(list2.toArray(), 0, 2,String[].class); 这段代码可以将list、set集合转换为任意类型的数组。 int[] arr = {1,2,3}; List list = Arrays.stream(arr).boxed().toList(); //将基本数据类型的数组转换为list集合 String[] arr = {"a","b","c"}; List list = Arrays.asList(arr); System.out.println(list); //将封装类型数组转换为list集合。
/*
* Arrays.asList()用于将数组转换为集合,不能转换基本类型的数组
* 可以使用stream流强制转换 Arrays.stream(arr).boxed()
* 转换之后的集合无法增删元素,转换之后的list,对原始数组的修改会影响转换之后的list,asList方法返回的list并不是java.util包下的arrayList,
* 可以用java.util包下的arrayList来存放转换之后的list即可 List list = new ArrayList(Arrays.asList(arr));
* 比如像这样:List list = Arrays.asList(str);
* */
Collections.sort(list, new Comparator<String>(){
public int compare(String o1, String o2){
return o1.compareTo(o2);
}
});
//lambda表达式的简便形式:
Collections.sort(list, (o1, o2)->o1.compareTo(o2));
list.forEach(System.out::println);
//----------------------------------------------------------------------------------
List<String> listaa = new ArrayList<>();
for(String s:list){
if(s.startsWith("a")){
listaa.add(s.toUpperCase());
}
}
Collections.sort(listaa);
System.out.println(listaa);
List<String> lista = list.stream().filter(s->s.startsWith("a"))
.map(String::toUpperCase).sorted().collect(Collectors.toList());
lista.forEach(System.out::println);
System.out.println(lista);
//-------------------------------------------------------------------------------------------
List<Integer> list = Arrays.asList(1,2,3,4,5);
int sum = list.stream().reduce(0, (a,b)-> a+b);
System.out.println(sum);
接口中的属性问题
java1.8 default修饰方法
default加入就是为了解决接口中不能有默认方法的问题,在实现类中可以重写这个default方法也可以不重写。
在接口中定义属性的知识点:
1,属性默认的修饰词是:public static final
2,定义的时候必须初始化。
3,在接口中定义的属性 等同于 常量,接口中不允许定义变量;
接口中的变量默认的修饰词:public static final
String name = "chenchaoyang";
相当于public static final String name1 = "chenchaoyang";
super关键字
子类中有与父类的同名属性和方法,方法重写时使用super来调用父类的属性和方法。
如果子类的构造方法中既没有显式地调用父类的构造方法,也没有通过this调用同一类中其他重载的构造方法(该方法中也没有显式地调用父类的构造方法),那么系统会默认调用super(),因此,为了避免在子类的构造方法中产生异常,建议父类中必须提供空的构造方法。
方法重写
在子类中定义的方法与父类的方法在方法名,方法参数的个数、类型和顺序,返回类型(也可以是子类)上全部一致时,被称为方法重写。
- 重写方法必须和被重写的方法具有相同的方法名、参数列表、返回类型(也可以是子类)
- 重写方法不能使用比被重写方法更严格的访问权限。
子类方法的访问修饰符(如 public、protected、default、private)的范围必须大于或等于父类方法的访问修饰符,这是为了确保子类可以访问到父类中的方法。如果子类方法的访问修饰符范围小于父类方法,则可能会造成访问权限不足的问题。
- 重写方法不能声明抛出比被重写方法范围更大的异常。
子类方法声明抛出的异常类型必须是父类方法声明抛出的异常类型的子类,或者与父类方法声明的异常类型相同。这是为了确保对于多态调用时,程序能够正确地捕获和处理异常。如果子类方法声明抛出的异常范围大于父类方法,则可能导致捕获不到异常或发生异常处理错误。
- 重写发生在子、父类之间,同一类中的方法只能被重载,不能被重写。
如果不能继承一个方法,则不能重写这个方法。
- 方法重写严格把握五点:
- 三同、一大、一小。具体内容以及与方法重载的区别见下:
- 方法重写
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
- 此为一大;
- 父类的成员方法只能被它的子类重写。
- 声明为static的方法不能被重写,但是能够被再次声明。
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 此为一小;
构造方法不能被重写。
若子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法,如需父类中原有的方法则可使用 super 关键字。
static修饰符
被static修饰的变量称为静态变量,静态变量属于整个类,而局部变量属于方法,只在该方法内有效,所以static不能修饰局部变量
Java中静态变量只能在类主体中定义,不能在方法中定义。 静态变量属于类所有而不属于方法。 //静态成员共享相同的内存空间
使用static修饰类中的成员和方法后,该属性或方法就成为了类的属性和方法。
static修饰类的属性或方法也称为类成员(类变量、静态成员)或类方法(静态方法)。
类方法是静态方法,实例方法是通过new创建实例调用的方法。
在类方法中调用本类的类方法可直接调用。 实例方法也叫做对象方法。 类方法是属于整个类的,而实例方法是属于类的某个对象的。 由于类方法是属于整个类的,并不属于类的哪个对象,所以类方法的方法体中不能有与类的对象有关的内容。即类方法体有如下限制: (1) 类方法中不能引用对象变量; (2) 类方法中不能调用类的对象方法; (3) 在类方法中不能使用super、this关键字。 (4)类方法不能被覆盖。 如果违反这些限制,就会导致程序编译错误。 与类方法相比,对象方法几乎没有什么限制: (1) 对象方法中可以引用对象变量,也可以引用类变量; (2) 对象方法中可以调用类方法; (3) 对象方法中可以使用super、this关键字。
父类静态方法不能被子类重写,而是被隐藏!
static方法不能被子类覆写,在子类中定义了和父类完全相同的static方法,则父类的static方法被隐藏,Son.staticmethod()或new Son().staticmethod()都是调用的子类的static方法,如果是Father.staticmethod()或者Father f = new Son(); f.staticmethod()调用的都是父类的static方法。
静态属性由整个类(包括该类的所有实例)共享,不需要依赖某个具体的对象而存在,实际就是一个共享的变量。
可以直接由类调用,而不需要实例化对象进行调用,这就是静态方法。
在JAVA中,静态代码块是在类加载时执行的,而不是在每次创建对象时执行的。当类被加载时,静态代码块会按照在类中出现的顺序被执行一次。这意味着无论创建多少个对象,静态代码块只会执行一次。
当类被初次加载时,静态代码块会被执行。类的加载通常发生在使用该类之前,例如创建对象实例、调用静态方法或访问静态变量时。
final修饰符
对于类、方法来说,abstract关键字和final关键字不能同时使用,因为作用相互矛盾
需要注意的一点是:因为重写的前提是子类可以从父类中继承此方法,如果父类中final修饰的方法同时访问控制权限为private,将会导致子类中不能直接继承到此方法,因此,此时可以在子类中定义相同的方法名和参数,此时不再产生重写与final的矛盾,而是在子类中重新定义了新的方法。
当final关键字用于修饰局部变量的时候,这个局部变量就不能更改
对于 基本类型 来说,不可改变指的是变量当中的数据不可改变,但是对于 引用类型 来说,不可改变的指的是变量当中的地址值不可改变
final修饰的变量使其成为了符号常量,只能赋值1次,并且只能在声明的同时或在构造方法中显式赋值,才能使用。
final不允许修饰构造方法及抽象方法。
被final修饰的变量,是java定义一个常量的基本形式。
instance运算符是在运行时指出引用变量所指向的对象是否是特定类型的一个实例。instanceof运算符的运算结果返回一个布尔值,指出一个引用变量所指向的对象是否是指定类型或它的子类型的一个实例,也可以说指定类型名是否是这个引用变量所指向对象的所属类型或所属类型的父类型。
IO操作流
根据流操作的数据单位不同,流可以分为字节流和字符流。字节流以字节为单位进行数据的读写。
以InputStream或OutputStream结尾的流为字节流,以Reader或Writer结尾的流为字符流。
方法 | 说明 |
public abstract void write(int b) | 将方法整数参数的二进制最低8位(第一个字节)写入输出流中 |
public void write(byte[] b) | 将字节数组b中的所有字节写入输出流中 |
public void write(byte[] b,int off,int len) | 将字节数组b中从偏移量off开始的len个字节的数据写入输出流中 |
public void flush() | 刷新输出流,强制缓冲区中的输出字节写入输出流中 |
public void close() | 关闭输出流,释放和这个流有关的系统资源 |
OutputStream类中的主要方法
方法 | 说明 |
public abstract int read() | 从输入流中读取下一个字节数据。将字节值转换为0~255之间的无符号整数返回,如果返回-1,表示读到了输入流的末尾。 |
public int read(byte[] b) | 从输入流中读取一定数目的字节数据,存放在一个缓冲字节数组,同时以一个整数形式返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。 |
public int read(byte[] b,int off, int len) | 从输入流中读取一定数目的字节数据,存放在一个缓冲字节数组,同时以一个整数形式返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。off指定在数组b中存放数据的起始偏移位置;len指定读取的最大字节数。 |
public long skip(long n) | 在输入流中跳过n个字节,并返回实际跳过的字节数。 |
do while循环
条件表达式出现在循环的尾部,所以循环中的 statement(s) 会在条件被测试之前至少执行一次。
优先队列
PriorityQueue<Integer> queue = new PriorityQueue<Integer>((p1,p2)->p2-p1); //从大到小排序(lambda表达式)
其表示为:
PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() {
public int compare(Integer o1, Integer o2){
return o2 - o1;
}
})
双端队列
线性集合,支持两端插入和移除元素的方法。
反射
加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称为反射。(JVM知识)
mock对象:
package com.test; import java.io.InputStream; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class AJava { public static void main(String[] args) throws IOException { AJava a = new AJava(); ClassLoader cl = a.getClass().getClassLoader(); InputStream in = cl.getResourceAsStream("1.txt"); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line).append(System.lineSeparator()); } System.out.println(sb.toString()); reader.close(); } } package com.first; import org.apache.commons.io.IOUtils; import java.io.InputStream; /** * Created by Yifan Jia on 2018/5/5. */ public class BJava { public static void main(String[] args) throws Exception { BJava b = new BJava(); Class bClass = b.getClass(); InputStream in = bClass.getResourceAsStream("/1.txt"); String s = IOUtils.toString(in); System.out.println(s); } } 类装载器负责从Java字符文件将字符流读入内存,并构造Class类对象,所以通过它可以得到一个文件的输入流。 装载类的过程非常简单:查找类所在位置,并将找到的Java类的字节码装入内存,生成对应的Class对象。
1、使用ClassLoader加载文件时,在getResourceAsStream("")中填入的路径是相对于
classes/文件下的,比如我们写一个getResourceAsStream("1.txt"),其实对应的是盘符:…\项目名\target\classes\1.txt文件。(如果是eclipse则是在相应的classes下)
2、使用Class加载文件时,在getResourceAsStream("")中填入的路径是相对于
当前的.class文件所在的目录,比如我们写一个getResourceAsStream("1.txt"),其实对应的是盘符:…\项目名\target\classes\com\first\1.txt文件,在上面的例子中,应为改路径下没有1.txt文件,所有会提示错误。但是当我们在文件前加一个\符号时getResourceAsStream("/1.txt"),就会变成和使用ClassLoader一样,变成相对于
classes/文件,所以第二次就加载成功了。
public class Test { public static void main(String[] args) { StringBuilder sb = new StringBuilder(); Class cl = sb.getClass(); // It returns an array of interfaces represented // by the class StringBuilder Class[] interfaces = cl.getInterfaces(); for (int i = 0; i < interfaces.length; ++i) { System.out.print("StringBuilder Interfaces: "); System.out.println(interfaces[i].toString()); } } } 输出结果: StringBuilder Interfaces: interface java.io.Serializable StringBuilder Interfaces: interface java.lang.Comparable StringBuilder Interfaces: interface java.lang.CharSequence
总之,Proxy.newProxyInstance() 方法用于创建动态代理对象,并将方法调用转发给指定的 InvocationHandler 处理。
invoke()
在InvocationHandler接口中的invoke()方法中,有三个参数:
Object proxy:代理对象本身。在 invoke() 方法中,可以使用 proxy 参数来调用代理对象的其他方法,或者在特定情况下使用代理对象本身。
Method method:被调用的方法对象。method 参数表示当前正在调用的方法,通过它可以获取方法的名称、参数等信息。
Object[] args:方法的参数数组。args 参数是一个对象数组,表示传递给方法的参数。通过这个数组,可以获取方法调用时传递的具体参数值。
总结起来,invoke() 方法中的三个参数的含义如下:
proxy 参数:代理对象本身,可以使用它调用代理对象的其他方法。
method 参数:被调用的方法对象,可以通过它获取方法的相关信息。
args 参数:方法的参数数组,可以获取方法调用时传递的具体参数值。
————————————————
通过在 invoke()方法中使用这些参数,我们可以在代理对象的方法
被调用时,实现自定义的逻辑和行为,
例如在方法调用前后添加日志、执行额外的操作、拦截方法调用等。
Lambda表达式
而在JDK8之后增加了一种语言特性,Lambda表达式。Lambda表达式为Java添加了缺失的函数式编程特点。而Lambda表达式是对象,它依赖于一个特别的类型-----函数式接口。
也就是说lambda表达式和函数式接口有很大的联系,和匿名内部类之间的联系也很大!
Lambda表达式表示函数,可以用于替代某些匿名内部类对象,从而让程序更简洁,可读性更好。
Lambda表达式只能替代函数式接口的匿名内部类。
Lambda表达式语法:()- > {}
一个Lambda表达式可以有零个或多个参数;
参数的类型既可以明确声明,也可以根据上下文来推断。例如:(int a)与(a)效果相同;
所有参数需包含在圆括号内,参数之间用逗号相隔。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c);
空圆括号代表参数集为空。例如:() -> 42;
当只有一个参数,且其类型可推导时,圆括号()可省略。例如:a -> return a*a;
Lambda表达式的主体可包含零条或多条语句;
如果Lambda表达式的主体只有一条语句,花括号{}可省略。匿名函数的返回类型与该主体表达式一致;
如果Lambda表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空。
Java | 函数式接口与Lambda表达式之间微妙的关系_lamda表达式依赖对象吗-CSDN博客
URLClassLoader的使用
类加载器,