牛客刷题笔记 JAVA-500道 持续更新中

发布于:2022-12-10 ⋅ 阅读:(1028) ⋅ 点赞:(0)

interface

interface 中的方法默认为 public abstract 的,变量默认为 public static final

反射

反射 Class
get 方法是获得 user 对象下前面指定好的属性的值,而获取 static 属性时 get() 括号里的对象可以写成 null

getDeclaredField 和 getField 的差异
参考资料
getDeclaredField:查找该 Class 所有声明属性 (静态/非静态),但是他不会去找实现的接口/父类的属性
getField:只查找该类 public 类型的属性,如果找不到则往上找他的接口、父类,依次往上,直到找到或者已经没有接口/父类
问题:此题本身就有问题,不够严谨,没有给出 User 类的实现代码,所以我们并不知道这个 “static” 属性的访问类型啊,是 public/protect/privare/还是包访问类型呢

反射破坏代码的封装性,破坏原有的访问修饰符访问限制

  • 永生堆

JUC

1447

  • Daemon 线程

1.首先我们必须清楚的认识到 java 的线程分为两类: 用户线程和 daemon 线程

A.  用户线程: 用户线程可以简单的理解为用户定义的线程,当然包括 main 线程 (以前我错误的认为 main 线程也是一个 daemon 线程,但是慢慢的发现原来 main 线程不是,因为如果我再 main 线程中创建一个用户线程,并且打出日志,我们会发现这样一个问题,main 线程运行结束了,但是我们的线程任然在运行).

B.  daemon 线程: daemon 线程是为我们创建的用户线程提供服务的线程,比如说 jvm 的 GC 等等,这样的线程有一个非常明显的特征: 当用户线程运行结束的时候,daemon 线程将会自动退出.(由此我们可以推出下面关于 daemon 线程的几条基本特点)

2. daemon 线程的特点:

A.  守护线程创建的过程中需要先调用 setDaemon 方法进行设置,然后再启动线程.否则会报出 IllegalThreadStateException 异常.(个人在想一个问题,为什么不能动态更改线程为 daemon 线程?有时间一个补上这个内容,现在给出一个猜测: 是因为 jvm 判断线程状态的时候,如果当前只存在一个线程 Thread1,如果我们把这个线程动态更改为 daemon 线程,jvm 会认为当前已经不存在用户线程而退出,稍后将会给出正确结论,抱歉!如果有哪位大牛看到,希望给出指点,谢谢!)

B.  由于 daemon 线程的终止条件是当前是否存在用户线程,所以我们不能指派 daemon 线程来进行一些业务操作,而只能服务用户线程.

C.  daemon 线程创建的子线程任然是 daemon 线程.

thread. run 并没有开启一个新线程

Synchronized 的作用主要有三个:

1 原子性:确保线程互斥的访问同步代码;
2 可见性:保证共享变量的修改能够及时可见,其实是通过 Java 内存模型中的 “对一个变量 unlock 操作之前,必须要同步到主内存中;如果对一个变量进行 lock 操作,则将会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中 load 操作或 assign 操作初始化变量值” 来保证的;
3 有序性:有效解决重排序问题,即 “一个 unlock 操作先行发生 (happen-before) 于后面对同一个锁的 lock 操作”;
从语法上讲,Synchronized 可以把任何一个非 null 对象作为 " 锁 ",在 HotSpot JVM 实现中,锁有个专门的名字:对象监视器(Object Monitor)。

Synchronized 总共有三种用法:

1 当 synchronized 作用在实例方法时,监视器锁(monitor)便是对象实例(this);
2 当 synchronized 作用在静态方法时,监视器锁(monitor)便是对象的 Class 实例,因为 Class 数据存在于永久代,因此静态方法锁相当于该类的一个全局锁;
3 当 synchronized 作用在某一个对象实例时,监视器锁(monitor)便是括号括起来的对象实例;

yield

关于下面这段Java程序,哪些描述是正确的:( ) public…_Java专项练习_牛客网 (nowcoder.com)

Thread.yield() 方法作用是:暂停当前正在执行的线程对象,并执行其他线程。

yield() 应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用 yield() 的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证 yield() 达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

结论:yield() 从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield() 将导致线程从运行状态转到可运行状态,但有可能没有效果。
暂停当前正在执行的线程对象,并执行其他线程。

yield() 应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用 yield() 的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证 yield() 达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

结论:yield() 从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield() 将导致线程从运行状态转到可运行状态,但有可能没有效果。

线程的状态

阻塞队列

常见的阻塞队列

  • ArrayBlockingQueue:基于数组,在创建 ArrayBlockingQueue 对象时必须制定容量大小,先进先出队列,有界队列,容量有上限。
  • LinkedBlockingQueue:基于链表,在创建 LinkedBlockingQueue 对象时如果不指定容量大小,默认大小为 Integer.MAX_VALUE,先进先出队列,有界队列,容量有上限。
  • PriorityBlockingQueue:按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。注意,此阻塞队列为无界阻塞队列,即容量没有上限。

