java面向对象基础
抽象类
-
抽象类与接口都是实现面向抽象编程的一种手段。
-
如果一个
class
定义了方法,但没有具体执行代码,这个方法就是抽象方法,抽象方法用abstract
修饰。类也必须用abstract修饰。 -
-
面向抽象编程的本质就是:
-
上层代码只定义规范(例如:
abstract class Person
); -
不需要子类就可以实现业务逻辑(正常编译);
-
具体的业务逻辑由不同的子类实现,调用者并不关心。
-
public class Main {
public static void main(String[] args) {
Person p = new Student();//抽象类引用具体实现子类
p.run();
}
}
abstract class Person {
public abstract void run();
}
class Student extends Person {
接口
-
接口定义的所有方法都是public abstract的,不需要写出来。
-
接口不能定义字段
-
接口还可以继承另一个接口,使用extends关键字。
-
可以定义default方法。子类可以不实现default方法。
public class Main {
public static void main(String[] args) {
Person p = new Student("Xiao Ming");
p.run();
}
}
interface Person {
String getName();
default void run() {
System.out.println(getName() + " run");
}
}
class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
静态字段和方法
-
不推荐用
实例变量.静态字段/方法
去访问静态字段/方法,因为在Java程序中,实例对象并没有静态字段/方法。在代码中,实例对象能访问静态字段/方法只是因为编译器可以根据实例类型自动转换为类名.静态字段方法
来访问静态对象。 -
因为静态方法属于
class
而不属于实例,因此,静态方法内部,无法访问this
变量,也无法访问实例字段,它只能访问静态字段。 -
静态方法经常用于工具类。
-
但是,
interface
是可以有静态字段的,并且静态字段必须为final
类型:
public interface Person {
public static final int MALE = 1;
public static final int FEMALE = 2;
}
实际上,因为interface
的字段只能是public static final
类型,所以我们可以把这些修饰符都去掉。
包
-
要特别注意:包没有父子关系。java.util和java.util.zip是不同的包,两者没有任何继承关系。
import
导入别的包的类有三种方式:
-
直接写出完整类名
// Person.java
package ming;
public class Person {
public void run() {
mr.jun.Arrays arrays = new mr.jun.Arrays();
}
}
-
用
import
语句,导入小军的Arrays
,然后写简单类名:
// Person.java
package ming;
// 导入完整类名:
import mr.jun.Arrays;
public class Person {
public void run() {
Arrays arrays = new Arrays();
}
}
在写import
的时候,可以使用*
,表示把这个包下面的所有class
都导入进来(但不包括子包的class
)。一般不推荐这种写法,因为在导入了多个包后,很难看出Arrays
类属于哪个包。
-
还有一种
import static
的语法,它可以导入可以导入一个类的静态字段和静态方法:
package main;
// 导入System类的所有静态字段和静态方法:
import static java.lang.System.*;
public class Main {
public static void main(String[] args) {
// 相当于调用System.out.println(…)
out.println("Hello, world!");
}
}
import static
很少使用。
Java编译器最终编译出的.class
文件只使用完整类名,因此,在代码中,当编译器遇到一个class
名称时:
-
如果是完整类名,就直接根据完整类名查找这个
class
; -
如果是简单类名,按下面的顺序依次查找:
-
查找当前
package
是否存在这个class
; -
查找
import
的包是否包含这个class
; -
查找
java.lang
包是否包含这个class
。
-
因此,编写class的时候,编译器会自动帮我们做两个import动作:
-
默认自动
import
当前package
的其他class
; -
默认自动
import java.lang.*
。
访问权限
-
类的修饰符只有public和package两种。要么可以获取这个类的实例要么只能是同包的类才能获取这个类的实例。
-
对类、字段以及方法能不能获取到有三种场景:能不能new 一个对象,对象.field or 对象.method,子类继承父类能不能使用父类的字段/方法。
-
能不能访问到一个类的字段/方法首先看能不能实例这个类(即目标类的修饰符是
public
还是package
,然后看方法/字段的修饰符。
四种修饰符:
-
public。可以被其他任何类访问。
-
private。只能在类内部使用,使用对象.字段或方法或者子类内部都不能使用。
-
protected。子类内部可以使用父类的
protected
字段/方法。与public
区别在于不同包目标类实例无法使用protected
方法/字段。
package com.bi;
public class Person {
private int i;
protected int j;
protected void test(){
System.out.println("aa");
}
}
package com.bi.wang; //不同包
import com.bi.Person;
public class Wang{
public static void main(String[] args) {
Person person = new Person();
person.j=3; //失败,因为访问的是protected字段
person.test(); //失败,因为访问的是protected方法
}
}
package com.bi;//同一个包 public class Main { public static void main(String[] args) { Person p = new Person(); p.j=3;//ok p.test();//ok } }
package com.bi.wang;//不同包 import com.bi.Person; public class Wang extends Person{ void run(){ test();//ok,因为是子类 System.out.println(j);//ok } }
-
package。可以修饰
class
和方法/字段,不用写出来。package
修饰的类、字段、方法只有同一个包才能访问,外包无法获取实例,无法在子类使用package
修饰的字段/方法。-
同包:
package com.bi; class Person { //package,不用写出来 int i;//package int j; void test() {//package System.out.println("aa"); } }
package com.bi; //同包 class Main { public static void main(String[] args) { Student student = new Student(); student.j=33; student.run(); Person person = new Person(); person.j=44; person.test(); } } //同包 class Student extends Person{ void run(){ test(); System.out.println(j); } }
-
不同包:
package com.bi; public class Person { //package,不用写出来 int i;//package int j; void test() {//package System.out.println("aa"); } }
package com.bi.wang;//不同包 import com.bi.Person; public class Wang { public static void main(String[] args) { Person person = new Person();//可以实例化对象,因为public修饰Person类 person.j=3;//faild,不同包,这里类似protected } } class Junior extends Person{ void test3(){ System.out.println(i);//faild,不同包 test();//faild,不同包 } }
-
最佳实践:
-
类内部
public
在前,private
方法在后,因为暴露给外部的方法优先放在明显的位置。 -
如果不确定是否需要
public
,就不声明为public
,即尽可能少地暴露对外的字段和方法。 -
把方法定义为
package
权限有助于测试,因为测试类和被测试类只要位于同一个package
,测试代码就可以访问被测试类的package
权限方法。
final
-
用
final
修饰class
可以阻止被继承。 -
用
final
修饰method
可以阻止被子类覆写。 -
用
final
修饰field
可以阻止被重新赋值。 -
用
final
修饰局部变量可以阻止被重新赋值。
总结,final修饰就是不要改变的意思。
内部类
如果一个类定义在另一个类的内部,这个类就是Inner Class:
public class Main { public static void main(String[] args) { Outer outer = new Outer("Nested"); // 实例化一个Outer Outer.Inner inner = outer.new Inner(); // 实例化一个Inner inner.hello(); } } class Outer { private String name; Outer(String name) { this.name = name; } class Inner { void hello() { System.out.println("Hello, " + Outer.this.name); } } }
上述定义的Outer
是一个普通类,而Inner
是一个Inner Class,它与普通类有个最大的不同,就是Inner Class的实例不能单独存在,必须依附于一个Outer Class的实例。
因为Inner Class除了有一个this
指向它自己,还隐含地持有一个Outer Class实例,可以用Outer.this
访问这个实例。所以,实例化一个Inner Class不能脱离Outer实例。
观察Java编译器编译后的.class
文件可以发现,Outer
类被编译为Outer.class
,而Inner
类被编译为Outer$Inner.class
。
Inner class
也可以在方法内部定义。
Anonymous Class
public class Main { public static void main(String[] args) { Outer outer = new Outer("Nested"); outer.asyncHello(); } } class Outer { private String name; Outer(String name) { this.name = name; } void asyncHello() { Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello, " + Outer.this.name); } }; new Thread(r).start(); } }
观察asyncHello()
方法,我们在方法内部实例化了一个Runnable
。Runnable
本身是接口,接口是不能实例化的,所以这里实际上是定义了一个实现了Runnable
接口的匿名类,并且通过new
实例化该匿名类,然后转型为Runnable
。在定义匿名类的时候就必须实例化它。
之所以我们要定义匿名类,是因为在这里我们通常不关心类名,比直接定义Inner Class可以少写很多代码。
观察Java编译器编译后的.class
文件可以发现,Outer
类被编译为Outer.class
,而匿名类被编译为Outer$1.class
。如果有多个匿名类,Java编译器会将每个匿名类依次命名为Outer$1
、Outer$2
、Outer$3
……
除了接口外,匿名类也完全可以继承自普通类。
Static Nested Class
public class Main { public static void main(String[] args) { Outer.StaticNested sn = new Outer.StaticNested(); sn.hello(); } } class Outer { private static String NAME = "OUTER"; private String name; Outer(String name) { this.name = name; } static class StaticNested { void hello() { System.out.println("Hello, " + Outer.NAME); } } }
用static
修饰的内部类和Inner Class有很大的不同,它不再依附于Outer
的实例,而是一个完全独立的类,因此无法引用Outer.this
。