继承
通过继承可以基于现有的类创建一个新的类,继承父类所有公开的成员还可添加新的成员变量和方法进行扩展
is a是继承的一个明显的特征,比如有一个水果类和一个苹果类,而苹果也是一种水果
继承使用extends关键字,java中只有单继承,但支持多重继承(一个类只可以继承一个类,但其父类还可以继承其他的类,达到多重继承的效果),java中每一个基类都默认继承Object,即Object类是所有类的超类
简单的写个继承的例子
public class Father {}
public class Son extends Father{}
构造
在创建每个类时,默认会有一个空参构造,但如果自定义了一个任意的有参数的构造,那么这个空参构造便不复存在,构造函数最大的意义就是为了在创建对象时为成员变量初始化,基于这条规则可能在继承时遇到的问题,同时构造器不能被继承(只能在类内部通过super调用,且必须在子类的构造的第一行中,如果一个类想要被继承,必须有一个可被子类访问的构造函数,该构造函数的权限可以为public、protected、和default(不声明))
下面的例子是父类有一个String类型的变量作为参数的构造方法,而类Son继承了这个类,该类会默认调用父类的空参构造,而父类此时已经没有了空参构造,所以Son类无论写不写这个空参构造都会编译失败
public class Father {
private String name;
public Father(String name) {
this.name = name;
}
}
public class Son extends Father{
// 编译出错
public Son() {
}
}
解决方法
子类同样提供一个有参构造,并手动调用父类的构造,使用super关键字可以调用父类中的构造和成员
public class Son extends Father{
public Son(String name) {
super(name);
}
}
访问域
同包:子类可以访问父类中任意非私有的成员变量和成员方法
不同包:子类可以访问父类中使用public或protected修饰的成员变量和成员方法
多态
父类引用指向子类对象是最常见的说法
多态中最容易模糊的就是变量和方法的访问问题
public class Father {
public Integer age;
LocalDateTime localDateTime;
protected String sex;
private String name;
static Integer zz = 10;
public Father() {
name = "father";
}
public static void staticFun(){
System.out.println("father staticFun");
}
public void print(String message){
System.out.println("father:"+message);
}
}
public class Son extends Father {
Integer age = 10;
Double height = 115.5;
// static Integer zz = 20;
@Override
public void print(String message) {
System.out.println("son:"+message);
}
public static void staticFun(){
System.out.println("son staticFun");
}
public void say(){
System.out.println("son: say()");
}
}
public class MyTest {
public static void main(String[] args) {
// 父类引用指向子类对象
Father fa = new Son();
// 成员变量 编译时看左边(在编辑器内根本访问不到子类特有的成员变量) 运行时看右边(即使子类中有和父类同名的变量,运行时还是会走父类的变量)
System.out.println(fa.age);
// 成员方法 编译看左边(访问不到子类中特有的方法)
// fa.say();
// 成员方法的运行 首先多态的情况调用方法时会首先去父类中查看有没有该方法,如果没有直接编译出错
// 如果父类有,在调用时会看子类是否有该方法,如果没有调用父类方法,如果有调用子类方法,这也是多态的最大特点之一
// 成员方法 编译看左边 运行看右边
// son:message
fa.print("message");
// 静态函数 编译和运行都看左边(父类),对于静态成员不推荐使用对象访问
fa.staticFun();
// System.out.println(fa.zz);
}
}
总结:
1.成员变量 : 编译和运行都看左边
2.成员方法 : 编译看左边,如果父类有编译通过,运行时如果子类没有重写该方法,则调用父类的方法,如果子类重写了该方法,则调用子类重写后的方法
3.静态成员 : 首先静态成员(变量和方法)都不推荐使用对象方法,编译和运行都看左边
类型转换
Father father = new Son();
// class xyz.taoqz.chapter3.Son 类型依然是子类类型(这个是没有想到的~~)
System.out.println(father.getClass());
// 编译错误
// father.say();
// 向下转型,可以获得更多方法
Son s = (Son) father;
s.say();
// 如果不知道某个变量属于哪个类型可以使用instanceof关键字进行判断后再进行转换
if(变量 instanceof 类型){}
抽象类
抽象类可以将同一类事物的共同属性抽取出来,作为更加抽象的基类供子类进行扩展,抽象类不能创建对象这也让抽象类的抽象得到了体现
抽象类使用abstract修饰,命名规则:抽象类命名使用Abstract或Base开头
//public final abstract class BasePerson {
public abstract class BasePerson {
public String name;
public Integer age;
public void print(){}
public abstract void fun();
}
public class Student extends BasePerson {
@Override
public void fun() {
System.out.println("必须重写");
}
}
abstract关键字不能和final关键字共用,因为abstract本身的意义就是为了其他子类具象化,如果加了final关键字则不能进行继承,那设立抽象类也就没有了意义,抽象方法同理
抽象类不能实例化对象,也就是不能new,抽象类中可以有抽象方法和非抽象方法,但是如果有抽象方法,子类在继承时必须实现该方法(Java8中提供的default方法只能在接口中使用)
Object类
Object类是Java中所有类的始祖,Java中没有明确使用extends关键字继承其他父类默认继承自Object,但是不需要使用extends声明,并且如果声明继承了其他父类,那么该父类如果没有继承其他类也会默认继承Object,所以说Object类中的所有方法都可以被任意类继承使用,可以使用Object类型的变量引用任何对象的类
拆装箱
ArrayList<int> array = new ArrayList<>();
上面的代码将不能通过编译期,因为集合只能存储引用数据类型,但有时确实有刚需,所有的基本类型都有与之对应的一个类,Java又在JDK5之后引入了自动拆装箱
基本数据类型 | 对应的包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
boolean | Boolean |
char | Character |
float | Float |
double | Double |
其中表示数值的包装类都继承子java.lang.Number类,对象包装器是不可变的都由final修饰
ArrayList<Integer> array = new ArrayList<>();
array.add(18);
上面代码将一个基本数据类型赋值给一个泛型为Integer的集合中,做了自动装箱,实际
array.add(Integer.valueOf(18));
不仅提供了自动装箱,还有自动拆箱
// 将Integer的数值赋值给一个基本数据类型int
int num = array.get(0);
// 实际为
num = array.get(0).intValue();
// 同样支持算术运算符合赋值运算符中
Integer count = 10;
int result = count + 8;
注意事项
Integer类
使用包装类比较数值是否相等时,请使用其重写后的equals()方法,以Integer举例
Integer num1 = 10; //
// 等同于
// num1 = Integer.valueOf(10);
// 创建了一个新的Integer对象
Integer num2 = new Integer(10);
// == 在比较基本数据类型时比较的是其值是否相等
// 比较引用数据类型时比较的是其地址值是否相同
System.out.println(num1 == num2); // false
// equals比较两个引用数据类型的值是否相同
System.out.println(num1.equals(num2)); // true
Integer num1 = 127;
Integer num2 = 127;
System.out.println(num1 == num2); // true
System.out.println(num1.equals(num2)); // true
Integer num1 = 128;
Integer num2 = 128;
System.out.println(num1 == num2); // false
System.out.println(num1.equals(num2)); // true
可以看到很奇怪的两个127的Integer对象使用==号可以得出相等而128则不行,查看Integer类源码,其中重要的几个方法
// 被包装的那个基本数据类型的数值
private final int value;
// 构造方法,总是创建一个新的Integer对象
public Integer(int value) {
this.value = value;
}
// 调用该方法,返回内部包装的int值
public int intValue() {
return value;
}
// 将其转为Integer类型,在调用intValue()返回数值,作比较
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
// Integer num = 10
// 等同于 num = Integer.valueOf(10);
// 本质便是调用该方法,可以看到有个IntegerCache类,该类便可解决前面的疑问
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
// 该类是类Integer的一个内部类
// 主要作用为将 -128 - 127的数值做了缓存
// 所以无论使用怎样的方法创建数值在 -128 - 127的Integer对象,不管在使用==还是equals()都是相等的,反之超过该范围或使用new创建的都是新的Integer对象
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
Integer类的缓存数值范围可以修改,其他类则不可以
可以在编辑器中添加参数 -XX:AutoBoxCacheMax=200 指定范围
也正因为这些对应的包装类都是引用类型,所以还需特别注意空指针
除了Integer类以外,其他的整型对应的包装类内部都有一个内部类,缓存了-127 -- 128的数
Character类也提供了缓存,对对应int值在 0-127 范围的字符做了缓存
// Character类源码
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}
private static class CharacterCache {
private CharacterCache(){}
static final Character cache[] = new Character[127 + 1];
static {
for (int i = 0; i < cache.length; i++)
cache[i] = new Character((char)i);
}
}
由于Java中方法都是值传递,如果想使用方法改变一个变量的值,比如
org.omg.CORBA包下提供了暴露内部value的对应的"包装类"
@Test
public void demo9(){
int num = 10;
change(num);
change2(num);
IntHolder intHolder = new IntHolder(num);
change3(intHolder);
System.out.println(intHolder.value);
}
public void change(int x){
x *= 10;
}
// Integer为
public void change2(Integer x){
x *= 10;
}
public void change3(IntHolder x){
x.value *= 10;
}
可变参
在Java5之前每个方法都有固定数量的参数,在Java5后提供了可变参数的方法,其参数可以为0,1或者多个,可变参数必须放在参数列表的最后,这是因为如果之后还有同类型的形参,那该参数将永远拿不到实参(即使不同类型也必须将其放在参数列表的最后)
其本质和数组没有什么区别,在传参或者取值时都可以使用数组的方式进行操作
@Test
public void demo(){
String[] strs = {"Hello","Java","!!!"};
print(1,strs);
}
public void print(int a,String... num){
System.out.println(num[1]);
}
枚举
枚举:本身也是一个类,编译后的文件也是由.class结尾,声明时将对应的class的位置替换为关键字enum,所有自定义的枚举类都继承自类java.lang.Enum,在有需要常量的情况下可以考虑使用枚举类
java类不可直接继承Enum枚举类
定义一个枚举类
和普通的类差别不大,也可以拥有成员变量和成员方法
// 将对应的class替换为enum
public enum Size {
// 编译失败,枚举值必须在枚举类的第一行中定义
// private String number;
// 枚举类中的常量
SMALL("S"),MEDIUM("M"),LARGE("L"),EXTRA_LARGE("XL");
// 成员变量
private String number;
// 构造方法,默认是private也必须为private
Size(String number){
// super关键字并不能在枚举类中使用
// super("z",1);
this.number = number;
}
public String getNumber() {
return number;
}
}
常用的方法
首先看一下所有枚举类共同的父类Enum中的方法
// 静态方法 返回包含全部枚举值的数组
Size[] values = Size.values();
for (Size value : values) {
// 直接打印时调用的就是toString方法
// System.out.println(value);
System.out.println(value.toString());
// 和toString方法的输出一致,都是常量的String字符串名称,toString内部也是直接返回name
System.out.println(value.name()); // SMALL MEDIUM LARGE EXTRA_LARGE
// 返回常量在枚举类中定义的顺序,从0开始
System.out.println(value.ordinal()); // 0 1 2 3
// 自定义的方法,返回枚举值的构造中的变量
System.out.println(value.getNumber()); // S M L XL
// System.out.println(value.compareTo());
// System.out.println(value.equals());
}
// 静态方法,根据枚举类型和常量名称 返回一个枚举常量
Size smAll = Size.valueOf(Size.class, "SMALL");
System.out.println(smAll);
其中的两个比较方法可以去看Enum类中的具体实现compareTo()、equals()
// 类Enum 是一个抽象类
// 看源码得知,获取枚举类中常量的信息的两个变量都是私有其不可变的,但提供了两个公开的不可变的方法来获取对应值
private final String name;
public final String name() {
return name;
}
private final int ordinal;
public final int ordinal() {
return ordinal;
}
// 唯一的构造,修饰符为protected(枚举类不能通过super访问,但类不提供构造不能被继承)
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
// Enum类重写了equals方法,所以比较枚举时可以不必使用该方法直接使用 == 比较即可
public final boolean equals(Object other) {
return this==other;
}
// 不能被克隆
/**
* Throws CloneNotSupportedException. This guarantees that enums
* are never cloned, which is necessary to preserve their "singleton"
* status.
*
* @return (never returns)
*/
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
// 根据其定义顺序比较大小
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
反射
java.lang.reflect包下提供了丰富的关于反射的工具集,可以动态操纵Java代码结构,反射多用于工具类和框架中
在代码中反射的基础是先获取Java类的字节码对象及Class对象,对应的类便是Class类,获取一个类Class对象有三种方式
/**
* 获取Class对象的三种方式
*
* @throws ClassNotFoundException
*/
@Test
public void fun1() throws ClassNotFoundException {
// 使用Class类的静态方法 参数为 类的全路径名称
// 该方法会抛出一个 ClassNotFoundException异常
Class<?> personClass1 = Class.forName("xyz.taoqz.chapter3.reflect.domain.Person");
System.out.println(personClass1); // class xyz.taoqz.chapter3.reflect.domain.Person
// 使用实例对象的getClass()方法,该方法继承自Object
Person person = new Person();
Class<? extends Person> personClass2 = person.getClass();
System.out.println(personClass2); // class xyz.taoqz.chapter3.reflect.domain.Person
// 每个类都会隐含有一个静态的 class属性
// 通过该方式获取类的Class对象很方便 不用创建对象也不会抛出异常
Class<Person> personClass3 = Person.class;
System.out.println(personClass3); // class xyz.taoqz.chapter3.reflect.domain.Person
// 获取接口的Class对象
Class<MyInterface> myInterfaceClass = MyInterface.class;
System.out.println(myInterfaceClass); //interface xyz.taoqz.chapter3.reflect.domain.MyInterface
// 生成Class对象时,会先判断内存中是否已经加载
// 获取的都是同一个Class对象,所以结果都为true
System.out.println(personClass1 == personClass2);
System.out.println(personClass2 == personClass3);
}
反射不仅可以获取类的Class对象,还可以获取基本数据类型和数组的Class对象,只不过有些特殊
/**
* 获取基本数据类型和数组的类对象
*/
@Test
public void demo() {
Class<Integer> integerClass = int.class;
// 基本数据类型和其包装类进行比较
System.out.println(integerClass == Integer.class); // false
System.out.println(Integer.TYPE == integerClass); // true
// 基本数据类型
System.out.println(double.class); // double
// 包装类数组类型
System.out.println(Double[].class.getName()); // [Ljava.lang.Double;
System.out.println(Integer[].class.getName()); // Ljava.lang.Integer;
// 基本数据类型数组的class对象的名称有点特殊
System.out.println(int[].class.getName()); // [I
System.out.println(double[].class.getName()); // [D
System.out.println(float[].class.getName()); // [F
// 二维数组
// 只有同一类型并且维度相同的数组,才会共享同一份字节码文件
String[][] strings = new String[2][];
Class c1 = strings.getClass();
String[] strs = new String[3];
String[] strs2 = new String[3];
Class c2 = strs.getClass();
Class c3 = strs2.getClass();
System.out.println(c1 == c2); // false
System.out.println(c2 == c3); // true
}
Integer.TYPE的JDK源码实现
/**
* The {@code Class} instance representing the primitive type
* {@code int}.
*
* @since JDK1.1
*/
@SuppressWarnings("unchecked")
public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
/*
* Return the Virtual Machine's Class object for the named
* primitive type.
*/
static native Class<?> getPrimitiveClass(String name);
除了Class表示类的对象外,还有其他类来表示类中的其他结构
Constructor : 构造方法
Parameter : 方法中的参数
Method : 成员方法
Field : 成员变量 (可以拿到包括使用静态和final修饰的成员变量)
由于参数没有修饰符的概念,所以排除它其他几种类都提供了XXX.getDeclaredXXXs()的方法,无视修饰符拿到类中所有对应的成员,如果需要运行,同样调用XXX.setAccessible(true)方法即可,而普通的获取方法只能获取使用public修饰的成员
多态时使用反射获取的是子类的Class对象,所以操作的结构也只是子类中的内容
/**
* Class对象中常用的方法
*/
@Test
public void getConstructors() throws Exception {
// 获取类的Class对象
Class<?> perClass = Class.forName("xyz.taoqz.chapter3.reflect.domain.Person");
// 创建实例
// 使用该方式创建实例时,该类中必须有一个默认的空参构造,因为该方法底层调用的也是类的空参构造,不然会报错 很重要!!!
Object obj = perClass.newInstance();
System.out.println(obj instanceof Person); // true
// 获取类中所有使用public修饰的构造方法
Constructor<?>[] constructors = perClass.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor.getName());
// 获取构造方法的参数 数组
Parameter[] parameters = constructor.getParameters();
// 获取每个参数的类型(如果是引用类型,打印其) 和名称(arg+参数在类中定义的顺序从0开始)
for (Parameter parameter : parameters) {
System.out.println(parameter.getType().getName());
// 默认调用toString()方法 会有 class 前缀
System.out.println(parameter.getType() + "==" + parameter.getName());
}
}
// 指定参数类型获取构造
Constructor<?> constructor = perClass.getConstructor(String.class, boolean.class, Integer.class, int.class);
// 通过构造方法创建对象
Object zs = constructor.newInstance("zs", true, 10, 20);
System.out.println(zs);
// 获取指定参数的构造 无视修饰符
Constructor<?> declaredConstructor = perClass.getDeclaredConstructor(String.class);
// 获取类中所有的构造方法 无视修饰符
// Constructor<?>[] declaredConstructors = perClass.getDeclaredConstructors();
// 如果要运行类中私有成员 需要使用该方法
declaredConstructor.setAccessible(true);
Object lisi = declaredConstructor.newInstance("lisi");
System.out.println(lisi);
}
@Test
public void getFields() throws Exception {
Class<?> perClass = Class.forName("xyz.taoqz.chapter3.reflect.domain.Person");
Object obj = perClass.newInstance();
// 获取类中使用public修饰的成员变量
Field[] fields = perClass.getFields();
System.out.println(fields.length);
for (Field field : fields) {
System.out.println(field.getName());
}
// 获取指定的变量,并赋值到对象中
Field count = perClass.getField("count");
count.set(obj,20);
System.out.println(obj);
// 获取所有非public修饰的成员变量
Field[] declaredFields = perClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField.getName());
}
Field name = perClass.getDeclaredField("name");
name.setAccessible(true);
name.set(obj,"taoqz");
System.out.println(obj);
// 获取对象中指定的字段值
Class<Person> personClass = Person.class;
Person person = personClass.newInstance();
person.setCount(10);
Field age = personClass.getDeclaredField("count");
age.setAccessible(true);
int anInt = age.getInt(person);
System.out.println(anInt);
}
@Test
public void getMethods() throws Exception {
Class<?> perClass = Class.forName("xyz.taoqz.chapter3.reflect.domain.Person");
Object obj = perClass.newInstance();
// 获取本类中所有方法,也就是不会获取父类中的方法
Method[] declaredMethods = perClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod); // public boolean xyz.taoqz.chapter3.reflect.domain.Person.equals(java.lang.Object)
System.out.println(declaredMethod.getName()); // equals
System.out.println(declaredMethod.getModifiers()); // 获取方法的修饰符对应的数值
System.out.println(Modifier.toString(declaredMethod.getModifiers())); // 打印修饰符
System.out.println(declaredMethod.getReturnType().getName()); // 获取方法返回值类型
System.out.println("========================================");
}
// Constructor<?> constructor = perClass.getConstructor(String.class, Integer.class, Integer.class);
Constructor<?> constructor = perClass.getConstructor(String.class, boolean.class, Integer.class, int.class);
Object instance = constructor.newInstance("张三", true, 10, 1);
System.out.println(instance instanceof Person);
System.out.println(instance);
// 获取类中指定方法并执行
Method print = perClass.getMethod("print", null);
Object result = print.invoke(instance, null);
System.out.println(result);
System.out.println(instance);
Method sMethod = perClass.getMethod("sMethod", String.class);
Object result2 = sMethod.invoke(instance, "李四");
System.out.println(result2);
// Method print1 = perClass.getMethod("print", String.class);
Method print1 = perClass.getDeclaredMethod("print", String.class);
print1.setAccessible(true);
Object zz = print1.invoke(instance, "123");
System.out.println(zz);
}
继承的注意事项
将公共操作和域放在超类中
尽量不要使用protected,因为其并不能很好的保护封装性,因为子类也是可以无限派生的
继承和抽象的区别
比如动物都有吃的方法,而每种动物的吃法不同,如果使用普通的类进行继承,那么方法内写什么都不太合适,所以干脆不要方法体,任由子类发挥