大家好,今天我打算整理并总结关于JAVA,Android的相关方面的技能点,主要分为:
如果大家认真掌握好,那么你就相当于有了两年以上的开发经验,拿到12k以上的薪水是没什么问题的,废话少说,现在开始我们的第一部分java基础面试点部分.
一.面向对象的思想
特征:
1.继承
从父类(超类,基类)继承得到之类(派生类),让软件有了延续性,是封装程序中可变因素的重要手段.
2.封装
1.把数据和操作数据的方法封装起来,对数据的访问只提供接口
2.面向对象本质:将现实世界描绘成完全自治,封闭的对象
3.我们在类中写方法就是对实现细节的一种封装,编写类就是对数据和数据操作的封装
结论:封装一切可以封装的东西,只对外提供最简单的接口
3.多态
1.不同的子类对象对同一个消息做出不同的响应,而同样的对象(分类)调用同样的方法,能够做不同的事情:爸爸喊一声,开始工作:打儿子就开拖拉机耕地,二儿子开飞机撒农药.
2.多态性的分类:
1.编译时(前绑定)
方法重载
2.运行时(后绑定),是面向对象的精髓
方法重写覆盖
1.A访问B提供的服务,B有多钟提供服务的方式,对A是透明的.
3.实现多态要做的两件事:
1.方法重写,覆盖
2.对象的造型
父类引用指向子类对象,同样的引用调用同样的方法就会根据子类对象的不同表现出不同的行为
4.抽象
1.是将一类对象的共同特征而而总结出来的构造类,包括:数据抽象,行为抽象.
2.抽象只关注属性和行为,不关注行为细节(无方法体)
开心小案例
代码:
interface Fu {
void doTask();
}
class Zi implements Fu {
@Override
public void doTask() {
System.out.println("我是大儿子,拖拉机播种");
}
}
class Zi1 implements Fu{
@Override
public void doTask() {
System.out.println("我是二哥,开飞机喷农药");
}
}
class Zi2 implements Fu{
@Override
public void doTask() {
System.out.println("我是三弟,远程控制蔬菜棚温度");
}
}
class Test{
public static void main(String[] args) {
Fu z = new Zi();
Fu z1 = new Zi1();
Fu z2 = new Zi2();
TallTool.addClild(z);
TallTool.addClild(z1);
TallTool.addClild(z2);
TallTool.tall();
}
}
其他包的工具类:
import java.util.ArrayList;
class TallTool {
static ArrayList l = new ArrayList();
public static void addClild(Fu f) {
l.add(f);
}
public static void tall() {
for (Fu f : l) {
//多态指向子类对象
f.doTask();
}
}
}
打印结果:
我是大儿子,拖拉机播种
我是二哥,开飞机喷农药
我是三弟,远程控制蔬菜棚温度
二.多态
1.实现多态的机制
1.靠的是父类或接口定义的引用变量指向子类或者具体实现类对象
2.程序调用的方法在运行期才动态绑定,引用的是:指向的具体事例对象的方法(内存中运行的对象的方法),而不是引用变量类型中定义的方法.
2.多态的案例:
1.口诀的解析:“成员变量,静态方法看左边;非静态方法:编译看左边,运行看右边。”
2.父类变量引用子类对象时:Fu f = new Zi();
1.在这个引用变量f指向的对象中,他的成员变量和静态方法与父类是一致的;
2.他的非静态方法,在编译时是与父类一致的,运行时却与子类一致(发生了复写)。
代码例子:
class Fu {
int num = 5;//成员变量
static void method_static() {
System.out.println("fu static");
}
void method() {
System.out.println("fu method");
}
}
class Zi extends Fu {
int num = 8;
static void method_static() {
System.out.println("zi method_static");
}
void method() {
System.out.println("zi method");
}
}
class DuoTaiDemo {
public static void main(String[] args) {
Fu f = new Zi();
System.out.println(f.num);//与父类一致
f.method_static();//与父类一致
f.method();//编译时与父类一致,运行时与子类一致
Zi z = new Zi();
System.out.println(z.num);
z.method_static();
z.method3();
}
}
输出结果:
5
fu method_static
zi method
8
zi method_static
zi method
分析:
Fu f = new Zi();
了解变量f到底是什么,把这句子分2段:
1.Fu f;这是声明一个变量f为Fu这个类,那么知道了f肯定是Fu类。
2.f=new Zi();中建立一个子类对象赋值给了f,结果是什么?即:拥有了"被Zi类函数覆盖"后的Fu类对象"f",也就是说f不可能变成其他比如Zi这个类.
对于成员变量:
f所代表的是函数被复写后(多态的意义)的一个Fu类,而Fu类原来有的成员变量(不是成员函数不可能被复写)没有任何变化.
结论1:成员变量:编译和运行都看Fu。
对于成员方法:
f的Fu类函数被复写了.
获得结论2:非静态方法:编译看Fu,运行看Zi
对于静态方法:编译和运行都看Fu
很简单,首先我们要理解静态情况下发生了什么?
静态时,Fu类的所有函数跟随Fu类加载而加载了。
Fu类的静态函数(是先于对象建立之前就存在了,无法被后出现的Zi类对象所复写的,所以没发生复写.
结论3:静态方法:编译和运行都看Fu。
三.异常的处理
1.种类
按异常需要处理的"时机"分类:
1.编译时异常,也叫检测异常:CheckedException.
1.只有java提供检测异常,认为检测异常都是可以被处理的异常,所以必须"显式"处理"检测异常"
2.如果不处理编译就会出错,体现了java的设计哲学:没有完善掉错误的代码就没有执行的机会
3.检测异常处理的两种方法:
1.知道怎么处理:用try...catch处理
2.不知道怎么处理:方法抛出处理
2.运行时异常,RuntimeException.
1.代码运行的时候才发现的异常,通常不需要try...catch.
2.如:除数为0,数组下标越界等.
3.因为产生的频繁,处理麻烦,如果都处理对可读性和运行效率影响太大,当然如果有需求也可以捕获.
2.catch和finally的返回值
public int result(){
try {
int a = 1/0;
return 1;
} catch (Exception e) {
return 2;
}finally{ //int b = 1/0;
//int j = 3/0;//再加上这个就输出异常了
return 3;
}
打印结果是 3:
解析:
普通理解catch再到finally:第3行除数是0,return 1就执行不到,来到catch 然后return 2.结束
实际的情况是:异常机制有一个原则:如果catch里面有return或者诸如break等让函数终止的,而且finally里面也有return,那么必须先执行fianlly里面的代码块后才确认
所以代码读到catch的return的时候就跳到了finally的return 3,catch里不会返回.
//普通情况1:
public static int getResult2() {
int num = 0;
try {
int i = 2 / 0;
num = 1;
return num--;
} catch (Exception e) {
num = num+1; //先得1
}
finally {
num = num-2; //再变成1-2=-1;
}
return num; //返回-1;
}
//普通情况2:
public static int getResult2() {
int num = 0;
try {
int i = 2 / 0;
num = 1;
return num--;
} catch (Exception e) {
return num+1; //已经返回了1
}
finally {
num = num-2; //然后再变:1-2 = -1;但上面已经返回了1;
}
}
四.数据类型
1.占的字节
类型 | 字节 |
---|---|
byte | 1 |
short | 2 |
int | 4 |
long | 8 |
float | 4 |
double | 8 |
char | 2 |
boolean | 1 |
2.String属于吗?能继承吗?
1.不属于基本数据类型,是引用类型,底层是char数组实现.
2.是被final修饰的类,所以不能被继承;
五.IO
1.几种类型
1.字节流
继承于InputStream和OutputStream基类.
具体实现(用于文件):FileInputStream,FileOutputStream
2.字符流
继承于Reader和Writer基类
具体实现:FileReader,FileWriter
2.字节流怎么转成字符流
字节输入流转字符输入流通过InputStreamReader实现,构造函数传入(inputStream)
字节输出流转字符输出流通过OutputStreamWriter实现,构造函数传入(OutputStream)
3.如何将java对象序列化到文件
1.如Person类,Student类
public class Demo {
public static void main(String[] args)throws Exception{
//放入
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/obj"));
oos.writeObject(new User("Hello JAVA", 99));
oos.close();
//读取
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/obj"));
User user = (User) ois.readObject();
System.out.println(user);
ois.close();
}
}
//要实现Serializable,否则运行出现异常
class User implements Serializable {
private String name;
private int score;
User(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
拓展:
Serializable和Parcelable的区别
1.作用:
Serializable:保存到本地文件,数据库,网络流,或者程序间.
Parcelable:因Serializable效率过慢而设计,为在不同安卓程序AIDL高效传输而设计,仅在"内存中"存在,是通过IBinder通信的消息的载体.
2.效率和选择:
1.内存间传输数据优先Parcelable:性能更好,内存开销少.
2.需要保持或者网络传输优先Serializable:因为能持久化数据,而Parcelable不推荐持久化
六.集合
1.HashMap排序上机题
1.User类有name,age属性
2.有一个HashMap集合存放多个用户
3.写一个方法实现对HashMap里面内容进行排序,方法接收HashMap为形参,返回值也是这个
4.要求进行age倒序,并且key,value键值保持原来的对应.
方法1(万能).
public class Demo {
public static void main(String[] args)throws Exception {
//HashMap如果键是用整形的数字(1,2,3)就可以按键的顺序排,如果按字符型的数字("1","2","3")就不规则排,TreeMap技能排序整,也能排序字符型的键
HashMap hm = new HashMap<>();
hm.put(1, new User("Andy", 24));
hm.put(5, new User("Josh", 22));
hm.put(3, new User("Lucy", 23));
hm.put(8, new User("Lucy", 26));
hm.put(4, new User("Lucy", 25));
hm.put(2, new User("Lucy", 20));
System.out.println("--------------------排序前----------------------");
System.out.println(hm);
HashMap hmsort = getSortHashMap(hm);
System.out.println("--------------------排序后----------------------");
System.out.println(hmsort);
}
/**
* 处理逻辑:
* 1.对比User里的age,然后用方法进行排序,
* 2.然后放入LinkedHashMap
* 3.然后放入LinkedHashMap因为是其之类,直接返回即可
*/
private static HashMap getSortHashMap(HashMap hm) {
//LinkedHashMap是HashMap的之类,根据存储的方式排列
LinkedHashMap lh = new LinkedHashMap();
//定义一个集合放置单个对象类,等下要根据年龄排序
ArrayList list = new ArrayList<>();
Set> entries = hm.entrySet();
Iterator> iterator = entries.iterator();
while (iterator.hasNext()) {
Map.Entry next = iterator.next();
//获取值,放入集合
User value = next.getValue();
list.add(value);
}
//集合工具类根据对象的年龄重新排序
Collections.sort(list, new Comparator() {
@Override
public int compare(User o1, User o2) {
return o1.age - o2.age;
}
});
//遍历集合,获取匹配的键,然后存入按存入顺序的LinkedList
for (int i = 0; i < list.size(); i++) {
//逐个对比
User user = list.get(i);
Iterator> iterator2 = entries.iterator();
while (iterator2.hasNext()) {
Map.Entry next = iterator2.next();
Integer key = next.getKey();
User value = next.getValue();
//相同就放入
if (value.equals(user)) {
//放入
lh.put(key, value);
}
}
}
return lh;
}
}
方法2(技巧转换).
/**
* 处理逻辑:
* 1.获取"键值对"集
* 2.键值对集变成 集合,是键值对的"集"
* 3.通过集合工具(对比年龄)类排列
* 3.然后放入LinkedHashMap因为是其之类,直接返回即可
*/
private static HashMap<Integer, User> getSortHashMap(HashMap<Integer, User> hm) {
//获取键值对
Set<Map.Entry<Integer, User>> entries = hm.entrySet();
//键值对转换成集合list,然后对比
ArrayList<Map.Entry<Integer,User>> list = new ArrayList<Map.Entry<Integer,User>>(entries);//直接构造放入
//集合工具类根据对象的年龄重新排序
Collections.sort(list, new Comparator<Map.Entry<Integer, User>>() {
@Override
public int compare(Map.Entry<Integer, User> o1, Map.Entry<Integer, User> o2) {
return o1.getValue().age-o2.getValue().age;
}
});
//LinkedHashMap是HashMap的之类,根据存储的方式排列
LinkedHashMap<Integer,User> lh = new LinkedHashMap();
for (Map.Entry<Integer,User> map :
list) {
lh.put(map.getKey(), map.getValue());
}
return lh;
}
2.集合安全性问题
1.ArrayList,HashSet,HashMap线程安全吗?如何变成安全的?
1.看源码,没加锁,所以不安全,vector和hashable才是安全,因为核心方法加了锁synchronized.
2.变成安全的方法,集合工具类:
1.Collections.synchronizedCollection(c)
2.Collections.synchronizedList(list)
3.Collections.synchronizedSet(set)
4.Collections.synchronizedMap(map)
工具类.锁对应的类型(对象),原理就是核心方法加了关键字.
3.ArrayList内部的实现
1.构造函数
1.空构造:
使用了一个new Object[0]数组
2.带参数构造
1.小于0时,抛出运行时异常
2.大于0时,创建一个长度为该值的新数组
3.传入Collection的子类,判断是否为空,是则空指针异常,不是就转成数组a,赋值为成员变量array,他的size就是数组的长度
2.内部如何实现数组的增加和删除和清空
3.数组长度是固定的,而不断往其中添加对象,是如何管理数组?
4.并发集合和普通集合区别
1.普通集合
性能最高,但不保证多线程的安全性和并发的可靠性.
2.同步集合
仅仅是添加了synchronized同步锁,牺牲了新能
3.并发集合
性能更低,通过复杂的策略不仅保证了多线程的安全,有提高并发时的效率.
七.多线程
1.两种创建方式
1.继承Threa类
2.实现Runnable接口
最后都通过重写run方法实现线程
2.wait和sleep区别
1.wait释放锁,sleep不释放
2.wait用于线程间交互,sleep用于暂停
3.wait等待CPU,sleep攒着CPU睡觉
3.synchronized和volatile关键字作用
volatile :易变,不稳定之意,一个成员变量被修饰后:
1.保证了不同线程对这个变量操作时的可见性:一个线程修改了某变量值,该值对其他线程立即可见.
2.禁止进行指令重排序
volatile告诉jvm,当前变量在寄存器(工作内存)中值是不确定的,需要从主存中读取
synchronized锁定当前变量,只有当前线程可访问,其他的阻塞.
1.volatile只能在变量级别
synchronized可在变量,方法,类级别
2.volatile紧能实现变量的修改可见性,不能保证原子性
synchronized能实现变量的修改可见性并能保证原子性
3.volatile不会造成线程的阻塞
synchronized可能会造成线程的阻塞。
4.volatile 标记的变量不会被编译器优化;
synchronized 标记的变量可以被编译器优化;
4.线程并发访问代码
public class Demo3 {
public static void main(String[] args) {
final Counter counter = new Counter();
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
counter.inc();
}
}).start();
}
System.out.println(counter);
}
}
class Counter {
private volatitle int count = 0;
public void inc() {
try {
Thread.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
//增加
count++;
}
@Override
public String toString() {
return "[count = " + count + "]";
}
}
问,是否打印1000?
不是,结果不可能等于1000,肯定是小于1000的,线程安全问题,所以小于,不管有无都小于.
1.线程池是什么?对线程池的理解?怎么使用?
2.使用方法
3.举例对比
3.启动的策略
4.如何控制”并发线程”的”并发线程数”
因为这一块是重要的部分,并且讲解比较详细,所以我为大家特意写了一遍,请大家点击链接.
请点击点击我”秒杀线程池”
请介绍两者的特点,参数,以及优缺点.
同样的我特意写了一篇详细的”消息异步机制”的文章
点我征服异步消息机制