1、类、超类和子类
1.1 关键字this和super
关键字this的两个用途:1、引用隐式参数 2、调用该类其他的构造器
关键字super的两个用途:1、调用超类的方法 2、调用超类的构造器
1.2 继承层次
Java不支持多继承,但继承并不仅限于一个层次。
* 由一个公共超类派生出来的所有类的集合被称为继承层次;
* 在继承层次中,从某个特定的类到其祖先的路径被称为该类的继承链,一个祖先类可以拥有多个子孙继承链
1.3 多态
一个对象变量可以指示多种实际类型的现象被称为多态;
在运行时能够自动的选择调用哪个方法的现象称为动态绑定
1.3.1 判断是否应该设计为继承关系的简单规则
判断是否应该设计为继承关系的简单规则:"is a"规则,它表明子类的每个对象也是超类的对象;
* 即:可以将一个子类的对象赋给超类变量;然而,不能将一个超类的引用赋给子类变量;
* 注意:在java中,子类数组的引用可以转换成超类数组的引用,而不需要采用强制类型转换;
而实际上,就会导致调用一个不存在的实例域,引发ArrayStoreException异常;所以,为了确保不发生这类错误,所有数组都要牢记创建他们的元素类型,并负责监督仅将类型兼容的引用存储到数组中。
1.3.2 调用对象方法的执行过程
调用对象方法的执行过程:
1、编译器查看对象的声明类型和方法名
2、编译器查看调用方法时提供的参数类型
3、如果是private方法、static方法、final方法或者构造器,那么编译器将可以准确的知道应该调用哪个方法(静态绑定);与此对应的是,调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定
4、当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法
* 注:在覆盖一个方法的时候,子类方法不能低于超类方法的可见性,特别是,如果超类方法是public,子类方法一定要声明为public
1.4 final类和方法
有时候,可能希望阻止人们利用某个类定义子类。不允许被扩展的类被称为final类;
如果在定义类的时候使用了final修饰符,就表明这个类是final类;
类中的特定方法也可以被声明为final;如果这样做,子类就不能覆盖这个方法
(final类中的所有方法自动成为final方法)
将方法或类声明为final的主要目的是:确保他们不会在子类中改变语义,例如Calendar类中的getTime()和setTime(),方法都被声明为final。这表明Calendar类的设计者负责实现Date类与日历状态之间的转换,而不允许子类处理这些问题;
同样的,String类也是final类,这意味着不允许任何人定义String的子类
1.5 强制类型转换:
将一个类型强制转换成另外一个类型的过程被称为类型转换。
将一个子类的引用赋给一个超类的变量,编译器是允许的;但将一个超类的引用赋给一个子类变量,必须进行类型装换
1.6 抽象类
1、使用abstract修饰的类是抽象类
2、抽象类中的方法可以是抽象的也可以是不抽象的
3、包含抽象方法的类一定是抽象类
4、抽象类不能被实例化,可以定义一个抽心类的对象变量,但是它只能引用非抽象子类的对象
1.7 受保护访问protected
在有些时候,人们希望超类中的某些方法允许被子类访问,或允许子类的方法访问超类的某个域。为此,需要将这些方法或域声明为protected
* private:仅对本类可见
* public:对所有类都可见
* protected:对本包及所有子类可见
抽象类:
package com.java01.day03;
/**
* @author: ju
* @date: 2020-04-30 15:27
*/
public abstract class AbstractPeople {
private int age;
/**
* 获取描述抽象方法
*/
public abstract void getDescription();
}
Person类(父类)继承 抽象类 AbstractPeople
package com.java01.day03;
import java.util.Objects;
/**
* @description: Person类
* @author: dajuweizhong
* @date: 2020-04-30 13:12
*/
public class Person extends AbstractPeople {
/**
* 姓名
*/
private String name;
/**
* 性别
*/
private String sex;
//空参构造
public Person() {
}
//有参构造
public Person(String name, String sex) {
this.name = name;
this.sex = sex;
}
//对外提供公有的get set方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
/**
* 吃饭 public方法,允许其他类调用
*/
public void eatFood(){
System.out.println("Person吃饭...");
}
/**
* 唱歌 protected方法,只允许本类及子类调用
*/
protected void singSong(){
System.out.println("Person唱歌...");
}
/**
* 跳舞 private方法,只允许本类调用,其他的类都不能操作调用
*/
private void dance(){
System.out.println("Person跳舞...");
}
/**
* 重写toString()方法
*/
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
/**
* 重写equals()方法
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Person)) {
return false;
}
Person person = (Person) o;
return Objects.equals(getName(), person.getName()) &&
Objects.equals(getSex(), person.getSex());
}
/**
* 重写hashCode()方法
*/
@Override
public int hashCode() {
return Objects.hash(getName(), getSex());
}
/**
* 获取描述抽象方法
*/
@Override
public void getDescription() {
System.out.println("Description" + this.toString());
}
}
子类 Student 继承 父类 Person
package com.java01.day03;
/**
* @description: Student学生类
* @author: dajuweizhong
* @date: 2020-04-30 13:27
*/
public class Student extends Person{
/**
* 成绩
*/
private int score;
/**
* 有参构造
*/
public Student(String name, String sex, int score) {
//super调用父类的构造
super(name, sex);
this.score = score;
}
/**
* 重写父类singSong方法
*/
@Override
public void singSong(){
System.out.println("Student唱歌...");
}
public Student(int score) {
this.score = score;
}
//get set方法
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}
测试代码:
//父类指向子类对象
Person person = new Student("张三", "男", 12);
System.out.println(person.toString()); //Person{name='张三', sex='男'}
Student student = new Student("李四", "男", 12);
System.out.println(student.toString()); //Person{name='李四', sex='男'}
person.eatFood(); //Person吃饭...
student.singSong(); //Student唱歌...
student.getDescription(); //DescriptionPerson{name='李四', sex='男'}
2、Object:所有类的超类
如果没有明确的指出超类,Object就被认为是这个类的超类
可以使用Object类型的变量引用任何类型的对象
在java中,只有基本类型不是对象,如:数值、字符和布尔类型的值都不是对象。
所有的数组类型,不管是对象数组还是基本类型数组都扩展于Object类
可以重写Object的toString、equals、hashCode等方法
/**
* 重写equals()方法
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Person)) {
return false;
}
Person person = (Person) o;
return Objects.equals(getName(), person.getName()) &&
Objects.equals(getSex(), person.getSex());
}
/**
* 重写hashCode()方法
*/
@Override
public int hashCode() {
return Objects.hash(getName(), getSex());
}
3、泛型数组列表:ArrayList
ArrayList是一个采用类型参数的泛型类。为了指定数组列表保存的元素对象类型,
需要用一对尖括号将类名括起来加在后面,例如:ArrayList students = new ArrayList<>();
测试代码:
//构造一个ArrayList<Student> students
ArrayList<Student> students = new ArrayList<>();
Student student2 = new Student("王五", "男", 15);
//使用add()方法可以将元素添加到数组列表中
students.add(student);
students.add(student2);
int size = students.size();
//访问数组列表元素,将索引为1的元素改为student对象
students.set(1, student);
//判断是否包含
boolean contains = students.contains(student2);
students.set(1, student2);
//移除student2
boolean remove = students.remove(student2);
4、对象包装器与自动装箱
有时,需要将int这样的基本类型转换为对象,所有的基本类型都有一个与之对应的类:Integer Double Long Float Short Byte Character Void Boolean;
对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。 同时,对象包装器类还是final,因此不能定义他们的子类
强调:装箱和拆箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插入必要的方法调用;虚拟机只是执行这些字节码
测试代码:
/**
* 自动装箱 自动拆箱
*/
List<Integer> integerList = new ArrayList<>();
for (int i = 0; i< 5; i++){
//自动装箱 Integer integer = Integer.valueOf(i);
//integerList.add(Integer.valueOf(i));
integerList.add(i);
}
//自动拆箱 int i = integerList.get(1).intValue();
int i = integerList.get(1);
5、参数数量可变的方法
实例代码:
/**
* 参数数量可变的方法
* @param objects Object...objects == Object[] objects 数组参数对象
*/
public static void print(Object...objects){
for (Object o : objects){
System.out.println(o.toString());
}
}
public static void main(String[] args){
/**
* 参数数量可变的方法
*/
print(student, student2, 3, "aaaa");
/*
* 输出结果:
* Person{name='李四', sex='男'}
* Person{name='王五', sex='男'}
* 3
* aaaa
*/
}
6、反射
能够分析类能力的程序成为反射。反射可以用来:
1、在运行中分析类的能力
2、在运行中查看对象
3、实现通用的数组操作代码
在程序运行期间,java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。
这个信息跟踪着每个对象所属的类,虚拟机利用运行时类型信息选择相应的方法执行。
* Class类:可以通过专门的Java类访问这些信息,保存这些信息的类被称为Class类
在运行时使用反射分析对象具体表现为:
* 类型名称
String typeName = aClass.getTypeName();
* 描述public static..的使用情况
int modifiers = aClass.getModifiers();
* 返回所有方法数组
Method[] methods = aClass.getMethods();
* 返回所有构造数组
Constructor<?>[] constructors = aClass.getConstructors();
* 返回所有参数数组
Field[] fields = aClass.getFields();
* 返回所有参数数组(包括从父类继承的/从抽象类继承的)
Field[] declaredFields = aClass.getDeclaredFields();
等等...
但是,反射机智的默认行为受限于java的访问控制。
然而,如果一个java程序没有收到安全管理器的控制,就可以覆盖访问控制。
为了达到这个目的,需要调用Field、Method或Constructor对象的setAccessible()方法 :field.setAccessible(true);
setAccessible()方法的特性是为了调试、持久存储和相似机制提供
* 使用反射编写泛型数组代码:
1、拿到数组a
2、获取a数组的类对象
3、使用getComponentType()方法确认数组对应的类型
4、使用Array.newInstance()创建新的数组
5、使用System.arraycopy()将ss数组拷贝到新数组
* 调用任意方法:
在Method类中有一个invoke()方法,它允许调用包装在当前Method对象中的方法
示例代码:
package com.java01.day03;
import com.java01.day02.Employee;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* @description:
* @author: dajuweizhong
* @date: 2020-04-30 13:36
*/
public class Test {
public static void main(String[] args){
//父类指向子类对象
Person person = new Student("张三", "男", 12);
Student student = new Student("李四", "男", 12);
/**
* 反射
*/
Student s = new Student("反射Student", "男", 18);
//通过getClass()获取Class对象
Class<? extends Student> aClass = s.getClass();
System.out.println(aClass); //class com.java01.day03.Student
//通过getName()获取class类路径(类名)
String name = aClass.getName();
System.out.println(name); //com.java01.day03.Student
/**
* 使用
* try { ... } catch (Exception e) {
* e.printStackTrace();
* } 抛出异常
*/
try {
//也可以通过类名(类路径)使用forName()获取Class对象
Class<?> dateClass = Class.forName("java.util.Date");
System.out.println(dateClass); //class java.util.Date
//使用newInstance()构造一个对象
Object instance = dateClass.newInstance();
System.out.println(instance); //Wed May 06 14:41:48 GMT+08:00 2020
Date date = (Date) instance;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
System.out.println(simpleDateFormat.format(date)); //2020-44-06 14:44:23
//类型名称
String typeName = aClass.getTypeName();
//描述public static..的使用情况
int modifiers = aClass.getModifiers();
//返回所有方法数组
Method[] methods = aClass.getMethods();
//返回所有构造数组
Constructor<?>[] constructors = aClass.getConstructors();
//返回所有参数数组
Field[] fields = aClass.getFields();
//返回所有参数数组(包括从父类继承的/从抽象类继承的)
Field[] declaredFields = aClass.getDeclaredFields();
System.out.println(typeName);
System.out.println(modifiers);
System.out.println(Arrays.toString(methods));
System.out.println(Arrays.toString(constructors));
System.out.println(Arrays.toString(fields));
System.out.println(Arrays.toString(declaredFields));
/*
输出结果
com.java01.day03.Student
1
[public void com.java01.day03.Student.singSong(), public int com.java01.day03.Student.getScore(), public void com.java01.day03.Student.setScore(int), public boolean com.java01.day03.Person.equals(java.lang.Object), public java.lang.String com.java01.day03.Person.toString(), public int com.java01.day03.Person.hashCode(), public java.lang.String com.java01.day03.Person.getName(), public void com.java01.day03.Person.setName(java.lang.String), public java.lang.String com.java01.day03.Person.getSex(), public void com.java01.day03.Person.getDescription(), public void com.java01.day03.Person.eatFood(), public void com.java01.day03.Person.setSex(java.lang.String), public final void java.lang.Object.wait() throws java.lang.InterruptedException, public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException, public final native java.lang.Class java.lang.Object.getClass(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll()]
[public com.java01.day03.Student(int), public com.java01.day03.Student(java.lang.String,java.lang.String,int)]
[]
[private int com.java01.day03.Student.score]
*/
/**
* 由于"score"是私有域,所以调用get方法的时候会报异常java.lang.IllegalAccessException
* 所以我们就要使用到:field.setAccessible(true);
*/
Field field = aClass.getDeclaredField("score");
field.setAccessible(true);
Object o = field.get(s); //18
System.out.println(o);
} catch (Exception e) {
e.printStackTrace();
}
/**
* 使用反射编写泛型数组代码
*/
//Student数组ss
Student[] ss = new Student[100];
int newLength = 150;
//获取ss数组的类对象
Class<? extends Student[]> ssClass = ss.getClass();
//使用getComponentType()方法确认数组对应的类型
Class<?> componentType = ssClass.getComponentType();
int length = Array.getLength(ss);
//使用Array.newInstance()创建新的数组
Object newArray = Array.newInstance(componentType, newLength);
//使用System.arraycopy()将ss数组拷贝到新数组
System.arraycopy(ss, 0, newArray,0, Math.min(length, newLength));
System.out.println(newArray);
/**
* 调用任意方法 getMethod ; invoke
*/
try {
//使用.class.getMethod拿到getDescription方法
Method getDescription = Person.class.getMethod("getDescription");
//调用invoke方法执行getDescription方法
Object invoke = getDescription.invoke(person);
System.out.println(invoke);
} catch (Exception e) {
e.printStackTrace();
}
}
}
7、继承设计的技巧
1、将公共操作和域放在超类
2、不要使用受保护的域
3、使用继承实现 “ is a ” 的关系
4、除非所有继承的方法都有意义,否则不要使用继承
5、在覆盖方法时,不要改变预期的行为
6、使用多态,而非类型信息
7、不要过多的使用反射