目录
1.继承必须使用extends关键字
2.父类中非私有的属性和方法可以被子类继承
3.构造方法不能被继承,只能通过super关键字去调用
4.调用构造方法并不会创建对象,但是会初始化数据
创建对象的方式1.new关键字 + 构造方法
创建对象的方式2.classloader
创建对象的方式3.反射
那么大家晚上好,我是今天晚上的主讲老师,我是兔哥。
Java的三大特性,继承、封装和多态。
今天,咱就来聊聊继承。虽然在Java内卷的今天,继承不一定会在面试中问到,但是,这个知识点是我们平时开发代码的重中之重。
因此,掌握Java继承非常有必要。
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{
}
我们一般会使用到的访问权限,就说public,protected和private三种。
A类有一个私有的name,子类无法访问,只能自己访问。
验证
如果我们换成public
public class A {
public String name = "A";
}
都没有报错 , 验证成功!
不过,对于我们希望被子类继承的部分,可以用protected,意思是受保护的,允许传承给子类。
一样不会报错。
但是,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,会造成很多麻烦,其他包调用不到了!所以,父类一般就是给我们继承用的,而不是给你直接在其他地方调用的。
父类的构造方法是不能被继承的,但我们可以通过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");
}
}
看你具体的情况。
我们举了这么多例子,不难发现,构造方法的作用只是初始化数据而已。
而我们创建对象是用new关键字配合构造方法的形式,所以我们可能会误认为调用构造方法就会创建对象了。
创建对象的方法,大体有三种:
A a = new A("A",100);
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的代码附在最后。
其实上面的例子已经用到了反射。
可见,构造方法只是用来初始化数据的,并不会新建一个对象。
而且,构造方法在当前类中,可以在其他构造方法中通过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";
}
}
}