面试相关问题

海银财富: 自我介绍,
       公司项目,模块流程,如订单流程,订单状态如何控制。
       数据优化方案?
       公司架构,solr部分,原理,分词器,数据同步等,activeMQ推送,结合仓储系统。
       memcache 部分原理,主要存储什么?
       mysql与oracle,(比较)
       mysql的分页原理

       数据的插入,快速的方法

     servlet 的生命周期

       。。。
       框架的应用场景

孟长江:   自我介绍,
       问简历内容,质疑奖项(程序设计二等奖),主要设计了什么?
       公司项目分多少模块?介绍项目
       审核,付款流程。
       抽象类与接口
       sql中long类型如何转换成date类型?
       sql外连接内链接
       nginx的应用场景,原理,如何配置
       dubbo应用,模块。
       linux指令,如查看端口有没有被占用
       。。。

易商互动: 简历内容,
       公司项目,架构
       mybatis相关
       mysql建表的两种类型,区别
       依赖注入,@resource等介绍
       给出应用场景,写sql语句(orderby ,groupby,limit,多张表)
       memcache相关,原理,存储内容。

       公司模块,数据交易量

       反射,反射的实现;

       webservice相关

驴妈妈:   单选与多选混合选择题25
        java基础
        object属性方法
        301,302,403,404,400

        tcp,udp

        java里的静态成员变量是放在了堆内存还是栈内存



       属性与方法的区别

      上传图片代码流程,如何限制大小

       Struts继承了什么
       mybatis,#与$的区别
       dubbo生产者与消费者

       dubbo如何序列化

微盟:Aop实现原理?

         排序算法的实现原理,说一到两种?

          Mybatis实现原理?

         如何实现分库分表?

         集合的体系结构,其中的差别,原型?

         set与list的contains方法区别?

        数据库主从如何配置?

        ==与equals实现原码?

        垃圾回收器原理?

        jvm 堆栈问题?

        solr的节点?

        线程安全?

        高并发事物实现?

        线程链?

        最近读的一本书,讲讲?


堆区: 

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

2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量




程序运行时,我们最好对数据保存到什么地方做到心中有数。特别要注意的是内在的分配,有六个地方都可以保存数据:
1、 寄存器。这是最快的保存区域,因为它位于和其他所有保存方式不同的地方:处理器内部。然而,寄存器的数量十分有限,所以寄存器是根据需要由编译器分配。我们对此没有直接的控制权,也不可能在自己的程序里找到寄存器存在的任何踪迹
2、 堆栈。驻留于常规RAM(随机访问存储器)区域。但可通过它的“堆栈指针”获得处理的直接支持。堆栈指针若向下移,会创建新的内存;若向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。创建程序时,java编译器必须准确地知道堆栈内保存的所有数据的“长度”以及“存在时间”。这是由于它必须生成相应的代码,以便向上和向下移动指针。这一限制无疑影响了程序的灵活性,所以尽管有些java数据要保存在堆栈里——特别是对象句柄,但java对象并不放到其中。
3、堆。一种常规用途的内存池(也在RAM区域),其中保存了java对象。和堆栈不同:“内存堆”或“堆”最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间。因此,用堆保存数据时会得到更大的灵活性。要求创建一个对象时,只需用new命令编制相碰的代码即可。执行这些代码时,会在堆里自动进行数据的保存。当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间
4、 静态存储。这儿的“静态”是指“位于固定位置”。程序运行期间,静态存储的数据将随时等候调用。可用static关键字指出一个对象的特定元素是静态的。但java对象本身永远都不会置入静态存储空间。
5、 常数存储。常数值通常直接置于程序代码内部。这样做是安全的。因为它们永远都不会改变,有的常数需要严格地保护,所以可考虑将它们置入只读存储器(ROM)。
6、 非RAM存储。若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。其中两个最主要的例子便是“流式对象”和“固定对象”。对于流式对象,对象会变成字节流,通常会发给另一台机器,而对于固定对象,对象保存在磁盘中。即使程序中止运行,它们仍可保持自己的状态不变。对于这些类型的数据存储,一个特别有用的技艺就是它们能存在于其他媒体中,一旦需要,甚至能将它们恢复成普通的、基于RAM的对象。


首先,java里面是没有静态变量这个概念的,不信你自己在方法里面定义一个static int i =0;java里只有静态成员变量。它属于类的属性。至于他放在那里?楼上说的是静态区。我不知道到底有没有这个翻译。但是 深入jvm里是是翻译为方法区的。虚拟机的体系结构:堆,方法区,本地方法栈,pc寄存器。而方法区保存的就是一个类的模板,堆是放类的实例的。栈是一般来用来函数计算的。随便找本计算机底层的书都知道了。栈里的数据,函数执行完就不会存储了。这就是为什么局部变量每一次都是一样的。就算给他加一后,下次执行函数的时候还是原来的样子。


public class Foo {
    int value;
    public Foo(int value){
        this.value = value;
    }
    
    @Override
    public boolean equals(Object obj) {
        if(obj instanceof Foo){
            Foo foo = (Foo)obj;
//下一行是什么意思?
            return value == this.value;
        }else{
            return false;
        }
    }
    