线程通信

在 Java 中,常用的线程通信方式有两种,分别是利用 Monitor 实现线程通信、利用 Condition 实现线程通信。线程同步是线程通信的前提,所以究竟采用哪种方式实现通信,取决于线程同步的方式。

如果是采用 synchronized 关键字进行同步,则需要依赖 Monitor(同步监视器)实现线程通信,Monitor 就是锁对象。在 synchronized 同步模式下,锁对象可以是任意的类型,所以通信方法自然就被定义在 Object 类中了,这些方法包括:wait()、notify()、notifyAll()。一个线程通过 Monitor 调用 wait() 时,它就会释放锁并在此等待。当其他线程通过 Monitor 调用 notify() 时,则会唤醒在此等待的一个线程。当其他线程通过 Monitor 调用 notifyAll() 时,则会唤醒在此等待的所有线程。

JDK 1.5 新增了 Lock 接口及其实现类,提供了更为灵活的同步方式。如果是采用 Lock 对象进行同步,则需要依赖 Condition 实现线程通信,Condition 对象是由 Lock 对象创建出来的,它依赖于 Lock 对象。Condition 对象中定义的通信方法,与 Object 类中的通信方法类似,它包括 await()、signal()、signalAll()。通过名字就能看出它们的含义了,当通过 Condition 调用 await() 时当前线程释放锁并等待,当通过 Condition 调用 signal() 时唤醒一个等待的线程,当通过 Condition 调用 signalAll() 时则唤醒所有等待的线程。

Future:是获取一个正在执行线程的结果。对该线程有取消和判断是否执行完毕等操作。

Semaphore:控制某个资源可以被同时访问的个数;

ReentranLock与Synchronized类似,可以访问隐式锁相同的一些基本行为和语义,功能更强大

CountDownLatch 一个线程等待其他多个线程执行完毕,再执行

异常

异常分为运行时异常,非运行时异常和 error,其中 error 是系统异常,只能重启系统解决。非运行时异常需要我们自己补获,而运行异常是程序运行时由虚拟机帮助我们补获,运行时异常包括数组的溢出,内存的溢出空指针,分母为 0 等!
运行时异常,属于隐式抛异常,其主体是 Java 虚拟机,指的是 Java 虚拟机在执行过程中,碰到无法继续执行的异常状态,自动抛出异常

try catch finally

1.执行 try | catch 语句块里面的代码,若有 return,将暂时保存 return 要返回的值,去执行 finally 语句块的代码,最后才 return。

JVM

线程

首先 jvm 中没有进程的概念 ,但是 jvm 中的线程映射为操作系统中的进程,对应关系为 1:1。那这道题的问的就是 jvm 中线程如何异步执行 。 在 jvm 中 是使用监视器锁来实现不同线程的异步执行, 在语法的表现就是 synchronized 。

类加载器 ClassLoader

Bootstrap ClassLoader/启动类加载器
主要负责 jdk_home/lib 目录下的核心 api 或 -Xbootclasspath 选项指定的 jar 包装入工作.

Extension ClassLoader/扩展类加载器
主要负责 jdk_home/lib/ext 目录下的 jar 包或 -Djava.ext.dirs 指定目录下的 jar 包装入工作

System ClassLoader/系统类加载器
主要负责 java -classpath/-Djava.class.path 所指的目录下的类与 jar 包装入工作.

User Custom ClassLoader/用户自定义类加载器 (java.lang.ClassLoader 的子类)
在程序运行期间, 通过 java.lang.ClassLoader 的子类动态加载 class 文件, 体现 java 动态实时类装入特性.

重载和重写

作用范围:重写的作用范围是父类和子类之间;重载是发生在一个类里面参数列表:重载必须不同;重写不能修改返回类型:重载可修改;重写方法返回相同类型或子类抛出异常:重载可修改;重写可减少或删除,一定不能抛出新的或者更广的异常

重载就是一句话:同名不同参,返回值无关。访问修饰符也无关
覆盖/重写:同名同参,子类返回类型小于等于父类方法返回类型

Java 重载的规则:

1、必须具有不同的参数列表;

2、可以有不同的返回类型,只要参数列表不同就可以;

3、可以有不同的访问修饰符;

4、可以抛出不同的异常;

5、方法能够在一个类中或者在一个子类中被重载。

方法的重写:

1、在子类中可以根据需要对从基类中继承来的方法进行重写。

2、重写的方法和被重写的方法必须具有相同方法名称、参数列表,子类返回类型小于等于父类方法返回类型, 子类抛出异常小于等于父类方法抛出异常,返回类型必须要在有继承关系的前提下比较。

3、重写方法不能使用比被重写的方法更严格的访问权限。

IO 流

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZT0vFfkL-1663838465960)(https://uploadfiles.nowcoder.com/images/20200805/643412545_1596634989327_DEF638F8839D3C558612E08DC0A11BFF)]

  • 字符流和字节流的区别

按照流的角色来分,可以分为节点流和处理流。
可以从/向一个特定的 IO 设备(如磁盘、网络)读/写数据的流,称为节点流,节点流也被成为低级流。
处理流是对一个已存在的流进行连接或封装,通过封装后的流来实现数据读/写功能,处理流也被称为高级流。

按照流是否直接与特定的地方(如磁盘、内存、设备等)相连,分为节点流和处理流两类。

  • 节点流:可以从或向一个特定的地方(节点)读写数据。如 FileReader.

  • 处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如 BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。

JAVA 常用的节点流:

  • 文 件 FileInputStream FileOutputStrean FileReader FileWriter 文件进行处理的节点流。

  • 字符串 StringReader StringWriter 对字符串进行处理的节点流。

  • 数 组 ByteArrayInputStream ByteArrayOutputStreamCharArrayReader CharArrayWriter 对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)。

  • 管 道 PipedInputStream PipedOutputStream PipedReaderPipedWriter 对管道进行处理的节点流。

常用处理流(关闭处理流使用关闭里面的节点流)

  • 缓冲流:BufferedInputStrean BufferedOutputStream BufferedReader BufferedWriter 增加缓冲功能,避免频繁读写硬盘。

  • 转换流:InputStreamReader OutputStreamReader 实现字节流和字符流之间的转换。

  • 数据流 DataInputStream DataOutputStream 等 - 提供将基础数据类型写入到文件中,或者读取出来.

流的关闭顺序

另一种情况:看依赖关系,如果流 a 依赖流 b,应该先关闭流 a,再关闭流 b

例如处理流 a 依赖节点流 b,应该先关闭处理流 a,再关闭节点流 b

当然完全可以只关闭处理流,不用关闭节点流。处理流关闭的时候,会调用其处理的节点流的关闭方法

如果将节点流关闭以后再关闭处理流,会抛出 IO 异常;

  1. FileOutputStream fos = new FileOutputStream("f:\\\\william");

  2. OutputStreamWriter osw = newOutputStreamWriter(fos);

  3. BufferedWriter bw = newBufferedWriter(osw);

  4. bw.write("hello world!");

正确关闭流的顺序是:

  1. bw.close();

  2. osw.close();

  3. fos.close();

final

final 表示 最终 的意思,它修饰的类是不能被继承的;final 修饰的方法能被继承(Math 类里就有),但是不能够被重写。其实关系并不复杂,你只需要记住这样一句话:final 可用于声明属性、方法和类,分别表示属性不可变,方法不可重写,类不可继承。当然 final 修饰的方法是可以被重载的。

(36条消息) 【JavaSE基础】小看final关键字?教你如何深入理解和使用_执 梗的博客-CSDN博客

  1. final 修饰变量,则等同于常量

  2. final 修饰方法中的参数,称为最终参数。

  3. final 修饰类,则类不能被继承

  4. final 修饰方法,则方法不能被重写。final 的成员方法除了能读取类的成员变量,还能读取类变量。类的成员变量是没有 static, 类变量是 static 修饰的成员变量

  5. final 不能修饰抽象类

final 修饰的方法可以被重载 但不能被重写,重写相等于另外一个方法了,不违反 final 不可改变的原则。

最终类就是被 final 修饰的类,最终方法就是被 final 修饰的方法。最终类不能被继承,最终方法不能被重写

多态

多态绑定机制:

  1. 实例方法与引用变量所引用的对象的方法绑定;
  2. 静态方法与引用变量所声明的类型的方法绑定;
  3. 成员变量(实例变量、静态变量)与引用变量所声明的类型的成员变量绑定。

正则表达式

大写表示“非”,d 表示 digit 数字。非数字就是 \D, w 表示 word,非单词就是 \W

String

public static String toString(char c) { return String.valueOf(c); }

 public static String valueOf(char c) { char data[] = {c}; return new String(data, true); }

对于这道题,考察的是对 String 类型的认识以及编译器优化。Java 中 String 不是基本类型,但是有些时候和基本类型差不多,如 String b = “tao” ; 可以对变量直接赋值,而不用 new 一个对象(当然也可以用 new)。所以 String 这个类型值得好好研究下。

Java 中的变量和基本类型的值存放于栈内存,而 new 出来的对象本身存放于堆内存,指向对象的引用还是存放在栈内存。例如如下的代码:

int i=1;

String s = new String( “Hello World” );

变量 i 和 s 以及 1 存放在栈内存,而 s 指向的对象”Hello World”存放于堆内存。

栈内存的一个特点是数据共享,这样设计是为了减小内存消耗,前面定义了 i=1,i 和 1 都在栈内存内,如果再定义一个 j=1,此时将 j 放入栈内存,然后查找栈内存中是否有 1,如果有则 j 指向 1。如果再给 j 赋值 2,则在栈内存中查找是否有 2,如果没有就在栈内存中放一个 2,然后 j 指向 2。也就是如果常量在栈内存中,就将变量指向该常量,如果没有就在该栈内存增加一个该常量,并将变量指向该常量。


如果 j++,这时指向的变量并不会改变,而是在栈内寻找新的常量(比原来的常量大 1),如果栈内存有则指向它,如果没有就在栈内存中加入此常量并将 j 指向它。这种基本类型之间比较大小和我们逻辑上判断大小是一致的。如定义 i 和 j 是都赋值 1,则 i == j 结果为 true。
== 用于判断两个变量指向的地址是否一样。i == j 就是判断 i 指向的 1 和 j 指向的 1 是同一个吗?当然是了。对于直接赋值的字符串常量(如 String s=“Hello World”;中的 Hello World)也是存放在栈内存中,而 new 出来的字符串对象(即 String 对象)是存放在堆内存中。如果定义 String s=“Hello World”和 String w=“Hello World”,s == w 吗?肯定是 true,因为他们指向的是同一个 Hello World。

堆内存没有数据共享的特点,前面定义的 String s = new String( “Hello World” ); 后,变量 s 在栈内存内,Hello World 这个 String 对象在堆内存内。如果定义 String w = new String( "Hello World" );,则会在堆内存创建一个新的 String 对象,变量 w 存放在栈内存,w 指向这个新的 String 对象。堆内存中不同对象(指同一类型的不同对象)的比较如果用 == 则结果肯定都是 false,比如 s == w?当然不等,s 和 w 指向堆内存中不同的 String 对象。如果判断两个 String 对象相等呢?用 equals 方法。

说了这么多只是说了这道题的铺垫知识,还没进入主题,下面分析这道题。 MESSAGE 成员变量及其指向的字符串常量肯定都是在栈内存里的,变量 a 运算完也是指向一个字符串“ taobao ”啊?是不是同一个呢?这涉及到编译器优化问题。对于字符串常量的相加,在编译时直接将字符串合并,而不是等到运行时再合并。也就是说
String a = “tao” + “bao” ; 和 String a = “taobao” ; 编译出的字节码是一样的。所以等到运行时,根据上面说的栈内存是数据共享原则,a 和 MESSAGE 指向的是同一个字符串。而对于后面的 (b+c) 又是什么情况呢?b+c 只能等到运行时才能判定是什么字符串,编译器不会优化,想想这也是有道理的,编译器怕你对 b 的值改变,所以编译器不会优化。运行时 b+c 计算出来的 “taobao” 和栈内存里已经有的 “taobao” 是一个吗?不是。b+c 计算出来的 “taobao” 应该是放在堆内存中的 String 对象。这可以通过 System. out .println( (b+c)== MESSAGE ); 的结果为 false 来证明这一点。如果计算出来的 b+c 也是在栈内存,那结果应该是 true。Java 对 String 的相加是通过 StringBuffer 实现的,先构造一个 StringBuffer 里面存放”tao”,然后调用 append() 方法追加”bao”,然后将值为”taobao”的 StringBuffer 转化成 String 对象。StringBuffer 对象在堆内存中,那转换成的 String 对象理所应当的也是在堆内存中。下面改造一下这个语句 System.out.println( (b+c).intern()== MESSAGE ); 结果是 true, intern() 方法 先检查 String 池 ( 或者说成栈内存 ) 中是否存在相同的字符串常量,如果有就返回。所以 intern() 返回的就是 MESSAGE 指向的 “taobao”。再把变量 b 和 c 的定义改一下,

final  String b =  "tao" ;

final  String c =  "bao" ;
System. out . println ( (b+c)== MESSAGE );

现在 b 和 c 不可能再次赋值了,所以编译器将 b+c 编译成了”taobao”。因此,这时的结果是 true。

在字符串相加中,只要有一个是非 final 类型的变量,编译器就不会优化,因为这样的变量可能发生改变,所以编译器不可能将这样的变量替换成常量。例如将变量 b 的 final 去掉,结果又变成了 false。这也就意味着会用到 StringBuffer 对象,计算的结果在堆内存中。

如果对指向堆内存中的对象的 String 变量调用 intern() 会怎么样呢?实际上这个问题已经说过了,(b+c).intern(),b+c 的结果就是在堆内存中。对于指向栈内存中字符串常量的变量调用 intern() 返回的还是它自己,没有多大意义。它会根据堆内存中对象的值,去查找 String 池中是否有相同的字符串,如果有就将变量指向这个 string 池中的变量。

String a = "tao"+"bao";

String b = new String("taobao");
System.out.println(a==MESSAGE); //true

System.out.println(b==MESSAGE);  //false

b = b.intern();

System.out.println(b==MESSAGE); //true

System. out .println(a==a.intern());  //true

instance of

instance 是 java 的二元运算符,用来判断他左边的对象是否为右面类(接口,抽象类,父类)的实例

命令行

java,exe 是 java 虚拟机

javadoc.exe 用来制作 java 文档

jdb.exe 是 java 的调试器

javaprof, exe 是剖析工具

Servlet

JEE5.0 中的 Servlet 相关的就下面这几个包:
javax.servlet
javax.servlet.jsp
java.servlet.jsp.el
java.servlet.jsp.tagext
而最用得多的就是
javax.servlet
javax.servlet.http
这两个包了.

