目录
1.继承必须使用extends关键字
2.父类中非私有的属性和方法可以被子类继承
3.构造方法不能被继承,只能通过super关键字去调用
4.调用构造方法并不会创建对象,但是会初始化数据
创建对象的方式1.new关键字 + 构造方法
创建对象的方式2.classloader
创建对象的方式3.反射
那么大家晚上好,我是今天晚上的主讲老师,我是兔哥。
Java的三大特性,继承、封装和多态。
今天,咱就来聊聊继承。虽然在Java内卷的今天,继承不一定会在面试中问到,但是,这个知识点是我们平时开发代码的重中之重。
因此,掌握Java继承非常有必要。
1.继承必须使用extends关键字
extends关键字代表我们可以继承一个父类,也叫超类。
比如,我有一个A类。
public class A { private String name = "A"; }
在新建一个B类,去继承A类
public class B extends A{ }
那么我们就说B类是A类的子类。Java不允许多继承,只允许单继承,但是可以多层继承。
什么是多层继承,就是我在新建一个C类,继承B类
public class C extends B{ }
2.父类中非私有的属性和方法可以被子类继承
我们一般会使用到的访问权限,就说public,protected和private三种。
A类有一个私有的name,子类无法访问,只能自己访问。
验证
如果我们换成public
public class A { public String name = "A"; }
都没有报错 , 验证成功!
不过,对于我们希望被子类继承的部分,可以用protected,意思是受保护的,允许传承给子类。
如果我们给name设置成protected,再看B和C类
一样不会报错。
但是,protected的属性,在其他包不能访问。(报错了)
Demo.java
public class Demo { public static void main(String[] args) { String name = new A().name; System.out.println(name); } }
上面的代码是会报错的,同时,它也不能在A类的同一个包的子包中被访问到。
只能在同一个包中,或者自己的子类中被访问。
如果Demo也是A的子类
public class Demo extends A{ public void show(){ System.out.println(super.name); } }
这是允许的,不会报错(哪怕不在一个包中),我们在B类和C类也能访问到name,但是需要用到super关键字。
让我们绕回来,总结下就是父类中非私有的属性和方法可以被子类继承!
如果不设置成public,会造成很多麻烦,其他包调用不到了!所以,父类一般就是给我们继承用的,而不是给你直接在其他地方调用的。
3.构造方法不能被继承,只能通过super关键字去调用
父类的构造方法是不能被继承的,但我们可以通过super关键字去调用。
当我们new一个B类对象,A类的构造器会优先调用。
当我们new一个C类对象,A类和B类的构造器会先后调用。
验证:
public class A { protected String name = "A"; public A(){ System.out.println("A"); } }
public class B extends A{ public B(){ System.out.println("B"); } }
public class C extends B{ public C(){ System.out.println("C"); } }
测试
public class Demo { public static void main(String[] args) { new C(); } }
结果
同样的,父类构造器必须是public或protected,如果用private,子类也无法使用
C类虽然编译不报错,但是运行一定报错。
父类构造器如果没有声明缺省的,却声明了有参构造器,则子类必须用super关键字调用父类的有参构造。
public class A { protected String name = "A"; protected A(String name){ System.out.println("A"); this.name = name; } }
B类报错
必须手动用super关键字调用一下父类的有参构造才行,哪怕你只是做做样子
public class B extends A{ public B(){ super(null); System.out.println("B"); } }
如果A类有多个构造器,但偏偏就是没有显式声明缺省构造
public class A { protected String name = "A"; protected int price = 100; protected A(String name){ System.out.println("A"); this.name = name; } protected A(String name,int price){ System.out.println("A"); this.name = name; this.price = price; } }
子类随便super一个父类的构造就行,但一定要有。
public class B extends A{ public B(){ super(null); System.out.println("B"); } }
这种也行
public class B extends A{ public B(){ super(null,1); System.out.println("B"); } }
看你具体的情况。
4.调用构造方法并不会创建对象,但是会初始化数据
我们举了这么多例子,不难发现,构造方法的作用只是初始化数据而已。
而我们创建对象是用new关键字配合构造方法的形式,所以我们可能会误认为调用构造方法就会创建对象了。
创建对象的方法,大体有三种:
创建对象的方式1.new关键字 + 构造方法
A a = new A("A",100);
创建对象的方式2.classloader
DiskClassLoader dcl = new DiskClassLoader("D:\\idea-workspace\\j2se\\out\\production\\j2se\\com\\javaxbfs\\bean"); /** 找到对应的class * */ Class<?> aClass = dcl.findClass("com.javaxbfs.bean.A"); /** 获取构造函数 * */ Constructor<?> constructor = aClass.getConstructor(String.class); /** 用构造函数创建对象 * */ Object o = constructor.newInstance("A"); Method show = aClass.getMethod("show"); show.invoke(o);
DiskClassLoader是我们的自定义类加载器,用来加载磁盘上另外的class。DiskClassLoader的代码附在最后。
创建对象的方式3.反射
其实上面的例子已经用到了反射。
可见,构造方法只是用来初始化数据的,并不会新建一个对象。
而且,构造方法在当前类中,可以在其他构造方法中通过this调用,在子类构造方法中,可以通过super关键字调用(也只能出现在子类的构造方法中)。并且,必须是在方法的第一行。
最后,说说继承的好处,最大的好处,自然是代码的复用,缺点则是增加了耦合。
附上DiskClassLoader的代码:
import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; public class DiskClassLoader extends ClassLoader { private String mLibPath; public DiskClassLoader(String path) { // TODO Auto-generated constructor stub mLibPath = path; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { // TODO Auto-generated method stub String fileName = getFileName(name); File file = new File(mLibPath,fileName); try { FileInputStream is = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len = 0; try { while ((len = is.read()) != -1) { bos.write(len); } } catch (IOException e) { e.printStackTrace(); } byte[] data = bos.toByteArray(); is.close(); bos.close(); return defineClass(name,data,0,data.length); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return super.findClass(name); } //获取要加载 的class文件名 private String getFileName(String name) { // TODO Auto-generated method stub int index = name.lastIndexOf('.'); if(index == -1){ return name+".class"; }else{ return name.substring(index+1)+".class"; } } }