    public static void main(String[] args) {
        ArrayList<Foo> list = new ArrayList<Foo>();
        HashSet<Foo> set = new HashSet<Foo>();
        list.add(new Foo(1));
        set.add(new Foo(1));
        System.out.println(list.contains(new Foo(1)));
        System.out.println(set.contains(new Foo(1)));
    }

}

1、HashSet的contains返回true,当且仅当equals返回true并且hashCode返回相等的值 ;

2、list.contains(o),系统会对list中的每个元素e调用o.equals(e),方法,加入list中有n个元素,那么会调用n次o.equals(e),只要有一次o.equals(e)返回了true,那么list.contains(o)返回true,否则返回false。

Arraylist是有序的,实现了可变大小的数组(可以自增加),允许重复元素
 Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。如果要实现值相同的两个元素比较,返回true,还需要重写hascode方法,让它们的hascode相同。

JAVA中几种集合(List、Set和Map)的区别

Set对每个对象只接受一次,并使用自己内部的排序方法(通常,你只关心某个元素是否属于Set,而不关心它的顺序--否则应该使用List)。Map同样对每个元素保存一份,但这是基于"键"的,Map也有内置的排序,因而不关心元素添加的顺序。如果添加元素的顺序对你很重要,应该使用 LinkedHashSet或者LinkedHashMap.
总结:List有顺序有重复没有排序,set无重复有排序,map的key也和set一样。如果想跟List一样需要有插入元素的顺序,请使用LinkedHashSet或者LinkedHashMap。
  List的功能方法
  实际上有两种List: 一种是基本的ArrayList,其优点在于随机访问元素,另一种是更强大的LinkedList,它并不是为快速随机访问设计的,而是具有一套更通用的方法。
  List : 次序是List最重要的特点:它保证维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(这只推荐LinkedList使用。)一个List可以生成ListIterator,使用它可以从两个方向遍历List,也可以从List中间插入和移除元素。
  ArrayList : 由数组实现的List。允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历ArrayList,而不是用来插入和移除元素。因为那比LinkedList开销要大很多。
  LinkedList : 对顺序访问进行了优化,向List中间插入与删除的开销并不大。随机访问则相对较慢。(使用ArrayList代替。)还具有下列方法:addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(), 这些方法 (没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。
  Set的功能方法
  Set : 存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。
  HashSet : 为快速查找设计的Set。存入HashSet的对象必须定义hashCode()。
  TreeSet : 保存次序的Set, 底层为树结构。使用它可以从Set中提取有序的序列。
  LinkedHashSet : 具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。
  Map的功能方法
  方法put(Object key, Object value)添加一个“值”(想要得东西)和与“值”相关联的“键”(key)(使用它来查找)。方法get(Object key)返回与给定“键”相关联的“值”。可以用containsKey()和containsValue()测试Map中是否包含某个“键”或“值”。标准的Java类库中包含了几种不同的Map:HashMap, TreeMap, LinkedHashMap, WeakHashMap, IdentityHashMap。它们都有同样的基本接口Map,但是行为、效率、排序策略、保存对象的生命周期和判定“键”等价的策略等各不相同。
  执行效率是Map的一个大问题。看看get()要做哪些事,就会明白为什么在ArrayList中搜索“键”是相当慢的。而这正是HashMap提高速度的地方。HashMap使用了特殊的值,称为“散列码”(hash code),来取代对键的缓慢搜索。“散列码”是“相对唯一”用以代表对象的int值,它是通过将该对象的某些信息进行转换而生成的。所有Java对象都能产生散列码,因为hashCode()是定义在基类Object中的方法。
  HashMap就是使用对象的hashCode()进行快速查询的。此方法能够显著提高性能。
  Map : 维护“键值对”的关联性,使你可以通过“键”查找“值”
  HashMap : Map基于散列表的实现。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量capacity和负载因子load factor,以调整容器的性能。
  LinkedHashMap : 类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点。而在迭代访问时发而更快,因为它使用链表维护内部次序。
  TreeMap : 基于红黑树数据结构的实现。查看“键”或“键值对”时,它们会被排序(次序由Comparabel或Comparator决定)。TreeMap的特点在于,你得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。
  WeakHashMao : 弱键(weak key)Map,Map中使用的对象也被允许释放: 这是为解决特殊问题设计的。如果没有map之外的引用指向某个“键”,则此“键”可以被垃圾收集器回收。
  IdentifyHashMap : 使用==代替equals()对“键”作比较的hash map。专为解决特殊问题而设计。

(二)静态代码块的初始化顺序

class Parent{ static String name = "hello"; { System.out.println("parent block"); } static { System.out.println("parent static block"); } public Parent(){ System.out.println("parent constructor"); } }
class Child extends Parent{ static String childName = "hello"; { System.out.println("child block"); } static { System.out.println("child static block"); } public Child(){ System.out.println("child constructor"); } }
public class StaticIniBlockOrderTest {
public static void main(String[] args) { new Child();//语句(*) } }
问题:当执行完语句(*)时,打印结果是什么顺序?为什么? 解答:当执行完语句(*)时,打印结果是这样一个顺序 :
parent static block child static block parent block parent constructor child block child constructor

 

分析:当执行new Child()时,它首先去看父类里面有没有静态代码块,如果有,它先去执行父类里面静态代码块里面的内容,当父类的静态代码块里面的内容执行完毕之后,接着去执行子类(自己这个类)里面的静态代码块,当子类的静态代码块执行完毕之后,它接着又去看父类有没有非静态代码块,如果有就执行父类的非静态代码块,父类的非静态代码块执行完毕,接着执行父类的构造方法;父类的构造方法执行完毕之后,它接着去看子类有没有非静态代码块,如果有就执行子类的非静态代码块。子类的非静态代码块执行完毕再去执行子类的构造方法,这个就是一个对象的初始化顺序。

总结: 对象的初始化顺序:首先执行父类静态的内容,父类静态的内容执行完毕后,接着去执行子类的静态的内容,当子类的静态内容执行完毕之后,再去看父类有没有非静态代码块,如果有就执行父类的非静态代码块,父类的非静态代码块执行完毕,接着执行父类的构造方法;父类的构造方法执行完毕之后,它接着去看子类有没有非静态代码块,如果有就执行子类的非静态代码块。子类的非静态代码块执行完毕再去执行子类的构造方法。总之一句话,静态代码块内容先执行,接着执行父类非静态代码块和构造方法,然后执行子类非静态代码块和构造方法。

注意:子类的构造方法,不管这个构造方法带不带参数,默认的它都会先去寻找父类的不带参数的构造方法。如果父类没有不带参数的构造方法,那么子类必须用supper关键子来调用父类带参数的构造方法,否则编译不能通过。


JVM 初始化一般初始化一个类:
1>假如这个类还没有被加载和连接,程序先加载并连接该类。
2>假如该类的直接父类还没有被初始化,则先初始化直接父类。
3>假如类中的初始化语句,则执行这些初始化语句。
初始化语句顺序是先执行:代码块,然后执行变量


public class test{
static int i=6;@2
static (){//@1
i=5;
}
}
这里先执行@1然后再执行@2部分

1. Classloader的作用,概括来说就是将编译后的class装载、加载到机器内存中,为了以后的程序的执行提供前提条件。
2. 一段程序引发的思考:
风中叶老师在他的视频中给了我们一段程序,号称是世界上所有的Java程序员都会犯的错误。
诡异代码如下:
Java代码
package test01;        
class Singleton {     
public static Singleton singleton = new Singleton();   
public static int a;        
public static int b = 0;          
private Singleton() {         
super();           
a++;           
b++;        
}  
public static Singleton GetInstence() {
   return singleton;       
}   
}     
public class MyTest {             /**        * @param args        */        public static void main(String[] args) {          
Singleton mysingleton = Singleton.GetInstence();             System.out.println(mysingleton.a);             System.out.println(mysingleton.b);    
    }       
}   
一般不假思索的结论就是,a=1,b=1。给出的原因是:a、b都是静态变量,在构造函数调用的时候已经对a和b都加1了。答案就都是1。但是运行完后答案却是a=1,b=0。

下面我们将代码稍微变一下
Java代码
public static Singleton singleton = new Singleton(); 
public static int a;     public static int b = 0;   
的代码部分替换成
Java代码
public static int a;   
public static int b = 0;  
public static Singleton singleton = new Singleton();   
效果就是刚才预期的a=1,b=1。

为什么呢,这3句无非就是静态变量的声明、初始化,值的变化和声明的顺序还有关系吗?Java不是面向对象的吗?怎么和结构化的语言似地,顺序还有关系。这个就是和Java虚拟机JVM加载类的原理有着直接的关系。
先执行静态代码块,然后再执行变量

1. 类在JVM中的工作原理
要想使用一个Java类为自己工作,必须经过以下几个过程

1):类加载load:从字节码二进制文件——.class文件将类加载到内存,从而达到类的从硬盘上到内存上的一个迁移,所有的程序必须加载到内存才能工作。将内存中的class放到运行时数据区的方法区内,之后在堆区建立一个java.lang.Class对象,用来封装方法区的数据结构。这个时候就体现出了万事万物皆对象了,干什么事情都得有个对象。就是到了最底层究竟是鸡生蛋,还是蛋生鸡呢?类加载的最终产物就是堆中的一个java.lang.Class对象。
2):连接:连接又分为以下小步骤
验证:出于安全性的考虑,验证内存中的字节码是否符合JVM的规范,类的结构规范、语义检查、字节码操作是否合法、这个是为了防止用户自己建立一个非法的XX.class文件就进行工作了,或者是JVM版本冲突的问题,比如在JDK6下面编译通过的class(其中包含注解特性的类),是不能在JDK1.4的JVM下运行的。
准备:将类的静态变量进行分配内存空间、初始化默认值。(对象还没生成呢,所以这个时候没有实例变量什么事情)
解析:把类的符号引用转为直接引用(保留)
3):类的初始化: 将类的静态变量赋予正确的初始值,这个初始值是开发者自己定义时赋予的初始值,而不是默认值。

