11 May 2017

Java知识点大扫盲

在Java的学习过程中,多多少少会遗漏掉一些基本的知识点,而这些基本的知识点往往是奠定Java基础很重要的部分,里面的很多知识点我们编程中往往用不到,但理解它们能帮助我们更好地掌握Java,本文是一个长期贴,会不定时更新。

1、函数的形参到底获取的是实参的值还是实参的引用地址?

public class Main17{     
     String str = "start";
     char ch[] = {'a','b','c'};
     Integer a = 1;
     float f = 1.0f;
     double d = 1.0;     
     public void change(String str,char[] ch){
          str = "end";
          ch[0] = 'e';
     }     
     public void change(Integer a){
          a = 2;
     }     
     public void change(float f){
          f = 2.0f;
     }
     public void change(double d){
          d = 2.0;
     }     
     public static void main(String[] args) {
          Main17 m17 = new Main17();
          m17.change(m17.str, m17.ch);
          System.out.print(m17.str + " and ");
          System.out.println(m17.ch);
          m17.change(m17.a);
          System.out.println(m17.a);
          m17.change(m17.f);
          System.out.println(m17.f);
          m17.change(m17.d);
          System.out.println(m17.d);
     }
}
result:
start and ebc
1
1.0
1.0

我们可以发现String,float,double,这些值changea后都没变,但是char的值改变了,也就是说String,float,double给形参传入的是实参的值,因为这些类都是final类(还有:Integer,Long,Short);而char给形参传入的是实参的引用地址,所以change中改变了char的值会反映到类的全局变量。

2、Java中&&和&以及||和|的区别

&&和&都是表示与,区别是&&只要第一个条件为false,后面条件就不再判断。而&要对所有的条件都进行判断。

但是||和|都是表示“或”,区别是||只要第一个条件为true,后面的条件就不再判断,而|要对所有的条件进行判断。

3、==与equals

String s = "hello";
String t = “hello”;
char c [] = {'h','e','l','l','o'};

s == t //true
s.equals(c)//false

首先==与equals是有明显区别的。

==强调栈中的比较,可以理解为地址比较

equals强调对象的内容比较

String s=“hello”;会在栈中生成hello字符串,并存入字符串常量池中。

String t=“hello” ;创建时,会在字符串常量池中寻找,当找到需要的hello时,不进行字符串的创建,引用已有的。 所以,s==t返回true,s.equals(t)也是true。

char c[]={‘h’,’e’,’l’,’l’,’o’}; c==s这个是不存在的,==两边类型不同,t.equals(c)这个语句在anObject instanceof String这步判断不会通过,也就是cha[] 压根不能与String相比较,类型不是相同的。返回false。

4、静态成员变量或静态代码块>mian方法>非静态成员变量或非静态代码块>构造方法

静态方法或静态块会比构造方法(包括父类的构造函数)先加载,且只加载一次。

class Test{
     public Test(){
          System.out.println("I'm 构造器");
     }
     static{
          System.out.println("I'm static");
     }
}
result:
I'm static
I'm 构造器

5、String的replaceAll方法

String classFile = "com.jd.". replaceAll(".", "/") 
	+ "MyClass.class";
System.out.println(classFile);
result:
/////////MyClass.class

由于replaceAll方法的第一个参数是一个正则表达式,而”.”在正则表达式中表示任何字符,所以会把前面字符串的所有字符都替换成”/”。如果想替换的只是”.”,那么要写成”\.”。

6、try-catch-finally

public static int func (){
    try{
        return 1;
    }catch (Exception e){
        return 2;
    }finally{
        return 3;
    }
}
result:
3

Try catch finally获取异常错误的结构,try中没有异常时,但是有return等跳转语句,这样会引发程序控制流离开当前的try,即在return语句执行之后返回之前去完成finally中资源的释放。 由于代码中的finally中有return语句,将try中的return语句覆盖,程序直接跳出。

7、匿名内部类

匿名内部类也就是没有名字的内部类。

正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写 但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口。

实例1:不使用匿名内部类来实现抽象方法

abstract class Person {
    public abstract void eat();
}
class Child extends Person {
    public void eat() {
        System.out.println("eat something");
    }
}
public class Demo {
    public static void main(String[] args) {
        Person p = new Child();
        p.eat();
    }
}
运行结果:eat something

