- 面向对象编程
- 关于访问权限
- 封装
- 包
- default 包访问权限
- import关键字
- 静态导入
- 常见的系统包
- 继承
- protected继承权限
- this和super
- this关键字
- super关键字
- this和super的区别
- final关键字
- 组合关系
- 多态
- 向上转型
- 方法重写
- 方法重载和方法重写的区别
- 向下转型
- 抽象类
- 接口
private(私有的,当前类中可见)
default(包访问权限,当前包中可见,不包含子包,同级目录下可见)
protected(继承权限,不同包有继承关系的类之间可见)
public(共有的,当前项目可见)
这四个访问权限的可见范围由小到大
private < default < protected < public
封装封装:使用private关键字将属性/方法进行封装,这个属性/方法就对外部隐藏了,只在当前类的内部可见。
封装增加了保护性和易用性
保护性:自然是外部无法直接访问,只能通过提供的方法来访问,意思就是规则由我来定,自然增加了保护性
易用性:当你通过程序提供的方法来 *** 作属性,你并不需要知道具体是如何运行的,你只需要知道你 *** 作的结果是怎样的(例如:电脑一键启动,你只需要知道电脑按开机键就能启动,至于具体的cpu怎么运行,显卡又怎么运行你当然是不知道的)
private访问权限就是在当前类的内部可见
包我们要了解包访问权限,首先要知道什么是包
Java中的‘包’对应 *** 作系统就是文件夹,使用package关键字声明一个包,若存在多个文件夹的嵌套,使用‘.'分隔
default 包访问权限没有任何修饰符的权限就是包访问权限,意味着当前包中的所有类都可以访问这个成员,但也是仅限于当前包,连字包也不可见
若你在package1中定义了一个包访问权限的Test类,这个package1还有一个字包package2,这时package2是不能直接使用Test的,这也带来了个好处,那就是不同包中是可以重名的
import关键字而若此时我们想要在一个包中使用另外一个包中的类
这时就需要用到import这个关键字,来导入包中的某个具体类
import 包名.类名;
如我们导入java中util包中的Date类
import java.util.Date;
需要注意的是import导入的只是某个包中的某个具体的类,并不是导入这个包
而若我们此时需要用到util包中的多个类,这时一句一句写的话就有些麻烦,可以使用
import 包名.*;
还是需要注意,这时的导入依然是导入包中的具体某个类,并不是导入这个包,这点非常要注意不要产生误解,只是这样导入用到这个包中的类的时候,会按需加载,你使用该包下的哪个类就会自动加载哪个类
但是这样使用会有一个问题,那就是重名问题
若两个包内都有同样的名字的类,这时就会让系统无法识别到底是使用哪个包中的类
package Test;
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
Date date = new Date();
}
}
util和sql这两个包中都有Date这个类,这时就会出现以下报错
这就是使用import需要注意的两个问题
1.import导入的始终是某个具体的类,并不会导入某个包
2.import 包名.*;需要注意重名问题
关于import 包名.*;的重名问题的解决方法有两个
1.直接使用类的全名称
package Test;
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
//这时就是明确表示使用的是util包下的Date类
java.util.Date date = new java.util.Date();
}
}
2.明确指定导入的是哪个包下的类
package Test;
//明确指明了此时Date类就是util包中的Date类
import java.util.Date;
import java.sql.*;
public class Test {
public static void main(String[] args) {
Date date = new Date();
}
}
静态导入
静态导入就是使用import和static关键字导入某个包中的静态方法和属性
import static java.lang.System.*;
这就表示导入了System这个包中的所有静态方法和属性
这样我们在使用的时候就相当于,这个方法是你自己写的,存在于你这个类中
//导入System中的所有静态方法和属性
import static java.lang.System.*;
public class Test {
public static void main(String[] args) {
//使用之前经常使用的打印方法
System.out.println(123);
//把包名省略一样可以使用
out.println(123);
}
}
//打印结果
123
123
虽然是可以这样,但并不推荐使用,本来使用类全称.静态方法/静态属性 来访问静态域已经很简单了,省不省略这一步也无伤大雅
常见的系统包-
java.lang :JDK的基础类,System,String,Object
-
java.lang.reflect:反射开发包
-
java.util :工具包(集合类都在这个包下,Arrays,LinkedList,HashMap)
-
java.io:I/O 开发包 ,文件读取和写入
-
java.net:网络编程开发包,Socket
-
java.sql:数据库开发用到的包
protected继承权限
我们要了解继承权限,首先要清楚什么是继承
定义了三个类,分别是Person,Chinese和American
package Person;
public class Person {
public String name;
public int age;
public void eat(){
System.out.println(this.name + "正在吃饭");
}
public void sleep(){
System.out.println(this.name + "正在睡觉");
}
}
package Person;
public class Chinese {
public String name;
public int age;
public void eat(){
System.out.println(this.name + "正在吃饭");
}
public void sleep(){
System.out.println(this.name + "正在睡觉");
}
}
package Person;
public class American {
public String name;
public int age;
public void eat(){
System.out.println(this.name + "正在吃饭");
}
public void sleep(){
System.out.println(this.name + "正在睡觉");
}
}
这三个类确实都有,名字,年龄,以及吃饭和睡觉这两个行为,这是肯定的,但我们发现,这三个类的代码时完全一样的,这就造成了代码冗余
而且这时,Person和Chinese以及American是满足is a关系的
Chinese is a Person
American is a Person
这时就可以使用关键字extends来继承
package Person;
public class Person {
public String name;
public int age;
public void eat(){
System.out.println(this.name + "正在吃饭");
}
public void sleep(){
System.out.println(this.name + "正在睡觉");
}
}
package Person;
public class Chinese extends Person {
}
package Person;
public class American extends Person {
}
package Person;
public class Test {
public static void main(String[] args) {
Chinese chinese = new Chinese();
chinese.name = "中国人";
chinese.age = 18;
System.out.println(chinese.name + " " + chinese.age);
chinese.eat();
chinese.sleep();
American american = new American();
american.name = "美国人";
american.age = 18;
System.out.println(american.name + " " + american.age);
american.eat();
american.sleep();
}
}
这时Chinese和American下一句代码都没有,但却是能正常使用
继承可以避免代码冗余,但不能为了省略代码就简单的使用继承
要想使用继承必须满足 is a 关系,如Chinese is a Person,American is a Person
若一个类和另一个类满足 类 is a 另一个类 一定存在继承关系
继承的规则:
1.要能使用继承前提必须满足类之间的 is a 关系
2.在Java中一个子类只能使用extends继承一个父类
在Java中不允许多重继承,但允许多层继承
就如哈士奇 is a Dog,哈士奇 is a Animal ,虽然都存在继承关系,但不能
public calss erha extends Dog,Animal{
}
这在Java中是不允许的,但是C++中是有多重继承的
但Java可以多层继承
public calss erha extends Dog{
}
public calss Dog extends Animal{
}
3.子类会继承父类的所有属性和方法(显示继承和隐式继承)
显示继承:所有public属性和方法可以直接使用
显示继承不用多说,可以直接访问到继承的方法或属性
隐式继承:private属性和方法,子类其实也继承了这个属性和方法,但是无法直接使用只能使用父类提供的方法来 *** 作
就如我们将Person中的age属性改为private权限
public class Person {
public String name;
private int age;
public void eat(){
System.out.println(this.name + "正在吃饭");
}
public void sleep(){
System.out.println(this.name + "正在睡觉");
}
}
就会出现以上报错,虽然他继承了,但是没有权限访问,那么如何证明他确实继承了呢?
public class Person {
public String name;
private int age;
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void eat(){
System.out.println(this.name + "正在吃饭");
}
public void sleep(){
System.out.println(this.name + "正在睡觉");
}
}
我们可以在Person这个类中,设置setter和getter方法,由我们提供方法来让age可以被 *** 作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c9U0kFzE-1651829228597)(E:\桌面\extends3.jpg)]
这时就能说明他确实是继承了,但是是隐式继承,并不能直接 *** 作,只能通过提供的方法来 *** 作,这就是隐式继承
了解完了继承就可以来看看 protected继承权限
protected继承权限protected继承权限的可见范围是,当前包中不同类可见以及不同包中有继承关系的类的内部可见
将Person类中的age改为继承权限
public class Person {
public String name;
protected int age;
public void eat(){
System.out.println(this.name + "正在吃饭");
}
public void sleep(){
System.out.println(this.name + "正在睡觉");
}
}
这时在同包中的不同类(无继承关系)中是可见的,这个同包并不包括这个包下的子包
知道了同包中不同类可见,那还有不同包中有继承关系的类中也可见
Japanese这个类是在Japanese这个包中的,并不在Person这个包中,但却可以直接使用age这个属性且没有报错,这就说明protected修饰的age属性在不同包有继承关系的类中的可见的
this关键字:表示当前对象的引用
this修饰属性,表示直接从当前类中找同名属性
当有继承关系时,默认先当前类中寻找同名属性,若当前类中没有则会在父类中去寻找
,若任不存在继续向上寻找
this修饰方法也一样,先是从当前类中寻找,之后向上寻找
super关键字super关键字:表示从直接父类中开始寻找(super关键字先直接父类寻找,若不存在继续向上寻找)
修饰属性,表示直接从父类中去寻找同名属性,找到之后就不再继续向上寻找
例
public class Person {
private String name;
}
public class Chinese extends Person {
public String name;
public void fun(String name){
super.name = name;
}
}
尽管父类的name属性被private修饰无法直接访问,但依然是找到的是父类同名的name方法,只是没有权利访问这个name属性,且既然找到了也不会再继续在Person(假设Person还有父类)的父类上再寻找,只有父类不存在才会继续向上寻找
修饰方法,表示直接从父类中去寻找方法
而这涉及到一个知识,就是有继承关系的类,要想产生子类的对象,首先要产生父类对象,就比如没有你的父亲,哪来的你
public class Person {
public Person(){
System.out.println("1.Person的无参构造");
}
}
public class Chinese extends Person {
public Chinese(){
System.out.println("2.Chinese的无参构造");
}
}
class Test {
public static void main(String[] args) {
Chinese chinese = new Chinese();
}
}
可以看到,是先打印了Person的无参构造,说明是先产生了Person对象再产生Chinese
public class Person {
public Person(){
System.out.println("1.Person的无参构造");
}
{
System.out.println("2.Person的构造块");
}
static{
System.out.println("3.Person的静态块");
}
}
public class Chinese extends Person {
public Chinese(){
System.out.println("4.Chinese的无参构造");
}
{
System.out.println("5.Chinese的构造块");
}
static{
System.out.println("6.Chinese的静态块");
}
}
class Test {
public static void main(String[] args) {
System.out.println("7.主方法开始");
Chinese chinese = new Chinese();
System.out.println("8.主方法结束");
}
}
这个Chinese对象执行的顺序是怎样的?
super修饰构造方法
直接就是super();直接调用父类的无参构造(可选择写或不写),若父类中没有无参构造,则在子类的构造方法的首行必须使用super(参数)来调用,不能够省略
public class Person {
public String name;
public Person(String name){
this.name = name;
System.out.println("1.Person的无参构造");
}
}
public class Chinese extends Person {
public Chinese(){
super();
System.out.println("2.Chinese的无参构造");
}
}
这时是会报错的
还有需要注意的就是在一个构造方法中this和super是不能同时显示使用的
public class Person {
public String name;
public Person(){
this.name = name;
System.out.println("1.Person的无参构造");
}
}
public class Chinese extends Person {
public Chinese(){
}
public Chinese(String name){
super();
this();
System.out.println("2.Chinese的无参构造");
}
}
而不显示使用是没问题的,例如把super();删了
public class Chinese extends Person {
public Chinese(){
System.out.println("3.Chinese的无参构造");
}
public Chinese(String name){
this();
System.out.println("2.Chinese的有参构造");
}
}
一样是先上升到父类,产生父类对象,再回来调用构造方法,也就是在this();前面默认有一个super();
super修饰普通方法
super修饰普通方法,和修饰属性一样,直接从父类中寻找同名方法
this和super的区别super关键字不能指代当前父类的引用
this关键字表示当前类的对象的引用
System.out.println(this);
System.out.println(super); //报错
//不能使用super来表示当前父类的引用,但是使用super.方法/属性是可以的
System.out.println(super.name);
System.out.println(super.toString);
final关键字
-
final修饰属性,表示该属性具备了常属性,不能被更改
-
final修饰类,表示这个类无法被继承
-
final修饰方法,表示这个方法无法被覆写
类和类之间的关系,除了继承关系还有组合关系
组合关系即使 has a 关系
例
class School{
Student student;
Teacher teacher;
}
School has a student,School has a teacher,在School里面有着多种类
多态多态:一个引用可以表现出多种行为/特性,就称之为多态
向上转型之前我们创建一个对象都是使用,类名称 类引用 = new 该类对象();
如:Chinese chinese = new Chinese();
而我们还可以写成这样
Person per = new Chinese();
父类名称 父类引用 = new 子类对象(); (这个子类不一定是直接子类,但前提是有继承关系)
例如 Erha extends Dog; Dog extends Animal;
Animal animal = new Erha();
这个称之为向上转型,向上转型的最大意义在于参数的统一化,降低使用者的使用难度
若我们没有向上转型,我们写一个fun方法来接收Animal及其子类作为参数,这时Animal有多少个子类,我们就得重载多少次fun方法
且在使用时,若没有向上转型,我要使用fun方法,就得了解Animal以及其所有子类的对象,我才能知道调用的是谁
只需要知道是否是该类的子类就能判断是否能够使用这个fun方法,且还避免了多次重载代码
只要是Animal的子类都能够使用fun这个方法,有了向上转型之后,就能使用最顶端的父类的引用就可以指代所有的子类对象
且当有个新类也是这个类的子类是非常容易扩展的,例如cat也是Animal
public class Cat extends Animal{}
一样是可以使用这个方法
而多态就是基于向上转型和重写的
我们在Animal、Dog和Erha都定义了一个eat方法
public class Animal {
public void eat(){
System.out.println("Animal的eat方法");
}
}
public class Dog extends Animal {
public void eat(){
System.out.println("Dog的eat方法");
}
}
public class Erha extends Dog{
public void eat() {
System.out.println("Erha的eat方法");
}
}
在fun方法中我们调用eat方法看看怎么样的
public class Test {
public static void main(String[] args) {
fun(new Animal());
fun(new Dog());
fun(new Erha());
}
public static void fun(Animal animal){
animal.eat();
}
}
我们可以发现,他用的是各自类的eat的方法,而不是Animal的eat方法
这就是多态,一个引用可以表现出多种行为/特性
而各自类的eat方法称之为方法重写,那方法重写是什么呢
方法重写方法重写(override):是发生在有继承关系的类之间,子类定义了和父类除了权限不同,其他全部相同的方法,这样的一组方法称之为方法重写
就如上面Animal、Dog和Erha的eat方法
方法重写之后具体调用的是谁,我们只需要看new的是哪个对象即可,new的是哪个对象且该对象有重写该方法,调用的就是该对象自己的方法
而若此类中没有重写该方法,则是根据就近匹配原则,去向上找同名方法,先是找直系父类,若直系父类没有重写,再继续向上找
当发生重写时,子类的权限必须 >= 父类权限才可以重写
private < default < protected < public
但又一个权限例外,private权限并不在这之内,方法重写只能是default、protected、public权限
为什么呢?
当你使用private权限修饰方法的时候,只要出了这个类的内部,外部就无法知道有这个方法的存在,既然连它的存在都不知道,那我还怎么去重写这个方法
注意事项
方法重写,不能重写static方法,多态的本质就是因为调用了不同的子类对象,只有这些子类覆写了相应的方法,才能表现出不同的行为,但是static和对象无关,它修饰的方法是属于类的
所以方法重写只发生在普通方法中
方法重载和方法重写的区别区别 | 重载(overload) | 重写(override) | |
---|---|---|---|
1 | 概念 | 方法的名称相同,参数列表不同、与返回值类型无关 | 方法名称、返回值类型、参数列表完全相同,只与权限有关 |
2 | 范围 | 一个类中 | 存在继承关系的类中 |
3 | 限制 | 没有权限要求 | 被覆写的方法的权限要大于父类的权限 |
4 | static | 没要求 | 无法重写static方法 |
既然有向上转型,那肯定就有向下转型
父类名称 父类引用 = new 子类对象();
子类和父类是存在is a 关系所以这是天然发生的向上转型
Animal animal = new Dog();
若此时我们Dog中还有个自己独有的play();方法,我们需要调用这个play();方法,此时就不能使用animal.play();来调用了,
public class Animal {
public void eat(){
System.out.println("Animal的eat方法");
}
}
public class Dog extends Animal {
@Override
public void eat(){
System.out.println("Dog的eat方法");
}
public void play(){
System.out.println("Dog的play方法");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Dog();
animal.eat();
animal.play();
}
}
这时就会报错,我们并不能这样使用
那么我们想要调用这个play方法就需要向下转型,才能调用
使用 子类名称 子类引用 = (子类名称) 父类引用就可以将animal向下转型为Dog类
使用dog(子类引用)就能访问到Dog自己的方法
public class Test {
public static void main(String[] args) {
Animal animal = new Dog();
animal.eat();
Dog dog = (Dog)animal;
dog.play();
}
}
要想知道animal能调用什么方法,只需要看Animal有什么方法即可,而具体的表现形式,若子类有重写次方法就表现出子类的方法
向下转型还有个条件
要发生向下转型,首先要发生向上转型
Animal animal = new Dog();
Dog dog = (Dog)animal;
这样是可以向下转型的,因为Dog先向上转型为animal了,而
Animal animal = new Animal();
Dog dog = (Dog)animal;
此时这个animal与Dog类毫无关系,所以animal无法向下转型
Animal animal = new Dog();
就如披着Dog皮的Animal,本质上它依然是个Dog
看到这个就得反应是类型转换异常
当发生向下转型时会有风险,类型转换异常,使用instanceof关键字
引用名称 instanceof 类 -> 返回布尔值 ,表示该引用指向的本质是不是该类的对象
检查animal1和animal2是否是Dog这个类的对象,是返回true否则返回false
抽象类若需要强制要求子类覆写方法,可以使用抽象类
抽象类是概念化的,没办法具体到某个实例,描述这一类对象共同拥有的属性和行为
如形状,说到形状你一下并不能反应出具体的形状,但这个形状和其子类圆形、正方形、三角形都有一些共同的属性和行为
抽象类是普通类的‘超集’,就是普通类有的抽象类都有,只是抽象类比普通类多了一些抽象方法
抽象方法所在的类必须是抽象类,子类若是继承了抽象类,必须覆写所有抽象方法(前提是子类是普通类)
在Java中使用abstract关键字定义抽象类或者抽象方法
1.抽象方法所在的类必须使用abstract声明为抽象类
这个print方法就是一个抽象方法,所以声明Sharp为一个抽象类
抽象方法只有方法声明,没有方法体
若一个方法定义为抽象方法,在抽象类中是没有具体的实现的,是延迟到子类中实现
注意事项
不能说没有方法体的方法就是抽象方法
2.若一个类使用abstract声明为抽象类,那这个类是无法实例化对象的,哪怕该类中没有抽象方法
只能是通过子类向上转型为抽象父类的引用
Sharp sharp = new Sharp();
这样是不行的,直接报错
3.子类继承了抽象类,必须强制覆写抽象类的所有抽象方法(前提是子类是普通类),且任然是只能继承一个父类
public abstract class Sharp {
public abstract void print();
}
public class Cycle extends Sharp{
@Override
public void print() {
System.out.println("●");
}
}
public class Triangle extends Sharp{
@Override
public void print() {
System.out.println("▲");
}
}
public class Square extends Sharp{
}
若没有覆写抽象方法则会报错
4.抽象类是普通类的‘超集’,只是比普通类多了一些抽象方法,虽然抽象类无法实例化对象,但也是可以存在构造方法,子类在实例化是仍然遵循继承的原则,先调用父类(抽象类)的构造方法,然后调用子类的构造方法产生子类对象
5.虽然抽象类无法实例化对象,但子类也是需要满足is a原则,子类和抽象父类之间仍然是满足“继承树”的关系
接口接口的使用一般表示两种场景
1.接口表示具备某种能力/行为,子类实现接口时不是is a 关系,而是具备这种能力
例如:跑步–> Person、Dog、Cat等各种类都具备跑步这个行为,所以都能实现跑步这个接口
2.接口表示一种规范或者标准
如:“USB接口”;“5G标准”
在接口中只有全局常量和抽象方法,其他东西统统没有
在Java中使用interface来声明接口,子类则使用implements实现接口
示例:USB接口
首先在new的时候要选择interface
对于USB接口来说他有两个核心方法 那就是插入方法和工作方法
public interface USB {
public abstract void plugIn();
public abstract void workOn();
}
这是就定义了一个USB接口
由于接口中只有抽象方法和全局常量,他是延迟到子类中实现的,USB的子类就有鼠标,键盘等
在子类中使用使用implements实现接口,必须覆写所有的抽象方法(前提是子类是普通类)
public class Keyboard implements USB{
@Override
public void plugIn() {
System.out.println("插入键盘,安装键盘驱动");
}
@Override
public void workOn() {
System.out.println("键盘正常工作");
}
}
public class Mouse implements USB{
@Override
public void plugIn() {
System.out.println("插入鼠标,安装鼠标驱动");
}
@Override
public void workOn() {
System.out.println("鼠标正常工作");
}
}
这就是我们实现的两个子类
我们来模拟一下电脑的USB接口
public class Computer {
public static void main(String[] args) {
Computer computer = new Computer();
Mouse mouse = new Mouse();
computer.fun(mouse);
Keyboard keyboard = new Keyboard();
computer.fun(keyboard);
}
public void fun(USB usb){
usb.plugIn();
usb.workOn();
}
}
注意事项
接口是允许多实现的,就如一个子类Person他既会跑又会游泳
public interface IRun {
public abstract void run();
}
public interface ISwim {
public abstract void swim();
}
public calss Person implements IRun,ISwim{
@Override
public void run(){
}
@Override
public void swim(){
}
}
接口表示一种能力,一个类当然可以具备多种能力,就如一个人有各种各样的技能
还有需要注意的是,因为接口只有抽象方法和全局常量,所以在接口中
public abstract 和 static final 是可以省略的
只需要保留最核心的方法返回值,方法参数列表,方法名称即可
public interface ISwim {
void swim();
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)