2. 类的主动使用与被动使用
以下是视为主动使用一个类,其他情况均视为被动使用!
1):初学者最为常用的new一个类的实例对象(声明不叫主动使用)
2):直接调用类的静态方法。
3):对类的静态变量进行读取、赋值操作的
4):反射调用一个类的方法。
5):初始化一个类的子类的时候,父类也相当于被程序主动调用了(如果调用子类的静态变量是从父类继承过来并没有复写的,那么也就相当于只用到了父类的东东,和子类无关,所以这个时候子类不需要进行类初始化)。
6):直接运行一个main函数入口的类。


所有的JVM实现(不同的厂商有不同的实现,有人就说IBM的实现比Sun的要好……)在首次主动调用类和接口的时候才会初始化他们。

1. 类的加载方式
1):本地编译好的class中直接加载
2):网络加载:java.net.URLClassLoader可以加载url指定的类
3):从jar、zip等等压缩文件加载类,自动解析jar文件找到class文件去加载util类
4):从java源代码文件动态编译成为class文件
2. 类加载器

JVM自带的默认加载器
1):根类加载器:bootstrap,由C++编写,所有Java程序无法获得。
2):扩展类加载器:由Java编写。
3):系统类、应用类加载器:由Java编写。
用户自定义的类加载器:java.lang.ClassLoader的子类,用户可以定制类的加载方式。每一个类都包含了加载他的ClassLoader的一个引用——getClass().getClassLoader()。如果返回的是null,证明加载他的ClassLoader是根加载器bootstrap。

