您的位置:首页 > 编程语言 > Java开发

java注意事项

2015-08-07 19:58 661 查看
注:有包名的类不能调用无包名的类。

基础部分,数据类型方面:

1:在定义Long或者Float类型变量的时候,要加L或者f。

  整数默认是int类型,浮点数默认是double。

  byte,short在定义的时候,他们接收的其实是一个int类型的值。

  这个是自己做了一个数据检测的,如果不再它们的范围内,就报错。

 

2:byte值的问题

      byte b1 = 127;

   byte b5=128; //编译不通过

      byte b2 = (byte)128; //-128

      byte b3 = (byte)129; //-127

      byte b4 = (byte)130; //-126

      byte的范围:-128 ~ 127

      也就是说byte表示的类型数是从-128到127之间的,如果超过了范围,比如定义一个byte类型的数位128,那么那么它就会从头开始表示,即-128,byte
b3 = (byte)129  b3就是-127了。

      128:10000000

      -128:10000000 (这里的1即是符号位,也是数值位)

     

3:数据类型转换之默认转换

      byte,short,char -- >int -- >long -- >float -- > double

     

      long: 8个字节

      float:4个字节

那么问题来了,8字节的long类型转为4字节的float类型,怎么存储呢?

      A:它们底层的存储结构不同。(所有的整数都是按照101010...这样存储的,而所有的浮点数存时用一种科学计数法存储,它存时存有效数字位以及字幂,有效数字位也是按二进制存储的)

      B:float表示的数据范围比long的范围要大

            long:2^63-1

            float:3.4*10^38 > 2*10^38 > 2*8^38 = 2*2^3^38 = 2*2^114 > 2^63-1

 

4:Java语言中的字符char可以存储一个中文汉字吗?为什么呢?

      可以。因为java语言中的字符占用两个字节。

     

      Java语言采用的是Unicode编码。

 

5.面试题:

            short s=1;s = s+1;

            short s=1;s+=1;

            上面两个代码有没有问题,如果有,那里有问题。

           

答:第一个有问题,第二个没有,那么为什么第二个木有问题呢?

                  扩展的赋值运算符其实隐含了一个强制类型转换。

                  s += 1;

                  不是等价于 s = s + 1;

                  而是等价于 s = (s的数据类型)(s + 1);

 

6.交换a和b值面试题:

方法一(位异或法):

int  a=10;  int  b=20;  用位异或实现a和b的交换

a = a ^ b;

b = a ^ b;

a = a ^ b;

这样a和b就完成交换了,即可以简单记忆为:左边a,b,a;右边a^b。

方法二(变量相加法):

a = a+b;

b = a - b;

a = a - b;

这样a和b就完成交换了。

方法三(一句话搞定):

b = (a+b) - (a=b)

这样a和b就完成交换了。

 

7.下面式子哪个不能编译:

   int [] a1=new int [3];

   int [] a2=new int [4];

   int [][] a3=new int [2][3];

   String[] a4=new String[4];

 

(1).Object obj1=a1;

(2).Object obj2=a4;

(3).Object[]  obj3=a1;    

(4)Object[] obj4=a3;

(5)Object [] obj5=a4;

 

答:第三个不能编译,因为第三个是一维数组,数组里存放的是基本类型,不能把基本类型转化为Object类型。其他的都可以,第四个要注意,Object数组里存放的是一个int类型的一维数组,而一维数组是引用类型,可以转为Object。

 

8.面试题:

              byte b1=3,b2=4,b;

              b=b1+b2;

              b=3+4;

              哪句是编译失败的呢?为什么呢?

       答:b = b1 + b2;是有问题的。

              因为变量相加,会首先看类型问题,最终把结果赋值的也会考虑类型问题。

              常量相加,首先做加法,然后看结果是否在赋值的数据类型范围内,如果不是,才报错。

 

9.面试题:请用最有效率的方式写出计算2乘以8的结果?2 * 8

   答:2 << 3

 

 

 

 

 

