5. OO中的一些基本概念
继承
父类的非private方法自动被子类继承
class student extends person{ void HA(){ super.HA(); System.out.println("My HA"); } }
super:访问父类中的成员
this:当前对象(子类)中的成员
子类在构造方法中可以用super调用父类中的构造方法
接口 interface
接口是对类的进一步抽象,用于定义某些类共同的方法/变量。接口的所有方法都自动是public abstract
接口可以被多种类继承。
同一个接口可以有多种不同的实现。
类似C++的纯虚函数
interface CL{ void ADD(int A); } class CCL implemented CL{ public void ADD(int A){ .... } } public static void main(){ CL cls=new CCL(); cls.ADD(5); }
接口中可以定义常量,默认是public static final,值不可修改。
eg:List是ArrayList和LinkedList的接口
多态(polymorphism)
多态指一个程序中相同名字表示不同含义的情况。
multiple implementation of the same interface
definition of the reference could point to different types of implementations(for code reuse)
1. 编译时多态:重载(overload),编译时就能决定
2. 运行时多态:重写(override),是动态绑定的(虚方法调用),运行时才决定(系统根据调用该方法的实例的类型来决定,可能运行子类中的方法)
instance of:判断某变量类型
java中默认的方法都是虚方法,但有例外:
- static方法:与实例类型无关,只与声明时的类型有关
- private方法:子类看不见,无法虚化
- final方法:子类无法覆盖
虚方法:
class Shape{ void draw(){ println("S"); } } class Circle extends Shape{ void draw(){ p("C"); } } class Triangle extends Shape{ void draw(){ p("L"); } }
class TestVirtualInvoke{ static void doStuff( Shape s ){ s.draw(); } public static void main( String [] args ){ Circle c = new Circle();
doStuff(c); //C Triangle t = new Triangle();
doStuff(t); //L } }
再来看个例子:
class Shape{ static void draw(){ println("S"); } } class Circle extends Shape{ static void draw(){ println("C"); } } class Triangle extends Shape{ static void draw(){ println("L"); } }
class TestStaticInvoke{ static void doStuff( Shape s ){ s.draw(); } public static void main( String [] args ){ Circle c = new Circle(); doStuff(c); //S Triangle t = new Triangle(); doStuff(t); //S Shape s = new Circle(); doStuff(s); //S s.draw(); //S //dostuff()的参数中声明的是Shape Circle c2 = new Circle(); c2.draw(); //C //直接draw,draw中声明的是Circle } }
内部类
在类中再定义一个类
使用:外部对象名.内部类名
static内部类:表示实际上是个外部类
局部类
方法中定义类
局部类不能访问所在方法的非final的局部变量
class CA{ private int s = 111; class CB { //内部类 private int s = 222; public void mb(int s) { println(s); //333 局部变量s println(this.s); //222 内部类对象的属性s println(CA.this.s); //111 外层类对象属性s } } public Object makeTheInner(int localVar) { final int fl = localVar; return new Object() { //局部类 //Object: 用父类的类名,定义时即创建实例 public String toString() { return (s+" "+fl); } //111 333 }; } } class Solution{ public static void main(String args[]){ CA aa = new CA(); CA.CB bb = aa.new CB(); bb.mb(333); Object cc=new CA().makeTheInner(333); } }
匿名类
一种特殊的内部类。没有类名,定义时即生成一个实例,一次性使用。
不能定义构造方法,直接用父类的构造方法
eg:排序中的自定义比较函数
Book[] books = new Book[10]; Arrays.<Book>sort(books, new Comparator<Book>(){ public int compare(Book b1, Book b2){ return b1.getPrice()-b2.getPrice(); } });
泛型
对于不同类采用相同的处理方法. 例如 Vector<String> vs=new Vector<String> ();
public class Box<T> { // T stands for "Type" private T t; public void set(T t) { this.t = t; } public T get() { return t; } }
两个复杂一点的例子:
class GenericTreeClass { public static void main(String[] args){ TNode<String> t = new TNode<>("Roo"); t.add("Left"); t.add("Right"); t.getChild(0).add("aaa"); t.traverse(); } } class TNode<T>{ //T:类型参数,可以表示任意类型 T val; ArrayList<TNode<T>> ch=new ArrayList<>(); TNode(T v){ this.val = v; } public T getValue(){ return this.val; } public void add(T v) { TNode<T> child = new TNode<>(v); this.ch.add(child); } public TNode<T> getChild(int i) { if ((i<0)||(i>this.ch.size())) return null; return (TNode<T>)this.ch.get(i); } public void traverse() { System.out.println(this.val); for (TNode child : this.ch) child.traverse(); } } class GenericMethod { public static void main(String[] args){ Date date = BU.<Date>gi("java.util.Date"); System.out.println(date); } } class BU{ //<T>: 类型参数 T: 返回类型 public static <T> T gi( String cn ){ try { Class c = Class.forName(cn); return (T) c.newInstance(); } catch (ClassNotFoundException ex){} catch (InstantiationException ex){} catch (IllegalAccessException ex){} return null; } }
反射(reflection):
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性
interface Servlet{ void init(); void service( String params, byte [] requestBuffer, OutputStream output); } ........ if(servlet==null){ servlet=(Servlet)Class.forName(srvName).newInstance(); //对不同srvName自动识别类型,并创建实例servlet servlet.init(); } servlet.service(params, requestBuffer, socket.getOutputStream());
每个类都有一个 Class 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。
类加载相当于 Class 对象的加载,类在第一次使用时才动态加载到 JVM 中。也可以使用 Class.forName("com.mysql.jdbc.Driver")
这种方式来控制类的加载,该方法会返回一个 Class 对象。
反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。
Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库主要包含了以下三个类:
- Field :可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段;
- Method :可以使用 invoke() 方法调用与 Method 对象关联的方法;
- Constructor :可以用 Constructor 的 newInstance() 创建新的对象。
反射的优点:
- 可扩展性 :应用程序可以利用全限定名创建可扩展对象的实例,来使用来自外部的用户自定义类。
- 类浏览器和可视化开发环境 :一个类浏览器需要可以枚举类的成员。可视化开发环境(如 IDE)可以从利用反射中可用的类型信息中受益,以帮助程序员编写正确的代码。
- 调试器和测试工具 : 调试器需要能够检查一个类里的私有成员。测试工具可以利用反射来自动地调用类里定义的可被发现的 API 定义,以确保一组测试中有较高的代码覆盖率。
反射的缺点:
尽管反射非常强大,但也不能滥用。如果一个功能可以不用反射完成,那么最好就不用。在我们使用反射技术时,下面几条内容应该牢记于心。
-
性能开销 :反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。
-
安全限制 :使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。
-
内部暴露 :由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
6. OO中的关键字
public / private / protected
Java 中有三个访问权限修饰符:private、protected 以及 public,如果不加访问修饰符,表示包级可见。
可以对类或类中的成员(字段以及方法)加上访问修饰符。
- 类可见表示其它类可以用这个类创建实例对象。
- 成员可见表示其它类可以用这个类的实例对象访问到该成员;
protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。
设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。
如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。
字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。例如下面的例子中,AccessExample 拥有 id 公有字段,如果在某个时刻,我们想要使用 int 存储 id 字段,那么就需要修改所有的客户端代码。
public class AccessExample {
public String id;
}
可以使用公有的 getter 和 setter 方法来替换公有字段,这样的话就可以控制对字段的修改行为。
public class AccessExample {
private int id;
public String getId() {
return id + "";
}
public void setId(String id) {
this.id = Integer.valueOf(id);
}
}
但是也有例外,如果是包级私有的类或者私有的嵌套类,那么直接暴露成员不会有特别大的影响。
public class AccessWithInnerClassExample {
private class InnerClass {
int x;
}
private InnerClass innerClass;
public AccessWithInnerClassExample() {
innerClass = new InnerClass();
}
public int getValue() {
return innerClass.x; // 直接访问
}
}
abstract
抽象类和抽象方法都使用 abstract 关键字进行声明。如果一个类中包含抽象方法,那么这个类必须声明为抽象类。抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类。
抽象类:不可被实例化(instantiated)
抽象方法:只声明不实现,但在子类中必须被实现。 abstract int Add(int a, int b);
只有抽象类可以包含抽象方法。抽象类也可以有构造方法
关于abstract还可以参考 https://www.cnblogs.com/pdev/p/11188301.html
public abstract class AbstractClassExample {
protected int x;
private int y;
public abstract void func1();
public void func2() {
System.out.println("func2");
}
}
public class AbstractExtendClassExample extends AbstractClassExample {
@Override
public void func1() {
System.out.println("func1");
}
}
// AbstractClassExample ac1 = new AbstractClassExample(); // 'AbstractClassExample' is abstract; cannot be instantiated
AbstractClassExample ac2 = new AbstractExtendClassExample();
ac2.func1();
Interface
接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。
从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。
接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。
接口的字段默认都是 static 和 final 的。
public interface InterfaceExample {
void func1();
default void func2(){
System.out.println("func2");
}
int x = 123;
// int y; // Variable 'y' might not have been initialized
public int z = 0; // Modifier 'public' is redundant for interface fields
// private int k = 0; // Modifier 'private' not allowed here
// protected int l = 0; // Modifier 'protected' not allowed here
// private void fun3(); // Modifier 'private' not allowed here
}
public class InterfaceImplementExample implements InterfaceExample {
@Override
public void func1() {
System.out.println("func1");
}
}
// InterfaceExample ie1 = new InterfaceExample(); // 'InterfaceExample' is abstract; cannot be instantiated
InterfaceExample ie2 = new InterfaceImplementExample();
ie2.func1();
System.out.println(InterfaceExample.x);
抽象类和接口的比较
- 从设计层面上看,抽象类提供了一种 IS-A 关系,那么就必须满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。
- 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
- 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
- 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。
使用接口:
- 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法;
- 需要使用多重继承。
使用抽象类:
- 需要在几个相关的类中共享代码。
- 需要能控制继承来的成员的访问权限,而不是都为 public。
- 需要继承非静态和非常量字段。
在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。
overload / overwrite / override
overload:重载 同名方法的参数个数/类型不同,返回类型可以不同。通过重载来实现多态(polymorphism)
存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。应该注意的是,返回值不同,其它都相同不算是重载。
overwrite:重写 子类中定义与父类方法名、返回值类型、参数表都完全相同的方法
override:覆盖 子类中修改父类中的同名方法(同overwrite)。可以加@override标签注释一下
重写存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。
为了满足里式替换原则,重写有以下三个限制:
- 子类方法的访问权限必须大于等于父类方法;
- 子类方法的返回类型必须是父类方法返回类型或为其子类型。
- 子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型。
使用 @Override 注解,可以让编译器帮忙检查是否满足上面的三个限制条件。
下面的示例中,SubClass 为 SuperClass 的子类,SubClass 重写了 SuperClass 的 func() 方法。其中:
- 子类方法访问权限为 public,大于父类的 protected。
- 子类的返回类型为 ArrayList,是父类返回类型 List 的子类。
- 子类抛出的异常类型为 Exception,是父类抛出异常 Throwable 的子类。
- 子类重写方法使用 @Override 注解,从而让编译器自动检查是否满足限制条件。
class SuperClass {
protected List<Integer> func() throws Throwable {
return new ArrayList<>();
}
}
class SubClass extends SuperClass {
@Override
public ArrayList<Integer> func() throws Exception {
return new ArrayList<>();
}
}
在调用一个方法时,先从本类中查找看是否有对应的方法,如果没有查找到再到父类中查看,看是否有继承来的方法。否则就要对参数进行转型,转成父类之后看是否有对应的方法。总的来说,方法调用的优先级为:
- this.func(this)
- super.func(this)
- this.func(super)
- super.func(super)
/*
A
|
B
|
C
|
D
*/
class A {
public void show(A obj) {
System.out.println("A.show(A)");
}
public void show(C obj) {
System.out.println("A.show(C)");
}
}
class B extends A {
@Override
public void show(A obj) {
System.out.println("B.show(A)");
}
}
class C extends B {
}
class D extends C {
}
public static void main(String[] args) {
A a = new A();
B b = new B();
C c = new C();
D d = new D();
// 在 A 中存在 show(A obj),直接调用
a.show(a); // A.show(A)
// 在 A 中不存在 show(B obj),将 B 转型成其父类 A
a.show(b); // A.show(A)
// 在 B 中存在从 A 继承来的 show(C obj),直接调用
b.show(c); // A.show(C)
// 在 B 中不存在 show(D obj),但是存在从 A 继承来的 show(C obj),将 D 转型成其父类 C
b.show(d); // A.show(C)
// 引用的还是 B 对象,所以 ba 和 b 的调用结果一样
A ba = new B();
ba.show(c); // A.show(C)
ba.show(d); // A.show(C)
}
super
- 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。
- 访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
public class SuperExample {
protected int x;
protected int y;
public SuperExample(int x, int y) {
this.x = x;
this.y = y;
}
public void func() {
System.out.println("SuperExample.func()");
}
}
public class SuperExtendExample extends SuperExample {
private int z;
public SuperExtendExample(int x, int y, int z) {
super(x, y);
this.z = z;
}
@Override
public void func() {
super.func();
System.out.println("SuperExtendExample.func()");
}
}
SuperExample e = new SuperExtendExample(1, 2, 3);
e.func();
SuperExample.func()
SuperExtendExample.func()