目录
封装的理解
概念
(面向对象的三大特征之一)将数据和行为以类的形式进行封装 ,通过权限修饰符可以隐藏细节或控制访问的权限,从而达到权限控制的目的。
目的
1.隐藏 类的实现细节。
2.让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里加入控制逻辑,限制 对成员变量的不合理访问。
3.可进行数据检查,从而有利于保证对象信息的完整性。便于修改,提高代码的可维护性。
4.为了实现良好的封装,需要从两个方面考虑。
①将对象的成员变量和实现细节隐藏起来,不允许外部直接访问。
②把方法暴露出来,让方法来控制对这些成员变量进行安全的访问和操作。
因此,封装实际上有两个方面的含义:把该隐藏的隐藏起来,把该暴露的暴露出来。这两个方面都需要通过使用Java提供的访问控制符来实现。
权限修饰符
访问权限从大到小
public(广场) protected (社区)default (家)private(自己的房间)
注意 :访问控制级在子类中,是指在同一个包的子类,即内部子类,不包括外部子类。
如何快速定义一个标准的Java类
1.普通方法
①属性,加私有化属性
②公有化的get,set。用来帮助私有化属性设置值以及获得值
(步骤:右键---》sources--》generate getters and setters)
③构造器; 包括无参构造器,有参构造器。
(步骤:右键---》sources--》generate constructor using fields)
④toString方法**,用于格式化输出对象中数据**。
(步骤:右键---》sources--》generatetoString)
java
public class Student {
//学号、姓名、性别、年龄
//1、私有化的属性
private String code;
private String name;
private String sex;
private int age;
//2、公有化的get、set方法
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//3、构造器【无参的、有参的】
public Student(String code, String name, String sex, int age) {
super();
this.code = code;
this.name = name;
this.sex = sex;
this.age = age;
}
public Student() {
super();
}
// 4、toString方法:作用:方便输出查看对象数据
@Override
public String toString() {
return "Student [code=" + code + ", name=" + name + ", sex=" + sex + ", age=" + age + "]";
}
}
2.快捷键
alt+shift+s 即可弹出source的页面,其余操作与上面同。
练习
1.定义标准的医生类:id、name、subject、sex
定义方法:check方法,检查病人,入参Person的对象
方法操作为输出病人的个人信息
思路:定义Doctor类(标准的java类)--属性--方法,person类,再实例测试。
java
//医生类
public class Doctor {
// 1、私有化属性id、name、subject、sex
private int id;
private String name;
private String subject;
private String sex;
//定义方法:check方法,检查病人,入参Person的对象 方法操作为输出病人的个人信息
public void check(Person p) {
System.out.println("病人姓名:"+p.name+"性别:"+p.sex);
}
// 2、get、set
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
// 3、构造器
public Doctor(int id, String name, String subject, String sex) {
super();
this.id = id;
this.name = name;
this.subject = subject;
this.sex = sex;
}
public Doctor() {
super();
}
// 4、toString
@Override
public String toString() {
return "Doctor [id=" + id + ", name=" + name + ", subject=" + subject + ", sex=" + sex + "]";
}
}
java
//人类:由多个、无数个真实存在的对象抽象而成:具备这个种类的一些共同点:外貌--行为
public class Person {
// 在类体中定义的属性和方法
//一般类中的属性都是私有化的,方法则按需定义【如果没有特殊要求,则是public】
//如果在继承关系中,父类给子类继承的属性和方法建议是public修饰
//封装是将属性及行为以类的形式封装,通过权限修饰符将该隐藏的隐藏,将该暴露的暴露【达到权限控制的目的】
// 外貌--属性--静态特征 定义一个【Person】类 中有: 名称name、性别sex、地址address的属性
//protected是在子类内部可以访问
public String name;
protected String sex;
private String address;
//name,sex,age,wealth,cash
public double wealth;
public double cash;
//提供公有化的获取及设置的方法
public void setName(String n) {
name = n;
}
public String getName() {
return name;
}
//修饰符是default
void eat() {
System.out.println("吃饭");
}
// 构造器本质是一个特殊的方法【不需要返回值类型、方法名和类名一致】
public Person() {// 无参构造器
}
public Person(String n, String s, String a) {// 全参构造器:在创建对象的同时,给成员变量赋值
name = n;
sex = s;
address = a;
}
}
2.银行类:属性{name,adress,totalwealth}--提取现金的方法bank
person:属性{name,sex,age,wealth,cash}
思路与练习1同,练习1的测试与练习二的测试相类似。
java
//银行类
public class Bank {
// name,adress,totalwealth
private String name;
private String adress;
private double totalwealth;
//--提取现金的方法bank
public void drawMoney(Person p,double money) {
//银行总财富减少
totalwealth = totalwealth-money;
//人现金流增加
p.cash = p.cash+money;
//
System.out.println(p.name+"成功取款:"+money);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAdress() {
return adress;
}
public void setAdress(String adress) {
this.adress = adress;
}
public double getTotalwealth() {
return totalwealth;
}
public void setTotalwealth(double totalwealth) {
this.totalwealth = totalwealth;
}
public Bank(String name, String adress, double totalwealth) {
super();
this.name = name;
this.adress = adress;
this.totalwealth = totalwealth;
}
public Bank() {
super();
}
@Override
public String toString() {
return "Bank [name=" + name + ", adress=" + adress + ", totalwealth=" + totalwealth + "]";
}
}
static
定义
static 表示的静态的意思,属于类的,当使用到这个类的时候会静态加载静态的属性以及方法。
static用于修饰变量和方法、代码块
静态与非静态区分
1.有static修饰的则为静态的,没有的则为非静态
2.静态的是属于类 的,静态的东西是每个对象都共享 的【同一个内存区域】
而非静态是属于对象的,每个对象都有一个独立的内存空间【互不干涉】
3.静态方法可以通过类名直接访问; 非静态的属性和方法要通过对象访问访问
4.非静态代码块可以直接访问 当前类中的非静态方法 和属性。
静态的代码块中,不能直接访问非静态的方法和属性,除非通过对象调用。
使用静态与非静态的场合
工具方法、常量使用静态修饰,同一个类中共享的可以静态
定义类时的 成员****方法以及属性,一般就是非静态修饰
java
//静态与非静态
public class StaticTest {
//非静态
int num;
//静态
static int item;
public StaticTest(){
}
public StaticTest(int i,int n){
item = i;
num = n;
}
//思考:能不能在静态方法中调用非静态的方法及属性?不可以,除非创建对象,通过对象来调用【本质是内存分配】
//静态的属性及方法会在类加载时,会将其加载到内存中【静态的属于类的】
public static void main(String[] args) {
// 静态的是属于类的,在调用时可以直接加载到内存
//[静态的属性和方法可以直接通过类名调用,也可以用对象来进行调用]
//非静态的:没有static修饰的都是为非静态的 【非静态的属于对象的】
test();
item=5;//静态的直接调用(内部类,属性)
StaticTest.test();//若为外部类,则以这种形式 例子:Arrays.toString() (类.方法名直接调用)
StaticTest.item = 5;//若属性为外部类的属性,则以这种形式
StaticTest test = new StaticTest();//本质上也是通过类型来调用(用对象来进行调用)
test.item = 6;
StaticTest test1 = new StaticTest(3,5);
StaticTest test2 = new StaticTest();
test2.num = 10;
System.out.println(test1.item+":"+test1.num);//3 , 5
System.out.println(test2.item+":"+test2.num);//3 , 10//num非静态,每个对象值不同。
test2.item = 15;
System.out.println(test1.item);//结果为15 ,与对象没关系,item是个静态即属于类。
}
public static void test() {
System.out.println("test");
}
public void demo() {//不能在main(为静态)中,即静态的方法中直接引用,得创建对象来调用。
System.out.println("test");
}
}
成员变量和局部变量
成员变量
在类中定义的变量,其中包括类 变量(静态的)和实例变量(非静态的)
局部变量
是局部有效的,通常在方法形参 、方法体 、代码块中定义。
分为1.形参(在方法签名(即参数列表)中定义的变量)
2.方法局部变量(在方法内定义)
**3.**代码块局部变量(在代码块内定义)
例子讲解:两数交换
java
public class Swap {
//定义一个两数交换的方法
//swap(int a,int b) ab局部变量
public static void swap(int a,int b) {
int item = a;
a = b;
b = item;
System.out.println("swap-a="+a);//5
System.out.println("swap-b="+b);//3
}
public static void main(String[] args) {
//局部变量
int a = 3;
int b = 5;
swap(a,b);
//调用了交换方法,但值却没发生变化。
System.out.println("main-a="+a);//3
System.out.println("main-b="+b);//5
}
}
解决方法
从以下两个角度解决两数交换,值却交换不了的问题。
1、没有交换同一个变量【成员变量】
java
public class Swap {
//解决方法一:成员变量(静态,非静态都可以,只是调用方法不同)
int sa ;
int sb ;
//定义一个两数交换的方法
public void swap(int a,int b) {
sa=a;
sb=b;
int item = sa;
sa = sb;
sb = item;
}
public static void main(String[] args) {
Swap s1=new Swap();//sa,sb变量,swap()方法为非静态成员,因此需要创建一个新的对象来调用方法与赋值
s1.swap(3, 5);
System.out.println("main-a="+s1.sa);//5
System.out.println("main-b="+s1.sb);//3
}
}
2、问题:没有返回值【增加一个返回值--数组】
java
public class Swap {
//解决方法二:增加一个返回值(数组返回值)
//定义一个两数交换的方法
public static int[] swap(int a,int b) {//返回一个int[]数组
int item = a;
a = b;
b = item;
return new int[] {a,b};
}
public static void main(String[] args) {
int a=3;
int b=5;
int[] res=swap(a,b);//静态,直接调用swap方法
a=res[0];//赋值
b=res[1];
System.out.println("main-a="+a);//5
System.out.println("main-b="+b);//3
}
}
变量的初始化时机
局部变量
默认不会初始化,在使用的时候再进行初始化
成员变量
静态成员变量(在类加载的时候初始化)
非静态的成员变量(在创建对象的时候初始化)
==与equals方法
==
使用==是判断变量的数值是否一致
1.基本数据类型(八大数据类型)是判断数值
2.引用类型是判断对象
比较内存中的存放地址(堆地址),同个new出来的对象才是true
equals
数组对象的equals方法是继承Object,所以是默认是==;
在不重写的情况下,默认比较地址值,但一般情况下JMM会自动重写·重写后,比较两个对象的内容是否相等。
ctrl键放在equal函数上,点开,若没有代码,则在Eclipse中加载源码
源码:
可以通过改写equals 方法实现判断对象是否相同的操作
其中:数组工具类的equals方法是重新定义的,所以可以比较数组的数值;
**说明:**只要是引用类型,若没有重新指定equals方法,则还是==实现判断
改写例子
java
public class ItGay {
private String name;
private String sex;
private int age;
//改写比较方法
@Override
public boolean equals(Object obj) {
//1、当对象一致,则数值肯定一致
if(this==obj) {
return true;
}
if(this.hashCode()==obj.hashCode()) {
return true;
}
//比较对象中的数值是否完全相等
ItGay obj2 = (ItGay) obj;
//
if(!name.equals(obj2.name)) {//name
return false;
}
if(!sex.equals(obj2.sex)) {//sex
return false;
}
if(age!=obj2.age) {//age
return false;
}
//数值都相同,则返回true
return true;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public ItGay(String name, String sex, int age) {
super();
this.name = name;
this.sex = sex;
this.age = age;
}
public ItGay() {
super();
}
/*@Override
public String toString() {
return "ItGay [name=" + name + ", sex=" + sex + ", age=" + age + "]";
}*/
}
java
//测试类
public class ItGayTest {
public static void main(String[] args) {
// 创建对象
ItGay g1 = new ItGay("tom", "男", 48);
ItGay g2 = new ItGay("tom", "男", 48);
//使用==比较引用类型时,实际是比较对象是否一致【客观】
System.out.println(g1==g2);
System.out.println(g1.equals(g2));//实质上是使用Object中的equals【默认使用==】
//当两个对象的数值完全相同时,则主观认为两个对象是同一个对象【主观】,重写equal方法
}
}
什么是hashcode
hashcode主要为了提高对象判断、对象查询的效率而生成的一套机制
JVM每new一个Object,它都会将这个Object丢到一个Hash哈希表中去,这样的话,下次做 Object的比較或者取这个对象的时候,它会依据对象的hashcode再从Hash表中取这个对象。这样做的目的是提高取对象的效率。
--hashcode并不等于物理内存的地址
--equals 方法能比较两个对象的内容是否相等,因此可以用来查找某个对象是否在集合容器中,通常大致就是逐一去取集合中的每个对象元素与需要查询的对象进行equals比较,当发现某个元素与要查找的对象进行equals方法比较的结果相等时,则停止继续查找并返回肯定的信息,否则,返回否定的信息。
但是通过这种比较的方式效率很低,时间复杂度比较高 。那么我们是否可以通过某种编码方式,将每一个对象都具有某个特定的码值,根据码值将对象分组然后划分到不同的区域 ,这样当我们需要在集合中查询某个对象时,我们先根据该对象的码值就能确定该对象存储在哪一个区域,然后再到该区域中通过equals方式比较内容是否相等,就能知道该对象是否存在集合中。【散列表的算法】
通过这种方式我们减少了查询比较的次数,优化了查询的效率同时也就减少了查询的时间。
java
//hashCode只是一套用于快速识别对象--判断对象是否一致的机制,不等于物理地址
//涉及到散列表的算法,散列表通过hashCode提高查找对象的效率
System.out.println(g1.hashCode());
System.out.println(g2.hashCode());
类加载原理以及分析
类加载的时机
当Java程序首次通过下面6种方式来使用某个类或接口时 ,系统就会初始化该类或接口。
1.创建类的实例。为某个类创建实例的方式包括:使用new操作符来创建实例,通过反射来创建实例,通过反序列化的方式来创建实例。
2.创建类的实例。为某个类创建实例的方式包括:使用new操作符来创建实例,通过反射来创建
3.访问某个类或接口的**类变量,**或为该类变量赋值。
4.使用反射方式来强制创建某个类或接口对应的 java.lang.Class对象。例如代码:
5**.Class.forName("Person")**,如果系统还未初始化 Person类,则这行代码将会导致该Person类被初始化,并返回 Person类对应的java.lang.Class对象。关于Class 的 forName方法请参考18.3节。初始化某个类的子类。当初始化某个类的子类时,该子类的所有父类都会被初始化。
6.直接使用java.exe命令来运行某个主类。当运行某个主类时,程序会先初始化该主类。
除此之外,下面的几种情形需要特别指出。
对于一个final型的类变量,如果该类变量的值在编译时就可以确定下来,那么这个类变量相当于"宏变量"。Java编译器会在编译时直接把这个类变量出现的地方替换成它的值,因此即使程序使用该静态类变量,也不会导致该类的初始化。例如下面示例程序的结果。
java
//测试类
public class ClassLoaderTest {
static int item = 5;
static void test() {
System.out.println("test");
}
public static void main(String[] args) {
System.out.println(1);
}
//静态初始化块,是当类加载的时候会执行
static {
System.out.println("类加载了!");
}
}
class ClassLoaderChild extends ClassLoaderTest{
}
java
public class ClassLoaderMain {
public static void main(String[] args) throws ClassNotFoundException {
// 1、调用类中的静态方法和属性
// ClassLoaderTest.test();
// System.out.println(ClassLoaderTest.item);
// 2、使用反射加载某个类
// Class.forName("com.day0118.ClassLoaderTest");
// 3、初始化某个类的子类
new ClassLoaderChild();
// 4、当在类中运行main方法时
}
}