内部类方面:

1.成员内部类面试题:要求:请填空分别输出30,20,10

     
注意:

     
      1:内部类和外部类没有继承关系。

     
      2:通过外部类名限定this对象

     
            Outer.this

  class Outer {

     
public int num = 10;

     
class Inner {

     
      public int num = 20;

     
      public void show() {

     
            int num = 30;

     
            System.out.println(?); 
 num

     
            System.out.println(??); 
this.num

     
            //System.out.println(new Outer().num);

     
            System.out.println(???);  
Outer.this.num

     
      }

     
}

}

class InnerClassTest {

     
public static void main(String[] args) {

     
      Outer.Inner oi = new Outer().new Inner();

     
      oi.show();

     
}   

}

2.匿名内部类面试题:

要求:    
按照要求,补齐代码

     
            interface Inter { void show(); }

     
            class Outer { //补齐代码 }

     
            class OuterDemo {

     
                  public static void main(String[] args) {

     
                          Outer.method().show();

     
                    }

     
            }

     
            要求在控制台输出”HelloWorld”

 

interface Inter {

     
void show();

     
//public abstract

}

 

class Outer {

     
//补齐代码

     
public static Inter method() {

     
      //子类对象 --
子类匿名对象

     
      return new Inter() {

     
            public void show() {

     
                  System.out.println("HelloWorld");

     
            }

     
      };

     
}

}

 

class OuterDemo {

     
public static void main(String[] args) {

     
      Outer.method().show();

     
      /* 分析:

     
            1:Outer.method()可以看出method()应该是Outer中的一个静态方法。

     
            2:Outer.method().show()可以看出method()方法的返回值是一个Inter对象。

     
                  又由于接口Inter中有一个show()方法,所以我认为method()方法的返回值类型是一个接口。

     
      */

     
}

}

 

集合方面:

1.集合遍历时需要注意:防止出现NoSuchElementException;

例如以下代码:

// 创建集合对象

            Collection c = new ArrayList();

            // 创建学生对象

            Student s1 = new Student("林青霞", 27);

            Student s2 = new Student("风清扬", 30);

            Student s3 = new Student("令狐冲", 33);

            Student s4 = new Student("武鑫", 25);

            Student s5 = new Student("刘晓曲", 22);

            // 把学生添加到集合中

            c.add(s1);

            c.add(s2);

            c.add(s3);

            c.add(s4);

            c.add(s5);

// 遍历

            Iterator it = c.iterator();

            while (it.hasNext()) {

                  Student s = (Student) it.next();

                  System.out.println(s.getName() + "---" + s.getAge());

                 
// System.out.println(((Student) it.next()).getName() + "---"

     
            // + ((Student) it.next()).getAge());

        // NoSuchElementException
不要多次使用it.next()方法

            }

如上红色代码处将会出现异常(NoSuchElementException),因为

It.next()方法会自动取下一个元素,也就是说它是取了第一个人的名字,而接着取了第二个人的年龄(不是第一个人的,因为当取完第一个人的名字时,((Student) it.next()).getAge()方法使它跳到第二个人上去了)

 

2.将上面while(){}部分代码替换成for()循环。

源码:

Iterator it = c.iterator();

            while (it.hasNext()) {

                  Student s = (Student) it.next();

                  System.out.println(s.getName() + "---" + s.getAge());

}

改写:

for(Iterator it = c.iterator();it.hasNext();){

             Student s = (Student) it.next();

             System.out.println(s.getName() + "---" + s.getAge());

             }

For循环改写后的优点是for代码执行完后对象Iterator it
就销毁了(for代码出栈,内容销毁)

 

 

泛型方面:

1.List<String> listArray = new ArrayList<>();
有没有办法向listArray集合中放入integer类型或其他类型的数据?

 答:如果按正常的add()方法添加肯定是不行的,编译器不通过。但 
是利用反射是可以的。例如:

listArray.getClass().getMethod("add",Object.class).invoke(listArray,
23);


这样子是通过对象得到Class文件,进而得到该类的方法,调用该方法。反射越过编译器的检查,就可以把其他类型放入listArray集合中了。泛型是给编译器看的,运行期不存在泛型,这叫做泛型的擦除。因此可以利用反射越过编译器。

2.下面代码会不会报错?

 
Vector v1=new Vector<String>();    
(1)

 
Vector<Object> v=v1;                 
(2)

答:编译器不会报错,第一条把参数化类型给原子类型不会报错,第二句把原子类型给参数化类型也不会报错。我们不能把两句连起来看,因为编译器一句一句的执行,是一个严格按语法检查的工具,不考虑运行时的效果。

3.注:数组不能和泛型结合。

4.问题:定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法如何定义?

答:

 
错误方式:

publicstaticvoid printCollection(Collection<Object> cols){
    
    

    
     for(Object obj:cols){

    
          System.out.println(obj);

    
     }

    
     cols.add("abc");//不会报错        

    
     cols=new HashSet<Date>();//编译报错    
② 

    
}   


 正确方式:

   publicstaticvoid printCollection(Collection<?> cols){
    
    

    
     for(Object obj:cols){

    
          System.out.println(obj);

    
     }

    
     cols.add("abc");//报错        

    
     cols.size();//不会报错,这个方法与参数类型没有关系   

    
     cols=new HashSet<Date>();//  
③ 

    
}


分析以上情况:

在错误方式下,①不会报错,Collection<Object>
中的Object只是说明Collection<Object>实例对象中的方法接受的参数是Object,String对象也是Object的,因此不会报错。②编译报错Collection<Object>是一种具体类型,new
HashSet<Date>也是一种具体类型,两者不兼容,因此编译会报错。

在正确方式下:①报错,因为Collection<?> cols可以与任意参数类型匹配,到底匹配的是什么类型,只有以后才知道,而①方式添加一个String对象,就会报错,因为它不知道自己未来匹配的就一定是String。

②不会报错,因为这个方法与参数类型没有关系。③不会报错,因为Collection<?>可以接受任意类型,那么Date类型也可以。

总结:使用?通配符可以引用其他各种参数类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。

5.java的泛型:java的泛型类型(或者泛型)类似于C++的模板。但这种相似性仅限于表面,java语言的泛型基本上完全是在编译器中实现的,用于编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码,这种技术叫做擦除(编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除),这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为java厂商升级其jvm造成障碍,所以java的泛型采用了可以完全在编译器中实现的擦除。所以List<Integer>和List<String>得到的字节码是一份字节码。

6.只有引用类型才能作为泛型方法的实际参数。

7.除了在应用泛型时可以使用extends限定符,在自定义泛型时也可以使用extends限定符,例如:Class.getAnnotation()的定义。并且可以使用&来指定多个边界,如<V
extends Serializable & cloneable> void method(){}。

8.普通方法、构造方法、静态方法中都可以使用泛型。

9.也可以用类型变量表示异常,称为参数化异常,可以用于方法的throws列表中,但是不能用于catch子句中。

  private static <T extends Exception> sayHello() throws T{

try{

}catch(Exception e){

Throw (T)e;

   }

 }

10.在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如: public static <k,v> V getValue(k key){return map.get(key);}

11.类型参数的类型推断:

  根据调用泛型方法时实际传递的参数类型或返回值类型判断,具体规则如下:

 (1)当某个类变量只在整个参数列表中的所有参数和返回值中的一处被调用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭借着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:

  swap(new String[3],3,4) —> static <E> void  swap(E[] a,int i, int j)

(2) 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多出的实际应用类型都对应同一种类型来确定,这很容易凭借感觉推断出来,例如:

 add(3,5) —> static <T> T add(T a,T b)

(3) 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多出的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出了问题:

  fill(new Integer[3],3.5f) —> static <T> void fill(T [] a,T v)

(4) 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,并且使用返回值,这时优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:

 int x=(3,3.5f) —> static <T> T add(T a,T b);

(5) 参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译出现问题:

copy(new Integer[5],new String[5])—> static <T> void copy(T[] a,T[] b)

copy(new Vector<String>(),new Integer[5])
—> static <T> void copy(Collection <T> a,T[] b);

12.在泛型类中,当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。例如:①编译出错,②可以(因为声明为泛型方法)

13.下面代码是重载吗?

   public  void  test(List<String> list ){}

   public  void  test(List<Integer> list ){}

 答:不是重载,这两个方法同时存在编译器会报错,因为编译后泛型擦除,去掉类型化,参数就是一样的了。

 

14.利用反射获得泛型的实际参数类型方法。

   答:可以用如下方法: 

 

得到的结果:

分析上面代码:第①句是利用反射得到该类的字节码文件,进而得到方法。第②句是得到泛型类型的参数,第③句转换为具体的类型,第④句是得到实际参数类型。

很多框架利用这样的代码生成具体参数类型的对象。

 

 

反射、类加载方面

1.注:Class文件里面的东西不是字节码,只有用类加载器把Class文件加载进内存后,类加载器对文件进行处理(安全检查等等)后,最终在内存中得到的二进制的文件才是字节码。

 

2.注解的生命周期有三个阶段:java源文件、class文件、内存中的字节码。Javac把源文件翻译成class文件时有可能去掉注解,类加载器把class文件加载进内存是也有可能去掉注解。所以一搬可以加上@ReteionPolicy来声明注解存在哪个阶段,@ReteionPolicy的三种取值:ReteionPolicy.SOURCE,ReteionPolicy.CLASS,ReteionPolicy.SUNTIME,默认值在CLASS阶段。
 

类加载器方面:

1.类加载器也是java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap。这个类是用C++写的。

2.查看类加载器的名称。

输出结果:

3.查看类加载器的继承关系:

输出结果:

从结果可以看出AppClassLoader类加载器继承ExtClassLoader类加载器,而null表示BootStrap类加载器,因为它不是用java写的,所以用java的getName()方法当然得不到该类加载器了。

4.类加载器之间的关系以及管辖范围:

5.类加载器的委托机制:

 (1)当java虚拟机要加载一个类时,到底派哪个类加载器去加载呢?

 答:首先当前线程的类加载器去加载线程中的第一个类。

     如果类A中引用了类B,java虚拟机使用加载类A的类加载器来加载类B。

     还可以调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

 

6.每个ClassLoader本身只能分别加载特定位置和目录的类,但它们可以委托其他类加载器去加载类,这就是类加载器的委托模式。类加载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类加载器去进行真正的加载。当回到最初的类加载器时,如果它自己也不能完成类的加载,那就应该报告ClassNotFoundException异常。

 

7.有一个面试题:能不能自己写一个类叫java.lang.System?

 答:通常情况下不可以,类加载器的委托机制保证了先使用父类的类加载器,父类的类加载器先加载了java.lang.System类了,也就是说,为了不让我们写System类,类加载器采用委托机制,这样可以保证爸爸优先,也就是总是使用爸爸们能找到的类,这样总是使用java系统提供的System类。但如果我们自己写类加载器是可以加载自己的System类的。

 

 代理方面:

1.让java虚拟机创建动态类以及实例对象,需要给它提供哪些信息?

答:三个方面:

u      生成的类有哪些方法,通过让其实现哪些接口的方式进行告知。

u     产生的类字节码必须有一个关联的类加载器对象。

u     生成的类中方法的代码是怎样的,也由我们提供。把我们的代码写在一个约定好的接口对象的方法中,把对象传给它,它调用我的方法,即相当于它插入我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加以点代码,就可以看到这些代码被调用运行了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: