java面试总结(1)——基础知识

1、什么是多态?

多态,父类的引用指向子类的对象,使父类的引用呈现出子类的特性。好处在于减少了类之间的耦合性,例如:

 

public Abstract Class Car
{
    void run(){...};
}
public class BMW extends Car
{
    //子类实现自己的run方法
    void run(){...}
}
public Class MyCar
{
     runCar(Car car)
     {
           car.run();
     }
}
 

 

 通过父类的引用来调用子类的方法,如此可以免去为每种车定义不同的runCar方法,如runBMW(BMW bmw),从而是Car和MyCar两个类解耦。

 

2、抽象类与接口

1)从语法上,一个是abstract class,一个是interface,一个类可以实现多个接口,却只能继承一个抽象类,抽象类中可以有成员变量,方法可以有实现,但是接口中的变量只能是static finall的。

2)从含以上,抽象类与子类的关系是“is a”,而接口是“like a”,它赋予一个类更多的行为,是类属性与行为上更高层次上的抽象。如:给汽车赋予报警器的功能,这时候Car可以implements Alarm接口,并实现Alarm接口中的方法。

 

3、反射

反射是允许程序在运行时对自身进行检查(自审,Introspection),动态获取类的信息(类名,方法,成员变量,构造函数)和调用对象方法的特性。例如,

 

Class clazz = Class.forName("java.lang.Object");
Object obj = clazz.newInstance();
Methdo method = clazz.getMethod("方法名",new Class[](int.Class,int.Class));
Object result = method.invoke();

 

 应用举例——java动态代理

1)定义真实类的接口,及其实现

 

public interface MyAction {
public void doAction();
}

     public class Action1 implements MyAction{

public void doAction()
{
    System.out.println("from Action1!!");
}
}

 

2)定义代理类中,代理过程的实现

 

public class MyInvocationHandler implements InvocationHandler{
    private Object obj;
    
    public MyInvocationHandler(Object obj)
    {
        this.obj = obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("before"+method);
        method.invoke(obj, args);
        System.out.println("after"+method);
        return null;
    }

}

 

 3)通过java的Proxy类动态生成代理类,返回一个真实类的接口,

 

public class ProxyTest {
public static void main(String[] args)
{
    Action1 action1 = new Action1();
    MyInvocationHandler myInvocationHandler = new MyInvocationHandler(action1);
    MyAction action = (MyAction)Proxy.newProxyInstance(action1.getClass().getClassLoader(), action1.getClass().getInterfaces(),myInvocationHandler);
    action.doAction();
    System.out.println(action.getClass());
}
}

 4、java内存模型

寄存器、栈(进程独享)、堆(共享)、静态区(方法去)、常量池(每个类都维护一个自己的常量池)。

堆区: 

1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令) 
2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身 
栈区: 
1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中 
2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。 
3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。 
方法区: 
1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。 
2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。 

一个例子:

 

public   class  AppMain                //运行时, jvm 把appmain的信息都放入方法区 
{ 
public   static   void  main(String[] args)  //main 方法本身放入方法区。 
{ 
Sample test1 = new  Sample( " 测试1 " );   //test1是引用,所以放到栈区里, Sample是自定义对象应该放到堆里面 
Sample test2 = new  Sample( " 测试2 " ); 

test1.printName(); 
test2.printName(); 
} 
} Sample.java 

public   class  Sample        //运行时, jvm 把appmain的信息都放入方法区 
{ 
/** 范例名称 */ 
private  name;      //new Sample实例后, name 引用放入栈区里,  name 对象放入堆里 

/** 构造方法 */ 
public  Sample(String name) 
{ 
this .name = name; 
} 

/** 输出 */ 
public   void  printName()   //print方法本身放入 方法区里。 
{ 
System.out.println(name); 
} 
} 

 

 

