第一章 static关键字
1.static关键字
1.1 概述
有些时候,我们希望在类中定义一些能够被类的所有对象所共享的全局内容,这些内容仅在内存中存储一份,以便于我们对这些共享内容的管理和维护,同时也能够节省更多的内存空间。
静态,就是 Java 提供给我们解决上述问题的一种机制,使用 static 关键字修饰的成员(包括成员变量、成员方法、代码块和内部类)就是类的静态成员。静态成员最大的特点就是它属于类,而不是对象。我们可以在不创建对象的情况下,直接通过类访问静态成员。
需要注意的是,静态成员是随着类的加载而存在的,这意味着静态成员始终优先于对象存在。
静态成员的好处:
- 全局的,可以被所有对象所共享,在内存中只存储一份,更节省内存空间。
- 可以直接通过类访问而不用创建对象,编码效率更高。
静态成员的缺点:
- 静态成员是属于类的,类不被卸载,静态成员就一直存在。而实例成员会随着对象的销毁一并被 GC 回收。
1.2 修饰成员变量
格式:
- 格式:
修饰符 static 数据类型 变量名;
特点:
- 被static修饰的成员变量叫做静态成员变量\类变量
- 被static修饰的成员变量会被该类的所有对象共享
- 如果该类的某个对象修改了静态成员变量的值,那么该类的所有对象使用的都是修改后的值
访问方式:
- 对象名.静态成员变量名
- 类名.静态成员变量名 ------->推荐\掌握
案例:
public class Chinese {
// 非静态成员变量
String name;
// 静态成员变量
static String country;
public Chinese(String name,String country) {
this.name = name;
this.country = country;
}
public Chinese() {
}
}
public class Test {
public static void main(String[] args) {
/*
- 格式: 修饰符 static 数据类型 变量名;
特点:
- 被static修饰的成员变量叫做静态成员变量\类变量
- 被static修饰的成员变量会被该类的所有对象共享
- 如果该类的某个对象修改了静态成员变量的值,那么该类的所有对象使用的都是修改后的值
访问方式:
- 对象名.静态成员变量名
- 类名.静态成员变量名 ------->推荐\掌握
*/
// 创建Chinese对象
Chinese c1 = new Chinese("张三","中华人民共和国");
// 姓名:张三,国籍:中华人民共和国
System.out.println("姓名:"+c1.name+",国籍:"+c1.country);
System.out.println("=======");
// 创建Chinese对象
Chinese c2 = new Chinese();
// 姓名:null,国籍:中华人民共和国
System.out.println("姓名:"+c2.name+",国籍:"+c2.country);
System.out.println("=======");
// 如果该类的某个对象修改了静态成员变量的值,那么该类的所有对象使用的都是修改后的值
c2.country = "中国";
// 姓名:张三,国籍:中国
System.out.println("姓名:"+c1.name+",国籍:"+c1.country);
// 姓名:null,国籍:中国
System.out.println("姓名:"+c2.name+",国籍:"+c2.country);
System.out.println("=======");
System.out.println(Chinese.country);// 中国
}
}
1.3 修饰成员方法
格式:
修饰符 static 返回值类型 方法名(形参列名){
方法体
}
特点:
- 被static修饰的成员方法叫做静态方法
访问方式:
- 对象名.方法名(实参);
- 类名.方法名(实参);------->推荐\掌握
案例:
public class Chinese {
public void show1(){
System.out.println("非静态方法 show1...");
}
public static void show2(){
System.out.println("静态方法 show2...");
}
}
public class Test {
public static void main(String[] args) {
/*
修饰成员方法
*/
Chinese.show2();// 推荐
System.out.println("======");
Chinese c = new Chinese();
c.show1();
c.show2();
}
}
1.4 静态方法调用的注意事项
-
静态方法中不能出现this关键字(this表示当前对象,静态方法存在时还没有对象)
-
静态方法中只能直接访问静态成员变量和静态成员方法
-
静态方法中不能直接访问非静态成员变量和非静态成员方法
-
非静态方法中可以直接访问一切成员变量和成员方法
public class Chinese { // 非静态成员变量 String name; // 静态成员变量 static String country; // 静态成员方法 public static void method1(){ // 静态方法中不能出现this关键字 // System.out.println(this.name); // 静态方法中不能直接访问非静态成员变量和非静态成员方法 // System.out.println(name); // show1(); // 静态方法中只能直接访问静态成员变量和静态成员方法 System.out.println(country); method2(); } public static void method2(){ System.out.println("静态成员方法method2..."); } // 非静态方法 public void show1(){ System.out.println("非静态成员方法show1..."); } public void show2(){ // 访问非静态成员 System.out.println(name); show1(); // 访问静态成员 System.out.println(country); method2(); } } public class Test { public static void main(String[] args) { /* 静态方法中不能出现this关键字 静态方法中只能直接访问静态成员变量和静态成员方法 静态方法中不能直接访问非静态成员变量和非静态成员方法 非静态方法中可以直接访问一切成员变量和成员方法 */ Chinese.method1(); System.out.println("====="); // Chinese c = new Chinese(); // c.show2(); new Chinese().show2(); } }
1.5 修饰代码块
使用 { } 括起来的代码称为代码块,Java 语言提供了四种代码块。
局部代码块:写在方法中的代码块,作用是限制变量的作用范围。
构造代码块:写在类中方法外的代码块,每次创建类的对象时都会执行,且优先于构造方法之前执行。
静态代码块:使用 static 修饰的构造代码块就是静态代码块,静态代码块是类被加载时执行,且仅执行一次。
同步代码块:多线程时再学,暂时无需关注。
格式:
static {
}
// 静态代码块
位置:
- 类中方法外(类的成员位置)
执行:
- 随着类的加载而执行,并且只执行一次
- 类的加载: 第一次使用该类的时候,就会加载,并且只加载一次
使用场景:
- 如果程序中某段代码只需要执行一次,就可以把该段代码放入静态代码块中
- eg: 加载驱动,读取配置文件中的数据…
案例
class Chinese {
{
System.out.println("Chinese 构造代码块...");
}
static {
System.out.println("Chinese 静态代码块...");
}
public Chinese(){
System.out.println("Chinese 空参构造...");
}
}
public class Test {
public static void main(String[] args) {
System.out.println("开始");
Chinese c1 = new Chinese();
Chinese c2 = new Chinese();
System.out.println("结束");
/**
* 开始
* Chinese 静态代码块...
* Chinese 构造代码块...
* Chinese 空参构造...
* Chinese 构造代码块...
* Chinese 空参构造...
* 结束
*/
}
}
1.6 以后开发中static的应用
概述
以后的项目中,通常会需要一些“全局变量”或者“全局的工具方法”,这些全局变量和方法,可以单独定义在一个类中,并声明为static(静态)的,可以很方便的通过类名访问
例如
public class MathUtils {
// ctrl+shift+u
public static final double PI = 3.14;
public static final int WIDTH = 600;
public static final int HEIGHT = 800;
public static int getMax(int[] arr){
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max){
max = arr[i];
}
}
return max;
}
}
public class Test {
public static void main(String[] args) {
// 需求:在一个工具类中,定义一个π变量和获取int数组最大值方法
System.out.println(MathUtils.PI);// 3.14
int[] arr = {1,23,45,23,434,54,32,65,76};
System.out.println(MathUtils.getMax(arr));// 434
}
}
第二章 接口
3.1 概述
- 概述: 是 Java 语言中的一种数据类型(引用类型),如果说类中封装了成员变量、构造方法和成员方法,那么接口主要就是用于封装抽象方法的。和类一样,接口也会被编译成 class 字节码文件
- 接口中的成员:
- 静态常量,抽象方法(JDK8以前)
- 默认方法和静态方法(JDK 8开始)
- 私有方法(jdk9)
- 定义接口需要使用interface关键字—>编译后也会产生class文件
- 接口没有构造方法,也不能创建对象,接口存在的意义就是给子类实现(类似于继承),
- 实现接口的类叫做实现类,实现类如果是普通类必须重写接口中所有的抽象方法,否则实现类是一个抽象类
抽象类和接口:
因为抽象类和接口要解决的问题是不同的
抽象类封装了继承体系种的共性内容,而接口则是作为继承体系之外的扩展而存在的。
由于类只支持单继承,这影响了程序的可扩展性和可维护性,而接口的出现彻底解决了 Java 语言的单继承问题。
后期我们要学习 Lambda 表达式,它是基于接口的。
3.2 定义格式
格式
public interface 接口名称 {
// 静态常量
// 抽象方法
// 默认方法
// 静态方法
// 私有方法
}
案例
public interface IA {
// 常量:使用public static final修饰,而这三个修饰符可以省略不写
public static final int NUM = 10;
// 抽象方法:使用public abstract修饰,而这两个修饰符可以省略不写
public abstract void method1();
// 默认方法: 使用public default修饰,public可以省略不写,default不可以省略
public default void method2(){
System.out.println("IA 默认方法method2");
}
// 静态方法:使用public static修饰,public可以省略不写,static不可以省略
public static void method3(){
System.out.println("IA 静态方法method3");
}
// 私有方法: 使用private修饰,private不可以省略
// private void method4(){}
// private static void method5(){}
}
3.3 实现接口
实现概述
- 概述:类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。
实现格式:
-
单实现
public class 实现类名 implements 接口名{}
-
多实现
public class 实现类名 implements 接口名1,接口名2,接口名3,...{}
-
先继承后实现
public class 实现类名 extends 父类名 implements 接口名1,接口名2,接口名3,...{}
案例演示:
interface IA{}
interface IB{}
interface IC{}
class Fu{}
// 单实现
class Imp1 implements IA{}
// 多实现
class Imp2 implements IA,IB,IC{}
// 先继承后实现
class Zi extends Fu implements IA,IB,IC{}
3.4 接口中成员的访问特点
-
接口中成员的访问特点: 接口中常量: 主要供接口名直接访问,可以被实现类继承 接口中抽象方法:就是供实现类重写的 接口中默认方法:可以供实现类对象直接调用,或者在实现类中重写 接口中静态方法:只供接口名直接访问,不可以被实现类继承 接口中私有方法:只能在接口内部方法中调用,不可以被实现类继承
-
案例:
public interface IA { // 常量: 使用public static final修饰 public static final int NUM = 10; // 抽象方法: 使用public abstract修饰 public abstract void method1(); // 默认方法: 使用public default修饰 public default void method2(){ System.out.println("IA接口的默认方法method2"); // method4(); } // 静态方法: 使用public static修饰 public static void method3(){ System.out.println("IA接口的静态方法method3"); } // 私有方法: 使用private修饰 //private void method4(){} //private static void method5(){} } public class Imp implements IA { @Override public void method1() { System.out.println("实现类重写接口中的抽象方法method1"); } @Override public void method2() { System.out.println("实现类重写接口中的默认方法method2"); } } public class Test { public static void main(String[] args) { /* 接口中成员的访问特点: 接口中常量: 主要供接口名直接访问,也可以被实现类继承 接口中抽象方法:就是供实现类重写的 接口中默认方法:可以供实现类对象直接调用,或者在实现类中重写 接口中静态方法:只供接口名直接访问,不可以被实现类继承 接口中私有方法:只能在接口内部方法中调用,不可以被实现类继承 */ // 访问接口中的常量 System.out.println(IA.NUM);// 10 System.out.println(Imp.NUM);// 10 // 访问接口中的抽象方法 Imp imp = new Imp(); imp.method1(); // 访问接口中的默认方法 imp.method2(); // 访问接口中的静态方法 IA.method3(); } }
3.5 多实现时的几种冲突情况
公有静态常量的冲突
interface IA{
public static final int A = 10;
public static final int B = 10;
}
interface IB{
public static final int A = 20;
}
class Imp implements IA,IB{
}
public class Test {
public static void main(String[] args) {
// 多实现常量冲突: 多个接口中同名的常量不能被实现类继承
// System.out.println(Imp.A);// 编译报错
System.out.println(Imp.B);// 10
}
}
公有抽象方法的冲突
interface IA{
public abstract void show();
}
interface IB{
public abstract void show();
}
class Imp implements IA,IB{
@Override
public void show() {
System.out.println("重写一次show方法...");
}
}
public class Test {
public static void main(String[] args) {
// 抽象方法冲突: 必须重写一次抽象方法
}
}
公有默认方法的冲突
interface IA{
public default void show(){
System.out.println("IA show...");
}
}
interface IB{
public default void show(){
System.out.println("IB show...");
}
}
class Imp implements IA,IB{
@Override
public void show() {
System.out.println("重写一次show方法");
}
}
public class Test {
public static void main(String[] args) {
// 默认方法的冲突: 必须重写一次默认方法
Imp imp = new Imp();
imp.show();
}
}
公有静态方法的冲突
interface IA{
public static void show(){
System.out.println("IA show...");
}
}
interface IB{
public static void show(){
System.out.println("IB show...");
}
}
class Imp implements IA,IB{
}
public class Test {
public static void main(String[] args) {
// 静态方法的冲突: 不存在冲突,因为静态方法不能被实现类继承
}
}
私有方法的冲突
- 不存在冲突,因为私有方法不能被实现类继承
3.6 接口和接口的关系
接口与接口之间的关系
-
接口和接口之间是继承关系
-
单继承
public interface A{} public interface B extends A{}
-
多继承
public interface A{} public interface B{} public interface C extends A,B{}
-
多层继承
public interface A{} public interface B extends A{} public interface C extends B{}
接口继承接口的冲突情况
公有静态常量的冲突 `
interface IA{
public static final int B = 10;
public static final int C = 10;
}
interface IB{
public static final int B = 20;
}
interface IC extends IA,IB{
}
public class Test {
public static void main(String[] args) {
// 常量冲突: 多个接口中同名的常量不会被子接口继承,不同名的会被继承
// System.out.println(IC.B);// 编译报错
System.out.println(IC.C);// 10
}
}
公有抽象方法冲突
interface IA{
public abstract void show();
}
interface IB{
public abstract void show();
}
interface IC extends IA,IB{
}
class Imp implements IC{
@Override
public void show() {
System.out.println("重写一次即可");
}
}
public class Test {
// 抽象方法冲突: 多个父接口中有相同的抽象方法,子接口只会继承一个抽象方法
}
公有默认方法的冲突
interface IA{
public default void show(){
System.out.println("IA show...");
}
}
interface IB{
public default void show(){
System.out.println("IB show...");
}
}
interface IC extends IA,IB{
@Override
default void show() {
System.out.println("必须重写一次同名的默认方法");
}
}
public class Test {
// 默认方法冲突: 多个父接口中有相同的默认方法,子接口必须重写默认方法(default)
public static void main(String[] args) {
}
}
公有静态方法和私有方法
-
不存在冲突,因为静态方法和私有方法不会被子接口继承
interface IA{ public static void show(){ System.out.println("IA show..."); } } interface IB{ public static void show(){ System.out.println("IB show..."); } } interface IC extends IA,IB{ } public class Test { public static void main(String[] args) { // 静态方法冲突: 子接口无法继承父接口的静态方法,所以不存在冲突 } }
3.7 继承的同时又实现存在的问题
父类和接口的公有静态常量的冲突
interface IA{
public static final int A = 10;
}
class Fu{
public static final int A = 20;
}
class Imp extends Fu implements IA{
}
public class Test {
public static void main(String[] args) {
// 常量冲突: 实现类不会继承有冲突的常量
// System.out.println(Imp.A);// 编译报错
}
}
父类和接口的抽象方法冲突
interface IA {
public abstract void show();
}
abstract class Fu {
public abstract void show();
}
class Imp extends Fu implements IA {
@Override
public void show() {
System.out.println("必须重写一次");
}
}
public class Test {
// 抽象方法冲突: 实现类必须重写一次有冲突的抽象方法
}
父类和接口的公有默认方法的冲突
interface IA {
public default void show(){
System.out.println("IA show");
}
}
abstract class Fu {
public void show(){
System.out.println("Fu show");
}
}
class Imp extends Fu implements IA {
}
public class Test {
public static void main(String[] args) {
// 默认方法的冲突: 优先访问父类的
Imp imp = new Imp();
imp.show();// Fu show
}
}
父类和接口的公有静态方法
interface IA {
public static void show(){
System.out.println("IA show");
}
}
abstract class Fu {
public static void show(){
System.out.println("Fu show");
}
}
class Imp extends Fu implements IA {
}
public class Test {
public static void main(String[] args) {
// 静态方法的冲突: 不存在冲突,使用的是父类的静态方法
Imp.show();// Fu show
}
}
父类和接口的私有方法
- 不存在冲突
3.8 抽象类和接口的练习
需求:
通过实例进行分析和代码演示抽象类和接口的用法。
1、举例:
犬: 抽象父类
行为:吼叫;吃饭;
缉毒犬:继承犬类,实现缉毒的接口
行为:吼叫;吃饭;缉毒;
缉毒的接口: 缉毒的功能
抽象类: 如果同一群事物共同的功能就定义在抽象类中
接口: 如果不同类型的事物共同拥有的功能就定义在接口中,让这些事物的类实现该接口
实现:
-
Dog
// 抽象父类 public abstract class Dog { // 吼叫 public abstract void houJiao(); // 吃饭 public abstract void eat(); }
-
JiDu
public interface JiDu { public abstract void jiDu(); }
-
JiDuDog
public class JiDuDog extends Dog implements JiDu{ @Override public void houJiao() { System.out.println("汪汪汪..."); } @Override public void eat() { System.out.println("吃狗粮..."); } @Override public void jiDu() { System.out.println("使用鼻子闻来缉毒..."); } }
-
Test
public class Test { public static void main(String[] args) { // 创建缉毒犬对象 JiDuDog jd = new JiDuDog(); jd.eat(); jd.jiDu(); jd.houJiao(); } }
第三章 多态
3.1 概述
- 多态是面向对象三大特征之一。
- 多态指对于同一行为,对于不同的对象,具有不同的表现形式。在程序中,表示对同一方法,不同的对象有不同实现。
- 实现多态的前提条件
- 继承或者实现(二选一)
- 父类引用指向子类对象 eg: Fu f = new Zi();(多态的语法格式)
- 方法的重写(没有重写方法多态是没有意义的,子类重写方法才能有不同的表现形式)
3.2 实现多态
-
父类的引用指向子类的对象:
- 格式:
父类类型 变量名 = new 子类类型(实参);
- eg:
Fu f = new Zi();
- 格式:
-
接口的引用指向实现类的对象:
- 格式:
接口名 变量名 = new 实现类名(实参);
- eg:
IA a = new Imp();
- 格式:
-
继承时的多态:
abstract class Animal{ public abstract void eat(); } class Dog extends Animal{ @Override public void eat() { System.out.println("狗吃骨头..."); } } class Cat extends Animal{ @Override public void eat() { System.out.println("猫吃鱼..."); } } public class Test { public static void main(String[] args) { /* 实现多态的三个条件: 1.继承 2.父类的引用指向子类的对象 3.方法的重写 */ // 多态 Animal anl = new Dog(); anl.eat(); anl = new Cat(); anl.eat(); } }
-
实现时的多态:
interface IA{ void show(); } class Imp implements IA{ @Override public void show() { System.out.println("实现类重写的show..."); } } public class Test { public static void main(String[] args) { /* 实现多态的三个条件: 1.实现 2.接口的引用指向实现类的对象 3.方法的重写 */ // 多态 IA a = new Imp(); a.show(); } }
3.3 多态时访问成员的特点
- 1.成员变量: 访问的是父类的成员变量,编译看左边(父类),运行看左边(父类)
- 2.成员方法:
- 非静态成员方法:编译看左边(父类),运行看右边(子类)
- 静态成员方法: 编译看左边(父类),运行看左边(父类)
- 记忆: 除了非静态成员方法编译看左边,运行看右边,其余都看左边
class Fu{
int num = 10;
public void method1(){
System.out.println("Fu 非静态方法method1...");
}
public static void method2(){
System.out.println("Fu 静态方法method2...");
}
}
class Zi extends Fu{
int num = 20;
public void method1(){
System.out.println("Zi 非静态方法method1...");
}
public static void method2(){
System.out.println("Zi 静态方法method2...");
}
}
public class Test {
public static void main(String[] args) {
/*
多态时访问成员的特点:
1.成员变量: 编译看左边(父类),运行看左边(父类)
2.成员方法:
非静态成员方法:编译看左边(父类),运行看右边(子类)
静态成员方法: 编译看左边(父类),运行看左边(父类)
除了非静态成员方法编译看左边,运行看右边,其余都看左边
*/
// 父类的引用指向子类的对象
Fu f = new Zi();
System.out.println(f.num);// 10
f.method1();// Zi 非静态方法method1...
f.method2();// Fu 静态方法method2...
}
}
3.4 多态的几种表现形式
-
普通父类多态
public class Fu{} public class Zi extends Fu{} public class Test{ public static void main(String[] args){ Fu f = new Zi();// 多态 } }
-
抽象父类多态
public abstract class Fu{} public class Zi extends Fu{} public class Test{ public static void main(String[] args){ Fu f = new Zi();// 多态 } }
-
父接口多态
public interface IA{} public class Imp implements IA{} public class Test{ public static void main(String[] args){ IA a = new Imp();// 多态 } }
3.5 多态的应用场景
多态的的应用场景包括变量多态、参数多态和返回值多态
-
变量多态 -----> 意义不大
class Animal{ public void eat(){ System.out.println("吃东西..."); } } class Dog extends Animal{ public void eat(){ System.out.println("狗吃骨头..."); } } class Cat extends Animal{ } public class Test { public static void main(String[] args) { // 父类的引用指向子类的对象 Animal anl1 = new Dog();// 变量多态 Animal anl2 = new Cat();// 变量多态 // 使用anl1变量一定要根据多态时成员访问特点去使用 anl1.eat();// 狗吃骨头... } }
-
形参多态----> 常用
-
结论: 参数的类型是父类类型,那么就可以接收该父类类型的对象以及该父类类型的所有子类对象
class Animal{ public void eat(){ System.out.println("吃东西..."); } } class Dog extends Animal{ public void eat(){ System.out.println("狗吃骨头..."); } } class Cat extends Animal{ public void eat(){ System.out.println("猫吃鱼..."); } } public class Test { public static void main(String[] args) { Dog d = new Dog(); method(d);// 狗吃骨头... System.out.println("======"); Cat c = new Cat(); method(c);// 猫吃鱼... } // 定义一个方法,既可以接收Dog对象也可以接收Cat对象,甚至Animal类对象以及Animal类其他子类对象,并且在方法中调用eat方法 // 形参多态: 参数的类型是父类类型,那么就可以接收该父类类型的对象以及该父类类型的所有子类对象 // 实参赋值给形参的时候构成了多态 public static void method(Animal anl){ anl.eat(); } // 需求:定义一个方法,可以接收Dog对象,并且在方法中调用eat方法 /*public static void method1(Dog dog){ dog.eat(); }*/ // 需求:定义一个方法,可以接收Cat对象,并且在方法中调用eat方法 /*public static void method2(Cat cat){ cat.eat(); }*/ // ... }
-
-
返回值多态—> 常用
- 返回值类型为父类类型,那么就可以返回该父类类型的对象,或者该父类类型的子类对象
class Animal { public void eat() { System.out.println("吃东西..."); } } class Dog extends Animal { public void eat() { System.out.println("狗吃骨头..."); } } class Cat extends Animal { public void eat() { System.out.println("猫吃鱼..."); } } public class Test { public static void main(String[] args) { // 接收返回值的时候其实就是多态: Animal anl1 = new Dog(); Animal anl1 = method1("Dog"); anl1.eat();// 狗吃骨头... System.out.println("====="); // 接收返回值的时候其实就是多态: Animal anl2 = new Cat(); Animal anl2 = method1("Cat"); anl2.eat();// 猫吃鱼... } // 定义一个方法,可以返回Dog对象,也可以返回Cat对象,也可以返回Animal对象,或者Animal类的其他子类对象 // 返回值多态: 返回值类型为父类类型,那么就可以返回该父类类型的对象,或者该父类类型的子类对象 public static Animal method1(String classType) { //return new Dog(); //return new Cat(); //return new Animal(); if ("Dog".equals(classType)) { return new Dog(); } else if ("Cat".equals(classType)) { return new Cat(); } else if ("Animal".equals(classType)) { return new Animal(); } else { return null; } } }
3.6 多态的好处和弊端
-
好处: 多态的好处是提升了代码的扩展性和复用性
- - 扩展性:以返回值多态为例,如果有新的子类出现,方法的返回值类型和方法的调用者无需做任何修改。
- - 复用性:以参数多态为例,调用方法可以传递该类及其所有子类对象,无需针对多个类编写多个方法。
-
弊端:
-
无法直接访问子类独有的成员变量和成员方法(因为多态时成员访问特点编译都是看左边(父类))
-
案例:
class Animal { public void eat() { System.out.println("吃东西..."); } } class Dog extends Animal { // 独有的成员变量 int num = 10; @Override public void eat() { System.out.println("狗吃骨头..."); } // 独有的成员方法 public void lookHome() { System.out.println("狗正在看家..."); } } public class Test { public static void main(String[] args) { // 多态的弊端: 无法访问子类独有的成员变量和成员方法 // 变量多态 Animal anl1 = new Dog(); // System.out.println(anl1.num);// 编译报错,因为编译看左边,父类没有num成员变量 // anl1.lookHome();// 编译报错,因为编译看左边,父类没有lookHome成员方法 // 返回值多态 Animal anl2 = method1("Dog"); // System.out.println(anl2.num);// 编译报错,因为编译看左边,父类没有num成员变量 // anl2.lookHome();// 编译报错,因为编译看左边,父类没有lookHome成员方法 } // 形参多态 public static void method(Animal anl) { anl.eat(); // anl.lookHome();// 编译报错,因为编译看左边,父类没有lookHome成员方法 } // 返回值多态 public static Animal method1(String classType) { //return new Dog(); //return new Cat(); //return new Animal(); if ("Dog".equals(classType)) { return new Dog(); } else if ("Animal".equals(classType)) { return new Animal(); } else { return null; } } }
-
3.7 引用类型转换
-
为什么要转型
- 为了实现多态
- 为了解决多态的弊端
-
如何转型:
- 向上转型:
父类类型 变量名 = new 子类名(实参);
- 向下转型:
子类类型 变量名 = (子类类型) 父类类型的变量;
- 注意: 父类类型的变量指向的对象一定是左边子类类型的对象,否则会报类型转换异常ClassCastException
- 向上转型:
-
避免转型异常—>转型判断
- instanceof关键字
- 格式:
变量名 instanceof 数据类型
- 执行:
- 如果变量指向的对象的数据类型是属于后面的数据类型,就返回true,否则返回false
-
案例:
class Animal { public void eat() { System.out.println("吃东西..."); } } class Dog extends Animal { // 独有的成员变量 int num = 10; @Override public void eat() { System.out.println("狗吃骨头..."); } // 独有的成员方法 public void lookHome() { System.out.println("狗正在看家..."); } } class Cat extends Animal { @Override public void eat() { System.out.println("猫吃鱼..."); } } public class Test1 { public static void main(String[] args) { /* 为什么转型 1.为了实现多态 2.为了解决多态的弊端(无法访问子类独有的成员变量和成员方法) 引用类型转换分类 向上转型:子类类型向父类类型转换的过程,称为向上转型。这个过程是默认的。 格式:父类类型 变量名 = new 子类类型() 或 子类对象引用; Dog d = new Dog(); Animal anl = new Dog(); Animal anl = d; 向下转型:父类类型向子类类型转换的过程,称为向下转型,这个过程需要强制执行。 格式:子类类型 变量名 = (子类类型) 父类类型变量; 要求: 父类类型的变量必须指向的是前面子类类型的对象 类型转换判断: 使用instanceof关键字 格式: 变量名 instanceof 数据类型 执行: 如果变量指向的对象是属于后面的数据类型,那就返回true 如果变量指向的对象不属于后面的数据类型,那就返回false */ // 向上转型: 父类类型的引用指向子类类型的对象 Animal anl1 = new Dog(); anl1.eat(); // System.out.println(anl1.num);// 编译报错 // anl1.lookHome();// 编译报错 //Dog d = new Dog(); //Animal anl2 = d; System.out.println("======="); // 改变anl1指向的对象 anl1 = new Cat(); // 向下转型: 子类类型向父类类型转换的过程 if (anl1 instanceof Dog){ Dog dog = (Dog) anl1; System.out.println(dog.num); dog.lookHome(); } } }
第四章 内部类
4.1 内部类的概述
- 概述: 将一个类定义在另一个类的里面,里面的那个类就叫做内部类,外面的那个类就叫做外部类
- 特点: 内部类是一个独立的类,在编译后,有自己独立的class文件,前面冠以:外部类名+$+内部类类名
- 分类:
- 成员内部类
- 匿名内部类
- 局部内部类(自己了解)
4.2 成员内部类
-
概述: 定义在类的成员位置(类中方法外)的类就叫做成员内部类
-
格式:
class 外部类名{ class 内部类名{ } }
-
成员访问特点:
-
成员内部类中的成员变量和成员方法在其他类中访问,需要创建成员内部类对象
-
格式:
外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
-
案例:
public class Body { public class Heart{ // 成员变量 int numN = 20; // 成员方法 public void methodN1(){ System.out.println("成员内部类的methodN1方法..."); } } } public class Test { public static void main(String[] args) { // 创建Heart对象 Body.Heart bh = new Body().new Heart(); System.out.println(bh.numN); bh.methodN1(); } }
-
-
注意事项:
-
在成员内部类中,可以直接访问外部类的一切成员,包括外部类的私有成员
-
在外部类中,需要直接访问内部类的成员,需要创建内部类对象来访问
public class Body { int numW = 10; public void methodW1(){ System.out.println("外部类的methodW1方法..."); } // - 在外部类中,需要直接访问内部类的成员,需要创建内部类对象来访问 public void methodW2(){ System.out.println("外部类的methodW2方法..."); // 创建内部类对象 Heart heart = new Heart(); System.out.println(heart.numN); heart.methodN1(); } public class Heart{ // 成员变量 int numN = 20; // 成员方法 public void methodN1(){ System.out.println("成员内部类的methodN1方法..."); } // - 在成员内部类中,可以直接访问外部类的一切成员,包括外部类的私有成员 public void methodN2(){ System.out.println(numW); methodW1(); } } } public class Test { public static void main(String[] args) { // 创建Heart对象 Body.Heart bh = new Body().new Heart(); System.out.println(bh.numN); bh.methodN1(); bh.methodN2(); System.out.println("======"); new Body().methodW2(); } }
-
-
扩展
class Body { int num = 10; public class Heart{ // 成员变量 int num = 20; // 成员方法 public void show(){ int num = 30; System.out.println("局部变量num:"+num);// 30 System.out.println("内部类的成员变量num:"+this.num);// 20 System.out.println("外部类的成员变量num:"+Body.this.num);// 10 } } } public class Test { public static void main(String[] args) { // 创建Heart对象 Body.Heart bh = new Body().new Heart(); bh.show(); } }
4.3 匿名内部类(重点)
-
概述
- 本质其实就是一个类的子类对象
- 本质其实就是一个接口的实现类对象
-
格式
new 类名(){ 必须重写所有抽象方法 }; new 接口名(){ 必须重写所有抽象方法 };
-
使用场景
- 如果想得到一个抽象类的子类对象,那么就可以直接给该类的匿名内部类
- 如果想得到一个接口的实现类对象,那么就可以直接给该接口的匿名内部类
-
案例1:
abstract class Animal{ public abstract void eat(); } public class Test { public static void main(String[] args) { // 需求:调用Animal类的eat方法 /* 以前: 1.创建一个子类继承Animal类,在子类中重写eat方法 2.创建子类对象 3.使用子类对象调用eat方法 现在: 直接创建Animal类的匿名内部类,然后使用该匿名内部类调用eat方法即可 */ // 父类的引用指向子类的对象 Animal anl1 = new Animal() { @Override public void eat() { System.out.println("匿名内部类的eat..."); } }; anl1.eat(); } }
-
案例2:
interface IA{ void show(); } public class Test { public static void main(String[] args) { // 需求:得到IA接口的实现类对象调用show方法 /* 以前: 1.创建一个实现类实现IA接口,重写show方法 2.创建实现类对象 3.使用实现类对象调用show方法 现在: 直接创建IA接口的匿名内部类调用show方法 */ IA a = new IA() { @Override public void show() { System.out.println("匿名内部类的show"); } }; a.show(); System.out.println("====="); new IA() { @Override public void show() { System.out.println("匿名内部类的show"); } }.show(); } }
第五章 引用类型使用小结
5.1 引用类型作为方法参数和返回值
-
引用类型作为方法的参数传递的是地址值
-
引用类型作为方法的返回值返回的是地址值
-
案例
class Person{ String name; public Person(String name) { this.name = name; } public void eat(){ System.out.println("吃饭..."); } } public class Test { public static void main(String[] args) { /* 需求:定义如下类,演示引用类型作为方法的参数和返回值 人类: 属性:姓名 行为:吃饭 在测试类中,定义一个方法,能接收人类对象,并且可以返回一个人类对象 */ Person person = new Person("张三"); System.out.println(person.name);// 张三 // 调用method1方法,传入person对象 Person p1 = method1(person); System.out.println(person.name);// 李四 System.out.println(p1 == person);// true } // 引用类型作为方法的参数和返回值 public static Person method1(Person p){ p.name = "李四"; return p; } }
5.2 引用类型作为成员变量
abstract class Pet{}
interface FaShu{}
class Person{
int age;// 基本类型作为成员变量
String name; // 普通类作为成员变量
Pet pet;// 抽象类作为成员变量
FaShu fs;// 接口作为成员变量
}
public class Test {
public static void main(String[] args) {
// 创建Person对象
Person p = new Person();
// 给p对象的属性赋值
p.age = 18;
p.name = "张三";
p.pet = new Pet() {};// 成员变量类型为抽象类,那么就赋该抽象类的子类对象
p.fs = new FaShu() {};// 成员变量类型为接口,那么就赋该接口的实现类对象
}
}
总结
- 能够使用static修饰成员变量
格式: static 数据类型 变量名
使用: 类名.变量名;
特点: 被该类的所有对象共享
- 能够使用static修饰成员方法
格式: 方法的返回值类型前面加static
使用: 类名.方法名(实参);
- 能够使用static修饰静态代码块
格式: static{}
位置: 类中方法外
执行: 随着类的加载而执行,并且只执行一次,优先于构造方法执行
- 能够写出接口的定义格式
格式: public interface 接口名{}
- 能够写出接口的实现格式
格式: 单实现,多实现,先继承后实现
public class 实现类名 implements 接口名{}
public class 实现类名 implements 接口名1,接口名,...{}
public class 实现类名 extends 父类 implements 接口名1,接口名,...{}
- 能够说出接口中的成员特点
接口中的常量: 主要供接口名直接访问,当然也可以被实现类继承
接口中的抽象方法: 就是供实现类重写的
接口中的默认方法: 可以供实现类对象直接继承调用,也可以供实现类重写
接口中的静态方法: 只能供接口名直接访问,不能别实现类继承
接口中的私有方法: 只能在接口内部直接访问,不能被实现类继承
- 能够说出多态的前提
继承\实现
父类的引用指向子类的对象\接口的引用指向实现类的对象
方法的重写
- 能够写出多态的格式
父类类型 变量名 = new 子类类型(实参);
- 能够理解多态向上转型和向下转型
向上转型: 父类类型 变量名 = new 子类类型(实参); 存在的意义就是为了实现多态
向下转型: 子类类型 变量名 = (子类类型)父类类型的变量; 存在的意义就是为了解决多态的弊端
注意: 父类类型的变量一定要指向左边子类类型的对象,否则会报类型转换异常ClassCastException
避免转型异常; 变量名 instanceof 数据类型
- 能够说出内部类概念
一个类中包含另一个类,被包含的类就是内部类
- 能够理解匿名内部类的编写格式
本质: 表示一个类的子类对象,或者一个接口的实现类对象
格式:
new 类名(){重写抽象方法};
new 接口名(){重写抽象方法};