继承

​ 通过继承可以基于现有的类创建一个新的类,继承父类所有公开的成员还可添加新的成员变量和方法进行扩展

​ 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;

注意事项

image-20200608220134824

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类的缓存数值范围可以修改,其他类则不可以

image-20200609163733820

可以在编辑器中添加参数 -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或者多个,可变参数必须放在参数列表的最后,这是因为如果之后还有同类型的形参,那该参数将永远拿不到实参(即使不同类型也必须将其放在参数列表的最后)

image-20200608224406824

其本质和数组没有什么区别,在传参或者取值时都可以使用数组的方式进行操作

@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中的方法

image-20200609230134364

// 静态方法 返回包含全部枚举值的数组
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,因为其并不能很好的保护封装性,因为子类也是可以无限派生的

继承和抽象的区别

比如动物都有吃的方法,而每种动物的吃法不同,如果使用普通的类进行继承,那么方法内写什么都不太合适,所以干脆不要方法体,任由子类发挥

Copyright © TaoQZ 2019 all right reserved,powered by Gitbook作者联系方式:taoqingzhou@gmail.com 修订时间: 2024-11-19 17:25:43

results matching ""

    No results matching ""

    results matching ""

      No results matching ""