继承 Inheritance
继承就是服用已存在的类,继承原有类的方法和域。并可以加一些新的方法或者覆盖原有的方法。
子类与超类
Java中类继承是单亲模型,即类只能继承一个类。
理论上加Manager与Employee之间是is-a的关系,即每一个经理都是一个员工。
class Manager extends Employee{
}
java使用extends关键字来表示继承关系。已存在的类被称为超类,基类或父类,新类称之为子类,派生类或孩子类。Java程序员一般使用超类和子类术语表达这种关系。
覆盖方法 override
子类覆盖父类的方法,例如
public class Manager extends Employee
{
public double getSalary()
{
}
}
子类不能直接访问父类中私有域和方法,访问父类中的方法要使用super关键字
@Override
public double getSalary() {
return super.getSalary() ;
}
子类构造器
可以调用super()来调用父类构造器,super初始化必须是子类构造器的第一条语句。
public Manager(String name, double salary, int year, int month, int dayOfMonth) {
super(name, salary, year, month, dayOfMonth);
}
多态 polymorphism
继承是is-a的关系,每一个Manager都是Employee的子类,所以程序中任何出现超类的地方都可以用子类置换。
Employee e = null;
e = new Employee();
e = new Manager();
我们称之为对象对变是多态的,即一个Employee变量既可以引用一个Employee对象,也可以引用一个Employee类的任何一个子类的对象。
当子类覆盖了父类方法时,会调用子类的覆盖后的方法。
方法调用
编译器查看对象的声明类型与方法名,假设调用
x.f(param),且隐式参数x声明为C类的对象,如果有多个方法名为f的方法,编译器会一一列举C类及其超类中访问属性为public且名为f的方法。(私有方法不可访问) 编译器获得了所有可能调用的候选方法。编译器查看调用方法的参数类型,在所有方法
f中寻找一个参数类型完全匹配的方法,这个过程称之为重载解析overloading例如调用x.f("Hello"),编译器会调用f(String)而不是f(int)由于可以类型转换int=>double,Manager=>Employee,这个过程会很复杂。子类覆盖父类方法时,允许返回类型为原类型的子类型。
例如父类有
public Employee getBuddy(){ . . . }子类可以覆盖为
public Manager getBuddy(){}如果是
private``static``final``构造器方法,编译器可以准确的知道调用哪个方法,这种方式成为静态绑定static binding.于此对应,调用方法依赖隐式参数的实际类型,并在运行时动态绑定。当程序运行,并且采用动态绑定调用方法时,虚拟机一定会调用与
x所引用对象的实际类型最合适的那个类的方法。假设x的实际类型时D,D是C的子类,如果D定义了f(String),就直接调用它,否走将在C中寻找f(String),以此类推。
final 类和方法和域
不允许被继承的类称为final类,例如定一个了final类
public final class FinalEmploee extends Employee {
}
类中的方法也可以声明final,怎么做后子类不能覆盖这个方法
final 类中所有方法会自动成为final方法
将域声明为final表示构造对象之后就不运行改变他们的值了。将类声明为final,只有其中的方法会自动成为final,不包括域。
域为final即只允许赋值一次。
public final int a;
public FinalEmploee() {
a = 3;
}
或
public final int a = 3;
下面这种是错误的语法
public final int a = 1;
public FinalEmploee() {
a = 3;
}
强制类型转换
将一个类型强制转换称另一个类型的过程成为类型转换。
例如将double转换为int
double x = 3.1415;
int nx = (int) x;
转换后nx=3,转换丢弃了小数部分。
一个类对象的引用可以转换称其子类的对象引用。
Employee可以直接引用Manager
Employee e = new Manager();
Manager m = (Manager) e;
如果Employee不能转换为Manager,就会产生一个ClassCastException异常。
例如,会抛出一个ClassCastException异常
Employee e = new Employee();
Manager m2 = (Manager) e;
一次在经行类型转换之前,先检查一下能否转换成功, 使用instanceof判断类型。
if(e instanceof Manager)
{
Manager m2 = (Manager) e;
}
只能在继承层次内进行类型转换
超类转换为子类前,应使用
instanceof检查
抽象类
抽象方法
只定义不实现的方法,并且没有{}
public abstract String getName();
使用abstract定义的对象,包含抽象方法的类必须被声明为抽象类。
public abstract class AbstractPerson {
public abstract String getName();
}
抽象类中可以包含普通方法.
protected
protected 作用域可以声明域或方法只有包和子类可用。
Object 类
Object类是Java中类的祖先,如果没有明确指定超类,Object会被认为是这个类的超类。
可以用Object类型变量引用任何类型的对象
Obejct obj = new String("123")
在需要的时候可以将Object转换成原始类型
Object object = new String("123");
if(object instanceof String)
{
String s = (String) object;
}
Java中只有基本类型不是对象。
所有的数组类型(对象数组,基本类型数组)也都扩展了Object类。
以下语法是正确的
Object obj;
String[] ss = new String[10];
obj = ss;
obj = new int[10];
equals 方法
Object用来检测一个对象是否等于一个对象,Object中判断两个对象是否有相同的引用。
可以重载equals方法来实现自己的相等逻辑。
Object中的equals方法用来检测另一个对象是否等于当前对象。
Java 要求 equals方法有以下特性:
- 自反性 任何非空x x.equals(x) 返回 true
- 对称性 x.equals(y) == y.equals(x)
- 传递性 如果x.equals(y) y.equlas(z) 则 x.equals(z);
- 一致性 如果x,y引用对象无变化,反复调用x.equals(y)结果相同
- 非空引用x x.equals(null) 返回false
设计一个完美equals的步骤
显式参数命名为
otherObject检测
this和otherObject是否引用同一个对象if (this == otherObject) return true;检测
otherObject是否为null,为null返回falseif (null == otherObject) return true;检测
this与otherObject是否为同一个类,如果equals语义在之类中要有所改变,就使用getClass检测if(getClass() != otherObject.getClass()) return false;
如果之类语义相同,使用instanceof检测
//true
new Person() instanceof Object
```
if (!(otherObject instanceof ClassName))
return false;
```
```
```
- 将
otherObject转换为需要的类型变量
CLassName other = (ClassName) otherObject;
- 进行域比较,使用
==比较基本类型 , 使用equals比较对象域,数组可以使用Arrays.equlas比较,如果所有域都相等,返回true,否则返回false;
hashCode 方法
hashCode是由一个对象导出的一个int值,散列码是没有规律的。
Object默认的hashCode就是对象内存存储地址。
如果重新定义equals方法,就必须重新定义hashCode方法,以便用户可以将对象插入到散列表中。
数组的hashCode可以使用Arrays.hashCode计算。
toString方法
类用来返回对象值的字符串。
Java重载了String类型的+操作,当一个字符串 +一个对象时,会调用该对象的toString()方法。
数组可以使用Arrays.toString()来展示。
clone
创建一个对象的副本。
泛型数组
使用底层数组必须在使用时确定数组的大小,使用ArrayList类可以不用关心底层细节,类会自动调节数组容量。
ArrayList是一个采用了类型参数的泛型类(generic class)。即可以指定数组列表保存的元素对象类型。
使用<>将类名包裹起来,例如ArrayList<String>
声明一个保存String对象的数组列表:
ArrayList<String> list = new ArrayList<String>();
Java SE 7后可以省略右边的类型参数
ArrayList<String> list = new ArrayList<>();
CRUD
添加
使用add方法将元素添加到数组列表。
list.add("123")
固定空间
如果可以估算出数组要存储的元素数量,可以使用
list.ensureCapacity(100);来分配数组空间,这样add前100个元素就不会重新分配空间。
初始化时也可以传递初始容量
ArrayList<String> list = new ArrayList<>(100);
消减空间
trimToSize将数组列表的存储容量削减到当前尺寸。
获取长度
使用size方法获取数组列表的长度
添加元素
add(E obj)在列表尾端添加一个元素
add(int index, E element)在指定下标插入一个元素,原有下标及其后元素后移一位。
设置元素
``set(int index, E element)设置下标index为element`元素。
获取元素
get(int index)获取元素。
拷贝到一个新数组
toArray方法
删除元素
remove(index)
遍历
可以使用for each循环遍历数组
for(String s: list)
{
...
}
历史遗留代码
添加@SuppressWamings("unchecked")并使用强制类型转换。
@SuppressWarnings("unchecked") ArrayList<Employee> result =
(ArrayList<Employee>) employeeDB.find(query) ; // yields another warning
自动装箱
Java中基本类型都有一个与之对应的类,这些类被称为包装器(wrapper)。这些类分别为:Integer、Long、Float、Double、Short、Byte、Character 、Void 和Boolean ( 前 6 个类派生于公共的超类Number)。
例如要订一个整型数组列表,<>中类型参数不允许是基本类型,就必须这样写
List<Integer> list = new ArrayList<>();
由于每个值分别包装在对象中, 所以
ArrayList<lnteger>的效率远远低于int[ ]数 组。因此, 应该用它构造小型集合, 其原因是此时程序员操作的方便性要比执行效率更 加重要。
装箱
这个数组列表可以直接添加int元素到ArrayLis<lnteger>中
list.add(3)
将自动转换为
list.add(Integer.valueOf(3));
这种变换称为自动装箱autoboxing
拆箱
与之相反,将Integer对象赋值给int值时,会自动拆箱
int n = list.get(i)
会转换为
int n = list.get(i).intValue()
普通算数表达式也能自动拆装箱。
Integer n = 3;
System.out.println(n);
n++;
System.out.println(n);
编译器将自动地插人一条对象拆箱的指令, 然后进行自增计算, 最后再将结果装箱。
自动装箱的==操作没有被重载,所以不能用于判断内部值是否相等,==只是比较对象是否执行通过同一个内存区域。
Integer a = 200;
Integer b = 200;
System.out.println(a==b);//false
如果要比较包装类型内部值,要使用equals方法比较。
自动装箱规范要求boolean、byte、char 127, 介于-128 ~ 127 之间的short 和 int 被包装到固定的对象中。例如, 如果在前面的例子中将a 和b 初始化为100, 对它们 进行比较的结果一定成立。
另外, 如果在一个条件表达式中混合使用Integer 和Double 类型, Integer 值就会拆箱,提升为double, 再装箱为Double.
Integer intA = 1;
Double douD = 2.0;
System.out.println(true?intA:douD);// 输出 1.0
装箱和拆箱是编译器认可的, 而不是虚拟机。编译器在生成类的字节码时, 插人必要的方法调用。虚拟机只是执行这些字节码。
修改数值参数
Java传参都是值传递,所以不可能编写一个修改整型参数x值的方法。
public static void triple(int x) // won't work
{
x = 3 * x; // modifies local variable
}
使用Integer也不能改变,应该Integer对象是不可变的.
如果要修改数值参数值,可以使用org.omg.CORBA中的holder类型,包括IntHolder,BooleanHolder等。每种持有者都有一个公共值域,通过它修改存储其中的值。
public static void testHolder(IntHolder intHolder){
intHolder.value++;
}
解析String
Integer有静态方法parseInt(String s)和parseInt(String s,int radix)将字符串解析为int类型,radix是进制。
static Integer valueOf(String s) Static Integer value Of(String s, int radix)
是返回一个Integer对象。
可变参数
例如
public PrintStream printf(String format, Object ... args) {
return format(format, args);
}
printf方法接受两个参数,一个是String类型,一个是Object[]数组,即Object ... args等于Object[] args.
当只传入一个Object[]数组时,该数组将重新定义为可变参数列表。
public static void test(String s, Object... args) {
System.out.println(args.length);
}
public static void main(String[] args) {
TestArgs.test("1",new Object[]{"2","3"}); // args = ["2","3"]
TestArgs.test("1",new Object[]{"2","3"},"4","5");//args = [["2","3"],"4","5"]
}
枚举
定义一个枚举
public enum Size {
SMALL, MEDIUM, LARGE, EXTRA_LARGE
}
实际上,枚举也是声明了一个类,刚好有4个实例。
因此在比较枚举类型值时,直接使用==就可以。
因为枚举也是类,所以可以在枚举类型添加构造器,方法和类.构造器只在构造枚举常量时被调用。
public enum Size {
SMALL("S"), MEDIUM("M"), LARGE("l"), EXTRA_LARGE("XL");
private String abbreviation;
private Size(String abbreviation) {
this.abbreviation = abbreviation;
}
public String getAbbreviation() {
return abbreviation;
}
public static void main(String[] args) {
System.out.println(EXTRA_LARGE.getAbbreviation());
}
}
枚举类的构造器只能为private。所有枚举类型都是Enum的子类,其中最有用的方法是toString,它返回枚举常量名。
例如, Size.SMALL.toString() 将返回字符串"SMALL"
toString的逆向方法为静态方法valueOf,他将字符串解析为枚举类型。
Enum.valueOf(Size.class,"SMALL")
枚举类也可直接调用
Size.valueOf("SMALL")
每个枚举类有一个静态的values方法,他返回给包含全部枚举值的数组。
ordinal()返回枚举常量位置,从0开始计数。
反射
赋予程序员动态操作Java代码的能力,此功能大量应用于框架。反射机制可以用来:
- 在运行时分析类的能力。
- 在运行时查看对象, 例如, 编写一个toString 方法供所有类使用。
- 实现通用的数组操作代码。
- 利用Method 对象, 类似于
javascrpit中变量引用函数
Class类
程序运行时,Java 运行时系统为所有对象维护一个称之为运行时的类型标识。这个信心保存每个对象所属的类足迹,虚拟机利用运行时信息选择相应方法执行。
Java用Class类保存这些信息,Object类中的getClass()方法返回一个Class类型实例。
Class cl = new Object().getClass()
获取Class对象的方式
类获取 通过 T.class 例如
String.classint[].classT可以时任意Java类型,包括私有类型。 例如String.class,int.class,int[].class对象获取 通过实例对象的
getClass()方法获取Class对象Class.forName(String) 通过字符串类名获取
Class.forName(String)
根据字符串生成类信息,例如JDBC驱动的注册采用此方式。
生成类信息时,会调用要生成类的静态初始化方法并且初始化静态域。
可能会抛出ClassNotFoundException异常
getName
调用Class对象的getName方法,返回这个类的名字.如果类在一个包里,包名也会作为类名的一部分。
Class cl = new Object().getClass();
//java.lang.Object System.out.println(cl.getName());
通过Class对象生成实例
使用newInstance()方法生成实例
Object object2 = (Object) cl.newInstance();
System.out.println(object2);
这个方法可能会抛出 InstantiationException, IllegalAccessException异常
如果类的构造器有参数,就必须使用
Constructor类中的newInstance方法
通过getConstructor获取Constructor对象
Constructor Field Method 通用属性
getName返回名称
getModifiers返回一个整型值,不同位描述public,static这类修饰符。通过Modifier中的静态方法toString分析这个整型值,例如
Modifier.isFinal()
Modifier.isPublic()
Modifier.toString()
构造器和方法的getParameterTypes()返回参数Class[]数组,getReturnType返回返回类型的Class
使用setAccessible(true)可以覆盖Java访问控制机制,访问privatep rotected和默认域
Constructor
描述类的构造器
getConstructors返回所有public构造器
getDeclaredConstructors全部构造器(包括私有,默认,公有,保护,但不包括超类的构造器)
可以通过newInstance(Object[] args)创建一个对象实例
Field
描述类的域
getFields返回所有public域
getDeclaredFields全部域(包括私有,默认,公有,保护,但不包括超类的成员)
getName返回域名称
getType返回域类型(Class对象)
get(Object)方法获取实例对象的值,null位类的静态域值。
set(obj,value)方法用来设置域值。
Method
描述域的方法
getMethods返回所有public方法
getDeclaredMethods全部方法(包括私有,默认,公有,保护,但不包括超类的方法)
getName返回域名称
数组反射
通过class.getComponentType获取数组类内部类型。
通过Array.newInstance(Class<?> componentType, int length)创建一个对象