可以看到,我们用Child继承了Person类,然后实现了Child的一个实例,将其向上转型为Person类的引用。

但是,如果此处的Child类只使用一次,那么将其编写为独立的一个类岂不是很麻烦?

这个时候就引入了匿名内部类。

实例2:匿名内部类的基本实现

abstract class Person {
    public abstract void eat();
}
public class Demo {
    public static void main(String[] args) {
        Person p = new Person() {
            public void eat() {
                System.out.println("eat something");
            }
        };
        p.eat();
    }
}
运行结果:eat something

可以看到,我们直接将抽象类Person中的方法在大括号中实现了。

这样便可以省略一个类的书写。

并且,匿名内部类还能用于接口上。

实例3:在接口上使用匿名内部类

interface Person {
    public void eat();
}
public class Demo {
    public static void main(String[] args) {
        Person p = new Person() {
            public void eat() {
                System.out.println("eat something");
            }
        };
        p.eat();
    }
}
运行结果:eat something

由上面的例子可以看出,只要一个类是抽象的或是一个接口,那么其子类中的方法都可以使用匿名内部类来实现。

最常用的情况就是在多线程的实现上,因为要实现多线程必须继承Thread类或是继承Runnable接口。

实例4:Thread类的匿名内部类实现

public class Demo {
    public static void main(String[] args) {
        Thread t = new Thread() {
            public void run() {
                for (int i = 1; i <= 5; i++) {
                    System.out.print(i + " ");
                }
            }
        };
        t.start();
    }
}
运行结果:1 2 3 4 5

实例5:Runnable接口的匿名内部类实现

public class Demo {
    public static void main(String[] args) {
        Runnable r = new Runnable() {
            public void run() {
                for (int i = 1; i <= 5; i++) {
                    System.out.print(i + " ");
                }
            }
        };
        Thread t = new Thread(r);
        t.start();
    }
}
运行结果:1 2 3 4 5

8、Integer等的比较

Integer i01 = 59;
int i02 = 59;
Integer i03 = Integer.valueOf(59);
Integer i04 = new Integer(59);

i01 == i02//true
i01 == i03//true
i02 == i03//true
i01 == i04//false
i02 == i04//true
i03 == i04//false

Integer.valueOf(59)会从Integer中的私有静态类IntegerCache中获取Integer对象,所以与i01和i02对比都为true,而new Integer(59)是新创建Integer对象,新分配了内存,所以与i01和i03对比都为false。int和Integer(只要值相等,无论new否)比,都为true,因为会把Integer自动拆箱为int再去比,所以i02与i04对比为true

Byte,Short,Integer,Long,Character这5种整型的包装类在对应值小于等于127并且大于等于-128时会使用常量池,因为他们至占用一个字节(-128~127);超出这个范围将会返回一个新的包装类对象。

比如

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];
}

public static Long valueOf(long l) {
    final int offset = 128;
    if (l >= -128 && l <= 127) { // will cache
        return LongCache.cache[(int)l + offset];
    }
    return new Long(l);
}

public static Short valueOf(short s) {
    final int offset = 128;
    int sAsInt = s;
    if (sAsInt >= -128 && sAsInt <= 127) { // must cache
        return ShortCache.cache[sAsInt + offset];
    }
    return new Short(s);
}

所以

public static void main(String[] args) {

    Integer a = 127;
    Integer b = 127;

    Integer c = 128;
    Integer d = 128;

    System.out.println(a == b);//true
    System.out.println(c == d);//false
}

9、Spring的事务传播特性

PROPAGATION_required–支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。

PROPAGATION_supports–支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATION_mandatory–支持当前事务,如果当前没有事务,就抛出异常。

PROPAGATION_requires _new–新建事务,如果当前存在事务,把当前事务挂起。

PROPAGATION_not _supported–以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

PROPAGATION_never–以非事务方式执行,如果当前存在事务,则抛出异常。

10、i++与++i

public class Inc {
    public static void main(String[] args) {
       Inc inc = new Inc();
       int i = 0;
       inc.fermin(i); //查看知识点1
       i= i ++;
       System.out.println(i);  //0,这个结果是不是很意外
    }
    void fermin(int i){
       i++;
    }
}