基本数据类型

字节数及表示范围

1.  默认值         取值范围 示例
    

字节型 : 0 -2^7—-2^7-1 byte b=10;

字符型 : ‘ \u0000′ 0—-2^16-1         char c=’c’ ;

short : 0 -2^15—-2^15-1 short s=10;

int : 0 -2^31—-2^31-1 int i=10;

long : 0 -2^63—-2^63-1     long o=10L;

float : 0.0f -2^31—-2^31-1 float f=10.0F

double : 0.0d -2^63—-2^63-1 double d=10.0;

boolean: false true\false boolean flag=true;

类型转换(含包装类)

java 中整型默认的是 int,浮点默认的是 double.

B: double 类型的 11.1 转成 float,是需要强制转换的

C: double 类型的 0.0 转成 int,也是需要强制转换的

D: int 转为 封装类型 Double,是无法编译的
Double oD = 3.0,会把 double 类型的 3.0 自动装箱为 Double,没有问题

向下转换类型需要强制转换,自动装箱不提供向上装型。

包装类的“==”运算在不遇到算术运算的情况下不会自动拆箱

包装类的equals()方法不处理数据转型

NIO 网络编程

  • 网络编程

UDP

Java 的 java.net 包中,提供了两个类 DatagramSocket 和 DatagramPacket 来支持 UDP 的数据报(Datagram)通信

其中 DatagramSocket 用于在程序之间建立传送数据报的通信通道,DatagramPacket 则用来表示一个数据报。DatagramSocket 发送的每个包都需要指定地址,而 DatagramPacket 则是在首次创建时指定地址,以后所有数据的发生都通过此 socket。

UDP 的客户端编程也是 4 个部分:建立连接、发送数据、接受数据和关闭连接。

https://blog.csdn.net/qq_38180223/article/details/81502584

TCP

Java 实现 TCP 数据传输涉及到的类有 Socket、ServerSocket;TCP 分客户端服务端,而 UDP 不分客户端服务端

https://blog.csdn.net/yy455363056/article/details/80210461

