继承
作用
- 提高了代码的复用性。
 - 让类与类之间产生了关系。有了这个关系,才有了多态的特性。
 
代码示范
父类代码
public class Parent {
    public void say() {
        System.out.println("父类的say方法");
    }
}
 
子类代码,继承父类,也就拥有了say方法
public class Son extends Parent {
}
 
测试代码
public class Main {
    public static void main(String[] args) {
        Son son=new Son();
        son.say();
        /**
         * 输出结果
         * 父类的say方法
         */
    }
}
 
注意事项
- 不要为了某些功能而继承,继承之间应该是is a关系。
 - Java只支持单继承
 - Java支持多层级继承,例如3继承2,2继承1,那么3就拥有1和2的方法。
 
this和super关键字
简介
this代表调用该方法的引用,而super则代表父类对象的引用。当我们在类内部要使用自己的方法时可以使用this,要调用父类方法时,可以使用super。
this和super使用实例
父类的方法
public class Parent {
    public void say() {
        System.out.println("父类的say方法");
    }
}
 
子类方法
public class Son extends Parent {
    public void say2(){
        super.say();
        System.out.println("父类say完子类say");
    }
}
 
测试输出结果
public class Main {
    public static void main(String[] args) {
        Son son=new Son();
        son.say2();
        /**
         * 输出结果
         * 父类的say方法
         * 父类say完子类say
         */
    }
}
 
继承工作原理解析
代码示例
可以看到父类的代码如下所示,设置一个num为3
public class Parent {
    private int num=3;
    public int getNum() {
        return num;
    }
    public void setNum(int num) {
        this.num = num;
    }
}
 
子类继承父类,num设置为4
public class Son extends Parent {
    private int num = 4;
    public void show() {
        System.out.println(this.num);
    }
}
 
输出结果,可以看到输出的是子类的值,当然如果show中用的是super.getNum(),输出结果就为3,那么jvm是如何工作的呢?
public class Main {
    public static void main(String[] args) {
        Son son=new Son();
        son.show();
        /**
         * 输出结果
         * 4
         */
    }
}
 
图解子父类初始化过程
- 方法区的非静态区加载父类和子类的信息
 - 实例化son类时,堆区开辟一个空间
 - 若son无num,super和this指向的都是父类的num,反之只有thi指向自己的num
 

重写
概述
子类编写一个方法参数和名字都和父类一样时,就会执行子类重写的代码,并且我们需要使用@Override关键字注明这个方法是重写的方法。
代码示例
父类的代码示例
public class Parent {
    public List getList() {
        return Collections.emptyList();
    }
}
 
子类的代码示例
public class Son extends Parent {
    @Override
    public List getList() {
        return super.getList();
    }
}
 
注意事项
-  
子类重写父类方法是必须遵循两小一大原则。
1. 访问权限大于父类 2. 抛出异常小于父类 3. 返回值小于父类 
代码如下所示,可以看到子类抛出的异常以及返回值都小于父类
父类
public class Parent {
    public List getList() throws Exception {
        return Collections.emptyList();
    }
}
 
子类
public class Son extends Parent {
    @Override
    public ArrayList getList()throws IllegalAccessException {
        return new ArrayList();
    }
}
 
- 重写时希望保留父类的逻辑可以在代码首行用代码覆盖一下。
 
public class Parent {
    public void say(){
        System.out.println("父类的say方法");
    }
}
 
如下所示子类基于父类的方法实现了自己的特定逻辑
public class Son extends Parent {
    @Override
    public void say() {
        super.say();
        System.out.println("子类的say方法");
    }
}
 
子父类中的构造函数
- 子类对象进行初始化时,会隐式的调用父类的无参构造方法。
 - 如果你要显示调用父类构造方法一定要在第一行声明,否则你做的所有操作都有可能会被父类构造方法覆盖。
 
代码示例如下所示
父类的代码
public class Parent {
    public Parent() {
        System.out.println("父类的构造方法");
    }
    public void say(){
        System.out.println("父类的say方法");
    }
}
 
子类的代码
public class Son extends Parent {
    @Override
    public void say() {
        super.say();
        System.out.println("子类的say方法");
    }
}
 
