深入理解面向对象(第二篇)
🏀面向对象
🥅package和import
❤️package
1、为什么要使用package?
package是java中包机制,包机制的作用是为了方便程序的管理;
不同功能的类分别存放在不同的包下(按照功能划分的,不同软件包具有不同的功能)
2、package是怎么用的?
package是一个关键字,后面加包名;例如:package com.dlu.javase.day01
注意:package语句只允许出现在java源代码的第一行
3、包名的命名规范?
一般采用公司域名倒叙的方式(因为公司域名具有全球唯一性)
包名命名规范:公司域名倒序+/名+模块名+功能名
4、对于带有package的java程序怎么编译?怎么运行?
⭐️以文本编译器为例:
1、编译:javac -d . PackageTest01
-d表示带包编译
.表示编译之后生成的class文件放到当前目录下
2、运行:java com.dlu.javase.day01.PackageTest01
3、解释:实际上在PackageTest01.java文件目录下会生成com文件夹---》com文件夹下生成dlu文件夹---》dlu文件夹下生成javase文件夹---》javase文件夹下生成day01文件夹---》day01文件夹下生成PackageTest01.class文件。运行时还是在PackageTest01.java文件目录下执行:java com.dlu.javase.day01.PackageTest01即可!
⭐️IDEA编译器为例:
1、先创建包(Package);和创建类的时候步骤一致
2、创建包之后,在包下面再创建类,生成的类就会自动带上package com.dlu.javase.day01;然后直接运行就行
package com.dlu.javase.day01; public class PackageTest01 { public static void main(String[] args) { System.out.println("HelloWorld"); } }![]()
❤️import
⭐例1:两个类包名相同(包名能省略)
package com.dlu.javase.day01; //包名与PackageTest01包名相同 public class Test01{ public static void main(String[] args){ // 创建PackageTest01对象 //PackageTest01的完整类名是:com.dlu.javase.day01.PackageTest01(这是带包的情况下类名) com.dlu.javase.day01.PackageTest01 hw = new com.dlu.javase.day01.PackageTest01(); System.out.println(hw);//com.dlu.javase.day01.PackageTest01@15db9742 //包名省略?---也没问题 //这里的包名之所以可以省略,是因为PackageTest01和Test01在同一个Package(包)下 PackageTest01 hw1 = new PackageTest01(); System.out.println(hw1);//com.dlu.javase.day01.PackageTest01@6d06d69c } }
⭐例2:两个类包名不同(包名不能省略,可以用import进行导包)
1、import什么时候使用?例如:A类中使用B类:
A和B类都在同一个包下;不需要import!
A和B类不在同一个包下;需要使用import!默认java.lang.*;这个包下的类(直接子类)都不需要使用import导入
2、import怎么用?
import语句只能出现在package语句下,class声明语句之前。
import语句也可以采用星号(*)的方式,星号省略的是当前类名,不能多省略
package com; //包名与PackageTest01包名不相同 //import com.dlu.javase.day01.PackageTest01;//导如需要的包 import com.dlu.javase.day01.*;//采用星号的方式也是可以的 public class Test02{ public static void main(String[] args){ // 创建PackageTest01对象 com.dlu.javase.day01.PackageTest01 hw = new com.dlu.javase.day01.PackageTest01(); System.out.println(hw);//com.dlu.javase.day01.PackageTest01@15db9742 //这里包名就不能省略: /* Test02在com包下 PackageTest01在com.dlu.javase.day01下 不在同一个包下,包名不能省略 */ /* 但是我们每次创建PackageTest01对象又很麻烦,那么长的类名;所以这就需要import PackageTest01 hw1 = new PackageTest01(); System.out.println(hw1); //err */ //import com.dlu.javase.day01.PackageTest01导入包以后,就可以省略包了 PackageTest01 hw2 = new PackageTest01(); System.out.println(hw2);//com.dlu.javase.day01.PackageTest01@6d06d69c } }
❤️解释遗留问题Scanner
package com.dlu.javase.day01; import java.util.Scanner;//导包 //import java.util.*;//也可以 public class Test03{ public static void main(String[] args){ //为什么这样写? //Test03类和Scanner类不在同一个包下;java.util就是Scanner的包名 //java.util.Scanner s = new java.util.Scanner(System.in); //import导包的形式 Scanner s = new Scanner(System.in); String str = s.next(); System.out.println("您输入的字符串是:"+str); //String为什么不需要导包呢? //因为在lang包下的直接之类都不要导入(在比如:System) java.lang.String name = "张三"; String username = "李四"; System.out.println(name); System.out.println(username); } }
❤️总结
⭐package(对于文本编译器)
第一:package出现在java源文件第一行。
第二:带有包名怎么编译?javac -d . xxx.java
第三:怎么运行?java 完整类名
补充:以后说类名的时候,如果带着包名描述,表示完整类名。如果没有带包,描述的话,表示简类名。例如:
(1)java.util.Scanner 完整类名。
(2)Scanner 简类名⭐import(对于文本编译器)
1、import什么时候不需要?
java.lang下的不需要,同包下不需要;其它一律都需要!2、怎么用?
(1)import 完整类名;import java.util.Scanner; // 完整类名。
(2)或者import 包名.*
import java.util.*;
编译器在编译的时候,会自动把*变成具体的类名。
(3)想省但也不能太省了。
import java.*;这是不允许的,因为在java语言中规定,这里的*只代表某些类的名字。
3、不同包名下的相同类名不会冲突;例如:org.Test和com.Test不会冲突
🥅访问控制权限修饰符
1、访问控制权限都有哪些?4个。
private 私有protected 受保护
public 公开
默认(什么都没有)
2、以上的4个访问控制权限:控制的范围是什么?
(1)private 表示私有的,只能在本类中访问(2)protected表示只能在本类、同包、子类中访问。
(3)public 表示公开的,在任何位置都可以访问
(4)“默认”表示只能在本类,以及同包下访问。
访问控制修饰符 本类 同包 子类 任意位置 --------------------------------------------------------------------------- public 可以 可以 可以 可以 protected 可以 可以 可以 不行 默认 可以 可以 不行 不行 private 可以 不行 不行 不行![]()
范围从大到小排序:public(4) > protected(3) > 默认(2) > private(1)
3、访问控制权限修饰符可以修饰什么?
属性(4个都能用)
方法(4个都能用)
类(public和默认能用,其它不行。)
接口(public和默认能用,其它不行。)
❤️例:在com.dlu包下的User类
package com.dlu; public class User{ // 私有的 private int id; // 受保护的 protected int age; // 公开的 public int weight; //什么也没有,默认的 String name; }
⭐【包没变】在User类(com.dlu)相同包下的Test(com.dlu)类:只有private修饰的不能访问;区分出来第一个private
package com.dlu; public class Test{ public static void main(String[] args){ User u = new User(); //System.out.println(u.id); // private私有的不可以访问 System.out.println(u.age); //protected可以访问 System.out.println(u.weight); //public可以访问 System.out.println(u.name); //默认的可以访问 } }
⭐【包变了】在User类(com.dlu)不同包下的Test01(com.dlu01)类:只有public能访问;区分出来第二个public
package com.dlu01; //包变了 import com.dlu.User; //导包 public class Test01{ public static void main(String[] args){ User u = new User(); ////System.out.println(u.id); // private私有的不可以访问 //System.out.println(u.age); //protected不可以访问 System.out.println(u.weight); //public修饰的可以访问 //System.out.println(u.name); //默认的不可以访问 } }
⭐【包变了】在User类(com.dlu)不同包下的Test02(com.dlu02)类,并且Test02继承User类;区分出protected
package com.dlu02; //包变了 import com.dlu.User; //导包 //User在com.dlu包下;Test在com.dlu02包下;User和Test不在同一个包 //但是Test02是User的子类 public class Test02 extends User{ public static void main(String[] args){ Test02 t = new Test02(); System.out.println(t.age); // protected修饰的可以访问 //System.out.println(t.name); //默认的不能访问 } }
🥅Object类
JDK类库的根类:Object
⭐这个老祖宗类中的方法我们需要先研究一下,因为这些方法都是所有子类通用的。
任何一个类默认继承Object。就算没有直接继承,最终也会间接继承。⭐Object类当中有哪些常用的方法?我们去哪里找这些方法呢?
第一种方法:去源代码当中;但是这种方式比较麻烦,源代码也比较难;C:\Program Files\Java\jdk-13.0.2\lib\src\java.base\java\lang包下的Object类
第二种方法:去查阅java的类库的帮助文档。例如我安装的:jdk1.8:C:\Java学习\javaSE学习\6.JDK帮助文档\jdk8-oracle官方英文-帮助文档\jdk8帮助文档\java\lang下的Object.html
⭐什么是API?
应用程序编程接口。(Application Program Interface)
整个JDK的类库(src)就是一个javase的API。
每一个API都会配置一套API帮助文档。
SUN公司提前写好的这套类库就是API。(一般每一份API都对应一份API帮助文档。)⭐目前为止我们只需要知道这几个方法即可:
protected Object clone() // 负责对象克隆的(以后接触再讲)。 int hashCode() // 获取对象哈希值的一个方法。 boolean equals(Object obj) // 判断两个对象是否相等 String toString() // 将对象转换成字符串形式 protected void finalize() // 垃圾回收器负责调用的方法![]()
❤️例1:Object类的toString方法
1、源代码
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }![]()
toString()方法默认实现是:类名+@+对象的内存地址转换为十六进制的形式
2、Sun公司设计toString()方法的目的是什么? 通过调用这个方法可以将一个“java对象”转换成“字符串表示形式”
3、建议所有的子类都去重写toString()方法,toString()方法应该是一个简洁的、详实的、易阅读的
public class ObjectToString { public static void main(String[] args) { MyTime mt = new MyTime(2022,1,1); //重写toString()方法之前 System.out.println(mt); //MyTime@4554617c;默认会调用toString方法 //重写toString()方法之后 System.out.println(mt); //2022年1月1月(简洁的、详实的、易阅读的) } } class MyTime{ //默认继承Object类 private int year; private int month; private int day; //构造方法 public MyTime() { } public MyTime(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } //重写toString()方法 public String toString(){ return this.year+"年"+this.month+"月"+this.day+"月"; } }
❤️例2:Object类的equals方法
1、源代码
public boolean equals(Object obj) { return (this == obj); }![]()
2、Sun公司设计equals方法的目的是什么? 通过equals方法来判断两个对象是否相等
3、我们需要研究一下Object类给的这个默认equals方法够不够用?(不够用) 在Object类当中的equals方法当中,默认采用的是“==”判断两个java对象是否相等; 而“==”判断的是两个java对象的内存地址,我们应该判断两个java对象的内容是否相等;要使用equals方法!
public class ObjectEquals { public static void main(String[] args) { //1.判断两个基本数据类型是否相等,直接用“==” // "=="是判断a中保存的100和b中保存的100是否相等 int a = 10; int b = 10; System.out.println(a == b);// true //2.判断两个java对象是否相等呢?(直接用==) // 这里的“==”判断的是t1中保存的对象内存地址和t2中保存的对象内存地址是否相等 MyTime01 t1 = new MyTime01(2008,8,8); MyTime01 t2 = new MyTime01(2008,8,8); System.out.println(t1 == t2);// false; // 但实际上这两个对象表示的时间是一样的,所以这两个对象应该是相同的true, // 所以不能用"=="来直接判断两个对象是否相等 //3.判断两个java对象是否相等呢?(调用equals方法) //---重写equals方法之前,默认调用的是Object类的equals方法 boolean bool = t1.equals(t2);//多态 System.out.println(bool); //---重写equals方法之后 System.out.println(t1.equals(t2)); //4.目前程序有bug嘛,如果为空呢?(没有,但是效率低,进行改良) MyTime01 t3 = null; System.out.println(t1.equals(t3)); //没有bug,因为下面有instanceof进行判断,空指针直接就return false } } class MyTime01 { //默认继承Object类 private int year; private int month; private int day; public MyTime01() { } public MyTime01(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } //-----------重写equals方法 public boolean equals(Object obj) { //当年、月、日都相同,表示日期相同 //获取第一个日期的年月日 int year1 = this.year; int month1 = this.month; int day1 = this.day; //obj访问不到年月日,要想访问子类型中特有的东西,所以要进行向下转型 if(obj instanceof MyTime01){ //判断 MyTime01 mt = (MyTime01)obj; int year2 = mt.year; int month2 = mt.month; int day2 = mt.day; if(year1 == year2 && month1 == month2 && day1 == day2){ return true; } } return false; } /* ------------------代码优化 public boolean equals(Object obj) { //1.先判断空 if(obj == null){ return false; } //2.判断是不是MyTime01对象 if(!(obj instanceof MyTime01)){ return false; } //3.判断内存地址是否相等,内存地址相等,肯定是同一个对象 if(this == obj){ return true; } //4.直接进行比较 MyTime01 tt = (MyTime01)obj; if(this.year == tt.year && this.month == tt.month && this.day == tt.day){ return true; } return false; } */ /* -------------------代码再次优化 public boolean equals(Object obj) { if(obj == null || !(obj instanceof MyTime01)){ return false; } if(this == obj){ return true; } MyTime01 tt = (MyTime01)obj; return this.year == tt.year && this.month == tt.month && this.day == tt.day; } */ /*-----------------也可以利用IDEA自动生成 public String toString() { return "MyTime01{" + "year=" + year + ", month=" + month + ", day=" + day + '}'; } public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MyTime01 myTime01 = (MyTime01) o; return year == myTime01.year && month == myTime01.month && day == myTime01.day; } */ }
⭐比较字符串的大小
1、java语言中的字符串String已经重写toString方法和重写equals方法
2、比较两个两个字符串也不能使用"==";必须调用equals方法
3、对于字符串比较大小,如果是直接写出来的,例如:String s = "abc"可以直接用"=="进行比较;如果是new出来的,例如:String s = new String("abc")就必须使用equals方法进行比较;所以我们平时就使用equals方法来判断引用数据类型,更加的通用
4、总结:java中基本数据类型比较是否相等,使用"=="
java中引用数据类型比较是否相等,统一使用equals方法进行判断
public class ObjectEquals02 { public static void main(String[] args) { //大部分情况下,采用这样的方式创建字符串对象 String s1 = "hello"; String s2 = "abc"; //实际上String也是一个类,不属于基本数据类型,既然是一个类一定有构造方法 String s3 = new String("Test1"); String s4 = new String("Test1"); System.out.println(s3 == s4);//false,因为比较的是内存的地址 //比较两个两个字符串也不能使用"==";必须调用重写的equals方法 //对于String类已经重写了String和toString方法 System.out.println(s3.equals(s4));// true //也重写了toString方法 System.out.println(s3);// Test1 } }
⭐一个综合的例题
public class ObjectEquals03 { public static void main(String[] args) { Student s1 = new Student(1234,"六盘水"); Student s2 = new Student(1234,"六盘水"); System.out.println(s1.equals(s2)); // 多态 } } class Student{ private int id; private String school; //构造方法 public Student() { } public Student(int id, String school) { this.id = id; this.school = school; } //当学号和学校相同,表示是同一个学生 public boolean equals(Object obj){ if(obj == null || !(obj instanceof Student)){ return false; } if(this == obj){ return true; } Student s = (Student)obj; // 向下转型 return this.id == s.id && this.school.equals(s.school); } }
⭐equals方法深层次理解(重点掌握)
对于equals方法我们重写要彻底!
//equals方法重写要彻底 public class ObjectEquals04 { public static void main(String[] args) { /*Address1 addr = new Address1("安徽","西城区","1111"); User1 u = new User1("张三",addr);*/ //就等价于 User1 u1 = new User1("张三",new Address1("安徽","西城区","1111")); User1 u2 = new User1("张三",new Address1("安徽","西城区","1111")); System.out.println(u1.equals(u2)); } } class User1{ String name; Address1 addr; //构造方法 public User1() { } public User1(String name, Address1 addr) { this.name = name; this.addr = addr; } //重写equals //规则:当一个用户的用户名和家庭住址都相同,表示同一个用户 public boolean equals(Object obj){ if(obj == null || !(obj instanceof User1)){ return false; } if(this == obj){ return true; } User1 u = (User1)obj; return this.name.equals(u.name) && this.addr.equals(u.addr); } } class Address1{ String city; String street; String zipcode; //构造方法 public Address1() { } public Address1(String city, String street, String zipcode) { this.city = city; this.street = street; this.zipcode = zipcode; } // 注意:这里如果没有重写equals方法;调用的是Object的equals方法,比较的是addr内存的地址 // 这里的equals方法判断的是:Address对象和Address对象是否相等 //所以这里也要重写equals,重写必须彻底! //前面String我们没有进行重写是因为默认String类已经重写了equals方法 public boolean equals(Object obj) { if(obj == null || !(obj instanceof Address1)){ return false; } if(this == obj){ return true; } Address1 address = (Address1)obj; return this.city.equals(address.city) && this.street.equals(address.street) && this.zipcode.equals(address.zipcode); } }
❤️例3:Object的finalize方法(了解)
1、在Object类中的源代码:
protected void finalize() throws Throwable { } // GC:负责调用finalize()方法![]()
2、finalize()方法只有一个方法体,里面没有代码,而且这个方法是protected修饰的
3、这个方法不需要程序员手动调用,JVM的垃圾回收器负责调用这个方法。
不像equals、toString方法是需要程序员手动调用的;finalize只需要重写,重写完成后自动会有程序来调用
4、finalize执行的时机:
当一个java对象即将被垃圾回收器回收的时候,垃圾回收器负责调用finalize()方法
5、finalize()方法实际上是SUN公司为java程序员准备的一个时机,垃圾销毁时机。
如果希望在对象销毁时机执行一段代码的话,这段代码要写到finalize()方法中
6、回顾:静态代码块的作用是什么?static{....}
静态代码块在类加载时刻执行,并且只执行一次;这是一个SUN准备的类加载时机
finalize()方法同样也是SUN为程序员准备的一个时机,这个时机是垃圾回收时机
7、java中的垃圾回收器不是轻易启动的,垃圾太少,或者时间没到等,也有可能不启动
public class ObjectFinalize { public static void main(String[] args) { /*Person1 p = new Person1(); // 怎么把Person对象变成垃圾? p = null; // 这里可能就没有启动 //多造点垃圾,就可以启动 for(int i=0;i<1000000;i++){ Person1 p1 = new Person1(); p1 = null; }*/ //有一段代码可以建议垃圾回收器启动 Person1 p = new Person1(); p = null; System.gc();// 建议启动垃圾回收器(知识启动的可能性增大了) } } class Person1{ // 重写finalize方法 // Person类型的对象被垃圾回收器回收的时候,垃圾回收器负责调用p.finalize(); protected void finalize() throws Throwable { System.out.println(this+"即将被销毁!"); } }
❤️例4:Object的hashCode方法
1、在Object中的hashCode方法是怎样的?
public native int hashCode(); // 这个方法不是抽象方法,带有native关键字,底层调用C++程序。![]()
2、hashCode()方法返回的是哈希码:
实际上就是一个java对象的内存地址,经过哈希算法,得出的一个值。
所以hashCode()方法的执行结果可以等同看做一个java对象的内存地址。
public class ObjectHashCode{ public static void main(String[] args){ Object o = new Object(); int hashCodeValue = o.hashCode(); // 对象内存地址经过哈希算法转换的一个数字。可以等同看做内存地址。 System.out.println(hashCodeValue); //1163157884 MyClass mc = new MyClass(); int hashCodeValue2 = mc.hashCode(); System.out.println(hashCodeValue2); //1956725890 } } class MyClass { }
🥅匿名内部类
❤️内部类概述
匿名内部类:
1、什么是内部类?
内部类:在类的内部又定义了一个新的类。被称为内部类。
2、内部类的分类:
静态内部类:类似于静态变量
实例内部类:类似于实例变量
局部内部类:类似于局部变量
3、使用内部类编写的代码,可读性很差。能不用尽量不用。
4、匿名内部类是局部内部类的一种。
因为这个类没有名字而得名,叫做匿名内部类。
5、学习匿名内部类主要是以后在阅读别人代码的时候,能够理解。
并不代表以后都要这样写。因为匿名内部类有两个缺点:
缺点1:太复杂,太乱,可读性差。
缺点2:类没有名字,以后想重复使用,不能用。
public class InnerTest01{ // 静态变量 static String country; //1. 静态内部类 static class Inner1{ } // 实例变量 int age; //2. 实例内部类。 class Inner2{ } // 方法 public void doSome(){ // 局部变量 int i = 100; //3. 局部内部类。 class Inner3{ } } public void doOther(){ // doSome()方法中的局部内部类Inner3,在doOther()中不能用。 } //-------------------------------------------------------------------- // main方法,入口 public static void main(String[] args){ // 调用MyMath中的mySum方法。 MyMath001 mm = new MyMath001(); /* Compute c = new ComputeImpl(); mm.mySum(c, 100, 200); */ //代码合并 //我们调用的时候里面传的是接口Compute c:mySum(Compute c, int x, int y); //所以这里调用应该需要new一个接口传进去,又因为接口是抽象类,不能new对象, //所以要写一个类ComputeImpl去实现接口,用来new对象 mm.mySum(new ComputeImpl(), 100, 200); } } // 负责计算的接口 interface Compute{ // 抽象方法 int sum(int a, int b); } // 编写一个Compute接口的实现类ComputeImpl class ComputeImpl implements Compute{ // 对方法的实现 public int sum(int a, int b){ return a + b; } } // 数学类 class MyMath001{ // 数学求和方法 public void mySum(Compute c, int x, int y){ //Compute c 把这个接口看成String name更好理解 int retValue = c.sum(x, y); System.out.println(x + "+" + y + "=" + retValue); } }
❤️内部类的使用
使用匿名内部类,下面那个ComputeImpl类实现Compute接口就不需要写了!对于匿名内部类能看懂代码就行,可读性差,且不能重复使用!
public class InnerTest02{ // main方法,入口 public static void main(String[] args){ // 调用MyMath中的mySum方法。 MyMath001 mm = new MyMath001(); //我们不使用Compute接口的实现类ComputeImpl;使用匿名内部类实现 //mm.mySum(new ComputeImpl(), 100, 200); //使用匿名内部类,记住语法;以后能看懂就行 mm.mySum(new Compute() { public int sum(int a, int b) { return a+b; } },100,200); } } // 负责计算的接口 interface Compute{ // 抽象方法 int sum(int a, int b); } //-------使用匿名内部类,接口的实现就不需要了 /*// 编写一个Compute接口的实现类ComputeImpl class ComputeImpl implements Compute{ // 对方法的实现 public int sum(int a, int b){ return a + b; } }*/ // 数学类 class MyMath001{ // 数学求和方法 public void mySum(Compute c, int x, int y){ //Compute c 把这个接口看成String name更好理解 int retValue = c.sum(x, y); System.out.println(x + "+" + y + "=" + retValue); } }
结束语
今天的分享就到这里,想要提升编程思维的,快快去注册牛客网开始刷题吧!各种大厂面试真题在等你哦!
💬刷题神器,从基础到大厂面试题👉点击跳转刷题网站
编辑