面向对象的三大基本特征:封装、继承、多态。
封装(Encapsulation)是面向对象的三大特征之一,它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。采用封装的思想保证了类内部数据结构的完整性,使用该类的用户不能轻易地直接操作此数据结构,只能操作类允许公开的数据。这样就避免了外部操作对内部数据的影响,提高了程序的可维护性。
同一个类中 | 同一个包中 | 不同包的子类 | 不同包的无关类 | |
---|---|---|---|---|
public | ✓ | ✓ | ✓ | ✓ |
protected | ✓ | ✓ | ✓ | |
default | ✓ | ✓ | ||
private | ✓ |
public(公共访问权限):这是一个最宽松的访问控制级别,如果一个成员(包括成员变量、方法和构造器等)或者一个外部类使用 public 访问控制符修饰,那么这个成员或外部类就可以被所有类访问,不管访问类和被访问类是否处于同一个包中,是否具有父子继承关系。 protected(子类访问权限):如果一个成员(包括成员变量、方法和构造器等)使用 protected 访问控制符修饰,那么这个成员既可以被同一个包中的其他类访问,也可以被不同包中的子类访问。在通常情况下,如果使用 protected 来修饰一个方法,通常是希望其子类来重写这个方法。 default(包访问权限):如果类里的一个成员(包括成员变量、方法和构造器等)或者一个外部类不使用任何访问控制符修饰,就称它是包访问权限的,default 访问控制的成员或外部类可以被相同包下的其他类访问。 private(当前类访问权限):如果类里的一个成员(包括成员变量、方法和构造器等)使用private访问控制符来修饰,则这个成员只能在当前类的内部被访问。很显然,这个访问控制符用于修饰成员变量最合适,使用它来修饰成员变量就可以把成员变量隐藏在该类的内部。
public class Test {
int a = 3;
public static void main(String[] args) {
test(5);
}
public void test(int a) {
System.out.println(a); // 5
}
}
运行上述代码可以看到输出的是 5 ,而不是 3。这就是 Java 的就近原则,如果想要输出 5 则需要借助 this 关键字,this 代表所在类当前对象的引用,即谁调用就代表谁。
final 修饰类,被修饰的类不能被继承、修饰方法,被修饰的方法不能被重写、修饰变量,被修饰的变量不能重新赋值。
// 修饰类
final class 类名{ ··· }
// 修饰方法
final 返回值类型 方法名( 参数列表 ) { ··· }
// 修饰变量
final 数据类型 变量名;
(1)私有化 使用 private 关键字修饰成员变量,使其私有化。 (2)暴露方法 提供 setXXX()/getXXX()方法来设置和获取成员变量。 (3)this 在成员方法中局部变量和成员变量重名是就需要使用 this 了。
public class Add {
// 私有成员变量
private int a;
private int b;
// 提供公共访问方法
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
//成员方法
public int add() {
int sum = a + b;
return sum;
}
}
在类中除了成员方法之外,还存在一种特殊类型的方法,那就是构造方法。构造方法是一个与类同名的方法,对象的创建就是通过构造方法完成的。每当类实例化一个对象时,类都会自动调用构造方法。
public 构造方法名( 参数列表 ) {
方法体;
}
构造方法名与类名必须一致。 有参数的构造方法称为有参构造,没有参数的构造方法称为空参构造。 构造方法可以重载,但无论如何,Java类至少包含一个构造器。 若没有写任何构造方法,JVM 会自动帮你写一个空参构造。
public class Test {
// 成员变量
private int a;
private int b;
// 空参构造
public Test() {
}
// 有参构造
public Test(int a, int b) {
this.a = a;
this.b = b;
}
//set/get方法
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
}
继承的基本思想是基于某个父类的扩展,并制定出一个新的子类,子类可以继承父类原有的属性和方法,也可以增加原来父类所不具备的属性和方法,或者直接重写父类中的某些方法。
在 Java 中,让一个类继承另一个类需要使用 extends 关键字,子类在继承父类的同时,也继承了父类中的属性和方法。
子类可以重写父类中的方法,但是再重写之后想要调用父类中的方法需要借助 super 关键字,super 关键字代表父类对象。
public class 父类 { ··· }
public class 子类 extends 父类 { ··· }
(1)成员变量: 不重名没有影响,使用谁就是谁。 重名则 new 谁就是谁的 ( 开发中一般不会重名 ),此时如果要使用父类的非私有成员变量与需要使用 super.父类成员变量名。 (2)成员方法: 不重名没有影响,使用那个方法就是那个方法。 重名则为子类重写(覆盖)父类方法,声明不变,重新实现。 (3)构造方法: 构造方法名与类名一致,所以不能被继承。 构造方法是用来初始化成员变量的,所以子类初始化的过程中,父类必须先于子类初始化。即调用构造方法时,一定先调用父类的构造方法。 子类的空参构造中会默认写有 super(),表示调用父类的构造方法,父类的成员变量初始化之后才可以给子类使用。 (4)其他注意事项: 子类覆盖父类方法是,必须保证子类方法权限大于父类。 子类覆盖父类方法要保证,权限、返回值、方法名、参数列表一模一样。 类的继承只支持单继承,不支持多继承 所有类的父类都是 Object
// 父类,手机类
public class Phone {
// 成员变量
private String brand;
private String price;
// 构造方法
public Phone() {
}
public Phone(String brand, String price) {
this.brand = brand;
this.price = price;
}
// 成员方法
public void call() {
System.out.print("打电话!");
}
public void send() {
System.out.println("发短信!");
}
public void show() {
System.out.println(brand + "牌手机,售价" + price);
}
}
// 子类,小米手机
public class MiPhone extends Phone{
// 构造方法
public MiPhone() {
// 默认有 super();
}
public MiPhone(String brand, String price) {
super("mi 10 pro","3999");
}
//重写父类方法
@Override
public void call() {
super.call(); //调用父类原有 call() 方法
// 子类新增功能
System.out.print("显示姓名! ");
System.out.print("显示号码! ");
}
//子类特有方法
public void inter() {
System.out.println("上网!");
}
}
//测试类
public class test {
public static void main(String[] args) {
//new 对象
MiPhone mp = new MiPhone();
//调用父类方法
mp.send(); // 发短信!
//调用子类重写方法
mp.call(); // 打电话!显示姓名! 显示号码!
//调用子类特有方法
mp.inter(); // 上网!
}
}
(1)抽象方法:使用 abstract 修饰的没有方法体的方法称为抽象方法。 (2)抽象类:包含抽象方法的类称为抽象类。
修饰符 class abstract 类名 {
修饰符 abstract 返回值类型 方法名();
}
抽象类不能创建对象。 抽象类中可以有构造方法,是提供给子类创建对象时初始化父类成员变量使用。 子类需重写父类所有的抽象方法,除非子类也是抽象类。 抽象方法一定要在抽象类中,抽象类不一定要有抽象方法。 最终必须有子类是实现父类所有的抽象方法。
//父类,抽象类
public abstract class Fu {
public abstract void num();
}
//子类,实现父类
public class Zi extends Fu{
public void num() {
System.out.println("子类");
}
}
接口是Java中一种引用类型,是方法的集合。接口中主要封装了方法,包含抽象方法(JDK 1.7 之前),默认方法、静态方法(JDK 8),私有方法(JDK 9)。
public interface 接口名{
//抽象方法(JDK 1.7 之前)
//默认方法(JDK 8)
//静态方法(JDK 8)
//私有方法(JDK 9)
}
(1)抽象方法(☆☆☆☆☆)
//与抽象类相似
public interface Inter{
public abstract void method();
}
(2)默认方法
//主要用于升级接口,防止以前的实现类报错,新创建的实现类可以覆盖默认方法。
//接口中有多个默认方法是,实现类都可以继承使用。
public interface Inter{
public default void method(){
System.out.println("run···");
}
}
(3)静态方法
//直接使用接口已经完成的现成功能。
public interface Inter{
public static void method(){
System.out.println("run···");
}
}
(4)私有方法
//用于抽取接口中相同的代码块
//1.私有方法:只有默认方法可以调用
public default void methodA(){
method();
}
public default void methodB(){
method();
}
private void method() {
System.out.println("runA···");
System.out.println("runB···");
}
//2.私有静态方法:默认方法和静态方法都可以调用
public default void methodA(){
method();
}
public static void methodB(){
method();
}
private static void method() {
System.out.println("runA···");
System.out.println("runB···");
}
注意: 接口不能直接 new 调用接口原本的默认方法:接口名.super.默认方法名(); 静态方法属于接口,只能通过:接口名.静态方法名() 调用。
// 继承并实现
public class 类名 extends 抽象类 implement 接口名{
//方法体
}
// 多实现
public class 类名 [ extends Object ] implement 接口名1, 接口名2,··· ,接口名n {
//方法体
}
// 多继承
public interface 类名 extends 接口名1, 接口名2,··· ,接口名n {
//方法体
}
注意: 如果抽象方法有重名,则只需要重写一次。 如果默认方法有重名,则必须重写。 那个接口在前则那个接口优先级高。
Java 引用变量有两个类型:一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态。
// 父类引用指向子类对象
父类 变量名 = new 子类();
// 接口引用指向实现类对象
接口 变量名 = new 实现类();
多态调用成员变量时:编译看左,运行看左。即:父类没有则不能使用 多态调用成员方法时:编译看左,运行看右。即:父类没有则不能通过编译
向上转型(默认)
父类 类名 = new 子类();
为什么要想上转型:父类类型作为方法形式参数,传递子类对象,更能体现多态的扩展性与便利性 向下转型(强制)
子类 类名1 = ( 子类 ) 类名2;
为什么要向下转型:使用多态方式无法调用子类特有方法。 向下转型需要注意只有以前是这个类才能转。 instanceof 关键字 用来判断向下转型时是否属于该类。例如: 动物A instanceof 猫;
//父类
public abstract class Phone {
public abstract void price();
}
//子类,联想
public class LenovoPhone extends Phone{
public void newPrice() {
System.out.println("3999");
}
}
//子类,华为
public class HonorPhone extends Phone{
public void newPrice() {
System.out.println("6999");
}
}
//测试类
public class Test{
public static void main(String[] arg) {
//父类引用指向子类对象
Phone phone = new LenovoPhone();
method(phone);
}
//判断属于那个子类
public void method(phone phone) {
if(phone instanceof LenovoPhone) {
//向下强转
LenovoPhone lp = (LenovoPhone)phone;
//调用联想手机类独有方法
lp.newPrice();
System.out.println("这是联想手机");
}
if(phone instanceof HonorPhone) {
//向下强转
HonorPhone hp = (HonorPhone)phone;
//调用华为手机类独有方法
hp.newPrice();
System.out.println("这是华为手机");
}
}
}
//外部类
class 类名 {
//内部类
class 类名 {
···
}
}
成员内部类定义在类中方法外 内部类可以直接访问外部类的成员,包括私有成员 外部类要想访问内部类的成员必须先创建内部类对象
// 外部类
class 类名 {
// 方法
修饰符 返回值类型 方法名( 参数列表 ) {
class 类名 { //局部内部类
···
}
}
}
局部内部类定义在方法中 局部内部内在堆中,局部变量在栈中,方法出栈后,局部变量消失,局部内部类等待 JVM 回收
父类或接口名 对象名 = new 父类或接口 {
//方法重写
@Override
}
【等号左边】 是多态,父类引用指向子类对象。 【等号右边】 定义并创建父类或接口的类对象。 匿名内部类是省略了【实现类/子类】名称,匿名对现象是省略了【对象名】。
//接口
public interface Inter {
//抽象方法
public abstract void method();
}
//测试类
public class InterAnonymous {
public static void main(String[] ards) {
//匿名内部类
new Inter {
public void method() {
System.out.println("使用了匿名内部类和匿名对象");
}
}.method();
}
}