之前面试的时候一直会问到一些问题,因为最近已经定下研究生的事情了,在这里就先学习研究生阶段需要用到的语言Java,虽然之前也学过,但是总觉得对某些部分的了解仅仅是个大概,写这篇文章的目的就相当于读书笔记吧。
首先面试经常问到的:面向对象的基本特征有哪些?分别有什么优点?
面向对象基本特征:封装、继承、多态。
封装:将对象的实现细节隐藏起来,然后通过一些公用方法来暴露该对象的功能。提高类的内聚性,降低对象之间的耦合性。
继承:面向对象实现软件复用的重要手段,当子类继承父类后,子类作为一种特殊的父类,将直接获得父类的属性和方法
多态:子类对象可以直接赋给父类对象,但运行时仍然表现出子类的行为特征,这意味着同一个类型的对象执行同一个方法时可以表现出多种行为特征。在某个函数接收一个对象实例作为参数时,用父类作为该参数,然后就可以根据不同的传入对象来具体实现不同的功能了。
注释:单行,多行,文档。
文档注释:通过JDK的javadoc工具将源代码里的稳当主食提取成一份系统的API文档。
基础数据类型:补码=原码(符号位不变)取反+1;在将一个数赋值给一个long型的数时,若该数在最后没有L,则不以long类型来存储计算。
按位非:把操作数在计算机底层的二进制码按位取反。负数在计算机中用原码的补码存在。
Java中的大部分运算符是从左向右结合的,只有单目运算符、赋值运算符、三目运算符例外,这三个运算符是从右往左结合的。
在使用if....else语句的基本规则:总是优先把包含范围小的条件放在前面处理。
在Java中的foreach语句中,foreach中的循环变量相当于一个临时变量,系统会把数组元素依次赋给这个临时变量,这个临时变量不是数组元素,仅仅是保存了数组元素的值。
Java和C++的一个区别:在Java中如果声明一个类的数组,则该类不需要有默认构造函数也可以声明数组,不过在C++中要求类有默认构造函数。因为Java中将数组中的类实例声明为null。
Java String类的substring()方法是按照字符截取的,如果要按照字节截取,就需要将String对象转化成字节数组,使用String类中的getBytes(编码方式)方法。得到一个byte[]
Java中的List是一个接口,ArrayList等是类,所以一般使用的时候都是使用ArrayList,其中ArrayList<>尖括号中的内容是类,所以如果要存入基本类型则需要存入其对应的包装类。
Java中的this关键词根据其出现的位置的不同,有两种情形:
1、 构造器中引用该构造器正在初始化的对象
2、 在方法中引用调用该方法的对象。
Java中的import语句中的*仅表示该包下的所有类并不包含这个包中的子包。
import static语句表示静态导入,用于导入指定类的单个静态Field、方法和全部静态Field、方法。即使用import static导入的是某个包中的类的全部静态声明的东西。
Java中的常用包:
java.lang:包含Java语言的核心类,例如String,Math, System, Thread等。
java.util:包含Java大量工具类/接口和集合框架类/接口,例如Arrays,List,Set等
java.net:网络编程
java.io:包含Java输入输出编程相关
java.text:Java格式化相关
java.sql JDBC数据库编程
java.awt:抽象窗口工具集,用于构建图形用户界面
java.swing:包含Swing图形用户界面编程,用于构建平台无关的GUI程序。
在一个类的构造器中调用另一个构造器,则使用this(参数)的形式。
Java引用变量有两个类型,一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋予该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态。Java中的方法具有多态性而Field不具有多态性。引用变量在编译时只能调用其类型所具有的方法,在运行时可以调用其实际的类所具有的方法,所以多态的实现首先就需要重写父类中的某个方法,否则在编译时因为子类中不具有该函数就会出现编译错误。
Java在父类的构造函数中调用被子类重写的方法会引发错误。牢记在调用函数时前面总会有个this关键词,在新建一个子类对象时会调用父类的构造函数,调用这个构造函数的主语是子类,所以父类构造函数中调用的函数是子类重写的函数。因为this指向的是子类。继承表示的是一种“是(is-a)关系”,组合表示的是一种“有(has-a)关系”。
Java中的初始化块:当创建Java对象时,系统总是先调用该类里定义的初始化块,在执行构造器之前执行。初始化块即在类的声明里面随便用大括号圈起一段代码就可以表示初始化块了,初始化块里的代码可以包含大部分的语句。
以下代码将无法编译:
public void compute() throws IOException, ParseException{
try{
//code with IOException or ParseE
}catch(Exception e){
throw e;
}
因为抛出了一个Exception的类型的异常,-但是将catch语句括号中的代码改成final Exception e就会e是什么就抛出什么类型,不会自动向上转换。因为final限制了e不能被改变。
Java创建对象时的操作顺序:1、系统为该对象的所有实例field分配内存
2、程序开始对这些实例变量进行初始化,顺序为:限制性初始化块或声明Field时指定的初始值,在执行构造器里指定的初始值。
3、先分别执行父类的static初始化块,然后执行父类a的初始化块、构造函数;父类b的初始化块、构造函数.......
public class test{
static int a = 9;
static{
a = 6;
}
public static void main(String[] args){
System.out.println(a);
}
}
以上代码得到6
public class test{
static{
a = 6;
}
static int a = 9;
public static void main(String[] args){
System.out.println(a);
}
}
以上代码得到9 JVM先分配Field的内存,然后按语句顺序进行赋值
Java中的接口中定义一组公用方法,而在抽象类中该定义什么就定义什么。使一个类变成抽象类:在其中定义一个抽象方法;直接定义成抽象类;继承抽象类但是不完全实现其中的方法。抽象类不能创建实例。
Java中的接口中的所有内容都是public修饰符\
接口中定义的任意常量在实现接口的类中可以直接访问(即不必使用[接口.字段]的方式进行访问)
Java源文件中最多只能有一个public接口,如果一个Java源文件里定义了一个public接口,则该源文件的主文件名必须与该接口名相同。
Java中的面向接口编程是一个比较犀利的东西,其中有简单工厂模式和命令模式。命令模式的形式很像C++中的函数指针。面向接口编程比较重要的一点就是如果一个接口声明为A,类B implements A,则在函数中若有形参为(A,a),则a可以传入B的实例。
Java中如果外部类成员变量、内部类成员变量与内部类里方法的局部变量同名,则可通过使用this、外部类类名.this作为限定来区分。
如果有一个内部类子类的对象存在,则一定存在与之对应的外部类对象。匿名内部类只能访问final修饰的外部类的Field。
回调:允许客户类通过内部类引用来调用其外部类的方法。
声明在函数内部的内部类只能访问声明为final的形参。
Java中的集合里存入的只能是对象(对象的引用变量),不能是基本类型的值。
Set:
如果两个元素通过equals()方法比较返回true,但是它们的hashCode()方法返回值不相等,HashSet会把它们存在不同的位置。也就是说HashSet判断重复的方法是equals()和hashCode()都相等才能够得到两个对象相同。HashSet通过hashCode来决定对象存放的位置,如果两个对象的hashCode相同但是equals不相等,则在该位置使用链式结构来存储。在HashSet中存入可变对象之后要小心处理,不然很容易使HashSet不能准确访问该元素。TreeSet采用红黑树的数据接口来存储集合元素。
小于X,大于或等于X,从X(包含)到Xx(不包含)
数组与链表的优缺点对比:
数组以一块连续的内存区来保存所有的数组元素,所以数组在随机访问时性能最好。链表在执行插入、删除操作时有很好的性能,进行迭代操作时,链表表现的性能要优于数组。
List:
List判断两个对象相等只要通过equals()方法比较返回true即可。
如果开始就知道ArrayList或Vector集合需要保存多少个元素,则可以在创建它们时就指定initialCapacity大小。如果创建空的ArrayList或Vector集合石不指定initialCapacity参数,则Object[]数组的大小默认为10。所以为了防止多次扩张带来的效率低下,可以在已知存入数据的大小时就先指定initialCapacity的值。
在所有能够实现排序的集合中,有两种排序方式,一种是自然排序使用集合中的元素的类实现Comparalble接口,另一种是定制排序,在创建集合对象的时候传入一个Comparator对象,这个对象一般是一个匿名内部类。Java中的接口对象即为实现接口的类。
用容器实现的栈的toString()方法都是从栈底到栈顶进行逐个输出。即先输出先进栈的,后输出后进栈的。但是如果从其中取出数据的话依然遵守先进后出原则。视为一个桶。
队列的第一个元素表示第一个进队列的元素,队列的最后一个元素表示最后进队列的元素。
List集合的使用建议:
1、如果需要遍历List集合元素,对于ArrayList、Vector集合,应该使用随机访问方法(get)来遍历集合元素,这样性能更好,对于LinkedList使用Iterator迭代器来遍历集合元素。
2、如果需要经常执行插入、删除操作来改变List集合的大小,应该使用LinkedList集合(底层使用链表实现)。若使用ArrayList、Vector(底层使用数组实现),往往需要重新分配内部数组的大小,时间开销比LinkedList要大几十倍。
3、如果涉及到互斥使用List,开发者可考虑使用Collection将集合包装成线程安全的集合。
当需要很大的内存空间(大于JVM默认内存空间),使用以下命令来运行程序:
java -Xms128m -Xmx512m filename
其中Xms是JVM的堆内存初始大小
Xmx是JVM的堆内存最大大小。128m和512m表示大小。
Map的key不允许重复,即同一个Map对象的任何两个key通过equals方法比较总返回false。Java先是实现了Map,然后通过包装一个所有value为null的Map就实现了Set集合。
HashSet和HashMap、Hashtable类似,尽量不要使用可变对象作为存入的对象(对于Map是作为Key),同时尽量不要修改可变Key对象。
集合中使用Collections.synchronizedXxx()来将指定集合包装成线程同步的集合。其中Xxx可以用Set、Map、List替代。
Java7中可以使用List<String> ls = new List<>();来声明泛型集合。在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参(即不允许使用泛型),因为泛型的实际类型只有在类实例化之后才会定义,所以类中的泛型变量不能使静态的。
泛型中表示所有类型的父类使用?标记。例如List<?> a = 右边的数可以为List<anytype>。A extends B, List<A> 不是List<B>的子类。可以使用List<? extends A>来表示接受所有A的子类即List<>尖括号中的类为A的子类(包括A)。
语法:
public void drawAll(List<? extends Shape> shapes){}
不能把对象放入一个未知类型的集合中。即不能有
List<?> a = new List<String>();
a.add("hello world");
泛型中为类型形参设置多个上限时(至多有一个父类上限,可以有多个接口上限)。类上限必须位于第一位。
语法:
public class Apple<T extends Number & java.io.Serializable>{}
泛型方法:
public <T> void A(T[] a,Collection<T> b){}
在方法中使用泛型参数无须显示传入实际类型参数,编译器会根据实参推断类型参数的值。上述方法用下面语句调用:
String[] a = {"a","b"};
ArrayList<String> l= new ArrayList<>();
A(a,l);
泛型方法和类型通配符的区别:
通配符被设计用来支持灵活的子类化。即形如Collection<?>
泛型方法被用来表示方法的一个或多个参数之间的类型依赖关系。
boolean <T> A(T a, T b);
设定通配符的下限:
使用语句<? super Type>表示它必须是Type本身或者是Type的父类。
设定通配符的上限:
使用语句<? extends Type>表示它必须是Type本身或者是Type的子类。
在使用泛型方法的时候要特别注意方法重载是否会有问题。Java中的强制类型转换发生在父类和子类之间。如果两个类没有任何关系则强制类型转换会引起运行时错误。
异常:
throws关键字主要在方法签名中使用,用于声明该方法可能抛出的异常。throw关键字用于抛出异常。
Swing中为一个空间绑定键盘驱动代码:
JTextField jtf = new JTextField(15);
jtf.getInputMap().put(KeyStroke.getKeyStroke('\n',java.awt.event.InputEvent.CTRL_MASK),"send");
jtf.getActionMap().put("send",Action);
数据库中可以设置auto_increment的只能是键,同时如果要往数据库中存入图片、音乐等内容的话应该使用Blob类型数据。
程序在创建RowSet的时候已经将数据从底层数据库读到了内存中,因此可以充分利用计算机的内存,降低数据库服务器的负载,提高程序性能。RowSet对应 C#中的DataSet。
面试经常会问到的问题:ACID性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持续性(Durability)——数据库中事务的特性。
JDBC中的批量更新可以使用Statement对象的addBatch()方法将多条语句同时收集起来,然后调用Statement对象的executeBatch()方法同时执行这些SQL语句。
DatabaseMetaData和ResultSetMetaData是很实用的两个关于数据库中的各种详细信息的类。
Java不支持泛型数组
文件锁可以用来控制并发访问,但是对于高并发访问的情形,还是推荐使用数据库来保存程序信息而不是文件。
使用RandomAccessFile允许程序自由移动文件指针,访问文件的任意位置,但是要做插入操作的话需要把插入部分之后的所有内容先缓存起来,插入之后再在插入内容之后补充之前缓存的文件内容。所以Java中直接进行插入操作是不现实的?
在多线程编程时,使用继承Thread类的方式来实现多线程的话,多个线程无法共享线程类的实例变量。因为需要new两个线程类,这种new的方式使得两个线程类是相互区别没有联系的,所以其中的成员Field变量是无法共享的。
Java中实现多线程的方式:
1、使用继承Thread类
2、使用实现Runnable接口
3、使用实现Callable,使用FutureTask来包装Callable实例,然后用Thread(FutureTask)方法来实现多线程。
Java中实现创建类的实例的方法
1、 使用new操作符来创建实例
2、 通过反射来创建实例
3、 通过反序列化的方式来创建实例。
Java中创建并使用自定义类加载器的时候可以直接对.java文件进行运行,用这种方式可以通过对.java文件进行加密然后保存,编译运行的时候再解密某种程度上起到保护源码的作用。
以现在的水平看有关JDK动态代理和AOP的过程还是有些吃力。
动态代理:(参考http://blog.csdn.net/cjl5678/article/details/10586645)
设计模式中有个很经典的模式叫做代理模式,就是在某个类之外定义一个包装类,外部代码通过调用这个包装类里方法来进行被包装的类的方法调用,可以在外部包装类方法调用内部类方法之前加上一些额外的功能,但是静态的代理模式会因为需要被代理的类越来越多而有越来越多的代理类,这时候动态代理就应声出现了。
动态代理仅仅需要实现一个代理类(因为Object类是所有类的基类),所以在实例化一个动态代理类的时候,传入一个被代理的类的实例,(这里由于Proxy.newProxyInstance方法需要传入一个类实现的接口,所以需要根据接口来实现)。
动态代理类的实现方法一般如下:
public class LogHandler implements InvocationHandler{
private Object targetObject;
public Object newProxyInstance(Object targetObject){
this.targetObject = targetObject;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
System.out.println("start-->>"+method.getName());
for(int i = 0;i<args.length;i++){
System.out.println(args[i]);
}
Object ret = null;
try{
ret = method.invoke(targetObject, args);
System.out.println("success-->>"+method.getName());
}catch(Exception ex){
ex.printStackTrace();
System.out.println("error-->>"+method.getName());
throw ex;
}
return ret;
}
}
然后在定义了这个动态代理类之后,所有的其他类的调用可以通过这个代理类来实现,首先定义一个接口(因为java中的动态代理必须通过接口来实现)
public interface UserManager {
public String addUser(String userId,String userName);
}
public class UserManagerImpl implements UserManager{
public String addUser(String userId,String userName){
try{
System.out.println("addUser: "+userName);
}catch(Exception ex){
ex.printStackTrace();
}
return userName;
}
}
定义好需要被代理的类之后,程序通过如下方式使用类的方法:
public class Client {
public static void main(String[] args){
LogHandler logHandler = new LogHandler();
UserManagerImpl userManager = (UserManagerImpl)logHandler.newProxyInstance(new UserManagerImpl());
String name = userManager.addUser("00001", "张三");
}
}
来执行被代理类的方法。这样,可以在代理类的method.invoke()方法执行之前做些额外的事情,增加功能。(AOP的实现类似)
希望之后能够查看和涉及有关这方面更多的知识。其中Java中的网络编程也是一个难点。其中的NIO中的Selector的用法也不怎么明白。