`
Everyday都不同
  • 浏览: 714172 次
  • 性别: Icon_minigender_1
  • 来自: 宇宙
社区版块
存档分类
最新评论

面试系列(四):Java基础知识

阅读更多

基础也是Java面试里最基本的考查~ 下面就以我遇到的较为常见的点来整理,比较零散,仅供参考。

 

1、switch……case

要注意case之间要用break来分隔,否则将会一直执行下去直到有break的地方:

public static void switchTest(int i) {
		switch(i) {
			case 1:
				System.out.println("============1");
				break;
			case 2:
				System.out.println("============2");
//				break;
			case 3:
				System.out.println("============3");
				break;
			default:
				System.out.println("==========more");
		}
	}

 case 2的时候,break是注释的,此时调用:

for(int i=0; i<5; i++) {
	switchTest(i);
}

 打印结果为:

==========more

============1

============2

============3

============3

==========more

default为所有case都不满足的时候走的逻辑;因为case 2的时候没有break,所以case 2的时候走完2的逻辑也会走case 3的逻辑。而如果把注释的break放开,打印结果将会是:

==========more

============1

============2

============3

==========more

注意:在jdk1.7+后,case里不仅支持整型,也支持字符串。当case判断条件为整型且为变量时,需注意该变量一定是final int的~

 

2、Java异常处理机制

1)异常的分类

Java中用Throwable类代表异常,而Throwable类会派生出Exception和Error两个子类。

Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题;

Exception(异常):是程序本身可以处理的异常。

 

运行时异常:都是RuntimeException类及其子类异常,也称非检查异常,是指在编译期间,不检查这类异常是否捕获。如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

非运行时异常 (编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

 

throw主动抛出异常;throws声明异常(在方法定义上),则调用此方法的地方必须捕获或者继续throws~

 

2)需要注意的点

2.1:如果一段代码用try{} catch(){} 包围起来,就算发生了异常,但catch后面的代码还是会执行;如果在for循环中,try catch把整个for都包起来,那么循环到某次发生异常时,整个循环就会中断;如果把try catch写在for里面包围每次循环的内容,那么本次如果发生异常将不会影响整个循环的继续进行。

 

2.2:try后面可以跟多个catch,最好在所有特殊异常最后写一个顶级的父类异常:Exception必须写在最后,其子类写在前面。当所有子类异常都没有catch到它时,就会进入Exception的分支。

 

2.3:当try catch finally遇上return

原则:任何执行try 或者catch中的return语句之前(无论try或catch里面有没有被return掉),都会执行finally语句,如果finally存在的话。 如果finally中有return语句,那么程序就以finally里的return为准,否则以try或catch里的return为准。

public static void main(String[] args) {
		System.out.println("调用结果:" + test());
		
	}
	
	static int test()
	{
		int x = 1;
		try
		{
			x++;
			System.out.println("x:" + x);
			System.out.println(x/0);
			return x;
		} catch(Exception e) {
			e.printStackTrace();
			return 0;
		}
		finally
		{
			++x;
			System.out.println("x:" + x);
//			return x;
		}
	}

 打印结果为:

x:2

java.lang.ArithmeticException: / by zero

at com.nineclient.Test5.test(Test5.java:17)

at com.nineclient.Test5.main(Test5.java:6)

x:3

调用结果:0

而把finally的注释放开,结果为:

x:2

java.lang.ArithmeticException: / by zero

at com.nineclient.Test5.test(Test5.java:17)

at com.nineclient.Test5.main(Test5.java:6)

x:3

调用结果:3

说明以finally的return为准了。而如果这样:

static int test()
	{
		int x = 1;
		try
		{
			x++;
			System.out.println("x:" + x);
			return x;
		} catch(Exception e) {
			e.printStackTrace();
			return 0;
		}
		finally
		{
			++x;
			System.out.println("x:" + x);
		}
	}

 打印结果为:

x:2

x:3

调用结果:2

说明即时try里已经return了。finally里的代码还是会继续执行的~但是finally里的++x并不会影响最终return的值,虽然x此时变成3了,但是return的时候还是try里第一次x++后的值。

 

 3、String

1)基本

java.lang.String类的实例用于封装一个字符序列,字符串对象为“不变对象”,一旦对字符串进行修改操作,会创建新的对象。Java中允许我们将一个字面量赋值给字符串引用类型变量,处于性能的考虑,Java对字面量产生的字符串进行了缓存,将他们缓存在字符串的常量池中,对于重复出现的字面量赋值,JVM会先查找常量池中是否存在这个字符串,有就直接引用,减少字符串对象的创建,节省内存资源。

String s = new String("abc");//创建了2个对象,内容都是"abc":一个在字符串常量池,一个在堆里。s只是对象的引用,下同
		  String s1 = "abc";//s1指向字符串常量池里内容为"abc"
		  String s2 = new String("abc");//创建了1个对象,在堆里,内容指向堆里的"abc"
		  String s3 = "abc";//s3指向了字符串常量池里内容为"abc",跟s1一样都指向池里的相同内容
		  
		  System.out.println(s == s1);//false
		  System.out.println(s == s2);//false
		  System.out.println(s1 == s2);//false
		  System.out.println(s1 == s3);//true
		  
		  System.out.println();
		  
		  String o = "o";
		  String k = "k";
		  String ok = "ok";
		  System.out.println(ok == "o" + k);//false, "o"+k会在堆里新生成一个对象
		  System.out.println(ok == "o" + "k");//true, 都是在常量池里,相加也是返回池里的"ok"
                  System.out.println(ok == o + k); //false 

 2)String的封装类

StringBuilder和StringBuffer都是String的封装类,对于字符串的操作建议用这个

StringUtils.countMatch(String str, String subStr);//统计subStr在str里出现的次数

StringBuilder:线程非安全的

StringBuffer:线程安全的

		  String test = "Today" + "is" + "sunny";
		  String test2 = "Todayissunny";
		  String t1 = "Today";
		  String t2 = "is";
		  String t3 = "sunny";
		  System.out.println(test == test2);//true
		  System.out.println(test == (t1+t2+t3));//false

 

 4、基本类型的包装类

数值型:整型—— byte(8位)<short(16位)<int(32位)<long(64位)(按所占字节数从小到大排列,数值范围-2n~2n-1 n为幂) 1字节=8位

浮点型:float(32位)<double(64位) 定义float类型时,如果值是非整数,则需加f后缀。可以将float类型的值赋给double型的变量,反之则不行~

文本型:char——字符常量为单引号括起来的单个字符

布尔型:boolean——true/false

public static void main(String[] args) {
		Integer a = 1;
		int b = 1;
		Integer c = 1;
		Integer d1 = new Integer(1);
		System.out.println(a == b);//true, a会自动"拆箱"变成int
		System.out.println(a ==c);//true
		System.out.println(a ==d1);//false,因为d1指向的是通过new出来的值
		
		//需注意:在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,
		//便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象
		//所有整型都同理(Byte,Short,Long),有自己的cache范围~
		Integer a1 = 200;
		Integer b1 = 200;
		System.out.println(a1 == b1);//false
		System.out.println();
		
		//在某个范围内的整型数值的个数是有限的,而浮点数却不是。所以每次都会创建一个新的Float对象
		//所有浮点类型都同理(Double)
		Float f = 1.0f;
		float f1 = 1.0f;
		Float f2 = 1.0f;
		double d = 1.0;
		System.out.println(f == f1);//true
		System.out.println(f == f2);//false,两个对象 都是Float
		System.out.println(f1 ==d);//true,double自动转型成float
		System.out.println();
		
		Boolean bool1 = true;
		Boolean bool2  = true;
		System.out.println(bool1 == bool2);//true
}

 double/float在计算时会有舍入误差,想要得到更加精确的结果,可以使用java.math.Bidecimal

add(), subtract(), multiply(), divide()

 

5、集合

1)接口

Java的集合框架用Collection顶级接口来表示,该接口又派生出List和Set两个接口。

List:规定了子类实现的特征为有序且元素可重复,常用实现类有ArrayList和LinkedList。ArrayList使用数组实现,所以更适合读取存储的数据;LinkedList使用链表实现,所以更适合插入、删除元素。

Set:规定了子类实现的特征为无序且元素不可重复。常用实现类有HashSet和TreeSet。HashSet使用散列算法实现的Set集合,而TreeSet则使用二叉树算法实现。

2)集合转为数组

		Set<String> set = new HashSet<String>();
		set.add("3");
		set.add("1");
		set.add("1");
		set.add("2");
		//集合转为数组,list和set都有这个语法
		String[] arr = (String[]) set.toArray(new String[0]);
		System.out.println(Arrays.toString(arr));//[3, 2, 1]

 数组转为集合:Arrays.asList(数组);

3)排序

Collections.sort(list);//一般用于数值排序

Collections.sort(Collection c, Comparator cc);//new Comparator 则必须实现方法compare(T o1, T o2)

4)Map

key不能为空且不能重复。

containsKey(Object key)——查看当前Map中是否包含给定的key

containsValue(Object value)——查看当前Map中是否包含给定的value

 遍历Map的3种方式:遍历所有key,遍历所有键值对Entry,遍历所有value

		//遍历key
		for(String key : map.keySet()) {
			System.out.println("key:" + key + ", value:" + map.get(key));
		}
		//遍历键值对
		for(Entry<String, String> entry : map.entrySet()) {
			System.out.println("key:" + entry.getKey() + ", value:" + entry.getValue());
		}
		//遍历value
		for(String value : map.values()) {
			System.out.println("value:" + value);
		}

 

6、数组

主要是注意一些数组的语法~

		int[] a = {1,2,3,4,5,6};
		int[] b = {4,5,6,7,8};
		int[] c = {11,9,10,13,7};
		//打印
		System.out.println(Arrays.toString(a));
		//排序
		Arrays.sort(c);
		System.out.println(Arrays.toString(c));
		//复制
		int[] a2 = a;
		a2[1]++;
		System.out.println("a:" + Arrays.toString(a) + ", a2:" + Arrays.toString(a2));//相互影响
		int[] d = new int[a.length];
		System.arraycopy(a, 0, d, 0, a.length);//src, srcPos, dest, destPos, length
		System.out.println(Arrays.toString(d));
		int[] d2 = Arrays.copyOf(a, a.length);
		System.out.println(Arrays.toString(d2));
		
		//扩容
		int[] e = new int[a.length + b.length];
		System.arraycopy(a, 0, e, 0, a.length);
		System.arraycopy(b, 0, e, a.length, b.length);
		System.out.println("e:" + Arrays.toString(e));

 还有二维数组等~

 

7、面向对象

几大特性:封装、继承、多态

1)接口和抽象类的区别(选择答出几点即可):

抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。

 

抽象类要被子类继承,接口要被类实现。

 

接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现

 

接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。

 

抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。

 

抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结果

 

抽象类里可以没有抽象方法

 

如果一个类里有抽象方法,那么这个类只能是抽象类

 

抽象方法要被实现,所以不能是静态的,也不能是私有的。

 

2)重载和重写的区别

重写,是指子类重新定义父类的虚函数的做法。它是覆盖了一个方法并且对其重写,以求达到不同的作用。重写要注意以下的几点: 

1、重写的方法的标志必须要和被重写的方法的标志完全匹配,才能达到覆盖的效果; 

2、重写的方法的返回值类型必须和被重写的方法的返回一致; 

3、重写的方法所抛出的异常必须和被重写方法的所抛出的异常一致,或者是其子类; 

4、被重写的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。 

 

 

重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。它是指我们可以定义一些名称相同的方法,通过定义不同的输入参数来区分这些方法,然后再调用时,在使用重载要注意以下的几点: 

1、在使用重载时只能通过不同的参数样式。例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方法内的几个参数类型必须不一样,例如可以是fun(int,float),但是不能为fun(int,int)); 

2、不能通过访问权限、返回类型、抛出的异常进行重载; 

3、方法的异常类型和数目不会对重载造成影响; 

4、对于继承来说,如果某一方法在父类中是访问权限是priavte,那么就不能在子类对其进行重载,如果定义的话,也只是定义了一个新方法,而不会达到重载的效果。

 

3)访问修饰符范围

访问级别

访问控制修饰符

同类

同包

子类

不同的包

公开

public

受保护

protected

--

默认

没有访问控制修饰符

--

--

私有

private

--

--

--

 

 

8.final

final修饰的方法不可以被重写(但可以重载哈~),final修饰的类不可以有子类。

当final修饰基本变量类型时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变 。

当final修饰引用类型变量时,final只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但是这个对象(对象的非final成员变量的值可以改变)完全可以发生改变。

final修饰的类成员,程序员最好显示地指定其初始值;使用final修饰局部变量时,既可以在定义时指定默认值,也可以不指定默认值。但是只能初始化一次。

(注:全局变量,即成员变量会被默认赋值;而局部变量的值必须由程序员显式初始化)

 

9、常见设计模式

1)工厂模式

这里直接从拓展性最好的抽象工厂模式说起~

public interface Sender {  
    public void Send();  
}  

//两个实现类:
public class MailSender implements Sender {  
    @Override  
    public void Send() {  
        System.out.println("this is mailsender!");  
    }  
}  

public class SmsSender implements Sender {  
  
    @Override  
    public void Send() {  
        System.out.println("this is sms sender!");  
    }  
}  

//两个工厂类:


public class SendMailFactory implements Provider {  
      
    @Override  
    public Sender produce(){  
        return new MailSender();  
    }  
}  

public class SendSmsFactory implements Provider{  
  
    @Override  
    public Sender produce() {  
        return new SmsSender();  
    }  
}  
//再提供一个接口:


public interface Provider {  
    public Sender produce();  
} 

 测试类:

public class Test {  
  
    public static void main(String[] args) {  
        Provider provider = new SendMailFactory();  
        Sender sender = provider.produce();  
        sender.Send();  
    }  
} 

 2)单例模式

超常见,这里以线程安全的单例模式为例:

public class Singleton {  
  
    private static Singleton instance = null;  
  
    private Singleton() {  
    }  
  
    private static synchronized void syncInit() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
    }  
  
    public static Singleton getInstance() {  
        if (instance == null) {  
            syncInit();  
        }  
        return instance;  
    }  
} 

 3)代理模式

 代理:类似专业的中介,代替我们去做某些事。

 

public interface Sourceable {  
    public void method();  
}  

public class Source implements Sourceable {  
  
    @Override  
    public void method() {  
        System.out.println("the original method!");  
    }  
}  

public class Proxy implements Sourceable {  
  
    private Source source;  
    public Proxy(){  
        super();  
        this.source = new Source();  
    }  
    @Override  
    public void method() {  
        before();  
        source.method();  
        atfer();  
    }  
    private void atfer() {  
        System.out.println("after proxy!");  
    }  
    private void before() {  
        System.out.println("before proxy!");  
    }  
} 

 测试类:

public class ProxyTest {  
  
    public static void main(String[] args) {  
        Sourceable source = new Proxy();  
        source.method();  
    }  
  
}  

 结果:

before proxy!
the original method!
after proxy!

代理模式的应用场景:

如果已有的方法在使用的时候需要对原有的方法进行改进,此时有两种办法:

1、修改原有的方法来适应。这样违反了“对扩展开放,对修改关闭”的原则。

2、就是采用一个代理类调用原有的方法,且对产生的结果进行控制。这种方法就是代理模式。

使用代理模式,可以将功能划分的更加清晰,有助于后期维护!

 

4)桥接模式

把事物和其具体实现分开,使他们可以各自独立的变化。桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化,像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。

 

public interface Sourceable {  
    public void method();  
}  
//分别定义两个实现类:


public class SourceSub1 implements Sourceable {  
  
    @Override  
    public void method() {  
        System.out.println("this is the first sub!");  
    }  
}  

public class SourceSub2 implements Sourceable {  
  
    @Override  
    public void method() {  
        System.out.println("this is the second sub!");  
    }  
}  
//定义一个桥,持有Sourceable的一个实例:


public abstract class Bridge {  
    private Sourceable source;  
  
    public void method(){  
        source.method();  
    }  
      
    public Sourceable getSource() {  
        return source;  
    }  
  
    public void setSource(Sourceable source) {  
        this.source = source;  
    }  
}  

public class MyBridge extends Bridge {  
    public void method(){  
        getSource().method();  
    }  
}  

 测试类:

 

public class BridgeTest {  
      
    public static void main(String[] args) {  
          
        Bridge bridge = new MyBridge();  
          
        /*调用第一个对象*/  
        Sourceable source1 = new SourceSub1();  
        bridge.setSource(source1);  
        bridge.method();  
          
        /*调用第二个对象*/  
        Sourceable source2 = new SourceSub2();  
        bridge.setSource(source2);  
        bridge.method();  
    }  
}  

 打印结果:

this is the first sub!
this is the second sub!

 

5)享元模式(参考数据库连接池)

 享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用

 

6)策略模式

策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。

策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可。

public interface MemberStrategy {
    /**
     * 计算图书的价格
     * @param booksPrice    图书的原价
     * @return    计算出打折后的价格
     */
    public double calcPrice(double booksPrice);
}

  //初级会员折扣类


public class PrimaryMemberStrategy implements MemberStrategy {

    @Override
    public double calcPrice(double booksPrice) {
        
        System.out.println("对于初级会员的没有折扣");
        return booksPrice;
    }

}

  //中级会员折扣类


public class IntermediateMemberStrategy implements MemberStrategy {

    @Override
    public double calcPrice(double booksPrice) {

        System.out.println("对于中级会员的折扣为10%");
        return booksPrice * 0.9;
    }

}

  //高级会员折扣类


public class AdvancedMemberStrategy implements MemberStrategy {

    @Override
    public double calcPrice(double booksPrice) {
        
        System.out.println("对于高级会员的折扣为20%");
        return booksPrice * 0.8;
    }
}

   //价格类


public class Price {
    //持有一个具体的策略对象
    private MemberStrategy strategy;
    /**
     * 构造函数,传入一个具体的策略对象
     * @param strategy    具体的策略对象
     */
    public Price(MemberStrategy strategy){
        this.strategy = strategy;
    }
    
    /**
     * 计算图书的价格
     * @param booksPrice    图书的原价
     * @return    计算出打折后的价格
     */
    public double quote(double booksPrice){
        return this.strategy.calcPrice(booksPrice);
    }
}

 测试类:

public class StrategyTest{

    public static void main(String[] args) {
        //选择并创建需要使用的策略对象
        MemberStrategy strategy = new AdvancedMemberStrategy();
        //创建环境
        Price price = new Price(strategy);
        //计算价格
        double quote = price.quote(300);
        System.out.println("图书的最终价格为:" + quote);
    }

}

 打印结果:

对于高级会员的折扣为20%

 

图书的最终价格为:240.0

 

………………

策略模式基本上答了这些就够了~

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics