错题一
- 选项中哪一行代码可以替换
//add code here
而不产生编译错误:
public abstract class MyClass {
public int constInt = 5;
//add code here
public void method() {
}
}
1、抽象类不一定有抽象方法,有抽象方法的类一定是抽象类
2、抽象类中可以有构造方法,抽象类不能进行实例化,那么要构造方法有什么作用呢? 用于子类访问父类数据时的初始化;
3、抽象类不能直接实例化那么,抽象类如何实例化呢? 按照多态的方式,由具体的子类实例化;其实这也是多态的一种,抽象类多态。
4、抽象类的子类,要么也是抽象类,要么必须重写抽象类中的所有抽象方法;
【抽象类的成员特点】:
成员变量:既可以是变量,也可以是常量。 成员变量不能直接进行运算,可以写在代码块{}或者静态代码块中static{}中;
构造方法:有,用于子类访问父类数据的初始化;
成员方法:既可以是抽象的,也可以是非抽象的(default关键字修饰的方法);
抽象方法:强制要求子类做的事情;
非抽象方法:子类继承的事情,提高代码复用性;
错题二
- 下面有关servlet和cgi的描述,说法错误的是?
【CGI(Common Gateway Interface),通用网关接口】
- 通用网关接口,简称CGI,是一种根据请求信息动态产生回应内容的技术。通过CGI,Web 服务器可以将根据请求不同启动不同的外部程序,并将请求内容转发给该程序,在程序执行结束后,将执行结果作为回应返回给客户端。
- 也就是说,
对于每个请求,都要产生一个新的进程进行处理
。因为每个进程都会占有很多服务器的资源和时间,这就导致服务器无法同时处理很多的并发请求。 - 另外CGI程序都是与操作系统平台相关的,虽然在互联网爆发的初期,CGI为开发互联网应用做出了很大的贡献,但是随着技术的发展,开始逐渐衰落。
【Servlet】
- Servlet最初是在1995年由James Gosling 提出的,因为使用该技术需要复杂的Web服务器支持,所以当时并没有得到重视,也就放弃了。后来随着Web应用复杂度的提升,并要求提供更高的并发处理能力,Servlet被重新捡起,并在Java平台上得到实现,
现在提起Servlet,指的都是Java Servlet
。 - Java Servlet要求必须运行在Web服务器当中,与Web服务器之间属于分工和互补关系。确切的说,
在实际运行的时候Java Servlet与Web服务器会融为一体,如同一个程序一样运行在同一个Java虚拟机(JVM)当中
。与CGI不同的是,Servlet对每个请求都是单独启动一个线程,而不是进程。这种处理方式大幅度地降低了系统里的进程数量,提高了系统的并发处理能力
。 - 另外因为Java Servlet是运行在虚拟机之上的,也就解决了跨平台问题。如果没有Servlet的出现,也就没有互联网的今天。
- 在Servlet出现之后,随着使用范围的扩大,人们发现了它的一个很大的一个弊端。那就是为了能够输出HTML格式内容,需要编写大量重复代码,造成不必要的重复劳动。为了解决这个问题,基于Servlet技术产生了JavaServet Pages技术,也就是JSP。
- Servlet和JSP两者分工协作,Servlet侧重于解决运算和业务逻辑问题,JSP则侧重于解决展示问题。 Servlet与JSP一起为Web应用开发带来了巨大的贡献,后来出现的众多Java Web应用开发框架都是基于这两种技术的,更确切的说,都是基于Servlet技术的。
- Servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁。
- 而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于Servlet 。
错题三
- Test.main()函数执行后的输出是( )
class Test {
public static void main(String[] args) {
System.out.println(new B().getValue());
}
static class A {
protected int value;
public A (int v) {
setValue(v);
}
public void setValue(int value) {
this.value= value;
}
public int getValue() {
try {
value ++;
return value;
} finally {
this.setValue(value);
System.out.println(value);
}
}
}
static class B extends A {
public B () {
super(5);
setValue(getValue()- 3);
}
public void setValue(int value) {
super.setValue(2 * value);
}
}
}
本题中需要遵循一个原则:除非是super.xxx显示调用父类的方法或子类没有该方法时,其余情况都是执行子类重写过后的方法;
这道题本身过程比较多,容易做错;
错题四
- Collection继承体系如下:
java.lang.Iterable
接口被Collection继承;
错题五
- 关于Java中的ClassLoader下面的哪些描述是错误的:( )
关于类加载器,之前我写了一篇文章详解:
学习了其他人的评论之后,总结:
1、从Java虚拟机的角度讲,只有两种类加载器:
启动类加载器:使用C++实现,是虚拟机的一部分;
其他所有类加载器:由Java语言实现,独立于虚拟机之外,全部继承自抽象类;
从开发人员的角度来看,类加载器还可以划分为:
启动类加载器(Bootstrap ClassLoader): 负责加载存放在
<JAVA_HOME>/lib目录中或者被-Xbootclasspath参数
所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符的类库即使放在lib目录中也不会被加载)类库加载到虚拟机中内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,直接使用null代替即可。
扩展类加载器(Extension ClassLoader):这个类加载器由sun.misc.Launcher$ExtClassLoader
实现,负责加载<JAVA_HOME>/lib/ext
目录中的,或者被java.ext.dirs
系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
应用类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher$AppClassLoader
实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法
的返回值,所以也称它为系统类加载器(System ClassLoader)。他负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。对此,如果有必要开发者可以加入自己定义的类加载器。
2、 一般对于我们Java程序员来说,类的加载使用的是双亲委派模型,即当一个类需要加载时,会将类传给Application ClassLoader(但是Application ClassLoader并不会加载),不管它是否能加载,而是传给它的"父类" Extension ClassLoader,Extension ClassLoader同样不会加载,同样传给 Bootstrap ClassLoader(注意不是我们常说的那种父类,但是可以这样理解),这时Bootstrap ClassLoader会判断它是否能加载,能加载就直接加载了,不能加载就传给Extension ClassLoader,Extension ClassLoader同样的判断是否能加载,能加载就直接加载,不能加载就传给Application ClassLoader,然后Application ClassLoader也判断能否加载,如果还是不能加载应该就是报ClassNotFoundException
了。这就是双亲委托模型的简单理解。
3、对于上面的"父类"为什么要打引号,因为它们并不是真的像Java中继承的关系,而是组合
的关系,即在"子类"中存在一个成员变量指向"父类"的引用。
4、一个类只需要加载一次就够了,所以要保证线程安全。
5、之所以使用双亲委托机制是为了保证Java程序的稳定运作
,比如当你使用的不是双亲委托模型的时候,然后刚好开发者又定义了一个类,一个java.lang.String这样一个类,如果不使用双亲委托模型,当类加载的时候就有可能会加载开发者定义的String类,这导致了java代码的一片混乱,可读性极差。所以可以这么说,不同的类加载器加载出来的类是不一样的,不同的类加载器加载同一个类会在方法区产生两个不同的类,彼此不可见,并且在堆中生成不同的Class实例
。对于接口,其实就是一个特殊的类,和类一样,在堆中产生不同的class对象。
错题六
- 下面哪些描述是正确的:()
public class Test {
public static class A {
private B ref;
public void setB(B b) {
ref = b;
}
}
public static Class B {
private A ref;
public void setA(A a) {
ref = a;
}
}
public static void main(String args[]) {
…
start();
….
}
public static void start() { A a = new A();
B b = new B();
a.setB(b);
b = null; //
a = null;
…
}
}
- 内存如下:
a -> "a(b)"
b -> "b"
a引用指向一块空间,这块空间里面包含着b对象;
b引用指向一块空间,这块空间是b对象 A选项,b = null执行后b可以被垃圾回收。
这里"b可以被垃圾回收"中的b指的是引用b指向的内存,这块内存即使不被引用b指向,还是被引用a指向着,不会被回收;
从代码中可以看到,a = null是在b = null后执行的,该行执行后,引用a和b都没有指向对象,对象会被回收。
错题七
- 关于Java以下描述正确的有( )
A:native是由调用本地方法库(如操作系统底层函数),可以由C,C++实现,A正确
B:import是用于导包语句,其前面可以出现package,用来声明包的,B错误
C:接口方法的修饰符可以是:public,abstract,default,static(后两者需要有{}),C正确
D:构造方法可以用private,protected,default,private,D错误
错题八
- 在Java语言中,下列关于字符集编码(Character set encoding)和国际化(i18n)的问题,哪些是正确的?
很多人都把Unicode编码挂在嘴边,其实咱们现实生活中遇到的编码基本都是Unicode的
因为Unicode兼容了大多数老版本的编码规范例如 ASCII
Unicode编码定义了这个世界上几乎所有字符(就是你眼睛看到的长那个样子的符号)的数字表示
也就是说Unicode为每个字符发了一张身份证,这张身份证上有一串唯一的数字ID确定了这个字符
在这个纷乱世界上存在的唯一性。Unicode给这串数字ID起了个名字叫[码点](Code Point)
而很多人说的编码其实是想表达[Unicode转换格式](即UTF,Unicode Transformation Formats)
有没有觉得眼前一亮豁然开朗?没错 这就是我们看到的UTF-8/UTF-16/UTF-32的前缀来源
这个[Unicode转换格式]的存在是为了解决[码点]在计算机中的二进制表现形式而设计的
毕竟我们的机内表示涉及存储位宽,兼容古老编码格式,码点是数值过大的罕见字符等问题
[码点]经过映射后得到的二进制串的转换格式单位称之为[码元](Code Unit)。也就是说如果有一种UTF的码点二进制表示有n字节,其码元为8位(1个byte),那么其拥有码元n个。每种UTF的码元都不同,其宽度被作为区分写在了UTF的后缀——这就是UTF-8/UTF-16/UTF-32的由来。UTF-8的码元是8位的,UTF-16的码元是16位的。大部分的编程语言采用16位的码元作为机内表示。这就是我们在各种语言中调用获取一个字符串中character的数量时会出现这么多混乱的原因。事实上我们调用这些方法时取得的不是字符个数,而是码元个数!一旦我们的字符串中包含了位于基本平面之外的码点,那么就会需要更多的码元来表示,这个时候就会出现测试时常见的困惑——为何return的字符数比实际字符数要多?所以实际写代码时要特别注意这个问题。
采取不同的映射方式可以得到不同格式的二进制串,但是他们背后所表示的[码点]永远是一致的就好像你换身份证但是身份证号不变一样。由于平时人们误把[转换格式]也称为[编码],所以造成今天Unicode/UTF傻傻分不清楚且遣词造句运用混乱的悲桑局面。
Unicode 编码 发展到今天 扩展到了 21 位(从 U+0000 到 U+10FFFF )。这一点很重要: Unicode 不是 16 位的编码, 它是 21 位的。这 21 位提供了 1,114,112 个码点,其中,只有大概 10% 正在使用,所以还有相当大的扩充空间。
编码空间被分成 17 个平面(plane),每个平面有 65,536 个字符(正好填充2个字节,16位)。0 号平面叫做「基本多文种平面」( BMP, Basic Multilingual Plane ),涵盖了几乎所有你能遇到的字符,除了 emoji(emoji位于1号平面 - -)。其它平面叫做补充平面,大多是空的。
总结一下各种编码格式的特质:
UTF-32
最清楚明了的一个 UTF 就是 UTF-32 :它在每个码点上使用整 32 位。32 大于 21,因此每一个 UTF-32 值都可以直接表示对应的码点。尽管简单,UTF-32却几乎从来不在实际中使用,因为每个字符占用 4 字节太浪费空间了。
UTF-16 以及「***对」( Surrogate Pairs )的概念
UTF-16要常见得多,它是根据有 16 位固定长度的码元( code units )定义的。UTF-16 本身是一种长度可变的编码。基本多文种平面(BMP)中的每一个码点都直接与一个码元相映射。鉴于 BMP 几乎囊括了所有常见字符,UTF-16 一般只需要 UTF-32 一半的空间。其它平面里很少使用的码点都是用两个 16 位的码元来编码的,这两个合起来表示一个码点的码元就叫做***对( surrogate pair )。
UTF-8
UTF-8 使用一到四个字节来编码一个码点。从 0 到 127 的这些码点直接映射成 1 个字节(对于只包含这个范围字符的文本来说,这一点使得 UTF-8 和 ASCII 完全相同)。接下来的 1,920 个码点映射成 2 个字节,在 BMP 里所有剩下的码点需要 3 个字节。Unicode 的其他平面里的码点则需要 4 个字节。UTF-8 是基于 8 位的码元的,因此它并不需要关心字节顺序(不过仍有一些程序会在 UTF-8 文件里加上多余的 BOM)。
有效率的空间使用(仅就西方语言来讲),以及不需要操心字节顺序问题使得 UTF-8 成为存储和交流 Unicode 文本方面的最佳编码。它也已经是文件格式、网络协议以及 Web API 领域里事实上的标准了。
我们的JVM中保存码点是UTF16的转换格式,从char的位宽为16位也可以看得出来。由于绝大部分编码的码点位于基本平面,所以使用16位可以几乎表示所有常用字符。这就是许多语言编译器或运行时都使用UTF16的原因。英文在使用UTF16时也是2字节表示的。当我们想要使用其他平面的字符时,码元超过2个字节,就需要使用***对在语言中的特定表示方式,譬如‘\U112233’之类的。
使用UTF8时,常用的Alphabet和Numeric都在前127字节,被有效率地用一个字节表示。而我们的中文由于排在1920个码点之后,所以使用3个字节表示,这方面就比UTF16转换格式耗费更多空间。
最后,不论使用哪种UTF转换格式,都是程序员自己可以选择的一种表达方式而已。我们可以通过Java方便的API进行自如转换。
错题九
- Java类Demo中存在方法func0、func1、func2、func3和func4,请问该方法中,哪些是不合法的定义?( )
public class Demo{
float func0()
{
byte i=1;
return i;
}
float func1()
{
int i=1;
return;
}
float func2()
{
short i=2;
return i;
}
float func3()
{
long i=3;
return i;
}
float func4()
{
double i=4;
return i;
}
}
long → float 无须强制转换(这个最选项容易出错),正确。
- float占4个字节为什么比long占8个字节大呢,因为底层的实现方式不同。浮点数的32位并不是简单直接表示大小,而是按照一定标准分配的。
- 第1位,符号位,即S
- 接下来8位,指数域,即E。
- 剩下23位,小数域,即M,取值范围为[1 ,2 ) 或[0 , 1)
- 然后按照公式:
V=(-1)^s * M * 2^E
也就是说浮点数在内存中的32位不是简单地转换为十进制,而是通过公式来计算而来,通过这个公式虽然,只有4个字节,但浮点数最大值要比长整型的范围要大。
算法题一:最长公共前缀
- 给定一个按非递减顺序排序的整数数组 A,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。
示例 1:
输入:[-4,-1,0,3,10]
输出:[0,1,9,16,100]
示例 2:
输入:[-7,-3,2,3,11]
输出:[4,9,9,49,121]
【提示】:
1 <= A.length <= 10000;
-10000 <= A[i] <= 10000;
A 已按非递减顺序排序。
自己的做法:
public static String longestCommonPrefix(String[] strs) {
//先把传递过来的string[]中的每个字符串的前缀都取出来
if (strs.length == 0) {
return "";
}
ArrayList<Character> list = new ArrayList<>();
//第一个字符串将他的所有值存储进去结果集当中
for (int i = 0; i < strs[0].length(); i++) {
list.add(strs[0].charAt(i));
}
//System.out.println(list);
//[f, l, o, w, e, r]
//一次遍历后续所有字符串
for (int i = 1; i < strs.length; i++) {
String ele = strs[i];
//System.out.println(ele);
//遍历list结果集
for (int j = 0; j < list.size(); j++) {
if (ele.length() > j && ele.charAt(j) == list.get(j)) {
//不做操作
//System.out.println("进来没有:" + ele.charAt(j) + "------" + list.get(j));
} else {
list.add(j, ' ');
break;
}
}
//System.out.println("一轮结束" + list);
}
StringBuffer sb = new StringBuffer();
for (int i = 0; i < list.size(); i++) {
if (list.get(i) == ' ') {
break;
}
sb.append(list.get(i));
}
//System.out.println(sb + "这是sb");
return sb.toString();
}
自己的想法是:先把第一个字符串的所有字符当作结果集存储起来,然后依次遍历后面的字符串,每个字符串中的每个字符都拿出来与结果集进行比较,如果此时结果集中还存在字符,并且当前字符串与结果集字符串的相同位置上的字符都一样,不做任何操作;否则将结果集中的该字符置为‘ ’空字符;最后拼接结果集中的该字符返回;
官方的做法:
这个题目官方有好几种做法,这里我选择了一种理解:
class Solution {
public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) {
return "";
}
int minLength = Integer.MAX_VALUE;
for (String str : strs) {
minLength = Math.min(minLength, str.length());
}
int low = 0, high = minLength;
while (low < high) {
int mid = (high - low + 1) / 2 + low;
if (isCommonPrefix(strs, mid)) {
low = mid;
} else {
high = mid - 1;
}
}
return strs[0].substring(0, low);
}
public boolean isCommonPrefix(String[] strs, int length) {
String str0 = strs[0].substring(0, length);
int count = strs.length;
for (int i = 1; i < count; i++) {
String str = strs[i];
for (int j = 0; j < length; j++) {
if (str0.charAt(j) != str.charAt(j)) {
return false;
}
}
}
return true;
}
}
【二分查找】:
1、最开始,如果该字符数组不存在或者长度为0,直接返回null;
2、遍历所有字符串找到字符串的长度最小值;
3、定义最高、最低、中间位,执行while循环;
4、将所有字符串和中间位索引交给isCommonPrefix(),该方法用于判断mid索引之前是不是公共部分,如果不是将最高位置换为mid的前一位,如果是让最低位变为当前的mid;
5、这样整个循环下来,很快找到公共前缀;
6、在字符串量很大的时候,这个算法的时间复杂度相较于我自己的做法很有优势,因为他不是遍历判断,而是一部分一部分的判断;
算法题二:有序数组的平方
- 给定一个按非递减顺序排序的整数数组 A,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。
示例 1:
输入:[-4,-1,0,3,10]
输出:[0,1,9,16,100]
示例 2:
输入:[-7,-3,2,3,11]
输出:[4,9,9,49,121]
提示:
1 <= A.length <= 10000
-10000 <= A[i] <= 10000
A 已按非递减顺序排序。
自己的做法:
public int[] sortedSquares(int[] A) {
/*
* 1、最基本的做法就是:
* 首先遍历原来的数组,将所有的数据都计算平方覆盖原来的值
* 2、排序就OK了
* */
int[] result = new int[A.length];
for (int i = 0; i < A.length; i++) {
result[i] = (int) Math.pow(A[i], 2);
}
Arrays.sort(result);
//System.out.println(Arrays.toString(result));
return result;
}
就代码量来看,这个题目本身不是很困难;
官方的做法:
【双指针】:
class Solution {
public int[] sortedSquares(int[] A) {
int n = A.length;
int[] ans = new int[n];
for (int i = 0, j = n - 1, pos = n - 1; i <= j;) {
if (A[i] * A[i] > A[j] * A[j]) {
ans[pos] = A[i] * A[i];
++i;
} else {
ans[pos] = A[j] * A[j];
--j;
}
--pos;
}
return ans;
}
}
这个题目官方使用了双指针的做法,思路为:
首先明确:给定的数组已经是拍好顺序的;
1、首先给定三个指针,i和pos,i 由前向后指向的是数组中的元素,j由后向前指向数组中的元素,pos由前向后指向的是结果集中的元素;
2、如果i指向的元素的平方大于j指向的元素,就将i指向的元素的平方放在结果集的pos位置,i指针向前挪动一位;
3、否则的话结果集的最后一位放的是j指向的元素,此时j向前挪动一位;
4、最终返回结果集;