继承 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
返回false
if (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.class
int[].class
T可以时任意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)
创建一个对象