继承(is - a)
基本思想:基于已有的类创建新的类。
增加新的方法和字段,使新类适应新的情况
类,子类,超类
原则上最一般的方法放在超类,特殊方法放在子类
覆盖方法
子类中提供一个新的方法覆盖超类中的方法
子类访问超类中的方法,使用super关键字
子类构造器不能访问超类中的私有字段,用一个构造器初始化私有字段,利用super语法调用构造器,必须是子类构造器第一条内容
class Manager{
super.(name,age,salary);
}
this的含义:
1. 指示隐式参数的引用
2. 调用该类中的其他构造器
继承层次
由一个公共超类派生出所有类的集合称为继承层次
多态
多态:一个对象变量可以指示多种实际类型的现象
动态绑定:运行时自动选择适当的方法
子类每个对象也是超类的对象,is-a指的是程序中出现超类对象的任何地方都可以用子类对象替代
不能把超类的引用赋给子类变量
子类 对象 = 超类 (×)
子类引用数组可以转换为超类引用的数组
对于private方法,static方法,final方法,构造器,编译器可以准确知道应该调用哪个方法
调用方法过程:
1. 虚拟机获取e的实际类型方法表
2. 虚拟机查找有该方法的类
3. 虚拟机调用这个方法
阻止继承 final和方法
我们希望阻止人们利用某个类定义子类,很简单,把他变成final类
强制类型转换
某个类的对象引用转换或另外一个类的对象引用
进行强制类型转换,才能通过运行时的检查
一个良好的程序设计习惯:在进行强制类型转换之前,查看是否能够成功的转换
- 只有在继承层次内进行强转
- 将超类强转成子类之前用instanceof检查
Manager a = new Manager("a", 1, 1, 1, 1);
Employee b = new Employee("b", 2, 2, 2, 2);
if (a instanceof Employee){ System.out.println("√"); }}
抽象类
祖先类拥有一般性,将它作为派生其他类的基类
受保护访问
把类中的字段标记为private ,方法标记为public
需求:限制超类中的某个方法只允许子类访问,希望允许子类的方法访问超类的某个字段
protected 保护字段
private | 仅对本类可见 |
---|---|
public | 对外部完全可见 |
protected | 对本包和所有子类可见 |
默认 | 对本包可见 |
Object:所有类的超类
Object类型变量
Object 类型变量只能用于作为各种值的一个泛型容器
清楚对象的原始类型,进行相应的强制类型转换
equals方法
equals方法用于检测一个对象是否等于另外一个对象
equals方法确定两个对象引用是否相等,如果两个对象引用相等,两个对象肯定就相等
只有两个对象属于同一个类时,才可能相等
相等测试与继承
编写一个完美的equals方法:
显式参数命名为otherObject,稍后强转为一个名为other的变量
检测this和otherObject是否相等
检测otherObject是否为null,如果是返回false
比较this与otherObject的类
将otherObject强转为相应类型变量
根据相等性概念的要求比较字段,用==比较基本类型字段,用equals比较对象字段,如果所有字段都匹配,返回true,否则 false
子类重新定义equals,其中包含一个super.equals(other)调用
public boolean equals(Object otherObject){
if (this == otherObject){
return true;
}
if (otherObject == null){
return false;
}
if (getClass() != otherObject.getClass()){
return false;
}
Employee other = (Employee)otherObject;
return Objects.equals(name,other.name) &&salary == other.salary && Objects.equals(hireDay,other.hireDay);
}
hashCode方法
散列码是由对象导出的一个整型值,每个对象都有一个默认的散列码,其值由对象存储地址得出,通过hashCode码判断地址是否相同
toString方法
返回表示对象值的一个字符串,可以由子类调用,随处可见
toString 方法是一种非常有用的调试工具
强烈建议自定义的每一个类添加toString方法,自己和他人都收益
class EqualsTest{
public static void main(String[] args) {
Employee a_a1 = new Employee("A A", 75000, 1987, 12, 15);
Employee a_a2 = a_a1;
Employee a_a3 = new Employee("A_A", 75000, 1987, 12, 15);
Employee b_b = new Employee("B_B", 50000, 1989, 10, 1);
System.out.println("a_a1 == a_a2" + (a_a1 == a_a2));
System.out.println("a_a1 == a_a3" + (a_a1 == a_a3));
System.out.println("a_a1.equals(a_a3)" + (a_a1.equals(a_a3)));
System.out.println("a_a1.equals(b_b)" + (a_a1.equals(b_b)));
Manager c_c = new Manager("C_C", 80000, 1987, 12, 15);
Manager boss = new Manager("C_C", 80000, 1987, 12, 15);
boss.setBonus(5000);
System.out.println("boss.toString():" + boss);
System.out.println("c_c.equals(boss):" + c_c.equals(boss));
System.out.println("a_a1.hashCode()" + a_a1.hashCode());
System.out.println("a_a3.hashCode()" + a_a3.hashCode());
System.out.println("b_b.hashCode()" + b_b.hashCode());
System.out.println("c_c.hashCode()" + c_c.hashCode());
}
}
class Employee{
private String name;
private double salary;
private LocalDate hireDay;
public Employee(String name, double salary,int year,int month,int day) {
this.name = name;
this.salary = salary;
hireDay = LocalDate.of(year,month,day);
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
public LocalDate getHireDay() {
return hireDay;
}
public void raiseSalary(double byPercent){
double raise = salary * byPercent / 100;
}
public boolean equals(Object otherObject){
if (this == otherObject){
return true;
}
if (otherObject == null){
return false;
}
if (getClass() != otherObject.getClass()){
return false;
}
Employee other = (Employee)otherObject;
return Objects.equals(name,other.name) &&salary == other.salary && Objects.equals(hireDay,other.hireDay);
}
public int hashCode(){
return Objects.hash(name,salary,hireDay);
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", salary=" + salary +
", hireDay=" + hireDay +
'}';
}
}
class Manager1 extends Employee{
private double bonus;
public Manager1(String name, double salary, int year, int month, int day) {
super(name, salary, year, month, day);
bonus = 0;
}
@Override
public double getSalary() {
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
public void setBonus(double bonus){
this.bonus = bonus;
}
@Override
public boolean equals(Object otherObject) {
if (!super.equals(otherObject))return false;
Manager1 other = (Manager1) otherObject;
return bonus == other.bonus;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), bonus);
}
@Override
public String toString() {
return super.toString() + "[bonus=" + bonus + "]";
}
}
泛型数组列表
java 允许运行时确定数组的大小
Arraylist类,自动调整数组容量,不需要为此编写代码
弥补数组短板
访问或改变数组元素用get set 方法,而不是较简单的下标
存在取长补短的方法
既可以灵活扩展数组,又可以方便访问数组下标
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee("d",3,3,3,3));
employees.add(new Employee("d",3,3,3,3));
employees.add(new Employee("d",3,3,3,3));
employees.add(new Employee("d",3,3,3,3));
Employee[] employees1 = new Employee[employees.size()];
employees.toArray(employees1);
类型化与原始数组列表的兼容性
public void update(ArrayList list){}
public ArrayList find(String query){}
ArrayList<Employee> result = e.find(query);//报错
ArrayList<Employee> result = (ArrayList<Employee>) e.find(query);//还是报错
@SuppressWarnings("unchecked")
ArrayList<Employee> result = (ArrayList<Employee>) e.find(query);//√
//确保代码没问题,再写注解
参数数量可变的方法 (可变参数)
public void print(int a,Object ... args){}
枚举类
构造器私有
public static void main(String[] args) {
Num[] values = Num.values();
for (Num value : values) {
System.out.println(value);
}
}
}
enum Num{
PI(3.14),E(2.7);
private double d;
Num(double d) {
this.d = d;
}
public double getD() {
return d;
}
public void setD(double d) {
this.d = d;
}
@Override
public String toString() {
return "Num{" +
"d=" + d +
'}';
}
}
反射
反射是 一种功能强大且复杂的机制,适合开发工具的程序员,不适合一般的应用程序员
反射库提供了丰富且精巧的工具集,用来编写能够操纵java代码的程序,使用反射,
java可以支持用户界面生成器,对象关系映射器,需要动态查询类能力的开发工具
能分析类能力的程序叫反射
作用:
- 在运行时分析类的能力
- 在运行时检查对象
- 实现泛型数组操作代码
- 利用Method对象,类似C++函数指针
Class类
获得class类对象的方法:
- 对象. getClass();
- Class.forName(类名);
- T.class;
用 == 运算符实现两个类对象的比较 , instanceof 又可能是父子关系,就不可行
构建对象实例
var className = "java.util.Random";
Class cl = Class.forName(className);
Object obj = cl.getConstructor().newInstance();
声明异常入门
运行发生错误,程序抛出一个异常,没有提供处理器,程序会终止,在控制台打印异常类型
异常分:1. 非检查型异常 2. 检查型异常
不是所有错误都能避免的,需要一个throws声明
使用反射分析类
反射机制最重要的内容—检查类的结构
publicclass java.util.Date
{
public java.util.Date(int, int, int, int, int, int);
public java.util.Date(java.lang.String);
public java.util.Date();
public java.util.Date(long);
public java.util.Date(int, int, int);
public java.util.Date(int, int, int, int, int);
public boolean after(java.util.Date);
public boolean before(java.util.Date);
public boolean equals(java.lang.Object);
public java.lang.String toString();
public int hashCode();
public java.lang.Object clone();
public int compareTo(java.util.Date);
public volatile int compareTo(java.lang.Object);
private void readObject(java.io.ObjectInputStream);
private void writeObject(java.io.ObjectOutputStream);
private final sun.util.calendar.BaseCalendar$Date normalize();
private final sun.util.calendar.BaseCalendar$Date normalize(sun.util.calendar.BaseCalendar$Date);
public static long parse(java.lang.String);
public int getDate();
public static java.util.Date from(java.time.Instant);
public long getTime();
public void setTime(long);
public static long UTC(int, int, int, int, int, int);
private static final java.lang.StringBuilder convertToAbbr(java.lang.StringBuilder, java.lang.String);
private final sun.util.calendar.BaseCalendar$Date getCalendarDate();
private static final sun.util.calendar.BaseCalendar getCalendarSystem(long);
private static final sun.util.calendar.BaseCalendar getCalendarSystem(sun.util.calendar.BaseCalendar$Date);
private static final sun.util.calendar.BaseCalendar getCalendarSystem(int);
public int getDay();
public int getHours();
private static final synchronized sun.util.calendar.BaseCalendar getJulianCalendar();
static final long getMillisOf(java.util.Date);
public int getMinutes();
public int getMonth();
public int getSeconds();
private final long getTimeImpl();
public int getTimezoneOffset();
public int getYear();
public void setDate(int);
public void setHours(int);
public void setMinutes(int);
public void setMonth(int);
public void setSeconds(int);
public void setYear(int);
public java.lang.String toGMTString();
public java.time.Instant toInstant();
public java.lang.String toLocaleString();
private static final sun.util.calendar.BaseCalendar gcal;
private static sun.util.calendar.BaseCalendar jcal;
private transient long fastTime;
private transient sun.util.calendar.BaseCalendar$Date cdate;
private static int defaultCenturyStart;
private static final long serialVersionUID;
private static final [Ljava.lang.String; wtb;
private static final [I ttb;
}
D:\Java\jdk8\bin\java.exe "-javaagent:D:\java\IntelliJ IDEA 2021.2.3\lib\idea_rt.jar=62728:D:\java\IntelliJ IDEA 2021.2.3\bin" -Dfile.encoding=UTF-8 -classpath D:\java\jdk8\jre\lib\charsets.jar;D:\java\jdk8\jre\lib\deploy.jar;D:\java\jdk8\jre\lib\ext\access-bridge-64.jar;D:\java\jdk8\jre\lib\ext\cldrdata.jar;D:\java\jdk8\jre\lib\ext\dnsns.jar;D:\java\jdk8\jre\lib\ext\jaccess.jar;D:\java\jdk8\jre\lib\ext\jfxrt.jar;D:\java\jdk8\jre\lib\ext\localedata.jar;D:\java\jdk8\jre\lib\ext\nashorn.jar;D:\java\jdk8\jre\lib\ext\sunec.jar;D:\java\jdk8\jre\lib\ext\sunjce_provider.jar;D:\java\jdk8\jre\lib\ext\sunmscapi.jar;D:\java\jdk8\jre\lib\ext\sunpkcs11.jar;D:\java\jdk8\jre\lib\ext\zipfs.jar;D:\java\jdk8\jre\lib\javaws.jar;D:\java\jdk8\jre\lib\jce.jar;D:\java\jdk8\jre\lib\jfr.jar;D:\java\jdk8\jre\lib\jfxswt.jar;D:\java\jdk8\jre\lib\jsse.jar;D:\java\jdk8\jre\lib\management-agent.jar;D:\java\jdk8\jre\lib\plugin.jar;D:\java\jdk8\jre\lib\resources.jar;D:\java\jdk8\jre\lib\rt.jar;D:\java\projectfu\out\production\projectfu;C:\Users\11021\.m2\repository\junit\junit\4.13.1\junit-4.13.1.jar;C:\Users\11021\.m2\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar com.fu.javatec.chapter05.ReflectionTest
Enter class name (e.g. java.util.Date):
java.util.Date
java.lang.Object---====
public class java.util.Date
{
public java.util.Date(int, int, int, int, int, int);
public java.util.Date(java.lang.String);
public java.util.Date();
public java.util.Date(long);
public java.util.Date(int, int, int);
public java.util.Date(int, int, int, int, int);
public boolean after(java.util.Date);
public boolean before(java.util.Date);
public boolean equals(java.lang.Object);
public java.lang.String toString();
public int hashCode();
public java.lang.Object clone();
public int compareTo(java.util.Date);
public volatile int compareTo(java.lang.Object);
private void readObject(java.io.ObjectInputStream);
private void writeObject(java.io.ObjectOutputStream);
private final sun.util.calendar.BaseCalendar$Date normalize();
private final sun.util.calendar.BaseCalendar$Date normalize(sun.util.calendar.BaseCalendar$Date);
public static long parse(java.lang.String);
public int getDate();
public static java.util.Date from(java.time.Instant);
public long getTime();
public void setTime(long);
private static final java.lang.StringBuilder convertToAbbr(java.lang.StringBuilder, java.lang.String);
private final sun.util.calendar.BaseCalendar$Date getCalendarDate();
private static final sun.util.calendar.BaseCalendar getCalendarSystem(long);
private static final sun.util.calendar.BaseCalendar getCalendarSystem(int);
private static final sun.util.calendar.BaseCalendar getCalendarSystem(sun.util.calendar.BaseCalendar$Date);
public int getDay();
public int getHours();
private static final synchronized sun.util.calendar.BaseCalendar getJulianCalendar();
static final long getMillisOf(java.util.Date);
public int getMinutes();
public int getMonth();
public int getSeconds();
private final long getTimeImpl();
public int getTimezoneOffset();
public int getYear();
public void setDate(int);
public void setHours(int);
public void setMinutes(int);
public void setMonth(int);
public void setSeconds(int);
public void setYear(int);
public java.lang.String toGMTString();
public java.time.Instant toInstant();
public java.lang.String toLocaleString();
public static long UTC(int, int, int, int, int, int);
private static final sun.util.calendar.BaseCalendar gcal;
private static sun.util.calendar.BaseCalendar jcal;
private transient long fastTime;
private transient sun.util.calendar.BaseCalendar$Date cdate;
private static int defaultCenturyStart;
private static final long serialVersionUID;
private static final [Ljava.lang.String; wtb;
private static final [I ttb;
}
Process finished with exit code 0
使用反射分析对象
如何查看任意对象数据字段的名字和类型
- 获得对应的Class对象
- 在这个Class对象上调用getDeclaredFields
查看对象指定字段的内容,利用反射可以查看在编译时不知道的对象字段
可以获得值,可以设置值
getDeclaredFileds获得所有的数据字段,使用setAccessible便利方法将所有字段设置为可访问的
package com.fu.javatec.chapter05;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
public class Demo07 { }
class ObjectAnalyzer{
private ArrayList<Object> visited = new ArrayList<>();
public String toString(Object obj) throws ReflectiveOperationException{
if (obj == null) return "null";
if (visited.contains(obj)) return "...";
visited.add(obj);
Class cl = obj.getClass();
if (cl == String.class) return (String)obj;
if (cl.isArray()){
String r = cl.getComponentType() + "[]{";
for (int i = 0; i < Array.getLength(obj); i++) {
if (i > 0) r += ",";
Object val = Array.get(obj, i);
if (cl.getComponentType().isPrimitive()) r += val;
else r += toString(val);
}
return r + "}";
}
String r = cl.getName();
do {
r += "l";
Field[] fields = cl.getDeclaredFields();
AccessibleObject.setAccessible(fields,true);
for (Field f : fields) {
if (!Modifier.isStatic(f.getModifiers())){
if (!r.endsWith("{")) r += ",";
r += f.getName() + "=";
Class t = f.getType();
Object val = f.get(obj);
if (t.isPrimitive()) r += val;
else r += toString(val);
}
}
r += "]";
cl = cl.getSuperclass();
}
while(cl != null);
return r;
}
}
使用反射编写泛型数组代码
java.lang…reflect 包中的 Array 类允许动态创建数组
要获得新数组元素类型:
- 获得a数组的类对象
- 确认他是一个数组
- 使用Class类的getComponentType方法确认数组的正确类型
package com.fu.javatec.chapter05;
import java.lang.reflect.Array;
import java.util.Arrays;
public class Demo08 {
public static void main(String[] args) {
int []a = {1,2,3};
a = (int[]) goodCopyOf(a,10);
System.out.println(Arrays.toString(a));
String[] b = {"Tom","Dick","Harry"};
b = (String[]) goodCopyOf(b,10);
}
public static Object[] badCopyOf(Object[] a,int newLength){
Object[] newArray = new Object[newLength];
System.arraycopy(a,0,newArray,0,Math.min(a.length,newLength));
return newArray;
}
public static Object goodCopyOf(Object a,int newLength){
Class cl = a.getClass();
if (!cl.isArray())return null;
Class componentType = cl.getComponentType();
int length = Array.getLength(a);
Object newArray = Array.newInstance(componentType, newLength);
System.arraycopy(a,0,newArray,0,Math.min(length,newLength));
return newArray;
}
}
使用反射调用任意方法和构造器
java没有提供途径将一个方法的存储地址传给另外一个方法
高斯林:方法指针是很危险的,而且容易出错
Field类中的get方法查看一个对象的字段,Method类有invoke方法,调用包装在当前Method对象中的方法
Object invoke(Object obj,Object... args)
第一个参数是隐式参数,其余的对象提供显式参数
对于静态方法,第一个参数可以省略,把他设置为null
打印数学函数
package com.fu.javatec.chapter05;
import java.lang.reflect.Method;
import java.sql.Ref;
public class Demo09 {
}
class MethodTableTest{
public static void main(String[] args) throws ReflectiveOperationException{
Method square = MethodTableTest.class.getMethod("square", double.class);
Method sqrt = Math.class.getMethod("sqrt", double.class);
printTable(1,10,10,square);
printTable(1,10,10,sqrt);
}
public static double square(double x){
return x * x;
}
public static void printTable(double from,double to,int n,Method f) throws ReflectiveOperationException {
System.out.println(f);
double dx = (to - from) / (n - 1);
for (double x = from;x <= to;x += dx){
double y = (Double) f.invoke(null , x);
System.out.printf("%10.4f | %10.4f%n",x,y);
}
}
}
建议仅在绝对必要的时候才在你自己的程序中使用Method对象,通常更好的做法是使用接口以及java 8 引入的 Lambda 表达式
特别强调:建议 java 开发者不要使用回调函数的Method对象,可以使用回调的接口,这样代码执行速度块,易于维护
继承设计技巧
1. 将公共操作和字段放在超类中
2. 不要使用受保护的字段
3. 使用继承实现 is-a 关系
4. 除非所有继承的方法有意义,否则不用继承
5. 在覆盖方法时,不要改变预期的行为
6. 使用多态,而不要使用类型信息,使用多态或接口更容易维护和发展
7. 不要滥用反射