HttpServletResponse

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WU1auzko-1663838465964)(https://uploadfiles.nowcoder.com/images/20211023/195264425_1634958669310/45D0CE969235AC19CDD80998E3992563)]
1、同名Header可以有多个 ,Header[] getHeaders(String name)。
2、运行时使用的是第一个, Header getFirstHeader(String name)。
3、addHeader,如果同名header已存在,则追加至原同名header后面。
4、setHeader,如果同名header已存在,则覆盖一个同名header。

集合

Hashtable
(1)Hashtable 是一个散列表,它存储的内容是键值对 (key-value) 映射。
(2)Hashtable 的函数都是同步的,这意味着它是 线程安全 的。它的 key、value 都不可以为 null。
(3)HashTable 直接使用对象的 hashCode。
HashMap:
(1)由 数组 + 链表 组成的,基于 哈希表的 Map 实现,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的。
(2)不是线程安全的,HashMap 可以接受为 null 的键 (key) 和值 (value)。
(3)HashMap 重新计算 hash 值
Hashtable, HashMap, Properties 继承关系如下:

public class Hashtable<K,V> extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable

public class HashMap<K,V>extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable  

HashTable详解

Queue

1、LinkedBlockingQueue:基于链接节点的可选限定的 blocking queue (队列) 。 这个队列排列元素 FIFO(先进先出)。队列的头部是队列中最长的元素。 队列的尾部是队列中最短时间的元素。 新元素插入队列的尾部,队列检索操作获取队列头部的元素。 链接队列通常具有比基于阵列的队列更高的吞吐量,但在大多数并发应用程序中的可预测性能较低。

blocking queue 说明:不接受 null 元素;可能是容量有限的;实现被设计为主要用于生产者 - 消费者队列;不支持任何类型的“关闭”或“关闭”操作,表示不再添加项目实现是线程安全的;

2、PriorityQueue:

  • 2.1、基于优先级堆的无限优先级 queue (队列) 。 优先级队列的元素根据它们的有序 natural ordering ,或由一个 Comparator (比较) 在队列构造的时候提供,这取决于所使用的构造方法。 优先队列不允许 null (空) 元素。 依靠自然排序的优先级队列也不允许插入不可比较的对象(这样做可能导致 ClassCastException )。

  • 2.2、该队列的头部是相对于指定顺序的最小元素。 如果多个元素被绑定到最小值,那么头就是这些元素之一 - 关系被任意破坏。 队列检索操作 poll , remove , peek 和 element 访问在队列的头部的元件。

  • 2.3、优先级队列是无限制的,但是具有管理用于在队列上存储元素的数组的大小的内部容量 。 它始终至少与队列大小一样大。 当元素被添加到优先级队列中时,其容量会自动增长。 没有规定增长政策的细节。

  • 2.4、该类及其迭代器实现 Collection 和 Iterator 接口的所有可选方法。 方法 iterator() 中提供的迭代器不能保证以任何特定顺序遍历优先级队列的元素。 如果需要有序遍历,请考虑使用 Arrays.sort(pq.toArray()) 。

  • 2.5、请注意,此实现不同步。 如果任何线程修改队列,多线程不应同时访问 PriorityQueue 实例。 而是使用线程安全的 PriorityBlockingQueue 类。

实现注意事项:此实现提供了 O(log(n))的时间入队和出队方法( offer , poll , remove() 和 add ); remove(Object) 和 contains(Object) 方法的线性时间; 和恒定时间检索方法( peek (偷看) , element 和 size )。

3、ConcurrentLinkedQueue:基于链接节点的并发 deque(deque 是双端队列) 。 并发插入,删除和访问操作可以跨多个线程安全执行。 A ConcurrentLinkedDeque 是许多线程将共享对公共集合的访问的适当选择。像大多数其他并发集合实现一样,此类不允许使用 null 元素。

ArrayList, LinkedList, Vector 的区别是什么?

  • ArrayList: 内部采用数组存储元素,支持高效随机访问,支持动态调整大小

  • LinkedList: 内部采用链表来存储元素,支持快速插入/删除元素,但不支持高效地随机访问

  • Vector: 可以看作线程安全版的 ArrayList

String, StringBuilder, StringBuffer 的区别是什么?

  • String: 不可变的字符序列,若要向其中添加新字符需要创建一个新的 String 对象

  • StringBuilder: 可变字符序列,支持向其中添加新字符(无需创建新对象)

  • StringBuffer: 可以看作线程安全版的 StringBuilder

  • Map, Set, List, Queue、Stack 的特点及用法。

  • Map<K, V>: Java 中存储键值对的数据类型都实现了这个接口,表示“映射表”。支持的两个核心操作是 get(Object key) 以及 put(K key, V value),分别用来获取键对应的值以及向映射表中插入键值对。

    • Set<E>: 实现了这个接口的集合类型中不允许存在重复的元素,代表数学意义上的“集合”。它所支持的核心操作有 add(E e),remove(Object o), contains(Object o),分别用于添加元素,删除元素以及判断给定元素是否存在于集中。

    • List<E>: Java 中集合框架中的列表类型都实现了这个接口,表示一种有序序列。支持 get(int index), add(E e) 等操作。

    • Queue<E>: Java 集合框架中的队列接口,代表了“先进先出”队列。支持 add(E element), remove() 等操作。

    • Stack<E>:Java 集合框架中表示堆栈的数据类型,堆栈是一种“后进先出”的数据结构。支持 push(E item), pop() 等操作。

  • HashMap 和 HashTable 的区别

    • HashTable 是线程安全的,而 HashMap 不是

    • HashMap 中允许存在 null 键和 null 值,而 HashTable 中不允许

面向对象

编译看左边,运行看右边

成员变量和局部变量
😁成员变量:在类范围里定义的变量,Field

  1. 类 Field:定义变量时有 static 修饰的就是类 Field
  2. 实例 Field : 定义 Field 是没有 static 修饰的就是实例 Field

😁局部变量:是在方法体里定义的变量。局部变量除了形参之外,都必需显式初始化

  • 方法局部变量
  • 代码块局部变量
  • 形参:在定义方法签名时定义的变量,形参的作用域在整个方法内有效

从语法上,在同一个 Class 里,成员变量的作用范围在整个类内有效,一个类不能定义两个同名的成员变量;一个方法类里面不能定义两个同名的局部变量。

Java 中允许局部变量和成员变量同名。如果方法里的局部变量和成员变量同名,局部变量回覆盖成员变量。【就近原则】

如果需要在此方法中引用被覆盖的成员变量,可以使用 this(对于实例 Field)或类名(类 Field)作为调用者来限定访问成员变量。

尝试编译以下程序会产生怎么样的结果?()

public class MyClass {
    long var;
    public void MyClass(long param) { var = param; }//(1)//这个不是构造方法
    public static void main(String[] args) {
        MyClass a, b;
        a =new MyClass();//(2)
        b =new MyClass(5);//(3)
    }
}

编译错误将发生在(1),因为构造函数不指定返回值
编译错误将发生在(2),因为该类没有默认构造函数
编译错误将在(3)处发生,因为该类没有构造函数,该构造函数接受一个 int 类型的参数
该程序将正确编译和执行
类的普通方法名称可与构造方法名称相等!!!!类的构造方法没有返回值这一说,所以也不能写 void。

类内部的方法可以直接调用静态私有变量

构造方法不能被子类继承,所以用 final 修饰没有意义。构造方法用于创建一个新的对象,不能作为类的静态方法,所以用 static 修饰没有意义。此外,Java 语言不支持 native 或 synchronized 的构造方法。

初始化

在调用子类构造器之前,会先调用父类构造器,当子类构造器中没有使用 “super(参数或无参数)” 指定调用父类构造器时,是 默认调用父类的无参构造器,如果父类中包含有参构造器,却没有无参构造器,则在子类构造器中一定要使用“super(参数)”指定调用父类的有参构造器,不然就会报错。

比较详细的 Java 初始化的加载顺序

先把静态给过一遍,然后非静态,最后构造。父完成再子进行。成员变量早于 代码块。

一般情况:

静态成员变量

静态代码块

非静态成员变量

非静态代码块

构造方法

如果是有父类的:

父类静态成员变量

父类静态代码块

子类静态成员变量

子类静态代码块

父类非静态成员变量

父类非静态代码块

父类构造方法

子类非静态成员变量

子类非静态代码块

子类构造方法

super 和 this 的异同:

  1. super(参数):调用基类中的某一个构造函数(应该为构造函数中的第一条语句)

  2. this(参数):调用本类中另一种形成的构造函数(应该为构造函数中的第一条语句)

  3. super: 它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员数据或函数,基类与派生类中有相同成员定义时如:super.变量名 super.成员函数据名(实参)

  4. this:它代表当前对象名(在程序中易产生二义性之处,应使用 this 来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用 this 来指明成员变量名)

  5. 调用 super() 必须写在子类构造方法的第一行,否则编译不通过。每个子类构造方法的第一条语句,都是隐含地调用 super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错

  6. super() 和 this() 类似,区别是,super() 从子类中调用父类的构造方法,this() 在同一类内调用其它方法。

7)super() 和 this() 均需放在构造方法内第一行。

  1. 尽管可以用 this 调用一个构造器,但却不能调用两个。

  2. this 和 super 不能同时出现在一个构造函数里面,因为 this 必然会调用其它的构造函数,其它的构造函数必然也会有 super 语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。

  3. this() 和 super() 都指的是对象,所以,均不可以在 static 环境中使用。包括:static 变量,static 方法,static 语句块。

  4. 从本质上讲,this 是一个指向本对象的指针, 然而 super 是一个 Java 关键字。

