26. == 与 equals(重要)
== :它的作用 是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型 == 比较的是值,引用数据类型 == 比较的是 内存地址)
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()的作用就是获取哈希码,也称为散列码;它实际上是返回一个 int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()在散列表中才有用,在其它情况下没用。在散列表中 hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。
hashCode()与 equals()的相关规定
更多阅读:Java hashCode() 和 equals()的若干问题解答
在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。 它用来描述各种程序设计语言(不只是 Java)中方法参数传递方式。
Java 程序设计语言采用按值调用。也就是说,方法得到的是所有参数的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容;
下面通过3个例子说明:
例1:
/**
* @author silentCow
* @Date 2020/8/30 9:01
* 值传递
*/
public class ZCD {
public static void main(String[] args) {
int num1 = 100;
int num2 = 200;
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=200
b=100
num1=100
num2=200
解析:
在 swap 方法中,a、b的值进行交换,并不会影响到num1、num2,因为,a、b中的值,只是从num1、num2中复制过来的,也就是说,a、b 相当于 num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。
通过上面的例子,我们知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看例2。
例2:
/**
* @author silentCow
* @Date 2020/8/30 9:23
*/
public class ZCD2 {
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
System.out.println(array[0]);
change(array);
System.out.println(array[0]);
}
public static void change(int[] arr) {
// 将数组的第一个元素变为0
arr[0] = 0;
}
}
输出结果:
1
0
解析:
arr 被初始化 array 的拷贝也就是一个对象的引用,也就是说 arr 和 array 指向的是同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。
通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。
很多程序设计语言(特别是,C++和 Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员认为 Java 程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。
例3
/**
* @author silentCow
* @Date 2020/8/30 9:42
*/
public class ZCD3 {
public static void main(String[] args) {
Student s1 = new Student("小王");
Student s2 = new Student("小红");
swap(s1, s2);
System.out.println("s1:" + s1.getName());
System.out.println("s2:" + s2.getName());
}
public static void swap(Student a, Student b) {
Student temp = a;
a = b;
b = temp;
System.out.println("a:" + a.getName());
System.out.println("b:" + b.getName());
}
}
输出结果:
a:小红
b:小王
s1:小王
s2:小红
解析:
交换之前:
交换之后:
通过上面两张图可以很清晰的看出: 方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap 方法的参数 a和 b 被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝
总结
Java 程序设计语言对对象采用的不是引用调用。实际上,对象引用是按值调用的。
Java中方法参数的使用情况:
线程:与进程相似,但线程是 一个比进程更小的执行单位。一个进程在执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作的时候,负担要比进程小的多。也正因如此,线程被称之为轻量级进程。
程序:是含有指令和数据的文件,被存储在磁盘或其它的数据存储设备中,也就是说程序是静态的代码。
进程:是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建、运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接一个指令的执行着,同时,每个进程还占用某些系统资源,如CPU、内存空间、文件、输入输出设备的使用权等。换句话说,当程序 在执行中,将会被操作系统载入内存中。
线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是相互独立的,而各线程不一定,因为同一进程中的线程极有可能相互影响。从另一个角度来看,进程属于操作系统的范畴,主要是同一时间段内,可以执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态(图源《Java并发编程艺术》4.1.4 节)。
线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示(图源《Java 并发编程艺术》4.1.4 节):
由上图可以看出:
线程创建之后它将处于NEW(新建)状态,调用start()方法后开始运行,线程这时候处于READY(可运行)状态。可运行状态的线程获得了 cpu 时间片(timeslice)后就处于RUNNING(运行)状态。
操作系统隐藏 Java 虚拟机(JVM)中的 READY 和 RUNNING 状态,它只能看到 RUNNABLE 状态,所以 Java 系统一般将这两个状态统称为RUNNABLE(运行中)状态。
当线程执行wait()方法之后,线程进入WAITING(等待)状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而TIME_WAITING(超时等待)状态相当于在等待状态的基础上增加了超时限制,比如通过sleep(long millis)方法或wait(long millis)方法可以将 Java线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到BLOCKED(阻塞)状态。线程在执行Runnable 的run()方法之后将会进入到TERMINATED(终止)状态。
final 关键字主要用在三个地方:变量、方法、类
在 Java 中,所有的异常都有一个共同的祖先 java.lang 包含的 Throwable 类。
Throwable:有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。
Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java 虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如 Java 虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过 Error 的子类描述。
Exception(异常):是程序本身可以处理的异常。Exception 类有一个重要的子类 RuntimeException。RuntimeException 异常由 Java虚拟机抛出。NullPointerException(要访问的变量没有引用任何对象时,抛出该异常)、ArithmeticException(算术运算异常,一个整数除以 0时,抛出该异常)和ArrayIndexOutOfBoundsException(下标越界异常)。
注意:异常和错误的区别:异常能被程序本身处理,错误是无法处理。
在以下 4 种特殊情况下,finally 块不会被执行:
注意:当 try 语句和 finally 语句中都有 return 语句时,在方法返回之前,finally 语句的内容将被执行,并且 finally 语句的返回值将会覆盖原始的返回值。如下:
public static int f(intvalue) {
try {
returnvalue * value;
} finally {
if (valueWX2) {
return0;
}
}
}
如果调用f(2)
,返回值将是 0,因为 finally 语句的返回值覆盖了 try 语句块的返回值。
对于不想进行序列化的变量,使用 transient 关键字修饰。
transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。
方法1:通过 Scanner
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();
方法2:通过 BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s=input.readLine();
Java IO流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系,Java IO流的40 多个类都是从 如下 4个抽象类基类中派生出来的。
按操作方式分类结构图:
按操作对象分类结构图:
问题:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?
答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时的,并且,如果我们不知道编码类型就很容易出现乱码问题。所以,I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符流进行操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话则使用字符流比较好。
final 关键字,意思是最终的、不可修改的,最见不得变化,用来修饰类、方法和变量,具有以下特点:
说明:使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。
static 关键字主要有以下四种使用场景:
类名.静态变量名
类名.静态方法名()
import static
这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。this关键字用于引用类的当前实例。 例如:
class Manager {
Employees[] employees;
void manageEmployees() {
int totalEmp = this.employees.length;
System.out.println("Total employees: " + totalEmp);
this.report();
}
void report() { }
}
在上面的示例中,this关键字用于两个地方:
此关键字是可选的,这意味着如果上面的示例在不使用此关键字的情况下表现相同。 但是,使用此关键字可能会使代码更易读或易懂。
super关键字用于从子类访问父类的变量和方法。 例如:
public class Super {
protected int number;
protected showNumber() {
System.out.println("number = " + number);
}
}
public class Sub extends Super {
void bar() {
super.number = 10;
super.showNumber();
}
}
在上面的例子中,Sub 类访问父类成员变量 number 并调用其其父类 Super 的 showNumber()
方法。
使用 this 和 super 要注意的问题:
super()
调用父类中的其他构造方法时,该语句必须处于构造器的首行,否则编译器会报错。另外,this 调用本类中的其他构造方法时,也要放在首行。简单解释一下:
被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, this和super是属于对象范畴的东西,而静态方法是属于类范畴的东西。
更多详细内容请参考:Collections 工具类和 Arrays 工具类常见方法
Collections 工具类常用方法:
void reverse(List list)//反转
void shuffle(List list)//随机排序
void sort(List list)//按自然排序的升序排序
void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑
void swap(List list, int i , int j)//交换两个索引位置的元素
void rotate(List list, int distance)//旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面。
int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的
int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)
int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素。
int frequency(Collection c, Object o)//统计元素出现次数
int indexOfSubList(List list, List target)//统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target).
boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素
Collections提供了多个synchronizedXxx()
方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。
我们知道 HashSet,TreeSet,ArrayList,LinkedList,HashMap,TreeMap 都是线程不安全的。Collections提供了多个静态方法可以把他们包装成线程同步的集合。
最好不要用下面这些方法,效率非常低,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合。
方法如下:
synchronizedCollection(Collection<T> c) //返回指定 collection 支持的同步(线程安全的)collection。
synchronizedList(List<T> list)//返回指定列表支持的同步(线程安全的)List。
synchronizedMap(Map<K,V> m) //返回由指定映射支持的同步(线程安全的)Map。
synchronizedSet(Set<T> s) //返回指定 set 支持的同步(线程安全的)set。
emptyXxx(): 返回一个空的、不可变的集合对象,此处的集合既可以是List,也可以是Set,还可以是Map。
singletonXxx(): 返回一个只包含指定对象(只有一个或一个元素)的不可变的集合对象,此处的集合可以是:List,Set,Map。
unmodifiableXxx(): 返回指定集合对象的不可变视图,此处的集合可以是:List,Set,Map。
上面三类方法的参数是原有的集合对象,返回值是该集合的”只读“版本。
sort()
binarySearch()
equals()
fill()
asList()
toString()
copyOf()
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。