首先,系统收到了我们发出的指令,启动了一个Java虚拟机进程,这个进程首先从classpath中找到AppMain.class文件,读取这个文件中的二进制数据,然后把Appmain类的类信息存放到运行时数据区的方法区中。这一过程称为AppMain类的加载过程。 
接着,Java虚拟机定位到方法区中AppMain类的Main()方法的字节码,开始执行它的指令。这个main()方法的第一条语句就是: 
Sample test1=new Sample("测试1"); 
语句很简单啦,就是让java虚拟机创建一个Sample实例,并且呢,使引用变量test1引用这个实例。貌似小case一桩哦,就让我们来跟踪一下Java虚拟机,看看它究竟是怎么来执行这个任务的: 
1、 Java虚拟机一看,不就是建立一个Sample实例吗,简单,于是就直奔方法区而去,先找到Sample类的类型信息再说。结果呢,嘿嘿,没找到@@,这会儿的方法区里还没有Sample类呢。可Java虚拟机也不是一根筋的笨蛋,于是,它发扬“自己动手,丰衣足食”的作风,立马加载了Sample类,把Sample类的类型信息存放在方法区里。 
2、 好啦,资料找到了,下面就开始干活啦。Java虚拟机做的第一件事情就是在堆区中为一个新的Sample实例分配内存, 这个Sample实例持有着指向方法区的Sample类的类型信息的引用。这里所说的引用,实际上指的是Sample类的类型信息在方法区中的内存地址,其实,就是有点类似于C语言里的指针啦~~,而这个地址呢,就存放了在Sample实例的数据区里。 
3、 在JAVA虚拟机进程中,每个线程都会拥有一个方法调用栈,用来跟踪线程运行中一系列的方法调用过程,栈中的每一个元素就被称为栈帧,每当线程调用一个方法的时候就会向方法栈压入一个新帧。这里的帧用来存储方法的参数、局部变量和运算过程中的临时数据。OK,原理讲完了,就让我们来继续我们的跟踪行动!位于“=”前的Test1是一个在main()方法中定义的变量,可见,它是一个局部变量,因此,它被会添加到了执行main()方法的主线程的JAVA方法调用栈中。而“=”将把这个test1变量指向堆区中的Sample实例,也就是说,它持有指向Sample实例的引用。 
OK,到这里为止呢,JAVA虚拟机就完成了这个简单语句的执行任务。参考我们的行动向导图,我们终于初步摸清了JAVA虚拟机的一点点底细了,COOL! 
接下来,JAVA虚拟机将继续执行后续指令,在堆区里继续创建另一个Sample实例,然后依次执行它们的printName()方法。当JAVA虚拟机执行test1.printName()方法时,JAVA虚拟机根据局部变量test1持有的引用,定位到堆区中的Sample实例,再根据Sample实例持有的引用,定位到方法去中Sample类的类型信息,从而获得printName()方法的字节码,接着执行printName()方法包含的指令。 

 

 

5、类加载

将二进制Class文件加载到java内存中的方法区中。步骤

1)加载;

2)链接:验证;准备(为类的静态变量分配内存,并初始化为默认值);解析(将符号引用转化为直接引用)

3)初始化,为类的静态变量赋予正确的初始值。

注:只有当类或接口被首次主动使用时,才进行初始化步骤。

主动使用包括:

1)创建类实例;

2)方位某个类或接口的静态变量;

3)反射,Class.forName;

4)初始化一个类的子类;

5)被标明是启动类的类,包含main的类;

 

加载规则:父类委托

加载器类型:

1)根加载器(bootstrap),加载javahome/lib

2)扩展加载器,加载javahome/lib/ext

3)系统加载器,加载自定义类,是用户自定义类加载器的父类。

自定义ClassLoad——实现从自定义路径加载类

 

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;


public class MyClassLoader extends ClassLoader 
{
    private String name;
    private String path = "d:\\";
    private final String fileType = ".class";
    
    public MyClassLoader(String name) {
        super();
        this.name = name;
    }
    
    public MyClassLoader(ClassLoader parent, String name)
    {
        super(parent);
        this.name = name;
    }
    
    @Override
    public String toString()
    {
        return this.name;
    }
    protected Class findClass(String name) throws ClassNotFoundException 
    {
        byte[] data = loadClassData(name);
        return this.defineClass(name,data, 0, data.length);
        //throw new ClassNotFoundException(name);
        //return clazz;
    }

