- a++与++a的区别
a++ 先执行表达式,再自增,在那一行中 a仍为原值
++a 先自增,在执行表达式,在那一行中a为增加之后的值int a = 1; if (a++ == 1) { System.out.println("right"); } else { System.out.println("false"); } if (++a == 2) { System.out.println("right"); } else { System.out.println("false"); }
- 无头链表
无头链表就是指头指针直接指向第一个节点,
有头链表指的是头指针指向头结点,头结点的指针再指向第一个节点。一般来说,头结点不存任何信息,或者存链表的长度。
头结点是为了防止链表为空而设置的,如果链表为空,有头结点的时候,头指针指向头结点;但在无头结点的时候,头指针为null。
- Java的按值传递和按引用传递
Java中的数据类型总体来说分为两类,分别是基本数据类型和引用数据类型。详细如下图:
Java数据类型
基础数据类型占用字节数
对于基本数据类型来说,他们的按值传递调用函数并不会改变在原函数中的值。
public class Swap {
public static void main(String[] args) {
int x = 10;
int y = 20;
swap(x, y);
System.out.println("x(2) = " + x);
System.out.println("y(2) = " + y);
}
public static void swap(int x, int y) {
int temp = x;
x = y;
y = temp;
System.out.println("x(1) = " + x);
System.out.println("y(1) = " + y);
}
}
/*输出
x(1) = 20
y(1) = 10
x(2) = 10
y(2) = 20
*/
上面程序main函数调用swap函数来交换 x,y的值,然而调用函数之后发现main中x,y的值并未交换。包括在Java api中找不到一个可以交换两个变量的方法。这与Java语言的特性有关。通过一个图就可以知道上面程序的运行结果了。
由上图可知,main函数中的x,y和swap函数中的x,y分别存放在不同的区域,在main中调用swap函数的时候,会将main中的x,y的值赋给swap中的x,y。当swap函数中对x,y交换时只是对swap帧中的x,y做交换,并不会改变main中的x,y。所以当函数返回时main中的x,y并不会改变。swap执行过程图如下:
对于基本数据类型 short int long float double char byte boolean这八种按值传递调用函数并不会改变在原函数中的值。
引用数据类型的按值传递
引用数据数据类型分为三种:①接口 ②类 ③数组 对于引用数据类型的按值传递先给出一个实例对比实例进行分析。
public static void main(String[] args) {
int []a={10,20};
System.out.println("a[0] :"a[0]+"a[1] : "+a[1]);//a[0]=10,a[1]=20;
swap(a, 0, 1);
System.out.println("a[0] :"a[0]+"a[1] : "+a[1]);//a[0]=20,a[1]=10;
}
public static void swap(int []a,int i,int j){
int temp=a[i];
a[i]=a[j];
a[j]=temp;
System.out.println("a[0] :"a[0]+"a[1] : "+a[1]);//a[0]=20,a[1]=10;
}
//输出
/*a[0]=10 a[1]=20
a[0]=20 a[1]=10
a[0]=20 a[1]=10
*/
运行程序后发现,swap函数对a[0] ,a[1]的操作竟然影响到了main函数中的a[0] ,a[1]的值,真是不可思议。为什么会产生如此之结果。原来引用类型的按值传递,传递的是对象的地址。还是用图来解释一下。
由图可以看出在swap中仅仅是得到了数组的地址,并没有对数组的元素进行复制,在swap中对数组的操作是直接对main函数中数组的操作,因此swap函数返回后main函数中的a[0] ,a[1]的值发生交换。
数组、类、接口按值传递的时候都是传递对象的地址。
- ==与equals
==作用是比较两个对象的地址是不是相等。即判断两个对象是否是同一个对象,基本数据类型==比较的是值,引用数据类型==比较的是内存地址。
equals()一般有两种使用情况:
1.情况1:类没有覆盖equals方法,则equals比较的是对象的内存地址。等价于==比较两个对象。
2.情况2:类覆盖了equals方法,一般都重写equals方法来确定两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
说明:
1、String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。
2、当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。
修改String的值的时候,JVM会先回收掉之前的String,再创建新的String,String为字符串常量,不可修改。所以String效率最慢,适合处理少量字符串。
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");
}
}
}
- final关键字
final关键字可以用来修饰变量,类,方法
1、对于一个final变量:如果是基本数据类型,则其数值一旦在初始化之后便不能更改;如果是引用数据类型,则在其初始化之后便不能再让其指向另一个对象。
2、当用final修饰一个类时:表名这个类不能被继承,final类中所有的方法都会被隐式的制定为final方法。
3、使用final修饰一个方法的原因有两个:第一把方法锁住,以防任何继承类修改它的含义,第二为了效率
- static关键字
- 修饰成员变量和成员方法
- 静态代码块
- 修饰类(只能修饰内部类)
- 静态导包(用来导入类中的静态资源,1.5之后的新特性)
修饰成员变量和成员方法
被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。
方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。
调用格式:
类名.静态变量名
类名.静态方法名()
如果变量或者方法被 private 则代表该属性或者该方法只能在类的内部被访问而不能在类的外部被访问。
静态代码块
静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
静态内部类
静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:
- 它的创建是不需要依赖外围类的创建。
- 它不能使用任何外围类的非static成员变量和方法。
静态导包
格式为:import static
这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法
- 队列中add方法和offer方法的区别
add():Inserts the specified element at the tail of this queue. As the queue is unbounded, this method will never throw IllegalStateException or return false.
offer():Inserts the specified element at the tail of this queue. As the queue is unbounded, this method will never return false.
区别:两者都是往队列尾部插入元素,不同的时候,当超出队列界限的时候,add()方法是抛出异常让你处理,而offer()方法是直接返回false
- Stack中pop方法与peek方法的区别
相同点:都是返回栈顶元素
不同点:pop方法弹出栈顶元素,peek方法只返回栈顶的值,但不弹出栈顶元素
- 判断栈是否为空要使用isEmpty()方法
- &&注意事项
1.&&有短路功能,&&左边的第一个表达式为FALSE的时候,不执行第二个表达式;但&的话左右两边的表达式都执行。
2.因为&&有短路功能,所以要注意&&左右两边的表达式顺序问题,比如!stackAux.isEmpty() && stackAux.peek() == popA[popIndex]
此表达式左右两边不能倒过来,因为万一倒过来,如果栈为空了,那么stack.peek
方法就会报空指针,等不到判断右侧的是否为空就抛异常。导致错误。
-
栈与队列定义与常用方法
栈与队列均可用LinkedList定义
LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
LinkedList 实现 List 接口,能对它进行队列操作。
LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
LinkedList 是非同步的。
栈:LinkedList<Integer> stack = new LinkedList<>();
// 压入栈
stack.push();
// 弹出栈
stack.pop();
// 返回栈顶元素
stack.peek();队:
Queue<TreeNode> queue = new LinkedList<>();
// 添加一个元素并返回true 如果队列已满,则返回false
queue.offer();
// 移除并返问队列头部的元素 如果队列为空,则返回null
queue.poll();
// 检索并删除此队列的头。 此方法与poll不同之处在于,如果此队列为空,它将抛出异常
queue.remove();
// 返回队列头部的元素 如果队列为空,则返回null
queue.peek();双端队列:
-
堆的实现
最大堆:PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder());
最小堆:
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
方法:
// 添加一个元素并返回true 如果队列已满,则返回false
maxHeap.offer();
// 移除并返问队列头部的元素 如果队列为空,则返回null
maxHeap.poll();
// 检索并删除此队列的头。 此方法与poll不同之处在于,如果此队列为空,它将抛出异常
maxHeap.remove();
// 返回队列头部的元素 如果队列为空,则返回null
maxHeap.peek();
// 从该队列中删除指定元素的单个实例(如果存在)
maxHeap.remove(Object o);
-
二维数组
可以理解为一维数组,数组中的每个元素为数组
行的长度 array.length
列的长度 array[i].length
-
关于Java中split分隔符
使用一个或多个空格分割字符串 正确写法:
String [] arr = str.split("\\s+")
正则表达式\s表示匹配任何空白字符,+表示匹配一次或多次。
. * ^ |等符号在正则表达式中属于一种有特殊含义的字符,如果使用此种字符作为分隔符,必须使用转义符即\加以转义。String ss = new String("W10.07"); String s[] = ss.split("\\.");
- Integer和int的区别
- int是基本数据类型,Integer是int的包装类
- Integer变量必须实例化之后才能使用,而int不需要
- Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向这个对象;而int是直接存储数据的值。
- Integer默认值是null,int默认值是0.
-
与、或、异或
与运算:0&0=0; 0&1=0; 1&0=0; 1&1=1;
或运算:0|0=0; 0|1=1; 1|0=1; 1|1=1;
异或运算: 0^0=0; 0^1=1; 1^0=1; 1^1=0;即:参加运算的两个对象,如果两个相应位为“异”(值不同),则该位结果为1,否则为0。
-
static、继承代码加载顺序
父类静态代码块 --> 子类静态代码块--> 父类非静态代码块-->父类构造方法-->子类非静态代码块-->子类构造方法。
-
Map的遍历方法
通常使用foreach循环
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
if (entry.getValue() > array.length / 2) {
return entry.getKey();
}
}
这种方式既可以获取到key,也可以获取到value
还有其他方法:
foreach中遍历的可以是map.values 仅有values
也可以是map.getKeys 仅有key、
java中Map遍历的四种方式
-
try catch finally
在try中return之前会执行finally中的代码,如果finally中有return则直接return,值为finally中修改的,如果finally中没有return,则执行try中的return,数值仍然是try中的。意思就是即使finally中对返回的值进行了修改,真正return的还是try中的。
-
抽象类可以定义构造函数吗?
可以,抽象类可以声明并且定义构造函数,但不可以对抽象类实例化,有些面试官问既然不可以实例化,为什么需要构造函数?构造函数的作用是初始化抽象类内部声明的通用变量,并被各处实现使用,另外,如果没有显示的提供构造函数,编译器将为抽象类添加默认的无参构造函数,否则子类无法编译,因为在任何构造函数的第一句都隐式调用super(),Java中默认超类的构造函数。
- String为啥是final的?
- 为了实现字符串池
- 为了线程安全
- 为了实现String可以创建HashCode不可变性
首先了解final关键字的用途,它在修饰变量,方法,类时含义各不相同
- 变量:一旦初始化,变量值就不能修改
- 方法:该方法不能被子类重写
- 类:该类不能被继承
只有当string是final的,字符串常量池的实现,可以节省内存空间,提高效率(因为不同的字符串变量指向常量池中同一个字符串)
只有当string是final的,同一个字符串实例可以被多个线程共享,这样便不用因为线程安全问题而使用同步。字符串本身便是线程安全的。
只有当String是final的,在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的key,字符串的处理速度要快过其他键对象,这就是为什么HashMap常常使用String做键的原因。
-
Native关键字
Native用来修饰方法,用native修饰的方法表示告知JVM调用,该方法在外部定义,我们可以用任何语言去实现它,简单的讲,一个native方法就是一个Java调用非Java代码的接口。
native语法:- 修饰方法的位置必须要在返回类型之前,和其他方法控制符前后关系不受限制
- 不能用abstract修饰,没有方法体,没有左右大括号
- 返回值可以是任意类型。
-
retry使用
retry是一个标记,标记程序跳出循环后从哪里执行,功能类似与goto,retry一般是跟随着for循环出现,
网友评论