输出结果如下所示,可以看到父类构造函数的方法的输出结果输出了
public class Main {
    public static void main(String[] args) {
        Son son=new Son();
        son.say();
        /**
         * 输出结果
         * 父类的构造方法
         * 父类的say方法
         * 子类的say方法
         */
    }
}
 
final关键字
关键字简介
- final关键字可以修饰类、方法、变量
 - 修饰类,则这个类不可被继承
 - 修饰方法则这个方法不可便重写
 - 修饰变量,若为引用类型则该引用指向地址不可被修改,但是引用可以被修改。若基础类型则值不可修改。而且这个关键字可以修饰成员变量和局部变量
 
代码示例
public final class Parent {
    private final int a = 3;
    public Parent() {
        final int c=2;
        System.out.println("父类的构造方法");
    }
    public final void say() {
        System.out.println("父类的say方法");
    }
}
 
抽象类
使用场景
当多个类中出现相同功能,但是功能主体不同,这是可以进行向上抽取。这时,只抽取功能定义,而不抽取功能主体。
抽象类特点
- 抽象方法一定在抽象类中。
 - 抽象方法和抽象类都必须被
abstract关键字修饰。 - 抽象类不可以用new创建对象。因为调用抽象方法没意义。
 - 抽象类中的抽象方法要被使用,必须由子类继承并实现所有的抽象方法后,才能建立子类对象调用。如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。
 
抽象类和一般类没有太大的不同。
该如何描述事物,就如何描述事物,只不过,该事物出现了一些暂时不知道如何实现或者实现方式不一定。
 这些不确定的部分,也是该事物的功能,需要明确出现。但是无法定义主体。这时,通过抽象方法来表示最为合适。
抽象类常见问题
abstract 关键字,和哪些关键字不能共存?
- final:被final修饰的类不能有子类。而被abstract修饰的类一定是一个父类。
 - private: 抽象类中的私有的抽象方法,不被子类所知,就无法被复写。而抽象方法出现的就是需要被复写。
 - static:如果static可以修饰抽象方法,那么连对象都省了,直接类名调用就可以了。可是抽象方法运行没意义。
 
抽象类常见使用场景——模板方法模式
抽象类常常作为模板方法的实施方案,以下便是笔者之前写过关于模板方法的文章,感兴趣的可以看看
设计模式-模板方法
接口
格式特点:
- 接口中常见定义:常量,抽象方法。
 - 接口中的成员都有固定修饰符。
常量:public static final
方法:public abstract
记住:接口中的成员都是public的。 
接口和抽象类有什么共同点和区别?
共同点
- 接口和抽象类都可以包含抽象方法和默认方法
 - 都不能被实例化
 
不同点
- 接口的变量必须是public static final修饰,抽象类可以默认不赋初值
 - 类可以继承多接口,抽象类只能继承一个
 - 接口常用于定义行为,继承该接口的类就会拥有某些行为,而抽象类则是抽取共性提供抽象方法给子类实现
 
多态概述
什么是多态
多态即事物可以有多种存在形态,如:猫可以是猫类也可以是动物类
代码示例
Cat cat=new Cat();
Animal animal=new Cat();
 
多态的体现
父类引用指向子类对象,通俗来说就是父类引用可以指向自己的对象
public void static main(String[] args){
function(new Cat());//会输出cat的eat方法而不是Animal的
function(new Dog());//会输出dog的eat方法而不是Animal的
}
public static void function(Animal a)//Animal a = new Cat();
	{
		a.eat();
	}
 
多态的好处
提高了程序的扩展性,使得同一个函数得以复用,方便后续开发的扩展。
使用多态的前提
- 必须是类与类之间有继承(extends)或者实现关系(写一个接口让另一个类implement)
 - 存在对父类的覆盖
 
多态的弊端
只能使用父类引用访问父类成员,即cat继承Animal类并且覆盖Animal的eat或者相关方法。
多态的转型
Animal a=new Cat();//父类指向子类 向下转型
if(a instanceof Cat )//判断是否该animal类是否可以转为cat
Cat c=(Cat)a;//强转为cat 向下转型
 
