京东实习面试(2018.3.13)
//今天京东通知面试了,一面面完,我觉得我玩完了。。唉。。。不过记录一下自己的面试经验还是很重要的(虽然只是一个实习面试),为下次的面试做准备吧。
先是问了一下总成绩,数据结构,算法的成绩,然后问了之前有没有做过相关的安卓工程,问了简历上的实习经历和项目经历。因为自己面的是安卓开发,所以重点问的是安卓的项目和获奖项目。
之后是一些专业性的问题:
1.字母组合的代码:abc三个字母组合可以组合成6种,abc,acb,bac,bca,cab,cba,而aac的组合只有3种,aac,caa,aca,输出用户输入字符串的所有排列组合结果。(算法题)
2.JAVA的面向对象编程三大特征:封装,继承,多态,解释这三个概念并用代码解释;java的继承题目,A extends B,new A(),输出结果
3.死锁的定义,用代码写死锁;
4.onNewInent
5.安卓开发时,前端和后台传递数据的方法;
解:1.参考全排列
2.内容来源:Java面向对象的三大特征
封装和继承几乎都是为多态而准备的
一、 封装
- 封装:利用抽象数据类型把数据和基于数据的操作封装起来,使其成为一个不可分割的整体,数据隐藏在抽象数据内部,尽可能的隐藏数据细节,只保留一些接口使其与外界发生联系。也就是说用户无需知道内部的数据和方法的具体实现细节,只需根据留在外部的接口进行操作就行。
- 封装的步骤
1)修改属性的可见性来限制对属性的访问
2)为每个属性创建一队赋值和取值方法,用于对这些属性的访问
3)在赋值和取值方法中,加入对属性的存取限制
为了实现良好的封装,我们通常将类的成员变量声明为private,在通过public方法来对这个变量来访问。对一个变量的操作,一般有读取和赋值2个操作,,我们分别定义2个方法来实现这2个操作,一个是getXX(XX表示要访问的成员变量的名字)用来读取这个成员变量,另一个是setXX()用来对这个变量赋值。
例子:
public class Husband { /* * 对属性的封装一个人的姓名、性别、年龄、妻子都是这个人的私有属性 */ private String name; private String sex; private int age; private Wife wife; /* * setter()、getter()是该对象对外开发的接口 */ public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void setWife(Wife wife) { this.wife = wife; } }
二、 继承
继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力
继承所表达的就是一种对象类之间的相交关系,它使得某类对象可以继承另外一类对象的数据成员和成员方法。若类B继承类A,则属于B的对象便具有类A的全部或部分性质(数据属性)和功能(操作),我们称被继承的类A为基类、父类或超类,而称继承类B为A的派生类或子类。
继承避免了对一般类和特殊类之间共同特征进行的重复描述。同时,通过继承可以清晰地表达每一项共同特征所适应的概念范围——在一般类中定义的属性和操作适应于这个类本身以及它以下的每一层特殊类的全部对象。运用继承原则使得系统模型比较简练也比较清晰。
继承的实例
class Person1 { public String name = "xiaomiao"; public int age = 20; } class Student extends Person1 { void study() { System.out.println("I can study!"); } } public class JiCheng { public static void main(String args[]) { Student stu = new Student(); // stu.name = "zhangsan"; // stu.age = 20; System.out.println("name=" + stu.name + ",,," + "age=" + stu.age); } }
三、 多态
方法的重写、重载与动态连接构成多态性;
Java之所以引入多态的概念,原因之一是它在类的继承问题上和C++不同,后者允许多继承,这确实给其带来的非常强大的功能,但是复杂的继承关系也给C++开发者带来了更大的麻烦,为了规避风险,Java只允许单继承,派生类与基类间有IS-A的关系(即“猫”is a “动物”)。这样做虽然保证了继承关系的简单明了,但是势必在功能上有很大的限制,所以,Java引入了多态性的概念以弥补这点的不足,此外,抽象类和接口也是解决单继承规定限制的重要手段。同时,多态也是面向对象编程的精髓所在。
要理解多态性,首先要知道什么是“向上转型”。
我定义了一个子类Cat,它继承了Animal类,那么后者就是前者的父类。我可以通过
Cat c = new Cat(); 例化一个Cat的对象,这个不难理解。
但当我这样定义时: Animal a = new Cat();
这代表什么意思呢?
很简单,它表示我定义了一个Animal类型的引用,指向新建的Cat类型的对象。由于Cat是继承自它的父类Animal,所以Animal类型的引用是可以指向Cat类型的对象的。那么这样做有什么意义呢?因为子类是对父类的一个改进和扩充,所以一般子类在功能上较父类更强大,属性较父类更独特,定义一个父类类型的引用指向一个子类的对象既可以使用子类强大的功能,又可以抽取父类的共性。所以, 父类引用只能调用父类中存在的方法和属性,不能调用子类的扩展部分;因为父类引用指向的是堆中子类对象继承的父类;(但是如果强制把超类转换成子类的话,就可以调用子类中新添加而超类没有的方法了。)
同时,父类中的一个方法只有在父类中定义而在子类中没有重写的情况下,才可以被父类类型的引用调用;
对于父类中定义的方法,如果子类中重写了该方法,那么父类类型的引用将会调用子类中的这个方法,这就是动态连接。
3.内容来源:多线程死锁的产生以及如何避免死锁
死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
死锁产生的必要条件
产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生。
- 互斥条件:在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
- 不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
- 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
- 循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, ..., pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的资源被P0占有。
/** * 一个简单的死锁类 * 当DeadLock类的对象flag==1时(td1),先锁定o1,睡眠500毫秒 * 而td1在睡眠的时候另一个flag==0的对象(td2)线程启动,先锁定o2,睡眠500毫秒 * td1睡眠结束后需要锁定o2才能继续执行,而此时o2已被td2锁定; * td2睡眠结束后需要锁定o1才能继续执行,而此时o1已被td1锁定; * td1、td2相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。 */ public class DeadLock implements Runnable { public int flag = 1; //静态对象是类的所有对象共享的 private static Object o1 = new Object(), o2 = new Object(); @Override public void run() { System.out.println("flag=" + flag); if (flag == 1) { synchronized (o1) { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } synchronized (o2) { System.out.println("1"); } } } if (flag == 0) { synchronized (o2) { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } synchronized (o1) { System.out.println("0"); } } } } public static void main(String[] args) { DeadLock td1 = new DeadLock(); DeadLock td2 = new DeadLock(); td1.flag = 1; td2.flag = 0; //td1,td2都处于可执行状态,但JVM线程调度先执行哪个线程是不确定的。 //td2的run()可能在td1的run()之前运行 new Thread(td1).start(); new Thread(td2).start(); } }
4.内容来源:Activity生命周期与onNewIntent
standard模式下Activity的生命周期
Activity第一次启动时:onCreate -> onStart -> onResume
Activity再次启动时:onPause(原来的实例) -> onCreate(新的实例) -> onStart(新的实例) -> onResume(新的实例) -> onStop(原来的实例)
所以,标准模式下,activity被多次启动,不会回调onNewIntent方法。
非standard模式下Activity的生命周期
Activity第一次启动时:onCreate -> onStart -> onResume
Activity再次启动时:onPause -> onNewInent -> onResume
Activity第三次启动时:onPause -> onNewInent -> onResume
所以,非标准模式下,activity第一次被启动,会正常回调生命周期方法,不会回调onNewIntent方法。多次启动该activity后,均会调用 onNewInent方法。
注意:当调用到onNewIntent(intent)时,该参数的intent是最新的intent,可在onNewIntent中使用setIntent(intent),这样后续使用getIntent()时将获得最新的intent.否则,后续getIntent()得到的是旧的intent。
A画面singleTask模式下:
画面如下跳转:
A-B-A
A的生命周期如下,开发者模式中,保留活动:
1. A启动:onCreate: null == savedInstanceState -> onStart -> onResume
2. 跳转到B:onStop
3. 跳转到A:onNewIntent -> onRestart -> onStart -> onResume
A的生命周期如下,开发者模式中,不保留活动:
1. A启动:onCreate: null == savedInstanceState -> onStart -> onResume
2. 跳转到B:onStop -> onDestroy
3. 跳转到A:onCreate: null == savedInstanceState -> onStart -> onResume
也就是说,不会走onNewIntent
退出账号重新登录的实现方式
主画面设置成singleTask模式,那么在退出登录后,重新statActivity主画面,并且intent传入一个标志位:
1.主画面未被异常关闭时,此时会走onNewIntent()方法,并且主画面之上的画面会被推出栈,在该方法中根据intent传入的标志位,启动重新登录画面,关闭主画面;
2.主画面被异常关闭时,此时不会走onNewIntent()方法,并且主画面之上的画面会被推出栈,在onCreate方法中根据intent传入的标志位,启动重新登录画面,关闭主画面。
注意:
activity异常退出的时候,比如屏幕横竖切换、内存不足低优先级的activity被杀死,ondestroy仍然会被调用,除非直接退出整个进程System.exit(0)不会调用ondestroy。那么在oncreate里把activity对象放到一个栈里,然后在ondestroy中把这个activity移除栈,是没有问题的,不会发生内存泄漏。