复用类(继承组合)与多态
Java面向对象编程思想的三种基本特征:继承、封装、复合和多态
- 封装:只公开代码的访问接口,隐藏其内部的数据和具体实现;通常采用public,protected,private来进行访问权限控制;封装可以隐藏实现细节,还可以使得代码模块化以对象形式呈现
- 继承、组合与代理之间的区别与联系:组合只是将对象引用置于新类中即可,表述的是一种has-a的关系,而继承通过extends关键字实现,自动得到基类中所有域和方法,表述的是一种is-a的关系;而代理是介于两者之间的一种关系,首先将对象引用置于新类中,然后在新类中构建同名方法来暴露该对象的所有方法(类似于继承);继承可以扩展已存在的代码模块,并且表达出层次逻辑和归属关系
- 多态与向上转型相反,通常通过接口和抽象类来实现(覆盖重写方法),我们把重载(overloading)称为编译时多态,把覆盖(overriding)称为运行时多态;多态可以达到接口重用的目的,深刻地体现了一种设计原则里-依赖倒置原则;程序要依赖超类(抽象类、接口)不要依赖具体类,基于接口的开发,而不是基于实现的开发。
- 运行时多态(也被称作动态绑定)是指允许将子类类型的引用赋值给父类类型的引用,赋值之后,父对象在执行期间(而非编译期间)就可以根据当前赋值给它的子对象的特性以不同的方式运作,根据实际类型判断并调用相应的属性和方法;编译时的多态是指参数列表的不同, 来区分不同的函数, 在编译后, 就自动变成两个不同的函数名.
- 动态绑定的底层实现是指针,我们知道程序中的方法是一段代码,存放在专门的内存区域(代码区),当我们在程序执行期间new 出一个对象后调用其方法的时候,JVM动态的把指针指向实际的对象重写的方法,从而实现了动态绑定.
- 动态绑定的最大好处就是给我们的程序的可扩展性带来了相当大的提高;如果没有动态绑定,我们一般情况下的做法是在子类中用instanceof判断一个对象是不是我们需要的当前具体对象,但当我们定义好多个子类的时候,每次都要判断,现在有了动态绑定,我们不需要再去判断,而是JVM动态给我们指向具体的对象并调用相应的属性和方法.
继承与初始化:导出类继承自基类,构造器调用顺序是从上至下基类到导出类层层调用,若是默认构造器则不需要在导出类中显式调用基类构造器,否则需要首先在导出类构造器中使用super关键字调用基类构造器。见《Java编程思想P146和P158》
接口与内部类
- 接口与抽象类的区别联系:
- 一个类可以实现多个接口但是只能实现一个抽象类
- 接口中都是抽象方法,抽象类中可以有实现的方法
- 接口中方法都是public的,属性都是public static final的;但是抽象类中方法和属性都可以自由定义修饰符
- 接口不能有构造器,抽象类可以有构造器,但是两者都不能实例化
静态内部类(嵌套类)与非静态内部类,方法内部类,匿名内部类区别:
- 嵌套类不需要内部类对象与外围类对象之间有联系,而内部类自动拥有其外围类所有成员的访问权(因为内部类对象拥有一个指向其外围类对象的引用);创建内部类对象之前需要先创建外围类对象,而创建嵌套类对象则不需要
- 嵌套类不能访问外围类的非静态属性,但是内部类可以访问外围类的所有属性和方法(包括private),内部类不能有static变量
- 局部内部类: 该类被创建在外围类的方法或者作用域中,局部内部类的访问仅限于方法内或者该作用域内
匿名内部类: 在java Listener中出现的非常多,必须继承某个接口或者父类,在其中只能重写一个方法;没有修饰符;匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class;多线程的实现也是匿名内部类的一种
scan_bt.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub } }); history_bt.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub } });
- 成员内部类,就是作为外部类的成员,可以直接使用外部类的所有成员和方法,即使是private的。虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问
- 为什么需要内部类?
- 内部类提供了进入和操作外围类的窗口,可以直接调用外部类的private方法和属性,但是外部类却不可以调用内部类的方法和属性
- 内部类完善了“多重继承”机制,他能提供继承多个具体类或抽象类的能力
- 内部类可以更好地封装,它被封装在外部类里,不允许同包其他类访问
- 内部类的继承与覆盖 —- 见《Java编程思想P212》
异常处理与类型信息
- 异常处理是java中唯一正式的错误报告机制,用于解决运行期间遇到的问题;可以用try……catch进行捕获也可以在定义方法或者类时使用throws声明。
- Error类和Exception类区别它们的父类都是throwable类,他们的区别是:
- Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。
- Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。
- Exception类又分为运行时异常(Runtime Exception)和受检查的异常(Checked Exception ),运行时异常;ArithmaticException算术异常,IllegalArgumentException非法参数异常,NullPointerException 空指针异常,编译能通过,但是一运行就终止了,程序不会处理运行时异常,出现这类异常,程序会终止。而受检查的异常,要么用try……catch捕获,要么用throws字句声明抛出,交给它的父类处理,否则编译不会通过,SQLException
- return与finally:
- 在try里边return会先return再执行finally;在catch里边return则先执行finally再return
- 在finally里边return会导致异常丢失,不再抛出异常(另一种异常丢失是因为前一个异常还没处理就抛出下一个异常,前一个异常就丢失了)
Java反射机制
反射:反射机制不但可以让我们在程序运行时获取类的函数、属性、父类、接口等 Class 内部信息,还可以让我们在运行期实例化对象,调用方法,通过调用get/set方法获取变量的值,即使方法或属性是私有的的也可以通过反射的形式调用,可以用于使用在编译器为程序生成代码之后很久才会出现的类,比如基于构建编程和远程方法调用,主要是通过Class类和java.lang.reflect类库来实现
//记载指定路径中的class对象的两个方法 Class.forName(String className); public static Class<?> forName (String className, boolean shouldInitialize, ClassLoader classLoader) //编译器获取类的class对象 Class<?> myObjectClass = MyObject.class; //编译器获取对象的class对象 Student me = new Student("mr.simple"); Class<?> clazz = me.getClass(); // 运行期获取指定路径的 Class 对象 Class<?> clz = Class.forName("org.java.advance.reflect.Student"); // 通过 Class 对象获取 Constructor,Student 的构造函数有一个字符串参数 // 因此这里需要传递参数的类型 ( Student 类见后面的代码 ) Constructor<?> constructor = clz.getConstructor(String.class); // 通过 Constructor 来创建 Student 对象 Object obj = constructor.newInstance("mr.simple"); //获取父类和接口 Class<?> superClass = obj.getClass().getSuperclass(); Class<?>[] interfaceses = obj.getClass().getInterfaces(); System.out.println(" obj : " + obj.toString());
- Class 中的 getDeclaredMethods 函数,它会获取到当前类中的public、default、protected、private 的所有方法; getDeclaredMethod(String name, Class…<?> parameterTypes)则是获取当前类中某个指定的方法
- Class 中的 getMethods 函数用于获取当前类以及父类中的所有 public 方法,而 getMethod(String name, Class…<?> parameterTypes) 则是获取某个指定的public方法
- Class 中的 getDeclaredFields 函数,它会获取到当前类中的public、default、protected、private 的所有属性; getDeclaredField(String name)则是获取当前类中某个指定的属性
- Class 中的 getFields 函数用于获取当前类以及父类中的所有 public 属性,而 getField(String name) 则是获取某个指定的public属性
- RTTI(Run-Time Type Identification):在运行时识别对象的类型;其主要限制是在编译时就必须已知对象类型,也就是知道类名,它的三种应用场景:①类型转换 如:Shape s = (Shape)circle ②Shape.class这种类字面常量 ③instanceof判断某对象是否是某类或其之类的实例
- RTTI和反射之间真正的区别在于:对RTTI来说,编译器在编译时打开和检查.class文件;对于反射机制来说,.class文件在编译时不可获取,只有等到运行时才能打开和检查。
枚举、注解与泛型
- Java Enum用法
- 注解又叫做元数据,一种在代码中添加信息的形式化方法,该信息可以在后期利用反射机制进行使用,我们可以通过编写注解处理器来解析代码中的注解,也可以使用apt来解析。
- 泛型主要是用于容器定义,泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如 List
在运行时仅用一个List来表示。它提供了编译期的类型安全,确保你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException。
字符串与数组
持有对象的容器
java I/O系统与并发
final、finally和finalize对比
- final用于声明属性方法和类,分别表示:属性不可变,方法不可覆盖,类不可继承。可以用于关闭方法动态绑定
- finally是异常处理语句的一部分,表示总是执行。一般用于释放除内存之外的资源(例如已打开的文件或者网络连接)
- finalize是Object的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法。
String、StringBuffer和StringBuilder区别
- 如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。
- String类对象是不可变对象,其在创建之后不能再改变其所拥有的所有private final成员变量;因为String类本身就是final类而且其源码中的char[]数组引用,offset(记录字符串首字母在value数组中对应的下标),count(记录字符串的长度),hash等变量也是final的(JDK1.7中只存在hash和char[]数组),除非使用反射机制去改变char[]引用指向的堆中的数组数据;而String的substring, replace, replaceAll, toLowerCase等函数都是新建一个堆中的String对象然后返回给String的引用,从而使得引用指向了新的String对象,并没有改变原来的String对象
- 字符串常量池:首先需要确认字符串常量池存放的是对象引用,不是对象,字符串常量池存在于方法区中(JDK8移到了堆中),而对象都创建在堆内存中;
- 当代码中出现字面量形式(String str1 = “droid”;)创建字符串对象时,JVM首先会对这个字面量进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回,否则新的字符串对象被创建,然后将这个引用放入字符串常量池,并返回该引用;
- 当我们使用了new来构造字符串对象的时候,不管字符串常量池中有没有相同内容的对象的引用,新的字符串对象都会创建
- 字符串常量池的好处就是减少相同内容字符串的创建,节省内存空间。坏处牺牲了CPU计算时间来换空间。CPU计算时间主要用于在字符串常量池中查找是否有内容相同对象的引用。不过其内部实现为HashTable,所以计算成本较低。 Link
用反射可以反射出String对象中的私有 value属性, 进而改变通过获得的char[]引用去改变数组的结构,例如:
public static void testReflection() throws Exception { //创建字符串"Hello World", 并赋给引用s String s = "Hello World"; System.out.println("s = " + s); //Hello World //获取String类中的value字段 Field valueFieldOfString = String.class.getDeclaredField("value"); //改变value属性的访问权限 valueFieldOfString.setAccessible(true); //获取s对象上的value属性的值 char[] value = (char[]) valueFieldOfString.get(s); //改变value所引用的数组中的第5个字符 value[5] = '_'; System.out.println("s = " + s); //Hello_World }
StringBuilder:非线程安全的可变字符串,单线程时字符串拼接最快
StringBuffer:通过调用其append和insert方法来添加字符;线程安全的可变字符串,因为内部的方法都加了synchronized关键字; String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接
// String +操作快 String S1 = “This is only a” + “ simple” + “test”; //其实就是:String S1 = “This is only a simple test” // StringBuffer.append()更快 String S2 = “This is only a”; String S3 = “ simple”; String S4 = “ test”; String S1 = S2 +S3 + S4;
- JDK1.7中subString函数改变:String类中不再有offset和count变量;这意味着成员变量char[] value将不会被共享;String.substring方法会为每个子串创建一个新的char[] value(而不是共享母串的char[] value)。这意味着String.substring方法的时间复杂度由常数阶变为线性阶。这种变化的好处是String对象占用的内存稍微少了一些(比以前少8个字节),同时确保String.substring方法不会导致内存泄漏
[java中equals,hashcode,==分析]
- == : 对于基本类型的变量(int, char等), 只有==方法用于判断两者是否相等;==也可用于判断两个引用变量是否相等,但是引用变量相等只是说明两个引用变量引用了同一个对象
- equals(): 该方法继承自Object类,它在Object类中是用==实现的,跟==一样默认是比较两个引用是否相等;但是实际应用中它主要用于判断两个String,Integer等对象或者自定义对象在内存中的值是否相等; String等对象重载了equals方法,所以可以用来比较值是否相等;
- hashcode(): 哈希函数的作用是把不定长度的输入通过哈希算法将其压缩成固定长度的输出(int整数), 不同的输入可能转换成相同的输出;其在Object类中的默认实现是返回对象的内存地址的哈希码,不同的对象即使内容相同但引用不同也会返回不同的哈希码.主要用于HashMap等容器中的快速定位, 通过哈希值就可以直接定位到Map中Entry数组的某个位置,然后再循环该位置对应的链表用equals判断
是否都相等。 - set在使用add,remove等函数时会调用equals函数去判断两个自定义对象的地址是否相同,而不是比较两个对象的内容是否相同,需要重载hashcode和equals函数,类似于String类一样重载,才能够实现去重的目的!! HashSet中的元素实际上是作为HashMap中的Key存放在HashMap中的,为了保证HashSet中的对象不会出现重复值,在被存放元素的类中必须要重写hashCode()和equals()这两个方法。
null判断将常量写在前面的好处
- if(val == null) 和 if(null == val)的区别:后一种方式的主要优点是防止将==写成=和将!=写成=,因为后一种方式如果出现漏写,编译器会提示报错便于检查出代码错误;而前一种不会报错,造成逻辑bug不能被编译器检查出来
- ”abc”.equals(val)和val.equals(“abc”)的区别:前一种可以防止当val=null时编译器报空指针错误
java生成随机数的方法(返回1-6的六个随机数)
Random rand = new Random();
system.out.println(rand.nextInt(6) + 1);
java反转字符串方法
String reverse(String s)
{return new StringBuilder(s).reverse().toString();}
函数参数的预防性检测
针对传入参数显式检测,throw相应的Exception;包括参数为null,数值溢出以及为负,数组下标越界
Java常见位操作
// 1. 获得int型最大值
System.out.println((1 << 31) - 1);// 2147483647, 由于优先级关系,括号不可省略
System.out.println(~(1 << 31));// 2147483647
// 2. 获得int型最小值
System.out.println(1 << 31);
System.out.println(1 << -1);
// 3. 获得long类型的最大值
System.out.println(((long)1 << 127) - 1);
// 4. 乘以2运算
System.out.println(x<<1);
// 5. 除以2运算(负奇数的运算不可用)
System.out.println(x>>1);
// 6. 乘以2的m次方
System.out.println(x<<m);
// 7. 除以2的m次方
System.out.println(x>>m);
// 8. 判断一个数的奇偶性
System.out.println((10 & 1) == 1);
System.out.println((9 & 1) == 1);
// 9. 不用临时变量交换两个数(面试常考)
a ^= b;
b ^= a;
a ^= b;
// 10. 取绝对值(某些机器上,效率比n>0 ? n:-n 高)
int n = -1;
System.out.println((n ^ (n >> 31)) - (n >> 31));
/* n>>31 取得n的符号,若n为正数,n>>31等于0,若n为负数,n>>31等于-1
若n为正数 n^0-0数不变,若n为负数n^-1 需要计算n和-1的补码,异或后再取补码,
结果n变号并且绝对值减1,再减去-1就是绝对值 */
// 11. 取两个数的最大值(某些机器上,效率比a>b ? a:b高)
System.out.println(b&((a-b)>>31) | a&(~(a-b)>>31));
// 12. 取两个数的最小值(某些机器上,效率比a>b ? b:a高)
System.out.println(a&((a-b)>>31) | b&(~(a-b)>>31));
// 13. 判断符号是否相同(true 表示 x和y有相同的符号, false表示x,y有相反的符号。)
System.out.println((a ^ b) > 0);
// 14. 计算2的n次方 n > 0
System.out.println(2<<(n-1));
// 15. 判断一个数n是不是2的幂
System.out.println((n & (n - 1)) == 0);
/*如果是2的幂,n一定是100... n-1就是1111....
所以做与运算结果为0*/
// 16. 求两个整数的平均值
System.out.println((a+b) >> 1);
// 17. 从低位到高位,取n的第m位
int m = 2;
System.out.println((n >> (m-1)) & 1);
// 18. 从低位到高位.将n的第m位置为1
System.out.println(n | (1<<(m-1)));
/*将1左移m-1位找到第m位,得到000...1...000
n在和这个数做或运算*/
// 19. 从低位到高位,将n的第m位置为0
System.out.println(n & ~(0<<(m-1)));
/* 将1左移m-1位找到第m位,取反后变成111...0...1111
n再和这个数做与运算*/