封装
Java通过不同的访问修饰符(access specifier)来实现访问控制或隐藏实现
四种访问类型
-
public
任何类都能访问
-
protected
仅限继承关系或包内
-
包访问权限 package access(没有关键字)
不加修饰符的默认权限,包内访问权限
-
private
类内访问权限
是否可以打破这种限制?答案是可以的,通过反射获取某个class的对象然后通过.setAccessible(true)方法打破访问权限
package priv.wzb.javabase.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* @author Satsuki
* @time 2019/9/12 15:37
* @description:
* 应用反射API动态的操作
* 类名,属性,方法,构造器等
*/
public class Demo02 {
public static void main(String[] args) {
String path = "priv.wzb.javabase.reflection.RUser";
try {
Class<RUser> clazz = (Class<RUser>) Class.forName(path);
//通过反射API调用构造方法,构造对象
RUser u = clazz.newInstance();//其实是调用了user的无参构造方法
//记得重写类的无参构造器因为反射的应用会调用类的无参构造器生成对象
Constructor<RUser> declaredConstructor = clazz.getDeclaredConstructor(int.class, int.class, String.class);
RUser u2 = declaredConstructor.newInstance(1001, 17, "satsuki");
System.out.println(u2.toString());
//通过反射API调用普通方法
RUser u3 = clazz.newInstance();
System.out.println(u.hashCode()==u3.hashCode());
Method setUname = clazz.getDeclaredMethod("setUname", String.class);
setUname.invoke(u3,"stk3");
System.out.println(u3.getUname());
//通过反射API操作属性
RUser u4 = clazz.newInstance();
Field f = clazz.getDeclaredField("uname");
f.setAccessible(true);//这个属性不用做安全检查直接访问
f.set(u4,"stk4");
System.out.println(u4.getUname());
} catch (Exception e) {
e.printStackTrace();
}
}
}
继承
继承是代码复用的实现方式之一,但是会带来复杂的代码层次结构,在复用代码的时候优先考虑组合(Composition)而不是继承(Inheritance)
只有考虑要向上转型upcasting的时候才考虑使用继承的结构(重写父类方法,多态性,面向接口编程,隐藏具体实现,对外展示具有抽象意义(抽象的行为大致要做什么)的接口)
继承两种方式:
- 可通过extends关键字实现子类继承父类(抽象类/正常类)每个class只能extends一个父类
- 通过implements关键字实现接口(interface)
那么是否可以多extends呢?通过内部类可以在某种意义上实现多重extends
package priv.wzb.javabook.reuse;
/**
* @program: Design_Pattern
* @description:
* @author: wangzibai
* @create: 2020-09-05 14:32
**/
class A{
}
class B{
}
public class MulExtends extends A{
/**
* 内部类实现多extends
* 非静态的内部类在初始化时
* 会默认包含一个指向外部类的指针以调用外部类中的属性与方法
*/
class C extends B{
}
}
多态
多态有两种
-
一种是类意义上的多态
父子继承/实现接口 重写具有相同函数签名(类的函数数量,类型,顺序都一致)方法
-
一种是方法的多态
重写方法与访问修饰符无关,与参数列表的函数签名有关,函数签名不同就可以实现方法的多态,同一个方法名的不同具体行为
可以用接口/抽象类/父类接收具体的子类例如
List<Integer> list = new Arraylist<>();
在编译时,编译器不需要知道任何具体信息以进行正确的调用。所有对list方法的调用都是通过动态绑定进行的。
在运行时将基类引用与具体对象动态绑定调用具体子类对象的具体方法
然而有一些缺点,如果子类是扩展而引用中没有子类中扩展的方法那么就只能通过向下转型将父类转为子类
在Java中子类转父类是无风险的,只会丢失部分细节,所有用父类的地方都可以用子类来代替,这称为向上转型,而向下转型(父类转子类)是有风险的。
其实Java中的引用指向的是栈中的某个位置,primitive类型在栈中保存具体值,而对象则在栈中保存对象在堆中的地址。
父类引用如果指向的不是堆中子类对象而是父类对象,那么向下转型后也无法拥有子类对象的某些扩展方法实现调用这些方法就会报错。
向下转型失败报错ClassCastException
package priv.wzb.javabook.polymorphism;
import org.junit.Test;
import java.util.LinkedList;
import java.util.List;
/**
* @program: Design_Pattern
* @description:
* @author: wangzibai
* @create: 2020-09-05 14:26
**/
public class MTest1 {
@Test
public void casting(){
// 向上转型
// 用父类引用接子类对象,没问题
List<Integer> list = new LinkedList<>();
// 看似调用父类的add方法实则运行时具体找到子类对象调用子类方法的具体实现
// 也没问题
list.add(1);
// 报错,父类中没有getLast方法
// list.getLast
// 父类向下转型
// 由于指向子类对象所以没问题
Integer first = ((LinkedList<Integer>) list).getFirst();
System.out.println(first);
}
}
编写构造器有一条良好规范:做尽量少的事让对象进入良好状态。如果有可能的话,尽量不要调用类中的任何方法。在基类的构造器中能安全调用的只有基类的 final 方法(这也适用于可被看作是 final 的 private 方法)。这些方法不能被重写,因此不会产生意想不到的结果。你可能无法永远遵循这条规范,但应该朝着它努力。
使用继承表达行为的差异,使用属性表达状态的变化
使用继承可以重写方法或添加新方法表现行为差异
小结
面向对象编程OOP的各种特性总是为了简化代码而存在的。如何保证代码结构的完善与可读性是要考虑的,冗余的代码可能增加可读性,在某些程度上是否也可以被允许?
过早的思考代码结构会陷入泥潭,在思考复杂结构之前,你的代码是否已经足够复杂?需求驱动优化,不要过早优化,程序是增量的过程,正如人类学习。依赖各类实验与分析。
代码来源:https://github.com/luoziling/Design_Pattern