加载顺序
1. 回顾那个诡异的代码
从入口开始看
Singleton mysingleton = Singleton.GetInstence();
是根据内部类的静态方法要一个Singleton实例。
这个时候就属于主动调用Singleton类了。
之后内存开始加载Singleton类
1):对Singleton的所有的静态变量分配空间,赋默认的值,所以在这个时候,singleton=null、a=0、b=0。注意b的0是默认值,并不是咱们手工为其赋予的的那个0值。
2):之后对静态变量赋值,这个时候的赋值就是我们在程序里手工初始化的那个值了。此时singleton = new Singleton();调用了构造方法。构造方法里面a=1、b=1。之后接着顺序往下执行。
3):
public static int a;       public static int b = 0; 
a没有赋值,保持原状a=1。b被赋值了,b原先的1值被覆盖了,b=0。所以结果就是这么来的。类中的静态块static块也是顺序地从上到下执行的。
2. 编译时常量、非编译时常量的静态变量
如下代码
Java代码
package test01;         class FinalStatic {             public static final int A = 4 + 4;             static {             System.out.println("如果执行了,证明类初始化了……");         }         }         public class MyTest03 {             /**        * @param args        */        public static void main(String[] args) {             System.out.println(FinalStatic.A);         }         }   
结果是只打印出了8,证明类并没有初始化。反编译源码发现class里面的内容是

public static final int A = 8;

也就是说编译器很智能的、在编译的时候自己就能算出4+4是8,是一个固定的数字。没有什么未知的因素在里面。

将代码稍微改一下

public static final int A = 4 + new Random().nextInt(10);
这个时候静态块就执行了,证明类初始化了。在静态final变量在编译时不定的情况下。如果客户程序这个时候访问了该类的静态变量,那就会对类进行初始化,所以尽量静态final变量尽量没什么可因素在里面1,否则性能会有所下降。
1. ClassLoader的剖析
ClassLoader的loadClass方法加载一个类不属于主动调用,不会导致类的初始化。如下代码块
Java代码
ClassLoader classLoader = ClassLoader.getSystemClassLoader();     Class clazz = classLoader.loadClass("test01.ClassDemo");  
并不会让类加载器初始化test01.ClassDemo,因为这不属于主动调用此类。
ClassLoader的关系:
根加载器——》扩展类加载器——》应用类加载器——》用户自定义类加载器
加载类的过程是首先从根加载器开始加载、根加载器加载不了的,由扩展类加载器加载,再加载不了的有应用加载器加载,应用加载器如果还加载不了就由自定义的加载器(一定继承自java.lang. ClassLoader)加载、如果自定义的加载器还加载不了。而且下面已经没有再特殊的类加载器了,就会抛出ClassNotFoundException,表面上异常是类找不到,实际上是class加载失败,更不能创建该类的Class对象。
若一个类能在某一层类加载器成功加载,那么这一层的加载器称为定义类加载器。那么在这层类生成的Class引用返回下一层加载器叫做初始类加载器。因为加载成功后返回一个Class引用给它的服务对象——也就是调用它的类加载器。考虑到安全,父委托加载机制。
ClassLoader加载类的原代码如下
  
初始化系统ClassLoader代码如下

它里面调用了很多native的方法,也就是通过JNI调用底层C++的代码。

当一个类被加载、连接、初始化后,它的生命周期就开始了,当代表该类的Class对象不再被引用、即已经不可触及的时候,Class对象的生命周期结束。那么该类的方法区内的数据也会被卸载,从而结束该类的生命周期。一个类的生命周期取决于它Class对象的生命周期。由Java虚拟机自带的默认加载器(根加载器、扩展加载器、系统加载器)所加载的类在JVM生命周期中始终不被卸载。所以这些类的Class对象(我称其为实例的模板对象)始终能被触及!而由用户自定义的类加载器所加载的类会被卸载掉!

