1、抽象类和接口的异同点?
相同点
- 都是不断向上抽取而来的。
不同点
- 抽象类需要被继承,而且只能单继承。接口需要被实现,而且可以多实现。
- 抽象类中可以定义抽象方法和非抽象方法,子类继承后,可以直接使用非抽象方法。接口中只能定义抽象方法,必须由子类去实现。
- 抽象类的继承,是is a关系,定义该体系的基本共性内容。接口的实现是like a 关系。
注:多重继承指的是一个类可以同时从多于一个的父类那里继承行为和特征,然而我们知道Java为了保证数据安全,它只允许单继承。解决办法有两种,一是使用接口,一是内部类。
内部类示意:
public class Son {
/**
* 内部类继承Father类
*/
class Father_1 extends Father{
public int strong(){
return super.strong() + 1;
}
}
class Mother_1 extends Mother{
public int kind(){
return super.kind() - 2;
}
}
public int getStrong(){
return new Father_1().strong();
}
public int getKind(){
return new Mother_1().kind();
}
}
2、接口回调
满足回调的两个基本条件:
- Class A调用Class B中的X方法
- ClassB中X方法执行的过程中调用Class A中的Y方法完成回调
一个简单的回调示意:
package com.aige.test;
/**
* @description 该类用来模拟总经理
*/
public class Manager {
/**
* @param personnel 传入一个员工类的对象
*/
public Manager(Personnel personnel) {
// 想让该让员工做什么
personnel.doSomething(this, "整理公司文件");
} /
**
* @description 当员工做完总经理让他做的事后就通过该方法通知总经理
* @param result 事情结果
*/
public void phoneCall(String result) {
System.out.println("事情" + result);
}
}
/**
* @description 该类用来模拟员工
*/
public class Personnel {
public void doSomething(Manager manager, String task) {
// 总经理通过doSomething方法告诉员工要做什么
System.out.println("总经理要你做" + task);
String result = "做完了";
// 当事情做完了我们就通过总经理公布的phoneCall方法通知总经理结果
manager.phoneCall(result);
}
}
以上例子代码简单通俗地描述了回调,但是这里我就会有这样一个疑问:假设总经理出差前交了件事情给我去办,不巧……副总经理也要给我件事去办,更无耻的是……主管也发任务过来了,都要求说做完就打电话通知他们……这时我们就要定义更多类,而且针对每个类的实现函数不一样。
最终想实现的目的就是A调用B,B实现一个处理任务的函数,处理完成后调用A中的通知函数,B可以适配所有的A。其实就是多态的思想,具体实现如下:
package com.aige.test;
/**
* @description 回调接口
*/
public interface CallBack {
// 回调方法
public void backResult(String result);
}
/**
* @description 该类用来模拟总经理
*/
public class Manager implements CallBack {
/**
* @param personnel 传入一个员工类的对象
*/
public Manager(Personnel personnel) {
// 想让该让员工做什么
personnel.doSomething(this, "整理公司文件");
} /
**
* @description 当员工做完总经理让他做的事后就通过该方法通知总经理
* @param result 事情结果
*/
public void backResult(String result) {
System.out.println("事情" + result);
}
}
/**
* @description 该类用来模拟员工
*/
public class Personnel {
public void doSomething(CallBack callBack, String task) {
// 总经理通过doSomething方法告诉员工要做什么
System.out.println("总经理要你做" + task);
String result = "做完了";
// 当事情做完了我们就通过总经理公布的phoneCall方法通知总经理结果
callBack.backResult(result);
}
}
3、异常处理
Java通 过API中Throwable类的众多子类描述各种不同的异常。因而,Java异常都是对象,是Throwable子类的实例,描述了出现在一段编码中的 错误条件。当条件生成时,错误将引发异常。
Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。
Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。
Exception(异常):是程序本身可以处理的异常。
4、String、StringBuffer、StringBuilder
5、object类
object是所有类的超类,实现了以下11个方法。继承类可以进行重写。
public final native Class<?> getClass()
public native int hashCode()
public boolean equals(Object obj)
protected native Object clone() throws CloneNotSupportedException
public String toString()
public final native void notify()
public final native void notifyAll()
public final native void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException
public final void wait() throws InterruptedException
protected void finalize() throws Throwable { }
https://fangjian0423.github.io/2016/03/12/java-Object-method/
6、集合
1) list
以下几类实现了list接口。
- LinkedList:List 接口的链接列表实现。它实现所有可选的列表操作。
- ArrayList:List 接口的大小可变数组的实现。它实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。
- Vector:实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件
一些常用操作:
https://www.cnblogs.com/epeter/p/5648026.html
2)set
Set集合不允许重复元素,是因为Set判断两个对象相同不是使用==运算符,而是根据equals方法。
Set接口包括HashSet、TreeSet和EnumSet三个实现类。
HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值也相等
HashMap基础
http://wiki.jikexueyuan.com/project/java-enhancement/java-twentythree.html
3) Queue
Queue接口与List、Set同一级别,都是继承了Collection接口。
Queue使用时要尽量避免Collection的add()和remove()方法,而是要使用offer()来加入元素,使用poll()来获取并移出元素。
如果要使用前端而不移出该元素,使用 element()或者peek()方法。
值得注意的是LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用。
import java.util.LinkedList;
import java.util.Queue;
class Hello {
public static void main(String[] a) {
Queue<String> queue = new LinkedList<>();
queue.offer("1");//插入一个元素
queue.offer("2");
queue.offer("3");
//打印元素个数
System.out.println("queue.size() " + queue.size());//queue.size() 3
//遍历打印所有的元素,按照插入的顺序打印
for (String string : queue) {
System.out.println(string);
}
System.out.println("queue.size() " + queue.size());//queue.size() 3 上面只是简单循环,没改变队列
String getOneFrom1 = queue.element();
System.out.println("getOneFrom1 " + getOneFrom1);//getOneFrom1 1 因为使用前端而不移出该元素
System.out.println("queue.size() " + queue.size());//queue.size() 3 队列变啦才怪
String getOneFrom2 = queue.peek();
System.out.println("getOneFrom2 " + getOneFrom2);//getOneFrom2 1 因为使用前端而不移出该元素
System.out.println("queue.size() " + queue.size());//queue.size() 3 队列变啦才怪
String getOneFrom3 = queue.poll();
System.out.println("getOneFrom3 " + getOneFrom3);//getOneFrom3 1 获取并移出元素
System.out.println("queue.size() " + queue.size());//queue.size() 2 队列变啦
}
}
4) Map
https://blog.csdn.net/guomutian911/article/details/45771621
对hashmap讲解的比较详细
map或者HashSet都会对添加的元素进行判重,但如何判断呢,有以下机制:
两个对象的哈希码相同并且(两个对象是同一个对象或者两个对象相等[equals为true])
7、基本类型和包装类
http://alexyyek.github.io/2014/12/29/wrapperClass/
public static void main(String[] args){
Integer i = 10; //装箱
int index = i; //拆箱
}
上面是自动装箱和拆箱,本质上,装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的xxxValue方法实现的。(xxx代表对应的基本数据类型)。
分析下面的结果:
public class Main {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2); // true
System.out.println(i3==i4); // false
}
}
第一个为true说明两个指向同一个对象,为什么呢,从下面代码可以看出,在通过valueOf方法创建Integer对象的时候,如果数值在 [-128,127] 之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
而对于double类
public class Main {
public static void main(String[] args) {
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1==i2); // false
System.out.println(i3==i4); // false
}
}
具体为什么,读者可以去查看Double类的valueOf的实现。
在这里只解释一下为什么Double类的valueOf方法会采用与Integer类的valueOf方法不同的实现。很简单:在某个范围内的整型数值的个数是有限的,而浮点数却不是。
注意,Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的,Double、Float的valueOf方法的实现是类似的。
public class Main {
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L;
System.out.println(c==d);
System.out.println(e==f);
System.out.println(c==(a+b));
System.out.println(c.equals(a+b));
System.out.println(g==(a+b));
System.out.println(g.equals(a+b));
System.out.println(g.equals(a+h));
}
}
true
false
true
true
true
false
true
第一个和第二个输出结果没有什么疑问。第三句由于 a+b 包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等。而对于c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较。同理对于后面的也是这样,不过要注意倒数第二个和最后一个输出的结果(如果数值是int类型的,装箱过程调用的是Integer.valueOf;如果是long类型的,装箱调用的Long.valueOf方法)。
8、 泛型
参考
https://blog.csdn.net/s10461/article/details/53941091
先看看简单的泛型
class Point<T>{// 此处可以随便写标识符号
private T x ;
private T y ;
public void setX(T x){//作为参数
this.x = x ;
}
public void setY(T y){
this.y = y ;
}
public T getX(){//作为返回值
return this.x ;
}
public T getY(){
return this.y ;
}
};
不难理解,泛型就是一种类似于模版的语法,当不确定传入的参数时就可以使用。
字母规范
E — Element,常用在java Collection里,如:List<E>,Iterator<E>,Set<E>
K,V — Key,Value,代表Map的键值对
N — Number,数字
T — Type,类型,如String,Integer等等
泛型好处:
- 提高安全性: 将运行期的错误转换到编译期. 如果我们在对一个对象所赋的值不符合其泛型的规定, 就会编译报错.
- 避免强转: 比如我们在使用List时, 如果我们不使用泛型, 当从List中取出元素时, 其类型会是默认的Object, 我们必须将其向下转型为String才能使用。比如:
List l = new ArrayList();
l.add("abc");
String s = (String) l.get(0);
而使用泛型,就可以保证存入和取出的都是String类型, 不必在进行cast了。比如:
List<String> l = new ArrayList<>();
l.add("abc");
String s = l.get(0);
9、static 和 final
1)static
Java把内存分为栈内存和堆内存,其中栈内存用来存放一些基本类型的变量、数组和对象的引用,堆内存主要存放一些对象。在JVM加载一个类的时候,若该类存在static修饰的成员变量和成员方法,则会为这些成员变量和成员方法在固定的位置开辟一个固定大小的内存区域,有了这些“固定”的特性,那么JVM就可以非常方便地访问他们。
值得注意的是对象的static修饰的成员变量和方法是类属性,它不依赖于某个特定的实例变量,也就是说它被该类的所有实例共享。所有实例的引用都指向同一个地方,任何一个实例对其的修改都会导致其他实例的变化。
下面程序:
class staticCode {
static String school;
static {
school = "hello";
System.out.println("nice to meet you");
}
public void hello(){
System.out.println("hello boy");
}
}
public class testDemo {
public static void main(String[] args) {
staticCode s = new staticCode();
new staticCode();
s.hello();
}
}
输出:
nice to meet you
hello boy
上面创建了两个对象,但是“nice to meet you”只打印了一次,说明静态变量和方法是所有实例共享的,只初始化一次。
更多理解参考
2) final
- 修饰类,当用final修饰一个类时,表明这个类不能被继承。
- 修饰方法,防止任何继承类对他的修改。private也有类似的作用。
- 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
public class Test {
public static void main(String[] args) {
String a = "hello2";
final String b = "hello";
String d = "hello";
String c = b + 2;
String e = d + 2;
System.out.println((a == c));
System.out.println((a == e));
}
}
上面程序第一个打印为true,第二个打印为false。
补充:
public class testDemo {
public static void main(String[] args) {
String d = "hello";
String c = "helloworld";
String e = "hello" + "world";
String f = d + "world";
System.out.println(e==c);
System.out.println(f == c);
}
}
上面的输出也是 true和false。
对于那些在编译时就能确定的字面量都会存放在运行时常量池中,比如:
String c = "hello " + "world"; //JVM会将此代码优化为String c = "hello world";
String d = "hello world";
常量池中的变量是固定的,不可修改的,因此对e来说,编译阶段确定了是“helloworld”,会先在常量池中寻找看有没有,如果有,直接返回地址,如果没有就会新开辟一块区域。因此e和c是相等的。
而如下代码
String b = a + "world";
其中a是变量,在编译时不能确定值,所以不会被放在运行时常量池中,而是在堆中重新new了一块儿内存。
再回到第一个代码段,对用final修饰的变量,JVM在编译时,当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。这种和C语言中的宏替换有点像。因此上面两个程序段就有点类似了。
10、 存在继承初始化顺序
存在继承的情况下,初始化顺序为:
父类(静态变量、静态语句块)
子类(静态变量、静态语句块)
父类(实例变量、普通语句块)
父类(构造函数)
子类(实例变量、普通语句块)
子类(构造函数)