修饰符 class 类名 {
// 1.成员变量(属性)
// 2.成员方法 (行为)
// 3.构造方法 (初始化类的对象数据的)
}
类名 对象名称 = new 类名();
Student stu = new Student();
1. 利用private关键字来修饰成员变量
2. 利用public来修饰get和set方法
1. private修饰成员变量
```java
public class Student {
private String name;
private int age;
}
```
2. public修饰get和set方法
this就代表所在类当前对象的地址值,即当前对象
1. 用于普遍的get和set方法
2. 用于构造方法
static是静态的意思。 static可以修饰成员变量或者修饰方法。
有static修饰成员变量,说明他是属于类的,这个成员变量成为类变量或者静态成员变量。直接用类名访问。
public class Student {
String name; //实例的成员变量
int age;
static String teacherName; //静态成员变量
}
Student.teacherName = "gzk";//直接用类名调用即可
ps:因为类只有一个,所以静态成员变量在内存区域中也只存在一份。所有的对象都可以共享这个变量。
共享??
我们一个班的同学共同拥有一个班主任叫gzk,而我们每一个同学的teacher的成员变量中都是一样的班主任,在给成员赋值时,我们没有必要每一次都对teacher赋值。因此这个在书写成员变量时应该加上static关键字
static String teacherName; //静态成员变量
加上static关键字后,我们每一位同学就共享了这个变量。只需要赋一次值,对于每一个学生都是一样的。
无static修饰的成员变量属于每个对象的, 这个成员变量叫实例变量,之前我们写成员变量就是实例成员变量。
需要注意的是:实例成员变量属于每个对象,必须创建类的对象才可以访问。
格式:对象.实例成员变量
代码见上
有static修饰成员方法,说明这个成员方法是属于类的,这个成员方法称为类方法或者静态方法。 直接用 类名访问即可。因为类只有一个,所以静态方法在内存区域中也只存在一份。所有的对象都可以共享这个方法。
与静态成员变量一样,静态方法也是直接通过==类名.方法名称==即可访问。
public static void mehtod(){
System.out.println("静态方法");
}
Student.mehtod();//直接用类名调用
无static修饰的成员方法属于每个对象的,这个成员方法也叫做实例方法。
需要注意的是:实例方法是属于每个对象,必须创建类的对象才可以访问。
格式:对象.实例方法
在这里类中有许多相同或者类似的行为,每一个都写,很麻烦。这里要找关系做继承结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cof6hP6h-1689520570815)(C:\Users\郭giao\AppData\Roaming\Typora\typora-user-images\image-20230714220812131.png)]
通过 extends
关键字,可以声明一个子类继承另外一个父类,定义格式如下:
class 父类 {
...
}
class 子类 extends 父类 {
...
}
需要注意:Java是单继承的,一个类只能继承一个直接父类,跟现实世界很像,但是Java中的子类是更加强大的。
但是支持多层继承(爷爷)
不是,子类不能继承父类的构造方法。
why?
子类如果继承父类的构造方法,从方法名不和文件名(对象名字)一致性上就已经出错了。
正确:
public class Person { //父类
String name;
int age;
public Person() {
System.out.println("父类的无参构造");
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Student extends Person { //子类,继承父类
public Student(){
//子类构造方法中隐藏的super()去访问父类的无参构造
super();//必须写在有效代码的第一行 ,默认访问了父类的无参构造
System.out.println("子类的无参构造");
}
public Student(String name ,int age){
super(name,age);
}
}
值得注意的是子类可以继承父类的私有成员(成员变量,方法),只是子类无法直接访问而已,可以通过getter/setter方法访问父类的private成员变量。
public class Demo03 {
public static void main(String[] args) {
Zi z = new Zi();
System.out.println(z.num1);
// System.out.println(z.num2); // 私有的子类无法使用
// 通过getter/setter方法访问父类的private成员变量
System.out.println(z.getNum2());
z.show1();
// z.show2(); // 私有的子类无法使用
}
}
class Fu {
public int num1 = 10;
private int num2 = 20;
public void show1() {
System.out.println("show1");
}
private void show2() {
System.out.println("show2");
}
public int getNum2() {
return num2;
}
public void setNum2(int num2) {
this.num2 = num2;
}
}
class Zi extends Fu {
}
如果子类和父类的成员变量不重名,这时访问是没有影响的。
class Fu {
// Fu中的成员变量
int num = 5;
}
class Zi extends Fu {
// Zi中的成员变量
int num2 = 6;
// Zi中的成员方法
public void show() {
// 访问父类中的num
System.out.println("Fu num="+num); // 继承而来,所以直接访问。
// 访问子类中的num2
System.out.println("Zi num2="+num2);
}
}
class Demo04 {
public static void main(String[] args) {
// 创建子类对象
Zi z = new Zi();
// 调用子类中的show方法
z.show();
}
}
演示结果:
Fu num = 5
Zi num2 = 6
如果子类和父类成员变量出现重名,访问受到影响
class Fu1 {
// Fu中的成员变量。
int num = 5;
}
class Zi1 extends Fu1 {
// Zi中的成员变量
int num = 6;
public void show() {
// 访问父类中的num
System.out.println("Fu num=" + num);
// 访问子类中的num
System.out.println("Zi num=" + num);
}
}
class Demo04 {
public static void main(String[] args) {
// 创建子类对象
Zi1 z = new Zi1();
// 调用子类中的show方法
z1.show();
}
}
演示结果:
Fu num = 6
Zi num = 6
这里代码中,我们在子类中定义了个show方法,在show方法里想要去访问父类和子类中的num。由于这个方法写在子类中,在成员变量重名的影响下,他会先去本类中(这里是子类)寻找num,如果找到则执行,如果没找到,则去父类中找。(这就是为什么输出了num都是6的原因)
怎么解决???
用到了super关键字
子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用super
关键字,修饰父类成员变量,类似于之前学过的 this
。
需要注意的是:super代表的是父类对象的引用,this代表的是当前对象的引用。
super.父类成员变量名
class Fu {
// Fu中的成员变量。
int num = 5;
}
class Zi extends Fu {
// Zi中的成员变量
int num = 6;
public void show() {
int num = 1;
// 访问方法中的num
System.out.println("method num=" + num);
// 访问子类中的num
System.out.println("Zi num=" + this.num);
// 访问父类中的num
System.out.println("Fu num=" + super.num);
}
}
class Demo04 {
public static void main(String[] args) {
// 创建子类对象
Zi1 z = new Zi1();
// 调用子类中的show方法
z1.show();
}
}
演示结果:
method num=1
Zi num=6
Fu num=5
Fu类中的成员是非私有的,我们可以直接访问
如果是私有的,我们就需要用到gette和setter方法了
如果子类父类中出现不重名的成员方法,这时的调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。
class Fu {
public void show() {
System.out.println("Fu类中的show方法执行");
}
}
class Zi extends Fu {
public void show2() {
System.out.println("Zi类中的show2方法执行");
}
}
public class Demo05 {
public static void main(String[] args) {
Zi z = new Zi();
//子类中没有show方法,但是可以找到父类方法去执行
z.show();
z.show2(); //子类有show2方法直接执行
}
}
如果子类父类中出现==重名的成员方法,则创建子类对象调用该方法的时候,子类对象会优先调用自己的方法==。
class Fu {
public void show() {
System.out.println("Fu show");
}
}
class Zi extends Fu {
//子类重写了父类的show方法
public void show() {
System.out.println("Zi show");
}
}
public class ExtendsDemo05{
public static void main(String[] args) {
Zi z = new Zi();
// 子类中有show方法,只执行重写后的show方法
z.show(); // Zi show
}
}
继承后子类构方法器特点:子类所有构造方法的第一行都会默认先调用父类的无参构造方法
class Person {
private String name;
private int age;
public Person() {
System.out.println("父类无参");
}
// getter/setter省略
}
class Student extends Person {
private double score;
public Student() {
//super(); // 调用父类无参,默认就存在,可以不写,必须在第一行
System.out.println("子类无参");
}
public Student(double score) {
//super(); // 调用父类无参,默认就存在,可以不写,必须再第一行
this.score = score;
System.out.println("子类有参");
}
}
public class Demo07 {
public static void main(String[] args) {
Student s1 = new Student();
System.out.println("----------");
Student s2 = new Student(99.9);
}
}
输出结果:
父类无参
子类无参
----------
父类无参
子类有参
方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。
子类继承父类的方法,但是父类方法不满足子类的需求,则需对子类的方法重写一个与父类名字相同的方法,以覆盖父类的方法。
public class Animal {
public void run(){
System.out.println("动物跑的很快!");
}
public void cry(){
System.out.println("动物都可以叫~~~");
}
}
public class Cat extends Animal {
public void cry(){
System.out.println("我们一起学猫叫,喵喵喵!喵的非常好听!");
}
}
public class Test {
public static void main(String[] args) {
// 创建子类对象
Cat ddm = new Cat();
// 调用父类继承而来的方法
ddm.run();
// 调用子类重写的方法
ddm.cry();
}
}
@Override:注解,重写注解校验!
这个注解标记的方法,就说明这个方法必须是重写父类的方法,否则编译阶段报错。
建议重写都加上这个注解,一方面可以提高代码的可读性,一方面可以防止重写出错!
加上后的子类代码形式如下:
public class Cat extends Animal {
// 声明不变,重新实现
// 方法名称与父类全部一样,只是方法体中的功能重写写了!
@Override
public void cry(){
System.out.println("我们一起学猫叫,喵喵喵!喵的非常好听!");
}
}
// 一个类只能有一个父类,不可以有多个父类。
class A {}
class B {}
class C1 extends A {} // ok
// class C2 extends A, B {} // error
// A可以有多个子类
class A {}
class C1 extends A {}
class C2 extends A {}
class A {}
class C1 extends A {}
class D extends C1 {}
顶层父类是Object类。所有的类默认继承Object,作为父类。
父类类型 变量名=new 子类/实现类构造器;
变量名.方法名();
如果没有多态,下图中register方法只能传递学生对象,但是Teacher和administrator也可以注册,但是他俩调用不了register方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j9sQ1fvC-1689520570816)(C:\Users\郭giao\AppData\Roaming\Typora\typora-user-images\image-20230715144358262.png)]
有了多态之后,方法的形参就可以定义为共同的父类Person。
要注意的是:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cFrwbaNp-1689520570816)(C:\Users\郭giao\AppData\Roaming\Typora\typora-user-images\image-20230715144945349.png)]
代码示例:
父类:
public class Person {
private String name;
private int age;
空参构造
带全部参数的构造
get和set方法
public void show() {
System.out.println(name + ", " + age);
}
}
子类1:
public class Administrator extends Person {
@Override
public void show() {
System.out.println("管理员的信息为:" + getName() + ", " + getAge());
}
}
子类2:
public class Student extends Person {
@Override
public void show() {
System.out.println("学生的信息为:" + getName() + ", " + getAge());
}
}
子类3:
public class Teacher extends Person {
@Override
public void show() {
System.out.println("老师的信息为:" + getName() + ", " + getAge());
}
}
测试类:
public class Test {
public static void main(String[] args) {
//创建三个对象,并调用register方法
Student s = new Student();
s.setName("张三");
s.setAge(18);
Teacher t = new Teacher();
t.setName("王建国");
t.setAge(30);
Administrator admin = new Administrator();
admin.setName("管理员");
admin.setAge(35);
register(s);
register(t);
register(admin);
}
//这个方法既能接收老师,又能接收学生,还能接收管理员
//只能把参数写成这三个类型的父类
public static void register(Person p) {
p.show();
}
}
多态: 是指同一行为,具有多个不同表现形式。
从上面案例可以看出,Cat和Dog都是动物,都是吃这一行为,但是出现的效果(表现形式)是不一样的。
前提
父类类型:指子类对象继承的父类类型,或者实现类的父接口类型
调用成员变量时:编译看左边,运行也看左边。(多态调用成员变量的两也)
调用成员方法时:编译看左边,运行看右边。(多态调用成员方法的先左后右)
代码体现:
public class Test {
public static void main(String[] args) {
//创建对象(多态方式)
//Fu f =new Zi();
Animal a = new Dog(); //多态
//调用成员变量:编译看左边,运行也看左边
//编译看左边:javac编译代码的时候,会看左边的父类中有没有这个变量(name),如果没有,则报错
//运行看左边:运行时,实际获取的时左边父类中成员变量的值
System.out.println(a.name); //动物(父类的成员变量)
//调用成员方法:编译看左边,运行看右边
//编译看左边:在编译时,会看左边的父类是否有这个方法,如果没有则编译失败
//运行看右边:在运行时,实际上运行的是子类中的方法
a.show(); //Dog--show (子类的方法)
}
}
class Animal { //父类
String name = "动物";
public void show() {
System.out.println("Animal--show");
}
}
class Dog extends Animal { //子类
String name = "狗";
@Override
public void show() {
System.out.println("Dog---show");
}
}
我们已经知道多态编译阶段是看左边父类类型的,这样就会导致,在子类中的某些独有的功能就无法通过多态的写法访问了。
多态的弊端:不能够访问子类的==独有(特有)==方法
原因: 多态中调用方法,要先去父类中看有无此方法,如果无则不能运行
这里了话,既然是子类 的特有方法,那么在父类中是肯定没有的。因此不能够调用到子类的特有方法。
解决办法: 变回子类类型就可以了。
Dog d = (Dog) a;
((Dog)a).lookHome;
细节: 转换的时候不能乱转,如果转错类型就会出错
//instanceof关键字
if(a instanceof Dog){ //a是狗吗 是---true 不是--false
Dog d=(Dog)a;
((Dog)a).lookHome();
}else if(a instanceof Cat){ //a是猫吗
Cat c=(Cat)a;
c.catchMouse();
}else{
System.out.println("没有这个类型无法转换");
}
多态的写法无法访问子类的特有功能了。
回顾基本数据类型转换:
多态的转型分为向上转型(自动)和向下转型(强制)两种。
父类类型 变量名=new 子类类型();
如:Animal a=new Cat();
原因是:父类类型相对与子类来说是大范围的类型,Animal是动物类,是父类类型。Cat是猫类,是子类类型。Animal类型的范围当然很大,包含一切动物。
所以子类范围小可以直接自动转型给父类类型的变量。
向下转型:父类向子类转换的过程。——强制的
一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
使用格式:
子类类型 变量名=(子类类型)父类变量名;
如:Aniaml a=new Cat();
Cat c=(Cat)a;
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用
子类拥有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。
转型演示,代码如下:
定义类:
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void watchHouse() {
System.out.println("看家");
}
}
定义测试类:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Cat c = (Cat) a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
}
}
转型的过程中,一不小心就会遇到这样的问题,请看如下代码:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Dog d = (Dog) a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
}
}
这段代码可以通过编译,但是运行时,却报出了 ClassCastException
,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。
为了避免ClassCastException的发生,Java提供了 instanceof
关键字,给引用变量做类型的校验,格式如下:
变量名 instanceof 数据类型
如果变量属于该数据类型或者其子类类型,返回true。
如果变量不属于该数据类型或者其子类类型,返回false。
所以,转换前,我们最好先做一个判断,代码如下:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
if (a instanceof Cat) {
Cat c = (Cat) a;
c.catchMouse(); // 调用的是 Cat 的 catchMouse
} else if (a instanceof Dog) {
Dog d = (Dog) a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
}
}
JDK14的时候提出了新特性,把判断和强转合并成了一行
//新特性
//先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
//如果不是,则不强转,结果直接是false
if(a instanceof Dog d){
d.lookHome();
}else if(a instanceof Cat c){
c.catchMouse();
}else{
System.out.println("没有这个类型,无法转换");
}
包名的命名规范:
域名.路径名.xxx.xxx
// 例如:com.itheima.oa
什么时候需要导包?
情况一:在使用Java中提供的非核心包中的类时
情况二:使用自己写的其他包中的类时
什么时候不需要导包?
情况一:在使用Java核心包(java.lang)中的类时
情况二:在使用自己写的同一个包中的类时
假设demo1和demo2中都有一个Student该如何使用?
代码示例:
//使用全类名的形式即可。
//全类名:包名 + 类名
//拷贝全类名的快捷键:选中类名crtl + shift + alt + c 或者用鼠标点copy,再点击copy Reference
com.itheima.homework.demo1.Student s1=new com.itheima.homework.demo1.Student();
com.itheima.homework.demo2.Students2=new com.itheima.homework.demo2.Student();
在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限,我们之前已经学习过了public 和
private,接下来我们研究一下protected和默认修饰符的作用。
public:公共的,所有地方都可以访问。
protected:本类 ,本包,其他包中的子类都可以访问。
默认(没有修饰符):本类 ,本包可以访问。
注意:默认是空着不写,不是default
private:私有的,当前类可以访问。
`public > protected > 默认 > private
public | protected | 默认 | private | |
---|---|---|---|---|
同一类中 | √ | √ | √ | √ |
同一包中的类 | √ | √ | √ | |
不同包的子类 | √ | √ | ||
不同包中的无关类 | √ |
可见,public具有最大权限。private则是最小权限。
编写代码时,如果没有特殊的考虑,建议这样使用权限:
private
,隐藏细节。 public
,方便创建对象。public
,方便调用方法。小贴士:不加权限修饰符,就是默认权限
学习了继承后,我们知道,子类可以在父类的基础上改写父类内容,比如,方法重写。
如果有一个方法我不想别人去改写里面内容,该怎么办呢?
Java提供了final
关键字,表示修饰的内容不可变。
final修饰的类,不能够被继承
格式如下:
final class 类名 {
}
代码:
final class Fu {
}
// class Zi extends Fu {} // 报错,不能继承final的类
查询API发现像 public final class String
、public final class Math
、public final class Scanner
等,很多我们学习过的类,都是被final修饰的,目的就是供我们使用,而不让我们所以改变其内容。
final修饰的方法,不可以被重写
格式如下:
修饰符 final 返回值类型 方法名(参数列表){
//方法体
}
代码:
class Fu2 {
final public void show1() {
System.out.println("Fu2 show1");
}
public void show2() {
System.out.println("Fu2 show2");
}
}
class Zi2 extends Fu2 {
// @Override
// public void show1() {
// System.out.println("Zi2 show1");
// }
@Override
public void show2() {
System.out.println("Zi2 show2");
}
}
局部变量——基本类型
基本类型的局部变量,被final修饰后,只能赋值一次,不能再更改。代码如下:
public class FinalDemo1 {
public static void main(String[] args) {
// 声明变量,使用final修饰
final int a;
// 第一次赋值
a = 10;
// 第二次赋值
a = 20; // 报错,不可重新赋值
// 声明变量,直接赋值,使用final修饰
final int b = 10;
// 第二次赋值
b = 20; // 报错,不可重新赋值
}
}
思考,下面两种写法,哪种可以通过编译?
写法1:
final int c=0;
for(int i=0;i< 10;i++){
c=i;
System.out.println(c);
}
写法2:
for(int i=0;i< 10;i++){
final int c=i;
System.out.println(c);
}
根据 final
的定义,写法1报错!写法2,为什么通过编译呢?因为每次循环,都是一次新的变量c。这也是大家需要注意的地方。
成员变量涉及到初始化的问题,初始化方式有显示初始化和构造方法初始化,只能选择其中一个:
public class Student {
final int num = 10;
}
构造方法初始化(在构造方法中赋值一次)(不常用,了解即可)。
注意:每个构造方法中都要赋值一次!
public class Student {
final int num = 10;
final int num2;
public Student() {
this.num2 = 20;
// this.num2 = 20;
}
public Student(String name) {
this.num2 = 20;
// this.num2 = 20;
}
}
被final修饰的常量名称,一般都有书写规范,所有字母都大写。
父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了(因为子类对象会调用自己重写的方法)。换句话说,父类可能知道子类应该有哪个功能,但是功能具体怎么实现父类是不清楚的(由子类自己决定),父类只需要提供一个没有方法体的定义即可,具体实现交给子类自己去实现。我们把没有方法体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类。
abstract是抽象的意思,用于修饰方法方法和类,修饰的方法是抽象方法,修饰的类是抽象类。
使用abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。
定义格式:
修饰符 abstract 返回值类型 方法名 (参数列表);
代码举例:
public abstract void run();
如果一个类包含抽象方法,那么该类必须是抽象类。注意:抽象类不一定有抽象方法,但是有抽象方法的类必须定义成抽象类。
定义格式:
abstract class 类名字 {
}
代码举例:
public abstract class Animal {
public abstract void run();
}
要求:继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。
代码举例:
// 父类,抽象类
abstract class Employee {
private String id;
private String name;
private double salary;
public Employee() {
}
public Employee(String id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
// 抽象方法
// 抽象方法必须要放在抽象类中
abstract public void work();
}
// 定义一个子类继承抽象类
class Manager extends Employee {
public Manager() {
}
public Manager(String id, String name, double salary) {
super(id, name, salary);
}
// 2.重写父类的抽象方法
@Override
public void work() {
System.out.println("管理其他人");
}
}
// 定义一个子类继承抽象类
class Cook extends Employee {
public Cook() {
}
public Cook(String id, String name, double salary) {
super(id, name, salary);
}
@Override
public void work() {
System.out.println("厨师炒菜多加点盐...");
}
}
// 测试类
public class Demo10 {
public static void main(String[] args) {
// 创建抽象类,抽象类不能创建对象
// 假设抽象类让我们创建对象,里面的抽象方法没有方法体,无法执行.所以不让我们创建对象
// Employee e = new Employee();
// e.work();
// 3.创建子类
Manager m = new Manager();
m.work();
Cook c = new Cook("ap002", "库克", 1);
c.work();
}
}
此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法。
抽象类的特征总结起来有得有失。
有得:抽象类得到了拥有抽象方法的能力。
有失:抽象类失去了创建对象的能力。
其他成员(构造方法,实例方法,静态方法等)抽象类都是具备的。
当idea报错时,知道怎么改即可
细节:
1. 抽象类不能创建对象,如果创建,编译报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
- 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
理解:子类的构造方法中,有默认的super(),需要访问父类构造方法。
- 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
- 抽象类的子类,必须重写抽象父类中==所有的抽象方法==,否则子类也必须定义成抽象类,编译无法通过而报错。
理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
- 抽象类存在的意义是为了被子类继承。
理解:抽象类中已经实现的是模板中确定的成员,抽象类不确定如何实现的定义成抽象方法,交给具体的子类去实现。
抽象类存在的意义是为了被子类继承,否则抽象类将毫无意义。抽象类可以强制让子类,一定要按照规定的格式进行重写。
我们已经学完了抽象类,抽象类中可以用抽象方法,也可以有普通方法,构造方法,成员变量等。那么什么是接口呢?接口是更加彻底的抽象,JDK7之前,包括JDK7,接口中全部是抽象方法。接口同样是不能创建对象的。
//接口的定义格式:
interface 接口名称{
// 抽象方法
}
// 接口的声明:interface
// 接口名称:首字母大写,满足“驼峰模式”
在JDK7,包括JDK7之前,接口中的只有包含:抽象方法和常量
注意:接口中的抽象方法默认会自动加上public abstract修饰程序员无需自己手写!!
按照规范:以后接口中的抽象方法建议不要写上public abstract。因为没有必要啊,默认会加上。
在接口中定义的成员变量默认会加上: public static final修饰。也就是说在接口中定义的成员变量实际上是一个常量。这里是使用public static final修饰后,变量值就不可被修改,并且是静态化的变量可以直接用接口名访问,所以也叫常量。常量必须要给初始值。常量命名规范建议字母全部大写,多个单词用下划线连接。
public interface InterF {
// 抽象方法!
// public abstract void run();
void run();
// public abstract String getName();
String getName();
// public abstract int add(int a , int b);
int add(int a , int b);
// 它的最终写法是:
// public static final int AGE = 12 ;
int AGE = 12; //常量
String SCHOOL_NAME = "黑马程序员";
}
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements关键字。
/**接口的实现:
在Java中接口是被实现的,实现接口的类称为实现类。
实现类的格式:*/
class 类名 implements 接口1,接口2,接口3...{
}
意义:接口体现的是一种规范,接口对实现类是一种强制性的约束,要么全部完成接口申明的功能,要么自己也定义成抽象类。这正是一种强制性的规范。
假如我们定义一个运动员的接口(规范),代码如下:
/**
接口:接口体现的是规范。
* */
public interface SportMan {
void run(); // 抽象方法,跑步。
void law(); // 抽象方法,遵守法律。
String compittion(String project); // 抽象方法,比赛。
}
接下来定义一个乒乓球运动员类,实现接口,实现接口的实现类代码如下:
package com.itheima._03接口的实现;
/**
* 接口的实现:
* 在Java中接口是被实现的,实现接口的类称为实现类。
* 实现类的格式:
* class 类名 implements 接口1,接口2,接口3...{
*
*
* }
* */
public class PingPongMan implements SportMan {
@Override
public void run() {
System.out.println("乒乓球运动员稍微跑一下!!");
}
@Override
public void law() {
System.out.println("乒乓球运动员守法!");
}
@Override
public String compittion(String project) {
return "参加"+project+"得金牌!";
}
}
测试代码:
public class TestMain {
public static void main(String[] args) {
// 创建实现类对象。
PingPongMan zjk = new PingPongMan();
zjk.run();
zjk.law();
System.out.println(zjk.compittion("全球乒乓球比赛"));
}
}
类与接口之间的关系是多实现的,一个类可以同时实现多个接口。
首先我们先定义两个接口,代码如下:
/** 法律规范:接口*/
public interface Law {
void rule();
}
/** 这一个运动员的规范:接口*/
public interface SportMan {
void run();
}
然后定义一个实现类:
/**
* Java中接口是可以被多实现的:
* 一个类可以实现多个接口: Law, SportMan
*
* */
public class JumpMan implements Law ,SportMan {
@Override
public void rule() {
System.out.println("尊长守法");
}
@Override
public void run() {
System.out.println("训练跑步!");
}
}
从上面可以看出类与接口之间是可以多实现的,我们可以理解成实现多个规范,这是合理的。
Java中,接口与接口之间是可以多继承的:也就是一个接口可以同时继承多个接口。大家一定要注意:
类与接口是实现关系
接口与接口是继承关系
接口继承接口就是把其他接口的抽象方法与本接口进行了合并。
案例演示:
public interface Abc {
void go();
void test();
}
/** 法律规范:接口*/
public interface Law {
void rule();
void test();
}
*
* 总结:
* 接口与类之间是多实现的。
* 接口与接口之间是多继承的。
* */
public interface SportMan extends Law , Abc {
void run();
}
只要重写一次即可。此时重写的方法,既表示重写1接口的,也表示重写2接口的。
继承的父类,就好比是亲爸爸一样
实现的接口,就好比是干爹一样
可以继承一个类的同时,再实现多个接口,只不过,要把接口里面所有的抽象方法,全部实现。
实现类可以继承一个抽象类的同时,再实现其他多个接口,只不过要把里面所有的抽象方法全部重写。
处理办法一:如果父类中的方法体,能满足当前业务的需求,在子类中可以不用重写。
处理办法二:如果父类中的方法体,不能满足当前业务的需求,需要在子类中重写。
可以在接口跟实现类中间,新建一个中间类(适配器类)
让这个适配器类去实现接口,对接口里面的所有的方法做空重写。
让子类继承这个适配器类,想要用到哪个方法,就重写哪个方法。
因为中间类没有什么实际的意义,所以一般会把中间类定义为抽象的,不让外界创建对象
将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。可以把内部类理解成寄生,外部类理解成宿主。
一个事物内部还有一个独立的事物,内部的事物脱离外部的事物无法独立使用
B类表示的事物是A类的一部分,且B单独存在没有意义。
按定义的位置来分
成员内部类的特点:
内部类的使用格式:
外部类.内部类。 // 访问内部类的类型都是用 外部类.内部类
获取成员内部类对象的两种方式:
方式一: 外部直接创建成员变量的对象
外部类.内部类 变量 = new 外部类().new 内部类();
方式一:
public class Test {
public static void main(String[] args) {
// 宿主:外部类对象。
// Outer out = new Outer();
// 创建内部类对象。
Outer.Inner oi = new Outer().new Inner();
oi.method();
}
}
class Outer {
// 成员内部类,属于外部类对象的。
// 拓展:成员内部类不能定义静态成员。
public class Inner{
// 这里面的东西与类是完全一样的。
public void method(){
System.out.println("内部类中的方法被调用了");
}
}
}
方式二:在外部类中定义一个方法提供内部类的对象
方式二:
public class Outer {
String name;
private class Inner{
static int a = 10;
}
public Inner getInstance(){
return new Inner(); //返回内部类的对象
}
}
public class Test {
public static void main(String[] args) {
Outer o = new Outer();
System.out.println(o.getInstance());
}
}
编写成员内部类的注意点:
详解:
内部类被private修饰,外界无法直接获取内部类的对象,只能通过3.3节中的方式二获取内部类的对象
被其他权限修饰符修饰的内部类一般用3.3节中的方式一直接获取内部类的对象
内部类被static修饰是成员内部类中的特殊情况,叫做静态内部类下面单独学习。
内部类如果想要访问外部类的成员变量,外部类的变量必须用final修饰,JDK8以前必须手动写final,JDK8之后不需要手动写,JDK默认加上。
请在?地方向上相应代码,以达到输出的内容
注意:内部类访问外部类对象的格式是:外部类名.this
public class Test {
public static void main(String[] args) {
Outer.inner oi = new Outer().new inner();
oi.method();
}
}
class Outer { // 外部类
private int a = 30;
// 在成员位置定义一个类
class inner {
private int a = 20;
public void method() {
int a = 10;
System.out.println(???); // 10 答案:a
System.out.println(???); // 20 答案:this.a
System.out.println(???); // 30 答案:Outer.this.a
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lyu3i3qP-1689520570817)(C:\Users\郭giao\AppData\Roaming\Typora\typora-user-images\image-20230715220214454.png)]
在内部对象002中,有一个Outer this 指向的地址是001(外部类),所以从内部类访问外部类可以通过这个。
外部类名.内部类名 对象名 = new 外部类名.内部类名();
Outer.Inner oi = new Outer.Inner();
oi.show1();
外部类名.内部类名.方法名();
Outer.Inner.show2();
定义格式:
class 外部类名 {
数据类型 变量名;
修饰符 返回值类型 方法名(参数列表) {
// …
class 内部类 {
// 成员变量
// 成员方法
}
}
}
public class Outer {
public void show(){
int a = 10;//局部变量
//局部内部类
class Inner{
}
}
}
能够修饰局部变量的,都能修饰局部内部类,比如final。。。。
public class Outer {
public void show(){
int a = 10;//局部变量
//局部内部类
class Inner{
String name;
int age;
public void method1(){
System.out.println("局部内部类中的method1");
}
public static void method2(){
System.out.println("局部内部类中的method2静态方法");
}
}
//创建对象
Inner i = new Inner();
System.out.println(i.name);
System.out.println(i.age);
i.method1();
Inner.method2();
}
}
该类可以访问外部类的成员,也可以访问方法内的局部变量。
public class Outer {
int b = 12;//外部
public void show() {
int a = 10;//局部变量
//局部内部类
class Inner {
String name;
int age;
public void method1() {
//访问外部
System.out.println(b);
//方法内的局部变量
System.out.println(a)
System.out.println("局部内部类中的method1");
}
}
}
}
**匿名内部类: ** 是内部类的简化写法,本质上就是隐藏了名字的内部类。(比较常用)
可以写在成员位置,或者局部位置
new 类名/接口名(){
重写方法;
};
包含了:
从语法上看,这个整体其实是匿名内部类的对象
格式的细节:整体就是一个类的子类对象或者接口的实现类对象
实际上,如果我们希望定义一个只要使用一次的类,就可以考虑匿名内部类。
匿名内部类的本质作用:简化代码
之前我们使用接口,要做一下操作:
定义子类
重写接口方法
创建子类对象
调用重写方法
interface Swim {
public abstract void swimming();
}
// 1. 定义接口的实现类
class Student implements Swim {
// 2. 重写抽象方法
@Override
public void swimming() {
System.out.println("狗刨式...");
}
}
public class Test {
public static void main(String[] args) {
// 3. 创建实现类对象
Student s = new Student();
// 4. 调用方法
s.swimming();
}
}
我们的目的,最终只是为了调用方法,那么能不能简化一下,把以上四步合成一步呢?匿名内部类就是做这样的快捷方式。
匿名内部类必须继承一个父类或者实现一个接口
匿名内部类格式:
new 父类名或者接口名(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};
public class Animal {
public void eat(){
System.out.println("吃东西");
}
}
public class Test {
public static void main(String[] args) {
/*
new 类名或者接口名() {
重写方法;
};
*/
//Dog d= new Dog();
//d.eat();
method(
new Animal() {
@Override
public void eat() {
System.out.println("重写狗吃吃");
}
}
);
}
public static void method(Animal a) { //Animal a = 子类对象 多态
a.eat();//编译看左边,运行看右边
}
}
package 面向对象进阶.内部类.匿名内部类;
public class Test2 {
public static void main(String[] args) {
Swim s = new Swim() {
@Override
public void swim() {
System.out.println("重写之后的swim方法");
}
};
s.swim(); // 接口的多态
new Swim() {
@Override
public void swim() {
System.out.println("重写之后的swim方法");
}
}.swim();
//对象.方法
}
}
}
}
}
### 9. 匿名内部类
#### 1. 概述
**匿名内部类: ** 是内部类的简化写法,本质上就是隐藏了名字的内部类。(比较常用)
可以写在成员位置,或者局部位置
#### 2. 格式
```java
new 类名/接口名(){
重写方法;
};
包含了:
从语法上看,这个整体其实是匿名内部类的对象
格式的细节:整体就是一个类的子类对象或者接口的实现类对象
实际上,如果我们希望定义一个只要使用一次的类,就可以考虑匿名内部类。
匿名内部类的本质作用:简化代码
之前我们使用接口,要做一下操作:
定义子类
重写接口方法
创建子类对象
调用重写方法
interface Swim {
public abstract void swimming();
}
// 1. 定义接口的实现类
class Student implements Swim {
// 2. 重写抽象方法
@Override
public void swimming() {
System.out.println("狗刨式...");
}
}
public class Test {
public static void main(String[] args) {
// 3. 创建实现类对象
Student s = new Student();
// 4. 调用方法
s.swimming();
}
}
我们的目的,最终只是为了调用方法,那么能不能简化一下,把以上四步合成一步呢?匿名内部类就是做这样的快捷方式。
匿名内部类必须继承一个父类或者实现一个接口
匿名内部类格式:
new 父类名或者接口名(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};
public class Animal {
public void eat(){
System.out.println("吃东西");
}
}
public class Test {
public static void main(String[] args) {
/*
new 类名或者接口名() {
重写方法;
};
*/
//Dog d= new Dog();
//d.eat();
method(
new Animal() {
@Override
public void eat() {
System.out.println("重写狗吃吃");
}
}
);
}
public static void method(Animal a) { //Animal a = 子类对象 多态
a.eat();//编译看左边,运行看右边
}
}
package 面向对象进阶.内部类.匿名内部类;
public class Test2 {
public static void main(String[] args) {
Swim s = new Swim() {
@Override
public void swim() {
System.out.println("重写之后的swim方法");
}
};
s.swim(); // 接口的多态
new Swim() {
@Override
public void swim() {
System.out.println("重写之后的swim方法");
}
}.swim();
//对象.方法
}
}