JVM类加载机制

 全盘负责
  父类委托:所谓父类委托是先让parent(父)类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
  缓存机制
  通过反射查看类信息
  Java程序中获得Class对象通常有如下三种方法:
  a) 使用Class类的forName()静态方法.该方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名(必须添加完整包名)。
  b) 调用某个类的class属性来获取该类对应的Class对象。
  c) 调用某个对象的getClass()方法,该方法是java.lang.Object类中的一个方法,所以所有java对象都可以调用该方法,该方法将会返回该对象所属类对应的Class对象。
  b方法:代码更安全,程序在编译阶段就可以检查需要访问的Class对象是否存在。
  程序性能提高,因为这种方法无需调用方法,所以性能更好。
  一旦获得某个类所对应的Class对象后,就可以调用Class对象的方法来获得该对象和该类的真实信息。
  getDeclared 与访问级别无关,显式声明的。
  get 获得所有的但只是public,包括继承的。
  使用反射生成并操作对象
  Class对象可以获得该类里包括的方法(由Methode对象表示),构造器(由Constructor对象表示),Field(Field对象表示),这三个类都定义在java.lang.reflect包下,并实现了java.lang.reflect.Member接口,程序可以通过Method对象来执行对应的方法,通过Constructor对象来调用对应的构造器创建对象,能通过Field对象直接访问并修改对象的属性值。
  通过反射来生成对象有如下两种方式:
  a) 使用Class对象的newInstance()方法来创建该Class对象对应类的实例,这种方法要求该Class对象的对应类有默认构造器,而执行newInstance()方法时实际上是利用默认构造器来创建该类的实例。
  b) 先利用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例,通过这种方式可以选择使用某个类的制定构造器来创建实例。
  实际上只有当程序需要动态地创建该对象时才会考虑使用反射,通常在开发通用性比较广的框架和基础平台时可能会大量使用反射。
  当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods()方法或者getMethod()方法来获取全部或指定方法----这两个方法的返回值是Method对象数组,或者Method对象。
  每个Method对象包含一个方法,获得Method对象后,程序就可通过该Method来调用对应方法,在Method里包含一个invoke方法。
  Obejct invoke(Object obj, Object …args);该方法中的obj是执行该方法的主调,后面的args是执行该方法时传入该方法的实参。
  当通过Method的invoke方法来调用对应的方法时,Java会要求程序必须有调用该方法的权限,如果程序确实需要调用某个对象的invoke方法,可以先调用Method对象的如下方法:
  setAccessible(boolean flag):将flag对象的accessible标志设置为指示的Boolean值。
  true表示该Method在使用时应该取消Java语言访问权限检查。

java中try{}catch{}和finally{}的执行顺序问题

 今天我给大家讲解一下java的的错误和异常处理机制以及相关异常的执行顺序问题。如有不足的地方,欢迎批评指正~

 

1、首相简单介绍一下java中的错误(Error)和异常(Exception)

    错误和异常的介绍:

    在java.lang软件包中有一个java.lang.Throwable类,这个类是java中所有错误和异常的超类。

    在java中错误和异常的继承主要有两个: 分别为Error和Exception 这两个。

    Error:         是java中所有错误类的父类,就是jvm出现错误,以及系统蹦溃等现象,这些错误没办法通过程序来处理,对于系统错误,一般不需要开发

                     人员处理(也无法处理), 比如内存溢出(Out of Memory)和线程死锁等系统问题。所以在程序中需要使用catch来捕捉处理这类的错误或者使用throw来抛出相

                     关的异常。

    Exception:  又可以分为checkedException(编译时异常) 和RuntimeException(运行时异常) 这两种异常,checkedException异常在进行编译的

                     时候就可以知道会不会发生异常,如果不对这些异常进行抛出、捕获的话就不能通过编译,如在使用java的io读取文件的时候,可能会会出现

                     所读取的文件不存在的情况 对于,对于这类编译时的异常必须手动去处理它们(捕获或者抛出)。否则的话是无法正常通过编译器的。 而

                     RuntimeException就是运行的时候出现的异常,在之前你是没办法确定是不是会出现异常。这类异常仅仅在程序运行的过程中才会发现。

                     比如数组下标越界(ArrayIndexOutOfBoundsException),强制转换报错(ClassCastException,一部分),空指针异常(NullPointerException)

                    除数为零(/ by zero) 对于这类运行时的异常是否抛出, 由用户根据自身的情况 决定是否抛出异常。java并不强制要求用户 一定处理。

   2、异常处理过程

  把会出现异常的程序段放在try中,当抛出异常的时候就会在系统中生成一个异常对象,然后进行查找捕获这个异常,然后进行处理这个异常,处理之后接着执行下面的程序。

出现异常之后如果没有进行捕获处理系统就会直接将这个异常栈的跟踪信息直接打印出来之后就结束这个程序的执行。

     对于异常的处理方式有两种分别为:try{}catch{}finally{}和throws下面简单说一下这两者的区别和联系

     请先看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Test{
    public static void main(String[] args){
        Test2 test2 = new Test2();
        try {
           System.out.println( "invoke the method begin!" );
           test2.method();
           System.out.println( "invoke the method end!" );
        } catch (Exception e){
           System.out.println( "catch Exception!" );
        }
    }
}
 
