Java核心技术.卷I(第11版)
第4章:对象与类
1.继承
使用extends
关键字
public class SubClazz extends ParentClazz{
...
}
覆盖(override)或重写(overwrite):
继承时期发生
子类不能直接访问父类的私有字段,因此要使用get
和set
方法
调用父类的非私有方法,super.xxx()
子类构造器:
public SubClazz(String s, int i){
super(s,i);//调用父类同参数的构造器,对父类私有字段初始化
c = "haha";
}
没有显式调用,则自动调用父类的无参构造器,否则报编译错误。
归纳:要么默认调用无参构造器,要么就显式调用构造器
多态(polymorphism):一个对象变量可以指示多种实际类型的现象
比如,循环遍历一个父类对象数组,运行时虚拟机知道它的实际对象,并调用相对应的方法。
动态绑定(dynamic binding):运行时自动选择对应合适的方法(不同重写过的方法或者说不同子类的相同方法)
好处就是,对代码扩展来讲,非常好,虚拟机会自己确定具体的方法,并且不用修改其他部分代码。
阻止继承(final类和方法):
被final
关键字修饰的类和方法
对于final字段,构造对象后,值不能被改变。
final类的方法自动成为final,但是字段不会。
强制类型转换:使用()
double x = 3.14;
int nx = (int)x;
子类可以转成父类,没问题;父类转子类之前应该先instanceof
判断一下
a instanceof Clazz:判断a是否是Clazz类的实例
对于a是null的情况,一定返回false,因为null不引用任何对象
受保护访问,private
:
父类的方法只被子类访问
子类的方法访问父类的某个字段
受保护字段只能被同一个包的类访问,一般这个不归规范,不利于OOP,不用
受保护方法,被子类使用,不被其他类使用。(待测试加深印象,如Object的clone方法)
2.this和super总结
this:
super:
调用父类的方法
调用父类的构造器
此时,必须是构造器的第一句
3.方法调用过程
编译器查看对象的声明类型和方法名
编译器会找出该类和其父类的所有可能被调用的方法
编译器确定方法调用中的参数类型,找到对应匹配的方法
这就是**重载解析
**
如果是private
, static
, final
方法或者构造器,那么编译器就可以准确知道应该调用哪个方法。
这就是静态绑定
,就是只要在该类寻找就行,也不用涉及重载的问题。对应的是动态绑定
。
程序运行并采用动态绑定调用方法时
虚拟机为每个类准备了方法表(method table)
,调用方法时,虚拟机查表就好了。
4.fianl总结
被final
关键字修饰的类和方法
对于final字段,构造对象后,值不能被改变。
final类的方法自动成为final,但是字段不会。
final的用意:确保被final修饰的方法和类不会在子类中改变语义,就是被覆盖重写,保证状态唯一。
5.抽象类总结
越顶层的类往往越具有抽象性,使用abstract
声明该类;
子类使用extends
继承抽象类;
即使类不含抽象方法,类也可以是抽象类;
抽象类不可以被实例化;
但可以定义一个抽象类的对象变量,用这个变量去引用非抽象子类的对象,此时去调用方法就会定位到具体实现的对应方法
public abstract class Person{
private String name;
public Person(String name){
this.name = name;
}
public abstract String getdescription();
}
抽象方法类似占位符,具体使用时再具体实现。抽象方法在具体子类中都要被实现。
扩展抽象类:
6.访问控制修饰符总结
public
公开,对外部可见
protected
对本包和所有子类可见,一般就用在方法上,字段上不提倡
private
仅对本类可见
default
对本包可见
那么就是三个层级:包、本类、子类
7. Object基类
Object类型的变量可以引用任何类型的对象
在Java中,只有基本类型(primitive type)不是对象,比如数值、字符和布尔类型的值都不是对象
数组类型是对象,扩展了Object类
equals方法:(不详细,待补充)
检测两个对象是否相等,Object类实现的equal方法是比较两个对象的引用。
Object.equals(a,b)//a,b都为null时,返回ture
equals方法要具有以下特性:
hashCode方法:
hash code(散列码):传入对象,返回整型值
每个对象都继承Object类,因此,每个对象默认有一个散列码,而且一般不相同
两个相等的对象的散列码也要一致。因此,如果重新定义了equals方法,hashCode方法可能也要重新定义。
null的散列码是0
toString方法:
返回表示对象值的一个字符串
对象与一个字符串通过操作符“+”连接,Java编译器自动调用toString方法获得这个对象的字符串描述。即,
/**
* 自动调用toString方法
*/
x.toString();
// ==
"" + x;
System.out.println(x);
// ==
system。呕吐。println(x.tostring());
//字符串输出
String s = Arrary.tostring(arrNum);
Class类:
Class是一个类,
Class getClass()方法,返回包含对象信息的类对象
String getName(), 返回类名
Class getSuperclass(), 以Class对象形式返回这个类的父类
8.泛型数组
Java允许运行时确定数组大小
ArrayList,数组列表,类似数组,可以自动调整数组容量。但是不能通过索引访问对象元素
ArrayList,是一个有类型参数(type parameter)的泛型类(generic class)
ArrayList<Person> p = new ArrayList<>(); //泛型为Person
p.add(new person());
add()方法的内部机制:如果调用add而内部数组已经满了,那么数组列表会创建一个更大的数组,并把所有的对象都拷贝过去。
ensureCapacity(100),表示可以add100次,超过后就创建一个更大的数组列表
add和set方法接受任意类型的对象,所以需要注意,在强制转换的时候就可能出现问题。
数组列表的元素拷贝到数组中:
var list = new ArrayList<X>();
//todo: add element
var a = new X[list.size()];
list.toArray(a);//拷贝成数组,便于索引访问
ArrayList插入(add)和删除(remove),插入和删除,都要整体移动后面的所有元素,使用链表插入和删除的效率高。
9.对象包装器和自动装箱
所有的基本类型都有一个与之对应的类,便于将基本类型转换成对象。
这些类就是包装器(wrapper),这些类的父类是Number
类。包装器类都是final类,因此不能派生出子类。
在泛型中,<>
只能使用包装器类,不能使用基本类型。
var list = new ArrayList<Integer>();
自动装箱(autoboxing):基本类型的值封装成对应对象
list.add(3);
//自动变换成
list.add(Integer.valueOf(3));
与之对应的,自动拆箱:对象自动转换成基本类型的值
// list是一个整型数组列表
int n = list.get(i);
//自动转换成
int n = list.get(i).intValue()
装箱和拆箱都是编译器干的活,虚拟机只是负责执行这些字节码。
类型转换:
String s = "3";
int x = Integer.paraseInt(s);//静态方法paraseInt()
10.参数数量可变的方法
type... var
public static double max(double... values){
double largest = Double.NEGATIVE_INFINITY;
for (double v:values) if (v > largest) largest = v;
return largest;
}
double m = max(3.1, 40.1, -5);
分析:编译器将new double[]{3.1, 40.1, -5}传给max方法。
11.枚举类 enum关键字
声明的是一个类,枚举类。
经典模式:
public enum Size{SMALL, MEDIUM, LARGE, EXTRA_LARGE}
比较全的:
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;}
}
enum构造器一定是私有的,否则语法错误。
所有的枚举类都是Enum类。
Size.SMALL.toString(); //"SMALL"
Size s = Enum.valueOf(Size.class, "SMALL");//s=Size.SMALL
Size[] values = Size.values();//返回全部枚举值的数组
int ordinal();//返回枚举常量的索引(下标)
12.反射
能够分析类能力的程序成为反射(reflective),用来编写动态操纵Java代码的程序。
比如:支持 用户界面生成器;对象关系映射器;其他需要动态查询类能力的开发工具。
Class类:
程序运行期间,Java运行时系统会对每个对象维护一个运行时类型标识
,跟踪每个对象所属的类,以便执行正确的方法。
虚拟机为每个类型(包括基础类型)管理一个唯一的Class对象,因此可以使用“===”作比较。
与instanceof不同的是,他不涵括子类,只与自身类比较。
static Class forName(String className): 返回一个Class对象,表示名为className的类。
利用反射分析类的能力:
最重要:检查类的结构;
java.lang.reflect
包中Field、Method和Constructor三个类分别用来描述类的字段、方法和构造器;使用这三个类的getName方法来返回。具体看API。
使用反射在运行时分析对象:
也就是动态获取某个实例的某个字段的具体值,诸如此类。
步骤:
使用反射编写泛型数组:
。。。跳过
调用任意方法和构造器:
。。。跳过
13.声明异常入门
非检查型异常
比如越界错误和访问null引用,就是运行时候报错
检查型异常
编译器会检查,就是编译报错
在方法上使用throws XXXException
,调用该方法的任何方法也要一样声明,包括main方法。
这样会捕获异常而不会因为异常终止程序。
14.继承的设计技巧
公共操作和字段放在父类中
不使用受保护的字段
protected,对本包和所有子类可见,因此子类或同一包的其他类可以直接访问父类的字段,破坏了封装性
使用继承实现“is-a”关系
除非所有继承的方法都有意义,否则就不要使用继承
在覆盖方法时,不要改变预期的行为
使用多态,而不要使用类型信息
也就是如果多个具体行为都具有同一个概念,就是用多态来完成,不要死板的使用类型类判断在做具体的行为。
可以将这个放在父类或者接口中,再具体实现。
不要滥用反射