BAT TMD这些大厂对员工的技术要求都比较高,但是一般面试都会从基础开始,基础面不通过后面的自然不行。而且基础知识都是可以深度挖掘的,遇到那种喜欢从基础知识挖掘的面试官,基础不行肯定面试不通过。所以我们继续为大家整理基础面试的知识点,作为抛砖引玉,希望您能有所收获。
维护、拓展性:比如你要做一个画板程序,其中里面有一个面板类,主要负责绘画功能,然后你就这样定义了这个类。可是在不久将来,你突然发现这个类满足不了你了,然后你又要重新设计这个类,更糟糕是你可能要放弃这个类,那么其他地方可能有引用他,这样修改起来很麻烦。如果你一开始定义一个接口,把绘制功能放在接口里,然后定义类时实现这个接口,然后你只要用这个接口去引用实现它的类就行了,以后要换的话只不过是引用另一个类而已,这样就达到维护、拓展的方便性。
安全、严密性:接口是实现软件松耦合的重要手段,它描叙了系统对外的所有服务,而不涉及任何具体的实现细节。这样就比较安全、严密一些(一般软件服务商考虑的比较多)。
其实接口的意义属于编程中的一种规范,具体的意义需要在编程中去感知,没有一个严格的定论。
限定参数类型的上界:参数类型必须是T或T的子类型
限定参数类型的下界:参数类型必须是T或T的超类型,类型最高可到Object ,最低是T
泛型的中的通配符挺难理解的,我这里举个例子解释一下:
(1)首先我们定义几个类:
class Fruit {}
class Apple extends Fruit {}
class Orange extends Fruit {}
下面这个例子中,我们创建了一个泛型类Reader,然后在fun1()中当我们尝试:
Fruit f = fruitReader.readExact(apples);
编译器会报错,因为List
与List
之间并没有任何的关系。
public class GenericReading {
static List apples = Arrays.asList(new Apple());
static List fruit = Arrays.asList(new Fruit());
static class Reader {
T readExact(List list) {
return list.get(0);
}
}
static void f1() {
Reader fruitReader = new Reader();
// Errors: List cannot be applied to List.
// Fruit f = fruitReader.readExact(apples);
}
public static void main(String[] args) {
fun1();
}
}
但是按照我们通常的思维习惯,Apple和Fruit之间肯定是存在联系,然而编译器却无法识别,那怎么在泛型代码中解决这个问题呢?我们可以通过使用通配符来解决这个问题:
static class CovariantReader<T> {
T readCovariant(List extends T> list) {
return list.get(0);
}
}
static void f2() {
CovariantReader fruitReader = new CovariantReader();
Fruit f = fruitReader.readCovariant(fruit);
Fruit a = fruitReader.readCovariant(apples);
}
public static void main(String[] args) {
f2();
}
这样就相当与告诉编译器, fruitReader
的readCovariant
方法接受的参数只要是满足Fruit的子类就行(包括Fruit自身),这样子类和父类之间的关系也就关联上了。
这个知识点还包含 PECS原则:
上面我们看到了类似的用法,利用它我们可以从list里面get元素,那么我们可不可以往list里面add元素呢?我们来尝试一下:
public class GenericsAndCovariance {
public static void main(String[] args) {
// Wildcards allow covariance:
List flist = new ArrayList();
// Compile Error: can't add any type of object:
// flist.add(new Apple())
// flist.add(new Orange())
// flist.add(new Fruit())
// flist.add(new Object())
flist.add(null); // Legal but uninteresting
// We Know that it returns at least Fruit:
Fruit f = flist.get(0);
}
}
答案是否定,Java编译器不允许我们这样做,为什么呢?对于这个问题我们不妨从编译器的角度去考虑。因为List flist
它自身可以有多种含义:
List flist = new ArrayList();
List flist = new ArrayList();
List flist = new ArrayList();
所以对于实现了的集合类只能将它视为Producer向外提供(get)元素,而不能作为Consumer来对外获取(add)元素。
如果我们要add元素应该怎么做呢?可以使用:
public class GenericWriting {
static List apples = new ArrayList();
static List fruit = new ArrayList();
static void writeExact(List list, T item) {
list.add(item);
}
static void f1() {
writeExact(apples, new Apple());
writeExact(fruit, new Apple());
static void writeWithWildcard(List list, T item) {
list.add(item)
}
static void f2() {
writeWithWildcard(apples, new Apple());
writeWithWildcard(fruit, new Apple());
}
public static void main(String[] args) {
f1(); f2();
}
}
这样我们可以往容器里面添加元素了,但是使用super的坏处是以后不能get容器里面的元素了,原因很简单,我们继续从编译器的角度考虑这个问题,对于List list
,它可以有下面几种含义:
List super Apple> list = new ArrayList();
List super Apple> list = new ArrayList();
List super Apple> list = new ArrayList
当我们尝试通过list来get一个Apple的时候,可能会get得到一个Fruit,这个Fruit可以是Orange等其他类型的Fruit。
根据上面的例子,我们可以总结出一条规律,”Producer Extends, Consumer Super”:
如何阅读过一些Java集合类的源码,可以发现通常我们会将两者结合起来一起用,比如像下面这样:
public class Collections {
public static void copy(List dest, List src) {
for (int i=0; iset(i, src.get(i));
}
}
上面这个例子来自于网络,我觉得是目前最能说清楚泛型通配符差异的例子。感兴趣的可以去看看原文:原文链接
Java序列化的方式有:Java原生以流的方法进行的序列化、Json序列化、FastJson序列化、Protobuff序列化等。
Android可用的序列化方式还有Parcelable。
Serializable和Parcelable的区别:
位于java.io的路径下,显然是Java通用接口。实际上Serializable就是把Java对象序列化为二进制文件,然后进行传递就非常方便了(这里的传递包括进程之间,程序与网络之间,以及程序与本地存储之间)。使用起来相当简单,比如你自己写一个Person类,只要该类implements了Serializable接口就可以了。好了,这时候你的Person对象就已经实现了序列化,可以通过Intent对象随便跳转了。虽然按照规矩此时应该奉上一段示例代码,但实在是太简单了就算了。
通过Serializable接口实现的序列化虽然方便,但却有个“致命”的问题,那就是效率不高,而在这方面有着洁癖的Google表示不能忍,就给我们提供了更高效率的Parcelable,反正大部分开发人员序列化都是为了传递对象而不是保存在本地,使用Parcelable确实更高效。
静态方法属于静态绑定,在编译阶段已经确定函数名和地址。重写的意思是重新定义父类的虚函数,但是虚函数是动态绑定的,而静态方法是静态绑定的,所以静态函数必然不能是虚函数,也就不存在所说的重写了。你在子类中重新写一个同名的函数,覆盖了父类的同名函数,在使用子类指针进行调用的时候,调用的就是子类的这个静态方法。