Java使用了中间缓存变量机制:

i=i++;等同于:

temp=i; (等号右边的i)
i=i+1;      (等号右边的i)
i=temp;   (等号左边的i)

而i=++i;则等同于:

i=i+1;
temp=i;
i=temp;

但如果把“i= i ++; ”改为“i++”,结果会是1。

public class Spike
{
    public static void main(String[] args)
    {
        Counter a = new Counter();
        System.out.println(a.increment());
        System.out.println(a.anotherIncrement());
        Counter b = new Counter();
        System.out.println(b.increment());
    }
}
class Counter
{
    private static int count = 0;
    public int increment()
    {
        return count++;
    }
    public int anotherIncrement()
    {
        return ++count;
    }
}
result:
0 2 2

increment()方法返回当前count值,然后count增加1

antoherIncrement()方法让count增加1,然后返回count值

11、null

public class Main17{
  private static void testMethod(){
        System.out.println("testMethod");
   }
    public static void main(String args[]) {
      ((Main17)null).testMethod();
    }
}
result:
testMethod

Java中,null是一个关键字,不是对象,也不是Objcet的实例,它用来标识一个不确定的对象。因此可以将null赋给引用类型变量,但不可以将null赋给基本类型变量。

比如:int a = null;是错误的。Ojbect o = null是正确的。

12、类方法、类变量和实例方法、实例变量

用static修饰的方法和变量是类方法、类变量,可以直接用类名.的方法来调用;

没有用static修饰的方法和变量就属于实例方法、实例变量,必须实例化类之后用实例化对象才能调用。在类方法中调用实例变量会报错,在实例方法中调用实例变量则没有问题。

由于类方法是属于整个类的,并不属于类的哪个对象,所以类方法的方法体中不能有与类的对象有关的内容。即类方法体有如下限制:

  1. 类方法中不能引用对象变量;
  2. 类方法中不能调用类的对象方法;
  3. 在类方法中不能使用super、this关键字。
  4. 类方法不能被覆盖。

如果违反这些限制,就会导致程序编译错误。

与类方法相比,对象方法几乎没有什么限制:

  1. 对象方法中可以引用对象变量,也可以引用类变量;
  2. 对象方法中可以调用类方法;
  3. 对象方法中可以使用super、this关键字。

13、Collection与Collections的区别

java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。

Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
│ └HashSet

└Queue

java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,因为内部的构造函数被私有化了就像一个工具类,服务于Java的Collection框架。

14、super()与this()

1)调用super()必须写在子类构造方法的第一行,否则编译不通过。每个子类构造方法的第一条语句,都是隐含地调用super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错。

2)super()和this()类似,区别是,super从子类中调用父类的构造方法,this()在同一类内调用其它方法。

3)super()和this()均需放在构造方法内第一行,既然两个都要放在第一行,那么两个就不可能同时存在一个构造函数里。

4)尽管可以用this调用一个构造器,但却不能调用两个。

5)this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。

6)从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。

public class Main17{
    public Main17(){
        super();
        System.out.println("1");
    }
    public Main17(int a){
        this();
        System.out.println(a);
    }
    public Main17(int a, String s){
        this(a);
        System.out.println(s+a);
    }
    public static void main(String[] args) {
        Main17 main17 = new Main17(2,"end:");
    }
}
result:
1
2
end:2

15、Java中的float与double精度

float的精度最多8位,double的精度最多17位。

public static void main(String[] args) {
		
		double a = 1.0;
		double b = 1.0;
		System.out.println(a == b);//这样的代码是拿不到offer的
		double t = 30.000000000000000;
		double c = 30.000000000000003;//double最多17位
		double d = 30.0000000000000003;
		System.out.println(t == c);//false
		System.out.println(t == d);//true
		System.out.println(Double.compare(t, d));//0 数学上相等
		System.out.println(c <= 30);//false
		System.out.println(d <= 30);//true  d总位数超过了17,则会将小数点多余的3去掉即等于30.000000000000000
		
		float e = 4.0000003f;//float最多8位
		float f = 4.00000003f;
		System.out.println(e <= 4);//false
		System.out.println(f <= 4);//true f总位数超过了8,则会将小数点多余的3去掉即等于4.0000000
	}

Tags:
Stats: