Java三大特性:封装、继承、多态【详解】
封装
定义
隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读取和修改的访问级别便是封装。
在开发中造一个类就是封装,有时也会说封装一个类。封装可以隐藏一些细节或者包含数据不能被随意修改。
比如这是一个敏感的数据,我不能够让别人直接操纵到这个数据,因此我用private把它包装起来,对外通过一个public的接口给别人使用它的权限。
在程序中,为防止类的属性被无操作导致程序错误,所以属性也是不允许外部直接操作的,要通过固定的放回操作,例如get/set方法。
权限
提到封装,就不得不提到访问权限,它的目的是为了在代码中表示具体的属性或方法的访问权限。
权限修饰符一共有4个:public、protected、缺省、private
访问范围 | private | friendly(默认) | protected | public |
---|---|---|---|---|
同一个类 | 可访问 | 可访问 | 可访问 | 可访问 |
同一个包中的其他类 | 不可访问 | 可访问 | 可访问 | 可访问 |
不同包中的子类 | 不可访问 | 不可访问 | 可访问 | 可访问 |
不同包中的非子类 | 不可访问 | 不可访问 | 不可访问 | 可访问 |
get/set方法
通过 public 的方法提供对私有字段的访问。这些方法通常称为 “getter” 和 “setter”。
具体写法:
- boolean数据类型的写法是,get方法:isXxx()(xxx是属性的名字),set方法:setXxx(boolean xxx)。注意大写小,属性的名字是xxx,is和set后都首字母大写了;
- 除了boolean数据类型,写法都是,get方法getXxx(),set方法setXxx(数据类型 参数)。
IDEA生成get/set方法
继承
定义
现实生活中,子女能够继承父母拥有的财产,而在程序中也是一样的。
程序中的继承指的是子类拥有父类的特征和行为,这是类与类之间的一种关系。它不仅可以继承父类的代码,还能对其进行复用,扩展和修改。
Java对接口允许多实现,但对类只支持单继承,也就是说一个类有且只有一个父类。
【补充:超类】:超类(父类)是被其他类继承的类。它包含共享的属性和行为,子类通过继承超类来复用这些属性和行为。超类通常代表了某一类对象的通用特征,而子类则在此基础上扩展或修改功能。
超类(父类)(Superclass 或 Base Class):被继承的类,提供共享的属性和方法。
子类(Subclass 或 Derived Class):继承超类的类,拥有超类的属性和方法,同时也可以新增自己的属性和方法。
例子
// 超类(父类)
class Animal {
String name;
// 父类构造方法
public Animal(String name) {
this.name = name;
}
// 父类的方法
public void speak() {
System.out.println("动物在叫");
}
public void eat() {
System.out.println("动物在吃东西");
}
}
// 子类(继承自Animal)
class Dog extends Animal {
// 子类构造方法,调用父类构造方法
public Dog(String name) {
super(name); // 使用 super 调用父类构造方法
}
// 重写父类的方法
@Override
public void speak() {
System.out.println(name + " 说:汪汪");
}
// 子类新增方法
public void bark() {
System.out.println(name + " 在叫");
}
}
public class Main {
public static void main(String[] args) {
// 创建 Dog 对象
Dog dog = new Dog("Buddy");
// 调用父类继承过来的方法
dog.eat(); // 输出: 动物在吃东西
// 调用子类重写的方法
dog.speak(); // 输出: Buddy 说汪汪
// 调用子类独有的方法
dog.bark(); // 输出: Buddy 在叫
}
}
注意
-
权限修饰符,重写的权限修饰符要大于或等于父类方法的的权限修饰符(public>protected>缺省的>private);
-
final修饰的方法不能被重写;
-
static修饰的方法不能被重写,如果子类的方法也用static修饰,也不构成方法的重写,而是在子类中声明了一个属于子类的静态方法
super关键字
super
关键字在 Java 中用于引用父类的成员。它的作用是访问父类的方法和构造函数,类似于 this
关键字,但 this
是指当前对象,而 super
指向父类的对象。使用 super
可以在子类中调用父类的方法,特别是在子类重写了父类的方法时。
主要用法和注意事项:
调用父类的方法:
super
可以用来调用父类的方法。但调用时要注意方法的访问权限,必须确保父类的方法是可访问的(例如,方法的访问修饰符不能是 private
,否则无法访问)。
调用父类的构造方法:
在子类的构造函数中,通常会隐式地调用父类的无参构造方法。如果父类没有无参构造函数,子类则必须显式地调用父类的构造方法。调用父类构造方法时,可以使用 super()
。例如,如果父类只有带参数的构造函数,子类需要通过 super
(参数) 来调用父类的构造方法。
默认继承 Object 类:
在 Java 中,所有类都默认继承自 Object 类。也就是说,如果你没有显式地声明 extends Object,Java 会自动为你添加这一部分。比如,public class Person {} 和 public class Person extends Object {} 是等效的。
方法重写中的 super:
当子类重写了父类的方法后,如果希望调用父类版本的方法,而不是子类重写后的方法,可以使用 super。这在方法重写中尤为常见,尤其当子类需要在保留父类方法的基础上添加自己的行为时。
instanceof(补充)
instanceof是一个双目运算符,运算的结果是一个boolean数据类型,表示是否是某种数据类型。变量 instanceof 某数据类型A 如果变量是A数据类型则返回true,否则返回false。
public class Demo003 {
public static void main(String[] args) {
Object i1 = 10;//Integer是一个引用数据类型,表示正数,与int可以相互转换
System.out.println(i1 instanceof Integer);
System.out.println(i1 instanceof Object);
System.out.println(i1 instanceof Teacher);
System.out.println("-------------------------");
Teacher teacher = new Teacher();
System.out.println(teacher instanceof Teacher);
System.out.println(teacher instanceof Person);
System.out.println(teacher instanceof Object);
}
}
结果:
true
true
false
-------------------------
true
true
true
多态
定义
多态是面向对象的一个重要特征,它是指在父类中定义好了属性和方法,子类继承父类之后可以具有不同的数据类型表现或者不同的行为。
同一件事情,发生在不同对象的身上,就会产生不同的结果。核心的一句话:父类引用指向子类对象。
例如:Animal类有makeSound(发出声音)方法,子类Dog和Cat的makeSound方法都有不同的实现,当调用makeSound方法时会根据具体对象的实现而执行。
public class Animal {
public void makeSound(){
System.out.println("动物发出的声音");
}
}
public class Cat extends Animal {
@Override
public void makeSound(){
System.out.println("喵喵喵");
}
}
public class Dog extends Animal {
@Override
public void makeSound(){
System.out.println("汪汪汪");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
animal.makeSound();
Animal dog = new Dog();
dog.makeSound();
Animal cat = new Cat();
cat.makeSound();
}
}
输出结果:
动物发出的声音
汪汪汪
喵喵喵
转型问题
数据类型的转换包括:向上转型和向下转型
向上转型:向父类转换,可以直接赋值。
向下转型:向子类转换,需要进行强制类型转换。
例子:
public class DataType {
public static void main(String[] args) {
/*String数据类型是Object类型的子类*/
Object o1 = new Object();
String s1 = "a";
/*向上转型*/
Object o2 = s1;
System.out.println(o2);
Object oString = "b";
/*向下转型*/
String sObject = (String)oString;
System.out.println(sObject);
/*向下转型,但是会报错,原因是o1是Object数据,而且存放的数据也不是一个String数据类型的数据*/
/*强制转换有时候语法上不会报错,但运行时会报错*/
String s2 = (String)o1;
System.out.println(s2);
}
}
数据类型的传递
在不同数据类型的情况下,值的传递是不同的。
基础数据类型
基础数据类型作为参数传递时候,传递的是值,也就说在方法中修改这个参数的值,不会影响到实参的数据。
这个也叫做值传递
public class Type {
public static void testInt(int a){
System.out.println("进入方法,在修改值之前:" + a);
a = 10;
System.out.println("进入方法,在修改值之后:" + a);
}
public static void main(String[] args) {
int a = -10;
System.out.println("在进入方法之前:" + a);
testInt(a);
System.out.println("在方法执行之后:" + a);
}
}
结果:
在进入方法之前:-10
进入方法,在修改值之前:-10
进入方法,在修改值之后:10
在方法执行之后:-10
引用数据类型
JDK提供的大多数数据类型,传递的是对象的引用,也就是地址,会修改实参的数据,这个也叫做引用传递。
public class Type {
public static void main(String[] args) {
Student student = new Student();
student.setName("小明");
student.setAge(10);
System.out.println("在进入方法之前:" + student.getName() + "的年龄是:" + student.getAge());
testStudent(student);
System.out.println("在方法执行之后:" + student.getName() + "的年龄是:" + student.getAge());
}
public static void testStudent(Student student){
System.out.println("进入方法,在修改值之前:" + student.getName() + "的年龄是:" + student.getAge());
student.setName("大明");
student.setAge(20);
System.out.println("进入方法,在修改值之前后:" + student.getName() + "的年龄是:" + student.getAge());
}
}
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
结果:
在进入方法之前:小明的年龄是:10
进入方法,在修改值之前:小明的年龄是:10
进入方法,在修改值之前后:大明的年龄是:20
在方法执行之后:大明的年龄是:20
少数:不修改实参数据
String,Integer,Long…
public class Type {
public static void main(String[] args) {
String str = "我是原始字符串";
System.out.println("在进入方法之前:" + str);
testString(str);
System.out.println("在方法执行之后:" + str);
}
public static void testString(String str){
System.out.println("进入方法,在修改值之前:" + str);
str = "我被修改了";
System.out.println("进入方法,在修改值之后:" + str);
}
}
结果:
在进入方法之前:我是原始字符串
进入方法,在修改值之前:我是原始字符串
进入方法,在修改值之后:我被修改了
在方法执行之后:我是原始字符串