第十章、面向对象
一、总结
变量/属性当赋值一次后,若未重新赋值,其值一直不变
方法每调用一次,就执行一次
变量/属性不存在调用,只有方法才能调用
二、面向过程与面向对象
1、面向过程(蛋炒饭 耦合度高,扩展力低)
主要关注点是:实现的具体过程,因果关系(集成显卡的开发思路)
- 优点:
- 对于业务逻辑比较简单的程序,可以达到快速开发,前期投入成本较低
- 缺点:
- 采用面向过程的方式开发很难解决非常复杂的业务逻辑,另外面向过程的方式导致软件元素之间的“耦合度”非常高,只要其中一环出现问题,整个系统受到影响,导致最终软件的扩展力差。另外,由于没有“独立体”的概念,所以无法达到组件复用
2、面向对象 object-oriented(盖饭 耦合度低,扩展力高)
面向对象:object-oriented 简称OO
主要关注点是:关注对象(独立体)能完成哪些功能(独立显卡的开发思路)
- 优点:
- 耦合度低,扩展力高,更容易解决现实世界当中更复杂的业务逻辑,组件复用性强
- 缺点:
- 前期投入成本高,需要进行独立体的抽取,大量的系统分析和设计
耦合度和扩展力的关系:反比关系,耦合度越高,扩展力越低
C语言是纯面向过程的,C++是半面向对象,Java是纯面向对象
现在出现的一些新的编程语言多数都是面向对象的,人在认识现实世界的时候以面向对象的方式,面向对象更符合人的思维方式
面向对象的三大特征:
- 封装
- 继承
- 多态
所有面向对象的编程语言都有这三大特征
采用面向对象的方式开发一个软件,声明周期当中:整个生命周期贯穿使用OO面向对象方式
- 面向对象的分析:OOA
- 面向对象的设计:OOD
- 面向对象的编程:OOP
3、类和对象的概念
类的定义:
- 类是现实世界当中不存在的,是一个模板,是一个概念。是人类大脑抽象的结果
- 类代表一类事物
- 在现实世界中,对象A和对象B之间具有共同的特征,进行抽象总结出一个模板,称为类
对象的概念
- 对象是现实世界中实际存在的个体。
软件开发的过程:
- 程序员位于现实世界与虚拟实践的中间点,通过类和对象将现实世界中的东西在虚拟世界实现
- 程序员先观察现实世界,从现实世界当中寻找对象
- 寻找了N多个对象后,发现所有对象都有共同特征
- 程序员在大脑中形成了一个模板(类)
- Java程序员可以通过Java代码来表述一个类
- Java程序中有了类的定义
- 可以通过类来创建对象
- 有了对象之后,可以让对象直接协作起来形成一个系统
类--(实例化)--》 对象
对象--(抽象)--》类
对象又被称为实例/instance
- 类描述的是对象的共同特征
- 在访问这些特征的时候,必须先创建对象
- 一个类主要描述的是:状态 + 动作
状态--》一个类的属性
动作--》一个类的方法
类{
属性;//描述对象的状态信息
方法://描述对象的动作信息
}
属性和方法具体到某个对象之后,可能最终的结果不一样
对象和对象之间有共同特征,但是具体到对象之后有数据的差异
第十一章、java中常用的修饰符关键字
java程序中,类是基本单位,方法是一般单位,成员变量是最小单位
修饰符以类级别为单位 修饰类中的子单位,子单位中不能有任何修饰符
包级别 > 类级别 > 实例级别
小级别可以直接访问大级别的,大级别不能直接访问小级别的
在Java中,有许多修饰符关键字用于限制类、方法、变量等的访问级别和特性。以下是常用的修饰符关键字:
一、default:纯包级私有,条件下等同于实例私有
若是在同一个包内的其他类中访问(默认等同于实例私有),在不同包中访问时必须加public
如果在Java中定义类,变量或方法时不加修饰符,它们将默认为使用Java中的默认访问修饰符。对于类的成员变量和方法,它们的默认访问修饰符是“包级私有”(package-private),也称为“default”修饰符。这意味着它们只能在同一个包内的其他类中访问(默认对象私有)。对于局部变量,没有默认修饰符,因为它们只能在定义它们的方法或代码块中使用。建议在定义变量和方法时显式指定访问修饰符,以确保程序的可读性和可维护性。
二、public:表示公开的,可以被任何类访问,主要用于打破包级私有,因为public使用import机制可以导包。
public关键字用于指定公开的访问级别,表示该类、方法或变量可以被其他任何类访问。
主要用于打破包级私有
public class MyClass {
public int myVariable;
public void myMethod() {
// do something
}
}
这里,MyClass类和其中的myVariable变量和myMethod()方法都被指定为public,所以它们可以被其他任何类访问。
在Java编程语言中,public是一种访问修饰符(Access Modifier),它可以用于类、方法、属性等的声明上。public关键字表示这些成员是公共的,可以被任何类、方法或对象调用和访问。
具体来说,使用public修饰的类、方法、属性等可以被任何其他类访问和调用,无论这些类是否在同一个包中。
如果一个类不在同一个包中,那么我们需要通过导入(import)语句来引入该类,才能够使用它。
使用public关键字修饰的类、方法、属性等可以被其他包中的类访问和调用,但是在使用时,需要确保以下两点:
- 在使用public成员的类中,需要通过import语句将所使用的类引入进来;
- 在被引入的类中,public成员的访问修饰符不能是protected、private或默认(即没有任何访问修饰符),否则在引入该类的外部无法访问这些成员。
需要注意的是,如果被引入的类是一个包级私有(默认)类,则在另一个包中无法访问该类。在这种情况下,只能在同一个包中的其他类中访问该类。
假设我们有两个类:com.example.package1.ClassA
和com.example.package2.ClassB
,它们分别位于com.example.package1
和com.example.package2
两个不同的包中。
ClassA
中有一个公共方法public void methodA()
,而ClassB
中想要使用这个方法。那么,我们需要在ClassB
中通过import语句将ClassA
引入进来,例如:
package com.example.package2;
import com.example.package1.ClassA;
public class ClassB {
public static void main(String[] args) {
ClassA a = new ClassA();
a.methodA();
}
}
在这个例子中,我们使用import com.example.package1.ClassA
语句将ClassA
引入进来,并在main
方法中创建了一个ClassA
对象并调用了其中的methodA
方法。由于methodA
方法是公共的(public),因此可以在其他包中的类中调用它。
而如果没有使用public关键字修饰,则只能在同一个包中的其他类中被访问和调用,无法跨越包边界。如果没有修饰符将默认为default“包级私有”,即使跨包导入也无法访问。因为没有public的话直接导入不了
总的来说,public关键字是Java中非常重要的关键字之一,它使得程序员可以更加方便地进行类的封装、继承、多态等面向对象编程的特性,也使得Java的代码重用性更加高效。
三、private:类和该类实例共同所有
1、只有private修饰 (偏向实例所有),只能在当前类中访问通过实例或非static方法访问。
private关键字用于指定类和实例共同所有的访问级别,表示该变量或方法只能在当前类中访问
public class MyClass {
private int myVariable;
private void myMethod() {
// do something
}
}
这里,myVariable变量和myMethod()方法都被指定为private,所以只有MyClass类内部可以访问它们。
package com.example.mypackage;
public class Address {
//成员变量中的实例变量
//城市
public String city;
//街道
public static String street;
//邮编
private String zipcode;
public void fun(){
zipcode = "jzq666";
System.out.println(zipcode);
}
public static void main(String[] args) {
Address a = new Address();
a.fun(); //jzq666
a.zipcode = "jzq";
System.out.println(a.zipcode); //jzq
}
}
2、private static共同修饰 (偏向类所有)只能在当前类中访问通过类或static方法访问。
只能在当前类中访问通过类或static方法访问。出了类就不能访问了,得通过接口
四、protected:表示受保护的,只能在当前类、同一包中的其他类或该类的子类中访问。
protected关键字用于指定受保护的访问级别,表示该变量或方法只能在当前类、同一包中的其他类或该类的子类中访问
public class MyClass {
protected int myVariable;
protected void myMethod() {
// do something
}
}
这里,myVariable变量和myMethod()方法都被指定为protected,所以除了MyClass类内部,同一包中的其他类和MyClass的子类也可以访问它们。
五、static:纯类级私有,表示静态的,用于指定类变量或类方法。
一般情况下没有static和final为纯实例私有
static关键字用于指定类变量或类方法,表示它们属于类而不是属于实例对象,可以在没有创建实例对象的情况下直接使用。
一般通过类来访问,但是 变量(属性)和方法 可以通过实例来强制访问,其实实例并没用到,相当于还是类来访问
public class MyClass {
public static int myStaticVariable;
public static void myStaticMethod() {
// do something
}
}
这里,myStaticVariable变量和myStaticMethod()方法都被指定为static,所以可以通过类名直接访问它们,而不需要创建实例对象。
六、final:表示不可变的,用于指定一个类、方法或变量不可被修改。
final关键字用于指定不可变的特性,可以用于指定一个类、方法或变量不可被修改
1、final是一个关键字,表示最终的,不可变的
2、final修饰的类无法继承
3、final修饰的方法无法覆盖(重写)
4、fina修饰的变量一旦赋值之后,不可重新赋值,即final修饰的变量只能赋一次值
- final修饰的成员变量
- 必须要进行初始化赋值,不能使用默认初始化,因为成员会进行初始化赋默认值,final修饰赋值后不可修改,所以需要再初始化赋值
- final修饰的实例变量,系统不负责赋默认值,要求程序员必须手动赋值
这个手动赋值,在变量后面赋值可以,在构造方法中赋值也可以。- final修饰的局部变量
- 没有默认初始化,不算赋值,需手动赋值
5、final修饰的引用,一日指向某个对象之后,不能再指向其它对象,那么被指向的对象无法被均圾回收器回收,直到当前方法结束,才会释放内存
final修饰的引用虽然指向某个对象之后不能指向其它对象,但是所指向的对象内部的内存是可以被修改的。
6、final修饰的实例变量,一般和static联合使用,被称为常量,又称静态常量。
- 常量的定义语法格式: public static final 数据类型 常量名 = 值;
- 常量和静态变量都是存储在方法区,都是在类加载时初始化,调用方法也一样,区别在于常量不能变
- 常量名全部大写,每个单词之间采用下划线衔接
- 常量一般都是公共的,采用public修饰
7、如果一个final变量既在声明时进行了初始化,又在构造函数中进行了初始化,那么在构造函数中的初始化赋值会覆盖声明时的初始化值。
public class MyClass {
public final int myFinalVariable = 100;
public final void myFinalMethod() {
// do something
}
}
这里,myFinalVariable变量和myFinalMethod()方法都被指定为final,所以它们不能被修改。
FinalTest01.java
package com.final关键字.javase;
/**
* 关于java语言中的final关键字:
* 1、final是一个关键字,表示最终的,不可变的
* 2、final修饰的类无法继承
* 3、final修饰的方法无法覆盖
* 4、final修饰的变量一旦赋值之后,不可重新赋值
*
*/
public class FinalTest01 {
public static void main(String[] args) {
String s = "123";
System.out.println(s.length());
String str = new String("666");
System.out.println(str);
String s1 = "abc";
System.out.println(s1.replaceAll("c","a"));
System.out.println(s1.trim());
System.out.println(s1.isEmpty());
}
}
FinalTest02.java
package com.final关键字.javase;
public class FinalTest02 {
//第一种解决方案
final int i = 10;
//第二种解决方案
final int j;
public FinalTest02(){
this.j = 10;
}
public static void main(String[] args) {
final int k;
k = 10;
}
}
FinalTest03.java
package com.final关键字.javase;
public class FinalTest03 {
public static void main(String[] args) {
User u = new User(100);
//引用重新赋值
// u = new User(200);
System.out.println(u.getId());
final User u1 = new User(300);
//final修饰的引用,一旦指向某个对象之后,不能在指向其他对象,那么被指向的对象无法被垃圾回收机制回收
//u1 = new User(400);
System.out.println(u1.getId());
//final修饰的引用虽然指向某个对象之后不能再指向其他对象,但是所指向的对象内部的内存是可以被修改的
u1.setId(400);
System.out.println(u1.getId());
}
}
class User {
private int id;
public User() {
}
public User(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
FinalTest04.java
package com.final关键字.javase;
public class FinalTest04 {
public static void main(String[] args) {
System.out.println(Chinese.COUNTRY);
System.out.println("圆周率:" + Math.PI);
}
}
class Math{
public static final double PI = 3.1415926;
}
class Chinese{
//需求:每一个中国人的国籍都是中国,而且国籍不会改变
//常量
static final String COUNTRY = "中国";
}
FinalTest01.java
public class FinalTest01 {
public static void main(String[] args){
A a = new B();
if(a instanceof B){
B b = (B)a;
b.doSome();
}
final int i;
//只能赋一次值
i = 100;
//i = 200;
System.out.println(i);
}
}
class A extends Object{
}
class B extends A {
public void doSome(){
System.out.println("A 's doSome!");
}
}
FinalTest02.java
public class FinalTest02{
public static void main(String[] args){
//Person p = new Person(30);
//System.out.println(p.age);
//======================================
final Person p1 = new Person(30);
//错误: 无法为最终变量p1分配值
//p1 = new Person(30);
//错误: 无法为最终变量p1分配值
//p1 = null;
System.out.println(p1.age);
p1.age = 50;
System.out.println(p1.age);
}
}
class Person extends Object{
int age;
public Person(){
super();
}
public Person(int age){
super();
this.age = age;
}
}
FinalTest03.java
public class FinalTest03{
public static void main(String[] args){
User u = new User(80);
//System.out.println(u.age);
}
}
class User extends Object{
//成员变量
//错误: 变量 age 未在默认构造器中初始化
//final int age;
final int age = 20;
final double height = 180;
//要在系统赋默认值之前赋值
final double weight;
public User(double weight){
//这也算赋值
this.weight = weight;
}
}
FinalTest04.java
public class FinalTest04{
public static void main(String[] args){
Chinese c = new Chinese();
System.out.println(Chinese.COUNTRY);
}
}
class Chinese extends Object{
//实例变量
String idCard;
//实例变量
String name;
//静态常量,一般都是公开的,常量名大写,单词间用下划线_ 隔开
public static final String COUNTRY = "中国";
}
七、abstract:表示抽象的,用于指定抽象类和抽象方法。
abstract关键字用于指定抽象类和抽象方法,表示这些类和方法没有具体实现,需要由子类实现
public abstract class MyAbstractClass {
public abstract void myAbstractMethod();
}
这里,MyAbstractClass类和myAbstractMethod()方法都被指定为abstract,所以需要由子类实现myAbstractMethod()方法。
八、synchronized:表示同步的,用于保证多线程并发执行时的安全性。
synchronized关键字用于保证多线程并发执行时的安全性,可以用于指定方法或代码块
public class MyClass {
public synchronized void myMethod() {
// do something
}
}
这里,myMethod()方法被指定为synchronized,表示在执行该方法时会对该方法加锁,保证同一时间只有一个线程可以访问该方法
九、volatile:表示易变的,用于指定变量在多线程环境下的可见性和原子性。
volatile关键字用于指定变量在多线程环境下的可见性和原子性
public class MyClass {
public volatile int myVariable;
}
这里,myVariable变量被指定为volatile,表示该变量在多线程环境下的读写操作是原子的,且在一个线程中修改该变量后,其他线程会立即看到该变量的修改结果。
十、transient:表示瞬态的,用于指定变量不参与序列化过程。
transient关键字用于指定变量不参与序列化过程
public class MyClass implements Serializable {
private transient int myTransientVariable;
}
这里,myTransientVariable变量被指定为transient,表示在将MyClass对象序列化时,该变量不会被序列化。
十一、native:表示本地的,用于指定方法用本地语言实现。
native关键字用于指定方法用本地语言实现
public class MyClass {
public native void myNativeMethod();
}
这里,myNativeMethod()方法被指定为native,表示该方法的实现是用本地语言(如C、C++等)实现的。
System.out.println() 输出原理
在Java虚拟机启动时,会执行一些初始化操作,其中包括System
类的初始化。在System
类初始化期间,会创建一个PrintStream
对象,并将它赋值给System.out
。这个PrintStream
对象代表了标准输出流,并且默认情况下将其输出到控制台上。
System.out
对象的初始化是由System
类的静态代码块完成的,这个代码块会在System
类被加载时执行。System
类的加载通常在Java虚拟机启动时就发生了。当Java虚拟机首次加载System
类时,它会执行System
类的静态代码块,并将System.out
初始化为一个PrintStream
对象。
需要注意的是,虽然System.out
的初始化是在Java虚拟机启动时发生的,但如果在程序运行时重新赋值System.out
,则可以改变输出流的目标。例如,你可以将System.out
重新赋值为一个文件输出流,这样所有的System.out
输出都会被写入该文件。
这些修饰符关键字可以单独使用,也可以组合使用。组合使用时,一般的顺序为public、protected、private、static、abstract、final、synchronized、volatile、transient和native。
十二、访问控制权限修饰符
1、访问控制权限修饰符来控制元素的访问范围
2、访问控制权限修饰符包括:
- public:表示公开的,在任何位置都可以访问
- protected:本类,同包,子类 可访问
- 缺省:本类,同包 可访问
- private:表示私有的,只能在本类中访问
3、访问控制权限修饰符可以修饰类、接口,成员变量、方法
属性:4个都能用
实例 / 静态方法:4个都能用
类:public和默认能用,其他不行
接口:public和默认能用,其他不行
4、当某个数据希望某个子类使用,使用protected进行修饰
5、修饰符的范围:
private < 缺省 < protected < public6、类只能采用public和缺省 的修饰符修饰(内部类除外)
Test01.java
package com.访问控制权限修饰符.javase;
public class Test01 {
public static void main(String[] args) {
User u = new User();
//protected 和 缺省 修饰符修饰的变量,方法 和类都可以同包中调用
System.out.println(u.i);
System.out.println(u.j);
}
}
User.java
package com.访问控制权限修饰符.javase;
public class User {
//受保护的
protected int i = 10;
//缺省的
int j = 20;
//可以修饰方法
private void m1(){
}
void m2(){
}
protected void m3(){
}
public void m4(){
}
public static void sumInt(int num){
int sum = 0;
boolean flag = true;
for (int i = 2 ; i <= num ; i++){
for(int j = 2 ; j < i ; j++){
if(i % j == 0){
flag = false;
break;
}
flag = true;
}
if(flag){
sum += i;
}
}
System.out.println(sum);
}
public static void main(String[] args) {
sumInt(100);
}
}
Driver.java
package com.访问权限控制符001.javase;
import com.访问控制权限修饰符.javase.User;
public class Driver extends User {
public void m(){
System.out.println(this.i);
//j为缺省修饰符,只能在同一个包中调用
//System.out.println(this.j);
}
}
UserTest.java
package com.访问权限控制符001.javase;
import com.访问控制权限修饰符.javase.User;
public class UserTest {
public static void main(String[] args) {
User u = new User();
//i 为 protected 修饰,只能在同一个包中 或者 不同包中的该类的子类中调用
//System.out.println(u.i);
}
}
第十二章、类
一、概述
java中一切皆对象,一切皆类型,只要是属于该类型的变量(引用),都可以调该类型中所有的属性和方法(包括实例和静态的)
二、类的定义
语法结构
[修饰符列表] class 类名 { //修饰符列表可选
属性;
方法;
}
注意:Java语言中所有的class都属于引用数据类型,class后跟的“类名”为引用数据类型,因为通过class定义的是类,属于引用数据类型。
三、类级别和实例级别访问原则
实例级别 < 类级别
- 访问方面
- 小级别可以直接访问大级别的,大级别不能直接访问小级别的
- 包含方面
- 小级别中有大级别的信息,大级别中没有小级别的信息
- 实例级别中有类级别的信息
- 类级别中没有实例级别的信息
注意:类中的一切属性和方法,在类内类外都可以通过实例对象或this访问和修改
四、实例成员永远在运行时解析,静态成员永远在编译时解析
- 当操作实例成员时,起决定性作用的是引用中保存的值,是在运行时确定
- 当操作静态成员时,起决定性作用的是引用的类型,是在编译时确定
五、不支持动态添加属性和方法
Java 是一种静态类型语言,不像 Python 那样支持在运行时动态添加属性和方法。在 Java 中,类的成员(属性和方法)必须在类定义时进行显式声明,并且在运行时无法添加新的成员。
在 Java 中,要添加新的属性或方法,您需要在类定义中进行声明,然后在相应的对象上调用它们。例如:
public class MyClass {
// 属性声明
private int age;
// 方法声明
public void sayHello() {
System.out.println("Hello, I am a method!");
}
// Getter 和 Setter 方法用于访问和修改属性
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
然后在其他类中使用 MyClass
的实例时,可以访问已经声明的属性和方法:
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.sayHello(); // 输出: Hello, I am a method!
obj.setAge(25);
System.out.println("Age: " + obj.getAge()); // 输出: Age: 25
}
}
在 Java 中,要动态地添加属性和方法,您需要重新编译类并重新运行程序。这与 Python 的动态性不同,在 Python 中,您可以在运行时动态地修改类和实例的属性和方法。因此,Java 在这方面相对更为严格。
六、属性
属性(包括实例和静态)都是是采用一个变量的形式来完成定义的,为成员变量,在类体中定义,没有赋值时采用默认值;
- 只有变量能保存数据
- 注意:引用数据类型默认为null
1、实例变量 修饰符没有static 纯实例私有
实例变量存储在堆内存中的实例中,类内类外都通过 引用.变量名;来访问
- 调用一个对象一份 相当于python的普通字段
- 实例变量是成员变量
- 必须创建对象来访问,又称为实例变量 / 对象变量 / 对象级别的变量
- 类级别访问不了实例变量
- 局部变量是在栈内存中存储
例如:直接在类体中定义,不加任何修饰符(缺省修饰符)
定义:
- int id;
- String name;
- boolean sex;
访问:
- 实例对象(引用). 属性名
2、静态变量 修饰符有static 纯类私有
静态变量存储在方法区内存中,类内类外都通过 类名.变量名;调用
- 静态变量只初始化一份,
- 存储在方法区的class对象中,
- 属于类级私有,所有实例共同的属性
- 也可通过 实例对象(引用).属性名调用(不推荐)
特殊情况:使用实例对象(引用).静态变量 ,在字节码文件中还是编译为 类名.静态变量,和实例对象无关
Java中不支持动态添加属性和方法,因此虽然实例对象访问静态数据,但是底层还是使用类名来访问,因此使用实例对象访问也是可以修改静态数据
public class 静态属性测试 {
public static int i = 10;
public void m(){
//实例方法中,在同一个类中,不需要类名,可以直接访问静态数据
//静态数据的访问不需要实例对象的参与,即使是实例对象来访问,底层也是采用类名的方法来访问
//因此 this.i 也是访问的静态属性,可以直接修改静态属性
this.i += 5; //等同于 i += 5
}
public static void main(String[] args) {
静态属性测试 obj = new 静态属性测试();
obj.m();
System.out.println(静态属性测试.i); //15
//访问静态数据不需要实例对象的参与,因此即使实例对象为null,也不会出现NullPointException
obj = null;
System.out.println(obj.i); //15
}
}
3、声明为实例变量和静态变量的原则
- 声明为实例变量:
- 所有对象都有这个属性,但是这个属性的值会随着对象的变化而变化《不同对象的这个属性具体的值不同]
- 声明为静态变量:
- -所有对象都有这个属性,并目所有对象的这个属性的值是一样的,建议定义为静态变量,节省内存的开销。
静态变量在类加载的时候初始化,内存在方法区中开辟。访问的时候不需要创建对象,直接使用“类名 . 静态变量名"的方式访问。
七、方法
1、实例方法 修饰符没有static 纯实例私有
实例方法存储在堆内存中的实例中,类内类外都通过 引用.方法名(); 调用
实例方法可直接访问实例变量和实例方法,有“当前对象”(this对象)
也能访问静态变量和静态方法,通过 类名.变量名/方法名 (同一个类中类名可省略)
2、静态方法 修饰符有static 纯类私有
静态方法存储在方法区内存中的class对象中,类内类外都通过 类名.方法名(); 调用
静态方法只初始化一份,存储在方法区的class对象中,属于类级私有,所有实例共同的方法
静态方法可直接访问静态变量和静态方法,没有“当前对象”(this对象)
要访问实例变量和方法,必须先创建实例对象,然后使用实例来访问
3、构造方法
1、构造方法既不属于实例方法,也不属于静态方法,是一种特殊的方法(但更偏向于实例方法,因为构造方法中有this,属于小级别的),主要用于实例对象初始化中
- 实例变量必须现在类体中声明或赋值,然后再构造方法中完成初始化赋值或重新赋值
- 不论实例变量是在类体中赋值,还是在构造方法中赋值,必须等待构造方法执行,才能完成所有实例变量的初始化赋值
2、编译在类的字节码文件中,存储在方法区内存中的class对象中,使用new来调用创建实例对象,不能使用 引用. 来调用
3.1、构造方法可以给实例变量赋值,也可以给静态变量赋值
int i;
static int j;
public Test(int i,int j){
this.i = i;
this.j = 40; //同样也会修改原来的静态变量j
Test.j = j; //比较少用
}
4、声明为实例方法和静态方法的原则
- 1、方法描述的是动作,当所有的对象执行这个动作的时候,最终产生影响是一样的,那么这个动作已经不再属于某一个对象动作了,可以将这个动作提升为类级别的动作,模板级别的动作。
- 2、静态方法中无法直接访问实例变量和实例方法。
- 3、当一个行为 / 动作执行的过程中是需要对象参与的,那么这个方法一定要定义为“实例方法”,不能带static关键字
- 4、大多数方法都定义为实例方法,一般一个行为或者一个动作在发生的时候,都需要对象的参与。但是也有例外,例如:大多数“工具类"中的方法都是静态方法,因为工具类就是方便编程,为了方便方法的调用,自然不需new对象是最好的。
特殊情况:
八、类初始化
1、class文件只加载一次,类只在加载class文件时初始化一次,存储在方法区的class对象中,属于类级私有,所有实例共同的属性和方法
2、类初始化时初始化所有静态变量和静态方法,其他实例变量和实例方法在对象初始化时进行
在Java中,当类加载器加载一个class文件时,会在方法区(Method Area)中进行类的初始化。类的初始化是Java虚拟机在首次使用该类时进行的,主要包括静态变量的初始化和静态代码块的执行。以下是Java中类的初始化过程:
类加载器加载class文件并将其字节码数据存放在方法区中。
验证字节码数据的正确性,包括是否符合Java语法规范、是否有不合法的符号引用等。
对类的静态变量进行初始化,分配内存并设置默认值。(先执行)
执行静态代码块中的语句,例如语句中初始化类的静态成员变量等。(后执行)
如果该类有父类,则递归执行父类的初始化过程。
类的初始化完成后,Java虚拟机会将该类的class对象放入方法区的类信息数据结构中,以便后续的访问和使用。
在Java中,可以使用静态代码块来初始化类的静态成员变量和执行其他静态代码逻辑。以下是一个示例代码,演示如何在静态代码块中进行类的初始化:
public class MyClass {
static int myStaticVar;
static {
// 静态代码块中初始化静态变量
myStaticVar = 42;
}
public static void main(String[] args) {
// 调用类的静态成员变量
System.out.println("MyClass.myStaticVar = " + MyClass.myStaticVar);
}
}
在上面的示例中,我们使用静态代码块初始化了MyClass
类的静态成员变量myStaticVar
,并在main()
方法中输出该变量的值。当该类被加载时,静态代码块会被执行,初始化静态变量myStaticVar
的值为42
。
1、静态代码块
语法格式:
static{java语句;
}
特点:
- 1、静态代码块在类加载时执行,并且只执行一次。
- 2、静态代码块在一个类中可以编写多个,并且遵循自上而下的顺序依次执行。
- 3、在main方法执行之前执行
静态代码块的作用:
- 这当然和具体的需求有关,例如项目中要求在类加载的时刻/时机执行代码完成日志的记录。那么这段记录日志的代码就可以编写到静态代码块当中,完成日志记录。
- 静态代码块是iava为程序员准备一个特殊的时刻,这个特殊的时刻被称为类加载时刻。若希望在此刻执行一段特殊的程序,这段程序可以直接放到静态代码块当中。
- 通常在静态代码块当中完成预备工作,先完成数据的准备工具,例如:初始化连接池,解析双配置文件
九、内存图分析
第十三章、类封装
一、概述
封装的好处:
- 1、封装之后,对于那个事物来说,看不到这个事物比较复杂的那一面,只能看到该事物简单的那一面。复杂性封装,对外提供简单的操作入口。照相机就是一个很好的封装的案例,照相机的实现原理非常复杂但是对于使用照相机的人来说,操作起来是非常方便的是非常便捷的。还有像电视机也是封装的,电视机内存实现非常复杂,但是对于使用者来说不需要关心内部的实现原理,只需要会提作遥控器就行。
- 2、封装之后才会形成真正的“对象”,真正的“独立体”
- 3、封装就意味着以后的程序可以重复使用。并且这个事物应该适应性比较强,在任何场合都可以使用。
- 4、封装之后,对于事物本身,提高了安全性。[安全级别高]
二、属性封装(针对实例属性)
1、一般针对实例属性,静态属性也可以封装
在 java 中,属性封装针对的是实例属性,而不是静态属性。
因为静态属性是所有对象所共享的,应该是public公共的,私有化没有意义
实例属性是定义在类的实例对象上的属性,每个实例对象都可以拥有不同的属性值。属性封装通过访问控制符(例如私有属性)来限制外部直接访问和修改实例属性,从而隐藏实现细节,只暴露必要的接口供外部使用。
静态属性(类属性)是定义在类本身上的属性,它属于类而不是类的实例对象。静态属性在所有实例对象之间是共享的,每个实例对象的静态属性都指向同一个内存地址。因为静态属性是与类绑定的,所以它不需要通过实例对象来访问,而是直接通过类名来访问。
虽然静态属性在类的定义中声明,但它并不属于类的封装范畴。静态属性的访问不受访问控制符的限制,因为它不属于类的实例对象。任何外部用户都可以直接通过类名来访问静态属性。
2、封装的步骤
- 1、所有属性私有化,使用private关键字进行修饰,private表示类和该类的实例共有的,修饰的所有数据只能在本类中访问.
- 2、对外提供简单的操作入口,也就是说以后外部程序要想访问age属性,必须通过这些简单的入口进行访问:
- 对外提供两个公开的方法,分别是set方法和get方法
- 想修改age属性,调用set方法
- 想读取age属性,调用get方法
- 3、setter方法的命名规范:
public void setAge (int a){
//编写业务逻辑代码进行控制
age = a;
}
- 4、getter方法的命名规范
public int getAge ( ){
return age;
}
3、生成getters和setters
选择相应的属性直接生成
案例:
User.java
package com.practice.javase;
/**
* 用户类
*
*/
public class User {
//属性私有化
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private String name;
// public void setAge1(int age){
// age = age; //java有就近原则,这里其实并没有给age赋值,都是局部变量age
// }
//setter
void setAge(int a){
//编写业务逻辑代码进行控制
if (a > 0 && a <= 150){
age = a;
}else {
System.out.println("您输入的年龄不合法!");
}
}
//getter
int getAge(){
return age;
}
}
UserTest01.java
package com.practice.javase;
public class UserTest01 {
public static void main(String[] args) {
User u = new User();
//编译报错:类外不能直接访问
//System.out.println(u.age);
//修改
u.setAge(-20);
//读取
System.out.println(u.getAge());
}
}
三、方法封装(针对实例方法)
1、一般针对实例方法,静态方法也可以封装
在 java中,方法封装针对的是实例方法,而不是静态方法。
因为静态方法是所有对象所共享的,应该是public公共的,私有化没有意义
一般很少封装方法,只封装属性
在Java中,封装是指将类的内部细节隐藏起来,使其对外部不可见,同时提供公共的方法给外部使用。
2、封装的步骤
要在Java中封装方法,需要遵循以下步骤:
-
将方法设置为私有(private):这样该方法就只能在当前类内部访问,无法被其他类访问。
-
提供公共的方法(public method):这些公共方法可以被其他类访问,并且这些方法可以访问私有方法。
以下是一个示例代码,其中封装了一个私有方法和一个公共方法:
public class MyClass {
private void myPrivateMethod() {
// 这里是私有方法的实现
}
public void myPublicMethod() {
// 这里是公共方法的实现
myPrivateMethod(); // 调用私有方法
}
}
在上面的示例中,myPrivateMethod()
方法是私有方法,只能在MyClass
类内部访问。而myPublicMethod()
方法是公共方法,可以被其他类访问,同时它也可以访问myPrivateMethod()
方法。这种封装的方式可以保护类的内部实现细节,同时提供公共的接口给外部使用。
四、私有的属性和方法不能被子类继承
在 Java 中,私有的属性(fields)和方法(methods)都不能被子类继承,因为它们在子类中是不可见的。私有成员是类中的一种封装机制,它们只能在声明该成员的类内部使用,对外部类和子类都不可见。因此,子类无法继承或访问父类中的私有属性和方法。
考虑以下示例:
class Parent {
private int privateField = 10;
private void privateMethod() {
System.out.println("This is a private method in Parent.");
}
public void publicMethod() {
System.out.println("This is a public method in Parent.");
privateMethod();
}
public int getPrivateField() {
return privateField;
}
}
class Child extends Parent {
// 试图访问父类的私有字段(这是不允许的)
// public void childMethod() {
// System.out.println("Child accessing private field in Parent: " + privateField);
// }
// 试图重写父类的私有方法(这是不允许的)
// private void privateMethod() {
// System.out.println("This is a private method in Child.");
// }
}
public class Main {
public static void main(String[] args) {
Parent parent = new Parent();
System.out.println(parent.getPrivateField()); // Output: 10
parent.publicMethod(); // Output: This is a public method in Parent. This is a private method in Parent.
Child child = new Child();
child.publicMethod(); // Output: This is a public method in Parent. This is a private method in Parent.
}
}
在这个例子中,Parent
类有一个私有属性 privateField
和一个私有方法 privateMethod()
,它们只能在 Parent
类的内部被访问。子类 Child
试图访问父类的私有字段和重写父类的私有方法,但是这是不允许的,因为子类无法访问父类中的私有成员。
虽然私有方法不能被继承,但是父类中的其他非私有方法可以被子类继承和重写。只有非私有方法(包括默认访问修饰符 package-private
,protected
和 public
方法)才能在子类中被继承和重写。
第十四章、类继承
一、概述
继承是面向对象三大特征之一,依次分别是:封装、继承、多态
1、继承的作用:
继承“基本"的作用是:代码复用。
最"重要”的作用是:有了继承才有了以后"方法的覆盖”和”多态机制”。
- 代码重用:继承允许子类继承父类的属性和方法,从而使子类可以重用父类的代码,减少了代码的冗余。这样可以提高代码的可维护性和扩展性,避免重复编写相似的代码。
- 继承层次:通过继承,可以创建类之间的继承层次结构。子类可以继续被其他类继承,形成更复杂的继承关系,这样可以更好地组织和管理代码。
- 代码抽象:通过继承,可以将通用的属性和方法放在父类中,而子类只需要实现特定的功能。这样可以实现代码的抽象,将类的通用特性和具体实现分离,使代码更加清晰和易于理解。
- 多态性:继承是实现多态性的基础。多态性允许在父类的引用上调用子类的方法,从而实现不同对象对同一消息的响应。这样可以提高代码的灵活性和可扩展性。
2、继承语法格式
[修饰符列表] class 类名 extends 父类名 {
类体 = 属性 + 方法
}
子类将继承父类的属性和方法,这意味着子类可以直接使用父类中定义的属性和方法,无需重新编写相同的代码。子类也可以重写父类的方法或添加新的属性和方法,以满足子类自身的需求。
3、java中只支持单继承
java语言当中的继承只支持单继承,一个类不能同时继承很多类,只能继承一个类。
在C++,Python中支持多继承。
4、关于继承中的一些术语
B类继承A类,其中:
- A类称为:父类、基类、超类、superclass
- B类称为:子类、派生类、subclass
5、子类继承父类的数据
私有的不支持继承
构造方法不支持继承
其它数据都可以被继承,属性(实例变量,静态变量)方法(实例方法,静态方法)
-
实例变量:子类可以继承父类中的所有属性。这意味着子类可以直接访问和使用父类中定义的实例变量。
-
实例方法:子类可以继承父类中的所有方法。这使得子类可以直接调用父类的方法,无需重新编写相同的代码。
-
静态变量):如果父类有类变量,子类也可以继承父类的类变量。子类可以通过类名或实例访问这些类变量。
-
静态方法:子类可以继承父类中的静态方法。子类可以直接调用父类的静态方法。
public class OOPTest {
//实例变量
String a = "父类实例变量";
//静态变量
public static String b = "父类静态变量";
//实例方法
public void instanceMethod(){
System.out.println("父类实例方法");
}
//静态方法
public static void staticMethod(){
System.out.println("父类静态方法");
}
}
//子类继承了父类的实例变量,静态变量,实例方法,静态方法
class Test extends OOPTest{
public static String b = "子类静态变量";
public static void staticMethod(){
System.out.println("子类静态方法");
}
public static void main(String[] args) {
OOPTest oopTest = new Test();
/**
* 父类实例变量
* 父类静态变量
* 父类实例方法
* 父类静态方法
*/
System.out.println(oopTest.a);
System.out.println(oopTest.b);
oopTest.instanceMethod();
oopTest.staticMethod();
System.out.println("==========================");
/**
* 子类静态变量
* 子类静态方法
*/
System.out.println(Test.b);
Test.staticMethod();
System.out.println("==========================");
/**
* 父类静态变量
* 父类静态方法
*/
System.out.println(OOPTest.b);
OOPTest.staticMethod();
System.out.println("==========================");
//测试静态变量和静态方法不会被重写(覆盖)
/**
* 子类静态变量
* 子类静态方法
* 父类静态变量
* 父类静态方法
*/
Test test1 = new Test();
System.out.println(test1.b);
test1.staticMethod();
OOPTest test2 = new Test();
System.out.println(test2.b);
test2.staticMethod();
}
}
6、间接继承
虽然java语言当中只支持单继承,但是一个类也可以间接继承其它类,
例如:
- C extends B
- B extends A
- A extends T
- C直接继承B类,但是C类间接继承T、A类。
7、所有类均默认继承Object类
java语言中假设一个类没有显示的继承任何类该类
- 默认继承JavaSE库当中提供的 java.lang.object 类
- java语言中任何一个类中都有object类的特征。
8、继承链
若调用的全局变量和方法(包括实例方法和静态方法)在子类中没有,则从父类中查找,一级一级向上,直到Object类
- 一般是由实例对象来调用类中所有的属性和方法,因为实例对象为小级别的,小级别中包含大级别的信息
- 实例对象不但可以调用本类中所有属性和方法,还可以调用父类中所有的属性和方法
- 三种继承链模式
- 实例模式
- 静态模式
- 实例和静态模式
9、继承的本质包括以下几个方面
-
代码重用:通过继承,子类可以复用父类中的代码,而不需要重新编写相同的代码,从而减少了代码量,并且提高了代码的可维护性。
-
属性和方法的继承:子类继承了父类的属性和方法,使得子类可以访问父类中的成员变量和方法,从而扩展了子类的功能。
-
多态性:通过继承,子类可以重写父类的方法,实现多态性,这使得代码更加的灵活,能够适应不同的场景。
-
类型的兼容性:由于子类继承了父类的属性和方法,所以子类可以被当作父类的类型来使用,这提高了代码的可扩展性和可维护性。
堆内存中实例对象继承图示:
二、子类级别和父类级别访问原则
子类级别 < 父类级别
- 访问方面
- 小级别可以直接访问大级别的,大级别不能直接访问小级别的
- 包含方面
- 小级别中有大级别的信息,大级别中没有小级别的信息
三、继承属性(主要针对实例属性,运行时解析)
1、若引用修饰类型为父类类型,则优先直接调用父类的(父类必须得有该属性,否则静态绑定失败,编译报错),根据引用变量中实际的值,根据继承链,最后决定调哪一个
2、若引用修饰类型为子类类型,则优先直接调用子类的(子类必须得有该属性,否则静态绑定失败,编译报错),根据引用变量中实际的值,根据继承链,最后决定调哪一个
四、继承方法(主要针对实例方法,运行时解析)
1、若引用修饰类型为父类类型,则优先直接调用父类的(父类必须得有该方法,否则静态绑定失败,编译报错),根据引用变量中实际的值,然后从父类到子类查询,最后决定调哪一个
2、若引用修饰类型为子类类型,则优先直接调用子类的(子类必须得有该方法,否则静态绑定失败,编译报错),根据引用变量中实际的值,然后从父类到子类查询,最后决定调哪一个
案例一:
Account.java
package com.继承.javase;
public class Account {
private int id;
private double balance;
public Account() {
}
public Account(int id, double balance) {
this.id = id;
this.balance = balance;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
CreditAccount.java
package com.继承.javase;
public class CreditAccount extends Account{
private double credit;
public CreditAccount(){
}
public CreditAccount(double credit) {
this.credit = credit;
}
public double getCredit() {
return credit;
}
public void setCredit(double credit) {
this.credit = credit;
}
}
ExtendsTest01.java
package com.继承.javase;
public class ExtendsTest01 {
public static void main(String[] args) {
/*ExtendsTest01 et = new ExtendsTest01();
String s = et.toString();
System.out.println(s);*/
CreditAccount ca = new CreditAccount(0.8);
System.out.println(ca.getId() + "," + ca.getBalance() + "," + ca.getCredit()); //0,0.0,0.8
// CreditAccount ca1 = new CreditAccount();
ca.setId(1);
ca.setBalance(666);
ca.setCredit(0.8);
System.out.println(ca.getId() + "," + ca.getBalance() + "," + ca.getCredit()); //1,666.0,0.8
}
}
五、方法重写(覆盖 override)
方法覆盖又被称为方法重写,英语单词:override[[官方的]/overwrite
建议方法重写的时候尽量复制粘贴,不要编写,容易出错,导致没有产生覆盖。
1、方法重写的意义
方法重写的主要意义在于实现多态性(polymorphism)和代码的可扩展性。多态性是面向对象编程的一个重要特性,它允许不同的对象对相同的方法调用做出不同的响应。通过方法重写,子类可以重新定义父类的方法,从而在不改变方法名和参数的情况下实现不同的行为。
为什么不直接定义一个新的方法而是要重写父类方法的方法名和参数?这是因为方法重写实现了方法的覆盖,子类可以将自己特定的行为“覆盖”在父类方法的基础上,而不用另外定义新的方法。这样做的好处有:
-
统一接口:在多态的实现中,往往会使用父类类型引用指向子类对象,然后通过父类引用调用方法。如果子类直接定义新的方法,那么在使用多态时,无法通过父类引用调用子类特定的方法。而方法重写可以保持方法名和参数的一致性,使得通过父类引用调用方法时可以在不同的子类上产生不同的行为。
-
扩展功能:通过重写方法,子类可以扩展父类的功能,而不用从头开始实现一个全新的方法。这样,子类可以在保留原有功能的基础上,添加自己的特定功能,提高代码的复用性。
-
继承关系:方法重写维持了子类和父类的继承关系,使得子类仍然是父类的一种类型。这有利于代码的组织和维护,并且使得多态性能够在不同子类间有效运行。
总结来说,方法重写使得代码更加简洁、灵活和易于维护。通过重写父类的方法,子类可以实现自己特定的行为,而不用重新定义新的方法名和参数。这是面向对象编程中多态性的重要体现,提供了代码的可扩展性和灵活性
2、方法重写定义
当父类中的方法已经无法满足当前子类的业务需求,子类有必要将父类中继承过来的方法进行重新编写这个重新编写的过程称为方法重写/方法覆盖
3、方法重写满足的条件
方法重写就是将父类中相同返回值类型相同,方法名相同,参数列表相同的方法给覆盖了,并不是父类中的该方法没有了,而是将父类中的该方法隐藏了,需要使用另一方式调用
- 方法重写发生在具有继承关系的父子类之间
- 方法重写要求存在继承关系,即一个类必须是另一个类的子类。子类可以继承父类的方法。
- private 修饰的私有方法不能被重写,因为不能被子类继承
- final 修饰的方法不能被重写,因为不能被子类继承
- 方法重写的时候: 返回值类型相同,方法名相同,形参列表相同
- 这意味着返回值,方法名和参数的个数及顺序都必须匹配。
- 方法重写的时候:访问权限不能更低,可以更高。
- 子类重写方法的访问权限不能比父类更严格。例如,如果父类方法是
public
访问权限,子类重写的方法也必须是public
访问权限。 - 缺省 < protected < public
- 子类重写方法的访问权限不能比父类更严格。例如,如果父类方法是
- 方法重写的时候:抛出异常不能更多,可以更少。[以后讲,讲完异常之后才能解释]
4、方法不能重写的情况
方法没有重写就是没有将父类中的方法覆盖了,没有将父类中的该方法隐藏了,而是在子类中新建了一个新的方法,还是使用原来的方法调用
- 私有方法不能继承,所以不能覆盖。
- 构造方法不能继承,所以不能覆盖。
- 静态方法不存在覆盖。
- 覆盖只针对实例方法,不针对属性和静态方法。
5、方法重写的本质
- 方法覆盖的本质在于多态性。多态性是指在程序运行时,一个对象可能具有多种不同的形态。具体来说,在Java中,多态性可以通过继承和方法覆盖来实现。
- 当我们使用父类类型的引用变量来引用子类类型对象时,程序会根据引用变量中具体的值,来决定调用哪个方法。如果这个方法被子类覆盖了,那么程序会调用子类中的方法。这种行为称为动态绑定或运行时绑定。
- 方法覆盖的本质就是在实现多态性。它让我们能够在不知道对象具体类型的情况下,调用相同的方法,并且可以根据对象的实际类型动态地调用对应的方法。这种灵活性使得我们能够编写更加通用、可扩展的代码。
6、静态变量和静态方法不存在覆盖
当子类定义与父类中的静态方法相同的方法时,实际上是创建了一个新的静态方法,而不是覆盖父类的静态方法。
静态方法是与类相关联的,而不是与实例相关联的。当我们调用静态方法时,它不是针对特定的实例进行的,而是针对整个类进行的。这意味着,无论我们创建多少个该类的实例,它们都可以访问相同的静态方法。
当子类继承父类时,子类会继承父类中的静态方法,因为静态方法是与类相关联的。子类可以调用父类中的静态方法,也可以创建与父类中的静态方法相同的静态方法。但是,由于静态方法是根据编译时类型解析的,而不是根据运行时类型解析的,所以子类创建的静态方法不会覆盖父类中的静态方法,而只是隐藏了它。
这意味着,如果我们使用一个父类类型的引用变量来引用一个子类对象,然后调用静态方法,实际上调用的是父类中的静态方法。因此,即使子类中定义了与父类中的静态方法相同的静态方法,它也无法被调用。
下面我将用一个简单的Java程序来说明静态方法无法覆盖的问题。
假设有一个父类和一个子类,它们都有一个名为print
的静态方法:
class Parent {
public static void print() {
System.out.println("Parent");
}
}
class Child extends Parent {
public static void print() {
System.out.println("Child");
}
}
在上面的代码中,父类Parent
和子类Child
都有一个名为print
的静态方法。子类Child
的print
方法与父类Parent
的print
方法具有相同的名称和参数列表。
然后,我们可以创建一个Main
类来测试这些类的行为:
public class Main {
public static void main(String[] args) {
Parent.print();
Child.print();
Parent p = new Child();
p.print();
}
}
在上面的代码中,我们首先调用父类Parent
的静态方法print
,输出的结果是Parent
。接着,我们调用子类Child
的静态方法print
,输出的结果是Child
。
然而,当我们创建一个父类类型的引用变量p
并将其指向子类Child
的对象时,调用p.print()
时输出的结果却是Parent
,而不是我们预期的Child
。
这是因为在编译时,编译器只能确定静态方法print
的调用是由Parent
类调用的,而无法确定具体调用的是哪个子类。因此,即使在运行时调用子类中的静态方法print
,实际上也是调用父类中的静态方法print
。因此,静态方法无法被覆盖,而只能被继承。
7、方法重写只针对于实例方法,不针对实例变量
在 Java 中,继承是面向对象编程的一种重要特性。当一个类继承另一个类时,它会继承父类的实例方法和实例变量,但是并不存在实例变量的重写这一概念。子类可以通过继承获得父类的实例变量,但无法对父类的实例变量进行重写。
子类可以通过继承获得父类的实例变量,这意味着子类可以直接访问并使用父类中定义的实例变量。但是,如果子类中定义了与父类相同名称的实例变量,它不会重写父类的实例变量,而是创建了一个新的独立的实例变量。这就意味着父类和子类会分别拥有各自的实例变量,它们彼此独立,互不影响。
需要注意的是,方法重写(override)是面向对象编程中的一个重要概念,它允许子类重新定义父类中已经存在的方法。子类可以提供对父类方法的新实现,以满足特定需求,而不改变父类的结构。
8、方法重写的时候:抛出异常不能更多,可以更少
[一般都直接复制原方法进行重写,不会修改上抛异常的大小]
package com.javase.exception;
/**
* 之前讲解方法重写的时候,遗留了一个问题?
* 重写之后的方法不能比重写之前的方法抛出更多的(更宽泛)的异常,可以更少
*/
class Animal {
//相当于上抛RuntimeException
public void doSome() {}
public void doOther() throws Exception{}
}
class Cat extends Animal {
//编译报错:上抛异常大于原方法
// public void doSome() throws Exception { }
//编译正常,相当于上抛RuntimeException
// public void doOther() {}
//编译正常,上抛异常大小和原方法一样
// public void doOther() throws Exception{}
//编译正常:上抛异常小于原方法
public void doOther() throws NullPointerException{}
}
第十五章、多态
一、概述
多态是 Java 中面向对象编程的一个重要特性,指的是同一种类型的对象,在不同的情况下表现出不同的形态和行为。父类型引用指向子类型对象的这种机制导致程序存在编译阶段绑定和运行阶段绑定两种不同的状态,称为多态语法机制
在 Java 中,多态可以通过继承、接口实现和方法重载等方式实现。其中,最常用的是继承和方法重写,通过继承子类重写父类方法的方式来实现多态。
1、多态的分类
具体来说,多态可以分为编译时多态和运行时多态。
编译时多态
是指在编译时就能确定方法的调用形式,主要是方法重载,Java 会根据方法的参数个数、类型和顺序等因素来确定要调用的方法。
运行时多态
是指在程序运行时根据对象的实际类型来确定方法的调用形式,主要是继承和方法重写,Java 会根据对象的实际类型来选择调用哪个方法。
总之,多态是 Java 中面向对象编程的一个重要特性,通过继承和方法重写等方式实现。多态可以使程序更灵活、更可扩展,提高代码的复用性和维护性。
2、多态的作用
降低程序的耦合度,提高程序的扩展力
能使用多态尽量使用多态
3、多态的核心
核心:面向抽象编程,不要面向具体编程
父类类型引用指向子类实例对象
二、四个重要概念
Java 中的方法调用可以通过静态绑定和动态绑定两种方式来实现。静态绑定是在编译时就能确定方法的调用,而动态绑定是在运行时根据对象类型确定方法的调用。
以下四个概念必须有类继承关系
1、静态绑定(静态分派)
静态绑定发生在编译时,它是通过引用变量的静态类型来确定调用哪个方法。具体来说,静态绑定是指编译器根据方法调用的类型来决定要调用哪个方法。
理解:java是静态语言,其实类中所有的属性(成员变量)和方法都要在编译时静态绑定,如果引用的数据类型中没有该属性和方法,直接静态绑定失败,编译报错,到不了运行阶段,只有静态绑定通过,除了静态属性和方法(即实例属性和方法)才会在运行阶段发生动态绑定。
在Java中,静态绑定是指在编译时确定调用的方法或函数。这也被称为早期绑定,因为方法调用是在编译时解析的,编译器可以确定要调用的方法或函数。Java中的静态绑定常见于类的静态属性和方法的调用。
2、动态绑定(动态分派)
动态绑定发生在运行时,它是通过引用中实际存储的值(实例对象的动态类型)来确定调用哪个属性和方法。具体来说,动态绑定是指在程序运行时,根据实际对象的类型来确定要调用的属性和方法。
在Java中,动态绑定是指在运行时根据实际对象的类型来确定要调用的方法或函数。这也被称为晚期绑定。在Java中,所有非静态的属性和方法都是动态绑定的,即在运行时确定方法调用。
public class Animal {
public void makeSound() {
System.out.println("Animal is making a sound.");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog is barking.");
}
}
public class Main {
public static void main(String[] args) {
Animal animal1 = new Dog();
Dog animal2 = new Dog();
animal1.makeSound(); //Dog is barking.
animal2.makeSound(); //Dog is barking.
}
}
在上面的代码中,animal1 是 Animal 类型的变量,但是它指向的实际对象是 Dog 类型的对象。在调用 animal1.makeSound() 方法时,根据实际对象的类型 Dog,运行时将调用 Dog 类中的 makeSound() 方法。在调用 animal2.makeSound() 方法时,根据变量类型和实际对象的类型都是 Dog,运行时也将调用 Dog 类中的 makeSound() 方法。这就是动态绑定的过程。
总之,静态绑定在编译时确定调用哪个方法,而动态绑定在运行时根据实际对象的类型来确定调用哪个方法。在实际开发中,通常更关注动态绑定,因为它使得 Java 可以实现多态性。在 Java 中,多态性是指同一方法或操作可以在不同的对象上有不同的表现形式。通过动态绑定,Java 可以在运行时根据对象的类型来确定要调用哪个方法,从而实现多态性。
3、向上转型 upcasting
向上转型是将子类类型对象赋给父类类型的引用变量,可以通过将子类型对象赋值给父类类型的引用变量来实现向上转型,相当于自动类型转换。
向上转型只要编译通过,运行一定不会有问题
class Animal {
public void move() {
System.out.println("Animal is moving");
}
}
class Dog extends Animal {
public void move() {
System.out.println("Dog is moving");
}
}
Animal animal = new Dog(); // 向上转型
Dog dog = (Dog) animal; // 向下转型
dog.move(); // 输出 "Dog is moving"
在上面的代码中,我们将Dog对象向上转型为Animal对象,这样我们就可以通过Animal引用变量调用Dog对象的方法。
4、向下转型 downcasting
向下转型是将父类类型的引用变量强制转换成子类类型的引用变量,可以通过使用强制类型转换符来实现向下转型。相当于强制类型转换。
向下转型编译通过,运行可能错误
- 向下转型的适用情况:
- 当调用的方法是子类型中特有的,在父类型中不存在必须进行向下转型
- 向下转型的条件
- 必须存在继承关系,原始类型引用变量转换为目标类型引用变量
- 引用变量的原始数据类型和目标数据类型必须存在继承关系
- 强制类型转换符((数据类型)括号中数据类型)的数据类型和目标数据类型必须相同或存在继承关系
- 否则编译报错
- 原始类型引用变量中存储的值必须为目标类型或者目标类型的子类型的实例对象
- 否则运行报错:ClassCastException
package 多态;
public class Animal {
public void move(){
System.out.println("动物在移动!");
}
}
class Cat extends Animal{
@Override
public void move() {
super.move();
System.out.println("猫在移动");
}
public void makeSound(){
System.out.println("猫在叫");
}
}
class DouBao extends Cat{
@Override
public void move() {
super.move();
System.out.println("豆包在移动");
}
@Override
public void makeSound() {
super.makeSound();
System.out.println("豆包在叫");
}
public static void main(String[] args) {
//向上转型
Animal c1 = new Cat();
c1.move();
/**
* 动物在移动!
* 猫在移动
*/
//向下转型
Cat c2 = (Cat) c1;
c2.makeSound();
/**
* 猫在叫
*/
//向上转型
Animal c3 = new DouBao();
c3.move();
System.out.println("==========");
/**
* 动物在移动!
* 猫在移动
* 豆包在移动
*/
//向下转型
Cat c4 = (Cat) c3;
c4.makeSound();
System.out.println("==========");
/**
* 猫在叫
* 豆包在叫
*/
Cat c5 = (DouBao) c3;
c5.makeSound();
/**
* 猫在叫
* 豆包在叫
*/
}
}
在上面的代码中,我们将Animal对象向上转型为Dog对象,然后使用强制类型转换符将其强制转换回Dog对象,这样我们就可以直接调用Dog对象的方法。
三、ClassCastException:类型转换异常
java.lang.ClassCastException 运行时异常
1、编译期类型转换异常
//编译期异常:引用变量的原始数据类型和目标数据类型没有存在继承关系
//第一种情况
//Inconvertible types; cannot cast '多态.Animal' to 'java.lang.String'
String c6 = (String) c3;
//第二种情况
//Inconvertible types; cannot cast '多态.Animal' to 'java.lang.Integer'
Cat c7 = (Integer) c3;
2、运行期类型转换异常
以下两种情况发生运行时异常:ClassCastException
原始类型引用变量中存储的值为目标类型的父类的实例对象
//运行时异常
//原始类型引用变量中存储的值为目标类型的父类的实例对象
/**
* Exception in thread "main" java.lang.ClassCastException: class 多态.Animal cannot be cast to class 多态.Cat (多态.Animal and 多态.Cat are in unnamed module of loader 'app')
* at 多态.DouBao.main(Animal.java:83)
*/
// Cat c6 = (Cat) new Animal();
原始类型引用变量中存储的值和目标类型没有关系
class Bird extends Animal{}
//原始类型引用变量中存储的值和目标类型没有关系
/**
* Exception in thread "main" java.lang.ClassCastException: class 多态.Bird cannot be cast to class 多态.Cat (多态.Bird and 多态.Cat are in unnamed module of loader 'app')
* at 多态.DouBao.main(Animal.java:93)
*/
Animal a = new Bird();
Cat c7 = (Cat) a;
- 以上程序编译是没有问题的,因为编译器检查到a的数据类型是Animal,Animal和Cat之间存在继承关系,并且Animal是父类型,Cat是子类型父类型转换成子类型叫做向下转型,语法合格。
- 程序虽然编译通过了,但是程序在运行阶段会出现异常,因为JVM堆内存当中真实存在的对象是Bird类型,Bird对象无法转换成Cat对象,因为两种类型之间不存在任何继承关系,Bird对象也不是Cat类的实例,此时出现了著名的异常:java.lang.ClassCastException 类型转换异常,这种异常总是在“向下转型”的时候会发生。
3、避免ClassCastException
避免向下转型出现的classCastException
使用instanceof运算符可以避免出现以上的异常。
instanceof运算符
1、语法格式:(引用 instanceof 数据类型名(类名))
2、以上运算符的执行结果类型是布尔类型,结果可能是true/false
3、关于运算结果true/false:假设: (a instanceof Animal)
truie表示:a这个引用指向的对象是一个Animal类型。
false表示:a这个引用指向的对象不是一个Animal类型。
使用子类实例化一个对象,则该实例均为子类和子类所继承的父类的实例,instanceof均为true
4、在进行强制类型转换之前,先使用instanceof 运算符进行判断
package com.多态.javase;
public class 多态Test02 {
public static void main(String[] args) {
//向上转型
Animal a1 = new Cat();
/**
* //等价于
* Cat c1 = new Cat();
* Animal a1 = c1;
*/
Animal a2 = new Bird();
/**
* 等价于
* Bird b1 = new Bird();
* Animal a2 = b1;
*/
//向下转型
if(a1 instanceof Cat){
Cat c1 = (Cat) a1;
c1.catchMouse();
}
if(a2 instanceof Bird){
Bird b1 = (Bird) a2;
b1.fly();
}
}
}
四、案例
1、案例一:Animal,Cat,Bird多态
Animal.java
package com.多态.javase;
//动物类
public class Animal {
public void move(){
System.out.println("动物在移动!");
}
}
Cat.java
package com.多态.javase;
//猫类
public class Cat extends Animal{
//重写父类中继承的方法
@Override
public void move(){
System.out.println("猫在走猫步!");
}
/**
* 该方法不是继承父类中的
* 是cat类中特有的方法
*/
public void catchMouse(){
System.out.println("猫抓老鼠!");
}
}
Bird.java
package com.多态.javase;
public class Bird extends Animal{
//重写父类方法
@Override
public void move(){
System.out.println("鸟儿在飞翔!");
}
public void fly(){
System.out.println("Bird fly!");
}
}
多态Test01.java
package com.多态.javase;
/**
* 关于java中多态语法机制:
* 1、以上animal,cat,bird三个类之间的关系
* cat继承animal
* bird继承animal
* cat和bird之间没有任何继承关系
* 2、面向对象三大特征:封装,继承,多态
*
* 3、关于多态中涉及到的几个概念:
* 向上转型:upcasting 自动类型转换
* 子类型转换成父类型,又被称为自动类型转换
* 向下转型:downcasting 强制类型转换
* 父类型转换成子类型,又被称为强制类型转换,需要加强制类型转换符
* 无论是向上转型还是向下转型,两种类型之间必须有继承关系。否则编译报错
*
*
*/
public class 多态Test01 {
public static void main(String[] args) {
//以前所写的代码
/*Animal a1 = new Animal();
a1.move();
Cat c1 = new Cat();
c1.move();
c1.catchMouse();
Bird b1 = new Bird();
b1.move();*/
//使用多态语法机制
/**
* 1、Animal和cat之间存在继承关系,Animal是父类,Cat是子类
* 2、Cat is a Animal
* 3、new Cat() 创建的对象类型是Cat,a2这个引用的数据类型是Animal,可见他们进行了类型转换
* 4、子类型转换成了父类型,称为向上转型(upcasting),或称为自动类型转换
* 5、java中允许这种语法:父类型引用指向子类型对象
*/
Animal a2 = new Cat();
//Bird b = new Cat(); 编译报错:因为Bird和Cat不是继承关系
/**
* 1、java程序永远都分为编译阶段和运行阶段
* 2、先分析编译阶段,再分析运行阶段,编译无法通过,根本是无法运行的
* 3、编译阶段编译器检查a2这个引用的数据类型是Animal,由于Animal.class字节码文件中有move()方法,所以编译通过了,这个过程称为静态绑定,编译阶段绑定
* 4、只有静态绑定成功之后才有后续的运行
* 5、运行阶段会发生动态绑定,运行阶段绑定
*/
a2.move(); //猫在走猫步!
/**
* 一下程序报错是因为:
* 1、编译器检查到a2的类型是Animal类型,会从Animal.class字节码文件中查找catchMouse()方法,最终没有找到该方法
* 2、导致静态绑定失败,也就是说编译失败,何谈运行
*/
//a2.catchMouse();
/**
* 假设让a2引用变量来执行catchMouse方法
*
*/
Cat c2 = (Cat) a2;
c2.catchMouse(); //猫抓老鼠!
System.out.println(c2 instanceof Animal);
System.out.println(c2 instanceof Cat);
/**
* Exception in thread "main" java.lang.ClassCastException: class com.多态.javase.Animal cannot be cast to class com.多态.javase.Cat (com.多态.javase.Animal and com.多态.javase.Cat are in unnamed module of loader 'app')
* at com.多态.javase.多态Test01.main(多态Test01.java:58)
*/
// Cat c2 = (Cat) new Animal();
// Cat c2 = (Cat) a2;
// c2.move();
// c2.catchMouse();
Animal a3 = new Bird();
if(a3 instanceof Cat){
Cat c3 = (Cat) a3;
//调用子类中特有的方法
c3.catchMouse();
} else if (a3 instanceof Bird) {
Bird b2 = (Bird) a3;
//调用子类中特有的方法
b2.fly();
}
}
}
2、案例二:主人喂养宠物多态
Pet.java
package com.多态.javase;
/**
* 宠物类
*/
public class Pet {
/**
* 所有的宠物都可以吃东西
*/
void eat(){
}
}
Snake.java
package com.多态.javase;
public class Snake extends Pet{
public void eat(){
System.out.println("蛇吞象");
}
}
Dog.java
package com.多态.javase;
public class Dog extends Pet{
public void eat(){
System.out.println("小狗爱吃骨头!");
}
}
Cat.java
package com.多态.javase;
//猫类
public class Cat extends Pet {
public void eat(){
System.out.println("小猫爱吃鱼!");
}
}
Master.java
package com.多态.javase;
//这种方式没有使用多态机制,扩展力很差,因为只有加一个新的宠物,在Master中就要加一个新的方法
/*public class Master {
*//**
* 喂养宠物的方法
*//*
public void feed(Cat cat){
cat.eat();
}
public void feed(Dog dog){
dog.eat();
}
}*/
/**
* 提高程序的扩展力就是降低程序的耦合度(称为解耦合)
* Master主人类面向的是一个抽象的Pet,不在面向具体编程
* 面向抽象编程的好处是:耦合度低
*/
public class Master {
/**
* 如果Pet类中没有eat()方法,或者eat()方法不是public访问修饰符,那么你可能无法在feed方法中调用该方法。
* 此外,如果你尝试将一个不是Pet类或其子类的对象传递给feed方法,编译器将会报错,因为该对象无法转换为Pet类型。
* @param pet:起决定作用的是Pet数据类型,此处发生了向上转型
*/
public void feed(Pet pet) {
pet.eat();
}
public static void main(String[] args) {
// feed(1);
}
}
package com.多态.javase;
/**
* 程序的扩展能力,柔韧性
*/
public class Test {
public static void main(String[] args) {
//创建主人实例
Master zhangsan = new Master();
//创建猫实例
Cat tom = new Cat();
//主人喂养猫
zhangsan.feed(tom);
//创建狗实例
Dog erha = new Dog();
//主人喂养狗
zhangsan.feed(erha);
zhangsan.feed(new Snake());
}
}
第十六章、实例对象
一、概述
每次新new一个对象,即使初始化时相同的,内存地址都不同的,都是一个新的实例对象
二、实例对象初始化过程
1、实例对象初始化步骤
- 1、执行声明时初始化(new 类名() ; 如果声明时未赋值则赋默认值)
- 2、执行实例代码块
- 3、执行构造方法
- 4、在堆内存中的实例对象中初始化实例属性和实例方法
- 5、返回实例对象的引用
实例代码块和构造方法平级,都用于实例变量初始化赋值,构造方法可执行赋默认值或具体值,实例代码块只能执行赋具体值
当调用执行构造方法时,则开始执行实例对象初始化过程,从上往下依次执行。
2、注意事项
1、每创建一个对象都要进行一次初始化
2、需要注意的是,在Java中,静态成员变量和静态方法的初始化是在类加载时进行的,而不是在对象创建时进行的。静态成员变量和静态方法只会被初始化一次,且在整个程序运行期间都会存在。
3、需要注意的是,成员变量的初始化顺序是按照声明的顺序进行的,即先声明的成员变量会先被初始化。而静态成员变量的初始化顺序是按照声明的顺序进行的,且在类加载时进行。同时,如果一个成员变量被声明为 final,则必须在声明时或者构造器中对其进行初始化,不能使用对象初始化块对其进行初始化。
4、
new
关键字会在堆上分配内存,并返回一个指向该对象的引用。因此,可以说new
关键字创建了一个新的对象,并将其引用存储到了操作数栈的栈顶。5、对象和引用
- 对象:目前在使用new运算符在堆内存中开辟的内存空间称为对象
- 引用:是一个变量,不一定是局部变量,还可能是成员变量。引用保存了内存地址,指向了堆内存当中的对象
- 所有访问实例相关的数据,都需要通过“引用."的方式访问,因为只有通过引用才能找到对象。
- 只有一个空的引用,访问对象的实例相关的数据会出现空指针异常
在Java中,对象初始化是指在创建一个对象时,将对象的所有成员变量初始化为它们的默认值或者指定的初始值。对象初始化分为两种方式:
三、对象的默认初始化
对象的默认初始化:在创建对象时,如果没有显式地初始化成员变量,则会自动将成员变量初始化为默认值。例如,int 类型的成员变量默认值为 0,boolean 类型的成员变量默认值为 false,引用类型的成员变量默认值为 null。
演示默认初始化
public class Person {
private int age;
private String name;
private boolean isMale;
private Object obj;
public static void main(String[] args) {
Person person = new Person();
System.out.println("age = " + person.age);
System.out.println("name = " + person.name);
System.out.println("isMale = " + person.isMale);
System.out.println("obj = " + person.obj);
}
}
在上面的示例中,我们创建了一个Person对象,并输出了它的成员变量的值。由于我们没有显式地对成员变量进行初始化,所以它们会被自动初始化为它们的默认值:
- int 类型的 age 成员变量默认值为 0
- String 类型的 name 成员变量默认值为 null
- boolean 类型的 isMale 成员变量默认值为 false
- Object 类型的 obj 成员变量默认值为 null
输出结果为:
age = 0
name = null
isMale = false
obj = null
声明时初始化:成员变量的初始化可以在声明时进行,例如
public class Person {
private int age = 18;
private String name = "Tom";
}
四、对象的显式初始化
1、对象初始化块(实例代码块)
对象的显式初始化:可以在创建对象时使用构造方法或者对象初始化块对成员变量进行显式初始化。例如:
public class Person {
private String name;
private int age;
// 构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 对象初始化块
{
System.out.println("对象初始化块");
}
}
在上面的示例中,构造方法用于对成员变量进行初始化,而对象初始化块在每次创建对象时都会执行一次。
对象初始化块:可以使用对象初始化块在构造器之前对成员变量进行初始化,例如:
public class Person {
private int age;
private String name;
{
age = 18;
name = "Tom";
}
}
实例语句块/代码块
1、实例代码块可以编写多个,也是遵循自上而下的顺序依次执行
2、实例代码块在构造方法执行之前执行,实例代码块执行一次,构造方法对应执行一次。
3、实例代码块也是java语言为程序员准备一个特殊的时机,这个特殊时机被称为: 对象初始化时机。
package com.static关键字.javase;
public class 实例代码块 {
public 实例代码块(){
System.out.println("缺省构造器执行!");
}
{
System.out.println(1);
}
{
System.out.println(2);
}
{
System.out.println(3);
}
public static void main(String[] args) {
System.out.println("main");
new 实例代码块();
/**
* 1
* 2
* 3
* 缺省构造器执行!
*/
new 实例代码块();
/**
* 1
* 2
* 3
* 缺省构造器执行!
*/
}
}
2、构造器初始化 不能添加static
又称 构造方法 / 构造器 / Constructor
关于java类中的构造方法:
1、构造方法又被称为构造函数/构造器/Constructor
2、构造方法语法结构:[修饰符列表] 构造方法名 (形式参数列表){ //无返回值类型
构造方法体;
}
- 对于构造方法来说,“返回值类型"不需要指定,并且也不能写void,只要写上void,那么这个方法就成为普通方法了。
- 对于构造方法来说,构造方法的方法名必须和类名保持一致。
1、构造方法的修饰符列表
在Java中,构造器通常与类的名称相同,用于创建对象实例。构造器需要使用特定的关键字来标记,即public
、private
、protected
和package-private
中的一个。构造器的标记方式如下:
public
构造器:使用public
关键字来标记,这意味着它可以在任何地方被访问,包括该类的外部。示例代码如下:
public class MyClass {
public MyClass() {
// 构造器代码
}
}
private
构造器:使用private
关键字来标记,这意味着它只能在该类内部被访问。示例代码如下:
public class MyClass {
private MyClass() {
// 构造器代码
}
}
protected
构造器:使用protected
关键字来标记,这意味着它可以在该类及其子类中被访问。示例代码如下:
public class MyClass {
protected MyClass() {
// 构造器代码
}
}
- 默认构造器(
package-private
):如果没有显式地标记构造器的访问级别,则默认为package-private
,这意味着它只能在相同包中的其他类中被访问。示例代码如下
public class MyClass {
MyClass() {
// 构造器代码
}
}
需要注意的是,构造器的名称必须与类名相同,并且没有返回类型。在一个类中可以有多个构造器,它们可以有不同的参数列表,这被称为构造器重载。
2、构造方法的作用
- 构造方法存在的意义是,通过构造方法的调用,可以创建对象。
- 一般用于给实例变量赋值,也可以给静态变量赋值(比较少用)
- 实例变量的内存空间是在构造方法执行过程当中完成开辟的。完成初始化的。
系统在默认赋值的时候,也是在构造方法执行过程当中完成的赋值。
3、构造方法应该怎么调用
- 第一种方式:new 构造方法名(实参列表)
- 第二种方式:this(实参列表 )
- 第三种方式:super(实参列表 )
4、new关键字原理
在Java中,new
关键字用于创建一个对象的实例。它的原理可以分为以下几个步骤:
-
类加载:首先,Java虚拟机(JVM)会根据类的全限定名(包括包名)来加载类的字节码文件。如果该类尚未加载,JVM会查找并加载相应的类文件。
-
内存分配:一旦类加载完成,JVM会为该类分配内存。内存分配通常发生在Java堆(Heap)中,用于存储对象实例。
-
对象初始化:分配内存后,JVM会对新创建的对象进行初始化。这包括执行构造函数(Constructor)和初始化对象的成员变量。
-
返回引用:在对象初始化完成后,
new
关键字会返回该对象的引用。引用是指向对象在内存中存储位置的指针。
需要注意的是,new
关键字只是创建了对象的实例,并不包含对象的方法和成员的具体实现。对象的方法和成员实现是通过调用相应的类定义的方法和访问成员变量来完成的。
在Java中,使用new
关键字创建对象的示例代码如下:
MyClass obj = new MyClass();
在上述示例中,new
关键字创建了一个MyClass
类的实例,并将该实例的引用赋值给obj
变量。
总结起来,new
关键字的原理包括类加载、内存分配、对象初始化和返回引用。它是Java中创建对象实例的重要步骤,使得我们能够在程序中使用类的功能和属性。
在Java中,使用new
关键字创建对象时,会自动调用类的构造方法(Constructor)。这是因为构造方法的主要作用是初始化对象的状态,并在对象创建时执行必要的初始化操作。下面解释一下这个机制:
-
构造方法的特殊性:构造方法与普通方法不同,它的名称与类名完全相同,并且没有返回类型。当使用
new
关键字创建对象时,编译器会自动寻找与类名相匹配的构造方法,并在对象创建过程中调用它。 -
对象初始化:在创建对象之前,需要为对象分配内存空间。这是通过调用构造方法来实现的。构造方法负责分配内存并初始化对象的成员变量,将对象从一种初始状态转变为可用状态。
-
隐式调用:使用
new
关键字创建对象时,编译器会在后台隐式地调用匹配的构造方法。这样,在对象创建完成后,我们可以直接使用该对象的实例进行操作,而不需要手动调用构造方法。
需要注意的是,一个类可以有多个构造方法,根据参数列表的不同进行重载。在使用new
关键字创建对象时,编译器会根据提供的参数类型选择匹配的构造方法。如果没有定义任何构造方法,默认的无参构造方法会被调用。
示例代码如下所示,展示了一个带有构造方法的类和使用new
关键字创建对象的过程:
public class MyClass {
private int value;
// 构造方法
public MyClass(int value) {
this.value = value;
System.out.println("构造方法被调用");
}
public int getValue() {
return value;
}
public static void main(String[] args) {
// 使用new关键字创建对象
MyClass obj = new MyClass(10);
System.out.println("对象值: " + obj.getValue());
}
}
在上述示例中,我们定义了一个MyClass
类,它有一个带有参数的构造方法。在main
方法中,我们使用new
关键字创建了一个MyClass
对象,并将参数传递给构造方法。在对象创建的过程中,构造方法会被调用,并完成对象的初始化。最后,我们可以通过对象实例调用类的方法。
因此,使用new
关键字会触发类的构造方法的调用,确保对象在创建后具有正确的初始状态。这样,我们可以在创建对象时执行必要的初始化操作,并确保对象的正确使用。
5、构造方法调用执行之后,返回对象的引用
- 每一个构造方法实际上执行结束之后都有返回值,但是这个"return 值;"这样的语向不需要写。构造方法结束的时候java程序自动返回值。
- 并且返回值类型是构造方法所在类的类型。由于构造方法的返回值类型就是类本身,所以返回值类型不需要编写。
1、当一个类中没有定义任何构造方法的话,系统默认给该类提供一个无参数的构造方法,这个构造方法被称为缺省构造器;
2、当一个类显示的将构造方法定义出来了,那么系统则不再默认为这个类提供缺省构造器了建议开发中手动的为当前类提供无参数构造方法。因为无参数构造方法太常用了
3、构造方法支持重载机制。在一个类当中编写多个构造方法,这多个构造方法显然已经构成方法重载机制。4、只要使用 new 调用构造器就会创建对象,并在“堆内存”中开辟该对象的内存空间,并返回该实例对象的引用。
案例
Account.java
package com.jzq.javase;
public class Account {
/**
* 1、无参数构造器
* 2、初始化实例变量的内存空间
*/
public Account(){
account = null;
balance = 0.0;
}
/**
*
* @param account
* @param balance:
*/
public Account(String account,double balance){
this.account = account;
this.balance = balance;
}
//账号
private String account;
//余额
private double balance;
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
ConstructorTest02.java
package com.jzq.javase;
public class ConstructorTest02 {
public static void main(String[] args) {
//创建对象
Account acc = new Account();
System.out.println("账号:" + acc.getAccount());
System.out.println("余额:" + acc.getBalance());
Account acc1 = new Account("123",90000);
System.out.println("账号:" + acc1.getAccount());
System.out.println("余额:" + acc1.getBalance());
}
}
6、构造方法参数传递的值不止用于给属性赋值,也可用于判断
public Test(int i){
super();
if(i == 0){
System.out.println(123);
}
}
7、String初始化
new
关键字用于在堆上分配内存并创建一个新的对象。对于 String
类型的对象而言,new
关键字会在堆上分配内存,并返回一个指向该对象的引用。因此,可以说 new
关键字创建了一个新的 String
对象,并将其引用存储到了操作数栈的栈顶。
然而,String
对象并不是一个空对象,而是一个已经初始化的对象。String
类型的对象可以通过多种方式进行初始化,例如使用字面值、调用带有参数的构造方法等。例如,以下代码会创建一个新的 String
对象,并将其初始化为 "hello"
:
String str = new String("hello");
在这个过程中,new
关键字会创建一个新的 String
对象,并将其引用存储到 str
变量中。同时,由于我们在构造方法中传入了 "hello"
字符串,因此该 String
对象也会被初始化为 "hello"
。
总之,new
关键字不仅仅是分配内存和创建对象,它还可以通过调用构造方法等方式对对象进行初始化。
四、对象的创建和使用
1、创建实例对象语法
语法:new 类名( );
用法:数据类型(类名)引用名 = new 类名( );
2、访问实例变量的语法
读取数据: 引用.变量名;
修改数据:引用.变量名 = 值; //赋值语法
3、访问实例方法的语法
实例对象引用 . 实例方法名( 实参列表 );
4、案例一: 学生类创建对象
//学生类,是一个模板,描述了所有学生的状态和行为
//Student是类,引用数据类型,它的类型名就是Student
public class Student{
//成员变量,必须创建对象,通过对象访问属性,不能直接通过类访问
//成员变量又被称为实例变量,对象变量,对象级别的变量
//实例变量,不创建对象的话内存空间是不存在的,只有创建了对象,内存空间才会创建
int id; //默认为0
String name; //默认null
int age; //默认为0
boolean sex; //默认false
String addr; //默认null
}
//成员变量没有手动赋值时,为默认类型
//引用数据类型的默认值为 null
/*
* 数据类型 默认值
* -------------------------------
* byte,short,int,long 0
* float,double 0.0
* boolean false
* char \u0000
* 引用数据类型 null
*
*
* */
public class OOTest01 {
public static void main(String[] args) {
//i是一个基本数据类型
int i = 10;
//通过一个类可以实例化N个对象
//实例化对象语法:new 类名();
//new是java中的一个运算符
//new运算符的作用是创建对象,在JVM堆内存中开辟新的内存空间
//方法区内存:在类加载的时候,class字节码文件拍你段被加载到该内存的空间中
//栈内存(局部变量):方法代码执行的时候,会给该方法分配内存空间
//堆内存(引用数据类型):new的对象存在在堆内存中
//Student是一个引用数据类型
//s是一个变量名,是一个局部变量,在栈中保存
//对象:new运算符在堆内存中开辟的内存空间
//引用:是一个变量,保存java对象的内存地址
//java中程序员不能直接操作内存,没有指针的概念,只能通过引用访问
Student s = new Student();
//访问示例对象的语法格式:
//读取数据:引用.变量名;
//修改数据:引用.变量名 = 值;
int stuID = s.id;
String stuName = s.name;
int stuAge = s.age;
boolean stuSex = s.sex;
String stuAddr = s.addr;
System.out.println("学号--》" + stuID);
System.out.println("姓名--》" + stuName);
System.out.println("年龄--》" + stuAge);
System.out.println("性别--》" + stuSex);
System.out.println("地址--》" + stuAddr);
System.out.println(s.id);
System.out.println(s.name);
System.out.println(s.age);
System.out.println(s.sex);
System.out.println(s.addr);
System.out.println("===================================");
s.id = 1502090230;
s.name = "贾卓群";
s.age = 27;
s.sex = true;
s.addr = "陕西西安";
System.out.println(s.id);
System.out.println(s.name);
System.out.println(s.age);
System.out.println(s.sex);
System.out.println(s.addr);
//再创建一个对象
Student stu = new Student();
System.out.println(stu.id);
System.out.println(stu.name);
System.out.println(stu.sex);
System.out.println(stu.age);
System.out.println(stu.addr);
//编译报错:id是实例变量不能直接使用“类名”的方式访问
//System.out.println(Student.id);
}
}
内存图分析
2、案例二:用户和地址类交互创建对象
Address.java
public class Address {
//成员变量中的实例变量
//城市
String city;
//街道
String street;
//邮编
String zipcode;
}
User.java
public class User {
//成员变量中的实例变量
//用户编号,基本数据类型:整数型
//实例变量
int id;
//用户名,引用数据类型:字符串
//实例变量,是一个引用
String name;
//家庭住址,引用数据类型:地址
//实例变量,是一个引用
Address address;
}
OOTest02.java
public class OOTest02 {
public static void main(String[] args) {
//创建user对象
User u = new User();
System.out.println(u.id);
System.out.println(u.name);
System.out.println(u.address);
//修改实例变量值
u.id = 110;
u.name = "jack"; //会创建一个String对象
u.address = new Address();
//在main方法中只有一个引用“u”
System.out.println(u.name + "居住在的城市:" + u.address.city);
System.out.println(u.name + "居住在的街道:" + u.address.city);
System.out.println(u.name + "的邮编" + u.address.city);
u.address.city = "陕西西安";
u.address.street = "长延堡街道";
u.address.zipcode = "710000";
System.out.println(u.name + "居住在的城市:" + u.address.city);
System.out.println(u.name + "居住在的街道:" + u.address.city);
System.out.println(u.name + "的邮编" + u.address.city);
}
}
内存图分析
OOTest03.java
public class OOTest03 {
public static void main(String[] args) {
//u局部变量,是一个引用
User u = new User();
//address局部变量,是一个引用
Address address = new Address();
//将address引用赋值给u.address实例变量
u.address = address;
System.out.println(u.address.city); //null
address.city = "西安";
System.out.println(u.address.city); //西安
}
}
3、案例三:丈夫和妻子类交互创建对象
Husband.java
public class Husband {
//姓名
String name;
//丈夫实例变量中含有妻子引用
Wife w;
}
Wife.java
public class Wife {
//姓名
String name;
//妻子实例变量中含有丈夫引用
Husband h;
}
OOTest04.java
public class OOTest04 {
public static void main(String[] args) {
//创建一个丈夫对象
Husband huangXiaoMing = new Husband();
huangXiaoMing.name = "黄晓明";
//创建一个妻子对象
Wife baby = new Wife();
baby.name = "baby";
//结婚
huangXiaoMing.w = baby;
baby.h = huangXiaoMing;
System.out.println(huangXiaoMing.name + "的妻子的名字" + huangXiaoMing.w.name);
System.out.println(baby.name + "的丈夫的名字" + baby.h.name);
}
}
4、案例四:学生和计算机类交互创建对象
public class OOTest06 {
public static void main(String[] args) {
Student1 s = new Student1();
s.computer = new Computer();
System.out.println(s.name +"的学号为:"+ s.id + ",使用的电脑品牌是:" + s.computer.brand + ",型号是:"+s.computer.model+",颜色是:" + s.computer.color);
s.id = 1502090230;
s.name = "贾卓群";
s.computer.brand = "机械革命";
s.computer.model = "Code1";
s.computer.color = "银色";
System.out.println(s.name +"的学号为:"+ s.id + ",使用的电脑品牌是:" + s.computer.brand + ",型号是:"+s.computer.model+",颜色是:" + s.computer.color);
System.out.println(s.computer); //Computer@4554617c
}
}
class Computer {
//品牌
String brand;
//型号
String model;
//颜色
String color;
}
class Student1{
//学号
int id;
//姓名
String name;
Computer computer;
}
五、空指针异常
1、null
null不是一个对象,它只是一个表示引用为空的特殊值。
- null可以赋值给任何引用数据类型的变量
- 理解为:null将本来从堆内存返回给栈内存中的变量的引用中的内存地址设置为空了,但是引用中的类型没变,和变量的类型一致
- null不能复制给基本数据类型的变量
- 理解为:基本数据类型的变量本身就是保存在栈内存中的,不需要来自堆内存中的引用,所以会编译报错
在Java中,null不是任何类的实例(使用instanceof 运算符会返回 false)。它是一个特殊的值,用于表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组和自定义类型,但null本身不是由任何类实例化而来。
当一个引用类型的变量被赋值为null时,它不再指向任何对象,没有具体的实例与之关联。因此,不能对null进行方法调用或属性访问,否则会抛出空指针异常(NullPointerException)。
-
null的含义:
- null表示一个引用类型变量当前没有引用任何对象。
- null可以被赋值给任何引用类型的变量,包括类、接口、数组和自定义类型。
-
null的使用:
- 当一个对象引用不再需要指向任何对象时,可以将其赋值为null,这有助于释放对象所占用的内存并进行垃圾回收。
- 在条件判断中,可以使用null来检查一个引用是否为空,以避免NullPointerException(空指针异常)的发生。
注意事项:
- 在比较引用类型变量时,使用
equals()
方法而不是==
操作符,以避免由于使用==
操作符比较null引用而导致的错误。 - 当使用null引用调用对象的方法或访问对象的属性时,会抛出NullPointerException。因此,在使用引用之前,应该先进行null检查。
总结:null在Java中表示一个引用类型的变量为空,不指向任何对象。在使用null引用时需要注意空指针异常,并且可以将null赋值给任何引用类型的变量。
2、NullPointerException
运行时异常
空指针异常:空引用访问实例相关的数据
实例相关的数据:访问数据的时候必须有对象的参与
报错:Exception in thread "main" java.lang.NullPointerException
public class OOTest05 {
public static void main(String[] args) {
Customer c = new Customer();
System.out.println(c.id);
//空指针异常:空引用访问实例相关的数据
//实例相关的数据:访问数据的是时候必须有对象的参与
//报错:Exception in thread "main" java.lang.NullPointerException
//c = null;
c.id = null;
System.out.println(c.id);
}
}
String str = null; // 将一个String类型的变量赋值为null
// 对null进行方法调用会抛出空指针异常
int length = str.length(); // 这里会抛出空指针异常,因为str为null
// null可以赋值给自定义类型的变量
MyClass obj = null; // 将一个自定义类的变量赋值为null
六、this关键字
1、定义
解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是this,this指向的是一个对象,这个对象我们称为函数执行的上下文对象,根据函数的调用方式的不同,this会指向不同的对象。多数情况下this可省略
1、this是一个关键字,翻译为:这个,java中的this在实例方法声明时是隐式形参定义,调用时是隐式实参定义
2、this是一个引用,this是一个变量,this变量中保存了内存地址指向了自身,this存储在JVM堆内存java对象内部。3、创建100个java对象,每一个对象都有this且一个对象至少有两个this,(一个是该对象本身,另一个是Object对象)一般只嵌套层最外面的那个this,也就说有100个不同的this
4、this在多数情况下都是可以省略不写的
5、用来区分实例变量和局部变量时,this不能省略
2、this可以出现的位置
- this可以出现在“实例方法”当中,(语法:this.当前类实例变量名/实例方法名 )
- this指向当前正在执行这个动作或属性的类的实例对象。不能用在带有static关键字的“静态方法”中,因为static是纯类私有,没有this对象
- this可以出现在“构造方法”当中, (语法:this( )或 this.当前类的实例数据 )
- 通过当前的的构造方法调用其他的当前类的构造方法,使用this( )调用当前类的实例的其他构造方法,而不用使用new,会创建一个新的对象
- this()这种语法只能出现在构造函数的第一行,只能用一次
-
主方法与非主方法:
- 一般在主方法中使用 对象名 调用当前类的实例变量和实例方法;
- 不在主方法中使用 this 来调用当前类中的实例变量和实例方法
3、this的指向
4、案例一:this关键字只能出现在实例方法和构造方法中,不能出现在静态方法中
Customer.java
package com.this关键字.javase;
public class Customer {
//构造方法
public Customer(){
}
//实例变量 堆内存的对象内部存储
public String name;
public static String name1;
//不带static关键字的一个方法
//顾客的购物行为:每一个顾客购物最终的结果是不一样的,所以购物这个行为是属于对象级别的行为
//重点:没有static关键字的方法被称为“实例方法”,类内类外使用 引用.方法名(); 访问
public void shopping(){
System.out.println(this.name + "在购物!");
}
public static void doSome(){
/*
* 这个执行过程中没有“当前对象”,因为带有static的方式是通过类名.方法名调用
* “上下文”对象中没有”当前对象“
* */
// System.out.println(name);
//可以使用一下方法,但是绝对不是当前对象的name
Customer c = new Customer();
System.out.println(c.name); //这里访问的name是c引用指向对象的name
}
}
CustomerTest.java
package com.this关键字.javase;
public class CustomerTest {
public static void main(String[] args) {
Customer c1 = new Customer();
c1.name = "zhangsan";
c1.shopping(); //zhangsan在购物!
Customer c2 = new Customer();
c2.name = "lisi";
c2.shopping(); //lisi在购物!
/**
*静态方法
*/
Customer.doSome();
}
}
5、案例二:this可以在实例方法调用其他实例方法,并且this可省略
package com.this关键字.javase;
public class ThisTest01 {
//实例对象,static方法不能访问
int i = 10;
public static void main(String[] args) {
// System.out.println(i);
ThisTest01 tt = new ThisTest01();
tt.run();
}
public void doSome(){
System.out.println("do some!");
}
public void run(){
System.out.println("run execute!");
this.doSome();
doSome(); //也可以省略this,默认为“当前对象”
}
}
6、案例三:不能省略this的情况
package com.this关键字.javase;
public class User {
/**
* 实例变量
*/
private int id;
private String name;
/**
* 等号前面的this.id是实例变量id
* 等号后面的id是局部变量id
* @param id:局部变量
*/
public void setId(int id){
this.id = id;
}
public int getId(){
return this.id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 构造器
* @param id:局部变量
* @param name:局部变量
*/
public User(int id,String name){
this.id = id;
this.name = name;
}
}
class Test{
public static void main(String[] args) {
User u = new User(100,"zhangsan");
System.out.println(u.getId());
System.out.println(u.getName());
u.setName("lisi");
System.out.println(u.getName());
}
}
7、案例四:this()调用同类中其他构造方法的演示
package com.this关键字.javase;
public class ThisTest02 {
//实例属性
private int year;
private int month;
private int day;
//getters and setters
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
//构造器
/*
* 需求:调用无参构造器的时候,默认为1970-1-1
* */
public ThisTest02() {
/*this.year = 1970;
this.month = 1;
this.day = 1;*/
//以下代码表示创建了一个全新的对象
//new ThisTest02(1970,1,1);
// System.out.println(123);
//只能写在第一行
this(1970,1,2);
}
public ThisTest02(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
//对外提供一个方法将日期输出到控制台
public void printDate(){
System.out.println(this.year + "年 " + this.month + "月 " + this.day + "日");
}
}
class run{
public static void main(String[] args) {
//创建对象1
ThisTest02 t1 = new ThisTest02();
t1.printDate();
//创建对象2
ThisTest02 t2 = new ThisTest02(2008,1,1);
t2.printDate();
}
}
8、案例五:演示实例方法中使用this访问其他实例成员,静态方法中使用类名 访问其他静态成员
package com.this关键字.javase;
public class ThisTest003 {
//带有static的方法
public static void method1(){
System.out.println("method1");
//调用doSome
//完整方式
ThisTest003.doSome();
//省略方式
doSome();
//调用doOther
//完整方式
ThisTest003 tt2 = new ThisTest003();
tt2.doOther();
//访问i
System.out.println(tt2.i);
//访问j
System.out.println(j);
System.out.println(ThisTest003.j);
}
//没有static的方法
public void method2(){
System.out.println("method2");
//调用doSome
//完整方法
ThisTest003.doSome();
//省略方法
this.doSome();
//调用doOther
//完整方法
this.doOther();
//省略方法
doOther();
//访问i
System.out.println(i);
System.out.println(this.i);
//访问j
System.out.println(ThisTest003.j);
System.out.println(j);
}
public static void main(String[] args) {
//调用method1
//完整方式
ThisTest003.method1();
//省略方式
method1();
//调用method2
//完整方式
ThisTest003 tt1 = new ThisTest003();
tt1.method2();
}
//实例变量
int i = 10;
static int j = 20;
//带有static的方法
public static void doSome(){
System.out.println("do some!");
}
//没有static的方法
public void doOther(){
System.out.println("do other!");
}
}
七、super关键字
1、定义
super关键字代表的就是“当前对象”的那部分父类型的实例特征,是this引用变量的一部分
super 不是引用,也不保存内存地址,不指向任何实例对象
一个类的无参数构造方法一定要手动创建出来
super是一个关键字,全部小写。
2、super和this的区别和联系
super和this对比着学习。
this :
1、this能出现在实例方法和构造方法中。2、this的语法是: this."、 this()"
3、this不能使用在静态方法中。
4、this,大部分情况下是可以省略的。this.什么时候不能省略呢?在区分局部变量和实例变量的时候不能省略
public void setName (string name) {
this .name = name;
}
5、this() 只能出现在构造方法第一行,通过当前的构造方法去调用“本类"中其它的构造方法,目的是: 代码复用。
super :
1、super能出现在实例方法和构造方法中2、super的语法是: super."、super() "
3、super不能使用在静态方法中,super. 不能省略,省略默认为this .
4、super. 不能省略的条件:
如果子类中有与父类同名的实例变量和实例方法,如果想要调用父类中的则不能省略
5、super() 只能出现在构造方法第一行,通过当前的构造方法去调用父类”中的构造方法,
目的是:创建子类对象的时候,先初始化父类型特征。
6、super()表示通过子类的构谐方法调用父类的构造方法。模拟现实世界中的这种场景:要想有儿子,需要先有父亲。
3、super()详解
1、重要的结论:
当一个构造方法第一行:既没有this() 又没有super() 的话,默认会有一个super():表示通过当前子类的构造方法调用父类的无参数构造方法,所以必须保证父类的无参数构造方法是存在的。
- 注意: this()和super() 不能共存,在一个构造方法中只能有一个,它们都是只能出现在构造方法第一行。
- super()是默认就有的,this()不是默认的
- 无论是怎样折腾,父类的构造方法是一定会执行的。(百分百的。所以一定会有super()方法)
2、注意:在构造方法执行过程中一连串调用了父类的构造方法,父类的构造方法又继续向下调用它的父类的构造方法,但是实际上对象只创建了一个,父类实例互相嵌套
3、super(实参)的作用是: 初始化当前对象的父类型特征,并不是创建新对象。实际上对象只创建了1个。
4、super的出现位置
- super可以出现在“实例方法”当中,(语法:super.父类实例变量名/实例方法名 )
- super指向当前正在执行这个动作或属性的类的实例对象的父类的实例(嵌套)。不能用在带有static关键字的“静态方法”中,因为static是纯类私有,没有super对象
- 在父和子中有同名的属性,或者说有同名的方法,如果要在子类中访问父类的数据,必须使用super.
- super可以出现在“构造方法”当中, (语法:super( )或 super.父类实例数据 )
- 通过当前的的构造方法调用其他的父类构造方法,使用super( )调用当前实例的父类的其他构造方法,而不用使用new,会创建一个新的对象
- super()这种语法只能出现在构造函数的第一行,只能用一次
-
主方法与非主方法:
- 一般在主方法中使用 对象名 调用当前类的实例变量和实例方法;
- 不在主方法中使用 super来调用当前类实例的父类中的实例变量和实例方法
5、object类只有 一个无参构造方法,必须调用
Object老祖宗类只有一个无参构造方法,必须调用
6、案例一:super() 和 this()方法演示
SuperTest01.java
package com.super关键字.javase;
public class SuperTest01 {
public static void main(String[] args) {
//创建子类对象
new B();
/*
* 执行结果:
* A类的无参数构造方法!
B类的有参数构造方法(String)
B类的无参数构造方法!
* */
}
}
class A extends Object{
//应手动的将无参数构造方法写出来
public A(){
super();
System.out.println("A类的无参数构造方法!");
}
//一个类如果没有收到提供任何构造方法,系统会默认提供一个无参数的构造方法
//一个类如果手动提供了一个构造方法,那么无参数构造方法系统将不再提供
public A(int i){
super();
System.out.println("A类的有参数构造方法(int)!");
}
}
class B extends A{
public B(){
this("zhangsan");
// super(100); //默认会有这个,但是由于有了this(),所以没有super()
System.out.println("B类的无参数构造方法!");
}
public B(String name){
super();
System.out.println("B类的有参数构造方法(String)");
}
}
SuperTest02.java
package com.super关键字.javase;
public class SuperTest02 {
public static void main(String[] args) {
new C1();
/*
* 执行结果:
* A1类的无参数构造方法指向
* B1的有参数构造方法执行(String)
* C1的有参数构造方法执行(String,int)
* C1的有参数构造方法执行(String)
* C1类的无参数构造方法执行
* */
}
}
class A1 extends Object{
public A1(){
super();
System.out.println("A1类的无参数构造方法指向");
}
}
class B1 extends A1{
public B1(){
super();
System.out.println("B1的无参数构造方法执行");
}
public B1(String name){
super();
System.out.println("B1的有参数构造方法执行(String)");
}
}
class C1 extends B1{
public C1(){
this("zhangsan");
System.out.println("C1类的无参数构造方法执行");
}
public C1(String name){
this(name,20);
System.out.println("C1的有参数构造方法执行(String)");
}
public C1(String name,int age){
super(name);
System.out.println("C1的有参数构造方法执行(String,int)");
}
}
SuperTest03.java
package com.super关键字.javase;
public class SuperTest03 {
public static void main(String[] args) {
/*Account a = Account.m1();
a.setActno("jzq");
System.out.println(a.getActno());*/
CreditAccount ca1 = new CreditAccount();
System.out.println(ca1.getActno() + "," + ca1.getBalance() + "," +ca1.getCredit());
CreditAccount ca2 = new CreditAccount("jzq",10000,1.0);
System.out.println(ca2.getActno() + "," + ca2.getBalance() + "," +ca2.getCredit());
}
}
class Account extends Object{
private String actno;
private double balance;
public Account() {
super();
}
/*public static Account m1(){
return new Account();
}*/
//setters and getters
public Account(String actno, double balance) {
super();
this.actno = actno;
this.balance = balance;
}
//
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
System.out.println();
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
class CreditAccount extends Account {
//子类特有
private double credit;
public CreditAccount() {
super();
}
public CreditAccount(String actno, double balance, double credit) {
//父类私有,在类外不能调用,编译会报错
/*this.actno = actno;
this.balance = balance;*/
super(actno, balance);
this.credit = credit;
}
//setters and getters
public double getCredit() {
return credit;
}
public void setCredit(double credit) {
this.credit = credit;
}
}
7、案例二:super. 不能省略,省略默认为this .
SuperTest05.java
package com.super关键字.javase;
public class SuperTest05 {
public static void main(String[] args) {
Vip1 vip = new Vip1("张三");
vip.shopping();
/*
* 输出结果:
null正在购物
张三正在购物
null正在购物
* */
vip.doSome();
}
}
class Customer1 extends Object{
String name;
public Customer1() {
super();
}
public Customer1(String name){
super();
this.name = name;
}
public void m1(){
System.out.println("Customer1 's m1");
}
public void doSome(){
System.out.println(this.name + "do some!"); //张三do some!
System.out.println(name + "do some!"); //张三do some!
}
}
class Vip1 extends Customer1{
//假设子类也有一个同名属性
String name;
public Vip1(){
super();
}
public Vip1(String name){
super(name);
// this.m1();
}
@Override
public void m1(){
System.out.println("Vip1 's m1");
}
public void shopping(){
System.out.println(this.name + "正在购物"); //null正在购物
//如果父类和子类中有同名属性,
System.out.println(super.name + "正在购物"); //张三正在购物
System.out.println(name + "正在购物"); //null正在购物
this.m1(); //Vip1 's m1
super.m1(); //Customer1 's m1
m1(); //Vip1 's m1
}
}
8、案例三:super. 和 this. 演示
SuperTest06.java
package com.super关键字.javase;
public class SuperTest06 extends Object{
public void doSome(){
//输出“引用”的时候,会自动调用引用的 toString() 方法
// System.out.println(this.toString());
System.out.println(this); //SuperTest06@4554617c
//编译报错:super不能单独使用
//System.out.println(super);
Cat c = new Cat();
c.yidong();
}
//this和super不能使用在static静态方法中
/*public static void doOther(){
System.out.println(this);
System.out.println(super.xxx);
}*/
public static void main(String[] args) {
SuperTest06 st = new SuperTest06();
st.doSome();
}
}
class Animal extends Object{
public void move(){
System.out.println("Animal move");
}
}
class Cat extends Animal{
@Override
public void move(){
System.out.println("Cat move");
}
public void yidong(){
this.move(); //Cat move
move(); //Cat move
super.move(); //Animal move
}
}
第十七章、包管理机制
在Java中,自定义的类和方法可以直接在同一个包中被访问和使用,无需显式导入。
Java语言规范中规定了在同一个包中的类可以访问彼此的protected和default访问级别的成员,尽管在同一个包中的类可以直接访问和使用,但它们仍然是不同的类文件。它们之间的访问权限是由Java语言规范中定义的规则来控制的
一、概述
包又称为“package”,java中引入package这种语法机制主要是为了方便程序的管理,不同功能的类被分门别类放到不同的软件包中,方便管理
二、package机制
在java源程序第一行上编写 package语句,Package只能编写一个语句
语法结构: package 包名; //指代编译后的class文件所在的位置
1、语法规范
1、包名的命名规范:域名倒序 + 项目名 + 模块名 + 功能名
主要采用这种方式重名的几率较低,因为域名具有全球唯一性
com.bjpowernode.os.user.service; com: company
有5个目录
- com目录
- bjpowernode目录
- os目录
- user目录
- service目录
2、包名要求全部小写,包名也是标识符,必须遵守标识符的命名规则
3、一个包将来对应的是一个目录,目录之间使用点(.)隔开
在 Java 中,包的命名规则遵循一定的约定俗成。通常,包名都是以小写字母开始,并采用倒置的域名格式来命名,例如:com.example.mypackage
。
包名应该只包含小写字母、数字和点号(.),不应该包含任何其他字符。点号用于分隔包名的各个部分,每个部分应该是一个有效的 Java 标识符,且应该尽可能地具有描述性。例如,如果你的应用程序需要处理音乐文件,那么你可以使用 com.example.music
作为你的包名,这样可以更好地描述应用程序的功能。
包名应该尽可能地简洁和有意义,避免使用过长或者无意义的包名。此外,包名应该尽可能地避免与其他类库或者应用程序的包名冲突,这样可以避免命名冲突。
总之,包名是 Java 中非常重要的一个概念,好的包名可以提高代码的可读性和可维护性,因此在编写 Java 代码时需要注意包名的命名规则和约定。
2、使用package语法
使用了package机制之后,运行class文件需在class文件名前加上 该class文件所在的包名
构成完整类名:包名 + 类名
第一种方式:
编译:javac java源文件路径 (在硬盘上生成一个class文件:Test01.class)
手动方式创建目录:将Test01.class 字节码文件放到指定的目录下
运行:java com.beyondsoft.javase.day1.Test01
第二种方式:
编译:javac -d 编译之后存放路径 java源文件的路径
Javac -d . *.java 将当前路径中的*.java编译之后存放到当前目录下
例如:将F:\Hello.java 文件编译之后放到c:\目录下
Javac -d c:\ F:\Hello.java
运行:JVM的类加载器ClassLoader 默认从当前路径下加载
保证DOS命令窗口的路径先切换到com包所在的路径,执行:是从com包所在的路径起始的
java com.beyondsoft.javase.day1.Test01
3、省略前面包名的条件
当两个class文件在一个包下时或者Java.lang.* 可省略,
4、完整类名用法
三、import机制
1、语法规范
import 语法格式:
import 完整类名; //完整类名带包名的
Import 包名 . * ; // *表示通配符,只到类这一级,表示该包下面的所有类
2、不需要显示导入的条件
1、Java.lang.* 不需要手动引入,系统自动引入
lang:language语言包,是java语言的核心类库,不要手动引入
2、同一个包中的class文件
3、使用条件
用来完成导入其他类,同一个包下的类不需要导入,不在同一个包下的类需要手动导入
4、使用规范
import语句需要编写在package语句之下,class语句之上
例如,如果在一个包中定义了一个名为MyClass
的类,在同一个包中的其他类中可以直接使用MyClass
,无需显式导入:
package com.example;
public class MyClass {
// ...
}
// 在同一个包中的其他类中可以直接使用 MyClass
package com.example;
public class OtherClass {
MyClass myObject = new MyClass();
// ...
}
如果在不同的包中使用自定义类,就需要显式导入了。例如:
如果你需要导入的类比较多,你也可以使用通配符 *
来导入整个包
package com.example1;
import com.example2.MyClass;
public class OtherClass {
MyClass myObject = new MyClass();
// ...
}
在上面的例子中,com.example1
包中的OtherClass
类需要使用com.example2
包中的MyClass
类,因此需要使用import
语句显式导入。
Test01.java
// 使用package机制之后,类名不再是Test01 而是 com/beyondsoft/javase/day1/Test01
//需要手动创建对应目录,将Test01.class文件放到指定目录中
package com.beyondsoft.javase.day1; // 一共有四个目录
public class Test01{
public static void main(String[] args){
System.out.println("Test 's main method executed!");
}
}
Test02.java
package com.beyondsoft.javase.day1;
public class Test02{
public static void main(String[] args){
//完整类名:com.beyondsoft.javase.day1.Test01
com.beyondsoft.javase.day1.Test01 t = new Test01();
System.out.println(t); //com.beyondsoft.javase.day1.Test01@372f7a8d
//可以省略包名
Test01 tt = new Test01();
System.out.println(tt); //com.beyondsoft.javase.day1.Test01@2f92e0f4
}
}
Test03.java
package com.beyondsoft;
public class Test03{
public static void main(String[] args){
/*
创建Test01实例对象
以下代码编译错误:当省略包名之后,会在当前包下赵Test.class文件
实际上编译器去找:com.beyondsoft.Test01.class文件,该文件不存在,所以编译报错
Test01 t = new Test01();
System.out.println(t);
*/
//修改以上错误
com.beyondsoft.javase.day1.Test01 t = new com.beyondsoft.javase.day1.Test01();
System.out.println(t); //com.beyondsoft.javase.day1.Test01@372f7a8d
com.beyondsoft.javase.day1.Test01 tt = new com.beyondsoft.javase.day1.Test01();
System.out.println(tt); //com.beyondsoft.javase.day1.Test01@2f92e0f4
}
}
Test04.java
import java.util.*;
public class Test04{
public static void main(String[] args){
com.beyondsoft.javase.day1.Test01 t = new com.beyondsoft.javase.day1.Test01();
System.out.println(t); //com.beyondsoft.javase.day1.Test01@372f7a8d
com.beyondsoft.javase.day1.Test01 tt = new com.beyondsoft.javase.day1.Test01();
System.out.println(tt); //com.beyondsoft.javase.day1.Test01@2f92e0f4
com.beyondsoft.javase.day1.Test01 ttt = new com.beyondsoft.javase.day1.Test01();
System.out.println(ttt); //com.beyondsoft.javase.day1.Test01@28a418fc
//以上程序可以,就是麻烦一些
Test01 t1 = new Test01();
System.out.println(t1); //com.beyondsoft.javase.day1.Test01@5305068a
String s = "abc";
System.out.println(s);
//直接编写以下代码编译报错
Date d = new Date();
}
}
四、module(模块)和package(包)机制
在 Java 中,包(Package)和模块(Module)都是用于组织和管理代码的机制,但它们有着不同的作用和用途。让我们逐一详解它们:
1、包(Package)
包是一种用于组织类和接口的命名空间。它是 Java 编程中的一个基本概念,用于解决命名冲突和代码组织的问题。通过将相关的类和接口放置在同一个包中,可以提高代码的可读性和可维护性。
- 包的定义:在 Java 源代码文件的开头使用
package
关键字定义包名。
package com.example.myapp;
-
包的作用:包的主要作用是将类和接口组织在一起,避免命名冲突,并提供可见性控制。默认情况下,没有显式包声明的类属于无名包(unnamed package),而显式声明了包的类则属于指定的包。
-
包的访问控制:Java 中的包提供了包级私有访问控制,即包内部的类和接口可以相互访问,但包外部的类和接口无法访问包内部的包级私有成员。
2、模块(Module)
模块是 Java 9 引入的一个新特性,它是一组相关的包的集合,并提供了更强大的封装和可见性控制机制。模块化系统(Module System)旨在解决传统 Java 类路径(classpath)带来的一些问题,如冗余、版本冲突和安全性问题。
- 模块的定义:在 Java 9 及以上版本中,可以使用
module
关键字定义一个模块。
module com.example.myappmodule {
// 模块声明
}
-
模块的作用:模块将相关的包组织在一起,形成一个单独的单元。模块提供了更严格的可见性控制,只有在
module-info.java
中显式导出的包才能被其他模块访问,其它未导出的包对外部是隐藏的。 -
模块的导出:模块可以使用
exports
关键字显式导出包,使得其他模块可以访问导出的包中的类和接口。如果未导出包,则对外部是隐藏的,只能在本模块内部访问。 -
模块化系统的优势:模块化系统使得应用程序更易于维护和扩展,同时提高了代码的安全性和性能,因为只有导出的包才能被其他模块访问,未导出的包对外部是不可见的。
在 Java 中,包和模块是用于组织和管理代码的两个重要机制,它们都有助于提高代码的可读性、可维护性和安全性。包主要用于逻辑组织代码,而模块则在包的基础上提供了更强大的封装和可见性控制,以解决类路径问题和提高应用程序的健壮性。
报错:Module declaration should be located in a module's source root 怎么解决
这个报错通常出现在使用 Java 模块化系统时,module-info.java
文件没有放置在模块的源代码根目录下。解决这个问题的方法是确保 module-info.java
文件位于正确的位置。
在 Java 9 及以上版本中,如果你想使用模块化系统,确保以下步骤:
-
创建模块源代码目录: 在你的项目中,创建一个名为
src
(或其他你喜欢的名称)的目录作为模块的源代码根目录。在这个目录下,再创建一个名为module-info.java
的文件,用于声明模块信息。 -
定义模块: 在
module-info.java
文件中定义模块信息,包括模块的名称和导出的包。确保正确地使用module
关键字定义模块。例如,假设你的项目结构如下:
your-project/
├── src/
│ ├── module-info.java // 在这里声明模块信息
│ └── com/
│ └── example/
│ └── myapp/
│ └── Main.java
└── ...
java中的模块如何使用
在 Java 9 中引入了模块化系统,它的目标是解决Java平台的可伸缩性、性能和安全性等问题。模块系统允许将代码划分为更小的模块,这些模块可以在需要时进行组合和重用。以下是在 Java 中使用模块的基本步骤:
-
创建模块: 在你的项目中,你需要定义一个模块。一个模块就是一个目录,其中包含一个名为
module-info.java
的文件。该文件用于声明模块的名称、依赖关系和导出的包。一个简单的例子如下:
module com.example.mymodule {
requires java.base; // 声明依赖的Java标准库模块
exports com.example.mymodule.package1; // 导出包,使其他模块可以访问其中的公共类
}
- 编译模块: 使用 JDK 9 或更高版本的
javac
编译器编译包含模块声明的代码。例如,使用以下命令:
javac -d outDir module-info.java com/example/mymodule/package1/*.java
-
这将编译你的模块代码,并将生成的类文件放在指定的输出目录
outDir
中。 -
运行模块: 在 Java 9 中,你可以使用
java
命令运行模块。例如,要运行你的模块,可以使用以下命令:
java --module-path outDir -m com.example.mymodule/com.example.mymodule.package1.MainClass
-
其中
outDir
是编译输出的目录,com.example.mymodule
是模块名,com.example.mymodule.package1.MainClass
是你的模块的主类。 -
处理依赖关系: 在模块中声明的
requires
部分定义了模块的依赖关系。在运行时,Java 将自动处理模块之间的依赖关系。
以上是简单的模块使用示例。值得注意的是,模块化系统提供了更复杂的功能,包括自动模块(用于非模块化的旧代码)和命名模块(用于第三方库的模块化)。了解这些更高级的概念可以帮助你更好地组织和管理你的 Java 项目。
在另一个模块中如何访问尼
在 Java 模块系统中,一个模块可以通过导出(exports)其包(package)来使其他模块能够访问其中的公共类和接口。导出的包就相当于模块的公共接口,其他模块可以依赖于该模块并使用其导出的公共功能。
假设你有两个模块:模块 A 和模块 B。现在,你想在模块 B 中访问模块 A 中导出的某个包,下面是步骤:
-
确保模块 A 已经导出了相应的包。 在模块 A 的
module-info.java
文件中,你需要使用exports
关键字导出需要在其他模块中访问的包。例如:
module com.example.moduleA {
exports com.example.moduleA.package1; // 导出 com.example.moduleA.package1 包
}
确保模块 B 已经声明对模块 A 的依赖。 在模块 B 的 module-info.java
文件中,你需要使用 requires
关键字声明对模块 A 的依赖。例如:
module com.example.moduleB {
requires com.example.moduleA; // 声明对 com.example.moduleA 模块的依赖
}
在模块 B 中使用模块 A 中导出的包。 在模块 B 的代码中,你现在可以直接访问模块 A 中导出的包的公共类和接口。例如:
// 在 com.example.moduleB 中的代码中
import com.example.moduleA.package1.SomeClass;
public class MyClassInModuleB {
public static void main(String[] args) {
SomeClass someObject = new SomeClass();
// 现在你可以使用 SomeClass 类的实例了
}
}
通过上述步骤,模块 B 就可以访问模块 A 中导出的包,并使用其中的公共类和接口。需要注意的是,只有被导出的包中的公共类和接口才能在其他模块中被访问,非导出的包中的类对其他模块是不可见的。这也是模块化系统的一个重要特性,可以帮助你控制模块之间的可见性和访问权限,从而更好地管理代码的依赖关系。