class Test2{
    public void method() throws Exception{
        System.out.println( "method begin!" );
        int a = 10 ;
        int b = 0 ;
        int c = a/b;
        System.out.println( "method end!" );
    }
}
 
很明显,答案出来了:
invoke the method begin!
method begin!
catch Exception!

  

     对于try{}catch{}finally{}而言,用户可能确定知道代码会出现相关的异常,把有可能出现问题的地方放到try中去执行,如果一旦出现异常,立刻终止当前代码的继续

     执行,转而去执行catch{}里面的内容。对于这类异常用户已经处理了,不会在向上抛出。

    对于throws而言,一般使用在方法名的后面,使用throws关键字的时候,一般是开发者不确定出现什么异常或者出现异常的情况可能有多种。这时开发者在方法后面加      throws关键字抛出相关的异常。对于调用该方法的其它开发者者必须捕获这个异常或者继续throws这个异常,把这个异常传递下去,交给其对应的父类去处理。

    需要注意throw关键字和throws关键字是有区别的。throw一般用于方法中,抛出用户自定义的异常如 throw new MyException("用户自定义异常")。

    而throws是用在方法名的后面,通知使用该方法的人,当前方法有可能抛出异常。

   

     如果简单的理解可以这样看:对于throws可以理解为抛出,抛出给别人,自己不处理。而try{}catch{}finally{}则可以理解为截断,开发者自己处理这个异常。

   

     3、异常处理的执行顺序(针对try{}catch{}finally{}而言)

     对于try{}catch{}finally{}而言,,它们的执行顺序很简单,如果在try{}中捕获相应的异常,中断当前代码的执行,转而去执行catch{}中的内容,最后去执行

    finally{}中方法,一般来说finally中的方法都是会被执行的,其中finally中很大程度上用于资源的释放。

    下面讲解一些我们java程序员需要注意的地方。

          afinally中的代码总是会执行吗?

             答:no,如果一个方法内在执行try{}语句之前就已经return了,那么finally语句指定不会执行了。因为它根本没有进入try语句中

                        如果在一个try语句中调用System.exit(0);方法,那么就会退出当前java虚拟机,那么finally也就没有执行的机会了。

          bfinally在return之前执行还是在return之后执行?

            答:很多人可能会说在return执行之前执行。我的答案是在return中间执行,是不是很特别,请按下面的例子:

           

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.yonyou.test;
 
 
class Test{
 
   public static void main(String[] args) {
       System.out.println(method());
      }
   public static int method(){
       int x= 1 ;
       try {
           return x;
       } catch (Exception e)
       {
           return 0 ;
       } finally {
           ++x;
       }
       
   }
     }

  请问输出的结果是多少呢?

    正确答案是:1

   下面我来讲解一下这个程序的执行过程,

    首先程序在执行到try{}语句中的return方法后,就会先返回相应的值,并把相应的值存储在一个临时栈中去保存这个结果。这时临时栈中存储的值为1。

    但是程序不会立刻返回,转而回去执行finally中的方法,++x,在finally执行完后,方法全部执行完,这时会再次调用return方法,注意这时

   不在是返回值,而是告诉主调程序,被调程序已经执行完了,你可以接着去执行你主程序的其它方法了。但是请注意,此时返回的值还是原来保存在临时

   栈中的值1。

 为了更好的理解这个问题,我们看下面的程序:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.yonyou.test;
 
 
class Test{
 
   public static void main(String[] args) {
       System.out.println(method());
      }
   public static int method(){
       try {
           return 1 ;
       } catch (Exception e)
       {
           return 0 ;
       } finally {
           return 2 ;
       }
       
   }
     }

  这时的正确答案又是多少呢?

     没错是2,这里仅仅需要注意的是在try{}语句中执行到return 1 会在临时栈中存储值为1的变量。接着回去执行finally里面的内容,这时执行finally中的return 2;方法,这时

    临时栈中的值就是变为 2,会覆盖原来临时栈中的值1.所以它的返回值为2。

  

     cfinally方法是必须的吗?

         不是,开发者可以根据自身的情况去决定是否使用finally关键字。

try
catch
finally

1、将预见可能引发异常的代码包含在try语句块中。
2、如果发生了异常,则转入catch的执行。catch有几种写法:
catch
这将捕获任何发生的异常。
catch(Exception e)
这将捕获任何发生的异常。另外,还提供e参数,你可以在处理异常时使用e参数来获得有关异常的信息。
catch(Exception的派生类 e)
这将捕获派生类定义的异常,例如,我想捕获一个无效操作的异常,可以如下写:
catch(InvalidOperationException e)
{
....
}
这样,如果try语句块中抛出的异常是InvalidOperationException,将转入该处执行,其他异常不处理。

catch可以有多个,也可以没有,每个catch可以处理一个特定的异常。.net按照你catch的顺序查找异常处理块,如果找到,则进行处理,如果找不到,则向上一层次抛出。如果没有上一层次,则向用户抛出,此时,如果你在调试,程序将中断运行,如果是部署的程序,将会中止。

如果没有catch块,异常总是向上层(如果有)抛出,或者中断程序运行。