Object

object 类的结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CxDsIHiM-1663838465964)(http://qiniu.tomoya.top/20161205181623207)]
如图可知,Object 类有 12 个成员方法,按照用途可以分为以下几种
1,构造函数
2,hashCode 和 equale 函数用来判断对象是否相同,
3,wait(),wait(long),wait(long,int),notify,notifyAll()
4,toString() 和 getClass,
5,clone()
6,finalize() 用于在垃圾回收

函数说明

clone()

clone() 函数的用途是用来另存一个当前存在的对象。

hashCode() 和 equale()

  • equale() 用于确认两个对象是否相同。

  • hashCode() 用于获取对象的哈希值,这个值的作用是检索,具体的作用可以参考 这里

  • 哈希值相同的对象不一定 equale()

  • equale() 返回 true 的两个对象一定相同。

toString() 和 getClass()

toString() 返回一个 String 对象,用来标识自己
getClass() 返回一个 Class 对象,如果打印出来会发现结果是如下格式

class package.name.xxx

因为返回的是一个 class 对象,后面可以跟 class 类的方法。用的是谁的构造函数,那么 getClass 返回的就是谁的类型。
getClass() 经常用于 java 反射机制

wait(),wait(long),wait(long,int),notify(),notifyAll()

  • 这几个函数体现的是 Java 的多线程机制

  • 在使用的时候要求在 synchronize 语句中使用

  • wait() 用于让当前线程失去操作权限,当前线程进入等待序列

  • notify() 用于随机通知一个持有对象的锁的线程获取操作权限

  • notifyAll() 用于通知所有持有对象的锁的线程获取操作权限

  • wait(long) 和 wait(long,int) 用于设定下一次获取锁的距离当前释放锁的时间间隔

finalize()

这个函数在进行垃圾回收的时候会用到,匿名对象回收之前会调用到,具体的例子如图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nVXM9uHK-1663838465965)(http://qiniu.tomoya.top/20161206085522582)]

访问控制(Access Control)

Java 中有 4 个级别的访问权限,从高到低如下所示:

  • public:在任何地方都是可见的

  • protected:仅在自己的包中、自己的子类中可见

  • 无修饰符(package-private):仅在自己的包中可见

  • private:仅在自己的类中可见

使用注意:

  • 上述 4 个访问权限都可以修饰类的成员,比如成员变量、方法、嵌套类(Nested Class)等

  • 只有 public、无修饰符(package-private)可以修饰 顶级类(Top-level Class)

  • 上述 4 个访问权限不可以修饰局部类(Local Class)、局部变量

  • 一个 Java 源文件中可以定义多个顶级类,public 顶级类的名字必须和文件名一样

内部类

抽象类

含有 abstract 修饰符的 class 即为抽象类,abstract 类不能创建的实例对象。含有 abstract 方法的类必须定义为 abstract class,abstract class 类中的方法不必是抽象的。abstract class 类中定义抽象方法必须在具体 (Concrete) 子类中实现,所以,不能有抽象构造方法或抽象静态方法。如果的子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为 abstract 类型。
接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为 public abstract 类型,接口中的成员变量类型默认为 public static final。
下面比较一下两者的语法区别:
1.抽象类可以有构造方法,接口中不能有构造方法。
2.抽象类中可以有普通成员变量,接口中没有普通成员变量
3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
4. 抽象类中的抽象方法的访问类型可以是 public,protected 和(默认类型,虽然
eclipse 下不报错,但应该也不行),但接口中的抽象方法只能是 public 类型的,并且默认即为 public abstract 类型。
5. 抽象类中可以包含静态方法,接口中不能包含静态方法
6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是 public static final 类型,并且默认即为 public static final 类型。
7. 一个类可以实现多个接口,但只能继承一个抽象类。
下面接着再说说两者在应用上的区别:
接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约。而抽象类在代码实现方面发挥作用,可以实现代码的重用,

例如,模板方法设计模式是抽象类的一个典型应用,假设某个项目的所有 Servlet 类都要用相同的方式进行权限判断、记录访问日志和处理异常,那么就可以定义一个抽象的基类,让所有的 Servlet 都继承这个抽象基类,在抽象基类的 service 方法中完成权限判断、记录访问日志和处理异常的代码,在各个子类中只是完成各自的业务逻辑代码,伪代码如下:

package com.lei;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public abstract class BaseServlet extends HttpServlet {

 /**
 * serialVersionUID属性概述
 * 
 */
 private static final long serialVersionUID = 1L;

 public final void service(HttpServletRequest request,
 HttpServletResponse response) throws IOException, ServletException {
 // 记录访问日志
 // 进行权限判断
 if (true)// if条件里写的是“具有权限”
 {
 try {
 doService(request, response);
 } catch (IOException e) {
 // 记录异常信息
 }
 }

 }

 protected abstract void doService(HttpServletRequest request,
 HttpServletResponse response) throws IOException, ServletException;
}

	



实现类如下:

package com.lei;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyServlet extends BaseServlet{

 /**
 * serialVersionUID属性概述
 * 
 */
 private static final long serialVersionUID = 1L;

 @Override
 protected void doService(HttpServletRequest request,
 HttpServletResponse response) throws IOException, ServletException {
 // TODO Auto-generated method stub
 
 }

}


父类方法中间的某段代码不确定,留给子类干,就用模板方法设计模式。
备注:这道题的思路是先从总体解释抽象类和接口的基本概念,然后再比较两者的语法细节,最后再说两者的应用区别。
比较两者语法细节区别的条理是:先从一个类中的构造方法、普通成员变量和方法(包括抽象方法),静态变量和方法,继承性等 6 个方面逐一去比较回答,接着从第三者继承的角度的回答,特别是最后用了一个典型的例子来展现自己深厚的技术功底。

staic

static方法属于静态绑定,只需要看声明时类型就可以判断运行时调用的是哪个方法,即Super。

后面调用的都是 Super 的 getType 方法,输出 3 次

以下代码执行的结果是多少()?_Java专项练习_牛客网 (nowcoder.com)

这是静态分派的过程,在编译时已经决定了使用 super 的方法,因为 subToSuper 是指 super 对象,可是为什么会选择 collection 呢,for 循环出来他们实际上指的是 collection 对象表示的,即类似于 Collection col = new HashSet <> (); 这样传入方法 getType()中的参数就是 col,左边是静态类型,右边是实际类型。由于重载实际上是使用静态分派的,重载时是通过参数的静态类型而不是实际类型作为判定依据的。详细参考深入理解 java 虚拟机 248 页解释。

static方法属于静态绑定,只需要看声明时类型就可以判断运行时调用的是哪个方法,即Super。

后面调用的都是Super的getType方法,输出3次

数组

真数组

程序设计语言中,数组元素在内存中是一个接着一个线性存放的,通过第一个元素就能访问随后的元素,这样的数组称之为“真数组”。

实现了真数组为 Java 语言健壮性的特点之一。

二维数组定义

定义二维数组

int [ ] a [ ]=new int [10] [10];

int a [ ] [ ]=new int [10] [10];

int [ ] [ ] a=new int [10] [10];    

三种方式均可,一维,三维,四维以此类推。

JDBC

Jdbc 六大步骤: 1.注册驱动 2.获取连接 3.创建语句对象 4.执行 sql 5.处理语句集 6 关闭连接

switch

jdk1.7之前byte,short ,int ,char
jdk1.7之后加入String
java8,switch支持10种类型
基本类型:byte char short int
包装类 :Byte,Short,Character,Integer String enum
实际只支持int类型 Java实际只能支持int类型的switch语句,那其他的类型时如何支持的
a、基本类型byte char short 原因:这些基本数字类型可自动向上转为int, 实际还是用的int。
b、基本类型包装类Byte,Short,Character,Integer 原因:java的自动拆箱机制 可看这些对象自动转为基本类型
c、String 类型 原因:实际switch比较的string.hashCode值,它是一个int类型
d、enum 类型原因 :实际比较的是 enum 的 ordinal 值(表示枚举值的顺序),它也是一个 int 类型所以也可以说 switch 语句只支持 int 类型

本文含有隐藏内容,请 开通VIP 后查看