十个不可忽视的Java基础知识
写在最前
此系列文章是作者在最近参加了一些笔试面试之后进行的一波小总结,本文为第一篇,将持续定期更新。
无论你认为自己的编程技术在同龄人中多么出类拔萃,自己写过多少高技术含量paper,跟过多少学校的项目。一个你可能从未仔细探索的小问题很可能让你的能力不受信任,相信不少人和我一样笔试时后悔没提前巩固一下基础知识。说多都是泪...
文中的部分例子和定义来自作者十星推荐,每个学java人都看过的《Thinking in Java》,推荐大家看英文原版,会很大程度加深对Java的理解。另外,问题提纲的总结来自作者最近研读的机械工业出版社的《Java程序员面试笔试宝典》。文章主干,分析解释部分全部由作者手打,标明网上资料忘记出处的,希望细心人指点。
1.static的作用
static,顾名思义,静态。当类中的成员变量或方法声明为static时,无需为此类创建参照,便可对其进行操作。即,不依赖该类的实例,同时也被该类的实例所共享。下面通过两种static的使用情况进行分析:
- static变量
同为成员变量,与实例变量每创建一次便为其分配一次内存不同,JVM只为static变量分配一次内存,并在加载类的过程中完成该操作。可用类名直接访问此变量或通过实例调用,前者是被推荐的,因为这样不仅强调了该变量的static属性,而且也在某种程度上使编译器更容易去进行优化。所以在对象之间有共享值或为了方便访问某种变量时一般需要使用static变量。
- static方法
对于static方法,也同时可以通过类名调用或实例调用。因此需要注意的是,static方法中不能用this或super关键字,不能直接访问此方法所在类的实例变量或实例方法,只能访问该类的静态成员变量和方法。因为实例变量和方法有特定的对象,而静态方法占据一个特定的数据区域。
举例:
Class StaticTest{
static int i = 47;
int j = 10;
}
Class Incrementable{
static void increment(){
//通过类名直接对i进行操作
StaticTest.i++;
//此处无法对j进行访问,因为其为实例变量
}
}
2.final的作用
final,在Java中通常解释为不可变的,也就是说,final关键字是用来防止变化。一般我们在两种情况下使用:设计(design)或效率(efficiency)。下面分情况来分析final关键字的作用:
- final数据——声明类中属性或变量
每种编程语言都有一种声明常量的方法,java中便是final。基本类型(Primitive Type)和引用类型(Object Reference Type)的属性在声明final后,里面存放的值都不可再改变。但有所不同的是,在基本类型中,这个值是实在的,比如100,"java";在引用类型中存放的是地址,所以final只是使其地址不可改变,这个地址所指的对象、数组皆是可以改变的。需要注意的是,static final的基本变量命名法,全大写字母,单词之间用"_"(underscore)连接。举例:
public static final int VALUE_ONE = 9;
- final方法
使用final声明方法主要是为了给方法加锁以防止继承的类对其内容进行修改,也就是使其不可重写(override)。因此final方法可以被继承,但不能被重写。
- final类
在前面加上final关键字的类是因为你不希望此类被继承,换句话说,在某种设计情况下你的类永远不需要去作出改变,或者为了安全原因你不希望它有子类。简单举例:
class SmallBrain{}
final class Dinosaur {
int i = 7;
int j = 1;
SmallBrain x = new SmallBrain();
void f(){}
}
//class Further extends Dinosaur{}此句无法执行,因为Dinosaur类为final
public class Jurassic {
Public static void main(String[] args) {
Dinosaur n = new Dinosaur();
n.f();
n.i = 40;
n.j++
}
}
还有一个网上看到的例子,忘记出处了:
final byte bt1 = 1;
final byte bt2 = 2;
byte bt3 = bt1 + bt2;
此例中若没有final便会报错,因为如果去掉final,bt1和bt2在运算时JVM将其自动转换为了int类型变量,最后相当于将一个int类型赋值给了一个byte类型。
3.Overload与Override
为了对比,我们先来看一下两者的英文定义:
- Overriding
Having two methods with the same arguments, but different implementations.
- Overloading
A feature that allows a class to have two or more methods having same name, if their argument lists are different.
不难看出,Overload和Override都是Java多态性(Polymorphism)的体现。其中Overriding(重写)是指父类与子类中,同一方法,相同名称和参数,重新写其内容,相当于“推翻”了父类中的定义。而Overloading(重载)是指在同一类中,定义多个同名方法,但其中的参数类型或次序不同。例子如下:
- Overriding
class Dog {
public void bark(){
System.out.println("woof ");
}
}
class Hound extends Dog{
public void sniff(){
System.out.println("sniff");
}
public void bark(){
System.out.println("bowl ");
}
}
- Overloading
class Dog {
public void bark(){
System.out.println("woof ");
}
//overloading method
public void bark(int num){
for (int i = 0; i < num; i++ ) {
System.out.println("woof ");
}
}
}
4.组合与继承
组合(Composition)与继承(Inheritance)是Java最常用的两种类的复用方法。要了解他们的区别,我们先来看一下英文定义。
- 组合
Achieved by using instance variables that refers to other objects.
- 继承
A mechanism wherein a new class is derived from an existing class.
从字面意思来看,组合是在一个类中使用一个类的引用,通常说明一个类具有某种属性,有"has a"的关系。比如下面的例子,洒水机"has a"水源:
class WaterSource{
private String s;
WaterSource(){
System.out.println("WaterSource()");
s = "Constructed";
}
public String toString() {
return s;
}
}
public class SprinklerSystem {
private String valve1, valve2, valve3, valve4;
private WaterSource source = new WaterSource();
private int i;
private float f;
public String toString() {
return
"valve1 = " + valve1 + " " +
"valve2 = " + valve2 + " " +
"valve3 = " + valve3 + " " +
"valve4 = " + valve4 + "\n" +
"i = " + i + " " + "f = " + f + " " +
"source = " + source;
}
public static void main(String[] args) {
SprinklerSystem sprinklers = new SprinklerSystem();
System.out.println(sprinklers);
}
}
例子中的SprinklerSystem类中创建了WaterSource类的参照,因此此段代码中最先输出的为"WaterSource",这就是最简单的组合。
而继承是一个新类派生于旧类的关系,那么也就是说具有旧类的属性,有"is a"的关系,大家应该都对此非常熟悉,因此在此处不再举例。
不难看出,当某物具有多项属性时使用组合,比如汽车有引擎,车门,轮胎。当某物属于一种某物时使用继承,比如哈士奇是一种狗。
5.clone的作用
clone()是Java中用来复制对象的方法,由于Java取消了指针的概念,很多人对引用和对象的区别有所忽视,全面理解clone()将对此有很大帮助。
需要注意的是,当写出如下代码时
Student st1 = new Student("Daniel");
Student st2 = st1;
System.out.println(st1);
System.out.println(st2);
打印结果(地址)是完全相同的,因为st1与st2为一个对象的两个引用。因此我们在使用"="操作符赋值是,只是进行了引用的复制。而clone()方法,才是真正实现对象复制的途径,与上面的做对比:
Student st1 = new Student("Daniel");
Student st2 = (Student)st1.clone();
System.out.println(st1);
System.out.println(st2);
在上面的代码中,由于clone()返回的是Object对象,所以需要进行向下强制转换为Student。此时打印的结果就是两个不同的地址,真正地完成了对象的复制。
- 深拷贝(Deep Copy)和浅拷贝(Shallow Copy)
这里我们又要再次提到基本数据类型(Primitive Type)和非基础类型(Non-Primitive Type)的区别了,看下面一段代码:
Public class Student {
private int id;
private String name;
Public Student(int id, String name){
this.age = age;
this.name = name;
}
public Student() {}
public int getId() {
return age;
}
public String getName(){
return name;
}
}
其中id是基本数据类型,在拷贝后没有什么问题,然而name是String类型,因此前面提到的方法拷贝过来的只是一个引用值,便是浅拷贝。相对而言,深复制就是再创建一个相同的String对象,将这个对象的饮用赋值给拷贝出的新对象。
要实现深拷贝,我们需要先实现Cloneable接口并重写clone()方法,将上述代码略作修改:
Public class Student implements Cloneable{
private int id;
private String name;
Public Student(int id, String name){
this.age = age;
this.name = name;
}
public Student() {}
public int getId() {
return age;
}
public String getName(){
return name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return (Student)super.clone();
}
}
需要注意的是,在套用多层对象的组合方法中,彻底的深拷贝需要在每一层对象中都实现cloneable接口并重写clone()方法。非常复杂,在实际开发中用处不多,但如前文所题,有助于读者对内存结构有深一步的了解。
6.内部类(Inner Classes)
顾名思义,内部类是指讲一个类定义内置于另一个类定义之中。关于它的作用,《Thinking in Java》里是这样说明的:
The inner class is a valuable feature because it allows you to group classes that logically belong together and to control the visibility of one within the other.
将逻辑相通的类声明为内部类使代码更易控制或处理,在安卓开发中我们会经常见到此类用法。因为每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。——以上黑体摘自《Thinking in Java》,如翻译拗口请见谅。
下面我们来看一个包裹的例子,来简单分析内部类的使用方法。
public class Parcel1 {
class Contents {
private int i = 11;
public value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
public void ship(String dest) {
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel1 p = new Parcel1();
p.ship("Tasmania");
}
}
其中Destination和Contents便是内部类我们可以在非静态(non-static)方法中对其进行调用,当再静态方法如main()中对其进行调用时,需使用“外部类.内部类”的方式对其进行引用。如,将上述代码进行修改。
public class Parcel2 {
class Contents {
private int i = 11;
public value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
public Destination to(String s) {
return new Destination(s);
}
public Contents contents() {
return new Contents();
}
public void ship(String dest) {
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel2 p = new Parcel2();
p.ship("Tasmania");
Parcel2 q = new Parcel2();
Parcel2.Contents c = q.contents();
Parcel2.Destination d = q.to("Borneo")
}
}
在此例子中调用Contents以及Destination对象时,使用了“外部类.内部类”的调用方法。
7.接口与抽象类
在国外论坛上对于接口(Interface)和抽象类(Abstract class)的讨论就一直不断。我们还是先来看一下它们的英文定义。
- 抽象类
Classes that contain one or more abstract methods.
- 接口
It is a collection of abstract methods.
乍一看貌似差不多,都是有关抽象方法的集合,关于它们的区别,仅从定义上来看,有两点不同:
1.接口暗示着全部方法为抽象,且不能有任何的implementation。需要注意的是,这里有一个常考的点就是,既然不能implement,那么接口可以继承吗?答案是可以的。相对于接口,抽象类中可以有执行默认行为的实例方法,也就是可以有implementation。
2.在接口中声明的变量默认为final类型,然而在抽象类中可以有非final类型的变量。
那么在应用上该如何选用这两者呢,首先我们需要知道他们的本质。接口是对动作的抽象,而抽象类则是对根源的抽象。比如人要吃东西,狗也要吃东西,那么我们就可以把吃东西作为一个接口。其中人和狗都属于生物,那么我们就可以将生物定为一个抽象类。需要注意的是,一个类只可以继承一个类(抽象类),但却可以实现多个接口。理解这一点不难,上例中人和狗是生物,但不可能同时是非生物体。
我们来看一个经典的报警门的例子,相信大部分人都见过:
interface Alram {
void alarm();
}
abstract class Door {
void open();
void close();
}
class AlarmDoor extends Door implements Alarm {
void oepn() { }
void close() { }
void alarm() { }
}
此例中,只要是门,固有属性都会有开和关,所以将门设置为抽象类,报警门只是门的一种。然而报警门的特殊功能是报警,因此我们将报警设置为一个接口。在报警门的这个类中,便继承了门,实现了报警接口。
8.stack和heap
关于堆(heap)和栈(stack)的理解不只局限于java,无论是任何语言,对此概念区分理解都是非常重要的。stackoverflow是这么说的:
Stack is used for static memory allocation and Heap for dynamic memory allocation
stack是静态内存,而heap是动态分配。通俗来说,栈就是用来放引用的,当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。而堆是用来放对象和数组的,在Java中,这些对象和数组是由new来创建的,而在堆中分配的内存,将由Java虚拟机的自动垃圾回收器来管理。
关于实例中的关系,只要你能理解上文中提到过的clone()方法,理解起来非常轻松。
9.== 和 equals
作为经常出现在condition里的判断方法,区分==和equals是非常重要的,尤其是在安卓的开发中。关于两者的比较,我们要联合前文提到的堆和栈来理解。" == "是比较两个变量栈中的内容是否相等,而与之相对应的"equals"则是比较堆中的内容是否相等。让我们来举例说明:
public class Test {
public static void main(String[] args) {
String s1 = "Daniel";
String s2 = new String("Daniel");
if (s1 == s2){
System.out.println("s1 == s2");
}else{
System.out.println("s1 != s2");
}
if (s1 .equals(s2)){
System.out.println("s1 equals s2");
}else{
System.out.println("s1 not equals s2");
}
}
}
本例中输出的为s1 != s2以及s1 equals s2。由此不难理解,s1与s2地址不同,但内容相同。
10. 反射机制
反射机制是我们在开发过程中用的最多的,但可能你对他的文字定义并不了解:
Java Reflection makes it possible to inspect classes, interfaces, fields and methods at runtime, without knowing the names of the classes, methods etc. at compile time. It is also possible to instantiate new objects, invoke methods and get/set field values using reflection
也就是说,对于一个类,通过反射机制,我能知道他的属性和方法;对于我创建的一个对象,我能够调用他的属性与方法。这种获得信息以及调用对象是动态(Dynamic)的。我们通过stackoverflow上一个简单的例子来理解:
public class TestReflect {
public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<Integer>();
Method method = list.getClass().getMethod("add", Object.class);
method.invoke(list, "Java reflection");
System.out.println(list.get(0));
}
}
Java中所有的对象都有getClass()方法,用以获取Class对象。不难看出,上述代码在泛型为Integer的ArrayList中存放一个String类型的对象。
需要注意的是,除了getClass()还有两种方式可以获取class对象,一是forName(),另一个则是使用名称.class来代表。最后我将放上一个CSDN的Winiex's Blog中给出的一个例子给读者去进行分析,相信在你成功分析了本例之后,对反射机制的运用将会感觉"Easy as pie"了。
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* <a href="http://lib.csdn.net/base/java" class='replace_word' title="Java 知识库" target='_blank' style='color:#df3434; font-weight:bold;'>Java </a>Reflection Cookbook
*
* @author Michael Lee
* @since 2006-8-23
* @version 0.1a
*/
public class Reflection {
/**
* 得到某个对象的公共属性
*
* @param owner, fieldName
* @return 该属性对象
* @throws Exception
*
*/
public Object getProperty(Object owner, String fieldName) throws Exception {
Class ownerClass = owner.getClass();
Field field = ownerClass.getField(fieldName);
Object property = field.get(owner);
return property;
}
/**
* 得到某类的静态公共属性
*
* @param className 类名
* @param fieldName 属性名
* @return 该属性对象
* @throws Exception
*/
public Object getStaticProperty(String className, String fieldName)
throws Exception {
Class ownerClass = Class.forName(className);
Field field = ownerClass.getField(fieldName);
Object property = field.get(ownerClass);
return property;
}
/**
* 执行某对象方法
*
* @param owner
* 对象
* @param methodName
* 方法名
* @param args
* 参数
* @return 方法返回值
* @throws Exception
*/
public Object invokeMethod(Object owner, String methodName, Object[] args)
throws Exception {
Class ownerClass = owner.getClass();
Class[] argsClass = new Class[args.length];
for (int i = 0, j = args.length; i < j; i++) {
argsClass[i] = args[i].getClass();
}
Method method = ownerClass.getMethod(methodName, argsClass);
return method.invoke(owner, args);
}
/**
* 执行某类的静态方法
*
* @param className
* 类名
* @param methodName
* 方法名
* @param args
* 参数数组
* @return 执行方法返回的结果
* @throws Exception
*/
public Object invokeStaticMethod(String className, String methodName,
Object[] args) throws Exception {
Class ownerClass = Class.forName(className);
Class[] argsClass = new Class[args.length];
for (int i = 0, j = args.length; i < j; i++) {
argsClass[i] = args[i].getClass();
}
Method method = ownerClass.getMethod(methodName, argsClass);
return method.invoke(null, args);
}
/**
* 新建实例
*
* @param className
* 类名
* @param args
* 构造函数的参数
* @return 新建的实例
* @throws Exception
*/
public Object newInstance(String className, Object[] args) throws Exception {
Class newoneClass = Class.forName(className);
Class[] argsClass = new Class[args.length];
for (int i = 0, j = args.length; i < j; i++) {
argsClass[i] = args[i].getClass();
}
Constructor cons = newoneClass.getConstructor(argsClass);
return cons.newInstance(args);
}
/**
* 是不是某个类的实例
* @param obj 实例
* @param cls 类
* @return 如果 obj 是此类的实例,则返回 true
*/
public boolean isInstance(Object obj, Class cls) {
return cls.isInstance(obj);
}
/**
* 得到数组中的某个元素
* @param array 数组
* @param index 索引
* @return 返回指定数组对象中索引组件的值
*/
public Object getByArray(Object array, int index) {
return Array.get(array,index);
}
}
关于反射机制更多的信息在作者的另一篇文章中有详细说明:http://www.jianshu.com/p/381ec446a318
结语
本文到此处告一段落,感谢大家的阅读。我也曾拜读很多“高科技”的文章去学习实现很多拉风的安卓特效,然而基础知识无时无刻都在敲响警钟,我坚信基础知识是解决问题的最强后勤保障。
最后,文章还有很多不尽人意指出,如果有错误和建议还请大家指出,作为新人只希望通过总结能与大家一起提高。本文将持续连载,希望大家多多支持。有疑问的欢迎加我的weibo:LightningDC进行交流。