Java面向对象
文章目录
前言
这只是个人对自己学习的一个总结复习,有看到的小伙伴,仅供参考,有不对的地方,欢迎指正!qq:2530318393
一、类与对象
1、什么是类与对象:
对象: Java中用来描述现实生活中真实存在的个体的概念,也就是:对象即真实个体。
类: 同一种对象在生活中有千千万万,于是从对象中抽象出来类。
关系: 它们的关系就像印钱的模板与钱一样,很多一样的钱就可以制作一个模板,这个模板就是类,反之模板印出来的钱一定与模板一样。从一个类创建出来的对象也一定符合这个类的定义。由对象确定出类,由类衍生出对象。
2、类与对象都有些什么?怎么创建?怎么用?
包含: 它们的本质就是对现实生活中个体的描述,描述现实生活中的个体:长啥样?会干啥?可以总结为:特征、行为;对应抽象到类就是:
1、属性包含变量与常量(变量分为成员变量与类变量(类变量是所有对象共有的(static修饰的),就像人类这个类,都要上厕所,所以创建了一个变量叫公共厕所,大家一起使用。具体的后面的文章再细说))
2、方法(不论对象存在多少个,都只会只存在一份,具体原因仍然后面说。仍然分为普通的对象方法与类方法(static修饰的),类方法可以不用创建对象直接使用)。
创建: 类通过关键词class创建,创建了类之后才能创建对象,对象通过关键词new创建。
使用: 使用主要就是使用其内部的属性(变量)与方法,使用方法:类变量与方法都是通过:类名.方法名/变量名;普通的对象方法与变量:对象名.方法名/变量名
程序示例:
public class Test { //创建类
int id; //成员属性,可以不用初始化,系统自动初始化,整型默认为0,浮点默认0.0,boolean默认false,引用默认null
static boolean isClass = true; //也可以手动初始化
void show(){
System.out.println("我是Test类的show方法!");
}
static void iAmClass(){
System.out.println("我是Test类的类方法!");
}
public static void main(String[] args) {
Test test = new Test(); //创建一个对象,对象为引用类型变量
int m = test.id; //使用成员
System.out.println("m:"+m);
test.show(); //使用对象方法
if (Test.isClass) //使用类变量
Test.iAmClass(); //使用类方法
}
}
二、方法
1、方法的签名
由方法名与参数列表组成
2、方法的重载与重写
此处参考我另外一篇博文:Java学习重写与重载
3、构造方法
1、啥是构造方法:也叫构造函数、构造器,在类创建对象时是用来对对象初始化的方法。在创建对象时被调用。
2、作用:给成员变量赋初值
3、长相:方法名与类名相同,没有返回值类型(void也没有)
4、提供:自己不写时系统默认提供一个无参构造,支持传参(形参名与成员变量名重复时,此时会用到this关键字),支持重载,重载后系统不再默认提供无参构造,如若此时需要无参构造需要自己写。
4、null
表示”空“的意思。引用类型变量系统默认值就是null,为null的变量不能进行任何的点操作,否则会报空指针异常(NullPointerException)
5、this关键字
两种情况下必须使用:
- 当局部变量和成员变量同名并且想要使用成员变量时
- 本类中构造方法间的相互调用
代表当前对象,哪个对象调用它就指哪个对象。点后面可以跟变量名与方法名,或者this(参数列表)调用构造器(不管是this还是super调用构造器,都只能放在第一行,所以不能同时写this()与super(),因为同时写总有一个不在第一行!),但是很少用。不加时其实系统会默认自动加上。具体操作见下面代码示例:
public class Test { //创建类
int id; //成员属性,可以不用初始化,系统自动初始化,整型默认为0,浮点默认0.0,boolean默认false,引用默认null
int n;
public Test() { //无参构造
}
public Test(int n) { //有参构造函数,重载
this(); //无意义,几乎不用
this.n = n; 调用当前对象的n变量
}
public Test(int id,int n) { //有参构造,重载
this(n); //有意义,但不常用
this.id = id; //调用当前对象的id变量
}
void show(){ //加不上this.都不会报错,不加系统会默认加上
System.out.println("我是Test类的show方法!我所在对象有两个成员变量,它们分别为:id="+id+",n="+this.n);
this.show1(); //调用本类方法
show1(); //调用本类方法
}
void show1(){
System.out.println("我是Test类的show1方法!");
}
public static void main(String[] args) {
Test test = new Test(); //创建一个对象
test.show(); //使用对象方法
}
}
三、引用数据类型数组
引用类型数组,顾名思义,就是用来存储引用类型数据的数组。与基本数据类型数组(元素是基本数据类型,初始值为0、0.0、false,不初始化直接使用不会报错)的区别就在于元素是引用数据类型,也就是对象,初始默认值为null,必须自己手动初始化,否则就是空指针异常。还有就是同样注意数组下标不能越界。
程序示例:
public class Test { //创建类
int id;
public static void main(String[] args) {
Test[] test = new Test[5]; //创建一个引用类型数组
test[0] = new Test(); //初始化第一个元素
for (int i = 0; i < test.length; i++) {
System.out.println(test[i]); //输出数组,第一个初始化了,指向了具体的对象,会输出地址,后面的都将为null
//System.out.println(test[i].id); //此行会在i等于1时报空指针异常,所以为了安全,引用类型数组必须初始化后再使用
}
}
}
四、继承与super关键字
与人类继承类似,Java中的类也存在继承关系。super关键字是对父类的一种资源运用。其使用与this类似,只是所代表的对象变为了当前对象所继承的父类对象。
1、继承
1、由关键词extends实现,被继承的类叫做超类,也叫父类;继承的类叫做派生类,也叫子类。实现继承后,子类拥有父类的除private关键词修饰和默认修饰(父子类异包)之外的所有属性与方法资源,但是父类不能拥有子类的任何资源。静态资源也能继承,但不能被重写。
继承具有单一继承(一个老爹)与多重继承(老爹的老爹的…)的性质。(这里补充一下:Java中没有多继承的概念,有的是多重继承的概念。我个人觉得从字面意思来看,多重继承就是继承传递性的描述,也就是父亲又有它的父亲···一重一重的继承关系,感觉很恰当,多继承从字面意思来看就是继承多个,这与继承的单一性背道而驰。但是在Java中恰恰是代表相反的意思,并且没有多继承,所以就是:Java不支持多重继承(这里多重的意思就是同时继承多个),个人感觉不好理解,所以我觉得在理解上就按照我自己觉得通俗易懂的方式理解,但是在进行某些描述时记得改过来换个说法就是。换壳不换芯的道理。)继承就是父类对子类共性的提取。
2、构造派生类之前必须先构造超类,派生类构造中若自己不调超类构造,则默认super()调超类无参构造,若自己调了,则不再默认提供
代码如下(示例):
2、super关键字
super:指代当前对象的超类对象(只能在创建的类中使用,而不能通过对象点super点父类的来引用)
super的用法:
1)super.成员变量名----------------访问超类的成员变量
2)super.方法名()------------------调用超类的方法
3)super()-------------------------调用超类的构造方法
程序示例:
public class Test { //创建类
public static void main(String[] args) {
new Sun().show1();
new Grandson().show2();
}
}
class Father{
int a; //成员变量
static int k;
static void staticTest(){
System.out.println("我是Father类的静态方法!");
}
String show(){ //方法
return"我是Father类的show()方法!";
}
}
class Sun extends Father{
int b; //自己的
void show1(){
System.out.println("我是Sun类的show1()方法!");
System.out.println("我能用我老爸的东西:"+super.a+super.show()+k); //使用老爸的静态与非静态资源
staticTest(); //父类静态资源
}
}
class Grandson extends Sun{
int c;
void show2(){
System.out.println("我是Grandson类的show2()方法!");
System.out.println("我能用我老爸与老爸的老爸的东西:"+super.a+k); //继承的传递性,老爸的老爸的,爷爷辈的
super.show1(); //老爸的
staticTest(); //爷爷辈的静态资源
}
}
五、Java向上转型
这里只做简单复习,具体详细的后面会专门对Java的向上转型与向下转型做复习。
1、父类的引用指向子类的对象,这里的继承关系同样满足传递性,也就是一个爷辈的超类同样也能指向孙辈的对象,这也是向上转型。理解时:从右往左看(Java语句的执行也是从右往左,只是我们写代码时习惯从左往右),右边对象类型小于左边的超类,所以就是向上转型。
2、使用:引用变量能用什么(也就是能点出什么),是以大类型为准,也就是引用的类型,而不是右边对象的类型,这是Java的规定。拿生活中的例子来看:你和你老爸长得像,你就打着他的名号去找他的好哥们借钱,那么你的一言一行肯定得按照你老爸的来,否则就穿帮了,这钱就“骗”不成了!哈哈!
六、方法的重写,与重载的区别
七、package与import
1、package关键字
Java的package就是用来解决Java类名冲突的,我们现在使用到的类很少的时候,我们或许能起个十多二十个(或多点或少点),但是成百上千,成千上万个?蒙了吧!所以Java的包,也就是package就是解决这一问题的!一个类真正的的全称是:包名+类名(Java的.class文件中类的名字就是:包名路径+类名,包名的.换成了/),包的命名也应该具有层次感,一般所有字母小写,用英文的点来创建子包!而包名的命名一般都是公司的域名反写。同包中的类不能重名。
2、import关键字
Java的import关键字是解决跨包访问的问题的。Java类的访问权限修饰只有public与默认(默认修饰的类不能被垮包访问,不能垮包被继承)的两种,这里只讨论public修饰的。Java想要垮包访问类,就需要使用import关键字导入被访问类,也就是我们所说的导包。不导包也能访问,但是必须把类的全程写上,这种方法繁琐易出错,不建议使用。
程序示例:
我创建了一个外包:cn.tedu,里面创建了两个Java文件,三个类;然后再在main包中的Main类访问应用
package cn.tedu; //本类所在包
/**
* 因为目前在达内学习,就用他们公司的域名了
**/
public class Test {
Test1 test1 = new Test1(); //本java文件能用
}
class Test1{
}
package cn.tedu; //本类所在包
public class Test2 {
Test1 test1 = new Test1(); //同包能用
}
package main; //本类所在包
import cn.tedu.*; //导包
public class Main
extends Test //继承外包的公共类,没问题
//extends Test1 //报错,垮包继承,只能继承public修饰的
{
Test test = new Test(); //使用外包类(公共的)
//Test1 test1 = new Test1(); //会报错,外包的非公共类(默认的)不能使用
}
八、访问控制修饰符
1、public:公共的,所有的类都能访问(类、类的属性方法)
2、private:私有的,仅本类能访问,子孙类都不能(类的属性方法)
3、protected:同包类,派生类、本类(类的属性方法)
4、默认的:同包类、本类(类的属性方法)
本类 | 同包 | 子类 | 任意 | |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | |
default | √ | √ | ||
private | √ |
说明:
1)类的访问修饰符只能是public或默认的
2)类中成员的访问修饰符如上4种都可以
示例程序(示范的类,同包类测试类,外包类测试类,外包类继承测试类,具体的包看每个类的最顶行):
package cn.tedu;
public class Test {
public int id;
protected int score;
private int age;
String name;
void show(){
System.out.println("age="+this.age+"这个变量别人都不能访问!除了我自己");
}
}
package cn.tedu;
public class Demo{
Test test = new Test();
public void show(){ //能访问三个(public、protected、默认的(同包)),除了private的不能访问
System.out.println("test.id="+test.id+",test.name="+test.name+",test.score ="+test.score);
}
}
package main;
import cn.tedu.Demo;
import cn.tedu.Test;
public class Main {
public static void main(String[] args) {
Test test = new Test();
System.out.println(test.id); //只能访问一个public修饰的变量,哪怕是通过它的对象
Demo demo = new Demo();
demo.show();
}
}
class C1 extends Test{//继承测试类
void show(){
System.out.println(super.id+super.score); //只能访问父类的这两个变量,还有一个private与默认修饰的变量无法访问,
//默认的不能访问是因为垮包继承了,默认修饰的无法垮包使用
}
}
九、final关键字与static关键字
1、final:最终的,不能改变的,不能修饰接口,因为接口就是用来被继承的。
修饰变量-----变量的值不能被改变,就成了一个常量
修饰方法-----修饰的方法允许被继承,被重载,但是不能被重写
修饰类--------被修饰的类不能被继承(不能生儿子!yan了!),但是能继承别的类
2、static:可以用来修饰变量(静态变量----所有对象所共享的数据(图片、音频、视频等))、方法(静态方法)、代码块(静态代码块)。
静态变量与静态方法都具有的特性:静态资源是所有的对象共享的,是属于类成员级别的,不论类对象存在多少个,都只会存在一份,存在于方法区,在类被加载时被加载,通常都是通过类名点方法/变量名访问使用,可以但不提倡使用对象点使用。
静态方法不具有隐士的this传递,不能直接访问实例成员,但是可以在里面new对象,再通过对象访问成员。想想:通过类名来访问的时候,当类加载的时候这个方法就加载出来了,所以它一定是先于所有的对象的,如果它自己不造对象来使用,就没有用的,没用的还偏要用,肯定报错了。就相当于!你拿把枪对着别人的脑袋,你说等我开枪了你再躲,但是你不能死!你必须给我活者!这能行吗?
静态代码块(用来初始化/加载静态资源(图片、音频、视频等))在类被加载的时候就被执行,只执行一次,因为类只加载一次。
3、static final:通过类名点访问,值不能被改变,必须初始化,命名建议所有英文字母大写,单词之间用_分割,存储在方法区中,编译器在编译时会将常量直接替换为具体的值,省了变量的取值操作,效率高,使用次数高时,用常量,方便修改,一两次时直接用值
示例程序:
package main;
public class Main {
static int m = 100; //定义并初始化一个静态变量
static final double PI = Math.PI; //定义一个“常量”,赋值不用管,反正就是Java给的圆周率
static String imgName;
static {
m++; //自加
//PI = 50.0; //报错,常量不能再被修改
imgName = "帅哥.png";
}
public static void main(String[] args) {//直接通过类名点访问,在执行到类名时就将类加载。但是都只执行一次(通过三次类名点与结果就能看出)
System.out.println("Main.imgName="+Main.imgName+",Main.PI="+Main.PI+"Main.m="+Main.m);
System.out.println("Main.PI="+PI); //main方法所在的类的常量可以不用类名点也能访问,也不建议也不不建议,自己斟酌
}
}
class A{
final void show(){
}
}
final class B extends A{ //可以继承别人
final void test(){
}
}
class C extends A //继承A
//extends B //报错,B类无法被继承
{
//void show(){ //重写父类的final方法,报错
//}
}
十、abstract关键字
Java的关键字abstract可以用来修饰类(修饰的类叫抽象类)与方法(修饰的方法叫抽象方法)。
Java抽象类不能被普通方式实例化(new对象,但是可以通过匿名内部类来创建对象,匿名内部类的本质也是创建了一个匿名类来继承此抽
象类)。
可以是作为一个引用变量的类型指向其子类的对象(数组引用也可以,数组对象实际就是指向一堆实例的引用),
可以与普通类一样拥有成员变量与方法(包括构造方法),需要被继承才能充分运用。因为不能实例化对象,所以不继承就无多大意义,
只有继承之后才能通过其指向的子类来使用,才有意义。虽然可以通过匿名内部类使用,但是别忘了它的本质也是继承。
含有抽象方法的类必须是抽象类,抽象类可以不含抽象方法。
存在的意义:某些行为在具体的子类对象中都不同,不能牵强的给他们确定,必须由它们自己写,但是为了出于对某种描述的完整性的考虑,
限制子类必须有这个行为。具体的运用:银行的贷款抵押时对历史抵押贷款是否还清的判断,必须重写判断,若调用者不判断,银行估计早就完蛋了。
抽象类也能继承抽象类
抽象方法不能含有方法体,规定它是不完整的,必须由子类来继承的时候重写来完整它,简言:父债子偿;当然儿子也可以耍赖让孙子来还债。即:将某个继承抽象类的类继续声明为抽象类,把抽象方法继续抛给下一个派生类。但是不常用。
某继承了一个抽象类的子类也可以在继续声明为抽象类的同时选择性的重写父类的某一部分抽象方法,以及拥有自己特有的抽象方法,其余父类的抽象方法则继续抛给继承它的子孙类来完成。
示例程序:
package main;
/**
* @program:
* @description:
* @author: Mr.XiaoShi
* @create: 2021-01-11 10:21
**/
/*Java抽象类与抽象方法演示*/
public abstract class Main { //不含抽象方法的抽象类
public static void main(String[] args) {
Test1 test1 = new Test1() { //匿名内部类方式
@Override
public void put() {
System.out.println("我是父类的抽象方法");
}
@Override
public void put1() {
}
@Override
public void show() {
System.out.println("我是我自己的抽象方法");
}
};
Test1 test12 = new Test2(); //向上造型
Main [] abstracts = new Main[]{new Test2(),new Test2()}; //数组
test1.put();
test1.show();
test12.put();
test12.show();
abstracts[1].put();
}
abstract public void put();
abstract public void put1();
}
abstract class Test1 extends Main{
public abstract void show();
public void put() {
System.out.println("这是父类的put抽象方法");
}
}
class Test2 extends Test1{ //子类必须把父类的所有抽象方法(包括父类继承下来的)重写完,否则编译报错
public void put1() {
System.out.println("这是爷辈的put1抽象方法");
}
public void show() {
System.out.println("这是父类的show抽象方法");
}
}
十一、接口
Java中的接口是由关键字interface定义的一种特殊的类,接口必须由类实现之后才具有意义。类实现接口通过关键字implement来完成实现。
自jdk1.8以后接口具有的一些特点:
- 1、接口中的属性只能有静态常量,不具有变量。接口的静态常量与类的类似,既能通过接口名.常量名访问,也能通过实现类的示例对象点出来访问。但是一般情况书写的时候只需要加类型与初始化就行了,那些修饰关键字是必须的,都会默认加上,写上反而增加了代码量。
- 2、接口中的方法只能是公共的抽象方法与公共的静态方法和默认方法,默认方法与静态方法允许拥有方法体。默认方法只能通过实现类的实例对象访问,静态方法只能通过接口名访问
- 3、接口没有构造器,也没有构造代码块(包括静态构造代码块)
- 4、接口与接口之间可以存在继承关系,而且接口可以允许多继承。
- 5、接口中的“普通方法”hui’b会被编译器默认加上public abstract修饰。
- 6、与普通类类似,接口的静态方法不能被重写覆盖。接口的静态资源只能被接口名调用。
- 7、默认方法的好处:如果一个已经投入使用的接口需要扩展一个新的方法,在JDK8以前,我们必须再该接口的所有实现类中都添加该方法的实现,否则编译会出错。如果实现类数量很少且我们有修改的权限,可能工作量会少,但是如果实现类很多或者我们没有修改代码的权限,这样的话就需要修改大量的类,不仅麻烦还容易出错。而默认方法提供的实现,使得新添加的接口功能就不会破坏现有的代码结构,想使用新功能时照常使用就行了。
程序示例:
package 接口测试;
import java.util.RandomAccess;
/*接口可以多继承接口(这里接口是随便找的Java的两个接口来演示,接口本身情况不必深究)*/
public interface Test extends Runnable, RandomAccess {
double m = 0.0; //不加修饰符默认public static final修饰
public final int a = 0; //默认添加static
public static final int k = 1; //允许多个静态常量
//public 接口测试.Test(){}; //报错,接口没有构造器
void show(); //这里虽然没有加上public abstract,但是它是存在的,由编译器自动加上
public abstract void show1();
public static void show2(){} //较高版本的jdk(1.8之后)在接口中支持静态方法与默认方法
public static void show3(){} //静态方法可以有多个
default void show4(){}; //默认方法
default void show5(){}; //默认方法也可以有多个
}
interface Test1{
default void show5(){}; //默认方法,与Test接口的默认方法相同
}
/*一个类继承了带有抽象方法的接口后,可以不重写抽象方法,但是必须把这个类变为抽象类*/
abstract class A implements Test{
}
/*否则必须重写接口的抽象方法*/
class B implements Test,Test1{
public static void main(String[] args) {
System.out.println(Test.m); //这个
/*接口不能被实例化,必须由实现类的实例化对对象来调用资源,匿名内部类也是创建了一个无名类来实现接口*/
Test t = new Test() {
@Override
public void show() {
}
@Override
public void show1() {
}
@Override
public void run() {
}
}; //匿名内部类后面的分号不能丢
t.show();t.show1();t.show4();t.show5();
//发现使用类名.普通的接口常量也不会报错,也就是普通的接口常量既可以通过接口名访问,
// 也可以通过实现类的对象引用访问
System.out.println("Test接口的常量a="+t.a+"\t以及静态常量k="+Test.k);
B b = new B();
b.show4(); //实现类对象使用默认方法
b.show5(); //实现类对象访问所实现的多个接口共有的并且自己重写后的默认方法
System.out.println("Test接口的常量a="+b.a+"\t以及静态常量k="+b.k);
}
@Override
public void run() {
}
@Override
public void show() {
}
@Override
public void show1() {
}
@Override
public void show4() {
}
/*下面对show5()方法的重写注释掉会报错,因为两个所实现的接口都有这个默认方法,子类必须重写,
否则对象执行时不知道运行谁的。即实现的多个接口都实现了某个默认方法时,实现类必须重写这个方法*/
@Override
public void show5() {
}
}