多态的常见面试题
成员函数在多态调用输出结果是什么?
答:编译看左边,运行看右边
在多态中成员函数的特点:
 在编译时期:参阅引用型变量所属的类中是否有调用的方法。如果有,编译通过,如果没有编译失败。
 在运行时期:参阅对象所属的类中是否有调用的方法。
父类
public class Parent {
    public void func() {
        System.out.println("Parent");
    }
}
 
子类
public class Son extends Parent {
    @Override
    public void func() {
        System.out.println("Son");
    }
}
 
输出结果可以看到,编译看左边,输出结果完全看右边
public class Main {
    public static void main(String[] args) {
        Parent p = new Son();
        p.func();
        /**
         * 输出结果
         * Son
         */
    }
}
 
成员变量编译和结果取决于什么?
成员变量编译和结果全都取决于引用类型,即左边声明的类名,代码如下所示
父类
public class Parent {
    public  int num=1;
    public void func() {
        System.out.println("Parent");
    }
}
 
子类
public class Son extends Parent {
    public  int num=2;
    @Override
    public void func() {
        System.out.println("Son");
    }
}
 
可以看到输出解决是父类的数据
public class Main {
    public static void main(String[] args) {
        Parent p = new Son();
        System.out.println(p.num);
        /**
         * 输出结果
         * 1
         */
    }
}
 
静态函数编译运行结果取决于什么?
全看左边的引用类型,引用类型是什么就输出什么
class Fu
{
	
	static void method4()
	{
		System.out.println("fu method_4");
	}
}
class Zi extends Fu
{
	
	static void method4()
	{
		System.out.println("zi method_4");
	}
}
class  Test
{
	public static void main(String[] args) 
	{
		
		Fu f = new Zi();
		f.method4();//看该对象引用类型 所以输出fu method_4
		Zi z = new Zi();
		z.method4();//看该对象引用类型 所以输出zi method_4
	}
}
 
图解代码运行原理
如下图,静态区加载的是类信息,而不是this和spuer关键字,而且静态方法也不可以被重写所以,所以运行结果就根据左边的引用类型决定

object类
概述
Object:是所有对象的直接后者间接父类,传说中的上帝。
 该类中定义的肯定是所有对象都具备的功能。
equals和toString
equals方法
比较的是对象的地址空间是否相同
toString方法
输出的是对象的类型+“@”+内存地址的十六进制
 Object类中已经提供了对对象是否相同的比较方法。
如果自定义类中也有比较相同的功能,没有必要重新定义。
 只要沿袭父类中的功能,建立自己特有比较内容即可。这就是覆盖。
代码
class Demo //extends Object
{
	private int num;
	Demo(int num)
	{
		this.num = num;
	}
	
	//覆盖原有的equals
	public boolean equals(Object obj)//Object obj = new Demo();
	{
		if(!(obj instanceof Demo))
			return false;
		Demo d = (Demo)obj;
		return this.num == d.num;
	}
	
	//覆盖原有的toString
	public String toString()
	{
		return "demo:"+num;
	}
}
class Person 
{
}
class ObjectDemo 
{
	public static void main(String[] args) 
	{
		Demo d1 = new Demo(4);
		System.out.println(d1);//输出语句打印对象时,会自动调用对象的toString方法。打印对象的字符串表现形式。
		Demo d2 = new Demo(7);
		System.out.println(d2.toString());//打印的是d2.getName()+"@@"+Integer.toHexString(d2.hashCode()
	}
}
 
创建和销毁对象注意事项
用静态工程替代构造器
遇到多构造器参数建议使用建造者模式
 优点:
 1. 保证类的不可变
 2. 实现的可变参数
 3. 增加可读性
 