3、finally
finally可以没有,也可以只有一个。无论有没有发生异常,它总会在这个异常处理结构的最后运行。即使你在try块内用return返回了,在返回前,finally总是要执行,这以便让你有机会能够在异常处理最后做一些清理工作。如关闭数据库连接等等。
注意:如果没有catch语句块,那么finally块就是必须的。

如果你不希望在这里处理异常,而当异常发生时提交到上层处理,但在这个地方无论发生异常,都要必须要执行一些操作,就可以使用tryfinally,
很典型的应用就是进行数据库操作:
用下面这个原语来说明:
try
{
DataConnection.Open();
DataCommand.ExecuteReader();
...
return;
}
finally
{
DataConnection.Close();
}

无论是否抛出异常,也无论从什么地方return返回,finally语句块总是会执行,这样你有机会调用Close来关闭数据库连接(即使未打开或打开失败,关闭操作永远是可以执行的),以便于释放已经产生的连接,释放资源。


顺便说明,return是可以放在try语句块中的。但不管在什么时机返回,在返回前,finally将会执行。

小结

try { //执行的代码,其中可能有异常。一旦发现异常,则立即跳到catch执行。否则不会执行catch里面的内容 }

catch { //除非try里面执行代码发生了异常,否则这里的代码不会执行 }

finally { //不管什么情况都会执行,包括try catch 里面用了return,可以理解为只要执行了try或者catch,就一定会执行 finally }


JAVA中Object类中 有几个方法

Object类是所有类的直接或间接基类,如果一个类在声明时未继承基类,Java就默认其基类是Object,故Object被称为根类。该类位于java.lang包中,它有如下几个常用方法:
equals():用于比较两个对象是否指向同一块内存区域,相当于==运算符。(注意:在String类中,已将该方法改写比较字符串内容是否相同);
hashCode():返回该对象的哈希码值(整数),用于标识一个对象,如果两个对象相等,则哈希码值一定相同;
toString():返回值是String类型,描述当前对象的有关信息,当对象与String型数据的连接时,自动调用其toString()方法。

protected Object clone()创建并返回此对象的一个副本。
boolean equals(Object obj)指示其他某个对象是否与此对象“相等”。
protected void finalize()当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
Class<?> getClass()返回此 Object 的运行时类。
int hashCode()返回该对象的哈希码值。
void notify()唤醒在此对象监视器上等待的单个线程。
void notifyAll()唤醒在此对象监视器上等待的所有线程。
String toString()返回该对象的字符串表示。
void wait()在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
void wait(long timeout)在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。
void wait(long timeout, int nanos)在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。

关键字final的用法

有三种用法,分别是与变量、方法和类一起使用:
当final 与变量一起使用时,可声明常量。此后,变量的值不可以再改变;
当final与方法一起使用时,它阻止类方法的重写;
当final与类使用时,它阻止类的继承(该类的所有方法都是final)。

abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。

abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于 abstract class和interface 选择显得比较随意。

其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的 理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析, 试图给开发者提供一个在二者之间进行选择的依据。

一、理解抽象类

abstract class和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用 于定义抽象类的一种方法,请读者注意区分)定义的,那么什么是抽象类,使用抽象类

能为我们带来什么好处呢?

在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是 这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一 个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领 域进行分析 、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象 比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形 这 样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念 在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题 领域没有对 应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。
在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行 为 的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽 象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可 以操作一个 抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过 从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读 者一定知道,为了能够 实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的 关键所在。

二、从语法定义层面看abstract class和interface

在语法层面,Java语言对于abstract class和interface给出了不同的定义方式,下面以 定义一个名为Demo的抽象类为例来说明这种不同。使用abstract class的方式定义Demo 抽象类的方式如下:

  1. abstract class Demo { 

  2. abstract void method1(); 

  3. abstract void method2(); 

  4. … 

  5. } 
使用interface的方式定义Demo抽象类的方式如下:

  1. interface Demo { 

  2. void method1(); 

  3. void method2(); 

  4. … 

在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方 法,而在interface方式的实现中,Demo只能够有静态的 不能被修改的数据成员(也就 是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法
都是abstract的。从某种意义上说,interface是一种特殊 形式的abstract class。 从编程的角度来看,abstract class和interface都可以用来实现"design by contract"
的思想。但是在具体的使用上面还是有一些区别的。

首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承 关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑 Java对于多重继承的支持方面的一种折中考虑吧。
其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的 定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会 增加 一些复杂性,有时会造成很大的麻烦。在抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上 的 麻烦。因为如果后来想修改类的界面(一般通过abstract class或者interface来表 示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就 会非常的麻烦,可能要花费很多的时 间(对于派生类很多的情况,尤为如此)。但是如 果界面是通过abstract class来实现的,那么可能就只需要修改定义在abstract class 中的默认行为就可以了。

同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类 的 每一个派生类中,违反了"one rule,one place"原则,造成代码重复,同样不利于以后 的维护。因此,在abstract class和interface间进行选择时要非常的小心。

三、从设计理念层面看abstract class和interface

上面主要从语法定义和编程的角度论述了abstract class和interface的区别,这些层面 的区别是比较低层次的、非本质的。本文将从另一个层面:abstract class和interface 所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理

解二者概念的本质所在。

前面已经提到过,abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is a"关系,即父类和派生类在概念本质上应该是 相同的。对于interface 来说则不然,并不要求interface的实现者和interface定义在 概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使 论述便于理解 ,下面将通过一个简单的实例进行说明。

考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一 个表示该抽象概念的类型,定义方式分别如下所示:


使用abstract class方式定义Door:


  1. abstract class Door { 

  2. abstract void open(); 

  3. abstract void close(); 


使用interface方式定义Door:

  1. interface Door { 

  2. void open(); 

  3. void close(); 

其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使 用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区 别。

如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在 本 例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面 无关的问题都做了简化或者忽略)下面将罗列出可能的解决方案,并从设计理念层面对 这些不 同的方案进行分析。


解决方案一:

简单的在Door的定义中增加一个alarm方法,如下:

  1. abstract class Door { 

  2. abstract void open(); 

  3. abstract void close(); 

  4. abstract void alarm(); 

或者


  1. interface Door { 

  2. void open(); 

  3. void close(); 

  4. void alarm(); 


那么具有报警功能的AlarmDoor的定义方式如下:


  1. class AlarmDoor extends Door { 

  2. void open() { … } 

  3. void close() { … } 

  4. void alarm() { … } 

或者

  1. class AlarmDoor implements Door { 

  2. void open() { … } 

  3. void close() { … } 

  4. void alarm() { … } 

  5. } 

这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple ),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方 法混在了一起。这样引起的一个问题是那些仅仅 依赖于Door这个概念的模块会因为"报 警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。

解决方案二:

既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它 们分别定义在代 表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义; 两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概 念使用interface方式定义。

显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是 不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的 概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。

如果两个概念都使用interface方式来定义,那么就反映出两个问题:

1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?

2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们 的设计意图,因为在这两个概念的定义上(均使用 interface方式定义)反映不出上述 含义。

如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同 时它有具有报警 的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过, abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。
所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又 具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过 interface方式定 义。如下所示:

  1. abstract class Door { 

  2. abstract void open(); 

  3. abstract void close(); 


  4. interface Alarm { 

  5. void alarm(); 


  6. class AlarmDoor extends Door implements Alarm { 

  7. void open() { … } 

  8. void close() { … } 

  9. void alarm() { … } 

这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计 意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如
果我们认为AlarmDoor在概念本质上是报警器,同时又具有 Door的功能,那么上述的定义方式就要反过来了。

abstract class和interface是Java语言中的两种定义抽象类的方式,它们之间有很大的 相似性。但是对于它们的选择却又往往反映出对于问题领域中的概 念本质的理解、对于 设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系(虽然都能够实 现需求的功能)。这其实也是语言的一种的惯用法。


抽象方法是必须实现的方法。就象动物都要呼吸。但是鱼用鳃呼吸,猪用肺呼吸。动物类要有呼吸方法。怎么呼吸就是子类的事了。现在有很多讨论和建议提倡用interface代替abstract类,两者从理论上可以做一般性的 混用,但是在实际应用中,他们还是有一定区别的。抽象类一般作为公共的父类为子类的扩展提供基础,这里的扩展包括了属性上和行为上的。而接口一般来说不考虑属性,只考虑方法,使得子类可以自由的填补或者扩展接口所定义的方法,就像JAVA王子所说 的事件中的适配器就是一个很好的应用。用一个简单的例子,比如说一个教师,我们把它作为一个抽象类,有自己的属性,比如说年龄,教育程度,教师编号等等,而教师也是分很多种类的,我们就可以继承教师类而扩展特有的种类属性,而普遍属性已经直接继承了下来。
而接口呢~还是拿教师做例子,教师的行为很多,除了和普通人相同的以外,还有职业相关的行为,比如改考卷,讲课等等,我们把这些行为定义成无body的方法,作为一个集合,它是一个interface。而教师张三李四的各自行为特点又有不同,那么他们就可以扩展自己的行为body。从这点意义上来说,interface偏重于行为。
总之,在许多情况下,接口确实可以代替抽象类,如果你不需要刻意表达属性上的继承 的话。



如下表达式:
A a1 = new A();
它代表A是类,a1是引用
,a1不是对象,new A()才是对象,a1引用指向new A()这个对象。

在JAVA里,“=”不能被看成是一个赋值语句,它不是在把一个对象赋给另外一个对象,它的执行过程实质上是将右边对象的地址传给了左边的引用,使得左边的引用指向了右边的对象。JAVA表面上看起来没有指针,但它的引用其实质就是一个指针,引用里面存放的并不是对象,而是该对象的地址,使得该引用指向了对象。在JAVA里,“=”语句不应该被翻译成赋值语句,因为它所执行的确实不是一个赋值的过程,而是一个传地址的过程,被译成赋值语句会造成很多误解,译得不准确。

 

再如:
A a2;
它代表A是类,a2是引用,a2不是对象,a2所指向的对象为空null;

 

再如:
a2 = a1;
它代表,a2是引用,a1也是引用,a1所指向的对象的地址传给了a2(传址),使得a2和a1指向了同一对象。

综上所述,可以简单的记为,在初始化时,“=”语句左边的是引用,右边new出来的是对象。
在后面的左右都是引用的“=”语句时,左右的引用同时指向了右边引用所指向的对象。

再所谓实例,其实就是对象的同义词。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值