Java 牛客选择题中碰到的一些知识点总结.持续更新中
- 面向对象的三大特性
- 创建对象的方式
- C++和Java的一些区别
- 线程
- 多线程
- 异常
- Object类
- 重写原则
- URL
- Java中常用命令
- 内部类对外部类的访问
- Integer包装类的数值比较
- hashCode()和equals对比
- 类的加载顺序
- Spring事务
- ceil和floor
- 枚举类
- 常见的包名
- boolean的理解
- %和mod的区别
- 集合元素的添加
- 访问修饰符
- 泛型的理解
- socket编程
- Web容器
- final关键字的理解
- 值传递和引用传递
- 字符集编码
- super和this关键字
- RMI
- application对象
- JSP内置对象
- 服务端转发和客户端重定向
- hash冲突
- 字符串的比较
- 将指定的集合包装成线程同步的集合
- Condition类和Object类和ReetrantLock类区别
- 泛型的标识符
- JVM
- 接口
- 局部变量的理解
- JDBC
面向对象的三大特性
三大特性:封装、继承、多态
可重用性:说的是多态
可扩展性:说的是继承
易于管理和维护:说的是封装
创建对象的方式
package com.choice.question18;
import org.junit.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.List;
/**
* 创建对象的几种方式
*
* @author wty
* @date 2022/12/1 22:27
*/
public class ObjectExercise {
@Test
public void test() throws ClassNotFoundException, InstantiationException, IllegalAccessException, CloneNotSupportedException, IOException {
// 1. new关键字
A a = new A();
// 2.用newInstance()创建对象
Class aClass = Class.forName("com.choice.question18.A");
Object o = aClass.newInstance();
// 3.如果一个类要实现clone(),那么需要重写public clone()方法
B b = new B();
Object clone = b.clone();
// 4.用反序列化,读取文件中的Object信息
String path = "";
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(path));
objectInputStream.readObject();
}
}
class A {
}
class B extends Object {
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
C++和Java的一些区别
- 二者都是面向对象的语言
- C++中有指针,Java中没有指针
- C++中String类型是char[]数组,以/0结尾
- C++支持类的多继承,Java不支持类的多继承,但是可以实现多个接口间接实现了多继承
- java和C++都有三个特征:封装、继承和多态(面向对象的三大特性)
针对第3点,看一下String的源码
线程
主线程和子线程
线程的插队
下列代码执行结果为()
public static void main(String args[])throws InterruptedException{
Thread t=new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.print("2");
}
});
t.start();
t.join();
System.out.print("1");
}
注意题目代码写在main方法中,main是主线程,而t是子线程,t.join的含义是,main线程被t子线程插队了。所以会先跑子线程的方法。
输出:
21
线程的执行顺序
以下程序运行的结果为 ( )
package com.choice.question21;
/**
* @author wty
* @date 2023/1/6 17:43
*/
public class Example extends Thread {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print("run ");
}
public static void main(String[] args) {
Example example = new Example();
example.run();
System.out.print("main");
}
}
A.run main
B.main run
C.main
D.run
E.不能确定
解析:
这道题的关键点在于run方法是不会开启线程的,开启线程的是start方法,所以,example.run调用的只是run方法,都会先执行,所以答案是A
线程InterruptedException
抛InterruptedException的代表方法有:
java.lang.Object 类的 wait 方法
java.lang.Thread 类的 sleep 方法
java.lang.Thread 类的 join 方法
CyclicBarrier是一个屏障类,它的await方法可以简单的理解为:等待多个线程同时到达之后才能继续进行,在此之前它就是这些线程的屏障,线程不能继续进行,而对于失败的同步尝试,CyclicBarrier 使用了一种要么全部要么全不 (all-or-none) 的破坏模式:如果因为中断、失败或者超时等原因,导致线程过早地离开了屏障点,那么在该屏障点等待的其他所有线程也将通过 BrokenBarrierException(如果它们几乎同时被中断,则用 interruptedException)以反常的方式离开。因此它被中断也是可以抛出interruptedException的
多线程
变量声明中的多线程
这里题目说了是修饰变量的,那么可以看到violate关键字修饰变量。
synchronized不是修饰变量的,而是用来修饰方法或代码块的
当一个变量定义为 volatile 之后,将具备两种特性:
-
保证此变量对所有的线程的可见性,即当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存来完成。
-
禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。
异常
Java中异常分为2种
1:RuntimeException(运行时异常)IndexOutOfBoundsException、NullPointerException
2:OtherException(编译时异常) IOException、SQLException
运行时异常可以用try和catch包裹
package com.choice.question18;
import org.junit.Test;
/**
* @author wty
* @date 2022/12/1 23:06
*/
public class ExceptionExercise {
@Test
public void test() {
int array[] = new int[5];
// 会出来运行时异常
try {
array[5] = 1;
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
}
}
输出结果
可以看到抛出了IndexOutOfBoundsException的子类ArrayIndexOutOfBoundsException
java.lang.ArrayIndexOutOfBoundsException: 5
at com.choice.question18.ExceptionExercise.test(ExceptionExercise.java:16)…………
Object类
方法名 | 返回值类型 | 描述 |
---|---|---|
clone() | protected Object | 创建并返回此对象的副本。 |
equals(Object obj) | boolean | 指示一些其他对象是否等于此对象。 |
finalize() | protected void | 当垃圾收集确定不再有对该对象的引用时,垃圾收集器在对象上调用该对象。 |
getClass() | 类<?> | 返回此Object的运行时类。 |
hashCode() | int | 返回对象的哈希码值。 |
notify() | void | 唤醒正在等待对象监视器的单个线程。 |
notifyAll() | void | 唤醒正在等待对象监视器的所有线程。 |
toString() | String | 返回对象的字符串表示形式。 |
wait() | void | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。 |
wait(long timeout) | void | 导致当前线程等待,直到另一个线程调用 notify()方法或该对象的 notifyAll()方法,或者指定的时间已过。 |
wait(long timeout, int nanos) | void | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法,或者某些其他线程中断当前线程,或一定量的实时时间。 |
重写原则
两同:1.参数(个数、顺序、类型)相同2.方法名相同
两小:1.返回值类型小于等于父类2.异常类型小于等于父类
一大:访问修饰符大于等于父类
URL
URL重写了toString()方法,所以返回对象的时候输出的是重写的方法里的内容
@Test
public void test2() throws MalformedURLException {
URL u = new URL("http://www.123.com");
System.out.println(u);
}
输出
http://www.123.com
源码
public String toString() {
return this.toExternalForm();
}
Java中常用命令
java -d 指定放置生成的类文件的位置
内部类对外部类的访问
package com.choice.question19;
import org.junit.Test;
import java.util.Comparator;
/**
* @author wty
* @date 2022/12/29 22:33
*/
public class QuestionDemo {
@Test
public void test() {
// 局部内部类
new OuterClass().testOuter();
// 成员内部类
OuterClass outerClass = new OuterClass();
outerClass.tell();
}
}
class OuterClass {
private int a = 10;
// 局部内部类
public void testOuter() {
class InnerClass {
public void T() {
System.out.println(a);
}
}
InnerClass a = new InnerClass();
a.T();
}
// 成员内部类
public class InnerMember {
public void getX() {
System.out.println(a);
}
}
public void tell() {
InnerMember innerMember = new InnerMember();
innerMember.getX();
}
// 匿名内部类
{
Integer c = 3;
Integer d = 4;
int comparator = new Comparator<Integer>() {
@Override
public int compare(Integer c, Integer d) {
System.out.println("匿名内部类" + a);
return Integer.compare(c, d);
}
}.compare(c, d);
}
}
输出
匿名内部类10
10
匿名内部类10
10
Integer包装类的数值比较
package com.choice.question19;
import org.junit.Test;
/**
* @author wty
* @date 2022/12/29 22:51
*/
@SuppressWarnings({"all"})
public class Question20 {
@Test
public void test() {
Integer n1 = new Integer(47);
Integer n2 = new Integer(47);
System.out.print(n1 == n2);
System.out.print(",");
System.out.println(n1 != n2);
Integer n3 = 47;
Integer n4 = 47;
System.out.println(n3 == n4);
}
}
输出
false,true
true
这里需要注意Integer.valusOf()的取值范围是-128~127
和byte的取值范围一致
hashCode()和equals对比
得出:
1.equals()相等的两个对象他们的hashCode()肯定相等,也就是用equals()对比是绝对可靠的。
2.hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。
所有对于需要大量并且快速的对比的话如果都用equals()去做显然效率太低,所以解决方式是:
每当需要对比的时候,首先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等(也就是不必再用equal()去再对比了),如果hashCode()相同,此时再对比他们的equals(),如果equals()也相同,则表示这两个对象是真的相同了,这样既能大大提高了效率也保证了对比的绝对正确性!
类的加载顺序
package com.choice.question19;
/**
* @author wty
* @date 2022/12/29 23:09
*/
public class QuestionDemo03 {
public static void main(String[] args) {
Test t2 = new Test();
}
}
class Test {
public static Test t1 = new Test();
{
System.out.println("blockA");
}
static {
System.out.println("blockB");
}
}
输出结果
blockA
blockB
blockA
这里有个套娃,t2要实例化,先加载test类,即先运行静态内容,这里静态内容又需要实例化一个test,普通代码块随着类的创建执行一次,而为什么不先执行套娃test中的静态代码块b,应该是已经准备在第一次加载中加载,所以套娃的test创建后,运行A,然后准备加载的静态代码块运行,输出B,然后t2创建,又输出普通代码块A。
Spring事务
支持当前事务的情况:
TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
不支持当前事务的情况:
TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
以非事务方式运行,如果当前存在事务,则把当前事务挂起。 TransactionDefinition.PROPAGATION_NEVER:
以非事务方式运行,如果当前存在事务,则抛出异常。
其他情况:
TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
ceil和floor
package com.choice.question19;
import org.junit.Test;
/**
* @author wty
* @date 2022/12/29 23:24
*/
public class Question04 {
@Test
public void test() {
double d1 = -0.5;
System.out.println("Ceil d1=" + Math.ceil(d1));
System.out.println("floor d1=" + Math.floor(d1));
}
}
输出
Ceil d1=-0.0
floor d1=-1.0
枚举类
package com.choice.question19;
/**
* @author wty
* @date 2022/12/29 23:32
*/
public class QuestionDemo05 {
public static void main(String[] args) {
System.out.println(AccountType.FIXED);
}
enum AccountType {
SAVING, FIXED, CURRENT;
private AccountType() {
System.out.println("It is a account type");
}
}
}
输出结果
It is a account type
It is a account type
It is a account type
FIXED
枚举类有三个实例,故调用三次构造方法
常见的包名
java.awt:包含构成抽象窗口工具集的多个类,用来构建和管理应用程序的图形用户界面 。
java.lang:提供java编成语言的程序设计的基础类
java.io:包含提供多种输出输入功能的类
java.net:包含执行与网络有关的类,如URL,SCOKET,SEVERSOCKET。
java.applet:包含java小应用程序的类
java.util:包含一些实用性的类
boolean的理解
package com.choice.question20;
import org.junit.Test;
import java.net.Socket;
/**
* @author wty
* @date 2023/1/3 11:37
*/
public class QuestionDemo20 {
boolean aa;
@Test
public void test() {
int bb;
aa = true;
aa = False;
aa = "true";
aa = 0;
}
}
boolean的初始化默认值
%和mod的区别
package com.choice.question20;
import java.math.BigInteger;
/**
* @author wty
* @date 2023/1/3 11:52
*/
public class RomTest {
public static void main(String[] args) {
BigInteger b1;
BigInteger b2;
BigInteger b3;
b1 = new BigInteger("-20");
b2 = new BigInteger("3");
b3 = b1.mod(b2);
System.out.println("b3:" + b3);
Integer i1 = -20;
Integer i2 = -3;
Integer i3 = i1 % i2;
System.out.println("i3:" + i3);
}
}
输出
b3:1
i3:-2
看答案,明显i3是正确的,当出现负数(左操作数)的时候,%是正确的,可以计算负数,而mod不行
集合元素的添加
package com.choice.question20;
import org.junit.Test;
import java.util.ArrayList;
/**
* @author wty
* @date 2023/1/3 13:07
*/
public class QuesDemo01 {
@Test
public void test() {
ArrayList<Integer> nums = new ArrayList<>();
nums.add(5);
nums.add(3);
nums.add(1);
nums.add(6); // 5,3,1,6
nums.add(0, 4); // 在0位置添加元素4,5,3,1,6
nums.remove(1); // 移除1位置的元素 4,3,1,6
nums.forEach(System.out::println);
}
}
输出结果
4
3
1
6
访问修饰符
当前类 | 当前包 | 其它包的子类 | 所有类 | |
---|---|---|---|---|
priivate | √ | |||
default | √ | √ | ||
protect | √ | √ | √ | |
public | √ | √ | √ | √ |
该题目private修饰了Person中的name,如果private修改成public则答案是A
这里注意看,向上转型和向下转型看的是编译类型,而不是运行类型
泛型的理解
泛型仅仅是java的语法糖,它不会影响java虚拟机生成的汇编代码,在编译阶段,虚拟机就会把泛型的类型擦除,还原成没有泛型的代码,顶多编译速度稍微慢一些,执行速度是完全没有什么区别的。
socket编程
TCP编程如下,没有C选项
UDP编程中
分为发送发和接收方
接收方有recieve方法
发送方是send方法
Web容器
servlet:是一套技术标准,内含与web应用相关的一系列接口,用于为web应用实现方式提供宏观解决方案。
web容器:例如tomcat,用于接受、响应客户端的请求,负责将HTTP请求转换为HttpServletRequest对象,也就是创建servlet实例对象。
final关键字的理解
A中final修饰类,该类不能继承,而抽象类,是需要继承才有意义的,所以final不能修饰抽象类
B中final修饰方法,该方法不能被重写,而抽象方法,需要重写来具体实现方法体内的内容,所以final不能修饰抽象方法
C抽象方法的方法体不能写,仅仅声明即可。
值传递和引用传递
一、搞清楚 基本类型 和 引用类型的不同之处
int num = 10;
String str = "hello";
如图所示:
num是基本类型,值就直接保存在变量中。
而str是引用类型,变量中保存的只是实际对象的地址。一般称这种变量为"引用",引用指向实际对象,实际对象中保存着内容。
二、搞清楚赋值运算符(=)的作用
在一的基础上赋值
num = 20;
str = "java";
对于基本类型 num ,赋值运算符会直接改变变量的值,原来的值被覆盖掉。
对于引用类型 str,赋值运算符会改变引用中所保存的地址,原来的地址被覆盖掉。但是原来的对象不会被改变(重要)。
三、调用方法时发生了什么?参数传递基本上就是赋值操作。
第一个例子:基本类型
package com.choice.question21;
import org.junit.Test;
/**
* @author wty
* @date 2023/1/3 14:56
*/
public class DemoReference {
int num = 20;
void foo(int value) {
value = 100;
}
@Test
public void test() {
foo(num);
System.out.println(num);
}
}
输出结果
20
可以发现最后输出的num并没有改变
第二个例子:没有提供改变自身方法的引用类型
package com.choice.question21;
import org.junit.Test;
/**
* @author wty
* @date 2023/1/3 14:56
*/
public class DemoReference {
String str = "java";
void foo(String text) {
text = "window";
}
@Test
public void test() {
foo(str);
System.out.println(str);
}
}
输出结果
java
可以发现最后输出的str并没有改变
第三个例子:提供了改变自身方法的引用类型
package com.choice.question21;
import org.junit.Test;
/**
* @author wty
* @date 2023/1/3 14:56
*/
public class DemoReference {
StringBuilder str = new StringBuilder("java");
void foo(StringBuilder builder) {
builder.append(" exercise");
}
@Test
public void test() {
foo(str);
System.out.println(str);
}
}
输出结果
java exercise
图示:初始赋值
调用foo方法,形参builder指向实参str对应的java
builder往字符串后append
最后输出str的结果
第四个例子:提供了改变自身方法的引用类型,但是不使用,而是使用赋值运算符。
package com.choice.question21;
import org.junit.Test;
/**
* @author wty
* @date 2023/1/3 14:56
*/
public class DemoReference {
StringBuilder str = new StringBuilder("java");
void foo(StringBuilder builder) {
builder = new StringBuilder("apple");
}
@Test
public void test() {
foo(str);
System.out.println(str);
}
}
输出结果
java
图示
初始化赋值
调用foo方法,形参builder指向实参str对应的java
builder赋了一个新值
输出str的结果
### 第五个例子:引用类型作为方法的参数值
[单选题]给出以下代码,请给出结果.
链接:https://www.nowcoder.com/questionTerminal/cf9c207b61924fc0876071f86dda76b7
来源:牛客网
class Two{
Byte x;
}
class PassO{
public static void main(String[] args){
PassO p=new PassO();
p.start();
}
void start(){
Two t=new Two();
System.out.print(t.x+””);
Two t2=fix(t);
System.out.print(t.x+” ” +t2.x);
}
Two fix(Two tt){
tt.x=42;
return tt;
}
}
这道题需要注意:
1.包装类型的默认值为null,因为我们只声明了对象引用,并没有初始化,这样就会让JVM以为我们不需要为对象开辟空间,而只是在虚拟机栈中存放引用,并用关键词null,标明这个引用与堆中没有任何链接。
初始化类型 | 初始化值 |
---|---|
Byte | null |
Shot | null |
Long | null |
Integer | null |
Double | null |
Float | null |
Char | null |
Boolean | null |
int | 0 |
short | 0 |
byte | 0 |
long | 0 |
char | null |
boolean | false |
double | 0.0 |
float | 0.0 |
图示
所以t2和t最后都变成了42
字符集编码
A 显然是错误的,Java一律采用Unicode编码方式,每个字符无论中文还是英文字符都占用2个字节。
B 也是不正确的,不同的编码之间是可以转换的,通常流程如下: 将字符串S以其自身编码方式分解为字节数组,再将字节数组以你想要输出的编码方式重新编码为字符串。 例:String newUTF8Str = new String(oldGBKStr.getBytes(“GBK”), “UTF8”);
C 是正确的。Java虚拟机中通常使用UTF-16的方式保存一个字符 。
D 也是正确的。ResourceBundle能够依据Local的不同,选择性的读取与Local对应后缀的properties文件,以达到国际化的目的。
综上所述,答案是 C 和 D。
super和this关键字
A选项正确。
B选项,super()必须在第一行的原因是: 子类是有可能访问父类对象的, 比如在构造函数中使用父类对象的成员函数和变量, 在成员初始化使用了父类, 在代码块中使用了父类等等, 所以为保证在子类可以访问父类对象之前,一定要完成对父类对象的初始化。关于this()必须在第一行的原因,我们假设这样一种情况,,类B是类A的子类, 如果this()可以在构造函数的任意行使用, 那么当程序运行到构造函数B()的第一行,发现没有调用this()和super(),那么就会自动在第一行补齐super() 来完成对父类对象的初始化, 然后返回子类的构造函数继续执行, 当运行到构造函数B()的"this() ;"时, 调用B类对象的构造函数, 还会对父类对象再次初始化!,这就造成了资源的浪费,以及某些意想不到的错误。也正因如此C选项错误。
D选项,无论是this()还是super()指的都是对象,而static环境中是无法使用非静态变量的。因此D选项错误。
RMI
RMI(Remote Method Invocation)远程方法调用是一种计算机之间利用远程对象互相调用实现双方通讯的一种通讯机制。使用这种机制,某一台计算机上的对象可以调用另外一台计算机上的对象来获取远程数据。RMI是Enterprise JavaBeans的支柱,是建立分布式Java应用程序的方便途径。在过去,TCP/IP套接字通讯是远程通讯的主要手段,但此开发方式没有使用面向对象的方式实现开发,在开发一个如此的通讯机制时往往令程序员感觉到乏味,对此RPC(Remote Procedure Call)应运而生,它使程序员更容易地调用远程程序,但在面对复杂的信息传讯时,RPC依然未能很好的支持,而且RPC未能做到面向对象调用的开发模式。针对RPC服务遗留的问题,RMI出现在世人面前,它被设计成一种面向对象的通讯方式,允许程序员使用远程对象来实现通信,并且支持多线程的服务,这是一次远程通讯的变革,为远程通信开辟新的里程碑。 RMI的开发步骤 先创建远程接口及声明远程方法,注意这是实现双方通讯的接口,需要继承Remote 开发一个类来实现远程接口及远程方法,值得注意的是实现类需要继承UnicastRemoteObject 通过javac命令编译文件,通过java -server 命令注册服务,启动远程对象 最后客户端查找远程对象,并调用远程方法 所以选C
application对象
application对象是共享的,多个用户共享一个,以此实现数据共享和通信。
application对象实现了用户间数据的共享,可存放全局变量。它开始于服务器的启动,直到服务器的关闭,在此期间,此对象将一直存在;这样在用户的前后连接或不同用户之间的连接中,可以对此对象的同一属性进行操作;在任何地方对此对象属性的操作,都将影响到其他用户对此的访问。服务器的启动和关闭决定了application对象的生命。它是ServletContext类的实例。
JSP内置对象
JSP内置对象和属性列举如下:
- request对象
客户端的请求信息被封装在request对象中,通过它才能了解到客户的需求,然后做出响应。它是HttpServletRequest类的实例。 - response对象
response对象包含了响应客户请求的有关信息,但在JSP中很少直接用到它。它是HttpServletResponse类的实例。 - session对象
session对象指的是客户端与服务器的一次会话,从客户连到服务器的一个WebApplication开始,直到客户端与服务器断开连接为止。它是HttpSession类的实例. - out对象
out对象是JspWriter类的实例,是向客户端输出内容常用的对象 - page对象
page对象就是指向当前JSP页面本身,有点象类中的this指针,它是java.lang.Object类的实例 - application对象
application对象实现了用户间数据的共享,可存放全局变量。它开始于服务器的启动,直到服务器的关闭,在此期间,此对象将一直存在;这样在用户的前后连接或不同用户之间的连接中,可以对此对象的同一属性进行操作;在任何地方对此对象属性的操作,都将影响到其他用户对此的访问。服务器的启动和关闭决定了application对象的生命。它是ServletContext类的实例。 - exception对象
exception对象是一个例外对象,当一个页面在运行过程中发生了例外,就产生这个对象。如果一个JSP页面要应用此对象,就必须把isErrorPage设为true,否则无法编译。他实际上是java.lang.Throwable的对象 - pageContext对象
pageContext对象提供了对JSP页面内所有的对象及名字空间的访问,也就是说他可以访问到本页所在的SESSION,也可以取本页面所在的application的某一属性值,他相当于页面中所有功能的集大成者,它的本 类名也叫pageContext。 - config对象
config对象是在一个Servlet初始化时,JSP引擎向它传递信息用的,此信息包括Servlet初始化时所要用到的参数(通过属性名和属性值构成)以及服务器的有关信息(通过传递一个ServletContext对象)
服务端转发和客户端重定向
服务端转发:forward,服务器获取跳转页面内容传给用户,用户地址栏不变。
客户端重定向redirect,是服务器向客户端发送转向的地址,redirect后地址栏变成新的地址因此这个题是A。
hash冲突
threadlocalmap使用开放定址法解决hash冲突,
hashmap使用链地址法解决hash冲突
字符串的比较
变量a是运行时动态加载的,此时会在堆内存中生成一个myString字符串,指向堆内存字符串地址。
变量b是编译时静态加载的,此时会在常量池中存放一个myString字符串,指向常量池字符串地址。
所以变量a和变量b指向的地址不同,答案A是false
而变量c在编译时对"my" + "String"进行StringBuffer().append拼接成myString字符串,再去常量池查找,找到之后指向该字符串地址。
所以答案C是true。
答案B和答案A其实本质一样,变量c和变量b指向同个地址,答案A是false,那么B也是false。
变量d指向变量c,因为答案C是true,则二者输出结果一致,答案D也是true。
将指定的集合包装成线程同步的集合
Map map = Collections.synchronizedMap(new HashMap());
List list = Collections.synchronizedList(new ArrayList());
Set set = Collections.synchronizedSet(new HashSet());
Condition类和Object类和ReetrantLock类区别
- Condition类await方法和Object类的wait方法等效
- Condition类的signal方法和Object类的notify方法等效
- Condition类的signalAll方法和Object类的notifyAll方法等效
- ReetrantLock的lock和unlock方法可以唤醒指定条件的线程,而Ojbect类的notify方法唤醒的是随机的线程。
泛型的标识符
assignable to表示把前面的值赋值给后面的。
- 只看尖括号里边的!!明确点和范围两个概念
- 如果尖括号里的是一个类,那么尖括号里的就是一个点,比如List,List,List
- 如果尖括号里面带有问号,那么代表一个范围,<? extends A> 代表小于等于A的范围,<? super A>代表大于等于A的范围,<?>代表全部范围
- 尖括号里的所有点之间互相赋值都是错,除非是俩相同的点
- 尖括号小范围赋值给大范围,对,大范围赋值给小范围,错。如果某点包含在某个范围里,那么可以赋值,否则,不能赋值
- List<?>和List 是相等的,都代表最大范围
- 补充:List既是点也是范围,当表示范围时,表示最大范围
A选项:
List 对象 = List< A > 对象 因为List范围最大,那么可以赋值,正确
B选项
List< A >对象 = List< B > 对象,因为是点对点赋值,错误
C选项
List< ? >对象 = List< Object >对象,因为List< ? >和List 是相等的,范围最大,正确
D选项
List<?extends B> 对象 = List< D > 对象,因为D是B的子类,可以把一个点赋值给一个范围,正确
E选项
List< A > 对象 = List< ?extends A > 对象,不能把一个范围赋值给一个点,所以错误
F选项
List任意对象 = List< Object > 对象,List任意对象如果是一个点,那么点和点赋值就是错误的
G选项
List< ?extends A > 对象 = List< ?extends B >对象,B是A的子类正确
JVM
线程共享区域和私有区域
本题目主要考察了线程共享区域和非共享的区域。
堆、方法区属于线程共享。
其他区域属于线程私有区域。
堆内存配置参数详解
Xmn表示年轻代的大小,题目中说了是500M。
-XX:SurvivorRatio=3 表示伊甸区和一个幸存区大小的比值,而我们知道幸存区有两个,那么堆内存的年轻代被分成了5个部分,伊甸区占3份,幸存区占2份,则伊甸区的大小为300M。
接口
接口中常量的调用
package com.choice.question21;
/**
* @author wty
* @date 2023/1/6 19:01
*/
@SuppressWarnings({"all"})
public class A implements B {
public static void main(String[] args) {
int i;
A a1 = new A();
i = a1.k;
System.out.println("i=" + i);
}
}
interface B {
int k = 10;
}
输出结果
i=10
这里可以看到,接口中常量默认情况是public static final修饰的,其实现类可以通过对象直接调用常量。
再看下另一道题目:
接口中的方法都是抽象方法,默认是public修饰的,其实现类的方法重写,访问修饰符必须是和接口相同或者范围更大的,这里接口的方法是public,实现类也必须是public的,接口实现类可以直接使用接口中的成员变量(常量)。
局部变量的理解
局部变量可以先申明不用初始化,但使用到了一定要先初始化。
例如:
package com.choice.question22;
import org.junit.Test;
/**
* @author wty
* @date 2023/1/6 19:11
*/
@SuppressWarnings({"all"})
public class Question22Demo {
@Test
public void test() {
int x;
int b = 0;
System.out.println(b);
}
}
运行结果:
0
这里x声明了,但是没有初始化,后续也没使用,这种可以正常运行,不会报错。
再看另一种情况:
package com.choice.question22;
import org.junit.Test;
/**
* @author wty
* @date 2023/1/6 19:11
*/
@SuppressWarnings({"all"})
public class Question22Demo {
@Test
public void test() {
int x;
int b = 0;
System.out.println(b);
System.out.println(x);
}
}
编译报错
这里可以看到一旦使用了x,那么就会编译错误。
静态代码块也可以看作是局部变量
下面代码运行结果是?
public class Test{
static{
int x=5;
}
static int x,y;
public static void main(String args[]){
x--;
myMethod( );
System.out.println(x+y+ ++x);
}
public static void myMethod( ){
y=x++ + ++x;
}
}
A.compiletime error
B.prints:1
C.prints:2
D.prints:3
E.prints:7
F.prints:8
答案:D
解析:
1.静态语句块中x为局部变量,不影响静态变量x的值。
2.x和y为静态变量,默认初始值为0,属于当前类,其值得改变会影响整个类运行。
3.java中自增操作非原子性的
main方法中:
执行x–后 x=-1
调用myMethod方法,y=x++ + ++x; y = 0, x = 1
最后输出结果:
x+y+ ++x,先执行x+y,结果为1,执行++x结果为2,相加得到最终结果为3
JDBC
Statement和PreparedStatement
创建Statement是不传参的,PreparedStatement是需要传入sql语句。
说一下preparedStatement和statement的区别与联系:在JDBC应用中,如果你已经是稍有水平开发者,你就应该始终以PreparedStatement代替Statement.也就是说,在任何时候都不要使用Statement。
PreparedStatement 接口继承 Statement ,PreparedStatement 实例包含已编译的 SQL 语句, 所以其执行速度要快于 Statement 对象。
Statement为一条Sql语句生成执行计划, 如果要执行两条sql语句
select colume from table where colume=1;
select colume from table where colume=2;
会生成两个执行计划 一千个查询就生成一千个执行计划! PreparedStatement用于使用绑定变量重用执行计划
select colume from table where colume=:x;
通过set不同数据只需要生成一次执行计划,可以重用