    private byte[] loadClassData(String name) 
    {
        byte[] data = null;
        InputStream is = null;
        ByteArrayOutputStream bos = null;
        try
        {
        name = name.replace(".", "\\");
        is = new FileInputStream(new File(path+name+fileType));
        bos = new ByteArrayOutputStream();
        int ch = 0;
        if(-1 != (ch= is.read()))
        {
            bos.write(ch);
        }
        data = bos.toByteArray();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally
        {
            try
            {
                bos.close();
                is.close();
            }
            catch(Exception e)
            {
                e.printStackTrace();
            }
        }
        return data;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public String getFileType() {
        return fileType;
    }
    
    public static void main(String[] args)throws Exception
    {
        MyClassLoader loader1 = new MyClassLoader("loader1");
        loader1.setPath("G:\\test\\server\\");
        MyClassLoader loader2 = new MyClassLoader(loader1,"loader2");
        loader2.setPath("G:\\test\\client\\");
        MyClassLoader loader3 = new MyClassLoader(null, "loader3");
        loader3.setPath("G:\\test\\other\\");
        //test(loader1);
        //test(loader2);
        //test(loader3);
        Class clazz = loader1.loadClass("Sample");
        Object obj = clazz.newInstance();
        System.out.println(clazz.hashCode());
        loader1 = null;
        clazz = null;
        obj = null;
        
        loader1 = new MyClassLoader("loader1");
        loader1.setPath("G:\\test\\server\\");
        clazz = loader1.loadClass("Sample");
        obj = clazz.newInstance();
        System.out.println(clazz.hashCode());
    }
    
    public static void test(ClassLoader classLoader)throws Exception
    {
        Class clazz = classLoader.loadClass("Sample");
        Object object = clazz.newInstance();
    }
}
 

6、jdbc和DAO

步骤:

1)加载驱动类

Class.forName("com.mysql.jdbc.Driver");

2)获取连接

Connection conn = DriverManager.getConnection("连接字","用户名","密码");

例子:

 

public class Test {
    public static void main(String[] args) {
        try {
            // 加载MySql的驱动类
            Class clazz = Class.forName("com.mysql.jdbc.Driver");
            System.out.println(clazz.getClassLoader());
        } catch (ClassNotFoundException e) {
            System.out.println("找不到驱动程序类 ,加载驱动失败!");
            e.printStackTrace();
        }

        String url = "jdbc:mysql://localhost:3306/test";
        String username = "root";
        String password = "123456";
        try {
            String sql = "select * from custom";
            String sql1 = "select * from custom where id = ?";
            Connection conn = DriverManager.getConnection(url, username,
                    password);
            PreparedStatement pstmt = conn.prepareStatement(sql1) ;
            pstmt.setInt(1, 1);
            /*Statement statement = conn.createStatement();
            ResultSet rs = statement.executeQuery(sql);*/
            ResultSet rs = pstmt.executeQuery();
            
            while (rs.next()) {
                String name = rs.getString("name");
                String pass = rs.getString(3); // 此方法比较高效
                System.out.println(name + "**" + pass);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }
}

使用PreparedStatement的好处:

1)有效的利用了数据库的缓存几只,DB会缓存部分重复使用的语句,如果是Statement的话,每次查询条件稍有不同就认为是不同语句,无法使用缓存

2)防止sql注入,如sql语句中有"select * from custom where id" = + username ,username是网页传来的值,那么username="yang or 1==1"就能使查询条件用为为真,从而查询到所有的信息。

 DAO Factory

组件:

1)数值对象(VO)

 

public class CustomBean {

}

 

 2)DAO接口

 

public interface CustomDao {
    public List findAll();
}

 

 3)DAO接口实现类

 

public class CustomDaoImp implements CustomDao {

    @Override
    public List findAll() {
        // TODO Auto-generated method stub
        return null;
    }

}
public class CustomDaoImpHibernate implements CustomDao{...}

 

 4)DAO工厂

 

public class DaoFactory {
    public static CustomDao getCustomDao()
    {
        return new CustomDaoImp();
        //return new CustomDaoImpHibernate();
    }
}
 

 

优点:

1)实现了数据访问与业务逻辑的分离;

2)引入了Factory模式,通过工厂返回接口,隐藏了具体数据库操作的实现,与具体代码实现分离,可以是Hibernate或者jdbc,因此代码的修改只需修改DaoFactory中的返回的对象。 

 

7、String、StringBuffer和StringBuilder

String不可变字符串,后两种通过append可以使字符串增长(要预留空间,所以初始化时要指定合适的长度,避免内存浪费)。

StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是线程不安全的。
StringBuffer 始于 JDK 1.0
StringBuilder 始于 JDK 1.5
从 JDK 1.5 开始,带有字符串变量的连接操作(+)如String s +="aa";,JVM 内部采用的是StringBuilder 来实现的,而之前这个操作是采用 StringBuffer 实现的。

你可能感兴趣的:(java,java,基础,面试,笔试)