/**
 * 遇到多构造器参数建议使用建造者模式
 * 优点:
 * 1. 保证类的不可变
 * 2. 实现的可变参数
 * 3. 增加可读性
 */
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;
    public static class Builder {
        private int servingSize;
        private int servings;
        //        可选参数,赋值上默认值
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbohydrate = 0;
        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }
        public Builder calories(int val) {
            calories = val;
            return this;
        }
        public Builder fat(int val) {
            fat = val;
            return this;
        }
        public Builder sodium(int val) {
            sodium = val;
            return this;
        }
        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }
        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }
    @Override
    public String toString() {
        return "NutritionFacts{" +
                "servingSize=" + servingSize +
                ", servings=" + servings +
                ", calories=" + calories +
                ", fat=" + fat +
                ", sodium=" + sodium +
                ", carbohydrate=" + carbohydrate +
                '}';
    }
    public NutritionFacts(Builder builder) {
        this.servingSize = builder.servingSize;
        this.servings = builder.servings;
        this.calories = builder.calories;
        this.fat = builder.fat;
        this.sodium = builder.sodium;
        this.carbohydrate = builder.carbohydrate;
    }
    
}
 
对象创建示例
public static void main(String[] args) {
        NutritionFacts nutritionFacts = new NutritionFacts.Builder(240, 8).calories(300).sodium(50).build();
        System.out.println(nutritionFacts.toString());//NutritionFacts{servingSize=240, servings=8, calories=300, fat=0, sodium=50, carbohydrate=0}
    }
 
避免创建不必要的对象
频繁创建对象会增加GC回收压力以及系统开销,所以我们应该避免创建不必要的对象
下面这段代码,从源码我们就可以看到matches方法会创建一个Pattern,所以我们可以对正则匹配的方法进行响应重构
 // Performance can be greatly improved! (Page 22)
    static boolean isRomanNumeralSlow(String s) {
        /**
         * 底层实际会创建一个Pattern 频繁调用会创建多个Pattern对象
         *  Pattern p = Pattern.compile(regex);
         *         Matcher m = p.matcher(input);
         *         return m.matches();
         */
        return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
                + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
    }
 
所以我们建议基于matches底层调用方式自己手写一个match方法避免没必要的对象创建
 /**
     * 手动创建pattern避免频繁创建对象
     */
    private static final Pattern pattern = Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})"
            + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
    static boolean isRomanNumeralFast(String s) {
        return pattern.matcher(s).matches();
    }
 
消除过期的对象引用
如下所示,Stack 的elements成员变量是一个数组,当我们调用pop逻辑上是将栈顶元素弹出,实际上jvm是无法感知这种逻辑弹出的,所以我们需要手动消除这个对象引用,否则会出现OOM异常
public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    public Stack() {
        //引用指向数组,我们所谓的size以及活动非活动元素对于虚拟机来说都是无感的
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }
    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }
    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object element = elements[--size];
        //消除过期引用
        elements[--size] = null;
        return element;
    }
    /**
     * Ensure space for at least one more element, roughly
     * doubling the capacity each time the array needs to grow.
     */
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
    public static void main(String[] args) {
        Stack stack = new Stack();
        for (String arg : args)
            stack.push(arg);
        while (true)
            System.err.println(stack.pop());
    }
}
 
try-with-resource优先于try-finally
使用try-with-resource相较于后者更加简洁、清晰、产生的异常信息也更有价值
public class Copy {
    private static final int BUFFER_SIZE = 8 * 1024;
    // try-finally is ugly when used with more than one resource! (Page 34)
    static void copy(String src, String dst) throws IOException {
        InputStream in = new FileInputStream(src);
        try {
            OutputStream out = new FileOutputStream(dst);
            try {
                byte[] buf = new byte[BUFFER_SIZE];
                int n;
                while ((n = in.read(buf)) >= 0)
                    out.write(buf, 0, n);
            } finally {
                out.close();
            }
        } finally {
            in.close();
        }
    }
    static void copyWithTryResource(String src, String dst) throws IOException {
        try (InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst)) {
            byte[] buf = new byte[BUFFER_SIZE];
            int n;
            while ((n = in.read(buf)) >= 0)
                out.write(buf, 0, n);
        }
    }
    public static void main(String[] args) throws Exception{
        copy("D:\\source.txt","dst.txt");
        copyWithTryResource("D:\\source.txt","dst2.txt");
    }
}
 
参考文献
Java基础常见面试题总结(中)
Effective Java中文版(第3版)
Java程序性能优化



















